v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
variable-reducer.h
Go to the documentation of this file.
1// Copyright 2022 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#ifndef V8_COMPILER_TURBOSHAFT_VARIABLE_REDUCER_H_
6#define V8_COMPILER_TURBOSHAFT_VARIABLE_REDUCER_H_
7
8#include <algorithm>
9#include <optional>
10
11#include "src/base/logging.h"
20
22
24
25// When cloning a Block or duplicating an Operation, we end up with some
26// Operations of the old graph mapping to multiple Operations in the new graph.
27// When using those Operations in subsequent Operations, we need to know which
28// of the new-Operation to use, and, in particular, if a Block has 2
29// predecessors that have a mapping for the same old-Operation, we need to
30// merge them in a Phi node. All of this is handled by the VariableAssembler.
31//
32// The typical workflow when working with the VariableAssembler would be:
33// - At some point, you need to introduce a Variable (for instance
34// because you cloned a block or an Operation) and call NewVariable or
35// NewLoopInvariantVariable to get a fresh Variable. A loop invariant
36// variable must not need loop phis, that is, not change its value
37// depending on loop iteration while being visible across loop iterations.
38// - You can then Set the new-OpIndex associated with this Variable in the
39// current Block with the Set method.
40// - If you later need to set an OpIndex for this Variable in another Block,
41// call Set again.
42// - At any time, you can call Get to get the new-Operation associated to
43// this Variable. Get will return:
44// * if the current block is dominated by a block who did a Set on the
45// Variable, then the Operation that was Set then.
46// * otherwise, the current block must be dominated by a Merge whose
47// predecessors have all Set this Variable. In that case, the
48// VariableAssembler introduced a Phi in this merge, and will return
49// this Phi.
50//
51// Note that the VariableAssembler does not do "old-OpIndex => Variable"
52// book-keeping: the users of the Variable should do that themselves (which
53// is what CopyingPhase does for instance).
54
55// VariableReducer always adds a RequiredOptimizationReducer, because phis
56// with constant inputs introduced by `VariableReducer` need to be eliminated.
57template <class AfterNext>
61
64 return var.data().active_loop_variables_index;
65 }
66 };
67
69 : ChangeTrackingSnapshotTable<VariableTable, OpIndex, VariableData> {
74
77
78 void OnNewKey(Variable var, OpIndex value) { DCHECK(!value.valid()); }
79 void OnValueChange(Variable var, OpIndex old_value, OpIndex new_value) {
80 if (var.data().loop_invariant) {
81 return;
82 }
83 if (old_value.valid() && !new_value.valid()) {
84 active_loop_variables.Remove(var);
85 } else if (!old_value.valid() && new_value.valid()) {
86 active_loop_variables.Add(var);
87 }
88 }
89 };
90
91 public:
93
94 void Bind(Block* new_block) {
95 Next::Bind(new_block);
96
98
99 predecessors_.clear();
100 for (const Block* pred : new_block->PredecessorsIterable()) {
101 std::optional<Snapshot> pred_snapshot =
102 block_to_snapshot_mapping_[pred->index()];
103 DCHECK(pred_snapshot.has_value());
104 predecessors_.push_back(pred_snapshot.value());
105 }
106 std::reverse(predecessors_.begin(), predecessors_.end());
107
108 auto merge_variables =
109 [&](Variable var, base::Vector<const OpIndex> predecessors) -> OpIndex {
110 for (OpIndex idx : predecessors) {
111 if (!idx.valid()) {
112 // If any of the predecessors' value is Invalid, then we shouldn't
113 // merge {var}.
114 return OpIndex::Invalid();
115 } else if (__ output_graph()
116 .Get(idx)
117 .template Is<LoadRootRegisterOp>()) {
118 // Variables that once contain the root register never contain another
119 // value.
120 return __ LoadRootRegister();
121 }
122 }
123 return MergeOpIndices(predecessors, var.data().rep);
124 };
125
127 current_block_ = new_block;
128 if (new_block->IsLoop()) {
129 // When starting a loop, we need to create a PendingLoopPhi for each
130 // currently active variable (except those that are marked as
131 // loop-invariant).
132 auto active_loop_variables_begin = table_.active_loop_variables.begin();
133 auto active_loop_variables_end = table_.active_loop_variables.end();
134 if (active_loop_variables_begin != active_loop_variables_end) {
137 MaybeRegisterRepresentation rep = var.data().rep;
139 V<Any> pending_loop_phi =
140 __ PendingLoopPhi(table_.Get(var), RegisterRepresentation(rep));
141 SetVariable(var, pending_loop_phi);
142 pending_phis.push_back({var, pending_loop_phi});
143 }
144 loop_pending_phis_[new_block->index()].emplace(pending_phis);
145 }
146 }
147 }
148
151 DCHECK(block_to_snapshot_mapping_[block->index()].has_value());
153 is_temporary_ = true;
154 }
160
161 V<None> REDUCE(Goto)(Block* destination, bool is_backedge) {
162 V<None> result = Next::ReduceGoto(destination, is_backedge);
163 if (!destination->IsBound()) {
164 return result;
165 }
166
167 // For loops, we have to "fix" the PendingLoopPhis (= replace them with
168 // regular loop phis).
169 DCHECK(destination->IsLoop());
170 DCHECK_EQ(destination->PredecessorCount(), 2);
171
172 if (loop_pending_phis_.contains(destination->index())) {
173 for (auto [var, pending_phi_idx] :
174 loop_pending_phis_[destination->index()].value()) {
175 const PendingLoopPhiOp& pending_phi =
176 __ Get(pending_phi_idx).template Cast<PendingLoopPhiOp>();
177 __ output_graph().template Replace<PhiOp>(
178 pending_phi_idx,
179 base::VectorOf({pending_phi.first(), GetVariable(var)}),
180 pending_phi.rep);
181 }
182 }
183
184 return result;
185 }
186
187 OpIndex GetVariable(Variable var) { return table_.Get(var); }
188
189 OpIndex GetPredecessorValue(Variable var, int predecessor_index) {
190 return table_.GetPredecessorValue(var, predecessor_index);
191 }
192
193 void SetVariable(Variable var, OpIndex new_index) {
195 if (V8_UNLIKELY(__ generating_unreachable_operations())) return;
196 table_.Set(var, new_index);
197 }
198 template <typename Rep>
199 void Set(Variable var, V<Rep> value) {
201 if (V8_UNLIKELY(__ generating_unreachable_operations())) return;
202 DCHECK(
204 table_.Set(var, value);
205 }
206
215
216 // SealAndSaveVariableSnapshot seals the current snapshot, and stores it in
217 // {block_to_snapshot_mapping_}, so that it can be used for later merging.
219 if (table_.IsSealed()) {
220 DCHECK_EQ(current_block_, nullptr);
221 return;
222 }
223
226 current_block_ = nullptr;
227 }
228
229 private:
231 MaybeRegisterRepresentation maybe_rep) {
232 if (maybe_rep != MaybeRegisterRepresentation::None()) {
233 // Every Operation that has a RegisterRepresentation can be merged with a
234 // simple Phi.
235 return __ Phi(base::VectorOf(inputs), RegisterRepresentation(maybe_rep));
236 } else if (__ output_graph().Get(inputs[0]).template Is<FrameStateOp>()) {
237 // Frame states need be be merged recursively, because they represent
238 // multiple scalar values that will lead to multiple phi nodes.
239 return MergeFrameState(inputs);
240 } else {
241 return OpIndex::Invalid();
242 }
243 }
244
247 for (OpIndex idx : frame_states_indices) {
248 frame_states.push_back(
249 &__ output_graph().Get(idx).template Cast<FrameStateOp>());
250 }
251 const FrameStateOp* first_frame = frame_states[0];
252
253#if DEBUG
254 // Making sure that all frame states have the same number of inputs, the
255 // same "inlined" field, and the same data.
256 for (auto frame_state : frame_states) {
257 DCHECK_EQ(first_frame->input_count, frame_state->input_count);
258 DCHECK_EQ(first_frame->inlined, frame_state->inlined);
259 DCHECK_EQ(*first_frame->data, *frame_state->data);
260 }
261#endif
262
264
265 // Merging the parent frame states.
266 if (first_frame->inlined) {
267 ZoneVector<OpIndex> indices_to_merge(__ phase_zone());
268 bool all_parent_frame_states_are_the_same = true;
269 for (auto frame_state : frame_states) {
270 indices_to_merge.push_back(frame_state->parent_frame_state());
271 all_parent_frame_states_are_the_same =
272 all_parent_frame_states_are_the_same &&
273 first_frame->parent_frame_state() ==
274 frame_state->parent_frame_state();
275 }
276 if (all_parent_frame_states_are_the_same) {
277 new_inputs.push_back(first_frame->parent_frame_state());
278 } else {
279 OpIndex merged_parent_frame_state =
280 MergeFrameState(base::VectorOf(indices_to_merge));
281 new_inputs.push_back(merged_parent_frame_state);
282 }
283 }
284
285 // Merging the state values.
286 for (int i = 0; i < first_frame->state_values_count(); i++) {
287 ZoneVector<OpIndex> indices_to_merge(__ phase_zone());
288 bool all_inputs_are_the_same = true;
289 for (auto frame_state : frame_states) {
290 indices_to_merge.push_back(frame_state->state_value(i));
291 all_inputs_are_the_same =
292 all_inputs_are_the_same &&
293 first_frame->state_value(i) == frame_state->state_value(i);
294 }
295 if (all_inputs_are_the_same) {
296 // This input does not need to be merged, since its identical for all of
297 // the frame states.
298 new_inputs.push_back(first_frame->state_value(i));
299 } else {
300 RegisterRepresentation rep = first_frame->state_value_rep(i);
301 OpIndex new_input =
302 MergeOpIndices(base::VectorOf(indices_to_merge), rep);
303 new_inputs.push_back(new_input);
304 }
305 }
306
307 return __ FrameState(base::VectorOf(new_inputs), first_frame->inlined,
308 first_frame->data);
309 }
310
312 const Block* current_block_ = nullptr;
315 bool is_temporary_ = false;
316
317 // {predecessors_} is used during merging, but we use an instance variable for
318 // it, in order to save memory and not reallocate it for each merge.
320
321 // Map from loop headers to the pending loop phis in these headers which have
322 // to be patched on backedges.
324 std::optional<ZoneVector<std::pair<Variable, OpIndex>>>>
326};
327
329
330} // namespace v8::internal::compiler::turboshaft
331
332#endif // V8_COMPILER_TURBOSHAFT_VARIABLE_REDUCER_H_
#define REDUCE(operation)
void push_back(const T &value)
NeighboringPredecessorIterable PredecessorsIterable() const
Definition graph.h:340
Key NewKey(KeyData data, Value initial_value=Value{})
void StartNewSnapshot(base::Vector< const Snapshot > predecessors)
static constexpr MaybeRegisterRepresentation None()
static constexpr OpIndex Invalid()
Definition index.h:88
OpIndex REDUCE Phi(base::Vector< const OpIndex > inputs, RegisterRepresentation rep)
const Value & GetPredecessorValue(Key key, int predecessor_index)
OpIndex GetPredecessorValue(Variable var, int predecessor_index)
OpIndex MergeFrameState(base::Vector< const OpIndex > frame_states_indices)
OpIndex MergeOpIndices(base::Vector< const OpIndex > inputs, MaybeRegisterRepresentation maybe_rep)
Variable NewLoopInvariantVariable(MaybeRegisterRepresentation rep)
V< None > REDUCE Goto(Block *destination, bool is_backedge)
void SetVariable(Variable var, OpIndex new_index)
Variable NewVariable(MaybeRegisterRepresentation rep)
ZoneAbslFlatHashMap< BlockIndex, std::optional< ZoneVector< std::pair< Variable, OpIndex > > > > loop_pending_phis_
GrowingBlockSidetable< std::optional< Snapshot > > block_to_snapshot_mapping_
#define TURBOSHAFT_REDUCER_BOILERPLATE(Name)
Definition assembler.h:823
ZoneVector< RpoNumber > & result
InstructionOperand destination
constexpr Vector< T > VectorOf(T *start, size_t size)
Definition vector.h:360
V8_INLINE const Operation & Get(const Graph &graph, OpIndex index)
Definition graph.h:1231
bool Is(IndirectHandle< U > value)
Definition handles-inl.h:51
Tagged< To > Cast(Tagged< From > value, const v8::SourceLocation &loc=INIT_SOURCE_LOCATION_IN_DEBUG)
Definition casting.h:150
#define DCHECK_NOT_NULL(val)
Definition logging.h:492
#define DCHECK_NE(v1, v2)
Definition logging.h:486
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
const OpIndex state_value(size_t idx) const
RegisterRepresentation state_value_rep(size_t idx) const
void OnValueChange(Variable var, OpIndex old_value, OpIndex new_value)
ZoneIntrusiveSet< Variable, GetActiveLoopVariablesIndex > active_loop_variables
#define V8_UNLIKELY(condition)
Definition v8config.h:660