| ... | @@ -653,16 +653,17 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void { | ... | @@ -653,16 +653,17 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void { |
| 653 | | 653 | |
| 654 | fn validateFeatures( | 654 | fn validateFeatures( |
| 655 | wasm: *const Wasm, | 655 | wasm: *const Wasm, |
| 656 | arena: Allocator, | | |
| 657 | to_emit: *[@typeInfo(types.Feature.Tag).Enum.fields.len]bool, | 656 | to_emit: *[@typeInfo(types.Feature.Tag).Enum.fields.len]bool, |
| 658 | emit_features_count: *u32, | 657 | emit_features_count: *u32, |
| 659 | ) !void { | 658 | ) !void { |
| 660 | const cpu_features = wasm.base.options.target.cpu.features; | 659 | const cpu_features = wasm.base.options.target.cpu.features; |
| 661 | const infer = cpu_features.isEmpty(); // when the user did not define any features, we infer them from linked objects. | 660 | const infer = cpu_features.isEmpty(); // when the user did not define any features, we infer them from linked objects. |
| 662 | var allowed = std.AutoHashMap(types.Feature.Tag, void).init(arena); | 661 | const known_features_count = @typeInfo(types.Feature.Tag).Enum.fields.len; |
| 663 | var used = std.AutoArrayHashMap(types.Feature.Tag, []const u8).init(arena); | 662 | |
| 664 | var disallowed = std.AutoHashMap(types.Feature.Tag, []const u8).init(arena); | 663 | var allowed = [_]bool{false} ** known_features_count; |
| 665 | var required = std.AutoHashMap(types.Feature.Tag, []const u8).init(arena); | 664 | var used = [_]u17{0} ** known_features_count; |
| | 665 | var disallowed = [_]u17{0} ** known_features_count; |
| | 666 | var required = [_]u17{0} ** known_features_count; |
| 666 | | 667 | |
| 667 | // when false, we fail linking. We only verify this after a loop to catch all invalid features. | 668 | // when false, we fail linking. We only verify this after a loop to catch all invalid features. |
| 668 | var valid_feature_set = true; | 669 | var valid_feature_set = true; |
| ... | @@ -670,41 +671,29 @@ fn validateFeatures( | ... | @@ -670,41 +671,29 @@ fn validateFeatures( |
| 670 | // When the user has given an explicit list of features to enable, | 671 | // When the user has given an explicit list of features to enable, |
| 671 | // we extract them and insert each into the 'allowed' list. | 672 | // we extract them and insert each into the 'allowed' list. |
| 672 | if (!infer) { | 673 | if (!infer) { |
| 673 | try allowed.ensureUnusedCapacity(std.Target.wasm.all_features.len); | | |
| 674 | // std.builtin.Type.EnumField | | |
| 675 | inline for (@typeInfo(std.Target.wasm.Feature).Enum.fields) |feature_field| { | 674 | inline for (@typeInfo(std.Target.wasm.Feature).Enum.fields) |feature_field| { |
| 676 | if (cpu_features.isEnabled(feature_field.value)) { | 675 | if (cpu_features.isEnabled(feature_field.value)) { |
| 677 | allowed.putAssumeCapacityNoClobber(@intToEnum(types.Feature.Tag, feature_field.value), {}); | 676 | allowed[feature_field.value] = true; |
| | 677 | emit_features_count.* += 1; |
| 678 | } | 678 | } |
| 679 | } | 679 | } |
| 680 | } | 680 | } |
| 681 | | 681 | |
| 682 | // extract all the used, disallowed and required features from each | 682 | // extract all the used, disallowed and required features from each |
| 683 | // linked object file so we can test them. | 683 | // linked object file so we can test them. |
| 684 | for (wasm.objects.items) |object| { | 684 | for (wasm.objects.items) |object, object_index| { |
| 685 | for (object.features) |feature| { | 685 | for (object.features) |feature| { |
| | 686 | const value = @intCast(u16, object_index) << 1 | @as(u1, 1); |
| 686 | switch (feature.prefix) { | 687 | switch (feature.prefix) { |
| 687 | .used => { | 688 | .used => { |
| 688 | const gop = try used.getOrPut(feature.tag); | 689 | used[@enumToInt(feature.tag)] = value; |
| 689 | if (!gop.found_existing) { | | |
| 690 | gop.value_ptr.* = object.name; | | |
| 691 | } | | |
| 692 | }, | 690 | }, |
| 693 | .disallowed => { | 691 | .disallowed => { |
| 694 | const gop = try disallowed.getOrPut(feature.tag); | 692 | disallowed[@enumToInt(feature.tag)] = value; |
| 695 | if (!gop.found_existing) { | | |
| 696 | gop.value_ptr.* = object.name; | | |
| 697 | } | | |
| 698 | }, | 693 | }, |
| 699 | .required => { | 694 | .required => { |
| 700 | const gop = try required.getOrPut(feature.tag); | 695 | required[@enumToInt(feature.tag)] = value; |
| 701 | if (!gop.found_existing) { | 696 | used[@enumToInt(feature.tag)] = value; |
| 702 | gop.value_ptr.* = object.name; | | |
| 703 | } | | |
| 704 | const used_gop = try used.getOrPut(feature.tag); | | |
| 705 | if (!used_gop.found_existing) { | | |
| 706 | used_gop.value_ptr.* = object.name; | | |
| 707 | } | | |
| 708 | }, | 697 | }, |
| 709 | } | 698 | } |
| 710 | } | 699 | } |
| ... | @@ -713,13 +702,14 @@ fn validateFeatures( | ... | @@ -713,13 +702,14 @@ fn validateFeatures( |
| 713 | // when we infer the features, we allow each feature found in the 'used' set | 702 | // when we infer the features, we allow each feature found in the 'used' set |
| 714 | // and insert it into the 'allowed' set. When features are not inferred, | 703 | // and insert it into the 'allowed' set. When features are not inferred, |
| 715 | // we validate that a used feature is allowed. | 704 | // we validate that a used feature is allowed. |
| 716 | if (infer) try allowed.ensureUnusedCapacity(@intCast(u32, used.count())); | 705 | for (used) |used_set, used_index| { |
| 717 | for (used.keys()) |used_feature, used_index| { | 706 | const is_enabled = @truncate(u1, used_set) != 0; |
| 718 | if (infer) { | 707 | if (infer) { |
| 719 | allowed.putAssumeCapacityNoClobber(used_feature, {}); | 708 | allowed[used_index] = is_enabled; |
| 720 | } else if (!allowed.contains(used_feature)) { | 709 | emit_features_count.* += @boolToInt(is_enabled); |
| 721 | log.err("feature '{s}' not allowed, but used by linked object", .{@tagName(used_feature)}); | 710 | } else if (is_enabled and !allowed[used_index]) { |
| 722 | log.err(" defined in '{s}'", .{used.values()[used_index]}); | 711 | log.err("feature '{s}' not allowed, but used by linked object", .{(@intToEnum(types.Feature.Tag, used_index)).toString()}); |
| | 712 | log.err(" defined in '{s}'", .{wasm.objects.items[used_set >> 1].name}); |
| 723 | valid_feature_set = false; | 713 | valid_feature_set = false; |
| 724 | } | 714 | } |
| 725 | } | 715 | } |
| ... | @@ -730,27 +720,27 @@ fn validateFeatures( | ... | @@ -730,27 +720,27 @@ fn validateFeatures( |
| 730 | | 720 | |
| 731 | // For each linked object, validate the required and disallowed features | 721 | // For each linked object, validate the required and disallowed features |
| 732 | for (wasm.objects.items) |object| { | 722 | for (wasm.objects.items) |object| { |
| 733 | var object_used_features = std.AutoHashMap(types.Feature.Tag, void).init(arena); | 723 | var object_used_features = [_]bool{false} ** known_features_count; |
| 734 | try object_used_features.ensureTotalCapacity(@intCast(u32, object.features.len)); | | |
| 735 | for (object.features) |feature| { | 724 | for (object.features) |feature| { |
| 736 | if (feature.prefix == .disallowed) continue; // already defined in 'disallowed' set. | 725 | if (feature.prefix == .disallowed) continue; // already defined in 'disallowed' set. |
| 737 | // from here a feature is always used | 726 | // from here a feature is always used |
| 738 | if (disallowed.get(feature.tag)) |disallowed_object_name| { | 727 | const disallowed_feature = disallowed[@enumToInt(feature.tag)]; |
| 739 | log.err("feature '{s}' is disallowed, but used by linked object", .{@tagName(feature.tag)}); | 728 | if (@truncate(u1, disallowed_feature) != 0) { |
| 740 | log.err(" disallowed by '{s}'", .{disallowed_object_name}); | 729 | log.err("feature '{s}' is disallowed, but used by linked object", .{feature.tag.toString()}); |
| | 730 | log.err(" disallowed by '{s}'", .{wasm.objects.items[disallowed_feature >> 1].name}); |
| 741 | log.err(" used in '{s}'", .{object.name}); | 731 | log.err(" used in '{s}'", .{object.name}); |
| 742 | valid_feature_set = false; | 732 | valid_feature_set = false; |
| 743 | } | 733 | } |
| 744 | | 734 | |
| 745 | object_used_features.putAssumeCapacity(feature.tag, {}); | 735 | object_used_features[@enumToInt(feature.tag)] = true; |
| 746 | } | 736 | } |
| 747 | | 737 | |
| 748 | // validate the linked object file has each required feature | 738 | // validate the linked object file has each required feature |
| 749 | var required_it = required.iterator(); | 739 | for (required) |required_feature, feature_index| { |
| 750 | while (required_it.next()) |required_feature| { | 740 | const is_required = @truncate(u1, required_feature) != 0; |
| 751 | if (!object_used_features.contains(required_feature.key_ptr.*)) { | 741 | if (is_required and !object_used_features[feature_index]) { |
| 752 | log.err("feature '{s}' is required but not used in linked object", .{@tagName(required_feature.key_ptr.*)}); | 742 | log.err("feature '{s}' is required but not used in linked object", .{(@intToEnum(types.Feature.Tag, feature_index)).toString()}); |
| 753 | log.err(" required by '{s}'", .{required_feature.value_ptr.*}); | 743 | log.err(" required by '{s}'", .{wasm.objects.items[required_feature >> 1].name}); |
| 754 | log.err(" missing in '{s}'", .{object.name}); | 744 | log.err(" missing in '{s}'", .{object.name}); |
| 755 | valid_feature_set = false; | 745 | valid_feature_set = false; |
| 756 | } | 746 | } |
| ... | @@ -761,12 +751,7 @@ fn validateFeatures( | ... | @@ -761,12 +751,7 @@ fn validateFeatures( |
| 761 | return error.InvalidFeatureSet; | 751 | return error.InvalidFeatureSet; |
| 762 | } | 752 | } |
| 763 | | 753 | |
| 764 | if (allowed.count() > 0) { | 754 | to_emit.* = allowed; |
| 765 | emit_features_count.* = allowed.count(); | | |
| 766 | for (to_emit) |*feature_enabled, feature_index| { | | |
| 767 | feature_enabled.* = allowed.contains(@intToEnum(types.Feature.Tag, feature_index)); | | |
| 768 | } | | |
| 769 | } | | |
| 770 | } | 755 | } |
| 771 | | 756 | |
| 772 | fn checkUndefinedSymbols(wasm: *const Wasm) !void { | 757 | fn checkUndefinedSymbols(wasm: *const Wasm) !void { |
| ... | @@ -2278,7 +2263,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod | ... | @@ -2278,7 +2263,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod |
| 2278 | | 2263 | |
| 2279 | var emit_features_count: u32 = 0; | 2264 | var emit_features_count: u32 = 0; |
| 2280 | var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined; | 2265 | var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined; |
| 2281 | try wasm.validateFeatures(arena, &enabled_features, &emit_features_count); | 2266 | try wasm.validateFeatures(&enabled_features, &emit_features_count); |
| 2282 | try wasm.resolveSymbolsInArchives(); | 2267 | try wasm.resolveSymbolsInArchives(); |
| 2283 | try wasm.checkUndefinedSymbols(); | 2268 | try wasm.checkUndefinedSymbols(); |
| 2284 | | 2269 | |
| ... | @@ -2832,7 +2817,7 @@ fn emitFeaturesSection(binary_bytes: *std.ArrayList(u8), enabled_features: []con | ... | @@ -2832,7 +2817,7 @@ fn emitFeaturesSection(binary_bytes: *std.ArrayList(u8), enabled_features: []con |
| 2832 | if (enabled) { | 2817 | if (enabled) { |
| 2833 | const feature: types.Feature = .{ .prefix = .used, .tag = @intToEnum(types.Feature.Tag, feature_index) }; | 2818 | const feature: types.Feature = .{ .prefix = .used, .tag = @intToEnum(types.Feature.Tag, feature_index) }; |
| 2834 | try leb.writeULEB128(writer, @enumToInt(feature.prefix)); | 2819 | try leb.writeULEB128(writer, @enumToInt(feature.prefix)); |
| 2835 | const string = feature.toString(); | 2820 | const string = feature.tag.toString(); |
| 2836 | try leb.writeULEB128(writer, @intCast(u32, string.len)); | 2821 | try leb.writeULEB128(writer, @intCast(u32, string.len)); |
| 2837 | try writer.writeAll(string); | 2822 | try writer.writeAll(string); |
| 2838 | } | 2823 | } |