authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-26 20:07:34+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-27 15:11:03+01:00
log6e88df44a29e0c30c341f113cf4771e08fc1f0fe
tree93f5cab9205d166060d39fda5c38544d093f4bd1
parenta54ac0888542d027b09e5c43729a7cfbe4a51393
signaturelock-open Commit is signed but in an unrecognized format.

wasm-linker: Link into binary during flush

This contains a few additions: - Proper stack pointer calculation keeping alignment in mind. - Setting up memory layout (including user flags). - Export or import memory - Handle 'easy' linker tasks during incremental compilation, while offloading heavy-tracking/computation tasks to `flush()` - This architecture allows us to easily integrate with the rest of 'zwld' to implement linking stage2 code with external object files.

2 files changed, 272 insertions(+), 142 deletions(-)

src/link/Wasm.zig+262-116
...@@ -39,8 +39,6 @@ llvm_object: ?*LlvmObject = null,...@@ -39,8 +39,6 @@ llvm_object: ?*LlvmObject = null,
39/// to support existing code.39/// to support existing code.
40/// TODO: Allow setting this through a flag?40/// TODO: Allow setting this through a flag?
41host_name: []const u8 = "env",41host_name: []const u8 = "env",
42/// The last `DeclBlock` that was initialized will be saved here.
43last_atom: ?*Atom = null,
44/// List of all `Decl` that are currently alive.42/// List of all `Decl` that are currently alive.
45/// This is ment for bookkeeping so we can safely cleanup all codegen memory43/// This is ment for bookkeeping so we can safely cleanup all codegen memory
46/// when calling `deinit`44/// when calling `deinit`
...@@ -57,10 +55,8 @@ code_section_index: ?u32 = null,...@@ -57,10 +55,8 @@ code_section_index: ?u32 = null,
57/// The count of imported functions. This number will be appended55/// The count of imported functions. This number will be appended
58/// to the function indexes as their index starts at the lowest non-extern function.56/// to the function indexes as their index starts at the lowest non-extern function.
59imported_functions_count: u32 = 0,57imported_functions_count: u32 = 0,
60/// List of all 'extern' declarations58/// Map of symbol indexes, represented by its `wasm.Import`
61imports: std.ArrayListUnmanaged(wasm.Import) = .{},59imports: std.AutoHashMapUnmanaged(u32, wasm.Import) = .{},
62/// List of indexes of symbols representing extern declarations.
63import_symbols: std.ArrayListUnmanaged(u32) = .{},
64/// Represents non-synthetic section entries.60/// Represents non-synthetic section entries.
65/// Used for code, data and custom sections.61/// Used for code, data and custom sections.
66segments: std.ArrayListUnmanaged(Segment) = .{},62segments: std.ArrayListUnmanaged(Segment) = .{},
...@@ -77,6 +73,8 @@ func_types: std.ArrayListUnmanaged(wasm.Type) = .{},...@@ -77,6 +73,8 @@ func_types: std.ArrayListUnmanaged(wasm.Type) = .{},
77functions: std.ArrayListUnmanaged(wasm.Func) = .{},73functions: std.ArrayListUnmanaged(wasm.Func) = .{},
78/// Output global section74/// Output global section
79globals: std.ArrayListUnmanaged(wasm.Global) = .{},75globals: std.ArrayListUnmanaged(wasm.Global) = .{},
76/// Memory section
77memories: wasm.Memory = .{ .limits = .{ .min = 0, .max = null } },
8078
81/// Indirect function table, used to call function pointers79/// Indirect function table, used to call function pointers
82/// When this is non-zero, we must emit a table entry,80/// When this is non-zero, we must emit a table entry,
...@@ -180,7 +178,6 @@ pub fn deinit(self: *Wasm) void {...@@ -180,7 +178,6 @@ pub fn deinit(self: *Wasm) void {
180178
181 // free output sections179 // free output sections
182 self.imports.deinit(self.base.allocator);180 self.imports.deinit(self.base.allocator);
183 self.import_symbols.deinit(self.base.allocator);
184 self.func_types.deinit(self.base.allocator);181 self.func_types.deinit(self.base.allocator);
185 self.functions.deinit(self.base.allocator);182 self.functions.deinit(self.base.allocator);
186 self.globals.deinit(self.base.allocator);183 self.globals.deinit(self.base.allocator);
...@@ -221,6 +218,8 @@ pub fn updateFunc(self: *Wasm, module: *Module, func: *Module.Fn, air: Air, live...@@ -221,6 +218,8 @@ pub fn updateFunc(self: *Wasm, module: *Module, func: *Module.Fn, air: Air, live
221 const decl = func.owner_decl;218 const decl = func.owner_decl;
222 assert(decl.link.wasm.sym_index != 0); // Must call allocateDeclIndexes()219 assert(decl.link.wasm.sym_index != 0); // Must call allocateDeclIndexes()
223220
221 decl.link.wasm.clear();
222
224 var codegen: CodeGen = .{223 var codegen: CodeGen = .{
225 .gpa = self.base.allocator,224 .gpa = self.base.allocator,
226 .air = air,225 .air = air,
...@@ -259,6 +258,8 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {...@@ -259,6 +258,8 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
259 }258 }
260 assert(decl.link.wasm.sym_index != 0); // Must call allocateDeclIndexes()259 assert(decl.link.wasm.sym_index != 0); // Must call allocateDeclIndexes()
261260
261 decl.link.wasm.clear();
262
262 var codegen: CodeGen = .{263 var codegen: CodeGen = .{
263 .gpa = self.base.allocator,264 .gpa = self.base.allocator,
264 .air = undefined,265 .air = undefined,
...@@ -293,19 +294,14 @@ fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, result: CodeGen.Result, cod...@@ -293,19 +294,14 @@ fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, result: CodeGen.Result, cod
293 .externally_managed => |payload| payload,294 .externally_managed => |payload| payload,
294 };295 };
295296
297 if (decl.isExtern()) {
298 try self.addOrUpdateImport(decl);
299 }
300
301 if (code.len == 0) return;
296 const atom: *Atom = &decl.link.wasm;302 const atom: *Atom = &decl.link.wasm;
297 atom.size = @intCast(u32, code.len);303 atom.size = @intCast(u32, code.len);
298 try atom.code.appendSlice(self.base.allocator, code);304 try atom.code.appendSlice(self.base.allocator, code);
299
300 // If we're updating an existing decl, unplug it first
301 // to avoid infinite loops due to earlier links
302 atom.unplug();
303
304 if (decl.isExtern()) {
305 try self.createUndefinedSymbol(decl, atom.sym_index);
306 } else {
307 try self.createDefinedSymbol(decl, atom.sym_index, atom);
308 }
309}305}
310306
311pub fn updateDeclExports(307pub fn updateDeclExports(
...@@ -326,60 +322,62 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {...@@ -326,60 +322,62 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
326 if (build_options.have_llvm) {322 if (build_options.have_llvm) {
327 if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl);323 if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl);
328 }324 }
329
330 const atom = &decl.link.wasm;325 const atom = &decl.link.wasm;
331
332 if (self.last_atom == atom) {
333 self.last_atom = atom.prev;
334 }
335
336 atom.unplug();
337 self.symbols_free_list.append(self.base.allocator, atom.sym_index) catch {};326 self.symbols_free_list.append(self.base.allocator, atom.sym_index) catch {};
338 atom.deinit(self.base.allocator);327 atom.deinit(self.base.allocator);
339 _ = self.decls.remove(decl);328 _ = self.decls.remove(decl);
329
330 if (decl.isExtern()) {
331 const import = self.imports.fetchRemove(decl.link.wasm.sym_index).?.value;
332 switch (import.kind) {
333 .function => self.imported_functions_count -= 1,
334 else => unreachable,
335 }
336 }
340}337}
341338
342fn createUndefinedSymbol(self: *Wasm, decl: *Module.Decl, symbol_index: u32) !void {339fn addOrUpdateImport(self: *Wasm, decl: *Module.Decl) !void {
343 var symbol: *Symbol = &self.symbols.items[symbol_index];340 const symbol_index = decl.link.wasm.sym_index;
341 const symbol: *Symbol = &self.symbols.items[symbol_index];
344 symbol.name = decl.name;342 symbol.name = decl.name;
345 symbol.setUndefined(true);343 symbol.setUndefined(true);
346 switch (decl.ty.zigTypeTag()) {344 switch (decl.ty.zigTypeTag()) {
347 .Fn => {345 .Fn => {
348 symbol.index = self.imported_functions_count;346 const gop = try self.imports.getOrPut(self.base.allocator, symbol_index);
349 self.imported_functions_count += 1;347 if (!gop.found_existing) {
350 try self.import_symbols.append(self.base.allocator, symbol_index);348 self.imported_functions_count += 1;
351 try self.imports.append(self.base.allocator, .{349 gop.value_ptr.* = .{
352 .module_name = self.host_name,350 .module_name = self.host_name,
353 .name = std.mem.span(decl.name),351 .name = std.mem.span(symbol.name),
354 .kind = .{ .function = decl.fn_link.wasm.type_index },352 .kind = .{ .function = decl.fn_link.wasm.type_index },
355 });353 };
354 }
356 },355 },
357 else => @panic("TODO: Implement undefined symbols for non-function declarations"),356 else => @panic("TODO: Implement undefined symbols for non-function declarations"),
358 }357 }
359}358}
360359
361/// Creates a defined symbol, as well as inserts the given `atom` into the chain360fn parseDeclIntoAtom(self: *Wasm, decl: *Module.Decl) !void {
362fn createDefinedSymbol(self: *Wasm, decl: *Module.Decl, symbol_index: u32, atom: *Atom) !void {361 const atom: *Atom = &decl.link.wasm;
363 const symbol: *Symbol = &self.symbols.items[symbol_index];362 const symbol: *Symbol = &self.symbols.items[atom.sym_index];
364 symbol.name = decl.name;363 symbol.name = decl.name;
365 const final_index = switch (decl.ty.zigTypeTag()) {364 atom.alignment = decl.ty.abiAlignment(self.base.options.target);
365 const final_index: u32 = switch (decl.ty.zigTypeTag()) {
366 .Fn => result: {366 .Fn => result: {
367 const type_index = decl.fn_link.wasm.type_index;367 const fn_data = decl.fn_link.wasm;
368 const index = @intCast(u32, self.functions.items.len);368 const type_index = fn_data.type_index;
369 const index = @intCast(u32, self.functions.items.len + self.imported_functions_count);
369 try self.functions.append(self.base.allocator, .{ .type_index = type_index });370 try self.functions.append(self.base.allocator, .{ .type_index = type_index });
370 symbol.tag = .function;371 symbol.tag = .function;
371 symbol.index = index;372 symbol.index = index;
372 atom.alignment = 1;
373373
374 if (self.code_section_index == null) {374 if (self.code_section_index == null) {
375 self.code_section_index = @intCast(u32, self.segments.items.len);375 self.code_section_index = @intCast(u32, self.segments.items.len);
376 try self.segments.append(self.base.allocator, .{376 try self.segments.append(self.base.allocator, .{
377 .alignment = atom.alignment,377 .alignment = atom.alignment,
378 .size = atom.size,378 .size = atom.size,
379 .offset = atom.offset,379 .offset = 0,
380 });380 });
381 } else {
382 self.segments.items[self.code_section_index.?].size += atom.size;
383 }381 }
384382
385 break :result self.code_section_index.?;383 break :result self.code_section_index.?;
...@@ -390,11 +388,11 @@ fn createDefinedSymbol(self: *Wasm, decl: *Module.Decl, symbol_index: u32, atom:...@@ -390,11 +388,11 @@ fn createDefinedSymbol(self: *Wasm, decl: *Module.Decl, symbol_index: u32, atom:
390 self.segments.items[gop.value_ptr.*].size += atom.size;388 self.segments.items[gop.value_ptr.*].size += atom.size;
391 break :blk gop.value_ptr.*;389 break :blk gop.value_ptr.*;
392 } else blk: {390 } else blk: {
393 const index = @intCast(u32, self.segments.items.len) - @boolToInt(self.code_section_index != null);391 const index = @intCast(u32, self.segments.items.len);
394 try self.segments.append(self.base.allocator, .{392 try self.segments.append(self.base.allocator, .{
395 .alignment = atom.alignment,393 .alignment = atom.alignment,
396 .size = atom.size,394 .size = 0,
397 .offset = atom.offset,395 .offset = 0,
398 });396 });
399 gop.value_ptr.* = index;397 gop.value_ptr.* = index;
400 break :blk index;398 break :blk index;
...@@ -413,20 +411,153 @@ fn createDefinedSymbol(self: *Wasm, decl: *Module.Decl, symbol_index: u32, atom:...@@ -413,20 +411,153 @@ fn createDefinedSymbol(self: *Wasm, decl: *Module.Decl, symbol_index: u32, atom:
413 symbol.tag = .data;411 symbol.tag = .data;
414 symbol.index = info_index;412 symbol.index = info_index;
415 atom.alignment = decl.ty.abiAlignment(self.base.options.target);413 atom.alignment = decl.ty.abiAlignment(self.base.options.target);
414
416 break :result atom_index;415 break :result atom_index;
417 },416 },
418 };417 };
419418
419 const segment: *Segment = &self.segments.items[final_index];
420 segment.alignment = std.math.max(segment.alignment, atom.alignment);
421 segment.size = std.mem.alignForwardGeneric(
422 u32,
423 std.mem.alignForwardGeneric(u32, segment.size, atom.alignment) + atom.size,
424 segment.alignment,
425 );
426
420 if (self.atoms.getPtr(final_index)) |last| {427 if (self.atoms.getPtr(final_index)) |last| {
421 last.*.next = atom;428 last.*.next = atom;
422 atom.prev = last.*;429 atom.prev = last.*;
423 atom.offset = last.*.offset + last.*.size;
424 last.* = atom;430 last.* = atom;
425 } else {431 } else {
426 try self.atoms.putNoClobber(self.base.allocator, final_index, atom);432 try self.atoms.putNoClobber(self.base.allocator, final_index, atom);
427 }433 }
428}434}
429435
436fn allocateAtoms(self: *Wasm) !void {
437 var it = self.atoms.iterator();
438 while (it.next()) |entry| {
439 var atom: *Atom = entry.value_ptr.*.getFirst();
440 var offset: u32 = 0;
441 while (true) {
442 offset = std.mem.alignForwardGeneric(u32, offset, atom.alignment);
443 atom.offset = offset;
444 log.debug("Atom '{s}' allocated from 0x{x:0>8} to 0x{x:0>8} size={d}", .{
445 self.symbols.items[atom.sym_index].name,
446 offset,
447 offset + atom.size,
448 atom.size,
449 });
450 offset += atom.size;
451 atom = atom.next orelse break;
452 }
453 }
454}
455
456fn setupImports(self: *Wasm) void {
457 var function_index: u32 = 0;
458 var it = self.imports.iterator();
459 while (it.next()) |entry| {
460 const symbol = &self.symbols.items[entry.key_ptr.*];
461 const import: wasm.Import = entry.value_ptr.*;
462 switch (import.kind) {
463 .function => {
464 symbol.index = function_index;
465 function_index += 1;
466 },
467 else => unreachable,
468 }
469 }
470}
471
472/// Sets up the memory section of the wasm module, as well as the stack.
473fn setupMemory(self: *Wasm) !void {
474 log.debug("Setting up memory layout", .{});
475 const page_size = 64 * 1024;
476 const stack_size = self.base.options.stack_size_override orelse page_size * 1;
477 const stack_alignment = 16;
478 var memory_ptr: u64 = self.base.options.global_base orelse 1024;
479 memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, stack_alignment);
480
481 var offset: u32 = @intCast(u32, memory_ptr);
482 for (self.segments.items) |*segment, i| {
483 // skip 'code' segments
484 if (self.code_section_index) |index| {
485 if (index == i) continue;
486 }
487 memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, segment.alignment);
488 memory_ptr += segment.size;
489 segment.offset = offset;
490 offset += segment.size;
491 }
492
493 memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, stack_alignment);
494 memory_ptr += stack_size;
495
496 // Setup the max amount of pages
497 // For now we only support wasm32 by setting the maximum allowed memory size 2^32-1
498 const max_memory_allowed: u64 = (1 << 32) - 1;
499
500 if (self.base.options.initial_memory) |initial_memory| {
501 if (!std.mem.isAlignedGeneric(u64, initial_memory, page_size)) {
502 log.err("Initial memory must be {d}-byte aligned", .{page_size});
503 return error.MissAlignment;
504 }
505 if (memory_ptr > initial_memory) {
506 log.err("Initial memory too small, must be at least {d} bytes", .{memory_ptr});
507 return error.MemoryTooSmall;
508 }
509 if (initial_memory > max_memory_allowed) {
510 log.err("Initial memory exceeds maximum memory {d}", .{max_memory_allowed});
511 return error.MemoryTooBig;
512 }
513 memory_ptr = initial_memory;
514 }
515
516 // In case we do not import memory, but define it ourselves,
517 // set the minimum amount of pages on the memory section.
518 self.memories.limits.min = @intCast(u32, std.mem.alignForwardGeneric(u64, memory_ptr, page_size) / page_size);
519 log.debug("Total memory pages: {d}", .{self.memories.limits.min});
520
521 if (self.base.options.max_memory) |max_memory| {
522 if (!std.mem.isAlignedGeneric(u64, max_memory, page_size)) {
523 log.err("Maximum memory must be {d}-byte aligned", .{page_size});
524 return error.MissAlignment;
525 }
526 if (memory_ptr > max_memory) {
527 log.err("Maxmimum memory too small, must be at least {d} bytes", .{memory_ptr});
528 return error.MemoryTooSmall;
529 }
530 if (max_memory > max_memory_allowed) {
531 log.err("Maximum memory exceeds maxmium amount {d}", .{max_memory_allowed});
532 return error.MemoryTooBig;
533 }
534 self.memories.limits.max = @intCast(u32, max_memory / page_size);
535 log.debug("Maximum memory pages: {d}", .{self.memories.limits.max});
536 }
537
538 // We always put the stack pointer global at index 0
539 self.globals.items[0].init.i32_const = @bitCast(i32, @intCast(u32, memory_ptr));
540}
541
542fn resetState(self: *Wasm) void {
543 for (self.segment_info.items) |*segment_info| {
544 self.base.allocator.free(segment_info.name);
545 }
546 var decl_it = self.decls.keyIterator();
547 while (decl_it.next()) |decl| {
548 const atom = &decl.*.link.wasm;
549 atom.next = null;
550 atom.prev = null;
551 }
552 self.functions.clearRetainingCapacity();
553 self.segments.clearRetainingCapacity();
554 self.segment_info.clearRetainingCapacity();
555 self.data_segments.clearRetainingCapacity();
556 self.function_table.clearRetainingCapacity();
557 self.atoms.clearRetainingCapacity();
558 self.code_section_index = null;
559}
560
430pub fn flush(self: *Wasm, comp: *Compilation) !void {561pub fn flush(self: *Wasm, comp: *Compilation) !void {
431 if (build_options.have_llvm and self.base.options.use_lld) {562 if (build_options.have_llvm and self.base.options.use_lld) {
432 return self.linkWithLLD(comp);563 return self.linkWithLLD(comp);
...@@ -440,22 +571,21 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -440,22 +571,21 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
440 const tracy = trace(@src());571 const tracy = trace(@src());
441 defer tracy.end();572 defer tracy.end();
442573
443 const file = self.base.file.?;574 // When we finish/error we reset the state of the linker
444 const header_size = 5 + 1;575 // So we can rebuild the binary file on each incremental update
445 // The size of the emulated stack576 defer self.resetState();
446 const stack_size = @intCast(u32, self.base.options.stack_size_override orelse std.wasm.page_size);577 self.setupImports();
447578 var decl_it = self.decls.keyIterator();
448 var data_size: u32 = 0;579 while (decl_it.next()) |decl| {
449 for (self.segments.items) |segment, index| {580 if (decl.*.isExtern()) continue;
450 // skip 'code' segments as they do not count towards data section size581 try self.parseDeclIntoAtom(decl.*);
451 if (self.code_section_index) |code_index| {
452 if (index == code_index) continue;
453 }
454 data_size += segment.size;
455 }582 }
456583
457 // set the stack size on the global584 try self.setupMemory();
458 self.globals.items[0].init.i32_const = @bitCast(i32, data_size + stack_size);585 try self.allocateAtoms();
586
587 const file = self.base.file.?;
588 const header_size = 5 + 1;
459589
460 // No need to rewrite the magic/version header590 // No need to rewrite the magic/version header
461 try file.setEndPos(@sizeOf(@TypeOf(wasm.magic ++ wasm.version)));591 try file.setEndPos(@sizeOf(@TypeOf(wasm.magic ++ wasm.version)));
...@@ -484,42 +614,34 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -484,42 +614,34 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
484 }614 }
485615
486 // Import section616 // Import section
487 if (self.import_symbols.items.len > 0) {617 const import_mem = self.base.options.import_memory;
618 if (self.imports.count() != 0 or import_mem) {
488 const header_offset = try reserveVecSectionHeader(file);619 const header_offset = try reserveVecSectionHeader(file);
489 const writer = file.writer();620 const writer = file.writer();
490 for (self.import_symbols.items) |symbol_index| {621
491 const import_symbol = self.symbols.items[symbol_index];622 var it = self.imports.iterator();
623 while (it.next()) |entry| {
624 const import_symbol = self.symbols.items[entry.key_ptr.*];
492 std.debug.assert(import_symbol.isUndefined());625 std.debug.assert(import_symbol.isUndefined());
493 try leb.writeULEB128(writer, @intCast(u32, self.host_name.len));626 const import = entry.value_ptr.*;
494 try writer.writeAll(self.host_name);627 try emitImport(writer, import);
495
496 const name = std.mem.span(import_symbol.name);
497 try leb.writeULEB128(writer, @intCast(u32, name.len));
498 try writer.writeAll(name);
499
500 try writer.writeByte(wasm.externalKind(import_symbol.tag.externalType()));
501 const import = self.findImport(import_symbol.index, import_symbol.tag.externalType()).?;
502 switch (import.kind) {
503 .function => |type_index| try leb.writeULEB128(writer, type_index),
504 .global => |global_type| {
505 try leb.writeULEB128(writer, wasm.valtype(global_type.valtype));
506 try writer.writeByte(@boolToInt(global_type.mutable));
507 },
508 .table => |table| {
509 try leb.writeULEB128(writer, wasm.reftype(table.reftype));
510 try emitLimits(writer, table.limits);
511 },
512 .memory => |limits| {
513 try emitLimits(writer, limits);
514 },
515 }
516 }628 }
629
630 if (import_mem) {
631 const mem_imp: wasm.Import = .{
632 .module_name = self.host_name,
633 .name = "memory",
634 .kind = .{ .memory = self.memories.limits },
635 };
636 try emitImport(writer, mem_imp);
637 }
638
517 try writeVecSectionHeader(639 try writeVecSectionHeader(
518 file,640 file,
519 header_offset,641 header_offset,
520 .import,642 .import,
521 @intCast(u32, (try file.getPos()) - header_offset - header_size),643 @intCast(u32, (try file.getPos()) - header_offset - header_size),
522 @intCast(u32, self.imports.items.len),644 @intCast(u32, self.imports.count() + @boolToInt(import_mem)),
523 );645 );
524 }646 }
525647
...@@ -541,21 +663,11 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -541,21 +663,11 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
541 }663 }
542664
543 // Memory section665 // Memory section
544 {666 if (!self.base.options.import_memory) {
545 const header_offset = try reserveVecSectionHeader(file);667 const header_offset = try reserveVecSectionHeader(file);
546 const writer = file.writer();668 const writer = file.writer();
547669
548 try leb.writeULEB128(writer, @as(u32, 0));670 try emitLimits(writer, self.memories.limits);
549 // Calculate the amount of memory pages are required and write them.
550 // Wasm uses 64kB page sizes. Round up to ensure the data segments fit into the memory
551 try leb.writeULEB128(
552 writer,
553 try std.math.divCeil(
554 u32,
555 data_size + stack_size,
556 std.wasm.page_size,
557 ),
558 );
559 try writeVecSectionHeader(671 try writeVecSectionHeader(
560 file,672 file,
561 header_offset,673 header_offset,
...@@ -590,7 +702,6 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -590,7 +702,6 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
590 const header_offset = try reserveVecSectionHeader(file);702 const header_offset = try reserveVecSectionHeader(file);
591 const writer = file.writer();703 const writer = file.writer();
592 var count: u32 = 0;704 var count: u32 = 0;
593 var func_index: u32 = self.imported_functions_count;
594 for (module.decl_exports.values()) |exports| {705 for (module.decl_exports.values()) |exports| {
595 for (exports) |exprt| {706 for (exports) |exprt| {
596 // Export name length + name707 // Export name length + name
...@@ -599,11 +710,13 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -599,11 +710,13 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
599710
600 switch (exprt.exported_decl.ty.zigTypeTag()) {711 switch (exprt.exported_decl.ty.zigTypeTag()) {
601 .Fn => {712 .Fn => {
713 const target = exprt.exported_decl.link.wasm.sym_index;
714 const target_symbol = self.symbols.items[target];
715 std.debug.assert(target_symbol.tag == .function);
602 // Type of the export716 // Type of the export
603 try writer.writeByte(wasm.externalKind(.function));717 try writer.writeByte(wasm.externalKind(.function));
604 // Exported function index718 // Exported function index
605 try leb.writeULEB128(writer, func_index);719 try leb.writeULEB128(writer, target_symbol.index);
606 func_index += 1;
607 },720 },
608 else => return error.TODOImplementNonFnDeclsForWasm,721 else => return error.TODOImplementNonFnDeclsForWasm,
609 }722 }
...@@ -613,7 +726,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -613,7 +726,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
613 }726 }
614727
615 // export memory if size is not 0728 // export memory if size is not 0
616 if (data_size != 0) {729 if (!self.base.options.import_memory) {
617 try leb.writeULEB128(writer, @intCast(u32, "memory".len));730 try leb.writeULEB128(writer, @intCast(u32, "memory".len));
618 try writer.writeAll("memory");731 try writer.writeAll("memory");
619 try writer.writeByte(wasm.externalKind(.memory));732 try writer.writeByte(wasm.externalKind(.memory));
...@@ -639,7 +752,6 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -639,7 +752,6 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
639 try atom.resolveRelocs(self);752 try atom.resolveRelocs(self);
640 try leb.writeULEB128(writer, atom.size);753 try leb.writeULEB128(writer, atom.size);
641 try writer.writeAll(atom.code.items);754 try writer.writeAll(atom.code.items);
642
643 atom = atom.next orelse break;755 atom = atom.next orelse break;
644 }756 }
645 try writeVecSectionHeader(757 try writeVecSectionHeader(
...@@ -657,37 +769,47 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -657,37 +769,47 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
657 const writer = file.writer();769 const writer = file.writer();
658770
659 var it = self.data_segments.iterator();771 var it = self.data_segments.iterator();
772 var segment_count: u32 = 0;
660 while (it.next()) |entry| {773 while (it.next()) |entry| {
661 // do not output 'bss' section774 // do not output 'bss' section
662 if (std.mem.eql(u8, entry.key_ptr.*, ".bss")) continue;775 if (std.mem.eql(u8, entry.key_ptr.*, ".bss")) continue;
776 segment_count += 1;
663 const atom_index = entry.value_ptr.*;777 const atom_index = entry.value_ptr.*;
664 var atom = self.atoms.getPtr(atom_index).?.*.getFirst();778 var atom: *Atom = self.atoms.getPtr(atom_index).?.*.getFirst();
665 var segment = self.segments.items[atom_index];779 var segment = self.segments.items[atom_index];
666780
667 // flag and index to memory section (currently, there can only be 1 memory section in wasm)781 // flag and index to memory section (currently, there can only be 1 memory section in wasm)
668 try leb.writeULEB128(writer, @as(u32, 0));782 try leb.writeULEB128(writer, @as(u32, 0));
669
670 // offset into data section783 // offset into data section
671 try writer.writeByte(wasm.opcode(.i32_const));784 try emitInit(writer, .{ .i32_const = @bitCast(i32, segment.offset) });
672 try leb.writeILEB128(writer, @as(i32, 0));
673 try writer.writeByte(wasm.opcode(.end));
674
675 // offset table + data size
676 try leb.writeULEB128(writer, segment.size);785 try leb.writeULEB128(writer, segment.size);
677786
678 // fill in the offset table and the data segments787 // fill in the offset table and the data segments
679 var current_offset: u32 = 0;788 var current_offset: u32 = 0;
680 while (true) {789 while (true) {
681 try atom.resolveRelocs(self);790 try atom.resolveRelocs(self);
791
792 // Pad with zeroes to ensure all segments are aligned
793 if (current_offset != atom.offset) {
794 const diff = atom.offset - current_offset;
795 try writer.writeByteNTimes(0, diff);
796 current_offset += diff;
797 }
682 std.debug.assert(current_offset == atom.offset);798 std.debug.assert(current_offset == atom.offset);
683 std.debug.assert(atom.code.items.len == atom.size);799 std.debug.assert(atom.code.items.len == atom.size);
684
685 try writer.writeAll(atom.code.items);800 try writer.writeAll(atom.code.items);
686801
687 current_offset += atom.size;802 current_offset += atom.size;
688 if (atom.next) |next| {803 if (atom.next) |next| {
689 atom = next;804 atom = next;
690 } else break;805 } else {
806 // also pad with zeroes when last atom to ensure
807 // segments are aligned.
808 if (current_offset != segment.size) {
809 try writer.writeByteNTimes(0, segment.size - current_offset);
810 }
811 break;
812 }
691 }813 }
692 }814 }
693815
...@@ -696,7 +818,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -696,7 +818,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
696 header_offset,818 header_offset,
697 .data,819 .data,
698 @intCast(u32, (try file.getPos()) - header_offset - header_size),820 @intCast(u32, (try file.getPos()) - header_offset - header_size),
699 @intCast(u32, 1), // only 1 data section821 @intCast(u32, segment_count),
700 );822 );
701 }823 }
702}824}
...@@ -735,6 +857,30 @@ fn emitInit(writer: anytype, init_expr: wasm.InitExpression) !void {...@@ -735,6 +857,30 @@ fn emitInit(writer: anytype, init_expr: wasm.InitExpression) !void {
735 try writer.writeByte(wasm.opcode(.end));857 try writer.writeByte(wasm.opcode(.end));
736}858}
737859
860fn emitImport(writer: anytype, import: wasm.Import) !void {
861 try leb.writeULEB128(writer, @intCast(u32, import.module_name.len));
862 try writer.writeAll(import.module_name);
863
864 try leb.writeULEB128(writer, @intCast(u32, import.name.len));
865 try writer.writeAll(import.name);
866
867 try writer.writeByte(@enumToInt(import.kind));
868 switch (import.kind) {
869 .function => |type_index| try leb.writeULEB128(writer, type_index),
870 .global => |global_type| {
871 try leb.writeULEB128(writer, wasm.valtype(global_type.valtype));
872 try writer.writeByte(@boolToInt(global_type.mutable));
873 },
874 .table => |table| {
875 try leb.writeULEB128(writer, wasm.reftype(table.reftype));
876 try emitLimits(writer, table.limits);
877 },
878 .memory => |limits| {
879 try emitLimits(writer, limits);
880 },
881 }
882}
883
738fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {884fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
739 const tracy = trace(@src());885 const tracy = trace(@src());
740 defer tracy.end();886 defer tracy.end();
src/link/Wasm/Atom.zig+10-26
...@@ -6,7 +6,7 @@ const Wasm = @import("../Wasm.zig");...@@ -6,7 +6,7 @@ const Wasm = @import("../Wasm.zig");
6const Symbol = @import("Symbol.zig");6const Symbol = @import("Symbol.zig");
77
8const leb = std.leb;8const leb = std.leb;
9const log = std.log.scoped(.zld);9const log = std.log.scoped(.link);
10const mem = std.mem;10const mem = std.mem;
11const Allocator = mem.Allocator;11const Allocator = mem.Allocator;
1212
...@@ -42,12 +42,18 @@ pub const empty: Atom = .{...@@ -42,12 +42,18 @@ pub const empty: Atom = .{
42};42};
4343
44/// Frees all resources owned by this `Atom`.44/// Frees all resources owned by this `Atom`.
45/// Also destroys itself, making any usage of this atom illegal.
46pub fn deinit(self: *Atom, gpa: *Allocator) void {45pub fn deinit(self: *Atom, gpa: *Allocator) void {
47 self.relocs.deinit(gpa);46 self.relocs.deinit(gpa);
48 self.code.deinit(gpa);47 self.code.deinit(gpa);
49}48}
5049
50/// Sets the length of relocations and code to '0',
51/// effectively resetting them and allowing them to be re-populated.
52pub fn clear(self: *Atom) void {
53 self.relocs.clearRetainingCapacity();
54 self.code.clearRetainingCapacity();
55}
56
51pub fn format(self: Atom, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {57pub fn format(self: Atom, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
52 _ = fmt;58 _ = fmt;
53 _ = options;59 _ = options;
...@@ -66,26 +72,6 @@ pub fn getFirst(self: *Atom) *Atom {...@@ -66,26 +72,6 @@ pub fn getFirst(self: *Atom) *Atom {
66 return tmp;72 return tmp;
67}73}
6874
69/// Returns the last `Atom` from a given atom
70pub fn getLast(self: *Atom) *Atom {
71 var tmp = self;
72 while (tmp.next) |next| tmp = next;
73 return tmp;
74}
75
76/// Unplugs the `Atom` from the chain
77pub fn unplug(self: *Atom) void {
78 if (self.prev) |prev| {
79 prev.next = self.next;
80 }
81
82 if (self.next) |next| {
83 next.prev = self.prev;
84 }
85 self.next = null;
86 self.prev = null;
87}
88
89/// Resolves the relocations within the atom, writing the new value75/// Resolves the relocations within the atom, writing the new value
90/// at the calculated offset.76/// at the calculated offset.
91pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) !void {77pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) !void {
...@@ -163,12 +149,10 @@ fn relocationValue(relocation: types.Relocation, wasm_bin: *const Wasm) !u64 {...@@ -163,12 +149,10 @@ fn relocationValue(relocation: types.Relocation, wasm_bin: *const Wasm) !u64 {
163 var target_atom = wasm_bin.atoms.getPtr(atom_index).?.*.getFirst();149 var target_atom = wasm_bin.atoms.getPtr(atom_index).?.*.getFirst();
164 while (true) {150 while (true) {
165 if (target_atom.sym_index == relocation.index) break;151 if (target_atom.sym_index == relocation.index) break;
166 if (target_atom.next) |next| {152 target_atom = target_atom.next orelse break;
167 target_atom = next;
168 } else break;
169 }153 }
170 const segment = wasm_bin.segments.items[atom_index];154 const segment = wasm_bin.segments.items[atom_index];
171 const base = wasm_bin.base.options.global_base orelse 0;155 const base = wasm_bin.base.options.global_base orelse 1024;
172 const offset = target_atom.offset + segment.offset;156 const offset = target_atom.offset + segment.offset;
173 break :blk offset + base + (relocation.addend orelse 0);157 break :blk offset + base + (relocation.addend orelse 0);
174 },158 },