| ... | ... | @@ -7,136 +7,292 @@ const mem = std.mem; |
| 7 | 7 | |
| 8 | 8 | const Module = @import("../Module.zig"); |
| 9 | 9 | const Decl = Module.Decl; |
| 10 | | const Inst = @import("../ir.zig").Inst; |
| 10 | const ir = @import("../ir.zig"); |
| 11 | const Inst = ir.Inst; |
| 11 | 12 | const Type = @import("../type.zig").Type; |
| 12 | 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 | /// 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 | |
| 28 | pub const ValueTable = std.AutoArrayHashMap(*Inst, WValue); |
| 29 | |
| 30 | /// Using a given Zig type, returns the corresponding wasm value type |
| 31 | fn genValtype(ty: Type) ?u8 { |
| 15 | 32 | return switch (ty.tag()) { |
| 16 | | .u32, .i32 => 0x7F, |
| 17 | | .u64, .i64 => 0x7E, |
| 18 | 33 | .f32 => 0x7D, |
| 19 | 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 { |
| 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 |
| 43 | pub 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 | } |
| 27 | 101 | |
| 28 | | // functype magic |
| 29 | | try writer.writeByte(0x60); |
| 102 | fn genFunctype(self: *Code) !void { |
| 103 | const ty = self.decl.typed_value.most_recent.typed_value.ty; |
| 104 | const writer = self.func_type_data.writer(); |
| 30 | 105 | |
| 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)); |
| 106 | // functype magic |
| 107 | try writer.writeByte(0x60); |
| 108 | |
| 109 | // param types |
| 110 | try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen())); |
| 111 | if (ty.fnParamLen() != 0) { |
| 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 | } |
| 39 | 134 | |
| 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 => { |
| 135 | /// Generates the wasm bytecode for the given `code` |
| 136 | pub fn gen(self: *Code) !void { |
| 137 | assert(self.bytes.items.len == 0); |
| 138 | try self.genFunctype(); |
| 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 | 168 | try leb.writeULEB128(writer, @as(u32, 1)); |
| 46 | | try writer.writeByte(genValtype(return_type)); |
| 47 | | }, |
| 169 | try leb.writeULEB128(writer, local); // valtype |
| 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 | | } |
| 50 | 188 | |
| 51 | | pub fn genCode(buf: *ArrayList(u8), decl: *Decl) !void { |
| 52 | | assert(buf.items.len == 0); |
| 53 | | const writer = buf.writer(); |
| 189 | fn genInst(self: *Code, inst: *Inst) !WValue { |
| 190 | return switch (inst.tag) { |
| 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 | } |
| 54 | 203 | |
| 55 | | // Reserve space to write the size after generating the code |
| 56 | | try buf.resize(5); |
| 204 | fn genRet(self: *Code, inst: *Inst.UnOp) !WValue { |
| 205 | const operand = try self.resolveInst(inst.operand); |
| 206 | try self.emitWValue(operand); |
| 207 | return WValue.none; |
| 208 | } |
| 57 | 209 | |
| 58 | | // Write the size of the locals vec |
| 59 | | // TODO: implement locals |
| 60 | | try leb.writeULEB128(writer, @as(u32, 0)); |
| 210 | fn genCall(self: *Code, inst: *Inst.Call) !WValue { |
| 211 | const func_inst = inst.func.castTag(.constant).?; |
| 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; |
| 61 | 215 | |
| 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); |
| 216 | for (inst.args) |arg| { |
| 217 | const arg_val = try self.resolveInst(arg); |
| 218 | try self.emitWValue(arg_val); |
| 219 | } |
| 67 | 220 | |
| 68 | | // Write 'end' opcode |
| 69 | | try writer.writeByte(0x0B); |
| 221 | try self.bytes.append(0x10); // call |
| 70 | 222 | |
| 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 | | } |
| 223 | // The function index immediate argument will be filled in using this data |
| 224 | // in link.Wasm.flush(). |
| 225 | try self.decl.fn_link.wasm.?.idx_refs.append(self.gpa, .{ |
| 226 | .offset = @intCast(u32, self.bytes.items.len), |
| 227 | .decl = target, |
| 228 | }); |
| 76 | 229 | |
| 77 | | fn 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 | | } |
| 230 | return WValue.none; |
| 231 | } |
| 87 | 232 | |
| 88 | | fn 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, |
| 233 | fn genAlloc(self: *Code, inst: *Inst.NoOp) !WValue { |
| 234 | defer self.local_index += 1; |
| 235 | return WValue{ .local = self.local_index }; |
| 119 | 236 | } |
| 120 | | } |
| 121 | 237 | |
| 122 | | fn genRet(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.UnOp) !void { |
| 123 | | try genInst(buf, decl, inst.operand); |
| 124 | | } |
| 238 | fn genStore(self: *Code, inst: *Inst.BinOp) !WValue { |
| 239 | const writer = self.bytes.writer(); |
| 125 | 240 | |
| 126 | | fn 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; |
| 241 | const lhs = try self.resolveInst(inst.lhs); |
| 131 | 242 | |
| 132 | | if (inst.args.len != 0) return error.TODOImplementMoreWasmCodegen; |
| 243 | const rhs = try self.resolveInst(inst.rhs); |
| 244 | try self.emitWValue(rhs); |
| 133 | 245 | |
| 134 | | try buf.append(0x10); // call |
| 246 | try writer.writeByte(0x21); // local.set |
| 247 | try leb.writeULEB128(writer, lhs.local); |
| 135 | 248 | |
| 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 | | } |
| 249 | return WValue.none; |
| 250 | } |
| 251 | |
| 252 | fn genLoad(self: *Code, inst: *Inst.UnOp) !WValue { |
| 253 | const operand = self.resolveInst(inst.operand); |
| 254 | |
| 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 | }; |