Skip to content

Commit 229d793

Browse files
mralephcommit-bot@chromium.org
authored andcommitted
[vm/compiler] Support materializing unboxed variables when entering catch.
Previously we tried to rely on the assumption that all variables would be boxed - so the machinery for setting correct catch-entry state only supported tagged values and constants. However this both leads to worse code and is not entirely correct assumption. This also: - renames various confusingly named classes: we move away from talking about "catch entry state" to "catch entry moves" - because we only record a subset of moves that needs to be performed and that does not describe the whole state; - refactors a bunch of associated code to be more readable and maintainable; - adds documentation about catch implementation in optimized code to runtime/docs/compiler; Fixes flutter/flutter#21685. Change-Id: I03ae361a1bb7710acbd9f661ae014e663a163c59 Reviewed-on: https://dart-review.googlesource.com/74860 Commit-Queue: Vyacheslav Egorov <vegorov@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com>
1 parent eec96f9 commit 229d793

34 files changed

+721
-345
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Exceptions Implementation
2+
3+
This page describes how exceptions throwing and catching is implemented in the
4+
VM.
5+
6+
## Intermediate Language
7+
8+
Dart VM's IL **does not** explicitly represent exceptional control flow in its
9+
flow graph, there are **no** explicit exceptional edges connecting potentially
10+
throwing instructions (e.g. calls) with corresponding catch blocks. Instead this
11+
connection is defined at the block level: all exceptions that occur in any block
12+
with the given `try_index` will be caught by `CatchBlockEntry` with the equal
13+
`catch_try_index`.
14+
15+
![Catch Block Entry](images/catch-block-entry-0.png)
16+
17+
For optimized code this means that data flow associated with exceptional control
18+
flow is also represented implicitly: due to the absence of explicit exceptional
19+
edges the data flow can't be represented using explicit phi-functions. Instead
20+
in optimized code each `CatchBlockEntry` is treated almost as if it was an
21+
independent entry into the function: for each variable `v` `CatchBlockEntry`
22+
will contain a `Parameter(...)` instruction restoring variable state at catch
23+
entry from a fixed location on the stack. When an exception is thrown runtime
24+
system takes care of populating these stack slots with right values - current
25+
state of corresponding local variables. It's easy to see a parallel between
26+
these `Parameter(...)` instructions and `Phi(...)` instructions that would be
27+
used if exception control flow would be explicit.
28+
29+
![Catch Block Entry](images/catch-block-entry-1.png)
30+
31+
How does runtime system populate stack slots corresponding to these
32+
`Parameter(...)` instructions? During compilation necessary information is
33+
available in _deoptimization environment_ attached to the instruction. This
34+
environment encodes the state of local variables in terms of SSA values i.e. if
35+
we need to reconstruct unoptimized frame which SSA value should be stored into
36+
the given local variable (see [Optimized
37+
IL](compiler-pipeline-overview.md#optimized-il) for
38+
an overview). However the way we use these information for exception handling is
39+
slightly different in JIT and AOT modes.
40+
41+
### AOT mode
42+
43+
AOT mode does not support deoptimization and thus AOT compiler does not
44+
associate any deoptimization metadata with generated code. Instead
45+
deoptimization environments associated with instructions that can throw are
46+
converted into `CatchEntryMoves` metadata during code generation and resulting
47+
metadata is stored `RawCode::catch_entry_moves_maps_` in a compressed form.
48+
49+
`CatchEntryMoves` is essentially a sequence of moves which runtime needs to
50+
perform to create the state that catch entry expects. There are three types of
51+
moves:
52+
53+
* `*(FP + Dst) <- ObjectPool[PoolIndex]` - a move of a constant from an object
54+
pool;
55+
* `*(FP + Dst) <- *(FP + Src)` - a move of a tagged value;
56+
* `*(FP + Dst) <- Box<Rep>(*(FP + Src))` - a boxing operation for an untagged
57+
value;
58+
59+
When an exception is caught runtime decompresses the metadata associated with the
60+
call site which has thrown an exception and uses it to prepare the state of the
61+
stack for the catch block entry. See
62+
`ExceptionHandlerFinder::{ReadCompressedCatchEntryMoves, ExecuteCatchEntryMoves}`.
63+
64+
NOTE: See [this
65+
design/motivation](https://docs.google.com/a/google.com/document/d/1_vX8VkvHVA1Om7jjONiWLA325k_JmSZuvVClet-x-xM/edit?usp=sharing)
66+
document for `CatchEntryMoves` metadata
67+
68+
### JIT mode
69+
70+
JIT mode heavily relies on deoptimization and all call instructions have (lazy)
71+
deoptimization environments associated with them. These environments are
72+
converted to [deoptimization
73+
instructions](deoptimization.md#in-optimized-code)
74+
during code generation and stored on the `Code` object.
75+
76+
When an exception is caught the runtime system converts deoptimization
77+
environment associated with the call site that threw an exception into
78+
`CatchEntryMoves` and then uses it to prepare the state of the stack for the
79+
catch block entry. See `ExceptionHandlerFinder::{GetCatchEntryMovesFromDeopt, ExecuteCatchEntryMoves}`.
80+
81+
Constructing `CatchEntryMoves` dynamically from deoptimization instructions
82+
allows to avoid unnecessary duplication of the metadata and save memory: as
83+
deoptimization environments contain all information necessary for constructing
84+
correct stack state.
85+
86+
IMPORTANT: There is a subtle difference between DBC and other architectures with
87+
respect to catch block entry state. On normal architectures `Parameter(i)` at
88+
catch entry would be associated with the same stack space that would be used to
89+
store variable with index `i`. On DBC however at catch entry `Parameter(i)`
90+
would be allocated to a separate scratch space at the very top of the register
91+
space. See `FlowGraphAllocator::ProcessInitialDefinition` and
92+
`FlowGraphCompiler::CatchEntryRegForVariable`.
34.1 KB
Loading
81.6 KB
Loading
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
// VMOptions=--no-background-compilation --enable-inlining-annotations --optimization-counter-threshold=100
5+
6+
// Verify that runtime correctly materializes unboxed variables on the catch
7+
// entry in optimized code.
8+
9+
import 'dart:typed_data';
10+
11+
import 'package:expect/expect.dart';
12+
13+
const NeverInline = "NeverInline";
14+
15+
@NeverInline
16+
void testThrow(bool shouldThrow) {
17+
var dbl = 0.0;
18+
var i32 = 0;
19+
var i64 = 0;
20+
var f32x4 = new Float32x4.zero();
21+
var f64x2 = new Float64x2.zero();
22+
var i32x4 = new Int32x4(0, 0, 0, 0);
23+
try {
24+
for (var i = 0; i < 100; i++) {
25+
dbl += i;
26+
i32 = i | 0x70000000;
27+
i64 = i | 0x80000000;
28+
final d = i.toDouble();
29+
f32x4 += new Float32x4(d, -d, d, -d);
30+
f64x2 += new Float64x2(d, -d);
31+
i32x4 += new Int32x4(-i, i, -i, i);
32+
if (shouldThrow && i == 50) {
33+
throw "";
34+
}
35+
}
36+
} catch (e) {}
37+
38+
if (shouldThrow) {
39+
Expect.equals(1275.0, dbl);
40+
Expect.equals(0x70000000 | 50, i32);
41+
Expect.equals(0x80000000 | 50, i64);
42+
Expect.listEquals([1275.0, -1275.0, 1275.0, -1275.0],
43+
[f32x4.x, f32x4.y, f32x4.z, f32x4.w]);
44+
Expect.listEquals([1275.0, -1275.0], [f64x2.x, f64x2.y]);
45+
Expect.listEquals(
46+
[-1275, 1275, -1275, 1275], [i32x4.x, i32x4.y, i32x4.z, i32x4.w]);
47+
}
48+
}
49+
50+
void main() {
51+
for (var i = 0; i < 100; i++) testThrow(false);
52+
testThrow(true);
53+
}

runtime/tests/vm/vm.status

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ cc/DartAPI_New: Fail # Issue #33041
6060
dart/redirection_type_shuffling_test/00: RuntimeError, Pass
6161
dart/redirection_type_shuffling_test/none: RuntimeError
6262

63+
[ $runtime != vm && $runtime != dart_precompiled ]
64+
dart/catch_entry_state: SkipByDesign
65+
6366
[ $compiler != dartk && $compiler != dartkb ]
6467
cc/IsolateReload_KernelIncrementalCompile: SkipByDesign
6568
cc/IsolateReload_KernelIncrementalCompileAppAndLib: SkipByDesign

runtime/vm/clustered_snapshot.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1666,7 +1666,7 @@ class CodeSerializationCluster : public SerializationCluster {
16661666
s->Push(code->ptr()->exception_handlers_);
16671667
s->Push(code->ptr()->pc_descriptors_);
16681668
#if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER)
1669-
s->Push(code->ptr()->catch_entry_.catch_entry_state_maps_);
1669+
s->Push(code->ptr()->catch_entry_.catch_entry_moves_maps_);
16701670
#else
16711671
s->Push(code->ptr()->catch_entry_.variables_);
16721672
#endif
@@ -1727,7 +1727,7 @@ class CodeSerializationCluster : public SerializationCluster {
17271727
s->WriteRef(code->ptr()->exception_handlers_);
17281728
s->WriteRef(code->ptr()->pc_descriptors_);
17291729
#if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER)
1730-
s->WriteRef(code->ptr()->catch_entry_.catch_entry_state_maps_);
1730+
s->WriteRef(code->ptr()->catch_entry_.catch_entry_moves_maps_);
17311731
#else
17321732
s->WriteRef(code->ptr()->catch_entry_.variables_);
17331733
#endif
@@ -1810,7 +1810,7 @@ class CodeDeserializationCluster : public DeserializationCluster {
18101810
code->ptr()->pc_descriptors_ =
18111811
reinterpret_cast<RawPcDescriptors*>(d->ReadRef());
18121812
#if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER)
1813-
code->ptr()->catch_entry_.catch_entry_state_maps_ =
1813+
code->ptr()->catch_entry_.catch_entry_moves_maps_ =
18141814
reinterpret_cast<RawTypedData*>(d->ReadRef());
18151815
#else
18161816
code->ptr()->catch_entry_.variables_ =

runtime/vm/code_descriptors.cc

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -112,18 +112,19 @@ RawExceptionHandlers* ExceptionHandlerList::FinalizeExceptionHandlers(
112112
return handlers.raw();
113113
}
114114

115-
static uint8_t* zone_allocator(uint8_t* ptr,
116-
intptr_t old_size,
117-
intptr_t new_size) {
115+
static uint8_t* ZoneAllocator(uint8_t* ptr,
116+
intptr_t old_size,
117+
intptr_t new_size) {
118118
Zone* zone = Thread::Current()->zone();
119119
return zone->Realloc<uint8_t>(ptr, old_size, new_size);
120120
}
121121

122-
class CatchEntryStateMapBuilder::TrieNode : public ZoneAllocated {
122+
#if !defined(DART_PRECOMPILED_RUNTIME)
123+
class CatchEntryMovesMapBuilder::TrieNode : public ZoneAllocated {
123124
public:
124-
TrieNode() : pair_(), entry_state_offset_(-1) {}
125-
TrieNode(CatchEntryStatePair pair, intptr_t index)
126-
: pair_(pair), entry_state_offset_(index) {}
125+
TrieNode() : move_(), entry_state_offset_(-1) {}
126+
TrieNode(CatchEntryMove move, intptr_t index)
127+
: move_(move), entry_state_offset_(index) {}
127128

128129
intptr_t Offset() { return entry_state_offset_; }
129130

@@ -132,42 +133,36 @@ class CatchEntryStateMapBuilder::TrieNode : public ZoneAllocated {
132133
return node;
133134
}
134135

135-
TrieNode* Follow(CatchEntryStatePair next) {
136+
TrieNode* Follow(CatchEntryMove next) {
136137
for (intptr_t i = 0; i < children_.length(); i++) {
137-
if (children_[i]->pair_ == next) return children_[i];
138+
if (children_[i]->move_ == next) return children_[i];
138139
}
139140
return NULL;
140141
}
141142

142143
private:
143-
CatchEntryStatePair pair_;
144+
CatchEntryMove move_;
144145
const intptr_t entry_state_offset_;
145146
GrowableArray<TrieNode*> children_;
146147
};
147148

148-
CatchEntryStateMapBuilder::CatchEntryStateMapBuilder()
149+
CatchEntryMovesMapBuilder::CatchEntryMovesMapBuilder()
149150
: zone_(Thread::Current()->zone()),
150151
root_(new TrieNode()),
151152
current_pc_offset_(0),
152153
buffer_(NULL),
153-
stream_(&buffer_, zone_allocator, 64) {}
154+
stream_(&buffer_, ZoneAllocator, 64) {}
154155

155-
void CatchEntryStateMapBuilder::AppendMove(intptr_t src_slot,
156-
intptr_t dest_slot) {
157-
moves_.Add(CatchEntryStatePair::FromMove(src_slot, dest_slot));
156+
void CatchEntryMovesMapBuilder::Append(const CatchEntryMove& move) {
157+
moves_.Add(move);
158158
}
159159

160-
void CatchEntryStateMapBuilder::AppendConstant(intptr_t pool_id,
161-
intptr_t dest_slot) {
162-
moves_.Add(CatchEntryStatePair::FromConstant(pool_id, dest_slot));
163-
}
164-
165-
void CatchEntryStateMapBuilder::NewMapping(intptr_t pc_offset) {
160+
void CatchEntryMovesMapBuilder::NewMapping(intptr_t pc_offset) {
166161
moves_.Clear();
167162
current_pc_offset_ = pc_offset;
168163
}
169164

170-
void CatchEntryStateMapBuilder::EndMapping() {
165+
void CatchEntryMovesMapBuilder::EndMapping() {
171166
intptr_t suffix_length = 0;
172167
TrieNode* suffix = root_;
173168
// Find the largest common suffix, get the last node of the path.
@@ -189,16 +184,15 @@ void CatchEntryStateMapBuilder::EndMapping() {
189184
// Write the unshared part, adding it to the trie.
190185
TrieNode* node = suffix;
191186
for (intptr_t i = length - 1; i >= 0; i--) {
192-
Writer::Write(&stream_, moves_[i].src);
193-
Writer::Write(&stream_, moves_[i].dest);
187+
moves_[i].WriteTo(&stream_);
194188

195189
TrieNode* child = new (zone_) TrieNode(moves_[i], current_offset);
196190
node->Insert(child);
197191
node = child;
198192
}
199193
}
200194

201-
RawTypedData* CatchEntryStateMapBuilder::FinalizeCatchEntryStateMap() {
195+
RawTypedData* CatchEntryMovesMapBuilder::FinalizeCatchEntryMovesMap() {
202196
TypedData& td = TypedData::Handle(TypedData::New(
203197
kTypedDataInt8ArrayCid, stream_.bytes_written(), Heap::kOld));
204198
NoSafepointScope no_safepoint;
@@ -209,6 +203,7 @@ RawTypedData* CatchEntryStateMapBuilder::FinalizeCatchEntryStateMap() {
209203
}
210204
return td.raw();
211205
}
206+
#endif // !defined(DART_PRECOMPILED_RUNTIME)
212207

213208
const TokenPosition CodeSourceMapBuilder::kInitialPosition =
214209
TokenPosition(TokenPosition::kDartCodeProloguePos);
@@ -230,7 +225,7 @@ CodeSourceMapBuilder::CodeSourceMapBuilder(
230225
inlined_functions_(
231226
GrowableObjectArray::Handle(GrowableObjectArray::New(Heap::kOld))),
232227
buffer_(NULL),
233-
stream_(&buffer_, zone_allocator, 64),
228+
stream_(&buffer_, ZoneAllocator, 64),
234229
stack_traces_only_(stack_traces_only) {
235230
buffered_inline_id_stack_.Add(0);
236231
buffered_token_pos_stack_.Add(kInitialPosition);

runtime/vm/code_descriptors.h

Lines changed: 9 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -144,56 +144,30 @@ class ExceptionHandlerList : public ZoneAllocated {
144144
DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerList);
145145
};
146146

147-
// An encoded move from stack/constant to stack performed
148-
struct CatchEntryStatePair {
149-
enum { kCatchEntryStateIsMove = 1, kCatchEntryStateDestShift = 1 };
150-
151-
intptr_t src, dest;
152-
153-
static CatchEntryStatePair FromConstant(intptr_t pool_id,
154-
intptr_t dest_slot) {
155-
CatchEntryStatePair pair;
156-
pair.src = pool_id;
157-
pair.dest = (dest_slot << kCatchEntryStateDestShift);
158-
return pair;
159-
}
160-
161-
static CatchEntryStatePair FromMove(intptr_t src_slot, intptr_t dest_slot) {
162-
CatchEntryStatePair pair;
163-
pair.src = src_slot;
164-
pair.dest =
165-
(dest_slot << kCatchEntryStateDestShift) | kCatchEntryStateIsMove;
166-
return pair;
167-
}
168-
169-
bool operator==(const CatchEntryStatePair& rhs) {
170-
return src == rhs.src && dest == rhs.dest;
171-
}
172-
};
173-
174-
// Used to construct CatchEntryState metadata for AoT mode of compilation.
175-
class CatchEntryStateMapBuilder : public ZoneAllocated {
147+
#if !defined(DART_PRECOMPILED_RUNTIME)
148+
// Used to construct CatchEntryMoves for the AOT mode of compilation.
149+
class CatchEntryMovesMapBuilder : public ZoneAllocated {
176150
public:
177-
CatchEntryStateMapBuilder();
151+
CatchEntryMovesMapBuilder();
178152

179153
void NewMapping(intptr_t pc_offset);
180-
void AppendMove(intptr_t src_slot, intptr_t dest_slot);
181-
void AppendConstant(intptr_t pool_id, intptr_t dest_slot);
154+
void Append(const CatchEntryMove& move);
182155
void EndMapping();
183-
RawTypedData* FinalizeCatchEntryStateMap();
156+
RawTypedData* FinalizeCatchEntryMovesMap();
184157

185158
private:
186159
class TrieNode;
187160

188161
Zone* zone_;
189162
TrieNode* root_;
190163
intptr_t current_pc_offset_;
191-
GrowableArray<CatchEntryStatePair> moves_;
164+
GrowableArray<CatchEntryMove> moves_;
192165
uint8_t* buffer_;
193166
WriteStream stream_;
194167

195-
DISALLOW_COPY_AND_ASSIGN(CatchEntryStateMapBuilder);
168+
DISALLOW_COPY_AND_ASSIGN(CatchEntryMovesMapBuilder);
196169
};
170+
#endif // !defined(DART_PRECOMPILED_RUNTIME)
197171

198172
// A CodeSourceMap maps from pc offsets to a stack of inlined functions and
199173
// their positions. This is encoded as a little bytecode that pushes and pops

runtime/vm/compiler/aot/precompiler.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2709,7 +2709,7 @@ void PrecompileParsedFunctionHelper::FinalizeCompilation(
27092709
graph_compiler->FinalizeStackMaps(code);
27102710
graph_compiler->FinalizeVarDescriptors(code);
27112711
graph_compiler->FinalizeExceptionHandlers(code);
2712-
graph_compiler->FinalizeCatchEntryStateMap(code);
2712+
graph_compiler->FinalizeCatchEntryMovesMap(code);
27132713
graph_compiler->FinalizeStaticCallTargetsTable(code);
27142714
graph_compiler->FinalizeCodeSourceMap(code);
27152715

0 commit comments

Comments
 (0)