// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#ifndef RUNTIME_VM_COMPILER_FFI_NATIVE_LOCATION_H_
#define RUNTIME_VM_COMPILER_FFI_NATIVE_LOCATION_H_

#if defined(DART_PRECOMPILED_RUNTIME)
#error "AOT runtime should not use compiler sources (including header files)"
#endif  // defined(DART_PRECOMPILED_RUNTIME)

#include "platform/assert.h"
#include "vm/compiler/ffi/native_type.h"
#include "vm/compiler/runtime_api.h"
#include "vm/constants.h"
#include "vm/growable_array.h"

#if !defined(FFI_UNIT_TESTS)
#include "vm/compiler/backend/locations.h"
#endif

namespace dart {

class BaseTextBuffer;

namespace compiler {

namespace ffi {

class NativeRegistersLocation;
class NativeFpuRegistersLocation;
class NativeStackLocation;
class MultipleNativeLocations;
class PointerToMemoryLocation;
class BothNativeLocations;

// NativeLocation objects are used in the FFI to describe argument and return
// value locations in all native ABIs that the FFI supports.
//
// NativeLocations contain two NativeTypes.
// * The payload type.
// * The container type, equal to or larger than the payload. If the
//   container is larger than the payload, the upper bits are defined by sign
//   or zero extension.
//   Container type is also used to denote an integer container when floating
//   point values are passed in integer registers.
//
// NativeLocations can express things that dart::Locations cannot express:
// * Multiple consecutive registers.
// * Multiple sizes of FPU registers (e.g. S, D, and Q on Arm32).
// * Arbitrary byte-size stack locations, at byte-size offsets.
//   (The Location class uses word-size offsets.)
// * Pointers to a memory location.
// * Split between multiple registers and stack.
//
// NativeLocations cannot express the following dart::Locations:
// * No PairLocations. Instead, NativeRegistersLocations can have multiple
//   registers, and NativeStackLocations can have arbitrary types.
// * No ConstantLocations.
//
// NativeLocation does not satisfy the invariant of Location: bitwise
// inequality cannot be used to determine disjointness.
class NativeLocation : public ZoneObject {
 public:
#if !defined(FFI_UNIT_TESTS)
  static bool LocationCanBeExpressed(Location loc, Representation rep);
  static NativeLocation& FromLocation(Zone* zone,
                                      Location loc,
                                      Representation rep);
  static NativeLocation& FromPairLocation(Zone* zone,
                                          Location loc,
                                          Representation rep,
                                          intptr_t index);
#endif

  // The type of the data at this location.
  const NativeType& payload_type() const { return payload_type_; }

  // The location container size, possibly larger than data.
  //
  // If the container is larger than the data, the remaining bits are _not_
  // undefined. For example a uint8 inside a uint32 has the upper 24 bits set
  // to 0. Effectively allowing the value to be read as uint8, uint16 and
  // uint32.
  const NativeType& container_type() const { return container_type_; }

  virtual NativeLocation& WithOtherNativeType(
      Zone* zone,
      const NativeType& new_payload_type,
      const NativeType& new_container_type) const = 0;

#if defined(TARGET_ARCH_ARM)
  const NativeLocation& WidenToQFpuRegister(Zone* zone) const;
#endif  // defined(TARGET_ARCH_ARM)

  NativeLocation& WidenTo4Bytes(Zone* zone) const;
  NativeLocation& WidenTo8Bytes(Zone* zone) const;

  virtual bool IsRegisters() const { return false; }
  virtual bool IsFpuRegisters() const { return false; }
  virtual bool IsStack() const { return false; }
  virtual bool IsMultiple() const { return false; }
  virtual bool IsPointerToMemory() const { return false; }
  virtual bool IsBoth() const { return false; }

  virtual bool IsExpressibleAsLocation() const { return false; }
#if !defined(FFI_UNIT_TESTS)
  virtual Location AsLocation() const {
    ASSERT(IsExpressibleAsLocation());
    UNREACHABLE_THIS();
  }
#endif

  virtual void PrintTo(BaseTextBuffer* f) const;
  const char* ToCString(Zone* zone) const;
#if !defined(FFI_UNIT_TESTS)
  const char* ToCString() const;
#endif

  const NativeRegistersLocation& AsRegisters() const;
  const NativeFpuRegistersLocation& AsFpuRegisters() const;
  const NativeStackLocation& AsStack() const;
  const MultipleNativeLocations& AsMultiple() const;
  const PointerToMemoryLocation& AsPointerToMemory() const;
  const BothNativeLocations& AsBoth() const;

  // Retrieve one part from this location when it is split into multiple parts.
  virtual NativeLocation& Split(Zone* zone,
                                intptr_t num_parts,
                                intptr_t index) const {
    UNREACHABLE_THIS();
  }

  // Return the top of the stack in bytes. Recurses over its constituents when
  // MultipleNativeLocations.
  virtual intptr_t StackTopInBytes() const { return 0; }

  // Equality of location, ignores the payload and container native types.
  virtual bool Equals(const NativeLocation& other) const { UNREACHABLE_THIS(); }

  virtual ~NativeLocation() {}

 protected:
  NativeLocation(const NativeType& payload_type,
                 const NativeType& container_type)
      : payload_type_(payload_type), container_type_(container_type) {}

 private:
  const NativeType& payload_type_;
  // The location container size, possibly larger than data.
  //
  // If the container is larger than the data, the remaining bits are _not_
  // undefined. For example a uint8 inside a uint32 has the upper 24 bits set
  // to 0. Effectively allowing the value to be read as uint8, uint16 and
  // uint32.
  const NativeType& container_type_;
};

class NativeRegistersLocation : public NativeLocation {
 public:
  NativeRegistersLocation(const NativeType& payload_type,
                          const NativeType& container_type,
                          ZoneGrowableArray<Register>* registers)
      : NativeLocation(payload_type, container_type), regs_(registers) {}
  NativeRegistersLocation(Zone* zone,
                          const NativeType& payload_type,
                          const NativeType& container_type,
                          Register reg)
      : NativeLocation(payload_type, container_type) {
    regs_ = new (zone) ZoneGrowableArray<Register>(zone, 1);
    regs_->Add(reg);
  }
  NativeRegistersLocation(Zone* zone,
                          const NativeType& payload_type,
                          const NativeType& container_type,
                          Register register1,
                          Register register2)
      : NativeLocation(payload_type, container_type) {
    regs_ = new (zone) ZoneGrowableArray<Register>(zone, 2);
    regs_->Add(register1);
    regs_->Add(register2);
  }
  virtual ~NativeRegistersLocation() {}

  virtual NativeRegistersLocation& WithOtherNativeType(
      Zone* zone,
      const NativeType& new_payload_type,
      const NativeType& new_container_type) const {
    return *new (zone)
        NativeRegistersLocation(new_payload_type, new_container_type, regs_);
  }

  virtual bool IsRegisters() const { return true; }
  virtual bool IsExpressibleAsLocation() const {
    return num_regs() == 1 || num_regs() == 2;
  }
#if !defined(FFI_UNIT_TESTS)
  virtual Location AsLocation() const;
#endif
  intptr_t num_regs() const { return regs_->length(); }
  Register reg_at(intptr_t index) const { return regs_->At(index); }

  virtual NativeRegistersLocation& Split(Zone* zone,
                                         intptr_t num_parts,
                                         intptr_t index) const;

  virtual void PrintTo(BaseTextBuffer* f) const;

  virtual bool Equals(const NativeLocation& other) const;

 private:
  ZoneGrowableArray<Register>* regs_;

  DISALLOW_COPY_AND_ASSIGN(NativeRegistersLocation);
};

enum FpuRegisterKind {
  kQuadFpuReg,    // 16 bytes
  kDoubleFpuReg,  //  8 bytes, a double
  kSingleFpuReg   //  4 bytes, a float
};

intptr_t SizeFromFpuRegisterKind(FpuRegisterKind kind);
FpuRegisterKind FpuRegisterKindFromSize(intptr_t size_in_bytes);

class NativeFpuRegistersLocation : public NativeLocation {
 public:
  NativeFpuRegistersLocation(const NativeType& payload_type,
                             const NativeType& container_type,
                             FpuRegisterKind fpu_reg_kind,
                             intptr_t fpu_register)
      : NativeLocation(payload_type, container_type),
        fpu_reg_kind_(fpu_reg_kind),
        fpu_reg_(fpu_register) {}
  NativeFpuRegistersLocation(const NativeType& payload_type,
                             const NativeType& container_type,
                             FpuRegister fpu_register)
      : NativeLocation(payload_type, container_type),
        fpu_reg_kind_(kQuadFpuReg),
        fpu_reg_(fpu_register) {}
#if defined(TARGET_ARCH_ARM)
  NativeFpuRegistersLocation(const NativeType& payload_type,
                             const NativeType& container_type,
                             DRegister fpu_register)
      : NativeLocation(payload_type, container_type),
        fpu_reg_kind_(kDoubleFpuReg),
        fpu_reg_(fpu_register) {}
  NativeFpuRegistersLocation(const NativeType& payload_type,
                             const NativeType& container_type,
                             SRegister fpu_register)
      : NativeLocation(payload_type, container_type),
        fpu_reg_kind_(kSingleFpuReg),
        fpu_reg_(fpu_register) {}
#endif  // defined(TARGET_ARCH_ARM)
  virtual ~NativeFpuRegistersLocation() {}

  virtual NativeFpuRegistersLocation& WithOtherNativeType(
      Zone* zone,
      const NativeType& new_payload_type,
      const NativeType& new_container_type) const {
    return *new (zone) NativeFpuRegistersLocation(
        new_payload_type, new_container_type, fpu_reg_kind_, fpu_reg_);
  }
  virtual bool IsFpuRegisters() const { return true; }
  virtual bool IsExpressibleAsLocation() const {
    return fpu_reg_kind_ == kQuadFpuReg;
  }
#if !defined(FFI_UNIT_TESTS)
  virtual Location AsLocation() const {
    ASSERT(IsExpressibleAsLocation());
    return Location::FpuRegisterLocation(fpu_reg());
  }
#endif
  FpuRegisterKind fpu_reg_kind() const { return fpu_reg_kind_; }
  FpuRegister fpu_reg() const {
    ASSERT(fpu_reg_kind_ == kQuadFpuReg);
    return static_cast<FpuRegister>(fpu_reg_);
  }
#if defined(TARGET_ARCH_ARM)
  DRegister fpu_d_reg() const {
    ASSERT(fpu_reg_kind_ == kDoubleFpuReg);
    return static_cast<DRegister>(fpu_reg_);
  }
  SRegister fpu_s_reg() const {
    ASSERT(fpu_reg_kind_ == kSingleFpuReg);
    return static_cast<SRegister>(fpu_reg_);
  }
  DRegister fpu_as_d_reg() const;
  SRegister fpu_as_s_reg() const;

  bool IsLowestBits() const;
#endif  // defined(TARGET_ARCH_ARM)

  virtual void PrintTo(BaseTextBuffer* f) const;

  virtual bool Equals(const NativeLocation& other) const;

 private:
  FpuRegisterKind fpu_reg_kind_;
  intptr_t fpu_reg_;
  DISALLOW_COPY_AND_ASSIGN(NativeFpuRegistersLocation);
};

class NativeStackLocation : public NativeLocation {
 public:
  NativeStackLocation(const NativeType& payload_type,
                      const NativeType& container_type,
                      Register base_register,
                      intptr_t offset_in_bytes)
      : NativeLocation(payload_type, container_type),
        base_register_(base_register),
        offset_in_bytes_(offset_in_bytes) {}
  virtual ~NativeStackLocation() {}

  virtual NativeStackLocation& WithOtherNativeType(
      Zone* zone,
      const NativeType& new_payload_type,
      const NativeType& new_container_type) const {
    return *new (zone) NativeStackLocation(new_payload_type, new_container_type,
                                           base_register_, offset_in_bytes_);
  }

  virtual bool IsStack() const { return true; }
  virtual bool IsExpressibleAsLocation() const {
    const intptr_t size = payload_type().SizeInBytes();
    const intptr_t size_slots = size / compiler::target::kWordSize;
    return offset_in_bytes_ % compiler::target::kWordSize == 0 &&
           size % compiler::target::kWordSize == 0 &&
           (size_slots == 1 || size_slots == 2);
  }

#if !defined(FFI_UNIT_TESTS)
  virtual Location AsLocation() const;

  // ConstantInstr expects DoubleStackSlot for doubles, even on 64-bit systems.
  //
  // So this return a wrong-sized Location on purpose.
  Location AsDoubleStackSlotLocation() const {
    ASSERT(compiler::target::kWordSize == 8);
    return Location::DoubleStackSlot(offset_in_words(), base_register_);
  }
#endif

  virtual NativeStackLocation& Split(Zone* zone,
                                     intptr_t num_parts,
                                     intptr_t index) const;

  virtual intptr_t StackTopInBytes() const {
    return offset_in_bytes() + container_type().SizeInBytes();
  }

  virtual void PrintTo(BaseTextBuffer* f) const;

  virtual bool Equals(const NativeLocation& other) const;

  Register base_register() const { return base_register_; }
  intptr_t offset_in_bytes() const { return offset_in_bytes_; }

 private:
  intptr_t offset_in_words() const {
    ASSERT(offset_in_bytes_ % compiler::target::kWordSize == 0);
    return offset_in_bytes_ / compiler::target::kWordSize;
  }

  Register base_register_;
  intptr_t offset_in_bytes_;

  DISALLOW_COPY_AND_ASSIGN(NativeStackLocation);
};

// The location of a pointer pointing to a compound.
//
// For arguments a pointer to a copy of an object. The backing copy of the
// object typically resides on the stack.
//
// For return values a pointer to empty space that should hold the object. This
// space also typically resides on the stack.
class PointerToMemoryLocation : public NativeLocation {
 public:
  PointerToMemoryLocation(const NativeLocation& pointer_location,
                          const NativeCompoundType& object_pointed_to)
      : NativeLocation(object_pointed_to, object_pointed_to),
        pointer_location_(pointer_location),
        pointer_return_location_(pointer_location) {
    ASSERT(pointer_location.IsRegisters() || pointer_location.IsStack());
  }
  PointerToMemoryLocation(const NativeLocation& pointer_location,
                          const NativeLocation& pointer_return_location,
                          const NativeCompoundType& object_pointed_to)
      : NativeLocation(object_pointed_to, object_pointed_to),
        pointer_location_(pointer_location),
        pointer_return_location_(pointer_return_location) {
    ASSERT(pointer_location.IsRegisters() || pointer_location.IsStack());
  }

  virtual ~PointerToMemoryLocation() {}

  virtual bool IsPointerToMemory() const { return true; }

  virtual void PrintTo(BaseTextBuffer* f) const;

  virtual bool Equals(const NativeLocation& other) const;

  virtual NativeLocation& WithOtherNativeType(
      Zone* zone,
      const NativeType& new_payload_type,
      const NativeType& new_container_type) const {
    UNREACHABLE_THIS();
  }

  virtual intptr_t StackTopInBytes() const {
    return pointer_location().StackTopInBytes();
  }

  // The location where the pointer is passed to the function.
  const NativeLocation& pointer_location() const { return pointer_location_; }

  // The location where the pointer is returned from the function.
  const NativeLocation& pointer_return_location() const {
    return pointer_return_location_;
  }

 private:
  const NativeLocation& pointer_location_;
  // The return location is only in use for return values, not for arguments.
  const NativeLocation& pointer_return_location_;

  DISALLOW_COPY_AND_ASSIGN(PointerToMemoryLocation);
};

using NativeLocations = ZoneGrowableArray<const NativeLocation*>;

// A struct broken up over multiple native locations.
class MultipleNativeLocations : public NativeLocation {
 public:
  MultipleNativeLocations(const NativeCompoundType& payload_type,
                          const NativeLocations& locations)
      : NativeLocation(payload_type, payload_type), locations_(locations) {}
  virtual ~MultipleNativeLocations() {}

  virtual bool IsMultiple() const { return true; }

  virtual void PrintTo(BaseTextBuffer* f) const;

  virtual NativeLocation& WithOtherNativeType(
      Zone* zone,
      const NativeType& new_payload_type,
      const NativeType& new_container_type) const {
    UNREACHABLE_THIS();
  }

  virtual intptr_t StackTopInBytes() const;

  const NativeLocations& locations() const { return locations_; }

 private:
  const NativeLocations& locations_;
  DISALLOW_COPY_AND_ASSIGN(MultipleNativeLocations);
};

// The location of a value that is in two locations.
//
// Should only happen on win_x64 with variadic arguments.
class BothNativeLocations : public NativeLocation {
 public:
  BothNativeLocations(const NativeLocation& location0,
                      const NativeLocation& location1)
      : NativeLocation(location0.payload_type(), location0.container_type()),
        location0_(location0),
        location1_(location1) {}
  virtual ~BothNativeLocations() {}

  virtual bool IsBoth() const { return true; }

  virtual void PrintTo(BaseTextBuffer* f) const;

  virtual NativeLocation& WithOtherNativeType(
      Zone* zone,
      const NativeType& new_payload_type,
      const NativeType& new_container_type) const {
    UNREACHABLE_THIS();
  }

  virtual intptr_t StackTopInBytes() const {
    // Only used with registers.
    return 0;
  }

  const NativeLocation& location(intptr_t index) const {
    ASSERT(index == 0 || index == 1);
    if (index == 0) {
      return location0_;
    }
    return location1_;
  }

 private:
  const NativeLocation& location0_;
  const NativeLocation& location1_;
  DISALLOW_COPY_AND_ASSIGN(BothNativeLocations);
};

#if !defined(FFI_UNIT_TESTS)
// Return a memory operand for stack slot locations.
compiler::Address NativeLocationToStackSlotAddress(
    const NativeStackLocation& loc);
#endif

}  // namespace ffi

}  // namespace compiler

}  // namespace dart

#endif  // RUNTIME_VM_COMPILER_FFI_NATIVE_LOCATION_H_
