Reland: [modules] Properly initialize declared variables.

Before evaluating a module, all variables declared at the top-level
in _any_ of the modules in the dependency graph must be initialized.
This is observable because a module A can access a variable imported
from module B (e.g. a function) at a point when module B's body hasn't
been evaluated yet.

We achieve this by implementing modules internally as generators with
two states (not initialized, initialized).

R=adamk@chromium.org
BUG=v8:1569
CQ_INCLUDE_TRYBOTS=master.tryserver.v8:v8_win_dbg

Committed: https://crrev.com/f4dfb6fbe1cdd9a0f287a1a9c496e1f69f6f5d20
Committed: https://crrev.com/8c52a411583e870bd5ed100864caa58f491c5d88
Review-Url: https://codereview.chromium.org/2375793002
Cr-Original-Original-Commit-Position: refs/heads/master@{#39871}
Cr-Original-Commit-Position: refs/heads/master@{#39892}
Cr-Commit-Position: refs/heads/master@{#39900}
This commit is contained in:
neis 2016-09-30 00:53:43 -07:00 committed by Commit bot
parent db99bdff76
commit 427242326c
16 changed files with 167 additions and 49 deletions

View File

@ -152,7 +152,8 @@ DeclarationScope::DeclarationScope(Zone* zone, Scope* outer_scope,
ModuleScope::ModuleScope(DeclarationScope* script_scope,
AstValueFactory* ast_value_factory)
: DeclarationScope(ast_value_factory->zone(), script_scope, MODULE_SCOPE) {
: DeclarationScope(ast_value_factory->zone(), script_scope, MODULE_SCOPE,
kModule) {
Zone* zone = ast_value_factory->zone();
module_descriptor_ = new (zone) ModuleDescriptor(zone);
set_language_mode(STRICT);

View File

@ -703,6 +703,12 @@ void Genesis::CreateIteratorMaps(Handle<JSFunction> empty) {
SimpleInstallFunction(generator_object_prototype, "throw",
Builtins::kGeneratorPrototypeThrow, 1, true);
// Internal version of generator_prototype_next, flagged as non-native.
Handle<JSFunction> generator_next_internal =
SimpleCreateFunction(isolate(), factory()->next_string(),
Builtins::kGeneratorPrototypeNext, 1, true);
native_context()->set_generator_next_internal(*generator_next_internal);
// Create maps for generator functions and their prototypes. Store those
// maps in the native context. The "prototype" property descriptor is
// writable, non-enumerable, and non-configurable (as per ES6 draft

View File

@ -36,6 +36,7 @@ enum ContextLookupFlags {
#define NATIVE_CONTEXT_INTRINSIC_FUNCTIONS(V) \
V(IS_ARRAYLIKE, JSFunction, is_arraylike) \
V(GENERATOR_NEXT_INTERNAL, JSFunction, generator_next_internal) \
V(GET_TEMPLATE_CALL_SITE_INDEX, JSFunction, get_template_call_site) \
V(MAKE_ERROR_INDEX, JSFunction, make_error) \
V(MAKE_RANGE_ERROR_INDEX, JSFunction, make_range_error) \

View File

@ -1078,6 +1078,7 @@ enum FunctionKind : uint16_t {
kGetterFunction = 1 << 6,
kSetterFunction = 1 << 7,
kAsyncFunction = 1 << 8,
kModule = 1 << 9,
kAccessorFunction = kGetterFunction | kSetterFunction,
kDefaultBaseConstructor = kDefaultConstructor | kBaseConstructor,
kDefaultSubclassConstructor = kDefaultConstructor | kSubclassConstructor,
@ -1091,6 +1092,7 @@ inline bool IsValidFunctionKind(FunctionKind kind) {
return kind == FunctionKind::kNormalFunction ||
kind == FunctionKind::kArrowFunction ||
kind == FunctionKind::kGeneratorFunction ||
kind == FunctionKind::kModule ||
kind == FunctionKind::kConciseMethod ||
kind == FunctionKind::kConciseGeneratorMethod ||
kind == FunctionKind::kGetterFunction ||
@ -1117,13 +1119,18 @@ inline bool IsGeneratorFunction(FunctionKind kind) {
return kind & FunctionKind::kGeneratorFunction;
}
inline bool IsModule(FunctionKind kind) {
DCHECK(IsValidFunctionKind(kind));
return kind & FunctionKind::kModule;
}
inline bool IsAsyncFunction(FunctionKind kind) {
DCHECK(IsValidFunctionKind(kind));
return kind & FunctionKind::kAsyncFunction;
}
inline bool IsResumableFunction(FunctionKind kind) {
return IsGeneratorFunction(kind) || IsAsyncFunction(kind);
return IsGeneratorFunction(kind) || IsAsyncFunction(kind) || IsModule(kind);
}
inline bool IsConciseMethod(FunctionKind kind) {

View File

@ -932,7 +932,12 @@ void BytecodeGenerator::VisitVariableDeclaration(VariableDeclaration* decl) {
break;
}
case VariableLocation::MODULE:
// Nothing to do here.
if (variable->IsExport() && variable->binding_needs_init()) {
builder()->LoadTheHole();
VisitVariableAssignment(variable, Token::INIT,
FeedbackVectorSlot::Invalid());
}
// Nothing to do for imports.
break;
}
}
@ -972,7 +977,8 @@ void BytecodeGenerator::VisitFunctionDeclaration(FunctionDeclaration* decl) {
break;
}
case VariableLocation::MODULE:
DCHECK(variable->mode() == LET);
DCHECK_EQ(variable->mode(), LET);
DCHECK(variable->IsExport());
VisitForAccumulatorValue(decl->fun());
VisitVariableAssignment(variable, Token::INIT,
FeedbackVectorSlot::Invalid());
@ -2018,6 +2024,7 @@ void BytecodeGenerator::VisitVariableLoad(Variable* variable,
.StoreAccumulatorInRegister(module_request)
.CallRuntime(Runtime::kLoadModuleImport, import_name, 2);
}
BuildHoleCheckForVariableLoad(variable);
break;
}
}

View File

@ -19906,7 +19906,7 @@ bool Module::Instantiate(Handle<Module> module, v8::Local<v8::Context> context,
}
MaybeHandle<Object> Module::Evaluate(Handle<Module> module) {
DCHECK(module->code()->IsJSFunction());
DCHECK(module->code()->IsJSFunction()); // Instantiated.
Isolate* isolate = module->GetIsolate();
@ -19914,17 +19914,28 @@ MaybeHandle<Object> Module::Evaluate(Handle<Module> module) {
if (module->evaluated()) return isolate->factory()->undefined_value();
module->set_evaluated(true);
// Initialization.
Handle<JSFunction> function(JSFunction::cast(module->code()), isolate);
DCHECK_EQ(MODULE_SCOPE, function->shared()->scope_info()->scope_type());
Handle<Object> receiver = isolate->factory()->undefined_value();
Handle<Object> argv[] = {module};
Handle<Object> generator;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, generator,
Execution::Call(isolate, function, receiver, arraysize(argv), argv),
Object);
// Recursion.
Handle<FixedArray> requested_modules(module->requested_modules(), isolate);
for (int i = 0, length = requested_modules->length(); i < length; ++i) {
Handle<Module> import(Module::cast(requested_modules->get(i)), isolate);
RETURN_ON_EXCEPTION(isolate, Evaluate(import), Object);
}
Handle<JSFunction> function(JSFunction::cast(module->code()), isolate);
DCHECK_EQ(MODULE_SCOPE, function->shared()->scope_info()->scope_type());
Handle<Object> receiver = isolate->factory()->undefined_value();
Handle<Object> argv[] = {module};
return Execution::Call(isolate, function, receiver, arraysize(argv), argv);
// Evaluation of module body.
Handle<JSFunction> resume(
isolate->native_context()->generator_next_internal(), isolate);
return Execution::Call(isolate, resume, generator, 0, nullptr);
}
} // namespace internal

View File

@ -4542,7 +4542,7 @@ class ScopeInfo : public FixedArray {
class HasSimpleParametersField
: public BitField<bool, AsmFunctionField::kNext, 1> {};
class FunctionKindField
: public BitField<FunctionKind, HasSimpleParametersField::kNext, 9> {};
: public BitField<FunctionKind, HasSimpleParametersField::kNext, 10> {};
class HasOuterScopeInfoField
: public BitField<bool, FunctionKindField::kNext, 1> {};
class IsDebugEvaluateScopeField
@ -7707,6 +7707,7 @@ class SharedFunctionInfo: public HeapObject {
kIsSetterFunction,
// byte 3
kIsAsyncFunction,
kIsModule,
kDeserialized,
kIsDeclaration,
kIsAsmWasmBroken,
@ -7728,9 +7729,11 @@ class SharedFunctionInfo: public HeapObject {
ASSERT_FUNCTION_KIND_ORDER(kBaseConstructor, kIsBaseConstructor);
ASSERT_FUNCTION_KIND_ORDER(kGetterFunction, kIsGetterFunction);
ASSERT_FUNCTION_KIND_ORDER(kSetterFunction, kIsSetterFunction);
ASSERT_FUNCTION_KIND_ORDER(kAsyncFunction, kIsAsyncFunction);
ASSERT_FUNCTION_KIND_ORDER(kModule, kIsModule);
#undef ASSERT_FUNCTION_KIND_ORDER
class FunctionKindBits : public BitField<FunctionKind, kIsArrow, 9> {};
class FunctionKindBits : public BitField<FunctionKind, kIsArrow, 10> {};
class DeoptCountBits : public BitField<int, 0, 4> {};
class OptReenableTriesBits : public BitField<int, 4, 18> {};

View File

@ -768,12 +768,13 @@ FunctionLiteral* Parser::DoParseProgram(ParseInfo* info) {
{
Scope* outer = original_scope_;
DCHECK_NOT_NULL(outer);
parsing_module_ = info->is_module();
if (info->is_eval()) {
if (!outer->is_script_scope() || is_strict(info->language_mode())) {
parsing_mode = PARSE_EAGERLY;
}
outer = NewEvalScope(outer);
} else if (info->is_module()) {
} else if (parsing_module_) {
DCHECK_EQ(outer, info->script_scope());
outer = NewModuleScope(info->script_scope());
// Never do lazy parsing in modules. If we want to support this in the
@ -793,7 +794,6 @@ FunctionLiteral* Parser::DoParseProgram(ParseInfo* info) {
ZoneList<Statement*>* body = new(zone()) ZoneList<Statement*>(16, zone());
bool ok = true;
int beg_pos = scanner()->location().beg_pos;
parsing_module_ = info->is_module();
if (parsing_module_) {
// Declare the special module parameter.
auto name = ast_value_factory()->empty_string();
@ -805,6 +805,13 @@ FunctionLiteral* Parser::DoParseProgram(ParseInfo* info) {
DCHECK(!is_duplicate);
var->AllocateTo(VariableLocation::PARAMETER, 0);
PrepareGeneratorVariables(&function_state);
Expression* initial_yield =
BuildInitialYield(kNoSourcePosition, kGeneratorFunction);
body->Add(
factory()->NewExpressionStatement(initial_yield, kNoSourcePosition),
zone());
ParseModuleItemList(body, &ok);
ok = ok &&
module()->Validate(this->scope()->AsModuleScope(),
@ -2529,6 +2536,20 @@ void Parser::ReindexLiterals(const ParserFormalParameters& parameters) {
}
}
void Parser::PrepareGeneratorVariables(FunctionState* function_state) {
// For generators, allocating variables in contexts is currently a win
// because it minimizes the work needed to suspend and resume an
// activation. The machine code produced for generators (by full-codegen)
// relies on this forced context allocation, but not in an essential way.
scope()->ForceContextAllocation();
// Calling a generator returns a generator object. That object is stored
// in a temporary variable, a definition that is used by "yield"
// expressions.
Variable* temp =
NewTemporary(ast_value_factory()->dot_generator_object_string());
function_state->set_generator_object_variable(temp);
}
FunctionLiteral* Parser::ParseFunctionLiteral(
const AstRawString* function_name, Scanner::Location function_name_location,
@ -2650,20 +2671,7 @@ FunctionLiteral* Parser::ParseFunctionLiteral(
ExpressionClassifier formals_classifier(this, &duplicate_finder);
if (is_generator) {
// For generators, allocating variables in contexts is currently a win
// because it minimizes the work needed to suspend and resume an
// activation. The machine code produced for generators (by full-codegen)
// relies on this forced context allocation, but not in an essential way.
this->scope()->ForceContextAllocation();
// Calling a generator returns a generator object. That object is stored
// in a temporary variable, a definition that is used by "yield"
// expressions. This also marks the FunctionState as a generator.
Variable* temp =
NewTemporary(ast_value_factory()->dot_generator_object_string());
function_state.set_generator_object_variable(temp);
}
if (is_generator) PrepareGeneratorVariables(&function_state);
Expect(Token::LPAREN, CHECK_OK);
int start_position = scanner()->location().beg_pos;
@ -3124,6 +3132,21 @@ Variable* Parser::PromiseVariable() {
return promise;
}
Expression* Parser::BuildInitialYield(int pos, FunctionKind kind) {
Expression* allocation = BuildCreateJSGeneratorObject(pos, kind);
VariableProxy* init_proxy =
factory()->NewVariableProxy(function_state_->generator_object_variable());
Assignment* assignment = factory()->NewAssignment(
Token::INIT, init_proxy, allocation, kNoSourcePosition);
VariableProxy* get_proxy =
factory()->NewVariableProxy(function_state_->generator_object_variable());
// The position of the yield is important for reporting the exception
// caused by calling the .throw method on a generator suspended at the
// initial yield (i.e. right after generator instantiation).
return factory()->NewYield(get_proxy, assignment, scope()->start_position(),
Yield::kOnExceptionThrow);
}
ZoneList<Statement*>* Parser::ParseEagerFunctionBody(
const AstRawString* function_name, int pos,
const ParserFormalParameters& parameters, FunctionKind kind,
@ -3176,26 +3199,10 @@ ZoneList<Statement*>* Parser::ParseEagerFunctionBody(
Block* try_block =
factory()->NewBlock(nullptr, 3, false, kNoSourcePosition);
{
Expression* allocation = BuildCreateJSGeneratorObject(pos, kind);
VariableProxy* init_proxy = factory()->NewVariableProxy(
function_state_->generator_object_variable());
Assignment* assignment = factory()->NewAssignment(
Token::INIT, init_proxy, allocation, kNoSourcePosition);
VariableProxy* get_proxy = factory()->NewVariableProxy(
function_state_->generator_object_variable());
// The position of the yield is important for reporting the exception
// caused by calling the .throw method on a generator suspended at the
// initial yield (i.e. right after generator instantiation).
Yield* yield = factory()->NewYield(get_proxy, assignment,
scope()->start_position(),
Yield::kOnExceptionThrow);
try_block->statements()->Add(
factory()->NewExpressionStatement(yield, kNoSourcePosition),
zone());
}
Expression* initial_yield = BuildInitialYield(pos, kind);
try_block->statements()->Add(
factory()->NewExpressionStatement(initial_yield, kNoSourcePosition),
zone());
ParseStatementList(try_block->statements(), Token::RBRACE, CHECK_OK);
Statement* final_return = factory()->NewReturnStatement(

View File

@ -216,6 +216,8 @@ class Parser : public ParserBase<Parser> {
return scope()->NewTemporary(name);
}
void PrepareGeneratorVariables(FunctionState* function_state);
// Limit the allowed number of local variables in a function. The hard limit
// is that offsets computed by FullCodeGenerator::StackOperand and similar
// functions are ints, and they should not overflow. In addition, accessing
@ -575,6 +577,7 @@ class Parser : public ParserBase<Parser> {
friend class InitializerRewriter;
void RewriteParameterInitializer(Expression* expr, Scope* scope);
Expression* BuildInitialYield(int pos, FunctionKind kind);
Expression* BuildCreateJSGeneratorObject(int pos, FunctionKind kind);
Expression* BuildResolvePromise(Expression* value, int pos);
Expression* BuildRejectPromise(Expression* value, int pos);

View File

@ -79,7 +79,7 @@ bytecodes: [
/* 15 S> */ B(LdrUndefined), R(0),
B(CreateArrayLiteral), U8(0), U8(0), U8(9),
B(Star), R(1),
B(CallJSRuntime), U8(140), R(0), U8(2),
B(CallJSRuntime), U8(141), R(0), U8(2),
/* 44 S> */ B(Return),
]
constant pool: [

View File

@ -0,0 +1,9 @@
// Copyright 2016 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.
//
// MODULE
// Make sure the generator resume function doesn't show up in the stack trace.
const stack = (new Error).stack;
assertEquals(2, stack.split(/\r\n|\r|\n/).length);

View File

@ -0,0 +1,9 @@
// Copyright 2016 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.
//
// MODULE
import "modules-skip-init1.js";
export function bar() { return 42 };
bar = 5;

View File

@ -0,0 +1,8 @@
// Copyright 2016 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.
//
// MODULE
import {bar} from "modules-init1.js";
assertEquals(5, bar);

View File

@ -0,0 +1,20 @@
// Copyright 2016 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.
//
// MODULE
import {check} from "modules-skip-init3.js";
assertSame(undefined, w);
assertThrows(() => x, ReferenceError);
assertThrows(() => y, ReferenceError);
assertThrows(() => z, ReferenceError);
export function* v() { return 40 }
export var w = 41;
export let x = 42;
export class y {};
export const z = "hello world";
assertTrue(check());

View File

@ -0,0 +1,6 @@
// Copyright 2012 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.
import {bar} from "modules-init1.js";
assertEquals(42, bar());

View File

@ -0,0 +1,20 @@
// Copyright 2016 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.
import {v, w, x, y, z} from "modules-init3.js";
assertEquals({value: 40, done: true}, v().next());
assertSame(undefined, w);
assertThrows(() => x, ReferenceError);
assertThrows(() => y, ReferenceError);
assertThrows(() => z, ReferenceError);
export function check() {
assertEquals({value: 40, done: true}, v().next());
assertEquals(41, w);
assertEquals(42, x);
assertEquals("y", y.name);
assertEquals("hello world", z);
return true;
}