Commit 352e81424fe for nodejs
commit 352e81424feef226f04423d48752f0845d616196
Author: Node.js GitHub Bot <github-bot@iojs.org>
Date: Mon Oct 26 13:50:47 2026 -0400
deps: patch V8 to 15.6.75.25
Refs: https://github.com/v8/v8/compare/15.6.75.13...15.6.75.25
PR-URL: https://github.com/nodejs/node/pull/66820
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Richard Lau <richard.lau@ibm.com>
diff --git a/deps/v8/include/v8-version.h b/deps/v8/include/v8-version.h
index d47b8f7f5c8..f91b6e7018f 100644
--- a/deps/v8/include/v8-version.h
+++ b/deps/v8/include/v8-version.h
@@ -11,7 +11,7 @@
#define V8_MAJOR_VERSION 15
#define V8_MINOR_VERSION 6
#define V8_BUILD_NUMBER 75
-#define V8_PATCH_LEVEL 13
+#define V8_PATCH_LEVEL 25
// Use 1 for candidates and 0 otherwise.
// (Boolean macro values are not supported by all preprocessors.)
diff --git a/deps/v8/src/ast/scopes.cc b/deps/v8/src/ast/scopes.cc
index 7713cd8103d..725f007719d 100644
--- a/deps/v8/src/ast/scopes.cc
+++ b/deps/v8/src/ast/scopes.cc
@@ -345,6 +345,7 @@ void DeclarationScope::SetDefaults() {
is_module_scope());
set_needs_private_name_context_chain_recalc(false);
set_class_scope_has_private_brand(false);
+ set_class_initializer_allows_await(false);
receiver_ = nullptr;
new_target_ = nullptr;
function_ = nullptr;
@@ -1575,7 +1576,8 @@ bool Scope::NeedsScopeInfo() const {
}
bool Scope::ShouldBanArguments() {
- return GetReceiverScope()->should_ban_arguments();
+ return GetReceiverScope()->should_ban_arguments() &&
+ !GetClosureScope()->from_scope_info();
}
DeclarationScope* Scope::GetReceiverScope() {
diff --git a/deps/v8/src/ast/scopes.h b/deps/v8/src/ast/scopes.h
index f85e7420968..fa13cc9a176 100644
--- a/deps/v8/src/ast/scopes.h
+++ b/deps/v8/src/ast/scopes.h
@@ -1387,6 +1387,13 @@ class V8_EXPORT_PRIVATE DeclarationScope : public Scope {
return ClassScopeHasPrivateBrandField::decode(flags_);
}
+ bool class_initializer_allows_await() const {
+ return ClassInitializerAllowsAwaitField::decode(flags_);
+ }
+ void set_class_initializer_allows_await(bool value) {
+ flags_ = ClassInitializerAllowsAwaitField::update(flags_, value);
+ }
+
private:
V8_INLINE void AllocateParameter(Variable* var, int index);
@@ -1430,6 +1437,8 @@ class V8_EXPORT_PRIVATE DeclarationScope : public Scope {
HasThisDeclarationField::Next<bool, 1>;
using ClassScopeHasPrivateBrandField =
NeedsPrivateNameContextChainRecalcField::Next<bool, 1>;
+ using ClassInitializerAllowsAwaitField =
+ ClassScopeHasPrivateBrandField::Next<bool, 1>;
#if DEBUG
bool is_being_lazily_parsed_;
diff --git a/deps/v8/src/codegen/compiler.cc b/deps/v8/src/codegen/compiler.cc
index fbcf0d8ba77..0a3599ee41c 100644
--- a/deps/v8/src/codegen/compiler.cc
+++ b/deps/v8/src/codegen/compiler.cc
@@ -2852,8 +2852,8 @@ void BackgroundDeserializeTask::Run() {
LocalHandleScope handle_scope(&isolate);
DirectHandle<SharedFunctionInfo> inner_result;
- off_thread_data_ =
- CodeSerializer::StartDeserializeOffThread(&isolate, &cached_data_);
+ CodeSerializer::StartDeserializeOffThread(&isolate, &cached_data_,
+ &off_thread_data_);
if (v8_flags.enable_slow_asserts && off_thread_data_.HasResult()) {
#ifdef ENABLE_SLOW_DCHECKS
MergeAssumptionChecker checker(&isolate, false);
@@ -2866,6 +2866,11 @@ void BackgroundDeserializeTask::SourceTextAvailable(
Isolate* isolate, Handle<String> source_text,
const ScriptDetails& script_details) {
DCHECK_EQ(isolate, isolate_for_local_isolate_);
+ if (!CodeSerializer::NotifySourceTextAvailable(isolate, &off_thread_data_,
+ &cached_data_, source_text,
+ script_details)) {
+ return;
+ }
LanguageMode language_mode = construct_language_mode(v8_flags.use_strict);
background_merge_task_.SetUpOnMainThread(isolate, source_text, script_details,
language_mode);
diff --git a/deps/v8/src/codegen/compiler.h b/deps/v8/src/codegen/compiler.h
index 7f8c3152e8a..cfa6af01010 100644
--- a/deps/v8/src/codegen/compiler.h
+++ b/deps/v8/src/codegen/compiler.h
@@ -719,7 +719,7 @@ class V8_EXPORT_PRIVATE BackgroundDeserializeTask {
private:
Isolate* isolate_for_local_isolate_;
AlignedCachedData cached_data_;
- CodeSerializer::OffThreadDeserializeData off_thread_data_;
+ OffThreadDeserializeData off_thread_data_;
BackgroundMergeTask background_merge_task_;
TimedHistogram* timer_;
int64_t background_time_in_microseconds_ = 0;
diff --git a/deps/v8/src/compiler/common-operator.cc b/deps/v8/src/compiler/common-operator.cc
index 37ab33d7f74..0bc7016d6ab 100644
--- a/deps/v8/src/compiler/common-operator.cc
+++ b/deps/v8/src/compiler/common-operator.cc
@@ -515,6 +515,32 @@ const ExitMachineGraphParameters& ExitMachineGraphParametersOf(
return OpParameter<ExitMachineGraphParameters>(op);
}
+#if V8_ENABLE_SANDBOX
+size_t hash_value(const LoadTrustedPointerParameters& params) {
+ return base::hash_combine(params.offset, params.tag);
+}
+
+V8_EXPORT_PRIVATE std::ostream& operator<<(
+ std::ostream& os, LoadTrustedPointerParameters params) {
+ return os << "offset=" << params.offset << ", tag.first=" << params.tag.first
+ << ", tag.last=" << params.tag.last;
+}
+
+class LoadTrustedPointerOperator final
+ : public Operator1<LoadTrustedPointerParameters> {
+ public:
+ LoadTrustedPointerOperator(int offset, IndirectPointerTagRange tag)
+ : Operator1<LoadTrustedPointerParameters>(
+ IrOpcode::kLoadTrustedPointer, Operator::kEliminatable,
+ "LoadTrustedPointer", 1, 1, 1, 1, 1, 0, {offset, tag}) {}
+};
+
+const Operator* CommonOperatorBuilder::LoadTrustedPointer(
+ int offset, IndirectPointerTagRange tag) {
+ return zone_->New<LoadTrustedPointerOperator>(offset, tag);
+}
+#endif // V8_ENABLE_SANDBOX
+
#define COMMON_CACHED_OP_LIST(V) \
V(Plug, Operator::kNoProperties, 0, 0, 0, 1, 0, 0) \
V(Dead, Operator::kFoldable, 0, 0, 0, 1, 1, 1) \
diff --git a/deps/v8/src/compiler/common-operator.h b/deps/v8/src/compiler/common-operator.h
index 6499e23637c..3a1188f3574 100644
--- a/deps/v8/src/compiler/common-operator.h
+++ b/deps/v8/src/compiler/common-operator.h
@@ -538,6 +538,20 @@ V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
V8_EXPORT_PRIVATE const ExitMachineGraphParameters&
ExitMachineGraphParametersOf(const Operator* op) V8_WARN_UNUSED_RESULT;
+#if V8_ENABLE_SANDBOX
+struct LoadTrustedPointerParameters {
+ int offset;
+ IndirectPointerTagRange tag;
+
+ bool operator==(const LoadTrustedPointerParameters&) const = default;
+};
+
+size_t hash_value(const LoadTrustedPointerParameters& params);
+
+V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
+ LoadTrustedPointerParameters params);
+#endif // V8_ENABLE_SANDBOX
+
// Interface for building common operators that can be used at any level of IR,
// including JavaScript, mid-level, and low-level.
class V8_EXPORT_PRIVATE CommonOperatorBuilder final
@@ -616,6 +630,9 @@ class V8_EXPORT_PRIVATE CommonOperatorBuilder final
const Operator* HeapConstant(const Handle<HeapObject>&);
const Operator* CompressedHeapConstant(const Handle<HeapObject>&);
const Operator* TrustedHeapConstant(const Handle<HeapObject>&);
+#if V8_ENABLE_SANDBOX
+ const Operator* LoadTrustedPointer(int offset, IndirectPointerTagRange tag);
+#endif
const Operator* ObjectId(uint32_t);
const Operator* RelocatableInt32Constant(int32_t value,
diff --git a/deps/v8/src/compiler/opcodes.h b/deps/v8/src/compiler/opcodes.h
index de11918ad13..d74406bfe7b 100644
--- a/deps/v8/src/compiler/opcodes.h
+++ b/deps/v8/src/compiler/opcodes.h
@@ -85,15 +85,16 @@
V(EnterMachineGraph) \
V(ExitMachineGraph)
-#define COMMON_OP_LIST(V) \
- CONSTANT_OP_LIST(V) \
- INNER_OP_LIST(V) \
- V(Unreachable) \
- V(DeadValue) \
- V(Dead) \
- V(Plug) \
- V(SLVerifierHint) \
- V(MajorGCForCompilerTesting) \
+#define COMMON_OP_LIST(V) \
+ CONSTANT_OP_LIST(V) \
+ INNER_OP_LIST(V) \
+ V(Unreachable) \
+ V(DeadValue) \
+ V(Dead) \
+ V(Plug) \
+ V(SLVerifierHint) \
+ V(MajorGCForCompilerTesting) \
+ IF_SANDBOX(V, LoadTrustedPointer) \
V(StaticAssert)
// Opcodes for JavaScript operators.
diff --git a/deps/v8/src/compiler/simplified-lowering-verifier.cc b/deps/v8/src/compiler/simplified-lowering-verifier.cc
index d288ed102b0..6d41bbb8a32 100644
--- a/deps/v8/src/compiler/simplified-lowering-verifier.cc
+++ b/deps/v8/src/compiler/simplified-lowering-verifier.cc
@@ -504,6 +504,7 @@ void SimplifiedLoweringVerifier::VisitNode(Node* node,
CASE(Dead)
CASE(Plug)
CASE(MajorGCForCompilerTesting)
+ IF_SANDBOX(CASE, LoadTrustedPointer)
CASE(StaticAssert)
// Simplified change operators
CASE(ChangeTaggedSignedToInt32)
diff --git a/deps/v8/src/compiler/turbofan-typer.cc b/deps/v8/src/compiler/turbofan-typer.cc
index 16fa4b6c443..04df6121129 100644
--- a/deps/v8/src/compiler/turbofan-typer.cc
+++ b/deps/v8/src/compiler/turbofan-typer.cc
@@ -1297,6 +1297,9 @@ Type Typer::Visitor::TypeMajorGCForCompilerTesting(Node* node) {
UNREACHABLE();
}
Type Typer::Visitor::TypeStaticAssert(Node* node) { UNREACHABLE(); }
+#if V8_ENABLE_SANDBOX
+Type Typer::Visitor::TypeLoadTrustedPointer(Node* node) { UNREACHABLE(); }
+#endif
Type Typer::Visitor::TypeSLVerifierHint(Node* node) { UNREACHABLE(); }
// JS comparison operators.
diff --git a/deps/v8/src/compiler/turboshaft/graph-builder.cc b/deps/v8/src/compiler/turboshaft/graph-builder.cc
index 4864bf773c5..5866bba154e 100644
--- a/deps/v8/src/compiler/turboshaft/graph-builder.cc
+++ b/deps/v8/src/compiler/turboshaft/graph-builder.cc
@@ -1434,6 +1434,14 @@ OpIndex GraphBuilder::Process(
return __ Load(Map(node->InputAt(0)), Map(node->InputAt(1)),
LoadOp::Kind::Trapping(), loaded_rep);
}
+#if V8_ENABLE_SANDBOX
+ case IrOpcode::kLoadTrustedPointer: {
+ auto params = OpParameter<LoadTrustedPointerParameters>(op);
+ return __ LoadTrustedPointer(Map(node->InputAt(0)),
+ LoadOp::Kind::TaggedBase(), params.tag,
+ params.offset + kHeapObjectTag);
+ }
+#endif
case IrOpcode::kStore:
case IrOpcode::kUnalignedStore: {
diff --git a/deps/v8/src/compiler/verifier.cc b/deps/v8/src/compiler/verifier.cc
index a4787d8391b..39d2dbced49 100644
--- a/deps/v8/src/compiler/verifier.cc
+++ b/deps/v8/src/compiler/verifier.cc
@@ -653,6 +653,11 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
case IrOpcode::kExitMachineGraph:
CheckValueInputIs(node, 0, Type::Machine());
break;
+#if V8_ENABLE_SANDBOX
+ case IrOpcode::kLoadTrustedPointer:
+ // TODO(manoskouk): what are the constraints here?
+ break;
+#endif
// JavaScript operators
// --------------------
diff --git a/deps/v8/src/compiler/wasm-graph-assembler.cc b/deps/v8/src/compiler/wasm-graph-assembler.cc
index c5c4dd9160d..f4e55a54a11 100644
--- a/deps/v8/src/compiler/wasm-graph-assembler.cc
+++ b/deps/v8/src/compiler/wasm-graph-assembler.cc
@@ -242,55 +242,6 @@ Node* WasmGraphAssembler::BuildDecodeSandboxedExternalPointer(
#endif // V8_ENABLE_SANDBOX
}
-Node* WasmGraphAssembler::BuildDecodeTrustedPointer(
- Node* handle, IndirectPointerTagRange tag_range) {
-#if V8_ENABLE_SANDBOX
- Node* index = Word32Shr(handle, Int32Constant(kTrustedPointerHandleShift));
- Node* offset = ChangeUint32ToUint64(
- Word32Shl(index, Int32Constant(kTrustedPointerTableEntrySizeLog2)));
- Node* table = Load(MachineType::Pointer(), LoadRootRegister(),
- IsolateData::trusted_pointer_table_offset() +
- Internals::kExternalEntityTableBasePointerOffset);
- Node* decoded_ptr = Load(MachineType::Pointer(), table, offset);
-
- if (IsFastIndirectPointerTagRange(tag_range)) {
- uint64_t mask = ComputeUntaggingMaskForFastIndirectPointerTag(tag_range);
- decoded_ptr = WordAnd(decoded_ptr, IntPtrConstant(mask));
- } else {
- Node* tag =
- WordShr(decoded_ptr, IntPtrConstant(kTrustedPointerTableTagShift));
-
- auto ok = MakeLabel();
- if (tag_range.Size() == 1) {
- // The common and simple case: we expect exactly one tag.
- Node* expected_tag = IntPtrConstant(tag_range.first);
- GotoIf(WordEqual(tag, expected_tag), &ok, BranchHint::kTrue);
- RuntimeAbort(AbortReason::kIndirectPointerTagMismatch);
- Goto(&ok); // Unreachable, but needed for graph coherency.
- } else {
- // Range check.
- Node* diff = IntPtrSub(tag, IntPtrConstant(tag_range.first));
- GotoIf(Uint64LessThanOrEqual(
- diff, IntPtrConstant(tag_range.last - tag_range.first)),
- &ok, BranchHint::kTrue);
- RuntimeAbort(AbortReason::kIndirectPointerTagMismatch);
- Goto(&ok); // Unreachable, but needed for graph coherency.
- }
-
- Bind(&ok);
- decoded_ptr =
- WordAnd(decoded_ptr, IntPtrConstant(kTrustedPointerTablePayloadMask));
- }
-
- // We have to change the type of the result value to Tagged, so if the value
- // gets spilled on the stack, it will get processed by the GC.
- decoded_ptr = BitcastWordToTagged(decoded_ptr);
- return decoded_ptr;
-#else
- UNREACHABLE();
-#endif // V8_ENABLE_SANDBOX
-}
-
Node* WasmGraphAssembler::BuildLoadExternalPointerFromObject(
Node* object, int field_offset, ExternalPointerTagRange tag_range,
Node* isolate_root) {
@@ -408,38 +359,14 @@ Node* WasmGraphAssembler::LoadByteArrayElement(Node* byte_array,
return LoadFromObject(type, byte_array, offset);
}
-Node* WasmGraphAssembler::LoadImmutableTrustedPointerFromObject(
- Node* object, int field_offset, IndirectPointerTagRange tag) {
- Node* offset = IntPtrConstant(field_offset);
-#ifdef V8_ENABLE_SANDBOX
- Node* handle = LoadImmutableFromObject(MachineType::Uint32(), object, offset);
- return BuildDecodeTrustedPointer(handle, tag);
-#else
- return LoadImmutableFromObject(MachineType::TaggedPointer(), object, offset);
-#endif
-}
-
Node* WasmGraphAssembler::LoadTrustedPointerFromObject(
Node* object, int field_offset, IndirectPointerTagRange tag) {
- Node* offset = IntPtrConstant(field_offset);
#ifdef V8_ENABLE_SANDBOX
- Node* handle = LoadFromObject(MachineType::Uint32(), object, offset);
- return BuildDecodeTrustedPointer(handle, tag);
+ return AddNode(graph()->NewNode(common_.LoadTrustedPointer(field_offset, tag),
+ object, effect(), control()));
#else
- return LoadFromObject(MachineType::TaggedPointer(), object, offset);
-#endif
-}
-
-std::pair<Node*, Node*>
-WasmGraphAssembler::LoadTrustedPointerFromObjectTrapOnNull(
- Node* object, int field_offset, IndirectPointerTagRange tag) {
Node* offset = IntPtrConstant(field_offset);
-#ifdef V8_ENABLE_SANDBOX
- Node* handle = LoadTrapOnNull(MachineType::Uint32(), object, offset);
- return {handle, BuildDecodeTrustedPointer(handle, tag)};
-#else
- Node* value = LoadTrapOnNull(MachineType::TaggedPointer(), object, offset);
- return {value, value};
+ return LoadFromObject(MachineType::TaggedPointer(), object, offset);
#endif
}
@@ -465,7 +392,7 @@ Node* WasmGraphAssembler::LoadContextNoCellFromJSFunction(Node* js_function) {
Node* WasmGraphAssembler::LoadFunctionDataFromJSFunction(Node* js_function) {
Node* shared = LoadSharedFunctionInfo(js_function);
- return LoadImmutableTrustedPointerFromObject(
+ return LoadTrustedPointerFromObject(
shared,
offsetof(SharedFunctionInfo, trusted_function_data_) - kHeapObjectTag,
kWasmExportedFunctionDataIndirectPointerTag);
@@ -624,7 +551,7 @@ Node* WasmGraphAssembler::StringPrepareForGetCodeunit(Node* string) {
Node* WasmGraphAssembler::LoadTrustedDataFromInstanceObject(
Node* instance_object) {
- return LoadImmutableTrustedPointerFromObject(
+ return LoadTrustedPointerFromObject(
instance_object,
offsetof(WasmInstanceObject, trusted_data_) - kHeapObjectTag,
kWasmTrustedInstanceDataIndirectPointerTag);
diff --git a/deps/v8/src/compiler/wasm-graph-assembler.h b/deps/v8/src/compiler/wasm-graph-assembler.h
index f10e0205091..0737d7999e7 100644
--- a/deps/v8/src/compiler/wasm-graph-assembler.h
+++ b/deps/v8/src/compiler/wasm-graph-assembler.h
@@ -28,7 +28,8 @@ class WasmGraphAssembler : public GraphAssembler {
public:
WasmGraphAssembler(MachineGraph* mcgraph, Zone* zone)
: GraphAssembler(mcgraph, zone, BranchSemantics::kMachine),
- simplified_(zone) {}
+ simplified_(zone),
+ common_(zone) {}
// While CallBuiltin() translates to a direct call to the address of the
// builtin, CallBuiltinThroughJumptable instead jumps to a slot in a jump
@@ -177,15 +178,8 @@ class WasmGraphAssembler : public GraphAssembler {
ExternalPointerTagRange tag_range,
Node* isolate_root);
- Node* LoadImmutableTrustedPointerFromObject(Node* object, int offset,
- IndirectPointerTagRange tag);
Node* LoadTrustedPointerFromObject(Node* object, int offset,
IndirectPointerTagRange tag);
- // Returns the load node (where the source position for the trap needs to be
- // set by the caller) and the result.
- std::pair<Node*, Node*> LoadTrustedPointerFromObjectTrapOnNull(
- Node* object, int offset, IndirectPointerTagRange tag);
- Node* BuildDecodeTrustedPointer(Node* handle, IndirectPointerTagRange tag);
Node* IsSmi(Node* object);
@@ -345,6 +339,7 @@ class WasmGraphAssembler : public GraphAssembler {
}
SimplifiedOperatorBuilder simplified_;
+ CommonOperatorBuilder common_;
};
} // namespace compiler
diff --git a/deps/v8/src/execution/frames.cc b/deps/v8/src/execution/frames.cc
index 51055f12e0c..12cadd23272 100644
--- a/deps/v8/src/execution/frames.cc
+++ b/deps/v8/src/execution/frames.cc
@@ -1903,10 +1903,35 @@ void WasmFrame::Iterate(RootVisitor* v) const {
bool has_tagged_outgoing_params =
wasm_code->kind() != wasm::WasmCode::kWasmFunction &&
wasm_code->kind() != wasm::WasmCode::kWasmToCapiWrapper;
- if (!InFastCCall() && has_tagged_outgoing_params) {
- FullObjectSlot parameters_base(&Memory<Address>(sp()));
- v->VisitRootPointers(Root::kStackRoots, nullptr, parameters_base,
- parameters_limit);
+ if (!InFastCCall()) {
+ if (has_tagged_outgoing_params) {
+ FullObjectSlot parameters_base(&Memory<Address>(sp()));
+ v->VisitRootPointers(Root::kStackRoots, nullptr, parameters_base,
+ parameters_limit);
+ } else if (wasm_code->kind() == wasm::WasmCode::kWasmFunction &&
+ callee_pc() != kNullAddress) {
+ // If a Wasm function calls a builtin with tagged stack parameters,
+ // visit those parameters. (Calls to other Wasm functions or to
+ // WasmToJsWrapperCSA have their tagged stack parameters visited by the
+ // callee frame instead.)
+ Builtin builtin =
+ OffHeapInstructionStream::TryLookupCode(isolate(), callee_pc());
+ if (Builtins::IsBuiltinId(builtin)) {
+ CallInterfaceDescriptor descriptor =
+ Builtins::CallInterfaceDescriptorFor(builtin);
+ DCHECK(!descriptor.AllowVarArgs());
+ if (!(descriptor.flags() & CallInterfaceDescriptorData::kNoStackScan)) {
+ int stack_params = descriptor.GetStackParameterCount();
+ DCHECK_IMPLIES(builtin == Builtin::kWasmToJsWrapperCSA,
+ stack_params == 0);
+ if (stack_params > 0) {
+ FullObjectSlot parameters_base(&Memory<Address>(sp()));
+ v->VisitRootPointers(Root::kStackRoots, nullptr, parameters_base,
+ parameters_base + stack_params);
+ }
+ }
+ }
+ }
}
// Visit pointer spill slots and locals.
diff --git a/deps/v8/src/ic/ic.cc b/deps/v8/src/ic/ic.cc
index 7ae7d8f99d0..681dac659f1 100644
--- a/deps/v8/src/ic/ic.cc
+++ b/deps/v8/src/ic/ic.cc
@@ -1760,17 +1760,6 @@ KeyedAccessLoadMode GetUpdatedLoadModeForMap(Isolate* isolate,
Handle<Object> KeyedLoadIC::LoadElementHandler(
DirectHandle<Map> receiver_map, KeyedAccessLoadMode new_load_mode,
MaybeDirectHandle<Map> maybe_transition_target) {
- // Has a getter interceptor, or is any has and has a query interceptor.
- if (receiver_map->has_indexed_interceptor() &&
- (receiver_map->GetIndexedInterceptor()->has_getter() ||
- (IsAnyHas() && receiver_map->GetIndexedInterceptor()->has_query())) &&
- !receiver_map->GetIndexedInterceptor()->non_masking()) {
- // TODO(jgruber): Update counter name.
- TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_LoadIndexedInterceptorStub);
- return IsAnyHas() ? BUILTIN_CODE(isolate(), HasIndexedInterceptorIC)
- : BUILTIN_CODE(isolate(), LoadIndexedInterceptorIC);
- }
-
InstanceType instance_type = receiver_map->instance_type();
if (instance_type < FIRST_NONSTRING_TYPE) {
TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_LoadIndexedStringDH);
@@ -1784,6 +1773,23 @@ Handle<Object> KeyedLoadIC::LoadElementHandler(
if (instance_type == JS_PROXY_TYPE) {
return LoadHandler::LoadProxy(isolate());
}
+ if (receiver_map->is_access_check_needed()) {
+ TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_SlowStub);
+ return LoadHandler::LoadSlow(isolate());
+ }
+
+ // Has a getter interceptor, or is any has and has a query interceptor.
+ if (receiver_map->has_indexed_interceptor() &&
+ (receiver_map->GetIndexedInterceptor()->has_getter() ||
+ (IsAnyHas() && receiver_map->GetIndexedInterceptor()->has_query())) &&
+ !receiver_map->GetIndexedInterceptor()->non_masking()) {
+ DCHECK(!receiver_map->is_access_check_needed());
+ // TODO(jgruber): Update counter name.
+ TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_LoadIndexedInterceptorStub);
+ return IsAnyHas() ? BUILTIN_CODE(isolate(), HasIndexedInterceptorIC)
+ : BUILTIN_CODE(isolate(), LoadIndexedInterceptorIC);
+ }
+
#if V8_ENABLE_WEBASSEMBLY
if (InstanceTypeChecker::IsWasmObject(instance_type)) {
// TODO(jgruber): Update counter name.
@@ -2870,7 +2876,8 @@ void KeyedStoreIC::UpdateStoreElement(Handle<Map> receiver_map,
Handle<Object> KeyedStoreIC::StoreElementHandler(
DirectHandle<Map> receiver_map, KeyedAccessStoreMode store_mode,
MaybeDirectHandle<UnionOf<Smi, Cell>> prev_validity_cell) {
- if (!IsJSObjectMap(*receiver_map)) {
+ if (!IsJSObjectMap(*receiver_map) || receiver_map->is_access_check_needed() ||
+ receiver_map->IsMapInArrayPrototypeChain(isolate())) {
// DefineKeyedOwnIC, which is used to define computed fields in instances,
// should handled by the slow stub below instead of the proxy stub.
if (IsJSProxyMap(*receiver_map) && !IsDefineKeyedOwnIC()) {
diff --git a/deps/v8/src/maglev/maglev-graph-builder.cc b/deps/v8/src/maglev/maglev-graph-builder.cc
index b0bca8d4128..ca199bab0fa 100644
--- a/deps/v8/src/maglev/maglev-graph-builder.cc
+++ b/deps/v8/src/maglev/maglev-graph-builder.cc
@@ -9110,16 +9110,21 @@ MaglevGraphBuilder::BuildJSArrayBuiltinMapSwitchOnElementsKind(
sub_graph.GotoOrTrim(&*object_case);
sub_graph.Bind(&*object_case);
}
- any_successful |= (!build_kind_specific(kind).IsDoneWithAbort());
+ ReduceResult result = build_kind_specific(kind);
if (IsSmiElementsKind(kind) && make_smi_fallthrough_to_object) {
// After building the SMI specific parts (see build_kind_specific call
// above), jump to the beginning of the case for the object elements
// kind.
DCHECK(object_case.has_value());
sub_graph.GotoOrTrim(&*object_case);
+
+ // Whether the Smi case is successful depends on whether the object case
+ // is successful, so don't update any_successful here.
} else {
DCHECK(do_return.has_value());
sub_graph.GotoOrTrim(&*do_return);
+
+ any_successful |= !result.IsDoneWithAbort();
}
sub_graph.Bind(&check_next_map);
} else {
@@ -16971,11 +16976,11 @@ void MaglevGraphBuilder::ProcessMergePointPredecessors(
ResetBuilderCachedState();
if (merge_state.is_loop()) {
- DCHECK_EQ(merge_state.predecessors_so_far(),
- merge_state.predecessor_count() - 1);
+ CHECK_EQ(merge_state.predecessors_so_far(),
+ merge_state.predecessor_count() - 1);
} else {
- DCHECK_EQ(merge_state.predecessors_so_far(),
- merge_state.predecessor_count());
+ CHECK_EQ(merge_state.predecessors_so_far(),
+ merge_state.predecessor_count());
}
if (merge_state.predecessor_count() == 1) {
diff --git a/deps/v8/src/maglev/maglev-graph-optimizer.cc b/deps/v8/src/maglev/maglev-graph-optimizer.cc
index 8f5ec525e29..a78420a1338 100644
--- a/deps/v8/src/maglev/maglev-graph-optimizer.cc
+++ b/deps/v8/src/maglev/maglev-graph-optimizer.cc
@@ -688,11 +688,54 @@ ValueNode* MaglevGraphOptimizer::GetConstantWithRepresentation(
}
}
+namespace {
+
+// Returns the type check that `alt` is guaranteed to have already performed
+// on its tagged input node.
+NodeType GuaranteedInputType(UseRepresentation repr, ValueNode* alt) {
+ switch (repr) {
+ case UseRepresentation::kInt32:
+ case UseRepresentation::kFloat64:
+ return NodeType::kNumber;
+ case UseRepresentation::kHoleyFloat64:
+ return NodeType::kNumberOrUndefined;
+ // Unlike Int32/Float64, a Truncate node doesn't have a single fixed check,
+ // it depends on its assumed_input_type.
+ case UseRepresentation::kTruncatedInt32:
+ if (auto* t = alt->TryCast<TruncateCheckedNumberOrOddballToInt32>()) {
+ return t->assumed_input_type();
+ }
+ if (auto* t = alt->TryCast<TruncateUnsafeNumberOrOddballToInt32>()) {
+ return t->assumed_input_type();
+ }
+ return NodeType::kNumberOrOddball;
+ case UseRepresentation::kTagged:
+ case UseRepresentation::kTaggedForNumberToString:
+ case UseRepresentation::kUint32:
+ case UseRepresentation::kNonTruncated:
+ UNREACHABLE();
+ }
+ UNREACHABLE();
+}
+
+} // namespace
+
+bool MaglevGraphOptimizer::CanReuseAlternative(
+ ValueNode* node, ValueNode* alt, UseRepresentation repr,
+ std::optional<NodeType> assumed_input_type) {
+ if (!assumed_input_type.has_value()) return true;
+ // We cannot replace a stricter check with a weaker one, as subsequent
+ // operations might rely on this node to deopt invalid inputs.
+ return reducer_.CheckType(node, *assumed_input_type) ||
+ NodeTypeIs(GuaranteedInputType(repr, alt), *assumed_input_type);
+}
+
MaybeReduceResult MaglevGraphOptimizer::GetUntaggedValueWithRepresentation(
ValueNode* node, UseRepresentation use_repr,
std::optional<NodeType> assumed_input_type) {
DCHECK_NE(use_repr, UseRepresentation::kTagged);
- if (node->value_representation() == ValueRepresentationFromUse(use_repr)) {
+ if (node->value_representation() == ValueRepresentationFromUse(use_repr) &&
+ CanReuseAlternative(node, node, use_repr, assumed_input_type)) {
return node;
}
if (node->Is<ReturnedValue>()) {
@@ -710,8 +753,11 @@ MaybeReduceResult MaglevGraphOptimizer::GetUntaggedValueWithRepresentation(
// Check if we already have a canonical conversion.
NodeInfo* node_info =
known_node_aspects().GetOrCreateInfoFor(broker(), node);
- auto& alternative = node_info->alternative();
- if (ValueNode* alt = alternative.get(use_repr)) return alt;
+ if (ValueNode* alternative = node_info->alternative().get(use_repr);
+ alternative &&
+ CanReuseAlternative(node, alternative, use_repr, assumed_input_type)) {
+ return alternative;
+ }
// If `node` is itself tagging an untagged value, convert that value
// directly instead of untagging the tagged result.
if (node->is_conversion()) {
@@ -726,6 +772,15 @@ MaybeReduceResult MaglevGraphOptimizer::GetUntaggedValueWithRepresentation(
if (!CanEagerDeopt()) {
return {};
}
+ if (node->is_holey_float64() && assumed_input_type.has_value() &&
+ NodeTypeIs(*assumed_input_type, NodeType::kNumberOrBoolean)) {
+ // HoleyFloat64 can hold the hole or undefined, whereas the replaced node
+ // checked that the input is a number (or boolean, which cannot occur in
+ // HoleyFloat64). Convert to Float64 first so CheckedHoleyFloat64ToFloat64
+ // preserves the check that subsequent operations may rely on.
+ GET_VALUE_OR_ABORT(
+ node, reducer_.GetFloat64ForToNumber(node, *assumed_input_type));
+ }
switch (use_repr) {
case UseRepresentation::kInt32:
return reducer_.GetInt32(node);
diff --git a/deps/v8/src/maglev/maglev-graph-optimizer.h b/deps/v8/src/maglev/maglev-graph-optimizer.h
index f41212a81ab..7e98d1059f2 100644
--- a/deps/v8/src/maglev/maglev-graph-optimizer.h
+++ b/deps/v8/src/maglev/maglev-graph-optimizer.h
@@ -150,6 +150,12 @@ class V8_EXPORT_PRIVATE MaglevGraphOptimizer {
ValueNode* node, UseRepresentation repr,
std::optional<NodeType> assumed_input_type);
+ // Returns true if reusing the cached conversion 'alt' safely preserves all
+ // required type checks for 'node'.
+ bool CanReuseAlternative(ValueNode* node, ValueNode* alt,
+ UseRepresentation repr,
+ std::optional<NodeType> assumed_input_type);
+
// Records the untagged input of a tagging conversion as the matching
// untagged alternative of `tagged`, so a later untagging use can reuse it.
template <ValueRepresentation kRepresentation>
diff --git a/deps/v8/src/maglev/maglev-known-node-aspects.cc b/deps/v8/src/maglev/maglev-known-node-aspects.cc
index f4abfca2495..63895d1f5f0 100644
--- a/deps/v8/src/maglev/maglev-known-node-aspects.cc
+++ b/deps/v8/src/maglev/maglev-known-node-aspects.cc
@@ -652,22 +652,22 @@ void KnownNodeAspects::ClearUnstableNodeAspectsForStoreMap(
StoreMap* node, bool is_tracing_enabled) {
if (!node->is_transitioning()) return;
- if (NodeInfo* node_info = TryGetInfoFor(node->ValueInput().node())) {
- if (node_info->possible_maps_are_known() && !node_info->maps_are_stale() &&
- node_info->possible_maps().size() == 1) {
- compiler::MapRef old_map = node_info->possible_maps().at(0);
- auto MaybeAliases = [&](compiler::MapRef map) -> bool {
- return map.equals(old_map);
- };
- // Mark aliasing maps as stale.
- if (MarkMapsStaleIfAny(MaybeAliases)) {
- if (V8_UNLIKELY(v8_flags.trace_maglev_kna && is_tracing_enabled)) {
- std::cout << kRed << "[KNA] StoreMap: Invalidate alias "
- << Brief(*old_map.object()) << kReset << std::endl;
- }
- }
- return;
- }
+ if (MarkSingleMapAsStale(node->ValueInput().node(), is_tracing_enabled)) {
+ return;
+ }
+
+ // TODO(olivf): Only invalidate nodes with the same type.
+ OnSideEffect();
+ if (V8_UNLIKELY(v8_flags.trace_maglev_kna && is_tracing_enabled)) {
+ std::cout << kRed << "[KNA] StoreMap: Invalidate all unstable maps"
+ << kReset << std::endl;
+ }
+}
+
+void KnownNodeAspects::ClearUnstableNodeAspectsForMigration(
+ Node* node, bool is_tracing_enabled) {
+ if (MarkSingleMapAsStale(node->input_node(0), is_tracing_enabled)) {
+ return;
}
// TODO(olivf): Only invalidate nodes with the same type.
@@ -1009,6 +1009,28 @@ KnownNodeAspects::ContextStoreResult KnownNodeAspects::RecordContextSlotStore(
return {ContextStoreResult::kSetNewValue, std::move(aliased_slots)};
}
+bool KnownNodeAspects::MarkSingleMapAsStale(ValueNode* node,
+ bool is_tracing_enabled) {
+ if (NodeInfo* node_info = TryGetInfoFor(node)) {
+ if (node_info->possible_maps_are_known() && !node_info->maps_are_stale() &&
+ node_info->possible_maps().size() == 1) {
+ compiler::MapRef old_map = node_info->possible_maps().at(0);
+ auto MaybeAliases = [&](compiler::MapRef map) -> bool {
+ return map.equals(old_map);
+ };
+ // Mark aliasing maps as stale.
+ if (MarkMapsStaleIfAny(MaybeAliases)) {
+ if (V8_UNLIKELY(v8_flags.trace_maglev_kna && is_tracing_enabled)) {
+ std::cout << kRed << "[KNA]: MarkSingleMapAsStale: Invalidate alias "
+ << Brief(*old_map.object()) << kReset << std::endl;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
#ifdef DEBUG
bool IsStringRootIndex(RootIndex index) {
if (index > RootIndex::kLastReadOnlyRoot) return false;
diff --git a/deps/v8/src/maglev/maglev-known-node-aspects.h b/deps/v8/src/maglev/maglev-known-node-aspects.h
index b7f03911366..dd1ce8cc385 100644
--- a/deps/v8/src/maglev/maglev-known-node-aspects.h
+++ b/deps/v8/src/maglev/maglev-known-node-aspects.h
@@ -407,6 +407,8 @@ class KnownNodeAspects {
void ClearUnstableNodeAspectsForStoreMap(StoreMap* node,
bool is_tracing_enabled);
+ void ClearUnstableNodeAspectsForMigration(Node* node,
+ bool is_tracing_enabled);
void ClearUnstableNodeAspectsForElementsTransition(
const ZoneVector<compiler::MapRef>& transition_sources,
bool is_tracing_enabled);
@@ -824,8 +826,7 @@ class KnownNodeAspects {
std::is_same_v<NodeT,
CheckMapsWithMigrationAndDeopt> ||
std::is_same_v<NodeT, MigrateMapIfNeeded>) {
- // These instructions only migrate representations of values, not the
- // values themselves, so cached values are still valid.
+ ClearUnstableNodeAspectsForMigration(node, is_tracing_enabled);
} else if constexpr (std::is_same_v<NodeT, StoreMap>) {
ClearUnstableNodeAspectsForStoreMap(node, is_tracing_enabled);
} else if constexpr (std::is_same_v<NodeT, TransitionElementsKind> ||
@@ -910,6 +911,8 @@ class KnownNodeAspects {
SmallZoneVector<LoadedContextSlotsKey, 8> ClearAliasedContextSlotsFor(
Graph* graph, ValueNode* context, int offset, ValueNode* value);
+ bool MarkSingleMapAsStale(ValueNode* node, bool is_tracing_enabled);
+
static constexpr uint32_t kEffectEpochForPureInstructions =
std::numeric_limits<uint32_t>::max();
static constexpr uint32_t kEffectEpochOverflow =
diff --git a/deps/v8/src/maglev/maglev-phi-representation-selector.cc b/deps/v8/src/maglev/maglev-phi-representation-selector.cc
index eff255ed43c..5b44744d9e9 100644
--- a/deps/v8/src/maglev/maglev-phi-representation-selector.cc
+++ b/deps/v8/src/maglev/maglev-phi-representation-selector.cc
@@ -1074,7 +1074,7 @@ ValueNode* MaglevPhiRepresentationSelector::GetReplacementForPhiInputConversion(
}
ProcessResult MaglevPhiRepresentationSelector::UpdateUntaggingOfPhi(
- Phi* phi, ValueNode* old_untagging) {
+ Phi* phi, ValueNode* old_untagging, const ProcessingState* state) {
DCHECK_EQ(old_untagging->input_count(), 1);
DCHECK(old_untagging->input(0).node()->Is<Phi>());
@@ -1192,24 +1192,27 @@ ProcessResult MaglevPhiRepresentationSelector::UpdateUntaggingOfPhi(
if (old_untagging->Is<TruncateUnsafeNumberOrOddballToInt32>()) {
conversion_is_truncating_float64 = true;
} else if (old_untagging->Is<TruncateCheckedNumberOrOddballToInt32>()) {
- if (from_repr == ValueRepresentation::kFloat64) {
- // If {from_repr} is Float64, then the "CheckedNumber" part is
- // guaranteed to pass and this operation just truncates its value to
- // Int32.
- conversion_is_truncating_float64 = true;
- } else {
- DCHECK_EQ(from_repr, ValueRepresentation::kHoleyFloat64);
+ if (from_repr == ValueRepresentation::kHoleyFloat64) {
const auto* truncate =
old_untagging->Cast<TruncateCheckedNumberOrOddballToInt32>();
// A HoleyFloat64 hole really means Undefined rather than the_hole:
// whenever it gets rematerialized, it will always be rematerialized as
- // Undefined. So, we can use a truncating conversion as long as the
- // original assumed input type was allowing truncating Undefined.
- // Assumptions without Undefined need to deopt for Hole/Undefined
- // ==> not truncating.
- conversion_is_truncating_float64 = !NodeTypeIs(
- truncate->assumed_input_type(), NodeType::kNumberOrBoolean);
+ // Undefined. Assumptions without Undefined need to deopt on
+ // Hole/Undefined before truncating the Float64 value to Int32.
+ if (!NodeTypeCanBe(truncate->assumed_input_type(),
+ NodeType::kUndefined)) {
+ eager_deopt_frame_ = &old_untagging->eager_deopt_info()->top_frame();
+ AddNewNodeNoInputConversion<CheckHoleyFloat64NotHoleOrUndefined>(
+ reducer_.current_block(),
+ BasicBlockPosition::At(state->node_index()), {phi});
+#ifdef DEBUG
+ eager_deopt_frame_ = nullptr;
+#endif // DEBUG
+ }
+ } else {
+ DCHECK_EQ(from_repr, ValueRepresentation::kFloat64);
}
+ conversion_is_truncating_float64 = true;
}
Opcode needed_conversion = GetOpcodeForConversion(
diff --git a/deps/v8/src/maglev/maglev-phi-representation-selector.h b/deps/v8/src/maglev/maglev-phi-representation-selector.h
index 5ecca6b95f2..3007727a099 100644
--- a/deps/v8/src/maglev/maglev-phi-representation-selector.h
+++ b/deps/v8/src/maglev/maglev-phi-representation-selector.h
@@ -206,7 +206,7 @@ class MaglevPhiRepresentationSelector {
// removed altogether (or rather, replaced by an identity node).
return UpdateUntaggingOfPhi(
node->NodeBase::input(0).node()->template Cast<Phi>(),
- node->template Cast<ValueNode>());
+ node->template Cast<ValueNode>(), state);
}
return ProcessResult::kContinue;
}
@@ -273,7 +273,8 @@ class MaglevPhiRepresentationSelector {
// Updates {old_untagging} to reflect that its Phi input has been untagged and
// that a different conversion is now needed.
- ProcessResult UpdateUntaggingOfPhi(Phi* phi, ValueNode* old_untagging);
+ ProcessResult UpdateUntaggingOfPhi(Phi* phi, ValueNode* old_untagging,
+ const ProcessingState* state);
ProcessResult EmitUnconditionalDeopt(NodeBase* node, DeoptimizeReason reason);
diff --git a/deps/v8/src/maglev/maglev-range-analysis.h b/deps/v8/src/maglev/maglev-range-analysis.h
index 51744959af0..57d415f06ed 100644
--- a/deps/v8/src/maglev/maglev-range-analysis.h
+++ b/deps/v8/src/maglev/maglev-range-analysis.h
@@ -67,6 +67,18 @@ class NodeRanges {
return *range;
}
+ void InitializeRangeIfMissing(BasicBlock* block, ValueNode* node,
+ Range range) {
+ DCHECK(ranges_initialized_.Contains(block->id()));
+ RangeMap& map = ranges_[block->id()];
+ if (!map.find(node)) {
+ map = map.insert(zone(), node, range);
+ TRACE_RANGE("Initializing in Block b" << block->id() << ": "
+ << PrintNodeBrief(node) << ": "
+ << range);
+ }
+ }
+
void UnionUpdate(BasicBlock* block, ValueNode* node, Range range) {
DCHECK(ranges_initialized_.Contains(block->id()));
RangeMap& map = ranges_[block->id()];
@@ -210,6 +222,15 @@ class RangeProcessor {
BlockProcessResult PreProcessBasicBlock(BasicBlock* block) {
current_block_ = block;
ranges_.EnsureMapExistsFor(block);
+ if (block->has_state() && block->state()->is_resumable_loop() &&
+ block->predecessor_count() == 1) {
+ // Resumable loop headers might not have a live forward predecessor: that
+ // part of the code might be dead even though the loop is still reachable
+ // via a resume point. Make sure we initialize phis in such case. If the
+ // loop has a live predecessor, ProcessPhis is called when processing that
+ // live predecessor, and this code is a no-op.
+ InitializeLoopPhisIfMissing(block);
+ }
return BlockProcessResult::kContinue;
}
BlockProcessResult PostProcessBasicBlock(BasicBlock* block) {
@@ -543,6 +564,14 @@ class RangeProcessor {
}
}
+ void InitializeLoopPhisIfMissing(BasicBlock* block) {
+ if (!block->has_phi()) return;
+ for (Phi* phi : *block->phis()) {
+ ranges_.InitializeRangeIfMissing(
+ block, phi, phi->is_int32() ? Range::Int32() : Range::All());
+ }
+ }
+
// Returns true if the loop reach a fixpoint.
bool ProcessLoopPhisBackedge(BasicBlock* block, BasicBlock* backedge_pred) {
if (!block->has_phi()) return true;
diff --git a/deps/v8/src/objects/map-updater.cc b/deps/v8/src/objects/map-updater.cc
index d62c94772f6..f1437356d69 100644
--- a/deps/v8/src/objects/map-updater.cc
+++ b/deps/v8/src/objects/map-updater.cc
@@ -385,8 +385,9 @@ std::optional<Tagged<Map>> MapUpdater::TryUpdateNoLock(Isolate* isolate,
}
if (root_map->prototype() != old_map->prototype()) {
- auto maybe_transition = TransitionsAccessor::GetPrototypeTransition(
- isolate, root_map, old_map->prototype());
+ auto maybe_transition =
+ TransitionsAccessor(isolate, root_map, IsConcurrent(cmode))
+ .GetPrototypeTransition(old_map->prototype());
if (!maybe_transition) {
return {};
}
diff --git a/deps/v8/src/objects/shared-function-info-inl.h b/deps/v8/src/objects/shared-function-info-inl.h
index 93de8a242af..c4504c0f58b 100644
--- a/deps/v8/src/objects/shared-function-info-inl.h
+++ b/deps/v8/src/objects/shared-function-info-inl.h
@@ -511,6 +511,9 @@ BIT_FIELD_ACCESSORS(SharedFunctionInfo, relaxed_flags,
SharedFunctionInfo::PrivateNameLookupSkipsOuterClassBit)
BIT_FIELD_ACCESSORS(SharedFunctionInfo, relaxed_flags, is_hoisted_in_context,
SharedFunctionInfo::IsHoistedInContextBit)
+BIT_FIELD_ACCESSORS(SharedFunctionInfo, relaxed_flags,
+ class_initializer_allows_await,
+ SharedFunctionInfo::ClassInitializerAllowsAwaitBit)
bool SharedFunctionInfo::optimization_disabled(CodeKind kind) const {
switch (kind) {
diff --git a/deps/v8/src/objects/shared-function-info.cc b/deps/v8/src/objects/shared-function-info.cc
index 5151351c3ac..0f4078ec21b 100644
--- a/deps/v8/src/objects/shared-function-info.cc
+++ b/deps/v8/src/objects/shared-function-info.cc
@@ -600,6 +600,10 @@ void SharedFunctionInfo::InitFromFunctionLiteral(IsolateT* isolate,
lit->has_static_private_methods_or_accessors());
raw_sfi->set_is_hoisted_in_context(lit->scope()->is_hoisted_in_context());
+ DCHECK_IMPLIES(lit->scope()->class_initializer_allows_await(),
+ IsClassInitializerFunction(lit->kind()));
+ raw_sfi->set_class_initializer_allows_await(
+ lit->scope()->class_initializer_allows_await());
raw_sfi->set_is_toplevel(is_toplevel);
DCHECK(IsTheHole(raw_sfi->outer_scope_info()));
diff --git a/deps/v8/src/objects/shared-function-info.h b/deps/v8/src/objects/shared-function-info.h
index d5f8a8fc2a9..dd0e0bcb5a4 100644
--- a/deps/v8/src/objects/shared-function-info.h
+++ b/deps/v8/src/objects/shared-function-info.h
@@ -289,6 +289,7 @@ V8_OBJECT class SharedFunctionInfo : public HeapObject {
PropertiesAreFinalBit::Next<bool, 1>;
using IsHoistedInContextBit =
PrivateNameLookupSkipsOuterClassBit::Next<bool, 1>;
+ using ClassInitializerAllowsAwaitBit = IsHoistedInContextBit::Next<bool, 1>;
// Bit positions in |flags2|.
using ClassScopeHasPrivateBrandBit = base::BitField<bool, 0, 1, uint8_t>;
using HasStaticPrivateMethodsOrAccessorsBit =
@@ -708,6 +709,9 @@ V8_OBJECT class SharedFunctionInfo : public HeapObject {
// Indicates that the function is a hoisted-in-context declaration.
DECL_BOOLEAN_ACCESSORS(is_hoisted_in_context)
+ // Indicates that a class initializer's enclosing scope allowed await.
+ DECL_BOOLEAN_ACCESSORS(class_initializer_allows_await)
+
inline FunctionKind kind() const;
int UniqueIdInScript() const;
diff --git a/deps/v8/src/objects/shared-function-info.tq b/deps/v8/src/objects/shared-function-info.tq
index f4949b0bc77..b46945f2c38 100644
--- a/deps/v8/src/objects/shared-function-info.tq
+++ b/deps/v8/src/objects/shared-function-info.tq
@@ -42,6 +42,7 @@ bitfield struct SharedFunctionInfoFlags extends uint32 {
properties_are_final: bool: 1 bit;
private_name_lookup_skips_outer_class: bool: 1 bit;
is_hoisted_in_context: bool: 1 bit;
+ class_initializer_allows_await: bool: 1 bit;
}
@cppScope('SharedFunctionInfo')
diff --git a/deps/v8/src/objects/transitions-inl.h b/deps/v8/src/objects/transitions-inl.h
index af509675f54..b694790a4cd 100644
--- a/deps/v8/src/objects/transitions-inl.h
+++ b/deps/v8/src/objects/transitions-inl.h
@@ -400,6 +400,14 @@ MaybeHandle<Map> TransitionsAccessor::SearchSpecial(Isolate* isolate,
return MaybeHandle<Map>(result, isolate);
}
+// static
+std::optional<Tagged<Map>> TransitionsAccessor::GetPrototypeTransition(
+ Isolate* isolate, Tagged<Map> map, Tagged<Object> prototype) {
+ // This helper is supposed to be used only from the main thread.
+ DCHECK_EQ(ThreadId::Current(), isolate->thread_id());
+ return TransitionsAccessor(isolate, map).GetPrototypeTransition(prototype);
+}
+
// TODO(375937549): Convert to uint32_t.
int TransitionArray::number_of_transitions() const {
if (ulength().value() < kFirstIndex) return 0;
diff --git a/deps/v8/src/objects/transitions.cc b/deps/v8/src/objects/transitions.cc
index 907e8e695b5..0c0ac419f30 100644
--- a/deps/v8/src/objects/transitions.cc
+++ b/deps/v8/src/objects/transitions.cc
@@ -435,8 +435,8 @@ bool TransitionsAccessor::PutPrototypeTransition(Isolate* isolate,
const uint32_t header = TransitionArray::kProtoTransitionHeaderSize;
- DirectHandle<WeakFixedArray> cache(GetPrototypeTransitions(isolate, *map),
- isolate);
+ DirectHandle<WeakFixedArray> cache(
+ TransitionsAccessor(isolate, *map).GetPrototypeTransitions(), isolate);
const uint32_t cache_len = cache->ulength().value();
const uint32_t capacity = cache_len >= header ? cache_len - header : 0;
const uint32_t transitions =
@@ -477,11 +477,13 @@ bool TransitionsAccessor::PutPrototypeTransition(Isolate* isolate,
return true;
}
-// static
std::optional<Tagged<Map>> TransitionsAccessor::GetPrototypeTransition(
- Isolate* isolate, Tagged<Map> map, Tagged<Object> prototype) {
+ Tagged<Object> prototype) {
DisallowGarbageCollection no_gc;
- Tagged<WeakFixedArray> cache = GetPrototypeTransitions(isolate, map);
+ if (encoding() != kFullTransitionArray) return {};
+ base::MutexGuardIf guard(isolate_->full_transition_array_access(),
+ concurrent_access_);
+ Tagged<WeakFixedArray> cache = GetPrototypeTransitions();
const uint32_t length = TransitionArray::NumberOfPrototypeTransitions(cache);
for (uint32_t i = 0; i < length; i++) {
Tagged<MaybeObject> target =
@@ -498,19 +500,11 @@ std::optional<Tagged<Map>> TransitionsAccessor::GetPrototypeTransition(
return {};
}
-// static
-Tagged<WeakFixedArray> TransitionsAccessor::GetPrototypeTransitions(
- Isolate* isolate, Tagged<Map> map) {
- Tagged<MaybeObject> raw_transitions = map->raw_transitions(kAcquireLoad);
- if (GetEncoding(isolate, raw_transitions) != kFullTransitionArray) {
- return ReadOnlyRoots(isolate).empty_weak_fixed_array();
- }
- Tagged<TransitionArray> transition_array =
- GetTransitionArray(isolate, raw_transitions);
- if (!transition_array->HasPrototypeTransitions()) {
- return ReadOnlyRoots(isolate).empty_weak_fixed_array();
+Tagged<WeakFixedArray> TransitionsAccessor::GetPrototypeTransitions() {
+ if (!HasPrototypeTransitions()) {
+ return ReadOnlyRoots(isolate_).empty_weak_fixed_array();
}
- return transition_array->GetPrototypeTransitions();
+ return transitions()->GetPrototypeTransitions();
}
// static
diff --git a/deps/v8/src/objects/transitions.h b/deps/v8/src/objects/transitions.h
index 408bcaf0f4e..d7e948ef958 100644
--- a/deps/v8/src/objects/transitions.h
+++ b/deps/v8/src/objects/transitions.h
@@ -185,8 +185,13 @@ class V8_EXPORT_PRIVATE TransitionsAccessor {
static bool PutPrototypeTransition(Isolate* isolate, DirectHandle<Map>,
DirectHandle<Object> prototype,
DirectHandle<Map> target_map);
- static std::optional<Tagged<Map>> GetPrototypeTransition(
+
+ std::optional<Tagged<Map>> GetPrototypeTransition(Tagged<Object> prototype);
+ // Convenient helper for calling from the main thread, for other cases
+ // use the instance method above.
+ static inline std::optional<Tagged<Map>> GetPrototypeTransition(
Isolate* isolate, Tagged<Map> map, Tagged<Object> prototype);
+
bool HasPrototypeTransitions();
// During the first-time Map::Update and Map::TryUpdate, the migration target
@@ -267,8 +272,7 @@ class V8_EXPORT_PRIVATE TransitionsAccessor {
static void SetPrototypeTransitions(
Isolate* isolate, DirectHandle<Map> map,
DirectHandle<WeakFixedArray> proto_transitions);
- static Tagged<WeakFixedArray> GetPrototypeTransitions(Isolate* isolate,
- Tagged<Map> map);
+ Tagged<WeakFixedArray> GetPrototypeTransitions();
static void InsertHelper(Isolate* isolate, DirectHandle<Map> map,
DirectHandle<Name> name, DirectHandle<Map> target,
diff --git a/deps/v8/src/parsing/parse-info.cc b/deps/v8/src/parsing/parse-info.cc
index f33708abdf8..aa1647ec7f4 100644
--- a/deps/v8/src/parsing/parse-info.cc
+++ b/deps/v8/src/parsing/parse-info.cc
@@ -55,6 +55,8 @@ UnoptimizedCompileFlags UnoptimizedCompileFlags::ForFunctionCompile(
flags.set_is_lazy_compile(true);
flags.set_is_repl_mode(script->is_repl_mode());
+ flags.set_class_initializer_allows_await(
+ shared->class_initializer_allows_await());
// Do not support re-parsing top-level function of a wrapped script.
DCHECK_IMPLIES(flags.is_toplevel(), !script->is_wrapped());
diff --git a/deps/v8/src/parsing/parse-info.h b/deps/v8/src/parsing/parse-info.h
index ef6f4f577ee..d62979f3f62 100644
--- a/deps/v8/src/parsing/parse-info.h
+++ b/deps/v8/src/parsing/parse-info.h
@@ -66,6 +66,7 @@ class Zone;
V(compile_hints_magic_enabled, bool, 1, _) \
V(compile_hints_per_function_magic_enabled, bool, 1, _) \
V(is_hoisted_in_context, bool, 1, _) \
+ V(class_initializer_allows_await, bool, 1, _) \
V(allow_heap_allocation, bool, 1, _)
class V8_EXPORT_PRIVATE UnoptimizedCompileFlags {
diff --git a/deps/v8/src/parsing/parser-base.h b/deps/v8/src/parsing/parser-base.h
index 5b552c58379..1df1fa026f5 100644
--- a/deps/v8/src/parsing/parser-base.h
+++ b/deps/v8/src/parsing/parser-base.h
@@ -450,7 +450,11 @@ class ParserBase {
class FunctionState final : public BlockState {
public:
FunctionState(FunctionState** function_state_stack, Scope** scope_stack,
- DeclarationScope* scope);
+ DeclarationScope* scope)
+ : FunctionState(function_state_stack, scope_stack, scope,
+ scope->function_kind()) {}
+ FunctionState(FunctionState** function_state_stack, Scope** scope_stack,
+ DeclarationScope* scope, FunctionKind kind);
~FunctionState();
DeclarationScope* scope() const { return scope_->AsDeclarationScope(); }
@@ -467,7 +471,7 @@ class ParserBase {
int suspend_count() const { return suspend_count_; }
bool CanSuspend() const { return suspend_count_ > 0; }
- FunctionKind kind() const { return scope()->function_kind(); }
+ FunctionKind kind() const { return kind_; }
bool next_function_is_likely_called() const {
return next_function_is_likely_called_;
@@ -534,6 +538,7 @@ class ParserBase {
FunctionState** function_state_stack_;
FunctionState* outer_function_state_;
DeclarationScope* scope_;
+ FunctionKind kind_;
Target* target_stack_ = nullptr; // for break, continue statements
// A reason, if any, why this function should not be optimized.
@@ -639,6 +644,8 @@ class ParserBase {
static_elements_scope = parser->NewFunctionScope(kind);
static_elements_scope->SetLanguageMode(LanguageMode::kStrict);
static_elements_scope->set_start_position(beg_pos);
+ static_elements_scope->set_class_initializer_allows_await(
+ parser->is_await_allowed());
static_elements_function_id = info_id;
// Actually consume the id. The id that was passed in might be an
// earlier id in case of computed property names.
@@ -657,6 +664,8 @@ class ParserBase {
instance_members_scope = parser->NewFunctionScope(kind);
instance_members_scope->SetLanguageMode(LanguageMode::kStrict);
instance_members_scope->set_start_position(beg_pos);
+ instance_members_scope->set_class_initializer_allows_await(
+ parser->is_await_allowed());
instance_members_function_id = info_id;
// Actually consume the id. The id that was passed in might be an
// earlier id in case of computed property names.
@@ -1833,13 +1842,14 @@ class ParserBase {
template <typename Impl>
ParserBase<Impl>::FunctionState::FunctionState(
FunctionState** function_state_stack, Scope** scope_stack,
- DeclarationScope* scope)
+ DeclarationScope* scope, FunctionKind kind)
: BlockState(scope_stack, scope),
expected_property_count_(0),
suspend_count_(0),
function_state_stack_(function_state_stack),
outer_function_state_(*function_state_stack),
scope_(scope),
+ kind_(kind),
dont_optimize_reason_(BailoutReason::kNoReason),
next_function_is_likely_called_(false),
previous_function_was_likely_called_(false),
diff --git a/deps/v8/src/parsing/parser.cc b/deps/v8/src/parsing/parser.cc
index 94f19994ccf..388e4f1c96e 100644
--- a/deps/v8/src/parsing/parser.cc
+++ b/deps/v8/src/parsing/parser.cc
@@ -1274,10 +1274,19 @@ FunctionLiteral* Parser::ParseClassForMemberInitialization(
// this point the scanner should be rewound to the position of the class
// token.
DCHECK_EQ(peek_position(), initializer_pos);
- // Insert a FunctionState with the closest outer Declaration scope
+ // Insert a FunctionState with the closest outer Declaration scope.
+ // The immediate enclosing function scope may have been elided from the
+ // ScopeInfo chain if it did not allocate a context. Since class bodies are
+ // always strict mode, 'yield' is never a valid identifier in computed
+ // property names, so we can unconditionally enable generator mode; whether
+ // 'await' is a keyword is recorded on the initializer SharedFunctionInfo.
DeclarationScope* nearest_decl_scope = original_scope_->GetDeclarationScope();
DCHECK_NOT_NULL(nearest_decl_scope);
- FunctionState function_state(&function_state_, &scope_, nearest_decl_scope);
+ FunctionKind enclosing_kind = flags().class_initializer_allows_await()
+ ? FunctionKind::kAsyncGeneratorFunction
+ : FunctionKind::kGeneratorFunction;
+ FunctionState function_state(&function_state_, &scope_, nearest_decl_scope,
+ enclosing_kind);
// We preparse the class members that are not fields with initializers
// in order to collect the function literal ids.
diff --git a/deps/v8/src/snapshot/code-serializer.cc b/deps/v8/src/snapshot/code-serializer.cc
index 034ef5f52ea..78ba29eeca3 100644
--- a/deps/v8/src/snapshot/code-serializer.cc
+++ b/deps/v8/src/snapshot/code-serializer.cc
@@ -345,8 +345,8 @@ class StressOffThreadDeserializeThread final : public base::Thread {
LocalIsolate local_isolate(isolate_, ThreadKind::kBackground);
UnparkedScope unparked_scope(&local_isolate);
LocalHandleScope handle_scope(&local_isolate);
- off_thread_data_ =
- CodeSerializer::StartDeserializeOffThread(&local_isolate, cached_data_);
+ CodeSerializer::StartDeserializeOffThread(&local_isolate, cached_data_,
+ &off_thread_data_);
}
MaybeDirectHandle<SharedFunctionInfo> Finalize(
@@ -360,7 +360,7 @@ class StressOffThreadDeserializeThread final : public base::Thread {
private:
Isolate* isolate_;
AlignedCachedData* cached_data_;
- CodeSerializer::OffThreadDeserializeData off_thread_data_;
+ OffThreadDeserializeData off_thread_data_;
};
void FinalizeDeserialization(Isolate* isolate,
@@ -554,8 +554,7 @@ MaybeDirectHandle<SharedFunctionInfo> CodeSerializer::Deserialize(
return scope.CloseAndEscape(result);
}
-DirectHandle<Script> CodeSerializer::OffThreadDeserializeData::GetOnlyScript(
- LocalHeap* heap) {
+DirectHandle<Script> OffThreadDeserializeData::GetOnlyScript(LocalHeap* heap) {
std::unique_ptr<PersistentHandles> previous_persistent_handles =
heap->DetachPersistentHandles();
heap->AttachPersistentHandles(std::move(persistent_handles));
@@ -573,32 +572,47 @@ DirectHandle<Script> CodeSerializer::OffThreadDeserializeData::GetOnlyScript(
return script;
}
-CodeSerializer::OffThreadDeserializeData
-CodeSerializer::StartDeserializeOffThread(LocalIsolate* local_isolate,
- AlignedCachedData* cached_data) {
- OffThreadDeserializeData result;
-
+void CodeSerializer::StartDeserializeOffThread(LocalIsolate* local_isolate,
+ AlignedCachedData* cached_data,
+ OffThreadDeserializeData* data) {
DCHECK(!local_isolate->heap()->HasPersistentHandles());
const SerializedCodeData scd =
SerializedCodeData::FromCachedDataWithoutSource(
- local_isolate, cached_data, &result.sanity_check_result);
- if (result.sanity_check_result != SerializedCodeSanityCheckResult::kSuccess) {
+ local_isolate, cached_data, &data->sanity_check_result);
+ if (data->sanity_check_result != SerializedCodeSanityCheckResult::kSuccess) {
// Exit early but don't report yet, we'll re-check this when finishing on
// the main thread
DCHECK(cached_data->rejected());
- return result;
+ return;
}
MaybeDirectHandle<SharedFunctionInfo> local_maybe_result =
OffThreadObjectDeserializer::DeserializeSharedFunctionInfo(
- local_isolate, &scd, &result.scripts);
+ local_isolate, &scd, &data->scripts);
- result.maybe_result =
+ data->maybe_result =
local_isolate->heap()->NewPersistentMaybeHandle(local_maybe_result);
- result.persistent_handles = local_isolate->heap()->DetachPersistentHandles();
+ data->persistent_handles = local_isolate->heap()->DetachPersistentHandles();
+}
- return result;
+bool CodeSerializer::NotifySourceTextAvailable(
+ Isolate* isolate, OffThreadDeserializeData* data,
+ const AlignedCachedData* cached_data, DirectHandle<String> source,
+ const ScriptDetails& script_details) {
+ HandleScope scope(isolate);
+
+ // Only check the source hash against the immutable cached_data header and
+ // write the main-thread-only data->source_sanity_check_result field here. Do
+ // not read/write data->sanity_check_result or mutate cached_data->Reject(),
+ // as StartDeserializeOffThread may still be running concurrently on a
+ // background thread. Full rejection and histogram reporting happen in
+ // FinishOffThreadDeserialize.
+ SerializedCodeData scd(cached_data);
+ SerializedCodeSanityCheckResult sanity_check_result =
+ scd.SanityCheckJustSource(isolate, source, script_details);
+ data->source_sanity_check_result = sanity_check_result;
+ return sanity_check_result == SerializedCodeSanityCheckResult::kSuccess;
}
MaybeDirectHandle<SharedFunctionInfo>
@@ -614,11 +628,6 @@ CodeSerializer::FinishOffThreadDeserialize(
HandleScope scope(isolate);
- DirectHandle<FixedArray> wrapped_arguments;
- if (!script_details.wrapped_arguments.is_null()) {
- wrapped_arguments = script_details.wrapped_arguments.ToHandleChecked();
- }
-
// Do a source sanity check now that we have the source. It's important for
// FromPartiallySanityCheckedCachedData call that the sanity_check_result
// holds the result of the off-thread sanity check.
@@ -626,10 +635,9 @@ CodeSerializer::FinishOffThreadDeserialize(
data.sanity_check_result;
const SerializedCodeData scd =
SerializedCodeData::FromPartiallySanityCheckedCachedData(
- cached_data,
- SerializedCodeData::SourceHash(source, wrapped_arguments,
- script_details.origin_options),
+ isolate, cached_data, data, source, script_details,
&sanity_check_result);
+ USE(scd);
if (sanity_check_result != SerializedCodeSanityCheckResult::kSuccess) {
// The only case where the deserialization result could exist despite a
// check failure is on a source mismatch, since we can't test for this
@@ -768,8 +776,22 @@ SerializedCodeSanityCheckResult SerializedCodeData::SanityCheck(
return SanityCheckJustSource(expected_source_hash);
}
+SerializedCodeSanityCheckResult SerializedCodeData::SanityCheckJustSource(
+ Isolate* isolate, DirectHandle<String> source,
+ const ScriptDetails& script_details) const {
+ DirectHandle<FixedArray> wrapped_arguments;
+ if (!script_details.wrapped_arguments.is_null()) {
+ wrapped_arguments = script_details.wrapped_arguments.ToHandleChecked();
+ }
+ return SanityCheckJustSource(
+ SourceHash(source, wrapped_arguments, script_details.origin_options));
+}
+
SerializedCodeSanityCheckResult SerializedCodeData::SanityCheckJustSource(
uint32_t expected_source_hash) const {
+ if (size_ < kHeaderSize) {
+ return SerializedCodeSanityCheckResult::kInvalidHeader;
+ }
uint32_t source_hash = GetHeaderValue(kSourceHashOffset);
if (source_hash != expected_source_hash) {
return SerializedCodeSanityCheckResult::kSourceMismatch;
@@ -847,7 +869,7 @@ base::Vector<const uint8_t> SerializedCodeData::Payload() const {
return base::Vector<const uint8_t>(payload, length);
}
-SerializedCodeData::SerializedCodeData(AlignedCachedData* data)
+SerializedCodeData::SerializedCodeData(const AlignedCachedData* data)
: SerializedData(const_cast<uint8_t*>(data->data()), data->length()) {}
SerializedCodeData SerializedCodeData::FromCachedData(
@@ -882,12 +904,14 @@ SerializedCodeData SerializedCodeData::FromCachedDataWithoutSource(
}
SerializedCodeData SerializedCodeData::FromPartiallySanityCheckedCachedData(
- AlignedCachedData* cached_data, uint32_t expected_source_hash,
+ Isolate* isolate, AlignedCachedData* cached_data,
+ const OffThreadDeserializeData& data, DirectHandle<String> source,
+ const ScriptDetails& script_details,
SerializedCodeSanityCheckResult* rejection_result) {
- DisallowGarbageCollection no_gc;
// The previous call to FromCachedDataWithoutSource may have already rejected
// the cached data, so reuse the previous rejection result if it's not a
// success.
+ *rejection_result = data.sanity_check_result;
if (*rejection_result != SerializedCodeSanityCheckResult::kSuccess) {
// FromCachedDataWithoutSource doesn't check the source, so there can't be
// a source mismatch.
@@ -897,7 +921,14 @@ SerializedCodeData SerializedCodeData::FromPartiallySanityCheckedCachedData(
return SerializedCodeData(nullptr, 0);
}
SerializedCodeData scd(cached_data);
- *rejection_result = scd.SanityCheckJustSource(expected_source_hash);
+ if (data.source_sanity_check_result.has_value()) {
+ DCHECK_EQ(*data.source_sanity_check_result,
+ scd.SanityCheckJustSource(isolate, source, script_details));
+ *rejection_result = *data.source_sanity_check_result;
+ } else {
+ *rejection_result =
+ scd.SanityCheckJustSource(isolate, source, script_details);
+ }
if (*rejection_result != SerializedCodeSanityCheckResult::kSuccess) {
// This check only checks the source, so the only possible failure is a
// source mismatch.
diff --git a/deps/v8/src/snapshot/code-serializer.h b/deps/v8/src/snapshot/code-serializer.h
index ed2281113f4..6cfafc4d26b 100644
--- a/deps/v8/src/snapshot/code-serializer.h
+++ b/deps/v8/src/snapshot/code-serializer.h
@@ -5,6 +5,8 @@
#ifndef V8_SNAPSHOT_CODE_SERIALIZER_H_
#define V8_SNAPSHOT_CODE_SERIALIZER_H_
+#include <optional>
+
#include "src/base/macros.h"
#include "src/codegen/script-details.h"
#include "src/snapshot/serializer.h"
@@ -57,21 +59,26 @@ typedef v8::ScriptCompiler::CachedData::CompatibilityCheckResult
// histogram definition.
static_assert(static_cast<int>(SerializedCodeSanityCheckResult::kLast) == 9);
-class CodeSerializer : public Serializer {
+class CodeSerializer;
+
+struct OffThreadDeserializeData {
public:
- struct OffThreadDeserializeData {
- public:
- bool HasResult() const { return !maybe_result.is_null(); }
- DirectHandle<Script> GetOnlyScript(LocalHeap* heap);
-
- private:
- friend class CodeSerializer;
- MaybeIndirectHandle<SharedFunctionInfo> maybe_result;
- std::vector<IndirectHandle<Script>> scripts;
- std::unique_ptr<PersistentHandles> persistent_handles;
- SerializedCodeSanityCheckResult sanity_check_result;
- };
+ bool HasResult() const { return !maybe_result.is_null(); }
+ DirectHandle<Script> GetOnlyScript(LocalHeap* heap);
+
+ private:
+ friend class CodeSerializer;
+ friend class SerializedCodeData;
+ MaybeIndirectHandle<SharedFunctionInfo> maybe_result;
+ std::vector<IndirectHandle<Script>> scripts;
+ std::unique_ptr<PersistentHandles> persistent_handles;
+ SerializedCodeSanityCheckResult sanity_check_result =
+ SerializedCodeSanityCheckResult::kSuccess;
+ std::optional<SerializedCodeSanityCheckResult> source_sanity_check_result;
+};
+class CodeSerializer : public Serializer {
+ public:
CodeSerializer(const CodeSerializer&) = delete;
CodeSerializer& operator=(const CodeSerializer&) = delete;
V8_EXPORT_PRIVATE static ScriptCompiler::CachedData* Serialize(
@@ -85,9 +92,14 @@ class CodeSerializer : public Serializer {
DirectHandle<String> source, const ScriptDetails& script_details,
MaybeDirectHandle<Script> maybe_cached_script = {});
- V8_WARN_UNUSED_RESULT static OffThreadDeserializeData
- StartDeserializeOffThread(LocalIsolate* isolate,
- AlignedCachedData* cached_data);
+ static void StartDeserializeOffThread(LocalIsolate* isolate,
+ AlignedCachedData* cached_data,
+ OffThreadDeserializeData* data);
+
+ V8_WARN_UNUSED_RESULT static bool NotifySourceTextAvailable(
+ Isolate* isolate, OffThreadDeserializeData* data,
+ const AlignedCachedData* cached_data, DirectHandle<String> source,
+ const ScriptDetails& script_details);
V8_WARN_UNUSED_RESULT static MaybeDirectHandle<SharedFunctionInfo>
FinishOffThreadDeserialize(
@@ -140,7 +152,9 @@ class SerializedCodeData : public SerializedData {
// FromCachedDataWithoutSource. The rejection result from that call should be
// passed into this one.
static SerializedCodeData FromPartiallySanityCheckedCachedData(
- AlignedCachedData* cached_data, uint32_t expected_source_hash,
+ Isolate* isolate, AlignedCachedData* cached_data,
+ const OffThreadDeserializeData& data, DirectHandle<String> source,
+ const ScriptDetails& script_details,
SerializedCodeSanityCheckResult* rejection_result);
// Used when producing.
@@ -157,7 +171,8 @@ class SerializedCodeData : public SerializedData {
ScriptOriginOptions origin_options);
private:
- explicit SerializedCodeData(AlignedCachedData* data);
+ friend class CodeSerializer;
+ explicit SerializedCodeData(const AlignedCachedData* data);
SerializedCodeData(const uint8_t* data, int size)
: SerializedData(const_cast<uint8_t*>(data), size) {}
@@ -169,6 +184,9 @@ class SerializedCodeData : public SerializedData {
SerializedCodeSanityCheckResult SanityCheck(
uint32_t expected_ro_snapshot_checksum,
uint32_t expected_source_hash) const;
+ SerializedCodeSanityCheckResult SanityCheckJustSource(
+ Isolate* isolate, DirectHandle<String> source,
+ const ScriptDetails& script_details) const;
SerializedCodeSanityCheckResult SanityCheckJustSource(
uint32_t expected_source_hash) const;
SerializedCodeSanityCheckResult SanityCheckWithoutSource(
diff --git a/deps/v8/test/mjsunit/es6/regress-class-member-init-reparse-await-yield-arguments.js b/deps/v8/test/mjsunit/es6/regress-class-member-init-reparse-await-yield-arguments.js
new file mode 100644
index 00000000000..d53e38336e3
--- /dev/null
+++ b/deps/v8/test/mjsunit/es6/regress-class-member-init-reparse-await-yield-arguments.js
@@ -0,0 +1,82 @@
+// Copyright 2026 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
+
+// Test reparsing of class member initializers when the immediately enclosing
+// function did not allocate a context (and is therefore absent from the
+// ScopeInfo chain).
+
+// 1. Computed property key using 'arguments' inside a contextless function
+// nested inside a class static block whose initializer allocates a context.
+{
+ let x = 1;
+ class Outer {
+ static {
+ () => this;
+ function foo() {
+ class Inner {
+ [arguments] = x();
+ }
+ new Inner();
+ }
+ assertThrows(foo, TypeError, "x is not a function");
+ }
+ }
+}
+
+// 2. Computed property key using 'await / 1' inside a contextless async
+// function where 'await / 1... /g' is an AwaitExpression of a RegExp literal.
+{
+ let x = 1;
+ async function inner() {
+ class C {
+ [await / 1]() { [[a] + 1 /g] = { a: x() }
+ }
+ new C();
+ }
+ let error;
+ inner().catch(e => { error = e; });
+ %PerformMicrotaskCheckpoint();
+ assertInstanceof(error, TypeError);
+ assertMatches(/x is not a function/, error.message);
+}
+
+// 3. Computed property key using 'yield' inside a contextless generator
+// function.
+{
+ let x = 1;
+ function* gen() {
+ class C {
+ [yield 1] = x();
+ }
+ return C;
+ }
+ let iter = gen();
+ assertEquals({ value: 1, done: false }, iter.next());
+ let C = iter.next("prop").value;
+ assertThrows(() => new C(), TypeError, "x is not a function");
+}
+
+// 4. Computed property key using 'await / 1' as division inside a contextless
+// synchronous function nested inside an async function that allocates a
+// context.
+{
+ var await = 2;
+ async function outer() {
+ let x = 1;
+ function sync_inner() {
+ class C {
+ [await / 1] = x();
+ }
+ new C();
+ }
+ sync_inner();
+ }
+ let error;
+ outer().catch(e => { error = e; });
+ %PerformMicrotaskCheckpoint();
+ assertInstanceof(error, TypeError);
+ assertMatches(/x is not a function/, error.message);
+}
diff --git a/deps/v8/test/mjsunit/maglev/regress-564326894.js b/deps/v8/test/mjsunit/maglev/regress-564326894.js
new file mode 100644
index 00000000000..0a48ca5b224
--- /dev/null
+++ b/deps/v8/test/mjsunit/maglev/regress-564326894.js
@@ -0,0 +1,29 @@
+// Copyright 2026 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 --maglev
+
+const arr = [1.5, ,];
+
+function foo(a) {
+ let x = a[0];
+ let y = a[0];
+ for (let i = 0; i < 5; i++) {
+ const t = (y | 0) + y;
+ y = x;
+ x = t;
+ }
+ return x;
+}
+
+%PrepareFunctionForOptimization(foo);
+assertEquals(8.5, foo(arr));
+assertEquals(8.5, foo(arr));
+%OptimizeMaglevOnNextCall(foo);
+assertEquals(8.5, foo(arr));
+assertOptimized(foo);
+
+// Passing a hole when feedback only saw Number must still deopt and return NaN.
+assertEquals(NaN, foo([, 1.5]));
+assertUnoptimized(foo);
diff --git a/deps/v8/test/mjsunit/maglev/regress-569286602-1.js b/deps/v8/test/mjsunit/maglev/regress-569286602-1.js
new file mode 100644
index 00000000000..b89ebdcb482
--- /dev/null
+++ b/deps/v8/test/mjsunit/maglev/regress-569286602-1.js
@@ -0,0 +1,24 @@
+// Copyright 2026 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 --maglev
+
+function f(r) {
+ let pos;
+ while ('a'.startsWith('b', pos)) { do { pos = 1; } while (pos); }
+ r.push(pos);
+}
+
+%PrepareFunctionForOptimization(f);
+let a = [{}];
+assertEquals(undefined, f(a));
+assertEquals([{}, undefined], a);
+let b = [1];
+assertEquals(undefined, f(b));
+assertEquals([1, undefined], b);
+
+%OptimizeMaglevOnNextCall(f);
+let c = [1];
+assertEquals(undefined, f(c));
+assertEquals([1, undefined], c);
diff --git a/deps/v8/test/mjsunit/maglev/regress-569286602-2.js b/deps/v8/test/mjsunit/maglev/regress-569286602-2.js
new file mode 100644
index 00000000000..dc186b862a9
--- /dev/null
+++ b/deps/v8/test/mjsunit/maglev/regress-569286602-2.js
@@ -0,0 +1,24 @@
+// Copyright 2026 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 --maglev
+
+function f(r) {
+ let pos;
+ while ('a'.startsWith('b', pos)) { do { pos = 1; } while (pos); }
+ if (r) r.push(pos);
+}
+
+%PrepareFunctionForOptimization(f);
+let a = [{}];
+assertEquals(undefined, f(a));
+assertEquals([{}, undefined], a);
+let b = [1];
+assertEquals(undefined, f(b));
+assertEquals([1, undefined], b);
+
+%OptimizeMaglevOnNextCall(f);
+let c = [1];
+assertEquals(undefined, f(c));
+assertEquals([1, undefined], c);
diff --git a/deps/v8/test/mjsunit/mjsunit.status b/deps/v8/test/mjsunit/mjsunit.status
index af6517a6a8f..5974a6eb18b 100644
--- a/deps/v8/test/mjsunit/mjsunit.status
+++ b/deps/v8/test/mjsunit/mjsunit.status
@@ -2943,6 +2943,7 @@
'regress/wasm/regress-543901836': [SKIP],
'regress/wasm/regress-560536735': [SKIP],
'regress/wasm/regress-561660149': [SKIP],
+ 'regress/wasm/regress-574223268': [SKIP],
'compiler/regress-347724915': [SKIP],
'regress/regress-crbug-418520151': [SKIP],
'regress/regress-535000102': [SKIP],
diff --git a/deps/v8/test/mjsunit/regress/regress-566091009.js b/deps/v8/test/mjsunit/regress/regress-566091009.js
new file mode 100644
index 00000000000..aaf8c217d3c
--- /dev/null
+++ b/deps/v8/test/mjsunit/regress/regress-566091009.js
@@ -0,0 +1,69 @@
+// Copyright 2026 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-lazy-feedback-allocation --allow-natives-syntax
+
+(function TestAccessCheck() {
+ const target = d8.test.createAccessCheckedObject(true);
+ target[0] = "secret";
+ d8.test.setAccessPolicy(target, false);
+
+ const namedObj = { a: 1 };
+
+ // 1. KeyedLoadIC: transition from named property access to element access.
+ function read(obj, key) {
+ return obj[key];
+ }
+ read(namedObj, "a");
+ assertThrows(() => read(target, "a"), TypeError);
+ read([1.1], 0);
+ assertThrows(() => read(target, 0), TypeError);
+
+ // 2. KeyedHasIC: transition from named property access to element access.
+ function has(obj, key) {
+ return key in obj;
+ }
+ has(namedObj, "a");
+ assertThrows(() => has(target, "a"), TypeError);
+ has([1.1], 0);
+ assertThrows(() => has(target, 0), TypeError);
+
+ // 3. KeyedStoreIC: transition from named property access to element access.
+ function write(obj, key, val) {
+ obj[key] = val;
+ }
+ write(namedObj, "a", 2);
+ assertThrows(() => write(target, "a", 2), TypeError);
+ write([1.1], 0, 2.2);
+ assertThrows(() => write(target, 0, "overwritten"), TypeError);
+
+ d8.test.setAccessPolicy(target, true);
+ assertEquals("secret", target[0]);
+})();
+
+(function TestNoElementsProtector() {
+ function readHole(a) {
+ return a[0];
+ }
+ const holey = [, 1];
+ %PrepareFunctionForOptimization(readHole);
+ assertEquals(undefined, readHole(holey));
+ %OptimizeFunctionOnNextCall(readHole);
+ assertEquals(undefined, readHole(holey));
+
+ Object.defineProperty(Object.prototype, "prop", {
+ set(_) {},
+ configurable: true,
+ });
+ function write(o, k, v) {
+ o[k] = v;
+ }
+ write({ prop: 1 }, "prop", 1);
+ write(Array.prototype, "prop", 1);
+ write([], 0, 1);
+ write(Array.prototype, 0, "boom");
+
+ assertEquals("boom", holey[0]);
+ assertEquals("boom", readHole(holey));
+})();
diff --git a/deps/v8/test/mjsunit/regress/regress-570003644-1.js b/deps/v8/test/mjsunit/regress/regress-570003644-1.js
new file mode 100644
index 00000000000..f56634f7a54
--- /dev/null
+++ b/deps/v8/test/mjsunit/regress/regress-570003644-1.js
@@ -0,0 +1,110 @@
+// Copyright 2026 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: --maglev-non-eager-inlining --expose-gc --allow-natives-syntax
+
+class D {
+ constructor(x) {
+ this.x = x;
+ this.p = 7;
+ this.q = 9;
+ }
+}
+
+// Map: MD1 where x is const SMI
+let dOld = new D(1);
+
+// Map: MD2 (after adding "tail")
+// Map transition: MD1 --tail--> MD2
+let dExtra = new D(1);
+dExtra.tail = 1;
+
+class C {
+ constructor(z) {
+ this.z = z;
+ this.q = 11;
+ }
+}
+
+// Map: MC1 where z is a const SMI
+let cOld = new C(1);
+
+// Map: MC2 w where z is a const HeapNumber
+let cTarget = new C(1.5);
+
+// Migrate to MC2: this makes MC2 a migration target
+cOld.z = 2.5;
+
+// Larger than the eager-inline bytecode limit.
+function noop() {
+ let n = 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ return n;
+}
+%PrepareFunctionForOptimization(noop);
+
+function polyQ(o) {
+ noop(); // Not inlined
+ // MigrateMapIfNeeded, then a polymorphic branch with MC2 and MD1 (in this
+ // order).
+ return o.q;
+}
+%PrepareFunctionForOptimization(polyQ);
+
+polyQ(cTarget);
+polyQ(dOld);
+
+let smi_array = [0, 0];
+
+function victim(cObj, dObj) {
+ // CheckMapsWithMigration(MC2) cObj; tries to migrate to MC2 (but the cTarget we
+ // pass already has MC2).
+ cObj.q;
+
+ // CheckMaps(MD1) dObj
+ dObj.p;
+
+ // Eagerly inlining polyQ. Here we need to conservatively assume that the noop
+ // call might change the maps, so we get:
+ // CallFunction noop
+ // MigrateMapIfNeeded dObj
+ // Polymoprhic branch (MC2, MD1):
+ // /-- Branch if dObj's map is MC2
+ // | |
+ // | v
+ // | CheckMap(MD1) dObj <<< Load-bearing! Must not be eliminated
+ // | |
+ // | v
+ // \--> Result unused, so this is empty
+ polyQ(dObj);
+
+ // The map better be MD1 or this goes wrong.
+ smi_array[0] = dObj.x;
+}
+%PrepareFunctionForOptimization(victim);
+victim(cTarget, dOld);
+
+%OptimizeMaglevOnNextCall(victim);
+
+victim(cTarget, dOld);
+
+// Move the smi_array backing store to old space before storing a young pointer.
+gc();
+
+// Create a new map MD3 where x is a HeapNumber; MD1 deprecated.
+new D(1.5);
+
+// Now the MigrateMapIfNeeded will migrate dOld to MD3 where x is a HeapNumber.
+victim(cTarget, dOld);
diff --git a/deps/v8/test/mjsunit/regress/regress-570003644-2.js b/deps/v8/test/mjsunit/regress/regress-570003644-2.js
new file mode 100644
index 00000000000..fe9c96dd215
--- /dev/null
+++ b/deps/v8/test/mjsunit/regress/regress-570003644-2.js
@@ -0,0 +1,111 @@
+// Copyright 2026 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: --maglev-non-eager-inlining --expose-gc --allow-natives-syntax
+// Flags: --max-maglev-eager-inlined-bytecode-size=0
+
+class D {
+ constructor(x) {
+ this.x = x;
+ this.p = 7;
+ this.q = 9;
+ }
+}
+
+// Map: MD1 where x is const SMI
+let dOld = new D(1);
+
+// Map: MD2 (after adding "tail")
+// Map transition: MD1 --tail--> MD2
+let dExtra = new D(1);
+dExtra.tail = 1;
+
+class C {
+ constructor(z) {
+ this.z = z;
+ this.q = 11;
+ }
+}
+
+// Map: MC1 where z is a const SMI
+let cOld = new C(1);
+
+// Map: MC2 w where z is a const HeapNumber
+let cTarget = new C(1.5);
+
+// Migrate to MC2: this makes MC2 a migration target
+cOld.z = 2.5;
+
+// Larger than the eager-inline bytecode limit.
+function noop() {
+ let n = 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ n += 0;
+ return n;
+}
+%PrepareFunctionForOptimization(noop);
+
+function polyQ(o) {
+ noop(); // Not inlined
+ // MigrateMapIfNeeded, then a polymorphic branch with MC2 and MD1 (in this
+ // order).
+ return o.q;
+}
+%PrepareFunctionForOptimization(polyQ);
+
+polyQ(cTarget);
+polyQ(dOld);
+
+let smi_array = [0, 0];
+
+function victim(cObj, dObj) {
+ // CheckMapsWithMigration(MC2) cObj; tries to migrate to MC2 (but the cTarget we
+ // pass already has MC2).
+ cObj.q;
+
+ // CheckMaps(MD1) dObj
+ dObj.p;
+
+ // Eventually we'l inline polyQ here. Here we need to conservatively assume
+ // that the noop call might change the maps, so we get:
+ // CallFunction noop
+ // MigrateMapIfNeeded dObj
+ // Polymoprhic branch (MC2, MD1):
+ // /-- Branch if dObj's map is MC2
+ // | |
+ // | v
+ // | CheckMap(MD1) dObj <<< Load-bearing! Must not be eliminated
+ // | |
+ // | v
+ // \--> Result unused, so this is empty
+ polyQ(dObj);
+
+ // The map better be MD1 or this goes wrong.
+ smi_array[0] = dObj.x;
+}
+%PrepareFunctionForOptimization(victim);
+victim(cTarget, dOld);
+
+%OptimizeMaglevOnNextCall(victim);
+
+victim(cTarget, dOld);
+
+// Move the smi_array backing store to old space before storing a young pointer.
+gc();
+
+// Create a new map MD3 where x is a HeapNumber; MD1 deprecated.
+new D(1.5);
+
+// Now the MigrateMapIfNeeded will migrate dOld to MD3 where x is a HeapNumber.
+victim(cTarget, dOld);
diff --git a/deps/v8/test/mjsunit/regress/regress-570203091-1.js b/deps/v8/test/mjsunit/regress/regress-570203091-1.js
new file mode 100644
index 00000000000..2ac6c407c77
--- /dev/null
+++ b/deps/v8/test/mjsunit/regress/regress-570203091-1.js
@@ -0,0 +1,29 @@
+// Copyright 2026 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: --turbolev --allow-natives-syntax
+
+let sink = 0;
+function* gen() {
+ try { unknown_global; } catch {}
+ let x = 0;
+ for (;;) {
+ if (x === 0) {
+ sink = 1;
+ } else {
+ sink = 2;
+ }
+ const is_zero = x === 0 ? true : false;
+ x = (yield is_zero).length;
+ }
+}
+
+%PrepareFunctionForOptimization(gen);
+const g = gen();
+g.next(); // x = 0
+g.next("a"); // x = 1
+g.next(""); // x = 0
+
+%OptimizeFunctionOnNextCall(gen);
+assertEquals(false, g.next("hello").value);
diff --git a/deps/v8/test/mjsunit/regress/regress-570203091-2.js b/deps/v8/test/mjsunit/regress/regress-570203091-2.js
new file mode 100644
index 00000000000..f99037288cd
--- /dev/null
+++ b/deps/v8/test/mjsunit/regress/regress-570203091-2.js
@@ -0,0 +1,35 @@
+// Copyright 2026 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: --turbolev --allow-natives-syntax
+
+let sink = 0;
+const arr = [1.1, 2.2];
+const victim = [3.3];
+
+function* gen() {
+ try { unknown_global; } catch {}
+ let x = 0;
+ for (;;) {
+ if (x === 0) {
+ sink = 1;
+ } else {
+ sink = 2;
+ }
+ if (arr.length <= 0) return;
+ arr[x] = 1.5;
+ x = (yield).length;
+ }
+}
+
+%PrepareFunctionForOptimization(gen);
+const g = gen();
+g.next(); // x = 0
+g.next("a"); // x = 1
+g.next(""); // x = 0
+
+%OptimizeFunctionOnNextCall(gen);
+g.next("abcdef");
+assertEquals(3.3, victim[0]);
+assertEquals(7, arr.length);
diff --git a/deps/v8/test/mjsunit/regress/wasm/regress-574223268.js b/deps/v8/test/mjsunit/regress/wasm/regress-574223268.js
new file mode 100644
index 00000000000..edbbaa76ede
--- /dev/null
+++ b/deps/v8/test/mjsunit/regress/wasm/regress-574223268.js
@@ -0,0 +1,55 @@
+// Copyright 2026 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 --stress-compaction
+
+d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
+
+function buildModule(numTargets, tableSize = numTargets) {
+ const builder = new WasmModuleBuilder();
+ builder.addMemory(1, 1);
+ const sig_v_v = builder.addType(kSig_v_v);
+ const sig_v_i = builder.addType(kSig_v_i);
+ const funcs = [];
+ for (let i = 0; i < numTargets; i++) {
+ const f = builder.addFunction('f' + i, sig_v_v)
+ .addBody([
+ kExprI32Const, 0,
+ kExprI32Const, 42 + i,
+ kExprI32StoreMem, 0, 0,
+ ])
+ .exportFunc();
+ funcs.push(f.index);
+ }
+ builder.addFunction('call', sig_v_i)
+ .addBody([
+ kExprLocalGet, 0,
+ kExprCallIndirect, sig_v_v, kTableZero,
+ ])
+ .exportFunc();
+ builder.setTableBounds(tableSize, tableSize);
+ builder.addActiveElementSegment(0, wasmI32Const(0), funcs);
+ return builder.toModule();
+}
+
+// Fill the bootstrap trusted page(s) (which are marked never_evacuate) so that
+// subsequent trusted objects land on movable pages.
+const filler = [];
+const fillerModule = buildModule(1, 2000);
+for (let i = 0; i < 64; i++) {
+ filler.push(new WebAssembly.Instance(fillerModule));
+}
+
+const targetModule = buildModule(2);
+const instance = new WebAssembly.Instance(targetModule);
+
+// Compile both callees up front.
+instance.exports.f0();
+instance.exports.f1();
+// Make the call site monomorphic on target 0.
+instance.exports.call(0);
+// Force the monomorphic-miss FixedArray allocation inside CallIndirectIC to
+// trigger a compacting GC while WasmTrustedInstanceData is on the stack.
+%SimulateNewspaceFull();
+instance.exports.call(1);
diff --git a/deps/v8/test/mjsunit/turbolev/regress-567129324.js b/deps/v8/test/mjsunit/turbolev/regress-567129324.js
new file mode 100644
index 00000000000..1abeaeaafc1
--- /dev/null
+++ b/deps/v8/test/mjsunit/turbolev/regress-567129324.js
@@ -0,0 +1,38 @@
+// Copyright 2026 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 --turbolev
+// Flags: --max-turbolev-eager-inlined-bytecode-size=0
+
+// The graph optimizer must not replace a TruncateChecked...ToInt32[Number]
+// with a cached truncation that only assumed NumberOrOddball: the Number check
+// narrows `x` (Smi|Boolean) to Smi, which the Smi-field store relies on.
+// `alt` must not be eagerly inlined, so that its truncation is only added to
+// the graph by the inliner, after `x | y` has been built.
+
+function alt(x) {
+ return Math.imul(x, 3);
+}
+
+const obj = { b: 123 };
+
+function f(flag, y, obj) {
+ const x = flag ? true : 123;
+ const a = alt(x);
+ const b = x | y;
+ obj.b = x;
+ return b + a;
+}
+
+%PrepareFunctionForOptimization(alt);
+%PrepareFunctionForOptimization(f);
+f(false, 0.125, obj);
+%OptimizeFunctionOnNextCall(f);
+assertEquals(123 + 369, f(false, 0.125, obj));
+assertOptimized(f);
+
+assertEquals(1 + 3, f(true, 0.125, obj));
+assertUnoptimized(f);
+assertEquals(true, obj.b);
+%HeapObjectVerify(obj);
diff --git a/deps/v8/test/mjsunit/turbolev/regress-568348290.js b/deps/v8/test/mjsunit/turbolev/regress-568348290.js
new file mode 100644
index 00000000000..2f39a98cc33
--- /dev/null
+++ b/deps/v8/test/mjsunit/turbolev/regress-568348290.js
@@ -0,0 +1,149 @@
+// Copyright 2026 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 --turbolev
+// Flags: --max-turbolev-eager-inlined-bytecode-size=0
+
+// 1. Unwrapping ReturnedValue(HoleyFloat64) for a kNumber truncation must
+// preserve the number check via CheckedHoleyFloat64ToFloat64.
+(function TestReturnedValueHoleyFloat64() {
+ function get_elem(arr, i) {
+ return arr[i];
+ }
+
+ function opt(arr, i) {
+ const x = get_elem(arr, i);
+ const y = x | 0;
+ return String(x) + y;
+ }
+
+ const holey = [1.5, , 2.5];
+ %PrepareFunctionForOptimization(get_elem);
+ %PrepareFunctionForOptimization(opt);
+ assertEquals('1.51', opt(holey, 0));
+ assertEquals(undefined, get_elem(holey, 1));
+ assertEquals('1.51', opt(holey, 0));
+ %OptimizeFunctionOnNextCall(opt);
+ assertEquals('1.51', opt(holey, 0));
+ assertOptimized(opt);
+
+ assertEquals('undefined0', opt(holey, 1));
+ assertUnoptimized(opt);
+})();
+
+// 2. Unwrapping HoleyFloat64ToTagged(HoleyFloat64) for a kNumber truncation
+// must also preserve the number check.
+(function TestHoleyFloat64ToTaggedConversion() {
+ function get_elem_tagged(arr, i, flag) {
+ let v = arr[i];
+ if (flag) {
+ v = arr[0];
+ }
+ return v;
+ }
+
+ function opt(arr, i, flag) {
+ const x = get_elem_tagged(arr, i, flag);
+ const y = x | 0;
+ return String(x) + y;
+ }
+
+ const holey = [1.5, , 2.5];
+ %PrepareFunctionForOptimization(get_elem_tagged);
+ %PrepareFunctionForOptimization(opt);
+ assertEquals('1.51', opt(holey, 0, false));
+ assertEquals(undefined, get_elem_tagged(holey, 1, false));
+ assertEquals(1.5, get_elem_tagged(holey, 1, true));
+ assertEquals('1.51', opt(holey, 0, false));
+ %OptimizeFunctionOnNextCall(opt);
+ assertEquals('1.51', opt(holey, 0, false));
+ assertOptimized(opt);
+
+ assertEquals('undefined0', opt(holey, 1, false));
+ assertUnoptimized(opt);
+})();
+
+// 3. A prior weaker truncation (NumberOrOddball via Math.imul) on the same
+// HoleyFloat64 node must not cause a subsequent kNumber truncation to skip
+// CheckedHoleyFloat64ToFloat64.
+(function TestWeakerTruncationBeforeNumberTruncation() {
+ function get_elem(arr, i) {
+ return arr[i];
+ }
+
+ function imul(x) {
+ return Math.imul(x, 3);
+ }
+
+ function opt(arr, i) {
+ const x = get_elem(arr, i);
+ const a = imul(x);
+ const b = x | 0;
+ return String(x) + a + b;
+ }
+
+ const holey = [1.5, , 2.5];
+ %PrepareFunctionForOptimization(get_elem);
+ %PrepareFunctionForOptimization(imul);
+ %PrepareFunctionForOptimization(opt);
+ assertEquals('1.531', opt(holey, 0));
+ assertEquals(undefined, get_elem(holey, 1));
+ assertEquals('1.531', opt(holey, 0));
+ %OptimizeFunctionOnNextCall(opt);
+ assertEquals('1.531', opt(holey, 0));
+ assertOptimized(opt);
+
+ assertEquals('undefined00', opt(holey, 1));
+ assertUnoptimized(opt);
+})();
+
+// 4. Merging kSmi and kJSReceiverOrNullOrUndefined and intersecting with
+// kNumber on `x | y` narrows `x` to kSmi, eliding CheckSmi on a subsequent
+// Smi-field store. Dropping the kNumber check corrupts `smi_obj.a` with
+// #undefined in a Smi field.
+(function TestSmiFieldCorruptionViaTypeIntersection() {
+ function get_elem(arr, i) {
+ return arr[i];
+ }
+
+ const smi_obj = {a: 100};
+ smi_obj.a = 1;
+
+ function opt(arr, i, mode, bailout, y, obj, n) {
+ const x = get_elem(arr, i);
+ let cmp = false;
+ if (mode === 1) {
+ cmp = (x + 1) === 2;
+ } else {
+ cmp = (x === n);
+ if (bailout) return 0;
+ }
+ const b = x | y;
+ obj.a = x;
+ return b + (cmp ? 1 : 0);
+ }
+
+ const smi_arr = [1, 2];
+ const holey = [1, , 2.5];
+
+ %PrepareFunctionForOptimization(opt);
+ opt(smi_arr, 0, 1, false, 0.125, smi_obj, null);
+ opt(smi_arr, 0, 1, false, 0.125, smi_obj, null);
+
+ %PrepareFunctionForOptimization(get_elem);
+ get_elem(holey, 0);
+ get_elem(holey, 1);
+
+ opt(holey, 1, 0, true, 0.125, smi_obj, null);
+ opt(holey, 1, 0, true, 0.125, smi_obj, null);
+
+ %OptimizeFunctionOnNextCall(opt);
+ assertEquals(2, opt(holey, 0, 1, false, 0.125, smi_obj, null));
+ assertOptimized(opt);
+
+ assertEquals(0, opt(holey, 1, 0, false, 0.125, smi_obj, null));
+ assertUnoptimized(opt);
+ assertEquals(undefined, smi_obj.a);
+ %HeapObjectVerify(smi_obj);
+})();
diff --git a/deps/v8/test/unittests/api/deserialize-unittest.cc b/deps/v8/test/unittests/api/deserialize-unittest.cc
index ecd607e46fe..fb9fb9983b7 100644
--- a/deps/v8/test/unittests/api/deserialize-unittest.cc
+++ b/deps/v8/test/unittests/api/deserialize-unittest.cc
@@ -1333,4 +1333,148 @@ TEST_F(MergeDeserializedCodeTest, MergeThatStartsButDoesNotFinish) {
}
}
+TEST_F(MergeDeserializedCodeTest, ModuleCacheClassicScriptMismatch) {
+ i::v8_flags.merge_background_deserialized_script_with_compilation_cache =
+ true;
+ std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
+ IsolateAndContextScope scope(this);
+ i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate());
+ PersistentScriptOrigin default_origin(
+ isolate(), NewString("http://example.com/test.js"));
+
+ constexpr char kPolyglotSource[] =
+ "let L0 = 1;\n"
+ "var z, o;\n"
+ "function g() { throw 1; }\n"
+ "function K(a, b) { var t = globalThis.top; (t.__keep = t.__keep || "
+ "[]).push(a, b); }\n"
+ "z = await /1; function lazyOuter() { function inner() {} } { try { g() "
+ "} catch /* 1, o = { a: function () {}, 'NAMENAMENAMENAME' //*/\n"
+ "(e) { K(() => e, function later() { return e }) } }\n";
+
+ // Setup: define `await` and `top` in global scope for classic script parse.
+ {
+ v8::HandleScope handle_scope(isolate());
+ Local<Script> setup =
+ Script::Compile(
+ context(), NewString("var await = 6; globalThis.top = globalThis;"))
+ .ToLocalChecked();
+ CHECK(!setup->Run(context()).IsEmpty());
+ }
+
+ // Step 1: Compile and run as a classic script, populating the Isolate
+ // compilation cache and retaining catch-block closures in globalThis.__keep.
+ // Also compile as a module with the same resource name and create a module
+ // code cache, then age and flush the classic script's top-level bytecode.
+ {
+ v8::HandleScope handle_scope(isolate());
+ ScriptOrigin classic_origin = default_origin.AsScriptOrigin();
+ Local<Script> script =
+ Script::Compile(context(), NewString(kPolyglotSource), &classic_origin)
+ .ToLocalChecked();
+ CHECK(!script->Run(context()).IsEmpty());
+
+ ScriptOrigin module_origin(NewString("http://example.com/test.js"), 0, 0,
+ false, -1, Local<Value>(), false, false,
+ /*is_module=*/true);
+ ScriptCompiler::Source module_source(NewString(kPolyglotSource),
+ module_origin);
+ Local<Module> module =
+ ScriptCompiler::CompileModule(isolate(), &module_source)
+ .ToLocalChecked();
+ cached_data.reset(
+ ScriptCompiler::CreateCodeCache(module->GetUnboundModuleScript()));
+
+ i::SharedFunctionInfo::EnsureOldForTesting(GetSharedFunctionInfo(script));
+ }
+
+ // Step 2: Flush the classic script's top-level bytecode via major GC.
+ {
+ i::DisableConservativeStackScanningScopeForTesting no_css_scope(
+ i_isolate->heap());
+ InvokeMajorGC(i_isolate);
+ InvokeMajorGC(i_isolate);
+ }
+
+ // Step 3a: Start off-thread consumption of the MODULE code cache, then notify
+ // SourceTextAvailable with the CLASSIC ScriptOrigin after Run() completes.
+ {
+ auto task_cached_data = std::make_unique<ScriptCompiler::CachedData>(
+ cached_data->data, cached_data->length,
+ ScriptCompiler::CachedData::BufferNotOwned);
+ DeserializeThread deserialize_thread(
+ ScriptCompiler::StartConsumingCodeCache(
+ isolate(), std::make_unique<ScriptCompiler::CachedData>(
+ cached_data->data, cached_data->length,
+ ScriptCompiler::CachedData::BufferNotOwned)));
+ CHECK(deserialize_thread.Start());
+ deserialize_thread.Join();
+
+ std::unique_ptr<ScriptCompiler::ConsumeCodeCacheTask> task =
+ deserialize_thread.TakeTask();
+ task->SourceTextAvailable(isolate(), NewString(kPolyglotSource),
+ default_origin.AsScriptOrigin());
+ CHECK(!task->ShouldMergeWithExistingScript());
+
+ v8::HandleScope handle_scope(isolate());
+ ScriptCompiler::Source source(NewString(kPolyglotSource),
+ default_origin.AsScriptOrigin(),
+ task_cached_data.release(), task.release());
+ Local<Script> script =
+ ScriptCompiler::Compile(context(), &source,
+ ScriptCompiler::kConsumeCodeCache)
+ .ToLocalChecked();
+ CHECK(!script.IsEmpty());
+ CHECK(source.GetCachedData()->rejected);
+
+ // Age and flush the recompiled classic script's top-level bytecode again so
+ // Step 3b also sees a flushed Script in the Isolate compilation cache.
+ i::SharedFunctionInfo::EnsureOldForTesting(GetSharedFunctionInfo(script));
+ }
+ {
+ i::DisableConservativeStackScanningScopeForTesting no_css_scope(
+ i_isolate->heap());
+ InvokeMajorGC(i_isolate);
+ InvokeMajorGC(i_isolate);
+ }
+
+ // Step 3b: Also test calling SourceTextAvailable on the main thread BEFORE
+ // Run() executes on the background thread.
+ {
+ auto task_cached_data = std::make_unique<ScriptCompiler::CachedData>(
+ cached_data->data, cached_data->length,
+ ScriptCompiler::CachedData::BufferNotOwned);
+ std::unique_ptr<ScriptCompiler::ConsumeCodeCacheTask> task(
+ ScriptCompiler::StartConsumingCodeCache(
+ isolate(), std::make_unique<ScriptCompiler::CachedData>(
+ cached_data->data, cached_data->length,
+ ScriptCompiler::CachedData::BufferNotOwned)));
+ task->SourceTextAvailable(isolate(), NewString(kPolyglotSource),
+ default_origin.AsScriptOrigin());
+
+ DeserializeThread deserialize_thread(task.release());
+ CHECK(deserialize_thread.Start());
+ deserialize_thread.Join();
+ task = deserialize_thread.TakeTask();
+ CHECK(!task->ShouldMergeWithExistingScript());
+
+ v8::HandleScope handle_scope(isolate());
+ ScriptCompiler::Source source(NewString(kPolyglotSource),
+ default_origin.AsScriptOrigin(),
+ task_cached_data.release(), task.release());
+ Local<Script> script =
+ ScriptCompiler::Compile(context(), &source,
+ ScriptCompiler::kConsumeCodeCache)
+ .ToLocalChecked();
+ CHECK(!script.IsEmpty());
+ CHECK(source.GetCachedData()->rejected);
+
+ Local<Script> run_kept =
+ Script::Compile(context(),
+ NewString("for (const f of globalThis.__keep) f();"))
+ .ToLocalChecked();
+ CHECK(!run_kept->Run(context()).IsEmpty());
+ }
+}
+
} // namespace v8