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; }; i64 lb_sizeof(LLVMTypeRef type); i64 lb_alignof(LLVMTypeRef type); 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}; } lbArgType lb_arg_type_direct(LLVMTypeRef type) { return lb_arg_type_direct(type, nullptr, nullptr, nullptr); } lbArgType lb_arg_type_indirect(LLVMTypeRef type, LLVMAttributeRef attr) { return lbArgType{lbArg_Indirect, type, nullptr, nullptr, attr, nullptr, 0, false}; } 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}; } 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; }; i64 llvm_align_formula(i64 off, i64 a) { return (off + a - 1) / a * a; } bool lb_is_type_kind(LLVMTypeRef type, LLVMTypeKind kind) { if (type == nullptr) { return false; } return LLVMGetTypeKind(type) == kind; } LLVMTypeRef lb_function_type_to_llvm_ptr(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) { 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 LLVMPointerType(func_type, 0); } 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"); LLVMAttributeRef nocapture_attr = lb_create_enum_attribute(c, "nocapture"); 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); } 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 (!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 = offset+arg_count; LLVMAddAttributeAtIndex(fn, context_index, noalias_attr); LLVMAddAttributeAtIndex(fn, context_index, nonnull_attr); LLVMAddAttributeAtIndex(fn, context_index, nocapture_attr); } } 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.word_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 = LLVMGetElementType(type); i64 elem_size = lb_sizeof(elem); i64 count = LLVMGetArrayLength(type); i64 size = count * elem_size; return size; } break; case LLVMX86_MMXTypeKind: return 8; case LLVMVectorTypeKind: { LLVMTypeRef elem = LLVMGetElementType(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_align); } } GB_PANIC("Unhandled type for lb_sizeof -> %s", LLVMPrintTypeToString(type)); return 0; } 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.word_size); } case LLVMHalfTypeKind: return 2; case LLVMFloatTypeKind: return 4; case LLVMDoubleTypeKind: return 8; case LLVMPointerTypeKind: return build_context.word_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(LLVMGetElementType(type)); case LLVMX86_MMXTypeKind: return 8; case LLVMVectorTypeKind: { // TODO(bill): This appears to be correct but LLVM isn't necessarily "great" with regards to documentation LLVMTypeRef elem = LLVMGetElementType(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_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(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, LLVMTypeRef return_type, bool return_is_defined, ProcCallingConvention calling_convention) typedef LB_ABI_INFO(lbAbiInfoType); // NOTE(bill): I hate `namespace` in C++ but this is just because I don't want to prefix everything namespace lbAbi386 { Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count); lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined); LB_ABI_INFO(abi_info) { lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count); ft->ret = compute_return_type(c, return_type, return_is_defined); ft->calling_convention = calling_convention; return ft; } 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.word_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); } Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count) { auto args = array_make(heap_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 { args[i] = lb_arg_type_indirect(t, nullptr); } } else { args[i] = non_struct(c, t, false); } } return args; } lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined) { 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); } 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 { Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count); LB_ABI_INFO(abi_info) { lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count); ft->ret = lbAbi386::compute_return_type(c, return_type, return_is_defined); ft->calling_convention = calling_convention; return ft; } Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count) { auto args = array_make(heap_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; } }; // 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_SSEFs, RegClass_SSEFv, RegClass_SSEDs, RegClass_SSEDv, RegClass_SSEInt8, RegClass_SSEInt16, RegClass_SSEInt32, RegClass_SSEInt64, RegClass_SSEUp, RegClass_X87, RegClass_X87Up, RegClass_ComplexX87, RegClass_Memory, }; bool is_sse(RegClass reg_class) { switch (reg_class) { 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; } void all_mem(Array *cs) { for_array(i, *cs) { (*cs)[i] = RegClass_Memory; } } enum Amd64TypeAttributeKind { Amd64TypeAttribute_None, Amd64TypeAttribute_ByVal, Amd64TypeAttribute_StructRect, }; lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined); void classify_with(LLVMTypeRef t, Array *cls, i64 ix, i64 off); void fixup(LLVMTypeRef t, Array *cls); lbArgType amd64_type(LLVMContextRef c, LLVMTypeRef type, Amd64TypeAttributeKind attribute_kind, ProcCallingConvention calling_convention); Array classify(LLVMTypeRef t); LLVMTypeRef llreg(LLVMContextRef c, Array const ®_classes); LB_ABI_INFO(abi_info) { lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->calling_convention = calling_convention; ft->args = array_make(heap_allocator(), arg_count); for (unsigned i = 0; i < arg_count; i++) { ft->args[i] = amd64_type(c, arg_types[i], Amd64TypeAttribute_ByVal, calling_convention); } if (return_is_defined) { ft->ret = amd64_type(c, return_type, Amd64TypeAttribute_StructRect, calling_convention); } else { ft->ret = lb_arg_type_direct(LLVMVoidTypeInContext(c)); } return ft; } 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; } bool is_register(LLVMTypeRef type) { LLVMTypeKind kind = LLVMGetTypeKind(type); i64 sz = lb_sizeof(type); if (sz == 0) { return false; } switch (kind) { case LLVMIntegerTypeKind: case LLVMHalfTypeKind: case LLVMFloatTypeKind: case LLVMDoubleTypeKind: case LLVMPointerTypeKind: return true; } return false; } lbArgType amd64_type(LLVMContextRef c, LLVMTypeRef type, Amd64TypeAttributeKind attribute_kind, ProcCallingConvention calling_convention) { 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); } auto cls = classify(type); 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_byval(c, type); } attribute = nullptr; } else if (attribute_kind == Amd64TypeAttribute_StructRect) { attribute = lb_create_enum_attribute_with_type(c, "sret", type); } return lb_arg_type_indirect(type, attribute); } else { return lb_arg_type_direct(type, llreg(c, cls), nullptr, nullptr); } } 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); } Array classify(LLVMTypeRef t) { 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 { classify_with(t, ®_classes, 0, 0); fixup(t, ®_classes); } return reg_classes; } 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_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; } 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)) { i++; while (i != e && oldv == RegClass_SSEUp) { i++; } } else if (oldv == RegClass_X87) { i++; while (i != e && oldv == RegClass_X87Up) { i++; } } else { i++; } } } } 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; } LLVMTypeRef llreg(LLVMContextRef c, Array const ®_classes) { auto types = array_make(heap_allocator(), 0, reg_classes.count); for (isize i = 0; i < reg_classes.count; /**/) { RegClass reg_class = reg_classes[i]; switch (reg_class) { case RegClass_Int: array_add(&types, LLVMIntTypeInContext(c, 64)); break; 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_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); i += vec_len; continue; } break; case RegClass_SSEFs: array_add(&types, LLVMFloatTypeInContext(c)); break; case RegClass_SSEDs: array_add(&types, LLVMDoubleTypeInContext(c)); 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); } 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: case LLVMPointerTypeKind: case LLVMHalfTypeKind: unify(cls, ix + off/8, RegClass_Int); 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 = LLVMGetElementType(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 = LLVMGetElementType(t); i64 elem_sz = lb_sizeof(elem); LLVMTypeKind elem_kind = LLVMGetTypeKind(elem); RegClass reg = RegClass_NoClass; switch (elem_kind) { case LLVMIntegerTypeKind: case LLVMHalfTypeKind: switch (LLVMGetIntTypeWidth(elem)) { case 8: reg = RegClass_SSEInt8; case 16: reg = RegClass_SSEInt16; case 32: reg = RegClass_SSEInt32; case 64: reg = RegClass_SSEInt64; default: GB_PANIC("Unhandled integer width for vector type"); } 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; } } lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined) { if (!return_is_defined) { return lb_arg_type_direct(LLVMVoidTypeInContext(c)); } else if (lb_is_type_kind(return_type, LLVMStructTypeKind)) { 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); } LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type); return lb_arg_type_indirect(return_type, attr); } else if (build_context.metrics.os == TargetOs_windows && lb_is_type_kind(return_type, LLVMIntegerTypeKind) && lb_sizeof(return_type) == 16) { return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 128), nullptr, nullptr); } return non_struct(c, return_type); } }; namespace lbAbiArm64 { Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count); lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined); bool is_homogenous_aggregate(LLVMContextRef c, LLVMTypeRef type, LLVMTypeRef *base_type_, unsigned *member_count_); LB_ABI_INFO(abi_info) { lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->ret = compute_return_type(c, return_type, return_is_defined); ft -> args = compute_arg_types(c, arg_types, arg_count); ft->calling_convention = calling_convention; return ft; } 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; } 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); } 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 = LLVMGetElementType(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; } 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; } 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 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; } unsigned is_homogenous_aggregate_small_enough(LLVMTypeRef *base_type_, unsigned member_count_) { return (member_count_ <= 4); } lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef type, bool return_is_defined) { LLVMTypeRef homo_base_type = {}; unsigned homo_member_count = 0; if (!return_is_defined) { return lb_arg_type_direct(LLVMVoidTypeInContext(c)); } else if (is_register(type)) { return non_struct(c, type); } 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)) { return lb_arg_type_direct(type, LLVMArrayType(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 //LLVMTypeRef array_type = LLVMArrayType(homo_base_type, homo_member_count); LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", type); return lb_arg_type_indirect(type, attr); } } else { i64 size = lb_sizeof(type); if (size <= 16) { LLVMTypeRef cast_type = nullptr; if (size <= 1) { cast_type = LLVMInt8TypeInContext(c); } else if (size <= 2) { cast_type = LLVMInt16TypeInContext(c); } else if (size <= 4) { cast_type = LLVMInt32TypeInContext(c); } else if (size <= 8) { cast_type = LLVMInt64TypeInContext(c); } else { unsigned count = cast(unsigned)((size+7)/8); cast_type = LLVMArrayType(LLVMInt64TypeInContext(c), count); } return lb_arg_type_direct(type, cast_type, nullptr, nullptr); } else { LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", type); return lb_arg_type_indirect(type, attr); } } } Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count) { auto args = array_make(heap_allocator(), arg_count); for (unsigned i = 0; i < arg_count; i++) { LLVMTypeRef type = arg_types[i]; LLVMTypeRef homo_base_type = {}; unsigned homo_member_count = 0; if (is_register(type)) { args[i] = non_struct(c, type); } else if (is_homogenous_aggregate(c, type, &homo_base_type, &homo_member_count)) { args[i] = lb_arg_type_direct(type, LLVMArrayType(homo_base_type, homo_member_count), nullptr, nullptr); } else { i64 size = lb_sizeof(type); if (size <= 16) { LLVMTypeRef cast_type = nullptr; if (size <= 1) { cast_type = LLVMIntTypeInContext(c, 8); } else if (size <= 2) { cast_type = LLVMIntTypeInContext(c, 16); } else if (size <= 4) { cast_type = LLVMIntTypeInContext(c, 32); } else if (size <= 8) { cast_type = LLVMIntTypeInContext(c, 64); } else { unsigned count = cast(unsigned)((size+7)/8); cast_type = LLVMArrayType(LLVMIntTypeInContext(c, 64), count); } 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. */ Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count); lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined); enum {MAX_DIRECT_STRUCT_SIZE = 32}; LB_ABI_INFO(abi_info) { lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count); ft->ret = compute_return_type(c, return_type, return_is_defined); ft->calling_convention = calling_convention; return ft; } lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type, bool is_return) { if (!is_return && type == LLVMIntTypeInContext(c, 128)) { LLVMTypeRef cast_type = LLVMVectorType(LLVMInt64TypeInContext(c), 2); 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); } 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) <= 8; } return false; } bool type_can_be_direct(LLVMTypeRef type) { LLVMTypeKind kind = LLVMGetTypeKind(type); i64 sz = lb_sizeof(type); if (sz == 0) { return false; } if (sz <= MAX_DIRECT_STRUCT_SIZE) { if (kind == LLVMArrayTypeKind) { if (is_basic_register_type(LLVMGetElementType(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; } lbArgType is_struct(LLVMContextRef c, LLVMTypeRef type) { 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)) { return lb_arg_type_direct(type); } return lb_arg_type_indirect(type, nullptr); } Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count) { auto args = array_make(heap_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) { args[i] = is_struct(c, t); } else { args[i] = non_struct(c, t, false); } } return args; } lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined) { 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)) { return lb_arg_type_direct(return_type); } 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); } 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 { Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention); lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined); LB_ABI_INFO(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); ft->ret = compute_return_type(c, return_type, return_is_defined); ft->calling_convention = calling_convention; return ft; } 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; } 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); } Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention) { auto args = array_make(heap_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); 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(LLVMArrayType(LLVMIntTypeInContext(c, 32), n)); } else { unsigned n = cast(unsigned)((sz + 7) / 8); args[i] = lb_arg_type_direct(LLVMArrayType(LLVMIntTypeInContext(c, 64), n)); } } } return args; } lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined) { if (!return_is_defined) { return lb_arg_type_direct(LLVMVoidTypeInContext(c)); } else if (!is_register(return_type, true)) { 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); } }; LB_ABI_INFO(lb_get_abi_info) { switch (calling_convention) { case ProcCC_None: case ProcCC_InlineAsm: { lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->args = array_make(heap_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(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); case ProcCC_SysV: GB_ASSERT(build_context.metrics.arch == TargetArch_amd64); return lbAbiAmd64SysV::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); } switch (build_context.metrics.arch) { case TargetArch_amd64: if (build_context.metrics.os == TargetOs_windows || build_context.metrics.abi == TargetABI_Win64) { return lbAbiAmd64Win64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); } else if (build_context.metrics.abi == TargetABI_SysV) { return lbAbiAmd64SysV::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); } else { return lbAbiAmd64SysV::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); } case TargetArch_i386: return lbAbi386::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); case TargetArch_arm32: return lbAbiArm32::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); case TargetArch_arm64: return lbAbiArm64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); case TargetArch_wasm32: case TargetArch_wasm64: return lbAbiWasm::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); } GB_PANIC("Unsupported ABI"); return {}; }