1 // Copyright 2016 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 #include "src/interpreter/bytecode-peephole-optimizer.h"
6
7 #include "src/objects-inl.h"
8 #include "src/objects.h"
9
10 namespace v8 {
11 namespace internal {
12 namespace interpreter {
13
BytecodePeepholeOptimizer(BytecodePipelineStage * next_stage)14 BytecodePeepholeOptimizer::BytecodePeepholeOptimizer(
15 BytecodePipelineStage* next_stage)
16 : next_stage_(next_stage),
17 last_(BytecodeNode::Illegal(BytecodeSourceInfo())) {
18 InvalidateLast();
19 }
20
21 // override
ToBytecodeArray(Isolate * isolate,int register_count,int parameter_count,Handle<FixedArray> handler_table)22 Handle<BytecodeArray> BytecodePeepholeOptimizer::ToBytecodeArray(
23 Isolate* isolate, int register_count, int parameter_count,
24 Handle<FixedArray> handler_table) {
25 Flush();
26 return next_stage_->ToBytecodeArray(isolate, register_count, parameter_count,
27 handler_table);
28 }
29
30 // override
BindLabel(BytecodeLabel * label)31 void BytecodePeepholeOptimizer::BindLabel(BytecodeLabel* label) {
32 Flush();
33 next_stage_->BindLabel(label);
34 }
35
36 // override
BindLabel(const BytecodeLabel & target,BytecodeLabel * label)37 void BytecodePeepholeOptimizer::BindLabel(const BytecodeLabel& target,
38 BytecodeLabel* label) {
39 // There is no need to flush here, it will have been flushed when
40 // |target| was bound.
41 next_stage_->BindLabel(target, label);
42 }
43
44 // override
WriteJump(BytecodeNode * node,BytecodeLabel * label)45 void BytecodePeepholeOptimizer::WriteJump(BytecodeNode* node,
46 BytecodeLabel* label) {
47 // Handlers for jump bytecodes do not emit |node| as WriteJump()
48 // requires the |label| and having a label argument in all action
49 // handlers results in dead work in the non-jump case.
50 ApplyPeepholeAction(node);
51 next_stage()->WriteJump(node, label);
52 }
53
54 // override
Write(BytecodeNode * node)55 void BytecodePeepholeOptimizer::Write(BytecodeNode* node) {
56 // Handlers for non-jump bytecodes run to completion emitting
57 // bytecode to next stage as appropriate.
58 ApplyPeepholeAction(node);
59 }
60
Flush()61 void BytecodePeepholeOptimizer::Flush() {
62 if (LastIsValid()) {
63 next_stage_->Write(&last_);
64 InvalidateLast();
65 }
66 }
67
InvalidateLast()68 void BytecodePeepholeOptimizer::InvalidateLast() {
69 last_ = BytecodeNode::Illegal(BytecodeSourceInfo());
70 }
71
LastIsValid() const72 bool BytecodePeepholeOptimizer::LastIsValid() const {
73 return last_.bytecode() != Bytecode::kIllegal;
74 }
75
SetLast(const BytecodeNode * const node)76 void BytecodePeepholeOptimizer::SetLast(const BytecodeNode* const node) {
77 // An action shouldn't leave a NOP as last bytecode unless it has
78 // source position information. NOP without source information can
79 // always be elided.
80 DCHECK(node->bytecode() != Bytecode::kNop || node->source_info().is_valid());
81 last_ = *node;
82 }
83
CanElideLastBasedOnSourcePosition(const BytecodeNode * const current) const84 bool BytecodePeepholeOptimizer::CanElideLastBasedOnSourcePosition(
85 const BytecodeNode* const current) const {
86 //
87 // The rules for allowing the elision of the last bytecode based
88 // on source position are:
89 //
90 // C U R R E N T
91 // +--------+--------+--------+
92 // | None | Expr | Stmt |
93 // L +--------+--------+--------+--------+
94 // | None | YES | YES | YES |
95 // A +--------+--------+--------+--------+
96 // | Expr | YES | MAYBE | MAYBE |
97 // S +--------+--------+--------+--------+
98 // | Stmt | YES | NO | NO |
99 // T +--------+--------+--------+--------+
100 //
101 // The goal is not lose any statement positions and not lose useful
102 // expression positions. Whenever the last bytecode is elided it's
103 // source position information is applied to the current node
104 // updating it if necessary.
105 //
106 // The last bytecode could be elided for the MAYBE cases if the last
107 // bytecode is known not to throw. If it throws, the system would
108 // not have correct stack trace information. The appropriate check
109 // for this would be Bytecodes::IsWithoutExternalSideEffects(). By
110 // default, the upstream bytecode generator filters out unneeded
111 // expression position information so there is neglible benefit to
112 // handling MAYBE specially. Hence MAYBE is treated the same as NO.
113 //
114 return (!last_.source_info().is_valid() ||
115 !current->source_info().is_valid());
116 }
117
118 namespace {
119
TransformLdaSmiBinaryOpToBinaryOpWithSmi(Bytecode new_bytecode,BytecodeNode * const last,BytecodeNode * const current)120 BytecodeNode TransformLdaSmiBinaryOpToBinaryOpWithSmi(
121 Bytecode new_bytecode, BytecodeNode* const last,
122 BytecodeNode* const current) {
123 DCHECK_EQ(last->bytecode(), Bytecode::kLdaSmi);
124 BytecodeNode node(new_bytecode, last->operand(0), current->operand(0),
125 current->operand(1), current->source_info());
126 if (last->source_info().is_valid()) {
127 node.set_source_info(last->source_info());
128 }
129 return node;
130 }
131
TransformLdaZeroBinaryOpToBinaryOpWithZero(Bytecode new_bytecode,BytecodeNode * const last,BytecodeNode * const current)132 BytecodeNode TransformLdaZeroBinaryOpToBinaryOpWithZero(
133 Bytecode new_bytecode, BytecodeNode* const last,
134 BytecodeNode* const current) {
135 DCHECK_EQ(last->bytecode(), Bytecode::kLdaZero);
136 BytecodeNode node(new_bytecode, 0, current->operand(0), current->operand(1),
137 current->source_info());
138 if (last->source_info().is_valid()) {
139 node.set_source_info(last->source_info());
140 }
141 return node;
142 }
143
TransformEqualityWithNullOrUndefined(Bytecode new_bytecode,BytecodeNode * const last,BytecodeNode * const current)144 BytecodeNode TransformEqualityWithNullOrUndefined(Bytecode new_bytecode,
145 BytecodeNode* const last,
146 BytecodeNode* const current) {
147 DCHECK((last->bytecode() == Bytecode::kLdaNull) ||
148 (last->bytecode() == Bytecode::kLdaUndefined));
149 DCHECK((current->bytecode() == Bytecode::kTestEqual) ||
150 (current->bytecode() == Bytecode::kTestEqualStrict));
151 BytecodeNode node(new_bytecode, current->operand(0), current->source_info());
152 if (last->source_info().is_valid()) {
153 node.set_source_info(last->source_info());
154 }
155 return node;
156 }
157
158 } // namespace
159
DefaultAction(BytecodeNode * const node,const PeepholeActionAndData * action_data)160 void BytecodePeepholeOptimizer::DefaultAction(
161 BytecodeNode* const node, const PeepholeActionAndData* action_data) {
162 DCHECK(LastIsValid());
163 DCHECK(!Bytecodes::IsJump(node->bytecode()));
164
165 next_stage()->Write(last());
166 SetLast(node);
167 }
168
UpdateLastAction(BytecodeNode * const node,const PeepholeActionAndData * action_data)169 void BytecodePeepholeOptimizer::UpdateLastAction(
170 BytecodeNode* const node, const PeepholeActionAndData* action_data) {
171 DCHECK(!LastIsValid());
172 DCHECK(!Bytecodes::IsJump(node->bytecode()));
173
174 SetLast(node);
175 }
176
UpdateLastIfSourceInfoPresentAction(BytecodeNode * const node,const PeepholeActionAndData * action_data)177 void BytecodePeepholeOptimizer::UpdateLastIfSourceInfoPresentAction(
178 BytecodeNode* const node, const PeepholeActionAndData* action_data) {
179 DCHECK(!LastIsValid());
180 DCHECK(!Bytecodes::IsJump(node->bytecode()));
181
182 if (node->source_info().is_valid()) {
183 SetLast(node);
184 }
185 }
186
ElideCurrentAction(BytecodeNode * const node,const PeepholeActionAndData * action_data)187 void BytecodePeepholeOptimizer::ElideCurrentAction(
188 BytecodeNode* const node, const PeepholeActionAndData* action_data) {
189 DCHECK(LastIsValid());
190 DCHECK(!Bytecodes::IsJump(node->bytecode()));
191
192 if (node->source_info().is_valid()) {
193 // Preserve the source information by replacing the node bytecode
194 // with a no op bytecode.
195 BytecodeNode new_node(BytecodeNode::Nop(node->source_info()));
196 DefaultAction(&new_node);
197 } else {
198 // Nothing to do, keep last and wait for next bytecode to pair with it.
199 }
200 }
201
ElideCurrentIfOperand0MatchesAction(BytecodeNode * const node,const PeepholeActionAndData * action_data)202 void BytecodePeepholeOptimizer::ElideCurrentIfOperand0MatchesAction(
203 BytecodeNode* const node, const PeepholeActionAndData* action_data) {
204 DCHECK(LastIsValid());
205 DCHECK(!Bytecodes::IsJump(node->bytecode()));
206
207 if (last()->operand(0) == node->operand(0)) {
208 ElideCurrentAction(node);
209 } else {
210 DefaultAction(node);
211 }
212 }
213
ElideLastAction(BytecodeNode * const node,const PeepholeActionAndData * action_data)214 void BytecodePeepholeOptimizer::ElideLastAction(
215 BytecodeNode* const node, const PeepholeActionAndData* action_data) {
216 DCHECK(LastIsValid());
217 DCHECK(!Bytecodes::IsJump(node->bytecode()));
218
219 if (CanElideLastBasedOnSourcePosition(node)) {
220 if (last()->source_info().is_valid()) {
221 // |node| can not have a valid source position if the source
222 // position of last() is valid (per rules in
223 // CanElideLastBasedOnSourcePosition()).
224 node->set_source_info(last()->source_info());
225 }
226 SetLast(node);
227 } else {
228 DefaultAction(node);
229 }
230 }
231
ChangeBytecodeAction(BytecodeNode * const node,const PeepholeActionAndData * action_data)232 void BytecodePeepholeOptimizer::ChangeBytecodeAction(
233 BytecodeNode* const node, const PeepholeActionAndData* action_data) {
234 DCHECK(LastIsValid());
235 DCHECK(!Bytecodes::IsJump(node->bytecode()));
236
237 node->replace_bytecode(action_data->bytecode);
238 DefaultAction(node);
239 }
240
TransformLdaSmiBinaryOpToBinaryOpWithSmiAction(BytecodeNode * const node,const PeepholeActionAndData * action_data)241 void BytecodePeepholeOptimizer::TransformLdaSmiBinaryOpToBinaryOpWithSmiAction(
242 BytecodeNode* const node, const PeepholeActionAndData* action_data) {
243 DCHECK(LastIsValid());
244 DCHECK(!Bytecodes::IsJump(node->bytecode()));
245
246 if (!node->source_info().is_valid() || !last()->source_info().is_valid()) {
247 // Fused last and current into current.
248 BytecodeNode new_node(TransformLdaSmiBinaryOpToBinaryOpWithSmi(
249 action_data->bytecode, last(), node));
250 SetLast(&new_node);
251 } else {
252 DefaultAction(node);
253 }
254 }
255
256 void BytecodePeepholeOptimizer::
TransformLdaZeroBinaryOpToBinaryOpWithZeroAction(BytecodeNode * const node,const PeepholeActionAndData * action_data)257 TransformLdaZeroBinaryOpToBinaryOpWithZeroAction(
258 BytecodeNode* const node, const PeepholeActionAndData* action_data) {
259 DCHECK(LastIsValid());
260 DCHECK(!Bytecodes::IsJump(node->bytecode()));
261 if (!node->source_info().is_valid() || !last()->source_info().is_valid()) {
262 // Fused last and current into current.
263 BytecodeNode new_node(TransformLdaZeroBinaryOpToBinaryOpWithZero(
264 action_data->bytecode, last(), node));
265 SetLast(&new_node);
266 } else {
267 DefaultAction(node);
268 }
269 }
270
TransformEqualityWithNullOrUndefinedAction(BytecodeNode * const node,const PeepholeActionAndData * action_data)271 void BytecodePeepholeOptimizer::TransformEqualityWithNullOrUndefinedAction(
272 BytecodeNode* const node, const PeepholeActionAndData* action_data) {
273 DCHECK(LastIsValid());
274 DCHECK(!Bytecodes::IsJump(node->bytecode()));
275 // Fused last and current into current.
276 BytecodeNode new_node(TransformEqualityWithNullOrUndefined(
277 action_data->bytecode, last(), node));
278 SetLast(&new_node);
279 }
280
DefaultJumpAction(BytecodeNode * const node,const PeepholeActionAndData * action_data)281 void BytecodePeepholeOptimizer::DefaultJumpAction(
282 BytecodeNode* const node, const PeepholeActionAndData* action_data) {
283 DCHECK(LastIsValid());
284 DCHECK(Bytecodes::IsJump(node->bytecode()));
285
286 next_stage()->Write(last());
287 InvalidateLast();
288 }
289
UpdateLastJumpAction(BytecodeNode * const node,const PeepholeActionAndData * action_data)290 void BytecodePeepholeOptimizer::UpdateLastJumpAction(
291 BytecodeNode* const node, const PeepholeActionAndData* action_data) {
292 DCHECK(!LastIsValid());
293 DCHECK(Bytecodes::IsJump(node->bytecode()));
294 }
295
ChangeJumpBytecodeAction(BytecodeNode * const node,const PeepholeActionAndData * action_data)296 void BytecodePeepholeOptimizer::ChangeJumpBytecodeAction(
297 BytecodeNode* const node, const PeepholeActionAndData* action_data) {
298 DCHECK(LastIsValid());
299 DCHECK(Bytecodes::IsJump(node->bytecode()));
300
301 next_stage()->Write(last());
302 InvalidateLast();
303 node->replace_bytecode(action_data->bytecode);
304 }
305
ElideLastBeforeJumpAction(BytecodeNode * const node,const PeepholeActionAndData * action_data)306 void BytecodePeepholeOptimizer::ElideLastBeforeJumpAction(
307 BytecodeNode* const node, const PeepholeActionAndData* action_data) {
308 DCHECK(LastIsValid());
309 DCHECK(Bytecodes::IsJump(node->bytecode()));
310
311 if (!CanElideLastBasedOnSourcePosition(node)) {
312 next_stage()->Write(last());
313 } else if (!node->source_info().is_valid()) {
314 node->set_source_info(last()->source_info());
315 }
316 InvalidateLast();
317 }
318
ApplyPeepholeAction(BytecodeNode * const node)319 void BytecodePeepholeOptimizer::ApplyPeepholeAction(BytecodeNode* const node) {
320 // A single table is used for looking up peephole optimization
321 // matches as it is observed to have better performance. This is
322 // inspite of the fact that jump bytecodes and non-jump bytecodes
323 // have different processing logic, in particular a jump bytecode
324 // always needs to emit the jump via WriteJump().
325 const PeepholeActionAndData* const action_data =
326 PeepholeActionTable::Lookup(last()->bytecode(), node->bytecode());
327 switch (action_data->action) {
328 #define CASE(Action) \
329 case PeepholeAction::k##Action: \
330 Action(node, action_data); \
331 break;
332 PEEPHOLE_ACTION_LIST(CASE)
333 #undef CASE
334 default:
335 UNREACHABLE();
336 break;
337 }
338 }
339
340 } // namespace interpreter
341 } // namespace internal
342 } // namespace v8
343