From f5d9d739d70f5d99756e277cc7c77484d60ddf42 Mon Sep 17 00:00:00 2001 From: Belhorma Bendebiche Date: Fri, 23 Jul 2021 12:43:38 -0400 Subject: [PATCH] stage1: Expand SysV C ABI support for small structs While the SysV ABI is not that complicated, LLVM does not allow us direct access to enforce it. By mimicking the IR generated by clang, we can trick LLVM into doing the right thing. This involves two main additions: 1. `AGG` ABI class This is not part of the spec, but since we have to track class per eightbyte and not per struct, the current enum is not enough. I considered adding multiple classes like: `INTEGER_INTEGER`, `INTEGER_SSE`, `SSE_INTEGER`. However, all of those cases would trigger the same code path so it's simpler to collapse into one. This class is only used on SysV. 2. LLVM C ABI type Clang uses different types in C ABI function signatures than the original structs passed in, and does conversion. For example, this struct: `{ i8, i8, float }` would use `{ i16, float }` at ABI boundaries. When passed as an argument, it is instead split into two arguments `i16` and `float`. Therefore, for every struct that passes ABI boundaries we need to keep track of its corresponding ABI type. Here are some more examples: ``` | Struct | ABI equivalent | | { i8, i8, i8, i8 } | i32 | | { float, float } | double | | { float, i32, i8 } | { float, i64 } | ``` Then, we must update function calls, returns, parameter lists and inits to properly convert back and forth as needed. --- src/stage1/all_types.hpp | 5 +- src/stage1/analyze.cpp | 124 +++++++++++++++++++++++++++- src/stage1/analyze.hpp | 3 + src/stage1/codegen.cpp | 160 +++++++++++++++++++++++++++---------- test/stage1/c_abi/cfuncs.c | 89 +++++++++++++++++++++ test/stage1/c_abi/main.zig | 87 ++++++++++++++++++++ 6 files changed, 419 insertions(+), 49 deletions(-) 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;