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