authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2024-01-13 17:48:21+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2024-02-29 15:22:58+01:00
logba0e84a411074fe661b7df14edb2595267edcd30
treece303dc35caa8dacf481836e7e9fd2c428814be3
parent5c0766b6c8f1aea18815206e0698953a35384a21
signaturelock-open Commit is signed but in an unrecognized format.

wasm: move Zig module-linkage to ZigObject

Rather than specializing the linker-driver to be able to handle objects generated by a ZCU, we store all data in-memory in ZigObject. ZigObject acts more like a regular object file which will allow us to treat it as us. This will make linking much more simple, but will also reduce the complexity of incremental-linking as we can simply update ZigObject and relink it.

1 files changed, 998 insertions(+), 0 deletions(-)

src/link/Wasm/ZigObject.zig created+998
......@@ -0,0 +1,998 @@
1//! ZigObject encapsulates the state of the incrementally compiled Zig module.
2//! It stores the associated input local and global symbols, allocated atoms,
3//! and any relocations that may have been emitted.
4//! Think about this as fake in-memory Object file for the Zig module.
5
6/// List of all `Decl` that are currently alive.
7/// Each index maps to the corresponding `Atom.Index`.
8decls: std.AutoHashMapUnmanaged(InternPool.DeclIndex, Atom.Index) = .{},
9/// List of function type signatures for this Zig module.
10func_types: std.ArrayListUnmanaged(std.wasm.Type) = .{},
11/// Map of symbol locations, represented by its `types.Import`.
12imports: std.AutoHashMapUnmanaged(u32, types.Import) = .{},
13/// List of WebAssembly globals.
14globals: std.ArrayListUnmanaged(std.wasm.Global) = .{},
15/// Mapping between an `Atom` and its type index representing the Wasm
16/// type of the function signature.
17atom_types: std.AutoHashMapUnmanaged(Atom.Index, u32) = .{},
18/// List of all symbols generated by Zig code.
19symbols: std.ArrayListUnmanaged(Symbol) = .{},
20/// Map from symbol name offset to their index into the `symbols` list.
21global_syms: std.AutoHashMapUnmanaged(u32, u32) = .{},
22/// List of symbol indexes which are free to be used.
23symbols_free_list: std.ArrayListUnmanaged(u32) = .{},
24/// Extra metadata about the linking section, such as alignment of segments and their name.
25segment_info: std.ArrayListUnmanage(types.Segment) = &.{},
26/// File encapsulated string table, used to deduplicate strings within the generated file.
27string_table: StringTable = .{},
28/// Map for storing anonymous declarations. Each anonymous decl maps to its Atom's index.
29anon_decls: std.AutoArrayHashMapUnmanaged(InternPool.Index, Atom.Index) = .{},
30/// Represents the symbol index of the error name table
31/// When this is `null`, no code references an error using runtime `@errorName`.
32/// During initializion, a symbol with corresponding atom will be created that is
33/// used to perform relocations to the pointer of this table.
34/// The actual table is populated during `flush`.
35error_table_symbol: ?u32 = null,
36/// Amount of functions in the `import` sections.
37imported_functions_count: u32 = 0,
38/// Amount of globals in the `import` section.
39imported_globals_count: u32 = 0,
40/// Symbol index representing the stack pointer. This will be set upon initializion
41/// of a new `ZigObject`. Codegen will make calls into this to create relocations for
42/// this symbol each time the stack pointer is moved.
43stack_pointer_sym: u32,
44
45/// Frees and invalidates all memory of the incrementally compiled Zig module.
46/// It is illegal behavior to access the `ZigObject` after calling `deinit`.
47pub fn deinit(zig_object: *ZigObject, gpa: std.mem.Allocator) void {
48 for (zig_object.segment_info.values()) |segment_info| {
49 gpa.free(segment_info.name);
50 }
51
52 // For decls and anon decls we free the memory of its atoms.
53 // The memory of atoms parsed from object files is managed by
54 // the object file itself, and therefore we can skip those.
55 {
56 var it = zig_object.decls.valueIterator();
57 while (it.next()) |atom_index_ptr| {
58 const atom = zig_object.getAtomPtr(atom_index_ptr.*);
59 for (atom.locals.items) |local_index| {
60 const local_atom = zig_object.getAtomPtr(local_index);
61 local_atom.deinit(gpa);
62 }
63 atom.deinit(gpa);
64 }
65 }
66 {
67 for (zig_object.anon_decls.values()) |atom_index| {
68 const atom = zig_object.getAtomPtr(atom_index);
69 for (atom.locals.items) |local_index| {
70 const local_atom = zig_object.getAtomPtr(local_index);
71 local_atom.deinit(gpa);
72 }
73 atom.deinit(gpa);
74 }
75 }
76 zig_object.decls.deinit(gpa);
77 zig_object.anon_decls.deinit(gpa);
78 zig_object.symbols.deinit(gpa);
79 zig_object.symbols_free_list.deinit(gpa);
80 zig_object.segment_info.deinit(gpa);
81
82 zig_object.string_table.deinit(gpa);
83 zig_object.* = undefined;
84}
85
86/// Allocates a new symbol and returns its index.
87/// Will re-use slots when a symbol was freed at an earlier stage.
88pub fn allocateSymbol(zig_object: *ZigObject, gpa: std.mem.Allocator) !u32 {
89 try zig_object.symbols.ensureUnusedCapacity(gpa, 1);
90 const symbol: Symbol = .{
91 .name = std.math.maxInt(u32), // will be set after updateDecl as well as during atom creation for decls
92 .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
93 .tag = .undefined, // will be set after updateDecl
94 .index = std.math.maxInt(u32), // will be set during atom parsing
95 .virtual_address = std.math.maxInt(u32), // will be set during atom allocation
96 };
97 if (zig_object.symbols_free_list.popOrNull()) |index| {
98 zig_object.symbols.items[index] = symbol;
99 return index;
100 }
101 const index = @as(u32, @intCast(zig_object.symbols.items.len));
102 zig_object.symbols.appendAssumeCapacity(symbol);
103 return index;
104}
105
106// Generate code for the Decl, storing it in memory to be later written to
107// the file on flush().
108pub fn updateDecl(zig_object: *ZigObject, wasm_file: *Wasm, mod: *Module, decl_index: InternPool.DeclIndex) !void {
109 const decl = mod.declPtr(decl_index);
110 if (decl.val.getFunction(mod)) |_| {
111 return;
112 } else if (decl.val.getExternFunc(mod)) |_| {
113 return;
114 }
115
116 const gpa = wasm_file.base.comp.gpa;
117 const atom_index = try zig_object.getOrCreateAtomForDecl(decl_index);
118 const atom = wasm_file.getAtomPtr(atom_index);
119 atom.clear();
120
121 if (decl.isExtern(mod)) {
122 const variable = decl.getOwnedVariable(mod).?;
123 const name = mod.intern_pool.stringToSlice(decl.name);
124 const lib_name = mod.intern_pool.stringToSliceUnwrap(variable.lib_name);
125 return wasm_file.addOrUpdateImport(name, atom.sym_index, lib_name, null);
126 }
127 const val = if (decl.val.getVariable(mod)) |variable| Value.fromInterned(variable.init) else decl.val;
128
129 var code_writer = std.ArrayList(u8).init(gpa);
130 defer code_writer.deinit();
131
132 const res = try codegen.generateSymbol(
133 &wasm_file.base,
134 decl.srcLoc(mod),
135 .{ .ty = decl.ty, .val = val },
136 &code_writer,
137 .none,
138 .{ .parent_atom_index = atom.sym_index },
139 );
140
141 const code = switch (res) {
142 .ok => code_writer.items,
143 .fail => |em| {
144 decl.analysis = .codegen_failure;
145 try mod.failed_decls.put(mod.gpa, decl_index, em);
146 return;
147 },
148 };
149
150 return wasm_file.finishUpdateDecl(decl_index, code, .data);
151}
152
153pub fn updateFunc(zig_object: *ZigObject, wasm_file: *Wasm, mod: *Module, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {
154 const gpa = wasm_file.base.comp.gpa;
155 const func = mod.funcInfo(func_index);
156 const decl_index = func.owner_decl;
157 const decl = mod.declPtr(decl_index);
158 const atom_index = try zig_object.getOrCreateAtomForDecl(decl_index);
159 const atom = wasm_file.getAtomPtr(atom_index);
160 atom.clear();
161
162 var code_writer = std.ArrayList(u8).init(gpa);
163 defer code_writer.deinit();
164 const result = try codegen.generateFunction(
165 &wasm_file.base,
166 decl.srcLoc(mod),
167 func_index,
168 air,
169 liveness,
170 &code_writer,
171 .none,
172 );
173
174 const code = switch (result) {
175 .ok => code_writer.items,
176 .fail => |em| {
177 decl.analysis = .codegen_failure;
178 try mod.failed_decls.put(mod.gpa, decl_index, em);
179 return;
180 },
181 };
182
183 return zig_object.finishUpdateDecl(wasm_file, decl_index, code, .function);
184}
185
186fn finishUpdateDecl(zig_object: *ZigObject, wasm_file: *Wasm, decl_index: InternPool.DeclIndex, code: []const u8, symbol_tag: Symbol.Tag) !void {
187 const gpa = wasm_file.base.comp.gpa;
188 const mod = wasm_file.base.comp.module.?;
189 const decl = mod.declPtr(decl_index);
190 const atom_index = zig_object.decls.get(decl_index).?;
191 const atom = wasm_file.getAtomPtr(atom_index);
192 const symbol = &zig_object.symbols.items[atom.sym_index];
193 const full_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
194 symbol.name = try zig_object.string_table.insert(gpa, full_name);
195 symbol.tag = symbol_tag;
196 try atom.code.appendSlice(gpa, code);
197 try wasm_file.resolved_symbols.put(gpa, atom.symbolLoc(), {});
198
199 atom.size = @intCast(code.len);
200 if (code.len == 0) return;
201 atom.alignment = decl.getAlignment(mod);
202}
203
204/// For a given `InternPool.DeclIndex` returns its corresponding `Atom.Index`.
205/// When the index was not found, a new `Atom` will be created, and its index will be returned.
206/// The newly created Atom is empty with default fields as specified by `Atom.empty`.
207pub fn getOrCreateAtomForDecl(zig_object: *ZigObject, wasm_file: *Wasm, decl_index: InternPool.DeclIndex) !Atom.Index {
208 const gpa = wasm_file.base.comp.gpa;
209 const gop = try zig_object.decls.getOrPut(gpa, decl_index);
210 if (!gop.found_existing) {
211 const atom_index = try wasm_file.createAtom();
212 gop.value_ptr.* = atom_index;
213 const atom = wasm_file.getAtom(atom_index);
214 const symbol = atom.symbolLoc().getSymbol(wasm_file);
215 const mod = wasm_file.base.comp.module.?;
216 const decl = mod.declPtr(decl_index);
217 const full_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
218 symbol.name = try wasm_file.string_table.insert(gpa, full_name);
219 }
220 return gop.value_ptr.*;
221}
222
223pub fn lowerAnonDecl(
224 zig_object: *ZigObject,
225 wasm_file: *Wasm,
226 decl_val: InternPool.Index,
227 explicit_alignment: InternPool.Alignment,
228 src_loc: Module.SrcLoc,
229) !codegen.Result {
230 const gpa = wasm_file.base.comp.gpa;
231 const gop = try zig_object.anon_decls.getOrPut(gpa, decl_val);
232 if (!gop.found_existing) {
233 const mod = wasm_file.base.comp.module.?;
234 const ty = Type.fromInterned(mod.intern_pool.typeOf(decl_val));
235 const tv: TypedValue = .{ .ty = ty, .val = Value.fromInterned(decl_val) };
236 var name_buf: [32]u8 = undefined;
237 const name = std.fmt.bufPrint(&name_buf, "__anon_{d}", .{
238 @intFromEnum(decl_val),
239 }) catch unreachable;
240
241 switch (try zig_object.lowerConst(name, tv, src_loc)) {
242 .ok => |atom_index| zig_object.anon_decls.values()[gop.index] = atom_index,
243 .fail => |em| return .{ .fail = em },
244 }
245 }
246
247 const atom = wasm_file.getAtomPtr(zig_object.anon_decls.values()[gop.index]);
248 atom.alignment = switch (atom.alignment) {
249 .none => explicit_alignment,
250 else => switch (explicit_alignment) {
251 .none => atom.alignment,
252 else => atom.alignment.maxStrict(explicit_alignment),
253 },
254 };
255 return .ok;
256}
257
258/// Lowers a constant typed value to a local symbol and atom.
259/// Returns the symbol index of the local
260/// The given `decl` is the parent decl whom owns the constant.
261pub fn lowerUnnamedConst(zig_object: *ZigObject, wasm_file: *Wasm, tv: TypedValue, decl_index: InternPool.DeclIndex) !u32 {
262 const gpa = wasm_file.base.comp.gpa;
263 const mod = wasm_file.base.comp.module.?;
264 std.debug.assert(tv.ty.zigTypeTag(mod) != .Fn); // cannot create local symbols for functions
265 const decl = mod.declPtr(decl_index);
266
267 const parent_atom_index = try zig_object.getOrCreateAtomForDecl(decl_index);
268 const parent_atom = wasm_file.getAtom(parent_atom_index);
269 const local_index = parent_atom.locals.items.len;
270 const fqn = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
271 const name = try std.fmt.allocPrintZ(gpa, "__unnamed_{s}_{d}", .{
272 fqn, local_index,
273 });
274 defer gpa.free(name);
275
276 switch (try zig_object.lowerConst(name, tv, decl.srcLoc(mod))) {
277 .ok => |atom_index| {
278 try wasm_file.getAtomPtr(parent_atom_index).locals.append(gpa, atom_index);
279 return wasm_file.getAtom(atom_index).getSymbolIndex().?;
280 },
281 .fail => |em| {
282 decl.analysis = .codegen_failure;
283 try mod.failed_decls.put(mod.gpa, decl_index, em);
284 return error.CodegenFail;
285 },
286 }
287}
288
289const LowerConstResult = union(enum) {
290 ok: Atom.Index,
291 fail: *Module.ErrorMsg,
292};
293
294fn lowerConst(zig_object: *ZigObject, wasm_file: *Wasm, name: []const u8, tv: TypedValue, src_loc: Module.SrcLoc) !LowerConstResult {
295 const gpa = wasm_file.base.comp.gpa;
296 const mod = wasm_file.base.comp.module.?;
297
298 // Create and initialize a new local symbol and atom
299 const atom_index = try wasm_file.createAtom();
300 var value_bytes = std.ArrayList(u8).init(gpa);
301 defer value_bytes.deinit();
302
303 const code = code: {
304 const atom = wasm_file.getAtomPtr(atom_index);
305 atom.alignment = tv.ty.abiAlignment(mod);
306 zig_object.symbols.items[atom.sym_index] = .{
307 .name = try zig_object.string_table.insert(gpa, name),
308 .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
309 .tag = .data,
310 .index = undefined,
311 .virtual_address = undefined,
312 };
313
314 const result = try codegen.generateSymbol(
315 &wasm_file.base,
316 src_loc,
317 tv,
318 &value_bytes,
319 .none,
320 .{
321 .parent_atom_index = atom.sym_index,
322 .addend = null,
323 },
324 );
325 break :code switch (result) {
326 .ok => value_bytes.items,
327 .fail => |em| {
328 return .{ .fail = em };
329 },
330 };
331 };
332
333 const atom = wasm_file.getAtomPtr(atom_index);
334 atom.size = @intCast(code.len);
335 try atom.code.appendSlice(gpa, code);
336 return .{ .ok = atom_index };
337}
338
339/// Returns the symbol index of the error name table.
340///
341/// When the symbol does not yet exist, it will create a new one instead.
342pub fn getErrorTableSymbol(zig_object: *ZigObject, wasm_file: *Wasm) !u32 {
343 if (zig_object.error_table_symbol) |symbol| {
344 return symbol;
345 }
346
347 // no error was referenced yet, so create a new symbol and atom for it
348 // and then return said symbol's index. The final table will be populated
349 // during `flush` when we know all possible error names.
350 const gpa = wasm_file.base.gpa;
351 const sym_index = try zig_object.allocateSymbol(gpa);
352 const atom_index = try wasm_file.createAtom(sym_index);
353 const atom = wasm_file.getAtomPtr(atom_index);
354 const slice_ty = Type.slice_const_u8_sentinel_0;
355 const mod = wasm_file.base.comp.module.?;
356 atom.alignment = slice_ty.abiAlignment(mod);
357
358 const sym_name = try zig_object.string_table.insert(gpa, "__zig_err_name_table");
359 const symbol = &zig_object.symbols.items[sym_index];
360 symbol.* = .{
361 .name = sym_name,
362 .tag = .data,
363 .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
364 .index = 0,
365 .virtual_address = undefined,
366 };
367 symbol.mark();
368
369 log.debug("Error name table was created with symbol index: ({d})", .{sym_index});
370 zig_object.error_table_symbol = sym_index;
371 return sym_index;
372}
373
374/// Populates the error name table, when `error_table_symbol` is not null.
375///
376/// This creates a table that consists of pointers and length to each error name.
377/// The table is what is being pointed to within the runtime bodies that are generated.
378fn populateErrorNameTable(zig_object: *ZigObject, wasm_file: *Wasm) !void {
379 const symbol_index = zig_object.error_table_symbol orelse return;
380 const gpa = wasm_file.base.comp.gpa;
381 const atom_index = wasm_file.symbol_atom.get(.{ .file = null, .index = symbol_index }).?;
382
383 // Rather than creating a symbol for each individual error name,
384 // we create a symbol for the entire region of error names. We then calculate
385 // the pointers into the list using addends which are appended to the relocation.
386 const names_sym_index = try zig_object.allocateSymbol(gpa);
387 const names_atom_index = try wasm_file.createAtom(names_sym_index);
388 const names_atom = wasm_file.getAtomPtr(names_atom_index);
389 names_atom.alignment = .@"1";
390 const sym_name = try zig_object.string_table.insert(gpa, "__zig_err_names");
391 const names_symbol = &zig_object.symbols.items[names_sym_index];
392 names_symbol.* = .{
393 .name = sym_name,
394 .tag = .data,
395 .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
396 .index = 0,
397 .virtual_address = undefined,
398 };
399 names_symbol.mark();
400
401 log.debug("Populating error names", .{});
402
403 // Addend for each relocation to the table
404 var addend: u32 = 0;
405 const mod = wasm_file.base.comp.module.?;
406 for (mod.global_error_set.keys()) |error_name_nts| {
407 const atom = wasm_file.getAtomPtr(atom_index);
408
409 const error_name = mod.intern_pool.stringToSlice(error_name_nts);
410 const len = @as(u32, @intCast(error_name.len + 1)); // names are 0-termianted
411
412 const slice_ty = Type.slice_const_u8_sentinel_0;
413 const offset = @as(u32, @intCast(atom.code.items.len));
414 // first we create the data for the slice of the name
415 try atom.code.appendNTimes(gpa, 0, 4); // ptr to name, will be relocated
416 try atom.code.writer(gpa).writeInt(u32, len - 1, .little);
417 // create relocation to the error name
418 try atom.relocs.append(gpa, .{
419 .index = names_atom.sym_index,
420 .relocation_type = .R_WASM_MEMORY_ADDR_I32,
421 .offset = offset,
422 .addend = @as(i32, @intCast(addend)),
423 });
424 atom.size += @as(u32, @intCast(slice_ty.abiSize(mod)));
425 addend += len;
426
427 // as we updated the error name table, we now store the actual name within the names atom
428 try names_atom.code.ensureUnusedCapacity(gpa, len);
429 names_atom.code.appendSliceAssumeCapacity(error_name);
430 names_atom.code.appendAssumeCapacity(0);
431
432 log.debug("Populated error name: '{s}'", .{error_name});
433 }
434 names_atom.size = addend;
435
436 // link the atoms with the rest of the binary so they can be allocated
437 // and relocations will be performed.
438 try wasm_file.parseAtom(atom_index, .{ .data = .read_only });
439 try wasm_file.parseAtom(names_atom_index, .{ .data = .read_only });
440}
441
442/// Either creates a new import, or updates one if existing.
443/// When `type_index` is non-null, we assume an external function.
444/// In all other cases, a data-symbol will be created instead.
445pub fn addOrUpdateImport(
446 zig_object: *ZigObject,
447 wasm_file: *Wasm,
448 /// Name of the import
449 name: []const u8,
450 /// Symbol index that is external
451 symbol_index: u32,
452 /// Optional library name (i.e. `extern "c" fn foo() void`
453 lib_name: ?[:0]const u8,
454 /// The index of the type that represents the function signature
455 /// when the extern is a function. When this is null, a data-symbol
456 /// is asserted instead.
457 type_index: ?u32,
458) !void {
459 const gpa = wasm_file.base.comp.gpa;
460 std.debug.assert(symbol_index != 0);
461 // For the import name, we use the decl's name, rather than the fully qualified name
462 // Also mangle the name when the lib name is set and not equal to "C" so imports with the same
463 // name but different module can be resolved correctly.
464 const mangle_name = lib_name != null and
465 !std.mem.eql(u8, lib_name.?, "c");
466 const full_name = if (mangle_name) full_name: {
467 break :full_name try std.fmt.allocPrint(gpa, "{s}|{s}", .{ name, lib_name.? });
468 } else name;
469 defer if (mangle_name) gpa.free(full_name);
470
471 const decl_name_index = try zig_object.string_table.insert(gpa, full_name);
472 const symbol: *Symbol = &zig_object.symbols.items[symbol_index];
473 symbol.setUndefined(true);
474 symbol.setGlobal(true);
475 symbol.name = decl_name_index;
476 if (mangle_name) {
477 // we specified a specific name for the symbol that does not match the import name
478 symbol.setFlag(.WASM_SYM_EXPLICIT_NAME);
479 }
480
481 if (type_index) |ty_index| {
482 const gop = try zig_object.imports.getOrPut(gpa, symbol_index);
483 const module_name = if (lib_name) |l_name| blk: {
484 break :blk l_name;
485 } else wasm_file.host_name;
486 if (!gop.found_existing) {
487 gop.value_ptr.* = .{
488 .module_name = try zig_object.string_table.insert(gpa, module_name),
489 .name = try zig_object.string_table.insert(gpa, name),
490 .kind = .{ .function = ty_index },
491 };
492 zig_object.imported_functions_count += 1;
493 }
494 }
495}
496
497/// Returns the symbol index from a symbol of which its flag is set global,
498/// such as an exported or imported symbol.
499/// If the symbol does not yet exist, creates a new one symbol instead
500/// and then returns the index to it.
501pub fn getGlobalSymbol(zig_object: *ZigObject, wasm_file: *Wasm, name: []const u8) !u32 {
502 const gpa = wasm_file.base.comp.gpa;
503 const name_index = try zig_object.string_table.insert(gpa, name);
504 const gop = try zig_object.global_syms.getOrPut(gpa, name_index);
505 if (gop.found_existing) {
506 return gop.value_ptr.index;
507 }
508
509 var symbol: Symbol = .{
510 .name = name_index,
511 .flags = 0,
512 .index = undefined, // index to type will be set after merging function symbols
513 .tag = .function,
514 .virtual_address = undefined,
515 };
516 symbol.setGlobal(true);
517 symbol.setUndefined(true);
518
519 const sym_index = if (zig_object.symbol.popOrNull()) |index| index else blk: {
520 const index: u32 = @intCast(zig_object.symbols.items.len);
521 try zig_object.symbols.ensureUnusedCapacity(gpa, 1);
522 zig_object.symbols.items.len += 1;
523 break :blk index;
524 };
525 zig_object.symbols.items[sym_index] = symbol;
526 gop.value_ptr.* = .{ .index = sym_index, .file = null };
527 return sym_index;
528}
529
530/// For a given decl, find the given symbol index's atom, and create a relocation for the type.
531/// Returns the given pointer address
532pub fn getDeclVAddr(
533 zig_object: *ZigObject,
534 wasm_file: *Wasm,
535 decl_index: InternPool.DeclIndex,
536 reloc_info: link.File.RelocInfo,
537) !u64 {
538 const target = wasm_file.base.comp.root_mod.resolved_target.result;
539 const gpa = wasm_file.base.comp.gpa;
540 const mod = wasm_file.base.comp.module.?;
541 const decl = mod.declPtr(decl_index);
542
543 const target_atom_index = try zig_object.getOrCreateAtomForDecl(wasm_file, decl_index);
544 const target_symbol_index = wasm_file.getAtom(target_atom_index).sym_index;
545
546 std.debug.assert(reloc_info.parent_atom_index != 0);
547 const atom_index = wasm_file.symbol_atom.get(.{ .file = null, .index = reloc_info.parent_atom_index }).?;
548 const atom = wasm_file.getAtomPtr(atom_index);
549 const is_wasm32 = target.cpu.arch == .wasm32;
550 if (decl.ty.zigTypeTag(mod) == .Fn) {
551 std.debug.assert(reloc_info.addend == 0); // addend not allowed for function relocations
552 try atom.relocs.append(gpa, .{
553 .index = target_symbol_index,
554 .offset = @intCast(reloc_info.offset),
555 .relocation_type = if (is_wasm32) .R_WASM_TABLE_INDEX_I32 else .R_WASM_TABLE_INDEX_I64,
556 });
557 } else {
558 try atom.relocs.append(gpa, .{
559 .index = target_symbol_index,
560 .offset = @intCast(reloc_info.offset),
561 .relocation_type = if (is_wasm32) .R_WASM_MEMORY_ADDR_I32 else .R_WASM_MEMORY_ADDR_I64,
562 .addend = @intCast(reloc_info.addend),
563 });
564 }
565
566 // we do not know the final address at this point,
567 // as atom allocation will determine the address and relocations
568 // will calculate and rewrite this. Therefore, we simply return the symbol index
569 // that was targeted.
570 return target_symbol_index;
571}
572
573pub fn getAnonDeclVAddr(
574 zig_object: *ZigObject,
575 wasm_file: *Wasm,
576 decl_val: InternPool.Index,
577 reloc_info: link.File.RelocInfo,
578) !u64 {
579 const gpa = wasm_file.base.comp.gpa;
580 const target = wasm_file.base.comp.root_mod.resolved_target.result;
581 const atom_index = zig_object.anon_decls.get(decl_val).?;
582 const target_symbol_index = wasm_file.getAtom(atom_index).getSymbolIndex().?;
583
584 const parent_atom_index = wasm_file.symbol_atom.get(.{ .file = null, .index = reloc_info.parent_atom_index }).?;
585 const parent_atom = wasm_file.getAtomPtr(parent_atom_index);
586 const is_wasm32 = target.cpu.arch == .wasm32;
587 const mod = wasm_file.base.comp.module.?;
588 const ty = Type.fromInterned(mod.intern_pool.typeOf(decl_val));
589 if (ty.zigTypeTag(mod) == .Fn) {
590 std.debug.assert(reloc_info.addend == 0); // addend not allowed for function relocations
591 try parent_atom.relocs.append(gpa, .{
592 .index = target_symbol_index,
593 .offset = @intCast(reloc_info.offset),
594 .relocation_type = if (is_wasm32) .R_WASM_TABLE_INDEX_I32 else .R_WASM_TABLE_INDEX_I64,
595 });
596 } else {
597 try parent_atom.relocs.append(gpa, .{
598 .index = target_symbol_index,
599 .offset = @intCast(reloc_info.offset),
600 .relocation_type = if (is_wasm32) .R_WASM_MEMORY_ADDR_I32 else .R_WASM_MEMORY_ADDR_I64,
601 .addend = @intCast(reloc_info.addend),
602 });
603 }
604
605 // we do not know the final address at this point,
606 // as atom allocation will determine the address and relocations
607 // will calculate and rewrite this. Therefore, we simply return the symbol index
608 // that was targeted.
609 return target_symbol_index;
610}
611
612pub fn deleteDeclExport(
613 zig_object: *ZigObject,
614 wasm_file: *Wasm,
615 decl_index: InternPool.DeclIndex,
616) void {
617 const atom_index = zig_object.decls.get(decl_index) orelse return;
618 const sym_index = wasm_file.getAtom(atom_index).sym_index;
619 const loc: Wasm.SymbolLoc = .{ .file = null, .index = sym_index };
620 const symbol = loc.getSymbol(wasm_file);
621 std.debug.assert(zig_object.global_syms.remove(symbol.name));
622}
623
624pub fn updateExports(
625 zig_object: *ZigObject,
626 wasm_file: *Wasm,
627 mod: *Module,
628 exported: Module.Exported,
629 exports: []const *Module.Export,
630) !void {
631 const decl_index = switch (exported) {
632 .decl_index => |i| i,
633 .value => |val| {
634 _ = val;
635 @panic("TODO: implement Wasm linker code for exporting a constant value");
636 },
637 };
638 const decl = mod.declPtr(decl_index);
639 const atom_index = try zig_object.getOrCreateAtomForDecl(decl_index);
640 const atom = wasm_file.getAtom(atom_index);
641 const atom_sym = atom.symbolLoc().getSymbol(wasm_file).*;
642 const gpa = mod.gpa;
643
644 for (exports) |exp| {
645 if (mod.intern_pool.stringToSliceUnwrap(exp.opts.section)) |section| {
646 try mod.failed_exports.putNoClobber(gpa, exp, try Module.ErrorMsg.create(
647 gpa,
648 decl.srcLoc(mod),
649 "Unimplemented: ExportOptions.section '{s}'",
650 .{section},
651 ));
652 continue;
653 }
654
655 const exported_decl_index = switch (exp.exported) {
656 .value => {
657 try mod.failed_exports.putNoClobber(gpa, exp, try Module.ErrorMsg.create(
658 gpa,
659 decl.srcLoc(mod),
660 "Unimplemented: exporting a named constant value",
661 .{},
662 ));
663 continue;
664 },
665 .decl_index => |i| i,
666 };
667 const exported_atom_index = try zig_object.getOrCreateAtomForDecl(exported_decl_index);
668 const exported_atom = wasm_file.getAtom(exported_atom_index);
669 // const export_name = try zig_object.string_table.put(gpa, mod.intern_pool.stringToSlice(exp.opts.name));
670 const sym_loc = exported_atom.symbolLoc();
671 const symbol = sym_loc.getSymbol(wasm_file);
672 symbol.setGlobal(true);
673 symbol.setUndefined(false);
674 symbol.index = atom_sym.index;
675 symbol.tag = atom_sym.tag;
676 symbol.name = atom_sym.name;
677
678 switch (exp.opts.linkage) {
679 .Internal => {
680 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
681 symbol.setFlag(.WASM_SYM_BINDING_WEAK);
682 },
683 .Weak => {
684 symbol.setFlag(.WASM_SYM_BINDING_WEAK);
685 },
686 .Strong => {}, // symbols are strong by default
687 .LinkOnce => {
688 try mod.failed_exports.putNoClobber(gpa, exp, try Module.ErrorMsg.create(
689 gpa,
690 decl.srcLoc(mod),
691 "Unimplemented: LinkOnce",
692 .{},
693 ));
694 continue;
695 },
696 }
697
698 // TODO: Revisit this
699 // if (zig_object.global_syms.get(export_name)) |existing_loc| {
700 // if (existing_loc.index == atom.sym_index) continue;
701 // const existing_sym: Symbol = existing_loc.getSymbol(wasm_file).*;
702
703 // if (!existing_sym.isUndefined()) blk: {
704 // if (symbol.isWeak()) {
705 // try wasm_file.discarded.put(gpa, existing_loc, sym_loc);
706 // continue; // to-be-exported symbol is weak, so we keep the existing symbol
707 // }
708
709 // // new symbol is not weak while existing is, replace existing symbol
710 // if (existing_sym.isWeak()) {
711 // break :blk;
712 // }
713 // // When both the to-be-exported symbol and the already existing symbol
714 // // are strong symbols, we have a linker error.
715 // // In the other case we replace one with the other.
716 // try mod.failed_exports.put(gpa, exp, try Module.ErrorMsg.create(
717 // gpa,
718 // decl.srcLoc(mod),
719 // \\LinkError: symbol '{}' defined multiple times
720 // \\ first definition in '{s}'
721 // \\ next definition in '{s}'
722 // ,
723 // .{ exp.opts.name.fmt(&mod.intern_pool), wasm_file.name, wasm_file.name },
724 // ));
725 // continue;
726 // }
727
728 // // in this case the existing symbol must be replaced either because it's weak or undefined.
729 // try wasm.discarded.put(gpa, existing_loc, sym_loc);
730 // _ = wasm.imports.remove(existing_loc);
731 // _ = wasm.undefs.swapRemove(existing_sym.name);
732 // }
733
734 // // Ensure the symbol will be exported using the given name
735 // if (!mod.intern_pool.stringEqlSlice(exp.opts.name, sym_loc.getName(wasm))) {
736 // try wasm.export_names.put(gpa, sym_loc, export_name);
737 // }
738
739 // try wasm.globals.put(
740 // gpa,
741 // export_name,
742 // sym_loc,
743 // );
744 }
745}
746
747pub fn freeDecl(zig_object: *ZigObject, wasm_file: *Wasm, decl_index: InternPool.DeclIndex) void {
748 const gpa = wasm_file.base.comp.gpa;
749 const mod = wasm_file.base.comp.module.?;
750 const decl = mod.declPtr(decl_index);
751 const atom_index = zig_object.decls.get(decl_index).?;
752 const atom = wasm_file.getAtomPtr(atom_index);
753 zig_object.symbols_free_list.append(gpa, atom.sym_index) catch {};
754 _ = zig_object.decls.remove(decl_index);
755 zig_object.symbols.items[atom.sym_index].tag = .dead;
756 for (atom.locals.items) |local_atom_index| {
757 const local_atom = wasm_file.getAtom(local_atom_index);
758 const local_symbol = &zig_object.symbols.items[local_atom.sym_index];
759 local_symbol.tag = .dead; // also for any local symbol
760 zig_object.symbols_free_list.append(gpa, local_atom.sym_index) catch {};
761 std.denug.assert(wasm_file.symbol_atom.remove(local_atom.symbolLoc()));
762 }
763
764 if (decl.isExtern(mod)) {
765 _ = zig_object.imports.remove(atom.getSymbolIndex().?);
766 }
767 _ = wasm_file.symbol_atom.remove(atom.symbolLoc());
768
769 // if (wasm.dwarf) |*dwarf| {
770 // dwarf.freeDecl(decl_index);
771 // }
772
773 if (atom.next) |next_atom_index| {
774 const next_atom = wasm_file.getAtomPtr(next_atom_index);
775 next_atom.prev = atom.prev;
776 atom.next = null;
777 }
778 if (atom.prev) |prev_index| {
779 const prev_atom = wasm_file.getAtomPtr(prev_index);
780 prev_atom.next = atom.next;
781 atom.prev = null;
782 }
783}
784
785pub fn getTypeIndex(zig_object: *const ZigObject, func_type: std.wasm.Type) ?u32 {
786 var index: u32 = 0;
787 while (index < zig_object.func_types.items.len) : (index += 1) {
788 if (zig_object.func_types.items[index].eql(func_type)) return index;
789 }
790 return null;
791}
792
793/// Searches for a matching function signature. When no matching signature is found,
794/// a new entry will be made. The value returned is the index of the type within `wasm.func_types`.
795pub fn putOrGetFuncType(zig_object: *ZigObject, gpa: std.mem.Allocator, func_type: std.wasm.Type) !u32 {
796 if (zig_object.getTypeIndex(func_type)) |index| {
797 return index;
798 }
799
800 // functype does not exist.
801 const index: u32 = @intCast(zig_object.func_types.items.len);
802 const params = try gpa.dupe(std.wasm.Valtype, func_type.params);
803 errdefer gpa.free(params);
804 const returns = try gpa.dupe(std.wasm.Valtype, func_type.returns);
805 errdefer gpa.free(returns);
806 try zig_object.func_types.append(gpa, .{
807 .params = params,
808 .returns = returns,
809 });
810 return index;
811}
812
813/// Kind represents the type of an Atom, which is only
814/// used to parse a decl into an Atom to define in which section
815/// or segment it should be placed.
816const Kind = union(enum) {
817 /// Represents the segment the data symbol should
818 /// be inserted into.
819 /// TODO: Add TLS segments
820 data: enum {
821 read_only,
822 uninitialized,
823 initialized,
824 },
825 function: void,
826
827 /// Returns the segment name the data kind represents.
828 /// Asserts `kind` has its active tag set to `data`.
829 fn segmentName(kind: Kind) []const u8 {
830 switch (kind.data) {
831 .read_only => return ".rodata.",
832 .uninitialized => return ".bss.",
833 .initialized => return ".data.",
834 }
835 }
836};
837
838/// Parses an Atom and inserts its metadata into the corresponding sections.
839pub fn parseAtom(zig_object: *ZigObject, wasm_file: *Wasm, atom_index: Atom.Index, kind: Kind) !void {
840 // TODO: Revisit
841 _ = zig_object;
842 _ = wasm_file;
843 _ = atom_index;
844 _ = kind;
845 // const comp = wasm.base.comp;
846 // const gpa = comp.gpa;
847 // const shared_memory = comp.config.shared_memory;
848 // const import_memory = comp.config.import_memory;
849 // const atom = wasm.getAtomPtr(atom_index);
850 // const symbol = (SymbolLoc{ .file = null, .index = atom.sym_index }).getSymbol(wasm);
851 // const do_garbage_collect = wasm.base.gc_sections;
852
853 // if (symbol.isDead() and do_garbage_collect) {
854 // // Prevent unreferenced symbols from being parsed.
855 // return;
856 // }
857
858 // const final_index: u32 = switch (kind) {
859 // .function => result: {
860 // const index: u32 = @intCast(wasm.functions.count() + wasm.imported_functions_count);
861 // const type_index = wasm.atom_types.get(atom_index).?;
862 // try wasm.functions.putNoClobber(
863 // gpa,
864 // .{ .file = null, .index = index },
865 // .{ .func = .{ .type_index = type_index }, .sym_index = atom.sym_index },
866 // );
867 // symbol.tag = .function;
868 // symbol.index = index;
869
870 // if (wasm.code_section_index == null) {
871 // wasm.code_section_index = @intCast(wasm.segments.items.len);
872 // try wasm.segments.append(gpa, .{
873 // .alignment = atom.alignment,
874 // .size = atom.size,
875 // .offset = 0,
876 // .flags = 0,
877 // });
878 // }
879
880 // break :result wasm.code_section_index.?;
881 // },
882 // .data => result: {
883 // const segment_name = try std.mem.concat(gpa, u8, &.{
884 // kind.segmentName(),
885 // wasm.string_table.get(symbol.name),
886 // });
887 // errdefer gpa.free(segment_name);
888 // const segment_info: types.Segment = .{
889 // .name = segment_name,
890 // .alignment = atom.alignment,
891 // .flags = 0,
892 // };
893 // symbol.tag = .data;
894
895 // // when creating an object file, or importing memory and the data belongs in the .bss segment
896 // // we set the entire region of it to zeroes.
897 // // We do not have to do this when exporting the memory (the default) because the runtime
898 // // will do it for us, and we do not emit the bss segment at all.
899 // if ((wasm.base.comp.config.output_mode == .Obj or import_memory) and kind.data == .uninitialized) {
900 // @memset(atom.code.items, 0);
901 // }
902
903 // const should_merge = wasm.base.comp.config.output_mode != .Obj;
904 // const gop = try wasm.data_segments.getOrPut(gpa, segment_info.outputName(should_merge));
905 // if (gop.found_existing) {
906 // const index = gop.value_ptr.*;
907 // wasm.segments.items[index].size += atom.size;
908
909 // symbol.index = @intCast(wasm.segment_info.getIndex(index).?);
910 // // segment info already exists, so free its memory
911 // gpa.free(segment_name);
912 // break :result index;
913 // } else {
914 // const index: u32 = @intCast(wasm.segments.items.len);
915 // var flags: u32 = 0;
916 // if (shared_memory) {
917 // flags |= @intFromEnum(Segment.Flag.WASM_DATA_SEGMENT_IS_PASSIVE);
918 // }
919 // try wasm.segments.append(gpa, .{
920 // .alignment = atom.alignment,
921 // .size = 0,
922 // .offset = 0,
923 // .flags = flags,
924 // });
925 // gop.value_ptr.* = index;
926
927 // const info_index: u32 = @intCast(wasm.segment_info.count());
928 // try wasm.segment_info.put(gpa, index, segment_info);
929 // symbol.index = info_index;
930 // break :result index;
931 // }
932 // },
933 // };
934
935 // const segment: *Segment = &wasm.segments.items[final_index];
936 // segment.alignment = segment.alignment.max(atom.alignment);
937
938 // try wasm.appendAtomAtIndex(final_index, atom_index);
939}
940
941/// Generates an atom containing the global error set' size.
942/// This will only be generated if the symbol exists.
943fn setupErrorsLen(zig_object: *ZigObject, wasm_file: *Wasm) !void {
944 const gpa = wasm_file.base.comp.gpa;
945 const loc = zig_object.findGlobalSymbol("__zig_errors_len") orelse return;
946
947 const errors_len = wasm_file.base.comp.module.?.global_error_set.count();
948 // overwrite existing atom if it already exists (maybe the error set has increased)
949 // if not, allcoate a new atom.
950 const atom_index = if (wasm_file.symbol_atom.get(loc)) |index| blk: {
951 const atom = wasm_file.getAtomPtr(index);
952 if (atom.next) |next_atom_index| {
953 const next_atom = wasm_file.getAtomPtr(next_atom_index);
954 next_atom.prev = atom.prev;
955 atom.next = null;
956 }
957 if (atom.prev) |prev_index| {
958 const prev_atom = wasm_file.getAtomPtr(prev_index);
959 prev_atom.next = atom.next;
960 atom.prev = null;
961 }
962 atom.deinit(gpa);
963 break :blk index;
964 } else new_atom: {
965 const atom_index: Atom.Index = @intCast(wasm_file.managed_atoms.items.len);
966 try wasm_file.symbol_atom.put(gpa, loc, atom_index);
967 try wasm_file.managed_atoms.append(gpa, undefined);
968 break :new_atom atom_index;
969 };
970 const atom = wasm_file.getAtomPtr(atom_index);
971 atom.* = Atom.empty;
972 atom.sym_index = loc.index;
973 atom.size = 2;
974 try atom.code.writer(gpa).writeInt(u16, @intCast(errors_len), .little);
975
976 // try wasm.parseAtom(atom_index, .{ .data = .read_only });
977}
978
979const build_options = @import("build_options");
980const builtin = @import("builtin");
981const codegen = @import("../../codegen.zig");
982const link = @import("../../link.zig");
983const log = std.log.scoped(.zig_object);
984const std = @import("std");
985const types = @import("types.zig");
986
987const Air = @import("../../Air.zig");
988const Atom = @import("Atom.zig");
989const InternPool = @import("../../InternPool.zig");
990const Liveness = @import("../../Liveness.zig");
991const Module = @import("../../Module.zig");
992const StringTable = @import("../StringTable.zig");
993const Symbol = @import("Symbol.zig");
994const Type = @import("../../type.zig").Type;
995const TypedValue = @import("../../TypedValue.zig");
996const Value = @import("../../value.zig").Value;
997const Wasm = @import("../Wasm.zig");
998const ZigObject = @This();