authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-02-13 16:34:51+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-02-17 18:11:48+01:00
loge7be0bef43e5fc7d19bbe184b9dc5209f52f745c
tree985053858e9995a6c7e64cd0d01a077b5ac144c8
parentd1c74ac42dd4f1306a0dcb7acbbf1f95fe515627

wasm-linker: Add Object file parsing

This upstreams the object file parsing from zwld, bringing us closer to being able to link stage2 code with object files/C-code as well as replacing lld with the self-hosted linker once feature complete.

3 files changed, 920 insertions(+), 9 deletions(-)

src/link/Wasm.zig+72-8
......@@ -25,6 +25,7 @@ const LlvmObject = @import("../codegen/llvm.zig").Object;
2525const Air = @import("../Air.zig");
2626const Liveness = @import("../Liveness.zig");
2727const Symbol = @import("Wasm/Symbol.zig");
28const Object = @import("Wasm/Object.zig");
2829const types = @import("Wasm/types.zig");
2930
3031pub const base_tag = link.File.Tag.wasm;
......@@ -73,7 +74,7 @@ func_types: std.ArrayListUnmanaged(wasm.Type) = .{},
7374/// Output function section
7475functions: std.ArrayListUnmanaged(wasm.Func) = .{},
7576/// Output global section
76globals: std.ArrayListUnmanaged(wasm.Global) = .{},
77wasm_globals: std.ArrayListUnmanaged(wasm.Global) = .{},
7778/// Memory section
7879memories: wasm.Memory = .{ .limits = .{ .min = 0, .max = null } },
7980
......@@ -84,6 +85,17 @@ memories: wasm.Memory = .{ .limits = .{ .min = 0, .max = null } },
8485/// Note: Key is symbol index, value represents the index into the table
8586function_table: std.AutoHashMapUnmanaged(u32, u32) = .{},
8687
88/// All object files and their data which are linked into the final binary
89objects: std.ArrayListUnmanaged(Object) = .{},
90/// Maps discarded symbols and their positions to the location of the symbol
91/// it was resolved to
92discarded: std.AutoHashMapUnmanaged(SymbolLoc, SymbolLoc) = .{},
93/// Mapping between symbol names and their respective location.
94/// This map contains all symbols that will be written into the final binary
95/// and were either defined, or resolved.
96/// TODO: Use string interning and make the key an index, rather than a unique string.
97symbol_resolver: std.StringArrayHashMapUnmanaged(SymbolLoc) = .{},
98
8799pub const Segment = struct {
88100 alignment: u32,
89101 size: u32,
......@@ -98,6 +110,14 @@ pub const FnData = struct {
98110 };
99111};
100112
113pub const SymbolLoc = struct {
114 /// The index of the symbol within the specified file
115 index: u32,
116 /// The index of the object file where the symbol resides.
117 /// When this is `null` the symbol comes from a non-object file.
118 file: ?u16,
119};
120
101121pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Wasm {
102122 assert(options.object_format == .wasm);
103123
......@@ -115,7 +135,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
115135 try file.writeAll(&(wasm.magic ++ wasm.version));
116136
117137 // As sym_index '0' is reserved, we use it for our stack pointer symbol
118 const global = try wasm_bin.globals.addOne(allocator);
138 const global = try wasm_bin.wasm_globals.addOne(allocator);
119139 global.* = .{
120140 .global_type = .{
121141 .valtype = .i32,
......@@ -152,6 +172,31 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Wasm {
152172 return self;
153173}
154174
175fn parseInputFiles(self: *Wasm, files: []const []const u8) !void {
176 for (files) |path| {
177 if (try self.parseObjectFile(path)) continue;
178 log.warn("Unexpected file format at path: '{s}'", .{path});
179 }
180}
181
182/// Parses the object file from given path. Returns true when the given file was an object
183/// file and parsed successfully. Returns false when file is not an object file.
184/// May return an error instead when parsing failed.
185fn parseObjectFile(self: *Wasm, path: []const u8) !bool {
186 const file = try fs.cwd().openFile(path, .{});
187 errdefer file.close();
188
189 var object = Object.init(self.base.allocator, file, path) catch |err| {
190 if (err == error.InvalidMagicByte) {
191 log.warn("Self hosted linker does not support non-object file parsing", .{});
192 return false;
193 } else return err;
194 };
195 errdefer object.deinit(self.base.allocator);
196 try self.objects.append(self.base.allocator, object);
197 return true;
198}
199
155200pub fn deinit(self: *Wasm) void {
156201 if (build_options.have_llvm) {
157202 if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator);
......@@ -182,7 +227,7 @@ pub fn deinit(self: *Wasm) void {
182227 self.imports.deinit(self.base.allocator);
183228 self.func_types.deinit(self.base.allocator);
184229 self.functions.deinit(self.base.allocator);
185 self.globals.deinit(self.base.allocator);
230 self.wasm_globals.deinit(self.base.allocator);
186231 self.function_table.deinit(self.base.allocator);
187232}
188233
......@@ -587,7 +632,7 @@ fn setupMemory(self: *Wasm) !void {
587632 memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, stack_alignment);
588633 memory_ptr += stack_size;
589634 // We always put the stack pointer global at index 0
590 self.globals.items[0].init.i32_const = @bitCast(i32, @intCast(u32, memory_ptr));
635 self.wasm_globals.items[0].init.i32_const = @bitCast(i32, @intCast(u32, memory_ptr));
591636 }
592637
593638 var offset: u32 = @intCast(u32, memory_ptr);
......@@ -605,7 +650,7 @@ fn setupMemory(self: *Wasm) !void {
605650 if (!place_stack_first) {
606651 memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, stack_alignment);
607652 memory_ptr += stack_size;
608 self.globals.items[0].init.i32_const = @bitCast(i32, @intCast(u32, memory_ptr));
653 self.wasm_globals.items[0].init.i32_const = @bitCast(i32, @intCast(u32, memory_ptr));
609654 }
610655
611656 // Setup the max amount of pages
......@@ -690,6 +735,25 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
690735 const tracy = trace(@src());
691736 defer tracy.end();
692737
738 // Used for all temporary memory allocated during flushin
739 var arena_instance = std.heap.ArenaAllocator.init(self.base.allocator);
740 defer arena_instance.deinit();
741 const arena = arena_instance.allocator();
742
743 // Positional arguments to the linker such as object files and static archives.
744 var positionals = std.ArrayList([]const u8).init(arena);
745 try positionals.ensureUnusedCapacity(self.base.options.objects.len);
746
747 for (self.base.options.objects) |object| {
748 positionals.appendAssumeCapacity(object.path);
749 }
750
751 for (comp.c_object_table.keys()) |c_object| {
752 try positionals.append(c_object.status.success.object_path);
753 }
754 // TODO: Also link with other objects such as compiler-rt
755 try self.parseInputFiles(positionals.items);
756
693757 // When we finish/error we reset the state of the linker
694758 // So we can rebuild the binary file on each incremental update
695759 defer self.resetState();
......@@ -852,7 +916,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
852916 const header_offset = try reserveVecSectionHeader(file);
853917 const writer = file.writer();
854918
855 for (self.globals.items) |global| {
919 for (self.wasm_globals.items) |global| {
856920 try writer.writeByte(wasm.valtype(global.global_type.valtype));
857921 try writer.writeByte(@boolToInt(global.global_type.mutable));
858922 try emitInit(writer, global.init);
......@@ -863,7 +927,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
863927 header_offset,
864928 .global,
865929 @intCast(u32, (try file.getPos()) - header_offset - header_size),
866 @intCast(u32, self.globals.items.len),
930 @intCast(u32, self.wasm_globals.items.len),
867931 );
868932 }
869933
......@@ -1039,7 +1103,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
10391103
10401104 var funcs = try std.ArrayList(Name).initCapacity(self.base.allocator, self.functions.items.len + self.imported_functions_count);
10411105 defer funcs.deinit();
1042 var globals = try std.ArrayList(Name).initCapacity(self.base.allocator, self.globals.items.len);
1106 var globals = try std.ArrayList(Name).initCapacity(self.base.allocator, self.wasm_globals.items.len);
10431107 defer globals.deinit();
10441108 var segments = try std.ArrayList(Name).initCapacity(self.base.allocator, self.data_segments.count());
10451109 defer segments.deinit();
src/link/Wasm/Atom.zig+1-1
......@@ -50,7 +50,7 @@ pub fn deinit(self: *Atom, gpa: Allocator) void {
5050 self.relocs.deinit(gpa);
5151 self.code.deinit(gpa);
5252
53 while (self.locals.popOrNull()) |*local| {
53 while (self.locals.items) |*local| {
5454 local.deinit(gpa);
5555 }
5656 self.locals.deinit(gpa);
src/link/Wasm/Object.zig created+847
......@@ -0,0 +1,847 @@
1//! Object represents a wasm object file. When initializing a new
2//! `Object`, it will parse the contents of a given file handler, and verify
3//! the data on correctness. The result can then be used by the linker.
4const Object = @This();
5
6const Atom = @import("Atom.zig");
7const types = @import("types.zig");
8const std = @import("std");
9const Wasm = @import("Wasm.zig");
10const Symbol = @import("Symbol.zig");
11
12const Allocator = std.mem.Allocator;
13const leb = std.leb;
14const meta = std.meta;
15
16const log = std.log.scoped(.zwld);
17
18/// Wasm spec version used for this `Object`
19version: u32 = 0,
20/// The entire object file is read and parsed in a single pass.
21/// For this reason it's a lot simpler to use an arena and store the entire
22/// state after parsing. This also allows to free all memory at once.
23arena: std.heap.ArenaAllocator.State = .{},
24/// The file descriptor that represents the wasm object file.
25file: ?std.fs.File = null,
26/// Name (read path) of the object file.
27name: []const u8,
28/// Parsed type section
29types: []const std.wasm.Type = &.{},
30/// A list of all imports for this module
31imports: []std.wasm.Import = &.{},
32/// Parsed function section
33functions: []std.wasm.Func = &.{},
34/// Parsed table section
35tables: []std.wasm.Table = &.{},
36/// Parsed memory section
37memories: []const std.wasm.Memory = &.{},
38/// Parsed global section
39globals: []std.wasm.Global = &.{},
40/// Parsed export section
41exports: []const std.wasm.Export = &.{},
42/// Parsed element section
43elements: []const std.wasm.Element = &.{},
44/// Represents the function ID that must be called on startup.
45/// This is `null` by default as runtimes may determine the startup
46/// function themselves. This is essentially legacy.
47start: ?u32 = null,
48/// A slice of features that tell the linker what features are mandatory,
49/// used (or therefore missing) and must generate an error when another
50/// object uses features that are not supported by the other.
51features: []const types.Feature = &.{},
52/// A table that maps the relocations we must perform where the key represents
53/// the section that the list of relocations applies to.
54relocations: std.AutoArrayHashMapUnmanaged(u32, []types.Relocation) = .{},
55/// Table of symbols belonging to this Object file
56symtable: []Symbol = &.{},
57/// Extra metadata about the linking section, such as alignment of segments and their name
58segment_info: []const types.Segment = &.{},
59/// A sequence of function initializers that must be called on startup
60init_funcs: []const types.InitFunc = &.{},
61/// Comdat information
62comdat_info: []const types.Comdat = &.{},
63/// Represents non-synthetic sections that can essentially be mem-cpy'd into place
64/// after performing relocations.
65relocatable_data: []RelocatableData = &.{},
66
67/// Represents a single item within a section (depending on its `type`)
68const RelocatableData = struct {
69 /// The type of the relocatable data
70 type: enum { data, code, custom },
71 /// Pointer to the data of the segment, where it's length is written to `size`
72 data: [*]u8,
73 /// The size in bytes of the data representing the segment within the section
74 size: u32,
75 /// The index within the section itself
76 index: u32,
77 /// The offset within the section where the data starts
78 offset: u32,
79 /// Represents the index of the section it belongs to
80 section_index: u32,
81
82 /// Returns the alignment of the segment, by retrieving it from the segment
83 /// meta data of the given object file.
84 /// NOTE: Alignment is encoded as a power of 2, so we shift the symbol's
85 /// alignment to retrieve the natural alignment.
86 pub fn getAlignment(self: RelocatableData, object: *const Object) u32 {
87 if (self.type != .data) return 1;
88 const data_alignment = object.segment_info[self.index].alignment;
89 if (data_alignment == 0) return 1;
90 // Decode from power of 2 to natural alignment
91 return @as(u32, 1) << @intCast(u5, data_alignment);
92 }
93
94 /// Returns the symbol kind that corresponds to the relocatable section
95 pub fn getSymbolKind(self: RelocatableData) Symbol.Tag {
96 return switch (self.type) {
97 .data => .data,
98 .code => .function,
99 .custom => .section,
100 };
101 }
102};
103
104pub const InitError = error{NotObjectFile} || ParseError || std.fs.File.ReadError;
105
106/// Initializes a new `Object` from a wasm object file.
107pub fn init(gpa: Allocator, file: std.fs.File, path: []const u8) InitError!Object {
108 var object: Object = .{
109 .file = file,
110 .name = path,
111 };
112
113 var arena = std.heap.ArenaAllocator.init(gpa);
114 errdefer arena.deinit();
115
116 var is_object_file: bool = false;
117 try object.parse(arena.allocator(), file.reader(), &is_object_file);
118 object.arena = arena.state;
119 if (!is_object_file) return error.NotObjectFile;
120
121 return object;
122}
123
124/// Frees all memory of `Object` at once. The given `Allocator` must be
125/// the same allocator that was used when `init` was called.
126pub fn deinit(self: *Object, gpa: Allocator) void {
127 self.arena.promote(gpa).deinit();
128 self.* = undefined;
129}
130
131/// Finds the import within the list of imports from a given kind and index of that kind.
132/// Asserts the import exists
133pub fn findImport(self: *const Object, import_kind: std.wasm.ExternalKind, index: u32) *std.wasm.Import {
134 var i: u32 = 0;
135 return for (self.imports) |*import| {
136 if (std.meta.activeTag(import.kind) == import_kind) {
137 if (i == index) return import;
138 i += 1;
139 }
140 } else unreachable; // Only existing imports are allowed to be found
141}
142
143/// Counts the entries of imported `kind` and returns the result
144pub fn importedCountByKind(self: *const Object, kind: std.wasm.ExternalKind) u32 {
145 var i: u32 = 0;
146 return for (self.imports) |imp| {
147 if (@as(std.wasm.ExternalKind, imp.kind) == kind) i += 1;
148 } else i;
149}
150
151/// Returns a table by a given id, rather than by its index within the list.
152pub fn getTable(self: *const Object, id: u32) *std.wasm.Table {
153 return for (self.tables) |*table| {
154 if (table.table_idx == id) break table;
155 } else unreachable;
156}
157
158/// Checks if the object file is an MVP version.
159/// When that's the case, we check if there's an import table definiton with its name
160/// set to '__indirect_function_table". When that's also the case,
161/// we initialize a new table symbol that corresponds to that import and return that symbol.
162///
163/// When the object file is *NOT* MVP, we return `null`.
164fn checkLegacyIndirectFunctionTable(self: *Object) !?Symbol {
165 var table_count: usize = 0;
166 for (self.symtable) |sym| {
167 if (sym.tag == .table) table_count += 1;
168 }
169
170 const import_table_count = self.importedCountByKind(.table);
171
172 // For each import table, we also have a symbol so this is not a legacy object file
173 if (import_table_count == table_count) return null;
174
175 if (table_count != 0) {
176 log.err("Expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{
177 import_table_count,
178 table_count,
179 });
180 return error.MissingTableSymbols;
181 }
182
183 // MVP object files cannot have any table definitions, only imports (for the indirect function table).
184 if (self.tables.len > 0) {
185 log.err("Unexpected table definition without representing table symbols.", .{});
186 return error.UnexpectedTable;
187 }
188
189 if (import_table_count != 1) {
190 log.err("Found more than one table import, but no representing table symbols", .{});
191 return error.MissingTableSymbols;
192 }
193
194 var table_import: std.wasm.Import = for (self.imports) |imp| {
195 if (imp.kind == .table) {
196 break imp;
197 }
198 } else unreachable;
199
200 if (!std.mem.eql(u8, table_import.name, "__indirect_function_table")) {
201 log.err("Non-indirect function table import '{s}' is missing a corresponding symbol", .{table_import.name});
202 return error.MissingTableSymbols;
203 }
204
205 var table_symbol: Symbol = .{
206 .flags = 0,
207 .name = table_import.name,
208 .tag = .table,
209 .index = 0,
210 };
211 table_symbol.setFlag(.WASM_SYM_UNDEFINED);
212 table_symbol.setFlag(.WASM_SYM_NO_STRIP);
213 return table_symbol;
214}
215
216/// Error set containing parsing errors.
217/// Merged with reader's errorset by `Parser`
218pub const ParseError = error{
219 /// The magic byte is either missing or does not contain \0Asm
220 InvalidMagicByte,
221 /// The wasm version is either missing or does not match the supported version.
222 InvalidWasmVersion,
223 /// Expected the functype byte while parsing the Type section but did not find it.
224 ExpectedFuncType,
225 /// Missing an 'end' opcode when defining a constant expression.
226 MissingEndForExpression,
227 /// Missing an 'end' opcode at the end of a body expression.
228 MissingEndForBody,
229 /// The size defined in the section code mismatches with the actual payload size.
230 MalformedSection,
231 /// Stream has reached the end. Unreachable for caller and must be handled internally
232 /// by the parser.
233 EndOfStream,
234 /// Ran out of memory when allocating.
235 OutOfMemory,
236 /// A non-zero flag was provided for comdat info
237 UnexpectedValue,
238 /// An import symbol contains an index to an import that does
239 /// not exist, or no imports were defined.
240 InvalidIndex,
241 /// The section "linking" contains a version that is not supported.
242 UnsupportedVersion,
243 /// When reading the data in leb128 compressed format, its value was overflown.
244 Overflow,
245 /// Found table definitions but no corresponding table symbols
246 MissingTableSymbols,
247 /// Did not expect a table definiton, but did find one
248 UnexpectedTable,
249 /// Object file contains a feature that is unknown to the linker
250 UnknownFeature,
251};
252
253fn parse(self: *Object, gpa: Allocator, reader: anytype, is_object_file: *bool) Parser(@TypeOf(reader)).Error!void {
254 var parser = Parser(@TypeOf(reader)).init(self, reader);
255 return parser.parseObject(gpa, is_object_file);
256}
257
258fn Parser(comptime ReaderType: type) type {
259 return struct {
260 const Self = @This();
261 const Error = ReaderType.Error || ParseError;
262
263 reader: std.io.CountingReader(ReaderType),
264 /// Object file we're building
265 object: *Object,
266
267 fn init(object: *Object, reader: ReaderType) Self {
268 return .{ .object = object, .reader = std.io.countingReader(reader) };
269 }
270
271 /// Verifies that the first 4 bytes contains \0Asm
272 fn verifyMagicBytes(self: *Self) Error!void {
273 var magic_bytes: [4]u8 = undefined;
274
275 try self.reader.reader().readNoEof(&magic_bytes);
276 if (!std.mem.eql(u8, &magic_bytes, &std.wasm.magic)) {
277 log.debug("Invalid magic bytes '{s}'", .{&magic_bytes});
278 return error.InvalidMagicByte;
279 }
280 }
281
282 fn parseObject(self: *Self, gpa: Allocator, is_object_file: *bool) Error!void {
283 try self.verifyMagicBytes();
284 const version = try self.reader.reader().readIntLittle(u32);
285
286 self.object.version = version;
287 var relocatable_data = std.ArrayList(RelocatableData).init(gpa);
288 defer relocatable_data.deinit();
289
290 var section_index: u32 = 0;
291 while (self.reader.reader().readByte()) |byte| : (section_index += 1) {
292 const len = try readLeb(u32, self.reader.reader());
293 const reader = std.io.limitedReader(self.reader.reader(), len).reader();
294 switch (@intToEnum(std.wasm.Section, byte)) {
295 .custom => {
296 const name_len = try readLeb(u32, reader);
297 const name = try gpa.alloc(u8, name_len);
298 defer gpa.free(name);
299 try reader.readNoEof(name);
300
301 if (std.mem.eql(u8, name, "linking")) {
302 is_object_file.* = true;
303 try self.parseMetadata(gpa, reader.context.bytes_left);
304 } else if (std.mem.startsWith(u8, name, "reloc")) {
305 try self.parseRelocations(gpa);
306 } else if (std.mem.eql(u8, name, "target_features")) {
307 try self.parseFeatures(gpa);
308 } else {
309 try reader.skipBytes(reader.context.bytes_left, .{});
310 }
311 },
312 .type => {
313 for (try readVec(&self.object.types, reader, gpa)) |*type_val| {
314 if ((try reader.readByte()) != std.wasm.function_type) return error.ExpectedFuncType;
315
316 for (try readVec(&type_val.params, reader, gpa)) |*param| {
317 param.* = try readEnum(std.wasm.Valtype, reader);
318 }
319
320 for (try readVec(&type_val.returns, reader, gpa)) |*result| {
321 result.* = try readEnum(std.wasm.Valtype, reader);
322 }
323 }
324 try assertEnd(reader);
325 },
326 .import => {
327 for (try readVec(&self.object.imports, reader, gpa)) |*import| {
328 const module_len = try readLeb(u32, reader);
329 const module_name = try gpa.alloc(u8, module_len);
330 try reader.readNoEof(module_name);
331
332 const name_len = try readLeb(u32, reader);
333 const name = try gpa.alloc(u8, name_len);
334 try reader.readNoEof(name);
335
336 const kind = try readEnum(std.wasm.ExternalKind, reader);
337 const kind_value: std.wasm.Import.Kind = switch (kind) {
338 .function => .{ .function = try readLeb(u32, reader) },
339 .memory => .{ .memory = try readLimits(reader) },
340 .global => .{ .global = .{
341 .valtype = try readEnum(std.wasm.Valtype, reader),
342 .mutable = (try reader.readByte()) == 0x01,
343 } },
344 .table => .{ .table = .{
345 .reftype = try readEnum(std.wasm.RefType, reader),
346 .limits = try readLimits(reader),
347 } },
348 };
349
350 import.* = .{
351 .module_name = module_name,
352 .name = name,
353 .kind = kind_value,
354 };
355 }
356 try assertEnd(reader);
357 },
358 .function => {
359 for (try readVec(&self.object.functions, reader, gpa)) |*func| {
360 func.* = .{ .type_index = try readLeb(u32, reader) };
361 }
362 try assertEnd(reader);
363 },
364 .table => {
365 for (try readVec(&self.object.tables, reader, gpa)) |*table| {
366 table.* = .{
367 .reftype = try readEnum(std.wasm.RefType, reader),
368 .limits = try readLimits(reader),
369 };
370 }
371 try assertEnd(reader);
372 },
373 .memory => {
374 for (try readVec(&self.object.memories, reader, gpa)) |*memory| {
375 memory.* = .{ .limits = try readLimits(reader) };
376 }
377 try assertEnd(reader);
378 },
379 .global => {
380 for (try readVec(&self.object.globals, reader, gpa)) |*global| {
381 global.* = .{
382 .global_type = .{
383 .valtype = try readEnum(std.wasm.Valtype, reader),
384 .mutable = (try reader.readByte()) == 0x01,
385 },
386 .init = try readInit(reader),
387 };
388 }
389 try assertEnd(reader);
390 },
391 .@"export" => {
392 for (try readVec(&self.object.exports, reader, gpa)) |*exp| {
393 const name_len = try readLeb(u32, reader);
394 const name = try gpa.alloc(u8, name_len);
395 try reader.readNoEof(name);
396 exp.* = .{
397 .name = name,
398 .kind = try readEnum(std.wasm.ExternalKind, reader),
399 .index = try readLeb(u32, reader),
400 };
401 }
402 try assertEnd(reader);
403 },
404 .start => {
405 self.object.start = try readLeb(u32, reader);
406 try assertEnd(reader);
407 },
408 .element => {
409 for (try readVec(&self.object.elements, reader, gpa)) |*elem| {
410 elem.table_index = try readLeb(u32, reader);
411 elem.offset = try readInit(reader);
412
413 for (try readVec(&elem.func_indexes, reader, gpa)) |*idx| {
414 idx.* = try readLeb(u32, reader);
415 }
416 }
417 try assertEnd(reader);
418 },
419 .code => {
420 var start = reader.context.bytes_left;
421 var index: u32 = 0;
422 const count = try readLeb(u32, reader);
423 while (index < count) : (index += 1) {
424 const code_len = try readLeb(u32, reader);
425 const offset = @intCast(u32, start - reader.context.bytes_left);
426 const data = try gpa.alloc(u8, code_len);
427 try reader.readNoEof(data);
428 try relocatable_data.append(.{
429 .type = .code,
430 .data = data.ptr,
431 .size = code_len,
432 .index = self.object.importedCountByKind(.function) + index,
433 .offset = offset,
434 .section_index = section_index,
435 });
436 }
437 },
438 .data => {
439 var start = reader.context.bytes_left;
440 var index: u32 = 0;
441 const count = try readLeb(u32, reader);
442 while (index < count) : (index += 1) {
443 const flags = try readLeb(u32, reader);
444 const data_offset = try readInit(reader);
445 _ = flags; // TODO: Do we need to check flags to detect passive/active memory?
446 _ = data_offset;
447 const data_len = try readLeb(u32, reader);
448 const offset = @intCast(u32, start - reader.context.bytes_left);
449 const data = try gpa.alloc(u8, data_len);
450 try reader.readNoEof(data);
451 try relocatable_data.append(.{
452 .type = .data,
453 .data = data.ptr,
454 .size = data_len,
455 .index = index,
456 .offset = offset,
457 .section_index = section_index,
458 });
459 }
460 },
461 else => try self.reader.reader().skipBytes(len, .{}),
462 }
463 } else |err| switch (err) {
464 error.EndOfStream => {}, // finished parsing the file
465 else => |e| return e,
466 }
467 self.object.relocatable_data = relocatable_data.toOwnedSlice();
468 }
469
470 /// Based on the "features" custom section, parses it into a list of
471 /// features that tell the linker what features were enabled and may be mandatory
472 /// to be able to link.
473 /// Logs an info message when an undefined feature is detected.
474 fn parseFeatures(self: *Self, gpa: Allocator) !void {
475 const reader = self.reader.reader();
476 for (try readVec(&self.object.features, reader, gpa)) |*feature| {
477 const prefix = try readEnum(types.Feature.Prefix, reader);
478 const name_len = try leb.readULEB128(u32, reader);
479 const name = try gpa.alloc(u8, name_len);
480 try reader.readNoEof(name);
481
482 const tag = types.known_features.get(name) orelse {
483 log.err("Object file contains unknown feature: {s}", .{name});
484 return error.UnknownFeature;
485 };
486 feature.* = .{
487 .prefix = prefix,
488 .tag = tag,
489 };
490 }
491 }
492
493 /// Parses a "reloc" custom section into a list of relocations.
494 /// The relocations are mapped into `Object` where the key is the section
495 /// they apply to.
496 fn parseRelocations(self: *Self, gpa: Allocator) !void {
497 const reader = self.reader.reader();
498 const section = try leb.readULEB128(u32, reader);
499 const count = try leb.readULEB128(u32, reader);
500 const relocations = try gpa.alloc(types.Relocation, count);
501
502 log.debug("Found {d} relocations for section ({d})", .{
503 count,
504 section,
505 });
506
507 for (relocations) |*relocation| {
508 const rel_type = try leb.readULEB128(u8, reader);
509 const rel_type_enum = @intToEnum(types.Relocation.RelocationType, rel_type);
510 relocation.* = .{
511 .relocation_type = rel_type_enum,
512 .offset = try leb.readULEB128(u32, reader),
513 .index = try leb.readULEB128(u32, reader),
514 .addend = if (rel_type_enum.addendIsPresent()) try leb.readULEB128(u32, reader) else null,
515 };
516 log.debug("Found relocation: type({s}) offset({d}) index({d}) addend({d})", .{
517 @tagName(relocation.relocation_type),
518 relocation.offset,
519 relocation.index,
520 relocation.addend,
521 });
522 }
523
524 try self.object.relocations.putNoClobber(gpa, section, relocations);
525 }
526
527 /// Parses the "linking" custom section. Versions that are not
528 /// supported will be an error. `payload_size` is required to be able
529 /// to calculate the subsections we need to parse, as that data is not
530 /// available within the section itself.
531 fn parseMetadata(self: *Self, gpa: Allocator, payload_size: usize) !void {
532 var limited = std.io.limitedReader(self.reader.reader(), payload_size);
533 const limited_reader = limited.reader();
534
535 const version = try leb.readULEB128(u32, limited_reader);
536 log.debug("Link meta data version: {d}", .{version});
537 if (version != 2) return error.UnsupportedVersion;
538
539 while (limited.bytes_left > 0) {
540 try self.parseSubsection(gpa, limited_reader);
541 }
542 }
543
544 /// Parses a `spec.Subsection`.
545 /// The `reader` param for this is to provide a `LimitedReader`, which allows
546 /// us to only read until a max length.
547 ///
548 /// `self` is used to provide access to other sections that may be needed,
549 /// such as access to the `import` section to find the name of a symbol.
550 fn parseSubsection(self: *Self, gpa: Allocator, reader: anytype) !void {
551 const sub_type = try leb.readULEB128(u8, reader);
552 log.debug("Found subsection: {s}", .{@tagName(@intToEnum(types.SubsectionType, sub_type))});
553 const payload_len = try leb.readULEB128(u32, reader);
554 if (payload_len == 0) return;
555
556 var limited = std.io.limitedReader(reader, payload_len);
557 const limited_reader = limited.reader();
558
559 // every subsection contains a 'count' field
560 const count = try leb.readULEB128(u32, limited_reader);
561
562 switch (@intToEnum(types.SubsectionType, sub_type)) {
563 .WASM_SEGMENT_INFO => {
564 const segments = try gpa.alloc(types.Segment, count);
565 for (segments) |*segment| {
566 const name_len = try leb.readULEB128(u32, reader);
567 const name = try gpa.alloc(u8, name_len);
568 try reader.readNoEof(name);
569 segment.* = .{
570 .name = name,
571 .alignment = try leb.readULEB128(u32, reader),
572 .flags = try leb.readULEB128(u32, reader),
573 };
574 log.debug("Found segment: {s} align({d}) flags({b})", .{
575 segment.name,
576 segment.alignment,
577 segment.flags,
578 });
579 }
580 self.object.segment_info = segments;
581 },
582 .WASM_INIT_FUNCS => {
583 const funcs = try gpa.alloc(types.InitFunc, count);
584 for (funcs) |*func| {
585 func.* = .{
586 .priority = try leb.readULEB128(u32, reader),
587 .symbol_index = try leb.readULEB128(u32, reader),
588 };
589 log.debug("Found function - prio: {d}, index: {d}", .{ func.priority, func.symbol_index });
590 }
591 self.object.init_funcs = funcs;
592 },
593 .WASM_COMDAT_INFO => {
594 const comdats = try gpa.alloc(types.Comdat, count);
595 for (comdats) |*comdat| {
596 const name_len = try leb.readULEB128(u32, reader);
597 const name = try gpa.alloc(u8, name_len);
598 try reader.readNoEof(name);
599
600 const flags = try leb.readULEB128(u32, reader);
601 if (flags != 0) {
602 return error.UnexpectedValue;
603 }
604
605 const symbol_count = try leb.readULEB128(u32, reader);
606 const symbols = try gpa.alloc(types.ComdatSym, symbol_count);
607 for (symbols) |*symbol| {
608 symbol.* = .{
609 .kind = @intToEnum(types.ComdatSym.Type, try leb.readULEB128(u8, reader)),
610 .index = try leb.readULEB128(u32, reader),
611 };
612 }
613
614 comdat.* = .{
615 .name = name,
616 .flags = flags,
617 .symbols = symbols,
618 };
619 }
620
621 self.object.comdat_info = comdats;
622 },
623 .WASM_SYMBOL_TABLE => {
624 var symbols = try std.ArrayList(Symbol).initCapacity(gpa, count);
625
626 var i: usize = 0;
627 while (i < count) : (i += 1) {
628 const symbol = symbols.addOneAssumeCapacity();
629 symbol.* = try self.parseSymbol(gpa, reader);
630 log.debug("Found symbol: type({s}) name({s}) flags(0b{b:0>8})", .{
631 @tagName(symbol.tag),
632 symbol.name,
633 symbol.flags,
634 });
635 }
636
637 // we found all symbols, check for indirect function table
638 // in case of an MVP object file
639 if (try self.object.checkLegacyIndirectFunctionTable()) |symbol| {
640 try symbols.append(symbol);
641 log.debug("Found legacy indirect function table. Created symbol", .{});
642 }
643
644 self.object.symtable = symbols.toOwnedSlice();
645 },
646 }
647 }
648
649 /// Parses the symbol information based on its kind,
650 /// requires access to `Object` to find the name of a symbol when it's
651 /// an import and flag `WASM_SYM_EXPLICIT_NAME` is not set.
652 fn parseSymbol(self: *Self, gpa: Allocator, reader: anytype) !Symbol {
653 const tag = @intToEnum(Symbol.Tag, try leb.readULEB128(u8, reader));
654 const flags = try leb.readULEB128(u32, reader);
655 var symbol: Symbol = .{
656 .flags = flags,
657 .tag = tag,
658 .name = undefined,
659 .index = undefined,
660 };
661
662 switch (tag) {
663 .data => {
664 const name_len = try leb.readULEB128(u32, reader);
665 const name = try gpa.alloc(u8, name_len);
666 try reader.readNoEof(name);
667 symbol.name = name;
668
669 // Data symbols only have the following fields if the symbol is defined
670 if (symbol.isDefined()) {
671 symbol.index = try leb.readULEB128(u32, reader);
672 // @TODO: We should verify those values
673 _ = try leb.readULEB128(u32, reader);
674 _ = try leb.readULEB128(u32, reader);
675 }
676 },
677 .section => {
678 symbol.index = try leb.readULEB128(u32, reader);
679 symbol.name = @tagName(symbol.tag);
680 },
681 else => {
682 symbol.index = try leb.readULEB128(u32, reader);
683 var maybe_import: ?*std.wasm.Import = null;
684
685 const is_undefined = symbol.isUndefined();
686 if (is_undefined) {
687 maybe_import = self.object.findImport(symbol.externalType(), symbol.index);
688 }
689 const explicit_name = symbol.hasFlag(.WASM_SYM_EXPLICIT_NAME);
690 if (!(is_undefined and !explicit_name)) {
691 const name_len = try leb.readULEB128(u32, reader);
692 const name = try gpa.alloc(u8, name_len);
693 try reader.readNoEof(name);
694 symbol.name = name;
695 } else {
696 symbol.name = maybe_import.?.name;
697 }
698 },
699 }
700 return symbol;
701 }
702 };
703}
704
705/// First reads the count from the reader and then allocate
706/// a slice of ptr child's element type.
707fn readVec(ptr: anytype, reader: anytype, gpa: Allocator) ![]ElementType(@TypeOf(ptr)) {
708 const len = try readLeb(u32, reader);
709 const slice = try gpa.alloc(ElementType(@TypeOf(ptr)), len);
710 ptr.* = slice;
711 return slice;
712}
713
714fn ElementType(comptime ptr: type) type {
715 return meta.Elem(meta.Child(ptr));
716}
717
718/// Uses either `readILEB128` or `readULEB128` depending on the
719/// signedness of the given type `T`.
720/// Asserts `T` is an integer.
721fn readLeb(comptime T: type, reader: anytype) !T {
722 if (comptime std.meta.trait.isSignedInt(T)) {
723 return try leb.readILEB128(T, reader);
724 } else {
725 return try leb.readULEB128(T, reader);
726 }
727}
728
729/// Reads an enum type from the given reader.
730/// Asserts `T` is an enum
731fn readEnum(comptime T: type, reader: anytype) !T {
732 switch (@typeInfo(T)) {
733 .Enum => |enum_type| return @intToEnum(T, try readLeb(enum_type.tag_type, reader)),
734 else => @compileError("T must be an enum. Instead was given type " ++ @typeName(T)),
735 }
736}
737
738fn readLimits(reader: anytype) !std.wasm.Limits {
739 const flags = try readLeb(u1, reader);
740 const min = try readLeb(u32, reader);
741 return std.wasm.Limits{
742 .min = min,
743 .max = if (flags == 0) null else try readLeb(u32, reader),
744 };
745}
746
747fn readInit(reader: anytype) !std.wasm.InitExpression {
748 const opcode = try reader.readByte();
749 const init_expr: std.wasm.InitExpression = switch (@intToEnum(std.wasm.Opcode, opcode)) {
750 .i32_const => .{ .i32_const = try readLeb(i32, reader) },
751 .global_get => .{ .global_get = try readLeb(u32, reader) },
752 else => @panic("TODO: initexpression for other opcodes"),
753 };
754
755 if ((try readEnum(std.wasm.Opcode, reader)) != .end) return error.MissingEndForExpression;
756 return init_expr;
757}
758
759fn assertEnd(reader: anytype) !void {
760 var buf: [1]u8 = undefined;
761 const len = try reader.read(&buf);
762 if (len != 0) return error.MalformedSection;
763 if (reader.context.bytes_left != 0) return error.MalformedSection;
764}
765
766/// Parses an object file into atoms, for code and data sections
767pub fn parseIntoAtoms(self: *Object, gpa: Allocator, object_index: u16, wasm_bin: *Wasm) !void {
768 log.debug("Parsing data section into atoms", .{});
769 const Key = struct {
770 kind: Symbol.Tag,
771 index: u32,
772 };
773 var symbol_for_segment = std.AutoArrayHashMap(Key, u32).init(gpa);
774 defer symbol_for_segment.deinit();
775
776 for (self.symtable) |symbol, symbol_index| {
777 switch (symbol.tag) {
778 .function, .data => if (!symbol.isUndefined()) {
779 try symbol_for_segment.putNoClobber(
780 .{ .kind = symbol.tag, .index = symbol.index },
781 @intCast(u32, symbol_index),
782 );
783 },
784 else => continue,
785 }
786 }
787
788 for (self.relocatable_data) |relocatable_data, index| {
789 const sym_index = symbol_for_segment.get(.{
790 .kind = relocatable_data.getSymbolKind(),
791 .index = @intCast(u32, relocatable_data.index),
792 }) orelse continue; // encountered a segment we do not create an atom for
793 const final_index = try wasm_bin.getMatchingSegment(gpa, object_index, @intCast(u32, index));
794
795 const atom = try Atom.create(gpa);
796 errdefer atom.deinit(gpa);
797
798 try wasm_bin.managed_atoms.append(gpa, atom);
799 atom.file = object_index;
800 atom.size = relocatable_data.size;
801 atom.alignment = relocatable_data.getAlignment(self);
802 atom.sym_index = sym_index;
803
804 const relocations: []types.Relocation = self.relocations.get(relocatable_data.section_index) orelse &.{};
805 for (relocations) |*relocation| {
806 if (isInbetween(relocatable_data.offset, atom.size, relocation.offset)) {
807 // set the offset relative to the offset of the segment itself,
808 // rather than within the entire section.
809 relocation.offset -= relocatable_data.offset;
810 try atom.relocs.append(gpa, relocation.*);
811
812 if (relocation.isTableIndex()) {
813 try wasm_bin.elements.appendSymbol(gpa, .{
814 .file = object_index,
815 .sym_index = relocation.index,
816 });
817 }
818 }
819 }
820
821 // TODO: Replace `atom.code` from an existing slice to a pointer to the data
822 try atom.code.appendSlice(gpa, relocatable_data.data[0..relocatable_data.size]);
823
824 const segment: *Wasm.Segment = &wasm_bin.segments.items[final_index];
825 segment.alignment = std.math.max(segment.alignment, atom.alignment);
826 segment.size = std.mem.alignForwardGeneric(
827 u32,
828 std.mem.alignForwardGeneric(u32, segment.size, atom.alignment) + atom.size,
829 segment.alignment,
830 );
831
832 if (wasm_bin.atoms.getPtr(final_index)) |last| {
833 last.*.next = atom;
834 atom.prev = last.*;
835 last.* = atom;
836 } else {
837 try wasm_bin.atoms.putNoClobber(gpa, final_index, atom);
838 }
839 log.debug("Parsed into atom: '{s}'", .{self.symtable[atom.sym_index].name});
840 }
841}
842
843/// Verifies if a given value is in between a minimum -and maximum value.
844/// The maxmimum value is calculated using the length, both start and end are inclusive.
845inline fn isInbetween(min: u32, length: u32, value: u32) bool {
846 return value >= min and value <= min + length;
847}