authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-04-06 03:45:23-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-04-06 13:02:55-07:00
logf668c8bfd65489d1d38716e2973e0ee1cf0e8c52
treee727d4ffb756663eebe3844eb362b995e838e2be
parent34bb670bb695969ce57cb3fc0e1bb673a1cc7243

x86_64: fix abi of nested structs


3 files changed, 225 insertions(+), 105 deletions(-)

src/arch/x86_64/abi.zig+102-99
......@@ -13,7 +13,7 @@ pub const Class = enum {
1313 integer_per_element,
1414};
1515
16pub fn classifyWindows(ty: Type, mod: *Module) Class {
16pub fn classifyWindows(ty: Type, zcu: *Zcu) Class {
1717 // https://docs.microsoft.com/en-gb/cpp/build/x64-calling-convention?view=vs-2017
1818 // "There's a strict one-to-one correspondence between a function call's arguments
1919 // and the registers used for those arguments. Any argument that doesn't fit in 8
......@@ -22,7 +22,7 @@ pub fn classifyWindows(ty: Type, mod: *Module) Class {
2222 // "All floating point operations are done using the 16 XMM registers."
2323 // "Structs and unions of size 8, 16, 32, or 64 bits, and __m64 types, are passed
2424 // as if they were integers of the same size."
25 switch (ty.zigTypeTag(mod)) {
25 switch (ty.zigTypeTag(zcu)) {
2626 .Pointer,
2727 .Int,
2828 .Bool,
......@@ -37,12 +37,12 @@ pub fn classifyWindows(ty: Type, mod: *Module) Class {
3737 .ErrorUnion,
3838 .AnyFrame,
3939 .Frame,
40 => switch (ty.abiSize(mod)) {
40 => switch (ty.abiSize(zcu)) {
4141 0 => unreachable,
4242 1, 2, 4, 8 => return .integer,
43 else => switch (ty.zigTypeTag(mod)) {
43 else => switch (ty.zigTypeTag(zcu)) {
4444 .Int => return .win_i128,
45 .Struct, .Union => if (ty.containerLayout(mod) == .@"packed") {
45 .Struct, .Union => if (ty.containerLayout(zcu) == .@"packed") {
4646 return .win_i128;
4747 } else {
4848 return .memory;
......@@ -69,16 +69,16 @@ pub const Context = enum { ret, arg, field, other };
6969
7070/// There are a maximum of 8 possible return slots. Returned values are in
7171/// the beginning of the array; unused slots are filled with .none.
72pub fn classifySystemV(ty: Type, mod: *Module, ctx: Context) [8]Class {
73 const ip = &mod.intern_pool;
74 const target = mod.getTarget();
72pub fn classifySystemV(ty: Type, zcu: *Zcu, ctx: Context) [8]Class {
73 const ip = &zcu.intern_pool;
74 const target = zcu.getTarget();
7575 const memory_class = [_]Class{
7676 .memory, .none, .none, .none,
7777 .none, .none, .none, .none,
7878 };
7979 var result = [1]Class{.none} ** 8;
80 switch (ty.zigTypeTag(mod)) {
81 .Pointer => switch (ty.ptrSize(mod)) {
80 switch (ty.zigTypeTag(zcu)) {
81 .Pointer => switch (ty.ptrSize(zcu)) {
8282 .Slice => {
8383 result[0] = .integer;
8484 result[1] = .integer;
......@@ -90,7 +90,7 @@ pub fn classifySystemV(ty: Type, mod: *Module, ctx: Context) [8]Class {
9090 },
9191 },
9292 .Int, .Enum, .ErrorSet => {
93 const bits = ty.intInfo(mod).bits;
93 const bits = ty.intInfo(zcu).bits;
9494 if (bits <= 64) {
9595 result[0] = .integer;
9696 return result;
......@@ -160,8 +160,8 @@ pub fn classifySystemV(ty: Type, mod: *Module, ctx: Context) [8]Class {
160160 else => unreachable,
161161 },
162162 .Vector => {
163 const elem_ty = ty.childType(mod);
164 const bits = elem_ty.bitSize(mod) * ty.arrayLen(mod);
163 const elem_ty = ty.childType(zcu);
164 const bits = elem_ty.bitSize(zcu) * ty.arrayLen(zcu);
165165 if (elem_ty.toIntern() == .bool_type) {
166166 if (bits <= 32) return .{
167167 .integer, .none, .none, .none,
......@@ -225,7 +225,7 @@ pub fn classifySystemV(ty: Type, mod: *Module, ctx: Context) [8]Class {
225225 return memory_class;
226226 },
227227 .Optional => {
228 if (ty.isPtrLikeOptional(mod)) {
228 if (ty.isPtrLikeOptional(zcu)) {
229229 result[0] = .integer;
230230 return result;
231231 }
......@@ -236,9 +236,9 @@ pub fn classifySystemV(ty: Type, mod: *Module, ctx: Context) [8]Class {
236236 // it contains unaligned fields, it has class MEMORY"
237237 // "If the size of the aggregate exceeds a single eightbyte, each is classified
238238 // separately.".
239 const struct_type = mod.typeToStruct(ty).?;
240 const ty_size = ty.abiSize(mod);
241 if (struct_type.layout == .@"packed") {
239 const loaded_struct = ip.loadStructType(ty.toIntern());
240 const ty_size = ty.abiSize(zcu);
241 if (loaded_struct.layout == .@"packed") {
242242 assert(ty_size <= 16);
243243 result[0] = .integer;
244244 if (ty_size > 8) result[1] = .integer;
......@@ -247,82 +247,8 @@ pub fn classifySystemV(ty: Type, mod: *Module, ctx: Context) [8]Class {
247247 if (ty_size > 64)
248248 return memory_class;
249249
250 var result_i: usize = 0; // out of 8
251 var byte_i: usize = 0; // out of 8
252 for (struct_type.field_types.get(ip), 0..) |field_ty_ip, i| {
253 const field_ty = Type.fromInterned(field_ty_ip);
254 const field_align = struct_type.fieldAlign(ip, i);
255 if (field_align != .none and field_align.compare(.lt, field_ty.abiAlignment(mod)))
256 return memory_class;
257 const field_size = field_ty.abiSize(mod);
258 const field_class_array = classifySystemV(field_ty, mod, .field);
259 const field_class = std.mem.sliceTo(&field_class_array, .none);
260 if (byte_i + field_size <= 8) {
261 // Combine this field with the previous one.
262 combine: {
263 // "If both classes are equal, this is the resulting class."
264 if (result[result_i] == field_class[0]) {
265 if (result[result_i] == .float) {
266 result[result_i] = .float_combine;
267 }
268 break :combine;
269 }
270
271 // "If one of the classes is NO_CLASS, the resulting class
272 // is the other class."
273 if (result[result_i] == .none) {
274 result[result_i] = field_class[0];
275 break :combine;
276 }
277 assert(field_class[0] != .none);
278
279 // "If one of the classes is MEMORY, the result is the MEMORY class."
280 if (result[result_i] == .memory or field_class[0] == .memory) {
281 result[result_i] = .memory;
282 break :combine;
283 }
284
285 // "If one of the classes is INTEGER, the result is the INTEGER."
286 if (result[result_i] == .integer or field_class[0] == .integer) {
287 result[result_i] = .integer;
288 break :combine;
289 }
290
291 // "If one of the classes is X87, X87UP, COMPLEX_X87 class,
292 // MEMORY is used as class."
293 if (result[result_i] == .x87 or
294 result[result_i] == .x87up or
295 result[result_i] == .complex_x87 or
296 field_class[0] == .x87 or
297 field_class[0] == .x87up or
298 field_class[0] == .complex_x87)
299 {
300 result[result_i] = .memory;
301 break :combine;
302 }
303
304 // "Otherwise class SSE is used."
305 result[result_i] = .sse;
306 }
307 byte_i += @as(usize, @intCast(field_size));
308 if (byte_i == 8) {
309 byte_i = 0;
310 result_i += 1;
311 }
312 } else {
313 // Cannot combine this field with the previous one.
314 if (byte_i != 0) {
315 byte_i = 0;
316 result_i += 1;
317 }
318 @memcpy(result[result_i..][0..field_class.len], field_class);
319 result_i += field_class.len;
320 // If there are any bytes leftover, we have to try to combine
321 // the next field with them.
322 byte_i = @as(usize, @intCast(field_size % 8));
323 if (byte_i != 0) result_i -= 1;
324 }
325 }
250 var byte_offset: u64 = 0;
251 classifySystemVStruct(&result, &byte_offset, loaded_struct, zcu);
326252
327253 // Post-merger cleanup
328254
......@@ -354,8 +280,8 @@ pub fn classifySystemV(ty: Type, mod: *Module, ctx: Context) [8]Class {
354280 // it contains unaligned fields, it has class MEMORY"
355281 // "If the size of the aggregate exceeds a single eightbyte, each is classified
356282 // separately.".
357 const union_obj = mod.typeToUnion(ty).?;
358 const ty_size = mod.unionAbiSize(union_obj);
283 const union_obj = zcu.typeToUnion(ty).?;
284 const ty_size = zcu.unionAbiSize(union_obj);
359285 if (union_obj.getLayout(ip) == .@"packed") {
360286 assert(ty_size <= 16);
361287 result[0] = .integer;
......@@ -368,12 +294,12 @@ pub fn classifySystemV(ty: Type, mod: *Module, ctx: Context) [8]Class {
368294 for (union_obj.field_types.get(ip), 0..) |field_ty, field_index| {
369295 const field_align = union_obj.fieldAlign(ip, @intCast(field_index));
370296 if (field_align != .none and
371 field_align.compare(.lt, Type.fromInterned(field_ty).abiAlignment(mod)))
297 field_align.compare(.lt, Type.fromInterned(field_ty).abiAlignment(zcu)))
372298 {
373299 return memory_class;
374300 }
375301 // Combine this field with the previous one.
376 const field_class = classifySystemV(Type.fromInterned(field_ty), mod, .field);
302 const field_class = classifySystemV(Type.fromInterned(field_ty), zcu, .field);
377303 for (&result, 0..) |*result_item, i| {
378304 const field_item = field_class[i];
379305 // "If both classes are equal, this is the resulting class."
......@@ -447,7 +373,7 @@ pub fn classifySystemV(ty: Type, mod: *Module, ctx: Context) [8]Class {
447373 return result;
448374 },
449375 .Array => {
450 const ty_size = ty.abiSize(mod);
376 const ty_size = ty.abiSize(zcu);
451377 if (ty_size <= 8) {
452378 result[0] = .integer;
453379 return result;
......@@ -463,6 +389,82 @@ pub fn classifySystemV(ty: Type, mod: *Module, ctx: Context) [8]Class {
463389 }
464390}
465391
392fn classifySystemVStruct(
393 result: *[8]Class,
394 byte_offset: *u64,
395 loaded_struct: InternPool.LoadedStructType,
396 zcu: *Zcu,
397) void {
398 const ip = &zcu.intern_pool;
399 var field_it = loaded_struct.iterateRuntimeOrder(ip);
400 while (field_it.next()) |field_index| {
401 const field_ty = Type.fromInterned(loaded_struct.field_types.get(ip)[field_index]);
402 const field_align = loaded_struct.fieldAlign(ip, field_index);
403 byte_offset.* = std.mem.alignForward(
404 u64,
405 byte_offset.*,
406 field_align.toByteUnits() orelse field_ty.abiAlignment(zcu).toByteUnits().?,
407 );
408 if (zcu.typeToStruct(field_ty)) |field_loaded_struct| {
409 if (field_loaded_struct.layout != .@"packed") {
410 classifySystemVStruct(result, byte_offset, field_loaded_struct, zcu);
411 continue;
412 }
413 }
414 const field_class = std.mem.sliceTo(&classifySystemV(field_ty, zcu, .field), .none);
415 const field_size = field_ty.abiSize(zcu);
416 combine: {
417 // Combine this field with the previous one.
418 const result_class = &result[@intCast(byte_offset.* / 8)];
419 // "If both classes are equal, this is the resulting class."
420 if (result_class.* == field_class[0]) {
421 if (result_class.* == .float) {
422 result_class.* = .float_combine;
423 }
424 break :combine;
425 }
426
427 // "If one of the classes is NO_CLASS, the resulting class
428 // is the other class."
429 if (result_class.* == .none) {
430 result_class.* = field_class[0];
431 break :combine;
432 }
433 assert(field_class[0] != .none);
434
435 // "If one of the classes is MEMORY, the result is the MEMORY class."
436 if (result_class.* == .memory or field_class[0] == .memory) {
437 result_class.* = .memory;
438 break :combine;
439 }
440
441 // "If one of the classes is INTEGER, the result is the INTEGER."
442 if (result_class.* == .integer or field_class[0] == .integer) {
443 result_class.* = .integer;
444 break :combine;
445 }
446
447 // "If one of the classes is X87, X87UP, COMPLEX_X87 class,
448 // MEMORY is used as class."
449 if (result_class.* == .x87 or
450 result_class.* == .x87up or
451 result_class.* == .complex_x87 or
452 field_class[0] == .x87 or
453 field_class[0] == .x87up or
454 field_class[0] == .complex_x87)
455 {
456 result_class.* = .memory;
457 break :combine;
458 }
459
460 // "Otherwise class SSE is used."
461 result_class.* = .sse;
462 }
463 @memcpy(result[@intCast(byte_offset.* / 8 + 1)..][0 .. field_class.len - 1], field_class[1..]);
464 byte_offset.* += field_size;
465 }
466}
467
466468pub const SysV = struct {
467469 /// Note that .rsp and .rbp also belong to this set, however, we never expect to use them
468470 /// for anything else but stack offset tracking therefore we exclude them from this set.
......@@ -592,8 +594,9 @@ const std = @import("std");
592594const assert = std.debug.assert;
593595const testing = std.testing;
594596
595const Module = @import("../../Module.zig");
597const InternPool = @import("../../InternPool.zig");
596598const Register = @import("bits.zig").Register;
597599const RegisterManagerFn = @import("../../register_manager.zig").RegisterManager;
598600const Type = @import("../../type.zig").Type;
599601const Value = @import("../../Value.zig");
602const Zcu = @import("../../Module.zig");
test/c_abi/cfuncs.c+59-1
......@@ -227,6 +227,48 @@ void c_struct_u64_u64_8(size_t, size_t, size_t, size_t, size_t, size_t, size_t,
227227 assert_or_panic(s.b == 40);
228228}
229229
230struct Struct_f32f32_f32 {
231 struct {
232 float b, c;
233 } a;
234 float d;
235};
236
237struct Struct_f32f32_f32 zig_ret_struct_f32f32_f32(void);
238
239void zig_struct_f32f32_f32(struct Struct_f32f32_f32);
240
241struct Struct_f32f32_f32 c_ret_struct_f32f32_f32(void) {
242 return (struct Struct_f32f32_f32){ { 1.0f, 2.0f }, 3.0f };
243}
244
245void c_struct_f32f32_f32(struct Struct_f32f32_f32 s) {
246 assert_or_panic(s.a.b == 1.0f);
247 assert_or_panic(s.a.c == 2.0f);
248 assert_or_panic(s.d == 3.0f);
249}
250
251struct Struct_f32_f32f32 {
252 float a;
253 struct {
254 float c, d;
255 } b;
256};
257
258struct Struct_f32_f32f32 zig_ret_struct_f32_f32f32(void);
259
260void zig_struct_f32_f32f32(struct Struct_f32_f32f32);
261
262struct Struct_f32_f32f32 c_ret_struct_f32_f32f32(void) {
263 return (struct Struct_f32_f32f32){ 1.0f, { 2.0f, 3.0f } };
264}
265
266void c_struct_f32_f32f32(struct Struct_f32_f32f32 s) {
267 assert_or_panic(s.a == 1.0f);
268 assert_or_panic(s.b.c == 2.0f);
269 assert_or_panic(s.b.d == 3.0f);
270}
271
230272struct BigStruct {
231273 uint64_t a;
232274 uint64_t b;
......@@ -2603,9 +2645,25 @@ void run_c_tests(void) {
26032645 zig_struct_u64_u64_7(0, 1, 2, 3, 4, 5, 6, (struct Struct_u64_u64){ .a = 17, .b = 18 });
26042646 zig_struct_u64_u64_8(0, 1, 2, 3, 4, 5, 6, 7, (struct Struct_u64_u64){ .a = 19, .b = 20 });
26052647 }
2648
2649#if !defined(ZIG_RISCV64)
2650 {
2651 struct Struct_f32f32_f32 s = zig_ret_struct_f32f32_f32();
2652 assert_or_panic(s.a.b == 1.0f);
2653 assert_or_panic(s.a.c == 2.0f);
2654 assert_or_panic(s.d == 3.0f);
2655 zig_struct_f32f32_f32((struct Struct_f32f32_f32){ { 1.0f, 2.0f }, 3.0f });
2656 }
2657
2658 {
2659 struct Struct_f32_f32f32 s = zig_ret_struct_f32_f32f32();
2660 assert_or_panic(s.a == 1.0f);
2661 assert_or_panic(s.b.c == 2.0f);
2662 assert_or_panic(s.b.d == 3.0f);
2663 zig_struct_f32_f32f32((struct Struct_f32_f32f32){ 1.0f, { 2.0f, 3.0f } });
2664 }
26062665#endif
26072666
2608#if !defined __mips__ && !defined ZIG_PPC32
26092667 {
26102668 struct BigStruct s = {1, 2, 3, 4, 5};
26112669 zig_big_struct(s);
test/c_abi/main.zig+64-5
......@@ -273,6 +273,7 @@ const Struct_u64_u64 = extern struct {
273273export fn zig_ret_struct_u64_u64() Struct_u64_u64 {
274274 return .{ .a = 1, .b = 2 };
275275}
276
276277export fn zig_struct_u64_u64_0(s: Struct_u64_u64) void {
277278 expect(s.a == 3) catch @panic("test failure");
278279 expect(s.b == 4) catch @panic("test failure");
......@@ -326,11 +327,9 @@ test "C ABI struct u64 u64" {
326327 if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest;
327328 if (builtin.cpu.arch.isPPC()) return error.SkipZigTest;
328329
329 {
330 const s = c_ret_struct_u64_u64();
331 try expect(s.a == 21);
332 try expect(s.b == 22);
333 }
330 const s = c_ret_struct_u64_u64();
331 try expect(s.a == 21);
332 try expect(s.b == 22);
334333 c_struct_u64_u64_0(.{ .a = 23, .b = 24 });
335334 c_struct_u64_u64_1(0, .{ .a = 25, .b = 26 });
336335 c_struct_u64_u64_2(0, 1, .{ .a = 27, .b = 28 });
......@@ -342,6 +341,66 @@ test "C ABI struct u64 u64" {
342341 c_struct_u64_u64_8(0, 1, 2, 3, 4, 5, 6, 7, .{ .a = 39, .b = 40 });
343342}
344343
344const Struct_f32f32_f32 = extern struct {
345 a: extern struct { b: f32, c: f32 },
346 d: f32,
347};
348
349export fn zig_ret_struct_f32f32_f32() Struct_f32f32_f32 {
350 return .{ .a = .{ .b = 1.0, .c = 2.0 }, .d = 3.0 };
351}
352
353export fn zig_struct_f32f32_f32(s: Struct_f32f32_f32) void {
354 expect(s.a.b == 1.0) catch @panic("test failure");
355 expect(s.a.c == 2.0) catch @panic("test failure");
356 expect(s.d == 3.0) catch @panic("test failure");
357}
358
359extern fn c_ret_struct_f32f32_f32() Struct_f32f32_f32;
360
361extern fn c_struct_f32f32_f32(Struct_f32f32_f32) void;
362
363test "C ABI struct {f32,f32} f32" {
364 if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest;
365 if (builtin.cpu.arch.isPPC()) return error.SkipZigTest;
366
367 const s = c_ret_struct_f32f32_f32();
368 try expect(s.a.b == 1.0);
369 try expect(s.a.c == 2.0);
370 try expect(s.d == 3.0);
371 c_struct_f32f32_f32(.{ .a = .{ .b = 1.0, .c = 2.0 }, .d = 3.0 });
372}
373
374const Struct_f32_f32f32 = extern struct {
375 a: f32,
376 b: extern struct { c: f32, d: f32 },
377};
378
379export fn zig_ret_struct_f32_f32f32() Struct_f32_f32f32 {
380 return .{ .a = 1.0, .b = .{ .c = 2.0, .d = 3.0 } };
381}
382
383export fn zig_struct_f32_f32f32(s: Struct_f32_f32f32) void {
384 expect(s.a == 1.0) catch @panic("test failure");
385 expect(s.b.c == 2.0) catch @panic("test failure");
386 expect(s.b.d == 3.0) catch @panic("test failure");
387}
388
389extern fn c_ret_struct_f32_f32f32() Struct_f32_f32f32;
390
391extern fn c_struct_f32_f32f32(Struct_f32_f32f32) void;
392
393test "C ABI struct f32 {f32,f32}" {
394 if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest;
395 if (builtin.cpu.arch.isPPC()) return error.SkipZigTest;
396
397 const s = c_ret_struct_f32_f32f32();
398 try expect(s.a == 1.0);
399 try expect(s.b.c == 2.0);
400 try expect(s.b.d == 3.0);
401 c_struct_f32_f32f32(.{ .a = 1.0, .b = .{ .c = 2.0, .d = 3.0 } });
402}
403
345404const BigStruct = extern struct {
346405 a: u64,
347406 b: u64,