authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-10-25 21:16:51+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-10-25 21:16:51+02:00
logc0710b0c42716bb7173b9fcc2785f9bf5175ae0f
tree33144c17bd3606709342edde8db3b2549e1bccf8
parent3d1d19f3877190db42544acf9e0ed26784ba82ba
signaturelock-open Commit is signed but in an unrecognized format.

use fixed-size arrays for feature lists

Considering all possible features are known by the linker during compile-time, we can create arrays on the stack instead of dynamically allocating hash maps. We use a simple bitset to determine whether a feature is enabled or not, and from which object file it originates. This allows us to make feature validation slightly faster and use less runtime memory. In the future this could be enhanced further by having a single array instead with a more sophisticated bitset.

3 files changed, 55 insertions(+), 69 deletions(-)

src/link.zig+1
......@@ -696,6 +696,7 @@ pub const File = struct {
696696 GlobalTypeMismatch,
697697 InvalidCharacter,
698698 InvalidEntryKind,
699 InvalidFeatureSet,
699700 InvalidFormat,
700701 InvalidIndex,
701702 InvalidMagicByte,
src/link/Wasm.zig+35-50
......@@ -653,16 +653,17 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void {
653653
654654fn validateFeatures(
655655 wasm: *const Wasm,
656 arena: Allocator,
657656 to_emit: *[@typeInfo(types.Feature.Tag).Enum.fields.len]bool,
658657 emit_features_count: *u32,
659658) !void {
660659 const cpu_features = wasm.base.options.target.cpu.features;
661660 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);
663 var used = std.AutoArrayHashMap(types.Feature.Tag, []const u8).init(arena);
664 var disallowed = std.AutoHashMap(types.Feature.Tag, []const u8).init(arena);
665 var required = std.AutoHashMap(types.Feature.Tag, []const u8).init(arena);
661 const known_features_count = @typeInfo(types.Feature.Tag).Enum.fields.len;
662
663 var allowed = [_]bool{false} ** known_features_count;
664 var used = [_]u17{0} ** known_features_count;
665 var disallowed = [_]u17{0} ** known_features_count;
666 var required = [_]u17{0} ** known_features_count;
666667
667668 // when false, we fail linking. We only verify this after a loop to catch all invalid features.
668669 var valid_feature_set = true;
......@@ -670,41 +671,29 @@ fn validateFeatures(
670671 // When the user has given an explicit list of features to enable,
671672 // we extract them and insert each into the 'allowed' list.
672673 if (!infer) {
673 try allowed.ensureUnusedCapacity(std.Target.wasm.all_features.len);
674 // std.builtin.Type.EnumField
675674 inline for (@typeInfo(std.Target.wasm.Feature).Enum.fields) |feature_field| {
676675 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;
678678 }
679679 }
680680 }
681681
682682 // extract all the used, disallowed and required features from each
683683 // linked object file so we can test them.
684 for (wasm.objects.items) |object| {
684 for (wasm.objects.items) |object, object_index| {
685685 for (object.features) |feature| {
686 const value = @intCast(u16, object_index) << 1 | @as(u1, 1);
686687 switch (feature.prefix) {
687688 .used => {
688 const gop = try used.getOrPut(feature.tag);
689 if (!gop.found_existing) {
690 gop.value_ptr.* = object.name;
691 }
689 used[@enumToInt(feature.tag)] = value;
692690 },
693691 .disallowed => {
694 const gop = try disallowed.getOrPut(feature.tag);
695 if (!gop.found_existing) {
696 gop.value_ptr.* = object.name;
697 }
692 disallowed[@enumToInt(feature.tag)] = value;
698693 },
699694 .required => {
700 const gop = try required.getOrPut(feature.tag);
701 if (!gop.found_existing) {
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 }
695 required[@enumToInt(feature.tag)] = value;
696 used[@enumToInt(feature.tag)] = value;
708697 },
709698 }
710699 }
......@@ -713,13 +702,14 @@ fn validateFeatures(
713702 // when we infer the features, we allow each feature found in the 'used' set
714703 // and insert it into the 'allowed' set. When features are not inferred,
715704 // we validate that a used feature is allowed.
716 if (infer) try allowed.ensureUnusedCapacity(@intCast(u32, used.count()));
717 for (used.keys()) |used_feature, used_index| {
705 for (used) |used_set, used_index| {
706 const is_enabled = @truncate(u1, used_set) != 0;
718707 if (infer) {
719 allowed.putAssumeCapacityNoClobber(used_feature, {});
720 } else if (!allowed.contains(used_feature)) {
721 log.err("feature '{s}' not allowed, but used by linked object", .{@tagName(used_feature)});
722 log.err(" defined in '{s}'", .{used.values()[used_index]});
708 allowed[used_index] = is_enabled;
709 emit_features_count.* += @boolToInt(is_enabled);
710 } else if (is_enabled and !allowed[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});
723713 valid_feature_set = false;
724714 }
725715 }
......@@ -730,27 +720,27 @@ fn validateFeatures(
730720
731721 // For each linked object, validate the required and disallowed features
732722 for (wasm.objects.items) |object| {
733 var object_used_features = std.AutoHashMap(types.Feature.Tag, void).init(arena);
734 try object_used_features.ensureTotalCapacity(@intCast(u32, object.features.len));
723 var object_used_features = [_]bool{false} ** known_features_count;
735724 for (object.features) |feature| {
736725 if (feature.prefix == .disallowed) continue; // already defined in 'disallowed' set.
737726 // from here a feature is always used
738 if (disallowed.get(feature.tag)) |disallowed_object_name| {
739 log.err("feature '{s}' is disallowed, but used by linked object", .{@tagName(feature.tag)});
740 log.err(" disallowed by '{s}'", .{disallowed_object_name});
727 const disallowed_feature = disallowed[@enumToInt(feature.tag)];
728 if (@truncate(u1, disallowed_feature) != 0) {
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});
741731 log.err(" used in '{s}'", .{object.name});
742732 valid_feature_set = false;
743733 }
744734
745 object_used_features.putAssumeCapacity(feature.tag, {});
735 object_used_features[@enumToInt(feature.tag)] = true;
746736 }
747737
748738 // validate the linked object file has each required feature
749 var required_it = required.iterator();
750 while (required_it.next()) |required_feature| {
751 if (!object_used_features.contains(required_feature.key_ptr.*)) {
752 log.err("feature '{s}' is required but not used in linked object", .{@tagName(required_feature.key_ptr.*)});
753 log.err(" required by '{s}'", .{required_feature.value_ptr.*});
739 for (required) |required_feature, feature_index| {
740 const is_required = @truncate(u1, required_feature) != 0;
741 if (is_required and !object_used_features[feature_index]) {
742 log.err("feature '{s}' is required but not used in linked object", .{(@intToEnum(types.Feature.Tag, feature_index)).toString()});
743 log.err(" required by '{s}'", .{wasm.objects.items[required_feature >> 1].name});
754744 log.err(" missing in '{s}'", .{object.name});
755745 valid_feature_set = false;
756746 }
......@@ -761,12 +751,7 @@ fn validateFeatures(
761751 return error.InvalidFeatureSet;
762752 }
763753
764 if (allowed.count() > 0) {
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 }
754 to_emit.* = allowed;
770755}
771756
772757fn checkUndefinedSymbols(wasm: *const Wasm) !void {
......@@ -2278,7 +2263,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
22782263
22792264 var emit_features_count: u32 = 0;
22802265 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);
22822267 try wasm.resolveSymbolsInArchives();
22832268 try wasm.checkUndefinedSymbols();
22842269
......@@ -2832,7 +2817,7 @@ fn emitFeaturesSection(binary_bytes: *std.ArrayList(u8), enabled_features: []con
28322817 if (enabled) {
28332818 const feature: types.Feature = .{ .prefix = .used, .tag = @intToEnum(types.Feature.Tag, feature_index) };
28342819 try leb.writeULEB128(writer, @enumToInt(feature.prefix));
2835 const string = feature.toString();
2820 const string = feature.tag.toString();
28362821 try leb.writeULEB128(writer, @intCast(u32, string.len));
28372822 try writer.writeAll(string);
28382823 }
src/link/Wasm/types.zig+19-19
......@@ -203,6 +203,24 @@ pub const Feature = struct {
203203 pub fn fromCpuFeature(feature: std.Target.wasm.Feature) Tag {
204204 return @intToEnum(Tag, @enumToInt(feature));
205205 }
206
207 pub fn toString(tag: Tag) []const u8 {
208 return switch (tag) {
209 .atomics => "atomics",
210 .bulk_memory => "bulk-memory",
211 .exception_handling => "exception-handling",
212 .extended_const => "extended-const",
213 .multivalue => "multivalue",
214 .mutable_globals => "mutable-globals",
215 .nontrapping_fptoint => "nontrapping-fptoint",
216 .reference_types => "reference-types",
217 .relaxed_simd => "relaxed-simd",
218 .sign_ext => "sign-ext",
219 .simd128 => "simd128",
220 .tail_call => "tail-call",
221 .shared_mem => "shared-mem",
222 };
223 }
206224 };
207225
208226 pub const Prefix = enum(u8) {
......@@ -211,28 +229,10 @@ pub const Feature = struct {
211229 required = '=',
212230 };
213231
214 pub fn toString(feature: Feature) []const u8 {
215 return switch (feature.tag) {
216 .atomics => "atomics",
217 .bulk_memory => "bulk-memory",
218 .exception_handling => "exception-handling",
219 .extended_const => "extended-const",
220 .multivalue => "multivalue",
221 .mutable_globals => "mutable-globals",
222 .nontrapping_fptoint => "nontrapping-fptoint",
223 .reference_types => "reference-types",
224 .relaxed_simd => "relaxed-simd",
225 .sign_ext => "sign-ext",
226 .simd128 => "simd128",
227 .tail_call => "tail-call",
228 .shared_mem => "shared-mem",
229 };
230 }
231
232232 pub fn format(feature: Feature, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void {
233233 _ = opt;
234234 _ = fmt;
235 try writer.print("{c} {s}", .{ feature.prefix, feature.toString() });
235 try writer.print("{c} {s}", .{ feature.prefix, feature.tag.toString() });
236236 }
237237};
238238