Skip to content

Conversation

@kewdex
Copy link
Contributor

@kewdex kewdex commented Dec 11, 2025

untested - wanted to see what the CI thinks

@ChALkeR
Copy link
Collaborator

ChALkeR commented Jan 28, 2026

I'm not sure why this is needed

@kewdex
Copy link
Contributor Author

kewdex commented Jan 28, 2026

@ChALkeR

The Hermes runtime has some options to perform "lazy compilation" for JS -> Hermes bytecode. My understanding is that JavaScript function's compilation to bytecode is deferred until they need to be executed The default approach is called "smart compilation" which kinda decides based on the file & function size what the lazy compile and what not.

The reason I made this PR is because it could impact the perf tests - as it would essentially include the time spent "lazy compiling" it rather than executing code which wouldn't be a very fair benchmark.

I think we can close this pull request though and just execute the hermes binary with the --eager flag to force full compilation before executing perf tests.

I asked Claude to check when lazy compilation get's used, I tried figuring it out myself a few weeks ago but my curiosity went down the rabbit hole and I got side tracked. The claude summary does seem accurate and aligns with my understanding from my manual look into this

The main hermes CLI binary (tools/hermes/hermes.cpp) fully supports lazy compilation through command-line flags defined in the compiler driver.

The binary uses driver::compileFromCommandLineOptions() which inherits all compilation options from CompilerDriver.cpp.

Command-Line Flags

Flag Description Source
-lazy Force fully lazy compilation for all files CompilerDriver.cpp:315-319
-eager Force fully eager compilation CompilerDriver.cpp:321-325

Default Behavior

By default, Hermes uses a hybrid approach based on file and function size thresholds:

Thresholds

Defined in include/hermes/BCGen/HBC/HBC.h:38-45:

Threshold Default Value Purpose
preemptiveFileCompilationThreshold 1 << 16 (64KB) Files smaller than this are compiled eagerly
preemptiveFunctionCompilationThreshold 160 bytes Functions smaller than this are compiled eagerly even in lazy mode

Configuration Logic

The lazy compilation mode is configured in CompilerDriver.cpp:1177-1200:

{
  // Set default lazy mode using defaults from CompileFlags to keep it in one
  // place.
  hermes::hbc::CompileFlags defaultFlags{};
  context->setPreemptiveFileCompilationThreshold(
      defaultFlags.preemptiveFileCompilationThreshold);
  context->setPreemptiveFunctionCompilationThreshold(
      defaultFlags.preemptiveFunctionCompilationThreshold);
}

if (cl::BytecodeFormat != cl::BytecodeFormatKind::HBC ||
    cl::DumpTarget != Execute || cl::OptimizationLevel > cl::OptLevel::Og ||
    cl::EagerCompilation) {
  // Make sure nothing is lazy
  context->setLazyCompilation(false);
} else if (cl::LazyCompilation) {
  // Make sure everything is lazy
  context->setLazyCompilation(true);
  context->setPreemptiveFileCompilationThreshold(0);
  context->setPreemptiveFunctionCompilationThreshold(0);
} else {
  // By default with no optimization, use lazy compilation for "large" files
  context->setLazyCompilation(true);
}

File Size Check

The actual size check occurs in BCProviderFromSrc.cpp:164-192 and CompilerDriver.cpp:803-814:

bool isLargeFile =
    buffer->size() >= context->getPreemptiveFileCompilationThreshold();
// ...
if (context->isLazyCompilation() && isLargeFile) {
  // Enable lazy parsing/compilation
}

Constraints and Limitations

Lazy compilation has several constraints, validated in CompilerDriver.cpp:998-1016:

if (cl::LazyCompilation && cl::EagerCompilation) {
  err("Can't specify both -lazy and -eager");
}
// Validate lazy compilation flags.
if (cl::LazyCompilation) {
  if (!targetHBC) {
    err("-lazy only works with -target=HBC");
  }
  if (cl::OptimizationLevel > cl::OptLevel::Og) {
    err("-lazy does not work with -O");
  }
  if (cl::BytecodeMode) {
    err("-lazy doesn't make sense with bytecode");
  }
  if (customOptEnabled) {
    err("-lazy doesn't allow custom optimizations");
  }
  if (cl::CommonJS) {
    err("-lazy doesn't support CommonJS modules");
  }
}
Constraint Reason
Only -target=HBC Lazy compilation requires HBC bytecode target
No optimization (-O) Optimizations are incompatible with lazy compilation
Source only Doesn't make sense with pre-compiled bytecode
No CommonJS CommonJS modules not supported
No custom optimizations Custom optimization passes not allowed

How Lazy Compilation Works

From the official documentation:

Parsing Phases

  1. PreParse: Runs the parser over the whole file but discards the AST. Only retains PreParsedData with function locations and metadata.

  2. LazyParse: Uses PreParsedData to skip over sufficiently large function bodies. Function bodies are stubbed out with isLazyFunctionBody flag set to true.

  3. FullParse: A regular parse that produces a full AST. Only runs when the program attempts to execute a lazy function.

IR Representation

Lazy functions are represented using LazyCompilationDataInst which stores data needed for deferred compilation:

class LazyCompilationDataInst : public Instruction {
  LazyCompilationData data_;
  // ...
};

Runtime Compilation

When a lazy function is called, CodeBlock::lazyCompile detects the missing bytecode and triggers hbc::compileLazyFunction to compile the function on-demand.

Usage Examples

# Default behavior: lazy for files >= 64KB
hermes script.js

# Force lazy compilation for all files
hermes -lazy script.js

# Force eager compilation (no lazy)
hermes -eager script.js

# Lazy compilation is automatically disabled with optimization
hermes -O script.js  # Will NOT use lazy compilation

@kewdex kewdex closed this Jan 28, 2026
@kewdex kewdex deleted the kewde/hermes-bytecode-compilation branch January 28, 2026 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants