/*
 * Copyright 2010-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
 * that can be found in the LICENSE file.
 */

#include "ExternalRCRef.hpp"

#include <atomic>
#include <optional>

#include "CompilerConstants.hpp"
#include "ExternalRCRefRegistry.hpp"
#include "GC.hpp"
#include "KAssert.h"
#include "Memory.h"
#include "PointerBits.h"
#include "ReferenceOps.hpp"
#include "ThreadData.hpp"
#include "ThreadRegistry.hpp"

using namespace kotlin;

// Implementation of kotlin.native.internal.ref.ExternalRCRef

namespace {

constexpr unsigned kPermanentTag = 1;

}

RUNTIME_NOTHROW extern "C" mm::RawExternalRCRef* Kotlin_native_internal_ref_createRetainedExternalRCRef(KRef obj) {
    return mm::createRetainedExternalRCRef(obj);
}

RUNTIME_NOTHROW extern "C" mm::RawExternalRCRef* Kotlin_native_internal_ref_createUnretainedExternalRCRef(KRef obj) {
    return mm::createUnretainedExternalRCRef(obj);
}

RUNTIME_NOTHROW extern "C" void Kotlin_native_internal_ref_disposeExternalRCRef(mm::RawExternalRCRef* ref) {
    return mm::disposeExternalRCRef(ref);
}

RUNTIME_NOTHROW extern "C" OBJ_GETTER(Kotlin_native_internal_ref_dereferenceExternalRCRef, mm::RawExternalRCRef* ref) {
    RETURN_OBJ(mm::dereferenceExternalRCRef(ref));
}

RUNTIME_NOTHROW extern "C" void Kotlin_native_internal_ref_retainExternalRCRef(mm::RawExternalRCRef* ref) {
    mm::retainExternalRCRef(ref);
}

RUNTIME_NOTHROW extern "C" void Kotlin_native_internal_ref_releaseExternalRCRef(mm::RawExternalRCRef* ref) {
    mm::releaseExternalRCRef(ref);
}

RUNTIME_NOTHROW extern "C" OBJ_GETTER(Kotlin_native_internal_ref_dereferenceExternalRCRefOrNull, mm::RawExternalRCRef* ref) {
    RETURN_RESULT_OF(mm::tryRefExternalRCRef, ref);
}

RUNTIME_NOTHROW extern "C" mm::RawExternalRCRef* Kotlin_mm_createRetainedExternalRCRef(KRef obj) {
    return mm::createRetainedExternalRCRef(obj);
}

RUNTIME_NOTHROW extern "C" void Kotlin_mm_releaseExternalRCRef(mm::RawExternalRCRef* ref) {
    mm::releaseExternalRCRef(ref);
}

RUNTIME_NOTHROW extern "C" void Kotlin_mm_disposeExternalRCRef(mm::RawExternalRCRef* ref) {
    mm::disposeExternalRCRef(ref);
}

mm::ExternalRCRefImpl::ExternalRCRefImpl(mm::ExternalRCRefRegistry& registry, KRef obj, Rc rc) noexcept : obj_(obj), rc_(rc) {
    RuntimeAssert(obj != nullptr, "Creating ExternalRCRefImpl for null object");
    RuntimeAssert(rc >= 0, "Creating ExternalRCRefImpl with negative rc %d", rc);
    // Runtime tests occasionally use sentinel values under 8 for opaque objects
    RuntimeAssert(reinterpret_cast<uintptr_t>(obj) < 8u || !obj->stack(), "Creating ExternalRCRefImpl to a stack-allocated object %p", obj);

    if (rc > 0) {
        registry.insertIntoRootsHead(*this);
    }
}

mm::ExternalRCRefImpl::~ExternalRCRefImpl() {
    if (compiler::runtimeAssertsEnabled()) {
        auto rc = rc_.load(std::memory_order_relaxed);
        RuntimeAssert(rc == disposedMarker, "Deleting ExternalRCRefImpl@%p with rc %d", this, rc);
    }
}

// static
mm::ExternalRCRefImpl& mm::ExternalRCRefImpl::create(KRef obj, Rc rc) noexcept {
    return mm::ThreadRegistry::Instance().CurrentThreadData()->externalRCRefRegistry().createExternalRCRefImpl(obj, rc);
}

void mm::ExternalRCRefImpl::dispose() noexcept {
    // Synchronization with `ExternalRCRefRegistry::findAliveNode()`.
    // TODO: When assertions are disabled, exchange may pollute the
    //       generated assembly. Check if this a problem.
    auto rc = rc_.exchange(disposedMarker, std::memory_order_release);
    if (compiler::runtimeAssertsEnabled()) {
        if (rc > 0) {
            auto* obj = objAtomic().load(std::memory_order_relaxed);
            // In objc export if ObjCClass extends from KtClass
            // doing retain+autorelease inside [ObjCClass dealloc] will cause
            // this->dispose() be called after this->retain() but before
            // subsequent this->release().
            // However, since this happens in dealloc, the stored object must
            // have been cleared already.
            RuntimeAssert(obj == nullptr, "Disposing ExternalRCRefImpl@%p with rc %d and uncleaned object %p", this, rc, obj);
        }
        RuntimeAssert(rc >= 0, "Disposing ExternalRCRefImpl@%p with rc %d", this, rc);
    }
}

KRef mm::ExternalRCRefImpl::ref() const noexcept {
    if (compiler::runtimeAssertsEnabled()) {
        AssertThreadState(ThreadState::kRunnable);
        auto rc = rc_.load(std::memory_order_relaxed);
        RuntimeAssert(rc >= 0, "Dereferencing ExternalRCRefImpl@%p with rc %d", this, rc);
    }
    return objAtomic().load(std::memory_order_relaxed);
}

