authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-01-18 12:35:52-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-01-18 12:35:52-08:00
log46dd058d59a108e84e822fb43cf011301bfc68bc
treea2fba653c177cec43ae0c3b0ae43df4e06e9c6b0
parent0353c9601a7a72cf453738818d0fba4e980512ba
parent6a87ce0b62a3c9d3a795f3a16b1650f4a8c3b2fc
signature Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #7797 from Luukdegram/wasm-refactor

stage2: wasm - Refactor codegen for wasm similar to other backends

4 files changed, 423 insertions(+), 169 deletions(-)

src/codegen/wasm.zig+274-104
......@@ -7,136 +7,306 @@ const mem = std.mem;
77
88const Module = @import("../Module.zig");
99const Decl = Module.Decl;
10const Inst = @import("../ir.zig").Inst;
10const ir = @import("../ir.zig");
11const Inst = ir.Inst;
1112const Type = @import("../type.zig").Type;
1213const 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 /// Instruction holding a constant `Value`
22 constant: *Inst,
23 /// Block label
24 block_idx: u32,
25};
26
27/// Hashmap to store generated `WValue` for each `Inst`
28pub const ValueTable = std.AutoHashMap(*Inst, WValue);
29
30/// Using a given `Type`, returns the corresponding wasm value type
31fn genValtype(ty: Type) ?u8 {
1532 return switch (ty.tag()) {
16 .u32, .i32 => 0x7F,
17 .u64, .i64 => 0x7E,
1833 .f32 => 0x7D,
1934 .f64 => 0x7C,
20 else => @panic("TODO: Implement more types for wasm."),
35 .u32, .i32 => 0x7F,
36 .u64, .i64 => 0x7E,
37 else => null,
2138 };
2239}
2340
24pub fn genFunctype(buf: *ArrayList(u8), decl: *Decl) !void {
25 const ty = decl.typed_value.most_recent.typed_value.ty;
26 const writer = buf.writer();
41/// Code represents the `Code` section of wasm that
42/// belongs to a function
43pub const Context = 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 bytecode belonging to the 'code' section.
51 code: ArrayList(u8),
52 /// Contains the generated function type bytecode for the current function
53 /// found in `decl`
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 /// If codegen fails, an error messages will be allocated and saved in `err_msg`
60 err_msg: *Compilation.ErrorMsg,
61
62 const InnerError = error{
63 OutOfMemory,
64 CodegenFail,
65 };
66
67 /// Sets `err_msg` on `Context` and returns `error.CodegemFail` which is caught in link/Wasm.zig
68 fn fail(self: *Context, src: usize, comptime fmt: []const u8, args: anytype) InnerError {
69 self.err_msg = try Compilation.ErrorMsg.create(self.gpa, src, fmt, args);
70 return error.CodegenFail;
71 }
72
73 /// Resolves the `WValue` for the given instruction `inst`
74 /// When the given instruction has a `Value`, it returns a constant instead
75 fn resolveInst(self: Context, inst: *Inst) WValue {
76 if (!inst.ty.hasCodeGenBits()) return .none;
77
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: *Context, val: WValue) InnerError!void {
87 const writer = self.code.writer();
88 switch (val) {
89 .none, .block_idx => {},
90 .local => |idx| {
91 try writer.writeByte(0x20); // local.get
92 try leb.writeULEB128(writer, idx);
93 },
94 .constant => |inst| try self.emitConstant(inst.castTag(.constant).?), // creates a new constant onto the stack
95 }
96 }
97
98 fn genFunctype(self: *Context) InnerError!void {
99 const ty = self.decl.typed_value.most_recent.typed_value.ty;
100 const writer = self.func_type_data.writer();
27101
28 // functype magic
29 try writer.writeByte(0x60);
102 // functype magic
103 try writer.writeByte(0x60);
30104
31 // param types
32 try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen()));
33 if (ty.fnParamLen() != 0) {
34 const params = try buf.allocator.alloc(Type, ty.fnParamLen());
35 defer buf.allocator.free(params);
36 ty.fnParamTypes(params);
37 for (params) |param_type| try writer.writeByte(genValtype(param_type));
105 // param types
106 try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen()));
107 if (ty.fnParamLen() != 0) {
108 const params = try self.gpa.alloc(Type, ty.fnParamLen());
109 defer self.gpa.free(params);
110 ty.fnParamTypes(params);
111 for (params) |param_type| {
112 const val_type = genValtype(param_type) orelse
113 return self.fail(self.decl.src(), "TODO: Wasm codegen - arg type value for type '{s}'", .{param_type.tag()});
114 try writer.writeByte(val_type);
115 }
116 }
117
118 // return type
119 const return_type = ty.fnReturnType();
120 switch (return_type.tag()) {
121 .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)),
122 else => |ret_type| {
123 try leb.writeULEB128(writer, @as(u32, 1));
124 const val_type = genValtype(return_type) orelse
125 return self.fail(self.decl.src(), "TODO: Wasm codegen - return type value for type '{s}'", .{ret_type});
126 try writer.writeByte(val_type);
127 },
128 }
38129 }
39130
40 // return type
41 const return_type = ty.fnReturnType();
42 switch (return_type.tag()) {
43 .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)),
44 else => {
131 /// Generates the wasm bytecode for the function declaration belonging to `Context`
132 pub fn gen(self: *Context) InnerError!void {
133 assert(self.code.items.len == 0);
134 try self.genFunctype();
135 const writer = self.code.writer();
136
137 // Reserve space to write the size after generating the code
138 try self.code.resize(5);
139
140 // Write instructions
141 // TODO: check for and handle death of instructions
142 const tv = self.decl.typed_value.most_recent.typed_value;
143 const mod_fn = tv.val.castTag(.function).?.data;
144
145 var locals = std.ArrayList(u8).init(self.gpa);
146 defer locals.deinit();
147
148 for (mod_fn.body.instructions) |inst| {
149 if (inst.tag != .alloc) continue;
150
151 const alloc: *Inst.NoOp = inst.castTag(.alloc).?;
152 const elem_type = alloc.base.ty.elemType();
153
154 const wasm_type = genValtype(elem_type) orelse
155 return self.fail(inst.src, "TODO: Wasm codegen - valtype for type '{s}'", .{elem_type.tag()});
156
157 try locals.append(wasm_type);
158 }
159
160 try leb.writeULEB128(writer, @intCast(u32, locals.items.len));
161
162 // emit the actual locals amount
163 for (locals.items) |local| {
45164 try leb.writeULEB128(writer, @as(u32, 1));
46 try writer.writeByte(genValtype(return_type));
47 },
165 try leb.writeULEB128(writer, local); // valtype
166 }
167
168 try self.genBody(mod_fn.body);
169
170 try writer.writeByte(0x0B); // end
171
172 // Fill in the size of the generated code to the reserved space at the
173 // beginning of the buffer.
174 const size = self.code.items.len - 5 + self.decl.fn_link.wasm.?.idx_refs.items.len * 5;
175 leb.writeUnsignedFixed(5, self.code.items[0..5], @intCast(u32, size));
48176 }
49}
50177
51pub fn genCode(buf: *ArrayList(u8), decl: *Decl) !void {
52 assert(buf.items.len == 0);
53 const writer = buf.writer();
178 fn genInst(self: *Context, inst: *Inst) InnerError!WValue {
179 return switch (inst.tag) {
180 .add => self.genAdd(inst.castTag(.add).?),
181 .alloc => self.genAlloc(inst.castTag(.alloc).?),
182 .arg => self.genArg(inst.castTag(.arg).?),
183 .call => self.genCall(inst.castTag(.call).?),
184 .constant => unreachable,
185 .dbg_stmt => WValue.none,
186 .load => self.genLoad(inst.castTag(.load).?),
187 .ret => self.genRet(inst.castTag(.ret).?),
188 .retvoid => WValue.none,
189 .store => self.genStore(inst.castTag(.store).?),
190 else => self.fail(inst.src, "TODO: Implement wasm inst: {s}", .{inst.tag}),
191 };
192 }
54193
55 // Reserve space to write the size after generating the code
56 try buf.resize(5);
194 fn genBody(self: *Context, body: ir.Body) InnerError!void {
195 for (body.instructions) |inst| {
196 const result = try self.genInst(inst);
197 try self.values.putNoClobber(inst, result);
198 }
199 }
57200
58 // Write the size of the locals vec
59 // TODO: implement locals
60 try leb.writeULEB128(writer, @as(u32, 0));
201 fn genRet(self: *Context, inst: *Inst.UnOp) InnerError!WValue {
202 // TODO: Implement tail calls
203 const operand = self.resolveInst(inst.operand);
204 try self.emitWValue(operand);
205 return WValue.none;
206 }
61207
62 // Write instructions
63 // TODO: check for and handle death of instructions
64 const tv = decl.typed_value.most_recent.typed_value;
65 const mod_fn = tv.val.castTag(.function).?.data;
66 for (mod_fn.body.instructions) |inst| try genInst(buf, decl, inst);
208 fn genCall(self: *Context, inst: *Inst.Call) InnerError!WValue {
209 const func_inst = inst.func.castTag(.constant).?;
210 const func = func_inst.val.castTag(.function).?.data;
211 const target = func.owner_decl;
212 const target_ty = target.typed_value.most_recent.typed_value.ty;
67213
68 // Write 'end' opcode
69 try writer.writeByte(0x0B);
214 for (inst.args) |arg| {
215 const arg_val = self.resolveInst(arg);
216 try self.emitWValue(arg_val);
217 }
70218
71 // Fill in the size of the generated code to the reserved space at the
72 // beginning of the buffer.
73 const size = buf.items.len - 5 + decl.fn_link.wasm.?.idx_refs.items.len * 5;
74 leb.writeUnsignedFixed(5, buf.items[0..5], @intCast(u32, size));
75}
219 try self.code.append(0x10); // call
76220
77fn genInst(buf: *ArrayList(u8), decl: *Decl, inst: *Inst) !void {
78 return switch (inst.tag) {
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}
221 // The function index immediate argument will be filled in using this data
222 // in link.Wasm.flush().
223 try self.decl.fn_link.wasm.?.idx_refs.append(self.gpa, .{
224 .offset = @intCast(u32, self.code.items.len),
225 .decl = target,
226 });
87227
88fn genConstant(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.Constant) !void {
89 const writer = buf.writer();
90 switch (inst.base.ty.tag()) {
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,
228 return WValue.none;
119229 }
120}
121230
122fn genRet(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.UnOp) !void {
123 try genInst(buf, decl, inst.operand);
124}
231 fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue {
232 defer self.local_index += 1;
233 return WValue{ .local = self.local_index };
234 }
125235
126fn genCall(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.Call) !void {
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;
236 fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue {
237 const writer = self.code.writer();
131238
132 if (inst.args.len != 0) return error.TODOImplementMoreWasmCodegen;
239 const lhs = self.resolveInst(inst.lhs);
240 const rhs = self.resolveInst(inst.rhs);
241 try self.emitWValue(rhs);
133242
134 try buf.append(0x10); // call
243 try writer.writeByte(0x21); // local.set
244 try leb.writeULEB128(writer, lhs.local);
245 return WValue.none;
246 }
135247
136 // The function index immediate argument will be filled in using this data
137 // in link.Wasm.flush().
138 try decl.fn_link.wasm.?.idx_refs.append(buf.allocator, .{
139 .offset = @intCast(u32, buf.items.len),
140 .decl = target,
141 });
142}
248 fn genLoad(self: *Context, inst: *Inst.UnOp) InnerError!WValue {
249 const operand = self.resolveInst(inst.operand);
250 try self.emitWValue(operand);
251 return WValue.none;
252 }
253
254 fn genArg(self: *Context, inst: *Inst.Arg) InnerError!WValue {
255 // arguments share the index with locals
256 defer self.local_index += 1;
257 return WValue{ .local = self.local_index };
258 }
259
260 fn genAdd(self: *Context, inst: *Inst.BinOp) InnerError!WValue {
261 const lhs = self.resolveInst(inst.lhs);
262 const rhs = self.resolveInst(inst.rhs);
263
264 try self.emitWValue(lhs);
265 try self.emitWValue(rhs);
266
267 const opcode: u8 = switch (inst.base.ty.tag()) {
268 .u32, .i32 => 0x6A, //i32.add
269 .u64, .i64 => 0x7C, //i64.add
270 .f32 => 0x92, //f32.add
271 .f64 => 0xA0, //f64.add
272 else => return self.fail(inst.base.src, "TODO - Implement wasm genAdd for type '{s}'", .{inst.base.ty.tag()}),
273 };
274
275 try self.code.append(opcode);
276 return WValue.none;
277 }
278
279 fn emitConstant(self: *Context, inst: *Inst.Constant) InnerError!void {
280 const writer = self.code.writer();
281 switch (inst.base.ty.tag()) {
282 .u32 => {
283 try writer.writeByte(0x41); // i32.const
284 try leb.writeILEB128(writer, inst.val.toUnsignedInt());
285 },
286 .i32 => {
287 try writer.writeByte(0x41); // i32.const
288 try leb.writeILEB128(writer, inst.val.toSignedInt());
289 },
290 .u64 => {
291 try writer.writeByte(0x42); // i64.const
292 try leb.writeILEB128(writer, inst.val.toUnsignedInt());
293 },
294 .i64 => {
295 try writer.writeByte(0x42); // i64.const
296 try leb.writeILEB128(writer, inst.val.toSignedInt());
297 },
298 .f32 => {
299 try writer.writeByte(0x43); // f32.const
300 // TODO: enforce LE byte order
301 try writer.writeAll(mem.asBytes(&inst.val.toFloat(f32)));
302 },
303 .f64 => {
304 try writer.writeByte(0x44); // f64.const
305 // TODO: enforce LE byte order
306 try writer.writeAll(mem.asBytes(&inst.val.toFloat(f64)));
307 },
308 .void => {},
309 else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for type {s}", .{ty}),
310 }
311 }
312};
src/link/Wasm.zig+23-4
......@@ -118,10 +118,29 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
118118
119119 var managed_functype = fn_data.functype.toManaged(self.base.allocator);
120120 var managed_code = fn_data.code.toManaged(self.base.allocator);
121 try codegen.genFunctype(&managed_functype, decl);
122 try codegen.genCode(&managed_code, decl);
123 fn_data.functype = managed_functype.toUnmanaged();
124 fn_data.code = managed_code.toUnmanaged();
121
122 var context = codegen.Context{
123 .gpa = self.base.allocator,
124 .values = codegen.ValueTable.init(self.base.allocator),
125 .code = managed_code,
126 .func_type_data = managed_functype,
127 .decl = decl,
128 .err_msg = undefined,
129 };
130 defer context.values.deinit();
131
132 // generate the 'code' section for the function declaration
133 context.gen() catch |err| switch (err) {
134 error.CodegenFail => {
135 decl.analysis = .codegen_failure;
136 try module.failed_decls.put(module.gpa, decl, context.err_msg);
137 return;
138 },
139 else => |e| return err,
140 };
141
142 fn_data.functype = context.func_type_data.toUnmanaged();
143 fn_data.code = context.code.toUnmanaged();
125144}
126145
127146pub fn updateDeclExports(
test/stage2/test.zig+1-61
......@@ -21,17 +21,13 @@ const linux_riscv64 = std.zig.CrossTarget{
2121 .os_tag = .linux,
2222};
2323
24const wasi = std.zig.CrossTarget{
25 .cpu_arch = .wasm32,
26 .os_tag = .wasi,
27};
28
2924pub fn addCases(ctx: *TestContext) !void {
3025 try @import("cbe.zig").addCases(ctx);
3126 try @import("spu-ii.zig").addCases(ctx);
3227 try @import("arm.zig").addCases(ctx);
3328 try @import("aarch64.zig").addCases(ctx);
3429 try @import("llvm.zig").addCases(ctx);
30 try @import("wasm.zig").addCases(ctx);
3531
3632 {
3733 var case = ctx.exe("hello world with updates", linux_x64);
......@@ -1136,62 +1132,6 @@ pub fn addCases(ctx: *TestContext) !void {
11361132 });
11371133 }
11381134
1139 {
1140 var case = ctx.exe("wasm function calls", wasi);
1141
1142 case.addCompareOutput(
1143 \\export fn _start() u32 {
1144 \\ foo();
1145 \\ bar();
1146 \\ return 42;
1147 \\}
1148 \\fn foo() void {
1149 \\ bar();
1150 \\ bar();
1151 \\}
1152 \\fn bar() void {}
1153 ,
1154 "42\n",
1155 );
1156
1157 case.addCompareOutput(
1158 \\export fn _start() i64 {
1159 \\ bar();
1160 \\ foo();
1161 \\ foo();
1162 \\ bar();
1163 \\ foo();
1164 \\ bar();
1165 \\ return 42;
1166 \\}
1167 \\fn foo() void {
1168 \\ bar();
1169 \\}
1170 \\fn bar() void {}
1171 ,
1172 "42\n",
1173 );
1174
1175 case.addCompareOutput(
1176 \\export fn _start() f32 {
1177 \\ bar();
1178 \\ foo();
1179 \\ return 42.0;
1180 \\}
1181 \\fn foo() void {
1182 \\ bar();
1183 \\ bar();
1184 \\ bar();
1185 \\}
1186 \\fn bar() void {}
1187 ,
1188 // This is what you get when you take the bits of the IEE-754
1189 // representation of 42.0 and reinterpret them as an unsigned
1190 // integer. Guess that's a bug in wasmtime.
1191 "1109917696\n",
1192 );
1193 }
1194
11951135 ctx.compileError("function redefinition", linux_x64,
11961136 \\fn entry() void {}
11971137 \\fn entry() void {}
test/stage2/wasm.zig created+125
......@@ -0,0 +1,125 @@
1const std = @import("std");
2const TestContext = @import("../../src/test.zig").TestContext;
3
4const wasi = std.zig.CrossTarget{
5 .cpu_arch = .wasm32,
6 .os_tag = .wasi,
7};
8
9pub fn addCases(ctx: *TestContext) !void {
10 {
11 var case = ctx.exe("wasm function calls", wasi);
12
13 case.addCompareOutput(
14 \\export fn _start() u32 {
15 \\ foo();
16 \\ bar();
17 \\ return 42;
18 \\}
19 \\fn foo() void {
20 \\ bar();
21 \\ bar();
22 \\}
23 \\fn bar() void {}
24 ,
25 "42\n",
26 );
27
28 case.addCompareOutput(
29 \\export fn _start() i64 {
30 \\ bar();
31 \\ foo();
32 \\ foo();
33 \\ bar();
34 \\ foo();
35 \\ bar();
36 \\ return 42;
37 \\}
38 \\fn foo() void {
39 \\ bar();
40 \\}
41 \\fn bar() void {}
42 ,
43 "42\n",
44 );
45
46 case.addCompareOutput(
47 \\export fn _start() f32 {
48 \\ bar();
49 \\ foo();
50 \\ return 42.0;
51 \\}
52 \\fn foo() void {
53 \\ bar();
54 \\ bar();
55 \\ bar();
56 \\}
57 \\fn bar() void {}
58 ,
59 // This is what you get when you take the bits of the IEE-754
60 // representation of 42.0 and reinterpret them as an unsigned
61 // integer. Guess that's a bug in wasmtime.
62 "1109917696\n",
63 );
64
65 case.addCompareOutput(
66 \\export fn _start() u32 {
67 \\ foo(10, 20);
68 \\ return 5;
69 \\}
70 \\fn foo(x: u32, y: u32) void {}
71 , "5\n");
72 }
73
74 {
75 var case = ctx.exe("wasm locals", wasi);
76
77 case.addCompareOutput(
78 \\export fn _start() u32 {
79 \\ var i: u32 = 5;
80 \\ var y: f32 = 42.0;
81 \\ var x: u32 = 10;
82 \\ return i;
83 \\}
84 , "5\n");
85
86 case.addCompareOutput(
87 \\export fn _start() u32 {
88 \\ var i: u32 = 5;
89 \\ var y: f32 = 42.0;
90 \\ var x: u32 = 10;
91 \\ foo(i, x);
92 \\ i = x;
93 \\ return i;
94 \\}
95 \\fn foo(x: u32, y: u32) void {
96 \\ var i: u32 = 10;
97 \\ i = x;
98 \\}
99 , "10\n");
100 }
101
102 {
103 var case = ctx.exe("wasm binary operands", wasi);
104
105 case.addCompareOutput(
106 \\export fn _start() u32 {
107 \\ var i: u32 = 5;
108 \\ i += 20;
109 \\ return i;
110 \\}
111 , "25\n");
112
113 case.addCompareOutput(
114 \\export fn _start() u32 {
115 \\ var i: u32 = 5;
116 \\ i += 20;
117 \\ var result: u32 = foo(i, 10);
118 \\ return result;
119 \\}
120 \\fn foo(x: u32, y: u32) u32 {
121 \\ return x + y;
122 \\}
123 , "35\n");
124 }
125}