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