[torque] Add option for printing Torque IR

While working on the Torque compiler, I've sometimes found it difficult
to understand Torque's intermediate representation and how it
corresponds to the output. In this change, I propose adding a build flag
that instructs Torque to emit comments describing its IR, interspersed
in the generated code. This is particularly useful for seeing the stack
management instructions (Peek, Poke, and DeleteRange) which don't emit
any corresponding C++ code.

Bug: v8:7793
Change-Id: I24bdec47da76c9bd751b928d3cd92aa513dc6593
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2748040
Reviewed-by: Nico Hartmann <nicohartmann@chromium.org>
Commit-Queue: Seth Brenith <seth.brenith@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#73352}
This commit is contained in:
Seth Brenith 2021-03-10 11:24:38 -08:00 committed by Commit Bot
parent df748fc03e
commit ad0e581c48
10 changed files with 278 additions and 2 deletions

View File

@ -252,6 +252,9 @@ declare_args() {
# file generation
v8_verify_torque_generation_invariance = false
# Generate comments describing the Torque intermediate representation.
v8_annotate_torque_ir = false
# Disable all snapshot compression.
v8_enable_snapshot_compression = true
@ -1537,6 +1540,9 @@ template("run_torque") {
"-v8-root",
rebase_path(".", root_build_dir),
]
if (v8_annotate_torque_ir) {
args += [ "-annotate-ir" ]
}
if (defined(invoker.args)) {
args += invoker.args
}

View File

@ -14,6 +14,7 @@ DEFINE_CONTEXTUAL_VARIABLE(TargetArchitecture)
GlobalContext::GlobalContext(Ast ast)
: collect_language_server_data_(false),
force_assert_statements_(false),
annotate_ir_(false),
ast_(std::move(ast)) {
CurrentScope::Scope current_scope(nullptr);
CurrentSourcePosition::Scope current_source_position(

View File

@ -54,6 +54,8 @@ class GlobalContext : public ContextualClass<GlobalContext> {
static bool force_assert_statements() {
return Get().force_assert_statements_;
}
static void SetAnnotateIR() { Get().annotate_ir_ = true; }
static bool annotate_ir() { return Get().annotate_ir_; }
static Ast* ast() { return &Get().ast_; }
static std::string MakeUniqueName(const std::string& base) {
return base + "_" + std::to_string(Get().fresh_ids_[base]++);
@ -106,6 +108,7 @@ class GlobalContext : public ContextualClass<GlobalContext> {
private:
bool collect_language_server_data_;
bool force_assert_statements_;
bool annotate_ir_;
Namespace* default_namespace_;
Ast ast_;
std::vector<std::unique_ptr<Declarable>> declarables_;

View File

@ -129,6 +129,11 @@ DefinitionLocation NamespaceConstantInstruction::GetValueDefinition(
return DefinitionLocation::Instruction(this, index);
}
std::ostream& operator<<(std::ostream& os,
const NamespaceConstantInstruction& instruction) {
return os << "NamespaceConstant " << instruction.constant->external_name();
}
void InstructionBase::InvalidateTransientTypes(
Stack<const Type*>* stack) const {
auto current = stack->begin();
@ -183,6 +188,22 @@ DefinitionLocation CallIntrinsicInstruction::GetValueDefinition(
return DefinitionLocation::Instruction(this, index);
}
std::ostream& operator<<(std::ostream& os,
const CallIntrinsicInstruction& instruction) {
os << "CallIntrinsic " << instruction.intrinsic->ReadableName();
if (!instruction.specialization_types.empty()) {
os << "<";
PrintCommaSeparatedList(
os, instruction.specialization_types,
[](const Type* type) -> const Type& { return *type; });
os << ">";
}
os << "(";
PrintCommaSeparatedList(os, instruction.constexpr_arguments);
os << ")";
return os;
}
void CallCsaMacroInstruction::TypeInstruction(Stack<const Type*>* stack,
ControlFlowGraph* cfg) const {
std::vector<const Type*> parameter_types =
@ -243,6 +264,18 @@ DefinitionLocation CallCsaMacroInstruction::GetValueDefinition(
return DefinitionLocation::Instruction(this, index);
}
std::ostream& operator<<(std::ostream& os,
const CallCsaMacroInstruction& instruction) {
os << "CallCsaMacro " << instruction.macro->ReadableName();
os << "(";
PrintCommaSeparatedList(os, instruction.constexpr_arguments);
os << ")";
if (instruction.catch_block) {
os << ", catch block " << (*instruction.catch_block)->id();
}
return os;
}
void CallCsaMacroAndBranchInstruction::TypeInstruction(
Stack<const Type*>* stack, ControlFlowGraph* cfg) const {
std::vector<const Type*> parameter_types =
@ -363,6 +396,26 @@ CallCsaMacroAndBranchInstruction::GetExceptionObjectDefinition() const {
return DefinitionLocation::Instruction(this, GetValueDefinitionCount());
}
std::ostream& operator<<(std::ostream& os,
const CallCsaMacroAndBranchInstruction& instruction) {
os << "CallCsaMacroAndBranch " << instruction.macro->ReadableName();
os << "(";
PrintCommaSeparatedList(os, instruction.constexpr_arguments);
os << ")";
if (instruction.return_continuation) {
os << ", return continuation " << (*instruction.return_continuation)->id();
}
if (!instruction.label_blocks.empty()) {
os << ", label blocks ";
PrintCommaSeparatedList(os, instruction.label_blocks,
[](Block* block) { return block->id(); });
}
if (instruction.catch_block) {
os << ", catch block " << (*instruction.catch_block)->id();
}
return os;
}
void CallBuiltinInstruction::TypeInstruction(Stack<const Type*>* stack,
ControlFlowGraph* cfg) const {
std::vector<const Type*> argument_types = stack->PopMany(argc);
@ -447,6 +500,19 @@ DefinitionLocation CallBuiltinPointerInstruction::GetValueDefinition(
return DefinitionLocation::Instruction(this, index);
}
std::ostream& operator<<(std::ostream& os,
const CallBuiltinInstruction& instruction) {
os << "CallBuiltin " << instruction.builtin->ReadableName()
<< ", argc: " << instruction.argc;
if (instruction.is_tailcall) {
os << ", is_tailcall";
}
if (instruction.catch_block) {
os << ", catch block " << (*instruction.catch_block)->id();
}
return os;
}
void CallRuntimeInstruction::TypeInstruction(Stack<const Type*>* stack,
ControlFlowGraph* cfg) const {
std::vector<const Type*> argument_types = stack->PopMany(argc);
@ -507,6 +573,19 @@ CallRuntimeInstruction::GetExceptionObjectDefinition() const {
return DefinitionLocation::Instruction(this, GetValueDefinitionCount());
}
std::ostream& operator<<(std::ostream& os,
const CallRuntimeInstruction& instruction) {
os << "CallRuntime " << instruction.runtime_function->ReadableName()
<< ", argc: " << instruction.argc;
if (instruction.is_tailcall) {
os << ", is_tailcall";
}
if (instruction.catch_block) {
os << ", catch block " << (*instruction.catch_block)->id();
}
return os;
}
void BranchInstruction::TypeInstruction(Stack<const Type*>* stack,
ControlFlowGraph* cfg) const {
const Type* condition_type = stack->Pop();
@ -524,6 +603,12 @@ void BranchInstruction::RecomputeDefinitionLocations(
if_false->MergeInputDefinitions(*locations, worklist);
}
std::ostream& operator<<(std::ostream& os,
const BranchInstruction& instruction) {
return os << "Branch true: " << instruction.if_true->id()
<< ", false: " << instruction.if_false->id();
}
void ConstexprBranchInstruction::TypeInstruction(Stack<const Type*>* stack,
ControlFlowGraph* cfg) const {
if_true->SetInputTypes(*stack);
@ -536,6 +621,13 @@ void ConstexprBranchInstruction::RecomputeDefinitionLocations(
if_false->MergeInputDefinitions(*locations, worklist);
}
std::ostream& operator<<(std::ostream& os,
const ConstexprBranchInstruction& instruction) {
return os << "ConstexprBranch " << instruction.condition
<< ", true: " << instruction.if_true->id()
<< ", false: " << instruction.if_false->id();
}
void GotoInstruction::TypeInstruction(Stack<const Type*>* stack,
ControlFlowGraph* cfg) const {
destination->SetInputTypes(*stack);
@ -546,6 +638,10 @@ void GotoInstruction::RecomputeDefinitionLocations(
destination->MergeInputDefinitions(*locations, worklist);
}
std::ostream& operator<<(std::ostream& os, const GotoInstruction& instruction) {
return os << "Goto " << instruction.destination->id();
}
void GotoExternalInstruction::TypeInstruction(Stack<const Type*>* stack,
ControlFlowGraph* cfg) const {
if (variable_names.size() != stack->Size()) {
@ -693,6 +789,16 @@ DefinitionLocation MakeLazyNodeInstruction::GetValueDefinition() const {
return DefinitionLocation::Instruction(this, 0);
}
std::ostream& operator<<(std::ostream& os,
const MakeLazyNodeInstruction& instruction) {
os << "MakeLazyNode " << instruction.macro->ReadableName() << ", "
<< *instruction.result_type;
for (const std::string& arg : instruction.constexpr_arguments) {
os << ", " << arg;
}
return os;
}
bool CallRuntimeInstruction::IsBlockTerminator() const {
return is_tailcall || runtime_function->signature().return_type ==
TypeOracle::GetNeverType();

View File

@ -288,6 +288,15 @@ struct PeekInstruction : InstructionBase {
base::Optional<const Type*> widened_type;
};
inline std::ostream& operator<<(std::ostream& os,
const PeekInstruction& instruction) {
os << "Peek " << instruction.slot;
if (instruction.widened_type) {
os << ", " << **instruction.widened_type;
}
return os;
}
struct PokeInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
@ -298,6 +307,15 @@ struct PokeInstruction : InstructionBase {
base::Optional<const Type*> widened_type;
};
inline std::ostream& operator<<(std::ostream& os,
const PokeInstruction& instruction) {
os << "Poke " << instruction.slot;
if (instruction.widened_type) {
os << ", " << **instruction.widened_type;
}
return os;
}
// Preserve the top {preserved_slots} number of slots, and delete
// {deleted_slots} number or slots below.
struct DeleteRangeInstruction : InstructionBase {
@ -307,6 +325,11 @@ struct DeleteRangeInstruction : InstructionBase {
StackRange range;
};
inline std::ostream& operator<<(std::ostream& os,
const DeleteRangeInstruction& instruction) {
return os << "DeleteRange " << instruction.range;
}
struct PushUninitializedInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
explicit PushUninitializedInstruction(const Type* type) : type(type) {}
@ -316,6 +339,11 @@ struct PushUninitializedInstruction : InstructionBase {
const Type* type;
};
inline std::ostream& operator<<(
std::ostream& os, const PushUninitializedInstruction& instruction) {
return os << "PushUninitialized " << *instruction.type;
}
struct PushBuiltinPointerInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
PushBuiltinPointerInstruction(std::string external_name, const Type* type)
@ -329,6 +357,13 @@ struct PushBuiltinPointerInstruction : InstructionBase {
const Type* type;
};
inline std::ostream& operator<<(
std::ostream& os, const PushBuiltinPointerInstruction& instruction) {
return os << "PushBuiltinPointer "
<< StringLiteralQuote(instruction.external_name) << ", "
<< *instruction.type;
}
struct NamespaceConstantInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
explicit NamespaceConstantInstruction(NamespaceConstant* constant)
@ -340,6 +375,9 @@ struct NamespaceConstantInstruction : InstructionBase {
NamespaceConstant* constant;
};
std::ostream& operator<<(std::ostream& os,
const NamespaceConstantInstruction& instruction);
struct LoadReferenceInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
explicit LoadReferenceInstruction(const Type* type) : type(type) {}
@ -349,12 +387,22 @@ struct LoadReferenceInstruction : InstructionBase {
const Type* type;
};
inline std::ostream& operator<<(std::ostream& os,
const LoadReferenceInstruction& instruction) {
return os << "LoadReference " << *instruction.type;
}
struct StoreReferenceInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
explicit StoreReferenceInstruction(const Type* type) : type(type) {}
const Type* type;
};
inline std::ostream& operator<<(std::ostream& os,
const StoreReferenceInstruction& instruction) {
return os << "StoreReference " << *instruction.type;
}
// Pops a bitfield struct; pushes a bitfield value extracted from it.
struct LoadBitFieldInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
@ -368,6 +416,12 @@ struct LoadBitFieldInstruction : InstructionBase {
BitField bit_field;
};
inline std::ostream& operator<<(std::ostream& os,
const LoadBitFieldInstruction& instruction) {
return os << "LoadBitField " << *instruction.bit_field_struct_type << ", "
<< instruction.bit_field.name_and_type.name;
}
// Pops a bitfield value and a bitfield struct; pushes a new bitfield struct
// containing the updated value.
struct StoreBitFieldInstruction : InstructionBase {
@ -386,6 +440,16 @@ struct StoreBitFieldInstruction : InstructionBase {
bool starts_as_zero;
};
inline std::ostream& operator<<(std::ostream& os,
const StoreBitFieldInstruction& instruction) {
os << "StoreBitField " << *instruction.bit_field_struct_type << ", "
<< instruction.bit_field.name_and_type.name;
if (instruction.starts_as_zero) {
os << ", starts_as_zero";
}
return os;
}
struct CallIntrinsicInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
CallIntrinsicInstruction(Intrinsic* intrinsic,
@ -403,6 +467,9 @@ struct CallIntrinsicInstruction : InstructionBase {
std::vector<std::string> constexpr_arguments;
};
std::ostream& operator<<(std::ostream& os,
const CallIntrinsicInstruction& instruction);
struct CallCsaMacroInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
CallCsaMacroInstruction(Macro* macro,
@ -424,6 +491,9 @@ struct CallCsaMacroInstruction : InstructionBase {
base::Optional<Block*> catch_block;
};
std::ostream& operator<<(std::ostream& os,
const CallCsaMacroInstruction& instruction);
struct CallCsaMacroAndBranchInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
CallCsaMacroAndBranchInstruction(Macro* macro,
@ -458,6 +528,9 @@ struct CallCsaMacroAndBranchInstruction : InstructionBase {
base::Optional<Block*> catch_block;
};
std::ostream& operator<<(std::ostream& os,
const CallCsaMacroAndBranchInstruction& instruction);
struct MakeLazyNodeInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
MakeLazyNodeInstruction(Macro* macro, const Type* result_type,
@ -473,6 +546,9 @@ struct MakeLazyNodeInstruction : InstructionBase {
std::vector<std::string> constexpr_arguments;
};
std::ostream& operator<<(std::ostream& os,
const MakeLazyNodeInstruction& instruction);
struct CallBuiltinInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
bool IsBlockTerminator() const override { return is_tailcall; }
@ -496,6 +572,9 @@ struct CallBuiltinInstruction : InstructionBase {
base::Optional<Block*> catch_block;
};
std::ostream& operator<<(std::ostream& os,
const CallBuiltinInstruction& instruction);
struct CallBuiltinPointerInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
bool IsBlockTerminator() const override { return is_tailcall; }
@ -511,6 +590,16 @@ struct CallBuiltinPointerInstruction : InstructionBase {
size_t argc;
};
inline std::ostream& operator<<(
std::ostream& os, const CallBuiltinPointerInstruction& instruction) {
os << "CallBuiltinPointer " << *instruction.type
<< ", argc: " << instruction.argc;
if (instruction.is_tailcall) {
os << ", is_tailcall";
}
return os;
}
struct CallRuntimeInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
bool IsBlockTerminator() const override;
@ -535,6 +624,9 @@ struct CallRuntimeInstruction : InstructionBase {
base::Optional<Block*> catch_block;
};
std::ostream& operator<<(std::ostream& os,
const CallRuntimeInstruction& instruction);
struct BranchInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
bool IsBlockTerminator() const override { return true; }
@ -550,6 +642,9 @@ struct BranchInstruction : InstructionBase {
Block* if_false;
};
std::ostream& operator<<(std::ostream& os,
const BranchInstruction& instruction);
struct ConstexprBranchInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
bool IsBlockTerminator() const override { return true; }
@ -567,6 +662,9 @@ struct ConstexprBranchInstruction : InstructionBase {
Block* if_false;
};
std::ostream& operator<<(std::ostream& os,
const ConstexprBranchInstruction& instruction);
struct GotoInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
bool IsBlockTerminator() const override { return true; }
@ -579,6 +677,8 @@ struct GotoInstruction : InstructionBase {
Block* destination;
};
std::ostream& operator<<(std::ostream& os, const GotoInstruction& instruction);
struct GotoExternalInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
bool IsBlockTerminator() const override { return true; }
@ -592,6 +692,15 @@ struct GotoExternalInstruction : InstructionBase {
std::vector<std::string> variable_names;
};
inline std::ostream& operator<<(std::ostream& os,
const GotoExternalInstruction& instruction) {
os << "GotoExternal " << instruction.destination;
for (const std::string& name : instruction.variable_names) {
os << ", " << name;
}
return os;
}
struct ReturnInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
explicit ReturnInstruction(size_t count) : count(count) {}
@ -600,6 +709,11 @@ struct ReturnInstruction : InstructionBase {
size_t count; // How many values to return.
};
inline std::ostream& operator<<(std::ostream& os,
const ReturnInstruction& instruction) {
return os << "Return count: " << instruction.count;
}
struct PrintConstantStringInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
explicit PrintConstantStringInstruction(std::string message)
@ -608,17 +722,39 @@ struct PrintConstantStringInstruction : InstructionBase {
std::string message;
};
inline std::ostream& operator<<(
std::ostream& os, const PrintConstantStringInstruction& instruction) {
return os << "PrintConstantString "
<< StringLiteralQuote(instruction.message);
}
struct AbortInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
enum class Kind { kDebugBreak, kUnreachable, kAssertionFailure };
bool IsBlockTerminator() const override { return kind != Kind::kDebugBreak; }
explicit AbortInstruction(Kind kind, std::string message = "")
: kind(kind), message(std::move(message)) {}
static const char* KindToString(Kind kind) {
switch (kind) {
case Kind::kDebugBreak:
return "kDebugBreak";
case Kind::kUnreachable:
return "kUnreachable";
case Kind::kAssertionFailure:
return "kAssertionFailure";
}
}
Kind kind;
std::string message;
};
inline std::ostream& operator<<(std::ostream& os,
const AbortInstruction& instruction) {
return os << "Abort " << AbortInstruction::KindToString(instruction.kind)
<< ", " << StringLiteralQuote(instruction.message);
}
struct UnsafeCastInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
explicit UnsafeCastInstruction(const Type* destination_type)
@ -629,6 +765,11 @@ struct UnsafeCastInstruction : InstructionBase {
const Type* destination_type;
};
inline std::ostream& operator<<(std::ostream& os,
const UnsafeCastInstruction& instruction) {
return os << "UnsafeCast " << *instruction.destination_type;
}
} // namespace torque
} // namespace internal
} // namespace v8

View File

@ -4,6 +4,8 @@
#include "src/torque/torque-code-generator.h"
#include "src/torque/global-context.h"
namespace v8 {
namespace internal {
namespace torque {
@ -31,8 +33,11 @@ void TorqueCodeGenerator::EmitInstruction(const Instruction& instruction,
#endif
switch (instruction.kind()) {
#define ENUM_ITEM(T) \
case InstructionKind::k##T: \
#define ENUM_ITEM(T) \
case InstructionKind::k##T: \
if (GlobalContext::annotate_ir()) { \
EmitIRAnnotation(instruction.Cast<T>(), stack); \
} \
return EmitInstruction(instruction.Cast<T>(), stack);
TORQUE_INSTRUCTION_LIST(ENUM_ITEM)
#undef ENUM_ITEM

View File

@ -74,6 +74,12 @@ class TorqueCodeGenerator {
void EmitInstruction(const Instruction& instruction,
Stack<std::string>* stack);
template <typename T>
void EmitIRAnnotation(const T& instruction, Stack<std::string>* stack) {
out() << " // " << instruction
<< ", starting stack size: " << stack->Size() << "\n";
}
#define EMIT_INSTRUCTION_DECLARATION(T) \
void EmitInstruction(const T& instruction, Stack<std::string>* stack);
TORQUE_BACKEND_AGNOSTIC_INSTRUCTION_LIST(EMIT_INSTRUCTION_DECLARATION)

View File

@ -53,6 +53,9 @@ void CompileCurrentAst(TorqueCompilerOptions options) {
if (options.force_assert_statements) {
GlobalContext::SetForceAssertStatements();
}
if (options.annotate_ir) {
GlobalContext::SetAnnotateIR();
}
TargetArchitecture::Scope target_architecture(options.force_32bit_output);
TypeOracle::Scope type_oracle;
CurrentScope::Scope current_namespace(GlobalContext::GetDefaultNamespace());

View File

@ -30,6 +30,9 @@ struct TorqueCompilerOptions {
// architectures. Note that this does not needed in Chromium/V8 land, since we
// always build with the same bit width as the target architecture.
bool force_32bit_output = false;
// Adds extra comments in output that show Torque intermediate representation.
bool annotate_ir = false;
};
struct TorqueCompilerResult {

View File

@ -34,6 +34,8 @@ int WrappedMain(int argc, const char** argv) {
options.v8_root = std::string(argv[++i]);
} else if (argument == "-m32") {
options.force_32bit_output = true;
} else if (argument == "-annotate-ir") {
options.annotate_ir = true;
} else {
// Otherwise it's a .tq file. Remember it for compilation.
files.emplace_back(std::move(argument));