Commit 85b669d497
src/link/Wasm/types.zig
@@ -183,18 +183,7 @@ pub const Feature = struct {
/// Type of the feature, must be unique in the sequence of features.
tag: Tag,
- pub const Tag = enum {
- atomics,
- bulk_memory,
- exception_handling,
- multivalue,
- mutable_globals,
- nontrapping_fptoint,
- sign_ext,
- simd128,
- tail_call,
- shared_mem,
- };
+ pub const Tag = std.Target.wasm.Feature;
pub const Prefix = enum(u8) {
used = '+',
@@ -204,13 +193,18 @@ pub const Feature = struct {
pub fn toString(feature: Feature) []const u8 {
return switch (feature.tag) {
+ .atomics => "atomics",
.bulk_memory => "bulk-memory",
.exception_handling => "exception-handling",
+ .extended_const => "extended-const",
+ .multivalue => "multivalue",
.mutable_globals => "mutable-globals",
.nontrapping_fptoint => "nontrapping-fptoint",
+ .reference_types => "reference-types",
+ .relaxed_simd => "relaxed-simd",
.sign_ext => "sign-ext",
+ .simd128 => "simd128",
.tail_call => "tail-call",
- else => @tagName(feature),
};
}
@@ -225,11 +219,13 @@ pub const known_features = std.ComptimeStringMap(Feature.Tag, .{
.{ "atomics", .atomics },
.{ "bulk-memory", .bulk_memory },
.{ "exception-handling", .exception_handling },
+ .{ "extended-const", .extended_const },
.{ "multivalue", .multivalue },
.{ "mutable-globals", .mutable_globals },
.{ "nontrapping-fptoint", .nontrapping_fptoint },
+ .{ "reference-types", .reference_types },
+ .{ "relaxed-simd", .relaxed_simd },
.{ "sign-ext", .sign_ext },
.{ "simd128", .simd128 },
.{ "tail-call", .tail_call },
- .{ "shared-mem", .shared_mem },
});
src/link/Wasm.zig
@@ -651,6 +651,112 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void {
}
}
+fn validateFeatures(wasm: *const Wasm, arena: Allocator) !void {
+ const cpu_features = wasm.base.options.target.cpu.features;
+ const infer = cpu_features.isEmpty(); // when the user did not define any features, we infer them from linked objects.
+ var allowed = std.AutoHashMap(std.Target.wasm.Feature, void).init(arena);
+ var used = std.AutoArrayHashMap(std.Target.wasm.Feature, []const u8).init(arena);
+ var disallowed = std.AutoHashMap(std.Target.wasm.Feature, []const u8).init(arena);
+ var required = std.AutoHashMap(std.Target.wasm.Feature, []const u8).init(arena);
+
+ // when false, we fail linking. We only verify this after a loop to catch all invalid features.
+ var valid_feature_set = true;
+
+ // When the user has given an explicit list of features to enable,
+ // we extract them and insert each into the 'allowed' list.
+ if (!infer) {
+ try allowed.ensureUnusedCapacity(std.Target.wasm.all_features.len);
+ // std.builtin.Type.EnumField
+ inline for (@typeInfo(std.Target.wasm.Feature).Enum.fields) |feature_field| {
+ if (cpu_features.isEnabled(feature_field.value)) {
+ allowed.putAssumeCapacityNoClobber(@intToEnum(std.Target.wasm.Feature, feature_field.value), {});
+ }
+ }
+ }
+
+ // extract all the used, disallowed and required features from each
+ // linked object file so we can test them.
+ for (wasm.objects.items) |object| {
+ for (object.features) |feature| {
+ switch (feature.prefix) {
+ .used => {
+ const gop = try used.getOrPut(feature.tag);
+ if (!gop.found_existing) {
+ gop.value_ptr.* = object.name;
+ }
+ },
+ .disallowed => {
+ const gop = try disallowed.getOrPut(feature.tag);
+ if (!gop.found_existing) {
+ gop.value_ptr.* = object.name;
+ }
+ },
+ .required => {
+ const gop = try required.getOrPut(feature.tag);
+ if (!gop.found_existing) {
+ gop.value_ptr.* = object.name;
+ }
+ const used_gop = try used.getOrPut(feature.tag);
+ if (!used_gop.found_existing) {
+ used_gop.value_ptr.* = object.name;
+ }
+ },
+ }
+ }
+ }
+
+ // when we infer the features, we allow each feature found in the 'used' set
+ // and insert it into the 'allowed' set. When features are not inferred,
+ // we validate that a used feature is allowed.
+ if (infer) try allowed.ensureUnusedCapacity(@intCast(u32, used.count()));
+ for (used.keys()) |used_feature, used_index| {
+ if (infer) {
+ allowed.putAssumeCapacityNoClobber(used_feature, {});
+ } else if (!allowed.contains(used_feature)) {
+ log.err("feature '{s}' not allowed, but used by linked object", .{@tagName(used_feature)});
+ log.err(" defined in '{s}'", .{used.values()[used_index]});
+ valid_feature_set = false;
+ }
+ }
+
+ if (!valid_feature_set) {
+ return error.InvalidFeatureSet;
+ }
+
+ // For each linked object, validate the required and disallowed features
+ for (wasm.objects.items) |object| {
+ var object_used_features = std.AutoHashMap(std.Target.wasm.Feature, void).init(arena);
+ try object_used_features.ensureTotalCapacity(@intCast(u32, object.features.len));
+ for (object.features) |feature| {
+ if (feature.prefix == .disallowed) continue; // already defined in 'disallowed' set.
+ // from here a feature is always used
+ if (disallowed.get(feature.tag)) |disallowed_object_name| {
+ log.err("feature '{s}' is disallowed, but used by linked object", .{@tagName(feature.tag)});
+ log.err(" disallowed by '{s}'", .{disallowed_object_name});
+ log.err(" used in '{s}'", .{object.name});
+ valid_feature_set = false;
+ }
+
+ object_used_features.putAssumeCapacity(feature.tag, {});
+ }
+
+ // validate the linked object file has each required feature
+ var required_it = required.iterator();
+ while (required_it.next()) |required_feature| {
+ if (!object_used_features.contains(required_feature.key_ptr.*)) {
+ log.err("feature '{s}' is required but not used in linked object", .{@tagName(required_feature.key_ptr.*)});
+ log.err(" required by '{s}'", .{required_feature.value_ptr.*});
+ log.err(" missing in '{s}'", .{object.name});
+ valid_feature_set = false;
+ }
+ }
+ }
+
+ if (!valid_feature_set) {
+ return error.InvalidFeatureSet;
+ }
+}
+
fn checkUndefinedSymbols(wasm: *const Wasm) !void {
if (wasm.base.options.output_mode == .Obj) return;
@@ -2158,6 +2264,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
try wasm.resolveSymbolsInObject(@intCast(u16, object_index));
}
+ try wasm.validateFeatures(arena);
try wasm.resolveSymbolsInArchives();
try wasm.checkUndefinedSymbols();