Skip to content

Commit

Permalink
Migration from ESR 115 to 128
Browse files Browse the repository at this point in the history
Includes updated build instructions and a migration guide.
One of the examples needs to be updated, with the new column number API;
and the WeakRefs example needs its job queue and realm creation options
adjusted. Everything else builds as is.
  • Loading branch information
ptomato committed Aug 8, 2024
1 parent ea93476 commit 31e6175
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 17 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
This repository contains documentation and examples for people who want
to embed the SpiderMonkey JavaScript engine.

The information on this `esr102` branch applies to SpiderMonkey 102.x, an
The information on this `esr128` branch applies to SpiderMonkey 128.x, an
Extended Support Release (ESR).
For other versions of SpiderMonkey, check the other branches: `next` for
information that will apply in the next as-yet-unreleased ESR, or
Expand Down
12 changes: 6 additions & 6 deletions docs/Building SpiderMonkey.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Use these instructions to build your own copy of SpiderMonkey.
## Prerequisites ##

You will need a **C++ compiler** that can handle the C++17 standard,
**Rust** version [1.66][minimum-rust-version] or later, **GNU Make**,
**Rust** version [1.76][minimum-rust-version] or later, **GNU Make**,
**zlib**, and **libffi**.
These can usually be installed with a package manager.

Expand All @@ -16,16 +16,16 @@ These can usually be installed with a package manager.
> `--with-system-icu` in the build instructions below, for a shorter
> build time.
[minimum-rust-version]: https://searchfox.org/mozilla-esr115/rev/61b47de1faebf23626e519b2464b461589fbea3e/python/mozboot/mozboot/util.py#14
[minimum-icu-version]: https://searchfox.org/mozilla-esr115/rev/61b47de1faebf23626e519b2464b461589fbea3e/js/moz.configure#1107
[minimum-rust-version]: https://searchfox.org/mozilla-esr128/rev/2d28d1b9e757a35095de45c818a0432e031f339d/python/mozboot/mozboot/util.py#14
[minimum-icu-version]: https://searchfox.org/mozilla-esr128/rev/2d28d1b9e757a35095de45c818a0432e031f339d/js/moz.configure#1308

## Getting the source code ##

Currently, the most reliable way to get the SpiderMonkey source code is
to download the Firefox source.
At the time of writing, the latest source for Firefox ESR 115, which
contains the source for SpiderMonkey ESR 115, can be found here:
https://ftp.mozilla.org/pub/firefox/releases/115.1.0esr/source/
At the time of writing, the latest source for Firefox ESR 128, which
contains the source for SpiderMonkey ESR 128, can be found here:
https://ftp.mozilla.org/pub/firefox/releases/128.1.0esr/source/

The ESR releases have a major release approximately once a year with
security patches released throughout the year.
Expand Down
151 changes: 151 additions & 0 deletions docs/Migration Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,157 @@
This document describes how to port your code from using one ESR version
of SpiderMonkey to the next ESR version.

## ESR 115 to ESR 128 ##

### New API for column numbers ###

Previously, column numbers (in source locations and stack frames) were
stored as mostly as numbers starting from 0.
When column numbers are shown in a user-facing context, however, they
are usually displayed as starting at 1.
SpiderMonkey has changed the internal representation of column numbers
to start at 1 as well.
There is a new set of APIs in `<js/ColumnNumber.h>` that allows
explicitly converting from 1-origin to 0-origin in order to avoid
confusion, and handles tagged column numbers from WASM frames.

**Recommendation:** You will need to use the new column number API if
you obtain a column number from any stack frame API such as
`JS::GetSavedFrameColumn()`.
Example:

```c++
// old
uint32_t column;
if (JS::GetSavedFrameColumn(cx, nullptr, frame, &column) != JS::SavedFrameResult::Ok)
return false;
std::cout << "column number " << column + 1;

// new
JS::TaggedColumnNumberOneOrigin tagged_column;
if (JS::GetSavedFrameColumn(cx, nullptr, frame, &tagged_column) != JS::SavedFrameResult::Ok)
return false;
std::cout << "column number " << tagged_column.oneOriginValue();
```

The function `JS::CreateError()` takes a `JS::ColumnNumberOneOrigin`
which is different from `JS::TaggedColumnNumberOneOrigin`.
The "tagged" type exists to accommodate WASM frames, which have a
function index instead of a column number.

If you don't have WASM frames, you can convert the tagged column to an
untagged column number using:
```c++
JS::ColumnNumberOneOrigin{tagged_column.toLimitedColumnNumber()};
```
This will assert that the column number is not tagged as a WASM function
index.
If you have WASM frames, you'll need to handle those separately.
See [bug 1847469](https://bugzilla.mozilla.org/show_bug.cgi?id=1847469).
### New API for ArrayBuffer buffers ###
ArrayBuffer APIs that take data buffers from the embedding, such as
`JS::NewExternalArrayBuffer()` and `JS::NewArrayBufferWithContents()`,
now take a `mozilla::UniquePtr&&` argument for the data buffer.
**Recommendation:** This is a fairly straightforward migration, in which
you can just put your data buffer into a UniquePtr and then
`std::move()` it into the ArrayBuffer.
If you previously passed a free callback, use that callback as the
deleter argument to the UniquePtr.
Examples:
```c++
// old
JS::RootedObject array_buffer{cx, JS::NewExternalArrayBuffer(
cx, len, my_struct->buffer, my_buffer_free_func, my_struct)};
// new
mozilla::UniquePtr<void, JS::BufferContentsDeleter> contents{
my_struct->buffer, {my_buffer_free_func, my_struct}};
JS::RootedObject array_buffer{cx, JS::NewExternalArrayBuffer(
cx, len, std::move(contents))};
```

### `JS::GCPolicy::needsSweep()` ###

`JS::GCPolicy<T>` has a new method, `needsSweep()`.
It is like a const version of `JS::GCPolicy::traceWeak()`, but the sense
of the boolean return value is reversed.
(Returns true if the weak pointer is dead, and false if it is live.)
The argument is const, so the method will not be called if the pointer
needs to move.
Implementing the method is optional.

**Recommendation:** If you have specialized `JS::GCPolicy` for any of
your classes, consider whether you need to implement this method.
If your class has a weak pointer, you probably do.

Also, note that `JS::GCPolicy<JS::Heap<T>>` does not implement
`needsSweep()`.
If you were storing a `JS::Heap` inside `JS::WeakCache<T>`, you will
need to use a different data structure.
An easy drop-in replacement for `JS::Heap` suitable for this purpose
would be something like this:

```c++
template <typename T>
class WeakPtr : public JS::Heap<T> {
public:
using JS::Heap<T>::Heap;
using JS::Heap<T>::operator=;
};

namespace JS {

template <typename T>
struct GCPolicy<WeakPtr<T>> {
static void trace(JSTracer* trc, WeakPtr<T>* thingp, const char* name) {
return JS::TraceEdge(trc, thingp, name);
}

static bool traceWeak(JSTracer* trc, WeakPtr<T>* thingp) {
return js::gc::TraceWeakEdge(trc, thingp);
}

static bool needsSweep(JSTracer* trc, const WeakPtr<T>* thingp) {
WeakPtr<T> thing{*thingp};
return !js::gc::TraceWeakEdge(trc, &thing);
}
};

} // namespace JS
```
### Getting Function IDs ###
`JS_GetFunctionId()` and `JS_GetFunctionDisplayId()` now require a
JSContext.
There are now alternatives that don't take a JSContext parameter, but
avoid computing the full function name with "`get `" or "`set `" prefix
for accessors: `JS_GetMaybePartialFunctionId()` and
`JS_GetMaybePartialFunctionDisplayId()`.
**Recommendation:** The fact that these APIs didn't require a JSContext
meant they were convenient to use for log output or debugging.
If you were using them for this purpose, switch to the `MaybePartial`
alternatives.
Otherwise, just adapt to the new signature.
### Various API changes ###
This is a non-exhaustive list of minor API changes and renames.
- The type of `JSErrorBase::filename()` is now `JS::ConstUTF8CharsZ`
instead of a C string, so when using `JSErrorReport` you will need to
use `.c_str()` if you still need the C string.
- Several runtime flags for new features in `JS::RealmCreationOptions`
are enabled unconditionally, so the flags are removed.
- Classes that extend `JS::JobQueue` must now implement
`JS::JobQueue::isDrainingStopped()`, which is an abstract method.
## ESR 102 to ESR 115 ##
### More Versatile `JS_InitClass` ###
Expand Down
8 changes: 5 additions & 3 deletions examples/cookbook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <mozilla/Unused.h>

#include <js/Array.h>
#include <js/ColumnNumber.h>
#include <js/CompilationAndEvaluation.h>
#include <js/Conversions.h>
#include <js/Initialization.h>
Expand Down Expand Up @@ -352,9 +353,10 @@ static bool ThrowValue(JSContext* cx, JS::HandleValue exc) {
*
* return ThrowError(cx, global, message, __FILE__, __LINE__, column);
*/
static bool ThrowError(JSContext* cx, JS::HandleObject global,
const char* message, const char* filename,
int32_t lineno, int32_t colno = 0) {
static bool ThrowError(
JSContext* cx, JS::HandleObject global, const char* message,
const char* filename, int32_t lineno,
JS::ColumnNumberOneOrigin colno = JS::ColumnNumberOneOrigin{1}) {
JS::RootedString messageStr(cx, JS_NewStringCopyZ(cx, message));
if (!messageStr) return false;
JS::RootedString filenameStr(cx, JS_NewStringCopyZ(cx, filename));
Expand Down
9 changes: 4 additions & 5 deletions examples/weakref.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ class CustomJobQueue : public JS::JobQueue {
// JS::JobQueue override
bool empty() const override { return m_queue.empty(); }

// JS::JobQueue override
bool isDrainingStopped() const override { return !m_draining; }

void queueFinalizationRegistryCallback(JSFunction* callback) {
mozilla::Unused << m_finalizationRegistryCallbacks.append(callback);
}
Expand Down Expand Up @@ -247,16 +250,12 @@ static bool WeakRefExample(JSContext* cx) {
JS::SetHostCleanupFinalizationRegistryCallback(
cx, CleanupFinalizationRegistry, &jobQueue);

JS::RealmOptions options;
options.creationOptions().setWeakRefsEnabled(
JS::WeakRefSpecifier::EnabledWithoutCleanupSome);

static JSClass GlobalClass = {"WeakRefsGlobal", JSCLASS_GLOBAL_FLAGS,
&JS::DefaultGlobalClassOps};

JS::Rooted<JSObject*> global{
cx, JS_NewGlobalObject(cx, &GlobalClass, nullptr, JS::FireOnNewGlobalHook,
options)};
JS::RealmOptions{})};
if (!global) return false;

JSAutoRealm ar{cx, global};
Expand Down
4 changes: 2 additions & 2 deletions meson.build
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
project('spidermonkey-embedding-examples', 'cpp', version: 'esr115',
project('spidermonkey-embedding-examples', 'cpp', version: 'esr128',
meson_version: '>= 0.43.0',
default_options: ['cpp_std=c++20', 'warning_level=3'])

Expand All @@ -7,7 +7,7 @@ cxx = meson.get_compiler('cpp')
args = []

zlib = dependency('zlib') # (is already a SpiderMonkey dependency)
spidermonkey = dependency('mozjs-127a1')
spidermonkey = dependency('mozjs-128')
readline = cxx.find_library('readline')

# Check if SpiderMonkey was compiled with --enable-debug. If this is the case,
Expand Down

0 comments on commit 31e6175

Please sign in to comment.