[turbofan] Optimize import.meta

Make JSContextSpecialization constant-fold import.meta loads if the
meta object has already been created.

Most of this CL was contributed by Gus Caplan.

This is a verbatim copy of CL
https://chromium-review.googlesource.com/c/v8/v8/+/2170982
which could not be landed due to the wrong email address
being used.

TBR=verwaest@chromium.org
TBR=gsathya@chromium.org

Bug: v8:7044
Change-Id: Ief45f3082dc756265904ff500305d32717071e81
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2299375
Reviewed-by: Georg Neis <neis@chromium.org>
Commit-Queue: Georg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#68875}
This commit is contained in:
Georg Neis 2020-07-15 16:55:51 +02:00 committed by Commit Bot
parent e72702454a
commit cd718536ec
22 changed files with 320 additions and 12 deletions

View File

@ -135,6 +135,7 @@ class V8_EXPORT_PRIVATE ObjectRef {
#undef HEAP_AS_METHOD_DECL
bool IsNullOrUndefined() const;
bool IsTheHole() const;
bool BooleanValue() const;
Maybe<double> OddballToNumber() const;
@ -379,6 +380,8 @@ class ContextRef : public HeapObjectRef {
int index, SerializationPolicy policy =
SerializationPolicy::kAssumeSerialized) const;
SourceTextModuleRef GetModule(SerializationPolicy policy) const;
// We only serialize the ScopeInfo if certain Promise
// builtins are called.
void SerializeScopeInfo();
@ -860,6 +863,7 @@ class SourceTextModuleRef : public HeapObjectRef {
void Serialize();
base::Optional<CellRef> GetCell(int cell_index) const;
ObjectRef import_meta() const;
};
class TemplateObjectDescriptionRef : public HeapObjectRef {

View File

@ -25,6 +25,8 @@ Reduction JSContextSpecialization::Reduce(Node* node) {
return ReduceJSLoadContext(node);
case IrOpcode::kJSStoreContext:
return ReduceJSStoreContext(node);
case IrOpcode::kJSGetImportMeta:
return ReduceJSGetImportMeta(node);
default:
break;
}
@ -217,6 +219,62 @@ Reduction JSContextSpecialization::ReduceJSStoreContext(Node* node) {
return SimplifyJSStoreContext(node, jsgraph()->Constant(concrete), depth);
}
base::Optional<ContextRef> GetModuleContext(JSHeapBroker* broker, Node* node,
Maybe<OuterContext> maybe_context) {
size_t depth = std::numeric_limits<size_t>::max();
Node* context = NodeProperties::GetOuterContext(node, &depth);
auto find_context = [](ContextRef c) {
while (c.map().instance_type() != MODULE_CONTEXT_TYPE) {
size_t depth = 1;
c = c.previous(&depth);
CHECK_EQ(depth, 0);
}
return c;
};
switch (context->opcode()) {
case IrOpcode::kHeapConstant: {
HeapObjectRef object(broker, HeapConstantOf(context->op()));
if (object.IsContext()) {
return find_context(object.AsContext());
}
break;
}
case IrOpcode::kParameter: {
OuterContext outer;
if (maybe_context.To(&outer) && IsContextParameter(context)) {
return find_context(ContextRef(broker, outer.context));
}
break;
}
default:
break;
}
return base::Optional<ContextRef>();
}
Reduction JSContextSpecialization::ReduceJSGetImportMeta(Node* node) {
base::Optional<ContextRef> maybe_context =
GetModuleContext(broker(), node, outer());
if (!maybe_context.has_value()) return NoChange();
ContextRef context = maybe_context.value();
SourceTextModuleRef module =
context.get(Context::EXTENSION_INDEX).value().AsSourceTextModule();
ObjectRef import_meta = module.import_meta();
if (import_meta.IsJSObject()) {
Node* import_meta_const = jsgraph()->Constant(import_meta);
ReplaceWithValue(node, import_meta_const);
return Changed(import_meta_const);
} else {
DCHECK(import_meta.IsTheHole());
// The import.meta object has not yet been created. Let JSGenericLowering
// replace the operator with a runtime call.
return NoChange();
}
}
Isolate* JSContextSpecialization::isolate() const {
return jsgraph()->isolate();

View File

@ -28,7 +28,9 @@ struct OuterContext {
// Specializes a given JSGraph to a given context, potentially constant folding
// some {LoadContext} nodes or strength reducing some {StoreContext} nodes.
// Additionally, constant-folds the function parameter if {closure} is given.
// Additionally, constant-folds the function parameter if {closure} is given,
// and constant-folds import.meta loads if the corresponding object already
// exists.
//
// The context can be the incoming function context or any outer context
// thereof, as indicated by {outer}'s {distance}.
@ -53,6 +55,7 @@ class V8_EXPORT_PRIVATE JSContextSpecialization final : public AdvancedReducer {
Reduction ReduceParameter(Node* node);
Reduction ReduceJSLoadContext(Node* node);
Reduction ReduceJSStoreContext(Node* node);
Reduction ReduceJSGetImportMeta(Node* node);
Reduction SimplifyJSStoreContext(Node* node, Node* new_context,
size_t new_depth);

View File

@ -1176,6 +1176,10 @@ void JSGenericLowering::LowerJSStoreModule(Node* node) {
UNREACHABLE(); // Eliminated in typed lowering.
}
void JSGenericLowering::LowerJSGetImportMeta(Node* node) {
ReplaceWithRuntimeCall(node, Runtime::kGetImportMetaObject);
}
void JSGenericLowering::LowerJSGeneratorStore(Node* node) {
UNREACHABLE(); // Eliminated in typed lowering.
}

View File

@ -1855,11 +1855,13 @@ class SourceTextModuleData : public HeapObjectData {
void Serialize(JSHeapBroker* broker);
CellData* GetCell(JSHeapBroker* broker, int cell_index) const;
ObjectData* GetImportMeta(JSHeapBroker* broker) const;
private:
bool serialized_ = false;
ZoneVector<CellData*> imports_;
ZoneVector<CellData*> exports_;
ObjectData* import_meta_;
};
SourceTextModuleData::SourceTextModuleData(JSHeapBroker* broker,
@ -1867,7 +1869,8 @@ SourceTextModuleData::SourceTextModuleData(JSHeapBroker* broker,
Handle<SourceTextModule> object)
: HeapObjectData(broker, storage, object),
imports_(broker->zone()),
exports_(broker->zone()) {}
exports_(broker->zone()),
import_meta_(nullptr) {}
CellData* SourceTextModuleData::GetCell(JSHeapBroker* broker,
int cell_index) const {
@ -1892,6 +1895,11 @@ CellData* SourceTextModuleData::GetCell(JSHeapBroker* broker,
return cell;
}
ObjectData* SourceTextModuleData::GetImportMeta(JSHeapBroker* broker) const {
CHECK(serialized_);
return import_meta_;
}
void SourceTextModuleData::Serialize(JSHeapBroker* broker) {
if (serialized_) return;
serialized_ = true;
@ -1919,6 +1927,10 @@ void SourceTextModuleData::Serialize(JSHeapBroker* broker) {
exports_.push_back(broker->GetOrCreateData(exports->get(i))->AsCell());
}
TRACE(broker, "Copied " << exports_.size() << " exports");
DCHECK_NULL(import_meta_);
import_meta_ = broker->GetOrCreateData(module->import_meta());
TRACE(broker, "Copied import_meta");
}
class CellData : public HeapObjectData {
@ -2384,6 +2396,18 @@ base::Optional<ObjectRef> ContextRef::get(int index,
return base::nullopt;
}
SourceTextModuleRef ContextRef::GetModule(SerializationPolicy policy) const {
ContextRef current = *this;
while (current.map().instance_type() != MODULE_CONTEXT_TYPE) {
size_t depth = 1;
current = current.previous(&depth, policy);
CHECK_EQ(depth, 0);
}
return current.get(Context::EXTENSION_INDEX, policy)
.value()
.AsSourceTextModule();
}
JSHeapBroker::JSHeapBroker(
Isolate* isolate, Zone* broker_zone, bool tracing_enabled,
bool is_concurrent_inlining, bool is_native_context_independent,
@ -3811,6 +3835,11 @@ bool ObjectRef::IsNullOrUndefined() const {
return type == OddballType::kNull || type == OddballType::kUndefined;
}
bool ObjectRef::IsTheHole() const {
return IsHeapObject() &&
AsHeapObject().map().oddball_type() == OddballType::kHole;
}
bool ObjectRef::BooleanValue() const {
if (data_->should_access_heap()) {
AllowHandleDereferenceIf allow_handle_dereference(data()->kind(),
@ -3932,6 +3961,20 @@ base::Optional<CellRef> SourceTextModuleRef::GetCell(int cell_index) const {
return CellRef(broker(), cell);
}
ObjectRef SourceTextModuleRef::import_meta() const {
if (data_->should_access_heap()) {
DCHECK(data_->kind() != ObjectDataKind::kUnserializedReadOnlyHeapObject);
AllowHandleAllocationIf allow_handle_allocation(data()->kind(),
broker()->mode());
AllowHandleDereferenceIf allow_handle_dereference(data()->kind(),
broker()->mode());
return ObjectRef(broker(),
handle(object()->import_meta(), broker()->isolate()));
}
return ObjectRef(broker(),
data()->AsSourceTextModule()->GetImportMeta(broker()));
}
ObjectRef::ObjectRef(JSHeapBroker* broker, Handle<Object> object,
bool check_type)
: broker_(broker) {

View File

@ -88,6 +88,8 @@ Reduction JSIntrinsicLowering::Reduce(Node* node) {
return ReduceCall(node);
case Runtime::kInlineIncBlockCounter:
return ReduceIncBlockCounter(node);
case Runtime::kInlineGetImportMetaObject:
return ReduceGetImportMetaObject(node);
default:
break;
}
@ -350,6 +352,11 @@ Reduction JSIntrinsicLowering::ReduceIncBlockCounter(Node* node) {
kDoesNotNeedFrameState);
}
Reduction JSIntrinsicLowering::ReduceGetImportMetaObject(Node* node) {
NodeProperties::ChangeOp(node, javascript()->GetImportMeta());
return Changed(node);
}
Reduction JSIntrinsicLowering::Change(Node* node, const Operator* op, Node* a,
Node* b) {
RelaxControls(node);

View File

@ -65,6 +65,7 @@ class V8_EXPORT_PRIVATE JSIntrinsicLowering final
Reduction ReduceToString(Node* node);
Reduction ReduceCall(Node* node);
Reduction ReduceIncBlockCounter(Node* node);
Reduction ReduceGetImportMetaObject(Node* node);
Reduction Change(Node* node, const Operator* op);
Reduction Change(Node* node, const Operator* op, Node* a, Node* b);

View File

@ -1115,6 +1115,14 @@ const Operator* JSOperatorBuilder::LoadModule(int32_t cell_index) {
cell_index); // parameter
}
const Operator* JSOperatorBuilder::GetImportMeta() {
return new (zone()) Operator( // --
IrOpcode::kJSGetImportMeta, // opcode
Operator::kNoProperties, // flags
"JSGetImportMeta", // name
0, 1, 1, 1, 1, 2); // counts
}
const Operator* JSOperatorBuilder::StoreModule(int32_t cell_index) {
return zone()->New<Operator1<int32_t>>( // --
IrOpcode::kJSStoreModule, // opcode

View File

@ -954,6 +954,8 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
const Operator* LoadModule(int32_t cell_index);
const Operator* StoreModule(int32_t cell_index);
const Operator* GetImportMeta();
const Operator* HasInPrototypeChain();
const Operator* InstanceOf(const FeedbackSource& feedback);
const Operator* OrdinaryHasInstance();

View File

@ -217,6 +217,7 @@
V(JSStoreMessage) \
V(JSLoadModule) \
V(JSStoreModule) \
V(JSGetImportMeta) \
V(JSGeneratorStore) \
V(JSGeneratorRestoreContinuation) \
V(JSGeneratorRestoreContext) \

View File

@ -74,6 +74,7 @@ bool OperatorProperties::NeedsExactContext(const Operator* op) {
case IrOpcode::kJSDebugger:
case IrOpcode::kJSDeleteProperty:
case IrOpcode::kJSGeneratorStore:
case IrOpcode::kJSGetImportMeta:
case IrOpcode::kJSHasProperty:
case IrOpcode::kJSHasContextExtension:
case IrOpcode::kJSLoadContext:
@ -240,6 +241,7 @@ bool OperatorProperties::HasFrameStateInput(const Operator* op) {
case IrOpcode::kJSPerformPromiseThen:
case IrOpcode::kJSObjectIsArray:
case IrOpcode::kJSRegExpTest:
case IrOpcode::kJSGetImportMeta:
// Iterator protocol operations
case IrOpcode::kJSGetIterator:

View File

@ -1475,6 +1475,20 @@ void SerializerForBackgroundCompilation::VisitInvokeIntrinsic(
Builtins::kCopyDataProperties));
break;
}
case Runtime::kInlineGetImportMetaObject: {
Hints const& context_hints = environment()->current_context_hints();
for (auto x : context_hints.constants()) {
ContextRef(broker(), x)
.GetModule(SerializationPolicy::kSerializeIfNeeded)
.Serialize();
}
for (auto x : context_hints.virtual_contexts()) {
ContextRef(broker(), x.context)
.GetModule(SerializationPolicy::kSerializeIfNeeded)
.Serialize();
}
break;
}
default: {
break;
}

View File

@ -1857,6 +1857,8 @@ Type Typer::Visitor::TypeJSLoadModule(Node* node) { return Type::Any(); }
Type Typer::Visitor::TypeJSStoreModule(Node* node) { UNREACHABLE(); }
Type Typer::Visitor::TypeJSGetImportMeta(Node* node) { return Type::Any(); }
Type Typer::Visitor::TypeJSGeneratorStore(Node* node) { UNREACHABLE(); }
Type Typer::Visitor::TypeJSGeneratorRestoreContinuation(Node* node) {

View File

@ -820,6 +820,10 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
CheckNotTyped(node);
break;
case IrOpcode::kJSGetImportMeta:
CheckTypeIs(node, Type::Any());
break;
case IrOpcode::kJSGeneratorStore:
CheckNotTyped(node);
break;

View File

@ -4065,7 +4065,7 @@ void Isolate::SetHostImportModuleDynamicallyCallback(
host_import_module_dynamically_callback_ = callback;
}
Handle<JSObject> Isolate::RunHostInitializeImportMetaObjectCallback(
MaybeHandle<JSObject> Isolate::RunHostInitializeImportMetaObjectCallback(
Handle<SourceTextModule> module) {
CHECK(module->import_meta().IsTheHole(this));
Handle<JSObject> import_meta = factory()->NewJSObjectWithNullProto();
@ -4075,7 +4075,10 @@ Handle<JSObject> Isolate::RunHostInitializeImportMetaObjectCallback(
host_initialize_import_meta_object_callback_(
api_context, Utils::ToLocal(Handle<Module>::cast(module)),
v8::Local<v8::Object>::Cast(v8::Utils::ToLocal(import_meta)));
CHECK(!has_scheduled_exception());
if (has_scheduled_exception()) {
PromoteScheduledException();
return {};
}
}
return import_meta;
}

View File

@ -1435,7 +1435,7 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
void SetHostInitializeImportMetaObjectCallback(
HostInitializeImportMetaObjectCallback callback);
Handle<JSObject> RunHostInitializeImportMetaObjectCallback(
MaybeHandle<JSObject> RunHostInitializeImportMetaObjectCallback(
Handle<SourceTextModule> module);
void RegisterEmbeddedFileWriter(EmbeddedFileWriterInterface* writer) {

View File

@ -582,11 +582,14 @@ Handle<JSModuleNamespace> SourceTextModule::GetModuleNamespace(
return Module::GetModuleNamespace(isolate, requested_module);
}
Handle<JSObject> SourceTextModule::GetImportMeta(
MaybeHandle<JSObject> SourceTextModule::GetImportMeta(
Isolate* isolate, Handle<SourceTextModule> module) {
Handle<HeapObject> import_meta(module->import_meta(), isolate);
if (import_meta->IsTheHole(isolate)) {
import_meta = isolate->RunHostInitializeImportMetaObjectCallback(module);
if (!isolate->RunHostInitializeImportMetaObjectCallback(module).ToHandle(
&import_meta)) {
return {};
}
module->set_import_meta(*import_meta);
}
return Handle<JSObject>::cast(import_meta);

View File

@ -62,7 +62,7 @@ class SourceTextModule
// Get the import.meta object of [module]. If it doesn't exist yet, it is
// created and passed to the embedder callback for initialization.
V8_EXPORT_PRIVATE static Handle<JSObject> GetImportMeta(
V8_EXPORT_PRIVATE static MaybeHandle<JSObject> GetImportMeta(
Isolate* isolate, Handle<SourceTextModule> module);
using BodyDescriptor =

View File

@ -41,7 +41,8 @@ RUNTIME_FUNCTION(Runtime_GetImportMetaObject) {
HandleScope scope(isolate);
DCHECK_EQ(0, args.length());
Handle<SourceTextModule> module(isolate->context().module(), isolate);
return *SourceTextModule::GetImportMeta(isolate, module);
RETURN_RESULT_OR_FAILURE(isolate,
SourceTextModule::GetImportMeta(isolate, module));
}
} // namespace internal

View File

@ -25856,9 +25856,11 @@ TEST(ImportMeta) {
v8::ScriptCompiler::Source source(source_text, origin);
Local<Module> module =
v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
i::Handle<i::JSObject> meta = i::SourceTextModule::GetImportMeta(
i_isolate,
i::Handle<i::SourceTextModule>::cast(v8::Utils::OpenHandle(*module)));
i::Handle<i::JSObject> meta =
i::SourceTextModule::GetImportMeta(
i_isolate,
i::Handle<i::SourceTextModule>::cast(v8::Utils::OpenHandle(*module)))
.ToHandleChecked();
Local<Object> meta_obj = Local<Object>::Cast(v8::Utils::ToLocal(meta));
CHECK(meta_obj->Get(context.local(), v8_str("foo"))
.ToLocalChecked()
@ -25880,6 +25882,102 @@ TEST(ImportMeta) {
}
}
void HostInitializeImportMetaObjectCallbackThrow(Local<Context> context,
Local<Module> module,
Local<Object> meta) {
CcTest::isolate()->ThrowException(v8_num(42));
}
TEST(ImportMetaThrowUnhandled) {
i::FLAG_harmony_dynamic_import = true;
i::FLAG_harmony_import_meta = true;
LocalContext context;
v8::Isolate* isolate = context->GetIsolate();
v8::HandleScope scope(isolate);
isolate->SetHostInitializeImportMetaObjectCallback(
HostInitializeImportMetaObjectCallbackThrow);
Local<String> url = v8_str("www.google.com");
Local<String> source_text =
v8_str("export default function() { return import.meta }");
v8::ScriptOrigin origin(url, Local<v8::Integer>(), Local<v8::Integer>(),
Local<v8::Boolean>(), Local<v8::Integer>(),
Local<v8::Value>(), Local<v8::Boolean>(),
Local<v8::Boolean>(), True(isolate));
v8::ScriptCompiler::Source source(source_text, origin);
Local<Module> module =
v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
module->InstantiateModule(context.local(), UnexpectedModuleResolveCallback)
.ToChecked();
Local<Value> result = module->Evaluate(context.local()).ToLocalChecked();
if (i::FLAG_harmony_top_level_await) {
auto promise = Local<v8::Promise>::Cast(result);
CHECK_EQ(promise->State(), v8::Promise::kFulfilled);
}
Local<Object> ns = module->GetModuleNamespace().As<Object>();
Local<Value> closure =
ns->Get(context.local(), v8_str("default")).ToLocalChecked();
v8::TryCatch try_catch(isolate);
CHECK(Function::Cast(*closure)
->Call(context.local(), v8::Undefined(isolate), 0, nullptr)
.IsEmpty());
CHECK(try_catch.HasCaught());
CHECK(try_catch.Exception()->StrictEquals(v8_num(42)));
}
TEST(ImportMetaThrowHandled) {
i::FLAG_harmony_dynamic_import = true;
i::FLAG_harmony_import_meta = true;
LocalContext context;
v8::Isolate* isolate = context->GetIsolate();
v8::HandleScope scope(isolate);
isolate->SetHostInitializeImportMetaObjectCallback(
HostInitializeImportMetaObjectCallbackThrow);
Local<String> url = v8_str("www.google.com");
Local<String> source_text = v8_str(R"javascript(
export default function() {
try {
import.meta;
} catch {
return true;
}
return false;
}
)javascript");
v8::ScriptOrigin origin(url, Local<v8::Integer>(), Local<v8::Integer>(),
Local<v8::Boolean>(), Local<v8::Integer>(),
Local<v8::Value>(), Local<v8::Boolean>(),
Local<v8::Boolean>(), True(isolate));
v8::ScriptCompiler::Source source(source_text, origin);
Local<Module> module =
v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
module->InstantiateModule(context.local(), UnexpectedModuleResolveCallback)
.ToChecked();
Local<Value> result = module->Evaluate(context.local()).ToLocalChecked();
if (i::FLAG_harmony_top_level_await) {
auto promise = Local<v8::Promise>::Cast(result);
CHECK_EQ(promise->State(), v8::Promise::kFulfilled);
}
Local<Object> ns = module->GetModuleNamespace().As<Object>();
Local<Value> closure =
ns->Get(context.local(), v8_str("default")).ToLocalChecked();
v8::TryCatch try_catch(isolate);
CHECK(Function::Cast(*closure)
->Call(context.local(), v8::Undefined(isolate), 0, nullptr)
.ToLocalChecked()
->IsTrue());
CHECK(!try_catch.HasCaught());
}
TEST(GetModuleNamespace) {
LocalContext context;
v8::Isolate* isolate = context->GetIsolate();

View File

@ -0,0 +1,47 @@
// Copyright 2020 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: --allow-natives-syntax --opt
import { getImportMeta } from 'modules-skip-import-meta-export.mjs';
function foo() {
return import.meta;
}
%PrepareFunctionForOptimization(foo);
%OptimizeFunctionOnNextCall(foo);
// Optimize when import.meta hasn't been created yet.
assertEquals('object', typeof foo());
assertEquals(import.meta, foo());
assertOptimized(foo);
function bar() {
return import.meta;
}
%PrepareFunctionForOptimization(bar);
// Optimize when import.meta already exists.
%OptimizeFunctionOnNextCall(bar);
assertEquals(import.meta, bar());
assertOptimized(bar);
%PrepareFunctionForOptimization(getImportMeta);
%OptimizeFunctionOnNextCall(getImportMeta);
assertEquals('object', typeof getImportMeta());
assertOptimized(getImportMeta);
assertNotEquals(import.meta, getImportMeta());
assertOptimized(getImportMeta);
function baz() {
return getImportMeta();
}
// Test inlined (from another module) import.meta accesses.
%PrepareFunctionForOptimization(baz);
baz();
%OptimizeFunctionOnNextCall(baz);
assertEquals('object', typeof baz());
assertNotEquals(import.meta, baz());
assertEquals(baz(), getImportMeta());
assertOptimized(baz);

View File

@ -0,0 +1,3 @@
export function getImportMeta() {
return import.meta;
}