Skip to content

Commit

Permalink
Create ProfileRuntime to handle all profiling operations
Browse files Browse the repository at this point in the history
Summary:
Trying to consolidate all the profiling APIs into one place.  The idea is to use
the same class both for recording profiling information and for fetching
profiling information when emitting HIR.

Currently the two operations are independent, profiling data captured during
program execution is recorded as part of `TypeProfiler` objects in the `Runtime`
singleton, and profiling data used by HIR generation is fetched from the
`ProfileData` singleton after it's been read from a file.  The goal is to enable
HIR generation to use the profiling data that's been captured while the program
has been executing.  This will be a future change.

The new API works mostly the same as before, but there are behavior changes to
call out.

* Clearing profile data now always clears both the `TypeProfiler` objects and
  `ProfileData`.  This is fine because we only ever use one of them during
  execution, never both.  There's an extra check in `ProfileRuntime` that
  prevents the use of `profileInstr()` after `deserialize()` has been called.
  It doesn't really make sense to load profiling information ahead-of-time and
  to try to combine it with profiling information that's been gathered during
  the current execution.

* `HIRBuilder` will now re-fetch the profiling information for a given code
  object on every bytecode instruction.  This is more work than the current API
  as we're accessing an extra hash map for every bytecode instruction, but it
  keeps the API cleaner.  The `CodeProfile` objects we have for recording
  profiles don't match the `CodeProfileData` objects we get from file-based
  profiles.  We could give them a shared interface but that would require
  virtual methods.

* `ProfileRuntime` always returns a single vector of expected types, rather than
  a vector of vectors of all possible type combinations.  This means that
  `HIRBuilder` will no longer emit `HintType` instructions for polymorphic
  bytecode instructions.  `HintType` is currently unused, so the resulting
  machine code should look the same.

* If there's no type associated with a specific value in the types vector
  returned by `getProfiledTypes()`, then it will be returned as `TTop`. These
  are skipped over when generating HIR like what was being done with
  `PyFrameObject*` values that were null.

Reviewed By: swtaarrs

Differential Revision: D47556446

fbshipit-source-id: 3b0831e1b148c2738f140fa964d3eed97a67a55e
  • Loading branch information
