aboutsummaryrefslogtreecommitdiff
path: root/src/llvm_abi.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/llvm_abi.cpp')
-rw-r--r--src/llvm_abi.cpp269
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 &reg_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);
}