// Copyright 2026 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.
void BytecodeAnalysis::BuildBlocks() { // Potential optimizations // * Simple xmm reg allocation, with constant hoisting. // * Only do this if the code actually uses the xmm regs. // * Careful around caller-saved xmms (for outgoing calls) and callee-saved // for returns. // * Push callee-saved xmm regs in the prologue,epilogue, and when calling to // C. // * Simdify much smaller chunks, e.g. AndCheck4Chars. But: reloading the // current string into vector regs is expensive. // The ..Masked optimization is hard to beat because it implicitly assumes // that it's okay to be a bit fuzzy wrt fast simd candidate location - the // scalar suffix will filter out bad matches. // * Load elimination. We could always load 8/4 byte chunks and eliminate // following LoadCurrentCharacter with cp_offset inside this range. Loop // unrolling could create more opportunities. Comparison ops would have to // be updated to mask appropriately. // * Fold the repeated Load1-Check1 sequence. // * EBB analysis. // * Single use for each of the char loads. // * cp_offset is (almost) consecutive. // * The uses are all CheckFoo where Foo is the same in all uses (modulo // Not), and the jump target is the same. // * Dead code elimination. // * Backtrack elimination (if only one backtrack target exists) // * Remove all pushbacktracks. // * Replace kBacktrack by Goto. // * Likewise, if the Backtrack bytecode is dead. // * Regexp stack check elimination (Extended Basic Blocks). // * Align loop headers.
// Pass 1: Identify leaders. // A leader is: // 1. The first instruction (offset 0). // 2. The target of any jump. // 3. The instruction following a terminator. // // This phase collects all bytecode offsets that start a new basic block.
// Collect jump targets. Some bytecodes (like kPushBacktrack) don't // immediately branch, but their target will eventually be a leader. bool has_nontrivial_jumptarget = false;
Bytecodes::DispatchOnBytecode(bytecode, [&]<Bytecode bc>() { using Operands = BytecodeOperands<bc>;
Operands::ForEachOperand([&]<auto op>() { if constexpr (Operands::Type(op) == BytecodeOperandType::kJumpTarget) {
uint32_t target =
Operands::template Get<op>(it.current_address(), no_gc_);
DCHECK_LT(target, length_); if constexpr (bc == Bytecode::kPushBacktrack) {
backtrack_targets_.push_back(target);
leaders.push_back(target); return;
} if (target == next_offset) {
treat_as_fallthrough = true; return;
}
has_nontrivial_jumptarget = true;
leaders.push_back(target);
}
});
});
// If we have a jump target AND can fall through, the fall-through must // start a new block. Similarly, if we cannot fall through, the next // instruction starts a new block. if (treat_as_fallthrough && has_nontrivial_jumptarget) {
leaders.push_back(next_offset);
} elseif (!treat_as_fallthrough) {
leaders.push_back(next_offset);
}
}
// Finalize block boundaries. We sort and unique the leaders to get a clean // list of block starts.
leaders.push_back(length_);
std::sort(leaders.begin(), leaders.end());
leaders.erase(std::unique(leaders.begin(), leaders.end()), leaders.end());
block_starts_ = std::move(leaders);
// Initialize data structures for the graph.
successors_.assign(total_blocks, ZoneVector<uint32_t>(zone_));
predecessors_.assign(total_blocks, ZoneVector<uint32_t>(zone_));
uses_current_char_.Resize(total_blocks, zone_);
loads_current_char_.Resize(total_blocks, zone_);
terminates_with_backtrack_.Resize(total_blocks, zone_);
// Backtrack canonicalization: instead of edges from every 'Backtrack' site // to every 'PushBacktrack' target, we create a single backtrack dispatch // node. All 'Backtrack' sites point to it, and it points to all unique // target blocks.
ZoneVector<uint32_t> backtrack_target_blocks(zone_); for (uint32_t target : backtrack_targets_) {
backtrack_target_blocks.push_back(GetBlockId(target));
}
std::sort(backtrack_target_blocks.begin(), backtrack_target_blocks.end());
backtrack_target_blocks.erase(std::unique(backtrack_target_blocks.begin(),
backtrack_target_blocks.end()),
backtrack_target_blocks.end());
// A block "uses" current_character if any use exists before a load in the // same block; any uses after a local load are not counted because they // consume the locally loaded value. while (it.current_offset() < end) {
Bytecode bytecode = it.current_bytecode(); const BytecodeFlags flags = Bytecodes::Flags(bytecode);
// Only the last instruction of a block can have non-fallthrough // successors. if (it.current_offset() == end) { if (bytecode == Bytecode::kBacktrack) {
terminates_with_backtrack_.Add(block_id);
successors_[block_id].push_back(backtrack_dispatch_id());
} else { constbool may_branch =
(flags & ReBcFlag::kNoBranchDespiteJumpTargetOperand) == 0; constbool is_fallthrough = (flags & ReBcFlag::kNoFallthrough) == 0;
for (uint32_t v : successors_[u]) { // Edges involving the backtrack dispatch node are not real // control flow — they model the runtime backtrack stack, not loops. bool is_backtrack_edge =
u == backtrack_dispatch_id() || v == backtrack_dispatch_id(); if (!is_backtrack_edge && recursion_stack.Contains(v)) {
is_loop_header_.Add(v);
back_edges_.push_back({u, v});
} elseif (!visited.Contains(v)) {
dfs_stack.push({v, 0, false});
}
}
}
// Extended Basic Block (EBB) identification. // An EBB starts at: // 1. The entry block (0). // 2. Any block with multiple predecessors. // 3. Any block that is the target of a backtrack edge.
visited.Clear(); int next_ebb_id = 0;
block_to_ebb_id_[0] = next_ebb_id;
dfs_stack.push({0, next_ebb_id++, false});
while (!dfs_stack.empty()) {
Frame frame = dfs_stack.top();
dfs_stack.pop();
const uint32_t u = frame.block_id; if (visited.Contains(u)) { continue;
}
visited.Add(u);
for (uint32_t v : successors_[u]) { if (visited.Contains(v)) continue;
int ebb_id = block_to_ebb_id_[v]; if (ebb_id == -1) {
ebb_id = frame.ebb_id; bool is_backtrack_edge =
u == backtrack_dispatch_id() || v == backtrack_dispatch_id(); if (predecessors_[v].size() > 1 || is_backtrack_edge) {
ebb_id = next_ebb_id++;
}
block_to_ebb_id_[v] = ebb_id;
}
dfs_stack.push({v, ebb_id, false});
}
}
// Loop member identification. for (constauto& edge : back_edges_) {
uint32_t header = edge.second;
uint32_t latch = edge.first;
// Find or create loop info for this header. // TODO(jgruber): not linear search. BlockInfo could point at LoopInfo.
LoopInfo* loop = nullptr; for (auto& l : loops_) { if (l.header_block_id == header) {
loop = &l; break;
}
} if (loop == nullptr) {
loops_.emplace_back(header, total_blocks, zone_);
loop = &loops_.back();
loop->members.Add(header);
}
// Add blocks to loop body by walking backwards from latch to header // using the predecessor edges. if (!loop->members.Contains(latch)) {
ZoneVector<uint32_t> worklist(zone_);
worklist.push_back(latch);
loop->members.Add(latch);
while (!worklist.empty()) {
uint32_t block = worklist.back();
worklist.pop_back();
if (block == header) continue;
for (uint32_t pred : predecessors_[block]) { if (!loop->members.Contains(pred)) {
loop->members.Add(pred);
worklist.push_back(pred);
}
}
}
}
}
// Compute loop exits now that loop infos are complete. for (auto& loop : loops_) { for (uint32_t block : loop.members) { for (uint32_t successor : successors_[block]) { if (!loop.members.Contains(successor)) {
loop.exits.push_back({block, successor});
}
}
}
}
}
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.