Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/js/src/irregexp/imported/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 46 kB image not shown  

Quelle  regexp-bytecode-peephole.cc   Sprache: C

 

// 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 "irregexp/imported/regexp-bytecode-peephole.h"

#include <limits>
#include <memory>
#include <optional>
#include <unordered_map>
#include <vector>

#include "irregexp/imported/regexp-bytecode-generator-inl.h"
#include "irregexp/imported/regexp-bytecode-generator.h"
#include "irregexp/imported/regexp-bytecodes-inl.h"
#include "irregexp/imported/regexp-bytecodes.h"

namespace v8 {
namespace internal {
namespace regexp {

namespace {

class BytecodeArgument {
 public:
  BytecodeArgument(int offset, int length) : offset_(offset), length_(length) {}

  int offset() const { return offset_; }
  int length() const { return length_; }

 private:
  // TODO(jgruber): This should store {offset,type} as well.
  // TODO(jgruber): Consider changing offset_ to be relative to the current
  // bytecode instead of the start of the bytecode sequence that is being
  // optimized. It is confusing that src/dst offsets have different semantics.
  int offset_;
  int length_;
};

// Describes a bytecode operand for use in a peephole sequence.
struct OpInfo {
  uint16_t offset;
  BytecodeOperandType type;
  constexpr int size() const { return Bytecodes::Size(type); }

  // Usage:
  //   OpInfo::Get<BytecodeOperands<BYTECODE>,
  //   BytecodeOperands<BYTECODE>::Operand::OPERAND>()
  template <class kBytecodeOperands, auto kOperand>
  static OpInfo Get() {
    static constexpr int kOffset = kBytecodeOperands::Offset(kOperand);
    static constexpr BytecodeOperandType kType =
        kBytecodeOperands::Type(kOperand);
    DCHECK_LE(static_cast<uint32_t>(kOffset),
              std::numeric_limits<decltype(offset)>::max());
    return {kOffset, kType};
  }
};
static_assert(sizeof(OpInfo) <= kSystemPointerSize);  // Passed by value.

class BytecodeArgumentMapping : public BytecodeArgument {
 public:
  enum class Type : uint8_t { kDefault, kOffsetAfterSequence };

  BytecodeArgumentMapping(int offset, int length, OpInfo op_info)
      : BytecodeArgument(offset, length),
        type_(Type::kDefault),
        op_info_(op_info) {}

  BytecodeArgumentMapping(Type type, OpInfo op_info)
      : BytecodeArgument(-1, -1), type_(type), op_info_(op_info) {
    DCHECK_NE(type, Type::kDefault);
  }

  Type type() const { return type_; }
  int new_offset() const { return op_info_.offset; }
  BytecodeOperandType new_operand_type() const { return op_info_.type; }
  int new_length() const { return op_info_.size(); }

 private:
  Type type_;
  OpInfo op_info_;
};

struct BytecodeArgumentCheck : public 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:
  explicit BytecodeSequenceNode(std::optional<Bytecode> bytecode);
  // Adds a new node as child of the current node if it isn't a child already.
  BytecodeSequenceNode& FollowedBy(Bytecode bytecode);
  // Marks the end of a sequence and sets optimized bytecode to replace all
  // bytecodes of the sequence with.
  BytecodeSequenceNode& ReplaceWith(Bytecode 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()).
  // to_op_info: Operand info of the argument in the optimized bytecode.
  // from_bytecode_sequence_index: Zero-based index of the referred bytecode
  // within the sequence (e.g. the bytecode passed to CreateSequence() has
  // index 0).
  // from_op_info: Operand info of the argument in the referred bytecode.
  BytecodeSequenceNode& MapArgument(OpInfo to_op_info,
                                    int from_bytecode_sequence_index,
                                    OpInfo from_op_info);

  // Emits the offset after the whole sequence.
  // This should be used for every sequence that doesn't end in an unconditional
  // jump. The offset isn't statically known, as bytecodes might be preserved
  // after the sequence if they were jump targets from bytecodes outside the
  // sequence. The emitted offset is after these potentially preserved
  // bytecodes.
  BytecodeSequenceNode& EmitOffsetAfterSequence(OpInfo op_info);
  // Verifies that we've created mappings in the order they are specified.
  bool BytecodeArgumentMappingCreatedInOrder(OpInfo op_info);
  // 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.
  // op_info: Operand info of the argument to check.
  // 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(OpInfo op_info,
                                               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(
      OpInfo this_op_info, int other_bytecode_index_in_sequence,
      OpInfo other_op_info);

  // 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).
  // op_info: Operand info of the argument to ignore.
  BytecodeSequenceNode& IgnoreArgument(int bytecode_index_in_sequence,
                                       OpInfo op_info);
  // 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 uint8_t* bytecode, int pc) const;
  // 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.
  Bytecode OptimizedBytecode() const;
  // Returns the child of the current node matching the given bytecode or
  // nullptr if no such child is found.
  BytecodeSequenceNode* Find(Bytecode 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())
  std::vector<BytecodeArgument>::const_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())
  std::vector<BytecodeArgument>::const_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);

  std::optional<Bytecode> bytecode_;
  std::optional<Bytecode> bytecode_replacement_;
  int index_in_sequence_;
  int start_offset_;
  BytecodeSequenceNode* parent_;
  std::unordered_map<Bytecode, std::unique_ptr<BytecodeSequenceNode>> children_;
  std::vector<BytecodeArgumentMapping> argument_mapping_;
  std::vector<BytecodeArgumentCheck> argument_check_;
  std::vector<BytecodeArgument> argument_ignored_;
  // True, iff the node has been fully created (including conditions). This
  // implies that it is invalid to add further conditions.
  // Note that it currently *is* valid to define the replacement bytecode and
  // its arguments mappings after a node has been sealed.
  bool sealed_ = false;
};

class BytecodePeepholeSequences {
 public:
  BytecodePeepholeSequences();
  const BytecodeSequenceNode* sequences() const { return &sequences_; }

 private:
  void DefineStandardSequences();
  BytecodeSequenceNode& CreateSequence(Bytecode bytecode);

  BytecodeSequenceNode sequences_;
};

class BytecodePeephole {
 public:
  // Parses bytecode and fills the internal buffer with the potentially
  // optimized bytecode. Returns true when optimizations were performed, false
  // otherwise.
  static bool OptimizeBytecode(Zone* zone, const BytecodeWriter* src_writer,
                               BytecodeWriter* dst_writer);

 private:
  BytecodePeephole(Zone* zone, const BytecodeWriter* src_writer,
                   BytecodeWriter* dst_writer);

  // 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 uint8_t* bytecode, int bytecode_length,
                          int start_pc);
  // Emits optimized bytecode to the internal buffer. start_pc points to the
  // start of the sequence in bytecode and last_node is the last
  // BytecodeSequenceNode of the matching sequence found.
  void EmitOptimization(int start_pc, const uint8_t* bytecode,
                        const BytecodeSequenceNode& last_node);
  // 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);
  // Updates all jump targets in the new bytecode.
  void FixJumps();
  void EmitArgument(int start_pc, const uint8_t* bytecode,
                    BytecodeArgumentMapping arg);
  int pc() const;
  Zone* zone() const;

  BytecodeWriter* const dst_writer_;
  const BytecodeWriter* const src_writer_;

  // TODO(jgruber): We should also replace all of these raw offsets with
  // OpInfo. That should allow us to not expose the "raw" Emit publicly in the
  // Writer.
  // 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 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* const zone_;

  // Points at the first pc in src_writer that has not yet been emitted. Used
  // for batch copying unchanged regions of the incoming bytecodes.
  int next_src_pc_to_emit_;

  DISALLOW_IMPLICIT_CONSTRUCTORS(BytecodePeephole);
};

template <typename T>
T GetValue(const uint8_t* buffer, int pos) {
  DCHECK(IsAligned(reinterpret_cast<Address>(buffer + pos), alignof(T)));
  return *reinterpret_cast<const T*>(buffer + pos);
}

int32_t GetArgumentValue(const uint8_t* bytecode, int offset, int length) {
  switch (length) {
    case 1:
      return GetValue<uint8_t>(bytecode, offset);
    case 2:
      return GetValue<int16_t>(bytecode, offset);
    case 4:
      return GetValue<int32_t>(bytecode, offset);
    default:
      UNREACHABLE();
  }
}

BytecodeSequenceNode::BytecodeSequenceNode(std::optional<Bytecode> bytecode)
    : bytecode_(bytecode),
      bytecode_replacement_(std::nullopt),
      index_in_sequence_(0),
      start_offset_(0),
      parent_(nullptr) {}

BytecodeSequenceNode& BytecodeSequenceNode::FollowedBy(Bytecode bytecode) {
  sealed_ = true;
  if (children_.find(bytecode) == children_.end()) {
    auto new_node = std::make_unique<BytecodeSequenceNode>(bytecode);
    // If node is not the first in the sequence, set offsets and parent.
    if (bytecode_.has_value()) {
      new_node->start_offset_ =
          start_offset_ + Bytecodes::Size(bytecode_.value());
      new_node->index_in_sequence_ = index_in_sequence_ + 1;
      new_node->parent_ = this;
    }
    children_[bytecode] = std::move(new_node);
  }

  BytecodeSequenceNode* node = children_[bytecode].get();
  // If this fails, the node was previously created as part of another
  // sequence. We can reuse it, but only if there are no condition for both the
  // previous and the current use.
  // TODO(jgruber): We could also reuse the node if non-empty previous and
  // current conditions are identical, but that's harder to check.
  // TODO(jgruber): Ideally conditions would become part of the tree (ie nodes
  // with different conditions are siblings), but this changes runtime behavior
  // of the peephole scanning algorithm to DFS. Sequence creation would also
  // need to handle deduplication. All possible, but not trivial.
  DCHECK(node->argument_check_.empty());
  return *node;
}

BytecodeSequenceNode& BytecodeSequenceNode::ReplaceWith(Bytecode bytecode) {
  DCHECK(!bytecode_replacement_.has_value());
  bytecode_replacement_ = bytecode;
  return *this;
}

BytecodeSequenceNode& BytecodeSequenceNode::MapArgument(
    OpInfo to_op_info, int from_bytecode_sequence_index, OpInfo from_op_info) {
  int src_offset = from_op_info.offset;
  int src_size = from_op_info.size();

  DCHECK_LE(from_bytecode_sequence_index, index_in_sequence_);
  DCHECK(BytecodeArgumentMappingCreatedInOrder(to_op_info));

  BytecodeSequenceNode& ref_node =
      GetNodeByIndexInSequence(from_bytecode_sequence_index);
  DCHECK_LT(src_offset, Bytecodes::Size(ref_node.bytecode_.value()));

  int offset_from_start_of_sequence = ref_node.start_offset_ + src_offset;
  argument_mapping_.push_back(BytecodeArgumentMapping{
      offset_from_start_of_sequence, src_size, to_op_info});
  return *this;
}

BytecodeSequenceNode& BytecodeSequenceNode::EmitOffsetAfterSequence(
    OpInfo op_info) {
  DCHECK(BytecodeArgumentMappingCreatedInOrder(op_info));
  argument_mapping_.push_back(BytecodeArgumentMapping{
      BytecodeArgumentMapping::Type::kOffsetAfterSequence, op_info});
  return *this;
}

bool BytecodeSequenceNode::BytecodeArgumentMappingCreatedInOrder(
    OpInfo op_info) {
  DCHECK(IsSequence());
  if (argument_mapping_.empty()) return true;

  const BytecodeArgumentMapping& m = argument_mapping_.back();
  int offset_after_last = m.new_offset() + m.new_length();
  // TODO(jgruber): It'd be more precise to distinguish between special and
  // basic operand types, but we currently don't expose that information
  // except through templates.
  int dst_size = op_info.size();
  int alignment = std::min(dst_size, kBytecodeAlignment);
  return RoundUp(offset_after_last, alignment) == op_info.offset;
}

BytecodeSequenceNode& BytecodeSequenceNode::IfArgumentEqualsOffset(
    OpInfo op_info, int check_byte_offset) {
  DCHECK(!sealed_);
  int size = op_info.size();
  int offset = op_info.offset;

  DCHECK_LT(offset, Bytecodes::Size(bytecode_.value()));
  DCHECK(size == 1 || size == 2 || size == 4);

  int offset_from_start_of_sequence = start_offset_ + offset;

  argument_check_.push_back(BytecodeArgumentCheck{offset_from_start_of_sequence,
                                                  size, check_byte_offset});

  return *this;
}

BytecodeSequenceNode& BytecodeSequenceNode::IfArgumentEqualsValueAtOffset(
    OpInfo this_op_info, int other_bytecode_index_in_sequence,
    OpInfo other_op_info) {
  DCHECK(!sealed_);
  int size_1 = this_op_info.size();
  int size_2 = other_op_info.size();

  DCHECK_LT(this_op_info.offset, Bytecodes::Size(bytecode_.value()));
  DCHECK_LE(other_bytecode_index_in_sequence, index_in_sequence_);
  DCHECK_EQ(size_1, size_2);

  BytecodeSequenceNode& ref_node =
      GetNodeByIndexInSequence(other_bytecode_index_in_sequence);
  DCHECK_LT(other_op_info.offset, Bytecodes::Size(ref_node.bytecode_.value()));

  int offset_from_start_of_sequence = start_offset_ + this_op_info.offset;
  int other_offset_from_start_of_sequence =
      ref_node.start_offset_ + other_op_info.offset;

  argument_check_.push_back(
      BytecodeArgumentCheck{offset_from_start_of_sequence, size_1,
                            other_offset_from_start_of_sequence, size_2});

  return *this;
}

BytecodeSequenceNode& BytecodeSequenceNode::IgnoreArgument(
    int bytecode_index_in_sequence, OpInfo op_info) {
  int size = op_info.size();
  int offset = op_info.offset;

  DCHECK(IsSequence());
  DCHECK_LE(bytecode_index_in_sequence, index_in_sequence_);

  BytecodeSequenceNode& ref_node =
      GetNodeByIndexInSequence(bytecode_index_in_sequence);
  DCHECK_LT(offset, Bytecodes::Size(ref_node.bytecode_.value()));

  int offset_from_start_of_sequence = ref_node.start_offset_ + offset;

  argument_ignored_.push_back(
      BytecodeArgument{offset_from_start_of_sequence, size});

  return *this;
}

bool BytecodeSequenceNode::CheckArguments(const uint8_t* bytecode,
                                          int pc) const {
  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_.has_value();
}

int BytecodeSequenceNode::SequenceLength() const {
  return start_offset_ + Bytecodes::Size(bytecode_.value());
}

Bytecode BytecodeSequenceNode::OptimizedBytecode() const {
  return bytecode_replacement_.value();
}

BytecodeSequenceNode* BytecodeSequenceNode::Find(Bytecode bytecode) const {
  auto found = children_.find(bytecode);
  if (found == children_.end()) return nullptr;
  return found->second.get();
}

size_t BytecodeSequenceNode::ArgumentSize() const {
  DCHECK(IsSequence());
  return argument_mapping_.size();
}

BytecodeArgumentMapping BytecodeSequenceNode::ArgumentMapping(
    size_t index) const {
  DCHECK(IsSequence());
  DCHECK_LT(index, argument_mapping_.size());

  return argument_mapping_.at(index);
}

std::vector<BytecodeArgument>::const_iterator
BytecodeSequenceNode::ArgumentIgnoredBegin() const {
  DCHECK(IsSequence());
  return argument_ignored_.begin();
}

std::vector<BytecodeArgument>::const_iterator
BytecodeSequenceNode::ArgumentIgnoredEnd() const {
  DCHECK(IsSequence());
  return argument_ignored_.end();
}

bool BytecodeSequenceNode::HasIgnoredArguments() const {
  return !argument_ignored_.empty();
}

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;
  }
}

BytecodePeepholeSequences::BytecodePeepholeSequences()
    : sequences_(std::nullopt) {
  DefineStandardSequences();
}

BytecodeSequenceNode& BytecodePeepholeSequences::CreateSequence(
    Bytecode bytecode) {
  return sequences_.FollowedBy(bytecode);
}

BytecodePeephole::BytecodePeephole(Zone* zone, const BytecodeWriter* src_writer,
                                   BytecodeWriter* dst_writer)
    : dst_writer_(dst_writer),
      src_writer_(src_writer),
      jump_usage_counts_(zone),
      jump_destination_fixups_(zone),
      zone_(zone),
      next_src_pc_to_emit_(0) {
  dst_writer_->buffer().reserve(src_writer_->length());
  // Prepare jump usage counts.
  for (auto jump_edge : src_writer_->jump_edges()) {
    int jump_destination = jump_edge.second;
    jump_usage_counts_[jump_destination]++;
  }
  // 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.
  jump_destination_fixups_.emplace(-1, 0);
  // Sentinel fixups at end of (old) bytecode so we don't have to check for
  // end of iterator inside the fixup loop.
  DCHECK_LE(src_writer_->length(), std::numeric_limits<int>::max());
  jump_destination_fixups_.emplace(static_cast<int>(src_writer_->length()), 0);
}

void BytecodePeepholeSequences::DefineStandardSequences() {
  using B = Bytecode;
#define I(BYTECODE, OPERAND)              \
  OpInfo::Get<BytecodeOperands<BYTECODE>, \
              BytecodeOperands<BYTECODE>::Operand::OPERAND>()
#define T(OPERAND) I(Target, OPERAND)

  // Commonly used sequences can be found by creating regexp bytecode traces
  // (--trace-regexp-bytecodes) and using v8/tools/regexp-sequences.py.

  {
    static constexpr auto Target = B::kSkipUntilBitInTable;
    CreateSequence(B::kLoadCurrentCharacter)
        .FollowedBy(B::kCheckBitInTable)
        .FollowedBy(B::kAdvanceCpAndGoto)
        .IfArgumentEqualsOffset(I(B::kAdvanceCpAndGoto, on_goto), 0)
        .ReplaceWith(Target)
        .MapArgument(T(cp_offset), 0, I(B::kLoadCurrentCharacter, cp_offset))
        .MapArgument(T(advance_by), 2, I(B::kAdvanceCpAndGoto, by))
        .MapArgument(T(table), 1, I(B::kCheckBitInTable, table))
        .MapArgument(T(on_match), 1, I(B::kCheckBitInTable, on_bit_set))
        .MapArgument(T(on_no_match), 0, I(B::kLoadCurrentCharacter, on_failure))
        .IgnoreArgument(2, I(B::kAdvanceCpAndGoto, on_goto));
  }

  {
    static constexpr auto Target = B::kSkipUntilCharPosChecked;
    CreateSequence(B::kCheckPosition)
        .FollowedBy(B::kLoadCurrentCharacterUnchecked)
        .FollowedBy(B::kCheckCharacter)
        .FollowedBy(B::kAdvanceCpAndGoto)
        .IfArgumentEqualsOffset(I(B::kAdvanceCpAndGoto, on_goto), 0)
        .ReplaceWith(Target)
        .MapArgument(T(cp_offset), 1,
                     I(B::kLoadCurrentCharacterUnchecked, cp_offset))
        .MapArgument(T(advance_by), 3, I(B::kAdvanceCpAndGoto, by))
        .MapArgument(T(character), 2, I(B::kCheckCharacter, character))
        .MapArgument(T(eats_at_least), 0, I(B::kCheckPosition, cp_offset))
        .MapArgument(T(on_match), 2, I(B::kCheckCharacter, on_equal))
        .MapArgument(T(on_no_match), 0, I(B::kCheckPosition, on_failure))
        .IgnoreArgument(3, I(B::kAdvanceCpAndGoto, on_goto));
  }

  {
    static constexpr auto Target = B::kSkipUntilCharAnd;
    CreateSequence(B::kCheckPosition)
        .FollowedBy(B::kLoadCurrentCharacterUnchecked)
        .FollowedBy(B::kCheckCharacterAfterAnd)
        .FollowedBy(B::kAdvanceCpAndGoto)
        .IfArgumentEqualsOffset(I(B::kAdvanceCpAndGoto, on_goto), 0)
        .ReplaceWith(Target)
        .MapArgument(T(cp_offset), 1,
                     I(B::kLoadCurrentCharacterUnchecked, cp_offset))
        .MapArgument(T(advance_by), 3, I(B::kAdvanceCpAndGoto, by))
        .MapArgument(T(character), 2, I(B::kCheckCharacterAfterAnd, character))
        .MapArgument(T(mask), 2, I(B::kCheckCharacterAfterAnd, mask))
        .MapArgument(T(eats_at_least), 0, I(B::kCheckPosition, cp_offset))
        .MapArgument(T(on_match), 2, I(B::kCheckCharacterAfterAnd, on_equal))
        .MapArgument(T(on_no_match), 0, I(B::kCheckPosition, on_failure))
        .IgnoreArgument(3, I(B::kAdvanceCpAndGoto, on_goto));
  }

  // 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.
  {
    static constexpr auto Target = B::kSkipUntilChar;
    CreateSequence(B::kLoadCurrentCharacter)
        .FollowedBy(B::kCheckCharacter)
        .FollowedBy(B::kAdvanceCpAndGoto)
        .IfArgumentEqualsOffset(I(B::kAdvanceCpAndGoto, on_goto), 0)
        .ReplaceWith(Target)
        .MapArgument(T(cp_offset), 0, I(B::kLoadCurrentCharacter, cp_offset))
        .MapArgument(T(advance_by), 2, I(B::kAdvanceCpAndGoto, by))
        .MapArgument(T(character), 1, I(B::kCheckCharacter, character))
        .MapArgument(T(on_match), 1, I(B::kCheckCharacter, on_equal))
        .MapArgument(T(on_no_match), 0, I(B::kLoadCurrentCharacter, on_failure))
        .IgnoreArgument(2, I(B::kAdvanceCpAndGoto, on_goto));
  }

  {
    static constexpr auto Target = B::kSkipUntilCharOrChar;
    CreateSequence(B::kLoadCurrentCharacter)
        .FollowedBy(B::kCheckCharacter)
        .FollowedBy(B::kCheckCharacter)
        .IfArgumentEqualsValueAtOffset(I(B::kCheckCharacter, on_equal), 1,
                                       I(B::kCheckCharacter, on_equal))
        .FollowedBy(B::kAdvanceCpAndGoto)
        .IfArgumentEqualsOffset(I(B::kAdvanceCpAndGoto, on_goto), 0)
        .ReplaceWith(Target)
        .MapArgument(T(cp_offset), 0, I(B::kLoadCurrentCharacter, cp_offset))
        .MapArgument(T(advance_by), 3, I(B::kAdvanceCpAndGoto, by))
        .MapArgument(T(char1), 1, I(B::kCheckCharacter, character))
        .MapArgument(T(char2), 2, I(B::kCheckCharacter, character))
        .MapArgument(T(on_match), 1, I(B::kCheckCharacter, on_equal))
        .MapArgument(T(on_no_match), 0, I(B::kLoadCurrentCharacter, on_failure))
        .IgnoreArgument(2, I(B::kCheckCharacter, on_equal))
        .IgnoreArgument(3, I(B::kAdvanceCpAndGoto, on_goto));
  }

  {
    static constexpr auto Target = B::kSkipUntilGtOrNotBitInTable;
    CreateSequence(B::kLoadCurrentCharacter)
        .FollowedBy(B::kCheckCharacterGT)
        // Sequence is only valid if the jump target of kCheckCharacterGT is the
        // first bytecode AFTER the whole sequence.
        .IfArgumentEqualsOffset(I(B::kCheckCharacterGT, on_greater), 56)
        .FollowedBy(B::kCheckBitInTable)
        // Sequence is only valid if the jump target of kCheckBitInTable is
        // the kAdvanceCpAndGoto bytecode at the end of the sequence.
        .IfArgumentEqualsOffset(I(B::kCheckBitInTable, on_bit_set), 48)
        .FollowedBy(B::kGoTo)
        // Sequence is only valid if the jump target of kGoTo is the same as the
        // jump target of kCheckCharacterGT (i.e. both jump to the first
        // bytecode AFTER the whole sequence.
        .IfArgumentEqualsValueAtOffset(I(B::kGoTo, label), 1,
                                       I(B::kCheckCharacterGT, on_greater))
        .FollowedBy(B::kAdvanceCpAndGoto)
        .IfArgumentEqualsOffset(I(B::kAdvanceCpAndGoto, on_goto), 0)
        .ReplaceWith(Target)
        .MapArgument(T(cp_offset), 0, I(B::kLoadCurrentCharacter, cp_offset))
        .MapArgument(T(advance_by), 4, I(B::kAdvanceCpAndGoto, by))
        .MapArgument(T(character), 1, I(B::kCheckCharacterGT, limit))
        .MapArgument(T(table), 2, I(B::kCheckBitInTable, table))
        .MapArgument(T(on_match), 1, I(B::kCheckCharacterGT, on_greater))
        .MapArgument(T(on_no_match), 0, I(B::kLoadCurrentCharacter, on_failure))
        .IgnoreArgument(2, I(B::kCheckBitInTable, on_bit_set))
        .IgnoreArgument(3, I(B::kGoTo, label))
        .IgnoreArgument(4, I(B::kAdvanceCpAndGoto, on_goto));
  }
  {
    static constexpr auto Target = B::kSkipUntilOneOfMasked;
    CreateSequence(B::kCheckPosition)
        .FollowedBy(B::kLoad4CurrentCharsUnchecked)
        .FollowedBy(B::kAndCheck4Chars)
        // Jump target is the offset of the next AndCheck4Chars (right after
        // AdvanceCpAndGoto).
        .IfArgumentEqualsOffset(I(B::kAndCheck4Chars, on_equal), 0x24)
        .FollowedBy(B::kAdvanceCpAndGoto)
        .IfArgumentEqualsOffset(I(B::kAdvanceCpAndGoto, on_goto), 0)
        .FollowedBy(B::kAndCheck4Chars)
        .FollowedBy(B::kAndCheckNot4Chars)
        // Jump target is AdvanceCpAndGoto.
        .IfArgumentEqualsOffset(I(B::kAndCheckNot4Chars, on_not_equal), 0x1c)
        .ReplaceWith(Target)
        .MapArgument(T(cp_offset), 1,
                     I(B::kLoad4CurrentCharsUnchecked, cp_offset))
        .MapArgument(T(advance_by), 3, I(B::kAdvanceCpAndGoto, by))
        .MapArgument(T(both_chars), 2, I(B::kAndCheck4Chars, characters))
        .MapArgument(T(both_mask), 2, I(B::kAndCheck4Chars, mask))
        .MapArgument(T(max_offset), 0, I(B::kCheckPosition, cp_offset))
        .MapArgument(T(chars1), 4, I(B::kAndCheck4Chars, characters))
        .MapArgument(T(mask1), 4, I(B::kAndCheck4Chars, mask))
        .MapArgument(T(chars2), 5, I(B::kAndCheckNot4Chars, characters))
        .MapArgument(T(mask2), 5, I(B::kAndCheckNot4Chars, mask))
        .MapArgument(T(on_match1), 4, I(B::kAndCheck4Chars, on_equal))
        .EmitOffsetAfterSequence(T(on_match2))
        .MapArgument(T(on_failure), 0, I(B::kCheckPosition, on_failure))
        .IgnoreArgument(3, I(B::kAdvanceCpAndGoto, on_goto))
        .IgnoreArgument(2, I(B::kAndCheck4Chars, on_equal));
  }
  // The original bytecode sequence for kSkipUntilOneOfMasked3 is:
  //
  // sequence offset name
  // bc0   0  SkipUntilBitInTable
  // bc1  20  CheckPosition
  // bc2  28  Load4CurrentCharsUnchecked
  // bc3  2c  AndCheck4Chars
  // bc4  3c  AdvanceCpAndGoto
  // bc5  48  Load4CurrentChars
  // bc6  4c  AndCheck4Chars
  // bc7  5c  AndCheck4Chars
  // bc8  6c  AndCheckNot4Chars
  {
    static constexpr int kOffsetOfBc0SkipUntilBitInTable = 0x0;
    static constexpr int kOffsetOfBc1CheckCurrentPosition = 0x20;
    static constexpr int kOffsetOfBc4AdvanceBcAndGoto = 0x3c;
    static constexpr auto Target = B::kSkipUntilOneOfMasked3;
    BytecodeSequenceNode& s0 =
        CreateSequence(B::kSkipUntilBitInTable)
            .IfArgumentEqualsOffset(I(B::kSkipUntilBitInTable, on_no_match),
                                    kOffsetOfBc1CheckCurrentPosition)
            .IfArgumentEqualsOffset(I(B::kSkipUntilBitInTable, on_no_match),
                                    kOffsetOfBc1CheckCurrentPosition);

    DCHECK_EQ(s0.SequenceLength(), 0x20);
    DCHECK_EQ(s0.SequenceLength(), kOffsetOfBc1CheckCurrentPosition);
    static constexpr int kOffsetOfBc5Load4CurrentChars = 0x44;
    BytecodeSequenceNode& s1 =
        s0.FollowedBy(B::kCheckPosition)
            .FollowedBy(B::kLoad4CurrentCharsUnchecked)
            .FollowedBy(B::kAndCheck4Chars)
            .IfArgumentEqualsOffset(I(B::kAndCheck4Chars, on_equal),
                                    kOffsetOfBc5Load4CurrentChars);

    DCHECK_EQ(s1.SequenceLength(), 0x3c);
    DCHECK_EQ(s1.SequenceLength(), kOffsetOfBc4AdvanceBcAndGoto);
    BytecodeSequenceNode& s2 =
        s1.FollowedBy(B::kAdvanceCpAndGoto)
            .IfArgumentEqualsOffset(I(B::kAdvanceCpAndGoto, on_goto),
                                    kOffsetOfBc0SkipUntilBitInTable);

    DCHECK_EQ(s2.SequenceLength(), 0x44);
    DCHECK_EQ(s2.SequenceLength(), kOffsetOfBc5Load4CurrentChars);
    BytecodeSequenceNode& s3 =
        s2.FollowedBy(B::kLoad4CurrentChars)
            .IfArgumentEqualsOffset(I(B::kLoad4CurrentChars, on_failure),
                                    kOffsetOfBc4AdvanceBcAndGoto)
            .FollowedBy(B::kAndCheck4Chars)
            .FollowedBy(B::kAndCheck4Chars)
            .FollowedBy(B::kAndCheckNot4Chars)
            .IfArgumentEqualsOffset(I(B::kAndCheckNot4Chars, on_not_equal),
                                    kOffsetOfBc4AdvanceBcAndGoto);

    s3.ReplaceWith(Target)
        .MapArgument(T(bc0_cp_offset), 0, I(B::kSkipUntilBitInTable, cp_offset))
        .MapArgument(T(bc0_advance_by), 0,
                     I(B::kSkipUntilBitInTable, advance_by))
        .MapArgument(T(bc0_table), 0, I(B::kSkipUntilBitInTable, table))
        .IgnoreArgument(0, I(B::kSkipUntilBitInTable, on_match))
        .IgnoreArgument(0, I(B::kSkipUntilBitInTable, on_no_match))
        .MapArgument(T(bc1_cp_offset), 1, I(B::kCheckPosition, cp_offset))
        .MapArgument(T(bc1_on_failure), 1, I(B::kCheckPosition, on_failure))
        .MapArgument(T(bc2_cp_offset), 2,
                     I(B::kLoad4CurrentCharsUnchecked, cp_offset))
        .MapArgument(T(bc3_characters), 3, I(B::kAndCheck4Chars, characters))
        .MapArgument(T(bc3_mask), 3, I(B::kAndCheck4Chars, mask))
        .IgnoreArgument(3, I(B::kAndCheck4Chars, on_equal))
        .MapArgument(T(bc4_by), 4, I(B::kAdvanceCpAndGoto, by))
        .IgnoreArgument(4, I(B::kAdvanceCpAndGoto, on_goto))
        .MapArgument(T(bc5_cp_offset), 5, I(B::kLoad4CurrentChars, cp_offset))
        .IgnoreArgument(5, I(B::kLoad4CurrentChars, on_failure))
        .MapArgument(T(bc6_characters), 6, I(B::kAndCheck4Chars, characters))
        .MapArgument(T(bc6_mask), 6, I(B::kAndCheck4Chars, mask))
        .MapArgument(T(bc6_on_equal), 6, I(B::kAndCheck4Chars, on_equal))
        .MapArgument(T(bc7_characters), 7, I(B::kAndCheck4Chars, characters))
        .MapArgument(T(bc7_mask), 7, I(B::kAndCheck4Chars, mask))
        .MapArgument(T(bc7_on_equal), 7, I(B::kAndCheck4Chars, on_equal))
        .MapArgument(T(bc8_characters), 8, I(B::kAndCheckNot4Chars, characters))
        .MapArgument(T(bc8_mask), 8, I(B::kAndCheckNot4Chars, mask))
        .IgnoreArgument(8, I(B::kAndCheckNot4Chars, on_not_equal))
        .EmitOffsetAfterSequence(T(fallthrough_jump_target));
  }

#undef I
#undef T
}
bool BytecodePeephole::OptimizeBytecode(Zone* zone,
                                        const BytecodeWriter* src_writer,
                                        BytecodeWriter* dst_writer) {
  BytecodePeephole p(zone, src_writer, dst_writer);

  const uint8_t* bytecode = src_writer->buffer().data();
  // TODO(375937549): Convert length to uint32_t.
  int length = static_cast<int>(src_writer->length());

  int old_pc = 0;
  bool did_optimize = false;

  while (old_pc < length) {
    int replaced_len = p.TryOptimizeSequence(bytecode, length, old_pc);
    if (replaced_len > 0) {
      old_pc += replaced_len;
      did_optimize = true;
    } else {
      int bc_len = Bytecodes::Size(bytecode[old_pc]);
      old_pc += bc_len;
    }
  }

  if (did_optimize) {
    // If we optimized anything, we must flush the remaining unoptimized bytes.
    // If we didn't optimize anything, we leave the dst_writer empty and the
    // caller will continue using src_writer (effectively a no-op pass).
    if (old_pc > p.next_src_pc_to_emit_) {
      dst_writer->EmitRawBytecodeStream(src_writer, p.next_src_pc_to_emit_,
                                        old_pc - p.next_src_pc_to_emit_);
    }
    p.FixJumps();
  }

  return did_optimize;
}

DEFINE_LAZY_LEAKY_OBJECT_GETTER(BytecodePeepholeSequences, GetStandardSequences)

int BytecodePeephole::TryOptimizeSequence(const uint8_t* bytecode,
                                          int bytecode_length, int start_pc) {
  const BytecodeSequenceNode* seq_node = GetStandardSequences()->sequences();
  const BytecodeSequenceNode* valid_seq_end = nullptr;

  int current_pc = start_pc;

  // Check for the longest valid sequence matching any of the pre-defined
  // sequences in the Trie data structure.
  while (current_pc < bytecode_length) {
    seq_node = seq_node->Find(Bytecodes::FromByte(bytecode[current_pc]));
    if (seq_node == nullptr) break;
    if (!seq_node->CheckArguments(bytecode, start_pc)) break;

    if (seq_node->IsSequence()) valid_seq_end = seq_node;
    current_pc += Bytecodes::Size(bytecode[current_pc]);
  }

  if (valid_seq_end) {
    EmitOptimization(start_pc, bytecode, *valid_seq_end);
    return valid_seq_end->SequenceLength();
  }

  return 0;
}

void BytecodePeephole::EmitOptimization(int start_pc, const uint8_t* bytecode,
                                        const BytecodeSequenceNode& last_node) {
  // Flush any sequence of bytecodes which we haven't emitted yet.
  if (start_pc > next_src_pc_to_emit_) {
    dst_writer_->EmitRawBytecodeStream(src_writer_, next_src_pc_to_emit_,
                                       start_pc - next_src_pc_to_emit_);
  }
  const int sequence_length = last_node.SequenceLength();
  next_src_pc_to_emit_ = start_pc + sequence_length;

  // Update usage counts for all jumps originating in the sequence we are about
  // to replace. Counts for the target locations are decremented here. Emitting
  // kJumpTarget below will again increment for newly emitted destinations.
  auto edge_it = src_writer_->jump_edges().lower_bound(start_pc);
  while (edge_it != src_writer_->jump_edges().end() &&
         edge_it->first < start_pc + sequence_length) {
    int target = edge_it->second;
    auto count_it = jump_usage_counts_.find(target);
    DCHECK_NE(count_it, jump_usage_counts_.end());
    count_it->second--;
    edge_it++;
  }

  int optimized_start_pc = pc();
  // List of offsets in the optimized sequence that need to be patched to the
  // offset value right after the optimized sequence.
  ZoneLinkedList<uint32_t> after_sequence_offsets(zone());

  const Bytecode bc = last_node.OptimizedBytecode();
  dst_writer_->EmitBytecode(bc);

  for (size_t arg_idx = 0; arg_idx < last_node.ArgumentSize(); arg_idx++) {
    BytecodeArgumentMapping arg_map = last_node.ArgumentMapping(arg_idx);
    if (arg_map.type() == BytecodeArgumentMapping::Type::kDefault) {
      if (arg_map.new_operand_type() == BytecodeOperandType::kJumpTarget) {
        int target = GetArgumentValue(bytecode, start_pc + arg_map.offset(),
                                      arg_map.length());
        jump_usage_counts_[target]++;
      }
      EmitArgument(start_pc, bytecode, arg_map);
    } else {
      DCHECK_EQ(arg_map.type(),
                BytecodeArgumentMapping::Type::kOffsetAfterSequence);
      after_sequence_offsets.push_back(optimized_start_pc +
                                       arg_map.new_offset());
      // Reserve space to overwrite later with the pc after this sequence.
      dst_writer_->Emit<uint32_t>(0, arg_map.new_offset());
    }
  }

  // Final alignment.
  dst_writer_->Finalize(bc);
  DCHECK_EQ(pc(), optimized_start_pc + Bytecodes::Size(bc));

  int fixup_length = Bytecodes::Size(bc) - sequence_length;

  // 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 + sequence_length;
  if (jump_destination_candidate != jump_usage_counts_.end() &&
      jump_candidate_destination < start_pc + sequence_length) {
    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 = src_writer_->jump_edges().lower_bound(preserve_from);
         jump_iter != src_writer_->jump_edges().end() &&
         jump_iter->first /* jump source */ < start_pc + sequence_length;
         ++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 bytecodes up to an unconditional
    // jump.
    int preserve_length = start_pc + sequence_length - preserve_from;
    fixup_length += 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);
    dst_writer_->EmitRawBytecodeStream(src_writer_, preserve_from,
                                       preserve_length);
  } else {
    AddJumpDestinationFixup(fixup_length, start_pc + 1);
  }

  for (uint32_t offset : after_sequence_offsets) {
    DCHECK_EQ(dst_writer_->buffer()[offset], 0);
    dst_writer_->OverwriteValue<uint32_t>(pc(), offset);
    // Register the offset in jump_edges_ so that subsequent peephole passes
    // adjust it when bytecodes shift.
    dst_writer_->jump_edges().emplace(offset, start_pc + sequence_length);
  }
}

void BytecodePeephole::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 BytecodePeephole::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 BytecodePeephole::FixJumps() {
  for (auto jump_edge : dst_writer_->jump_edges()) {
    int jump_source = jump_edge.first;
    int jump_destination = jump_edge.second;
    int fixed_jump_destination =
        jump_destination +
        (--jump_destination_fixups_.upper_bound(jump_destination))->second;
    DCHECK_LT(fixed_jump_destination, pc());
    // TODO(pthier): This check could be better if we track the bytecodes
    // actually used and check if we jump to one of them.
    DCHECK(Bytecodes::IsValidJumpTarget(
        dst_writer_->buffer()[fixed_jump_destination]));

    if (jump_destination != fixed_jump_destination) {
      dst_writer_->PatchJump(fixed_jump_destination, jump_source);
    }
  }
}

void BytecodePeephole::EmitArgument(int start_pc, const uint8_t* bytecode,
                                    BytecodeArgumentMapping arg) {
  const BytecodeOperandType type = arg.new_operand_type();

  switch (type) {
#define CASE(Name, ...)                                                       \
  case BytecodeOperandType::k##Name: {                                        \
    DCHECK_LE(arg.length(), kInt32Size);                                      \
    using CType = OperandTypeTraits<BytecodeOperandType::k##Name>::kCType;    \
    CType value = static_cast<CType>(                                         \
        GetArgumentValue(bytecode, start_pc + arg.offset(), arg.length()));   \
    dst_writer_->EmitOperand<BytecodeOperandType::k##Name>(value,             \
                                                           arg.new_offset()); \
  } break;
    BASIC_BYTECODE_OPERAND_TYPE_LIST(CASE)
    BASIC_BYTECODE_OPERAND_TYPE_LIMITS_LIST(CASE)
