hermes Documentation

A JavaScript engine optimized for running React Native.

11,381 stars JavaScript JavaScript 110 files · ~132,219 tokens
RAW Doc

Technical Documentation: facebook/hermes

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (`README.md`)

Hermes JS Engine



Hermes is a JavaScript engine optimized for fast start-up of React Native apps. It features ahead-of-time static optimization and compact bytecode.

If you're only interested in using pre-built Hermes in a new or existing React Native app, you do not need to follow this guide or have direct access to the Hermes source. Instead, just follow these instructions to enable Hermes.

Noted that each Hermes release is aimed at a specific RN version. The rule of thumb is to always follow Hermes releases strictly. Version mismatch can result in instant crash of your apps in the worst case scenario.

If you want to know how to build and hack on Hermes directly, and/or integrate Hermes built from source into a React Native app then read on.

Hermes Blog - Technical articles and deep dives into Hermes internals.

The instructions here very briefly cover steps to build the Hermes CLI. They assume you have typical native development tools setup for your OS, and support for cmake and Ninja. For more details of required dependencies, building Hermes with different options, etc. follow these links instead:

To build a local debug version of the Hermes CLI tools the following steps should get you started on macOS/Linux:

shell
mkdir hermes_workingdir
cd hermes_workingdir
git clone https://github.com/facebook/hermes.git
cmake -S hermes -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build ./build

Or if you're using Windows, the following should get you going in a Git Bash shell:

shell
mkdir hermes_workingdir
cd hermes_workingdir
git -c core.autocrlf=false clone https://github.com/facebook/hermes.git
cmake -S hermes -B build -G 'Visual Studio 16 2019' -A x64
cmake --build ./build

You will now be in a directory with the output of building Hermes into CLI tools. From here you can run a piece of JavaScript as follows:

shell
echo "'use strict'; function hello() { print('Hello World'); } hello();" | ./bin/hermes

Contributing

The main purpose of this repository is to continue to evolve Hermes, making it faster and more efficient. We are grateful to the community for contributing bugfixes and improvements. Read below to learn how you can take part in improving Hermes.

Code of Conduct

Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please read the full text so that you can understand what actions will and will not be tolerated.

Contributing Guide

Read our contributing guide to learn about our development process, how to propose bugfixes and improvements, and how to build and test your changes to Hermes.

License

Hermes is MIT licensed.


Overview (`agent-perf/README.md`)

Static Hermes Agent Performance Infrastructure

Tools for measuring, analyzing, and comparing Static Hermes performance.
All tools output compact (single-line) structured JSON (--json) for
programmatic parsing and human-readable text by default. Compact JSON
minimizes token consumption when AI agents ingest tool output. Designed
for both human engineers and AI agents.

Quick Start

bash
# Build shermes with cmake
cmake -B cmake-build-release -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build cmake-build-release --target shermes

# Run benchmarks
agent-perf/tools/run_benchmarks.sh <shermes> micro --json

# A/B performance comparison (statistical)
agent-perf/tools/benchmark_compare.sh <shermes_a> <shermes_b> --suite all

# A/B binary size comparison
agent-perf/tools/binary_size_compare.sh --baseline <shermes_a> --optimized <shermes_b> --suite octane

# Analyze generated C code
shermes -emit-c -o /tmp/out.c test.js
agent-perf/tools/generated_code_analyzer.py -i /tmp/out.c --json

# Quick performance health check
agent-perf/tools/perf_proxies.sh <shermes> benchmarks/octane/richards.js

Directory Structure

text
agent-perf/
├── tools/              24 analysis and measurement tools
├── benchmarks/
│   ├── micro/          9 targeted subsystem benchmarks (<5s each)
│   │   ├── arithmetic.js
│   │   ├── array_operations.js
│   │   ├── closures_scopes.js
│   │   ├── control_flow.js
│   │   ├── function_calls.js
│   │   ├── object_creation.js
│   │   ├── property_access.js
│   │   ├── string_operations.js
│   │   └── typed_operations.js
│   └── macro/          4 app-representative workloads
│       ├── app_startup.js
│       ├── component_tree.js
│       ├── json_processing.js
│       └── module_system.js
├── schemas/
│   └── perf_schema.json    JSON output format documentation
└── tests/              13 unit test files (250+ tests)

Additionally, Octane benchmarks live at benchmarks/octane/ in the SH root.

CLI Conventions

All tools follow these conventions:

  • --help / -h — prints usage, arguments, and examples
  • --json — outputs compact (single-line) structured JSON, optimized for minimal token consumption by AI agents
  • --typed — compiles with Static Hermes typed mode
  • --compare <shermes_b> — A/B comparison mode (shell tools)
  • Exit code 0 on success, non-zero on error or regression detection
  • Shell tools accept positional arguments; Python tools use named flags

Tool Reference

Benchmarking and Comparison

`run_benchmarks.sh` — Benchmark Runner

Compiles and runs the standard benchmark suites, extracting results from
RESULT: lines (micro/macro) or Octane score lines.

Questions answered:

  • How fast is this shermes build on the standard benchmarks?
  • What are the ops/sec for each micro/macro benchmark?
  • What is the Octane score?
text
Usage: run_benchmarks.sh <shermes_binary> [micro|macro|octane|all] [options]

Arguments:
  <shermes_binary>          Path to the shermes compiler binary (required)
  [micro|macro|octane|all]  Benchmark suite to run (default: all)

Options:
  --json                    Output structured JSON
  --typed                   Compile with --typed flag
  --iterations <N>          Number of iterations per benchmark (default: 5)
  --octane                  Also run benchmarks/octane/ suite
  --shermes-args "ARGS"     Extra arguments to pass to shermes

`benchmark_compare.sh` — Statistical A/B Comparison

Statistical A/B comparison of two shermes binaries. Uses interleaved execution
for bias reduction and Mann-Whitney U test for significance.

Questions answered:

  • Is binary B faster or slower than binary A?
  • Are performance differences statistically significant?
  • What is the geometric mean delta across benchmarks?
text
Usage: benchmark_compare.sh <shermes_a> <shermes_b> [options]

Arguments:
  <shermes_a>               Path to baseline shermes binary (required)
  <shermes_b>               Path to candidate shermes binary (required)

Options:
  --suite <name>            Benchmark suite: micro, macro, all (default: all)
  --json                    Output structured JSON
  --typed                   Compile with --typed flag
  --iterations <N>          Iterations per benchmark (default: 11)
  --threshold <PCT>         Regression threshold percentage (default: 2.0)

Exit code 1 if any benchmark regresses beyond threshold.

`count_instructions.sh` — Deterministic Instruction Counts

Measures retired CPU instruction counts using perf stat -e instructions.
Instructions are the most deterministic performance metric — not affected by
system load or thermal throttling.

Questions answered:

  • How many instructions does this workload execute?
  • Did a compiler change increase or decrease instruction count?
  • How many instructions does compilation itself take?
text
Usage: count_instructions.sh <js_file> [options]

Arguments:
  <js_file>                 JavaScript file to measure (required)

Options:
  --shermes <path>          Path to shermes binary (auto-detected if not set)
  --compare <a> <b>         A/B comparison mode with two shermes binaries
  --iterations <N>          Number of iterations (default: 11)
  --typed                   Compile with --typed flag
  --compile-only            Measure compilation instructions only (no execution)
  --json                    Output structured JSON

Binary Size Analysis

`binary_size_compare.sh` — A/B Binary Size Comparison

End-to-end binary size comparison for Static Hermes compiled JS programs.
Compiles benchmarks with two shermes builds and compares .text section sizes,
call site counts, and per-call-site overhead. Optionally uses bloaty for
deeper section/symbol/compilation-unit analysis when available.

Questions answered:

  • How much does a compiler/header change affect generated binary size?
  • How many _sh_ljs_get_by_val_rjs (or other) call sites exist?
  • What is the per-call-site overhead of an inlined fast path?
  • (with --bloaty) Which sections, symbols, and compilation units grew most?
text
Usage: binary_size_compare.sh --baseline <shermes_a> --optimized <shermes_b> [options]

Options:
  --baseline <path>         Path to baseline shermes binary (required)
  --optimized <path>        Path to optimized shermes binary (required)
  --js <file.js>            JS file to compile (can specify multiple times)
  --suite <name>            Benchmark suite: micro, macro, octane, all
  --typed                   Compile with --typed flag
  --strip                   Compare stripped binaries (removes debug info)
  --bloaty                  Use bloaty for deeper symbol/section analysis (if installed)
  --json                    Output structured JSON

When --bloaty is specified and bloaty is installed, each benchmark's output
includes additional section-level, symbol-level (top 20), and compilation-unit
(top 10) breakdowns from bloaty's DWARF-aware analysis. Falls back to the
default nm/objdump analysis if bloaty is not available.

`binary_size_analyzer.py` — Symbol-Level Size Analysis

Parses nm/objdump output from two binaries to produce a detailed
symbol-level size comparison. Categorizes symbols and identifies the
largest contributors to size growth.

Questions answered:

  • How much did the .text section grow/shrink?
  • Which individual symbols changed size the most?
  • Are there new inlined functions present in B but not A?
  • What is the get_by_val-related symbol growth?
text
Usage: binary_size_analyzer.py [options]

Options:
  --baseline <path>         Path to baseline binary
  --optimized <path>        Path to optimized binary
  --nm-a <path>             Path to pre-captured nm output for baseline
  --nm-b <path>             Path to pre-captured nm output for optimized
  --json                    Output JSON

Either --baseline/--optimized or --nm-a/--nm-b is required.

Symbol categories: get_by_val, get_by_id, property_put, calls,
arithmetic, generated_js, runtime, other.


Generated Code Analysis

`generated_code_analyzer.py` — Runtime Call Analysis

The primary diagnostic tool for understanding generated C code. Categorizes
every _sh_* runtime call, reports per-function statistics, and detects
anti-patterns.

Questions answered:

  • How many runtime calls are in the generated code?
  • Which category dominates (property access, GC, arithmetic, etc.)?
  • Which functions have the highest call density?
  • Are there redundant type checks or excessive GC scoping?
text
Usage: generated_code_analyzer.py -i <generated.c> [options]

Options:
  -i, --input <path>        Path to .c file from shermes -emit-c (required)
  --json                    Output JSON

Categories tracked: property_access, type_check, gc, arithmetic,
comparison, call, object, string, other.

Detected patterns: redundant_type_check, excessive_gc_scoping,
inline_opportunity, high_call_density.

`generated_code_antipatterns.py` — C Compiler Optimization Inhibitors

Scans generated .c files for patterns that prevent the C compiler from
optimizing well — artifacts of code generation that indicate improvement
opportunities in shermes.

Questions answered:

  • Does the generated C code have patterns that prevent optimization?
  • Are there unnecessary GC root operations? Redundant casts?
  • Is there excessive pointer aliasing?
text
Usage: generated_code_antipatterns.py -i <generated.c> [options]

Options:
  -i, --input <path>        Path to generated .c file (required)
  --json                    Output JSON

Detected patterns (by severity): excessive_pointer_aliasing,
redundant_null_check, excessive_function_calls, unnecessary_gc_root,
redundant_cast, large_stack_frame, unreachable_after_throw.

`generated_code_report.sh` — Per-Function Code Metrics

Generates C code and reports per-function metrics: lines of C, runtime call
count, and complexity indicators (branch count).

Questions answered:

  • How many functions did the compiler generate?
  • How large is each function? What is its runtime call count?
  • How does generated code differ between two shermes builds?
text
Usage: generated_code_report.sh <shermes_binary> <workload.js> [options]

Options:
  --typed                   Compile with --typed flag
  --json                    Output structured JSON
  --compare <shermes_b>     Compare generated code from two shermes binaries

`sh_source_mapper.py` — JS-to-C Line Mapping

Maps between JS source, generated C, and native code by parsing #line
directives in C code generated by shermes -emit-c. Essential for correlating
native code hotspots with original JS source.

Questions answered:

  • Which JS source line does C line 150 correspond to?
  • Which C lines were generated from JS line 25?
  • What is the full source map between JS and generated C?
text
Usage: sh_source_mapper.py -i <generated.c> [options]

Options:
  -i, --input <path>        Path to .c file from shermes -emit-c (required)
  --c-line <N>              Query: find JS source line for this C line
  --js-line <N>             Query: find all C lines for this JS line
  --js-file <name>          Filter --js-line results to a specific JS file
  --json                    Output JSON

`type_coverage_analyzer.py` — Typed Mode Effectiveness

Compares two generated .c files (typed vs. untyped) to measure how
effectively typed mode eliminates or reduces runtime calls.

Questions answered:

  • How effective is typed mode at eliminating runtime calls?
  • Which _sh_* calls are fully eliminated by type information?
  • What is the per-function type coverage percentage?
  • Are there calls that increased in typed mode (regressions)?
text
Usage: type_coverage_analyzer.py -t <typed.c> -u <untyped.c> [options]

Options:
  -t, --typed <path>        Path to typed-mode generated .c file (required)
  -u, --untyped <path>      Path to untyped-mode generated .c file (required)
  --json                    Output JSON

Profiling and Hotspot Analysis

`collect_pmu.sh` — Hardware PMU Counter Collection

Collects hardware Performance Monitoring Unit counters using perf stat.
Computes derived ratios (IPC, miss rates). Requires Linux perf.

Questions answered:

  • What is the IPC for this workload?
  • What are the cache, branch, and TLB miss rates?
  • How do two shermes builds compare on hardware counters?
text
Usage: collect_pmu.sh <shermes_binary> <workload.js> [options]

Options:
  --typed                   Compile with --typed flag
  --json                    Output structured JSON
  --compare <shermes_b>     A/B comparison with second shermes binary
  --shermes-args "ARGS"     Extra arguments to pass to shermes

Counters: instructions, cycles, L1-dcache-load-misses,
L1-icache-load-misses, branch-misses, dTLB-load-misses.

`pmu_anomalies.py` — PMU Anomaly Detection

Accepts PMU counter data as JSON and flags values that exceed known-good
thresholds. Pairs with collect_pmu.sh --json output.

Questions answered:

  • Are there microarchitectural performance anomalies?
  • Is IPC suspiciously low?
  • Are cache or branch misprediction rates abnormal?
text
Usage: pmu_anomalies.py [-i <pmu_data.json>] [options]

Options:
  -i, --input <path>        Path to PMU JSON data (default: stdin)
  --json                    Output JSON

Thresholds: IPC < 1.0, branch miss > 10%, L1-icache miss > 5%,
L1-dcache miss > 10%, dTLB miss > 3%.

`profile_to_json.py` — Perf Report Parser

Converts perf report --stdio output to structured JSON, extracting
per-function profiling data.

Questions answered:

  • What are the hottest functions in a profiled binary?
  • How are samples distributed across modules?
text
Usage: profile_to_json.py [-i <perf_report.txt>] [options]

Options:
  -i, --input <path>        Path to perf report output (default: stdin)
  -n, --top <N>             Show only top N entries
  -f, --filter <substr>     Filter to entries whose module contains substring
  --json                    Output JSON

`annotate_disasm.py` — Instruction-Level Hotspot Annotation

Produces hotness-annotated disassembly from perf annotate output, marking
hot instructions (>=1.0% samples) with >>>.

Questions answered:

  • Which assembly instructions are the hottest?
  • Where is the CPU spending the most time at the instruction level?
text
Usage: annotate_disasm.py [-i <perf_annotate.txt>] [options]

Options:
  -i, --input <path>        Path to perf annotate --stdio output (default: stdin)
  -f, --function <name>     Filter to a specific function (substring match)
  --json                    Output JSON

`annotate_source.py` — Source-Level Hotspot Annotation

Overlays line-level profile data onto source code, mapping perf annotate
sample counts back to source lines and marking hot lines with [HOT X.X%].

Questions answered:

  • Which source code lines consume the most CPU time?
  • Where are the hotspots in the source (not assembly) view?
text
Usage: annotate_source.py [-i <perf_annotate.txt>] [options]

Options:
  -i, --input <path>        Path to perf annotate --stdio output (default: stdin)
  -f, --function <name>     Filter to a specific function (substring match)
  --json                    Output JSON

`runtime_call_profiler.py` — Runtime Function Profiling

Profiles _sh_* runtime function usage from perf report output. Ranks by
sample count, groups by category, and identifies top optimization candidates.

Questions answered:

  • Which _sh_* runtime functions consume the most CPU?
  • Which category (property access, GC, arithmetic) dominates?
  • What are the top optimization candidates?
text
Usage: runtime_call_profiler.py [-i <perf_report.txt>] [options]

Options:
  -i, --input <path>        Path to perf report --stdio output (default: stdin)
  -n, --top <N>             Number of top candidates to show (default: 20)
  --json                    Output JSON

Anti-Pattern Scanners

`js_antipattern_scan.py` — JavaScript Anti-Patterns

Scans JavaScript source for patterns that exercise slow engine paths in
Hermes/Static Hermes, preventing effective AOT compilation.

Questions answered:

  • Does this JS code use patterns that are slow in Hermes?
  • Are there uses of eval, with, arguments, delete?
  • Are there sparse arrays or megamorphic property accesses?
text
Usage: js_antipattern_scan.py -i <file_or_dir> [options]

Options:
  -i, --input <path>        JS file or directory to scan (required)
  --json                    Output JSON

Detected patterns: eval_usage, with_statement,
arguments_materialization, megamorphic_access, dynamic_property_name,
prototype_pollution, delete_operator, try_catch_in_loop,
sparse_array, implicit_global.

`antipattern_scan.py` — C++ Performance Anti-Patterns

Scans C++ source files for common performance anti-patterns. Optionally
cross-references with profiling data to focus on hot functions.

Questions answered:

  • Does this C++ code contain known performance anti-patterns?
  • Are large types being passed by value?
  • Are there redundant container lookups?
text
Usage: antipattern_scan.py -i <file_or_dir> [options]

Options:
  -i, --input <path>        C++ source file or directory (required)
  -p, --profile <path>      Profile JSON for cross-referencing hot functions
  --json                    Output JSON

Detected patterns: pass_by_value, repeated_lookup,
string_concat_loop, virtual_dispatch_hot, unnecessary_copy,
redundant_find_access.


Compiler Analysis

`compiler_stats.sh` — Pass Statistics

Captures compiler pass statistics from shermes --print-stats, including
LLVM STATISTIC() counters and pass timing.

Questions answered:

  • How many inlining decisions were made?
  • How much time does each pass take?
  • What are the DCE/CSE/type inference counters?
text
Usage: compiler_stats.sh <shermes_binary> <workload.js> [options]

Options:
  --typed                   Compile with --typed flag
  --json                    Output structured JSON

`compilation_profiler.sh` — Pipeline Stage Timing

Measures time for each compilation stage independently: JS-to-C, C-to-object,
and full pipeline (including linking).

Questions answered:

  • Which compilation stage is the bottleneck?
  • How long does JS-to-C take vs. C compilation vs. linking?
  • What are the file sizes at each stage?
text
Usage: compilation_profiler.sh <shermes_binary> <workload.js> [options]

Options:
  --typed                   Compile with --typed flag
  --json                    Output structured JSON

`pass_experiment.sh` — Optimization Pass Marginal Value

Measures the marginal value of a specific optimization pass by comparing the
full pipeline vs. pipeline with that pass excluded.

Questions answered:

  • How much does a specific pass (inlining, DCE, CSE) contribute?
  • What happens to code size and performance if we disable it?
text
Usage: pass_experiment.sh <shermes_binary> <workload.js> --disable <pass> [options]

Arguments:
  <shermes_binary>          Path to shermes (required)
  <workload.js>             JavaScript file (required)

Options:
  --disable <pass_name>     Pass to exclude (required)
  --typed                   Compile with --typed flag
  --json                    Output structured JSON

Available passes: dce, cse, mem2reg, instsimplify, simplifycfg,
stackpromotion, typeinference, inlining, codemotion, funcsigopts,
tdzdedup.

`regalloc_report.py` — Register Allocation Quality

Analyzes register allocation quality from shermes -dump-ra output. Reports
per-function register count, spill count, and max register pressure.

Questions answered:

  • How many registers does each function use?
  • Which functions have the most spills?
  • Are there register allocation quality problems?
text
Usage: regalloc_report.py [-i <dump_ra.txt>] [options]

Options:
  -i, --input <path>        Path to shermes -dump-ra output (default: stdin)
  --json                    Output JSON

GC Analysis

`gc_stats.sh` — Garbage Collection Statistics

Compiles JS, runs the native binary, and captures GC statistics from stderr.
Computes allocation rate, GC overhead %, and survival ratio.

Questions answered:

  • What is the GC overhead for this workload?
  • What is the allocation rate?
  • How does GC behavior differ between two builds?
text
Usage: gc_stats.sh <shermes_binary> <workload.js> [options]

Options:
  --typed                   Compile with --typed flag
  --json                    Output structured JSON
  --compare <shermes_b>     A/B comparison with second shermes binary

Multi-Metric

`perf_proxies.sh` — Quick Performance Health Check

Fast multi-dimensional performance assessment without requiring hardware
counters. Measures code size, call density, and compilation latency.

Questions answered:

  • What is the overall "performance health" of a shermes build?
  • How do code size, call density, and compilation latency compare?
text
Usage: perf_proxies.sh <shermes> <js_file_or_dir> [options]

Options:
  --typed                   Compile with --typed flag
  --compare <shermes_b>     A/B comparison mode
  --json                    Output structured JSON

Metrics: generated C size (lines/bytes), native binary size, runtime call
density (_sh_* calls), compilation latency (ms per stage), function count
and average size.


Workflow 1: Performance Regression Check

Quick check for performance regressions between two shermes builds.

bash
# Statistical A/B comparison with significance testing
agent-perf/tools/benchmark_compare.sh <baseline> <candidate> --suite all --json

# Deterministic instruction count comparison (most stable metric)
agent-perf/tools/count_instructions.sh benchmarks/octane/richards.js \
  --compare <baseline> <candidate> --iterations 21

Workflow 2: Binary Size Impact Assessment

Quantify code size cost of an optimization that inlines code into generated C.

bash
# Step 1: A/B binary size across Octane
agent-perf/tools/binary_size_compare.sh \
  --baseline <shermes_a> --optimized <shermes_b> --suite octane

# Step 2: Symbol-level drill-down on a specific benchmark
agent-perf/tools/binary_size_analyzer.py \
  --baseline baseline_richards.o --optimized optimized_richards.o

Workflow 3: Generated Code Quality Analysis

Understand what the compiler produces and find optimization opportunities.

bash
# Generate C code
shermes -emit-c -o /tmp/out.c workload.js

# Analyze runtime calls by category
agent-perf/tools/generated_code_analyzer.py -i /tmp/out.c --json

# Find code patterns that inhibit C compiler optimization
agent-perf/tools/generated_code_antipatterns.py -i /tmp/out.c

# Map hotspots back to JS source
agent-perf/tools/sh_source_mapper.py -i /tmp/out.c --c-line 150

Workflow 4: Typed Mode Effectiveness

Measure how much type information improves generated code.

bash
# Generate both typed and untyped C code
shermes -emit-c -o /tmp/untyped.c workload.js
shermes -emit-c --typed -o /tmp/typed.c workload.js

# Compare runtime call elimination
agent-perf/tools/type_coverage_analyzer.py -t /tmp/typed.c -u /tmp/untyped.c --json

Workflow 5: Deep Profiling Investigation

Full profiling workflow from PMU counters to source-level hotspots.

bash
# Step 1: Collect PMU counters
agent-perf/tools/collect_pmu.sh <shermes> workload.js --json > /tmp/pmu.json

# Step 2: Check for anomalies
agent-perf/tools/pmu_anomalies.py -i /tmp/pmu.json

# Step 3: Profile with perf
perf record -g -- ./compiled_binary
perf report --stdio > /tmp/report.txt

# Step 4: Parse profile and find hot runtime functions
agent-perf/tools/profile_to_json.py -i /tmp/report.txt --json
agent-perf/tools/runtime_call_profiler.py -i /tmp/report.txt --json

# Step 5: Annotate source with hotspots
perf annotate --stdio > /tmp/annotate.txt
agent-perf/tools/annotate_source.py -i /tmp/annotate.txt

Workflow 6: Compiler Pass Analysis

Evaluate the contribution of individual optimization passes.

bash
# Measure marginal value of inlining
agent-perf/tools/pass_experiment.sh <shermes> workload.js --disable inlining --json

# Compare pass statistics between builds
agent-perf/tools/compiler_stats.sh <shermes> workload.js --json

Tool Summary Table

Tool Category Questions Answered
run_benchmarks.sh Benchmark How fast is this build? What are the ops/sec?
benchmark_compare.sh Benchmark Is B faster/slower than A? Statistically significant?
count_instructions.sh Benchmark How many instructions? Deterministic delta?
binary_size_compare.sh Binary Size .text growth? Call sites? Per-site overhead?
binary_size_analyzer.py Binary Size Which symbols grew? What category?
generated_code_analyzer.py Code Analysis Runtime call categories? Call density? Patterns?
generated_code_antipatterns.py Code Analysis C optimization inhibitors? GC root waste?
generated_code_report.sh Code Analysis Per-function metrics? A/B code diff?
sh_source_mapper.py Code Analysis JS line for C line? C lines for JS line?
type_coverage_analyzer.py Code Analysis Type coverage %? Eliminated calls?
collect_pmu.sh Profiling IPC? Cache/branch/TLB miss rates?
pmu_anomalies.py Profiling Microarch anomalies? Abnormal rates?
profile_to_json.py Profiling Hottest functions? Sample distribution?
annotate_disasm.py Profiling Hot assembly instructions?
annotate_source.py Profiling Hot source lines?
runtime_call_profiler.py Profiling Hot _sh_* functions? Top optimization targets?
js_antipattern_scan.py Anti-Pattern Slow JS patterns? eval/with/arguments?
antipattern_scan.py Anti-Pattern C++ anti-patterns? Pass-by-value?
compiler_stats.sh Compiler Pass counters? DCE/CSE/inline stats?
compilation_profiler.sh Compiler Stage bottleneck? JS-to-C vs C vs link?
pass_experiment.sh Compiler Marginal value of a pass?
regalloc_report.py Compiler Register pressure? Spill count?
gc_stats.sh GC GC overhead %? Allocation rate?
perf_proxies.sh Multi-Metric Overall health? Code size + density + latency?
  • .llms/rules/sh_perf_benchmarking.md — Performance benchmarking rules (opt-mode, metrics, statistics)
  • .llms/rules/sh_backend.md — SH code generation rules
  • .llms/rules/sh_runtime.md — SH runtime library rules
  • .llms/rules/sh_type_system.md — Typed mode rules
  • .llms/rules/sh_compiler_pipeline.md — Compilation pipeline rules
  • .llms/skills/sh_perf_investigation.md — Performance investigation workflow
  • schemas/perf_schema.json — JSON output format documentation

Testing

All Python tools have unit tests in tests/ using Python's built-in unittest
module. Tests use synthetic input data — no shermes binary, perf counters, or
build system required.

bash
# Run ALL tests (from xplat/static_h/)
python3 -m unittest discover -s agent-perf/tests -p 'test_*.py' -v

# Run a single test file
python3 -m unittest agent-perf/tests/test_generated_code_analyzer.py -v

# Run a specific test class
python3 -m unittest agent-perf.tests.test_generated_code_analyzer.TestAnalyzeGeneratedCode -v

# Run a specific test method
python3 -m unittest agent-perf.tests.test_generated_code_analyzer.TestAnalyzeGeneratedCode.test_basic_analysis -v

# Validate all shell scripts (syntax check only)
for f in agent-perf/tools/*.sh; do bash -n "$f"; done

Important: Always run from the xplat/static_h/ directory. Tests import
tools via relative path (sys.path.insert to agent-perf/tools/).


Overview (`API/hermes_abi/README.md`)

Experimental C based stable ABI for Hermes

This directory contains ongoing work to develop a stable C-based ABI for Hermes and an accompanying C++ JSI wrapper. It is a work in progress and is not supported for general use.

The goal of this ABI is to allow Hermes to be updated independently of the rest of a React Native application. Note that this does not immediately solve the general problem of ABI stability for RN extensions, since they are still written against the C++ JSI, and consume a C++ jsi::Runtime provided by RN. However, that is an eventual goal of this work.


Overview (`API/hermes_sandbox/README.md`)

Hermes Sandboxed Runtime implementation

This directory contains a sandboxed version of Hermes that is produced by compiling Hermes to wasm, and then compiling the resulting wasm to C using wasm2c. The sandboxed code exposes Hermes' C-API, and JSI is re-implemented on top of it.

The generated C is pre-generated and checked in to simplify the build. It will need to be periodically updated to incorporate bug fixes, and especially when there are changes to JSI or the bytecode version.

Re-generating the sandbox

Install emsdk by following the instructions here. Then download wasm2c from wabt.

Build hermesc for the host platform, in order to generate bytecode that is used by the Hermes build. The commands below use build_host to refer to this host build.

Generate the debug build with the following command. Note that it is built with O2 and without slow asserts to avoid slowing down execution too dramatically, since the generated C code for the debug build is then typically built without optimisations.

text
cmake -S <path to hermes> -B build_wasm_dbg \
  -DCMAKE_TOOLCHAIN_FILE=<path to emsdk>/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \
  -DIMPORT_HERMESC=build_host/ImportHermesc.cmake \
  -DCMAKE_BUILD_TYPE=Debug -DHERMES_UNICODE_LITE=ON \
  -DCMAKE_CXX_FLAGS=-O2  -DCMAKE_C_FLAGS=-O2 \
  -DCMAKE_EXE_LINKER_FLAGS="-sALLOW_MEMORY_GROWTH=1 -sSTACK_SIZE=256KB -sGLOBAL_BASE=16384" \
  -DHERMES_ENABLE_DEBUGGER=OFF -DHERMES_SLOW_DEBUG=OFF \
  -DHERMES_IS_MOBILE_BUILD=ON -G Ninja

Then generate the C artefact by running the following:

text
cmake --build build_wasm_dbg --target hermesSandboxImpl \
  && wasm2c build_wasm_dbg/API/hermes_sandbox/hermesSandboxImpl.wasm -n hermes --num-outputs 8 \
     -o <path to hermes>/API/hermes_sandbox/external/hermes_sandbox_impl_dbg_compiled.c

Likewise for the release build. Note that the release build is still built with -g2 to preserve function names. Since the generated C is going to be compiled anyway, this doesn't have any effect on the final size of release builds.

text
cmake -S <path to hermes> -B build_wasm_opt \
  -DCMAKE_TOOLCHAIN_FILE=<path to emsdk>/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \
  -DIMPORT_HERMESC=build_host/ImportHermesc.cmake \
  -DCMAKE_BUILD_TYPE=Release -DHERMES_UNICODE_LITE=ON \
  -DCMAKE_EXE_LINKER_FLAGS="-sALLOW_MEMORY_GROWTH=1 -sSTACK_SIZE=256KB -sGLOBAL_BASE=16384 -g2" \
  -DHERMES_ENABLE_DEBUGGER=OFF -DHERMES_IS_MOBILE_BUILD=ON -G Ninja

cmake --build build_wasm_opt --target hermesSandboxImpl \
  && wasm2c build_wasm_opt/API/hermes_sandbox/hermesSandboxImpl.wasm -n hermes --num-outputs 8 \
     -o <path to hermes>/API/hermes_sandbox/external/hermes_sandbox_impl_opt_compiled.c

The currently generated artefact was generated using emsdk 3.1.39, and wasm2c 1.0.33. Note that using a newer wasm2c will also require updating the checked in wasm-rt files in the external directory, which provide the runtime library for the generated code.


Overview (`API/napi/README.md`)

Hermes Node-API

This is an implementation of Node-API
(N-API) v10 for the Hermes JavaScript engine. It allows native addons written
against the Node-API ABI to run on Hermes without modification.

The implementation is built directly on Hermes VM internals (not JSI), covering
all non-experimental APIs through NAPI_VERSION 10. See
COMPATIBILITY.md for the full API support matrix.

Architecture

napi_value is a pointer to a PinnedHermesValue slot in a handle scope
block pool. Handle scopes, references, and other NAPI-managed state live in the
napi_env__ struct, which registers a custom GC roots function with the
Hermes Runtime.

Native addon callbacks are bridged through NativeConstructor trampolines.
Async work and thread-safe functions use a pluggable host integration interface
(hermes_napi_host) that the host application provides.

Source Files

File Purpose
hermes_napi.h Core env struct, handle scopes, refs, macros
hermes_napi_internal.h Internal helpers and trampoline declarations
hermes_napi.cpp Env lifecycle, GC root marking, module loading
hermes_napi_value.cpp Primitives, typeof, coercion, comparison
hermes_napi_string.cpp String create/extract (UTF-8, UTF-16, Latin-1)
hermes_napi_object.cpp Property operations, define_properties, freeze/seal
hermes_napi_function.cpp Functions, call, new_instance, define_class
hermes_napi_error.cpp Error creation and throwing
hermes_napi_reference.cpp Strong/weak references
hermes_napi_external.cpp External values (DecoratedObject)
hermes_napi_wrap.cpp Object wrapping and finalizers
hermes_napi_scope.cpp Handle scopes, instance data, cleanup hooks
hermes_napi_arraybuffer.cpp ArrayBuffer create, external, detach
hermes_napi_typedarray.cpp TypedArray (all 11 types)
hermes_napi_dataview.cpp DataView
hermes_napi_buffer.cpp Buffer (as Uint8Array)
hermes_napi_promise.cpp Promise create/resolve/reject
hermes_napi_bigint.cpp BigInt create/extract
hermes_napi_typetag.cpp 128-bit type tags
hermes_napi_tsfn.cpp Thread-safe functions
hermes_napi_async_work.cpp Async work
hermes_napi_async_context.cpp Async context and callback scopes

Building

bash
# Build the NAPI library
cmake --build <build-dir> --target hermesNapi

# Build and run unit tests
cmake --build <build-dir> --target NapiTests && <build-dir>/unittests/napi/NapiTests

# Run all NAPI test suites (CTS + vendored Node.js tests)
cmake --build <build-dir> --target check-napi

Testing

Tests are organized in three layers:

  • Unit tests (unittests/napi/) — 31 GTest files testing individual APIs
    in isolation.
  • Node.js NAPI tests (external/node-api-tests/) — ~65 test files
    vendored from the Node.js source tree. 33 pass, 31 skipped (due to Node.js
    module dependencies, experimental APIs, or Hermes-specific limitations).
    Zero failures.
  • node-api-cts (external/node-api-cts/) — The official conformance test
    suite (currently a WIP upstream project with minimal coverage).

Known Limitations

  • External strings (node_api_create_external_string_*) always copy the
    data. Hermes does not support external string storage.
  • napi_adjust_external_memory tracks the counter but does not influence
    GC scheduling.
  • napi_get_uv_event_loop returns the host-provided uv_loop if set in
    hermes_napi_host, otherwise returns napi_generic_failure.
  • Weak reference shutdown ordering — all weak references use a
    WeakRefSlot managed by the GC. Destroying refs during Runtime teardown
    can trigger assertions in debug builds because the ref lifetime outlives
    the Runtime. A few Node.js tests are skipped for this reason.
  • Experimental APIs (NAPI_EXPERIMENTAL) are not included.

Overview (`benchmarks/bench-runner/README.md`)

benchmark_runner

Introduction

bench-runner is a tool to run benchmarks and compare results, between different engines, and between different versions of the same engine. It is loosely based on gc_bench, but:

  • Doesn't try to get detailed GC statistics -- it just uses elapsed time, as reported by the benchmarks themselves. (NB: any benchmark run in this framework must report it's time, printing a line of the form "Time: ". That is, the difference between a Date() created at the start and end of execution.)
  • Does allow the benchmark to be run by multiple different engines.
  • Does allow runs with the same engine, before/after some change, to be compared.

Basic operation

To run the benchmarks:

text
python3 xplat/static_h/benchmarks/bench-runner/bench-runner.py --hermes -b ~/sworkspace/build_release/bin/hermes --cats v8

The result of this will be something like:

text
Ran each benchmark 1 times
Runtime:  hermes
General Stats:
                   Benchmark                       total
                                                  (time)
--------------------------------------------------------
                   v8-crypto           552.0  s +-  0.0%
                v8-deltablue           787.0  s +-  0.0%
                 v8-raytrace           163.0  s +-  0.0%
                   v8-regexp           329.0  s +-  0.0%
                 v8-richards           854.0  s +-  0.0%
                    v8-splay           139.0  s +-  0.0%

Arguments

bench_runner shares many arguments with gc_bench.

You can specify the set of benchmarks to run, either individually:

  • --benchmarks (or --bm) benchmark1 ... benchmarkN

or as a whole category:

  • --categories (or --cats) cat1 ... catN

The benchmark and categories names are available via --help.

Other arguments:

  • --count (-c) N: run each benchmark N times.
  • --keep-tmp (-k): retain bytecode files for hermes.
  • --verbose (-v): verbose output.
  • --debug (-d): debug output.
  • --hermes: use the hermes vm.
  • --synth: use the synthetic benchmark interpreter.

Some arguments are new in bench-runner, or have added options:

  • --binary (-b): path to the binary to run as the engine. Before was just hermes or synthtrace; now can be an arbitrary engine, like v8 or jsc.

  • --output-format (-f): format of the output. Was "ascii" or "tab-separated". To this we add "json": write the results as a json file, suitable as input for the merger program, described below.

  • --out file: write the output to the specified file. This is especially useful with "-f json".

  • --v8, --v8jitless: as with --hermes, specify that you want to run with v8 (or v8 with the --jitless flag). Note that this requires a --binary specification; it doesn't have a default for where v8 lives.

Using bench-merge

Say you want to compare v8 and hermes at some point in time, or hermes before and after some modification. Let's consider the latter case. I built a release build of hermes in the "before" revision, and then ran, from the bench-runner directory:

text
python3 bench-runner.py --hermes -b ~/sworkspace/build_release/bin/hermes --cats v8 octane -l before -f json --out ~/tmp/before.json -c 5

Then I moved to the "after" revision, again built hermes, and ran:

text
python3 bench-runner.py --hermes -b ~/sworkspace/build_release/bin/hermes --cats v8 octane -l after -f json --out ~/tmp/after.json  -c 5

python3 bench-merge.py ~/tmp/before.json ~/tmp/after.json > ~/tmp/before-after.txt

The last output file, before-after.txt, has output like:

text
runner                   benchmark                  Total time                       ratio
================================================================================================================
             hermes (before)                       box2d          1677.4  s +-  0.8%                       1.000
              hermes (after)                       box2d          1653.4  s +-  0.3%                       0.986
----------------------------------------------------------------------------------------------------------------
             hermes (before)                   code-load          2376.0  s +-  1.5%                       1.000
              hermes (after)                   code-load          2433.0  s +-  4.8%                       1.024
...
             hermes (before)                    v8-splay           115.4  s +-  0.8%                       1.000
              hermes (after)                    v8-splay           113.8  s +-  1.7%                       0.986
----------------------------------------------------------------------------------------------------------------
             hermes (before)                     geomean                                                   1.000
              hermes (after)                     geomean                                                   0.988

TBD:

  • Benchmarks must be modified to measure and print their own elapsed time, in a known engine-independent way. Will add more benchmarks.
  • Add jsc.
  • We should probably make the heap-size configs apply in some reasonable way to non-Hermes engines.
  • Add labels for comparisons between different versions of the same runtime. (The output above is from a later version that already has this.)
  • Might want a way to run, say, JSC vs Hermes in one command. (That might be a composite script of the commands in this diff.)

Overview (`benchmarks/imgui-demo/README.md`)

ImGUI Demo

Imported and modified from https://github.com/tmikov/gpt-scroller/tree/c-impl.


Overview (`benchmarks/jit-benches/README.md`)

Random benchmarks that we found useful while working on the JIT.
They should eventually be moved to appropriate directories.


Overview (`benchmarks/many-subclasses/README.md`)

This tests performance when accessing properties in a parent class via
instances of different child classes. The same function is called instances
of different child classes, but only accessed fields in their common parent
class.

many.js

This is the JS version of the code.

many-sh-1.js

This is the SH version of the code, with type annotation. It can be compiled
and executed with the following command:

text
shermes -typed many-sh-1.js && ./a.out

many-sh-2.js

Is a version of the benchmark where we have prevented inlinling of the bench()
function by returning it, which creates a second reference. The goal is to
examine the quality of the code in the non-inlined function.

A problem immediately become apparent: the types of N and res are not
number, which forces emitting of slower out-of-line instructions.

Another problem is that N is not treated as a constant.

The reason for this are two-fold:

  • We still don't fully type variables. That code is coming soon.
  • Type inference can't do its job, because the variables are initialized too
    late - in the middle of the function. That means there is a period of time
    where both are initialized to undefined (without TDZ; TDZ doesn't change
    the picture here). We can't provde that bench() isn't called at that time
    and doesn't see the undefined.

many-sh-3.js

It improves on many-sh-2.js by moving the initialization of res and N to
the top. Now N is finally treated as a constant, and we don't see undefined
as a valid type for res.

However another problem becomes apparent: we still infer that res can be a
string.

many-sh-4.js

It improves on the previous one by marking bench() as inline. This allows us
to inline the invocation, while also keeping the body around. This is slightly
faster, but still nowhere as performant as the fully inlined version.


Overview (`benchmarks/nbody/README.md`)

nbody benchmark

The Computer Language Benchmarks Game
https://salsa.debian.org/benchmarksgame-team/benchmarksgame/
contributed by Isaac Gouy
modified by Andrey Filatkin

License is BSD 3-Clause, see LICENSE.md

Tests floating point numerical computation and reading properties from an object.

There are type-annotated versions in fully-typed directory.

  • fully-typed/nbody.js: the original file was type-annotated with Flow types. Body was turned into ES6 class.
  • fully-typed/nbody.ts: copied from the flow-annotated file. The type declaration for built-in functions (e.g. print()) are not yet available, so the use site was ts-ignored.

Overview (`benchmarks/octane/README.md`)

Octane

Obtained from this fork of Octane at 2bfcf3dcec9353fe836028964569eca1c3dd876c.

The files in this directory are modified by putting the related source files together so that each file is individually complete and executable. Each file is prefixed with the base.js that was originally available in the repository.

The following files are are further concatenated:

  1. gbemu.js includes both gbemu-part1.js and gbemu-part2.js
  2. typescript.js includes all of typescript.js, typescript-input.js and typescript-compiler.js
  3. zlib.js include both zlib.js and zlib-data.js.

Furthermore, original zlib benchmark's code is entirely contained in a single string that is then passed to eval in zlib-data.js. The original content in zlib-data.js has been modified with the following transformation:

  1. Get the body of the benchmark by replacing eval() with print ().
  2. Apply prettier.
  3. Expose a dummy function read(), which isn't used but is referred to.

Finally, at the end of each file, a call to BenchmarkSuite.RunSuites is invoked with the corresponding Print functions.


Overview (`benchmarks/raytracer/README.md`)

Raytrace Benchmark

Copied from https://github.com/microsoft/TypeScriptSamples/tree/b675f11d4ef8b7f94aa563586332415407bfe168/raytracer .


Overview (`benchmarks/sqlite-read/README.md`)

Reading an SQLite3 DB into memory

This benchmark compares the time it takes to read an sqlite3 database into memory in C++ and Static Hermes.

The actual DB API is mocked using a very simple implementation in sqmock.cpp, implementing only a few
SQLite3 APIs.


Overview (`benchmarks/widgets/README.md`)

widgets benchmark

Features used:

  • classes (inheritance, methods, field lookups)
  • array methods and Map

Directories:

  • original/: the original Flow annotated benchmark.
  • single-file/: packaged in a single file.
  • single-file/cpp/: C++ version.
  • simple-classes/: uses only Flow classes, no other complex types.

The /stripped/ subdirectory in every case contains the JS without Flow type
annotations.

The /es5/ subdirectory, where present, contains a version of the code lowered
to ES5. When lowering a single file only Babel is used, but when lowering
multuple modules we use Webpack.


Overview (`doc/blog/README.md`)

Hermes Blog

Technical articles and deep dives into Hermes internals.

Posts

2026

2025

2024


Overview (`examples/ffi/README.md`)

FFI Examples

This directory contains examples of using the FFI to call C functions from Static Hermes.

These examples are not meant to be production quality, but rather to demonstrate
the basic usage of the FFI. They don't necessarily have robust error checking.
Only ASCII strings are supported (no UTF-8, though it would not be difficult to add).

For now these examples have only been tested on MacOS, but they should work on Linux.

getenv.js

A very simple example reading the PATH environment variable and printing it.
To run it:

shell
shermes -typed -exec getenv.js

fopen.js

An example demonstrating fopen(), fread() and fclose(). To run use:

shell
shermes -typed -exec fopen.js

sqlite.js

Demonstrates using libsqlite3. Opens a databas, executes a query, and prints the results.

This example requires that libsqlite3 be installed on your system. On MacOS, it is already installed.
On Linux you may need to install it. You may need to supply -L and -Wc,-I flags to shermes.

shell
# First create the database
./init_db.sh
# Then run the example
shermes -typed -exec sqlite.js -lsqlite3

Overview (`external/asmjit/README.md`)

Imported AsmJIt from https://github.com/asmjit/asmjit.git
2024-08-11
330aa64386f394e090eb1062c645f9d021a761bc

Deleted .git, .github and .gitignore


Overview (`external/boost/README.md`)

This directory contains a slightly modified partial copy of Boost needed by Boost.Context.

  1. Downloaded https://archives.boost.io/release/1.86.0/source/boost_1_86_0.tar.
    bz2.
  2. Copied only the parts needed by Boost.Context.
  3. Deleted boost/context/continuation*.hpp since only fiber is needed.
  4. Deleted boost/context/pooled_fixedsize_stack.hpp because of its
    dependency on intrusive_ptr.
  5. Removed unused include of intrusive_ptr.hpp from
    boost/context/fiber_fcontext.hpp.
  6. Minimal updates to libs/context/CMakeLists.txt to remove other library
    dependencies, remove library alias, support CMAKE_OSX_ARCHITECTURES
  7. Rename the boost namespace to hoost using a preprocessor define.
  8. Rename the xxx_fcontext() globals to have a hoost_ prefix.
  9. Change the include path from boost/ to hoost/.

Overview (`external/dragonbox/README.md`)

Downloaded dragonbox.h from https://github.com/jk-jeon/dragonbox/blob/master/include/dragonbox/dragonbox.h, and placed under external/dragonbox/dragonbox/
Downloaded 2025-12-26
Git commit: beeeef91cf6fef89a4d4ba5e95d47ca64ccb3a44

Patches

Worked around an MSVC VS2019 (vs16.11.31) bug with template template
parameter forwarding in a class template's default non-type argument. The
upstream SFINAE pattern bool = is_in_range(0) as a default
template argument of compute_impl triggers MSVC errors C2672/C3207 because
the compiler fails to resolve Info as a class template in that context.
Wrapped the call in a helper struct is_in_range_v and changed the default
to is_in_range_v<...>::value; MSVC handles the struct instantiation
correctly. Clang and GCC compile both forms.
Patch: patches/msvc-is-in-range-wrapper.patch.


Overview (`external/esprima/README.md`)




Esprima (esprima.org, BSD license) is a high performance,
standard-compliant ECMAScript
parser written in ECMAScript (also popularly known as
JavaScript).
Esprima is created and maintained by Ariya Hidayat,
with the help of many contributors.

Features

API

Esprima can be used to perform lexical analysis (tokenization) or syntactic analysis (parsing) of a JavaScript program.

A simple example on Node.js REPL:

javascript
> var esprima = require('esprima');
> var program = 'const answer = 42';

> esprima.tokenize(program);
[ { type: 'Keyword', value: 'const' },
  { type: 'Identifier', value: 'answer' },
  { type: 'Punctuator', value: '=' },
  { type: 'Numeric', value: '42' } ]
  
> esprima.parseScript(program);
{ type: 'Program',
  body:
   [ { type: 'VariableDeclaration',
       declarations: [Object],
       kind: 'const' } ],
  sourceType: 'script' }

For more information, please read the complete documentation.


Overview (`external/fast_float/README.md`)

Downloaded fast_float.h from https://github.com/fastfloat/fast_float/releases, and placed under external/fast_float/fast_float/
Version 8.1.0, downloaded 2025-11-5
88b1e5321c6918cbe091afd76728296290668403


Overview (`external/flowtest/README.md`)

Flow git commit: 0d843c455cf38fb059d1b693afe59c4f4360b5d4


Overview (`external/llvh/README.md`)

LLVH

This is a copy of LLVM libraries and headers that are used by Hermes.
The copy was made from https://github.com/llvm-mirror/llvm.git with
hash c179d7b006348005d2da228aed4c3c251590baa3 .

gtest patches

There's a bug in googletest-1.8.0 when compiling on Windows, with clang,
with exceptions disabled: GTEST_HAS_EXCEPTIONS isn't defined to the correct
value.
Pulled in this fix: https://github.com/google/googletest/commit/3498a1ac52deb83f30b8170c78bfba9dc6227198
from googletest-1.10.0 into googletest-1.8.0.
It only modifies ./utils/unittest/googletest/include/gtest/internal/gtest-port.h.

Fixed cxxabi.h detection for MSVC under Wine. MSVC doesn't have cxxabi.h, and
__has_include is buggy under Wine (see https://bugs.winehq.org/show_bug.cgi?id=54130).
Patch: ./utils/unittest/googletest/patches/gtest-port-msvc-cxxabi.patch.

Added UniversalTersePrinter specialization for std::nullptr_t to fix MSVC
ambiguous operator<< error.
Patch: ./utils/unittest/googletest/patches/gtest-printers-nullptr.patch.

Added UniversalTersePrinter specialization for char16_t*. MSVC explicitly
deletes operator<< for char16_t* in <ostream>. Uses proper C-style
escaping consistent with gtest's char*/wchar_t* string printing.
Patch: ./utils/unittest/googletest/patches/gtest-printers-char16.patch.

Suppressed -Wundef warnings in gtest.h and gmock.h by wrapping the
header contents with #pragma GCC diagnostic push/ignored/pop. These headers
use #if on macros that may not be defined, which is harmless but noisy under
-Wundef. Files modified:

  • ./utils/unittest/googletest/include/gtest/gtest.h
  • ./utils/unittest/googlemock/include/gmock/gmock.h

LIT patches

In order to fix some Windows python3 problems, it was necessary to pull in some
updates to the LIT testing tool from LLVM.
The version used was LLVM release v9.0.1,
commit hash c1a0a213378a458fbea1a5c77b315c7dce08fd05, from November 27, 2019.

This was done like so:

text
git clone https://github.com/llvm/llvm-project.git
cd llvm-project
git checkout c1a0a213378a458fbea1a5c77b315c7dce08fd05
cp -r ./llvm/utils/lit/* /path/to/hermes/external/llvh/utils/lit

Some copied files were omitted because we don't use them
(like lit self-tests and examples).

Update: pull in the run_under argument in googletest.py, from LLVM release
v20.1.8, commit hash 87f0227cb60147a26a1eeb4fb06e3b505e9c7261.

Skip empty RUN lines after substitution in TestRunner.py. When a
substitution replaces a RUN line with an empty string, the line is dropped.
If all RUN lines are dropped, the test is marked UNSUPPORTED. This allows
lit.cfg to disable specific tools (e.g. %shermes) by substituting them
with a marker and then replacing the entire line with an empty string.
Patch: patches/lit-skip-empty-run-lines.patch.

Other imports from v9.0.1

Support/JSON and Support/ToolOutputFile.

Rename to LLVH

To avoid conflicts when linking with mainline LLVM, the namespace and include directories were
manually renamed to llvh. That left renaming some C public symbols. Unused files under llvh-c/
were removed and lastly a manual rename of the remaining global symbols was performed. The last
step is recorded in patches/rename-c-interface.patch.

abi-breaking.h

Upstream LLVM generates abi-breaking.h via CMake's configure_file(),
defining LLVM_ENABLE_ABI_BREAKING_CHECKS and LLVM_ENABLE_REVERSE_ITERATION.
LLVH does not use the LLVM build system, so include/llvh/Config/abi-breaking.h
is a hand-written header that defines both macros to 0. Without these
definitions, #if LLVM_ENABLE_ABI_BREAKING_CHECKS silently evaluates to 0
(correct behavior, but produces -Wundef warnings).


Overview (`external/node-api-cts/README.md`)

Node-API Conformance Test Suite (`node-api-cts`)

A test suite for Node-API implementors across different JS engines and runtimes.
Written in ECMAScript & C/C++ with an implementor customizable harness.

[!IMPORTANT]
This repository is currently a work-in-progress and shouldn't yet be relied on by anyone.

Overview

Tests

The tests are divided into three buckets:

  • tests/harness/* exercising the implementor's test harness.

and two parts based on the two header files declaring the Node-API functions:

  • tests/js-native-api/* testing the engine-specific part of Node-API defined in the js_native_api.h header.
  • tests/node-api/* testing the runtime-specific part of Node-API defined in the node_api.h header.

Implementors

This repository offers an opportunity for implementors of Node-API to maintain (parts of) their implementor-specific harness inside this repository, in a sub-directory of the implementors directory. We do this in hope of increased velocity from faster iteration and potentials for reuse of code across the harnesses.

We maintain a list of other runtimes implementing Node-API in doc/node-api-engine-bindings.md of the nodejs/abi-stable-node repository.


Overview (`lib/InternalJavaScript/README.md`)

Internal Bytecode

Promise

The Promise Internal Bytecode is generated on the fly by running:

sh
$ utils/gen-promise-internal-bc.sh

Iterator helpers

The Iterator Helper polyfill is adapted from https://github.com/es-shims/iterator-helpers/ at commit fbd6ffee76f28c0aa1f99180d6971caa6a36f39c. The Iterator.concat polyfill was later adapted at commit b790e7409435f3bd3f43edbf9dc1d962818d8f46. The required dependencies are taken and flattened in 05-IteratorHelperDependencies.js and the actual Iterator implementation are followed after the file. The dependencies may be simplified from the original source. Assertions from the polyfill, which threw TypeErrors, are removed.

Note the original polyfill's dependencies included many polyfills for features that are supported by Hermes. In those cases, they are may be removed and use Hermes implementation directly.
Examples (non-exhausive):

  • Errors are supported so its polyfill implementation is removed.
  • Checks for Symbols support are removed since Hermes support Symbols.
  • Hermes implements Function.prototype.bind, so the polyfill for this is removed.

The implementation for Iterator.prototype.constructor and Iterator.prototype [ %Symbol.toStringTag% ] differs from the original polyfill in order to adhere to spec compliance. The changes uses the polyfill for the abstract operation SetterThatIgnoresPrototypeProperties to help with the implementation.

At the time of adaptation, the original polyfill is based off the proposal at https://tc39.es/proposal-iterator-helpers/. This differs slightly from the spec, and the polyfill reflects some of these changes:

  1. The original polyfill uses this instanceof methodName to guard against constructor calls. This triggers [[GetPrototypeOf]] on the receiver, which is observable via Proxy and violates the spec's expected side-effect ordering. Replaced with new.target !== undefined, which is not observable and correctly detects constructor calls.
  2. An IteratorRecord with undefined [[NextMethod]] is created prior to argument checking. The original polyfill checks the argument first, then obtains the IteratorRecord directly via GetIteratorDirect.
  3. The ToPrimive Abstract Operation in the polyfill is not spec compliant. It should check %Symbol.toPrimitive%, which has been added in our adaption of the polyfill.
  4. The SLOT.assert function from the internal-slot package calls channel.assert, which uses objectInspect to build its error message. objectInspect calls Object.prototype.toString.call(obj), which accesses obj[Symbol.toStringTag] — an observable side effect on Proxy objects. Removed the channel.assert call since the subsequent SLOT.has check is sufficient and does not trigger any observable property accesses.

Overview (`tools/hcdp/README.md`)

Usage

In the hcdp directory:

  1. Build hcdp binary using BUCK or CMake
  2. Install npm packages
text
npm install
  1. Run hcdp.js, passing it the path to the hcdp binary, and the script to be debugged.
    e.g.:
text
node ./hcdp.js ~/hcdp ~/loop.js

Implementation

The hcdp tool carries out Hermes debugging via CDP. There are
two main parts to the tool: a JavaScript component and a C++ component.

C++

The C++ component uses the Hermes API to host the objects required to carry out a debugging session (Hermes Runtime, CDP Debug API, CDP Agents). It accepts IPC messages instructing it when to create/destroy a CDP Agent or handle a message. It emits IPC messages for responses and notifications emitted from CDP Agents.

JavaScript

The JavaScript component hosts a websocket server to communicate with debug clients, prints CDP messages as they flow back and forth, and handles keyboard input from the user.

Incoming websocket messages from the debug client are converted to IPC messages to the C++ component. IPC messages from the C++ component are converted to websocket messages back to the debug client.

IPC Messages

IPC messages carry a type, agentId, and optionally message. The following message types are supported:

  • C - Connect. agentId indicates the unique ID of the new agent to be created. message is unused.

  • M - Message. agentId indicates the ID of the previously-created agent that should handle the message.

  • D - Disconnect. agentId indicates the ID of the previously-created agent to be destroyed. message is unused.


Overview (`tools/secdiff/README.md`)

secdiff

Section-by-section interactive diff tool using Python's curses.

Input:
A file with some number of sections that have lines delimiting the sections.

Usage:

text
secdiff.py [-h] -s SEPARATOR filename

Provide a separator marker with '-s' and the file to diff the sections of.

Keybindings:

  • l/h: Go to the next/prev pair of sections to diff.
  • j/k: Scroll down/up in the current diff.

Overview (`unsupported/juno/README.md`)

Juno

Use of Juno is currently unsupported

Juno is an optimizing compiler which consumes and outputs JavaScript.


Overview (`utils/testsuite/README.md`)

Hermes Test Runner

How to run

The runner requires two paths:

  1. Path to the test suite directory that we support (i.e., test262, mjsunit,
    esprima, flow and a CVE testsuite). This can be
    a list separated by whitespace, so you can run multiple test suites at once.
  2. Path to the directory of Hermes binaries, including hermes (andshermes
    depending on extra flags).

Example usage:

sh
# ./build is the cmake build directory.

# flow
./utils/test_runner.py external/flowtest/test/flow -b ./build/bin

# esprima
.utils/test_runner.py  external/esprima/test_fixtures -b ./build/bin

Use --compile-args and --vm-args if you want to apply extra compliler or VM
flags to run the testsuite. By default, we use the below flags for test262 and
the CVE testsuite:

sh
# For hermes compiler
-test262 -fno-static-builtins -Xes6-block-scoping -Xenable-tdz
# For hermes VM (running the bytecode)
-Xes6-proxy -Xhermes-internal-test-methods -Xmicrotask-queue

Since esprima testsuite includes only AST tests, we simply use -dump-ast. For
flow testsuite, we use additionally below flags:

sh
-parse-flow -Xparse-component-syntax -parse-jsx -Xinclude-empty-ast-nodes -Xparse-flow-match

Skiplist

skiplist.json is a config file that contains a list of tests that we want to
skip by default. It has several sections:

  • "manual_skip_list": This is managed manually. We usually put tests that we plan
    to support soon here and remove them manually once it's supported.
  • "skip_list": We put all tests that currently fail due to spec incompliance or
    unsupported feature here.
  • "lazy_skip_list": Tests we want to skip when "--lazy" is passed.
  • "permanent_skip_list": Tests we do not plan to fix or support.
  • "handlesan_skip_list": Tests that should be run with "-gc-sanitize-handles=0".
  • "unsupported_features": Tests with unsupported features in test262. We may
    support them in the future.
  • "permanent_unsupported_features": Tests with unsupported features in 262. We
    don't have a plan to support them.
  • "intl_tests": Tests requires INTL to be enabled.
  • "platform_skip_list": Tests that need to be skipped depending on platforms.

Run with flag --test-skiplist to force running tests that are included in the
skiplist.json config (all other sections, and "lazy_skip_list" if --lazy is
given, "intl_tests" if --test-intl is given).

After it finishes, all passed tests included in the skiplist config file will be
printed, and you can tell the runner to remove these tests from the config by
following the prompt (we will only update "skip_list" and "permanent_skip_list"
section for now). Note that if some tests are covered by a folder path in the
config, the test runner will first find all tests under that folder, exclude
those passed, and insert the rest into the config file. If you don't want this
behavior for a specific folder, please add it to the manual list with a comment
explaining the reason.


Overview (`API/hermes/extensions/README.md`)

Hermes Extensions

This directory contains JSI-based extensions that add functionality to the Hermes JavaScript runtime.

Why JSI?

Extensions use JSI (JavaScript Interface) rather than internal Hermes APIs. This provides:

  • Stability: JSI is a stable public API. Extensions won't break when Hermes internals change.
  • Portability: JSI works with any JSI-compatible runtime, not just Hermes.
  • Simplicity: JSI is easier to use than internal VM APIs.

Directory Structure

text
extensions/
├── 00-header.js          # IIFE wrapper (do not modify)
├── 99-footer.js          # IIFE wrapper (do not modify)
├── 20-Dummy.js           # Example core extension (JS)
├── Dummy.h/cpp           # Example core extension (C++)
├── Extensions.h/cpp      # Extension installation orchestration
├── Intrinsics.h/cpp      # Captures built-in constructors
├── JSIUtils.h/cpp        # Shared utility functions for extensions
├── ExtensionsBytecode.*  # Generated bytecode embedding
└── contrib/              # Community-contributed extensions
    └── README.md         # Contributor guidelines

How Extensions Work

Each extension has two parts:

  1. JavaScript file (NN-ExtensionName.js): Defines a setup function assigned to extensions.ExtensionName. The numeric prefix controls load order.

  2. C++ files (ExtensionName.h/cpp): Calls the JS setup function, optionally passing native helper functions.

At runtime:

  1. All JS files are concatenated and compiled to bytecode at build time
  2. The bytecode is embedded in the binary and executed at startup
  3. Each extension's C++ installer retrieves and calls its JS setup function

Adding an Extension

Core Extensions

Core extensions are maintained by the Hermes team. To add one:

  1. Create NN-ExtensionName.js with your setup function
  2. Create ExtensionName.h and ExtensionName.cpp
  3. Add the C++ file to EXTENSIONS_CPP_SOURCES in CMakeLists.txt
  4. Call your installer from Extensions.cpp

Community Contributions

See contrib/README.md for guidelines on contributing extensions.

Intrinsics

The Intrinsics.h/cpp files capture references to built-in constructors (like TypeError, Uint8Array) before any user code runs. Extensions should use these captured intrinsics rather than reading from globals, which users might have replaced.

cpp
#include "Intrinsics.h"

// Use captured TypeError instead of global
throwTypeError(rt, "Expected a string");

JSI Utilities

The JSIUtils.h header provides common utilities for working with JSI, such as TypedArray buffer access and safe integer conversion. Include it to get Intrinsics.h as well. See the header for documentation.


Overview (`benchmarks/MiniReact/no-deps/README.md`)

This is the MiniReact benchmark with the import statements deleted.

The relevant dependencies have been inlined when possible, e.g. invariant.


Overview (`benchmarks/MiniReact/no-objects/README.md`)

This is the no-objects MiniReact benchmark with objects and other unsupported
features removed.

Setup

text
(cd ~/builds; cmake -S ~/fbsource/xplat/static_h/benchmarks/ -B benchmarksdebug -G Ninja -DHERMES_BUILD=~/builds/shdebug -DHERMES_SRC=~/fbsource/xplat/static_h) && (cd ~/fbsource/xplat/static_h/benchmarks/build-helpers/flow-bundler; yarn install)

Run with Static Hermes

text
~/fbsource/xplat/static_h/benchmarks/MiniReact/no-objects/build.sh && (cd ~/builds/shdebug/; ninja hermesvm shermes && ./bin/shermes -typed -exec -g ~/fbsource/xplat/static_h/benchmarks/MiniReact/no-objects/out/simple.js)

Run with Hermes

text
~/fbsource/xplat/static_h/benchmarks/MiniReact/no-objects/build.sh && (cd ~/builds/shdebug/; ninja hermesvm hermes && ./bin/hermes -exec -g ~/fbsource/xplat/static_h/benchmarks/MiniReact/no-objects/out/simple-lowered.js)

Overview (`benchmarks/build-helpers/flow-bundler/README.md`)

Flow Bundler

This bundler allows providing any number of entry points then producing a single
"bundle" file that includes all entrypoints and any imported files. The bundler
does not do any lowering so maintains all Flow typing needed for Static Hermes.

Usage

First install the needed npm dependencies:

text
yarn install
// or
npm install

Basic usage:

text
./benchmarks/build-helpers/flow-bundler/bin/flow-bundler \
  --out output/path/of/bundle.js
  --root project/root
  path/to/entrypoint1.js
  path/to/entrypoint2.js

For more detailed usage see "help":

text
./benchmarks/build-helpers/flow-bundler/bin/flow-bundler --help

Overview (`benchmarks/raytracer/original/README.md`)

TypeScript Sample: Raytracer

Overview

This sample shows a raytracer implementation in TypeScript.

Running

text
tsc --sourcemap raytracer.ts
start raytracer.html

Overview (`benchmarks/widgets/original/README.md`)

Original Widgets Benchmark

main.js is a copy of main.mjs used only by Webpack.


Overview (`benchmarks/widgets/simple-classes/README.md`)

simple classes version

  • no static
  • no tuple or object types

Overview (`benchmarks/widgets/single-file/README.md`)

single file widgets benchmark

Concatenate all the files in original together in an order which typechecks with flow.
Remove all uses of import/export.


Overview (`external/asmjit/asmjit/README.md`)

AsmJit

AsmJit is a lightweight library for machine code generation written in C++ language.

See asmjit.com page for more details, examples, and documentation.

Documentation

Contributing

Breaking Changes

Breaking the API is sometimes inevitable, what to do?

Project Organization

  • / - Project root
    • src - Source code
      • asmjit - Source code and headers (always point include path in here)
        • core - Core API, backend independent except relocations
        • arm - ARM specific API, used only by ARM and AArch64 backends
        • x86 - X86 specific API, used only by X86 and X64 backends
    • test - Unit and integration tests (don't embed in your project)
    • tools - Tools used for configuring, documenting, and generating files

Ports

  • 32-bit ARM/Thumb port (work in progress)
  • RISC-V port (not in progress, help welcome)

Support

Notable Donors List:

Authors & Maintainers


Overview (`external/boost/boost_1_86_0/README.md`)

Boost C++ Libraries

The Boost project provides free peer-reviewed portable C++ source libraries.

We emphasize libraries that work well with the C++ Standard Library. Boost
libraries are intended to be widely useful, and usable across a broad spectrum
of applications. The Boost license encourages both commercial and non-commercial use
and does not require attribution for binary use.

The project website is www.boost.org, where you can obtain more information and
download the current release.


Overview (`external/node-api-tests/hermes/README.md`)

Hermes Node.js NAPI Test Harness

This directory contains the harness for running the Node.js NAPI test
suite under Hermes. The test sources in ../js-native-api/ and
../node-api/ are vendored from Node.js.

Patches to Vendored Tests

The following vendored test files have been patched for Hermes
compatibility. All patches are marked with [Hermes patch] comments.

V8-specific error message regexes

Hermes and V8 use different error message formats for strict-mode
TypeError. For example:

text
V8:     "Cannot assign to read only property 'x' of object '#<Object>'"
Hermes: "Cannot assign to read-only property 'x'"

V8:     "Cannot set property x of #<Object> which has only a getter"
Hermes: "Cannot assign to property 'x' which has only a getter"

The assert.throws() regexes in these tests were relaxed to accept both
formats while still verifying the correct error type and property name:

  • js-native-api/test_properties/test.js
  • js-native-api/test_constructor/test.js
  • js-native-api/6_object_wrap/test.js

Strict Mode Detection

The harness prepends assert/common/require code before each test, which
moves 'use strict' directives out of top-of-script position. To
compensate, run-node-tests.cmake detects 'use strict' at the top of
the original test source (after leading comments) and passes -strict
to hermes.


Overview (`public/hermes/Public/README.md`)

The Public directory contains type declarations made available to both the
Hermes runtime and the Hermes API. The idea is to give both projects a shared
vocabulary of types to reduce the need for bridging and glue code.

The Public directory may not use types from the Hermes, VM, or API layers, or
LLVM types. It may use std types and its own types only.

Configuration

Most Runtime constructor parameters are passed in through configuration structs
created using the macros defined in CtorConfig.h. Below is an example:

text
#define FOO_FIELDS(F)          \
  F(size_t, Bar, 0)            \
  F(std::string, Baz, "Hello") \
  /* FOO_FIELDS END */

_HERMES_CTORCONFIG_STRUCT(FooConfig, FOO_FIELDS, {
  Bar_ = std::min(Bar_, Baz_.size());
});

This defines a FooConfig struct, with two fields, with the following types,
names and default values:

text
size_t Bar_ = 0;
std::string Baz_ = "Hello";

FooConfig's default constructor produces a config with all default values.

Accessors

Each parameter has an associated getter, in this example FooConfig has:

text
size_t FooConfig::getBar() const;
std::string FooConfig::getBaz() const;

Building

Each config also has a corresponding Builder type -- in this case
FooConfig::Builder -- which is the preferred way to override defaults:

text
auto fooConfig =
  FooConfig::Builder()
    .withBar(10)
    .withBaz("Hello, world!")
    .build();

This defines a new config, fooConfig, where Bar = 10 and Baz = "Hello, world!". Note that

  • every field is optional,
  • if omitted a field retains its default value,
  • and the underlying FooConfig is obtained through a call to
    FooConfig::Builder::build().

It is possible to query a builder on whether a given field has been overridden:
A call to bool FooConfig::Builder::hasBar() const returns true on a given
builder if and only if the builder has had withBar(...) called on it
previously.

Validation and Consistency

The config definition can also include arbitrary logic that is run when the
config is built (The last parameter to _HERMES_CTORCONFIG_STRUCT). This is
typically used to enforce constraints between parameters. If this is not
required, an empty block can be supplied (i.e. {}). In the example it
ensures the value of Bar never exceeds the length of the string in Baz.

The body of the logic has mutable access to all the variables in the config.
However, note that their names are suffixed with underscores.. It also
provides access to the builder (useful for querying whether the fields were
explicitly overridden or not). The body should not return anything.

NB The default values of the config are assumed to be unchanged by the
supplied logic. As a result, the logic is not run as part of the config's
default constructor.

Rebuild

It is possible to modify the contents of an existing config, but only by
converting it back into a builder, using

text
FooConfig::Builder FooConfig::rebuild() const;

which returns a fresh builder with fields populated from the config it was
called on.

NB Fields in the fresh builder are not considered overridden, even if their
values diverge from the config's defaults.


Overview (`test/SourceMap/translator/README.md`)

Merge Input Source Map with debug info Tests

To update these tests, first update the .js file that you want to update.

throw.js

Tests exception stack traces.

Note: make sure each function occupies a separate line so that line numbers
in the symbolicated traces will match the original trace.
Then, run uglify (can be installed via npm install uglify-js)
For example:

text
uglifyjs throw.js --source-map -o throw.min.js

This will minify the files and place a source map which can be used for testing
in the directory.

prog1.js

Tests compile time errors.

To update this test, run:

text
../../../benchmarks/build-helpers/flow-bundler/bin/flow-bundler --root=prog1 prog1/index.js --out=prog1.js

prog2/ directory.

Tests automatic source map detection. To regenerate, from within that directory run npm install as
needed and run ./gen.sh


Overview (`test/hermes/contrib/README.md`)

Contrib Extension Tests

This directory contains tests for community-contributed extensions.

Automatic Exclusion

Tests in this directory are automatically skipped when building with:

bash
cmake -B build -DHERMES_ENABLE_CONTRIB_EXTENSIONS=OFF

This is handled by lit.local.cfg which checks for the contrib_extensions feature.

Adding Tests

When adding a new contrib extension, place its tests in this directory following the standard Hermes test format:

javascript
/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

// RUN: %hermes %s | %FileCheck %s --match-full-lines

print("Hello from my extension");
// CHECK: Hello from my extension

Tests here use the same Lit infrastructure as other Hermes tests. See test/hermes/ for more examples.


Overview (`tools/fuzzers/fuzzilli/README.md`)

Fuzzing with Fuzzilli

Get Fuzzilli

shell
git clone https://github.com/googleprojectzero/fuzzilli.git

Setup Profiles for Hermes

shell
mv profile/*.swift $FUZZILLI_LOCATION/Sources/FuzzilliCli/Profiles/

Build Instrumented Binary

shell
mkdir fuzzilli_build && cd fuzzilli_build
cmake .. -DHERMES_ENABLE_FUZZILLI=ON -DHERMES_ENABLE_TRACE_PC_GUARD=ON $OTHER_FLAGS
make fuzzilli

Start Fuzzing:

shell
cd $FUZZILLI_LOCATION
swift run  FuzzilliCli --profile=hermes $BINARY_LOCATION

Overview (`API/hermes/extensions/contrib/README.md`)

Contributing Extensions to Hermes

This directory contains community-contributed extensions. These extensions are maintained by their contributors and the community, not by the Hermes team.

What This Means

  • Community Support: If you encounter issues with a contrib extension, reach out to its author or the community rather than the Hermes team.
  • Opt-Out Available: Contrib extensions can be disabled for critical deployments with -DHERMES_ENABLE_CONTRIB_EXTENSIONS=OFF.
  • Promotion Path: Popular, well-maintained extensions may be promoted to core if they become widely adopted.

Requirements for Acceptance

  1. JSI Only: Extensions must use only JSI APIs, not internal Hermes APIs.
  2. Tests Required: Include .js test files using Lit (same format as Hermes tests).
  3. Follow Patterns: Match the existing extension structure (see ContribDummy example).
  4. Documentation: Provide clear documentation of what your extension does.

How to Add a Contrib Extension

Step 1: Create the JavaScript File

Create NN-YourExtension.js where NN is a two-digit number controlling load order:

javascript
/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

// YourExtension description
  extensions.YourExtension = function(nativeHelpers) {
    // Setup code here
    // nativeHelpers contains any native functions passed from C++
  };

Step 2: Create the C++ Header

Create YourExtension.h:

cpp
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#ifndef HERMES_EXTENSIONS_CONTRIB_YOUREXTENSION_H
#define HERMES_EXTENSIONS_CONTRIB_YOUREXTENSION_H

#include <jsi/jsi.h>

namespace facebook {
namespace hermes {

void installYourExtension(jsi::Runtime &runtime, jsi::Object &extensions);

} // namespace hermes
} // namespace facebook

#endif

Step 3: Create the C++ Implementation

Create YourExtension.cpp:

cpp
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#include "YourExtension.h"
#include "../Intrinsics.h"  // If you need intrinsics

namespace facebook {
namespace hermes {

void installYourExtension(jsi::Runtime &rt, jsi::Object &extensions) {
  // Create native helpers object if needed
  jsi::Object nativeHelpers = jsi::Object(rt);

  // Add native functions (optional)
  nativeHelpers.setProperty(rt, "myNativeFunc",
    jsi::Function::createFromHostFunction(rt,
      jsi::PropNameID::forAscii(rt, "myNativeFunc"),
      1,  // argument count
      [](jsi::Runtime &rt, const jsi::Value &,
         const jsi::Value *args, size_t count) -> jsi::Value {
        // Implementation
        return jsi::Value::undefined();
      }));

  // Get and call the setup function
  jsi::Function setup = extensions.getPropertyAsFunction(rt, "YourExtension");
  setup.call(rt, nativeHelpers);
}

} // namespace hermes
} // namespace facebook

Step 4: Update Build and Registration

All updates are within the contrib/ directory:

a) Add to CMakeLists.txt:

cmake
set(CONTRIB_EXTENSIONS_CPP_SOURCES
  ${CMAKE_CURRENT_SOURCE_DIR}/ContribExtensions.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/ContribDummy.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/YourExtension.cpp  # Add this
  PARENT_SCOPE
)

b) Add to ContribExtensions.cpp:

cpp
#include "ContribExtensions.h"

#include "ContribDummy.h"
#include "YourExtension.h"  // Add this

void installContribExtensions(jsi::Runtime &rt, jsi::Object &extensions) {
  installContribDummy(rt, extensions);
  installYourExtension(rt, extensions);  // Add this
}

Step 5: Add Tests

Create test files in test/hermes/contrib/ following the Lit test format:

javascript
/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

// RUN: %hermes %s | %FileCheck %s --match-full-lines

// Test your extension here
print("Expected output");
// CHECK: Expected output

Tests in test/hermes/contrib/ are automatically skipped when contrib extensions are disabled.

Disabling Contrib Extensions

To build Hermes without contrib extensions:

bash
cmake -B build -DHERMES_ENABLE_CONTRIB_EXTENSIONS=OFF

This is useful for:

  • Critical production deployments requiring minimal attack surface
  • Debugging issues to isolate core vs contrib behavior
  • Builds with strict binary size requirements

Overview (`benchmarks/lib/libgui/cimgui/README.md`)

cimgui [](https://travis-ci.org/cimgui/cimgui)

This is a thin c-api wrapper programmatically generated for the excellent C++ immediate mode gui Dear ImGui.
All imgui.h functions are programmatically wrapped.
Generated files are: cimgui.cpp, cimgui.h for C compilation. Also for helping in bindings creation, definitions.lua with function definition information and structs_and_enums.lua.
This library is intended as a intermediate layer to be able to use Dear ImGui from other languages that can interface with C (like D - see D-binding)

History:

Initially cimgui was developed by Stephan Dilly as hand-written code but lately turned into an auto-generated version by sonoro1234 in order to keep up with imgui more easily (letting the user select the desired branch and commit)

Notes:

compilation

  • clone

    • git clone --recursive https://github.com/cimgui/cimgui.git
    • git submodule update
  • compile

    For compiling with backends there is now an example with SDL2 and opengl3 in folder backend_test. It will generate a cimgui_sdl module and a test_sdl executable. You only need to provide SDL_PATH telling cmake where to look for SDL2 cmake installation.

using generator

  • this is only needed (before compilation) if you want an imgui version different from the one provided, otherwise generation is already done.
  • you will need LuaJIT (https://github.com/LuaJIT/LuaJIT.git better 2.1 branch) or precompiled for linux/macOS/windows in https://luapower.com/luajit/download
  • you need to use also a C++ compiler for doing preprocessing: gcc (In windows MinGW-W64-builds for example), clang or cl (MSVC). (this repo was done with gcc)
  • update imgui folder to the version you desire.
  • edit generator/generator.bat on windows, or generator/generator.sh on linux, to choose between gcc, clang, or cl and to choose desired backends and whether imgui_internal is generated or not and Freetype2 is used or not.
  • the defaults of generator are gcc as compiler, imgui_internal included and sdl, glfw, opengl2 and opengl3 as backends.
  • edit config_generator.lua for adding includes needed by your chosen backends (vulkan needs that).
  • Run generator.bat or generator.sh with gcc, clang or cl and LuaJIT on your PATH.
  • as a result some files are generated: cimgui.cpp, cimgui.h and cimgui_impl.h for compiling and some lua/json files with information about the binding: definitions.json with function info, structs_and_enums.json with struct and enum info, impl_definitions.json with functions from the backends info.
  • You can pass compiler flags to generator.sh or generator.bat at the end of the call to further specify the compiler behavior. (e.g. -DIMGUI_USER_CONFIG or -DIMGUI_USE_WCHAR32)

generate binding

definitions description

  • It is a collection in which key is the cimgui name that would result without overloadings and the value is an array of overloadings (may be only one overloading)
  • Each overloading is a collection. Some relevant keys and values are:
    • stname : the name of the struct the function belongs to (will be "" if it is top level in ImGui namespace)
    • ov_cimguiname : the overloaded cimgui name (if absent it would be taken from cimguiname)
    • cimguiname : the name without overloading (this should be used if there is not ov_cimguiname)
    • ret : the return type
    • retref : is set if original return type is a reference. (will be a pointer in cimgui)
    • argsT : an array of collections (each one with type: argument type and name: the argument name, when the argument is a function pointer also ret: return type and signature: the function signature)
    • args : a string of argsT concatenated and separated by commas
    • call_args : a string with the argument names separated by commas for calling imgui function
    • defaults : a collection in which key is argument name and value is the default value.
    • manual : will be true if this function is hand-written (not generated)
    • skipped : will be true if this function is not generated (and not hand-written)
    • isvararg : is set if some argument is a vararg
    • constructor : is set if the function is a constructor for a class.
    • destructor : is set if the function is a destructor for a class but not just a default destructor.
    • realdestructor : is set if the function is a destructor for a class
    • templated : is set if the function belongs to a templated class (ImVector)
    • templatedgen: is set if the function belongs to a struct generated from template (ImVector_ImWchar)
    • nonUDT : if present the original function was returning a user defined type so that signature has been changed to accept a pointer to the UDT as first argument.
    • location : name of the header file and linenumber this function comes from. (imgui:000, internal:123, imgui_impl_xxx:123)
    • is_static_function : is setted when it is an struct static function.

structs_and_enums description

  • Is is a collection with three items:
    • under key enums we get the enums collection in which each key is the enum tagname and the value is an array of the ordered values represented as a collection with keys
      • name : the name of this enum value
      • value : the C string
      • calc_value : the numeric value corresponding to value
    • under key structs we get the structs collection in which the key is the struct name and the value is an array of the struct members. Each one given as a collection with keys
      • type : the type of the struct member
      • template_type : if type has a template argument (as ImVector) here will be
      • name : the name of the struct member
      • size : the number of array elements (when it is an array)
      • bitfield : the bitfield width (in case it is a bitfield)
    • under key locations we get the locations collection in which each key is the enum tagname or the struct name and the value is the name of the header file and line number this comes from.

usage

  • use whatever method is in ImGui c++ namespace in the original imgui.h by prepending ig
  • methods have the same parameter list and return values (where possible)
  • functions that belong to a struct have an extra first argument with a pointer to the struct.
  • where a function returns UDT (user defined type) by value some compilers complain so the function is generated accepting a pointer to the UDT type as the first argument (or second if belongs to a struct).

usage with backends

  • look at backend_test folder for a cmake module building with SDL and opengl3.

example bindings based on cimgui

C examples based on cimgui


Overview (`benchmarks/widgets/original/es5/README.md`)

ES5 Packaged with Webpack

The pre-generated file is widgets.js. To regenerate it:

text
npm install
npm run build

Overview (`benchmarks/widgets/original/stripped/README.md`)

The original benchmark stripped of types so it can run with v8. The conversion
was performed by the script strip.sh using Juno.

To run the benchmark, use v8 main.mjs.

It can probably run with NodeJS too, but appears that all files need to be renamed
to have a .mjs extension as well as the imports referring to them.


Overview (`benchmarks/widgets/single-file/es5/README.md`)

Single File ES5

The pre-generated file is widgets.js. To regenerate it:

text
npm install
npm run build

Overview (`external/asmjit/asmjit/db/README.md`)

AsmJit Instruction Database

This is a database of instructions that is used by AsmJit to generate its internal database and also assembler implementations. This project started initially as AsmDB, but was merged to AsmJit later to make the maintenance easier. The database was created in a way so that each instruction definition would only need a single line in JSON data file. The data is then processed by architecture specific data readers that make the data canonical and ready for processing.

AsmJit database provides the following ISAs:

  • isa_x86.json - provides X86 instruction data (both 32-bit and 64-bit)
  • isa_aarch32.json - provides AArch32 instruction data (A32/T16/T32 encoding)
  • isa_aarch64.json - provides AArch64 instruction data (A64 encoding)
  • isa_aarch64_sme.json - provides AArch64 SME instruction data (work-in-progress)

To Be Documented

This project will be refactored and documented in the future.

License

AsmJit database is dual licensed under Zlib (AsmJit license) or public domain. The database can be used for any purpose, not just by AsmJit.


Overview (`tools/hermes-parser/js/babel-plugin-syntax-hermes-parser/README.md`)

babel-plugin-syntax-hermes-parser

Hermes parser plugin for Babel. This plugin switches Babel to use hermes-parser instead of the @babel/parser. Since Hermes parser uses C++ compiled to WASM it is significantly faster and provides full syntax support for Flow.

Install

Using npm:

sh
npm install --save-dev babel-plugin-syntax-hermes-parser

or using yarn:

sh
yarn add babel-plugin-syntax-hermes-parser --dev

Usage

The plugin can be enabled via:

text
// babel.config.json
{
  "plugins": ["babel-plugin-syntax-hermes-parser"]
}

If parser options need to be provide you can do so via the parserOpts config:

text
// babel.config.json
{
  "plugins": ["babel-plugin-syntax-hermes-parser"],
  "parserOpts": {
    "allowReturnOutsideFunction": true
  }
}

Overview (`tools/hermes-parser/js/flow-api-translator/README.md`)

flow-api-translator

Development of flow-api-translator has moved to the
facebook/flow repository.


Overview (`tools/hermes-parser/js/hermes-eslint/README.md`)

hermes-eslint

hermes-eslint is a custom parser for ESLint. It is the recommended parser for use for linting with Flow code.

Usage

Flat config (`eslint.config.js`)

To use hermes-eslint with ESLint flat config, set it as
languageOptions.parser:

js
module.exports = [
  {
    languageOptions: {
      parser: require('hermes-eslint'),
    },
  },
];

You can configure parser options with
languageOptions.parserOptions:

js
module.exports = [
  {
    languageOptions: {
      parser: require('hermes-eslint'),
      parserOptions: {
        sourceType: 'module',
      },
    },
  },
];

Legacy config (`.eslintrc`)

If you are still using legacy ESLint config, specify
"hermes-eslint" as the "parser" in your .eslintrc file:

json
{
  "parser": "hermes-eslint"
}

The ESLint documentation provides more information about how to configure ESLint, including how to specify a custom parser.

Options

You may provide additional configuration for hermes-eslint by passing an
object containing configuration options as parserOptions in your ESLint
configuration file. In flat config, this is languageOptions.parserOptions.
This object may contain the following properties:

ts
type ParserOptions = {
  /**
   * The identifier that's used for JSX Element creation (after transpilation).
   * This should not be a member expression - just the root identifier (i.e. use "React" instead of "React.createElement").
   *
   * To use the new global JSX transform function, you can explicitly set this to `null`.
   *
   * Defaults to `"React"`.
   */
  jsxPragma?: string | null,

  /**
   * The identifier that's used for JSX fragment elements (after transpilation).
   * If `null`, assumes transpilation will always use a member on `jsxFactory` (i.e. React.Fragment).
   * This should not be a member expression - just the root identifier (i.e. use "h" instead of "h.Fragment").
   *
   * Defaults to `null`.
   */
  jsxFragmentName?: string | null,

  /**
   * The source type of the script.
   *
   * Defaults to `"module"`.
   */
  sourceType?: 'script' | 'module',

  /**
   * Ignore <fbt /> JSX elements when adding references to the module-level `React` variable.
   * FBT is JSX that's transformed to non-JSX and thus references differently
   *
   * https://facebook.github.io/fbt/
   */
  fbt?: boolean,
};
json
{
  "parser": "hermes-eslint",
  "parserOptions": {
    "sourceType": "module"
  }
}

Overview (`tools/hermes-parser/js/hermes-estree/README.md`)

hermes-estree

Flow types for the Flow-ESTree spec produced by the hermes parser


Overview (`tools/hermes-parser/js/hermes-parser/README.md`)

hermes-parser

A JavaScript parser built from the Hermes engine's parser compiled to WebAssembly. Can parse ES6, Flow, and JSX syntax.

API

The Hermes parser exposes a single parse(code, [options]) function, where code is the source code to parse as a string, and options is an optional object that may contain the following properties:

  • babel: boolean, defaults to false. If true, output an AST conforming to Babel's AST format. If false, output an AST conforming to the ESTree AST format.
  • allowReturnOutsideFunction: boolean, defaults to false. If true, do not error on return statements found outside functions.
  • flow: "all" or "detect", defaults to "detect". If "detect", only parse syntax as Flow syntax where it is ambiguous whether it is a Flow feature or regular JavaScript when the @flow pragma is present in the file. Otherwise if "all", always parse ambiguous syntax as Flow syntax regardless of the presence of an @flow pragma. For example foo<T>(x) in a file without an @flow pragma will be parsed as two comparisons if set to "detect", otherwise if set to "all" or the @flow pragma is included it will be parsed as a call expression with a type argument.
  • sourceFilename: string, defaults to null. The filename corresponding to the code that is to be parsed. If non-null, the filename will be added to all source locations in the output AST.
  • sourceType: "module", "script", or "unambiguous" (default). If "unambiguous", source type will be automatically detected and set to "module" if any ES6 imports or exports are present in the code, otherwise source type will be set to "script".
  • tokens: boolean, defaults to false. If true, add all tokens to a tokens property on the root node.
  • transformOptions: object. Options to be supplied to relevant transforms.
    • TransformEnumSyntax
      • enable: boolean - Whether to enable the transform. By default, false.
      • getRuntime (optional): () => Expression - The expression which should be a reference to the Flow Enums runtime. By default, is require('flow-enums-runtime').

Overview (`tools/hermes-parser/js/hermes-transform/README.md`)

hermes-transform

A package for traversing and transforming a hermes AST.

This package is pieced together and inspired by code from:


Overview (`tools/hermes-parser/js/prettier-plugin-hermes-parser/README.md`)

prettier-plugin-hermes-parser

Hermes parser plugin for Prettier. Unless you want to be on the bleeding edge, you should use the official @prettier/plugin-hermes instead.

Usage

More details on using Prettier plugins: https://prettier.io/docs/en/plugins.html#using-plugins

To then use the parser you will need to instruct Prettier to use hermes as the parser for your required files:

text
// .prettierrc
{
  "plugins": ["prettier-plugin-hermes-parser"],
  "overrides": [
    {
      "files": ["*.js", "*.jsx", "*.flow"],
      "options": {
        "parser": "hermes"
      }
    }
  ]
}

More details on configuring Prettier parsers: https://prettier.io/docs/en/configuration.html#setting-the-parserdocsenoptionshtmlparser-option


Overview (`unittests/API/synthtest/tests/README.md`)

Synthetic benchmark replay tests

Each file in this directory is a single integration test for synthetic benchmark
parsing and replay.

Adding a new test

Let each test file have a unique identifier, call it testName.
Each test file defines two functions:

cpp
const char *testNameTrace();
const char *testNameSource();

testNameTrace should return a string of JSON that represents a synthetic trace
of a hypothetical native interaction with JS. This interaction can do things
like define native functions that are called from JS, or make native objects, or
simply call functions in JS.
NOTE: The object ids used in the trace string is only meant to be used as a
unique identifier, it doesn't have any special meaning. Most tests start at 10
just because of convention. You can use object ids like 3, 400000, or 1345, as
long as they are unique integers.
NOTE: We use these test traces for two tests each: one uses Strings to
represent property names, the other PropNameIDs. To make this
possible, we have a "fake" CreatePropNameRecord type. The string
substitution in Driver.cpp (where this convention is further
explained) leaves changes CreatePropNameRecord into either
CreateString or CreatePropNameID, depending on which test variant
we're doing. Note that there are CreateStringRecords for strings that
are not used as property names; these are left unchanged.

testNameSource is some JS source code that interacts with the native
environment created by the trace. It should throw exceptions if the native code
does not behave as expected, this will cause the test to fail.

Make sure testName is added to the macro list in TestFunctions.h.
Then Driver.cpp will call these new functions for you!

Debugging a broken test

Use gdb, and put a breakpoint on TraceInterpreter::execFunction. Then step
through each event that's executed by the interpreter. Currently it's not
particularly easy to also debug the JS, that would require a hybrid debugger.


Overview (`unsupported/hermes/crates/hermes_diagnostics/README.md`)

hermes_diagnostics

Types for representing compiler diagnostics. Includes a general-purpose representation
of diagnostics with related information which can be converted into miette::Diagnostic to exploit miette's pretty printing of errors.

Unlike miette, lsp_types, and other diagnostic libraries, the error severities are categorized
to allow different tools to report them at different levels. For example, a tool may choose to
report todo errors or ignore them.


Overview (`unsupported/hermes/crates/hermes_estree/README.md`)

hermes_estree

This crate is a Rust representation of the ESTree format and
popular extenions including JSX and (eventually) Flow and TypeScript.

Because this crate is intended to support usage in both Rust toolchains and for interop in JavaSCript-based toolchains, the format is designed to support accurate serialization to/from estree-compatible JSON.


Overview (`unsupported/hermes/crates/hermes_estree_codegen/README.md`)

hermes_estree_codegen

This crate is a build dependency for hermes_estree, and contains codegen logic to produce Rust code to describe the ESTree format
given a JSON schema.


Overview (`unsupported/hermes/crates/hermes_parser/README.md`)

hermes_parser

Wrapper around the Hermes parser that exposes parse results as a hermes_estree AST.


Overview (`unsupported/hermes/crates/hermes_semantic_analysis/README.md`)

hermes_semantic_analysis

Implements semantic analysis of JavaScript name resolution and scope information over the hermes_estree AST. Eventually this
analysis will be extended to cover Flow and TypeScript in addition to the core JS and JSX language.

NOTE: this analsyis assumes strict mode and does not support legacy non-strict semantics.


Overview (`unsupported/hermes/crates/hermes_utils/README.md`)

hermes_utils

This is a catch-all crate for utilities and helper code that doesn't have an obvious home elsewhere.
It is expected that this crate will be depended on by lots of other crates in the project. However,
this crate should generally not depend on other workspace crates — that's an indication that the
utility you're adding belongs with the crate that uses the utility.


Overview (`API/hermes/cdp/tools/hermes-inspector-msggen/README.md`)

@react-native/hermes-inspector-msggen

Version

Installation

text
yarn add --dev @babel/cli @babel/core @babel/preset-env @babel/preset-flow jest @react-native/hermes-inspector-msggen

Note: We're using yarn to install deps. Feel free to change commands to use npm 3+ and npx if you like

Testing

To run the tests in this package, run the following commands from the React Native root folder:

  1. yarn to install the dependencies. You just need to run this once
  2. yarn jest packages/hermes-inspector-msggen.

Overview (`external/boost/boost_1_86_0/libs/context/README.md`)

boost.context

boost.context is a foundational library that provides a sort of cooperative multitasking on a single thread.
By providing an abstraction of the current execution state in the current thread, including the stack (with
local variables) and stack pointer, all registers and CPU flags, and the instruction pointer, a execution_context
instance represents a specific point in the application's execution path. This is useful for building
higher-level abstractions, like coroutines, cooperative threads (userland threads) or an equivalent to
C# keyword yield in C++.

A fiber provides the means to suspend the current execution path and to transfer execution control,
thereby permitting another fiber to run on the current thread. This state full transfer mechanism
enables a fiber to suspend execution from within nested functions and, later, to resume from where it
was suspended. While the execution path represented by a fiber only runs on a single thread, it can be
migrated to another thread at any given time.

A context switch between threads requires system calls (involving the OS kernel), which can cost more than
thousand CPU cycles on x86 CPUs. By contrast, transferring control among fibers requires only fewer than
hundred CPU cycles because it does not involve system calls as it is done within a single thread.

boost.context requires C++11!


Overview (`tools/hermes-parser/js/hermes-eslint/__tests__/eslint-scope/README.md`)

eslint-scope tests

These tests are forked from eslint-scope. The original license for eslint-scope can be found in the ESLINT_SCOPE_LICENSE file in the root of this package. The tests have been reformatted and modified to work with Jest.


Overview (`tools/hermes-parser/js/hermes-eslint/src/scope-manager/README.md`)

Hermes ESLint Scope Manager

This is a fork of typescript-eslint's scope manager, enhanced to support Flow.

The original license for typescript-eslint's scope manager can be found in the TYPESCRIPT_ESLINT_SCOPE_MANAGER_LICENSE file in the root of this package.


Overview (`tools/hermes-parser/js/hermes-transform/src/transform/comments/prettier/README.md`)

Comment Attachment

This is a fork of prettier's comment attachment algorithm. The original license for prettier can be found in the PRETTIER_LICENCE file in the root of this package.
Specifically this fork contains the attach method from src/main/comments.js and the minimum set of code required for it.

The folder structure has been maintained to make merging future updates easier.


Doc BuildingAndRunning (`doc/BuildingAndRunning.md`)


id: building-and-running title: Building and Running

This document describes how to build and run Hermes as a standalone compiler and VM. To use Hermes in the context of a React Native app, see the React Native documentation.

Dependencies

Hermes is a C++17 project. clang, gcc, and Visual C++ are supported. Hermes also requires cmake, git, Python. It builds with CMake and ninja.

The Hermes REPL will also use libreadline, if available.

ICU is not required for a default build: Unicode case conversion, normalization, collation and date formatting are all self-contained. ICU is needed only for -DHERMES_ENABLE_INTL=ON (off by default), and even then only on Linux and Windows, since Apple and Android builds use their platform's own Intl implementation. Install libicu-dev (Ubuntu) or icu (Arch) if you are enabling Intl.

To install dependencies on Ubuntu:

text
apt install build-essential cmake git ninja-build python3 tzdata libreadline-dev

On Arch Linux:

text
pacman -S cmake git ninja python zip readline

On Mac via Homebrew:

text
brew install cmake git ninja

Building on Linux and macOS

Hermes will place its build files in the current directory by default.
You can also give explicit source and build directories, use --help on the build scripts to see how.

Create a base directory to work in, e.g. ~/workspace, and cd into it.
(Tip: avoid naming it hermes, as hermes will be one of several subdirectories in the workspace).
After cding, follow the steps below to generate the Hermes build system:

text
git clone https://github.com/facebook/hermes.git
cmake -S hermes -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug

The build system has now been generated in the build directory. To perform the build:

text
cmake --build ./build

Release Build

The above instructions create an unoptimized debug build. The -DCMAKE_BUILD_TYPE=Release flag will create a release build:

text
cmake -S hermes -B build_release -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build ./build_release

Building on Windows

To build on Windows using Visual Studio with a checkout in the hermes directory:

text
cmake -S hermes -B build -G 'Visual Studio 16 2019'
cmake --build ./build

Running Hermes

The primary binary is the hermes tool, which will be found at build/bin/hermes. This tool compiles JavaScript to Hermes bytecode. It can also execute JavaScript, from source or bytecode or be used as a REPL.

Executing JavaScript with Hermes

text
hermes test.js

Compiling and Executing JavaScript with Bytecode

text
hermes -emit-binary -out test.hbc test.js
hermes test.hbc

Running Tests

To run the Hermes test suite:

text
cmake --build ./build --target check-hermes

To run Hermes against the test262 suite, you need to have a Hermes binary built
already and a clone of the test262 repo:

text
hermes/utils/testsuite/run_testsuite.py -b <hermes_build> <test262>

E.g. if we configured at ~/hermes_build (i.e. ~/hermes_build/bin/hermes is
an executable) and cloned test262 at ~/test262, then perform:

text
hermes/utils/testsuite/run_testsuite.py -b ~/hermes_build ~/test262/test

Note that you can also only test against part of a test suite, e.g. to run the
Intl402 subset of the test262, you can specifiy a subdir:

text
hermes/utils/testsuite/run_testsuite.py -b ~/hermes_build ~/test262/test/intl402

Formatting Code

To automatically format all your changes, you will need clang-format, then
simply run:

text
hermes/utils/format.sh

AddressSanitizer (ASan) Build

The -HERMES_ENABLE_ADDRESS_SANITIZER=ON flag will create a ASan build:

text
git clone https://github.com/facebook/hermes.git
cmake -S hermes -B asan_build -G Ninja -D HERMES_ENABLE_ADDRESS_SANITIZER=ON -DCMAKE_BUILD_TYPE=Debug
cmake --build ./asan_build

You can verify the build by looking for asan symbols in the hermes binary:

text
nm asan_build/bin/hermes | grep asan

Other Tools

In addition to hermes, the following tools will be built:

  • hdb: JavaScript command line debugger
  • hbcdump: Hermes bytecode disassembler
  • hermesc: Standalone Hermes compiler. This can compile JavaScript to Hermes bytecode, but does not support executing it.
  • hvm: Standalone Hermes VM. This can execute Hermes bytecode, but does not support compiling it.

Doc CodingStandards (`doc/CodingStandards.md`)


id: coding-standards title: Coding Standards

Hermes Coding Standards

This document provides guidance for the kind of code that should go in to
the Hermes project. The rules in this document will allow us to scale the
project and ensure that the code base remains readable and maintainable.

Code Quality

Code quality standards in Hermes are very strict. While we welcome and
support community contributions, expect scrutiny on changes that do not
meet the following standards:

  • Contributions must be well understood and explained in PRs. We cannot accept
    and maintain contributions that we don't fully understand.
  • Contributions should not use shortcuts or hacks in the name of expediency.
    Hermes is a critical piece of infrastructure for many apps and systems, and
    such code creates ongoing work for maintainers.

Language Features

Hermes uses C++14.

Exceptions or RTTI are prohibited, except where needed for
interfacing with other software that requires them.

GCC language extensions are discouraged for compatibility with MSVC, or they
should be conditionally enabled.

Code Style

TODO: This should be sorted and expanded.

  • Every declaration must have a doc-comment.

Naming

  • Member variables use a "_" suffix unless they are public, where it is
    acceptable to omit the suffix.
  • Classes use pascal case (MyClass).
  • Functions and methods use camel case (myFunc).
  • Variables generally use camel case (myVar). We also allow "abbreviated
    case": "basic block" can be abbreviated as BB, "instruction" can be
    abbreviated as I, etc.
  • Constants use either capitalized snake case (MY_CONST) or camel case with
    "k" prefix (kMyConst).

Misc

  • struct should be used only when it is a true POD - there are no constructors
    or destructors of any kind. For everything else use class.
  • Inlining: Only completely trivial (one line) methods can be defined in the
    class body. The rest should be declared as inline both at the declaration and
    the definition.

Small incremental changes

The project is developed using small incremental changes. These changes can be
small bug fixes or minor tweaks. Other times, these changes are small steps
along the path to reaching larger stated goals. Long-term development branches
suffer from many problems, including the lack of visibility, difficulty of code
review, lack of testing of the branch and merge difficulty.

Commits that go into the project need to be reviewable. This means that commits
need to be relatively small, well documented and self contained.

Add tests

Functional changes to the compiler need to include a testcase. Unit tests and
regression tests are critical to the qualification of the compiler. Every bug
fix needs to include a testcase.

Reduce test cases as much as possible! It is unacceptable to commit big programs
because they do not describe the essence of the failure, they are fragile, and
they slow testing down. Tests need to be short and focused.

Format your code

clang-format is required to enforce code style and formatting.
Commits that only change the formatting of code should go in independent of
functional changes.

Commit messages

Here are some guidelines about the format of the commit message:

Separate the commit message into a single-line title and a separate body that
describes the change. Make the title short (80 chars) and readable. In changes
that are restricted to a specific part of the code, include a [tag] at the start
of the line in square brackets—for example, “[docs] ... ”.

If the commit fixes an issue in the bug tracking system, include a link or a
task number.

When reverting a change make sure to add a short note that describes why the
patch is being reverted.

Code review

The project relies heavily on code review to maintain the software quality.

Review other people’s changes! Anybody is allowed to review code and comment
on patches.

All changes, by all developers, must be reviewed before they are committed to
the repository. Smaller changes (if the developer is the de-facto owner of the
code base) can be reviewed after being committed.


Doc CrossCompilation (`doc/CrossCompilation.md`)


id: cross-compilation title: Cross Compilation

This document describes how to build Hermes in a cross compilation setting, e.g.
building for Android, WASM (with Emscripten), or any other platforms different
than the host development machines.

Android compilation

Edit paths:

  • CMAKE_TOOLCHAIN_FILE should point to whatever android_ndk you need.
  • IMPORT_HOST_COMPILERS should point to whatever SH host build you want to use for InternalBytecode compilation.
  • -B path is to your build directory, or omit it to build in current directory.

Ensure that CMAKE_CXX_FLAGS_RELEASE is -O3 -DNDEBUG if you're in Release mode.

Then you can use adb push to copy any files into /data/local/tmp/ on an Android device and run it there using 'adb shell'.

text
cmake ~/fbsource/xplat/static_h \
  -B ~/static_h/build_android \
  -DHERMES_FACEBOOK_BUILD=OFF \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_TOOLCHAIN_FILE=/opt/android_ndk/r17fb2/build/cmake/android.toolchain.cmake \
  -DHERMES_UNICODE_LITE=ON \
  -DIMPORT_HOST_COMPILERS=~/static_h/build_release/ImportHostCompilers.cmake \
  -DHERMES_ENABLE_DEBUGGER=OFF \
  -DANDROID_ABI=arm64-v8a -G Ninja

HERMES_UNICODE_LITE=ON selects the self-contained no-op Unicode backend.
Without it, an Android build uses the Java backend, which needs a JVM at
runtime and so is not usable from a binary pushed with adb push and run
under adb shell.

No ICU is needed here, or in any cross-compilation target. Hermes no
longer depends on ICU at all. The platform_unicode layer -- case
conversion, normalization, collation and date formatting -- is
self-contained, and the ICU-backed Intl backend has been removed.
-DHERMES_ENABLE_INTL=ON is now supported only on Apple and Android,
which have their own native Intl implementations.

Emscripten

For Emscripten, you can find an example from the test-emscripten job from hermes/.circleci/config.yml. Also see more details at Building with Emscripten

Compiling and Running ARM32 Static Hermes from ARM64

On the ARM64 Linux machine you need clang-16 and lld-16.

On the ARM64 Linux machine on which you're compiling:

text
# Add architecture
dpkg --add-architecture armhf
# Update
sudo apt update
# For compiling
sudo apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf binutils-arm-linux-gnueabihf libc6:armhf libstdc++6:armhf

If you're executing on an ARM64 machine, set it up as well:

text
# Add architecture
dpkg --add-architecture armhf
# Update
sudo apt update
# For running
sudo apt install libc6:armhf libstdc++6:armhf

There's a CMake toolchain file in the source directory that will then set up Clang to compile for ARM32. To use it:

text
CC=clang-16 CXX=clang++-16 cmake ${SRC_DIR?} -GNinja -DCMAKE_TOOLCHAIN_FILE=${SRC_DIR?}/armhf/arm-linux-gnueabihf.toolchain.cmake -DCMAKE_BUILD_TYPE=Release

Then you can run ninja and build 32-bit binaries and libraries. They'll work on the machine on which you're executing.


Doc Design (`doc/Design.md`)


id: design title: Design Overview

This is the design document for the Hermes Engine.

JSON estree parser

JavaScript parser

IRGen

Hermes Bytecode Generator

The Hermes bytecode generator is responsible for translating the
Hermes high-level IR into Hermes bytecode. This section only describes the
generation of the opcode stream, not the whole bundle file format that is
described in a separate document.

The bytecode is a register-based bytecode. The number of registers is
infinite, but there are some restrictions on the registers. For example,
registers that are allocated to call instructions must be consecutive, and most
instructions only accept an 8-bit register index.

The first phase of bytecode generation is lowering of some instructions to
target-specific instructions. Next, the register allocator allocates
registers for each instruction in the lowered IR. Notice that the Hermes IR
has a single return value per instruction. We model multiple return values or
in-out variables using alloc-stack instructions.

One interesting design decision here is that register allocation happens on the
IR prior to instruction selection. Performing register allocation prior to
lowering is often done in JIT compilers where the lowering phase is trivial and
close to a 1:1 translation between high-level IR and the low-level target IR.
The lowered Hermes IR is very close to the bytecode format with a few
exceptions: the handling of variables, the representation of constants and the
representation of stack-allocated variables. Stack allocated registers are
implemented as values that are pinned into a specific registers (at least during
the lifetime of the stack allocation). Load/Store instructions from the stack
allocation values are lowered into MOV instructions.

The current register allocation implementation is a very simple linear scan. The
allocation has four main steps. First, we number the instructions in the
functions, and traverse the basic blocks in the function in reverse-post-order
scan. Second, we calculate the liveness graph of the result of each
instruction in the function. We propagate this information across the function
with a simple data-flow scan. Third, we calculate the liveness interval of each
instruction. Finally, we scan the instructions in the function one by one and
assign registers. We maintain a list of currently live intervals that correspond
to the register file. We represent the register file with a simple bit vector
and prefer to allocate registers from the beginning of the file. When we run out
of registers we simply allocate new registers. When we reach instructions that
require target-specific handling we call the backend to fill in the details. For
example, the Hermes backend handles call instructions in a special way. At the
moment we do not coalesce values and our PHI nodes generate two MOVs.

After register allocation we lower the Hermes IR into pseudo opcodes using a
one-to-one or one-to-many translation. There are a few interesting optimizations
during this translation. The first interesting optimization is the optimization
that eliminates redundant constant jumps. It is possible to eliminate many
branches by scheduling basic blocks in a way that maximizes the number of
fall-through opportunities. The current algorithm uses a greedy approach where
we emit basic blocks in some order (that starts with the entry block), and
attempt to place the fall-through destination right after terminators that
branch (conditional and unconditional branches) if the block has not been
generated already. In the future we may need to investigate if this greedy
algorithm is optimal.

When we generate the opcode stream we need to encode jump targets. However, when
we emit opcodes that refer to jump destinations that we have not yet emitted
then we do not know the address of the destination. We solve this problem by
emitting a dummy value and keeping a side-table that saves the location that we
need to patch. When we finish emitting all of the opcodes we scan the side table
and patch all of the locations that refer to previously unresolved addresses.

Hermes Bytecode Instructions

Hermes bytecode adopts variable-length instructions. Each operand to a bytecode
instruction has a fixed-type and width, defined by the opcode. For instance,
Jmp takes a 1-byte offset as the jump target, while JmpLong takes a 4-byte
offset as the jump target. Fixed-type/width instructions allow us to decode
them efficiently in the interpreter.
However we are trading off with an increasing number of
opcodes to handle different operand widths (e.g. two Jmp opcodes instead of
one). We believe that we are able to avoid opcode explosion by generating the
code smartly. A full list of Hermes bytecode opcodes can be found in
BytecodeList.def. There are a few interesting design decisions worth mentioning
here:

  • Registers: We discovered that in all of the Facebook mobile JS code as well as
    majority of external benchmarks, no function ever uses more than 256 registers.
    Hence we always use 1-byte to represent register index, which will be most
    efficient for the normal cases. Spilling is implemented via MovLong which
    supports 32 bit register indices.
  • Constants: we achieve constant loading fully through instructions.
    For fixed-value constants such as undefined, null, true and false, we introduce
    a corresponding load opcode for each of them into a register (e.g.
    LoadConstUndefined dstReg); For 32-bit integers, we introduce LoadConstInt
    opcode, which takes a 4-bytes immediate value and load it into a register; for
    doubles, we introduce LoadConstDouble, which takes a 8-bytes immediate value
    and load it into a register; finally for strings, we introduce LoadConstString,
    which takes an index to the string table from which to load into a register.
    Doing so can significantly reduce the size of the bytecode, however it does
    introduce a few more opcodes which could slow down the interpreter.
  • Non-local Variable Access: Local variables are translated to registers.
    Non-local variables are variables from different scopes/environments. Without a
    compiler, accessing non-local variables in JavaScript usually means a scope
    lookup (i.e. locating the closest scope in the scope chain that defines the
    variable), followed by a symbol lookup in that scope.
    However with a compiler, it is possible to statically determine the scope of
    every variable, and hence there is no need for a real scope lookup. In Hermes
    backend, for each non-local variable access, we simply calculate the delta
    between the defining scope and the current scope, and locate such scope using
    the delta (i.e. number of times the VM needs to follow the scope chain)
    directly. Furthermore, since we know exactly what and how many variables are
    defined in each scope (except the global scope), we skip the symbol lookup in
    the scope but instead using a direct index access to retrieve such variable in
    the located scope. We believe that skipping both scope lookups and symbol
    lookups can significantly improve the runtime performance.

Bytecode File Format

The bytecode file contains the bytecode as well as necessary metadata and
auxiliary data sections for the VM to execute properly. The file format is
defined in BytecodeFileFormat.h, and structures as following:

  • FILE HEADER: The file header contains the MAGIC, the current format version,
    and a list of global metadata, including the file size, offset of the function
    header table, offset of the string table, index of the global code and number
    of functions.
  • FUNCTION HEADER TABLE: This is a list of function headers. Each function header
    contains metadata of a function, such as the offset of the function bytecode in
    the file, number of parameters, size of the frame/environment, size of the
    bytecode and etc. This list also naturally assigns an index to each function in
    the file, which makes access to each function convenient in the VM.
  • STRING TABLE&STORAGE: All the strings used in all functions are uniqued and
    stored in the section to avoid redundant string storage. This section contains
    two parts: The string storage, which is a long sequence of raw characters; the
    string table, which is a list of pairs, each pair represents a string through
    the offset to the string storage and the length of the string. The string table
    also naturally assigns an index to each unique string, which makes it
    convenient to refer to the strings in the bytecode.
  • FUNCTION BYTECODES: This section is the core section of the bytecode file,
    containing a list of compiled function body. The function body contains its
    executable bytecode, along with a few tables that are used by the bytecode,
    including the exception handler table (tells where to jump to when exception
    happens), the array buffer (used to initialize constant arrays). Likely there
    will be a few more tables coming to support RegExp and debug information.

Serialization / Deserialization

Because the Hermes backend is in the same codebase as the VM, there are
opportunities for us to share code between them. In particular, we want the
serialization in the backend to be able to share the same target data structure
as the deserialization in the VM. This introduces some interesting questions:

  • How can we share data/code without having to link too much code on each side?
  • How to avoid data copies during both serialization and deserialization?

We introduce two design pieces to achieve code sharing efficiently:

  • Generator: During serialization, we often need many auxiliary data and
    functionality to aid the process, though many of that will not be needed in the
    end. To ensure the separation of the complexity and allow the deserialization
    to share data/code most efficiently, we use a BytecodeModuleGenerator and a
    list of BytecodeFunctionGenerator for the purpose of generating bytecode and
    serialization. After all the processing, they will eventually generate a
    BytecodeModule and a list of BytecodeFunction that contains minimum amount of
    data/functionality required to generate the bytecode file. Hence we can share
    this minimum data structure between the backend and the VM.
  • StreamVector: During serialization, we need to move/copy the part of the
    auxiliary data from the generator to the shared minimum data structure; during
    deserialization, we need to move/copy the content of the file into the shared
    minimum data structure. Both can be expensive if not managed properly. To
    minimize the copying overhead, we abstracted the complexity using a class named
    StreamVector. During serialization, StreamVector allows us to move the data out
    of the generator without copying (through std::vector::swap); during
    deserialization, StreamVector allows us to take in a raw data pointer to the
    memory buffer from the file directly, without having to copy them too.

Interaction with the VM

At runtime, the VM will deserialize the bytecode from the file and interpret
it. A few components are involved:
BytecodeModule: This is the in-memory representation of the whole bytecode
file, containing all the bytecode functions. During both serialization and
deserialization, this data structure is generated, as a static representation
of the whole file.

  • BytecodeFunction: This is the in-memory representation of a function’s
    bytecode.
  • RuntimeModule: This is the dynamic version of the bytecode module, containing
    necessary runtime information for interpretation.
  • CodeBlock: This is the dynamic version of the bytecode function, containing
    necessary runtime information to execute a function.
  • Domain: A GC-managed proxy which references a set of RuntimeModules, acting as
    a bridge between the GC heap and the C++ heap.
  • JSFunction: This is the Function object in JavaScript.

It is important (and tricky) to efficiently manage the memory and ownership of
these objects properly at runtime. The following figure demonstrates it. Own
means one manages the memory of the other through unique_ptr; Pointer means one
has a raw pointer to the other, without any ownership. Indirectly Own is a
special kind of ownership, which will be explained below. To summarize the
ownership:

  • JSFunction is a JavaScript object, and hence managed by the heap/garbage
    collector directly.
  • JSFunction owns a GC-visible reference to a Domain, and a pointer to the
    corresponding CodeBlock to execute.
  • CodeBlock contains a pointer to both the RuntimeModule to access runtime
    information, as well as a pointer to the corresponding BytecodeFunction which
    contains the static function bytecode to execute.
  • BytecodeModule owns a list of BytecodeFunction.
  • RuntimeModule owns a list of CodeBlock, as well as the corresponding
    BytecodeModule.
  • Domain owns one or more RuntimeModule. This is the mechanism by which an extant
    JSFunction keeps the backing bytecode alive.

Interpreter

Garbage Collector


Doc Emscripten (`doc/Emscripten.md`)


id: emscripten title: Building with Emscripten

Compiling to Wasm

Prerequisites

This guide assumes you are familiar with building Hermes locally and have the necessary
tools installed (e.g., CMake, Ninja, and a compatible toolchain). The instructions are
written for macOS but should work on Linux with minimal adjustments.

Install Emscripten

Install emsdk by following the directions on the Emscripten website for emsdk.

Then, inside the installed directory, we need to make sure we have the latest toolchain:

shell
./emsdk install latest
./emsdk activate latest

Create a Parent Builds Directory

We need a directory to contain the different Hermes builds:

shell
mkdir ~/hermes-builds
cd ~/hermes-builds

Setup Environment Variables

For convenience, we rely on two environment variables throughout this document:

shell
export Emsdk=<path-to-emsdk-directory>
export HermesSourcePath=<path-to-hermes-source-directory>

Build Hermes for the Host

Hermes uses a two stage build process because parts of Hermes need to be
built using Hermes itself. For the first stage, we need to build Hermes for
the host. We will also use the host build later to compile .js to .c.

shell
# From within the hermes-builds directory
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -S ${HermesSourcePath?} -B build-host
# Build hermes and shermes for the host
cmake --build build-host --target hermesc --target hermes --target shermes --parallel

Compile the Hermes VM to Wasm

shell
# From within the hermes-builds directory
cmake -G Ninja \
      -S ${HermesSourcePath?} -B build-wasm \
      -DCMAKE_TOOLCHAIN_FILE=${Emsdk?}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \
      -DCMAKE_BUILD_TYPE=Release \
      -DIMPORT_HOST_COMPILERS=build-host/ImportHostCompilers.cmake \
      -DHAVE_SYS_IOCTL_H=0 \
      -DCMAKE_EXE_LINKER_FLAGS="-sNODERAWFS=1 -sALLOW_MEMORY_GROWTH=1 -sSTACK_SIZE=256KB"
# Build the VM and libraries
cmake --build build-wasm --target hermes --target shermes --target shermes-dep --parallel

Important options:

  • -G Ninja is optional but recommended. It instructs CMake to use Ninja as a build tool.
  • CMAKE_BUILD_TYPE: set it to one of CMake's build modes: Debug, Release,
    MinSizeRel, etc.
  • -DHAVE_SYS_IOCTL_H=0 is needed for compatibility with the Emscripten runtime environment.
  • NODERAWFS: set to 1 if you will be running Hermes directly with Node. It
    enables direct access to the filesystem.
  • ALLOW_MEMORY_GROWTH: whether to pre-allocate all memory, or let it grow over time

Under Emscripten, Hermes relies on a small amount of JavaScript
to be executed by the Wasm host (like Node.js or a browser). If you intend to run it under a "pure" Wasm host, consider
using this flag:

  • -DHERMES_UNICODE_LITE= if set to ON, provides a minimal mostly stubbed-out Unicode implementation.

Note that running under a "pure" Wasm host is not described here and will likely require more tweaks.

Now that the VM is compiled to Wasm, we can examine it:

shell
ls -l build-wasm/bin/hermes.*

Execute Some JavaScript with the Wasm Hermes VM

Let's create a small .js file:

shell
echo 'var x = "hello"; console.log(`${x} world`);' > hello.js

Let's run the example with the Wasm VM:

shell
node ./build-wasm/bin/hermes.js hello.js

Let's compile the example to bytecode and then run the bytecode:

shell
node ./build-wasm/bin/hermes.js hello.js --emit-binary -out hello.hbc
node ./build-wasm/bin/hermes.js hello.hbc

Finally, let's compare the performance of the Wasm VM with the native one
by running one of the micro-benchmarks that come with Hermes.

shell
# Run the micro-benchmark with the host VM.
./build-host/bin/hermes -w ${HermesSourcePath?}/benchmarks/bench-runner/resource/test-suites/micros/interp-dispatch.js
# Run it with the Wasm VM
node ./build-wasm/bin/hermes.js -w ${HermesSourcePath?}/benchmarks/bench-runner/resource/test-suites/micros/interp-dispatch.js

Compile JavaScript to Wasm

Now, let's compile JavaScript to Wasm! This is still not directly supported by
the Hermes CLI tools, so we will need a manual step. But no worries, it is easy.

First, we must make sure we have activated the Emscripten SDK in the current shell.

shell
emcc --help

If that runs fine, the SDK is already active. Otherwise, we need to activate it:

shell
source ${Emsdk?}/emsdk_env.sh

Note that we didn't need to activate it when we were building with CMake, because
we used a CMake toolchain file.

Now we are ready to compile the example js file we created earlier directly to Wasm.
wasm-compile.sh is a helper script in the Hermes utils/ directory.

shell
${HermesSourcePath?}/utils/wasm-compile.sh build-host build-wasm hello.js

The compilation generates two files:

  • hello-wasm.wasm: the compiled Wasm module. Note that this also contains
    the entire JS library.
  • hello-wasm.js: this is the Node.js wrapper to load the Wasm module.

We can run it:

shell
node ./hello-wasm.js

Now let's try compiling the micro-benchmark to Wasm:

shell
${HermesSourcePath?}/utils/wasm-compile.sh build-host build-wasm \
  ${HermesSourcePath?}/benchmarks/bench-runner/resource/test-suites/micros/interp-dispatch.js

Doc Features (`doc/Features.md`)


id: language-features title: Language Features

Hermes is a JavaScript engine optimized for fast start-up. It executes JavaScript using a combination of ahead-of-time (AOT) compilation to bytecode and runtime interpretation.

Hermes aims to support the latest ECMAScript specification, prioritizing language features over new library functions. However, some features are intentionally excluded or de-prioritized if they conflict with Hermes' design goals of performance, low memory use, and efficient AOT compilation.

Supported ECMAScript Features

Hermes provides broad support for ECMAScript features, including:

  • Full support for ES2015 (ES6) Language Features:

    • let and const declarations (including Temporal Dead Zone behavior)
    • Arrow Functions (=>)
    • Classes (Basic syntax: class, extends, static methods, super, constructor)
    • Enhanced Object Literals (shorthand properties, computed names, methods)
    • Template Literals (tagged and untagged)
    • Destructuring Assignment (arrays and objects, including rest ...)
    • Default, Rest, and Spread (...) operators
    • Iterators and Generators (Symbol.iterator, for..of, function*)
    • Symbols (primitive type, Symbol(), well-known symbols)
    • And all other ES2015 language constructs (unless listed below).
  • Key Post-ES2015 Language Features:

    • async / await (ES2017)
    • Object Rest/Spread Properties (ES2018)
    • RegExp Named Capture Groups (ES2018)
    • Optional Chaining (?.) (ES2020)
    • Nullish Coalescing (??) (ES2020)
    • BigInt (ES2020)
    • Class Fields & Static Blocks (ES2022):
      • Public and Private Instance Fields (property declarations and initializers, e.g., myField = 1;, #privateField = 2;)
      • Public and Private Static Fields (e.g., static staticField = 3;, static #privateStatic = 4;)
      • Static Initialization Blocks (static {})
    • RegExp Match Indices (/d flag) (ES2022)
  • Key Library Features:

    • ES2015 standard built-ins (Promise, Set, Map, WeakSet, WeakMap, TypedArrays, Reflect, Proxy, updated methods on Array, String, Object, etc.)
    • Extended Promise API (see "Known Deviations" for the limits of Symbol.species and microtask timing):
      • Promise.prototype.finally (ES2018)
      • Promise.allSettled (ES2020)
      • Promise.any (ES2021)
      • Promise.withResolvers (ES2024)
      • Promise.try (ES2025)
    • Symbol.prototype.description (ES2019 - see "Known Deviations")
    • WeakRef (ES2021 - see "Planned Features" regarding FinalizationRegistry)
    • Intl API (Basic DateTimeFormat, NumberFormat support; see "Planned Features")
    • Note: Support for the latest standard library features may lag behind language feature support.

Planned Features

Features Hermes intends to support in the future. Active development or implementation hasn't started or completed yet.

  • FinalizationRegistry: The complementary API to WeakRef.
  • Expanded Intl Functionality: Support for APIs such as DisplayNames, ListFormat, PluralRules, RelativeTimeFormat, Locale, etc.
  • Other Standard Library Features: Newer library additions (e.g., Iterator Helpers, Array Grouping methods) are considered but may be lower priority.

Intentionally Excluded / De-prioritized Features

These features are not supported, often due to incompatibility with Hermes' AOT compilation strategy, performance concerns, limited utility, or complexity costs.

  • Local eval(): eval() cannot access or modify local variables in the surrounding lexical scope (neither in strict nor non-strict mode). It operates only on the global scope. This restriction is fundamental to Hermes' AOT compilation and optimization strategy; supporting local eval would severely degrade performance across the engine. Use of eval is strongly discouraged.
  • with Statements: Not supported (throws a SyntaxError). It hinders performance and optimization and is disallowed in strict mode.
  • ES Modules (import/export): Hermes does not currently provide a runtime module loader. While an implementation existed previously, it was removed as the React Native ecosystem relies heavily on bundlers (like Metro) which provide their own module systems. A future goal is to potentially parse module syntax to give the AOT compiler better visibility for cross-module optimizations, but not necessarily to replace bundler functionality at runtime.
  • Symbol.species: Generally not supported for built-in methods. While some specific methods might incidentally behave compatibly, Hermes does not guarantee or commit to supporting the Symbol.species pattern due to its complexity and performance overhead, particularly for an AOT-focused engine.
  • Non-Strict arguments Object Behavior: See "Known Deviations" for details on how Hermes deviates from the spec regarding parameter syncing, assignment, and var shadowing for the arguments object in non-strict mode. These deviations simplify implementation and improve performance.
  • Symbol.unscopables: Not relevant as with is not supported.

Known Deviations & Implementation Details

Specific behaviors where Hermes differs from the ECMAScript specification or has notable implementation characteristics. These deviations are often deliberate choices prioritizing performance, simplicity, or compatibility with Hermes' AOT compilation model, especially concerning rarely used or complex legacy features.

  • arguments Object Behavior (Non-Strict Mode): Hermes simplifies arguments object handling in non-strict ("loose") mode compared to the specification:

    • No Parameter Syncing: Assigning to indices of the arguments object does not dynamically update the corresponding named parameters, and vice-versa (e.g., setting arguments[0] = x does not change the value of the first named parameter). This matches strict mode behavior. (Motivation: High cost and complexity for a rare feature).
    • Assignment Forbidden: Direct assignment to the arguments identifier itself (e.g., arguments = ...;) is disallowed, unlike in spec-compliant loose mode. (Motivation: Rare, complex, little practical benefit).
    • var arguments Shadows: Declaring var arguments; inside a function creates a new variable that shadows the arguments object (like let arguments;) rather than aliasing it. (Motivation: Extremely rare, no known practical uses).
    • These arguments deviations are considered very low priority to align with the spec due to the reasons mentioned.
  • Function Declaration Hoisting (Non-Strict Mode Blocks): The exact semantics of how function declarations within nested blocks are hoisted ("promoted") in non-strict mode may differ from the spec in some complex corner cases. For example, a function declared in an inner block might incorrectly overwrite a function declared in an outer block at the function scope level. (Motivation: Affects rare edge cases primarily in non-strict mode; other engines may exhibit similar behavior. Low priority to fix).

  • Function.prototype.toString(): Due to AOT compilation to bytecode, this method does not return the original JavaScript source code. It typically returns a placeholder like "[native code]" or "[bytecode]".

  • Promise Implementation: Promises are implemented using an internally bundled polyfill compiled to bytecode rather than as a native intrinsic. The polyfill covers the full ES2025 surface (then, catch, finally, all, allSettled, any, race, resolve, reject, try, withResolvers, Symbol.toStringTag), but the following corners differ from the spec:

    • No Symbol.species dispatch. Per spec, the prototype methods Promise.prototype.then, Promise.prototype.catch, and Promise.prototype.finally use SpeciesConstructor(this, %Promise%) to determine the constructor of the chained promise. The polyfill substitutes this.constructor (and takes the built-in Promise directly on the common-case this.constructor === Promise fast path), so a subclass that overrides Symbol.species will not see its override applied (consistent with the engine-wide "Symbol.species not supported" stance noted above). The static methods (all, allSettled, any, race, resolve, reject) use this via NewPromiseCapability per spec — Symbol.species is not involved there.
    • Extra microtask hop on subclass .then. When this.constructor !== Promise, the polyfill bridges the user's reaction to the subclass capability through an intermediate core Promise, costing one additional microtask hop compared to spec's PerformPromiseThen, which attaches the reaction directly to the capability. The fast path (this.constructor === Promise) is unaffected.
    • Promise.all 1-microtask fast path (non-spec, default). When an input is an already-fulfilled core Promise, the polyfill synchronously invokes the resolve element and collapses the spec-mandated PerformPromiseAll step 8.e microtask hop. This is a deliberate performance default; the spec-compliant .then-only dispatch is enabled under the --test262 runtime flag.
    • Microtask timing in await. Hermes' async function machinery lowers to a generator wrapped with the polyfill's then, and a few test262 tick-counting tests (e.g. await-non-promise-thenable, the for-await-of/ticks-with-…-constructor-lookup family) observe a different microtask sequence than spec because the polyfill does one fewer .constructor read on each await hop.
    • See the polyfill source.
  • Symbol.prototype.description Conformance: While Symbol('desc').description works as expected, Symbol().description currently returns '' (empty string) instead of the spec-compliant undefined.


Doc GCSafeCoding (`doc/GCSafeCoding.md`)

C++ Coding With GC

Hermes is a JavaScript engine with a garbage collector (GC). Various entities like
JS objects, hidden classes, property maps, and so on are allocated in the GC heap
and their lifetime is managed by the garbage collector — they can be freed when the
GC discovers that they are no longer referenced. Additionally, the GC can
automatically move GC-managed entities for heap compaction or other reasons.

Writing C++ code that uses GC-managed objects can be difficult because of this —
objects that the C++ code is accessing can be freed or moved. This document explains
the rules for writing correct and performant GC-safe code.

The fundamental rule of GC-safe native code

The GC can only determine that objects are unreachable or move objects at
GC safepoints. Without that guarantee, it would be impossible to write GC-safe
C++ code. So, the fundamental requirement for GC-safe code is that all "live"
pointers to GC-managed entities used by the C++ code must be stored in locations
known to the GC before a GC safepoint, and must be reloaded from those locations
after the GC safepoint.

Storing the pointers in a location known to the GC ensures that the GC will consider
those objects reachable and will not free them. Reloading the pointers after the safe
point guarantees that if the GC moved the objects, the native code will use the
updated pointer value.

This is all very abstract. In the next sections we will talk about more concrete
patterns.

GC safepoints

In practice, a GC safepoint is either an allocation, or a function call that might
transitively reach a GC safepoint. Not all C++ calls can reach a GC safepoint.

In the Hermes code base, we use naming conventions, parameter types, and explicit
doc-comments to communicate this. The rules are:

  • A function that takes a Runtime & or PointerBase & parameter (both of which
    provide access to the GC) is assumed to be able to reach GC safepoints, unless
    explicitly documented otherwise, or named with a suffix like _noalloc or
    _nogc.
  • A function with an _RJS suffix may invoke JavaScript code recursively
    ("Recursive JavaScript"), so it definitely reaches GC safepoints.
    For example: JSObject::getNamed_RJS(), toString_RJS(), toNumber_RJS().
  • A function that does not take Runtime & or PointerBase &, and is not
    _RJS, is generally safe to call without protecting pointers.

These rules must be followed absolutely by C++ code — all pointers to heap values
must be stored before and reloaded after. (In practice, the values simply "live" in
locations known to the GC and the C++ code loads them every time. The C++ compiler is
smart enough to optimize out consecutive loads if the underlying value couldn't have
changed.)

GC roots vs heap values

The GC needs a starting point to discover which objects are alive. That starting
point is the set of roots — pointers to GC-managed objects that live outside
the GC heap, in locations the GC is explicitly told about. Roots include:

  • The register stack — the VM's operand stack used by the bytecode interpreter.
  • Locals — stack-allocated PinnedValue<> fields registered via LocalsRAII.
  • GCScope chains — dynamically allocated PinnedHermesValue slots managed by
    GCScope (legacy).
  • Runtime fields — PinnedHermesValue fields inside the Runtime object
    itself, such as well-known prototypes (arrayPrototype, objectPrototype, etc.).

None of these live in the GC heap. They live on the C++ stack or in fixed locations
in the Runtime. The GC knows about each category and walks them all during
collection (see Runtime::markRoots() in Runtime.cpp).

Starting from these roots, the GC traces through every pointer it finds. An object
in the GC heap is alive if and only if it is transitively reachable from at least
one root. Any object not reachable from any root can be freed. If the GC moves an
object (for compaction), it updates every pointer it knows about — both roots and
pointers stored inside other GC heap objects.

The key insight for C++ code: if you hold a raw pointer to a GC object and that
pointer is not stored in a root, the GC does not know about it.
The pointed-to
object could be freed (use-after-free) or moved (dangling pointer). This is why all
live pointers must be stored in roots before any GC safepoint.

HermesValue

HermesValue is the fundamental value type exposed to the GC — it is a NaN-boxed
64-bit value that can contain JS primitives (numbers, booleans, undefined, null,
symbols) and, importantly, pointers to GC-managed objects.

The GC works on HermesValues, using the NaN-boxing tag bits to determine whether
each value is a pointer or a primitive. When a value is a pointer, the GC traces
through it, and updates it if the pointed-to object moves.

PinnedHermesValue

PinnedHermesValue is a subclass of HermesValue. The only purpose of the subclass
is to indicate that this HermesValue instance is a GC root — it lives in
memory that is not in the GC heap (and is not movable by the GC), and is known to
the GC as a root.

Note that declaring something as PinnedHermesValue does not magically register
it as a root. The declaration is a semantic marker: if a value is stored outside
the GC heap and is registered with the GC as a root (through Locals, GCScope, or
Runtime fields), it should be typed as PinnedHermesValue to indicate its
"root-ness".

PinnedHermesValue is essential for writing GC-safe code. The "locations known to
the GC" mentioned in the first section are all PinnedHermesValue roots.
Conceptually, C++ code stores all pointers to GC objects it needs into
PinnedHermesValue instances before a safepoint and reloads them after. In
practice, there are convenient C++ abstractions built on top of
PinnedHermesValue.

Handle and MutableHandle

A Handle<T> is a wrapper around a pointer to an immutable PinnedHermesValue
(const PinnedHermesValue *), where the PinnedHermesValue is known to contain a
value of type T (a number, bool, JSObject, etc.). Most internal APIs accept
Handles instead of direct pointers.

A Handle<> (i.e. Handle<HermesValue>) is an untyped handle that can hold any
HermesValue.

A MutableHandle<T> is similar to a Handle<T>, but it points to a mutable
PinnedHermesValue and allows updating the stored value.

Handle<T> is trivially copyable (it is just a pointer) and is designed to be
passed by value.

An important property: a PinnedValue<T> implicitly converts to Handle<T>. This
means that when a function accepts Handle<T>, you can pass a PinnedValue<T>
directly.

PseudoHandle

PseudoHandle<T> holds a GC-managed value without protecting it via a root. It
is a move-only type — once moved from, the original is invalidated (in debug mode,
accessing an invalidated PseudoHandle asserts).

PseudoHandle exists for performance: in many cases a function produces a value and
the caller immediately stores it in a root. Wrapping the value in a PseudoHandle
encodes in the type system that the value is unrooted and must be stored somewhere
safe before any GC safepoint. Many internal APIs return
CallResult> for this reason.

Common patterns with PseudoHandle:

cpp
// Storing into a PinnedValue (moves and invalidates the PseudoHandle):
lv.obj = std::move(*result);

// Converting to a Handle (allocates in GCScope — legacy):
auto handle = runtime.makeHandle(std::move(pseudoHandle));

CallResult and error handling

CallResult<T> is the standard return type for operations that can throw a JS
exception. It is either a value of type T or ExecutionStatus::EXCEPTION. The
typical usage pattern is:

cpp
auto result = someOperation_RJS(runtime, args);
if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION)) {
  return ExecutionStatus::EXCEPTION;
}
// Use *result or result.getValue() to get the value.
lv.obj = std::move(*result);

Most functions that can trigger GC return CallResult> or
CallResult<HermesValue>. The caller must check for exceptions before using the
value, and must store the value into a root before the next GC safepoint.

GCScope (legacy)

GCScope is a RAII-based variable-sized container of PinnedHermesValue. GCScopes
are always instantiated in a stack-like manner. They keep themselves in a singly
linked list — the root of this list is known to the GC. The GC crawls all GCScopes
during garbage collection, making all PinnedHermesValue stored there roots.

A GCScope is an efficient dynamic container — it dynamically allocates new
PinnedHermesValue slots internally and returns instances of Handle<> or
MutableHandle<>. However, to prevent unlimited growth, it is configured with a
fixed limit (48 by default in debug builds). This limit can be changed per scope.

The typical usage pattern is to declare a GCScope at function entry, allocate values
inside it as needed (keeping the handles). The GCScope is destroyed automatically
when the function exits.

IMPORTANT: Values allocated in a GCScope must not be used after the scope is
destroyed. This mistake is rare but it happens — typically by attempting to return a
Handle<> that was allocated in a GCScope that is about to be destroyed.

Implicit GCScope allocation

runtime.makeHandle() and runtime.makeMutableHandle(), as well as the
Handle<> and MutableHandle<> constructors, implicitly allocate a slot in the
topmost (i.e. most recently created) GCScope. This means:

  • If a function creates Handle<> or MutableHandle<> values (and does not
    return them to the caller), it likely needs its own GCScope, or at the very
    least a GCScopeMarkerRAII to free the slots when they are no longer needed.
    A GCScopeMarkerRAII is preferred when only one or two handles are allocated.
  • If a function calls APIs that return a Handle<>, those handles are also
    allocated in the topmost GCScope, so the same logic applies. Note that this
    only applies to functions that take Runtime & or PointerBase & — those are
    the ones that can access the GCScope and allocate new slots. Functions like
    vmcast<> that do not take these parameters simply cast an existing handle
    without allocating a new one.

Without a local GCScope or GCScopeMarkerRAII, handles accumulate in the
caller's GCScope, which can cause it to exceed its slot limit.

The problem with loops and GCScopeMarkerRAII (legacy)

A common problem when using GCScope is allocating handles in a loop, causing
potentially unbounded growth of rooted PinnedHermesValue slots. This is why
GCScope has a limit on the maximum number of handles.

One solution is to create a GCScope in the loop body. This works, but is
heavyweight — a GCScope preallocates space for 16 handles and must register and
deregister itself from the linked list every iteration.

GCScopeMarkerRAII is a lightweight alternative. It saves the state of a GCScope on
creation and frees all subsequently allocated handles on destruction. A typical
pattern is to place it at the top of the loop body:

cpp
GCScope gcScope(runtime);
// ...
for (...) {
  GCScopeMarkerRAII marker(gcScope);
  // Handles allocated here are freed at end of each iteration.
}

Or, more efficiently, create the marker outside the loop and call flush()
explicitly:

cpp
GCScope gcScope(runtime);
// ...
auto marker = gcScope.createMarker();
for (...) {
  gcScope.flushToMarker(marker);
  // ...
}

Locals and PinnedValue\ (preferred API)

All new code must use Locals + PinnedValue<T> instead of GCScope +
makeHandle() for rooting local GC values.
Do not introduce new GCScope
instances or makeHandle() calls. Existing code is being migrated incrementally.

Locals with PinnedValue<T> is the preferred way to create rooted storage for
local GC values. Handle<T> remains the standard type for passing GC values between
functions — PinnedValue<T> implicitly converts to Handle<T>, so the two work
together naturally.

How it works

You declare an anonymous struct inheriting from Locals, with PinnedValue<T>
fields for each GC value you need to keep alive. Then create a LocalsRAII to
register the struct with the runtime. LocalsRAII pushes the struct onto a linked
list (runtime.vmLocals). During GC, Runtime::markRoots() walks this list and
marks every PinnedHermesValue in every Locals struct as a root.

cpp
// Locals struct lives on the C++ stack. The PinnedValue fields are
// PinnedHermesValues that the GC will mark as roots.
struct : public Locals {
  PinnedValue<JSObject> obj;
  PinnedValue<StringPrimitive> str;
  PinnedValue<> genericValue;  // untyped — can hold any HermesValue
} lv;
LocalsRAII lraii(runtime, &lv);

Why Locals is better than GCScope

GCScope + Handle Locals + PinnedValue
Dynamically allocates PinnedHermesValue slots Fields are part of the struct — no dynamic allocation
Handle<T> is a pointer to a PinnedHermesValue — one level of indirection PinnedValue<T> is the PinnedHermesValue — direct access
Easy to accidentally allocate handles in a loop, requiring GCScopeMarkerRAII Fixed set of fields — loops cannot cause unbounded growth
48-handle limit per scope (debug assertion) No limit — number of fields is determined at compile time

Assignment patterns

cpp
// From a PseudoHandle (the typical case after a CallResult):
lv.obj = std::move(*callResult);

// From a CallResult with a HermesValue that you know is a specific type:
lv.obj.castAndSetHermesValue<JSObject>(callResult.getValue());

// From a raw pointer:
lv.obj = someJSObjectPtr;

// From a GCPointer:
lv.obj = someGCPointer.get(runtime);

// Clearing (so it doesn't keep an object alive unnecessarily):
lv.obj = nullptr;

Passing PinnedValue where Handle is expected

PinnedValue<T> implicitly converts to Handle<T>, so you can pass it directly
to functions that accept handles:

cpp
struct : public Locals {
  PinnedValue<JSObject> O;
} lv;
LocalsRAII lraii(runtime, &lv);
lv.O.castAndSetHermesValue<JSObject>(*objRes);

// getPrototypeOf takes Handle<JSObject>, but PinnedValue<JSObject> converts:
return getPrototypeOf(runtime, lv.O);

Reading from PinnedValue

cpp
// Get the underlying typed value:
JSObject *rawPtr = lv.obj.get();   // or *lv.obj
JSObject *rawPtr = *lv.obj;

// Dereference to access members:
lv.obj->someMethod();

// Get as HermesValue:
HermesValue hv = lv.obj.getHermesValue();

Template functions

When calling PinnedValue member function templates like castAndSetHermesValue<T>
from a template context where the PinnedValue type is dependent, C++ requires the
template keyword before the member name:

cpp
template <typename T>
void doSomething(Runtime &runtime, HermesValue value) {
  struct : public Locals {
    PinnedValue<T> obj;
  } lv;
  LocalsRAII lraii(runtime, &lv);
  // "template" is required because PinnedValue<T> is a dependent type:
  lv.obj.template castAndSetHermesValue<T>(value);
}

Without the template keyword, the compiler parses the < as a less-than operator
instead of the start of a template argument list, causing a compile error. This
applies to any template member function called on a PinnedValue with a dependent
type parameter.

Real-world example

From arrayConstructor in Array.cpp:

cpp
CallResult<HermesValue> arrayConstructor(void *, Runtime &runtime) {
  NativeArgs args = runtime.getCurrentFrame().getNativeArgs();
  struct : public Locals {
    PinnedValue<JSObject> selfParent;
    PinnedValue<JSArray> self;
  } lv;
  LocalsRAII lraii(runtime, &lv);

  if (LLVM_LIKELY(!args.isConstructorCall() || ...)) {
    CallResult<PseudoHandle<JSArray>> selfRes =
        JSArray::create(runtime, runtime.arrayPrototype);
    if (LLVM_UNLIKELY(selfRes == ExecutionStatus::EXCEPTION))
      return ExecutionStatus::EXCEPTION;
    lv.self = std::move(*selfRes);
  } else {
    CallResult<PseudoHandle<JSObject>> thisParentRes =
        NativeConstructor::parentForNewThis_RJS(runtime, ...);
    if (LLVM_UNLIKELY(thisParentRes == ExecutionStatus::EXCEPTION))
      return ExecutionStatus::EXCEPTION;
    lv.selfParent = std::move(*thisParentRes);
    auto arrRes = JSArray::create(runtime, lv.selfParent);
    if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION))
      return ExecutionStatus::EXCEPTION;
    lv.self = std::move(*arrRes);
  }

  // ...use lv.self throughout the rest of the function...
  return lv.self.getHermesValue();
}

Locals and loops

Locals solves the loop problem inherently — the PinnedValue fields are a fixed
set, so you simply reuse them each iteration without needing GCScopeMarkerRAII.

However, some existing code uses a GCScope + GCScopeMarkerRAII alongside
Locals for functions that call APIs returning Handle<> (which require a
GCScope). In such cases, the Locals hold the long-lived values, while the
GCScope manages short-lived temporaries created by those API calls:

cpp
struct : Locals {
  PinnedValue<JSObject> O;
  PinnedValue<> elem;
  PinnedValue<StringPrimitive> sep;
} lv;
LocalsRAII lraii(runtime, &lv);

GCScope gcScope(runtime);
// ...
auto marker = gcScope.createMarker();
for (uint32_t i = 0; i < len; gcScope.flushToMarker(marker), ++i) {
  // Temporary handles created inside the loop are flushed each iteration.
  // Long-lived values stored in lv.* persist across iterations.
  auto strRes = toString_RJS(runtime, lv.elem);
  if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION))
    return ExecutionStatus::EXCEPTION;
  lv.sep = std::move(*strRes);
}

Common mistakes

Holding raw pointers across GC safepoints

cpp
// WRONG — rawPtr may dangle after the allocation:
JSObject *rawPtr = someHandle->getObject();
auto newObj = JSObject::create(runtime);  // GC safepoint!
rawPtr->doSomething();  // rawPtr may be invalid!

// CORRECT — store in a PinnedValue, reload after:
struct : public Locals {
  PinnedValue<JSObject> obj;
} lv;
LocalsRAII lraii(runtime, &lv);
lv.obj = someHandle->getObject();
auto newObj = JSObject::create(runtime);  // GC safepoint
lv.obj->doSomething();  // Safe: PinnedValue is a root

Holding PseudoHandles across GC safepoints

PseudoHandle<T> is not a GC root — it holds an unrooted value just like a
raw pointer. It is equally dangerous to use a PseudoHandle after a GC
safepoint. This is easy to miss because PseudoHandle looks like a "safe" smart
pointer type, but it is not rooted.

Watch especially for multi-step creation patterns where two ::create()
calls are made in sequence:

cpp
// WRONG — ctor PseudoHandle is stale after the second create():
auto ctor = NativeConstructor::create(runtime, ...);
auto proto = JSObject::create(runtime);  // GC safepoint — ctor is stale!
lv.ctor = std::move(ctor);              // Too late — ctor is already stale!

// CORRECT — root the PseudoHandle before the next allocation:
lv.ctor = NativeConstructor::create(runtime, ...);  // Root immediately.
lv.proto = JSObject::create(runtime);   // GC safepoint — lv.ctor is safe.

This bug often goes undetected because the stale value is still valid most of
the time — the GC only moves objects during compaction, which is rare. Enable
HERMESVM_SANITIZE_HANDLES (enabled by default in ASAN builds) to catch these
bugs deterministically by moving the heap after every allocation.

Returning a Handle from a destroyed GCScope or Locals

cpp
// WRONG — the Handle points into the destroyed GCScope:
Handle<JSObject> createThing(Runtime &runtime) {
  GCScope gcScope(runtime);
  auto handle = runtime.makeHandle(JSObject::create(runtime));
  return handle;  // gcScope destroyed here — handle dangles!
}

// WRONG — returning a Handle from a PinnedValue that is about to be destroyed:
Handle<JSObject> createThing(Runtime &runtime) {
  struct : public Locals {
    PinnedValue<JSObject> obj;
  } lv;
  LocalsRAII lraii(runtime, &lv);
  lv.obj = ...;
  return lv.obj;  // lraii destroyed here — Handle dangles!
}

Instead, return a PseudoHandle (which copies the value out, not a pointer to the
root):

cpp
// CORRECT — return a PseudoHandle:
CallResult<PseudoHandle<JSObject>> createThing(Runtime &runtime) {
  struct : public Locals {
    PinnedValue<JSObject> obj;
  } lv;
  LocalsRAII lraii(runtime, &lv);
  lv.obj = ...;
  return PseudoHandle<JSObject>::create(*lv.obj);
}

// CORRECT — write into a caller-provided MutableHandle:
void createThing(Runtime &runtime, MutableHandle<JSObject> result) {
  // No local rooting needed — we write directly into the caller's root.
  auto res = JSObject::create(runtime);
  result = res.get();
}

// CORRECT — return a HermesValue (caller roots it, so risky):
CallResult<HermesValue> createThing(Runtime &runtime) {
  struct : public Locals {
    PinnedValue<JSObject> obj;
  } lv;
  LocalsRAII lraii(runtime, &lv);
  lv.obj = ...;
  return lv.obj.getHermesValue();
}

Forgetting to check for exceptions

cpp
// WRONG — using the value without checking for exception:
auto result = someOperation_RJS(runtime, args);
lv.obj = std::move(*result);  // May dereference an exception!

// CORRECT:
auto result = someOperation_RJS(runtime, args);
if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION))
  return ExecutionStatus::EXCEPTION;
lv.obj = std::move(*result);

Null prototype handling

When traversing prototype chains, always check for null:

cpp
auto protoRes = JSObject::getPrototypeOf(obj, runtime);
if (LLVM_UNLIKELY(protoRes == ExecutionStatus::EXCEPTION))
  return ExecutionStatus::EXCEPTION;
if (!*protoRes) {
  lv.O = nullptr;  // End of prototype chain
} else {
  lv.O.castAndSetHermesValue<JSObject>(protoRes->getHermesValue());
}

Doc GenGC (`doc/GenGC.md`)


id: gengc title: The GenGC Garbage Collector

GenGC used to be the default garbage collector for Hermes, and aims to provide a
generational garbage collector that returns memory to the OS aggressively and
frequently. The newer GC is called Hades, and it has much lower
pause times than GenGC. We recommend most users use Hades instead.

Throughout this document, we will refer to the heap, which in this context
means the memory space where JS objects reside. This is separate from the C and
C++ malloc heap, and in general if this document says "heap" it means the
space for JS objects.

Heap Segments

The heap is made out of many fixed-size regions of memory known as
heap segments (or more briefly as segments). Currently these segments
are 4 MiB, but this can be changed with the CMake build configuration variable
-DHERMESVM_LOG_HEAP_SEGMENT_SIZE=number. Memory is acquired and released on a
per-segment basis. Segments are allocated using mmap on POSIX systems and
VirtualAlloc on Windows systems. These functions allow virtual memory to be
requested for some space, and allows us to unmap subregions so that alignment
can be guaranteed. This is done in separate regions because we found that a lot
of devices have trouble allocating enough virtual memory upfront to have the
entire heap be contiguous. By allowing it to grow virtual address space usage
with the size of the heap (and shrink as well), it works on a broader set of
devices. We chose 4 MiB as the size of segments because it was small enough to
not exhaust virtual address space, while also being large enough to not have a
lot of overhead in managing the segments.

Each heap segment is aligned to begin on a pointer address that is a multiple
of its size. For example, if segments are 4 MiB wide, then their start
addresses are aligned on a 4 MiB boundary (the low 22 bits are all 0). This
means for any pointer into the JS heap, you can get the segment start address
with a simple bitwise-and operation. All metadata for the segment is stored at
the front of the segment.

At the beginning of a segment, there is a small amount of memory reserved for
some metadata about the heap. Specifically, the following things are kept there:

  • Segment ID: explained in Compressed Pointers
  • CardTable: explained in Generations
  • MarkBitArray: explained in Mark Phase
  • Guard page: a page of memory that is protected from reads and writes to ensure
    bugs in the VM or GC don't accidentally overwrite metadata.

The rest of the segment is free space available for JS values to be allocated
into. See
AlignedHeapSegment::Contents
for details about the layout of a heap segment.

Object Types

In order to determine reachability, we need to be able to discover
what objects point to. To do this for the variety of types in Hermes, we
define a system of metadata for each object, associated to its type which is
encoded as a VTable. Metadata is built once for the first Runtime created, and
is then re-used for every subsequent runtime. The metadata describes at what
offsets from the head of an object there are pointers. It also describes for
any array type where the length of the array can be found, so a dynamic number
of pointers can be found. This design allows pointers to be marked without
performing any type checks, branches, or virtual dispatch.

The VTable * is embedded in the header of everything allocated on the heap.
This is represented by the C++ superclass of all heap objects, called GCCell.
This way, given a pointer to something in the heap, its type (and pointer
metadata) can be found. The type is stored as a CellKind enum. The VTable
also includes function pointers for dynamic dispatch. This is used to implement
type-based dispatch for things like property accesses and function calls.

Generations

GenGC is so named because it is generational, meaning it manages groups of
allocated objects based on their age. Age is determined by how many collections
an object has survived. GenGC has two generations: the Young Generation (YG)
and Old Generation (OG).
Allocations go initially into YG, and if they survive the first YG collection
cycle, they are promoted to OG. The OG is collected less frequently and over a
larger number of objects. YG is collected frequently because it is assumed that
there are a lot of objects that were allocated but became garbage quickly. This
is known as the
Generational Hypothesis:
young objects are more likely to die than old objects.

The young generation of GenGC is a single segment, regardless of the size
of the heap as a whole. Since YG is much smaller than OG, this is also a much
shorter pause to collect YG. Since the YG is also a single segment, it's very
fast to check if a pointer is a YG pointer: get the segment start address and
compare to the YG start address. The allocation algorithm is as follows:

  • Attempt to allocate into the YG segment by bumping a pointer called the
    "level" of the segment up. If the level would go past the end of the segment, it
    fails
  • If the bump allocation succeeds, the allocation is complete and control
    returns to the VM
  • If the bump allocation fails, start a YG collection cycle. This will evacuate
    the YG into the OG, and leave some empty space.
  • Try bump allocation again after the YG collection cycle completes. If it
    succeeds, return.
  • Else, try to allocate directly into the OG.
  • If the OG is full, start a full collection cycle (collect garbage in both the
    YG and OG). Once that completes, try allocating directly into OG again.
  • If all that fails, that means there's not enough space for the allocation,
    raise an Out of Memory (OOM) error

The old generation of GenGC is a list of segments, each of which is created
after an allocation failed in the previous segment. The OG maintains an
allocation context which remembers the most recent active segment, and this is
where promoted YG objects are allocated into. Once the OG has reached its
configured size limit, it will do a full collection, trying to free garbage
inside the OG.

There are more details on collection cycles in the
Collection Cycles section.

Allocate directly into the Old Geneneration (Pre-Tenuring)

Sometimes there are applications where the generational hypothesis doesn't hold
true, or in other words they allocate more objects that are long-lived than
short-lived. This typically occurs during the initialization period of an
application. If this rule applies to your application, you can request this
behavior from Hermes by changing the GCConfig that's used at Runtime
construction time:

cpp
hermes::vm::GCConfig::Builder gcConfigBuilder{};
gcConfigBuilder
    .withAllocInYoung(false)
    .withRevertToYGAtTTI(true);
std::unique_ptr<jsi::Runtime> runtime = makeHermesRuntime(
    hermes::vm::RuntimeConfig::Builder()
        .withGCConfig(gcConfigBuilder.build())
);

The AllocInYoung parameter defaults to true, and controls whether
allocations go into YG by default or OG.
The RevertToYGAtTTI parameter defaults to false, and is unused unless
AllocInYoung is true.

See the full documentation of GCConfig for
more details and other configuration options.

Collection Cycles

GenGC determines garbage that can be collected in a program based on what is
reachable in the graph of objects. The graph has roots that define the
entrypoint to the graph. These are typically things like variables on the
JS call stack. From the roots, the entire graph of objects is traversed by
following each property to another object. Think of it as similar to a
traditional graph search algorithm, although we make many optimizations specific
to the heap structure.

GenGC has two different types of garbage collection cycles
(abbreviated as "GC"). There is a Young Generation Collection or YG GC, and a
Full Collection or full GC.
A YG GC collects only objects present in the YG, and promotes objects that are
reachable to the OG so they are collected less frequently.
A full GC collects both the OG and YG simultaneously, and determines the
reachability of every object in the heap.

There are two common concepts across both types of collections:

  • Mark: Marking means setting some state related to an object to say that it was
    found to be reachable during the traversal of the heap. In GenGC's case, this
    is often referred to as a "mark bit"
  • Sweep: Sweeping means reclaiming unused space so that it can be re-used by the
    allocator. This can look very different depending on the generation

Young Generation Collection

When a YG collection is triggered, it first needs to mark the roots of the
object graph. Because YG collections occur very frequently, we want the set of
roots to be small. To achieve this we ignore any roots that are known to be
only in the OG. When each pointer is discovered for the first time, an
allocation is made in the OG for the same size, the YG object's contents are
copied, and a forwarding pointer is left in the header of the YG object pointing
to the new OG object. After that, if we see the same pointer again, we replace
it with the value of the forwarding pointer.

We also need to consider any OG objects that point into YG as part of the root
set. That is handled by a separate mechanism during JS execution, detailed below
in the section on Write Barriers.

To find all the reachable objects, we take advantage of the linear nature of
OG, and scan linearly from the original end of the allocated region, until
there aren't any more promoted objects. Each promoted object is scanned for its
own pointers. This is how all YG objects are discovered. It's similar to a
semi-space algorithm where the second space is the OG.

Once the objects are all moved, any weak references pointing to YG objects are
updated if the object is still alive, and then any finalizers are run by
iterating over a list of finalizers.

Write Barriers

As part of the root set, we also consider any OG objects that point into YG.
To determine where these exist without iterating over the whole OG, we use a
write barrier whenever a pointer value is written to during JS execution.
If it would create an old -> young pointer, we want to mark the source object
as part of the root set of YG.

The way we do this is by classifying a small region of memory in the OG as a
card, currently 512 bytes. We determine the card for a pointer using the
same alignment trick to get the base address of the segment, then use division
to figure out the card index. The card table is a list of bytes stored in
the header of the segment, and when we find an object pointing into the YG, we
"dirty" its card by setting the byte value to 1. We don't use a bitmap because
the card table is only 8 KiB, and shrinking it further isn't worth it.

During YG collection time, we can quickly scan each card table for the set of
dirtied cards, then we mark all of the objects on that card. We use a separate
card object table to find the first object for a card. That table contains
another byte per card, except it is signed. If the sign is positive, it means
"go back that many bytes to find the first object on this card". If the sign
is negative, it means "go back that many cards and check the table at that
index". This encoding scheme allows a low memory cost way to jump across very
large objects.

Note that the way cards are dirtied means there's a chance of promoting some
YG objects that aren't actually live. This tradeoff is chosen so the cards take
up less memory and can be scanned quickly. Smaller cards are more precise, but
take up more memory and require more scanning.

Full Collection

A full collection is much larger than a YG collection, so it happens less
frequently. It needs to determine the reachability of every object in the heap.
A full collection is triggered when the OG doesn't have enough space to satisfy
an allocation from a YG promotion or a direct-to-OG allocation.

Mark Phase

The collection begins with a call to markRoots, although this time
additionally marking roots known to only point into the OG, such as the
IdentifierTable. The first time an object is encountered, it sets a bit in the
mark bit array, so future accesses know that it has already been found. Then we
push the newly discovered object on top of a mark stack.

The mark stack is drained by popping off the top object, scanning all of its
pointers, and pushing objects it discovers onto the mark stack.

There are two changes implemented to prevent the mark stack from growing without
bound:

  • If a pointer to an object is discovered that is after the current address, it
    isn't pushed on the mark stack. Instead, we jump to the next marked bit when
    the mark stack is empty. If the pointer is before the current address, it has to
    be pushed on the stack because we won't go backwards in the heap normally
  • If the mark stack overflows a limit, we drop the whole stack and re-scan from
    the beginning of the heap. Since many objects have already been marked, this
    will go much faster than previous times. It's guaranteed to terminate because
    in the worst case eventually every object will be marked and there's nothing
    to collect.

Once the marking is complete this way, full reachability is known, and sweeping
can begin.

Weak Map resolution

However, one problem to be handled are weak references, which are pointers
that do not keep what they point to alive. Now that we know reachability, we
know which weak pointers to make null.

But there's a special case of the JS type WeakMap that has to be handled:
the keys are weak, and the values are strong, unless they keep the key itself
alive. This means the values can't be marked until the key is determined
reachable through some other means.

We delay marking any WeakMap key/value pairs until the end of marking. Then for
each WeakMap, and for each key that is reachable in the weak map, we then mark
the value. If marking the value ends up making another WeakMap key reachable,
we then need to mark that. We repeat this for every key that becomes reachable
until there's nothing more to mark. At that point we can finally null out keys
that were never found to be reachable.

Sweep & Compact Phases

Now that full reachability has been computed, we know which objects are garbage.
We want to reclaim that memory so that it can be allocated again. To do so, we
will compact all live memory down so that there's no space between them,
overwriting the space that was used by dead objects.

To do this, we iterate through every live object in the heap using the mark
bits, and for each one we compute its new address. We place that new address
as a forwarding pointer in the header of the object (displacing the VTable *
to a temporary vector to avoid losing the type information). Once a forwarding
pointer has been determined for everything in the heap, we do a second pass to
fixup pointers within objects to use the forwarded value. Finally, we copy the
memory from the previous location to the new location, overwriting whatever used
to be there.

The end result of this is that all live memory sits right next to each other in
the heap segments, and all dead memory has now been reclaimed for the allocator.
We use madvise on POSIX systems to tell the operating system that the leftover
memory is now not needed by the program, and it's free to be paged out and zero
filled if the OS needs to.

At this point control returns to the JS engine, typically to finish the
allocation that started the collection.


Doc Hades (`doc/Hades.md`)


id: hades title: The Hades Garbage Collector

Hades is a garbage collector for Hermes that aims to improve pause times by an
order of magnitude over GenGC. The main principle Hades uses to achieve those
low pause times is that most of the garbage collection work happens in a
background thread concurrently with the interpreter running JavaScript code.
This is distinct from GenGC, which only runs on a single thread which is shared
with the interpreter.

Enabling Hades

In local builds on the command line using CMake (which forwards to CMake), Hades
is the default GC used, and currently the only GC supported for production use.
The GC being used is controlled by the CMake variable -DHERMESVM_GCKIND=value.

To use a pre-built package of Hermes with Hades enabled, check the
Releases page on Github.
As of right now, there aren't any available, but we'll be making one available
with v0.8 and later.

Check Which GC is Used

If you want to know what GC is being used in your application, you can find out
with some JS:

js
const gcName = HermesInternal.getRuntimeProperties().GC;
// If you're running Hermes on the command line, use print.
print(gcName);
// If you're running Hermes in some kind of framework like React Native,
// console.log should exist.
console.log(gcName);

This will print one of:

  • "hades (concurrent)": You're using Hades in concurrent mode
  • "hades (incremental)": You're using Hades in incremental mode

Basics

Most of the basic heap structure of Hades is similar to GenGC, so it is
recommended to read the GenGC Documentation first, in particular
the following sections:

Generations

Similarly to GenGC, Hades also has two generations: the Young Generation
(YG) and Old Generation (OG). Allocations go initially into YG, and if they
survive the first collection they go into OG. YG works exactly the same as
GenGC, but OG has a different allocation strategy that allows for gaps.

Freelist Allocator

Hades's OG is a list of heap segments, and each heap segment maintains a
Free List of empty space. Each Free List Cell points to the next cell,
called an explicit free list. This is opposed to an implicit free list, where
the length is used to traverse both free and used cells. A free list is used
because it allows empty space to be left where it is, without requiring
compaction. This is a requirement for concurrent allocations and sweeping.

Furthermore, the free list is size-segregated, meaning each size class gets
a separate free list. In other words, cells of size N only point to other
cells of size N. This allows an allocation of size N to be satisfied
instantly with the head of the free list. Each free list head is stored at an
index in a fixed-size array for small cell sizes. These are known as
buckets.

Hades does not do any rounding up of sizes beyond the required heap alignment
of 8 bytes. This means there is one bucket for each multiple of 8, up to 2048
bytes. From 2048 bytes to the maximum heap segment size (4 MiB) the buckets go
by powers of 2. For large buckets, we store cells that are greater than or equal
to the size bucket, but less than the next power of 2. Cells that need less than
the size of the free list cell carve out a small piece of the cell, and put
the remaining piece on a the free list corresponding to its new size.

Due to having these free lists be per-segment, we need a quick way to find which
segment has free space for a given size. We do this with a series of bit arrays,
where bits are flipped to 0 as a free list is exhausted in a segment, and
flipped back to 1 when sweeping frees some cells. We have per-segment free lists
so that sweeping and compaction (which also operate on a per-segment basis) can
destroy them efficiently and create a new list. It can do this to easily
coalesce adjacent free regions.

Collection Cycles

Hades has two different types of collections: a YG collection (YG GC) and an
OG collection (OG GC). The former is almost exactly the same as GenGC's, so we
won't repeat it here. The OG GC is very different though, because it runs
concurrently in a background thread.

For the purposes of distinguishing these two threads, we'll name them as
follows:

  • Mutator Thread: The thread running the JS interpreter
  • GC Thread: The thread running any GC operations such as marking or
    sweeping

Note that currently there is only ever a single GC thread at any point in time.
We also cache the thread and reuse it instead of making a new one for each
collection.

There are three different locks used throughout the GC:

  • The GC Mutex is used to protect structures like mark bits, card tables,
    and the free lists
  • The WeakRef Mutex is used to protect structures used during weak ref
    marking
  • The Write Barrier Mutex is used to protect a small buffer used by write
    barriers and concurrent marking

The GC mutex is used to protect most things, as they tend to all be accessed at
the same time. There was no need for finer grained locks yet, with the exception
of the write barrier mutex because write barriers are executed all the time.

An OG GC is started once the OG is about 75% full. We start it a bit early so
that it can complete sweeping before reaching 100% full and avoid blocking any
allocations.

Mark Phase

The first step of an OG GC is to mark all of the roots of the object graph.
We only ever start an OG GC when YG is empty, so there's no need to mark any of
YG.

Marking an object consists of the following steps:

  • Using mark bits, check if an object has been visited already
  • If it has been visited already, there's nothing to do
  • If not, push it onto a mark stack that will be drained later
  • Set its mark bit
  • If the object pointed to is a WeakMap, put it onto a separate stack
    (see Weak Map Resolution)

Draining the mark stack works as follows:

  • Acquire a lock on the GC mutex
  • Check if the write barrier buffer has objects that need to be marked, if so,
    add them to the mark stack
  • While we've marked fewer than a certain number of bytes, defaults to 8 KiB
    • Pull one object off the mark stack
    • Get its type metadata (see Object Types)
    • Use the metadata to find pointers to other objects
    • Those other objects will be pushed on the stack
  • Release the lock on the GC mutex

This can run almost entirely uninterrupted on the background thread since very
few things need to acquire the GC mutex. The most common way to interrupt
marking is when YG fills up, as it requires the GC mutex in order to evacuate
YG.

Write Barriers

There's an important race condition to consider when thinking about concurrent
marking: what happens if a pointer is modified while we're reading it?

There are two different races that are possible here:

  • A non-atomic read of the pointer might race with a non-atomic write, and the
    reads or writes might "tear" (meaning you see only part of the write)
  • You might miss marking the old value or the new value

The first is handled on 64-bit platforms because all of the reads are of a 64
bit value, which can be atomically handled cheaply on a 64-bit CPU. See the
Incremental Mode section for what we do on a 32-bit CPU.

The second problem is harder to solve. If we see the old value, and the new
object isn't marked anywhere else, we would accidentally think it's garbage and
collect it! Alternatively, if we see the new value, the old value won't be
marked. This would be a problem if the old pointer was moved from one object to
another, but we had already marked the second object.

In order to fix this, we need to know when a pointer is modified during
concurrent marking. Hades implements this through an additional write barrier.
This write barrier is based on a principle called "Snapshot at the Beginning"
(SATB). The principle is that we want to collect the OG based on a snapshot
of the heap when the collection began. Which means if a pointer is changed, we
want to make sure we mark the old value instead of the new value.

This might feel counter-intuitive compared to the more common alternative
approach known as "Incremental Update" (IU), where the new value is marked. The
reason Hades uses SATB instead is that it has a nice guarantee: you'll never
need to revisit any object you have already marked. This means there is a finite
upper bound on the amount of work marking has to do. IU write barrier based
systems often have a race near the end, where the GC thread needs to pause the
mutator thread to try and complete marking as fast as possible. If it exceeds
a time quota, it resumes the mutator and tries again later. We avoid this
complexity with our SATB barrier.

A second benefit of SATB is that we can treat any allocations made into OG
between the start and end of the collection as alive by default, without
needing to mark them.

And the final benefit of SATB is that there is never a need to mark the roots
again to finish a collection, as their old values were handled at the start
of the collection while the mutator was paused.

The barrier works by pushing the old value onto a small fixed size buffer, which
has space for 128 elements. Once it fills up, the Write Barrier Lock is taken to
"flush" the buffer into a separate mark stack used by the concurrent marker.
This means a lock is only taken every 128 write barriers. It uses a separate
mutex from the GC mutex to ensure a write barrier is not blocked for very long
if the GC thread happens to be reading from the separate mark stack.

Complete Marking

Once the mark stack is empty, there are a few details that need to be handled in
order to complete marking and move on to sweeping:

  • Flush any remaining write barrier pointers left
  • Handle WeakMap resolution
  • Fix weak references (WeakRefs)

Handling these things can be very tricky concurrently, so in order to prevent
bugs and infinite loops, we pause the mutator during this time. Even though SATB
write barriers don't require the mutator to pause, these other operations do
require a pause, so unfortunately this is still required.

Flushing the remaining write barrier pointers just means copying the pointers
into the mark stack and draining it one more time. This could potentially take
a very long time, but in practice that is exceedingly rare.

WeakMap resolution is handled in the same way that GenGC handles it. We do this
during a mutator pause mostly because we didn't want to rewrite the algorithm
to work in a concurrent context, as it's already very complicated on its own.

Weak References also need to be cleared if they point to something that is
now garbage, and this is much easier to do with the mutator paused. Otherwise
the mutator would need a lock to read a weak reference value. This could be
improved to simply be an atomic operation in the future, but for now this can't
be atomic.

Once that's all taken care of, we can move on to sweeping.

WeakRef Read Barriers

There's a caveat to mention about WeakRefs and the SATB barrier. If you read a
pointer out of a weak reference and store it in an object on the heap, SATB
won't record the change, and the object might not be found reachable. Something
similar can happen if a weak ref is read and placed into a root.

To fix this, we have WeakRef reads perform a barrier on the pointer being read.
This conservatively assumes the pointer being read is alive. WeakRefs are not
read from that often, so this was deemed an acceptable cost.

Note that there also weak roots, such as the HiddenClass cache stored in each
CodeBlock. These do not perform a read barrier, specifically because they are
only ever used for comparisons. They never produce a pointer that was otherwise
dead. A possible simplification of this in the future could use a HiddenClass ID
instead of a pointer, as it achieves the same effect without requiring a special
case.

Sweep Phase

Once complete reachability information is known, the OG GC turns off the
SATB barriers. The sweeper iterates over one segment at a time, allowing the
mutator to interleave. This is specifically allowed because the sweeper only
ever modifies garbage objects that aren't used, therefore there's no races.
It holds the GC mutex to prevent YG from allocating into the OG while it's
being swept.

The process works as follows:

  • Acquire a lock on the GC mutex
  • Clear one heap segment's free list
  • Iterate linearly over cells, using the embedded length to skip over live cells
  • Check if a cell is new garbage using its type tag
  • If it is not new garbage, continue to the next cell
  • If it is new garbage, turn it into a free list cell.
  • Contiguous unused regions are added as a single region onto the new free list
  • Once all cells in the segment have been processed, release the lock

Once that process is completed for every heap segment, sweeping completes and
the OG collection is over.

Compact Phase

Compacting live memory to be closer together is still a beneficial concept in
Hades, as it allows us to return unused memory to the OS, and reduces the
fragmentation of the free lists for mostly empty heap segments. Implementing it
is more tricky than GenGC though, as we can't modify pointers concurrently with
the mutator thread.

Due to these restrictions, we can currently only compact a single segment
(called the compactee) for each full collection. Compaction runs as part of the
collection cycle and flows as follows:

  1. At the start of an OG collection, determine whether the heap is currently
    larger than its target size. If so, select and record a segment to compact.
  2. Write barriers start dirtying cards for pointers pointing into the
    compaction candidate. This will continue until the compaction is fully complete.
  3. Marking begins. During marking, we dirty cards in the card table
    corresponding to any on-heap pointers that point into the compaction candidate.
    Any YG collection that occurs during marking needs special care. Promoted
    objects will not be scanned by the OG since they are allocated as marked, so
    they need to be scanned for compactee pointers after they have been promoted.
    Furthermore, the card table cannot be cleared at the end of the YG collection,
    since that would erase information from the ongoing compaction.
  4. During the STW pause, the internal state of the GC is updated to signal that
    all pointers into the compactee have been marked, and that the next YG
    collection should complete the compaction.
  5. Sweeping. The segment identified for compaction will not be swept, however
    compaction may take place during sweeping if the next YG collection starts
    before sweeping is complete. Note that write barriers will continue to be active
    until compaction is complete, since new pointers from the OG into the compactee
    may be added.
  6. Compaction. The next young gen collection evacuates both the YG and the
    compactee. It will mark long lived roots and update pointers based on the
    previously dirtied cards. Combining compaction with YG collections lets us
    share the overhead of updating roots, and lets us avoid tracking pointers from
    the YG into the compactee.
  7. The now empty segment is released by the GC and returned to the OS.

Incremental Mode

Hades's concurrent marking relies on being able to read a 64-bit value
atomically at the same time it might be modified by the mutator. If the
underlying hardware supports this natively, then we use it.
However, some hardware does not support doing those atomic reads in a lock-free
manner, primarily 32-bit ARM CPUs. Since Hermes's main target is mobile devices,
it's important for us to still support them, and have some of the fast pause
time guarantees that Hades gives.

In order to do this, on 32-bit platforms we don't use any other threads, and
instead run Hades in "incremental mode". This means instead of marking objects
concurrently on the GC thread, we use a portion of each YG GC to do some OG GC
work. This means the OG GC is completed incrementally on each individual YG GC.
Each YG GC takes a little bit longer while an OG GC is active, but the penalty
is small enough to still have better guarantees than running a fully blocking OG
GC.

The concurrent mode of Hades has faster pauses and is preferred to be used if
possible, but incremental mode has to be used on most 32-bit CPUs. You can also
use incremental mode if threads aren't supported on your platform, or if you
prefer to not use threads for some other reason.


Doc HighLevelOptimizations (`doc/HighLevelOptimizations.md`)


id: high-level-optimizations title: High Level Optimizations

JS authors frequently use high level library functions which they expect to perform efficiently.
They call these in conjunction with other library calls to write code which has similarities to
functional programming languages.

Executing this kind of code in Hermes can be slow due to the compiler not having proper visibility
into the JS library. However, we can get some information about what is being called in certain
scenarios, such as with -fstatic-builtins or in actual typed SH code.

This document is a place to put some ideas on how to make specific scenarios more efficient
in the future. It is possible we'll just happen to optimize some cases here as a result of good
type information in SH and a solid optimization pipeline.

Spreading and modifying existing structures

An example of some sample code found in input to Hermes to clone a Map and add an element:

text
return new Map([...Array.from(origMap), [id, obj]]);
  1. Iterates origMap and creates a new array %tmp1, element by element.
  2. Creates a new array %tmp2, iterates %tmp1, and copies the elements.
  3. Adds the new array [id, obj] to %tmp2.
  4. Iterates %tmp2 and populates a newly created Map.

Some potential ways to simplify things:

  1. ...Array.from(a) is the same as ...a provided that the array iterator has not been modified.
  2. new Map([...oldMap, [x, y]]) is the same as const %tmp = new Map(oldMap); %tmp.set(x, y)
    provided that the Map iterator has not been modified.

If we can enforce that iterators haven't been modified, then we can do step 1 potentially even in
legacy code, and step 2 in typed code.


Doc IR (`doc/IR.md`)


id: ir title: Design of the IR

Introduction

This document is a reference manual for the Hermes High-level IR. The IR is a
Static Single Assignment (SSA) based representation that captures the JavaScript
language semantics. It features optional types (values may be annotated with
types).

The IR representation is designed to be used as an in-memory form. The IR can be
dumped to human readable assembly-like format.

Well-Formedness

This section describes the rules that define a valid IR.

  • Instructions must be dominated by their operands.

  • Basic block must end with a terminator, which is a branch instruction or a
    return instruction.

  • There has to be exactly one terminator for each basic block.

  • AllocStack instructions must appear at the 'entry' basic block (the first basic
    block in the function).

  • PHI Nodes must appear at the beginning of the basic block and must contain
    an entry for each predecessor.

Frames:

Every JavaScript function is a closure that can capture variables from its
declaration scope. The declaration scope may be the global scope or another
function. A closure is a pair of function code, which contains the code of the
function, and a context which saves the environment that the closure may access.

Function variables that are captured by a closure are allocated in a scope on
the heap and the scope is passed as part of the context to the closure.
Closures may access variables at all nest-levels, which means that they can
access multiple scopes. We implement this feature by nesting scopes and linking
them together. Each scope (the set of captured variables) also has a reference
to the scope of the caller function. Closures can access variables at different
nesting levels by loading the parent scope for each scope.

At the IR Level we define a few instructions that can load and store frame
variables. Functions own variables, that are storage units that represent
variables at the source level. These variables can be optimized away or
promoted to the stack, etc. We define the AllocStack instruction for allocating
variable storage. The only instructions that can access the storage values
(Variable and AllocStack) are the Load and Store instructions. It is not
possible to save the address of the allocation itself. Depending on the
implementation of the virtual machine, the heap allocations may be packed into a
single frame.

Types:

The Hermes high-level IR is optionally typed. Values may be annotated with the
expected type of the value. The types are optional and untyped programs are
correct. The types represent primitive JavaScript types, or refinement of these
types. All values can be annotated with types: Functions, Instructions and
Parameters. Annotations of functions represent the type of the returned value.
The type annotations are not a suggestion or a hint. They must be correct and
consistent or else the program may fail at run time. Programs are expected to
run and have the same semantics when the type annotations are stripped.

Some instructions in the IR expect operands of specific types, or produce
specific types. For example, the CondBranch instruction expects the condition
operand to be annotated as a boolean type. The ToBool instruction produces
values that are annotated as boolean type.

The optimizer may optimize the IR based on type annotations. For example, if the
operand of the instruction ToBool is annotated with the boolean type then the
optimizer is free to remove the instruction and replace all uses with the
operand of the conversion instruction.

Example:

This is a short example of a valid program in textual IR. The function contains
four basic blocks with a few instructions. The code below follow the
requirements of a well-formed function defined in the previous section.

text
function forEach(cond : boolean, value : number)
    %BB0:
      %0 = BranchInst %BB1
    %BB1:
      %2 = CondBranchInst %cond, %BB2, %BB3
    %BB2:
      %3 = ReturnInst %cond
    %BB3:
      %4 = ReturnInst %value

Side Effects:

The instructions in the IR are annotated with side effect labels. The
labels describe the kind of effect the instruction has on memory, IO
state (for example, hardware IO such as drawing to a screen or sending
a network packet), and whether they can throw. The two kind of memory
side effects are 'read' and 'write'. ('write' actually indicates
"read and/or write".) These side effect specifications indicate that
the instruction does not have IO effects or throw. The 'unknown' side
effect is maximally conservative: it indicates that the instruction
may read, write, have IO effects, and/or throw. These side effect
annotations allow the optimizer to decide which optimizations are
legal. For example, it is not legal to sink instructions that can
write to memory past each other. It is not legal to hoist instructions
that read to memory outside of a loop if there is another instruction
that writes to memory in that loop, because the writer instruction may
clobber memory that the reader may read.

Some instructions that allocate memory are marked as not having any side
effects. This is because the instructions only touch the newly allocated memory
and do not influence the rest of the heap. Notice that we don't model the effect
of instructions on the garbage collector or anything like that, just the content
of the allocated memory.

Register allocation

In Hermes, register allocation is performed on the IR. An infinite number of
virtual registers are allocated while preserving the constraints of the IR and
of the low-level target. Registers are allocated in an attempt to reduce the
number of registers, values across PhiNodes are coalesced, AllocStack
instructions are assigned with a dedicated register and arguments to call
instructions are placed in consecutive registers.

While in SSA form, the IR does not support all of the things that can be done
with registers. This is why the Register Allocator introduces MOV instructions
that represent a copy of one register to another. We lower some of the
load/store instructions into MOVs, and spill registers with MOV instructions.

Generator Overview

When generating the IR for a generator function, we make two functions:
an outer GeneratorFunction and an inner function.
The outer function calls CreateGenerator on the inner function,
and returns the resultant generator.

The inner function assumes that it can store and retrieve state from
its own closure. As such, it contains instructions to start, save/yield,
and resume generators.

CreateGenerator:
An instruction to create a generator given a Function.
First, it creates an inner GeneratorInnerFunction, then it wraps it in a Generator object.
Used by the GeneratorFunction to create the generator which is returned upon calling it.

StartGenerator:
Always the first instruction executed when an inner function is called.
Restores values for all local variables in the generator,
and jumps to the resume point of the generator if it's suspended.
If the generator hasn't been started yet, simply continues execution.

SaveAndYield:
Saves necessary state to the closure and yields execution.
In practice, this will save state and use the return opcode to
allow the caller to get the yield result.
Emitting a SaveAndYield also emits its corresponding ResumeGenerator.

ResumeGenerator:
Placed at the start of the block following the SaveAndYield to which it
corresponds. Resumes execution by loading context from the closure,
and then using state stored in the closure, does one of 3 things:

  • Continues execution with the result of the instruction being the
    argument to .next().
  • Throws a value immediately
  • Sets an %isReturn flag to true, which later instructions may branch on to execute the finally
    if necessary, and then return.

Instruction semantics

This section describes the semantic of the instruction that are defined in the
IR. Please make sure to update this section as new instructions are added to the
compiler.

BranchInst

BranchInst _
Description Jumps to a different basic block.
Example %0 = BranchInst %BB1
Arguments A single operand which is the target basic block.
Semantics Terminates a basic block and 'jumps' to a different basic block.
Effects Does not read or write from memory.

ReturnInst

ReturnInst _
Description Leaves the function and returns a value.
Example %0 = ReturnInst %17
Arguments A single operand which is the returned value. Notice the functions that return without an explicit value return the 'undefined' value.
Semantics Terminates a basic block and transfer the control to the caller of the current function.
Effects Does not read or write from memory.

AllocStackInst

AllocStackInst _
Description Allocates a variable on the stack.
Example %0 = AllocStackInst $name
Arguments $name is the textual representation of the variable at the sourcecode level.
Semantics AllocStack allocates a variable on the stack. Depending on the implementation of the VM, the variables may be packed into a single frame. AllocStack values may be used by instructions in different functions that represent closures created by the current functions. AllocStack values are used to represent local and captured variables. The AllocStack itself needs to be used directly. It is not possible to save a reference to the reference. The lifetime of the AllocStack may not exceed the lifetime of the allocating function.
Effects Does not read or write from memory.

LoadFrameInst

LoadFrameInst _
Description Loads a value from a variable.
Example %1 = LoadFrameInst %scope, %variable
Arguments %variable is a location in %scope to load from.
Semantics The the instruction reads from a variable. The variable must be valid and found in the given scope.
Effects Reads from memory.

LoadStackInst

LoadStackInst _
Description Loads a value from a stack allocated memory pointed by a reference.
Example %1 = LoadInst %0
Arguments The address from which the instruction loads.
Semantics The the instruction reads from memory. The address must be a valid stack address.
Effects Reads from memory.

StoreFrameInst

StoreFrameInst _
Description Stores a value to a frame variable.
Example %1 = StoreFrameInst %scope, %value, %variable
Arguments %value is the value to be stored. %variable is a variable in %scope where where the value will be stored.
Semantics The the instruction saves a value to memory. The variable must be a real variable found in the scope.
Effects Writes to memory.

StoreStackInst

StoreStackInst _
Description Stores a value to a stack allocated memory.
Example %1 = StoreStackInst %value, %stack_allocated
Arguments %value is the value to be stored. %address is the reference to stack allocation.
Semantics The the instruction saves a value to memory. The address must be a valid stack allocation.
Effects Writes to memory.

ToPropertyKeyInst

ToPropertyKeyInst _
Description Converts a JavaScript value into a property key.
Example %1 = ToPropertyKeyInst %input
Arguments The value to cast.
Semantics Implements ES15 7.1.19 ToPropertyKey ( argument ).
Effects May execute.

AsNumberInst

AsNumberInst _
Description Casts a JavaScript value into a number value.
Example %1 = AsNumberInst %input
Arguments The value to cast.
Semantics The instruction follows the JavaScript rules for converting types into numbers.
Effects May read or write to memory.

AsInt32Inst

AsInt32Inst _
Description Casts a JavaScript value into a signed 32-bit integer value.
Example %1 = AsInt32Inst %input
Arguments The value to cast.
Semantics The instruction follows the JavaScript rules for converting types into 32-bit signed integers.
Effects May read or write to memory.

AsUint32Inst

AsUint32Inst _
Description Casts a JavaScript value into an unsigned 32-bit integer value.
Example %1 = AsUint32Inst %input
Arguments The value to cast.
Semantics The instruction follows the JavaScript rules for converting types into 32-bit unsigned integers (equivalent to >>> 0).
Effects May read or write to memory.

AddEmptyStringInst

AddEmptyStringInst _
Description Convert a value to string as if evaluating value + ''
Example %1 = AddEmptyStringInst %input
Arguments The value to cast.
Semantics The instruction follows the JavaScript rules for adding an empty string to a value (ES5.1 11.6.1).
Effects May read or write to memory or throw.

CreatePrivateNameInst

CreatePrivateNameInst _
Description Create a globally unique private value, settings its internal [[Description]] to the given string literal. Practically speakig, this will result in a symbol value created at runtime with its [[Description]] set.
Example %1 = CreatePrivateNameInst %descString
Arguments %descString is the string description of the name. It must be a string literal.
Semantics Allocate a new globally unique private name.
Effects Does not read or write to memory.

CondBranchInst

CondBranchInst _
Description Jumps to one of two blocks depending on a condition value.
Example %1 = CondBranchInst %cond, %BB1, %BB2
Arguments %cond is the condition variable, %BB1 is the 'True' block, %BB2 is the 'False' block.
Semantics The instruction observes the value of a typed value and jumps to one of two basic blocks. If the condition is evaluated as 'True' the program jumps to the 'True' block. Otherwise the program jumps to the 'False' block.
Effects Does not read or write from memory.

HBCCompareBranchInst

HBCCompareBranchInst _
Description Performs a binary comparison of the two operands and a conditional branch depending on the result.
Example %0 = CompareBranch %x, %y, %BB1, %BB2
Arguments %x and %y are the operands of the binary operation, %BB1 is the 'True' block, %BB2 is the 'False' block.
Semantics The instruction follows the rules of JavaScript for each one of the binary operators defined in the instruction. If the condition is evaluated as 'True' the program jumps to the 'True' block. Otherwise the program jumps to the 'False' block.
Effects May read and write memory.

HBCFCompareBranchInst

HBCFCompareBranchInst _
Description Performs a binary comparison of the two numbers and a conditional branch depending on the result.
Example %0 = HBCFCompareBranch %x, %y, %BB1, %BB2
Arguments %x and %y are the operands of the binary operation, %BB1 is the 'True' block, %BB2 is the 'False' block.
Semantics Performs a numeric comparison on the two doubles. If the condition is evaluated as 'True' the program jumps to the 'True' block. Otherwise the program jumps to the 'False' block.
Effects Does not read or write to memory.

HBCCmpBrTypeOfIsInst

HBCCmpBrTypeOfIsInst _
Description Compare the type of a value against known types and branch.
Example %0 = HBCCmpBrTypeOfIsInst %val, %types, %trueBlock, %falseBlock
Arguments %val is the argument to the typeof comparison, %types is a TypeOfIsTypes (see Typeof.h), %trueBlock and %falseBlock are the targets
Semantics Branch to true if the type of %val matches the bit in %types, false otherwise.
Effects Does not read or write memory.

GetParentScopeInst

GetParentScopeInst _
Description Retrieve the parent scope of the current function.
Example %0 = GetParentScopeInst %varScope, %parentScopeParam
Arguments %varScope is the VariableScope that describes the parent environment. %parentScopeParam is dummy parameter used to model usage of the parent environment.
Semantics The instruction returns the enclosing environment of the currently executing function.
Effects Does not read or write to memory.

CreateScopeInst

CreateScopeInst _
Description Creates a new scope which can be used to store variables.
Example %0 = CreateScopeInst %variablescope, %parentScope
Arguments %variablescope is a VariableScope describing the variables stored in the produced scope. %parentScope is the scope to use as the parent of the new scope, or EmptySentinel if the scope does not have a parent.
Semantics The instruction creates a new scope which can be used to store/retrieve variables, and allow inner functions to access variables in an enclosing scope.
Effects Does not read or write to memory.

ResolveScopeInst

ResolveScopeInst _
Description Traverse the scope chain to retrieve an enclosing scope.
Example %0 = ResolveScopeInst %variablescope, %startVarScope, %startScope
Arguments %variablescope is the VariableScope corresponding to the enclosing scope to retrieve. %startScope is the scope from which to start traversing. %startVarScope is the VariableScope associated with %startScope.
Semantics The instruction retrieves the requested scope, which must be reachable from the starting scope.
Effects Does not read or write to memory.

LIRResolveScopeInst

LIRResolveScopeInst _
Description Traverse the scope chain to retrieve an enclosing scope.
Example %0 = LIRResolveScopeInst %variablescope, %startScope, %numLevels
Arguments %variablescope is the VariableScope corresponding to the enclosing scope to retrieve. %startScope is the scope from which to start traversing. %numLevels is the number of levels to walk up the chain.
Semantics The instruction retrieves the requested scope, which must be %numLevels up from the starting scope.
Effects Does not read or write to memory.

GetClosureScopeInst

GetClosureScopeInst _
Description Retrieve the scope from the given closure.
Example %0 = GetClosureScopeInst %varScope, %function, %closure
Arguments %varScope is the VariableScope that describes the resulting scope. %function is the IR function that the closure operand is known to refer to. %closure is the closure from which to read the scope.
Semantics The instruction returns the scope stored in the given closure.
Effects Does not read or write to memory.

CreateFunctionInst

CreateFunctionInst _
Description Constructs a new function into the current scope from its code representation.
Example %0 = CreateFunction %scope, %varScope, %function
Arguments %function is the function that represents the code of the generated closure. %scope is the surrounding environment. %varScope is the VariableScope that describes %scope.
Semantics The instruction creates a new closure that may access the lexical scope of the current function
Effects Does not read or write to memory.

CreateClassInst

CreateClassInst _
Description Constructs a new class into the current scope from its constructor code representation.
Example %0 = CreateClassInst %scope, %varScope, %function, %superClass, %homeObjectOutput
Arguments %function is the function that represents the code of the constructor. %scope is the surrounding environment. %varScope is the VariableScope that describes %scope. %superClass is the class to inherit from; in base classes this is an empty sentinel value. %homeObjectOutput is an out parameter which will contain the home object (.prototype) of the class.
Semantics The instruction creates a new class that may access the lexical scope of the current function, and an inherit from a given super class. (ES2023 15.7.14) This results in the creation of 2 objects: the class function object itself, and the "home" object. The home object is where methods are put. The home object can be found on the .prototype of the class, and the class can be found on the .constructor of the home object.
Effects Writes to stack memory. May execute JS if it's a derived class.

BinaryOperatorInst

BinaryOperatorInst _
Description Performs the binary operation on the two operands.
Example %0 = BinaryOperatorInst %x, %y
Arguments %x and %y are the operands of the binary operation.
Semantics The instruction follows the rules of JavaScript for each one of the binary operators defined in the instruction.
Effects May read and write memory.

DirectEvalInst

DirectEvalInst _
Description Implement direct eval.
Example %0 = DirectEvalInst %%evalText, %strictCaller
Arguments %evalArg is the value which will be evaluated.
Semantics Implement the semantics of ES6 PerformEval(%evalText, evalRealm, strictCaller, direct=true) (ES6 18.2.1.1).
Effects Unknown

CreateThisInst

CreateThisInst _
Description Creates the object to be used as the this parameter of a construct call.
Example %0 = CreateThisInst %closure, %newtarget, %functionCode
Arguments %closure is the closure that will be invoked as a constructor, it may be any value, and the instruction will throw if it is not a valid callable. %newtarget is the new.target value to use for the call, it must either be the same value as %closure, or a valid callable. %functionCode is the IR function that is known to be associated with the closure operand, or EmptySentinel.
Semantics The instruction is responsible for preparing the this parameter of a construct call. If %closure is a legacy class constructor, the instruction does not do anything and immediately produces undefined, since those constructors handle creating this internally. Otherwise, this the instruction determines the new.target to use for retrieving the .prototype (which may involve iterating over bound functions), reads the .prototype from it, and creates an object with that value as its parent.
Effects May execute JS if %closure is not a legacy class constructor.

CallInst

CallInst _
Description Calls another function with some arguments.
Example %0 = CallInst %callee, %target, %calleeIsAlwaysClosure, %env, %newtarget %this, %arg0, %arg1, %arg2, ...
Arguments %callee is the closure to execute. %target is either EmptySentinel or the only possible Function this call can invoke. %calleeIsAlwaysClosure indicates whether the callee needs to be checked for being a function. %env is the environment for the function or EmptySentinel. %newtarget is the new.target value to use for the call. %this is a reference to the 'this' value. Arguments %arg0 ... %argN are the arguments passed to the function.
Semantics The instruction passes the control to the callee, that must be of closure type. The arguments are mapped to the parameters. Unmapped parameters are initialized to 'undefined'.
Effects May read and write memory.

CallBuiltinInst

CallBuiltinInst _
Description Calls a builtin function passing "undefined" for this
Example %0 = CallBuiltinInst %builtinNumber, %undefined, %undefined, %arg0, %arg1, %arg2, ...
Arguments %builtinNumber is the builtin to execute. Arguments %arg0 ... %argN are the arguments passed to the function.
Semantics The instruction passes the control to the builtin in a VM-specific way. The arguments are mapped to the parameters. Unmapped parameters are initialized to 'undefined'.
Effects May read and write memory.

CallIntrinsicInst

CallIntrinsicInst _
Description Calls an unsafe compiler intrinsic, passing "undefined" for this
Example %0 = CallIntrinsicInst %intrinsicsIndex, %undefined, %arg0, %arg1, %arg2, ...
Arguments %intrinsicsIndex is the intrinsic to execute. Arguments %arg0 ... %argN are the arguments passed to the function.
Semantics The instruction passes the control to the intrinsics in a VM-specific way. The arguments are mapped to the parameters.
Effects May read and write memory.

GetBuiltinClosureInst

GetBuiltinClosureInst _
Description Get a closure of a builtin function
Example %0 = GetBuiltinClosureInst %builtinNumber
Arguments %builtinNumber is the builtin to return the closure of.
Semantics
Effects Reads from memory.

LoadPropertyInst

LoadPropertyInst _
Description Loads the value of a field from a JavaScript object.
Example %0 = LoadPropertyInst %object, %property
Arguments %object is the object to load from. %property is the name of the field.
Semantics The instruction follows the rules of JavaScript property access in ES5.1 sec 11.2.1. The operation GetValue (ES5.1. sec 8.7.1) is then applied to the returned Reference.
Effects May read and write memory or throw.

LoadOwnPrivateFieldInst

LoadOwnPrivateFieldInst _
Description Loads the value of a private name from a JavaScript object.
Example %0 = LoadOwnPrivateFieldInst %object, %property : symbol
Arguments %object is the object to load from. %property is the private name symbol.
Semantics The instruction honors ES2024 7.3.30 PrivateGet. It looks up a private name on the object and throws if it does not exist. However, this does not have to deal with accessors or methods because those are stored outside of the instance.
Effects May execute JS.
TryLoadGlobalPropertyInst _
Description Loads the value of an existing field from the global object or throw if it doesn't exist.
Example %0 = TryLoadGlobalPropertyInst %object, %property
Arguments %object is the global object. %property is the name of the field, which must be a string literal.
Semantics Similar to LoadPropertyInst, but throw if the field doesn't exist.
Effects May read and write memory or throw.

LoadPropertyWithReceiverInst

LoadPropertyWithReceiverInst _
Description Loads the value of a field from a JavaScript object.
Example %0 = LoadPropertyWithReceiverInst %object, %property, %receiver
Arguments %object is the object to load from. %property is the name of the field. %receiver is receiver of the operation.
Semantics The instruction implements ES2024 6.2.5.5 GetValue. This is used for super references.
Effects May read and write memory or throw.

DeletePropertyInst

DeletePropertyInst _
Description Deletes the value of a field from a JavaScript object.
Example %0 = DeletePropertyInst %object, %property
Arguments %object is the object to modify. %property is the name of the field.
Semantics The instruction follows the rules of JavaScript property access.
Effects May read and write memory.

StorePropertyInst

StorePropertyInst _
Description Stores a value to field in a JavaScript object.
Example %4 = StorePropertyInst %value, %object, %property
Arguments %value is the value to be stored. %object is the object where the field %property will be created or modified.
Semantics The instruction follows the rules of JavaScript property access in ES5.1 sec 11.2.1. The operation PutValue (ES5.1. sec 8.7.2) is then applied to the returned Reference.
Effects May read and write memory or throw.

StorePropertyWithReceiverInst

StorePropertyWithReceiverInst _
Description Stores a value to field in a JavaScript object, with a specified receiver object.
Example %4 = StorePropertyWithReceiverInst %value, %object, %property, %receiver, %isStrict
Arguments %value is the value to be stored. %object is the object where the field %property will be created or modified. %receiver will be the receiver operand when invoking the [[Set]] internal method. If %isStrict is true, the operation will fail if there is an incompatible property descriptor found during the attempted store..
Semantics This follows the same semantics as StorePropertyInst, except the receiver is explicitly specified.
Effects May read and write memory or throw.

TryStoreGlobalPropertyInst

TryStoreGlobalPropertyInst _
Description Attempt to store a value into an existing field of the global object and throw if it doesn't exist.
Example %4 = TryStoreGlobalPropertyInst %value, %object, %property
Arguments %value is the value to be stored. %object is the global object, where the field %property will be stored. %property must be a string literal.
Semantics Similar to StorePropertyInst, but throw if the field doesn't exist.
Effects May read and write memory or throw.

DefineOwnPropertyInst

DefineOwnPropertyInst _
Description Define an own property of JavaScript object. Will throw if the property write was not successful (e.g. trying to store to a non-writable property.)
Example %4 = DefineOwnPropertyInst %value, %object : object, %property, %enumerable : boolean
Arguments %value is the value to be stored. %object must be of an object type; it's the object where the field with name %property will be created or modified. %enumerable determines whether a new property will be created as enumerable or not.
Semantics Implements ES15 7.3.8 DefinePropertyOrThrow. The instruction follows the rules of JavaScript own property access. The property is created or updated in the instance of the object, regardless of whether the same property already exists earlier in the prototype chain.
Effects May read and write memory.

DefineOwnInDenseArrayInst

DefineOwnInDenseArrayInst _
Description Define an own property in a dense JavaScript array at a specific index. This is an optimized version for dense array property assignment. Used only when populating newly created array literals.
Example %0 = DefineOwnInDenseArrayInst %value, %array : object, %arrayIndex : LiteralNumber
Arguments %value is the value to be stored. %array is the dense array object where the property will be defined. %arrayIndex is a literal number representing the array index where the property will be stored.
Semantics This instruction is used for efficient property definition. It requires that the array is dense and that the ArrayStorage underlying it has a size which is greater than the arrayIndex operand. The property is defined directly at the specified index without prototype chain traversal.
Effects Writes to heap.

StoreOwnPrivateFieldInst

StoreOwnPrivateFieldInst _
Description Store a private own property to an object.
Example %4 = StoreOwnPrivateFieldInst %value, %object, %property : symbol, %enumerable : boolean
Arguments %value is the value to be stored. %object should be an object, if not this throws. %property must be the value of a symbol. %enumerable must always be false.
Semantics This instruction implements ES2024 7.3.31 PrivateSet. This instruction will throw if the private property does not already exist on %object.
Effects May read and write memory.

AddOwnPrivateFieldInst

AddOwnPrivateFieldInst _
Description Add a new private own field.
Example %4 = AddOwnPrivateFieldInst %value, %object, %property : symbol, %enumerable : boolean
Arguments %value is the value to be stored. %object must be an object. %property must be the value of a symbol. %enumerable should always be false.
Semantics This instruction is used to initialize private fields on objects. As such it implements ES2024 7.3.27 PrivateFieldAdd- except this instruction will assume that the property being added does not already exist.
Effects May read and write memory.

PrivateBrandCheckInst

PrivateBrandCheckInst _
Description Check that an object has a private brand installed.
Example PrivateBrandCheckInst %object, %brand : privateName
Arguments %object is the object to check. %brand must be a private name representing the brand to look for.
Semantics This instruction implements the brand check for private methods and accessors. It throws a TypeError if the brand is not present on the object.
Effects May read memory and throw.

DefineOwnGetterSetterInst

DefineOwnGetterSetterInst _
Description Associates a pair of getter and setter with an own field in a JavaScript object, replacing the previous value.
Example %4 = DefineOwnGetterSetterInst %getter, %setter, %object, %property, %enumerable
Arguments %getter is a getter accessor, or undefined. %setter is a setter accessor, or undefined. %object is the object where the field %property will be created or modified. %enumerable determines whether a new property will be created as enumerable or not.
Semantics The instruction follows the rules of JavaScript property access. The property is created or updated in the instance of the object, regardless of whether the same property already exists earlier in the prototype chain. It replaces both accessors even if one or both of the parameters are undefined.
Effects May read and write memory.

AllocObjectInst

AllocObjectInst _
Description Allocates a new JavaScript object on the heap.
Example %0 = AllocObjectInst %sizeHint : LiteralNumber, %parent : EmptySentinel or null or Value
Arguments *%sizeHint% indicates that the object will need at least that many property slots. %parent is the optional parent to create the object with: EmptySentinel means use Object.prototype, null means no parent, or otherwise use the specified value.
Semantics The instruction creates a new JavaScript object on the heap. If the parent is invalid (not EmptySenyinel, null or object), it is silently ignored.
Effects Does not read or write to memory.

AllocObjectLiteralInst

AllocObjectLiteralInst _
Description Allocates a new JavaScript object on the heap. During lowering pass it will be lowered to either an AllocObjectInst or a LIRAllocObjectFromBufferInst.
Example %0 = AllocObjectLiteralInst %parent, "prop1" : string, 10 : number
Arguments %parent is the parent of the new object. %prop_map is a vector of (Literal*, value*) pairs which represents the properties and their keys in the object literal.
Semantics The instruction creates a new JavaScript object on the heap with an initial list of properties.
Effects Does not read or write to memory.

AllocTypedObjectInst

AllocTypedObjectInst _
Description Allocates a new typed object on the heap. During lowering pass it will be lowered to either an AllocObjectInst or a LIRAllocObjectFromBufferInst.
Example %0 = AllocTypedObjectInst %parent, "prop1" : string, 10 : number
Arguments %parent is the parent of the new object, and the other operands are alternating (Literal*, value*) pairs which represent the properties and their keys in the typed class.
Semantics The instruction creates a new JavaScript object on the heap with an initial list of properties, which may include 'uninit' values. Properties are enumerable.
Effects Does not read or write to memory.

AllocTypedNonEnumObjectInst

AllocTypedNonEnumObjectInst _
Description Allocates a new typed object on the heap with non-enumerable properties. Used for home objects (class prototypes with methods). During lowering pass it will be lowered to a LIRAllocTypedNonEnumObjectFromBufferInst.
Example %0 = AllocTypedNonEnumObjectInst %parent, "prop1" : string, 10 : number
Arguments %parent is the parent of the new object, and the other operands are alternating (Literal*, value*) pairs which represent the properties and their keys in the typed class.
Semantics The instruction creates a new JavaScript object on the heap with an initial list of non-enumerable properties, which may include 'uninit' values.
Effects Does not read or write to memory.

AllocArrayInst

AllocArrayInst _
Description Allocates a new JavaScript array on the heap.
Example %0 = AllocArrayInst %sizeHint, %value0, %value1, ...
Arguments sizeHint tells the size of the array that the VM should allocate. It must be equal or larger than the initial list of elements in this instruction. The rest of the values are all literal values as the initial elements of the array. Non-literal values or values after elision will be inserted into the array separately.
Semantics The instruction creates a new JavaScript array on the heap with a hinted size and initial list of elements.
Effects Does not read or write to memory.

AllocFastArrayInst

AllocFastArrayInst _
Description Allocates a new FastArray on the heap.
Example %0 = AllocFastArrayInst %capacity
Arguments %capacity is a hint to the VM for how large the initial capacity should be.
Semantics The instruction creates a new empty FastArray on the heap.
Effects Does not read or write to memory.

GetTemplateObjectInst

GetTemplateObjectInst _
Description Gets the object to pass to the tagged template function.
Example %0 = GetTemplateObjectInst %templateObjID, %dup, %string1, ...
Arguments %templateObjID is the cache key for the template. %dup indicates whether the raw strings are duplicates of the cooked strings. If %dup is true, then all %string operands are raw. Otherwise, some number of raw %string operands are followed by the same number of cooked %string operands.
Semantics If cached, retrieves from the template cache. Otherwise, updates the cache with a new template object with the cooked strings, which also contains an object containing the raw strings. Returns the resultant object.
Effects Does not read or write to program memory. Updates template cache.

TypeOfInst

TypeOfInst _
Description The JS typeof operator
Example %0 = TypeOfInst %val
Arguments %val is the value whose type we want to obtain.
Semantics Obtains a string representing the type of the operand.
Effects Does not read or write to memory.

TypeOfIsInst

TypeOfIsInst _
Description Compare the type of a value against known types
Example %0 = TypeOfIsInst %val, %types
Arguments %val is the value whose type we want to compare, %types is the TypeOfIsTypes (see Typeof.h) to compare against.
Semantics Returns true if the type of %val matches the bit in the TypeOfIsTypes, false otherwise.
Effects Does not read or write to memory.

CreateArgumentsInst

CreateArgumentsInst _
Description Allocates the JavaScript arguments array-like object on the heap.
Example %0 = CreateArgumentsInst
Arguments None.
Semantics The instruction creates the arguments object, populates it with copies of the values of the arguments (according to "strict mode" semantics) and sets arguments.length to the number of arguments (this isn't copied or counted). There should be only one CreateArgumentsInst in a function.
Effects Does not read or write to memory.

CreateRegExpInst

CreateRegExpInst _
Description Construct a RegExp object from a regexp literal.
Example %0 = CreateRegExpInst "pattern", "flags"
Arguments pattern: LiteralString and flags: LiteralString
Semantics It is equivalent to calling RegExp(pattern, flags), except that it calls the built-in constructor, even if RegExp has been overridden.
Effects Does not read or write to memory.

SwitchInst

SwitchInst _
Description The ‘switch‘ instruction is used to transfer control to one of different places.
Example %0 = SwitchInst %input, %default, [%val0, %block0], [%val1, %block1] ..
Arguments The instruction accepts an input, a default block, and one or more pairs of value-destination values. The value must be a primitive JS type, and the destination must be a basic block within the current function.
Semantics The semantic of the instruction is identical to a sequence of 'if' statements that compare the value of the input to each of the case statements. Repeating the same value is not allowed.
Effects May read and write memory.

GetPNamesInst

GetPNamesInt _
Description Generates the property enumerator, which is a collection of registers that hold the state of the enumerator (iterator, object base, index, size, etc).
Example %0 = GetPNamesInt %propertyAddr, %baseAddr, %indexAddr, %sizeAddr, %iteratorAddr, %onEmpty, %onLast
Arguments The first 5 parameters are addresses (stack allocated addresses) that represent the state of the property enumerator. The last two argument are jump destination for the two cases: empty object and object with some properties.
Semantics This instruction is a terminator instruction and prepares the enumerator for the GetNextPNameInst instruction to consume.
Effects May read and write memory.

GetNextPNameInst

GetNextPNameInst _
Description Loads the next property from the object property enumerator.
Example %0 = GetNextPNameInst %propertyAddr, %baseAddr, %indexAddr, %sizeAddr, %iteratorAddr, %onLast, %onSome
Arguments The first argument is the destination where the name of the property is written into. The next 4 arguments are the state of the property enumerator. The last two arguments are the destination blocks for: no next property, or some property available.
Semantics This instruction is a terminator instruction that uses the state that was prepared by the GetPNamesInst instruction.
Effects May read and write memory.

CatchInst

CatchInst _
Description This instruction catches an exception, and returns that exception.
Example %0 = CatchInst
Arguments This instruction does not have arguments.
Semantics This instruction will be generated for each catch block and for each finally block. The current exception will be returned. CatchInst can only show up at the beginning of a basic block. The coverage and depth information for the CatchInst will be constructed dynamically later during bytecode generation.
Effects May read and write memory.

ThrowInst

ThrowInst _
Description This instruction will throw an exception.
Example %0 = ThrowInst %e, %catchTarget
Arguments This instruction takes one required operand, which is the register that contains the exception value. The second operand is optional and indicates the closest surrounding catch block; it must be present if there is one, and must be missing otherwise.
Semantics This instruction is a terminator instruction that will transition the control to the CatchInst that covers this instruction with closest scope.
Effects May read and write memory.

ThrowTypeErrorInst

ThrowTypeErrorInst _
Description This instruction will create and throw a TypeError.
Example %0 = ThrowTypeErrorInst %message
Arguments This instruction takes one argument, %message, which will be converted to a string and used as the message for the TypeError.
Semantics This instruction is a terminator instruction that will transition the control to the CatchInst that covers this instruction with closest scope.
Effects Will throw.

TryStartInst

TryStartInst _
Description Mark the beginning of the try blocks.
Example %0 = TryStartInst %catchTargetBlock, %tryBodyBlock
Arguments This instruction takes 2 arguments: %tryBodyBlock is the block where the body of Try starts, %catchTargetBlock is the basic block that contains the CatchInst which covers this try. Both %tryBodyBlock and %catchTargetBlock are successors of this instruction.
Semantics This is a nop, used only for tracking the beginning of try blocks.
Effects Does not read or write memory.

TryEndInst

TryEndInst _
Description A terminator that marks the end of the try blocks.
Example %0 = TryEndInst %CatchBlock, %BranchDest
Arguments The active catch block and the destination block to branch to.
Semantics This is a nop, used only for tracking the end of try blocks.
Effects Technically this instruction itself does not touch memory, however we mark it as may write to prevent optimizations going pass this instruction.

BranchIfBuiltinInst

BranchIfBuiltinInst _
Description Check if a value is the same as a known builtin value.
Example %0 = BranchIfBuiltin %builtinNumber, %arg, %trueDest, %falseDest
Arguments The builtin number, the value to check, and the blocks to branch to.
Semantics Branch to %trueDest if %arg is the same pointer as the builtin with %builtinNumber, else branch to %falseDest.
Effects None

PhiInst

PhiInst _
Description This is a Phi node instruction.
Example %0 = PhiInst %value0, %block0, [%value1, %block1]
Arguments A list of pairs of value and incoming basic block.
Semantics The PhiNode needs to have a single entry for each incoming basic block of the block the PHI is located in. The incoming value must dominate the last instruction in the incoming block.
Effects Does not read or write memory.

MovInst

MovInst _
Description The MOV inst represents a low-level operation of moving one register to another.
Example %0 = MovInst %value0
Arguments Any value.
Semantics The Mov instruction is only valid after Register Allocation in bytecode as we move away from SSA form.
Effects Does not read or write memory.

ImplicitMovInst

ImplicitMovInst _
Description The ImplicitMov inst represents moving one register to another, except the mov will be performed implicitly by an immediately-subsequent instruction. This is used to express to the optimizer instructions which modify registers other than their destination.
Example %0 = ImplicitMovInst %value0
Arguments Any value.
Semantics The ImplicitMov instruction is only valid after Register Allocation in bytecode as we move away from SSA form.
Effects Does not read or write memory.

DebuggerInst

DebuggerInst _
Description This instruction corresponds to the JavaScript debugger statement.
Example %0 = DebuggerInst
Arguments It takes no arguments and returns no values.
Semantics Its behavior is implementation-dependent.
Effects Does not read or write to memory.

GetNewTargetInst

GetNewTargetInst _
Description Obtains the value of new.target in the current function or constructor.
Example %0 = GetNewTargetInst, %param
Arguments %param is a dummy JSDynamicParam used to quickly find usages of new.target.
Semantics It must only be called from a ES6 class constructor or ES5 function. If the callee was invoked from new, it returns the function object of the direct constructor, otherwise undefined.
Effects Does not read or write memory

ThrowIfInst

ThrowIfInst _
Description Check whether the value belongs to one of the "rejected" types, and if it is, throw ReferenceError, otherwise return it.
Example %_ = ThrowIfInst %value, %rejectedTypesUnion
Arguments %value is the value to check. %rejectedTypesUnion is a union of types that should be rejected.
Semantics It is used to implement ES6 TDZ functionality. Variables declared with let are poisoned with empty until they are initialized.
Effects Potentially throws an exception. Has no other side effects.

ThrowIfThisInitializedInst

ThrowIfThisInitializedInst _
Description Check whether the value of this in a derived class constructor is already initialized. If so, throw.
Example %_ = ThrowIfThisInitializedInst %thisVal
Arguments %thisVal is the value to check.
Semantics This is used to guard against double-initialization of this, which happens by invoking super() multiple times.
Effects Potentially throws an exception. Has no other side effects.

CoerceThisNS

CoerceThisNS _
Description Coerces its argument using the rules of "this" coercion to object in non-strict mode.
Example %0 = CoerceThisNS %value0
Arguments Any value.
Semantics
Effects Does not read or write memory (it potentially creates a new object)

CreateGenerator

CreateGenerator _
Description Constructs a new GeneratorInnerFunction from its code representation, and wraps it in a Generator object.
Example %0 = CreateFunction %scope, %varScope, %function
Arguments %function is the function that represents the code of the generator's inner function. %scope is the surrounding environment. %varScope is the VariableScope that describes %scope.
Semantics Creates a new GeneratorInnerFunction closure that may access the environment and wraps it in a generator
Effects Does not read or write to memory (creates a new object).

SaveAndYield

SaveAndYield _
Description Saves information needed to resume generator execution and yield.
Example %0 = SaveAndYield %value, %isDelegated, %next
Arguments %value is the value to yield, %isDelegated determines if the value to be yielded should be wrapped (true when inside a yield*), %next is the next BasicBlock to execute upon resuming, which must begin with a ResumeGeneratorInst (generated alongside SaveAndYield).
Semantics Saves the frame variables and the next IP to the closure, and yield execution.
Effects Reads and writes to memory, may throw or execute.

ResumeGenerator

ResumeGenerator _
Description Perform the user-requested action on resuming a generator.
Example %0 = ResumeGenerator %isReturn
Arguments %isReturn is an output argument set to true if the user requested a return, false otherwise.
Semantics If the user requested next(), continue on. If the user requested throw(), throw. If the user requested return(), set %isReturn to true and continue. Subsequent instructions will check %isReturn and execute any finally handlers, for example, before returning.
Effects May read and write memory. (may throw)

IteratorBegin

IteratorBegin _
Description Begins array destructuring on a given iterable source.
Example %0 = IteratorBegin %sourceOrNext
Arguments %sourceOrNext[in/out] is the stack location for source to destructure from. Is set to source if performing array iteration, else set to the .next() method of the iterator.
Semantics If %sourceOrNext is an Array then it remains unmodified and the instruction returns 0, but if it is not, it is replaced with the 'next' method so that it can be called on each step of the iteration and the instruction returns the iterator object. If the [Symbol.iterator] function throws, this instruction will throw.
Effects May read and write memory, may throw or execute.

IteratorNext

IteratorNext _
Description Destructures the next value from a given iterator.
Example %0 = IteratorNext %iterator %sourceOrNext
Arguments %iterator is the index or the iterator. %sourceOrNext is the input stack location (source to destructure from) or the next method.
Semantics If %iterator is an index: if %iterator is less than %sourceOrNext.length, reads the value from %sourceOrNext and increments the index, else sets %iterator to undefined and returns undefined. If %iterator is an actual iterator, calls %sourceOrNext as a next method and evaluates to the result value. When iteration is complete, sets %iterator to undefined as a signal that we're done.
text
if (typeof %iterator === 'undefined') {
  return undefined;
}
if (typeof %iterator === 'number') {
  if (%iterator >= %sourceOrNext.length) {
    %iterator = undefined;
    return undefined;
  }
  return %sourceOrNext[%iterator];
}
var iterResult = %sourceOrNext();
if (iterResult.done) {
  %iterator = undefined;
  return undefined;
}
return iterResult.value;

Effects | May read and write memory, may throw or execute.

IteratorClose

IteratorClose _
Description Closes an iterator if it exists.
Example %0 = IteratorClose %iterator %ignoreInnerException
Arguments %iterator is the index or the iterator. %ignoreInnerException is a boolean literal.
Semantics If %iterator is an iterator, calls .return() on it to close it. Otherwise, this is a no-op. If .return() throws, the exception is ignored when %ignoreInnerException is true.
Effects May read and write memory, may throw or execute.

UnreachableInst

UnreachableInst _
Description Crashes the VM (ifndef NDEBUG).
Example %0 = UnreachableInst
Arguments None.
Semantics Can be added to stubs and similar to verify that they are never executed.
Effects Marked as reading/writing memory to avoid reordering.

Target Instructions

Some high-level IR instructions are lowered into a sequence of low-level machine
instructions. In order to perform register allocation on these instructions we
need to perform lowering, which is a form of instruction selection. The semantic
of these instructions are identical to the semantic of the relevant target
instructions.

LIRGetGlobalObjectInst

LIRGetGlobalObjectInst _
Description Obtain the "global" object
Example %0 = LIRGetGlobalObjectInst
Arguments None.
Semantics The instruction returns a reference to the "global" object.
Effects Does not read or write to memory.

HBCCreateFunctionEnvironmentInst

HBCCreateFunctionEnvironmentInst _
Description Create a new environment with the function's parent environment as its parent.
Example %0 = HBCCreateFunctionEnvironmentInst %varScope, %parentScopeParam
Arguments %varScope is the variable scope that this instruction will produce. %parentScopeParam is dummy parameter used to model usage of the parent environment.
Semantics The instruction creates a new environment for a function.
Effects Does not read or write to memory.

HBCResolveParentEnvironmentInst

HBCResolveParentEnvironmentInst _
Description Traverse the chain of environments starting at the current function's parent to find a given environment.
Example %0 = HBCResolveParentEnvironmentInst %varScope, %numLevels, %parentScopeParam
Arguments %varScope is the variable scope to resolve to. %numLevels is the number of scopes up from the current function's parent that the result will be found. %parentScopeParam is dummy parameter used to model usage of the parent environment.
Semantics The instruction resolves an environment that is a parent of the current function's environment.
Effects Does not read or write to memory.

LIRAllocObjectFromBufferInst

LIRAllocObjectFromBufferInst _
Description Allocates a new JavaScript object on the heap, and initializes it with values from the object buffer.
Example %0 = LIRAllocObjectFromBufferInst %parent, %value0, %value1, ...
Arguments %parent is the parent of the new object. The subsequent values are all literal values, with alternating keys and values. Non-serializable values will be inserted into the array separately.
Semantics The instruction creates a new JavaScript object on the heap with an initial list of properties.
Effects Does not read or write to memory.

LIRAllocTypedObjectFromBufferInst

LIRAllocTypedObjectFromBufferInst _
Description Allocates a new typed class instance on the heap, and initializes it with values from the buffer. Properties are enumerable.
Example %0 = LIRAllocTypedObjectFromBufferInst %parent, %key0, %value0, ...
Arguments The %parent is the parent of the result object. The remaining operands are all literal values, with alternating keys and values. Non-literal values will be inserted into the array separately.
Semantics The instruction creates a new JavaScript typed object on the heap with an initial list of enumerable properties.
Effects Does not read or write to memory.

LIRAllocTypedNonEnumObjectFromBufferInst

LIRAllocTypedNonEnumObjectFromBufferInst _
Description Allocates a new typed class instance on the heap with non-enumerable properties, and initializes it with values from the buffer. Used for home objects (class prototypes).
Example %0 = LIRAllocTypedNonEnumObjectFromBufferInst %parent, %key0, %value0, ...
Arguments The %parent is the parent of the result object. The remaining operands are all literal values, with alternating keys and values. Non-literal values will be inserted into the array separately.
Semantics The instruction creates a new JavaScript typed object on the heap with an initial list of non-enumerable properties.
Effects Does not read or write to memory.

HBCCallNInst

HBCCallNInst _
Description Calls a function with a fixed number of arguments (from 1 to 4, inclusive).
Example %0 = HBCCallNInst %callee, %this, %arg0, %arg1, %arg2
Arguments %callee is the function to execute. %this is a reference to the 'this' value. Arguments %arg0 ... %argN are the arguments passed to the function.
Semantics The instruction copies its arguments (starting from this) into the parameter-passing registers at the end of the frame, and passes the control to the callee, which must be of closure type. The arguments are mapped to the parameters. Unmapped parameters are initialized to 'undefined'.
Effects May read and write memory.

HBCCallWithArgCountInst

HBCCallWithArgCountInst _
Description This instruction contains the same operands as CallInst, in addition to explicitly passing in the argument count as an operand.
Example %0 = HBCCallWithArgCountInst %callee, %target, %calleeIsAlwaysClosure, %env, %newtarget, $argcount, %this, %arg0, %arg1, %arg2, ...
Arguments %argcount is the number of arguments to the function, including 'this'.
Semantics The instruction passes the control to the callee, that must be of closure type. The arguments are mapped to the parameters. Unmapped parameters are initialized to 'undefined'.
Effects May read and write memory.

prload

prload _
Description Load a typed object property by index
Example %0 = prload (:type) %object, %propIndex, %propName
Arguments %object is the object to load from. %propIndex is the property index. %propName is the property name (which isn't actually used). type is the type of the property.
Semantics Load the property without any checking.
Effects May read memory.

prstore

prstore _
Description Store a typed object property by index
Example prstore %value, %object, %propIndex, %propName, %nonPointer
Arguments %value is the value to store. %object is the object to store into. %propIndex is the property index. %propName is the property name (which isn't actually used). %nonPointer is a boolean indicating whether the property is a non-pointer type (meaning that both the old and the new value are not pointers).
Semantics Store the property without any checking.
Effects May read memory.

FastArrayLoad

FastArrayLoad _
Description Loads an element from a FastArray, validates the index.
Example FastArrayLoad %array (:object), %index (:number)
Arguments %array is the array, %index is the index to read
Semantics Throw if the index is not an unsigned integer within range, otherwise, load the value at %index from %array.
Effects May read memory or throw.

FastArrayStore

FastArrayStore
--- | --- |
Description | Stores an element to an array, validates the index.
Example | FastArrayStore %storedValue, %array, %index
Arguments | %array is the array, %index is the index to write to, %storedValue is the value to write
Semantics | Throw if the index is not an unsigned integer within range, otherwise, store %storedValue to %array.
Effects | May write memory or throw.

FastArrayLength

FastArrayLength _
Description Read the length of a FastArray
Example FastArrayLength (:number) %array
Arguments %array is the array
Semantics Read the length property of the array and return it.
Effects May read memory.

FastArrayPushInst

FastArrayPushInst _
Description Pushes an element onto a FastArray.
Example FastArrayPushInst %pushedValue, %array
Arguments %pushedValue is the value to be pushed, %array is the array we are pushing onto.
Semantics Push an element onto the target array, increasing its length by 1. If accommodating the additional element causes a reallocation past the maximum allowable allocation size, throw an exception.
Effects May write memory or throw.

FastArrayAppendInst

FastArrayAppendInst _
Description Appends the elements of one FastArray to another.
Example FastArrayAppendInst %other, %array
Arguments %other is array from which elements will be copied, %array is the array onto which elements will be appended.
Semantics Copy the elements from %other into the end of %array, increasing its length by the number of elements in %other. If accommodating the additional elements causes a reallocation past the maximum allowable allocation size, throw an exception.
Effects May write memory or throw.

LoadParentNoTraps

LoadParentNoTraps _
Description Loads the parent for a legacy, ordinary untyped object. The given object cannot be proxy. Returns null or an object.
Example %0 = LoadParentNoTraps %object
Arguments %object must be of type object.
Semantics Read the parent.
Effects May read memory.

TypedLoadParent

TypedLoadParent _
Description Loads the parent (the vtable) for a typed object instance of a class
Example %0 = TypedLoadParent %object
Arguments %object is an instance of a typed class
Semantics Read the parent without any checks.
Effects May read memory.

FUnaryMath

FUnaryMath _
Description Instruction class for a floating point unary math operation.
Example FNegate (:number), %operand (:number)
Arguments %operand is the input value
Semantics Perform the specified op on a floating point number, return a number.
Effects None

FBinaryMath

FBinaryMath _
Description Instruction class for a floating point binary math operation.
Example FAdd (:number), %left (:number), %right (:number)
Arguments %left and %right are the input values
Semantics Perform the specified op on two floating point numbers (same type), return a number.
Effects None

FCompare

FCompare _
Description Instruction class for comparing floating point numbers
Example FLessThan (:bool), %left (:number), %right (:number)
Arguments %left and %right are the input values
Semantics Perform the specified compare on two floating point numbers (same type), return bool. Note that if any operand is NaN, comparison always returns false.
Effects None

StringConcat

StringConcat _
Description Concatenate N strings and return the resultant string.
Example StringConcat (:string), %op1 (:string), ..., %opN (:string)
Arguments %op1 to %opN are the input values which must be strings.
Semantics Perform the specified concatenation on strings.
Effects None

HBCStringConcat

HBCStringConcat _
Description Concatenate 2 strings and return the resultant string.
Example StringConcat (:string), %left (:string), %right (:string)
Arguments %left and %right are the input values which must be strings.
Semantics Perform the specified concatenation on strings.
Effects None

UnionNarrowTrusted

UnionNarrowTrusted _
Description Narrow a union type when the compiler has proven that the operand is more specific.
Example UnionNarrowTrusted (:number), %operand (:number | empty)
Arguments %operand is the input value
Semantics Narrow the type, but doesn't change the value.
Effects None

CheckedTypeCast

CheckedTypeCast _
Description Attempt to cast to the result type, throw if unable.
Example CheckedTypeCast (:U), %operand (:T), %type
Arguments %operand is the value to cast, %type is the type to cast to
Semantics Cast from type T to U, throw when the cast is not valid. The result type may be narrower than the
specified cast type, if the compiler can prove that the input value is more specific.
Effects May throw.

NativeCall

NativeCall _
Description Call a native function.
Example NativeCall (:type) %nativeFunctionPtr, %nativeSignature, %arg1, %arg2, ...
Arguments %nativeFunctionPtr is the pointer to the native function. %nativeSignature is the signature of the native function. Arguments %arg1 ... %argN are the arguments passed to the function encoded as JS values.
Semantics The JS arguments are converted to native types. The result is converted to the JS type.
Effects Unknown.

GetNativeRuntime

GetNativeRuntime _
Description Get a native pointer to the native runtime to be passed to a native function.
Example GetNativeRuntime (:number)
Arguments None
Semantics Get the native runtime pointer.
Effects None

LIRDeadValue

LIRDeadValue _
Description Create a "dead value" of the specified type. This instruction is created during lowering in code that will never execute, but is needed to satisfy the type constraints of downstream instruction that will also never execute.
Example LIRDeadValue (:number)
Arguments None
Semantics Create a value of the specified type.
Effects None

LazyCompilationData

NOTE: LazyCompilationData relies on the fact that we don't delete Variables during the lazy compilation pipeline. That means no stack promotion, and the existing full optimization pipeline cannot run, because we haven't yet figured out which variables are captured by child functions.

LazyCompilationData _
Description Data needed for lazy compilation, including the VariableScope chain used to access captured variables.
Example LazyCompilationData %capturedThis, %capturedNewTarget, %capturedArguments, %capturedHomeObject, %parentVS, %parentParentVS, ...
Arguments The captured values are used in case the lazy function may have arrow functions as children which need to capture the values as variables. In the case of %capturedHomeObject, this is needed even for non-arrow functions. %parentVS the immediately enclosing VariableScope. The remaining operands are successively the ancestors of %parentVS, which are kept as operands to ensure the VariableScopes aren't deleted across lazy compilation calls.
Semantics Information needed for lazy compilation. Deleted after use.
Effects None

EvalCompilationData

NOTE: EvalCompilationData relies on the fact that we don't delete Variables during the eval compilation pipeline. That means no stack promotion, and the existing full optimization pipeline cannot run, because we haven't yet figured out which variables are captured by child functions.

EvalCompilationData _
Description Data needed for eval compilation within this function, including the VariableScope chain used to access captured variables.
Example EvalCompilationData %capturedThis, %capturedNewTarget, %capturedArguments, %VS, %parentVS, ...
Arguments The captured values are used in case the eval function may have arrow functions as children which need to capture the values as variables. %VS the VariableScope for the function (block scoping not supported). The remaining operands are successively the ancestors of %VS, which are kept as operands to ensure the VariableScopes aren't deleted across compilation calls.
Semantics Information needed for eval compilation.
Effects None

Doc IntlAPIs (`doc/IntlAPIs.md`)


id: intl title: Internationalization APIs

This document describes the current state of the Android and iOS implementation of the ECMAScript Internationalization API Specification (ECMA-402, or Intl). ECMA-402 is still evolving and the latest iteration that was taken into account is 7th edition which was published in June 2020. Each new edition is built on top of the last one and adds new capabilities typically as,

  • New Intl service constructors (e.g. Intl.Collator, Intl.NumberFormat etc.) or extending existing ones by accepting more parameters
  • New functions or properties in Intl objects (e.g. Intl.Collator.prototype.compare)
  • New locale aware functions in standard Javascript object prototypes (e.g. String.prototype.localeCompare)

One popular implementation strategy followed by other engines, is to bundle an internationalization framework (typically ICU) along with the application package. This guarantees deterministic behaviours at the cost of applications package bloat. We decided to consume the Android and iOS platform provided facilities for space efficiency, but at the cost of some variance in behaviours across Android and iOS platforms. This also includes behavioural variations between different versions of Android.

ECMA-402 Compliance

Supported on both platforms

  • Intl.Collator

    • Intl.Collator.supportedLocalesOf
    • Intl.Collator.prototype.compare
    • Intl.Collator.prototype.resolvedOptions
  • Intl.NumberFormat*

    • Intl.NumberFormat.supportedLocalesOf
    • Intl.NumberFormat.prototype.format
    • Intl.NumberFormat.prototype.resolvedOptions
  • Intl.DateTimeFormat*

    • Intl.DateTimeFormat.supportedLocalesOf
    • Intl.DateTimeFormat.prototype.format
    • Intl.DateTimeFormat.prototype.resolvedOptions
  • Intl.getCanonicalLocales

  • String.prototype

    • localeCompare
    • toLocaleLowerCase
    • toLocaleUpperCase
  • Array.prototype

    • toLocaleString
  • Number.prototype

    • toLocaleString
  • Date.prototype

    • toLocaleString
    • toLocaleDateString
    • toLocaleTimeString

Supported on Android only

  • Intl.NumberFormat
    • Intl.NumberFormat.prototype.formatToParts

* Limitations on property support

Limited iOS property support

  • Intl.NumberFormat implementation does not support the following properties,

    • notation: 'compact'
    • notation: 'engineering'
    • compactDisplay
    • signDisplay
  • Intl.DateTimeFormat implementation does not support the following properties,

Limited Android property support

  • Intl.DateTimeFormat implementation does not support the following properties,
    • dayPeriod
    • fractionalSecondDigits
    • formatMatcher The property enables the implementation to pick the best display format when it supports only a subset of all possible formats. ICU library in Android platform and hence our implementation allows all subsets and formats which makes this formatMatcher property unnecessary.

Limitations across Android SDKs

Android 11

  • The keys of the object returned by resolvedOptions function in all Intl services are not deterministically ordered as prescribed by spec.
  • DateFormat: ECMAScript beginning of time (-8,640,000,000,000,000), is formatted as November 271817, instead of expected April 271822.
  • Intl.NumberFormat implementation has some rough edges in supporting the following properties,
    • style: 'unit'
    • notation: 'compact'
    • signDisplay
    • currencyFormat

Android 10 and older (SDK < 30)

  • Intl.NumberFormat: Scientific notation formatting has issues on some cases. e.g. -Infinity may get formatted as '-∞E0' instead of expected '-∞'. Another manifestation of the issues is that the formatToParts may return 4 parts instead of 2.
  • Intl.NumberFormat: Compact notation formatToParts doesn't identify unit, hence we report unit as 'literal'. For e.g. the second part of "100ac" gets reported as "literal" instead of "compact"

Android 9 and older (SDK < 29)

  • There are some failures likely due to older Unicode and CLDR version, which are hard to generalize. Some examples are,
    • Intl.NumberFormat: 'Percent' is not accepted as a unit.
    • Intl.NumberFormat: unit symbols difference, kph vs km/h
    • Some issue in significant digit precision, which is not yet looked into the details.

Android 8.0 – 8.1 and older (SDK < 28)

  • Intl.getCanonicalLocales: Unicode/CLDR version differences results in some variances. e.g. und-u-tz-utc vs. und-u-tz-gmt.
  • Intl.NumberFormat: CompactFormatter doesn't respect the precision inputs.

Android 7.0 - 7.1 and older (SDK < 26)

  • Intl.getCanonicalLocales: Unicode/CLDR version differences results in some variances. e.g. und-u-ms-imperial vs. und-u-ms-uksystem.

Android 7.0 - 7.1 and older (SDK < 24)

  • Intl.Collator: Doesn't canonically decompose the input strings. Canonically equivalent string with non-identical code points may not match.
  • Intl.getCanonicalLocales: Unicode/CLDR version differences results in some variances. e.g. und-u-ca-ethiopic-amete-alem vs. und-u-ca-ethioaa, und-u-ks-primary vs. und-u-ks-level1.
  • Intl.NumberFormat: Unit style does not work.
  • Intl.NumberFormat: There are issues in the precision configuration due to lack of APIs.
  • Intl.DateFormat: There are issues with the calendar configuration which needs to be dug into.

SDK < 21 and older

On platforms before 21, Locale.forLanguageTag() is not available, hence we can't construct java.util.Locale object from locale tag. Hence, we fallback to English for any locale input.

Internationalization framework in Android Platform

Our implementation is essentially projecting the Android platform provided internationalization facilities through the ECMA-402 specified services. It implies that the results of running the same code may vary between devices running different versions of Android.

Android platform internationalization libraries have been based on ICU4j project. Version of ICU4j and the backing CLDR data varies across Android platform versions. Also, the ICU APIs were never exposed directly, but only through wrappers and aliases. This results in significant variance in internationalization API surface and data across platform versions.

The following table summarizes ICU, CLDR and Unicode versions available on the Android platforms.

Platform 24+ where ICU4j APIs are available.

Android Platform Version ICU Unicode CLDR
Android 11 (API level 30) ICU4J 66.1 (ref) Unicode 13 beta CLDR 36.1
Android 10 (API level 29) ICU4j 63.2 (ref) CLDR 34 Unicode 11.0
Android 9 (API level 28) ICU4j 60.2 (ref) CLDR 32.0.1 Unicode 10.0
Android 8.0 - 8.1 (API levels 26 - 27) ICU4j 58.2( ref) CLDR 30.0.3 Unicode 9.0
Android 7.0 - 7.1 (API levels 24 - 25) ICU4j 56 (ref) CLDR 28 Unicode 8.0

Pre-24 platforms

Android Platform Version ICU Unicode CLDR
Android 6.0 (API level 23) ICU4j 55.1 (ref) CLDR 27.0.1 Unicode 7.0
Android 5.0 (API levels 21–22) ICU4j 53 (ref) CLDR 25 Unicode 6.3
Android 4.4 (API levels 19–20) ICU4j 51 (ref) CLDR 23 Unicode 6.2
Android 4.3 (API level 18) ICU4j 50 (ref) CLDR 22.1 Unicode 6.2
Android 4.1 (API levels 16–17) ICU4j 4.8 (ref) CLDR 2.0 Unicode 6.0

In summary,

  1. Platforms >= 24 have much better internationalization support than earlier, as many ICU classes are available as is.

  2. Platforms 21-24 still has reasonable internationalization support, by allowing creation of Locale objects and enabling selected ICU services through java.text namespace.

  3. Platforms < 21 doesn't allow creation of Locale objects from tags, severely limiting general purpose international code.

  4. Platform 30 has introduced classes under android.icu.number namespace which will majorly improve our Intl.NumberFormat implementation

Impact on Android Application Size

The following numbers are measured using a test application which takes dependency on the Hermes library to evaluate a JavaScript snippet. Essentially, enabling Intl APIs adds 57-62K per ABI.

Product APK Size NOINTL INTL DIFF PERC
ARM64 1,672,235 1,729,579 57,344 3.43%
ARM 1,471,539 1,528,883 57,344 3.90%
X86_64 1,844,255 1,901,599 57,344 3.11%
X86 1,950,739 2,012,179 61,440 3.15%

The overhead is contributed by both compiled native C++ and Java bits

The uncompressed size of the Hermes shared library got bigger as follows,

libhermes.so Size NOINTL INTL DIFF PERC
ARM64 2,473,760 2,551,592 77,832 3.15%
ARM 1,696,672 1,754,016 57,344 3.38%
X86_64 2,633,528 2,711,368 77,840 2.96%
X86 2,859,916 2,945,936 86,020 3.01%

And the Java bits got bigger as well,

Java Size NOINTL INTL DIFF PERC
classes.jar(in hermes.aar) 559 120975 120,416 21541.32%
classes.dex(replapp.apk) 160708 234808 74,100 46.11%

Please note that the application dex file contains non-hermes class files too.

And finally, this is the increase in the final npm package,

NPM Package NOINTL INTL DIFF PERC
hermes 214447973 219291220 4,843,247 2.26%

Doc LazyEvalCompilation (`doc/LazyEvalCompilation.md`)


id: lazy-eval-compilation title: Lazy/Eval Compilation

This is a high level overview of how we handle compiling code while the VM is executing, using existing Environments. Used for lazy compilation (deferring full compilation of functions until they are executed) and debugger local eval (used by the debugger REPL). For more detailed explanations about each of the parts, look at comments in the code for each of the compiler pipeline phases.

The concepts of lazy and eval compilation have conceptual overlap, but use of one doesn't technically require use of the other. It's possible to run lazy compilation but disallow debugger local eval and vice versa. The code supplied to debugger local eval may also be lazily compiled, but it doesn't have to be.

NOTE: All of the below currently only works when executing from source, as it relies on BCProviderFromSrc. Lazy compilation doesn't make sense when running from an HBC file (everything's already compiled) and debugging HBC hasn't been implemented yet. We also rely on optimizations not running, to avoid deleting variables/promoting them to the stack, because we rely on everything being in Environments at runtime.

Parsing

The parser has 3 phases when running lazy compilation:

  • PreParse: Run the parser over the whole file, but discard the AST. The only information retained is PreParsedData, which has information on function locations and metadata (arrow functions, strict mode, etc.).
  • LazyParse: Run the parser using the PreParsedData to skip over sufficiently large function bodies. The function bodies are stubbed out and the isLazyFunctionBody flag on their BlockStatementNode is set to true.
  • FullParse: A regular parse. Produces a full AST for the input JS source.

In lazy compilation, the PreParse and LazyParse passes run before execution begins. Then FullParse is run when the program attempts to actually execute a lazy function.

Data Storage

The data we need to run both lazy compilation and debugger eval is stored in BCProviderFromSrc's CompilationData. It points to all the information needed to run semantic resolution, IRGen, and HBC BCGen.

BCProviderFromSrc uses shared_ptr to store the IR Module and SemContext, because it's possible for us to use the same Module and SemContext for multiple BCProviderFromSrcs. That happens during debugger eval when the SemContext and IR are reused (to be able to access captured variables) but the source is new so we use a new BCProvider (because one BCProvider corresponds to one RuntimeModule, which corresponds to a single JS source).

The fact that we persist Module and SemContext allows us to run the compiler with new code almost as if it existed the whole time.

We introduce two IR instructions: LazyCompilationDataInst and EvalCompilationDataInst. These store data needed for lazy/eval compilation respectively and ensure that relevant Variables are kept alive by storing every parent VariableScope as an operand (the VariableScopes have users, so they won't get destroyed).

We can optionally have a pointer to an IR Function from the functionIR_ field of BytecodeFunction, which allows us to get to the relevant CompilationDataInst. The functionIR_ will be destroyed when the BytecodeFunction is itself destroyed along with its BytecodeModule when the GC cleans up the corresponding RuntimeModule.

Semantic Resolution

First pass

The first time the resolver runs with lazy compilation or in preparation for allowing debugger eval, it stores extra data to the FunctionInfo by populating the bindingTableScope field. In lazy compilation mode, it doesn't have to enter lazy functions because they're empty stub BlockStatementNodes.

Actual Resolution

SemanticResolver::runLazy is the entrypoint into SemanticResolver for lazy compilation. It uses an existing SemContext and adds new information to it. After resolving a lazy function, it cleans up binding table state unless we still need it for debugger eval in the future.

SemanticResolver::runInScope allows for local eval, and operates similarly, though it requires a ProgramNode as input. It doesn't free any preexisting SemContext data. It is called with a new SemContext, which is created as a child of the original SemContext to share the binding table but to allow it to be freed separately from the root SemContext.

These paths just run the SemanticResolver as usual after some setup, because they can restore the binding table state and internal SemanticResolver state based on their input.

IRGen

First pass

When IRGen encounters a lazy function body stub, it calls setupLazyFunction, which makes an IR Function with a LazyCompilationDataInst and an UnreachableInst terminator.

When IRGen actually compiles a function, if debugger eval is to be supported, it replaces the body of the IR Function with an EvalCompilationDataInst.

We also now have two lists of Functions in the Module: one ordinary list of functions to be compiled, and a compiledFunctions_ list which only holds functions that have already passed through the whole pipeline once. This is a performance optimization to avoid reiterating through these functions needlessly every single time we rerun IRGen.

Actual IRGen

Running IRGen on a lazy Function can produce many new IR Functions, because there may be many small functions inside the lazy function. These new functions will be some mix of lazy and eagerly compiled. They are not added to the compiledFunctions_ list yet.

BCGen

This part interacts heavily with the IR data structures.

First pass

On trying to run BCGen for a lazy Function, assign it a bcFunctionID. This allows it to get a dummy CodeBlock created at runtime, and we can use the ID to replace the lazy bytecode with real bytecode. Then, assign it to the functionIR_ field on the new BytecodeFunction so that we can find it later. Move the function to the compiledFunctions_ list so that we no longer treat it as a member of the regular function list in the IR lowering pipeline. Generate a mostly empty function header and empty bytecode, so we have enough information to run actual compilation later. So an IR Function used in lazy compilation can be: not compiled yet (and containing a LazyCompilationDataInst), assigned a BC ID but not actually compiled (and in compiledFunctions_), or destroyed and replaced with a real Function with real instructions.

Similarly for functions with EvalCompilationDataInst. They're already in the compiledFunctions_ list, but we store them in functionIR_.

Actual compilation

When the function is called during lazy compilation, generateLazyFunctions is issued to recompile every Function in the Module that isn't a lazy/eval function. This assigns bcFunctionIDs to all newly discovered functions. They are then generated as usual, and the BCProviderFromSrc is modified to point to the updated values for string tables, bytecode function tables, etc. by calling setBytecodeModuleRefs.

generateForEval works similarly but the key distinction is that eval creates a new BCProvider, so the existing one is not updated, and there's no existing BytecodeFunction with an ID that has to be replaced.

After either of these functions consumes the newly generated functions in the Module, we call resetForMoreCompilation which cleans upany Functions we don't need any more or replaces their body with just EvalCompilationDataInst if they're needed still for eval. It also deletes any now-unused VariableScopes.

Running the pipeline

Lazy compilation is called via CodeBlock::lazyCompile, which detects whether there's no bytecode on a CodeBlock and calls hbc::compileLazyFunction. It modifies the existing BCProviderFromSrc, and CodeBlock replaces its own internal state with the new information.

Debugger eval runs through the standard directEval pipeline, which uses evalInEnvironment. If evalInEnvironment is provided a Handle<Environment> then it won't run the pipeline from scratch but rather go through hbc::compileEvalModule.


Doc MemoryProfilers (`doc/MemoryProfilers.md`)


id: memory-profilers title: Memory Profilers

This page describes the various tools you can use to diagnose memory issues in
your app using Hermes. The tools can be used in a variety of ways based on
your needs.

The main goal of these profilers are to help you identify ways your app can
reduce its memory footprint. This is important for improving the user experience
in many ways:

  • Your app can run faster and have fewer, less expensive garbage collection
    cycles
  • Your app can avoid running out of memory entirely, and crashing with an Out of Memory (OOM) error
  • Your app can avoid being killed by the operating system for using too much memory

Heap Snapshots

A Heap Snapshot is a view of the entire JavaScript heap at a point in time. It
represents the data as a graph, where nodes are the values in the heap, and the
edges are pointers from one node to another. The snapshot can answer these types
of questions:

  • What types of values is my app using?
  • How much memory do those values use?
  • What memory is being retained unnecessarily?

The nodes in the snapshot can be any value usable in JS, such as:

  • Objects: {a: 1, b: 2}
  • Arrays: [1, 2, 3]
  • Functions: function foo(args) { ... }
  • Strings: "hello world"
  • Numbers: 3.14
  • Native host memory, injected into the heap via jsi::HostFunction and
    jsi::HostObject in the JSI API
  • Hermes-specific internal memory, such as Environment, ArrayStorage, and
    HiddenClass, and the backing storage of ArrayBuffers.

Each node has a list of edges to other nodes, which can be used to determine
what nodes retain other nodes. If node A retains node B, that means if A
is reachable by your program, B will also remain reachable. Therefore, B cannot
be garbage collected until A is garbage collected.
Many heap snapshot viewers will define the difference between shallow size
and retained size. Shallow size is the size of just that node, whereas
retained size is the size of all the nodes the current node retains.

The snapshot format is a JSON file ending in .heapsnapshot, and is the same
format used by the Chrome Developer Tools and the V8 JavaScript engine. This
means all tools that work with heap snapshots taken from V8, Chrome, or Node.js
are also compatible with snapshots taken from Hermes.

In particular, you can take memory snapshots from the Chrome Developer Tools
connected to Hermes!

Taking a heap snapshot with the Chrome DevTools

Before reading this section, make sure you can
connect the Chrome DevTools to Hermes
to debug some JavaScript. Once you have that set up you can proceed.

  1. Open the "Memory" tab of the DevTools, and make sure the "Heap snapshot" radio
    button is selected
  2. (Optional): Click on the garbage can icon in the top left to collect any
    unreachable objects. This will prevent them from showing up in the snapshot.
  3. Click on "Take snapshot"