const TypeInfo* mm::ExternalRCRefImpl::typeInfo() const noexcept {
    if (compiler::runtimeAssertsEnabled()) {
        auto rc = rc_.load(std::memory_order_relaxed);
        RuntimeAssert(rc >= 0, "Getting TypeInfo from ExternalRCRefImpl@%p with rc %d", this, rc);
    }
    auto* obj = objAtomic().load(std::memory_order_relaxed);
    RuntimeAssert(obj, "Getting TypeInfo from ExternalRCRefImpl@%p that already has nulled-out object", this);
    return obj->type_info();
}

OBJ_GETTER0(mm::ExternalRCRefImpl::tryRef) noexcept {
    AssertThreadState(ThreadState::kRunnable);
    RETURN_RESULT_OF(mm::weakRefReadBarrier, objAtomic());
}

void mm::ExternalRCRefImpl::retainRef() noexcept {
    auto rc = rc_.fetch_add(1, std::memory_order_relaxed);
    RuntimeAssert(rc >= 0, "Retaining ExternalRCRefImpl@%p with rc %d", this, rc);
    if (rc == 0) {
        if (!objAtomic().load(std::memory_order_relaxed)) {
            // In objc export if ObjCClass extends from KtClass
            // calling retain inside [ObjCClass dealloc] will cause
            // node.retainRef() be called after node.obj_ was cleared but
            // before node.dispose().
            // We could place it into the root set, and it'll be removed
            // from it at some later point. But let's just skip it.
            return;
        }

        // With the current CMS implementation no barrier is required here.
        // The CMS builds Snapshot-at-the-beginning mark closure,
        // which means, it has to remember only the concurrent deletion of references, not creation.
        // TODO: A write-into-root-set barrier might be required here for other concurrent mark strategies.

        // In case of non-concurrent root set scanning, it is only required for the object to already be in roots.
        // If 0->1 happened from `[ObjCClass _tryRetain]`, it would first hold the object
        // on the stack via `tryRef`.
        // If 0->1 happened during construction:
        // * First of all, currently it's impossible because the `Node` is created with rc=1 and not inserted
        //   into the roots list until publishing.
        // * Even if the above changes, for the construction, the object must have been passed in from somewhere,
        //   so it must be reachable anyway.
        // If 0->1 happened because an object is passing through the interop border for the second time (or more)
        // (e.g. accessing a non-permanent global a couple of times). Follows the construction case above:
        // "the object must have been passed in from somewhere, so it must be reachable anyway".

        // 0->1 changes require putting this node into the root set.
        ExternalRCRefRegistry::instance().insertIntoRootsHead(*this);
    }
}

void mm::ExternalRCRefImpl::releaseRef() noexcept {
    if (gc::barriers::ExternalRCRefReleaseGuard::isNoop()) {
        auto rcBefore = rc_.fetch_sub(1, std::memory_order_relaxed);
        RuntimeAssert(
                rcBefore > 0, "Releasing ExternalRCRefImpl@%p(%p %s) with rc %d", this, obj_, obj_->type_info()->fqName().c_str(),
                rcBefore);
    } else {
        // A 1->0 release is potentially a removal from global root set.
        // The CMS GC scans global root set concurrently. A guard is required.
        auto rcBefore = rc_.load(std::memory_order_relaxed);
        while (true) {
            std::optional<gc::barriers::ExternalRCRefReleaseGuard> guard;
            if (rcBefore == 1) {
                // The guard is only required in case of the last reference release (0->1).
                // We avoid it in all other cases, as the guard can be quite an overhead: e.g. taking the GC lock.
                // We also drop the guard if CAS below fails and we retry. This way, the GC will be allowed to take the lock
                // sooner. This does, however, hurt a thread that failed to decrement, because it may have to wait for the GC.
                guard = gc::barriers::ExternalRCRefReleaseGuard{mm::DirectRefAccessor{obj_}};
            }
            if (rc_.compare_exchange_strong(rcBefore, rcBefore - 1, std::memory_order_relaxed)) break;
        }

        RuntimeAssert(
                rcBefore > 0, "Releasing ExternalRCRefImpl@%p(%p %s) with rc %d", this, obj_, obj_->type_info()->fqName().c_str(),
                rcBefore);
    }
}

// static
const mm::ExternalRCRefImpl* mm::ExternalRCRefImpl::fromRaw(const RawExternalRCRef* ref) noexcept {
    auto* obj = mm::externalRCRefAsPermanentObject(ref);
    RuntimeAssert(obj == nullptr, "Trying to convert permanent ExternalRCRef (for %p) into ExternalRCRefImpl", obj);
    return reinterpret_cast<const mm::ExternalRCRefImpl*>(ref);
}

KRef kotlin::mm::externalRCRefAsPermanentObject(const RawExternalRCRef* ref) noexcept {
    RuntimeAssert(ref != nullptr, "Cannot handle nullptr");
    if (hasPointerBits(ref, kPermanentTag)) {
        auto obj = clearPointerBits(reinterpret_cast<KConstRef>(ref), kPermanentTag);
        RuntimeAssert(obj->permanent(), "Permanent ExternalRCRef for non-permanent object %p", obj);
        return const_cast<KRef>(obj); // RawExternalRCRef* for permanent objects is always created from KRef, not KConstRef.
    }
    return nullptr;
}

mm::RawExternalRCRef* kotlin::mm::permanentObjectAsExternalRCRef(KRef obj) noexcept {
    RuntimeAssert(obj != nullptr, "Cannot handle nullptr");
    RuntimeAssert(obj->permanent(), "Object %p must be permanent", obj);
    return reinterpret_cast<mm::RawExternalRCRef*>(setPointerBits(obj, kPermanentTag));
}
