authorgravatar for cody+topolarity@tapscott.meCody Tapscott <cody+topolarity@tapscott.me> 2022-01-24 11:22:38-07:00
committergravatar for cody+topolarity@tapscott.meCody Tapscott <cody+topolarity@tapscott.me> 2022-01-24 12:00:04-07:00
log587a4437dbe4518671f4a894edb7d0f8e51d2ee1
treec93f9148fce23702ca11d871d7847b3e71f8e88c
parent983dfcd3fbd70347537f7b63db9838848caf0ac0

Add `union` support to the C backend.

There are some differences vs. the union encoding in the LLVM backend: - Tagged unions with a 0-bit payload do not become their tag type. Instead, they are a struct with an empty `union` as their payload field. - We do not order the `payload`/`tag` storage based on their alignment

2 files changed, 141 insertions(+), 16 deletions(-)

src/codegen/c.zig+140-15
...@@ -589,6 +589,40 @@ pub const DeclGen = struct {...@@ -589,6 +589,40 @@ pub const DeclGen = struct {
589589
590 try writer.writeAll("}");590 try writer.writeAll("}");
591 },591 },
592 .Union => {
593 const union_obj = val.castTag(.@"union").?.data;
594 const target = dg.module.getTarget();
595 const layout = ty.unionGetLayout(target);
596
597 try writer.writeAll("(");
598 try dg.renderType(writer, ty);
599 try writer.writeAll("){");
600
601 if (ty.unionTagType()) |tag_ty| {
602 if (layout.tag_size != 0) {
603 try writer.writeAll(".tag = ");
604 try dg.renderValue(writer, tag_ty, union_obj.tag);
605 try writer.writeAll(", ");
606 }
607 try writer.writeAll(".payload = {");
608 }
609
610 const index = switch (ty.tag()) {
611 .union_tagged => ty.castTag(.union_tagged).?.data.tag_ty.enumTagFieldIndex(union_obj.tag).?,
612 .@"union" => ty.castTag(.@"union").?.data.tag_ty.enumTagFieldIndex(union_obj.tag).?,
613 else => unreachable,
614 };
615 const field_ty = ty.unionFields().values()[index].ty;
616 const field_name = ty.unionFields().keys()[index];
617 if (field_ty.hasCodeGenBits()) {
618 try writer.print(".{} = ", .{fmtIdent(field_name)});
619 try dg.renderValue(writer, field_ty, union_obj.val);
620 }
621 if (ty.unionTagType()) |_| {
622 try writer.writeAll("}");
623 }
624 try writer.writeAll("}");
625 },
592626
593 .ComptimeInt => unreachable,627 .ComptimeInt => unreachable,
594 .ComptimeFloat => unreachable,628 .ComptimeFloat => unreachable,
...@@ -601,7 +635,6 @@ pub const DeclGen = struct {...@@ -601,7 +635,6 @@ pub const DeclGen = struct {
601 .BoundFn => unreachable,635 .BoundFn => unreachable,
602 .Opaque => unreachable,636 .Opaque => unreachable,
603637
604 .Union,
605 .Frame,638 .Frame,
606 .AnyFrame,639 .AnyFrame,
607 .Vector,640 .Vector,
...@@ -781,6 +814,65 @@ pub const DeclGen = struct {...@@ -781,6 +814,65 @@ pub const DeclGen = struct {
781 return name;814 return name;
782 }815 }
783816
817 fn renderUnionTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
818 const fqn = switch (t.tag()) {
819 .@"union" => try t.castTag(.@"union").?.data.getFullyQualifiedName(dg.typedefs.allocator),
820 .union_tagged => try t.castTag(.union_tagged).?.data.getFullyQualifiedName(dg.typedefs.allocator),
821 else => unreachable,
822 };
823 defer dg.typedefs.allocator.free(fqn);
824
825 const target = dg.module.getTarget();
826 const layout = t.unionGetLayout(target);
827
828 var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
829 defer buffer.deinit();
830
831 try buffer.appendSlice("typedef ");
832 if (t.unionTagType()) |tag_ty| {
833 const name: CValue = .{ .bytes = "tag" };
834 try buffer.appendSlice("struct {\n ");
835 if (layout.tag_size != 0) {
836 try dg.renderTypeAndName(buffer.writer(), tag_ty, name, .Mut, Value.initTag(.abi_align_default));
837 try buffer.appendSlice(";\n");
838 }
839 }
840
841 try buffer.appendSlice("union {\n");
842 {
843 var it = t.unionFields().iterator();
844 while (it.next()) |entry| {
845 const field_ty = entry.value_ptr.ty;
846 if (!field_ty.hasCodeGenBits()) continue;
847 const alignment = entry.value_ptr.abi_align;
848 const name: CValue = .{ .identifier = entry.key_ptr.* };
849 try buffer.append(' ');
850 try dg.renderTypeAndName(buffer.writer(), field_ty, name, .Mut, alignment);
851 try buffer.appendSlice(";\n");
852 }
853 }
854 try buffer.appendSlice("} ");
855
856 if (t.unionTagType()) |_| {
857 try buffer.appendSlice("payload;\n} ");
858 }
859
860 const name_start = buffer.items.len;
861 try buffer.writer().print("zig_U_{s};\n", .{fmtIdent(fqn)});
862
863 const rendered = buffer.toOwnedSlice();
864 errdefer dg.typedefs.allocator.free(rendered);
865 const name = rendered[name_start .. rendered.len - 2];
866
867 try dg.typedefs.ensureUnusedCapacity(1);
868 dg.typedefs.putAssumeCapacityNoClobber(
869 try t.copy(dg.typedefs_arena),
870 .{ .name = name, .rendered = rendered },
871 );
872
873 return name;
874 }
875
784 fn renderErrorUnionTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {876 fn renderErrorUnionTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
785 const child_type = t.errorUnionPayload();877 const child_type = t.errorUnionPayload();
786 const err_set_type = t.errorUnionSet();878 const err_set_type = t.errorUnionSet();
...@@ -959,6 +1051,12 @@ pub const DeclGen = struct {...@@ -959,6 +1051,12 @@ pub const DeclGen = struct {
9591051
960 return w.writeAll(name);1052 return w.writeAll(name);
961 },1053 },
1054 .Union => {
1055 const name = dg.getTypedefName(t) orelse
1056 try dg.renderUnionTypedef(t);
1057
1058 return w.writeAll(name);
1059 },
962 .Enum => {1060 .Enum => {
963 // For enums, we simply use the integer tag type.1061 // For enums, we simply use the integer tag type.
964 var int_tag_ty_buffer: Type.Payload.Bits = undefined;1062 var int_tag_ty_buffer: Type.Payload.Bits = undefined;
...@@ -967,7 +1065,6 @@ pub const DeclGen = struct {...@@ -967,7 +1065,6 @@ pub const DeclGen = struct {
967 try dg.renderType(w, int_tag_ty);1065 try dg.renderType(w, int_tag_ty);
968 },1066 },
9691067
970 .Union,
971 .Frame,1068 .Frame,
972 .AnyFrame,1069 .AnyFrame,
973 .Vector,1070 .Vector,
...@@ -2671,21 +2768,36 @@ fn airStructFieldPtrIndex(f: *Function, inst: Air.Inst.Index, index: u8) !CValue...@@ -2671,21 +2768,36 @@ fn airStructFieldPtrIndex(f: *Function, inst: Air.Inst.Index, index: u8) !CValue
26712768
2672fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struct_ptr: CValue, index: u32) !CValue {2769fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struct_ptr: CValue, index: u32) !CValue {
2673 const writer = f.object.writer();2770 const writer = f.object.writer();
2674 const struct_obj = struct_ptr_ty.elemType().castTag(.@"struct").?.data;2771 const struct_ty = struct_ptr_ty.elemType();
2675 const field_name = struct_obj.fields.keys()[index];2772 var field_name: []const u8 = undefined;
2676 const field_val = struct_obj.fields.values()[index];2773 var field_val_ty: Type = undefined;
2677 const addrof = if (field_val.ty.zigTypeTag() == .Array) "" else "&";2774
2775 switch (struct_ty.tag()) {
2776 .@"struct" => {
2777 const fields = struct_ty.structFields();
2778 field_name = fields.keys()[index];
2779 field_val_ty = fields.values()[index].ty;
2780 },
2781 .@"union", .union_tagged => {
2782 const fields = struct_ty.unionFields();
2783 field_name = fields.keys()[index];
2784 field_val_ty = fields.values()[index].ty;
2785 },
2786 else => unreachable,
2787 }
2788 const addrof = if (field_val_ty.zigTypeTag() == .Array) "" else "&";
2789 const payload = if (struct_ty.tag() == .union_tagged) "payload." else "";
26782790
2679 const inst_ty = f.air.typeOfIndex(inst);2791 const inst_ty = f.air.typeOfIndex(inst);
2680 const local = try f.allocLocal(inst_ty, .Const);2792 const local = try f.allocLocal(inst_ty, .Const);
2681 switch (struct_ptr) {2793 switch (struct_ptr) {
2682 .local_ref => |i| {2794 .local_ref => |i| {
2683 try writer.print(" = {s}t{d}.{};\n", .{ addrof, i, fmtIdent(field_name) });2795 try writer.print(" = {s}t{d}.{s}{};\n", .{ addrof, i, payload, fmtIdent(field_name) });
2684 },2796 },
2685 else => {2797 else => {
2686 try writer.print(" = {s}", .{addrof});2798 try writer.print(" = {s}", .{addrof});
2687 try f.writeCValue(writer, struct_ptr);2799 try f.writeCValue(writer, struct_ptr);
2688 try writer.print("->{};\n", .{fmtIdent(field_name)});2800 try writer.print("->{s}{};\n", .{ payload, fmtIdent(field_name) });
2689 },2801 },
2690 }2802 }
2691 return local;2803 return local;
...@@ -2700,14 +2812,18 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -2700,14 +2812,18 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue {
2700 const writer = f.object.writer();2812 const writer = f.object.writer();
2701 const struct_byval = try f.resolveInst(extra.struct_operand);2813 const struct_byval = try f.resolveInst(extra.struct_operand);
2702 const struct_ty = f.air.typeOf(extra.struct_operand);2814 const struct_ty = f.air.typeOf(extra.struct_operand);
2703 const struct_obj = struct_ty.castTag(.@"struct").?.data;2815 const field_name = switch (struct_ty.tag()) {
2704 const field_name = struct_obj.fields.keys()[extra.field_index];2816 .@"struct" => struct_ty.structFields().keys()[extra.field_index],
2817 .@"union", .union_tagged => struct_ty.unionFields().keys()[extra.field_index],
2818 else => unreachable,
2819 };
2820 const payload = if (struct_ty.tag() == .union_tagged) "payload." else "";
27052821
2706 const inst_ty = f.air.typeOfIndex(inst);2822 const inst_ty = f.air.typeOfIndex(inst);
2707 const local = try f.allocLocal(inst_ty, .Const);2823 const local = try f.allocLocal(inst_ty, .Const);
2708 try writer.writeAll(" = ");2824 try writer.writeAll(" = ");
2709 try f.writeCValue(writer, struct_byval);2825 try f.writeCValue(writer, struct_byval);
2710 try writer.print(".{};\n", .{fmtIdent(field_name)});2826 try writer.print(".{s}{};\n", .{ payload, fmtIdent(field_name) });
2711 return local;2827 return local;
2712}2828}
27132829
...@@ -3048,9 +3164,13 @@ fn airSetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -3048,9 +3164,13 @@ fn airSetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue {
3048 const new_tag = try f.resolveInst(bin_op.rhs);3164 const new_tag = try f.resolveInst(bin_op.rhs);
3049 const writer = f.object.writer();3165 const writer = f.object.writer();
30503166
3051 try writer.writeAll("*");3167 const union_ty = f.air.typeOf(bin_op.lhs).childType();
3168 const target = f.object.dg.module.getTarget();
3169 const layout = union_ty.unionGetLayout(target);
3170 if (layout.tag_size == 0) return CValue.none;
3171
3052 try f.writeCValue(writer, union_ptr);3172 try f.writeCValue(writer, union_ptr);
3053 try writer.writeAll(" = ");3173 try writer.writeAll("->tag = ");
3054 try f.writeCValue(writer, new_tag);3174 try f.writeCValue(writer, new_tag);
3055 try writer.writeAll(";\n");3175 try writer.writeAll(";\n");
30563176
...@@ -3064,12 +3184,17 @@ fn airGetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -3064,12 +3184,17 @@ fn airGetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue {
3064 const inst_ty = f.air.typeOfIndex(inst);3184 const inst_ty = f.air.typeOfIndex(inst);
3065 const local = try f.allocLocal(inst_ty, .Const);3185 const local = try f.allocLocal(inst_ty, .Const);
3066 const ty_op = f.air.instructions.items(.data)[inst].ty_op;3186 const ty_op = f.air.instructions.items(.data)[inst].ty_op;
3187 const un_ty = f.air.typeOf(ty_op.operand);
3067 const writer = f.object.writer();3188 const writer = f.object.writer();
3068 const operand = try f.resolveInst(ty_op.operand);3189 const operand = try f.resolveInst(ty_op.operand);
30693190
3070 try writer.writeAll("get_union_tag(");3191 const target = f.object.dg.module.getTarget();
3192 const layout = un_ty.unionGetLayout(target);
3193 if (layout.tag_size == 0) return CValue.none;
3194
3195 try writer.writeAll(" = ");
3071 try f.writeCValue(writer, operand);3196 try f.writeCValue(writer, operand);
3072 try writer.writeAll(");\n");3197 try writer.writeAll(".tag;\n");
3073 return local;3198 return local;
3074}3199}
30753200
test/behavior.zig+1-1
...@@ -68,7 +68,7 @@ test {...@@ -68,7 +68,7 @@ test {
68 _ = @import("behavior/cast_int.zig");68 _ = @import("behavior/cast_int.zig");
69 _ = @import("behavior/int128.zig");69 _ = @import("behavior/int128.zig");
70 _ = @import("behavior/union.zig");70 _ = @import("behavior/union.zig");
71// _ = @import("behavior/translate_c_macros.zig");71 _ = @import("behavior/translate_c_macros.zig");
7272
73 if (builtin.zig_backend != .stage2_c) {73 if (builtin.zig_backend != .stage2_c) {
74 // Tests that pass for stage1 and the llvm backend.74 // Tests that pass for stage1 and the llvm backend.