Commit 282a74c7 authored by Jakob Gruber's avatar Jakob Gruber Committed by Commit Bot

Reland "[regexp] Bytecode peephole optimization"

This is a reland of 66129430

Fixed: Unaligned reads, unspecified evaluation order.

Original change's description:
> [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: Peter Marshall <petermarshall@chromium.org>
> Reviewed-by: Jakob Gruber <jgruber@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#63992}

Cq-Include-Trybots: luci.v8.try:v8_linux64_ubsan_rel_ng
Cq-Include-Trybots: luci.v8.try:v8_linux_gcc_rel
Bug: v8:9330,chromium:1008502,chromium:1008631
Change-Id: Ib9fc395b6809aa1debdb54d9fba5b7f09a235e5b
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1828917Reviewed-by: 's avatarPeter Marshall <petermarshall@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#64064}
parent 14ffd21d
......@@ -2778,6 +2778,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) {
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_(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());
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
#error Big-endian support is not yet implemented.
#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
#error Big-endian support is not yet implemented.
#else
DCHECK_EQ(prev_val & 0xFFFFFF00, 0);
OverwriteValue<uint32_t>(pc() - sizeof(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
// 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 = Load32Aligned(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"
......@@ -881,7 +882,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