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