Commit 08f68102 authored by Leszek Swirski's avatar Leszek Swirski Committed by Commit Bot

[parser] Allow declaring variables without a proxy

Declare Variables with a name and position, rather than by passing
through a VariableProxy. This allows us to not create dummy proxies
for things like function declarations, and allows us to consider those
declarations unused.

As a side-effect, we also have to check if a variable is unused in the
bytecode generator (as it will no longer be allocated), and we end up
skip generating code/SFIs for dead variables/functions.

Change-Id: I4c2c872473f23e124f9456b4b92f87159658f8e0
Reviewed-on: https://chromium-review.googlesource.com/c/1414916
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Reviewed-by: 's avatarRoss McIlroy <rmcilroy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59088}
parent 2452e598
......@@ -558,13 +558,13 @@ void DeclarationScope::HoistSloppyBlockFunctions(AstNodeFactory* factory) {
const AstRawString* name = index_and_name.second;
if (factory) {
DCHECK(!is_being_lazily_parsed_);
VariableProxy* proxy = factory->NewVariableProxy(name, NORMAL_VARIABLE);
auto declaration = factory->NewVariableDeclaration(kNoSourcePosition);
bool was_added;
// Based on the preceding checks, it doesn't matter what we pass as
// sloppy_mode_block_scope_function_redefinition.
bool ok = true;
DeclareVariable(declaration, proxy, VariableMode::kVar, NORMAL_VARIABLE,
DeclareVariable(declaration, name, kNoSourcePosition, VariableMode::kVar,
NORMAL_VARIABLE,
Variable::DefaultInitializationFlag(VariableMode::kVar),
&was_added, nullptr, &ok);
DCHECK(ok);
......@@ -949,15 +949,29 @@ Variable* Scope::DeclareLocal(const AstRawString* name, VariableMode mode,
mode == VariableMode::kVar || mode == VariableMode::kLet ||
mode == VariableMode::kConst);
DCHECK(!GetDeclarationScope()->was_lazily_parsed());
return Declare(zone(), name, mode, kind, init_flag, kNotAssigned, was_added);
Variable* var =
Declare(zone(), name, mode, kind, init_flag, kNotAssigned, was_added);
// Pessimistically assume that top-level variables will be assigned and used.
//
// Top-level variables in a script can be accessed by other scripts or even
// become global properties. While this does not apply to top-level variables
// in a module (assuming they are not exported), we must still mark these as
// assigned because they might be accessed by a lazily parsed top-level
// function, which, for efficiency, we preparse without variable tracking.
if (is_script_scope() || is_module_scope()) {
if (mode != VariableMode::kConst) var->set_maybe_assigned();
var->set_is_used();
}
return var;
}
// TODO(leszeks): Avoid passing the proxy into here, passing the raw_name alone
// instead.
Variable* Scope::DeclareVariable(
Declaration* declaration, VariableProxy* proxy, VariableMode mode,
VariableKind kind, InitializationFlag init, bool* was_added,
bool* sloppy_mode_block_scope_function_redefinition, bool* ok) {
Declaration* declaration, const AstRawString* name, int pos,
VariableMode mode, VariableKind kind, InitializationFlag init,
bool* was_added, bool* sloppy_mode_block_scope_function_redefinition,
bool* ok) {
DCHECK(IsDeclaredVariableMode(mode));
DCHECK(!already_resolved_);
DCHECK(!GetDeclarationScope()->is_being_lazily_parsed());
......@@ -965,7 +979,7 @@ Variable* Scope::DeclareVariable(
if (mode == VariableMode::kVar && !is_declaration_scope()) {
return GetDeclarationScope()->DeclareVariable(
declaration, proxy, mode, kind, init, was_added,
declaration, name, pos, mode, kind, init, was_added,
sloppy_mode_block_scope_function_redefinition, ok);
}
DCHECK(!is_catch_scope());
......@@ -973,19 +987,7 @@ Variable* Scope::DeclareVariable(
DCHECK(is_declaration_scope() ||
(IsLexicalVariableMode(mode) && is_block_scope()));
DCHECK_NOT_NULL(proxy->raw_name());
const AstRawString* name = proxy->raw_name();
// Pessimistically assume that top-level variables will be assigned.
//
// Top-level variables in a script can be accessed by other scripts or even
// become global properties. While this does not apply to top-level variables
// in a module (assuming they are not exported), we must still mark these as
// assigned because they might be accessed by a lazily parsed top-level
// function, which, for efficiency, we preparse without variable tracking.
if (is_script_scope() || is_module_scope()) {
if (mode != VariableMode::kConst) proxy->set_is_assigned();
}
DCHECK_NOT_NULL(name);
Variable* var = LookupLocal(name);
// Declare the variable in the declaration scope.
......@@ -998,7 +1000,9 @@ Variable* Scope::DeclareVariable(
// The proxy is bound to a lookup variable to force a dynamic declaration
// using the DeclareEvalVar or DeclareEvalFunction runtime functions.
DCHECK_EQ(NORMAL_VARIABLE, kind);
var = NonLocal(proxy->raw_name(), VariableMode::kDynamic);
var = NonLocal(name, VariableMode::kDynamic);
// Mark the var as used in case anyone outside the eval wants to use it.
var->set_is_used();
} else {
// Declare the name.
var = DeclareLocal(name, mode, kind, was_added, init);
......@@ -1051,7 +1055,6 @@ Variable* Scope::DeclareVariable(
// lead to repeated DeclareEvalVar or DeclareEvalFunction calls.
decls_.Add(declaration);
declaration->set_var(var);
proxy->BindTo(var);
return var;
}
......@@ -2048,8 +2051,8 @@ bool Scope::ResolveVariablesRecursively(ParseInfo* info) {
DCHECK(proxy->IsPrivateName());
return false;
}
var->set_is_used();
if (!var->is_dynamic()) {
var->set_is_used();
var->ForceContextAllocation();
if (proxy->is_assigned()) var->set_maybe_assigned();
}
......
......@@ -225,8 +225,8 @@ class V8_EXPORT_PRIVATE Scope : public NON_EXPORTED_BASE(ZoneObject) {
VariableKind kind, bool* was_added,
InitializationFlag init_flag = kCreatedInitialized);
Variable* DeclareVariable(Declaration* declaration, VariableProxy* proxy,
VariableMode mode, VariableKind kind,
Variable* DeclareVariable(Declaration* declaration, const AstRawString* name,
int pos, VariableMode mode, VariableKind kind,
InitializationFlag init, bool* was_added,
bool* sloppy_mode_block_scope_function_redefinition,
bool* ok);
......
......@@ -1221,6 +1221,9 @@ void BytecodeGenerator::VisitBlockDeclarationsAndStatements(Block* stmt) {
void BytecodeGenerator::VisitVariableDeclaration(VariableDeclaration* decl) {
Variable* variable = decl->var();
// Unused variables don't need to be visited.
if (!variable->is_used()) return;
switch (variable->location()) {
case VariableLocation::UNALLOCATED: {
DCHECK(!variable->binding_needs_init());
......@@ -1275,6 +1278,9 @@ void BytecodeGenerator::VisitFunctionDeclaration(FunctionDeclaration* decl) {
DCHECK(variable->mode() == VariableMode::kLet ||
variable->mode() == VariableMode::kVar ||
variable->mode() == VariableMode::kDynamic);
// Unused variables don't need to be visited.
if (!variable->is_used()) return;
switch (variable->location()) {
case VariableLocation::UNALLOCATED: {
FeedbackSlot slot =
......
......@@ -274,7 +274,7 @@ class VariableDeclarationParsingScope : public ExpressionScope<Types> {
VariableProxy* Declare(VariableProxy* proxy) {
VariableKind kind = NORMAL_VARIABLE;
bool was_added;
this->parser()->DeclareVariable(
this->parser()->DeclareAndBindVariable(
proxy, kind, mode_, Variable::DefaultInitializationFlag(mode_),
this->parser()->scope(), &was_added, proxy->position());
if (was_added &&
......@@ -346,7 +346,7 @@ class ParameterDeclarationParsingScope : public ExpressionScope<Types> {
VariableKind kind = PARAMETER_VARIABLE;
VariableMode mode = VariableMode::kVar;
bool was_added;
this->parser()->DeclareVariable(
this->parser()->DeclareAndBindVariable(
proxy, kind, mode, Variable::DefaultInitializationFlag(mode),
this->parser()->scope(), &was_added, proxy->position());
if (!has_duplicate() && !was_added) {
......@@ -670,9 +670,9 @@ class ArrowHeadParsingScope : public ExpressionParsingScope<Types> {
for (int i = 0; i < this->variable_list()->length(); i++) {
VariableProxy* proxy = this->variable_list()->at(i);
bool was_added;
this->parser()->DeclareVariable(proxy, kind, mode,
Variable::DefaultInitializationFlag(mode),
result, &was_added, proxy->position());
this->parser()->DeclareAndBindVariable(
proxy, kind, mode, Variable::DefaultInitializationFlag(mode), result,
&was_added, proxy->position());
if (!was_added) {
ExpressionScope<Types>::Report(proxy->location(),
MessageTemplate::kParamDupe);
......
This diff is collapsed.
......@@ -410,17 +410,23 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) {
// Implement sloppy block-scoped functions, ES2015 Annex B 3.3
void InsertSloppyBlockFunctionVarBindings(DeclarationScope* scope);
VariableProxy* DeclareVariable(const AstRawString* name, VariableMode mode,
int pos);
VariableProxy* DeclareVariable(const AstRawString* name, VariableMode mode,
InitializationFlag init, int pos);
void DeclareVariable(VariableProxy* proxy, VariableKind kind,
VariableMode mode, InitializationFlag init,
Scope* declaration_scope, bool* added, int begin,
int end = kNoSourcePosition);
void Declare(Declaration* declaration, VariableProxy* proxy,
void DeclareUnboundVariable(const AstRawString* name, VariableMode mode,
InitializationFlag init, int pos);
V8_WARN_UNUSED_RESULT
VariableProxy* DeclareBoundVariable(const AstRawString* name,
VariableMode mode, int pos);
void DeclareAndBindVariable(VariableProxy* proxy, VariableKind kind,
VariableMode mode, InitializationFlag init,
Scope* declaration_scope, bool* was_added,
int begin, int end = kNoSourcePosition);
V8_WARN_UNUSED_RESULT
Variable* DeclareVariable(const AstRawString* name, VariableKind kind,
VariableMode mode, InitializationFlag init,
Scope* declaration_scope, bool* was_added,
int begin, int end = kNoSourcePosition);
void Declare(Declaration* declaration, const AstRawString* name,
VariableKind kind, VariableMode mode, InitializationFlag init,
Scope* declaration_scope, bool* added,
Scope* declaration_scope, bool* was_added, int var_begin_pos,
int var_end_pos = kNoSourcePosition);
bool TargetStackContainsLabel(const AstRawString* label);
......
......@@ -1087,11 +1087,18 @@ class PreParser : public ParserBase<PreParser> {
return PreParserStatement::Default();
}
void DeclareVariable(VariableProxy* proxy, VariableKind kind,
void DeclareVariable(const AstRawString* name, VariableKind kind,
VariableMode mode, InitializationFlag init, Scope* scope,
bool* was_added, int position) {
DeclareVariableName(name, mode, scope, was_added, position, kind);
}
void DeclareAndBindVariable(const VariableProxy* proxy, VariableKind kind,
VariableMode mode, InitializationFlag init,
Scope* scope, bool* was_added, int position) {
DeclareVariableName(proxy->raw_name(), mode, scope, was_added, position,
kind);
// Don't bother actually binding the proxy.
}
void DeclareVariableName(const AstRawString* name, VariableMode mode,
......
......@@ -9,16 +9,14 @@ wrap: yes
snippet: "
const x = 10; function f1() {return x;}
"
frame size: 2
frame size: 1
parameter count: 1
bytecode array length: 21
bytecode array length: 15
bytecodes: [
B(CreateFunctionContext), U8(0), U8(1),
B(PushContext), R(1),
B(PushContext), R(0),
B(LdaTheHole),
B(StaCurrentContextSlot), U8(4),
B(CreateClosure), U8(1), U8(0), U8(2),
B(Star), R(0),
/* 30 E> */ B(StackCheck),
/* 44 S> */ B(LdaSmi), I8(10),
/* 44 E> */ B(StaCurrentContextSlot), U8(4),
......@@ -27,7 +25,6 @@ bytecodes: [
]
constant pool: [
SCOPE_INFO_TYPE,
SHARED_FUNCTION_INFO_TYPE,
]
handlers: [
]
......@@ -36,16 +33,14 @@ handlers: [
snippet: "
const x = 10; function f1() {return x;} return x;
"
frame size: 2
frame size: 1
parameter count: 1
bytecode array length: 22
bytecode array length: 16
bytecodes: [
B(CreateFunctionContext), U8(0), U8(1),
B(PushContext), R(1),
B(PushContext), R(0),
B(LdaTheHole),
B(StaCurrentContextSlot), U8(4),
B(CreateClosure), U8(1), U8(0), U8(2),
B(Star), R(0),
/* 30 E> */ B(StackCheck),
/* 44 S> */ B(LdaSmi), I8(10),
/* 44 E> */ B(StaCurrentContextSlot), U8(4),
......@@ -54,7 +49,6 @@ bytecodes: [
]
constant pool: [
SCOPE_INFO_TYPE,
SHARED_FUNCTION_INFO_TYPE,
]
handlers: [
]
......@@ -63,21 +57,19 @@ handlers: [
snippet: "
const x = (x = 20); function f1() {return x;}
"
frame size: 3
frame size: 2
parameter count: 1
bytecode array length: 32
bytecode array length: 26
bytecodes: [
B(CreateFunctionContext), U8(0), U8(1),
B(PushContext), R(1),
B(PushContext), R(0),
B(LdaTheHole),
B(StaCurrentContextSlot), U8(4),
B(CreateClosure), U8(1), U8(0), U8(2),
B(Star), R(0),
/* 30 E> */ B(StackCheck),
/* 44 S> */ B(LdaSmi), I8(20),
B(Star), R(2),
B(Star), R(1),
B(LdaCurrentContextSlot), U8(4),
/* 47 E> */ B(ThrowReferenceErrorIfHole), U8(2),
/* 47 E> */ B(ThrowReferenceErrorIfHole), U8(1),
B(CallRuntime), U16(Runtime::kThrowConstAssignError), R(0), U8(0),
/* 44 E> */ B(StaCurrentContextSlot), U8(4),
B(LdaUndefined),
......@@ -85,7 +77,6 @@ bytecodes: [
]
constant pool: [
SCOPE_INFO_TYPE,
SHARED_FUNCTION_INFO_TYPE,
ONE_BYTE_INTERNALIZED_STRING_TYPE ["x"],
]
handlers: [
......@@ -95,16 +86,14 @@ handlers: [
snippet: "
const x = 10; x = 20; function f1() {return x;}
"
frame size: 2
frame size: 1
parameter count: 1
bytecode array length: 28
bytecode array length: 22
bytecodes: [
B(CreateFunctionContext), U8(0), U8(1),
B(PushContext), R(1),
B(PushContext), R(0),
B(LdaTheHole),
B(StaCurrentContextSlot), U8(4),
B(CreateClosure), U8(1), U8(0), U8(2),
B(Star), R(0),
/* 30 E> */ B(StackCheck),
/* 44 S> */ B(LdaSmi), I8(10),
/* 44 E> */ B(StaCurrentContextSlot), U8(4),
......@@ -115,7 +104,6 @@ bytecodes: [
]
constant pool: [
SCOPE_INFO_TYPE,
SHARED_FUNCTION_INFO_TYPE,
]
handlers: [
]
......
......@@ -9,16 +9,14 @@ wrap: yes
snippet: "
let x = 10; function f1() {return x;}
"
frame size: 2
frame size: 1
parameter count: 1
bytecode array length: 21
bytecode array length: 15
bytecodes: [
B(CreateFunctionContext), U8(0), U8(1),
B(PushContext), R(1),
B(PushContext), R(0),
B(LdaTheHole),
B(StaCurrentContextSlot), U8(4),
B(CreateClosure), U8(1), U8(0), U8(2),
B(Star), R(0),
/* 30 E> */ B(StackCheck),
/* 42 S> */ B(LdaSmi), I8(10),
/* 42 E> */ B(StaCurrentContextSlot), U8(4),
......@@ -27,7 +25,6 @@ bytecodes: [
]
constant pool: [
SCOPE_INFO_TYPE,
SHARED_FUNCTION_INFO_TYPE,
]
handlers: [
]
......@@ -36,16 +33,14 @@ handlers: [
snippet: "
let x = 10; function f1() {return x;} return x;
"
frame size: 2
frame size: 1
parameter count: 1
bytecode array length: 22
bytecode array length: 16
bytecodes: [
B(CreateFunctionContext), U8(0), U8(1),
B(PushContext), R(1),
B(PushContext), R(0),
B(LdaTheHole),
B(StaCurrentContextSlot), U8(4),
B(CreateClosure), U8(1), U8(0), U8(2),
B(Star), R(0),
/* 30 E> */ B(StackCheck),
/* 42 S> */ B(LdaSmi), I8(10),
/* 42 E> */ B(StaCurrentContextSlot), U8(4),
......@@ -54,7 +49,6 @@ bytecodes: [
]
constant pool: [
SCOPE_INFO_TYPE,
SHARED_FUNCTION_INFO_TYPE,
]
handlers: [
]
......@@ -63,22 +57,20 @@ handlers: [
snippet: "
let x = (x = 20); function f1() {return x;}
"
frame size: 3
frame size: 2
parameter count: 1
bytecode array length: 31
bytecode array length: 25
bytecodes: [
B(CreateFunctionContext), U8(0), U8(1),
B(PushContext), R(1),
B(PushContext), R(0),
B(LdaTheHole),
B(StaCurrentContextSlot), U8(4),
B(CreateClosure), U8(1), U8(0), U8(2),
B(Star), R(0),
/* 30 E> */ B(StackCheck),
/* 42 S> */ B(LdaSmi), I8(20),
B(Star), R(2),
B(Star), R(1),
B(LdaCurrentContextSlot), U8(4),
/* 45 E> */ B(ThrowReferenceErrorIfHole), U8(2),
B(Ldar), R(2),
/* 45 E> */ B(ThrowReferenceErrorIfHole), U8(1),
B(Ldar), R(1),
B(StaCurrentContextSlot), U8(4),
/* 42 E> */ B(StaCurrentContextSlot), U8(4),
B(LdaUndefined),
......@@ -86,7 +78,6 @@ bytecodes: [
]
constant pool: [
SCOPE_INFO_TYPE,
SHARED_FUNCTION_INFO_TYPE,
ONE_BYTE_INTERNALIZED_STRING_TYPE ["x"],
]
handlers: [
......@@ -96,16 +87,14 @@ handlers: [
snippet: "
let x = 10; x = 20; function f1() {return x;}
"
frame size: 2
frame size: 1
parameter count: 1
bytecode array length: 25
bytecode array length: 19
bytecodes: [
B(CreateFunctionContext), U8(0), U8(1),
B(PushContext), R(1),
B(PushContext), R(0),
B(LdaTheHole),
B(StaCurrentContextSlot), U8(4),
B(CreateClosure), U8(1), U8(0), U8(2),
B(Star), R(0),
/* 30 E> */ B(StackCheck),
/* 42 S> */ B(LdaSmi), I8(10),
/* 42 E> */ B(StaCurrentContextSlot), U8(4),
......@@ -116,7 +105,6 @@ bytecodes: [
]
constant pool: [
SCOPE_INFO_TYPE,
SHARED_FUNCTION_INFO_TYPE,
]
handlers: [
]
......
......@@ -3615,7 +3615,7 @@ static void TestMaybeAssigned(Input input, const char* variable, bool module,
}
CHECK_NOT_NULL(var);
CHECK(var->is_used());
CHECK_IMPLIES(input.assigned, var->is_used());
STATIC_ASSERT(true == i::kMaybeAssigned);
CHECK_EQ(input.assigned, var->maybe_assigned() == i::kMaybeAssigned);
}
......
// 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: --no-always-opt --no-stress-opt
Debug = debug.Debug
var exception = null;
function listener(event, exec_state, event_data, data) {
if (event != Debug.DebugEvent.Break) return;
try {
// Evaluating the live function should succeed.
assertEquals(exec_state.frame(0).evaluate("live()").value(), 1);
// Evaluating the dead function should fail.
assertThrows(()=>exec_state.frame(0).evaluate("dead()"), ReferenceError);
} catch (e) {
exception = e;
print(e + e.stack);
}
}
Debug.setListener(listener);
(function() {
"use strict";
function live() { return 1; }
function dead() { return 2; }
// Use 'foo' to make it non-dead.
live;
debugger;
})();
Debug.setListener(null);
assertNull(exception);
......@@ -33,6 +33,8 @@ Debug.setListener(listener);
a *= 2;
e *= 2;
}
// Make sure bar is 'used' so that it is visible to the debugger.
bar;
debugger;
assertEquals(5, a);
assertEquals(7, e);
......
......@@ -32,6 +32,8 @@ Debug.setListener(listener);
function* generator(a, b) {
function set_a_to_5() { a = 5 }
// Make sure set_a_to_5 is 'used' so that it is visible to the debugger.
set_a_to_5;
var b = 3; // Shadows a parameter.
debugger;
yield a;
......
......@@ -37,11 +37,16 @@ function f(e, x) {
// and 'e' binds to the exception.
function write_0(v) { e = v }
function write_1(v) { x = v }
// Make sure write_0 and write_1 are 'used' so that they are visible to the
// debugger.
write_0, write_1;
debugger;
assertEquals("foo", e); // overwritten by the debugger
}
assertEquals("argument", e); // debugger did not overwrite
function write_2(v) { e = v }
// Make sure write_2 is 'used' so that it is visible to the debugger.
write_2;
debugger;
assertEquals("bar", e);
assertEquals("modified", x);
......
......@@ -970,17 +970,6 @@ Running test: testPreciseCountCoveragePartial
}
]
}
[6] : {
functionName : nested_4
isBlockCoverage : false
ranges : [
[0] : {
count : 0
endOffset : 201
startOffset : 179
}
]
}
]
scriptId : <scriptId>
url : testPreciseCountCoveragePartial
......
......@@ -753,17 +753,6 @@ Running test: testPreciseCountCoveragePartial
}
]
}
[6] : {
functionName : nested_4
isBlockCoverage : false
ranges : [
[0] : {
count : 0
endOffset : 201
startOffset : 179
}
]
}
]
scriptId : <scriptId>
url : testPreciseCountCoveragePartial
......
......@@ -267,7 +267,11 @@ function foo6() { Promise.resolve().then(() => 42^) }
Running test: arrowFunctionReturn
#() => #239#
#
function foo() { function boo() { #return 239# } #}
function foo() { function boo() { return 239 } #}
#
function foo() { function boo() { #return 239# }; #boo #}
#
function foo() { let boo = #function() { #return 239# }; #}
#
#() => { #239 #}
#
......
......@@ -135,6 +135,8 @@ function foo6() { Promise.resolve().then(() => 42) }
checkSource('() => 239\n', { lineNumber: 0, columnNumber: 0 })
.then(() => checkSource('function foo() { function boo() { return 239 } }\n', { lineNumber: 0, columnNumber: 0 }))
.then(() => checkSource('function foo() { function boo() { return 239 }; boo }\n', { lineNumber: 0, columnNumber: 0 }))
.then(() => checkSource('function foo() { let boo = function() { return 239 }; }\n', { lineNumber: 0, columnNumber: 0 }))
.then(() => checkSource('() => { 239 }\n', { lineNumber: 0, columnNumber: 0 }))
.then(() => checkSource('function foo() { 239 }\n', { lineNumber: 0, columnNumber: 0 }))
// TODO(kozyatinskiy): lineNumber for return position should be only 9, not 8.
......
......@@ -10,6 +10,7 @@ function testFunction()
var o = 0;
function f() { return 1; }
function g() { o = 2; return o; }
f,g;
debugger;
}
//# sourceURL=foo.js`);
......
*%(basename)s:8: SyntaxError: Identifier '*default*' has already been declared
export default x = 1;
^
^^^^^^^^^^^^^
SyntaxError: Identifier '*default*' has already been declared
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