Jit/hir/printer.cpp (912 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#include "Jit/hir/printer.h"
#include "Python.h"
#include "Jit/hir/hir.h"
#include "Jit/util.h"
#include <fmt/format.h>
#include <json.hpp>
#include <algorithm>
#include <fstream>
#include <sstream>
#include <vector>
namespace jit {
namespace hir {
void HIRPrinter::Indent() {
indent_level_ += 1;
}
std::ostream& HIRPrinter::Indented(std::ostream& os) {
os << line_prefix_;
for (int i = 0; i < indent_level_; i++) {
os << " ";
}
return os;
}
void HIRPrinter::Dedent() {
indent_level_ -= 1;
}
void HIRPrinter::Print(std::ostream& os, const Function& func) {
fmt::print(
os, "fun {} {{\n", func.fullname.empty() ? "<unknown>" : func.fullname);
Indent();
Print(os, func.cfg);
Dedent();
os << "}" << std::endl;
}
void HIRPrinter::Print(std::ostream& os, const CFG& cfg) {
return Print(os, cfg, cfg.entry_block);
}
void HIRPrinter::Print(std::ostream& os, const CFG& cfg, BasicBlock* start) {
std::vector<BasicBlock*> blocks = cfg.GetRPOTraversal(start);
auto last_block = blocks.back();
for (auto block : blocks) {
Print(os, *block);
if (block != last_block) {
os << std::endl;
}
}
}
void HIRPrinter::Print(std::ostream& os, const BasicBlock& block) {
Indented(os);
fmt::print(os, "bb {}", block.id);
auto& in_edges = block.in_edges();
if (!in_edges.empty()) {
std::vector<const Edge*> edges(in_edges.begin(), in_edges.end());
std::sort(edges.begin(), edges.end(), [](auto& e1, auto& e2) {
return e1->from()->id < e2->from()->id;
});
os << " (preds ";
auto sep = "";
for (auto edge : edges) {
fmt::print(os, "{}{}", sep, edge->from()->id);
sep = ", ";
}
os << ")";
}
os << " {\n";
Indent();
for (auto& instr : block) {
if (instr.IsSnapshot() && !show_snapshots_) {
continue;
}
Print(os, instr);
os << std::endl;
}
Dedent();
Indented(os) << "}" << std::endl;
}
static void print_reg_states(
std::ostream& os,
const std::vector<RegState>& reg_states) {
auto rss = reg_states;
std::sort(rss.begin(), rss.end(), [](RegState& a, RegState& b) {
return a.reg->id() < b.reg->id();
});
os << fmt::format("<{}>", rss.size());
if (!rss.empty()) {
os << " ";
}
auto sep = "";
for (auto& reg_state : rss) {
const char* prefix = "?";
switch (reg_state.value_kind) {
case ValueKind::kSigned: {
prefix = "s";
break;
}
case ValueKind::kUnsigned: {
prefix = "uns";
break;
}
case ValueKind::kBool: {
prefix = "bool";
break;
}
case ValueKind::kDouble:
prefix = "double";
break;
case ValueKind::kObject: {
switch (reg_state.ref_kind) {
case RefKind::kUncounted: {
prefix = "unc";
break;
}
case RefKind::kBorrowed: {
prefix = "b";
break;
}
case RefKind::kOwned: {
prefix = "o";
break;
}
}
break;
}
}
os << fmt::format("{}{}:{}", sep, prefix, reg_state.reg->name());
sep = " ";
}
}
static const int kMaxASCII = 127;
static std::string escape_non_ascii(const std::string& str) {
std::string result;
for (size_t i = 0; i < str.size(); ++i) {
unsigned char c = str[i];
if (c > kMaxASCII) {
result += '\\';
result += std::to_string(c);
} else {
result += static_cast<char>(c);
}
}
return result;
}
static std::string escape_unicode(const char* data, Py_ssize_t size) {
std::string ret = "\"";
for (Py_ssize_t i = 0; i < size; ++i) {
char c = data[i];
switch (c) {
case '"':
case '\\':
ret += '\\';
ret += c;
break;
case '\n':
ret += "\\n";
break;
default:
if (static_cast<unsigned char>(c) > kMaxASCII) {
ret += '\\';
ret += std::to_string(static_cast<unsigned char>(c));
} else {
ret += c;
}
break;
}
}
ret += '"';
return ret;
}
static std::string escape_unicode(PyObject* str) {
Py_ssize_t size;
const char* data = PyUnicode_AsUTF8AndSize(str, &size);
if (data == nullptr) {
PyErr_Clear();
return "";
}
return escape_unicode(data, size);
}
static std::string format_name_impl(int idx, PyObject* names) {
return fmt::format(
"{}; {}", idx, escape_unicode(PyTuple_GET_ITEM(names, idx)));
}
static std::string format_name(const Instr& instr, int idx) {
auto code = instr.code();
if (idx < 0 || code == nullptr) {
return fmt::format("{}", idx);
}
return format_name_impl(idx, code->co_names);
}
static std::string format_load_super(const LoadSuperBase& load) {
auto code = load.code();
if (code == nullptr) {
return fmt::format("{} {}", load.name_idx(), load.no_args_in_super_call());
}
return fmt::format(
"{}, {}",
format_name_impl(load.name_idx(), code->co_names),
load.no_args_in_super_call());
}
static std::string format_varname(const Instr& instr, int idx) {
auto code = instr.code();
if (idx < 0 || code == nullptr) {
return fmt::format("{}", idx);
}
auto names = getVarnameTuple(code, &idx);
return format_name_impl(idx, names);
}
static std::string format_immediates(const Instr& instr) {
switch (instr.opcode()) {
case Opcode::kAssign:
case Opcode::kBatchDecref:
case Opcode::kBuildString:
case Opcode::kCheckExc:
case Opcode::kCheckNeg:
case Opcode::kCheckSequenceBounds:
case Opcode::kClearError:
case Opcode::kDecref:
case Opcode::kDeleteSubscr:
case Opcode::kDeopt:
case Opcode::kEndInlinedFunction:
case Opcode::kGetIter:
case Opcode::kGetTuple:
case Opcode::kGuard:
case Opcode::kIncref:
case Opcode::kInitFunction:
case Opcode::kInvokeIterNext:
case Opcode::kIsErrStopAsyncIteration:
case Opcode::kIsInstance:
case Opcode::kIsNegativeAndErrOccurred:
case Opcode::kIsSubtype:
case Opcode::kIsTruthy:
case Opcode::kListAppend:
case Opcode::kListExtend:
case Opcode::kLoadCellItem:
case Opcode::kLoadCurrentFunc:
case Opcode::kLoadEvalBreaker:
case Opcode::kLoadFieldAddress:
case Opcode::kLoadVarObjectSize:
case Opcode::kMakeCell:
case Opcode::kMakeFunction:
case Opcode::kMakeSet:
case Opcode::kMakeTupleFromList:
case Opcode::kMergeDictUnpack:
case Opcode::kMergeSetUnpack:
case Opcode::kRaise:
case Opcode::kRepeatList:
case Opcode::kRepeatTuple:
case Opcode::kRunPeriodicTasks:
case Opcode::kSetCurrentAwaiter:
case Opcode::kSetCellItem:
case Opcode::kSetDictItem:
case Opcode::kSetSetItem:
case Opcode::kSnapshot:
case Opcode::kStealCellItem:
case Opcode::kStoreArrayItem:
case Opcode::kStoreSubscr:
case Opcode::kWaitHandleLoadCoroOrResult:
case Opcode::kWaitHandleLoadWaiter:
case Opcode::kWaitHandleRelease:
case Opcode::kXDecref:
case Opcode::kXIncref: {
return "";
}
case Opcode::kBeginInlinedFunction:
return static_cast<const BeginInlinedFunction&>(instr).fullname();
case Opcode::kLoadArrayItem: {
const auto& load = static_cast<const LoadArrayItem&>(instr);
return load.offset() == 0 ? "" : fmt::format("Offset[{}]", load.offset());
}
case Opcode::kReturn: {
const auto& ret = static_cast<const Return&>(instr);
return ret.type() != TObject ? ret.type().toString() : "";
}
case Opcode::kCallEx: {
const auto& call = static_cast<const CallEx&>(instr);
return call.isAwaited() ? "awaited" : "";
}
case Opcode::kCallExKw: {
const auto& call = static_cast<const CallExKw&>(instr);
return call.isAwaited() ? "awaited" : "";
}
case Opcode::kBinaryOp: {
const auto& bin_op = static_cast<const BinaryOp&>(instr);
return GetBinaryOpName(bin_op.op());
}
case Opcode::kUnaryOp: {
const auto& unary_op = static_cast<const UnaryOp&>(instr);
return GetUnaryOpName(unary_op.op());
}
case Opcode::kBranch: {
const auto& branch = static_cast<const Branch&>(instr);
return fmt::format("{}", branch.target()->id);
}
case Opcode::kVectorCall:
case Opcode::kVectorCallStatic:
case Opcode::kVectorCallKW: {
const auto& call = static_cast<const VectorCallBase&>(instr);
return fmt::format(
"{}{}", call.numArgs(), call.isAwaited() ? ", awaited" : "");
}
case Opcode::kCallCFunc: {
const auto& call = static_cast<const CallCFunc&>(instr);
return call.funcName();
}
case Opcode::kCallMethod: {
const auto& call = static_cast<const CallMethod&>(instr);
return fmt::format(
"{}{}", call.NumOperands(), call.isAwaited() ? ", awaited" : "");
}
case Opcode::kCallStatic: {
const auto& call = static_cast<const CallStatic&>(instr);
return fmt::format("{}", call.NumOperands());
}
case Opcode::kCallStaticRetVoid: {
const auto& call = static_cast<const CallStatic&>(instr);
return fmt::format("{}", call.NumOperands());
}
case Opcode::kInvokeStaticFunction: {
const auto& call = static_cast<const InvokeStaticFunction&>(instr);
return fmt::format(
"{}.{}, {}, {}",
PyUnicode_AsUTF8(call.func()->func_module),
PyUnicode_AsUTF8(call.func()->func_qualname),
call.NumOperands(),
call.ret_type());
}
case Opcode::kInvokeMethod: {
const auto& call = static_cast<const InvokeMethod&>(instr);
return fmt::format(
"{}{}", call.NumOperands(), call.isAwaited() ? ", awaited" : "");
}
case Opcode::kLoadField: {
const auto& lf = static_cast<const LoadField&>(instr);
std::size_t offset = lf.offset();
#ifdef Py_TRACE_REFS
// Keep these stable from the offset of ob_refcnt, in trace refs
// we have 2 extra next/prev pointers linking all objects together
offset -= (sizeof(PyObject*) * 2);
#endif
return fmt::format(
"{}@{}, {}, {}",
lf.name(),
offset,
lf.type(),
lf.borrowed() ? "borrowed" : "owned");
}
case Opcode::kStoreField: {
const auto& sf = static_cast<const StoreField&>(instr);
return fmt::format("{}@{}", sf.name(), sf.offset());
}
case Opcode::kCast: {
const auto& cast = static_cast<const Cast&>(instr);
if (cast.optional()) {
return fmt::format("Optional[{}]", cast.pytype()->tp_name);
} else {
return fmt::format("{}", cast.pytype()->tp_name);
}
}
case Opcode::kTpAlloc: {
const auto& tp_alloc = static_cast<const TpAlloc&>(instr);
return fmt::format("{}", tp_alloc.pytype()->tp_name);
}
case Opcode::kCompare: {
const auto& cmp = static_cast<const Compare&>(instr);
return GetCompareOpName(cmp.op());
}
case Opcode::kLongCompare: {
const auto& cmp = static_cast<const LongCompare&>(instr);
return GetCompareOpName(cmp.op());
}
case Opcode::kLongBinaryOp: {
const auto& bin = static_cast<const LongBinaryOp&>(instr);
return GetBinaryOpName(bin.op());
}
case Opcode::kCompareBool: {
const auto& cmp = static_cast<const Compare&>(instr);
return GetCompareOpName(cmp.op());
}
case Opcode::kIntConvert: {
const auto& conv = static_cast<const IntConvert&>(instr);
return conv.type().toString();
}
case Opcode::kPrimitiveUnaryOp: {
const auto& unary = static_cast<const PrimitiveUnaryOp&>(instr);
return GetPrimitiveUnaryOpName(unary.op());
}
case Opcode::kCondBranch:
case Opcode::kCondBranchIterNotDone:
case Opcode::kCondBranchCheckType: {
const auto& cond = static_cast<const CondBranchBase&>(instr);
auto targets =
fmt::format("{}, {}", cond.true_bb()->id, cond.false_bb()->id);
if (cond.IsCondBranchCheckType()) {
Type type = static_cast<const CondBranchCheckType&>(cond).type();
return fmt::format("{}, {}", targets, type);
}
return targets;
}
case Opcode::kDoubleBinaryOp: {
const auto& bin_op = static_cast<const DoubleBinaryOp&>(instr);
return GetBinaryOpName(bin_op.op());
}
case Opcode::kLoadArg: {
const auto& load = static_cast<const LoadArg&>(instr);
auto varname = format_varname(load, load.arg_idx());
if (load.type() == TObject) {
return varname;
}
return fmt::format("{}, {}", varname, load.type());
}
case Opcode::kLoadAttrSpecial: {
const auto& load = static_cast<const LoadAttrSpecial&>(instr);
_Py_Identifier* id = load.id();
return fmt::format("\"{}\"", id->string);
}
case Opcode::kLoadMethod: {
const auto& load = static_cast<const LoadMethod&>(instr);
return format_name(load, load.name_idx());
}
case Opcode::kLoadMethodSuper: {
return format_load_super(static_cast<const LoadSuperBase&>(instr));
}
case Opcode::kLoadAttrSuper: {
return format_load_super(static_cast<const LoadSuperBase&>(instr));
}
case Opcode::kLoadConst: {
const auto& load = static_cast<const LoadConst&>(instr);
return fmt::format("{}", load.type());
}
case Opcode::kLoadFunctionIndirect: {
const auto& load = static_cast<const LoadFunctionIndirect&>(instr);
PyObject* func = *load.funcptr();
const char* name;
if (PyFunction_Check(func)) {
name = PyUnicode_AsUTF8(((PyFunctionObject*)func)->func_name);
} else {
name = Py_TYPE(func)->tp_name;
}
return fmt::format("{}", name);
}
case Opcode::kIntBinaryOp: {
const auto& bin_op = static_cast<const IntBinaryOp&>(instr);
return GetBinaryOpName(bin_op.op());
}
case Opcode::kPrimitiveCompare: {
const auto& cmp = static_cast<const PrimitiveCompare&>(instr);
return GetPrimitiveCompareOpName(cmp.op());
}
case Opcode::kPrimitiveBox: {
const auto& box = static_cast<const PrimitiveBox&>(instr);
return fmt::format("{}", box.type());
}
case Opcode::kPrimitiveUnbox: {
const auto& unbox = static_cast<const PrimitiveUnbox&>(instr);
return fmt::format("{}", unbox.type());
}
case Opcode::kLoadGlobalCached: {
const auto& load = static_cast<const LoadGlobalCached&>(instr);
return format_name(load, load.name_idx());
}
case Opcode::kLoadGlobal: {
const auto& load = static_cast<const LoadGlobal&>(instr);
return format_name(load, load.name_idx());
}
case Opcode::kMakeListTuple: {
const auto& makelt = static_cast<const MakeListTuple&>(instr);
return fmt::format(
"{}, {}", makelt.is_tuple() ? "tuple" : "list", makelt.nvalues());
}
case Opcode::kInitListTuple: {
const auto& initlt = static_cast<const InitListTuple&>(instr);
return fmt::format(
"{}, {}", initlt.is_tuple() ? "tuple" : "list", initlt.num_args());
}
case Opcode::kLoadTupleItem: {
const auto& loaditem = static_cast<const LoadTupleItem&>(instr);
return fmt::format("{}", loaditem.idx());
}
case Opcode::kMakeCheckedDict: {
const auto& makedict = static_cast<const MakeCheckedDict&>(instr);
return fmt::format("{} {}", makedict.type(), makedict.GetCapacity());
}
case Opcode::kMakeCheckedList: {
const auto& makelist = static_cast<const MakeCheckedList&>(instr);
return fmt::format("{} {}", makelist.type(), makelist.GetCapacity());
}
case Opcode::kMakeDict: {
const auto& makedict = static_cast<const MakeDict&>(instr);
return fmt::format("{}", makedict.GetCapacity());
}
case Opcode::kPhi: {
const auto& phi = static_cast<const Phi&>(instr);
std::stringstream ss;
bool first = true;
for (auto& bb : phi.basic_blocks()) {
if (first) {
first = false;
} else {
ss << ", ";
}
ss << bb->id;
}
return ss.str();
}
case Opcode::kDeleteAttr:
case Opcode::kLoadAttr:
case Opcode::kStoreAttr: {
const auto& named = static_cast<const DeoptBaseWithNameIdx&>(instr);
return format_name(named, named.name_idx());
}
case Opcode::kInPlaceOp: {
const auto& inplace_op = static_cast<const InPlaceOp&>(instr);
return GetInPlaceOpName(inplace_op.op());
}
case Opcode::kBuildSlice: {
const auto& build_slice = static_cast<const BuildSlice&>(instr);
return fmt::format("{}", build_slice.NumOperands());
}
case Opcode::kLoadTypeAttrCacheItem: {
const auto& i = static_cast<const LoadTypeAttrCacheItem&>(instr);
return fmt::format("{}, {}", i.cache_id(), i.item_idx());
}
case Opcode::kFillTypeAttrCache: {
const auto& ftac = static_cast<const FillTypeAttrCache&>(instr);
return fmt::format("{}, {}", ftac.cache_id(), ftac.name_idx());
}
case Opcode::kSetFunctionAttr: {
const auto& set_fn_attr = static_cast<const SetFunctionAttr&>(instr);
return fmt::format("{}", functionFieldName(set_fn_attr.field()));
}
case Opcode::kCheckField:
case Opcode::kCheckFreevar:
case Opcode::kCheckVar: {
const auto& check = static_cast<const CheckBaseWithName&>(instr);
return escape_unicode(check.name());
}
case Opcode::kGuardIs: {
const auto& gs = static_cast<const GuardIs&>(instr);
return fmt::format("{}", getStablePointer(gs.target()));
}
case Opcode::kGuardType: {
const auto& gs = static_cast<const GuardType&>(instr);
return fmt::format("{}", gs.target().toString());
}
case Opcode::kHintType: {
std::ostringstream os;
auto profile_sep = "";
const auto& hint = static_cast<const HintType&>(instr);
os << fmt::format("{}, ", hint.NumOperands());
for (auto types_seen : hint.seenTypes()) {
os << fmt::format("{}<", profile_sep);
auto type_sep = "";
for (auto type : types_seen) {
os << fmt::format("{}{}", type_sep, type.toString());
type_sep = ", ";
}
os << ">";
profile_sep = ", ";
}
return os.str();
}
case Opcode::kUseType: {
const auto& gs = static_cast<const UseType&>(instr);
return fmt::format("{}", gs.type().toString());
}
case Opcode::kRaiseAwaitableError: {
const auto& ra = static_cast<const RaiseAwaitableError&>(instr);
if (ra.with_opcode() == BEFORE_ASYNC_WITH) {
return "BEFORE_ASYNC_WITH";
}
if (ra.with_opcode() == WITH_CLEANUP_START) {
return "WITH_CLEANUP_START";
}
return fmt::format("invalid:{}", ra.with_opcode());
}
case Opcode::kRaiseStatic: {
const auto& pyerr = static_cast<const RaiseStatic&>(instr);
std::ostringstream os;
print_reg_states(os, pyerr.live_regs());
return fmt::format(
"{}, \"{}\", <{}>",
PyExceptionClass_Name(pyerr.excType()),
pyerr.fmt(),
os.str());
}
case Opcode::kInitialYield:
case Opcode::kYieldValue:
case Opcode::kYieldAndYieldFrom:
case Opcode::kYieldFrom: {
std::ostringstream os;
auto sep = "";
for (auto reg : dynamic_cast<const YieldBase*>(&instr)->liveOwnedRegs()) {
os << fmt::format("{}o:{}", sep, reg->name());
sep = ", ";
}
for (auto reg :
dynamic_cast<const YieldBase*>(&instr)->liveUnownedRegs()) {
os << fmt::format("{}u:{}", sep, reg->name());
sep = ", ";
}
return os.str();
}
case Opcode::kImportFrom: {
const auto& import_from = static_cast<const ImportFrom&>(instr);
return format_name(import_from, import_from.nameIdx());
}
case Opcode::kImportName: {
const auto& import_name = static_cast<const ImportName&>(instr);
return format_name(import_name, import_name.name_idx());
}
case Opcode::kRefineType: {
const auto& rt = static_cast<const RefineType&>(instr);
return rt.type().toString();
}
case Opcode::kFormatValue: {
int conversion = static_cast<const FormatValue&>(instr).conversion();
switch (conversion) {
case FVC_NONE:
return "None";
case FVC_STR:
return "Str";
case FVC_REPR:
return "Repr";
case FVC_ASCII:
return "ASCII";
}
JIT_CHECK(false, "Unknown conversion type.");
}
case Opcode::kUnpackExToTuple: {
const auto& i = static_cast<const UnpackExToTuple&>(instr);
return fmt::format("{}, {}", i.before(), i.after());
}
case Opcode::kDeoptPatchpoint: {
const auto& dp = static_cast<const DeoptPatchpoint&>(instr);
return fmt::format("{}", getStablePointer(dp.patcher()));
}
}
JIT_CHECK(false, "invalid opcode %d", static_cast<int>(instr.opcode()));
}
void HIRPrinter::Print(std::ostream& os, const Instr& instr) {
Indented(os);
if (Register* dst = instr.GetOutput()) {
os << dst->name();
if (dst->type() != TTop) {
os << ":" << dst->type();
}
os << " = ";
}
os << instr.opname();
auto immed = format_immediates(instr);
if (!immed.empty()) {
os << "<" << immed << ">";
}
for (size_t i = 0, n = instr.NumOperands(); i < n; ++i) {
auto op = instr.GetOperand(i);
if (op != nullptr) {
os << " " << op->name();
} else {
os << " nullptr";
}
}
auto fs = get_frame_state(instr);
auto db = dynamic_cast<const DeoptBase*>(&instr);
if (db != nullptr) {
os << " {" << std::endl;
Indent();
if (!db->descr().empty()) {
Indented(os) << fmt::format("Descr '{}'\n", db->descr());
}
if (Register* guilty_reg = db->guiltyReg()) {
Indented(os) << fmt::format("GuiltyReg {}\n", *guilty_reg);
}
if (db->live_regs().size() > 0) {
Indented(os) << "LiveValues";
print_reg_states(os, db->live_regs());
os << std::endl;
}
if (fs != nullptr) {
Indented(os) << "FrameState {" << std::endl;
Indent();
Print(os, *fs);
Dedent();
Indented(os) << "}" << std::endl;
}
Dedent();
Indented(os) << "}";
} else if (fs != nullptr) {
os << " {" << std::endl;
Indent();
Print(os, *fs);
Dedent();
Indented(os) << "}";
}
}
void HIRPrinter::Print(std::ostream& os, const FrameState& state) {
Indented(os) << "NextInstrOffset " << state.next_instr_offset << std::endl;
auto nlocals = state.locals.size();
if (nlocals > 0) {
Indented(os) << "Locals<" << nlocals << ">";
for (auto reg : state.locals) {
if (reg == nullptr) {
os << " <null>";
} else {
os << " " << reg->name();
}
}
os << std::endl;
}
auto ncells = state.cells.size();
if (ncells > 0) {
Indented(os) << "Cells<" << ncells << ">";
for (auto reg : state.cells) {
if (reg == nullptr) {
os << " <null>";
} else {
os << " " << reg->name();
}
}
os << std::endl;
}
auto opstack_size = state.stack.size();
if (opstack_size > 0) {
Indented(os) << "Stack<" << opstack_size << ">";
for (std::size_t i = 0; i < opstack_size; i++) {
os << " " << state.stack.at(i)->name();
}
os << std::endl;
}
auto& bs = state.block_stack;
if (bs.size() > 0) {
Indented(os) << "BlockStack {" << std::endl;
Indent();
for (std::size_t i = 0; i < bs.size(); i++) {
auto& entry = bs.at(i);
Indented(os) << fmt::format(
"Opcode {} HandlerOff {} StackLevel {}\n",
entry.opcode,
entry.handler_off,
entry.stack_level);
}
Dedent();
Indented(os) << "}" << std::endl;
}
}
static int lastLineNumber(PyCodeObject* code) {
int last_line = -1;
for (Py_ssize_t off = 0; off < code->co_codelen;
off += sizeof(_Py_CODEUNIT)) {
last_line = std::max(last_line, PyCode_Addr2Line(code, off));
}
return last_line;
}
nlohmann::json JSONPrinter::PrintSource(const Function& func) {
PyCodeObject* code = func.code;
if (code == nullptr) {
// No code; must be from a test;
return nlohmann::json();
}
PyObject* co_filename = code->co_filename;
JIT_CHECK(co_filename != nullptr, "filename must not be null");
const char* filename = PyUnicode_AsUTF8(co_filename);
if (filename == nullptr) {
PyErr_Clear();
return nlohmann::json();
}
std::ifstream infile(filename);
if (!infile) {
// TODO(emacs): Does this handle nonexistent files?
return nlohmann::json();
}
nlohmann::json result;
result["name"] = "Source";
result["type"] = "text";
result["filename"] = filename;
int start = code->co_firstlineno;
result["first_line_number"] = start;
nlohmann::json lines;
int end = lastLineNumber(code);
std::string line;
int count = 0;
while (std::getline(infile, line)) {
count++;
if (count > end) {
break;
} // done
if (count < start) {
continue;
} // too early
lines.emplace_back(line);
}
infile.close();
result["lines"] = lines;
return result;
}
// TODO(emacs): Fake line numbers using instruction addresses to better
// associate how instructions change over time
// TODO(emacs): Make JSON utils so we stop copying so much stuff around
#define MAKE_OPNAME(opname, opnum) {opnum, #opname},
static std::unordered_map<unsigned, const char*> kOpnames{
PY_OPCODES(MAKE_OPNAME)};
#undef MAKE_OPNAME
// TODO(emacs): Write basic blocks for bytecode (using BytecodeInstructionBlock
// and BlockMap?). Need to figure out how to allocate block IDs without
// actually modifying the HIR function in place.
nlohmann::json JSONPrinter::PrintBytecode(const Function& func) {
nlohmann::json result;
result["name"] = "Bytecode";
result["type"] = "asm";
nlohmann::json block;
block["name"] = "bb0";
nlohmann::json instrs_json = nlohmann::json::array();
PyCodeObject* code = func.code;
_Py_CODEUNIT* instrs = code->co_rawcode;
Py_ssize_t num_instrs = code->co_codelen / sizeof(_Py_CODEUNIT);
for (Py_ssize_t i = 0, off = 0; i < num_instrs;
i++, off += sizeof(_Py_CODEUNIT)) {
unsigned char opcode = _Py_OPCODE(instrs[i]);
unsigned char oparg = _Py_OPARG(instrs[i]);
nlohmann::json instr;
instr["address"] = off;
instr["line"] = PyCode_Addr2Line(code, off);
instr["opcode"] = fmt::format("{} {}", kOpnames[opcode], oparg);
instrs_json.emplace_back(instr);
}
block["instrs"] = instrs_json;
result["blocks"] = nlohmann::json::array({block});
return result;
}
nlohmann::json JSONPrinter::Print(const Instr& instr) {
nlohmann::json result;
result["line"] = instr.lineNumber();
Register* output = instr.GetOutput();
if (output != nullptr) {
result["output"] = output->name();
if (output->type() != TTop) {
// Output must be escaped since literal Python values such as \222 can be
// in the type.
result["type"] = escape_non_ascii(output->type().toString());
}
}
std::string opcode = instr.opname();
auto immed = format_immediates(instr);
if (!immed.empty()) {
// Output must be escaped since literal Python values such as \222 can be
// in the type.
opcode += "<" + escape_non_ascii(immed) + ">";
}
result["opcode"] = opcode;
nlohmann::json operands = nlohmann::json::array();
for (size_t i = 0, n = instr.NumOperands(); i < n; i++) {
auto op = instr.GetOperand(i);
if (op != nullptr) {
operands.emplace_back(op->name());
} else {
operands.emplace_back(nlohmann::json());
}
}
result["operands"] = operands;
return result;
}
nlohmann::json JSONPrinter::Print(const BasicBlock& block) {
nlohmann::json result;
result["name"] = fmt::format("bb{}", block.id);
auto& in_edges = block.in_edges();
nlohmann::json preds = nlohmann::json::array();
if (!in_edges.empty()) {
std::vector<const Edge*> edges(in_edges.begin(), in_edges.end());
std::sort(edges.begin(), edges.end(), [](auto& e1, auto& e2) {
return e1->from()->id < e2->from()->id;
});
for (auto edge : edges) {
preds.emplace_back(fmt::format("bb{}", edge->from()->id));
}
}
result["preds"] = preds;
nlohmann::json instrs = nlohmann::json::array();
for (auto& instr : block) {
if (instr.IsSnapshot()) {
continue;
}
if (instr.IsTerminator()) {
// Handle specially below
break;
}
instrs.emplace_back(Print(instr));
}
result["instrs"] = instrs;
const Instr* instr = block.GetTerminator();
JIT_CHECK(instr != nullptr, "expected terminator");
result["terminator"] = Print(*instr);
nlohmann::json succs = nlohmann::json::array();
for (std::size_t i = 0; i < instr->numEdges(); i++) {
BasicBlock* succ = instr->successor(i);
succs.emplace_back(fmt::format("bb{}", succ->id));
}
result["succs"] = succs;
return result;
}
nlohmann::json JSONPrinter::Print(const CFG& cfg) {
std::vector<BasicBlock*> blocks = cfg.GetRPOTraversal();
nlohmann::json result;
for (auto block : blocks) {
result.emplace_back(Print(*block));
}
return result;
}
void JSONPrinter::Print(
nlohmann::json& passes,
const Function& func,
const char* pass_name,
std::size_t time_ns) {
nlohmann::json result;
result["name"] = pass_name;
result["type"] = "ssa";
result["time_ns"] = time_ns;
result["blocks"] = Print(func.cfg);
passes.push_back(result);
}
void DebugPrint(const CFG& cfg) {
HIRPrinter(true).Print(std::cout, cfg);
}
void DebugPrint(const BasicBlock& block) {
HIRPrinter(true).Print(std::cout, block);
}
void DebugPrint(const Instr& instr) {
HIRPrinter(true).Print(std::cout, instr);
}
} // namespace hir
} // namespace jit