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
signature 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 {
163163 .global_get => return .global_get,
164164 .global_set => return .global_set,
165165
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.?) {
166 .load => if (args.width) |width| switch (width) {
167 8 => switch (args.valtype1.?) {
168 .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 .f32, .f64 => unreachable,
171 },
172 16 => switch (args.valtype1.?) {
173 .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 .f32, .f64 => unreachable,
176 },
177 32 => switch (args.valtype1.?) {
178 .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u,
179 .i32, .f32, .f64 => unreachable,
180 },
181 else => unreachable,
182 } else switch (args.valtype1.?) {
185183 .i32 => return .i32_load,
186184 .i64 => return .i64_load,
187185 .f32 => return .f32_load,
......@@ -469,6 +467,13 @@ test "Wasm - buildOpcode" {
469467 testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64);
470468}
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
472477/// Hashmap to store generated `WValue` for each `Inst`
473478pub const ValueTable = std.AutoHashMapUnmanaged(*Inst, WValue);
474479
......@@ -504,6 +509,8 @@ pub const Context = struct {
504509 const InnerError = error{
505510 OutOfMemory,
506511 CodegenFail,
512 /// Can occur when dereferencing a pointer that points to a `Decl` of which the analysis has failed
513 AnalysisFail,
507514 };
508515
509516 pub fn deinit(self: *Context) void {
......@@ -604,48 +611,65 @@ pub const Context = struct {
604611 }
605612
606613 /// 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 {
608615 assert(self.code.items.len == 0);
609 try self.genFunctype();
610616
611 // Write instructions
612 // TODO: check for and handle death of instructions
613617 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);
618 switch (tv.ty.zigTypeTag()) {
619 .Fn => {
620 try self.genFunctype();
621
622 // Write instructions
623 // TODO: check for and handle death of instructions
624 const mod_fn = blk: {
625 if (tv.val.castTag(.function)) |func| break :blk func.data;
626 if (tv.val.castTag(.extern_fn)) |ext_fn| return Result.appended; // don't need code body for extern functions
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' position
626 {
627 leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len));
652 const writer = self.code.writer();
653 try writer.writeByte(wasm.opcode(.end));
628654
629 // offset into 'code' section where we will put our locals types
630 var local_offset: usize = 10;
655 // Fill in the size of the generated code to the reserved space at the
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 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 }
660 // codegen data has been appended to `code`
661 return Result.appended;
662 },
663 .Array => {
664 if (tv.val.castTag(.bytes)) |payload| {
665 if (tv.ty.sentinel()) |sentinel| {
666 // TODO, handle sentinel correctly
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}),
640672 }
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));
649673 }
650674
651675 fn genInst(self: *Context, inst: *Inst) InnerError!WValue {
src/link/Wasm.zig+109-12
......@@ -29,6 +29,32 @@ pub const FnData = struct {
2929 idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }) = .{},
3030};
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
3258base: link.File,
3359
3460/// 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) = .{},
4571/// to support existing code.
4672/// TODO: Allow setting this through a flag?
4773host_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
4979pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm {
5080 assert(options.object_format == .wasm);
......@@ -52,7 +82,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
5282 if (options.use_llvm) return error.LLVM_BackendIsTODO_ForWasm; // TODO
5383 if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO
5484
55 // TODO: read the file and keep vaild parts instead of truncating
85 // TODO: read the file and keep valid parts instead of truncating
5686 const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true });
5787 errdefer file.close();
5888
......@@ -92,14 +122,13 @@ pub fn deinit(self: *Wasm) void {
92122 }
93123 self.funcs.deinit(self.base.allocator);
94124 self.ext_funcs.deinit(self.base.allocator);
125 self.data.segments.deinit(self.base.allocator);
95126}
96127
97128// Generate code for the Decl, storing it in memory to be later written to
98129// the file on flush().
99130pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
100131 const typed_value = decl.typed_value.most_recent.typed_value;
101 if (typed_value.ty.zigTypeTag() != .Fn)
102 return error.TODOImplementNonFnDeclsForWasm;
103132
104133 if (decl.fn_link.wasm) |*fn_data| {
105134 fn_data.functype.items.len = 0;
......@@ -111,6 +140,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
111140 switch (decl.typed_value.most_recent.typed_value.val.tag()) {
112141 .function => try self.funcs.append(self.base.allocator, decl),
113142 .extern_fn => try self.ext_funcs.append(self.base.allocator, decl),
143 .bytes => {},
114144 else => return error.TODOImplementNonFnDeclsForWasm,
115145 }
116146 }
......@@ -132,7 +162,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
132162 defer context.deinit();
133163
134164 // generate the 'code' section for the function declaration
135 context.gen() catch |err| switch (err) {
165 const result = context.gen() catch |err| switch (err) {
136166 error.CodegenFail => {
137167 decl.analysis = .codegen_failure;
138168 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 {
141171 else => |e| return err,
142172 };
143173
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 }
174 switch (typed_value.ty.zigTypeTag()) {
175 .Fn => {
176 // as locals are patched afterwards, the offsets of funcidx's are off,
177 // here we update them to correct them
178 for (decl.fn_link.wasm.?.idx_refs.items) |*func| {
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();
152 fn_data.code = context.code.toUnmanaged();
183 fn_data.functype = context.func_type_data.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 }
153192}
154193
155194pub fn updateDeclExports(
......@@ -257,6 +296,22 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
257296 );
258297 }
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
260315 // Export section
261316 if (self.base.options.module) |module| {
262317 const header_offset = try reserveVecSectionHeader(file);
......@@ -281,6 +336,16 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
281336 count += 1;
282337 }
283338 }
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
284349 try writeVecSectionHeader(
285350 file,
286351 header_offset,
......@@ -320,6 +385,38 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
320385 @intCast(u32, self.funcs.items.len),
321386 );
322387 }
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 }
323420}
324421
325422fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {