/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* SpiderMonkey initialization and shutdown code. */

#include "js/Initialization.h"

#include "mozilla/Assertions.h"

#include <ctype.h>

#include "jstypes.h"

#include "builtin/AtomicsObject.h"
#include "ds/MemoryProtectionExceptionHandler.h"
#include "gc/Statistics.h"
#include "jit/ExecutableAllocator.h"
#include "jit/Ion.h"
#include "jit/JitCommon.h"
#include "js/Utility.h"
#if ENABLE_INTL_API
#include "unicode/uclean.h"
#include "unicode/utypes.h"
#endif // ENABLE_INTL_API
#include "vm/DateTime.h"
#include "vm/HelperThreads.h"
#include "vm/Runtime.h"
#include "vm/Time.h"
#include "vm/TraceLogging.h"
#include "vtune/VTuneWrapper.h"
#include "wasm/WasmBuiltins.h"
#include "wasm/WasmInstance.h"

using JS::detail::InitState;
using JS::detail::libraryInitState;
using js::FutexThread;

InitState JS::detail::libraryInitState;

#ifdef DEBUG
static unsigned
MessageParameterCount(const char* format)
{
    unsigned numfmtspecs = 0;
    for (const char* fmt = format; *fmt != '\0'; fmt++) {
        if (*fmt == '{' && isdigit(fmt[1]))
            ++numfmtspecs;
    }
    return numfmtspecs;
}

static void
CheckMessageParameterCounts()
{
    // Assert that each message format has the correct number of braced
    // parameters.
# define MSG_DEF(name, count, exception, format)           \
        MOZ_ASSERT(MessageParameterCount(format) == count);
# include "js.msg"
# undef MSG_DEF
}
#endif /* DEBUG */

#define RETURN_IF_FAIL(code) do { if (!code) return #code " failed"; } while (0)

JS_PUBLIC_API(const char*)
JS::detail::InitWithFailureDiagnostic(bool isDebugBuild)
{
    // Verify that our DEBUG setting matches the caller's.
#ifdef DEBUG
    MOZ_RELEASE_ASSERT(isDebugBuild);
#else
    MOZ_RELEASE_ASSERT(!isDebugBuild);
#endif

    MOZ_ASSERT(libraryInitState == InitState::Uninitialized,
               "must call JS_Init once before any JSAPI operation except "
               "JS_SetICUMemoryFunctions");
    MOZ_ASSERT(!JSRuntime::hasLiveRuntimes(),
               "how do we have live runtimes before JS_Init?");

    PRMJ_NowInit();

    // The first invocation of `ProcessCreation` creates a temporary thread
    // and crashes if that fails, i.e. because we're out of memory. To prevent
    // that from happening at some later time, get it out of the way during
    // startup.
    mozilla::TimeStamp::ProcessCreation();

#ifdef DEBUG
    CheckMessageParameterCounts();
#endif

    RETURN_IF_FAIL(js::TlsContext.init());

#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
    RETURN_IF_FAIL(js::oom::InitThreadType());
    js::oom::SetThreadType(js::THREAD_TYPE_COOPERATING);
#endif

    RETURN_IF_FAIL(js::Mutex::Init());

    RETURN_IF_FAIL(js::wasm::InitInstanceStaticData());

    js::gc::InitMemorySubsystem(); // Ensure gc::SystemPageSize() works.

    RETURN_IF_FAIL(js::jit::InitProcessExecutableMemory());

    MOZ_ALWAYS_TRUE(js::MemoryProtectionExceptionHandler::install());

    RETURN_IF_FAIL(js::jit::InitializeIon());

    RETURN_IF_FAIL(js::InitDateTimeState());

#ifdef MOZ_VTUNE
    RETURN_IF_FAIL(js::vtune::Initialize());
#endif

#if EXPOSE_INTL_API
    UErrorCode err = U_ZERO_ERROR;
    u_init(&err);
    if (U_FAILURE(err))
        return "u_init() failed";
#endif // EXPOSE_INTL_API

    RETURN_IF_FAIL(js::CreateHelperThreadsState());
    RETURN_IF_FAIL(FutexThread::initialize());
    RETURN_IF_FAIL(js::gcstats::Statistics::initialize());

#ifdef JS_SIMULATOR
    RETURN_IF_FAIL(js::jit::SimulatorProcess::initialize());
#endif

    libraryInitState = InitState::Running;
    return nullptr;
}

#undef RETURN_IF_FAIL

JS_PUBLIC_API(void)
JS_ShutDown(void)
{
    MOZ_ASSERT(libraryInitState == InitState::Running,
               "JS_ShutDown must only be called after JS_Init and can't race with it");
#ifdef DEBUG
    if (JSRuntime::hasLiveRuntimes()) {
        // Gecko is too buggy to assert this just yet.
        fprintf(stderr,
                "WARNING: YOU ARE LEAKING THE WORLD (at least one JSRuntime "
                "and everything alive inside it, that is) AT JS_ShutDown "
                "TIME.  FIX THIS!\n");
    }
#endif

    FutexThread::destroy();

    js::DestroyHelperThreadsState();

#ifdef JS_SIMULATOR
    js::jit::SimulatorProcess::destroy();
#endif

#ifdef JS_TRACE_LOGGING
    js::DestroyTraceLoggerThreadState();
    js::DestroyTraceLoggerGraphState();
#endif

    js::MemoryProtectionExceptionHandler::uninstall();

    js::wasm::ShutDownInstanceStaticData();

    js::Mutex::ShutDown();

    // The only difficult-to-address reason for the restriction that you can't
    // call JS_Init/stuff/JS_ShutDown multiple times is the Windows PRMJ
    // NowInit initialization code, which uses PR_CallOnce to initialize the
    // PRMJ_Now subsystem.  (For reinitialization to be permitted, we'd need to
    // "reset" the called-once status -- doable, but more trouble than it's
    // worth now.)  Initializing that subsystem from JS_Init eliminates the
    // problem, but initialization can take a comparatively long time (15ms or
    // so), so we really don't want to do it in JS_Init, and we really do want
    // to do it only when PRMJ_Now is eventually called.
    PRMJ_NowShutdown();

#if EXPOSE_INTL_API
    u_cleanup();
#endif // EXPOSE_INTL_API

#ifdef MOZ_VTUNE
    js::vtune::Shutdown();
#endif // MOZ_VTUNE

    js::FinishDateTimeState();

    if (!JSRuntime::hasLiveRuntimes()) {
        js::wasm::ReleaseBuiltinThunks();
        js::jit::ReleaseProcessExecutableMemory();
    }

    libraryInitState = InitState::ShutDown;
}

JS_PUBLIC_API(bool)
JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, JS_ICUReallocFn reallocFn, JS_ICUFreeFn freeFn)
{
    MOZ_ASSERT(libraryInitState == InitState::Uninitialized,
               "must call JS_SetICUMemoryFunctions before any other JSAPI "
               "operation (including JS_Init)");

#if EXPOSE_INTL_API
    UErrorCode status = U_ZERO_ERROR;
    u_setMemoryFunctions(/* context = */ nullptr, allocFn, reallocFn, freeFn, &status);
    return U_SUCCESS(status);
#else
    return true;
#endif
}
