// 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_MARSHALLER_H_
#define RUNTIME_VM_COMPILER_FFI_MARSHALLER_H_

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

#include <platform/globals.h>

#include "vm/compiler/backend/locations.h"
#include "vm/compiler/ffi/callback.h"
#include "vm/compiler/ffi/native_calling_convention.h"
#include "vm/compiler/ffi/native_location.h"
#include "vm/compiler/ffi/native_type.h"
#include "vm/object.h"

namespace dart {

namespace compiler {

namespace ffi {

// Values below 0 index result (result might be multiple if composite).
const intptr_t kResultIndex = -1;

// Inspects the function signature and transitively any class and field
// definitions and annotations.
const NativeFunctionType* NativeFunctionTypeFromFunctionType(
    Zone* zone,
    const FunctionType& c_signature,
    const char** error);

// Provides the mapping from the native calling convention to the Dart calling
// convention.
//
// This class is set up in a query-able way so that it's underlying logic can
// be extended to support more native ABI features and calling conventions.
class BaseMarshaller : public ZoneObject {
 public:
  intptr_t num_args() const {
    return native_calling_convention_.argument_locations().length();
  }

  // Number of definitions passed to FfiCall, number of NativeParams, or number
  // of definitions passed to NativeReturn in IL.
  //
  // All non-struct values have 1 definition, struct values can have either 1
  // or multiple definitions. If a struct has multiple definitions, they either
  // correspond to the number of native locations in the native ABI or to word-
  // sized chunks.
  //
  // `arg_index` is the index of an argument.
  // `def_index_in_argument` is the definition in one argument.
  // `def_index_global` is the index of the definition in all arguments.
  intptr_t NumArgumentDefinitions() const;
  virtual intptr_t NumDefinitions(intptr_t arg_index) const;
  virtual intptr_t NumReturnDefinitions() const = 0;
  bool ArgumentIndexIsReturn(intptr_t arg_index) const;
  bool DefinitionIndexIsReturn(intptr_t def_index_global) const;
  intptr_t ArgumentIndex(intptr_t def_index_global) const;
  intptr_t FirstDefinitionIndex(intptr_t arg_index) const;
  intptr_t DefinitionInArgument(intptr_t def_index_global,
                                intptr_t arg_index) const;
  intptr_t DefinitionIndex(intptr_t def_index_in_arg, intptr_t arg_index) const;

  // The location of the argument at `arg_index`.
  const NativeLocation& Location(intptr_t arg_index) const {
    if (arg_index == kResultIndex) {
      return native_calling_convention_.return_location();
    }
    return *native_calling_convention_.argument_locations().At(arg_index);
  }

  // Unboxed representation on how the value is passed or received from regular
  // Dart code.
  //
  // Implemented in BaseMarshaller because most Representations are the same
  // in Calls and Callbacks.
  Representation RepInDart(intptr_t arg_index) const;

  // Representation on how the value is passed to or received from the FfiCall
  // instruction or StaticCall, NativeParameter, and NativeReturn instructions.
  virtual Representation RepInFfiCall(intptr_t def_index_global) const;
  void RepsInFfiCall(intptr_t arg_index,
                     GrowableArray<Representation>* out) const;

  // Bitcasting floats to ints, only required in SoftFP.
  bool RequiresBitCast(intptr_t index) const {
    return Location(index).payload_type().IsFloat() &&
           Location(index).container_type().IsInt();
  }

  // 8 or 16 bit int value to sign extend from.
  const NativeType& SignExtendFrom(intptr_t arg_index) const {
    return Location(arg_index).payload_type();
  }

  // The C Type (expressed in a Dart Type) of the argument at `arg_index`.
  //
  // Excluding the #0 argument which is the function pointer.
  //
  // Recurses into VarArgs if needed.
  AbstractTypePtr CType(intptr_t arg_index) const;

  AbstractTypePtr DartType(intptr_t arg_index) const;

 protected:
  bool IsPointerDartType(intptr_t arg_index) const;
  bool IsPointerCType(intptr_t arg_index) const;

 public:
  // The Dart and C Type is Pointer.
  //
  // Requires boxing or unboxing the Pointer object to int.
  bool IsPointerPointer(intptr_t arg_index) const;

  // The Dart type is TypedData and the C type is Pointer.
  //
  // Requires passing the typed data base in as tagged pointer.
  //
  // TODO(https://dartbug.com/55444): The typed data address load could be
  // done in IL.
  bool IsTypedDataPointer(intptr_t arg_index) const;

  // The Dart type is a compound (for example an Array or a TypedData+offset),
  // and the C type is Pointer.
  //
  // Requires passing in two definitions in IL: TypedDataBase + offset.
  //
  // TODO(https://dartbug.com/55444): The typed data address load could be
  // done in IL.
  bool IsCompoundPointer(intptr_t arg_index) const;

  // The C type is Handle, the Dart type can be anything.
  //
  // Requires passing the pointer to the Dart object in a handle.
  bool IsHandleCType(intptr_t arg_index) const;

