authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-01-03 19:46:54+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-01-04 17:46:58+01:00
logc519d9c80e5b207a84b9bd414727bd41cb4fadfa
treefb72dfc603307552b309585246b1eb14e566bac6
parent5c228765f1094d30e64d13c0077c67b2867ecd6a
signaturelock-open Commit is signed but in an unrecognized format.

wasm: Implement (and fix) most optional instructions

Previously we were performing the wrapping and unwrapping operations incorrectly. We now correctly create the type and set its values. Besides this, we also set the null-byte to the incorrect value, which meant we were doing the opposite action of a is_null check. This is now fixed as well. While implementing this, I found a small bug in the wrapErrUnionPayload where we would load a pointer value and save that, rather than store the pointer with the error. This is now fixed as well, by copying the entire operand into the payload of the error union.

1 files changed, 164 insertions(+), 62 deletions(-)

src/arch/wasm/CodeGen.zig+164-62
......@@ -1256,6 +1256,28 @@ fn isByRef(ty: Type) bool {
12561256 }
12571257}
12581258
1259/// Creates a new local for a pointer that points to memory with given offset.
1260/// This can be used to get a pointer to a struct field, error payload, etc.
1261fn buildPointerOffset(self: *Self, ptr_value: WValue, offset: u64) InnerError!WValue {
1262 // do not perform arithmetic when offset is 0.
1263 if (offset == 0) return ptr_value;
1264 const result_ptr = try self.allocLocal(Type.usize);
1265 try self.emitWValue(ptr_value);
1266 switch (self.target.cpu.arch.ptrBitWidth()) {
1267 32 => {
1268 try self.addImm32(@bitCast(i32, @intCast(u32, offset)));
1269 try self.addTag(.i32_add);
1270 },
1271 64 => {
1272 try self.addImm64(offset);
1273 try self.addTag(.i64_add);
1274 },
1275 else => unreachable,
1276 }
1277 try self.addLabel(.local_set, result_ptr.local);
1278 return result_ptr;
1279}
1280
12591281fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
12601282 const air_tags = self.air.instructions.items(.tag);
12611283 return switch (air_tags[inst]) {
......@@ -1296,16 +1318,16 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
12961318 .is_err => self.airIsErr(inst, .i32_ne),
12971319 .is_non_err => self.airIsErr(inst, .i32_eq),
12981320
1299 .is_null => self.airIsNull(inst, .i32_ne),
1300 .is_non_null => self.airIsNull(inst, .i32_eq),
1301 .is_null_ptr => self.airIsNull(inst, .i32_ne),
1302 .is_non_null_ptr => self.airIsNull(inst, .i32_eq),
1321 .is_null => self.airIsNull(inst, .i32_eq, .value),
1322 .is_non_null => self.airIsNull(inst, .i32_ne, .value),
1323 .is_null_ptr => self.airIsNull(inst, .i32_eq, .ptr),
1324 .is_non_null_ptr => self.airIsNull(inst, .i32_ne, .ptr),
13031325
13041326 .load => self.airLoad(inst),
13051327 .loop => self.airLoop(inst),
13061328 .not => self.airNot(inst),
13071329 .optional_payload => self.airOptionalPayload(inst),
1308 .optional_payload_ptr => self.airOptionalPayload(inst),
1330 .optional_payload_ptr => self.airOptionalPayloadPtr(inst),
13091331 .optional_payload_ptr_set => self.airOptionalPayloadPtrSet(inst),
13101332 .ptr_add => self.airPtrBinOp(inst, .add),
13111333 .ptr_sub => self.airPtrBinOp(inst, .sub),
......@@ -1482,14 +1504,20 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
14821504}
14831505
14841506fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1485 const child_type = self.air.typeOfIndex(inst).childType();
1507 const pointee_type = self.air.typeOfIndex(inst).childType();
14861508
14871509 // Initialize the stack
14881510 if (self.initial_stack_value == .none) {
14891511 try self.initializeStack();
14901512 }
1491 if (child_type.abiSize(self.target) == 0) return WValue{ .none = {} };
1492 return self.allocStack(child_type);
1513
1514 if (!pointee_type.hasCodeGenBits()) {
1515 // when the pointee is zero-sized, we still want to create a pointer.
1516 // but instead use a default pointer type as storage.
1517 const zero_ptr = try self.allocStack(Type.usize);
1518 return zero_ptr;
1519 }
1520 return self.allocStack(pointee_type);
14931521}
14941522
14951523fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
......@@ -1516,6 +1544,8 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
15161544 const tag_ty = if (ty.zigTypeTag() == .ErrorUnion) ty.errorUnionSet() else Type.initTag(.u8);
15171545 const payload_offset = if (ty.zigTypeTag() == .ErrorUnion)
15181546 @intCast(u32, tag_ty.abiSize(self.target))
1547 else if (ty.isPtrLikeOptional())
1548 @as(u32, 0)
15191549 else
15201550 @intCast(u32, ty.abiSize(self.target) - payload_ty.abiSize(self.target));
15211551
......@@ -1528,6 +1558,10 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
15281558 try self.addLabel(.local_set, mem_local.local);
15291559 try self.store(lhs, mem_local, ty, 0);
15301560 return;
1561 } else if (ty.isPtrLikeOptional()) {
1562 // set the address of rhs to lhs
1563 try self.store(lhs, rhs, Type.usize, 0);
1564 return;
15311565 }
15321566 // constant will contain both tag and payload,
15331567 // so save those in 2 temporary locals before storing them
......@@ -1546,6 +1580,12 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
15461580 return;
15471581 },
15481582 .local => {
1583 // When the optional is pointer-like, we simply store the pointer
1584 // instead.
1585 if (ty.isPtrLikeOptional()) {
1586 try self.store(lhs, rhs, Type.usize, 0);
1587 return;
1588 }
15491589 // Load values from `rhs` stack position and store in `lhs` instead
15501590 const tag_local = try self.load(rhs, tag_ty, 0);
15511591 if (payload_ty.hasCodeGenBits()) {
......@@ -1630,7 +1670,6 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
16301670 .ErrorSet,
16311671 .Enum,
16321672 .Bool,
1633 .ErrorUnion,
16341673 => @intCast(u8, ty.abiSize(self.target)),
16351674 else => @as(u8, 4),
16361675 };
......@@ -1684,6 +1723,10 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {
16841723 .Bool,
16851724 .ErrorUnion,
16861725 => @intCast(u8, ty.abiSize(self.target)),
1726 .Optional => blk: {
1727 if (ty.isPtrLikeOptional()) break :blk @intCast(u8, self.ptrSize());
1728 break :blk @intCast(u8, ty.abiSize(self.target));
1729 },
16871730 else => @as(u8, 4),
16881731 };
16891732
......@@ -1828,7 +1871,7 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {
18281871 }
18291872 } else if (val.castTag(.int_u64)) |int_ptr| {
18301873 try self.addImm32(@bitCast(i32, @intCast(u32, int_ptr.data)));
1831 } else if (val.tag() == .zero) {
1874 } else if (val.tag() == .zero or val.tag() == .null_value) {
18321875 try self.addImm32(0);
18331876 } else if (val.tag() == .one) {
18341877 try self.addImm32(1);
......@@ -1886,18 +1929,19 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {
18861929 var buf: Type.Payload.ElemType = undefined;
18871930 const payload_type = ty.optionalChild(&buf);
18881931 if (ty.isPtrLikeOptional()) {
1889 return self.fail("Wasm TODO: emitConstant for optional pointer", .{});
1932 try self.emitConstant(val, payload_type);
1933 return;
18901934 }
18911935
18921936 // When constant has value 'null', set is_null local to '1'
18931937 // and payload to '0'
18941938 if (val.castTag(.opt_payload)) |payload| {
1895 try self.addImm32(0);
1939 try self.addImm32(1);
18961940 if (payload_type.hasCodeGenBits())
18971941 try self.emitConstant(payload.data, payload_type);
18981942 } else {
18991943 // set null-tag
1900 try self.addImm32(1);
1944 try self.addImm32(0);
19011945 // null-tag is set, so write a '0' const
19021946 try self.addImm32(0);
19031947 }
......@@ -2065,23 +2109,34 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
20652109}
20662110
20672111fn airCmp(self: *Self, inst: Air.Inst.Index, op: std.math.CompareOperator) InnerError!WValue {
2068 const data: Air.Inst.Data = self.air.instructions.items(.data)[inst];
2069 const lhs = self.resolveInst(data.bin_op.lhs);
2070 const rhs = self.resolveInst(data.bin_op.rhs);
2071 const lhs_ty = self.air.typeOf(data.bin_op.lhs);
2112 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2113 const lhs = self.resolveInst(bin_op.lhs);
2114 const rhs = self.resolveInst(bin_op.rhs);
2115 const operand_ty = self.air.typeOf(bin_op.lhs);
20722116
20732117 try self.emitWValue(lhs);
20742118 try self.emitWValue(rhs);
20752119
2120 if (operand_ty.zigTypeTag() == .Optional and !operand_ty.isPtrLikeOptional()) {
2121 var buf: Type.Payload.ElemType = undefined;
2122 const payload_ty = operand_ty.optionalChild(&buf);
2123 if (payload_ty.hasCodeGenBits()) {
2124 // When we hit this case, we must check the value of optionals
2125 // that are not pointers. This means first checking against non-null for
2126 // both lhs and rhs, as well as checking the payload are matching of lhs and rhs
2127 return self.fail("TODO: Implement airCmp for comparing optionals", .{});
2128 }
2129 }
2130
20762131 const signedness: std.builtin.Signedness = blk: {
20772132 // by default we tell the operand type is unsigned (i.e. bools and enum values)
2078 if (lhs_ty.zigTypeTag() != .Int) break :blk .unsigned;
2133 if (operand_ty.zigTypeTag() != .Int) break :blk .unsigned;
20792134
20802135 // incase of an actual integer, we emit the correct signedness
2081 break :blk lhs_ty.intInfo(self.target).signedness;
2136 break :blk operand_ty.intInfo(self.target).signedness;
20822137 };
20832138 const opcode: wasm.Opcode = buildOpcode(.{
2084 .valtype1 = try self.typeToValtype(lhs_ty),
2139 .valtype1 = try self.typeToValtype(operand_ty),
20852140 .op = switch (op) {
20862141 .lt => .lt,
20872142 .lte => .le,
......@@ -2173,7 +2228,7 @@ fn airStructFieldPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
21732228 struct_ty.structFieldType(extra.data.field_index),
21742229 });
21752230 };
2176 return structFieldPtr(struct_ptr, offset);
2231 return self.structFieldPtr(struct_ptr, offset);
21772232}
21782233
21792234fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u32) InnerError!WValue {
......@@ -2186,10 +2241,10 @@ fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u32) InnerEr
21862241 field_ty,
21872242 });
21882243 };
2189 return structFieldPtr(struct_ptr, offset);
2244 return self.structFieldPtr(struct_ptr, offset);
21902245}
21912246
2192fn structFieldPtr(struct_ptr: WValue, offset: u32) InnerError!WValue {
2247fn structFieldPtr(self: *Self, struct_ptr: WValue, offset: u32) InnerError!WValue {
21932248 var final_offset = offset;
21942249 const local = switch (struct_ptr) {
21952250 .local => |local| local,
......@@ -2199,7 +2254,7 @@ fn structFieldPtr(struct_ptr: WValue, offset: u32) InnerError!WValue {
21992254 },
22002255 else => unreachable,
22012256 };
2202 return WValue{ .local_with_offset = .{ .local = local, .offset = final_offset } };
2257 return self.buildPointerOffset(.{ .local = local }, final_offset);
22032258}
22042259
22052260fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
......@@ -2434,24 +2489,13 @@ fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
24342489 const offset = err_ty.errorUnionSet().abiSize(self.target);
24352490
24362491 const err_union = try self.allocStack(err_ty);
2437 const to_store = switch (op_ty.zigTypeTag()) {
2438 // for those types we must load the pointer and then store
2439 // its value
2440 .Pointer, .Optional => blk: {
2441 if (!op_ty.isPtrLikeOptional()) {
2442 return self.fail("TODO: airWrapErrUnionPayload for optional type {}", .{op_ty});
2443 }
2444 break :blk try self.load(operand, op_ty, 0);
2445 },
2446 .Int => operand,
2447 else => return self.fail("TODO: airWrapErrUnionPayload for type {}", .{op_ty}),
2448 };
2449
2450 try self.store(err_union, to_store, op_ty, @intCast(u32, offset));
2492 const payload_ptr = try self.buildPointerOffset(err_union, offset);
2493 try self.store(payload_ptr, operand, op_ty, 0);
24512494
24522495 // ensure we also write '0' to the error part, so any present stack value gets overwritten by it.
2453 const tmp_local = try self.allocLocal(err_ty.errorUnionSet()); // locals are '0' by default.
2454 try self.store(err_union, tmp_local, err_ty.errorUnionSet(), 0);
2496 try self.addLabel(.local_get, err_union.local);
2497 try self.addImm32(0);
2498 try self.addMemArg(.i32_store16, .{ .offset = 0, .alignment = 2 });
24552499
24562500 return err_union;
24572501}
......@@ -2499,64 +2543,122 @@ fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
24992543 return result;
25002544}
25012545
2502fn airIsNull(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {
2546fn airIsNull(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode, op_kind: enum { value, ptr }) InnerError!WValue {
25032547 const un_op = self.air.instructions.items(.data)[inst].un_op;
25042548 const operand = self.resolveInst(un_op);
25052549
25062550 const op_ty = self.air.typeOf(un_op);
2551 const optional_ty = if (op_kind == .ptr) op_ty.childType() else op_ty;
25072552 try self.emitWValue(operand);
2508 if (!op_ty.isPtrLikeOptional()) {
2509 try self.addMemArg(.i32_load8_u, .{ .offset = 0, .alignment = 1 });
2553 if (!optional_ty.isPtrLikeOptional()) {
2554 var buf: Type.Payload.ElemType = undefined;
2555 const payload_ty = optional_ty.optionalChild(&buf);
2556 // When payload is zero-bits, we can treat operand as a value, rather than a
2557 // stack value
2558 if (payload_ty.hasCodeGenBits()) {
2559 try self.addMemArg(.i32_load8_u, .{ .offset = 0, .alignment = 1 });
2560 }
25102561 }
25112562
2512 // Compare the error value with '0'
2563 // Compare the null value with '0'
25132564 try self.addImm32(0);
25142565 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
25152566
2516 const is_null_tmp = try self.allocLocal(Type.initTag(.u8));
2567 const is_null_tmp = try self.allocLocal(Type.initTag(.i32));
25172568 try self.addLabel(.local_set, is_null_tmp.local);
25182569 return is_null_tmp;
25192570}
25202571
25212572fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2573 if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
25222574 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
25232575 const operand = self.resolveInst(ty_op.operand);
25242576 const opt_ty = self.air.typeOf(ty_op.operand);
2577 const payload_ty = self.air.typeOfIndex(inst);
2578 if (!payload_ty.hasCodeGenBits()) return WValue{ .none = {} };
2579 if (opt_ty.isPtrLikeOptional()) return operand;
25252580
2526 // For pointers we simply return its stack address, rather than
2527 // loading its value
2528 if (opt_ty.zigTypeTag() == .Pointer) {
2529 return WValue{ .local_with_offset = .{ .local = operand.local, .offset = 1 } };
2581 const offset = opt_ty.abiSize(self.target) - payload_ty.abiSize(self.target);
2582
2583 if (isByRef(payload_ty)) {
2584 return self.buildPointerOffset(operand, offset);
25302585 }
25312586
2532 if (opt_ty.isPtrLikeOptional()) return operand;
2587 return self.load(operand, payload_ty, @intCast(u32, offset));
2588}
2589
2590fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2591 if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
2592
2593 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2594 const operand = self.resolveInst(ty_op.operand);
2595 const opt_ty = self.air.typeOf(ty_op.operand).childType();
25332596
25342597 var buf: Type.Payload.ElemType = undefined;
2535 const child_ty = opt_ty.optionalChild(&buf);
2536 const offset = opt_ty.abiSize(self.target) - child_ty.abiSize(self.target);
2598 const payload_ty = opt_ty.optionalChild(&buf);
2599 if (!payload_ty.hasCodeGenBits() or opt_ty.isPtrLikeOptional()) {
2600 return operand;
2601 }
25372602
2538 return self.load(operand, child_ty, @intCast(u32, offset));
2603 const offset = opt_ty.abiSize(self.target) - payload_ty.abiSize(self.target);
2604 return self.buildPointerOffset(operand, offset);
25392605}
25402606
25412607fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
25422608 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
25432609 const operand = self.resolveInst(ty_op.operand);
2544 _ = operand;
2545 return self.fail("TODO - wasm codegen for optional_payload_ptr_set", .{});
2610 const opt_ty = self.air.typeOf(ty_op.operand).childType();
2611 var buf: Type.Payload.ElemType = undefined;
2612 const payload_ty = opt_ty.optionalChild(&buf);
2613 if (!payload_ty.hasCodeGenBits()) {
2614 return self.fail("TODO: Implement OptionalPayloadPtrSet for optional with zero-sized type {}", .{payload_ty});
2615 }
2616
2617 if (opt_ty.isPtrLikeOptional()) {
2618 return operand;
2619 }
2620
2621 const offset = std.math.cast(u32, opt_ty.abiSize(self.target) - payload_ty.abiSize(self.target)) catch {
2622 return self.fail("Optional type {} too big to fit into stack frame", .{opt_ty});
2623 };
2624
2625 try self.emitWValue(operand);
2626 try self.addImm32(1);
2627 try self.addMemArg(.i32_store8, .{ .offset = 0, .alignment = 1 });
2628
2629 return self.buildPointerOffset(operand, offset);
25462630}
25472631
25482632fn airWrapOptional(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2633 if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
2634
25492635 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2636 const payload_ty = self.air.typeOf(ty_op.operand);
2637 if (!payload_ty.hasCodeGenBits()) {
2638 const non_null_bit = try self.allocStack(Type.initTag(.u1));
2639 try self.addLabel(.local_get, non_null_bit.local);
2640 try self.addImm32(1);
2641 try self.addMemArg(.i32_store8, .{ .offset = 0, .alignment = 1 });
2642 return non_null_bit;
2643 }
2644
25502645 const operand = self.resolveInst(ty_op.operand);
2646 const op_ty = self.air.typeOfIndex(inst);
2647 if (op_ty.isPtrLikeOptional()) {
2648 return operand;
2649 }
2650 const offset = std.math.cast(u32, op_ty.abiSize(self.target) - payload_ty.abiSize(self.target)) catch {
2651 return self.fail("Optional type {} too big to fit into stack frame", .{op_ty});
2652 };
25512653
2552 const op_ty = self.air.typeOf(ty_op.operand);
2553 const optional_ty = self.air.getRefType(ty_op.ty);
2554 const offset = optional_ty.abiSize(self.target) - op_ty.abiSize(self.target);
2654 // Create optional type, set the non-null bit, and store the operand inside the optional type
2655 const result = try self.allocStack(op_ty);
2656 try self.addLabel(.local_get, result.local);
2657 try self.addImm32(1);
2658 try self.addMemArg(.i32_store8, .{ .offset = 0, .alignment = 1 });
2659 try self.store(result, operand, payload_ty, offset);
25552660
2556 return WValue{ .local_with_offset = .{
2557 .local = operand.local,
2558 .offset = @intCast(u32, offset),
2559 } };
2661 return result;
25602662}
25612663
25622664fn airSliceLen(self: *Self, inst: Air.Inst.Index) InnerError!WValue {