// 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.

#include "vm/compiler/ffi/callback.h"

#include "vm/canonical_tables.h"
#include "vm/class_finalizer.h"
#include "vm/object_store.h"
#include "vm/symbols.h"

namespace dart {

namespace compiler {

namespace ffi {

const String& NativeCallbackFunctionName(Thread* thread,
                                         Zone* zone,
                                         const Function& dart_target,
                                         FfiCallbackKind kind) {
  switch (kind) {
    case FfiCallbackKind::kAsyncCallback:
      return Symbols::FfiAsyncCallback();
    case FfiCallbackKind::kIsolateLocalClosureCallback:
      return Symbols::FfiIsolateLocalCallback();
    case FfiCallbackKind::kIsolateLocalStaticCallback:
      return String::Handle(
          zone, Symbols::FromConcat(thread, Symbols::FfiCallback(),
                                    String::Handle(zone, dart_target.name())));
    case FfiCallbackKind::kIsolateGroupBoundClosureCallback:
      return Symbols::FfiIsolateGroupBoundCallback();
    case FfiCallbackKind::kIsolateGroupBoundStaticCallback:
      return String::Handle(
          zone, Symbols::FromConcat(thread, Symbols::FfiCallback(),
                                    String::Handle(zone, dart_target.name())));
    default:
      UNREACHABLE();
  }
}

FunctionPtr NativeCallbackFunction(const FunctionType& c_signature,
                                   const Function& dart_target,
                                   const Instance& exceptional_return,
                                   FfiCallbackKind kind) {
  Thread* const thread = Thread::Current();
  Zone* const zone = thread->zone();
  Function& function = Function::Handle(zone);
  ASSERT(c_signature.IsCanonical());
  ASSERT(exceptional_return.IsSmi() || exceptional_return.IsCanonical());

  // Create a new Function named '<target>_FfiCallback' and stick it in the
  // 'dart:ffi' library. Note that these functions will never be invoked by
  // Dart, so they may have duplicate names.
  const auto& name =
      NativeCallbackFunctionName(thread, zone, dart_target, kind);
  const Library& lib = Library::Handle(zone, Library::FfiLibrary());
  const Class& owner_class = Class::Handle(zone, lib.toplevel_class());
  auto& signature = FunctionType::Handle(zone, FunctionType::New());
  function =
      Function::New(signature, name, UntaggedFunction::kFfiTrampoline,
                    /*is_static=*/true,
                    /*is_const=*/false,
                    /*is_abstract=*/false,
                    /*is_external=*/false,
                    /*is_native=*/false, owner_class, TokenPosition::kNoSource);
  function.set_is_debuggable(false);

  // Set callback-specific fields which the flow-graph builder needs to generate
  // the body.
  function.SetFfiCSignature(c_signature);
  function.SetFfiCallbackTarget(dart_target);
  function.SetFfiCallbackKind(kind);

  // We need to load the exceptional return value as a constant in the generated
  // function. Even though the FE ensures that it is a constant, it could still
  // be a literal allocated in new space. We need to copy it into old space in
  // that case.
  //
  // Exceptional return values currently cannot be pointers because we don't
  // have constant pointers.
  ASSERT(exceptional_return.IsNull() || exceptional_return.IsNumber() ||
         exceptional_return.IsBool());
  if (!exceptional_return.IsSmi() && exceptional_return.IsNew()) {
    function.SetFfiCallbackExceptionalReturn(Instance::Handle(
        zone, exceptional_return.CopyShallowToOldSpace(thread)));
  } else {
    function.SetFfiCallbackExceptionalReturn(exceptional_return);
  }

  // The dart type of the FfiCallback has no arguments or type arguments and
  // has a result type of dynamic, as the callback is never invoked via Dart,
  // only via native calls that do not use this information. Having no Dart
  // arguments ensures the scope builder does not add inappropriate parameter
  // variables.
  signature.set_result_type(Object::dynamic_type());
  // Finalize (and thus canonicalize) the signature.
  signature ^= ClassFinalizer::FinalizeType(signature);
  function.SetSignature(signature);

  {
    // Ensure only one thread updates the cache of deduped ffi trampoline
    // functions.
    auto isolate_group = thread->isolate_group();
    SafepointWriteRwLocker ml(thread, isolate_group->program_lock());

    auto object_store = isolate_group->object_store();
    if (object_store->ffi_callback_functions() == Array::null()) {
      FfiCallbackFunctionSet set(
          HashTables::New<FfiCallbackFunctionSet>(/*initial_capacity=*/4));
      object_store->set_ffi_callback_functions(set.Release());
    }
    FfiCallbackFunctionSet set(object_store->ffi_callback_functions());

    const intptr_t entry_count_before = set.NumOccupied();
    function ^= set.InsertOrGet(function);
    const intptr_t entry_count_after = set.NumOccupied();

    object_store->set_ffi_callback_functions(set.Release());

    if (entry_count_before != entry_count_after) {
      function.AssignFfiCallbackId(entry_count_before);
    } else {
      ASSERT(function.FfiCallbackId() != -1);
    }
  }

  return function.ptr();
}

static void EnsureFfiCallbackMetadata(Thread* thread, intptr_t callback_id) {
  static constexpr intptr_t kInitialCallbackIdsReserved = 16;

  auto object_store = thread->isolate_group()->object_store();
  auto zone = thread->zone();

  auto& code_array =
      GrowableObjectArray::Handle(zone, object_store->ffi_callback_code());
  if (code_array.IsNull()) {
    code_array =
        GrowableObjectArray::New(kInitialCallbackIdsReserved, Heap::kOld);
    object_store->set_ffi_callback_code(code_array);
  }
  if (code_array.Length() <= callback_id) {
    // Ensure we've enough space in the arrays.
    while (!(callback_id < code_array.Length())) {
      code_array.Add(Code::null_object());
    }
  }

  ASSERT(callback_id < code_array.Length());
}

void SetFfiCallbackCode(Thread* thread,
                        const Function& ffi_trampoline,
                        const Code& code) {
  auto zone = thread->zone();

  const intptr_t callback_id = ffi_trampoline.FfiCallbackId();
  EnsureFfiCallbackMetadata(thread, callback_id);

  auto object_store = thread->isolate_group()->object_store();
  const auto& code_array =
      GrowableObjectArray::Handle(zone, object_store->ffi_callback_code());
  code_array.SetAt(callback_id, code);
}

}  // namespace ffi

}  // namespace compiler

}  // namespace dart
