authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-01-10 18:51:01+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-01-15 23:27:38+01:00
logbb74f72e97f3d503c4801bb7ca3d6412ad01b28c
tree8fbfc0cd69011f69a6f3a521a5906226435dc62c
parentb204ea0349d5a580fc8ba9d8059c520301072072
signaturelock-open Commit is signed but in an unrecognized format.

stage2: refactor wasm backend - similar to the other backends


2 files changed, 283 insertions(+), 108 deletions(-)

src/codegen/wasm.zig+260-104
...@@ -7,136 +7,292 @@ const mem = std.mem;...@@ -7,136 +7,292 @@ const mem = std.mem;
77
8const Module = @import("../Module.zig");8const Module = @import("../Module.zig");
9const Decl = Module.Decl;9const Decl = Module.Decl;
10const Inst = @import("../ir.zig").Inst;10const ir = @import("../ir.zig");
11const Inst = ir.Inst;
11const Type = @import("../type.zig").Type;12const Type = @import("../type.zig").Type;
12const Value = @import("../value.zig").Value;13const Value = @import("../value.zig").Value;
14const Compilation = @import("../Compilation.zig");
1315
14fn genValtype(ty: Type) u8 {16/// Wasm Value, created when generating an instruction
17const WValue = union(enum) {
18 none: void,
19 /// Index of the local variable
20 local: u32,
21 /// A constant instruction
22 constant: *Inst,
23 /// Each newly created wasm block have a label
24 /// in the form of an index.
25 block_idx: u32,
26};
27
28pub const ValueTable = std.AutoArrayHashMap(*Inst, WValue);
29
30/// Using a given Zig type, returns the corresponding wasm value type
31fn genValtype(ty: Type) ?u8 {
15 return switch (ty.tag()) {32 return switch (ty.tag()) {
16 .u32, .i32 => 0x7F,
17 .u64, .i64 => 0x7E,
18 .f32 => 0x7D,33 .f32 => 0x7D,
19 .f64 => 0x7C,34 .f64 => 0x7C,
20 else => @panic("TODO: Implement more types for wasm."),35 .u32, .i32 => 0x7F,
36 .u64, .i64 => 0x7E,
37 else => null,
21 };38 };
22}39}
2340
24pub fn genFunctype(buf: *ArrayList(u8), decl: *Decl) !void {41/// Code represents the `Code` section of wasm that
25 const ty = decl.typed_value.most_recent.typed_value.ty;42/// belongs to a function
26 const writer = buf.writer();43pub const Code = struct {
44 /// Reference to the function declaration the code
45 /// section belongs to
46 decl: *Decl,
47 gpa: *mem.Allocator,
48 /// Table to save `WValue`'s generated by an `Inst`
49 values: ValueTable,
50 /// `bytes` contains the wasm instructions that have been emitted
51 /// this is what will be emitted after codegen to write the wasm binary
52 bytes: ArrayList(u8),
53 /// Contains the generated function type bytecode for the current function
54 func_type_data: ArrayList(u8),
55 /// The index the next local generated will have
56 /// NOTE: arguments share the index with locals therefore the first variable
57 /// will have the index that comes after the last argument's index
58 local_index: u32 = 0,
59 /// The index the next argument generated will have
60 arg_index: u32 = 0,
61 /// If codegen fails, an error messages will be allocated and saved
62 /// in `err_msg`
63 err_msg: *Compilation.ErrorMsg,
64
65 const InnerError = error{
66 OutOfMemory,
67 CodegenFail,
68 };
69
70 fn fail(self: *Code, src: usize, comptime fmt: []const u8, args: anytype) InnerError {
71 self.err_msg = try Compilation.ErrorMsg.create(self.gpa, src, fmt, args);
72 return error.CodegenFail;
73 }
74
75 /// Returns the `WValue` for the given `inst`
76 /// creates a new WValue for constants and returns that instead
77 fn resolveInst(self: Code, inst: *Inst) !WValue {
78 if (inst.value()) |_| {
79 return WValue{ .constant = inst };
80 }
81
82 return self.values.get(inst).?; // Instruction does not dominate all uses!
83 }
84
85 /// Writes the bytecode depending on the given `WValue` in `val`
86 fn emitWValue(self: *Code, val: WValue) !void {
87 const writer = self.bytes.writer();
88 switch (val) {
89 .none => unreachable,
90 .block_idx => unreachable,
91 // loads the local onto the stack at the given index
92 .local => |idx| {
93 // local.set
94 try writer.writeByte(0x20);
95 try leb.writeULEB128(writer, idx);
96 },
97 // creates a new constant onto the stack
98 .constant => |inst| try self.emitConstant(inst.castTag(.constant).?),
99 }
100 }
27101
28 // functype magic102 fn genFunctype(self: *Code) !void {
29 try writer.writeByte(0x60);103 const ty = self.decl.typed_value.most_recent.typed_value.ty;
104 const writer = self.func_type_data.writer();
30105
31 // param types106 // functype magic
32 try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen()));107 try writer.writeByte(0x60);
33 if (ty.fnParamLen() != 0) {108
34 const params = try buf.allocator.alloc(Type, ty.fnParamLen());109 // param types
35 defer buf.allocator.free(params);110 try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen()));
36 ty.fnParamTypes(params);111 if (ty.fnParamLen() != 0) {
37 for (params) |param_type| try writer.writeByte(genValtype(param_type));112 const params = try self.gpa.alloc(Type, ty.fnParamLen());
113 defer self.gpa.free(params);
114 ty.fnParamTypes(params);
115 for (params) |param_type| {
116 const val_type = genValtype(param_type) orelse
117 return self.fail(self.decl.src(), "TODO: Wasm generate wasm type value for type '{s}'", .{param_type.tag()});
118 try writer.writeByte(val_type);
119 }
120 }
121
122 // return type
123 const return_type = ty.fnReturnType();
124 switch (return_type.tag()) {
125 .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)),
126 else => |ret_type| {
127 try leb.writeULEB128(writer, @as(u32, 1));
128 const val_type = genValtype(return_type) orelse
129 return self.fail(self.decl.src(), "TODO: Wasm generate wasm return type value for type '{s}'", .{ret_type});
130 try writer.writeByte(val_type);
131 },
132 }
38 }133 }
39134
40 // return type135 /// Generates the wasm bytecode for the given `code`
41 const return_type = ty.fnReturnType();136 pub fn gen(self: *Code) !void {
42 switch (return_type.tag()) {137 assert(self.bytes.items.len == 0);
43 .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)),138 try self.genFunctype();
44 else => {139 const writer = self.bytes.writer();
140
141 // Reserve space to write the size after generating the code
142 try self.bytes.resize(5);
143
144 // Write instructions
145 // TODO: check for and handle death of instructions
146 const tv = self.decl.typed_value.most_recent.typed_value;
147 const mod_fn = tv.val.castTag(.function).?.data;
148
149 var locals = std.ArrayList(u8).init(self.gpa);
150 defer locals.deinit();
151
152 for (mod_fn.body.instructions) |inst| {
153 if (inst.tag != .alloc) continue;
154
155 const alloc: *Inst.NoOp = inst.castTag(.alloc).?;
156 const elem_type = alloc.base.ty.elemType();
157
158 const wasm_type = genValtype(elem_type) orelse
159 return self.fail(inst.src, "TODO: Wasm generate wasm type value for type '{s}'", .{elem_type.tag()});
160
161 try locals.append(wasm_type);
162 }
163
164 try leb.writeULEB128(writer, @intCast(u32, locals.items.len));
165
166 // emit the actual locals amount
167 for (locals.items) |local| {
45 try leb.writeULEB128(writer, @as(u32, 1));168 try leb.writeULEB128(writer, @as(u32, 1));
46 try writer.writeByte(genValtype(return_type));169 try leb.writeULEB128(writer, local); // valtype
47 },170 }
171
172 for (mod_fn.body.instructions) |inst| {
173 const result = try self.genInst(inst);
174
175 if (result != .none) {
176 try self.values.putNoClobber(inst, result);
177 }
178 }
179
180 // Write 'end' opcode
181 try writer.writeByte(0x0B);
182
183 // Fill in the size of the generated code to the reserved space at the
184 // beginning of the buffer.
185 const size = self.bytes.items.len - 5 + self.decl.fn_link.wasm.?.idx_refs.items.len * 5;
186 leb.writeUnsignedFixed(5, self.bytes.items[0..5], @intCast(u32, size));
48 }187 }
49}
50188
51pub fn genCode(buf: *ArrayList(u8), decl: *Decl) !void {189 fn genInst(self: *Code, inst: *Inst) !WValue {
52 assert(buf.items.len == 0);190 return switch (inst.tag) {
53 const writer = buf.writer();191 .alloc => self.genAlloc(inst.castTag(.alloc).?),
192 .arg => self.genArg(inst.castTag(.arg).?),
193 .call => self.genCall(inst.castTag(.call).?),
194 .constant => unreachable,
195 .dbg_stmt => WValue.none,
196 .load => self.genLoad(inst.castTag(.load).?),
197 .ret => self.genRet(inst.castTag(.ret).?),
198 .retvoid => WValue.none,
199 .store => self.genStore(inst.castTag(.store).?),
200 else => self.fail(inst.src, "TODO: Implement wasm inst: {s}", .{inst.tag}),
201 };
202 }
54203
55 // Reserve space to write the size after generating the code204 fn genRet(self: *Code, inst: *Inst.UnOp) !WValue {
56 try buf.resize(5);205 const operand = try self.resolveInst(inst.operand);
206 try self.emitWValue(operand);
207 return WValue.none;
208 }
57209
58 // Write the size of the locals vec210 fn genCall(self: *Code, inst: *Inst.Call) !WValue {
59 // TODO: implement locals211 const func_inst = inst.func.castTag(.constant).?;
60 try leb.writeULEB128(writer, @as(u32, 0));212 const func = func_inst.val.castTag(.function).?.data;
213 const target = func.owner_decl;
214 const target_ty = target.typed_value.most_recent.typed_value.ty;
61215
62 // Write instructions216 for (inst.args) |arg| {
63 // TODO: check for and handle death of instructions217 const arg_val = try self.resolveInst(arg);
64 const tv = decl.typed_value.most_recent.typed_value;218 try self.emitWValue(arg_val);
65 const mod_fn = tv.val.castTag(.function).?.data;219 }
66 for (mod_fn.body.instructions) |inst| try genInst(buf, decl, inst);
67220
68 // Write 'end' opcode221 try self.bytes.append(0x10); // call
69 try writer.writeByte(0x0B);
70222
71 // Fill in the size of the generated code to the reserved space at the223 // The function index immediate argument will be filled in using this data
72 // beginning of the buffer.224 // in link.Wasm.flush().
73 const size = buf.items.len - 5 + decl.fn_link.wasm.?.idx_refs.items.len * 5;225 try self.decl.fn_link.wasm.?.idx_refs.append(self.gpa, .{
74 leb.writeUnsignedFixed(5, buf.items[0..5], @intCast(u32, size));226 .offset = @intCast(u32, self.bytes.items.len),
75}227 .decl = target,
228 });
76229
77fn genInst(buf: *ArrayList(u8), decl: *Decl, inst: *Inst) !void {230 return WValue.none;
78 return switch (inst.tag) {231 }
79 .call => genCall(buf, decl, inst.castTag(.call).?),
80 .constant => genConstant(buf, decl, inst.castTag(.constant).?),
81 .dbg_stmt => {},
82 .ret => genRet(buf, decl, inst.castTag(.ret).?),
83 .retvoid => {},
84 else => error.TODOImplementMoreWasmCodegen,
85 };
86}
87232
88fn genConstant(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.Constant) !void {233 fn genAlloc(self: *Code, inst: *Inst.NoOp) !WValue {
89 const writer = buf.writer();234 defer self.local_index += 1;
90 switch (inst.base.ty.tag()) {235 return WValue{ .local = self.local_index };
91 .u32 => {
92 try writer.writeByte(0x41); // i32.const
93 try leb.writeILEB128(writer, inst.val.toUnsignedInt());
94 },
95 .i32 => {
96 try writer.writeByte(0x41); // i32.const
97 try leb.writeILEB128(writer, inst.val.toSignedInt());
98 },
99 .u64 => {
100 try writer.writeByte(0x42); // i64.const
101 try leb.writeILEB128(writer, inst.val.toUnsignedInt());
102 },
103 .i64 => {
104 try writer.writeByte(0x42); // i64.const
105 try leb.writeILEB128(writer, inst.val.toSignedInt());
106 },
107 .f32 => {
108 try writer.writeByte(0x43); // f32.const
109 // TODO: enforce LE byte order
110 try writer.writeAll(mem.asBytes(&inst.val.toFloat(f32)));
111 },
112 .f64 => {
113 try writer.writeByte(0x44); // f64.const
114 // TODO: enforce LE byte order
115 try writer.writeAll(mem.asBytes(&inst.val.toFloat(f64)));
116 },
117 .void => {},
118 else => return error.TODOImplementMoreWasmCodegen,
119 }236 }
120}
121237
122fn genRet(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.UnOp) !void {238 fn genStore(self: *Code, inst: *Inst.BinOp) !WValue {
123 try genInst(buf, decl, inst.operand);239 const writer = self.bytes.writer();
124}
125240
126fn genCall(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.Call) !void {241 const lhs = try self.resolveInst(inst.lhs);
127 const func_inst = inst.func.castTag(.constant).?;
128 const func = func_inst.val.castTag(.function).?.data;
129 const target = func.owner_decl;
130 const target_ty = target.typed_value.most_recent.typed_value.ty;
131242
132 if (inst.args.len != 0) return error.TODOImplementMoreWasmCodegen;243 const rhs = try self.resolveInst(inst.rhs);
244 try self.emitWValue(rhs);
133245
134 try buf.append(0x10); // call246 try writer.writeByte(0x21); // local.set
247 try leb.writeULEB128(writer, lhs.local);
135248
136 // The function index immediate argument will be filled in using this data249 return WValue.none;
137 // in link.Wasm.flush().250 }
138 try decl.fn_link.wasm.?.idx_refs.append(buf.allocator, .{251
139 .offset = @intCast(u32, buf.items.len),252 fn genLoad(self: *Code, inst: *Inst.UnOp) !WValue {
140 .decl = target,253 const operand = self.resolveInst(inst.operand);
141 });254
142}255 // ensure index to local
256 return WValue{ .local = operand.local };
257 }
258
259 fn genArg(self: *Code, inst: *Inst.Arg) !WValue {
260 // arguments share the index with locals
261 defer self.local_index += 1;
262 return WValue{ .local = self.local_index };
263 }
264
265 fn emitConstant(self: *Code, inst: *Inst.Constant) !void {
266 const writer = self.bytes.writer();
267 switch (inst.base.ty.tag()) {
268 .u32 => {
269 try writer.writeByte(0x41); // i32.const
270 try leb.writeILEB128(writer, inst.val.toUnsignedInt());
271 },
272 .i32 => {
273 try writer.writeByte(0x41); // i32.const
274 try leb.writeILEB128(writer, inst.val.toSignedInt());
275 },
276 .u64 => {
277 try writer.writeByte(0x42); // i64.const
278 try leb.writeILEB128(writer, inst.val.toUnsignedInt());
279 },
280 .i64 => {
281 try writer.writeByte(0x42); // i64.const
282 try leb.writeILEB128(writer, inst.val.toSignedInt());
283 },
284 .f32 => {
285 try writer.writeByte(0x43); // f32.const
286 // TODO: enforce LE byte order
287 try writer.writeAll(mem.asBytes(&inst.val.toFloat(f32)));
288 },
289 .f64 => {
290 try writer.writeByte(0x44); // f64.const
291 // TODO: enforce LE byte order
292 try writer.writeAll(mem.asBytes(&inst.val.toFloat(f64)));
293 },
294 .void => {},
295 else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for type {s}", .{ty}),
296 }
297 }
298};
src/link/Wasm.zig+23-4
...@@ -118,10 +118,29 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {...@@ -118,10 +118,29 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
118118
119 var managed_functype = fn_data.functype.toManaged(self.base.allocator);119 var managed_functype = fn_data.functype.toManaged(self.base.allocator);
120 var managed_code = fn_data.code.toManaged(self.base.allocator);120 var managed_code = fn_data.code.toManaged(self.base.allocator);
121 try codegen.genFunctype(&managed_functype, decl);121
122 try codegen.genCode(&managed_code, decl);122 var code = codegen.Code{
123 fn_data.functype = managed_functype.toUnmanaged();123 .gpa = self.base.allocator,
124 fn_data.code = managed_code.toUnmanaged();124 .values = codegen.ValueTable.init(self.base.allocator),
125 .bytes = managed_code,
126 .func_type_data = managed_functype,
127 .decl = decl,
128 .err_msg = undefined,
129 };
130 defer code.values.deinit();
131
132 // generate the 'code' section for the function declaration
133 code.gen() catch |err| switch (err) {
134 error.CodegenFail => {
135 decl.analysis = .codegen_failure;
136 try module.failed_decls.put(module.gpa, decl, code.err_msg);
137 return;
138 },
139 else => |e| return err,
140 };
141
142 fn_data.functype = code.func_type_data.toUnmanaged();
143 fn_data.code = code.bytes.toUnmanaged();
125}144}
126145
127pub fn updateDeclExports(146pub fn updateDeclExports(