require "spec"
require "llvm"
require "compiler/crystal/codegen/abi/avr"

{% if LibLLVM::BUILT_TARGETS.includes?(:avr) %}
  LLVM.init_avr
{% end %}

private def abi
  triple = "avr-unknown-unknown-atmega328p"
  target = LLVM::Target.from_triple(triple)
  machine = target.create_target_machine(triple)
  machine.enable_global_isel = false
  Crystal::ABI::AVR.new(machine)
end

private def test(msg, &block : Crystal::ABI, LLVM::Context ->)
  it msg do
    abi = abi()
    ctx = LLVM::Context.new
    block.call(abi, ctx)
  end
end

class Crystal::ABI
  describe AVR do
    {% if LibLLVM::BUILT_TARGETS.includes?(:avr) %}
      describe "align" do
        test "for integer" do |abi, ctx|
          abi.align(ctx.int1).should be_a(::Int32)
          abi.align(ctx.int1).should eq(1)
          abi.align(ctx.int8).should eq(1)
          abi.align(ctx.int16).should eq(1)
          abi.align(ctx.int32).should eq(1)
          abi.align(ctx.int64).should eq(1)
        end

        test "for pointer" do |abi, ctx|
          abi.align(ctx.int8.pointer).should eq(1)
        end

        test "for float" do |abi, ctx|
          abi.align(ctx.float).should eq(1)
        end

        test "for double" do |abi, ctx|
          abi.align(ctx.double).should eq(1)
        end

        test "for struct" do |abi, ctx|
          abi.align(ctx.struct([ctx.int32, ctx.int64])).should eq(1)
          abi.align(ctx.struct([ctx.int8, ctx.int16])).should eq(1)
        end

        test "for packed struct" do |abi, ctx|
          abi.align(ctx.struct([ctx.int32, ctx.int64], packed: true)).should eq(1)
        end

        test "for array" do |abi, ctx|
          abi.align(ctx.int16.array(10)).should eq(1)
        end
      end

      describe "size" do
        test "for integer" do |abi, ctx|
          abi.size(ctx.int1).should be_a(::Int32)
          abi.size(ctx.int1).should eq(1)
          abi.size(ctx.int8).should eq(1)
          abi.size(ctx.int16).should eq(2)
          abi.size(ctx.int32).should eq(4)
          abi.size(ctx.int64).should eq(8)
        end

        test "for pointer" do |abi, ctx|
          abi.size(ctx.int8.pointer).should eq(2)
        end

        test "for float" do |abi, ctx|
          abi.size(ctx.float).should eq(4)
        end

        test "for double" do |abi, ctx|
          abi.size(ctx.double).should eq(8)
        end

        test "for struct" do |abi, ctx|
          abi.size(ctx.struct([ctx.int32, ctx.int64])).should eq(12)
          abi.size(ctx.struct([ctx.int16, ctx.int8])).should eq(3)
          abi.size(ctx.struct([ctx.int32, ctx.int8, ctx.int8])).should eq(6)
        end

        test "for packed struct" do |abi, ctx|
          abi.size(ctx.struct([ctx.int32, ctx.int8], packed: true)).should eq(5)
        end

        test "for array" do |abi, ctx|
          abi.size(ctx.int16.array(10)).should eq(20)
        end
      end

      describe "abi_info" do
        {% for bits in [1, 8, 16, 32, 64] %}
          test "int{{bits}}" do |abi, ctx|
            arg_type = ArgType.direct(ctx.int{{bits}})
            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(Crystal::ABI::ArgKind::Direct)
            info.return_type.should eq(arg_type)
            info.return_type.kind.should eq(Crystal::ABI::ArgKind::Direct)
          end
        {% end %}

        test "float" do |abi, ctx|
          arg_type = ArgType.direct(ctx.float)
          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(Crystal::ABI::ArgKind::Direct)
          info.return_type.should eq(arg_type)
          info.return_type.kind.should eq(Crystal::ABI::ArgKind::Direct)
        end

        test "double" do |abi, ctx|
          arg_type = ArgType.direct(ctx.double)
          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(Crystal::ABI::ArgKind::Direct)
          info.return_type.should eq(arg_type)
          info.return_type.kind.should eq(Crystal::ABI::ArgKind::Direct)
        end

        test "multiple arguments" do |abi, ctx|
          args = Array.new(9) { ctx.int16 }
          info = abi.abi_info(args, ctx.int8, false, ctx)
          info.arg_types.size.should eq(9)
          info.arg_types.each(&.kind.should eq(Crystal::ABI::ArgKind::Direct))
        end

        test "multiple arguments above registers" do |abi, ctx|
          args = Array.new(5) { ctx.int32 }
          info = abi.abi_info(args, ctx.int8, false, ctx)
          info.arg_types.size.should eq(5)
          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|
          args = [
            ctx.int8,                           # rounded to 2 bytes
            ctx.struct([ctx.int32, ctx.int32]), # 8 bytes
            ctx.struct([ctx.int32, ctx.int32]), # 8 bytes
          ]
          info = abi.abi_info(args, ctx.void, false, ctx)
          info.arg_types.size.should eq(3)
          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|
          args = [
            ctx.int32,                          # 4 bytes
            ctx.struct([ctx.int32, ctx.int32]), # 8 bytes
            ctx.struct([ctx.int32, ctx.int32]), # 8 bytes
          ]
          info = abi.abi_info(args, ctx.void, false, ctx)
          info.arg_types.size.should eq(3)
          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 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 LLVM::Type, rty, true, ctx)
          info.return_type.kind.should eq(Crystal::ABI::ArgKind::Indirect)
        end
      end
    {% end %}
  end
end
