Skip to content

Commit 98c2c12

Browse files
authored
[mypyc] Foundational support for tuple literals (+ None and bool), try 2 (#10148)
This adds back #10041 that was reverted because of failing Windows builds. The Windows issue was fixed in #10147, so this is just a rebased version of the original PR.
1 parent 5eecc40 commit 98c2c12

File tree

7 files changed

+148
-25
lines changed

7 files changed

+148
-25
lines changed

mypyc/codegen/emitmodule.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,9 @@ def generate_literal_tables(self) -> None:
643643
# Descriptions of complex literals
644644
init_complex = c_array_initializer(literals.encoded_complex_values())
645645
self.declare_global('const double []', 'CPyLit_Complex', initializer=init_complex)
646+
# Descriptions of tuple literals
647+
init_tuple = c_array_initializer(literals.encoded_tuple_values())
648+
self.declare_global('const int []', 'CPyLit_Tuple', initializer=init_tuple)
646649

647650
def generate_export_table(self, decl_emitter: Emitter, code_emitter: Emitter) -> None:
648651
"""Generate the declaration and definition of the group's export struct.
@@ -816,7 +819,7 @@ def generate_globals_init(self, emitter: Emitter) -> None:
816819
for symbol, fixup in self.simple_inits:
817820
emitter.emit_line('{} = {};'.format(symbol, fixup))
818821

819-
values = 'CPyLit_Str, CPyLit_Bytes, CPyLit_Int, CPyLit_Float, CPyLit_Complex'
822+
values = 'CPyLit_Str, CPyLit_Bytes, CPyLit_Int, CPyLit_Float, CPyLit_Complex, CPyLit_Tuple'
820823
emitter.emit_lines('if (CPyStatics_Initialize(CPyStatics, {}) < 0) {{'.format(values),
821824
'return -1;',
822825
'}')

mypyc/codegen/literals.py

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1-
from typing import Dict, List, Union
1+
from typing import Dict, List, Union, Tuple, Any, cast
2+
3+
from typing_extensions import Final
4+
5+
6+
# Supported Python literal types. All tuple items must have supported
7+
# literal types as well, but we can't represent the type precisely.
8+
LiteralValue = Union[str, bytes, int, bool, float, complex, Tuple[object, ...], None]
9+
10+
11+
# Some literals are singletons and handled specially (None, False and True)
12+
NUM_SINGLETONS = 3 # type: Final
213

314

415
class Literals:
@@ -11,9 +22,13 @@ def __init__(self) -> None:
1122
self.int_literals = {} # type: Dict[int, int]
1223
self.float_literals = {} # type: Dict[float, int]
1324
self.complex_literals = {} # type: Dict[complex, int]
25+
self.tuple_literals = {} # type: Dict[Tuple[object, ...], int]
1426

15-
def record_literal(self, value: Union[str, bytes, int, float, complex]) -> None:
27+
def record_literal(self, value: LiteralValue) -> None:
1628
"""Ensure that the literal value is available in generated code."""
29+
if value is None or value is True or value is False:
30+
# These are special cased and always present
31+
return
1732
if isinstance(value, str):
1833
str_literals = self.str_literals
1934
if value not in str_literals:
@@ -34,15 +49,29 @@ def record_literal(self, value: Union[str, bytes, int, float, complex]) -> None:
3449
complex_literals = self.complex_literals
3550
if value not in complex_literals:
3651
complex_literals[value] = len(complex_literals)
52+
elif isinstance(value, tuple):
53+
tuple_literals = self.tuple_literals
54+
if value not in tuple_literals:
55+
for item in value:
56+
self.record_literal(cast(Any, item))
57+
tuple_literals[value] = len(tuple_literals)
3758
else:
3859
assert False, 'invalid literal: %r' % value
3960

40-
def literal_index(self, value: Union[str, bytes, int, float, complex]) -> int:
61+
def literal_index(self, value: LiteralValue) -> int:
4162
"""Return the index to the literals array for given value."""
42-
# The array contains first all str values, followed by bytes values, etc.
63+
# The array contains first None and booleans, followed by all str values,
64+
# followed by bytes values, etc.
65+
if value is None:
66+
return 0
67+
elif value is False:
68+
return 1
69+
elif value is True:
70+
return 2
71+
n = NUM_SINGLETONS
4372
if isinstance(value, str):
44-
return self.str_literals[value]
45-
n = len(self.str_literals)
73+
return n + self.str_literals[value]
74+
n += len(self.str_literals)
4675
if isinstance(value, bytes):
4776
return n + self.bytes_literals[value]
4877
n += len(self.bytes_literals)
@@ -54,11 +83,16 @@ def literal_index(self, value: Union[str, bytes, int, float, complex]) -> int:
5483
n += len(self.float_literals)
5584
if isinstance(value, complex):
5685
return n + self.complex_literals[value]
86+
n += len(self.complex_literals)
87+
if isinstance(value, tuple):
88+
return n + self.tuple_literals[value]
5789
assert False, 'invalid literal: %r' % value
5890

5991
def num_literals(self) -> int:
60-
return (len(self.str_literals) + len(self.bytes_literals) + len(self.int_literals) +
61-
len(self.float_literals) + len(self.complex_literals))
92+
# The first three are for None, True and False
93+
return (NUM_SINGLETONS + len(self.str_literals) + len(self.bytes_literals) +
94+
len(self.int_literals) + len(self.float_literals) + len(self.complex_literals) +
95+
len(self.tuple_literals))
6296

6397
# The following methods return the C encodings of literal values
6498
# of different types
@@ -78,6 +112,34 @@ def encoded_float_values(self) -> List[str]:
78112
def encoded_complex_values(self) -> List[str]:
79113
return _encode_complex_values(self.complex_literals)
80114

115+
def encoded_tuple_values(self) -> List[str]:
116+
"""Encode tuple values into a C array.
117+
118+
The format of the result is like this:
119+
120+
<number of tuples>
121+
<length of the first tuple>
122+
<literal index of first item>
123+
...
124+
<literal index of last item>
125+
<length of the second tuple>
126+
...
127+
"""
128+
values = self.tuple_literals
129+
value_by_index = {}
130+
for value, index in values.items():
131+
value_by_index[index] = value
132+
result = []
133+
num = len(values)
134+
result.append(str(num))
135+
for i in range(num):
136+
value = value_by_index[i]
137+
result.append(str(len(value)))
138+
for item in value:
139+
index = self.literal_index(cast(Any, item))
140+
result.append(str(index))
141+
return result
142+
81143

82144
def _encode_str_values(values: Dict[str, int]) -> List[bytes]:
83145
value_by_index = {}

mypyc/ir/ops.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -500,20 +500,24 @@ class LoadLiteral(RegisterOp):
500500
This is used to load a static PyObject * value corresponding to
501501
a literal of one of the supported types.
502502
503-
NOTE: For int literals, both int_rprimitive (CPyTagged) and
504-
object_primitive (PyObject *) are supported as types. However,
505-
when using int_rprimitive, the value must *not* be small enough
506-
to fit in an unboxed integer.
503+
Tuple literals must contain only valid literal values as items.
507504
508505
NOTE: You can use this to load boxed (Python) int objects. Use
509506
Integer to load unboxed, tagged integers or fixed-width,
510507
low-level integers.
508+
509+
For int literals, both int_rprimitive (CPyTagged) and
510+
object_primitive (PyObject *) are supported as rtype. However,
511+
when using int_rprimitive, the value must *not* be small enough
512+
to fit in an unboxed integer.
511513
"""
512514

513515
error_kind = ERR_NEVER
514516
is_borrowed = True
515517

516-
def __init__(self, value: Union[str, bytes, int, float, complex], rtype: RType) -> None:
518+
def __init__(self,
519+
value: Union[None, str, bytes, bool, int, float, complex, Tuple[object, ...]],
520+
rtype: RType) -> None:
517521
self.value = value
518522
self.type = rtype
519523

mypyc/irbuild/expression.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
1717
from mypy.types import TupleType, get_proper_type, Instance
1818

1919
from mypyc.common import MAX_SHORT_INT
20-
from mypyc.ir.ops import (
21-
Value, Register, TupleGet, TupleSet, BasicBlock, Assign, LoadAddress
22-
)
20+
from mypyc.ir.ops import Value, Register, TupleGet, TupleSet, BasicBlock, Assign, LoadAddress
2321
from mypyc.ir.rtypes import (
2422
RTuple, object_rprimitive, is_none_rprimitive, int_rprimitive, is_int_rprimitive
2523
)

mypyc/lib-rt/CPy.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,8 @@ int CPyStatics_Initialize(PyObject **statics,
531531
const char * const *bytestrings,
532532
const char * const *ints,
533533
const double *floats,
534-
const double *complex_numbers);
534+
const double *complex_numbers,
535+
const int *tuples);
535536

