blob 4770da76 (176442B) - Raw
1 const Wasm = @This(); 2 3 const std = @import("std"); 4 5 const assert = std.debug.assert; 6 const build_options = @import("build_options"); 7 const builtin = @import("builtin"); 8 const codegen = @import("../codegen.zig"); 9 const fs = std.fs; 10 const leb = std.leb; 11 const link = @import("../link.zig"); 12 const lldMain = @import("../main.zig").lldMain; 13 const log = std.log.scoped(.link); 14 const mem = std.mem; 15 const trace = @import("../tracy.zig").trace; 16 const types = @import("Wasm/types.zig"); 17 const wasi_libc = @import("../wasi_libc.zig"); 18 19 const Air = @import("../Air.zig"); 20 const Allocator = std.mem.Allocator; 21 const Archive = @import("Wasm/Archive.zig"); 22 const Cache = std.Build.Cache; 23 const CodeGen = @import("../arch/wasm/CodeGen.zig"); 24 const Compilation = @import("../Compilation.zig"); 25 const Dwarf = @import("Dwarf.zig"); 26 const File = @import("Wasm/file.zig").File; 27 const InternPool = @import("../InternPool.zig"); 28 const Liveness = @import("../Liveness.zig"); 29 const LlvmObject = @import("../codegen/llvm.zig").Object; 30 const Module = @import("../Module.zig"); 31 const Object = @import("Wasm/Object.zig"); 32 const Symbol = @import("Wasm/Symbol.zig"); 33 const Type = @import("../type.zig").Type; 34 const TypedValue = @import("../TypedValue.zig"); 35 const Value = @import("../value.zig").Value; 36 const ZigObject = @import("Wasm/ZigObject.zig"); 37 38 pub const Atom = @import("Wasm/Atom.zig"); 39 pub const Relocation = types.Relocation; 40 41 pub const base_tag: link.File.Tag = .wasm; 42 43 base: link.File, 44 /// Symbol name of the entry function to export 45 entry_name: ?[]const u8, 46 /// When true, will allow undefined symbols 47 import_symbols: bool, 48 /// List of *global* symbol names to export to the host environment. 49 export_symbol_names: []const []const u8, 50 /// When defined, sets the start of the data section. 51 global_base: ?u64, 52 /// When defined, sets the initial memory size of the memory. 53 initial_memory: ?u64, 54 /// When defined, sets the maximum memory size of the memory. 55 max_memory: ?u64, 56 /// When true, will import the function table from the host environment. 57 import_table: bool, 58 /// When true, will export the function table to the host environment. 59 export_table: bool, 60 /// Output name of the file 61 name: []const u8, 62 /// If this is not null, an object file is created by LLVM and linked with LLD afterwards. 63 llvm_object: ?*LlvmObject = null, 64 /// The file index of a `ZigObject`. This will only contain a valid index when a zcu exists, 65 /// and the chosen backend is the Wasm backend. 66 zig_object_index: File.Index = .null, 67 /// List of relocatable files to be linked into the final binary. 68 files: std.MultiArrayList(File.Entry) = .{}, 69 /// When importing objects from the host environment, a name must be supplied. 70 /// LLVM uses "env" by default when none is given. This would be a good default for Zig 71 /// to support existing code. 72 /// TODO: Allow setting this through a flag? 73 host_name: []const u8 = "env", 74 /// List of all symbols generated by Zig code. 75 synthetic_symbols: std.ArrayListUnmanaged(Symbol) = .{}, 76 /// Maps atoms to their segment index 77 atoms: std.AutoHashMapUnmanaged(u32, Atom.Index) = .{}, 78 /// List of all atoms. 79 managed_atoms: std.ArrayListUnmanaged(Atom) = .{}, 80 /// Represents the index into `segments` where the 'code' section 81 /// lives. 82 code_section_index: ?u32 = null, 83 /// The index of the segment representing the custom '.debug_info' section. 84 debug_info_index: ?u32 = null, 85 /// The index of the segment representing the custom '.debug_line' section. 86 debug_line_index: ?u32 = null, 87 /// The index of the segment representing the custom '.debug_loc' section. 88 debug_loc_index: ?u32 = null, 89 /// The index of the segment representing the custom '.debug_ranges' section. 90 debug_ranges_index: ?u32 = null, 91 /// The index of the segment representing the custom '.debug_pubnames' section. 92 debug_pubnames_index: ?u32 = null, 93 /// The index of the segment representing the custom '.debug_pubtypes' section. 94 debug_pubtypes_index: ?u32 = null, 95 /// The index of the segment representing the custom '.debug_pubtypes' section. 96 debug_str_index: ?u32 = null, 97 /// The index of the segment representing the custom '.debug_pubtypes' section. 98 debug_abbrev_index: ?u32 = null, 99 /// The count of imported functions. This number will be appended 100 /// to the function indexes as their index starts at the lowest non-extern function. 101 imported_functions_count: u32 = 0, 102 /// The count of imported wasm globals. This number will be appended 103 /// to the global indexes when sections are merged. 104 imported_globals_count: u32 = 0, 105 /// The count of imported tables. This number will be appended 106 /// to the table indexes when sections are merged. 107 imported_tables_count: u32 = 0, 108 /// Map of symbol locations, represented by its `types.Import` 109 imports: std.AutoHashMapUnmanaged(SymbolLoc, types.Import) = .{}, 110 /// Represents non-synthetic section entries. 111 /// Used for code, data and custom sections. 112 segments: std.ArrayListUnmanaged(Segment) = .{}, 113 /// Maps a data segment key (such as .rodata) to the index into `segments`. 114 data_segments: std.StringArrayHashMapUnmanaged(u32) = .{}, 115 /// A table of `types.Segment` which provide meta data 116 /// about a data symbol such as its name where the key is 117 /// the segment index, which can be found from `data_segments` 118 segment_info: std.AutoArrayHashMapUnmanaged(u32, types.Segment) = .{}, 119 /// Deduplicated string table for strings used by symbols, imports and exports. 120 string_table: StringTable = .{}, 121 122 // Output sections 123 /// Output type section 124 func_types: std.ArrayListUnmanaged(std.wasm.Type) = .{}, 125 /// Output function section where the key is the original 126 /// function index and the value is function. 127 /// This allows us to map multiple symbols to the same function. 128 functions: std.AutoArrayHashMapUnmanaged( 129 struct { file: File.Index, index: u32 }, 130 struct { func: std.wasm.Func, sym_index: u32 }, 131 ) = .{}, 132 /// Output global section 133 wasm_globals: std.ArrayListUnmanaged(std.wasm.Global) = .{}, 134 /// Memory section 135 memories: std.wasm.Memory = .{ .limits = .{ 136 .min = 0, 137 .max = undefined, 138 .flags = 0, 139 } }, 140 /// Output table section 141 tables: std.ArrayListUnmanaged(std.wasm.Table) = .{}, 142 /// Output export section 143 exports: std.ArrayListUnmanaged(types.Export) = .{}, 144 /// List of initialization functions. These must be called in order of priority 145 /// by the (synthetic) __wasm_call_ctors function. 146 init_funcs: std.ArrayListUnmanaged(InitFuncLoc) = .{}, 147 /// Index to a function defining the entry of the wasm file 148 entry: ?u32 = null, 149 150 /// Indirect function table, used to call function pointers 151 /// When this is non-zero, we must emit a table entry, 152 /// as well as an 'elements' section. 153 /// 154 /// Note: Key is symbol location, value represents the index into the table 155 function_table: std.AutoHashMapUnmanaged(SymbolLoc, u32) = .{}, 156 157 /// All object files and their data which are linked into the final binary 158 objects: std.ArrayListUnmanaged(File.Index) = .{}, 159 /// All archive files that are lazy loaded. 160 /// e.g. when an undefined symbol references a symbol from the archive. 161 archives: std.ArrayListUnmanaged(Archive) = .{}, 162 163 /// A map of global names (read: offset into string table) to their symbol location 164 globals: std.AutoHashMapUnmanaged(u32, SymbolLoc) = .{}, 165 /// The list of GOT symbols and their location 166 got_symbols: std.ArrayListUnmanaged(SymbolLoc) = .{}, 167 /// Maps discarded symbols and their positions to the location of the symbol 168 /// it was resolved to 169 discarded: std.AutoHashMapUnmanaged(SymbolLoc, SymbolLoc) = .{}, 170 /// List of all symbol locations which have been resolved by the linker and will be emit 171 /// into the final binary. 172 resolved_symbols: std.AutoArrayHashMapUnmanaged(SymbolLoc, void) = .{}, 173 /// Symbols that remain undefined after symbol resolution. 174 /// Note: The key represents an offset into the string table, rather than the actual string. 175 undefs: std.AutoArrayHashMapUnmanaged(u32, SymbolLoc) = .{}, 176 /// Maps a symbol's location to an atom. This can be used to find meta 177 /// data of a symbol, such as its size, or its offset to perform a relocation. 178 /// Undefined (and synthetic) symbols do not have an Atom and therefore cannot be mapped. 179 symbol_atom: std.AutoHashMapUnmanaged(SymbolLoc, Atom.Index) = .{}, 180 /// Maps a symbol's location to its export name, which may differ from the decl's name 181 /// which does the exporting. 182 /// Note: The value represents the offset into the string table, rather than the actual string. 183 export_names: std.AutoHashMapUnmanaged(SymbolLoc, u32) = .{}, 184 185 /// List of atom indexes of functions that are generated by the backend, 186 /// rather than by the linker. 187 synthetic_functions: std.ArrayListUnmanaged(Atom.Index) = .{}, 188 189 pub const Alignment = types.Alignment; 190 191 pub const Segment = struct { 192 alignment: Alignment, 193 size: u32, 194 offset: u32, 195 flags: u32, 196 197 pub const Flag = enum(u32) { 198 WASM_DATA_SEGMENT_IS_PASSIVE = 0x01, 199 WASM_DATA_SEGMENT_HAS_MEMINDEX = 0x02, 200 }; 201 202 pub fn isPassive(segment: Segment) bool { 203 return segment.flags & @intFromEnum(Flag.WASM_DATA_SEGMENT_IS_PASSIVE) != 0; 204 } 205 206 /// For a given segment, determines if it needs passive initialization 207 fn needsPassiveInitialization(segment: Segment, import_mem: bool, name: []const u8) bool { 208 if (import_mem and !std.mem.eql(u8, name, ".bss")) { 209 return true; 210 } 211 return segment.isPassive(); 212 } 213 }; 214 215 pub const Export = struct { 216 sym_index: ?u32 = null, 217 }; 218 219 pub const SymbolLoc = struct { 220 /// The index of the symbol within the specified file 221 index: u32, 222 /// The index of the object file where the symbol resides. 223 file: File.Index, 224 225 /// From a given location, returns the corresponding symbol in the wasm binary 226 pub fn getSymbol(loc: SymbolLoc, wasm_file: *const Wasm) *Symbol { 227 if (wasm_file.discarded.get(loc)) |new_loc| { 228 return new_loc.getSymbol(wasm_file); 229 } 230 if (wasm_file.file(loc.file)) |obj_file| { 231 return obj_file.symbol(loc.index); 232 } 233 return &wasm_file.synthetic_symbols.items[loc.index]; 234 } 235 236 /// From a given location, returns the name of the symbol. 237 pub fn getName(loc: SymbolLoc, wasm_file: *const Wasm) []const u8 { 238 if (wasm_file.discarded.get(loc)) |new_loc| { 239 return new_loc.getName(wasm_file); 240 } 241 if (wasm_file.file(loc.file)) |obj_file| { 242 return obj_file.symbolName(loc.index); 243 } 244 return wasm_file.string_table.get(wasm_file.synthetic_symbols.items[loc.index].name); 245 } 246 247 /// From a given symbol location, returns the final location. 248 /// e.g. when a symbol was resolved and replaced by the symbol 249 /// in a different file, this will return said location. 250 /// If the symbol wasn't replaced by another, this will return 251 /// the given location itwasm. 252 pub fn finalLoc(loc: SymbolLoc, wasm_file: *const Wasm) SymbolLoc { 253 if (wasm_file.discarded.get(loc)) |new_loc| { 254 return new_loc.finalLoc(wasm_file); 255 } 256 return loc; 257 } 258 }; 259 260 // Contains the location of the function symbol, as well as 261 /// the priority itself of the initialization function. 262 pub const InitFuncLoc = struct { 263 /// object file index in the list of objects. 264 /// Unlike `SymbolLoc` this cannot be `null` as we never define 265 /// our own ctors. 266 file: File.Index, 267 /// Symbol index within the corresponding object file. 268 index: u32, 269 /// The priority in which the constructor must be called. 270 priority: u32, 271 272 /// From a given `InitFuncLoc` returns the corresponding function symbol 273 fn getSymbol(loc: InitFuncLoc, wasm: *const Wasm) *Symbol { 274 return getSymbolLoc(loc).getSymbol(wasm); 275 } 276 277 /// Turns the given `InitFuncLoc` into a `SymbolLoc` 278 fn getSymbolLoc(loc: InitFuncLoc) SymbolLoc { 279 return .{ .file = loc.file, .index = loc.index }; 280 } 281 282 /// Returns true when `lhs` has a higher priority (e.i. value closer to 0) than `rhs`. 283 fn lessThan(ctx: void, lhs: InitFuncLoc, rhs: InitFuncLoc) bool { 284 _ = ctx; 285 return lhs.priority < rhs.priority; 286 } 287 }; 288 /// Generic string table that duplicates strings 289 /// and converts them into offsets instead. 290 pub const StringTable = struct { 291 /// Table that maps string offsets, which is used to de-duplicate strings. 292 /// Rather than having the offset map to the data, the `StringContext` holds all bytes of the string. 293 /// The strings are stored as a contigious array where each string is zero-terminated. 294 string_table: std.HashMapUnmanaged( 295 u32, 296 void, 297 std.hash_map.StringIndexContext, 298 std.hash_map.default_max_load_percentage, 299 ) = .{}, 300 /// Holds the actual data of the string table. 301 string_data: std.ArrayListUnmanaged(u8) = .{}, 302 303 /// Accepts a string and searches for a corresponding string. 304 /// When found, de-duplicates the string and returns the existing offset instead. 305 /// When the string is not found in the `string_table`, a new entry will be inserted 306 /// and the new offset to its data will be returned. 307 pub fn put(table: *StringTable, allocator: Allocator, string: []const u8) !u32 { 308 const gop = try table.string_table.getOrPutContextAdapted( 309 allocator, 310 string, 311 std.hash_map.StringIndexAdapter{ .bytes = &table.string_data }, 312 .{ .bytes = &table.string_data }, 313 ); 314 if (gop.found_existing) { 315 const off = gop.key_ptr.*; 316 log.debug("reusing string '{s}' at offset 0x{x}", .{ string, off }); 317 return off; 318 } 319 320 try table.string_data.ensureUnusedCapacity(allocator, string.len + 1); 321 const offset = @as(u32, @intCast(table.string_data.items.len)); 322 323 log.debug("writing new string '{s}' at offset 0x{x}", .{ string, offset }); 324 325 table.string_data.appendSliceAssumeCapacity(string); 326 table.string_data.appendAssumeCapacity(0); 327 328 gop.key_ptr.* = offset; 329 330 return offset; 331 } 332 333 /// From a given offset, returns its corresponding string value. 334 /// Asserts offset does not exceed bounds. 335 pub fn get(table: StringTable, off: u32) []const u8 { 336 assert(off < table.string_data.items.len); 337 return mem.sliceTo(@as([*:0]const u8, @ptrCast(table.string_data.items.ptr + off)), 0); 338 } 339 340 /// Returns the offset of a given string when it exists. 341 /// Will return null if the given string does not yet exist within the string table. 342 pub fn getOffset(table: *StringTable, string: []const u8) ?u32 { 343 return table.string_table.getKeyAdapted( 344 string, 345 std.hash_map.StringIndexAdapter{ .bytes = &table.string_data }, 346 ); 347 } 348 349 /// Frees all resources of the string table. Any references pointing 350 /// to the strings will be invalid. 351 pub fn deinit(table: *StringTable, allocator: Allocator) void { 352 table.string_data.deinit(allocator); 353 table.string_table.deinit(allocator); 354 table.* = undefined; 355 } 356 }; 357 358 pub fn open( 359 arena: Allocator, 360 comp: *Compilation, 361 emit: Compilation.Emit, 362 options: link.File.OpenOptions, 363 ) !*Wasm { 364 // TODO: restore saved linker state, don't truncate the file, and 365 // participate in incremental compilation. 366 return createEmpty(arena, comp, emit, options); 367 } 368 369 pub fn createEmpty( 370 arena: Allocator, 371 comp: *Compilation, 372 emit: Compilation.Emit, 373 options: link.File.OpenOptions, 374 ) !*Wasm { 375 const gpa = comp.gpa; 376 const target = comp.root_mod.resolved_target.result; 377 assert(target.ofmt == .wasm); 378 379 const use_lld = build_options.have_llvm and comp.config.use_lld; 380 const use_llvm = comp.config.use_llvm; 381 const output_mode = comp.config.output_mode; 382 const shared_memory = comp.config.shared_memory; 383 const wasi_exec_model = comp.config.wasi_exec_model; 384 385 // If using LLD to link, this code should produce an object file so that it 386 // can be passed to LLD. 387 // If using LLVM to generate the object file for the zig compilation unit, 388 // we need a place to put the object file so that it can be subsequently 389 // handled. 390 const zcu_object_sub_path = if (!use_lld and !use_llvm) 391 null 392 else 393 try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path}); 394 395 const wasm = try arena.create(Wasm); 396 wasm.* = .{ 397 .base = .{ 398 .tag = .wasm, 399 .comp = comp, 400 .emit = emit, 401 .zcu_object_sub_path = zcu_object_sub_path, 402 .gc_sections = options.gc_sections orelse (output_mode != .Obj), 403 .print_gc_sections = options.print_gc_sections, 404 .stack_size = options.stack_size orelse switch (target.os.tag) { 405 .freestanding => 1 * 1024 * 1024, // 1 MiB 406 else => 16 * 1024 * 1024, // 16 MiB 407 }, 408 .allow_shlib_undefined = options.allow_shlib_undefined orelse false, 409 .file = null, 410 .disable_lld_caching = options.disable_lld_caching, 411 .build_id = options.build_id, 412 .rpath_list = options.rpath_list, 413 }, 414 .name = undefined, 415 .import_table = options.import_table, 416 .export_table = options.export_table, 417 .import_symbols = options.import_symbols, 418 .export_symbol_names = options.export_symbol_names, 419 .global_base = options.global_base, 420 .initial_memory = options.initial_memory, 421 .max_memory = options.max_memory, 422 423 .entry_name = switch (options.entry) { 424 .disabled => null, 425 .default => if (output_mode != .Exe) null else defaultEntrySymbolName(wasi_exec_model), 426 .enabled => defaultEntrySymbolName(wasi_exec_model), 427 .named => |name| name, 428 }, 429 }; 430 if (use_llvm and comp.config.have_zcu) { 431 wasm.llvm_object = try LlvmObject.create(arena, comp); 432 } 433 errdefer wasm.base.destroy(); 434 435 if (use_lld and (use_llvm or !comp.config.have_zcu)) { 436 // LLVM emits the object file (if any); LLD links it into the final product. 437 return wasm; 438 } 439 440 // What path should this Wasm linker code output to? 441 // If using LLD to link, this code should produce an object file so that it 442 // can be passed to LLD. 443 const sub_path = if (use_lld) zcu_object_sub_path.? else emit.sub_path; 444 445 wasm.base.file = try emit.directory.handle.createFile(sub_path, .{ 446 .truncate = true, 447 .read = true, 448 .mode = if (fs.has_executable_bit) 449 if (target.os.tag == .wasi and output_mode == .Exe) 450 fs.File.default_mode | 0b001_000_000 451 else 452 fs.File.default_mode 453 else 454 0, 455 }); 456 wasm.name = sub_path; 457 458 // create stack pointer symbol 459 { 460 const loc = try wasm.createSyntheticSymbol("__stack_pointer", .global); 461 const symbol = loc.getSymbol(wasm); 462 // For object files we will import the stack pointer symbol 463 if (output_mode == .Obj) { 464 symbol.setUndefined(true); 465 symbol.index = @intCast(wasm.imported_globals_count); 466 wasm.imported_globals_count += 1; 467 try wasm.imports.putNoClobber( 468 gpa, 469 loc, 470 .{ 471 .module_name = try wasm.string_table.put(gpa, wasm.host_name), 472 .name = symbol.name, 473 .kind = .{ .global = .{ .valtype = .i32, .mutable = true } }, 474 }, 475 ); 476 } else { 477 symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len); 478 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); 479 const global = try wasm.wasm_globals.addOne(gpa); 480 global.* = .{ 481 .global_type = .{ 482 .valtype = .i32, 483 .mutable = true, 484 }, 485 .init = .{ .i32_const = 0 }, 486 }; 487 } 488 } 489 490 // create indirect function pointer symbol 491 { 492 const loc = try wasm.createSyntheticSymbol("__indirect_function_table", .table); 493 const symbol = loc.getSymbol(wasm); 494 const table: std.wasm.Table = .{ 495 .limits = .{ .flags = 0, .min = 0, .max = undefined }, // will be overwritten during `mapFunctionTable` 496 .reftype = .funcref, 497 }; 498 if (output_mode == .Obj or options.import_table) { 499 symbol.setUndefined(true); 500 symbol.index = @intCast(wasm.imported_tables_count); 501 wasm.imported_tables_count += 1; 502 try wasm.imports.put(gpa, loc, .{ 503 .module_name = try wasm.string_table.put(gpa, wasm.host_name), 504 .name = symbol.name, 505 .kind = .{ .table = table }, 506 }); 507 } else { 508 symbol.index = @as(u32, @intCast(wasm.imported_tables_count + wasm.tables.items.len)); 509 try wasm.tables.append(gpa, table); 510 if (wasm.export_table) { 511 symbol.setFlag(.WASM_SYM_EXPORTED); 512 } else { 513 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); 514 } 515 } 516 } 517 518 // create __wasm_call_ctors 519 { 520 const loc = try wasm.createSyntheticSymbol("__wasm_call_ctors", .function); 521 const symbol = loc.getSymbol(wasm); 522 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); 523 // we do not know the function index until after we merged all sections. 524 // Therefore we set `symbol.index` and create its corresponding references 525 // at the end during `initializeCallCtorsFunction`. 526 } 527 528 // shared-memory symbols for TLS support 529 if (shared_memory) { 530 { 531 const loc = try wasm.createSyntheticSymbol("__tls_base", .global); 532 const symbol = loc.getSymbol(wasm); 533 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); 534 symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len); 535 try wasm.wasm_globals.append(gpa, .{ 536 .global_type = .{ .valtype = .i32, .mutable = true }, 537 .init = .{ .i32_const = undefined }, 538 }); 539 } 540 { 541 const loc = try wasm.createSyntheticSymbol("__tls_size", .global); 542 const symbol = loc.getSymbol(wasm); 543 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); 544 symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len); 545 try wasm.wasm_globals.append(gpa, .{ 546 .global_type = .{ .valtype = .i32, .mutable = false }, 547 .init = .{ .i32_const = undefined }, 548 }); 549 } 550 { 551 const loc = try wasm.createSyntheticSymbol("__tls_align", .global); 552 const symbol = loc.getSymbol(wasm); 553 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); 554 symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len); 555 try wasm.wasm_globals.append(gpa, .{ 556 .global_type = .{ .valtype = .i32, .mutable = false }, 557 .init = .{ .i32_const = undefined }, 558 }); 559 } 560 { 561 const loc = try wasm.createSyntheticSymbol("__wasm_init_tls", .function); 562 const symbol = loc.getSymbol(wasm); 563 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); 564 } 565 } 566 567 if (comp.module) |zcu| { 568 if (!use_llvm) { 569 const index: File.Index = @enumFromInt(wasm.files.len); 570 var zig_object: ZigObject = .{ 571 .index = index, 572 .path = try std.fmt.allocPrint(gpa, "{s}.o", .{std.fs.path.stem(zcu.main_mod.root_src_path)}), 573 .stack_pointer_sym = undefined, 574 }; 575 try zig_object.init(wasm); 576 try wasm.files.append(gpa, .{ .zig_object = zig_object }); 577 wasm.zig_object_index = index; 578 } 579 } 580 581 return wasm; 582 } 583 584 pub fn file(wasm: *const Wasm, index: File.Index) ?File { 585 if (index == .null) return null; 586 const tag = wasm.files.items(.tags)[@intFromEnum(index)]; 587 return switch (tag) { 588 .zig_object => .{ .zig_object = &wasm.files.items(.data)[@intFromEnum(index)].zig_object }, 589 .object => .{ .object = &wasm.files.items(.data)[@intFromEnum(index)].object }, 590 }; 591 } 592 593 pub fn zigObjectPtr(wasm: *Wasm) ?*ZigObject { 594 if (wasm.zig_object_index == .null) return null; 595 return &wasm.files.items(.data)[@intFromEnum(wasm.zig_object_index)].zig_object; 596 } 597 598 pub fn getTypeIndex(wasm: *const Wasm, func_type: std.wasm.Type) ?u32 { 599 var index: u32 = 0; 600 while (index < wasm.func_types.items.len) : (index += 1) { 601 if (wasm.func_types.items[index].eql(func_type)) return index; 602 } 603 return null; 604 } 605 606 /// Either creates a new import, or updates one if existing. 607 /// When `type_index` is non-null, we assume an external function. 608 /// In all other cases, a data-symbol will be created instead. 609 pub fn addOrUpdateImport( 610 wasm: *Wasm, 611 /// Name of the import 612 name: []const u8, 613 /// Symbol index that is external 614 symbol_index: u32, 615 /// Optional library name (i.e. `extern "c" fn foo() void` 616 lib_name: ?[:0]const u8, 617 /// The index of the type that represents the function signature 618 /// when the extern is a function. When this is null, a data-symbol 619 /// is asserted instead. 620 type_index: ?u32, 621 ) !void { 622 return wasm.zigObjectPtr().?.addOrUpdateImport(wasm, name, symbol_index, lib_name, type_index); 623 } 624 625 /// For a given name, creates a new global synthetic symbol. 626 /// Leaves index undefined and the default flags (0). 627 fn createSyntheticSymbol(wasm: *Wasm, name: []const u8, tag: Symbol.Tag) !SymbolLoc { 628 const gpa = wasm.base.comp.gpa; 629 const name_offset = try wasm.string_table.put(gpa, name); 630 return wasm.createSyntheticSymbolOffset(name_offset, tag); 631 } 632 633 fn createSyntheticSymbolOffset(wasm: *Wasm, name_offset: u32, tag: Symbol.Tag) !SymbolLoc { 634 const sym_index = @as(u32, @intCast(wasm.synthetic_symbols.items.len)); 635 const loc: SymbolLoc = .{ .index = sym_index, .file = .null }; 636 const gpa = wasm.base.comp.gpa; 637 try wasm.synthetic_symbols.append(gpa, .{ 638 .name = name_offset, 639 .flags = 0, 640 .tag = tag, 641 .index = undefined, 642 .virtual_address = undefined, 643 }); 644 try wasm.resolved_symbols.putNoClobber(gpa, loc, {}); 645 try wasm.globals.put(gpa, name_offset, loc); 646 return loc; 647 } 648 649 fn parseInputFiles(wasm: *Wasm, files: []const []const u8) !void { 650 for (files) |path| { 651 if (try wasm.parseObjectFile(path)) continue; 652 if (try wasm.parseArchive(path, false)) continue; // load archives lazily 653 log.warn("Unexpected file format at path: '{s}'", .{path}); 654 } 655 } 656 657 /// Parses the object file from given path. Returns true when the given file was an object 658 /// file and parsed successfully. Returns false when file is not an object file. 659 /// May return an error instead when parsing failed. 660 fn parseObjectFile(wasm: *Wasm, path: []const u8) !bool { 661 const obj_file = try fs.cwd().openFile(path, .{}); 662 errdefer obj_file.close(); 663 664 const gpa = wasm.base.comp.gpa; 665 var object = Object.create(gpa, obj_file, path, null) catch |err| switch (err) { 666 error.InvalidMagicByte, error.NotObjectFile => return false, 667 else => |e| return e, 668 }; 669 errdefer object.deinit(gpa); 670 object.index = @enumFromInt(wasm.files.len); 671 try wasm.files.append(gpa, .{ .object = object }); 672 try wasm.objects.append(gpa, object.index); 673 return true; 674 } 675 676 /// Creates a new empty `Atom` and returns its `Atom.Index` 677 pub fn createAtom(wasm: *Wasm, sym_index: u32, file_index: File.Index) !Atom.Index { 678 const gpa = wasm.base.comp.gpa; 679 const index: Atom.Index = @intCast(wasm.managed_atoms.items.len); 680 const atom = try wasm.managed_atoms.addOne(gpa); 681 atom.* = .{ .file = file_index, .sym_index = sym_index }; 682 try wasm.symbol_atom.putNoClobber(gpa, atom.symbolLoc(), index); 683 684 return index; 685 } 686 687 pub inline fn getAtom(wasm: *const Wasm, index: Atom.Index) Atom { 688 return wasm.managed_atoms.items[index]; 689 } 690 691 pub inline fn getAtomPtr(wasm: *Wasm, index: Atom.Index) *Atom { 692 return &wasm.managed_atoms.items[index]; 693 } 694 695 /// Parses an archive file and will then parse each object file 696 /// that was found in the archive file. 697 /// Returns false when the file is not an archive file. 698 /// May return an error instead when parsing failed. 699 /// 700 /// When `force_load` is `true`, it will for link all object files in the archive. 701 /// When false, it will only link with object files that contain symbols that 702 /// are referenced by other object files or Zig code. 703 fn parseArchive(wasm: *Wasm, path: []const u8, force_load: bool) !bool { 704 const gpa = wasm.base.comp.gpa; 705 706 const archive_file = try fs.cwd().openFile(path, .{}); 707 errdefer archive_file.close(); 708 709 var archive: Archive = .{ 710 .file = archive_file, 711 .name = path, 712 }; 713 archive.parse(gpa) catch |err| switch (err) { 714 error.EndOfStream, error.NotArchive => { 715 archive.deinit(gpa); 716 return false; 717 }, 718 else => |e| return e, 719 }; 720 721 if (!force_load) { 722 errdefer archive.deinit(gpa); 723 try wasm.archives.append(gpa, archive); 724 return true; 725 } 726 defer archive.deinit(gpa); 727 728 // In this case we must force link all embedded object files within the archive 729 // We loop over all symbols, and then group them by offset as the offset 730 // notates where the object file starts. 731 var offsets = std.AutoArrayHashMap(u32, void).init(gpa); 732 defer offsets.deinit(); 733 for (archive.toc.values()) |symbol_offsets| { 734 for (symbol_offsets.items) |sym_offset| { 735 try offsets.put(sym_offset, {}); 736 } 737 } 738 739 for (offsets.keys()) |file_offset| { 740 var object = try archive.parseObject(gpa, file_offset); 741 object.index = @enumFromInt(wasm.files.len); 742 try wasm.files.append(gpa, .{ .object = object }); 743 try wasm.objects.append(gpa, object.index); 744 } 745 746 return true; 747 } 748 749 fn requiresTLSReloc(wasm: *const Wasm) bool { 750 for (wasm.got_symbols.items) |loc| { 751 if (loc.getSymbol(wasm).isTLS()) { 752 return true; 753 } 754 } 755 return false; 756 } 757 758 fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void { 759 const gpa = wasm.base.comp.gpa; 760 const obj_file = wasm.file(file_index).?; 761 log.debug("Resolving symbols in object: '{s}'", .{obj_file.path()}); 762 763 for (obj_file.symbols(), 0..) |symbol, i| { 764 const sym_index: u32 = @intCast(i); 765 const location: SymbolLoc = .{ .file = file_index, .index = sym_index }; 766 const sym_name = obj_file.string(symbol.name); 767 if (mem.eql(u8, sym_name, "__indirect_function_table")) { 768 continue; 769 } 770 const sym_name_index = try wasm.string_table.put(gpa, sym_name); 771 772 if (symbol.isLocal()) { 773 if (symbol.isUndefined()) { 774 log.err("Local symbols are not allowed to reference imports", .{}); 775 log.err(" symbol '{s}' defined in '{s}'", .{ sym_name, obj_file.path() }); 776 return error.UndefinedLocal; 777 } 778 try wasm.resolved_symbols.putNoClobber(gpa, location, {}); 779 continue; 780 } 781 782 const maybe_existing = try wasm.globals.getOrPut(gpa, sym_name_index); 783 if (!maybe_existing.found_existing) { 784 maybe_existing.value_ptr.* = location; 785 try wasm.resolved_symbols.putNoClobber(gpa, location, {}); 786 787 if (symbol.isUndefined()) { 788 try wasm.undefs.putNoClobber(gpa, sym_name_index, location); 789 } 790 continue; 791 } 792 793 const existing_loc = maybe_existing.value_ptr.*; 794 const existing_sym: *Symbol = existing_loc.getSymbol(wasm); 795 const existing_file = wasm.file(existing_loc.file); 796 797 const existing_file_path = if (existing_file) |existing_obj_file| 798 existing_obj_file.path() 799 else 800 wasm.name; 801 802 if (!existing_sym.isUndefined()) outer: { 803 if (!symbol.isUndefined()) inner: { 804 if (symbol.isWeak()) { 805 break :inner; // ignore the new symbol (discard it) 806 } 807 if (existing_sym.isWeak()) { 808 break :outer; // existing is weak, while new one isn't. Replace it. 809 } 810 // both are defined and weak, we have a symbol collision. 811 log.err("symbol '{s}' defined multiple times", .{sym_name}); 812 log.err(" first definition in '{s}'", .{existing_file_path}); 813 log.err(" next definition in '{s}'", .{obj_file.path()}); 814 return error.SymbolCollision; 815 } 816 817 try wasm.discarded.put(gpa, location, existing_loc); 818 continue; // Do not overwrite defined symbols with undefined symbols 819 } 820 821 if (symbol.tag != existing_sym.tag) { 822 log.err("symbol '{s}' mismatching types '{s}' and '{s}'", .{ sym_name, @tagName(symbol.tag), @tagName(existing_sym.tag) }); 823 log.err(" first definition in '{s}'", .{existing_file_path}); 824 log.err(" next definition in '{s}'", .{obj_file.path()}); 825 return error.SymbolMismatchingType; 826 } 827 828 if (existing_sym.isUndefined() and symbol.isUndefined()) { 829 // only verify module/import name for function symbols 830 if (symbol.tag == .function) { 831 const existing_name = if (existing_file) |existing_obj| blk: { 832 const imp = existing_obj.import(existing_loc.index); 833 break :blk existing_obj.string(imp.module_name); 834 } else blk: { 835 const name_index = wasm.imports.get(existing_loc).?.module_name; 836 break :blk wasm.string_table.get(name_index); 837 }; 838 839 const imp = obj_file.import(sym_index); 840 const module_name = obj_file.string(imp.module_name); 841 if (!mem.eql(u8, existing_name, module_name)) { 842 log.err("symbol '{s}' module name mismatch. Expected '{s}', but found '{s}'", .{ 843 sym_name, 844 existing_name, 845 module_name, 846 }); 847 log.err(" first definition in '{s}'", .{existing_file_path}); 848 log.err(" next definition in '{s}'", .{obj_file.path()}); 849 return error.ModuleNameMismatch; 850 } 851 } 852 853 // both undefined so skip overwriting existing symbol and discard the new symbol 854 try wasm.discarded.put(gpa, location, existing_loc); 855 continue; 856 } 857 858 if (existing_sym.tag == .global) { 859 const existing_ty = wasm.getGlobalType(existing_loc); 860 const new_ty = wasm.getGlobalType(location); 861 if (existing_ty.mutable != new_ty.mutable or existing_ty.valtype != new_ty.valtype) { 862 log.err("symbol '{s}' mismatching global types", .{sym_name}); 863 log.err(" first definition in '{s}'", .{existing_file_path}); 864 log.err(" next definition in '{s}'", .{obj_file.path()}); 865 return error.GlobalTypeMismatch; 866 } 867 } 868 869 if (existing_sym.tag == .function) { 870 const existing_ty = wasm.getFunctionSignature(existing_loc); 871 const new_ty = wasm.getFunctionSignature(location); 872 if (!existing_ty.eql(new_ty)) { 873 log.err("symbol '{s}' mismatching function signatures.", .{sym_name}); 874 log.err(" expected signature {}, but found signature {}", .{ existing_ty, new_ty }); 875 log.err(" first definition in '{s}'", .{existing_file_path}); 876 log.err(" next definition in '{s}'", .{obj_file.path()}); 877 return error.FunctionSignatureMismatch; 878 } 879 } 880 881 // when both symbols are weak, we skip overwriting unless the existing 882 // symbol is weak and the new one isn't, in which case we *do* overwrite it. 883 if (existing_sym.isWeak() and symbol.isWeak()) blk: { 884 if (existing_sym.isUndefined() and !symbol.isUndefined()) break :blk; 885 try wasm.discarded.put(gpa, location, existing_loc); 886 continue; 887 } 888 889 // simply overwrite with the new symbol 890 log.debug("Overwriting symbol '{s}'", .{sym_name}); 891 log.debug(" old definition in '{s}'", .{existing_file_path}); 892 log.debug(" new definition in '{s}'", .{obj_file.path()}); 893 try wasm.discarded.putNoClobber(gpa, existing_loc, location); 894 maybe_existing.value_ptr.* = location; 895 try wasm.globals.put(gpa, sym_name_index, location); 896 try wasm.resolved_symbols.put(gpa, location, {}); 897 assert(wasm.resolved_symbols.swapRemove(existing_loc)); 898 if (existing_sym.isUndefined()) { 899 _ = wasm.undefs.swapRemove(sym_name_index); 900 } 901 } 902 } 903 904 fn resolveSymbolsInArchives(wasm: *Wasm) !void { 905 const gpa = wasm.base.comp.gpa; 906 if (wasm.archives.items.len == 0) return; 907 908 log.debug("Resolving symbols in archives", .{}); 909 var index: u32 = 0; 910 undef_loop: while (index < wasm.undefs.count()) { 911 const sym_name_index = wasm.undefs.keys()[index]; 912 913 for (wasm.archives.items) |archive| { 914 const sym_name = wasm.string_table.get(sym_name_index); 915 log.debug("Detected symbol '{s}' in archive '{s}', parsing objects..", .{ sym_name, archive.name }); 916 const offset = archive.toc.get(sym_name) orelse { 917 // symbol does not exist in this archive 918 continue; 919 }; 920 921 // Symbol is found in unparsed object file within current archive. 922 // Parse object and and resolve symbols again before we check remaining 923 // undefined symbols. 924 var object = try archive.parseObject(gpa, offset.items[0]); 925 object.index = @enumFromInt(wasm.files.len); 926 try wasm.files.append(gpa, .{ .object = object }); 927 try wasm.objects.append(gpa, object.index); 928 try wasm.resolveSymbolsInObject(object.index); 929 930 // continue loop for any remaining undefined symbols that still exist 931 // after resolving last object file 932 continue :undef_loop; 933 } 934 index += 1; 935 } 936 } 937 938 /// Writes an unsigned 32-bit integer as a LEB128-encoded 'i32.const' value. 939 fn writeI32Const(writer: anytype, val: u32) !void { 940 try writer.writeByte(std.wasm.opcode(.i32_const)); 941 try leb.writeILEB128(writer, @as(i32, @bitCast(val))); 942 } 943 944 fn setupInitMemoryFunction(wasm: *Wasm) !void { 945 const comp = wasm.base.comp; 946 const gpa = comp.gpa; 947 const shared_memory = comp.config.shared_memory; 948 const import_memory = comp.config.import_memory; 949 950 // Passive segments are used to avoid memory being reinitialized on each 951 // thread's instantiation. These passive segments are initialized and 952 // dropped in __wasm_init_memory, which is registered as the start function 953 // We also initialize bss segments (using memory.fill) as part of this 954 // function. 955 if (!wasm.hasPassiveInitializationSegments()) { 956 return; 957 } 958 959 const flag_address: u32 = if (shared_memory) address: { 960 // when we have passive initialization segments and shared memory 961 // `setupMemory` will create this symbol and set its virtual address. 962 const loc = wasm.findGlobalSymbol("__wasm_init_memory_flag").?; 963 break :address loc.getSymbol(wasm).virtual_address; 964 } else 0; 965 966 var function_body = std.ArrayList(u8).init(gpa); 967 defer function_body.deinit(); 968 const writer = function_body.writer(); 969 970 // we have 0 locals 971 try leb.writeULEB128(writer, @as(u32, 0)); 972 973 if (shared_memory) { 974 // destination blocks 975 // based on values we jump to corresponding label 976 try writer.writeByte(std.wasm.opcode(.block)); // $drop 977 try writer.writeByte(std.wasm.block_empty); // block type 978 979 try writer.writeByte(std.wasm.opcode(.block)); // $wait 980 try writer.writeByte(std.wasm.block_empty); // block type 981 982 try writer.writeByte(std.wasm.opcode(.block)); // $init 983 try writer.writeByte(std.wasm.block_empty); // block type 984 985 // atomically check 986 try writeI32Const(writer, flag_address); 987 try writeI32Const(writer, 0); 988 try writeI32Const(writer, 1); 989 try writer.writeByte(std.wasm.opcode(.atomics_prefix)); 990 try leb.writeULEB128(writer, std.wasm.atomicsOpcode(.i32_atomic_rmw_cmpxchg)); 991 try leb.writeULEB128(writer, @as(u32, 2)); // alignment 992 try leb.writeULEB128(writer, @as(u32, 0)); // offset 993 994 // based on the value from the atomic check, jump to the label. 995 try writer.writeByte(std.wasm.opcode(.br_table)); 996 try leb.writeULEB128(writer, @as(u32, 2)); // length of the table (we have 3 blocks but because of the mandatory default the length is 2). 997 try leb.writeULEB128(writer, @as(u32, 0)); // $init 998 try leb.writeULEB128(writer, @as(u32, 1)); // $wait 999 try leb.writeULEB128(writer, @as(u32, 2)); // $drop 1000 try writer.writeByte(std.wasm.opcode(.end)); 1001 } 1002 1003 var it = wasm.data_segments.iterator(); 1004 var segment_index: u32 = 0; 1005 while (it.next()) |entry| : (segment_index += 1) { 1006 const segment: Segment = wasm.segments.items[entry.value_ptr.*]; 1007 if (segment.needsPassiveInitialization(import_memory, entry.key_ptr.*)) { 1008 // For passive BSS segments we can simple issue a memory.fill(0). 1009 // For non-BSS segments we do a memory.init. Both these 1010 // instructions take as their first argument the destination 1011 // address. 1012 try writeI32Const(writer, segment.offset); 1013 1014 if (shared_memory and std.mem.eql(u8, entry.key_ptr.*, ".tdata")) { 1015 // When we initialize the TLS segment we also set the `__tls_base` 1016 // global. This allows the runtime to use this static copy of the 1017 // TLS data for the first/main thread. 1018 try writeI32Const(writer, segment.offset); 1019 try writer.writeByte(std.wasm.opcode(.global_set)); 1020 const loc = wasm.findGlobalSymbol("__tls_base").?; 1021 try leb.writeULEB128(writer, loc.getSymbol(wasm).index); 1022 } 1023 1024 try writeI32Const(writer, 0); 1025 try writeI32Const(writer, segment.size); 1026 try writer.writeByte(std.wasm.opcode(.misc_prefix)); 1027 if (std.mem.eql(u8, entry.key_ptr.*, ".bss")) { 1028 // fill bss segment with zeroes 1029 try leb.writeULEB128(writer, std.wasm.miscOpcode(.memory_fill)); 1030 } else { 1031 // initialize the segment 1032 try leb.writeULEB128(writer, std.wasm.miscOpcode(.memory_init)); 1033 try leb.writeULEB128(writer, segment_index); 1034 } 1035 try writer.writeByte(0); // memory index immediate 1036 } 1037 } 1038 1039 if (shared_memory) { 1040 // we set the init memory flag to value '2' 1041 try writeI32Const(writer, flag_address); 1042 try writeI32Const(writer, 2); 1043 try writer.writeByte(std.wasm.opcode(.atomics_prefix)); 1044 try leb.writeULEB128(writer, std.wasm.atomicsOpcode(.i32_atomic_store)); 1045 try leb.writeULEB128(writer, @as(u32, 2)); // alignment 1046 try leb.writeULEB128(writer, @as(u32, 0)); // offset 1047 1048 // notify any waiters for segment initialization completion 1049 try writeI32Const(writer, flag_address); 1050 try writer.writeByte(std.wasm.opcode(.i32_const)); 1051 try leb.writeILEB128(writer, @as(i32, -1)); // number of waiters 1052 try writer.writeByte(std.wasm.opcode(.atomics_prefix)); 1053 try leb.writeULEB128(writer, std.wasm.atomicsOpcode(.memory_atomic_notify)); 1054 try leb.writeULEB128(writer, @as(u32, 2)); // alignment 1055 try leb.writeULEB128(writer, @as(u32, 0)); // offset 1056 try writer.writeByte(std.wasm.opcode(.drop)); 1057 1058 // branch and drop segments 1059 try writer.writeByte(std.wasm.opcode(.br)); 1060 try leb.writeULEB128(writer, @as(u32, 1)); 1061 1062 // wait for thread to initialize memory segments 1063 try writer.writeByte(std.wasm.opcode(.end)); // end $wait 1064 try writeI32Const(writer, flag_address); 1065 try writeI32Const(writer, 1); // expected flag value 1066 try writer.writeByte(std.wasm.opcode(.i64_const)); 1067 try leb.writeILEB128(writer, @as(i64, -1)); // timeout 1068 try writer.writeByte(std.wasm.opcode(.atomics_prefix)); 1069 try leb.writeULEB128(writer, std.wasm.atomicsOpcode(.memory_atomic_wait32)); 1070 try leb.writeULEB128(writer, @as(u32, 2)); // alignment 1071 try leb.writeULEB128(writer, @as(u32, 0)); // offset 1072 try writer.writeByte(std.wasm.opcode(.drop)); 1073 1074 try writer.writeByte(std.wasm.opcode(.end)); // end $drop 1075 } 1076 1077 it.reset(); 1078 segment_index = 0; 1079 while (it.next()) |entry| : (segment_index += 1) { 1080 const name = entry.key_ptr.*; 1081 const segment: Segment = wasm.segments.items[entry.value_ptr.*]; 1082 if (segment.needsPassiveInitialization(import_memory, name) and 1083 !std.mem.eql(u8, name, ".bss")) 1084 { 1085 // The TLS region should not be dropped since its is needed 1086 // during the initialization of each thread (__wasm_init_tls). 1087 if (shared_memory and std.mem.eql(u8, name, ".tdata")) { 1088 continue; 1089 } 1090 1091 try writer.writeByte(std.wasm.opcode(.misc_prefix)); 1092 try leb.writeULEB128(writer, std.wasm.miscOpcode(.data_drop)); 1093 try leb.writeULEB128(writer, segment_index); 1094 } 1095 } 1096 1097 // End of the function body 1098 try writer.writeByte(std.wasm.opcode(.end)); 1099 1100 try wasm.createSyntheticFunction( 1101 "__wasm_init_memory", 1102 std.wasm.Type{ .params = &.{}, .returns = &.{} }, 1103 &function_body, 1104 ); 1105 } 1106 1107 /// Constructs a synthetic function that performs runtime relocations for 1108 /// TLS symbols. This function is called by `__wasm_init_tls`. 1109 fn setupTLSRelocationsFunction(wasm: *Wasm) !void { 1110 const comp = wasm.base.comp; 1111 const gpa = comp.gpa; 1112 const shared_memory = comp.config.shared_memory; 1113 1114 // When we have TLS GOT entries and shared memory is enabled, 1115 // we must perform runtime relocations or else we don't create the function. 1116 if (!shared_memory or !wasm.requiresTLSReloc()) { 1117 return; 1118 } 1119 1120 // const loc = try wasm.createSyntheticSymbol("__wasm_apply_global_tls_relocs"); 1121 var function_body = std.ArrayList(u8).init(gpa); 1122 defer function_body.deinit(); 1123 const writer = function_body.writer(); 1124 1125 // locals (we have none) 1126 try writer.writeByte(0); 1127 for (wasm.got_symbols.items, 0..) |got_loc, got_index| { 1128 const sym: *Symbol = got_loc.getSymbol(wasm); 1129 if (!sym.isTLS()) continue; // only relocate TLS symbols 1130 if (sym.tag == .data and sym.isDefined()) { 1131 // get __tls_base 1132 try writer.writeByte(std.wasm.opcode(.global_get)); 1133 try leb.writeULEB128(writer, wasm.findGlobalSymbol("__tls_base").?.getSymbol(wasm).index); 1134 1135 // add the virtual address of the symbol 1136 try writer.writeByte(std.wasm.opcode(.i32_const)); 1137 try leb.writeULEB128(writer, sym.virtual_address); 1138 } else if (sym.tag == .function) { 1139 @panic("TODO: relocate GOT entry of function"); 1140 } else continue; 1141 1142 try writer.writeByte(std.wasm.opcode(.i32_add)); 1143 try writer.writeByte(std.wasm.opcode(.global_set)); 1144 try leb.writeULEB128(writer, wasm.imported_globals_count + @as(u32, @intCast(wasm.wasm_globals.items.len + got_index))); 1145 } 1146 try writer.writeByte(std.wasm.opcode(.end)); 1147 1148 try wasm.createSyntheticFunction( 1149 "__wasm_apply_global_tls_relocs", 1150 std.wasm.Type{ .params = &.{}, .returns = &.{} }, 1151 &function_body, 1152 ); 1153 } 1154 1155 fn validateFeatures( 1156 wasm: *const Wasm, 1157 to_emit: *[@typeInfo(types.Feature.Tag).Enum.fields.len]bool, 1158 emit_features_count: *u32, 1159 ) !void { 1160 const comp = wasm.base.comp; 1161 const target = comp.root_mod.resolved_target.result; 1162 const shared_memory = comp.config.shared_memory; 1163 const cpu_features = target.cpu.features; 1164 const infer = cpu_features.isEmpty(); // when the user did not define any features, we infer them from linked objects. 1165 const known_features_count = @typeInfo(types.Feature.Tag).Enum.fields.len; 1166 1167 var allowed = [_]bool{false} ** known_features_count; 1168 var used = [_]u17{0} ** known_features_count; 1169 var disallowed = [_]u17{0} ** known_features_count; 1170 var required = [_]u17{0} ** known_features_count; 1171 1172 // when false, we fail linking. We only verify this after a loop to catch all invalid features. 1173 var valid_feature_set = true; 1174 // will be set to true when there's any TLS segment found in any of the object files 1175 var has_tls = false; 1176 1177 // When the user has given an explicit list of features to enable, 1178 // we extract them and insert each into the 'allowed' list. 1179 if (!infer) { 1180 inline for (@typeInfo(std.Target.wasm.Feature).Enum.fields) |feature_field| { 1181 if (cpu_features.isEnabled(feature_field.value)) { 1182 allowed[feature_field.value] = true; 1183 emit_features_count.* += 1; 1184 } 1185 } 1186 } 1187 1188 // extract all the used, disallowed and required features from each 1189 // linked object file so we can test them. 1190 for (wasm.objects.items) |file_index| { 1191 const object: Object = wasm.files.items(.data)[@intFromEnum(file_index)].object; 1192 for (object.features) |feature| { 1193 const value = @as(u16, @intFromEnum(file_index)) << 1 | @as(u1, 1); 1194 switch (feature.prefix) { 1195 .used => { 1196 used[@intFromEnum(feature.tag)] = value; 1197 }, 1198 .disallowed => { 1199 disallowed[@intFromEnum(feature.tag)] = value; 1200 }, 1201 .required => { 1202 required[@intFromEnum(feature.tag)] = value; 1203 used[@intFromEnum(feature.tag)] = value; 1204 }, 1205 } 1206 } 1207 1208 for (object.segment_info) |segment| { 1209 if (segment.isTLS()) { 1210 has_tls = true; 1211 } 1212 } 1213 } 1214 1215 // when we infer the features, we allow each feature found in the 'used' set 1216 // and insert it into the 'allowed' set. When features are not inferred, 1217 // we validate that a used feature is allowed. 1218 for (used, 0..) |used_set, used_index| { 1219 const is_enabled = @as(u1, @truncate(used_set)) != 0; 1220 if (infer) { 1221 allowed[used_index] = is_enabled; 1222 emit_features_count.* += @intFromBool(is_enabled); 1223 } else if (is_enabled and !allowed[used_index]) { 1224 log.err("feature '{}' not allowed, but used by linked object", .{@as(types.Feature.Tag, @enumFromInt(used_index))}); 1225 log.err(" defined in '{s}'", .{wasm.files.items(.data)[used_set >> 1].object.path}); 1226 valid_feature_set = false; 1227 } 1228 } 1229 1230 if (!valid_feature_set) { 1231 return error.InvalidFeatureSet; 1232 } 1233 1234 if (shared_memory) { 1235 const disallowed_feature = disallowed[@intFromEnum(types.Feature.Tag.shared_mem)]; 1236 if (@as(u1, @truncate(disallowed_feature)) != 0) { 1237 log.err( 1238 "shared-memory is disallowed by '{s}' because it wasn't compiled with 'atomics' and 'bulk-memory' features enabled", 1239 .{wasm.files.items(.data)[disallowed_feature >> 1].object.path}, 1240 ); 1241 valid_feature_set = false; 1242 } 1243 1244 for ([_]types.Feature.Tag{ .atomics, .bulk_memory }) |feature| { 1245 if (!allowed[@intFromEnum(feature)]) { 1246 log.err("feature '{}' is not used but is required for shared-memory", .{feature}); 1247 } 1248 } 1249 } 1250 1251 if (has_tls) { 1252 for ([_]types.Feature.Tag{ .atomics, .bulk_memory }) |feature| { 1253 if (!allowed[@intFromEnum(feature)]) { 1254 log.err("feature '{}' is not used but is required for thread-local storage", .{feature}); 1255 } 1256 } 1257 } 1258 // For each linked object, validate the required and disallowed features 1259 for (wasm.objects.items) |file_index| { 1260 var object_used_features = [_]bool{false} ** known_features_count; 1261 const object = wasm.files.items(.data)[@intFromEnum(file_index)].object; 1262 for (object.features) |feature| { 1263 if (feature.prefix == .disallowed) continue; // already defined in 'disallowed' set. 1264 // from here a feature is always used 1265 const disallowed_feature = disallowed[@intFromEnum(feature.tag)]; 1266 if (@as(u1, @truncate(disallowed_feature)) != 0) { 1267 log.err("feature '{}' is disallowed, but used by linked object", .{feature.tag}); 1268 log.err(" disallowed by '{s}'", .{wasm.files.items(.data)[disallowed_feature >> 1].object.path}); 1269 log.err(" used in '{s}'", .{object.path}); 1270 valid_feature_set = false; 1271 } 1272 1273 object_used_features[@intFromEnum(feature.tag)] = true; 1274 } 1275 1276 // validate the linked object file has each required feature 1277 for (required, 0..) |required_feature, feature_index| { 1278 const is_required = @as(u1, @truncate(required_feature)) != 0; 1279 if (is_required and !object_used_features[feature_index]) { 1280 log.err("feature '{}' is required but not used in linked object", .{@as(types.Feature.Tag, @enumFromInt(feature_index))}); 1281 log.err(" required by '{s}'", .{wasm.files.items(.data)[required_feature >> 1].object.path}); 1282 log.err(" missing in '{s}'", .{object.path}); 1283 valid_feature_set = false; 1284 } 1285 } 1286 } 1287 1288 if (!valid_feature_set) { 1289 return error.InvalidFeatureSet; 1290 } 1291 1292 to_emit.* = allowed; 1293 } 1294 1295 /// Creates synthetic linker-symbols, but only if they are being referenced from 1296 /// any object file. For instance, the `__heap_base` symbol will only be created, 1297 /// if one or multiple undefined references exist. When none exist, the symbol will 1298 /// not be created, ensuring we don't unneccesarily emit unreferenced symbols. 1299 fn resolveLazySymbols(wasm: *Wasm) !void { 1300 const comp = wasm.base.comp; 1301 const gpa = comp.gpa; 1302 const shared_memory = comp.config.shared_memory; 1303 1304 if (wasm.string_table.getOffset("__heap_base")) |name_offset| { 1305 if (wasm.undefs.fetchSwapRemove(name_offset)) |kv| { 1306 const loc = try wasm.createSyntheticSymbolOffset(name_offset, .data); 1307 try wasm.discarded.putNoClobber(gpa, kv.value, loc); 1308 _ = wasm.resolved_symbols.swapRemove(loc); // we don't want to emit this symbol, only use it for relocations. 1309 } 1310 } 1311 1312 if (wasm.string_table.getOffset("__heap_end")) |name_offset| { 1313 if (wasm.undefs.fetchSwapRemove(name_offset)) |kv| { 1314 const loc = try wasm.createSyntheticSymbolOffset(name_offset, .data); 1315 try wasm.discarded.putNoClobber(gpa, kv.value, loc); 1316 _ = wasm.resolved_symbols.swapRemove(loc); 1317 } 1318 } 1319 1320 if (!shared_memory) { 1321 if (wasm.string_table.getOffset("__tls_base")) |name_offset| { 1322 if (wasm.undefs.fetchSwapRemove(name_offset)) |kv| { 1323 const loc = try wasm.createSyntheticSymbolOffset(name_offset, .global); 1324 try wasm.discarded.putNoClobber(gpa, kv.value, loc); 1325 _ = wasm.resolved_symbols.swapRemove(kv.value); 1326 const symbol = loc.getSymbol(wasm); 1327 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); 1328 symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len); 1329 try wasm.wasm_globals.append(gpa, .{ 1330 .global_type = .{ .valtype = .i32, .mutable = true }, 1331 .init = .{ .i32_const = undefined }, 1332 }); 1333 } 1334 } 1335 } 1336 if (wasm.string_table.getOffset("__zig_errors_len")) |name_offset| { 1337 if (wasm.undefs.fetchSwapRemove(name_offset)) |kv| { 1338 const loc = try wasm.createSyntheticSymbolOffset(name_offset, .data); 1339 try wasm.discarded.putNoClobber(gpa, kv.value, loc); 1340 _ = wasm.resolved_symbols.swapRemove(kv.value); 1341 } 1342 } 1343 } 1344 1345 // Tries to find a global symbol by its name. Returns null when not found, 1346 /// and its location when it is found. 1347 pub fn findGlobalSymbol(wasm: *Wasm, name: []const u8) ?SymbolLoc { 1348 const offset = wasm.string_table.getOffset(name) orelse return null; 1349 return wasm.globals.get(offset); 1350 } 1351 1352 fn checkUndefinedSymbols(wasm: *const Wasm) !void { 1353 const comp = wasm.base.comp; 1354 if (comp.config.output_mode == .Obj) return; 1355 if (wasm.import_symbols) return; 1356 1357 var found_undefined_symbols = false; 1358 for (wasm.undefs.values()) |undef| { 1359 const symbol = undef.getSymbol(wasm); 1360 if (symbol.tag == .data) { 1361 found_undefined_symbols = true; 1362 const file_name = if (wasm.file(undef.file)) |obj_file| 1363 obj_file.path() 1364 else 1365 wasm.name; 1366 const symbol_name = undef.getName(wasm); 1367 log.err("could not resolve undefined symbol '{s}'", .{symbol_name}); 1368 log.err(" defined in '{s}'", .{file_name}); 1369 } 1370 } 1371 if (found_undefined_symbols) { 1372 return error.UndefinedSymbol; 1373 } 1374 } 1375 1376 pub fn deinit(wasm: *Wasm) void { 1377 const gpa = wasm.base.comp.gpa; 1378 if (wasm.llvm_object) |llvm_object| llvm_object.deinit(); 1379 1380 for (wasm.func_types.items) |*func_type| { 1381 func_type.deinit(gpa); 1382 } 1383 for (wasm.segment_info.values()) |segment_info| { 1384 gpa.free(segment_info.name); 1385 } 1386 if (wasm.zigObjectPtr()) |zig_obj| { 1387 zig_obj.deinit(wasm); 1388 } 1389 for (wasm.objects.items) |obj_index| { 1390 wasm.file(obj_index).?.object.deinit(gpa); 1391 } 1392 1393 for (wasm.archives.items) |*archive| { 1394 archive.deinit(gpa); 1395 } 1396 1397 for (wasm.synthetic_functions.items) |atom_index| { 1398 const atom = wasm.getAtomPtr(atom_index); 1399 atom.deinit(gpa); 1400 } 1401 1402 wasm.synthetic_symbols.deinit(gpa); 1403 wasm.globals.deinit(gpa); 1404 wasm.resolved_symbols.deinit(gpa); 1405 wasm.undefs.deinit(gpa); 1406 wasm.discarded.deinit(gpa); 1407 wasm.symbol_atom.deinit(gpa); 1408 wasm.export_names.deinit(gpa); 1409 wasm.atoms.deinit(gpa); 1410 wasm.managed_atoms.deinit(gpa); 1411 wasm.segments.deinit(gpa); 1412 wasm.data_segments.deinit(gpa); 1413 wasm.segment_info.deinit(gpa); 1414 wasm.objects.deinit(gpa); 1415 wasm.archives.deinit(gpa); 1416 1417 // free output sections 1418 wasm.imports.deinit(gpa); 1419 wasm.func_types.deinit(gpa); 1420 wasm.functions.deinit(gpa); 1421 wasm.wasm_globals.deinit(gpa); 1422 wasm.function_table.deinit(gpa); 1423 wasm.tables.deinit(gpa); 1424 wasm.init_funcs.deinit(gpa); 1425 wasm.exports.deinit(gpa); 1426 1427 wasm.string_table.deinit(gpa); 1428 wasm.synthetic_functions.deinit(gpa); 1429 } 1430 1431 pub fn updateFunc(wasm: *Wasm, mod: *Module, func_index: InternPool.Index, air: Air, liveness: Liveness) !void { 1432 if (build_options.skip_non_native and builtin.object_format != .wasm) { 1433 @panic("Attempted to compile for object format that was disabled by build configuration"); 1434 } 1435 if (wasm.llvm_object) |llvm_object| return llvm_object.updateFunc(mod, func_index, air, liveness); 1436 try wasm.zigObjectPtr().?.updateFunc(wasm, mod, func_index, air, liveness); 1437 } 1438 1439 // Generate code for the Decl, storing it in memory to be later written to 1440 // the file on flush(). 1441 pub fn updateDecl(wasm: *Wasm, mod: *Module, decl_index: InternPool.DeclIndex) !void { 1442 if (build_options.skip_non_native and builtin.object_format != .wasm) { 1443 @panic("Attempted to compile for object format that was disabled by build configuration"); 1444 } 1445 if (wasm.llvm_object) |llvm_object| return llvm_object.updateDecl(mod, decl_index); 1446 try wasm.zigObjectPtr().?.updateDecl(wasm, mod, decl_index); 1447 } 1448 1449 pub fn updateDeclLineNumber(wasm: *Wasm, mod: *Module, decl_index: InternPool.DeclIndex) !void { 1450 if (wasm.llvm_object) |_| return; 1451 try wasm.zigObjectPtr().?.updateDeclLineNumber(mod, decl_index); 1452 } 1453 1454 /// From a given symbol location, returns its `wasm.GlobalType`. 1455 /// Asserts the Symbol represents a global. 1456 fn getGlobalType(wasm: *const Wasm, loc: SymbolLoc) std.wasm.GlobalType { 1457 const symbol = loc.getSymbol(wasm); 1458 assert(symbol.tag == .global); 1459 const is_undefined = symbol.isUndefined(); 1460 if (wasm.file(loc.file)) |obj_file| { 1461 if (is_undefined) { 1462 return obj_file.import(loc.index).kind.global; 1463 } 1464 return obj_file.globals()[symbol.index - obj_file.importedGlobals()].global_type; 1465 } 1466 if (is_undefined) { 1467 return wasm.imports.get(loc).?.kind.global; 1468 } 1469 return wasm.wasm_globals.items[symbol.index].global_type; 1470 } 1471 1472 /// From a given symbol location, returns its `wasm.Type`. 1473 /// Asserts the Symbol represents a function. 1474 fn getFunctionSignature(wasm: *const Wasm, loc: SymbolLoc) std.wasm.Type { 1475 const symbol = loc.getSymbol(wasm); 1476 assert(symbol.tag == .function); 1477 const is_undefined = symbol.isUndefined(); 1478 if (wasm.file(loc.file)) |obj_file| { 1479 if (is_undefined) { 1480 const ty_index = obj_file.import(loc.index).kind.function; 1481 return obj_file.funcTypes()[ty_index]; 1482 } 1483 const type_index = obj_file.function(loc.index).type_index; 1484 return obj_file.funcTypes()[type_index]; 1485 } 1486 if (is_undefined) { 1487 const ty_index = wasm.imports.get(loc).?.kind.function; 1488 return wasm.func_types.items[ty_index]; 1489 } 1490 return wasm.func_types.items[wasm.functions.get(.{ .file = loc.file, .index = symbol.index }).?.func.type_index]; 1491 } 1492 1493 /// Lowers a constant typed value to a local symbol and atom. 1494 /// Returns the symbol index of the local 1495 /// The given `decl` is the parent decl whom owns the constant. 1496 pub fn lowerUnnamedConst(wasm: *Wasm, tv: TypedValue, decl_index: InternPool.DeclIndex) !u32 { 1497 return wasm.zigObjectPtr().?.lowerUnnamedConst(wasm, tv, decl_index); 1498 } 1499 1500 /// Returns the symbol index from a symbol of which its flag is set global, 1501 /// such as an exported or imported symbol. 1502 /// If the symbol does not yet exist, creates a new one symbol instead 1503 /// and then returns the index to it. 1504 pub fn getGlobalSymbol(wasm: *Wasm, name: []const u8, lib_name: ?[]const u8) !u32 { 1505 _ = lib_name; 1506 return wasm.zigObjectPtr().?.getGlobalSymbol(wasm.base.comp.gpa, name); 1507 } 1508 1509 /// For a given decl, find the given symbol index's atom, and create a relocation for the type. 1510 /// Returns the given pointer address 1511 pub fn getDeclVAddr( 1512 wasm: *Wasm, 1513 decl_index: InternPool.DeclIndex, 1514 reloc_info: link.File.RelocInfo, 1515 ) !u64 { 1516 return wasm.zigObjectPtr().?.getDeclVAddr(wasm, decl_index, reloc_info); 1517 } 1518 1519 pub fn lowerAnonDecl( 1520 wasm: *Wasm, 1521 decl_val: InternPool.Index, 1522 explicit_alignment: Alignment, 1523 src_loc: Module.SrcLoc, 1524 ) !codegen.Result { 1525 return wasm.zigObjectPtr().?.lowerAnonDecl(wasm, decl_val, explicit_alignment, src_loc); 1526 } 1527 1528 pub fn getAnonDeclVAddr(wasm: *Wasm, decl_val: InternPool.Index, reloc_info: link.File.RelocInfo) !u64 { 1529 return wasm.zigObjectPtr().?.getAnonDeclVAddr(wasm, decl_val, reloc_info); 1530 } 1531 1532 pub fn deleteDeclExport( 1533 wasm: *Wasm, 1534 decl_index: InternPool.DeclIndex, 1535 name: InternPool.NullTerminatedString, 1536 ) void { 1537 if (wasm.llvm_object) |_| return; 1538 return wasm.zigObjectPtr().?.deleteDeclExport(wasm, decl_index, name); 1539 } 1540 1541 pub fn updateExports( 1542 wasm: *Wasm, 1543 mod: *Module, 1544 exported: Module.Exported, 1545 exports: []const *Module.Export, 1546 ) !void { 1547 if (build_options.skip_non_native and builtin.object_format != .wasm) { 1548 @panic("Attempted to compile for object format that was disabled by build configuration"); 1549 } 1550 if (wasm.llvm_object) |llvm_object| return llvm_object.updateExports(mod, exported, exports); 1551 return wasm.zigObjectPtr().?.updateExports(wasm, mod, exported, exports); 1552 } 1553 1554 pub fn freeDecl(wasm: *Wasm, decl_index: InternPool.DeclIndex) void { 1555 if (wasm.llvm_object) |llvm_object| return llvm_object.freeDecl(decl_index); 1556 return wasm.zigObjectPtr().?.freeDecl(wasm, decl_index); 1557 } 1558 1559 /// Assigns indexes to all indirect functions. 1560 /// Starts at offset 1, where the value `0` represents an unresolved function pointer 1561 /// or null-pointer 1562 fn mapFunctionTable(wasm: *Wasm) void { 1563 var it = wasm.function_table.iterator(); 1564 var index: u32 = 1; 1565 while (it.next()) |entry| { 1566 const symbol = entry.key_ptr.*.getSymbol(wasm); 1567 if (symbol.isAlive()) { 1568 entry.value_ptr.* = index; 1569 index += 1; 1570 } else { 1571 wasm.function_table.removeByPtr(entry.key_ptr); 1572 } 1573 } 1574 1575 if (wasm.import_table or wasm.base.comp.config.output_mode == .Obj) { 1576 const sym_loc = wasm.findGlobalSymbol("__indirect_function_table").?; 1577 const import = wasm.imports.getPtr(sym_loc).?; 1578 import.kind.table.limits.min = index - 1; // we start at index 1. 1579 } else if (index > 1) { 1580 log.debug("Appending indirect function table", .{}); 1581 const sym_loc = wasm.findGlobalSymbol("__indirect_function_table").?; 1582 const symbol = sym_loc.getSymbol(wasm); 1583 const table = &wasm.tables.items[symbol.index - wasm.imported_tables_count]; 1584 table.limits = .{ .min = index, .max = index, .flags = 0x1 }; 1585 } 1586 } 1587 1588 /// From a given index, append the given `Atom` at the back of the linked list. 1589 /// Simply inserts it into the map of atoms when it doesn't exist yet. 1590 pub fn appendAtomAtIndex(wasm: *Wasm, index: u32, atom_index: Atom.Index) !void { 1591 const gpa = wasm.base.comp.gpa; 1592 const atom = wasm.getAtomPtr(atom_index); 1593 if (wasm.atoms.getPtr(index)) |last_index_ptr| { 1594 atom.prev = last_index_ptr.*; 1595 last_index_ptr.* = atom_index; 1596 } else { 1597 try wasm.atoms.putNoClobber(gpa, index, atom_index); 1598 } 1599 } 1600 1601 fn allocateAtoms(wasm: *Wasm) !void { 1602 // first sort the data segments 1603 try sortDataSegments(wasm); 1604 1605 var it = wasm.atoms.iterator(); 1606 while (it.next()) |entry| { 1607 const segment = &wasm.segments.items[entry.key_ptr.*]; 1608 var atom_index = entry.value_ptr.*; 1609 if (entry.key_ptr.* == wasm.code_section_index) { 1610 // Code section is allocated upon writing as they are required to be ordered 1611 // to synchronise with the function section. 1612 continue; 1613 } 1614 var offset: u32 = 0; 1615 while (true) { 1616 const atom = wasm.getAtomPtr(atom_index); 1617 const symbol_loc = atom.symbolLoc(); 1618 // Ensure we get the original symbol, so we verify the correct symbol on whether 1619 // it is dead or not and ensure an atom is removed when dead. 1620 // This is required as we may have parsed aliases into atoms. 1621 const sym = if (wasm.file(symbol_loc.file)) |obj_file| 1622 obj_file.symbol(symbol_loc.index).* 1623 else 1624 wasm.synthetic_symbols.items[symbol_loc.index]; 1625 1626 // Dead symbols must be unlinked from the linked-list to prevent them 1627 // from being emit into the binary. 1628 if (sym.isDead()) { 1629 if (entry.value_ptr.* == atom_index and atom.prev != null) { 1630 // When the atom is dead and is also the first atom retrieved from wasm.atoms(index) we update 1631 // the entry to point it to the previous atom to ensure we do not start with a dead symbol that 1632 // was removed and therefore do not emit any code at all. 1633 entry.value_ptr.* = atom.prev.?; 1634 } 1635 atom_index = atom.prev orelse break; 1636 atom.prev = null; 1637 continue; 1638 } 1639 offset = @intCast(atom.alignment.forward(offset)); 1640 atom.offset = offset; 1641 log.debug("Atom '{s}' allocated from 0x{x:0>8} to 0x{x:0>8} size={d}", .{ 1642 symbol_loc.getName(wasm), 1643 offset, 1644 offset + atom.size, 1645 atom.size, 1646 }); 1647 offset += atom.size; 1648 atom_index = atom.prev orelse break; 1649 } 1650 segment.size = @intCast(segment.alignment.forward(offset)); 1651 } 1652 } 1653 1654 /// For each data symbol, sets the virtual address. 1655 fn allocateVirtualAddresses(wasm: *Wasm) void { 1656 for (wasm.resolved_symbols.keys()) |loc| { 1657 const symbol = loc.getSymbol(wasm); 1658 if (symbol.tag != .data or symbol.isDead()) { 1659 // Only data symbols have virtual addresses. 1660 // Dead symbols do not get allocated, so we don't need to set their virtual address either. 1661 continue; 1662 } 1663 const atom_index = wasm.symbol_atom.get(loc) orelse { 1664 // synthetic symbol that does not contain an atom 1665 continue; 1666 }; 1667 1668 const atom = wasm.getAtom(atom_index); 1669 const merge_segment = wasm.base.comp.config.output_mode != .Obj; 1670 const segment_info = if (atom.file != .null) 1671 wasm.file(atom.file).?.segmentInfo() 1672 else 1673 wasm.segment_info.values(); 1674 const segment_name = segment_info[symbol.index].outputName(merge_segment); 1675 const segment_index = wasm.data_segments.get(segment_name).?; 1676 const segment = wasm.segments.items[segment_index]; 1677 1678 // TLS symbols have their virtual address set relative to their own TLS segment, 1679 // rather than the entire Data section. 1680 if (symbol.hasFlag(.WASM_SYM_TLS)) { 1681 symbol.virtual_address = atom.offset; 1682 } else { 1683 symbol.virtual_address = atom.offset + segment.offset; 1684 } 1685 } 1686 } 1687 1688 fn sortDataSegments(wasm: *Wasm) !void { 1689 const gpa = wasm.base.comp.gpa; 1690 var new_mapping: std.StringArrayHashMapUnmanaged(u32) = .{}; 1691 try new_mapping.ensureUnusedCapacity(gpa, wasm.data_segments.count()); 1692 errdefer new_mapping.deinit(gpa); 1693 1694 const keys = try gpa.dupe([]const u8, wasm.data_segments.keys()); 1695 defer gpa.free(keys); 1696 1697 const SortContext = struct { 1698 fn sort(_: void, lhs: []const u8, rhs: []const u8) bool { 1699 return order(lhs) < order(rhs); 1700 } 1701 1702 fn order(name: []const u8) u8 { 1703 if (mem.startsWith(u8, name, ".rodata")) return 0; 1704 if (mem.startsWith(u8, name, ".data")) return 1; 1705 if (mem.startsWith(u8, name, ".text")) return 2; 1706 return 3; 1707 } 1708 }; 1709 1710 mem.sort([]const u8, keys, {}, SortContext.sort); 1711 for (keys) |key| { 1712 const segment_index = wasm.data_segments.get(key).?; 1713 new_mapping.putAssumeCapacity(key, segment_index); 1714 } 1715 wasm.data_segments.deinit(gpa); 1716 wasm.data_segments = new_mapping; 1717 } 1718 1719 /// Obtains all initfuncs from each object file, verifies its function signature, 1720 /// and then appends it to our final `init_funcs` list. 1721 /// After all functions have been inserted, the functions will be ordered based 1722 /// on their priority. 1723 /// NOTE: This function must be called before we merged any other section. 1724 /// This is because all init funcs in the object files contain references to the 1725 /// original functions and their types. We need to know the type to verify it doesn't 1726 /// contain any parameters. 1727 fn setupInitFunctions(wasm: *Wasm) !void { 1728 const gpa = wasm.base.comp.gpa; 1729 // There's no constructors for Zig so we can simply search through linked object files only. 1730 for (wasm.objects.items) |file_index| { 1731 const object: Object = wasm.files.items(.data)[@intFromEnum(file_index)].object; 1732 try wasm.init_funcs.ensureUnusedCapacity(gpa, object.init_funcs.len); 1733 for (object.init_funcs) |init_func| { 1734 const symbol = object.symtable[init_func.symbol_index]; 1735 const ty: std.wasm.Type = if (symbol.isUndefined()) ty: { 1736 const imp: types.Import = object.findImport(symbol); 1737 break :ty object.func_types[imp.kind.function]; 1738 } else ty: { 1739 const func_index = symbol.index - object.imported_functions_count; 1740 const func = object.functions[func_index]; 1741 break :ty object.func_types[func.type_index]; 1742 }; 1743 if (ty.params.len != 0) { 1744 log.err("constructor functions cannot take arguments: '{s}'", .{object.string_table.get(symbol.name)}); 1745 return error.InvalidInitFunc; 1746 } 1747 log.debug("appended init func '{s}'\n", .{object.string_table.get(symbol.name)}); 1748 wasm.init_funcs.appendAssumeCapacity(.{ 1749 .index = init_func.symbol_index, 1750 .file = file_index, 1751 .priority = init_func.priority, 1752 }); 1753 try wasm.mark(.{ .index = init_func.symbol_index, .file = file_index }); 1754 } 1755 } 1756 1757 // sort the initfunctions based on their priority 1758 mem.sort(InitFuncLoc, wasm.init_funcs.items, {}, InitFuncLoc.lessThan); 1759 1760 if (wasm.init_funcs.items.len > 0) { 1761 const loc = wasm.findGlobalSymbol("__wasm_call_ctors").?; 1762 try wasm.mark(loc); 1763 } 1764 } 1765 1766 /// Creates a function body for the `__wasm_call_ctors` symbol. 1767 /// Loops over all constructors found in `init_funcs` and calls them 1768 /// respectively based on their priority which was sorted by `setupInitFunctions`. 1769 /// NOTE: This function must be called after we merged all sections to ensure the 1770 /// references to the function stored in the symbol have been finalized so we end 1771 /// up calling the resolved function. 1772 fn initializeCallCtorsFunction(wasm: *Wasm) !void { 1773 const gpa = wasm.base.comp.gpa; 1774 // No code to emit, so also no ctors to call 1775 if (wasm.code_section_index == null) { 1776 // Make sure to remove it from the resolved symbols so we do not emit 1777 // it within any section. TODO: Remove this once we implement garbage collection. 1778 const loc = wasm.findGlobalSymbol("__wasm_call_ctors").?; 1779 assert(wasm.resolved_symbols.swapRemove(loc)); 1780 return; 1781 } 1782 1783 var function_body = std.ArrayList(u8).init(gpa); 1784 defer function_body.deinit(); 1785 const writer = function_body.writer(); 1786 1787 // Create the function body 1788 { 1789 // Write locals count (we have none) 1790 try leb.writeULEB128(writer, @as(u32, 0)); 1791 1792 // call constructors 1793 for (wasm.init_funcs.items) |init_func_loc| { 1794 const symbol = init_func_loc.getSymbol(wasm); 1795 const func = wasm.functions.values()[symbol.index - wasm.imported_functions_count].func; 1796 const ty = wasm.func_types.items[func.type_index]; 1797 1798 // Call function by its function index 1799 try writer.writeByte(std.wasm.opcode(.call)); 1800 try leb.writeULEB128(writer, symbol.index); 1801 1802 // drop all returned values from the stack as __wasm_call_ctors has no return value 1803 for (ty.returns) |_| { 1804 try writer.writeByte(std.wasm.opcode(.drop)); 1805 } 1806 } 1807 1808 // End function body 1809 try writer.writeByte(std.wasm.opcode(.end)); 1810 } 1811 1812 try wasm.createSyntheticFunction( 1813 "__wasm_call_ctors", 1814 std.wasm.Type{ .params = &.{}, .returns = &.{} }, 1815 &function_body, 1816 ); 1817 } 1818 1819 fn createSyntheticFunction( 1820 wasm: *Wasm, 1821 symbol_name: []const u8, 1822 func_ty: std.wasm.Type, 1823 function_body: *std.ArrayList(u8), 1824 ) !void { 1825 const gpa = wasm.base.comp.gpa; 1826 const loc = wasm.findGlobalSymbol(symbol_name) orelse 1827 try wasm.createSyntheticSymbol(symbol_name, .function); 1828 const symbol = loc.getSymbol(wasm); 1829 if (symbol.isDead()) { 1830 return; 1831 } 1832 const ty_index = try wasm.putOrGetFuncType(func_ty); 1833 // create function with above type 1834 const func_index = wasm.imported_functions_count + @as(u32, @intCast(wasm.functions.count())); 1835 try wasm.functions.putNoClobber( 1836 gpa, 1837 .{ .file = .null, .index = func_index }, 1838 .{ .func = .{ .type_index = ty_index }, .sym_index = loc.index }, 1839 ); 1840 symbol.index = func_index; 1841 1842 // create the atom that will be output into the final binary 1843 const atom_index = try wasm.createAtom(loc.index, .null); 1844 const atom = wasm.getAtomPtr(atom_index); 1845 atom.size = @intCast(function_body.items.len); 1846 atom.code = function_body.moveToUnmanaged(); 1847 try wasm.appendAtomAtIndex(wasm.code_section_index.?, atom_index); 1848 } 1849 1850 /// Unlike `createSyntheticFunction` this function is to be called by 1851 /// the codegeneration backend. This will not allocate the created Atom yet. 1852 /// Returns the index of the symbol. 1853 pub fn createFunction( 1854 wasm: *Wasm, 1855 symbol_name: []const u8, 1856 func_ty: std.wasm.Type, 1857 function_body: *std.ArrayList(u8), 1858 relocations: *std.ArrayList(Relocation), 1859 ) !u32 { 1860 return wasm.zigObjectPtr().?.createFunction(wasm, symbol_name, func_ty, function_body, relocations); 1861 } 1862 1863 /// If required, sets the function index in the `start` section. 1864 fn setupStartSection(wasm: *Wasm) !void { 1865 if (wasm.findGlobalSymbol("__wasm_init_memory")) |loc| { 1866 wasm.entry = loc.getSymbol(wasm).index; 1867 } 1868 } 1869 1870 fn initializeTLSFunction(wasm: *Wasm) !void { 1871 const comp = wasm.base.comp; 1872 const gpa = comp.gpa; 1873 const shared_memory = comp.config.shared_memory; 1874 1875 if (!shared_memory) return; 1876 1877 var function_body = std.ArrayList(u8).init(gpa); 1878 defer function_body.deinit(); 1879 const writer = function_body.writer(); 1880 1881 // locals 1882 try writer.writeByte(0); 1883 1884 // If there's a TLS segment, initialize it during runtime using the bulk-memory feature 1885 if (wasm.data_segments.getIndex(".tdata")) |data_index| { 1886 const segment_index = wasm.data_segments.entries.items(.value)[data_index]; 1887 const segment = wasm.segments.items[segment_index]; 1888 1889 const param_local: u32 = 0; 1890 1891 try writer.writeByte(std.wasm.opcode(.local_get)); 1892 try leb.writeULEB128(writer, param_local); 1893 1894 const tls_base_loc = wasm.findGlobalSymbol("__tls_base").?; 1895 try writer.writeByte(std.wasm.opcode(.global_set)); 1896 try leb.writeULEB128(writer, tls_base_loc.getSymbol(wasm).index); 1897 1898 // load stack values for the bulk-memory operation 1899 { 1900 try writer.writeByte(std.wasm.opcode(.local_get)); 1901 try leb.writeULEB128(writer, param_local); 1902 1903 try writer.writeByte(std.wasm.opcode(.i32_const)); 1904 try leb.writeULEB128(writer, @as(u32, 0)); //segment offset 1905 1906 try writer.writeByte(std.wasm.opcode(.i32_const)); 1907 try leb.writeULEB128(writer, @as(u32, segment.size)); //segment offset 1908 } 1909 1910 // perform the bulk-memory operation to initialize the data segment 1911 try writer.writeByte(std.wasm.opcode(.misc_prefix)); 1912 try leb.writeULEB128(writer, std.wasm.miscOpcode(.memory_init)); 1913 // segment immediate 1914 try leb.writeULEB128(writer, @as(u32, @intCast(data_index))); 1915 // memory index immediate (always 0) 1916 try leb.writeULEB128(writer, @as(u32, 0)); 1917 } 1918 1919 // If we have to perform any TLS relocations, call the corresponding function 1920 // which performs all runtime TLS relocations. This is a synthetic function, 1921 // generated by the linker. 1922 if (wasm.findGlobalSymbol("__wasm_apply_global_tls_relocs")) |loc| { 1923 try writer.writeByte(std.wasm.opcode(.call)); 1924 try leb.writeULEB128(writer, loc.getSymbol(wasm).index); 1925 } 1926 1927 try writer.writeByte(std.wasm.opcode(.end)); 1928 1929 try wasm.createSyntheticFunction( 1930 "__wasm_init_tls", 1931 std.wasm.Type{ .params = &.{.i32}, .returns = &.{} }, 1932 &function_body, 1933 ); 1934 } 1935 1936 fn setupImports(wasm: *Wasm) !void { 1937 const gpa = wasm.base.comp.gpa; 1938 log.debug("Merging imports", .{}); 1939 for (wasm.resolved_symbols.keys()) |symbol_loc| { 1940 const obj_file = wasm.file(symbol_loc.file) orelse { 1941 // Synthetic symbols will already exist in the `import` section 1942 continue; 1943 }; 1944 1945 const symbol = symbol_loc.getSymbol(wasm); 1946 if (symbol.isDead() or 1947 !symbol.requiresImport() or 1948 std.mem.eql(u8, symbol_loc.getName(wasm), "__indirect_function_table")) 1949 { 1950 continue; 1951 } 1952 1953 log.debug("Symbol '{s}' will be imported from the host", .{symbol_loc.getName(wasm)}); 1954 const import = obj_file.import(symbol_loc.index); 1955 1956 // We copy the import to a new import to ensure the names contain references 1957 // to the internal string table, rather than of the object file. 1958 const new_imp: types.Import = .{ 1959 .module_name = try wasm.string_table.put(gpa, obj_file.string(import.module_name)), 1960 .name = try wasm.string_table.put(gpa, obj_file.string(import.name)), 1961 .kind = import.kind, 1962 }; 1963 // TODO: De-duplicate imports when they contain the same names and type 1964 try wasm.imports.putNoClobber(gpa, symbol_loc, new_imp); 1965 } 1966 1967 // Assign all indexes of the imports to their representing symbols 1968 var function_index: u32 = 0; 1969 var global_index: u32 = 0; 1970 var table_index: u32 = 0; 1971 var it = wasm.imports.iterator(); 1972 while (it.next()) |entry| { 1973 const symbol = entry.key_ptr.*.getSymbol(wasm); 1974 const import: types.Import = entry.value_ptr.*; 1975 switch (import.kind) { 1976 .function => { 1977 symbol.index = function_index; 1978 function_index += 1; 1979 }, 1980 .global => { 1981 symbol.index = global_index; 1982 global_index += 1; 1983 }, 1984 .table => { 1985 symbol.index = table_index; 1986 table_index += 1; 1987 }, 1988 else => unreachable, 1989 } 1990 } 1991 wasm.imported_functions_count = function_index; 1992 wasm.imported_globals_count = global_index; 1993 wasm.imported_tables_count = table_index; 1994 1995 log.debug("Merged ({d}) functions, ({d}) globals, and ({d}) tables into import section", .{ 1996 function_index, 1997 global_index, 1998 table_index, 1999 }); 2000 } 2001 2002 /// Takes the global, function and table section from each linked object file 2003 /// and merges it into a single section for each. 2004 fn mergeSections(wasm: *Wasm) !void { 2005 const gpa = wasm.base.comp.gpa; 2006 2007 var removed_duplicates = std.ArrayList(SymbolLoc).init(gpa); 2008 defer removed_duplicates.deinit(); 2009 2010 for (wasm.resolved_symbols.keys()) |sym_loc| { 2011 const obj_file = wasm.file(sym_loc.file) orelse { 2012 // Zig code-generated symbols are already within the sections and do not 2013 // require to be merged 2014 continue; 2015 }; 2016 2017 const symbol = obj_file.symbol(sym_loc.index); 2018 if (symbol.isDead() or symbol.isUndefined()) { 2019 // Skip undefined symbols as they go in the `import` section 2020 continue; 2021 } 2022 2023 switch (symbol.tag) { 2024 .function => { 2025 const gop = try wasm.functions.getOrPut( 2026 gpa, 2027 .{ .file = sym_loc.file, .index = symbol.index }, 2028 ); 2029 if (gop.found_existing) { 2030 // We found an alias to the same function, discard this symbol in favor of 2031 // the original symbol and point the discard function to it. This ensures 2032 // we only emit a single function, instead of duplicates. 2033 symbol.unmark(); 2034 try wasm.discarded.putNoClobber( 2035 gpa, 2036 sym_loc, 2037 .{ .file = gop.key_ptr.*.file, .index = gop.value_ptr.*.sym_index }, 2038 ); 2039 try removed_duplicates.append(sym_loc); 2040 continue; 2041 } 2042 gop.value_ptr.* = .{ .func = obj_file.function(sym_loc.index), .sym_index = sym_loc.index }; 2043 symbol.index = @as(u32, @intCast(gop.index)) + wasm.imported_functions_count; 2044 }, 2045 .global => { 2046 const index = symbol.index - obj_file.importedFunctions(); 2047 const original_global = obj_file.globals()[index]; 2048 symbol.index = @as(u32, @intCast(wasm.wasm_globals.items.len)) + wasm.imported_globals_count; 2049 try wasm.wasm_globals.append(gpa, original_global); 2050 }, 2051 .table => { 2052 const index = symbol.index - obj_file.importedFunctions(); 2053 // assert it's a regular relocatable object file as `ZigObject` will never 2054 // contain a table. 2055 const original_table = obj_file.object.tables[index]; 2056 symbol.index = @as(u32, @intCast(wasm.tables.items.len)) + wasm.imported_tables_count; 2057 try wasm.tables.append(gpa, original_table); 2058 }, 2059 else => {}, 2060 } 2061 } 2062 2063 // For any removed duplicates, remove them from the resolved symbols list 2064 for (removed_duplicates.items) |sym_loc| { 2065 assert(wasm.resolved_symbols.swapRemove(sym_loc)); 2066 } 2067 2068 log.debug("Merged ({d}) functions", .{wasm.functions.count()}); 2069 log.debug("Merged ({d}) globals", .{wasm.wasm_globals.items.len}); 2070 log.debug("Merged ({d}) tables", .{wasm.tables.items.len}); 2071 } 2072 2073 /// Merges function types of all object files into the final 2074 /// 'types' section, while assigning the type index to the representing 2075 /// section (import, export, function). 2076 fn mergeTypes(wasm: *Wasm) !void { 2077 const gpa = wasm.base.comp.gpa; 2078 // A map to track which functions have already had their 2079 // type inserted. If we do this for the same function multiple times, 2080 // it will be overwritten with the incorrect type. 2081 var dirty = std.AutoHashMap(u32, void).init(gpa); 2082 try dirty.ensureUnusedCapacity(@as(u32, @intCast(wasm.functions.count()))); 2083 defer dirty.deinit(); 2084 2085 for (wasm.resolved_symbols.keys()) |sym_loc| { 2086 const obj_file = wasm.file(sym_loc.file) orelse { 2087 // zig code-generated symbols are already present in final type section 2088 continue; 2089 }; 2090 2091 const symbol = obj_file.symbol(sym_loc.index); 2092 if (symbol.tag != .function or symbol.isDead()) { 2093 // Only functions have types. Only retrieve the type of referenced functions. 2094 continue; 2095 } 2096 2097 if (symbol.isUndefined()) { 2098 log.debug("Adding type from extern function '{s}'", .{sym_loc.getName(wasm)}); 2099 const import: *types.Import = wasm.imports.getPtr(sym_loc) orelse continue; 2100 const original_type = obj_file.funcTypes()[import.kind.function]; 2101 import.kind.function = try wasm.putOrGetFuncType(original_type); 2102 } else if (!dirty.contains(symbol.index)) { 2103 log.debug("Adding type from function '{s}'", .{sym_loc.getName(wasm)}); 2104 const func = &wasm.functions.values()[symbol.index - wasm.imported_functions_count].func; 2105 func.type_index = try wasm.putOrGetFuncType(obj_file.funcTypes()[func.type_index]); 2106 dirty.putAssumeCapacityNoClobber(symbol.index, {}); 2107 } 2108 } 2109 log.debug("Completed merging and deduplicating types. Total count: ({d})", .{wasm.func_types.items.len}); 2110 } 2111 2112 fn setupExports(wasm: *Wasm) !void { 2113 const comp = wasm.base.comp; 2114 const gpa = comp.gpa; 2115 if (comp.config.output_mode == .Obj) return; 2116 log.debug("Building exports from symbols", .{}); 2117 2118 const force_exp_names = wasm.export_symbol_names; 2119 if (force_exp_names.len > 0) { 2120 var failed_exports = false; 2121 2122 for (force_exp_names) |exp_name| { 2123 const loc = wasm.findGlobalSymbol(exp_name) orelse { 2124 log.err("could not export '{s}', symbol not found", .{exp_name}); 2125 failed_exports = true; 2126 continue; 2127 }; 2128 2129 const symbol = loc.getSymbol(wasm); 2130 symbol.setFlag(.WASM_SYM_EXPORTED); 2131 } 2132 2133 if (failed_exports) { 2134 return error.MissingSymbol; 2135 } 2136 } 2137 2138 for (wasm.resolved_symbols.keys()) |sym_loc| { 2139 const symbol = sym_loc.getSymbol(wasm); 2140 if (!symbol.isExported(comp.config.rdynamic)) continue; 2141 2142 const sym_name = sym_loc.getName(wasm); 2143 const export_name = if (wasm.export_names.get(sym_loc)) |name| name else blk: { 2144 if (sym_loc.file == .null) break :blk symbol.name; 2145 break :blk try wasm.string_table.put(gpa, sym_name); 2146 }; 2147 const exp: types.Export = if (symbol.tag == .data) exp: { 2148 const global_index = @as(u32, @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len)); 2149 try wasm.wasm_globals.append(gpa, .{ 2150 .global_type = .{ .valtype = .i32, .mutable = false }, 2151 .init = .{ .i32_const = @as(i32, @intCast(symbol.virtual_address)) }, 2152 }); 2153 break :exp .{ 2154 .name = export_name, 2155 .kind = .global, 2156 .index = global_index, 2157 }; 2158 } else .{ 2159 .name = export_name, 2160 .kind = symbol.tag.externalType(), 2161 .index = symbol.index, 2162 }; 2163 log.debug("Exporting symbol '{s}' as '{s}' at index: ({d})", .{ 2164 sym_name, 2165 wasm.string_table.get(exp.name), 2166 exp.index, 2167 }); 2168 try wasm.exports.append(gpa, exp); 2169 } 2170 2171 log.debug("Completed building exports. Total count: ({d})", .{wasm.exports.items.len}); 2172 } 2173 2174 fn setupStart(wasm: *Wasm) !void { 2175 const comp = wasm.base.comp; 2176 // do not export entry point if user set none or no default was set. 2177 const entry_name = wasm.entry_name orelse return; 2178 2179 const symbol_loc = wasm.findGlobalSymbol(entry_name) orelse { 2180 log.err("Entry symbol '{s}' missing, use '-fno-entry' to suppress", .{entry_name}); 2181 return error.MissingSymbol; 2182 }; 2183 2184 const symbol = symbol_loc.getSymbol(wasm); 2185 if (symbol.tag != .function) { 2186 log.err("Entry symbol '{s}' is not a function", .{entry_name}); 2187 return error.InvalidEntryKind; 2188 } 2189 2190 // Ensure the symbol is exported so host environment can access it 2191 if (comp.config.output_mode != .Obj) { 2192 symbol.setFlag(.WASM_SYM_EXPORTED); 2193 } 2194 } 2195 2196 /// Sets up the memory section of the wasm module, as well as the stack. 2197 fn setupMemory(wasm: *Wasm) !void { 2198 const comp = wasm.base.comp; 2199 const shared_memory = comp.config.shared_memory; 2200 log.debug("Setting up memory layout", .{}); 2201 const page_size = std.wasm.page_size; // 64kb 2202 const stack_alignment: Alignment = .@"16"; // wasm's stack alignment as specified by tool-convention 2203 const heap_alignment: Alignment = .@"16"; // wasm's heap alignment as specified by tool-convention 2204 2205 // Always place the stack at the start by default 2206 // unless the user specified the global-base flag 2207 var place_stack_first = true; 2208 var memory_ptr: u64 = if (wasm.global_base) |base| blk: { 2209 place_stack_first = false; 2210 break :blk base; 2211 } else 0; 2212 2213 const is_obj = comp.config.output_mode == .Obj; 2214 2215 const stack_ptr = if (wasm.findGlobalSymbol("__stack_pointer")) |loc| index: { 2216 const sym = loc.getSymbol(wasm); 2217 break :index sym.index - wasm.imported_globals_count; 2218 } else null; 2219 2220 if (place_stack_first and !is_obj) { 2221 memory_ptr = stack_alignment.forward(memory_ptr); 2222 memory_ptr += wasm.base.stack_size; 2223 // We always put the stack pointer global at index 0 2224 if (stack_ptr) |index| { 2225 wasm.wasm_globals.items[index].init.i32_const = @as(i32, @bitCast(@as(u32, @intCast(memory_ptr)))); 2226 } 2227 } 2228 2229 var offset: u32 = @as(u32, @intCast(memory_ptr)); 2230 var data_seg_it = wasm.data_segments.iterator(); 2231 while (data_seg_it.next()) |entry| { 2232 const segment = &wasm.segments.items[entry.value_ptr.*]; 2233 memory_ptr = segment.alignment.forward(memory_ptr); 2234 2235 // set TLS-related symbols 2236 if (mem.eql(u8, entry.key_ptr.*, ".tdata")) { 2237 if (wasm.findGlobalSymbol("__tls_size")) |loc| { 2238 const sym = loc.getSymbol(wasm); 2239 wasm.wasm_globals.items[sym.index - wasm.imported_globals_count].init.i32_const = @intCast(segment.size); 2240 } 2241 if (wasm.findGlobalSymbol("__tls_align")) |loc| { 2242 const sym = loc.getSymbol(wasm); 2243 wasm.wasm_globals.items[sym.index - wasm.imported_globals_count].init.i32_const = @intCast(segment.alignment.toByteUnitsOptional().?); 2244 } 2245 if (wasm.findGlobalSymbol("__tls_base")) |loc| { 2246 const sym = loc.getSymbol(wasm); 2247 wasm.wasm_globals.items[sym.index - wasm.imported_globals_count].init.i32_const = if (shared_memory) 2248 @as(i32, 0) 2249 else 2250 @as(i32, @intCast(memory_ptr)); 2251 } 2252 } 2253 2254 memory_ptr += segment.size; 2255 segment.offset = offset; 2256 offset += segment.size; 2257 } 2258 2259 // create the memory init flag which is used by the init memory function 2260 if (shared_memory and wasm.hasPassiveInitializationSegments()) { 2261 // align to pointer size 2262 memory_ptr = mem.alignForward(u64, memory_ptr, 4); 2263 const loc = try wasm.createSyntheticSymbol("__wasm_init_memory_flag", .data); 2264 const sym = loc.getSymbol(wasm); 2265 sym.virtual_address = @as(u32, @intCast(memory_ptr)); 2266 memory_ptr += 4; 2267 } 2268 2269 if (!place_stack_first and !is_obj) { 2270 memory_ptr = stack_alignment.forward(memory_ptr); 2271 memory_ptr += wasm.base.stack_size; 2272 if (stack_ptr) |index| { 2273 wasm.wasm_globals.items[index].init.i32_const = @as(i32, @bitCast(@as(u32, @intCast(memory_ptr)))); 2274 } 2275 } 2276 2277 // One of the linked object files has a reference to the __heap_base symbol. 2278 // We must set its virtual address so it can be used in relocations. 2279 if (wasm.findGlobalSymbol("__heap_base")) |loc| { 2280 const symbol = loc.getSymbol(wasm); 2281 symbol.virtual_address = @intCast(heap_alignment.forward(memory_ptr)); 2282 } 2283 2284 // Setup the max amount of pages 2285 // For now we only support wasm32 by setting the maximum allowed memory size 2^32-1 2286 const max_memory_allowed: u64 = (1 << 32) - 1; 2287 2288 if (wasm.initial_memory) |initial_memory| { 2289 if (!std.mem.isAlignedGeneric(u64, initial_memory, page_size)) { 2290 log.err("Initial memory must be {d}-byte aligned", .{page_size}); 2291 return error.MissAlignment; 2292 } 2293 if (memory_ptr > initial_memory) { 2294 log.err("Initial memory too small, must be at least {d} bytes", .{memory_ptr}); 2295 return error.MemoryTooSmall; 2296 } 2297 if (initial_memory > max_memory_allowed) { 2298 log.err("Initial memory exceeds maximum memory {d}", .{max_memory_allowed}); 2299 return error.MemoryTooBig; 2300 } 2301 memory_ptr = initial_memory; 2302 } 2303 memory_ptr = mem.alignForward(u64, memory_ptr, std.wasm.page_size); 2304 // In case we do not import memory, but define it ourselves, 2305 // set the minimum amount of pages on the memory section. 2306 wasm.memories.limits.min = @as(u32, @intCast(memory_ptr / page_size)); 2307 log.debug("Total memory pages: {d}", .{wasm.memories.limits.min}); 2308 2309 if (wasm.findGlobalSymbol("__heap_end")) |loc| { 2310 const symbol = loc.getSymbol(wasm); 2311 symbol.virtual_address = @as(u32, @intCast(memory_ptr)); 2312 } 2313 2314 if (wasm.max_memory) |max_memory| { 2315 if (!std.mem.isAlignedGeneric(u64, max_memory, page_size)) { 2316 log.err("Maximum memory must be {d}-byte aligned", .{page_size}); 2317 return error.MissAlignment; 2318 } 2319 if (memory_ptr > max_memory) { 2320 log.err("Maxmimum memory too small, must be at least {d} bytes", .{memory_ptr}); 2321 return error.MemoryTooSmall; 2322 } 2323 if (max_memory > max_memory_allowed) { 2324 log.err("Maximum memory exceeds maxmium amount {d}", .{max_memory_allowed}); 2325 return error.MemoryTooBig; 2326 } 2327 wasm.memories.limits.max = @as(u32, @intCast(max_memory / page_size)); 2328 wasm.memories.limits.setFlag(.WASM_LIMITS_FLAG_HAS_MAX); 2329 if (shared_memory) { 2330 wasm.memories.limits.setFlag(.WASM_LIMITS_FLAG_IS_SHARED); 2331 } 2332 log.debug("Maximum memory pages: {?d}", .{wasm.memories.limits.max}); 2333 } 2334 } 2335 2336 /// From a given object's index and the index of the segment, returns the corresponding 2337 /// index of the segment within the final data section. When the segment does not yet 2338 /// exist, a new one will be initialized and appended. The new index will be returned in that case. 2339 pub fn getMatchingSegment(wasm: *Wasm, file_index: File.Index, symbol_index: u32) !u32 { 2340 const comp = wasm.base.comp; 2341 const gpa = comp.gpa; 2342 const obj_file = wasm.file(file_index).?; 2343 const symbol = obj_file.symbols()[symbol_index]; 2344 const index: u32 = @intCast(wasm.segments.items.len); 2345 const shared_memory = comp.config.shared_memory; 2346 2347 switch (symbol.tag) { 2348 .data => { 2349 const segment_info = obj_file.segmentInfo()[symbol.index]; 2350 const merge_segment = comp.config.output_mode != .Obj; 2351 const result = try wasm.data_segments.getOrPut(gpa, segment_info.outputName(merge_segment)); 2352 if (!result.found_existing) { 2353 result.value_ptr.* = index; 2354 var flags: u32 = 0; 2355 if (shared_memory) { 2356 flags |= @intFromEnum(Segment.Flag.WASM_DATA_SEGMENT_IS_PASSIVE); 2357 } 2358 try wasm.segments.append(gpa, .{ 2359 .alignment = .@"1", 2360 .size = 0, 2361 .offset = 0, 2362 .flags = flags, 2363 }); 2364 try wasm.segment_info.putNoClobber(gpa, index, .{ 2365 .name = try gpa.dupe(u8, segment_info.name), 2366 .alignment = segment_info.alignment, 2367 .flags = segment_info.flags, 2368 }); 2369 return index; 2370 } else return result.value_ptr.*; 2371 }, 2372 .function => return wasm.code_section_index orelse blk: { 2373 wasm.code_section_index = index; 2374 try wasm.appendDummySegment(); 2375 break :blk index; 2376 }, 2377 .section => { 2378 const section_name = obj_file.symbolName(symbol_index); 2379 if (mem.eql(u8, section_name, ".debug_info")) { 2380 return wasm.debug_info_index orelse blk: { 2381 wasm.debug_info_index = index; 2382 try wasm.appendDummySegment(); 2383 break :blk index; 2384 }; 2385 } else if (mem.eql(u8, section_name, ".debug_line")) { 2386 return wasm.debug_line_index orelse blk: { 2387 wasm.debug_line_index = index; 2388 try wasm.appendDummySegment(); 2389 break :blk index; 2390 }; 2391 } else if (mem.eql(u8, section_name, ".debug_loc")) { 2392 return wasm.debug_loc_index orelse blk: { 2393 wasm.debug_loc_index = index; 2394 try wasm.appendDummySegment(); 2395 break :blk index; 2396 }; 2397 } else if (mem.eql(u8, section_name, ".debug_ranges")) { 2398 return wasm.debug_ranges_index orelse blk: { 2399 wasm.debug_ranges_index = index; 2400 try wasm.appendDummySegment(); 2401 break :blk index; 2402 }; 2403 } else if (mem.eql(u8, section_name, ".debug_pubnames")) { 2404 return wasm.debug_pubnames_index orelse blk: { 2405 wasm.debug_pubnames_index = index; 2406 try wasm.appendDummySegment(); 2407 break :blk index; 2408 }; 2409 } else if (mem.eql(u8, section_name, ".debug_pubtypes")) { 2410 return wasm.debug_pubtypes_index orelse blk: { 2411 wasm.debug_pubtypes_index = index; 2412 try wasm.appendDummySegment(); 2413 break :blk index; 2414 }; 2415 } else if (mem.eql(u8, section_name, ".debug_abbrev")) { 2416 return wasm.debug_abbrev_index orelse blk: { 2417 wasm.debug_abbrev_index = index; 2418 try wasm.appendDummySegment(); 2419 break :blk index; 2420 }; 2421 } else if (mem.eql(u8, section_name, ".debug_str")) { 2422 return wasm.debug_str_index orelse blk: { 2423 wasm.debug_str_index = index; 2424 try wasm.appendDummySegment(); 2425 break :blk index; 2426 }; 2427 } else { 2428 log.err("found unknown section '{s}'", .{section_name}); 2429 return error.UnexpectedValue; 2430 } 2431 }, 2432 else => unreachable, 2433 } 2434 } 2435 2436 /// Appends a new segment with default field values 2437 fn appendDummySegment(wasm: *Wasm) !void { 2438 const gpa = wasm.base.comp.gpa; 2439 try wasm.segments.append(gpa, .{ 2440 .alignment = .@"1", 2441 .size = 0, 2442 .offset = 0, 2443 .flags = 0, 2444 }); 2445 } 2446 2447 fn resetState(wasm: *Wasm) void { 2448 const gpa = wasm.base.comp.gpa; 2449 2450 for (wasm.segment_info.values()) |segment_info| { 2451 gpa.free(segment_info.name); 2452 } 2453 2454 // TODO: Revisit 2455 // var atom_it = wasm.decls.valueIterator(); 2456 // while (atom_it.next()) |atom_index| { 2457 // const atom = wasm.getAtomPtr(atom_index.*); 2458 // atom.next = null; 2459 // atom.prev = null; 2460 2461 // for (atom.locals.items) |local_atom_index| { 2462 // const local_atom = wasm.getAtomPtr(local_atom_index); 2463 // local_atom.next = null; 2464 // local_atom.prev = null; 2465 // } 2466 // } 2467 2468 wasm.functions.clearRetainingCapacity(); 2469 wasm.exports.clearRetainingCapacity(); 2470 wasm.segments.clearRetainingCapacity(); 2471 wasm.segment_info.clearRetainingCapacity(); 2472 wasm.data_segments.clearRetainingCapacity(); 2473 wasm.atoms.clearRetainingCapacity(); 2474 wasm.symbol_atom.clearRetainingCapacity(); 2475 wasm.code_section_index = null; 2476 wasm.debug_info_index = null; 2477 wasm.debug_line_index = null; 2478 wasm.debug_loc_index = null; 2479 wasm.debug_str_index = null; 2480 wasm.debug_ranges_index = null; 2481 wasm.debug_abbrev_index = null; 2482 wasm.debug_pubnames_index = null; 2483 wasm.debug_pubtypes_index = null; 2484 } 2485 2486 pub fn flush(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void { 2487 const comp = wasm.base.comp; 2488 const use_lld = build_options.have_llvm and comp.config.use_lld; 2489 const use_llvm = comp.config.use_llvm; 2490 2491 if (use_lld) { 2492 return wasm.linkWithLLD(arena, prog_node); 2493 } else if (use_llvm) { 2494 return wasm.linkWithZld(arena, prog_node); 2495 } else { 2496 return wasm.flushModule(arena, prog_node); 2497 } 2498 } 2499 2500 /// Uses the in-house linker to link one or multiple object -and archive files into a WebAssembly binary. 2501 fn linkWithZld(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void { 2502 const tracy = trace(@src()); 2503 defer tracy.end(); 2504 2505 const comp = wasm.base.comp; 2506 const shared_memory = comp.config.shared_memory; 2507 const import_memory = comp.config.import_memory; 2508 2509 const directory = wasm.base.emit.directory; // Just an alias to make it shorter to type. 2510 const full_out_path = try directory.join(arena, &[_][]const u8{wasm.base.emit.sub_path}); 2511 const opt_zcu = comp.module; 2512 const use_llvm = comp.config.use_llvm; 2513 2514 // If there is no Zig code to compile, then we should skip flushing the output file because it 2515 // will not be part of the linker line anyway. 2516 const module_obj_path: ?[]const u8 = if (opt_zcu != null) blk: { 2517 assert(use_llvm); // `linkWithZld` should never be called when the Wasm backend is used 2518 try wasm.flushModule(arena, prog_node); 2519 2520 if (fs.path.dirname(full_out_path)) |dirname| { 2521 break :blk try fs.path.join(arena, &.{ dirname, wasm.base.zcu_object_sub_path.? }); 2522 } else { 2523 break :blk wasm.base.zcu_object_sub_path.?; 2524 } 2525 } else null; 2526 2527 var sub_prog_node = prog_node.start("Wasm Flush", 0); 2528 sub_prog_node.activate(); 2529 defer sub_prog_node.end(); 2530 2531 const compiler_rt_path: ?[]const u8 = blk: { 2532 if (comp.compiler_rt_obj) |obj| break :blk obj.full_object_path; 2533 if (comp.compiler_rt_lib) |lib| break :blk lib.full_object_path; 2534 break :blk null; 2535 }; 2536 2537 const id_symlink_basename = "zld.id"; 2538 2539 var man: Cache.Manifest = undefined; 2540 defer if (!wasm.base.disable_lld_caching) man.deinit(); 2541 var digest: [Cache.hex_digest_len]u8 = undefined; 2542 2543 const objects = comp.objects; 2544 2545 // NOTE: The following section must be maintained to be equal 2546 // as the section defined in `linkWithLLD` 2547 if (!wasm.base.disable_lld_caching) { 2548 man = comp.cache_parent.obtain(); 2549 2550 // We are about to obtain this lock, so here we give other processes a chance first. 2551 wasm.base.releaseLock(); 2552 2553 comptime assert(Compilation.link_hash_implementation_version == 12); 2554 2555 for (objects) |obj| { 2556 _ = try man.addFile(obj.path, null); 2557 man.hash.add(obj.must_link); 2558 } 2559 for (comp.c_object_table.keys()) |key| { 2560 _ = try man.addFile(key.status.success.object_path, null); 2561 } 2562 try man.addOptionalFile(module_obj_path); 2563 try man.addOptionalFile(compiler_rt_path); 2564 man.hash.addOptionalBytes(wasm.entry_name); 2565 man.hash.add(wasm.base.stack_size); 2566 man.hash.add(wasm.base.build_id); 2567 man.hash.add(import_memory); 2568 man.hash.add(shared_memory); 2569 man.hash.add(wasm.import_table); 2570 man.hash.add(wasm.export_table); 2571 man.hash.addOptional(wasm.initial_memory); 2572 man.hash.addOptional(wasm.max_memory); 2573 man.hash.addOptional(wasm.global_base); 2574 man.hash.addListOfBytes(wasm.export_symbol_names); 2575 // strip does not need to go into the linker hash because it is part of the hash namespace 2576 2577 // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. 2578 _ = try man.hit(); 2579 digest = man.final(); 2580 2581 var prev_digest_buf: [digest.len]u8 = undefined; 2582 const prev_digest: []u8 = Cache.readSmallFile( 2583 directory.handle, 2584 id_symlink_basename, 2585 &prev_digest_buf, 2586 ) catch |err| blk: { 2587 log.debug("WASM LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); 2588 // Handle this as a cache miss. 2589 break :blk prev_digest_buf[0..0]; 2590 }; 2591 if (mem.eql(u8, prev_digest, &digest)) { 2592 log.debug("WASM LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); 2593 // Hot diggity dog! The output binary is already there. 2594 wasm.base.lock = man.toOwnedLock(); 2595 return; 2596 } 2597 log.debug("WASM LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); 2598 2599 // We are about to change the output file to be different, so we invalidate the build hash now. 2600 directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { 2601 error.FileNotFound => {}, 2602 else => |e| return e, 2603 }; 2604 } 2605 2606 // Positional arguments to the linker such as object files and static archives. 2607 var positionals = std.ArrayList([]const u8).init(arena); 2608 try positionals.ensureUnusedCapacity(objects.len); 2609 2610 const target = comp.root_mod.resolved_target.result; 2611 const output_mode = comp.config.output_mode; 2612 const link_mode = comp.config.link_mode; 2613 const link_libc = comp.config.link_libc; 2614 const link_libcpp = comp.config.link_libcpp; 2615 const wasi_exec_model = comp.config.wasi_exec_model; 2616 2617 // When the target os is WASI, we allow linking with WASI-LIBC 2618 if (target.os.tag == .wasi) { 2619 const is_exe_or_dyn_lib = output_mode == .Exe or 2620 (output_mode == .Lib and link_mode == .Dynamic); 2621 if (is_exe_or_dyn_lib) { 2622 for (comp.wasi_emulated_libs) |crt_file| { 2623 try positionals.append(try comp.get_libc_crt_file( 2624 arena, 2625 wasi_libc.emulatedLibCRFileLibName(crt_file), 2626 )); 2627 } 2628 2629 if (link_libc) { 2630 try positionals.append(try comp.get_libc_crt_file( 2631 arena, 2632 wasi_libc.execModelCrtFileFullName(wasi_exec_model), 2633 )); 2634 try positionals.append(try comp.get_libc_crt_file(arena, "libc.a")); 2635 } 2636 2637 if (link_libcpp) { 2638 try positionals.append(comp.libcxx_static_lib.?.full_object_path); 2639 try positionals.append(comp.libcxxabi_static_lib.?.full_object_path); 2640 } 2641 } 2642 } 2643 2644 if (module_obj_path) |path| { 2645 try positionals.append(path); 2646 } 2647 2648 for (objects) |object| { 2649 try positionals.append(object.path); 2650 } 2651 2652 for (comp.c_object_table.keys()) |c_object| { 2653 try positionals.append(c_object.status.success.object_path); 2654 } 2655 2656 if (comp.compiler_rt_lib) |lib| try positionals.append(lib.full_object_path); 2657 if (comp.compiler_rt_obj) |obj| try positionals.append(obj.full_object_path); 2658 2659 try wasm.parseInputFiles(positionals.items); 2660 2661 for (wasm.objects.items) |object_index| { 2662 try wasm.resolveSymbolsInObject(object_index); 2663 } 2664 2665 var emit_features_count: u32 = 0; 2666 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined; 2667 try wasm.validateFeatures(&enabled_features, &emit_features_count); 2668 try wasm.resolveSymbolsInArchives(); 2669 try wasm.resolveLazySymbols(); 2670 try wasm.checkUndefinedSymbols(); 2671 2672 try wasm.setupInitFunctions(); 2673 try wasm.setupStart(); 2674 2675 try wasm.markReferences(); 2676 try wasm.setupImports(); 2677 try wasm.mergeSections(); 2678 try wasm.mergeTypes(); 2679 try wasm.allocateAtoms(); 2680 try wasm.setupMemory(); 2681 wasm.allocateVirtualAddresses(); 2682 wasm.mapFunctionTable(); 2683 try wasm.initializeCallCtorsFunction(); 2684 try wasm.setupInitMemoryFunction(); 2685 try wasm.setupTLSRelocationsFunction(); 2686 try wasm.initializeTLSFunction(); 2687 try wasm.setupStartSection(); 2688 try wasm.setupExports(); 2689 try wasm.writeToFile(enabled_features, emit_features_count, arena); 2690 2691 if (!wasm.base.disable_lld_caching) { 2692 // Update the file with the digest. If it fails we can continue; it only 2693 // means that the next invocation will have an unnecessary cache miss. 2694 Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { 2695 log.warn("failed to save linking hash digest symlink: {s}", .{@errorName(err)}); 2696 }; 2697 // Again failure here only means an unnecessary cache miss. 2698 man.writeManifest() catch |err| { 2699 log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); 2700 }; 2701 // We hang on to this lock so that the output file path can be used without 2702 // other processes clobbering it. 2703 wasm.base.lock = man.toOwnedLock(); 2704 } 2705 } 2706 2707 pub fn flushModule(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void { 2708 const tracy = trace(@src()); 2709 defer tracy.end(); 2710 2711 const comp = wasm.base.comp; 2712 2713 if (wasm.llvm_object) |llvm_object| { 2714 try wasm.base.emitLlvmObject(arena, llvm_object, prog_node); 2715 return; 2716 } 2717 2718 var sub_prog_node = prog_node.start("Wasm Flush", 0); 2719 sub_prog_node.activate(); 2720 defer sub_prog_node.end(); 2721 2722 // ensure the error names table is populated when an error name is referenced 2723 // try wasm.populateErrorNameTable(); 2724 2725 const objects = comp.objects; 2726 2727 // Positional arguments to the linker such as object files and static archives. 2728 var positionals = std.ArrayList([]const u8).init(arena); 2729 try positionals.ensureUnusedCapacity(objects.len); 2730 2731 for (objects) |object| { 2732 positionals.appendAssumeCapacity(object.path); 2733 } 2734 2735 for (comp.c_object_table.keys()) |c_object| { 2736 try positionals.append(c_object.status.success.object_path); 2737 } 2738 2739 if (comp.compiler_rt_lib) |lib| try positionals.append(lib.full_object_path); 2740 if (comp.compiler_rt_obj) |obj| try positionals.append(obj.full_object_path); 2741 2742 try wasm.parseInputFiles(positionals.items); 2743 2744 if (wasm.zig_object_index != .null) { 2745 try wasm.resolveSymbolsInObject(wasm.zig_object_index); 2746 } 2747 for (wasm.objects.items) |object_index| { 2748 try wasm.resolveSymbolsInObject(object_index); 2749 } 2750 2751 var emit_features_count: u32 = 0; 2752 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined; 2753 try wasm.validateFeatures(&enabled_features, &emit_features_count); 2754 try wasm.resolveSymbolsInArchives(); 2755 try wasm.resolveLazySymbols(); 2756 try wasm.checkUndefinedSymbols(); 2757 2758 // When we finish/error we reset the state of the linker 2759 // So we can rebuild the binary file on each incremental update 2760 defer wasm.resetState(); 2761 try wasm.setupInitFunctions(); 2762 try wasm.setupStart(); 2763 try wasm.markReferences(); 2764 // try wasm.setupErrorsLen(); 2765 try wasm.setupImports(); 2766 // if (comp.module) |mod| { 2767 // var decl_it = wasm.decls.iterator(); 2768 // while (decl_it.next()) |entry| { 2769 // const decl = mod.declPtr(entry.key_ptr.*); 2770 // if (decl.isExtern(mod)) continue; 2771 // const atom_index = entry.value_ptr.*; 2772 // const atom = wasm.getAtomPtr(atom_index); 2773 // if (decl.ty.zigTypeTag(mod) == .Fn) { 2774 // try wasm.parseAtom(atom_index, .function); 2775 // } else if (decl.getOwnedVariable(mod)) |variable| { 2776 // if (variable.is_const) { 2777 // try wasm.parseAtom(atom_index, .{ .data = .read_only }); 2778 // } else if (Value.fromInterned(variable.init).isUndefDeep(mod)) { 2779 // // for safe build modes, we store the atom in the data segment, 2780 // // whereas for unsafe build modes we store it in bss. 2781 // const decl_namespace = mod.namespacePtr(decl.src_namespace); 2782 // const optimize_mode = decl_namespace.file_scope.mod.optimize_mode; 2783 // const is_initialized = switch (optimize_mode) { 2784 // .Debug, .ReleaseSafe => true, 2785 // .ReleaseFast, .ReleaseSmall => false, 2786 // }; 2787 // try wasm.parseAtom(atom_index, .{ .data = if (is_initialized) .initialized else .uninitialized }); 2788 // } else { 2789 // // when the decl is all zeroes, we store the atom in the bss segment, 2790 // // in all other cases it will be in the data segment. 2791 // const is_zeroes = for (atom.code.items) |byte| { 2792 // if (byte != 0) break false; 2793 // } else true; 2794 // try wasm.parseAtom(atom_index, .{ .data = if (is_zeroes) .uninitialized else .initialized }); 2795 // } 2796 // } else { 2797 // try wasm.parseAtom(atom_index, .{ .data = .read_only }); 2798 // } 2799 2800 // // also parse atoms for a decl's locals 2801 // for (atom.locals.items) |local_atom_index| { 2802 // try wasm.parseAtom(local_atom_index, .{ .data = .read_only }); 2803 // } 2804 // } 2805 // // parse anonymous declarations 2806 // for (wasm.anon_decls.keys(), wasm.anon_decls.values()) |decl_val, atom_index| { 2807 // const ty = Type.fromInterned(mod.intern_pool.typeOf(decl_val)); 2808 // if (ty.zigTypeTag(mod) == .Fn) { 2809 // try wasm.parseAtom(atom_index, .function); 2810 // } else { 2811 // try wasm.parseAtom(atom_index, .{ .data = .read_only }); 2812 // } 2813 // } 2814 2815 // // also parse any backend-generated functions 2816 // for (wasm.synthetic_functions.items) |atom_index| { 2817 // try wasm.parseAtom(atom_index, .function); 2818 // } 2819 2820 // if (wasm.dwarf) |*dwarf| { 2821 // try dwarf.flushModule(comp.module.?); 2822 // } 2823 // } 2824 2825 try wasm.mergeSections(); 2826 try wasm.mergeTypes(); 2827 try wasm.allocateAtoms(); 2828 try wasm.setupMemory(); 2829 wasm.allocateVirtualAddresses(); 2830 wasm.mapFunctionTable(); 2831 try wasm.initializeCallCtorsFunction(); 2832 try wasm.setupInitMemoryFunction(); 2833 try wasm.setupTLSRelocationsFunction(); 2834 try wasm.initializeTLSFunction(); 2835 try wasm.setupStartSection(); 2836 try wasm.setupExports(); 2837 try wasm.writeToFile(enabled_features, emit_features_count, arena); 2838 } 2839 2840 /// Writes the WebAssembly in-memory module to the file 2841 fn writeToFile( 2842 wasm: *Wasm, 2843 enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool, 2844 feature_count: u32, 2845 arena: Allocator, 2846 ) !void { 2847 const comp = wasm.base.comp; 2848 const gpa = comp.gpa; 2849 const use_llvm = comp.config.use_llvm; 2850 const use_lld = build_options.have_llvm and comp.config.use_lld; 2851 const shared_memory = comp.config.shared_memory; 2852 const import_memory = comp.config.import_memory; 2853 const export_memory = comp.config.export_memory; 2854 2855 // Size of each section header 2856 const header_size = 5 + 1; 2857 // The amount of sections that will be written 2858 var section_count: u32 = 0; 2859 // Index of the code section. Used to tell relocation table where the section lives. 2860 var code_section_index: ?u32 = null; 2861 // Index of the data section. Used to tell relocation table where the section lives. 2862 var data_section_index: ?u32 = null; 2863 const is_obj = comp.config.output_mode == .Obj or (!use_llvm and use_lld); 2864 2865 var binary_bytes = std.ArrayList(u8).init(gpa); 2866 defer binary_bytes.deinit(); 2867 const binary_writer = binary_bytes.writer(); 2868 2869 // We write the magic bytes at the end so they will only be written 2870 // if everything succeeded as expected. So populate with 0's for now. 2871 try binary_writer.writeAll(&[_]u8{0} ** 8); 2872 // (Re)set file pointer to 0 2873 try wasm.base.file.?.setEndPos(0); 2874 try wasm.base.file.?.seekTo(0); 2875 2876 // Type section 2877 if (wasm.func_types.items.len != 0) { 2878 const header_offset = try reserveVecSectionHeader(&binary_bytes); 2879 log.debug("Writing type section. Count: ({d})", .{wasm.func_types.items.len}); 2880 for (wasm.func_types.items) |func_type| { 2881 try leb.writeULEB128(binary_writer, std.wasm.function_type); 2882 try leb.writeULEB128(binary_writer, @as(u32, @intCast(func_type.params.len))); 2883 for (func_type.params) |param_ty| { 2884 try leb.writeULEB128(binary_writer, std.wasm.valtype(param_ty)); 2885 } 2886 try leb.writeULEB128(binary_writer, @as(u32, @intCast(func_type.returns.len))); 2887 for (func_type.returns) |ret_ty| { 2888 try leb.writeULEB128(binary_writer, std.wasm.valtype(ret_ty)); 2889 } 2890 } 2891 2892 try writeVecSectionHeader( 2893 binary_bytes.items, 2894 header_offset, 2895 .type, 2896 @intCast(binary_bytes.items.len - header_offset - header_size), 2897 @intCast(wasm.func_types.items.len), 2898 ); 2899 section_count += 1; 2900 } 2901 2902 // Import section 2903 if (wasm.imports.count() != 0 or import_memory) { 2904 const header_offset = try reserveVecSectionHeader(&binary_bytes); 2905 2906 var it = wasm.imports.iterator(); 2907 while (it.next()) |entry| { 2908 assert(entry.key_ptr.*.getSymbol(wasm).isUndefined()); 2909 const import = entry.value_ptr.*; 2910 try wasm.emitImport(binary_writer, import); 2911 } 2912 2913 if (import_memory) { 2914 const mem_name = if (is_obj) "__linear_memory" else "memory"; 2915 const mem_imp: types.Import = .{ 2916 .module_name = try wasm.string_table.put(gpa, wasm.host_name), 2917 .name = try wasm.string_table.put(gpa, mem_name), 2918 .kind = .{ .memory = wasm.memories.limits }, 2919 }; 2920 try wasm.emitImport(binary_writer, mem_imp); 2921 } 2922 2923 try writeVecSectionHeader( 2924 binary_bytes.items, 2925 header_offset, 2926 .import, 2927 @intCast(binary_bytes.items.len - header_offset - header_size), 2928 @intCast(wasm.imports.count() + @intFromBool(import_memory)), 2929 ); 2930 section_count += 1; 2931 } 2932 2933 // Function section 2934 if (wasm.functions.count() != 0) { 2935 const header_offset = try reserveVecSectionHeader(&binary_bytes); 2936 for (wasm.functions.values()) |function| { 2937 try leb.writeULEB128(binary_writer, function.func.type_index); 2938 } 2939 2940 try writeVecSectionHeader( 2941 binary_bytes.items, 2942 header_offset, 2943 .function, 2944 @intCast(binary_bytes.items.len - header_offset - header_size), 2945 @intCast(wasm.functions.count()), 2946 ); 2947 section_count += 1; 2948 } 2949 2950 // Table section 2951 if (wasm.tables.items.len > 0) { 2952 const header_offset = try reserveVecSectionHeader(&binary_bytes); 2953 2954 for (wasm.tables.items) |table| { 2955 try leb.writeULEB128(binary_writer, std.wasm.reftype(table.reftype)); 2956 try emitLimits(binary_writer, table.limits); 2957 } 2958 2959 try writeVecSectionHeader( 2960 binary_bytes.items, 2961 header_offset, 2962 .table, 2963 @intCast(binary_bytes.items.len - header_offset - header_size), 2964 @intCast(wasm.tables.items.len), 2965 ); 2966 section_count += 1; 2967 } 2968 2969 // Memory section 2970 if (!import_memory) { 2971 const header_offset = try reserveVecSectionHeader(&binary_bytes); 2972 2973 try emitLimits(binary_writer, wasm.memories.limits); 2974 try writeVecSectionHeader( 2975 binary_bytes.items, 2976 header_offset, 2977 .memory, 2978 @intCast(binary_bytes.items.len - header_offset - header_size), 2979 1, // wasm currently only supports 1 linear memory segment 2980 ); 2981 section_count += 1; 2982 } 2983 2984 // Global section (used to emit stack pointer) 2985 if (wasm.wasm_globals.items.len > 0) { 2986 const header_offset = try reserveVecSectionHeader(&binary_bytes); 2987 2988 for (wasm.wasm_globals.items) |global| { 2989 try binary_writer.writeByte(std.wasm.valtype(global.global_type.valtype)); 2990 try binary_writer.writeByte(@intFromBool(global.global_type.mutable)); 2991 try emitInit(binary_writer, global.init); 2992 } 2993 2994 try writeVecSectionHeader( 2995 binary_bytes.items, 2996 header_offset, 2997 .global, 2998 @intCast(binary_bytes.items.len - header_offset - header_size), 2999 @intCast(wasm.wasm_globals.items.len), 3000 ); 3001 section_count += 1; 3002 } 3003 3004 // Export section 3005 if (wasm.exports.items.len != 0 or export_memory) { 3006 const header_offset = try reserveVecSectionHeader(&binary_bytes); 3007 3008 for (wasm.exports.items) |exp| { 3009 const name = wasm.string_table.get(exp.name); 3010 try leb.writeULEB128(binary_writer, @as(u32, @intCast(name.len))); 3011 try binary_writer.writeAll(name); 3012 try leb.writeULEB128(binary_writer, @intFromEnum(exp.kind)); 3013 try leb.writeULEB128(binary_writer, exp.index); 3014 } 3015 3016 if (export_memory) { 3017 try leb.writeULEB128(binary_writer, @as(u32, @intCast("memory".len))); 3018 try binary_writer.writeAll("memory"); 3019 try binary_writer.writeByte(std.wasm.externalKind(.memory)); 3020 try leb.writeULEB128(binary_writer, @as(u32, 0)); 3021 } 3022 3023 try writeVecSectionHeader( 3024 binary_bytes.items, 3025 header_offset, 3026 .@"export", 3027 @intCast(binary_bytes.items.len - header_offset - header_size), 3028 @intCast(wasm.exports.items.len + @intFromBool(export_memory)), 3029 ); 3030 section_count += 1; 3031 } 3032 3033 if (wasm.entry) |entry_index| { 3034 const header_offset = try reserveVecSectionHeader(&binary_bytes); 3035 try writeVecSectionHeader( 3036 binary_bytes.items, 3037 header_offset, 3038 .start, 3039 @intCast(binary_bytes.items.len - header_offset - header_size), 3040 entry_index, 3041 ); 3042 } 3043 3044 // element section (function table) 3045 if (wasm.function_table.count() > 0) { 3046 const header_offset = try reserveVecSectionHeader(&binary_bytes); 3047 3048 const table_loc = wasm.findGlobalSymbol("__indirect_function_table").?; 3049 const table_sym = table_loc.getSymbol(wasm); 3050 3051 const flags: u32 = if (table_sym.index == 0) 0x0 else 0x02; // passive with implicit 0-index table or set table index manually 3052 try leb.writeULEB128(binary_writer, flags); 3053 if (flags == 0x02) { 3054 try leb.writeULEB128(binary_writer, table_sym.index); 3055 } 3056 try emitInit(binary_writer, .{ .i32_const = 1 }); // We start at index 1, so unresolved function pointers are invalid 3057 if (flags == 0x02) { 3058 try leb.writeULEB128(binary_writer, @as(u8, 0)); // represents funcref 3059 } 3060 try leb.writeULEB128(binary_writer, @as(u32, @intCast(wasm.function_table.count()))); 3061 var symbol_it = wasm.function_table.keyIterator(); 3062 while (symbol_it.next()) |symbol_loc_ptr| { 3063 const sym = symbol_loc_ptr.*.getSymbol(wasm); 3064 try leb.writeULEB128(binary_writer, sym.index); 3065 } 3066 3067 try writeVecSectionHeader( 3068 binary_bytes.items, 3069 header_offset, 3070 .element, 3071 @intCast(binary_bytes.items.len - header_offset - header_size), 3072 1, 3073 ); 3074 section_count += 1; 3075 } 3076 3077 // When the shared-memory option is enabled, we *must* emit the 'data count' section. 3078 const data_segments_count = wasm.data_segments.count() - @intFromBool(wasm.data_segments.contains(".bss") and !import_memory); 3079 if (data_segments_count != 0 and shared_memory) { 3080 const header_offset = try reserveVecSectionHeader(&binary_bytes); 3081 try writeVecSectionHeader( 3082 binary_bytes.items, 3083 header_offset, 3084 .data_count, 3085 @intCast(binary_bytes.items.len - header_offset - header_size), 3086 @intCast(data_segments_count), 3087 ); 3088 } 3089 3090 // Code section 3091 if (wasm.code_section_index != null) { 3092 const header_offset = try reserveVecSectionHeader(&binary_bytes); 3093 const start_offset = binary_bytes.items.len - 5; // minus 5 so start offset is 5 to include entry count 3094 3095 var func_it = wasm.functions.iterator(); 3096 while (func_it.next()) |entry| { 3097 const sym_loc: SymbolLoc = .{ .index = entry.value_ptr.sym_index, .file = entry.key_ptr.file }; 3098 const atom_index = wasm.symbol_atom.get(sym_loc).?; 3099 const atom = wasm.getAtomPtr(atom_index); 3100 3101 if (!is_obj) { 3102 atom.resolveRelocs(wasm); 3103 } 3104 atom.offset = @intCast(binary_bytes.items.len - start_offset); 3105 try leb.writeULEB128(binary_writer, atom.size); 3106 try binary_writer.writeAll(atom.code.items); 3107 } 3108 3109 try writeVecSectionHeader( 3110 binary_bytes.items, 3111 header_offset, 3112 .code, 3113 @intCast(binary_bytes.items.len - header_offset - header_size), 3114 @intCast(wasm.functions.count()), 3115 ); 3116 code_section_index = section_count; 3117 section_count += 1; 3118 } 3119 3120 // Data section 3121 if (data_segments_count != 0) { 3122 const header_offset = try reserveVecSectionHeader(&binary_bytes); 3123 3124 var it = wasm.data_segments.iterator(); 3125 var segment_count: u32 = 0; 3126 while (it.next()) |entry| { 3127 // do not output 'bss' section unless we import memory and therefore 3128 // want to guarantee the data is zero initialized 3129 if (!import_memory and std.mem.eql(u8, entry.key_ptr.*, ".bss")) continue; 3130 const segment_index = entry.value_ptr.*; 3131 const segment = wasm.segments.items[segment_index]; 3132 if (segment.size == 0) continue; // do not emit empty segments 3133 segment_count += 1; 3134 var atom_index = wasm.atoms.get(segment_index).?; 3135 3136 try leb.writeULEB128(binary_writer, segment.flags); 3137 if (segment.flags & @intFromEnum(Wasm.Segment.Flag.WASM_DATA_SEGMENT_HAS_MEMINDEX) != 0) { 3138 try leb.writeULEB128(binary_writer, @as(u32, 0)); // memory is always index 0 as we only have 1 memory entry 3139 } 3140 // when a segment is passive, it's initialized during runtime. 3141 if (!segment.isPassive()) { 3142 try emitInit(binary_writer, .{ .i32_const = @as(i32, @bitCast(segment.offset)) }); 3143 } 3144 // offset into data section 3145 try leb.writeULEB128(binary_writer, segment.size); 3146 3147 // fill in the offset table and the data segments 3148 var current_offset: u32 = 0; 3149 while (true) { 3150 const atom = wasm.getAtomPtr(atom_index); 3151 if (!is_obj) { 3152 atom.resolveRelocs(wasm); 3153 } 3154 3155 // Pad with zeroes to ensure all segments are aligned 3156 if (current_offset != atom.offset) { 3157 const diff = atom.offset - current_offset; 3158 try binary_writer.writeByteNTimes(0, diff); 3159 current_offset += diff; 3160 } 3161 assert(current_offset == atom.offset); 3162 assert(atom.code.items.len == atom.size); 3163 try binary_writer.writeAll(atom.code.items); 3164 3165 current_offset += atom.size; 3166 if (atom.prev) |prev| { 3167 atom_index = prev; 3168 } else { 3169 // also pad with zeroes when last atom to ensure 3170 // segments are aligned. 3171 if (current_offset != segment.size) { 3172 try binary_writer.writeByteNTimes(0, segment.size - current_offset); 3173 current_offset += segment.size - current_offset; 3174 } 3175 break; 3176 } 3177 } 3178 assert(current_offset == segment.size); 3179 } 3180 3181 try writeVecSectionHeader( 3182 binary_bytes.items, 3183 header_offset, 3184 .data, 3185 @intCast(binary_bytes.items.len - header_offset - header_size), 3186 @intCast(segment_count), 3187 ); 3188 data_section_index = section_count; 3189 section_count += 1; 3190 } 3191 3192 if (is_obj) { 3193 // relocations need to point to the index of a symbol in the final symbol table. To save memory, 3194 // we never store all symbols in a single table, but store a location reference instead. 3195 // This means that for a relocatable object file, we need to generate one and provide it to the relocation sections. 3196 var symbol_table = std.AutoArrayHashMap(SymbolLoc, u32).init(arena); 3197 try wasm.emitLinkSection(&binary_bytes, &symbol_table); 3198 if (code_section_index) |code_index| { 3199 try wasm.emitCodeRelocations(&binary_bytes, code_index, symbol_table); 3200 } 3201 if (data_section_index) |data_index| { 3202 try wasm.emitDataRelocations(&binary_bytes, data_index, symbol_table); 3203 } 3204 } else if (comp.config.debug_format != .strip) { 3205 try wasm.emitNameSection(&binary_bytes, arena); 3206 } 3207 3208 if (comp.config.debug_format != .strip) { 3209 // The build id must be computed on the main sections only, 3210 // so we have to do it now, before the debug sections. 3211 switch (wasm.base.build_id) { 3212 .none => {}, 3213 .fast => { 3214 var id: [16]u8 = undefined; 3215 std.crypto.hash.sha3.TurboShake128(null).hash(binary_bytes.items, &id, .{}); 3216 var uuid: [36]u8 = undefined; 3217 _ = try std.fmt.bufPrint(&uuid, "{s}-{s}-{s}-{s}-{s}", .{ 3218 std.fmt.fmtSliceHexLower(id[0..4]), 3219 std.fmt.fmtSliceHexLower(id[4..6]), 3220 std.fmt.fmtSliceHexLower(id[6..8]), 3221 std.fmt.fmtSliceHexLower(id[8..10]), 3222 std.fmt.fmtSliceHexLower(id[10..]), 3223 }); 3224 try emitBuildIdSection(&binary_bytes, &uuid); 3225 }, 3226 .hexstring => |hs| { 3227 var buffer: [32 * 2]u8 = undefined; 3228 const str = std.fmt.bufPrint(&buffer, "{s}", .{ 3229 std.fmt.fmtSliceHexLower(hs.toSlice()), 3230 }) catch unreachable; 3231 try emitBuildIdSection(&binary_bytes, str); 3232 }, 3233 else => |mode| log.err("build-id '{s}' is not supported for WASM", .{@tagName(mode)}), 3234 } 3235 3236 var debug_bytes = std.ArrayList(u8).init(gpa); 3237 defer debug_bytes.deinit(); 3238 3239 const DebugSection = struct { 3240 name: []const u8, 3241 index: ?u32, 3242 }; 3243 3244 const debug_sections: []const DebugSection = &.{ 3245 .{ .name = ".debug_info", .index = wasm.debug_info_index }, 3246 .{ .name = ".debug_pubtypes", .index = wasm.debug_pubtypes_index }, 3247 .{ .name = ".debug_abbrev", .index = wasm.debug_abbrev_index }, 3248 .{ .name = ".debug_line", .index = wasm.debug_line_index }, 3249 .{ .name = ".debug_str", .index = wasm.debug_str_index }, 3250 .{ .name = ".debug_pubnames", .index = wasm.debug_pubnames_index }, 3251 .{ .name = ".debug_loc", .index = wasm.debug_loc_index }, 3252 .{ .name = ".debug_ranges", .index = wasm.debug_ranges_index }, 3253 }; 3254 3255 for (debug_sections) |item| { 3256 if (item.index) |index| { 3257 var atom = wasm.getAtomPtr(wasm.atoms.get(index).?); 3258 while (true) { 3259 atom.resolveRelocs(wasm); 3260 try debug_bytes.appendSlice(atom.code.items); 3261 atom = if (atom.prev) |prev| wasm.getAtomPtr(prev) else break; 3262 } 3263 try emitDebugSection(&binary_bytes, debug_bytes.items, item.name); 3264 debug_bytes.clearRetainingCapacity(); 3265 } 3266 } 3267 3268 try emitProducerSection(&binary_bytes); 3269 if (feature_count > 0) { 3270 try emitFeaturesSection(&binary_bytes, &enabled_features, feature_count); 3271 } 3272 } 3273 3274 // Only when writing all sections executed properly we write the magic 3275 // bytes. This allows us to easily detect what went wrong while generating 3276 // the final binary. 3277 { 3278 const src = std.wasm.magic ++ std.wasm.version; 3279 binary_bytes.items[0..src.len].* = src; 3280 } 3281 3282 // finally, write the entire binary into the file. 3283 var iovec = [_]std.os.iovec_const{.{ 3284 .iov_base = binary_bytes.items.ptr, 3285 .iov_len = binary_bytes.items.len, 3286 }}; 3287 try wasm.base.file.?.writevAll(&iovec); 3288 } 3289 3290 fn emitDebugSection(binary_bytes: *std.ArrayList(u8), data: []const u8, name: []const u8) !void { 3291 if (data.len == 0) return; 3292 const header_offset = try reserveCustomSectionHeader(binary_bytes); 3293 const writer = binary_bytes.writer(); 3294 try leb.writeULEB128(writer, @as(u32, @intCast(name.len))); 3295 try writer.writeAll(name); 3296 3297 const start = binary_bytes.items.len - header_offset; 3298 log.debug("Emit debug section: '{s}' start=0x{x:0>8} end=0x{x:0>8}", .{ name, start, start + data.len }); 3299 try writer.writeAll(data); 3300 3301 try writeCustomSectionHeader( 3302 binary_bytes.items, 3303 header_offset, 3304 @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)), 3305 ); 3306 } 3307 3308 fn emitProducerSection(binary_bytes: *std.ArrayList(u8)) !void { 3309 const header_offset = try reserveCustomSectionHeader(binary_bytes); 3310 3311 const writer = binary_bytes.writer(); 3312 const producers = "producers"; 3313 try leb.writeULEB128(writer, @as(u32, @intCast(producers.len))); 3314 try writer.writeAll(producers); 3315 3316 try leb.writeULEB128(writer, @as(u32, 2)); // 2 fields: Language + processed-by 3317 3318 // used for the Zig version 3319 var version_buf: [100]u8 = undefined; 3320 const version = try std.fmt.bufPrint(&version_buf, "{}", .{build_options.semver}); 3321 3322 // language field 3323 { 3324 const language = "language"; 3325 try leb.writeULEB128(writer, @as(u32, @intCast(language.len))); 3326 try writer.writeAll(language); 3327 3328 // field_value_count (TODO: Parse object files for producer sections to detect their language) 3329 try leb.writeULEB128(writer, @as(u32, 1)); 3330 3331 // versioned name 3332 { 3333 try leb.writeULEB128(writer, @as(u32, 3)); // len of "Zig" 3334 try writer.writeAll("Zig"); 3335 3336 try leb.writeULEB128(writer, @as(u32, @intCast(version.len))); 3337 try writer.writeAll(version); 3338 } 3339 } 3340 3341 // processed-by field 3342 { 3343 const processed_by = "processed-by"; 3344 try leb.writeULEB128(writer, @as(u32, @intCast(processed_by.len))); 3345 try writer.writeAll(processed_by); 3346 3347 // field_value_count (TODO: Parse object files for producer sections to detect other used tools) 3348 try leb.writeULEB128(writer, @as(u32, 1)); 3349 3350 // versioned name 3351 { 3352 try leb.writeULEB128(writer, @as(u32, 3)); // len of "Zig" 3353 try writer.writeAll("Zig"); 3354 3355 try leb.writeULEB128(writer, @as(u32, @intCast(version.len))); 3356 try writer.writeAll(version); 3357 } 3358 } 3359 3360 try writeCustomSectionHeader( 3361 binary_bytes.items, 3362 header_offset, 3363 @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)), 3364 ); 3365 } 3366 3367 fn emitBuildIdSection(binary_bytes: *std.ArrayList(u8), build_id: []const u8) !void { 3368 const header_offset = try reserveCustomSectionHeader(binary_bytes); 3369 3370 const writer = binary_bytes.writer(); 3371 const hdr_build_id = "build_id"; 3372 try leb.writeULEB128(writer, @as(u32, @intCast(hdr_build_id.len))); 3373 try writer.writeAll(hdr_build_id); 3374 3375 try leb.writeULEB128(writer, @as(u32, 1)); 3376 try leb.writeULEB128(writer, @as(u32, @intCast(build_id.len))); 3377 try writer.writeAll(build_id); 3378 3379 try writeCustomSectionHeader( 3380 binary_bytes.items, 3381 header_offset, 3382 @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)), 3383 ); 3384 } 3385 3386 fn emitFeaturesSection(binary_bytes: *std.ArrayList(u8), enabled_features: []const bool, features_count: u32) !void { 3387 const header_offset = try reserveCustomSectionHeader(binary_bytes); 3388 3389 const writer = binary_bytes.writer(); 3390 const target_features = "target_features"; 3391 try leb.writeULEB128(writer, @as(u32, @intCast(target_features.len))); 3392 try writer.writeAll(target_features); 3393 3394 try leb.writeULEB128(writer, features_count); 3395 for (enabled_features, 0..) |enabled, feature_index| { 3396 if (enabled) { 3397 const feature: types.Feature = .{ .prefix = .used, .tag = @as(types.Feature.Tag, @enumFromInt(feature_index)) }; 3398 try leb.writeULEB128(writer, @intFromEnum(feature.prefix)); 3399 var buf: [100]u8 = undefined; 3400 const string = try std.fmt.bufPrint(&buf, "{}", .{feature.tag}); 3401 try leb.writeULEB128(writer, @as(u32, @intCast(string.len))); 3402 try writer.writeAll(string); 3403 } 3404 } 3405 3406 try writeCustomSectionHeader( 3407 binary_bytes.items, 3408 header_offset, 3409 @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)), 3410 ); 3411 } 3412 3413 fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem.Allocator) !void { 3414 const comp = wasm.base.comp; 3415 const import_memory = comp.config.import_memory; 3416 const Name = struct { 3417 index: u32, 3418 name: []const u8, 3419 3420 fn lessThan(context: void, lhs: @This(), rhs: @This()) bool { 3421 _ = context; 3422 return lhs.index < rhs.index; 3423 } 3424 }; 3425 3426 // we must de-duplicate symbols that point to the same function 3427 var funcs = std.AutoArrayHashMap(u32, Name).init(arena); 3428 try funcs.ensureUnusedCapacity(wasm.functions.count() + wasm.imported_functions_count); 3429 var globals = try std.ArrayList(Name).initCapacity(arena, wasm.wasm_globals.items.len + wasm.imported_globals_count); 3430 var segments = try std.ArrayList(Name).initCapacity(arena, wasm.data_segments.count()); 3431 3432 for (wasm.resolved_symbols.keys()) |sym_loc| { 3433 const symbol = sym_loc.getSymbol(wasm).*; 3434 if (symbol.isDead()) { 3435 continue; 3436 } 3437 const name = sym_loc.getName(wasm); 3438 switch (symbol.tag) { 3439 .function => { 3440 const gop = funcs.getOrPutAssumeCapacity(symbol.index); 3441 if (!gop.found_existing) { 3442 gop.value_ptr.* = .{ .index = symbol.index, .name = name }; 3443 } 3444 }, 3445 .global => globals.appendAssumeCapacity(.{ .index = symbol.index, .name = name }), 3446 else => {}, 3447 } 3448 } 3449 // data segments are already 'ordered' 3450 var data_segment_index: u32 = 0; 3451 for (wasm.data_segments.keys()) |key| { 3452 // bss section is not emitted when this condition holds true, so we also 3453 // do not output a name for it. 3454 if (!import_memory and std.mem.eql(u8, key, ".bss")) continue; 3455 segments.appendAssumeCapacity(.{ .index = data_segment_index, .name = key }); 3456 data_segment_index += 1; 3457 } 3458 3459 mem.sort(Name, funcs.values(), {}, Name.lessThan); 3460 mem.sort(Name, globals.items, {}, Name.lessThan); 3461 3462 const header_offset = try reserveCustomSectionHeader(binary_bytes); 3463 const writer = binary_bytes.writer(); 3464 try leb.writeULEB128(writer, @as(u32, @intCast("name".len))); 3465 try writer.writeAll("name"); 3466 3467 try wasm.emitNameSubsection(.function, funcs.values(), writer); 3468 try wasm.emitNameSubsection(.global, globals.items, writer); 3469 try wasm.emitNameSubsection(.data_segment, segments.items, writer); 3470 3471 try writeCustomSectionHeader( 3472 binary_bytes.items, 3473 header_offset, 3474 @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)), 3475 ); 3476 } 3477 3478 fn emitNameSubsection(wasm: *Wasm, section_id: std.wasm.NameSubsection, names: anytype, writer: anytype) !void { 3479 const gpa = wasm.base.comp.gpa; 3480 3481 // We must emit subsection size, so first write to a temporary list 3482 var section_list = std.ArrayList(u8).init(gpa); 3483 defer section_list.deinit(); 3484 const sub_writer = section_list.writer(); 3485 3486 try leb.writeULEB128(sub_writer, @as(u32, @intCast(names.len))); 3487 for (names) |name| { 3488 log.debug("Emit symbol '{s}' type({s})", .{ name.name, @tagName(section_id) }); 3489 try leb.writeULEB128(sub_writer, name.index); 3490 try leb.writeULEB128(sub_writer, @as(u32, @intCast(name.name.len))); 3491 try sub_writer.writeAll(name.name); 3492 } 3493 3494 // From now, write to the actual writer 3495 try leb.writeULEB128(writer, @intFromEnum(section_id)); 3496 try leb.writeULEB128(writer, @as(u32, @intCast(section_list.items.len))); 3497 try writer.writeAll(section_list.items); 3498 } 3499 3500 fn emitLimits(writer: anytype, limits: std.wasm.Limits) !void { 3501 try writer.writeByte(limits.flags); 3502 try leb.writeULEB128(writer, limits.min); 3503 if (limits.hasFlag(.WASM_LIMITS_FLAG_HAS_MAX)) { 3504 try leb.writeULEB128(writer, limits.max); 3505 } 3506 } 3507 3508 fn emitInit(writer: anytype, init_expr: std.wasm.InitExpression) !void { 3509 switch (init_expr) { 3510 .i32_const => |val| { 3511 try writer.writeByte(std.wasm.opcode(.i32_const)); 3512 try leb.writeILEB128(writer, val); 3513 }, 3514 .i64_const => |val| { 3515 try writer.writeByte(std.wasm.opcode(.i64_const)); 3516 try leb.writeILEB128(writer, val); 3517 }, 3518 .f32_const => |val| { 3519 try writer.writeByte(std.wasm.opcode(.f32_const)); 3520 try writer.writeInt(u32, @bitCast(val), .little); 3521 }, 3522 .f64_const => |val| { 3523 try writer.writeByte(std.wasm.opcode(.f64_const)); 3524 try writer.writeInt(u64, @bitCast(val), .little); 3525 }, 3526 .global_get => |val| { 3527 try writer.writeByte(std.wasm.opcode(.global_get)); 3528 try leb.writeULEB128(writer, val); 3529 }, 3530 } 3531 try writer.writeByte(std.wasm.opcode(.end)); 3532 } 3533 3534 fn emitImport(wasm: *Wasm, writer: anytype, import: types.Import) !void { 3535 const module_name = wasm.string_table.get(import.module_name); 3536 try leb.writeULEB128(writer, @as(u32, @intCast(module_name.len))); 3537 try writer.writeAll(module_name); 3538 3539 const name = wasm.string_table.get(import.name); 3540 try leb.writeULEB128(writer, @as(u32, @intCast(name.len))); 3541 try writer.writeAll(name); 3542 3543 try writer.writeByte(@intFromEnum(import.kind)); 3544 switch (import.kind) { 3545 .function => |type_index| try leb.writeULEB128(writer, type_index), 3546 .global => |global_type| { 3547 try leb.writeULEB128(writer, std.wasm.valtype(global_type.valtype)); 3548 try writer.writeByte(@intFromBool(global_type.mutable)); 3549 }, 3550 .table => |table| { 3551 try leb.writeULEB128(writer, std.wasm.reftype(table.reftype)); 3552 try emitLimits(writer, table.limits); 3553 }, 3554 .memory => |limits| { 3555 try emitLimits(writer, limits); 3556 }, 3557 } 3558 } 3559 3560 fn linkWithLLD(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) !void { 3561 const tracy = trace(@src()); 3562 defer tracy.end(); 3563 3564 const comp = wasm.base.comp; 3565 const shared_memory = comp.config.shared_memory; 3566 const export_memory = comp.config.export_memory; 3567 const import_memory = comp.config.import_memory; 3568 const target = comp.root_mod.resolved_target.result; 3569 3570 const gpa = comp.gpa; 3571 3572 const directory = wasm.base.emit.directory; // Just an alias to make it shorter to type. 3573 const full_out_path = try directory.join(arena, &[_][]const u8{wasm.base.emit.sub_path}); 3574 3575 // If there is no Zig code to compile, then we should skip flushing the output file because it 3576 // will not be part of the linker line anyway. 3577 const module_obj_path: ?[]const u8 = if (comp.module != null) blk: { 3578 try wasm.flushModule(arena, prog_node); 3579 3580 if (fs.path.dirname(full_out_path)) |dirname| { 3581 break :blk try fs.path.join(arena, &.{ dirname, wasm.base.zcu_object_sub_path.? }); 3582 } else { 3583 break :blk wasm.base.zcu_object_sub_path.?; 3584 } 3585 } else null; 3586 3587 var sub_prog_node = prog_node.start("LLD Link", 0); 3588 sub_prog_node.activate(); 3589 sub_prog_node.context.refresh(); 3590 defer sub_prog_node.end(); 3591 3592 const is_obj = comp.config.output_mode == .Obj; 3593 const compiler_rt_path: ?[]const u8 = blk: { 3594 if (comp.compiler_rt_lib) |lib| break :blk lib.full_object_path; 3595 if (comp.compiler_rt_obj) |obj| break :blk obj.full_object_path; 3596 break :blk null; 3597 }; 3598 3599 const id_symlink_basename = "lld.id"; 3600 3601 var man: Cache.Manifest = undefined; 3602 defer if (!wasm.base.disable_lld_caching) man.deinit(); 3603 3604 var digest: [Cache.hex_digest_len]u8 = undefined; 3605 3606 if (!wasm.base.disable_lld_caching) { 3607 man = comp.cache_parent.obtain(); 3608 3609 // We are about to obtain this lock, so here we give other processes a chance first. 3610 wasm.base.releaseLock(); 3611 3612 comptime assert(Compilation.link_hash_implementation_version == 12); 3613 3614 for (comp.objects) |obj| { 3615 _ = try man.addFile(obj.path, null); 3616 man.hash.add(obj.must_link); 3617 } 3618 for (comp.c_object_table.keys()) |key| { 3619 _ = try man.addFile(key.status.success.object_path, null); 3620 } 3621 try man.addOptionalFile(module_obj_path); 3622 try man.addOptionalFile(compiler_rt_path); 3623 man.hash.addOptionalBytes(wasm.entry_name); 3624 man.hash.add(wasm.base.stack_size); 3625 man.hash.add(wasm.base.build_id); 3626 man.hash.add(import_memory); 3627 man.hash.add(export_memory); 3628 man.hash.add(wasm.import_table); 3629 man.hash.add(wasm.export_table); 3630 man.hash.addOptional(wasm.initial_memory); 3631 man.hash.addOptional(wasm.max_memory); 3632 man.hash.add(shared_memory); 3633 man.hash.addOptional(wasm.global_base); 3634 man.hash.addListOfBytes(wasm.export_symbol_names); 3635 // strip does not need to go into the linker hash because it is part of the hash namespace 3636 3637 // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. 3638 _ = try man.hit(); 3639 digest = man.final(); 3640 3641 var prev_digest_buf: [digest.len]u8 = undefined; 3642 const prev_digest: []u8 = Cache.readSmallFile( 3643 directory.handle, 3644 id_symlink_basename, 3645 &prev_digest_buf, 3646 ) catch |err| blk: { 3647 log.debug("WASM LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); 3648 // Handle this as a cache miss. 3649 break :blk prev_digest_buf[0..0]; 3650 }; 3651 if (mem.eql(u8, prev_digest, &digest)) { 3652 log.debug("WASM LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); 3653 // Hot diggity dog! The output binary is already there. 3654 wasm.base.lock = man.toOwnedLock(); 3655 return; 3656 } 3657 log.debug("WASM LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); 3658 3659 // We are about to change the output file to be different, so we invalidate the build hash now. 3660 directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { 3661 error.FileNotFound => {}, 3662 else => |e| return e, 3663 }; 3664 } 3665 3666 if (is_obj) { 3667 // LLD's WASM driver does not support the equivalent of `-r` so we do a simple file copy 3668 // here. TODO: think carefully about how we can avoid this redundant operation when doing 3669 // build-obj. See also the corresponding TODO in linkAsArchive. 3670 const the_object_path = blk: { 3671 if (comp.objects.len != 0) 3672 break :blk comp.objects[0].path; 3673 3674 if (comp.c_object_table.count() != 0) 3675 break :blk comp.c_object_table.keys()[0].status.success.object_path; 3676 3677 if (module_obj_path) |p| 3678 break :blk p; 3679 3680 // TODO I think this is unreachable. Audit this situation when solving the above TODO 3681 // regarding eliding redundant object -> object transformations. 3682 return error.NoObjectsToLink; 3683 }; 3684 // This can happen when using --enable-cache and using the stage1 backend. In this case 3685 // we can skip the file copy. 3686 if (!mem.eql(u8, the_object_path, full_out_path)) { 3687 try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); 3688 } 3689 } else { 3690 // Create an LLD command line and invoke it. 3691 var argv = std.ArrayList([]const u8).init(gpa); 3692 defer argv.deinit(); 3693 // We will invoke ourselves as a child process to gain access to LLD. 3694 // This is necessary because LLD does not behave properly as a library - 3695 // it calls exit() and does not reset all global data between invocations. 3696 const linker_command = "wasm-ld"; 3697 try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); 3698 try argv.append("--error-limit=0"); 3699 3700 if (comp.config.lto) { 3701 switch (comp.root_mod.optimize_mode) { 3702 .Debug => {}, 3703 .ReleaseSmall => try argv.append("-O2"), 3704 .ReleaseFast, .ReleaseSafe => try argv.append("-O3"), 3705 } 3706 } 3707 3708 if (import_memory) { 3709 try argv.append("--import-memory"); 3710 } 3711 3712 if (export_memory) { 3713 try argv.append("--export-memory"); 3714 } 3715 3716 if (wasm.import_table) { 3717 assert(!wasm.export_table); 3718 try argv.append("--import-table"); 3719 } 3720 3721 if (wasm.export_table) { 3722 assert(!wasm.import_table); 3723 try argv.append("--export-table"); 3724 } 3725 3726 // For wasm-ld we only need to specify '--no-gc-sections' when the user explicitly 3727 // specified it as garbage collection is enabled by default. 3728 if (!wasm.base.gc_sections) { 3729 try argv.append("--no-gc-sections"); 3730 } 3731 3732 if (comp.config.debug_format == .strip) { 3733 try argv.append("-s"); 3734 } 3735 3736 if (wasm.initial_memory) |initial_memory| { 3737 const arg = try std.fmt.allocPrint(arena, "--initial-memory={d}", .{initial_memory}); 3738 try argv.append(arg); 3739 } 3740 3741 if (wasm.max_memory) |max_memory| { 3742 const arg = try std.fmt.allocPrint(arena, "--max-memory={d}", .{max_memory}); 3743 try argv.append(arg); 3744 } 3745 3746 if (shared_memory) { 3747 try argv.append("--shared-memory"); 3748 } 3749 3750 if (wasm.global_base) |global_base| { 3751 const arg = try std.fmt.allocPrint(arena, "--global-base={d}", .{global_base}); 3752 try argv.append(arg); 3753 } else { 3754 // We prepend it by default, so when a stack overflow happens the runtime will trap correctly, 3755 // rather than silently overwrite all global declarations. See https://github.com/ziglang/zig/issues/4496 3756 // 3757 // The user can overwrite this behavior by setting the global-base 3758 try argv.append("--stack-first"); 3759 } 3760 3761 // Users are allowed to specify which symbols they want to export to the wasm host. 3762 for (wasm.export_symbol_names) |symbol_name| { 3763 const arg = try std.fmt.allocPrint(arena, "--export={s}", .{symbol_name}); 3764 try argv.append(arg); 3765 } 3766 3767 if (comp.config.rdynamic) { 3768 try argv.append("--export-dynamic"); 3769 } 3770 3771 if (wasm.entry_name) |entry_name| { 3772 try argv.appendSlice(&.{ "--entry", entry_name }); 3773 } else { 3774 try argv.append("--no-entry"); 3775 } 3776 3777 try argv.appendSlice(&.{ 3778 "-z", 3779 try std.fmt.allocPrint(arena, "stack-size={d}", .{wasm.base.stack_size}), 3780 }); 3781 3782 if (wasm.import_symbols) { 3783 try argv.append("--allow-undefined"); 3784 } 3785 3786 if (comp.config.output_mode == .Lib and comp.config.link_mode == .Dynamic) { 3787 try argv.append("--shared"); 3788 } 3789 if (comp.config.pie) { 3790 try argv.append("--pie"); 3791 } 3792 3793 // XXX - TODO: add when wasm-ld supports --build-id. 3794 // if (wasm.base.build_id) { 3795 // try argv.append("--build-id=tree"); 3796 // } 3797 3798 try argv.appendSlice(&.{ "-o", full_out_path }); 3799 3800 if (target.cpu.arch == .wasm64) { 3801 try argv.append("-mwasm64"); 3802 } 3803 3804 if (target.os.tag == .wasi) { 3805 const is_exe_or_dyn_lib = comp.config.output_mode == .Exe or 3806 (comp.config.output_mode == .Lib and comp.config.link_mode == .Dynamic); 3807 if (is_exe_or_dyn_lib) { 3808 for (comp.wasi_emulated_libs) |crt_file| { 3809 try argv.append(try comp.get_libc_crt_file( 3810 arena, 3811 wasi_libc.emulatedLibCRFileLibName(crt_file), 3812 )); 3813 } 3814 3815 if (comp.config.link_libc) { 3816 try argv.append(try comp.get_libc_crt_file( 3817 arena, 3818 wasi_libc.execModelCrtFileFullName(comp.config.wasi_exec_model), 3819 )); 3820 try argv.append(try comp.get_libc_crt_file(arena, "libc.a")); 3821 } 3822 3823 if (comp.config.link_libcpp) { 3824 try argv.append(comp.libcxx_static_lib.?.full_object_path); 3825 try argv.append(comp.libcxxabi_static_lib.?.full_object_path); 3826 } 3827 } 3828 } 3829 3830 // Positional arguments to the linker such as object files. 3831 var whole_archive = false; 3832 for (comp.objects) |obj| { 3833 if (obj.must_link and !whole_archive) { 3834 try argv.append("-whole-archive"); 3835 whole_archive = true; 3836 } else if (!obj.must_link and whole_archive) { 3837 try argv.append("-no-whole-archive"); 3838 whole_archive = false; 3839 } 3840 try argv.append(obj.path); 3841 } 3842 if (whole_archive) { 3843 try argv.append("-no-whole-archive"); 3844 whole_archive = false; 3845 } 3846 3847 for (comp.c_object_table.keys()) |key| { 3848 try argv.append(key.status.success.object_path); 3849 } 3850 if (module_obj_path) |p| { 3851 try argv.append(p); 3852 } 3853 3854 if (comp.config.output_mode != .Obj and 3855 !comp.skip_linker_dependencies and 3856 !comp.config.link_libc) 3857 { 3858 try argv.append(comp.libc_static_lib.?.full_object_path); 3859 } 3860 3861 if (compiler_rt_path) |p| { 3862 try argv.append(p); 3863 } 3864 3865 if (comp.verbose_link) { 3866 // Skip over our own name so that the LLD linker name is the first argv item. 3867 Compilation.dump_argv(argv.items[1..]); 3868 } 3869 3870 if (std.process.can_spawn) { 3871 // If possible, we run LLD as a child process because it does not always 3872 // behave properly as a library, unfortunately. 3873 // https://github.com/ziglang/zig/issues/3825 3874 var child = std.ChildProcess.init(argv.items, arena); 3875 if (comp.clang_passthrough_mode) { 3876 child.stdin_behavior = .Inherit; 3877 child.stdout_behavior = .Inherit; 3878 child.stderr_behavior = .Inherit; 3879 3880 const term = child.spawnAndWait() catch |err| { 3881 log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); 3882 return error.UnableToSpawnWasm; 3883 }; 3884 switch (term) { 3885 .Exited => |code| { 3886 if (code != 0) { 3887 std.process.exit(code); 3888 } 3889 }, 3890 else => std.process.abort(), 3891 } 3892 } else { 3893 child.stdin_behavior = .Ignore; 3894 child.stdout_behavior = .Ignore; 3895 child.stderr_behavior = .Pipe; 3896 3897 try child.spawn(); 3898 3899 const stderr = try child.stderr.?.reader().readAllAlloc(arena, std.math.maxInt(usize)); 3900 3901 const term = child.wait() catch |err| { 3902 log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); 3903 return error.UnableToSpawnWasm; 3904 }; 3905 3906 switch (term) { 3907 .Exited => |code| { 3908 if (code != 0) { 3909 comp.lockAndParseLldStderr(linker_command, stderr); 3910 return error.LLDReportedFailure; 3911 } 3912 }, 3913 else => { 3914 log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); 3915 return error.LLDCrashed; 3916 }, 3917 } 3918 3919 if (stderr.len != 0) { 3920 log.warn("unexpected LLD stderr:\n{s}", .{stderr}); 3921 } 3922 } 3923 } else { 3924 const exit_code = try lldMain(arena, argv.items, false); 3925 if (exit_code != 0) { 3926 if (comp.clang_passthrough_mode) { 3927 std.process.exit(exit_code); 3928 } else { 3929 return error.LLDReportedFailure; 3930 } 3931 } 3932 } 3933 3934 // Give +x to the .wasm file if it is an executable and the OS is WASI. 3935 // Some systems may be configured to execute such binaries directly. Even if that 3936 // is not the case, it means we will get "exec format error" when trying to run 3937 // it, and then can react to that in the same way as trying to run an ELF file 3938 // from a foreign CPU architecture. 3939 if (fs.has_executable_bit and target.os.tag == .wasi and 3940 comp.config.output_mode == .Exe) 3941 { 3942 // TODO: what's our strategy for reporting linker errors from this function? 3943 // report a nice error here with the file path if it fails instead of 3944 // just returning the error code. 3945 // chmod does not interact with umask, so we use a conservative -rwxr--r-- here. 3946 std.os.fchmodat(fs.cwd().fd, full_out_path, 0o744, 0) catch |err| switch (err) { 3947 error.OperationNotSupported => unreachable, // Not a symlink. 3948 else => |e| return e, 3949 }; 3950 } 3951 } 3952 3953 if (!wasm.base.disable_lld_caching) { 3954 // Update the file with the digest. If it fails we can continue; it only 3955 // means that the next invocation will have an unnecessary cache miss. 3956 Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { 3957 log.warn("failed to save linking hash digest symlink: {s}", .{@errorName(err)}); 3958 }; 3959 // Again failure here only means an unnecessary cache miss. 3960 man.writeManifest() catch |err| { 3961 log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); 3962 }; 3963 // We hang on to this lock so that the output file path can be used without 3964 // other processes clobbering it. 3965 wasm.base.lock = man.toOwnedLock(); 3966 } 3967 } 3968 3969 fn reserveVecSectionHeader(bytes: *std.ArrayList(u8)) !u32 { 3970 // section id + fixed leb contents size + fixed leb vector length 3971 const header_size = 1 + 5 + 5; 3972 const offset = @as(u32, @intCast(bytes.items.len)); 3973 try bytes.appendSlice(&[_]u8{0} ** header_size); 3974 return offset; 3975 } 3976 3977 fn reserveCustomSectionHeader(bytes: *std.ArrayList(u8)) !u32 { 3978 // unlike regular section, we don't emit the count 3979 const header_size = 1 + 5; 3980 const offset = @as(u32, @intCast(bytes.items.len)); 3981 try bytes.appendSlice(&[_]u8{0} ** header_size); 3982 return offset; 3983 } 3984 3985 fn writeVecSectionHeader(buffer: []u8, offset: u32, section: std.wasm.Section, size: u32, items: u32) !void { 3986 var buf: [1 + 5 + 5]u8 = undefined; 3987 buf[0] = @intFromEnum(section); 3988 leb.writeUnsignedFixed(5, buf[1..6], size); 3989 leb.writeUnsignedFixed(5, buf[6..], items); 3990 buffer[offset..][0..buf.len].* = buf; 3991 } 3992 3993 fn writeCustomSectionHeader(buffer: []u8, offset: u32, size: u32) !void { 3994 var buf: [1 + 5]u8 = undefined; 3995 buf[0] = 0; // 0 = 'custom' section 3996 leb.writeUnsignedFixed(5, buf[1..6], size); 3997 buffer[offset..][0..buf.len].* = buf; 3998 } 3999 4000 fn emitLinkSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), symbol_table: *std.AutoArrayHashMap(SymbolLoc, u32)) !void { 4001 const offset = try reserveCustomSectionHeader(binary_bytes); 4002 const writer = binary_bytes.writer(); 4003 // emit "linking" custom section name 4004 const section_name = "linking"; 4005 try leb.writeULEB128(writer, section_name.len); 4006 try writer.writeAll(section_name); 4007 4008 // meta data version, which is currently '2' 4009 try leb.writeULEB128(writer, @as(u32, 2)); 4010 4011 // For each subsection type (found in types.Subsection) we can emit a section. 4012 // Currently, we only support emitting segment info and the symbol table. 4013 try wasm.emitSymbolTable(binary_bytes, symbol_table); 4014 try wasm.emitSegmentInfo(binary_bytes); 4015 4016 const size: u32 = @intCast(binary_bytes.items.len - offset - 6); 4017 try writeCustomSectionHeader(binary_bytes.items, offset, size); 4018 } 4019 4020 fn emitSymbolTable(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), symbol_table: *std.AutoArrayHashMap(SymbolLoc, u32)) !void { 4021 const writer = binary_bytes.writer(); 4022 4023 try leb.writeULEB128(writer, @intFromEnum(types.SubsectionType.WASM_SYMBOL_TABLE)); 4024 const table_offset = binary_bytes.items.len; 4025 4026 var symbol_count: u32 = 0; 4027 for (wasm.resolved_symbols.keys()) |sym_loc| { 4028 const symbol = sym_loc.getSymbol(wasm).*; 4029 if (symbol.tag == .dead) continue; // Do not emit dead symbols 4030 try symbol_table.putNoClobber(sym_loc, symbol_count); 4031 symbol_count += 1; 4032 log.debug("Emit symbol: {}", .{symbol}); 4033 try leb.writeULEB128(writer, @intFromEnum(symbol.tag)); 4034 try leb.writeULEB128(writer, symbol.flags); 4035 4036 const sym_name = if (wasm.export_names.get(sym_loc)) |exp_name| wasm.string_table.get(exp_name) else sym_loc.getName(wasm); 4037 switch (symbol.tag) { 4038 .data => { 4039 try leb.writeULEB128(writer, @as(u32, @intCast(sym_name.len))); 4040 try writer.writeAll(sym_name); 4041 4042 if (symbol.isDefined()) { 4043 try leb.writeULEB128(writer, symbol.index); 4044 const atom_index = wasm.symbol_atom.get(sym_loc).?; 4045 const atom = wasm.getAtom(atom_index); 4046 try leb.writeULEB128(writer, @as(u32, atom.offset)); 4047 try leb.writeULEB128(writer, @as(u32, atom.size)); 4048 } 4049 }, 4050 .section => { 4051 try leb.writeULEB128(writer, symbol.index); 4052 }, 4053 else => { 4054 try leb.writeULEB128(writer, symbol.index); 4055 if (symbol.isDefined()) { 4056 try leb.writeULEB128(writer, @as(u32, @intCast(sym_name.len))); 4057 try writer.writeAll(sym_name); 4058 } 4059 }, 4060 } 4061 } 4062 4063 var buf: [10]u8 = undefined; 4064 leb.writeUnsignedFixed(5, buf[0..5], @intCast(binary_bytes.items.len - table_offset + 5)); 4065 leb.writeUnsignedFixed(5, buf[5..], symbol_count); 4066 try binary_bytes.insertSlice(table_offset, &buf); 4067 } 4068 4069 fn emitSegmentInfo(wasm: *Wasm, binary_bytes: *std.ArrayList(u8)) !void { 4070 const writer = binary_bytes.writer(); 4071 try leb.writeULEB128(writer, @intFromEnum(types.SubsectionType.WASM_SEGMENT_INFO)); 4072 const segment_offset = binary_bytes.items.len; 4073 4074 try leb.writeULEB128(writer, @as(u32, @intCast(wasm.segment_info.count()))); 4075 for (wasm.segment_info.values()) |segment_info| { 4076 log.debug("Emit segment: {s} align({d}) flags({b})", .{ 4077 segment_info.name, 4078 segment_info.alignment, 4079 segment_info.flags, 4080 }); 4081 try leb.writeULEB128(writer, @as(u32, @intCast(segment_info.name.len))); 4082 try writer.writeAll(segment_info.name); 4083 try leb.writeULEB128(writer, segment_info.alignment.toLog2Units()); 4084 try leb.writeULEB128(writer, segment_info.flags); 4085 } 4086 4087 var buf: [5]u8 = undefined; 4088 leb.writeUnsignedFixed(5, &buf, @as(u32, @intCast(binary_bytes.items.len - segment_offset))); 4089 try binary_bytes.insertSlice(segment_offset, &buf); 4090 } 4091 4092 pub fn getULEB128Size(uint_value: anytype) u32 { 4093 const T = @TypeOf(uint_value); 4094 const U = if (@typeInfo(T).Int.bits < 8) u8 else T; 4095 var value = @as(U, @intCast(uint_value)); 4096 4097 var size: u32 = 0; 4098 while (value != 0) : (size += 1) { 4099 value >>= 7; 4100 } 4101 return size; 4102 } 4103 4104 /// For each relocatable section, emits a custom "relocation.<section_name>" section 4105 fn emitCodeRelocations( 4106 wasm: *Wasm, 4107 binary_bytes: *std.ArrayList(u8), 4108 section_index: u32, 4109 symbol_table: std.AutoArrayHashMap(SymbolLoc, u32), 4110 ) !void { 4111 const code_index = wasm.code_section_index orelse return; 4112 const writer = binary_bytes.writer(); 4113 const header_offset = try reserveCustomSectionHeader(binary_bytes); 4114 4115 // write custom section information 4116 const name = "reloc.CODE"; 4117 try leb.writeULEB128(writer, @as(u32, @intCast(name.len))); 4118 try writer.writeAll(name); 4119 try leb.writeULEB128(writer, section_index); 4120 const reloc_start = binary_bytes.items.len; 4121 4122 var count: u32 = 0; 4123 var atom: *Atom = wasm.getAtomPtr(wasm.atoms.get(code_index).?); 4124 // for each atom, we calculate the uleb size and append that 4125 var size_offset: u32 = 5; // account for code section size leb128 4126 while (true) { 4127 size_offset += getULEB128Size(atom.size); 4128 for (atom.relocs.items) |relocation| { 4129 count += 1; 4130 const sym_loc: SymbolLoc = .{ .file = atom.file, .index = relocation.index }; 4131 const symbol_index = symbol_table.get(sym_loc).?; 4132 try leb.writeULEB128(writer, @intFromEnum(relocation.relocation_type)); 4133 const offset = atom.offset + relocation.offset + size_offset; 4134 try leb.writeULEB128(writer, offset); 4135 try leb.writeULEB128(writer, symbol_index); 4136 if (relocation.relocation_type.addendIsPresent()) { 4137 try leb.writeILEB128(writer, relocation.addend); 4138 } 4139 log.debug("Emit relocation: {}", .{relocation}); 4140 } 4141 atom = if (atom.prev) |prev| wasm.getAtomPtr(prev) else break; 4142 } 4143 if (count == 0) return; 4144 var buf: [5]u8 = undefined; 4145 leb.writeUnsignedFixed(5, &buf, count); 4146 try binary_bytes.insertSlice(reloc_start, &buf); 4147 const size: u32 = @intCast(binary_bytes.items.len - header_offset - 6); 4148 try writeCustomSectionHeader(binary_bytes.items, header_offset, size); 4149 } 4150 4151 fn emitDataRelocations( 4152 wasm: *Wasm, 4153 binary_bytes: *std.ArrayList(u8), 4154 section_index: u32, 4155 symbol_table: std.AutoArrayHashMap(SymbolLoc, u32), 4156 ) !void { 4157 if (wasm.data_segments.count() == 0) return; 4158 const writer = binary_bytes.writer(); 4159 const header_offset = try reserveCustomSectionHeader(binary_bytes); 4160 4161 // write custom section information 4162 const name = "reloc.DATA"; 4163 try leb.writeULEB128(writer, @as(u32, @intCast(name.len))); 4164 try writer.writeAll(name); 4165 try leb.writeULEB128(writer, section_index); 4166 const reloc_start = binary_bytes.items.len; 4167 4168 var count: u32 = 0; 4169 // for each atom, we calculate the uleb size and append that 4170 var size_offset: u32 = 5; // account for code section size leb128 4171 for (wasm.data_segments.values()) |segment_index| { 4172 var atom: *Atom = wasm.getAtomPtr(wasm.atoms.get(segment_index).?); 4173 while (true) { 4174 size_offset += getULEB128Size(atom.size); 4175 for (atom.relocs.items) |relocation| { 4176 count += 1; 4177 const sym_loc: SymbolLoc = .{ .file = atom.file, .index = relocation.index }; 4178 const symbol_index = symbol_table.get(sym_loc).?; 4179 try leb.writeULEB128(writer, @intFromEnum(relocation.relocation_type)); 4180 const offset = atom.offset + relocation.offset + size_offset; 4181 try leb.writeULEB128(writer, offset); 4182 try leb.writeULEB128(writer, symbol_index); 4183 if (relocation.relocation_type.addendIsPresent()) { 4184 try leb.writeILEB128(writer, relocation.addend); 4185 } 4186 log.debug("Emit relocation: {}", .{relocation}); 4187 } 4188 atom = if (atom.prev) |prev| wasm.getAtomPtr(prev) else break; 4189 } 4190 } 4191 if (count == 0) return; 4192 4193 var buf: [5]u8 = undefined; 4194 leb.writeUnsignedFixed(5, &buf, count); 4195 try binary_bytes.insertSlice(reloc_start, &buf); 4196 const size = @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)); 4197 try writeCustomSectionHeader(binary_bytes.items, header_offset, size); 4198 } 4199 4200 fn hasPassiveInitializationSegments(wasm: *const Wasm) bool { 4201 const comp = wasm.base.comp; 4202 const import_memory = comp.config.import_memory; 4203 4204 var it = wasm.data_segments.iterator(); 4205 while (it.next()) |entry| { 4206 const segment: Segment = wasm.segments.items[entry.value_ptr.*]; 4207 if (segment.needsPassiveInitialization(import_memory, entry.key_ptr.*)) { 4208 return true; 4209 } 4210 } 4211 return false; 4212 } 4213 4214 /// Searches for a matching function signature. When no matching signature is found, 4215 /// a new entry will be made. The value returned is the index of the type within `wasm.func_types`. 4216 pub fn putOrGetFuncType(wasm: *Wasm, func_type: std.wasm.Type) !u32 { 4217 if (wasm.getTypeIndex(func_type)) |index| { 4218 return index; 4219 } 4220 4221 // functype does not exist. 4222 const gpa = wasm.base.comp.gpa; 4223 const index: u32 = @intCast(wasm.func_types.items.len); 4224 const params = try gpa.dupe(std.wasm.Valtype, func_type.params); 4225 errdefer gpa.free(params); 4226 const returns = try gpa.dupe(std.wasm.Valtype, func_type.returns); 4227 errdefer gpa.free(returns); 4228 try wasm.func_types.append(gpa, .{ 4229 .params = params, 4230 .returns = returns, 4231 }); 4232 return index; 4233 } 4234 4235 /// For the given `decl_index`, stores the corresponding type representing the function signature. 4236 /// Asserts declaration has an associated `Atom`. 4237 /// Returns the index into the list of types. 4238 pub fn storeDeclType(wasm: *Wasm, decl_index: InternPool.DeclIndex, func_type: std.wasm.Type) !u32 { 4239 return wasm.zigObjectPtr().?.storeDeclType(wasm.base.comp.gpa, decl_index, func_type); 4240 } 4241 4242 /// Returns the symbol index of the error name table. 4243 /// 4244 /// When the symbol does not yet exist, it will create a new one instead. 4245 pub fn getErrorTableSymbol(wasm_file: *Wasm) !u32 { 4246 return wasm_file.zigObjectPtr().?.getErrorTableSymbol(wasm_file); 4247 } 4248 4249 /// For a given `InternPool.DeclIndex` returns its corresponding `Atom.Index`. 4250 /// When the index was not found, a new `Atom` will be created, and its index will be returned. 4251 /// The newly created Atom is empty with default fields as specified by `Atom.empty`. 4252 pub fn getOrCreateAtomForDecl(wasm_file: *Wasm, decl_index: InternPool.DeclIndex) !Atom.Index { 4253 return wasm_file.zigObjectPtr().?.getOrCreateAtomForDecl(wasm_file, decl_index); 4254 } 4255 4256 /// Verifies all resolved symbols and checks whether itself needs to be marked alive, 4257 /// as well as any of its references. 4258 fn markReferences(wasm: *Wasm) !void { 4259 const tracy = trace(@src()); 4260 defer tracy.end(); 4261 4262 const do_garbage_collect = wasm.base.gc_sections; 4263 const comp = wasm.base.comp; 4264 4265 for (wasm.resolved_symbols.keys()) |sym_loc| { 4266 const sym = sym_loc.getSymbol(wasm); 4267 if (sym.isExported(comp.config.rdynamic) or sym.isNoStrip() or !do_garbage_collect) { 4268 try wasm.mark(sym_loc); 4269 continue; 4270 } 4271 4272 // Debug sections may require to be parsed and marked when it contains 4273 // relocations to alive symbols. 4274 if (sym.tag == .section and comp.config.debug_format != .strip) { 4275 const obj_file = wasm.file(sym_loc.file) orelse continue; // Incremental debug info is done independently 4276 _ = try obj_file.parseSymbolIntoAtom(wasm, sym_loc.index); 4277 sym.mark(); 4278 } 4279 } 4280 } 4281 4282 /// Marks a symbol as 'alive' recursively so itself and any references it contains to 4283 /// other symbols will not be omit from the binary. 4284 fn mark(wasm: *Wasm, loc: SymbolLoc) !void { 4285 const symbol = loc.getSymbol(wasm); 4286 if (symbol.isAlive()) { 4287 // Symbol is already marked alive, including its references. 4288 // This means we can skip it so we don't end up marking the same symbols 4289 // multiple times. 4290 return; 4291 } 4292 symbol.mark(); 4293 if (symbol.isUndefined()) { 4294 // undefined symbols do not have an associated `Atom` and therefore also 4295 // do not contain relocations. 4296 return; 4297 } 4298 4299 const atom_index = if (wasm.file(loc.file)) |obj_file| 4300 try obj_file.parseSymbolIntoAtom(wasm, loc.index) 4301 else 4302 wasm.symbol_atom.get(loc) orelse return; 4303 4304 const atom = wasm.getAtom(atom_index); 4305 for (atom.relocs.items) |reloc| { 4306 const target_loc: SymbolLoc = .{ .index = reloc.index, .file = loc.file }; 4307 try wasm.mark(target_loc.finalLoc(wasm)); 4308 } 4309 } 4310 4311 fn defaultEntrySymbolName(wasi_exec_model: std.builtin.WasiExecModel) []const u8 { 4312 return switch (wasi_exec_model) { 4313 .reactor => "_initialize", 4314 .command => "_start", 4315 }; 4316 }