diff options
| author | Laytan <laytanlaats@hotmail.com> | 2024-08-15 20:39:35 +0200 |
|---|---|---|
| committer | Laytan Laats <laytanlaats@hotmail.com> | 2024-08-20 14:06:40 +0200 |
| commit | ca6ef95b038f3eb443971240de73924a721485cc (patch) | |
| tree | fb72aa52d41f114540dfcc3c5832dc88b2b5cbd5 /src/llvm_abi.cpp | |
| parent | 29838da782ad4ce77507665f6f6aa36142ceeac1 (diff) | |
add support for linux_riscv64 and freestanding_riscv64
Diffstat (limited to 'src/llvm_abi.cpp')
| -rw-r--r-- | src/llvm_abi.cpp | 269 |
1 files changed, 260 insertions, 9 deletions
diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index c21cd0a46..0837e40bf 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -332,7 +332,8 @@ gb_internal i64 lb_alignof(LLVMTypeRef type) { } -#define LB_ABI_INFO(name) lbFunctionType *name(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, LLVMTypeRef return_type, bool return_is_defined, bool return_is_tuple, ProcCallingConvention calling_convention, Type *original_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) +#define LB_ABI_INFO_CTX(name) lbFunctionType *name(LLVMContextRef c, 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) @@ -379,7 +380,7 @@ namespace lbAbi386 { gb_internal Array<lbArgType> 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) { + gb_internal LB_ABI_INFO_CTX(abi_info) { lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count); @@ -460,7 +461,7 @@ namespace lbAbiAmd64Win64 { gb_internal Array<lbArgType> 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) { + gb_internal LB_ABI_INFO_CTX(abi_info) { lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count); @@ -570,7 +571,7 @@ namespace lbAbiAmd64SysV { gb_internal Array<RegClass> classify(LLVMTypeRef t); gb_internal LLVMTypeRef llreg(LLVMContextRef c, Array<RegClass> const ®_classes, LLVMTypeRef type); - gb_internal LB_ABI_INFO(abi_info) { + gb_internal LB_ABI_INFO_CTX(abi_info) { lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->calling_convention = calling_convention; @@ -1008,7 +1009,7 @@ namespace lbAbiArm64 { 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) { + gb_internal LB_ABI_INFO_CTX(abi_info) { lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count); @@ -1242,7 +1243,7 @@ namespace lbAbiWasm { enum {MAX_DIRECT_STRUCT_SIZE = 32}; - gb_internal LB_ABI_INFO(abi_info) { + gb_internal LB_ABI_INFO_CTX(abi_info) { lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->calling_convention = calling_convention; @@ -1407,7 +1408,7 @@ namespace lbAbiArm32 { gb_internal Array<lbArgType> 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); - gb_internal LB_ABI_INFO(abi_info) { + gb_internal LB_ABI_INFO_CTX(abi_info) { lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count, calling_convention); @@ -1485,8 +1486,256 @@ namespace lbAbiArm32 { } }; +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<LLVMTypeRef> *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); + } + } + + 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. + if (kind == LLVMStructTypeKind && size <= gb_max(2*xlen, 2*flen)) { + Array<LLVMTypeRef> fields = array_make<LLVMTypeRef>(temporary_allocator(), 0, LLVMCountStructElementTypes(type)); + flatten(m, &fields, type, false); + + if (fields.count == 1) { + type = fields[0]; + } else { + type = LLVMStructTypeInContext(c, fields.data, cast(unsigned)fields.count, false); + } + + kind = LLVMGetTypeKind(type); + size = lb_sizeof(type); + GB_ASSERT_MSG(size == lb_sizeof(orig_type), "flattened: %s of size %d, original: %s of size %d", LLVMPrintTypeToString(type), size, LLVMPrintTypeToString(orig_type), lb_sizeof(orig_type)); + } + + if (is_float(type) && size <= flen && *fprs_left >= 1) { + *fprs_left -= 1; + return non_struct(c, orig_type); + } + + if (kind == LLVMStructTypeKind && size <= 2*flen) { + unsigned elem_count = LLVMCountStructElementTypes(type); + if (elem_count == 2) { + LLVMTypeRef ty1 = LLVMStructGetTypeAtIndex(type, 0); + i64 ty1s = lb_sizeof(ty1); + LLVMTypeRef ty2 = LLVMStructGetTypeAtIndex(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, type, nullptr, nullptr); + } + + if (is_float(ty1) && is_register(ty2) && ty1s <= flen && ty2s <= xlen && *fprs_left >= 1 && *gprs_left >= 1) { + *fprs_left -= 1; + *gprs_left -= 1; + return lb_arg_type_direct(orig_type, type, nullptr, nullptr); + } + + if (is_register(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, 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<lbArgType> 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<lbArgType>(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 = gb_alloc_item(permanent_allocator(), lbFunctionType); + 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: @@ -1534,6 +1783,8 @@ gb_internal LB_ABI_INFO(lb_get_abi_info_internal) { return lbAbiWasm::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); case TargetArch_wasm64p32: return lbAbiWasm::abi_info(c, 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"); @@ -1543,7 +1794,7 @@ gb_internal LB_ABI_INFO(lb_get_abi_info_internal) { gb_internal LB_ABI_INFO(lb_get_abi_info) { lbFunctionType *ft = lb_get_abi_info_internal( - c, + m, arg_types, arg_count, return_type, return_is_defined, ALLOW_SPLIT_MULTI_RETURNS && return_is_tuple && is_calling_convention_odin(calling_convention), @@ -1555,7 +1806,7 @@ gb_internal LB_ABI_INFO(lb_get_abi_info) { // 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(c), 0)); + lbArgType context_param = lb_arg_type_direct(LLVMPointerType(LLVMInt8TypeInContext(m->ctx), 0)); array_add(&ft->args, context_param); } |