master
1const Object = @This();
2
3const Wasm = @import("../Wasm.zig");
4const Alignment = Wasm.Alignment;
5
6const std = @import("std");
7const Allocator = std.mem.Allocator;
8const Path = std.Build.Cache.Path;
9const log = std.log.scoped(.object);
10const assert = std.debug.assert;
11
12/// Wasm spec version used for this `Object`
13version: u32,
14/// For error reporting purposes only.
15/// Name (read path) of the object or archive file.
16path: Path,
17/// For error reporting purposes only.
18/// If this represents an object in an archive, it's the basename of the
19/// object, and path refers to the archive.
20archive_member_name: Wasm.OptionalString,
21/// Represents the function ID that must be called on startup.
22/// This is `null` by default as runtimes may determine the startup
23/// function themselves. This is essentially legacy.
24start_function: Wasm.OptionalObjectFunctionIndex,
25/// A slice of features that tell the linker what features are mandatory, used
26/// (or therefore missing) and must generate an error when another object uses
27/// features that are not supported by the other.
28features: Wasm.Feature.Set,
29/// Points into `Wasm.object_functions`
30functions: RelativeSlice,
31/// Points into `Wasm.object_function_imports`
32function_imports: RelativeSlice,
33/// Points into `Wasm.object_global_imports`
34global_imports: RelativeSlice,
35/// Points into `Wasm.object_table_imports`
36table_imports: RelativeSlice,
37// Points into `Wasm.object_data_imports`
38data_imports: RelativeSlice,
39/// Points into Wasm object_custom_segments
40custom_segments: RelativeSlice,
41/// Points into Wasm object_init_funcs
42init_funcs: RelativeSlice,
43/// Points into Wasm object_comdats
44comdats: RelativeSlice,
45/// Guaranteed to be non-null when functions has nonzero length.
46code_section_index: ?Wasm.ObjectSectionIndex,
47/// Guaranteed to be non-null when globals has nonzero length.
48global_section_index: ?Wasm.ObjectSectionIndex,
49/// Guaranteed to be non-null when data segments has nonzero length.
50data_section_index: ?Wasm.ObjectSectionIndex,
51is_included: bool,
52
53pub const RelativeSlice = struct {
54 off: u32,
55 len: u32,
56};
57
58pub const SegmentInfo = struct {
59 name: Wasm.String,
60 flags: Flags,
61
62 /// Matches the ABI.
63 pub const Flags = packed struct(u32) {
64 /// Signals that the segment contains only null terminated strings allowing
65 /// the linker to perform merging.
66 strings: bool,
67 /// The segment contains thread-local data. This means that a unique copy
68 /// of this segment will be created for each thread.
69 tls: bool,
70 /// If the object file is included in the final link, the segment should be
71 /// retained in the final output regardless of whether it is used by the
72 /// program.
73 retain: bool,
74 alignment: Alignment,
75
76 _: u23 = 0,
77 };
78};
79
80pub const FunctionImport = struct {
81 module_name: Wasm.String,
82 name: Wasm.String,
83 function_index: ScratchSpace.FuncTypeIndex,
84};
85
86pub const GlobalImport = struct {
87 module_name: Wasm.String,
88 name: Wasm.String,
89 valtype: std.wasm.Valtype,
90 mutable: bool,
91};
92
93pub const TableImport = struct {
94 module_name: Wasm.String,
95 name: Wasm.String,
96 limits_min: u32,
97 limits_max: u32,
98 limits_has_max: bool,
99 limits_is_shared: bool,
100 ref_type: std.wasm.RefType,
101};
102
103pub const DataSegmentFlags = enum(u32) { active, passive, active_memidx };
104
105pub const SubsectionType = enum(u8) {
106 segment_info = 5,
107 init_funcs = 6,
108 comdat_info = 7,
109 symbol_table = 8,
110};
111
112/// Specified by https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md
113pub const RelocationType = enum(u8) {
114 function_index_leb = 0,
115 table_index_sleb = 1,
116 table_index_i32 = 2,
117 memory_addr_leb = 3,
118 memory_addr_sleb = 4,
119 memory_addr_i32 = 5,
120 type_index_leb = 6,
121 global_index_leb = 7,
122 function_offset_i32 = 8,
123 section_offset_i32 = 9,
124 event_index_leb = 10,
125 memory_addr_rel_sleb = 11,
126 table_index_rel_sleb = 12,
127 global_index_i32 = 13,
128 memory_addr_leb64 = 14,
129 memory_addr_sleb64 = 15,
130 memory_addr_i64 = 16,
131 memory_addr_rel_sleb64 = 17,
132 table_index_sleb64 = 18,
133 table_index_i64 = 19,
134 table_number_leb = 20,
135 memory_addr_tls_sleb = 21,
136 function_offset_i64 = 22,
137 memory_addr_locrel_i32 = 23,
138 table_index_rel_sleb64 = 24,
139 memory_addr_tls_sleb64 = 25,
140 function_index_i32 = 26,
141};
142
143pub const Symbol = struct {
144 flags: Wasm.SymbolFlags,
145 name: Wasm.OptionalString,
146 pointee: Pointee,
147
148 /// https://github.com/WebAssembly/tool-conventions/blob/df8d737539eb8a8f446ba5eab9dc670c40dfb81e/Linking.md#symbol-table-subsection
149 const Tag = enum(u8) {
150 function,
151 data,
152 global,
153 section,
154 event,
155 table,
156 };
157
158 const Pointee = union(enum) {
159 function: Wasm.ObjectFunctionIndex,
160 function_import: ScratchSpace.FuncImportIndex,
161 data: Wasm.ObjectData.Index,
162 data_import: void,
163 global: Wasm.ObjectGlobalIndex,
164 global_import: ScratchSpace.GlobalImportIndex,
165 section: Wasm.ObjectSectionIndex,
166 table: Wasm.ObjectTableIndex,
167 table_import: ScratchSpace.TableImportIndex,
168 };
169};
170
171pub const ScratchSpace = struct {
172 func_types: std.ArrayList(Wasm.FunctionType.Index) = .empty,
173 func_type_indexes: std.ArrayList(FuncTypeIndex) = .empty,
174 func_imports: std.ArrayList(FunctionImport) = .empty,
175 global_imports: std.ArrayList(GlobalImport) = .empty,
176 table_imports: std.ArrayList(TableImport) = .empty,
177 symbol_table: std.ArrayList(Symbol) = .empty,
178 segment_info: std.ArrayList(SegmentInfo) = .empty,
179 exports: std.ArrayList(Export) = .empty,
180
181 const Export = struct {
182 name: Wasm.String,
183 pointee: Pointee,
184
185 const Pointee = union(std.wasm.ExternalKind) {
186 function: Wasm.ObjectFunctionIndex,
187 table: Wasm.ObjectTableIndex,
188 memory: Wasm.ObjectMemory.Index,
189 global: Wasm.ObjectGlobalIndex,
190 };
191 };
192
193 /// Index into `func_imports`.
194 const FuncImportIndex = enum(u32) {
195 _,
196
197 fn ptr(index: FuncImportIndex, ss: *const ScratchSpace) *FunctionImport {
198 return &ss.func_imports.items[@intFromEnum(index)];
199 }
200 };
201
202 /// Index into `global_imports`.
203 const GlobalImportIndex = enum(u32) {
204 _,
205
206 fn ptr(index: GlobalImportIndex, ss: *const ScratchSpace) *GlobalImport {
207 return &ss.global_imports.items[@intFromEnum(index)];
208 }
209 };
210
211 /// Index into `table_imports`.
212 const TableImportIndex = enum(u32) {
213 _,
214
215 fn ptr(index: TableImportIndex, ss: *const ScratchSpace) *TableImport {
216 return &ss.table_imports.items[@intFromEnum(index)];
217 }
218 };
219
220 /// Index into `func_types`.
221 const FuncTypeIndex = enum(u32) {
222 _,
223
224 fn ptr(index: FuncTypeIndex, ss: *const ScratchSpace) *Wasm.FunctionType.Index {
225 return &ss.func_types.items[@intFromEnum(index)];
226 }
227 };
228
229 pub fn deinit(ss: *ScratchSpace, gpa: Allocator) void {
230 ss.exports.deinit(gpa);
231 ss.func_types.deinit(gpa);
232 ss.func_type_indexes.deinit(gpa);
233 ss.func_imports.deinit(gpa);
234 ss.global_imports.deinit(gpa);
235 ss.table_imports.deinit(gpa);
236 ss.symbol_table.deinit(gpa);
237 ss.segment_info.deinit(gpa);
238 ss.* = undefined;
239 }
240
241 fn clear(ss: *ScratchSpace) void {
242 ss.exports.clearRetainingCapacity();
243 ss.func_types.clearRetainingCapacity();
244 ss.func_type_indexes.clearRetainingCapacity();
245 ss.func_imports.clearRetainingCapacity();
246 ss.global_imports.clearRetainingCapacity();
247 ss.table_imports.clearRetainingCapacity();
248 ss.symbol_table.clearRetainingCapacity();
249 ss.segment_info.clearRetainingCapacity();
250 }
251};
252
253pub fn parse(
254 wasm: *Wasm,
255 bytes: []const u8,
256 path: Path,
257 archive_member_name: ?[]const u8,
258 host_name: Wasm.OptionalString,
259 ss: *ScratchSpace,
260 must_link: bool,
261 gc_sections: bool,
262) anyerror!Object {
263 const comp = wasm.base.comp;
264 const gpa = comp.gpa;
265 const diags = &comp.link_diags;
266
267 var pos: usize = 0;
268
269 if (!std.mem.eql(u8, bytes[0..std.wasm.magic.len], &std.wasm.magic)) return error.BadObjectMagic;
270 pos += std.wasm.magic.len;
271
272 const version = std.mem.readInt(u32, bytes[pos..][0..4], .little);
273 pos += 4;
274
275 const data_segment_start: u32 = @intCast(wasm.object_data_segments.items.len);
276 const custom_segment_start: u32 = @intCast(wasm.object_custom_segments.entries.len);
277 const functions_start: u32 = @intCast(wasm.object_functions.items.len);
278 const tables_start: u32 = @intCast(wasm.object_tables.items.len);
279 const memories_start: u32 = @intCast(wasm.object_memories.items.len);
280 const globals_start: u32 = @intCast(wasm.object_globals.items.len);
281 const init_funcs_start: u32 = @intCast(wasm.object_init_funcs.items.len);
282 const comdats_start: u32 = @intCast(wasm.object_comdats.items.len);
283 const function_imports_start: u32 = @intCast(wasm.object_function_imports.entries.len);
284 const global_imports_start: u32 = @intCast(wasm.object_global_imports.entries.len);
285 const table_imports_start: u32 = @intCast(wasm.object_table_imports.entries.len);
286 const data_imports_start: u32 = @intCast(wasm.object_data_imports.entries.len);
287 const local_section_index_base = wasm.object_total_sections;
288 const object_index: Wasm.ObjectIndex = @enumFromInt(wasm.objects.items.len);
289 const source_location: Wasm.SourceLocation = .fromObject(object_index, wasm);
290
291 ss.clear();
292
293 var start_function: Wasm.OptionalObjectFunctionIndex = .none;
294 var opt_features: ?Wasm.Feature.Set = null;
295 var saw_linking_section = false;
296 var has_tls = false;
297 var table_import_symbol_count: usize = 0;
298 var code_section_index: ?Wasm.ObjectSectionIndex = null;
299 var global_section_index: ?Wasm.ObjectSectionIndex = null;
300 var data_section_index: ?Wasm.ObjectSectionIndex = null;
301 while (pos < bytes.len) : (wasm.object_total_sections += 1) {
302 const section_index: Wasm.ObjectSectionIndex = @enumFromInt(wasm.object_total_sections);
303
304 const section_tag: std.wasm.Section = @enumFromInt(bytes[pos]);
305 pos += 1;
306
307 const len, pos = readLeb(u32, bytes, pos);
308 const section_end = pos + len;
309 switch (section_tag) {
310 .custom => {
311 const section_name, pos = readBytes(bytes, pos);
312 if (std.mem.eql(u8, section_name, "linking")) {
313 saw_linking_section = true;
314 const section_version, pos = readLeb(u32, bytes, pos);
315 log.debug("link meta data version: {d}", .{section_version});
316 if (section_version != 2) return error.UnsupportedVersion;
317 while (pos < section_end) {
318 const sub_type, pos = readLeb(u8, bytes, pos);
319 log.debug("found subsection: {s}", .{@tagName(@as(SubsectionType, @enumFromInt(sub_type)))});
320 const payload_len, pos = readLeb(u32, bytes, pos);
321 if (payload_len == 0) break;
322
323 const count, pos = readLeb(u32, bytes, pos);
324
325 switch (@as(SubsectionType, @enumFromInt(sub_type))) {
326 .segment_info => {
327 for (try ss.segment_info.addManyAsSlice(gpa, count)) |*segment| {
328 const name, pos = readBytes(bytes, pos);
329 const alignment, pos = readLeb(u32, bytes, pos);
330 const flags_u32, pos = readLeb(u32, bytes, pos);
331 const flags: SegmentInfo.Flags = @bitCast(flags_u32);
332 const tls = flags.tls or
333 // Supports legacy object files that specified
334 // being TLS by the name instead of the TLS flag.
335 std.mem.startsWith(u8, name, ".tdata") or
336 std.mem.startsWith(u8, name, ".tbss");
337 has_tls = has_tls or tls;
338 segment.* = .{
339 .name = try wasm.internString(name),
340 .flags = .{
341 .strings = flags.strings,
342 .tls = tls,
343 .alignment = @enumFromInt(alignment),
344 .retain = flags.retain,
345 },
346 };
347 }
348 },
349 .init_funcs => {
350 for (try wasm.object_init_funcs.addManyAsSlice(gpa, count)) |*func| {
351 const priority, pos = readLeb(u32, bytes, pos);
352 const symbol_index, pos = readLeb(u32, bytes, pos);
353 if (symbol_index > ss.symbol_table.items.len)
354 return diags.failParse(path, "init_funcs before symbol table", .{});
355 const sym = &ss.symbol_table.items[symbol_index];
356 if (sym.pointee != .function) {
357 return diags.failParse(path, "init_func symbol '{s}' not a function", .{
358 sym.name.slice(wasm).?,
359 });
360 } else if (sym.flags.undefined) {
361 return diags.failParse(path, "init_func symbol '{s}' is an import", .{
362 sym.name.slice(wasm).?,
363 });
364 }
365 func.* = .{
366 .priority = priority,
367 .function_index = sym.pointee.function,
368 };
369 }
370 },
371 .comdat_info => {
372 for (try wasm.object_comdats.addManyAsSlice(gpa, count)) |*comdat| {
373 const name, pos = readBytes(bytes, pos);
374 const flags, pos = readLeb(u32, bytes, pos);
375 if (flags != 0) return error.UnexpectedComdatFlags;
376 const symbol_count, pos = readLeb(u32, bytes, pos);
377 const start_off: u32 = @intCast(wasm.object_comdat_symbols.len);
378 try wasm.object_comdat_symbols.ensureUnusedCapacity(gpa, symbol_count);
379 for (0..symbol_count) |_| {
380 const kind, pos = readEnum(Wasm.Comdat.Symbol.Type, bytes, pos);
381 const index, pos = readLeb(u32, bytes, pos);
382 if (true) @panic("TODO rebase index depending on kind");
383 wasm.object_comdat_symbols.appendAssumeCapacity(.{
384 .kind = kind,
385 .index = index,
386 });
387 }
388 comdat.* = .{
389 .name = try wasm.internString(name),
390 .flags = flags,
391 .symbols = .{
392 .off = start_off,
393 .len = @intCast(wasm.object_comdat_symbols.len - start_off),
394 },
395 };
396 }
397 },
398 .symbol_table => {
399 for (try ss.symbol_table.addManyAsSlice(gpa, count)) |*symbol| {
400 const tag, pos = readEnum(Symbol.Tag, bytes, pos);
401 const flags, pos = readLeb(u32, bytes, pos);
402 symbol.* = .{
403 .flags = @bitCast(flags),
404 .name = .none,
405 .pointee = undefined,
406 };
407 symbol.flags.initZigSpecific(must_link, gc_sections);
408
409 switch (tag) {
410 .data => {
411 const name, pos = readBytes(bytes, pos);
412 const interned_name = try wasm.internString(name);
413 symbol.name = interned_name.toOptional();
414 if (symbol.flags.undefined) {
415 symbol.pointee = .data_import;
416 } else {
417 const segment_index, pos = readLeb(u32, bytes, pos);
418 const segment_offset, pos = readLeb(u32, bytes, pos);
419 const size, pos = readLeb(u32, bytes, pos);
420 try wasm.object_datas.append(gpa, .{
421 .segment = @enumFromInt(data_segment_start + segment_index),
422 .offset = segment_offset,
423 .size = size,
424 .name = interned_name,
425 .flags = symbol.flags,
426 });
427 symbol.pointee = .{
428 .data = @enumFromInt(wasm.object_datas.items.len - 1),
429 };
430 }
431 },
432 .section => {
433 const local_section, pos = readLeb(u32, bytes, pos);
434 const section: Wasm.ObjectSectionIndex = @enumFromInt(local_section_index_base + local_section);
435 symbol.pointee = .{ .section = section };
436 },
437
438 .function => {
439 const local_index, pos = readLeb(u32, bytes, pos);
440 if (symbol.flags.undefined) {
441 const function_import: ScratchSpace.FuncImportIndex = @enumFromInt(local_index);
442 symbol.pointee = .{ .function_import = function_import };
443 if (symbol.flags.explicit_name) {
444 const name, pos = readBytes(bytes, pos);
445 symbol.name = (try wasm.internString(name)).toOptional();
446 } else {
447 symbol.name = function_import.ptr(ss).name.toOptional();
448 }
449 } else {
450 symbol.pointee = .{ .function = @enumFromInt(functions_start + (local_index - ss.func_imports.items.len)) };
451 const name, pos = readBytes(bytes, pos);
452 symbol.name = (try wasm.internString(name)).toOptional();
453 }
454 },
455 .global => {
456 const local_index, pos = readLeb(u32, bytes, pos);
457 if (symbol.flags.undefined) {
458 const global_import: ScratchSpace.GlobalImportIndex = @enumFromInt(local_index);
459 symbol.pointee = .{ .global_import = global_import };
460 if (symbol.flags.explicit_name) {
461 const name, pos = readBytes(bytes, pos);
462 symbol.name = (try wasm.internString(name)).toOptional();
463 } else {
464 symbol.name = global_import.ptr(ss).name.toOptional();
465 }
466 } else {
467 symbol.pointee = .{ .global = @enumFromInt(globals_start + (local_index - ss.global_imports.items.len)) };
468 const name, pos = readBytes(bytes, pos);
469 symbol.name = (try wasm.internString(name)).toOptional();
470 }
471 },
472 .table => {
473 const local_index, pos = readLeb(u32, bytes, pos);
474 if (symbol.flags.undefined) {
475 table_import_symbol_count += 1;
476 const table_import: ScratchSpace.TableImportIndex = @enumFromInt(local_index);
477 symbol.pointee = .{ .table_import = table_import };
478 if (symbol.flags.explicit_name) {
479 const name, pos = readBytes(bytes, pos);
480 symbol.name = (try wasm.internString(name)).toOptional();
481 } else {
482 symbol.name = table_import.ptr(ss).name.toOptional();
483 }
484 } else {
485 symbol.pointee = .{ .table = @enumFromInt(tables_start + (local_index - ss.table_imports.items.len)) };
486 const name, pos = readBytes(bytes, pos);
487 symbol.name = (try wasm.internString(name)).toOptional();
488 }
489 },
490 else => {
491 log.debug("unrecognized symbol type tag: {x}", .{@intFromEnum(tag)});
492 return error.UnrecognizedSymbolType;
493 },
494 }
495 }
496 },
497 }
498 }
499 } else if (std.mem.startsWith(u8, section_name, "reloc.")) {
500 // 'The "reloc." custom sections must come after the "linking" custom section'
501 if (!saw_linking_section) return error.RelocBeforeLinkingSection;
502
503 // "Relocation sections start with an identifier specifying
504 // which section they apply to, and must be sequenced in
505 // the module after that section."
506 // "Relocation sections can only target code, data and custom sections."
507 const local_section, pos = readLeb(u32, bytes, pos);
508 const count, pos = readLeb(u32, bytes, pos);
509 const section: Wasm.ObjectSectionIndex = @enumFromInt(local_section_index_base + local_section);
510
511 log.debug("found {d} relocations for section={d}", .{ count, section });
512
513 var prev_offset: u32 = 0;
514 try wasm.object_relocations.ensureUnusedCapacity(gpa, count);
515 for (0..count) |_| {
516 const tag: RelocationType = @enumFromInt(bytes[pos]);
517 pos += 1;
518 const offset, pos = readLeb(u32, bytes, pos);
519 const index, pos = readLeb(u32, bytes, pos);
520
521 if (offset < prev_offset)
522 return diags.failParse(path, "relocation entries not sorted by offset", .{});
523 prev_offset = offset;
524
525 const sym = &ss.symbol_table.items[index];
526
527 switch (tag) {
528 .memory_addr_leb,
529 .memory_addr_sleb,
530 .memory_addr_i32,
531 .memory_addr_rel_sleb,
532 .memory_addr_leb64,
533 .memory_addr_sleb64,
534 .memory_addr_i64,
535 .memory_addr_rel_sleb64,
536 .memory_addr_tls_sleb,
537 .memory_addr_locrel_i32,
538 .memory_addr_tls_sleb64,
539 => {
540 const addend: i32, pos = readLeb(i32, bytes, pos);
541 wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
542 .data => |data| .{
543 .tag = .fromType(tag),
544 .offset = offset,
545 .pointee = .{ .data = data },
546 .addend = addend,
547 },
548 .data_import => .{
549 .tag = .fromTypeImport(tag),
550 .offset = offset,
551 .pointee = .{ .symbol_name = sym.name.unwrap().? },
552 .addend = addend,
553 },
554 else => unreachable,
555 });
556 },
557 .function_offset_i32, .function_offset_i64 => {
558 const addend: i32, pos = readLeb(i32, bytes, pos);
559 wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
560 .function => .{
561 .tag = .fromType(tag),
562 .offset = offset,
563 .pointee = .{ .function = sym.pointee.function },
564 .addend = addend,
565 },
566 .function_import => .{
567 .tag = .fromTypeImport(tag),
568 .offset = offset,
569 .pointee = .{ .symbol_name = sym.name.unwrap().? },
570 .addend = addend,
571 },
572 else => unreachable,
573 });
574 },
575 .section_offset_i32 => {
576 const addend: i32, pos = readLeb(i32, bytes, pos);
577 wasm.object_relocations.appendAssumeCapacity(.{
578 .tag = .section_offset_i32,
579 .offset = offset,
580 .pointee = .{ .section = sym.pointee.section },
581 .addend = addend,
582 });
583 },
584 .type_index_leb => {
585 wasm.object_relocations.appendAssumeCapacity(.{
586 .tag = .type_index_leb,
587 .offset = offset,
588 .pointee = .{ .type_index = ss.func_types.items[index] },
589 .addend = undefined,
590 });
591 },
592 .function_index_leb,
593 .function_index_i32,
594 .table_index_sleb,
595 .table_index_i32,
596 .table_index_sleb64,
597 .table_index_i64,
598 .table_index_rel_sleb,
599 .table_index_rel_sleb64,
600 => {
601 wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
602 .function => .{
603 .tag = .fromType(tag),
604 .offset = offset,
605 .pointee = .{ .function = sym.pointee.function },
606 .addend = undefined,
607 },
608 .function_import => .{
609 .tag = .fromTypeImport(tag),
610 .offset = offset,
611 .pointee = .{ .symbol_name = sym.name.unwrap().? },
612 .addend = undefined,
613 },
614 else => unreachable,
615 });
616 },
617 .global_index_leb, .global_index_i32 => {
618 wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
619 .global => .{
620 .tag = .fromType(tag),
621 .offset = offset,
622 .pointee = .{ .global = sym.pointee.global },
623 .addend = undefined,
624 },
625 .global_import => .{
626 .tag = .fromTypeImport(tag),
627 .offset = offset,
628 .pointee = .{ .symbol_name = sym.name.unwrap().? },
629 .addend = undefined,
630 },
631 else => unreachable,
632 });
633 },
634
635 .table_number_leb => {
636 wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
637 .table => .{
638 .tag = .fromType(tag),
639 .offset = offset,
640 .pointee = .{ .table = sym.pointee.table },
641 .addend = undefined,
642 },
643 .table_import => .{
644 .tag = .fromTypeImport(tag),
645 .offset = offset,
646 .pointee = .{ .symbol_name = sym.name.unwrap().? },
647 .addend = undefined,
648 },
649 else => unreachable,
650 });
651 },
652 .event_index_leb => return diags.failParse(path, "unsupported relocation: R_WASM_EVENT_INDEX_LEB", .{}),
653 }
654 }
655
656 try wasm.object_relocations_table.putNoClobber(gpa, section, .{
657 .off = @intCast(wasm.object_relocations.len - count),
658 .len = count,
659 });
660 } else if (std.mem.eql(u8, section_name, "target_features")) {
661 opt_features, pos = try parseFeatures(wasm, bytes, pos, path);
662 } else if (std.mem.startsWith(u8, section_name, ".debug")) {
663 const debug_content = bytes[pos..section_end];
664 pos = section_end;
665
666 const data_off: u32 = @intCast(wasm.string_bytes.items.len);
667 try wasm.string_bytes.appendSlice(gpa, debug_content);
668
669 try wasm.object_custom_segments.put(gpa, section_index, .{
670 .payload = .{
671 .off = @enumFromInt(data_off),
672 .len = @intCast(debug_content.len),
673 },
674 .flags = .{},
675 .section_name = try wasm.internString(section_name),
676 });
677 } else {
678 pos = section_end;
679 }
680 },
681 .type => {
682 const func_types_len, pos = readLeb(u32, bytes, pos);
683 for (try ss.func_types.addManyAsSlice(gpa, func_types_len)) |*func_type| {
684 if (bytes[pos] != std.wasm.function_type) return error.ExpectedFuncType;
685 pos += 1;
686
687 const params, pos = readBytes(bytes, pos);
688 const returns, pos = readBytes(bytes, pos);
689 func_type.* = try wasm.addFuncType(.{
690 .params = .fromString(try wasm.internString(params)),
691 .returns = .fromString(try wasm.internString(returns)),
692 });
693 }
694 },
695 .import => {
696 const imports_len, pos = readLeb(u32, bytes, pos);
697 for (0..imports_len) |_| {
698 const module_name, pos = readBytes(bytes, pos);
699 const name, pos = readBytes(bytes, pos);
700 const kind, pos = readEnum(std.wasm.ExternalKind, bytes, pos);
701 const interned_module_name = try wasm.internString(module_name);
702 const interned_name = try wasm.internString(name);
703 switch (kind) {
704 .function => {
705 const function, pos = readLeb(u32, bytes, pos);
706 try ss.func_imports.append(gpa, .{
707 .module_name = interned_module_name,
708 .name = interned_name,
709 .function_index = @enumFromInt(function),
710 });
711 },
712 .memory => {
713 const limits, pos = readLimits(bytes, pos);
714 const gop = try wasm.object_memory_imports.getOrPut(gpa, interned_name);
715 if (gop.found_existing) {
716 if (gop.value_ptr.module_name != interned_module_name) {
717 var err = try diags.addErrorWithNotes(2);
718 try err.addMsg("memory '{s}' mismatching module names", .{name});
719 gop.value_ptr.source_location.addNote(&err, "module '{s}' here", .{
720 gop.value_ptr.module_name.slice(wasm),
721 });
722 source_location.addNote(&err, "module '{s}' here", .{module_name});
723 }
724 // TODO error for mismatching flags
725 gop.value_ptr.limits_min = @min(gop.value_ptr.limits_min, limits.min);
726 gop.value_ptr.limits_max = @max(gop.value_ptr.limits_max, limits.max);
727 } else {
728 gop.value_ptr.* = .{
729 .module_name = interned_module_name,
730 .limits_min = limits.min,
731 .limits_max = limits.max,
732 .limits_has_max = limits.flags.has_max,
733 .limits_is_shared = limits.flags.is_shared,
734 .source_location = source_location,
735 };
736 }
737 },
738 .global => {
739 const valtype, pos = readEnum(std.wasm.Valtype, bytes, pos);
740 const mutable = bytes[pos] == 0x01;
741 pos += 1;
742 try ss.global_imports.append(gpa, .{
743 .name = interned_name,
744 .valtype = valtype,
745 .mutable = mutable,
746 .module_name = interned_module_name,
747 });
748 },
749 .table => {
750 const ref_type, pos = readEnum(std.wasm.RefType, bytes, pos);
751 const limits, pos = readLimits(bytes, pos);
752 try ss.table_imports.append(gpa, .{
753 .name = interned_name,
754 .module_name = interned_module_name,
755 .limits_min = limits.min,
756 .limits_max = limits.max,
757 .limits_has_max = limits.flags.has_max,
758 .limits_is_shared = limits.flags.is_shared,
759 .ref_type = ref_type,
760 });
761 },
762 }
763 }
764 },
765 .function => {
766 const functions_len, pos = readLeb(u32, bytes, pos);
767 for (try ss.func_type_indexes.addManyAsSlice(gpa, functions_len)) |*func_type_index| {
768 const i, pos = readLeb(u32, bytes, pos);
769 func_type_index.* = @enumFromInt(i);
770 }
771 },
772 .table => {
773 const tables_len, pos = readLeb(u32, bytes, pos);
774 for (try wasm.object_tables.addManyAsSlice(gpa, tables_len)) |*table| {
775 const ref_type, pos = readEnum(std.wasm.RefType, bytes, pos);
776 const limits, pos = readLimits(bytes, pos);
777 table.* = .{
778 .name = .none,
779 .module_name = .none,
780 .flags = .{
781 .ref_type = .from(ref_type),
782 .limits_has_max = limits.flags.has_max,
783 .limits_is_shared = limits.flags.is_shared,
784 },
785 .limits_min = limits.min,
786 .limits_max = limits.max,
787 };
788 }
789 },
790 .memory => {
791 const memories_len, pos = readLeb(u32, bytes, pos);
792 for (try wasm.object_memories.addManyAsSlice(gpa, memories_len)) |*memory| {
793 const limits, pos = readLimits(bytes, pos);
794 memory.* = .{
795 .name = .none,
796 .flags = .{
797 .limits_has_max = limits.flags.has_max,
798 .limits_is_shared = limits.flags.is_shared,
799 },
800 .limits_min = limits.min,
801 .limits_max = limits.max,
802 };
803 }
804 },
805 .global => {
806 if (global_section_index != null)
807 return diags.failParse(path, "object has more than one global section", .{});
808 global_section_index = section_index;
809
810 const section_start = pos;
811 const globals_len, pos = readLeb(u32, bytes, pos);
812 for (try wasm.object_globals.addManyAsSlice(gpa, globals_len)) |*global| {
813 const valtype, pos = readEnum(std.wasm.Valtype, bytes, pos);
814 const mutable = bytes[pos] == 0x01;
815 pos += 1;
816 const init_start = pos;
817 const expr, pos = try readInit(wasm, bytes, pos);
818 global.* = .{
819 .name = .none,
820 .flags = .{
821 .global_type = .{
822 .valtype = .from(valtype),
823 .mutable = mutable,
824 },
825 },
826 .expr = expr,
827 .object_index = object_index,
828 .offset = @intCast(init_start - section_start),
829 .size = @intCast(pos - init_start),
830 };
831 }
832 },
833 .@"export" => {
834 const exports_len, pos = readLeb(u32, bytes, pos);
835 // Read into scratch space, and then later add this data as if
836 // it were extra symbol table entries, but allow merging with
837 // existing symbol table data if the name matches.
838 for (try ss.exports.addManyAsSlice(gpa, exports_len)) |*exp| {
839 const name, pos = readBytes(bytes, pos);
840 const kind: std.wasm.ExternalKind = @enumFromInt(bytes[pos]);
841 pos += 1;
842 const index, pos = readLeb(u32, bytes, pos);
843 exp.* = .{
844 .name = try wasm.internString(name),
845 .pointee = switch (kind) {
846 .function => .{ .function = @enumFromInt(functions_start + (index - ss.func_imports.items.len)) },
847 .table => .{ .table = @enumFromInt(tables_start + (index - ss.table_imports.items.len)) },
848 .memory => .{ .memory = @enumFromInt(memories_start + index) },
849 .global => .{ .global = @enumFromInt(globals_start + (index - ss.global_imports.items.len)) },
850 },
851 };
852 }
853 },
854 .start => {
855 const index, pos = readLeb(u32, bytes, pos);
856 start_function = @enumFromInt(functions_start + index);
857 },
858 .element => {
859 log.warn("unimplemented: element section in {f} {?s}", .{ path, archive_member_name });
860 pos = section_end;
861 },
862 .code => {
863 if (code_section_index != null)
864 return diags.failParse(path, "object has more than one code section", .{});
865 code_section_index = section_index;
866
867 const start = pos;
868 const count, pos = readLeb(u32, bytes, pos);
869 for (try wasm.object_functions.addManyAsSlice(gpa, count)) |*elem| {
870 const code_len, pos = readLeb(u32, bytes, pos);
871 const offset: u32 = @intCast(pos - start);
872 const payload = try wasm.addRelocatableDataPayload(bytes[pos..][0..code_len]);
873 pos += code_len;
874 elem.* = .{
875 .flags = .{}, // populated from symbol table
876 .name = .none, // populated from symbol table
877 .type_index = undefined, // populated from func_types
878 .code = payload,
879 .offset = offset,
880 .object_index = object_index,
881 };
882 }
883 },
884 .data => {
885 if (data_section_index != null)
886 return diags.failParse(path, "object has more than one data section", .{});
887 data_section_index = section_index;
888
889 const section_start = pos;
890 const count, pos = readLeb(u32, bytes, pos);
891 for (try wasm.object_data_segments.addManyAsSlice(gpa, count)) |*elem| {
892 const flags, pos = readEnum(DataSegmentFlags, bytes, pos);
893 if (flags == .active_memidx) {
894 const memidx, pos = readLeb(u32, bytes, pos);
895 if (memidx != 0) return diags.failParse(path, "data section uses mem index {d}", .{memidx});
896 }
897 //const expr, pos = if (flags != .passive) try readInit(wasm, bytes, pos) else .{ .none, pos };
898 if (flags != .passive) pos = try skipInit(bytes, pos);
899 const data_len, pos = readLeb(u32, bytes, pos);
900 const segment_start = pos;
901 const payload = try wasm.addRelocatableDataPayload(bytes[pos..][0..data_len]);
902 pos += data_len;
903 elem.* = .{
904 .payload = payload,
905 .name = .none, // Populated from segment_info
906 .flags = .{
907 .is_passive = flags == .passive,
908 }, // Remainder populated from segment_info
909 .offset = @intCast(segment_start - section_start),
910 .object_index = object_index,
911 };
912 }
913 },
914 else => pos = section_end,
915 }
916 if (pos != section_end) return error.MalformedSection;
917 }
918 if (!saw_linking_section) return error.MissingLinkingSection;
919
920 const cpu = comp.root_mod.resolved_target.result.cpu;
921
922 if (has_tls) {
923 if (!cpu.has(.wasm, .atomics))
924 return diags.failParse(path, "object has TLS segment but target CPU feature atomics is disabled", .{});
925 if (!cpu.has(.wasm, .bulk_memory))
926 return diags.failParse(path, "object has TLS segment but target CPU feature bulk_memory is disabled", .{});
927 }
928
929 const features = opt_features orelse return error.MissingFeatures;
930 for (features.slice(wasm)) |feat| {
931 log.debug("feature: {s}{s}", .{ @tagName(feat.prefix), @tagName(feat.tag) });
932 switch (feat.prefix) {
933 .invalid => unreachable,
934 .@"-" => switch (feat.tag) {
935 .@"shared-mem" => if (comp.config.shared_memory) {
936 return diags.failParse(path, "object forbids shared-mem but compilation enables it", .{});
937 },
938 else => {
939 const f = feat.tag.toCpuFeature().?;
940 if (cpu.has(.wasm, f)) {
941 return diags.failParse(
942 path,
943 "object forbids {s} but specified target features include {s}",
944 .{ @tagName(feat.tag), @tagName(f) },
945 );
946 }
947 },
948 },
949 .@"+", .@"=" => switch (feat.tag) {
950 .@"shared-mem" => if (!comp.config.shared_memory) {
951 return diags.failParse(path, "object requires shared-mem but compilation disables it", .{});
952 },
953 else => {
954 const f = feat.tag.toCpuFeature().?;
955 if (!cpu.has(.wasm, f)) {
956 return diags.failParse(
957 path,
958 "object requires {s} but specified target features exclude {s}",
959 .{ @tagName(feat.tag), @tagName(f) },
960 );
961 }
962 },
963 },
964 }
965 }
966
967 // Apply function type information.
968 for (ss.func_type_indexes.items, wasm.object_functions.items[functions_start..]) |func_type, *func| {
969 func.type_index = func_type.ptr(ss).*;
970 }
971
972 // Apply symbol table information.
973 for (ss.symbol_table.items) |symbol| switch (symbol.pointee) {
974 .function_import => |index| {
975 const ptr = index.ptr(ss);
976 const name = symbol.name.unwrap() orelse ptr.name;
977 if (symbol.flags.binding == .local) {
978 diags.addParseError(path, "local symbol '{s}' references import", .{name.slice(wasm)});
979 continue;
980 }
981 const gop = try wasm.object_function_imports.getOrPut(gpa, name);
982 const fn_ty_index = ptr.function_index.ptr(ss).*;
983 if (gop.found_existing) {
984 if (gop.value_ptr.type != fn_ty_index) {
985 var err = try diags.addErrorWithNotes(2);
986 try err.addMsg("symbol '{s}' mismatching function signatures", .{name.slice(wasm)});
987 gop.value_ptr.source_location.addNote(&err, "imported as {f} here", .{
988 gop.value_ptr.type.fmt(wasm),
989 });
990 source_location.addNote(&err, "imported as {f} here", .{fn_ty_index.fmt(wasm)});
991 continue;
992 }
993 if (gop.value_ptr.module_name != ptr.module_name.toOptional()) {
994 var err = try diags.addErrorWithNotes(2);
995 try err.addMsg("symbol '{s}' mismatching module names", .{name.slice(wasm)});
996 if (gop.value_ptr.module_name.slice(wasm)) |module_name| {
997 gop.value_ptr.source_location.addNote(&err, "module '{s}' here", .{module_name});
998 } else {
999 gop.value_ptr.source_location.addNote(&err, "no module here", .{});
1000 }
1001 source_location.addNote(&err, "module '{s}' here", .{ptr.module_name.slice(wasm)});
1002 continue;
1003 }
1004 if (gop.value_ptr.name != ptr.name) {
1005 var err = try diags.addErrorWithNotes(2);
1006 try err.addMsg("symbol '{s}' mismatching import names", .{name.slice(wasm)});
1007 gop.value_ptr.source_location.addNote(&err, "imported as '{s}' here", .{gop.value_ptr.name.slice(wasm)});
1008 source_location.addNote(&err, "imported as '{s}' here", .{ptr.name.slice(wasm)});
1009 continue;
1010 }
1011 } else {
1012 gop.value_ptr.* = .{
1013 .flags = symbol.flags,
1014 .module_name = ptr.module_name.toOptional(),
1015 .name = ptr.name,
1016 .source_location = source_location,
1017 .resolution = .unresolved,
1018 .type = fn_ty_index,
1019 };
1020 }
1021 },
1022 .global_import => |index| {
1023 const ptr = index.ptr(ss);
1024 const name = symbol.name.unwrap() orelse ptr.name;
1025 if (symbol.flags.binding == .local) {
1026 diags.addParseError(path, "local symbol '{s}' references import", .{name.slice(wasm)});
1027 continue;
1028 }
1029 const gop = try wasm.object_global_imports.getOrPut(gpa, name);
1030 if (gop.found_existing) {
1031 const existing_ty = gop.value_ptr.type();
1032 if (ptr.valtype != existing_ty.valtype) {
1033 var err = try diags.addErrorWithNotes(2);
1034 try err.addMsg("symbol '{s}' mismatching global types", .{name.slice(wasm)});
1035 gop.value_ptr.source_location.addNote(&err, "type {s} here", .{@tagName(existing_ty.valtype)});
1036 source_location.addNote(&err, "type {s} here", .{@tagName(ptr.valtype)});
1037 continue;
1038 }
1039 if (ptr.mutable != existing_ty.mutable) {
1040 var err = try diags.addErrorWithNotes(2);
1041 try err.addMsg("symbol '{s}' mismatching global mutability", .{name.slice(wasm)});
1042 gop.value_ptr.source_location.addNote(&err, "{s} here", .{
1043 if (existing_ty.mutable) "mutable" else "not mutable",
1044 });
1045 source_location.addNote(&err, "{s} here", .{
1046 if (ptr.mutable) "mutable" else "not mutable",
1047 });
1048 continue;
1049 }
1050 if (gop.value_ptr.module_name != ptr.module_name.toOptional()) {
1051 var err = try diags.addErrorWithNotes(2);
1052 try err.addMsg("symbol '{s}' mismatching module names", .{name.slice(wasm)});
1053 if (gop.value_ptr.module_name.slice(wasm)) |module_name| {
1054 gop.value_ptr.source_location.addNote(&err, "module '{s}' here", .{module_name});
1055 } else {
1056 gop.value_ptr.source_location.addNote(&err, "no module here", .{});
1057 }
1058 source_location.addNote(&err, "module '{s}' here", .{ptr.module_name.slice(wasm)});
1059 continue;
1060 }
1061 if (gop.value_ptr.name != ptr.name) {
1062 var err = try diags.addErrorWithNotes(2);
1063 try err.addMsg("symbol '{s}' mismatching import names", .{name.slice(wasm)});
1064 gop.value_ptr.source_location.addNote(&err, "imported as '{s}' here", .{gop.value_ptr.name.slice(wasm)});
1065 source_location.addNote(&err, "imported as '{s}' here", .{ptr.name.slice(wasm)});
1066 continue;
1067 }
1068 } else {
1069 gop.value_ptr.* = .{
1070 .flags = symbol.flags,
1071 .module_name = ptr.module_name.toOptional(),
1072 .name = ptr.name,
1073 .source_location = source_location,
1074 .resolution = .unresolved,
1075 };
1076 gop.value_ptr.flags.global_type = .{
1077 .valtype = .from(ptr.valtype),
1078 .mutable = ptr.mutable,
1079 };
1080 }
1081 },
1082 .table_import => |index| {
1083 const ptr = index.ptr(ss);
1084 const name = symbol.name.unwrap() orelse ptr.name;
1085 if (symbol.flags.binding == .local) {
1086 diags.addParseError(path, "local symbol '{s}' references import", .{name.slice(wasm)});
1087 continue;
1088 }
1089 const gop = try wasm.object_table_imports.getOrPut(gpa, name);
1090 if (gop.found_existing) {
1091 const existing_reftype = gop.value_ptr.flags.ref_type.to();
1092 if (ptr.ref_type != existing_reftype) {
1093 var err = try diags.addErrorWithNotes(2);
1094 try err.addMsg("symbol '{s}' mismatching table reftypes", .{name.slice(wasm)});
1095 gop.value_ptr.source_location.addNote(&err, "{s} here", .{@tagName(existing_reftype)});
1096 source_location.addNote(&err, "{s} here", .{@tagName(ptr.ref_type)});
1097 continue;
1098 }
1099 if (gop.value_ptr.module_name != ptr.module_name) {
1100 var err = try diags.addErrorWithNotes(2);
1101 try err.addMsg("symbol '{s}' mismatching module names", .{name.slice(wasm)});
1102 gop.value_ptr.source_location.addNote(&err, "module '{s}' here", .{
1103 gop.value_ptr.module_name.slice(wasm),
1104 });
1105 source_location.addNote(&err, "module '{s}' here", .{ptr.module_name.slice(wasm)});
1106 continue;
1107 }
1108 if (gop.value_ptr.name != ptr.name) {
1109 var err = try diags.addErrorWithNotes(2);
1110 try err.addMsg("symbol '{s}' mismatching import names", .{name.slice(wasm)});
1111 gop.value_ptr.source_location.addNote(&err, "imported as '{s}' here", .{gop.value_ptr.name.slice(wasm)});
1112 source_location.addNote(&err, "imported as '{s}' here", .{ptr.name.slice(wasm)});
1113 continue;
1114 }
1115 if (symbol.flags.binding == .strong) gop.value_ptr.flags.binding = .strong;
1116 if (!symbol.flags.visibility_hidden) gop.value_ptr.flags.visibility_hidden = false;
1117 if (symbol.flags.no_strip) gop.value_ptr.flags.no_strip = true;
1118 } else {
1119 gop.value_ptr.* = .{
1120 .flags = symbol.flags,
1121 .module_name = ptr.module_name,
1122 .name = ptr.name,
1123 .source_location = source_location,
1124 .resolution = .unresolved,
1125 .limits_min = ptr.limits_min,
1126 .limits_max = ptr.limits_max,
1127 };
1128 gop.value_ptr.flags.limits_has_max = ptr.limits_has_max;
1129 gop.value_ptr.flags.limits_is_shared = ptr.limits_is_shared;
1130 gop.value_ptr.flags.ref_type = .from(ptr.ref_type);
1131 }
1132 },
1133 .data_import => {
1134 const name = symbol.name.unwrap().?;
1135 if (symbol.flags.binding == .local) {
1136 diags.addParseError(path, "local symbol '{s}' references import", .{name.slice(wasm)});
1137 continue;
1138 }
1139 const gop = try wasm.object_data_imports.getOrPut(gpa, name);
1140 if (!gop.found_existing) gop.value_ptr.* = .{
1141 .flags = symbol.flags,
1142 .source_location = source_location,
1143 .resolution = .unresolved,
1144 };
1145 },
1146 .function => |index| {
1147 assert(!symbol.flags.undefined);
1148 const ptr = index.ptr(wasm);
1149 ptr.name = symbol.name;
1150 ptr.flags = symbol.flags;
1151 if (symbol.flags.binding == .local) continue; // No participation in symbol resolution.
1152 const name = symbol.name.unwrap().?;
1153 const gop = try wasm.object_function_imports.getOrPut(gpa, name);
1154 if (gop.found_existing) {
1155 if (gop.value_ptr.type != ptr.type_index) {
1156 var err = try diags.addErrorWithNotes(2);
1157 try err.addMsg("function signature mismatch: {s}", .{name.slice(wasm)});
1158 gop.value_ptr.source_location.addNote(&err, "exported as {f} here", .{
1159 ptr.type_index.fmt(wasm),
1160 });
1161 const word = if (gop.value_ptr.resolution == .unresolved) "imported" else "exported";
1162 source_location.addNote(&err, "{s} as {f} here", .{ word, gop.value_ptr.type.fmt(wasm) });
1163 continue;
1164 }
1165 if (gop.value_ptr.resolution == .unresolved or gop.value_ptr.flags.binding == .weak) {
1166 // Intentional: if they're both weak, take the last one.
1167 gop.value_ptr.source_location = source_location;
1168 gop.value_ptr.module_name = host_name;
1169 gop.value_ptr.resolution = .fromObjectFunction(wasm, index);
1170 gop.value_ptr.flags = symbol.flags;
1171 continue;
1172 }
1173 if (ptr.flags.binding == .weak) {
1174 // Keep the existing one.
1175 continue;
1176 }
1177 var err = try diags.addErrorWithNotes(2);
1178 try err.addMsg("symbol collision: {s}", .{name.slice(wasm)});
1179 gop.value_ptr.source_location.addNote(&err, "exported as {f} here", .{ptr.type_index.fmt(wasm)});
1180 source_location.addNote(&err, "exported as {f} here", .{gop.value_ptr.type.fmt(wasm)});
1181 continue;
1182 } else {
1183 gop.value_ptr.* = .{
1184 .flags = symbol.flags,
1185 .module_name = host_name,
1186 .name = name,
1187 .source_location = source_location,
1188 .resolution = .fromObjectFunction(wasm, index),
1189 .type = ptr.type_index,
1190 };
1191 }
1192 },
1193 .global => |index| {
1194 assert(!symbol.flags.undefined);
1195 const ptr = index.ptr(wasm);
1196 ptr.name = symbol.name;
1197 ptr.flags = symbol.flags;
1198 if (symbol.flags.binding == .local) continue; // No participation in symbol resolution.
1199 const name = symbol.name.unwrap().?;
1200 const new_ty = ptr.type();
1201 const gop = try wasm.object_global_imports.getOrPut(gpa, name);
1202 if (gop.found_existing) {
1203 const existing_ty = gop.value_ptr.type();
1204 if (new_ty.valtype != existing_ty.valtype) {
1205 var err = try diags.addErrorWithNotes(2);
1206 try err.addMsg("symbol '{s}' mismatching global types", .{name.slice(wasm)});
1207 gop.value_ptr.source_location.addNote(&err, "type {s} here", .{@tagName(existing_ty.valtype)});
1208 source_location.addNote(&err, "type {s} here", .{@tagName(new_ty.valtype)});
1209 continue;
1210 }
1211 if (new_ty.mutable != existing_ty.mutable) {
1212 var err = try diags.addErrorWithNotes(2);
1213 try err.addMsg("symbol '{s}' mismatching global mutability", .{name.slice(wasm)});
1214 gop.value_ptr.source_location.addNote(&err, "{s} here", .{
1215 if (existing_ty.mutable) "mutable" else "not mutable",
1216 });
1217 source_location.addNote(&err, "{s} here", .{
1218 if (new_ty.mutable) "mutable" else "not mutable",
1219 });
1220 continue;
1221 }
1222 if (gop.value_ptr.resolution == .unresolved or gop.value_ptr.flags.binding == .weak) {
1223 // Intentional: if they're both weak, take the last one.
1224 gop.value_ptr.source_location = source_location;
1225 gop.value_ptr.module_name = host_name;
1226 gop.value_ptr.resolution = .fromObjectGlobal(wasm, index);
1227 gop.value_ptr.flags = symbol.flags;
1228 continue;
1229 }
1230 if (ptr.flags.binding == .weak) {
1231 // Keep the existing one.
1232 continue;
1233 }
1234 var err = try diags.addErrorWithNotes(2);
1235 try err.addMsg("symbol collision: {s}", .{name.slice(wasm)});
1236 gop.value_ptr.source_location.addNote(&err, "exported as {s} here", .{@tagName(existing_ty.valtype)});
1237 source_location.addNote(&err, "exported as {s} here", .{@tagName(new_ty.valtype)});
1238 continue;
1239 } else {
1240 gop.value_ptr.* = .{
1241 .flags = symbol.flags,
1242 .module_name = .none,
1243 .name = name,
1244 .source_location = source_location,
1245 .resolution = .fromObjectGlobal(wasm, index),
1246 };
1247 gop.value_ptr.flags.global_type = .{
1248 .valtype = .from(new_ty.valtype),
1249 .mutable = new_ty.mutable,
1250 };
1251 }
1252 },
1253 .table => |i| {
1254 assert(!symbol.flags.undefined);
1255 const ptr = i.ptr(wasm);
1256 ptr.name = symbol.name;
1257 ptr.flags = symbol.flags;
1258 },
1259 .data => |index| {
1260 assert(!symbol.flags.undefined);
1261 const ptr = index.ptr(wasm);
1262 const name = ptr.name;
1263 assert(name.toOptional() == symbol.name);
1264 ptr.flags = symbol.flags;
1265 if (symbol.flags.binding == .local) continue; // No participation in symbol resolution.
1266 const gop = try wasm.object_data_imports.getOrPut(gpa, name);
1267 if (gop.found_existing) {
1268 if (gop.value_ptr.resolution == .unresolved or gop.value_ptr.flags.binding == .weak) {
1269 // Intentional: if they're both weak, take the last one.
1270 gop.value_ptr.source_location = source_location;
1271 gop.value_ptr.resolution = .fromObjectDataIndex(wasm, index);
1272 gop.value_ptr.flags = symbol.flags;
1273 continue;
1274 }
1275 if (ptr.flags.binding == .weak) {
1276 // Keep the existing one.
1277 continue;
1278 }
1279 var err = try diags.addErrorWithNotes(2);
1280 try err.addMsg("symbol collision: {s}", .{name.slice(wasm)});
1281 gop.value_ptr.source_location.addNote(&err, "exported here", .{});
1282 source_location.addNote(&err, "exported here", .{});
1283 continue;
1284 } else {
1285 gop.value_ptr.* = .{
1286 .flags = symbol.flags,
1287 .source_location = source_location,
1288 .resolution = .fromObjectDataIndex(wasm, index),
1289 };
1290 }
1291 },
1292 .section => |i| {
1293 // Name is provided by the section directly; symbol table does not have it.
1294 //const ptr = i.ptr(wasm);
1295 //ptr.flags = symbol.flags;
1296 _ = i;
1297 if (symbol.flags.undefined and symbol.flags.binding == .local) {
1298 const name = symbol.name.slice(wasm).?;
1299 diags.addParseError(path, "local symbol '{s}' references import", .{name});
1300 }
1301 },
1302 };
1303
1304 // Apply export section info. This is done after the symbol table above so
1305 // that the symbol table can take precedence, overriding the export name.
1306 for (ss.exports.items) |*exp| {
1307 switch (exp.pointee) {
1308 inline .function, .table, .memory, .global => |index| {
1309 const ptr = index.ptr(wasm);
1310 ptr.name = exp.name.toOptional();
1311 ptr.flags.exported = true;
1312 },
1313 }
1314 }
1315
1316 // Apply segment_info.
1317 const data_segments = wasm.object_data_segments.items[data_segment_start..];
1318 if (data_segments.len != ss.segment_info.items.len) {
1319 return diags.failParse(path, "expected {d} segment_info entries; found {d}", .{
1320 data_segments.len, ss.segment_info.items.len,
1321 });
1322 }
1323 for (data_segments, ss.segment_info.items) |*data, info| {
1324 data.name = info.name.toOptional();
1325 data.flags = .{
1326 .is_passive = data.flags.is_passive,
1327 .strings = info.flags.strings,
1328 .tls = info.flags.tls,
1329 .retain = info.flags.retain,
1330 .alignment = info.flags.alignment,
1331 };
1332 }
1333
1334 // Check for indirect function table in case of an MVP object file.
1335 legacy_indirect_function_table: {
1336 // If there is a symbol for each import table, this is not a legacy object file.
1337 if (ss.table_imports.items.len == table_import_symbol_count) break :legacy_indirect_function_table;
1338 if (table_import_symbol_count != 0) {
1339 return diags.failParse(path, "expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{
1340 ss.table_imports.items.len, table_import_symbol_count,
1341 });
1342 }
1343 // MVP object files cannot have any table definitions, only imports
1344 // (for the indirect function table).
1345 const tables = wasm.object_tables.items[tables_start..];
1346 if (tables.len > 0) {
1347 return diags.failParse(path, "table definition without representing table symbols", .{});
1348 }
1349 if (ss.table_imports.items.len != 1) {
1350 return diags.failParse(path, "found more than one table import, but no representing table symbols", .{});
1351 }
1352 const table_import_name = ss.table_imports.items[0].name;
1353 if (table_import_name != wasm.preloaded_strings.__indirect_function_table) {
1354 return diags.failParse(path, "non-indirect function table import '{s}' is missing a corresponding symbol", .{
1355 table_import_name.slice(wasm),
1356 });
1357 }
1358 const ptr = wasm.object_table_imports.getPtr(table_import_name).?;
1359 ptr.flags = .{
1360 .undefined = true,
1361 .no_strip = true,
1362 };
1363 }
1364
1365 for (wasm.object_init_funcs.items[init_funcs_start..]) |init_func| {
1366 const func = init_func.function_index.ptr(wasm);
1367 const params = func.type_index.ptr(wasm).params.slice(wasm);
1368 if (params.len != 0) diags.addError("constructor function '{s}' has non-empty parameter list", .{
1369 func.name.slice(wasm).?,
1370 });
1371 }
1372
1373 const functions_len: u32 = @intCast(wasm.object_functions.items.len - functions_start);
1374 if (functions_len > 0 and code_section_index == null)
1375 return diags.failParse(path, "code section missing ({d} functions)", .{functions_len});
1376
1377 return .{
1378 .version = version,
1379 .path = path,
1380 .archive_member_name = try wasm.internOptionalString(archive_member_name),
1381 .start_function = start_function,
1382 .features = features,
1383 .functions = .{
1384 .off = functions_start,
1385 .len = functions_len,
1386 },
1387 .function_imports = .{
1388 .off = function_imports_start,
1389 .len = @intCast(wasm.object_function_imports.entries.len - function_imports_start),
1390 },
1391 .global_imports = .{
1392 .off = global_imports_start,
1393 .len = @intCast(wasm.object_global_imports.entries.len - global_imports_start),
1394 },
1395 .table_imports = .{
1396 .off = table_imports_start,
1397 .len = @intCast(wasm.object_table_imports.entries.len - table_imports_start),
1398 },
1399 .data_imports = .{
1400 .off = data_imports_start,
1401 .len = @intCast(wasm.object_data_imports.entries.len - data_imports_start),
1402 },
1403 .init_funcs = .{
1404 .off = init_funcs_start,
1405 .len = @intCast(wasm.object_init_funcs.items.len - init_funcs_start),
1406 },
1407 .comdats = .{
1408 .off = comdats_start,
1409 .len = @intCast(wasm.object_comdats.items.len - comdats_start),
1410 },
1411 .custom_segments = .{
1412 .off = custom_segment_start,
1413 .len = @intCast(wasm.object_custom_segments.entries.len - custom_segment_start),
1414 },
1415 .code_section_index = code_section_index,
1416 .global_section_index = global_section_index,
1417 .data_section_index = data_section_index,
1418 .is_included = must_link,
1419 };
1420}
1421
1422/// Based on the "features" custom section, parses it into a list of
1423/// features that tell the linker what features were enabled and may be mandatory
1424/// to be able to link.
1425fn parseFeatures(
1426 wasm: *Wasm,
1427 bytes: []const u8,
1428 start_pos: usize,
1429 path: Path,
1430) error{ OutOfMemory, LinkFailure }!struct { Wasm.Feature.Set, usize } {
1431 const gpa = wasm.base.comp.gpa;
1432 const diags = &wasm.base.comp.link_diags;
1433 const features_len, var pos = readLeb(u32, bytes, start_pos);
1434 // This temporary allocation could be avoided by using the string_bytes buffer as a scratch space.
1435 const feature_buffer = try gpa.alloc(Wasm.Feature, features_len);
1436 defer gpa.free(feature_buffer);
1437 for (feature_buffer) |*feature| {
1438 const prefix: Wasm.Feature.Prefix = switch (bytes[pos]) {
1439 '-' => .@"-",
1440 '+' => .@"+",
1441 '=' => .@"=",
1442 else => |b| return diags.failParse(path, "invalid feature prefix: 0x{x}", .{b}),
1443 };
1444 pos += 1;
1445 const name, pos = readBytes(bytes, pos);
1446 const tag = std.meta.stringToEnum(Wasm.Feature.Tag, name) orelse {
1447 return diags.failParse(path, "unrecognized wasm feature in object: {s}", .{name});
1448 };
1449 feature.* = .{
1450 .prefix = prefix,
1451 .tag = tag,
1452 };
1453 }
1454 std.mem.sortUnstable(Wasm.Feature, feature_buffer, {}, Wasm.Feature.lessThan);
1455
1456 return .{
1457 .fromString(try wasm.internString(@ptrCast(feature_buffer))),
1458 pos,
1459 };
1460}
1461
1462fn readLeb(comptime T: type, bytes: []const u8, pos: usize) struct { T, usize } {
1463 var reader: std.Io.Reader = .fixed(bytes[pos..]);
1464 return .{
1465 reader.takeLeb128(T) catch unreachable,
1466 pos + reader.seek,
1467 };
1468}
1469
1470fn readBytes(bytes: []const u8, start_pos: usize) struct { []const u8, usize } {
1471 const len, const pos = readLeb(u32, bytes, start_pos);
1472 return .{
1473 bytes[pos..][0..len],
1474 pos + len,
1475 };
1476}
1477
1478fn readEnum(comptime T: type, bytes: []const u8, pos: usize) struct { T, usize } {
1479 const Tag = @typeInfo(T).@"enum".tag_type;
1480 const int, const new_pos = readLeb(Tag, bytes, pos);
1481 return .{ @enumFromInt(int), new_pos };
1482}
1483
1484fn readLimits(bytes: []const u8, start_pos: usize) struct { std.wasm.Limits, usize } {
1485 const flags: std.wasm.Limits.Flags = @bitCast(bytes[start_pos]);
1486 const min, const max_pos = readLeb(u32, bytes, start_pos + 1);
1487 const max, const end_pos = if (flags.has_max) readLeb(u32, bytes, max_pos) else .{ 0, max_pos };
1488 return .{ .{
1489 .flags = flags,
1490 .min = min,
1491 .max = max,
1492 }, end_pos };
1493}
1494
1495fn readInit(wasm: *Wasm, bytes: []const u8, pos: usize) !struct { Wasm.Expr, usize } {
1496 const end_pos = try skipInit(bytes, pos); // one after the end opcode
1497 return .{ try wasm.addExpr(bytes[pos..end_pos]), end_pos };
1498}
1499
1500pub fn exprEndPos(bytes: []const u8, pos: usize) error{InvalidInitOpcode}!usize {
1501 const opcode = bytes[pos];
1502 return switch (@as(std.wasm.Opcode, @enumFromInt(opcode))) {
1503 .i32_const => readLeb(i32, bytes, pos + 1)[1],
1504 .i64_const => readLeb(i64, bytes, pos + 1)[1],
1505 .f32_const => pos + 5,
1506 .f64_const => pos + 9,
1507 .global_get => readLeb(u32, bytes, pos + 1)[1],
1508 else => return error.InvalidInitOpcode,
1509 };
1510}
1511
1512fn skipInit(bytes: []const u8, pos: usize) !usize {
1513 const end_pos = try exprEndPos(bytes, pos);
1514 const op, const final_pos = readEnum(std.wasm.Opcode, bytes, end_pos);
1515 if (op != .end) return error.InitExprMissingEnd;
1516 return final_pos;
1517}