Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,25 @@ V8 Fast API callbacks, idle-task GC scheduling, and CI consolidation.

### Changed

* **Sandbox status** (`cgo.go`, `deps/build.py`):
`V8_ENABLE_SANDBOX` remains active in `cgo.go` to match the prebuilt
static libraries. `deps/build.py` sets `v8_enable_sandbox=false` for
local rebuilds — once deps are rebuilt without the sandbox, the
`NewArrayBufferExternal` zero-copy path activates automatically.
`SandboxEnabled()` reports the compile-time state at runtime.
* **V8 sandbox disabled** (`cgo.go`, `deps/build.py`, `deps/*/`):
Rebuilt V8 monolith for all 4 platforms with
`v8_enable_sandbox=false`. Removed `-DV8_ENABLE_SANDBOX` from
`cgo.go`. `SandboxEnabled()` now returns `false` and
`NewArrayBufferExternal` uses true zero-copy (no memcpy fallback).

* **Fork-maintained V8 deps** (`deps/{os}_{arch}/`):
Replaced upstream `tommie/v8go/deps/*` submodules with local
platform directories containing split archives, `cgo.go`, `go.mod`,
and `vendor.go`. `go.mod` uses `replace` directives to point to
these local deps.

* **V8 build infrastructure** (`.github/workflows/build-v8-deps.yml`,
`deps/build-all-local.sh`, `Makefile`, `deps/Dockerfile.builder`):
Added CI workflow for rebuilding V8 from source via
`workflow_dispatch`, plus local build tooling (`make v8-deps-all`)
with Docker for Linux targets. linux/amd64 uses gcc
(`is_clang=false`) for GNU `ld` compatibility; cross-compile targets
use V8's clang with `use_custom_libcxx=true`.

* **CI consolidated** (`.github/workflows/ci.yml`):
Collapsed from 10 jobs to 2 (one per OS). Each job runs lint, build,
Expand Down
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,13 +276,16 @@ The ChessCom fork adds:
caller-controlled time budget.
* **Zero-copy ArrayBuffer** -- `NewArrayBufferExternal(ctx, []byte)`
wraps Go memory as an ArrayBuffer via external BackingStore +
`runtime.Pinner`. Falls back to copy when V8 sandbox is active;
`runtime.Pinner`. True zero-copy with fork deps (sandbox disabled);
`SandboxEnabled()` reports the mode at runtime.
* **V8 Fast API callbacks** -- `NewFastFunctionTemplate` registers a
C-linkage fast path that TurboFan calls directly, bypassing CGo and
argument marshaling on hot call sites.
* **GC lifecycle callbacks** -- `AddGCPrologueCallback` and
`AddGCEpilogueCallback` for observing GC cycles with typed events.
* **Sandbox-disabled V8 deps** -- fork-maintained V8 static libraries
compiled with `v8_enable_sandbox=false` for all 4 platforms, with
CI rebuild workflow and local build tooling (`make v8-deps-all`).

## Versioning

Expand All @@ -292,10 +295,26 @@ the changes above).

## V8 dependency

See `deps/v8/` for the version of V8 currently in use. Prebuilt static
libraries of V8 are included for Linux and macOS so you should not need
The fork maintains its own V8 static libraries under `deps/{os}_{arch}/`
compiled with `v8_enable_sandbox=false` to enable zero-copy ArrayBuffer.
Prebuilt archives are included for all platforms so you should not need
to build V8 yourself.

### Rebuilding V8 deps

To rebuild V8 from source (e.g. after updating `deps/v8_hash`):

```bash
# Via GitHub Actions (recommended)
gh workflow run build-v8-deps.yml

# Or locally on macOS (all 4 platforms)
make v8-deps-all
```

See [docs/maintaining.md](docs/maintaining.md) for full details on the
build pipeline, platform-specific flags, and linker compatibility.

## Documentation

| Document | Description |
Expand Down
60 changes: 50 additions & 10 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ global.Set("version", "1.0.0")
val, _ := ctx.RunScript("version", "v.js")
```

### Retained value count

Returns the number of C-side persistent handles tracked by a context.
Useful for detecting value leaks.

```go
count := ctx.RetainedValueCount()
```

### Microtask checkpoint

```go
Expand All @@ -100,6 +109,11 @@ go func() {
}()
val, err := ctx.RunScript(longRunningScript, "slow.js")
// err will be an ExecutionTerminated error

// Check termination state
if iso.IsExecutionTerminating() {
iso.CancelTerminateExecution()
}
```

### Heap statistics
Expand Down Expand Up @@ -210,7 +224,22 @@ val.Object() // *Object (returns nil if not an object)
### Comparison

```go
val1.SameValue(val2) // Object.is() semantics
val1.SameValue(val2) // Object.is() semantics
val1.StrictEquals(val2) // === semantics
```

### Type introspection

```go
val.TypeOf() // "string", "number", "object", etc.
```

### JSON marshaling

`Value` implements `json.Marshaler`:

```go
data, err := json.Marshal(val) // calls val.MarshalJSON()
```

### Release
Expand Down Expand Up @@ -296,6 +325,14 @@ fn, _ := val.AsFunction()
result, err := fn.Call(ctx.Global(), arg1, arg2)
```

### Constructing objects from functions

```go
val, _ := ctx.Global().Get("MyClass")
fn, _ := val.AsFunction()
obj, err := fn.NewInstance(arg1, arg2) // equivalent to `new MyClass(arg1, arg2)`
```

### Creating functions from Go

```go
Expand Down Expand Up @@ -1186,30 +1223,33 @@ copy(backing, myData)

### NewArrayBufferExternal

Creates an ArrayBuffer backed directly by a Go byte slice. When the V8
sandbox is disabled, this is true zero-copy — JS and Go share the same
memory. When `V8_ENABLE_SANDBOX` is active (current prebuilt deps), it
falls back to alloc + copy internally. The slice is pinned via
Creates an ArrayBuffer backed directly by a Go byte slice with true
zero-copy — JS and Go share the same memory. The slice is pinned via
`runtime.Pinner` and released when V8 GCs the ArrayBuffer.

```go
data := make([]byte, 64*1024)
ab, _ := v8.NewArrayBufferExternal(ctx, data)
// With sandbox disabled: JS reads/writes `data` directly.
// With sandbox enabled: V8 copies data in; subsequent mutations
// to the Go slice are NOT visible in JS.
// JS reads/writes `data` directly (zero-copy).
```

The fork's prebuilt deps are compiled with `v8_enable_sandbox=false`,
so `NewArrayBufferExternal` always uses the zero-copy path. If a
custom V8 build has the sandbox enabled, the function falls back to
alloc + `memcpy` internally.

### SandboxEnabled

Reports whether the V8 binary was compiled with `V8_ENABLE_SANDBOX`.
Use this to decide between zero-copy and copy-in strategies at runtime.
The fork's prebuilt deps have the sandbox disabled, so this returns
`false`. Useful for downstream consumers that may build against
different V8 binaries.

```go
if v8.SandboxEnabled() {
// External ArrayBuffer will copy; use NewArrayBufferAlloc + write instead
} else {
// True zero-copy via NewArrayBufferExternal
// True zero-copy via NewArrayBufferExternal (default for fork deps)
}
```

Expand Down
84 changes: 72 additions & 12 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,15 @@ consists of:
| File | Role |
|---|---|
| `v8go.h` / `v8go.cc` | Core exports: `Init`, `Version`, `SetFlags`, `NewIsolate`, `RunScript` |
| `isolate.h` / `isolate.cc` | Isolate lifecycle, heap stats, script compilation |
| `isolate.h` / `isolate.cc` | Isolate lifecycle, heap stats, script compilation, idle tasks |
| `context.h` / `context.cc` | Context creation/disposal, `RunScript`, global object |
| `value.h` / `value.cc` | Value type checks, conversions, constructors |
| `object.h` / `object.cc` | Object property get/set/has/delete |
| `function_template.h` / `function_template.cc` | FunctionTemplate creation, callback wiring |
| `object.h` / `object.cc` | Object property get/set/has/delete, enumeration, prototype |
| `function_template.h` / `function_template.cc` | FunctionTemplate creation, callback wiring, Fast API |
| `object_template.h` / `object_template.cc` | ObjectTemplate creation, property configuration |
| `snapshot.h` / `snapshot.cc` | **Fork-specific.** SnapshotCreator lifecycle, blob serialisation |
| `arraybuffer.h` / `arraybuffer.cc` | **Fork-specific.** ArrayBuffer creation (copy, alloc, external zero-copy) |
| `fast_api.h` / `fast_api.cc` | **Fork-specific.** V8 Fast API CFunctionInfo builder |
| `symbol.h` / `symbol.cc` | Well-known symbol accessors |
| `inspector.h` / `inspector.cc` | V8 Inspector protocol bridge |
| `errors.h` / `errors.cc` | Error/exception construction |
Expand Down Expand Up @@ -426,16 +428,13 @@ enqueuing.
caller can then write directly into the backing store via
`ArrayBufferGetData`.

`NewArrayBufferExternal` attempts true zero-copy: it wraps Go-owned
`NewArrayBufferExternal` provides true zero-copy: it wraps Go-owned
memory as an external `BackingStore` via `runtime.Pinner`, so JS and
Go share the same bytes. However, when `V8_ENABLE_SANDBOX` is active
(the case with current prebuilt deps), V8 requires all backing stores
to reside inside its sandbox address space. In this mode, the C++
implementation (`arraybuffer.cc`) falls back to alloc + `memcpy` and
immediately releases the Go-side pin. The runtime function
`SandboxEnabled()` reports which mode is active. Once V8 deps are
rebuilt with `v8_enable_sandbox=false` (configured in `deps/build.py`),
the zero-copy path activates automatically.
Go share the same bytes. The fork's prebuilt V8 deps are compiled
with `v8_enable_sandbox=false`, enabling the zero-copy path. The C++
implementation (`arraybuffer.cc`) includes a fallback to alloc +
`memcpy` for custom V8 builds with the sandbox enabled; the runtime
function `SandboxEnabled()` reports which mode is active.

`NewExternalOneByteString` wraps a C++ `ExternalOneByteStringResource`
subclass that points at Go-owned memory. V8 reads directly from the Go
Expand Down Expand Up @@ -503,3 +502,64 @@ Module pointers are managed separately from Values because
`StringOutputStream` adapter, copies the result to a `malloc`'d buffer,
and deletes the V8-side snapshot. The Go side copies the buffer into a
Go byte slice and frees the C buffer.

## V8 build pipeline

The fork maintains its own V8 static libraries (with
`v8_enable_sandbox=false`) rather than consuming upstream's prebuilt
deps. The build pipeline produces split archives for 4 platforms.

```mermaid
graph LR
subgraph source ["Source"]
Hash["deps/v8_hash"]
Fetch["gclient sync"]
end

subgraph build ["Build (per platform)"]
GN["gn gen"]
Ninja["ninja v8_monolith"]
Split["split_ar"]
end

subgraph output ["Output"]
Archives["libv8-0.a, libv8-1.a, ..."]
CGo["update_cgo.py"]
GoMod["go.mod replace"]
end

Hash --> Fetch
Fetch --> GN
GN --> Ninja
Ninja --> Split
Split --> Archives
Archives --> CGo
CGo --> GoMod
```

### Platform decision tree

Each platform requires specific build flags to produce archives
that are compatible with the target's system linker:

| Platform | `is_clang` | `use_sysroot` | `use_custom_libcxx` | Reason |
|----------|-----------|---------------|---------------------|--------|
| linux/amd64 | `false` (gcc) | `false` | `false` | GNU `ld` cannot parse clang's ELF objects |
| linux/arm64 | `true` | `true` | `true` | Cross-compile from x86\_64; system headers lack C++20 |
| darwin/arm64 | `true` | `false` | `false` | Native build |
| darwin/amd64 | `true` | `false` | `true` | Cross-compile from arm64; system headers vary |

### Sandbox-disabled implications

With `v8_enable_sandbox=false`:

- `v8::ArrayBuffer::NewBackingStore` accepts external (Go-owned)
memory pointers, enabling true zero-copy `NewArrayBufferExternal`.
- The `V8_ENABLE_SANDBOX` preprocessor define is absent from `cgo.go`,
so C++ code compiled by CGo takes the non-sandbox code paths.
- `SandboxEnabled()` returns `false` at runtime.
- V8's virtual memory cage is not allocated, slightly reducing the
process address space footprint.

See [docs/maintaining.md](maintaining.md) for full rebuild
instructions via CI or locally.
Loading
Loading