#undef CASE
    case BytecodeOperandType::kBitTable: {
      DCHECK_EQ(arg.length(), 16);
      dst_writer_->EmitOperand<BytecodeOperandType::kBitTable>(
          bytecode + start_pc + arg.offset(), arg.new_offset());
    } break;
    default:
      UNREACHABLE();
  }
}

int BytecodePeephole::pc() const { return dst_writer_->pc(); }

Zone* BytecodePeephole::zone() const { return zone_; }

}  // namespace

// static
DirectHandle<TrustedByteArray> BytecodePeepholeOptimization::OptimizeBytecode(
    Isolate* isolate, Zone* zone, DirectHandle<RegExpData> re_data,
    BytecodeWriter* src_writer) {
  BytecodeWriter dst_writer(zone);

  // Preserve the original bytecode for tracing if needed.
  std::optional<ZoneVector<uint8_t>> original_bytecode;
  if (v8_flags.trace_regexp_peephole_optimization) {
    const ZoneVector<uint8_t>& src_buffer = src_writer->buffer();
    const auto begin = src_buffer.begin();
    original_bytecode.emplace(begin, begin + src_writer->length(), zone);
  }

  const bool did_optimize =
      BytecodePeephole::OptimizeBytecode(zone, src_writer, &dst_writer);
  // The result is in dst_writer iff a peephole rule fired; otherwise the
  // unchanged input bytecode is still in src_writer.
  BytecodeWriter* result = did_optimize ? &dst_writer : src_writer;
  const uint8_t* optimized_bytecode = result->buffer().data();
  uint32_t optimized_length = result->length();

  DirectHandle<TrustedByteArray> array =
      isolate->factory()->NewTrustedByteArray(optimized_length);
  MemCopy(array->begin(), optimized_bytecode, optimized_length);

#ifndef COMPILING_IRREGEXP_FOR_EXTERNAL_EMBEDDER
  if (did_optimize && v8_flags.trace_regexp_peephole_optimization) {
    std::unique_ptr<char[]> pattern_cstring =
        re_data->escaped_source()->ToCString();
    PrintF("Original Bytecode:\n");
    RegExpBytecodeDisassemble(original_bytecode->data(),
                              static_cast<uint32_t>(original_bytecode->size()),
                              pattern_cstring.get());
    PrintF("Optimized Bytecode:\n");
    RegExpBytecodeDisassemble(array->begin(), optimized_length,
                              pattern_cstring.get());
  }
#endif  // !COMPILING_IRREGEXP_FOR_EXTERNAL_EMBEDDER

  return array;
}

}  // namespace regexp
}  // namespace internal
}  // namespace v8

Messung V0.5 in Prozent
C=89 H=94 G=91

¤ Dauer der Verarbeitung: 0.20 Sekunden  (vorverarbeitet am  2026-09-28) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.