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