536537

537538
#ifdef __cplusplus

mypyc/lib-rt/misc_ops.c

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,16 @@ int CPyStatics_Initialize(PyObject **statics,
528528
const char * const *bytestrings,
529529
const char * const *ints,
530530
const double *floats,
531-
const double *complex_numbers) {
531+
const double *complex_numbers,
532+
const int *tuples) {
533+
PyObject **result = statics;
534+
// Start with some hard-coded values
535+
*result++ = Py_None;
536+
Py_INCREF(Py_None);
537+
*result++ = Py_False;
538+
Py_INCREF(Py_False);
539+
*result++ = Py_True;
540+
Py_INCREF(Py_True);
532541
if (strings) {
533542
for (; **strings != '\0'; strings++) {
534543
size_t num;
@@ -542,7 +551,7 @@ int CPyStatics_Initialize(PyObject **statics,
542551
return -1;
543552
}
544553
PyUnicode_InternInPlace(&obj);
545-
*statics++ = obj;
554+
*result++ = obj;
546555
data += len;
547556
}
548557
}
@@ -559,7 +568,7 @@ int CPyStatics_Initialize(PyObject **statics,
559568
if (obj == NULL) {
560569
return -1;
561570
}
562-
*statics++ = obj;
571+
*result++ = obj;
563572
data += len;
564573
}
565574
}
@@ -577,7 +586,7 @@ int CPyStatics_Initialize(PyObject **statics,
577586
}
578587
data = end;
579588
data++;
580-
*statics++ = obj;
589+
*result++ = obj;
581590
}
582591
}
583592
}
@@ -588,7 +597,7 @@ int CPyStatics_Initialize(PyObject **statics,
588597
if (obj == NULL) {
589598
return -1;
590599
}
591-
*statics++ = obj;
600+
*result++ = obj;
592601
}
593602
}
594603
if (complex_numbers) {
@@ -600,7 +609,24 @@ int CPyStatics_Initialize(PyObject **statics,
600609
if (obj == NULL) {
601610
return -1;
602611
}
603-
*statics++ = obj;
612+
*result++ = obj;
613+
}
614+
}
615+
if (tuples) {
616+
int num = *tuples++;
617+
while (num-- > 0) {
618+
int num_items = *tuples++;
619+
PyObject *obj = PyTuple_New(num_items);
620+
if (obj == NULL) {
621+
return -1;
622+
}
623+
int i;
624+
for (i = 0; i < num_items; i++) {
625+
PyObject *item = statics[*tuples++];
626+
Py_INCREF(item);
627+
PyTuple_SET_ITEM(obj, i, item);
628+
}
629+
*result++ = obj;
604630
}
605631
}
606632
return 0;

