Skip to content

Commit 68763ae

Browse files
authored
[mypyc] Implement builtins.len primitive for list (python#9271)
* implement list len primitive * use pointer type * rename * add PyObject * use CPyPtr * revert RStruct design and fix tests * list_len helper and updates according to review comments * fix * remove size_t_to_short_int
1 parent 1bbcd53 commit 68763ae

File tree

15 files changed

+417
-388
lines changed

15 files changed

+417
-388
lines changed

mypyc/codegen/emitfunc.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,8 @@ def visit_get_element_ptr(self, op: GetElementPtr) -> None:
477477
# TODO: support tuple type
478478
assert isinstance(op.src_type, RStruct)
479479
assert op.field in op.src_type.names, "Invalid field name."
480-
self.emit_line('%s = &%s.%s;' % (dest, src, op.field))
480+
self.emit_line('%s = (%s)&((%s *)%s)->%s;' % (dest, op.type._ctype, op.src_type.name,
481+
src, op.field))
481482

482483
# Helpers
483484

mypyc/ir/ops.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@
2525
from mypyc.ir.rtypes import (
2626
RType, RInstance, RTuple, RVoid, is_bool_rprimitive, is_int_rprimitive,
2727
is_short_int_rprimitive, is_none_rprimitive, object_rprimitive, bool_rprimitive,
28-
short_int_rprimitive, int_rprimitive, void_rtype, is_c_py_ssize_t_rprimitive,
29-
c_pyssize_t_rprimitive
28+
short_int_rprimitive, int_rprimitive, void_rtype, pointer_rprimitive, is_pointer_rprimitive
3029
)
3130
from mypyc.common import short_name
3231

@@ -1360,7 +1359,7 @@ def __init__(self, type: RType, src: Value, line: int = -1) -> None:
13601359
self.type = type
13611360
# TODO: for now we enforce that the src memory address should be Py_ssize_t
13621361
# later we should also support same width unsigned int
1363-
assert is_c_py_ssize_t_rprimitive(src.type)
1362+
assert is_pointer_rprimitive(src.type)
13641363
self.src = src
13651364

13661365
def sources(self) -> List[Value]:
@@ -1379,7 +1378,7 @@ class GetElementPtr(RegisterOp):
13791378

13801379
def __init__(self, src: Value, src_type: RType, field: str, line: int = -1) -> None:
13811380
super().__init__(line)
1382-
self.type = c_pyssize_t_rprimitive
1381+
self.type = pointer_rprimitive
13831382
self.src = src
13841383
self.src_type = src_type
13851384
self.field = field
@@ -1388,7 +1387,7 @@ def sources(self) -> List[Value]:
13881387
return [self.src]
13891388

13901389
def to_str(self, env: Environment) -> str:
1391-
return env.format("%r = get_element_ptr %r %r :: %r", self, self.src,
1390+
return env.format("%r = get_element_ptr %r %s :: %r", self, self.src,
13921391
self.field, self.src_type)
13931392

13941393
def accept(self, visitor: 'OpVisitor[T]') -> T:

mypyc/ir/rtypes.py

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,10 @@ def __init__(self,
182182
self.size = size
183183
# TODO: For low-level integers, they actually don't have undefined values
184184
# we need to figure out some way to represent here.
185-
if ctype in ('CPyTagged', 'int32_t', 'int64_t'):
185+
if ctype == 'CPyTagged':
186186
self.c_undefined = 'CPY_INT_TAG'
187+
elif ctype in ('int32_t', 'int64_t', 'CPyPtr'):
188+
self.c_undefined = '0'
187189
elif ctype == 'PyObject *':
188190
# Boxed types use the null pointer as the error value.
189191
self.c_undefined = 'NULL'
@@ -254,6 +256,10 @@ def __repr__(self) -> str:
254256
else:
255257
c_pyssize_t_rprimitive = int64_rprimitive
256258

259+
# low level pointer, represented as integer in C backends
260+
pointer_rprimitive = RPrimitive('ptr', is_unboxed=True, is_refcounted=False,
261+
ctype='CPyPtr') # type: Final
262+
257263
# Floats are represent as 'float' PyObject * values. (In the future
258264
# we'll likely switch to a more efficient, unboxed representation.)
259265
float_rprimitive = RPrimitive('builtins.float', is_unboxed=False,
@@ -311,6 +317,10 @@ def is_c_py_ssize_t_rprimitive(rtype: RType) -> bool:
311317
return rtype is c_pyssize_t_rprimitive
312318

313319

320+
def is_pointer_rprimitive(rtype: RType) -> bool:
321+
return rtype is pointer_rprimitive
322+
323+
314324
def is_float_rprimitive(rtype: RType) -> bool:
315325
return isinstance(rtype, RPrimitive) and rtype.name == 'builtins.float'
316326

@@ -514,12 +524,8 @@ def compute_aligned_offsets_and_size(types: List[RType]) -> Tuple[List[int], int
514524
return offsets, final_size
515525

516526

517-
class StructInfo:
518-
"""Struct-like type Infomation
519-
520-
StructInfo should work with registry to ensure constraints like the unique naming
521-
constraint for struct type
522-
"""
527+
class RStruct(RType):
528+
"""Represent CPython structs"""
523529
def __init__(self,
524530
name: str,
525531
names: List[str],
@@ -532,31 +538,7 @@ def __init__(self,
532538
for i in range(len(self.types) - len(self.names)):
533539
self.names.append('_item' + str(i))
534540
self.offsets, self.size = compute_aligned_offsets_and_size(types)
535-
536-
537-
class RStruct(RType):
538-
"""Represent CPython structs"""
539-
def __init__(self,
540-
info: StructInfo) -> None:
541-
self.info = info
542-
self.name = self.info.name
543-
self._ctype = self.info.name
544-
545-
@property
546-
def names(self) -> List[str]:
547-
return self.info.names
548-
549-
@property
550-
def types(self) -> List[RType]:
551-
return self.info.types
552-
553-
@property
554-
def offsets(self) -> List[int]:
555-
return self.info.offsets
556-
557-
@property
558-
def size(self) -> int:
559-
return self.info.size
541+
self._ctype = name
560542

561543
def accept(self, visitor: 'RTypeVisitor[T]') -> T:
562544
return visitor.visit_rstruct(self)
@@ -571,10 +553,11 @@ def __repr__(self) -> str:
571553
in zip(self.names, self.types)))
572554

573555
def __eq__(self, other: object) -> bool:
574-
return isinstance(other, RStruct) and self.info == other.info
556+
return (isinstance(other, RStruct) and self.name == other.name
557+
and self.names == other.names and self.types == other.types)
575558

576559
def __hash__(self) -> int:
577-
return hash(self.info)
560+
return hash((self.name, tuple(self.names), tuple(self.types)))
578561

579562
def serialize(self) -> JsonDict:
580563
assert False
@@ -687,3 +670,14 @@ def optional_value_type(rtype: RType) -> Optional[RType]:
687670
def is_optional_type(rtype: RType) -> bool:
688671
"""Is rtype an optional type with exactly two union items?"""
689672
return optional_value_type(rtype) is not None
673+
674+
675+
PyObject = RStruct(
676+
name='PyObject',
677+
names=['ob_refcnt', 'ob_type'],
678+
types=[c_pyssize_t_rprimitive, pointer_rprimitive])
679+
680+
PyVarObject = RStruct(
681+
name='PyVarObject',
682+
names=['ob_base', 'ob_size'],
683+
types=[PyObject, c_pyssize_t_rprimitive])

mypyc/irbuild/builder.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
from mypyc.ir.func_ir import FuncIR, INVALID_FUNC_DEF
4545
from mypyc.ir.class_ir import ClassIR, NonExtClassInfo
4646
from mypyc.primitives.registry import func_ops, CFunctionDescription, c_function_ops
47-
from mypyc.primitives.list_ops import list_len_op, to_list, list_pop_last
47+
from mypyc.primitives.list_ops import to_list, list_pop_last
4848
from mypyc.primitives.dict_ops import dict_get_item_op, dict_set_item_op
4949
from mypyc.primitives.generic_ops import py_setattr_op, iter_op, next_op
5050
from mypyc.primitives.misc_ops import true_op, false_op, import_op
@@ -238,6 +238,9 @@ def binary_int_op(self, type: RType, lhs: Value, rhs: Value, op: int, line: int)
238238
def compare_tagged(self, lhs: Value, rhs: Value, op: str, line: int) -> Value:
239239
return self.builder.compare_tagged(lhs, rhs, op, line)
240240

241+
def list_len(self, val: Value, line: int) -> Value:
242+
return self.builder.list_len(val, line)
243+
241244
@property
242245
def environment(self) -> Environment:
243246
return self.builder.environment
@@ -508,7 +511,7 @@ def process_iterator_tuple_assignment(self,
508511
if target.star_idx is not None:
509512
post_star_vals = target.items[split_idx + 1:]
510513
iter_list = self.call_c(to_list, [iterator], line)
511-
iter_list_len = self.primitive_op(list_len_op, [iter_list], line)
514+
iter_list_len = self.list_len(iter_list, line)
512515
post_star_len = self.add(LoadInt(len(post_star_vals)))
513516
condition = self.binary_op(post_star_len, iter_list_len, '<=', line)
514517

mypyc/irbuild/for_helpers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
from mypyc.primitives.exc_ops import no_err_occurred_op
3030
from mypyc.irbuild.builder import IRBuilder
3131

32-
3332
GenFunc = Callable[[], None]
3433

3534

@@ -333,6 +332,9 @@ def gen_cleanup(self) -> None:
333332

334333
def load_len(self, expr: Union[Value, AssignmentTarget]) -> Value:
335334
"""A helper to get collection length, used by several subclasses."""
335+
val = self.builder.read(expr, self.line)
336+
if is_list_rprimitive(val.type):
337+
return self.builder.builder.list_len(self.builder.read(expr, self.line), self.line)
336338
return self.builder.builder.builtin_call(
337339
[self.builder.read(expr, self.line)],
338340
'builtins.len',

mypyc/irbuild/ll_builder.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@
2121
Assign, Branch, Goto, Call, Box, Unbox, Cast, GetAttr,
2222
LoadStatic, MethodCall, PrimitiveOp, OpDescription, RegisterOp, CallC, Truncate,
2323
RaiseStandardError, Unreachable, LoadErrorValue, LoadGlobal,
24-
NAMESPACE_TYPE, NAMESPACE_MODULE, NAMESPACE_STATIC, BinaryIntOp
24+
NAMESPACE_TYPE, NAMESPACE_MODULE, NAMESPACE_STATIC, BinaryIntOp, GetElementPtr,
25+
LoadMem
2526
)
2627
from mypyc.ir.rtypes import (
2728
RType, RUnion, RInstance, optional_value_type, int_rprimitive, float_rprimitive,
2829
bool_rprimitive, list_rprimitive, str_rprimitive, is_none_rprimitive, object_rprimitive,
29-
c_pyssize_t_rprimitive, is_short_int_rprimitive, is_tagged
30+
c_pyssize_t_rprimitive, is_short_int_rprimitive, is_tagged, PyVarObject, short_int_rprimitive
3031
)
3132
from mypyc.ir.func_ir import FuncDecl, FuncSignature
3233
from mypyc.ir.class_ir import ClassIR, all_concrete_classes
@@ -40,7 +41,7 @@
4041
c_binary_ops, c_unary_ops
4142
)
4243
from mypyc.primitives.list_ops import (
43-
list_extend_op, list_len_op, new_list_op
44+
list_extend_op, new_list_op
4445
)
4546
from mypyc.primitives.tuple_ops import list_tuple_op, new_tuple_op
4647
from mypyc.primitives.dict_ops import (
@@ -703,7 +704,7 @@ def add_bool_branch(self, value: Value, true: BasicBlock, false: BasicBlock) ->
703704
zero = self.add(LoadInt(0))
704705
value = self.binary_op(value, zero, '!=', value.line)
705706
elif is_same_type(value.type, list_rprimitive):
706-
length = self.primitive_op(list_len_op, [value], value.line)
707+
length = self.list_len(value, value.line)
707708
zero = self.add(LoadInt(0))
708709
value = self.binary_op(length, zero, '!=', value.line)
709710
elif (isinstance(value.type, RInstance) and value.type.class_ir.is_ext_class
@@ -810,6 +811,12 @@ def matching_call_c(self,
810811
def binary_int_op(self, type: RType, lhs: Value, rhs: Value, op: int, line: int) -> Value:
811812
return self.add(BinaryIntOp(type, lhs, rhs, op, line))
812813

814+
def list_len(self, val: Value, line: int) -> Value:
815+
elem_address = self.add(GetElementPtr(val, PyVarObject, 'ob_size'))
816+
size_value = self.add(LoadMem(c_pyssize_t_rprimitive, elem_address))
817+
offset = self.add(LoadInt(1, -1, rtype=c_pyssize_t_rprimitive))
818+
return self.binary_int_op(short_int_rprimitive, size_value, offset,
819+
BinaryIntOp.LEFT_SHIFT, -1)
813820
# Internal helpers
814821

815822
def decompose_union_helper(self,

mypyc/irbuild/specialize.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
from mypy.types import AnyType, TypeOfAny
1919

2020
from mypyc.ir.ops import (
21-
Value, BasicBlock, LoadInt, RaiseStandardError, Unreachable, OpDescription
21+
Value, BasicBlock, LoadInt, RaiseStandardError, Unreachable, OpDescription,
2222
)
2323
from mypyc.ir.rtypes import (
2424
RType, RTuple, str_rprimitive, list_rprimitive, dict_rprimitive, set_rprimitive,
25-
bool_rprimitive, is_dict_rprimitive
25+
bool_rprimitive, is_dict_rprimitive, is_list_rprimitive,
2626
)
2727
from mypyc.primitives.dict_ops import dict_keys_op, dict_values_op, dict_items_op
2828
from mypyc.primitives.misc_ops import true_op, false_op
@@ -75,6 +75,9 @@ def translate_len(
7575
# though we still need to evaluate it.
7676
builder.accept(expr.args[0])
7777
return builder.add(LoadInt(len(expr_rtype.types)))
78+
elif is_list_rprimitive(expr_rtype):
79+
obj = builder.accept(expr.args[0])
80+
return builder.list_len(obj, -1)
7881
return None
7982

8083

mypyc/lib-rt/mypyc_util.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#define CPy_XDECREF(p) Py_XDECREF(p)
3333

3434
typedef size_t CPyTagged;
35+
typedef size_t CPyPtr;
3536

3637
#define CPY_INT_BITS (CHAR_BIT * sizeof(CPyTagged))
3738

mypyc/primitives/list_ops.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
c_int_rprimitive
99
)
1010
from mypyc.primitives.registry import (
11-
name_ref_op, func_op, custom_op, name_emit,
11+
name_ref_op, custom_op, name_emit,
1212
call_emit, c_function_op, c_binary_op, c_method_op
1313
)
1414

@@ -146,11 +146,3 @@ def emit_len(emitter: EmitterInterface, args: List[str], dest: str) -> None:
146146
emitter.emit_declaration('Py_ssize_t %s;' % temp)
147147
emitter.emit_line('%s = PyList_GET_SIZE(%s);' % (temp, args[0]))
148148
emitter.emit_line('%s = CPyTagged_ShortFromSsize_t(%s);' % (dest, temp))
149-
150-
151-
# len(list)
152-
list_len_op = func_op(name='builtins.len',
153-
arg_types=[list_rprimitive],
154-
result_type=short_int_rprimitive,
155-
error_kind=ERR_NEVER,
156-
emit=emit_len)

mypyc/primitives/struct_regsitry.py

Lines changed: 0 additions & 24 deletions
This file was deleted.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy