| ... | @@ -7,136 +7,306 @@ const mem = std.mem; | ... | @@ -7,136 +7,306 @@ const mem = std.mem; |
| 7 | | 7 | |
| 8 | const Module = @import("../Module.zig"); | 8 | const Module = @import("../Module.zig"); |
| 9 | const Decl = Module.Decl; | 9 | const Decl = Module.Decl; |
| 10 | const Inst = @import("../ir.zig").Inst; | 10 | const ir = @import("../ir.zig"); |
| | 11 | const Inst = ir.Inst; |
| 11 | const Type = @import("../type.zig").Type; | 12 | const Type = @import("../type.zig").Type; |
| 12 | const Value = @import("../value.zig").Value; | 13 | const Value = @import("../value.zig").Value; |
| | 14 | const Compilation = @import("../Compilation.zig"); |
| 13 | | 15 | |
| 14 | fn genValtype(ty: Type) u8 { | 16 | /// Wasm Value, created when generating an instruction |
| | 17 | const 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` |
| | 28 | pub const ValueTable = std.AutoHashMap(*Inst, WValue); |
| | 29 | |
| | 30 | /// Using a given `Type`, returns the corresponding wasm value type |
| | 31 | fn 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 | } |
| 23 | | 40 | |
| 24 | pub 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(); | 43 | pub 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(); |
| 27 | | 101 | |
| 28 | // functype magic | 102 | // functype magic |
| 29 | try writer.writeByte(0x60); | 103 | try writer.writeByte(0x60); |
| 30 | | 104 | |
| 31 | // param types | 105 | // param types |
| 32 | try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen())); | 106 | try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen())); |
| 33 | if (ty.fnParamLen() != 0) { | 107 | if (ty.fnParamLen() != 0) { |
| 34 | const params = try buf.allocator.alloc(Type, ty.fnParamLen()); | 108 | const params = try self.gpa.alloc(Type, ty.fnParamLen()); |
| 35 | defer buf.allocator.free(params); | 109 | defer self.gpa.free(params); |
| 36 | ty.fnParamTypes(params); | 110 | ty.fnParamTypes(params); |
| 37 | for (params) |param_type| try writer.writeByte(genValtype(param_type)); | 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 | } |
| 38 | } | 129 | } |
| 39 | | 130 | |
| 40 | // return type | 131 | /// Generates the wasm bytecode for the function declaration belonging to `Context` |
| 41 | const return_type = ty.fnReturnType(); | 132 | pub fn gen(self: *Context) InnerError!void { |
| 42 | switch (return_type.tag()) { | 133 | assert(self.code.items.len == 0); |
| 43 | .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)), | 134 | try self.genFunctype(); |
| 44 | else => { | 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| { |
| 45 | try leb.writeULEB128(writer, @as(u32, 1)); | 164 | try leb.writeULEB128(writer, @as(u32, 1)); |
| 46 | try writer.writeByte(genValtype(return_type)); | 165 | try leb.writeULEB128(writer, local); // valtype |
| 47 | }, | 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)); |
| 48 | } | 176 | } |
| 49 | } | | |
| 50 | | 177 | |
| 51 | pub fn genCode(buf: *ArrayList(u8), decl: *Decl) !void { | 178 | fn genInst(self: *Context, inst: *Inst) InnerError!WValue { |
| 52 | assert(buf.items.len == 0); | 179 | return switch (inst.tag) { |
| 53 | const writer = buf.writer(); | 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 | } |
| 54 | | 193 | |
| 55 | // Reserve space to write the size after generating the code | 194 | fn genBody(self: *Context, body: ir.Body) InnerError!void { |
| 56 | try buf.resize(5); | 195 | for (body.instructions) |inst| { |
| | 196 | const result = try self.genInst(inst); |
| | 197 | try self.values.putNoClobber(inst, result); |
| | 198 | } |
| | 199 | } |
| 57 | | 200 | |
| 58 | // Write the size of the locals vec | 201 | fn genRet(self: *Context, inst: *Inst.UnOp) InnerError!WValue { |
| 59 | // TODO: implement locals | 202 | // TODO: Implement tail calls |
| 60 | try leb.writeULEB128(writer, @as(u32, 0)); | 203 | const operand = self.resolveInst(inst.operand); |
| | 204 | try self.emitWValue(operand); |
| | 205 | return WValue.none; |
| | 206 | } |
| 61 | | 207 | |
| 62 | // Write instructions | 208 | fn genCall(self: *Context, inst: *Inst.Call) InnerError!WValue { |
| 63 | // TODO: check for and handle death of instructions | 209 | const func_inst = inst.func.castTag(.constant).?; |
| 64 | const tv = decl.typed_value.most_recent.typed_value; | 210 | const func = func_inst.val.castTag(.function).?.data; |
| 65 | const mod_fn = tv.val.castTag(.function).?.data; | 211 | const target = func.owner_decl; |
| 66 | for (mod_fn.body.instructions) |inst| try genInst(buf, decl, inst); | 212 | const target_ty = target.typed_value.most_recent.typed_value.ty; |
| 67 | | 213 | |
| 68 | // Write 'end' opcode | 214 | for (inst.args) |arg| { |
| 69 | try writer.writeByte(0x0B); | 215 | const arg_val = self.resolveInst(arg); |
| | 216 | try self.emitWValue(arg_val); |
| | 217 | } |
| 70 | | 218 | |
| 71 | // Fill in the size of the generated code to the reserved space at the | 219 | try self.code.append(0x10); // call |
| 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 | } | | |
| 76 | | 220 | |
| 77 | fn genInst(buf: *ArrayList(u8), decl: *Decl, inst: *Inst) !void { | 221 | // The function index immediate argument will be filled in using this data |
| 78 | return switch (inst.tag) { | 222 | // in link.Wasm.flush(). |
| 79 | .call => genCall(buf, decl, inst.castTag(.call).?), | 223 | try self.decl.fn_link.wasm.?.idx_refs.append(self.gpa, .{ |
| 80 | .constant => genConstant(buf, decl, inst.castTag(.constant).?), | 224 | .offset = @intCast(u32, self.code.items.len), |
| 81 | .dbg_stmt => {}, | 225 | .decl = target, |
| 82 | .ret => genRet(buf, decl, inst.castTag(.ret).?), | 226 | }); |
| 83 | .retvoid => {}, | | |
| 84 | else => error.TODOImplementMoreWasmCodegen, | | |
| 85 | }; | | |
| 86 | } | | |
| 87 | | 227 | |
| 88 | fn genConstant(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.Constant) !void { | 228 | return WValue.none; |
| 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, | | |
| 119 | } | 229 | } |
| 120 | } | | |
| 121 | | 230 | |
| 122 | fn genRet(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.UnOp) !void { | 231 | fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue { |
| 123 | try genInst(buf, decl, inst.operand); | 232 | defer self.local_index += 1; |
| 124 | } | 233 | return WValue{ .local = self.local_index }; |
| | 234 | } |
| 125 | | 235 | |
| 126 | fn genCall(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.Call) !void { | 236 | fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue { |
| 127 | const func_inst = inst.func.castTag(.constant).?; | 237 | const writer = self.code.writer(); |
| 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; | | |
| 131 | | 238 | |
| 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); |
| 133 | | 242 | |
| 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 | } |
| 135 | | 247 | |
| 136 | // The function index immediate argument will be filled in using this data | 248 | fn genLoad(self: *Context, inst: *Inst.UnOp) InnerError!WValue { |
| 137 | // in link.Wasm.flush(). | 249 | const operand = self.resolveInst(inst.operand); |
| 138 | try decl.fn_link.wasm.?.idx_refs.append(buf.allocator, .{ | 250 | try self.emitWValue(operand); |
| 139 | .offset = @intCast(u32, buf.items.len), | 251 | return WValue.none; |
| 140 | .decl = target, | 252 | } |
| 141 | }); | 253 | |
| 142 | } | 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 | }; |