diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index a72faa15ce..dfebc66cfd 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -107,6 +107,7 @@ enum X64CABIClass { X64CABIClass_MEMORY_nobyval, X64CABIClass_INTEGER, X64CABIClass_SSE, + X64CABIClass_AGG, }; struct Stage1Zir { @@ -1569,8 +1570,9 @@ struct ZigType { // These are not supposed to be accessed directly. They're // null during semantic analysis, memoized with get_llvm_type - // and get_llvm_di_type + // get_llvm_c_abi_type and get_llvm_di_type LLVMTypeRef llvm_type; + LLVMTypeRef llvm_c_abi_type; ZigLLVMDIType *llvm_di_type; union { @@ -1624,6 +1626,7 @@ struct GlobalExport { struct ZigFn { LLVMValueRef llvm_value; + LLVMValueRef abi_return_value; // alloca used when converting at SysV ABI boundaries const char *llvm_name; AstNode *proto_node; AstNode *body_node; diff --git a/src/stage1/analyze.cpp b/src/stage1/analyze.cpp index 501e43373d..cf61bf79b7 100644 --- a/src/stage1/analyze.cpp +++ b/src/stage1/analyze.cpp @@ -6063,6 +6063,12 @@ Error type_has_bits2(CodeGen *g, ZigType *type_entry, bool *result) { return ErrorNone; } +bool fn_returns_c_abi_small_struct(FnTypeId *fn_type_id) { + ZigType *type = fn_type_id->return_type; + return !calling_convention_allows_zig_types(fn_type_id->cc) && + type->id == ZigTypeIdStruct && type->abi_size <= 16; +} + // Whether you can infer the value based solely on the type. OnePossibleValue type_has_one_possible_value(CodeGen *g, ZigType *type_entry) { assert(type_entry != nullptr); @@ -8376,6 +8382,9 @@ static X64CABIClass type_system_V_abi_x86_64_class(CodeGen *g, ZigType *ty, size // be memory. return X64CABIClass_MEMORY; } + // "If the size of the aggregate exceeds a single eightbyte, each is classified + // separately.". + // "If one of the classes is MEMORY, the whole argument is passed in memory" X64CABIClass working_class = X64CABIClass_Unknown; for (uint32_t i = 0; i < ty->data.structure.src_field_count; i += 1) { X64CABIClass field_class = type_c_abi_x86_64_class(g, ty->data.structure.fields[0]->type_entry); @@ -8385,7 +8394,10 @@ static X64CABIClass type_system_V_abi_x86_64_class(CodeGen *g, ZigType *ty, size working_class = field_class; } } - return working_class; + if (working_class == X64CABIClass_MEMORY) { + return X64CABIClass_MEMORY; + } + return X64CABIClass_AGG; } case ZigTypeIdUnion: { // "If the size of an object is larger than four eightbytes, or it contains unaligned @@ -8407,7 +8419,7 @@ static X64CABIClass type_system_V_abi_x86_64_class(CodeGen *g, ZigType *ty, size X64CABIClass field_class = type_c_abi_x86_64_class(g, ty->data.unionation.fields->type_entry); if (field_class == X64CABIClass_Unknown) return X64CABIClass_Unknown; - if (i == 0 || field_class == X64CABIClass_MEMORY || working_class == X64CABIClass_SSE) { + if (i == 0 || field_class == X64CABIClass_MEMORY || field_class == X64CABIClass_INTEGER || working_class == X64CABIClass_SSE) { working_class = field_class; } } @@ -8678,6 +8690,95 @@ static LLVMTypeRef get_llvm_type_of_n_bytes(unsigned byte_size) { LLVMInt8Type() : LLVMArrayType(LLVMInt8Type(), byte_size); } +static LLVMTypeRef llvm_int_for_size(size_t size) { + if (size > 4) { + return LLVMInt64Type(); + } else if (size > 2) { + return LLVMInt32Type(); + } else if (size == 2) { + return LLVMInt16Type(); + } else { + return LLVMInt8Type(); + } +} + +static LLVMTypeRef llvm_sse_for_size(size_t size) { + if (size > 4) + return LLVMDoubleType(); + else + return LLVMFloatType(); +} + +// Since it's not possible to control calling convention or register +// allocation in LLVM, clang seems to use intermediate types to manipulate +// LLVM into doing the right thing. It uses a float to force SSE registers, +// and a struct when 2 registers must be used. Some examples: +// { f32 } -> float +// { f32, i32 } -> { float, i32 } +// { i32, i32, f32 } -> { i64, float } +// +// The implementation below does not match clang 1:1. For instance, clang +// uses `<2x float>` while we generate `double`. There's a lot more edge +// cases and complexity when converting back and forth in clang though, +// so below is the simplest implementation that passes all tests. +static Error resolve_llvm_c_abi_type(CodeGen *g, ZigType *ty) { + size_t ty_size = type_size(g, ty); + LLVMTypeRef abi_type; + switch (ty->id) { + case ZigTypeIdEnum: + case ZigTypeIdInt: + case ZigTypeIdBool: + abi_type = llvm_int_for_size(ty_size); + break; + case ZigTypeIdFloat: + case ZigTypeIdVector: + abi_type = llvm_sse_for_size(ty_size); + break; + case ZigTypeIdStruct: { + uint32_t eightbyte_index = 0; + size_t type_sizes[] = {0, 0}; + X64CABIClass type_classes[] = {X64CABIClass_Unknown, X64CABIClass_Unknown}; + for (uint32_t i = 0; i < ty->data.structure.src_field_count; i += 1) { + if (ty->data.structure.fields[i]->offset >= 8) { + eightbyte_index = 1; + } + X64CABIClass field_class = type_c_abi_x86_64_class(g, ty->data.structure.fields[i]->type_entry); + + if (field_class == X64CABIClass_INTEGER) { + type_classes[eightbyte_index] = X64CABIClass_INTEGER; + } else if (type_classes[eightbyte_index] == X64CABIClass_Unknown) { + type_classes[eightbyte_index] = field_class; + } + type_sizes[eightbyte_index] += ty->data.structure.fields[i]->type_entry->abi_size; + } + + LLVMTypeRef return_elem_types[] = { + LLVMVoidType(), + LLVMVoidType(), + }; + for (uint32_t i = 0; i <= eightbyte_index; i += 1) { + if (type_classes[i] == X64CABIClass_INTEGER) { + return_elem_types[i] = llvm_int_for_size(type_sizes[i]); + } else { + return_elem_types[i] = llvm_sse_for_size(type_sizes[i]); + } + } + if (eightbyte_index == 0) { + abi_type = return_elem_types[0]; + } else { + abi_type = LLVMStructType(return_elem_types, 2, false); + } + break; + } + case ZigTypeIdUnion: + default: + // currently unreachable + zig_panic("TODO: support C ABI unions"); + } + ty->llvm_c_abi_type = abi_type; + return ErrorNone; +} + static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveStatus wanted_resolve_status, ZigType *async_frame_type) { @@ -8936,6 +9037,9 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS g->type_resolve_stack.swap_remove(struct_type->data.structure.llvm_full_type_queue_index); struct_type->data.structure.llvm_full_type_queue_index = SIZE_MAX; } + + if (struct_type->abi_size <= 16 && struct_type->data.structure.layout == ContainerLayoutExtern) + resolve_llvm_c_abi_type(g, struct_type); } // This is to be used instead of void for debug info types, to avoid tripping @@ -9536,8 +9640,13 @@ static void resolve_llvm_types_fn_type(CodeGen *g, ZigType *fn_type) { assert(gen_param_types.items[i] != nullptr); } - fn_type->data.fn.raw_type_ref = LLVMFunctionType(get_llvm_type(g, gen_return_type), - gen_param_types.items, (unsigned int)gen_param_types.length, fn_type_id->is_var_args); + if (!first_arg_return && fn_returns_c_abi_small_struct(fn_type_id)) { + fn_type->data.fn.raw_type_ref = LLVMFunctionType(get_llvm_c_abi_type(g, gen_return_type), + gen_param_types.items, (unsigned int)gen_param_types.length, fn_type_id->is_var_args); + } else { + fn_type->data.fn.raw_type_ref = LLVMFunctionType(get_llvm_type(g, gen_return_type), + gen_param_types.items, (unsigned int)gen_param_types.length, fn_type_id->is_var_args); + } const unsigned fn_addrspace = ZigLLVMDataLayoutGetProgramAddressSpace(g->target_data_ref); fn_type->llvm_type = LLVMPointerType(fn_type->data.fn.raw_type_ref, fn_addrspace); fn_type->data.fn.raw_di_type = ZigLLVMCreateSubroutineType(g->dbuilder, param_di_types.items, (int)param_di_types.length, 0); @@ -9827,6 +9936,13 @@ static void resolve_llvm_types(CodeGen *g, ZigType *type, ResolveStatus wanted_r zig_unreachable(); } +LLVMTypeRef get_llvm_c_abi_type(CodeGen *g, ZigType *type) { + assertNoError(type_resolve(g, type, ResolveStatusLLVMFull)); + assert(type->abi_size == 0 || type->abi_size >= LLVMABISizeOfType(g->target_data_ref, type->llvm_type)); + assert(type->abi_align == 0 || type->abi_align >= LLVMABIAlignmentOfType(g->target_data_ref, type->llvm_type)); + return type->llvm_c_abi_type; +} + LLVMTypeRef get_llvm_type(CodeGen *g, ZigType *type) { assertNoError(type_resolve(g, type, ResolveStatusLLVMFull)); assert(type->abi_size == 0 || type->abi_size >= LLVMABISizeOfType(g->target_data_ref, type->llvm_type)); diff --git a/src/stage1/analyze.hpp b/src/stage1/analyze.hpp index 22951db0f0..8290ef572c 100644 --- a/src/stage1/analyze.hpp +++ b/src/stage1/analyze.hpp @@ -54,6 +54,8 @@ uint32_t get_async_frame_align_bytes(CodeGen *g); bool type_has_bits(CodeGen *g, ZigType *type_entry); Error type_has_bits2(CodeGen *g, ZigType *type_entry, bool *result); +bool fn_returns_c_abi_small_struct(FnTypeId *fn_type_id); + enum ExternPosition { ExternPositionFunctionParameter, ExternPositionFunctionReturn, @@ -268,6 +270,7 @@ Buf *type_bare_name(ZigType *t); Buf *type_h_name(ZigType *t); LLVMTypeRef get_llvm_type(CodeGen *g, ZigType *type); +LLVMTypeRef get_llvm_c_abi_type(CodeGen *g, ZigType *type); ZigLLVMDIType *get_llvm_di_type(CodeGen *g, ZigType *type); void add_cc_args(CodeGen *g, ZigList &args, const char *out_dep_path, bool translate_c, diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index 083d15068a..dcb69ca64a 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -2142,75 +2142,103 @@ static bool iter_function_params_c_abi(CodeGen *g, ZigType *fn_type, FnWalk *fn_ } } return true; - } else if (abi_class == X64CABIClass_SSE) { - // For now only handle structs with only floats/doubles in it. - if (ty->id != ZigTypeIdStruct) { - if (source_node != nullptr) { - give_up_with_c_abi_error(g, source_node); - } - // otherwise allow codegen code to report a compile error - return false; - } - - for (uint32_t i = 0; i < ty->data.structure.src_field_count; i += 1) { - if (ty->data.structure.fields[i]->type_entry->id != ZigTypeIdFloat) { - if (source_node != nullptr) { - give_up_with_c_abi_error(g, source_node); - } - // otherwise allow codegen code to report a compile error - return false; - } - } - - // The SystemV ABI says that we have to setup 1 FP register per f64. + } else if (abi_class == X64CABIClass_AGG) { + // The SystemV ABI says that we have to setup 1 register per eightbyte. // So two f32 can be passed in one f64, but 3 f32 have to be passed in 2 FP registers. - // To achieve this with LLVM API, we pass multiple f64 parameters to the LLVM function if - // the type is bigger than 8 bytes. + // Similarly, two i32 can be passed in one i64, but 3 i32 have to be passed in 2 registers. + // LLVM does not allow us to control registers in this way, nor to request specific + // ABI conventions. So we have to trick it into allocating the right registers, based + // on how clang does it. + + // First, we get the LLVM type corresponding to the C abi for the struct, then + // we pass each field as an argument. // Example: // extern struct { // x: f32, // y: f32, - // z: f32, + // z: i32, // }; - // const ptr = (*f64)*Struct; - // Register 1: ptr.* - // Register 2: (ptr + 1).* + // LLVM abi type: { double, i32 } + // const ptr = (*abi_type)*Struct; + // FP Register 1: abi_type[0] + // Register 1: abi_type[1] - // One floating point register per f64 or 2 f32's - size_t number_of_fp_regs = (ty_size + 7) / 8; + // However, if the struct fits in one register, then we'll pass it as such + size_t number_of_regs = (size_t)ceilf((float)ty_size / (float)8); + + LLVMTypeRef abi_type = get_llvm_c_abi_type(g, ty); + + assert(ty_size <= 16); switch (fn_walk->id) { case FnWalkIdAttrs: { - fn_walk->data.attrs.gen_i += number_of_fp_regs; + fn_walk->data.attrs.gen_i += number_of_regs; break; } case FnWalkIdCall: { - LLVMValueRef f64_ptr_to_struct = LLVMBuildBitCast(g->builder, val, LLVMPointerType(LLVMDoubleType(), 0), ""); - for (uint32_t i = 0; i < number_of_fp_regs; i += 1) { - LLVMValueRef index = LLVMConstInt(g->builtin_types.entry_usize->llvm_type, i, false); - LLVMValueRef indices[] = { index }; - LLVMValueRef adjusted_ptr_to_struct = LLVMBuildInBoundsGEP(g->builder, f64_ptr_to_struct, indices, 1, ""); + LLVMValueRef abi_ptr_to_struct = LLVMBuildBitCast(g->builder, val, LLVMPointerType(abi_type, 0), ""); + if (number_of_regs == 1) { + LLVMValueRef loaded = LLVMBuildLoad(g->builder, abi_ptr_to_struct, ""); + fn_walk->data.call.gen_param_values->append(loaded); + break; + } + for (uint32_t i = 0; i < number_of_regs; i += 1) { + LLVMValueRef zero = LLVMConstInt(LLVMInt32Type(), 0, false); + LLVMValueRef index = LLVMConstInt(LLVMInt32Type(), i, false); + LLVMValueRef indices[] = { zero, index }; + LLVMValueRef adjusted_ptr_to_struct = LLVMBuildInBoundsGEP(g->builder, abi_ptr_to_struct, indices, 2, ""); LLVMValueRef loaded = LLVMBuildLoad(g->builder, adjusted_ptr_to_struct, ""); fn_walk->data.call.gen_param_values->append(loaded); } break; } case FnWalkIdTypes: { - for (uint32_t i = 0; i < number_of_fp_regs; i += 1) { - fn_walk->data.types.gen_param_types->append(get_llvm_type(g, g->builtin_types.entry_f64)); + if (number_of_regs == 1) { + fn_walk->data.types.gen_param_types->append(abi_type); + fn_walk->data.types.param_di_types->append(get_llvm_di_type(g, g->builtin_types.entry_f64)); + break; + } + for (uint32_t i = 0; i < number_of_regs; i += 1) { + fn_walk->data.types.gen_param_types->append(LLVMStructGetTypeAtIndex(abi_type, i)); fn_walk->data.types.param_di_types->append(get_llvm_di_type(g, g->builtin_types.entry_f64)); } break; } - case FnWalkIdVars: + case FnWalkIdVars: { + var->value_ref = build_alloca(g, ty, var->name, var->align_bytes); + di_arg_index = fn_walk->data.vars.gen_i; + fn_walk->data.vars.gen_i += 1; + dest_ty = ty; + goto var_ok; + } case FnWalkIdInits: { - // TODO: Handle exporting functions - if (source_node != nullptr) { - give_up_with_c_abi_error(g, source_node); + // since we're representing the struct differently as an arg, and potentially + // splitting it, we have to do some work to put it back together. + // the one reg case is straightforward, but if we used two registers we have + // to iterate through the struct abi repr fields and load them one by one. + if (number_of_regs == 1) { + LLVMValueRef arg = LLVMGetParam(llvm_fn, fn_walk->data.inits.gen_i); + LLVMTypeRef ptr_to_int_type_ref = LLVMPointerType(abi_type, 0); + LLVMValueRef bitcasted = LLVMBuildBitCast(g->builder, var->value_ref, ptr_to_int_type_ref, ""); + gen_store_untyped(g, arg, bitcasted, var->align_bytes, false); + } else { + LLVMValueRef abi_ptr_to_struct = LLVMBuildBitCast(g->builder, var->value_ref, LLVMPointerType(abi_type, 0), ""); + for (uint32_t i = 0; i < number_of_regs; i += 1) { + LLVMValueRef arg = LLVMGetParam(llvm_fn, fn_walk->data.inits.gen_i + i); + LLVMValueRef zero = LLVMConstInt(LLVMInt32Type(), 0, false); + LLVMValueRef index = LLVMConstInt(LLVMInt32Type(), i, false); + LLVMValueRef indices[] = { zero, index }; + LLVMValueRef adjusted_ptr_to_struct = LLVMBuildInBoundsGEP(g->builder, abi_ptr_to_struct, indices, 2, ""); + LLVMBuildStore(g->builder, arg, adjusted_ptr_to_struct); + } + fn_walk->data.inits.gen_i += 1; } - // otherwise allow codegen code to report a compile error - return false; + if (var->decl_node) { + gen_var_debug_decl(g, var); + } + fn_walk->data.inits.gen_i += 1; + break; } } return true; @@ -2654,13 +2682,36 @@ static void gen_async_return(CodeGen *g, Stage1AirInstReturn *instruction) { LLVMBuildRetVoid(g->builder); } +static LLVMValueRef gen_convert_to_c_abi(CodeGen *g, LLVMValueRef location, LLVMValueRef value) { + ZigType *return_type = g->cur_fn->type_entry->data.fn.gen_return_type; + size_t size = type_size(g, return_type); + + LLVMTypeRef abi_return_type = get_llvm_c_abi_type(g, return_type); + LLVMTypeRef abi_return_type_pointer = LLVMPointerType(abi_return_type, 0); + + if (size < 8) { + LLVMValueRef bitcast = LLVMBuildBitCast(g->builder, value, abi_return_type_pointer, ""); + return LLVMBuildLoad(g->builder, bitcast, ""); + } else { + LLVMTypeRef i8ptr = LLVMPointerType(LLVMInt8Type(), 0); + LLVMValueRef bc_location = LLVMBuildBitCast(g->builder, location, i8ptr, ""); + LLVMValueRef bc_value = LLVMBuildBitCast(g->builder, value, i8ptr, ""); + + LLVMValueRef len = LLVMConstInt(LLVMInt64Type(), size, false); + ZigLLVMBuildMemCpy(g->builder, bc_location, 8, bc_value, return_type->abi_align, len, false); + return LLVMBuildLoad(g->builder, location, ""); + } +} + static LLVMValueRef ir_render_return(CodeGen *g, Stage1Air *executable, Stage1AirInstReturn *instruction) { if (fn_is_async(g->cur_fn)) { gen_async_return(g, instruction); return nullptr; } - if (want_first_arg_sret(g, &g->cur_fn->type_entry->data.fn.fn_type_id)) { + FnTypeId *fn_type_id = &g->cur_fn->type_entry->data.fn.fn_type_id; + + if (want_first_arg_sret(g, fn_type_id)) { if (instruction->operand == nullptr) { LLVMBuildRetVoid(g->builder); return nullptr; @@ -2671,6 +2722,16 @@ static LLVMValueRef ir_render_return(CodeGen *g, Stage1Air *executable, Stage1Ai ZigType *return_type = instruction->operand->value->type; gen_assign_raw(g, g->cur_ret_ptr, get_pointer_to_type(g, return_type, false), value); LLVMBuildRetVoid(g->builder); + } else if (fn_returns_c_abi_small_struct(fn_type_id)) { + LLVMValueRef location = g->cur_fn->abi_return_value; + if (instruction->operand == nullptr) { + LLVMValueRef converted = gen_convert_to_c_abi(g, location, g->cur_ret_ptr); + LLVMBuildRet(g->builder, converted); + } else { + LLVMValueRef value = ir_llvm_value(g, instruction->operand); + LLVMValueRef converted = gen_convert_to_c_abi(g, location, value); + LLVMBuildRet(g->builder, converted); + } } else if (g->cur_fn->type_entry->data.fn.fn_type_id.cc != CallingConventionAsync && handle_is_ptr(g, g->cur_fn->type_entry->data.fn.fn_type_id.return_type)) { @@ -4678,6 +4739,12 @@ static LLVMValueRef ir_render_call(CodeGen *g, Stage1Air *executable, Stage1AirI } else if (first_arg_ret) { ZigLLVMSetCallSret(result, get_llvm_type(g, src_return_type)); return result_loc; + } else if (fn_returns_c_abi_small_struct(fn_type_id)) { + LLVMTypeRef abi_type = get_llvm_c_abi_type(g, src_return_type); + LLVMTypeRef abi_type_ptr = LLVMPointerType(abi_type, 0); + LLVMValueRef bitcast = LLVMBuildBitCast(g->builder, result_loc, abi_type_ptr, ""); + LLVMBuildStore(g->builder, result, bitcast); + return result_loc; } else if (handle_is_ptr(g, src_return_type)) { LLVMValueRef store_instr = LLVMBuildStore(g->builder, result, result_loc); LLVMSetAlignment(store_instr, get_ptr_align(g, instruction->result_loc->value->type)); @@ -8291,6 +8358,11 @@ static void do_code_gen(CodeGen *g) { g->cur_err_ret_trace_val_stack = nullptr; } + if (fn_returns_c_abi_small_struct(fn_type_id)) { + LLVMTypeRef abi_type = get_llvm_c_abi_type(g, fn_type_id->return_type); + fn_table_entry->abi_return_value = LLVMBuildAlloca(g->builder, abi_type, ""); + } + if (!is_async) { // allocate async frames for nosuspend calls & awaits to async functions ZigType *largest_call_frame_type = nullptr; diff --git a/test/stage1/c_abi/cfuncs.c b/test/stage1/c_abi/cfuncs.c index a3b8c9a8c6..0182462716 100644 --- a/test/stage1/c_abi/cfuncs.c +++ b/test/stage1/c_abi/cfuncs.c @@ -61,7 +61,20 @@ struct SmallStructInts { uint8_t c; uint8_t d; }; + void zig_small_struct_ints(struct SmallStructInts); +struct SmallStructInts zig_ret_small_struct_ints(); + +struct MedStructMixed { + uint32_t a; + float b; + float c; + uint32_t d; +}; + +void zig_med_struct_mixed(struct MedStructMixed); +struct MedStructMixed zig_ret_med_struct_mixed(); + struct SplitStructInts { uint64_t a; @@ -70,6 +83,14 @@ struct SplitStructInts { }; void zig_split_struct_ints(struct SplitStructInts); +struct SplitStructMixed { + uint64_t a; + uint8_t b; + float c; +}; +void zig_split_struct_mixed(struct SplitStructMixed); +struct SplitStructMixed zig_ret_split_struct_mixed(); + struct BigStruct zig_big_struct_both(struct BigStruct); typedef struct Vector3 { @@ -121,6 +142,16 @@ void run_c_tests(void) { zig_split_struct_ints(s); } + { + struct MedStructMixed s = {1234, 100.0f, 1337.0f}; + zig_med_struct_mixed(s); + } + + { + struct SplitStructMixed s = {1234, 100, 1337.0f}; + zig_split_struct_mixed(s); + } + { struct BigStruct s = {30, 31, 32, 33, 34}; struct BigStruct res = zig_big_struct_both(s); @@ -230,6 +261,44 @@ void c_small_struct_ints(struct SmallStructInts x) { assert_or_panic(x.b == 2); assert_or_panic(x.c == 3); assert_or_panic(x.d == 4); + + struct SmallStructInts y = zig_ret_small_struct_ints(); + + assert_or_panic(y.a == 1); + assert_or_panic(y.b == 2); + assert_or_panic(y.c == 3); + assert_or_panic(y.d == 4); +} + +struct SmallStructInts c_ret_small_struct_ints() { + struct SmallStructInts s = { + .a = 1, + .b = 2, + .c = 3, + .d = 4, + }; + return s; +} + +void c_med_struct_mixed(struct MedStructMixed x) { + assert_or_panic(x.a == 1234); + assert_or_panic(x.b == 100.0f); + assert_or_panic(x.c == 1337.0f); + + struct MedStructMixed y = zig_ret_med_struct_mixed(); + + assert_or_panic(y.a == 1234); + assert_or_panic(y.b == 100.0f); + assert_or_panic(y.c == 1337.0f); +} + +struct MedStructMixed c_ret_med_struct_mixed() { + struct MedStructMixed s = { + .a = 1234, + .b = 100.0, + .c = 1337.0, + }; + return s; } void c_split_struct_ints(struct SplitStructInts x) { @@ -238,6 +307,26 @@ void c_split_struct_ints(struct SplitStructInts x) { assert_or_panic(x.c == 1337); } +void c_split_struct_mixed(struct SplitStructMixed x) { + assert_or_panic(x.a == 1234); + assert_or_panic(x.b == 100); + assert_or_panic(x.c == 1337.0f); + struct SplitStructMixed y = zig_ret_split_struct_mixed(); + + assert_or_panic(y.a == 1234); + assert_or_panic(y.b == 100); + assert_or_panic(y.c == 1337.0f); +} + +struct SplitStructMixed c_ret_split_struct_mixed() { + struct SplitStructMixed s = { + .a = 1234, + .b = 100, + .c = 1337.0f, + }; + return s; +} + struct BigStruct c_big_struct_both(struct BigStruct x) { assert_or_panic(x.a == 1); assert_or_panic(x.b == 2); diff --git a/test/stage1/c_abi/main.zig b/test/stage1/c_abi/main.zig index ed5cad7c56..b8137334ec 100644 --- a/test/stage1/c_abi/main.zig +++ b/test/stage1/c_abi/main.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const print = std.debug.print; const expect = std.testing.expect; extern fn run_c_tests() void; @@ -170,6 +171,34 @@ export fn zig_big_union(x: BigUnion) void { expect(x.a.e == 5) catch @panic("test failure"); } +const MedStructMixed = extern struct { + a: u32, + b: f32, + c: f32, + d: u32 = 0, +}; +extern fn c_med_struct_mixed(MedStructMixed) void; +extern fn c_ret_med_struct_mixed() MedStructMixed; + +test "C ABI medium struct of ints and floats" { + var s = MedStructMixed{ + .a = 1234, + .b = 100.0, + .c = 1337.0, + }; + c_med_struct_mixed(s); + var s2 = c_ret_med_struct_mixed(); + expect(s2.a == 1234) catch @panic("test failure"); + expect(s2.b == 100.0) catch @panic("test failure"); + expect(s2.c == 1337.0) catch @panic("test failure"); +} + +export fn zig_med_struct_mixed(x: MedStructMixed) void { + expect(x.a == 1234) catch @panic("test failure"); + expect(x.b == 100.0) catch @panic("test failure"); + expect(x.c == 1337.0) catch @panic("test failure"); +} + const SmallStructInts = extern struct { a: u8, b: u8, @@ -177,6 +206,7 @@ const SmallStructInts = extern struct { d: u8, }; extern fn c_small_struct_ints(SmallStructInts) void; +extern fn c_ret_small_struct_ints() SmallStructInts; test "C ABI small struct of ints" { var s = SmallStructInts{ @@ -186,6 +216,11 @@ test "C ABI small struct of ints" { .d = 4, }; c_small_struct_ints(s); + var s2 = c_ret_small_struct_ints(); + expect(s2.a == 1) catch @panic("test failure"); + expect(s2.b == 2) catch @panic("test failure"); + expect(s2.c == 3) catch @panic("test failure"); + expect(s2.d == 4) catch @panic("test failure"); } export fn zig_small_struct_ints(x: SmallStructInts) void { @@ -217,6 +252,33 @@ export fn zig_split_struct_ints(x: SplitStructInt) void { expect(x.c == 1337) catch @panic("test failure"); } +const SplitStructMixed = extern struct { + a: u64, + b: u8, + c: f32, +}; +extern fn c_split_struct_mixed(SplitStructMixed) void; +extern fn c_ret_split_struct_mixed() SplitStructMixed; + +test "C ABI split struct of ints and floats" { + var s = SplitStructMixed{ + .a = 1234, + .b = 100, + .c = 1337.0, + }; + c_split_struct_mixed(s); + var s2 = c_ret_split_struct_mixed(); + expect(s2.a == 1234) catch @panic("test failure"); + expect(s2.b == 100) catch @panic("test failure"); + expect(s2.c == 1337.0) catch @panic("test failure"); +} + +export fn zig_split_struct_mixed(x: SplitStructMixed) void { + expect(x.a == 1234) catch @panic("test failure"); + expect(x.b == 100) catch @panic("test failure"); + expect(x.c == 1337.0) catch @panic("test failure"); +} + extern fn c_big_struct_both(BigStruct) BigStruct; test "C ABI sret and byval together" { @@ -315,6 +377,31 @@ export fn zig_ret_i64() i64 { return -1; } +export fn zig_ret_small_struct_ints() SmallStructInts { + return .{ + .a = 1, + .b = 2, + .c = 3, + .d = 4, + }; +} + +export fn zig_ret_med_struct_mixed() MedStructMixed { + return .{ + .a = 1234, + .b = 100.0, + .c = 1337.0, + }; +} + +export fn zig_ret_split_struct_mixed() SplitStructMixed { + return .{ + .a = 1234, + .b = 100, + .c = 1337.0, + }; +} + extern fn c_ret_bool() bool; extern fn c_ret_u8() u8; extern fn c_ret_u16() u16;