mypyc/test/test_literals.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import unittest
44

55
from mypyc.codegen.literals import (
6-
format_str_literal, _encode_str_values, _encode_bytes_values, _encode_int_values
6+
Literals, format_str_literal, _encode_str_values, _encode_bytes_values, _encode_int_values
77
)
88

99

@@ -56,3 +56,32 @@ def test_encode_int_values(self) -> None:
5656
b'\x016' + b'0' * 100,
5757
b''
5858
]
59+
60+
def test_simple_literal_index(self) -> None:
61+
lit = Literals()
62+
lit.record_literal(1)
63+
lit.record_literal('y')
64+
lit.record_literal(True)
65+
lit.record_literal(None)
66+
lit.record_literal(False)
67+
assert lit.literal_index(None) == 0
68+
assert lit.literal_index(False) == 1
69+
assert lit.literal_index(True) == 2
70+
assert lit.literal_index('y') == 3
71+
assert lit.literal_index(1) == 4
72+
73+
def test_tuple_literal(self) -> None:
74+
lit = Literals()
75+
lit.record_literal((1, 'y', None, (b'a', 'b')))
76+
lit.record_literal((b'a', 'b'))
77+
lit.record_literal(())
78+
assert lit.literal_index((b'a', 'b')) == 7
79+
assert lit.literal_index((1, 'y', None, (b'a', 'b'))) == 8
80+
assert lit.literal_index(()) == 9
81+
print(lit.encoded_tuple_values())
82+
assert lit.encoded_tuple_values() == [
83+
'3', # Number of tuples
84+
'2', '5', '4', # First tuple (length=2)
85+
'4', '6', '3', '0', '7', # Second tuple (length=4)
86+
'0', # Third tuple (length=0)
87+
]

0 commit comments

Comments
 (0)