| ... | ... | @@ -4,6 +4,7 @@ const ArrayList = std.ArrayList; |
| 4 | 4 | const assert = std.debug.assert; |
| 5 | 5 | const leb = std.leb; |
| 6 | 6 | const mem = std.mem; |
| 7 | const wasm = std.wasm; |
| 7 | 8 | |
| 8 | 9 | const Module = @import("../Module.zig"); |
| 9 | 10 | const Decl = Module.Decl; |
| ... | ... | @@ -12,6 +13,7 @@ const Inst = ir.Inst; |
| 12 | 13 | const Type = @import("../type.zig").Type; |
| 13 | 14 | const Value = @import("../value.zig").Value; |
| 14 | 15 | const Compilation = @import("../Compilation.zig"); |
| 16 | const AnyMCValue = @import("../codegen.zig").AnyMCValue; |
| 15 | 17 | |
| 16 | 18 | /// Wasm Value, created when generating an instruction |
| 17 | 19 | const WValue = union(enum) { |
| ... | ... | @@ -20,23 +22,14 @@ const WValue = union(enum) { |
| 20 | 22 | local: u32, |
| 21 | 23 | /// Instruction holding a constant `Value` |
| 22 | 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 | 28 | block_idx: u32, |
| 25 | 29 | }; |
| 26 | 30 | |
| 27 | 31 | /// 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 { |
| 32 | | return switch (ty.tag()) { |
| 33 | | .f32 => 0x7D, |
| 34 | | .f64 => 0x7C, |
| 35 | | .u32, .i32 => 0x7F, |
| 36 | | .u64, .i64 => 0x7E, |
| 37 | | else => null, |
| 38 | | }; |
| 39 | | } |
| 32 | pub const ValueTable = std.AutoHashMapUnmanaged(*Inst, WValue); |
| 40 | 33 | |
| 41 | 34 | /// Code represents the `Code` section of wasm that |
| 42 | 35 | /// belongs to a function |
| ... | ... | @@ -58,13 +51,25 @@ pub const Context = struct { |
| 58 | 51 | local_index: u32 = 0, |
| 59 | 52 | /// If codegen fails, an error messages will be allocated and saved in `err_msg` |
| 60 | 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 | 61 | const InnerError = error{ |
| 63 | 62 | OutOfMemory, |
| 64 | 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 | 73 | fn fail(self: *Context, src: usize, comptime fmt: []const u8, args: anytype) InnerError { |
| 69 | 74 | self.err_msg = try Module.ErrorMsg.create(self.gpa, .{ |
| 70 | 75 | .file_scope = self.decl.getFileScope(), |
| ... | ... | @@ -85,13 +90,35 @@ pub const Context = struct { |
| 85 | 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 | 114 | /// Writes the bytecode depending on the given `WValue` in `val` |
| 89 | 115 | fn emitWValue(self: *Context, val: WValue) InnerError!void { |
| 90 | 116 | const writer = self.code.writer(); |
| 91 | 117 | switch (val) { |
| 92 | | .none, .block_idx => {}, |
| 118 | .block_idx => unreachable, |
| 119 | .none, .code_offset => {}, |
| 93 | 120 | .local => |idx| { |
| 94 | | try writer.writeByte(0x20); // local.get |
| 121 | try writer.writeByte(wasm.opcode(.local_get)); |
| 95 | 122 | try leb.writeULEB128(writer, idx); |
| 96 | 123 | }, |
| 97 | 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 | 129 | const ty = self.decl.typed_value.most_recent.typed_value.ty; |
| 103 | 130 | const writer = self.func_type_data.writer(); |
| 104 | 131 | |
| 105 | | // functype magic |
| 106 | | try writer.writeByte(0x60); |
| 132 | try writer.writeByte(wasm.function_type); |
| 107 | 133 | |
| 108 | 134 | // param types |
| 109 | 135 | try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen())); |
| ... | ... | @@ -112,8 +138,8 @@ pub const Context = struct { |
| 112 | 138 | defer self.gpa.free(params); |
| 113 | 139 | ty.fnParamTypes(params); |
| 114 | 140 | 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); |
| 117 | 143 | try writer.writeByte(val_type); |
| 118 | 144 | } |
| 119 | 145 | } |
| ... | ... | @@ -124,8 +150,8 @@ pub const Context = struct { |
| 124 | 150 | .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)), |
| 125 | 151 | else => |ret_type| { |
| 126 | 152 | 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); |
| 129 | 155 | try writer.writeByte(val_type); |
| 130 | 156 | }, |
| 131 | 157 | } |
| ... | ... | @@ -137,40 +163,33 @@ pub const Context = struct { |
| 137 | 163 | try self.genFunctype(); |
| 138 | 164 | const writer = self.code.writer(); |
| 139 | 165 | |
| 140 | | // Reserve space to write the size after generating the code |
| 141 | | try self.code.resize(5); |
| 166 | // Reserve space to write the size after generating the code as well as space for locals count |
| 167 | try self.code.resize(10); |
| 142 | 168 | |
| 143 | 169 | // Write instructions |
| 144 | 170 | // TODO: check for and handle death of instructions |
| 145 | 171 | const tv = self.decl.typed_value.most_recent.typed_value; |
| 146 | 172 | const mod_fn = tv.val.castTag(.function).?.data; |
| 173 | try self.genBody(mod_fn.body); |
| 147 | 174 | |
| 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 | | } |
| 175 | // finally, write our local types at the 'offset' position |
| 176 | { |
| 177 | leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len)); |
| 162 | 178 | |
| 163 | | try leb.writeULEB128(writer, @intCast(u32, locals.items.len)); |
| 179 | // offset into 'code' section where we will put our locals types |
| 180 | var local_offset: usize = 10; |
| 164 | 181 | |
| 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 |
| 182 | // emit the actual locals amount |
| 183 | for (self.locals.items) |local| { |
| 184 | var buf: [6]u8 = undefined; |
| 185 | leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1)); |
| 186 | buf[5] = local; |
| 187 | try self.code.insertSlice(local_offset, &buf); |
| 188 | local_offset += 6; |
| 189 | } |
| 169 | 190 | } |
| 170 | 191 | |
| 171 | | try self.genBody(mod_fn.body); |
| 172 | | |
| 173 | | try writer.writeByte(0x0B); // end |
| 192 | try writer.writeByte(wasm.opcode(.end)); |
| 174 | 193 | |
| 175 | 194 | // Fill in the size of the generated code to the reserved space at the |
| 176 | 195 | // beginning of the buffer. |
| ... | ... | @@ -183,10 +202,20 @@ pub const Context = struct { |
| 183 | 202 | .add => self.genAdd(inst.castTag(.add).?), |
| 184 | 203 | .alloc => self.genAlloc(inst.castTag(.alloc).?), |
| 185 | 204 | .arg => self.genArg(inst.castTag(.arg).?), |
| 205 | .block => self.genBlock(inst.castTag(.block).?), |
| 206 | .br => self.genBr(inst.castTag(.br).?), |
| 186 | 207 | .call => self.genCall(inst.castTag(.call).?), |
| 208 | .cmp_eq => self.genCmp(inst.castTag(.cmp_eq).?, .eq), |
| 209 | .cmp_gte => self.genCmp(inst.castTag(.cmp_gte).?, .gte), |
| 210 | .cmp_gt => self.genCmp(inst.castTag(.cmp_gt).?, .gt), |
| 211 | .cmp_lte => self.genCmp(inst.castTag(.cmp_lte).?, .lte), |
| 212 | .cmp_lt => self.genCmp(inst.castTag(.cmp_lt).?, .lt), |
| 213 | .cmp_neq => self.genCmp(inst.castTag(.cmp_neq).?, .neq), |
| 214 | .condbr => self.genCondBr(inst.castTag(.condbr).?), |
| 187 | 215 | .constant => unreachable, |
| 188 | 216 | .dbg_stmt => WValue.none, |
| 189 | 217 | .load => self.genLoad(inst.castTag(.load).?), |
| 218 | .loop => self.genLoop(inst.castTag(.loop).?), |
| 190 | 219 | .ret => self.genRet(inst.castTag(.ret).?), |
| 191 | 220 | .retvoid => WValue.none, |
| 192 | 221 | .store => self.genStore(inst.castTag(.store).?), |
| ... | ... | @@ -197,7 +226,7 @@ pub const Context = struct { |
| 197 | 226 | fn genBody(self: *Context, body: ir.Body) InnerError!void { |
| 198 | 227 | for (body.instructions) |inst| { |
| 199 | 228 | const result = try self.genInst(inst); |
| 200 | | try self.values.putNoClobber(inst, result); |
| 229 | try self.values.putNoClobber(self.gpa, inst, result); |
| 201 | 230 | } |
| 202 | 231 | } |
| 203 | 232 | |
| ... | ... | @@ -205,7 +234,7 @@ pub const Context = struct { |
| 205 | 234 | // TODO: Implement tail calls |
| 206 | 235 | const operand = self.resolveInst(inst.operand); |
| 207 | 236 | try self.emitWValue(operand); |
| 208 | | return WValue.none; |
| 237 | return .none; |
| 209 | 238 | } |
| 210 | 239 | |
| 211 | 240 | fn genCall(self: *Context, inst: *Inst.Call) InnerError!WValue { |
| ... | ... | @@ -219,7 +248,7 @@ pub const Context = struct { |
| 219 | 248 | try self.emitWValue(arg_val); |
| 220 | 249 | } |
| 221 | 250 | |
| 222 | | try self.code.append(0x10); // call |
| 251 | try self.code.append(wasm.opcode(.call)); |
| 223 | 252 | |
| 224 | 253 | // The function index immediate argument will be filled in using this data |
| 225 | 254 | // in link.Wasm.flush(). |
| ... | ... | @@ -228,10 +257,14 @@ pub const Context = struct { |
| 228 | 257 | .decl = target, |
| 229 | 258 | }); |
| 230 | 259 | |
| 231 | | return WValue.none; |
| 260 | return .none; |
| 232 | 261 | } |
| 233 | 262 | |
| 234 | 263 | fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue { |
| 264 | const elem_type = inst.base.ty.elemType(); |
| 265 | const valtype = try self.genValtype(inst.base.src, elem_type); |
| 266 | try self.locals.append(self.gpa, valtype); |
| 267 | |
| 235 | 268 | defer self.local_index += 1; |
| 236 | 269 | return WValue{ .local = self.local_index }; |
| 237 | 270 | } |
| ... | ... | @@ -243,15 +276,13 @@ pub const Context = struct { |
| 243 | 276 | const rhs = self.resolveInst(inst.rhs); |
| 244 | 277 | try self.emitWValue(rhs); |
| 245 | 278 | |
| 246 | | try writer.writeByte(0x21); // local.set |
| 279 | try writer.writeByte(wasm.opcode(.local_set)); |
| 247 | 280 | try leb.writeULEB128(writer, lhs.local); |
| 248 | | return WValue.none; |
| 281 | return .none; |
| 249 | 282 | } |
| 250 | 283 | |
| 251 | 284 | fn genLoad(self: *Context, inst: *Inst.UnOp) InnerError!WValue { |
| 252 | | const operand = self.resolveInst(inst.operand); |
| 253 | | try self.emitWValue(operand); |
| 254 | | return WValue.none; |
| 285 | return self.resolveInst(inst.operand); |
| 255 | 286 | } |
| 256 | 287 | |
| 257 | 288 | fn genArg(self: *Context, inst: *Inst.Arg) InnerError!WValue { |
| ... | ... | @@ -267,44 +298,44 @@ pub const Context = struct { |
| 267 | 298 | try self.emitWValue(lhs); |
| 268 | 299 | try self.emitWValue(rhs); |
| 269 | 300 | |
| 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 |
| 301 | const opcode: wasm.Opcode = switch (inst.base.ty.tag()) { |
| 302 | .u32, .i32 => .i32_add, |
| 303 | .u64, .i64 => .i64_add, |
| 304 | .f32 => .f32_add, |
| 305 | .f64 => .f64_add, |
| 275 | 306 | else => return self.fail(inst.base.src, "TODO - Implement wasm genAdd for type '{s}'", .{inst.base.ty.tag()}), |
| 276 | 307 | }; |
| 277 | 308 | |
| 278 | | try self.code.append(opcode); |
| 279 | | return WValue.none; |
| 309 | try self.code.append(wasm.opcode(opcode)); |
| 310 | return .none; |
| 280 | 311 | } |
| 281 | 312 | |
| 282 | 313 | fn emitConstant(self: *Context, inst: *Inst.Constant) InnerError!void { |
| 283 | 314 | const writer = self.code.writer(); |
| 284 | 315 | switch (inst.base.ty.tag()) { |
| 285 | 316 | .u32 => { |
| 286 | | try writer.writeByte(0x41); // i32.const |
| 317 | try writer.writeByte(wasm.opcode(.i32_const)); |
| 287 | 318 | try leb.writeILEB128(writer, inst.val.toUnsignedInt()); |
| 288 | 319 | }, |
| 289 | 320 | .i32 => { |
| 290 | | try writer.writeByte(0x41); // i32.const |
| 321 | try writer.writeByte(wasm.opcode(.i32_const)); |
| 291 | 322 | try leb.writeILEB128(writer, inst.val.toSignedInt()); |
| 292 | 323 | }, |
| 293 | 324 | .u64 => { |
| 294 | | try writer.writeByte(0x42); // i64.const |
| 325 | try writer.writeByte(wasm.opcode(.i64_const)); |
| 295 | 326 | try leb.writeILEB128(writer, inst.val.toUnsignedInt()); |
| 296 | 327 | }, |
| 297 | 328 | .i64 => { |
| 298 | | try writer.writeByte(0x42); // i64.const |
| 329 | try writer.writeByte(wasm.opcode(.i64_const)); |
| 299 | 330 | try leb.writeILEB128(writer, inst.val.toSignedInt()); |
| 300 | 331 | }, |
| 301 | 332 | .f32 => { |
| 302 | | try writer.writeByte(0x43); // f32.const |
| 333 | try writer.writeByte(wasm.opcode(.f32_const)); |
| 303 | 334 | // TODO: enforce LE byte order |
| 304 | 335 | try writer.writeAll(mem.asBytes(&inst.val.toFloat(f32))); |
| 305 | 336 | }, |
| 306 | 337 | .f64 => { |
| 307 | | try writer.writeByte(0x44); // f64.const |
| 338 | try writer.writeByte(wasm.opcode(.f64_const)); |
| 308 | 339 | // TODO: enforce LE byte order |
| 309 | 340 | try writer.writeAll(mem.asBytes(&inst.val.toFloat(f64))); |
| 310 | 341 | }, |
| ... | ... | @@ -312,4 +343,172 @@ pub const Context = struct { |
| 312 | 343 | else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for type {s}", .{ty}), |
| 313 | 344 | } |
| 314 | 345 | } |
| 346 | |
| 347 | fn genBlock(self: *Context, block: *Inst.Block) InnerError!WValue { |
| 348 | const block_ty = try self.genBlockType(block.base.src, block.base.ty); |
| 349 | |
| 350 | try self.startBlock(.block, block_ty, null); |
| 351 | block.codegen = .{ |
| 352 | // we don't use relocs, so using `relocs` is illegal behaviour. |
| 353 | .relocs = undefined, |
| 354 | // Here we set the current block idx, so breaks know the depth to jump |
| 355 | // to when breaking out. |
| 356 | .mcv = @bitCast(AnyMCValue, WValue{ .block_idx = self.block_depth }), |
| 357 | }; |
| 358 | try self.genBody(block.body); |
| 359 | try self.endBlock(); |
| 360 | |
| 361 | return .none; |
| 362 | } |
| 363 | |
| 364 | /// appends a new wasm block to the code section and increases the `block_depth` by 1 |
| 365 | fn startBlock(self: *Context, block_type: wasm.Opcode, valtype: u8, with_offset: ?usize) !void { |
| 366 | self.block_depth += 1; |
| 367 | if (with_offset) |offset| { |
| 368 | try self.code.insert(offset, wasm.opcode(block_type)); |
| 369 | try self.code.insert(offset + 1, valtype); |
| 370 | } else { |
| 371 | try self.code.append(wasm.opcode(block_type)); |
| 372 | try self.code.append(valtype); |
| 373 | } |
| 374 | } |
| 375 | |
| 376 | /// Ends the current wasm block and decreases the `block_depth` by 1 |
| 377 | fn endBlock(self: *Context) !void { |
| 378 | try self.code.append(wasm.opcode(.end)); |
| 379 | self.block_depth -= 1; |
| 380 | } |
| 381 | |
| 382 | fn genLoop(self: *Context, loop: *Inst.Loop) InnerError!WValue { |
| 383 | const loop_ty = try self.genBlockType(loop.base.src, loop.base.ty); |
| 384 | |
| 385 | try self.startBlock(.loop, loop_ty, null); |
| 386 | try self.genBody(loop.body); |
| 387 | |
| 388 | // breaking to the index of a loop block will continue the loop instead |
| 389 | try self.code.append(wasm.opcode(.br)); |
| 390 | try leb.writeULEB128(self.code.writer(), @as(u32, 0)); |
| 391 | |
| 392 | try self.endBlock(); |
| 393 | |
| 394 | return .none; |
| 395 | } |
| 396 | |
| 397 | fn genCondBr(self: *Context, condbr: *Inst.CondBr) InnerError!WValue { |
| 398 | const condition = self.resolveInst(condbr.condition); |
| 399 | const writer = self.code.writer(); |
| 400 | |
| 401 | // TODO: Handle death instructions for then and else body |
| 402 | |
| 403 | // insert blocks at the position of `offset` so |
| 404 | // the condition can jump to it |
| 405 | const offset = condition.code_offset; |
| 406 | const block_ty = try self.genBlockType(condbr.base.src, condbr.base.ty); |
| 407 | try self.startBlock(.block, block_ty, offset); |
| 408 | |
| 409 | // we inserted the block in front of the condition |
| 410 | // so now check if condition matches. If not, break outside this block |
| 411 | // and continue with the then codepath |
| 412 | try writer.writeByte(wasm.opcode(.br_if)); |
| 413 | try leb.writeULEB128(writer, @as(u32, 0)); |
| 414 | |
| 415 | try self.genBody(condbr.else_body); |
| 416 | try self.endBlock(); |
| 417 | |
| 418 | // Outer block that matches the condition |
| 419 | try self.genBody(condbr.then_body); |
| 420 | |
| 421 | return .none; |
| 422 | } |
| 423 | |
| 424 | fn genCmp(self: *Context, inst: *Inst.BinOp, op: std.math.CompareOperator) InnerError!WValue { |
| 425 | const ty = inst.lhs.ty.tag(); |
| 426 | |
| 427 | // save offset, so potential conditions can insert blocks in front of |
| 428 | // the comparison that we can later jump back to |
| 429 | const offset = self.code.items.len; |
| 430 | |
| 431 | const lhs = self.resolveInst(inst.lhs); |
| 432 | const rhs = self.resolveInst(inst.rhs); |
| 433 | |
| 434 | try self.emitWValue(lhs); |
| 435 | try self.emitWValue(rhs); |
| 436 | |
| 437 | const opcode_maybe: ?wasm.Opcode = switch (op) { |
| 438 | .lt => @as(?wasm.Opcode, switch (ty) { |
| 439 | .i32 => .i32_lt_s, |
| 440 | .u32 => .i32_lt_u, |
| 441 | .i64 => .i64_lt_s, |
| 442 | .u64 => .i64_lt_u, |
| 443 | .f32 => .f32_lt, |
| 444 | .f64 => .f64_lt, |
| 445 | else => null, |
| 446 | }), |
| 447 | .lte => @as(?wasm.Opcode, switch (ty) { |
| 448 | .i32 => .i32_le_s, |
| 449 | .u32 => .i32_le_u, |
| 450 | .i64 => .i64_le_s, |
| 451 | .u64 => .i64_le_u, |
| 452 | .f32 => .f32_le, |
| 453 | .f64 => .f64_le, |
| 454 | else => null, |
| 455 | }), |
| 456 | .eq => @as(?wasm.Opcode, switch (ty) { |
| 457 | .i32, .u32 => .i32_eq, |
| 458 | .i64, .u64 => .i64_eq, |
| 459 | .f32 => .f32_eq, |
| 460 | .f64 => .f64_eq, |
| 461 | else => null, |
| 462 | }), |
| 463 | .gte => @as(?wasm.Opcode, switch (ty) { |
| 464 | .i32 => .i32_ge_s, |
| 465 | .u32 => .i32_ge_u, |
| 466 | .i64 => .i64_ge_s, |
| 467 | .u64 => .i64_ge_u, |
| 468 | .f32 => .f32_ge, |
| 469 | .f64 => .f64_ge, |
| 470 | else => null, |
| 471 | }), |
| 472 | .gt => @as(?wasm.Opcode, switch (ty) { |
| 473 | .i32 => .i32_gt_s, |
| 474 | .u32 => .i32_gt_u, |
| 475 | .i64 => .i64_gt_s, |
| 476 | .u64 => .i64_gt_u, |
| 477 | .f32 => .f32_gt, |
| 478 | .f64 => .f64_gt, |
| 479 | else => null, |
| 480 | }), |
| 481 | .neq => @as(?wasm.Opcode, switch (ty) { |
| 482 | .i32, .u32 => .i32_ne, |
| 483 | .i64, .u64 => .i64_ne, |
| 484 | .f32 => .f32_ne, |
| 485 | .f64 => .f64_ne, |
| 486 | else => null, |
| 487 | }), |
| 488 | }; |
| 489 | |
| 490 | const opcode = opcode_maybe orelse |
| 491 | return self.fail(inst.base.src, "TODO - Wasm genCmp for type '{s}' and operator '{s}'", .{ ty, @tagName(op) }); |
| 492 | |
| 493 | try self.code.append(wasm.opcode(opcode)); |
| 494 | return WValue{ .code_offset = offset }; |
| 495 | } |
| 496 | |
| 497 | fn genBr(self: *Context, br: *Inst.Br) InnerError!WValue { |
| 498 | // of operand has codegen bits we should break with a value |
| 499 | if (br.operand.ty.hasCodeGenBits()) { |
| 500 | const operand = self.resolveInst(br.operand); |
| 501 | try self.emitWValue(operand); |
| 502 | } |
| 503 | |
| 504 | // every block contains a `WValue` with its block index. |
| 505 | // We then determine how far we have to jump to it by substracting it from current block depth |
| 506 | const wvalue = @bitCast(WValue, br.block.codegen.mcv); |
| 507 | const idx: u32 = self.block_depth - wvalue.block_idx; |
| 508 | const writer = self.code.writer(); |
| 509 | try writer.writeByte(wasm.opcode(.br)); |
| 510 | try leb.writeULEB128(writer, idx); |
| 511 | |
| 512 | return .none; |
| 513 | } |
| 315 | 514 | }; |