wasm: Implement optionals and ensure correct alignment

Rather than writing the alignment in its natural form, wasm binaries encode the alignment of types as the exponent of a power of 2.
So rather than performing this encoding during AIR->MIR, we do this while emitting MIR->binary encoding.
This allows us to keep alignment logic to its natural form while doing calculations (Which is what we need during linking as well).

We also implement optionals and pointers to an optional.
This commit is contained in:
Luuk de Gram
2021-11-19 22:15:53 +01:00
parent 460b3d39ea
commit ec5220405b
2 changed files with 61 additions and 22 deletions

View File

@@ -211,7 +211,8 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
32 => switch (args.valtype1.?) {
.i64 => return .i64_store32,
.i32 => return .i32_store,
.f32, .f64 => unreachable,
.f32 => return .f32_store,
.f64 => unreachable,
},
64 => switch (args.valtype1.?) {
.i64 => return .i64_store,
@@ -680,6 +681,7 @@ fn typeToValtype(self: *Self, ty: Type) InnerError!wasm.Valtype {
.ErrorSet,
.Struct,
.ErrorUnion,
.Optional,
=> wasm.Valtype.i32,
else => self.fail("TODO - Wasm valtype for type '{}'", .{ty}),
};
@@ -878,7 +880,7 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu
const ret_ty = fn_ty.fnReturnType();
switch (ret_ty.zigTypeTag()) {
.ErrorUnion => result.return_value = try self.allocLocal(Type.initTag(.i32)),
.ErrorUnion, .Optional => result.return_value = try self.allocLocal(Type.initTag(.i32)),
.Int, .Float, .Bool, .Void, .NoReturn => {},
else => return self.fail("TODO: Implement function return type {}", .{ret_ty}),
}
@@ -1063,7 +1065,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const ret_ty = target.ty.fnReturnType();
switch (ret_ty.zigTypeTag()) {
.ErrorUnion => {
.ErrorUnion, .Optional => {
const result_local = try self.allocLocal(ret_ty);
try self.addLabel(.local_set, result_local.local);
return result_local;
@@ -1111,14 +1113,15 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
.ErrorUnion, .Optional => {
var buf: Type.Payload.ElemType = undefined;
const payload_ty = if (ty.zigTypeTag() == .ErrorUnion) ty.errorUnionPayload() else ty.optionalChild(&buf);
const tag_ty = if (ty.zigTypeTag() == .ErrorUnion) ty.errorUnionSet() else Type.initTag(.i8);
const payload_offset = @intCast(u32, tag_ty.abiSize(self.target) / 8);
const tag_ty = if (ty.zigTypeTag() == .ErrorUnion) ty.errorUnionSet() else Type.initTag(.u8);
const payload_offset = @intCast(u32, tag_ty.abiSize(self.target));
if (rhs == .constant) {
// constant will contain both tag and payload,
// so save those in 2 temporary locals before storing them
// in memory
try self.emitWValue(rhs);
const tag_local = try self.allocLocal(Type.initTag(.i32));
const tag_local = try self.allocLocal(tag_ty);
const payload_local = try self.allocLocal(payload_ty);
try self.addLabel(.local_set, payload_local.local);
@@ -1147,8 +1150,14 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
});
// store rhs value at stack pointer's location in memory
const mem_arg_index = try self.addExtra(Mir.MemArg{ .offset = offset, .alignment = 0 });
try self.addInst(.{ .tag = Mir.Inst.Tag.fromOpcode(opcode), .data = .{ .payload = mem_arg_index } });
const mem_arg_index = try self.addExtra(Mir.MemArg{
.offset = offset,
.alignment = ty.abiAlignment(self.target),
});
try self.addInst(.{
.tag = Mir.Inst.Tag.fromOpcode(opcode),
.data = .{ .payload = mem_arg_index },
});
}
fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
@@ -1157,8 +1166,11 @@ fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const ty = self.air.getRefType(ty_op.ty);
return switch (ty.zigTypeTag()) {
.Struct, .ErrorUnion => operand,
else => self.load(operand, ty, 0),
.Struct, .ErrorUnion, .Optional => operand, // pass as pointer
else => switch (operand) {
.local_with_offset => |with_offset| try self.load(operand, ty, with_offset.offset),
else => try self.load(operand, ty, 0),
},
};
}
@@ -1174,7 +1186,10 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {
.signedness = signedness,
});
const mem_arg_index = try self.addExtra(Mir.MemArg{ .offset = offset, .alignment = 0 });
const mem_arg_index = try self.addExtra(Mir.MemArg{
.offset = offset,
.alignment = ty.abiAlignment(self.target),
});
try self.addInst(.{
.tag = Mir.Inst.Tag.fromOpcode(opcode),
.data = .{ .payload = mem_arg_index },
@@ -1301,7 +1316,7 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {
// memory instruction followed by their memarg immediate
// memarg ::== x:u32, y:u32 => {align x, offset y}
const extra_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 0 });
const extra_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 4 });
try self.addInst(.{ .tag = .i32_load, .data = .{ .payload = extra_index } });
} else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{val.tag()});
},
@@ -1774,7 +1789,10 @@ fn airIsErr(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!W
// load the error tag value
try self.emitWValue(operand);
const mem_arg_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 0 });
const mem_arg_index = try self.addExtra(Mir.MemArg{
.offset = 0,
.alignment = err_ty.abiAlignment(self.target),
});
try self.addInst(.{
.tag = .i32_load,
.data = .{ .payload = mem_arg_index },
@@ -1830,22 +1848,40 @@ fn airIsNull(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!
const un_op = self.air.instructions.items(.data)[inst].un_op;
const operand = self.resolveInst(un_op);
// load the null value which is positioned at local_with_offset's index
try self.emitWValue(.{ .local = operand.local_with_offset.local });
// load the null tag value
try self.emitWValue(operand);
const mem_arg_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 1 });
try self.addInst(.{
.tag = .i32_load8_u,
.data = .{ .payload = mem_arg_index },
});
// Compare the error value with '0'
try self.addImm32(0);
try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
// we save the result in a new local
const local = try self.allocLocal(Type.initTag(.i32));
try self.addLabel(.local_set, local.local);
return local;
const is_null_tmp = try self.allocLocal(Type.initTag(.u8));
try self.addLabel(.local_set, is_null_tmp.local);
return is_null_tmp;
}
fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand = self.resolveInst(ty_op.operand);
return WValue{ .local = operand.local_with_offset.local + 1 };
const opt_ty = self.air.typeOf(ty_op.operand);
// For pointers we simply return its stack address, rather than
// loading its value
if (opt_ty.zigTypeTag() == .Pointer) {
return WValue{ .local_with_offset = .{ .local = operand.local, .offset = 1 } };
}
if (opt_ty.isPtrLikeOptional()) return operand;
var buf: Type.Payload.ElemType = undefined;
const child_ty = opt_ty.optionalChild(&buf);
return self.load(operand, child_ty, @as(u32, 1)); // null tag is 1 byte
}
fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {

View File

@@ -251,7 +251,10 @@ fn emitMemArg(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
const extra_index = emit.mir.instructions.items(.data)[inst].payload;
const mem_arg = emit.mir.extraData(Mir.MemArg, extra_index).data;
try emit.code.append(@enumToInt(tag));
try leb128.writeULEB128(emit.code.writer(), mem_arg.alignment);
// wasm encodes alignment as power of 2, rather than natural alignment
const encoded_alignment = mem_arg.alignment >> 1;
try leb128.writeULEB128(emit.code.writer(), encoded_alignment);
try leb128.writeULEB128(emit.code.writer(), mem_arg.offset);
}