  // The Dart and C Types are boolean.
  //
  // Requires converting the boolean into an int in IL.
  bool IsBool(intptr_t arg_index) const;

  // The Dart and C Types are compound (pass by value).
  bool IsCompoundCType(intptr_t arg_index) const;

  // Treated as a null constant in Dart.
  bool IsVoid(intptr_t arg_index) const {
    return AbstractType::Handle(zone_, CType(arg_index)).type_class_id() ==
           kFfiVoidCid;
  }

  bool ContainsHandles() const;

  bool contains_varargs() const {
    return native_calling_convention_.contains_varargs();
  }

  // Note that the Dart arguments are indexed starting at
  // `dart_signature_params_start_at()`.
  //
  // Closures created by `asFunction` have the pointer as first parameter.
  // `@Native`s don't have an implicit first parameter.
  const Function& dart_signature() const { return dart_signature_; }
  intptr_t dart_signature_params_start_at() const {
    return dart_signature_params_start_at_;
  }
  const FunctionType& c_signature() const { return c_signature_; }
  StringPtr function_name() const { return dart_signature_.name(); }

 protected:
  BaseMarshaller(Zone* zone,
                 const Function& dart_signature,
                 intptr_t dart_signature_params_start_at,
                 const FunctionType& c_signature,
                 const NativeCallingConvention& native_calling_convention)
      : zone_(zone),
        dart_signature_(dart_signature),
        dart_signature_params_start_at_(dart_signature_params_start_at),
        c_signature_(c_signature),
        native_calling_convention_(native_calling_convention) {}

  ~BaseMarshaller() {}

  Zone* zone_;
  // Contains the function pointer as argument #0.
  // The Dart signature is used for the function and argument names.
  const Function& dart_signature_;
  const intptr_t dart_signature_params_start_at_;
  const FunctionType& c_signature_;
  const NativeCallingConvention& native_calling_convention_;
};

class CallMarshaller : public BaseMarshaller {
 public:
  static CallMarshaller* FromFunction(Zone* zone,
                                      const Function& function,
                                      intptr_t function_params_start_at,
                                      const FunctionType& c_signature,
                                      const char** error);

  CallMarshaller(Zone* zone,
                 const Function& dart_signature,
                 intptr_t dart_signature_params_start_at,
                 const FunctionType& c_signature,
                 const NativeCallingConvention& native_calling_convention)
      : BaseMarshaller(zone,
                       dart_signature,
                       dart_signature_params_start_at,
                       c_signature,
                       native_calling_convention) {}

  virtual intptr_t NumDefinitions(intptr_t arg_index) const;
  virtual intptr_t NumReturnDefinitions() const;

  virtual Representation RepInFfiCall(intptr_t def_index_global) const;

  // The location of the inputs to the IL FfiCall instruction.
  dart::Location LocInFfiCall(intptr_t def_index_global) const;

  // Allocate a TypedData before the FfiCall and pass it into the FfiCall so
  // that it can be populated in assembly.
  bool ReturnsCompound() const;
  intptr_t CompoundReturnSizeInBytes() const;

  // We allocate space for PointerToMemory arguments and PointerToMemory return
  // locations on the stack. This is faster than allocation ExternalTypedData.
  // Normal TypedData is not an option, as these might be relocated by GC
  // during FFI calls.
  intptr_t PassByPointerStackOffset(intptr_t arg_index) const;

  // The total amount of stack space required for FFI trampolines.
  intptr_t RequiredStackSpaceInBytes() const;

 protected:
  ~CallMarshaller() {}
};

class CallbackMarshaller : public BaseMarshaller {
 public:
  static CallbackMarshaller* FromFunction(Zone* zone,
                                          const Function& function,
                                          const char** error);

  CallbackMarshaller(Zone* zone,
                     const Function& dart_signature,
                     const FunctionType& c_signature,
                     const NativeCallingConvention& native_calling_convention,
                     const NativeLocations& callback_locs)
      : BaseMarshaller(zone,
                       dart_signature,
                       /*dart_signature_params_start_at=*/0,
                       c_signature,
                       native_calling_convention),
        callback_locs_(callback_locs) {}

  virtual Representation RepInFfiCall(intptr_t def_index_global) const;

  virtual intptr_t NumDefinitions(intptr_t arg_index) const;
  virtual intptr_t NumReturnDefinitions() const;

  // All parameters are saved on stack to do safe-point transition.
  const NativeLocation& NativeLocationOfNativeParameter(
      intptr_t def_index) const;

  // All parameters are saved on stack to do safe-point transition.
  dart::Location LocationOfNativeParameter(intptr_t def_index) const {
    const auto& native_loc = NativeLocationOfNativeParameter(def_index);
    if (native_loc.IsPointerToMemory()) {
      return native_loc.AsPointerToMemory().pointer_location().AsLocation();
    }
    return native_loc.AsLocation();
  }

 protected:
  ~CallbackMarshaller() {}

  const NativeLocations& callback_locs_;
};

}  // namespace ffi

}  // namespace compiler

}  // namespace dart

#endif  // RUNTIME_VM_COMPILER_FFI_MARSHALLER_H_
