authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-20 21:28:39+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-21 21:07:55+01:00
logdeb8d0765b46f75546f4342ad9078dbe6ad7b9be
tree960363fd9114f4e4485480a961a5be8b09abf412
parentec5220405b0218f892f6a1636ddd04d791017309
signaturelock-open Commit is signed but in an unrecognized format.

wasm: Fix text cases and add pointer test cases

Ensure all previous test cases are still passing, as well as add some basic tests for now for testing pointers to the stack. This means we can start implementing wasm's C ABI found at: https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md We also simplified the block logic by always using 'void' block types and instead writing the value to a local, which can then be referenced by continues instructions, as done currently by AIR. Besides this, we also no longer need to insert blocks at an offset, as we simply write the saved temporary after we create the block.

2 files changed, 153 insertions(+), 53 deletions(-)

src/arch/wasm/CodeGen.zig+118-53
......@@ -187,7 +187,13 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
187187 32 => switch (args.valtype1.?) {
188188 .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u,
189189 .i32 => return .i32_load,
190 .f32, .f64 => unreachable,
190 .f32 => return .f32_load,
191 .f64 => unreachable,
192 },
193 64 => switch (args.valtype1.?) {
194 .i64 => return .i64_load,
195 .f64 => return .f64_load,
196 else => unreachable,
191197 },
192198 else => unreachable,
193199 } else switch (args.valtype1.?) {
......@@ -216,6 +222,7 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
216222 },
217223 64 => switch (args.valtype1.?) {
218224 .i64 => return .i64_store,
225 .f64 => return .f64_store,
219226 else => unreachable,
220227 },
221228 else => unreachable,
......@@ -505,7 +512,10 @@ gpa: *mem.Allocator,
505512/// Table to save `WValue`'s generated by an `Air.Inst`
506513values: ValueTable,
507514/// Mapping from Air.Inst.Index to block ids
508blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, u32) = .{},
515blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, struct {
516 label: u32,
517 value: WValue,
518}) = .{},
509519/// `bytes` contains the wasm bytecode belonging to the 'code' section.
510520code: ArrayList(u8),
511521/// Contains the generated function type bytecode for the current function
......@@ -984,8 +994,8 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
984994 .dbg_stmt => WValue.none,
985995 .intcast => self.airIntcast(inst),
986996
987 .is_err => self.airIsErr(inst, .i32_eq),
988 .is_non_err => self.airIsErr(inst, .i32_ne),
997 .is_err => self.airIsErr(inst, .i32_ne),
998 .is_non_err => self.airIsErr(inst, .i32_eq),
989999
9901000 .is_null => self.airIsNull(inst, .i32_ne),
9911001 .is_non_null => self.airIsNull(inst, .i32_eq),
......@@ -1065,12 +1075,12 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
10651075
10661076 const ret_ty = target.ty.fnReturnType();
10671077 switch (ret_ty.zigTypeTag()) {
1068 .ErrorUnion, .Optional => {
1078 .Void, .NoReturn => return WValue.none,
1079 else => {
10691080 const result_local = try self.allocLocal(ret_ty);
10701081 try self.addLabel(.local_set, result_local.local);
10711082 return result_local;
10721083 },
1073 else => return WValue.none,
10741084 }
10751085}
10761086
......@@ -1086,7 +1096,7 @@ fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
10861096 if (abi_size == 0) return WValue{ .none = {} };
10871097
10881098 // local, containing the offset to the stack position
1089 const local = try self.allocLocal(child_type);
1099 const local = try self.allocLocal(Type.initTag(.i32)); // always pointer therefore i32
10901100 try self.moveStack(@intCast(u32, abi_size), local.local);
10911101
10921102 return local;
......@@ -1114,29 +1124,64 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
11141124 var buf: Type.Payload.ElemType = undefined;
11151125 const payload_ty = if (ty.zigTypeTag() == .ErrorUnion) ty.errorUnionPayload() else ty.optionalChild(&buf);
11161126 const tag_ty = if (ty.zigTypeTag() == .ErrorUnion) ty.errorUnionSet() else Type.initTag(.u8);
1117 const payload_offset = @intCast(u32, tag_ty.abiSize(self.target));
1118
1119 if (rhs == .constant) {
1120 // constant will contain both tag and payload,
1121 // so save those in 2 temporary locals before storing them
1122 // in memory
1123 try self.emitWValue(rhs);
1124 const tag_local = try self.allocLocal(tag_ty);
1125 const payload_local = try self.allocLocal(payload_ty);
1126
1127 try self.addLabel(.local_set, payload_local.local);
1128 try self.addLabel(.local_set, tag_local.local);
1129
1130 try self.store(lhs, tag_local, tag_ty, 0);
1131 return try self.store(lhs, payload_local, payload_ty, payload_offset);
1132 } else {
1133 // Load values from `rhs` stack position and store in `lhs` instead
1134 const tag_local = try self.load(rhs, tag_ty, 0);
1135 const payload_local = try self.load(rhs, payload_ty, payload_offset);
1127 const payload_offset = if (ty.zigTypeTag() == .ErrorUnion)
1128 @intCast(u32, tag_ty.abiSize(self.target))
1129 else
1130 @intCast(u32, ty.abiSize(self.target) - payload_ty.abiSize(self.target));
1131
1132 switch (rhs) {
1133 .constant => {
1134 // constant will contain both tag and payload,
1135 // so save those in 2 temporary locals before storing them
1136 // in memory
1137 try self.emitWValue(rhs);
1138 const tag_local = try self.allocLocal(tag_ty);
1139 const payload_local = try self.allocLocal(payload_ty);
1140
1141 try self.addLabel(.local_set, payload_local.local);
1142 try self.addLabel(.local_set, tag_local.local);
1143
1144 try self.store(lhs, tag_local, tag_ty, 0);
1145 return try self.store(lhs, payload_local, payload_ty, payload_offset);
1146 },
1147 .local => {
1148 // Load values from `rhs` stack position and store in `lhs` instead
1149 const tag_local = try self.load(rhs, tag_ty, 0);
1150 const payload_local = try self.load(rhs, payload_ty, payload_offset);
11361151
1137 try self.store(lhs, tag_local, tag_ty, 0);
1138 return try self.store(lhs, payload_local, payload_ty, payload_offset);
1152 try self.store(lhs, tag_local, tag_ty, 0);
1153 return try self.store(lhs, payload_local, payload_ty, payload_offset);
1154 },
1155 .local_with_offset => |with_offset| {
1156 const tag_local = try self.allocLocal(tag_ty);
1157 try self.addImm32(0);
1158 try self.store(lhs, tag_local, tag_ty, 0);
1159
1160 return try self.store(
1161 lhs,
1162 .{ .local = with_offset.local },
1163 payload_ty,
1164 with_offset.offset,
1165 );
1166 },
1167 else => unreachable,
1168 }
1169 },
1170 .Struct => {
1171 // we are copying a struct with its fields.
1172 // Replace this with a wasm memcpy instruction once we support that feature.
1173 const fields_len = ty.structFieldCount();
1174 var index: usize = 0;
1175 while (index < fields_len) : (index += 1) {
1176 const field_ty = ty.structFieldType(index);
1177 if (!field_ty.hasCodeGenBits()) continue;
1178 const field_offset = std.math.cast(u32, ty.structFieldOffset(index, self.target)) catch {
1179 return self.fail("Field type '{}' too big to fit into stack frame", .{field_ty});
1180 };
1181 const field_local = try self.load(rhs, field_ty, field_offset);
1182 try self.store(lhs, field_local, field_ty, field_offset);
11391183 }
1184 return;
11401185 },
11411186 else => {},
11421187 }
......@@ -1429,21 +1474,29 @@ fn airBlock(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
14291474 const extra = self.air.extraData(Air.Block, ty_pl.payload);
14301475 const body = self.air.extra[extra.end..][0..extra.data.body_len];
14311476
1432 try self.startBlock(.block, block_ty, null);
1477 // if block_ty is non-empty, we create a register to store the temporary value
1478 const block_result: WValue = if (block_ty != wasm.block_empty)
1479 try self.allocLocal(self.air.getRefType(ty_pl.ty))
1480 else
1481 WValue.none;
1482
1483 try self.startBlock(.block, wasm.block_empty);
14331484 // Here we set the current block idx, so breaks know the depth to jump
14341485 // to when breaking out.
1435 try self.blocks.putNoClobber(self.gpa, inst, self.block_depth);
1486 try self.blocks.putNoClobber(self.gpa, inst, .{
1487 .label = self.block_depth,
1488 .value = block_result,
1489 });
14361490 try self.genBody(body);
14371491 try self.endBlock();
14381492
1439 return .none;
1493 return block_result;
14401494}
14411495
14421496/// appends a new wasm block to the code section and increases the `block_depth` by 1
1443fn startBlock(self: *Self, block_tag: wasm.Opcode, valtype: u8, with_offset: ?usize) !void {
1497fn startBlock(self: *Self, block_tag: wasm.Opcode, valtype: u8) !void {
14441498 self.block_depth += 1;
1445 const offset = with_offset orelse self.mir_instructions.len;
1446 try self.addInstAt(offset, .{
1499 try self.addInst(.{
14471500 .tag = Mir.Inst.Tag.fromOpcode(block_tag),
14481501 .data = .{ .block_type = valtype },
14491502 });
......@@ -1462,7 +1515,7 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
14621515
14631516 // result type of loop is always 'noreturn', meaning we can always
14641517 // emit the wasm type 'block_empty'.
1465 try self.startBlock(.loop, wasm.block_empty, null);
1518 try self.startBlock(.loop, wasm.block_empty);
14661519 try self.genBody(body);
14671520
14681521 // breaking to the index of a loop block will continue the loop instead
......@@ -1480,13 +1533,10 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
14801533 const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
14811534 // TODO: Handle death instructions for then and else body
14821535
1483 // insert blocks at the position of `offset` so
1484 // the condition can jump to it
1485 const offset = self.mir_instructions.len;
1486 try self.emitWValue(condition);
1487
14881536 // result type is always noreturn, so use `block_empty` as type.
1489 try self.startBlock(.block, wasm.block_empty, offset);
1537 try self.startBlock(.block, wasm.block_empty);
1538 // emit the conditional value
1539 try self.emitWValue(condition);
14901540
14911541 // we inserted the block in front of the condition
14921542 // so now check if condition matches. If not, break outside this block
......@@ -1539,15 +1589,20 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: std.math.CompareOperator) Inner
15391589
15401590fn airBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
15411591 const br = self.air.instructions.items(.data)[inst].br;
1592 const block = self.blocks.get(br.block_inst).?;
15421593
15431594 // if operand has codegen bits we should break with a value
15441595 if (self.air.typeOf(br.operand).hasCodeGenBits()) {
15451596 try self.emitWValue(self.resolveInst(br.operand));
1597
1598 if (block.value != .none) {
1599 try self.addLabel(.local_set, block.value.local);
1600 }
15461601 }
15471602
15481603 // We map every block to its block index.
15491604 // We then determine how far we have to jump to it by subtracting it from current block depth
1550 const idx: u32 = self.block_depth - self.blocks.get(br.block_inst).?;
1605 const idx: u32 = self.block_depth - block.label;
15511606 try self.addLabel(.br, idx);
15521607
15531608 return .none;
......@@ -1677,7 +1732,7 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
16771732 }
16781733
16791734 case_list.appendAssumeCapacity(.{ .values = values, .body = case_body });
1680 try self.startBlock(.block, blocktype, null);
1735 try self.startBlock(.block, blocktype);
16811736 }
16821737
16831738 // When the highest and lowest values are seperated by '50',
......@@ -1690,7 +1745,7 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
16901745 const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len];
16911746 const has_else_body = else_body.len != 0;
16921747 if (has_else_body) {
1693 try self.startBlock(.block, blocktype, null);
1748 try self.startBlock(.block, blocktype);
16941749 }
16951750
16961751 if (!is_sparse) {
......@@ -1698,7 +1753,7 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
16981753 // The value 'target' represents the index into the table.
16991754 // Each index in the table represents a label to the branch
17001755 // to jump to.
1701 try self.startBlock(.block, blocktype, null);
1756 try self.startBlock(.block, blocktype);
17021757 try self.emitWValue(target);
17031758 if (lowest < 0) {
17041759 // since br_table works using indexes, starting from '0', we must ensure all values
......@@ -1754,7 +1809,7 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
17541809 try self.addLabel(.br_if, 0);
17551810 } else {
17561811 // in multi-value prongs we must check if any prongs match the target value.
1757 try self.startBlock(.block, blocktype, null);
1812 try self.startBlock(.block, blocktype);
17581813 for (case.values) |value| {
17591814 try self.emitWValue(target);
17601815 try self.emitConstant(value.value, target_ty);
......@@ -1794,7 +1849,7 @@ fn airIsErr(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!W
17941849 .alignment = err_ty.abiAlignment(self.target),
17951850 });
17961851 try self.addInst(.{
1797 .tag = .i32_load,
1852 .tag = .i32_load16_u,
17981853 .data = .{ .payload = mem_arg_index },
17991854 });
18001855
......@@ -1811,14 +1866,14 @@ fn airUnwrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue
18111866 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
18121867 const operand = self.resolveInst(ty_op.operand);
18131868 const err_ty = self.air.typeOf(ty_op.operand);
1814 const offset = @intCast(u32, err_ty.errorUnionSet().abiSize(self.target) / 8);
1815
1816 return self.load(operand, self.air.getRefType(ty_op.ty), offset);
1869 const offset = @intCast(u32, err_ty.errorUnionSet().abiSize(self.target));
1870 return self.load(operand, err_ty.errorUnionPayload(), offset);
18171871}
18181872
18191873fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
18201874 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1821 return self.resolveInst(ty_op.operand);
1875 _ = ty_op;
1876 return self.fail("TODO: wasm airWrapErrUnionPayload", .{});
18221877}
18231878
18241879fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
......@@ -1880,8 +1935,9 @@ fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
18801935
18811936 var buf: Type.Payload.ElemType = undefined;
18821937 const child_ty = opt_ty.optionalChild(&buf);
1938 const offset = opt_ty.abiSize(self.target) - child_ty.abiSize(self.target);
18831939
1884 return self.load(operand, child_ty, @as(u32, 1)); // null tag is 1 byte
1940 return self.load(operand, child_ty, @intCast(u32, offset));
18851941}
18861942
18871943fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
......@@ -1893,5 +1949,14 @@ fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!WValue
18931949
18941950fn airWrapOptional(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
18951951 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1896 return self.resolveInst(ty_op.operand);
1952 const operand = self.resolveInst(ty_op.operand);
1953
1954 const op_ty = self.air.typeOf(ty_op.operand);
1955 const optional_ty = self.air.getRefType(ty_op.ty);
1956 const offset = optional_ty.abiSize(self.target) - op_ty.abiSize(self.target);
1957
1958 return WValue{ .local_with_offset = .{
1959 .local = operand.local,
1960 .offset = @intCast(u32, offset),
1961 } };
18971962}
test/stage2/wasm.zig+35
......@@ -740,4 +740,39 @@ pub fn addCases(ctx: *TestContext) !void {
740740 \\}
741741 , "0\n");
742742 }
743
744 {
745 var case = ctx.exe("wasm pointers", wasi);
746
747 case.addCompareOutput(
748 \\pub export fn _start() u32 {
749 \\ var x: u32 = 0;
750 \\
751 \\ foo(&x);
752 \\ return x;
753 \\}
754 \\
755 \\fn foo(x: *u32)void {
756 \\ x.* = 2;
757 \\}
758 , "2\n");
759
760 case.addCompareOutput(
761 \\pub export fn _start() u32 {
762 \\ var x: u32 = 0;
763 \\
764 \\ foo(&x);
765 \\ bar(&x);
766 \\ return x;
767 \\}
768 \\
769 \\fn foo(x: *u32)void {
770 \\ x.* = 2;
771 \\}
772 \\
773 \\fn bar(x: *u32) void {
774 \\ x.* += 2;
775 \\}
776 , "4\n");
777 }
743778}