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;...@@ -280,3 +280,6 @@ pub const block_empty: u8 = 0x40;
280// binary constants280// binary constants
281pub const magic = [_]u8{ 0x00, 0x61, 0x73, 0x6D }; // \0asm281pub const magic = [_]u8{ 0x00, 0x61, 0x73, 0x6D }; // \0asm
282pub const version = [_]u8{ 0x01, 0x00, 0x00, 0x00 }; // version 1282pub 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(...@@ -3029,9 +3029,12 @@ fn astgenAndSemaVarDecl(
3029 };3029 };
3030 defer gen_scope.instructions.deinit(mod.gpa);3030 defer gen_scope.instructions.deinit(mod.gpa);
30313031
3032 const init_result_loc: AstGen.ResultLoc = if (var_decl.ast.type_node != 0) .{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),3033 .{
3034 } else .none;3034 .ty = try AstGen.expr(&gen_scope, &gen_scope.base, .{ .ty = .type_type }, var_decl.ast.type_node),
3035 }
3036 else
3037 .none;
30353038
3036 const init_inst = try AstGen.comptimeExpr(3039 const init_inst = try AstGen.comptimeExpr(
3037 &gen_scope,3040 &gen_scope,
...@@ -3834,7 +3837,7 @@ fn allocateNewDecl(...@@ -3834,7 +3837,7 @@ fn allocateNewDecl(
3834 .elf => .{ .elf = link.File.Elf.TextBlock.empty },3837 .elf => .{ .elf = link.File.Elf.TextBlock.empty },
3835 .macho => .{ .macho = link.File.MachO.TextBlock.empty },3838 .macho => .{ .macho = link.File.MachO.TextBlock.empty },
3836 .c => .{ .c = link.File.C.DeclBlock.empty },3839 .c => .{ .c = link.File.C.DeclBlock.empty },
3837 .wasm => .{ .wasm = {} },3840 .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty },
3838 .spirv => .{ .spirv = {} },3841 .spirv => .{ .spirv = {} },
3839 },3842 },
3840 .fn_link = switch (mod.comp.bin_file.tag) {3843 .fn_link = switch (mod.comp.bin_file.tag) {
...@@ -3842,7 +3845,7 @@ fn allocateNewDecl(...@@ -3842,7 +3845,7 @@ fn allocateNewDecl(
3842 .elf => .{ .elf = link.File.Elf.SrcFn.empty },3845 .elf => .{ .elf = link.File.Elf.SrcFn.empty },
3843 .macho => .{ .macho = link.File.MachO.SrcFn.empty },3846 .macho => .{ .macho = link.File.MachO.SrcFn.empty },
3844 .c => .{ .c = link.File.C.FnBlock.empty },3847 .c => .{ .c = link.File.C.FnBlock.empty },
3845 .wasm => .{ .wasm = null },3848 .wasm => .{ .wasm = link.File.Wasm.FnData.empty },
3846 .spirv => .{ .spirv = .{} },3849 .spirv => .{ .spirv = .{} },
3847 },3850 },
3848 .generation = 0,3851 .generation = 0,
src/codegen/wasm.zig+132-63
...@@ -16,9 +16,12 @@ const Value = @import("../value.zig").Value;...@@ -16,9 +16,12 @@ const Value = @import("../value.zig").Value;
16const Compilation = @import("../Compilation.zig");16const Compilation = @import("../Compilation.zig");
17const AnyMCValue = @import("../codegen.zig").AnyMCValue;17const AnyMCValue = @import("../codegen.zig").AnyMCValue;
18const LazySrcLoc = Module.LazySrcLoc;18const LazySrcLoc = Module.LazySrcLoc;
19const link = @import("../link.zig");
20const TypedValue = @import("../TypedValue.zig");
1921
20/// Wasm Value, created when generating an instruction22/// Wasm Value, created when generating an instruction
21const WValue = union(enum) {23const WValue = union(enum) {
24 /// May be referenced but is unused
22 none: void,25 none: void,
23 /// Index of the local variable26 /// Index of the local variable
24 local: u32,27 local: u32,
...@@ -163,25 +166,23 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {...@@ -163,25 +166,23 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
163 .global_get => return .global_get,166 .global_get => return .global_get,
164 .global_set => return .global_set,167 .global_set => return .global_set,
165168
166 .load => if (args.width) |width|169 .load => if (args.width) |width| switch (width) {
167 switch (width) {170 8 => switch (args.valtype1.?) {
168 8 => switch (args.valtype1.?) {171 .i32 => if (args.signedness.? == .signed) return .i32_load8_s else return .i32_load8_u,
169 .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,
170 .i64 => if (args.signedness.? == .signed) return .i64_load8_s else return .i64_load8_u,173 .f32, .f64 => unreachable,
171 .f32, .f64 => unreachable,174 },
172 },175 16 => switch (args.valtype1.?) {
173 16 => switch (args.valtype1.?) {176 .i32 => if (args.signedness.? == .signed) return .i32_load16_s else return .i32_load16_u,
174 .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,
175 .i64 => if (args.signedness.? == .signed) return .i64_load16_s else return .i64_load16_u,178 .f32, .f64 => unreachable,
176 .f32, .f64 => unreachable,179 },
177 },180 32 => switch (args.valtype1.?) {
178 32 => switch (args.valtype1.?) {181 .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u,
179 .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u,182 .i32, .f32, .f64 => unreachable,
180 .i32, .f32, .f64 => unreachable,183 },
181 },184 else => unreachable,
182 else => unreachable,185 } else switch (args.valtype1.?) {
183 }
184 else switch (args.valtype1.?) {
185 .i32 => return .i32_load,186 .i32 => return .i32_load,
186 .i64 => return .i64_load,187 .i64 => return .i64_load,
187 .f32 => return .f32_load,188 .f32 => return .f32_load,
...@@ -469,6 +470,13 @@ test "Wasm - buildOpcode" {...@@ -469,6 +470,13 @@ test "Wasm - buildOpcode" {
469 testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64);470 testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64);
470}471}
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
472/// Hashmap to store generated `WValue` for each `Inst`480/// Hashmap to store generated `WValue` for each `Inst`
473pub const ValueTable = std.AutoHashMapUnmanaged(*Inst, WValue);481pub const ValueTable = std.AutoHashMapUnmanaged(*Inst, WValue);
474482
...@@ -504,6 +512,8 @@ pub const Context = struct {...@@ -504,6 +512,8 @@ pub const Context = struct {
504 const InnerError = error{512 const InnerError = error{
505 OutOfMemory,513 OutOfMemory,
506 CodegenFail,514 CodegenFail,
515 /// Can occur when dereferencing a pointer that points to a `Decl` of which the analysis has failed
516 AnalysisFail,
507 };517 };
508518
509 pub fn deinit(self: *Context) void {519 pub fn deinit(self: *Context) void {
...@@ -533,11 +543,20 @@ pub const Context = struct {...@@ -533,11 +543,20 @@ pub const Context = struct {
533543
534 /// Using a given `Type`, returns the corresponding wasm Valtype544 /// Using a given `Type`, returns the corresponding wasm Valtype
535 fn typeToValtype(self: *Context, src: LazySrcLoc, ty: Type) InnerError!wasm.Valtype {545 fn typeToValtype(self: *Context, src: LazySrcLoc, ty: Type) InnerError!wasm.Valtype {
536 return switch (ty.tag()) {546 return switch (ty.zigTypeTag()) {
537 .f32 => .f32,547 .Float => blk: {
538 .f64 => .f64,548 const bits = ty.floatBits(self.target);
539 .u32, .i32, .bool => .i32,549 if (bits == 16 or bits == 32) break :blk wasm.Valtype.f32;
540 .u64, .i64 => .i64,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,
541 else => self.fail(src, "TODO - Wasm valtype for type '{s}'", .{ty.tag()}),560 else => self.fail(src, "TODO - Wasm valtype for type '{s}'", .{ty.tag()}),
542 };561 };
543 }562 }
...@@ -604,48 +623,82 @@ pub const Context = struct {...@@ -604,48 +623,82 @@ pub const Context = struct {
604 }623 }
605624
606 /// Generates the wasm bytecode for the function declaration belonging to `Context`625 /// Generates the wasm bytecode for the function declaration belonging to `Context`
607 pub fn gen(self: *Context) InnerError!void {626 pub fn gen(self: *Context, typed_value: TypedValue) InnerError!Result {
608 assert(self.code.items.len == 0);627 switch (typed_value.ty.zigTypeTag()) {
609 try self.genFunctype();628 .Fn => {
610629 try self.genFunctype();
611 // Write instructions630
612 // TODO: check for and handle death of instructions631 // Write instructions
613 const tv = self.decl.typed_value.most_recent.typed_value;632 // TODO: check for and handle death of instructions
614 const mod_fn = blk: {633 const mod_fn = blk: {
615 if (tv.val.castTag(.function)) |func| break :blk func.data;634 if (typed_value.val.castTag(.function)) |func| break :blk func.data;
616 if (tv.val.castTag(.extern_fn)) |ext_fn| return; // don't need codegen for extern functions635 if (typed_value.val.castTag(.extern_fn)) |ext_fn| return Result.appended; // don't need code body for extern functions
617 return self.fail(.{ .node_offset = 0 }, "TODO: Wasm codegen for decl type '{s}'", .{tv.ty.tag()});636 unreachable;
618 };637 };
619638
620 // Reserve space to write the size after generating the code as well as space for locals count639 // Reserve space to write the size after generating the code as well as space for locals count
621 try self.code.resize(10);640 try self.code.resize(10);
622641
623 try self.genBody(mod_fn.body);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' position661 const writer = self.code.writer();
626 {662 try writer.writeByte(wasm.opcode(.end));
627 leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len));
628663
629 // offset into 'code' section where we will put our locals types664 // Fill in the size of the generated code to the reserved space at the
630 var local_offset: usize = 10;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 amount669 // codegen data has been appended to `code`
633 for (self.locals.items) |local| {670 return Result.appended;
634 var buf: [6]u8 = undefined;671 },
635 leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1));672 .Array => {
636 buf[5] = local;673 if (typed_value.val.castTag(.bytes)) |payload| {
637 try self.code.insertSlice(local_offset, &buf);674 if (typed_value.ty.sentinel()) |sentinel| {
638 local_offset += 6;675 try self.code.appendSlice(payload.data);
639 }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}),
640 }701 }
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));
649 }702 }
650703
651 fn genInst(self: *Context, inst: *Inst) InnerError!WValue {704 fn genInst(self: *Context, inst: *Inst) InnerError!WValue {
...@@ -721,7 +774,7 @@ pub const Context = struct {...@@ -721,7 +774,7 @@ pub const Context = struct {
721774
722 // The function index immediate argument will be filled in using this data775 // The function index immediate argument will be filled in using this data
723 // in link.Wasm.flush().776 // 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, .{
725 .offset = @intCast(u32, self.code.items.len),778 .offset = @intCast(u32, self.code.items.len),
726 .decl = target,779 .decl = target,
727 });780 });
...@@ -812,6 +865,22 @@ pub const Context = struct {...@@ -812,6 +865,22 @@ pub const Context = struct {
812 else => |bits| return self.fail(inst.base.src, "Wasm TODO: emitConstant for float with {d} bits", .{bits}),865 else => |bits| return self.fail(inst.base.src, "Wasm TODO: emitConstant for float with {d} bits", .{bits}),
813 }866 }
814 },867 },
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 },
815 .Void => {},884 .Void => {},
816 else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for zigTypeTag {s}", .{ty}),885 else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for zigTypeTag {s}", .{ty}),
817 }886 }
src/link.zig+4-3
...@@ -138,7 +138,7 @@ pub const File = struct {...@@ -138,7 +138,7 @@ pub const File = struct {
138 coff: Coff.TextBlock,138 coff: Coff.TextBlock,
139 macho: MachO.TextBlock,139 macho: MachO.TextBlock,
140 c: C.DeclBlock,140 c: C.DeclBlock,
141 wasm: void,141 wasm: Wasm.DeclBlock,
142 spirv: void,142 spirv: void,
143 };143 };
144144
...@@ -147,7 +147,7 @@ pub const File = struct {...@@ -147,7 +147,7 @@ pub const File = struct {
147 coff: Coff.SrcFn,147 coff: Coff.SrcFn,
148 macho: MachO.SrcFn,148 macho: MachO.SrcFn,
149 c: C.FnBlock,149 c: C.FnBlock,
150 wasm: ?Wasm.FnData,150 wasm: Wasm.FnData,
151 spirv: SpirV.FnData,151 spirv: SpirV.FnData,
152 };152 };
153153
...@@ -328,7 +328,8 @@ pub const File = struct {...@@ -328,7 +328,8 @@ pub const File = struct {
328 .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),328 .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
329 .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),329 .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),
330 .c => return @fieldParentPtr(C, "base", base).allocateDeclIndexes(decl),330 .c => return @fieldParentPtr(C, "base", base).allocateDeclIndexes(decl),
331 .wasm, .spirv => {},331 .wasm => return @fieldParentPtr(Wasm, "base", base).allocateDeclIndexes(decl),
332 .spirv => {},
332 }333 }
333 }334 }
334335
src/link/Wasm.zig+278-62
...@@ -16,21 +16,11 @@ const link = @import("../link.zig");...@@ -16,21 +16,11 @@ const link = @import("../link.zig");
16const trace = @import("../tracy.zig").trace;16const trace = @import("../tracy.zig").trace;
17const build_options = @import("build_options");17const build_options = @import("build_options");
18const Cache = @import("../Cache.zig");18const Cache = @import("../Cache.zig");
19const TypedValue = @import("../TypedValue.zig");
1920
20pub const base_tag = link.File.Tag.wasm;21pub 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
32base: link.File,23base: link.File,
33
34/// List of all function Decls to be written to the output file. The index of24/// List of all function Decls to be written to the output file. The index of
35/// each Decl in this list at the time of writing the binary is used as the25/// each Decl in this list at the time of writing the binary is used as the
36/// function index. In the event where ext_funcs' size is not 0, the index of26/// 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) = .{},...@@ -45,6 +35,77 @@ ext_funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},
45/// to support existing code.35/// to support existing code.
46/// TODO: Allow setting this through a flag?36/// TODO: Allow setting this through a flag?
47host_name: []const u8 = "env",37host_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
49pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm {110pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm {
50 assert(options.object_format == .wasm);111 assert(options.object_format == .wasm);
...@@ -52,7 +113,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio...@@ -52,7 +113,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
52 if (options.use_llvm) return error.LLVM_BackendIsTODO_ForWasm; // TODO113 if (options.use_llvm) return error.LLVM_BackendIsTODO_ForWasm; // TODO
53 if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO114 if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO
54115
55 // TODO: read the file and keep vaild parts instead of truncating116 // TODO: read the file and keep valid parts instead of truncating
56 const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true });117 const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true });
57 errdefer file.close();118 errdefer file.close();
58119
...@@ -80,50 +141,67 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Wasm {...@@ -80,50 +141,67 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Wasm {
80}141}
81142
82pub fn deinit(self: *Wasm) void {143pub fn deinit(self: *Wasm) void {
83 for (self.funcs.items) |decl| {144 for (self.symbols.items) |decl| {
84 decl.fn_link.wasm.?.functype.deinit(self.base.allocator);145 decl.fn_link.wasm.functype.deinit(self.base.allocator);
85 decl.fn_link.wasm.?.code.deinit(self.base.allocator);146 decl.fn_link.wasm.code.deinit(self.base.allocator);
86 decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);147 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);
92 }148 }
149
93 self.funcs.deinit(self.base.allocator);150 self.funcs.deinit(self.base.allocator);
94 self.ext_funcs.deinit(self.base.allocator);151 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);
95}155}
96156
97// Generate code for the Decl, storing it in memory to be later written to157pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void {
98// the file on flush().158 if (decl.link.wasm.init) return;
99pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {159
100 const typed_value = decl.typed_value.most_recent.typed_value;160 try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1);
101 if (typed_value.ty.zigTypeTag() != .Fn)161 try self.symbols.ensureCapacity(self.base.allocator, self.symbols.items.len + 1);
102 return error.TODOImplementNonFnDeclsForWasm;
103162
104 if (decl.fn_link.wasm) |*fn_data| {163 const block = &decl.link.wasm;
105 fn_data.functype.items.len = 0;164 block.init = true;
106 fn_data.code.items.len = 0;165
107 fn_data.idx_refs.items.len = 0;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;
108 } else {171 } else {
109 decl.fn_link.wasm = .{};172 block.offset_index = @intCast(u32, self.offset_table.items.len);
110 // dependent on function type, appends it to the correct list173 _ = self.offset_table.addOneAssumeCapacity();
111 switch (decl.typed_value.most_recent.typed_value.val.tag()) {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
112 .function => try self.funcs.append(self.base.allocator, decl),182 .function => try self.funcs.append(self.base.allocator, decl),
113 .extern_fn => try self.ext_funcs.append(self.base.allocator, decl),183 .extern_fn => try self.ext_funcs.append(self.base.allocator, decl),
114 else => return error.TODOImplementNonFnDeclsForWasm,184 else => unreachable,
115 }185 }
116 }186 }
117 const fn_data = &decl.fn_link.wasm.?;187}
118188
119 var managed_functype = fn_data.functype.toManaged(self.base.allocator);189// Generate code for the Decl, storing it in memory to be later written to
120 var managed_code = fn_data.code.toManaged(self.base.allocator);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
122 var context = codegen.Context{200 var context = codegen.Context{
123 .gpa = self.base.allocator,201 .gpa = self.base.allocator,
124 .values = .{},202 .values = .{},
125 .code = managed_code,203 .code = fn_data.code.toManaged(self.base.allocator),
126 .func_type_data = managed_functype,204 .func_type_data = fn_data.functype.toManaged(self.base.allocator),
127 .decl = decl,205 .decl = decl,
128 .err_msg = undefined,206 .err_msg = undefined,
129 .locals = .{},207 .locals = .{},
...@@ -132,7 +210,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {...@@ -132,7 +210,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
132 defer context.deinit();210 defer context.deinit();
133211
134 // generate the 'code' section for the function declaration212 // 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) {
136 error.CodegenFail => {214 error.CodegenFail => {
137 decl.analysis = .codegen_failure;215 decl.analysis = .codegen_failure;
138 try module.failed_decls.put(module.gpa, decl, context.err_msg);216 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 {...@@ -141,15 +219,38 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
141 else => |e| return err,219 else => |e| return err,
142 };220 };
143221
144 // as locals are patched afterwards, the offsets of funcidx's are off,222 const code: []const u8 = switch (result) {
145 // here we update them to correct them223 .appended => @as([]const u8, context.code.items),
146 for (decl.fn_link.wasm.?.idx_refs.items) |*func| {224 .externally_managed => |payload| payload,
147 // For each local, add 6 bytes (count + type)225 };
148 func.offset += @intCast(u32, context.locals.items.len * 6);
149 }
150226
151 fn_data.functype = context.func_type_data.toUnmanaged();
152 fn_data.code = context.code.toUnmanaged();227 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;
153}254}
154255
155pub fn updateDeclExports(256pub fn updateDeclExports(
...@@ -160,18 +261,34 @@ pub fn updateDeclExports(...@@ -160,18 +261,34 @@ pub fn updateDeclExports(
160) !void {}261) !void {}
161262
162pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {263pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
163 // TODO: remove this assert when non-function Decls are implemented264 if (self.getFuncidx(decl)) |func_idx| {
164 assert(decl.typed_value.most_recent.typed_value.ty.zigTypeTag() == .Fn);265 switch (decl.typed_value.most_recent.typed_value.val.tag()) {
165 const func_idx = self.getFuncidx(decl).?;266 .function => _ = self.funcs.swapRemove(func_idx),
166 switch (decl.typed_value.most_recent.typed_value.val.tag()) {267 .extern_fn => _ = self.ext_funcs.swapRemove(func_idx),
167 .function => _ = self.funcs.swapRemove(func_idx),268 else => unreachable,
168 .extern_fn => _ = self.ext_funcs.swapRemove(func_idx),269 }
169 else => unreachable,270 }
271 const block = &decl.link.wasm;
272
273 if (self.last_block == block) {
274 self.last_block = block.prev;
170 }275 }
171 decl.fn_link.wasm.?.functype.deinit(self.base.allocator);276
172 decl.fn_link.wasm.?.code.deinit(self.base.allocator);277 block.unplug();
173 decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);278
174 decl.fn_link.wasm = null;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;
175}292}
176293
177pub fn flush(self: *Wasm, comp: *Compilation) !void {294pub fn flush(self: *Wasm, comp: *Compilation) !void {
...@@ -188,6 +305,25 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -188,6 +305,25 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
188305
189 const file = self.base.file.?;306 const file = self.base.file.?;
190 const header_size = 5 + 1;307 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
192 // No need to rewrite the magic/version header328 // No need to rewrite the magic/version header
193 try file.setEndPos(@sizeOf(@TypeOf(wasm.magic ++ wasm.version)));329 try file.setEndPos(@sizeOf(@TypeOf(wasm.magic ++ wasm.version)));
...@@ -199,8 +335,8 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -199,8 +335,8 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
199335
200 // extern functions are defined in the wasm binary first through the `import`336 // extern functions are defined in the wasm binary first through the `import`
201 // section, so define their func types first337 // section, so define their func types first
202 for (self.ext_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);
203 for (self.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
205 try writeVecSectionHeader(341 try writeVecSectionHeader(
206 file,342 file,
...@@ -257,6 +393,31 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -257,6 +393,31 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
257 );393 );
258 }394 }
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
260 // Export section421 // Export section
261 if (self.base.options.module) |module| {422 if (self.base.options.module) |module| {
262 const header_offset = try reserveVecSectionHeader(file);423 const header_offset = try reserveVecSectionHeader(file);
...@@ -281,6 +442,16 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -281,6 +442,16 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
281 count += 1;442 count += 1;
282 }443 }
283 }444 }
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
284 try writeVecSectionHeader(455 try writeVecSectionHeader(
285 file,456 file,
286 header_offset,457 header_offset,
...@@ -295,7 +466,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -295,7 +466,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
295 const header_offset = try reserveVecSectionHeader(file);466 const header_offset = try reserveVecSectionHeader(file);
296 const writer = file.writer();467 const writer = file.writer();
297 for (self.funcs.items) |decl| {468 for (self.funcs.items) |decl| {
298 const fn_data = &decl.fn_link.wasm.?;469 const fn_data = &decl.fn_link.wasm;
299470
300 // Write the already generated code to the file, inserting471 // Write the already generated code to the file, inserting
301 // function indexes where required.472 // function indexes where required.
...@@ -320,6 +491,51 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -320,6 +491,51 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
320 @intCast(u32, self.funcs.items.len),491 @intCast(u32, self.funcs.items.len),
321 );492 );
322 }493 }
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 }
323}539}
324540
325fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {541fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {