• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2015 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/interpreter/constant-array-builder.h"
8 #include "src/objects-inl.h"
9 #include "src/objects.h"
10 
11 namespace v8 {
12 namespace internal {
13 namespace interpreter {
14 
BytecodePeepholeOptimizer(ConstantArrayBuilder * constant_array_builder,BytecodePipelineStage * next_stage)15 BytecodePeepholeOptimizer::BytecodePeepholeOptimizer(
16     ConstantArrayBuilder* constant_array_builder,
17     BytecodePipelineStage* next_stage)
18     : constant_array_builder_(constant_array_builder), next_stage_(next_stage) {
19   InvalidateLast();
20 }
21 
22 // override
ToBytecodeArray(int fixed_register_count,int parameter_count,Handle<FixedArray> handler_table)23 Handle<BytecodeArray> BytecodePeepholeOptimizer::ToBytecodeArray(
24     int fixed_register_count, int parameter_count,
25     Handle<FixedArray> handler_table) {
26   Flush();
27   return next_stage_->ToBytecodeArray(fixed_register_count, parameter_count,
28                                       handler_table);
29 }
30 
31 // override
Write(BytecodeNode * node)32 void BytecodePeepholeOptimizer::Write(BytecodeNode* node) {
33   node = OptimizeAndEmitLast(node);
34   if (node != nullptr) {
35     SetLast(node);
36   }
37 }
38 
39 // override
WriteJump(BytecodeNode * node,BytecodeLabel * label)40 void BytecodePeepholeOptimizer::WriteJump(BytecodeNode* node,
41                                           BytecodeLabel* label) {
42   node = OptimizeAndEmitLast(node);
43   next_stage_->WriteJump(node, label);
44 }
45 
46 // override
BindLabel(BytecodeLabel * label)47 void BytecodePeepholeOptimizer::BindLabel(BytecodeLabel* label) {
48   Flush();
49   next_stage_->BindLabel(label);
50 }
51 
52 // override
BindLabel(const BytecodeLabel & target,BytecodeLabel * label)53 void BytecodePeepholeOptimizer::BindLabel(const BytecodeLabel& target,
54                                           BytecodeLabel* label) {
55   // There is no need to flush here, it will have been flushed when |target|
56   // was bound.
57   next_stage_->BindLabel(target, label);
58 }
59 
Flush()60 void BytecodePeepholeOptimizer::Flush() {
61   // TODO(oth/rmcilroy): We could check CanElideLast() here to potentially
62   // eliminate last rather than writing it.
63   if (LastIsValid()) {
64     next_stage_->Write(&last_);
65     InvalidateLast();
66   }
67 }
68 
InvalidateLast()69 void BytecodePeepholeOptimizer::InvalidateLast() {
70   last_.set_bytecode(Bytecode::kIllegal);
71 }
72 
LastIsValid() const73 bool BytecodePeepholeOptimizer::LastIsValid() const {
74   return last_.bytecode() != Bytecode::kIllegal;
75 }
76 
SetLast(const BytecodeNode * const node)77 void BytecodePeepholeOptimizer::SetLast(const BytecodeNode* const node) {
78   last_.Clone(node);
79 }
80 
GetConstantForIndexOperand(const BytecodeNode * const node,int index) const81 Handle<Object> BytecodePeepholeOptimizer::GetConstantForIndexOperand(
82     const BytecodeNode* const node, int index) const {
83   DCHECK_LE(index, node->operand_count());
84   DCHECK_EQ(Bytecodes::GetOperandType(node->bytecode(), 0), OperandType::kIdx);
85   uint32_t index_operand = node->operand(0);
86   return constant_array_builder_->At(index_operand);
87 }
88 
LastBytecodePutsNameInAccumulator() const89 bool BytecodePeepholeOptimizer::LastBytecodePutsNameInAccumulator() const {
90   DCHECK(LastIsValid());
91   return (last_.bytecode() == Bytecode::kTypeOf ||
92           last_.bytecode() == Bytecode::kToName ||
93           (last_.bytecode() == Bytecode::kLdaConstant &&
94            GetConstantForIndexOperand(&last_, 0)->IsName()));
95 }
96 
TryToRemoveLastExpressionPosition(const BytecodeNode * const current)97 void BytecodePeepholeOptimizer::TryToRemoveLastExpressionPosition(
98     const BytecodeNode* const current) {
99   if (current->source_info().is_valid() &&
100       last_.source_info().is_expression() &&
101       Bytecodes::IsWithoutExternalSideEffects(last_.bytecode())) {
102     // The last bytecode has been marked as expression. It has no
103     // external effects so can't throw and the current bytecode is a
104     // source position. Remove the expression position on the last
105     // bytecode to open up potential peephole optimizations and to
106     // save the memory and perf cost of storing the unneeded
107     // expression position.
108     last_.source_info().set_invalid();
109   }
110 }
111 
CanElideCurrent(const BytecodeNode * const current) const112 bool BytecodePeepholeOptimizer::CanElideCurrent(
113     const BytecodeNode* const current) const {
114   if (Bytecodes::IsLdarOrStar(last_.bytecode()) &&
115       Bytecodes::IsLdarOrStar(current->bytecode()) &&
116       current->operand(0) == last_.operand(0)) {
117     // Ldar and Star make the accumulator and register hold equivalent
118     // values. Only the first bytecode is needed if there's a sequence
119     // of back-to-back Ldar and Star bytecodes with the same operand.
120     return true;
121   } else if (current->bytecode() == Bytecode::kToName &&
122              LastBytecodePutsNameInAccumulator()) {
123     // If the previous bytecode ensured a name was in the accumulator,
124     // the type coercion ToName() can be elided.
125     return true;
126   } else {
127     // Additional candidates for eliding current:
128     // (i) ToNumber if the last puts a number in the accumulator.
129     return false;
130   }
131 }
132 
CanElideLastBasedOnSourcePosition(const BytecodeNode * const current) const133 bool BytecodePeepholeOptimizer::CanElideLastBasedOnSourcePosition(
134     const BytecodeNode* const current) const {
135   //
136   // The rules for allowing the elision of the last bytecode based
137   // on source position are:
138   //
139   //                     C U R R E N T
140   //              +--------+--------+--------+
141   //              |  None  |  Expr  |  Stmt  |
142   //  L  +--------+--------+--------+--------+
143   //     |  None  |  YES   |  YES   |  YES   |
144   //  A  +--------+--------+--------+--------+
145   //     |  Expr  |  YES   | MAYBE  |  MAYBE |
146   //  S  +--------+--------+--------+--------+
147   //     |  Stmt  |  YES   |   NO   |   NO   |
148   //  T  +--------+--------+--------+--------+
149   //
150   // The goal is not lose any statement positions and not lose useful
151   // expression positions. Whenever the last bytecode is elided it's
152   // source position information is applied to the current node
153   // updating it if necessary.
154   //
155   // The last bytecode can be elided for the MAYBE cases if the last
156   // bytecode is known not to throw. If it throws, the system would
157   // not have correct stack trace information. The appropriate check
158   // for this would be Bytecodes::IsWithoutExternalSideEffects(),
159   // which is checked in
160   // BytecodePeepholeOptimizer::TransformLastAndCurrentBytecodes() to
161   // keep the check here simple.
162   //
163   // In rare cases, bytecode generation produces consecutive bytecodes
164   // with the same expression positions. In principle, the latter of
165   // these can be elided, but would make this function more expensive.
166   //
167   return (!last_.source_info().is_valid() ||
168           !current->source_info().is_valid());
169 }
170 
171 namespace {
172 
TransformLdaStarToLdrLdar(Bytecode new_bytecode,BytecodeNode * const last,BytecodeNode * const current)173 void TransformLdaStarToLdrLdar(Bytecode new_bytecode, BytecodeNode* const last,
174                                BytecodeNode* const current) {
175   DCHECK_EQ(current->bytecode(), Bytecode::kStar);
176 
177   //
178   // An example transformation here would be:
179   //
180   //   LdaGlobal i0, i1  ____\  LdrGlobal i0, i1, R
181   //   Star R            ====/  Ldar R
182   //
183   // which loads a global value into both a register and the
184   // accumulator. However, in the second form the Ldar can often be
185   // peephole optimized away unlike the Star in the first form.
186   //
187   last->Transform(new_bytecode, current->operand(0));
188   current->set_bytecode(Bytecode::kLdar, current->operand(0));
189 }
190 
191 }  // namespace
192 
TransformLastAndCurrentBytecodes(BytecodeNode * const current)193 bool BytecodePeepholeOptimizer::TransformLastAndCurrentBytecodes(
194     BytecodeNode* const current) {
195   if (current->bytecode() == Bytecode::kStar &&
196       !current->source_info().is_statement()) {
197     // Note: If the Star is tagged with a statement position, we can't
198     // perform this transform as the store to the register will
199     // have the wrong ordering for stepping in the debugger.
200     switch (last_.bytecode()) {
201       case Bytecode::kLdaNamedProperty:
202         TransformLdaStarToLdrLdar(Bytecode::kLdrNamedProperty, &last_, current);
203         return true;
204       case Bytecode::kLdaKeyedProperty:
205         TransformLdaStarToLdrLdar(Bytecode::kLdrKeyedProperty, &last_, current);
206         return true;
207       case Bytecode::kLdaGlobal:
208         TransformLdaStarToLdrLdar(Bytecode::kLdrGlobal, &last_, current);
209         return true;
210       case Bytecode::kLdaContextSlot:
211         TransformLdaStarToLdrLdar(Bytecode::kLdrContextSlot, &last_, current);
212         return true;
213       case Bytecode::kLdaUndefined:
214         TransformLdaStarToLdrLdar(Bytecode::kLdrUndefined, &last_, current);
215         return true;
216       default:
217         break;
218     }
219   }
220   return false;
221 }
222 
RemoveToBooleanFromJump(BytecodeNode * const current)223 bool BytecodePeepholeOptimizer::RemoveToBooleanFromJump(
224     BytecodeNode* const current) {
225   bool can_remove = Bytecodes::IsJumpIfToBoolean(current->bytecode()) &&
226                     Bytecodes::WritesBooleanToAccumulator(last_.bytecode());
227   if (can_remove) {
228     // Conditional jumps with boolean conditions are emiitted in
229     // ToBoolean form by the bytecode array builder,
230     // i.e. JumpIfToBooleanTrue rather JumpIfTrue. The ToBoolean
231     // element can be removed if the previous bytecode put a boolean
232     // value in the accumulator.
233     Bytecode jump = Bytecodes::GetJumpWithoutToBoolean(current->bytecode());
234     current->set_bytecode(jump, current->operand(0));
235   }
236   return can_remove;
237 }
238 
RemoveToBooleanFromLogicalNot(BytecodeNode * const current)239 bool BytecodePeepholeOptimizer::RemoveToBooleanFromLogicalNot(
240     BytecodeNode* const current) {
241   bool can_remove = current->bytecode() == Bytecode::kToBooleanLogicalNot &&
242                     Bytecodes::WritesBooleanToAccumulator(last_.bytecode());
243   if (can_remove) {
244     // Logical-nots are emitted in ToBoolean form by the bytecode array
245     // builder, The ToBoolean element can be removed if the previous bytecode
246     // put a boolean value in the accumulator.
247     current->set_bytecode(Bytecode::kLogicalNot);
248   }
249   return can_remove;
250 }
251 
TransformCurrentBytecode(BytecodeNode * const current)252 bool BytecodePeepholeOptimizer::TransformCurrentBytecode(
253     BytecodeNode* const current) {
254   return RemoveToBooleanFromJump(current) ||
255          RemoveToBooleanFromLogicalNot(current);
256 }
257 
CanElideLast(const BytecodeNode * const current) const258 bool BytecodePeepholeOptimizer::CanElideLast(
259     const BytecodeNode* const current) const {
260   if (last_.bytecode() == Bytecode::kNop) {
261     // Nop are placeholders for holding source position information.
262     return true;
263   } else if (Bytecodes::IsAccumulatorLoadWithoutEffects(current->bytecode()) &&
264              Bytecodes::IsAccumulatorLoadWithoutEffects(last_.bytecode())) {
265     // The accumulator is invisible to the debugger. If there is a sequence of
266     // consecutive accumulator loads (that don't have side effects) then only
267     // the final load is potentially visible.
268     return true;
269   } else if (Bytecodes::GetAccumulatorUse(current->bytecode()) ==
270                  AccumulatorUse::kWrite &&
271              Bytecodes::IsAccumulatorLoadWithoutEffects(last_.bytecode())) {
272     // The current instruction clobbers the accumulator without reading it. The
273     // load in the last instruction can be elided as it has no effect.
274     return true;
275   } else {
276     return false;
277   }
278 }
279 
Optimize(BytecodeNode * current)280 BytecodeNode* BytecodePeepholeOptimizer::Optimize(BytecodeNode* current) {
281   TryToRemoveLastExpressionPosition(current);
282 
283   if (TransformCurrentBytecode(current) ||
284       TransformLastAndCurrentBytecodes(current)) {
285     return current;
286   }
287 
288   if (CanElideCurrent(current)) {
289     if (current->source_info().is_valid()) {
290       // Preserve the source information by replacing the current bytecode
291       // with a no op bytecode.
292       current->set_bytecode(Bytecode::kNop);
293     } else {
294       current = nullptr;
295     }
296     return current;
297   }
298 
299   if (CanElideLast(current) && CanElideLastBasedOnSourcePosition(current)) {
300     if (last_.source_info().is_valid()) {
301       // Current can not be valid per CanElideLastBasedOnSourcePosition().
302       current->source_info().Clone(last_.source_info());
303     }
304     InvalidateLast();
305     return current;
306   }
307 
308   return current;
309 }
310 
OptimizeAndEmitLast(BytecodeNode * current)311 BytecodeNode* BytecodePeepholeOptimizer::OptimizeAndEmitLast(
312     BytecodeNode* current) {
313   // Attempt optimization if there is an earlier node to optimize with.
314   if (LastIsValid()) {
315     current = Optimize(current);
316     // Only output the last node if it wasn't invalidated by the optimization.
317     if (LastIsValid()) {
318       next_stage_->Write(&last_);
319       InvalidateLast();
320     }
321   }
322   return current;
323 }
324 
325 }  // namespace interpreter
326 }  // namespace internal
327 }  // namespace v8
328