#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; }; 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, nullptr, 0, false}; } 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, nullptr, 0, false}; } gb_internal lbArgType lb_arg_type_indirect_byval(LLVMContextRef c, LLVMTypeRef type) { i64 alignment = lb_alignof(type); alignment = gb_max(alignment, 8); 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, nullptr, 0, 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; } #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 namespace lbAbi386 { gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count); 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); ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple); ft->calling_convention = calling_convention; return ft; } gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type, bool is_return) { 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 = 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 Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count) { 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); i64 sz = lb_sizeof(t); if (kind == LLVMStructTypeKind || kind == LLVMArrayTypeKind) { 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); } } 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); } }; namespace lbAbiAmd64Win64 { gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count); 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); 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) { 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); } } 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); } }; 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) { LLVMTypeKind kind = LLVMGetTypeKind(type); switch (kind) { case LLVMStructTypeKind: if (LLVMCountStructElementTypes(type) == 1) { return is_aggregate(LLVMStructGetTypeAtIndex(type, 0)); } return true; case LLVMArrayTypeKind: if (LLVMGetArrayLength(type) == 1) { return is_aggregate(LLVMGetElementType(type)); } 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 (is_register(type)) { LLVMAttributeRef attribute = nullptr; if (type == LLVMInt1TypeInContext(c)) { attribute = lb_create_enum_attribute(c, "zeroext"); } 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); } 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); } 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); } return lb_arg_type_direct(type, reg_type, nullptr, nullptr); } } 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) { 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; } 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; } } (*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++) { if (oldv != 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 (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); LLVMTypeRef vec_type = LLVMVectorType(elem_type, vec_len * elems_per_word); 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); 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 (!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 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_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: { 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) { 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)) { if (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 { //TODO(Platin): do i need to create stuff that can handle the diffrent return type? // else this needs a fix in llvm_backend_proc as we would need to cast it to the correct array type LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO(); //LLVMTypeRef array_type = llvm_array_type(homo_base_type, homo_member_count); LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type); return lb_arg_type_indirect(return_type, attr); } } else { 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); 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)) { if (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 { args[i] = lb_arg_type_indirect(type, 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); gb_internal lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined, ProcCallingConvention calling_convention); 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); ft->ret = compute_return_type(c, return_type, return_is_defined, calling_convention); 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) { 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 Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention) { auto args = array_make(lb_function_type_args_allocator(), 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); } else { i64 sz = lb_sizeof(t); i64 a = lb_alignof(t); // 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_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) { if (!return_is_defined) { return lb_arg_type_direct(LLVMVoidTypeInContext(c)); } else if (!is_register(return_type, true)) { if (calling_convention == ProcCC_CDecl && selected_subtarget == Subtarget_Playdate) { return lb_arg_type_direct(return_type); } switch (lb_sizeof(return_type)) { case 1: return lb_arg_type_direct(LLVMIntTypeInContext(c, 8), return_type, nullptr, nullptr); case 2: return lb_arg_type_direct(LLVMIntTypeInContext(c, 16), return_type, nullptr, nullptr); case 3: case 4: return lb_arg_type_direct(LLVMIntTypeInContext(c, 32), return_type, 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); } }; 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) { 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 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); } } // 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; } gb_internal lbArgType compute_arg_type(lbModule *m, LLVMTypeRef type, int *gprs_left, int *fprs_left, Type *odin_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; if (kind == LLVMStructTypeKind) { Array fields = array_make(temporary_allocator(), 0, LLVMCountStructElementTypes(type)); flatten(m, &fields, type, false); 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); } if (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. return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); } return non_struct(c, orig_type); } if (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; 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; 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; 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); } 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); for (unsigned i = 0; i < arg_count; i++) { LLVMTypeRef type = arg_types[i]; args[i] = compute_arg_type(m, type, gprs, fprs, odin_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)); } // There are two registers for return types. int gprs = 2; int fprs = 2; lbArgType ret = compute_arg_type(m, return_type, &gprs, &fprs, odin_type); // 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; }