From f2f770fdd3818d1ec14e51ce752aca5beb08e150 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sat, 4 Jul 2026 13:56:17 -0400 Subject: [PATCH] llvm: implement c abi for x86 --- lib/std/Target.zig | 9 +- lib/std/lang.zig | 1 + src/Sema.zig | 1 + src/Zcu.zig | 2 + src/codegen.zig | 195 +++++++++++++++++++++++++++++++++++ src/codegen/c.zig | 2 +- src/codegen/llvm.zig | 18 +++- src/codegen/llvm/FuncGen.zig | 117 +++++++++++++++++---- src/link/Dwarf.zig | 3 +- 9 files changed, 315 insertions(+), 33 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 3a6cfabb42be0d191520ef33ae7cbbc190e7c644..2f395dc0f03d6aa5673df31b4aa3bb92d66bf50a 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1797,6 +1797,7 @@ pub const Cpu = struct { .x86_sysv, .x86_win, + .x86_mingw, .x86_stdcall, .x86_fastcall, .x86_thiscall, @@ -3664,18 +3665,14 @@ pub fn cMaxIntAlignment(target: *const Target) u16 { pub fn cCallingConvention(target: *const Target) ?std.builtin.CallingConvention { return switch (target.cpu.arch) { .x86_64 => switch (target.os.tag) { - .windows, - .uefi, - => .{ .x86_64_win = .{} }, + .windows, .uefi => .{ .x86_64_win = .{} }, else => switch (target.abi) { .gnux32, .muslx32, .x32 => .{ .x86_64_x32 = .{} }, else => .{ .x86_64_sysv = .{} }, }, }, .x86 => switch (target.os.tag) { - .windows, - .uefi, - => .{ .x86_win = .{} }, + .windows, .uefi => if (target.isMinGW()) .{ .x86_mingw = .{} } else .{ .x86_win = .{} }, else => .{ .x86_sysv = .{} }, }, .x86_16 => .{ .x86_16_cdecl = .{} }, diff --git a/lib/std/lang.zig b/lib/std/lang.zig index 2f0868c39331456b03f055cafd8546f19ac51b07..f15f26d9f9330431846e52ff9d98b131fbc666e2 100644 --- a/lib/std/lang.zig +++ b/lib/std/lang.zig @@ -214,6 +214,7 @@ pub const CallingConvention = union(enum(u8)) { // Calling conventions for the `x86` architecture. x86_sysv: X86RegparmOptions, x86_win: X86RegparmOptions, + x86_mingw: X86RegparmOptions, x86_stdcall: X86RegparmOptions, x86_fastcall: CommonOptions, x86_thiscall: CommonOptions, diff --git a/src/Sema.zig b/src/Sema.zig index cb8f87836a5e2c8623670cb870f7bf2b4b05a43d..2181fa563b9e9dec42b1123cc3b738874df64ca7 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8501,6 +8501,7 @@ const calling_conventions_supporting_var_args = [_]std.lang.CallingConvention.Ta .x86_64_win, .x86_sysv, .x86_win, + .x86_mingw, .aarch64_aapcs, .aarch64_aapcs_darwin, .aarch64_aapcs_win, diff --git a/src/Zcu.zig b/src/Zcu.zig index d71d8ecf80ef074a1800c9d637744a023523fede..2af36db254d2edbcfe3ae8a05de265b882d46b01 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -4644,6 +4644,7 @@ pub fn callconvSupported(zcu: *Zcu, cc: std.lang.CallingConvention) union(enum) .x86_sysv, .x86_win, + .x86_mingw, .x86_stdcall, => |opts| opts.incoming_stack_alignment == null and opts.register_params == 0, @@ -4678,6 +4679,7 @@ pub fn callconvSupported(zcu: *Zcu, cc: std.lang.CallingConvention) union(enum) .stage2_x86 => switch (cc) { .x86_sysv, .x86_win, + .x86_mingw, => |opts| opts.incoming_stack_alignment == null and opts.register_params == 0, .naked => true, else => false, diff --git a/src/codegen.zig b/src/codegen.zig index a691b36a4811f7cf127de20aa50d33e2b6e8d700..4ad10d48c05c59608f06956209f7f9b66a6d2775 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -1124,6 +1124,201 @@ pub fn fieldOffset(ptr_agg_ty: Type, ptr_field_ty: Type, field_index: u32, zcu: }; } +pub const FlattenedItem = struct { offset: u64, type: ?Type }; +pub fn flattenType(items_buf: []FlattenedItem, ty: Type, zcu: *Zcu, opts: struct { + offset: u64 = 0, + allow_arrays: bool = true, + fn increaseOffset(opts: @This(), offset: u64) @This() { + return .{ + .offset = opts.offset + offset, + .allow_arrays = opts.allow_arrays, + }; + } +}) ?[]FlattenedItem { + const ip = &zcu.intern_pool; + switch (ip.indexToKey(ty.toIntern())) { + .int_type => |int_type| { + if (int_type.bits == 0) return items_buf[0..0]; + if (items_buf.len < 1) return null; + const items = items_buf[0..1]; + items.* = .{.{ .offset = opts.offset, .type = ty }}; + return items; + }, + .ptr_type => |ptr_type| switch (ptr_type.flags.size) { + .one, .many, .c => { + if (items_buf.len < 1) return null; + const items = items_buf[0..1]; + items.* = .{.{ .offset = opts.offset, .type = ty }}; + return items; + }, + .slice => { + if (items_buf.len < 2) return null; + const items = items_buf[0..2]; + const ptr_field_ty = ty.slicePtrFieldType(zcu); + items.* = .{ + .{ .offset = opts.offset, .type = ptr_field_ty }, + .{ .offset = opts.offset + ptr_field_ty.abiSize(zcu), .type = .usize }, + }; + return items; + }, + }, + .array_type => |array_type| { + const len = array_type.lenIncludingSentinel(); + if (len == 0) return items_buf[0..0]; + const elem_ty: Type = .fromInterned(array_type.child); + const elem_items = flattenType(items_buf, elem_ty, zcu, opts) orelse return null; + if (elem_items.len == 0) return items_buf[0..0]; + if (!opts.allow_arrays) return null; + const items_len, const items_overflow = @mulWithOverflow(elem_items.len, len); + if (items_overflow != 0 or items_buf.len < items_len) return null; + var items_index = elem_items.len; + const elem_size = elem_ty.abiSize(zcu); + var elem_offset: u64 = elem_size; + while (items_index != items_len) : ({ + items_index += elem_items.len; + elem_offset += elem_size; + }) for (items_buf[items_index..][0..elem_items.len], elem_items) |*item, elem_item| { + item.* = .{ .offset = elem_offset + elem_item.offset, .type = elem_item.type }; + }; + return items_buf[0..@intCast(items_len)]; + }, + .vector_type => |vector_type| { + if (vector_type.len == 0) return items_buf[0..0]; + if (items_buf.len < 1) return null; + const items = items_buf[0..1]; + items.* = .{.{ .offset = opts.offset, .type = ty }}; + return items; + }, + .opt_type, .error_union_type => return null, + .simple_type => |simple_type| switch (simple_type) { + .f16, + .f32, + .f64, + .f80, + .f128, + .usize, + .isize, + .c_char, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + .c_longdouble, + .bool, + .anyerror, + => { + if (items_buf.len < 1) return null; + const items = items_buf[0..1]; + items.* = .{.{ .offset = opts.offset, .type = ty }}; + return items; + }, + .anyopaque, .noreturn => return null, + .void, + .type, + .comptime_int, + .comptime_float, + .null, + .undefined, + .enum_literal, + => return items_buf[0..0], + .adhoc_inferred_error_set, .generic_poison => unreachable, + }, + .struct_type => { + const loaded_struct = ip.loadStructType(ty.toIntern()); + switch (loaded_struct.layout) { + .auto, .@"extern" => {}, + .@"packed" => return flattenType(items_buf, .fromInterned( + loaded_struct.packed_backing_int_type, + ), zcu, opts), + } + var items_len: usize = 0; + var offset: u64 = 0; + var field_it = loaded_struct.iterateRuntimeOrder(ip); + while (field_it.next()) |field_index| { + const field_ty: Type = .fromInterned(loaded_struct.field_types.get(ip)[field_index]); + const field_offset = loaded_struct.field_offsets.get(ip)[field_index]; + if (field_offset - offset > 0 and + (items_len == 0 or items_buf[items_len - 1].type != null)) + { + if (items_len == items_buf.len) return null; + items_buf[items_len] = .{ .offset = offset, .type = null }; + items_len += 1; + } + items_len += (flattenType(items_buf[items_len..], field_ty, zcu, opts.increaseOffset( + field_offset, + )) orelse return null).len; + offset = field_offset + field_ty.abiSize(zcu); + } + if (ty.abiSize(zcu) - offset > 0 and + (items_len == 0 or items_buf[items_len - 1].type != null)) + { + if (items_len == items_buf.len) return null; + items_buf[items_len] = .{ .offset = offset, .type = null }; + items_len += 1; + } + return items_buf[0..items_len]; + }, + .tuple_type => |tuple_type| { + if (items_buf.len < tuple_type.types.len) return null; + var items_len: usize = 0; + var offset: u64 = 0; + for (tuple_type.types.get(ip)) |field_ty_ip| { + const field_ty: Type = .fromInterned(field_ty_ip); + offset = field_ty.abiAlignment(zcu).forward(offset); + items_len += (flattenType(items_buf[items_len..], field_ty, zcu, opts.increaseOffset( + offset, + )) orelse return null).len; + offset += field_ty.abiSize(zcu); + } + return items_buf[0..items_len]; + }, + .union_type => { + const loaded_union = ip.loadUnionType(ty.toIntern()); + return switch (loaded_union.layout) { + .auto, .@"extern" => return null, + .@"packed" => return flattenType(items_buf, .fromInterned( + loaded_union.packed_backing_int_type, + ), zcu, opts), + }; + }, + .opaque_type, .spirv_type, .func_type => return null, + .enum_type => return flattenType(items_buf, .fromInterned( + ip.loadEnumType(ty.toIntern()).int_tag_type, + ), zcu, opts), + .error_set_type, .inferred_error_set_type => { + if (items_buf.len < 1) return null; + const items = items_buf[0..1]; + items.* = .{.{ .offset = opts.offset, .type = ty }}; + return items; + }, + .anyframe_type, + // values, not types + .undef, + .simple_value, + .@"extern", + .func, + .int, + .err, + .error_union, + .enum_literal, + .enum_tag, + .float, + .ptr, + .slice, + .opt, + .aggregate, + .un, + .bitpack, + // memoization, not types + .memoized_call, + => unreachable, + } +} + test { _ = aarch64; } diff --git a/src/codegen/c.zig b/src/codegen/c.zig index d1605b457e2f3673807c16fd10836612aae804d5..1633321426dfc3044177eebe0cd44e6fda3c6aa9 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -7289,7 +7289,7 @@ fn toCallingConvention(cc: std.lang.CallingConvention, zcu: *Zcu) ?[]const u8 { .x86_16_cdecl => "cdecl", .x86_16_regparmcall => "regparmcall", .x86_64_sysv, .x86_sysv => "sysv_abi", - .x86_64_win, .x86_win => "ms_abi", + .x86_64_win, .x86_win, .x86_mingw => "ms_abi", .x86_16_stdcall, .x86_stdcall => "stdcall", .x86_fastcall => "fastcall", .x86_thiscall => "thiscall", diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 689a0349ce7cf9aa2c49c2101f476303667c50cd..de5f084008896d56c36ff371078b6581a306a45f 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -4235,19 +4235,26 @@ pub const Object = struct { }; } + pub const Byval = struct { alignment: InternPool.Alignment = .none }; pub fn addByRefParamAttrs( o: *Object, attributes: *Builder.FunctionAttributes.Wip, llvm_arg_i: u32, - byval: bool, + maybe_byval: ?Byval, param_ty: Type, ) Allocator.Error!void { const llvm_param_ty = try o.lowerType(param_ty, .in_memory); - const alignment = param_ty.abiAlignment(o.zcu).toLlvm(); - try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder); try attributes.addParamAttr(llvm_arg_i, .readonly, &o.builder); - try attributes.addParamAttr(llvm_arg_i, .{ .@"align" = .wrap(alignment) }, &o.builder); - if (byval) try attributes.addParamAttr(llvm_arg_i, .{ .byval = llvm_param_ty }, &o.builder); + try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder); + try attributes.addParamAttr(llvm_arg_i, .noundef, &o.builder); + const alignment = if (maybe_byval) |byval| alignment: { + try attributes.addParamAttr(llvm_arg_i, .{ .byval = llvm_param_ty }, &o.builder); + break :alignment byval.alignment; + } else .none; + try attributes.addParamAttr(llvm_arg_i, .{ .@"align" = .wrap(switch (alignment) { + .none => param_ty.abiAlignment(o.zcu), + else => alignment, + }.toLlvm()) }, &o.builder); } pub fn getErrorNameTable(o: *Object) Allocator.Error!Builder.Variable.Index { @@ -4598,6 +4605,7 @@ pub fn toLlvmCallConvTag(cc_tag: std.lang.CallingConvention.Tag, target: *const .x86_16_interrupt, .x86_sysv, .x86_win, + .x86_mingw, .x86_thiscall_mingw, .x86_64_x32, .aarch64_aapcs, diff --git a/src/codegen/llvm/FuncGen.zig b/src/codegen/llvm/FuncGen.zig index 84a7af8362e91e54003cdf82378ce1037b2f617f..1dfdfba987f0e962049294699bba1bcce3aac4bf 100644 --- a/src/codegen/llvm/FuncGen.zig +++ b/src/codegen/llvm/FuncGen.zig @@ -218,7 +218,7 @@ pub fn genMainBody(fg: *FuncGen) TodoError!void { switch (lowering) { .no_bits => continue, .byval => { - assert(!it.byval_attr); + assert(it.byval_attr == null); const param_index = it.zig_index - 1; const param_ty: Type = .fromInterned(param_types[param_index]); const param = fg.wip.arg(it.llvm_index - 1); @@ -237,15 +237,16 @@ pub fn genMainBody(fg: *FuncGen) TodoError!void { .byref, .byref_mut => { const param_ty: Type = .fromInterned(param_types[it.zig_index - 1]); const param = fg.wip.arg(it.llvm_index - 1); + const alignment = if (it.byval_attr) |byval_attr| byval_attr.alignment else .none; - if (isByRef(param_ty, zcu)) { + if (alignment == .none and isByRef(param_ty, zcu)) { args.appendAssumeCapacity(param); } else { - args.appendAssumeCapacity(try fg.load(param, .none, param_ty, .normal)); + args.appendAssumeCapacity(try fg.load(param, alignment, param_ty, .normal)); } }, .abi_sized_int => { - assert(!it.byval_attr); + assert(it.byval_attr == null); const param_ty: Type = .fromInterned(param_types[it.zig_index - 1]); const param = fg.wip.arg(it.llvm_index - 1); @@ -260,7 +261,7 @@ pub fn genMainBody(fg: *FuncGen) TodoError!void { } }, .slice => { - assert(!it.byval_attr); + assert(it.byval_attr == null); const param_ty: Type = .fromInterned(param_types[it.zig_index - 1]); assert(!isByRef(param_ty, zcu)); const slice_val = try fg.wip.buildAggregate( @@ -271,7 +272,7 @@ pub fn genMainBody(fg: *FuncGen) TodoError!void { args.appendAssumeCapacity(slice_val); }, .multiple_llvm_types => { - assert(!it.byval_attr); + assert(it.byval_attr == null); const param_ty: Type = .fromInterned(param_types[it.zig_index - 1]); const param_alignment = param_ty.abiAlignment(zcu); const llvm_ty = try o.builder.arrayType(it.offsets_buffer[it.types_len], .i8); @@ -963,7 +964,7 @@ fn buildCall( => continue, .slice => { - assert(!it.byval_attr); + assert(it.byval_attr == null); const param_ty = Type.fromInterned(fn_info.param_types[it.zig_index - 1]); const ptr_info = param_ty.ptrInfo(zcu); const llvm_arg_i = it.llvm_index - 2; @@ -6913,7 +6914,7 @@ const ParamTypeIterator = struct { types_len: u32, types_buffer: [8]Builder.Type, offsets_buffer: [9]u64, - byval_attr: bool, + byval_attr: ?Object.Byval, const Lowering = union(enum) { no_bits, @@ -6931,7 +6932,7 @@ const ParamTypeIterator = struct { pub fn next(it: *ParamTypeIterator) Allocator.Error!?Lowering { if (it.zig_index >= it.param_types.len) return null; const ty = it.param_types[it.zig_index]; - it.byval_attr = false; + it.byval_attr = null; return nextInner(it, Type.fromInterned(ty)); } @@ -7008,7 +7009,7 @@ const ParamTypeIterator = struct { it.llvm_index += 1; switch (arm_c_abi.classifyType(ty, zcu, .arg)) { .memory => { - it.byval_attr = true; + it.byval_attr = .{}; return .byref; }, .byval => return .byval, @@ -7086,7 +7087,7 @@ const ParamTypeIterator = struct { it.llvm_index += 1; switch (mips_c_abi.classifyType(ty, zcu, .arg)) { .memory => { - it.byval_attr = true; + it.byval_attr = .{}; return .byref; }, .byval => return .byval, @@ -7158,15 +7159,15 @@ const ParamTypeIterator = struct { it.types_buffer[0..1].* = .{try it.object.lowerType(scalar_ty, .as_value)}; it.offsets_buffer[0..2].* = .{ 0, scalar_ty.abiSize(zcu) }; it.types_len = 1; - it.llvm_index += 1; it.zig_index += 1; + it.llvm_index += 1; return .multiple_llvm_types; } }, .indirect => { it.zig_index += 1; it.llvm_index += 1; - it.byval_attr = true; + it.byval_attr = .{}; return .byref; }, }, @@ -7177,10 +7178,56 @@ const ParamTypeIterator = struct { if (isScalar(zcu, ty)) { return .byval; } else { - it.byval_attr = true; + it.byval_attr = .{}; return .byref; } }, + .x86_sysv, .x86_win, .x86_mingw => { + if (isByRef(ty, zcu)) { + var items_buf: [1]codegen.FlattenedItem = undefined; + if (codegen.flattenType(&items_buf, ty, zcu, .{ + .allow_arrays = false, + })) |items| one_float: { + if (items.len != 1 or items[0].offset != 0) break :one_float; + const item_ty = items[0].type orelse break :one_float; + if (!item_ty.isRuntimeFloat()) break :one_float; + it.types_buffer[0..1].*, it.offsets_buffer[0..2].* = + switch (item_ty.floatBits(zcu.getTarget())) { + else => unreachable, + 32 => .{ .{.float}, .{ 0, 4 } }, + 64 => .{ .{.double}, .{ 0, 8 } }, + 16, 80, 128 => break :one_float, + }; + it.types_len = 1; + it.zig_index += 1; + it.llvm_index += 1; + return .multiple_llvm_types; + } + it.zig_index += 1; + it.llvm_index += 1; + it.byval_attr = .{ .alignment = .@"4" }; + return .byref; + } + if (ty.isAbiInt(zcu)) switch (ty.intInfo(zcu).bits) { + else => unreachable, + 8, 16, 32, 64 => { + it.zig_index += 1; + it.llvm_index += 1; + return .byval; + }, + 128 => { + it.types_buffer[0..2].* = .{ .i64, .i64 }; + it.offsets_buffer[0..3].* = .{ 0, 8, 16 }; + it.types_len = 2; + it.zig_index += 1; + it.llvm_index += 2; + return .multiple_llvm_types; + }, + }; + it.zig_index += 1; + it.llvm_index += 1; + return .byval; + }, .x86_64_sysv, .x86_64_x32 => return try it.next_x86_64_sysv(ty), .x86_64_win => return it.next_x86_64_win(ty), // TODO investigate other callconvs @@ -7277,7 +7324,7 @@ const ParamTypeIterator = struct { .x87 => { it.zig_index += 1; it.llvm_index += 1; - it.byval_attr = true; + it.byval_attr = .{}; return .byref; }, .x87up => unreachable, @@ -7285,7 +7332,7 @@ const ParamTypeIterator = struct { .memory => { it.zig_index += 1; it.llvm_index += 1; - it.byval_attr = true; + it.byval_attr = .{}; return .byref; }, .win_i128 => unreachable, // windows only @@ -7306,7 +7353,7 @@ const ParamTypeIterator = struct { if (it.llvm_index + classes_len > 6) { it.zig_index += 1; it.llvm_index += 1; - it.byval_attr = true; + it.byval_attr = .{}; return .byref; } } else if (!isByRef(ty, zcu)) { @@ -7340,7 +7387,7 @@ pub fn iterateParamTypes( .types_len = undefined, .types_buffer = undefined, .offsets_buffer = undefined, - .byval_attr = false, + .byval_attr = null, }; } @@ -7466,7 +7513,39 @@ pub fn fnReturnStrat(o: *Object, cc: std.lang.CallingConvention, ret_ty: Type) A return .by_val; } else .sret, .x86_fastcall => fnReturnStrat_x86_fastcall(o, zcu, ret_ty), - .x86_sysv, .x86_win => if (isByRef(ret_ty, zcu)) .sret else .by_val, + .x86_sysv, .x86_win, .x86_mingw => if (isByRef(ret_ty, zcu)) { + switch (cc) { + else => unreachable, + .x86_sysv => return .sret, + .x86_win => {}, + .x86_mingw => { + var items_buf: [1]codegen.FlattenedItem = undefined; + if (codegen.flattenType(&items_buf, ret_ty, zcu, .{})) |items| one_float: { + if (items.len != 1 or items[0].offset != 0) break :one_float; + const item_ty = items[0].type orelse break :one_float; + if (!item_ty.isRuntimeFloat()) break :one_float; + return .{ .mem_cast = switch (item_ty.floatBits(zcu.getTarget())) { + else => unreachable, + 16 => .half, + 32 => .float, + 64 => .double, + 80, 128 => break :one_float, + } }; + } + }, + } + return switch (ret_ty.abiSize(zcu)) { + 0 => .void, + 1 => .{ .mem_cast = .i8 }, + 2 => .{ .mem_cast = .i16 }, + 4 => .{ .mem_cast = .i32 }, + 8 => .{ .mem_cast = .i64 }, + else => .sret, + }; + } else if (ret_ty.isAbiInt(zcu) and ret_ty.intInfo(zcu).bits > 64) + .sret + else + .by_val, .x86_64_sysv, .x86_64_x32 => fnReturnStrat_x86_64_sysv(o, ret_ty), .x86_64_win => fnReturnStrat_x86_64_win(o, ret_ty), // TODO investigate other callconvs diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index 1b2aa830d23ee24903085a36db49ce1a42c91c72..52d0fc57a1d5238ac2fd837d10878083a94510c7 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -4151,8 +4151,7 @@ fn updateConstInner(dwarf: *Dwarf, pt: Zcu.PerThread, debug_const_index: link.Co .x86_64_regcall_v3_sysv => .LLVM_X86RegCall, .x86_64_regcall_v4_win => .LLVM_X86RegCall, .x86_64_vectorcall => .LLVM_vectorcall, - .x86_sysv => .normal, - .x86_win => .normal, + .x86_sysv, .x86_win, .x86_mingw => .normal, .x86_stdcall => .BORLAND_stdcall, .x86_fastcall => .BORLAND_msfastcall, .x86_thiscall => .BORLAND_thiscall, -- 2.54.0