Commit 777bcbf968

Luuk de Gram <luuk@degram.dev>
2022-10-20 16:58:00
wasm-linker: emit `target_features` section
When the result is not being stripped, we emit the `target_features` section based on all the used features. This includes features inferred from linked object files. Considering we know all possible features upfront, we can use an array and therefore do not have to dynamically allocate memory. Using this trick we can also easily order all features based the same ordering as found in `std.Target.wasm` which is the same ordering used by LLVM and the like.
1 parent 85b669d
Changed files (1)
src
src/link/Wasm.zig
@@ -651,7 +651,12 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void {
     }
 }
 
-fn validateFeatures(wasm: *const Wasm, arena: Allocator) !void {
+fn validateFeatures(
+    wasm: *const Wasm,
+    arena: Allocator,
+    to_emit: *[@typeInfo(std.Target.wasm.Feature).Enum.fields.len]bool,
+    emit_features_count: *u32,
+) !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);
@@ -755,6 +760,13 @@ fn validateFeatures(wasm: *const Wasm, arena: Allocator) !void {
     if (!valid_feature_set) {
         return error.InvalidFeatureSet;
     }
+
+    if (allowed.count() > 0) {
+        emit_features_count.* = allowed.count();
+        for (to_emit) |*feature_enabled, feature_index| {
+            feature_enabled.* = allowed.contains(@intToEnum(std.Target.wasm.Feature, feature_index));
+        }
+    }
 }
 
 fn checkUndefinedSymbols(wasm: *const Wasm) !void {
@@ -2264,7 +2276,9 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
         try wasm.resolveSymbolsInObject(@intCast(u16, object_index));
     }
 
-    try wasm.validateFeatures(arena);
+    var emit_features_count: u32 = 0;
+    var enabled_features: [@typeInfo(std.Target.wasm.Feature).Enum.fields.len]bool = undefined;
+    try wasm.validateFeatures(arena, &enabled_features, &emit_features_count);
     try wasm.resolveSymbolsInArchives();
     try wasm.checkUndefinedSymbols();
 
@@ -2710,6 +2724,9 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
         }
 
         try emitProducerSection(&binary_bytes);
+        if (emit_features_count > 0) {
+            try emitFeaturesSection(&binary_bytes, &enabled_features, emit_features_count);
+        }
     }
 
     // Only when writing all sections executed properly we write the magic
@@ -2802,6 +2819,32 @@ fn emitProducerSection(binary_bytes: *std.ArrayList(u8)) !void {
     );
 }
 
+fn emitFeaturesSection(binary_bytes: *std.ArrayList(u8), enabled_features: []const bool, features_count: u32) !void {
+    const header_offset = try reserveCustomSectionHeader(binary_bytes);
+
+    const writer = binary_bytes.writer();
+    const target_features = "target_features";
+    try leb.writeULEB128(writer, @intCast(u32, target_features.len));
+    try writer.writeAll(target_features);
+
+    try leb.writeULEB128(writer, features_count);
+    for (enabled_features) |enabled, feature_index| {
+        if (enabled) {
+            const feature: types.Feature = .{ .prefix = .used, .tag = @intToEnum(types.Feature.Tag, feature_index) };
+            try leb.writeULEB128(writer, @enumToInt(feature.prefix));
+            const string = feature.toString();
+            try leb.writeULEB128(writer, @intCast(u32, string.len));
+            try writer.writeAll(string);
+        }
+    }
+
+    try writeCustomSectionHeader(
+        binary_bytes.items,
+        header_offset,
+        @intCast(u32, binary_bytes.items.len - header_offset - 6),
+    );
+}
+
 fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem.Allocator) !void {
     const Name = struct {
         index: u32,