Commit be6cccb3a5
Changed files (6)
src/all_types.hpp
@@ -3284,4 +3284,28 @@ enum FloatMode {
FloatModeStrict,
};
+enum FnWalkId {
+ FnWalkIdAttrs,
+ FnWalkIdCall,
+};
+
+struct FnWalkAttrs {
+ ZigFn *fn;
+ unsigned gen_i;
+};
+
+struct FnWalkCall {
+ ZigList<LLVMValueRef> *gen_param_values;
+ IrInstructionCall *inst;
+ bool is_var_args;
+};
+
+struct FnWalk {
+ FnWalkId id;
+ union {
+ FnWalkAttrs attrs;
+ FnWalkCall call;
+ } data;
+};
+
#endif
src/analyze.cpp
@@ -1073,7 +1073,6 @@ bool type_is_c_abi_int(CodeGen *g, ZigType *ty) {
}
// If you edit this function you have to edit the corresponding code:
-// codegen.cpp:gen_c_abi_param
// analyze.cpp:gen_c_abi_param_type
// codegen.cpp:gen_c_abi_param_var
// codegen.cpp:gen_c_abi_param_var_init
src/codegen.cpp
@@ -466,7 +466,8 @@ static LLVMValueRef fn_llvm_value(CodeGen *g, ZigFn *fn_table_entry) {
}
bool external_linkage = linkage != GlobalLinkageIdInternal;
- if (fn_table_entry->type_entry->data.fn.fn_type_id.cc == CallingConventionStdcall && external_linkage &&
+ CallingConvention cc = fn_table_entry->type_entry->data.fn.fn_type_id.cc;
+ if (cc == CallingConventionStdcall && external_linkage &&
g->zig_target.arch.arch == ZigLLVM_x86)
{
// prevent llvm name mangling
@@ -510,17 +511,17 @@ static LLVMValueRef fn_llvm_value(CodeGen *g, ZigFn *fn_table_entry) {
break;
}
- if (fn_type->data.fn.fn_type_id.cc == CallingConventionNaked) {
+ if (cc == CallingConventionNaked) {
addLLVMFnAttr(fn_table_entry->llvm_value, "naked");
} else {
LLVMSetFunctionCallConv(fn_table_entry->llvm_value, get_llvm_cc(g, fn_type->data.fn.fn_type_id.cc));
}
- if (fn_type->data.fn.fn_type_id.cc == CallingConventionAsync) {
+ if (cc == CallingConventionAsync) {
addLLVMFnAttr(fn_table_entry->llvm_value, "optnone");
addLLVMFnAttr(fn_table_entry->llvm_value, "noinline");
}
- bool want_cold = fn_table_entry->is_cold || fn_type->data.fn.fn_type_id.cc == CallingConventionCold;
+ bool want_cold = fn_table_entry->is_cold || cc == CallingConventionCold;
if (want_cold) {
ZigLLVMAddFunctionAttrCold(fn_table_entry->llvm_value);
}
@@ -576,37 +577,16 @@ static LLVMValueRef fn_llvm_value(CodeGen *g, ZigFn *fn_table_entry) {
// nothing to do
} else if (type_is_codegen_pointer(return_type)) {
addLLVMAttr(fn_table_entry->llvm_value, 0, "nonnull");
- } else if (handle_is_ptr(return_type) &&
- calling_convention_does_first_arg_return(fn_type->data.fn.fn_type_id.cc))
- {
+ } else if (handle_is_ptr(return_type) && calling_convention_does_first_arg_return(cc)) {
addLLVMArgAttr(fn_table_entry->llvm_value, 0, "sret");
addLLVMArgAttr(fn_table_entry->llvm_value, 0, "nonnull");
}
-
// set parameter attributes
- for (size_t param_i = 0; param_i < fn_type->data.fn.fn_type_id.param_count; param_i += 1) {
- FnGenParamInfo *gen_info = &fn_type->data.fn.gen_param_info[param_i];
- size_t gen_index = gen_info->gen_index;
- bool is_byval = gen_info->is_byval;
-
- if (gen_index == SIZE_MAX) {
- continue;
- }
-
- FnTypeParamInfo *param_info = &fn_type->data.fn.fn_type_id.param_info[param_i];
-
- ZigType *param_type = gen_info->type;
- if (param_info->is_noalias) {
- addLLVMArgAttr(fn_table_entry->llvm_value, (unsigned)gen_index, "noalias");
- }
- if ((param_type->id == ZigTypeIdPointer && param_type->data.pointer.is_const) || is_byval) {
- addLLVMArgAttr(fn_table_entry->llvm_value, (unsigned)gen_index, "readonly");
- }
- if (param_type->id == ZigTypeIdPointer) {
- addLLVMArgAttr(fn_table_entry->llvm_value, (unsigned)gen_index, "nonnull");
- }
- }
+ FnWalk fn_walk = {};
+ fn_walk.id = FnWalkIdAttrs;
+ fn_walk.data.attrs.fn = fn_table_entry;
+ walk_function_params(g, fn_type, &fn_walk);
uint32_t err_ret_trace_arg_index = get_err_ret_trace_arg_index(g, fn_table_entry);
if (err_ret_trace_arg_index != UINT32_MAX) {
@@ -1888,6 +1868,216 @@ static LLVMValueRef ir_llvm_value(CodeGen *g, IrInstruction *instruction) {
return instruction->llvm_value;
}
+ATTRIBUTE_NORETURN
+static void report_errors_and_exit(CodeGen *g) {
+ assert(g->errors.length != 0);
+ for (size_t i = 0; i < g->errors.length; i += 1) {
+ ErrorMsg *err = g->errors.at(i);
+ print_err_msg(err, g->err_color);
+ }
+ exit(1);
+}
+
+static void report_errors_and_maybe_exit(CodeGen *g) {
+ if (g->errors.length != 0) {
+ report_errors_and_exit(g);
+ }
+}
+
+ATTRIBUTE_NORETURN
+static void give_up_with_c_abi_error(CodeGen *g, AstNode *source_node) {
+ ErrorMsg *msg = add_node_error(g, source_node,
+ buf_sprintf("TODO: support C ABI for more targets. https://github.com/ziglang/zig/issues/1481"));
+ add_error_note(g, msg, source_node,
+ buf_sprintf("pointers, integers, floats, bools, and enums work on all targets"));
+ report_errors_and_exit(g);
+}
+
+static bool iter_function_params_c_abi(CodeGen *g, ZigType *fn_type, FnWalk *fn_walk, size_t src_i) {
+ // Initialized from the type for some walks, but because of C var args,
+ // initialized based on callsite instructions for that one.
+ FnTypeParamInfo *param_info = nullptr;
+ ZigType *ty;
+ AstNode *source_node = nullptr;
+ LLVMValueRef val;
+ LLVMValueRef llvm_fn;
+ switch (fn_walk->id) {
+ case FnWalkIdAttrs:
+ if (src_i >= fn_type->data.fn.fn_type_id.param_count)
+ return false;
+ param_info = &fn_type->data.fn.fn_type_id.param_info[src_i];
+ ty = param_info->type;
+ source_node = fn_walk->data.attrs.fn->proto_node;
+ llvm_fn = fn_walk->data.attrs.fn->llvm_value;
+ break;
+ case FnWalkIdCall: {
+ if (src_i >= fn_walk->data.call.inst->arg_count)
+ return false;
+ IrInstruction *arg = fn_walk->data.call.inst->args[src_i];
+ ty = arg->value.type;
+ source_node = arg->source_node;
+ val = ir_llvm_value(g, arg);
+ break;
+ }
+ }
+ if (type_is_c_abi_int(g, ty) || ty->id == ZigTypeIdFloat ||
+ ty->id == ZigTypeIdInt // TODO investigate if we need to change this
+ ) {
+ switch (fn_walk->id) {
+ case FnWalkIdAttrs: {
+ ZigType *ptr_type = get_codegen_ptr_type(ty);
+ if (ptr_type != nullptr) {
+ if (ty->id != ZigTypeIdOptional) {
+ addLLVMArgAttr(llvm_fn, fn_walk->data.attrs.gen_i, "nonnull");
+ }
+ if (ptr_type->data.pointer.is_const) {
+ addLLVMArgAttr(llvm_fn, fn_walk->data.attrs.gen_i, "readonly");
+ }
+ if (param_info->is_noalias) {
+ addLLVMArgAttr(llvm_fn, fn_walk->data.attrs.gen_i, "noalias");
+ }
+ }
+ fn_walk->data.attrs.gen_i += 1;
+ break;
+ }
+ case FnWalkIdCall:
+ fn_walk->data.call.gen_param_values->append(val);
+ break;
+ }
+ return true;
+ }
+
+ // Arrays are just pointers
+ if (ty->id == ZigTypeIdArray) {
+ assert(handle_is_ptr(ty));
+ switch (fn_walk->id) {
+ case FnWalkIdAttrs:
+ addLLVMArgAttr(llvm_fn, fn_walk->data.attrs.gen_i, "nonnull");
+ fn_walk->data.attrs.gen_i += 1;
+ break;
+ case FnWalkIdCall:
+ fn_walk->data.call.gen_param_values->append(val);
+ break;
+ }
+ return true;
+ }
+
+ if (g->zig_target.arch.arch == ZigLLVM_x86_64) {
+ size_t ty_size = type_size(g, ty);
+ if (ty->id == ZigTypeIdStruct || ty->id == ZigTypeIdUnion) {
+ assert(handle_is_ptr(ty));
+
+ // "If the size of an object is larger than four eightbytes, or it contains unaligned
+ // fields, it has class MEMORY"
+ if (ty_size > 32) {
+ switch (fn_walk->id) {
+ case FnWalkIdAttrs:
+ addLLVMArgAttr(llvm_fn, fn_walk->data.attrs.gen_i, "byval");
+ addLLVMArgAttr(llvm_fn, fn_walk->data.attrs.gen_i, "nonnull");
+ fn_walk->data.attrs.gen_i += 1;
+ break;
+ case FnWalkIdCall:
+ fn_walk->data.call.gen_param_values->append(val);
+ break;
+ }
+ return true;
+ }
+ }
+ if (ty->id == ZigTypeIdStruct) {
+ assert(handle_is_ptr(ty));
+ // "If the size of the aggregate exceeds a single eightbyte, each is classified
+ // separately. Each eightbyte gets initialized to class NO_CLASS."
+ if (ty_size <= 8) {
+ bool contains_int = false;
+ for (size_t i = 0; i < ty->data.structure.src_field_count; i += 1) {
+ if (type_is_c_abi_int(g, ty->data.structure.fields[i].type_entry)) {
+ contains_int = true;
+ break;
+ }
+ }
+ if (contains_int) {
+ switch (fn_walk->id) {
+ case FnWalkIdAttrs:
+ fn_walk->data.attrs.gen_i += 1;
+ break;
+ case FnWalkIdCall: {
+ LLVMTypeRef ptr_to_int_type_ref = LLVMPointerType(LLVMIntType((unsigned)ty_size * 8), 0);
+ LLVMValueRef bitcasted = LLVMBuildBitCast(g->builder, val, ptr_to_int_type_ref, "");
+ LLVMValueRef loaded = LLVMBuildLoad(g->builder, bitcasted, "");
+ fn_walk->data.call.gen_param_values->append(loaded);
+ break;
+ }
+ }
+ return true;
+ }
+ }
+ }
+ }
+ if (source_node != nullptr) {
+ give_up_with_c_abi_error(g, source_node);
+ }
+ // otherwise allow codegen code to report a compile error
+ return false;
+}
+
+void walk_function_params(CodeGen *g, ZigType *fn_type, FnWalk *fn_walk) {
+ CallingConvention cc = fn_type->data.fn.fn_type_id.cc;
+ if (cc == CallingConventionC) {
+ size_t src_i = 0;
+ for (;;) {
+ if (!iter_function_params_c_abi(g, fn_type, fn_walk, src_i))
+ break;
+ src_i += 1;
+ }
+ return;
+ }
+ if (fn_walk->id == FnWalkIdCall) {
+ IrInstructionCall *instruction = fn_walk->data.call.inst;
+ bool is_var_args = fn_walk->data.call.is_var_args;
+ for (size_t call_i = 0; call_i < instruction->arg_count; call_i += 1) {
+ IrInstruction *param_instruction = instruction->args[call_i];
+ ZigType *param_type = param_instruction->value.type;
+ if (is_var_args || type_has_bits(param_type)) {
+ LLVMValueRef param_value = ir_llvm_value(g, param_instruction);
+ assert(param_value);
+ fn_walk->data.call.gen_param_values->append(param_value);
+ }
+ }
+ return;
+ }
+ for (size_t param_i = 0; param_i < fn_type->data.fn.fn_type_id.param_count; param_i += 1) {
+ FnGenParamInfo *gen_info = &fn_type->data.fn.gen_param_info[param_i];
+ size_t gen_index = gen_info->gen_index;
+
+ if (gen_index == SIZE_MAX) {
+ continue;
+ }
+
+ switch (fn_walk->id) {
+ case FnWalkIdAttrs: {
+ LLVMValueRef llvm_fn = fn_walk->data.attrs.fn->llvm_value;
+ bool is_byval = gen_info->is_byval;
+ FnTypeParamInfo *param_info = &fn_type->data.fn.fn_type_id.param_info[param_i];
+
+ ZigType *param_type = gen_info->type;
+ if (param_info->is_noalias) {
+ addLLVMArgAttr(llvm_fn, (unsigned)gen_index, "noalias");
+ }
+ if ((param_type->id == ZigTypeIdPointer && param_type->data.pointer.is_const) || is_byval) {
+ addLLVMArgAttr(llvm_fn, (unsigned)gen_index, "readonly");
+ }
+ if (param_type->id == ZigTypeIdPointer) {
+ addLLVMArgAttr(llvm_fn, (unsigned)gen_index, "nonnull");
+ }
+ break;
+ }
+ case FnWalkIdCall:
+ // handled before for loop
+ zig_unreachable();
+ }
+ }
+}
+
static LLVMValueRef ir_render_save_err_ret_addr(CodeGen *g, IrExecutable *executable,
IrInstructionSaveErrRetAddr *save_err_ret_addr_instruction)
{
@@ -3111,31 +3301,6 @@ static void set_call_instr_sret(CodeGen *g, LLVMValueRef call_instr) {
LLVMAddCallSiteAttribute(call_instr, 1, sret_attr);
}
-ATTRIBUTE_NORETURN
-static void report_errors_and_exit(CodeGen *g) {
- assert(g->errors.length != 0);
- for (size_t i = 0; i < g->errors.length; i += 1) {
- ErrorMsg *err = g->errors.at(i);
- print_err_msg(err, g->err_color);
- }
- exit(1);
-}
-
-static void report_errors_and_maybe_exit(CodeGen *g) {
- if (g->errors.length != 0) {
- report_errors_and_exit(g);
- }
-}
-
-ATTRIBUTE_NORETURN
-static void give_up_with_c_abi_error(CodeGen *g, AstNode *source_node) {
- ErrorMsg *msg = add_node_error(g, source_node,
- buf_sprintf("TODO: support C ABI for more targets. https://github.com/ziglang/zig/issues/1481"));
- add_error_note(g, msg, source_node,
- buf_sprintf("pointers, integers, floats, bools, and enums work on all targets"));
- report_errors_and_exit(g);
-}
-
static LLVMValueRef build_alloca(CodeGen *g, ZigType *type_entry, const char *name, uint32_t alignment) {
assert(alignment > 0);
LLVMValueRef result = LLVMBuildAlloca(g->builder, type_entry->type_ref, name);
@@ -3144,67 +3309,6 @@ static LLVMValueRef build_alloca(CodeGen *g, ZigType *type_entry, const char *na
}
// If you edit this function you have to edit the corresponding code:
-// codegen.cpp:gen_c_abi_param
-// analyze.cpp:gen_c_abi_param_type
-// codegen.cpp:gen_c_abi_param_var
-// codegen.cpp:gen_c_abi_param_var_init
-static void gen_c_abi_param(CodeGen *g, ZigList<LLVMValueRef> *gen_param_values, LLVMValueRef val,
- ZigType *ty, AstNode *source_node)
-{
- if (type_is_c_abi_int(g, ty) || ty->id == ZigTypeIdFloat ||
- ty->id == ZigTypeIdInt // TODO investigate if we need to change this
- ) {
- gen_param_values->append(val);
- return;
- }
-
- // Arrays are just pointers
- if (ty->id == ZigTypeIdArray) {
- assert(handle_is_ptr(ty));
- gen_param_values->append(val);
- return;
- }
-
- if (g->zig_target.arch.arch == ZigLLVM_x86_64) {
- // This code all assumes that val is a pointer.
- assert(handle_is_ptr(ty));
-
- size_t ty_size = type_size(g, ty);
- if (ty->id == ZigTypeIdStruct || ty->id == ZigTypeIdUnion) {
- // "If the size of an object is larger than four eightbytes, or it contains unaligned
- // fields, it has class MEMORY"
- if (ty_size > 32) {
- gen_param_values->append(val);
- return;
- }
- }
- if (ty->id == ZigTypeIdStruct) {
- // "If the size of the aggregate exceeds a single eightbyte, each is classified
- // separately. Each eightbyte gets initialized to class NO_CLASS."
- if (ty_size <= 8) {
- bool contains_int = false;
- for (size_t i = 0; i < ty->data.structure.src_field_count; i += 1) {
- if (type_is_c_abi_int(g, ty->data.structure.fields[i].type_entry)) {
- contains_int = true;
- break;
- }
- }
- if (contains_int) {
- LLVMTypeRef ptr_to_int_type_ref = LLVMPointerType(LLVMIntType((unsigned)ty_size * 8), 0);
- LLVMValueRef bitcasted = LLVMBuildBitCast(g->builder, val, ptr_to_int_type_ref, "");
- LLVMValueRef loaded = LLVMBuildLoad(g->builder, bitcasted, "");
- gen_param_values->append(loaded);
- return;
- }
- }
- }
- }
-
- give_up_with_c_abi_error(g, source_node);
-}
-
-// If you edit this function you have to edit the corresponding code:
-// codegen.cpp:gen_c_abi_param
// analyze.cpp:gen_c_abi_param_type
// codegen.cpp:gen_c_abi_param_var
// codegen.cpp:gen_c_abi_param_var_init
@@ -3283,7 +3387,6 @@ ok:
}
// If you edit this function you have to edit the corresponding code:
-// codegen.cpp:gen_c_abi_param
// analyze.cpp:gen_c_abi_param_type
// codegen.cpp:gen_c_abi_param_var
// codegen.cpp:gen_c_abi_param_var_init
@@ -3376,7 +3479,6 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr
bool ret_has_bits = type_has_bits(src_return_type);
CallingConvention cc = fn_type->data.fn.fn_type_id.cc;
- bool is_c_abi = cc == CallingConventionC;
bool first_arg_ret = ret_has_bits && handle_is_ptr(src_return_type) &&
calling_convention_does_first_arg_return(cc);
@@ -3395,19 +3497,12 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr
LLVMValueRef err_val_ptr = LLVMBuildStructGEP(g->builder, instruction->tmp_ptr, err_union_err_index, "");
gen_param_values.append(err_val_ptr);
}
- for (size_t call_i = 0; call_i < instruction->arg_count; call_i += 1) {
- IrInstruction *param_instruction = instruction->args[call_i];
- ZigType *param_type = param_instruction->value.type;
- if (is_var_args || type_has_bits(param_type)) {
- LLVMValueRef param_value = ir_llvm_value(g, param_instruction);
- assert(param_value);
- if (is_c_abi) {
- gen_c_abi_param(g, &gen_param_values, param_value, param_type, param_instruction->source_node);
- } else {
- gen_param_values.append(param_value);
- }
- }
- }
+ FnWalk fn_walk = {};
+ fn_walk.id = FnWalkIdCall;
+ fn_walk.data.call.inst = instruction;
+ fn_walk.data.call.is_var_args = is_var_args;
+ fn_walk.data.call.gen_param_values = &gen_param_values;
+ walk_function_params(g, fn_type, &fn_walk);
ZigLLVM_FnInline fn_inline;
switch (instruction->fn_inline) {
src/codegen.hpp
@@ -61,5 +61,6 @@ void codegen_translate_c(CodeGen *g, Buf *path);
Buf *codegen_generate_builtin_source(CodeGen *g);
+void walk_function_params(CodeGen *g, ZigType *fn_type, FnWalk *fn_walk);
#endif
test/stage1/c_abi/cfuncs.c
@@ -19,6 +19,29 @@ void zig_i16(int16_t);
void zig_i32(int32_t);
void zig_i64(int64_t);
+void zig_f32(float);
+void zig_f64(double);
+
+void zig_ptr(void *);
+
+void zig_bool(bool);
+
+void zig_array(uint8_t[10]);
+
+static uint8_t array[10] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};
+
+struct BigStruct {
+ uint64_t a;
+ uint64_t b;
+ uint64_t c;
+ uint64_t d;
+ uint8_t e;
+};
+
+void zig_big_struct(struct BigStruct);
+
+static struct BigStruct s = {1, 2, 3, 4, 5};
+
void run_c_tests(void) {
zig_u8(0xff);
zig_u16(0xfffe);
@@ -29,6 +52,17 @@ void run_c_tests(void) {
zig_i16(-2);
zig_i32(-3);
zig_i64(-4);
+
+ zig_f32(12.34f);
+ zig_f64(56.78);
+
+ zig_ptr((void*)0xdeadbeefL);
+
+ zig_bool(true);
+
+ zig_array(array);
+
+ zig_big_struct(s);
}
void c_u8(uint8_t x) {
@@ -62,3 +96,40 @@ void c_i32(int32_t x) {
void c_i64(int64_t x) {
assert_or_panic(x == -4);
}
+
+void c_f32(float x) {
+ assert_or_panic(x == 12.34f);
+}
+
+void c_f64(double x) {
+ assert_or_panic(x == 56.78);
+}
+
+void c_ptr(void *x) {
+ assert_or_panic(x == (void*)0xdeadbeefL);
+}
+
+void c_bool(bool x) {
+ assert_or_panic(x);
+}
+
+void c_array(uint8_t x[10]) {
+ assert_or_panic(x[0] == '1');
+ assert_or_panic(x[1] == '2');
+ assert_or_panic(x[2] == '3');
+ assert_or_panic(x[3] == '4');
+ assert_or_panic(x[4] == '5');
+ assert_or_panic(x[5] == '6');
+ assert_or_panic(x[6] == '7');
+ assert_or_panic(x[7] == '8');
+ assert_or_panic(x[8] == '9');
+ assert_or_panic(x[9] == '0');
+}
+
+void c_big_struct(struct BigStruct x) {
+ assert_or_panic(x.a == 1);
+ assert_or_panic(x.b == 2);
+ assert_or_panic(x.c == 3);
+ assert_or_panic(x.d == 4);
+ assert_or_panic(x.e == 5);
+}
test/stage1/c_abi/main.zig
@@ -56,3 +56,77 @@ export fn zig_i32(x: i32) void {
export fn zig_i64(x: i64) void {
assertOrPanic(x == -4);
}
+
+extern fn c_f32(f32) void;
+extern fn c_f64(f64) void;
+
+test "C ABI floats" {
+ c_f32(12.34);
+ c_f64(56.78);
+}
+
+export fn zig_f32(x: f32) void {
+ assertOrPanic(x == 12.34);
+}
+export fn zig_f64(x: f64) void {
+ assertOrPanic(x == 56.78);
+}
+
+extern fn c_ptr(*c_void) void;
+
+test "C ABI pointer" {
+ c_ptr(@intToPtr(*c_void, 0xdeadbeef));
+}
+
+export fn zig_ptr(x: *c_void) void {
+ assertOrPanic(@ptrToInt(x) == 0xdeadbeef);
+}
+
+extern fn c_bool(bool) void;
+
+test "C ABI bool" {
+ c_bool(true);
+}
+
+export fn zig_bool(x: bool) void {
+ assertOrPanic(x);
+}
+
+extern fn c_array([10]u8) void;
+
+test "C ABI array" {
+ var array: [10]u8 = "1234567890";
+ c_array(array);
+}
+
+export fn zig_array(x: [10]u8) void {
+ assertOrPanic(std.mem.eql(u8, x, "1234567890"));
+}
+
+const BigStruct = extern struct {
+ a: u64,
+ b: u64,
+ c: u64,
+ d: u64,
+ e: u8,
+};
+extern fn c_big_struct(BigStruct) void;
+
+test "C ABI big struct" {
+ var s = BigStruct{
+ .a = 1,
+ .b = 2,
+ .c = 3,
+ .d = 4,
+ .e = 5,
+ };
+ c_big_struct(s);
+}
+
+export fn zig_big_struct(x: BigStruct) void {
+ assertOrPanic(x.a == 1);
+ assertOrPanic(x.b == 2);
+ assertOrPanic(x.c == 3);
+ assertOrPanic(x.d == 4);
+ assertOrPanic(x.e == 5);
+}