Alex Malyshev authored and facebook-github-bot committed Aug 4, 2023
1 parent 24da019 commit d498b39
Show file tree
Hide file tree
Showing 14 changed files with 497 additions and 275 deletions.
1 change: 1 addition & 0 deletions Cinder/module/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"Jit/log.cpp",
"Jit/perf_jitdump.cpp",
"Jit/profile_data.cpp",
"Jit/profile_runtime.cpp",
"Jit/pyjit.cpp",
"Jit/runtime.cpp",
"Jit/runtime_support.cpp",
Expand Down
55 changes: 15 additions & 40 deletions Jit/hir/builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "Jit/hir/preload.h"
#include "Jit/hir/ssa.h"
#include "Jit/hir/type.h"
#include "Jit/profile_runtime.h"
#include "Jit/pyjit.h"
#include "Jit/ref.h"
#include "Jit/threaded_compile.h"
Expand Down Expand Up @@ -638,24 +639,23 @@ BasicBlock* HIRBuilder::buildHIRImpl(

void HIRBuilder::emitProfiledTypes(
TranslationContext& tc,
const CodeProfileData& profile_data,
const ProfileRuntime& profile_runtime,
const BytecodeInstruction& bc_instr) {
if (bc_instr.opcode() == CALL_METHOD) {
// TODO(T107300350): Ignore profiling data for CALL_METHOD because we lie
// about its stack inputs.
return;
}

const PolymorphicTypes types =
getProfiledTypes(profile_data, bc_instr.offset());
if (types.empty() || types[0].size() > tc.frame.stack.size()) {
std::vector<Type> types =
profile_runtime.getProfiledTypes(tc.frame.code, bc_instr.offset());

if (types.empty() || types.size() > tc.frame.stack.size()) {
// The types are either absent or invalid (e.g., from a different version
// of the code than what we're running now).
return;
}

const std::vector<BorrowedRef<PyTypeObject>> first_profile = types[0];

// TODO(T115140951): Add a more robust method of determining what type
// information differs between interpreter runs and static JITted bytecode
if (bc_instr.opcode() == STORE_FIELD) {
Expand All @@ -668,7 +668,7 @@ void HIRBuilder::emitProfiledTypes(
// Except for a few special cases, all instructions profile all of their
// inputs, with deeper stack elements first.
// TODO(T127457244): Centralize this information.
ssize_t stack_idx = first_profile.size() - 1;
ssize_t stack_idx = types.size() - 1;
if (bc_instr.opcode() == CALL_FUNCTION) {
stack_idx = bc_instr.oparg();
} else if (
Expand All @@ -678,32 +678,13 @@ void HIRBuilder::emitProfiledTypes(
} else if (bc_instr.opcode() == WITH_EXCEPT_START) {
stack_idx = 6;
}
if (types.size() == 1) {
for (auto type : first_profile) {
if (type != nullptr) {
Register* value = tc.frame.stack.top(stack_idx);
GuardType* guard =
tc.emit<GuardType>(value, Type::fromTypeExact(type), value);
guard->setGuiltyReg(value);
}
stack_idx--;
for (auto& type : types) {
if (type != TTop) {
Register* value = tc.frame.stack.top(stack_idx);
GuardType* guard = tc.emit<GuardType>(value, type, value);
guard->setGuiltyReg(value);
}
} else {
ProfiledTypes all_types;
for (auto type_vec : types) {
std::vector<Type> types;
for (auto type : type_vec) {
if (type != nullptr) {
types.emplace_back(Type::fromTypeExact(type));
}
}
all_types.emplace_back(types);
}
std::vector<Register*> args;
for (size_t i = 0; i < first_profile.size(); i++) {
args.emplace_back(tc.frame.stack.top(stack_idx - i));
}
tc.emit<HintType>(args.size(), all_types, args);
stack_idx--;
}
}

Expand Down Expand Up @@ -783,11 +764,7 @@ void HIRBuilder::translate(
std::unordered_set<BasicBlock*> processed;
std::unordered_set<BasicBlock*> loop_headers;

const CodeProfileData* profile_data = getProfileData(tc.frame.code);
JIT_DLOG(
"Found types for %d locations within %s.",
profile_data ? profile_data->size() : 0,
irfunc.fullname);
auto const& profile_runtime = Runtime::get()->profileRuntime();

while (!queue.empty()) {
auto tc = std::move(queue.front());
Expand All @@ -814,9 +791,7 @@ void HIRBuilder::translate(
BytecodeInstruction bc_instr = *bc_it;
tc.setCurrentInstr(bc_instr);

if (profile_data != nullptr) {
emitProfiledTypes(tc, *profile_data, bc_instr);
}
emitProfiledTypes(tc, profile_runtime, bc_instr);

// Translate instruction
switch (bc_instr.opcode()) {
Expand Down
4 changes: 2 additions & 2 deletions Jit/hir/builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#include "Jit/bytecode_offsets.h"
#include "Jit/hir/hir.h"
#include "Jit/hir/preload.h"
#include "Jit/profile_data.h"
#include "Jit/profile_runtime.h"
#include "Jit/stack.h"
#include "Jit/util.h"

Expand Down Expand Up @@ -150,7 +150,7 @@ class HIRBuilder {
const TranslationContext& tc);
void emitProfiledTypes(
TranslationContext& tc,
const CodeProfileData& profile_data,
const ProfileRuntime& profile_runtime,
const BytecodeInstruction& bc_instr);

void emitBinaryOp(
Expand Down
6 changes: 4 additions & 2 deletions Jit/hir/simplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include "Jit/hir/optimization.h"
#include "Jit/hir/printer.h"
#include "Jit/hir/ssa.h"
#include "Jit/profile_data.h"
#include "Jit/profile_runtime.h"
#include "Jit/runtime.h"
#include "Jit/type_deopt_patchers.h"

Expand Down Expand Up @@ -747,7 +747,9 @@ Register* simplifyLoadAttrSplitDict(
return nullptr;
}
BorrowedRef<PyHeapTypeObject> ht(type);
if (ht->ht_cached_keys == nullptr || !hasPrimedDictKeys(type)) {
auto& profile_runtime = Runtime::get()->profileRuntime();
if (ht->ht_cached_keys == nullptr ||
!profile_runtime.hasPrimedDictKeys(type)) {
return nullptr;
}
PyDictKeysObject* keys = ht->ht_cached_keys;
Expand Down
60 changes: 20 additions & 40 deletions Jit/profile_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,23 @@
#include "Jit/containers.h"
#include "Jit/hir/type.h"
#include "Jit/live_type_map.h"
#include "Jit/profile_runtime.h"
#include "Jit/ref.h"

#include <zlib.h>

#include <fstream>
#include <type_traits>

namespace jit {

std::regex profileDataStripPattern;

uint32_t hashBytecode(PyCodeObject* code) {
uint32_t crc = crc32(0, nullptr, 0);
BorrowedRef<> bc = code->co_code;
if (!PyBytes_Check(bc)) {
return crc;
}

char* buffer;
Py_ssize_t len;
if (PyBytes_AsStringAndSize(bc, &buffer, &len) < 0) {
return crc;
}

return crc32(crc, reinterpret_cast<unsigned char*>(buffer), len);
}

namespace {

// A CodeKey is an opaque value that uniquely identifies a specific code
// object. It may include information about the name, file path, and contents
// of the code object.
using CodeKey = std::string;

const uint64_t kMagicHeader = 0x7265646e6963;
constexpr uint32_t kThisPyVersion = PY_VERSION_HEX >> 16;

Expand All @@ -53,6 +41,15 @@ LiveTypeMap s_live_types;
#error Integers in profile data files are little endian.
#endif

CodeKey codeKey(PyCodeObject* code) {
const std::string filename = std::regex_replace(
unicodeAsString(code->co_filename), profileDataStripPattern, "");
const int firstlineno = code->co_firstlineno;
const std::string qualname = codeQualname(code);
uint32_t hash = hashBytecode(code);
return fmt::format("{}:{}:{}:{}", filename, firstlineno, qualname, hash);
}

template <typename T>
T read(std::istream& stream) {
static_assert(
Expand Down Expand Up @@ -105,11 +102,13 @@ void readVersion2(std::istream& stream) {
}
}

void writeVersion4(std::ostream& stream, const TypeProfiles& profiles) {
void writeVersion4(
std::ostream& stream,
const ProfileRuntime& profile_runtime) {
ProfileData data;
std::unordered_set<BorrowedRef<PyTypeObject>> dict_key_types;

for (auto& [code_obj, code_profile] : profiles) {
for (auto& [code_obj, code_profile] : profile_runtime) {
CodeProfileData code_data;
for (auto& profile_pair : code_profile.typed_hits) {
const TypeProfiler& profile = *profile_pair.second;
Expand Down Expand Up @@ -294,7 +293,7 @@ bool writeProfileData(std::ostream& stream) {
stream.exceptions(std::ios::badbit | std::ios::failbit);
write<uint64_t>(stream, kMagicHeader);
write<uint32_t>(stream, 4);
writeVersion4(stream, Runtime::get()->typeProfiles());
writeVersion4(stream, Runtime::get()->profileRuntime());
} catch (const std::runtime_error& e) {
JIT_LOG("Failed to write profile data to stream: %s", e.what());
return false;
Expand Down Expand Up @@ -336,25 +335,6 @@ bool hasPrimedDictKeys(BorrowedRef<PyTypeObject> type) {
return s_live_types.hasPrimedDictKeys(type);
}

std::string codeKey(PyCodeObject* code) {
const std::string filename = std::regex_replace(
unicodeAsString(code->co_filename), profileDataStripPattern, "");
const int firstlineno = code->co_firstlineno;
const std::string qualname = codeQualname(code);
uint32_t hash = hashBytecode(code);
return fmt::format("{}:{}:{}:{}", filename, firstlineno, qualname, hash);
}

std::string codeQualname(PyCodeObject* code) {
if (code->co_qualname != nullptr) {
return unicodeAsString(code->co_qualname);
}
if (code->co_name != nullptr) {
return unicodeAsString(code->co_name);
}
return "<unknown>";
}

int numCachedKeys(BorrowedRef<PyTypeObject> type) {
if (!PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
return 0;
Expand Down
15 changes: 0 additions & 15 deletions Jit/profile_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ namespace jit {
// Pattern to strip from filenames while computing CodeKeys.
extern std::regex profileDataStripPattern;

// Return a crc32 checksum of the bytecode for the given code object.
uint32_t hashBytecode(PyCodeObject* code);

// Load serialized profile data from the given filename or stream, returning
// true on success.
//
Expand Down Expand Up @@ -56,18 +53,6 @@ PolymorphicTypes getProfiledTypes(const CodeProfileData& data, BCOffset bc_off);
// which implies that they are unlikely to change at runtime.
bool hasPrimedDictKeys(BorrowedRef<PyTypeObject> type);

// A CodeKey is an opaque value that uniquely identifies a specific code
// object. It may include information about the name, file path, and contents
// of the code object.
using CodeKey = std::string;

// Return the code key for the given code object.
CodeKey codeKey(PyCodeObject* code);

// Return the qualname of the given code object, falling back to its name or
// "<unknown>" if not set.
std::string codeQualname(PyCodeObject* code);

// Return the number of cached split dict keys in the given type.
int numCachedKeys(BorrowedRef<PyTypeObject> type);

Expand Down
Loading

0 comments on commit d498b39

Please sign in to comment.