| ... | @@ -4,6 +4,7 @@ const ArrayList = std.ArrayList; | ... | @@ -4,6 +4,7 @@ const ArrayList = std.ArrayList; |
| 4 | const assert = std.debug.assert; | 4 | const assert = std.debug.assert; |
| 5 | const leb = std.leb; | 5 | const leb = std.leb; |
| 6 | const mem = std.mem; | 6 | const mem = std.mem; |
| | 7 | const wasm = std.wasm; |
| 7 | | 8 | |
| 8 | const Module = @import("../Module.zig"); | 9 | const Module = @import("../Module.zig"); |
| 9 | const Decl = Module.Decl; | 10 | const Decl = Module.Decl; |
| ... | @@ -12,6 +13,7 @@ const Inst = ir.Inst; | ... | @@ -12,6 +13,7 @@ const Inst = ir.Inst; |
| 12 | const Type = @import("../type.zig").Type; | 13 | const Type = @import("../type.zig").Type; |
| 13 | const Value = @import("../value.zig").Value; | 14 | const Value = @import("../value.zig").Value; |
| 14 | const Compilation = @import("../Compilation.zig"); | 15 | const Compilation = @import("../Compilation.zig"); |
| | 16 | const AnyMCValue = @import("../codegen.zig").AnyMCValue; |
| 15 | | 17 | |
| 16 | /// Wasm Value, created when generating an instruction | 18 | /// Wasm Value, created when generating an instruction |
| 17 | const WValue = union(enum) { | 19 | const WValue = union(enum) { |
| ... | @@ -20,23 +22,14 @@ const WValue = union(enum) { | ... | @@ -20,23 +22,14 @@ const WValue = union(enum) { |
| 20 | local: u32, | 22 | local: u32, |
| 21 | /// Instruction holding a constant `Value` | 23 | /// Instruction holding a constant `Value` |
| 22 | constant: *Inst, | 24 | 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 |
| 24 | block_idx: u32, | 28 | block_idx: u32, |
| 25 | }; | 29 | }; |
| 26 | | 30 | |
| 27 | /// Hashmap to store generated `WValue` for each `Inst` | 31 | /// Hashmap to store generated `WValue` for each `Inst` |
| 28 | pub const ValueTable = std.AutoHashMap(*Inst, WValue); | 32 | pub const ValueTable = std.AutoHashMapUnmanaged(*Inst, WValue); |
| 29 | | | |
| 30 | /// Using a given `Type`, returns the corresponding wasm value type | | |
| 31 | fn 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 | } | | |
| 40 | | 33 | |
| 41 | /// Code represents the `Code` section of wasm that | 34 | /// Code represents the `Code` section of wasm that |
| 42 | /// belongs to a function | 35 | /// belongs to a function |
| ... | @@ -58,13 +51,25 @@ pub const Context = struct { | ... | @@ -58,13 +51,25 @@ pub const Context = struct { |
| 58 | local_index: u32 = 0, | 51 | local_index: u32 = 0, |
| 59 | /// If codegen fails, an error messages will be allocated and saved in `err_msg` | 52 | /// If codegen fails, an error messages will be allocated and saved in `err_msg` |
| 60 | err_msg: *Module.ErrorMsg, | 53 | 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), |
| 61 | | 60 | |
| 62 | const InnerError = error{ | 61 | const InnerError = error{ |
| 63 | OutOfMemory, | 62 | OutOfMemory, |
| 64 | CodegenFail, | 63 | CodegenFail, |
| 65 | }; | 64 | }; |
| 66 | | 65 | |
| 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 |
| 68 | fn fail(self: *Context, src: usize, comptime fmt: []const u8, args: anytype) InnerError { | 73 | fn fail(self: *Context, src: usize, comptime fmt: []const u8, args: anytype) InnerError { |
| 69 | self.err_msg = try Module.ErrorMsg.create(self.gpa, .{ | 74 | self.err_msg = try Module.ErrorMsg.create(self.gpa, .{ |
| 70 | .file_scope = self.decl.getFileScope(), | 75 | .file_scope = self.decl.getFileScope(), |
| ... | @@ -85,13 +90,35 @@ pub const Context = struct { | ... | @@ -85,13 +90,35 @@ pub const Context = struct { |
| 85 | return self.values.get(inst).?; // Instruction does not dominate all uses! | 90 | return self.values.get(inst).?; // Instruction does not dominate all uses! |
| 86 | } | 91 | } |
| 87 | | 92 | |
| | 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 | |
| 88 | /// Writes the bytecode depending on the given `WValue` in `val` | 114 | /// Writes the bytecode depending on the given `WValue` in `val` |
| 89 | fn emitWValue(self: *Context, val: WValue) InnerError!void { | 115 | fn emitWValue(self: *Context, val: WValue) InnerError!void { |
| 90 | const writer = self.code.writer(); | 116 | const writer = self.code.writer(); |
| 91 | switch (val) { | 117 | switch (val) { |
| 92 | .none, .block_idx => {}, | 118 | .block_idx => unreachable, |
| | 119 | .none, .code_offset => {}, |
| 93 | .local => |idx| { | 120 | .local => |idx| { |
| 94 | try writer.writeByte(0x20); // local.get | 121 | try writer.writeByte(wasm.opcode(.local_get)); |
| 95 | try leb.writeULEB128(writer, idx); | 122 | try leb.writeULEB128(writer, idx); |
| 96 | }, | 123 | }, |
| 97 | .constant => |inst| try self.emitConstant(inst.castTag(.constant).?), // creates a new constant onto the stack | 124 | .constant => |inst| try self.emitConstant(inst.castTag(.constant).?), // creates a new constant onto the stack |
| ... | @@ -102,8 +129,7 @@ pub const Context = struct { | ... | @@ -102,8 +129,7 @@ pub const Context = struct { |
| 102 | const ty = self.decl.typed_value.most_recent.typed_value.ty; | 129 | const ty = self.decl.typed_value.most_recent.typed_value.ty; |
| 103 | const writer = self.func_type_data.writer(); | 130 | const writer = self.func_type_data.writer(); |
| 104 | | 131 | |
| 105 | // functype magic | 132 | try writer.writeByte(wasm.function_type); |
| 106 | try writer.writeByte(0x60); | | |
| 107 | | 133 | |
| 108 | // param types | 134 | // param types |
| 109 | try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen())); | 135 | try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen())); |
| ... | @@ -112,8 +138,8 @@ pub const Context = struct { | ... | @@ -112,8 +138,8 @@ pub const Context = struct { |
| 112 | defer self.gpa.free(params); | 138 | defer self.gpa.free(params); |
| 113 | ty.fnParamTypes(params); | 139 | ty.fnParamTypes(params); |
| 114 | for (params) |param_type| { | 140 | for (params) |param_type| { |
| 115 | const val_type = genValtype(param_type) orelse | 141 | // Can we maybe get the source index of each param? |
| 116 | return self.fail(self.decl.src(), "TODO: Wasm codegen - arg type value for type '{s}'", .{param_type.tag()}); | 142 | const val_type = try self.genValtype(self.decl.src(), param_type); |
| 117 | try writer.writeByte(val_type); | 143 | try writer.writeByte(val_type); |
| 118 | } | 144 | } |
| 119 | } | 145 | } |
| ... | @@ -124,8 +150,8 @@ pub const Context = struct { | ... | @@ -124,8 +150,8 @@ pub const Context = struct { |
| 124 | .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)), | 150 | .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)), |
| 125 | else => |ret_type| { | 151 | else => |ret_type| { |
| 126 | try leb.writeULEB128(writer, @as(u32, 1)); | 152 | try leb.writeULEB128(writer, @as(u32, 1)); |
| 127 | const val_type = genValtype(return_type) orelse | 153 | // Can we maybe get the source index of the return type? |
| 128 | return self.fail(self.decl.src(), "TODO: Wasm codegen - return type value for type '{s}'", .{ret_type}); | 154 | const val_type = try self.genValtype(self.decl.src(), return_type); |
| 129 | try writer.writeByte(val_type); | 155 | try writer.writeByte(val_type); |
| 130 | }, | 156 | }, |
| 131 | } | 157 | } |
| ... | @@ -140,37 +166,33 @@ pub const Context = struct { | ... | @@ -140,37 +166,33 @@ pub const Context = struct { |
| 140 | // Reserve space to write the size after generating the code | 166 | // Reserve space to write the size after generating the code |
| 141 | try self.code.resize(5); | 167 | try self.code.resize(5); |
| 142 | | 168 | |
| | 169 | // offset into 'code' section where we will put our locals count |
| | 170 | var local_offset = self.code.items.len; |
| | 171 | |
| 143 | // Write instructions | 172 | // Write instructions |
| 144 | // TODO: check for and handle death of instructions | 173 | // TODO: check for and handle death of instructions |
| 145 | const tv = self.decl.typed_value.most_recent.typed_value; | 174 | const tv = self.decl.typed_value.most_recent.typed_value; |
| 146 | const mod_fn = tv.val.castTag(.function).?.data; | 175 | const mod_fn = tv.val.castTag(.function).?.data; |
| | 176 | try self.genBody(mod_fn.body); |
| 147 | | 177 | |
| 148 | var locals = std.ArrayList(u8).init(self.gpa); | 178 | // finally, write our local types at the 'offset' position |
| 149 | defer locals.deinit(); | 179 | { |
| 150 | | 180 | var totals_buffer: [5]u8 = undefined; |
| 151 | for (mod_fn.body.instructions) |inst| { | 181 | leb.writeUnsignedFixed(5, totals_buffer[0..5], @intCast(u32, self.locals.items.len)); |
| 152 | if (inst.tag != .alloc) continue; | 182 | try self.code.insertSlice(local_offset, &totals_buffer); |
| 153 | | 183 | local_offset += 5; |
| 154 | const alloc: *Inst.NoOp = inst.castTag(.alloc).?; | 184 | |
| 155 | const elem_type = alloc.base.ty.elemType(); | 185 | // emit the actual locals amount |
| 156 | | 186 | for (self.locals.items) |local| { |
| 157 | const wasm_type = genValtype(elem_type) orelse | 187 | var buf: [6]u8 = undefined; |
| 158 | return self.fail(inst.src, "TODO: Wasm codegen - valtype for type '{s}'", .{elem_type.tag()}); | 188 | leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1)); |
| 159 | | 189 | buf[5] = local; |
| 160 | try locals.append(wasm_type); | 190 | try self.code.insertSlice(local_offset, &buf); |
| 161 | } | 191 | local_offset += 6; |
| 162 | | 192 | } |
| 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 | | |
| 169 | } | 193 | } |
| 170 | | 194 | |
| 171 | try self.genBody(mod_fn.body); | 195 | try writer.writeByte(wasm.opcode(.end)); |
| 172 | | | |
| 173 | try writer.writeByte(0x0B); // end | | |
| 174 | | 196 | |
| 175 | // Fill in the size of the generated code to the reserved space at the | 197 | // Fill in the size of the generated code to the reserved space at the |
| 176 | // beginning of the buffer. | 198 | // beginning of the buffer. |
| ... | @@ -183,10 +205,20 @@ pub const Context = struct { | ... | @@ -183,10 +205,20 @@ pub const Context = struct { |
| 183 | .add => self.genAdd(inst.castTag(.add).?), | 205 | .add => self.genAdd(inst.castTag(.add).?), |
| 184 | .alloc => self.genAlloc(inst.castTag(.alloc).?), | 206 | .alloc => self.genAlloc(inst.castTag(.alloc).?), |
| 185 | .arg => self.genArg(inst.castTag(.arg).?), | 207 | .arg => self.genArg(inst.castTag(.arg).?), |
| | 208 | .block => self.genBlock(inst.castTag(.block).?), |
| | 209 | .br => self.genBr(inst.castTag(.br).?), |
| 186 | .call => self.genCall(inst.castTag(.call).?), | 210 | .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).?), |
| 187 | .constant => unreachable, | 218 | .constant => unreachable, |
| 188 | .dbg_stmt => WValue.none, | 219 | .dbg_stmt => WValue.none, |
| 189 | .load => self.genLoad(inst.castTag(.load).?), | 220 | .load => self.genLoad(inst.castTag(.load).?), |
| | 221 | .loop => self.genLoop(inst.castTag(.loop).?), |
| 190 | .ret => self.genRet(inst.castTag(.ret).?), | 222 | .ret => self.genRet(inst.castTag(.ret).?), |
| 191 | .retvoid => WValue.none, | 223 | .retvoid => WValue.none, |
| 192 | .store => self.genStore(inst.castTag(.store).?), | 224 | .store => self.genStore(inst.castTag(.store).?), |
| ... | @@ -197,7 +229,7 @@ pub const Context = struct { | ... | @@ -197,7 +229,7 @@ pub const Context = struct { |
| 197 | fn genBody(self: *Context, body: ir.Body) InnerError!void { | 229 | fn genBody(self: *Context, body: ir.Body) InnerError!void { |
| 198 | for (body.instructions) |inst| { | 230 | for (body.instructions) |inst| { |
| 199 | const result = try self.genInst(inst); | 231 | const result = try self.genInst(inst); |
| 200 | try self.values.putNoClobber(inst, result); | 232 | try self.values.putNoClobber(self.gpa, inst, result); |
| 201 | } | 233 | } |
| 202 | } | 234 | } |
| 203 | | 235 | |
| ... | @@ -205,7 +237,7 @@ pub const Context = struct { | ... | @@ -205,7 +237,7 @@ pub const Context = struct { |
| 205 | // TODO: Implement tail calls | 237 | // TODO: Implement tail calls |
| 206 | const operand = self.resolveInst(inst.operand); | 238 | const operand = self.resolveInst(inst.operand); |
| 207 | try self.emitWValue(operand); | 239 | try self.emitWValue(operand); |
| 208 | return WValue.none; | 240 | return .none; |
| 209 | } | 241 | } |
| 210 | | 242 | |
| 211 | fn genCall(self: *Context, inst: *Inst.Call) InnerError!WValue { | 243 | fn genCall(self: *Context, inst: *Inst.Call) InnerError!WValue { |
| ... | @@ -219,7 +251,7 @@ pub const Context = struct { | ... | @@ -219,7 +251,7 @@ pub const Context = struct { |
| 219 | try self.emitWValue(arg_val); | 251 | try self.emitWValue(arg_val); |
| 220 | } | 252 | } |
| 221 | | 253 | |
| 222 | try self.code.append(0x10); // call | 254 | try self.code.append(wasm.opcode(.call)); |
| 223 | | 255 | |
| 224 | // The function index immediate argument will be filled in using this data | 256 | // The function index immediate argument will be filled in using this data |
| 225 | // in link.Wasm.flush(). | 257 | // in link.Wasm.flush(). |
| ... | @@ -228,10 +260,14 @@ pub const Context = struct { | ... | @@ -228,10 +260,14 @@ pub const Context = struct { |
| 228 | .decl = target, | 260 | .decl = target, |
| 229 | }); | 261 | }); |
| 230 | | 262 | |
| 231 | return WValue.none; | 263 | return .none; |
| 232 | } | 264 | } |
| 233 | | 265 | |
| 234 | fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue { | 266 | 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 | |
| 235 | defer self.local_index += 1; | 271 | defer self.local_index += 1; |
| 236 | return WValue{ .local = self.local_index }; | 272 | return WValue{ .local = self.local_index }; |
| 237 | } | 273 | } |
| ... | @@ -243,15 +279,14 @@ pub const Context = struct { | ... | @@ -243,15 +279,14 @@ pub const Context = struct { |
| 243 | const rhs = self.resolveInst(inst.rhs); | 279 | const rhs = self.resolveInst(inst.rhs); |
| 244 | try self.emitWValue(rhs); | 280 | try self.emitWValue(rhs); |
| 245 | | 281 | |
| 246 | try writer.writeByte(0x21); // local.set | 282 | try writer.writeByte(wasm.opcode(.local_set)); |
| 247 | try leb.writeULEB128(writer, lhs.local); | 283 | try leb.writeULEB128(writer, lhs.local); |
| 248 | return WValue.none; | 284 | return .none; |
| 249 | } | 285 | } |
| 250 | | 286 | |
| 251 | fn genLoad(self: *Context, inst: *Inst.UnOp) InnerError!WValue { | 287 | fn genLoad(self: *Context, inst: *Inst.UnOp) InnerError!WValue { |
| 252 | const operand = self.resolveInst(inst.operand); | 288 | const operand = self.resolveInst(inst.operand); |
| 253 | try self.emitWValue(operand); | 289 | return operand; |
| 254 | return WValue.none; | | |
| 255 | } | 290 | } |
| 256 | | 291 | |
| 257 | fn genArg(self: *Context, inst: *Inst.Arg) InnerError!WValue { | 292 | fn genArg(self: *Context, inst: *Inst.Arg) InnerError!WValue { |
| ... | @@ -267,44 +302,44 @@ pub const Context = struct { | ... | @@ -267,44 +302,44 @@ pub const Context = struct { |
| 267 | try self.emitWValue(lhs); | 302 | try self.emitWValue(lhs); |
| 268 | try self.emitWValue(rhs); | 303 | try self.emitWValue(rhs); |
| 269 | | 304 | |
| 270 | const opcode: u8 = switch (inst.base.ty.tag()) { | 305 | const opcode: wasm.Opcode = switch (inst.base.ty.tag()) { |
| 271 | .u32, .i32 => 0x6A, //i32.add | 306 | .u32, .i32 => .i32_add, |
| 272 | .u64, .i64 => 0x7C, //i64.add | 307 | .u64, .i64 => .i64_add, |
| 273 | .f32 => 0x92, //f32.add | 308 | .f32 => .f32_add, |
| 274 | .f64 => 0xA0, //f64.add | 309 | .f64 => .f64_add, |
| 275 | else => return self.fail(inst.base.src, "TODO - Implement wasm genAdd for type '{s}'", .{inst.base.ty.tag()}), | 310 | else => return self.fail(inst.base.src, "TODO - Implement wasm genAdd for type '{s}'", .{inst.base.ty.tag()}), |
| 276 | }; | 311 | }; |
| 277 | | 312 | |
| 278 | try self.code.append(opcode); | 313 | try self.code.append(wasm.opcode(opcode)); |
| 279 | return WValue.none; | 314 | return .none; |
| 280 | } | 315 | } |
| 281 | | 316 | |
| 282 | fn emitConstant(self: *Context, inst: *Inst.Constant) InnerError!void { | 317 | fn emitConstant(self: *Context, inst: *Inst.Constant) InnerError!void { |
| 283 | const writer = self.code.writer(); | 318 | const writer = self.code.writer(); |
| 284 | switch (inst.base.ty.tag()) { | 319 | switch (inst.base.ty.tag()) { |
| 285 | .u32 => { | 320 | .u32 => { |
| 286 | try writer.writeByte(0x41); // i32.const | 321 | try writer.writeByte(wasm.opcode(.i32_const)); |
| 287 | try leb.writeILEB128(writer, inst.val.toUnsignedInt()); | 322 | try leb.writeILEB128(writer, inst.val.toUnsignedInt()); |
| 288 | }, | 323 | }, |
| 289 | .i32 => { | 324 | .i32 => { |
| 290 | try writer.writeByte(0x41); // i32.const | 325 | try writer.writeByte(wasm.opcode(.i32_const)); |
| 291 | try leb.writeILEB128(writer, inst.val.toSignedInt()); | 326 | try leb.writeILEB128(writer, inst.val.toSignedInt()); |
| 292 | }, | 327 | }, |
| 293 | .u64 => { | 328 | .u64 => { |
| 294 | try writer.writeByte(0x42); // i64.const | 329 | try writer.writeByte(wasm.opcode(.i64_const)); |
| 295 | try leb.writeILEB128(writer, inst.val.toUnsignedInt()); | 330 | try leb.writeILEB128(writer, inst.val.toUnsignedInt()); |
| 296 | }, | 331 | }, |
| 297 | .i64 => { | 332 | .i64 => { |
| 298 | try writer.writeByte(0x42); // i64.const | 333 | try writer.writeByte(wasm.opcode(.i64_const)); |
| 299 | try leb.writeILEB128(writer, inst.val.toSignedInt()); | 334 | try leb.writeILEB128(writer, inst.val.toSignedInt()); |
| 300 | }, | 335 | }, |
| 301 | .f32 => { | 336 | .f32 => { |
| 302 | try writer.writeByte(0x43); // f32.const | 337 | try writer.writeByte(wasm.opcode(.f32_const)); |
| 303 | // TODO: enforce LE byte order | 338 | // TODO: enforce LE byte order |
| 304 | try writer.writeAll(mem.asBytes(&inst.val.toFloat(f32))); | 339 | try writer.writeAll(mem.asBytes(&inst.val.toFloat(f32))); |
| 305 | }, | 340 | }, |
| 306 | .f64 => { | 341 | .f64 => { |
| 307 | try writer.writeByte(0x44); // f64.const | 342 | try writer.writeByte(wasm.opcode(.f64_const)); |
| 308 | // TODO: enforce LE byte order | 343 | // TODO: enforce LE byte order |
| 309 | try writer.writeAll(mem.asBytes(&inst.val.toFloat(f64))); | 344 | try writer.writeAll(mem.asBytes(&inst.val.toFloat(f64))); |
| 310 | }, | 345 | }, |
| ... | @@ -312,4 +347,162 @@ pub const Context = struct { | ... | @@ -312,4 +347,162 @@ pub const Context = struct { |
| 312 | else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for type {s}", .{ty}), | 347 | else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for type {s}", .{ty}), |
| 313 | } | 348 | } |
| 314 | } | 349 | } |
| | 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 | } |
| 315 | }; | 508 | }; |