Commit fbcc2e87 authored by Simon Zünd's avatar Simon Zünd Committed by Commit Bot

Introduce REPL mode

Design doc: bit.ly/v8-repl-mode

This CL adds a new REPL mode that can be used via
DebugEvaluate::GlobalREPL. REPL mode only implements re-declaration
of 'let' bindings at the moment. Example:

REPL Input 1: let x = 21;
REPL Input 2: let x = 42;

This would normally throw a SyntaxError, but works in REPL mode.

The implementation is done by:
  - Setting a 'repl mode' bit on {Script}, {ScopeInfo}, {ParseInfo}
    and script {Scope}.
  - Each global let declaration still gets a slot reserved in the
    respective {ScriptContext}.
  - When a new REPL mode {ScriptContext} is created, name clashes
    for let bindings are not reported as errors.
  - Declarations, loads and stores for global let in REPL mode are
    now "load/store global" instead of accessing their respective
    context slot directly. This causes a lookup in the ScriptContextTable
    where the found slot for each name is guaranteed to be the same
    (the first one).

Bug: chromium:1004193, chromium:1018158
Change-Id: Ia6ab526b9f696400dbb8bfb611a4d43606119a47
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1876061
Commit-Queue: Simon Zünd <szuend@chromium.org>
Reviewed-by: 's avatarRoss McIlroy <rmcilroy@chromium.org>
Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/master@{#64793}
parent 38e59494
......@@ -1272,6 +1272,9 @@ void AstPrinter::VisitVariableProxy(VariableProxy* node) {
case VariableLocation::MODULE:
SNPrintF(buf + pos, " module");
break;
case VariableLocation::REPL_GLOBAL:
SNPrintF(buf + pos, " repl global[%d]", var->index());
break;
}
PrintLiteralWithModeIndented(buf.begin(), var, node->raw_name());
}
......
......@@ -297,6 +297,7 @@ void Scope::SetDefaults() {
private_name_lookup_skips_outer_class_ = false;
must_use_preparsed_scope_data_ = false;
is_repl_mode_scope_ = false;
num_stack_slots_ = 0;
num_heap_slots_ = ContextHeaderLength();
......@@ -362,6 +363,7 @@ Scope* Scope::DeserializeScopeChain(Isolate* isolate, Zone* zone,
if (deserialization_mode == DeserializationMode::kIncludingVariables) {
script_scope->SetScriptScopeInfo(handle(scope_info, isolate));
}
if (scope_info.IsReplModeScope()) script_scope->set_is_repl_mode_scope();
DCHECK(!scope_info.HasOuterScopeInfo());
break;
} else if (scope_info.scope_type() == FUNCTION_SCOPE) {
......@@ -582,6 +584,7 @@ bool DeclarationScope::Analyze(ParseInfo* info) {
}
if (!scope->AllocateVariables(info)) return false;
scope->GetScriptScope()->RewriteReplGlobalVariables();
#ifdef DEBUG
if (FLAG_print_scopes) {
......@@ -1335,6 +1338,14 @@ DeclarationScope* Scope::GetReceiverScope() {
return scope->AsDeclarationScope();
}
DeclarationScope* Scope::GetScriptScope() {
Scope* scope = this;
while (!scope->is_script_scope()) {
scope = scope->outer_scope();
}
return scope->AsDeclarationScope();
}
Scope* Scope::GetOuterScopeWithContext() {
Scope* scope = outer_scope_;
while (scope && !scope->NeedsContext()) {
......@@ -1551,6 +1562,17 @@ void DeclarationScope::AnalyzePartially(Parser* parser,
unresolved_list_ = std::move(new_unresolved_list);
}
void DeclarationScope::RewriteReplGlobalVariables() {
DCHECK(is_script_scope());
if (!is_repl_mode_scope()) return;
for (VariableMap::Entry* p = variables_.Start(); p != nullptr;
p = variables_.Next(p)) {
Variable* var = reinterpret_cast<Variable*>(p->value);
var->RewriteLocationForRepl();
}
}
#ifdef DEBUG
namespace {
......@@ -1600,6 +1622,9 @@ void PrintLocation(Variable* var) {
case VariableLocation::MODULE:
PrintF("module");
break;
case VariableLocation::REPL_GLOBAL:
PrintF("repl global[%d]", var->index());
break;
}
}
......@@ -2148,7 +2173,6 @@ bool Scope::MustAllocateInContext(Variable* var) {
return var->has_forced_context_allocation() || inner_scope_calls_eval_;
}
void Scope::AllocateStackSlot(Variable* var) {
if (is_block_scope()) {
outer_scope()->GetDeclarationScope()->AllocateStackSlot(var);
......
......@@ -503,6 +503,8 @@ class V8_EXPORT_PRIVATE Scope : public NON_EXPORTED_BASE(ZoneObject) {
// 'this' is bound, and what determines the function kind.
DeclarationScope* GetReceiverScope();
DeclarationScope* GetScriptScope();
// Find the innermost outer scope that needs a context.
Scope* GetOuterScopeWithContext();
......@@ -534,6 +536,8 @@ class V8_EXPORT_PRIVATE Scope : public NON_EXPORTED_BASE(ZoneObject) {
void set_is_debug_evaluate_scope() { is_debug_evaluate_scope_ = true; }
bool is_debug_evaluate_scope() const { return is_debug_evaluate_scope_; }
bool IsSkippableFunctionScope();
void set_is_repl_mode_scope() { is_repl_mode_scope_ = true; }
bool is_repl_mode_scope() const { return is_repl_mode_scope_; }
bool RemoveInnerScope(Scope* inner_scope) {
DCHECK_NOT_NULL(inner_scope);
......@@ -750,6 +754,10 @@ class V8_EXPORT_PRIVATE Scope : public NON_EXPORTED_BASE(ZoneObject) {
bool private_name_lookup_skips_outer_class_ : 1;
bool must_use_preparsed_scope_data_ : 1;
// True if this is a script scope that originated from
// DebugEvaluate::GlobalREPL().
bool is_repl_mode_scope_ : 1;
};
class V8_EXPORT_PRIVATE DeclarationScope : public Scope {
......@@ -1119,6 +1127,10 @@ class V8_EXPORT_PRIVATE DeclarationScope : public Scope {
}
void RecordNeedsPrivateNameContextChainRecalc();
// Re-writes the {VariableLocation} of top-level 'let' bindings from CONTEXT
// to REPL_GLOBAL. Should only be called on REPL scripts.
void RewriteReplGlobalVariables();
private:
V8_INLINE void AllocateParameter(Variable* var, int index);
......
......@@ -30,5 +30,20 @@ bool Variable::IsGlobalObjectProperty() const {
scope_ != nullptr && scope_->is_script_scope();
}
bool Variable::IsReplGlobalLet() const {
DCHECK_IMPLIES(scope()->is_repl_mode_scope(), scope()->is_script_scope());
return scope()->is_repl_mode_scope() && mode() == VariableMode::kLet;
}
void Variable::RewriteLocationForRepl() {
DCHECK(scope_->is_repl_mode_scope());
if (mode() == VariableMode::kLet) {
DCHECK_EQ(location(), VariableLocation::CONTEXT);
bit_field_ =
LocationField::update(bit_field_, VariableLocation::REPL_GLOBAL);
}
}
} // namespace internal
} // namespace v8
......@@ -121,6 +121,9 @@ class Variable final : public ZoneObject {
bool IsLookupSlot() const { return location() == VariableLocation::LOOKUP; }
bool IsGlobalObjectProperty() const;
// True for 'let' variables declared in the script scope of a REPL script.
bool IsReplGlobalLet() const;
bool is_dynamic() const { return IsDynamicVariableMode(mode()); }
// Returns the InitializationFlag this Variable was created with.
......@@ -235,6 +238,9 @@ class Variable final : public ZoneObject {
: kNeedsInitialization;
}
// Rewrites the VariableLocation of repl script scope 'lets' to REPL_GLOBAL.
void RewriteLocationForRepl();
using List = base::ThreadedList<Variable>;
private:
......
......@@ -135,6 +135,12 @@ ScriptOriginOptions OriginOptionsForEval(Object script) {
outer_origin_options.IsOpaque());
}
REPLMode OriginReplMode(Object script) {
if (!script.IsScript()) return REPLMode::kNo;
return Script::cast(script).is_repl_mode() ? REPLMode::kYes : REPLMode::kNo;
}
} // namespace
// ----------------------------------------------------------------------------
......@@ -1542,8 +1548,9 @@ MaybeHandle<JSFunction> Compiler::GetFunctionFromEval(
allow_eval_cache = true;
} else {
ParseInfo parse_info(isolate);
script = parse_info.CreateScript(
isolate, source, OriginOptionsForEval(outer_info->script()));
script = parse_info.CreateScript(isolate, source,
OriginOptionsForEval(outer_info->script()),
OriginReplMode(outer_info->script()));
script->set_compilation_type(Script::COMPILATION_TYPE_EVAL);
script->set_eval_from_shared(*outer_info);
if (eval_position == kNoSourcePosition) {
......@@ -1964,8 +1971,8 @@ Handle<Script> NewScript(Isolate* isolate, ParseInfo* parse_info,
ScriptOriginOptions origin_options,
NativesFlag natives) {
// Create a script object describing the script to be compiled.
Handle<Script> script =
parse_info->CreateScript(isolate, source, origin_options, natives);
Handle<Script> script = parse_info->CreateScript(
isolate, source, origin_options, script_details.repl_mode, natives);
Handle<Object> script_name;
if (script_details.name_obj.ToHandle(&script_name)) {
script->set_name(*script_name);
......@@ -2058,6 +2065,7 @@ MaybeHandle<SharedFunctionInfo> Compiler::GetSharedFunctionInfoForScript(
// No cache entry found compile the script.
NewScript(isolate, &parse_info, source, script_details, origin_options,
natives);
DCHECK_EQ(parse_info.is_repl_mode(), parse_info.script()->is_repl_mode());
// Compile the function and add it to the isolate cache.
if (origin_options.IsModule()) parse_info.set_module();
......
......@@ -112,15 +112,20 @@ class V8_EXPORT_PRIVATE Compiler : public AllStatic {
int eval_scope_position, int eval_position);
struct ScriptDetails {
ScriptDetails() : line_offset(0), column_offset(0) {}
ScriptDetails()
: line_offset(0), column_offset(0), repl_mode(REPLMode::kNo) {}
explicit ScriptDetails(Handle<Object> script_name)
: line_offset(0), column_offset(0), name_obj(script_name) {}
: line_offset(0),
column_offset(0),
name_obj(script_name),
repl_mode(REPLMode::kNo) {}
int line_offset;
int column_offset;
i::MaybeHandle<i::Object> name_obj;
i::MaybeHandle<i::Object> source_map_url;
i::MaybeHandle<i::FixedArray> host_defined_options;
REPLMode repl_mode;
};
// Create a function that results from wrapping |source| in a function,
......
......@@ -787,6 +787,12 @@ enum class BytecodeFlushMode {
kStressFlushBytecode,
};
// Indicates whether a script should be parsed and compiled in REPL mode.
enum class REPLMode {
kYes,
kNo,
};
// Flag indicating whether code is built into the VM (one of the natives files).
enum NativesFlag { NOT_NATIVES_CODE, EXTENSION_CODE, INSPECTOR_CODE };
......@@ -1195,7 +1201,17 @@ enum VariableLocation : uint8_t {
// A named slot in a module's export table.
MODULE,
kLastVariableLocation = MODULE
// An indexed slot in a script context. index() is the variable
// index in the context object on the heap, starting at 0.
// Important: REPL_GLOBAL variables from different scripts with the
// same name share a single script context slot. Every
// script context will reserve a slot, but only one will be used.
// REPL_GLOBAL variables are stored in script contexts, but accessed like
// globals, i.e. they always require a lookup at runtime to find the right
// script context.
REPL_GLOBAL,
kLastVariableLocation = REPL_GLOBAL
};
// ES6 specifies declarative environment records with mutable and immutable
......
......@@ -58,6 +58,30 @@ MaybeHandle<Object> DebugEvaluate::Global(Isolate* isolate,
return result;
}
MaybeHandle<Object> DebugEvaluate::GlobalREPL(Isolate* isolate,
Handle<String> source) {
Handle<Context> context = isolate->native_context();
Compiler::ScriptDetails script_details(isolate->factory()->empty_string());
script_details.repl_mode = REPLMode::kYes;
ScriptOriginOptions origin_options(false, true);
MaybeHandle<SharedFunctionInfo> maybe_function_info =
Compiler::GetSharedFunctionInfoForScript(
isolate, source, script_details, origin_options, nullptr, nullptr,
ScriptCompiler::kNoCompileOptions, ScriptCompiler::kNoCacheNoReason,
NOT_NATIVES_CODE);
Handle<SharedFunctionInfo> shared_info;
if (!maybe_function_info.ToHandle(&shared_info)) return MaybeHandle<Object>();
Handle<JSFunction> fun =
isolate->factory()->NewFunctionFromSharedFunctionInfo(shared_info,
context);
MaybeHandle<Object> result = Execution::Call(
isolate, fun, Handle<JSObject>(context->global_proxy(), isolate), 0,
nullptr);
return result;
}
MaybeHandle<Object> DebugEvaluate::Local(Isolate* isolate,
StackFrameId frame_id,
int inlined_jsframe_index,
......
......@@ -26,6 +26,9 @@ class DebugEvaluate : public AllStatic {
static MaybeHandle<Object> Global(Isolate* isolate, Handle<String> source,
debug::EvaluateGlobalMode mode);
static MaybeHandle<Object> GlobalREPL(Isolate* isolate,
Handle<String> source);
// Evaluate a piece of JavaScript in the context of a stack frame for
// debugging. Things that need special attention are:
// - Parameters and stack-allocated locals need to be materialized. Altered
......
......@@ -771,6 +771,8 @@ bool ScopeIterator::VisitLocals(const Visitor& visitor, Mode mode) const {
UNREACHABLE();
break;
case VariableLocation::REPL_GLOBAL:
// REPL declared variables are ignored for now.
case VariableLocation::UNALLOCATED:
continue;
......@@ -929,6 +931,10 @@ bool ScopeIterator::SetLocalVariableValue(Handle<String> variable_name,
*variable_name == ReadOnlyRoots(isolate_).arguments_string());
return false;
case VariableLocation::REPL_GLOBAL:
// Assignments to REPL declared variables are ignored for now.
return false;
case VariableLocation::PARAMETER: {
if (var->is_this()) return false;
if (frame_inspector_ == nullptr) {
......
......@@ -480,7 +480,11 @@ MaybeHandle<Object> LoadGlobalIC::Load(Handle<Name> name,
if (result->IsTheHole(isolate())) {
// Do not install stubs and stay pre-monomorphic for
// uninitialized accesses.
return ReferenceError(name);
THROW_NEW_ERROR(
isolate(),
NewReferenceError(MessageTemplate::kAccessedUninitializedVariable,
name),
Object);
}
bool use_ic = (state() != NO_FEEDBACK) && FLAG_use_ic && update_feedback;
......@@ -1403,7 +1407,11 @@ MaybeHandle<Object> StoreGlobalIC::Store(Handle<Name> name,
if (previous_value->IsTheHole(isolate())) {
// Do not install stubs and stay pre-monomorphic for
// uninitialized accesses.
return ReferenceError(name);
THROW_NEW_ERROR(
isolate(),
NewReferenceError(MessageTemplate::kAccessedUninitializedVariable,
name),
Object);
}
bool use_ic = (state() != NO_FEEDBACK) && FLAG_use_ic;
......@@ -2390,7 +2398,8 @@ RUNTIME_FUNCTION(Runtime_StoreGlobalIC_Slow) {
if (previous_value->IsTheHole(isolate)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewReferenceError(MessageTemplate::kNotDefined, name));
isolate, NewReferenceError(
MessageTemplate::kAccessedUninitializedVariable, name));
}
script_context->set(lookup_result.slot_index, *value);
......
......@@ -1363,6 +1363,9 @@ void BytecodeGenerator::VisitVariableDeclaration(VariableDeclaration* decl) {
builder()->LoadTheHole().StoreAccumulatorInRegister(destination);
}
break;
case VariableLocation::REPL_GLOBAL:
// REPL let's are stored in script contexts. They get initialized
// with the hole the same way as normal context allocated variables.
case VariableLocation::CONTEXT:
if (variable->binding_needs_init()) {
DCHECK_EQ(0, execution_context()->ContextChainDepth(variable->scope()));
......@@ -1416,6 +1419,7 @@ void BytecodeGenerator::VisitFunctionDeclaration(FunctionDeclaration* decl) {
BuildVariableAssignment(variable, Token::INIT, HoleCheckMode::kElided);
break;
}
case VariableLocation::REPL_GLOBAL:
case VariableLocation::CONTEXT: {
DCHECK_EQ(0, execution_context()->ContextChainDepth(variable->scope()));
VisitFunctionLiteral(decl->fun());
......@@ -3066,6 +3070,13 @@ void BytecodeGenerator::BuildVariableLoad(Variable* variable,
}
break;
}
case VariableLocation::REPL_GLOBAL: {
DCHECK(variable->IsReplGlobalLet());
FeedbackSlot slot = GetCachedLoadGlobalICSlot(typeof_mode, variable);
builder()->LoadGlobal(variable->raw_name(), feedback_index(slot),
typeof_mode);
break;
}
}
}
......@@ -3248,6 +3259,33 @@ void BytecodeGenerator::BuildVariableAssignment(
builder()->StoreModuleVariable(variable->index(), depth);
break;
}
case VariableLocation::REPL_GLOBAL: {
// A let declaration like 'let x = 7' is effectively translated to:
// <top of the script>:
// ScriptContext.x = TheHole;
// ...
// <where the actual 'let' is>:
// ScriptContextTable.x = 7; // no hole check
//
// The ScriptContext slot for 'x' that we store to here is not
// necessarily the ScriptContext of this script, but rather the
// first ScriptContext that has a slot for name 'x'.
DCHECK(variable->IsReplGlobalLet());
if (op == Token::INIT) {
RegisterList store_args = register_allocator()->NewRegisterList(2);
builder()
->StoreAccumulatorInRegister(store_args[1])
.LoadLiteral(variable->raw_name())
.StoreAccumulatorInRegister(store_args[0]);
builder()->CallRuntime(Runtime::kStoreGlobalNoHoleCheckForReplLet,
store_args);
} else {
FeedbackSlot slot =
GetCachedStoreGlobalICSlot(language_mode(), variable);
builder()->StoreGlobal(variable->raw_name(), feedback_index(slot));
}
break;
}
}
}
......
......@@ -289,6 +289,17 @@ Handle<Object> Context::Lookup(Handle<Context> context, Handle<String> name,
&maybe_assigned_flag, &is_static_flag);
DCHECK(slot_index < 0 || slot_index >= MIN_CONTEXT_SLOTS);
if (slot_index >= 0) {
// Re-direct lookup to the ScriptContextTable in case we find a hole in
// a REPL script context. REPL scripts allow re-declaration of
// script-level let bindings. The value itself is stored in the script
// context of the first script that declared a variable, all other
// script contexts will contain 'the hole' for that particular name.
if (scope_info.IsReplModeScope() &&
context->get(slot_index).IsTheHole(isolate)) {
context = Handle<Context>(context->previous(), isolate);
continue;
}
if (FLAG_trace_contexts) {
PrintF("=> found local in context slot %d (mode = %hhu)\n",
slot_index, static_cast<uint8_t>(mode));
......
......@@ -72,6 +72,7 @@ Handle<ScopeInfo> ScopeInfo::Create(Isolate* isolate, Zone* zone, Scope* scope,
for (Variable* var : *scope->locals()) {
switch (var->location()) {
case VariableLocation::CONTEXT:
case VariableLocation::REPL_GLOBAL:
context_local_count++;
break;
case VariableLocation::MODULE:
......@@ -207,7 +208,8 @@ Handle<ScopeInfo> ScopeInfo::Create(Isolate* isolate, Zone* zone, Scope* scope,
scope->ForceContextForLanguageMode()) |
PrivateNameLookupSkipsOuterClassField::encode(
scope->private_name_lookup_skips_outer_class()) |
HasContextExtensionSlotField::encode(scope->HasContextExtensionSlot());
HasContextExtensionSlotField::encode(scope->HasContextExtensionSlot()) |
IsReplModeScopeField::encode(scope->is_repl_mode_scope());
scope_info.SetFlags(flags);
scope_info.SetParameterCount(parameter_count);
......@@ -221,7 +223,8 @@ Handle<ScopeInfo> ScopeInfo::Create(Isolate* isolate, Zone* zone, Scope* scope,
for (Variable* var : *scope->locals()) {
switch (var->location()) {
case VariableLocation::CONTEXT: {
case VariableLocation::CONTEXT:
case VariableLocation::REPL_GLOBAL: {
// Due to duplicate parameters, context locals aren't guaranteed to
// come in order.
int local_index = var->index() - scope->ContextHeaderLength();
......@@ -394,7 +397,8 @@ Handle<ScopeInfo> ScopeInfo::CreateForWithScope(
IsDebugEvaluateScopeField::encode(false) |
ForceContextAllocationField::encode(false) |
PrivateNameLookupSkipsOuterClassField::encode(false) |
HasContextExtensionSlotField::encode(true);
HasContextExtensionSlotField::encode(true) |
IsReplModeScopeField::encode(false);
scope_info->SetFlags(flags);
scope_info->SetParameterCount(0);
......@@ -471,7 +475,8 @@ Handle<ScopeInfo> ScopeInfo::CreateForBootstrapping(Isolate* isolate,
IsDebugEvaluateScopeField::encode(false) |
ForceContextAllocationField::encode(false) |
PrivateNameLookupSkipsOuterClassField::encode(false) |
HasContextExtensionSlotField::encode(is_native_context);
HasContextExtensionSlotField::encode(is_native_context) |
IsReplModeScopeField::encode(false);
scope_info->SetFlags(flags);
scope_info->SetParameterCount(parameter_count);
scope_info->SetContextLocalCount(context_local_count);
......@@ -668,6 +673,11 @@ bool ScopeInfo::PrivateNameLookupSkipsOuterClass() const {
return PrivateNameLookupSkipsOuterClassField::decode(Flags());
}
bool ScopeInfo::IsReplModeScope() const {
if (length() == 0) return false;
return IsReplModeScopeField::decode(Flags());
}
bool ScopeInfo::HasContext() const { return ContextLength() > 0; }
Object ScopeInfo::FunctionName() const {
......
......@@ -197,6 +197,10 @@ class ScopeInfo : public FixedArray {
// closest outer class when resolving private names.
bool PrivateNameLookupSkipsOuterClass() const;
// REPL mode scopes allow re-declaraction of let variables. They come from
// debug evaluate but are different to IsDebugEvaluateScope().
bool IsReplModeScope() const;
#ifdef DEBUG
bool Equals(ScopeInfo other) const;
#endif
......@@ -269,6 +273,7 @@ class ScopeInfo : public FixedArray {
ForceContextAllocationField::Next<bool, 1>;
using HasContextExtensionSlotField =
PrivateNameLookupSkipsOuterClassField::Next<bool, 1>;
using IsReplModeScopeField = HasContextExtensionSlotField::Next<bool, 1>;
STATIC_ASSERT(kLastFunctionKind <= FunctionKindField::kMax);
......
......@@ -116,6 +116,15 @@ void Script::set_compilation_state(CompilationState state) {
set_flags(BooleanBit::set(flags(), kCompilationStateBit,
state == COMPILATION_STATE_COMPILED));
}
bool Script::is_repl_mode() const {
return BooleanBit::get(flags(), kREPLModeBit);
}
void Script::set_is_repl_mode(bool value) {
set_flags(BooleanBit::set(flags(), kREPLModeBit, value));
}
ScriptOriginOptions Script::origin_options() {
return ScriptOriginOptions((flags() & kOriginOptionsMask) >>
kOriginOptionsShift);
......
......@@ -128,6 +128,11 @@ class Script : public Struct {
inline CompilationState compilation_state();
inline void set_compilation_state(CompilationState state);
// [is_repl_mode]: whether this script originated from a REPL via debug
// evaluate and therefore has different semantics, e.g. re-declaring let.
inline bool is_repl_mode() const;
inline void set_is_repl_mode(bool value);
// [origin_options]: optional attributes set by the embedder via ScriptOrigin,
// and used by the embedder to make decisions about the script. V8 just passes
// this through. Encoded in the 'flags' field.
......@@ -212,7 +217,8 @@ class Script : public Struct {
// Bit positions in the flags field.
static const int kCompilationTypeBit = 0;
static const int kCompilationStateBit = 1;
static const int kOriginOptionsShift = 2;
static const int kREPLModeBit = 2;
static const int kOriginOptionsShift = 3;
static const int kOriginOptionsSize = 4;
static const int kOriginOptionsMask = ((1 << kOriginOptionsSize) - 1)
<< kOriginOptionsShift;
......
......@@ -664,6 +664,10 @@ void SharedFunctionInfo::set_script(HeapObject script) {
}
}
bool SharedFunctionInfo::is_repl_mode() const {
return script().IsScript() && Script::cast(script()).is_repl_mode();
}
bool SharedFunctionInfo::HasDebugInfo() const {
return script_or_debug_info().IsDebugInfo();
}
......
......@@ -364,6 +364,9 @@ class SharedFunctionInfo : public HeapObject {
inline HeapObject script() const;
inline void set_script(HeapObject script);
// True if the underlying script was parsed and compiled in REPL mode.
inline bool is_repl_mode() const;
// The function is subject to debugging if a debug info is attached.
inline bool HasDebugInfo() const;
inline DebugInfo GetDebugInfo() const;
......
......@@ -107,6 +107,8 @@ ParseInfo::ParseInfo(Isolate* isolate, Handle<SharedFunctionInfo> shared)
set_outer_scope_info(handle(shared->GetOuterScopeInfo(), isolate));
}
set_repl_mode(shared->is_repl_mode());
// CollectTypeProfile uses its own feedback slots. If we have existing
// FeedbackMetadata, we can only collect type profile if the feedback vector
// has the appropriate slots.
......@@ -165,6 +167,7 @@ DeclarationScope* ParseInfo::scope() const { return literal()->scope(); }
Handle<Script> ParseInfo::CreateScript(Isolate* isolate, Handle<String> source,
ScriptOriginOptions origin_options,
REPLMode repl_mode,
NativesFlag natives) {
// Create a script object describing the script to be compiled.
Handle<Script> script;
......@@ -187,6 +190,7 @@ Handle<Script> ParseInfo::CreateScript(Isolate* isolate, Handle<String> source,
break;
}
script->set_origin_options(origin_options);
script->set_is_repl_mode(repl_mode == REPLMode::kYes);
SetScriptForToplevelCompile(isolate, script);
return script;
......@@ -220,6 +224,7 @@ void ParseInfo::SetScriptForToplevelCompile(Isolate* isolate,
set_toplevel();
set_collect_type_profile(isolate->is_collecting_type_profile() &&
script->IsUserJavaScript());
set_repl_mode(script->is_repl_mode());
if (script->is_wrapped()) {
set_function_syntax_kind(FunctionSyntaxKind::kWrapped);
}
......
......@@ -56,6 +56,7 @@ class V8_EXPORT_PRIVATE ParseInfo {
Handle<Script> CreateScript(Isolate* isolate, Handle<String> source,
ScriptOriginOptions origin_options,
REPLMode repl_mode = REPLMode::kNo,
NativesFlag natives = NOT_NATIVES_CODE);
// Either returns the ast-value-factory associcated with this ParseInfo, or
......@@ -112,6 +113,7 @@ class V8_EXPORT_PRIVATE ParseInfo {
set_allow_harmony_nullish)
FLAG_ACCESSOR(kAllowHarmonyTopLevelAwait, allow_harmony_top_level_await,
set_allow_harmony_top_level_await)
FLAG_ACCESSOR(kREPLMode, is_repl_mode, set_repl_mode)
#undef FLAG_ACCESSOR
......@@ -291,37 +293,38 @@ class V8_EXPORT_PRIVATE ParseInfo {
// Various configuration flags for parsing.
enum Flag : uint32_t {
// ---------- Input flags ---------------------------
kToplevel = 1 << 0,
kEager = 1 << 1,
kEval = 1 << 2,
kStrictMode = 1 << 3,
kNative = 1 << 4,
kParseRestriction = 1 << 5,
kModule = 1 << 6,
kAllowLazyParsing = 1 << 7,
kLazyCompile = 1 << 8,
kCollectTypeProfile = 1 << 9,
kCoverageEnabled = 1 << 10,
kBlockCoverageEnabled = 1 << 11,
kIsAsmWasmBroken = 1 << 12,
kOnBackgroundThread = 1 << 13,
kAllowEvalCache = 1 << 14,
kRequiresInstanceMembersInitializer = 1 << 15,
kContainsAsmModule = 1 << 16,
kMightAlwaysOpt = 1 << 17,
kAllowLazyCompile = 1 << 18,
kAllowNativeSyntax = 1 << 19,
kAllowHarmonyPublicFields = 1 << 20,
kAllowHarmonyStaticFields = 1 << 21,
kAllowHarmonyDynamicImport = 1 << 22,
kAllowHarmonyImportMeta = 1 << 23,
kAllowHarmonyOptionalChaining = 1 << 24,
kAllowHarmonyPrivateFields = 1 << 25,
kAllowHarmonyPrivateMethods = 1 << 26,
kIsOneshotIIFE = 1 << 27,
kCollectSourcePositions = 1 << 28,
kAllowHarmonyNullish = 1 << 29,
kAllowHarmonyTopLevelAwait = 1 << 30,
kToplevel = 1u << 0,
kEager = 1u << 1,
kEval = 1u << 2,
kStrictMode = 1u << 3,
kNative = 1u << 4,
kParseRestriction = 1u << 5,
kModule = 1u << 6,
kAllowLazyParsing = 1u << 7,
kLazyCompile = 1u << 8,
kCollectTypeProfile = 1u << 9,
kCoverageEnabled = 1u << 10,
kBlockCoverageEnabled = 1u << 11,
kIsAsmWasmBroken = 1u << 12,
kOnBackgroundThread = 1u << 13,
kAllowEvalCache = 1u << 14,
kRequiresInstanceMembersInitializer = 1u << 15,
kContainsAsmModule = 1u << 16,
kMightAlwaysOpt = 1u << 17,
kAllowLazyCompile = 1u << 18,
kAllowNativeSyntax = 1u << 19,
kAllowHarmonyPublicFields = 1u << 20,
kAllowHarmonyStaticFields = 1u << 21,
kAllowHarmonyDynamicImport = 1u << 22,
kAllowHarmonyImportMeta = 1u << 23,
kAllowHarmonyOptionalChaining = 1u << 24,
kAllowHarmonyPrivateFields = 1u << 25,
kAllowHarmonyPrivateMethods = 1u << 26,
kIsOneshotIIFE = 1u << 27,
kCollectSourcePositions = 1u << 28,
kAllowHarmonyNullish = 1u << 29,
kAllowHarmonyTopLevelAwait = 1u << 30,
kREPLMode = 1u << 31,
};
//------------- Inputs to parsing and scope analysis -----------------------
......
......@@ -440,6 +440,7 @@ void Parser::InitializeEmptyScopeChain(ParseInfo* info) {
DeclarationScope* script_scope = NewScriptScope();
info->set_script_scope(script_scope);
original_scope_ = script_scope;
if (info->is_repl_mode()) script_scope->set_is_repl_mode_scope();
}
void Parser::DeserializeScopeChain(
......
......@@ -633,11 +633,18 @@ static Object FindNameClash(Isolate* isolate, Handle<ScopeInfo> scope_info,
ScriptContextTable::LookupResult lookup;
if (ScriptContextTable::Lookup(isolate, *script_context, *name, &lookup)) {
if (IsLexicalVariableMode(mode) || IsLexicalVariableMode(lookup.mode)) {
// ES#sec-globaldeclarationinstantiation 5.b:
// If envRec.HasLexicalDeclaration(name) is true, throw a SyntaxError
// exception.
return ThrowRedeclarationError(isolate, name,
RedeclarationType::kSyntaxError);
Handle<Context> context = ScriptContextTable::GetContext(
isolate, script_context, lookup.context_index);
// If we are trying to re-declare a REPL-mode let as a let, allow it.
if (!(mode == VariableMode::kLet && lookup.mode == VariableMode::kLet &&
scope_info->IsReplModeScope() &&
context->scope_info().IsReplModeScope())) {
// ES#sec-globaldeclarationinstantiation 5.b:
// If envRec.HasLexicalDeclaration(name) is true, throw a SyntaxError
// exception.
return ThrowRedeclarationError(isolate, name,
RedeclarationType::kSyntaxError);
}
}
}
......@@ -991,5 +998,26 @@ RUNTIME_FUNCTION(Runtime_StoreLookupSlot_SloppyHoisting) {
LanguageMode::kSloppy, lookup_flags));
}
RUNTIME_FUNCTION(Runtime_StoreGlobalNoHoleCheckForReplLet) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_ARG_HANDLE_CHECKED(String, name, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, value, 1);
Handle<Context> native_context = isolate->native_context();
Handle<ScriptContextTable> script_contexts(
native_context->script_context_table(), isolate);
ScriptContextTable::LookupResult lookup_result;
bool found = ScriptContextTable::Lookup(isolate, *script_contexts, *name,
&lookup_result);
CHECK(found);
Handle<Context> script_context = ScriptContextTable::GetContext(
isolate, script_contexts, lookup_result.context_index);
script_context->set(lookup_result.slot_index, *value);
return *value;
}
} // namespace internal
} // namespace v8
......@@ -13,6 +13,7 @@
#include "src/codegen/compiler.h"
#include "src/codegen/pending-optimization-table.h"
#include "src/compiler-dispatcher/optimizing-compile-dispatcher.h"
#include "src/debug/debug-evaluate.h"
#include "src/deoptimizer/deoptimizer.h"
#include "src/execution/arguments-inl.h"
#include "src/execution/frames-inl.h"
......@@ -208,6 +209,17 @@ RUNTIME_FUNCTION(Runtime_RunningInSimulator) {
#endif
}
RUNTIME_FUNCTION(Runtime_RuntimeEvaluateREPL) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(String, source, 0);
Handle<Object> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result, DebugEvaluate::GlobalREPL(isolate, source));
return *result;
}
RUNTIME_FUNCTION(Runtime_ICsAreEnabled) {
SealHandleScope shs(isolate);
DCHECK_EQ(0, args.length());
......
......@@ -380,30 +380,31 @@ namespace internal {
F(StringReplaceNonGlobalRegExpWithFunction, 3, 1) \
F(StringSplit, 3, 1)
#define FOR_EACH_INTRINSIC_SCOPES(F, I) \
F(DeclareEvalFunction, 2, 1) \
F(DeclareEvalVar, 1, 1) \
F(DeclareGlobals, 3, 1) \
F(DeleteLookupSlot, 1, 1) \
F(LoadLookupSlot, 1, 1) \
F(LoadLookupSlotInsideTypeof, 1, 1) \
F(NewArgumentsElements, 3, 1) \
\
F(NewClosure, 2, 1) \
F(NewClosure_Tenured, 2, 1) \
F(NewFunctionContext, 1, 1) \
F(NewRestParameter, 1, 1) \
F(NewScriptContext, 1, 1) \
F(NewSloppyArguments, 3, 1) \
F(NewSloppyArguments_Generic, 1, 1) \
F(NewStrictArguments, 1, 1) \
F(PushBlockContext, 1, 1) \
F(PushCatchContext, 2, 1) \
F(PushModuleContext, 2, 1) \
F(PushWithContext, 2, 1) \
F(StoreLookupSlot_Sloppy, 2, 1) \
F(StoreLookupSlot_SloppyHoisting, 2, 1) \
F(StoreLookupSlot_Strict, 2, 1) \
#define FOR_EACH_INTRINSIC_SCOPES(F, I) \
F(DeclareEvalFunction, 2, 1) \
F(DeclareEvalVar, 1, 1) \
F(DeclareGlobals, 3, 1) \
F(DeleteLookupSlot, 1, 1) \
F(LoadLookupSlot, 1, 1) \
F(LoadLookupSlotInsideTypeof, 1, 1) \
F(NewArgumentsElements, 3, 1) \
\
F(NewClosure, 2, 1) \
F(NewClosure_Tenured, 2, 1) \
F(NewFunctionContext, 1, 1) \
F(NewRestParameter, 1, 1) \
F(NewScriptContext, 1, 1) \
F(NewSloppyArguments, 3, 1) \
F(NewSloppyArguments_Generic, 1, 1) \
F(NewStrictArguments, 1, 1) \
F(PushBlockContext, 1, 1) \
F(PushCatchContext, 2, 1) \
F(PushModuleContext, 2, 1) \
F(PushWithContext, 2, 1) \
F(StoreGlobalNoHoleCheckForReplLet, 2, 1) \
F(StoreLookupSlot_Sloppy, 2, 1) \
F(StoreLookupSlot_SloppyHoisting, 2, 1) \
F(StoreLookupSlot_Strict, 2, 1) \
F(ThrowConstAssignError, 0, 1)
#define FOR_EACH_INTRINSIC_STRINGS(F, I) \
......@@ -511,6 +512,7 @@ namespace internal {
F(PrintWithNameForAssert, 2, 1) \
F(RedirectToWasmInterpreter, 2, 1) \
F(RunningInSimulator, 0, 1) \
F(RuntimeEvaluateREPL, 1, 1) \
F(SerializeWasmModule, 1, 1) \
F(SetAllocationTimeout, -1 /* 2 || 3 */, 1) \
F(SetForceSlowPath, 1, 1) \
......
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Test that *local* debug-evaluate properly works for REPL 'let'
// re-declarations.
Debug = debug.Debug
let exception = null;
function listener(event, exec_state, event_data, data) {
if (event != Debug.DebugEvent.Break) return;
try {
assertEquals(42, exec_state.frame(0).evaluate("x").value());
} catch (e) {
exception = e;
print(e + e.stack);
}
}
Debug.setListener(listener);
// First script introduces a let-binding 'x'. The value of
// 'x' lives in the ScriptContext of this REPL script.
Debug.evaluateGlobalREPL('let x = 21;');
// The second script re-declares 'x', but then breaks and
// evaluates x.
Debug.evaluateGlobalREPL(`
let x = 42;
(function foo() {
debugger;
})();
`);
Debug.setListener(null);
assertNull(exception);
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Flags: --opt
Debug = debug.Debug
const evaluate = Debug.evaluateGlobalREPL;
const evaluateNonREPL = (source) => Debug.evaluateGlobal(source, false).value();
// Test that a let declared variable 'x' bound by an optimized function is
// updated properly.
evaluate('let x = 42;');
evaluate('function foo() { return x; }');
%PrepareFunctionForOptimization(foo);
foo();
foo();
%OptimizeFunctionOnNextCall(foo);
assertEquals(42, foo());
assertOptimized(foo);
evaluate('let x = 21');
assertEquals(21, foo());
// Test that we do not throw a 'use before declaration error' and
// that the optimized code does not load the hole from the wrong
// script context.
evaluate('x; let x;');
assertEquals(undefined, foo());
This diff is collapsed.
......@@ -634,6 +634,10 @@ class DebugWrapper {
return this.reconstructRemoteObject(result);
}
evaluateGlobalREPL(expr) {
return %RuntimeEvaluateREPL(expr);
}
eventDataException(params) {
switch (params.data.type) {
case "string": {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment