Skip to content

Commit e541579

Browse files
Merge branch 'master' into patch-8
2 parents a2bff60 + d69419c commit e541579

File tree

17 files changed

+384
-95
lines changed

17 files changed

+384
-95
lines changed

mypyc/codegen/emitclass.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ def setter_name(cl: ClassIR, attribute: str, names: NameGenerator) -> str:
410410

411411

412412
def generate_object_struct(cl: ClassIR, emitter: Emitter) -> None:
413-
seen_attrs: set[tuple[str, RType]] = set()
413+
seen_attrs: set[str] = set()
414414
lines: list[str] = []
415415
lines += ["typedef struct {", "PyObject_HEAD", "CPyVTableItem *vtable;"]
416416
if cl.has_method("__call__"):
@@ -427,9 +427,11 @@ def generate_object_struct(cl: ClassIR, emitter: Emitter) -> None:
427427
lines.append(f"{BITMAP_TYPE} {attr};")
428428
bitmap_attrs.append(attr)
429429
for attr, rtype in base.attributes.items():
430-
if (attr, rtype) not in seen_attrs:
430+
# Generated class may redefine certain attributes with different
431+
# types in subclasses (this would be unsafe for user-defined classes).
432+
if attr not in seen_attrs:
431433
lines.append(f"{emitter.ctype_spaced(rtype)}{emitter.attr(attr)};")
432-
seen_attrs.add((attr, rtype))
434+
seen_attrs.add(attr)
433435

434436
if isinstance(rtype, RTuple):
435437
emitter.declare_tuple_struct(rtype)

mypyc/codegen/emitmodule.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,6 +1064,8 @@ def emit_module_exec_func(
10641064
"(PyObject *){t}_template, NULL, modname);".format(t=type_struct)
10651065
)
10661066
emitter.emit_lines(f"if (unlikely(!{type_struct}))", " goto fail;")
1067+
name_prefix = cl.name_prefix(emitter.names)
1068+
emitter.emit_line(f"CPyDef_{name_prefix}_trait_vtable_setup();")
10671069

10681070
emitter.emit_lines("if (CPyGlobalsInit() < 0)", " goto fail;")
10691071

mypyc/ir/func_ir.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,11 @@ def __init__(
149149
module_name: str,
150150
sig: FuncSignature,
151151
kind: int = FUNC_NORMAL,
152+
*,
152153
is_prop_setter: bool = False,
153154
is_prop_getter: bool = False,
155+
is_generator: bool = False,
156+
is_coroutine: bool = False,
154157
implicit: bool = False,
155158
internal: bool = False,
156159
) -> None:
@@ -161,6 +164,8 @@ def __init__(
161164
self.kind = kind
162165
self.is_prop_setter = is_prop_setter
163166
self.is_prop_getter = is_prop_getter
167+
self.is_generator = is_generator
168+
self.is_coroutine = is_coroutine
164169
if class_name is None:
165170
self.bound_sig: FuncSignature | None = None
166171
else:
@@ -219,6 +224,8 @@ def serialize(self) -> JsonDict:
219224
"kind": self.kind,
220225
"is_prop_setter": self.is_prop_setter,
221226
"is_prop_getter": self.is_prop_getter,
227+
"is_generator": self.is_generator,
228+
"is_coroutine": self.is_coroutine,
222229
"implicit": self.implicit,
223230
"internal": self.internal,
224231
}
@@ -240,10 +247,12 @@ def deserialize(cls, data: JsonDict, ctx: DeserMaps) -> FuncDecl:
240247
data["module_name"],
241248
FuncSignature.deserialize(data["sig"], ctx),
242249
data["kind"],
243-
data["is_prop_setter"],
244-
data["is_prop_getter"],
245-
data["implicit"],
246-
data["internal"],
250+
is_prop_setter=data["is_prop_setter"],
251+
is_prop_getter=data["is_prop_getter"],
252+
is_generator=data["is_generator"],
253+
is_coroutine=data["is_coroutine"],
254+
implicit=data["implicit"],
255+
internal=data["internal"],
247256
)
248257

249258

mypyc/irbuild/context.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,11 @@ def curr_env_reg(self) -> Value:
9898
def can_merge_generator_and_env_classes(self) -> bool:
9999
# In simple cases we can place the environment into the generator class,
100100
# instead of having two separate classes.
101-
return self.is_generator and not self.is_nested and not self.contains_nested
101+
if self._generator_class and not self._generator_class.ir.is_final_class:
102+
result = False
103+
else:
104+
result = self.is_generator and not self.is_nested and not self.contains_nested
105+
return result
102106

103107

104108
class ImplicitClass:

mypyc/irbuild/for_helpers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,6 +1218,11 @@ def get_expr_length(builder: IRBuilder, expr: Expression) -> int | None:
12181218
# performance boost and can be (sometimes) figured out pretty easily. set and dict
12191219
# comps *can* be done as well but will need special logic to consider the possibility
12201220
# of key conflicts. Range, enumerate, zip are all simple logic.
1221+
1222+
# we might still be able to get the length directly from the type
1223+
rtype = builder.node_type(expr)
1224+
if isinstance(rtype, RTuple):
1225+
return len(rtype.types)
12211226
return None
12221227

12231228

mypyc/irbuild/function.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
instantiate_callable_class,
7070
setup_callable_class,
7171
)
72-
from mypyc.irbuild.context import FuncInfo
72+
from mypyc.irbuild.context import FuncInfo, GeneratorClass
7373
from mypyc.irbuild.env_class import (
7474
add_vars_to_env,
7575
finalize_env_class,
@@ -246,6 +246,12 @@ def c() -> None:
246246
is_generator = fn_info.is_generator
247247
builder.enter(fn_info, ret_type=sig.ret_type)
248248

249+
if is_generator:
250+
fitem = builder.fn_info.fitem
251+
assert isinstance(fitem, FuncDef), fitem
252+
generator_class_ir = builder.mapper.fdef_to_generator[fitem]
253+
builder.fn_info.generator_class = GeneratorClass(generator_class_ir)
254+
249255
# Functions that contain nested functions need an environment class to store variables that
250256
# are free in their nested functions. Generator functions need an environment class to
251257
# store a variable denoting the next instruction to be executed when the __next__ function
@@ -357,8 +363,8 @@ def gen_func_ir(
357363
builder.module_name,
358364
sig,
359365
func_decl.kind,
360-
func_decl.is_prop_getter,
361-
func_decl.is_prop_setter,
366+
is_prop_getter=func_decl.is_prop_getter,
367+
is_prop_setter=func_decl.is_prop_setter,
362368
)
363369
func_ir = FuncIR(func_decl, args, blocks, fitem.line, traceback_name=fitem.name)
364370
else:

mypyc/irbuild/generator.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
object_rprimitive,
4040
)
4141
from mypyc.irbuild.builder import IRBuilder, calculate_arg_defaults, gen_arg_defaults
42-
from mypyc.irbuild.context import FuncInfo, GeneratorClass
42+
from mypyc.irbuild.context import FuncInfo
4343
from mypyc.irbuild.env_class import (
4444
add_args_to_env,
4545
add_vars_to_env,
@@ -166,10 +166,8 @@ def setup_generator_class(builder: IRBuilder) -> ClassIR:
166166
builder.fn_info.env_class = generator_class_ir
167167
else:
168168
generator_class_ir.attributes[ENV_ATTR_NAME] = RInstance(builder.fn_info.env_class)
169-
generator_class_ir.mro = [generator_class_ir]
170169

171170
builder.classes.append(generator_class_ir)
172-
builder.fn_info.generator_class = GeneratorClass(generator_class_ir)
173171
return generator_class_ir
174172

175173

mypyc/irbuild/prepare.py

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,15 @@ def prepare_func_def(
202202
else (FUNC_STATICMETHOD if fdef.is_static else FUNC_NORMAL)
203203
)
204204
sig = mapper.fdef_to_sig(fdef, options.strict_dunders_typing)
205-
decl = FuncDecl(fdef.name, class_name, module_name, sig, kind)
205+
decl = FuncDecl(
206+
fdef.name,
207+
class_name,
208+
module_name,
209+
sig,
210+
kind,
211+
is_generator=fdef.is_generator,
212+
is_coroutine=fdef.is_coroutine,
213+
)
206214
mapper.func_to_decl[fdef] = decl
207215
return decl
208216

@@ -217,7 +225,7 @@ def create_generator_class_for_func(
217225
"""
218226
assert fdef.is_coroutine or fdef.is_generator
219227
name = "_".join(x for x in [fdef.name, class_name] if x) + "_gen" + name_suffix
220-
cir = ClassIR(name, module_name, is_generated=True, is_final_class=True)
228+
cir = ClassIR(name, module_name, is_generated=True, is_final_class=class_name is None)
221229
cir.reuse_freed_instance = True
222230
mapper.fdef_to_generator[fdef] = cir
223231

@@ -816,14 +824,70 @@ def adjust_generator_classes_of_methods(mapper: Mapper) -> None:
816824
This is a separate pass after type map has been built, since we need all classes
817825
to be processed to analyze class hierarchies.
818826
"""
819-
for fdef, ir in mapper.func_to_decl.items():
827+
828+
generator_methods = []
829+
830+
for fdef, fn_ir in mapper.func_to_decl.items():
820831
if isinstance(fdef, FuncDef) and (fdef.is_coroutine or fdef.is_generator):
821-
gen_ir = create_generator_class_for_func(ir.module_name, ir.class_name, fdef, mapper)
832+
gen_ir = create_generator_class_for_func(
833+
fn_ir.module_name, fn_ir.class_name, fdef, mapper
834+
)
822835
# TODO: We could probably support decorators sometimes (static and class method?)
823836
if not fdef.is_decorated:
824-
# Give a more precise type for generators, so that we can optimize
825-
# code that uses them. They return a generator object, which has a
826-
# specific class. Without this, the type would have to be 'object'.
827-
ir.sig.ret_type = RInstance(gen_ir)
828-
if ir.bound_sig:
829-
ir.bound_sig.ret_type = RInstance(gen_ir)
837+
name = fn_ir.name
838+
precise_ret_type = True
839+
if fn_ir.class_name is not None:
840+
class_ir = mapper.type_to_ir[fdef.info]
841+
subcls = class_ir.subclasses()
842+
if subcls is None:
843+
# Override could be of a different type, so we can't make assumptions.
844+
precise_ret_type = False
845+
else:
846+
for s in subcls:
847+
if name in s.method_decls:
848+
m = s.method_decls[name]
849+
if (
850+
m.is_generator != fn_ir.is_generator
851+
or m.is_coroutine != fn_ir.is_coroutine
852+
):
853+
# Override is of a different kind, and the optimization
854+
# to use a precise generator return type doesn't work.
855+
precise_ret_type = False
856+
else:
857+
class_ir = None
858+
859+
if precise_ret_type:
860+
# Give a more precise type for generators, so that we can optimize
861+
# code that uses them. They return a generator object, which has a
862+
# specific class. Without this, the type would have to be 'object'.
863+
fn_ir.sig.ret_type = RInstance(gen_ir)
864+
if fn_ir.bound_sig:
865+
fn_ir.bound_sig.ret_type = RInstance(gen_ir)
866+
if class_ir is not None:
867+
if class_ir.is_method_final(name):
868+
gen_ir.is_final_class = True
869+
generator_methods.append((name, class_ir, gen_ir))
870+
871+
new_bases = {}
872+
873+
for name, class_ir, gen in generator_methods:
874+
# For generator methods, we need to have subclass generator classes inherit from
875+
# baseclass generator classes when there are overrides to maintain LSP.
876+
base = class_ir.real_base()
877+
if base is not None:
878+
if base.has_method(name):
879+
base_sig = base.method_sig(name)
880+
if isinstance(base_sig.ret_type, RInstance):
881+
base_gen = base_sig.ret_type.class_ir
882+
new_bases[gen] = base_gen
883+
884+
# Add generator inheritance relationships by adjusting MROs.
885+
for deriv, base in new_bases.items():
886+
if base.children is not None:
887+
base.children.append(deriv)
888+
while True:
889+
deriv.mro.append(base)
890+
deriv.base_mro.append(base)
891+
if base not in new_bases:
892+
break
893+
base = new_bases[base]

mypyc/lib-rt/CPy.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ CPyTagged CPyTagged_Remainder_(CPyTagged left, CPyTagged right);
148148
CPyTagged CPyTagged_BitwiseLongOp_(CPyTagged a, CPyTagged b, char op);
149149
CPyTagged CPyTagged_Rshift_(CPyTagged left, CPyTagged right);
150150
CPyTagged CPyTagged_Lshift_(CPyTagged left, CPyTagged right);
151+
CPyTagged CPyTagged_BitLength(CPyTagged self);
151152

152153
PyObject *CPyTagged_Str(CPyTagged n);
153154
CPyTagged CPyTagged_FromFloat(double f);

mypyc/lib-rt/int_ops.c

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
#include <Python.h>
66
#include "CPy.h"
77

8+
#ifdef _MSC_VER
9+
#include <intrin.h>
10+
#endif
11+
812
#ifndef _WIN32
913
// On 64-bit Linux and macOS, ssize_t and long are both 64 bits, and
1014
// PyLong_FromLong is faster than PyLong_FromSsize_t, so use the faster one
@@ -15,6 +19,17 @@
1519
#define CPyLong_FromSsize_t PyLong_FromSsize_t
1620
#endif
1721

22+
#if defined(__GNUC__) || defined(__clang__)
23+
# if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || (defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ == 8)
24+
# define CPY_CLZ(x) __builtin_clzll((unsigned long long)(x))
25+
# define CPY_BITS 64
26+
# else
27+
# define CPY_CLZ(x) __builtin_clz((unsigned int)(x))
28+
# define CPY_BITS 32
29+
# endif
30+
#endif
31+
32+
1833
CPyTagged CPyTagged_FromSsize_t(Py_ssize_t value) {
1934
// We use a Python object if the value shifted left by 1 is too
2035
// large for Py_ssize_t
@@ -581,3 +596,52 @@ double CPyTagged_TrueDivide(CPyTagged x, CPyTagged y) {
581596
}
582597
return 1.0;
583598
}
599+
600+
// int.bit_length()
601+
CPyTagged CPyTagged_BitLength(CPyTagged self) {
602+
// Handle zero
603+
if (self == 0) {
604+
return 0;
605+
}
606+
607+
// Fast path for small (tagged) ints
608+
if (CPyTagged_CheckShort(self)) {
609+
Py_ssize_t val = CPyTagged_ShortAsSsize_t(self);
610+
Py_ssize_t absval = val < 0 ? -val : val;
611+
int bits = 0;
612+
if (absval) {
613+
#if defined(_MSC_VER)
614+
#if defined(_WIN64)
615+
unsigned long idx;
616+
if (_BitScanReverse64(&idx, (unsigned __int64)absval)) {
617+
bits = (int)(idx + 1);
618+
}
619+
#else
620+
unsigned long idx;
621+
if (_BitScanReverse(&idx, (unsigned long)absval)) {
622+
bits = (int)(idx + 1);
623+
}
624+
#endif
625+
#elif defined(__GNUC__) || defined(__clang__)
626+
bits = (int)(CPY_BITS - CPY_CLZ(absval));
627+
#else
628+
// Fallback to loop if no builtin
629+
while (absval) {
630+
absval >>= 1;
631+
bits++;
632+
}
633+
#endif
634+
}
635+
return bits << 1;
636+
}
637+
638+
// Slow path for big ints
639+
PyObject *pyint = CPyTagged_AsObject(self);
640+
int bits = _PyLong_NumBits(pyint);
641+
Py_DECREF(pyint);
642+
if (bits < 0) {
643+
// _PyLong_NumBits sets an error on failure
644+
return CPY_INT_TAG;
645+
}
646+
return bits << 1;
647+
}

0 commit comments

Comments
 (0)