commit 5d6380b38d7c83055fe29eb7f8a19b1424276cd7 (tree)
parent 88be5bd81decab792cdb5b749b4bb5cf6d71e877
Author: Andrew Kelley <andrew@ziglang.org>
Date: Tue, 21 Dec 2021 21:06:08 -0800
Merge pull request #10384 from joachimschmidt557/stage2-arm-optionals
stage2 ARM: basic implementation of optionals and error unions
Diffstat:
2 files changed, 176 insertions(+), 51 deletions(-)
diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig
@@ -1104,7 +1104,13 @@ fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void {
fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
- const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union payload for {}", .{self.target.cpu.arch});
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
+ const err_ty = self.air.typeOf(ty_op.operand);
+ const payload_ty = err_ty.errorUnionPayload();
+ if (!payload_ty.hasCodeGenBits()) break :result MCValue.none;
+
+ return self.fail("TODO implement unwrap error union payload for non-empty payloads", .{});
+ };
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
}
@@ -2334,39 +2340,81 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
return self.finishAir(inst, .unreach, .{ .none, .none, .none });
}
-fn isNull(self: *Self, operand: MCValue) !MCValue {
- _ = operand;
- // Here you can specialize this instruction if it makes sense to, otherwise the default
- // will call isNonNull and invert the result.
- return self.fail("TODO call isNonNull and invert the result", .{});
+fn isNull(self: *Self, ty: Type, operand: MCValue) !MCValue {
+ if (ty.isPtrLikeOptional()) {
+ assert(ty.abiSize(self.target.*) == 4);
+
+ const reg_mcv: MCValue = switch (operand) {
+ .register => operand,
+ else => .{ .register = try self.copyToTmpRegister(ty, operand) },
+ };
+
+ try self.genArmBinOpCode(undefined, reg_mcv, .{ .immediate = 0 }, false, .cmp_eq, undefined);
+
+ return MCValue{ .compare_flags_unsigned = .eq };
+ } else {
+ return self.fail("TODO implement non-pointer optionals", .{});
+ }
}
-fn isNonNull(self: *Self, operand: MCValue) !MCValue {
- _ = operand;
- // Here you can specialize this instruction if it makes sense to, otherwise the default
- // will call isNull and invert the result.
- return self.fail("TODO call isNull and invert the result", .{});
+fn isNonNull(self: *Self, ty: Type, operand: MCValue) !MCValue {
+ const is_null_result = try self.isNull(ty, operand);
+ assert(is_null_result.compare_flags_unsigned == .eq);
+
+ return MCValue{ .compare_flags_unsigned = .neq };
}
-fn isErr(self: *Self, operand: MCValue) !MCValue {
+fn isErr(self: *Self, ty: Type, operand: MCValue) !MCValue {
_ = operand;
- // Here you can specialize this instruction if it makes sense to, otherwise the default
- // will call isNonNull and invert the result.
- return self.fail("TODO call isNonErr and invert the result", .{});
+
+ const error_type = ty.errorUnionSet();
+ const payload_type = ty.errorUnionPayload();
+
+ if (!error_type.hasCodeGenBits()) {
+ return MCValue{ .immediate = 0 }; // always false
+ } else if (!payload_type.hasCodeGenBits()) {
+ if (error_type.abiSize(self.target.*) <= 4) {
+ const reg_mcv: MCValue = switch (operand) {
+ .register => operand,
+ else => .{ .register = try self.copyToTmpRegister(error_type, operand) },
+ };
+
+ try self.genArmBinOpCode(undefined, reg_mcv, .{ .immediate = 0 }, false, .cmp_eq, undefined);
+
+ return MCValue{ .compare_flags_unsigned = .gt };
+ } else {
+ return self.fail("TODO isErr for errors with size > 4", .{});
+ }
+ } else {
+ return self.fail("TODO isErr for non-empty payloads", .{});
+ }
}
-fn isNonErr(self: *Self, operand: MCValue) !MCValue {
- _ = operand;
- // Here you can specialize this instruction if it makes sense to, otherwise the default
- // will call isNull and invert the result.
- return self.fail("TODO call isErr and invert the result", .{});
+fn isNonErr(self: *Self, ty: Type, operand: MCValue) !MCValue {
+ const is_err_result = try self.isErr(ty, operand);
+ switch (is_err_result) {
+ .compare_flags_unsigned => |op| {
+ assert(op == .gt);
+ return MCValue{ .compare_flags_unsigned = .lte };
+ },
+ .immediate => |imm| {
+ assert(imm == 0);
+ return MCValue{ .immediate = 1 };
+ },
+ else => unreachable,
+ }
}
fn airIsNull(self: *Self, inst: Air.Inst.Index) !void {
const un_op = self.air.instructions.items(.data)[inst].un_op;
+
+ try self.spillCompareFlagsIfOccupied();
+ self.compare_flags_inst = inst;
+
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
const operand = try self.resolveInst(un_op);
- break :result try self.isNull(operand);
+ const ty = self.air.typeOf(un_op);
+ break :result try self.isNull(ty, operand);
};
return self.finishAir(inst, result, .{ un_op, .none, .none });
}
@@ -2375,6 +2423,7 @@ fn airIsNullPtr(self: *Self, inst: Air.Inst.Index) !void {
const un_op = self.air.instructions.items(.data)[inst].un_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
const operand_ptr = try self.resolveInst(un_op);
+ const ptr_ty = self.air.typeOf(un_op);
const operand: MCValue = blk: {
if (self.reuseOperand(inst, un_op, 0, operand_ptr)) {
// The MCValue that holds the pointer can be re-used as the value.
@@ -2383,8 +2432,8 @@ fn airIsNullPtr(self: *Self, inst: Air.Inst.Index) !void {
break :blk try self.allocRegOrMem(inst, true);
}
};
- try self.load(operand, operand_ptr, self.air.typeOf(un_op));
- break :result try self.isNull(operand);
+ try self.load(operand, operand_ptr, ptr_ty);
+ break :result try self.isNull(ptr_ty.elemType(), operand);
};
return self.finishAir(inst, result, .{ un_op, .none, .none });
}
@@ -2393,7 +2442,8 @@ fn airIsNonNull(self: *Self, inst: Air.Inst.Index) !void {
const un_op = self.air.instructions.items(.data)[inst].un_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
const operand = try self.resolveInst(un_op);
- break :result try self.isNonNull(operand);
+ const ty = self.air.typeOf(un_op);
+ break :result try self.isNonNull(ty, operand);
};
return self.finishAir(inst, result, .{ un_op, .none, .none });
}
@@ -2402,6 +2452,7 @@ fn airIsNonNullPtr(self: *Self, inst: Air.Inst.Index) !void {
const un_op = self.air.instructions.items(.data)[inst].un_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
const operand_ptr = try self.resolveInst(un_op);
+ const ptr_ty = self.air.typeOf(un_op);
const operand: MCValue = blk: {
if (self.reuseOperand(inst, un_op, 0, operand_ptr)) {
// The MCValue that holds the pointer can be re-used as the value.
@@ -2410,8 +2461,8 @@ fn airIsNonNullPtr(self: *Self, inst: Air.Inst.Index) !void {
break :blk try self.allocRegOrMem(inst, true);
}
};
- try self.load(operand, operand_ptr, self.air.typeOf(un_op));
- break :result try self.isNonNull(operand);
+ try self.load(operand, operand_ptr, ptr_ty);
+ break :result try self.isNonNull(ptr_ty.elemType(), operand);
};
return self.finishAir(inst, result, .{ un_op, .none, .none });
}
@@ -2420,7 +2471,8 @@ fn airIsErr(self: *Self, inst: Air.Inst.Index) !void {
const un_op = self.air.instructions.items(.data)[inst].un_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
const operand = try self.resolveInst(un_op);
- break :result try self.isErr(operand);
+ const ty = self.air.typeOf(un_op);
+ break :result try self.isErr(ty, operand);
};
return self.finishAir(inst, result, .{ un_op, .none, .none });
}
@@ -2429,6 +2481,7 @@ fn airIsErrPtr(self: *Self, inst: Air.Inst.Index) !void {
const un_op = self.air.instructions.items(.data)[inst].un_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
const operand_ptr = try self.resolveInst(un_op);
+ const ptr_ty = self.air.typeOf(un_op);
const operand: MCValue = blk: {
if (self.reuseOperand(inst, un_op, 0, operand_ptr)) {
// The MCValue that holds the pointer can be re-used as the value.
@@ -2437,8 +2490,8 @@ fn airIsErrPtr(self: *Self, inst: Air.Inst.Index) !void {
break :blk try self.allocRegOrMem(inst, true);
}
};
- try self.load(operand, operand_ptr, self.air.typeOf(un_op));
- break :result try self.isErr(operand);
+ try self.load(operand, operand_ptr, ptr_ty);
+ break :result try self.isErr(ptr_ty.elemType(), operand);
};
return self.finishAir(inst, result, .{ un_op, .none, .none });
}
@@ -2447,7 +2500,8 @@ fn airIsNonErr(self: *Self, inst: Air.Inst.Index) !void {
const un_op = self.air.instructions.items(.data)[inst].un_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
const operand = try self.resolveInst(un_op);
- break :result try self.isNonErr(operand);
+ const ty = self.air.typeOf(un_op);
+ break :result try self.isNonErr(ty, operand);
};
return self.finishAir(inst, result, .{ un_op, .none, .none });
}
@@ -2456,6 +2510,7 @@ fn airIsNonErrPtr(self: *Self, inst: Air.Inst.Index) !void {
const un_op = self.air.instructions.items(.data)[inst].un_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
const operand_ptr = try self.resolveInst(un_op);
+ const ptr_ty = self.air.typeOf(un_op);
const operand: MCValue = blk: {
if (self.reuseOperand(inst, un_op, 0, operand_ptr)) {
// The MCValue that holds the pointer can be re-used as the value.
@@ -2464,8 +2519,8 @@ fn airIsNonErrPtr(self: *Self, inst: Air.Inst.Index) !void {
break :blk try self.allocRegOrMem(inst, true);
}
};
- try self.load(operand, operand_ptr, self.air.typeOf(un_op));
- break :result try self.isNonErr(operand);
+ try self.load(operand, operand_ptr, ptr_ty);
+ break :result try self.isNonErr(ptr_ty.elemType(), operand);
};
return self.finishAir(inst, result, .{ un_op, .none, .none });
}
@@ -3365,31 +3420,32 @@ fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue {
}
},
.ErrorSet => {
- switch (typed_value.val.tag()) {
- .@"error" => {
- const err_name = typed_value.val.castTag(.@"error").?.data.name;
- const module = self.bin_file.options.module.?;
- const global_error_set = module.global_error_set;
- const error_index = global_error_set.get(err_name).?;
- return MCValue{ .immediate = error_index };
- },
- else => {
- // In this case we are rendering an error union which has a 0 bits payload.
- return MCValue{ .immediate = 0 };
- },
- }
+ const err_name = typed_value.val.castTag(.@"error").?.data.name;
+ const module = self.bin_file.options.module.?;
+ const global_error_set = module.global_error_set;
+ const error_index = global_error_set.get(err_name).?;
+ return MCValue{ .immediate = error_index };
},
.ErrorUnion => {
const error_type = typed_value.ty.errorUnionSet();
const payload_type = typed_value.ty.errorUnionPayload();
- const sub_val = typed_value.val.castTag(.eu_payload).?.data;
- if (!payload_type.hasCodeGenBits()) {
- // We use the error type directly as the type.
- return self.genTypedValue(.{ .ty = error_type, .val = sub_val });
- }
+ if (typed_value.val.castTag(.eu_payload)) |pl| {
+ if (!payload_type.hasCodeGenBits()) {
+ // We use the error type directly as the type.
+ return MCValue{ .immediate = 0 };
+ }
- return self.fail("TODO implement error union const of type '{}'", .{typed_value.ty});
+ _ = pl;
+ return self.fail("TODO implement error union const of type '{}' (non-error)", .{typed_value.ty});
+ } else {
+ if (!payload_type.hasCodeGenBits()) {
+ // We use the error type directly as the type.
+ return self.genTypedValue(.{ .ty = error_type, .val = typed_value.val });
+ }
+
+ return self.fail("TODO implement error union const of type '{}' (error)", .{typed_value.ty});
+ }
},
else => return self.fail("TODO implement const of type '{}'", .{typed_value.ty}),
}
diff --git a/test/stage2/arm.zig b/test/stage2/arm.zig
@@ -568,4 +568,73 @@ pub fn addCases(ctx: *TestContext) !void {
"",
);
}
+
+ {
+ var case = ctx.exe("optionals", linux_arm);
+ case.addCompareOutput(
+ \\var x: u32 = 42;
+ \\
+ \\pub fn main() void {
+ \\ var p: ?*u32 = null;
+ \\ assert(p == null);
+ \\ p = &x;
+ \\ assert(p != null);
+ \\}
+ \\
+ \\fn assert(ok: bool) void {
+ \\ if (!ok) unreachable;
+ \\}
+ ,
+ "",
+ );
+ }
+
+ {
+ var case = ctx.exe("errors", linux_arm);
+ case.addCompareOutput(
+ \\pub fn main() void {
+ \\ foo() catch print();
+ \\}
+ \\
+ \\fn foo() anyerror!void {}
+ \\
+ \\fn print() void {
+ \\ asm volatile ("svc #0"
+ \\ :
+ \\ : [number] "{r7}" (4),
+ \\ [arg1] "{r0}" (1),
+ \\ [arg2] "{r1}" (@ptrToInt("Hello, World!\n")),
+ \\ [arg3] "{r2}" ("Hello, World!\n".len),
+ \\ : "memory"
+ \\ );
+ \\ return;
+ \\}
+ ,
+ "",
+ );
+
+ case.addCompareOutput(
+ \\pub fn main() void {
+ \\ foo() catch print();
+ \\}
+ \\
+ \\fn foo() anyerror!void {
+ \\ return error.Test;
+ \\}
+ \\
+ \\fn print() void {
+ \\ asm volatile ("svc #0"
+ \\ :
+ \\ : [number] "{r7}" (4),
+ \\ [arg1] "{r0}" (1),
+ \\ [arg2] "{r1}" (@ptrToInt("Hello, World!\n")),
+ \\ [arg3] "{r2}" ("Hello, World!\n".len),
+ \\ : "memory"
+ \\ );
+ \\ return;
+ \\}
+ ,
+ "Hello, World!\n",
+ );
+ }
}