int_ops.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. """Arbitrary-precision integer primitive ops.
  2. These mostly operate on (usually) unboxed integers that use a tagged pointer
  3. representation (CPyTagged) and correspond to the Python 'int' type.
  4. See also the documentation for mypyc.rtypes.int_rprimitive.
  5. Use mypyc.ir.ops.IntOp for operations on fixed-width/C integers.
  6. """
  7. from __future__ import annotations
  8. from typing import NamedTuple
  9. from mypyc.ir.ops import ERR_ALWAYS, ERR_MAGIC, ERR_MAGIC_OVERLAPPING, ERR_NEVER, ComparisonOp
  10. from mypyc.ir.rtypes import (
  11. RType,
  12. bit_rprimitive,
  13. bool_rprimitive,
  14. c_pyssize_t_rprimitive,
  15. float_rprimitive,
  16. int16_rprimitive,
  17. int32_rprimitive,
  18. int64_rprimitive,
  19. int_rprimitive,
  20. object_rprimitive,
  21. str_rprimitive,
  22. void_rtype,
  23. )
  24. from mypyc.primitives.registry import (
  25. CFunctionDescription,
  26. binary_op,
  27. custom_op,
  28. function_op,
  29. load_address_op,
  30. unary_op,
  31. )
  32. # Constructors for builtins.int and native int types have the same behavior. In
  33. # interpreted mode, native int types are just aliases to 'int'.
  34. for int_name in (
  35. "builtins.int",
  36. "mypy_extensions.i64",
  37. "mypy_extensions.i32",
  38. "mypy_extensions.i16",
  39. "mypy_extensions.u8",
  40. ):
  41. # These int constructors produce object_rprimitives that then need to be unboxed
  42. # I guess unboxing ourselves would save a check and branch though?
  43. # Get the type object for 'builtins.int' or a native int type.
  44. # For ordinary calls to int() we use a load_address to the type.
  45. # Native ints don't have a separate type object -- we just use 'builtins.int'.
  46. load_address_op(name=int_name, type=object_rprimitive, src="PyLong_Type")
  47. # int(float). We could do a bit better directly.
  48. function_op(
  49. name=int_name,
  50. arg_types=[float_rprimitive],
  51. return_type=int_rprimitive,
  52. c_function_name="CPyTagged_FromFloat",
  53. error_kind=ERR_MAGIC,
  54. )
  55. # int(string)
  56. function_op(
  57. name=int_name,
  58. arg_types=[str_rprimitive],
  59. return_type=object_rprimitive,
  60. c_function_name="CPyLong_FromStr",
  61. error_kind=ERR_MAGIC,
  62. )
  63. # int(string, base)
  64. function_op(
  65. name=int_name,
  66. arg_types=[str_rprimitive, int_rprimitive],
  67. return_type=object_rprimitive,
  68. c_function_name="CPyLong_FromStrWithBase",
  69. error_kind=ERR_MAGIC,
  70. )
  71. # str(int)
  72. int_to_str_op = function_op(
  73. name="builtins.str",
  74. arg_types=[int_rprimitive],
  75. return_type=str_rprimitive,
  76. c_function_name="CPyTagged_Str",
  77. error_kind=ERR_MAGIC,
  78. priority=2,
  79. )
  80. # We need a specialization for str on bools also since the int one is wrong...
  81. function_op(
  82. name="builtins.str",
  83. arg_types=[bool_rprimitive],
  84. return_type=str_rprimitive,
  85. c_function_name="CPyBool_Str",
  86. error_kind=ERR_MAGIC,
  87. priority=3,
  88. )
  89. def int_binary_op(
  90. name: str,
  91. c_function_name: str,
  92. return_type: RType = int_rprimitive,
  93. error_kind: int = ERR_NEVER,
  94. ) -> None:
  95. binary_op(
  96. name=name,
  97. arg_types=[int_rprimitive, int_rprimitive],
  98. return_type=return_type,
  99. c_function_name=c_function_name,
  100. error_kind=error_kind,
  101. )
  102. # Binary, unary and augmented assignment operations that operate on CPyTagged ints
  103. # are implemented as C functions.
  104. int_binary_op("+", "CPyTagged_Add")
  105. int_binary_op("-", "CPyTagged_Subtract")
  106. int_binary_op("*", "CPyTagged_Multiply")
  107. int_binary_op("&", "CPyTagged_And")
  108. int_binary_op("|", "CPyTagged_Or")
  109. int_binary_op("^", "CPyTagged_Xor")
  110. # Divide and remainder we honestly propagate errors from because they
  111. # can raise ZeroDivisionError
  112. int_binary_op("//", "CPyTagged_FloorDivide", error_kind=ERR_MAGIC)
  113. int_binary_op("%", "CPyTagged_Remainder", error_kind=ERR_MAGIC)
  114. # Negative shift counts raise an exception
  115. int_binary_op(">>", "CPyTagged_Rshift", error_kind=ERR_MAGIC)
  116. int_binary_op("<<", "CPyTagged_Lshift", error_kind=ERR_MAGIC)
  117. int_binary_op(
  118. "/", "CPyTagged_TrueDivide", return_type=float_rprimitive, error_kind=ERR_MAGIC_OVERLAPPING
  119. )
  120. # This should work because assignment operators are parsed differently
  121. # and the code in irbuild that handles it does the assignment
  122. # regardless of whether or not the operator works in place anyway.
  123. int_binary_op("+=", "CPyTagged_Add")
  124. int_binary_op("-=", "CPyTagged_Subtract")
  125. int_binary_op("*=", "CPyTagged_Multiply")
  126. int_binary_op("&=", "CPyTagged_And")
  127. int_binary_op("|=", "CPyTagged_Or")
  128. int_binary_op("^=", "CPyTagged_Xor")
  129. int_binary_op("//=", "CPyTagged_FloorDivide", error_kind=ERR_MAGIC)
  130. int_binary_op("%=", "CPyTagged_Remainder", error_kind=ERR_MAGIC)
  131. int_binary_op(">>=", "CPyTagged_Rshift", error_kind=ERR_MAGIC)
  132. int_binary_op("<<=", "CPyTagged_Lshift", error_kind=ERR_MAGIC)
  133. def int_unary_op(name: str, c_function_name: str) -> CFunctionDescription:
  134. return unary_op(
  135. name=name,
  136. arg_type=int_rprimitive,
  137. return_type=int_rprimitive,
  138. c_function_name=c_function_name,
  139. error_kind=ERR_NEVER,
  140. )
  141. int_neg_op = int_unary_op("-", "CPyTagged_Negate")
  142. int_invert_op = int_unary_op("~", "CPyTagged_Invert")
  143. # Primitives related to integer comparison operations:
  144. # Description for building int comparison ops
  145. #
  146. # Fields:
  147. # binary_op_variant: identify which IntOp to use when operands are short integers
  148. # c_func_description: the C function to call when operands are tagged integers
  149. # c_func_negated: whether to negate the C function call's result
  150. # c_func_swap_operands: whether to swap lhs and rhs when call the function
  151. class IntComparisonOpDescription(NamedTuple):
  152. binary_op_variant: int
  153. c_func_description: CFunctionDescription
  154. c_func_negated: bool
  155. c_func_swap_operands: bool
  156. # Equals operation on two boxed tagged integers
  157. int_equal_ = custom_op(
  158. arg_types=[int_rprimitive, int_rprimitive],
  159. return_type=bit_rprimitive,
  160. c_function_name="CPyTagged_IsEq_",
  161. error_kind=ERR_NEVER,
  162. )
  163. # Less than operation on two boxed tagged integers
  164. int_less_than_ = custom_op(
  165. arg_types=[int_rprimitive, int_rprimitive],
  166. return_type=bit_rprimitive,
  167. c_function_name="CPyTagged_IsLt_",
  168. error_kind=ERR_NEVER,
  169. )
  170. # Provide mapping from textual op to short int's op variant and boxed int's description.
  171. # Note that these are not complete implementations and require extra IR.
  172. int_comparison_op_mapping: dict[str, IntComparisonOpDescription] = {
  173. "==": IntComparisonOpDescription(ComparisonOp.EQ, int_equal_, False, False),
  174. "!=": IntComparisonOpDescription(ComparisonOp.NEQ, int_equal_, True, False),
  175. "<": IntComparisonOpDescription(ComparisonOp.SLT, int_less_than_, False, False),
  176. "<=": IntComparisonOpDescription(ComparisonOp.SLE, int_less_than_, True, True),
  177. ">": IntComparisonOpDescription(ComparisonOp.SGT, int_less_than_, False, True),
  178. ">=": IntComparisonOpDescription(ComparisonOp.SGE, int_less_than_, True, False),
  179. }
  180. int64_divide_op = custom_op(
  181. arg_types=[int64_rprimitive, int64_rprimitive],
  182. return_type=int64_rprimitive,
  183. c_function_name="CPyInt64_Divide",
  184. error_kind=ERR_MAGIC_OVERLAPPING,
  185. )
  186. int64_mod_op = custom_op(
  187. arg_types=[int64_rprimitive, int64_rprimitive],
  188. return_type=int64_rprimitive,
  189. c_function_name="CPyInt64_Remainder",
  190. error_kind=ERR_MAGIC_OVERLAPPING,
  191. )
  192. int32_divide_op = custom_op(
  193. arg_types=[int32_rprimitive, int32_rprimitive],
  194. return_type=int32_rprimitive,
  195. c_function_name="CPyInt32_Divide",
  196. error_kind=ERR_MAGIC_OVERLAPPING,
  197. )
  198. int32_mod_op = custom_op(
  199. arg_types=[int32_rprimitive, int32_rprimitive],
  200. return_type=int32_rprimitive,
  201. c_function_name="CPyInt32_Remainder",
  202. error_kind=ERR_MAGIC_OVERLAPPING,
  203. )
  204. int16_divide_op = custom_op(
  205. arg_types=[int16_rprimitive, int16_rprimitive],
  206. return_type=int16_rprimitive,
  207. c_function_name="CPyInt16_Divide",
  208. error_kind=ERR_MAGIC_OVERLAPPING,
  209. )
  210. int16_mod_op = custom_op(
  211. arg_types=[int16_rprimitive, int16_rprimitive],
  212. return_type=int16_rprimitive,
  213. c_function_name="CPyInt16_Remainder",
  214. error_kind=ERR_MAGIC_OVERLAPPING,
  215. )
  216. # Convert tagged int (as PyObject *) to i64
  217. int_to_int64_op = custom_op(
  218. arg_types=[object_rprimitive],
  219. return_type=int64_rprimitive,
  220. c_function_name="CPyLong_AsInt64",
  221. error_kind=ERR_MAGIC_OVERLAPPING,
  222. )
  223. ssize_t_to_int_op = custom_op(
  224. arg_types=[c_pyssize_t_rprimitive],
  225. return_type=int_rprimitive,
  226. c_function_name="CPyTagged_FromSsize_t",
  227. error_kind=ERR_MAGIC,
  228. )
  229. int64_to_int_op = custom_op(
  230. arg_types=[int64_rprimitive],
  231. return_type=int_rprimitive,
  232. c_function_name="CPyTagged_FromInt64",
  233. error_kind=ERR_MAGIC,
  234. )
  235. # Convert tagged int (as PyObject *) to i32
  236. int_to_int32_op = custom_op(
  237. arg_types=[object_rprimitive],
  238. return_type=int32_rprimitive,
  239. c_function_name="CPyLong_AsInt32",
  240. error_kind=ERR_MAGIC_OVERLAPPING,
  241. )
  242. int32_overflow = custom_op(
  243. arg_types=[],
  244. return_type=void_rtype,
  245. c_function_name="CPyInt32_Overflow",
  246. error_kind=ERR_ALWAYS,
  247. )
  248. int16_overflow = custom_op(
  249. arg_types=[],
  250. return_type=void_rtype,
  251. c_function_name="CPyInt16_Overflow",
  252. error_kind=ERR_ALWAYS,
  253. )
  254. uint8_overflow = custom_op(
  255. arg_types=[],
  256. return_type=void_rtype,
  257. c_function_name="CPyUInt8_Overflow",
  258. error_kind=ERR_ALWAYS,
  259. )