| ... | ... | @@ -651,6 +651,112 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void { |
| 651 | 651 | } |
| 652 | 652 | } |
| 653 | 653 | |
| 654 | fn validateFeatures(wasm: *const Wasm, arena: Allocator) !void { |
| 655 | const cpu_features = wasm.base.options.target.cpu.features; |
| 656 | const infer = cpu_features.isEmpty(); // when the user did not define any features, we infer them from linked objects. |
| 657 | var allowed = std.AutoHashMap(std.Target.wasm.Feature, void).init(arena); |
| 658 | var used = std.AutoArrayHashMap(std.Target.wasm.Feature, []const u8).init(arena); |
| 659 | var disallowed = std.AutoHashMap(std.Target.wasm.Feature, []const u8).init(arena); |
| 660 | var required = std.AutoHashMap(std.Target.wasm.Feature, []const u8).init(arena); |
| 661 | |
| 662 | // when false, we fail linking. We only verify this after a loop to catch all invalid features. |
| 663 | var valid_feature_set = true; |
| 664 | |
| 665 | // When the user has given an explicit list of features to enable, |
| 666 | // we extract them and insert each into the 'allowed' list. |
| 667 | if (!infer) { |
| 668 | try allowed.ensureUnusedCapacity(std.Target.wasm.all_features.len); |
| 669 | // std.builtin.Type.EnumField |
| 670 | inline for (@typeInfo(std.Target.wasm.Feature).Enum.fields) |feature_field| { |
| 671 | if (cpu_features.isEnabled(feature_field.value)) { |
| 672 | allowed.putAssumeCapacityNoClobber(@intToEnum(std.Target.wasm.Feature, feature_field.value), {}); |
| 673 | } |
| 674 | } |
| 675 | } |
| 676 | |
| 677 | // extract all the used, disallowed and required features from each |
| 678 | // linked object file so we can test them. |
| 679 | for (wasm.objects.items) |object| { |
| 680 | for (object.features) |feature| { |
| 681 | switch (feature.prefix) { |
| 682 | .used => { |
| 683 | const gop = try used.getOrPut(feature.tag); |
| 684 | if (!gop.found_existing) { |
| 685 | gop.value_ptr.* = object.name; |
| 686 | } |
| 687 | }, |
| 688 | .disallowed => { |
| 689 | const gop = try disallowed.getOrPut(feature.tag); |
| 690 | if (!gop.found_existing) { |
| 691 | gop.value_ptr.* = object.name; |
| 692 | } |
| 693 | }, |
| 694 | .required => { |
| 695 | const gop = try required.getOrPut(feature.tag); |
| 696 | if (!gop.found_existing) { |
| 697 | gop.value_ptr.* = object.name; |
| 698 | } |
| 699 | const used_gop = try used.getOrPut(feature.tag); |
| 700 | if (!used_gop.found_existing) { |
| 701 | used_gop.value_ptr.* = object.name; |
| 702 | } |
| 703 | }, |
| 704 | } |
| 705 | } |
| 706 | } |
| 707 | |
| 708 | // when we infer the features, we allow each feature found in the 'used' set |
| 709 | // and insert it into the 'allowed' set. When features are not inferred, |
| 710 | // we validate that a used feature is allowed. |
| 711 | if (infer) try allowed.ensureUnusedCapacity(@intCast(u32, used.count())); |
| 712 | for (used.keys()) |used_feature, used_index| { |
| 713 | if (infer) { |
| 714 | allowed.putAssumeCapacityNoClobber(used_feature, {}); |
| 715 | } else if (!allowed.contains(used_feature)) { |
| 716 | log.err("feature '{s}' not allowed, but used by linked object", .{@tagName(used_feature)}); |
| 717 | log.err(" defined in '{s}'", .{used.values()[used_index]}); |
| 718 | valid_feature_set = false; |
| 719 | } |
| 720 | } |
| 721 | |
| 722 | if (!valid_feature_set) { |
| 723 | return error.InvalidFeatureSet; |
| 724 | } |
| 725 | |
| 726 | // For each linked object, validate the required and disallowed features |
| 727 | for (wasm.objects.items) |object| { |
| 728 | var object_used_features = std.AutoHashMap(std.Target.wasm.Feature, void).init(arena); |
| 729 | try object_used_features.ensureTotalCapacity(@intCast(u32, object.features.len)); |
| 730 | for (object.features) |feature| { |
| 731 | if (feature.prefix == .disallowed) continue; // already defined in 'disallowed' set. |
| 732 | // from here a feature is always used |
| 733 | if (disallowed.get(feature.tag)) |disallowed_object_name| { |
| 734 | log.err("feature '{s}' is disallowed, but used by linked object", .{@tagName(feature.tag)}); |
| 735 | log.err(" disallowed by '{s}'", .{disallowed_object_name}); |
| 736 | log.err(" used in '{s}'", .{object.name}); |
| 737 | valid_feature_set = false; |
| 738 | } |
| 739 | |
| 740 | object_used_features.putAssumeCapacity(feature.tag, {}); |
| 741 | } |
| 742 | |
| 743 | // validate the linked object file has each required feature |
| 744 | var required_it = required.iterator(); |
| 745 | while (required_it.next()) |required_feature| { |
| 746 | if (!object_used_features.contains(required_feature.key_ptr.*)) { |
| 747 | log.err("feature '{s}' is required but not used in linked object", .{@tagName(required_feature.key_ptr.*)}); |
| 748 | log.err(" required by '{s}'", .{required_feature.value_ptr.*}); |
| 749 | log.err(" missing in '{s}'", .{object.name}); |
| 750 | valid_feature_set = false; |
| 751 | } |
| 752 | } |
| 753 | } |
| 754 | |
| 755 | if (!valid_feature_set) { |
| 756 | return error.InvalidFeatureSet; |
| 757 | } |
| 758 | } |
| 759 | |
| 654 | 760 | fn checkUndefinedSymbols(wasm: *const Wasm) !void { |
| 655 | 761 | if (wasm.base.options.output_mode == .Obj) return; |
| 656 | 762 | |
| ... | ... | @@ -2158,6 +2264,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod |
| 2158 | 2264 | try wasm.resolveSymbolsInObject(@intCast(u16, object_index)); |
| 2159 | 2265 | } |
| 2160 | 2266 | |
| 2267 | try wasm.validateFeatures(arena); |
| 2161 | 2268 | try wasm.resolveSymbolsInArchives(); |
| 2162 | 2269 | try wasm.checkUndefinedSymbols(); |
| 2163 | 2270 | |