wasm-c-abi: llvm fix struct handling + reorganize

I changed to `wasm/abi.zig`, this design is certainly better than the previous one. Still there is some conflict of interest between llvm and self-hosted backend, better design will appear when abi tests will be tested with self-hosted.

Resolves: #23304
Resolves: #23305
This commit is contained in:
Pavel Verigo 2025-04-27 15:36:24 +02:00 committed by Andrew Kelley
parent ad9cb40112
commit a843be44a0
6 changed files with 329 additions and 230 deletions

View file

@ -1408,11 +1408,22 @@ fn resolveCallingConventionValues(
}, },
.wasm_mvp => { .wasm_mvp => {
for (fn_info.param_types.get(ip)) |ty| { for (fn_info.param_types.get(ip)) |ty| {
const ty_classes = abi.classifyType(Type.fromInterned(ty), zcu); if (!Type.fromInterned(ty).hasRuntimeBitsIgnoreComptime(zcu)) {
for (ty_classes) |class| { continue;
if (class == .none) continue; }
try args.append(.{ .local = .{ .value = result.local_index, .references = 1 } }); switch (abi.classifyType(.fromInterned(ty), zcu)) {
result.local_index += 1; .direct => |scalar_ty| if (!abi.lowerAsDoubleI64(scalar_ty, zcu)) {
try args.append(.{ .local = .{ .value = result.local_index, .references = 1 } });
result.local_index += 1;
} else {
try args.append(.{ .local = .{ .value = result.local_index, .references = 1 } });
try args.append(.{ .local = .{ .value = result.local_index + 1, .references = 1 } });
result.local_index += 2;
},
.indirect => {
try args.append(.{ .local = .{ .value = result.local_index, .references = 1 } });
result.local_index += 1;
},
} }
} }
}, },
@ -1428,14 +1439,13 @@ pub fn firstParamSRet(
zcu: *const Zcu, zcu: *const Zcu,
target: *const std.Target, target: *const std.Target,
) bool { ) bool {
if (!return_type.hasRuntimeBitsIgnoreComptime(zcu)) return false;
switch (cc) { switch (cc) {
.@"inline" => unreachable, .@"inline" => unreachable,
.auto => return isByRef(return_type, zcu, target), .auto => return isByRef(return_type, zcu, target),
.wasm_mvp => { .wasm_mvp => switch (abi.classifyType(return_type, zcu)) {
const ty_classes = abi.classifyType(return_type, zcu); .direct => |scalar_ty| return abi.lowerAsDoubleI64(scalar_ty, zcu),
if (ty_classes[0] == .indirect) return true; .indirect => return true,
if (ty_classes[0] == .direct and ty_classes[1] == .direct) return true;
return false;
}, },
else => return false, else => return false,
} }
@ -1449,26 +1459,19 @@ fn lowerArg(cg: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value: WV
} }
const zcu = cg.pt.zcu; const zcu = cg.pt.zcu;
const ty_classes = abi.classifyType(ty, zcu);
assert(ty_classes[0] != .none); switch (abi.classifyType(ty, zcu)) {
switch (ty.zigTypeTag(zcu)) { .direct => |scalar_type| if (!abi.lowerAsDoubleI64(scalar_type, zcu)) {
.@"struct", .@"union" => { if (!isByRef(ty, zcu, cg.target)) {
if (ty_classes[0] == .indirect) {
return cg.lowerToStack(value); return cg.lowerToStack(value);
} else {
switch (value) {
.nav_ref, .stack_offset => _ = try cg.load(value, scalar_type, 0),
.dead => unreachable,
else => try cg.emitWValue(value),
}
} }
assert(ty_classes[0] == .direct); } else {
const scalar_type = abi.scalarType(ty, zcu);
switch (value) {
.nav_ref, .stack_offset => _ = try cg.load(value, scalar_type, 0),
.dead => unreachable,
else => try cg.emitWValue(value),
}
},
.int, .float => {
if (ty_classes[1] == .none) {
return cg.lowerToStack(value);
}
assert(ty_classes[0] == .direct and ty_classes[1] == .direct);
assert(ty.abiSize(zcu) == 16); assert(ty.abiSize(zcu) == 16);
// in this case we have an integer or float that must be lowered as 2 i64's. // in this case we have an integer or float that must be lowered as 2 i64's.
try cg.emitWValue(value); try cg.emitWValue(value);
@ -1476,7 +1479,7 @@ fn lowerArg(cg: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value: WV
try cg.emitWValue(value); try cg.emitWValue(value);
try cg.addMemArg(.i64_load, .{ .offset = value.offset() + 8, .alignment = 8 }); try cg.addMemArg(.i64_load, .{ .offset = value.offset() + 8, .alignment = 8 });
}, },
else => return cg.lowerToStack(value), .indirect => return cg.lowerToStack(value),
} }
} }
@ -2142,23 +2145,16 @@ fn airRet(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
if (cg.return_value != .none) { if (cg.return_value != .none) {
try cg.store(cg.return_value, operand, ret_ty, 0); try cg.store(cg.return_value, operand, ret_ty, 0);
} else if (fn_info.cc == .wasm_mvp and ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) { } else if (fn_info.cc == .wasm_mvp and ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
switch (ret_ty.zigTypeTag(zcu)) { switch (abi.classifyType(ret_ty, zcu)) {
// Aggregate types can be lowered as a singular value .direct => |scalar_type| {
.@"struct", .@"union" => { assert(!abi.lowerAsDoubleI64(scalar_type, zcu));
const scalar_type = abi.scalarType(ret_ty, zcu); if (!isByRef(ret_ty, zcu, cg.target)) {
try cg.emitWValue(operand); try cg.emitWValue(operand);
const opcode = buildOpcode(.{ } else {
.op = .load, _ = try cg.load(operand, scalar_type, 0);
.width = @as(u8, @intCast(scalar_type.abiSize(zcu) * 8)), }
.signedness = if (scalar_type.isSignedInt(zcu)) .signed else .unsigned,
.valtype1 = typeToValtype(scalar_type, zcu, cg.target),
});
try cg.addMemArg(Mir.Inst.Tag.fromOpcode(opcode), .{
.offset = operand.offset(),
.alignment = @intCast(scalar_type.abiAlignment(zcu).toByteUnits().?),
});
}, },
else => try cg.emitWValue(operand), .indirect => unreachable,
} }
} else { } else {
if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu) and ret_ty.isError(zcu)) { if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu) and ret_ty.isError(zcu)) {
@ -2284,14 +2280,24 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifie
break :result_value .none; break :result_value .none;
} else if (first_param_sret) { } else if (first_param_sret) {
break :result_value sret; break :result_value sret;
// TODO: Make this less fragile and optimize } else if (zcu.typeToFunc(fn_ty).?.cc == .wasm_mvp) {
} else if (zcu.typeToFunc(fn_ty).?.cc == .wasm_mvp and ret_ty.zigTypeTag(zcu) == .@"struct" or ret_ty.zigTypeTag(zcu) == .@"union") { switch (abi.classifyType(ret_ty, zcu)) {
const result_local = try cg.allocLocal(ret_ty); .direct => |scalar_type| {
try cg.addLocal(.local_set, result_local.local.value); assert(!abi.lowerAsDoubleI64(scalar_type, zcu));
const scalar_type = abi.scalarType(ret_ty, zcu); if (!isByRef(ret_ty, zcu, cg.target)) {
const result = try cg.allocStack(scalar_type); const result_local = try cg.allocLocal(ret_ty);
try cg.store(result, result_local, scalar_type, 0); try cg.addLocal(.local_set, result_local.local.value);
break :result_value result; break :result_value result_local;
} else {
const result_local = try cg.allocLocal(ret_ty);
try cg.addLocal(.local_set, result_local.local.value);
const result = try cg.allocStack(ret_ty);
try cg.store(result, result_local, scalar_type, 0);
break :result_value result;
}
},
.indirect => unreachable,
}
} else { } else {
const result_local = try cg.allocLocal(ret_ty); const result_local = try cg.allocLocal(ret_ty);
try cg.addLocal(.local_set, result_local.local.value); try cg.addLocal(.local_set, result_local.local.value);
@ -2597,26 +2603,17 @@ fn airArg(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
const cc = zcu.typeToFunc(zcu.navValue(cg.owner_nav).typeOf(zcu)).?.cc; const cc = zcu.typeToFunc(zcu.navValue(cg.owner_nav).typeOf(zcu)).?.cc;
const arg_ty = cg.typeOfIndex(inst); const arg_ty = cg.typeOfIndex(inst);
if (cc == .wasm_mvp) { if (cc == .wasm_mvp) {
const arg_classes = abi.classifyType(arg_ty, zcu); switch (abi.classifyType(arg_ty, zcu)) {
for (arg_classes) |class| { .direct => |scalar_ty| if (!abi.lowerAsDoubleI64(scalar_ty, zcu)) {
if (class != .none) {
cg.arg_index += 1; cg.arg_index += 1;
} } else {
} cg.arg_index += 2;
const result = try cg.allocStack(arg_ty);
// When we have an argument that's passed using more than a single parameter, try cg.store(result, arg, Type.u64, 0);
// we combine them into a single stack value try cg.store(result, cg.args[arg_index + 1], Type.u64, 8);
if (arg_classes[0] == .direct and arg_classes[1] == .direct) { return cg.finishAir(inst, result, &.{});
if (arg_ty.zigTypeTag(zcu) != .int and arg_ty.zigTypeTag(zcu) != .float) { },
return cg.fail( .indirect => cg.arg_index += 1,
"TODO: Implement C-ABI argument for type '{}'",
.{arg_ty.fmt(pt)},
);
}
const result = try cg.allocStack(arg_ty);
try cg.store(result, arg, Type.u64, 0);
try cg.store(result, cg.args[arg_index + 1], Type.u64, 8);
return cg.finishAir(inst, result, &.{});
} }
} else { } else {
cg.arg_index += 1; cg.arg_index += 1;

View file

@ -13,70 +13,55 @@ const Zcu = @import("../../Zcu.zig");
/// Defines how to pass a type as part of a function signature, /// Defines how to pass a type as part of a function signature,
/// both for parameters as well as return values. /// both for parameters as well as return values.
pub const Class = enum { direct, indirect, none }; pub const Class = union(enum) {
direct: Type,
const none: [2]Class = .{ .none, .none }; indirect,
const memory: [2]Class = .{ .indirect, .none }; };
const direct: [2]Class = .{ .direct, .none };
/// Classifies a given Zig type to determine how they must be passed /// Classifies a given Zig type to determine how they must be passed
/// or returned as value within a wasm function. /// or returned as value within a wasm function.
/// When all elements result in `.none`, no value must be passed in or returned. pub fn classifyType(ty: Type, zcu: *const Zcu) Class {
pub fn classifyType(ty: Type, zcu: *const Zcu) [2]Class {
const ip = &zcu.intern_pool; const ip = &zcu.intern_pool;
const target = zcu.getTarget(); assert(ty.hasRuntimeBitsIgnoreComptime(zcu));
if (!ty.hasRuntimeBitsIgnoreComptime(zcu)) return none;
switch (ty.zigTypeTag(zcu)) { switch (ty.zigTypeTag(zcu)) {
.int, .@"enum", .error_set => return .{ .direct = ty },
.float => return .{ .direct = ty },
.bool => return .{ .direct = ty },
.vector => return .{ .direct = ty },
.array => return .indirect,
.optional => {
assert(ty.isPtrLikeOptional(zcu));
return .{ .direct = ty };
},
.pointer => {
assert(!ty.isSlice(zcu));
return .{ .direct = ty };
},
.@"struct" => { .@"struct" => {
const struct_type = zcu.typeToStruct(ty).?; const struct_type = zcu.typeToStruct(ty).?;
if (struct_type.layout == .@"packed") { if (struct_type.layout == .@"packed") {
if (ty.bitSize(zcu) <= 64) return direct; return .{ .direct = ty };
return .{ .direct, .direct };
} }
if (struct_type.field_types.len > 1) { if (struct_type.field_types.len > 1) {
// The struct type is non-scalar. // The struct type is non-scalar.
return memory; return .indirect;
} }
const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[0]); const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[0]);
const explicit_align = struct_type.fieldAlign(ip, 0); const explicit_align = struct_type.fieldAlign(ip, 0);
if (explicit_align != .none) { if (explicit_align != .none) {
if (explicit_align.compareStrict(.gt, field_ty.abiAlignment(zcu))) if (explicit_align.compareStrict(.gt, field_ty.abiAlignment(zcu)))
return memory; return .indirect;
} }
return classifyType(field_ty, zcu); return classifyType(field_ty, zcu);
}, },
.int, .@"enum", .error_set => {
const int_bits = ty.intInfo(zcu).bits;
if (int_bits <= 64) return direct;
if (int_bits <= 128) return .{ .direct, .direct };
return memory;
},
.float => {
const float_bits = ty.floatBits(target);
if (float_bits <= 64) return direct;
if (float_bits <= 128) return .{ .direct, .direct };
return memory;
},
.bool => return direct,
.vector => return direct,
.array => return memory,
.optional => {
assert(ty.isPtrLikeOptional(zcu));
return direct;
},
.pointer => {
assert(!ty.isSlice(zcu));
return direct;
},
.@"union" => { .@"union" => {
const union_obj = zcu.typeToUnion(ty).?; const union_obj = zcu.typeToUnion(ty).?;
if (union_obj.flagsUnordered(ip).layout == .@"packed") { if (union_obj.flagsUnordered(ip).layout == .@"packed") {
if (ty.bitSize(zcu) <= 64) return direct; return .{ .direct = ty };
return .{ .direct, .direct };
} }
const layout = ty.unionGetLayout(zcu); const layout = ty.unionGetLayout(zcu);
assert(layout.tag_size == 0); assert(layout.tag_size == 0);
if (union_obj.field_types.len > 1) return memory; if (union_obj.field_types.len > 1) return .indirect;
const first_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[0]); const first_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[0]);
return classifyType(first_field_ty, zcu); return classifyType(first_field_ty, zcu);
}, },
@ -97,32 +82,6 @@ pub fn classifyType(ty: Type, zcu: *const Zcu) [2]Class {
} }
} }
/// Returns the scalar type a given type can represent. pub fn lowerAsDoubleI64(scalar_ty: Type, zcu: *const Zcu) bool {
/// Asserts given type can be represented as scalar, such as return scalar_ty.bitSize(zcu) > 64;
/// a struct with a single scalar field.
pub fn scalarType(ty: Type, zcu: *Zcu) Type {
const ip = &zcu.intern_pool;
switch (ty.zigTypeTag(zcu)) {
.@"struct" => {
if (zcu.typeToPackedStruct(ty)) |packed_struct| {
return scalarType(Type.fromInterned(packed_struct.backingIntTypeUnordered(ip)), zcu);
} else {
assert(ty.structFieldCount(zcu) == 1);
return scalarType(ty.fieldType(0, zcu), zcu);
}
},
.@"union" => {
const union_obj = zcu.typeToUnion(ty).?;
if (union_obj.flagsUnordered(ip).layout != .@"packed") {
const layout = Type.getUnionLayout(union_obj, zcu);
if (layout.payload_size == 0 and layout.tag_size != 0) {
return scalarType(ty.unionTagTypeSafety(zcu).?, zcu);
}
assert(union_obj.field_types.len == 1);
}
const first_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[0]);
return scalarType(first_field_ty, zcu);
},
else => return ty,
}
} }

