authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-10-19 21:48:21+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-10-25 20:48:05+02:00
log85b669d497641de383070353d50a6e4fd30abd49
treed43fbf603746364294020e50e0fac362aa109ab6
parent55c5da1b205dc5eddd2749381caacf28bea6f90e
signaturelock-open Commit is signed but in an unrecognized format.

wasm-linker: validate feature compatibility

Verifies disallowed and used/required features. After verifying, all errors will be emit to notify the user about incompatible features. When the user did not define any featureset, we infer the features from the linked objects instead.

2 files changed, 117 insertions(+), 14 deletions(-)

src/link/Wasm.zig+107
......@@ -651,6 +651,112 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void {
651651 }
652652}
653653
654fn 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
654760fn checkUndefinedSymbols(wasm: *const Wasm) !void {
655761 if (wasm.base.options.output_mode == .Obj) return;
656762
......@@ -2158,6 +2264,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
21582264 try wasm.resolveSymbolsInObject(@intCast(u16, object_index));
21592265 }
21602266
2267 try wasm.validateFeatures(arena);
21612268 try wasm.resolveSymbolsInArchives();
21622269 try wasm.checkUndefinedSymbols();
21632270
src/link/Wasm/types.zig+10-14
......@@ -183,18 +183,7 @@ pub const Feature = struct {
183183 /// Type of the feature, must be unique in the sequence of features.
184184 tag: Tag,
185185
186 pub const Tag = enum {
187 atomics,
188 bulk_memory,
189 exception_handling,
190 multivalue,
191 mutable_globals,
192 nontrapping_fptoint,
193 sign_ext,
194 simd128,
195 tail_call,
196 shared_mem,
197 };
186 pub const Tag = std.Target.wasm.Feature;
198187
199188 pub const Prefix = enum(u8) {
200189 used = '+',
......@@ -204,13 +193,18 @@ pub const Feature = struct {
204193
205194 pub fn toString(feature: Feature) []const u8 {
206195 return switch (feature.tag) {
196 .atomics => "atomics",
207197 .bulk_memory => "bulk-memory",
208198 .exception_handling => "exception-handling",
199 .extended_const => "extended-const",
200 .multivalue => "multivalue",
209201 .mutable_globals => "mutable-globals",
210202 .nontrapping_fptoint => "nontrapping-fptoint",
203 .reference_types => "reference-types",
204 .relaxed_simd => "relaxed-simd",
211205 .sign_ext => "sign-ext",
206 .simd128 => "simd128",
212207 .tail_call => "tail-call",
213 else => @tagName(feature),
214208 };
215209 }
216210
......@@ -225,11 +219,13 @@ pub const known_features = std.ComptimeStringMap(Feature.Tag, .{
225219 .{ "atomics", .atomics },
226220 .{ "bulk-memory", .bulk_memory },
227221 .{ "exception-handling", .exception_handling },
222 .{ "extended-const", .extended_const },
228223 .{ "multivalue", .multivalue },
229224 .{ "mutable-globals", .mutable_globals },
230225 .{ "nontrapping-fptoint", .nontrapping_fptoint },
226 .{ "reference-types", .reference_types },
227 .{ "relaxed-simd", .relaxed_simd },
231228 .{ "sign-ext", .sign_ext },
232229 .{ "simd128", .simd128 },
233230 .{ "tail-call", .tail_call },
234 .{ "shared-mem", .shared_mem },
235231});