Commit 5b4fa790 authored by Sigurd Schneider's avatar Sigurd Schneider Committed by Commit Bot

[turbolizer] Highlight gap instructions in the disassembly view

Change-Id: I9988ea2dfeccbfaa9e0197920703ab430a43acb7
Bug: v8:7327
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1674026
Commit-Queue: Sigurd Schneider <sigurds@chromium.org>
Reviewed-by: 's avatarDaniel Clifford <danno@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63375}
parent 0736599a
......@@ -148,7 +148,7 @@ void CodeGenerator::AssembleCode() {
if (info->is_source_positions_enabled()) {
AssembleSourcePosition(start_source_position());
}
offsets_info_.code_start_register_check = tasm()->pc_offset();
// Check that {kJavaScriptCallCodeStartRegister} has been set correctly.
if (FLAG_debug_code && (info->code_kind() == Code::OPTIMIZED_FUNCTION ||
info->code_kind() == Code::BYTECODE_HANDLER)) {
......@@ -156,6 +156,7 @@ void CodeGenerator::AssembleCode() {
AssembleCodeStartRegisterCheck();
}
offsets_info_.deopt_check = tasm()->pc_offset();
// We want to bailout only from JS functions, which are the only ones
// that are optimized.
if (info->IsOptimizing()) {
......@@ -164,6 +165,7 @@ void CodeGenerator::AssembleCode() {
BailoutIfDeoptimized();
}
offsets_info_.init_poison = tasm()->pc_offset();
InitializeSpeculationPoison();
// Define deoptimization literals for all inlined functions.
......@@ -193,10 +195,10 @@ void CodeGenerator::AssembleCode() {
if (info->trace_turbo_json_enabled()) {
block_starts_.assign(instructions()->instruction_blocks().size(), -1);
instr_starts_.assign(instructions()->instructions().size(), -1);
instr_starts_.assign(instructions()->instructions().size(), {});
}
// Assemble instructions in assembly order.
offsets_info_.blocks_start = tasm()->pc_offset();
for (const InstructionBlock* block : instructions()->ao_blocks()) {
// Align loop headers on vendor recommended boundaries.
if (block->ShouldAlign() && !tasm()->jump_optimization_info()) {
......@@ -254,6 +256,7 @@ void CodeGenerator::AssembleCode() {
}
// Assemble all out-of-line code.
offsets_info_.out_of_line_code = tasm()->pc_offset();
if (ools_) {
tasm()->RecordComment("-- Out of line code --");
for (OutOfLineCode* ool = ools_; ool; ool = ool->next()) {
......@@ -277,6 +280,7 @@ void CodeGenerator::AssembleCode() {
}
// Assemble deoptimization exits.
offsets_info_.deoptimization_exits = tasm()->pc_offset();
int last_updated = 0;
for (DeoptimizationExit* exit : deoptimization_exits_) {
if (exit->emitted()) continue;
......@@ -298,12 +302,14 @@ void CodeGenerator::AssembleCode() {
if (result_ != kSuccess) return;
}
offsets_info_.pools = tasm()->pc_offset();
// TODO(jgruber): Move all inlined metadata generation into a new,
// architecture-independent version of FinishCode. Currently, this includes
// the safepoint table, handler table, constant pool, and code comments, in
// that order.
FinishCode();
offsets_info_.jump_tables = tasm()->pc_offset();
// Emit the jump tables.
if (jump_tables_) {
tasm()->Align(kSystemPointerSize);
......@@ -489,11 +495,7 @@ bool CodeGenerator::IsMaterializableFromRoot(Handle<HeapObject> object,
CodeGenerator::CodeGenResult CodeGenerator::AssembleBlock(
const InstructionBlock* block) {
for (int i = block->code_start(); i < block->code_end(); ++i) {
if (info()->trace_turbo_json_enabled()) {
instr_starts_[i] = tasm()->pc_offset();
}
Instruction* instr = instructions()->InstructionAt(i);
CodeGenResult result = AssembleInstruction(instr, block);
CodeGenResult result = AssembleInstruction(i, block);
if (result != kSuccess) return result;
}
return kSuccess;
......@@ -647,7 +649,11 @@ RpoNumber CodeGenerator::ComputeBranchInfo(BranchInfo* branch,
}
CodeGenerator::CodeGenResult CodeGenerator::AssembleInstruction(
Instruction* instr, const InstructionBlock* block) {
int instruction_index, const InstructionBlock* block) {
Instruction* instr = instructions()->InstructionAt(instruction_index);
if (info()->trace_turbo_json_enabled()) {
instr_starts_[instruction_index].gap_pc_offset = tasm()->pc_offset();
}
int first_unused_stack_slot;
FlagsMode mode = FlagsModeField::decode(instr->opcode());
if (mode != kFlags_trap) {
......@@ -665,10 +671,17 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleInstruction(
if (instr->IsJump() && block->must_deconstruct_frame()) {
AssembleDeconstructFrame();
}
if (info()->trace_turbo_json_enabled()) {
instr_starts_[instruction_index].arch_instr_pc_offset = tasm()->pc_offset();
}
// Assemble architecture-specific code for the instruction.
CodeGenResult result = AssembleArchInstruction(instr);
if (result != kSuccess) return result;
if (info()->trace_turbo_json_enabled()) {
instr_starts_[instruction_index].condition_pc_offset = tasm()->pc_offset();
}
FlagsCondition condition = FlagsConditionField::decode(instr->opcode());
switch (mode) {
case kFlags_branch:
......
......@@ -85,6 +85,25 @@ class DeoptimizationLiteral {
const StringConstantBase* string_ = nullptr;
};
// These structs hold pc offsets for generated instructions and is only used
// when tracing for turbolizer is enabled.
struct TurbolizerCodeOffsetsInfo {
int code_start_register_check = -1;
int deopt_check = -1;
int init_poison = -1;
int blocks_start = -1;
int out_of_line_code = -1;
int deoptimization_exits = -1;
int pools = -1;
int jump_tables = -1;
};
struct TurbolizerInstructionStartInfo {
int gap_pc_offset = -1;
int arch_instr_pc_offset = -1;
int condition_pc_offset = -1;
};
// Generates native code for a sequence of instructions.
class V8_EXPORT_PRIVATE CodeGenerator final : public GapResolver::Assembler {
public:
......@@ -139,7 +158,13 @@ class V8_EXPORT_PRIVATE CodeGenerator final : public GapResolver::Assembler {
size_t GetHandlerTableOffset() const { return handler_table_offset_; }
const ZoneVector<int>& block_starts() const { return block_starts_; }
const ZoneVector<int>& instr_starts() const { return instr_starts_; }
const ZoneVector<TurbolizerInstructionStartInfo>& instr_starts() const {
return instr_starts_;
}
const TurbolizerCodeOffsetsInfo& offsets_info() const {
return offsets_info_;
}
static constexpr int kBinarySearchSwitchMinimalCases = 4;
......@@ -182,7 +207,7 @@ class V8_EXPORT_PRIVATE CodeGenerator final : public GapResolver::Assembler {
void GenerateSpeculationPoisonFromCodeStartRegister();
// Assemble code for the specified instruction.
CodeGenResult AssembleInstruction(Instruction* instr,
CodeGenResult AssembleInstruction(int instruction_index,
const InstructionBlock* block);
void AssembleGaps(Instruction* instr);
......@@ -419,7 +444,8 @@ class V8_EXPORT_PRIVATE CodeGenerator final : public GapResolver::Assembler {
CodeGenResult result_;
PoisoningMitigationLevel poisoning_level_;
ZoneVector<int> block_starts_;
ZoneVector<int> instr_starts_;
TurbolizerCodeOffsetsInfo offsets_info_;
ZoneVector<TurbolizerInstructionStartInfo> instr_starts_;
};
} // namespace compiler
......
......@@ -2921,7 +2921,7 @@ void PipelineImpl::VerifyGeneratedCodeIsIdempotent() {
}
struct InstructionStartsAsJSON {
const ZoneVector<int>* instr_starts;
const ZoneVector<TurbolizerInstructionStartInfo>* instr_starts;
};
std::ostream& operator<<(std::ostream& out, const InstructionStartsAsJSON& s) {
......@@ -2929,14 +2929,39 @@ std::ostream& operator<<(std::ostream& out, const InstructionStartsAsJSON& s) {
bool need_comma = false;
for (size_t i = 0; i < s.instr_starts->size(); ++i) {
if (need_comma) out << ", ";
int offset = (*s.instr_starts)[i];
out << "\"" << i << "\":" << offset;
const TurbolizerInstructionStartInfo& info = (*s.instr_starts)[i];
out << "\"" << i << "\": {";
out << "\"gap\": " << info.gap_pc_offset;
out << ", \"arch\": " << info.arch_instr_pc_offset;
out << ", \"condition\": " << info.condition_pc_offset;
out << "}";
need_comma = true;
}
out << "}";
return out;
}
struct TurbolizerCodeOffsetsInfoAsJSON {
const TurbolizerCodeOffsetsInfo* offsets_info;
};
std::ostream& operator<<(std::ostream& out,
const TurbolizerCodeOffsetsInfoAsJSON& s) {
out << ", \"codeOffsetsInfo\": {";
out << "\"codeStartRegisterCheck\": "
<< s.offsets_info->code_start_register_check << ", ";
out << "\"deoptCheck\": " << s.offsets_info->deopt_check << ", ";
out << "\"initPoison\": " << s.offsets_info->init_poison << ", ";
out << "\"blocksStart\": " << s.offsets_info->blocks_start << ", ";
out << "\"outOfLineCode\": " << s.offsets_info->out_of_line_code << ", ";
out << "\"deoptimizationExits\": " << s.offsets_info->deoptimization_exits
<< ", ";
out << "\"pools\": " << s.offsets_info->pools << ", ";
out << "\"jumpTables\": " << s.offsets_info->jump_tables;
out << "}";
return out;
}
void PipelineImpl::AssembleCode(Linkage* linkage,
std::unique_ptr<AssemblerBuffer> buffer) {
PipelineData* data = this->data_;
......@@ -2948,7 +2973,9 @@ void PipelineImpl::AssembleCode(Linkage* linkage,
TurboJsonFile json_of(data->info(), std::ios_base::app);
json_of << "{\"name\":\"code generation\""
<< ", \"type\":\"instructions\""
<< InstructionStartsAsJSON{&data->code_generator()->instr_starts()};
<< InstructionStartsAsJSON{&data->code_generator()->instr_starts()}
<< TurbolizerCodeOffsetsInfoAsJSON{
&data->code_generator()->offsets_info()};
json_of << "},\n";
}
data->DeleteInstructionZone();
......
......@@ -13,6 +13,7 @@ const toolboxHTML = `<div id="disassembly-toolbox">
<form>
<label><input id="show-instruction-address" type="checkbox" name="instruction-address">Show addresses</label>
<label><input id="show-instruction-binary" type="checkbox" name="instruction-binary">Show binary literal</label>
<label><input id="highlight-gap-instructions" type="checkbox" name="instruction-binary">Highlight gap instructions</label>
</form>
</div>`;
......@@ -26,6 +27,7 @@ export class DisassemblyView extends TextView {
offsetSelection: MySelection;
showInstructionAddressHandler: () => void;
showInstructionBinaryHandler: () => void;
highlightGapInstructionsHandler: () => void;
createViewElement() {
const pane = document.createElement('div');
......@@ -46,6 +48,9 @@ export class DisassemblyView extends TextView {
associateData: (text, fragment: HTMLElement) => {
const matches = text.match(/(?<address>0?x?[0-9a-fA-F]{8,16})(?<addressSpace>\s+)(?<offset>[0-9a-f]+)(?<offsetSpace>\s*)/);
const offset = Number.parseInt(matches.groups["offset"], 16);
const instructionKind = view.sourceResolver.getInstructionKindForPCOffset(offset);
fragment.dataset.instructionKind = instructionKind;
fragment.title = view.sourceResolver.instructionKindToReadableName(instructionKind);
const blockIds = view.sourceResolver.getBlockIdsForOffset(offset);
const blockIdElement = document.createElement("SPAN");
blockIdElement.className = "block-id com linkable-text";
......@@ -242,6 +247,17 @@ export class DisassemblyView extends TextView {
};
instructionBinaryInput.addEventListener("change", showInstructionBinaryHandler);
this.showInstructionBinaryHandler = showInstructionBinaryHandler;
const highlightGapInstructionsInput: HTMLInputElement = view.divNode.querySelector("#highlight-gap-instructions");
const lastHighlightGapInstructions = window.sessionStorage.getItem("highlight-gap-instructions");
highlightGapInstructionsInput.checked = lastHighlightGapInstructions == 'true';
const highlightGapInstructionsHandler = () => {
window.sessionStorage.setItem("highlight-gap-instructions", `${highlightGapInstructionsInput.checked}`);
view.divNode.classList.toggle("highlight-gap-instructions", highlightGapInstructionsInput.checked);
};
highlightGapInstructionsInput.addEventListener("change", highlightGapInstructionsHandler);
this.highlightGapInstructionsHandler = highlightGapInstructionsHandler;
}
updateSelection(scrollIntoView: boolean = false) {
......@@ -308,6 +324,7 @@ export class DisassemblyView extends TextView {
super.initializeContent(data, null);
this.showInstructionAddressHandler();
this.showInstructionBinaryHandler();
this.highlightGapInstructionsHandler();
console.timeEnd("disassembly-view");
}
......
......@@ -98,8 +98,10 @@ export class SequenceView extends TextView {
const instNodeEl = createElement("div", "instruction-node");
const instId = createElement("div", "instruction-id", instruction.id);
const offsets = view.sourceResolver.instructionToPcOffsets(instruction.id);
instId.classList.add("clickable");
instId.dataset.instructionId = instruction.id;
instId.setAttribute("title", `This instruction generated gap code at pc-offset 0x${offsets.gap.toString(16)}, code at pc-offset 0x${offsets.arch.toString(16)}, condition handling at pc-offset 0x${offsets.condition.toString(16)}.`);
instNodeEl.appendChild(instId);
const instContentsEl = createElement("div", "instruction-contents");
......
......@@ -83,6 +83,7 @@ interface InstructionsPhase {
instructionOffsetToPCOffset?: any;
blockIdtoInstructionRange?: any;
nodeIdToInstructionRange?: any;
codeOffsetsInfo?: CodeOffsetsInfo
}
interface GraphPhase {
......@@ -103,6 +104,22 @@ export interface Sequence {
blocks: Array<any>;
}
class CodeOffsetsInfo {
codeStartRegisterCheck: number;
deoptCheck: number;
initPoison: number;
blocksStart: number;
outOfLineCode: number;
deoptimizationExits: number;
pools: number;
jumpTables: number;
}
export class TurbolizerInstructionStartInfo {
gap: number;
arch: number;
condition: number;
}
export class SourceResolver {
nodePositionMap: Array<AnyPosition>;
sources: Array<Source>;
......@@ -115,11 +132,12 @@ export class SourceResolver {
lineToSourcePositions: Map<string, Array<AnyPosition>>;
nodeIdToInstructionRange: Array<[number, number]>;
blockIdToInstructionRange: Array<[number, number]>;
instructionToPCOffset: Array<number>;
instructionToPCOffset: Array<TurbolizerInstructionStartInfo>;
pcOffsetToInstructions: Map<number, Array<number>>;
pcOffsets: Array<number>;
blockIdToPCOffset: Array<number>;
blockStartPCtoBlockIds: Map<number, Array<number>>;
codeOffsetsInfo: CodeOffsetsInfo;
constructor() {
// Maps node ids to source positions.
......@@ -151,6 +169,7 @@ export class SourceResolver {
this.pcOffsets = [];
this.blockIdToPCOffset = [];
this.blockStartPCtoBlockIds = new Map();
this.codeOffsetsInfo = null;
}
getBlockIdsForOffset(offset): Array<number> {
......@@ -381,12 +400,18 @@ export class SourceResolver {
}
readInstructionOffsetToPCOffset(instructionToPCOffset) {
for (const [instruction, offset] of Object.entries<number>(instructionToPCOffset)) {
this.instructionToPCOffset[instruction] = offset;
if (!this.pcOffsetToInstructions.has(offset)) {
this.pcOffsetToInstructions.set(offset, []);
for (const [instruction, numberOrInfo] of Object.entries<number | TurbolizerInstructionStartInfo>(instructionToPCOffset)) {
let info: TurbolizerInstructionStartInfo;
if (typeof numberOrInfo == "number") {
info = { gap: numberOrInfo, arch: numberOrInfo, condition: numberOrInfo };
} else {
info = numberOrInfo;
}
this.pcOffsetToInstructions.get(offset).push(Number(instruction));
this.instructionToPCOffset[instruction] = info;
if (!this.pcOffsetToInstructions.has(info.gap)) {
this.pcOffsetToInstructions.set(info.gap, []);
}
this.pcOffsetToInstructions.get(info.gap).push(Number(instruction));
}
this.pcOffsets = Array.from(this.pcOffsetToInstructions.keys()).sort((a, b) => b - a);
}
......@@ -405,15 +430,67 @@ export class SourceResolver {
return -1;
}
instructionRangeToKeyPcOffsets([start, end]: [number, number]) {
getInstructionKindForPCOffset(offset: number) {
if (this.codeOffsetsInfo) {
if (offset >= this.codeOffsetsInfo.deoptimizationExits) {
if (offset >= this.codeOffsetsInfo.pools) {
return "pools";
} else if (offset >= this.codeOffsetsInfo.jumpTables) {
return "jump-tables";
} else {
return "deoptimization-exits";
}
}
if (offset < this.codeOffsetsInfo.deoptCheck) {
return "code-start-register";
} else if (offset < this.codeOffsetsInfo.initPoison) {
return "deopt-check";
} else if (offset < this.codeOffsetsInfo.blocksStart) {
return "init-poison";
}
}
const keyOffset = this.getKeyPcOffset(offset);
if (keyOffset != -1) {
const infos = this.pcOffsetToInstructions.get(keyOffset).map(instrId => this.instructionToPCOffset[instrId]).filter(info => info.gap != info.condition);
if (infos.length > 0) {
const info = infos[0];
if (!info || info.gap == info.condition) return "unknown";
if (offset < info.arch) return "gap";
if (offset < info.condition) return "arch";
return "condition";
}
}
return "unknown";
}
instructionKindToReadableName(instructionKind) {
switch (instructionKind) {
case "code-start-register": return "Check code register for right value";
case "deopt-check": return "Check if function was marked for deoptimization";
case "init-poison": return "Initialization of poison register";
case "gap": return "Instruction implementing a gap move";
case "arch": return "Instruction implementing the actual machine operation";
case "condition": return "Code implementing conditional after instruction";
case "pools": return "Data in a pool (e.g. constant pool)";
case "jump-tables": return "Part of a jump table";
case "deoptimization-exits": return "Jump to deoptimization exit";
}
return null;
}
instructionRangeToKeyPcOffsets([start, end]: [number, number]): Array<TurbolizerInstructionStartInfo> {
if (start == end) return [this.instructionToPCOffset[start]];
return this.instructionToPCOffset.slice(start, end);
}
instructionsToKeyPcOffsets(instructionIds: Iterable<number>) {
instructionToPcOffsets(instr: number): TurbolizerInstructionStartInfo {
return this.instructionToPCOffset[instr];
}
instructionsToKeyPcOffsets(instructionIds: Iterable<number>): Array<number> {
const keyPcOffsets = [];
for (const instructionId of instructionIds) {
keyPcOffsets.push(this.instructionToPCOffset[instructionId]);
keyPcOffsets.push(this.instructionToPCOffset[instructionId].gap);
}
return keyPcOffsets;
}
......@@ -487,6 +564,9 @@ export class SourceResolver {
if (phase.instructionOffsetToPCOffset) {
this.readInstructionOffsetToPCOffset(phase.instructionOffsetToPCOffset);
}
if (phase.codeOffsetsInfo) {
this.codeOffsetsInfo = phase.codeOffsetsInfo;
}
break;
case 'graph':
const graphPhase: GraphPhase = Object.assign(phase, { highestNodeId: 0 });
......
......@@ -702,3 +702,55 @@ ul.disassembly-list .block-id {
display: block;
padding-top: 2px;
}
div.highlight-gap-instructions [data-instruction-kind="gap"]+span+span {
background-color: #FAEEEE;
}
div.highlight-gap-instructions [data-instruction-kind="arch"]+span+span {
background-color: #EEFFEE;
}
div.highlight-gap-instructions [data-instruction-kind="condition"]+span+span {
background-color: #FFFFEE;
}
div.highlight-gap-instructions [data-instruction-kind="gap"] {
background-color: #FAEEEE;
}
div.highlight-gap-instructions [data-instruction-kind="arch"] {
background-color: #EEFFEE;
}
div.highlight-gap-instructions [data-instruction-kind="condition"] {
background-color: #FFFFEE;
}
div.highlight-gap-instructions [data-instruction-kind="deopt-check"] {
background-color: #FAEEFA;
}
div.highlight-gap-instructions [data-instruction-kind="init-poison"] {
background-color: #EEFFAA;
}
div.highlight-gap-instructions [data-instruction-kind="pools"] {
background-color: #6AA84F;
}
div.highlight-gap-instructions [data-instruction-kind="code-start-register"] {
background-color: #FFCCCC;
}
div.highlight-gap-instructions [data-instruction-kind="deoptimization-exits"] {
background-color: #CCCCFF;
}
[data-instruction-kind].selected {
background-color: yellow;
}
div.highlight-gap-instructions [data-instruction-kind].selected {
background-color: yellow;
}
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