v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
string-escape-analysis-reducer.h
Go to the documentation of this file.
1// Copyright 2024 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_STRING_ESCAPE_ANALYSIS_REDUCER_H_
6#define V8_COMPILER_TURBOSHAFT_STRING_ESCAPE_ANALYSIS_REDUCER_H_
7
16
18
19// StringEscapeAnalysisReducer tries to remove string concatenations whose
20// results are unused, or used only in FrameStates or in other string concations
21// that are themselves unused.
22//
23// The analysis (StringEscapeAnalyzer::Run) is pretty simple: we iterate the
24// graph backwards and mark all inputs of all operations as "escaping", except
25// for StringLength and FrameState which don't mark their input as escaping, and
26// for StringConcat, which only marks its inputs as escaping if it is itself
27// marked as escaping.
28
30
32 public:
40 void Run();
41
42 bool IsEscaping(OpIndex idx) const {
43 DCHECK(!graph_.Get(idx).Is<FrameStateOp>());
45 }
46
50
51 private:
52 const Graph& graph_;
54
55 void ProcessBlock(const Block& block);
56 void ProcessFrameState(V<FrameState> index, const FrameStateOp& framestate);
58 void MarkAllInputsAsEscaping(const Operation& op);
60 const StringConcatOp* concat);
63
68
71 const FrameStateOp* frame_state = &graph_.Get(idx).Cast<FrameStateOp>();
72 while (frame_state->inlined) {
73 V<FrameState> parent = frame_state->parent_frame_state();
75 frame_state = &graph_.Get(parent).Cast<FrameStateOp>();
76 }
77 }
78
79 // {escaping_operations_and_frame_states_to_recreate_} is used for 2 things:
80 // - For FrameState OpIndex, if the stored value is `true`, then the reducer
81 // should later reconstruct this FrameState (because it either contains an
82 // elided StringConcat, or because it's the parent of such a FrameState).
83 // - For other OpIndex, if the value stored is `true`, then the value is
84 // definitely escaping, and should not be elided (and its inputs shouldn't
85 // be elided either, etc.).
86 // This could easily be split in 2 variables, but it saves space to use a
87 // single variable for this.
90
91 // When we visit a StringConcat for the first time and it's not already in
92 // {escaping_operations_}, we can't know for sure yet that it will never be
93 // escaping, because of loop phis. So, we store it in
94 // {maybe_non_escaping_string_concats_}, which we revisit after having visited
95 // the whole graph, and only after this revisit do we know for sure that
96 // StringConcat that are not in {escaping_operations_} do not indeed escape.
98
100
102};
103
104template <class Next>
105class StringEscapeAnalysisReducer : public Next {
106 public:
107 TURBOSHAFT_REDUCER_BOILERPLATE(StringEscapeAnalysis)
108
109 // ElidedStringPart is an input of a StringConcat that is getting elided. It
110 // could be either a regular String that appears in the output graph
111 // (kNotElided), or another StringConcat that got elided as well (kElided).
113 enum class Kind : uint8_t { kNotElided, kElided };
114 union {
118
120
122 return ElidedStringPart(Kind::kElided, ig_index);
123 }
125 return ElidedStringPart(Kind::kNotElided, og_index);
126 }
127
128 bool is_elided() const { return kind == Kind::kElided; }
129
131 DCHECK_EQ(kind, Kind::kNotElided);
132 return data.og_index;
133 }
135 DCHECK_EQ(kind, Kind::kElided);
136 return data.ig_index;
137 }
138
139 bool operator==(const ElidedStringPart& other) const {
140 if (kind != other.kind) return false;
141 switch (kind) {
142 case Kind::kElided:
143 return ig_index() == other.ig_index();
144 case Kind::kNotElided:
145 return og_index() == other.og_index();
146 }
147 }
148
150 return ElidedStringPart(Kind::kNotElided, V<String>::Invalid());
151 }
152
153 private:
154 ElidedStringPart(Kind kind, V<String> index) : data(index), kind(kind) {}
155 };
156
157 void Analyze() {
158 if (v8_flags.turboshaft_string_concat_escape_analysis) {
159 analyzer_.Run();
160 }
161 Next::Analyze();
162 }
163
165 const StringConcatOp& op) {
166 LABEL_BLOCK(no_change) {
167 return Next::ReduceInputGraphStringConcat(ig_index, op);
168 }
169 if (!v8_flags.turboshaft_string_concat_escape_analysis) goto no_change;
170 if (analyzer_.IsEscaping(ig_index)) goto no_change;
171
172 // We're eliding this StringConcat.
173 ElidedStringPart left = GetElidedStringInput(op.left());
174 ElidedStringPart right = GetElidedStringInput(op.right());
175 elided_strings_.insert({ig_index, std::pair{left, right}});
176 return V<String>::Invalid();
177 }
178
180 V<FrameState> ig_index, const FrameStateOp& frame_state) {
181 LABEL_BLOCK(no_change) {
182 return Next::ReduceInputGraphFrameState(ig_index, frame_state);
183 }
184 if (!v8_flags.turboshaft_string_concat_escape_analysis) goto no_change;
185
186 if (!analyzer_.ShouldReconstructFrameState(ig_index)) goto no_change;
187
188 return BuildFrameState(frame_state, ig_index);
189 }
190
192 const StringLengthOp& op) {
193 LABEL_BLOCK(no_change) {
194 return Next::ReduceInputGraphStringLength(ig_index, op);
195 }
196 if (!v8_flags.turboshaft_string_concat_escape_analysis) goto no_change;
197
198 V<String> input_index = op.string();
199 if (const StringConcatOp* input = __ input_graph()
200 .Get(input_index)
201 .template TryCast<StringConcatOp>();
202 input && !analyzer_.IsEscaping(input_index)) {
203 return __ UntagSmi(__ MapToNewGraph(input->length()));
204 } else {
205 goto no_change;
206 }
207 }
208
209 private:
211 public:
212 explicit Deduplicator(Zone* zone) : string_ids_(zone) {}
213
215 uint32_t id;
217 };
219 // TODO(dmercadier): do better than a linear search here.
220 for (uint32_t id = 0; id < string_ids_.size(); id++) {
221 if (string_ids_[id] == index) {
222 return {id, true};
223 }
224 }
225 uint32_t new_id = static_cast<uint32_t>(string_ids_.size());
226 string_ids_.push_back(index);
227 return {new_id, false};
228 }
229
230 Deduplicator* clone(Zone* zone) const {
231 return zone->New<Deduplicator>(string_ids_);
232 }
233
234 private:
235 explicit Deduplicator(const ZoneVector<ElidedStringPart>& string_ids)
236 : string_ids_(string_ids) {}
237
238 // TODO(dmercadier): consider using a linked list for {string_ids_} so that
239 // we don't ever need to clone it.
241
242 friend class i::Zone; // For access to private constructor.
243 };
244
246 OpIndex ig_index) {
247 DCHECK(v8_flags.turboshaft_string_concat_escape_analysis);
248
249 const FrameStateInfo& info = input_frame_state.data->frame_state_info;
250
252 auto it =
253 input_frame_state.data->iterator(input_frame_state.state_values());
254
255 Deduplicator* deduplicator;
256 if (input_frame_state.inlined) {
257 V<FrameState> parent_ig_index = input_frame_state.parent_frame_state();
258 builder.AddParentFrameState(__ MapToNewGraph(parent_ig_index));
259
260 // The parent FrameState could contain dematerialized objects, and the
261 // current FrameState could reference those. Also, new IDs created for the
262 // current FrameState should not conflict with IDs in the parent frame
263 // state. Thus, we need to initialize the current Deduplicator with the
264 // one from the parent FrameState.
266 deduplicator = deduplicators_[parent_ig_index]->clone(__ phase_zone());
267 } else {
268 deduplicator =
269 __ phase_zone() -> template New<Deduplicator>(__ phase_zone());
270 }
271 deduplicators_[ig_index] = deduplicator;
272
273 // Closure
274 BuildFrameStateInput(&builder, &it, deduplicator);
275
276 // Parameters
277 for (int i = 0; i < info.parameter_count(); i++) {
278 BuildFrameStateInput(&builder, &it, deduplicator);
279 }
280
281 // Context
282 BuildFrameStateInput(&builder, &it, deduplicator);
283
284 // Registers/locals
285 for (int i = 0; i < info.local_count(); i++) {
286 BuildFrameStateInput(&builder, &it, deduplicator);
287 }
288
289 // Accumulator
290 for (int i = 0; i < info.stack_count(); i++) {
291 BuildFrameStateInput(&builder, &it, deduplicator);
292 }
293
294 return __ FrameState(builder.Inputs(), builder.inlined(),
295 builder.AllocateFrameStateData(info, __ graph_zone()));
296 }
297
300 Deduplicator* deduplicator) {
301 switch (it->current_instr()) {
303 case Instr::kInput: {
305 OpIndex input;
306 it->ConsumeInput(&type, &input);
307 if (elided_strings_.contains(input)) {
308 DCHECK(type.IsTagged());
310 deduplicator);
311 } else {
312 builder->AddInput(type, __ MapToNewGraph(input));
313 }
314 break;
315 }
316 case Instr::kDematerializedObject: {
317 uint32_t old_id;
318 uint32_t field_count;
319 it->ConsumeDematerializedObject(&old_id, &field_count);
320 builder->AddDematerializedObject(old_id, field_count);
321 for (uint32_t i = 0; i < field_count; ++i) {
322 BuildFrameStateInput(builder, it, deduplicator);
323 }
324 break;
325 }
326 case Instr::kDematerializedObjectReference: {
327 uint32_t old_id;
328 it->ConsumeDematerializedObjectReference(&old_id);
329 builder->AddDematerializedObjectReference(old_id);
330 break;
331 }
332 case Instr::kArgumentsElements: {
334 it->ConsumeArgumentsElements(&type);
335 builder->AddArgumentsElements(type);
336 break;
337 }
338 case Instr::kArgumentsLength:
339 it->ConsumeArgumentsLength();
340 builder->AddArgumentsLength();
341 break;
342 case Instr::kRestLength:
343 it->ConsumeRestLength();
344 builder->AddRestLength();
345 break;
346 case Instr::kUnusedRegister:
347 it->ConsumeUnusedRegister();
348 builder->AddUnusedRegister();
349 break;
350 case FrameStateData::Instr::kDematerializedStringConcat:
351 case FrameStateData::Instr::kDematerializedStringConcatReference:
352 // StringConcat should not have been escaped before this point.
353 UNREACHABLE();
354 }
355 }
356
358 ElidedStringPart maybe_elided,
359 Deduplicator* deduplicator) {
360 if (maybe_elided.is_elided()) {
361 typename Deduplicator::DuplicatedId dup_id =
362 deduplicator->GetDuplicatedIdForElidedString(maybe_elided);
363 if (dup_id.duplicated) {
364 // For performance reasons, we de-duplicate repeated StringConcat inputs
365 // in the FrameState. Unlike for elided objects, deduplication has no
366 // impact on correctness.
368 return;
369 }
370 builder->AddDematerializedStringConcat(dup_id.id);
371 std::pair<ElidedStringPart, ElidedStringPart> inputs =
372 elided_strings_.at(maybe_elided.ig_index());
373 BuildMaybeElidedString(builder, inputs.first, deduplicator);
374 BuildMaybeElidedString(builder, inputs.second, deduplicator);
375 } else {
376 builder->AddInput(MachineType::AnyTagged(), maybe_elided.og_index());
377 }
378 }
379
381 if (elided_strings_.contains(ig_index)) {
382 return ElidedStringPart::Elided(ig_index);
383 } else {
384 return ElidedStringPart::NotElided(__ MapToNewGraph(ig_index));
385 }
386 }
387
388 StringEscapeAnalyzer analyzer_{Asm().input_graph(), Asm().phase_zone()};
389 // Map from input OpIndex of elided strings to the pair of output OpIndex
390 // that are their left and right sides of the concatenation.
391 ZoneAbslFlatHashMap<V<String>, std::pair<ElidedStringPart, ElidedStringPart>>
392 elided_strings_{Asm().phase_zone()};
393
394 // Mapping from input-graph FrameState to the corresponding deduplicator.
396 &Asm().input_graph()};
397};
398
400
401} // namespace v8::internal::compiler::turboshaft
402
403#endif // V8_COMPILER_TURBOSHAFT_STRING_ESCAPE_ANALYSIS_REDUCER_H_
#define REDUCE_INPUT_GRAPH(operation)
union v8::internal::@341::BuiltinMetadata::KindSpecificData data
Builtins::Kind kind
Definition builtins.cc:40
static constexpr MachineType AnyTagged()
T * New(Args &&... args)
Definition zone.h:114
const FrameStateData * AllocateFrameStateData(const FrameStateInfo &info, Zone *zone)
Definition deopt-data.h:83
void AddDematerializedObject(uint32_t id, uint32_t field_count)
Definition deopt-data.h:56
void AddInput(MachineType type, OpIndex input)
Definition deopt-data.h:41
V< Word32 > REDUCE_INPUT_GRAPH StringLength(V< Word32 > ig_index, const StringLengthOp &op)
void BuildMaybeElidedString(FrameStateData::Builder *builder, ElidedStringPart maybe_elided, Deduplicator *deduplicator)
V< FrameState > BuildFrameState(const FrameStateOp &input_frame_state, OpIndex ig_index)
V< FrameState > REDUCE_INPUT_GRAPH FrameState(V< FrameState > ig_index, const FrameStateOp &frame_state)
void BuildFrameStateInput(FrameStateData::Builder *builder, FrameStateData::Iterator *it, Deduplicator *deduplicator)
V< String > REDUCE_INPUT_GRAPH StringConcat(V< String > ig_index, const StringConcatOp &op)
ZoneAbslFlatHashMap< V< String >, std::pair< ElidedStringPart, ElidedStringPart > > elided_strings_
void ProcessFrameState(V< FrameState > index, const FrameStateOp &framestate)
#define TURBOSHAFT_REDUCER_BOILERPLATE(Name)
Definition assembler.h:823
#define LABEL_BLOCK(label)
Definition assembler.h:910
Zone * graph_zone
V8_INLINE const Operation & Get(const Graph &graph, OpIndex index)
Definition graph.h:1231
bool TryCast(Tagged< From > value, Tagged< To > *out)
Definition casting.h:77
V8_EXPORT_PRIVATE FlagValues v8_flags
uint32_t concat
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
base::Vector< const OpIndex > state_values() const
wasm::ValueType type