authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-03-27 23:26:20+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-04-08 22:47:08+02:00
log00b2e31589b2f4c3f67ab2bf46e140e00df3f910
tree50531677b96e547c4a0b8219b807c0be6eee5748
parent9f744f19e7036df9a410f780f3394f6669b8079e
signaturelock-open Commit is signed but in an unrecognized format.

Basic "Hello world" working


2 files changed, 187 insertions(+), 66 deletions(-)

src/codegen/wasm.zig+78-54
...@@ -163,25 +163,23 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {...@@ -163,25 +163,23 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
163 .global_get => return .global_get,163 .global_get => return .global_get,
164 .global_set => return .global_set,164 .global_set => return .global_set,
165165
166 .load => if (args.width) |width|166 .load => if (args.width) |width| switch (width) {
167 switch (width) {167 8 => switch (args.valtype1.?) {
168 8 => switch (args.valtype1.?) {168 .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,169 .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,170 .f32, .f64 => unreachable,
171 .f32, .f64 => unreachable,171 },
172 },172 16 => switch (args.valtype1.?) {
173 16 => switch (args.valtype1.?) {173 .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,174 .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,175 .f32, .f64 => unreachable,
176 .f32, .f64 => unreachable,176 },
177 },177 32 => switch (args.valtype1.?) {
178 32 => switch (args.valtype1.?) {178 .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,179 .i32, .f32, .f64 => unreachable,
180 .i32, .f32, .f64 => unreachable,180 },
181 },181 else => unreachable,
182 else => unreachable,182 } else switch (args.valtype1.?) {
183 }
184 else switch (args.valtype1.?) {
185 .i32 => return .i32_load,183 .i32 => return .i32_load,
186 .i64 => return .i64_load,184 .i64 => return .i64_load,
187 .f32 => return .f32_load,185 .f32 => return .f32_load,
...@@ -469,6 +467,13 @@ test "Wasm - buildOpcode" {...@@ -469,6 +467,13 @@ test "Wasm - buildOpcode" {
469 testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64);467 testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64);
470}468}
471469
470pub const Result = union(enum) {
471 /// The codegen bytes have been appended to `Context.code`
472 appended: void,
473 /// The data is managed externally and are part of the `Result`
474 externally_managed: []const u8,
475};
476
472/// Hashmap to store generated `WValue` for each `Inst`477/// Hashmap to store generated `WValue` for each `Inst`
473pub const ValueTable = std.AutoHashMapUnmanaged(*Inst, WValue);478pub const ValueTable = std.AutoHashMapUnmanaged(*Inst, WValue);
474479
...@@ -504,6 +509,8 @@ pub const Context = struct {...@@ -504,6 +509,8 @@ pub const Context = struct {
504 const InnerError = error{509 const InnerError = error{
505 OutOfMemory,510 OutOfMemory,
506 CodegenFail,511 CodegenFail,
512 /// Can occur when dereferencing a pointer that points to a `Decl` of which the analysis has failed
513 AnalysisFail,
507 };514 };
508515
509 pub fn deinit(self: *Context) void {516 pub fn deinit(self: *Context) void {
...@@ -604,48 +611,65 @@ pub const Context = struct {...@@ -604,48 +611,65 @@ pub const Context = struct {
604 }611 }
605612
606 /// Generates the wasm bytecode for the function declaration belonging to `Context`613 /// Generates the wasm bytecode for the function declaration belonging to `Context`
607 pub fn gen(self: *Context) InnerError!void {614 pub fn gen(self: *Context) InnerError!Result {
608 assert(self.code.items.len == 0);615 assert(self.code.items.len == 0);
609 try self.genFunctype();
610616
611 // Write instructions
612 // TODO: check for and handle death of instructions
613 const tv = self.decl.typed_value.most_recent.typed_value;617 const tv = self.decl.typed_value.most_recent.typed_value;
614 const mod_fn = blk: {618 switch (tv.ty.zigTypeTag()) {
615 if (tv.val.castTag(.function)) |func| break :blk func.data;619 .Fn => {
616 if (tv.val.castTag(.extern_fn)) |ext_fn| return; // don't need codegen for extern functions620 try self.genFunctype();
617 return self.fail(.{ .node_offset = 0 }, "TODO: Wasm codegen for decl type '{s}'", .{tv.ty.tag()});621
618 };622 // Write instructions
619623 // TODO: check for and handle death of instructions
620 // Reserve space to write the size after generating the code as well as space for locals count624 const mod_fn = blk: {
621 try self.code.resize(10);625 if (tv.val.castTag(.function)) |func| break :blk func.data;
622626 if (tv.val.castTag(.extern_fn)) |ext_fn| return Result.appended; // don't need code body for extern functions
623 try self.genBody(mod_fn.body);627 return self.fail(.{ .node_offset = 0 }, "TODO: Wasm codegen for decl type '{s}'", .{tv.ty.tag()});
628 };
629
630 // Reserve space to write the size after generating the code as well as space for locals count
631 try self.code.resize(10);
632
633 try self.genBody(mod_fn.body);
634
635 // finally, write our local types at the 'offset' position
636 {
637 leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len));
638
639 // offset into 'code' section where we will put our locals types
640 var local_offset: usize = 10;
641
642 // emit the actual locals amount
643 for (self.locals.items) |local| {
644 var buf: [6]u8 = undefined;
645 leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1));
646 buf[5] = local;
647 try self.code.insertSlice(local_offset, &buf);
648 local_offset += 6;
649 }
650 }
624651
625 // finally, write our local types at the 'offset' position652 const writer = self.code.writer();
626 {653 try writer.writeByte(wasm.opcode(.end));
627 leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len));
628654
629 // offset into 'code' section where we will put our locals types655 // Fill in the size of the generated code to the reserved space at the
630 var local_offset: usize = 10;656 // beginning of the buffer.
657 const size = self.code.items.len - 5 + self.decl.fn_link.wasm.?.idx_refs.items.len * 5;
658 leb.writeUnsignedFixed(5, self.code.items[0..5], @intCast(u32, size));
631659
632 // emit the actual locals amount660 // codegen data has been appended to `code`
633 for (self.locals.items) |local| {661 return Result.appended;
634 var buf: [6]u8 = undefined;662 },
635 leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1));663 .Array => {
636 buf[5] = local;664 if (tv.val.castTag(.bytes)) |payload| {
637 try self.code.insertSlice(local_offset, &buf);665 if (tv.ty.sentinel()) |sentinel| {
638 local_offset += 6;666 // TODO, handle sentinel correctly
639 }667 }
668 return Result{ .externally_managed = payload.data };
669 } else return self.fail(.{ .node_offset = 0 }, "TODO implement gen for more kinds of arrays", .{});
670 },
671 else => |tag| return self.fail(.{ .node_offset = 0 }, "TODO: Implement zig type codegen for type: '{s}'", .{tag}),
640 }672 }
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 }673 }
650674
651 fn genInst(self: *Context, inst: *Inst) InnerError!WValue {675 fn genInst(self: *Context, inst: *Inst) InnerError!WValue {
src/link/Wasm.zig+109-12
...@@ -29,6 +29,32 @@ pub const FnData = struct {...@@ -29,6 +29,32 @@ pub const FnData = struct {
29 idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }) = .{},29 idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }) = .{},
30};30};
3131
32/// Data section of the wasm binary
33/// Each declaration will have its own 'data_segment' within the section
34/// where the offset is calculated using the previous segments and the content length
35/// of the data
36pub const DataSection = struct {
37 segments: std.AutoArrayHashMapUnmanaged(*const Module.Decl, []const u8) = .{},
38
39 /// Returns the offset into the data segment based on a given `Decl`
40 pub fn offset(self: DataSection, decl: *const Module.Decl) u32 {
41 var cur_offset: u32 = 0;
42 return for (self.segments.items()) |entry| {
43 if (entry.key == decl) break cur_offset;
44 cur_offset += @intCast(u32, entry.value.len);
45 } else cur_offset;
46 }
47
48 /// Returns the total payload size of the data section
49 pub fn size(self: DataSection) u32 {
50 var total: u32 = 0;
51 for (self.segments.items()) |entry| {
52 total += @intCast(u32, entry.value.len);
53 }
54 return total;
55 }
56};
57
32base: link.File,58base: link.File,
3359
34/// List of all function Decls to be written to the output file. The index of60/// List of all function Decls to be written to the output file. The index of
...@@ -45,6 +71,10 @@ ext_funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},...@@ -45,6 +71,10 @@ ext_funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},
45/// to support existing code.71/// to support existing code.
46/// TODO: Allow setting this through a flag?72/// TODO: Allow setting this through a flag?
47host_name: []const u8 = "env",73host_name: []const u8 = "env",
74/// Map of declarations with its bytes payload, used to keep track of all data segments
75/// that needs to be emit when creating the wasm binary.
76/// The `DataSection`'s lifetime must be kept alive until the linking stage.
77data: DataSection = .{},
4878
49pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm {79pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm {
50 assert(options.object_format == .wasm);80 assert(options.object_format == .wasm);
...@@ -52,7 +82,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio...@@ -52,7 +82,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
52 if (options.use_llvm) return error.LLVM_BackendIsTODO_ForWasm; // TODO82 if (options.use_llvm) return error.LLVM_BackendIsTODO_ForWasm; // TODO
53 if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO83 if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO
5484
55 // TODO: read the file and keep vaild parts instead of truncating85 // 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 });86 const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true });
57 errdefer file.close();87 errdefer file.close();
5888
...@@ -92,14 +122,13 @@ pub fn deinit(self: *Wasm) void {...@@ -92,14 +122,13 @@ pub fn deinit(self: *Wasm) void {
92 }122 }
93 self.funcs.deinit(self.base.allocator);123 self.funcs.deinit(self.base.allocator);
94 self.ext_funcs.deinit(self.base.allocator);124 self.ext_funcs.deinit(self.base.allocator);
125 self.data.segments.deinit(self.base.allocator);
95}126}
96127
97// Generate code for the Decl, storing it in memory to be later written to128// Generate code for the Decl, storing it in memory to be later written to
98// the file on flush().129// the file on flush().
99pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {130pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
100 const typed_value = decl.typed_value.most_recent.typed_value;131 const typed_value = decl.typed_value.most_recent.typed_value;
101 if (typed_value.ty.zigTypeTag() != .Fn)
102 return error.TODOImplementNonFnDeclsForWasm;
103132
104 if (decl.fn_link.wasm) |*fn_data| {133 if (decl.fn_link.wasm) |*fn_data| {
105 fn_data.functype.items.len = 0;134 fn_data.functype.items.len = 0;
...@@ -111,6 +140,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {...@@ -111,6 +140,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
111 switch (decl.typed_value.most_recent.typed_value.val.tag()) {140 switch (decl.typed_value.most_recent.typed_value.val.tag()) {
112 .function => try self.funcs.append(self.base.allocator, decl),141 .function => try self.funcs.append(self.base.allocator, decl),
113 .extern_fn => try self.ext_funcs.append(self.base.allocator, decl),142 .extern_fn => try self.ext_funcs.append(self.base.allocator, decl),
143 .bytes => {},
114 else => return error.TODOImplementNonFnDeclsForWasm,144 else => return error.TODOImplementNonFnDeclsForWasm,
115 }145 }
116 }146 }
...@@ -132,7 +162,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {...@@ -132,7 +162,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
132 defer context.deinit();162 defer context.deinit();
133163
134 // generate the 'code' section for the function declaration164 // generate the 'code' section for the function declaration
135 context.gen() catch |err| switch (err) {165 const result = context.gen() catch |err| switch (err) {
136 error.CodegenFail => {166 error.CodegenFail => {
137 decl.analysis = .codegen_failure;167 decl.analysis = .codegen_failure;
138 try module.failed_decls.put(module.gpa, decl, context.err_msg);168 try module.failed_decls.put(module.gpa, decl, context.err_msg);
...@@ -141,15 +171,24 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {...@@ -141,15 +171,24 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
141 else => |e| return err,171 else => |e| return err,
142 };172 };
143173
144 // as locals are patched afterwards, the offsets of funcidx's are off,174 switch (typed_value.ty.zigTypeTag()) {
145 // here we update them to correct them175 .Fn => {
146 for (decl.fn_link.wasm.?.idx_refs.items) |*func| {176 // as locals are patched afterwards, the offsets of funcidx's are off,
147 // For each local, add 6 bytes (count + type)177 // here we update them to correct them
148 func.offset += @intCast(u32, context.locals.items.len * 6);178 for (decl.fn_link.wasm.?.idx_refs.items) |*func| {
149 }179 // For each local, add 6 bytes (count + type)
180 func.offset += @intCast(u32, context.locals.items.len * 6);
181 }
150182
151 fn_data.functype = context.func_type_data.toUnmanaged();183 fn_data.functype = context.func_type_data.toUnmanaged();
152 fn_data.code = context.code.toUnmanaged();184 fn_data.code = context.code.toUnmanaged();
185 },
186 .Array => switch (result) {
187 .appended => unreachable,
188 .externally_managed => |payload| try self.data.segments.put(self.base.allocator, decl, payload),
189 },
190 else => return error.TODO,
191 }
153}192}
154193
155pub fn updateDeclExports(194pub fn updateDeclExports(
...@@ -257,6 +296,22 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -257,6 +296,22 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
257 );296 );
258 }297 }
259298
299 // Memory section
300 if (self.data.size() != 0) {
301 const header_offset = try reserveVecSectionHeader(file);
302 const writer = file.writer();
303
304 try leb.writeULEB128(writer, @as(u32, 0));
305 try leb.writeULEB128(writer, @as(u32, 1));
306 try writeVecSectionHeader(
307 file,
308 header_offset,
309 .memory,
310 @intCast(u32, (try file.getPos()) - header_offset - header_size),
311 @as(u32, 1),
312 );
313 }
314
260 // Export section315 // Export section
261 if (self.base.options.module) |module| {316 if (self.base.options.module) |module| {
262 const header_offset = try reserveVecSectionHeader(file);317 const header_offset = try reserveVecSectionHeader(file);
...@@ -281,6 +336,16 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -281,6 +336,16 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
281 count += 1;336 count += 1;
282 }337 }
283 }338 }
339
340 // export memory if size is not 0
341 if (self.data.size() != 0) {
342 try leb.writeULEB128(writer, @intCast(u32, "memory".len));
343 try writer.writeAll("memory");
344 try writer.writeByte(wasm.externalKind(.memory));
345 try leb.writeULEB128(writer, @as(u32, 0)); // only 1 memory 'object' can exist
346 count += 1;
347 }
348
284 try writeVecSectionHeader(349 try writeVecSectionHeader(
285 file,350 file,
286 header_offset,351 header_offset,
...@@ -320,6 +385,38 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -320,6 +385,38 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
320 @intCast(u32, self.funcs.items.len),385 @intCast(u32, self.funcs.items.len),
321 );386 );
322 }387 }
388
389 // Data section
390 {
391 const header_offset = try reserveVecSectionHeader(file);
392 const writer = file.writer();
393 var offset: i32 = 0;
394 for (self.data.segments.items()) |entry| {
395 // index to memory section (always 0 in current wasm version)
396 try leb.writeULEB128(writer, @as(u32, 0));
397
398 // offset into data section
399 try writer.writeByte(wasm.opcode(.i32_const));
400 try leb.writeILEB128(writer, offset);
401 try writer.writeByte(wasm.opcode(.end));
402
403 // payload size
404 const len = @intCast(u32, entry.value.len);
405 try leb.writeULEB128(writer, len);
406
407 // write payload
408 try writer.writeAll(entry.value);
409 offset += @bitCast(i32, len);
410 }
411
412 try writeVecSectionHeader(
413 file,
414 header_offset,
415 .data,
416 @intCast(u32, (try file.getPos()) - header_offset - header_size),
417 @intCast(u32, self.data.segments.items().len),
418 );
419 }
323}420}
324421
325fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {422fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {