Skip to content

Commit

Permalink
Make the preemptive compilation threshold configurable
Browse files Browse the repository at this point in the history
Summary:
This diff allows specifying the preemptive compilation threshold via CompileFlags.
Users get a toggle in CLI and RuntimeConfig that allows choosing between 1. fully eager, 2. fully lazy, 3. use default thresholds ("smart"). The default is facebook#3.

However, since we use `-O` by default, it overrides any lazy compilation when
using the `hermes` binary.

ChangeLog: [Internal] Inspector updated to use new RuntimeConfig laziness control

Reviewed By: tmikov

Differential Revision: D23356463

fbshipit-source-id: 508b7b2e6a218346c69acfec97e7891e388f0e9b
  • Loading branch information
willholen authored and facebook-github-bot committed Sep 14, 2020
1 parent de7c2ce commit 99f46fe
Show file tree
Hide file tree
Showing 25 changed files with 148 additions and 150 deletions.
24 changes: 17 additions & 7 deletions API/hermes/hermes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,21 @@ class HermesRuntimeImpl final : public HermesRuntime,
compileFlags_.debug = true;
#endif

switch (runtimeConfig.getCompilationMode()) {
case vm::SmartCompilation:
compileFlags_.lazy = true;
// (Leaves thresholds at default values)
break;
case vm::ForceEagerCompilation:
compileFlags_.lazy = false;
break;
case vm::ForceLazyCompilation:
compileFlags_.lazy = true;
compileFlags_.preemptiveFileCompilationThreshold = 0;
compileFlags_.preemptiveFunctionCompilationThreshold = 0;
break;
}

#ifndef HERMESJSI_ON_STACK
// Register the memory for the runtime if it isn't stored on the stack.
crashMgr_->registerMemory(&runtime_, sizeof(vm::Runtime));
Expand Down Expand Up @@ -1217,10 +1232,8 @@ void HermesRuntime::debugJavaScript(
const DebugFlags &debugFlags) {
vm::Runtime &runtime = impl(this)->runtime_;
vm::GCScope gcScope(&runtime);
hbc::CompileFlags flags{};
flags.debug = true;
flags.lazy = debugFlags.lazy;
vm::ExecutionStatus res = runtime.run(src, sourceURL, flags).getStatus();
vm::ExecutionStatus res =
runtime.run(src, sourceURL, impl(this)->compileFlags_).getStatus();
impl(this)->checkStatus(res);
}
#endif
Expand Down Expand Up @@ -1308,9 +1321,6 @@ HermesRuntimeImpl::prepareJavaScript(
bcErr = hbc::BCProviderFromBuffer::createBCProviderFromBuffer(
std::move(buffer));
} else {
compileFlags_.lazy =
(buffer->size() >=
::hermes::hbc::kDefaultSizeThresholdForLazyCompilation);
#if defined(HERMESVM_LEAN)
bcErr.second = "prepareJavaScript source compilation not supported";
#else
Expand Down
4 changes: 3 additions & 1 deletion API/hermes/hermes.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,9 @@ class HERMES_EXPORT HermesRuntime : public jsi::Runtime {
debugger::Debugger &getDebugger();

struct DebugFlags {
bool lazy{false};
// Looking for the .lazy flag? It's no longer necessary.
// Source is evaluated lazily by default. See
// RuntimeConfig::CompilationMode.
};

/// Evaluate the given code in an unoptimized form,
Expand Down
24 changes: 24 additions & 0 deletions include/hermes/AST/Context.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,14 @@ class Context {
/// first time.
bool lazyCompilation_{false};

/// Even if lazily compiling, eagerly compile any functions under this size in
/// bytes.
unsigned preemptiveFunctionCompilationThreshold_{0};

/// Even if lazily compiling, eagerly compile any files under this size in
/// bytes.
unsigned preemptiveFileCompilationThreshold_{0};

/// Allows Function.toString() to return original source code. As with lazy
/// compilation this requires source buffers, and hence this Context instance
/// to be retained after compilation.
Expand Down Expand Up @@ -327,6 +335,22 @@ class Context {
lazyCompilation_ = lazyCompilation;
}

unsigned getPreemptiveFunctionCompilationThreshold() {
return preemptiveFunctionCompilationThreshold_;
}

void setPreemptiveFunctionCompilationThreshold(unsigned byteCount) {
preemptiveFunctionCompilationThreshold_ = byteCount;
};

unsigned getPreemptiveFileCompilationThreshold() {
return preemptiveFileCompilationThreshold_;
}

void setPreemptiveFileCompilationThreshold(unsigned byteCount) {
preemptiveFileCompilationThreshold_ = byteCount;
};

bool allowFunctionToStringWithRuntimeSource() const {
return allowFunctionToStringWithRuntimeSource_;
}
Expand Down
17 changes: 10 additions & 7 deletions include/hermes/BCGen/HBC/BytecodeProviderFromSrc.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ struct CompileFlags {
bool optimize{false};
bool debug{false};
bool lazy{false};

/// Eagerly compile files under this number of bytes, even when lazy.
// Lazy compilation has significant per-module overhead, and is best applied
// to large bundles with a lot of unused code. Eager compilation is more
// efficient when compiling many small bundles with little unused code, such
// as when the API user loads smaller chunks of JS code on demand.
unsigned preemptiveFileCompilationThreshold{1 << 16};
/// Eagerly compile functions under this number of bytes, even when lazy.
unsigned preemptiveFunctionCompilationThreshold{160};

bool allowFunctionToStringWithRuntimeSource{false};
bool strict{false};
/// The value is optional; when it is set, the optimization setting is based
Expand All @@ -41,13 +51,6 @@ struct CompileFlags {
bool instrumentIR{false};
};

// The minimum code size in bytes before enabling lazy compilation.
// Lazy compilation has significant per-module overhead, and is best applied
// to large bundles with a lot of unused code. Eager compilation is more
// efficient when compiling many small bundles with little unused code, such as
// when the API user loads smaller chunks of JS code on demand.
static constexpr unsigned kDefaultSizeThresholdForLazyCompilation = 1 << 16;

#ifndef HERMESVM_LEAN
/// BCProviderFromSrc is used when we are construction the bytecode from
/// source compilation, i.e. we generate BytecodeModule/BytecodeFunction
Expand Down
13 changes: 10 additions & 3 deletions lib/BCGen/HBC/BytecodeProviderFromSrc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,13 @@ BCProviderFromSrc::createBCProviderFromSrc(

context->setStrictMode(compileFlags.strict);
context->setEnableEval(true);
if (!compileFlags.optimize) {
context->setLazyCompilation(compileFlags.lazy);
context->setPreemptiveFunctionCompilationThreshold(
compileFlags.preemptiveFunctionCompilationThreshold);
context->setPreemptiveFileCompilationThreshold(
compileFlags.preemptiveFileCompilationThreshold);

if (compileFlags.lazy && !compileFlags.optimize) {
context->setLazyCompilation(true);
}

context->setAllowFunctionToStringWithRuntimeSource(
Expand All @@ -186,6 +191,8 @@ BCProviderFromSrc::createBCProviderFromSrc(
declFileList.push_back(libParsed.getValue());
}

bool isLargeFile =
buffer->size() >= context->getPreemptiveFileCompilationThreshold();
int fileBufId = context->getSourceErrorManager().addNewSourceBuffer(
llvh::make_unique<HermesLLVMMemoryBuffer>(std::move(buffer), sourceURL));
if (sourceMap != nullptr) {
Expand All @@ -197,7 +204,7 @@ BCProviderFromSrc::createBCProviderFromSrc(

auto parserMode = parser::FullParse;
bool useStaticBuiltinDetected = false;
if (context->isLazyCompilation()) {
if (context->isLazyCompilation() && isLargeFile) {
if (!parser::JSParser::preParseBuffer(
*context, fileBufId, useStaticBuiltinDetected)) {
return {nullptr, outputManager.getErrorString()};
Expand Down
44 changes: 39 additions & 5 deletions lib/CompilerDriver/CompilerDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,13 @@ static opt<bool>
static opt<bool> LazyCompilation(
"lazy",
init(false),
desc("Compile source lazily when executing (HBC only)"),
desc("Force fully lazy compilation"),
cat(CompilerCategory));

static opt<bool> EagerCompilation(
"eager",
init(false),
desc("Force fully eager compilation"),
cat(CompilerCategory));

/// The following flags are exported so it may be used by the VM driver as well.
Expand Down Expand Up @@ -771,6 +777,9 @@ ESTree::NodePtr parseJS(
// builtin' directive in the source.
bool useStaticBuiltinDetected = false;

bool isLargeFile = fileBuf->getBufferSize() >=
context->getPreemptiveFileCompilationThreshold();

int fileBufId =
context->getSourceErrorManager().addNewSourceBuffer(std::move(fileBuf));
if (sourceMap != nullptr && sourceMapTranslator != nullptr) {
Expand All @@ -779,7 +788,7 @@ ESTree::NodePtr parseJS(

auto mode = parser::FullParse;

if (context->isLazyCompilation()) {
if (context->isLazyCompilation() && isLargeFile) {
if (!parser::JSParser::preParseBuffer(
*context, fileBufId, useStaticBuiltinDetected)) {
return nullptr;
Expand Down Expand Up @@ -891,6 +900,10 @@ bool validateFlags() {
"Specify output file with -out filename.");
}

if (cl::LazyCompilation && cl::EagerCompilation) {
err("Can't specify both -lazy and -eager");
}

// Validate lazy compilation flags.
if (cl::LazyCompilation) {
if (cl::BytecodeFormat != cl::BytecodeFormatKind::HBC)
Expand Down Expand Up @@ -1011,6 +1024,30 @@ std::shared_ptr<Context> createContext(
context->getSourceErrorManager().disableAllWarnings();
context->getSourceErrorManager().setErrorLimit(cl::ErrorLimit);

{
// 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::EagerCompilation || cl::DumpTarget == EmitBundle ||
cl::OptimizationLevel > cl::OptLevel::Og) {
// 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);
}

if (cl::CommonJS) {
context->setUseCJSModules(true);
}
Expand Down Expand Up @@ -1633,9 +1670,6 @@ CompileResult processSourceFiles(
}
}

// Enable lazy compilation if requested.
context->setLazyCompilation(cl::LazyCompilation);

// Allows Function.toString() to return original source code. As with lazy
// compilation this requires source buffers to be retained after compilation.
context->setAllowFunctionToStringWithRuntimeSource(cl::AllowFunctionToString);
Expand Down
8 changes: 2 additions & 6 deletions lib/Parser/JSParserImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ namespace hermes {
namespace parser {
namespace detail {

// If a function's source code body is smaller than this number of bytes,
// compile it immediately instead of creating a lazy stub.
static const int PreemptiveCompilationThresholdBytes = 160;

/// Declare a RAII recursion tracker. Check whether the recursion limit has
/// been exceeded, and if so generate an error and return an empty
/// llvh::Optional<>.
Expand Down Expand Up @@ -619,8 +615,8 @@ Optional<ESTree::BlockStatementNode *> JSParserImpl::parseFunctionBody(
auto startLoc = tok_->getStartLoc();
assert(preParsed_->bodyStartToEnd.count(startLoc) == 1);
auto endLoc = preParsed_->bodyStartToEnd[startLoc];
if (endLoc.getPointer() - startLoc.getPointer() >
PreemptiveCompilationThresholdBytes) {
if ((unsigned)(endLoc.getPointer() - startLoc.getPointer()) >=
context_.getPreemptiveFunctionCompilationThreshold()) {
lexer_.seek(endLoc);
advance();

Expand Down
2 changes: 1 addition & 1 deletion lib/VM/JSLib/eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ CallResult<HermesValue> evalInEnvironment(
compileFlags.includeLibHermes = false;
compileFlags.optimize = runtime->optimizedEval;
compileFlags.lazy =
utf8code.size() >= hbc::kDefaultSizeThresholdForLazyCompilation;
utf8code.size() >= compileFlags.preemptiveFileCompilationThreshold;
compileFlags.allowFunctionToStringWithRuntimeSource =
runtime->getAllowFunctionToStringWithRuntimeSource();
#ifdef HERMES_ENABLE_DEBUGGER
Expand Down
12 changes: 12 additions & 0 deletions public/hermes/Public/RuntimeConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ class raw_ostream;
namespace hermes {
namespace vm {

enum CompilationMode {
SmartCompilation,
ForceEagerCompilation,
ForceLazyCompilation
};

class PinnedHermesValue;
#ifdef HERMESVM_SERIALIZE
class Serializer;
Expand Down Expand Up @@ -105,6 +111,12 @@ class Deserializer;
/* runtime with CompileFlags::allowFunctionToStringWithRuntimeSource set. */ \
F(constexpr, bool, AllowFunctionToStringWithRuntimeSource, false) \
\
/* Choose lazy/eager compilation mode. */ \
F(constexpr, \
CompilationMode, \
CompilationMode, \
CompilationMode::SmartCompilation) \
\
/* An interface for managing crashes. */ \
F(HERMES_NON_CONSTEXPR, \
std::shared_ptr<CrashManager>, \
Expand Down
4 changes: 0 additions & 4 deletions test/IRGen/lazy-function-in-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ function f() {
function* g() {
var g_var = 32;
function h() {
/* Some text to pad out the function so that it won't be eagerly compiled
* for being too short. Lorem ipsum dolor sit amet, consectetur adipiscing
* elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
*/
return f_var + g_var;
}
yield h();
Expand Down
4 changes: 0 additions & 4 deletions test/IRGen/lazy-function-in-getter.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ function f() {
return {
get g() {
function h() {
/* Some text to pad out the function so that it won't be eagerly compiled
* for being too short. Lorem ipsum dolor sit amet, consectetur adipiscing
* elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
*/
}
}
};
Expand Down
10 changes: 5 additions & 5 deletions test/debugger/fuzzy-lazy-oneline-break.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// REQUIRES: debugger

// @nolint
function foo() { print('foo called'); /** * Some text to pad out the function so that it won't be eagerly compiled * for being too short. Lorem ipsum dolor sit amet, consectetur adipiscing * elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. */ function bar() { print('bar called'); /** * Some text to pad out the function so that it won't be eagerly compiled * for being too short. Lorem ipsum dolor sit amet, consectetur adipiscing * elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. */ } function baz() { print('baz called'); /** * Some text to pad out the function so that it won't be eagerly compiled * for being too short. Lorem ipsum dolor sit amet, consectetur adipiscing * elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. */ }; bar(); baz(); }
function foo() { print('foo called'); function bar() { print('bar called'); } function baz() { print('baz called'); }; bar(); baz(); }

debugger;
foo();
Expand All @@ -23,15 +23,15 @@ foo();
// CHECK: Break on 'debugger' statement in global: {{.*}}:14:1

// Breakpoint is in the middle of the 'print' for 'bar called'
// CHECK-NEXT: Set breakpoint 1 at {{.*}}:12:286
// CHECK-NEXT: Set breakpoint 1 at {{.*}}:12:56
// Breakpoint is in the middle of the 'print' for 'baz called'
// CHECK-NEXT: Set breakpoint 2 at {{.*}}:12:556
// CHECK-NEXT: Set breakpoint 2 at {{.*}}:12:96

// CHECK-NEXT: Continuing execution
// CHECK-NEXT: foo called
// CHECK-NEXT: Break on breakpoint 1 in bar: {{.*}}:12:286
// CHECK-NEXT: Break on breakpoint 1 in bar: {{.*}}:12:56
// CHECK-NEXT: Continuing execution
// CHECK-NEXT: bar called
// CHECK-NEXT: Break on breakpoint 2 in baz: {{.*}}:12:556
// CHECK-NEXT: Break on breakpoint 2 in baz: {{.*}}:12:96
// CHECK-NEXT: Continuing execution
// CHECK-NEXT: baz called
4 changes: 2 additions & 2 deletions test/debugger/fuzzy-lazy-oneline-break.js.debug
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
break 12 290
break 12 560
break 12 58
break 12 99
continue
continue
continue
6 changes: 1 addition & 5 deletions test/debugger/lazy-break-disable.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,12 @@

function foo() {
print('foo called');
/* Some text to pad out the function so that it won't be eagerly compiled
* for being too short. Lorem ipsum dolor sit amet, consectetur adipiscing
* elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
*/
}

debugger;
foo();

// CHECK: Break on 'debugger' statement in global: {{.*}}:19:1
// CHECK: Break on 'debugger' statement in global: {{.*}}:15:1
// CHECK-NEXT: Set breakpoint 1 at {{.*}}:12:3
// CHECK-NEXT: Disabled breakpoint 1
// CHECK-NEXT: 1 D {{.*}}:12:3
Expand Down
Loading

0 comments on commit 99f46fe

Please sign in to comment.