/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ECMASCRIPT_JIT_H
#define ECMASCRIPT_JIT_H

#include "ecmascript/base/config.h"
#include "ecmascript/common.h"
#include "ecmascript/compiler/compilation_env.h"
#include "ecmascript/platform/mutex.h"
#include "ecmascript/ecma_vm.h"
#include "ecmascript/mem/clock_scope.h"
#include "ecmascript/mem/machine_code.h"
#include "ecmascript/compiler/compiler_log.h"
#include "ecmascript/jit/jit_thread.h"
#include "ecmascript/jit/jit_dfx.h"
#include "ecmascript/jit/compile_decision.h"
#include "ecmascript/jit/jit_resources.h"

namespace panda::ecmascript {
class JitTask;

struct ThreadTaskInfo {
    std::deque<std::shared_ptr<JitTask>> installJitTasks_;
    bool skipInstallTask_ { false };

    std::atomic<uint32_t> jitTaskCnt_;
    ConditionVariable jitTaskCntCv_;
};

class Jit {
public:
    Jit() {}
    ~Jit();
    static PUBLIC_API Jit *GetInstance();
    void SetJitEnablePostFork(EcmaVM *vm, const std::string &bundleName);
    void PreFork();
    void ConfigJit(EcmaVM *vm);
    void SwitchProfileStubs(EcmaVM *vm);
    void ConfigOptions(EcmaVM *vm) const;
    void ConfigJitFortOptions(EcmaVM *vm);
    void SetEnableOrDisable(const JSRuntimeOptions &options, bool isEnableFastJit, bool isEnableBaselineJit);
    bool PUBLIC_API IsEnableFastJit() const;
    bool PUBLIC_API IsEnableBaselineJit() const;
    bool PUBLIC_API IsEnableJitFort() const;
    void SetEnableJitFort(bool isEnableJitFort);
    bool PUBLIC_API IsDisableCodeSign() const;
    void SetDisableCodeSign(bool isEnableCodeSign);
    bool PUBLIC_API IsEnableAsyncCopyToFort() const;
    void SetEnableAsyncCopyToFort(bool isEnableiAsyncCopyToFort);

    static void Compile(EcmaVM *vm, JSHandle<JSFunction> &jsFunction,
                        CompilerTier::Tier tier = CompilerTier::Tier::FAST,
                        int32_t offset = MachineCode::INVALID_OSR_OFFSET,
                        JitCompileMode::Mode mode = JitCompileMode::Mode::SYNC)
    {
        Compile(vm, jsFunction, CompilerTier(tier), offset, JitCompileMode(mode));
    }

    bool JitCompile(void *compiler, JitTask *jitTask);
    bool JitFinalize(void *compiler, JitTask *jitTask);
    void *CreateJitCompilerTask(JitTask *jitTask);
    bool IsInitialized() const
    {
        return initialized_;
    }

    void DeleteJitCompilerTask(void *compiler);

    void RequestInstallCode(std::shared_ptr<JitTask> jitTask);
    void InstallTasks(JSThread *jsThread);
    void ClearTask(const std::function<bool(common::Task *task)> &checkClear);
    void ClearTaskWithVm(EcmaVM *vm);
    void Destroy();
    uint32_t GetRunningTaskCnt(EcmaVM *vm);
    void CheckMechineCodeSpaceMemory(JSThread *thread, int remainSize);
    void ChangeTaskPoolState(bool inBackground);

    // dfx for jit warmup compile
    static void CountInterpExecFuncs(JSThread *jsThread, JSHandle<JSFunction> &jsFunction);

    bool IsAppJit() const
    {
        return isApp_;
    }

    uint32_t GetHotnessThreshold() const
    {
        return hotnessThreshold_;
    }

    void SetProfileNeedDump(bool isNeed)
    {
        isProfileNeedDump_ = isNeed;
    }

    bool IsProfileNeedDump() const
    {
        return isProfileNeedDump_;
    }

    JitDfx *GetJitDfx() const
    {
        return jitDfx_;
    }

    void IncJitTaskCnt(JSThread *thread);
    void DecJitTaskCnt(JSThread *thread);

    NO_COPY_SEMANTIC(Jit);
    NO_MOVE_SEMANTIC(Jit);

    class TimeScope : public ClockScope {
    public:
        PUBLIC_API explicit TimeScope(EcmaVM *vm, CString message, CompilerTier tier, bool outPutLog = true,
            bool isDebugLevel = false);
        explicit TimeScope(EcmaVM *vm)
            : vm_(vm), message_(""), tier_(CompilerTier::Tier::FAST), outPutLog_(false), isDebugLevel_(true) {}
        PUBLIC_API ~TimeScope();

        void appendMessage(const CString& value) { message_ += value; }

    private:
        EcmaVM *vm_;
        CString message_;
        CompilerTier tier_;
        bool outPutLog_;
        bool isDebugLevel_;
    };

    class JitLockBase {
    public:
        virtual ~JitLockBase() = default;

    protected:
        JitLockBase() = default;

        NO_COPY_SEMANTIC(JitLockBase);
        NO_MOVE_SEMANTIC(JitLockBase);
    };

    class JitLockHolder : public JitLockBase {
    public:
        explicit JitLockHolder(JSThread *thread) : thread_(nullptr), scope_(thread->GetEcmaVM())
        {
            if (thread->IsJitThread()) {
                thread_ = static_cast<JitThread*>(thread);
                if (!thread_->IsInRunningState()) {
                    thread_->ManagedCodeBegin();
                    isInManagedCode_ = true;
                }
                thread_->GetHostThread()->GetJitLock()->Lock();
                auto hostHeap = thread_->GetHostThread()->GetEcmaVM()->GetHeap();
                hostHeap->WaitCCFinished();
            }
        }

        explicit JitLockHolder(const CompilationEnv *env, CString message) : thread_(nullptr),
            scope_(env->GetJSThread()->GetEcmaVM(),
                "Jit Compile Pass: " + message + ", Time:", CompilerTier::Tier::FAST, false)
        {
            if (env->IsJitCompiler()) {
                JSThread *thread = env->GetJSThread();
                ASSERT(thread->IsJitThread());
                thread_ = static_cast<JitThread*>(thread);
                if (!thread_->IsInRunningState()) {
                    thread_->ManagedCodeBegin();
                    isInManagedCode_ = true;
                }
                thread_->GetHostThread()->GetJitLock()->Lock();
                auto hostHeap = thread_->GetHostThread()->GetEcmaVM()->GetHeap();
                hostHeap->WaitCCFinished();
            }
        }

        ~JitLockHolder()
        {
            if (thread_ != nullptr) {
                thread_->GetHostThread()->GetJitLock()->Unlock();
                if (isInManagedCode_) {
                    thread_->ManagedCodeEnd();
                }
            }
        }
        JitThread *thread_ {nullptr};
        TimeScope scope_;
        bool isInManagedCode_{false};
        ALLOW_HEAP_ACCESS
        NO_COPY_SEMANTIC(JitLockHolder);
        NO_MOVE_SEMANTIC(JitLockHolder);
    };

    class JitGCLockHolder : public JitLockBase {
    public:
        explicit JitGCLockHolder(JSThread *thread) : thread_(thread)
        {
            ASSERT(!thread->IsJitThread());
            if (Jit::GetInstance()->IsEnableFastJit() || Jit::GetInstance()->IsEnableBaselineJit()) {
                LockJit(thread_);
                locked_ = true;
            }
        }

        static void LockJit(JSThread *thread)
        {
            Clock::time_point start = Clock::now();
            thread->GetJitLock()->Lock();
            Jit::GetInstance()->GetJitDfx()->SetLockHoldingTime(
            std::chrono::duration_cast<std::chrono::microseconds>(Clock::now() - start).count());
        }

        static void UnlockJit(JSThread *thread)
        {
            thread->GetJitLock()->Unlock();
        }

        ~JitGCLockHolder()
        {
            if (locked_) {
                UnlockJit(thread_);
                locked_ = false;
            }
        }

    private:
        JSThread *thread_;
        bool locked_ { false };

        NO_COPY_SEMANTIC(JitGCLockHolder);
        NO_MOVE_SEMANTIC(JitGCLockHolder);
    };
#if ECMASCRIPT_ENABLE_ARK_STEED
    static void CompileArkSteed(EcmaVM *vm, JSHandle<JSFunction> &jsFunction,
                               CompilerTier tier = CompilerTier::Tier::FAST,
                               int32_t offset = MachineCode::INVALID_OSR_OFFSET,
                               JitCompileMode mode = JitCompileMode::Mode::ASYNC);
#endif

private:
    void Compile(EcmaVM *vm, const CompileDecision &decision);
    static void Compile(EcmaVM *vm, JSHandle<JSFunction> &jsFunction, CompilerTier tier,
                        int32_t offset, JitCompileMode mode);
#if ECMASCRIPT_ENABLE_ARK_STEED
    static bool IsArkSteedBytecodeSupported(EcmaVM *vm, JSHandle<JSFunction> &jsFunction);
#endif
    void CreateJitResources();
    bool IsLibResourcesResolved() const;
    bool initialized_ { false };
    bool fastJitEnable_ { false };
    bool baselineJitEnable_ { false };
    bool isApp_ { false };
    bool isProfileNeedDump_ { true };
    uint32_t hotnessThreshold_ { 0 };
    std::string bundleName_;

    std::unordered_map<JSThread*, ThreadTaskInfo> threadTaskInfo_;
    RecursiveMutex threadTaskInfoLock_;
    bool isEnableJitFort_ { true };
    bool isDisableCodeSign_ { true };
    bool isEnableAsyncCopyToFort_ { true };

    Mutex setEnableLock_;

    JitDfx *jitDfx_ { nullptr };
    std::unique_ptr<JitResources> jitResources_;
    static constexpr int MIN_CODE_SPACE_SIZE = 1_KB;
};
}  // namespace panda::ecmascript
#endif  // ECMASCRIPT_JIT_H