Skip to content

Commit c239369

Browse files
authored
[mypyc] Support the i16 native integer type (#15464)
The `i16` type behaves the same as `i64` and `i32`, but is obviously smaller. The PR is big, but it's mostly because of test cases, which are adapted from `i32` test cases. Also fix error handling in unboxing of native int and float types (i.e. all types with overlapping error values).
1 parent c099b20 commit c239369

File tree

22 files changed

+1160
-41
lines changed

22 files changed

+1160
-41
lines changed

mypy/types.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,11 @@
150150
)
151151

152152
# Mypyc fixed-width native int types (compatible with builtins.int)
153-
MYPYC_NATIVE_INT_NAMES: Final = ("mypy_extensions.i64", "mypy_extensions.i32")
153+
MYPYC_NATIVE_INT_NAMES: Final = (
154+
"mypy_extensions.i64",
155+
"mypy_extensions.i32",
156+
"mypy_extensions.i16",
157+
)
154158

155159
DATACLASS_TRANSFORM_NAMES: Final = (
156160
"typing.dataclass_transform",

mypyc/codegen/emit.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
is_dict_rprimitive,
3636
is_fixed_width_rtype,
3737
is_float_rprimitive,
38+
is_int16_rprimitive,
3839
is_int32_rprimitive,
3940
is_int64_rprimitive,
4041
is_int_rprimitive,
@@ -900,28 +901,35 @@ def emit_unbox(
900901
self.emit_line(f" {dest} = 1;")
901902
elif is_int64_rprimitive(typ):
902903
# Whether we are borrowing or not makes no difference.
904+
assert not optional # Not supported for overlapping error values
903905
if declare_dest:
904906
self.emit_line(f"int64_t {dest};")
905907
self.emit_line(f"{dest} = CPyLong_AsInt64({src});")
906-
# TODO: Handle 'optional'
907-
# TODO: Handle 'failure'
908+
if not isinstance(error, AssignHandler):
909+
self.emit_unbox_failure_with_overlapping_error_value(dest, typ, failure)
908910
elif is_int32_rprimitive(typ):
909911
# Whether we are borrowing or not makes no difference.
912+
assert not optional # Not supported for overlapping error values
910913
if declare_dest:
911914
self.emit_line(f"int32_t {dest};")
912915
self.emit_line(f"{dest} = CPyLong_AsInt32({src});")
913-
# TODO: Handle 'optional'
914-
# TODO: Handle 'failure'
916+
if not isinstance(error, AssignHandler):
917+
self.emit_unbox_failure_with_overlapping_error_value(dest, typ, failure)
918+
elif is_int16_rprimitive(typ):
919+
# Whether we are borrowing or not makes no difference.
920+
assert not optional # Not supported for overlapping error values
921+
if declare_dest:
922+
self.emit_line(f"int16_t {dest};")
923+
self.emit_line(f"{dest} = CPyLong_AsInt16({src});")
924+
if not isinstance(error, AssignHandler):
925+
self.emit_unbox_failure_with_overlapping_error_value(dest, typ, failure)
915926
elif is_float_rprimitive(typ):
927+
assert not optional # Not supported for overlapping error values
916928
if declare_dest:
917929
self.emit_line("double {};".format(dest))
918930
# TODO: Don't use __float__ and __index__
919931
self.emit_line(f"{dest} = PyFloat_AsDouble({src});")
920-
self.emit_lines(
921-
f"if ({dest} == -1.0 && PyErr_Occurred()) {{", f"{dest} = -113.0;", "}"
922-
)
923-
# TODO: Handle 'optional'
924-
# TODO: Handle 'failure'
932+
self.emit_lines(f"if ({dest} == -1.0 && PyErr_Occurred()) {{", failure, "}")
925933
elif isinstance(typ, RTuple):
926934
self.declare_tuple_struct(typ)
927935
if declare_dest:
@@ -1006,7 +1014,7 @@ def emit_box(
10061014
self.emit_lines(f"{declaration}{dest} = Py_None;")
10071015
if not can_borrow:
10081016
self.emit_inc_ref(dest, object_rprimitive)
1009-
elif is_int32_rprimitive(typ):
1017+
elif is_int32_rprimitive(typ) or is_int16_rprimitive(typ):
10101018
self.emit_line(f"{declaration}{dest} = PyLong_FromLong({src});")
10111019
elif is_int64_rprimitive(typ):
10121020
self.emit_line(f"{declaration}{dest} = PyLong_FromLongLong({src});")
@@ -1137,6 +1145,13 @@ def _emit_traceback(
11371145
if DEBUG_ERRORS:
11381146
self.emit_line('assert(PyErr_Occurred() != NULL && "failure w/o err!");')
11391147

1148+
def emit_unbox_failure_with_overlapping_error_value(
1149+
self, dest: str, typ: RType, failure: str
1150+
) -> None:
1151+
self.emit_line(f"if ({dest} == {self.c_error_value(typ)} && PyErr_Occurred()) {{")
1152+
self.emit_line(failure)
1153+
self.emit_line("}")
1154+
11401155

11411156
def c_array_initializer(components: list[str], *, indented: bool = False) -> str:
11421157
"""Construct an initializer for a C array variable.

mypyc/doc/float_operations.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Construction
1414
* ``float(x: int)``
1515
* ``float(x: i64)``
1616
* ``float(x: i32)``
17+
* ``float(x: i16)``
1718
* ``float(x: str)``
1819
* ``float(x: float)`` (no-op)
1920

@@ -28,8 +29,9 @@ Functions
2829
---------
2930

3031
* ``int(f)``
31-
* ``i32(f)`` (convert to ``i32``)
32-
* ``i64(f)`` (convert to ``i64``)
32+
* ``i64(f)`` (convert to 64-bit signed integer)
33+
* ``i32(f)`` (convert to 32-bit signed integer)
34+
* ``i16(f)`` (convert to 16-bit signed integer)
3335
* ``abs(f)``
3436
* ``math.sin(f)``
3537
* ``math.cos(f)``

mypyc/doc/int_operations.rst

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ Mypyc supports these integer types:
88
* ``int`` (arbitrary-precision integer)
99
* ``i64`` (64-bit signed integer)
1010
* ``i32`` (32-bit signed integer)
11+
* ``i16`` (16-bit signed integer)
1112

12-
``i64`` and ``i32`` are *native integer types* and must be imported
13+
``i64``, ``i32`` and ``i16`` are *native integer types* and must be imported
1314
from the ``mypy_extensions`` module. ``int`` corresponds to the Python
1415
``int`` type, but uses a more efficient runtime representation (tagged
15-
pointer). Native integer types are value types. All integer types have
16-
optimized primitive operations, but the native integer types are more
17-
efficient than ``int``, since they don't require range or bounds
18-
checks.
16+
pointer). Native integer types are value types.
17+
18+
All integer types have optimized primitive operations, but the native
19+
integer types are more efficient than ``int``, since they don't
20+
require range or bounds checks.
1921

2022
Operations on integers that are listed here have fast, optimized
2123
implementations. Other integer operations use generic implementations
@@ -31,6 +33,7 @@ Construction
3133
* ``int(x: float)``
3234
* ``int(x: i64)``
3335
* ``int(x: i32)``
36+
* ``int(x: i16)``
3437
* ``int(x: str)``
3538
* ``int(x: str, base: int)``
3639
* ``int(x: int)`` (no-op)
@@ -40,6 +43,7 @@ Construction
4043
* ``i64(x: int)``
4144
* ``i64(x: float)``
4245
* ``i64(x: i32)``
46+
* ``i64(x: i16)``
4347
* ``i64(x: str)``
4448
* ``i64(x: str, base: int)``
4549
* ``i64(x: i64)`` (no-op)
@@ -49,10 +53,21 @@ Construction
4953
* ``i32(x: int)``
5054
* ``i32(x: float)``
5155
* ``i32(x: i64)`` (truncate)
56+
* ``i32(x: i16)``
5257
* ``i32(x: str)``
5358
* ``i32(x: str, base: int)``
5459
* ``i32(x: i32)`` (no-op)
5560

61+
``i16`` type:
62+
63+
* ``i16(x: int)``
64+
* ``i16(x: float)``
65+
* ``i16(x: i64)`` (truncate)
66+
* ``i16(x: i32)`` (truncate)
67+
* ``i16(x: str)``
68+
* ``i16(x: str, base: int)``
69+
* ``i16(x: i16)`` (no-op)
70+
5671
Conversions from ``int`` to a native integer type raise
5772
``OverflowError`` if the value is too large or small. Conversions from
5873
a wider native integer type to a narrower one truncate the value and never

mypyc/doc/using_type_annotations.rst

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ implementations:
3232
* ``int`` (:ref:`native operations <int-ops>`)
3333
* ``i64`` (:ref:`documentation <native-ints>`, :ref:`native operations <int-ops>`)
3434
* ``i32`` (:ref:`documentation <native-ints>`, :ref:`native operations <int-ops>`)
35+
* ``i16`` (:ref:`documentation <native-ints>`, :ref:`native operations <int-ops>`)
3536
* ``float`` (:ref:`native operations <float-ops>`)
3637
* ``bool`` (:ref:`native operations <bool-ops>`)
3738
* ``str`` (:ref:`native operations <str-ops>`)
@@ -342,13 +343,14 @@ Examples::
342343
Native integer types
343344
--------------------
344345

345-
You can use the native integer types ``i64`` (64-bit signed integer)
346-
and ``i32`` (32-bit signed integer) if you know that integer values
347-
will always fit within fixed bounds. These types are faster than the
348-
arbitrary-precision ``int`` type, since they don't require overflow
349-
checks on operations. ``i32`` may also use less memory than ``int``
350-
values. The types are imported from the ``mypy_extensions`` module
351-
(installed via ``pip install mypy_extensions``).
346+
You can use the native integer types ``i64`` (64-bit signed integer),
347+
``i32`` (32-bit signed integer), and ``i16`` (16-bit signed integer)
348+
if you know that integer values will always fit within fixed
349+
bounds. These types are faster than the arbitrary-precision ``int``
350+
type, since they don't require overflow checks on operations. ``i32``
351+
and ``i16`` may also use less memory than ``int`` values. The types
352+
are imported from the ``mypy_extensions`` module (installed via ``pip
353+
install mypy_extensions``).
352354

353355
Example::
354356

mypyc/ir/rtypes.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ def __init__(
206206
self.error_overlap = error_overlap
207207
if ctype == "CPyTagged":
208208
self.c_undefined = "CPY_INT_TAG"
209-
elif ctype in ("int32_t", "int64_t"):
209+
elif ctype in ("int16_t", "int32_t", "int64_t"):
210210
# This is basically an arbitrary value that is pretty
211211
# unlikely to overlap with a real value.
212212
self.c_undefined = "-113"
@@ -290,6 +290,16 @@ def __hash__(self) -> int:
290290

291291
# Low level integer types (correspond to C integer types)
292292

293+
int16_rprimitive: Final = RPrimitive(
294+
"int16",
295+
is_unboxed=True,
296+
is_refcounted=False,
297+
is_native_int=True,
298+
is_signed=True,
299+
ctype="int16_t",
300+
size=2,
301+
error_overlap=True,
302+
)
293303
int32_rprimitive: Final = RPrimitive(
294304
"int32",
295305
is_unboxed=True,
@@ -432,6 +442,10 @@ def is_short_int_rprimitive(rtype: RType) -> bool:
432442
return rtype is short_int_rprimitive
433443

434444

445+
def is_int16_rprimitive(rtype: RType) -> bool:
446+
return rtype is int16_rprimitive
447+
448+
435449
def is_int32_rprimitive(rtype: RType) -> bool:
436450
return rtype is int32_rprimitive or (
437451
rtype is c_pyssize_t_rprimitive and rtype._ctype == "int32_t"
@@ -445,7 +459,7 @@ def is_int64_rprimitive(rtype: RType) -> bool:
445459

446460

447461
def is_fixed_width_rtype(rtype: RType) -> bool:
448-
return is_int32_rprimitive(rtype) or is_int64_rprimitive(rtype)
462+
return is_int64_rprimitive(rtype) or is_int32_rprimitive(rtype) or is_int16_rprimitive(rtype)
449463

450464

451465
def is_uint32_rprimitive(rtype: RType) -> bool:
@@ -536,6 +550,8 @@ def visit_rprimitive(self, t: RPrimitive) -> str:
536550
return "8" # "8 byte integer"
537551
elif t._ctype == "int32_t":
538552
return "4" # "4 byte integer"
553+
elif t._ctype == "int16_t":
554+
return "2" # "2 byte integer"
539555
elif t._ctype == "double":
540556
return "F"
541557
assert not t.is_unboxed, f"{t} unexpected unboxed type"

mypyc/irbuild/ll_builder.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
is_dict_rprimitive,
105105
is_fixed_width_rtype,
106106
is_float_rprimitive,
107+
is_int16_rprimitive,
107108
is_int32_rprimitive,
108109
is_int64_rprimitive,
109110
is_int_rprimitive,
@@ -146,6 +147,9 @@
146147
py_vectorcall_op,
147148
)
148149
from mypyc.primitives.int_ops import (
150+
int16_divide_op,
151+
int16_mod_op,
152+
int16_overflow,
149153
int32_divide_op,
150154
int32_mod_op,
151155
int32_overflow,
@@ -456,6 +460,10 @@ def coerce_int_to_fixed_width(self, src: Value, target_type: RType, line: int) -
456460
# Slow path just always generates an OverflowError
457461
self.call_c(int32_overflow, [], line)
458462
self.add(Unreachable())
463+
elif is_int16_rprimitive(target_type):
464+
# Slow path just always generates an OverflowError
465+
self.call_c(int16_overflow, [], line)
466+
self.add(Unreachable())
459467
else:
460468
assert False, target_type
461469

@@ -469,7 +477,7 @@ def coerce_short_int_to_fixed_width(self, src: Value, target_type: RType, line:
469477
assert False, (src.type, target_type)
470478

471479
def coerce_fixed_width_to_int(self, src: Value, line: int) -> Value:
472-
if is_int32_rprimitive(src.type) and PLATFORM_SIZE == 8:
480+
if (is_int32_rprimitive(src.type) and PLATFORM_SIZE == 8) or is_int16_rprimitive(src.type):
473481
# Simple case -- just sign extend and shift.
474482
extended = self.add(Extend(src, c_pyssize_t_rprimitive, signed=True))
475483
return self.int_op(
@@ -2038,6 +2046,8 @@ def fixed_width_int_op(self, type: RType, lhs: Value, rhs: Value, op: int, line:
20382046
prim = int64_divide_op
20392047
elif is_int32_rprimitive(type):
20402048
prim = int32_divide_op
2049+
elif is_int16_rprimitive(type):
2050+
prim = int16_divide_op
20412051
else:
20422052
assert False, type
20432053
return self.call_c(prim, [lhs, rhs], line)
@@ -2050,6 +2060,8 @@ def fixed_width_int_op(self, type: RType, lhs: Value, rhs: Value, op: int, line:
20502060
prim = int64_mod_op
20512061
elif is_int32_rprimitive(type):
20522062
prim = int32_mod_op
2063+
elif is_int16_rprimitive(type):
2064+
prim = int16_mod_op
20532065
else:
20542066
assert False, type
20552067
return self.call_c(prim, [lhs, rhs], line)

mypyc/irbuild/mapper.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
bytes_rprimitive,
3333
dict_rprimitive,
3434
float_rprimitive,
35+
int16_rprimitive,
3536
int32_rprimitive,
3637
int64_rprimitive,
3738
int_rprimitive,
@@ -102,6 +103,8 @@ def type_to_rtype(self, typ: Type | None) -> RType:
102103
return int64_rprimitive
103104
elif typ.type.fullname == "mypy_extensions.i32":
104105
return int32_rprimitive
106+
elif typ.type.fullname == "mypy_extensions.i16":
107+
return int16_rprimitive
105108
else:
106109
return object_rprimitive
107110
elif isinstance(typ, TupleType):

0 commit comments

Comments
 (0)