| 1 | //! Temporary, dynamically allocated structures used only during flush. |
| 2 | //! Could be constructed fresh each time, or kept around between updates to reduce heap allocations. |
| 3 | |
| 4 | const Flush = @This(); |
| 5 | const Wasm = @import("../Wasm.zig"); |
| 6 | const Object = @import("Object.zig"); |
| 7 | const Zcu = @import("../../Zcu.zig"); |
| 8 | const Alignment = Wasm.Alignment; |
| 9 | const String = Wasm.String; |
| 10 | const InternPool = @import("../../InternPool.zig"); |
| 11 | const Mir = @import("../../codegen/wasm/Mir.zig"); |
| 12 | |
| 13 | const build_options = @import("build_options"); |
| 14 | |
| 15 | const std = @import("std"); |
| 16 | const Allocator = std.mem.Allocator; |
| 17 | const mem = std.mem; |
| 18 | const leb = std.leb; |
| 19 | const log = std.log.scoped(.link); |
| 20 | const assert = std.debug.assert; |
| 21 | const ArrayList = std.ArrayList; |
| 22 | |
| 23 | /// Ordered list of data segments that will appear in the final binary. |
| 24 | /// When sorted, to-be-merged segments will be made adjacent. |
| 25 | /// Values are virtual address. |
| 26 | data_segments: std.array_hash_map.Auto(Wasm.DataSegmentId, u32) = .empty, |
| 27 | /// Each time a `data_segment` offset equals zero it indicates a new group, and |
| 28 | /// the next element in this array will contain the total merged segment size. |
| 29 | /// Value is the virtual memory address of the end of the segment. |
| 30 | data_segment_groups: ArrayList(DataSegmentGroup) = .empty, |
| 31 | |
| 32 | binary_bytes: ArrayList(u8) = .empty, |
| 33 | missing_exports: std.array_hash_map.Auto(String, void) = .empty, |
| 34 | function_imports: std.array_hash_map.Auto(String, Wasm.FunctionImportId) = .empty, |
| 35 | intrinsic_function_imports: std.array_hash_map.Auto(String, Wasm.FunctionType.Index) = .empty, |
| 36 | /// Function aliases emitted after function symbols. |
| 37 | function_export_symbols: std.array_hash_map.Auto(String, FunctionExportSymbol) = .empty, |
| 38 | global_imports: std.array_hash_map.Auto(String, Wasm.GlobalImportId) = .empty, |
| 39 | data_imports: std.array_hash_map.Auto(String, Wasm.DataImportId) = .empty, |
| 40 | /// Data aliases emitted after data symbols. |
| 41 | data_exports: std.array_hash_map.Auto(String, DataExportSymbol) = .empty, |
| 42 | |
| 43 | indirect_function_table: std.array_hash_map.Auto(Wasm.OutputFunctionIndex, void) = .empty, |
| 44 | sorted_init_funcs: std.ArrayList(Wasm.InitFunc) = .empty, |
| 45 | |
| 46 | /// A subset of the full interned function type list created only during flush. |
| 47 | func_types: std.array_hash_map.Auto(Wasm.FunctionType.Index, void) = .empty, |
| 48 | |
| 49 | enum_tag_name_table: std.array_hash_map.Auto(InternPool.Index, u32) = .empty, |
| 50 | |
| 51 | code_relocs: std.ArrayList(Relocation) = .empty, |
| 52 | data_relocs: std.ArrayList(Relocation) = .empty, |
| 53 | |
| 54 | /// For debug purposes only. |
| 55 | memory_layout_finished: bool = false, |
| 56 | |
| 57 | virtual_addrs: VirtualAddrs = undefined, |
| 58 | |
| 59 | /// Index into `func_types`. |
| 60 | pub const FuncTypeIndex = enum(u32) { |
| 61 | _, |
| 62 | |
| 63 | pub fn fromTypeIndex(i: Wasm.FunctionType.Index, f: *const Flush) FuncTypeIndex { |
| 64 | return @fromBackingInt(@intCast(f.func_types.getIndex(i).?)); |
| 65 | } |
| 66 | }; |
| 67 | |
| 68 | /// Index into SYMTAB_FUNCTION. |
| 69 | const FunctionSymbolIndex = enum(u32) { |
| 70 | _, |
| 71 | |
| 72 | fn fromOutputFunctionIndex(i: Wasm.OutputFunctionIndex) FunctionSymbolIndex { |
| 73 | return @fromBackingInt(@backingInt(i)); |
| 74 | } |
| 75 | |
| 76 | fn fromObjectFunctionHandlingWeak(wasm: *const Wasm, index: Wasm.ObjectFunctionIndex) FunctionSymbolIndex { |
| 77 | return fromOutputFunctionIndex(.fromObjectFunctionHandlingWeak(wasm, index)); |
| 78 | } |
| 79 | |
| 80 | fn fromIpNav(wasm: *const Wasm, nav_index: InternPool.Nav.Index) FunctionSymbolIndex { |
| 81 | return fromOutputFunctionIndex(.fromIpNav(wasm, nav_index)); |
| 82 | } |
| 83 | |
| 84 | fn fromTagIndexType(wasm: *const Wasm, ip_index: InternPool.Index) FunctionSymbolIndex { |
| 85 | return fromOutputFunctionIndex(.fromTagIndexType(wasm, ip_index)); |
| 86 | } |
| 87 | |
| 88 | fn fromSymbolName(wasm: *const Wasm, name: String) FunctionSymbolIndex { |
| 89 | const f = &wasm.flush_buffer; |
| 90 | if (f.function_imports.getIndex(name)) |i| return @fromBackingInt(@intCast(i)); |
| 91 | if (f.intrinsic_function_imports.getIndex(name)) |i| return @fromBackingInt(@intCast( |
| 92 | f.function_imports.entries.len + i, |
| 93 | )); |
| 94 | if (f.function_export_symbols.getIndex(name)) |i| return @fromBackingInt(@intCast( |
| 95 | f.function_imports.entries.len + f.intrinsic_function_imports.entries.len + |
| 96 | wasm.functions.entries.len + i, |
| 97 | )); |
| 98 | return fromOutputFunctionIndex(.fromSymbolName(wasm, name)); |
| 99 | } |
| 100 | }; |
| 101 | |
| 102 | /// Index into SYMTAB_DATA. |
| 103 | const DataSymbolIndex = enum(u32) { |
| 104 | _, |
| 105 | |
| 106 | fn fromOutputDataIndex(i: Wasm.OutputDataIndex) DataSymbolIndex { |
| 107 | return @fromBackingInt(@backingInt(i)); |
| 108 | } |
| 109 | |
| 110 | fn fromResolution(wasm: *const Wasm, resolution: Wasm.ObjectDataImport.Resolution) DataSymbolIndex { |
| 111 | return fromOutputDataIndex(Wasm.OutputDataIndex.fromResolution(wasm, resolution).?); |
| 112 | } |
| 113 | |
| 114 | fn fromObjectData(wasm: *const Wasm, index: Wasm.ObjectData.Index) DataSymbolIndex { |
| 115 | return fromOutputDataIndex(.fromObjectData(wasm, index)); |
| 116 | } |
| 117 | |
| 118 | fn fromUav(wasm: *const Wasm, ip_index: InternPool.Index) DataSymbolIndex { |
| 119 | return fromOutputDataIndex(.fromUav(wasm, ip_index)); |
| 120 | } |
| 121 | |
| 122 | fn fromNav(wasm: *const Wasm, nav_index: InternPool.Nav.Index) DataSymbolIndex { |
| 123 | return fromOutputDataIndex(.fromNav(wasm, nav_index)); |
| 124 | } |
| 125 | |
| 126 | fn fromSymbolName(wasm: *const Wasm, name: String) DataSymbolIndex { |
| 127 | const f = &wasm.flush_buffer; |
| 128 | if (f.data_imports.getIndex(name)) |i| return @fromBackingInt(@intCast(i)); |
| 129 | if (f.data_exports.getIndex(name)) |i| return @fromBackingInt(@intCast( |
| 130 | f.data_imports.entries.len + wasm.datas.entries.len + i, |
| 131 | )); |
| 132 | return fromOutputDataIndex(.fromSymbolName(wasm, name)); |
| 133 | } |
| 134 | }; |
| 135 | |
| 136 | /// Index into `indirect_function_table`. |
| 137 | const IndirectFunctionTableIndex = enum(u32) { |
| 138 | _, |
| 139 | |
| 140 | fn fromObjectFunctionHandlingWeak(wasm: *const Wasm, index: Wasm.ObjectFunctionIndex) IndirectFunctionTableIndex { |
| 141 | return fromOutputFunctionIndex(&wasm.flush_buffer, .fromObjectFunctionHandlingWeak(wasm, index)); |
| 142 | } |
| 143 | |
| 144 | fn fromSymbolName(wasm: *const Wasm, name: String) IndirectFunctionTableIndex { |
| 145 | return fromOutputFunctionIndex(&wasm.flush_buffer, .fromSymbolName(wasm, name)); |
| 146 | } |
| 147 | |
| 148 | fn fromOutputFunctionIndex(f: *const Flush, i: Wasm.OutputFunctionIndex) IndirectFunctionTableIndex { |
| 149 | return @fromBackingInt(@intCast(f.indirect_function_table.getIndex(i).?)); |
| 150 | } |
| 151 | |
| 152 | fn fromIpNav(wasm: *const Wasm, nav_index: InternPool.Nav.Index) IndirectFunctionTableIndex { |
| 153 | return fromOutputFunctionIndex(&wasm.flush_buffer, .fromIpNav(wasm, nav_index)); |
| 154 | } |
| 155 | |
| 156 | fn toAbi(i: IndirectFunctionTableIndex) u32 { |
| 157 | return @backingInt(i) + 1; |
| 158 | } |
| 159 | }; |
| 160 | |
| 161 | const SymbolTableOffsets = struct { |
| 162 | function: u32, |
| 163 | data: u32, |
| 164 | global: u32, |
| 165 | table: u32, |
| 166 | }; |
| 167 | |
| 168 | const FunctionExportSymbol = struct { |
| 169 | function_index: Wasm.FunctionIndex, |
| 170 | flags: Wasm.SymbolFlags, |
| 171 | }; |
| 172 | |
| 173 | const DataExportSymbol = struct { |
| 174 | resolution: Wasm.ObjectDataImport.Resolution, |
| 175 | flags: Wasm.SymbolFlags, |
| 176 | }; |
| 177 | |
| 178 | const Relocation = struct { |
| 179 | tag: Object.RelocationType, |
| 180 | offset: u32, |
| 181 | pointee: Pointee, |
| 182 | addend: i32, |
| 183 | |
| 184 | const Pointee = union { |
| 185 | data: DataSymbolIndex, |
| 186 | type_index: FuncTypeIndex, |
| 187 | section: Wasm.ObjectSectionIndex, |
| 188 | function: FunctionSymbolIndex, |
| 189 | global: Wasm.GlobalIndex, |
| 190 | table: Wasm.TableIndex, |
| 191 | }; |
| 192 | }; |
| 193 | |
| 194 | const DataSegmentGroup = struct { |
| 195 | first_segment: Wasm.DataSegmentId, |
| 196 | end_addr: u32, |
| 197 | }; |
| 198 | |
| 199 | pub fn clear(f: *Flush) void { |
| 200 | f.data_segments.clearRetainingCapacity(); |
| 201 | f.data_segment_groups.clearRetainingCapacity(); |
| 202 | f.binary_bytes.clearRetainingCapacity(); |
| 203 | f.intrinsic_function_imports.clearRetainingCapacity(); |
| 204 | f.function_export_symbols.clearRetainingCapacity(); |
| 205 | f.data_exports.clearRetainingCapacity(); |
| 206 | f.indirect_function_table.clearRetainingCapacity(); |
| 207 | f.sorted_init_funcs.clearRetainingCapacity(); |
| 208 | f.func_types.clearRetainingCapacity(); |
| 209 | f.enum_tag_name_table.clearRetainingCapacity(); |
| 210 | f.code_relocs.clearRetainingCapacity(); |
| 211 | f.data_relocs.clearRetainingCapacity(); |
| 212 | f.memory_layout_finished = false; |
| 213 | f.virtual_addrs = undefined; |
| 214 | } |
| 215 | |
| 216 | pub fn deinit(f: *Flush, gpa: Allocator) void { |
| 217 | f.data_segments.deinit(gpa); |
| 218 | f.data_segment_groups.deinit(gpa); |
| 219 | f.binary_bytes.deinit(gpa); |
| 220 | f.missing_exports.deinit(gpa); |
| 221 | f.function_imports.deinit(gpa); |
| 222 | f.intrinsic_function_imports.deinit(gpa); |
| 223 | f.function_export_symbols.deinit(gpa); |
| 224 | f.global_imports.deinit(gpa); |
| 225 | f.data_imports.deinit(gpa); |
| 226 | f.data_exports.deinit(gpa); |
| 227 | f.indirect_function_table.deinit(gpa); |
| 228 | f.sorted_init_funcs.deinit(gpa); |
| 229 | f.func_types.deinit(gpa); |
| 230 | f.enum_tag_name_table.deinit(gpa); |
| 231 | f.code_relocs.deinit(gpa); |
| 232 | f.data_relocs.deinit(gpa); |
| 233 | f.* = undefined; |
| 234 | } |
| 235 | |
| 236 | pub fn finish(f: *Flush, wasm: *Wasm) !void { |
| 237 | const comp = wasm.base.comp; |
| 238 | const io = comp.io; |
| 239 | const shared_memory = comp.config.shared_memory; |
| 240 | const diags = &comp.link_diags; |
| 241 | const gpa = comp.gpa; |
| 242 | const import_memory = comp.config.import_memory; |
| 243 | const export_memory = comp.config.export_memory; |
| 244 | const target = &comp.root_mod.resolved_target.result; |
| 245 | const is64 = switch (target.cpu.arch) { |
| 246 | .wasm32 => false, |
| 247 | .wasm64 => true, |
| 248 | else => unreachable, |
| 249 | }; |
| 250 | const is_obj = comp.config.output_mode == .Obj; |
| 251 | const allow_undefined = is_obj or wasm.import_symbols; |
| 252 | const zcu_references = if (comp.zcu) |zcu| try zcu.resolveReferences() else null; |
| 253 | |
| 254 | const entry_name = if (wasm.entry_resolution.isNavOrUnresolved(wasm)) wasm.entry_name else .none; |
| 255 | |
| 256 | if (comp.zcu) |zcu| { |
| 257 | const ip: *const InternPool = &zcu.intern_pool; // No mutations allowed! |
| 258 | const function_imports_start = wasm.function_imports.entries.len; |
| 259 | const global_imports_start = wasm.global_imports.entries.len; |
| 260 | const data_imports_start = wasm.data_imports.entries.len; |
| 261 | |
| 262 | log.debug("total MIR instructions: {d}", .{wasm.mir_instructions.len}); |
| 263 | |
| 264 | { |
| 265 | var i = wasm.function_imports_len_prelink; |
| 266 | while (i < f.function_imports.entries.len) { |
| 267 | const symbol_name = f.function_imports.keys()[i]; |
| 268 | if (wasm.object_function_imports.getIndex(symbol_name)) |import_index_usize| { |
| 269 | const import_index: Wasm.FunctionImport.Index = @fromBackingInt(@intCast(import_index_usize)); |
| 270 | try wasm.markFunctionImport(symbol_name, import_index.value(wasm), import_index); |
| 271 | f.function_imports.swapRemoveAt(i); |
| 272 | continue; |
| 273 | } |
| 274 | i += 1; |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | { |
| 279 | var i = wasm.data_imports_len_prelink; |
| 280 | while (i < f.data_imports.entries.len) { |
| 281 | const symbol_name = f.data_imports.keys()[i]; |
| 282 | if (wasm.object_data_imports.getIndex(symbol_name)) |import_index_usize| { |
| 283 | const import_index: Wasm.ObjectDataImport.Index = @fromBackingInt(@intCast(import_index_usize)); |
| 284 | try wasm.markDataImport(symbol_name, import_index.value(wasm), import_index); |
| 285 | f.data_imports.swapRemoveAt(i); |
| 286 | continue; |
| 287 | } |
| 288 | i += 1; |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | if (wasm.error_name_table_ref_count > 0) { |
| 293 | // Ensure Zcu error name structures are populated. |
| 294 | const full_error_names = ip.global_error_set.getNamesFromMainThread(); |
| 295 | try wasm.error_name_offs.ensureTotalCapacity(gpa, full_error_names.len + 1); |
| 296 | if (wasm.error_name_offs.items.len == 0) { |
| 297 | // Dummy entry at index 0 to avoid a sub instruction at `@errorName` sites. |
| 298 | wasm.error_name_offs.appendAssumeCapacity(0); |
| 299 | } |
| 300 | const new_error_names = full_error_names[wasm.error_name_offs.items.len - 1 ..]; |
| 301 | for (new_error_names) |error_name| { |
| 302 | wasm.error_name_offs.appendAssumeCapacity(@intCast(wasm.error_name_bytes.items.len)); |
| 303 | const s: [:0]const u8 = error_name.toSlice(ip); |
| 304 | try wasm.error_name_bytes.appendSlice(gpa, s[0 .. s.len + 1]); |
| 305 | } |
| 306 | } |
| 307 | |
| 308 | for (wasm.nav_exports.keys(), wasm.nav_exports.values()) |*nav_export, export_index| { |
| 309 | if (ip.isFunctionType(ip.getNav(nav_export.nav_index).resolved.?.type)) { |
| 310 | log.debug("flush export '{s}' nav={d}", .{ nav_export.name.slice(wasm), nav_export.nav_index }); |
| 311 | const function_index = Wasm.FunctionIndex.fromIpNav(wasm, nav_export.nav_index).?; |
| 312 | const explicit = f.missing_exports.swapRemove(nav_export.name); |
| 313 | const opts = export_index.ptr(zcu).opts; |
| 314 | const is_hidden = !explicit and switch (opts.visibility) { |
| 315 | .hidden => true, |
| 316 | .default, .protected => false, |
| 317 | }; |
| 318 | if (is_obj) try f.function_export_symbols.put(gpa, nav_export.name, .{ |
| 319 | .function_index = function_index, |
| 320 | .flags = .{ |
| 321 | .binding = switch (opts.linkage) { |
| 322 | .internal => .local, |
| 323 | .strong => .strong, |
| 324 | .weak => .weak, |
| 325 | .link_once => @panic("TODO: COMDAT"), |
| 326 | }, |
| 327 | .visibility_hidden = is_hidden, |
| 328 | .exported = !is_hidden, |
| 329 | }, |
| 330 | }); |
| 331 | if (is_hidden) { |
| 332 | try wasm.hidden_function_exports.put(gpa, nav_export.name, function_index); |
| 333 | } else { |
| 334 | try wasm.function_exports.put(gpa, nav_export.name, function_index); |
| 335 | } |
| 336 | _ = f.function_imports.swapRemove(nav_export.name); |
| 337 | |
| 338 | if (nav_export.name.toOptional() == entry_name) |
| 339 | wasm.entry_resolution = .fromIpNav(wasm, nav_export.nav_index); |
| 340 | } else { |
| 341 | // data exports are linker symbols |
| 342 | // explicit exports become address globals |
| 343 | const explicit = f.missing_exports.swapRemove(nav_export.name); |
| 344 | const opts = export_index.ptr(zcu).opts; |
| 345 | try f.data_exports.put(gpa, nav_export.name, .{ |
| 346 | .resolution = .fromIpNav(wasm, nav_export.nav_index), |
| 347 | .flags = if (is_obj) .{ |
| 348 | .binding = switch (opts.linkage) { |
| 349 | .internal => .local, |
| 350 | .strong => .strong, |
| 351 | .weak => .weak, |
| 352 | .link_once => @panic("TODO: COMDAT"), |
| 353 | }, |
| 354 | .visibility_hidden = !explicit and switch (opts.visibility) { |
| 355 | .default => false, |
| 356 | .hidden => true, |
| 357 | .protected => false, |
| 358 | }, |
| 359 | .exported = explicit, |
| 360 | .tls = ip.getNav(nav_export.nav_index).resolved.?.@"threadlocal", |
| 361 | } else .{}, |
| 362 | }); |
| 363 | _ = f.data_imports.swapRemove(nav_export.name); |
| 364 | if (explicit and !is_obj) { |
| 365 | const global_resolution: Wasm.GlobalImport.Resolution = .fromIpNav( |
| 366 | wasm, |
| 367 | nav_export.nav_index, |
| 368 | ); |
| 369 | try wasm.globals.put(gpa, global_resolution, {}); |
| 370 | try wasm.global_exports.append(gpa, .{ |
| 371 | .name = nav_export.name, |
| 372 | .global_index = Wasm.GlobalIndex.fromResolution(wasm, global_resolution).?, |
| 373 | }); |
| 374 | } |
| 375 | } |
| 376 | } |
| 377 | // handle exported values without navs |
| 378 | for (wasm.uav_exports.keys(), wasm.uav_exports.values()) |uav_export, export_index| { |
| 379 | assert(!ip.isFunctionType(ip.typeOf(uav_export.uav_index))); |
| 380 | const explicit = f.missing_exports.swapRemove(uav_export.name); |
| 381 | const opts = export_index.ptr(zcu).opts; |
| 382 | try f.data_exports.put(gpa, uav_export.name, .{ |
| 383 | .resolution = .fromIpIndex(wasm, uav_export.uav_index), |
| 384 | .flags = if (is_obj) .{ |
| 385 | .binding = switch (opts.linkage) { |
| 386 | .internal => .local, |
| 387 | .strong => .strong, |
| 388 | .weak => .weak, |
| 389 | .link_once => @panic("TODO: COMDAT"), |
| 390 | }, |
| 391 | .visibility_hidden = !explicit and switch (opts.visibility) { |
| 392 | .default => false, |
| 393 | .hidden => true, |
| 394 | .protected => false, |
| 395 | }, |
| 396 | .exported = explicit, |
| 397 | } else .{}, |
| 398 | }); |
| 399 | _ = f.data_imports.swapRemove(uav_export.name); |
| 400 | if (explicit and !is_obj) { |
| 401 | const global_resolution: Wasm.GlobalImport.Resolution = .fromIpIndex( |
| 402 | wasm, |
| 403 | uav_export.uav_index, |
| 404 | ); |
| 405 | try wasm.globals.put(gpa, global_resolution, {}); |
| 406 | try wasm.global_exports.append(gpa, .{ |
| 407 | .name = uav_export.name, |
| 408 | .global_index = Wasm.GlobalIndex.fromResolution(wasm, global_resolution).?, |
| 409 | }); |
| 410 | } |
| 411 | } |
| 412 | |
| 413 | // Detect any intrinsics that were called; they need to have dependencies on the symbols marked. |
| 414 | // Likewise detect `@tagName` calls so those functions can be included in the output and synthesized. |
| 415 | for (wasm.mir_instructions.items(.tag), wasm.mir_instructions.items(.data)) |tag, *data| switch (tag) { |
| 416 | .call_intrinsic => { |
| 417 | const symbol_name = try wasm.internString(@tagName(data.intrinsic)); |
| 418 | if (Wasm.FunctionIndex.fromSymbolName(wasm, symbol_name) == null and |
| 419 | !f.function_imports.contains(symbol_name)) |
| 420 | { |
| 421 | if (wasm.object_function_imports.getIndex(symbol_name)) |object_import_index| { |
| 422 | const i: Wasm.FunctionImport.Index = @fromBackingInt(@intCast(object_import_index)); |
| 423 | try wasm.markFunctionImport(symbol_name, i.value(wasm), i); |
| 424 | if (Wasm.FunctionIndex.fromSymbolName(wasm, symbol_name) == null) { |
| 425 | try f.function_imports.put(gpa, symbol_name, .fromObject(i, wasm)); |
| 426 | } |
| 427 | } else if (is_obj) { |
| 428 | const gop = try f.intrinsic_function_imports.getOrPut(gpa, symbol_name); |
| 429 | if (!gop.found_existing) gop.value_ptr.* = try wasm.intrinsicFunctionType(data.intrinsic); |
| 430 | } else { |
| 431 | return diags.fail("missing compiler runtime intrinsic '{t}' (undefined linker symbol)", .{ |
| 432 | data.intrinsic, |
| 433 | }); |
| 434 | } |
| 435 | } |
| 436 | }, |
| 437 | .call_indirect => { |
| 438 | const fn_info = zcu.typeToFunc(.fromInterned(data.ip_index)).?; |
| 439 | const type_index = wasm.getExistingFunctionType( |
| 440 | fn_info.cc, |
| 441 | fn_info.param_types.get(ip), |
| 442 | .fromInterned(fn_info.return_type), |
| 443 | fn_info.is_var_args, |
| 444 | target, |
| 445 | ).?; |
| 446 | try f.func_types.put(gpa, type_index, {}); |
| 447 | }, |
| 448 | .call_tag_index => { |
| 449 | assert(ip.indexToKey(data.ip_index) == .enum_type); |
| 450 | const gop = try wasm.zcu_funcs.getOrPut(gpa, data.ip_index); |
| 451 | if (!gop.found_existing) { |
| 452 | const int_tag_ty = Zcu.Type.fromInterned(data.ip_index).backingIntType(zcu); |
| 453 | gop.value_ptr.* = .{ .tag_name = .{ |
| 454 | .symbol_name = try wasm.internStringFmt("__zig_tag_index_{d}", .{data.ip_index}), |
| 455 | .type_index = try wasm.internFunctionType(.auto, &.{int_tag_ty.ip_index}, .u32, false, target), |
| 456 | } }; |
| 457 | } |
| 458 | try wasm.functions.put(gpa, .fromZcuFunc(wasm, @fromBackingInt(@intCast(gop.index))), {}); |
| 459 | }, |
| 460 | .enum_tag_name_table_ref => { |
| 461 | assert(ip.indexToKey(data.ip_index) == .enum_type); |
| 462 | const gop = try f.enum_tag_name_table.getOrPut(gpa, data.ip_index); |
| 463 | if (!gop.found_existing) { |
| 464 | wasm.tag_name_table_ref_count += 1; |
| 465 | gop.value_ptr.* = @intCast(wasm.tag_name_offs.items.len); |
| 466 | const tag_names = ip.loadEnumType(data.ip_index).field_names; |
| 467 | for (tag_names.get(ip)) |tag_name| { |
| 468 | const slice = tag_name.toSlice(ip); |
| 469 | try wasm.tag_name_offs.append(gpa, @intCast(wasm.tag_name_bytes.items.len)); |
| 470 | try wasm.tag_name_bytes.appendSlice(gpa, slice[0 .. slice.len + 1]); |
| 471 | } |
| 472 | } |
| 473 | }, |
| 474 | else => continue, |
| 475 | }; |
| 476 | |
| 477 | // marking above may discover additional imports |
| 478 | try f.function_imports.ensureUnusedCapacity(gpa, wasm.function_imports.entries.len - function_imports_start); |
| 479 | for ( |
| 480 | wasm.function_imports.keys()[function_imports_start..], |
| 481 | wasm.function_imports.values()[function_imports_start..], |
| 482 | ) |name, id| { |
| 483 | if (!f.function_imports.contains(name) and Wasm.FunctionIndex.fromSymbolName(wasm, name) == null) { |
| 484 | f.function_imports.putAssumeCapacity(name, id); |
| 485 | } |
| 486 | } |
| 487 | |
| 488 | try f.global_imports.ensureUnusedCapacity(gpa, wasm.global_imports.entries.len - global_imports_start); |
| 489 | for ( |
| 490 | wasm.global_imports.keys()[global_imports_start..], |
| 491 | wasm.global_imports.values()[global_imports_start..], |
| 492 | ) |name, id| { |
| 493 | if (!f.global_imports.contains(name)) f.global_imports.putAssumeCapacity(name, id); |
| 494 | } |
| 495 | |
| 496 | try f.data_imports.ensureUnusedCapacity(gpa, wasm.data_imports.entries.len - data_imports_start); |
| 497 | for ( |
| 498 | wasm.data_imports.keys()[data_imports_start..], |
| 499 | wasm.data_imports.values()[data_imports_start..], |
| 500 | ) |name, id| { |
| 501 | if (!f.data_imports.contains(name) and !f.data_exports.contains(name)) { |
| 502 | f.data_imports.putAssumeCapacity(name, id); |
| 503 | } |
| 504 | } |
| 505 | |
| 506 | for (f.missing_exports.keys()) |exp_name| { |
| 507 | diags.addError("manually specified export name '{s}' undefined", .{exp_name.slice(wasm)}); |
| 508 | } |
| 509 | } |
| 510 | |
| 511 | if (entry_name.unwrap()) |name| { |
| 512 | if (wasm.entry_resolution == .unresolved) { |
| 513 | var err = try diags.addErrorWithNotes(1); |
| 514 | try err.addMsg("entry symbol '{s}' missing", .{name.slice(wasm)}); |
| 515 | err.addNote("'-fno-entry' suppresses this error", .{}); |
| 516 | } |
| 517 | } |
| 518 | |
| 519 | if (!allow_undefined) { |
| 520 | for (f.function_imports.keys(), f.function_imports.values()) |name, function_import_id| { |
| 521 | if (function_import_id.undefinedAllowed(wasm)) continue; |
| 522 | const src_loc = function_import_id.sourceLocation(wasm); |
| 523 | src_loc.addError(wasm, "undefined function: {s}", .{name.slice(wasm)}); |
| 524 | } |
| 525 | for (f.global_imports.keys(), f.global_imports.values()) |name, global_import_id| { |
| 526 | const src_loc = global_import_id.sourceLocation(wasm); |
| 527 | src_loc.addError(wasm, "undefined global: {s}", .{name.slice(wasm)}); |
| 528 | } |
| 529 | for (wasm.table_imports.keys(), wasm.table_imports.values()) |name, table_import_id| { |
| 530 | const src_loc = table_import_id.value(wasm).source_location; |
| 531 | src_loc.addError(wasm, "undefined table: {s}", .{name.slice(wasm)}); |
| 532 | } |
| 533 | for (f.data_imports.keys(), f.data_imports.values()) |name, data_import_id| { |
| 534 | const src_loc = data_import_id.sourceLocation(wasm); |
| 535 | src_loc.addError(wasm, "undefined data: {s}", .{name.slice(wasm)}); |
| 536 | } |
| 537 | } |
| 538 | |
| 539 | if (diags.hasErrors()) return error.AlreadyReported; |
| 540 | |
| 541 | // Merge indirect function tables. |
| 542 | try f.indirect_function_table.ensureUnusedCapacity(gpa, wasm.zcu_indirect_function_set.entries.len + |
| 543 | wasm.object_indirect_function_import_set.entries.len + wasm.object_indirect_function_set.entries.len); |
| 544 | // This one goes first so the indexes can be stable for MIR lowering. |
| 545 | for (wasm.zcu_indirect_function_set.keys()) |nav_index| |
| 546 | f.indirect_function_table.putAssumeCapacity(.fromIpNav(wasm, nav_index), {}); |
| 547 | for (wasm.object_indirect_function_import_set.keys()) |symbol_name| |
| 548 | f.indirect_function_table.putAssumeCapacity(.fromSymbolName(wasm, symbol_name), {}); |
| 549 | for (wasm.object_indirect_function_set.keys()) |object_function_index| |
| 550 | f.indirect_function_table.putAssumeCapacity(.fromObjectFunction(wasm, object_function_index), {}); |
| 551 | |
| 552 | try f.sorted_init_funcs.ensureUnusedCapacity(gpa, wasm.object_init_funcs.items.len); |
| 553 | for (wasm.object_init_funcs.items) |init_func| { |
| 554 | const func = init_func.function_index.ptr(wasm); |
| 555 | if (!func.object_index.ptr(wasm).is_included) continue; |
| 556 | f.sorted_init_funcs.appendAssumeCapacity(init_func); |
| 557 | } |
| 558 | if (f.sorted_init_funcs.items.len > 0) { |
| 559 | // Zig has no constructors so these are only for object file inputs. |
| 560 | mem.sortUnstable(Wasm.InitFunc, f.sorted_init_funcs.items, {}, Wasm.InitFunc.lessThan); |
| 561 | if (!is_obj) try wasm.functions.put(gpa, .__wasm_call_ctors, {}); |
| 562 | } |
| 563 | |
| 564 | if (is_obj) { |
| 565 | try wasm.datas.ensureUnusedCapacity(gpa, wasm.uavs_obj.entries.len + wasm.navs_obj.entries.len + 4); |
| 566 | for (0..wasm.uavs_obj.entries.len) |i| wasm.datas.putAssumeCapacity( |
| 567 | .pack(wasm, .{ .uav_obj = @fromBackingInt(@intCast(i)) }), |
| 568 | {}, |
| 569 | ); |
| 570 | for (0..wasm.navs_obj.entries.len) |i| wasm.datas.putAssumeCapacity( |
| 571 | .pack(wasm, .{ .nav_obj = @fromBackingInt(@intCast(i)) }), |
| 572 | {}, |
| 573 | ); |
| 574 | if (wasm.error_name_table_ref_count > 0) { |
| 575 | wasm.datas.putAssumeCapacity(.__zig_error_names, {}); |
| 576 | wasm.datas.putAssumeCapacity(.__zig_error_name_table, {}); |
| 577 | } |
| 578 | if (wasm.tag_name_table_ref_count > 0) { |
| 579 | wasm.datas.putAssumeCapacity(.__zig_tag_names, {}); |
| 580 | wasm.datas.putAssumeCapacity(.__zig_tag_name_table, {}); |
| 581 | } |
| 582 | } |
| 583 | |
| 584 | // Merge and order the data segments. Depends on garbage collection so that |
| 585 | // unused segments can be omitted. |
| 586 | try f.data_segments.ensureUnusedCapacity(gpa, wasm.data_segments.entries.len + |
| 587 | wasm.uavs_obj.entries.len + wasm.navs_obj.entries.len + |
| 588 | wasm.uavs_exe.entries.len + wasm.navs_exe.entries.len + 4); |
| 589 | if (is_obj) assert(wasm.uavs_exe.entries.len == 0); |
| 590 | if (is_obj) assert(wasm.navs_exe.entries.len == 0); |
| 591 | if (!is_obj) assert(wasm.uavs_obj.entries.len == 0); |
| 592 | if (!is_obj) assert(wasm.navs_obj.entries.len == 0); |
| 593 | for (0..wasm.uavs_obj.entries.len) |uavs_index| f.data_segments.putAssumeCapacityNoClobber(.pack(wasm, .{ |
| 594 | .uav_obj = @fromBackingInt(@intCast(uavs_index)), |
| 595 | }), @as(u32, undefined)); |
| 596 | for (0..wasm.navs_obj.entries.len) |navs_index| f.data_segments.putAssumeCapacityNoClobber(.pack(wasm, .{ |
| 597 | .nav_obj = @fromBackingInt(@intCast(navs_index)), |
| 598 | }), @as(u32, undefined)); |
| 599 | for (0..wasm.uavs_exe.entries.len) |uavs_index| f.data_segments.putAssumeCapacityNoClobber(.pack(wasm, .{ |
| 600 | .uav_exe = @fromBackingInt(@intCast(uavs_index)), |
| 601 | }), @as(u32, undefined)); |
| 602 | for (0..wasm.navs_exe.entries.len) |navs_index| f.data_segments.putAssumeCapacityNoClobber(.pack(wasm, .{ |
| 603 | .nav_exe = @fromBackingInt(@intCast(navs_index)), |
| 604 | }), @as(u32, undefined)); |
| 605 | if (wasm.error_name_table_ref_count > 0) { |
| 606 | f.data_segments.putAssumeCapacity(.__zig_error_names, @as(u32, undefined)); |
| 607 | f.data_segments.putAssumeCapacity(.__zig_error_name_table, @as(u32, undefined)); |
| 608 | } |
| 609 | if (wasm.tag_name_table_ref_count > 0) { |
| 610 | f.data_segments.putAssumeCapacity(.__zig_tag_names, @as(u32, undefined)); |
| 611 | f.data_segments.putAssumeCapacity(.__zig_tag_name_table, @as(u32, undefined)); |
| 612 | } |
| 613 | for (wasm.data_segments.keys()) |data_id| f.data_segments.putAssumeCapacity(data_id, @as(u32, undefined)); |
| 614 | |
| 615 | try wasm.functions.ensureUnusedCapacity(gpa, 3); |
| 616 | |
| 617 | // Passive segments are used to avoid memory being reinitialized on each |
| 618 | // thread's instantiation. These passive segments are initialized and |
| 619 | // dropped in __wasm_init_memory, which is registered as the start function |
| 620 | // We also initialize bss segments (using memory.fill) as part of this |
| 621 | // function. |
| 622 | if (!is_obj and wasm.any_passive_inits) { |
| 623 | try wasm.addFunction(.__wasm_init_memory, &.{}, &.{}); |
| 624 | } |
| 625 | |
| 626 | try wasm.tables.ensureUnusedCapacity(gpa, 1); |
| 627 | |
| 628 | if (f.indirect_function_table.entries.len > 0) { |
| 629 | if (is_obj) { |
| 630 | const name = wasm.preloaded_strings.__indirect_function_table; |
| 631 | const gop = try wasm.object_table_imports.getOrPut(gpa, name); |
| 632 | if (!gop.found_existing) gop.value_ptr.* = .{ |
| 633 | .flags = .{ |
| 634 | .undefined = true, |
| 635 | .no_strip = true, |
| 636 | }, |
| 637 | .module_name = wasm.preloaded_strings.env, |
| 638 | .name = name, |
| 639 | .source_location = .zig_object_nofile, |
| 640 | .resolution = .unresolved, |
| 641 | .limits_min = 1, |
| 642 | .limits_max = 0, |
| 643 | }; |
| 644 | const import_index: Wasm.TableImport.Index = @fromBackingInt(@intCast(gop.index)); |
| 645 | try wasm.markTableImport(name, gop.value_ptr, import_index); |
| 646 | } else { |
| 647 | wasm.tables.putAssumeCapacity(.__indirect_function_table, {}); |
| 648 | } |
| 649 | } |
| 650 | |
| 651 | // Sort order: |
| 652 | // 0. Segment category (tls, data, zero) |
| 653 | // 1. Segment name prefix |
| 654 | // 2. Segment alignment |
| 655 | // 3. Reference count, descending (optimize for LEB encoding) |
| 656 | // 4. Segment name suffix |
| 657 | // 5. Segment ID interpreted as an integer (for determinism) |
| 658 | // |
| 659 | // TLS segments are intended to be merged with each other, and segments |
| 660 | // with a common prefix name are intended to be merged with each other. |
| 661 | // Sorting ensures the segments intended to be merged will be adjacent. |
| 662 | // |
| 663 | // Each Zcu Nav and Cau has an independent data segment ID in this logic. |
| 664 | // For the purposes of sorting, they are implicitly all named ".data". |
| 665 | const Sort = struct { |
| 666 | wasm: *const Wasm, |
| 667 | segments: []const Wasm.DataSegmentId, |
| 668 | pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool { |
| 669 | const lhs_segment = ctx.segments[lhs]; |
| 670 | const rhs_segment = ctx.segments[rhs]; |
| 671 | const lhs_category = @backingInt(lhs_segment.category(ctx.wasm)); |
| 672 | const rhs_category = @backingInt(rhs_segment.category(ctx.wasm)); |
| 673 | switch (std.math.order(lhs_category, rhs_category)) { |
| 674 | .lt => return true, |
| 675 | .gt => return false, |
| 676 | .eq => {}, |
| 677 | } |
| 678 | const lhs_segment_name = lhs_segment.name(ctx.wasm); |
| 679 | const rhs_segment_name = rhs_segment.name(ctx.wasm); |
| 680 | const lhs_prefix, const lhs_suffix = splitSegmentName(lhs_segment_name); |
| 681 | const rhs_prefix, const rhs_suffix = splitSegmentName(rhs_segment_name); |
| 682 | switch (mem.order(u8, lhs_prefix, rhs_prefix)) { |
| 683 | .lt => return true, |
| 684 | .gt => return false, |
| 685 | .eq => {}, |
| 686 | } |
| 687 | const lhs_alignment = lhs_segment.alignment(ctx.wasm); |
| 688 | const rhs_alignment = rhs_segment.alignment(ctx.wasm); |
| 689 | switch (lhs_alignment.order(rhs_alignment)) { |
| 690 | .lt => return false, |
| 691 | .gt => return true, |
| 692 | .eq => {}, |
| 693 | } |
| 694 | switch (std.math.order(lhs_segment.refCount(ctx.wasm), rhs_segment.refCount(ctx.wasm))) { |
| 695 | .lt => return false, |
| 696 | .gt => return true, |
| 697 | .eq => {}, |
| 698 | } |
| 699 | switch (mem.order(u8, lhs_suffix, rhs_suffix)) { |
| 700 | .lt => return true, |
| 701 | .gt => return false, |
| 702 | .eq => {}, |
| 703 | } |
| 704 | return @backingInt(lhs_segment) < @backingInt(rhs_segment); |
| 705 | } |
| 706 | }; |
| 707 | f.data_segments.sortUnstable(@as(Sort, .{ |
| 708 | .wasm = wasm, |
| 709 | .segments = f.data_segments.keys(), |
| 710 | })); |
| 711 | |
| 712 | const page_size = std.wasm.page_size; // 64kb |
| 713 | const stack_alignment: Alignment = .@"16"; // wasm's stack alignment as specified by tool-convention |
| 714 | const heap_alignment: Alignment = .@"16"; // wasm's heap alignment as specified by tool-convention |
| 715 | const pointer_alignment: Alignment = .@"4"; |
| 716 | // Always place the stack at the start by default unless the user specified the global-base flag. |
| 717 | const place_stack_first, var memory_ptr: u64 = if (wasm.global_base) |base| .{ false, base } else .{ true, 0 }; |
| 718 | |
| 719 | const virtual_addrs = &f.virtual_addrs; |
| 720 | virtual_addrs.* = .{ |
| 721 | .global_base = undefined, |
| 722 | .stack_pointer = undefined, |
| 723 | .heap_base = undefined, |
| 724 | .heap_end = undefined, |
| 725 | .wasm_first_page_end = page_size, |
| 726 | .tls_base = null, |
| 727 | .tls_align = .none, |
| 728 | .tls_size = null, |
| 729 | .init_memory_flag = null, |
| 730 | }; |
| 731 | |
| 732 | if (place_stack_first and !is_obj) { |
| 733 | memory_ptr = stack_alignment.forward(memory_ptr); |
| 734 | memory_ptr += wasm.base.stack_size; |
| 735 | virtual_addrs.stack_pointer = @intCast(memory_ptr); |
| 736 | } |
| 737 | |
| 738 | const data_vaddr: u32 = @intCast(memory_ptr); |
| 739 | virtual_addrs.global_base = data_vaddr; |
| 740 | |
| 741 | const segment_ids = f.data_segments.keys(); |
| 742 | const segment_vaddrs = f.data_segments.values(); |
| 743 | assert(f.data_segment_groups.items.len == 0); |
| 744 | if (segment_ids.len > 0) { |
| 745 | var seen_tls: enum { before, during, after } = .before; |
| 746 | var category: Wasm.DataSegmentId.Category = undefined; |
| 747 | var first_segment: Wasm.DataSegmentId = segment_ids[0]; |
| 748 | for (segment_ids, segment_vaddrs, 0..) |segment_id, *segment_vaddr, i| { |
| 749 | const alignment = segment_id.alignment(wasm); |
| 750 | category = segment_id.category(wasm); |
| 751 | const start_addr = alignment.forward(memory_ptr); |
| 752 | |
| 753 | const want_new_segment = b: { |
| 754 | if (is_obj) break :b i != 0; |
| 755 | switch (seen_tls) { |
| 756 | .before => switch (category) { |
| 757 | .tls => { |
| 758 | virtual_addrs.tls_base = if (shared_memory) 0 else @intCast(start_addr); |
| 759 | virtual_addrs.tls_align = alignment; |
| 760 | seen_tls = .during; |
| 761 | break :b f.data_segment_groups.items.len > 0; |
| 762 | }, |
| 763 | else => {}, |
| 764 | }, |
| 765 | .during => switch (category) { |
| 766 | .tls => { |
| 767 | virtual_addrs.tls_align = virtual_addrs.tls_align.maxStrict(alignment); |
| 768 | virtual_addrs.tls_size = @intCast(memory_ptr - virtual_addrs.tls_base.?); |
| 769 | break :b false; |
| 770 | }, |
| 771 | else => { |
| 772 | seen_tls = .after; |
| 773 | break :b true; |
| 774 | }, |
| 775 | }, |
| 776 | .after => {}, |
| 777 | } |
| 778 | break :b i >= 1 and !wantSegmentMerge(wasm, segment_ids[i - 1], segment_id, category); |
| 779 | }; |
| 780 | if (want_new_segment) { |
| 781 | log.debug("new segment group at 0x{x} {} {s} {}", .{ start_addr, segment_id, segment_id.name(wasm), category }); |
| 782 | try f.data_segment_groups.append(gpa, .{ |
| 783 | .end_addr = @intCast(memory_ptr), |
| 784 | .first_segment = first_segment, |
| 785 | }); |
| 786 | first_segment = segment_id; |
| 787 | } |
| 788 | |
| 789 | const size = segment_id.size(wasm); |
| 790 | segment_vaddr.* = @intCast(start_addr); |
| 791 | log.debug("0x{x} {d} {s}", .{ start_addr, @backingInt(segment_id), segment_id.name(wasm) }); |
| 792 | memory_ptr = start_addr + size; |
| 793 | } |
| 794 | if (is_obj or category != .zero) try f.data_segment_groups.append(gpa, .{ |
| 795 | .first_segment = first_segment, |
| 796 | .end_addr = @intCast(memory_ptr), |
| 797 | }); |
| 798 | if (category == .tls and seen_tls == .during) { |
| 799 | virtual_addrs.tls_size = @intCast(memory_ptr - virtual_addrs.tls_base.?); |
| 800 | } |
| 801 | } |
| 802 | |
| 803 | if (shared_memory and wasm.any_passive_inits) { |
| 804 | memory_ptr = pointer_alignment.forward(memory_ptr); |
| 805 | virtual_addrs.init_memory_flag = @intCast(memory_ptr); |
| 806 | memory_ptr += 4; |
| 807 | } |
| 808 | |
| 809 | if (!place_stack_first and !is_obj) { |
| 810 | memory_ptr = stack_alignment.forward(memory_ptr); |
| 811 | memory_ptr += wasm.base.stack_size; |
| 812 | virtual_addrs.stack_pointer = @intCast(memory_ptr); |
| 813 | } |
| 814 | |
| 815 | memory_ptr = heap_alignment.forward(memory_ptr); |
| 816 | virtual_addrs.heap_base = @intCast(memory_ptr); |
| 817 | |
| 818 | if (wasm.initial_memory) |initial_memory| { |
| 819 | if (!mem.isAlignedGeneric(u64, initial_memory, page_size)) { |
| 820 | diags.addError("initial memory value {d} is not {d}-byte aligned", .{ initial_memory, page_size }); |
| 821 | } |
| 822 | if (memory_ptr > initial_memory) { |
| 823 | diags.addError("initial memory value {d} insufficient; minimum {d}", .{ initial_memory, memory_ptr }); |
| 824 | } |
| 825 | if (initial_memory > std.math.maxInt(u32)) { |
| 826 | diags.addError("initial memory value {d} exceeds 32-bit address space", .{initial_memory}); |
| 827 | } |
| 828 | if (diags.hasErrors()) return error.AlreadyReported; |
| 829 | memory_ptr = initial_memory; |
| 830 | } else { |
| 831 | memory_ptr = mem.alignForward(u64, memory_ptr, std.wasm.page_size); |
| 832 | } |
| 833 | virtual_addrs.heap_end = @intCast(memory_ptr); |
| 834 | |
| 835 | // In case we do not import memory, but define it ourselves, set the |
| 836 | // minimum amount of pages on the memory section. |
| 837 | wasm.memories.limits.min = @intCast(memory_ptr / page_size); |
| 838 | log.debug("total memory pages: {d}", .{wasm.memories.limits.min}); |
| 839 | |
| 840 | if (wasm.max_memory) |max_memory| { |
| 841 | if (!mem.isAlignedGeneric(u64, max_memory, page_size)) { |
| 842 | diags.addError("maximum memory value {d} is not {d}-byte aligned", .{ max_memory, page_size }); |
| 843 | } |
| 844 | if (memory_ptr > max_memory) { |
| 845 | diags.addError("maximum memory value {d} insufficient; minimum {d}", .{ max_memory, memory_ptr }); |
| 846 | } |
| 847 | if (max_memory > std.math.maxInt(u32)) { |
| 848 | diags.addError("maximum memory value {d} exceeds 32-bit address space", .{max_memory}); |
| 849 | } |
| 850 | if (diags.hasErrors()) return error.AlreadyReported; |
| 851 | wasm.memories.limits.max = @intCast(max_memory / page_size); |
| 852 | wasm.memories.limits.flags.has_max = true; |
| 853 | if (shared_memory) wasm.memories.limits.flags.is_shared = true; |
| 854 | log.debug("maximum memory pages: {d}", .{wasm.memories.limits.max}); |
| 855 | } |
| 856 | f.memory_layout_finished = true; |
| 857 | |
| 858 | // When we have TLS GOT entries and shared memory is enabled, we must |
| 859 | // perform runtime relocations or else we don't create the function. |
| 860 | if (!is_obj and shared_memory and virtual_addrs.tls_base != null) { |
| 861 | // This logic that checks `any_tls_relocs` is missing the part where it |
| 862 | // also notices threadlocal globals from Zcu code. |
| 863 | if (wasm.any_tls_relocs) try wasm.addFunction(.__wasm_apply_global_tls_relocs, &.{}, &.{}); |
| 864 | try wasm.addFunction(.__wasm_init_tls, &.{.i32}, &.{}); |
| 865 | try wasm.globals.ensureUnusedCapacity(gpa, 3); |
| 866 | wasm.globals.putAssumeCapacity(.__tls_base, {}); |
| 867 | wasm.globals.putAssumeCapacity(.__tls_size, {}); |
| 868 | wasm.globals.putAssumeCapacity(.__tls_align, {}); |
| 869 | } |
| 870 | |
| 871 | var section_index: u32 = 0; |
| 872 | // Index of the code section. Used to tell relocation table where the section lives. |
| 873 | var code_section_index: ?u32 = null; |
| 874 | // Index of the data section. Used to tell relocation table where the section lives. |
| 875 | var data_section_index: ?u32 = null; |
| 876 | |
| 877 | const binary_bytes = &f.binary_bytes; |
| 878 | assert(binary_bytes.items.len == 0); |
| 879 | |
| 880 | try binary_bytes.appendSlice(gpa, &std.wasm.magic ++ &std.wasm.version); |
| 881 | assert(binary_bytes.items.len == 8); |
| 882 | |
| 883 | // Type section. |
| 884 | for (f.function_imports.values()) |id| { |
| 885 | try f.func_types.put(gpa, id.functionType(wasm), {}); |
| 886 | } |
| 887 | for (f.intrinsic_function_imports.values()) |type_index| { |
| 888 | try f.func_types.put(gpa, type_index, {}); |
| 889 | } |
| 890 | for (wasm.functions.keys()) |function| { |
| 891 | try f.func_types.put(gpa, function.typeIndex(wasm), {}); |
| 892 | } |
| 893 | if (f.func_types.entries.len != 0) { |
| 894 | const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); |
| 895 | for (f.func_types.keys()) |func_type_index| { |
| 896 | const func_type = func_type_index.ptr(wasm); |
| 897 | try appendLeb128(gpa, binary_bytes, std.wasm.function_type); |
| 898 | const params = func_type.params.slice(wasm); |
| 899 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(params.len))); |
| 900 | for (params) |param_ty| { |
| 901 | try appendLeb128(gpa, binary_bytes, @backingInt(param_ty)); |
| 902 | } |
| 903 | const returns = func_type.returns.slice(wasm); |
| 904 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(returns.len))); |
| 905 | for (returns) |ret_ty| { |
| 906 | try appendLeb128(gpa, binary_bytes, @backingInt(ret_ty)); |
| 907 | } |
| 908 | } |
| 909 | replaceVecSectionHeader(binary_bytes, header_offset, .type, @intCast(f.func_types.entries.len)); |
| 910 | section_index += 1; |
| 911 | } |
| 912 | |
| 913 | if (!is_obj) { |
| 914 | // TODO: sort function_imports by ref count descending for optimal LEB encodings |
| 915 | // TODO: sort global_imports by ref count descending for optimal LEB encodings |
| 916 | // TODO: sort output functions by ref count descending for optimal LEB encodings |
| 917 | } |
| 918 | |
| 919 | // Import section |
| 920 | { |
| 921 | var total_imports: usize = 0; |
| 922 | const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); |
| 923 | |
| 924 | for (f.function_imports.values()) |id| { |
| 925 | const module_name = (id.moduleName(wasm).unwrap() orelse wasm.preloaded_strings.env).slice(wasm); |
| 926 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(module_name.len))); |
| 927 | try binary_bytes.appendSlice(gpa, module_name); |
| 928 | |
| 929 | const name = id.importName(wasm).slice(wasm); |
| 930 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 931 | try binary_bytes.appendSlice(gpa, name); |
| 932 | |
| 933 | try binary_bytes.append(gpa, @backingInt(std.wasm.ExternalKind.function)); |
| 934 | const type_index: FuncTypeIndex = .fromTypeIndex(id.functionType(wasm), f); |
| 935 | try appendLeb128(gpa, binary_bytes, @backingInt(type_index)); |
| 936 | } |
| 937 | total_imports += f.function_imports.entries.len; |
| 938 | |
| 939 | for (f.intrinsic_function_imports.keys(), f.intrinsic_function_imports.values()) |name_string, type_index| { |
| 940 | const module_name = wasm.preloaded_strings.env.slice(wasm); |
| 941 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(module_name.len))); |
| 942 | try binary_bytes.appendSlice(gpa, module_name); |
| 943 | |
| 944 | const name = name_string.slice(wasm); |
| 945 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 946 | try binary_bytes.appendSlice(gpa, name); |
| 947 | |
| 948 | try binary_bytes.append(gpa, @backingInt(std.wasm.ExternalKind.function)); |
| 949 | try appendLeb128(gpa, binary_bytes, @backingInt(FuncTypeIndex.fromTypeIndex(type_index, f))); |
| 950 | } |
| 951 | total_imports += f.intrinsic_function_imports.entries.len; |
| 952 | |
| 953 | for (wasm.table_imports.values()) |id| { |
| 954 | const table_import = id.value(wasm); |
| 955 | const module_name = table_import.module_name.slice(wasm); |
| 956 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(module_name.len))); |
| 957 | try binary_bytes.appendSlice(gpa, module_name); |
| 958 | |
| 959 | const name = table_import.name.slice(wasm); |
| 960 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 961 | try binary_bytes.appendSlice(gpa, name); |
| 962 | |
| 963 | try binary_bytes.append(gpa, @backingInt(std.wasm.ExternalKind.table)); |
| 964 | try appendLeb128(gpa, binary_bytes, @backingInt(@as(std.wasm.RefType, table_import.flags.ref_type.to()))); |
| 965 | try emitLimits(gpa, binary_bytes, table_import.limits()); |
| 966 | } |
| 967 | total_imports += wasm.table_imports.entries.len; |
| 968 | |
| 969 | if (import_memory) { |
| 970 | const name = if (is_obj) wasm.preloaded_strings.__linear_memory else wasm.preloaded_strings.memory; |
| 971 | try emitMemoryImport(wasm, binary_bytes, name, &.{ |
| 972 | // TODO the import_memory option needs to specify from which module |
| 973 | .module_name = wasm.object_host_name.unwrap().?, |
| 974 | .limits_min = wasm.memories.limits.min, |
| 975 | .limits_max = wasm.memories.limits.max, |
| 976 | .limits_has_max = wasm.memories.limits.flags.has_max, |
| 977 | .limits_is_shared = wasm.memories.limits.flags.is_shared, |
| 978 | .source_location = .none, |
| 979 | }); |
| 980 | total_imports += 1; |
| 981 | } |
| 982 | |
| 983 | for (f.global_imports.values()) |id| { |
| 984 | const module_name = (id.moduleName(wasm).unwrap() orelse wasm.preloaded_strings.env).slice(wasm); |
| 985 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(module_name.len))); |
| 986 | try binary_bytes.appendSlice(gpa, module_name); |
| 987 | |
| 988 | const name = id.importName(wasm).slice(wasm); |
| 989 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 990 | try binary_bytes.appendSlice(gpa, name); |
| 991 | |
| 992 | try binary_bytes.append(gpa, @backingInt(std.wasm.ExternalKind.global)); |
| 993 | const global_type = id.globalType(wasm); |
| 994 | try appendLeb128(gpa, binary_bytes, @backingInt(@as(std.wasm.Valtype, global_type.valtype))); |
| 995 | try binary_bytes.append(gpa, @intFromBool(global_type.mutable)); |
| 996 | } |
| 997 | total_imports += f.global_imports.entries.len; |
| 998 | |
| 999 | if (total_imports > 0) { |
| 1000 | replaceVecSectionHeader(binary_bytes, header_offset, .import, @intCast(total_imports)); |
| 1001 | section_index += 1; |
| 1002 | } else { |
| 1003 | binary_bytes.shrinkRetainingCapacity(header_offset); |
| 1004 | } |
| 1005 | } |
| 1006 | |
| 1007 | // Function section |
| 1008 | if (wasm.functions.count() != 0) { |
| 1009 | const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); |
| 1010 | for (wasm.functions.keys()) |function| { |
| 1011 | const index: FuncTypeIndex = .fromTypeIndex(function.typeIndex(wasm), f); |
| 1012 | try appendLeb128(gpa, binary_bytes, @backingInt(index)); |
| 1013 | } |
| 1014 | |
| 1015 | replaceVecSectionHeader(binary_bytes, header_offset, .function, @intCast(wasm.functions.count())); |
| 1016 | section_index += 1; |
| 1017 | } |
| 1018 | |
| 1019 | // Table section |
| 1020 | if (wasm.tables.entries.len > 0) { |
| 1021 | const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); |
| 1022 | |
| 1023 | for (wasm.tables.keys()) |table| { |
| 1024 | try appendLeb128(gpa, binary_bytes, @backingInt(@as(std.wasm.RefType, table.refType(wasm)))); |
| 1025 | try emitLimits(gpa, binary_bytes, table.limits(wasm)); |
| 1026 | } |
| 1027 | |
| 1028 | replaceVecSectionHeader(binary_bytes, header_offset, .table, @intCast(wasm.tables.entries.len)); |
| 1029 | section_index += 1; |
| 1030 | } |
| 1031 | |
| 1032 | // Memory section. wasm currently only supports 1 linear memory segment. |
| 1033 | if (!import_memory) { |
| 1034 | const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); |
| 1035 | try emitLimits(gpa, binary_bytes, wasm.memories.limits); |
| 1036 | replaceVecSectionHeader(binary_bytes, header_offset, .memory, 1); |
| 1037 | section_index += 1; |
| 1038 | } |
| 1039 | |
| 1040 | // Global section. |
| 1041 | const globals_len: u32 = @intCast(wasm.globals.entries.len); |
| 1042 | if (globals_len > 0) { |
| 1043 | const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); |
| 1044 | |
| 1045 | for (wasm.globals.keys()) |global_resolution| { |
| 1046 | switch (global_resolution.unpack(wasm)) { |
| 1047 | .unresolved => unreachable, |
| 1048 | .__heap_base => try appendGlobal(gpa, binary_bytes, 0, virtual_addrs.heap_base, is64), |
| 1049 | .__heap_end => try appendGlobal(gpa, binary_bytes, 0, virtual_addrs.heap_end, is64), |
| 1050 | .__stack_pointer => try appendGlobal(gpa, binary_bytes, 1, virtual_addrs.stack_pointer, is64), |
| 1051 | .__tls_align => try appendGlobal(gpa, binary_bytes, 0, @intCast(virtual_addrs.tls_align.toByteUnits().?), is64), |
| 1052 | .__tls_base => try appendGlobal(gpa, binary_bytes, 1, virtual_addrs.tls_base.?, is64), |
| 1053 | .__tls_size => try appendGlobal(gpa, binary_bytes, 0, virtual_addrs.tls_size.?, is64), |
| 1054 | .object_global => |i| { |
| 1055 | const global = i.ptr(wasm); |
| 1056 | try binary_bytes.appendSlice(gpa, &.{ |
| 1057 | @backingInt(@as(std.wasm.Valtype, global.flags.global_type.valtype.to())), |
| 1058 | @intFromBool(global.flags.global_type.mutable), |
| 1059 | }); |
| 1060 | try emitExpr(wasm, binary_bytes, global.expr); |
| 1061 | }, |
| 1062 | .uav_exe => |i| try appendGlobal(gpa, binary_bytes, 0, wasm.uavAddr(i.key(wasm).*), is64), |
| 1063 | .nav_exe => |i| try appendGlobal(gpa, binary_bytes, 0, wasm.navAddr(i.key(wasm).*), is64), |
| 1064 | .uav_obj, .nav_obj => unreachable, |
| 1065 | } |
| 1066 | } |
| 1067 | |
| 1068 | replaceVecSectionHeader(binary_bytes, header_offset, .global, globals_len); |
| 1069 | section_index += 1; |
| 1070 | } |
| 1071 | |
| 1072 | // Export section |
| 1073 | { |
| 1074 | const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); |
| 1075 | var exports_len: usize = 0; |
| 1076 | |
| 1077 | for (wasm.function_exports.keys(), wasm.function_exports.values()) |exp_name, function_index| { |
| 1078 | const name = exp_name.slice(wasm); |
| 1079 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1080 | try binary_bytes.appendSlice(gpa, name); |
| 1081 | try binary_bytes.append(gpa, @backingInt(std.wasm.ExternalKind.function)); |
| 1082 | const func_index = Wasm.OutputFunctionIndex.fromFunctionIndex(wasm, function_index); |
| 1083 | try appendLeb128(gpa, binary_bytes, @backingInt(func_index)); |
| 1084 | } |
| 1085 | exports_len += wasm.function_exports.entries.len; |
| 1086 | |
| 1087 | if (wasm.export_table and f.indirect_function_table.entries.len > 0) { |
| 1088 | const name = "__indirect_function_table"; |
| 1089 | const index: u32 = @intCast(wasm.table_imports.entries.len + |
| 1090 | wasm.tables.getIndex(.__indirect_function_table).?); |
| 1091 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1092 | try binary_bytes.appendSlice(gpa, name); |
| 1093 | try binary_bytes.append(gpa, @backingInt(std.wasm.ExternalKind.table)); |
| 1094 | try appendLeb128(gpa, binary_bytes, index); |
| 1095 | exports_len += 1; |
| 1096 | } |
| 1097 | |
| 1098 | if (export_memory) { |
| 1099 | const name = "memory"; |
| 1100 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1101 | try binary_bytes.appendSlice(gpa, name); |
| 1102 | try binary_bytes.append(gpa, @backingInt(std.wasm.ExternalKind.memory)); |
| 1103 | try appendLeb128(gpa, binary_bytes, @as(u32, 0)); |
| 1104 | exports_len += 1; |
| 1105 | } |
| 1106 | |
| 1107 | for (wasm.global_exports.items) |exp| { |
| 1108 | const name = exp.name.slice(wasm); |
| 1109 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1110 | try binary_bytes.appendSlice(gpa, name); |
| 1111 | try binary_bytes.append(gpa, @backingInt(std.wasm.ExternalKind.global)); |
| 1112 | try appendLeb128(gpa, binary_bytes, @backingInt(exp.global_index)); |
| 1113 | } |
| 1114 | exports_len += wasm.global_exports.items.len; |
| 1115 | |
| 1116 | if (exports_len > 0) { |
| 1117 | replaceVecSectionHeader(binary_bytes, header_offset, .@"export", @intCast(exports_len)); |
| 1118 | section_index += 1; |
| 1119 | } else { |
| 1120 | binary_bytes.shrinkRetainingCapacity(header_offset); |
| 1121 | } |
| 1122 | } |
| 1123 | |
| 1124 | // start section |
| 1125 | if (wasm.functions.getIndex(.__wasm_init_memory)) |func_index| { |
| 1126 | try emitStartSection(gpa, binary_bytes, .fromFunctionIndex(wasm, @fromBackingInt(@intCast(func_index)))); |
| 1127 | section_index += 1; |
| 1128 | } |
| 1129 | |
| 1130 | // element section |
| 1131 | if (!is_obj and f.indirect_function_table.entries.len > 0) { |
| 1132 | const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); |
| 1133 | |
| 1134 | // indirect function table elements |
| 1135 | const table_index: u32 = @intCast( |
| 1136 | wasm.table_imports.getIndex(wasm.preloaded_strings.__indirect_function_table) orelse |
| 1137 | wasm.table_imports.entries.len + wasm.tables.getIndex(.__indirect_function_table).?, |
| 1138 | ); |
| 1139 | // passive with implicit 0-index table or set table index manually |
| 1140 | const flags: u32 = if (table_index == 0) 0x0 else 0x02; |
| 1141 | try appendLeb128(gpa, binary_bytes, flags); |
| 1142 | if (flags == 0x02) { |
| 1143 | try appendLeb128(gpa, binary_bytes, table_index); |
| 1144 | } |
| 1145 | // We start at index 1, so unresolved function pointers are invalid |
| 1146 | { |
| 1147 | var aw: std.Io.Writer.Allocating = .fromArrayList(gpa, binary_bytes); |
| 1148 | defer binary_bytes.* = aw.toArrayList(); |
| 1149 | try emitInit(&aw.writer, .{ .i32_const = 1 }); |
| 1150 | } |
| 1151 | if (flags == 0x02) { |
| 1152 | try appendLeb128(gpa, binary_bytes, @as(u8, 0)); // represents funcref |
| 1153 | } |
| 1154 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(f.indirect_function_table.entries.len))); |
| 1155 | for (f.indirect_function_table.keys()) |func_index| { |
| 1156 | try appendLeb128(gpa, binary_bytes, @backingInt(func_index)); |
| 1157 | } |
| 1158 | |
| 1159 | replaceVecSectionHeader(binary_bytes, header_offset, .element, 1); |
| 1160 | section_index += 1; |
| 1161 | } |
| 1162 | |
| 1163 | // When the shared-memory option is enabled, we *must* emit the 'data count' section. |
| 1164 | if (f.data_segment_groups.items.len > 0) { |
| 1165 | const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); |
| 1166 | replaceVecSectionHeader(binary_bytes, header_offset, .data_count, @intCast(f.data_segment_groups.items.len)); |
| 1167 | section_index += 1; |
| 1168 | } |
| 1169 | |
| 1170 | // Code section. |
| 1171 | if (wasm.functions.count() != 0) { |
| 1172 | const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); |
| 1173 | const section_offset = binary_bytes.items.len - uleb128size(@intCast(wasm.functions.count())); |
| 1174 | |
| 1175 | for (wasm.functions.keys()) |resolution| switch (resolution.unpack(wasm)) { |
| 1176 | .unresolved => unreachable, |
| 1177 | .__wasm_apply_global_tls_relocs => @panic("TODO lower __wasm_apply_global_tls_relocs"), |
| 1178 | .__wasm_call_ctors => { |
| 1179 | const code_start = try reserveSize(gpa, binary_bytes); |
| 1180 | defer replaceSize(binary_bytes, code_start); |
| 1181 | try emitCallCtorsFunction(wasm, binary_bytes); |
| 1182 | }, |
| 1183 | .__wasm_init_memory => { |
| 1184 | const code_start = try reserveSize(gpa, binary_bytes); |
| 1185 | defer replaceSize(binary_bytes, code_start); |
| 1186 | try emitInitMemoryFunction(wasm, binary_bytes); |
| 1187 | }, |
| 1188 | .__wasm_init_tls => { |
| 1189 | const code_start = try reserveSize(gpa, binary_bytes); |
| 1190 | defer replaceSize(binary_bytes, code_start); |
| 1191 | try emitInitTlsFunction(wasm, binary_bytes); |
| 1192 | }, |
| 1193 | .object_function => |i| { |
| 1194 | const ptr = i.ptr(wasm); |
| 1195 | const code = ptr.code.slice(wasm); |
| 1196 | try appendLeb128(gpa, binary_bytes, code.len); |
| 1197 | const code_start = binary_bytes.items.len; |
| 1198 | const output_offset: u32 = @intCast(binary_bytes.items.len - section_offset); |
| 1199 | try binary_bytes.appendSlice(gpa, code); |
| 1200 | if (is_obj) { |
| 1201 | try processRelocs( |
| 1202 | wasm, |
| 1203 | &f.code_relocs, |
| 1204 | output_offset, |
| 1205 | ptr.offset, |
| 1206 | ptr.relocations(wasm), |
| 1207 | ); |
| 1208 | } else { |
| 1209 | applyRelocs(binary_bytes.items[code_start..], ptr.offset, ptr.relocations(wasm), wasm); |
| 1210 | } |
| 1211 | }, |
| 1212 | .zcu_func => |i| { |
| 1213 | const function_offset: u32 = @intCast(binary_bytes.items.len - section_offset); |
| 1214 | const code_start = try reserveSize(gpa, binary_bytes); |
| 1215 | defer replaceSize(binary_bytes, code_start); |
| 1216 | |
| 1217 | log.debug("lowering function code for '{s}'", .{resolution.name(wasm).?}); |
| 1218 | |
| 1219 | const zcu = comp.zcu.?; |
| 1220 | const ip = &zcu.intern_pool; |
| 1221 | const ip_index = i.key(wasm).*; |
| 1222 | switch (ip.indexToKey(ip_index)) { |
| 1223 | .enum_type => { |
| 1224 | try emitTagIndexFunction(wasm, binary_bytes, ip_index); |
| 1225 | }, |
| 1226 | else => { |
| 1227 | if (!zcu_references.?.contains(.wrap(.{ .func = ip_index }))) { |
| 1228 | try binary_bytes.appendSlice(gpa, &.{ |
| 1229 | 0, // no locals |
| 1230 | @backingInt(std.wasm.Opcode.@"unreachable"), |
| 1231 | @backingInt(std.wasm.Opcode.end), |
| 1232 | }); |
| 1233 | continue; |
| 1234 | } |
| 1235 | const func = i.value(wasm).function; |
| 1236 | const mir: Mir = .{ |
| 1237 | .instructions = wasm.mir_instructions.slice().subslice(func.instructions_off, func.instructions_len), |
| 1238 | .extra = wasm.mir_extra.items[func.extra_off..][0..func.extra_len], |
| 1239 | .locals = wasm.mir_locals.items[func.locals_off..][0..func.locals_len], |
| 1240 | .prologue = func.prologue, |
| 1241 | // These fields are unused by `lower`. |
| 1242 | .uavs = undefined, |
| 1243 | .indirect_function_set = undefined, |
| 1244 | .func_tys = undefined, |
| 1245 | .error_name_table_ref_count = undefined, |
| 1246 | }; |
| 1247 | const body_start: u32 = @intCast(binary_bytes.items.len); |
| 1248 | const relocs_start: u32 = @intCast(wasm.zcu_relocations.len); |
| 1249 | defer wasm.zcu_relocations.shrinkRetainingCapacity(relocs_start); |
| 1250 | try mir.lower(wasm, binary_bytes); |
| 1251 | const relocs_len: u32 = @intCast(wasm.zcu_relocations.len - relocs_start); |
| 1252 | if (is_obj) { |
| 1253 | const body_len: u32 = @intCast(binary_bytes.items.len - @as(usize, body_start)); |
| 1254 | const output_offset = function_offset + uleb128size(body_len); |
| 1255 | try processZcuRelocs( |
| 1256 | wasm, |
| 1257 | &f.code_relocs, |
| 1258 | output_offset, |
| 1259 | body_start, |
| 1260 | .{ .off = relocs_start, .len = relocs_len }, |
| 1261 | ); |
| 1262 | } |
| 1263 | }, |
| 1264 | } |
| 1265 | }, |
| 1266 | }; |
| 1267 | |
| 1268 | replaceVecSectionHeader(binary_bytes, header_offset, .code, @intCast(wasm.functions.entries.len)); |
| 1269 | code_section_index = section_index; |
| 1270 | section_index += 1; |
| 1271 | } |
| 1272 | |
| 1273 | if (!is_obj) { |
| 1274 | for (wasm.uav_fixups.items) |uav_fixup| { |
| 1275 | const ds_id: Wasm.DataSegmentId = .pack(wasm, .{ .uav_exe = uav_fixup.uavs_exe_index }); |
| 1276 | const vaddr = f.data_segments.get(ds_id).? + uav_fixup.addend; |
| 1277 | if (!is64) { |
| 1278 | mem.writeInt(u32, wasm.string_bytes.items[uav_fixup.offset..][0..4], vaddr, .little); |
| 1279 | } else { |
| 1280 | mem.writeInt(u64, wasm.string_bytes.items[uav_fixup.offset..][0..8], vaddr, .little); |
| 1281 | } |
| 1282 | } |
| 1283 | for (wasm.nav_fixups.items) |nav_fixup| { |
| 1284 | const vaddr = wasm.navAddr(nav_fixup.nav_index) + nav_fixup.addend; |
| 1285 | if (!is64) { |
| 1286 | mem.writeInt(u32, wasm.string_bytes.items[nav_fixup.offset..][0..4], vaddr, .little); |
| 1287 | } else { |
| 1288 | mem.writeInt(u64, wasm.string_bytes.items[nav_fixup.offset..][0..8], vaddr, .little); |
| 1289 | } |
| 1290 | } |
| 1291 | for (wasm.func_table_fixups.items) |fixup| { |
| 1292 | const table_index: IndirectFunctionTableIndex = .fromIpNav(wasm, fixup.nav_index); |
| 1293 | if (!is64) { |
| 1294 | mem.writeInt(u32, wasm.string_bytes.items[fixup.offset..][0..4], table_index.toAbi(), .little); |
| 1295 | } else { |
| 1296 | mem.writeInt(u64, wasm.string_bytes.items[fixup.offset..][0..8], table_index.toAbi(), .little); |
| 1297 | } |
| 1298 | } |
| 1299 | } |
| 1300 | |
| 1301 | // Data section. |
| 1302 | if (f.data_segment_groups.items.len != 0) { |
| 1303 | const header_offset = try reserveVecSectionHeader(gpa, binary_bytes); |
| 1304 | const section_offset = binary_bytes.items.len - uleb128size(@intCast(f.data_segment_groups.items.len)); |
| 1305 | |
| 1306 | var group_index: u32 = 0; |
| 1307 | var segment_offset: u32 = 0; |
| 1308 | var group_start_addr: u32 = data_vaddr; |
| 1309 | var group_end_addr = f.data_segment_groups.items[group_index].end_addr; |
| 1310 | var first_segment_in_group = true; |
| 1311 | for (segment_ids, segment_vaddrs) |segment_id, segment_vaddr| { |
| 1312 | if (segment_vaddr >= group_end_addr) { |
| 1313 | try binary_bytes.appendNTimes(gpa, 0, group_end_addr - group_start_addr - segment_offset); |
| 1314 | group_index += 1; |
| 1315 | if (group_index >= f.data_segment_groups.items.len) { |
| 1316 | // All remaining segments are zero. |
| 1317 | break; |
| 1318 | } |
| 1319 | group_start_addr = group_end_addr; |
| 1320 | group_end_addr = f.data_segment_groups.items[group_index].end_addr; |
| 1321 | segment_offset = 0; |
| 1322 | first_segment_in_group = true; |
| 1323 | } |
| 1324 | if (first_segment_in_group) { |
| 1325 | first_segment_in_group = false; |
| 1326 | const group_size = group_end_addr - group_start_addr; |
| 1327 | log.debug("emit data section group, {d} bytes", .{group_size}); |
| 1328 | const flags: Object.DataSegmentFlags = if (segment_id.isPassive(wasm)) .passive else .active; |
| 1329 | try appendLeb128(gpa, binary_bytes, @backingInt(flags)); |
| 1330 | // Passive segments are initialized at runtime. |
| 1331 | if (flags != .passive) { |
| 1332 | var aw: std.Io.Writer.Allocating = .fromArrayList(gpa, binary_bytes); |
| 1333 | defer binary_bytes.* = aw.toArrayList(); |
| 1334 | try emitInit(&aw.writer, .{ .i32_const = @as(i32, @bitCast(group_start_addr)) }); |
| 1335 | } |
| 1336 | try appendLeb128(gpa, binary_bytes, group_size); |
| 1337 | } |
| 1338 | if (segment_id.isEmpty(wasm)) { |
| 1339 | if (is_obj) { |
| 1340 | const group_size = group_end_addr - group_start_addr; |
| 1341 | try binary_bytes.appendNTimes(gpa, 0, group_size - segment_offset); |
| 1342 | segment_offset = group_size; |
| 1343 | } |
| 1344 | continue; |
| 1345 | } |
| 1346 | |
| 1347 | // Padding for alignment. |
| 1348 | const needed_offset = segment_vaddr - group_start_addr; |
| 1349 | try binary_bytes.appendNTimes(gpa, 0, needed_offset - segment_offset); |
| 1350 | segment_offset = needed_offset; |
| 1351 | |
| 1352 | const code_start = binary_bytes.items.len; |
| 1353 | const output_offset: u32 = @intCast(binary_bytes.items.len - section_offset); |
| 1354 | append: { |
| 1355 | const code = switch (segment_id.unpack(wasm)) { |
| 1356 | .__zig_error_names => { |
| 1357 | try binary_bytes.appendSlice(gpa, wasm.error_name_bytes.items); |
| 1358 | break :append; |
| 1359 | }, |
| 1360 | .__zig_error_name_table => { |
| 1361 | if (is_obj) { |
| 1362 | try emitRelocatableNameTable( |
| 1363 | wasm, |
| 1364 | binary_bytes, |
| 1365 | &f.data_relocs, |
| 1366 | output_offset, |
| 1367 | wasm.error_name_offs.items, |
| 1368 | wasm.error_name_bytes.items, |
| 1369 | .__zig_error_names, |
| 1370 | ); |
| 1371 | } else { |
| 1372 | const base = f.data_segments.get(.__zig_error_names).?; |
| 1373 | try emitTagNameTable(wasm, binary_bytes, wasm.error_name_offs.items, wasm.error_name_bytes.items, base, is64); |
| 1374 | } |
| 1375 | break :append; |
| 1376 | }, |
| 1377 | .__zig_tag_names => { |
| 1378 | try binary_bytes.appendSlice(gpa, wasm.tag_name_bytes.items); |
| 1379 | break :append; |
| 1380 | }, |
| 1381 | .__zig_tag_name_table => { |
| 1382 | if (is_obj) { |
| 1383 | try emitRelocatableNameTable( |
| 1384 | wasm, |
| 1385 | binary_bytes, |
| 1386 | &f.data_relocs, |
| 1387 | output_offset, |
| 1388 | wasm.tag_name_offs.items, |
| 1389 | wasm.tag_name_bytes.items, |
| 1390 | .__zig_tag_names, |
| 1391 | ); |
| 1392 | } else { |
| 1393 | const base = f.data_segments.get(.__zig_tag_names).?; |
| 1394 | try emitTagNameTable(wasm, binary_bytes, wasm.tag_name_offs.items, wasm.tag_name_bytes.items, base, is64); |
| 1395 | } |
| 1396 | break :append; |
| 1397 | }, |
| 1398 | .object => |i| { |
| 1399 | const ptr = i.ptr(wasm); |
| 1400 | try binary_bytes.appendSlice(gpa, ptr.payload.slice(wasm)); |
| 1401 | if (is_obj) { |
| 1402 | try processRelocs( |
| 1403 | wasm, |
| 1404 | &f.data_relocs, |
| 1405 | output_offset, |
| 1406 | ptr.offset, |
| 1407 | ptr.relocations(wasm), |
| 1408 | ); |
| 1409 | } else { |
| 1410 | applyRelocs(binary_bytes.items[code_start..], ptr.offset, ptr.relocations(wasm), wasm); |
| 1411 | } |
| 1412 | break :append; |
| 1413 | }, |
| 1414 | inline .uav_obj, .nav_obj => |i| { |
| 1415 | const zcu_data = i.value(wasm); |
| 1416 | try binary_bytes.appendSlice(gpa, zcu_data.code.slice(wasm)); |
| 1417 | try processZcuRelocs( |
| 1418 | wasm, |
| 1419 | &f.data_relocs, |
| 1420 | output_offset, |
| 1421 | zcu_data.code.off.unwrap().?, |
| 1422 | zcu_data.relocs, |
| 1423 | ); |
| 1424 | break :append; |
| 1425 | }, |
| 1426 | inline .uav_exe, .nav_exe => |i| i.value(wasm).code, |
| 1427 | }; |
| 1428 | try binary_bytes.appendSlice(gpa, code.slice(wasm)); |
| 1429 | } |
| 1430 | segment_offset += @intCast(binary_bytes.items.len - code_start); |
| 1431 | } |
| 1432 | |
| 1433 | replaceVecSectionHeader(binary_bytes, header_offset, .data, @intCast(f.data_segment_groups.items.len)); |
| 1434 | data_section_index = section_index; |
| 1435 | section_index += 1; |
| 1436 | } |
| 1437 | |
| 1438 | if (is_obj) { |
| 1439 | var symbol_table_offsets: SymbolTableOffsets = undefined; |
| 1440 | { |
| 1441 | const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes); |
| 1442 | defer writeCustomSectionHeader(binary_bytes, header_offset); |
| 1443 | |
| 1444 | const linking_name = "linking"; |
| 1445 | try appendLeb128(gpa, binary_bytes, @as(u32, linking_name.len)); |
| 1446 | try binary_bytes.appendSlice(gpa, linking_name); |
| 1447 | |
| 1448 | try appendLeb128(gpa, binary_bytes, @as(u32, 2)); |
| 1449 | |
| 1450 | // WASM_SEGMENT_INFO |
| 1451 | { |
| 1452 | const sub_offset = try reserveCustomSectionHeader(gpa, binary_bytes); |
| 1453 | defer replaceHeader(binary_bytes, sub_offset, @backingInt(Object.SubsectionType.segment_info)); |
| 1454 | |
| 1455 | const total_data_segments: u32 = @intCast(f.data_segment_groups.items.len); |
| 1456 | try appendLeb128(gpa, binary_bytes, total_data_segments); |
| 1457 | |
| 1458 | for (f.data_segment_groups.items) |group| { |
| 1459 | const segment = group.first_segment; |
| 1460 | const name, _ = splitSegmentName(segment.name(wasm)); |
| 1461 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1462 | try binary_bytes.appendSlice(gpa, name); |
| 1463 | |
| 1464 | try appendLeb128(gpa, binary_bytes, @as(u32, segment.alignment(wasm).toLog2Units())); |
| 1465 | |
| 1466 | var flags: u32 = 0; |
| 1467 | if (segment.isStrings(wasm)) flags |= 1; |
| 1468 | if (segment.isTls(wasm)) flags |= 2; |
| 1469 | if (segment.isRetain(wasm)) flags |= 4; |
| 1470 | try appendLeb128(gpa, binary_bytes, flags); |
| 1471 | } |
| 1472 | } |
| 1473 | |
| 1474 | // WASM_SYMBOL_TABLE |
| 1475 | { |
| 1476 | const sub_offset = try reserveCustomSectionHeader(gpa, binary_bytes); |
| 1477 | defer replaceHeader(binary_bytes, sub_offset, @backingInt(Object.SubsectionType.symbol_table)); |
| 1478 | |
| 1479 | const total_symbols: u32 = @intCast( |
| 1480 | f.function_imports.entries.len + f.intrinsic_function_imports.entries.len + |
| 1481 | wasm.functions.entries.len + |
| 1482 | f.function_export_symbols.entries.len + |
| 1483 | f.data_imports.entries.len + wasm.datas.entries.len + f.data_exports.entries.len + |
| 1484 | f.global_imports.entries.len + wasm.globals.entries.len + |
| 1485 | wasm.table_imports.entries.len + wasm.tables.entries.len, |
| 1486 | ); |
| 1487 | try appendLeb128(gpa, binary_bytes, total_symbols); |
| 1488 | var symbol_count: u32 = 0; |
| 1489 | |
| 1490 | // SYMTAB_FUNCTION |
| 1491 | { |
| 1492 | symbol_table_offsets.function = symbol_count; |
| 1493 | for (f.function_imports.keys(), f.function_imports.values(), 0..) |symbol_name, i, function_index| { |
| 1494 | try binary_bytes.append(gpa, @backingInt(Object.Symbol.Tag.function)); |
| 1495 | const flags = i.flags(wasm); |
| 1496 | assert(flags.undefined); |
| 1497 | try appendLeb128(gpa, binary_bytes, flags.toAbiInteger()); |
| 1498 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(function_index))); |
| 1499 | if (flags.explicit_name) { |
| 1500 | const name = symbol_name.slice(wasm); |
| 1501 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1502 | try binary_bytes.appendSlice(gpa, name); |
| 1503 | } |
| 1504 | symbol_count += 1; |
| 1505 | } |
| 1506 | const intrinsic_flags: Wasm.SymbolFlags = .{ .undefined = true }; |
| 1507 | for (f.intrinsic_function_imports.keys(), f.function_imports.entries.len..) |_, function_index| { |
| 1508 | try binary_bytes.append(gpa, @backingInt(Object.Symbol.Tag.function)); |
| 1509 | try appendLeb128(gpa, binary_bytes, intrinsic_flags.toAbiInteger()); |
| 1510 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(function_index))); |
| 1511 | symbol_count += 1; |
| 1512 | } |
| 1513 | for ( |
| 1514 | wasm.functions.keys(), |
| 1515 | f.function_imports.entries.len + f.intrinsic_function_imports.entries.len.., |
| 1516 | ) |resolution, function_index| { |
| 1517 | const name = resolution.name(wasm).?; |
| 1518 | const flags = resolution.flags(wasm); |
| 1519 | try binary_bytes.append(gpa, @backingInt(Object.Symbol.Tag.function)); |
| 1520 | assert(!flags.undefined); |
| 1521 | try appendLeb128(gpa, binary_bytes, flags.toAbiInteger()); |
| 1522 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(function_index))); |
| 1523 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1524 | try binary_bytes.appendSlice(gpa, name); |
| 1525 | symbol_count += 1; |
| 1526 | } |
| 1527 | for ( |
| 1528 | f.function_export_symbols.keys(), |
| 1529 | f.function_export_symbols.values(), |
| 1530 | ) |name_string, symbol| { |
| 1531 | const name = name_string.slice(wasm); |
| 1532 | const function_index: Wasm.OutputFunctionIndex = .fromFunctionIndex( |
| 1533 | wasm, |
| 1534 | symbol.function_index, |
| 1535 | ); |
| 1536 | try binary_bytes.append(gpa, @backingInt(Object.Symbol.Tag.function)); |
| 1537 | try appendLeb128(gpa, binary_bytes, symbol.flags.toAbiInteger()); |
| 1538 | try appendLeb128(gpa, binary_bytes, @backingInt(function_index)); |
| 1539 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1540 | try binary_bytes.appendSlice(gpa, name); |
| 1541 | symbol_count += 1; |
| 1542 | } |
| 1543 | } |
| 1544 | |
| 1545 | // SYMTAB_DATA |
| 1546 | { |
| 1547 | symbol_table_offsets.data = symbol_count; |
| 1548 | for (f.data_imports.keys(), f.data_imports.values()) |name_string, data_index| { |
| 1549 | const name = name_string.slice(wasm); |
| 1550 | try binary_bytes.append(gpa, @backingInt(Object.Symbol.Tag.data)); |
| 1551 | const flags = data_index.flags(wasm); |
| 1552 | assert(flags.undefined); |
| 1553 | try appendLeb128(gpa, binary_bytes, flags.toAbiInteger()); |
| 1554 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1555 | try binary_bytes.appendSlice(gpa, name); |
| 1556 | symbol_count += 1; |
| 1557 | } |
| 1558 | for (wasm.datas.keys()) |resolution| { |
| 1559 | var buf: [32]u8 = undefined; |
| 1560 | const name = resolution.name(wasm, &buf); |
| 1561 | try binary_bytes.append(gpa, @backingInt(Object.Symbol.Tag.data)); |
| 1562 | const flags = resolution.flags(wasm); |
| 1563 | assert(!flags.undefined); |
| 1564 | try appendLeb128(gpa, binary_bytes, flags.toAbiInteger()); |
| 1565 | |
| 1566 | const data_loc = resolution.dataLoc(wasm); |
| 1567 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1568 | try binary_bytes.appendSlice(gpa, name); |
| 1569 | |
| 1570 | const segment_index = f.data_segments.getIndex(data_loc.segment).?; |
| 1571 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(segment_index))); |
| 1572 | try appendLeb128(gpa, binary_bytes, data_loc.offset); |
| 1573 | try appendLeb128(gpa, binary_bytes, resolution.size(wasm)); |
| 1574 | symbol_count += 1; |
| 1575 | } |
| 1576 | for (f.data_exports.keys(), f.data_exports.values()) |name_string, symbol| { |
| 1577 | const name = name_string.slice(wasm); |
| 1578 | try binary_bytes.append(gpa, @backingInt(Object.Symbol.Tag.data)); |
| 1579 | try appendLeb128(gpa, binary_bytes, symbol.flags.toAbiInteger()); |
| 1580 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1581 | try binary_bytes.appendSlice(gpa, name); |
| 1582 | |
| 1583 | const data_loc = symbol.resolution.dataLoc(wasm); |
| 1584 | const segment_index = f.data_segments.getIndex(data_loc.segment).?; |
| 1585 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(segment_index))); |
| 1586 | try appendLeb128(gpa, binary_bytes, data_loc.offset); |
| 1587 | try appendLeb128(gpa, binary_bytes, symbol.resolution.size(wasm)); |
| 1588 | symbol_count += 1; |
| 1589 | } |
| 1590 | } |
| 1591 | |
| 1592 | // SYMTAB_GLOBAL |
| 1593 | { |
| 1594 | symbol_table_offsets.global = symbol_count; |
| 1595 | for (f.global_imports.values(), 0..) |i, global_index| { |
| 1596 | try binary_bytes.append(gpa, @backingInt(Object.Symbol.Tag.global)); |
| 1597 | const flags = i.flags(wasm); |
| 1598 | assert(flags.undefined); |
| 1599 | try appendLeb128(gpa, binary_bytes, flags.toAbiInteger()); |
| 1600 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(global_index))); |
| 1601 | if (flags.explicit_name) { |
| 1602 | unreachable; // never set |
| 1603 | } |
| 1604 | symbol_count += 1; |
| 1605 | } |
| 1606 | for (wasm.globals.keys(), f.global_imports.entries.len..) |resolution, global_index| { |
| 1607 | var buf: [32]u8 = undefined; |
| 1608 | const name = resolution.name(wasm, &buf).?; |
| 1609 | try binary_bytes.append(gpa, @backingInt(Object.Symbol.Tag.global)); |
| 1610 | const flags = resolution.flags(wasm); |
| 1611 | assert(!flags.undefined); |
| 1612 | try appendLeb128(gpa, binary_bytes, flags.toAbiInteger()); |
| 1613 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(global_index))); |
| 1614 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1615 | try binary_bytes.appendSlice(gpa, name); |
| 1616 | symbol_count += 1; |
| 1617 | } |
| 1618 | } |
| 1619 | |
| 1620 | // SYMTAB_EVENT |
| 1621 | { |
| 1622 | // TODO not parsed yet |
| 1623 | } |
| 1624 | |
| 1625 | // SYMTAB_SECTION |
| 1626 | { |
| 1627 | // TODO not parsed correctly yet |
| 1628 | } |
| 1629 | |
| 1630 | // SYMTAB_TABLE |
| 1631 | { |
| 1632 | symbol_table_offsets.table = symbol_count; |
| 1633 | for (wasm.table_imports.values(), 0..) |i, table_index| { |
| 1634 | try binary_bytes.append(gpa, @backingInt(Object.Symbol.Tag.table)); |
| 1635 | const flags = i.value(wasm).flags; |
| 1636 | assert(flags.undefined); |
| 1637 | try appendLeb128(gpa, binary_bytes, flags.toAbiInteger()); |
| 1638 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(table_index))); |
| 1639 | if (flags.explicit_name) { |
| 1640 | unreachable; // never set |
| 1641 | } |
| 1642 | symbol_count += 1; |
| 1643 | } |
| 1644 | for (wasm.tables.keys(), wasm.table_imports.entries.len..) |resolution, table_index| { |
| 1645 | const name = resolution.name(wasm).?; |
| 1646 | try binary_bytes.append(gpa, @backingInt(Object.Symbol.Tag.table)); |
| 1647 | const flags = resolution.flags(wasm); |
| 1648 | assert(!flags.undefined); |
| 1649 | try appendLeb128(gpa, binary_bytes, flags.toAbiInteger()); |
| 1650 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(table_index))); |
| 1651 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1652 | try binary_bytes.appendSlice(gpa, name); |
| 1653 | symbol_count += 1; |
| 1654 | } |
| 1655 | } |
| 1656 | assert(symbol_count == total_symbols); |
| 1657 | } |
| 1658 | |
| 1659 | // WASM_INIT_FUNCS |
| 1660 | { |
| 1661 | const sub_offset = try reserveCustomSectionHeader(gpa, binary_bytes); |
| 1662 | defer replaceHeader(binary_bytes, sub_offset, @backingInt(Object.SubsectionType.init_funcs)); |
| 1663 | |
| 1664 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(f.sorted_init_funcs.items.len))); |
| 1665 | |
| 1666 | for (f.sorted_init_funcs.items) |init_func| { |
| 1667 | try appendLeb128(gpa, binary_bytes, init_func.priority); |
| 1668 | const out_index: Wasm.OutputFunctionIndex = .fromObjectFunction(wasm, init_func.function_index); |
| 1669 | const symbol_index: u32 = symbol_table_offsets.function + @backingInt(out_index); |
| 1670 | try appendLeb128(gpa, binary_bytes, symbol_index); |
| 1671 | } |
| 1672 | } |
| 1673 | |
| 1674 | // WASM_COMDAT_INFO |
| 1675 | { |
| 1676 | // TODO |
| 1677 | } |
| 1678 | } |
| 1679 | |
| 1680 | if (f.code_relocs.items.len != 0) try emitRelocSection( |
| 1681 | wasm, |
| 1682 | binary_bytes, |
| 1683 | code_section_index.?, |
| 1684 | "reloc.CODE", |
| 1685 | f.code_relocs.items, |
| 1686 | symbol_table_offsets, |
| 1687 | ); |
| 1688 | if (f.data_relocs.items.len != 0) try emitRelocSection( |
| 1689 | wasm, |
| 1690 | binary_bytes, |
| 1691 | data_section_index.?, |
| 1692 | "reloc.DATA", |
| 1693 | f.data_relocs.items, |
| 1694 | symbol_table_offsets, |
| 1695 | ); |
| 1696 | } else if (comp.config.debug_format != .strip) { |
| 1697 | try emitNameSection(wasm, f.data_segment_groups.items, binary_bytes); |
| 1698 | } |
| 1699 | |
| 1700 | if (comp.config.debug_format != .strip) { |
| 1701 | // The build id must be computed on the main sections only, |
| 1702 | // so we have to do it now, before the debug sections. |
| 1703 | switch (wasm.base.build_id) { |
| 1704 | .none => {}, |
| 1705 | .fast => { |
| 1706 | var id: [16]u8 = undefined; |
| 1707 | std.crypto.hash.sha3.TurboShake128(null).hash(binary_bytes.items, &id, .{}); |
| 1708 | var uuid: [36]u8 = undefined; |
| 1709 | _ = try std.mem.print(&uuid, "{x}-{x}-{x}-{x}-{x}", .{ |
| 1710 | id[0..4], id[4..6], id[6..8], id[8..10], id[10..], |
| 1711 | }); |
| 1712 | try emitBuildIdSection(gpa, binary_bytes, &uuid); |
| 1713 | }, |
| 1714 | .hexstring => |hs| { |
| 1715 | var buffer: [32 * 2]u8 = undefined; |
| 1716 | const str = std.mem.print(&buffer, "{x}", .{hs.toSlice()}) catch unreachable; |
| 1717 | try emitBuildIdSection(gpa, binary_bytes, str); |
| 1718 | }, |
| 1719 | else => |mode| { |
| 1720 | var err = try diags.addErrorWithNotes(0); |
| 1721 | try err.addMsg("build-id '{s}' is not supported for WebAssembly", .{@tagName(mode)}); |
| 1722 | }, |
| 1723 | } |
| 1724 | |
| 1725 | var debug_bytes = std.array_list.Managed(u8).init(gpa); |
| 1726 | defer debug_bytes.deinit(); |
| 1727 | |
| 1728 | try emitProducerSection(gpa, binary_bytes); |
| 1729 | try emitFeaturesSection(gpa, binary_bytes, target); |
| 1730 | } |
| 1731 | |
| 1732 | // Finally, write the entire binary into the file. |
| 1733 | var file_writer = wasm.base.file.?.writer(io, &.{}); |
| 1734 | file_writer.interface.writeAll(binary_bytes.items) catch |err| switch (err) { |
| 1735 | error.WriteFailed => return file_writer.err.?, |
| 1736 | }; |
| 1737 | file_writer.end() catch |err| switch (err) { |
| 1738 | error.WriteFailed => return file_writer.err.?, |
| 1739 | else => |e| return e, |
| 1740 | }; |
| 1741 | } |
| 1742 | |
| 1743 | const VirtualAddrs = struct { |
| 1744 | global_base: u32, |
| 1745 | stack_pointer: u32, |
| 1746 | heap_base: u32, |
| 1747 | heap_end: u32, |
| 1748 | wasm_first_page_end: u32, |
| 1749 | tls_base: ?u32, |
| 1750 | tls_align: Alignment, |
| 1751 | tls_size: ?u32, |
| 1752 | init_memory_flag: ?u32, |
| 1753 | }; |
| 1754 | |
| 1755 | fn emitNameSection( |
| 1756 | wasm: *Wasm, |
| 1757 | data_segment_groups: []const DataSegmentGroup, |
| 1758 | binary_bytes: *ArrayList(u8), |
| 1759 | ) !void { |
| 1760 | const f = &wasm.flush_buffer; |
| 1761 | const comp = wasm.base.comp; |
| 1762 | const gpa = comp.gpa; |
| 1763 | |
| 1764 | const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes); |
| 1765 | defer writeCustomSectionHeader(binary_bytes, header_offset); |
| 1766 | |
| 1767 | const name_name = "name"; |
| 1768 | try appendLeb128(gpa, binary_bytes, @as(u32, name_name.len)); |
| 1769 | try binary_bytes.appendSlice(gpa, name_name); |
| 1770 | |
| 1771 | { |
| 1772 | const sub_offset = try reserveCustomSectionHeader(gpa, binary_bytes); |
| 1773 | defer replaceHeader(binary_bytes, sub_offset, @backingInt(std.wasm.NameSubsection.function)); |
| 1774 | |
| 1775 | const total_functions: u32 = @intCast( |
| 1776 | f.function_imports.entries.len + f.intrinsic_function_imports.entries.len + |
| 1777 | wasm.functions.entries.len, |
| 1778 | ); |
| 1779 | try appendLeb128(gpa, binary_bytes, total_functions); |
| 1780 | |
| 1781 | for (f.function_imports.keys(), 0..) |name_index, function_index| { |
| 1782 | const name = name_index.slice(wasm); |
| 1783 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(function_index))); |
| 1784 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1785 | try binary_bytes.appendSlice(gpa, name); |
| 1786 | } |
| 1787 | for (f.intrinsic_function_imports.keys(), f.function_imports.entries.len..) |name_index, function_index| { |
| 1788 | const name = name_index.slice(wasm); |
| 1789 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(function_index))); |
| 1790 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1791 | try binary_bytes.appendSlice(gpa, name); |
| 1792 | } |
| 1793 | for ( |
| 1794 | wasm.functions.keys(), |
| 1795 | f.function_imports.entries.len + f.intrinsic_function_imports.entries.len.., |
| 1796 | ) |resolution, function_index| { |
| 1797 | const name = resolution.name(wasm).?; |
| 1798 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(function_index))); |
| 1799 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1800 | try binary_bytes.appendSlice(gpa, name); |
| 1801 | } |
| 1802 | } |
| 1803 | |
| 1804 | { |
| 1805 | const sub_offset = try reserveCustomSectionHeader(gpa, binary_bytes); |
| 1806 | defer replaceHeader(binary_bytes, sub_offset, @backingInt(std.wasm.NameSubsection.global)); |
| 1807 | |
| 1808 | const total_globals: u32 = @intCast(f.global_imports.entries.len + wasm.globals.entries.len); |
| 1809 | try appendLeb128(gpa, binary_bytes, total_globals); |
| 1810 | |
| 1811 | for (f.global_imports.keys(), 0..) |name_index, global_index| { |
| 1812 | const name = name_index.slice(wasm); |
| 1813 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(global_index))); |
| 1814 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1815 | try binary_bytes.appendSlice(gpa, name); |
| 1816 | } |
| 1817 | for (wasm.globals.keys(), f.global_imports.entries.len..) |resolution, global_index| { |
| 1818 | var buf: [32]u8 = undefined; |
| 1819 | const name = resolution.name(wasm, &buf).?; |
| 1820 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(global_index))); |
| 1821 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1822 | try binary_bytes.appendSlice(gpa, name); |
| 1823 | } |
| 1824 | } |
| 1825 | |
| 1826 | { |
| 1827 | const sub_offset = try reserveCustomSectionHeader(gpa, binary_bytes); |
| 1828 | defer replaceHeader(binary_bytes, sub_offset, @backingInt(std.wasm.NameSubsection.data_segment)); |
| 1829 | |
| 1830 | const total_data_segments: u32 = @intCast(data_segment_groups.len); |
| 1831 | try appendLeb128(gpa, binary_bytes, total_data_segments); |
| 1832 | |
| 1833 | for (data_segment_groups, 0..) |group, i| { |
| 1834 | const name, _ = splitSegmentName(group.first_segment.name(wasm)); |
| 1835 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(i))); |
| 1836 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1837 | try binary_bytes.appendSlice(gpa, name); |
| 1838 | } |
| 1839 | } |
| 1840 | } |
| 1841 | |
| 1842 | fn emitFeaturesSection( |
| 1843 | gpa: Allocator, |
| 1844 | binary_bytes: *ArrayList(u8), |
| 1845 | target: *const std.Target, |
| 1846 | ) Allocator.Error!void { |
| 1847 | const feature_count = target.cpu.features.count(); |
| 1848 | if (feature_count == 0) return; |
| 1849 | |
| 1850 | const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes); |
| 1851 | defer writeCustomSectionHeader(binary_bytes, header_offset); |
| 1852 | |
| 1853 | const target_features = "target_features"; |
| 1854 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(target_features.len))); |
| 1855 | try binary_bytes.appendSlice(gpa, target_features); |
| 1856 | |
| 1857 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(feature_count))); |
| 1858 | |
| 1859 | var safety_count = feature_count; |
| 1860 | for (target.cpu.arch.allFeaturesList(), 0..) |*feature, i| { |
| 1861 | if (!target.cpu.has(.wasm, @as(std.Target.wasm.Feature, @fromBackingInt(@intCast(i))))) continue; |
| 1862 | safety_count -= 1; |
| 1863 | |
| 1864 | try appendLeb128(gpa, binary_bytes, @as(u32, '+')); |
| 1865 | // Depends on llvm_name for the hyphenated version that matches wasm tooling conventions. |
| 1866 | const name = feature.llvm_name.?; |
| 1867 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 1868 | try binary_bytes.appendSlice(gpa, name); |
| 1869 | } |
| 1870 | assert(safety_count == 0); |
| 1871 | } |
| 1872 | |
| 1873 | fn emitBuildIdSection(gpa: Allocator, binary_bytes: *ArrayList(u8), build_id: []const u8) !void { |
| 1874 | const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes); |
| 1875 | defer writeCustomSectionHeader(binary_bytes, header_offset); |
| 1876 | |
| 1877 | const hdr_build_id = "build_id"; |
| 1878 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(hdr_build_id.len))); |
| 1879 | try binary_bytes.appendSlice(gpa, hdr_build_id); |
| 1880 | |
| 1881 | try appendLeb128(gpa, binary_bytes, @as(u32, 1)); |
| 1882 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(build_id.len))); |
| 1883 | try binary_bytes.appendSlice(gpa, build_id); |
| 1884 | } |
| 1885 | |
| 1886 | fn emitProducerSection(gpa: Allocator, binary_bytes: *ArrayList(u8)) !void { |
| 1887 | const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes); |
| 1888 | defer writeCustomSectionHeader(binary_bytes, header_offset); |
| 1889 | |
| 1890 | const producers = "producers"; |
| 1891 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(producers.len))); |
| 1892 | try binary_bytes.appendSlice(gpa, producers); |
| 1893 | |
| 1894 | try appendLeb128(gpa, binary_bytes, @as(u32, 2)); // 2 fields: Language + processed-by |
| 1895 | |
| 1896 | // language field |
| 1897 | { |
| 1898 | const language = "language"; |
| 1899 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(language.len))); |
| 1900 | try binary_bytes.appendSlice(gpa, language); |
| 1901 | |
| 1902 | // field_value_count (TODO: Parse object files for producer sections to detect their language) |
| 1903 | try appendLeb128(gpa, binary_bytes, @as(u32, 1)); |
| 1904 | |
| 1905 | // versioned name |
| 1906 | { |
| 1907 | try appendLeb128(gpa, binary_bytes, @as(u32, 3)); // len of "Zig" |
| 1908 | try binary_bytes.appendSlice(gpa, "Zig"); |
| 1909 | |
| 1910 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(build_options.version.len))); |
| 1911 | try binary_bytes.appendSlice(gpa, build_options.version); |
| 1912 | } |
| 1913 | } |
| 1914 | |
| 1915 | // processed-by field |
| 1916 | { |
| 1917 | const processed_by = "processed-by"; |
| 1918 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(processed_by.len))); |
| 1919 | try binary_bytes.appendSlice(gpa, processed_by); |
| 1920 | |
| 1921 | // field_value_count (TODO: Parse object files for producer sections to detect other used tools) |
| 1922 | try appendLeb128(gpa, binary_bytes, @as(u32, 1)); |
| 1923 | |
| 1924 | // versioned name |
| 1925 | { |
| 1926 | try appendLeb128(gpa, binary_bytes, @as(u32, 3)); // len of "Zig" |
| 1927 | try binary_bytes.appendSlice(gpa, "Zig"); |
| 1928 | |
| 1929 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(build_options.version.len))); |
| 1930 | try binary_bytes.appendSlice(gpa, build_options.version); |
| 1931 | } |
| 1932 | } |
| 1933 | } |
| 1934 | |
| 1935 | fn splitSegmentName(name: []const u8) struct { []const u8, []const u8 } { |
| 1936 | const start = @intFromBool(name.len >= 1 and name[0] == '.'); |
| 1937 | const pivot = mem.findScalarPos(u8, name, start, '.') orelse name.len; |
| 1938 | return .{ name[0..pivot], name[pivot..] }; |
| 1939 | } |
| 1940 | |
| 1941 | test splitSegmentName { |
| 1942 | { |
| 1943 | const a, const b = splitSegmentName(".data"); |
| 1944 | try std.testing.expectEqualStrings(".data", a); |
| 1945 | try std.testing.expectEqualStrings("", b); |
| 1946 | } |
| 1947 | } |
| 1948 | |
| 1949 | fn wantSegmentMerge( |
| 1950 | wasm: *const Wasm, |
| 1951 | a_id: Wasm.DataSegmentId, |
| 1952 | b_id: Wasm.DataSegmentId, |
| 1953 | b_category: Wasm.DataSegmentId.Category, |
| 1954 | ) bool { |
| 1955 | const a_category = a_id.category(wasm); |
| 1956 | if (a_category != b_category) return false; |
| 1957 | if (a_category == .tls or b_category == .tls) return false; |
| 1958 | if (a_id.isPassive(wasm) != b_id.isPassive(wasm)) return false; |
| 1959 | if (b_category == .zero) return true; |
| 1960 | const a_name = a_id.name(wasm); |
| 1961 | const b_name = b_id.name(wasm); |
| 1962 | const a_prefix, _ = splitSegmentName(a_name); |
| 1963 | const b_prefix, _ = splitSegmentName(b_name); |
| 1964 | return mem.eql(u8, a_prefix, b_prefix); |
| 1965 | } |
| 1966 | |
| 1967 | /// section id + fixed leb contents size + fixed leb vector length |
| 1968 | const section_header_reserve_size = 1 + 5 + 5; |
| 1969 | const section_header_size = 5 + 1; |
| 1970 | |
| 1971 | fn reserveVecSectionHeader(gpa: Allocator, bytes: *ArrayList(u8)) Allocator.Error!u32 { |
| 1972 | try bytes.appendNTimes(gpa, 0, section_header_reserve_size); |
| 1973 | return @intCast(bytes.items.len - section_header_reserve_size); |
| 1974 | } |
| 1975 | |
| 1976 | fn replaceVecSectionHeader( |
| 1977 | bytes: *ArrayList(u8), |
| 1978 | offset: u32, |
| 1979 | section: std.wasm.Section, |
| 1980 | n_items: u32, |
| 1981 | ) void { |
| 1982 | const size: u32 = @intCast(bytes.items.len - offset - section_header_reserve_size + uleb128size(n_items)); |
| 1983 | var buf: [section_header_reserve_size]u8 = undefined; |
| 1984 | var w: std.Io.Writer = .fixed(&buf); |
| 1985 | w.writeByte(@backingInt(section)) catch unreachable; |
| 1986 | w.writeUleb128(size) catch unreachable; |
| 1987 | w.writeUleb128(n_items) catch unreachable; |
| 1988 | bytes.replaceRangeAssumeCapacity(offset, section_header_reserve_size, w.buffered()); |
| 1989 | } |
| 1990 | |
| 1991 | fn reserveCustomSectionHeader(gpa: Allocator, bytes: *ArrayList(u8)) Allocator.Error!u32 { |
| 1992 | try bytes.appendNTimes(gpa, 0, section_header_size); |
| 1993 | return @intCast(bytes.items.len - section_header_size); |
| 1994 | } |
| 1995 | |
| 1996 | fn writeCustomSectionHeader(bytes: *ArrayList(u8), offset: u32) void { |
| 1997 | return replaceHeader(bytes, offset, 0); // 0 = 'custom' section |
| 1998 | } |
| 1999 | |
| 2000 | fn replaceHeader(bytes: *ArrayList(u8), offset: u32, tag: u8) void { |
| 2001 | const size: u32 = @intCast(bytes.items.len - offset - section_header_size); |
| 2002 | var buf: [section_header_size]u8 = undefined; |
| 2003 | var w: std.Io.Writer = .fixed(&buf); |
| 2004 | w.writeByte(tag) catch unreachable; |
| 2005 | w.writeUleb128(size) catch unreachable; |
| 2006 | bytes.replaceRangeAssumeCapacity(offset, section_header_size, w.buffered()); |
| 2007 | } |
| 2008 | |
| 2009 | const max_size_encoding = 5; |
| 2010 | |
| 2011 | fn reserveSize(gpa: Allocator, bytes: *ArrayList(u8)) Allocator.Error!u32 { |
| 2012 | try bytes.appendNTimes(gpa, 0, max_size_encoding); |
| 2013 | return @intCast(bytes.items.len - max_size_encoding); |
| 2014 | } |
| 2015 | |
| 2016 | fn replaceSize(bytes: *ArrayList(u8), offset: u32) void { |
| 2017 | const size: u32 = @intCast(bytes.items.len - offset - max_size_encoding); |
| 2018 | var buf: [max_size_encoding]u8 = undefined; |
| 2019 | var w: std.Io.Writer = .fixed(&buf); |
| 2020 | w.writeUleb128(size) catch unreachable; |
| 2021 | bytes.replaceRangeAssumeCapacity(offset, max_size_encoding, w.buffered()); |
| 2022 | } |
| 2023 | |
| 2024 | fn emitLimits( |
| 2025 | gpa: Allocator, |
| 2026 | binary_bytes: *ArrayList(u8), |
| 2027 | limits: std.wasm.Limits, |
| 2028 | ) Allocator.Error!void { |
| 2029 | try binary_bytes.append(gpa, @bitCast(limits.flags)); |
| 2030 | try appendLeb128(gpa, binary_bytes, limits.min); |
| 2031 | if (limits.flags.has_max) try appendLeb128(gpa, binary_bytes, limits.max); |
| 2032 | } |
| 2033 | |
| 2034 | fn emitMemoryImport( |
| 2035 | wasm: *Wasm, |
| 2036 | binary_bytes: *ArrayList(u8), |
| 2037 | name_index: String, |
| 2038 | memory_import: *const Wasm.MemoryImport, |
| 2039 | ) Allocator.Error!void { |
| 2040 | const gpa = wasm.base.comp.gpa; |
| 2041 | const module_name = memory_import.module_name.slice(wasm); |
| 2042 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(module_name.len))); |
| 2043 | try binary_bytes.appendSlice(gpa, module_name); |
| 2044 | |
| 2045 | const name = name_index.slice(wasm); |
| 2046 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(name.len))); |
| 2047 | try binary_bytes.appendSlice(gpa, name); |
| 2048 | |
| 2049 | try binary_bytes.append(gpa, @backingInt(std.wasm.ExternalKind.memory)); |
| 2050 | try emitLimits(gpa, binary_bytes, memory_import.limits()); |
| 2051 | } |
| 2052 | |
| 2053 | fn emitInit(writer: *std.Io.Writer, init_expr: std.wasm.InitExpression) !void { |
| 2054 | switch (init_expr) { |
| 2055 | .i32_const => |val| { |
| 2056 | try writer.writeByte(@backingInt(std.wasm.Opcode.i32_const)); |
| 2057 | try writer.writeSleb128(val); |
| 2058 | }, |
| 2059 | .i64_const => |val| { |
| 2060 | try writer.writeByte(@backingInt(std.wasm.Opcode.i64_const)); |
| 2061 | try writer.writeSleb128(val); |
| 2062 | }, |
| 2063 | .f32_const => |val| { |
| 2064 | try writer.writeByte(@backingInt(std.wasm.Opcode.f32_const)); |
| 2065 | try writer.writeInt(u32, @bitCast(val), .little); |
| 2066 | }, |
| 2067 | .f64_const => |val| { |
| 2068 | try writer.writeByte(@backingInt(std.wasm.Opcode.f64_const)); |
| 2069 | try writer.writeInt(u64, @bitCast(val), .little); |
| 2070 | }, |
| 2071 | .global_get => |val| { |
| 2072 | try writer.writeByte(@backingInt(std.wasm.Opcode.global_get)); |
| 2073 | try writer.writeUleb128(val); |
| 2074 | }, |
| 2075 | } |
| 2076 | try writer.writeByte(@backingInt(std.wasm.Opcode.end)); |
| 2077 | } |
| 2078 | |
| 2079 | pub fn emitExpr(wasm: *const Wasm, binary_bytes: *ArrayList(u8), expr: Wasm.Expr) Allocator.Error!void { |
| 2080 | const gpa = wasm.base.comp.gpa; |
| 2081 | const slice = expr.slice(wasm); |
| 2082 | try binary_bytes.appendSlice(gpa, slice[0 .. slice.len + 1]); // +1 to include end opcode |
| 2083 | } |
| 2084 | |
| 2085 | fn uleb128size(x: u32) u32 { |
| 2086 | var value = x; |
| 2087 | var size: u32 = 0; |
| 2088 | while (value != 0) : (size += 1) value >>= 7; |
| 2089 | return size; |
| 2090 | } |
| 2091 | |
| 2092 | fn emitTagNameTable( |
| 2093 | wasm: *const Wasm, |
| 2094 | code: *ArrayList(u8), |
| 2095 | tag_name_offs: []const u32, |
| 2096 | tag_name_bytes: []const u8, |
| 2097 | base: u32, |
| 2098 | is64: bool, |
| 2099 | ) error{OutOfMemory}!void { |
| 2100 | const gpa = wasm.base.comp.gpa; |
| 2101 | const ptr_size_bytes: usize = if (is64) 8 else 4; |
| 2102 | try code.ensureUnusedCapacity(gpa, ptr_size_bytes * 2 * tag_name_offs.len); |
| 2103 | for (tag_name_offs) |off| { |
| 2104 | const name_len: u32 = @intCast(mem.findScalar(u8, tag_name_bytes[off..], 0).?); |
| 2105 | if (is64) { |
| 2106 | mem.writeInt(u64, code.addManyAsArrayAssumeCapacity(8), base + off, .little); |
| 2107 | mem.writeInt(u64, code.addManyAsArrayAssumeCapacity(8), name_len, .little); |
| 2108 | } else { |
| 2109 | mem.writeInt(u32, code.addManyAsArrayAssumeCapacity(4), base + off, .little); |
| 2110 | mem.writeInt(u32, code.addManyAsArrayAssumeCapacity(4), name_len, .little); |
| 2111 | } |
| 2112 | } |
| 2113 | } |
| 2114 | |
| 2115 | fn emitRelocatableNameTable( |
| 2116 | wasm: *const Wasm, |
| 2117 | code: *ArrayList(u8), |
| 2118 | relocs: *ArrayList(Relocation), |
| 2119 | output_offset: u32, |
| 2120 | name_offs: []const u32, |
| 2121 | name_bytes: []const u8, |
| 2122 | names_resolution: Wasm.ObjectDataImport.Resolution, |
| 2123 | ) error{OutOfMemory}!void { |
| 2124 | const gpa = wasm.base.comp.gpa; |
| 2125 | const ptr_size = @divExact(wasm.base.comp.root_mod.resolved_target.result.ptrBitWidth(), 8); |
| 2126 | const table_start = code.items.len; |
| 2127 | const data_index: DataSymbolIndex = .fromResolution(wasm, names_resolution); |
| 2128 | try code.ensureUnusedCapacity(gpa, @as(usize, ptr_size) * 2 * name_offs.len); |
| 2129 | try relocs.ensureUnusedCapacity(gpa, name_offs.len); |
| 2130 | for (name_offs) |off| { |
| 2131 | const name_len: u32 = @intCast(mem.findScalar(u8, name_bytes[off..], 0).?); |
| 2132 | const reloc_offset = output_offset + @as(u32, @intCast(code.items.len - table_start)); |
| 2133 | switch (ptr_size) { |
| 2134 | 4 => { |
| 2135 | @memset(code.addManyAsArrayAssumeCapacity(4), 0); |
| 2136 | mem.writeInt(u32, code.addManyAsArrayAssumeCapacity(4), name_len, .little); |
| 2137 | }, |
| 2138 | 8 => { |
| 2139 | @memset(code.addManyAsArrayAssumeCapacity(8), 0); |
| 2140 | mem.writeInt(u64, code.addManyAsArrayAssumeCapacity(8), @intCast(name_len), .little); |
| 2141 | }, |
| 2142 | else => unreachable, |
| 2143 | } |
| 2144 | relocs.appendAssumeCapacity(.{ |
| 2145 | .tag = if (ptr_size == 4) .memory_addr_i32 else .memory_addr_i64, |
| 2146 | .offset = reloc_offset, |
| 2147 | .pointee = .{ .data = data_index }, |
| 2148 | .addend = @intCast(off), |
| 2149 | }); |
| 2150 | } |
| 2151 | } |
| 2152 | |
| 2153 | fn emitRelocSection( |
| 2154 | wasm: *const Wasm, |
| 2155 | binary_bytes: *ArrayList(u8), |
| 2156 | section_index: u32, |
| 2157 | reloc_name: []const u8, |
| 2158 | relocs: []const Relocation, |
| 2159 | symbol_table_offsets: SymbolTableOffsets, |
| 2160 | ) !void { |
| 2161 | const comp = wasm.base.comp; |
| 2162 | const gpa = comp.gpa; |
| 2163 | |
| 2164 | const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes); |
| 2165 | defer writeCustomSectionHeader(binary_bytes, header_offset); |
| 2166 | |
| 2167 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(reloc_name.len))); |
| 2168 | try binary_bytes.appendSlice(gpa, reloc_name); |
| 2169 | |
| 2170 | try appendLeb128(gpa, binary_bytes, section_index); |
| 2171 | try appendLeb128(gpa, binary_bytes, @as(u32, @intCast(relocs.len))); |
| 2172 | |
| 2173 | for (relocs) |r| { |
| 2174 | try binary_bytes.append(gpa, @backingInt(r.tag)); |
| 2175 | try appendLeb128(gpa, binary_bytes, r.offset); |
| 2176 | switch (r.tag) { |
| 2177 | .memory_addr_leb, |
| 2178 | .memory_addr_sleb, |
| 2179 | .memory_addr_i32, |
| 2180 | .memory_addr_rel_sleb, |
| 2181 | .memory_addr_leb64, |
| 2182 | .memory_addr_sleb64, |
| 2183 | .memory_addr_i64, |
| 2184 | .memory_addr_rel_sleb64, |
| 2185 | .memory_addr_tls_sleb, |
| 2186 | .memory_addr_locrel_i32, |
| 2187 | .memory_addr_tls_sleb64, |
| 2188 | => { |
| 2189 | const symbol_index: u32 = symbol_table_offsets.data + @backingInt(r.pointee.data); |
| 2190 | try appendLeb128(gpa, binary_bytes, symbol_index); |
| 2191 | }, |
| 2192 | .section_offset_i32 => { |
| 2193 | @panic("TODO"); |
| 2194 | }, |
| 2195 | .type_index_leb => { |
| 2196 | try appendLeb128(gpa, binary_bytes, @backingInt(r.pointee.type_index)); |
| 2197 | }, |
| 2198 | .function_offset_i32, |
| 2199 | .function_offset_i64, |
| 2200 | .function_index_leb, |
| 2201 | .function_index_i32, |
| 2202 | .table_index_sleb, |
| 2203 | .table_index_i32, |
| 2204 | .table_index_sleb64, |
| 2205 | .table_index_i64, |
| 2206 | .table_index_rel_sleb, |
| 2207 | .table_index_rel_sleb64, |
| 2208 | => { |
| 2209 | const symbol_index: u32 = symbol_table_offsets.function + @backingInt(r.pointee.function); |
| 2210 | try appendLeb128(gpa, binary_bytes, symbol_index); |
| 2211 | }, |
| 2212 | .global_index_leb, .global_index_i32 => { |
| 2213 | const symbol_index: u32 = symbol_table_offsets.global + @backingInt(r.pointee.global); |
| 2214 | try appendLeb128(gpa, binary_bytes, symbol_index); |
| 2215 | }, |
| 2216 | .table_number_leb => { |
| 2217 | const symbol_index: u32 = symbol_table_offsets.table + @backingInt(r.pointee.table); |
| 2218 | try appendLeb128(gpa, binary_bytes, symbol_index); |
| 2219 | }, |
| 2220 | .event_index_leb => @panic("TODO"), |
| 2221 | } |
| 2222 | switch (r.tag) { |
| 2223 | .memory_addr_leb, |
| 2224 | .memory_addr_sleb, |
| 2225 | .memory_addr_i32, |
| 2226 | .memory_addr_rel_sleb, |
| 2227 | .memory_addr_leb64, |
| 2228 | .memory_addr_sleb64, |
| 2229 | .memory_addr_i64, |
| 2230 | .memory_addr_rel_sleb64, |
| 2231 | .memory_addr_tls_sleb, |
| 2232 | .memory_addr_locrel_i32, |
| 2233 | .memory_addr_tls_sleb64, |
| 2234 | .function_offset_i32, |
| 2235 | .function_offset_i64, |
| 2236 | .section_offset_i32, |
| 2237 | => { |
| 2238 | try appendLeb128(gpa, binary_bytes, r.addend); |
| 2239 | }, |
| 2240 | else => {}, |
| 2241 | } |
| 2242 | } |
| 2243 | } |
| 2244 | |
| 2245 | fn processZcuRelocs( |
| 2246 | wasm: *const Wasm, |
| 2247 | out: *ArrayList(Relocation), |
| 2248 | output_offset: u32, |
| 2249 | input_offset: u32, |
| 2250 | relocs: Wasm.ZcuRelocation.Slice, |
| 2251 | ) !void { |
| 2252 | const gpa = wasm.base.comp.gpa; |
| 2253 | for ( |
| 2254 | relocs.tags(wasm), |
| 2255 | relocs.pointees(wasm), |
| 2256 | relocs.offsets(wasm), |
| 2257 | relocs.addends(wasm), |
| 2258 | ) |tag, pointee, offset, addend| { |
| 2259 | const output_pointee: Relocation.Pointee = switch (pointee) { |
| 2260 | .function_nav => |nav_index| .{ .function = .fromIpNav(wasm, nav_index) }, |
| 2261 | .function_name => |name| .{ .function = .fromSymbolName(wasm, name) }, |
| 2262 | .tag_function => |ip_index| .{ .function = .fromTagIndexType(wasm, ip_index) }, |
| 2263 | .data_uav => |ip_index| .{ .data = .fromUav(wasm, ip_index) }, |
| 2264 | .data_nav => |nav_index| .{ .data = .fromNav(wasm, nav_index) }, |
| 2265 | .data_resolution => |resolution| .{ .data = .fromResolution(wasm, resolution) }, |
| 2266 | .stack_pointer => .{ .global = .fromSymbolName(wasm, wasm.preloaded_strings.__stack_pointer) }, |
| 2267 | .type_index => |type_index| .{ .type_index = .fromTypeIndex(type_index, &wasm.flush_buffer) }, |
| 2268 | }; |
| 2269 | try out.append(gpa, .{ |
| 2270 | .tag = tag, |
| 2271 | .offset = output_offset + (offset - input_offset), |
| 2272 | .pointee = output_pointee, |
| 2273 | .addend = addend, |
| 2274 | }); |
| 2275 | } |
| 2276 | } |
| 2277 | |
| 2278 | fn processRelocs( |
| 2279 | wasm: *const Wasm, |
| 2280 | out: *ArrayList(Relocation), |
| 2281 | output_offset: u32, |
| 2282 | input_offset: u32, |
| 2283 | relocs: Wasm.ObjectRelocation.IterableSlice, |
| 2284 | ) !void { |
| 2285 | const gpa = wasm.base.comp.gpa; |
| 2286 | for ( |
| 2287 | relocs.slice.tags(wasm), |
| 2288 | relocs.slice.pointees(wasm), |
| 2289 | relocs.slice.offsets(wasm), |
| 2290 | relocs.slice.addends(wasm), |
| 2291 | ) |tag, pointee, offset, addend| { |
| 2292 | if (offset >= relocs.end) break; |
| 2293 | const rebased_offset = output_offset + (offset - input_offset); |
| 2294 | try out.ensureUnusedCapacity(gpa, 1); |
| 2295 | switch (tag) { |
| 2296 | .function_index_i32 => out.appendAssumeCapacity(.{ |
| 2297 | .tag = .function_index_i32, |
| 2298 | .offset = rebased_offset, |
| 2299 | .pointee = .{ .function = .fromObjectFunctionHandlingWeak(wasm, pointee.function) }, |
| 2300 | .addend = addend, |
| 2301 | }), |
| 2302 | .function_index_leb => out.appendAssumeCapacity(.{ |
| 2303 | .tag = .function_index_leb, |
| 2304 | .offset = rebased_offset, |
| 2305 | .pointee = .{ .function = .fromObjectFunctionHandlingWeak(wasm, pointee.function) }, |
| 2306 | .addend = addend, |
| 2307 | }), |
| 2308 | .function_offset_i32 => @panic("TODO this value is not known yet"), |
| 2309 | .function_offset_i64 => @panic("TODO this value is not known yet"), |
| 2310 | .table_index_i32 => out.appendAssumeCapacity(.{ |
| 2311 | .tag = .table_index_i32, |
| 2312 | .offset = rebased_offset, |
| 2313 | .pointee = .{ .function = .fromObjectFunctionHandlingWeak(wasm, pointee.function) }, |
| 2314 | .addend = addend, |
| 2315 | }), |
| 2316 | .table_index_i64 => out.appendAssumeCapacity(.{ |
| 2317 | .tag = .table_index_i64, |
| 2318 | .offset = rebased_offset, |
| 2319 | .pointee = .{ .function = .fromObjectFunctionHandlingWeak(wasm, pointee.function) }, |
| 2320 | .addend = addend, |
| 2321 | }), |
| 2322 | .table_index_rel_sleb => @panic("TODO what does this reloc tag mean?"), |
| 2323 | .table_index_rel_sleb64 => @panic("TODO what does this reloc tag mean?"), |
| 2324 | .table_index_sleb => out.appendAssumeCapacity(.{ |
| 2325 | .tag = .table_index_sleb, |
| 2326 | .offset = rebased_offset, |
| 2327 | .pointee = .{ .function = .fromObjectFunctionHandlingWeak(wasm, pointee.function) }, |
| 2328 | .addend = addend, |
| 2329 | }), |
| 2330 | .table_index_sleb64 => out.appendAssumeCapacity(.{ |
| 2331 | .tag = .table_index_sleb64, |
| 2332 | .offset = rebased_offset, |
| 2333 | .pointee = .{ .function = .fromObjectFunctionHandlingWeak(wasm, pointee.function) }, |
| 2334 | .addend = addend, |
| 2335 | }), |
| 2336 | |
| 2337 | .function_import_index_i32 => out.appendAssumeCapacity(.{ |
| 2338 | .tag = .function_index_i32, |
| 2339 | .offset = rebased_offset, |
| 2340 | .pointee = .{ .function = .fromSymbolName(wasm, pointee.symbol_name) }, |
| 2341 | .addend = addend, |
| 2342 | }), |
| 2343 | .function_import_index_leb => out.appendAssumeCapacity(.{ |
| 2344 | .tag = .function_index_leb, |
| 2345 | .offset = rebased_offset, |
| 2346 | .pointee = .{ .function = .fromSymbolName(wasm, pointee.symbol_name) }, |
| 2347 | .addend = addend, |
| 2348 | }), |
| 2349 | .function_import_offset_i32 => @panic("TODO this value is not known yet"), |
| 2350 | .function_import_offset_i64 => @panic("TODO this value is not known yet"), |
| 2351 | .table_import_index_i32 => out.appendAssumeCapacity(.{ |
| 2352 | .tag = .table_index_i32, |
| 2353 | .offset = rebased_offset, |
| 2354 | .pointee = .{ .function = .fromSymbolName(wasm, pointee.symbol_name) }, |
| 2355 | .addend = addend, |
| 2356 | }), |
| 2357 | .table_import_index_i64 => out.appendAssumeCapacity(.{ |
| 2358 | .tag = .table_index_i64, |
| 2359 | .offset = rebased_offset, |
| 2360 | .pointee = .{ .function = .fromSymbolName(wasm, pointee.symbol_name) }, |
| 2361 | .addend = addend, |
| 2362 | }), |
| 2363 | .table_import_index_rel_sleb => @panic("TODO what does this reloc tag mean?"), |
| 2364 | .table_import_index_rel_sleb64 => @panic("TODO what does this reloc tag mean?"), |
| 2365 | .table_import_index_sleb => out.appendAssumeCapacity(.{ |
| 2366 | .tag = .table_index_sleb, |
| 2367 | .offset = rebased_offset, |
| 2368 | .pointee = .{ .function = .fromSymbolName(wasm, pointee.symbol_name) }, |
| 2369 | .addend = addend, |
| 2370 | }), |
| 2371 | .table_import_index_sleb64 => out.appendAssumeCapacity(.{ |
| 2372 | .tag = .table_index_sleb64, |
| 2373 | .offset = rebased_offset, |
| 2374 | .pointee = .{ .function = .fromSymbolName(wasm, pointee.symbol_name) }, |
| 2375 | .addend = addend, |
| 2376 | }), |
| 2377 | |
| 2378 | .global_index_i32 => out.appendAssumeCapacity(.{ |
| 2379 | .tag = .global_index_i32, |
| 2380 | .offset = rebased_offset, |
| 2381 | .pointee = .{ .global = .fromObjectGlobalHandlingWeak(wasm, pointee.global) }, |
| 2382 | .addend = addend, |
| 2383 | }), |
| 2384 | .global_index_leb => out.appendAssumeCapacity(.{ |
| 2385 | .tag = .global_index_leb, |
| 2386 | .offset = rebased_offset, |
| 2387 | .pointee = .{ .global = .fromObjectGlobalHandlingWeak(wasm, pointee.global) }, |
| 2388 | .addend = addend, |
| 2389 | }), |
| 2390 | |
| 2391 | .global_import_index_i32 => out.appendAssumeCapacity(.{ |
| 2392 | .tag = .global_index_i32, |
| 2393 | .offset = rebased_offset, |
| 2394 | .pointee = .{ .global = .fromSymbolName(wasm, pointee.symbol_name) }, |
| 2395 | .addend = addend, |
| 2396 | }), |
| 2397 | .global_import_index_leb => out.appendAssumeCapacity(.{ |
| 2398 | .tag = .global_index_leb, |
| 2399 | .offset = rebased_offset, |
| 2400 | .pointee = .{ .global = .fromSymbolName(wasm, pointee.symbol_name) }, |
| 2401 | .addend = addend, |
| 2402 | }), |
| 2403 | |
| 2404 | .memory_addr_i32, |
| 2405 | .memory_addr_i64, |
| 2406 | .memory_addr_leb, |
| 2407 | .memory_addr_leb64, |
| 2408 | .memory_addr_sleb, |
| 2409 | .memory_addr_sleb64, |
| 2410 | .memory_addr_tls_sleb, |
| 2411 | .memory_addr_tls_sleb64, |
| 2412 | => out.appendAssumeCapacity(.{ |
| 2413 | .tag = memoryRelocationType(tag), |
| 2414 | .offset = rebased_offset, |
| 2415 | .pointee = .{ .data = .fromObjectData(wasm, pointee.data) }, |
| 2416 | .addend = addend, |
| 2417 | }), |
| 2418 | .memory_addr_locrel_i32 => @panic("TODO implement relocation memory_addr_locrel_i32"), |
| 2419 | .memory_addr_rel_sleb => @panic("TODO implement relocation memory_addr_rel_sleb"), |
| 2420 | .memory_addr_rel_sleb64 => @panic("TODO implement relocation memory_addr_rel_sleb64"), |
| 2421 | |
| 2422 | .memory_addr_import_i32, |
| 2423 | .memory_addr_import_i64, |
| 2424 | .memory_addr_import_leb, |
| 2425 | .memory_addr_import_leb64, |
| 2426 | .memory_addr_import_sleb, |
| 2427 | .memory_addr_import_sleb64, |
| 2428 | => out.appendAssumeCapacity(.{ |
| 2429 | .tag = memoryRelocationType(tag), |
| 2430 | .offset = rebased_offset, |
| 2431 | .pointee = .{ .data = .fromSymbolName(wasm, pointee.symbol_name) }, |
| 2432 | .addend = addend, |
| 2433 | }), |
| 2434 | .memory_addr_import_locrel_i32 => @panic("TODO implement relocation memory_addr_import_locrel_i32"), |
| 2435 | .memory_addr_import_rel_sleb => @panic("TODO implement relocation memory_addr_import_rel_sleb"), |
| 2436 | .memory_addr_import_rel_sleb64 => @panic("TODO implement memory_addr_import_rel_sleb64"), |
| 2437 | .memory_addr_import_tls_sleb => @panic("TODO"), |
| 2438 | .memory_addr_import_tls_sleb64 => @panic("TODO"), |
| 2439 | |
| 2440 | .section_offset_i32 => @panic("TODO this value is not known yet"), |
| 2441 | |
| 2442 | .table_number_leb => out.appendAssumeCapacity(.{ |
| 2443 | .tag = .table_number_leb, |
| 2444 | .offset = rebased_offset, |
| 2445 | .pointee = .{ .table = .fromObjectTable(wasm, pointee.table) }, |
| 2446 | .addend = addend, |
| 2447 | }), |
| 2448 | .table_import_number_leb => out.appendAssumeCapacity(.{ |
| 2449 | .tag = .table_number_leb, |
| 2450 | .offset = rebased_offset, |
| 2451 | .pointee = .{ .table = .fromSymbolName(wasm, pointee.symbol_name) }, |
| 2452 | .addend = addend, |
| 2453 | }), |
| 2454 | |
| 2455 | .type_index_leb => out.appendAssumeCapacity(.{ |
| 2456 | .tag = .type_index_leb, |
| 2457 | .offset = rebased_offset, |
| 2458 | .pointee = .{ .type_index = .fromTypeIndex(pointee.type_index, &wasm.flush_buffer) }, |
| 2459 | .addend = addend, |
| 2460 | }), |
| 2461 | } |
| 2462 | } |
| 2463 | } |
| 2464 | |
| 2465 | fn memoryRelocationType(tag: Wasm.ObjectRelocation.Tag) Object.RelocationType { |
| 2466 | return switch (tag) { |
| 2467 | .memory_addr_i32, .memory_addr_import_i32 => .memory_addr_i32, |
| 2468 | .memory_addr_i64, .memory_addr_import_i64 => .memory_addr_i64, |
| 2469 | .memory_addr_leb, .memory_addr_import_leb => .memory_addr_leb, |
| 2470 | .memory_addr_leb64, .memory_addr_import_leb64 => .memory_addr_leb64, |
| 2471 | .memory_addr_locrel_i32, .memory_addr_import_locrel_i32 => .memory_addr_locrel_i32, |
| 2472 | .memory_addr_rel_sleb, .memory_addr_import_rel_sleb => .memory_addr_rel_sleb, |
| 2473 | .memory_addr_rel_sleb64, .memory_addr_import_rel_sleb64 => .memory_addr_rel_sleb64, |
| 2474 | .memory_addr_sleb, .memory_addr_import_sleb => .memory_addr_sleb, |
| 2475 | .memory_addr_sleb64, .memory_addr_import_sleb64 => .memory_addr_sleb64, |
| 2476 | .memory_addr_tls_sleb, .memory_addr_import_tls_sleb => .memory_addr_tls_sleb, |
| 2477 | .memory_addr_tls_sleb64, .memory_addr_import_tls_sleb64 => .memory_addr_tls_sleb64, |
| 2478 | else => unreachable, |
| 2479 | }; |
| 2480 | } |
| 2481 | |
| 2482 | fn applyRelocs(code: []u8, code_offset: u32, relocs: Wasm.ObjectRelocation.IterableSlice, wasm: *const Wasm) void { |
| 2483 | for ( |
| 2484 | relocs.slice.tags(wasm), |
| 2485 | relocs.slice.pointees(wasm), |
| 2486 | relocs.slice.offsets(wasm), |
| 2487 | relocs.slice.addends(wasm), |
| 2488 | ) |tag, pointee, offset, *addend| { |
| 2489 | if (offset >= relocs.end) break; |
| 2490 | const sliced_code = code[offset - code_offset ..]; |
| 2491 | switch (tag) { |
| 2492 | .function_index_i32 => reloc_u32_function(sliced_code, .fromObjectFunctionHandlingWeak(wasm, pointee.function)), |
| 2493 | .function_index_leb => reloc_leb_function(sliced_code, .fromObjectFunctionHandlingWeak(wasm, pointee.function)), |
| 2494 | .function_offset_i32 => @panic("TODO this value is not known yet"), |
| 2495 | .function_offset_i64 => @panic("TODO this value is not known yet"), |
| 2496 | .table_index_i32 => reloc_u32_table_index(sliced_code, .fromObjectFunctionHandlingWeak(wasm, pointee.function)), |
| 2497 | .table_index_i64 => reloc_u64_table_index(sliced_code, .fromObjectFunctionHandlingWeak(wasm, pointee.function)), |
| 2498 | .table_index_rel_sleb => @panic("TODO what does this reloc tag mean?"), |
| 2499 | .table_index_rel_sleb64 => @panic("TODO what does this reloc tag mean?"), |
| 2500 | .table_index_sleb => reloc_sleb_table_index(sliced_code, .fromObjectFunctionHandlingWeak(wasm, pointee.function)), |
| 2501 | .table_index_sleb64 => reloc_sleb64_table_index(sliced_code, .fromObjectFunctionHandlingWeak(wasm, pointee.function)), |
| 2502 | |
| 2503 | .function_import_index_i32 => reloc_u32_function(sliced_code, .fromSymbolName(wasm, pointee.symbol_name)), |
| 2504 | .function_import_index_leb => reloc_leb_function(sliced_code, .fromSymbolName(wasm, pointee.symbol_name)), |
| 2505 | .function_import_offset_i32 => @panic("TODO this value is not known yet"), |
| 2506 | .function_import_offset_i64 => @panic("TODO this value is not known yet"), |
| 2507 | .table_import_index_i32 => reloc_u32_table_index(sliced_code, .fromSymbolName(wasm, pointee.symbol_name)), |
| 2508 | .table_import_index_i64 => reloc_u64_table_index(sliced_code, .fromSymbolName(wasm, pointee.symbol_name)), |
| 2509 | .table_import_index_rel_sleb => @panic("TODO what does this reloc tag mean?"), |
| 2510 | .table_import_index_rel_sleb64 => @panic("TODO what does this reloc tag mean?"), |
| 2511 | .table_import_index_sleb => reloc_sleb_table_index(sliced_code, .fromSymbolName(wasm, pointee.symbol_name)), |
| 2512 | .table_import_index_sleb64 => reloc_sleb64_table_index(sliced_code, .fromSymbolName(wasm, pointee.symbol_name)), |
| 2513 | |
| 2514 | .global_index_i32 => reloc_u32_global(sliced_code, .fromObjectGlobalHandlingWeak(wasm, pointee.global)), |
| 2515 | .global_index_leb => reloc_leb_global(sliced_code, .fromObjectGlobalHandlingWeak(wasm, pointee.global)), |
| 2516 | |
| 2517 | .global_import_index_i32 => reloc_u32_global(sliced_code, .fromSymbolName(wasm, pointee.symbol_name)), |
| 2518 | .global_import_index_leb => reloc_leb_global(sliced_code, .fromSymbolName(wasm, pointee.symbol_name)), |
| 2519 | |
| 2520 | .memory_addr_i32 => reloc_u32_addr(sliced_code, .fromObjectData(wasm, pointee.data, addend.*)), |
| 2521 | .memory_addr_i64 => reloc_u64_addr(sliced_code, .fromObjectData(wasm, pointee.data, addend.*)), |
| 2522 | .memory_addr_leb => reloc_leb_addr(sliced_code, .fromObjectData(wasm, pointee.data, addend.*)), |
| 2523 | .memory_addr_leb64 => reloc_leb64_addr(sliced_code, .fromObjectData(wasm, pointee.data, addend.*)), |
| 2524 | .memory_addr_locrel_i32 => @panic("TODO implement relocation memory_addr_locrel_i32"), |
| 2525 | .memory_addr_rel_sleb => @panic("TODO implement relocation memory_addr_rel_sleb"), |
| 2526 | .memory_addr_rel_sleb64 => @panic("TODO implement relocation memory_addr_rel_sleb64"), |
| 2527 | .memory_addr_sleb => reloc_sleb_addr(sliced_code, .fromObjectData(wasm, pointee.data, addend.*)), |
| 2528 | .memory_addr_sleb64 => reloc_sleb64_addr(sliced_code, .fromObjectData(wasm, pointee.data, addend.*)), |
| 2529 | .memory_addr_tls_sleb => reloc_sleb_addr(sliced_code, .fromObjectData(wasm, pointee.data, addend.*)), |
| 2530 | .memory_addr_tls_sleb64 => reloc_sleb64_addr(sliced_code, .fromObjectData(wasm, pointee.data, addend.*)), |
| 2531 | |
| 2532 | .memory_addr_import_i32 => reloc_u32_addr(sliced_code, .fromSymbolName(wasm, pointee.symbol_name, addend.*)), |
| 2533 | .memory_addr_import_i64 => reloc_u64_addr(sliced_code, .fromSymbolName(wasm, pointee.symbol_name, addend.*)), |
| 2534 | .memory_addr_import_leb => reloc_leb_addr(sliced_code, .fromSymbolName(wasm, pointee.symbol_name, addend.*)), |
| 2535 | .memory_addr_import_leb64 => reloc_leb64_addr(sliced_code, .fromSymbolName(wasm, pointee.symbol_name, addend.*)), |
| 2536 | .memory_addr_import_locrel_i32 => @panic("TODO implement relocation memory_addr_import_locrel_i32"), |
| 2537 | .memory_addr_import_rel_sleb => @panic("TODO implement relocation memory_addr_import_rel_sleb"), |
| 2538 | .memory_addr_import_rel_sleb64 => @panic("TODO implement memory_addr_import_rel_sleb64"), |
| 2539 | .memory_addr_import_sleb => reloc_sleb_addr(sliced_code, .fromSymbolName(wasm, pointee.symbol_name, addend.*)), |
| 2540 | .memory_addr_import_sleb64 => reloc_sleb64_addr(sliced_code, .fromSymbolName(wasm, pointee.symbol_name, addend.*)), |
| 2541 | .memory_addr_import_tls_sleb => @panic("TODO"), |
| 2542 | .memory_addr_import_tls_sleb64 => @panic("TODO"), |
| 2543 | |
| 2544 | .section_offset_i32 => @panic("TODO this value is not known yet"), |
| 2545 | |
| 2546 | .table_number_leb => reloc_leb_table(sliced_code, .fromObjectTable(wasm, pointee.table)), |
| 2547 | .table_import_number_leb => reloc_leb_table(sliced_code, .fromSymbolName(wasm, pointee.symbol_name)), |
| 2548 | |
| 2549 | .type_index_leb => reloc_leb_type(sliced_code, .fromTypeIndex(pointee.type_index, &wasm.flush_buffer)), |
| 2550 | } |
| 2551 | } |
| 2552 | } |
| 2553 | |
| 2554 | fn reloc_u32_table_index(code: []u8, i: IndirectFunctionTableIndex) void { |
| 2555 | mem.writeInt(u32, code[0..4], i.toAbi(), .little); |
| 2556 | } |
| 2557 | |
| 2558 | fn reloc_u64_table_index(code: []u8, i: IndirectFunctionTableIndex) void { |
| 2559 | mem.writeInt(u64, code[0..8], i.toAbi(), .little); |
| 2560 | } |
| 2561 | |
| 2562 | fn reloc_sleb_table_index(code: []u8, i: IndirectFunctionTableIndex) void { |
| 2563 | leb.writeSignedFixed(5, code[0..5], i.toAbi()); |
| 2564 | } |
| 2565 | |
| 2566 | fn reloc_sleb64_table_index(code: []u8, i: IndirectFunctionTableIndex) void { |
| 2567 | leb.writeSignedFixed(11, code[0..11], i.toAbi()); |
| 2568 | } |
| 2569 | |
| 2570 | fn reloc_u32_function(code: []u8, function: Wasm.OutputFunctionIndex) void { |
| 2571 | mem.writeInt(u32, code[0..4], @backingInt(function), .little); |
| 2572 | } |
| 2573 | |
| 2574 | fn reloc_leb_function(code: []u8, function: Wasm.OutputFunctionIndex) void { |
| 2575 | leb.writeUnsignedFixed(5, code[0..5], @backingInt(function)); |
| 2576 | } |
| 2577 | |
| 2578 | fn reloc_u32_global(code: []u8, global: Wasm.GlobalIndex) void { |
| 2579 | mem.writeInt(u32, code[0..4], @backingInt(global), .little); |
| 2580 | } |
| 2581 | |
| 2582 | fn reloc_leb_global(code: []u8, global: Wasm.GlobalIndex) void { |
| 2583 | leb.writeUnsignedFixed(5, code[0..5], @backingInt(global)); |
| 2584 | } |
| 2585 | |
| 2586 | const RelocAddr = struct { |
| 2587 | addr: u32, |
| 2588 | |
| 2589 | fn fromObjectData(wasm: *const Wasm, i: Wasm.ObjectData.Index, addend: i32) RelocAddr { |
| 2590 | return fromDataLoc(&wasm.flush_buffer, .fromObjectDataIndex(wasm, i), addend); |
| 2591 | } |
| 2592 | |
| 2593 | fn fromSymbolName(wasm: *const Wasm, name: String, addend: i32) RelocAddr { |
| 2594 | const flush = &wasm.flush_buffer; |
| 2595 | if (wasm.object_data_imports.getPtr(name)) |import| { |
| 2596 | if (import.resolution != .unresolved) { |
| 2597 | if (wasm.syntheticDataAddr(import.resolution)) |addr| { |
| 2598 | return fromAddr(addr, addend); |
| 2599 | } |
| 2600 | return fromDataLoc(flush, import.resolution.dataLoc(wasm), addend); |
| 2601 | } |
| 2602 | } |
| 2603 | if (flush.data_exports.get(name)) |symbol| { |
| 2604 | return fromDataLoc(flush, symbol.resolution.dataLoc(wasm), addend); |
| 2605 | } |
| 2606 | if (wasm.data_imports.get(name)) |id| { |
| 2607 | return fromDataLoc(flush, .fromDataImportId(wasm, id), addend); |
| 2608 | } |
| 2609 | unreachable; |
| 2610 | } |
| 2611 | |
| 2612 | fn fromDataLoc(flush: *const Flush, data_loc: Wasm.DataLoc, addend: i32) RelocAddr { |
| 2613 | return fromAddr(flush.data_segments.get(data_loc.segment).? + data_loc.offset, addend); |
| 2614 | } |
| 2615 | |
| 2616 | fn fromAddr(addr: u32, addend: i32) RelocAddr { |
| 2617 | return .{ .addr = @intCast(@as(i64, addr) + addend) }; |
| 2618 | } |
| 2619 | }; |
| 2620 | |
| 2621 | fn reloc_u32_addr(code: []u8, ra: RelocAddr) void { |
| 2622 | mem.writeInt(u32, code[0..4], ra.addr, .little); |
| 2623 | } |
| 2624 | |
| 2625 | fn reloc_u64_addr(code: []u8, ra: RelocAddr) void { |
| 2626 | mem.writeInt(u64, code[0..8], ra.addr, .little); |
| 2627 | } |
| 2628 | |
| 2629 | fn reloc_leb_addr(code: []u8, ra: RelocAddr) void { |
| 2630 | leb.writeUnsignedFixed(5, code[0..5], ra.addr); |
| 2631 | } |
| 2632 | |
| 2633 | fn reloc_leb64_addr(code: []u8, ra: RelocAddr) void { |
| 2634 | leb.writeUnsignedFixed(11, code[0..11], ra.addr); |
| 2635 | } |
| 2636 | |
| 2637 | fn reloc_sleb_addr(code: []u8, ra: RelocAddr) void { |
| 2638 | leb.writeSignedFixed(5, code[0..5], ra.addr); |
| 2639 | } |
| 2640 | |
| 2641 | fn reloc_sleb64_addr(code: []u8, ra: RelocAddr) void { |
| 2642 | leb.writeSignedFixed(11, code[0..11], ra.addr); |
| 2643 | } |
| 2644 | |
| 2645 | fn reloc_leb_table(code: []u8, table: Wasm.TableIndex) void { |
| 2646 | leb.writeUnsignedFixed(5, code[0..5], @backingInt(table)); |
| 2647 | } |
| 2648 | |
| 2649 | fn reloc_leb_type(code: []u8, index: FuncTypeIndex) void { |
| 2650 | leb.writeUnsignedFixed(5, code[0..5], @backingInt(index)); |
| 2651 | } |
| 2652 | |
| 2653 | fn emitCallCtorsFunction(wasm: *const Wasm, binary_bytes: *ArrayList(u8)) Allocator.Error!void { |
| 2654 | const gpa = wasm.base.comp.gpa; |
| 2655 | |
| 2656 | try binary_bytes.ensureUnusedCapacity(gpa, 5 + 1); |
| 2657 | appendReservedUleb32(binary_bytes, 0); // no locals |
| 2658 | |
| 2659 | for (wasm.flush_buffer.sorted_init_funcs.items) |init_func| { |
| 2660 | const func = init_func.function_index.ptr(wasm); |
| 2661 | const ty = func.type_index.ptr(wasm); |
| 2662 | const n_returns = ty.returns.slice(wasm).len; |
| 2663 | |
| 2664 | // Call function by its function index |
| 2665 | try binary_bytes.ensureUnusedCapacity(gpa, 1 + 5 + n_returns + 1); |
| 2666 | const call_index: Wasm.OutputFunctionIndex = .fromObjectFunction(wasm, init_func.function_index); |
| 2667 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.call)); |
| 2668 | appendReservedUleb32(binary_bytes, @backingInt(call_index)); |
| 2669 | |
| 2670 | // drop all returned values from the stack as __wasm_call_ctors has no return value |
| 2671 | binary_bytes.appendNTimesAssumeCapacity(@backingInt(std.wasm.Opcode.drop), n_returns); |
| 2672 | } |
| 2673 | |
| 2674 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.end)); // end function body |
| 2675 | } |
| 2676 | |
| 2677 | fn emitInitMemoryFunction(wasm: *const Wasm, binary_bytes: *ArrayList(u8)) Allocator.Error!void { |
| 2678 | const comp = wasm.base.comp; |
| 2679 | const gpa = comp.gpa; |
| 2680 | const shared_memory = comp.config.shared_memory; |
| 2681 | const virtual_addrs = &wasm.flush_buffer.virtual_addrs; |
| 2682 | |
| 2683 | // Passive segments are used to avoid memory being reinitialized on each |
| 2684 | // thread's instantiation. These passive segments are initialized and |
| 2685 | // dropped in __wasm_init_memory, which is registered as the start function |
| 2686 | // We also initialize bss segments (using memory.fill) as part of this |
| 2687 | // function. |
| 2688 | assert(wasm.any_passive_inits); |
| 2689 | |
| 2690 | try binary_bytes.ensureUnusedCapacity(gpa, 5 + 1); |
| 2691 | appendReservedUleb32(binary_bytes, 0); // no locals |
| 2692 | |
| 2693 | if (virtual_addrs.init_memory_flag) |flag_address| { |
| 2694 | assert(shared_memory); |
| 2695 | try binary_bytes.ensureUnusedCapacity(gpa, 2 * 3 + 6 * 3 + 1 + 6 * 3 + 1 + 5 * 4 + 1 + 1); |
| 2696 | // destination blocks |
| 2697 | // based on values we jump to corresponding label |
| 2698 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.block)); // $drop |
| 2699 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.BlockType.empty)); |
| 2700 | |
| 2701 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.block)); // $wait |
| 2702 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.BlockType.empty)); |
| 2703 | |
| 2704 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.block)); // $init |
| 2705 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.BlockType.empty)); |
| 2706 | |
| 2707 | // atomically check |
| 2708 | appendReservedI32Const(binary_bytes, flag_address); |
| 2709 | appendReservedI32Const(binary_bytes, 0); |
| 2710 | appendReservedI32Const(binary_bytes, 1); |
| 2711 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.atomics_prefix)); |
| 2712 | appendReservedUleb32(binary_bytes, @backingInt(std.wasm.AtomicsOpcode.i32_atomic_rmw_cmpxchg)); |
| 2713 | appendReservedUleb32(binary_bytes, 2); // alignment |
| 2714 | appendReservedUleb32(binary_bytes, 0); // offset |
| 2715 | |
| 2716 | // based on the value from the atomic check, jump to the label. |
| 2717 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.br_table)); |
| 2718 | appendReservedUleb32(binary_bytes, 2); // length of the table (we have 3 blocks but because of the mandatory default the length is 2). |
| 2719 | appendReservedUleb32(binary_bytes, 0); // $init |
| 2720 | appendReservedUleb32(binary_bytes, 1); // $wait |
| 2721 | appendReservedUleb32(binary_bytes, 2); // $drop |
| 2722 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.end)); |
| 2723 | } |
| 2724 | |
| 2725 | const segment_groups = wasm.flush_buffer.data_segment_groups.items; |
| 2726 | for (segment_groups, 0..) |group, segment_index| { |
| 2727 | const segment = group.first_segment; |
| 2728 | if (!segment.isPassive(wasm)) continue; |
| 2729 | |
| 2730 | const start_addr = wasm.flush_buffer.data_segments.get(segment).?; |
| 2731 | const segment_size: u32 = group.end_addr - start_addr; |
| 2732 | |
| 2733 | try binary_bytes.ensureUnusedCapacity(gpa, 6 + 6 + 1 + 5 + 6 + 6 + 1 + 6 * 2 + 1 + 1); |
| 2734 | |
| 2735 | // For passive BSS segments we can simply issue a memory.fill(0). For |
| 2736 | // non-BSS segments we do a memory.init. Both instructions take as |
| 2737 | // their first argument the destination address. |
| 2738 | appendReservedI32Const(binary_bytes, start_addr); |
| 2739 | |
| 2740 | if (shared_memory and segment.isTls(wasm)) { |
| 2741 | // When we initialize the TLS segment we also set the `__tls_base` |
| 2742 | // global. This allows the runtime to use this static copy of the |
| 2743 | // TLS data for the first/main thread. |
| 2744 | appendReservedI32Const(binary_bytes, start_addr); |
| 2745 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.global_set)); |
| 2746 | appendReservedUleb32(binary_bytes, virtual_addrs.tls_base.?); |
| 2747 | } |
| 2748 | |
| 2749 | appendReservedI32Const(binary_bytes, 0); |
| 2750 | appendReservedI32Const(binary_bytes, segment_size); |
| 2751 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.misc_prefix)); |
| 2752 | if (segment.isBss(wasm)) { |
| 2753 | // fill bss segment with zeroes |
| 2754 | appendReservedUleb32(binary_bytes, @backingInt(std.wasm.MiscOpcode.memory_fill)); |
| 2755 | } else { |
| 2756 | // initialize the segment |
| 2757 | appendReservedUleb32(binary_bytes, @backingInt(std.wasm.MiscOpcode.memory_init)); |
| 2758 | appendReservedUleb32(binary_bytes, @intCast(segment_index)); |
| 2759 | } |
| 2760 | binary_bytes.appendAssumeCapacity(0); // memory index immediate |
| 2761 | } |
| 2762 | |
| 2763 | if (virtual_addrs.init_memory_flag) |flag_address| { |
| 2764 | assert(shared_memory); |
| 2765 | try binary_bytes.ensureUnusedCapacity(gpa, 6 + 6 + 1 + 3 * 5 + 6 + 1 + 5 + 1 + 3 * 5 + 1 + 1 + 5 + 1 + 6 * 2 + 1 + 5 + 1 + 3 * 5 + 1 + 1 + 1); |
| 2766 | // we set the init memory flag to value '2' |
| 2767 | appendReservedI32Const(binary_bytes, flag_address); |
| 2768 | appendReservedI32Const(binary_bytes, 2); |
| 2769 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.atomics_prefix)); |
| 2770 | appendReservedUleb32(binary_bytes, @backingInt(std.wasm.AtomicsOpcode.i32_atomic_store)); |
| 2771 | appendReservedUleb32(binary_bytes, @as(u32, 2)); // alignment |
| 2772 | appendReservedUleb32(binary_bytes, @as(u32, 0)); // offset |
| 2773 | |
| 2774 | // notify any waiters for segment initialization completion |
| 2775 | appendReservedI32Const(binary_bytes, flag_address); |
| 2776 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.i32_const)); |
| 2777 | appendReservedLeb128(binary_bytes, @as(i32, -1)); // number of waiters |
| 2778 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.atomics_prefix)); |
| 2779 | appendReservedUleb32(binary_bytes, @backingInt(std.wasm.AtomicsOpcode.memory_atomic_notify)); |
| 2780 | appendReservedUleb32(binary_bytes, @as(u32, 2)); // alignment |
| 2781 | appendReservedUleb32(binary_bytes, @as(u32, 0)); // offset |
| 2782 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.drop)); |
| 2783 | |
| 2784 | // branch and drop segments |
| 2785 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.br)); |
| 2786 | appendReservedUleb32(binary_bytes, @as(u32, 1)); |
| 2787 | |
| 2788 | // wait for thread to initialize memory segments |
| 2789 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.end)); // end $wait |
| 2790 | appendReservedI32Const(binary_bytes, flag_address); |
| 2791 | appendReservedI32Const(binary_bytes, 1); // expected flag value |
| 2792 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.i64_const)); |
| 2793 | appendReservedLeb128(binary_bytes, @as(i64, -1)); // timeout |
| 2794 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.atomics_prefix)); |
| 2795 | appendReservedUleb32(binary_bytes, @backingInt(std.wasm.AtomicsOpcode.memory_atomic_wait32)); |
| 2796 | appendReservedUleb32(binary_bytes, @as(u32, 2)); // alignment |
| 2797 | appendReservedUleb32(binary_bytes, @as(u32, 0)); // offset |
| 2798 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.drop)); |
| 2799 | |
| 2800 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.end)); // end $drop |
| 2801 | } |
| 2802 | |
| 2803 | for (segment_groups, 0..) |group, segment_index| { |
| 2804 | const segment = group.first_segment; |
| 2805 | if (!segment.isPassive(wasm)) continue; |
| 2806 | if (segment.isBss(wasm)) continue; |
| 2807 | // The TLS region should not be dropped since its is needed |
| 2808 | // during the initialization of each thread (__wasm_init_tls). |
| 2809 | if (shared_memory and segment.isTls(wasm)) continue; |
| 2810 | |
| 2811 | try binary_bytes.ensureUnusedCapacity(gpa, 1 + 5 + 5 + 1); |
| 2812 | |
| 2813 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.misc_prefix)); |
| 2814 | appendReservedUleb32(binary_bytes, @backingInt(std.wasm.MiscOpcode.data_drop)); |
| 2815 | appendReservedUleb32(binary_bytes, @intCast(segment_index)); |
| 2816 | } |
| 2817 | |
| 2818 | // End of the function body |
| 2819 | binary_bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.end)); |
| 2820 | } |
| 2821 | |
| 2822 | fn emitInitTlsFunction(wasm: *const Wasm, bytes: *ArrayList(u8)) Allocator.Error!void { |
| 2823 | const comp = wasm.base.comp; |
| 2824 | const gpa = comp.gpa; |
| 2825 | |
| 2826 | assert(comp.config.shared_memory); |
| 2827 | |
| 2828 | try bytes.ensureUnusedCapacity(gpa, 5 * 10 + 8); |
| 2829 | |
| 2830 | appendReservedUleb32(bytes, 0); // no locals |
| 2831 | |
| 2832 | // If there's a TLS segment, initialize it during runtime using the bulk-memory feature |
| 2833 | // TLS segment is always the first one due to how we sort the data segments. |
| 2834 | const data_segments = wasm.flush_buffer.data_segments.keys(); |
| 2835 | if (data_segments.len > 0 and data_segments[0].isTls(wasm)) { |
| 2836 | const start_addr = wasm.flush_buffer.data_segments.values()[0]; |
| 2837 | const end_addr = wasm.flush_buffer.data_segment_groups.items[0].end_addr; |
| 2838 | const group_size = end_addr - start_addr; |
| 2839 | const data_segment_index = 0; |
| 2840 | |
| 2841 | const param_local: u32 = 0; |
| 2842 | |
| 2843 | bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.local_get)); |
| 2844 | appendReservedUleb32(bytes, param_local); |
| 2845 | |
| 2846 | const tls_base_global_index: Wasm.GlobalIndex = @fromBackingInt(@intCast(wasm.globals.getIndex(.__tls_base).?)); |
| 2847 | bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.global_set)); |
| 2848 | appendReservedUleb32(bytes, @backingInt(tls_base_global_index)); |
| 2849 | |
| 2850 | // load stack values for the bulk-memory operation |
| 2851 | { |
| 2852 | bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.local_get)); |
| 2853 | appendReservedUleb32(bytes, param_local); |
| 2854 | |
| 2855 | bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.i32_const)); |
| 2856 | appendReservedUleb32(bytes, 0); //segment offset |
| 2857 | |
| 2858 | bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.i32_const)); |
| 2859 | appendReservedUleb32(bytes, group_size); //segment offset |
| 2860 | } |
| 2861 | |
| 2862 | // perform the bulk-memory operation to initialize the data segment |
| 2863 | bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.misc_prefix)); |
| 2864 | appendReservedUleb32(bytes, @backingInt(std.wasm.MiscOpcode.memory_init)); |
| 2865 | // segment immediate |
| 2866 | appendReservedUleb32(bytes, data_segment_index); |
| 2867 | // memory index immediate (always 0) |
| 2868 | appendReservedUleb32(bytes, 0); |
| 2869 | } |
| 2870 | |
| 2871 | // If we have to perform any TLS relocations, call the corresponding function |
| 2872 | // which performs all runtime TLS relocations. This is a synthetic function, |
| 2873 | // generated by the linker. |
| 2874 | if (wasm.functions.getIndex(.__wasm_apply_global_tls_relocs)) |function_index| { |
| 2875 | const output_function_index: Wasm.OutputFunctionIndex = .fromFunctionIndex(wasm, @fromBackingInt(@intCast(function_index))); |
| 2876 | bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.call)); |
| 2877 | appendReservedUleb32(bytes, @backingInt(output_function_index)); |
| 2878 | } |
| 2879 | |
| 2880 | bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.end)); |
| 2881 | } |
| 2882 | |
| 2883 | fn emitStartSection(gpa: Allocator, bytes: *ArrayList(u8), i: Wasm.OutputFunctionIndex) !void { |
| 2884 | const header_offset = try reserveVecSectionHeader(gpa, bytes); |
| 2885 | replaceVecSectionHeader(bytes, header_offset, .start, @backingInt(i)); |
| 2886 | } |
| 2887 | |
| 2888 | fn emitTagIndexFunction( |
| 2889 | wasm: *Wasm, |
| 2890 | code: *ArrayList(u8), |
| 2891 | enum_type_ip: InternPool.Index, |
| 2892 | ) !void { |
| 2893 | const comp = wasm.base.comp; |
| 2894 | const gpa = comp.gpa; |
| 2895 | const zcu = comp.zcu.?; |
| 2896 | const ip = &zcu.intern_pool; |
| 2897 | const enum_type = ip.loadEnumType(enum_type_ip); |
| 2898 | const tag_values = enum_type.field_values.get(ip); |
| 2899 | |
| 2900 | if (tag_values.len == 0) { |
| 2901 | // Auto-numbered |
| 2902 | |
| 2903 | const len = enum_type.field_names.len; |
| 2904 | |
| 2905 | try code.ensureUnusedCapacity(gpa, 13 + 5 * 2); |
| 2906 | |
| 2907 | appendReservedUleb32(code, 0); // no locals |
| 2908 | |
| 2909 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.block)); |
| 2910 | code.appendAssumeCapacity(@backingInt(std.wasm.BlockType.empty)); |
| 2911 | |
| 2912 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.local_get)); |
| 2913 | appendReservedUleb32(code, 0); |
| 2914 | |
| 2915 | appendReservedI32Const(code, len); |
| 2916 | |
| 2917 | // if < len -> break out of block |
| 2918 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.i32_lt_u)); |
| 2919 | |
| 2920 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.br_if)); |
| 2921 | appendReservedUleb32(code, 0); |
| 2922 | |
| 2923 | // invalid -> return -1 |
| 2924 | appendReservedI32Const(code, ~@as(u32, 0)); |
| 2925 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.@"return")); |
| 2926 | |
| 2927 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.end)); |
| 2928 | |
| 2929 | // valid -> return input |
| 2930 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.local_get)); |
| 2931 | appendReservedUleb32(code, 0); |
| 2932 | |
| 2933 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.end)); |
| 2934 | |
| 2935 | return; |
| 2936 | } |
| 2937 | |
| 2938 | const int_info = Zcu.Type.intInfo(.fromInterned(enum_type.int_tag_type), zcu); |
| 2939 | const is_big_int = int_info.bits > 64; |
| 2940 | |
| 2941 | try code.ensureUnusedCapacity( |
| 2942 | gpa, |
| 2943 | (7 + tag_values.len * 6) * @sizeOf(std.wasm.Opcode) + |
| 2944 | (1 + tag_values.len * 1) * @sizeOf(std.wasm.BlockType) + |
| 2945 | (6 + tag_values.len * 3) * 5 + // appendReservedUleb32 |
| 2946 | (tag_values.len * 2) * 11, // appendReservedI32Const / appendReservedI64Const |
| 2947 | ); |
| 2948 | |
| 2949 | appendReservedUleb32(code, 0); // no locals |
| 2950 | |
| 2951 | for (tag_values, 0..) |tag_value, tag_index| { |
| 2952 | // block for this if case |
| 2953 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.block)); |
| 2954 | code.appendAssumeCapacity(@backingInt(std.wasm.BlockType.empty)); |
| 2955 | |
| 2956 | const val: Zcu.Value = .fromInterned(tag_value); |
| 2957 | if (is_big_int) { |
| 2958 | var val_space: Zcu.Value.BigIntSpace = undefined; |
| 2959 | const val_bigint = val.toBigInt(&val_space, zcu); |
| 2960 | const num_limbs = (int_info.bits + 63) / 64; |
| 2961 | |
| 2962 | const limbs = try gpa.alloc(u64, num_limbs); |
| 2963 | defer gpa.free(limbs); |
| 2964 | val_bigint.writeTwosComplement(@ptrCast(limbs), .little); |
| 2965 | |
| 2966 | try code.ensureUnusedCapacity(gpa, 35 * num_limbs); |
| 2967 | |
| 2968 | for (0..num_limbs) |limb_index| { |
| 2969 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.local_get)); |
| 2970 | appendReservedUleb32(code, 0); |
| 2971 | |
| 2972 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.i64_load)); |
| 2973 | appendReservedUleb32(code, @ctz(@as(u32, 8))); |
| 2974 | appendReservedUleb32(code, @intCast(limb_index * 8)); |
| 2975 | |
| 2976 | appendReservedI64Const(code, limbs[limb_index]); |
| 2977 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.i64_ne)); |
| 2978 | |
| 2979 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.br_if)); |
| 2980 | appendReservedUleb32(code, 0); |
| 2981 | } |
| 2982 | } else { |
| 2983 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.local_get)); |
| 2984 | appendReservedUleb32(code, 0); |
| 2985 | |
| 2986 | switch (int_info.bits) { |
| 2987 | 0...32 => { |
| 2988 | const x: u32 = switch (int_info.signedness) { |
| 2989 | .signed => @bitCast(@as(i32, @intCast(val.toSignedInt(zcu)))), |
| 2990 | .unsigned => @intCast(val.toUnsignedInt(zcu)), |
| 2991 | }; |
| 2992 | appendReservedI32Const(code, x); |
| 2993 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.i32_ne)); |
| 2994 | }, |
| 2995 | 33...64 => { |
| 2996 | const x: u64 = switch (int_info.signedness) { |
| 2997 | .signed => @bitCast(val.toSignedInt(zcu)), |
| 2998 | .unsigned => val.toUnsignedInt(zcu), |
| 2999 | }; |
| 3000 | appendReservedI64Const(code, x); |
| 3001 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.i64_ne)); |
| 3002 | }, |
| 3003 | else => unreachable, |
| 3004 | } |
| 3005 | |
| 3006 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.br_if)); |
| 3007 | appendReservedUleb32(code, 0); |
| 3008 | } |
| 3009 | |
| 3010 | appendReservedI32Const(code, @intCast(tag_index)); |
| 3011 | |
| 3012 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.@"return")); |
| 3013 | // end the block for this case |
| 3014 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.end)); |
| 3015 | } |
| 3016 | |
| 3017 | appendReservedI32Const(code, ~@as(u32, 0)); |
| 3018 | |
| 3019 | code.appendAssumeCapacity(@backingInt(std.wasm.Opcode.end)); |
| 3020 | } |
| 3021 | |
| 3022 | /// Writes an unsigned 32-bit integer as a LEB128-encoded 'i32.const' value. |
| 3023 | fn appendReservedI32Const(bytes: *ArrayList(u8), val: u32) void { |
| 3024 | bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.i32_const)); |
| 3025 | var w: std.Io.Writer = .fromArrayList(bytes); |
| 3026 | defer bytes.* = w.toArrayList(); |
| 3027 | return w.writeSleb128(@as(i32, @bitCast(val))) catch |err| switch (err) { |
| 3028 | error.WriteFailed => unreachable, |
| 3029 | }; |
| 3030 | } |
| 3031 | |
| 3032 | /// Writes an unsigned 64-bit integer as a LEB128-encoded 'i64.const' value. |
| 3033 | fn appendReservedI64Const(bytes: *ArrayList(u8), val: u64) void { |
| 3034 | bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.i64_const)); |
| 3035 | var w: std.Io.Writer = .fromArrayList(bytes); |
| 3036 | defer bytes.* = w.toArrayList(); |
| 3037 | return w.writeSleb128(@as(i64, @bitCast(val))) catch |err| switch (err) { |
| 3038 | error.WriteFailed => unreachable, |
| 3039 | }; |
| 3040 | } |
| 3041 | |
| 3042 | fn appendReservedUleb32(bytes: *ArrayList(u8), val: u32) void { |
| 3043 | var w: std.Io.Writer = .fromArrayList(bytes); |
| 3044 | defer bytes.* = w.toArrayList(); |
| 3045 | return w.writeUleb128(val) catch |err| switch (err) { |
| 3046 | error.WriteFailed => unreachable, |
| 3047 | }; |
| 3048 | } |
| 3049 | |
| 3050 | fn appendGlobal(gpa: Allocator, bytes: *ArrayList(u8), mutable: u8, val: u64, is64: bool) Allocator.Error!void { |
| 3051 | try bytes.ensureUnusedCapacity(gpa, if (is64) 14 else 9); |
| 3052 | bytes.appendAssumeCapacity(@backingInt(@as(std.wasm.Valtype, if (is64) .i64 else .i32))); |
| 3053 | bytes.appendAssumeCapacity(mutable); |
| 3054 | if (is64) { |
| 3055 | appendReservedI64Const(bytes, val); |
| 3056 | } else { |
| 3057 | appendReservedI32Const(bytes, @intCast(val)); |
| 3058 | } |
| 3059 | bytes.appendAssumeCapacity(@backingInt(std.wasm.Opcode.end)); |
| 3060 | } |
| 3061 | |
| 3062 | fn appendLeb128(gpa: Allocator, bytes: *ArrayList(u8), value: anytype) Allocator.Error!void { |
| 3063 | var aw: std.Io.Writer.Allocating = .fromArrayList(gpa, bytes); |
| 3064 | defer bytes.* = aw.toArrayList(); |
| 3065 | return aw.writer.writeLeb128(value) catch |err| switch (err) { |
| 3066 | error.WriteFailed => return error.OutOfMemory, |
| 3067 | }; |
| 3068 | } |
| 3069 | |
| 3070 | fn appendReservedLeb128(bytes: *ArrayList(u8), value: anytype) void { |
| 3071 | var w: std.Io.Writer = .fromArrayList(bytes); |
| 3072 | defer bytes.* = w.toArrayList(); |
| 3073 | return w.writeLeb128(value) catch |err| switch (err) { |
| 3074 | error.WriteFailed => unreachable, |
| 3075 | }; |
| 3076 | } |