1 // Copyright 2019 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 "irregexp/imported/regexp-bytecode-peephole.h"
6
7 #include "irregexp/imported/regexp-bytecodes.h"
8
9 namespace v8 {
10 namespace internal {
11
12 namespace {
13
14 struct BytecodeArgument {
15 int offset;
16 int length;
17
BytecodeArgumentv8::internal::__anon18e917590111::BytecodeArgument18 BytecodeArgument(int offset, int length) : offset(offset), length(length) {}
19 };
20
21 struct BytecodeArgumentMapping : BytecodeArgument {
22 int new_length;
23
BytecodeArgumentMappingv8::internal::__anon18e917590111::BytecodeArgumentMapping24 BytecodeArgumentMapping(int offset, int length, int new_length)
25 : BytecodeArgument(offset, length), new_length(new_length) {}
26 };
27
28 struct BytecodeArgumentCheck : BytecodeArgument {
29 enum CheckType { kCheckAddress = 0, kCheckValue };
30 CheckType type;
31 int check_offset;
32 int check_length;
33
BytecodeArgumentCheckv8::internal::__anon18e917590111::BytecodeArgumentCheck34 BytecodeArgumentCheck(int offset, int length, int check_offset)
35 : BytecodeArgument(offset, length),
36 type(kCheckAddress),
37 check_offset(check_offset) {}
BytecodeArgumentCheckv8::internal::__anon18e917590111::BytecodeArgumentCheck38 BytecodeArgumentCheck(int offset, int length, int check_offset,
39 int check_length)
40 : BytecodeArgument(offset, length),
41 type(kCheckValue),
42 check_offset(check_offset),
43 check_length(check_length) {}
44 };
45
46 // Trie-Node for storing bytecode sequences we want to optimize.
47 class BytecodeSequenceNode {
48 public:
49 // Dummy bytecode used when we need to store/return a bytecode but it's not a
50 // valid bytecode in the current context.
51 static constexpr int kDummyBytecode = -1;
52
53 BytecodeSequenceNode(int bytecode, Zone* zone);
54 // Adds a new node as child of the current node if it isn't a child already.
55 BytecodeSequenceNode& FollowedBy(int bytecode);
56 // Marks the end of a sequence and sets optimized bytecode to replace all
57 // bytecodes of the sequence with.
58 BytecodeSequenceNode& ReplaceWith(int bytecode);
59 // Maps arguments of bytecodes in the sequence to the optimized bytecode.
60 // Order of invocation determines order of arguments in the optimized
61 // bytecode.
62 // Invoking this method is only allowed on nodes that mark the end of a valid
63 // sequence (i.e. after ReplaceWith()).
64 // bytecode_index_in_sequence: Zero-based index of the referred bytecode
65 // within the sequence (e.g. the bytecode passed to CreateSequence() has
66 // index 0).
67 // argument_offset: Zero-based offset to the argument within the bytecode
68 // (e.g. the first argument that's not packed with the bytecode has offset 4).
69 // argument_byte_length: Length of the argument.
70 // new_argument_byte_length: Length of the argument in the new bytecode
71 // (= argument_byte_length if omitted).
72 BytecodeSequenceNode& MapArgument(int bytecode_index_in_sequence,
73 int argument_offset,
74 int argument_byte_length,
75 int new_argument_byte_length = 0);
76 // Adds a check to the sequence node making it only a valid sequence when the
77 // argument of the current bytecode at the specified offset matches the offset
78 // to check against.
79 // argument_offset: Zero-based offset to the argument within the bytecode
80 // (e.g. the first argument that's not packed with the bytecode has offset 4).
81 // argument_byte_length: Length of the argument.
82 // check_byte_offset: Zero-based offset relative to the beginning of the
83 // sequence that needs to match the value given by argument_offset. (e.g.
84 // check_byte_offset 0 matches the address of the first bytecode in the
85 // sequence).
86 BytecodeSequenceNode& IfArgumentEqualsOffset(int argument_offset,
87 int argument_byte_length,
88 int check_byte_offset);
89 // Adds a check to the sequence node making it only a valid sequence when the
90 // argument of the current bytecode at the specified offset matches the
91 // argument of another bytecode in the sequence.
92 // This is similar to IfArgumentEqualsOffset, except that this method matches
93 // the values of both arguments.
94 BytecodeSequenceNode& IfArgumentEqualsValueAtOffset(
95 int argument_offset, int argument_byte_length,
96 int other_bytecode_index_in_sequence, int other_argument_offset,
97 int other_argument_byte_length);
98 // Marks an argument as unused.
99 // All arguments that are not mapped explicitly have to be marked as unused.
100 // bytecode_index_in_sequence: Zero-based index of the referred bytecode
101 // within the sequence (e.g. the bytecode passed to CreateSequence() has
102 // index 0).
103 // argument_offset: Zero-based offset to the argument within the bytecode
104 // (e.g. the first argument that's not packed with the bytecode has offset 4).
105 // argument_byte_length: Length of the argument.
106 BytecodeSequenceNode& IgnoreArgument(int bytecode_index_in_sequence,
107 int argument_offset,
108 int argument_byte_length);
109 // Checks if the current node is valid for the sequence. I.e. all conditions
110 // set by IfArgumentEqualsOffset and IfArgumentEquals are fulfilled by this
111 // node for the actual bytecode sequence.
112 bool CheckArguments(const byte* bytecode, int pc);
113 // Returns whether this node marks the end of a valid sequence (i.e. can be
114 // replaced with an optimized bytecode).
115 bool IsSequence() const;
116 // Returns the length of the sequence in bytes.
117 int SequenceLength() const;
118 // Returns the optimized bytecode for the node or kDummyBytecode if it is not
119 // the end of a valid sequence.
120 int OptimizedBytecode() const;
121 // Returns the child of the current node matching the given bytecode or
122 // nullptr if no such child is found.
123 BytecodeSequenceNode* Find(int bytecode) const;
124 // Returns number of arguments mapped to the current node.
125 // Invoking this method is only allowed on nodes that mark the end of a valid
126 // sequence (i.e. if IsSequence())
127 size_t ArgumentSize() const;
128 // Returns the argument-mapping of the argument at index.
129 // Invoking this method is only allowed on nodes that mark the end of a valid
130 // sequence (i.e. if IsSequence())
131 BytecodeArgumentMapping ArgumentMapping(size_t index) const;
132 // Returns an iterator to begin of ignored arguments.
133 // Invoking this method is only allowed on nodes that mark the end of a valid
134 // sequence (i.e. if IsSequence())
135 ZoneLinkedList<BytecodeArgument>::iterator ArgumentIgnoredBegin() const;
136 // Returns an iterator to end of ignored arguments.
137 // Invoking this method is only allowed on nodes that mark the end of a valid
138 // sequence (i.e. if IsSequence())
139 ZoneLinkedList<BytecodeArgument>::iterator ArgumentIgnoredEnd() const;
140 // Returns whether the current node has ignored argument or not.
141 bool HasIgnoredArguments() const;
142
143 private:
144 // Returns a node in the sequence specified by its index within the sequence.
145 BytecodeSequenceNode& GetNodeByIndexInSequence(int index_in_sequence);
146 Zone* zone() const;
147
148 int bytecode_;
149 int bytecode_replacement_;
150 int index_in_sequence_;
151 int start_offset_;
152 BytecodeSequenceNode* parent_;
153 ZoneUnorderedMap<int, BytecodeSequenceNode*> children_;
154 ZoneVector<BytecodeArgumentMapping>* argument_mapping_;
155 ZoneLinkedList<BytecodeArgumentCheck>* argument_check_;
156 ZoneLinkedList<BytecodeArgument>* argument_ignored_;
157
158 Zone* zone_;
159 };
160
161 // These definitions are here in order to please the linker, which in debug mode
162 // sometimes requires static constants to be defined in .cc files.
163 constexpr int BytecodeSequenceNode::kDummyBytecode;
164
165 class RegExpBytecodePeephole {
166 public:
167 RegExpBytecodePeephole(Zone* zone, size_t buffer_size,
168 const ZoneUnorderedMap<int, int>& jump_edges);
169
170 // Parses bytecode and fills the internal buffer with the potentially
171 // optimized bytecode. Returns true when optimizations were performed, false
172 // otherwise.
173 bool OptimizeBytecode(const byte* bytecode, int length);
174 // Copies the internal bytecode buffer to another buffer. The caller is
175 // responsible for allocating/freeing the memory.
176 void CopyOptimizedBytecode(byte* to_address) const;
177 int Length() const;
178
179 private:
180 // Sets up all sequences that are going to be used.
181 void DefineStandardSequences();
182 // Starts a new bytecode sequence.
183 BytecodeSequenceNode& CreateSequence(int bytecode);
184 // Checks for optimization candidates at pc and emits optimized bytecode to
185 // the internal buffer. Returns the length of replaced bytecodes in bytes.
186 int TryOptimizeSequence(const byte* bytecode, int bytecode_length,
187 int start_pc);
188 // Emits optimized bytecode to the internal buffer. start_pc points to the
189 // start of the sequence in bytecode and last_node is the last
190 // BytecodeSequenceNode of the matching sequence found.
191 void EmitOptimization(int start_pc, const byte* bytecode,
192 const BytecodeSequenceNode& last_node);
193 // Adds a relative jump source fixup at pos.
194 // Jump source fixups are used to find offsets in the new bytecode that
195 // contain jump sources.
196 void AddJumpSourceFixup(int fixup, int pos);
197 // Adds a relative jump destination fixup at pos.
198 // Jump destination fixups are used to find offsets in the new bytecode that
199 // can be jumped to.
200 void AddJumpDestinationFixup(int fixup, int pos);
201 // Sets an absolute jump destination fixup at pos.
202 void SetJumpDestinationFixup(int fixup, int pos);
203 // Prepare internal structures used to fixup jumps.
204 void PrepareJumpStructures(const ZoneUnorderedMap<int, int>& jump_edges);
205 // Updates all jump targets in the new bytecode.
206 void FixJumps();
207 // Update a single jump.
208 void FixJump(int jump_source, int jump_destination);
209 void AddSentinelFixups(int pos);
210 template <typename T>
211 void EmitValue(T value);
212 template <typename T>
213 void OverwriteValue(int offset, T value);
214 void CopyRangeToOutput(const byte* orig_bytecode, int start, int length);
215 void SetRange(byte value, int count);
216 void EmitArgument(int start_pc, const byte* bytecode,
217 BytecodeArgumentMapping arg);
218 int pc() const;
219 Zone* zone() const;
220
221 ZoneVector<byte> optimized_bytecode_buffer_;
222 BytecodeSequenceNode* sequences_;
223 // Jumps used in old bytecode.
224 // Key: Jump source (offset where destination is stored in old bytecode)
225 // Value: Destination
226 ZoneMap<int, int> jump_edges_;
227 // Jumps used in new bytecode.
228 // Key: Jump source (offset where destination is stored in new bytecode)
229 // Value: Destination
230 ZoneMap<int, int> jump_edges_mapped_;
231 // Number of times a jump destination is used within the bytecode.
232 // Key: Jump destination (offset in old bytecode).
233 // Value: Number of times jump destination is used.
234 ZoneMap<int, int> jump_usage_counts_;
235 // Maps offsets in old bytecode to fixups of sources (delta to new bytecode).
236 // Key: Offset in old bytecode from where the fixup is valid.
237 // Value: Delta to map jump source from old bytecode to new bytecode in bytes.
238 ZoneMap<int, int> jump_source_fixups_;
239 // Maps offsets in old bytecode to fixups of destinations (delta to new
240 // bytecode).
241 // Key: Offset in old bytecode from where the fixup is valid.
242 // Value: Delta to map jump destinations from old bytecode to new bytecode in
243 // bytes.
244 ZoneMap<int, int> jump_destination_fixups_;
245
246 Zone* zone_;
247
248 DISALLOW_IMPLICIT_CONSTRUCTORS(RegExpBytecodePeephole);
249 };
250
251 template <typename T>
GetValue(const byte * buffer,int pos)252 T GetValue(const byte* buffer, int pos) {
253 DCHECK(IsAligned(reinterpret_cast<Address>(buffer + pos), alignof(T)));
254 return *reinterpret_cast<const T*>(buffer + pos);
255 }
256
GetArgumentValue(const byte * bytecode,int offset,int length)257 int32_t GetArgumentValue(const byte* bytecode, int offset, int length) {
258 switch (length) {
259 case 1:
260 return GetValue<byte>(bytecode, offset);
261 break;
262 case 2:
263 return GetValue<int16_t>(bytecode, offset);
264 break;
265 case 4:
266 return GetValue<int32_t>(bytecode, offset);
267 break;
268 default:
269 UNREACHABLE();
270 }
271 }
272
BytecodeSequenceNode(int bytecode,Zone * zone)273 BytecodeSequenceNode::BytecodeSequenceNode(int bytecode, Zone* zone)
274 : bytecode_(bytecode),
275 bytecode_replacement_(kDummyBytecode),
276 index_in_sequence_(0),
277 start_offset_(0),
278 parent_(nullptr),
279 children_(ZoneUnorderedMap<int, BytecodeSequenceNode*>(zone)),
280 argument_mapping_(zone->New<ZoneVector<BytecodeArgumentMapping>>(zone)),
281 argument_check_(zone->New<ZoneLinkedList<BytecodeArgumentCheck>>(zone)),
282 argument_ignored_(zone->New<ZoneLinkedList<BytecodeArgument>>(zone)),
283 zone_(zone) {}
284
FollowedBy(int bytecode)285 BytecodeSequenceNode& BytecodeSequenceNode::FollowedBy(int bytecode) {
286 DCHECK(0 <= bytecode && bytecode < kRegExpBytecodeCount);
287
288 if (children_.find(bytecode) == children_.end()) {
289 BytecodeSequenceNode* new_node =
290 zone()->New<BytecodeSequenceNode>(bytecode, zone());
291 // If node is not the first in the sequence, set offsets and parent.
292 if (bytecode_ != kDummyBytecode) {
293 new_node->start_offset_ = start_offset_ + RegExpBytecodeLength(bytecode_);
294 new_node->index_in_sequence_ = index_in_sequence_ + 1;
295 new_node->parent_ = this;
296 }
297 children_[bytecode] = new_node;
298 }
299
300 return *children_[bytecode];
301 }
302
ReplaceWith(int bytecode)303 BytecodeSequenceNode& BytecodeSequenceNode::ReplaceWith(int bytecode) {
304 DCHECK(0 <= bytecode && bytecode < kRegExpBytecodeCount);
305
306 bytecode_replacement_ = bytecode;
307
308 return *this;
309 }
310
MapArgument(int bytecode_index_in_sequence,int argument_offset,int argument_byte_length,int new_argument_byte_length)311 BytecodeSequenceNode& BytecodeSequenceNode::MapArgument(
312 int bytecode_index_in_sequence, int argument_offset,
313 int argument_byte_length, int new_argument_byte_length) {
314 DCHECK(IsSequence());
315 DCHECK_LE(bytecode_index_in_sequence, index_in_sequence_);
316
317 BytecodeSequenceNode& ref_node =
318 GetNodeByIndexInSequence(bytecode_index_in_sequence);
319 DCHECK_LT(argument_offset, RegExpBytecodeLength(ref_node.bytecode_));
320
321 int absolute_offset = ref_node.start_offset_ + argument_offset;
322 if (new_argument_byte_length == 0) {
323 new_argument_byte_length = argument_byte_length;
324 }
325
326 argument_mapping_->push_back(BytecodeArgumentMapping{
327 absolute_offset, argument_byte_length, new_argument_byte_length});
328
329 return *this;
330 }
331
IfArgumentEqualsOffset(int argument_offset,int argument_byte_length,int check_byte_offset)332 BytecodeSequenceNode& BytecodeSequenceNode::IfArgumentEqualsOffset(
333 int argument_offset, int argument_byte_length, int check_byte_offset) {
334 DCHECK_LT(argument_offset, RegExpBytecodeLength(bytecode_));
335 DCHECK(argument_byte_length == 1 || argument_byte_length == 2 ||
336 argument_byte_length == 4);
337
338 int absolute_offset = start_offset_ + argument_offset;
339
340 argument_check_->push_back(BytecodeArgumentCheck{
341 absolute_offset, argument_byte_length, check_byte_offset});
342
343 return *this;
344 }
345
IfArgumentEqualsValueAtOffset(int argument_offset,int argument_byte_length,int other_bytecode_index_in_sequence,int other_argument_offset,int other_argument_byte_length)346 BytecodeSequenceNode& BytecodeSequenceNode::IfArgumentEqualsValueAtOffset(
347 int argument_offset, int argument_byte_length,
348 int other_bytecode_index_in_sequence, int other_argument_offset,
349 int other_argument_byte_length) {
350 DCHECK_LT(argument_offset, RegExpBytecodeLength(bytecode_));
351 DCHECK_LE(other_bytecode_index_in_sequence, index_in_sequence_);
352 DCHECK_EQ(argument_byte_length, other_argument_byte_length);
353
354 BytecodeSequenceNode& ref_node =
355 GetNodeByIndexInSequence(other_bytecode_index_in_sequence);
356 DCHECK_LT(other_argument_offset, RegExpBytecodeLength(ref_node.bytecode_));
357
358 int absolute_offset = start_offset_ + argument_offset;
359 int other_absolute_offset = ref_node.start_offset_ + other_argument_offset;
360
361 argument_check_->push_back(
362 BytecodeArgumentCheck{absolute_offset, argument_byte_length,
363 other_absolute_offset, other_argument_byte_length});
364
365 return *this;
366 }
367
IgnoreArgument(int bytecode_index_in_sequence,int argument_offset,int argument_byte_length)368 BytecodeSequenceNode& BytecodeSequenceNode::IgnoreArgument(
369 int bytecode_index_in_sequence, int argument_offset,
370 int argument_byte_length) {
371 DCHECK(IsSequence());
372 DCHECK_LE(bytecode_index_in_sequence, index_in_sequence_);
373
374 BytecodeSequenceNode& ref_node =
375 GetNodeByIndexInSequence(bytecode_index_in_sequence);
376 DCHECK_LT(argument_offset, RegExpBytecodeLength(ref_node.bytecode_));
377
378 int absolute_offset = ref_node.start_offset_ + argument_offset;
379
380 argument_ignored_->push_back(
381 BytecodeArgument{absolute_offset, argument_byte_length});
382
383 return *this;
384 }
385
CheckArguments(const byte * bytecode,int pc)386 bool BytecodeSequenceNode::CheckArguments(const byte* bytecode, int pc) {
387 bool is_valid = true;
388 for (auto check_iter = argument_check_->begin();
389 check_iter != argument_check_->end() && is_valid; check_iter++) {
390 auto value =
391 GetArgumentValue(bytecode, pc + check_iter->offset, check_iter->length);
392 if (check_iter->type == BytecodeArgumentCheck::kCheckAddress) {
393 is_valid &= value == pc + check_iter->check_offset;
394 } else if (check_iter->type == BytecodeArgumentCheck::kCheckValue) {
395 auto other_value = GetArgumentValue(
396 bytecode, pc + check_iter->check_offset, check_iter->check_length);
397 is_valid &= value == other_value;
398 } else {
399 UNREACHABLE();
400 }
401 }
402 return is_valid;
403 }
404
IsSequence() const405 bool BytecodeSequenceNode::IsSequence() const {
406 return bytecode_replacement_ != kDummyBytecode;
407 }
408
SequenceLength() const409 int BytecodeSequenceNode::SequenceLength() const {
410 return start_offset_ + RegExpBytecodeLength(bytecode_);
411 }
412
OptimizedBytecode() const413 int BytecodeSequenceNode::OptimizedBytecode() const {
414 return bytecode_replacement_;
415 }
416
Find(int bytecode) const417 BytecodeSequenceNode* BytecodeSequenceNode::Find(int bytecode) const {
418 auto found = children_.find(bytecode);
419 if (found == children_.end()) return nullptr;
420 return found->second;
421 }
422
ArgumentSize() const423 size_t BytecodeSequenceNode::ArgumentSize() const {
424 DCHECK(IsSequence());
425 return argument_mapping_->size();
426 }
427
ArgumentMapping(size_t index) const428 BytecodeArgumentMapping BytecodeSequenceNode::ArgumentMapping(
429 size_t index) const {
430 DCHECK(IsSequence());
431 DCHECK(argument_mapping_ != nullptr);
432 DCHECK_LT(index, argument_mapping_->size());
433
434 return argument_mapping_->at(index);
435 }
436
437 ZoneLinkedList<BytecodeArgument>::iterator
ArgumentIgnoredBegin() const438 BytecodeSequenceNode::ArgumentIgnoredBegin() const {
439 DCHECK(IsSequence());
440 DCHECK(argument_ignored_ != nullptr);
441 return argument_ignored_->begin();
442 }
443
444 ZoneLinkedList<BytecodeArgument>::iterator
ArgumentIgnoredEnd() const445 BytecodeSequenceNode::ArgumentIgnoredEnd() const {
446 DCHECK(IsSequence());
447 DCHECK(argument_ignored_ != nullptr);
448 return argument_ignored_->end();
449 }
450
HasIgnoredArguments() const451 bool BytecodeSequenceNode::HasIgnoredArguments() const {
452 return argument_ignored_ != nullptr;
453 }
454
GetNodeByIndexInSequence(int index_in_sequence)455 BytecodeSequenceNode& BytecodeSequenceNode::GetNodeByIndexInSequence(
456 int index_in_sequence) {
457 DCHECK_LE(index_in_sequence, index_in_sequence_);
458
459 if (index_in_sequence < index_in_sequence_) {
460 DCHECK(parent_ != nullptr);
461 return parent_->GetNodeByIndexInSequence(index_in_sequence);
462 } else {
463 return *this;
464 }
465 }
466
zone() const467 Zone* BytecodeSequenceNode::zone() const { return zone_; }
468
RegExpBytecodePeephole(Zone * zone,size_t buffer_size,const ZoneUnorderedMap<int,int> & jump_edges)469 RegExpBytecodePeephole::RegExpBytecodePeephole(
470 Zone* zone, size_t buffer_size,
471 const ZoneUnorderedMap<int, int>& jump_edges)
472 : optimized_bytecode_buffer_(zone),
473 sequences_(zone->New<BytecodeSequenceNode>(
474 BytecodeSequenceNode::kDummyBytecode, zone)),
475 jump_edges_(zone),
476 jump_edges_mapped_(zone),
477 jump_usage_counts_(zone),
478 jump_source_fixups_(zone),
479 jump_destination_fixups_(zone),
480 zone_(zone) {
481 optimized_bytecode_buffer_.reserve(buffer_size);
482 PrepareJumpStructures(jump_edges);
483 DefineStandardSequences();
484 // Sentinel fixups at beginning of bytecode (position -1) so we don't have to
485 // check for end of iterator inside the fixup loop.
486 // In general fixups are deltas of original offsets of jump
487 // sources/destinations (in the old bytecode) to find them in the new
488 // bytecode. All jump targets are fixed after the new bytecode is fully
489 // emitted in the internal buffer.
490 AddSentinelFixups(-1);
491 // Sentinel fixups at end of (old) bytecode so we don't have to check for
492 // end of iterator inside the fixup loop.
493 DCHECK_LE(buffer_size, std::numeric_limits<int>::max());
494 AddSentinelFixups(static_cast<int>(buffer_size));
495 }
496
DefineStandardSequences()497 void RegExpBytecodePeephole::DefineStandardSequences() {
498 // Commonly used sequences can be found by creating regexp bytecode traces
499 // (--trace-regexp-bytecodes) and using v8/tools/regexp-sequences.py.
500 CreateSequence(BC_LOAD_CURRENT_CHAR)
501 .FollowedBy(BC_CHECK_BIT_IN_TABLE)
502 .FollowedBy(BC_ADVANCE_CP_AND_GOTO)
503 // Sequence is only valid if the jump target of ADVANCE_CP_AND_GOTO is the
504 // first bytecode in this sequence.
505 .IfArgumentEqualsOffset(4, 4, 0)
506 .ReplaceWith(BC_SKIP_UNTIL_BIT_IN_TABLE)
507 .MapArgument(0, 1, 3) // load offset
508 .MapArgument(2, 1, 3, 4) // advance by
509 .MapArgument(1, 8, 16) // bit table
510 .MapArgument(1, 4, 4) // goto when match
511 .MapArgument(0, 4, 4) // goto on failure
512 .IgnoreArgument(2, 4, 4); // loop jump
513
514 CreateSequence(BC_CHECK_CURRENT_POSITION)
515 .FollowedBy(BC_LOAD_CURRENT_CHAR_UNCHECKED)
516 .FollowedBy(BC_CHECK_CHAR)
517 .FollowedBy(BC_ADVANCE_CP_AND_GOTO)
518 // Sequence is only valid if the jump target of ADVANCE_CP_AND_GOTO is the
519 // first bytecode in this sequence.
520 .IfArgumentEqualsOffset(4, 4, 0)
521 .ReplaceWith(BC_SKIP_UNTIL_CHAR_POS_CHECKED)
522 .MapArgument(1, 1, 3) // load offset
523 .MapArgument(3, 1, 3, 2) // advance_by
524 .MapArgument(2, 1, 3, 2) // c
525 .MapArgument(0, 1, 3, 4) // eats at least
526 .MapArgument(2, 4, 4) // goto when match
527 .MapArgument(0, 4, 4) // goto on failure
528 .IgnoreArgument(3, 4, 4); // loop jump
529
530 CreateSequence(BC_CHECK_CURRENT_POSITION)
531 .FollowedBy(BC_LOAD_CURRENT_CHAR_UNCHECKED)
532 .FollowedBy(BC_AND_CHECK_CHAR)
533 .FollowedBy(BC_ADVANCE_CP_AND_GOTO)
534 // Sequence is only valid if the jump target of ADVANCE_CP_AND_GOTO is the
535 // first bytecode in this sequence.
536 .IfArgumentEqualsOffset(4, 4, 0)
537 .ReplaceWith(BC_SKIP_UNTIL_CHAR_AND)
538 .MapArgument(1, 1, 3) // load offset
539 .MapArgument(3, 1, 3, 2) // advance_by
540 .MapArgument(2, 1, 3, 2) // c
541 .MapArgument(2, 4, 4) // mask
542 .MapArgument(0, 1, 3, 4) // eats at least
543 .MapArgument(2, 8, 4) // goto when match
544 .MapArgument(0, 4, 4) // goto on failure
545 .IgnoreArgument(3, 4, 4); // loop jump
546
547 // TODO(pthier): It might make sense for short sequences like this one to only
548 // optimize them if the resulting optimization is not longer than the current
549 // one. This could be the case if there are jumps inside the sequence and we
550 // have to replicate parts of the sequence. A method to mark such sequences
551 // might be useful.
552 CreateSequence(BC_LOAD_CURRENT_CHAR)
553 .FollowedBy(BC_CHECK_CHAR)
554 .FollowedBy(BC_ADVANCE_CP_AND_GOTO)
555 // Sequence is only valid if the jump target of ADVANCE_CP_AND_GOTO is the
556 // first bytecode in this sequence.
557 .IfArgumentEqualsOffset(4, 4, 0)
558 .ReplaceWith(BC_SKIP_UNTIL_CHAR)
559 .MapArgument(0, 1, 3) // load offset
560 .MapArgument(2, 1, 3, 2) // advance by
561 .MapArgument(1, 1, 3, 2) // character
562 .MapArgument(1, 4, 4) // goto when match
563 .MapArgument(0, 4, 4) // goto on failure
564 .IgnoreArgument(2, 4, 4); // loop jump
565
566 CreateSequence(BC_LOAD_CURRENT_CHAR)
567 .FollowedBy(BC_CHECK_CHAR)
568 .FollowedBy(BC_CHECK_CHAR)
569 // Sequence is only valid if the jump targets of both CHECK_CHAR bytecodes
570 // are equal.
571 .IfArgumentEqualsValueAtOffset(4, 4, 1, 4, 4)
572 .FollowedBy(BC_ADVANCE_CP_AND_GOTO)
573 // Sequence is only valid if the jump target of ADVANCE_CP_AND_GOTO is the
574 // first bytecode in this sequence.
575 .IfArgumentEqualsOffset(4, 4, 0)
576 .ReplaceWith(BC_SKIP_UNTIL_CHAR_OR_CHAR)
577 .MapArgument(0, 1, 3) // load offset
578 .MapArgument(3, 1, 3, 4) // advance by
579 .MapArgument(1, 1, 3, 2) // character 1
580 .MapArgument(2, 1, 3, 2) // character 2
581 .MapArgument(1, 4, 4) // goto when match
582 .MapArgument(0, 4, 4) // goto on failure
583 .IgnoreArgument(2, 4, 4) // goto when match 2
584 .IgnoreArgument(3, 4, 4); // loop jump
585
586 CreateSequence(BC_LOAD_CURRENT_CHAR)
587 .FollowedBy(BC_CHECK_GT)
588 // Sequence is only valid if the jump target of CHECK_GT is the first
589 // bytecode AFTER the whole sequence.
590 .IfArgumentEqualsOffset(4, 4, 56)
591 .FollowedBy(BC_CHECK_BIT_IN_TABLE)
592 // Sequence is only valid if the jump target of CHECK_BIT_IN_TABLE is
593 // the ADVANCE_CP_AND_GOTO bytecode at the end of the sequence.
594 .IfArgumentEqualsOffset(4, 4, 48)
595 .FollowedBy(BC_GOTO)
596 // Sequence is only valid if the jump target of GOTO is the same as the
597 // jump target of CHECK_GT (i.e. both jump to the first bytecode AFTER the
598 // whole sequence.
599 .IfArgumentEqualsValueAtOffset(4, 4, 1, 4, 4)
600 .FollowedBy(BC_ADVANCE_CP_AND_GOTO)
601 // Sequence is only valid if the jump target of ADVANCE_CP_AND_GOTO is the
602 // first bytecode in this sequence.
603 .IfArgumentEqualsOffset(4, 4, 0)
604 .ReplaceWith(BC_SKIP_UNTIL_GT_OR_NOT_BIT_IN_TABLE)
605 .MapArgument(0, 1, 3) // load offset
606 .MapArgument(4, 1, 3, 2) // advance by
607 .MapArgument(1, 1, 3, 2) // character
608 .MapArgument(2, 8, 16) // bit table
609 .MapArgument(1, 4, 4) // goto when match
610 .MapArgument(0, 4, 4) // goto on failure
611 .IgnoreArgument(2, 4, 4) // indirect loop jump
612 .IgnoreArgument(3, 4, 4) // jump out of loop
613 .IgnoreArgument(4, 4, 4); // loop jump
614 }
615
OptimizeBytecode(const byte * bytecode,int length)616 bool RegExpBytecodePeephole::OptimizeBytecode(const byte* bytecode,
617 int length) {
618 int old_pc = 0;
619 bool did_optimize = false;
620
621 while (old_pc < length) {
622 int replaced_len = TryOptimizeSequence(bytecode, length, old_pc);
623 if (replaced_len > 0) {
624 old_pc += replaced_len;
625 did_optimize = true;
626 } else {
627 int bc = bytecode[old_pc];
628 int bc_len = RegExpBytecodeLength(bc);
629 CopyRangeToOutput(bytecode, old_pc, bc_len);
630 old_pc += bc_len;
631 }
632 }
633
634 if (did_optimize) {
635 FixJumps();
636 }
637
638 return did_optimize;
639 }
640
CopyOptimizedBytecode(byte * to_address) const641 void RegExpBytecodePeephole::CopyOptimizedBytecode(byte* to_address) const {
642 MemCopy(to_address, &(*optimized_bytecode_buffer_.begin()), Length());
643 }
644
Length() const645 int RegExpBytecodePeephole::Length() const { return pc(); }
646
CreateSequence(int bytecode)647 BytecodeSequenceNode& RegExpBytecodePeephole::CreateSequence(int bytecode) {
648 DCHECK(sequences_ != nullptr);
649 DCHECK(0 <= bytecode && bytecode < kRegExpBytecodeCount);
650
651 return sequences_->FollowedBy(bytecode);
652 }
653
TryOptimizeSequence(const byte * bytecode,int bytecode_length,int start_pc)654 int RegExpBytecodePeephole::TryOptimizeSequence(const byte* bytecode,
655 int bytecode_length,
656 int start_pc) {
657 BytecodeSequenceNode* seq_node = sequences_;
658 BytecodeSequenceNode* valid_seq_end = nullptr;
659
660 int current_pc = start_pc;
661
662 // Check for the longest valid sequence matching any of the pre-defined
663 // sequences in the Trie data structure.
664 while (current_pc < bytecode_length) {
665 seq_node = seq_node->Find(bytecode[current_pc]);
666 if (seq_node == nullptr) break;
667 if (!seq_node->CheckArguments(bytecode, start_pc)) break;
668
669 if (seq_node->IsSequence()) valid_seq_end = seq_node;
670 current_pc += RegExpBytecodeLength(bytecode[current_pc]);
671 }
672
673 if (valid_seq_end) {
674 EmitOptimization(start_pc, bytecode, *valid_seq_end);
675 return valid_seq_end->SequenceLength();
676 }
677
678 return 0;
679 }
680
EmitOptimization(int start_pc,const byte * bytecode,const BytecodeSequenceNode & last_node)681 void RegExpBytecodePeephole::EmitOptimization(
682 int start_pc, const byte* bytecode, const BytecodeSequenceNode& last_node) {
683 #ifdef DEBUG
684 int optimized_start_pc = pc();
685 #endif
686 // Jump sources that are mapped or marked as unused will be deleted at the end
687 // of this method. We don't delete them immediately as we might need the
688 // information when we have to preserve bytecodes at the end.
689 // TODO(pthier): Replace with a stack-allocated data structure.
690 ZoneLinkedList<int> delete_jumps = ZoneLinkedList<int>(zone());
691
692 uint32_t bc = last_node.OptimizedBytecode();
693 EmitValue(bc);
694
695 for (size_t arg = 0; arg < last_node.ArgumentSize(); arg++) {
696 BytecodeArgumentMapping arg_map = last_node.ArgumentMapping(arg);
697 int arg_pos = start_pc + arg_map.offset;
698 // If we map any jump source we mark the old source for deletion and insert
699 // a new jump.
700 auto jump_edge_iter = jump_edges_.find(arg_pos);
701 if (jump_edge_iter != jump_edges_.end()) {
702 int jump_source = jump_edge_iter->first;
703 int jump_destination = jump_edge_iter->second;
704 // Add new jump edge add current position.
705 jump_edges_mapped_.emplace(Length(), jump_destination);
706 // Mark old jump edge for deletion.
707 delete_jumps.push_back(jump_source);
708 // Decrement usage count of jump destination.
709 auto jump_count_iter = jump_usage_counts_.find(jump_destination);
710 DCHECK(jump_count_iter != jump_usage_counts_.end());
711 int& usage_count = jump_count_iter->second;
712 --usage_count;
713 }
714 // TODO(pthier): DCHECK that mapped arguments are never sources of jumps
715 // to destinations inside the sequence.
716 EmitArgument(start_pc, bytecode, arg_map);
717 }
718 DCHECK_EQ(pc(), optimized_start_pc +
719 RegExpBytecodeLength(last_node.OptimizedBytecode()));
720
721 // Remove jumps from arguments we ignore.
722 if (last_node.HasIgnoredArguments()) {
723 for (auto ignored_arg = last_node.ArgumentIgnoredBegin();
724 ignored_arg != last_node.ArgumentIgnoredEnd(); ignored_arg++) {
725 auto jump_edge_iter = jump_edges_.find(start_pc + ignored_arg->offset);
726 if (jump_edge_iter != jump_edges_.end()) {
727 int jump_source = jump_edge_iter->first;
728 int jump_destination = jump_edge_iter->second;
729 // Mark old jump edge for deletion.
730 delete_jumps.push_back(jump_source);
731 // Decrement usage count of jump destination.
732 auto jump_count_iter = jump_usage_counts_.find(jump_destination);
733 DCHECK(jump_count_iter != jump_usage_counts_.end());
734 int& usage_count = jump_count_iter->second;
735 --usage_count;
736 }
737 }
738 }
739
740 int fixup_length = RegExpBytecodeLength(bc) - last_node.SequenceLength();
741
742 // Check if there are any jumps inside the old sequence.
743 // If so we have to keep the bytecodes that are jumped to around.
744 auto jump_destination_candidate = jump_usage_counts_.upper_bound(start_pc);
745 int jump_candidate_destination = jump_destination_candidate->first;
746 int jump_candidate_count = jump_destination_candidate->second;
747 // Jump destinations only jumped to from inside the sequence will be ignored.
748 while (jump_destination_candidate != jump_usage_counts_.end() &&
749 jump_candidate_count == 0) {
750 ++jump_destination_candidate;
751 jump_candidate_destination = jump_destination_candidate->first;
752 jump_candidate_count = jump_destination_candidate->second;
753 }
754
755 int preserve_from = start_pc + last_node.SequenceLength();
756 if (jump_destination_candidate != jump_usage_counts_.end() &&
757 jump_candidate_destination < start_pc + last_node.SequenceLength()) {
758 preserve_from = jump_candidate_destination;
759 // Check if any jump in the sequence we are preserving has a jump
760 // destination inside the optimized sequence before the current position we
761 // want to preserve. If so we have to preserve all bytecodes starting at
762 // this jump destination.
763 for (auto jump_iter = jump_edges_.lower_bound(preserve_from);
764 jump_iter != jump_edges_.end() &&
765 jump_iter->first /* jump source */ <
766 start_pc + last_node.SequenceLength();
767 ++jump_iter) {
768 int jump_destination = jump_iter->second;
769 if (jump_destination > start_pc && jump_destination < preserve_from) {
770 preserve_from = jump_destination;
771 }
772 }
773
774 // We preserve everything to the end of the sequence. This is conservative
775 // since it would be enough to preserve all bytecudes up to an unconditional
776 // jump.
777 int preserve_length = start_pc + last_node.SequenceLength() - preserve_from;
778 fixup_length += preserve_length;
779 // Jumps after the start of the preserved sequence need fixup.
780 AddJumpSourceFixup(fixup_length,
781 start_pc + last_node.SequenceLength() - preserve_length);
782 // All jump targets after the start of the optimized sequence need to be
783 // fixed relative to the length of the optimized sequence including
784 // bytecodes we preserved.
785 AddJumpDestinationFixup(fixup_length, start_pc + 1);
786 // Jumps to the sequence we preserved need absolute fixup as they could
787 // occur before or after the sequence.
788 SetJumpDestinationFixup(pc() - preserve_from, preserve_from);
789 CopyRangeToOutput(bytecode, preserve_from, preserve_length);
790 } else {
791 AddJumpDestinationFixup(fixup_length, start_pc + 1);
792 // Jumps after the end of the old sequence need fixup.
793 AddJumpSourceFixup(fixup_length, start_pc + last_node.SequenceLength());
794 }
795
796 // Delete jumps we definitely don't need anymore
797 for (int del : delete_jumps) {
798 if (del < preserve_from) {
799 jump_edges_.erase(del);
800 }
801 }
802 }
803
AddJumpSourceFixup(int fixup,int pos)804 void RegExpBytecodePeephole::AddJumpSourceFixup(int fixup, int pos) {
805 auto previous_fixup = jump_source_fixups_.lower_bound(pos);
806 DCHECK(previous_fixup != jump_source_fixups_.end());
807 DCHECK(previous_fixup != jump_source_fixups_.begin());
808
809 int previous_fixup_value = (--previous_fixup)->second;
810 jump_source_fixups_[pos] = previous_fixup_value + fixup;
811 }
812
AddJumpDestinationFixup(int fixup,int pos)813 void RegExpBytecodePeephole::AddJumpDestinationFixup(int fixup, int pos) {
814 auto previous_fixup = jump_destination_fixups_.lower_bound(pos);
815 DCHECK(previous_fixup != jump_destination_fixups_.end());
816 DCHECK(previous_fixup != jump_destination_fixups_.begin());
817
818 int previous_fixup_value = (--previous_fixup)->second;
819 jump_destination_fixups_[pos] = previous_fixup_value + fixup;
820 }
821
SetJumpDestinationFixup(int fixup,int pos)822 void RegExpBytecodePeephole::SetJumpDestinationFixup(int fixup, int pos) {
823 auto previous_fixup = jump_destination_fixups_.lower_bound(pos);
824 DCHECK(previous_fixup != jump_destination_fixups_.end());
825 DCHECK(previous_fixup != jump_destination_fixups_.begin());
826
827 int previous_fixup_value = (--previous_fixup)->second;
828 jump_destination_fixups_.emplace(pos, fixup);
829 jump_destination_fixups_.emplace(pos + 1, previous_fixup_value);
830 }
831
PrepareJumpStructures(const ZoneUnorderedMap<int,int> & jump_edges)832 void RegExpBytecodePeephole::PrepareJumpStructures(
833 const ZoneUnorderedMap<int, int>& jump_edges) {
834 for (auto jump_edge : jump_edges) {
835 int jump_source = jump_edge.first;
836 int jump_destination = jump_edge.second;
837
838 jump_edges_.emplace(jump_source, jump_destination);
839 jump_usage_counts_[jump_destination]++;
840 }
841 }
842
FixJumps()843 void RegExpBytecodePeephole::FixJumps() {
844 int position_fixup = 0;
845 // Next position where fixup changes.
846 auto next_source_fixup = jump_source_fixups_.lower_bound(0);
847 int next_source_fixup_offset = next_source_fixup->first;
848 int next_source_fixup_value = next_source_fixup->second;
849
850 for (auto jump_edge : jump_edges_) {
851 int jump_source = jump_edge.first;
852 int jump_destination = jump_edge.second;
853 while (jump_source >= next_source_fixup_offset) {
854 position_fixup = next_source_fixup_value;
855 ++next_source_fixup;
856 next_source_fixup_offset = next_source_fixup->first;
857 next_source_fixup_value = next_source_fixup->second;
858 }
859 jump_source += position_fixup;
860
861 FixJump(jump_source, jump_destination);
862 }
863
864 // Mapped jump edges don't need source fixups, as the position already is an
865 // offset in the new bytecode.
866 for (auto jump_edge : jump_edges_mapped_) {
867 int jump_source = jump_edge.first;
868 int jump_destination = jump_edge.second;
869
870 FixJump(jump_source, jump_destination);
871 }
872 }
873
FixJump(int jump_source,int jump_destination)874 void RegExpBytecodePeephole::FixJump(int jump_source, int jump_destination) {
875 int fixed_jump_destination =
876 jump_destination +
877 (--jump_destination_fixups_.upper_bound(jump_destination))->second;
878 DCHECK_LT(fixed_jump_destination, Length());
879 #ifdef DEBUG
880 // TODO(pthier): This check could be better if we track the bytecodes
881 // actually used and check if we jump to one of them.
882 byte jump_bc = optimized_bytecode_buffer_[fixed_jump_destination];
883 DCHECK_GT(jump_bc, 0);
884 DCHECK_LT(jump_bc, kRegExpBytecodeCount);
885 #endif
886
887 if (jump_destination != fixed_jump_destination) {
888 OverwriteValue<uint32_t>(jump_source, fixed_jump_destination);
889 }
890 }
891
AddSentinelFixups(int pos)892 void RegExpBytecodePeephole::AddSentinelFixups(int pos) {
893 jump_source_fixups_.emplace(pos, 0);
894 jump_destination_fixups_.emplace(pos, 0);
895 }
896
897 template <typename T>
EmitValue(T value)898 void RegExpBytecodePeephole::EmitValue(T value) {
899 DCHECK(optimized_bytecode_buffer_.begin() + pc() ==
900 optimized_bytecode_buffer_.end());
901 byte* value_byte_iter = reinterpret_cast<byte*>(&value);
902 optimized_bytecode_buffer_.insert(optimized_bytecode_buffer_.end(),
903 value_byte_iter,
904 value_byte_iter + sizeof(T));
905 }
906
907 template <typename T>
OverwriteValue(int offset,T value)908 void RegExpBytecodePeephole::OverwriteValue(int offset, T value) {
909 byte* value_byte_iter = reinterpret_cast<byte*>(&value);
910 byte* value_byte_iter_end = value_byte_iter + sizeof(T);
911 while (value_byte_iter < value_byte_iter_end) {
912 optimized_bytecode_buffer_[offset++] = *value_byte_iter++;
913 }
914 }
915
CopyRangeToOutput(const byte * orig_bytecode,int start,int length)916 void RegExpBytecodePeephole::CopyRangeToOutput(const byte* orig_bytecode,
917 int start, int length) {
918 DCHECK(optimized_bytecode_buffer_.begin() + pc() ==
919 optimized_bytecode_buffer_.end());
920 optimized_bytecode_buffer_.insert(optimized_bytecode_buffer_.end(),
921 orig_bytecode + start,
922 orig_bytecode + start + length);
923 }
924
SetRange(byte value,int count)925 void RegExpBytecodePeephole::SetRange(byte value, int count) {
926 DCHECK(optimized_bytecode_buffer_.begin() + pc() ==
927 optimized_bytecode_buffer_.end());
928 optimized_bytecode_buffer_.insert(optimized_bytecode_buffer_.end(), count,
929 value);
930 }
931
EmitArgument(int start_pc,const byte * bytecode,BytecodeArgumentMapping arg)932 void RegExpBytecodePeephole::EmitArgument(int start_pc, const byte* bytecode,
933 BytecodeArgumentMapping arg) {
934 int arg_pos = start_pc + arg.offset;
935 switch (arg.length) {
936 case 1:
937 DCHECK_EQ(arg.new_length, arg.length);
938 EmitValue(GetValue<byte>(bytecode, arg_pos));
939 break;
940 case 2:
941 DCHECK_EQ(arg.new_length, arg.length);
942 EmitValue(GetValue<uint16_t>(bytecode, arg_pos));
943 break;
944 case 3: {
945 // Length 3 only occurs in 'packed' arguments where the lowermost byte is
946 // the current bytecode, and the remaining 3 bytes are the packed value.
947 //
948 // We load 4 bytes from position - 1 and shift out the bytecode.
949 #ifdef V8_TARGET_BIG_ENDIAN
950 UNIMPLEMENTED();
951 int32_t val = 0;
952 #else
953 int32_t val = GetValue<int32_t>(bytecode, arg_pos - 1) >> kBitsPerByte;
954 #endif // V8_TARGET_BIG_ENDIAN
955
956 switch (arg.new_length) {
957 case 2:
958 EmitValue<uint16_t>(val);
959 break;
960 case 3: {
961 // Pack with previously emitted value.
962 auto prev_val =
963 GetValue<int32_t>(&(*optimized_bytecode_buffer_.begin()),
964 Length() - sizeof(uint32_t));
965 #ifdef V8_TARGET_BIG_ENDIAN
966 UNIMPLEMENTED();
967 USE(prev_val);
968 #else
969 DCHECK_EQ(prev_val & 0xFFFFFF00, 0);
970 OverwriteValue<uint32_t>(
971 pc() - sizeof(uint32_t),
972 (static_cast<uint32_t>(val) << 8) | (prev_val & 0xFF));
973 #endif // V8_TARGET_BIG_ENDIAN
974 break;
975 }
976 case 4:
977 EmitValue<uint32_t>(val);
978 break;
979 }
980 break;
981 }
982 case 4:
983 DCHECK_EQ(arg.new_length, arg.length);
984 EmitValue(GetValue<uint32_t>(bytecode, arg_pos));
985 break;
986 case 8:
987 DCHECK_EQ(arg.new_length, arg.length);
988 EmitValue(GetValue<uint64_t>(bytecode, arg_pos));
989 break;
990 default:
991 CopyRangeToOutput(bytecode, arg_pos,
992 std::min(arg.length, arg.new_length));
993 if (arg.length < arg.new_length) {
994 SetRange(0x00, arg.new_length - arg.length);
995 }
996 break;
997 }
998 }
999
pc() const1000 int RegExpBytecodePeephole::pc() const {
1001 DCHECK_LE(optimized_bytecode_buffer_.size(), std::numeric_limits<int>::max());
1002 return static_cast<int>(optimized_bytecode_buffer_.size());
1003 }
1004
zone() const1005 Zone* RegExpBytecodePeephole::zone() const { return zone_; }
1006
1007 } // namespace
1008
1009 // static
OptimizeBytecode(Isolate * isolate,Zone * zone,Handle<String> source,const byte * bytecode,int length,const ZoneUnorderedMap<int,int> & jump_edges)1010 Handle<ByteArray> RegExpBytecodePeepholeOptimization::OptimizeBytecode(
1011 Isolate* isolate, Zone* zone, Handle<String> source, const byte* bytecode,
1012 int length, const ZoneUnorderedMap<int, int>& jump_edges) {
1013 RegExpBytecodePeephole peephole(zone, length, jump_edges);
1014 bool did_optimize = peephole.OptimizeBytecode(bytecode, length);
1015 Handle<ByteArray> array = isolate->factory()->NewByteArray(peephole.Length());
1016 peephole.CopyOptimizedBytecode(array->GetDataStartAddress());
1017
1018 if (did_optimize && FLAG_trace_regexp_peephole_optimization) {
1019 PrintF("Original Bytecode:\n");
1020 RegExpBytecodeDisassemble(bytecode, length, source->ToCString().get());
1021 PrintF("Optimized Bytecode:\n");
1022 RegExpBytecodeDisassemble(array->GetDataStartAddress(), peephole.Length(),
1023 source->ToCString().get());
1024 }
1025
1026 return array;
1027 }
1028
1029 } // namespace internal
1030 } // namespace v8
1031