authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-07-31 16:01:37+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-08-01 11:07:23+02:00
log61de59e121ebd5ad2a8af871ddb1d785c3694481
tree3faaf644c970ce194adfd77be0778d8939852d6a
parente58976542b096df6cbb2088b6c7a690d7b95c50a
signaturelock-open Commit is signed but in an unrecognized format.

wasm: Implement optionals

This uses the same approach as error unions, meaning it's a `WValue` with its tag set to `multi_value`. The initial index of the multi_value will contain the null-tag, used to check if the value is null or not. The other values will be the payload. To support the `.?` shorthand syntax, we save the result from checking the null-tag into a new local, which can then be loaded later in the block to either hit `unreachable` or set the actual payload value. Currently, it seems `.?` and `orelse unreachable` results in different AIR structure. TODO: Is this expected?

1 files changed, 104 insertions(+), 2 deletions(-)

src/codegen/wasm.zig+104-2
......@@ -590,7 +590,7 @@ pub const Context = struct {
590590 .Pointer,
591591 .ErrorSet,
592592 => wasm.Valtype.i32,
593 .Struct, .ErrorUnion => unreachable, // Multi typed, must be handled individually.
593 .Struct, .ErrorUnion, .Optional => unreachable, // Multi typed, must be handled individually.
594594 else => |tag| self.fail("TODO - Wasm valtype for type '{s}'", .{tag}),
595595 };
596596 }
......@@ -663,6 +663,23 @@ pub const Context = struct {
663663 .count = 2,
664664 } };
665665 },
666 .Optional => {
667 var opt_buf: Type.Payload.ElemType = undefined;
668 const child_type = ty.optionalChild(&opt_buf);
669 if (ty.isPtrLikeOptional()) {
670 return self.fail("TODO: wasm optional pointer", .{});
671 }
672
673 try self.locals.ensureCapacity(self.gpa, self.locals.items.len + 2);
674 self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // optional 'tag' for null-checking is always i32
675 self.locals.appendAssumeCapacity(try self.genValtype(child_type));
676 self.local_index += 2;
677
678 return WValue{ .multi_value = .{
679 .index = initial_index,
680 .count = 2,
681 } };
682 },
666683 else => {
667684 const valtype = try self.genValtype(ty);
668685 try self.locals.append(self.gpa, valtype);
......@@ -830,8 +847,15 @@ pub const Context = struct {
830847 .constant => unreachable,
831848 .dbg_stmt => WValue.none,
832849 .intcast => self.airIntcast(inst),
850
833851 .is_err => self.airIsErr(inst, .i32_ne),
834852 .is_non_err => self.airIsErr(inst, .i32_eq),
853
854 .is_null => self.airIsNull(inst, .i32_ne),
855 .is_non_null => self.airIsNull(inst, .i32_eq),
856 .is_null_ptr => self.airIsNull(inst, .i32_ne),
857 .is_non_null_ptr => self.airIsNull(inst, .i32_eq),
858
835859 .load => self.airLoad(inst),
836860 .loop => self.airLoop(inst),
837861 .not => self.airNot(inst),
......@@ -840,8 +864,13 @@ pub const Context = struct {
840864 .struct_field_ptr => self.airStructFieldPtr(inst),
841865 .switch_br => self.airSwitchBr(inst),
842866 .unreach => self.airUnreachable(inst),
867 .wrap_optional => self.airWrapOptional(inst),
868
843869 .unwrap_errunion_payload => self.airUnwrapErrUnionPayload(inst),
844870 .wrap_errunion_payload => self.airWrapErrUnionPayload(inst),
871
872 .optional_payload => self.airOptionalPayload(inst),
873 .optional_payload_ptr => self.airOptionalPayload(inst),
845874 else => |tag| self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
846875 };
847876 }
......@@ -926,6 +955,22 @@ pub const Context = struct {
926955 try leb.writeULEB128(writer, multi_value.index + i - 1);
927956 }
928957 },
958 .local => {
959 // This can occur when we wrap a single value into a multi-value,
960 // such as wrapping a non-optional value into an optional.
961 // This means we must zero the null-tag, and set the payload.
962 assert(multi_value.count == 2);
963 // set null-tag
964 try writer.writeByte(wasm.opcode(.i32_const));
965 try leb.writeULEB128(writer, @as(u32, 0));
966 try writer.writeByte(wasm.opcode(.local_set));
967 try leb.writeULEB128(writer, multi_value.index);
968
969 // set payload
970 try self.emitWValue(rhs);
971 try writer.writeByte(wasm.opcode(.local_set));
972 try leb.writeULEB128(writer, multi_value.index + 1);
973 },
929974 else => unreachable,
930975 },
931976 .local => |local| {
......@@ -1088,6 +1133,31 @@ pub const Context = struct {
10881133 try self.emitConstant(data, payload_type);
10891134 }
10901135 },
1136 .Optional => {
1137 var buf: Type.Payload.ElemType = undefined;
1138 const payload_type = ty.optionalChild(&buf);
1139 if (ty.isPtrLikeOptional()) {
1140 return self.fail("Wasm TODO: emitConstant for optional pointer", .{});
1141 }
1142
1143 // When constant has value 'null', set is_null local to '1'
1144 // and payload to '0'
1145 if (val.tag() == .null_value) {
1146 try writer.writeByte(wasm.opcode(.i32_const));
1147 try leb.writeILEB128(writer, @as(i32, 1));
1148
1149 const opcode: wasm.Opcode = buildOpcode(.{
1150 .op = .@"const",
1151 .valtype1 = try self.typeToValtype(payload_type),
1152 });
1153 try writer.writeByte(wasm.opcode(opcode));
1154 try leb.writeULEB128(writer, @as(u32, 0));
1155 } else {
1156 try writer.writeByte(wasm.opcode(.i32_const));
1157 try leb.writeILEB128(writer, @as(i32, 0));
1158 try self.emitConstant(val, payload_type);
1159 }
1160 },
10911161 else => |zig_type| return self.fail("Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}),
10921162 }
10931163 }
......@@ -1188,7 +1258,6 @@ pub const Context = struct {
11881258 const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len];
11891259 const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
11901260 const writer = self.code.writer();
1191
11921261 // TODO: Handle death instructions for then and else body
11931262
11941263 // insert blocks at the position of `offset` so
......@@ -1521,4 +1590,37 @@ pub const Context = struct {
15211590 // other cases are no-op
15221591 return .none;
15231592 }
1593
1594 fn airIsNull(self: *Context, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {
1595 const un_op = self.air.instructions.items(.data)[inst].un_op;
1596 const operand = self.resolveInst(un_op);
1597 // const offset = self.code.items.len;
1598 const writer = self.code.writer();
1599
1600 // load the null value which is positioned at multi_value's index
1601 try self.emitWValue(.{ .local = operand.multi_value.index });
1602 // Compare the null value with '0'
1603 try writer.writeByte(wasm.opcode(.i32_const));
1604 try leb.writeILEB128(writer, @as(i32, 0));
1605
1606 try writer.writeByte(@enumToInt(opcode));
1607
1608 // we save the result in a new local
1609 const local = try self.allocLocal(Type.initTag(.i32));
1610 try writer.writeByte(wasm.opcode(.local_set));
1611 try leb.writeULEB128(writer, local.local);
1612
1613 return local;
1614 }
1615
1616 fn airOptionalPayload(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1617 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1618 const operand = self.resolveInst(ty_op.operand);
1619 return WValue{ .local = operand.multi_value.index + 1 };
1620 }
1621
1622 fn airWrapOptional(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1623 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1624 return self.resolveInst(ty_op.operand);
1625 }
15241626};