Commit f5d9d739d7
Changed files (6)
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;
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));
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<const char *> &args, const char *out_dep_path, bool translate_c,
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;
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);
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;