diff --git a/spec/std/llvm/aarch64_spec.cr b/spec/compiler/codegen/abi/aarch64_spec.cr similarity index 95% rename from spec/std/llvm/aarch64_spec.cr rename to spec/compiler/codegen/abi/aarch64_spec.cr index 41a308b480ec..e02e26267028 100644 --- a/spec/std/llvm/aarch64_spec.cr +++ b/spec/compiler/codegen/abi/aarch64_spec.cr @@ -1,5 +1,6 @@ require "spec" require "llvm" +require "compiler/crystal/codegen/abi/aarch64" {% if LibLLVM::BUILT_TARGETS.includes?(:aarch64) %} LLVM.init_aarch64 @@ -10,10 +11,10 @@ private def abi target = LLVM::Target.from_triple(triple) machine = target.create_target_machine(triple) machine.enable_global_isel = false - LLVM::ABI::AArch64.new(machine) + Crystal::ABI::AArch64.new(machine) end -private def test(msg, &block : LLVM::ABI, LLVM::Context ->) +private def test(msg, &block : Crystal::ABI, LLVM::Context ->) it msg do abi = abi() ctx = LLVM::Context.new @@ -21,7 +22,7 @@ private def test(msg, &block : LLVM::ABI, LLVM::Context ->) end end -class LLVM::ABI +class Crystal::ABI describe AArch64 do {% if LibLLVM::BUILT_TARGETS.includes?(:aarch64) %} describe "align" do @@ -142,7 +143,7 @@ class LLVM::ABI info.arg_types.size.should eq(1) info.arg_types[0].should eq(ArgType.indirect(str, nil)) - info.return_type.should eq(ArgType.indirect(str, Attribute::StructRet)) + info.return_type.should eq(ArgType.indirect(str, LLVM::Attribute::StructRet)) end test "does with homogeneous structs" do |abi, ctx| diff --git a/spec/std/llvm/arm_abi_spec.cr b/spec/compiler/codegen/abi/arm_spec.cr similarity index 93% rename from spec/std/llvm/arm_abi_spec.cr rename to spec/compiler/codegen/abi/arm_spec.cr index 98ae9b588a41..75f0e0c47222 100644 --- a/spec/std/llvm/arm_abi_spec.cr +++ b/spec/compiler/codegen/abi/arm_spec.cr @@ -1,5 +1,6 @@ require "spec" require "llvm" +require "compiler/crystal/codegen/abi/arm" {% if LibLLVM::BUILT_TARGETS.includes?(:arm) %} LLVM.init_arm @@ -10,10 +11,10 @@ private def abi target = LLVM::Target.from_triple(triple) machine = target.create_target_machine(triple) machine.enable_global_isel = false - LLVM::ABI::ARM.new(machine) + Crystal::ABI::ARM.new(machine) end -private def test(msg, &block : LLVM::ABI, LLVM::Context ->) +private def test(msg, &block : Crystal::ABI, LLVM::Context ->) it msg do abi = abi() ctx = LLVM::Context.new @@ -21,7 +22,7 @@ private def test(msg, &block : LLVM::ABI, LLVM::Context ->) end end -class LLVM::ABI +class Crystal::ABI describe ARM do {% if LibLLVM::BUILT_TARGETS.includes?(:arm) %} describe "align" do @@ -130,7 +131,7 @@ class LLVM::ABI info.arg_types.size.should eq(1) info.arg_types[0].should eq(ArgType.direct(str, cast: ctx.int64.array(2))) - info.return_type.should eq(ArgType.indirect(str, Attribute::StructRet)) + info.return_type.should eq(ArgType.indirect(str, LLVM::Attribute::StructRet)) end test "does with structs between 64 and 128 bits" do |abi, ctx| @@ -142,7 +143,7 @@ class LLVM::ABI info.arg_types.size.should eq(1) info.arg_types[0].should eq(ArgType.direct(str, cast: ctx.int64.array(3))) - info.return_type.should eq(ArgType.indirect(str, Attribute::StructRet)) + info.return_type.should eq(ArgType.indirect(str, LLVM::Attribute::StructRet)) end end {% end %} diff --git a/spec/std/llvm/avr_spec.cr b/spec/compiler/codegen/abi/avr_spec.cr similarity index 74% rename from spec/std/llvm/avr_spec.cr rename to spec/compiler/codegen/abi/avr_spec.cr index a6e95d8937be..11542ea00018 100644 --- a/spec/std/llvm/avr_spec.cr +++ b/spec/compiler/codegen/abi/avr_spec.cr @@ -1,5 +1,6 @@ require "spec" require "llvm" +require "compiler/crystal/codegen/abi/avr" {% if LibLLVM::BUILT_TARGETS.includes?(:avr) %} LLVM.init_avr @@ -10,10 +11,10 @@ private def abi target = LLVM::Target.from_triple(triple) machine = target.create_target_machine(triple) machine.enable_global_isel = false - LLVM::ABI::AVR.new(machine) + Crystal::ABI::AVR.new(machine) end -private def test(msg, &block : LLVM::ABI, LLVM::Context ->) +private def test(msg, &block : Crystal::ABI, LLVM::Context ->) it msg do abi = abi() ctx = LLVM::Context.new @@ -21,7 +22,7 @@ private def test(msg, &block : LLVM::ABI, LLVM::Context ->) end end -class LLVM::ABI +class Crystal::ABI describe AVR do {% if LibLLVM::BUILT_TARGETS.includes?(:avr) %} describe "align" do @@ -104,9 +105,9 @@ class LLVM::ABI info = abi.abi_info([ctx.int{{bits}}], ctx.int{{bits}}, true, ctx) info.arg_types.size.should eq(1) info.arg_types[0].should eq(arg_type) - info.arg_types[0].kind.should eq(LLVM::ABI::ArgKind::Direct) + info.arg_types[0].kind.should eq(Crystal::ABI::ArgKind::Direct) info.return_type.should eq(arg_type) - info.return_type.kind.should eq(LLVM::ABI::ArgKind::Direct) + info.return_type.kind.should eq(Crystal::ABI::ArgKind::Direct) end {% end %} @@ -115,9 +116,9 @@ class LLVM::ABI info = abi.abi_info([ctx.float], ctx.float, true, ctx) info.arg_types.size.should eq(1) info.arg_types[0].should eq(arg_type) - info.arg_types[0].kind.should eq(LLVM::ABI::ArgKind::Direct) + info.arg_types[0].kind.should eq(Crystal::ABI::ArgKind::Direct) info.return_type.should eq(arg_type) - info.return_type.kind.should eq(LLVM::ABI::ArgKind::Direct) + info.return_type.kind.should eq(Crystal::ABI::ArgKind::Direct) end test "double" do |abi, ctx| @@ -125,27 +126,27 @@ class LLVM::ABI info = abi.abi_info([ctx.double], ctx.double, true, ctx) info.arg_types.size.should eq(1) info.arg_types[0].should eq(arg_type) - info.arg_types[0].kind.should eq(LLVM::ABI::ArgKind::Direct) + info.arg_types[0].kind.should eq(Crystal::ABI::ArgKind::Direct) info.return_type.should eq(arg_type) - info.return_type.kind.should eq(LLVM::ABI::ArgKind::Direct) + info.return_type.kind.should eq(Crystal::ABI::ArgKind::Direct) end test "multiple arguments" do |abi, ctx| args = 9.times.map { ctx.int16 }.to_a info = abi.abi_info(args, ctx.int8, false, ctx) info.arg_types.size.should eq(9) - info.arg_types.each(&.kind.should eq(LLVM::ABI::ArgKind::Direct)) + info.arg_types.each(&.kind.should eq(Crystal::ABI::ArgKind::Direct)) end test "multiple arguments above registers" do |abi, ctx| args = 5.times.map { ctx.int32 }.to_a info = abi.abi_info(args, ctx.int8, false, ctx) info.arg_types.size.should eq(5) - info.arg_types[0].kind.should eq(LLVM::ABI::ArgKind::Direct) - info.arg_types[1].kind.should eq(LLVM::ABI::ArgKind::Direct) - info.arg_types[2].kind.should eq(LLVM::ABI::ArgKind::Direct) - info.arg_types[3].kind.should eq(LLVM::ABI::ArgKind::Direct) - info.arg_types[4].kind.should eq(LLVM::ABI::ArgKind::Indirect) + info.arg_types[0].kind.should eq(Crystal::ABI::ArgKind::Direct) + info.arg_types[1].kind.should eq(Crystal::ABI::ArgKind::Direct) + info.arg_types[2].kind.should eq(Crystal::ABI::ArgKind::Direct) + info.arg_types[3].kind.should eq(Crystal::ABI::ArgKind::Direct) + info.arg_types[4].kind.should eq(Crystal::ABI::ArgKind::Indirect) end test "struct args within 18 bytes" do |abi, ctx| @@ -156,9 +157,9 @@ class LLVM::ABI ] info = abi.abi_info(args, ctx.void, false, ctx) info.arg_types.size.should eq(3) - info.arg_types[0].kind.should eq(LLVM::ABI::ArgKind::Direct) - info.arg_types[1].kind.should eq(LLVM::ABI::ArgKind::Direct) - info.arg_types[2].kind.should eq(LLVM::ABI::ArgKind::Direct) + info.arg_types[0].kind.should eq(Crystal::ABI::ArgKind::Direct) + info.arg_types[1].kind.should eq(Crystal::ABI::ArgKind::Direct) + info.arg_types[2].kind.should eq(Crystal::ABI::ArgKind::Direct) end test "struct args over 18 bytes" do |abi, ctx| @@ -169,21 +170,21 @@ class LLVM::ABI ] info = abi.abi_info(args, ctx.void, false, ctx) info.arg_types.size.should eq(3) - info.arg_types[0].kind.should eq(LLVM::ABI::ArgKind::Direct) - info.arg_types[1].kind.should eq(LLVM::ABI::ArgKind::Direct) - info.arg_types[2].kind.should eq(LLVM::ABI::ArgKind::Indirect) + info.arg_types[0].kind.should eq(Crystal::ABI::ArgKind::Direct) + info.arg_types[1].kind.should eq(Crystal::ABI::ArgKind::Direct) + info.arg_types[2].kind.should eq(Crystal::ABI::ArgKind::Indirect) end test "returns struct within 8 bytes" do |abi, ctx| rty = ctx.struct([ctx.int32, ctx.int32]) - info = abi.abi_info([] of Type, rty, true, ctx) - info.return_type.kind.should eq(LLVM::ABI::ArgKind::Direct) + info = abi.abi_info([] of LLVM::Type, rty, true, ctx) + info.return_type.kind.should eq(Crystal::ABI::ArgKind::Direct) end test "returns struct over 8 bytes" do |abi, ctx| rty = ctx.struct([ctx.int32, ctx.int32, ctx.int8]) - info = abi.abi_info([] of Type, rty, true, ctx) - info.return_type.kind.should eq(LLVM::ABI::ArgKind::Indirect) + info = abi.abi_info([] of LLVM::Type, rty, true, ctx) + info.return_type.kind.should eq(Crystal::ABI::ArgKind::Indirect) end end {% end %} diff --git a/spec/std/llvm/x86_64_abi_spec.cr b/spec/compiler/codegen/abi/x86_64_spec.cr similarity index 86% rename from spec/std/llvm/x86_64_abi_spec.cr rename to spec/compiler/codegen/abi/x86_64_spec.cr index 0ba644cefa01..bc8e423bf2ee 100644 --- a/spec/std/llvm/x86_64_abi_spec.cr +++ b/spec/compiler/codegen/abi/x86_64_spec.cr @@ -1,5 +1,7 @@ require "spec" require "llvm" +require "compiler/crystal/codegen/abi/x86_64" +require "compiler/crystal/codegen/abi/x86_win64" {% if LibLLVM::BUILT_TARGETS.includes?(:x86) %} LLVM.init_x86 @@ -10,10 +12,10 @@ private def abi(win64 = false) target = LLVM::Target.from_triple(triple) machine = target.create_target_machine(triple) machine.enable_global_isel = false - win64 ? LLVM::ABI::X86_Win64.new(machine) : LLVM::ABI::X86_64.new(machine) + win64 ? Crystal::ABI::X86_Win64.new(machine) : Crystal::ABI::X86_64.new(machine) end -private def test(msg, *, win64 = false, file = __FILE__, line = __LINE__, &block : LLVM::ABI, LLVM::Context ->) +private def test(msg, *, win64 = false, file = __FILE__, line = __LINE__, &block : Crystal::ABI, LLVM::Context ->) it msg, file: file, line: line do abi = abi(win64) ctx = LLVM::Context.new @@ -21,7 +23,7 @@ private def test(msg, *, win64 = false, file = __FILE__, line = __LINE__, &block end end -class LLVM::ABI +class Crystal::ABI describe X86_64 do {% if LibLLVM::BUILT_TARGETS.includes?(:x86) %} describe "align" do @@ -141,8 +143,8 @@ class LLVM::ABI info = abi.abi_info(arg_types, return_type, true, ctx) info.arg_types.size.should eq(1) - info.arg_types[0].should eq(ArgType.indirect(str, Attribute::ByVal)) - info.return_type.should eq(ArgType.indirect(str, Attribute::StructRet)) + info.arg_types[0].should eq(ArgType.indirect(str, LLVM::Attribute::ByVal)) + info.return_type.should eq(ArgType.indirect(str, LLVM::Attribute::StructRet)) end end {% end %} @@ -159,8 +161,8 @@ class LLVM::ABI info = abi.abi_info(arg_types, return_type, true, ctx) info.arg_types.size.should eq(1) - info.arg_types[0].should eq(ArgType.indirect(str, Attribute::ByVal)) - info.return_type.should eq(ArgType.indirect(str, Attribute::StructRet)) + info.arg_types[0].should eq(ArgType.indirect(str, LLVM::Attribute::ByVal)) + info.return_type.should eq(ArgType.indirect(str, LLVM::Attribute::StructRet)) end test "does with structs larger than 128 bits", win64: true do |abi, ctx| @@ -171,8 +173,8 @@ class LLVM::ABI info = abi.abi_info(arg_types, return_type, true, ctx) info.arg_types.size.should eq(1) - info.arg_types[0].should eq(ArgType.indirect(str, Attribute::ByVal)) - info.return_type.should eq(ArgType.indirect(str, Attribute::StructRet)) + info.arg_types[0].should eq(ArgType.indirect(str, LLVM::Attribute::ByVal)) + info.return_type.should eq(ArgType.indirect(str, LLVM::Attribute::StructRet)) end test "does with packed struct containing unaligned fields (#9873)" do |abi, ctx| @@ -183,8 +185,8 @@ class LLVM::ABI info = abi.abi_info(arg_types, return_type, true, ctx) info.arg_types.size.should eq(1) - info.arg_types[0].should eq(ArgType.indirect(str, Attribute::ByVal)) - info.return_type.should eq(ArgType.indirect(str, Attribute::StructRet)) + info.arg_types[0].should eq(ArgType.indirect(str, LLVM::Attribute::ByVal)) + info.return_type.should eq(ArgType.indirect(str, LLVM::Attribute::StructRet)) end test "does with packed struct not containing unaligned fields" do |abi, ctx| diff --git a/spec/std/llvm/x86_abi_spec.cr b/spec/compiler/codegen/abi/x86_spec.cr similarity index 93% rename from spec/std/llvm/x86_abi_spec.cr rename to spec/compiler/codegen/abi/x86_spec.cr index 27d387820298..f3c78c9f1306 100644 --- a/spec/std/llvm/x86_abi_spec.cr +++ b/spec/compiler/codegen/abi/x86_spec.cr @@ -2,6 +2,7 @@ require "spec" require "llvm" +require "compiler/crystal/codegen/abi/x86" {% if LibLLVM::BUILT_TARGETS.includes?(:x86) %} LLVM.init_x86 @@ -16,10 +17,10 @@ private def abi target = LLVM::Target.from_triple(triple) machine = target.create_target_machine(triple) machine.enable_global_isel = false - LLVM::ABI::X86.new(machine) + Crystal::ABI::X86.new(machine) end -private def test(msg, &block : LLVM::ABI, LLVM::Context ->) +private def test(msg, &block : Crystal::ABI, LLVM::Context ->) it msg do abi = abi() ctx = LLVM::Context.new @@ -27,7 +28,7 @@ private def test(msg, &block : LLVM::ABI, LLVM::Context ->) end end -class LLVM::ABI +class Crystal::ABI describe X86 do {% if LibLLVM::BUILT_TARGETS.includes?(:x86) %} describe "align" do @@ -147,8 +148,8 @@ class LLVM::ABI info = abi.abi_info(arg_types, return_type, true, ctx) info.arg_types.size.should eq(1) - info.arg_types[0].should eq(ArgType.indirect(str, Attribute::ByVal)) - info.return_type.should eq(ArgType.indirect(str, Attribute::StructRet)) + info.arg_types[0].should eq(ArgType.indirect(str, LLVM::Attribute::ByVal)) + info.return_type.should eq(ArgType.indirect(str, LLVM::Attribute::StructRet)) end end {% end %} diff --git a/src/compiler/crystal/codegen/abi.cr b/src/compiler/crystal/codegen/abi.cr new file mode 100644 index 000000000000..25134462c80f --- /dev/null +++ b/src/compiler/crystal/codegen/abi.cr @@ -0,0 +1,134 @@ +# Based on https://github.com/rust-lang/rust/blob/29ac04402d53d358a1f6200bea45a301ff05b2d1/src/librustc_trans/trans/cabi.rs +abstract class Crystal::ABI + getter target_data : LLVM::TargetData + getter? osx : Bool + getter? windows : Bool + + def initialize(target_machine : LLVM::TargetMachine) + @target_data = target_machine.data_layout + triple = target_machine.triple + @osx = !!(triple =~ /apple/) + @windows = !!(triple =~ /windows/) + end + + def self.from(target_machine : LLVM::TargetMachine) : self + triple = target_machine.triple + case triple + when /x86_64.+windows-(?:msvc|gnu)/ + X86_Win64.new(target_machine) + when /x86_64|amd64/ + X86_64.new(target_machine) + when /i386|i486|i586|i686/ + X86.new(target_machine) + when /aarch64|arm64/ + AArch64.new(target_machine) + when /arm/ + ARM.new(target_machine) + when /avr/ + AVR.new(target_machine, target_machine.cpu) + when /wasm32/ + Wasm32.new(target_machine) + else + raise "Unsupported ABI for target triple: #{triple}" + end + end + + abstract def abi_info(atys : Array(LLVM::Type), rty : LLVM::Type, ret_def : Bool, context : Context) + abstract def size(type : LLVM::Type) + abstract def align(type : LLVM::Type) + + def size(type : LLVM::Type, pointer_size) : Int32 + case type.kind + when LLVM::Type::Kind::Integer + (type.int_width + 7) // 8 + when LLVM::Type::Kind::Float + 4 + when LLVM::Type::Kind::Double + 8 + when LLVM::Type::Kind::Pointer + pointer_size + when LLVM::Type::Kind::Struct + if type.packed_struct? + type.struct_element_types.reduce(0) do |memo, elem| + memo + size(elem) + end + else + size = type.struct_element_types.reduce(0) do |memo, elem| + align_offset(memo, elem) + size(elem) + end + align_offset(size, type) + end + when LLVM::Type::Kind::Array + size(type.element_type) * type.array_size + else + raise "Unhandled LLVM::Type::Kind in size: #{type.kind}" + end + end + + def align_offset(offset, type) : Int32 + align = align(type) + (offset + align - 1) // align * align + end + + def align(type : LLVM::Type, pointer_size) : Int32 + case type.kind + when LLVM::Type::Kind::Integer + (type.int_width + 7) // 8 + when LLVM::Type::Kind::Float + 4 + when LLVM::Type::Kind::Double + 8 + when LLVM::Type::Kind::Pointer + pointer_size + when LLVM::Type::Kind::Struct + if type.packed_struct? + 1 + else + type.struct_element_types.reduce(1) do |memo, elem| + Math.max(memo, align(elem)) + end + end + when LLVM::Type::Kind::Array + align(type.element_type) + else + raise "Unhandled LLVM::Type::Kind in align: #{type.kind}" + end + end + + enum ArgKind + Direct + Indirect + Ignore + end + + struct ArgType + getter kind : ArgKind + getter type : LLVM::Type + getter cast : LLVM::Type? + getter pad : Nil + getter attr : LLVM::Attribute? + + def self.direct(type, cast = nil, pad = nil, attr = nil) + new ArgKind::Direct, type, cast, pad, attr + end + + def self.indirect(type, attr) : self + new ArgKind::Indirect, type, attr: attr + end + + def self.ignore(type) : self + new ArgKind::Ignore, type + end + + def initialize(@kind, @type, @cast = nil, @pad = nil, @attr = nil) + end + end + + class FunctionType + getter arg_types : Array(ArgType) + getter return_type : ArgType + + def initialize(@arg_types, @return_type) + end + end +end diff --git a/src/compiler/crystal/codegen/abi/aarch64.cr b/src/compiler/crystal/codegen/abi/aarch64.cr new file mode 100644 index 000000000000..c41dd36adc42 --- /dev/null +++ b/src/compiler/crystal/codegen/abi/aarch64.cr @@ -0,0 +1,158 @@ +require "../abi" + +# Based on +# https://github.com/rust-lang/rust/blob/master/src/librustc_trans/cabi_aarch64.rs +class Crystal::ABI::AArch64 < Crystal::ABI + def abi_info(atys : Array(LLVM::Type), rty : LLVM::Type, ret_def : Bool, context : LLVM::Context) : Crystal::ABI::FunctionType + ret_ty = compute_return_type(rty, ret_def, context) + arg_tys = atys.map { |aty| compute_arg_type(aty, context) } + FunctionType.new(arg_tys, ret_ty) + end + + def align(type : LLVM::Type) : Int32 + align(type, 8) + end + + def size(type : LLVM::Type) : Int32 + size(type, 8) + end + + def homogeneous_aggregate?(type) + homog_agg : {LLVM::Type, UInt64}? = case type.kind + when LLVM::Type::Kind::Float + return {type, 1_u64} + when LLVM::Type::Kind::Double + return {type, 1_u64} + when LLVM::Type::Kind::Array + check_array(type) + when LLVM::Type::Kind::Struct + check_struct(type) + end + + # Ensure we have at most four uniquely addressable members + if homog_agg + if 0 < homog_agg[1] <= 4 + return homog_agg + end + end + end + + private def check_array(type) + len = type.array_size.to_u64 + return if len == 0 + element = type.element_type + + # if our element is an HFA/HVA, so are we; multiply members by our len + if homog_agg = homogeneous_aggregate?(element) + base_type, members = homog_agg + {base_type, len * members} + end + end + + private def check_struct(type) + elements = type.struct_element_types + return if elements.empty? + + base_type = nil + members = 0_u64 + + elements.each do |element| + opt_homog_agg = homogeneous_aggregate?(element) + + # field isn't itself an HFA, so we aren't either + return unless opt_homog_agg + field_type, field_members = opt_homog_agg + + if !base_type + # first field - store its type and number of members + base_type = field_type + members = field_members + else + # 2nd or later field - give up if it's a different type; otherwise incr. members + return unless base_type == field_type + members += field_members + end + end + + return unless base_type + + if size(type) == size(base_type) * members + {base_type, members} + end + end + + private def compute_return_type(rty, ret_def, context) + if !ret_def + ArgType.direct(context.void) + elsif register?(rty) + non_struct(rty, context) + elsif homog_agg = homogeneous_aggregate?(rty) + base_type, members = homog_agg + ArgType.direct(rty, base_type.array(members)) + else + size = size(rty) + if size <= 16 + cast = if size <= 1 + context.int8 + elsif size <= 2 + context.int16 + elsif size <= 4 + context.int32 + elsif size <= 8 + context.int64 + else + context.int64.array(((size + 7) // 8).to_u64) + end + ArgType.direct(rty, cast) + else + ArgType.indirect(rty, LLVM::Attribute::StructRet) + end + end + end + + private def compute_arg_type(aty, context) + if register?(aty) + non_struct(aty, context) + elsif homog_agg = homogeneous_aggregate?(aty) + base_type, members = homog_agg + ArgType.direct(aty, base_type.array(members)) + else + size = size(aty) + if size <= 16 + cast = if size == 0 + context.int64.array(0) + elsif size <= 1 + context.int8 + elsif size <= 2 + context.int16 + elsif size <= 4 + context.int32 + elsif size <= 8 + context.int64 + else + context.int64.array(((size + 7) // 8).to_u64) + end + ArgType.direct(aty, cast) + else + ArgType.indirect(aty, nil) + end + end + end + + def register?(type) : Bool + case type.kind + when LLVM::Type::Kind::Integer, + LLVM::Type::Kind::Float, + LLVM::Type::Kind::Double, + LLVM::Type::Kind::Pointer + true + else + false + end + end + + private def non_struct(type, context) + attr = type == context.int1 ? LLVM::Attribute::ZExt : nil + ArgType.direct(type, attr: attr) + end +end diff --git a/src/compiler/crystal/codegen/abi/arm.cr b/src/compiler/crystal/codegen/abi/arm.cr new file mode 100644 index 000000000000..3abdd6d3af69 --- /dev/null +++ b/src/compiler/crystal/codegen/abi/arm.cr @@ -0,0 +1,65 @@ +require "../abi" + +# Based on https://github.com/rust-lang/rust/blob/dfe8bd10fe6763e0a1d5d55fa2574ecba27d3e2e/src/librustc_trans/cabi_arm.rs +class Crystal::ABI::ARM < Crystal::ABI + def abi_info(atys : Array(LLVM::Type), rty : LLVM::Type, ret_def : Bool, context : LLVM::Context) : Crystal::ABI::FunctionType + ret_ty = compute_return_type(rty, ret_def, context) + arg_tys = compute_arg_types(atys, context) + FunctionType.new(arg_tys, ret_ty) + end + + def align(type : LLVM::Type) : Int32 + align(type, 4) + end + + def size(type : LLVM::Type) : Int32 + size(type, 4) + end + + def register?(type) : Bool + case type.kind + when LLVM::Type::Kind::Integer, LLVM::Type::Kind::Float, LLVM::Type::Kind::Double, LLVM::Type::Kind::Pointer + true + else + false + end + end + + private def compute_return_type(rty, ret_def, context) + if !ret_def + ArgType.direct(context.void) + elsif register?(rty) + non_struct(rty, context) + else + case size(rty) + when 1 + ArgType.direct(rty, context.int8) + when 2 + ArgType.direct(rty, context.int16) + when 3, 4 + ArgType.direct(rty, context.int32) + else + ArgType.indirect(rty, LLVM::Attribute::StructRet) + end + end + end + + private def compute_arg_types(atys, context) + atys.map do |aty| + if register?(aty) + non_struct(aty, context) + else + if align(aty) <= 4 + ArgType.direct(aty, context.int32.array(((size(aty) + 3) // 4).to_u64)) + else + ArgType.direct(aty, context.int64.array(((size(aty) + 7) // 8).to_u64)) + end + end + end + end + + private def non_struct(type, context) + attr = type == context.int1 ? LLVM::Attribute::ZExt : nil + ArgType.direct(type, attr: attr) + end +end diff --git a/src/compiler/crystal/codegen/abi/avr.cr b/src/compiler/crystal/codegen/abi/avr.cr new file mode 100644 index 000000000000..4e55ec9eeda3 --- /dev/null +++ b/src/compiler/crystal/codegen/abi/avr.cr @@ -0,0 +1,82 @@ +require "../abi" + +class Crystal::ABI::AVR < Crystal::ABI + AVRTINY = StaticArray[ + "attiny4", + "attiny5", + "attiny9", + "attiny10", + "attiny102", + "attiny104", + "attiny20", + "attiny40", + ] + + def initialize(target_machine : LLVM::TargetMachine, mcpu : String? = nil) + super target_machine + + # "Reduced Tiny" core devices only have 16 General Purpose Registers + if mcpu.in?(AVRTINY) + @rsize = 4 # values above 4 bytes are returned by memory + @rmin = 20 # 6 registers for call arguments (R25..R20) + else + @rsize = 8 # values above 8 bytes are returned by memory + @rmin = 8 # 18 registers for call arguments (R25..R8) + end + end + + def align(type : LLVM::Type) : Int32 + target_data.abi_alignment(type).to_i32 + end + + def size(type : LLVM::Type) : Int32 + target_data.abi_size(type).to_i32 + end + + # Follows AVR GCC, while Clang (and Rust) are incompatible, despite LLVM + # itself being compliant. + # + # - + # - + def abi_info(atys : Array(LLVM::Type), rty : LLVM::Type, ret_def : Bool, context : LLVM::Context) : Crystal::ABI::FunctionType + ret_ty = compute_return_type(rty, ret_def, context) + arg_tys = compute_arg_types(atys, context) + FunctionType.new(arg_tys, ret_ty) + end + + # Pass in registers unless the returned type is a struct larger than 8 bytes + # (4 bytes on reduced tiny cores). + # + # Rust & Clang always return a struct _indirectly_. + private def compute_return_type(rty, ret_def, context) + if !ret_def + ArgType.direct(context.void) + elsif size(rty) > @rsize + ArgType.indirect(rty, LLVM::Attribute::StructRet) + else + # let the LLVM AVR backend handle the pw2ceil padding of structs + ArgType.direct(rty) + end + end + + # Fill the R25 -> R8 registers (R20 on reduced tiny cores), rounding odd byte + # sizes to the next even number, then pass by memory (indirect), so {i8, i32} + # are passed as R24 then R20..R23 (LSB -> MSB) for example. + # + # Rust & Clang always pass structs _indirectly_. + private def compute_arg_types(atys, context) + rn = 26 + atys.map do |aty| + bytes = size(aty) + bytes += 1 if bytes.odd? + rn -= bytes + + if bytes == 0 || rn < @rmin + ArgType.indirect(aty, LLVM::Attribute::StructRet) + else + # let the LLVM AVR backend handle the odd to next even number padding + ArgType.direct(aty) + end + end + end +end diff --git a/src/compiler/crystal/codegen/abi/wasm32.cr b/src/compiler/crystal/codegen/abi/wasm32.cr new file mode 100644 index 000000000000..2927024196b1 --- /dev/null +++ b/src/compiler/crystal/codegen/abi/wasm32.cr @@ -0,0 +1,44 @@ +require "../abi" + +class Crystal::ABI::Wasm32 < Crystal::ABI + def abi_info(atys : Array(LLVM::Type), rty : LLVM::Type, ret_def : Bool, context : LLVM::Context) + ret_ty = compute_return_type(rty, ret_def, context) + arg_tys = compute_arg_types(atys, context) + FunctionType.new(arg_tys, ret_ty) + end + + def align(type : LLVM::Type) + target_data.abi_alignment(type).to_i32 + end + + def size(type : LLVM::Type) + target_data.abi_size(type).to_i32 + end + + private def aggregate?(type) + case type.kind + when .struct?, .array? + true + else + false + end + end + + private def compute_return_type(rty, ret_def, context) + if aggregate?(rty) + ArgType.indirect(rty, LLVM::Attribute::ByVal) + else + ArgType.direct(rty) + end + end + + private def compute_arg_types(atys, context) + atys.map do |t| + if aggregate?(t) + ArgType.indirect(t, LLVM::Attribute::ByVal) + else + ArgType.direct(t) + end + end + end +end diff --git a/src/compiler/crystal/codegen/abi/x86.cr b/src/compiler/crystal/codegen/abi/x86.cr new file mode 100644 index 000000000000..87352c8481a8 --- /dev/null +++ b/src/compiler/crystal/codegen/abi/x86.cr @@ -0,0 +1,75 @@ +require "../abi" + +# Based on https://github.com/rust-lang/rust/blob/29ac04402d53d358a1f6200bea45a301ff05b2d1/src/librustc_trans/trans/cabi_x86.rs +class Crystal::ABI::X86 < Crystal::ABI + def abi_info(atys : Array(LLVM::Type), rty : LLVM::Type, ret_def : Bool, context : LLVM::Context) + ret_ty = compute_return_type(rty, ret_def, context) + arg_tys = compute_arg_types(atys, context) + FunctionType.new arg_tys, ret_ty + end + + def size(type : LLVM::Type) + target_data.abi_size(type).to_i32 + end + + def align(type : LLVM::Type) + target_data.abi_alignment(type).to_i32 + end + + private def compute_return_type(rty, ret_def, context) + if !ret_def + ArgType.direct(context.void) + elsif rty.kind == LLVM::Type::Kind::Struct + # Returning a structure. Most often, this will use + # a hidden first argument. On some platforms, though, + # small structs are returned as integers. + # + # Some links: + # http://www.angelcode.com/dev/callconv/callconv.html + # Clang's ABI handling is in lib/CodeGen/TargetInfo.cpp + + if osx? || windows? + case target_data.abi_size(rty) + when 1 then ret_value(rty, context.int8) + when 2 then ret_value(rty, context.int16) + when 4 then ret_value(rty, context.int32) + when 8 then ret_value(rty, context.int64) + else ret_pointer(rty) + end + else + ret_pointer(rty) + end + else + non_struct(rty, context) + end + end + + private def compute_arg_types(atys, context) + atys.map do |t| + case t.kind + when LLVM::Type::Kind::Struct + size = target_data.abi_size(t) + if size == 0 + ArgType.ignore(t) + else + ArgType.indirect(t, LLVM::Attribute::ByVal) + end + else + non_struct(t, context) + end + end + end + + private def ret_value(type, cast) + ArgType.direct(type, cast) + end + + private def ret_pointer(type) + ArgType.indirect(type, LLVM::Attribute::StructRet) + end + + private def non_struct(type, context) + attr = type == context.int1 ? LLVM::Attribute::ZExt : nil + ArgType.direct(type, attr: attr) + end +end diff --git a/src/compiler/crystal/codegen/abi/x86_64.cr b/src/compiler/crystal/codegen/abi/x86_64.cr new file mode 100644 index 000000000000..986540be1074 --- /dev/null +++ b/src/compiler/crystal/codegen/abi/x86_64.cr @@ -0,0 +1,311 @@ +require "../abi" + +# Based on https://github.com/rust-lang/rust/blob/29ac04402d53d358a1f6200bea45a301ff05b2d1/src/librustc_trans/trans/cabi_x86_64.rs +# See also, section 3.2.3 of the System V Application Binary Interface AMD64 Architecture Processor Supplement +class Crystal::ABI::X86_64 < Crystal::ABI + MAX_INT_REGS = 6 # %rdi, %rsi, %rdx, %rcx, %r8, %r9 + MAX_SSE_REGS = 8 # %xmm0-%xmm7 + + def abi_info(atys : Array(LLVM::Type), rty : LLVM::Type, ret_def : Bool, context : LLVM::Context) : Crystal::ABI::FunctionType + # registers available to pass arguments directly: int_regs can hold integers + # and pointers, sse_regs can hold floats and doubles + available_int_regs = MAX_INT_REGS + available_sse_regs = MAX_SSE_REGS + + if ret_def + ret_ty, _, _ = x86_64_type(rty, LLVM::Attribute::StructRet, context) { |cls| sret?(cls) } + if ret_ty.kind.indirect? + # return value is a caller-allocated struct which is passed in %rdi, + # so we have 1 less register available for passing arguments + available_int_regs -= 1 + end + else + ret_ty = ArgType.direct(context.void) + end + + arg_tys = atys.map do |arg_type| + abi_type, needed_int_regs, needed_sse_regs = x86_64_type(arg_type, LLVM::Attribute::ByVal, context) { |cls| pass_by_val?(cls) } + if available_int_regs >= needed_int_regs && available_sse_regs >= needed_sse_regs + available_int_regs -= needed_int_regs + available_sse_regs -= needed_sse_regs + abi_type + elsif !register?(arg_type) + # no available registers to pass the argument, but only mark aggregates + # as indirect byval types because LLVM will automatically pass register + # types in the stack + ArgType.indirect(arg_type, LLVM::Attribute::ByVal) + else + abi_type + end + end + + FunctionType.new arg_tys, ret_ty + end + + # returns the LLVM type (with attributes) and the number of integer and SSE + # registers needed to pass this value directly (ie. not using the stack) + def x86_64_type(type, ind_attr, context, &) : Tuple(ArgType, Int32, Int32) + if int_register?(type) + attr = type == context.int1 ? LLVM::Attribute::ZExt : nil + {ArgType.direct(type, attr: attr), 1, 0} + elsif sse_register?(type) + {ArgType.direct(type), 0, 1} + else + cls = classify(type) + if yield cls + {ArgType.indirect(type, ind_attr), 0, 0} + else + needed_int_regs = cls.count(&.int?) + needed_sse_regs = cls.count(&.sse?) + {ArgType.direct(type, llreg(context, cls)), needed_int_regs, needed_sse_regs} + end + end + end + + def register?(type) : Bool + int_register?(type) || sse_register?(type) + end + + def int_register?(type) : Bool + case type.kind + when LLVM::Type::Kind::Integer, LLVM::Type::Kind::Pointer + true + else + false + end + end + + def sse_register?(type) : Bool + case type.kind + when LLVM::Type::Kind::Float, LLVM::Type::Kind::Double + true + else + false + end + end + + def pass_by_val?(cls) : Bool + return false if cls.empty? + + cl = cls.first + cl.in?(RegClass::Memory, RegClass::X87, RegClass::ComplexX87) + end + + def sret?(cls) : Bool + return false if cls.empty? + + cls.first == RegClass::Memory + end + + def classify(type) + words = (size(type) + 7) // 8 + reg_classes = Array.new(words, RegClass::NoClass) + if words > 4 || has_misaligned_fields?(type) + all_mem(reg_classes) + else + classify(type, reg_classes, 0, 0) + fixup(type, reg_classes) + end + reg_classes + end + + def classify(ty, cls, ix, off) + t_align = align(ty) + t_size = size(ty) + + misalign = off % t_align + if misalign != 0 + i = off // 8 + e = (off + t_size + 7) // 8 + while i < e + unify(cls, ix + 1, RegClass::Memory) + i += 1 + end + return + end + + case ty.kind + when LLVM::Type::Kind::Integer, LLVM::Type::Kind::Pointer + unify(cls, ix + off // 8, RegClass::Int) + when LLVM::Type::Kind::Float + unify(cls, ix + off // 8, (off % 8 == 4) ? RegClass::SSEFv : RegClass::SSEFs) + when LLVM::Type::Kind::Double + unify(cls, ix + off // 8, RegClass::SSEDs) + when LLVM::Type::Kind::Struct + classify_struct(ty.struct_element_types, cls, ix, off, ty.packed_struct?) + when LLVM::Type::Kind::Array + len = ty.array_size + elt = ty.element_type + eltsz = size(elt) + i = 0 + while i < len + classify(elt, cls, ix, off + i * eltsz) + i += 1 + end + else + raise "Unhandled LLVM::Type::Kind in classify: #{ty.kind}" + end + end + + def classify_struct(tys, cls, i, off, packed) : Nil + field_off = off + tys.each do |ty| + field_off = align_offset(field_off, ty) unless packed + classify(ty, cls, i, field_off) + field_off += size(ty) + end + end + + def fixup(ty, cls) : Nil + i = 0 + ty_kind = ty.kind + e = cls.size + if e > 2 && ty_kind.in?(LLVM::Type::Kind::Struct, LLVM::Type::Kind::Array) + if cls[i].sse? + i += 1 + while i < e + if cls[i] != RegClass::SSEUp + all_mem(cls) + return + end + i += 1 + end + else + all_mem(cls) + return + end + else + while i < e + case + when cls[i] == RegClass::Memory + all_mem(cls) + return + when cls[i] == RegClass::X87Up + # for darwin + # cls[i] = RegClass::SSEDs + all_mem(cls) + return + when cls[i] == RegClass::SSEUp + cls[i] = RegClass::SSEDv + when cls[i].sse? + i += 1 + while i != e && cls[i] == RegClass::SSEUp + i += 1 + end + when cls[i] == RegClass::X87 + i += 1 + while i != e && cls[i] == RegClass::X87Up + i += 1 + end + else + i += 1 + end + end + end + end + + def unify(cls, i, newv) + case + when cls[i] == newv + return + when cls[i] == RegClass::NoClass + cls[i] = newv + when newv == RegClass::NoClass + return + when cls[i] == RegClass::Memory, newv == RegClass::Memory + return + when cls[i] == RegClass::Int, newv == RegClass::Int + return + when cls[i] == RegClass::X87, + cls[i] == RegClass::X87Up, + cls[i] == RegClass::ComplexX87, + newv == RegClass::X87, + newv == RegClass::X87Up, + newv == RegClass::ComplexX87 + cls[i] = RegClass::Memory + else + cls[i] = newv + end + end + + def all_mem(reg_classes) + reg_classes.fill(RegClass::Memory) + end + + def llreg(context, reg_classes) : LLVM::Type + types = Array(LLVM::Type).new + i = 0 + e = reg_classes.size + while i < e + case reg_classes[i] + when RegClass::Int + types << context.int64 + when RegClass::SSEFv + vec_len = llvec_len(reg_classes[i + 1..-1]) + vec_type = context.float.vector(vec_len * 2) + types << vec_type + i += vec_len + next + when RegClass::SSEFs + types << context.float + when RegClass::SSEDs + types << context.double + else + raise "Unhandled RegClass: #{reg_classes[i]}" + end + i += 1 + end + context.struct(types) + end + + def llvec_len(reg_classes) : Int32 + len = 1 + reg_classes.each do |reg_class| + break if reg_class != RegClass::SSEUp + len += 1 + end + len + end + + def align(type : LLVM::Type) : Int32 + align(type, 8) + end + + def size(type : LLVM::Type) : Int32 + size(type, 8) + end + + def has_misaligned_fields?(type : LLVM::Type) : Bool + return false unless type.packed_struct? + offset = 0 + type.struct_element_types.each do |elem| + return true unless offset.divisible_by?(align(elem)) + offset += size(elem) + end + false + end + + enum RegClass + NoClass + Int + SSEFs + SSEFv + SSEDs + SSEDv + SSEInt + SSEUp + X87 + X87Up + ComplexX87 + Memory + + def sse? : Bool + case self + when SSEFs, SSEFv, SSEDs + true + else + false + end + end + end +end diff --git a/src/compiler/crystal/codegen/abi/x86_win64.cr b/src/compiler/crystal/codegen/abi/x86_win64.cr new file mode 100644 index 000000000000..ba422b2188ac --- /dev/null +++ b/src/compiler/crystal/codegen/abi/x86_win64.cr @@ -0,0 +1,22 @@ +require "./x86" + +# Based on https://github.com/rust-lang/rust/blob/29ac04402d53d358a1f6200bea45a301ff05b2d1/src/librustc_trans/trans/cabi_x86_win64.rs +class Crystal::ABI::X86_Win64 < Crystal::ABI::X86 + private def compute_arg_types(atys, context) + atys.map do |t| + case t.kind + when LLVM::Type::Kind::Struct + size = target_data.abi_size(t) + case size + when 1 then ArgType.direct(t, context.int8) + when 2 then ArgType.direct(t, context.int16) + when 4 then ArgType.direct(t, context.int32) + when 8 then ArgType.direct(t, context.int64) + else ArgType.indirect(t, LLVM::Attribute::ByVal) + end + else + non_struct(t, context) + end + end + end +end diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 39769c71460c..80c6cd2f9720 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -4,6 +4,7 @@ require "../syntax/visitor" require "../semantic" require "../program" require "./llvm_builder_helper" +require "./abi/*" module Crystal MAIN_NAME = "__crystal_main" @@ -245,7 +246,7 @@ module Crystal record StringKey, mod : LLVM::Module, string : String record ModuleInfo, mod : LLVM::Module, typer : LLVMTyper, builder : CrystalLLVMBuilder - @abi : LLVM::ABI + @abi : ABI @main_ret_type : Type @argc : LLVM::Value @argv : LLVM::Value @@ -272,7 +273,7 @@ module Crystal @debug = Debug::Default, @frame_pointers : FramePointers = :auto, @llvm_context : LLVM::Context = LLVM::Context.new) - @abi = @program.target_machine.abi + @abi = ABI.from(@program.target_machine) # LLVM::Context.register(@llvm_context, "main") @llvm_mod = configure_module(@llvm_context.new_module("main_module")) @main_mod = @llvm_mod diff --git a/src/compiler/crystal/codegen/fun.cr b/src/compiler/crystal/codegen/fun.cr index 4723edec77fa..4f0e7be6fc51 100644 --- a/src/compiler/crystal/codegen/fun.cr +++ b/src/compiler/crystal/codegen/fun.cr @@ -414,7 +414,7 @@ class Crystal::CodeGenVisitor context.fun.add_attribute(attr, i + offset + 1, abi_arg_type.type) end - i += 1 unless abi_arg_type.kind == LLVM::ABI::ArgKind::Ignore + i += 1 unless abi_arg_type.kind == ABI::ArgKind::Ignore end # This is for sret diff --git a/src/llvm/abi.cr b/src/llvm/abi.cr index 181e9b6d8f2b..ed2b072d0745 100644 --- a/src/llvm/abi.cr +++ b/src/llvm/abi.cr @@ -1,9 +1,13 @@ +# LLVM::ABI is deprecated. The compiler uses Crystal::ABI instead. + # Based on https://github.com/rust-lang/rust/blob/29ac04402d53d358a1f6200bea45a301ff05b2d1/src/librustc_trans/trans/cabi.rs +@[Deprecated("This API is now internal to the compiler and no longer updated publicly.")] abstract class LLVM::ABI getter target_data : TargetData getter? osx : Bool getter? windows : Bool + @[Deprecated("This API is now internal to the compiler and no longer updated publicly.")] def initialize(target_machine : TargetMachine) @target_data = target_machine.data_layout triple = target_machine.triple @@ -73,12 +77,14 @@ abstract class LLVM::ABI end end + @[Deprecated("This API is now internal to the compiler and no longer updated publicly.")] enum ArgKind Direct Indirect Ignore end + @[Deprecated("This API is now internal to the compiler and no longer updated publicly.")] struct ArgType getter kind : ArgKind getter type : Type @@ -98,14 +104,17 @@ abstract class LLVM::ABI new ArgKind::Ignore, type end + @[Deprecated("This API is now internal to the compiler and no longer updated publicly.")] def initialize(@kind, @type, @cast = nil, @pad = nil, @attr = nil) end end + @[Deprecated("This API is now internal to the compiler and no longer updated publicly.")] class FunctionType getter arg_types : Array(ArgType) getter return_type : ArgType + @[Deprecated("This API is now internal to the compiler and no longer updated publicly.")] def initialize(@arg_types, @return_type) end end diff --git a/src/llvm/target_machine.cr b/src/llvm/target_machine.cr index 6e31836ef7f2..ad77f1fbc45f 100644 --- a/src/llvm/target_machine.cr +++ b/src/llvm/target_machine.cr @@ -45,6 +45,7 @@ class LLVM::TargetMachine true end + @[Deprecated("This API is now internal to the compiler and no longer updated publicly.")] def abi triple = self.triple case triple