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
4const Flush = @This();
5const Wasm = @import("../Wasm.zig");
6const Object = @import("Object.zig");
7const Zcu = @import("../../Zcu.zig");
8const Alignment = Wasm.Alignment;
9const String = Wasm.String;
10const InternPool = @import("../../InternPool.zig");
11const Mir = @import("../../codegen/wasm/Mir.zig");
12
13const build_options = @import("build_options");
14
15const std = @import("std");
16const Allocator = std.mem.Allocator;
17const mem = std.mem;
18const leb = std.leb;
19const log = std.log.scoped(.link);
20const assert = std.debug.assert;
21const 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.
26data_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.
30data_segment_groups: ArrayList(DataSegmentGroup) = .empty,
31
32binary_bytes: ArrayList(u8) = .empty,
33missing_exports: std.array_hash_map.Auto(String, void) = .empty,
34function_imports: std.array_hash_map.Auto(String, Wasm.FunctionImportId) = .empty,
35intrinsic_function_imports: std.array_hash_map.Auto(String, Wasm.FunctionType.Index) = .empty,
36/// Function aliases emitted after function symbols.
37function_export_symbols: std.array_hash_map.Auto(String, FunctionExportSymbol) = .empty,
38global_imports: std.array_hash_map.Auto(String, Wasm.GlobalImportId) = .empty,
39data_imports: std.array_hash_map.Auto(String, Wasm.DataImportId) = .empty,
40/// Data aliases emitted after data symbols.
41data_exports: std.array_hash_map.Auto(String, DataExportSymbol) = .empty,
42
43indirect_function_table: std.array_hash_map.Auto(Wasm.OutputFunctionIndex, void) = .empty,
44sorted_init_funcs: std.ArrayList(Wasm.InitFunc) = .empty,
45
46/// A subset of the full interned function type list created only during flush.
47func_types: std.array_hash_map.Auto(Wasm.FunctionType.Index, void) = .empty,
48
49enum_tag_name_table: std.array_hash_map.Auto(InternPool.Index, u32) = .empty,
50
51code_relocs: std.ArrayList(Relocation) = .empty,
52data_relocs: std.ArrayList(Relocation) = .empty,
53
54/// For debug purposes only.
55memory_layout_finished: bool = false,
56
57virtual_addrs: VirtualAddrs = undefined,
58
59/// Index into `func_types`.
60pub 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.
69const 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.
103const 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`.
137const 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
161const SymbolTableOffsets = struct {
162 function: u32,
163 data: u32,
164 global: u32,
165 table: u32,
166};
167
168const FunctionExportSymbol = struct {
169 function_index: Wasm.FunctionIndex,
170 flags: Wasm.SymbolFlags,
171};
172
173const DataExportSymbol = struct {
174 resolution: Wasm.ObjectDataImport.Resolution,
175 flags: Wasm.SymbolFlags,
176};
177
178const 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
194const DataSegmentGroup = struct {
195 first_segment: Wasm.DataSegmentId,
196 end_addr: u32,
197};
198
199pub 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
216pub 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
236pub 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
1743const 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
1755fn 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
1842fn 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
1873fn 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
1886fn 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
1935fn 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
1941test 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
1949fn 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
1968const section_header_reserve_size = 1 + 5 + 5;
1969const section_header_size = 5 + 1;
1970
1971fn 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
1976fn 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
1991fn 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
1996fn writeCustomSectionHeader(bytes: *ArrayList(u8), offset: u32) void {
1997 return replaceHeader(bytes, offset, 0); // 0 = 'custom' section
1998}
1999
2000fn 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
2009const max_size_encoding = 5;
2010
2011fn 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
2016fn 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
2024fn 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
2034fn 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
2053fn 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
2079pub 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
2085fn 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
2092fn 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
2115fn 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
2153fn 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
2245fn 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
2278fn 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
2465fn 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
2482fn 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
2554fn reloc_u32_table_index(code: []u8, i: IndirectFunctionTableIndex) void {
2555 mem.writeInt(u32, code[0..4], i.toAbi(), .little);
2556}
2557
2558fn reloc_u64_table_index(code: []u8, i: IndirectFunctionTableIndex) void {
2559 mem.writeInt(u64, code[0..8], i.toAbi(), .little);
2560}
2561
2562fn reloc_sleb_table_index(code: []u8, i: IndirectFunctionTableIndex) void {
2563 leb.writeSignedFixed(5, code[0..5], i.toAbi());
2564}
2565
2566fn reloc_sleb64_table_index(code: []u8, i: IndirectFunctionTableIndex) void {
2567 leb.writeSignedFixed(11, code[0..11], i.toAbi());
2568}
2569
2570fn reloc_u32_function(code: []u8, function: Wasm.OutputFunctionIndex) void {
2571 mem.writeInt(u32, code[0..4], @backingInt(function), .little);
2572}
2573
2574fn reloc_leb_function(code: []u8, function: Wasm.OutputFunctionIndex) void {
2575 leb.writeUnsignedFixed(5, code[0..5], @backingInt(function));
2576}
2577
2578fn reloc_u32_global(code: []u8, global: Wasm.GlobalIndex) void {
2579 mem.writeInt(u32, code[0..4], @backingInt(global), .little);
2580}
2581
2582fn reloc_leb_global(code: []u8, global: Wasm.GlobalIndex) void {
2583 leb.writeUnsignedFixed(5, code[0..5], @backingInt(global));
2584}
2585
2586const 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
2621fn reloc_u32_addr(code: []u8, ra: RelocAddr) void {
2622 mem.writeInt(u32, code[0..4], ra.addr, .little);
2623}
2624
2625fn reloc_u64_addr(code: []u8, ra: RelocAddr) void {
2626 mem.writeInt(u64, code[0..8], ra.addr, .little);
2627}
2628
2629fn reloc_leb_addr(code: []u8, ra: RelocAddr) void {
2630 leb.writeUnsignedFixed(5, code[0..5], ra.addr);
2631}
2632
2633fn reloc_leb64_addr(code: []u8, ra: RelocAddr) void {
2634 leb.writeUnsignedFixed(11, code[0..11], ra.addr);
2635}
2636
2637fn reloc_sleb_addr(code: []u8, ra: RelocAddr) void {
2638 leb.writeSignedFixed(5, code[0..5], ra.addr);
2639}
2640
2641fn reloc_sleb64_addr(code: []u8, ra: RelocAddr) void {
2642 leb.writeSignedFixed(11, code[0..11], ra.addr);
2643}
2644
2645fn reloc_leb_table(code: []u8, table: Wasm.TableIndex) void {
2646 leb.writeUnsignedFixed(5, code[0..5], @backingInt(table));
2647}
2648
2649fn reloc_leb_type(code: []u8, index: FuncTypeIndex) void {
2650 leb.writeUnsignedFixed(5, code[0..5], @backingInt(index));
2651}
2652
2653fn 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
2677fn 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
2822fn 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
2883fn 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
2888fn 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.
3023fn 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.
3033fn 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
3042fn 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
3050fn 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
3062fn 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
3070fn 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}