authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-01-24 14:35:14+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-01-24 14:38:35+01:00
loga0d81caec99fe0d1cd803b0ba461b6e02829b476
tree801e8c6ecc86426a98dd59dac2dfbfe3ca21cff2
parentccef167e9d5b3a34151cac46e39040b53f12eb68
signaturelock-open Commit is signed but in an unrecognized format.

Nested conditions and loops support


2 files changed, 263 insertions(+), 69 deletions(-)

src/codegen/wasm.zig+260-67
......@@ -4,6 +4,7 @@ const ArrayList = std.ArrayList;
44const assert = std.debug.assert;
55const leb = std.leb;
66const mem = std.mem;
7const wasm = std.wasm;
78
89const Module = @import("../Module.zig");
910const Decl = Module.Decl;
......@@ -12,6 +13,7 @@ const Inst = ir.Inst;
1213const Type = @import("../type.zig").Type;
1314const Value = @import("../value.zig").Value;
1415const Compilation = @import("../Compilation.zig");
16const AnyMCValue = @import("../codegen.zig").AnyMCValue;
1517
1618/// Wasm Value, created when generating an instruction
1719const WValue = union(enum) {
......@@ -20,23 +22,14 @@ const WValue = union(enum) {
2022 local: u32,
2123 /// Instruction holding a constant `Value`
2224 constant: *Inst,
23 /// Block label
25 /// Offset position in the list of bytecode instructions
26 code_offset: usize,
27 /// The label of the block, used by breaks to find its relative distance
2428 block_idx: u32,
2529};
2630
2731/// 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 {
32 return switch (ty.tag()) {
33 .f32 => 0x7D,
34 .f64 => 0x7C,
35 .u32, .i32 => 0x7F,
36 .u64, .i64 => 0x7E,
37 else => null,
38 };
39}
32pub const ValueTable = std.AutoHashMapUnmanaged(*Inst, WValue);
4033
4134/// Code represents the `Code` section of wasm that
4235/// belongs to a function
......@@ -58,13 +51,25 @@ pub const Context = struct {
5851 local_index: u32 = 0,
5952 /// If codegen fails, an error messages will be allocated and saved in `err_msg`
6053 err_msg: *Module.ErrorMsg,
54 /// Current block depth. Used to calculate the relative difference between a break
55 /// and block
56 block_depth: u32 = 0,
57 /// List of all locals' types generated throughout this declaration
58 /// used to emit locals count at start of 'code' section.
59 locals: std.ArrayListUnmanaged(u8),
6160
6261 const InnerError = error{
6362 OutOfMemory,
6463 CodegenFail,
6564 };
6665
67 /// Sets `err_msg` on `Context` and returns `error.CodegenFail` which is caught in link/Wasm.zig
66 pub fn deinit(self: *Context) void {
67 self.values.deinit(self.gpa);
68 self.locals.deinit(self.gpa);
69 self.* = undefined;
70 }
71
72 /// Sets `err_msg` on `Context` and returns `error.CodegemFail` which is caught in link/Wasm.zig
6873 fn fail(self: *Context, src: usize, comptime fmt: []const u8, args: anytype) InnerError {
6974 self.err_msg = try Module.ErrorMsg.create(self.gpa, .{
7075 .file_scope = self.decl.getFileScope(),
......@@ -85,13 +90,35 @@ pub const Context = struct {
8590 return self.values.get(inst).?; // Instruction does not dominate all uses!
8691 }
8792
93 /// Using a given `Type`, returns the corresponding wasm value type
94 fn genValtype(self: *Context, src: usize, ty: Type) InnerError!u8 {
95 return switch (ty.tag()) {
96 .f32 => wasm.valtype(.f32),
97 .f64 => wasm.valtype(.f64),
98 .u32, .i32 => wasm.valtype(.i32),
99 .u64, .i64 => wasm.valtype(.i64),
100 else => self.fail(src, "TODO - Wasm genValtype for type '{s}'", .{ty.tag()}),
101 };
102 }
103
104 /// Using a given `Type`, returns the corresponding wasm value type
105 /// Differently from `genValtype` this also allows `void` to create a block
106 /// with no return type
107 fn genBlockType(self: *Context, src: usize, ty: Type) InnerError!u8 {
108 return switch (ty.tag()) {
109 .void, .noreturn => wasm.block_empty,
110 else => self.genValtype(src, ty),
111 };
112 }
113
88114 /// Writes the bytecode depending on the given `WValue` in `val`
89115 fn emitWValue(self: *Context, val: WValue) InnerError!void {
90116 const writer = self.code.writer();
91117 switch (val) {
92 .none, .block_idx => {},
118 .block_idx => unreachable,
119 .none, .code_offset => {},
93120 .local => |idx| {
94 try writer.writeByte(0x20); // local.get
121 try writer.writeByte(wasm.opcode(.local_get));
95122 try leb.writeULEB128(writer, idx);
96123 },
97124 .constant => |inst| try self.emitConstant(inst.castTag(.constant).?), // creates a new constant onto the stack
......@@ -102,8 +129,7 @@ pub const Context = struct {
102129 const ty = self.decl.typed_value.most_recent.typed_value.ty;
103130 const writer = self.func_type_data.writer();
104131
105 // functype magic
106 try writer.writeByte(0x60);
132 try writer.writeByte(wasm.function_type);
107133
108134 // param types
109135 try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen()));
......@@ -112,8 +138,8 @@ pub const Context = struct {
112138 defer self.gpa.free(params);
113139 ty.fnParamTypes(params);
114140 for (params) |param_type| {
115 const val_type = genValtype(param_type) orelse
116 return self.fail(self.decl.src(), "TODO: Wasm codegen - arg type value for type '{s}'", .{param_type.tag()});
141 // Can we maybe get the source index of each param?
142 const val_type = try self.genValtype(self.decl.src(), param_type);
117143 try writer.writeByte(val_type);
118144 }
119145 }
......@@ -124,8 +150,8 @@ pub const Context = struct {
124150 .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)),
125151 else => |ret_type| {
126152 try leb.writeULEB128(writer, @as(u32, 1));
127 const val_type = genValtype(return_type) orelse
128 return self.fail(self.decl.src(), "TODO: Wasm codegen - return type value for type '{s}'", .{ret_type});
153 // Can we maybe get the source index of the return type?
154 const val_type = try self.genValtype(self.decl.src(), return_type);
129155 try writer.writeByte(val_type);
130156 },
131157 }
......@@ -140,37 +166,33 @@ pub const Context = struct {
140166 // Reserve space to write the size after generating the code
141167 try self.code.resize(5);
142168
169 // offset into 'code' section where we will put our locals count
170 var local_offset = self.code.items.len;
171
143172 // Write instructions
144173 // TODO: check for and handle death of instructions
145174 const tv = self.decl.typed_value.most_recent.typed_value;
146175 const mod_fn = tv.val.castTag(.function).?.data;
176 try self.genBody(mod_fn.body);
147177
148 var locals = std.ArrayList(u8).init(self.gpa);
149 defer locals.deinit();
150
151 for (mod_fn.body.instructions) |inst| {
152 if (inst.tag != .alloc) continue;
153
154 const alloc: *Inst.NoOp = inst.castTag(.alloc).?;
155 const elem_type = alloc.base.ty.elemType();
156
157 const wasm_type = genValtype(elem_type) orelse
158 return self.fail(inst.src, "TODO: Wasm codegen - valtype for type '{s}'", .{elem_type.tag()});
159
160 try locals.append(wasm_type);
161 }
162
163 try leb.writeULEB128(writer, @intCast(u32, locals.items.len));
164
165 // emit the actual locals amount
166 for (locals.items) |local| {
167 try leb.writeULEB128(writer, @as(u32, 1));
168 try leb.writeULEB128(writer, local); // valtype
178 // finally, write our local types at the 'offset' position
179 {
180 var totals_buffer: [5]u8 = undefined;
181 leb.writeUnsignedFixed(5, totals_buffer[0..5], @intCast(u32, self.locals.items.len));
182 try self.code.insertSlice(local_offset, &totals_buffer);
183 local_offset += 5;
184
185 // emit the actual locals amount
186 for (self.locals.items) |local| {
187 var buf: [6]u8 = undefined;
188 leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1));
189 buf[5] = local;
190 try self.code.insertSlice(local_offset, &buf);
191 local_offset += 6;
192 }
169193 }
170194
171 try self.genBody(mod_fn.body);
172
173 try writer.writeByte(0x0B); // end
195 try writer.writeByte(wasm.opcode(.end));
174196
175197 // Fill in the size of the generated code to the reserved space at the
176198 // beginning of the buffer.
......@@ -183,10 +205,20 @@ pub const Context = struct {
183205 .add => self.genAdd(inst.castTag(.add).?),
184206 .alloc => self.genAlloc(inst.castTag(.alloc).?),
185207 .arg => self.genArg(inst.castTag(.arg).?),
208 .block => self.genBlock(inst.castTag(.block).?),
209 .br => self.genBr(inst.castTag(.br).?),
186210 .call => self.genCall(inst.castTag(.call).?),
211 .cmp_eq => self.genCmp(inst.castTag(.cmp_eq).?, .eq),
212 .cmp_gte => self.genCmp(inst.castTag(.cmp_gte).?, .gte),
213 .cmp_gt => self.genCmp(inst.castTag(.cmp_gt).?, .gt),
214 .cmp_lte => self.genCmp(inst.castTag(.cmp_lte).?, .lte),
215 .cmp_lt => self.genCmp(inst.castTag(.cmp_lt).?, .lt),
216 .cmp_neq => self.genCmp(inst.castTag(.cmp_neq).?, .neq),
217 .condbr => self.genCondBr(inst.castTag(.condbr).?),
187218 .constant => unreachable,
188219 .dbg_stmt => WValue.none,
189220 .load => self.genLoad(inst.castTag(.load).?),
221 .loop => self.genLoop(inst.castTag(.loop).?),
190222 .ret => self.genRet(inst.castTag(.ret).?),
191223 .retvoid => WValue.none,
192224 .store => self.genStore(inst.castTag(.store).?),
......@@ -197,7 +229,7 @@ pub const Context = struct {
197229 fn genBody(self: *Context, body: ir.Body) InnerError!void {
198230 for (body.instructions) |inst| {
199231 const result = try self.genInst(inst);
200 try self.values.putNoClobber(inst, result);
232 try self.values.putNoClobber(self.gpa, inst, result);
201233 }
202234 }
203235
......@@ -205,7 +237,7 @@ pub const Context = struct {
205237 // TODO: Implement tail calls
206238 const operand = self.resolveInst(inst.operand);
207239 try self.emitWValue(operand);
208 return WValue.none;
240 return .none;
209241 }
210242
211243 fn genCall(self: *Context, inst: *Inst.Call) InnerError!WValue {
......@@ -219,7 +251,7 @@ pub const Context = struct {
219251 try self.emitWValue(arg_val);
220252 }
221253
222 try self.code.append(0x10); // call
254 try self.code.append(wasm.opcode(.call));
223255
224256 // The function index immediate argument will be filled in using this data
225257 // in link.Wasm.flush().
......@@ -228,10 +260,14 @@ pub const Context = struct {
228260 .decl = target,
229261 });
230262
231 return WValue.none;
263 return .none;
232264 }
233265
234266 fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue {
267 const elem_type = inst.base.ty.elemType();
268 const valtype = try self.genValtype(inst.base.src, elem_type);
269 try self.locals.append(self.gpa, valtype);
270
235271 defer self.local_index += 1;
236272 return WValue{ .local = self.local_index };
237273 }
......@@ -243,15 +279,14 @@ pub const Context = struct {
243279 const rhs = self.resolveInst(inst.rhs);
244280 try self.emitWValue(rhs);
245281
246 try writer.writeByte(0x21); // local.set
282 try writer.writeByte(wasm.opcode(.local_set));
247283 try leb.writeULEB128(writer, lhs.local);
248 return WValue.none;
284 return .none;
249285 }
250286
251287 fn genLoad(self: *Context, inst: *Inst.UnOp) InnerError!WValue {
252288 const operand = self.resolveInst(inst.operand);
253 try self.emitWValue(operand);
254 return WValue.none;
289 return operand;
255290 }
256291
257292 fn genArg(self: *Context, inst: *Inst.Arg) InnerError!WValue {
......@@ -267,44 +302,44 @@ pub const Context = struct {
267302 try self.emitWValue(lhs);
268303 try self.emitWValue(rhs);
269304
270 const opcode: u8 = switch (inst.base.ty.tag()) {
271 .u32, .i32 => 0x6A, //i32.add
272 .u64, .i64 => 0x7C, //i64.add
273 .f32 => 0x92, //f32.add
274 .f64 => 0xA0, //f64.add
305 const opcode: wasm.Opcode = switch (inst.base.ty.tag()) {
306 .u32, .i32 => .i32_add,
307 .u64, .i64 => .i64_add,
308 .f32 => .f32_add,
309 .f64 => .f64_add,
275310 else => return self.fail(inst.base.src, "TODO - Implement wasm genAdd for type '{s}'", .{inst.base.ty.tag()}),
276311 };
277312
278 try self.code.append(opcode);
279 return WValue.none;
313 try self.code.append(wasm.opcode(opcode));
314 return .none;
280315 }
281316
282317 fn emitConstant(self: *Context, inst: *Inst.Constant) InnerError!void {
283318 const writer = self.code.writer();
284319 switch (inst.base.ty.tag()) {
285320 .u32 => {
286 try writer.writeByte(0x41); // i32.const
321 try writer.writeByte(wasm.opcode(.i32_const));
287322 try leb.writeILEB128(writer, inst.val.toUnsignedInt());
288323 },
289324 .i32 => {
290 try writer.writeByte(0x41); // i32.const
325 try writer.writeByte(wasm.opcode(.i32_const));
291326 try leb.writeILEB128(writer, inst.val.toSignedInt());
292327 },
293328 .u64 => {
294 try writer.writeByte(0x42); // i64.const
329 try writer.writeByte(wasm.opcode(.i64_const));
295330 try leb.writeILEB128(writer, inst.val.toUnsignedInt());
296331 },
297332 .i64 => {
298 try writer.writeByte(0x42); // i64.const
333 try writer.writeByte(wasm.opcode(.i64_const));
299334 try leb.writeILEB128(writer, inst.val.toSignedInt());
300335 },
301336 .f32 => {
302 try writer.writeByte(0x43); // f32.const
337 try writer.writeByte(wasm.opcode(.f32_const));
303338 // TODO: enforce LE byte order
304339 try writer.writeAll(mem.asBytes(&inst.val.toFloat(f32)));
305340 },
306341 .f64 => {
307 try writer.writeByte(0x44); // f64.const
342 try writer.writeByte(wasm.opcode(.f64_const));
308343 // TODO: enforce LE byte order
309344 try writer.writeAll(mem.asBytes(&inst.val.toFloat(f64)));
310345 },
......@@ -312,4 +347,162 @@ pub const Context = struct {
312347 else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for type {s}", .{ty}),
313348 }
314349 }
350
351 fn genBlock(self: *Context, block: *Inst.Block) InnerError!WValue {
352 const block_ty = try self.genBlockType(block.base.src, block.base.ty);
353
354 block.codegen = .{
355 // we don't use relocs, so using `relocs` is illegal behaviour.
356 .relocs = undefined,
357 // Here we set the current block idx, so conditions know the depth to jump
358 // to when breaking out. This will be set to .none when it is found again within
359 // the same block
360 .mcv = @bitCast(AnyMCValue, WValue{ .block_idx = self.block_depth }),
361 };
362 self.block_depth += 1;
363
364 try self.code.append(wasm.opcode(.block));
365 try self.code.append(block_ty);
366 try self.genBody(block.body);
367 try self.code.append(wasm.opcode(.end));
368
369 self.block_depth -= 1;
370 return .none;
371 }
372
373 fn genLoop(self: *Context, loop: *Inst.Loop) InnerError!WValue {
374 const loop_ty = try self.genBlockType(loop.base.src, loop.base.ty);
375
376 try self.code.append(wasm.opcode(.loop));
377 try self.code.append(loop_ty);
378 self.block_depth += 1;
379 try self.genBody(loop.body);
380 self.block_depth -= 1;
381
382 try self.code.append(wasm.opcode(.end));
383
384 return .none;
385 }
386
387 fn genCondBr(self: *Context, condbr: *Inst.CondBr) InnerError!WValue {
388 const condition = self.resolveInst(condbr.condition);
389 const writer = self.code.writer();
390
391 // insert blocks at the position of `offset` so
392 // the condition can jump to it
393 const offset = condition.code_offset;
394 try self.code.insert(offset, wasm.opcode(.block));
395 try self.code.insert(offset, try self.genBlockType(condbr.base.src, condbr.base.ty));
396
397 // we inserted the block in front of the condition
398 // so now check if condition matches. If not, break outside this block
399 // and continue with the regular codepath
400 try writer.writeByte(wasm.opcode(.br_if));
401 try leb.writeULEB128(writer, @as(u32, 0));
402
403 // else body in case condition does not match
404 try self.genBody(condbr.else_body);
405
406 // finally, tell wasm we have reached the end of the block we inserted above
407 try writer.writeByte(wasm.opcode(.end));
408
409 // Outer block that matches the condition
410 try self.genBody(condbr.then_body);
411
412 return .none;
413 }
414
415 fn genCmp(self: *Context, inst: *Inst.BinOp, op: std.math.CompareOperator) InnerError!WValue {
416 const ty = inst.lhs.ty.tag();
417
418 // save offset, so potential conditions can insert blocks in front of
419 // the comparison that we can later jump back to
420 const offset = self.code.items.len - 1;
421
422 const lhs = self.resolveInst(inst.lhs);
423 const rhs = self.resolveInst(inst.rhs);
424
425 try self.emitWValue(lhs);
426 try self.emitWValue(rhs);
427
428 const opcode_maybe: ?wasm.Opcode = switch (op) {
429 .lt => @as(?wasm.Opcode, switch (ty) {
430 .i32 => .i32_lt_s,
431 .u32 => .i32_lt_u,
432 .i64 => .i64_lt_s,
433 .u64 => .i64_lt_u,
434 .f32 => .f32_lt,
435 .f64 => .f64_lt,
436 else => null,
437 }),
438 .lte => @as(?wasm.Opcode, switch (ty) {
439 .i32 => .i32_le_s,
440 .u32 => .i32_le_u,
441 .i64 => .i64_le_s,
442 .u64 => .i64_le_u,
443 .f32 => .f32_le,
444 .f64 => .f64_le,
445 else => null,
446 }),
447 .eq => @as(?wasm.Opcode, switch (ty) {
448 .i32, .u32 => .i32_eq,
449 .i64, .u64 => .i64_eq,
450 .f32 => .f32_eq,
451 .f64 => .f64_eq,
452 else => null,
453 }),
454 .gte => @as(?wasm.Opcode, switch (ty) {
455 .i32 => .i32_ge_s,
456 .u32 => .i32_ge_u,
457 .i64 => .i64_ge_s,
458 .u64 => .i64_ge_u,
459 .f32 => .f32_ge,
460 .f64 => .f64_ge,
461 else => null,
462 }),
463 .gt => @as(?wasm.Opcode, switch (ty) {
464 .i32 => .i32_gt_s,
465 .u32 => .i32_gt_u,
466 .i64 => .i64_gt_s,
467 .u64 => .i64_gt_u,
468 .f32 => .f32_gt,
469 .f64 => .f64_gt,
470 else => null,
471 }),
472 .neq => @as(?wasm.Opcode, switch (ty) {
473 .i32, .u32 => .i32_ne,
474 .i64, .u64 => .i64_ne,
475 .f32 => .f32_ne,
476 .f64 => .f64_ne,
477 else => null,
478 }),
479 };
480
481 const opcode = opcode_maybe orelse
482 return self.fail(inst.base.src, "TODO - Wasm genCmp for type '{s}' and operator '{s}'", .{ ty, @tagName(op) });
483
484 try self.code.append(wasm.opcode(opcode));
485 return WValue{ .code_offset = offset };
486 }
487
488 fn genBr(self: *Context, br: *Inst.Br) InnerError!WValue {
489 // of operand has codegen bits we should break with a value
490 if (br.operand.ty.hasCodeGenBits()) {
491 const operand = self.resolveInst(br.operand);
492 try self.emitWValue(operand);
493 }
494
495 // if the block contains a block_idx, do a relative jump to it
496 // if `wvalue` was already 'consumed', simply break out of current block
497 const wvalue = @bitCast(WValue, br.block.codegen.mcv);
498 const idx: u32 = if (wvalue == .block_idx) blk: {
499 br.block.codegen.mcv = @bitCast(AnyMCValue, WValue{ .none = {} });
500 break :blk self.block_depth - wvalue.block_idx;
501 } else 0;
502
503 const writer = self.code.writer();
504 try writer.writeByte(wasm.opcode(.br));
505 try leb.writeULEB128(writer, idx);
506 return WValue.none;
507 }
315508};
src/link/Wasm.zig+3-2
......@@ -103,13 +103,14 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
103103
104104 var context = codegen.Context{
105105 .gpa = self.base.allocator,
106 .values = codegen.ValueTable.init(self.base.allocator),
106 .values = .{},
107107 .code = managed_code,
108108 .func_type_data = managed_functype,
109109 .decl = decl,
110110 .err_msg = undefined,
111 .locals = .{},
111112 };
112 defer context.values.deinit();
113 defer context.deinit();
113114
114115 // generate the 'code' section for the function declaration
115116 context.gen() catch |err| switch (err) {