authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-09-29 14:04:52-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-09-29 14:04:52-07:00
logea6706b6f406606a7523e35e34e390fb880b607e
treee3d71639d007cbc528f13d30330fe04c0ac6cbf2
parent1d1f6a04214027da014cbc8eb780ff4c5e55f863

stage2: LLVM backend: implement struct type fwd decls

Makes struct types able to refer to themselves.

6 files changed, 154 insertions(+), 82 deletions(-)

src/Module.zig+1-1
...@@ -813,7 +813,7 @@ pub const Struct = struct {...@@ -813,7 +813,7 @@ pub const Struct = struct {
813 is_comptime: bool,813 is_comptime: bool,
814 };814 };
815815
816 pub fn getFullyQualifiedName(s: *Struct, gpa: *Allocator) ![]u8 {816 pub fn getFullyQualifiedName(s: *Struct, gpa: *Allocator) ![:0]u8 {
817 return s.owner_decl.getFullyQualifiedName(gpa);817 return s.owner_decl.getFullyQualifiedName(gpa);
818 }818 }
819819
src/codegen/llvm.zig+99-38
...@@ -164,9 +164,25 @@ pub const Object = struct {...@@ -164,9 +164,25 @@ pub const Object = struct {
164 /// * it works for functions not all globals.164 /// * it works for functions not all globals.
165 /// Therefore, this table keeps track of the mapping.165 /// Therefore, this table keeps track of the mapping.
166 decl_map: std.AutoHashMapUnmanaged(*const Module.Decl, *const llvm.Value),166 decl_map: std.AutoHashMapUnmanaged(*const Module.Decl, *const llvm.Value),
167 /// Maps Zig types to LLVM types. The table memory itself is backed by the GPA of
168 /// the compiler, but the Type/Value memory here is backed by `type_map_arena`.
169 /// TODO we need to remove entries from this map in response to incremental compilation
170 /// but I think the frontend won't tell us about types that get deleted because
171 /// hasCodeGenBits() is false for types.
172 type_map: TypeMap,
173 /// The backing memory for `type_map`. Periodically garbage collected after flush().
174 /// The code for doing the periodical GC is not yet implemented.
175 type_map_arena: std.heap.ArenaAllocator,
167 /// Where to put the output object file, relative to bin_file.options.emit directory.176 /// Where to put the output object file, relative to bin_file.options.emit directory.
168 sub_path: []const u8,177 sub_path: []const u8,
169178
179 pub const TypeMap = std.HashMapUnmanaged(
180 Type,
181 *const llvm.Type,
182 Type.HashContext64,
183 std.hash_map.default_max_load_percentage,
184 );
185
170 pub fn create(gpa: *Allocator, sub_path: []const u8, options: link.Options) !*Object {186 pub fn create(gpa: *Allocator, sub_path: []const u8, options: link.Options) !*Object {
171 const obj = try gpa.create(Object);187 const obj = try gpa.create(Object);
172 errdefer gpa.destroy(obj);188 errdefer gpa.destroy(obj);
...@@ -253,6 +269,8 @@ pub const Object = struct {...@@ -253,6 +269,8 @@ pub const Object = struct {
253 .context = context,269 .context = context,
254 .target_machine = target_machine,270 .target_machine = target_machine,
255 .decl_map = .{},271 .decl_map = .{},
272 .type_map = .{},
273 .type_map_arena = std.heap.ArenaAllocator.init(gpa),
256 .sub_path = sub_path,274 .sub_path = sub_path,
257 };275 };
258 }276 }
...@@ -262,6 +280,8 @@ pub const Object = struct {...@@ -262,6 +280,8 @@ pub const Object = struct {
262 self.llvm_module.dispose();280 self.llvm_module.dispose();
263 self.context.dispose();281 self.context.dispose();
264 self.decl_map.deinit(gpa);282 self.decl_map.deinit(gpa);
283 self.type_map.deinit(gpa);
284 self.type_map_arena.deinit();
265 self.* = undefined;285 self.* = undefined;
266 }286 }
267287
...@@ -725,10 +745,10 @@ pub const DeclGen = struct {...@@ -725,10 +745,10 @@ pub const DeclGen = struct {
725 }745 }
726746
727 fn llvmType(self: *DeclGen, t: Type) error{ OutOfMemory, CodegenFail }!*const llvm.Type {747 fn llvmType(self: *DeclGen, t: Type) error{ OutOfMemory, CodegenFail }!*const llvm.Type {
748 const gpa = self.gpa;
728 log.debug("llvmType for {}", .{t});749 log.debug("llvmType for {}", .{t});
729 switch (t.zigTypeTag()) {750 switch (t.zigTypeTag()) {
730 .Void => return self.context.voidType(),751 .Void, .NoReturn => return self.context.voidType(),
731 .NoReturn => return self.context.voidType(),
732 .Int => {752 .Int => {
733 const info = t.intInfo(self.module.getTarget());753 const info = t.intInfo(self.module.getTarget());
734 return self.context.intType(info.bits);754 return self.context.intType(info.bits);
...@@ -799,18 +819,38 @@ pub const DeclGen = struct {...@@ -799,18 +819,38 @@ pub const DeclGen = struct {
799 return self.context.intType(16);819 return self.context.intType(16);
800 },820 },
801 .Struct => {821 .Struct => {
822 const gop = try self.object.type_map.getOrPut(gpa, t);
823 if (gop.found_existing) return gop.value_ptr.*;
824
825 // The Type memory is ephemeral; since we want to store a longer-lived
826 // reference, we need to copy it here.
827 gop.key_ptr.* = try t.copy(&self.object.type_map_arena.allocator);
828
802 const struct_obj = t.castTag(.@"struct").?.data;829 const struct_obj = t.castTag(.@"struct").?.data;
803 assert(struct_obj.haveFieldTypes());830 assert(struct_obj.haveFieldTypes());
804 const llvm_fields = try self.gpa.alloc(*const llvm.Type, struct_obj.fields.count());831
805 defer self.gpa.free(llvm_fields);832 const name = try struct_obj.getFullyQualifiedName(gpa);
806 for (struct_obj.fields.values()) |field, i| {833 defer gpa.free(name);
807 llvm_fields[i] = try self.llvmType(field.ty);834
835 const llvm_struct_ty = self.context.structCreateNamed(name);
836 gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls
837
838 var llvm_field_types: std.ArrayListUnmanaged(*const llvm.Type) = .{};
839 try llvm_field_types.ensureTotalCapacity(gpa, struct_obj.fields.count());
840 defer llvm_field_types.deinit(gpa);
841
842 for (struct_obj.fields.values()) |field| {
843 if (!field.ty.hasCodeGenBits()) continue;
844 llvm_field_types.appendAssumeCapacity(try self.llvmType(field.ty));
808 }845 }
809 return self.context.structType(846
810 llvm_fields.ptr,847 llvm_struct_ty.structSetBody(
811 @intCast(c_uint, llvm_fields.len),848 llvm_field_types.items.ptr,
812 .False,849 @intCast(c_uint, llvm_field_types.items.len),
850 llvm.Bool.fromBool(struct_obj.layout == .Packed),
813 );851 );
852
853 return llvm_struct_ty;
814 },854 },
815 .Union => {855 .Union => {
816 const union_obj = t.castTag(.@"union").?.data;856 const union_obj = t.castTag(.@"union").?.data;
...@@ -838,8 +878,8 @@ pub const DeclGen = struct {...@@ -838,8 +878,8 @@ pub const DeclGen = struct {
838 .Fn => {878 .Fn => {
839 const ret_ty = try self.llvmType(t.fnReturnType());879 const ret_ty = try self.llvmType(t.fnReturnType());
840 const params_len = t.fnParamLen();880 const params_len = t.fnParamLen();
841 const llvm_params = try self.gpa.alloc(*const llvm.Type, params_len);881 const llvm_params = try gpa.alloc(*const llvm.Type, params_len);
842 defer self.gpa.free(llvm_params);882 defer gpa.free(llvm_params);
843 for (llvm_params) |*llvm_param, i| {883 for (llvm_params) |*llvm_param, i| {
844 llvm_param.* = try self.llvmType(t.fnParamType(i));884 llvm_param.* = try self.llvmType(t.fnParamType(i));
845 }885 }
...@@ -1073,21 +1113,26 @@ pub const DeclGen = struct {...@@ -1073,21 +1113,26 @@ pub const DeclGen = struct {
1073 return self.context.constStruct(&fields, fields.len, .False);1113 return self.context.constStruct(&fields, fields.len, .False);
1074 },1114 },
1075 .Struct => {1115 .Struct => {
1076 const fields_len = tv.ty.structFieldCount();1116 const llvm_struct_ty = try self.llvmType(tv.ty);
1077 const field_vals = tv.val.castTag(.@"struct").?.data;1117 const field_vals = tv.val.castTag(.@"struct").?.data;
1078 const gpa = self.gpa;1118 const gpa = self.gpa;
1079 const llvm_fields = try gpa.alloc(*const llvm.Value, fields_len);1119
1080 defer gpa.free(llvm_fields);1120 var llvm_fields: std.ArrayListUnmanaged(*const llvm.Value) = .{};
1081 for (llvm_fields) |*llvm_field, i| {1121 try llvm_fields.ensureTotalCapacity(gpa, field_vals.len);
1082 llvm_field.* = try self.genTypedValue(.{1122 defer llvm_fields.deinit(gpa);
1083 .ty = tv.ty.structFieldType(i),1123
1084 .val = field_vals[i],1124 for (field_vals) |field_val, i| {
1085 });1125 const field_ty = tv.ty.structFieldType(i);
1126 if (!field_ty.hasCodeGenBits()) continue;
1127
1128 llvm_fields.appendAssumeCapacity(try self.genTypedValue(.{
1129 .ty = field_ty,
1130 .val = field_val,
1131 }));
1086 }1132 }
1087 return self.context.constStruct(1133 return llvm_struct_ty.constNamedStruct(
1088 llvm_fields.ptr,1134 llvm_fields.items.ptr,
1089 @intCast(c_uint, llvm_fields.len),1135 @intCast(c_uint, llvm_fields.items.len),
1090 .False,
1091 );1136 );
1092 },1137 },
1093 .ComptimeInt => unreachable,1138 .ComptimeInt => unreachable,
...@@ -1692,13 +1737,15 @@ pub const FuncGen = struct {...@@ -1692,13 +1737,15 @@ pub const FuncGen = struct {
1692 const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data;1737 const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data;
1693 const struct_ptr = try self.resolveInst(struct_field.struct_operand);1738 const struct_ptr = try self.resolveInst(struct_field.struct_operand);
1694 const struct_ptr_ty = self.air.typeOf(struct_field.struct_operand);1739 const struct_ptr_ty = self.air.typeOf(struct_field.struct_operand);
1695 const field_index = @intCast(c_uint, struct_field.field_index);1740 return self.fieldPtr(inst, struct_ptr, struct_ptr_ty, struct_field.field_index);
1696 return self.fieldPtr(inst, struct_ptr, struct_ptr_ty, field_index);
1697 }1741 }
16981742
1699 fn airStructFieldPtrIndex(self: *FuncGen, inst: Air.Inst.Index, field_index: c_uint) !?*const llvm.Value {1743 fn airStructFieldPtrIndex(
1700 if (self.liveness.isUnused(inst))1744 self: *FuncGen,
1701 return null;1745 inst: Air.Inst.Index,
1746 field_index: u32,
1747 ) !?*const llvm.Value {
1748 if (self.liveness.isUnused(inst)) return null;
17021749
1703 const ty_op = self.air.instructions.items(.data)[inst].ty_op;1750 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1704 const struct_ptr = try self.resolveInst(ty_op.operand);1751 const struct_ptr = try self.resolveInst(ty_op.operand);
...@@ -1707,13 +1754,13 @@ pub const FuncGen = struct {...@@ -1707,13 +1754,13 @@ pub const FuncGen = struct {
1707 }1754 }
17081755
1709 fn airStructFieldVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {1756 fn airStructFieldVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
1710 if (self.liveness.isUnused(inst))1757 if (self.liveness.isUnused(inst)) return null;
1711 return null;
17121758
1713 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;1759 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
1714 const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data;1760 const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data;
1761 const struct_ty = self.air.typeOf(struct_field.struct_operand);
1715 const struct_byval = try self.resolveInst(struct_field.struct_operand);1762 const struct_byval = try self.resolveInst(struct_field.struct_operand);
1716 const field_index = @intCast(c_uint, struct_field.field_index);1763 const field_index = llvmFieldIndex(struct_ty, struct_field.field_index);
1717 return self.builder.buildExtractValue(struct_byval, field_index, "");1764 return self.builder.buildExtractValue(struct_byval, field_index, "");
1718 }1765 }
17191766
...@@ -2643,8 +2690,7 @@ pub const FuncGen = struct {...@@ -2643,8 +2690,7 @@ pub const FuncGen = struct {
2643 const fill_char = if (val_is_undef) u8_llvm_ty.constInt(0xaa, .False) else value;2690 const fill_char = if (val_is_undef) u8_llvm_ty.constInt(0xaa, .False) else value;
2644 const target = self.dg.module.getTarget();2691 const target = self.dg.module.getTarget();
2645 const dest_ptr_align = ptr_ty.ptrAlignment(target);2692 const dest_ptr_align = ptr_ty.ptrAlignment(target);
2646 const memset = self.builder.buildMemSet(dest_ptr_u8, fill_char, len, dest_ptr_align);2693 _ = self.builder.buildMemSet(dest_ptr_u8, fill_char, len, dest_ptr_align, ptr_ty.isVolatilePtr());
2647 memset.setVolatile(llvm.Bool.fromBool(ptr_ty.isVolatilePtr()));
26482694
2649 if (val_is_undef and self.dg.module.comp.bin_file.options.valgrind) {2695 if (val_is_undef and self.dg.module.comp.bin_file.options.valgrind) {
2650 // TODO generate valgrind client request to mark byte range as undefined2696 // TODO generate valgrind client request to mark byte range as undefined
...@@ -2667,14 +2713,14 @@ pub const FuncGen = struct {...@@ -2667,14 +2713,14 @@ pub const FuncGen = struct {
2667 const src_ptr_u8 = self.builder.buildBitCast(src_ptr, ptr_u8_llvm_ty, "");2713 const src_ptr_u8 = self.builder.buildBitCast(src_ptr, ptr_u8_llvm_ty, "");
2668 const is_volatile = src_ptr_ty.isVolatilePtr() or dest_ptr_ty.isVolatilePtr();2714 const is_volatile = src_ptr_ty.isVolatilePtr() or dest_ptr_ty.isVolatilePtr();
2669 const target = self.dg.module.getTarget();2715 const target = self.dg.module.getTarget();
2670 const memcpy = self.builder.buildMemCpy(2716 _ = self.builder.buildMemCpy(
2671 dest_ptr_u8,2717 dest_ptr_u8,
2672 dest_ptr_ty.ptrAlignment(target),2718 dest_ptr_ty.ptrAlignment(target),
2673 src_ptr_u8,2719 src_ptr_u8,
2674 src_ptr_ty.ptrAlignment(target),2720 src_ptr_ty.ptrAlignment(target),
2675 len,2721 len,
2722 is_volatile,
2676 );2723 );
2677 memcpy.setVolatile(llvm.Bool.fromBool(is_volatile));
2678 return null;2724 return null;
2679 }2725 }
26802726
...@@ -2741,11 +2787,14 @@ pub const FuncGen = struct {...@@ -2741,11 +2787,14 @@ pub const FuncGen = struct {
2741 inst: Air.Inst.Index,2787 inst: Air.Inst.Index,
2742 struct_ptr: *const llvm.Value,2788 struct_ptr: *const llvm.Value,
2743 struct_ptr_ty: Type,2789 struct_ptr_ty: Type,
2744 field_index: c_uint,2790 field_index: u32,
2745 ) !?*const llvm.Value {2791 ) !?*const llvm.Value {
2746 const struct_ty = struct_ptr_ty.childType();2792 const struct_ty = struct_ptr_ty.childType();
2747 switch (struct_ty.zigTypeTag()) {2793 switch (struct_ty.zigTypeTag()) {
2748 .Struct => return self.builder.buildStructGEP(struct_ptr, field_index, ""),2794 .Struct => {
2795 const llvm_field_index = llvmFieldIndex(struct_ty, field_index);
2796 return self.builder.buildStructGEP(struct_ptr, llvm_field_index, "");
2797 },
2749 .Union => return self.unionFieldPtr(inst, struct_ptr, struct_ty, field_index),2798 .Union => return self.unionFieldPtr(inst, struct_ptr, struct_ty, field_index),
2750 else => unreachable,2799 else => unreachable,
2751 }2800 }
...@@ -2968,3 +3017,15 @@ fn toLlvmAtomicRmwBinOp(...@@ -2968,3 +3017,15 @@ fn toLlvmAtomicRmwBinOp(
2968 .Min => if (is_signed) llvm.AtomicRMWBinOp.Min else return .UMin,3017 .Min => if (is_signed) llvm.AtomicRMWBinOp.Min else return .UMin,
2969 };3018 };
2970}3019}
3020
3021/// Take into account 0 bit fields.
3022fn llvmFieldIndex(ty: Type, index: u32) c_uint {
3023 const struct_obj = ty.castTag(.@"struct").?.data;
3024 var result: c_uint = 0;
3025 for (struct_obj.fields.values()[0..index]) |field| {
3026 if (field.ty.hasCodeGenBits()) {
3027 result += 1;
3028 }
3029 }
3030 return result;
3031}
src/codegen/llvm/bindings.zig+13-4
...@@ -181,6 +181,13 @@ pub const Type = opaque {...@@ -181,6 +181,13 @@ pub const Type = opaque {
181 pub const constArray = LLVMConstArray;181 pub const constArray = LLVMConstArray;
182 extern fn LLVMConstArray(ElementTy: *const Type, ConstantVals: [*]*const Value, Length: c_uint) *const Value;182 extern fn LLVMConstArray(ElementTy: *const Type, ConstantVals: [*]*const Value, Length: c_uint) *const Value;
183183
184 pub const constNamedStruct = LLVMConstNamedStruct;
185 extern fn LLVMConstNamedStruct(
186 StructTy: *const Type,
187 ConstantVals: [*]const *const Value,
188 Count: c_uint,
189 ) *const Value;
190
184 pub const getUndef = LLVMGetUndef;191 pub const getUndef = LLVMGetUndef;
185 extern fn LLVMGetUndef(Ty: *const Type) *const Value;192 extern fn LLVMGetUndef(Ty: *const Type) *const Value;
186193
...@@ -666,23 +673,25 @@ pub const Builder = opaque {...@@ -666,23 +673,25 @@ pub const Builder = opaque {
666 Name: [*:0]const u8,673 Name: [*:0]const u8,
667 ) *const Value;674 ) *const Value;
668675
669 pub const buildMemSet = LLVMBuildMemSet;676 pub const buildMemSet = ZigLLVMBuildMemSet;
670 extern fn LLVMBuildMemSet(677 extern fn ZigLLVMBuildMemSet(
671 B: *const Builder,678 B: *const Builder,
672 Ptr: *const Value,679 Ptr: *const Value,
673 Val: *const Value,680 Val: *const Value,
674 Len: *const Value,681 Len: *const Value,
675 Align: c_uint,682 Align: c_uint,
683 is_volatile: bool,
676 ) *const Value;684 ) *const Value;
677685
678 pub const buildMemCpy = LLVMBuildMemCpy;686 pub const buildMemCpy = ZigLLVMBuildMemCpy;
679 extern fn LLVMBuildMemCpy(687 extern fn ZigLLVMBuildMemCpy(
680 B: *const Builder,688 B: *const Builder,
681 Dst: *const Value,689 Dst: *const Value,
682 DstAlign: c_uint,690 DstAlign: c_uint,
683 Src: *const Value,691 Src: *const Value,
684 SrcAlign: c_uint,692 SrcAlign: c_uint,
685 Size: *const Value,693 Size: *const Value,
694 is_volatile: bool,
686 ) *const Value;695 ) *const Value;
687};696};
688697
src/type.zig+2
...@@ -1785,6 +1785,8 @@ pub const Type = extern union {...@@ -1785,6 +1785,8 @@ pub const Type = extern union {
1785 if (is_packed) @panic("TODO packed structs");1785 if (is_packed) @panic("TODO packed structs");
1786 var size: u64 = 0;1786 var size: u64 = 0;
1787 for (s.fields.values()) |field| {1787 for (s.fields.values()) |field| {
1788 if (!field.ty.hasCodeGenBits()) continue;
1789
1788 const field_align = a: {1790 const field_align = a: {
1789 if (field.abi_align.tag() == .abi_align_default) {1791 if (field.abi_align.tag() == .abi_align_default) {
1790 break :a field.ty.abiAlignment(target);1792 break :a field.ty.abiAlignment(target);
test/behavior/struct.zig+39
...@@ -90,3 +90,42 @@ test "call member function directly" {...@@ -90,3 +90,42 @@ test "call member function directly" {
90 const result = MemberFnTestFoo.member(instance);90 const result = MemberFnTestFoo.member(instance);
91 try expect(result == 1234);91 try expect(result == 1234);
92}92}
93
94test "struct point to self" {
95 var root: Node = undefined;
96 root.val.x = 1;
97
98 var node: Node = undefined;
99 node.next = &root;
100 node.val.x = 2;
101
102 root.next = &node;
103
104 try expect(node.next.next.next.val.x == 1);
105}
106
107test "void struct fields" {
108 const foo = VoidStructFieldsFoo{
109 .a = void{},
110 .b = 1,
111 .c = void{},
112 };
113 try expect(foo.b == 1);
114 try expect(@sizeOf(VoidStructFieldsFoo) == 4);
115}
116const VoidStructFieldsFoo = struct {
117 a: void,
118 b: i32,
119 c: void,
120};
121
122test "member functions" {
123 const r = MemberFnRand{ .seed = 1234 };
124 try expect(r.getSeed() == 1234);
125}
126const MemberFnRand = struct {
127 seed: u32,
128 pub fn getSeed(r: *const MemberFnRand) u32 {
129 return r.seed;
130 }
131};
test/behavior/struct_stage1.zig-39
...@@ -16,21 +16,6 @@ test "top level fields" {...@@ -16,21 +16,6 @@ test "top level fields" {
16 try expectEqual(@as(i32, 1235), instance.top_level_field);16 try expectEqual(@as(i32, 1235), instance.top_level_field);
17}17}
1818
19test "void struct fields" {
20 const foo = VoidStructFieldsFoo{
21 .a = void{},
22 .b = 1,
23 .c = void{},
24 };
25 try expect(foo.b == 1);
26 try expect(@sizeOf(VoidStructFieldsFoo) == 4);
27}
28const VoidStructFieldsFoo = struct {
29 a: void,
30 b: i32,
31 c: void,
32};
33
34const StructFoo = struct {19const StructFoo = struct {
35 a: i32,20 a: i32,
36 b: bool,21 b: bool,
...@@ -46,19 +31,6 @@ const Val = struct {...@@ -46,19 +31,6 @@ const Val = struct {
46 x: i32,31 x: i32,
47};32};
4833
49test "struct point to self" {
50 var root: Node = undefined;
51 root.val.x = 1;
52
53 var node: Node = undefined;
54 node.next = &root;
55 node.val.x = 2;
56
57 root.next = &node;
58
59 try expect(node.next.next.next.val.x == 1);
60}
61
62test "fn call of struct field" {34test "fn call of struct field" {
63 const Foo = struct {35 const Foo = struct {
64 ptr: fn () i32,36 ptr: fn () i32,
...@@ -89,17 +61,6 @@ test "store member function in variable" {...@@ -89,17 +61,6 @@ test "store member function in variable" {
89 try expect(result == 1234);61 try expect(result == 1234);
90}62}
9163
92test "member functions" {
93 const r = MemberFnRand{ .seed = 1234 };
94 try expect(r.getSeed() == 1234);
95}
96const MemberFnRand = struct {
97 seed: u32,
98 pub fn getSeed(r: *const MemberFnRand) u32 {
99 return r.seed;
100 }
101};
102
103test "return struct byval from function" {64test "return struct byval from function" {
104 const bar = makeBar2(1234, 5678);65 const bar = makeBar2(1234, 5678);
105 try expect(bar.y == 5678);66 try expect(bar.y == 5678);