Skip to content
Open
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
4 changes: 2 additions & 2 deletions docs/bytecode-vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ The `--profile` option on GocciaScriptLoader enables language-level profiling of
- `--profile=all` — both
- `--profile-output=path.json` — JSON export

The profiler follows the same singleton-tracker pattern as coverage (`Goccia.Coverage.pas`). When profiling is disabled, a predictable boolean guard remains in the dispatch loop. Enabled-mode overhead depends on the workload and profiling mode, so measure it on the corpus being investigated rather than relying on a fixed percentage.
The profiler follows the same singleton-tracker pattern as coverage (`Goccia.Coverage.pas`). `ExecuteClosureRegistersInternal` selects a production dispatch loop when coverage, opcode profiling, stop-IP, and the instruction limit are all inactive; that loop omits those per-instruction checks. Any of them being active selects the instrumented loop, which keeps the previous guards. Enabled-mode overhead depends on the workload and profiling mode, so measure it on the corpus being investigated rather than relying on a fixed percentage.

## Runtime Error Diagnostics

Expand Down Expand Up @@ -278,7 +278,7 @@ execution path.

## Instruction Limit

The dispatch loop supports an optional instruction counter (`Goccia.InstructionLimit.pas`). When armed, the counter increments on every dispatched instruction and the limit is checked at the top of each iteration. When disabled, only the guard read of the limit threadvar remains on the hot path. See [Embedding — Execution Limits](embedding.md#execution-limits) for the full API and interpreter-mode behavior.
The dispatch loop supports an optional instruction counter (`Goccia.InstructionLimit.pas`). When armed, execution uses the instrumented loop: the counter increments on every dispatched instruction and the limit is checked at the top of each iteration. When the budget is inactive, production dispatch omits that poll entirely. See [Embedding — Execution Limits](embedding.md#execution-limits) for the full API and interpreter-mode behavior.

## Binary Format

Expand Down
2 changes: 1 addition & 1 deletion docs/embedding.md
Original file line number Diff line number Diff line change
Expand Up @@ -868,7 +868,7 @@ finally
end;
```

Raises `TGocciaInstructionLimitError` when the limit is reached. A value of zero (the default) skips all counter increments and limit comparisons — only the guard read of `GMaxInstructions` remains on the hot path.
Raises `TGocciaInstructionLimitError` when the limit is reached. A value of zero (the default) leaves the instruction budget inactive, so bytecode production dispatch omits the per-instruction poll. A positive limit selects the instrumented loop, which increments and checks the counter on every dispatched instruction.

### Call Stack Depth Limit

Expand Down
4 changes: 2 additions & 2 deletions docs/profiling.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
- **Language-level profiling** — Operates inside the VM dispatch loop, providing data external profilers cannot see
- **Three modes** — `--profile=opcodes` (histogram + pair frequency + scalar hit rate), `--profile=functions` (per-function timing + allocations), `--profile=all` (both)
- **Export formats** — JSON (`--profile-output=path.json`) and collapsed flame graph (`--profile-format=flamegraph`)
- **Disabled-path cost** — A predictable boolean guard remains in the dispatch loop; measure enabled-mode overhead on the workload being profiled
- **Disabled-path cost** — Production dispatch omits the profiler; the instrumented loop (and its boolean guards) is used when opcode profiling is on. Measure enabled-mode overhead on the workload being profiled.
- **Corpus profile review** — Main CI publishes aggregate and detailed test262
profile reports for trend review; see [test262 profile report contract](test262.md#profile-report-contract)

## Overview

The `--profile` option on GocciaScriptLoader enables language-level profiling of the bytecode VM. It operates inside the dispatch loop, providing data that external profilers (like `sample` or `callgrind`) cannot see — which opcodes execute, which JS functions are hot, and where the VM allocates.

Profiling implies `--mode=bytecode` automatically, as does `--coverage` (see [Testing — Coverage](testing.md#coverage)). Near-zero overhead when disabled (boolean guard on the dispatch loop, same pattern as `--coverage`). The guard branches are consistently not-taken and well-predicted, but they are present in the compiled binary.
Profiling implies `--mode=bytecode` automatically, as does `--coverage` (see [Testing — Coverage](testing.md#coverage)). When profiling is off, production dispatch does not record opcodes; turning `--profile` on selects the instrumented loop that still carries the per-instruction profiler guard.

## CLI Usage

Expand Down
2 changes: 1 addition & 1 deletion docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,6 @@ When coverage is used with `.jsx`/`.tsx` files, the JSX transformer produces a s

### Architecture

Coverage uses a runtime boolean check (`FCoverageEnabled` on `TGocciaVM`). When `--coverage` is not passed, the boolean is `False` and branch prediction makes the check effectively free — no separate build is needed.
Coverage is recorded on the instrumented bytecode dispatch loop (`FCoverageEnabled` on `TGocciaVM`). When `--coverage` is not passed, production dispatch omits line-hit recording; enabling coverage, opcode profiling, stop-IP, or an instruction limit selects the instrumented loop. No separate build is needed.

Data is collected by `TGocciaCoverageTracker` (`Goccia.Coverage.pas`), a per-thread tracker that follows the same Initialize/Shutdown pattern as `TGarbageCollector` and `TGocciaCallStack`. During parallel test runs each worker thread initializes its own thread-local instance; after all workers complete, `TGocciaThreadPool.MergeCoverageInto` merges their data into the main thread's tracker via `TGocciaCoverageTracker.MergeFrom`, which adds the source hit *counts* into the destination (via `AddLineHits` / `AddBranchHits`) rather than registering a single hit per covered entry. Output formatting is in `Goccia.Coverage.Report.pas`. A JSX source map is registered with `TGocciaCoverageTracker` during file registration and applied only at report generation time, so the recording hot path is unaffected: `BuildTranslatedLineHits` translates transformed line hits back to original coordinates, and branch positions are translated via `TGocciaSourceMap.Translate` during report emission.
3 changes: 3 additions & 0 deletions source/units/Goccia.InstructionLimit.Test.pas
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ procedure TInstructionLimitTests.TestCapturedStateTracksLiveBudget;
State := CaptureInstructionLimitState;

// A disabled budget remains a no-op through the captured handle.
Expect<Boolean>(InstructionLimitIsActive).ToBe(False);
PollInstructionLimit(State);

// Starting after capture updates the same live state. Exactly two polls are
// accepted for a budget of two; the next poll raises before incrementing.
StartInstructionLimit(2);
Expect<Boolean>(InstructionLimitIsActive).ToBe(True);
PollInstructionLimit(State);
PollInstructionLimit(State);
RaisedExpected := False;
Expand All @@ -70,6 +72,7 @@ procedure TInstructionLimitTests.TestCapturedStateTracksLiveBudget;

// Clearing after capture must disable the same handle immediately.
ClearInstructionLimit;
Expect<Boolean>(InstructionLimitIsActive).ToBe(False);
PollInstructionLimit(State);
end;

Expand Down
6 changes: 6 additions & 0 deletions source/units/Goccia.InstructionLimit.pas
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ procedure ClearInstructionLimit;
procedure PushInstructionLimitScope(const AMaxInstructions: Int64);
procedure PopInstructionLimitScope;
function CaptureInstructionLimitState: PGocciaInstructionLimitState; {$IFDEF FPC}inline;{$ENDIF}
function InstructionLimitIsActive: Boolean; {$IFDEF FPC}inline;{$ENDIF}
procedure IncrementInstructionCounter; {$IFDEF FPC}inline;{$ENDIF}
procedure CheckInstructionLimit; {$IFDEF FPC}inline;{$ENDIF}
procedure PollInstructionLimit(
Expand Down Expand Up @@ -115,6 +116,11 @@ function CaptureInstructionLimitState: PGocciaInstructionLimitState;
Result := @GInstructionLimitState;
end;

function InstructionLimitIsActive: Boolean; {$IFDEF FPC}inline;{$ENDIF}
begin
Result := GInstructionLimitState.Active;
end;

procedure IncrementInstructionCounter; {$IFDEF FPC}inline;{$ENDIF}
begin
if GInstructionLimitState.Active then
Expand Down
Loading
Loading