Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 916d57c

Browse files
authored
Add a notion of "short" integers (mypyc/mypyc#419)
"Short" integers are a runtime subtype of regular ints, and can be used without coercion wherever an int can be used. However, they cannot be big ints, and so do not need reference counting, and can enable some operations to use fewer branches.
1 parent 3bfb5ae commit 916d57c

24 files changed

Lines changed: 844 additions & 517 deletions

lib-rt/CPy.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,26 @@ static inline bool CPyTagged_IsLe(CPyTagged left, CPyTagged right) {
595595
}
596596
}
597597

598+
static PyObject *CPyList_GetItemShort(PyObject *list, CPyTagged index) {
599+
long long n = CPyTagged_ShortAsLongLong(index);
600+
Py_ssize_t size = PyList_GET_SIZE(list);
601+
if (n >= 0) {
602+
if (n >= size) {
603+
PyErr_SetString(PyExc_IndexError, "list index out of range");
604+
return NULL;
605+
}
606+
} else {
607+
n += size;
608+
if (n < 0) {
609+
PyErr_SetString(PyExc_IndexError, "list index out of range");
610+
return NULL;
611+
}
612+
}
613+
PyObject *result = PyList_GET_ITEM(list, n);
614+
Py_INCREF(result);
615+
return result;
616+
}
617+
598618
static PyObject *CPyList_GetItem(PyObject *list, CPyTagged index) {
599619
if (CPyTagged_CheckShort(index)) {
600620
long long n = CPyTagged_ShortAsLongLong(index);

mypyc/emit.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from mypyc.common import REG_PREFIX, STATIC_PREFIX, TYPE_PREFIX, NATIVE_PREFIX
77
from mypyc.ops import (
88
Any, AssignmentTarget, Environment, BasicBlock, Value, Register, RType, RTuple, RInstance,
9-
RUnion, RPrimitive, RUnion, is_int_rprimitive, is_float_rprimitive, is_bool_rprimitive,
9+
RUnion, RPrimitive, is_int_rprimitive, is_short_int_rprimitive,
10+
is_float_rprimitive, is_bool_rprimitive,
1011
short_name, is_list_rprimitive, is_dict_rprimitive, is_set_rprimitive, is_tuple_rprimitive,
1112
is_none_rprimitive, is_object_rprimitive, object_rprimitive, is_str_rprimitive, ClassIR,
1213
FuncIR, FuncDecl, int_rprimitive, is_optional_type, optional_value_type
@@ -490,7 +491,7 @@ def emit_unbox(self, src: str, dest: str, typ: RType, custom_failure: Optional[s
490491
else:
491492
failure = [raise_exc,
492493
'%s = %s;' % (dest, self.c_error_value(typ))]
493-
if is_int_rprimitive(typ):
494+
if is_int_rprimitive(typ) or is_short_int_rprimitive(typ):
494495
if declare_dest:
495496
self.emit_line('CPyTagged {};'.format(dest))
496497
self.emit_arg_check(src, dest, typ, '(PyLong_Check({}))'.format(src), optional)
@@ -565,7 +566,7 @@ def emit_box(self, src: str, dest: str, typ: RType, declare_dest: bool = False)
565566
declaration = 'PyObject *'
566567
else:
567568
declaration = ''
568-
if is_int_rprimitive(typ):
569+
if is_int_rprimitive(typ) or is_short_int_rprimitive(typ):
569570
# Steal the existing reference if it exists.
570571
self.emit_line('{}{} = CPyTagged_StealAsObject({});'.format(declaration, dest, src))
571572
elif is_bool_rprimitive(typ):

mypyc/genops.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,12 @@ def f(x: int) -> int:
6262
is_object_rprimitive, LiteralsMap, FuncSignature, VTableAttr, VTableMethod, VTableEntries,
6363
NAMESPACE_TYPE, RaiseStandardError, LoadErrorValue, NO_TRACEBACK_LINE_NO, FuncDecl,
6464
FUNC_NORMAL, FUNC_STATICMETHOD, FUNC_CLASSMETHOD,
65-
RUnion, is_optional_type, optional_value_type
65+
RUnion, is_optional_type, optional_value_type, is_short_int_rprimitive,
6666
)
6767
from mypyc.ops_primitive import binary_ops, unary_ops, func_ops, method_ops, name_ref_ops
68+
from mypyc.ops_int import unsafe_short_add
6869
from mypyc.ops_list import (
69-
list_append_op, list_extend_op, list_len_op, list_get_item_op, list_set_item_op, new_list_op,
70+
list_append_op, list_extend_op, list_len_op, new_list_op,
7071
)
7172
from mypyc.ops_tuple import list_tuple_op
7273
from mypyc.ops_dict import new_dict_op, dict_get_item_op, dict_set_item_op, dict_update_op
@@ -82,6 +83,7 @@ def f(x: int) -> int:
8283
error_catch_op, restore_exc_info_op, exc_matches_op, get_exc_value_op,
8384
get_exc_info_op, keep_propagating_op,
8485
)
86+
from mypyc.rt_subtype import is_runtime_subtype
8587
from mypyc.subtype import is_subtype
8688
from mypyc.sametype import is_same_type, is_same_method_signature
8789
from mypyc.crash import catch_errors
@@ -1649,9 +1651,15 @@ def for_loop_helper(self, index: Lvalue, expr: Expression,
16491651

16501652
self.goto_and_activate(increment_block)
16511653

1652-
# Increment index register.
1653-
self.assign(index_target, self.binary_op(self.read(index_target, line),
1654-
self.add(LoadInt(1)), '+', line), line)
1654+
# Increment index register. If the range is known to fit in short ints, use
1655+
# short ints.
1656+
if is_short_int_rprimitive(start_reg.type) and is_short_int_rprimitive(end_reg.type):
1657+
new_val = self.primitive_op(
1658+
unsafe_short_add, [self.read(index_target, line), self.add(LoadInt(1))], line)
1659+
else:
1660+
new_val = self.binary_op(
1661+
self.read(index_target, line), self.add(LoadInt(1)), '+', line)
1662+
self.assign(index_target, new_val, line)
16551663

16561664
# Go back to loop condition check.
16571665
self.goto(condition_block)
@@ -1682,18 +1690,20 @@ def for_loop_helper(self, index: Lvalue, expr: Expression,
16821690
assert isinstance(target_list_type, Instance)
16831691
target_type = self.type_to_rtype(target_list_type.args[0])
16841692

1685-
value_box = self.add(PrimitiveOp([self.read(expr_target, line),
1686-
self.read(index_target, line)],
1687-
list_get_item_op, line))
1693+
value_box = self.translate_special_method_call(
1694+
self.read(expr_target, line), '__getitem__',
1695+
[self.read(index_target, line)], None, line)
1696+
assert value_box
16881697

16891698
self.assign(self.get_assignment_target(index),
16901699
self.unbox_or_cast(value_box, target_type, line), line)
16911700

16921701
body_insts()
16931702

16941703
self.goto_and_activate(increment_block)
1695-
self.assign(index_target, self.binary_op(self.read(index_target, line),
1696-
self.add(LoadInt(1)), '+', line), line)
1704+
self.assign(index_target, self.primitive_op(
1705+
unsafe_short_add,
1706+
[self.read(index_target, line), self.add(LoadInt(1))], line), line)
16971707
self.goto(condition_block)
16981708

16991709
self.pop_loop_stack()
@@ -1780,7 +1790,7 @@ def matching_primitive_op(self,
17801790
matching = desc
17811791
if matching:
17821792
target = self.primitive_op(matching, args, line)
1783-
if result_type and not is_same_type(target.type, result_type):
1793+
if result_type and not is_runtime_subtype(target.type, result_type):
17841794
if is_none_rprimitive(result_type):
17851795
# Special case None return. The actual result may actually be a bool
17861796
# and so we can't just coerce it.
@@ -2509,7 +2519,7 @@ def go(i: int, prev: Value) -> Value:
25092519
return go(0, self.accept(e.operands[0]))
25102520

25112521
def add_bool_branch(self, value: Value, true: BasicBlock, false: BasicBlock) -> None:
2512-
if is_same_type(value.type, int_rprimitive):
2522+
if is_runtime_subtype(value.type, int_rprimitive):
25132523
zero = self.add(LoadInt(0))
25142524
value = self.binary_op(value, zero, '!=', value.line)
25152525
elif is_same_type(value.type, list_rprimitive):
@@ -3937,7 +3947,7 @@ def coerce(self, src: Value, target_type: RType, line: int, force: bool = False)
39373947
if src.type.is_unboxed and not target_type.is_unboxed:
39383948
return self.box(src)
39393949
if ((src.type.is_unboxed and target_type.is_unboxed)
3940-
and not is_same_type(src.type, target_type)):
3950+
and not is_runtime_subtype(src.type, target_type)):
39413951
# To go from one unboxed type to another, we go through a boxed
39423952
# in-between value, for simplicity.
39433953
tmp = self.box(src)

mypyc/ops.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ def __repr__(self) -> str:
111111

112112
int_rprimitive = RPrimitive('builtins.int', is_unboxed=True, is_refcounted=True, ctype='CPyTagged')
113113

114+
short_int_rprimitive = RPrimitive('short_int', is_unboxed=True, is_refcounted=False,
115+
ctype='CPyTagged')
116+
114117
float_rprimitive = RPrimitive('builtins.float', is_unboxed=False, is_refcounted=True)
115118

116119
bool_rprimitive = RPrimitive('builtins.bool', is_unboxed=True, is_refcounted=False, ctype='char')
@@ -131,7 +134,11 @@ def __repr__(self) -> str:
131134

132135

133136
def is_int_rprimitive(rtype: RType) -> bool:
134-
return isinstance(rtype, RPrimitive) and rtype.name == 'builtins.int'
137+
return rtype is int_rprimitive
138+
139+
140+
def is_short_int_rprimitive(rtype: RType) -> bool:
141+
return rtype is short_int_rprimitive
135142

136143

137144
def is_float_rprimitive(rtype: RType) -> bool:
@@ -936,7 +943,7 @@ class LoadInt(RegisterOp):
936943
def __init__(self, value: int, line: int = -1) -> None:
937944
super().__init__(line)
938945
self.value = value
939-
self.type = int_rprimitive
946+
self.type = short_int_rprimitive
940947

941948
def sources(self) -> List[Value]:
942949
return []
@@ -1110,7 +1117,12 @@ class TupleSet(RegisterOp):
11101117
def __init__(self, items: List[Value], line: int) -> None:
11111118
super().__init__(line)
11121119
self.items = items
1113-
self.tuple_type = RTuple([arg.type for arg in items])
1120+
# Don't keep track of the fact that an int is short after it
1121+
# is put into a tuple, since we don't properly implement
1122+
# runtime subtyping for tuples.
1123+
self.tuple_type = RTuple(
1124+
[arg.type if not is_short_int_rprimitive(arg.type) else int_rprimitive
1125+
for arg in items])
11141126
self.type = self.tuple_type
11151127

11161128
def sources(self) -> List[Value]:

mypyc/ops_int.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from typing import List
22

33
from mypyc.ops import (
4-
PrimitiveOp, int_rprimitive, bool_rprimitive, float_rprimitive, object_rprimitive,
4+
PrimitiveOp,
5+
int_rprimitive, bool_rprimitive, float_rprimitive, object_rprimitive, short_int_rprimitive,
56
RType, EmitterInterface, OpDescription,
67
ERR_NEVER, ERR_MAGIC,
78
)
8-
from mypyc.ops_primitive import name_ref_op, binary_op, unary_op, func_op, simple_emit
9+
from mypyc.ops_primitive import name_ref_op, binary_op, unary_op, func_op, custom_op, simple_emit
910

1011
# These int constructors produce object_rprimitives that then need to be unboxed
1112
# I guess unboxing ourselves would save a check and branch though?
@@ -38,6 +39,15 @@ def int_binary_op(op: str, c_func_name: str, result_type: RType = int_rprimitive
3839

3940
def int_compare_op(op: str, c_func_name: str) -> None:
4041
int_binary_op(op, c_func_name, bool_rprimitive)
42+
# Generate a straight compare if we know both sides are short
43+
binary_op(op=op,
44+
arg_types=[short_int_rprimitive, short_int_rprimitive],
45+
result_type=bool_rprimitive,
46+
error_kind=ERR_NEVER,
47+
format_str='{dest} = {args[0]} %s {args[1]} :: short_int' % op,
48+
emit=simple_emit(
49+
'{dest} = (CPySignedInt){args[0]} %s (CPySignedInt){args[1]};' % op),
50+
priority=2)
4151

4252

4353
int_binary_op('+', 'CPyTagged_Add')
@@ -62,6 +72,13 @@ def int_compare_op(op: str, c_func_name: str) -> None:
6272
int_compare_op('>', 'CPyTagged_IsGt')
6373
int_compare_op('>=', 'CPyTagged_IsGe')
6474

75+
unsafe_short_add = custom_op(
76+
arg_types=[int_rprimitive, int_rprimitive],
77+
result_type=short_int_rprimitive,
78+
error_kind=ERR_NEVER,
79+
format_str='{dest} = {args[0]} + {args[1]} :: short_int',
80+
emit=simple_emit('{dest} = {args[0]} + {args[1]};'))
81+
6582

6683
def int_unary_op(op: str, c_func_name: str) -> OpDescription:
6784
return unary_op(op=op,

mypyc/ops_list.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from typing import List
44

55
from mypyc.ops import (
6-
int_rprimitive, list_rprimitive, object_rprimitive, bool_rprimitive, ERR_MAGIC, ERR_NEVER,
6+
int_rprimitive, short_int_rprimitive, list_rprimitive, object_rprimitive, bool_rprimitive,
7+
ERR_MAGIC, ERR_NEVER,
78
ERR_FALSE, EmitterInterface, PrimitiveOp, Value
89
)
910
from mypyc.ops_primitive import (
@@ -44,6 +45,16 @@ def emit_new(emitter: EmitterInterface, args: List[str], dest: str) -> None:
4445
emit=simple_emit('{dest} = CPyList_GetItem({args[0]}, {args[1]});'))
4546

4647

48+
# Version with no int bounds check for when it is known to be short
49+
method_op(
50+
name='__getitem__',
51+
arg_types=[list_rprimitive, short_int_rprimitive],
52+
result_type=object_rprimitive,
53+
error_kind=ERR_MAGIC,
54+
emit=simple_emit('{dest} = CPyList_GetItemShort({args[0]}, {args[1]});'),
55+
priority=2)
56+
57+
4758
list_set_item_op = method_op(
4859
name='__setitem__',
4960
arg_types=[list_rprimitive, int_rprimitive, object_rprimitive],
@@ -110,6 +121,6 @@ def emit_len(emitter: EmitterInterface, args: List[str], dest: str) -> None:
110121

111122
list_len_op = func_op(name='builtins.len',
112123
arg_types=[list_rprimitive],
113-
result_type=int_rprimitive,
124+
result_type=short_int_rprimitive,
114125
error_kind=ERR_NEVER,
115126
emit=emit_len)

mypyc/rt_subtype.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""'Runtime subtype' check for RTypes.
2+
3+
A type S is a runtime subtype of T if a value of type S can be used at runtime
4+
when a value of type T is expected without requiring any runtime conversions.
5+
6+
For boxed types, runtime subtyping is the same as regular subtyping.
7+
Unboxed subtypes, on the other hand, are not runtime subtypes of object
8+
(since they require boxing to be used as an object), but short ints
9+
are runtime subtypes of int.
10+
11+
Subtyping is used to determine whether an object can be in a
12+
particular place and runtime subtyping is used to determine whether a
13+
coercion is necessary first.
14+
"""
15+
16+
from mypyc.ops import (
17+
RType, RUnion, RInstance, RPrimitive, RTuple, RVoid, RTypeVisitor,
18+
is_bool_rprimitive, is_int_rprimitive, is_tuple_rprimitive, none_rprimitive,
19+
is_short_int_rprimitive,
20+
is_object_rprimitive
21+
)
22+
from mypyc.sametype import is_same_type
23+
from mypyc.subtype import is_subtype
24+
25+
26+
def is_runtime_subtype(left: RType, right: RType) -> bool:
27+
return left.accept(RTSubtypeVisitor(right))
28+
29+
30+
class RTSubtypeVisitor(RTypeVisitor[bool]):
31+
"""Is left a runtime subtype of right?
32+
33+
A few special cases such as right being 'object' are handled in
34+
is_runtime_subtype and don't need to be covered here.
35+
"""
36+
37+
def __init__(self, right: RType) -> None:
38+
self.right = right
39+
40+
def visit_rinstance(self, left: RInstance) -> bool:
41+
return is_subtype(left, self.right)
42+
43+
def visit_runion(self, left: RUnion) -> bool:
44+
return is_subtype(left, self.right)
45+
46+
def visit_rprimitive(self, left: RPrimitive) -> bool:
47+
if is_short_int_rprimitive(left) and is_int_rprimitive(self.right):
48+
return True
49+
return left is self.right
50+
51+
def visit_rtuple(self, left: RTuple) -> bool:
52+
# We might want to implement runtime subtyping for tuples. The
53+
# obstacle is that we generate different (but equivalent)
54+
# tuple structs.
55+
return is_same_type(left, self.right)
56+
57+
def visit_rvoid(self, left: RVoid) -> bool:
58+
return isinstance(self.right, RVoid)

mypyc/sametype.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def visit_runion(self, left: RUnion) -> bool:
4545
return False
4646

4747
def visit_rprimitive(self, left: RPrimitive) -> bool:
48-
return isinstance(self.right, RPrimitive) and left.name == self.right.name
48+
return left is self.right
4949

5050
def visit_rtuple(self, left: RTuple) -> bool:
5151
return (isinstance(self.right, RTuple)

mypyc/subtype.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from mypyc.ops import (
44
RType, RInstance, RPrimitive, RTuple, RVoid, RTypeVisitor, RUnion,
55
is_bool_rprimitive, is_int_rprimitive, is_tuple_rprimitive, none_rprimitive,
6+
is_short_int_rprimitive,
67
is_object_rprimitive
78
)
89

@@ -43,7 +44,9 @@ def visit_runion(self, left: RUnion) -> bool:
4344
def visit_rprimitive(self, left: RPrimitive) -> bool:
4445
if is_bool_rprimitive(left) and is_int_rprimitive(self.right):
4546
return True
46-
return isinstance(self.right, RPrimitive) and left.name == self.right.name
47+
if is_short_int_rprimitive(left) and is_int_rprimitive(self.right):
48+
return True
49+
return left is self.right
4750

4851
def visit_rtuple(self, left: RTuple) -> bool:
4952
if is_tuple_rprimitive(self.right):

0 commit comments

Comments
 (0)