View file

@ -11723,7 +11723,7 @@ fn firstParamSRet(fn_info: InternPool.Key.FuncType, zcu: *Zcu, target: std.Targe
.x86_64_win => x86_64_abi.classifyWindows(return_type, zcu) == .memory, .x86_64_win => x86_64_abi.classifyWindows(return_type, zcu) == .memory,
.x86_sysv, .x86_win => isByRef(return_type, zcu), .x86_sysv, .x86_win => isByRef(return_type, zcu),
.x86_stdcall => !isScalar(zcu, return_type), .x86_stdcall => !isScalar(zcu, return_type),
.wasm_mvp => wasm_c_abi.classifyType(return_type, zcu)[0] == .indirect, .wasm_mvp => wasm_c_abi.classifyType(return_type, zcu) == .indirect,
.aarch64_aapcs, .aarch64_aapcs,
.aarch64_aapcs_darwin, .aarch64_aapcs_darwin,
.aarch64_aapcs_win, .aarch64_aapcs_win,
@ -11808,18 +11808,9 @@ fn lowerFnRetTy(o: *Object, fn_info: InternPool.Key.FuncType) Allocator.Error!Bu
return o.builder.structType(.normal, types[0..types_len]); return o.builder.structType(.normal, types[0..types_len]);
}, },
}, },
.wasm_mvp => { .wasm_mvp => switch (wasm_c_abi.classifyType(return_type, zcu)) {
if (isScalar(zcu, return_type)) { .direct => |scalar_ty| return o.lowerType(scalar_ty),
return o.lowerType(return_type); .indirect => return .void,
}
const classes = wasm_c_abi.classifyType(return_type, zcu);
if (classes[0] == .indirect or classes[0] == .none) {
return .void;
}
assert(classes[0] == .direct and classes[1] == .none);
const scalar_type = wasm_c_abi.scalarType(return_type, zcu);
return o.builder.intType(@intCast(scalar_type.abiSize(zcu) * 8));
}, },
// TODO investigate other callconvs // TODO investigate other callconvs
else => return o.lowerType(return_type), else => return o.lowerType(return_type),
@ -12073,17 +12064,28 @@ const ParamTypeIterator = struct {
}, },
} }
}, },
.wasm_mvp => { .wasm_mvp => switch (wasm_c_abi.classifyType(ty, zcu)) {
it.zig_index += 1; .direct => |scalar_ty| {
it.llvm_index += 1; if (isScalar(zcu, ty)) {
if (isScalar(zcu, ty)) { it.zig_index += 1;
return .byval; it.llvm_index += 1;
} return .byval;
const classes = wasm_c_abi.classifyType(ty, zcu); } else {
if (classes[0] == .indirect) { var types_buffer: [8]Builder.Type = undefined;
types_buffer[0] = try it.object.lowerType(scalar_ty);
it.types_buffer = types_buffer;
it.types_len = 1;
it.llvm_index += 1;
it.zig_index += 1;
return .multiple_llvm_types;
}
},
.indirect => {
it.zig_index += 1;
it.llvm_index += 1;
it.byval_attr = true;
return .byref; return .byref;
} },
return .abi_sized_int;
}, },
// TODO investigate other callconvs // TODO investigate other callconvs
else => { else => {

View file

@ -4627,10 +4627,13 @@ fn convertZcuFnType(
try params_buffer.append(gpa, .i32); // memory address is always a 32-bit handle try params_buffer.append(gpa, .i32); // memory address is always a 32-bit handle
} else if (return_type.hasRuntimeBitsIgnoreComptime(zcu)) { } else if (return_type.hasRuntimeBitsIgnoreComptime(zcu)) {
if (cc == .wasm_mvp) { if (cc == .wasm_mvp) {
const res_classes = abi.classifyType(return_type, zcu); switch (abi.classifyType(return_type, zcu)) {
assert(res_classes[0] == .direct and res_classes[1] == .none); .direct => |scalar_ty| {
const scalar_type = abi.scalarType(return_type, zcu); assert(!abi.lowerAsDoubleI64(scalar_ty, zcu));
try returns_buffer.append(gpa, CodeGen.typeToValtype(scalar_type, zcu, target)); try returns_buffer.append(gpa, CodeGen.typeToValtype(scalar_ty, zcu, target));
},
.indirect => unreachable,
}
} else { } else {
try returns_buffer.append(gpa, CodeGen.typeToValtype(return_type, zcu, target)); try returns_buffer.append(gpa, CodeGen.typeToValtype(return_type, zcu, target));
} }
@ -4645,18 +4648,16 @@ fn convertZcuFnType(
switch (cc) { switch (cc) {
.wasm_mvp => { .wasm_mvp => {
const param_classes = abi.classifyType(param_type, zcu); switch (abi.classifyType(param_type, zcu)) {
if (param_classes[1] == .none) { .direct => |scalar_ty| {
if (param_classes[0] == .direct) { if (!abi.lowerAsDoubleI64(scalar_ty, zcu)) {
const scalar_type = abi.scalarType(param_type, zcu); try params_buffer.append(gpa, CodeGen.typeToValtype(scalar_ty, zcu, target));
try params_buffer.append(gpa, CodeGen.typeToValtype(scalar_type, zcu, target)); } else {
} else { try params_buffer.append(gpa, .i64);
try params_buffer.append(gpa, CodeGen.typeToValtype(param_type, zcu, target)); try params_buffer.append(gpa, .i64);
} }
} else { },
// i128/f128 .indirect => try params_buffer.append(gpa, CodeGen.typeToValtype(param_type, zcu, target)),
try params_buffer.append(gpa, .i64);
try params_buffer.append(gpa, .i64);
} }
}, },
else => try params_buffer.append(gpa, CodeGen.typeToValtype(param_type, zcu, target)), else => try params_buffer.append(gpa, CodeGen.typeToValtype(param_type, zcu, target)),

View file

@ -227,6 +227,38 @@ void c_struct_u64_u64_8(size_t, size_t, size_t, size_t, size_t, size_t, size_t,
assert_or_panic(s.b == 40); assert_or_panic(s.b == 40);
} }
struct Struct_f32 {
float a;
};
struct Struct_f32 zig_ret_struct_f32(void);
void zig_struct_f32(struct Struct_f32);
struct Struct_f32 c_ret_struct_f32(void) {
return (struct Struct_f32){ 2.5f };
}
void c_struct_f32(struct Struct_f32 s) {
assert_or_panic(s.a == 2.5f);
}
struct Struct_f64 {
double a;
};
struct Struct_f64 zig_ret_struct_f64(void);
void zig_struct_f64(struct Struct_f64);
struct Struct_f64 c_ret_struct_f64(void) {
return (struct Struct_f64){ 2.5 };
}
void c_struct_f64(struct Struct_f64 s) {
assert_or_panic(s.a == 2.5);
}
struct Struct_f32f32_f32 { struct Struct_f32f32_f32 {
struct { struct {
float b, c; float b, c;
@ -296,6 +328,13 @@ void c_struct_u32_union_u32_u32u32(struct Struct_u32_Union_u32_u32u32 s) {
assert_or_panic(s.b.c.e == 3); assert_or_panic(s.b.c.e == 3);
} }
struct Struct_i32_i32 {
int32_t a;
int32_t b;
};
void zig_struct_i32_i32(struct Struct_i32_i32);
struct BigStruct { struct BigStruct {
uint64_t a; uint64_t a;
uint64_t b; uint64_t b;
@ -2674,6 +2713,18 @@ void run_c_tests(void) {
} }
#if !defined(ZIG_RISCV64) #if !defined(ZIG_RISCV64)
{
struct Struct_f32 s = zig_ret_struct_f32();
assert_or_panic(s.a == 2.5f);
zig_struct_f32((struct Struct_f32){ 2.5f });
}
{
struct Struct_f64 s = zig_ret_struct_f64();
assert_or_panic(s.a == 2.5);
zig_struct_f64((struct Struct_f64){ 2.5 });
}
{ {
struct Struct_f32f32_f32 s = zig_ret_struct_f32f32_f32(); struct Struct_f32f32_f32 s = zig_ret_struct_f32f32_f32();
assert_or_panic(s.a.b == 1.0f); assert_or_panic(s.a.b == 1.0f);
@ -2699,6 +2750,10 @@ void run_c_tests(void) {
assert_or_panic(s.b.c.e == 3); assert_or_panic(s.b.c.e == 3);
zig_struct_u32_union_u32_u32u32(s); zig_struct_u32_union_u32_u32u32(s);
} }
{
struct Struct_i32_i32 s = {1, 2};
zig_struct_i32_i32(s);
}
#endif #endif
{ {
@ -5024,6 +5079,21 @@ double complex c_cmultd(double complex a, double complex b) {
return 1.5 + I * 13.5; return 1.5 + I * 13.5;
} }
struct Struct_i32_i32 c_mut_struct_i32_i32(struct Struct_i32_i32 s) {
assert_or_panic(s.a == 1);
assert_or_panic(s.b == 2);
s.a += 100;
s.b += 250;
assert_or_panic(s.a == 101);
assert_or_panic(s.b == 252);
return s;
}
void c_struct_i32_i32(struct Struct_i32_i32 s) {
assert_or_panic(s.a == 1);
assert_or_panic(s.b == 2);
}
void c_big_struct(struct BigStruct x) { void c_big_struct(struct BigStruct x) {
assert_or_panic(x.a == 1); assert_or_panic(x.a == 1);
assert_or_panic(x.b == 2); assert_or_panic(x.b == 2);

View file

@ -13,7 +13,7 @@ const expectEqual = std.testing.expectEqual;
const have_i128 = builtin.cpu.arch != .x86 and !builtin.cpu.arch.isArm() and const have_i128 = builtin.cpu.arch != .x86 and !builtin.cpu.arch.isArm() and
!builtin.cpu.arch.isMIPS() and !builtin.cpu.arch.isPowerPC32(); !builtin.cpu.arch.isMIPS() and !builtin.cpu.arch.isPowerPC32();
const have_f128 = builtin.cpu.arch.isX86() and !builtin.os.tag.isDarwin(); const have_f128 = builtin.cpu.arch.isWasm() or (builtin.cpu.arch.isX86() and !builtin.os.tag.isDarwin());
const have_f80 = builtin.cpu.arch.isX86(); const have_f80 = builtin.cpu.arch.isX86();
extern fn run_c_tests() void; extern fn run_c_tests() void;
@ -339,6 +339,56 @@ test "C ABI struct u64 u64" {
c_struct_u64_u64_8(0, 1, 2, 3, 4, 5, 6, 7, .{ .a = 39, .b = 40 }); c_struct_u64_u64_8(0, 1, 2, 3, 4, 5, 6, 7, .{ .a = 39, .b = 40 });
} }
const Struct_f32 = extern struct {
a: f32,
};
export fn zig_ret_struct_f32() Struct_f32 {
return .{ .a = 2.5 };
}
export fn zig_struct_f32(s: Struct_f32) void {
expect(s.a == 2.5) catch @panic("test failure");
}
extern fn c_ret_struct_f32() Struct_f32;
extern fn c_struct_f32(Struct_f32) void;
test "C ABI struct f32" {
if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest;
if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest;
const s = c_ret_struct_f32();
try expect(s.a == 2.5);
c_struct_f32(.{ .a = 2.5 });
}
const Struct_f64 = extern struct {
a: f64,
};
export fn zig_ret_struct_f64() Struct_f64 {
return .{ .a = 2.5 };
}
export fn zig_struct_f64(s: Struct_f64) void {
expect(s.a == 2.5) catch @panic("test failure");
}
extern fn c_ret_struct_f64() Struct_f64;
extern fn c_struct_f64(Struct_f64) void;
test "C ABI struct f64" {
if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest;
if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest;
const s = c_ret_struct_f64();
try expect(s.a == 2.5);
c_struct_f64(.{ .a = 2.5 });
}
const Struct_f32f32_f32 = extern struct { const Struct_f32f32_f32 = extern struct {
a: extern struct { b: f32, c: f32 }, a: extern struct { b: f32, c: f32 },
d: f32, d: f32,
@ -434,6 +484,34 @@ test "C ABI struct{u32,union{u32,struct{u32,u32}}}" {
c_struct_u32_union_u32_u32u32(.{ .a = 1, .b = .{ .c = .{ .d = 2, .e = 3 } } }); c_struct_u32_union_u32_u32u32(.{ .a = 1, .b = .{ .c = .{ .d = 2, .e = 3 } } });
} }
const Struct_i32_i32 = extern struct {
a: i32,
b: i32,
};
extern fn c_mut_struct_i32_i32(Struct_i32_i32) Struct_i32_i32;
extern fn c_struct_i32_i32(Struct_i32_i32) void;
test "C ABI struct i32 i32" {
if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest;
if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest;
const s: Struct_i32_i32 = .{
.a = 1,
.b = 2,
};
const mut_res = c_mut_struct_i32_i32(s);
try expect(s.a == 1);
try expect(s.b == 2);
try expect(mut_res.a == 101);
try expect(mut_res.b == 252);
c_struct_i32_i32(s);
}
export fn zig_struct_i32_i32(s: Struct_i32_i32) void {
expect(s.a == 1) catch @panic("test failure: zig_struct_i32_i32 1");
expect(s.b == 2) catch @panic("test failure: zig_struct_i32_i32 2");
}
const BigStruct = extern struct { const BigStruct = extern struct {
a: u64, a: u64,
b: u64, b: u64,
@ -5591,64 +5669,56 @@ test "f80 extra struct" {
try expect(a.b == 24); try expect(a.b == 24);
} }
comptime { export fn zig_f128(x: f128) f128 {
skip: { expect(x == 12) catch @panic("test failure");
if (builtin.target.cpu.arch.isWasm()) break :skip; return 34;
}
extern fn c_f128(f128) f128;
test "f128 bare" {
if (!have_f128) return error.SkipZigTest;
_ = struct { const a = c_f128(12.34);
export fn zig_f128(x: f128) f128 { try expect(@as(f64, @floatCast(a)) == 56.78);
expect(x == 12) catch @panic("test failure"); }
return 34;
}
extern fn c_f128(f128) f128;
test "f128 bare" {
if (!have_f128) return error.SkipZigTest;
const a = c_f128(12.34); const f128_struct = extern struct {
try expect(@as(f64, @floatCast(a)) == 56.78); a: f128,
} };
export fn zig_f128_struct(a: f128_struct) f128_struct {
expect(a.a == 12345) catch @panic("test failure");
return .{ .a = 98765 };
}
extern fn c_f128_struct(f128_struct) f128_struct;
test "f128 struct" {
if (!have_f128) return error.SkipZigTest;
const f128_struct = extern struct { const a = c_f128_struct(.{ .a = 12.34 });
a: f128, try expect(@as(f64, @floatCast(a.a)) == 56.78);
};
export fn zig_f128_struct(a: f128_struct) f128_struct {
expect(a.a == 12345) catch @panic("test failure");
return .{ .a = 98765 };
}
extern fn c_f128_struct(f128_struct) f128_struct;
test "f128 struct" {
if (!have_f128) return error.SkipZigTest;
const a = c_f128_struct(.{ .a = 12.34 }); const b = c_f128_f128_struct(.{ .a = 12.34, .b = 87.65 });
try expect(@as(f64, @floatCast(a.a)) == 56.78); try expect(@as(f64, @floatCast(b.a)) == 56.78);
try expect(@as(f64, @floatCast(b.b)) == 43.21);
}
const b = c_f128_f128_struct(.{ .a = 12.34, .b = 87.65 }); const f128_f128_struct = extern struct {
try expect(@as(f64, @floatCast(b.a)) == 56.78); a: f128,
try expect(@as(f64, @floatCast(b.b)) == 43.21); b: f128,
} };
export fn zig_f128_f128_struct(a: f128_f128_struct) f128_f128_struct {
expect(a.a == 13) catch @panic("test failure");
expect(a.b == 57) catch @panic("test failure");
return .{ .a = 24, .b = 68 };
}
extern fn c_f128_f128_struct(f128_f128_struct) f128_f128_struct;
test "f128 f128 struct" {
if (!have_f128) return error.SkipZigTest;
const f128_f128_struct = extern struct { const a = c_f128_struct(.{ .a = 12.34 });
a: f128, try expect(@as(f64, @floatCast(a.a)) == 56.78);
b: f128,
};
export fn zig_f128_f128_struct(a: f128_f128_struct) f128_f128_struct {
expect(a.a == 13) catch @panic("test failure");
expect(a.b == 57) catch @panic("test failure");
return .{ .a = 24, .b = 68 };
}
extern fn c_f128_f128_struct(f128_f128_struct) f128_f128_struct;
test "f128 f128 struct" {
if (!have_f128) return error.SkipZigTest;
const a = c_f128_struct(.{ .a = 12.34 }); const b = c_f128_f128_struct(.{ .a = 12.34, .b = 87.65 });
try expect(@as(f64, @floatCast(a.a)) == 56.78); try expect(@as(f64, @floatCast(b.a)) == 56.78);
try expect(@as(f64, @floatCast(b.b)) == 43.21);
const b = c_f128_f128_struct(.{ .a = 12.34, .b = 87.65 });
try expect(@as(f64, @floatCast(b.a)) == 56.78);
try expect(@as(f64, @floatCast(b.b)) == 43.21);
}
};
}
} }
// The stdcall attribute on C functions is ignored when compiled on non-x86 // The stdcall attribute on C functions is ignored when compiled on non-x86