Skip to content

Commit a9a91a5

Browse files
topolarityandrewrk
authored andcommitted
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.
1 parent 2f9264d commit a9a91a5

File tree

4 files changed

+172
-55
lines changed

4 files changed

+172
-55
lines changed

src/codegen/c.zig

Lines changed: 105 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -375,8 +375,6 @@ pub const DeclGen = struct {
375375
val: Value,
376376
decl: *Decl,
377377
) error{ OutOfMemory, AnalysisFail }!void {
378-
decl.markAlive();
379-
380378
const target = dg.module.getTarget();
381379

382380
if (ty.isSlice()) {
@@ -461,12 +459,14 @@ pub const DeclGen = struct {
461459
}
462460
}
463461

464-
// Renders a "child" pointer (e.g. ElemPtr, FieldPtr) by recursing
465-
// to the root decl/variable that acts as its parent
462+
// Renders a "parent" pointer by recursing to the root decl/variable
463+
// that its contents are defined with respect to.
466464
//
467-
// Used for .elem_ptr, .field_ptr, .opt_payload_ptr, .eu_payload_ptr, since
468-
// the Type of their container cannot be retrieved from their own Type
469-
fn renderChildPtr(dg: *DeclGen, writer: anytype, ptr_val: Value) error{ OutOfMemory, AnalysisFail }!Type {
465+
// Used for .elem_ptr, .field_ptr, .opt_payload_ptr, .eu_payload_ptr
466+
fn renderParentPtr(dg: *DeclGen, writer: anytype, ptr_val: Value, ptr_ty: Type) error{ OutOfMemory, AnalysisFail }!void {
467+
try writer.writeByte('(');
468+
try dg.renderTypecast(writer, ptr_ty);
469+
try writer.writeByte(')');
470470
switch (ptr_val.tag()) {
471471
.decl_ref_mut, .decl_ref, .variable => {
472472
const decl = switch (ptr_val.tag()) {
@@ -475,16 +475,12 @@ pub const DeclGen = struct {
475475
.variable => ptr_val.castTag(.variable).?.data.owner_decl,
476476
else => unreachable,
477477
};
478-
try dg.renderDeclName(writer, decl);
479-
return decl.ty;
478+
try dg.renderDeclValue(writer, ptr_ty, ptr_val, decl);
480479
},
481480
.field_ptr => {
482481
const field_ptr = ptr_val.castTag(.field_ptr).?.data;
482+
const container_ty = field_ptr.container_ty;
483483
const index = field_ptr.field_index;
484-
485-
try writer.writeAll("&(");
486-
const container_ty = try dg.renderChildPtr(writer, field_ptr.container_ptr);
487-
488484
const field_name = switch (container_ty.zigTypeTag()) {
489485
.Struct => container_ty.structFields().keys()[index],
490486
.Union => container_ty.unionFields().keys()[index],
@@ -495,19 +491,48 @@ pub const DeclGen = struct {
495491
.Union => container_ty.unionFields().values()[index].ty,
496492
else => unreachable,
497493
};
498-
try writer.print(").{ }", .{fmtIdent(field_name)});
494+
var container_ptr_ty_pl: Type.Payload.ElemType = .{
495+
.base = .{ .tag = .c_mut_pointer },
496+
.data = field_ptr.container_ty,
497+
};
498+
const container_ptr_ty = Type.initPayload(&container_ptr_ty_pl.base);
499499

500-
return field_ty;
500+
if (field_ty.hasRuntimeBitsIgnoreComptime()) {
501+
try writer.writeAll("&(");
502+
try dg.renderParentPtr(writer, field_ptr.container_ptr, container_ptr_ty);
503+
if (field_ptr.container_ty.tag() == .union_tagged) {
504+
try writer.print(")->payload.{ }", .{fmtIdent(field_name)});
505+
} else {
506+
try writer.print(")->{ }", .{fmtIdent(field_name)});
507+
}
508+
} else {
509+
try dg.renderParentPtr(writer, field_ptr.container_ptr, field_ty);
510+
}
501511
},
502512
.elem_ptr => {
503513
const elem_ptr = ptr_val.castTag(.elem_ptr).?.data;
514+
var elem_ptr_ty_pl: Type.Payload.ElemType = .{
515+
.base = .{ .tag = .c_mut_pointer },
516+
.data = elem_ptr.elem_ty,
517+
};
518+
const elem_ptr_ty = Type.initPayload(&elem_ptr_ty_pl.base);
519+
504520
try writer.writeAll("&(");
505-
const container_ty = try dg.renderChildPtr(writer, elem_ptr.array_ptr);
521+
try dg.renderParentPtr(writer, elem_ptr.array_ptr, elem_ptr_ty);
506522
try writer.print(")[{d}]", .{elem_ptr.index});
507-
return container_ty.childType();
508523
},
509-
.opt_payload_ptr => return dg.fail("implement renderChildPtr for optional payload", .{}),
510-
.eu_payload_ptr => return dg.fail("implement renderChildPtr for error union payload", .{}),
524+
.opt_payload_ptr, .eu_payload_ptr => {
525+
const payload_ptr = ptr_val.cast(Value.Payload.PayloadPtr).?.data;
526+
var container_ptr_ty_pl: Type.Payload.ElemType = .{
527+
.base = .{ .tag = .c_mut_pointer },
528+
.data = payload_ptr.container_ty,
529+
};
530+
const container_ptr_ty = Type.initPayload(&container_ptr_ty_pl.base);
531+
532+
try writer.writeAll("&(");
533+
try dg.renderParentPtr(writer, payload_ptr.container_ptr, container_ptr_ty);
534+
try writer.writeAll(")->payload");
535+
},
511536
else => unreachable,
512537
}
513538
}
@@ -559,6 +584,13 @@ pub const DeclGen = struct {
559584
.Int => switch (val.tag()) {
560585
.int_big_positive => try dg.renderBigIntConst(writer, val.castTag(.int_big_positive).?.asBigInt(), ty.isSignedInt()),
561586
.int_big_negative => try dg.renderBigIntConst(writer, val.castTag(.int_big_negative).?.asBigInt(), true),
587+
.field_ptr,
588+
.elem_ptr,
589+
.opt_payload_ptr,
590+
.eu_payload_ptr,
591+
.decl_ref_mut,
592+
.decl_ref,
593+
=> try dg.renderParentPtr(writer, val, ty),
562594
else => {
563595
if (ty.isSignedInt())
564596
return writer.print("{d}", .{val.toSignedInt()});
@@ -586,10 +618,6 @@ pub const DeclGen = struct {
586618
// to the assigned pointer type. Note this is just a hack to fix warnings from ordered comparisons (<, >, etc)
587619
// between pointers and 0, which is an extension to begin with.
588620
.zero => try writer.writeByte('0'),
589-
.decl_ref => {
590-
const decl = val.castTag(.decl_ref).?.data;
591-
return dg.renderDeclValue(writer, ty, val, decl);
592-
},
593621
.variable => {
594622
const decl = val.castTag(.variable).?.data.owner_decl;
595623
return dg.renderDeclValue(writer, ty, val, decl);
@@ -606,9 +634,6 @@ pub const DeclGen = struct {
606634
try dg.renderValue(writer, Type.usize, slice.len);
607635
try writer.writeAll("}");
608636
},
609-
.field_ptr, .elem_ptr, .opt_payload_ptr, .eu_payload_ptr => {
610-
_ = try dg.renderChildPtr(writer, val);
611-
},
612637
.function => {
613638
const func = val.castTag(.function).?.data;
614639
try dg.renderDeclName(writer, func.owner_decl);
@@ -622,6 +647,13 @@ pub const DeclGen = struct {
622647
try dg.renderTypecast(writer, ty);
623648
try writer.print(")0x{x}u)", .{val.toUnsignedInt(target)});
624649
},
650+
.field_ptr,
651+
.elem_ptr,
652+
.opt_payload_ptr,
653+
.eu_payload_ptr,
654+
.decl_ref_mut,
655+
.decl_ref,
656+
=> try dg.renderParentPtr(writer, val, ty),
625657
else => unreachable,
626658
},
627659
.Array => {
@@ -1326,7 +1358,7 @@ pub const DeclGen = struct {
13261358
return w.writeAll(name);
13271359
},
13281360
.Struct => {
1329-
const name = dg.getTypedefName(t) orelse if (t.isTuple())
1361+
const name = dg.getTypedefName(t) orelse if (t.isTuple() or t.tag() == .anon_struct)
13301362
try dg.renderTupleTypedef(t)
13311363
else
13321364
try dg.renderStructTypedef(t);
@@ -1346,11 +1378,11 @@ pub const DeclGen = struct {
13461378

13471379
try dg.renderType(w, int_tag_ty);
13481380
},
1381+
.Opaque => return w.writeAll("void"),
13491382

13501383
.Frame,
13511384
.AnyFrame,
13521385
.Vector,
1353-
.Opaque,
13541386
=> |tag| return dg.fail("TODO: C backend: implement value of type {s}", .{
13551387
@tagName(tag),
13561388
}),
@@ -1497,6 +1529,8 @@ pub const DeclGen = struct {
14971529
}
14981530

14991531
fn renderDeclName(dg: DeclGen, writer: anytype, decl: *Decl) !void {
1532+
decl.markAlive();
1533+
15001534
if (dg.module.decl_exports.get(decl)) |exports| {
15011535
return writer.writeAll(exports[0].options.name);
15021536
} else if (decl.val.tag() == .extern_fn) {
@@ -3188,7 +3222,7 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc
31883222
field_name = fields.keys()[index];
31893223
field_val_ty = fields.values()[index].ty;
31903224
},
3191-
.tuple => {
3225+
.tuple, .anon_struct => {
31923226
const tuple = struct_ty.tupleFields();
31933227
if (tuple.values[index].tag() != .unreachable_value) return CValue.none;
31943228

@@ -3203,9 +3237,17 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc
32033237
const inst_ty = f.air.typeOfIndex(inst);
32043238
const local = try f.allocLocal(inst_ty, .Const);
32053239

3206-
try writer.print(" = &", .{});
3207-
try f.writeCValueDeref(writer, struct_ptr);
3208-
try writer.print(".{s}{ };\n", .{ payload, fmtIdent(field_name) });
3240+
if (field_val_ty.hasRuntimeBitsIgnoreComptime()) {
3241+
try writer.writeAll(" = &");
3242+
try f.writeCValueDeref(writer, struct_ptr);
3243+
try writer.print(".{s}{ };\n", .{ payload, fmtIdent(field_name) });
3244+
} else {
3245+
try writer.writeAll(" = (");
3246+
try f.renderTypecast(writer, inst_ty);
3247+
try writer.writeByte(')');
3248+
try f.writeCValue(writer, struct_ptr);
3249+
try writer.writeAll(";\n");
3250+
}
32093251
return local;
32103252
}
32113253

@@ -3223,7 +3265,7 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue {
32233265
const field_name = switch (struct_ty.tag()) {
32243266
.@"struct" => struct_ty.structFields().keys()[extra.field_index],
32253267
.@"union", .union_tagged => struct_ty.unionFields().keys()[extra.field_index],
3226-
.tuple => blk: {
3268+
.tuple, .anon_struct => blk: {
32273269
const tuple = struct_ty.tupleFields();
32283270
if (tuple.values[extra.field_index].tag() != .unreachable_value) return CValue.none;
32293271

@@ -3348,8 +3390,36 @@ fn airWrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue {
33483390
}
33493391

33503392
fn airErrUnionPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue {
3351-
_ = inst;
3352-
return f.fail("TODO: C backend: implement airErrUnionPayloadPtrSet", .{});
3393+
const writer = f.object.writer();
3394+
const ty_op = f.air.instructions.items(.data)[inst].ty_op;
3395+
const operand = try f.resolveInst(ty_op.operand);
3396+
const error_union_ty = f.air.typeOf(ty_op.operand).childType();
3397+
3398+
const error_ty = error_union_ty.errorUnionSet();
3399+
const payload_ty = error_union_ty.errorUnionPayload();
3400+
3401+
// First, set the non-error value.
3402+
if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
3403+
try f.writeCValueDeref(writer, operand);
3404+
try writer.writeAll(" = ");
3405+
try f.object.dg.renderValue(writer, error_ty, Value.zero);
3406+
try writer.writeAll(";\n ");
3407+
3408+
return operand;
3409+
}
3410+
try f.writeCValueDeref(writer, operand);
3411+
try writer.writeAll(".error = ");
3412+
try f.object.dg.renderValue(writer, error_ty, Value.zero);
3413+
try writer.writeAll(";\n");
3414+
3415+
// Then return the payload pointer (only if it is used)
3416+
if (f.liveness.isUnused(inst)) return CValue.none;
3417+
3418+
const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const);
3419+
try writer.writeAll(" = &(");
3420+
try f.writeCValueDeref(writer, operand);
3421+
try writer.writeAll(").payload;\n");
3422+
return local;
33533423
}
33543424

33553425
fn airWrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue {

test/behavior/error.zig

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
const builtin = @import("builtin");
22
const std = @import("std");
33
const expect = std.testing.expect;
4-
const expectError = std.testing.expectError;
54
const expectEqual = std.testing.expectEqual;
65
const mem = std.mem;
76

7+
/// A more basic implementation of std.testing.expectError which
8+
/// does not require formatter/printing support
9+
fn expectError(expected_err: anyerror, observed_err_union: anytype) !void {
10+
if (observed_err_union) {
11+
return error.TestExpectedError;
12+
} else |err| if (err == expected_err) {
13+
return; // Success
14+
}
15+
return error.TestExpectedError;
16+
}
17+
818
test "error values" {
919
const a = @errorToInt(error.err1);
1020
const b = @errorToInt(error.err2);
@@ -329,7 +339,6 @@ fn intLiteral(str: []const u8) !?i64 {
329339

330340
test "nested error union function call in optional unwrap" {
331341
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
332-
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
333342
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
334343
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
335344
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
@@ -377,7 +386,6 @@ test "nested error union function call in optional unwrap" {
377386
}
378387

379388
test "return function call to error set from error union function" {
380-
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
381389
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
382390
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
383391
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" {
410418
}
411419

412420
test "nested catch" {
413-
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
414421
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
415422
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
416423
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
471478

472479
test "return result loc as peer result loc in inferred error set function" {
473480
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
474-
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
475481
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
476482
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
477483
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" {
503509
}
504510

505511
test "error payload type is correctly resolved" {
506-
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
507512
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
508513
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
509514
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
@@ -519,7 +524,7 @@ test "error payload type is correctly resolved" {
519524
}
520525
};
521526

522-
try expectEqual(MyIntWrapper{ .x = 42 }, try MyIntWrapper.create());
527+
try expect(std.meta.eql(MyIntWrapper{ .x = 42 }, try MyIntWrapper.create()));
523528
}
524529

525530
test "error union comptime caching" {

0 commit comments

Comments
 (0)