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