From a9a91a5d49070acaa783d9d65f34a652659160c9 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Mon, 21 Mar 2022 12:18:58 -0700 Subject: [PATCH] stage2 CBE: Improve support for unions and error sets This includes various fixes/improvements to the C backend to improve error/union support. It also fixes up our handling of decls, where some decls were not correctly marked alive. --- src/codegen/c.zig | 140 ++++++++++++++++++++++++++++---------- test/behavior/error.zig | 19 ++++-- test/behavior/ptrcast.zig | 62 +++++++++++++++-- test/behavior/union.zig | 6 -- 4 files changed, 172 insertions(+), 55 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index c62abdb3f6..16558c6e04 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -375,8 +375,6 @@ pub const DeclGen = struct { val: Value, decl: *Decl, ) error{ OutOfMemory, AnalysisFail }!void { - decl.markAlive(); - const target = dg.module.getTarget(); if (ty.isSlice()) { @@ -461,12 +459,14 @@ pub const DeclGen = struct { } } - // Renders a "child" pointer (e.g. ElemPtr, FieldPtr) by recursing - // to the root decl/variable that acts as its parent + // Renders a "parent" pointer by recursing to the root decl/variable + // that its contents are defined with respect to. // - // Used for .elem_ptr, .field_ptr, .opt_payload_ptr, .eu_payload_ptr, since - // the Type of their container cannot be retrieved from their own Type - fn renderChildPtr(dg: *DeclGen, writer: anytype, ptr_val: Value) error{ OutOfMemory, AnalysisFail }!Type { + // Used for .elem_ptr, .field_ptr, .opt_payload_ptr, .eu_payload_ptr + fn renderParentPtr(dg: *DeclGen, writer: anytype, ptr_val: Value, ptr_ty: Type) error{ OutOfMemory, AnalysisFail }!void { + try writer.writeByte('('); + try dg.renderTypecast(writer, ptr_ty); + try writer.writeByte(')'); switch (ptr_val.tag()) { .decl_ref_mut, .decl_ref, .variable => { const decl = switch (ptr_val.tag()) { @@ -475,16 +475,12 @@ pub const DeclGen = struct { .variable => ptr_val.castTag(.variable).?.data.owner_decl, else => unreachable, }; - try dg.renderDeclName(writer, decl); - return decl.ty; + try dg.renderDeclValue(writer, ptr_ty, ptr_val, decl); }, .field_ptr => { const field_ptr = ptr_val.castTag(.field_ptr).?.data; + const container_ty = field_ptr.container_ty; const index = field_ptr.field_index; - - try writer.writeAll("&("); - const container_ty = try dg.renderChildPtr(writer, field_ptr.container_ptr); - const field_name = switch (container_ty.zigTypeTag()) { .Struct => container_ty.structFields().keys()[index], .Union => container_ty.unionFields().keys()[index], @@ -495,19 +491,48 @@ pub const DeclGen = struct { .Union => container_ty.unionFields().values()[index].ty, else => unreachable, }; - try writer.print(").{ }", .{fmtIdent(field_name)}); + var container_ptr_ty_pl: Type.Payload.ElemType = .{ + .base = .{ .tag = .c_mut_pointer }, + .data = field_ptr.container_ty, + }; + const container_ptr_ty = Type.initPayload(&container_ptr_ty_pl.base); - return field_ty; + if (field_ty.hasRuntimeBitsIgnoreComptime()) { + try writer.writeAll("&("); + try dg.renderParentPtr(writer, field_ptr.container_ptr, container_ptr_ty); + if (field_ptr.container_ty.tag() == .union_tagged) { + try writer.print(")->payload.{ }", .{fmtIdent(field_name)}); + } else { + try writer.print(")->{ }", .{fmtIdent(field_name)}); + } + } else { + try dg.renderParentPtr(writer, field_ptr.container_ptr, field_ty); + } }, .elem_ptr => { const elem_ptr = ptr_val.castTag(.elem_ptr).?.data; + var elem_ptr_ty_pl: Type.Payload.ElemType = .{ + .base = .{ .tag = .c_mut_pointer }, + .data = elem_ptr.elem_ty, + }; + const elem_ptr_ty = Type.initPayload(&elem_ptr_ty_pl.base); + try writer.writeAll("&("); - const container_ty = try dg.renderChildPtr(writer, elem_ptr.array_ptr); + try dg.renderParentPtr(writer, elem_ptr.array_ptr, elem_ptr_ty); try writer.print(")[{d}]", .{elem_ptr.index}); - return container_ty.childType(); }, - .opt_payload_ptr => return dg.fail("implement renderChildPtr for optional payload", .{}), - .eu_payload_ptr => return dg.fail("implement renderChildPtr for error union payload", .{}), + .opt_payload_ptr, .eu_payload_ptr => { + const payload_ptr = ptr_val.cast(Value.Payload.PayloadPtr).?.data; + var container_ptr_ty_pl: Type.Payload.ElemType = .{ + .base = .{ .tag = .c_mut_pointer }, + .data = payload_ptr.container_ty, + }; + const container_ptr_ty = Type.initPayload(&container_ptr_ty_pl.base); + + try writer.writeAll("&("); + try dg.renderParentPtr(writer, payload_ptr.container_ptr, container_ptr_ty); + try writer.writeAll(")->payload"); + }, else => unreachable, } } @@ -559,6 +584,13 @@ pub const DeclGen = struct { .Int => switch (val.tag()) { .int_big_positive => try dg.renderBigIntConst(writer, val.castTag(.int_big_positive).?.asBigInt(), ty.isSignedInt()), .int_big_negative => try dg.renderBigIntConst(writer, val.castTag(.int_big_negative).?.asBigInt(), true), + .field_ptr, + .elem_ptr, + .opt_payload_ptr, + .eu_payload_ptr, + .decl_ref_mut, + .decl_ref, + => try dg.renderParentPtr(writer, val, ty), else => { if (ty.isSignedInt()) return writer.print("{d}", .{val.toSignedInt()}); @@ -586,10 +618,6 @@ pub const DeclGen = struct { // to the assigned pointer type. Note this is just a hack to fix warnings from ordered comparisons (<, >, etc) // between pointers and 0, which is an extension to begin with. .zero => try writer.writeByte('0'), - .decl_ref => { - const decl = val.castTag(.decl_ref).?.data; - return dg.renderDeclValue(writer, ty, val, decl); - }, .variable => { const decl = val.castTag(.variable).?.data.owner_decl; return dg.renderDeclValue(writer, ty, val, decl); @@ -606,9 +634,6 @@ pub const DeclGen = struct { try dg.renderValue(writer, Type.usize, slice.len); try writer.writeAll("}"); }, - .field_ptr, .elem_ptr, .opt_payload_ptr, .eu_payload_ptr => { - _ = try dg.renderChildPtr(writer, val); - }, .function => { const func = val.castTag(.function).?.data; try dg.renderDeclName(writer, func.owner_decl); @@ -622,6 +647,13 @@ pub const DeclGen = struct { try dg.renderTypecast(writer, ty); try writer.print(")0x{x}u)", .{val.toUnsignedInt(target)}); }, + .field_ptr, + .elem_ptr, + .opt_payload_ptr, + .eu_payload_ptr, + .decl_ref_mut, + .decl_ref, + => try dg.renderParentPtr(writer, val, ty), else => unreachable, }, .Array => { @@ -1326,7 +1358,7 @@ pub const DeclGen = struct { return w.writeAll(name); }, .Struct => { - const name = dg.getTypedefName(t) orelse if (t.isTuple()) + const name = dg.getTypedefName(t) orelse if (t.isTuple() or t.tag() == .anon_struct) try dg.renderTupleTypedef(t) else try dg.renderStructTypedef(t); @@ -1346,11 +1378,11 @@ pub const DeclGen = struct { try dg.renderType(w, int_tag_ty); }, + .Opaque => return w.writeAll("void"), .Frame, .AnyFrame, .Vector, - .Opaque, => |tag| return dg.fail("TODO: C backend: implement value of type {s}", .{ @tagName(tag), }), @@ -1497,6 +1529,8 @@ pub const DeclGen = struct { } fn renderDeclName(dg: DeclGen, writer: anytype, decl: *Decl) !void { + decl.markAlive(); + if (dg.module.decl_exports.get(decl)) |exports| { return writer.writeAll(exports[0].options.name); } else if (decl.val.tag() == .extern_fn) { @@ -3188,7 +3222,7 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc field_name = fields.keys()[index]; field_val_ty = fields.values()[index].ty; }, - .tuple => { + .tuple, .anon_struct => { const tuple = struct_ty.tupleFields(); if (tuple.values[index].tag() != .unreachable_value) return CValue.none; @@ -3203,9 +3237,17 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); - try writer.print(" = &", .{}); - try f.writeCValueDeref(writer, struct_ptr); - try writer.print(".{s}{ };\n", .{ payload, fmtIdent(field_name) }); + if (field_val_ty.hasRuntimeBitsIgnoreComptime()) { + try writer.writeAll(" = &"); + try f.writeCValueDeref(writer, struct_ptr); + try writer.print(".{s}{ };\n", .{ payload, fmtIdent(field_name) }); + } else { + try writer.writeAll(" = ("); + try f.renderTypecast(writer, inst_ty); + try writer.writeByte(')'); + try f.writeCValue(writer, struct_ptr); + try writer.writeAll(";\n"); + } return local; } @@ -3223,7 +3265,7 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue { const field_name = switch (struct_ty.tag()) { .@"struct" => struct_ty.structFields().keys()[extra.field_index], .@"union", .union_tagged => struct_ty.unionFields().keys()[extra.field_index], - .tuple => blk: { + .tuple, .anon_struct => blk: { const tuple = struct_ty.tupleFields(); if (tuple.values[extra.field_index].tag() != .unreachable_value) return CValue.none; @@ -3348,8 +3390,36 @@ fn airWrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue { } fn airErrUnionPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue { - _ = inst; - return f.fail("TODO: C backend: implement airErrUnionPayloadPtrSet", .{}); + const writer = f.object.writer(); + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const operand = try f.resolveInst(ty_op.operand); + const error_union_ty = f.air.typeOf(ty_op.operand).childType(); + + const error_ty = error_union_ty.errorUnionSet(); + const payload_ty = error_union_ty.errorUnionPayload(); + + // First, set the non-error value. + if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { + try f.writeCValueDeref(writer, operand); + try writer.writeAll(" = "); + try f.object.dg.renderValue(writer, error_ty, Value.zero); + try writer.writeAll(";\n "); + + return operand; + } + try f.writeCValueDeref(writer, operand); + try writer.writeAll(".error = "); + try f.object.dg.renderValue(writer, error_ty, Value.zero); + try writer.writeAll(";\n"); + + // Then return the payload pointer (only if it is used) + if (f.liveness.isUnused(inst)) return CValue.none; + + const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const); + try writer.writeAll(" = &("); + try f.writeCValueDeref(writer, operand); + try writer.writeAll(").payload;\n"); + return local; } fn airWrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue { diff --git a/test/behavior/error.zig b/test/behavior/error.zig index f24804c581..fab9ce6b40 100644 --- a/test/behavior/error.zig +++ b/test/behavior/error.zig @@ -1,10 +1,20 @@ const builtin = @import("builtin"); const std = @import("std"); const expect = std.testing.expect; -const expectError = std.testing.expectError; const expectEqual = std.testing.expectEqual; const mem = std.mem; +/// A more basic implementation of std.testing.expectError which +/// does not require formatter/printing support +fn expectError(expected_err: anyerror, observed_err_union: anytype) !void { + if (observed_err_union) { + return error.TestExpectedError; + } else |err| if (err == expected_err) { + return; // Success + } + return error.TestExpectedError; +} + test "error values" { const a = @errorToInt(error.err1); const b = @errorToInt(error.err2); @@ -329,7 +339,6 @@ fn intLiteral(str: []const u8) !?i64 { test "nested error union function call in optional unwrap" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO @@ -377,7 +386,6 @@ test "nested error union function call in optional unwrap" { } test "return function call to error set from error union function" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -410,7 +418,6 @@ test "optional error set is the same size as error set" { } test "nested catch" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -471,7 +478,6 @@ test "function pointer with return type that is error union with payload which i test "return result loc as peer result loc in inferred error set function" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -503,7 +509,6 @@ test "return result loc as peer result loc in inferred error set function" { } test "error payload type is correctly resolved" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -519,7 +524,7 @@ test "error payload type is correctly resolved" { } }; - try expectEqual(MyIntWrapper{ .x = 42 }, try MyIntWrapper.create()); + try expect(std.meta.eql(MyIntWrapper{ .x = 42 }, try MyIntWrapper.create())); } test "error union comptime caching" { diff --git a/test/behavior/ptrcast.zig b/test/behavior/ptrcast.zig index 1bceb89412..71b2281564 100644 --- a/test/behavior/ptrcast.zig +++ b/test/behavior/ptrcast.zig @@ -39,7 +39,6 @@ fn testReinterpretWithOffsetAndNoWellDefinedLayout() !void { } test "reinterpret bytes inside auto-layout struct as integer with nonzero offset" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO @@ -80,9 +79,9 @@ fn testReinterpretBytesAsExternStruct() !void { try expect(val == 5); } -test "reinterpret bytes of an extern struct into another" { +test "reinterpret bytes of an extern struct (with under-aligned fields) into another" { if (builtin.zig_backend == .stage1) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO: Under-aligned fields are not yet supported in the CBE if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO try testReinterpretExternStructAsExternStruct(); @@ -106,12 +105,39 @@ fn testReinterpretExternStructAsExternStruct() !void { try expect(val == 5); } -test "lower reinterpreted comptime field ptr" { +test "reinterpret bytes of an extern struct into another" { + if (builtin.zig_backend == .stage1) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + + try testReinterpretOverAlignedExternStructAsExternStruct(); + comptime try testReinterpretOverAlignedExternStructAsExternStruct(); +} + +fn testReinterpretOverAlignedExternStructAsExternStruct() !void { + const S1 = extern struct { + a: u32, + b: u32, + c: u8, + }; + comptime var bytes: S1 = .{ .a = 0, .b = 0, .c = 5 }; + + const S2 = extern struct { + a0: u32, + a1: u16, + a2: u16, + c: u8, + }; + var ptr = @ptrCast(*const S2, &bytes); + var val = ptr.c; + try expect(val == 5); +} + +test "lower reinterpreted comptime field ptr (with under-aligned fields)" { if (builtin.zig_backend == .stage1) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO: CBE does not yet support under-aligned fields if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO // Test lowering a field ptr @@ -131,8 +157,31 @@ test "lower reinterpreted comptime field ptr" { try expect(val2.* == 5); } +test "lower reinterpreted comptime field ptr" { + if (builtin.zig_backend == .stage1) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + + // Test lowering a field ptr + comptime var bytes align(4) = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 }; + const S = extern struct { + a: u32, + c: u8, + }; + comptime var ptr = @ptrCast(*const S, &bytes); + var val = &ptr.c; + try expect(val.* == 5); + + // Test lowering an elem ptr + comptime var src_value = S{ .a = 15, .c = 5 }; + comptime var ptr2 = @ptrCast(*[@sizeOf(S)]u8, &src_value); + var val2 = &ptr2[4]; + try expect(val2.* == 5); +} + test "reinterpret struct field at comptime" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO @@ -164,7 +213,6 @@ test "comptime ptrcast keeps larger alignment" { } test "implicit optional pointer to optional anyopaque pointer" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO diff --git a/test/behavior/union.zig b/test/behavior/union.zig index 642a1f6279..0541817145 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -436,8 +436,6 @@ test "global union with single field is correctly initialized" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO - glbl = Foo1{ .f = @typeInfo(Foo1).Union.fields[0].field_type{ .x = 123 }, }; @@ -456,8 +454,6 @@ test "initialize global array of union" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; - glbl_array[1] = FooUnion{ .U1 = 2 }; glbl_array[0] = FooUnion{ .U0 = 1 }; try expect(glbl_array[0].U0 == 1); @@ -1033,7 +1029,6 @@ test "switching on non exhaustive union" { } test "containers with single-field enums" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO @@ -1113,7 +1108,6 @@ test "union enum type gets a separate scope" { } test "global variable struct contains union initialized to non-most-aligned field" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO