diff --git a/mypyc/analysis/attrdefined.py b/mypyc/analysis/attrdefined.py index 170c0029ba04..dc871a93eba1 100644 --- a/mypyc/analysis/attrdefined.py +++ b/mypyc/analysis/attrdefined.py @@ -91,7 +91,7 @@ def foo(self) -> int: SetMem, Unreachable, ) -from mypyc.ir.rtypes import RInstance +from mypyc.ir.rtypes import RInstance, is_fixed_width_rtype # If True, print out all always-defined attributes of native classes (to aid # debugging and testing) @@ -120,6 +120,11 @@ def analyze_always_defined_attrs(class_irs: list[ClassIR]) -> None: for cl in class_irs: update_always_defined_attrs_using_subclasses(cl, seen) + # Final pass: detect attributes that need to use a bitmap to track definedness + seen = set() + for cl in class_irs: + detect_undefined_bitmap(cl, seen) + def analyze_always_defined_attrs_in_class(cl: ClassIR, seen: set[ClassIR]) -> None: if cl in seen: @@ -407,3 +412,17 @@ def update_always_defined_attrs_using_subclasses(cl: ClassIR, seen: set[ClassIR] removed.add(attr) cl._always_initialized_attrs -= removed seen.add(cl) + + +def detect_undefined_bitmap(cl: ClassIR, seen: Set[ClassIR]) -> None: + if cl in seen: + return + seen.add(cl) + for base in cl.base_mro[1:]: + detect_undefined_bitmap(cl, seen) + + if len(cl.base_mro) > 1: + cl.bitmap_attrs.extend(cl.base_mro[1].bitmap_attrs) + for n, t in cl.attributes.items(): + if is_fixed_width_rtype(t) and not cl.is_always_defined(n): + cl.bitmap_attrs.append(n) diff --git a/mypyc/codegen/emit.py b/mypyc/codegen/emit.py index 3fd48dcd1cb8..90919665a0d2 100644 --- a/mypyc/codegen/emit.py +++ b/mypyc/codegen/emit.py @@ -8,6 +8,7 @@ from mypyc.codegen.literals import Literals from mypyc.common import ( + ATTR_BITMAP_BITS, ATTR_PREFIX, FAST_ISINSTANCE_MAX_SUBCLASSES, NATIVE_PREFIX, @@ -329,21 +330,81 @@ def tuple_c_declaration(self, rtuple: RTuple) -> list[str]: return result + def bitmap_field(self, index: int) -> str: + """Return C field name used for attribute bitmap.""" + n = index // ATTR_BITMAP_BITS + if n == 0: + return "bitmap" + return f"bitmap{n + 1}" + + def attr_bitmap_expr(self, obj: str, cl: ClassIR, index: int) -> str: + """Return reference to the attribute definedness bitmap.""" + cast = f"({cl.struct_name(self.names)} *)" + attr = self.bitmap_field(index) + return f"({cast}{obj})->{attr}" + + def emit_attr_bitmap_set( + self, value: str, obj: str, rtype: RType, cl: ClassIR, attr: str + ) -> None: + """Mark an attribute as defined in the attribute bitmap. + + Assumes that the attribute is tracked in the bitmap (only some attributes + use the bitmap). If 'value' is not equal to the error value, do nothing. + """ + self._emit_attr_bitmap_update(value, obj, rtype, cl, attr, clear=False) + + def emit_attr_bitmap_clear(self, obj: str, rtype: RType, cl: ClassIR, attr: str) -> None: + """Mark an attribute as undefined in the attribute bitmap. + + Unlike emit_attr_bitmap_set, clear unconditionally. + """ + self._emit_attr_bitmap_update("", obj, rtype, cl, attr, clear=True) + + def _emit_attr_bitmap_update( + self, value: str, obj: str, rtype: RType, cl: ClassIR, attr: str, clear: bool + ) -> None: + if value: + self.emit_line(f"if (unlikely({value} == {self.c_undefined_value(rtype)})) {{") + index = cl.bitmap_attrs.index(attr) + mask = 1 << (index & (ATTR_BITMAP_BITS - 1)) + bitmap = self.attr_bitmap_expr(obj, cl, index) + if clear: + self.emit_line(f"{bitmap} &= ~{mask};") + else: + self.emit_line(f"{bitmap} |= {mask};") + if value: + self.emit_line("}") + def use_vectorcall(self) -> bool: return use_vectorcall(self.capi_version) def emit_undefined_attr_check( - self, rtype: RType, attr_expr: str, compare: str, unlikely: bool = False + self, + rtype: RType, + attr_expr: str, + compare: str, + obj: str, + attr: str, + cl: ClassIR, + *, + unlikely: bool = False, ) -> None: if isinstance(rtype, RTuple): - check = "({})".format( + check = "{}".format( self.tuple_undefined_check_cond(rtype, attr_expr, self.c_undefined_value, compare) ) else: - check = f"({attr_expr} {compare} {self.c_undefined_value(rtype)})" + undefined = self.c_undefined_value(rtype) + check = f"{attr_expr} {compare} {undefined}" if unlikely: - check = f"(unlikely{check})" - self.emit_line(f"if {check} {{") + check = f"unlikely({check})" + if is_fixed_width_rtype(rtype): + index = cl.bitmap_attrs.index(attr) + bit = 1 << (index & (ATTR_BITMAP_BITS - 1)) + attr = self.bitmap_field(index) + obj_expr = f"({cl.struct_name(self.names)} *){obj}" + check = f"{check} && !(({obj_expr})->{attr} & {bit})" + self.emit_line(f"if ({check}) {{") def tuple_undefined_check_cond( self, diff --git a/mypyc/codegen/emitclass.py b/mypyc/codegen/emitclass.py index 5434b5c01219..a93ef1b57a1e 100644 --- a/mypyc/codegen/emitclass.py +++ b/mypyc/codegen/emitclass.py @@ -17,10 +17,17 @@ generate_richcompare_wrapper, generate_set_del_item_wrapper, ) -from mypyc.common import NATIVE_PREFIX, PREFIX, REG_PREFIX, use_fastcall +from mypyc.common import ( + ATTR_BITMAP_BITS, + ATTR_BITMAP_TYPE, + NATIVE_PREFIX, + PREFIX, + REG_PREFIX, + use_fastcall, +) from mypyc.ir.class_ir import ClassIR, VTableEntries from mypyc.ir.func_ir import FUNC_CLASSMETHOD, FUNC_STATICMETHOD, FuncDecl, FuncIR -from mypyc.ir.rtypes import RTuple, RType, object_rprimitive +from mypyc.ir.rtypes import RTuple, RType, is_fixed_width_rtype, object_rprimitive from mypyc.namegen import NameGenerator from mypyc.sametype import is_same_type @@ -367,8 +374,17 @@ def generate_object_struct(cl: ClassIR, emitter: Emitter) -> None: lines += ["typedef struct {", "PyObject_HEAD", "CPyVTableItem *vtable;"] if cl.has_method("__call__") and emitter.use_vectorcall(): lines.append("vectorcallfunc vectorcall;") + bitmap_attrs = [] for base in reversed(cl.base_mro): if not base.is_trait: + if base.bitmap_attrs: + # Do we need another attribute bitmap field? + if emitter.bitmap_field(len(base.bitmap_attrs) - 1) not in bitmap_attrs: + for i in range(0, len(base.bitmap_attrs), ATTR_BITMAP_BITS): + attr = emitter.bitmap_field(i) + if attr not in bitmap_attrs: + lines.append(f"{ATTR_BITMAP_TYPE} {attr};") + bitmap_attrs.append(attr) for attr, rtype in base.attributes.items(): if (attr, rtype) not in seen_attrs: lines.append(f"{emitter.ctype_spaced(rtype)}{emitter.attr(attr)};") @@ -546,6 +562,9 @@ def generate_setup_for_class( emitter.emit_line("}") else: emitter.emit_line(f"self->vtable = {vtable_name};") + for i in range(0, len(cl.bitmap_attrs), ATTR_BITMAP_BITS): + field = emitter.bitmap_field(i) + emitter.emit_line(f"self->{field} = 0;") if cl.has_method("__call__") and emitter.use_vectorcall(): name = cl.method_decl("__call__").cname(emitter.names) @@ -887,7 +906,7 @@ def generate_getter(cl: ClassIR, attr: str, rtype: RType, emitter: Emitter) -> N always_defined = cl.is_always_defined(attr) and not rtype.is_refcounted if not always_defined: - emitter.emit_undefined_attr_check(rtype, attr_expr, "==", unlikely=True) + emitter.emit_undefined_attr_check(rtype, attr_expr, "==", "self", attr, cl, unlikely=True) emitter.emit_line("PyErr_SetString(PyExc_AttributeError,") emitter.emit_line(f' "attribute {repr(attr)} of {repr(cl.name)} undefined");') emitter.emit_line("return NULL;") @@ -926,7 +945,7 @@ def generate_setter(cl: ClassIR, attr: str, rtype: RType, emitter: Emitter) -> N if rtype.is_refcounted: attr_expr = f"self->{attr_field}" if not always_defined: - emitter.emit_undefined_attr_check(rtype, attr_expr, "!=") + emitter.emit_undefined_attr_check(rtype, attr_expr, "!=", "self", attr, cl) emitter.emit_dec_ref(f"self->{attr_field}", rtype) if not always_defined: emitter.emit_line("}") @@ -943,9 +962,14 @@ def generate_setter(cl: ClassIR, attr: str, rtype: RType, emitter: Emitter) -> N emitter.emit_lines("if (!tmp)", " return -1;") emitter.emit_inc_ref("tmp", rtype) emitter.emit_line(f"self->{attr_field} = tmp;") + if is_fixed_width_rtype(rtype) and not always_defined: + emitter.emit_attr_bitmap_set("tmp", "self", rtype, cl, attr) + if deletable: emitter.emit_line("} else") emitter.emit_line(f" self->{attr_field} = {emitter.c_undefined_value(rtype)};") + if is_fixed_width_rtype(rtype): + emitter.emit_attr_bitmap_clear("self", rtype, cl, attr) emitter.emit_line("return 0;") emitter.emit_line("}") diff --git a/mypyc/codegen/emitfunc.py b/mypyc/codegen/emitfunc.py index c0aaff2c5f99..2c096655f41e 100644 --- a/mypyc/codegen/emitfunc.py +++ b/mypyc/codegen/emitfunc.py @@ -60,6 +60,7 @@ RStruct, RTuple, RType, + is_fixed_width_rtype, is_int32_rprimitive, is_int64_rprimitive, is_int_rprimitive, @@ -353,7 +354,9 @@ def visit_get_attr(self, op: GetAttr) -> None: always_defined = cl.is_always_defined(op.attr) merged_branch = None if not always_defined: - self.emitter.emit_undefined_attr_check(attr_rtype, dest, "==", unlikely=True) + self.emitter.emit_undefined_attr_check( + attr_rtype, dest, "==", obj, op.attr, cl, unlikely=True + ) branch = self.next_branch() if branch is not None: if ( @@ -433,10 +436,17 @@ def visit_set_attr(self, op: SetAttr) -> None: # previously undefined), so decref the old value. always_defined = cl.is_always_defined(op.attr) if not always_defined: - self.emitter.emit_undefined_attr_check(attr_rtype, attr_expr, "!=") + self.emitter.emit_undefined_attr_check( + attr_rtype, attr_expr, "!=", obj, op.attr, cl + ) self.emitter.emit_dec_ref(attr_expr, attr_rtype) if not always_defined: self.emitter.emit_line("}") + elif is_fixed_width_rtype(attr_rtype) and not cl.is_always_defined(op.attr): + # If there is overlap with the error value, update bitmap to mark + # attribute as defined. + self.emitter.emit_attr_bitmap_set(src, obj, attr_rtype, cl, op.attr) + # This steals the reference to src, so we don't need to increment the arg self.emitter.emit_line(f"{attr_expr} = {src};") if op.error_kind == ERR_FALSE: diff --git a/mypyc/common.py b/mypyc/common.py index 8083d83c9d6a..e0202eaa3edc 100644 --- a/mypyc/common.py +++ b/mypyc/common.py @@ -53,6 +53,11 @@ MAX_LITERAL_SHORT_INT: Final = sys.maxsize >> 1 if not IS_MIXED_32_64_BIT_BUILD else 2**30 - 1 MIN_LITERAL_SHORT_INT: Final = -MAX_LITERAL_SHORT_INT - 1 +# Decription of the C type used to track definedness of attributes +# that have types with overlapping error values +ATTR_BITMAP_TYPE: Final = "uint32_t" +ATTR_BITMAP_BITS: Final = 32 + # Runtime C library files RUNTIME_C_FILES: Final = [ "init.c", diff --git a/mypyc/ir/class_ir.py b/mypyc/ir/class_ir.py index dca19e5a2e3c..7f55decfd754 100644 --- a/mypyc/ir/class_ir.py +++ b/mypyc/ir/class_ir.py @@ -130,7 +130,7 @@ def __init__( self.builtin_base: str | None = None # Default empty constructor self.ctor = FuncDecl(name, None, module_name, FuncSignature([], RInstance(self))) - + # Attributes defined in the class (not inherited) self.attributes: dict[str, RType] = {} # Deletable attributes self.deletable: list[str] = [] @@ -184,6 +184,13 @@ def __init__( # If True, __init__ can make 'self' visible to unanalyzed/arbitrary code self.init_self_leak = False + # Definedness of these attributes is backed by a bitmap. Index in the list + # indicates the bit number. Includes inherited attributes. We need the + # bitmap for types such as native ints that can't have a dedicated error + # value that doesn't overlap a valid value. The bitmap is used if the + # value of an attribute is the same as the error value. + self.bitmap_attrs: List[str] = [] + def __repr__(self) -> str: return ( "ClassIR(" diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index 56a1e6103acf..361221f5b710 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -633,7 +633,7 @@ def __init__(self, obj: Value, attr: str, line: int, *, borrow: bool = False) -> attr_type = obj.type.attr_type(attr) self.type = attr_type if is_fixed_width_rtype(attr_type): - self.error_kind = ERR_NEVER + self.error_kind = ERR_MAGIC_OVERLAPPING self.is_borrowed = borrow and attr_type.is_refcounted def sources(self) -> list[Value]: diff --git a/mypyc/irbuild/main.py b/mypyc/irbuild/main.py index e20872979b7a..9bbb90aad207 100644 --- a/mypyc/irbuild/main.py +++ b/mypyc/irbuild/main.py @@ -57,7 +57,11 @@ def build_ir( options: CompilerOptions, errors: Errors, ) -> ModuleIRs: - """Build IR for a set of modules that have been type-checked by mypy.""" + """Build basic IR for a set of modules that have been type-checked by mypy. + + The returned IR is not complete and requires additional + transformations, such as the insertion of refcount handling. + """ build_type_map(mapper, modules, graph, types, options, errors) singledispatch_info = find_singledispatch_register_impls(modules, errors) diff --git a/mypyc/test-data/run-i64.test b/mypyc/test-data/run-i64.test index d7bba82493b2..e91c24185f4f 100644 --- a/mypyc/test-data/run-i64.test +++ b/mypyc/test-data/run-i64.test @@ -335,10 +335,13 @@ def test_mixed_arithmetic_and_bitwise_ops() -> None: with assertRaises(OverflowError): assert int_too_small & i64_3 -[case testI64ErrorValues] +[case testI64ErrorValuesAndUndefined] from typing import Any import sys +from mypy_extensions import mypyc_attr +from typing_extensions import Final + MYPY = False if MYPY: from mypy_extensions import i64 @@ -390,3 +393,283 @@ def test_unbox_int_fails() -> None: o3: Any = -(1 << 63 + 1) with assertRaises(OverflowError, "int too large to convert to i64"): z: i64 = o3 + +class Uninit: + x: i64 + y: i64 = 0 + z: i64 + +class Derived(Uninit): + a: i64 = 1 + b: i64 + c: i64 = 2 + +class Derived2(Derived): + h: i64 + +def test_uninitialized_attr() -> None: + o = Uninit() + assert o.y == 0 + with assertRaises(AttributeError): + o.x + with assertRaises(AttributeError): + o.z + o.x = 1 + assert o.x == 1 + with assertRaises(AttributeError): + o.z + o.z = 2 + assert o.z == 2 + +# This is the error value, but it's also a valid normal value +MAGIC: Final = -113 + +def test_magic_value() -> None: + o = Uninit() + o.x = MAGIC + assert o.x == MAGIC + with assertRaises(AttributeError): + o.z + o.z = MAGIC + assert o.x == MAGIC + assert o.z == MAGIC + +def test_magic_value_via_any() -> None: + o: Any = Uninit() + with assertRaises(AttributeError): + o.x + with assertRaises(AttributeError): + o.z + o.x = MAGIC + assert o.x == MAGIC + with assertRaises(AttributeError): + o.z + o.z = MAGIC + assert o.z == MAGIC + +def test_magic_value_and_inheritance() -> None: + o = Derived2() + o.x = MAGIC + assert o.x == MAGIC + with assertRaises(AttributeError): + o.z + with assertRaises(AttributeError): + o.b + with assertRaises(AttributeError): + o.h + o.z = MAGIC + assert o.z == MAGIC + with assertRaises(AttributeError): + o.b + with assertRaises(AttributeError): + o.h + o.h = MAGIC + assert o.h == MAGIC + with assertRaises(AttributeError): + o.b + o.b = MAGIC + assert o.b == MAGIC + +@mypyc_attr(allow_interpreted_subclasses=True) +class MagicInit: + x: i64 = MAGIC + +def test_magic_value_as_initializer() -> None: + o = MagicInit() + assert o.x == MAGIC + +class ManyUninit: + a1: i64 + a2: i64 + a3: i64 + a4: i64 + a5: i64 + a6: i64 + a7: i64 + a8: i64 + a9: i64 + a10: i64 + a11: i64 + a12: i64 + a13: i64 + a14: i64 + a15: i64 + a16: i64 + a17: i64 + a18: i64 + a19: i64 + a20: i64 + a21: i64 + a22: i64 + a23: i64 + a24: i64 + a25: i64 + a26: i64 + a27: i64 + a28: i64 + a29: i64 + a30: i64 + a31: i64 + a32: i64 + a33: i64 + a34: i64 + a35: i64 + a36: i64 + a37: i64 + a38: i64 + a39: i64 + a40: i64 + a41: i64 + a42: i64 + a43: i64 + a44: i64 + a45: i64 + a46: i64 + a47: i64 + a48: i64 + a49: i64 + a50: i64 + a51: i64 + a52: i64 + a53: i64 + a54: i64 + a55: i64 + a56: i64 + a57: i64 + a58: i64 + a59: i64 + a60: i64 + a61: i64 + a62: i64 + a63: i64 + a64: i64 + a65: i64 + a66: i64 + a67: i64 + a68: i64 + a69: i64 + a70: i64 + a71: i64 + a72: i64 + a73: i64 + a74: i64 + a75: i64 + a76: i64 + a77: i64 + a78: i64 + a79: i64 + a80: i64 + a81: i64 + a82: i64 + a83: i64 + a84: i64 + a85: i64 + a86: i64 + a87: i64 + a88: i64 + a89: i64 + a90: i64 + a91: i64 + a92: i64 + a93: i64 + a94: i64 + a95: i64 + a96: i64 + a97: i64 + a98: i64 + a99: i64 + a100: i64 + +def test_many_uninitialized_attributes() -> None: + o = ManyUninit() + with assertRaises(AttributeError): + o.a1 + with assertRaises(AttributeError): + o.a10 + with assertRaises(AttributeError): + o.a20 + with assertRaises(AttributeError): + o.a30 + with assertRaises(AttributeError): + o.a31 + with assertRaises(AttributeError): + o.a32 + with assertRaises(AttributeError): + o.a33 + with assertRaises(AttributeError): + o.a40 + with assertRaises(AttributeError): + o.a50 + with assertRaises(AttributeError): + o.a60 + with assertRaises(AttributeError): + o.a62 + with assertRaises(AttributeError): + o.a63 + with assertRaises(AttributeError): + o.a64 + with assertRaises(AttributeError): + o.a65 + with assertRaises(AttributeError): + o.a80 + with assertRaises(AttributeError): + o.a100 + o.a30 = MAGIC + assert o.a30 == MAGIC + o.a31 = MAGIC + assert o.a31 == MAGIC + o.a32 = MAGIC + assert o.a32 == MAGIC + o.a33 = MAGIC + assert o.a33 == MAGIC + with assertRaises(AttributeError): + o.a34 + o.a62 = MAGIC + assert o.a62 == MAGIC + o.a63 = MAGIC + assert o.a63 == MAGIC + o.a64 = MAGIC + assert o.a64 == MAGIC + o.a65 = MAGIC + assert o.a65 == MAGIC + with assertRaises(AttributeError): + o.a66 + +class BaseNoBitmap: + x: int = 5 + +class DerivedBitmap(BaseNoBitmap): + # Subclass needs a bitmap, but base class doesn't have it. + y: i64 + +def test_derived_adds_bitmap() -> None: + d = DerivedBitmap() + d.x = 643 + b: BaseNoBitmap = d + assert b.x == 643 + +class Delete: + __deletable__ = ['x', 'y'] + x: i64 + y: i64 + +def test_del() -> None: + o = Delete() + o.x = MAGIC + o.y = -1 + assert o.x == MAGIC + assert o.y == -1 + del o.x + with assertRaises(AttributeError): + o.x + assert o.y == -1 + del o.y + with assertRaises(AttributeError): + o.y + o.x = 5 + assert o.x == 5 + with assertRaises(AttributeError): + o.y + del o.x + with assertRaises(AttributeError): + o.x diff --git a/mypyc/test/test_emitfunc.py b/mypyc/test/test_emitfunc.py index 5be1e61cba8d..d7dcf3be532b 100644 --- a/mypyc/test/test_emitfunc.py +++ b/mypyc/test/test_emitfunc.py @@ -9,6 +9,7 @@ from mypyc.ir.class_ir import ClassIR from mypyc.ir.func_ir import FuncDecl, FuncIR, FuncSignature, RuntimeArg from mypyc.ir.ops import ( + ERR_NEVER, Assign, AssignMulti, BasicBlock, @@ -103,7 +104,13 @@ def add_local(name: str, rtype: RType) -> Register: "tt", RTuple([RTuple([int_rprimitive, bool_rprimitive]), bool_rprimitive]) ) ir = ClassIR("A", "mod") - ir.attributes = {"x": bool_rprimitive, "y": int_rprimitive} + ir.attributes = { + "x": bool_rprimitive, + "y": int_rprimitive, + "i1": int64_rprimitive, + "i2": int32_rprimitive, + } + ir.bitmap_attrs = ["i1", "i2"] compute_vtable(ir) ir.mro = [ir] self.r = add_local("r", RInstance(ir)) @@ -397,6 +404,16 @@ def test_get_attr_merged(self) -> None: skip_next=True, ) + def test_get_attr_with_bitmap(self) -> None: + self.assert_emit( + GetAttr(self.r, "i1", 1), + """cpy_r_r0 = ((mod___AObject *)cpy_r_r)->_i1; + if (unlikely(cpy_r_r0 == -113) && !(((mod___AObject *)cpy_r_r)->bitmap & 1)) { + PyErr_SetString(PyExc_AttributeError, "attribute 'i1' of 'A' undefined"); + } + """, + ) + def test_set_attr(self) -> None: self.assert_emit( SetAttr(self.r, "y", self.m, 1), @@ -416,6 +433,62 @@ def test_set_attr_non_refcounted(self) -> None: """, ) + def test_set_attr_no_error(self) -> None: + op = SetAttr(self.r, "y", self.m, 1) + op.error_kind = ERR_NEVER + self.assert_emit( + op, + """if (((mod___AObject *)cpy_r_r)->_y != CPY_INT_TAG) { + CPyTagged_DECREF(((mod___AObject *)cpy_r_r)->_y); + } + ((mod___AObject *)cpy_r_r)->_y = cpy_r_m; + """, + ) + + def test_set_attr_non_refcounted_no_error(self) -> None: + op = SetAttr(self.r, "x", self.b, 1) + op.error_kind = ERR_NEVER + self.assert_emit( + op, + """((mod___AObject *)cpy_r_r)->_x = cpy_r_b; + """, + ) + + def test_set_attr_with_bitmap(self) -> None: + # For some rtypes the error value overlaps a valid value, so we need + # to use a separate bitmap to track defined attributes. + self.assert_emit( + SetAttr(self.r, "i1", self.i64, 1), + """if (unlikely(cpy_r_i64 == -113)) { + ((mod___AObject *)cpy_r_r)->bitmap |= 1; + } + ((mod___AObject *)cpy_r_r)->_i1 = cpy_r_i64; + cpy_r_r0 = 1; + """, + ) + self.assert_emit( + SetAttr(self.r, "i2", self.i32, 1), + """if (unlikely(cpy_r_i32 == -113)) { + ((mod___AObject *)cpy_r_r)->bitmap |= 2; + } + ((mod___AObject *)cpy_r_r)->_i2 = cpy_r_i32; + cpy_r_r0 = 1; + """, + ) + + def test_set_attr_init_with_bitmap(self) -> None: + op = SetAttr(self.r, "i1", self.i64, 1) + op.is_init = True + self.assert_emit( + op, + """if (unlikely(cpy_r_i64 == -113)) { + ((mod___AObject *)cpy_r_r)->bitmap |= 1; + } + ((mod___AObject *)cpy_r_r)->_i1 = cpy_r_i64; + cpy_r_r0 = 1; + """, + ) + def test_dict_get_item(self) -> None: self.assert_emit( CallC(