blob: f61df5a72a857e9f6ff380b3c1de8f8021a251a8 [file] [log] [blame]
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/regexp/regexp-bytecode-peephole.h"
#include "src/execution/isolate.h"
#include "src/flags/flags.h"
#include "src/objects/fixed-array.h"
#include "src/objects/objects-inl.h"
#include "src/regexp/regexp-bytecodes.h"
#include "src/utils/memcopy.h"
#include "src/utils/utils.h"
#include "src/zone/zone-containers.h"
#include "src/zone/zone.h"
namespace v8 {
namespace internal {
namespace {
struct BytecodeArgument {
int offset;
int length;
BytecodeArgument(int offset, int length) : offset(offset), length(length) {}
};
struct BytecodeArgumentMapping : BytecodeArgument {
int new_length;
BytecodeArgumentMapping(int offset, int length, int new_length)
: BytecodeArgument(offset, length), new_length(new_length) {}
};
struct BytecodeArgumentCheck : BytecodeArgument {
enum CheckType { kCheckAddress = 0, kCheckValue };
CheckType type;
int check_offset;
int check_length;
BytecodeArgumentCheck(int offset, int length, int check_offset)
: BytecodeArgument(offset, length),
type(kCheckAddress),
check_offset(check_offset) {}
BytecodeArgumentCheck(int offset, int length, int check_offset,
int check_length)
: BytecodeArgument(offset, length),
type(kCheckValue),
check_offset(check_offset),
check_length(check_length) {}
};
// Trie-Node for storing bytecode sequences we want to optimize.
class BytecodeSequenceNode {
public:
// Dummy bytecode used when we need to store/return a bytecode but it's not a
// valid bytecode in the current context.
static constexpr int kDummyBytecode = -1;
BytecodeSequenceNode(int bytecode, Zone* zone);
// Adds a new node as child of the current node if it isn't a child already.
BytecodeSequenceNode& FollowedBy(int bytecode);
// Marks the end of a sequence and sets optimized bytecode to replace all
// bytecodes of the sequence with.
BytecodeSequenceNode& ReplaceWith(int bytecode);
// Maps arguments of bytecodes in the sequence to the optimized bytecode.
// Order of invocation determines order of arguments in the optimized
// bytecode.
// Invoking this method is only allowed on nodes that mark the end of a valid
// sequence (i.e. after ReplaceWith()).
// bytecode_index_in_sequence: Zero-based index of the referred bytecode
// within the sequence (e.g. the bytecode passed to CreateSequence() has
// index 0).
// argument_offset: Zero-based offset to the argument within the bytecode
// (e.g. the first argument that's not packed with the bytecode has offset 4).
// argument_byte_length: Length of the argument.
// new_argument_byte_length: Length of the argument in the new bytecode
// (= argument_byte_length if omitted).
BytecodeSequenceNode& MapArgument(int bytecode_index_in_sequence,
int argument_offset,
int argument_byte_length,
int new_argument_byte_length = 0);
// Adds a check to the sequence node making it only a valid sequence when the
// argument of the current bytecode at the specified offset matches the offset
// to check against.
// argument_offset: Zero-based offset to the argument within the bytecode
// (e.g. the first argument that's not packed with the bytecode has offset 4).
// argument_byte_length: Length of the argument.
// check_byte_offset: Zero-based offset relative to the beginning of the
// sequence that needs to match the value given by argument_offset. (e.g.
// check_byte_offset 0 matches the address of the first bytecode in the
// sequence).
BytecodeSequenceNode& IfArgumentEqualsOffset(int argument_offset,
int argument_byte_length,
int check_byte_offset);
// Adds a check to the sequence node making it only a valid sequence when the
// argument of the current bytecode at the specified offset matches the
// argument of another bytecode in the sequence.
// This is similar to IfArgumentEqualsOffset, except that this method matches
// the values of both arguments.
BytecodeSequenceNode& IfArgumentEqualsValueAtOffset(
int argument_offset, int argument_byte_length,
int other_bytecode_index_in_sequence, int other_argument_offset,
int other_argument_byte_length);
// Marks an argument as unused.
// All arguments that are not mapped explicitly have to be marked as unused.
// bytecode_index_in_sequence: Zero-based index of the referred bytecode
// within the sequence (e.g. the bytecode passed to CreateSequence() has
// index 0).
// argument_offset: Zero-based offset to the argument within the bytecode
// (e.g. the first argument that's not packed with the bytecode has offset 4).
// argument_byte_length: Length of the argument.
BytecodeSequenceNode& IgnoreArgument(int bytecode_index_in_sequence,
int argument_offset,
int argument_byte_length);
// Checks if the current node is valid for the sequence. I.e. all conditions
// set by IfArgumentEqualsOffset and IfArgumentEquals are fulfilled by this
// node for the actual bytecode sequence.
bool CheckArguments(const byte* bytecode, int pc);
// Returns whether this node marks the end of a valid sequence (i.e. can be
// replaced with an optimized bytecode).
bool IsSequence() const;
// Returns the length of the sequence in bytes.
int SequenceLength() const;
// Returns the optimized bytecode for the node or kDummyBytecode if it is not
// the end of a valid sequence.
int OptimizedBytecode() const;
// Returns the child of the current node matching the given bytecode or
// nullptr if no such child is found.
BytecodeSequenceNode* Find(int bytecode) const;
// Returns number of arguments mapped to the current node.
// Invoking this method is only allowed on nodes that mark the end of a valid
// sequence (i.e. if IsSequence())
size_t ArgumentSize() const;
// Returns the argument-mapping of the argument at index.
// Invoking this method is only allowed on nodes that mark the end of a valid
// sequence (i.e. if IsSequence())
BytecodeArgumentMapping ArgumentMapping(size_t index) const;
// Returns an iterator to begin of ignored arguments.
// Invoking this method is only allowed on nodes that mark the end of a valid
// sequence (i.e. if IsSequence())
ZoneLinkedList<BytecodeArgument>::iterator ArgumentIgnoredBegin() const;
// Returns an iterator to end of ignored arguments.
// Invoking this method is only allowed on nodes that mark the end of a valid
// sequence (i.e. if IsSequence())
ZoneLinkedList<BytecodeArgument>::iterator ArgumentIgnoredEnd() const;
// Returns whether the current node has ignored argument or not.
bool HasIgnoredArguments() const;
private:
// Returns a node in the sequence specified by its index within the sequence.
BytecodeSequenceNode& GetNodeByIndexInSequence(int index_in_sequence);
Zone* zone() const;
int bytecode_;
int bytecode_replacement_;
int index_in_sequence_;
int start_offset_;
BytecodeSequenceNode* parent_;
ZoneUnorderedMap<int, BytecodeSequenceNode*> children_;
ZoneVector<BytecodeArgumentMapping>* argument_mapping_;
ZoneLinkedList<BytecodeArgumentCheck>* argument_check_;
ZoneLinkedList<BytecodeArgument>* argument_ignored_;
Zone* zone_;
};
// These definitions are here in order to please the linker, which in debug mode
// sometimes requires static constants to be defined in .cc files.
constexpr int BytecodeSequenceNode::kDummyBytecode;
class RegExpBytecodePeephole {
public:
RegExpBytecodePeephole(Zone* zone, size_t buffer_size,
const ZoneUnorderedMap<int, int>& jump_edges);
// Parses bytecode and fills the internal buffer with the potentially
// optimized bytecode. Returns true when optimizations were performed, false
// otherwise.
bool OptimizeBytecode(const byte* bytecode, int length);
// Copies the internal bytecode buffer to another buffer. The caller is
// responsible for allocating/freeing the memory.
void CopyOptimizedBytecode(byte* to_address) const;
int Length() const;
private:
// Sets up all sequences that are going to be used.
void DefineStandardSequences();
// Starts a new bytecode sequence.
BytecodeSequenceNode& CreateSequence(int bytecode);
// Checks for optimization candidates at pc and emits optimized bytecode to
// the internal buffer. Returns the length of replaced bytecodes in bytes.
int TryOptimizeSequence(const byte* bytecode, int bytecode_length,
int start_pc);
// Emits optimized bytecode to the internal buffer. start_pc points to the
// start of the sequence in bytecode and last_node is the last
// BytecodeSequenceNode of the matching sequence found.
void EmitOptimization(int start_pc, const byte* bytecode,
const BytecodeSequenceNode& last_node);
// Adds a relative jump source fixup at pos.
// Jump source fixups are used to find offsets in the new bytecode that
// contain jump sources.
void AddJumpSourceFixup(int fixup, int pos);
// Adds a relative jump destination fixup at pos.
// Jump destination fixups are used to find offsets in the new bytecode that
// can be jumped to.
void AddJumpDestinationFixup(int fixup, int pos);
// Sets an absolute jump destination fixup at pos.
void SetJumpDestinationFixup(int fixup, int pos);
// Prepare internal structures used to fixup jumps.
void PrepareJumpStructures(const ZoneUnorderedMap<int, int>& jump_edges);
// Updates all jump targets in the new bytecode.
void FixJumps();
// Update a single jump.
void FixJump(int jump_source, int jump_destination);
void AddSentinelFixups(int pos);
template <typename T>
void EmitValue(T value);
template <typename T>
void OverwriteValue(int offset, T value);
void CopyRangeToOutput(const byte* orig_bytecode, int start, int length);
void SetRange(byte value, int count);
void EmitArgument(int start_pc, const byte* bytecode,
BytecodeArgumentMapping arg);
int pc() const;
Zone* zone() const;
ZoneVector<byte> optimized_bytecode_buffer_;
BytecodeSequenceNode* sequences_;
// Jumps used in old bytecode.
// Key: Jump source (offset where destination is stored in old bytecode)
// Value: Destination
ZoneMap<int, int> jump_edges_;
// Jumps used in new bytecode.
// Key: Jump source (offset where destination is stored in new bytecode)
// Value: Destination
ZoneMap<int, int> jump_edges_mapped_;
// Number of times a jump destination is used within the bytecode.
// Key: Jump destination (offset in old bytecode).
// Value: Number of times jump destination is used.
ZoneMap<int, int> jump_usage_counts_;
// Maps offsets in old bytecode to fixups of sources (delta to new bytecode).
// Key: Offset in old bytecode from where the fixup is valid.
// Value: Delta to map jump source from old bytecode to new bytecode in bytes.
ZoneMap<int, int> jump_source_fixups_;
// Maps offsets in old bytecode to fixups of destinations (delta to new
// bytecode).
// Key: Offset in old bytecode from where the fixup is valid.
// Value: Delta to map jump destinations from old bytecode to new bytecode in
// bytes.
ZoneMap<int, int> jump_destination_fixups_;
Zone* zone_;
DISALLOW_IMPLICIT_CONSTRUCTORS(RegExpBytecodePeephole);
};
template <typename T>
T GetValue(const byte* buffer, int pos) {
DCHECK(IsAligned(reinterpret_cast<Address>(buffer + pos), alignof(T)));
return *reinterpret_cast<const T*>(buffer + pos);
}
int32_t GetArgumentValue(const byte* bytecode, int offset, int length) {
switch (length) {
case 1:
return GetValue<byte>(bytecode, offset);
break;
case 2:
return GetValue<int16_t>(bytecode, offset);
break;
case 4:
return GetValue<int32_t>(bytecode, offset);
break;
default:
UNREACHABLE();
}
}
BytecodeSequenceNode::BytecodeSequenceNode(int bytecode, Zone* zone)
: bytecode_(bytecode),
bytecode_replacement_(kDummyBytecode),
index_in_sequence_(0),
start_offset_(0),
parent_(nullptr),
children_(ZoneUnorderedMap<int, BytecodeSequenceNode*>(zone)),
argument_mapping_(zone->New<ZoneVector<BytecodeArgumentMapping>>(zone)),
argument_check_(zone->New<ZoneLinkedList<BytecodeArgumentCheck>>(zone)),
argument_ignored_(zone->New<ZoneLinkedList<BytecodeArgument>>(zone)),
zone_(zone) {}
BytecodeSequenceNode& BytecodeSequenceNode::FollowedBy(int bytecode) {
DCHECK(0 <= bytecode && bytecode < kRegExpBytecodeCount);
if (children_.find(bytecode) == children_.end()) {
BytecodeSequenceNode* new_node =
zone()->New<BytecodeSequenceNode>(bytecode, zone());
// If node is not the first in the sequence, set offsets and parent.
if (bytecode_ != kDummyBytecode) {
new_node->start_offset_ = start_offset_ + RegExpBytecodeLength(bytecode_);
new_node->index_in_sequence_ = index_in_sequence_ + 1;
new_node->parent_ = this;
}
children_[bytecode] = new_node;
}
return *children_[bytecode];
}
BytecodeSequenceNode& BytecodeSequenceNode::ReplaceWith(int bytecode) {
DCHECK(0 <= bytecode && bytecode < kRegExpBytecodeCount);
bytecode_replacement_ = bytecode;
return *this;
}
BytecodeSequenceNode& BytecodeSequenceNode::MapArgument(
int bytecode_index_in_sequence, int argument_offset,
int argument_byte_length, int new_argument_byte_length) {
DCHECK(IsSequence());
DCHECK_LE(bytecode_index_in_sequence, index_in_sequence_);
BytecodeSequenceNode& ref_node =
GetNodeByIndexInSequence(bytecode_index_in_sequence);
DCHECK_LT(argument_offset, RegExpBytecodeLength(ref_node.bytecode_));
int absolute_offset = ref_node.start_offset_ + argument_offset;
if (new_argument_byte_length == 0) {
new_argument_byte_length = argument_byte_length;
}
argument_mapping_->push_back(BytecodeArgumentMapping{
absolute_offset, argument_byte_length, new_argument_byte_length});
return *this;
}
BytecodeSequenceNode& BytecodeSequenceNode::IfArgumentEqualsOffset(
int argument_offset, int argument_byte_length, int check_byte_offset) {
DCHECK_LT(argument_offset, RegExpBytecodeLength(bytecode_));
DCHECK(argument_byte_length == 1 || argument_byte_length == 2 ||
argument_byte_length == 4);
int absolute_offset = start_offset_ + argument_offset;
argument_check_->push_back(BytecodeArgumentCheck{
absolute_offset, argument_byte_length, check_byte_offset});
return *this;
}
BytecodeSequenceNode& BytecodeSequenceNode::IfArgumentEqualsValueAtOffset(
int argument_offset, int argument_byte_length,
int other_bytecode_index_in_sequence, int other_argument_offset,
int other_argument_byte_length) {
DCHECK_LT(argument_offset, RegExpBytecodeLength(bytecode_));
DCHECK_LE(other_bytecode_index_in_sequence, index_in_sequence_);
DCHECK_EQ(argument_byte_length, other_argument_byte_length);
BytecodeSequenceNode& ref_node =
GetNodeByIndexInSequence(other_bytecode_index_in_sequence);
DCHECK_LT(other_argument_offset, RegExpBytecodeLength(ref_node.bytecode_));
int absolute_offset = start_offset_ + argument_offset;
int other_absolute_offset = ref_node.start_offset_ + other_argument_offset;
argument_check_->push_back(
BytecodeArgumentCheck{absolute_offset, argument_byte_length,
other_absolute_offset, other_argument_byte_length});
return *this;
}
BytecodeSequenceNode& BytecodeSequenceNode::IgnoreArgument(
int bytecode_index_in_sequence, int argument_offset,
int argument_byte_length) {
DCHECK(IsSequence());
DCHECK_LE(bytecode_index_in_sequence, index_in_sequence_);
BytecodeSequenceNode& ref_node =
GetNodeByIndexInSequence(bytecode_index_in_sequence);
DCHECK_LT(argument_offset, RegExpBytecodeLength(ref_node.bytecode_));
int absolute_offset = ref_node.start_offset_ + argument_offset;
argument_ignored_->push_back(
BytecodeArgument{absolute_offset, argument_byte_length});
return *this;
}
bool BytecodeSequenceNode::CheckArguments(const byte* bytecode, int pc) {
bool is_valid = true;
for (auto check_iter = argument_check_->begin();
check_iter != argument_check_->end() && is_valid; check_iter++) {
auto value =
GetArgumentValue(bytecode, pc + check_iter->offset, check_iter->length);
if (check_iter->type == BytecodeArgumentCheck::kCheckAddress) {
is_valid &= value == pc + check_iter->check_offset;
} else if (check_iter->type == BytecodeArgumentCheck::kCheckValue) {
auto other_value = GetArgumentValue(
bytecode, pc + check_iter->check_offset, check_iter->check_length);
is_valid &= value == other_value;
} else {
UNREACHABLE();
}
}
return is_valid;
}
bool BytecodeSequenceNode::IsSequence() const {
return bytecode_replacement_ != kDummyBytecode;
}
int BytecodeSequenceNode::SequenceLength() const {
return start_offset_ + RegExpBytecodeLength(bytecode_);
}
int BytecodeSequenceNode::OptimizedBytecode() const {
return bytecode_replacement_;
}
BytecodeSequenceNode* BytecodeSequenceNode::Find(int bytecode) const {
auto found = children_.find(bytecode);
if (found == children_.end()) return nullptr;
return found->second;
}
size_t BytecodeSequenceNode::ArgumentSize() const {
DCHECK(IsSequence());
return argument_mapping_->size();
}
BytecodeArgumentMapping BytecodeSequenceNode::ArgumentMapping(
size_t index) const {
DCHECK(IsSequence());
DCHECK(argument_mapping_ != nullptr);
DCHECK_LT(index, argument_mapping_->size());
return argument_mapping_->at(index);
}
ZoneLinkedList<BytecodeArgument>::iterator
BytecodeSequenceNode::ArgumentIgnoredBegin() const {
DCHECK(IsSequence());
DCHECK(argument_ignored_ != nullptr);
return argument_ignored_->begin();
}
ZoneLinkedList<BytecodeArgument>::iterator
BytecodeSequenceNode::ArgumentIgnoredEnd() const {
DCHECK(IsSequence());
DCHECK(argument_ignored_ != nullptr);
return argument_ignored_->end();
}
bool BytecodeSequenceNode::HasIgnoredArguments() const {
return argument_ignored_ != nullptr;
}
BytecodeSequenceNode& BytecodeSequenceNode::GetNodeByIndexInSequence(
int index_in_sequence) {
DCHECK_LE(index_in_sequence, index_in_sequence_);
if (index_in_sequence < index_in_sequence_) {
DCHECK(parent_ != nullptr);
return parent_->GetNodeByIndexInSequence(index_in_sequence);
} else {
return *this;
}
}
Zone* BytecodeSequenceNode::zone() const { return zone_; }
RegExpBytecodePeephole::RegExpBytecodePeephole(
Zone* zone, size_t buffer_size,
const ZoneUnorderedMap<int, int>& jump_edges)
: optimized_bytecode_buffer_(zone),
sequences_(zone->New<BytecodeSequenceNode>(
BytecodeSequenceNode::kDummyBytecode, zone)),
jump_edges_(zone),
jump_edges_mapped_(zone),
jump_usage_counts_(zone),
jump_source_fixups_(zone),
jump_destination_fixups_(zone),
zone_(zone) {
optimized_bytecode_buffer_.reserve(buffer_size);
PrepareJumpStructures(jump_edges);
DefineStandardSequences();
// Sentinel fixups at beginning of bytecode (position -1) so we don't have to
// check for end of iterator inside the fixup loop.
// In general fixups are deltas of original offsets of jump
// sources/destinations (in the old bytecode) to find them in the new
// bytecode. All jump targets are fixed after the new bytecode is fully
// emitted in the internal buffer.
AddSentinelFixups(-1);
// Sentinel fixups at end of (old) bytecode so we don't have to check for
// end of iterator inside the fixup loop.
DCHECK_LE(buffer_size, std::numeric_limits<int>::max());
AddSentinelFixups(static_cast<int>(buffer_size));
}
void RegExpBytecodePeephole::DefineStandardSequences() {
// Commonly used sequences can be found by creating regexp bytecode traces
// (--trace-regexp-bytecodes) and using v8/tools/regexp-sequences.py.
CreateSequence(BC_LOAD_CURRENT_CHAR)
.FollowedBy(BC_CHECK_BIT_IN_TABLE)
.FollowedBy(BC_ADVANCE_CP_AND_GOTO)
// Sequence is only valid if the jump target of ADVANCE_CP_AND_GOTO is the
// first bytecode in this sequence.
.IfArgumentEqualsOffset(4, 4, 0)
.ReplaceWith(BC_SKIP_UNTIL_BIT_IN_TABLE)
.MapArgument(0, 1, 3) // load offset
.MapArgument(2, 1, 3, 4) // advance by
.MapArgument(1, 8, 16) // bit table
.MapArgument(1, 4, 4) // goto when match
.MapArgument(0, 4, 4) // goto on failure
.IgnoreArgument(2, 4, 4); // loop jump
CreateSequence(BC_CHECK_CURRENT_POSITION)
.FollowedBy(BC_LOAD_CURRENT_CHAR_UNCHECKED)
.FollowedBy(BC_CHECK_CHAR)
.FollowedBy(BC_ADVANCE_CP_AND_GOTO)
// Sequence is only valid if the jump target of ADVANCE_CP_AND_GOTO is the
// first bytecode in this sequence.
.IfArgumentEqualsOffset(4, 4, 0)
.ReplaceWith(BC_SKIP_UNTIL_CHAR_POS_CHECKED)
.MapArgument(1, 1, 3) // load offset
.MapArgument(3, 1, 3, 2) // advance_by
.MapArgument(2, 1, 3, 2) // c
.MapArgument(0, 1, 3, 4) // eats at least
.MapArgument(2, 4, 4) // goto when match
.MapArgument(0, 4, 4) // goto on failure
.IgnoreArgument(3, 4, 4); // loop jump
CreateSequence(BC_CHECK_CURRENT_POSITION)
.FollowedBy(BC_LOAD_CURRENT_CHAR_UNCHECKED)
.FollowedBy(BC_AND_CHECK_CHAR)
.FollowedBy(BC_ADVANCE_CP_AND_GOTO)
// Sequence is only valid if the jump target of ADVANCE_CP_AND_GOTO is the
// first bytecode in this sequence.
.IfArgumentEqualsOffset(4, 4, 0)
.ReplaceWith(BC_SKIP_UNTIL_CHAR_AND)
.MapArgument(1, 1, 3) // load offset
.MapArgument(3, 1, 3, 2) // advance_by
.MapArgument(2, 1, 3, 2) // c
.MapArgument(2, 4, 4) // mask
.MapArgument(0, 1, 3, 4) // eats at least
.MapArgument(2, 8, 4) // goto when match
.MapArgument(0, 4, 4) // goto on failure
.IgnoreArgument(3, 4, 4); // loop jump
// TODO(pthier): It might make sense for short sequences like this one to only
// optimize them if the resulting optimization is not longer than the current
// one. This could be the case if there are jumps inside the sequence and we
// have to replicate parts of the sequence. A method to mark such sequences
// might be useful.
CreateSequence(BC_LOAD_CURRENT_CHAR)
.FollowedBy(BC_CHECK_CHAR)
.FollowedBy(BC_ADVANCE_CP_AND_GOTO)
// Sequence is only valid if the jump target of ADVANCE_CP_AND_GOTO is the
// first bytecode in this sequence.
.IfArgumentEqualsOffset(4, 4, 0)
.ReplaceWith(BC_SKIP_UNTIL_CHAR)
.MapArgument(0, 1, 3) // load offset
.MapArgument(2, 1, 3, 2) // advance by
.MapArgument(1, 1, 3, 2) // character
.MapArgument(1, 4, 4) // goto when match
.MapArgument(0, 4, 4) // goto on failure
.IgnoreArgument(2, 4, 4); // loop jump
CreateSequence(BC_LOAD_CURRENT_CHAR)
.FollowedBy(BC_CHECK_CHAR)
.FollowedBy(BC_CHECK_CHAR)
// Sequence is only valid if the jump targets of both CHECK_CHAR bytecodes
// are equal.
.IfArgumentEqualsValueAtOffset(4, 4, 1, 4, 4)
.FollowedBy(BC_ADVANCE_CP_AND_GOTO)
// Sequence is only valid if the jump target of ADVANCE_CP_AND_GOTO is the
// first bytecode in this sequence.
.IfArgumentEqualsOffset(4, 4, 0)
.ReplaceWith(BC_SKIP_UNTIL_CHAR_OR_CHAR)
.MapArgument(0, 1, 3) // load offset
.MapArgument(3, 1, 3, 4) // advance by
.MapArgument(1, 1, 3, 2) // character 1
.MapArgument(2, 1, 3, 2) // character 2
.MapArgument(1, 4, 4) // goto when match
.MapArgument(0, 4, 4) // goto on failure
.IgnoreArgument(2, 4, 4) // goto when match 2
.IgnoreArgument(3, 4, 4); // loop jump
CreateSequence(BC_LOAD_CURRENT_CHAR)
.FollowedBy(BC_CHECK_GT)
// Sequence is only valid if the jump target of CHECK_GT is the first
// bytecode AFTER the whole sequence.
.IfArgumentEqualsOffset(4, 4, 56)
.FollowedBy(BC_CHECK_BIT_IN_TABLE)
// Sequence is only valid if the jump target of CHECK_BIT_IN_TABLE is
// the ADVANCE_CP_AND_GOTO bytecode at the end of the sequence.
.IfArgumentEqualsOffset(4, 4, 48)
.FollowedBy(BC_GOTO)
// Sequence is only valid if the jump target of GOTO is the same as the
// jump target of CHECK_GT (i.e. both jump to the first bytecode AFTER the
// whole sequence.
.IfArgumentEqualsValueAtOffset(4, 4, 1, 4, 4)
.FollowedBy(BC_ADVANCE_CP_AND_GOTO)
// Sequence is only valid if the jump target of ADVANCE_CP_AND_GOTO is the
// first bytecode in this sequence.
.IfArgumentEqualsOffset(4, 4, 0)
.ReplaceWith(BC_SKIP_UNTIL_GT_OR_NOT_BIT_IN_TABLE)
.MapArgument(0, 1, 3) // load offset
.MapArgument(4, 1, 3, 2) // advance by
.MapArgument(1, 1, 3, 2) // character
.MapArgument(2, 8, 16) // bit table
.MapArgument(1, 4, 4) // goto when match
.MapArgument(0, 4, 4) // goto on failure
.IgnoreArgument(2, 4, 4) // indirect loop jump
.IgnoreArgument(3, 4, 4) // jump out of loop
.IgnoreArgument(4, 4, 4); // loop jump
}
bool RegExpBytecodePeephole::OptimizeBytecode(const byte* bytecode,
int length) {
int old_pc = 0;
bool did_optimize = false;
while (old_pc < length) {
int replaced_len = TryOptimizeSequence(bytecode, length, old_pc);
if (replaced_len > 0) {
old_pc += replaced_len;
did_optimize = true;
} else {
int bc = bytecode[old_pc];
int bc_len = RegExpBytecodeLength(bc);
CopyRangeToOutput(bytecode, old_pc, bc_len);
old_pc += bc_len;
}
}
if (did_optimize) {
FixJumps();
}
return did_optimize;
}
void RegExpBytecodePeephole::CopyOptimizedBytecode(byte* to_address) const {
MemCopy(to_address, &(*optimized_bytecode_buffer_.begin()), Length());
}
int RegExpBytecodePeephole::Length() const { return pc(); }
BytecodeSequenceNode& RegExpBytecodePeephole::CreateSequence(int bytecode) {
DCHECK(sequences_ != nullptr);
DCHECK(0 <= bytecode && bytecode < kRegExpBytecodeCount);
return sequences_->FollowedBy(bytecode);
}
int RegExpBytecodePeephole::TryOptimizeSequence(const byte* bytecode,
int bytecode_length,
int start_pc) {
BytecodeSequenceNode* seq_node = sequences_;
BytecodeSequenceNode* valid_seq_end = nullptr;
int current_pc = start_pc;
// Check for the longest valid sequence matching any of the pre-defined
// sequences in the Trie data structure.
while (current_pc < bytecode_length) {
seq_node = seq_node->Find(bytecode[current_pc]);
if (seq_node == nullptr) break;
if (!seq_node->CheckArguments(bytecode, start_pc)) break;
if (seq_node->IsSequence()) valid_seq_end = seq_node;
current_pc += RegExpBytecodeLength(bytecode[current_pc]);
}
if (valid_seq_end) {
EmitOptimization(start_pc, bytecode, *valid_seq_end);
return valid_seq_end->SequenceLength();
}
return 0;
}
void RegExpBytecodePeephole::EmitOptimization(
int start_pc, const byte* bytecode, const BytecodeSequenceNode& last_node) {
#ifdef DEBUG
int optimized_start_pc = pc();
#endif
// Jump sources that are mapped or marked as unused will be deleted at the end
// of this method. We don't delete them immediately as we might need the
// information when we have to preserve bytecodes at the end.
// TODO(pthier): Replace with a stack-allocated data structure.
ZoneLinkedList<int> delete_jumps = ZoneLinkedList<int>(zone());
uint32_t bc = last_node.OptimizedBytecode();
EmitValue(bc);
for (size_t arg = 0; arg < last_node.ArgumentSize(); arg++) {
BytecodeArgumentMapping arg_map = last_node.ArgumentMapping(arg);
int arg_pos = start_pc + arg_map.offset;
// If we map any jump source we mark the old source for deletion and insert
// a new jump.
auto jump_edge_iter = jump_edges_.find(arg_pos);
if (jump_edge_iter != jump_edges_.end()) {
int jump_source = jump_edge_iter->first;
int jump_destination = jump_edge_iter->second;
// Add new jump edge add current position.
jump_edges_mapped_.emplace(Length(), jump_destination);
// Mark old jump edge for deletion.
delete_jumps.push_back(jump_source);
// Decrement usage count of jump destination.
auto jump_count_iter = jump_usage_counts_.find(jump_destination);
DCHECK(jump_count_iter != jump_usage_counts_.end());
int& usage_count = jump_count_iter->second;
--usage_count;
}
// TODO(pthier): DCHECK that mapped arguments are never sources of jumps
// to destinations inside the sequence.
EmitArgument(start_pc, bytecode, arg_map);
}
DCHECK_EQ(pc(), optimized_start_pc +
RegExpBytecodeLength(last_node.OptimizedBytecode()));
// Remove jumps from arguments we ignore.
if (last_node.HasIgnoredArguments()) {
for (auto ignored_arg = last_node.ArgumentIgnoredBegin();
ignored_arg != last_node.ArgumentIgnoredEnd(); ignored_arg++) {
auto jump_edge_iter = jump_edges_.find(start_pc + ignored_arg->offset);
if (jump_edge_iter != jump_edges_.end()) {
int jump_source = jump_edge_iter->first;
int jump_destination = jump_edge_iter->second;
// Mark old jump edge for deletion.
delete_jumps.push_back(jump_source);
// Decrement usage count of jump destination.
auto jump_count_iter = jump_usage_counts_.find(jump_destination);
DCHECK(jump_count_iter != jump_usage_counts_.end());
int& usage_count = jump_count_iter->second;
--usage_count;
}
}
}
int fixup_length = RegExpBytecodeLength(bc) - last_node.SequenceLength();
// Check if there are any jumps inside the old sequence.
// If so we have to keep the bytecodes that are jumped to around.
auto jump_destination_candidate = jump_usage_counts_.upper_bound(start_pc);
int jump_candidate_destination = jump_destination_candidate->first;
int jump_candidate_count = jump_destination_candidate->second;
// Jump destinations only jumped to from inside the sequence will be ignored.
while (jump_destination_candidate != jump_usage_counts_.end() &&
jump_candidate_count == 0) {
++jump_destination_candidate;
jump_candidate_destination = jump_destination_candidate->first;
jump_candidate_count = jump_destination_candidate->second;
}
int preserve_from = start_pc + last_node.SequenceLength();
if (jump_destination_candidate != jump_usage_counts_.end() &&
jump_candidate_destination < start_pc + last_node.SequenceLength()) {
preserve_from = jump_candidate_destination;
// Check if any jump in the sequence we are preserving has a jump
// destination inside the optimized sequence before the current position we
// want to preserve. If so we have to preserve all bytecodes starting at
// this jump destination.
for (auto jump_iter = jump_edges_.lower_bound(preserve_from);
jump_iter != jump_edges_.end() &&
jump_iter->first /* jump source */ <
start_pc + last_node.SequenceLength();
++jump_iter) {
int jump_destination = jump_iter->second;
if (jump_destination > start_pc && jump_destination < preserve_from) {
preserve_from = jump_destination;
}
}
// We preserve everything to the end of the sequence. This is conservative
// since it would be enough to preserve all bytecudes up to an unconditional
// jump.
int preserve_length = start_pc + last_node.SequenceLength() - preserve_from;
fixup_length += preserve_length;
// Jumps after the start of the preserved sequence need fixup.
AddJumpSourceFixup(fixup_length,
start_pc + last_node.SequenceLength() - preserve_length);
// All jump targets after the start of the optimized sequence need to be
// fixed relative to the length of the optimized sequence including
// bytecodes we preserved.
AddJumpDestinationFixup(fixup_length, start_pc + 1);
// Jumps to the sequence we preserved need absolute fixup as they could
// occur before or after the sequence.
SetJumpDestinationFixup(pc() - preserve_from, preserve_from);
CopyRangeToOutput(bytecode, preserve_from, preserve_length);
} else {
AddJumpDestinationFixup(fixup_length, start_pc + 1);
// Jumps after the end of the old sequence need fixup.
AddJumpSourceFixup(fixup_length, start_pc + last_node.SequenceLength());
}
// Delete jumps we definitely don't need anymore
for (int del : delete_jumps) {
if (del < preserve_from) {
jump_edges_.erase(del);
}
}
}
void RegExpBytecodePeephole::AddJumpSourceFixup(int fixup, int pos) {
auto previous_fixup = jump_source_fixups_.lower_bound(pos);
DCHECK(previous_fixup != jump_source_fixups_.end());
DCHECK(previous_fixup != jump_source_fixups_.begin());
int previous_fixup_value = (--previous_fixup)->second;
jump_source_fixups_[pos] = previous_fixup_value + fixup;
}
void RegExpBytecodePeephole::AddJumpDestinationFixup(int fixup, int pos) {
auto previous_fixup = jump_destination_fixups_.lower_bound(pos);
DCHECK(previous_fixup != jump_destination_fixups_.end());
DCHECK(previous_fixup != jump_destination_fixups_.begin());
int previous_fixup_value = (--previous_fixup)->second;
jump_destination_fixups_[pos] = previous_fixup_value + fixup;
}
void RegExpBytecodePeephole::SetJumpDestinationFixup(int fixup, int pos) {
auto previous_fixup = jump_destination_fixups_.lower_bound(pos);
DCHECK(previous_fixup != jump_destination_fixups_.end());
DCHECK(previous_fixup != jump_destination_fixups_.begin());
int previous_fixup_value = (--previous_fixup)->second;
jump_destination_fixups_.emplace(pos, fixup);
jump_destination_fixups_.emplace(pos + 1, previous_fixup_value);
}
void RegExpBytecodePeephole::PrepareJumpStructures(
const ZoneUnorderedMap<int, int>& jump_edges) {
for (auto jump_edge : jump_edges) {
int jump_source = jump_edge.first;
int jump_destination = jump_edge.second;
jump_edges_.emplace(jump_source, jump_destination);
jump_usage_counts_[jump_destination]++;
}
}
void RegExpBytecodePeephole::FixJumps() {
int position_fixup = 0;
// Next position where fixup changes.
auto next_source_fixup = jump_source_fixups_.lower_bound(0);
int next_source_fixup_offset = next_source_fixup->first;
int next_source_fixup_value = next_source_fixup->second;
for (auto jump_edge : jump_edges_) {
int jump_source = jump_edge.first;
int jump_destination = jump_edge.second;
while (jump_source >= next_source_fixup_offset) {
position_fixup = next_source_fixup_value;
++next_source_fixup;
next_source_fixup_offset = next_source_fixup->first;
next_source_fixup_value = next_source_fixup->second;
}
jump_source += position_fixup;
FixJump(jump_source, jump_destination);
}
// Mapped jump edges don't need source fixups, as the position already is an
// offset in the new bytecode.
for (auto jump_edge : jump_edges_mapped_) {
int jump_source = jump_edge.first;
int jump_destination = jump_edge.second;
FixJump(jump_source, jump_destination);
}
}
void RegExpBytecodePeephole::FixJump(int jump_source, int jump_destination) {
int fixed_jump_destination =
jump_destination +
(--jump_destination_fixups_.upper_bound(jump_destination))->second;
DCHECK_LT(fixed_jump_destination, Length());
#ifdef DEBUG
// TODO(pthier): This check could be better if we track the bytecodes
// actually used and check if we jump to one of them.
byte jump_bc = optimized_bytecode_buffer_[fixed_jump_destination];
DCHECK_GT(jump_bc, 0);
DCHECK_LT(jump_bc, kRegExpBytecodeCount);
#endif
if (jump_destination != fixed_jump_destination) {
OverwriteValue<uint32_t>(jump_source, fixed_jump_destination);
}
}
void RegExpBytecodePeephole::AddSentinelFixups(int pos) {
jump_source_fixups_.emplace(pos, 0);
jump_destination_fixups_.emplace(pos, 0);
}
template <typename T>
void RegExpBytecodePeephole::EmitValue(T value) {
DCHECK(optimized_bytecode_buffer_.begin() + pc() ==
optimized_bytecode_buffer_.end());
byte* value_byte_iter = reinterpret_cast<byte*>(&value);
optimized_bytecode_buffer_.insert(optimized_bytecode_buffer_.end(),
value_byte_iter,
value_byte_iter + sizeof(T));
}
template <typename T>
void RegExpBytecodePeephole::OverwriteValue(int offset, T value) {
byte* value_byte_iter = reinterpret_cast<byte*>(&value);
byte* value_byte_iter_end = value_byte_iter + sizeof(T);
while (value_byte_iter < value_byte_iter_end) {
optimized_bytecode_buffer_[offset++] = *value_byte_iter++;
}
}
void RegExpBytecodePeephole::CopyRangeToOutput(const byte* orig_bytecode,
int start, int length) {
DCHECK(optimized_bytecode_buffer_.begin() + pc() ==
optimized_bytecode_buffer_.end());
optimized_bytecode_buffer_.insert(optimized_bytecode_buffer_.end(),
orig_bytecode + start,
orig_bytecode + start + length);
}
void RegExpBytecodePeephole::SetRange(byte value, int count) {
DCHECK(optimized_bytecode_buffer_.begin() + pc() ==
optimized_bytecode_buffer_.end());
optimized_bytecode_buffer_.insert(optimized_bytecode_buffer_.end(), count,
value);
}
void RegExpBytecodePeephole::EmitArgument(int start_pc, const byte* bytecode,
BytecodeArgumentMapping arg) {
int arg_pos = start_pc + arg.offset;
switch (arg.length) {
case 1:
DCHECK_EQ(arg.new_length, arg.length);
EmitValue(GetValue<byte>(bytecode, arg_pos));
break;
case 2:
DCHECK_EQ(arg.new_length, arg.length);
EmitValue(GetValue<uint16_t>(bytecode, arg_pos));
break;
case 3: {
// Length 3 only occurs in 'packed' arguments where the lowermost byte is
// the current bytecode, and the remaining 3 bytes are the packed value.
//
// We load 4 bytes from position - 1 and shift out the bytecode.
#ifdef V8_TARGET_BIG_ENDIAN
UNIMPLEMENTED();
int32_t val = 0;
#else
int32_t val = GetValue<int32_t>(bytecode, arg_pos - 1) >> kBitsPerByte;
#endif // V8_TARGET_BIG_ENDIAN
switch (arg.new_length) {
case 2:
EmitValue<uint16_t>(val);
break;
case 3: {
// Pack with previously emitted value.
auto prev_val =
GetValue<int32_t>(&(*optimized_bytecode_buffer_.begin()),
Length() - sizeof(uint32_t));
#ifdef V8_TARGET_BIG_ENDIAN
UNIMPLEMENTED();
USE(prev_val);
#else
DCHECK_EQ(prev_val & 0xFFFFFF00, 0);
OverwriteValue<uint32_t>(
pc() - sizeof(uint32_t),
(static_cast<uint32_t>(val) << 8) | (prev_val & 0xFF));
#endif // V8_TARGET_BIG_ENDIAN
break;
}
case 4:
EmitValue<uint32_t>(val);
break;
}
break;
}
case 4:
DCHECK_EQ(arg.new_length, arg.length);
EmitValue(GetValue<uint32_t>(bytecode, arg_pos));
break;
case 8:
DCHECK_EQ(arg.new_length, arg.length);
EmitValue(GetValue<uint64_t>(bytecode, arg_pos));
break;
default:
CopyRangeToOutput(bytecode, arg_pos, Min(arg.length, arg.new_length));
if (arg.length < arg.new_length) {
SetRange(0x00, arg.new_length - arg.length);
}
break;
}
}
int RegExpBytecodePeephole::pc() const {
DCHECK_LE(optimized_bytecode_buffer_.size(), std::numeric_limits<int>::max());
return static_cast<int>(optimized_bytecode_buffer_.size());
}
Zone* RegExpBytecodePeephole::zone() const { return zone_; }
} // namespace
// static
Handle<ByteArray> RegExpBytecodePeepholeOptimization::OptimizeBytecode(
Isolate* isolate, Zone* zone, Handle<String> source, const byte* bytecode,
int length, const ZoneUnorderedMap<int, int>& jump_edges) {
RegExpBytecodePeephole peephole(zone, length, jump_edges);
bool did_optimize = peephole.OptimizeBytecode(bytecode, length);
Handle<ByteArray> array = isolate->factory()->NewByteArray(peephole.Length());
peephole.CopyOptimizedBytecode(array->GetDataStartAddress());
if (did_optimize && FLAG_trace_regexp_peephole_optimization) {
PrintF("Original Bytecode:\n");
RegExpBytecodeDisassemble(bytecode, length, source->ToCString().get());
PrintF("Optimized Bytecode:\n");
RegExpBytecodeDisassemble(array->GetDataStartAddress(), peephole.Length(),
source->ToCString().get());
}
return array;
}
} // namespace internal
} // namespace v8