Commit 66129430 authored by Patrick Thier's avatar Patrick Thier Committed by Commit Bot

[regexp] Bytecode peephole optimization

Bytecodes used by the regular expression interpreter often occur in
specific sequences. The number of dispatches in the interpreter can be
reduced if those sequences are combined into a single bytecode.

This CL adds a peephole optimization pass for regexp bytecodes.
This pass checks the generated bytecode for pre-defined sequences that
can be merged into a single bytecode.

With the currently implemented bytecode sequences a speedup of 1.12x on
regex-dna and octane-regexp is achieved.

Bug: v8:9330
Change-Id: I827f93273a5848e5963c7e3329daeb898995d151
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1813743
Commit-Queue: Patrick Thier <pthier@google.com>
Reviewed-by: 's avatarPeter Marshall <petermarshall@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63992}
parent 4ce267a8
......@@ -2776,6 +2776,9 @@ v8_source_set("v8_base_without_compiler") {
"src/regexp/regexp-bytecode-generator-inl.h",
"src/regexp/regexp-bytecode-generator.cc",
"src/regexp/regexp-bytecode-generator.h",
"src/regexp/regexp-bytecode-peephole.cc",
"src/regexp/regexp-bytecode-peephole.h",
"src/regexp/regexp-bytecodes.cc",
"src/regexp/regexp-bytecodes.h",
"src/regexp/regexp-compiler-tonode.cc",
"src/regexp/regexp-compiler.cc",
......
......@@ -1274,6 +1274,15 @@ DEFINE_BOOL(regexp_tier_up, true,
DEFINE_INT(regexp_tier_up_ticks, 1,
"set the number of executions for the regexp interpreter before "
"tiering-up to the compiler")
DEFINE_BOOL(regexp_peephole_optimization, true,
"enable peephole optimization for regexp bytecode")
DEFINE_BOOL(trace_regexp_peephole_optimization, false,
"trace regexp bytecode peephole optimization")
DEFINE_BOOL(trace_regexp_bytecodes, false, "trace regexp bytecode execution")
DEFINE_BOOL(trace_regexp_assembler, false,
"trace regexp macro assembler calls.")
DEFINE_BOOL(trace_regexp_parser, false, "trace regexp parsing")
DEFINE_BOOL(trace_regexp_tier_up, false, "trace regexp tiering up execution")
// Testing flags test/cctest/test-{flags,api,serialization}.cc
DEFINE_BOOL(testing_bool_flag, true, "testing_bool_flag")
......@@ -1408,11 +1417,6 @@ DEFINE_BOOL(trace_isolates, false, "trace isolate state changes")
// Regexp
DEFINE_BOOL(regexp_possessive_quantifier, false,
"enable possessive quantifier syntax for testing")
DEFINE_BOOL(trace_regexp_bytecodes, false, "trace regexp bytecode execution")
DEFINE_BOOL(trace_regexp_assembler, false,
"trace regexp macro assembler calls.")
DEFINE_BOOL(trace_regexp_parser, false, "trace regexp parsing")
DEFINE_BOOL(trace_regexp_tier_up, false, "trace regexp tiering up execution")
// Debugger
DEFINE_BOOL(print_break_location, false, "print source location on debug break")
......
......@@ -7,6 +7,7 @@
#include "src/ast/ast.h"
#include "src/objects/objects-inl.h"
#include "src/regexp/regexp-bytecode-generator-inl.h"
#include "src/regexp/regexp-bytecode-peephole.h"
#include "src/regexp/regexp-bytecodes.h"
#include "src/regexp/regexp-macro-assembler.h"
......@@ -18,6 +19,7 @@ RegExpBytecodeGenerator::RegExpBytecodeGenerator(Isolate* isolate, Zone* zone)
buffer_(Vector<byte>::New(1024)),
pc_(0),
advance_current_end_(kInvalidPC),
jump_edges_(zone),
isolate_(isolate) {}
RegExpBytecodeGenerator::~RegExpBytecodeGenerator() {
......@@ -39,6 +41,7 @@ void RegExpBytecodeGenerator::Bind(Label* l) {
int fixup = pos;
pos = *reinterpret_cast<int32_t*>(buffer_.begin() + fixup);
*reinterpret_cast<uint32_t*>(buffer_.begin() + fixup) = pc_;
jump_edges_.emplace(fixup, pc_);
}
}
l->bind_to(pc_);
......@@ -46,16 +49,17 @@ void RegExpBytecodeGenerator::Bind(Label* l) {
void RegExpBytecodeGenerator::EmitOrLink(Label* l) {
if (l == nullptr) l = &backtrack_;
int pos = 0;
if (l->is_bound()) {
Emit32(l->pos());
pos = l->pos();
jump_edges_.emplace(pc_, pos);
} else {
int pos = 0;
if (l->is_linked()) {
pos = l->pos();
}
l->link_to(pc_);
Emit32(pos);
}
Emit32(pos);
}
void RegExpBytecodeGenerator::PopRegister(int register_index) {
......@@ -365,8 +369,16 @@ void RegExpBytecodeGenerator::IfRegisterEqPos(int register_index,
Handle<HeapObject> RegExpBytecodeGenerator::GetCode(Handle<String> source) {
Bind(&backtrack_);
Emit(BC_POP_BT, 0);
Handle<ByteArray> array = isolate_->factory()->NewByteArray(length());
Copy(array->GetDataStartAddress());
Handle<ByteArray> array;
if (FLAG_regexp_peephole_optimization) {
array = RegExpBytecodePeepholeOptimization::OptimizeBytecode(
isolate_, zone(), source, buffer_.begin(), length(), jump_edges_);
} else {
array = isolate_->factory()->NewByteArray(length());
Copy(array->GetDataStartAddress());
}
return array;
}
......
......@@ -100,6 +100,12 @@ class V8_EXPORT_PRIVATE RegExpBytecodeGenerator : public RegExpMacroAssembler {
int advance_current_offset_;
int advance_current_end_;
// Stores jump edges emitted for the bytecode (used by
// RegExpBytecodePeepholeOptimization).
// Key: jump source (offset in buffer_ where jump destination is stored).
// Value: jump destination (offset in buffer_ to jump to).
ZoneUnorderedMap<int, int> jump_edges_;
Isolate* isolate_;
static const int kInvalidPC = -1;
......
// 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_;
};
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 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) {
return *reinterpret_cast<const T*>(buffer + pos);
}
template <typename T>
T GetValue(const byte* buffer, int pos, unsigned mask) {
return GetValue<T>(buffer, pos) & mask;
}
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_(new (zone->New(sizeof(*argument_mapping_)))
ZoneVector<BytecodeArgumentMapping>(zone)),
argument_check_(new (zone->New(sizeof(*argument_check_)))
ZoneLinkedList<BytecodeArgumentCheck>(zone)),
argument_ignored_(new (zone->New(sizeof(*argument_ignored_)))
ZoneLinkedList<BytecodeArgument>(zone)),
zone_(zone) {}
BytecodeSequenceNode& BytecodeSequenceNode::FollowedBy(int bytecode) {
DCHECK(0 <= bytecode && bytecode < kRegExpBytecodeCount);
if (children_.find(bytecode) == children_.end()) {
BytecodeSequenceNode* new_node =
new (zone()->New(sizeof(BytecodeSequenceNode)))
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_GE(index, 0);
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_(new (zone->New(sizeof(*sequences_))) 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, 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 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 ((seq_node = seq_node->Find(bytecode[current_pc]))) {
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());
jump_source_fixups_[pos] = (--previous_fixup)->second + 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());
jump_destination_fixups_[pos] = (--previous_fixup)->second + 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());
jump_destination_fixups_.emplace(pos, fixup);
jump_destination_fixups_.emplace(pos + 1, (--previous_fixup)->second);
}
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: {
uint32_t val = GetValue<uint32_t>(bytecode, arg_pos, 0xFFFFFF);
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));
DCHECK_EQ(prev_val & 0xFFFFFF00, 0);
OverwriteValue<uint32_t>(pc() - sizeof(uint32_t),
(val << 8) | (prev_val & 0xFF));
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
// 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.
#ifndef V8_REGEXP_REGEXP_BYTECODE_PEEPHOLE_H_
#define V8_REGEXP_REGEXP_BYTECODE_PEEPHOLE_H_
#include "src/common/globals.h"
#include "src/zone/zone-containers.h"
namespace v8 {
namespace internal {
class ByteArray;
// Peephole optimization for regexp interpreter bytecode.
// Pre-defined bytecode sequences occuring in the bytecode generated by the
// RegExpBytecodeGenerator can be optimized into a single bytecode.
class RegExpBytecodePeepholeOptimization : public AllStatic {
public:
// Performs peephole optimization on the given bytecode and returns the
// optimized bytecode.
static Handle<ByteArray> OptimizeBytecode(
Isolate* isolate, Zone* zone, Handle<String> source, const byte* bytecode,
int length, const ZoneUnorderedMap<int, int>& jump_edges);
};
} // namespace internal
} // namespace v8
#endif // V8_REGEXP_REGEXP_BYTECODE_PEEPHOLE_H_
// 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-bytecodes.h"
#include <cctype>
#include "src/utils/utils.h"
namespace v8 {
namespace internal {
void RegExpBytecodeDisassembleSingle(const byte* code_base, const byte* pc) {
PrintF("%s", RegExpBytecodeName(*pc));
// Args and the bytecode as hex.
for (int i = 0; i < RegExpBytecodeLength(*pc); i++) {
PrintF(", %02x", pc[i]);
}
PrintF(" ");
// Args as ascii.
for (int i = 1; i < RegExpBytecodeLength(*pc); i++) {
unsigned char b = pc[i];
PrintF("%c", std::isprint(b) ? b : '.');
}
PrintF("\n");
}
void RegExpBytecodeDisassemble(const byte* code_base, int length,
const char* pattern) {
PrintF("[generated bytecode for regexp pattern: '%s']\n", pattern);
ptrdiff_t offset = 0;
while (offset < length) {
const byte* const pc = code_base + offset;
PrintF("%p %4" V8PRIxPTRDIFF " ", pc, offset);
RegExpBytecodeDisassembleSingle(code_base, pc);
offset += RegExpBytecodeLength(*pc);
}
}
} // namespace internal
} // namespace v8
......@@ -6,6 +6,7 @@
#define V8_REGEXP_REGEXP_BYTECODES_H_
#include "src/base/macros.h"
#include "src/common/globals.h"
namespace v8 {
namespace internal {
......@@ -24,6 +25,8 @@ const unsigned int MAX_FIRST_ARG = 0x7fffffu;
const int BYTECODE_SHIFT = 8;
STATIC_ASSERT(1 << BYTECODE_SHIFT > BYTECODE_MASK);
// TODO(pthier): Argument offsets of bytecodes should be easily accessible by
// name or at least by position.
#define BYTECODE_ITERATOR(V) \
V(BREAK, 0, 4) /* bc8 */ \
V(PUSH_CP, 1, 4) /* bc8 pad24 */ \
......@@ -41,25 +44,61 @@ STATIC_ASSERT(1 << BYTECODE_SHIFT > BYTECODE_MASK);
V(FAIL, 13, 4) /* bc8 pad24 */ \
V(SUCCEED, 14, 4) /* bc8 pad24 */ \
V(ADVANCE_CP, 15, 4) /* bc8 offset24 */ \
V(GOTO, 16, 8) /* bc8 pad24 addr32 */ \
/* Jump to another bytecode given its offset. */ \
/* Bit Layout: */ \
/* 0x00 - 0x07: 0x10 (fixed) Bytecode */ \
/* 0x08 - 0x1F: 0x00 (unused) Padding */ \
/* 0x20 - 0x3F: Address of bytecode to jump to */ \
V(GOTO, 16, 8) /* bc8 pad24 addr32 */ \
/* Check if offset is in range and load character at given offset. */ \
/* Bit Layout: */ \
/* 0x00 - 0x07: 0x11 (fixed) Bytecode */ \
/* 0x08 - 0x1F: Offset from current position */ \
/* 0x20 - 0x3F: Address of bytecode when load is out of range */ \
V(LOAD_CURRENT_CHAR, 17, 8) /* bc8 offset24 addr32 */ \
/* Load character at given offset without range checks. */ \
/* Bit Layout: */ \
/* 0x00 - 0x07: 0x12 (fixed) Bytecode */ \
/* 0x08 - 0x1F: Offset from current position */ \
V(LOAD_CURRENT_CHAR_UNCHECKED, 18, 4) /* bc8 offset24 */ \
V(LOAD_2_CURRENT_CHARS, 19, 8) /* bc8 offset24 addr32 */ \
V(LOAD_2_CURRENT_CHARS_UNCHECKED, 20, 4) /* bc8 offset24 */ \
V(LOAD_4_CURRENT_CHARS, 21, 8) /* bc8 offset24 addr32 */ \
V(LOAD_4_CURRENT_CHARS_UNCHECKED, 22, 4) /* bc8 offset24 */ \
V(CHECK_4_CHARS, 23, 12) /* bc8 pad24 uint32 addr32 */ \
V(CHECK_CHAR, 24, 8) /* bc8 pad8 uint16 addr32 */ \
/* Check if current character is equal to a given character */ \
/* Bit Layout: */ \
/* 0x00 - 0x07: 0x19 (fixed) Bytecode */ \
/* 0x08 - 0x0F: 0x00 (unused) Padding */ \
/* 0x10 - 0x1F: Character to check */ \
/* 0x20 - 0x3F: Address of bytecode when matched */ \
V(CHECK_CHAR, 24, 8) /* bc8 pad8 uint16 addr32 */ \
V(CHECK_NOT_4_CHARS, 25, 12) /* bc8 pad24 uint32 addr32 */ \
V(CHECK_NOT_CHAR, 26, 8) /* bc8 pad8 uint16 addr32 */ \
V(AND_CHECK_4_CHARS, 27, 16) /* bc8 pad24 uint32 uint32 addr32 */ \
V(AND_CHECK_CHAR, 28, 12) /* bc8 pad8 uint16 uint32 addr32 */ \
/* Checks if the current character combined with mask (bitwise and) */ \
/* matches a character (e.g. used when two characters in a disjunction */ \
/* differ by only a single bit */ \
/* Bit Layout: */ \
/* 0x00 - 0x07: 0x1c (fixed) Bytecode */ \
/* 0x08 - 0x0F: 0x00 (unused) Padding */ \
/* 0x10 - 0x1F: Character to match against (after mask aplied) */ \
/* 0x20 - 0x3F: Bitmask bitwise and combined with current character */ \
/* 0x40 - 0x5F: Address of bytecode when matched */ \
V(AND_CHECK_CHAR, 28, 12) /* bc8 pad8 uint16 uint32 addr32 */ \
V(AND_CHECK_NOT_4_CHARS, 29, 16) /* bc8 pad24 uint32 uint32 addr32 */ \
V(AND_CHECK_NOT_CHAR, 30, 12) /* bc8 pad8 uint16 uint32 addr32 */ \
V(MINUS_AND_CHECK_NOT_CHAR, 31, 12) /* bc8 pad8 uc16 uc16 uc16 addr32 */ \
V(CHECK_CHAR_IN_RANGE, 32, 12) /* bc8 pad24 uc16 uc16 addr32 */ \
V(CHECK_CHAR_NOT_IN_RANGE, 33, 12) /* bc8 pad24 uc16 uc16 addr32 */ \
V(CHECK_BIT_IN_TABLE, 34, 24) /* bc8 pad24 addr32 bits128 */ \
/* Checks if the current character matches any of the characters encoded */ \
/* in a bit table. Similar to/inspired by boyer moore string search */ \
/* Bit Layout: */ \
/* 0x00 - 0x07: 0x22 (fixed) Bytecode */ \
/* 0x08 - 0x1F: 0x00 (unused) Padding */ \
/* 0x20 - 0x3F: Address of bytecode when bit is set */ \
/* 0x40 - 0xBF: Bit table */ \
V(CHECK_BIT_IN_TABLE, 34, 24) /* bc8 pad24 addr32 bits128 */ \
V(CHECK_LT, 35, 8) /* bc8 pad8 uc16 addr32 */ \
V(CHECK_GT, 36, 8) /* bc8 pad8 uc16 addr32 */ \
V(CHECK_NOT_BACK_REF, 37, 8) /* bc8 reg_idx24 addr32 */ \
......@@ -74,10 +113,99 @@ STATIC_ASSERT(1 << BYTECODE_SHIFT > BYTECODE_MASK);
V(CHECK_REGISTER_EQ_POS, 46, 8) /* bc8 reg_idx24 addr32 */ \
V(CHECK_AT_START, 47, 8) /* bc8 pad24 addr32 */ \
V(CHECK_NOT_AT_START, 48, 8) /* bc8 offset24 addr32 */ \
/* Checks if the current position matches top of backtrack stack */ \
/* Bit Layout: */ \
/* 0x00 - 0x07: 0x31 (fixed) Bytecode */ \
/* 0x08 - 0x1F: 0x00 (unused) Padding */ \
/* 0x20 - 0x3F: Address of bytecode when current matches tos */ \
V(CHECK_GREEDY, 49, 8) /* bc8 pad24 addr32 */ \
V(ADVANCE_CP_AND_GOTO, 50, 8) /* bc8 offset24 addr32 */ \
/* Advance character pointer by given offset and jump to another bytecode.*/ \
/* Bit Layout: */ \
/* 0x00 - 0x07: 0x32 (fixed) Bytecode */ \
/* 0x08 - 0x1F: Number of characters to advance */ \
/* 0x20 - 0x3F: Address of bytecode to jump to */ \
V(ADVANCE_CP_AND_GOTO, 50, 8) /* bc8 offset24 addr32 */ \
V(SET_CURRENT_POSITION_FROM_END, 51, 4) /* bc8 idx24 */ \
V(CHECK_CURRENT_POSITION, 52, 8) /* bc8 idx24 addr32 */
/* Checks if current position + given offset is in range. */ \
/* Bit Layout: */ \
/* 0x00 - 0x07: 0x34 (fixed) Bytecode */ \
/* 0x08 - 0x1F: Offset from current position */ \
/* 0x20 - 0x3F: Address of bytecode when position is out of range */ \
V(CHECK_CURRENT_POSITION, 52, 8) /* bc8 idx24 addr32 */ \
/* Combination of: */ \
/* LOAD_CURRENT_CHAR, CHECK_BIT_IN_TABLE and ADVANCE_CP_AND_GOTO */ \
/* Emitted by RegExpBytecodePeepholeOptimization. */ \
/* Bit Layout: */ \
/* 0x00 - 0x07 0x35 (fixed) Bytecode */ \
/* 0x08 - 0x1F Load character offset from current position */ \
/* 0x20 - 0x3F Number of characters to advance */ \
/* 0x40 - 0xBF Bit Table */ \
/* 0xC0 - 0xDF Address of bytecode when character is matched */ \
/* 0xE0 - 0xFF Address of bytecode when no match */ \
V(SKIP_UNTIL_BIT_IN_TABLE, 53, 32) \
/* Combination of: */ \
/* CHECK_CURRENT_POSITION, LOAD_CURRENT_CHAR_UNCHECKED, AND_CHECK_CHAR */ \
/* and ADVANCE_CP_AND_GOTO */ \
/* Emitted by RegExpBytecodePeepholeOptimization. */ \
/* Bit Layout: */ \
/* 0x00 - 0x07 0x36 (fixed) Bytecode */ \
/* 0x08 - 0x1F Load character offset from current position */ \
/* 0x20 - 0x2F Number of characters to advance */ \
/* 0x30 - 0x3F Character to match against (after mask applied) */ \
/* 0x40 - 0x5F: Bitmask bitwise and combined with current character */ \
/* 0x60 - 0x7F Minimum number of characters this pattern consumes */ \
/* 0x80 - 0x9F Address of bytecode when character is matched */ \
/* 0xA0 - 0xBF Address of bytecode when no match */ \
V(SKIP_UNTIL_CHAR_AND, 54, 24) \
/* Combination of: */ \
/* LOAD_CURRENT_CHAR, CHECK_CHAR and ADVANCE_CP_AND_GOTO */ \
/* Emitted by RegExpBytecodePeepholeOptimization. */ \
/* Bit Layout: */ \
/* 0x00 - 0x07 0x37 (fixed) Bytecode */ \
/* 0x08 - 0x1F Load character offset from current position */ \
/* 0x20 - 0x2F Number of characters to advance */ \
/* 0x30 - 0x3F Character to match */ \
/* 0x40 - 0x5F Address of bytecode when character is matched */ \
/* 0x60 - 0x7F Address of bytecode when no match */ \
V(SKIP_UNTIL_CHAR, 55, 16) \
/* Combination of: */ \
/* CHECK_CURRENT_POSITION, LOAD_CURRENT_CHAR_UNCHECKED, CHECK_CHAR */ \
/* and ADVANCE_CP_AND_GOTO */ \
/* Emitted by RegExpBytecodePeepholeOptimization. */ \
/* Bit Layout: */ \
/* 0x00 - 0x07 0x38 (fixed) Bytecode */ \
/* 0x08 - 0x1F Load character offset from current position */ \
/* 0x20 - 0x2F Number of characters to advance */ \
/* 0x30 - 0x3F Character to match */ \
/* 0x40 - 0x5F Minimum number of characters this pattern consumes */ \
/* 0x60 - 0x7F Address of bytecode when character is matched */ \
/* 0x80 - 0x9F Address of bytecode when no match */ \
V(SKIP_UNTIL_CHAR_POS_CHECKED, 56, 20) \
/* Combination of: */ \
/* LOAD_CURRENT_CHAR, CHECK_CHAR, CHECK_CHAR and ADVANCE_CP_AND_GOTO */ \
/* Emitted by RegExpBytecodePeepholeOptimization. */ \
/* Bit Layout: */ \
/* 0x00 - 0x07 0x39 (fixed) Bytecode */ \
/* 0x08 - 0x1F Load character offset from current position */ \
/* 0x20 - 0x3F Number of characters to advance */ \
/* 0x40 - 0x4F Character to match */ \
/* 0x50 - 0x5F Other Character to match */ \
/* 0x60 - 0x7F Address of bytecode when either character is matched */ \
/* 0x80 - 0x9F Address of bytecode when no match */ \
V(SKIP_UNTIL_CHAR_OR_CHAR, 57, 20) \
/* Combination of: */ \
/* LOAD_CURRENT_CHAR, CHECK_GT, CHECK_BIT_IN_TABLE, GOTO and */ \
/* and ADVANCE_CP_AND_GOTO */ \
/* Emitted by RegExpBytecodePeepholeOptimization. */ \
/* Bit Layout: */ \
/* 0x00 - 0x07 0x3A (fixed) Bytecode */ \
/* 0x08 - 0x1F Load character offset from current position */ \
/* 0x20 - 0x2F Number of characters to advance */ \
/* 0x30 - 0x3F Character to check if it is less than current char */ \
/* 0x40 - 0xBF Bit Table */ \
/* 0xC0 - 0xDF Address of bytecode when character is matched */ \
/* 0xE0 - 0xFF Address of bytecode when no match */ \
V(SKIP_UNTIL_GT_OR_NOT_BIT_IN_TABLE, 58, 32)
#define COUNT(...) +1
static constexpr int kRegExpBytecodeCount = BYTECODE_ITERATOR(COUNT);
......@@ -87,7 +215,7 @@ static constexpr int kRegExpBytecodeCount = BYTECODE_ITERATOR(COUNT);
// contiguous, strictly increasing, and start at 0.
// TODO(jgruber): Do not explicitly assign values, instead generate them
// implicitly from the list order.
STATIC_ASSERT(kRegExpBytecodeCount == 53);
STATIC_ASSERT(kRegExpBytecodeCount == 59);
#define DECLARE_BYTECODES(name, code, length) \
static constexpr int BC_##name = code;
......@@ -114,6 +242,10 @@ inline const char* RegExpBytecodeName(int bytecode) {
return kRegExpBytecodeNames[bytecode];
}
void RegExpBytecodeDisassembleSingle(const byte* code_base, const byte* pc);
void RegExpBytecodeDisassemble(const byte* code_base, int length,
const char* pattern);
} // namespace internal
} // namespace v8
......
......@@ -64,23 +64,6 @@ bool BackRefMatchesNoCase(Isolate* isolate, int from, int current, int len,
return true;
}
void DisassembleSingleBytecode(const byte* code_base, const byte* pc) {
PrintF("%s", RegExpBytecodeName(*pc));
// Args and the bytecode as hex.
for (int i = 0; i < RegExpBytecodeLength(*pc); i++) {
PrintF(", %02x", pc[i]);
}
PrintF(" ");
// Args as ascii.
for (int i = 1; i < RegExpBytecodeLength(*pc); i++) {
unsigned char b = pc[i];
PrintF("%c", std::isprint(b) ? b : '.');
}
PrintF("\n");
}
#ifdef DEBUG
void MaybeTraceInterpreter(const byte* code_base, const byte* pc,
int stack_depth, int current_position,
......@@ -95,7 +78,7 @@ void MaybeTraceInterpreter(const byte* code_base, const byte* pc,
PrintF(format, pc - code_base, stack_depth, current_position, current_char,
printable ? current_char : '.');
DisassembleSingleBytecode(code_base, pc);
RegExpBytecodeDisassembleSingle(code_base, pc);
}
}
#endif // DEBUG
......@@ -257,6 +240,13 @@ IrregexpInterpreter::Result HandleInterrupts(
return IrregexpInterpreter::SUCCESS;
}
bool CheckBitInTable(const uint32_t current_char, const byte* const table) {
int mask = RegExpMacroAssembler::kTableMask;
int b = table[(current_char & mask) >> kBitsPerByteLog2];
int bit = (current_char & (kBitsPerByte - 1));
return (b & (1 << bit)) != 0;
}
// If computed gotos are supported by the compiler, we can get addresses to
// labels directly in C/C++. Every bytecode handler has its own label and we
// store the addresses in a dispatch table indexed by bytecode. To execute the
......@@ -281,7 +271,7 @@ IrregexpInterpreter::Result HandleInterrupts(
#define DISPATCH() \
pc = next_pc; \
insn = next_insn; \
break
goto switch_dispatch_continuation
#endif // V8_USE_COMPUTED_GOTO
// ADVANCE/SET_PC_FROM_OFFSET are separated from DISPATCH, because ideally some
......@@ -331,19 +321,13 @@ IrregexpInterpreter::Result RawMatch(Isolate* isolate, ByteArray code_array,
// Fill dispatch table from last defined bytecode up to the next power of two
// with BREAK (invalid operation).
// TODO(pthier): Find a way to fill up automatically (at compile time)
// 53 real bytecodes -> 11 fillers
// 59 real bytecodes -> 5 fillers
#define BYTECODE_FILLER_ITERATOR(V) \
V(BREAK) /* 1 */ \
V(BREAK) /* 2 */ \
V(BREAK) /* 3 */ \
V(BREAK) /* 4 */ \
V(BREAK) /* 5 */ \
V(BREAK) /* 6 */ \
V(BREAK) /* 7 */ \
V(BREAK) /* 8 */ \
V(BREAK) /* 9 */ \
V(BREAK) /* 10 */ \
V(BREAK) /* 11 */
V(BREAK) /* 5 */
#define COUNT(...) +1
static constexpr int kRegExpBytecodeFillerCount =
......@@ -652,10 +636,7 @@ IrregexpInterpreter::Result RawMatch(Isolate* isolate, ByteArray code_array,
DISPATCH();
}
BYTECODE(CHECK_BIT_IN_TABLE) {
int mask = RegExpMacroAssembler::kTableMask;
byte b = pc[8 + ((current_char & mask) >> kBitsPerByteLog2)];
int bit = (current_char & (kBitsPerByte - 1));
if ((b & (1 << bit)) != 0) {
if (CheckBitInTable(current_char, pc + 8)) {
SET_PC_FROM_OFFSET(Load32Aligned(pc + 4));
} else {
ADVANCE(CHECK_BIT_IN_TABLE);
......@@ -834,6 +815,118 @@ IrregexpInterpreter::Result RawMatch(Isolate* isolate, ByteArray code_array,
}
DISPATCH();
}
BYTECODE(SKIP_UNTIL_CHAR) {
int load_offset = (insn >> BYTECODE_SHIFT);
uint32_t advance = Load16Aligned(pc + 4);
uint32_t c = Load16Aligned(pc + 6);
while (static_cast<uintptr_t>(current + load_offset) <
static_cast<uintptr_t>(subject.length())) {
current_char = subject[current + load_offset];
if (c == current_char) {
SET_PC_FROM_OFFSET(Load32Aligned(pc + 8));
DISPATCH();
}
current += advance;
}
SET_PC_FROM_OFFSET(Load32Aligned(pc + 12));
DISPATCH();
}
BYTECODE(SKIP_UNTIL_CHAR_AND) {
int load_offset = (insn >> BYTECODE_SHIFT);
uint16_t advance = Load16Aligned(pc + 4);
uint16_t c = Load16Aligned(pc + 6);
uint32_t mask = Load32Aligned(pc + 8);
int32_t maximum_offset = Load32Aligned(pc + 12);
while (static_cast<uintptr_t>(current + maximum_offset) <=
static_cast<uintptr_t>(subject.length())) {
current_char = subject[current + load_offset];
if (c == (current_char & mask)) {
SET_PC_FROM_OFFSET(Load32Aligned(pc + 16));
DISPATCH();
}
current += advance;
}
SET_PC_FROM_OFFSET(Load32Aligned(pc + 20));
DISPATCH();
}
BYTECODE(SKIP_UNTIL_CHAR_POS_CHECKED) {
int load_offset = (insn >> BYTECODE_SHIFT);
uint16_t advance = Load16Aligned(pc + 4);
uint16_t c = Load16Aligned(pc + 6);
int32_t maximum_offset = Load32Aligned(pc + 8);
while (static_cast<uintptr_t>(current + maximum_offset) <=
static_cast<uintptr_t>(subject.length())) {
current_char = subject[current + load_offset];
if (c == current_char) {
SET_PC_FROM_OFFSET(Load32Aligned(pc + 12));
DISPATCH();
}
current += advance;
}
SET_PC_FROM_OFFSET(Load32Aligned(pc + 16));
DISPATCH();
}
BYTECODE(SKIP_UNTIL_BIT_IN_TABLE) {
int load_offset = (insn >> BYTECODE_SHIFT);
uint32_t advance = Load16Aligned(pc + 4);
const byte* table = pc + 8;
while (static_cast<uintptr_t>(current + load_offset) <
static_cast<uintptr_t>(subject.length())) {
current_char = subject[current + load_offset];
if (CheckBitInTable(current_char, table)) {
SET_PC_FROM_OFFSET(Load32Aligned(pc + 24));
DISPATCH();
}
current += advance;
}
SET_PC_FROM_OFFSET(Load32Aligned(pc + 28));
DISPATCH();
}
BYTECODE(SKIP_UNTIL_GT_OR_NOT_BIT_IN_TABLE) {
int load_offset = (insn >> BYTECODE_SHIFT);
uint16_t advance = Load16Aligned(pc + 4);
uint16_t limit = Load16Aligned(pc + 6);
const byte* table = pc + 8;
while (static_cast<uintptr_t>(current + load_offset) <
static_cast<uintptr_t>(subject.length())) {
current_char = subject[current + load_offset];
if (current_char > limit) {
SET_PC_FROM_OFFSET(Load32Aligned(pc + 24));
DISPATCH();
}
if (!CheckBitInTable(current_char, table)) {
SET_PC_FROM_OFFSET(Load32Aligned(pc + 24));
DISPATCH();
}
current += advance;
}
SET_PC_FROM_OFFSET(Load32Aligned(pc + 28));
DISPATCH();
}
BYTECODE(SKIP_UNTIL_CHAR_OR_CHAR) {
int load_offset = (insn >> BYTECODE_SHIFT);
uint32_t advance = Load16Aligned(pc + 4);
uint16_t c = Load16Aligned(pc + 8);
uint16_t c2 = Load16Aligned(pc + 10);
while (static_cast<uintptr_t>(current + load_offset) <
static_cast<uintptr_t>(subject.length())) {
current_char = subject[current + load_offset];
// The two if-statements below are split up intentionally, as combining
// them seems to result in register allocation behaving quite
// differently and slowing down the resulting code.
if (c == current_char) {
SET_PC_FROM_OFFSET(Load32Aligned(pc + 12));
DISPATCH();
}
if (c2 == current_char) {
SET_PC_FROM_OFFSET(Load32Aligned(pc + 12));
DISPATCH();
}
current += advance;
}
SET_PC_FROM_OFFSET(Load32Aligned(pc + 16));
DISPATCH();
}
#if V8_USE_COMPUTED_GOTO
// Lint gets confused a lot if we just use !V8_USE_COMPUTED_GOTO or ifndef
// V8_USE_COMPUTED_GOTO here.
......@@ -841,6 +934,9 @@ IrregexpInterpreter::Result RawMatch(Isolate* isolate, ByteArray code_array,
default:
UNREACHABLE();
}
// Label we jump to in DISPATCH(). There must be no instructions between the
// end of the switch, this label and the end of the loop.
switch_dispatch_continuation : {}
#endif // V8_USE_COMPUTED_GOTO
}
}
......@@ -855,25 +951,6 @@ IrregexpInterpreter::Result RawMatch(Isolate* isolate, ByteArray code_array,
} // namespace
// static
void IrregexpInterpreter::Disassemble(ByteArray byte_array,
const std::string& pattern) {
DisallowHeapAllocation no_gc;
PrintF("[generated bytecode for regexp pattern: '%s']\n", pattern.c_str());
const byte* const code_base = byte_array.GetDataStartAddress();
const int byte_array_length = byte_array.length();
ptrdiff_t offset = 0;
while (offset < byte_array_length) {
const byte* const pc = code_base + offset;
PrintF("%p %4" V8PRIxPTRDIFF " ", pc, offset);
DisassembleSingleBytecode(code_base, pc);
offset += RegExpBytecodeLength(*pc);
}
}
// static
IrregexpInterpreter::Result IrregexpInterpreter::Match(
Isolate* isolate, JSRegExp regexp, String subject_string, int* registers,
......
......@@ -46,8 +46,6 @@ class V8_EXPORT_PRIVATE IrregexpInterpreter : public AllStatic {
int registers_length, int start_position,
RegExp::CallOrigin call_origin);
static void Disassemble(ByteArray byte_array, const std::string& pattern);
private:
static Result Match(Isolate* isolate, JSRegExp regexp, String subject_string,
int* registers, int registers_length, int start_position,
......
......@@ -9,6 +9,7 @@
#include "src/heap/heap-inl.h"
#include "src/objects/js-regexp-inl.h"
#include "src/regexp/regexp-bytecode-generator.h"
#include "src/regexp/regexp-bytecodes.h"
#include "src/regexp/regexp-compiler.h"
#include "src/regexp/regexp-dotprinter.h"
#include "src/regexp/regexp-interpreter.h"
......@@ -867,7 +868,8 @@ bool RegExpImpl::Compile(Isolate* isolate, Zone* zone, RegExpCompileData* data,
data->compilation_target == RegExpCompilationTarget::kBytecode) {
Handle<ByteArray> bytecode(ByteArray::cast(result.code), isolate);
auto pattern_cstring = pattern->ToCString();
IrregexpInterpreter::Disassemble(*bytecode, pattern_cstring.get());
RegExpBytecodeDisassemble(bytecode->GetDataStartAddress(),
bytecode->length(), pattern_cstring.get());
}
}
......
......@@ -38,6 +38,7 @@
#include "src/objects/js-regexp-inl.h"
#include "src/objects/objects-inl.h"
#include "src/regexp/regexp-bytecode-generator.h"
#include "src/regexp/regexp-bytecodes.h"
#include "src/regexp/regexp-compiler.h"
#include "src/regexp/regexp-interpreter.h"
#include "src/regexp/regexp-macro-assembler-arch.h"
......@@ -1783,6 +1784,567 @@ TEST(UncachedExternalString) {
ExpectString("external.substring(1).match(re)[1]", "z");
}
// Test bytecode peephole optimization
void CreatePeepholeNoChangeBytecode(RegExpMacroAssembler* m) {
Label fail, backtrack;
m->PushBacktrack(&fail);
m->CheckNotAtStart(0, nullptr);
m->LoadCurrentCharacter(2, nullptr);
m->CheckNotCharacter('o', nullptr);
m->LoadCurrentCharacter(1, nullptr, false);
m->CheckNotCharacter('o', nullptr);
m->LoadCurrentCharacter(0, nullptr, false);
m->CheckNotCharacter('f', nullptr);
m->WriteCurrentPositionToRegister(0, 0);
m->WriteCurrentPositionToRegister(1, 3);
m->AdvanceCurrentPosition(3);
m->PushBacktrack(&backtrack);
m->Succeed();
m->Bind(&backtrack);
m->Backtrack();
m->Bind(&fail);
m->Fail();
}
TEST(PeepholeNoChange) {
Zone zone(CcTest::i_isolate()->allocator(), ZONE_NAME);
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
HandleScope scope(isolate);
RegExpBytecodeGenerator orig(CcTest::i_isolate(), &zone);
RegExpBytecodeGenerator opt(CcTest::i_isolate(), &zone);
CreatePeepholeNoChangeBytecode(&orig);
CreatePeepholeNoChangeBytecode(&opt);
Handle<String> source = factory->NewStringFromStaticChars("^foo");
i::FLAG_regexp_peephole_optimization = false;
Handle<ByteArray> array = Handle<ByteArray>::cast(orig.GetCode(source));
int length = array->length();
byte* byte_array = array->GetDataStartAddress();
i::FLAG_regexp_peephole_optimization = true;
Handle<ByteArray> array_optimized =
Handle<ByteArray>::cast(opt.GetCode(source));
byte* byte_array_optimized = array_optimized->GetDataStartAddress();
CHECK_EQ(0, memcmp(byte_array, byte_array_optimized, length));
}
void CreatePeepholeSkipUntilCharBytecode(RegExpMacroAssembler* m) {
Label start;
m->Bind(&start);
m->LoadCurrentCharacter(0, nullptr, true);
m->CheckCharacter('x', nullptr);
m->AdvanceCurrentPosition(1);
m->GoTo(&start);
}
TEST(PeepholeSkipUntilChar) {
Zone zone(CcTest::i_isolate()->allocator(), ZONE_NAME);
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
HandleScope scope(isolate);
RegExpBytecodeGenerator orig(CcTest::i_isolate(), &zone);
RegExpBytecodeGenerator opt(CcTest::i_isolate(), &zone);
CreatePeepholeSkipUntilCharBytecode(&orig);
CreatePeepholeSkipUntilCharBytecode(&opt);
Handle<String> source = factory->NewStringFromStaticChars("dummy");
i::FLAG_regexp_peephole_optimization = false;
Handle<ByteArray> array = Handle<ByteArray>::cast(orig.GetCode(source));
int length = array->length();
i::FLAG_regexp_peephole_optimization = true;
Handle<ByteArray> array_optimized =
Handle<ByteArray>::cast(opt.GetCode(source));
int length_optimized = array_optimized->length();
int length_expected = RegExpBytecodeLength(BC_LOAD_CURRENT_CHAR) +
RegExpBytecodeLength(BC_CHECK_CHAR) +
RegExpBytecodeLength(BC_ADVANCE_CP_AND_GOTO) +
RegExpBytecodeLength(BC_POP_BT);
int length_optimized_expected = RegExpBytecodeLength(BC_SKIP_UNTIL_CHAR) +
RegExpBytecodeLength(BC_POP_BT);
CHECK_EQ(length, length_expected);
CHECK_EQ(length_optimized, length_optimized_expected);
CHECK_EQ(BC_SKIP_UNTIL_CHAR, array_optimized->get(0));
CHECK_EQ(BC_POP_BT,
array_optimized->get(RegExpBytecodeLength(BC_SKIP_UNTIL_CHAR)));
}
void CreatePeepholeSkipUntilBitInTableBytecode(RegExpMacroAssembler* m,
Factory* factory) {
Handle<ByteArray> bit_table = factory->NewByteArray(
RegExpMacroAssembler::kTableSize, AllocationType::kOld);
for (uint32_t i = 0; i < RegExpMacroAssembler::kTableSize; i++) {
bit_table->set(i, 0);
}
Label start;
m->Bind(&start);
m->LoadCurrentCharacter(0, nullptr, true);
m->CheckBitInTable(bit_table, nullptr);
m->AdvanceCurrentPosition(1);
m->GoTo(&start);
}
TEST(PeepholeSkipUntilBitInTable) {
Zone zone(CcTest::i_isolate()->allocator(), ZONE_NAME);
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
HandleScope scope(isolate);
RegExpBytecodeGenerator orig(CcTest::i_isolate(), &zone);
RegExpBytecodeGenerator opt(CcTest::i_isolate(), &zone);
CreatePeepholeSkipUntilBitInTableBytecode(&orig, factory);
CreatePeepholeSkipUntilBitInTableBytecode(&opt, factory);
Handle<String> source = factory->NewStringFromStaticChars("dummy");
i::FLAG_regexp_peephole_optimization = false;
Handle<ByteArray> array = Handle<ByteArray>::cast(orig.GetCode(source));
int length = array->length();
i::FLAG_regexp_peephole_optimization = true;
Handle<ByteArray> array_optimized =
Handle<ByteArray>::cast(opt.GetCode(source));
int length_optimized = array_optimized->length();
int length_expected = RegExpBytecodeLength(BC_LOAD_CURRENT_CHAR) +
RegExpBytecodeLength(BC_CHECK_BIT_IN_TABLE) +
RegExpBytecodeLength(BC_ADVANCE_CP_AND_GOTO) +
RegExpBytecodeLength(BC_POP_BT);
int length_optimized_expected =
RegExpBytecodeLength(BC_SKIP_UNTIL_BIT_IN_TABLE) +
RegExpBytecodeLength(BC_POP_BT);
CHECK_EQ(length, length_expected);
CHECK_EQ(length_optimized, length_optimized_expected);
CHECK_EQ(BC_SKIP_UNTIL_BIT_IN_TABLE, array_optimized->get(0));
CHECK_EQ(BC_POP_BT, array_optimized->get(
RegExpBytecodeLength(BC_SKIP_UNTIL_BIT_IN_TABLE)));
}
void CreatePeepholeSkipUntilCharPosCheckedBytecode(RegExpMacroAssembler* m) {
Label start;
m->Bind(&start);
m->LoadCurrentCharacter(0, nullptr, true, 1, 2);
m->CheckCharacter('x', nullptr);
m->AdvanceCurrentPosition(1);
m->GoTo(&start);
}
TEST(PeepholeSkipUntilCharPosChecked) {
Zone zone(CcTest::i_isolate()->allocator(), ZONE_NAME);
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
HandleScope scope(isolate);
RegExpBytecodeGenerator orig(CcTest::i_isolate(), &zone);
RegExpBytecodeGenerator opt(CcTest::i_isolate(), &zone);
CreatePeepholeSkipUntilCharPosCheckedBytecode(&orig);
CreatePeepholeSkipUntilCharPosCheckedBytecode(&opt);
Handle<String> source = factory->NewStringFromStaticChars("dummy");
i::FLAG_regexp_peephole_optimization = false;
Handle<ByteArray> array = Handle<ByteArray>::cast(orig.GetCode(source));
int length = array->length();
i::FLAG_regexp_peephole_optimization = true;
Handle<ByteArray> array_optimized =
Handle<ByteArray>::cast(opt.GetCode(source));
int length_optimized = array_optimized->length();
int length_expected = RegExpBytecodeLength(BC_CHECK_CURRENT_POSITION) +
RegExpBytecodeLength(BC_LOAD_CURRENT_CHAR_UNCHECKED) +
RegExpBytecodeLength(BC_CHECK_CHAR) +
RegExpBytecodeLength(BC_ADVANCE_CP_AND_GOTO) +
RegExpBytecodeLength(BC_POP_BT);
int length_optimized_expected =
RegExpBytecodeLength(BC_SKIP_UNTIL_CHAR_POS_CHECKED) +
RegExpBytecodeLength(BC_POP_BT);
CHECK_EQ(length, length_expected);
CHECK_EQ(length_optimized, length_optimized_expected);
CHECK_EQ(BC_SKIP_UNTIL_CHAR_POS_CHECKED, array_optimized->get(0));
CHECK_EQ(BC_POP_BT, array_optimized->get(RegExpBytecodeLength(
BC_SKIP_UNTIL_CHAR_POS_CHECKED)));
}
void CreatePeepholeSkipUntilCharAndBytecode(RegExpMacroAssembler* m) {
Label start;
m->Bind(&start);
m->LoadCurrentCharacter(0, nullptr, true, 1, 2);
m->CheckCharacterAfterAnd('x', 0xFF, nullptr);
m->AdvanceCurrentPosition(1);
m->GoTo(&start);
}
TEST(PeepholeSkipUntilCharAnd) {
Zone zone(CcTest::i_isolate()->allocator(), ZONE_NAME);
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
HandleScope scope(isolate);
RegExpBytecodeGenerator orig(CcTest::i_isolate(), &zone);
RegExpBytecodeGenerator opt(CcTest::i_isolate(), &zone);
CreatePeepholeSkipUntilCharAndBytecode(&orig);
CreatePeepholeSkipUntilCharAndBytecode(&opt);
Handle<String> source = factory->NewStringFromStaticChars("dummy");
i::FLAG_regexp_peephole_optimization = false;
Handle<ByteArray> array = Handle<ByteArray>::cast(orig.GetCode(source));
int length = array->length();
i::FLAG_regexp_peephole_optimization = true;
Handle<ByteArray> array_optimized =
Handle<ByteArray>::cast(opt.GetCode(source));
int length_optimized = array_optimized->length();
int length_expected = RegExpBytecodeLength(BC_CHECK_CURRENT_POSITION) +
RegExpBytecodeLength(BC_LOAD_CURRENT_CHAR_UNCHECKED) +
RegExpBytecodeLength(BC_AND_CHECK_CHAR) +
RegExpBytecodeLength(BC_ADVANCE_CP_AND_GOTO) +
RegExpBytecodeLength(BC_POP_BT);
int length_optimized_expected = RegExpBytecodeLength(BC_SKIP_UNTIL_CHAR_AND) +
RegExpBytecodeLength(BC_POP_BT);
CHECK_EQ(length, length_expected);
CHECK_EQ(length_optimized, length_optimized_expected);
CHECK_EQ(BC_SKIP_UNTIL_CHAR_AND, array_optimized->get(0));
CHECK_EQ(BC_POP_BT,
array_optimized->get(RegExpBytecodeLength(BC_SKIP_UNTIL_CHAR_AND)));
}
void CreatePeepholeSkipUntilCharOrCharBytecode(RegExpMacroAssembler* m) {
Label start;
m->Bind(&start);
m->LoadCurrentCharacter(0, nullptr, true);
m->CheckCharacter('x', nullptr);
m->CheckCharacter('y', nullptr);
m->AdvanceCurrentPosition(1);
m->GoTo(&start);
}
TEST(PeepholeSkipUntilCharOrChar) {
Zone zone(CcTest::i_isolate()->allocator(), ZONE_NAME);
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
HandleScope scope(isolate);
RegExpBytecodeGenerator orig(CcTest::i_isolate(), &zone);
RegExpBytecodeGenerator opt(CcTest::i_isolate(), &zone);
CreatePeepholeSkipUntilCharOrCharBytecode(&orig);
CreatePeepholeSkipUntilCharOrCharBytecode(&opt);
Handle<String> source = factory->NewStringFromStaticChars("dummy");
i::FLAG_regexp_peephole_optimization = false;
Handle<ByteArray> array = Handle<ByteArray>::cast(orig.GetCode(source));
int length = array->length();
i::FLAG_regexp_peephole_optimization = true;
Handle<ByteArray> array_optimized =
Handle<ByteArray>::cast(opt.GetCode(source));
int length_optimized = array_optimized->length();
int length_expected = RegExpBytecodeLength(BC_LOAD_CURRENT_CHAR) +
RegExpBytecodeLength(BC_CHECK_CHAR) +
RegExpBytecodeLength(BC_CHECK_CHAR) +
RegExpBytecodeLength(BC_ADVANCE_CP_AND_GOTO) +
RegExpBytecodeLength(BC_POP_BT);
int length_optimized_expected =
RegExpBytecodeLength(BC_SKIP_UNTIL_CHAR_OR_CHAR) +
RegExpBytecodeLength(BC_POP_BT);
CHECK_EQ(length, length_expected);
CHECK_EQ(length_optimized, length_optimized_expected);
CHECK_EQ(BC_SKIP_UNTIL_CHAR_OR_CHAR, array_optimized->get(0));
CHECK_EQ(BC_POP_BT, array_optimized->get(
RegExpBytecodeLength(BC_SKIP_UNTIL_CHAR_OR_CHAR)));
}
void CreatePeepholeSkipUntilGtOrNotBitInTableBytecode(RegExpMacroAssembler* m,
Factory* factory) {
Handle<ByteArray> bit_table = factory->NewByteArray(
RegExpMacroAssembler::kTableSize, AllocationType::kOld);
for (uint32_t i = 0; i < RegExpMacroAssembler::kTableSize; i++) {
bit_table->set(i, 0);
}
Label start, end, advance;
m->Bind(&start);
m->LoadCurrentCharacter(0, nullptr, true);
m->CheckCharacterGT('x', nullptr);
m->CheckBitInTable(bit_table, &advance);
m->GoTo(&end);
m->Bind(&advance);
m->AdvanceCurrentPosition(1);
m->GoTo(&start);
m->Bind(&end);
}
TEST(PeepholeSkipUntilGtOrNotBitInTable) {
Zone zone(CcTest::i_isolate()->allocator(), ZONE_NAME);
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
HandleScope scope(isolate);
RegExpBytecodeGenerator orig(CcTest::i_isolate(), &zone);
RegExpBytecodeGenerator opt(CcTest::i_isolate(), &zone);
CreatePeepholeSkipUntilGtOrNotBitInTableBytecode(&orig, factory);
CreatePeepholeSkipUntilGtOrNotBitInTableBytecode(&opt, factory);
Handle<String> source = factory->NewStringFromStaticChars("dummy");
i::FLAG_regexp_peephole_optimization = false;
Handle<ByteArray> array = Handle<ByteArray>::cast(orig.GetCode(source));
int length = array->length();
i::FLAG_regexp_peephole_optimization = true;
Handle<ByteArray> array_optimized =
Handle<ByteArray>::cast(opt.GetCode(source));
int length_optimized = array_optimized->length();
int length_expected = RegExpBytecodeLength(BC_LOAD_CURRENT_CHAR) +
RegExpBytecodeLength(BC_CHECK_GT) +
RegExpBytecodeLength(BC_CHECK_BIT_IN_TABLE) +
RegExpBytecodeLength(BC_GOTO) +
RegExpBytecodeLength(BC_ADVANCE_CP_AND_GOTO) +
RegExpBytecodeLength(BC_POP_BT);
int length_optimized_expected =
RegExpBytecodeLength(BC_SKIP_UNTIL_GT_OR_NOT_BIT_IN_TABLE) +
RegExpBytecodeLength(BC_POP_BT);
CHECK_EQ(length, length_expected);
CHECK_EQ(length_optimized, length_optimized_expected);
CHECK_EQ(BC_SKIP_UNTIL_GT_OR_NOT_BIT_IN_TABLE, array_optimized->get(0));
CHECK_EQ(BC_POP_BT, array_optimized->get(RegExpBytecodeLength(
BC_SKIP_UNTIL_GT_OR_NOT_BIT_IN_TABLE)));
}
void CreatePeepholeLabelFixupsInsideBytecode(RegExpMacroAssembler* m,
Label* dummy_before,
Label* dummy_after,
Label* dummy_inside) {
Label loop;
m->Bind(dummy_before);
m->LoadCurrentCharacter(0, dummy_before);
m->CheckCharacter('a', dummy_after);
m->CheckCharacter('b', dummy_inside);
m->Bind(&loop);
m->LoadCurrentCharacter(0, nullptr, true);
m->CheckCharacter('x', nullptr);
m->Bind(dummy_inside);
m->CheckCharacter('y', nullptr);
m->AdvanceCurrentPosition(1);
m->GoTo(&loop);
m->Bind(dummy_after);
m->LoadCurrentCharacter(0, dummy_before);
m->CheckCharacter('a', dummy_after);
m->CheckCharacter('b', dummy_inside);
}
TEST(PeepholeLabelFixupsInside) {
Zone zone(CcTest::i_isolate()->allocator(), ZONE_NAME);
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
HandleScope scope(isolate);
RegExpBytecodeGenerator orig(CcTest::i_isolate(), &zone);
RegExpBytecodeGenerator opt(CcTest::i_isolate(), &zone);
{
Label dummy_before, dummy_after, dummy_inside;
CreatePeepholeLabelFixupsInsideBytecode(&opt, &dummy_before, &dummy_after,
&dummy_inside);
}
Label dummy_before, dummy_after, dummy_inside;
CreatePeepholeLabelFixupsInsideBytecode(&orig, &dummy_before, &dummy_after,
&dummy_inside);
CHECK_EQ(0x00, dummy_before.pos());
CHECK_EQ(0x28, dummy_inside.pos());
CHECK_EQ(0x38, dummy_after.pos());
const Label* labels[] = {&dummy_before, &dummy_after, &dummy_inside};
const int label_positions[4][3] = {
{0x04, 0x3C}, // dummy_before
{0x0C, 0x44}, // dummy after
{0x14, 0x4C} // dummy inside
};
Handle<String> source = factory->NewStringFromStaticChars("dummy");
i::FLAG_regexp_peephole_optimization = false;
Handle<ByteArray> array = Handle<ByteArray>::cast(orig.GetCode(source));
for (int label_idx = 0; label_idx < 3; label_idx++) {
for (int pos_idx = 0; pos_idx < 2; pos_idx++) {
CHECK_EQ(labels[label_idx]->pos(),
array->get(label_positions[label_idx][pos_idx]));
}
}
i::FLAG_regexp_peephole_optimization = true;
Handle<ByteArray> array_optimized =
Handle<ByteArray>::cast(opt.GetCode(source));
const int pos_fixups[] = {
0, // Position before optimization should be unchanged.
4, // Position after first replacement should be 4 (optimized size (20) -
// original size (32) + preserve length (16)).
};
const int target_fixups[] = {
0, // dummy_before should be unchanged
4, // dummy_inside should be 4
4 // dummy_after should be 4
};
for (int label_idx = 0; label_idx < 3; label_idx++) {
for (int pos_idx = 0; pos_idx < 2; pos_idx++) {
int label_pos = label_positions[label_idx][pos_idx] + pos_fixups[pos_idx];
int jump_address = *reinterpret_cast<uint32_t*>(
array_optimized->GetDataStartAddress() + label_pos);
int expected_jump_address =
labels[label_idx]->pos() + target_fixups[label_idx];
CHECK_EQ(expected_jump_address, jump_address);
}
}
}
void CreatePeepholeLabelFixupsComplexBytecode(RegExpMacroAssembler* m,
Label* dummy_before,
Label* dummy_between,
Label* dummy_after,
Label* dummy_inside) {
Label loop1, loop2;
m->Bind(dummy_before);
m->LoadCurrentCharacter(0, dummy_before);
m->CheckCharacter('a', dummy_between);
m->CheckCharacter('b', dummy_after);
m->CheckCharacter('c', dummy_inside);
m->Bind(&loop1);
m->LoadCurrentCharacter(0, nullptr, true);
m->CheckCharacter('x', nullptr);
m->CheckCharacter('y', nullptr);
m->AdvanceCurrentPosition(1);
m->GoTo(&loop1);
m->Bind(dummy_between);
m->LoadCurrentCharacter(0, dummy_before);
m->CheckCharacter('a', dummy_between);
m->CheckCharacter('b', dummy_after);
m->CheckCharacter('c', dummy_inside);
m->Bind(&loop2);
m->LoadCurrentCharacter(0, nullptr, true);
m->CheckCharacter('x', nullptr);
m->Bind(dummy_inside);
m->CheckCharacter('y', nullptr);
m->AdvanceCurrentPosition(1);
m->GoTo(&loop2);
m->Bind(dummy_after);
m->LoadCurrentCharacter(0, dummy_before);
m->CheckCharacter('a', dummy_between);
m->CheckCharacter('b', dummy_after);
m->CheckCharacter('c', dummy_inside);
}
TEST(PeepholeLabelFixupsComplex) {
Zone zone(CcTest::i_isolate()->allocator(), ZONE_NAME);
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
HandleScope scope(isolate);
RegExpBytecodeGenerator orig(CcTest::i_isolate(), &zone);
RegExpBytecodeGenerator opt(CcTest::i_isolate(), &zone);
{
Label dummy_before, dummy_between, dummy_after, dummy_inside;
CreatePeepholeLabelFixupsComplexBytecode(
&opt, &dummy_before, &dummy_between, &dummy_after, &dummy_inside);
}
Label dummy_before, dummy_between, dummy_after, dummy_inside;
CreatePeepholeLabelFixupsComplexBytecode(&orig, &dummy_before, &dummy_between,
&dummy_after, &dummy_inside);
CHECK_EQ(0x00, dummy_before.pos());
CHECK_EQ(0x40, dummy_between.pos());
CHECK_EQ(0x70, dummy_inside.pos());
CHECK_EQ(0x80, dummy_after.pos());
const Label* labels[] = {&dummy_before, &dummy_between, &dummy_after,
&dummy_inside};
const int label_positions[4][3] = {
{0x04, 0x44, 0x84}, // dummy_before
{0x0C, 0x4C, 0x8C}, // dummy between
{0x14, 0x54, 0x94}, // dummy after
{0x1C, 0x5C, 0x9C} // dummy inside
};
Handle<String> source = factory->NewStringFromStaticChars("dummy");
i::FLAG_regexp_peephole_optimization = false;
Handle<ByteArray> array = Handle<ByteArray>::cast(orig.GetCode(source));
for (int label_idx = 0; label_idx < 4; label_idx++) {
for (int pos_idx = 0; pos_idx < 3; pos_idx++) {
CHECK_EQ(labels[label_idx]->pos(),
array->get(label_positions[label_idx][pos_idx]));
}
}
i::FLAG_regexp_peephole_optimization = true;
Handle<ByteArray> array_optimized =
Handle<ByteArray>::cast(opt.GetCode(source));
const int pos_fixups[] = {
0, // Position before optimization should be unchanged.
-12, // Position after first replacement should be -12 (optimized size =
// 20 - 32 = original size).
-8 // Position after second replacement should be -8 (-12 from first
// optimization -12 from second optimization + 16 preserved
// bytecodes).
};
const int target_fixups[] = {
0, // dummy_before should be unchanged
-12, // dummy_between should be -12
-8, // dummy_inside should be -8
-8 // dummy_after should be -8
};
for (int label_idx = 0; label_idx < 4; label_idx++) {
for (int pos_idx = 0; pos_idx < 3; pos_idx++) {
int label_pos = label_positions[label_idx][pos_idx] + pos_fixups[pos_idx];
int jump_address = *reinterpret_cast<uint32_t*>(
array_optimized->GetDataStartAddress() + label_pos);
int expected_jump_address =
labels[label_idx]->pos() + target_fixups[label_idx];
CHECK_EQ(expected_jump_address, jump_address);
}
}
}
#undef CHECK_PARSE_ERROR
#undef CHECK_SIMPLE
#undef CHECK_MIN_MAX
......
#!/usr/bin/env python
# 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.
"""
python %prog trace-file
Parses output generated by v8 with flag --trace-regexp-bytecodes and generates
a list of the most common sequences.
"""
from __future__ import print_function
import sys
import re
import collections
def parse(file, seqlen):
# example:
# pc = 00, sp = 0, curpos = 0, curchar = 0000000a ..., bc = PUSH_BT, 02, 00, 00, 00, e8, 00, 00, 00 .......
rx = re.compile(r'pc = (?P<pc>[0-9a-f]+), sp = (?P<sp>\d+), '
r'curpos = (?P<curpos>\d+), curchar = (?P<char_hex>[0-9a-f]+) '
r'(:?\.|\()(?P<char>\.|\w)(:?\.|\)), bc = (?P<bc>\w+), .*')
total = 0
bc_cnt = [None] * seqlen
for i in xrange(seqlen):
bc_cnt[i] = {}
last = [None] * seqlen
with open(file) as f:
l = f.readline()
while l:
l = l.strip()
if l.startswith("Start bytecode interpreter"):
for i in xrange(seqlen):
last[i] = collections.deque(maxlen=i+1)
match = rx.search(l)
if match:
total += 1
bc = match.group('bc')
for i in xrange(seqlen):
last[i].append(bc)
key = ' --> '.join(last[i])
bc_cnt[i][key] = bc_cnt[i].get(key,0) + 1
l = f.readline()
return bc_cnt, total
def print_most_common(d, seqlen, total):
sorted_d = sorted(d.items(), key=lambda kv: kv[1], reverse=True)
for (k,v) in sorted_d:
if v*100/total < 1.0:
return
print("{}: {} ({} %)".format(k,v,(v*100/total)))
def main(argv):
max_seq = 7
bc_cnt, total = parse(argv[1],max_seq)
for i in xrange(max_seq):
print()
print("Most common of length {}".format(i+1))
print()
print_most_common(bc_cnt[i], i, total)
if __name__ == '__main__':
main(sys.argv)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment