Repository: electron/electron
Pull request: fix: reduce stack memory consumption in BytecodeGenerator

#### Description of Change

For https://github.com/microsoft/vscode/issues/283403

Backports

1) https://chromium-review.googlesource.com/c/v8/v8/+/7180480
2) https://chromium-review.googlesource.com/c/v8/v8/+/7160576
3) https://chromium-review.googlesource.com/c/v8/v8/+/7062734

2 and 3 are needed to cleanly land 1. However, most of the code changes are **noop** since `v8_flags.proto_assign_seq_opt` is experimental in this branch and disabled by default. The reason why stack memory consumption is improved for all scenarios can be found in https://github.com/microsoft/vscode/issues/283403#issuecomment-3737968271

**NB:** Changes are present in M144 and higher

#### Release Notes

Notes: fix stack overflow when compiling deeply nested JS functions

Changed files:
filename: patches/v8/.patches
patch: @@ -1,2 +1,5 @@
 chore_allow_customizing_microtask_policy_per_context.patch
 turboshaft_avoid_introducing_too_many_variables.patch
+runtime_setprototypeproperties_handling_of.patch
+runtime_correcting_setprototypeproperties.patch
+reduce_stack_memory_consumption_in_bytecodegenerator.patch

filename: patches/v8/reduce_stack_memory_consumption_in_bytecodegenerator.patch
patch: @@ -0,0 +1,263 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jakob Kummerow <jkummerow@chromium.org>
+Date: Fri, 21 Nov 2025 11:52:15 +0100
+Subject: Reduce stack memory consumption in BytecodeGenerator
+
+Using a SmallVector with 64 stack-allocated entries is not great
+for functions that can be used in deep recursions, so this patch
+replaces that with a ZoneVector.
+By allocating any backing store only when it's actually needed,
+and presizing that initial backing store when that happens, this
+should have similar performance while using a lot less stack space,
+allowing us to compile more deeply nested expressions.
+
+For the highly artificial example of a function full of nested
+empty blocks `function f() {{{{{...}}}}}`, this increases the max
+nesting level from around 670 to around 2600. With more aggressive
+inlining in official builds, it can have similar effects on patterns
+that don't even call `VisitStatements()` recursively.
+
+Bug: 429332174
+Change-Id: Id11e15ba0fd9bc39efa68bf83e1181fe0e7274a7
+Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7180480
+Reviewed-by: Raphael Herouart <rherouart@chromium.org>
+Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
+Reviewed-by: Leszek Swirski <leszeks@chromium.org>
+Cr-Commit-Position: refs/heads/main@{#103870}
+
+diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc
+index 63400abcc7ca0bd5369d566dd37e2ae93d7c3efe..a1cf142edc4c0224731ebab988bcbf9272acd57e 100644
+--- a/src/interpreter/bytecode-generator.cc
++++ b/src/interpreter/bytecode-generator.cc
+@@ -2306,10 +2306,7 @@ void BytecodeGenerator::VisitDeclarations(Declaration::List* declarations) {
+ // The subsequent ones must be about the same <var> to return true.
+ 
+ bool BytecodeGenerator::IsPrototypeAssignment(
+-    Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
+-    SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+-                    kInitialPropertyCount>& properties,
+-    std::unordered_set<const AstRawString*>& duplicates) {
++    Statement* stmt, std::unique_ptr<PrototypeAssignments>* assignments) {
+   // The expression Statement is an assignment
+   // ========================================
+   ExpressionStatement* expr_stmt = stmt->AsExpressionStatement();
+@@ -2381,41 +2378,46 @@ bool BytecodeGenerator::IsPrototypeAssignment(
+     return false;
+   }
+ 
+-  if (!duplicates.insert(prop_str).second) {
+-    return false;
+-  }
+-
+-  if (*var == nullptr) {
+-    // This is the first proto assignment in the sequence
+-    *var = tmp_var;
+-    *hole_check_mode = proto_prop->obj()->AsVariableProxy()->hole_check_mode();
+-  } else if (*var != tmp_var) {
+-    // This prototype assignment is about another var
+-    return false;
++  if (!*assignments) {
++    // This is the first prototype assignment in the sequence.
++    *assignments = std::make_unique<PrototypeAssignments>(
++        tmp_var, proto_prop->obj()->AsVariableProxy()->hole_check_mode(),
++        ZoneVector<PrototypeAssignment>(zone()));
++    (*assignments)->properties.reserve(8);
++    (*assignments)->duplicates.insert(prop_str);
++  } else {
++    if (!(*assignments)->duplicates.insert(prop_str).second) return false;
++    if ((*assignments)->var != tmp_var) {
++      // This prototype assignment is about another var.
++      return false;
++    }
++    DCHECK_EQ((*assignments)->hole_check_mode,
++              proto_prop->obj()->AsVariableProxy()->hole_check_mode());
+   }
+ 
+-  // Success
+-  properties.push_back(std::make_pair(
+-      prop_str,
+-      value));  // This will be reused as part of an ObjectLiteral
++  // Success!
++  (*assignments)
++      ->properties.push_back(std::make_pair(
++          prop_str,
++          value));  // This will be reused as part of an ObjectLiteral.
+ 
+-  DCHECK_EQ(*hole_check_mode,
+-            proto_prop->obj()->AsVariableProxy()->hole_check_mode());
+   return true;
+ }
+ 
+ void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
+-    const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+-                          kInitialPropertyCount>& properties,
+-    Variable* var, HoleCheckMode hole_check_mode) {
++    std::unique_ptr<PrototypeAssignments> assignments) {
+   // Create a boiler plate object in the constant pool to be merged into the
+-  // proto
++  // proto.
+   size_t entry = builder()->AllocateDeferredConstantPoolEntry();
+-  proto_assign_seq_.push_back(std::make_pair(
+-      zone()->New<ProtoAssignmentSeqBuilder>(properties), entry));
++  proto_assign_seq_.push_back(
++      std::make_pair(zone()->New<ProtoAssignmentSeqBuilder>(
++                         std::move(assignments->properties)),
++                     entry));
++  const ZoneVector<PrototypeAssignment>* props =
++      proto_assign_seq_.back().first->properties();
+ 
+   int first_idx = -1;
+-  for (auto& p : properties) {
++  for (auto& p : *props) {
+     auto func = p.second->AsFunctionLiteral();
+     if (func) {
+       int idx = GetNewClosureSlot(func);
+@@ -2426,13 +2428,13 @@ void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
+     }
+   }
+ 
+-  // We need it to be valid, even if unused
++  // We need {first_idx} to be valid, even if it's unused.
+   if (first_idx == -1) {
+     first_idx = 0;
+   }
+-  // Load the variable whose prototype is to be set into the Accumulator
+-  BuildVariableLoad(var, hole_check_mode);
+-  // Merge in-place proto-def boilerplate object into Accumulator
++  // Load the variable whose prototype is to be set into the Accumulator.
++  BuildVariableLoad(assignments->var, assignments->hole_check_mode);
++  // Merge in-place proto-def boilerplate object into the Accumulator.
+   builder()->SetPrototypeProperties(entry, first_idx);
+ }
+ 
+@@ -2440,25 +2442,23 @@ void BytecodeGenerator::VisitStatements(
+     const ZonePtrList<Statement>* statements, int start) {
+   for (int stmt_idx = start; stmt_idx < statements->length(); stmt_idx++) {
+     if (v8_flags.proto_assign_seq_opt) {
+-      Variable* var = nullptr;
+-      HoleCheckMode hole_check_mode;
+-
+       int proto_assign_idx = stmt_idx;
+-      SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+-                      kInitialPropertyCount>
+-          properties(zone());
+-      std::unordered_set<const AstRawString*> duplicates;
++      // {VisitStatements} can be used for deep recursions, so this is a
++      // stack-friendly design: statically we only need one {unique_ptr}, and
++      // the actual storage is heap-allocated when it is needed.
++      std::unique_ptr<PrototypeAssignments> assignments;
+       while (proto_assign_idx < statements->length() &&
+-             IsPrototypeAssignment(statements->at(proto_assign_idx), &var,
+-                                   &hole_check_mode, properties, duplicates)) {
++             IsPrototypeAssignment(statements->at(proto_assign_idx),
++                                   &assignments)) {
+         ++proto_assign_idx;
+       }
+ 
+       if (proto_assign_idx - stmt_idx > 1) {
+-        DCHECK_EQ((size_t)(proto_assign_idx - stmt_idx), properties.size());
+-        VisitConsecutivePrototypeAssignments(properties, var, hole_check_mode);
+-        stmt_idx = proto_assign_idx - 1;  // the outer loop should now ignore
+-                                          // these statements
++        DCHECK_EQ(static_cast<size_t>(proto_assign_idx - stmt_idx),
++                  assignments->properties.size());
++        VisitConsecutivePrototypeAssignments(std::move(assignments));
++        stmt_idx = proto_assign_idx - 1;  // The outer loop should now ignore
++                                          // these statements.
+         DCHECK(!builder()->RemainderOfBlockIsDead());
+         continue;
+       }
+diff --git a/src/interpreter/bytecode-generator.h b/src/interpreter/bytecode-generator.h
+index 5dd136a01848aaf4a182dd31aa05a6ebe845fae8..b2e4cf6d40c3cd66042f5f52533b4883ff159418 100644
+--- a/src/interpreter/bytecode-generator.h
++++ b/src/interpreter/bytecode-generator.h
+@@ -30,6 +30,13 @@ class LoopBuilder;
+ class BlockCoverageBuilder;
+ class BytecodeJumpTable;
+ 
++struct PrototypeAssignments {
++  Variable* var;
++  HoleCheckMode hole_check_mode;
++  ZoneVector<PrototypeAssignment> properties;
++  std::unordered_set<const AstRawString*> duplicates;
++};
++
+ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
+  public:
+   enum TypeHint : uint8_t {
+@@ -69,18 +76,12 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
+ #define DECLARE_VISIT(type) void Visit##type(type* node);
+   AST_NODE_LIST(DECLARE_VISIT)
+ #undef DECLARE_VISIT
+-  static constexpr int kInitialPropertyCount = 64;
+ 
+   bool IsPrototypeAssignment(
+-      Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
+-      SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+-                      kInitialPropertyCount>& properties,
+-      std::unordered_set<const AstRawString*>& duplicate);
+-
+-  void VisitConsecutivePrototypeAssignments(
+-      const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+-                            kInitialPropertyCount>& properties,
+-      Variable* var, HoleCheckMode hole_check_mode);
++      Statement* stmt, std::unique_ptr<PrototypeAssignments>* assignments);
++  V8_NOINLINE void VisitConsecutivePrototypeAssignments(
++      std::unique_ptr<PrototypeAssignments> assignments);
++
+   // Visiting function for declarations list and statements are overridden.
+   void VisitModuleDeclarations(Declaration::List* declarations);
+   void VisitGlobalDeclarations(Declaration::List* declarations);
+diff --git a/src/interpreter/prototype-assignment-sequence-builder.cc b/src/interpreter/prototype-assignment-sequence-builder.cc
+index a7c05b6e8555fde41ba6a81523437fd03b16c3e6..fb427c825c2753d3e12f557279a5230985a1ec44 100644
+--- a/src/interpreter/prototype-assignment-sequence-builder.cc
++++ b/src/interpreter/prototype-assignment-sequence-builder.cc
+@@ -25,7 +25,7 @@ void ProtoAssignmentSeqBuilder::BuildBoilerplateDescription(
+ 
+   int position = 0;
+   for (size_t i = 0; i < properties_.size(); i++) {
+-    auto pair = properties_.at(i);
++    PrototypeAssignment pair = properties_.at(i);
+     const AstRawString* key_str = pair.first;
+ 
+     DirectHandle<Object> key = Cast<Object>(key_str->string());
+diff --git a/src/interpreter/prototype-assignment-sequence-builder.h b/src/interpreter/prototype-assignment-sequence-builder.h
+index 0c29f4f4bf0843877b2bd568866d8e290d4f3b51..6583022aa43a30df707325e6b7a14891f97a4ef1 100644
+--- a/src/interpreter/prototype-assignment-sequence-builder.h
++++ b/src/interpreter/prototype-assignment-sequence-builder.h
+@@ -7,17 +7,17 @@
+ 
+ #include "src/ast/ast.h"
+ #include "src/objects/literal-objects.h"
++#include "src/zone/zone-containers.h"
+ 
+ namespace v8 {
+ namespace internal {
+ 
++using PrototypeAssignment = std::pair<const AstRawString*, Expression*>;
++
+ class ProtoAssignmentSeqBuilder final : public ZoneObject {
+  public:
+-  static constexpr int kInitialPropertyCount = 64;
+-
+   explicit ProtoAssignmentSeqBuilder(
+-      const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+-                            kInitialPropertyCount>& properties)
++      ZoneVector<PrototypeAssignment>&& properties)
+       : properties_(std::move(properties)) {}
+ 
+   ~ProtoAssignmentSeqBuilder() = default;
+@@ -53,10 +53,10 @@ class ProtoAssignmentSeqBuilder final : public ZoneObject {
+                                                   IsolateT* isolate,
+                                                   Handle<Script> script);
+ 
++  const ZoneVector<PrototypeAssignment>* properties() { return &properties_; }
++
+  private:
+-  SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+-                  kInitialPropertyCount>
+-      properties_;
++  ZoneVector<PrototypeAssignment> properties_;
+   IndirectHandle<ObjectBoilerplateDescription> boilerplate_description_;
+ };
+ 

filename: patches/v8/runtime_correcting_setprototypeproperties.patch
patch: @@ -0,0 +1,148 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Raphael Herouart <rherouart@chromium.org>
+Date: Tue, 18 Nov 2025 11:00:26 +0000
+Subject: [runtime] Correcting SetPrototypeProperties:
+
+ - Off by One Error in code generation
+ - Attributes should be preserved when setting properties
+ - SmallVector Memory Leak
+
+Bug: 429332174
+Change-Id: I8a669a98f44777ad54ea7e7bb06fc4f8c552c865
+Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7160576
+Reviewed-by: Toon Verwaest <verwaest@chromium.org>
+Commit-Queue: Raphael Herouart <rherouart@chromium.org>
+Cr-Commit-Position: refs/heads/main@{#103784}
+
+diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc
+index bf874d94ec34056d499fea9cbd034f959d506b58..63400abcc7ca0bd5369d566dd37e2ae93d7c3efe 100644
+--- a/src/interpreter/bytecode-generator.cc
++++ b/src/interpreter/bytecode-generator.cc
+@@ -2307,8 +2307,8 @@ void BytecodeGenerator::VisitDeclarations(Declaration::List* declarations) {
+ 
+ bool BytecodeGenerator::IsPrototypeAssignment(
+     Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
+-    base::SmallVector<std::pair<const AstRawString*, Expression*>,
+-                      kInitialPropertyCount>& properties,
++    SmallZoneVector<std::pair<const AstRawString*, Expression*>,
++                    kInitialPropertyCount>& properties,
+     std::unordered_set<const AstRawString*>& duplicates) {
+   // The expression Statement is an assignment
+   // ========================================
+@@ -2405,8 +2405,8 @@ bool BytecodeGenerator::IsPrototypeAssignment(
+ }
+ 
+ void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
+-    const base::SmallVector<std::pair<const AstRawString*, Expression*>,
+-                            kInitialPropertyCount>& properties,
++    const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
++                          kInitialPropertyCount>& properties,
+     Variable* var, HoleCheckMode hole_check_mode) {
+   // Create a boiler plate object in the constant pool to be merged into the
+   // proto
+@@ -2444,9 +2444,9 @@ void BytecodeGenerator::VisitStatements(
+       HoleCheckMode hole_check_mode;
+ 
+       int proto_assign_idx = stmt_idx;
+-      base::SmallVector<std::pair<const AstRawString*, Expression*>,
+-                        kInitialPropertyCount>
+-          properties;
++      SmallZoneVector<std::pair<const AstRawString*, Expression*>,
++                      kInitialPropertyCount>
++          properties(zone());
+       std::unordered_set<const AstRawString*> duplicates;
+       while (proto_assign_idx < statements->length() &&
+              IsPrototypeAssignment(statements->at(proto_assign_idx), &var,
+@@ -2457,10 +2457,10 @@ void BytecodeGenerator::VisitStatements(
+       if (proto_assign_idx - stmt_idx > 1) {
+         DCHECK_EQ((size_t)(proto_assign_idx - stmt_idx), properties.size());
+         VisitConsecutivePrototypeAssignments(properties, var, hole_check_mode);
+-        stmt_idx = proto_assign_idx;  // the outer loop should now ignore these
+-                                      // statements
++        stmt_idx = proto_assign_idx - 1;  // the outer loop should now ignore
++                                          // these statements
+         DCHECK(!builder()->RemainderOfBlockIsDead());
+-        if (stmt_idx == statements->length()) break;
++        continue;
+       }
+     }
+ 
+diff --git a/src/interpreter/bytecode-generator.h b/src/interpreter/bytecode-generator.h
+index 41c5cda1ca6dd4017b329e41bad8af3bcc4ee242..5dd136a01848aaf4a182dd31aa05a6ebe845fae8 100644
+--- a/src/interpreter/bytecode-generator.h
++++ b/src/interpreter/bytecode-generator.h
+@@ -73,13 +73,13 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
+ 
+   bool IsPrototypeAssignment(
+       Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
+-      base::SmallVector<std::pair<const AstRawString*, Expression*>,
+-                        kInitialPropertyCount>& properties,
++      SmallZoneVector<std::pair<const AstRawString*, Expression*>,
++                      kInitialPropertyCount>& properties,
+       std::unordered_set<const AstRawString*>& duplicate);
+ 
+   void VisitConsecutivePrototypeAssignments(
+-      const base::SmallVector<std::pair<const AstRawString*, Expression*>,
+-                              kInitialPropertyCount>& properties,
++      const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
++                            kInitialPropertyCount>& properties,
+       Variable* var, HoleCheckMode hole_check_mode);
+   // Visiting function for declarations list and statements are overridden.
+   void VisitModuleDeclarations(Declaration::List* declarations);
+diff --git a/src/interpreter/prototype-assignment-sequence-builder.h b/src/interpreter/prototype-assignment-sequence-builder.h
+index 880ed4dcec8399e9b52e6d1924b8773bff1db063..0c29f4f4bf0843877b2bd568866d8e290d4f3b51 100644
+--- a/src/interpreter/prototype-assignment-sequence-builder.h
++++ b/src/interpreter/prototype-assignment-sequence-builder.h
+@@ -16,8 +16,8 @@ class ProtoAssignmentSeqBuilder final : public ZoneObject {
+   static constexpr int kInitialPropertyCount = 64;
+ 
+   explicit ProtoAssignmentSeqBuilder(
+-      const base::SmallVector<std::pair<const AstRawString*, Expression*>,
+-                              kInitialPropertyCount>& properties)
++      const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
++                            kInitialPropertyCount>& properties)
+       : properties_(std::move(properties)) {}
+ 
+   ~ProtoAssignmentSeqBuilder() = default;
+@@ -54,8 +54,8 @@ class ProtoAssignmentSeqBuilder final : public ZoneObject {
+                                                   Handle<Script> script);
+ 
+  private:
+-  base::SmallVector<std::pair<const AstRawString*, Expression*>,
+-                    kInitialPropertyCount>
++  SmallZoneVector<std::pair<const AstRawString*, Expression*>,
++                  kInitialPropertyCount>
+       properties_;
+   IndirectHandle<ObjectBoilerplateDescription> boilerplate_description_;
+ };
+diff --git a/src/runtime/runtime-literals.cc b/src/runtime/runtime-literals.cc
+index ded3b466625c99da6f27936ae4e5e0c879baec1b..961b9ee5b7d329bb018300fe2f18bea60e1cd883 100644
+--- a/src/runtime/runtime-literals.cc
++++ b/src/runtime/runtime-literals.cc
+@@ -730,6 +730,13 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) {
+                                             feedback_cell_array, current_slot));
+   }
+ 
++  if (IsSpecialReceiverMap(js_proto->map())) {
++    RETURN_RESULT_OR_FAILURE(
++        isolate, SetPrototypePropertiesSlow(isolate, context, obj,
++                                            object_boilerplate_description,
++                                            feedback_cell_array, current_slot));
++  }
++
+   bool is_default_func_prototype =
+       IsDefaultFunctionPrototype(js_proto, isolate);
+ 
+@@ -782,7 +789,11 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) {
+       value = InstantiateIfSharedFunctionInfo(
+           context, isolate, value, feedback_cell_array, current_slot);
+       DirectHandle<String> name = Cast<String>(key);
+-      JSObject::SetOwnPropertyIgnoreAttributes(js_proto, name, value, NONE)
++      Maybe<PropertyAttributes> maybe = JSReceiver::GetPropertyAttributes(&it);
++      PropertyAttributes attr = maybe.ToChecked();
++      if (attr == ABSENT) attr = NONE;
++
++      JSObject::SetOwnPropertyIgnoreAttributes(js_proto, name, value, attr)
+           .Check();
+ 
+       result = value;

filename: patches/v8/runtime_setprototypeproperties_handling_of.patch
patch: @@ -0,0 +1,153 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Raphael Herouart <rherouart@chromium.org>
+Date: Tue, 21 Oct 2025 14:49:06 +0000
+Subject: [runtime] SetPrototypeProperties handling of
+ Hole/Undefined/Non-Object Values
+
+Bug: 451750214
+Change-Id: Ida29afe0b92ed4a6acff97214005ed3589093294
+Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7062734
+Commit-Queue: Raphael Herouart <rherouart@chromium.org>
+Reviewed-by: Leszek Swirski <leszeks@chromium.org>
+Cr-Commit-Position: refs/heads/main@{#103261}
+
+diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc
+index 6d444622bacbb71c4754bc055da5cae39ed213b7..bf874d94ec34056d499fea9cbd034f959d506b58 100644
+--- a/src/interpreter/bytecode-generator.cc
++++ b/src/interpreter/bytecode-generator.cc
+@@ -2306,7 +2306,7 @@ void BytecodeGenerator::VisitDeclarations(Declaration::List* declarations) {
+ // The subsequent ones must be about the same <var> to return true.
+ 
+ bool BytecodeGenerator::IsPrototypeAssignment(
+-    Statement* stmt, Variable** var,
++    Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
+     base::SmallVector<std::pair<const AstRawString*, Expression*>,
+                       kInitialPropertyCount>& properties,
+     std::unordered_set<const AstRawString*>& duplicates) {
+@@ -2388,6 +2388,7 @@ bool BytecodeGenerator::IsPrototypeAssignment(
+   if (*var == nullptr) {
+     // This is the first proto assignment in the sequence
+     *var = tmp_var;
++    *hole_check_mode = proto_prop->obj()->AsVariableProxy()->hole_check_mode();
+   } else if (*var != tmp_var) {
+     // This prototype assignment is about another var
+     return false;
+@@ -2398,13 +2399,15 @@ bool BytecodeGenerator::IsPrototypeAssignment(
+       prop_str,
+       value));  // This will be reused as part of an ObjectLiteral
+ 
++  DCHECK_EQ(*hole_check_mode,
++            proto_prop->obj()->AsVariableProxy()->hole_check_mode());
+   return true;
+ }
+ 
+ void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
+     const base::SmallVector<std::pair<const AstRawString*, Expression*>,
+                             kInitialPropertyCount>& properties,
+-    Variable* var) {
++    Variable* var, HoleCheckMode hole_check_mode) {
+   // Create a boiler plate object in the constant pool to be merged into the
+   // proto
+   size_t entry = builder()->AllocateDeferredConstantPoolEntry();
+@@ -2428,7 +2431,7 @@ void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
+     first_idx = 0;
+   }
+   // Load the variable whose prototype is to be set into the Accumulator
+-  BuildVariableLoad(var, HoleCheckMode::kElided);
++  BuildVariableLoad(var, hole_check_mode);
+   // Merge in-place proto-def boilerplate object into Accumulator
+   builder()->SetPrototypeProperties(entry, first_idx);
+ }
+@@ -2438,6 +2441,8 @@ void BytecodeGenerator::VisitStatements(
+   for (int stmt_idx = start; stmt_idx < statements->length(); stmt_idx++) {
+     if (v8_flags.proto_assign_seq_opt) {
+       Variable* var = nullptr;
++      HoleCheckMode hole_check_mode;
++
+       int proto_assign_idx = stmt_idx;
+       base::SmallVector<std::pair<const AstRawString*, Expression*>,
+                         kInitialPropertyCount>
+@@ -2445,13 +2450,13 @@ void BytecodeGenerator::VisitStatements(
+       std::unordered_set<const AstRawString*> duplicates;
+       while (proto_assign_idx < statements->length() &&
+              IsPrototypeAssignment(statements->at(proto_assign_idx), &var,
+-                                   properties, duplicates)) {
++                                   &hole_check_mode, properties, duplicates)) {
+         ++proto_assign_idx;
+       }
+ 
+       if (proto_assign_idx - stmt_idx > 1) {
+         DCHECK_EQ((size_t)(proto_assign_idx - stmt_idx), properties.size());
+-        VisitConsecutivePrototypeAssignments(properties, var);
++        VisitConsecutivePrototypeAssignments(properties, var, hole_check_mode);
+         stmt_idx = proto_assign_idx;  // the outer loop should now ignore these
+                                       // statements
+         DCHECK(!builder()->RemainderOfBlockIsDead());
+diff --git a/src/interpreter/bytecode-generator.h b/src/interpreter/bytecode-generator.h
+index b6421819c353febe283069852645a6b85e4c855a..41c5cda1ca6dd4017b329e41bad8af3bcc4ee242 100644
+--- a/src/interpreter/bytecode-generator.h
++++ b/src/interpreter/bytecode-generator.h
+@@ -72,7 +72,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
+   static constexpr int kInitialPropertyCount = 64;
+ 
+   bool IsPrototypeAssignment(
+-      Statement* stmt, Variable** var,
++      Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
+       base::SmallVector<std::pair<const AstRawString*, Expression*>,
+                         kInitialPropertyCount>& properties,
+       std::unordered_set<const AstRawString*>& duplicate);
+@@ -80,7 +80,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
+   void VisitConsecutivePrototypeAssignments(
+       const base::SmallVector<std::pair<const AstRawString*, Expression*>,
+                               kInitialPropertyCount>& properties,
+-      Variable* var);
++      Variable* var, HoleCheckMode hole_check_mode);
+   // Visiting function for declarations list and statements are overridden.
+   void VisitModuleDeclarations(Declaration::List* declarations);
+   void VisitGlobalDeclarations(Declaration::List* declarations);
+diff --git a/src/runtime/runtime-literals.cc b/src/runtime/runtime-literals.cc
+index 83074253314c438eed3006332c63c6bf7dfe4421..ded3b466625c99da6f27936ae4e5e0c879baec1b 100644
+--- a/src/runtime/runtime-literals.cc
++++ b/src/runtime/runtime-literals.cc
+@@ -613,7 +613,7 @@ static DirectHandle<Object> InstantiateIfSharedFunctionInfo(
+ }
+ 
+ static MaybeDirectHandle<Object> SetPrototypePropertiesSlow(
+-    Isolate* isolate, DirectHandle<Context> context, DirectHandle<JSObject> obj,
++    Isolate* isolate, DirectHandle<Context> context, DirectHandle<JSAny> obj,
+     Handle<ObjectBoilerplateDescription> object_boilerplate_description,
+     DirectHandle<ClosureFeedbackCellArray> feedback_cell_array,
+     int& current_slot, int start_index = 0) {
+@@ -684,7 +684,7 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) {
+   HandleScope scope(isolate);
+   DCHECK_EQ(4, args.length());
+   DirectHandle<Context> context(isolate->context(), isolate);
+-  DirectHandle<JSObject> obj = args.at<JSObject>(0);  // acc JS Object
++  DirectHandle<JSAny> obj = args.at<JSAny>(0);  // acc JS Object
+   Handle<ObjectBoilerplateDescription> object_boilerplate_description =
+       args.at<ObjectBoilerplateDescription>(1);
+   DirectHandle<ClosureFeedbackCellArray> feedback_cell_array =
+diff --git a/test/unittests/interpreter/bytecode_expectations/SetPrototypePropertiesOptimization.golden b/test/unittests/interpreter/bytecode_expectations/SetPrototypePropertiesOptimization.golden
+index 84ebbb755f8a5432c2e8da3d26ceb648bee2cf2d..bb9d697caec6e79810fa757abec4b71ad4cf3f2f 100644
+--- a/test/unittests/interpreter/bytecode_expectations/SetPrototypePropertiesOptimization.golden
++++ b/test/unittests/interpreter/bytecode_expectations/SetPrototypePropertiesOptimization.golden
+@@ -50,15 +50,17 @@ snippet: "
+ "
+ frame size: 0
+ parameter count: 1
+-bytecode array length: 7
++bytecode array length: 9
+ bytecodes: [
+                 B(LdaImmutableCurrentContextSlot), U8(2),
+-  /*  109 E> */ B(SetPrototypeProperties), U8(0), U8(0),
++  /*  109 E> */ B(ThrowReferenceErrorIfHole), U8(1),
++                B(SetPrototypeProperties), U8(0), U8(0),
+                 B(LdaUndefined),
+   /*  245 S> */ B(Return),
+ ]
+ constant pool: [
+   OBJECT_BOILERPLATE_DESCRIPTION_TYPE,
++  INTERNALIZED_ONE_BYTE_STRING_TYPE ["MyClass"],
+ ]
+ handlers: [
+ ]