Skip to content

Commit 197c0c9

Browse files
fix: backward jump type context compatibility check (#10)
Co-authored-by: Jules <57632293+JuliaPoo@users.noreply.github.com>
1 parent cd81ce6 commit 197c0c9

2 files changed

Lines changed: 185 additions & 61 deletions

File tree

Python/tier2.c

Lines changed: 169 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,61 @@ typenode_get_type(_Py_TYPENODE_t node)
248248
}
249249
}
250250

251+
/**
252+
* @brief Gets the location of the node in the type context
253+
* @param ctx Pointer to type context to look in
254+
* @param node A node in type context `ctx`
255+
* @param is_localvar Pointer to boolean.
256+
If `node` is inside `ctx->type_locals`, this will return true
257+
* @return The index into the array the node is in.
258+
* If `*is_localvar` is true, the array is `ctx->type_locals`.
259+
* Otherwise it is `ctx->type_stack`
260+
*/
261+
static int
262+
typenode_get_location(_PyTier2TypeContext *ctx, _Py_TYPENODE_t *node, bool *is_localvar)
263+
{
264+
// Search locals
265+
int nlocals = ctx->type_locals_len;
266+
int offset = (int)(node - ctx->type_locals);
267+
if (offset >= 0 && offset < nlocals) {
268+
*is_localvar = true;
269+
return offset;
270+
}
271+
272+
// Search stack
273+
int nstack = ctx->type_stack_len;
274+
offset = (int)(node - ctx->type_stack);
275+
for (int i = 0; i < nstack; i++) {
276+
*is_localvar = false;
277+
return offset;
278+
}
279+
280+
Py_UNREACHABLE();
281+
}
282+
283+
/**
284+
* @brief Check if two nodes in a type context are in the same tree
285+
* @param src
286+
*/
287+
static bool typenode_is_same_tree(_Py_TYPENODE_t *x, _Py_TYPENODE_t *y)
288+
{
289+
_Py_TYPENODE_t *x_rootref = x;
290+
_Py_TYPENODE_t *y_rootref = y;
291+
uintptr_t x_tag = _Py_TYPENODE_GET_TAG(*x);
292+
uintptr_t y_tag = _Py_TYPENODE_GET_TAG(*y);
293+
switch (y_tag) {
294+
case TYPE_REF: y_rootref = __typenode_get_rootptr(*y);
295+
case TYPE_ROOT: break;
296+
default: Py_UNREACHABLE();
297+
}
298+
switch (x_tag) {
299+
case TYPE_REF: x_rootref = __typenode_get_rootptr(*x);
300+
case TYPE_ROOT: break;
301+
default: Py_UNREACHABLE();
302+
}
303+
return x_rootref == y_rootref;
304+
}
305+
251306
/**
252307
* @brief Performs TYPE_SET operation. dst tree becomes part of src tree
253308
*
@@ -279,55 +334,23 @@ __type_propagate_TYPE_SET(
279334
}
280335
#endif
281336

282-
if (!src_is_new) {
283-
// Check if they are the same tree
284-
_Py_TYPENODE_t *srcrootref = src;
285-
_Py_TYPENODE_t *dstrootref = dst;
286-
uintptr_t dsttag = _Py_TYPENODE_GET_TAG(*dst);
287-
uintptr_t srctag = _Py_TYPENODE_GET_TAG(*src);
288-
switch (dsttag) {
289-
case TYPE_REF: dstrootref = __typenode_get_rootptr(*dst);
290-
case TYPE_ROOT:
291-
switch (srctag) {
292-
case TYPE_REF: srcrootref = __typenode_get_rootptr(*src);
293-
case TYPE_ROOT:
294-
if (srcrootref == dstrootref) {
295-
// Same tree, no point swapping
296-
return;
297-
}
298-
break;
299-
default:
300-
Py_UNREACHABLE();
301-
}
302-
break;
303-
default:
304-
Py_UNREACHABLE();
305-
}
337+
if (!src_is_new && typenode_is_same_tree(src, dst)) {
338+
return;
306339
}
307340

308341
uintptr_t tag = _Py_TYPENODE_GET_TAG(*dst);
342+
_Py_TYPENODE_t *rootref = dst;
309343
switch (tag) {
310-
case TYPE_ROOT: {
344+
case TYPE_REF: rootref = __typenode_get_rootptr(*dst);
345+
case TYPE_ROOT:
311346
if (!src_is_new) {
312347
// Make dst a reference to src
313-
*dst = _Py_TYPENODE_MAKE_REF((_Py_TYPENODE_t)src);
314-
break;
315-
}
316-
// Make dst the src
317-
*dst = (_Py_TYPENODE_t)src;
318-
break;
319-
}
320-
case TYPE_REF: {
321-
_Py_TYPENODE_t *rootref = __typenode_get_rootptr(*dst);
322-
if (!src_is_new) {
323-
// Traverse up to the root of dst, make root a reference to src
324348
*rootref = _Py_TYPENODE_MAKE_REF((_Py_TYPENODE_t)src);
325349
break;
326350
}
327-
// Make root of dst the src
351+
// Make dst the src
328352
*rootref = (_Py_TYPENODE_t)src;
329353
break;
330-
}
331354
default:
332355
Py_UNREACHABLE();
333356
}
@@ -365,6 +388,10 @@ __type_propagate_TYPE_OVERWRITE(
365388
}
366389
#endif
367390

391+
if (!src_is_new && typenode_is_same_tree(src, dst)) {
392+
return;
393+
}
394+
368395
uintptr_t tag = _Py_TYPENODE_GET_TAG(*dst);
369396
switch (tag) {
370397
case TYPE_ROOT: {
@@ -487,28 +514,8 @@ __type_propagate_TYPE_SWAP(
487514
_PyTier2TypeContext *type_context,
488515
_Py_TYPENODE_t *src, _Py_TYPENODE_t *dst)
489516
{
490-
// Check if they are the same tree
491-
_Py_TYPENODE_t *srcrootref = src;
492-
_Py_TYPENODE_t *dstrootref = dst;
493-
uintptr_t dsttag = _Py_TYPENODE_GET_TAG(*dst);
494-
uintptr_t srctag = _Py_TYPENODE_GET_TAG(*src);
495-
switch (dsttag) {
496-
case TYPE_REF: dstrootref = __typenode_get_rootptr(*dst);
497-
case TYPE_ROOT:
498-
switch (srctag) {
499-
case TYPE_REF: srcrootref = __typenode_get_rootptr(*src);
500-
case TYPE_ROOT:
501-
if (srcrootref == dstrootref) {
502-
// Same tree, no point swapping
503-
return;
504-
}
505-
break;
506-
default:
507-
Py_UNREACHABLE();
508-
}
509-
break;
510-
default:
511-
Py_UNREACHABLE();
517+
if (typenode_is_same_tree(src, dst)) {
518+
return;
512519
}
513520

514521
// src and dst are different tree,
@@ -2658,6 +2665,93 @@ _PyTier2_GenerateNextBB(
26582665
return metadata->tier2_start;
26592666
}
26602667

2668+
/**
2669+
* @brief Helper funnction of typecontext_is_compatible. See that for why we need this.
2670+
* @param ctx1 A pointer to a type context.
2671+
* @param ctx2 A pointer to a different type context.
2672+
* @param ctx1_node A pointer to type node belonging to ctx1.
2673+
* @param ctx2_node A pointer to type node belonging to ctx2t.
2674+
* @return If the type nodes' parent trees are compatible.
2675+
*/
2676+
static bool
2677+
typenode_is_compatible(
2678+
_PyTier2TypeContext *ctx1, _PyTier2TypeContext *ctx2,
2679+
_Py_TYPENODE_t *ctx1_node, _Py_TYPENODE_t *ctx2_node)
2680+
{
2681+
_Py_TYPENODE_t *root1 = ctx1_node;
2682+
_Py_TYPENODE_t *root2 = ctx2_node;
2683+
switch (_Py_TYPENODE_GET_TAG(*ctx1_node)) {
2684+
case TYPE_REF: root1 = __typenode_get_rootptr(*ctx1_node);
2685+
case TYPE_ROOT: break;
2686+
default: Py_UNREACHABLE();
2687+
}
2688+
switch (_Py_TYPENODE_GET_TAG(*ctx2_node)) {
2689+
case TYPE_REF: root2 = __typenode_get_rootptr(*ctx2_node);
2690+
case TYPE_ROOT: break;
2691+
default: Py_UNREACHABLE();
2692+
}
2693+
2694+
// Get location of each root
2695+
bool is_local1, is_local2;
2696+
int node_idx1 = typenode_get_location(ctx1, root1, &is_local1);
2697+
int node_idx2 = typenode_get_location(ctx2, root2, &is_local2);
2698+
2699+
// Map each root to the corresponding location in the other tree
2700+
_Py_TYPENODE_t* mappedroot1 = is_local1
2701+
? &ctx2->type_locals[node_idx1]
2702+
: &ctx2->type_stack[node_idx1];
2703+
_Py_TYPENODE_t* mappedroot2 = is_local2
2704+
? &ctx1->type_locals[node_idx2]
2705+
: &ctx1->type_stack[node_idx2];
2706+
2707+
return typenode_is_same_tree(mappedroot1, root2)
2708+
&& typenode_is_same_tree(mappedroot2, root1);
2709+
}
2710+
2711+
/**
2712+
* @brief Checks that type context 2 is compatible with context 1.
2713+
* ctx2 is compatible with ctx1 if any execution state with ctx2 can run on code emitted from ctx1
2714+
*
2715+
* @param ctx1 The base type context.
2716+
* @param ctx2 The type context to compare with.
2717+
* @return true if compatible, false otherwise.
2718+
*/
2719+
static bool
2720+
typecontext_is_compatible(_PyTier2TypeContext *ctx1, _PyTier2TypeContext *ctx2)
2721+
{
2722+
// This function does two things:
2723+
// 1. Check that the trees are the same "shape" and equivalent. This allows
2724+
// ctx1's trees to be a subtree of ctx2.
2725+
// 2. Check that the trees resolve to the same root type.
2726+
2727+
#ifdef Py_DEBUG
2728+
// These should be true during runtime
2729+
assert(ctx1->type_locals_len == ctx2->type_locals_len);
2730+
assert(ctx1->type_stack_len == ctx2->type_stack_len);
2731+
int stack_elems1 = (int)(ctx1->type_stack_ptr - ctx1->type_stack);
2732+
int stack_elems2 = (int)(ctx2->type_stack_ptr - ctx2->type_stack);
2733+
assert(stack_elems1 == stack_elems2);
2734+
#endif
2735+
2736+
// Check the locals
2737+
for (int i = 0; i < ctx1->type_locals_len; i++) {
2738+
if (!typenode_is_compatible(ctx1, ctx2, &ctx1->type_locals[i],
2739+
&ctx2->type_locals[i])) {
2740+
return false;
2741+
}
2742+
}
2743+
2744+
// Check the type stack
2745+
for (int i = 0; i < stack_elems1; i++) {
2746+
if (!typenode_is_compatible(ctx1, ctx2, &ctx1->type_stack[i],
2747+
&ctx2->type_stack[i])) {
2748+
return false;
2749+
}
2750+
}
2751+
2752+
return true;
2753+
}
2754+
26612755
/**
26622756
* @brief Calculates the difference between two type contexts.
26632757
* @param ctx1 The base type context.
@@ -2672,16 +2766,22 @@ diff_typecontext(_PyTier2TypeContext *ctx1, _PyTier2TypeContext *ctx2)
26722766
assert(ctx2 != NULL);
26732767
#if BB_DEBUG
26742768
fprintf(stderr, " [*] Diffing type contexts\n");
2769+
#if TYPEPROP_DEBUG
26752770
static void print_typestack(const _PyTier2TypeContext * type_context);
26762771
print_typestack(ctx1);
26772772
print_typestack(ctx2);
2773+
#endif
26782774
#endif
26792775
assert(ctx1->type_locals_len == ctx2->type_locals_len);
26802776
assert(ctx1->type_stack_len == ctx2->type_stack_len);
26812777
int stack_elems1 = (int)(ctx1->type_stack_ptr - ctx1->type_stack);
26822778
int stack_elems2 = (int)(ctx2->type_stack_ptr - ctx2->type_stack);
26832779
assert(stack_elems1 == stack_elems2);
26842780

2781+
if (!typecontext_is_compatible(ctx1, ctx2)) {
2782+
return INT_MAX;
2783+
}
2784+
26852785
int diff = 0;
26862786
// Check the difference in the type locals
26872787
for (int i = 0; i < ctx1->type_locals_len; i++) {
@@ -2822,9 +2922,17 @@ _PyTier2_LocateJumpBackwardsBB(_PyInterpreterFrame *frame, uint16_t bb_id_tagged
28222922
assert(jump_offset_id >= 0);
28232923
assert(candidate_bb_id >= 0);
28242924
assert(candidate_bb_tier1_start != NULL);
2925+
#if BB_DEBUG
2926+
if (matching_bb_id != -1) {
2927+
fprintf(stderr, "Found jump target BB ID: %d\n", matching_bb_id);
2928+
}
2929+
#endif
28252930
// We couldn't find a matching BB to jump to. Time to generate our own.
28262931
// This also requires rewriting our backwards jump to a forward jump later.
28272932
if (matching_bb_id == -1) {
2933+
#if BB_DEBUG
2934+
fprintf(stderr, "Generating new jump target BB ID: %d\n", matching_bb_id);
2935+
#endif
28282936
// We should use the type context occuring at the end of the loop.
28292937
_PyTier2TypeContext *copied = _PyTier2TypeContext_Copy(curr_type_context);
28302938
if (copied == NULL) {
@@ -2867,7 +2975,7 @@ _PyTier2_LocateJumpBackwardsBB(_PyInterpreterFrame *frame, uint16_t bb_id_tagged
28672975
assert(matching_bb_id >= 0);
28682976
assert(matching_bb_id <= t2_info->bb_data_curr);
28692977
#if BB_DEBUG
2870-
fprintf(stderr, "Found jump target BB ID: %d\n", matching_bb_id);
2978+
fprintf(stderr, "Using jump target BB ID: %d\n", matching_bb_id);
28712979
#endif
28722980
_PyTier2BBMetadata *target_metadata = t2_info->bb_data[matching_bb_id];
28732981
return target_metadata->tier2_start;

tier2_test.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,4 +447,20 @@ def test_iter_tuple(a):
447447
assert jmp_target.opname == "NOP" # Space for an EXTENDED_ARG
448448
assert insts[instidx + 1].opname == "BB_TEST_ITER_TUPLE" # The loop predicate
449449

450+
######################################################################
451+
# Tests for: Tier 2 backward jump type context compatiblity check #
452+
######################################################################
453+
with TestInfo("type context backwards jump compatibility check"):
454+
# See https://github.com/pylbbv/pylbbv/issues/9 for more information.
455+
def f(y,z,w):
456+
d = z
457+
for _ in [1,2]:
458+
z+z
459+
d+d
460+
d=w
461+
462+
463+
trigger_tier2(f, (1,1,1.))
464+
465+
# As long as it doesn't crash, everything's good.
450466
print("Tests completed ^-^")

0 commit comments

Comments
 (0)