Commit 9b3c8fd3a8

Luuk de Gram <luuk@degram.dev>
2024-01-14 17:24:18
wasm: initialize a `ZigObject` when required
When we have a ZigCompileUnit and don't use LLVM, we initialize the ZigObject which will encapsulate the Zig Module as an object file in- memory. During initialization we also create symbols which the object will need such as the stack pointer.
1 parent e54177e
Changed files (2)
src
src/link/Wasm/ZigObject.zig
@@ -3,6 +3,7 @@
 //! and any relocations that may have been emitted.
 //! Think about this as fake in-memory Object file for the Zig module.
 
+path: []const u8,
 /// List of all `Decl` that are currently alive.
 /// Each index maps to the corresponding `Atom.Index`.
 decls: std.AutoHashMapUnmanaged(InternPool.DeclIndex, Atom.Index) = .{},
@@ -22,7 +23,7 @@ global_syms: std.AutoHashMapUnmanaged(u32, u32) = .{},
 /// List of symbol indexes which are free to be used.
 symbols_free_list: std.ArrayListUnmanaged(u32) = .{},
 /// Extra metadata about the linking section, such as alignment of segments and their name.
-segment_info: std.ArrayListUnmanage(types.Segment) = &.{},
+segment_info: std.ArrayListUnmanaged(types.Segment) = .{},
 /// File encapsulated string table, used to deduplicate strings within the generated file.
 string_table: StringTable = .{},
 /// Map for storing anonymous declarations. Each anonymous decl maps to its Atom's index.
@@ -72,6 +73,30 @@ debug_str_index: ?u32 = null,
 /// The index of the segment representing the custom '.debug_pubtypes' section.
 debug_abbrev_index: ?u32 = null,
 
+/// Initializes the `ZigObject` with initial symbols.
+pub fn init(zig_object: *ZigObject, wasm_file: *Wasm) !void {
+    // Initialize an undefined global with the name __stack_pointer. Codegen will use
+    // this to generate relocations when moving the stack pointer. This symbol will be
+    // resolved automatically by the final linking stage.
+    try zig_object.createStackPointer(wasm_file);
+
+    // TODO: Initialize debug information when we reimplement Dwarf support.
+}
+
+fn createStackPointer(zig_object: *ZigObject, wasm_file: *Wasm) !void {
+    const gpa = wasm_file.base.comp.gpa;
+    const sym_index = try zig_object.getGlobalSymbol(gpa, "__stack_pointer", .global);
+    zig_object.symbols.items[sym_index].index = zig_object.imported_globals_count;
+    const is_wasm32 = wasm_file.base.comp.root_mod.resolved_target.result.cpu.arch == .wasm32;
+    try zig_object.imports.putNoClobber(gpa, sym_index, .{
+        .name = zig_object.symbols.items[sym_index].name,
+        .module_name = try zig_object.string_table.insert(gpa, wasm_file.host_name),
+        .kind = .{ .global = .{ .valtype = if (is_wasm32) .i32 else .i64, .mutable = true } },
+    });
+    zig_object.imported_globals_count += 1;
+    zig_object.stack_pointer_sym = sym_index;
+}
+
 /// Frees and invalidates all memory of the incrementally compiled Zig module.
 /// It is illegal behavior to access the `ZigObject` after calling `deinit`.
 pub fn deinit(zig_object: *ZigObject, gpa: std.mem.Allocator) void {
@@ -113,6 +138,7 @@ pub fn deinit(zig_object: *ZigObject, gpa: std.mem.Allocator) void {
     if (zig_object.dwarf) |*dwarf| {
         dwarf.deinit();
     }
+    gpa.free(zig_object.path);
     zig_object.* = undefined;
 }
 
@@ -531,32 +557,31 @@ pub fn addOrUpdateImport(
 /// such as an exported or imported symbol.
 /// If the symbol does not yet exist, creates a new one symbol instead
 /// and then returns the index to it.
-pub fn getGlobalSymbol(zig_object: *ZigObject, wasm_file: *Wasm, name: []const u8) !u32 {
-    const gpa = wasm_file.base.comp.gpa;
+pub fn getGlobalSymbol(zig_object: *ZigObject, gpa: std.mem.Allocator, name: []const u8, tag: Symbol.Tag) !u32 {
     const name_index = try zig_object.string_table.insert(gpa, name);
     const gop = try zig_object.global_syms.getOrPut(gpa, name_index);
     if (gop.found_existing) {
-        return gop.value_ptr.index;
+        return gop.value_ptr.*;
     }
 
     var symbol: Symbol = .{
         .name = name_index,
         .flags = 0,
-        .index = undefined, // index to type will be set after merging function symbols
-        .tag = .function,
-        .virtual_address = undefined,
+        .index = undefined, // index to type will be set after merging symbols
+        .tag = tag,
+        .virtual_address = std.math.maxInt(u32),
     };
     symbol.setGlobal(true);
     symbol.setUndefined(true);
 
-    const sym_index = if (zig_object.symbol.popOrNull()) |index| index else blk: {
+    const sym_index = if (zig_object.symbols_free_list.popOrNull()) |index| index else blk: {
         const index: u32 = @intCast(zig_object.symbols.items.len);
         try zig_object.symbols.ensureUnusedCapacity(gpa, 1);
         zig_object.symbols.items.len += 1;
         break :blk index;
     };
     zig_object.symbols.items[sym_index] = symbol;
-    gop.value_ptr.* = .{ .index = sym_index, .file = null };
+    gop.value_ptr.* = sym_index;
     return sym_index;
 }
 
src/link/Wasm.zig
@@ -1,37 +1,41 @@
 const Wasm = @This();
 
 const std = @import("std");
-const builtin = @import("builtin");
-const mem = std.mem;
-const Allocator = std.mem.Allocator;
+
 const assert = std.debug.assert;
+const build_options = @import("build_options");
+const builtin = @import("builtin");
+const codegen = @import("../codegen.zig");
 const fs = std.fs;
 const leb = std.leb;
-const log = std.log.scoped(.link);
-
-pub const Atom = @import("Wasm/Atom.zig");
-const Dwarf = @import("Dwarf.zig");
-const Module = @import("../Module.zig");
-const InternPool = @import("../InternPool.zig");
-const Compilation = @import("../Compilation.zig");
-const CodeGen = @import("../arch/wasm/CodeGen.zig");
-const codegen = @import("../codegen.zig");
 const link = @import("../link.zig");
 const lldMain = @import("../main.zig").lldMain;
+const log = std.log.scoped(.link);
+const mem = std.mem;
 const trace = @import("../tracy.zig").trace;
-const build_options = @import("build_options");
+const types = @import("Wasm/types.zig");
 const wasi_libc = @import("../wasi_libc.zig");
-const Cache = std.Build.Cache;
-const Type = @import("../type.zig").Type;
-const Value = @import("../Value.zig");
-const TypedValue = @import("../TypedValue.zig");
-const LlvmObject = @import("../codegen/llvm.zig").Object;
+
 const Air = @import("../Air.zig");
+const Allocator = std.mem.Allocator;
+const Archive = @import("Wasm/Archive.zig");
+const Cache = std.Build.Cache;
+const CodeGen = @import("../arch/wasm/CodeGen.zig");
+const Compilation = @import("../Compilation.zig");
+const Dwarf = @import("Dwarf.zig");
+const File = @import("Wasm/file.zig").File;
+const InternPool = @import("../InternPool.zig");
 const Liveness = @import("../Liveness.zig");
-const Symbol = @import("Wasm/Symbol.zig");
+const LlvmObject = @import("../codegen/llvm.zig").Object;
+const Module = @import("../Module.zig");
 const Object = @import("Wasm/Object.zig");
-const Archive = @import("Wasm/Archive.zig");
-const types = @import("Wasm/types.zig");
+const Symbol = @import("Wasm/Symbol.zig");
+const Type = @import("../type.zig").Type;
+const TypedValue = @import("../TypedValue.zig");
+const Value = @import("../value.zig").Value;
+const ZigObject = @import("Wasm/ZigObject.zig");
+
+pub const Atom = @import("Wasm/Atom.zig");
 pub const Relocation = types.Relocation;
 
 pub const base_tag: link.File.Tag = .wasm;
@@ -57,6 +61,11 @@ export_table: bool,
 name: []const u8,
 /// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
 llvm_object: ?*LlvmObject = null,
+/// The file index of a `ZigObject`. This will only contain a valid index when a zcu exists,
+/// and the chosen backend is the Wasm backend.
+zig_object_index: File.Index = .null,
+/// List of relocatable files to be linked into the final binary.
+files: std.MultiArrayList(File.Entry) = .{},
 /// When importing objects from the host environment, a name must be supplied.
 /// LLVM uses "env" by default when none is given. This would be a good default for Zig
 /// to support existing code.
@@ -556,9 +565,27 @@ pub fn createEmpty(
         }
     }
 
+    if (comp.module) |zcu| {
+        if (!use_llvm) {
+            const index: File.Index = @enumFromInt(wasm.files.len);
+            var zig_object: ZigObject = .{
+                .path = try std.fmt.allocPrint(gpa, "{s}.o", .{std.fs.path.stem(zcu.main_mod.root_src_path)}),
+                .stack_pointer_sym = undefined,
+            };
+            try zig_object.init(wasm);
+            try wasm.files.append(gpa, .{ .zig_object = zig_object });
+            wasm.zig_object_index = index;
+        }
+    }
+
     return wasm;
 }
 
+fn zigObjectPtr(wasm: *Wasm) ?*ZigObject {
+    if (wasm.zig_object_index == .null) return null;
+    return &wasm.files.items(.data)[@intFromEnum(wasm.zig_object_index)].zig_object;
+}
+
 /// For a given name, creates a new global synthetic symbol.
 /// Leaves index undefined and the default flags (0).
 fn createSyntheticSymbol(wasm: *Wasm, name: []const u8, tag: Symbol.Tag) !SymbolLoc {