#define ALLOW_SPLIT_MULTI_RETURNS true enum lbArgKind { lbArg_Direct, lbArg_Indirect, lbArg_Ignore, }; struct lbArgType { lbArgKind kind; LLVMTypeRef type; LLVMTypeRef cast_type; // Optional LLVMTypeRef pad_type; // Optional LLVMAttributeRef attribute; // Optional LLVMAttributeRef align_attribute; // Optional i64 byval_alignment; bool is_byval; bool no_capture; // For RiscV (Optional for others): A `cast_type` is normally applied by reinterpreting the value's // bits from offset zero. Only correct when the two layouts agree. When an ABI flattens an aggregate // it drops padding, and the dense type it produces puts the surviving members at different offsets // than they really have, eg `struct #min_field_align(16){i8, f32}` flattens to `{i8, float}`, moving // the float from offset 16 to offset 4. These give the real byte offset of each `cast_type` element. Slice coerce_offsets; }; gb_internal i64 lb_sizeof(LLVMTypeRef type); gb_internal i64 lb_alignof(LLVMTypeRef type); gb_internal lbArgType lb_arg_type_direct(LLVMTypeRef type, LLVMTypeRef cast_type, LLVMTypeRef pad_type, LLVMAttributeRef attr) { return lbArgType{lbArg_Direct, type, cast_type, pad_type, attr, {}, false}; } // Same as above, except coercion reads each element of `cast_type` from its real offset in `type` // instead of reinterpreting the bits from offset zero. See `coerce_offsets`. gb_internal lbArgType lb_arg_type_direct_fields(LLVMTypeRef type, LLVMTypeRef cast_type, Slice offsets) { lbArgType arg = lb_arg_type_direct(type, cast_type, nullptr, nullptr); arg.coerce_offsets = offsets; return arg; } gb_internal lbArgType lb_arg_type_direct(LLVMTypeRef type) { return lb_arg_type_direct(type, nullptr, nullptr, nullptr); } gb_internal lbArgType lb_arg_type_indirect(LLVMTypeRef type, LLVMAttributeRef attr) { return lbArgType{lbArg_Indirect, type, nullptr, nullptr, attr, {}, false}; } gb_internal lbArgType lb_arg_type_indirect_byval(LLVMContextRef c, LLVMTypeRef type, Type *source_type = nullptr) { // the outgoing stack slot, which i386 never over-aligns, not even for an over-aligned struct i64 alignment = build_context.ptr_size; if (build_context.metrics.arch != TargetArch_i386) { // `#align` and `#min_field_align` do not survive lowering, so ask the source // type where there is one i64 a = source_type != nullptr ? type_align_of(source_type) : lb_alignof(type); alignment = gb_max(alignment, a); } LLVMAttributeRef byval_attr = lb_create_enum_attribute_with_type(c, "byval", type); LLVMAttributeRef align_attr = lb_create_enum_attribute(c, "align", alignment); return lbArgType{lbArg_Indirect, type, nullptr, nullptr, byval_attr, align_attr, alignment, true}; } gb_internal lbArgType lb_arg_type_ignore(LLVMTypeRef type) { return lbArgType{lbArg_Ignore, type, nullptr, nullptr, nullptr, {}, false}; } struct lbFunctionType { LLVMContextRef ctx; ProcCallingConvention calling_convention; Array args; lbArgType ret; LLVMTypeRef multiple_return_original_type; // nullptr if not used isize original_arg_count; }; gb_internal gbAllocator lb_function_type_args_allocator(void) { return heap_allocator(); } gb_internal gb_inline i64 llvm_align_formula(i64 off, i64 a) { return (off + a - 1) / a * a; } gb_internal bool lb_is_type_kind(LLVMTypeRef type, LLVMTypeKind kind) { if (type == nullptr) { return false; } return LLVMGetTypeKind(type) == kind; } gb_internal LLVMTypeRef lb_function_type_to_llvm_raw(lbFunctionType *ft, bool is_var_arg) { unsigned arg_count = cast(unsigned)ft->args.count; unsigned offset = 0; LLVMTypeRef ret = nullptr; if (ft->ret.kind == lbArg_Direct) { if (ft->ret.cast_type != nullptr) { ret = ft->ret.cast_type; } else { ret = ft->ret.type; } } else if (ft->ret.kind == lbArg_Indirect) { offset += 1; ret = LLVMVoidTypeInContext(ft->ctx); } else if (ft->ret.kind == lbArg_Ignore) { ret = LLVMVoidTypeInContext(ft->ctx); } GB_ASSERT_MSG(ret != nullptr, "%d", ft->ret.kind); unsigned maximum_arg_count = offset+arg_count; LLVMTypeRef *args = gb_alloc_array(permanent_allocator(), LLVMTypeRef, maximum_arg_count); if (offset == 1) { GB_ASSERT(ft->ret.kind == lbArg_Indirect); args[0] = LLVMPointerType(ft->ret.type, 0); } unsigned arg_index = offset; for (unsigned i = 0; i < arg_count; i++) { lbArgType *arg = &ft->args[i]; if (arg->kind == lbArg_Direct) { LLVMTypeRef arg_type = nullptr; if (ft->args[i].cast_type != nullptr) { arg_type = arg->cast_type; } else { arg_type = arg->type; } args[arg_index++] = arg_type; } else if (arg->kind == lbArg_Indirect) { if (ft->multiple_return_original_type == nullptr || i < ft->original_arg_count) { GB_ASSERT(!lb_is_type_kind(arg->type, LLVMPointerTypeKind)); } args[arg_index++] = LLVMPointerType(arg->type, 0); } else if (arg->kind == lbArg_Ignore) { // ignore } } unsigned total_arg_count = arg_index; LLVMTypeRef func_type = LLVMFunctionType(ret, args, total_arg_count, is_var_arg); return func_type; } // LLVMTypeRef lb_function_type_to_llvm_ptr(lbFunctionType *ft, bool is_var_arg) { // LLVMTypeRef func_type = lb_function_type_to_llvm_raw(ft, is_var_arg); // return LLVMPointerType(func_type, 0); // } gb_internal void lb_add_function_type_attributes(LLVMValueRef fn, lbFunctionType *ft, ProcCallingConvention calling_convention) { if (ft == nullptr) { return; } unsigned arg_count = cast(unsigned)ft->args.count; unsigned offset = 0; if (ft->ret.kind == lbArg_Indirect) { offset += 1; } LLVMContextRef c = ft->ctx; LLVMAttributeRef noalias_attr = lb_create_enum_attribute(c, "noalias"); LLVMAttributeRef nonnull_attr = lb_create_enum_attribute(c, "nonnull"); #if LLVM_VERSION_MAJOR >= 21 LLVMAttributeRef nocapture_attr = lb_create_string_attribute(c, make_string_c("captures"), make_string_c("none")); #else LLVMAttributeRef nocapture_attr = lb_create_enum_attribute(c, "nocapture"); #endif unsigned arg_index = offset; for (unsigned i = 0; i < arg_count; i++) { lbArgType *arg = &ft->args[i]; if (arg->kind == lbArg_Ignore) { continue; } if (arg->attribute) { LLVMAddAttributeAtIndex(fn, arg_index+1, arg->attribute); } if (arg->align_attribute) { LLVMAddAttributeAtIndex(fn, arg_index+1, arg->align_attribute); } if (arg->no_capture) { LLVMAddAttributeAtIndex(fn, arg_index+1, nocapture_attr); } if (ft->multiple_return_original_type) { if (ft->original_arg_count <= i) { LLVMAddAttributeAtIndex(fn, arg_index+1, noalias_attr); LLVMAddAttributeAtIndex(fn, arg_index+1, nonnull_attr); } } arg_index++; } if (offset != 0 && ft->ret.kind == lbArg_Indirect && ft->ret.attribute != nullptr) { LLVMAddAttributeAtIndex(fn, offset, ft->ret.attribute); LLVMAddAttributeAtIndex(fn, offset, noalias_attr); } lbCallingConventionKind cc_kind = lbCallingConvention_C; // TODO(bill): Clean up this logic if (selected_subtarget == Subtarget_Playdate) { cc_kind = lbCallingConvention_ARM_AAPCS_VFP; } else if (!is_arch_wasm()) { cc_kind = lb_calling_convention_map[calling_convention]; } // if (build_context.metrics.arch == TargetArch_amd64) { // if (build_context.metrics.os == TargetOs_windows) { // if (cc_kind == lbCallingConvention_C) { // cc_kind = lbCallingConvention_Win64; // } // } else { // if (cc_kind == lbCallingConvention_C) { // cc_kind = lbCallingConvention_X86_64_SysV; // } // } // } LLVMSetFunctionCallConv(fn, cc_kind); if (calling_convention == ProcCC_Odin) { unsigned context_index = arg_index; LLVMAddAttributeAtIndex(fn, context_index, noalias_attr); LLVMAddAttributeAtIndex(fn, context_index, nonnull_attr); LLVMAddAttributeAtIndex(fn, context_index, nocapture_attr); } } gb_internal i64 lb_sizeof(LLVMTypeRef type) { LLVMTypeKind kind = LLVMGetTypeKind(type); switch (kind) { case LLVMVoidTypeKind: return 0; case LLVMIntegerTypeKind: { unsigned w = LLVMGetIntTypeWidth(type); return (w + 7)/8; } case LLVMHalfTypeKind: return 2; case LLVMFloatTypeKind: return 4; case LLVMDoubleTypeKind: return 8; case LLVMPointerTypeKind: return build_context.ptr_size; case LLVMStructTypeKind: { unsigned field_count = LLVMCountStructElementTypes(type); i64 offset = 0; if (LLVMIsPackedStruct(type)) { for (unsigned i = 0; i < field_count; i++) { LLVMTypeRef field = LLVMStructGetTypeAtIndex(type, i); offset += lb_sizeof(field); } } else { for (unsigned i = 0; i < field_count; i++) { LLVMTypeRef field = LLVMStructGetTypeAtIndex(type, i); i64 align = lb_alignof(field); offset = llvm_align_formula(offset, align); offset += lb_sizeof(field); } offset = llvm_align_formula(offset, lb_alignof(type)); } return offset; } break; case LLVMArrayTypeKind: { LLVMTypeRef elem = OdinLLVMGetArrayElementType(type); i64 elem_size = lb_sizeof(elem); i64 count = LLVMGetArrayLength(type); i64 size = count * elem_size; return size; } break; #if LLVM_VERSION_MAJOR < 20 case LLVMX86_MMXTypeKind: return 8; #endif case LLVMVectorTypeKind: { LLVMTypeRef elem = OdinLLVMGetVectorElementType(type); i64 elem_size = lb_sizeof(elem); i64 count = LLVMGetVectorSize(type); i64 size = count * elem_size; return next_pow2(size); } } GB_PANIC("Unhandled type for lb_sizeof -> %s", LLVMPrintTypeToString(type)); return 0; } gb_internal i64 lb_alignof(LLVMTypeRef type) { LLVMTypeKind kind = LLVMGetTypeKind(type); switch (kind) { case LLVMVoidTypeKind: return 1; case LLVMIntegerTypeKind: { unsigned w = LLVMGetIntTypeWidth(type); return gb_clamp((w + 7)/8, 1, build_context.max_align); } case LLVMHalfTypeKind: return 2; case LLVMFloatTypeKind: return 4; case LLVMDoubleTypeKind: return 8; case LLVMPointerTypeKind: return build_context.ptr_size; case LLVMStructTypeKind: { if (LLVMIsPackedStruct(type)) { return 1; } else { unsigned field_count = LLVMCountStructElementTypes(type); i64 max_align = 1; for (unsigned i = 0; i < field_count; i++) { LLVMTypeRef field = LLVMStructGetTypeAtIndex(type, i); i64 field_align = lb_alignof(field); max_align = gb_max(max_align, field_align); } return max_align; } } break; case LLVMArrayTypeKind: return lb_alignof(OdinLLVMGetArrayElementType(type)); #if LLVM_VERSION_MAJOR < 20 case LLVMX86_MMXTypeKind: return 8; #endif case LLVMVectorTypeKind: { // TODO(bill): This appears to be correct but LLVM isn't necessarily "great" with regards to documentation LLVMTypeRef elem = OdinLLVMGetVectorElementType(type); i64 elem_size = lb_sizeof(elem); i64 count = LLVMGetVectorSize(type); i64 size = count * elem_size; return gb_clamp(next_pow2(size), 1, build_context.max_simd_align); } } GB_PANIC("Unhandled type for lb_sizeof -> %s", LLVMPrintTypeToString(type)); // LLVMValueRef v = LLVMAlignOf(type); // GB_ASSERT(LLVMIsConstant(v)); // return LLVMConstIntGetSExtValue(v); return 1; } // The alignment LLVM itself will give the lowered type, which is not `lb_alignof`: // that applies `max_simd_align`, and LLVM knows nothing about it. A 32-byte vector // is 16-aligned on arm64 and Darwin and 32-aligned to LLVM, and a struct holding // one has to be packed or LLVM re-inserts padding and moves the member. gb_internal i64 lb_llvm_natural_alignof(LLVMTypeRef type) { switch (LLVMGetTypeKind(type)) { case LLVMStructTypeKind: { if (LLVMIsPackedStruct(type)) { return 1; } unsigned field_count = LLVMCountStructElementTypes(type); i64 max_align = 1; for (unsigned i = 0; i < field_count; i++) { max_align = gb_max(max_align, lb_llvm_natural_alignof(LLVMStructGetTypeAtIndex(type, i))); } return max_align; } case LLVMArrayTypeKind: return lb_llvm_natural_alignof(OdinLLVMGetArrayElementType(type)); case LLVMVectorTypeKind: return gb_max(next_pow2(lb_sizeof(type)), 1); } return lb_alignof(type); } #define LB_ABI_INFO(name) lbFunctionType *name(lbModule *m, LLVMTypeRef *arg_types, unsigned arg_count, LLVMTypeRef return_type, bool return_is_defined, bool return_is_tuple, ProcCallingConvention calling_convention, Type *original_type) typedef LB_ABI_INFO(lbAbiInfoType); #define LB_ABI_COMPUTE_RETURN_TYPE(name) lbArgType name(lbFunctionType *ft, LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined, bool return_is_tuple) typedef LB_ABI_COMPUTE_RETURN_TYPE(lbAbiComputeReturnType); gb_internal lbArgType lb_abi_modify_return_is_tuple(lbFunctionType *ft, LLVMContextRef c, LLVMTypeRef return_type, lbAbiComputeReturnType *compute_return_type) { GB_ASSERT(return_type != nullptr); GB_ASSERT(compute_return_type != nullptr); lbArgType return_arg = {}; if (lb_is_type_kind(return_type, LLVMStructTypeKind)) { unsigned field_count = LLVMCountStructElementTypes(return_type); if (field_count > 1) { ft->original_arg_count = ft->args.count; ft->multiple_return_original_type = return_type; for (unsigned i = 0; i < field_count-1; i++) { LLVMTypeRef field_type = LLVMStructGetTypeAtIndex(return_type, i); LLVMTypeRef field_pointer_type = LLVMPointerType(field_type, 0); lbArgType ret_partial = lb_arg_type_direct(field_pointer_type); array_add(&ft->args, ret_partial); } // override the return type for the last field LLVMTypeRef new_return_type = LLVMStructGetTypeAtIndex(return_type, field_count-1); return_arg = compute_return_type(ft, c, new_return_type, true, false); } } return return_arg; } #define LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO() do { \ if (return_is_tuple) { \ lbArgType new_return_type = lb_abi_modify_return_is_tuple(ft, c, return_type, compute_return_type); \ if (new_return_type.type != nullptr) { \ return new_return_type; \ } \ } \ } while (0) // NOTE(bill): I hate `namespace` in C++ but this is just because I don't want to prefix everything // Every psABI except AAPCS64 and Win64 makes the caller widen a sub-word integer to 32 bits, in // both argument and return position, and clang records that as `signext`/`zeroext`. A callee // compiled against the attribute reads the whole 32-bit register rather than the byte, so omitting // it hands the callee whatever the high bits happened to hold. gb_internal LLVMAttributeRef lb_integer_extension_attribute(LLVMContextRef c, LLVMTypeRef type, Type *source_type) { if (source_type == nullptr) { // Knowable without the source: an `i1` is always zero-extended. return type == LLVMInt1TypeInContext(c) ? lb_create_enum_attribute(c, "zeroext") : nullptr; } if (lb_sizeof(type) >= 4) { return nullptr; } if (!is_type_integer_like(source_type) && !is_type_enum(source_type)) { return nullptr; } if (is_type_unsigned(source_type) || is_type_boolean(source_type)) { return lb_create_enum_attribute(c, "zeroext"); } return lb_create_enum_attribute(c, "signext"); } // The source type of each parameter, where one exists. `arg_types` can carry entries with no // counterpart, so the tuple is walked rather than indexed. gb_internal Array lb_abi_param_source_types(Type *proc_type, unsigned arg_count) { auto out = array_make(temporary_allocator(), cast(isize)arg_count); Entity **params = nullptr; isize param_count = 0; if (proc_type != nullptr && proc_type->kind == Type_Proc && proc_type->Proc.params != nullptr) { params = proc_type->Proc.params->Tuple.variables.data; param_count = proc_type->Proc.params->Tuple.variables.count; } for (unsigned i = 0, j = 0; i < arg_count; i++, j++) { while (cast(isize)j < param_count && params[j]->kind != Entity_Variable) { j++; } out[i] = cast(isize)j < param_count ? params[j]->type : nullptr; } return out; } // A single result can be classified from its source type. A tuple cannot: it is split into // out-pointers, and C has no such return shape anyway. gb_internal Type *lb_abi_single_result_type(Type *proc_type) { if (proc_type != nullptr && proc_type->kind == Type_Proc && proc_type->Proc.results != nullptr && proc_type->Proc.results->Tuple.variables.count == 1) { return proc_type->Proc.results->Tuple.variables[0]->type; } return nullptr; } namespace lbAbi386 { gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, Type *original_type); gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type); gb_internal LB_ABI_INFO(abi_info) { LLVMContextRef c = m->ctx; lbFunctionType *ft = permanent_alloc_item(); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count, original_type); ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple); // `bit_field` is treated as a struct. Type *return_source = lb_abi_single_result_type(original_type); if (return_is_defined && !return_is_tuple && return_source != nullptr && is_type_bit_field(return_source) && !lb_is_type_kind(return_type, LLVMStructTypeKind) && !lb_is_type_kind(return_type, LLVMArrayTypeKind)) { // Windows and the BSDs return a small struct in registers, same as the scalar // path so only the psABI targets need moving to the hidden pointer. bool small_in_registers = build_context.metrics.os == TargetOs_windows || build_context.metrics.os == TargetOs_freebsd || build_context.metrics.os == TargetOs_openbsd; i64 sz = lb_sizeof(return_type); bool returned_in_registers = small_in_registers && (sz == 1 || sz == 2 || sz == 4 || sz == 8); if (!returned_in_registers) { LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type); ft->ret = lb_arg_type_indirect(return_type, attr); } } // A complex lowers to a struct of two floats. The struct rule sends every struct through // a hidden pointer. The psABI gives complex its own rule: one of eight bytes or fewer // comes back in EAX:EDX, and only the wider ones go through memory. `complex64` is // returned coerced to `i64` and `complex128` keeps the hidden pointer. if (return_is_defined && !return_is_tuple && return_source != nullptr && is_type_complex(return_source)) { i64 sz = lb_sizeof(return_type); if (sz > 0 && sz <= 8) { ft->ret = lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, cast(unsigned)(sz*8)), nullptr, nullptr); } } ft->calling_convention = calling_convention; return ft; } gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type, bool is_return, Type *source_type) { // A bare vector is passed and returned as itself; only aggregates // take the indirect path below, ergo the vector check has to be first. // // Exception is an 8-byte vector Arg whose element is an integer: its an MMX type; // clang coerces it to `i64` to keep it out of the MMX registers. An 8-byte // vector of floats is an SSE type and stays itself, so the rule turns on the element and // not on the width alone: // // <8 x i8> <4 x i16> <2 x i32> -> i64 // <2 x float> <4 x half> -> unchanged // // The RETURN is never coerced -- `<8 x i8>` comes back as itself. if (LLVMGetTypeKind(type) == LLVMVectorTypeKind) { if (!is_return && lb_sizeof(type) == 8 && LLVMGetTypeKind(LLVMGetElementType(type)) == LLVMIntegerTypeKind) { return lb_arg_type_direct(type, LLVMIntTypeInContext(c, 64), nullptr, nullptr); } return lb_arg_type_direct(type, nullptr, nullptr, nullptr); } if (!is_return && lb_sizeof(type) > 8) { return lb_arg_type_indirect(type, nullptr); } if (build_context.metrics.os == TargetOs_windows && build_context.ptr_size == 8 && lb_is_type_kind(type, LLVMIntegerTypeKind) && type == LLVMIntTypeInContext(c, 128)) { // NOTE(bill): Because Windows AMD64 is weird // TODO(bill): LLVM is probably bugged here and doesn't correctly generate the right code // So even though it is "technically" wrong, no cast might be the best option LLVMTypeRef cast_type = nullptr; if (true || !is_return) { cast_type = LLVMVectorType(LLVMInt64TypeInContext(c), 2); } return lb_arg_type_direct(type, cast_type, nullptr, nullptr); } LLVMAttributeRef attr = lb_integer_extension_attribute(c, type, source_type); return lb_arg_type_direct(type, nullptr, nullptr, attr); } gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, Type *original_type) { auto args = array_make(lb_function_type_args_allocator(), arg_count); auto srcs = lb_abi_param_source_types(original_type, arg_count); for (unsigned i = 0; i < arg_count; i++) { LLVMTypeRef t = arg_types[i]; LLVMTypeKind kind = LLVMGetTypeKind(t); i64 sz = lb_sizeof(t); // `bit_field` lowers to a bare integer; C represents it as a struct with bit-field // members, and i386 passes every struct by value on the stack. Use Src Type to match bool is_aggregate = kind == LLVMStructTypeKind || kind == LLVMArrayTypeKind || (srcs[i] != nullptr && is_type_bit_field(srcs[i])); if (is_aggregate) { if (sz == 0) { args[i] = lb_arg_type_ignore(t); } else { // Aggregates are pushed onto the stack by value, not passed as a pointer // to a caller-owned copy. This is the rule for both i386 targets. args[i] = lb_arg_type_indirect_byval(c, t); } } else { args[i] = non_struct(c, t, false, srcs[i]); } } return args; } gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type) { if (!return_is_defined) { return lb_arg_type_direct(LLVMVoidTypeInContext(c)); } else if (lb_is_type_kind(return_type, LLVMStructTypeKind) || lb_is_type_kind(return_type, LLVMArrayTypeKind)) { // Only some i386 targets return a small aggregate in EDX:EAX. The Intel386 System V // psABI returns every structure and union through the hidden pointer, with no size // threshold; Windows and the BSDs return one of eight bytes or fewer in registers. bool small_in_registers = build_context.metrics.os == TargetOs_windows || build_context.metrics.os == TargetOs_freebsd || build_context.metrics.os == TargetOs_openbsd; i64 sz = lb_sizeof(return_type); if (small_in_registers) { switch (sz) { case 1: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 8), nullptr, nullptr); case 2: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 16), nullptr, nullptr); case 4: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr); case 8: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 64), nullptr, nullptr); } } LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO(); LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type); return lb_arg_type_indirect(return_type, attr); } return non_struct(c, return_type, true, nullptr); } }; namespace lbAbiAmd64Win64 { gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, Type *original_type); gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type); gb_internal LB_ABI_INFO(abi_info) { LLVMContextRef c = m->ctx; lbFunctionType *ft = permanent_alloc_item(); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count, original_type); ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple); ft->calling_convention = calling_convention; return ft; } gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, Type *original_type) { auto args = array_make(lb_function_type_args_allocator(), arg_count); for (unsigned i = 0; i < arg_count; i++) { LLVMTypeRef t = arg_types[i]; LLVMTypeKind kind = LLVMGetTypeKind(t); if (kind == LLVMStructTypeKind || kind == LLVMArrayTypeKind) { i64 sz = lb_sizeof(t); switch (sz) { case 1: case 2: case 4: case 8: args[i] = lb_arg_type_direct(t, LLVMIntTypeInContext(c, 8*cast(unsigned)sz), nullptr, nullptr); break; default: args[i] = lb_arg_type_indirect(t, nullptr); break; } } else { args[i] = lbAbi386::non_struct(c, t, false, nullptr); } } return args; } gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type) { if (!return_is_defined) { return lb_arg_type_direct(LLVMVoidTypeInContext(c)); } else if (lb_is_type_kind(return_type, LLVMStructTypeKind) || lb_is_type_kind(return_type, LLVMArrayTypeKind)) { i64 sz = lb_sizeof(return_type); switch (sz) { case 1: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 8), nullptr, nullptr); case 2: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 16), nullptr, nullptr); case 4: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr); case 8: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 64), nullptr, nullptr); } LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO(); LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type); return lb_arg_type_indirect(return_type, attr); } return lbAbi386::non_struct(c, return_type, true, nullptr); } }; gb_internal bool is_llvm_type_slice_like(LLVMTypeRef type) { if (!lb_is_type_kind(type, LLVMStructTypeKind)) { return false; } if (LLVMCountStructElementTypes(type) != 2) { return false; } LLVMTypeRef fields[2] = {}; LLVMGetStructElementTypes(type, fields); if (!lb_is_type_kind(fields[0], LLVMPointerTypeKind)) { return false; } return lb_is_type_kind(fields[1], LLVMIntegerTypeKind) && lb_sizeof(fields[1]) == 8; } // NOTE(bill): I hate `namespace` in C++ but this is just because I don't want to prefix everything namespace lbAbiAmd64SysV { enum RegClass { RegClass_NoClass, RegClass_Int, RegClass_SSEHs, RegClass_SSEHv, RegClass_SSEFs, RegClass_SSEFv, RegClass_SSEDs, RegClass_SSEDv, RegClass_SSEInt8, RegClass_SSEInt16, RegClass_SSEInt32, RegClass_SSEInt64, RegClass_SSEInt128, RegClass_SSEUp, RegClass_X87, RegClass_X87Up, RegClass_ComplexX87, RegClass_Memory, }; gb_internal bool is_sse(RegClass reg_class) { switch (reg_class) { case RegClass_SSEHs: case RegClass_SSEHv: case RegClass_SSEFs: case RegClass_SSEFv: case RegClass_SSEDs: case RegClass_SSEDv: return true; case RegClass_SSEInt8: case RegClass_SSEInt16: case RegClass_SSEInt32: case RegClass_SSEInt64: return true; } return false; } gb_internal void all_mem(Array *cs) { for_array(i, *cs) { (*cs)[i] = RegClass_Memory; } } enum Amd64TypeAttributeKind { Amd64TypeAttribute_None, Amd64TypeAttribute_ByVal, Amd64TypeAttribute_StructRect, }; gb_internal void classify_with(LLVMTypeRef t, Array *cls, i64 ix, i64 off); gb_internal void unify(Array *cls, i64 i, RegClass const newv); gb_internal void fixup(LLVMTypeRef t, Array *cls); gb_internal lbArgType amd64_type(LLVMContextRef c, LLVMTypeRef type, Amd64TypeAttributeKind attribute_kind, ProcCallingConvention calling_convention, bool is_arg, i32 *int_regs, i32 *sse_regs, Type *source_type); gb_internal Array classify(LLVMTypeRef t, Type *source_type); gb_internal LLVMTypeRef llreg(LLVMContextRef c, Array const ®_classes, LLVMTypeRef type); gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type) { if (!return_is_defined) { return lb_arg_type_direct(LLVMVoidTypeInContext(c)); } LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO(); return amd64_type(c, return_type, Amd64TypeAttribute_StructRect, ft->calling_convention, false, nullptr, nullptr, nullptr); } gb_internal LB_ABI_INFO(abi_info) { LLVMContextRef c = m->ctx; lbFunctionType *ft = permanent_alloc_item(); ft->ctx = c; ft->calling_convention = calling_convention; i32 int_regs = 6; // rdi, rsi, rdx, rcx, r8, r9 i32 sse_regs = 8; // xmm0-xmm7 // The source type of each parameter, where one exists. `arg_types` can carry entries // with no counterpart. This walks the tuple the way lbAbiArm64 does and hands back nullptr once it runs out. Entity **params = nullptr; isize param_count = 0; if (original_type != nullptr && original_type->kind == Type_Proc && original_type->Proc.params != nullptr) { params = original_type->Proc.params->Tuple.variables.data; param_count = original_type->Proc.params->Tuple.variables.count; } ft->args = array_make(lb_function_type_args_allocator(), arg_count); for (unsigned i = 0, j = 0; i < arg_count; i++, j++) { while (cast(isize)j < param_count && params[j]->kind != Entity_Variable) { j++; } Type *source_type = cast(isize)j < param_count ? params[j]->type : nullptr; ft->args[i] = amd64_type(c, arg_types[i], Amd64TypeAttribute_ByVal, calling_convention, true, &int_regs, &sse_regs, source_type); } // A single result can be classified from its source type too. A tuple keeps the lowered // path: it is split into out-pointers below, and C has no such return shape anyway. Type *return_source = nullptr; if (return_is_defined && !return_is_tuple && original_type != nullptr && original_type->kind == Type_Proc && original_type->Proc.results != nullptr && original_type->Proc.results->Tuple.variables.count == 1) { return_source = original_type->Proc.results->Tuple.variables[0]->type; } if (return_source != nullptr) { ft->ret = amd64_type(c, return_type, Amd64TypeAttribute_StructRect, calling_convention, false, nullptr, nullptr, return_source); } else { ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple); } return ft; } gb_internal bool is_mem_cls(Array const &cls, Amd64TypeAttributeKind attribute_kind) { if (attribute_kind == Amd64TypeAttribute_ByVal) { if (cls.count == 0) { return false; } auto first = cls[0]; return first == RegClass_Memory || first == RegClass_X87 || first == RegClass_ComplexX87; } else if (attribute_kind == Amd64TypeAttribute_StructRect) { if (cls.count == 0) { return false; } return cls[0] == RegClass_Memory; } return false; } gb_internal bool is_register(LLVMTypeRef type) { LLVMTypeKind kind = LLVMGetTypeKind(type); i64 sz = lb_sizeof(type); if (sz == 0) { return false; } switch (kind) { case LLVMIntegerTypeKind: if (LLVM_VERSION_MAJOR >= 18 && sz >= 16) { return true; } return false; case LLVMHalfTypeKind: case LLVMFloatTypeKind: case LLVMDoubleTypeKind: case LLVMPointerTypeKind: return true; } return false; } gb_internal bool is_aggregate(LLVMTypeRef type) { // A single-member wrapper is passed like its member, but only while that // member still fits one eightbyte. `struct{i128}` needs two registers and // goes to memory when they are gone. A bare `i128` does not; clang emits // `byval align 16` for the struct and a plain `i128` for the scalar. LLVMTypeKind kind = LLVMGetTypeKind(type); switch (kind) { case LLVMStructTypeKind: if (LLVMCountStructElementTypes(type) == 1) { LLVMTypeRef elem = LLVMStructGetTypeAtIndex(type, 0); // A VECTOR member keeps the wrapper an aggregate whatever its size: // LLVM gives a vector its own oversized stack slot, so an unwrapped // `struct{#simd[2]f32}` takes 16 bytes where the ABI wants 8. if (LLVMGetTypeKind(elem) == LLVMVectorTypeKind) { return true; } return lb_sizeof(elem) > 8 || is_aggregate(elem); } return true; case LLVMArrayTypeKind: if (LLVMGetArrayLength(type) == 1) { LLVMTypeRef elem = OdinLLVMGetArrayElementType(type); return lb_sizeof(elem) > 8 || is_aggregate(elem); } return true; } return false; }; gb_internal lbArgType amd64_type(LLVMContextRef c, LLVMTypeRef type, Amd64TypeAttributeKind attribute_kind, ProcCallingConvention calling_convention, bool is_arg, i32 *int_regs, i32 *sse_regs, Type *source_type) { auto cls = classify(type, source_type); i32 needed_int = 0; i32 needed_sse = 0; for (auto c : cls) { switch (c) { case RegClass_Int: needed_int += 1; break; case RegClass_SSEHs: case RegClass_SSEHv: case RegClass_SSEFs: case RegClass_SSEFv: case RegClass_SSEDs: case RegClass_SSEDv: case RegClass_SSEInt8: case RegClass_SSEInt16: case RegClass_SSEInt32: case RegClass_SSEInt64: case RegClass_SSEInt128: case RegClass_SSEUp: needed_sse += 1; break; } } bool ran_out_of_regs = false; if (int_regs && sse_regs) { *int_regs -= needed_int; *sse_regs -= needed_sse; bool int_ok = *int_regs >= 0; bool sse_ok = *sse_regs >= 0; *int_regs = gb_max(*int_regs, 0); *sse_regs = gb_max(*sse_regs, 0); if ((!int_ok || !sse_ok) && is_aggregate(type)) { ran_out_of_regs = true; } } if (cls.count > 2 && is_sse(cls[0])) { // An SSE run wider than two eightbytes has no register to land in at // the baseline ISA. It goes to memory, bare vector or struct-wrapped. // A bare vector RETURN is the one exception: it is not an aggregate. // Clang gives it no hidden pointer and lets LLVM split it across // xmm0:xmm1, but the struct that wraps it still gets one. if (is_arg) { return lb_arg_type_indirect_byval(c, type, source_type); } if (LLVMGetTypeKind(type) != LLVMVectorTypeKind) { all_mem(&cls); } } if (is_register(type)) { LLVMAttributeRef attribute = lb_integer_extension_attribute(c, type, source_type); return lb_arg_type_direct(type, nullptr, nullptr, attribute); } else if (ran_out_of_regs) { if (is_arg) { return lb_arg_type_indirect_byval(c, type, source_type); } else { LLVMAttributeRef attribute = lb_create_enum_attribute_with_type(c, "sret", type); return lb_arg_type_indirect(type, attribute); } } else if (is_mem_cls(cls, attribute_kind)) { LLVMAttributeRef attribute = nullptr; if (attribute_kind == Amd64TypeAttribute_ByVal) { if (is_calling_convention_odin(calling_convention)) { return lb_arg_type_indirect(type, attribute); } return lb_arg_type_indirect_byval(c, type, source_type); } else if (attribute_kind == Amd64TypeAttribute_StructRect) { attribute = lb_create_enum_attribute_with_type(c, "sret", type); } return lb_arg_type_indirect(type, attribute); } else { LLVMTypeRef reg_type = nullptr; if (is_llvm_type_slice_like(type)) { // NOTE(bill): This is to make the ABI look closer to what the // original code is just for slices/strings whilst still adhering // the ABI rules for SysV reg_type = type; } else { reg_type = llreg(c, cls, type); } // `is_register` above answers false for every integer narrower than 16 bytes, so a // sub-word scalar lands HERE rather than in the direct arm, and this is where its // extension attribute has to go. LLVMAttributeRef attribute = lb_integer_extension_attribute(c, type, source_type); return lb_arg_type_direct(type, reg_type, nullptr, attribute); } } gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type) { LLVMAttributeRef attr = nullptr; LLVMTypeRef i1 = LLVMInt1TypeInContext(c); if (type == i1) { attr = lb_create_enum_attribute(c, "zeroext"); } return lb_arg_type_direct(type, nullptr, nullptr, attr); } // `classify_with` walks the LOWERED type, and lowering has already destroyed two // distinctions the ABI rules need: Odin materializes padding as an explicit `[N x i8]` // member, which is indistinguishable from a real `[N]u8` field, and a `#raw_union` becomes // an opaque integer, which is indistinguishable from a real integer. §3.2.3 says padding // contributes no class, and that a union merges the classes of all of its members. // // The source type still has both, so classify that instead where it is available. Only the // kinds handled below are eligible; anything else falls back to the lowered walk, so an // unrecognised type behaves exactly as it did before. gb_internal bool source_is_classifiable(Type *t) { Type *bt = base_type(t); if (bt == nullptr) { return false; } switch (bt->kind) { case Type_Basic: switch (bt->Basic.kind) { case Basic_bool: case Basic_b8: case Basic_b16: case Basic_b32: case Basic_b64: case Basic_i8: case Basic_u8: case Basic_i16: case Basic_u16: case Basic_i32: case Basic_u32: case Basic_i64: case Basic_u64: case Basic_i128: case Basic_u128: case Basic_int: case Basic_uint: case Basic_uintptr: case Basic_rawptr: case Basic_rune: case Basic_f16: case Basic_f32: case Basic_f64: return true; // Multi-word, but every word of them is a pointer or an integer, so the leaf rule // below classifies them correctly without knowing their shape. case Basic_string: case Basic_cstring: case Basic_any: case Basic_typeid: return true; } return false; case Type_Pointer: case Type_MultiPointer: case Type_Proc: // Integer-backed, or aggregates of pointers and integers. None of them can contain a // floating-point member, which is the only thing the leaf rule needs to tell apart. case Type_Enum: case Type_BitSet: case Type_Slice: case Type_DynamicArray: return true; case Type_Array: return source_is_classifiable(bt->Array.elem); // Odin matrices are laid out with no padding at all; see the note on // matrix_type_stride_in_bytes case Type_Matrix: return source_is_classifiable(bt->Matrix.elem); case Type_Struct: if (bt->Struct.is_packed || bt->Struct.soa_kind != StructSoa_None) { return false; } for (Entity *f : bt->Struct.fields) { if (!source_is_classifiable(f->type)) { return false; } } return true; } return false; } gb_internal void classify_source(Type *t, Array *cls, i64 ix, i64 off) { Type *bt = base_type(t); i64 t_size = type_size_of(bt); i64 t_align = type_align_of(bt); if (t_align != 0 && (off % t_align) != 0) { i64 e = (off + t_size + 7) / 8; for (i64 i = off / 8; i < e; i++) { unify(cls, ix+i, RegClass_Memory); } return; } switch (bt->kind) { case Type_Struct: // A `#raw_union` has every member at offset zero, and §3.2.3 merges them all -- // which is what makes `union{f32, u32}` INTEGER while `union{f32, f32}` is SSE. if (bt->Struct.is_raw_union) { for (Entity *f : bt->Struct.fields) { classify_source(f->type, cls, ix, off); } } else { for_array(i, bt->Struct.fields) { Type *ft = nullptr; i64 foff = type_offset_of(bt, i, &ft); classify_source(ft, cls, ix, off + foff); } } break; case Type_Array: { Type *elem = bt->Array.elem; i64 stride = type_size_of(elem); for (i64 i = 0; i < bt->Array.count; i++) { classify_source(elem, cls, ix, off + i*stride); } break; } case Type_Matrix: { Type *elem = bt->Matrix.elem; i64 stride = type_size_of(elem); i64 count = matrix_type_total_internal_elems(bt); for (i64 i = 0; i < count; i++) { classify_source(elem, cls, ix, off + i*stride); } break; } default: if (is_type_float(bt)) { switch (t_size) { case 2: unify(cls, ix + off/8, (off%8 != 0) ? RegClass_SSEHv : RegClass_SSEHs); break; case 4: unify(cls, ix + off/8, (off%8 == 4) ? RegClass_SSEFv : RegClass_SSEFs); break; default: unify(cls, ix + off/8, RegClass_SSEDs); break; } } else { i64 s = t_size; while (s > 0) { unify(cls, ix + off/8, RegClass_Int); off += 8; s -= 8; } } break; } } gb_internal Array classify(LLVMTypeRef t, Type *source_type) { i64 sz = lb_sizeof(t); i64 words = (sz + 7)/8; auto reg_classes = array_make(heap_allocator(), cast(isize)words); if (words > 4 && LLVMGetTypeKind(t) != LLVMVectorTypeKind) { // A BARE vector is exempt: it is not an aggregate. Clang never gives it // a hidden pointer however wide it is. `<16 x float>` is returned directly // and split across xmm0-xmm3, and the SSE run below still sends it to // memory as an ARGUMENT. all_mem(®_classes); } else { bool from_source = source_type != nullptr && source_is_classifiable(source_type) && type_size_of(base_type(source_type)) == sz; if (from_source) { classify_source(source_type, ®_classes, 0, 0); } else { classify_with(t, ®_classes, 0, 0); } fixup(t, ®_classes); if (from_source) { // An eightbyte that ends up NO_CLASS is not passed at all. Only the source walk // can produce one, the lowered walk classifies padding as INTEGER, and // nothing downstream has a case for it. // // This has to come AFTER `fixup`, which counts eightbytes to apply "larger than // two eightbytes is MEMORY". Dropping them first makes `#align(32){f32}` look // like a single SSE eightbyte instead of the memory argument it is. while (reg_classes.count > 0 && reg_classes[reg_classes.count-1] == RegClass_NoClass) { array_pop(®_classes); } } } return reg_classes; } // How much of its eightbyte an SSE class occupies, which is what `llreg` turns // it back into: the scalar classes are as wide as their element, and every `v` // class becomes a vector spanning the whole eightbyte. gb_internal i64 sse_class_width(RegClass c) { switch (c) { case RegClass_SSEHs: return 2; case RegClass_SSEFs: return 4; } return 8; } gb_internal void unify(Array *cls, i64 i, RegClass const newv) { RegClass const oldv = (*cls)[cast(isize)i]; if (oldv == newv) { return; } RegClass to_write = newv; if (oldv == RegClass_NoClass) { to_write = newv; } else if (newv == RegClass_NoClass) { return; } else if (oldv == RegClass_Memory || newv == RegClass_Memory) { to_write = RegClass_Memory; } else if (oldv == RegClass_Int || newv == RegClass_Int) { to_write = RegClass_Int; } else if (oldv == RegClass_X87 || oldv == RegClass_X87Up || oldv == RegClass_ComplexX87) { to_write = RegClass_Memory; } else if (newv == RegClass_X87 || newv == RegClass_X87Up || newv == RegClass_ComplexX87) { to_write = RegClass_Memory; } else if (newv == RegClass_SSEUp) { switch (oldv) { case RegClass_SSEHv: case RegClass_SSEHs: case RegClass_SSEFv: case RegClass_SSEFs: case RegClass_SSEDv: case RegClass_SSEDs: case RegClass_SSEInt8: case RegClass_SSEInt16: case RegClass_SSEInt32: case RegClass_SSEInt64: return; } } else if (is_sse(oldv) && is_sse(newv) && sse_class_width(oldv) > sse_class_width(newv)) { // The members OVERLAP, a union. Last-writer-wins would pass // `union{f64, f32}` as a 4-byte float, and `union{[2]f32, f32}` as one // lane of two, losing the top half of the eightbyte either way. Keeping // the WIDER class is what leaves `struct{f32, f16}` alone: that is Fs // then Hv at offset 4, and Hv spans the eightbyte, so it still widens // rather than gets picked over. return; } (*cls)[cast(isize)i] = to_write; } gb_internal void fixup(LLVMTypeRef t, Array *cls) { i64 i = 0; i64 e = cls->count; if (e > 2 && (lb_is_type_kind(t, LLVMStructTypeKind) || lb_is_type_kind(t, LLVMArrayTypeKind) || lb_is_type_kind(t, LLVMVectorTypeKind))) { RegClass &oldv = (*cls)[cast(isize)i]; if (is_sse(oldv)) { for (i++; i < e; i++) { // NOTE: the current eightbyte, not `oldv`, is bound to cls[0], which is never SSEUp if ((*cls)[cast(isize)i] != RegClass_SSEUp) { all_mem(cls); return; } } } else { all_mem(cls); return; } } else { while (i < e) { RegClass &oldv = (*cls)[cast(isize)i]; if (oldv == RegClass_Memory) { all_mem(cls); return; } else if (oldv == RegClass_X87Up) { // NOTE(bill): Darwin all_mem(cls); return; } else if (oldv == RegClass_SSEUp) { oldv = RegClass_SSEDv; } else if (is_sse(oldv)) { for (i++; i < e; i++) { RegClass v = (*cls)[cast(isize)i]; if (v != RegClass_SSEUp) { break; } } } else if (oldv == RegClass_X87) { for (i++; i < e; i++) { RegClass v = (*cls)[cast(isize)i]; if (v != RegClass_X87Up) { break; } } } else { i++; } } } } gb_internal unsigned llvec_len(Array const ®_classes, isize offset) { unsigned len = 1; for (isize i = offset; i < reg_classes.count; i++) { if (reg_classes[i] != RegClass_SSEUp) { break; } len++; } return len; } gb_internal LLVMTypeRef llreg(LLVMContextRef c, Array const ®_classes, LLVMTypeRef type) { auto types = array_make(heap_allocator(), 0, reg_classes.count); bool all_ints = true; for (RegClass reg_class : reg_classes) { if (reg_class != RegClass_Int) { all_ints = false; break; } } i64 sz = lb_sizeof(type); if (LLVMGetTypeKind(type) == LLVMVectorTypeKind && sz == 8 && reg_classes.count == 1 && is_sse(reg_classes[0])) { // LLVM rounds a bare vector's stack slot up to the legal vector width. // An 8-byte one takes 16 bytes where the ABI wants 8. clang coerces every // 64-bit vector to `double`, which lands in the same half of the same xmm // and takes one eightbyte on the stack. array_free(&types); return LLVMDoubleTypeInContext(c); } if (all_ints) { for_array(i, reg_classes) { GB_ASSERT(sz > 0); // TODO(bill): is this even correct? BECAUSE LLVM DOES NOT DOCUMENT ANY OF THIS!!! if (sz >= 8) { array_add(&types, LLVMIntTypeInContext(c, 64)); sz -= 8; } else { array_add(&types, LLVMIntTypeInContext(c, cast(unsigned)(sz*8))); sz = 0; } } } else { for (isize i = 0; i < reg_classes.count; /**/) { GB_ASSERT(sz > 0); RegClass reg_class = reg_classes[i]; switch (reg_class) { case RegClass_Int: { i64 rs = gb_min(sz, 8); array_add(&types, LLVMIntTypeInContext(c, cast(unsigned)(rs*8))); sz -= rs; break; } case RegClass_SSEHv: case RegClass_SSEFv: case RegClass_SSEDv: case RegClass_SSEInt8: case RegClass_SSEInt16: case RegClass_SSEInt32: case RegClass_SSEInt64: { unsigned elems_per_word = 0; LLVMTypeRef elem_type = nullptr; switch (reg_class) { case RegClass_SSEHv: elems_per_word = 4; elem_type = LLVMHalfTypeInContext(c); break; case RegClass_SSEFv: elems_per_word = 2; elem_type = LLVMFloatTypeInContext(c); break; case RegClass_SSEDv: elems_per_word = 1; elem_type = LLVMDoubleTypeInContext(c); break; case RegClass_SSEInt8: elems_per_word = 64/8; elem_type = LLVMIntTypeInContext(c, 8); break; case RegClass_SSEInt16: elems_per_word = 64/16; elem_type = LLVMIntTypeInContext(c, 16); break; case RegClass_SSEInt32: elems_per_word = 64/32; elem_type = LLVMIntTypeInContext(c, 32); break; case RegClass_SSEInt64: elems_per_word = 64/64; elem_type = LLVMIntTypeInContext(c, 64); break; } unsigned vec_len = llvec_len(reg_classes, i+1); unsigned lanes = vec_len * elems_per_word; // Never widen past what is actually left: a 4-byte vector // occupies half an eightbyte, and padding it to a whole one // makes the parameter 8 bytes where clang coerces to i32. i64 elem_bytes = lb_sizeof(elem_type); if (elem_bytes > 0 && sz > 0 && cast(i64)lanes * elem_bytes > sz) { lanes = cast(unsigned)(sz / elem_bytes); } if (lanes == 0) { lanes = 1; } LLVMTypeRef vec_type = LLVMVectorType(elem_type, lanes); array_add(&types, vec_type); sz -= lb_sizeof(vec_type); i += vec_len; continue; } break; case RegClass_SSEHs: array_add(&types, LLVMHalfTypeInContext(c)); sz -= 2; break; case RegClass_SSEFs: array_add(&types, LLVMFloatTypeInContext(c)); sz -= 4; break; case RegClass_SSEDs: array_add(&types, LLVMDoubleTypeInContext(c)); sz -= 8; break; default: GB_PANIC("Unhandled RegClass"); } i += 1; } } if (types.count == 1) { return types[0]; } return LLVMStructTypeInContext(c, types.data, cast(unsigned)types.count, false); } gb_internal void classify_with(LLVMTypeRef t, Array *cls, i64 ix, i64 off) { i64 t_align = lb_alignof(t); i64 t_size = lb_sizeof(t); i64 misalign = off % t_align; if (misalign != 0) { i64 e = (off + t_size + 7) / 8; for (i64 i = off / 8; i < e; i++) { unify(cls, ix+i, RegClass_Memory); } return; } switch (LLVMGetTypeKind(t)) { case LLVMIntegerTypeKind: { i64 s = t_size; while (s > 0) { unify(cls, ix + off/8, RegClass_Int); off += 8; s -= 8; } break; } case LLVMPointerTypeKind: unify(cls, ix + off/8, RegClass_Int); break; case LLVMHalfTypeKind: unify(cls, ix + off/8, (off%8 != 0) ? RegClass_SSEHv : RegClass_SSEHs); break; case LLVMFloatTypeKind: unify(cls, ix + off/8, (off%8 == 4) ? RegClass_SSEFv : RegClass_SSEFs); break; case LLVMDoubleTypeKind: unify(cls, ix + off/8, RegClass_SSEDs); break; case LLVMStructTypeKind: { LLVMBool packed = LLVMIsPackedStruct(t); unsigned field_count = LLVMCountStructElementTypes(t); i64 field_off = off; for (unsigned field_index = 0; field_index < field_count; field_index++) { LLVMTypeRef field_type = LLVMStructGetTypeAtIndex(t, field_index); if (!packed) { field_off = llvm_align_formula(field_off, lb_alignof(field_type)); } classify_with(field_type, cls, ix, field_off); field_off += lb_sizeof(field_type); } } break; case LLVMArrayTypeKind: { i64 len = LLVMGetArrayLength(t); LLVMTypeRef elem = OdinLLVMGetArrayElementType(t); i64 elem_sz = lb_sizeof(elem); for (i64 i = 0; i < len; i++) { classify_with(elem, cls, ix, off + i*elem_sz); } } break; case LLVMVectorTypeKind: { i64 len = LLVMGetVectorSize(t); LLVMTypeRef elem = OdinLLVMGetVectorElementType(t); i64 elem_sz = lb_sizeof(elem); LLVMTypeKind elem_kind = LLVMGetTypeKind(elem); if (t_size < 8) { // A vector narrower than an eightbyte is INTEGER, not SSE: // clang coerces `<4 x i8>` to `i32` and passes it in an integer // register. unify(cls, ix + off/8, RegClass_Int); break; } RegClass reg = RegClass_NoClass; switch (elem_kind) { case LLVMIntegerTypeKind: { unsigned elem_width = LLVMGetIntTypeWidth(elem); switch (elem_width) { case 8: reg = RegClass_SSEInt8; break; case 16: reg = RegClass_SSEInt16; break; case 32: reg = RegClass_SSEInt32; break; case 64: reg = RegClass_SSEInt64; break; default: if (elem_width > 64) { for (i64 i = 0; i < len; i++) { classify_with(elem, cls, ix, off + i*elem_sz); } break; } GB_PANIC("Unhandled integer width for vector type %u", elem_width); } break; }; case LLVMHalfTypeKind: reg = RegClass_SSEHv; break; case LLVMFloatTypeKind: reg = RegClass_SSEFv; break; case LLVMDoubleTypeKind: reg = RegClass_SSEDv; break; default: GB_PANIC("Unhandled vector element type"); } for (i64 i = 0; i < len; i++) { unify(cls, ix + (off + i*elem_sz)/8, reg); // NOTE(bill): Everything after the first one is the upper // half of a register reg = RegClass_SSEUp; } } break; default: GB_PANIC("Unhandled type"); break; } } }; namespace lbAbiArm64 { gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, Type* original_type); gb_internal bool is_register(LLVMTypeRef type); gb_internal bool is_homogenous_aggregate(LLVMContextRef c, LLVMTypeRef type, LLVMTypeRef *base_type_, unsigned *member_count_); gb_internal bool is_homogenous_aggregate_source(LLVMContextRef c, Type *t, LLVMTypeRef *base_type_, unsigned *member_count_); gb_internal unsigned is_homogenous_aggregate_small_enough(LLVMTypeRef base_type, unsigned member_count); gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type); gb_internal bool is_homogenous_aggregate(LLVMContextRef c, LLVMTypeRef type, LLVMTypeRef *base_type_, unsigned *member_count_); gb_internal LB_ABI_INFO(abi_info) { LLVMContextRef c = m->ctx; lbFunctionType *ft = permanent_alloc_item(); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count, original_type); // The same union case as in compute_arg_types, in return position. A tuple keeps the // lowered path; C has no such return shape, and the split into out-pointers below is // driven by the lowered type. Type *return_source = nullptr; if (return_is_defined && !return_is_tuple && original_type != nullptr && original_type->kind == Type_Proc && original_type->Proc.results != nullptr && original_type->Proc.results->Tuple.variables.count == 1) { return_source = original_type->Proc.results->Tuple.variables[0]->type; } LLVMTypeRef ret_base_type = nullptr; unsigned ret_member_count = 0; if (return_source != nullptr && !is_register(return_type) && !is_homogenous_aggregate(c, return_type, nullptr, nullptr) && is_homogenous_aggregate_source(c, return_source, &ret_base_type, &ret_member_count) && is_homogenous_aggregate_small_enough(ret_base_type, ret_member_count)) { ft->ret = lb_arg_type_direct(return_type, llvm_array_type(ret_base_type, ret_member_count), nullptr, nullptr); } else { ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple); } ft->calling_convention = calling_convention; return ft; } gb_internal bool is_register(LLVMTypeRef type) { LLVMTypeKind kind = LLVMGetTypeKind(type); switch (kind) { case LLVMIntegerTypeKind: case LLVMHalfTypeKind: case LLVMFloatTypeKind: case LLVMDoubleTypeKind: case LLVMPointerTypeKind: return true; case LLVMVectorTypeKind:{ i64 sz = lb_sizeof(type); return sz == 8 || sz == 16; } } return false; } gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type, Type* original_type) { LLVMAttributeRef attr = nullptr; LLVMTypeRef i1 = LLVMInt1TypeInContext(c); // https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Pass-arguments-to-functions-correctly // Darwin expects caller to take responsibility of zero/sign extending any arguments < 32bits. if (build_context.metrics.os == TargetOs_darwin && original_type != nullptr) { if ((is_type_integer_like(original_type) || is_type_enum(original_type)) && lb_sizeof(type) < 4) { if (is_type_unsigned(original_type) || is_type_boolean(original_type)) { attr = lb_create_enum_attribute(c, "zeroext"); } else { attr = lb_create_enum_attribute(c, "signext"); } } } else { if (type == i1) { attr = lb_create_enum_attribute(c, "zeroext"); } } return lb_arg_type_direct(type, nullptr, nullptr, attr); } gb_internal bool is_homogenous_array(LLVMContextRef c, LLVMTypeRef type, LLVMTypeRef *base_type_, unsigned *member_count_) { GB_ASSERT(lb_is_type_kind(type, LLVMArrayTypeKind)); unsigned len = LLVMGetArrayLength(type); if (len == 0) { return false; } LLVMTypeRef elem = OdinLLVMGetArrayElementType(type); LLVMTypeRef base_type = nullptr; unsigned member_count = 0; if (is_homogenous_aggregate(c, elem, &base_type, &member_count)) { if (base_type_) *base_type_ = base_type; if (member_count_) *member_count_ = member_count * len; return true; } return false; } gb_internal bool is_homogenous_struct(LLVMContextRef c, LLVMTypeRef type, LLVMTypeRef *base_type_, unsigned *member_count_) { GB_ASSERT(lb_is_type_kind(type, LLVMStructTypeKind)); unsigned elem_count = LLVMCountStructElementTypes(type); if (elem_count == 0) { return false; } LLVMTypeRef base_type = nullptr; unsigned member_count = 0; for (unsigned i = 0; i < elem_count; i++) { LLVMTypeRef field_type = nullptr; unsigned field_member_count = 0; LLVMTypeRef elem = LLVMStructGetTypeAtIndex(type, i); if (lb_is_type_kind(elem, LLVMStructTypeKind) && lb_sizeof(elem) == 0) { // an empty struct occupies nothing and is ignored continue; } if (!is_homogenous_aggregate(c, elem, &field_type, &field_member_count)) { return false; } if (base_type == nullptr) { base_type = field_type; member_count = field_member_count; } else { if (base_type != field_type) { return false; } member_count += field_member_count; } } if (base_type == nullptr) { return false; } if (lb_sizeof(type) == lb_sizeof(base_type) * member_count) { if (base_type_) *base_type_ = base_type; if (member_count_) *member_count_ = member_count; return true; } return false; } gb_internal bool is_homogenous_aggregate(LLVMContextRef c, LLVMTypeRef type, LLVMTypeRef *base_type_, unsigned *member_count_) { LLVMTypeKind kind = LLVMGetTypeKind(type); switch (kind) { case LLVMHalfTypeKind: case LLVMFloatTypeKind: case LLVMDoubleTypeKind: if (base_type_) *base_type_ = type; if (member_count_) *member_count_ = 1; return true; case LLVMVectorTypeKind:{ i64 sz = lb_sizeof(type); if (sz == 8 || sz == 16){ if (base_type_) *base_type_ = type; if (member_count_) *member_count_ = 1; return true; } return false; } case LLVMArrayTypeKind: return is_homogenous_array(c, type, base_type_, member_count_); case LLVMStructTypeKind: return is_homogenous_struct(c, type, base_type_, member_count_); } return false; } // §5.9.5 defines a Homogeneous Floating-point Aggregate over Composite Types // Odin lowers `#raw_union` to an opaque integer, so by the time the lowered type is // inspected the members are gone and `union{f32, f32}` is indistinguishable from an `i32`. // The source type still has them. gb_internal bool is_homogenous_aggregate_source(LLVMContextRef c, Type *t, LLVMTypeRef *base_type_, unsigned *member_count_) { if (t == nullptr) { return false; } Type *bt = base_type(t); if (bt == nullptr) { return false; } switch (bt->kind) { case Type_Basic: switch (bt->Basic.kind) { case Basic_f16: if (base_type_) *base_type_ = LLVMHalfTypeInContext(c); if (member_count_) *member_count_ = 1; return true; case Basic_f32: if (base_type_) *base_type_ = LLVMFloatTypeInContext(c); if (member_count_) *member_count_ = 1; return true; case Basic_f64: if (base_type_) *base_type_ = LLVMDoubleTypeInContext(c); if (member_count_) *member_count_ = 1; return true; } return false; case Type_Array: { if (bt->Array.count == 0) { // a zero-length member disqualifies the aggregate, unlike an empty struct return false; } LLVMTypeRef elem_base = nullptr; unsigned elem_count = 0; if (!is_homogenous_aggregate_source(c, bt->Array.elem, &elem_base, &elem_count)) { return false; } if (base_type_) *base_type_ = elem_base; if (member_count_) *member_count_ = cast(unsigned)(elem_count * bt->Array.count); return true; } case Type_Struct: { if (bt->Struct.is_packed || bt->Struct.soa_kind != StructSoa_None) { return false; } LLVMTypeRef found_base = nullptr; unsigned total = 0; for (Entity *f : bt->Struct.fields) { Type *fbt = base_type(f->type); if (fbt != nullptr && fbt->kind == Type_Struct && type_size_of(f->type) == 0) { // an empty struct occupies nothing and is ignored continue; } LLVMTypeRef field_base = nullptr; unsigned field_count = 0; if (!is_homogenous_aggregate_source(c, f->type, &field_base, &field_count)) { return false; } if (found_base == nullptr) { found_base = field_base; total = field_count; } else if (found_base != field_base) { return false; } else { total = bt->Struct.is_raw_union ? gb_max(total, field_count) : total + field_count; } } if (found_base == nullptr) { return false; } // Rejects anything with padding, matching is_homogenous_struct. if (type_size_of(bt) != lb_sizeof(found_base) * cast(i64)total) { return false; } if (base_type_) *base_type_ = found_base; if (member_count_) *member_count_ = total; return true; } } return false; } gb_internal unsigned is_homogenous_aggregate_small_enough(LLVMTypeRef base_type, unsigned member_count) { return (member_count <= 4); } gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type) { LLVMTypeRef homo_base_type = nullptr; unsigned homo_member_count = 0; if (!return_is_defined) { return lb_arg_type_direct(LLVMVoidTypeInContext(c)); } else if (is_register(return_type)) { return non_struct(c, return_type, nullptr); } else if (is_homogenous_aggregate(c, return_type, &homo_base_type, &homo_member_count) && is_homogenous_aggregate_small_enough(homo_base_type, homo_member_count)) { return lb_arg_type_direct(return_type, llvm_array_type(homo_base_type, homo_member_count), nullptr, nullptr); } else { // too many members to be an HFA falls through to the size rule, it does not // become indirect on its own: `struct{[5]f16}` is 10 bytes and goes in x0:x1 i64 size = lb_sizeof(return_type); if (size > 16) { LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO(); LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type); return lb_arg_type_indirect(return_type, attr); } GB_ASSERT(size <= 16); if (LLVMGetTypeKind(return_type) == LLVMVectorTypeKind) { // A vector too narrow to be a short vector is still RETURNED as // itself. clang coerces a 4-byte vector argument to `i32` and puts // it in w0, but returns `<4 x i8>` in v0; coercing the return too // picks the wrong register file. return lb_arg_type_direct(return_type, nullptr, nullptr, nullptr); } LLVMTypeRef cast_type = nullptr; if (size == 0) { cast_type = LLVMStructTypeInContext(c, nullptr, 0, false); } else if (size <= 8) { cast_type = LLVMIntTypeInContext(c, cast(unsigned)(size*8)); } else { LLVMTypeRef llvm_i64 = LLVMIntTypeInContext(c, 64); cast_type = llvm_array_type(llvm_i64, 2); } return lb_arg_type_direct(return_type, cast_type, nullptr, nullptr); } } gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, Type* original_type) { auto args = array_make(lb_function_type_args_allocator(), arg_count); GB_ASSERT(original_type->kind == Type_Proc); auto const ¶ms = original_type->Proc.params->Tuple.variables; for (unsigned i = 0, j = 0; i < arg_count; i++, j++) { while (params[j]->kind != Entity_Variable) { j++; } Type *ptype = params[j]->type; LLVMTypeRef type = arg_types[i]; LLVMTypeRef homo_base_type = {}; unsigned homo_member_count = 0; // A `#raw_union` lowers to a struct wrapping an opaque integer, so it is not a // homogeneous aggregate by the lowered type and falls through to the generic size // path below. §5.9.5 counts a union as a Composite Type, so ask the source type. LLVMTypeRef src_base_type = nullptr; unsigned src_member_count = 0; if (is_register(type)) { args[i] = non_struct(c, type, ptype); } else if (is_homogenous_aggregate(c, type, &homo_base_type, &homo_member_count) && is_homogenous_aggregate_small_enough(homo_base_type, homo_member_count)) { args[i] = lb_arg_type_direct(type, llvm_array_type(homo_base_type, homo_member_count), nullptr, nullptr); } else if (is_homogenous_aggregate_source(c, ptype, &src_base_type, &src_member_count) && is_homogenous_aggregate_small_enough(src_base_type, src_member_count)) { args[i] = lb_arg_type_direct(type, llvm_array_type(src_base_type, src_member_count), nullptr, nullptr); } else { i64 size = lb_sizeof(type); if (size <= 16) { LLVMTypeRef cast_type = nullptr; if (size == 0) { cast_type = LLVMStructTypeInContext(c, nullptr, 0, false); } else if (size <= 8) { cast_type = LLVMIntTypeInContext(c, cast(unsigned)(size*8)); } else { LLVMTypeRef llvm_i64 = LLVMIntTypeInContext(c, 64); cast_type = llvm_array_type(llvm_i64, 2); } args[i] = lb_arg_type_direct(type, cast_type, nullptr, nullptr); } else { args[i] = lb_arg_type_indirect(type, nullptr); } } } return args; } } namespace lbAbiWasm { /* NOTE(bill): All of this is custom since there is not an "official" ABI definition for WASM, especially for Odin. The approach taken optimizes for passing things in multiple registers/arguments if possible rather than by pointer. */ gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention, Type *original_type); gb_internal lbArgType compute_return_type(lbFunctionType *ft, LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined, bool return_is_tuple, Type* original_type); enum {MAX_DIRECT_STRUCT_SIZE = 32}; gb_internal LB_ABI_INFO(abi_info) { LLVMContextRef c = m->ctx; lbFunctionType *ft = permanent_alloc_item(); ft->ctx = c; ft->calling_convention = calling_convention; ft->args = compute_arg_types(c, arg_types, arg_count, calling_convention, original_type); GB_ASSERT(original_type->kind == Type_Proc); ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple, original_type->Proc.results); return ft; } gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type, bool is_return) { if (type == LLVMIntTypeInContext(c, 128)) { // LLVMTypeRef cast_type = LLVMVectorType(LLVMInt64TypeInContext(c), 2); LLVMTypeRef cast_type = nullptr; return lb_arg_type_direct(type, cast_type, nullptr, nullptr); } if (!is_return && lb_sizeof(type) > 8) { return lb_arg_type_indirect(type, nullptr); } LLVMAttributeRef attr = nullptr; LLVMTypeRef i1 = LLVMInt1TypeInContext(c); if (type == i1) { attr = lb_create_enum_attribute(c, "zeroext"); } return lb_arg_type_direct(type, nullptr, nullptr, attr); } gb_internal bool is_basic_register_type(LLVMTypeRef type) { switch (LLVMGetTypeKind(type)) { case LLVMHalfTypeKind: case LLVMFloatTypeKind: case LLVMDoubleTypeKind: case LLVMPointerTypeKind: return true; case LLVMIntegerTypeKind: return lb_sizeof(type) <= 16; } return false; } gb_internal bool type_can_be_direct(LLVMTypeRef type, Type *original_type, ProcCallingConvention calling_convention) { LLVMTypeKind kind = LLVMGetTypeKind(type); i64 sz = lb_sizeof(type); if (sz == 0) { return false; } if (calling_convention == ProcCC_CDecl) { // WASM Basic C ABI: // https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md#function-signatures if (kind == LLVMArrayTypeKind) { return false; } else if (kind == LLVMStructTypeKind) { unsigned count = LLVMCountStructElementTypes(type); // NOTE(laytan): raw unions are always structs with 1 field in LLVM, need to check our own def. Type *bt = base_type(original_type); if (bt->kind == Type_Struct && bt->Struct.is_raw_union) { count = cast(unsigned)bt->Struct.fields.count; } if (count == 1) { return type_can_be_direct( LLVMStructGetTypeAtIndex(type, 0), type_internal_index(original_type, 0), calling_convention ); } } else if (is_basic_register_type(type)) { return true; } } else if (sz <= MAX_DIRECT_STRUCT_SIZE) { if (kind == LLVMArrayTypeKind) { if (is_basic_register_type(OdinLLVMGetArrayElementType(type))) { return true; } } else if (kind == LLVMStructTypeKind) { unsigned count = LLVMCountStructElementTypes(type); for (unsigned i = 0; i < count; i++) { LLVMTypeRef elem = LLVMStructGetTypeAtIndex(type, i); if (!is_basic_register_type(elem)) { return false; } } return true; } } return false; } gb_internal lbArgType is_struct(LLVMContextRef c, LLVMTypeRef type, Type *original_type, ProcCallingConvention calling_convention) { LLVMTypeKind kind = LLVMGetTypeKind(type); GB_ASSERT(kind == LLVMArrayTypeKind || kind == LLVMStructTypeKind); i64 sz = lb_sizeof(type); if (sz == 0) { return lb_arg_type_ignore(type); } if (type_can_be_direct(type, original_type, calling_convention)) { return lb_arg_type_direct(type); } return lb_arg_type_indirect(type, nullptr); } gb_internal lbArgType pseudo_slice(LLVMContextRef c, LLVMTypeRef type, Type *original_type, ProcCallingConvention calling_convention) { if (build_context.metrics.ptr_size < build_context.metrics.int_size && type_can_be_direct(type, original_type, calling_convention)) { LLVMTypeRef types[2] = { LLVMStructGetTypeAtIndex(type, 0), // ignore padding LLVMStructGetTypeAtIndex(type, 2) }; LLVMTypeRef new_type = LLVMStructTypeInContext(c, types, gb_count_of(types), false); return lb_arg_type_direct(type, new_type, nullptr, nullptr); } else { return is_struct(c, type, original_type, calling_convention); } } gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention, Type *original_type) { auto args = array_make(lb_function_type_args_allocator(), arg_count); GB_ASSERT(original_type->kind == Type_Proc); GB_ASSERT(cast(isize)arg_count <= original_type->Proc.param_count); auto const ¶ms = original_type->Proc.params->Tuple.variables; for (unsigned i = 0, j = 0; i < arg_count; i++, j++) { while (params[j]->kind != Entity_Variable) { j++; } Type *ptype = params[j]->type; LLVMTypeRef t = arg_types[i]; LLVMTypeKind kind = LLVMGetTypeKind(t); if (kind == LLVMStructTypeKind || kind == LLVMArrayTypeKind) { if (is_type_slice(ptype) || is_type_string(ptype)) { args[i] = pseudo_slice(c, t, ptype, calling_convention); } else { args[i] = is_struct(c, t, ptype, calling_convention); } } else { args[i] = non_struct(c, t, false); } } return args; } gb_internal lbArgType compute_return_type(lbFunctionType *ft, LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined, bool return_is_tuple, Type* original_type) { if (!return_is_defined) { return lb_arg_type_direct(LLVMVoidTypeInContext(c)); } else if (lb_is_type_kind(return_type, LLVMStructTypeKind) || lb_is_type_kind(return_type, LLVMArrayTypeKind)) { if (type_can_be_direct(return_type, original_type, ft->calling_convention)) { return lb_arg_type_direct(return_type); } else if (ft->calling_convention != ProcCC_CDecl) { i64 sz = lb_sizeof(return_type); switch (sz) { case 1: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 8), nullptr, nullptr); case 2: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 16), nullptr, nullptr); case 4: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr); case 8: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 64), nullptr, nullptr); } } // Multiple returns. if (return_is_tuple) { \ lbArgType return_arg = {}; if (lb_is_type_kind(return_type, LLVMStructTypeKind)) { unsigned field_count = LLVMCountStructElementTypes(return_type); if (field_count > 1) { ft->original_arg_count = ft->args.count; ft->multiple_return_original_type = return_type; for (unsigned i = 0; i < field_count-1; i++) { LLVMTypeRef field_type = LLVMStructGetTypeAtIndex(return_type, i); LLVMTypeRef field_pointer_type = LLVMPointerType(field_type, 0); lbArgType ret_partial = lb_arg_type_direct(field_pointer_type); array_add(&ft->args, ret_partial); } return_arg = compute_return_type( ft, c, LLVMStructGetTypeAtIndex(return_type, field_count-1), true, false, type_internal_index(original_type, field_count-1) ); } } if (return_arg.type != nullptr) { return return_arg; } } LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type); return lb_arg_type_indirect(return_type, attr); } return non_struct(c, return_type, true); } } namespace lbAbiArm32 { gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention, Type *original_type); gb_internal lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined, ProcCallingConvention calling_convention, Type *return_source); gb_internal LB_ABI_INFO(abi_info) { LLVMContextRef c = m->ctx; lbFunctionType *ft = permanent_alloc_item(); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count, calling_convention, original_type); ft->ret = compute_return_type(c, return_type, return_is_defined, calling_convention, lb_abi_single_result_type(original_type)); ft->calling_convention = calling_convention; return ft; } gb_internal bool is_register(LLVMTypeRef type, bool is_return) { LLVMTypeKind kind = LLVMGetTypeKind(type); switch (kind) { case LLVMHalfTypeKind: case LLVMFloatTypeKind: case LLVMDoubleTypeKind: return true; case LLVMIntegerTypeKind: return lb_sizeof(type) <= 8; case LLVMFunctionTypeKind: return true; case LLVMPointerTypeKind: return true; case LLVMVectorTypeKind: return true; } return false; } gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type, bool is_return, Type *source_type) { // A bare vector narrower than a word has no register of its own to sit in, clang coerces // it to `i32` as an argument whatever its element is. The return keeps the vector // type, same as x86, except: a half vector is not a legal type at this microarchitecture // (`arm1176jzf-s` has VFP2 but no fp16), so clang coerces that one in both directions. // // <4 x i8> <2 x i16> <2 x half> as an argument -> i32 // <4 x i8> <2 x i16> as a return -> unchanged // <2 x half> as a return -> i32 if (LLVMGetTypeKind(type) == LLVMVectorTypeKind && lb_sizeof(type) == 4) { bool is_half = LLVMGetTypeKind(LLVMGetElementType(type)) == LLVMHalfTypeKind; if (!is_return || is_half) { return lb_arg_type_direct(type, LLVMIntTypeInContext(c, 32), nullptr, nullptr); } } LLVMAttributeRef attr = lb_integer_extension_attribute(c, type, source_type); return lb_arg_type_direct(type, nullptr, nullptr, attr); } // AAPCS32 §5.5, the VFP variant that the `gnueabihf` triple selects: an aggregate of at most // four members that are all the same fp type is a Homogeneous fp Aggregate, and travels // in s0-s3 / d0-d3 rather than in the core registers. Everything below coerces aggregates to // `[N x i32]`, which puts an HFA in r0-r3 where the C side reads s0-s3. // // The detector is arm64's: AAPCS64 states the same rule over the same shapes. // `coerce_` is set when the lowered type cannot express the HFA and LLVM has to be handed an // `[N x base]` instead of the type itself. That happens for a `#raw_union`, which has become // an opaque integer by now and AAPCS32 DOES count a union of floats as homogeneous gb_internal bool is_hfa(LLVMContextRef c, LLVMTypeRef type, Type *source_type, ProcCallingConvention calling_convention, LLVMTypeRef *coerce_) { if (is_calling_convention_odin(calling_convention)) { // Both sides are Odin, so the existing lowering is self-consistent; leave it alone. return false; } LLVMTypeRef base_type = nullptr; unsigned member_count = 0; bool needs_coerce = false; if (!lbAbiArm64::is_homogenous_aggregate(c, type, &base_type, &member_count)) { if (source_type == nullptr || !lbAbiArm64::is_homogenous_aggregate_source(c, source_type, &base_type, &member_count)) { return false; } needs_coerce = true; } if (member_count == 0 || member_count > 4) { return false; } switch (LLVMGetTypeKind(base_type)) { case LLVMFloatTypeKind: case LLVMDoubleTypeKind: break; case LLVMVectorTypeKind: // AAPCS32's short vectors are the 64-bit and 128-bit ones. An aggregate of up to // four of them is a Homogeneous Vector Aggregate, which rides in the VFP registers // exactly as an HFA does. Any other width is not a short vector and does not qualify. { i64 vec_size = lb_sizeof(base_type); if (vec_size != 8 && vec_size != 16) { return false; } } break; default: return false; } if (coerce_) { *coerce_ = needs_coerce ? llvm_array_type(base_type, member_count) : nullptr; } return true; } gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention, Type *original_type) { auto args = array_make(lb_function_type_args_allocator(), arg_count); auto srcs = lb_abi_param_source_types(original_type, arg_count); for (unsigned i = 0; i < arg_count; i++) { LLVMTypeRef t = arg_types[i]; if (is_register(t, false)) { args[i] = non_struct(c, t, false, srcs[i]); } else { i64 sz = lb_sizeof(t); i64 a = lb_alignof(t); LLVMTypeRef hfa_coerce = nullptr; // Added to support hard floats included in the playdates cortex-m7. if (calling_convention == ProcCC_CDecl && selected_subtarget == Subtarget_Playdate) { args[i] = lb_arg_type_direct(t); } else if (is_hfa(c, t, srcs[i], calling_convention, &hfa_coerce)) { args[i] = lb_arg_type_direct(t, hfa_coerce, nullptr, nullptr); } else if (is_calling_convention_odin(calling_convention) && sz > 8) { // Minor change to improve performance using the Odin calling conventions args[i] = lb_arg_type_indirect(t, nullptr); } else if (a <= 4) { unsigned n = cast(unsigned)((sz + 3) / 4); args[i] = lb_arg_type_direct(llvm_array_type(LLVMIntTypeInContext(c, 32), n)); } else { unsigned n = cast(unsigned)((sz + 7) / 8); args[i] = lb_arg_type_direct(llvm_array_type(LLVMIntTypeInContext(c, 64), n)); } } } return args; } gb_internal lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined, ProcCallingConvention calling_convention, Type *return_source) { if (!return_is_defined) { return lb_arg_type_direct(LLVMVoidTypeInContext(c)); } else if (LLVMGetTypeKind(return_type) == LLVMVectorTypeKind && lb_sizeof(return_type) > 16) { // A bare vector wider than a short vector has no register file to come back in. It // is returned through a hidden pointer. `is_register` answers true for every vector, // without this the caller returns it directly while the C callee stores // through an `sret` pointer that was never passed (segfault) LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type); return lb_arg_type_indirect(return_type, attr); } else if (!is_register(return_type, true)) { if (calling_convention == ProcCC_CDecl && selected_subtarget == Subtarget_Playdate) { return lb_arg_type_direct(return_type); } // An HFA is returned in s0-s3 / d0-d3 too. It must not fall through to the // integer coercions or to `sret`. LLVMTypeRef hfa_coerce = nullptr; if (is_hfa(c, return_type, return_source, calling_convention, &hfa_coerce)) { return lb_arg_type_direct(return_type, hfa_coerce, nullptr, nullptr); } // `lb_arg_type_direct` takes (type, cast_type), and the cast type is what the function actually returns. switch (lb_sizeof(return_type)) { case 1: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 8), nullptr, nullptr); case 2: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 16), nullptr, nullptr); case 3: case 4: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr); } LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type); return lb_arg_type_indirect(return_type, attr); } return non_struct(c, return_type, true, nullptr); } }; namespace lbAbiRiscv64 { gb_internal bool is_register(LLVMTypeRef type) { LLVMTypeKind kind = LLVMGetTypeKind(type); switch (kind) { case LLVMIntegerTypeKind: case LLVMHalfTypeKind: case LLVMFloatTypeKind: case LLVMDoubleTypeKind: case LLVMPointerTypeKind: return true; } return false; } gb_internal bool is_float(LLVMTypeRef type) { LLVMTypeKind kind = LLVMGetTypeKind(type); switch (kind) { case LLVMHalfTypeKind: case LLVMFloatTypeKind: case LLVMDoubleTypeKind: return true; default: return false; } } gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type, Type *source_type) { LLVMAttributeRef attr = lb_integer_extension_attribute(c, type, source_type); return lb_arg_type_direct(type, nullptr, nullptr, attr); } gb_internal void flatten(lbModule *m, Array *fields, LLVMTypeRef type, bool with_padding) { LLVMTypeKind kind = LLVMGetTypeKind(type); switch (kind) { case LLVMStructTypeKind: { if (LLVMIsPackedStruct(type)) { array_add(fields, type); break; } if (!with_padding) { auto field_remapping = map_get(&m->struct_field_remapping, cast(void *)type); if (field_remapping) { auto remap = *field_remapping; for_array(i, remap) { flatten(m, fields, LLVMStructGetTypeAtIndex(type, remap[i]), with_padding); } break; } else { debugf("no field mapping for type: %s\n", LLVMPrintTypeToString(type)); } } unsigned elem_count = LLVMCountStructElementTypes(type); for (unsigned i = 0; i < elem_count; i += 1) { flatten(m, fields, LLVMStructGetTypeAtIndex(type, i), with_padding); } break; } case LLVMArrayTypeKind: { unsigned len = LLVMGetArrayLength(type); LLVMTypeRef elem = OdinLLVMGetArrayElementType(type); for (unsigned i = 0; i < len; i += 1) { flatten(m, fields, elem, with_padding); } break; } default: array_add(fields, type); } } // `flatten` records which members survive; this records where they are. The two are walked // together so the dense type it builds can still be read from the real object: an over-aligned // member leaves a gap that the flatten removes, and reinterpreting the bits from offset zero // then reads the member from where the padding used to be. gb_internal void flatten_offsets(lbModule *m, Array *offsets, LLVMTypeRef type, i64 base) { switch (LLVMGetTypeKind(type)) { case LLVMStructTypeKind: { if (LLVMIsPackedStruct(type)) { array_add(offsets, base); break; } unsigned elem_count = LLVMCountStructElementTypes(type); // element offsets the way `lb_alignof` models LLVM's own layout auto elem_offsets = array_make(temporary_allocator(), 0, elem_count); i64 off = 0; for (unsigned i = 0; i < elem_count; i += 1) { LLVMTypeRef et = LLVMStructGetTypeAtIndex(type, i); i64 a = lb_alignof(et); if (a > 0) { off = align_formula(off, a); } array_add(&elem_offsets, off); off += lb_sizeof(et); } auto field_remapping = map_get(&m->struct_field_remapping, cast(void *)type); if (field_remapping) { auto remap = *field_remapping; for_array(i, remap) { flatten_offsets(m, offsets, LLVMStructGetTypeAtIndex(type, remap[i]), base + elem_offsets[remap[i]]); } break; } for (unsigned i = 0; i < elem_count; i += 1) { flatten_offsets(m, offsets, LLVMStructGetTypeAtIndex(type, i), base + elem_offsets[i]); } break; } case LLVMArrayTypeKind: { unsigned len = LLVMGetArrayLength(type); LLVMTypeRef elem = OdinLLVMGetArrayElementType(type); i64 stride = lb_sizeof(elem); for (unsigned i = 0; i < len; i += 1) { flatten_offsets(m, offsets, elem, base + cast(i64)i*stride); } break; } default: array_add(offsets, base); } } // The offsets the dense `cast_type` implies, so the two can be compared. When they agree the // ordinary bit-reinterpreting coercion is right and nothing needs to change. gb_internal bool flatten_moved_a_member(Array const &fields, Array const &offsets) { if (fields.count != offsets.count) { return false; } i64 off = 0; for_array(i, fields) { i64 a = lb_alignof(fields[i]); if (a > 0) { off = align_formula(off, a); } if (off != offsets[i]) { return true; } off += lb_sizeof(fields[i]); } return false; } // The psABI's rule is "one floating-point real and one integer (or bitfield)", and a pointer // is not an integer. `is_register` admits pointers and keeps that meaning for its other // callers, so the floating-point arms need their own predicate. gb_internal bool is_int_member(LLVMTypeRef type) { return LLVMGetTypeKind(type) == LLVMIntegerTypeKind && lb_sizeof(type) > 0; } // The psABI applies the hardware floating-point convention to a struct's MEMBERS. A union is // never flattened, so an aggregate holding one ANYWHERE, at any depth, and through an array, // takes the integer convention instead, whatever the union itself contains. // // The lowered type cannot answer this. A `#raw_union{f32}` comes out as a bare `float`, and a // two-member one comes out as the integer its padding filler is, which is indistinguishable // from a real integer member. Both have to be read off the source type. gb_internal bool contains_union(Type *t) { if (t == nullptr) { return false; } Type *bt = base_type(t); if (bt == nullptr) { return false; } switch (bt->kind) { case Type_Union: return true; case Type_Struct: if (bt->Struct.is_raw_union) { return true; } for (Entity *f : bt->Struct.fields) { if (contains_union(f->type)) { return true; } } return false; case Type_Array: return contains_union(bt->Array.elem); case Type_EnumeratedArray: return contains_union(bt->EnumeratedArray.elem); case Type_Matrix: return contains_union(bt->Matrix.elem); } return false; } gb_internal lbArgType compute_arg_type(lbModule *m, LLVMTypeRef type, int *gprs_left, int *fprs_left, Type *source_type) { LLVMContextRef c = m->ctx; int xlen = 8; // 8 byte int register size for riscv64. // NOTE: we are requiring both of these to be enabled so we can just hard-code 8. // int flen = 0; // if (check_target_feature_is_enabled(str_lit("d"), nullptr)) { // flen = 8; // Double precision floats are enabled. // } else if (check_target_feature_is_enabled(str_lit("f"), nullptr)) { // flen = 4; // Single precision floats are enabled. // } int flen = 8; LLVMTypeKind kind = LLVMGetTypeKind(type); i64 size = lb_sizeof(type); if (size == 0) { return lb_arg_type_direct(type, LLVMStructTypeInContext(c, nullptr, 0, false), nullptr, nullptr); } LLVMTypeRef orig_type = type; // Flatten down the type so it is easier to check all the ABI conditions. // Note that we also need to remove all implicit padding fields Odin adds so we keep ABI // compatibility for struct declarations. // The flattened form is for the floating-point rules, which are about the MEMBERS; the // integer fallback below is about the OBJECT, so `size` stays the size of the original. // The rules are stated over the members alone, so the aggregate's size does not gate // them: over-alignment grows a struct without changing any member type. LLVMTypeRef fp_type = type; LLVMTypeKind fp_kind = kind; i64 fp_size = size; Slice fp_offsets = {}; if (kind == LLVMStructTypeKind) { Array fields = array_make(temporary_allocator(), 0, LLVMCountStructElementTypes(type)); flatten(m, &fields, type, false); auto offsets = array_make(temporary_allocator(), 0, fields.count); flatten_offsets(m, &offsets, type, 0); if (flatten_moved_a_member(fields, offsets)) { fp_offsets = slice_clone_from_array(permanent_allocator(), offsets); } if (fields.count == 1) { fp_type = fields[0]; } else { fp_type = LLVMStructTypeInContext(c, fields.data, cast(unsigned)fields.count, false); } fp_kind = LLVMGetTypeKind(fp_type); fp_size = lb_sizeof(fp_type); } bool integer_only = contains_union(source_type); if (!integer_only && is_float(fp_type) && fp_size <= flen && *fprs_left >= 1) { *fprs_left -= 1; if (fp_type != orig_type) { // A struct that flattened to a single float has to be coerced to that float; // handing back the original sends an over-aligned one to integer registers. if (fp_offsets.count > 0) { return lb_arg_type_direct_fields(orig_type, fp_type, fp_offsets); } return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); } return non_struct(c, orig_type, source_type); } if (!integer_only && fp_kind == LLVMStructTypeKind && fp_size <= 2*flen) { unsigned elem_count = LLVMCountStructElementTypes(fp_type); if (elem_count == 2) { LLVMTypeRef ty1 = LLVMStructGetTypeAtIndex(fp_type, 0); i64 ty1s = lb_sizeof(ty1); LLVMTypeRef ty2 = LLVMStructGetTypeAtIndex(fp_type, 1); i64 ty2s = lb_sizeof(ty2); if (is_float(ty1) && is_float(ty2) && ty1s <= flen && ty2s <= flen && *fprs_left >= 2) { *fprs_left -= 2; if (fp_offsets.count > 0) { return lb_arg_type_direct_fields(orig_type, fp_type, fp_offsets); } return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); } if (is_float(ty1) && is_int_member(ty2) && ty1s <= flen && ty2s <= xlen && *fprs_left >= 1 && *gprs_left >= 1) { *fprs_left -= 1; *gprs_left -= 1; if (fp_offsets.count > 0) { return lb_arg_type_direct_fields(orig_type, fp_type, fp_offsets); } return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); } if (is_int_member(ty1) && is_float(ty2) && ty1s <= xlen && ty2s <= flen && *gprs_left >= 1 && *fprs_left >= 1) { *fprs_left -= 1; *gprs_left -= 1; if (fp_offsets.count > 0) { return lb_arg_type_direct_fields(orig_type, fp_type, fp_offsets); } return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); } } } // At this point all the cases for floating point registers are exhausted, fit it into // integer registers or the stack. // LLVM automatically handles putting args on the stack so we don't check the amount of registers that are left here. if (size <= xlen) { *gprs_left -= 1; if (is_register(type)) { return non_struct(c, orig_type, source_type); } else { return lb_arg_type_direct(orig_type, LLVMIntTypeInContext(c, cast(unsigned)(size*8)), nullptr, nullptr); } } else if (size <= 2*xlen) { LLVMTypeRef *fields = gb_alloc_array(temporary_allocator(), LLVMTypeRef, 2); fields[0] = LLVMIntTypeInContext(c, cast(unsigned)(xlen*8)); fields[1] = LLVMIntTypeInContext(c, cast(unsigned)((size-xlen)*8)); *gprs_left -= 2; return lb_arg_type_direct(orig_type, LLVMStructTypeInContext(c, fields, 2, false), nullptr, nullptr); } else { return lb_arg_type_indirect(orig_type, nullptr); } } gb_internal Array compute_arg_types(lbModule *m, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention, Type *odin_type, int *gprs, int *fprs) { auto args = array_make(lb_function_type_args_allocator(), arg_count); // The source type of each parameter, where one exists. `arg_types` can carry entries with // no counterpart, so this walks the tuple the way lbAbiAmd64SysV does and hands back // nullptr once it runs out. Entity **params = nullptr; isize param_count = 0; if (odin_type != nullptr && odin_type->kind == Type_Proc && odin_type->Proc.params != nullptr) { params = odin_type->Proc.params->Tuple.variables.data; param_count = odin_type->Proc.params->Tuple.variables.count; } for (unsigned i = 0, j = 0; i < arg_count; i++, j++) { while (cast(isize)j < param_count && params[j]->kind != Entity_Variable) { j++; } Type *source_type = cast(isize)j < param_count ? params[j]->type : nullptr; LLVMTypeRef type = arg_types[i]; args[i] = compute_arg_type(m, type, gprs, fprs, source_type); } return args; } gb_internal lbArgType compute_return_type(lbFunctionType *ft, lbModule *m, LLVMTypeRef return_type, bool return_is_defined, bool return_is_tuple, Type *odin_type, int *agprs) { LLVMContextRef c = m->ctx; if (!return_is_defined) { return lb_arg_type_direct(LLVMVoidTypeInContext(c)); } // A single result is classified from its source type. The union rule reaches the return // as well. A tuple keeps nullptr: it is split into out-pointers below. The recursive call // for the last tuple field lands here with a result count above one, so it takes the same path. Type *return_source = nullptr; if (!return_is_tuple && odin_type != nullptr && odin_type->kind == Type_Proc && odin_type->Proc.results != nullptr && odin_type->Proc.results->Tuple.variables.count == 1) { return_source = odin_type->Proc.results->Tuple.variables[0]->type; } // There are two registers for return types. int gprs = 2; int fprs = 2; lbArgType ret = compute_arg_type(m, return_type, &gprs, &fprs, return_source); // Return didn't fit into the return registers, so caller allocates and it is returned via // an out-pointer. if (ret.kind == lbArg_Indirect) { // Transform multiple return into out pointers if possible. if (return_is_tuple) { if (lb_is_type_kind(return_type, LLVMStructTypeKind)) { int field_count = cast(int)LLVMCountStructElementTypes(return_type); if (field_count > 1 && field_count <= *agprs) { ft->original_arg_count = ft->args.count; ft->multiple_return_original_type = return_type; for (int i = 0; i < field_count-1; i++) { LLVMTypeRef field_type = LLVMStructGetTypeAtIndex(return_type, i); LLVMTypeRef field_pointer_type = LLVMPointerType(field_type, 0); lbArgType ret_partial = lb_arg_type_direct(field_pointer_type); array_add(&ft->args, ret_partial); *agprs -= 1; } GB_ASSERT(*agprs >= 0); // override the return type for the last field LLVMTypeRef new_return_type = LLVMStructGetTypeAtIndex(return_type, field_count-1); return compute_return_type(ft, m, new_return_type, true, false, odin_type, agprs); } } } LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", ret.type); return lb_arg_type_indirect(ret.type, attr); } return ret; } gb_internal LB_ABI_INFO(abi_info) { lbFunctionType *ft = permanent_alloc_item(); ft->ctx = m->ctx; ft->calling_convention = calling_convention; int gprs = 8; int fprs = 8; ft->args = compute_arg_types(m, arg_types, arg_count, calling_convention, original_type, &gprs, &fprs); ft->ret = compute_return_type(ft, m, return_type, return_is_defined, return_is_tuple, original_type, &gprs); return ft; } } gb_internal LB_ABI_INFO(lb_get_abi_info_internal) { LLVMContextRef c = m->ctx; switch (calling_convention) { case ProcCC_None: case ProcCC_InlineAsm: { lbFunctionType *ft = permanent_alloc_item(); ft->ctx = c; ft->args = array_make(lb_function_type_args_allocator(), arg_count); for (unsigned i = 0; i < arg_count; i++) { ft->args[i] = lb_arg_type_direct(arg_types[i]); } if (return_is_defined) { ft->ret = lb_arg_type_direct(return_type); } else { ft->ret = lb_arg_type_direct(LLVMVoidTypeInContext(c)); } ft->calling_convention = calling_convention; return ft; } case ProcCC_Win64: GB_ASSERT(build_context.metrics.arch == TargetArch_amd64); return lbAbiAmd64Win64::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); case ProcCC_SysV: GB_ASSERT(build_context.metrics.arch == TargetArch_amd64); return lbAbiAmd64SysV::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); } switch (build_context.metrics.arch) { case TargetArch_amd64: if (build_context.metrics.os == TargetOs_windows) { return lbAbiAmd64Win64::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); } else if (build_context.metrics.abi == TargetABI_Win64) { return lbAbiAmd64Win64::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); } else if (build_context.metrics.abi == TargetABI_SysV) { return lbAbiAmd64SysV::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); } else { return lbAbiAmd64SysV::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); } case TargetArch_i386: return lbAbi386::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); case TargetArch_arm32: return lbAbiArm32::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); case TargetArch_arm64: return lbAbiArm64::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); case TargetArch_wasm32: return lbAbiWasm::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); case TargetArch_wasm64p32: return lbAbiWasm::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); case TargetArch_riscv64: return lbAbiRiscv64::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); } GB_PANIC("Unsupported ABI"); return {}; } gb_internal LB_ABI_INFO(lb_get_abi_info) { lbFunctionType *ft = lb_get_abi_info_internal( m, arg_types, arg_count, return_type, return_is_defined, ALLOW_SPLIT_MULTI_RETURNS && return_is_tuple && is_calling_convention_odin(calling_convention), calling_convention, base_type(original_type) ); // NOTE(bill): this is handled here rather than when developing the type in `lb_type_internal_for_procedures_raw` // This is to make it consistent when and how it is handled if (calling_convention == ProcCC_Odin) { // append the `context` pointer lbArgType context_param = lb_arg_type_direct(LLVMPointerType(LLVMInt8TypeInContext(m->ctx), 0)); array_add(&ft->args, context_param); } return ft; }