authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-04-09 10:08:21-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-04-09 10:08:21-07:00
log952032b40cd6e3dbffc5642d17a7a05fa7c83895
treec7b5840e3d5a8eedb95d1d6941db8f3d4ae38555
parentbfc8500390f0ed9371b01e2d9b1836b23bea68b9
parenteaaf75c1579e0202efb1b8b71155ea147d52c56a
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #8439 from Luukdegram/wasm-mem

stage2: wasm - "Hello world"

5 files changed, 425 insertions(+), 133 deletions(-)

lib/std/wasm.zig+3
......@@ -280,3 +280,6 @@ pub const block_empty: u8 = 0x40;
280280// binary constants
281281pub const magic = [_]u8{ 0x00, 0x61, 0x73, 0x6D }; // \0asm
282282pub const version = [_]u8{ 0x01, 0x00, 0x00, 0x00 }; // version 1
283
284// Each wasm page size is 64kB
285pub const page_size = 64 * 1024;
src/Module.zig+8-5
......@@ -3029,9 +3029,12 @@ fn astgenAndSemaVarDecl(
30293029 };
30303030 defer gen_scope.instructions.deinit(mod.gpa);
30313031
3032 const init_result_loc: AstGen.ResultLoc = if (var_decl.ast.type_node != 0) .{
3033 .ty = try AstGen.expr(&gen_scope, &gen_scope.base, .{ .ty = .type_type }, var_decl.ast.type_node),
3034 } else .none;
3032 const init_result_loc: AstGen.ResultLoc = if (var_decl.ast.type_node != 0)
3033 .{
3034 .ty = try AstGen.expr(&gen_scope, &gen_scope.base, .{ .ty = .type_type }, var_decl.ast.type_node),
3035 }
3036 else
3037 .none;
30353038
30363039 const init_inst = try AstGen.comptimeExpr(
30373040 &gen_scope,
......@@ -3834,7 +3837,7 @@ fn allocateNewDecl(
38343837 .elf => .{ .elf = link.File.Elf.TextBlock.empty },
38353838 .macho => .{ .macho = link.File.MachO.TextBlock.empty },
38363839 .c => .{ .c = link.File.C.DeclBlock.empty },
3837 .wasm => .{ .wasm = {} },
3840 .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty },
38383841 .spirv => .{ .spirv = {} },
38393842 },
38403843 .fn_link = switch (mod.comp.bin_file.tag) {
......@@ -3842,7 +3845,7 @@ fn allocateNewDecl(
38423845 .elf => .{ .elf = link.File.Elf.SrcFn.empty },
38433846 .macho => .{ .macho = link.File.MachO.SrcFn.empty },
38443847 .c => .{ .c = link.File.C.FnBlock.empty },
3845 .wasm => .{ .wasm = null },
3848 .wasm => .{ .wasm = link.File.Wasm.FnData.empty },
38463849 .spirv => .{ .spirv = .{} },
38473850 },
38483851 .generation = 0,
src/codegen/wasm.zig+132-63
......@@ -16,9 +16,12 @@ const Value = @import("../value.zig").Value;
1616const Compilation = @import("../Compilation.zig");
1717const AnyMCValue = @import("../codegen.zig").AnyMCValue;
1818const LazySrcLoc = Module.LazySrcLoc;
19const link = @import("../link.zig");
20const TypedValue = @import("../TypedValue.zig");
1921
2022/// Wasm Value, created when generating an instruction
2123const WValue = union(enum) {
24 /// May be referenced but is unused
2225 none: void,
2326 /// Index of the local variable
2427 local: u32,
......@@ -163,25 +166,23 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
163166 .global_get => return .global_get,
164167 .global_set => return .global_set,
165168
166 .load => if (args.width) |width|
167 switch (width) {
168 8 => switch (args.valtype1.?) {
169 .i32 => if (args.signedness.? == .signed) return .i32_load8_s else return .i32_load8_u,
170 .i64 => if (args.signedness.? == .signed) return .i64_load8_s else return .i64_load8_u,
171 .f32, .f64 => unreachable,
172 },
173 16 => switch (args.valtype1.?) {
174 .i32 => if (args.signedness.? == .signed) return .i32_load16_s else return .i32_load16_u,
175 .i64 => if (args.signedness.? == .signed) return .i64_load16_s else return .i64_load16_u,
176 .f32, .f64 => unreachable,
177 },
178 32 => switch (args.valtype1.?) {
179 .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u,
180 .i32, .f32, .f64 => unreachable,
181 },
182 else => unreachable,
183 }
184 else switch (args.valtype1.?) {
169 .load => if (args.width) |width| switch (width) {
170 8 => switch (args.valtype1.?) {
171 .i32 => if (args.signedness.? == .signed) return .i32_load8_s else return .i32_load8_u,
172 .i64 => if (args.signedness.? == .signed) return .i64_load8_s else return .i64_load8_u,
173 .f32, .f64 => unreachable,
174 },
175 16 => switch (args.valtype1.?) {
176 .i32 => if (args.signedness.? == .signed) return .i32_load16_s else return .i32_load16_u,
177 .i64 => if (args.signedness.? == .signed) return .i64_load16_s else return .i64_load16_u,
178 .f32, .f64 => unreachable,
179 },
180 32 => switch (args.valtype1.?) {
181 .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u,
182 .i32, .f32, .f64 => unreachable,
183 },
184 else => unreachable,
185 } else switch (args.valtype1.?) {
185186 .i32 => return .i32_load,
186187 .i64 => return .i64_load,
187188 .f32 => return .f32_load,
......@@ -469,6 +470,13 @@ test "Wasm - buildOpcode" {
469470 testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64);
470471}
471472
473pub const Result = union(enum) {
474 /// The codegen bytes have been appended to `Context.code`
475 appended: void,
476 /// The data is managed externally and are part of the `Result`
477 externally_managed: []const u8,
478};
479
472480/// Hashmap to store generated `WValue` for each `Inst`
473481pub const ValueTable = std.AutoHashMapUnmanaged(*Inst, WValue);
474482
......@@ -504,6 +512,8 @@ pub const Context = struct {
504512 const InnerError = error{
505513 OutOfMemory,
506514 CodegenFail,
515 /// Can occur when dereferencing a pointer that points to a `Decl` of which the analysis has failed
516 AnalysisFail,
507517 };
508518
509519 pub fn deinit(self: *Context) void {
......@@ -533,11 +543,20 @@ pub const Context = struct {
533543
534544 /// Using a given `Type`, returns the corresponding wasm Valtype
535545 fn typeToValtype(self: *Context, src: LazySrcLoc, ty: Type) InnerError!wasm.Valtype {
536 return switch (ty.tag()) {
537 .f32 => .f32,
538 .f64 => .f64,
539 .u32, .i32, .bool => .i32,
540 .u64, .i64 => .i64,
546 return switch (ty.zigTypeTag()) {
547 .Float => blk: {
548 const bits = ty.floatBits(self.target);
549 if (bits == 16 or bits == 32) break :blk wasm.Valtype.f32;
550 if (bits == 64) break :blk wasm.Valtype.f64;
551 return self.fail(src, "Float bit size not supported by wasm: '{d}'", .{bits});
552 },
553 .Int => blk: {
554 const info = ty.intInfo(self.target);
555 if (info.bits <= 32) break :blk wasm.Valtype.i32;
556 if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64;
557 return self.fail(src, "Integer bit size not supported by wasm: '{d}'", .{info.bits});
558 },
559 .Bool, .Pointer => wasm.Valtype.i32,
541560 else => self.fail(src, "TODO - Wasm valtype for type '{s}'", .{ty.tag()}),
542561 };
543562 }
......@@ -604,48 +623,82 @@ pub const Context = struct {
604623 }
605624
606625 /// Generates the wasm bytecode for the function declaration belonging to `Context`
607 pub fn gen(self: *Context) InnerError!void {
608 assert(self.code.items.len == 0);
609 try self.genFunctype();
610
611 // Write instructions
612 // TODO: check for and handle death of instructions
613 const tv = self.decl.typed_value.most_recent.typed_value;
614 const mod_fn = blk: {
615 if (tv.val.castTag(.function)) |func| break :blk func.data;
616 if (tv.val.castTag(.extern_fn)) |ext_fn| return; // don't need codegen for extern functions
617 return self.fail(.{ .node_offset = 0 }, "TODO: Wasm codegen for decl type '{s}'", .{tv.ty.tag()});
618 };
619
620 // Reserve space to write the size after generating the code as well as space for locals count
621 try self.code.resize(10);
622
623 try self.genBody(mod_fn.body);
626 pub fn gen(self: *Context, typed_value: TypedValue) InnerError!Result {
627 switch (typed_value.ty.zigTypeTag()) {
628 .Fn => {
629 try self.genFunctype();
630
631 // Write instructions
632 // TODO: check for and handle death of instructions
633 const mod_fn = blk: {
634 if (typed_value.val.castTag(.function)) |func| break :blk func.data;
635 if (typed_value.val.castTag(.extern_fn)) |ext_fn| return Result.appended; // don't need code body for extern functions
636 unreachable;
637 };
638
639 // Reserve space to write the size after generating the code as well as space for locals count
640 try self.code.resize(10);
641
642 try self.genBody(mod_fn.body);
643
644 // finally, write our local types at the 'offset' position
645 {
646 leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len));
647
648 // offset into 'code' section where we will put our locals types
649 var local_offset: usize = 10;
650
651 // emit the actual locals amount
652 for (self.locals.items) |local| {
653 var buf: [6]u8 = undefined;
654 leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1));
655 buf[5] = local;
656 try self.code.insertSlice(local_offset, &buf);
657 local_offset += 6;
658 }
659 }
624660
625 // finally, write our local types at the 'offset' position
626 {
627 leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len));
661 const writer = self.code.writer();
662 try writer.writeByte(wasm.opcode(.end));
628663
629 // offset into 'code' section where we will put our locals types
630 var local_offset: usize = 10;
664 // Fill in the size of the generated code to the reserved space at the
665 // beginning of the buffer.
666 const size = self.code.items.len - 5 + self.decl.fn_link.wasm.idx_refs.items.len * 5;
667 leb.writeUnsignedFixed(5, self.code.items[0..5], @intCast(u32, size));
631668
632 // emit the actual locals amount
633 for (self.locals.items) |local| {
634 var buf: [6]u8 = undefined;
635 leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1));
636 buf[5] = local;
637 try self.code.insertSlice(local_offset, &buf);
638 local_offset += 6;
639 }
669 // codegen data has been appended to `code`
670 return Result.appended;
671 },
672 .Array => {
673 if (typed_value.val.castTag(.bytes)) |payload| {
674 if (typed_value.ty.sentinel()) |sentinel| {
675 try self.code.appendSlice(payload.data);
676
677 switch (try self.gen(.{
678 .ty = typed_value.ty.elemType(),
679 .val = sentinel,
680 })) {
681 .appended => return Result.appended,
682 .externally_managed => |data| {
683 try self.code.appendSlice(data);
684 return Result.appended;
685 },
686 }
687 }
688 return Result{ .externally_managed = payload.data };
689 } else return self.fail(.{ .node_offset = 0 }, "TODO implement gen for more kinds of arrays", .{});
690 },
691 .Int => {
692 const info = typed_value.ty.intInfo(self.target);
693 if (info.bits == 8 and info.signedness == .unsigned) {
694 const int_byte = typed_value.val.toUnsignedInt();
695 try self.code.append(@intCast(u8, int_byte));
696 return Result.appended;
697 }
698 return self.fail(.{ .node_offset = 0 }, "TODO: Implement codegen for int type: '{}'", .{typed_value.ty});
699 },
700 else => |tag| return self.fail(.{ .node_offset = 0 }, "TODO: Implement zig type codegen for type: '{s}'", .{tag}),
640701 }
641
642 const writer = self.code.writer();
643 try writer.writeByte(wasm.opcode(.end));
644
645 // Fill in the size of the generated code to the reserved space at the
646 // beginning of the buffer.
647 const size = self.code.items.len - 5 + self.decl.fn_link.wasm.?.idx_refs.items.len * 5;
648 leb.writeUnsignedFixed(5, self.code.items[0..5], @intCast(u32, size));
649702 }
650703
651704 fn genInst(self: *Context, inst: *Inst) InnerError!WValue {
......@@ -721,7 +774,7 @@ pub const Context = struct {
721774
722775 // The function index immediate argument will be filled in using this data
723776 // in link.Wasm.flush().
724 try self.decl.fn_link.wasm.?.idx_refs.append(self.gpa, .{
777 try self.decl.fn_link.wasm.idx_refs.append(self.gpa, .{
725778 .offset = @intCast(u32, self.code.items.len),
726779 .decl = target,
727780 });
......@@ -812,6 +865,22 @@ pub const Context = struct {
812865 else => |bits| return self.fail(inst.base.src, "Wasm TODO: emitConstant for float with {d} bits", .{bits}),
813866 }
814867 },
868 .Pointer => {
869 if (inst.val.castTag(.decl_ref)) |payload| {
870 const decl = payload.data;
871
872 // offset into the offset table within the 'data' section
873 const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8;
874 try writer.writeByte(wasm.opcode(.i32_const));
875 try leb.writeULEB128(writer, decl.link.wasm.offset_index * ptr_width);
876
877 // memory instruction followed by their memarg immediate
878 // memarg ::== x:u32, y:u32 => {align x, offset y}
879 try writer.writeByte(wasm.opcode(.i32_load));
880 try leb.writeULEB128(writer, @as(u32, 0));
881 try leb.writeULEB128(writer, @as(u32, 0));
882 } else return self.fail(inst.base.src, "Wasm TODO: emitConstant for other const pointer tag {s}", .{inst.val.tag()});
883 },
815884 .Void => {},
816885 else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for zigTypeTag {s}", .{ty}),
817886 }
src/link.zig+4-3
......@@ -138,7 +138,7 @@ pub const File = struct {
138138 coff: Coff.TextBlock,
139139 macho: MachO.TextBlock,
140140 c: C.DeclBlock,
141 wasm: void,
141 wasm: Wasm.DeclBlock,
142142 spirv: void,
143143 };
144144
......@@ -147,7 +147,7 @@ pub const File = struct {
147147 coff: Coff.SrcFn,
148148 macho: MachO.SrcFn,
149149 c: C.FnBlock,
150 wasm: ?Wasm.FnData,
150 wasm: Wasm.FnData,
151151 spirv: SpirV.FnData,
152152 };
153153
......@@ -328,7 +328,8 @@ pub const File = struct {
328328 .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
329329 .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),
330330 .c => return @fieldParentPtr(C, "base", base).allocateDeclIndexes(decl),
331 .wasm, .spirv => {},
331 .wasm => return @fieldParentPtr(Wasm, "base", base).allocateDeclIndexes(decl),
332 .spirv => {},
332333 }
333334 }
334335
src/link/Wasm.zig+278-62
......@@ -16,21 +16,11 @@ const link = @import("../link.zig");
1616const trace = @import("../tracy.zig").trace;
1717const build_options = @import("build_options");
1818const Cache = @import("../Cache.zig");
19const TypedValue = @import("../TypedValue.zig");
1920
2021pub const base_tag = link.File.Tag.wasm;
2122
22pub const FnData = struct {
23 /// Generated code for the type of the function
24 functype: std.ArrayListUnmanaged(u8) = .{},
25 /// Generated code for the body of the function
26 code: std.ArrayListUnmanaged(u8) = .{},
27 /// Locations in the generated code where function indexes must be filled in.
28 /// This must be kept ordered by offset.
29 idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }) = .{},
30};
31
3223base: link.File,
33
3424/// List of all function Decls to be written to the output file. The index of
3525/// each Decl in this list at the time of writing the binary is used as the
3626/// function index. In the event where ext_funcs' size is not 0, the index of
......@@ -45,6 +35,77 @@ ext_funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},
4535/// to support existing code.
4636/// TODO: Allow setting this through a flag?
4737host_name: []const u8 = "env",
38/// The last `DeclBlock` that was initialized will be saved here.
39last_block: ?*DeclBlock = null,
40/// Table with offsets, each element represents an offset with the value being
41/// the offset into the 'data' section where the data lives
42offset_table: std.ArrayListUnmanaged(u32) = .{},
43/// List of offset indexes which are free to be used for new decl's.
44/// Each element's value points to an index into the offset_table.
45offset_table_free_list: std.ArrayListUnmanaged(u32) = .{},
46/// List of all `Decl` that are currently alive.
47/// This is ment for bookkeeping so we can safely cleanup all codegen memory
48/// when calling `deinit`
49symbols: std.ArrayListUnmanaged(*Module.Decl) = .{},
50
51pub const FnData = struct {
52 /// Generated code for the type of the function
53 functype: std.ArrayListUnmanaged(u8),
54 /// Generated code for the body of the function
55 code: std.ArrayListUnmanaged(u8),
56 /// Locations in the generated code where function indexes must be filled in.
57 /// This must be kept ordered by offset.
58 idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }),
59
60 pub const empty: FnData = .{
61 .functype = .{},
62 .code = .{},
63 .idx_refs = .{},
64 };
65};
66
67pub const DeclBlock = struct {
68 /// Determines whether the `DeclBlock` has been initialized for codegen.
69 init: bool,
70 /// Index into the `symbols` list.
71 symbol_index: u32,
72 /// Index into the offset table
73 offset_index: u32,
74 /// The size of the block and how large part of the data section it occupies.
75 /// Will be 0 when the Decl will not live inside the data section and `data` will be undefined.
76 size: u32,
77 /// Points to the previous and next blocks.
78 /// Can be used to find the total size, and used to calculate the `offset` based on the previous block.
79 prev: ?*DeclBlock,
80 next: ?*DeclBlock,
81 /// Pointer to data that will be written to the 'data' section.
82 /// This data either lives in `FnData.code` or is externally managed.
83 /// For data that does not live inside the 'data' section, this field will be undefined. (size == 0).
84 data: [*]const u8,
85
86 pub const empty: DeclBlock = .{
87 .init = false,
88 .symbol_index = 0,
89 .offset_index = 0,
90 .size = 0,
91 .prev = null,
92 .next = null,
93 .data = undefined,
94 };
95
96 /// Unplugs the `DeclBlock` from the chain
97 fn unplug(self: *DeclBlock) void {
98 if (self.prev) |prev| {
99 prev.next = self.next;
100 }
101
102 if (self.next) |next| {
103 next.prev = self.prev;
104 }
105 self.next = null;
106 self.prev = null;
107 }
108};
48109
49110pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm {
50111 assert(options.object_format == .wasm);
......@@ -52,7 +113,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
52113 if (options.use_llvm) return error.LLVM_BackendIsTODO_ForWasm; // TODO
53114 if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO
54115
55 // TODO: read the file and keep vaild parts instead of truncating
116 // TODO: read the file and keep valid parts instead of truncating
56117 const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true });
57118 errdefer file.close();
58119
......@@ -80,50 +141,67 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Wasm {
80141}
81142
82143pub fn deinit(self: *Wasm) void {
83 for (self.funcs.items) |decl| {
84 decl.fn_link.wasm.?.functype.deinit(self.base.allocator);
85 decl.fn_link.wasm.?.code.deinit(self.base.allocator);
86 decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);
87 }
88 for (self.ext_funcs.items) |decl| {
89 decl.fn_link.wasm.?.functype.deinit(self.base.allocator);
90 decl.fn_link.wasm.?.code.deinit(self.base.allocator);
91 decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);
144 for (self.symbols.items) |decl| {
145 decl.fn_link.wasm.functype.deinit(self.base.allocator);
146 decl.fn_link.wasm.code.deinit(self.base.allocator);
147 decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);
92148 }
149
93150 self.funcs.deinit(self.base.allocator);
94151 self.ext_funcs.deinit(self.base.allocator);
152 self.offset_table.deinit(self.base.allocator);
153 self.offset_table_free_list.deinit(self.base.allocator);
154 self.symbols.deinit(self.base.allocator);
95155}
96156
97// Generate code for the Decl, storing it in memory to be later written to
98// the file on flush().
99pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
100 const typed_value = decl.typed_value.most_recent.typed_value;
101 if (typed_value.ty.zigTypeTag() != .Fn)
102 return error.TODOImplementNonFnDeclsForWasm;
157pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void {
158 if (decl.link.wasm.init) return;
159
160 try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1);
161 try self.symbols.ensureCapacity(self.base.allocator, self.symbols.items.len + 1);
103162
104 if (decl.fn_link.wasm) |*fn_data| {
105 fn_data.functype.items.len = 0;
106 fn_data.code.items.len = 0;
107 fn_data.idx_refs.items.len = 0;
163 const block = &decl.link.wasm;
164 block.init = true;
165
166 block.symbol_index = @intCast(u32, self.symbols.items.len);
167 self.symbols.appendAssumeCapacity(decl);
168
169 if (self.offset_table_free_list.popOrNull()) |index| {
170 block.offset_index = index;
108171 } else {
109 decl.fn_link.wasm = .{};
110 // dependent on function type, appends it to the correct list
111 switch (decl.typed_value.most_recent.typed_value.val.tag()) {
172 block.offset_index = @intCast(u32, self.offset_table.items.len);
173 _ = self.offset_table.addOneAssumeCapacity();
174 }
175
176 self.offset_table.items[block.offset_index] = 0;
177
178 const typed_value = decl.typed_value.most_recent.typed_value;
179 if (typed_value.ty.zigTypeTag() == .Fn) {
180 switch (typed_value.val.tag()) {
181 // dependent on function type, appends it to the correct list
112182 .function => try self.funcs.append(self.base.allocator, decl),
113183 .extern_fn => try self.ext_funcs.append(self.base.allocator, decl),
114 else => return error.TODOImplementNonFnDeclsForWasm,
184 else => unreachable,
115185 }
116186 }
117 const fn_data = &decl.fn_link.wasm.?;
187}
118188
119 var managed_functype = fn_data.functype.toManaged(self.base.allocator);
120 var managed_code = fn_data.code.toManaged(self.base.allocator);
189// Generate code for the Decl, storing it in memory to be later written to
190// the file on flush().
191pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
192 std.debug.assert(decl.link.wasm.init); // Must call allocateDeclIndexes()
193
194 const typed_value = decl.typed_value.most_recent.typed_value;
195 const fn_data = &decl.fn_link.wasm;
196 fn_data.functype.items.len = 0;
197 fn_data.code.items.len = 0;
198 fn_data.idx_refs.items.len = 0;
121199
122200 var context = codegen.Context{
123201 .gpa = self.base.allocator,
124202 .values = .{},
125 .code = managed_code,
126 .func_type_data = managed_functype,
203 .code = fn_data.code.toManaged(self.base.allocator),
204 .func_type_data = fn_data.functype.toManaged(self.base.allocator),
127205 .decl = decl,
128206 .err_msg = undefined,
129207 .locals = .{},
......@@ -132,7 +210,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
132210 defer context.deinit();
133211
134212 // generate the 'code' section for the function declaration
135 context.gen() catch |err| switch (err) {
213 const result = context.gen(typed_value) catch |err| switch (err) {
136214 error.CodegenFail => {
137215 decl.analysis = .codegen_failure;
138216 try module.failed_decls.put(module.gpa, decl, context.err_msg);
......@@ -141,15 +219,38 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
141219 else => |e| return err,
142220 };
143221
144 // as locals are patched afterwards, the offsets of funcidx's are off,
145 // here we update them to correct them
146 for (decl.fn_link.wasm.?.idx_refs.items) |*func| {
147 // For each local, add 6 bytes (count + type)
148 func.offset += @intCast(u32, context.locals.items.len * 6);
149 }
222 const code: []const u8 = switch (result) {
223 .appended => @as([]const u8, context.code.items),
224 .externally_managed => |payload| payload,
225 };
150226
151 fn_data.functype = context.func_type_data.toUnmanaged();
152227 fn_data.code = context.code.toUnmanaged();
228 fn_data.functype = context.func_type_data.toUnmanaged();
229
230 const block = &decl.link.wasm;
231 if (typed_value.ty.zigTypeTag() == .Fn) {
232 // as locals are patched afterwards, the offsets of funcidx's are off,
233 // here we update them to correct them
234 for (fn_data.idx_refs.items) |*func| {
235 // For each local, add 6 bytes (count + type)
236 func.offset += @intCast(u32, context.locals.items.len * 6);
237 }
238 } else {
239 block.size = @intCast(u32, code.len);
240 block.data = code.ptr;
241 }
242
243 // If we're updating an existing decl, unplug it first
244 // to avoid infinite loops due to earlier links
245 block.unplug();
246
247 if (self.last_block) |last| {
248 if (last != block) {
249 last.next = block;
250 block.prev = last;
251 }
252 }
253 self.last_block = block;
153254}
154255
155256pub fn updateDeclExports(
......@@ -160,18 +261,34 @@ pub fn updateDeclExports(
160261) !void {}
161262
162263pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
163 // TODO: remove this assert when non-function Decls are implemented
164 assert(decl.typed_value.most_recent.typed_value.ty.zigTypeTag() == .Fn);
165 const func_idx = self.getFuncidx(decl).?;
166 switch (decl.typed_value.most_recent.typed_value.val.tag()) {
167 .function => _ = self.funcs.swapRemove(func_idx),
168 .extern_fn => _ = self.ext_funcs.swapRemove(func_idx),
169 else => unreachable,
264 if (self.getFuncidx(decl)) |func_idx| {
265 switch (decl.typed_value.most_recent.typed_value.val.tag()) {
266 .function => _ = self.funcs.swapRemove(func_idx),
267 .extern_fn => _ = self.ext_funcs.swapRemove(func_idx),
268 else => unreachable,
269 }
270 }
271 const block = &decl.link.wasm;
272
273 if (self.last_block == block) {
274 self.last_block = block.prev;
170275 }
171 decl.fn_link.wasm.?.functype.deinit(self.base.allocator);
172 decl.fn_link.wasm.?.code.deinit(self.base.allocator);
173 decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);
174 decl.fn_link.wasm = null;
276
277 block.unplug();
278
279 self.offset_table_free_list.append(self.base.allocator, decl.link.wasm.offset_index) catch {};
280 _ = self.symbols.swapRemove(block.symbol_index);
281
282 // update symbol_index as we swap removed the last symbol into the removed's position
283 if (block.symbol_index < self.symbols.items.len)
284 self.symbols.items[block.symbol_index].link.wasm.symbol_index = block.symbol_index;
285
286 block.init = false;
287
288 decl.fn_link.wasm.functype.deinit(self.base.allocator);
289 decl.fn_link.wasm.code.deinit(self.base.allocator);
290 decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);
291 decl.fn_link.wasm = undefined;
175292}
176293
177294pub fn flush(self: *Wasm, comp: *Compilation) !void {
......@@ -188,6 +305,25 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
188305
189306 const file = self.base.file.?;
190307 const header_size = 5 + 1;
308 // ptr_width in bytes
309 const ptr_width = self.base.options.target.cpu.arch.ptrBitWidth() / 8;
310 // The size of the offset table in bytes
311 // The table contains all decl's with its corresponding offset into
312 // the 'data' section
313 const offset_table_size = @intCast(u32, self.offset_table.items.len * ptr_width);
314
315 // The size of the data, this together with `offset_table_size` amounts to the
316 // total size of the 'data' section
317 var first_decl: ?*DeclBlock = null;
318 const data_size: u32 = if (self.last_block) |last| blk: {
319 var size = last.size;
320 var cur = last;
321 while (cur.prev) |prev| : (cur = prev) {
322 size += prev.size;
323 }
324 first_decl = cur;
325 break :blk size;
326 } else 0;
191327
192328 // No need to rewrite the magic/version header
193329 try file.setEndPos(@sizeOf(@TypeOf(wasm.magic ++ wasm.version)));
......@@ -199,8 +335,8 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
199335
200336 // extern functions are defined in the wasm binary first through the `import`
201337 // section, so define their func types first
202 for (self.ext_funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.?.functype.items);
203 for (self.funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.?.functype.items);
338 for (self.ext_funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.functype.items);
339 for (self.funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.functype.items);
204340
205341 try writeVecSectionHeader(
206342 file,
......@@ -257,6 +393,31 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
257393 );
258394 }
259395
396 // Memory section
397 if (data_size != 0) {
398 const header_offset = try reserveVecSectionHeader(file);
399 const writer = file.writer();
400
401 try leb.writeULEB128(writer, @as(u32, 0));
402 // Calculate the amount of memory pages are required and write them.
403 // Wasm uses 64kB page sizes. Round up to ensure the data segments fit into the memory
404 try leb.writeULEB128(
405 writer,
406 try std.math.divCeil(
407 u32,
408 offset_table_size + data_size,
409 std.wasm.page_size,
410 ),
411 );
412 try writeVecSectionHeader(
413 file,
414 header_offset,
415 .memory,
416 @intCast(u32, (try file.getPos()) - header_offset - header_size),
417 @as(u32, 1), // wasm currently only supports 1 linear memory segment
418 );
419 }
420
260421 // Export section
261422 if (self.base.options.module) |module| {
262423 const header_offset = try reserveVecSectionHeader(file);
......@@ -281,6 +442,16 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
281442 count += 1;
282443 }
283444 }
445
446 // export memory if size is not 0
447 if (data_size != 0) {
448 try leb.writeULEB128(writer, @intCast(u32, "memory".len));
449 try writer.writeAll("memory");
450 try writer.writeByte(wasm.externalKind(.memory));
451 try leb.writeULEB128(writer, @as(u32, 0)); // only 1 memory 'object' can exist
452 count += 1;
453 }
454
284455 try writeVecSectionHeader(
285456 file,
286457 header_offset,
......@@ -295,7 +466,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
295466 const header_offset = try reserveVecSectionHeader(file);
296467 const writer = file.writer();
297468 for (self.funcs.items) |decl| {
298 const fn_data = &decl.fn_link.wasm.?;
469 const fn_data = &decl.fn_link.wasm;
299470
300471 // Write the already generated code to the file, inserting
301472 // function indexes where required.
......@@ -320,6 +491,51 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
320491 @intCast(u32, self.funcs.items.len),
321492 );
322493 }
494
495 // Data section
496 if (data_size != 0) {
497 const header_offset = try reserveVecSectionHeader(file);
498 const writer = file.writer();
499 var len: u32 = 0;
500 // index to memory section (currently, there can only be 1 memory section in wasm)
501 try leb.writeULEB128(writer, @as(u32, 0));
502
503 // offset into data section
504 try writer.writeByte(wasm.opcode(.i32_const));
505 try leb.writeILEB128(writer, @as(i32, 0));
506 try writer.writeByte(wasm.opcode(.end));
507
508 const total_size = offset_table_size + data_size;
509
510 // offset table + data size
511 try leb.writeULEB128(writer, total_size);
512
513 // fill in the offset table and the data segments
514 const file_offset = try file.getPos();
515 var cur = first_decl;
516 var data_offset = offset_table_size;
517 while (cur) |cur_block| : (cur = cur_block.next) {
518 if (cur_block.size == 0) continue;
519 std.debug.assert(cur_block.init);
520
521 const offset = (cur_block.offset_index) * ptr_width;
522 var buf: [4]u8 = undefined;
523 std.mem.writeIntLittle(u32, &buf, data_offset);
524
525 try file.pwriteAll(&buf, file_offset + offset);
526 try file.pwriteAll(cur_block.data[0..cur_block.size], file_offset + data_offset);
527 data_offset += cur_block.size;
528 }
529
530 try file.seekTo(file_offset + data_offset);
531 try writeVecSectionHeader(
532 file,
533 header_offset,
534 .data,
535 @intCast(u32, (file_offset + data_offset) - header_offset - header_size),
536 @intCast(u32, 1), // only 1 data section
537 );
538 }
323539}
324540
325541fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {