engine.cpp 9.57 KB
Newer Older
1
/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/*
 * Copyright (c) 2013 Giovanni Campagna <scampa.giovanni@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

Philip Chimento's avatar
Philip Chimento committed
24 25
#include <config.h>

26 27
#include <stdint.h>

28
#ifdef _WIN32
29 30 31 32 33
#    define WIN32_LEAN_AND_MEAN
#    include <windows.h>
#endif

#include <utility>  // for move
34

35
#include <gio/gio.h>
36
#include <glib.h>
37

Philip Chimento's avatar
Philip Chimento committed
38 39 40 41 42 43 44 45 46 47
#include <js/ContextOptions.h>
#include <js/GCAPI.h>           // for JS_SetGCParameter, JS_AddFin...
#include <js/Initialization.h>  // for JS_Init, JS_ShutDown
#include <js/Promise.h>
#include <js/RootingAPI.h>
#include <js/TypeDecls.h>
#include <js/Warnings.h>
#include <js/experimental/SourceHook.h>
#include <jsapi.h>  // for InitSelfHostedCode, JS_Destr...
#include <mozilla/UniquePtr.h>
Philip Chimento's avatar
Philip Chimento committed
48

Philip Chimento's avatar
Philip Chimento committed
49
#include "gi/object.h"
50 51 52
#include "gjs/context-private.h"
#include "gjs/engine.h"
#include "gjs/jsapi-util.h"
Philip Chimento's avatar
Philip Chimento committed
53
#include "util/log.h"
54

55 56
static void gjs_finalize_callback(JSFreeOp*, JSFinalizeStatus status,
                                  void* data) {
57
    auto* gjs = static_cast<GjsContextPrivate*>(data);
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87

  /* Implementation note for mozjs 24:
     sweeping happens in two phases, in the first phase all
     GC things from the allocation arenas are queued for
     sweeping, then the actual sweeping happens.
     The first phase is marked by JSFINALIZE_GROUP_START,
     the second one by JSFINALIZE_GROUP_END, and finally
     we will see JSFINALIZE_COLLECTION_END at the end of
     all GC.
     (see jsgc.cpp, BeginSweepPhase/BeginSweepingZoneGroup
     and SweepPhase, all called from IncrementalCollectSlice).
     Incremental GC muds the waters, because BeginSweepPhase
     is always run to entirety, but SweepPhase can be run
     incrementally and mixed with JS code runs or even
     native code, when MaybeGC/IncrementalGC return.

     Luckily for us, objects are treated specially, and
     are not really queued for deferred incremental
     finalization (unless they are marked for background
     sweeping). Instead, they are finalized immediately
     during phase 1, so the following guarantees are
     true (and we rely on them)
     - phase 1 of GC will begin and end in the same JSAPI
       call (ie, our callback will be called with GROUP_START
       and the triggering JSAPI call will not return until
       we see a GROUP_END)
     - object finalization will begin and end in the same
       JSAPI call
     - therefore, if there is a finalizer frame somewhere
       in the stack, gjs_runtime_is_sweeping() will return
Philip Chimento's avatar
Philip Chimento committed
88
       true.
89 90 91 92 93 94 95 96 97 98

     Comments in mozjs24 imply that this behavior might
     change in the future, but it hasn't changed in
     mozilla-central as of 2014-02-23. In addition to
     that, the mozilla-central version has a huge comment
     in a different portion of the file, explaining
     why finalization of objects can't be mixed with JS
     code, so we can probably rely on this behavior.
  */

99
  if (status == JSFINALIZE_GROUP_PREPARE)
100
        gjs->set_sweeping(true);
101
  else if (status == JSFINALIZE_GROUP_END)
102
        gjs->set_sweeping(false);
103 104
}

105
static void on_garbage_collect(JSContext*, JSGCStatus status, void*) {
Philip Chimento's avatar
Philip Chimento committed
106 107 108 109
    /* We finalize any pending toggle refs before doing any garbage collection,
     * so that we can collect the JS wrapper objects, and in order to minimize
     * the chances of objects having a pending toggle up queued when they are
     * garbage collected. */
110 111
    if (status == JSGC_BEGIN) {
        gjs_debug_lifecycle(GJS_DEBUG_CONTEXT, "Begin garbage collection");
Philip Chimento's avatar
Philip Chimento committed
112
        gjs_object_clear_toggles();
113 114 115
    } else if (status == JSGC_END) {
        gjs_debug_lifecycle(GJS_DEBUG_CONTEXT, "End garbage collection");
    }
116 117
}

Philip Chimento's avatar
Philip Chimento committed
118 119 120
static void on_promise_unhandled_rejection(
    JSContext* cx, JS::HandleObject promise,
    JS::PromiseRejectionHandlingState state, void* data) {
121
    auto gjs = static_cast<GjsContextPrivate*>(data);
122 123
    uint64_t id = JS::GetPromiseID(promise);

Philip Chimento's avatar
Philip Chimento committed
124
    if (state == JS::PromiseRejectionHandlingState::Handled) {
125
        /* This happens when catching an exception from an await expression. */
126
        gjs->unregister_unhandled_promise_rejection(id);
127 128 129 130 131
        return;
    }

    JS::RootedObject allocation_site(cx, JS::GetPromiseAllocationSite(promise));
    GjsAutoChar stack = gjs_format_stack_trace(cx, allocation_site);
132
    gjs->register_unhandled_promise_rejection(id, std::move(stack));
133 134
}

135 136
bool gjs_load_internal_source(JSContext* cx, const char* filename, char** src,
                              size_t* length) {
137 138
    GError* error = nullptr;
    const char* path = filename + 11;  // len("resource://")
139
    GBytes* script_bytes =
140 141 142 143
        g_resources_lookup_data(path, G_RESOURCE_LOOKUP_FLAGS_NONE, &error);
    if (!script_bytes)
        return gjs_throw_gerror_message(cx, error);

144
    *src = static_cast<char*>(g_bytes_unref_to_data(script_bytes, length));
145 146 147 148
    return true;
}

class GjsSourceHook : public js::SourceHook {
149 150
    bool load(JSContext* cx, const char* filename,
              char16_t** two_byte_source G_GNUC_UNUSED, char** utf8_source,
151
              size_t* length) {
152 153
        // caller owns the source, per documentation of SourceHook
        return gjs_load_internal_source(cx, filename, utf8_source, length);
154 155 156
    }
};

Chun-wei Fan's avatar
Chun-wei Fan committed
157 158 159 160 161 162 163 164 165 166 167 168 169
#ifdef G_OS_WIN32
HMODULE gjs_dll;
static bool gjs_is_inited = false;

BOOL WINAPI
DllMain (HINSTANCE hinstDLL,
DWORD     fdwReason,
LPVOID    lpvReserved)
{
  switch (fdwReason)
  {
  case DLL_PROCESS_ATTACH:
    gjs_dll = hinstDLL;
170
    gjs_is_inited = JS_Init();
Chun-wei Fan's avatar
Chun-wei Fan committed
171 172 173
    break;

  case DLL_THREAD_DETACH:
174
    JS_ShutDown ();
Chun-wei Fan's avatar
Chun-wei Fan committed
175 176 177 178 179 180 181 182 183 184 185
    break;

  default:
    /* do nothing */
    ;
    }

  return TRUE;
}

#else
186 187 188 189 190 191 192 193
class GjsInit {
public:
    GjsInit() {
        if (!JS_Init())
            g_error("Could not initialize Javascript");
    }

    ~GjsInit() {
194
        JS_ShutDown();
195 196
    }

197
    explicit operator bool() const { return true; }
198 199 200
};

static GjsInit gjs_is_inited;
Chun-wei Fan's avatar
Chun-wei Fan committed
201
#endif
202

203
JSContext* gjs_create_js_context(GjsContextPrivate* uninitialized_gjs) {
Philip Chimento's avatar
Philip Chimento committed
204 205 206 207 208
    g_assert(gjs_is_inited);
    JSContext *cx = JS_NewContext(32 * 1024 * 1024 /* max bytes */);
    if (!cx)
        return nullptr;

209 210
    if (!JS::InitSelfHostedCode(cx)) {
        JS_DestroyContext(cx);
211
        return nullptr;
212
    }
213

Philip Chimento's avatar
Philip Chimento committed
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
    // commented are defaults in moz-24
    JS_SetNativeStackQuota(cx, 1024 * 1024);
    JS_SetGCParameter(cx, JSGC_MAX_MALLOC_BYTES, 128 * 1024 * 1024);
    JS_SetGCParameter(cx, JSGC_MAX_BYTES, -1);
    JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_INCREMENTAL);
    JS_SetGCParameter(cx, JSGC_SLICE_TIME_BUDGET, 10); /* ms */
    // JS_SetGCParameter(cx, JSGC_HIGH_FREQUENCY_TIME_LIMIT, 1000); /* ms */
    JS_SetGCParameter(cx, JSGC_DYNAMIC_MARK_SLICE, true);
    JS_SetGCParameter(cx, JSGC_DYNAMIC_HEAP_GROWTH, true);
    // JS_SetGCParameter(cx, JSGC_LOW_FREQUENCY_HEAP_GROWTH, 150);
    // JS_SetGCParameter(cx, JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN, 150);
    // JS_SetGCParameter(cx, JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX, 300);
    // JS_SetGCParameter(cx, JSGC_HIGH_FREQUENCY_LOW_LIMIT, 100);
    // JS_SetGCParameter(cx, JSGC_HIGH_FREQUENCY_HIGH_LIMIT, 500);
    // JS_SetGCParameter(cx, JSGC_ALLOCATION_THRESHOLD, 30);
    // JS_SetGCParameter(cx, JSGC_DECOMMIT_THRESHOLD, 32);

    /* set ourselves as the private data */
232
    JS_SetContextPrivate(cx, uninitialized_gjs);
Philip Chimento's avatar
Philip Chimento committed
233

234 235
    JS_AddFinalizeCallback(cx, gjs_finalize_callback, uninitialized_gjs);
    JS_SetGCCallback(cx, on_garbage_collect, uninitialized_gjs);
Philip Chimento's avatar
Philip Chimento committed
236
    JS::SetWarningReporter(cx, gjs_warning_reporter);
237
    JS::SetJobQueue(cx, dynamic_cast<JS::JobQueue*>(uninitialized_gjs));
238
    JS::SetPromiseRejectionTrackerCallback(cx, on_promise_unhandled_rejection,
239
                                           uninitialized_gjs);
Philip Chimento's avatar
Philip Chimento committed
240

241 242 243 244
    // We use this to handle "lazy sources" that SpiderMonkey doesn't need to
    // keep in memory. Most sources should be kept in memory, but we can skip
    // doing that for the realm bootstrap code, as it is already in memory in
    // the form of a GResource. Instead we use the "source hook" to retrieve it.
245 246 247
    auto hook = mozilla::MakeUnique<GjsSourceHook>();
    js::SetSourceHook(cx, std::move(hook));

248 249 250 251
    if (g_getenv("GJS_DISABLE_EXTRA_WARNINGS")) {
        g_warning(
            "GJS_DISABLE_EXTRA_WARNINGS has been removed, GJS no longer logs "
            "extra warnings.");
252 253
    }

254 255
    bool enable_jit = !(g_getenv("GJS_DISABLE_JIT"));
    if (enable_jit) {
Philip Chimento's avatar
Philip Chimento committed
256 257
        gjs_debug(GJS_DEBUG_CONTEXT, "Enabling JIT");
    }
258 259
    JS::ContextOptionsRef(cx)
        .setAsmJS(enable_jit);
260

261 262 263 264 265 266 267
    uint32_t value = enable_jit ? 1 : 0;

    JS_SetGlobalJitCompilerOption(
        cx, JSJitCompilerOption::JSJITCOMPILER_ION_ENABLE, value);
    JS_SetGlobalJitCompilerOption(
        cx, JSJitCompilerOption::JSJITCOMPILER_BASELINE_ENABLE, value);

Philip Chimento's avatar
Philip Chimento committed
268
    return cx;
269
}