Skip to content

Commit d05f2fc

Browse files
LeszekSwirskiV8 LUCI CQ
authored andcommitted
[maglev] Don't find property load continuations across try-catch blocks
The search for polymorphic property load continuations now stops at try-catch boundaries. Previously, it would scan across them, which could cause it to visit the try-block start once in the first continuation, and cause the second continuation to think it's inside the handler. Drive-by, clean-up the predicate around merge points (to be more explicit about loop headers only not having merge points when loop peeling), and DCHECK that the source position table state doesn't change, since there's nothing in the continuation finding that would make it change. Fixed: 449549329 Change-Id: I1a6c0321497a1cecdc774521186ab5abebef3e84 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7011291 Commit-Queue: Leszek Swirski <[email protected]> Reviewed-by: Marja Hölttä <[email protected]> Auto-Submit: Leszek Swirski <[email protected]> Cr-Commit-Position: refs/heads/main@{#102971}
1 parent f7489fc commit d05f2fc

4 files changed

Lines changed: 114 additions & 10 deletions

File tree

src/codegen/source-position-table.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ struct PositionTableEntry {
3636
int code_offset;
3737
bool is_statement;
3838
bool is_breakable;
39+
40+
bool operator==(const PositionTableEntry&) const = default;
3941
};
4042

4143
class V8_EXPORT_PRIVATE SourcePositionTableBuilder {
@@ -95,6 +97,8 @@ class V8_EXPORT_PRIVATE SourcePositionTableIterator {
9597
PositionTableEntry position_;
9698
IterationFilter iteration_filter_;
9799
FunctionEntryFilter function_entry_filter_;
100+
101+
bool operator==(const IndexAndPositionState&) const = default;
98102
};
99103

100104
// We expose three flavours of the iterator, depending on the argument passed

src/maglev/maglev-graph-builder.cc

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6930,15 +6930,18 @@ MaglevGraphBuilder::FindContinuationForPolymorphicPropertyLoad() {
69306930
}
69316931

69326932
int start_offset = iterator_.current_offset();
6933+
#ifdef DEBUG
69336934
SourcePositionTableIterator::IndexAndPositionState
69346935
start_source_position_iterator_state =
69356936
source_position_iterator_.GetState();
6937+
#endif
69366938

69376939
std::optional<ContinuationOffsets> continuation =
69386940
FindContinuationForPolymorphicPropertyLoadImpl();
69396941

69406942
iterator_.SetOffset(start_offset);
6941-
source_position_iterator_.RestoreState(start_source_position_iterator_state);
6943+
DCHECK_EQ(start_source_position_iterator_state,
6944+
source_position_iterator_.GetState());
69426945
return continuation;
69436946
}
69446947

@@ -6955,11 +6958,64 @@ MaglevGraphBuilder::FindContinuationForPolymorphicPropertyLoadImpl() {
69556958
// Where <allowed bytecodes> are:
69566959
// - not affecting control flow
69576960
// - not storing into REG
6961+
// - not the start or end of a try block
69586962
// and the continuation is limited in length.
69596963

6960-
// Skip GetnamedProperty.
6964+
// Try-block starts are not visible as control flow or basic blocks, so detect
6965+
// them using the bytecode offset.
6966+
int next_handler_change = kMaxInt;
6967+
HandlerTable table(*bytecode().object());
6968+
if (next_handler_table_index_ < table.NumberOfRangeEntries()) {
6969+
next_handler_change = table.GetRangeStart(next_handler_table_index_);
6970+
}
6971+
// Try-block ends are detected via the top end offset in the current handler
6972+
// stack.
6973+
if (IsInsideTryBlock()) {
6974+
const HandlerTableEntry& entry = catch_block_stack_.top();
6975+
next_handler_change = std::min(next_handler_change, entry.end);
6976+
}
6977+
6978+
auto IsOffsetAPolymorphicContinuationInterrupt =
6979+
[this, next_handler_change](int offset) {
6980+
// We can't continue a polymorphic load over a merge, since the
6981+
// other side of the merge will observe the call without the load.
6982+
//
6983+
// TODO(leszeks): I guess we could split that merge if we wanted to,
6984+
// introducing a new merge that has the polymorphic loads+calls on one
6985+
// side and the generic call on the other.
6986+
if (IsOffsetAMergePoint(offset)) return false;
6987+
6988+
// We currently can't continue a polymorphic load across a peeled
6989+
// loop header -- not because of any actual semantic reason, a peeled
6990+
// loop should be just like straightline code, but just because this
6991+
// iteration isn't compatible with the PeelLoop iteration.
6992+
//
6993+
// TODO(leszeks): We could probably make loop peeling work happen on the
6994+
// JumpLoop rather than loop header, and then this continuation code
6995+
// would work. Only for the first peeled iteration though, not for
6996+
// speeling.
6997+
if (loop_headers_to_peel_.Contains(offset)) return false;
6998+
6999+
// Loop peeling should be the only reason there was no merge point for a
7000+
// loop header.
7001+
DCHECK(!bytecode_analysis_.IsLoopHeader(offset));
7002+
7003+
// We can't currently continue a polymorphic load over a try-catch
7004+
// start/end -- again, not for any semantic reason, but just because
7005+
// this iteration doesn't consider the catch handler stack.
7006+
//
7007+
// TODO(leszeks): If this saved/restore the handler stack, it would
7008+
// probably work, but we'd need to confirm that later phases don't need
7009+
// strict nesting of handlers (since the first polymorphic call would
7010+
// be inside the handler range, but the second polymorphic load after it
7011+
// in linear scan order would be outside of the handler range).
7012+
if (offset >= next_handler_change) return false;
7013+
return true;
7014+
};
7015+
7016+
// Skip GetNamedProperty.
69617017
iterator_.Advance();
6962-
if (IsOffsetAMergePointOrLoopHeapder(iterator_.current_offset())) {
7018+
if (IsOffsetAPolymorphicContinuationInterrupt(iterator_.current_offset())) {
69637019
return {};
69647020
}
69657021

@@ -6981,7 +7037,7 @@ MaglevGraphBuilder::FindContinuationForPolymorphicPropertyLoadImpl() {
69817037
int limit = 20;
69827038
while (--limit > 0) {
69837039
iterator_.Advance();
6984-
if (IsOffsetAMergePointOrLoopHeapder(iterator_.current_offset())) {
7040+
if (IsOffsetAPolymorphicContinuationInterrupt(iterator_.current_offset())) {
69857041
return {};
69867042
}
69877043

src/maglev/maglev-graph-builder.h

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -441,15 +441,10 @@ class MaglevGraphBuilder {
441441

442442
// Return true if the given offset is a merge point, i.e. there are jumps
443443
// targetting it.
444-
bool IsOffsetAMergePoint(int offset) {
444+
bool IsOffsetAMergePoint(int offset) const {
445445
return merge_states_[offset] != nullptr;
446446
}
447447

448-
bool IsOffsetAMergePointOrLoopHeapder(int offset) {
449-
return IsOffsetAMergePoint(offset) ||
450-
bytecode_analysis().IsLoopHeader(offset);
451-
}
452-
453448
ValueNode* GetContextAtDepth(ValueNode* context, size_t depth);
454449
bool CheckContextExtensions(size_t depth);
455450

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2025 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
//
5+
// Flags: --allow-natives-syntax
6+
7+
// Create two distinct iterator types which both have a getter for `next`.
8+
class Iterator1 {
9+
get next() {
10+
return () => ({ done: true });
11+
}
12+
}
13+
class Iterator2 {
14+
get next() {
15+
return () => ({ done: true });
16+
}
17+
}
18+
19+
// Create two iterables which return instances of these two distinct iterators.
20+
const iterable1 = {
21+
[Symbol.iterator]() {
22+
return new Iterator1();
23+
},
24+
};
25+
const iterable2 = {
26+
[Symbol.iterator]() {
27+
return new Iterator2();
28+
},
29+
};
30+
31+
// Iterate the iterable using for-of.
32+
function foo(iterable) {
33+
for (const x of iterable) {
34+
return x;
35+
}
36+
}
37+
38+
// Make foo polymorphic in the iterator, specifically so that the feedback for
39+
// the iterator.next named load is polymorphic, with the feedback being two
40+
// distinct getters.
41+
%PrepareFunctionForOptimization(foo);
42+
foo(iterable1);
43+
foo(iterable2);
44+
45+
// The optimization should be successful and not trigger any DCHECKs, despite
46+
// the iterator.next load being before the for-of's implicit try block, and the
47+
// iterator.next() call being inside it.
48+
%OptimizeMaglevOnNextCall(foo);
49+
foo(iterable1);

0 commit comments

Comments
 (0)