From 2e15a404e287982b261c14240db44af9175f765a Mon Sep 17 00:00:00 2001 From: Scibuild <30519309+Scibuild@users.noreply.github.com> Date: Thu, 18 Nov 2021 08:26:15 +1100 Subject: [PATCH 1/2] C backend: errors and optionals * bitcast treats all pointers as pointers * correctly unwrapping error unions with pointers * equality operators for primitive optional types --- src/codegen/c.zig | 60 ++++++++++++++++++++++++++++----- test/behavior.zig | 5 ++- test/behavior/optional.zig | 19 ----------- test/behavior/optional_llvm.zig | 23 +++++++++++++ 4 files changed, 79 insertions(+), 28 deletions(-) create mode 100644 test/behavior/optional_llvm.zig diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 3aef5a8f922aaf2a6f0acb6f089880cb22c312f1..6f857abff0b022de1862b3a2934d10ccb7d79bd1 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1162,12 +1162,12 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .slice => try airSlice(f, inst), - .cmp_eq => try airBinOp(f, inst, " == "), + .cmp_eq => try airEquality(f, inst, .cmp_eq), .cmp_gt => try airBinOp(f, inst, " > "), .cmp_gte => try airBinOp(f, inst, " >= "), .cmp_lt => try airBinOp(f, inst, " < "), .cmp_lte => try airBinOp(f, inst, " <= "), - .cmp_neq => try airBinOp(f, inst, " != "), + .cmp_neq => try airEquality(f, inst, .cmp_neq), // bool_and and bool_or are non-short-circuit operations .bool_and => try airBinOp(f, inst, " & "), @@ -1257,9 +1257,9 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .slice_elem_ptr => try airSliceElemPtr(f, inst), .array_elem_val => try airArrayElemVal(f, inst), - .unwrap_errunion_payload => try airUnwrapErrUnionPay(f, inst), + .unwrap_errunion_payload => try airUnwrapErrUnionPay(f, inst, ""), .unwrap_errunion_err => try airUnwrapErrUnionErr(f, inst), - .unwrap_errunion_payload_ptr => try airUnwrapErrUnionPay(f, inst), + .unwrap_errunion_payload_ptr => try airUnwrapErrUnionPay(f, inst, "&"), .unwrap_errunion_err_ptr => try airUnwrapErrUnionErr(f, inst), .wrap_errunion_payload => try airWrapErrUnionPay(f, inst), .wrap_errunion_err => try airWrapErrUnionErr(f, inst), @@ -1908,6 +1908,51 @@ fn airBinOp(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue return local; } +fn airEquality(f: *Function, inst: Air.Inst.Index, op: Air.Inst.Tag) !CValue { + if (f.liveness.isUnused(inst)) + return CValue.none; + + const bin_op = f.air.instructions.items(.data)[inst].bin_op; + const lhs = try f.resolveInst(bin_op.lhs); + const rhs = try f.resolveInst(bin_op.rhs); + + const writer = f.object.writer(); + const inst_ty = f.air.typeOfIndex(inst); + const local = try f.allocLocal(inst_ty, .Const); + + try writer.writeAll(" = "); + + const lhs_ty = f.air.typeOf(bin_op.lhs); + if (lhs_ty.tag() == .optional) { + // (A && B) || (C && (A == B)) + // A = lhs.is_null ; B = rhs.is_null ; C = rhs.payload == lhs.payload + + try writer.writeAll(if (op == .cmp_eq) "((" else "!(("); + try f.writeCValue(writer, lhs); + try writer.writeAll(".is_null && "); + try f.writeCValue(writer, rhs); + try writer.writeAll(".is_null) || ("); + try f.writeCValue(writer, lhs); + try writer.writeAll(".payload == "); + try f.writeCValue(writer, rhs); + try writer.writeAll(".payload && "); + try f.writeCValue(writer, lhs); + try writer.writeAll(".is_null == "); + try f.writeCValue(writer, rhs); + try writer.writeAll(".is_null));\n"); + + return local; + } + + const operator = if (op == .cmp_eq) "==" else "!="; + try f.writeCValue(writer, lhs); + try writer.print("{s}", .{operator}); + try f.writeCValue(writer, rhs); + try writer.writeAll(";\n"); + + return local; +} + fn airPtrAddSub(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; @@ -2104,8 +2149,8 @@ fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue { const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); - if (inst_ty.zigTypeTag() == .Pointer and - f.air.typeOf(ty_op.operand).zigTypeTag() == .Pointer) + if (inst_ty.isPtrAtRuntime() and + f.air.typeOf(ty_op.operand).isPtrAtRuntime()) { const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = ("); @@ -2503,7 +2548,7 @@ fn airUnwrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue { return local; } -fn airUnwrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue { +fn airUnwrapErrUnionPay(f: *Function, inst: Air.Inst.Index, maybe_addrof: []const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; @@ -2519,7 +2564,6 @@ fn airUnwrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue { const inst_ty = f.air.typeOfIndex(inst); const maybe_deref = if (operand_ty.zigTypeTag() == .Pointer) "->" else "."; - const maybe_addrof = if (inst_ty.zigTypeTag() == .Pointer) "&" else ""; const local = try f.allocLocal(inst_ty, .Const); try writer.print(" = {s}(", .{maybe_addrof}); diff --git a/test/behavior.zig b/test/behavior.zig index 4c768e304ea07985f32e0c4ebf45c495ec1b8b34..c6d090b28aeaee7d790b7dc5010564a544025089 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -41,6 +41,8 @@ test { _ = @import("behavior/member_func.zig"); _ = @import("behavior/translate_c_macros.zig"); _ = @import("behavior/generics.zig"); + _ = @import("behavior/error.zig"); + _ = @import("behavior/optional.zig"); if (builtin.object_format != .c) { // Tests that pass for stage1 and stage2 but not the C backend. @@ -55,6 +57,7 @@ test { _ = @import("behavior/bugs/2006.zig"); _ = @import("behavior/bugs/3112.zig"); _ = @import("behavior/cast_llvm.zig"); + _ = @import("behavior/error.zig"); _ = @import("behavior/eval.zig"); _ = @import("behavior/floatop.zig"); _ = @import("behavior/fn.zig"); @@ -63,7 +66,7 @@ test { _ = @import("behavior/math.zig"); _ = @import("behavior/maximum_minimum.zig"); _ = @import("behavior/null_llvm.zig"); - _ = @import("behavior/optional.zig"); + _ = @import("behavior/optional_llvm.zig"); _ = @import("behavior/popcount.zig"); _ = @import("behavior/saturating_arithmetic.zig"); _ = @import("behavior/sizeof_and_typeof.zig"); diff --git a/test/behavior/optional.zig b/test/behavior/optional.zig index 7bd88ee4a017e5f01c91ba136711b2d7ba36cf82..6c78debc7d889cc65615d268fdf006df4ec8806c 100644 --- a/test/behavior/optional.zig +++ b/test/behavior/optional.zig @@ -18,25 +18,6 @@ test "passing an optional integer as a parameter" { comptime try expect(S.entry()); } -test "self-referential struct through a slice of optional" { - const S = struct { - const Node = struct { - children: []?Node, - data: ?u8, - - fn new() Node { - return Node{ - .children = undefined, - .data = null, - }; - } - }; - }; - - var n = S.Node.new(); - try expect(n.data == null); -} - pub const EmptyStruct = struct {}; test "optional pointer to size zero struct" { diff --git a/test/behavior/optional_llvm.zig b/test/behavior/optional_llvm.zig new file mode 100644 index 0000000000000000000000000000000000000000..d27c72dd1d29ab9c637791c093ba99bc91c159df --- /dev/null +++ b/test/behavior/optional_llvm.zig @@ -0,0 +1,23 @@ +const std = @import("std"); +const testing = std.testing; +const expect = testing.expect; +const expectEqual = testing.expectEqual; + +test "self-referential struct through a slice of optional" { + const S = struct { + const Node = struct { + children: []?Node, + data: ?u8, + + fn new() Node { + return Node{ + .children = undefined, + .data = null, + }; + } + }; + }; + + var n = S.Node.new(); + try expect(n.data == null); +} -- 2.54.0 From 7ee02b5e70c99159e861cd94adac4707a3315959 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 23 Nov 2021 13:54:08 -0700 Subject: [PATCH 2/2] C backend: avoid branching multiple times on AIR tag for cmp_eq and cmp_neq. --- src/codegen/c.zig | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 6f857abff0b022de1862b3a2934d10ccb7d79bd1..5695d22d457b0d28ee549819c60f80226adff41c 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1162,12 +1162,13 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .slice => try airSlice(f, inst), - .cmp_eq => try airEquality(f, inst, .cmp_eq), .cmp_gt => try airBinOp(f, inst, " > "), .cmp_gte => try airBinOp(f, inst, " >= "), .cmp_lt => try airBinOp(f, inst, " < "), .cmp_lte => try airBinOp(f, inst, " <= "), - .cmp_neq => try airEquality(f, inst, .cmp_neq), + + .cmp_eq => try airEquality(f, inst, "((", "=="), + .cmp_neq => try airEquality(f, inst, "!((", "!="), // bool_and and bool_or are non-short-circuit operations .bool_and => try airBinOp(f, inst, " & "), @@ -1908,9 +1909,13 @@ fn airBinOp(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue return local; } -fn airEquality(f: *Function, inst: Air.Inst.Index, op: Air.Inst.Tag) !CValue { - if (f.liveness.isUnused(inst)) - return CValue.none; +fn airEquality( + f: *Function, + inst: Air.Inst.Index, + negate_prefix: []const u8, + eq_op_str: []const u8, +) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const lhs = try f.resolveInst(bin_op.lhs); @@ -1927,7 +1932,7 @@ fn airEquality(f: *Function, inst: Air.Inst.Index, op: Air.Inst.Tag) !CValue { // (A && B) || (C && (A == B)) // A = lhs.is_null ; B = rhs.is_null ; C = rhs.payload == lhs.payload - try writer.writeAll(if (op == .cmp_eq) "((" else "!(("); + try writer.writeAll(negate_prefix); try f.writeCValue(writer, lhs); try writer.writeAll(".is_null && "); try f.writeCValue(writer, rhs); @@ -1944,9 +1949,8 @@ fn airEquality(f: *Function, inst: Air.Inst.Index, op: Air.Inst.Tag) !CValue { return local; } - const operator = if (op == .cmp_eq) "==" else "!="; try f.writeCValue(writer, lhs); - try writer.print("{s}", .{operator}); + try writer.writeAll(eq_op_str); try f.writeCValue(writer, rhs); try writer.writeAll(";\n"); -- 2.54.0