This PR changes the current JIT model from trace projection to trace recording. Benchmarking: better pyperformance (about 1.7% overall) geomean versus current https://raw.githubusercontent.com/facebookexperimental/free-threading-benchmarking/refs/heads/main/results/bm-20251108-3.15.0a1%2B-7e2bc1d-JIT/bm-20251108-vultr-x86_64-Fidget%252dSpinner-tracing_jit-3.15.0a1%2B-7e2bc1d-vs-base.svg, 100% faster Richards on the most improved benchmark versus the current JIT. Slowdown of about 10-15% on the worst benchmark versus the current JIT. **Note: the fastest version isn't the one merged, as it relies on fixing bugs in the specializing interpreter, which is left to another PR**. The speedup in the merged version is about 1.1%. https://raw.githubusercontent.com/facebookexperimental/free-threading-benchmarking/refs/heads/main/results/bm-20251112-3.15.0a1%2B-f8a764a-JIT/bm-20251112-vultr-x86_64-Fidget%252dSpinner-tracing_jit-3.15.0a1%2B-f8a764a-vs-base.svg Stats: 50% more uops executed, 30% more traces entered the last time we ran them. It also suggests our trace lengths for a real trace recording JIT are too short, as a lot of trace too long aborts https://github.com/facebookexperimental/free-threading-benchmarking/blob/main/results/bm-20251023-3.15.0a1%2B-eb73378-CLANG%2CJIT/bm-20251023-vultr-x86_64-Fidget%252dSpinner-tracing_jit-3.15.0a1%2B-eb73378-pystats-vs-base.md . This new JIT frontend is already able to record/execute significantly more instructions than the previous JIT frontend. In this PR, we are now able to record through custom dunders, simple object creation, generators, etc. None of these were done by the old JIT frontend. Some custom dunders uops were discovered to be broken as part of this work gh-140277 The optimizer stack space check is disabled, as it's no longer valid to deal with underflow. Pros: * Ignoring the generated tracer code as it's automatically created, this is only additional 1k lines of code. The maintenance burden is handled by the DSL and code generator. * `optimizer.c` is now significantly simpler, as we don't have to do strange things to recover the bytecode from a trace. * The new JIT frontend is able to handle a lot more control-flow than the old one. * Tracing is very low overhead. We use the tail calling interpreter/computed goto interpreter to switch between tracing mode and non-tracing mode. I call this mechanism dual dispatch, as we have two dispatch tables dispatching to each other. Specialization is still enabled while tracing. * Better handling of polymorphism. We leverage the specializing interpreter for this. Cons: * (For now) requires tail calling interpreter or computed gotos. This means no Windows JIT for now :(. Not to fret, tail calling is coming soon to Windows though https://github.com/python/cpython/pull/139962 Design: * After each instruction, the `record_previous_inst` function/label is executed. This does as the name suggests. * The tracing interpreter lowers bytecode to uops directly so that it can obtain "fresh" values at the point of lowering. * The tracing version behaves nearly identical to the normal interpreter, in fact it even has specialization! This allows it to run without much of a slowdown when tracing. The actual cost of tracing is only a function call and writes to memory. * The tracing interpreter uses the specializing interpreter's deopt to naturally form the side exit chains. This allows it to side exit chain effectively, without repeating much code. We force a re-specializing when tracing a deopt. * The tracing interpreter can even handle goto errors/exceptions, but I chose to disable them for now as it's not tested. * Because we do not share interpreter dispatch, there is should be no significant slowdown to the original specializing interpreter on tailcall and computed got with JIT disabled. With JIT enabled, there might be a slowdown in the form of the JIT trying to trace. * Things that could have dynamic instruction pointer effects are guarded on. The guard deopts to a new instruction --- `_DYNAMIC_EXIT`.
1151 lines
36 KiB
C
1151 lines
36 KiB
C
#ifdef _Py_TIER2
|
|
|
|
#include "Python.h"
|
|
|
|
#include "pycore_code.h"
|
|
#include "pycore_frame.h"
|
|
#include "pycore_long.h"
|
|
#include "pycore_optimizer.h"
|
|
#include "pycore_stats.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
|
|
/*
|
|
|
|
Symbols
|
|
=======
|
|
|
|
https://github.com/faster-cpython/ideas/blob/main/3.13/redundancy_eliminator.md
|
|
|
|
Logically, all symbols begin as UNKNOWN, and can transition downwards along the
|
|
edges of the lattice, but *never* upwards (see the diagram below). The UNKNOWN
|
|
state represents no information, and the BOTTOM state represents contradictory
|
|
information. Though symbols logically progress through all intermediate nodes,
|
|
we often skip in-between states for convenience:
|
|
|
|
UNKNOWN
|
|
| |
|
|
NULL |
|
|
| | <- Anything below this level is an object.
|
|
| NON_NULL-+
|
|
| | | <- Anything below this level has a known type version.
|
|
| TYPE_VERSION |
|
|
| | | <- Anything below this level has a known type.
|
|
| KNOWN_CLASS |
|
|
| | | | | |
|
|
| | | INT* | |
|
|
| | | | | | <- Anything below this level has a known truthiness.
|
|
| | | | | TRUTHINESS
|
|
| | | | | |
|
|
| TUPLE | | | |
|
|
| | | | | | <- Anything below this level is a known constant.
|
|
| KNOWN_VALUE--+
|
|
| | <- Anything below this level is unreachable.
|
|
BOTTOM
|
|
|
|
For example, after guarding that the type of an UNKNOWN local is int, we can
|
|
narrow the symbol to KNOWN_CLASS (logically progressing though NON_NULL and
|
|
TYPE_VERSION to get there). Later, we may learn that it is falsey based on the
|
|
result of a truth test, which would allow us to narrow the symbol to KNOWN_VALUE
|
|
(with a value of integer zero). If at any point we encounter a float guard on
|
|
the same symbol, that would be a contradiction, and the symbol would be set to
|
|
BOTTOM (indicating that the code is unreachable).
|
|
|
|
INT* is a limited range int, currently a "compact" int.
|
|
|
|
*/
|
|
|
|
#ifdef Py_DEBUG
|
|
static inline int get_lltrace(void) {
|
|
char *uop_debug = Py_GETENV("PYTHON_OPT_DEBUG");
|
|
int lltrace = 0;
|
|
if (uop_debug != NULL && *uop_debug >= '0') {
|
|
lltrace = *uop_debug - '0'; // TODO: Parse an int and all that
|
|
}
|
|
return lltrace;
|
|
}
|
|
#define DPRINTF(level, ...) \
|
|
if (get_lltrace() >= (level)) { printf(__VA_ARGS__); }
|
|
#else
|
|
#define DPRINTF(level, ...)
|
|
#endif
|
|
|
|
|
|
static JitOptSymbol NO_SPACE_SYMBOL = {
|
|
.tag = JIT_SYM_BOTTOM_TAG
|
|
};
|
|
|
|
static JitOptSymbol *
|
|
allocation_base(JitOptContext *ctx)
|
|
{
|
|
return ctx->t_arena.arena;
|
|
}
|
|
|
|
JitOptSymbol *
|
|
out_of_space(JitOptContext *ctx)
|
|
{
|
|
ctx->done = true;
|
|
ctx->out_of_space = true;
|
|
return &NO_SPACE_SYMBOL;
|
|
}
|
|
|
|
JitOptRef
|
|
out_of_space_ref(JitOptContext *ctx)
|
|
{
|
|
return PyJitRef_Wrap(out_of_space(ctx));
|
|
}
|
|
|
|
static JitOptSymbol *
|
|
sym_new(JitOptContext *ctx)
|
|
{
|
|
JitOptSymbol *self = &ctx->t_arena.arena[ctx->t_arena.ty_curr_number];
|
|
if (ctx->t_arena.ty_curr_number >= ctx->t_arena.ty_max_number) {
|
|
OPT_STAT_INC(optimizer_failure_reason_no_memory);
|
|
DPRINTF(1, "out of space for symbolic expression type\n");
|
|
return NULL;
|
|
}
|
|
ctx->t_arena.ty_curr_number++;
|
|
self->tag = JIT_SYM_UNKNOWN_TAG;;
|
|
return self;
|
|
}
|
|
|
|
static void make_const(JitOptSymbol *sym, PyObject *val)
|
|
{
|
|
sym->tag = JIT_SYM_KNOWN_VALUE_TAG;
|
|
sym->value.value = Py_NewRef(val);
|
|
}
|
|
|
|
static inline void
|
|
sym_set_bottom(JitOptContext *ctx, JitOptSymbol *sym)
|
|
{
|
|
sym->tag = JIT_SYM_BOTTOM_TAG;
|
|
ctx->done = true;
|
|
ctx->contradiction = true;
|
|
}
|
|
|
|
bool
|
|
_Py_uop_sym_is_bottom(JitOptRef ref)
|
|
{
|
|
JitOptSymbol *sym = PyJitRef_Unwrap(ref);
|
|
return sym->tag == JIT_SYM_BOTTOM_TAG;
|
|
}
|
|
|
|
bool
|
|
_Py_uop_sym_is_not_null(JitOptRef ref) {
|
|
JitOptSymbol *sym = PyJitRef_Unwrap(ref);
|
|
return sym->tag == JIT_SYM_NON_NULL_TAG || sym->tag > JIT_SYM_BOTTOM_TAG;
|
|
}
|
|
|
|
bool
|
|
_Py_uop_sym_is_const(JitOptContext *ctx, JitOptRef ref)
|
|
{
|
|
JitOptSymbol *sym = PyJitRef_Unwrap(ref);
|
|
if (sym->tag == JIT_SYM_KNOWN_VALUE_TAG) {
|
|
return true;
|
|
}
|
|
if (sym->tag == JIT_SYM_TRUTHINESS_TAG) {
|
|
JitOptSymbol *value = allocation_base(ctx) + sym->truthiness.value;
|
|
int truthiness = _Py_uop_sym_truthiness(ctx, PyJitRef_Wrap(value));
|
|
if (truthiness < 0) {
|
|
return false;
|
|
}
|
|
make_const(sym, (truthiness ^ sym->truthiness.invert) ? Py_True : Py_False);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
_Py_uop_sym_is_null(JitOptRef ref)
|
|
{
|
|
return PyJitRef_Unwrap(ref)->tag == JIT_SYM_NULL_TAG;
|
|
}
|
|
|
|
|
|
PyObject *
|
|
_Py_uop_sym_get_const(JitOptContext *ctx, JitOptRef ref)
|
|
{
|
|
JitOptSymbol *sym = PyJitRef_Unwrap(ref);
|
|
if (sym->tag == JIT_SYM_KNOWN_VALUE_TAG) {
|
|
return sym->value.value;
|
|
}
|
|
if (sym->tag == JIT_SYM_TRUTHINESS_TAG) {
|
|
JitOptSymbol *value = allocation_base(ctx) + sym->truthiness.value;
|
|
int truthiness = _Py_uop_sym_truthiness(ctx, PyJitRef_Wrap(value));
|
|
if (truthiness < 0) {
|
|
return NULL;
|
|
}
|
|
PyObject *res = (truthiness ^ sym->truthiness.invert) ? Py_True : Py_False;
|
|
make_const(sym, res);
|
|
return res;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
_PyStackRef
|
|
_Py_uop_sym_get_const_as_stackref(JitOptContext *ctx, JitOptRef sym)
|
|
{
|
|
PyObject *const_val = _Py_uop_sym_get_const(ctx, sym);
|
|
if (const_val == NULL) {
|
|
return PyStackRef_NULL;
|
|
}
|
|
return PyStackRef_FromPyObjectBorrow(const_val);
|
|
}
|
|
|
|
/*
|
|
Indicates whether the constant is safe to constant evaluate
|
|
(without side effects).
|
|
*/
|
|
bool
|
|
_Py_uop_sym_is_safe_const(JitOptContext *ctx, JitOptRef sym)
|
|
{
|
|
PyObject *const_val = _Py_uop_sym_get_const(ctx, sym);
|
|
if (const_val == NULL) {
|
|
return false;
|
|
}
|
|
if (_PyLong_CheckExactAndCompact(const_val)) {
|
|
return true;
|
|
}
|
|
PyTypeObject *typ = Py_TYPE(const_val);
|
|
return (typ == &PyUnicode_Type) ||
|
|
(typ == &PyFloat_Type) ||
|
|
(typ == &_PyNone_Type) ||
|
|
(typ == &PyBool_Type);
|
|
}
|
|
|
|
void
|
|
_Py_uop_sym_set_type(JitOptContext *ctx, JitOptRef ref, PyTypeObject *typ)
|
|
{
|
|
JitOptSymbol *sym = PyJitRef_Unwrap(ref);
|
|
JitSymType tag = sym->tag;
|
|
switch(tag) {
|
|
case JIT_SYM_NULL_TAG:
|
|
sym_set_bottom(ctx, sym);
|
|
return;
|
|
case JIT_SYM_KNOWN_CLASS_TAG:
|
|
if (sym->cls.type != typ) {
|
|
sym_set_bottom(ctx, sym);
|
|
}
|
|
return;
|
|
case JIT_SYM_TYPE_VERSION_TAG:
|
|
if (sym->version.version == typ->tp_version_tag) {
|
|
sym->tag = JIT_SYM_KNOWN_CLASS_TAG;
|
|
sym->cls.type = typ;
|
|
sym->cls.version = typ->tp_version_tag;
|
|
}
|
|
else {
|
|
sym_set_bottom(ctx, sym);
|
|
}
|
|
return;
|
|
case JIT_SYM_KNOWN_VALUE_TAG:
|
|
if (Py_TYPE(sym->value.value) != typ) {
|
|
Py_CLEAR(sym->value.value);
|
|
sym_set_bottom(ctx, sym);
|
|
}
|
|
return;
|
|
case JIT_SYM_TUPLE_TAG:
|
|
if (typ != &PyTuple_Type) {
|
|
sym_set_bottom(ctx, sym);
|
|
}
|
|
return;
|
|
case JIT_SYM_BOTTOM_TAG:
|
|
return;
|
|
case JIT_SYM_NON_NULL_TAG:
|
|
case JIT_SYM_UNKNOWN_TAG:
|
|
sym->tag = JIT_SYM_KNOWN_CLASS_TAG;
|
|
sym->cls.version = 0;
|
|
sym->cls.type = typ;
|
|
return;
|
|
case JIT_SYM_TRUTHINESS_TAG:
|
|
if (typ != &PyBool_Type) {
|
|
sym_set_bottom(ctx, sym);
|
|
}
|
|
return;
|
|
case JIT_SYM_COMPACT_INT:
|
|
if (typ != &PyLong_Type) {
|
|
sym_set_bottom(ctx, sym);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool
|
|
_Py_uop_sym_set_type_version(JitOptContext *ctx, JitOptRef ref, unsigned int version)
|
|
{
|
|
JitOptSymbol *sym = PyJitRef_Unwrap(ref);
|
|
PyTypeObject *type = _PyType_LookupByVersion(version);
|
|
if (type) {
|
|
_Py_uop_sym_set_type(ctx, ref, type);
|
|
}
|
|
JitSymType tag = sym->tag;
|
|
switch(tag) {
|
|
case JIT_SYM_NULL_TAG:
|
|
sym_set_bottom(ctx, sym);
|
|
return false;
|
|
case JIT_SYM_KNOWN_CLASS_TAG:
|
|
if (sym->cls.type->tp_version_tag != version) {
|
|
sym_set_bottom(ctx, sym);
|
|
return false;
|
|
}
|
|
else {
|
|
sym->cls.version = version;
|
|
return true;
|
|
}
|
|
case JIT_SYM_KNOWN_VALUE_TAG:
|
|
if (Py_TYPE(sym->value.value)->tp_version_tag != version) {
|
|
Py_CLEAR(sym->value.value);
|
|
sym_set_bottom(ctx, sym);
|
|
return false;
|
|
};
|
|
return true;
|
|
case JIT_SYM_TUPLE_TAG:
|
|
if (PyTuple_Type.tp_version_tag != version) {
|
|
sym_set_bottom(ctx, sym);
|
|
return false;
|
|
};
|
|
return true;
|
|
case JIT_SYM_TYPE_VERSION_TAG:
|
|
if (sym->version.version != version) {
|
|
sym_set_bottom(ctx, sym);
|
|
return false;
|
|
}
|
|
return true;
|
|
case JIT_SYM_BOTTOM_TAG:
|
|
return false;
|
|
case JIT_SYM_NON_NULL_TAG:
|
|
case JIT_SYM_UNKNOWN_TAG:
|
|
sym->tag = JIT_SYM_TYPE_VERSION_TAG;
|
|
sym->version.version = version;
|
|
return true;
|
|
case JIT_SYM_TRUTHINESS_TAG:
|
|
if (version != PyBool_Type.tp_version_tag) {
|
|
sym_set_bottom(ctx, sym);
|
|
return false;
|
|
}
|
|
return true;
|
|
case JIT_SYM_COMPACT_INT:
|
|
if (version != PyLong_Type.tp_version_tag) {
|
|
sym_set_bottom(ctx, sym);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
Py_UNREACHABLE();
|
|
}
|
|
|
|
void
|
|
_Py_uop_sym_set_const(JitOptContext *ctx, JitOptRef ref, PyObject *const_val)
|
|
{
|
|
JitOptSymbol *sym = PyJitRef_Unwrap(ref);
|
|
JitSymType tag = sym->tag;
|
|
switch(tag) {
|
|
case JIT_SYM_NULL_TAG:
|
|
sym_set_bottom(ctx, sym);
|
|
return;
|
|
case JIT_SYM_KNOWN_CLASS_TAG:
|
|
if (sym->cls.type != Py_TYPE(const_val)) {
|
|
sym_set_bottom(ctx, sym);
|
|
return;
|
|
}
|
|
make_const(sym, const_val);
|
|
return;
|
|
case JIT_SYM_KNOWN_VALUE_TAG:
|
|
if (sym->value.value != const_val) {
|
|
Py_CLEAR(sym->value.value);
|
|
sym_set_bottom(ctx, sym);
|
|
}
|
|
return;
|
|
case JIT_SYM_TUPLE_TAG:
|
|
if (PyTuple_CheckExact(const_val)) {
|
|
Py_ssize_t len = _Py_uop_sym_tuple_length(ref);
|
|
if (len == PyTuple_GET_SIZE(const_val)) {
|
|
for (Py_ssize_t i = 0; i < len; i++) {
|
|
JitOptRef sym_item = _Py_uop_sym_tuple_getitem(ctx, ref, i);
|
|
PyObject *item = PyTuple_GET_ITEM(const_val, i);
|
|
_Py_uop_sym_set_const(ctx, sym_item, item);
|
|
}
|
|
make_const(sym, const_val);
|
|
return;
|
|
}
|
|
}
|
|
sym_set_bottom(ctx, sym);
|
|
return;
|
|
case JIT_SYM_TYPE_VERSION_TAG:
|
|
if (sym->version.version != Py_TYPE(const_val)->tp_version_tag) {
|
|
sym_set_bottom(ctx, sym);
|
|
return;
|
|
}
|
|
make_const(sym, const_val);
|
|
return;
|
|
case JIT_SYM_BOTTOM_TAG:
|
|
return;
|
|
case JIT_SYM_NON_NULL_TAG:
|
|
case JIT_SYM_UNKNOWN_TAG:
|
|
make_const(sym, const_val);
|
|
return;
|
|
case JIT_SYM_TRUTHINESS_TAG:
|
|
if (!PyBool_Check(const_val) ||
|
|
(_Py_uop_sym_is_const(ctx, ref) &&
|
|
_Py_uop_sym_get_const(ctx, ref) != const_val))
|
|
{
|
|
sym_set_bottom(ctx, sym);
|
|
return;
|
|
}
|
|
JitOptRef value = PyJitRef_Wrap(
|
|
allocation_base(ctx) + sym->truthiness.value);
|
|
PyTypeObject *type = _Py_uop_sym_get_type(value);
|
|
if (const_val == (sym->truthiness.invert ? Py_False : Py_True)) {
|
|
// value is truthy. This is only useful for bool:
|
|
if (type == &PyBool_Type) {
|
|
_Py_uop_sym_set_const(ctx, value, Py_True);
|
|
}
|
|
}
|
|
// value is falsey:
|
|
else if (type == &PyBool_Type) {
|
|
_Py_uop_sym_set_const(ctx, value, Py_False);
|
|
}
|
|
else if (type == &PyLong_Type) {
|
|
_Py_uop_sym_set_const(ctx, value, Py_GetConstant(Py_CONSTANT_ZERO));
|
|
}
|
|
else if (type == &PyUnicode_Type) {
|
|
_Py_uop_sym_set_const(ctx, value, Py_GetConstant(Py_CONSTANT_EMPTY_STR));
|
|
}
|
|
// TODO: More types (GH-130415)!
|
|
make_const(sym, const_val);
|
|
return;
|
|
case JIT_SYM_COMPACT_INT:
|
|
if (_PyLong_CheckExactAndCompact(const_val)) {
|
|
make_const(sym, const_val);
|
|
}
|
|
else {
|
|
sym_set_bottom(ctx, sym);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
_Py_uop_sym_set_null(JitOptContext *ctx, JitOptRef ref)
|
|
{
|
|
JitOptSymbol *sym = PyJitRef_Unwrap(ref);
|
|
if (sym->tag == JIT_SYM_UNKNOWN_TAG) {
|
|
sym->tag = JIT_SYM_NULL_TAG;
|
|
}
|
|
else if (sym->tag > JIT_SYM_NULL_TAG) {
|
|
sym_set_bottom(ctx, sym);
|
|
}
|
|
}
|
|
|
|
void
|
|
_Py_uop_sym_set_non_null(JitOptContext *ctx, JitOptRef ref)
|
|
{
|
|
JitOptSymbol *sym = PyJitRef_Unwrap(ref);
|
|
if (sym->tag == JIT_SYM_UNKNOWN_TAG) {
|
|
sym->tag = JIT_SYM_NON_NULL_TAG;
|
|
}
|
|
else if (sym->tag == JIT_SYM_NULL_TAG) {
|
|
sym_set_bottom(ctx, sym);
|
|
}
|
|
}
|
|
|
|
JitOptRef
|
|
_Py_uop_sym_new_unknown(JitOptContext *ctx)
|
|
{
|
|
JitOptSymbol *res = sym_new(ctx);
|
|
if (res == NULL) {
|
|
return out_of_space_ref(ctx);
|
|
}
|
|
return PyJitRef_Wrap(res);
|
|
}
|
|
|
|
JitOptRef
|
|
_Py_uop_sym_new_not_null(JitOptContext *ctx)
|
|
{
|
|
JitOptSymbol *res = sym_new(ctx);
|
|
if (res == NULL) {
|
|
return out_of_space_ref(ctx);
|
|
}
|
|
res->tag = JIT_SYM_NON_NULL_TAG;
|
|
return PyJitRef_Wrap(res);
|
|
}
|
|
|
|
JitOptRef
|
|
_Py_uop_sym_new_type(JitOptContext *ctx, PyTypeObject *typ)
|
|
{
|
|
JitOptSymbol *res = sym_new(ctx);
|
|
if (res == NULL) {
|
|
return out_of_space_ref(ctx);
|
|
}
|
|
JitOptRef ref = PyJitRef_Wrap(res);
|
|
_Py_uop_sym_set_type(ctx, ref, typ);
|
|
return ref;
|
|
}
|
|
|
|
// Adds a new reference to const_val, owned by the symbol.
|
|
JitOptRef
|
|
_Py_uop_sym_new_const(JitOptContext *ctx, PyObject *const_val)
|
|
{
|
|
assert(const_val != NULL);
|
|
JitOptSymbol *res = sym_new(ctx);
|
|
if (res == NULL) {
|
|
return out_of_space_ref(ctx);
|
|
}
|
|
JitOptRef ref = PyJitRef_Wrap(res);
|
|
_Py_uop_sym_set_const(ctx, ref, const_val);
|
|
return ref;
|
|
}
|
|
|
|
JitOptRef
|
|
_Py_uop_sym_new_const_steal(JitOptContext *ctx, PyObject *const_val)
|
|
{
|
|
assert(const_val != NULL);
|
|
JitOptRef res = _Py_uop_sym_new_const(ctx, const_val);
|
|
// Decref once because sym_new_const increfs it.
|
|
Py_DECREF(const_val);
|
|
return res;
|
|
}
|
|
|
|
JitOptRef
|
|
_Py_uop_sym_new_null(JitOptContext *ctx)
|
|
{
|
|
JitOptSymbol *null_sym = sym_new(ctx);
|
|
if (null_sym == NULL) {
|
|
return out_of_space_ref(ctx);
|
|
}
|
|
JitOptRef ref = PyJitRef_Wrap(null_sym);
|
|
_Py_uop_sym_set_null(ctx, ref);
|
|
return ref;
|
|
}
|
|
|
|
PyTypeObject *
|
|
_Py_uop_sym_get_type(JitOptRef ref)
|
|
{
|
|
JitOptSymbol *sym = PyJitRef_Unwrap(ref);
|
|
JitSymType tag = sym->tag;
|
|
switch(tag) {
|
|
case JIT_SYM_NULL_TAG:
|
|
case JIT_SYM_BOTTOM_TAG:
|
|
case JIT_SYM_NON_NULL_TAG:
|
|
case JIT_SYM_UNKNOWN_TAG:
|
|
return NULL;
|
|
case JIT_SYM_KNOWN_CLASS_TAG:
|
|
return sym->cls.type;
|
|
case JIT_SYM_KNOWN_VALUE_TAG:
|
|
return Py_TYPE(sym->value.value);
|
|
case JIT_SYM_TYPE_VERSION_TAG:
|
|
return _PyType_LookupByVersion(sym->version.version);
|
|
case JIT_SYM_TUPLE_TAG:
|
|
return &PyTuple_Type;
|
|
case JIT_SYM_TRUTHINESS_TAG:
|
|
return &PyBool_Type;
|
|
case JIT_SYM_COMPACT_INT:
|
|
return &PyLong_Type;
|
|
|
|
}
|
|
Py_UNREACHABLE();
|
|
}
|
|
|
|
unsigned int
|
|
_Py_uop_sym_get_type_version(JitOptRef ref)
|
|
{
|
|
JitOptSymbol *sym = PyJitRef_Unwrap(ref);
|
|
JitSymType tag = sym->tag;
|
|
switch(tag) {
|
|
case JIT_SYM_NULL_TAG:
|
|
case JIT_SYM_BOTTOM_TAG:
|
|
case JIT_SYM_NON_NULL_TAG:
|
|
case JIT_SYM_UNKNOWN_TAG:
|
|
return 0;
|
|
case JIT_SYM_TYPE_VERSION_TAG:
|
|
return sym->version.version;
|
|
case JIT_SYM_KNOWN_CLASS_TAG:
|
|
return sym->cls.version;
|
|
case JIT_SYM_KNOWN_VALUE_TAG:
|
|
return Py_TYPE(sym->value.value)->tp_version_tag;
|
|
case JIT_SYM_TUPLE_TAG:
|
|
return PyTuple_Type.tp_version_tag;
|
|
case JIT_SYM_TRUTHINESS_TAG:
|
|
return PyBool_Type.tp_version_tag;
|
|
case JIT_SYM_COMPACT_INT:
|
|
return PyLong_Type.tp_version_tag;
|
|
}
|
|
Py_UNREACHABLE();
|
|
}
|
|
|
|
bool
|
|
_Py_uop_sym_has_type(JitOptRef sym)
|
|
{
|
|
return _Py_uop_sym_get_type(sym) != NULL;
|
|
}
|
|
|
|
bool
|
|
_Py_uop_sym_matches_type(JitOptRef sym, PyTypeObject *typ)
|
|
{
|
|
assert(typ != NULL && PyType_Check(typ));
|
|
return _Py_uop_sym_get_type(sym) == typ;
|
|
}
|
|
|
|
bool
|
|
_Py_uop_sym_matches_type_version(JitOptRef sym, unsigned int version)
|
|
{
|
|
return _Py_uop_sym_get_type_version(sym) == version;
|
|
}
|
|
|
|
int
|
|
_Py_uop_sym_truthiness(JitOptContext *ctx, JitOptRef ref)
|
|
{
|
|
JitOptSymbol *sym = PyJitRef_Unwrap(ref);
|
|
switch(sym->tag) {
|
|
case JIT_SYM_NULL_TAG:
|
|
case JIT_SYM_TYPE_VERSION_TAG:
|
|
case JIT_SYM_BOTTOM_TAG:
|
|
case JIT_SYM_NON_NULL_TAG:
|
|
case JIT_SYM_UNKNOWN_TAG:
|
|
case JIT_SYM_COMPACT_INT:
|
|
return -1;
|
|
case JIT_SYM_KNOWN_CLASS_TAG:
|
|
/* TODO :
|
|
* Instances of some classes are always
|
|
* true. We should return 1 in those cases */
|
|
return -1;
|
|
case JIT_SYM_KNOWN_VALUE_TAG:
|
|
break;
|
|
case JIT_SYM_TUPLE_TAG:
|
|
return sym->tuple.length != 0;
|
|
case JIT_SYM_TRUTHINESS_TAG:
|
|
;
|
|
JitOptSymbol *value = allocation_base(ctx) + sym->truthiness.value;
|
|
int truthiness = _Py_uop_sym_truthiness(ctx,
|
|
PyJitRef_Wrap(value));
|
|
if (truthiness < 0) {
|
|
return truthiness;
|
|
}
|
|
truthiness ^= sym->truthiness.invert;
|
|
make_const(sym, truthiness ? Py_True : Py_False);
|
|
return truthiness;
|
|
}
|
|
PyObject *value = sym->value.value;
|
|
/* Only handle a few known safe types */
|
|
if (value == Py_None) {
|
|
return 0;
|
|
}
|
|
PyTypeObject *tp = Py_TYPE(value);
|
|
if (tp == &PyLong_Type) {
|
|
return !_PyLong_IsZero((PyLongObject *)value);
|
|
}
|
|
if (tp == &PyUnicode_Type) {
|
|
return value != &_Py_STR(empty);
|
|
}
|
|
if (tp == &PyBool_Type) {
|
|
return value == Py_True;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
JitOptRef
|
|
_Py_uop_sym_new_tuple(JitOptContext *ctx, int size, JitOptRef *args)
|
|
{
|
|
JitOptSymbol *res = sym_new(ctx);
|
|
if (res == NULL) {
|
|
return out_of_space_ref(ctx);
|
|
}
|
|
if (size > MAX_SYMBOLIC_TUPLE_SIZE) {
|
|
res->tag = JIT_SYM_KNOWN_CLASS_TAG;
|
|
res->cls.type = &PyTuple_Type;
|
|
}
|
|
else {
|
|
res->tag = JIT_SYM_TUPLE_TAG;
|
|
res->tuple.length = size;
|
|
for (int i = 0; i < size; i++) {
|
|
res->tuple.items[i] = (uint16_t)(PyJitRef_Unwrap(args[i]) - allocation_base(ctx));
|
|
}
|
|
}
|
|
return PyJitRef_Wrap(res);
|
|
}
|
|
|
|
JitOptRef
|
|
_Py_uop_sym_tuple_getitem(JitOptContext *ctx, JitOptRef ref, Py_ssize_t item)
|
|
{
|
|
JitOptSymbol *sym = PyJitRef_Unwrap(ref);
|
|
assert(item >= 0);
|
|
if (sym->tag == JIT_SYM_KNOWN_VALUE_TAG) {
|
|
PyObject *tuple = sym->value.value;
|
|
if (PyTuple_CheckExact(tuple) && item < PyTuple_GET_SIZE(tuple)) {
|
|
return _Py_uop_sym_new_const(ctx, PyTuple_GET_ITEM(tuple, item));
|
|
}
|
|
}
|
|
else if (sym->tag == JIT_SYM_TUPLE_TAG && item < sym->tuple.length) {
|
|
return PyJitRef_Wrap(allocation_base(ctx) + sym->tuple.items[item]);
|
|
}
|
|
return _Py_uop_sym_new_not_null(ctx);
|
|
}
|
|
|
|
Py_ssize_t
|
|
_Py_uop_sym_tuple_length(JitOptRef ref)
|
|
{
|
|
JitOptSymbol *sym = PyJitRef_Unwrap(ref);
|
|
if (sym->tag == JIT_SYM_KNOWN_VALUE_TAG) {
|
|
PyObject *tuple = sym->value.value;
|
|
if (PyTuple_CheckExact(tuple)) {
|
|
return PyTuple_GET_SIZE(tuple);
|
|
}
|
|
}
|
|
else if (sym->tag == JIT_SYM_TUPLE_TAG) {
|
|
return sym->tuple.length;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// Return true if known to be immortal.
|
|
bool
|
|
_Py_uop_symbol_is_immortal(JitOptSymbol *sym)
|
|
{
|
|
if (sym->tag == JIT_SYM_KNOWN_VALUE_TAG) {
|
|
return _Py_IsImmortal(sym->value.value);
|
|
}
|
|
if (sym->tag == JIT_SYM_KNOWN_CLASS_TAG) {
|
|
return sym->cls.type == &PyBool_Type;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
_Py_uop_sym_is_compact_int(JitOptRef ref)
|
|
{
|
|
JitOptSymbol *sym = PyJitRef_Unwrap(ref);
|
|
if (sym->tag == JIT_SYM_KNOWN_VALUE_TAG) {
|
|
return (bool)_PyLong_CheckExactAndCompact(sym->value.value);
|
|
}
|
|
return sym->tag == JIT_SYM_COMPACT_INT;
|
|
}
|
|
|
|
bool
|
|
_Py_uop_sym_is_immortal(JitOptRef ref)
|
|
{
|
|
JitOptSymbol *sym = PyJitRef_Unwrap(ref);
|
|
return _Py_uop_symbol_is_immortal(sym);
|
|
}
|
|
|
|
void
|
|
_Py_uop_sym_set_compact_int(JitOptContext *ctx, JitOptRef ref)
|
|
{
|
|
JitOptSymbol *sym = PyJitRef_Unwrap(ref);
|
|
JitSymType tag = sym->tag;
|
|
switch(tag) {
|
|
case JIT_SYM_NULL_TAG:
|
|
sym_set_bottom(ctx, sym);
|
|
return;
|
|
case JIT_SYM_KNOWN_CLASS_TAG:
|
|
if (sym->cls.type == &PyLong_Type) {
|
|
sym->tag = JIT_SYM_COMPACT_INT;
|
|
} else {
|
|
sym_set_bottom(ctx, sym);
|
|
}
|
|
return;
|
|
case JIT_SYM_TYPE_VERSION_TAG:
|
|
if (sym->version.version == PyLong_Type.tp_version_tag) {
|
|
sym->tag = JIT_SYM_COMPACT_INT;
|
|
}
|
|
else {
|
|
sym_set_bottom(ctx, sym);
|
|
}
|
|
return;
|
|
case JIT_SYM_KNOWN_VALUE_TAG:
|
|
if (!_PyLong_CheckExactAndCompact(sym->value.value)) {
|
|
Py_CLEAR(sym->value.value);
|
|
sym_set_bottom(ctx, sym);
|
|
}
|
|
return;
|
|
case JIT_SYM_TUPLE_TAG:
|
|
case JIT_SYM_TRUTHINESS_TAG:
|
|
sym_set_bottom(ctx, sym);
|
|
return;
|
|
case JIT_SYM_BOTTOM_TAG:
|
|
case JIT_SYM_COMPACT_INT:
|
|
return;
|
|
case JIT_SYM_NON_NULL_TAG:
|
|
case JIT_SYM_UNKNOWN_TAG:
|
|
sym->tag = JIT_SYM_COMPACT_INT;
|
|
return;
|
|
}
|
|
}
|
|
|
|
JitOptRef
|
|
_Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptRef ref, bool truthy)
|
|
{
|
|
JitOptSymbol *value = PyJitRef_Unwrap(ref);
|
|
// It's clearer to invert this in the signature:
|
|
bool invert = !truthy;
|
|
if (value->tag == JIT_SYM_TRUTHINESS_TAG && value->truthiness.invert == invert) {
|
|
return ref;
|
|
}
|
|
JitOptSymbol *res = sym_new(ctx);
|
|
if (res == NULL) {
|
|
return out_of_space_ref(ctx);
|
|
}
|
|
int truthiness = _Py_uop_sym_truthiness(ctx, ref);
|
|
if (truthiness < 0) {
|
|
res->tag = JIT_SYM_TRUTHINESS_TAG;
|
|
res->truthiness.invert = invert;
|
|
res->truthiness.value = (uint16_t)(value - allocation_base(ctx));
|
|
}
|
|
else {
|
|
make_const(res, (truthiness ^ invert) ? Py_True : Py_False);
|
|
}
|
|
return PyJitRef_Wrap(res);
|
|
}
|
|
|
|
JitOptRef
|
|
_Py_uop_sym_new_compact_int(JitOptContext *ctx)
|
|
{
|
|
JitOptSymbol *sym = sym_new(ctx);
|
|
if (sym == NULL) {
|
|
return out_of_space_ref(ctx);
|
|
}
|
|
sym->tag = JIT_SYM_COMPACT_INT;
|
|
return PyJitRef_Wrap(sym);
|
|
}
|
|
|
|
// 0 on success, -1 on error.
|
|
_Py_UOpsAbstractFrame *
|
|
_Py_uop_frame_new(
|
|
JitOptContext *ctx,
|
|
PyCodeObject *co,
|
|
int curr_stackentries,
|
|
JitOptRef *args,
|
|
int arg_len)
|
|
{
|
|
if (ctx->curr_frame_depth >= MAX_ABSTRACT_FRAME_DEPTH) {
|
|
ctx->done = true;
|
|
ctx->out_of_space = true;
|
|
OPT_STAT_INC(optimizer_frame_overflow);
|
|
return NULL;
|
|
}
|
|
_Py_UOpsAbstractFrame *frame = &ctx->frames[ctx->curr_frame_depth];
|
|
frame->code = co;
|
|
frame->stack_len = co->co_stacksize;
|
|
frame->locals_len = co->co_nlocalsplus;
|
|
|
|
frame->locals = ctx->n_consumed;
|
|
frame->stack = frame->locals + co->co_nlocalsplus;
|
|
frame->stack_pointer = frame->stack + curr_stackentries;
|
|
frame->globals_checked_version = 0;
|
|
frame->globals_watched = false;
|
|
frame->func = NULL;
|
|
ctx->n_consumed = ctx->n_consumed + (co->co_nlocalsplus + co->co_stacksize);
|
|
if (ctx->n_consumed >= ctx->limit) {
|
|
ctx->done = true;
|
|
ctx->out_of_space = true;
|
|
return NULL;
|
|
}
|
|
|
|
// Initialize with the initial state of all local variables
|
|
for (int i = 0; i < arg_len; i++) {
|
|
frame->locals[i] = args[i];
|
|
}
|
|
|
|
for (int i = arg_len; i < co->co_nlocalsplus; i++) {
|
|
JitOptRef local = _Py_uop_sym_new_unknown(ctx);
|
|
frame->locals[i] = local;
|
|
}
|
|
|
|
|
|
// Initialize the stack as well
|
|
for (int i = 0; i < curr_stackentries; i++) {
|
|
JitOptRef stackvar = _Py_uop_sym_new_unknown(ctx);
|
|
frame->stack[i] = stackvar;
|
|
}
|
|
|
|
return frame;
|
|
}
|
|
|
|
void
|
|
_Py_uop_abstractcontext_fini(JitOptContext *ctx)
|
|
{
|
|
if (ctx == NULL) {
|
|
return;
|
|
}
|
|
ctx->curr_frame_depth = 0;
|
|
int tys = ctx->t_arena.ty_curr_number;
|
|
for (int i = 0; i < tys; i++) {
|
|
JitOptSymbol *sym = &ctx->t_arena.arena[i];
|
|
if (sym->tag == JIT_SYM_KNOWN_VALUE_TAG) {
|
|
Py_CLEAR(sym->value.value);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
_Py_uop_abstractcontext_init(JitOptContext *ctx)
|
|
{
|
|
static_assert(sizeof(JitOptSymbol) <= 3 * sizeof(uint64_t), "JitOptSymbol has grown");
|
|
ctx->limit = ctx->locals_and_stack + MAX_ABSTRACT_INTERP_SIZE;
|
|
ctx->n_consumed = ctx->locals_and_stack;
|
|
#ifdef Py_DEBUG // Aids debugging a little. There should never be NULL in the abstract interpreter.
|
|
for (int i = 0 ; i < MAX_ABSTRACT_INTERP_SIZE; i++) {
|
|
ctx->locals_and_stack[i] = PyJitRef_NULL;
|
|
}
|
|
#endif
|
|
|
|
// Setup the arena for sym expressions.
|
|
ctx->t_arena.ty_curr_number = 0;
|
|
ctx->t_arena.ty_max_number = TY_ARENA_SIZE;
|
|
|
|
// Frame setup
|
|
ctx->curr_frame_depth = 0;
|
|
|
|
// Ctx signals.
|
|
// Note: this must happen before frame_new, as it might override
|
|
// the result should frame_new set things to bottom.
|
|
ctx->done = false;
|
|
ctx->out_of_space = false;
|
|
ctx->contradiction = false;
|
|
ctx->builtins_watched = false;
|
|
}
|
|
|
|
int
|
|
_Py_uop_frame_pop(JitOptContext *ctx, PyCodeObject *co, int curr_stackentries)
|
|
{
|
|
_Py_UOpsAbstractFrame *frame = ctx->frame;
|
|
ctx->n_consumed = frame->locals;
|
|
|
|
ctx->curr_frame_depth--;
|
|
|
|
if (ctx->curr_frame_depth >= 1) {
|
|
ctx->frame = &ctx->frames[ctx->curr_frame_depth - 1];
|
|
|
|
// We returned to the correct code. Nothing to do here.
|
|
if (co == ctx->frame->code) {
|
|
return 0;
|
|
}
|
|
// Else: the code we recorded doesn't match the code we *think* we're
|
|
// returning to. We could trace anything, we can't just return to the
|
|
// old frame. We have to restore what the tracer recorded
|
|
// as the traced next frame.
|
|
// Remove the current frame, and later swap it out with the right one.
|
|
else {
|
|
ctx->curr_frame_depth--;
|
|
}
|
|
}
|
|
// Else: trace stack underflow.
|
|
|
|
// This handles swapping out frames.
|
|
assert(curr_stackentries >= 1);
|
|
// -1 to stackentries as we push to the stack our return value after this.
|
|
_Py_UOpsAbstractFrame *new_frame = _Py_uop_frame_new(ctx, co, curr_stackentries - 1, NULL, 0);
|
|
if (new_frame == NULL) {
|
|
ctx->done = true;
|
|
return 1;
|
|
}
|
|
|
|
ctx->curr_frame_depth++;
|
|
ctx->frame = new_frame;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define TEST_PREDICATE(PRED, MSG) \
|
|
do { \
|
|
if (!(PRED)) { \
|
|
PyErr_SetString( \
|
|
PyExc_AssertionError, \
|
|
(MSG)); \
|
|
goto fail; \
|
|
} \
|
|
} while (0)
|
|
|
|
static JitOptSymbol *
|
|
make_bottom(JitOptContext *ctx)
|
|
{
|
|
JitOptSymbol *sym = sym_new(ctx);
|
|
sym->tag = JIT_SYM_BOTTOM_TAG;
|
|
return sym;
|
|
}
|
|
|
|
PyObject *
|
|
_Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored))
|
|
{
|
|
JitOptContext context;
|
|
JitOptContext *ctx = &context;
|
|
_Py_uop_abstractcontext_init(ctx);
|
|
PyObject *val_42 = NULL;
|
|
PyObject *val_43 = NULL;
|
|
PyObject *val_big = NULL;
|
|
PyObject *tuple = NULL;
|
|
|
|
// Use a single 'sym' variable so copy-pasting tests is easier.
|
|
JitOptRef ref = _Py_uop_sym_new_unknown(ctx);
|
|
if (PyJitRef_IsNull(ref)) {
|
|
goto fail;
|
|
}
|
|
TEST_PREDICATE(!_Py_uop_sym_is_null(ref), "top is NULL");
|
|
TEST_PREDICATE(!_Py_uop_sym_is_not_null(ref), "top is not NULL");
|
|
TEST_PREDICATE(!_Py_uop_sym_matches_type(ref, &PyLong_Type), "top matches a type");
|
|
TEST_PREDICATE(!_Py_uop_sym_is_const(ctx, ref), "top is a constant");
|
|
TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref) == NULL, "top as constant is not NULL");
|
|
TEST_PREDICATE(!_Py_uop_sym_is_bottom(ref), "top is bottom");
|
|
|
|
ref = PyJitRef_Wrap(make_bottom(ctx));
|
|
if (PyJitRef_IsNull(ref)) {
|
|
goto fail;
|
|
}
|
|
TEST_PREDICATE(!_Py_uop_sym_is_null(ref), "bottom is NULL is not false");
|
|
TEST_PREDICATE(!_Py_uop_sym_is_not_null(ref), "bottom is not NULL is not false");
|
|
TEST_PREDICATE(!_Py_uop_sym_matches_type(ref, &PyLong_Type), "bottom matches a type");
|
|
TEST_PREDICATE(!_Py_uop_sym_is_const(ctx, ref), "bottom is a constant is not false");
|
|
TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref) == NULL, "bottom as constant is not NULL");
|
|
TEST_PREDICATE(_Py_uop_sym_is_bottom(ref), "bottom isn't bottom");
|
|
|
|
ref = _Py_uop_sym_new_type(ctx, &PyLong_Type);
|
|
if (PyJitRef_IsNull(ref)) {
|
|
goto fail;
|
|
}
|
|
TEST_PREDICATE(!_Py_uop_sym_is_null(ref), "int is NULL");
|
|
TEST_PREDICATE(_Py_uop_sym_is_not_null(ref), "int isn't not NULL");
|
|
TEST_PREDICATE(_Py_uop_sym_matches_type(ref, &PyLong_Type), "int isn't int");
|
|
TEST_PREDICATE(!_Py_uop_sym_matches_type(ref, &PyFloat_Type), "int matches float");
|
|
TEST_PREDICATE(!_Py_uop_sym_is_const(ctx, ref), "int is a constant");
|
|
TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref) == NULL, "int as constant is not NULL");
|
|
|
|
_Py_uop_sym_set_type(ctx, ref, &PyLong_Type); // Should be a no-op
|
|
TEST_PREDICATE(_Py_uop_sym_matches_type(ref, &PyLong_Type), "(int and int) isn't int");
|
|
|
|
_Py_uop_sym_set_type(ctx, ref, &PyFloat_Type); // Should make it bottom
|
|
TEST_PREDICATE(_Py_uop_sym_is_bottom(ref), "(int and float) isn't bottom");
|
|
|
|
val_42 = PyLong_FromLong(42);
|
|
assert(val_42 != NULL);
|
|
assert(_Py_IsImmortal(val_42));
|
|
|
|
val_43 = PyLong_FromLong(43);
|
|
assert(val_43 != NULL);
|
|
assert(_Py_IsImmortal(val_43));
|
|
|
|
ref = _Py_uop_sym_new_type(ctx, &PyLong_Type);
|
|
if (PyJitRef_IsNull(ref)) {
|
|
goto fail;
|
|
}
|
|
_Py_uop_sym_set_const(ctx, ref, val_42);
|
|
TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, ref) == 1, "bool(42) is not True");
|
|
TEST_PREDICATE(!_Py_uop_sym_is_null(ref), "42 is NULL");
|
|
TEST_PREDICATE(_Py_uop_sym_is_not_null(ref), "42 isn't not NULL");
|
|
TEST_PREDICATE(_Py_uop_sym_matches_type(ref, &PyLong_Type), "42 isn't an int");
|
|
TEST_PREDICATE(!_Py_uop_sym_matches_type(ref, &PyFloat_Type), "42 matches float");
|
|
TEST_PREDICATE(_Py_uop_sym_is_const(ctx, ref), "42 is not a constant");
|
|
TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref) != NULL, "42 as constant is NULL");
|
|
TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref) == val_42, "42 as constant isn't 42");
|
|
TEST_PREDICATE(_Py_uop_sym_is_immortal(ref), "42 is not immortal");
|
|
|
|
_Py_uop_sym_set_type(ctx, ref, &PyLong_Type); // Should be a no-op
|
|
TEST_PREDICATE(_Py_uop_sym_matches_type(ref, &PyLong_Type), "(42 and 42) isn't an int");
|
|
TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref) == val_42, "(42 and 42) as constant isn't 42");
|
|
|
|
_Py_uop_sym_set_type(ctx, ref, &PyFloat_Type); // Should make it bottom
|
|
TEST_PREDICATE(_Py_uop_sym_is_bottom(ref), "(42 and float) isn't bottom");
|
|
|
|
ref = _Py_uop_sym_new_type(ctx, &PyBool_Type);
|
|
TEST_PREDICATE(_Py_uop_sym_is_immortal(ref), "a bool is not immortal");
|
|
|
|
ref = _Py_uop_sym_new_type(ctx, &PyLong_Type);
|
|
if (PyJitRef_IsNull(ref)) {
|
|
goto fail;
|
|
}
|
|
_Py_uop_sym_set_const(ctx, ref, val_42);
|
|
_Py_uop_sym_set_const(ctx, ref, val_43); // Should make it bottom
|
|
TEST_PREDICATE(_Py_uop_sym_is_bottom(ref), "(42 and 43) isn't bottom");
|
|
|
|
|
|
ref = _Py_uop_sym_new_const(ctx, Py_None);
|
|
TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, ref) == 0, "bool(None) is not False");
|
|
ref = _Py_uop_sym_new_const(ctx, Py_False);
|
|
TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, ref) == 0, "bool(False) is not False");
|
|
ref = _Py_uop_sym_new_const(ctx, PyLong_FromLong(0));
|
|
TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, ref) == 0, "bool(0) is not False");
|
|
|
|
JitOptRef i1 = _Py_uop_sym_new_type(ctx, &PyFloat_Type);
|
|
JitOptRef i2 = _Py_uop_sym_new_const(ctx, val_43);
|
|
JitOptRef array[2] = { i1, i2 };
|
|
ref = _Py_uop_sym_new_tuple(ctx, 2, array);
|
|
TEST_PREDICATE(
|
|
_Py_uop_sym_matches_type(_Py_uop_sym_tuple_getitem(ctx, ref, 0), &PyFloat_Type),
|
|
"tuple item does not match value used to create tuple"
|
|
);
|
|
TEST_PREDICATE(
|
|
_Py_uop_sym_get_const(ctx, _Py_uop_sym_tuple_getitem(ctx, ref, 1)) == val_43,
|
|
"tuple item does not match value used to create tuple"
|
|
);
|
|
PyObject *pair[2] = { val_42, val_43 };
|
|
tuple = PyTuple_FromArray(pair, 2);
|
|
ref = _Py_uop_sym_new_const(ctx, tuple);
|
|
TEST_PREDICATE(
|
|
_Py_uop_sym_get_const(ctx, _Py_uop_sym_tuple_getitem(ctx, ref, 1)) == val_43,
|
|
"tuple item does not match value used to create tuple"
|
|
);
|
|
ref = _Py_uop_sym_new_type(ctx, &PyTuple_Type);
|
|
TEST_PREDICATE(
|
|
_Py_uop_sym_is_not_null(_Py_uop_sym_tuple_getitem(ctx, ref, 42)),
|
|
"Unknown tuple item is not narrowed to non-NULL"
|
|
);
|
|
JitOptRef value = _Py_uop_sym_new_type(ctx, &PyBool_Type);
|
|
ref = _Py_uop_sym_new_truthiness(ctx, value, false);
|
|
TEST_PREDICATE(_Py_uop_sym_matches_type(ref, &PyBool_Type), "truthiness is not boolean");
|
|
TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, ref) == -1, "truthiness is not unknown");
|
|
TEST_PREDICATE(_Py_uop_sym_is_const(ctx, ref) == false, "truthiness is constant");
|
|
TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref) == NULL, "truthiness is not NULL");
|
|
TEST_PREDICATE(_Py_uop_sym_is_const(ctx, value) == false, "value is constant");
|
|
TEST_PREDICATE(_Py_uop_sym_get_const(ctx, value) == NULL, "value is not NULL");
|
|
_Py_uop_sym_set_const(ctx, ref, Py_False);
|
|
TEST_PREDICATE(_Py_uop_sym_matches_type(ref, &PyBool_Type), "truthiness is not boolean");
|
|
TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, ref) == 0, "truthiness is not True");
|
|
TEST_PREDICATE(_Py_uop_sym_is_const(ctx, ref) == true, "truthiness is not constant");
|
|
TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref) == Py_False, "truthiness is not False");
|
|
TEST_PREDICATE(_Py_uop_sym_is_const(ctx, value) == true, "value is not constant");
|
|
TEST_PREDICATE(_Py_uop_sym_get_const(ctx, value) == Py_True, "value is not True");
|
|
|
|
|
|
val_big = PyNumber_Lshift(_PyLong_GetOne(), PyLong_FromLong(66));
|
|
if (val_big == NULL) {
|
|
goto fail;
|
|
}
|
|
|
|
JitOptRef ref_42 = _Py_uop_sym_new_const(ctx, val_42);
|
|
JitOptRef ref_big = _Py_uop_sym_new_const(ctx, val_big);
|
|
JitOptRef ref_int = _Py_uop_sym_new_compact_int(ctx);
|
|
TEST_PREDICATE(_Py_uop_sym_is_compact_int(ref_42), "42 is not a compact int");
|
|
TEST_PREDICATE(!_Py_uop_sym_is_compact_int(ref_big), "(1 << 66) is a compact int");
|
|
TEST_PREDICATE(_Py_uop_sym_is_compact_int(ref_int), "compact int is not a compact int");
|
|
TEST_PREDICATE(_Py_uop_sym_matches_type(ref_int, &PyLong_Type), "compact int is not an int");
|
|
|
|
_Py_uop_sym_set_type(ctx, ref_int, &PyLong_Type); // Should have no effect
|
|
TEST_PREDICATE(_Py_uop_sym_is_compact_int(ref_int), "compact int is not a compact int after cast");
|
|
TEST_PREDICATE(_Py_uop_sym_matches_type(ref_int, &PyLong_Type), "compact int is not an int after cast");
|
|
|
|
_Py_uop_sym_set_type(ctx, ref_int, &PyFloat_Type); // Should make it bottom
|
|
TEST_PREDICATE(_Py_uop_sym_is_bottom(ref_int), "compact int cast to float isn't bottom");
|
|
|
|
ref_int = _Py_uop_sym_new_compact_int(ctx);
|
|
_Py_uop_sym_set_const(ctx, ref_int, val_43);
|
|
TEST_PREDICATE(_Py_uop_sym_is_compact_int(ref_int), "43 is not a compact int");
|
|
TEST_PREDICATE(_Py_uop_sym_matches_type(ref_int, &PyLong_Type), "43 is not an int");
|
|
TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref_int) == val_43, "43 isn't 43");
|
|
|
|
_Py_uop_abstractcontext_fini(ctx);
|
|
Py_DECREF(val_42);
|
|
Py_DECREF(val_43);
|
|
Py_DECREF(val_big);
|
|
Py_DECREF(tuple);
|
|
Py_RETURN_NONE;
|
|
|
|
fail:
|
|
_Py_uop_abstractcontext_fini(ctx);
|
|
Py_XDECREF(val_42);
|
|
Py_XDECREF(val_43);
|
|
Py_XDECREF(val_big);
|
|
Py_DECREF(tuple);
|
|
return NULL;
|
|
}
|
|
|
|
#endif /* _Py_TIER2 */
|