| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- """Arbitrary-precision integer primitive ops.
- These mostly operate on (usually) unboxed integers that use a tagged pointer
- representation (CPyTagged) and correspond to the Python 'int' type.
- See also the documentation for mypyc.rtypes.int_rprimitive.
- Use mypyc.ir.ops.IntOp for operations on fixed-width/C integers.
- """
- from __future__ import annotations
- from typing import NamedTuple
- from mypyc.ir.ops import ERR_ALWAYS, ERR_MAGIC, ERR_MAGIC_OVERLAPPING, ERR_NEVER, ComparisonOp
- from mypyc.ir.rtypes import (
- RType,
- bit_rprimitive,
- bool_rprimitive,
- c_pyssize_t_rprimitive,
- float_rprimitive,
- int16_rprimitive,
- int32_rprimitive,
- int64_rprimitive,
- int_rprimitive,
- object_rprimitive,
- str_rprimitive,
- void_rtype,
- )
- from mypyc.primitives.registry import (
- CFunctionDescription,
- binary_op,
- custom_op,
- function_op,
- load_address_op,
- unary_op,
- )
- # Constructors for builtins.int and native int types have the same behavior. In
- # interpreted mode, native int types are just aliases to 'int'.
- for int_name in (
- "builtins.int",
- "mypy_extensions.i64",
- "mypy_extensions.i32",
- "mypy_extensions.i16",
- "mypy_extensions.u8",
- ):
- # These int constructors produce object_rprimitives that then need to be unboxed
- # I guess unboxing ourselves would save a check and branch though?
- # Get the type object for 'builtins.int' or a native int type.
- # For ordinary calls to int() we use a load_address to the type.
- # Native ints don't have a separate type object -- we just use 'builtins.int'.
- load_address_op(name=int_name, type=object_rprimitive, src="PyLong_Type")
- # int(float). We could do a bit better directly.
- function_op(
- name=int_name,
- arg_types=[float_rprimitive],
- return_type=int_rprimitive,
- c_function_name="CPyTagged_FromFloat",
- error_kind=ERR_MAGIC,
- )
- # int(string)
- function_op(
- name=int_name,
- arg_types=[str_rprimitive],
- return_type=object_rprimitive,
- c_function_name="CPyLong_FromStr",
- error_kind=ERR_MAGIC,
- )
- # int(string, base)
- function_op(
- name=int_name,
- arg_types=[str_rprimitive, int_rprimitive],
- return_type=object_rprimitive,
- c_function_name="CPyLong_FromStrWithBase",
- error_kind=ERR_MAGIC,
- )
- # str(int)
- int_to_str_op = function_op(
- name="builtins.str",
- arg_types=[int_rprimitive],
- return_type=str_rprimitive,
- c_function_name="CPyTagged_Str",
- error_kind=ERR_MAGIC,
- priority=2,
- )
- # We need a specialization for str on bools also since the int one is wrong...
- function_op(
- name="builtins.str",
- arg_types=[bool_rprimitive],
- return_type=str_rprimitive,
- c_function_name="CPyBool_Str",
- error_kind=ERR_MAGIC,
- priority=3,
- )
- def int_binary_op(
- name: str,
- c_function_name: str,
- return_type: RType = int_rprimitive,
- error_kind: int = ERR_NEVER,
- ) -> None:
- binary_op(
- name=name,
- arg_types=[int_rprimitive, int_rprimitive],
- return_type=return_type,
- c_function_name=c_function_name,
- error_kind=error_kind,
- )
- # Binary, unary and augmented assignment operations that operate on CPyTagged ints
- # are implemented as C functions.
- int_binary_op("+", "CPyTagged_Add")
- int_binary_op("-", "CPyTagged_Subtract")
- int_binary_op("*", "CPyTagged_Multiply")
- int_binary_op("&", "CPyTagged_And")
- int_binary_op("|", "CPyTagged_Or")
- int_binary_op("^", "CPyTagged_Xor")
- # Divide and remainder we honestly propagate errors from because they
- # can raise ZeroDivisionError
- int_binary_op("//", "CPyTagged_FloorDivide", error_kind=ERR_MAGIC)
- int_binary_op("%", "CPyTagged_Remainder", error_kind=ERR_MAGIC)
- # Negative shift counts raise an exception
- int_binary_op(">>", "CPyTagged_Rshift", error_kind=ERR_MAGIC)
- int_binary_op("<<", "CPyTagged_Lshift", error_kind=ERR_MAGIC)
- int_binary_op(
- "/", "CPyTagged_TrueDivide", return_type=float_rprimitive, error_kind=ERR_MAGIC_OVERLAPPING
- )
- # This should work because assignment operators are parsed differently
- # and the code in irbuild that handles it does the assignment
- # regardless of whether or not the operator works in place anyway.
- int_binary_op("+=", "CPyTagged_Add")
- int_binary_op("-=", "CPyTagged_Subtract")
- int_binary_op("*=", "CPyTagged_Multiply")
- int_binary_op("&=", "CPyTagged_And")
- int_binary_op("|=", "CPyTagged_Or")
- int_binary_op("^=", "CPyTagged_Xor")
- int_binary_op("//=", "CPyTagged_FloorDivide", error_kind=ERR_MAGIC)
- int_binary_op("%=", "CPyTagged_Remainder", error_kind=ERR_MAGIC)
- int_binary_op(">>=", "CPyTagged_Rshift", error_kind=ERR_MAGIC)
- int_binary_op("<<=", "CPyTagged_Lshift", error_kind=ERR_MAGIC)
- def int_unary_op(name: str, c_function_name: str) -> CFunctionDescription:
- return unary_op(
- name=name,
- arg_type=int_rprimitive,
- return_type=int_rprimitive,
- c_function_name=c_function_name,
- error_kind=ERR_NEVER,
- )
- int_neg_op = int_unary_op("-", "CPyTagged_Negate")
- int_invert_op = int_unary_op("~", "CPyTagged_Invert")
- # Primitives related to integer comparison operations:
- # Description for building int comparison ops
- #
- # Fields:
- # binary_op_variant: identify which IntOp to use when operands are short integers
- # c_func_description: the C function to call when operands are tagged integers
- # c_func_negated: whether to negate the C function call's result
- # c_func_swap_operands: whether to swap lhs and rhs when call the function
- class IntComparisonOpDescription(NamedTuple):
- binary_op_variant: int
- c_func_description: CFunctionDescription
- c_func_negated: bool
- c_func_swap_operands: bool
- # Equals operation on two boxed tagged integers
- int_equal_ = custom_op(
- arg_types=[int_rprimitive, int_rprimitive],
- return_type=bit_rprimitive,
- c_function_name="CPyTagged_IsEq_",
- error_kind=ERR_NEVER,
- )
- # Less than operation on two boxed tagged integers
- int_less_than_ = custom_op(
- arg_types=[int_rprimitive, int_rprimitive],
- return_type=bit_rprimitive,
- c_function_name="CPyTagged_IsLt_",
- error_kind=ERR_NEVER,
- )
- # Provide mapping from textual op to short int's op variant and boxed int's description.
- # Note that these are not complete implementations and require extra IR.
- int_comparison_op_mapping: dict[str, IntComparisonOpDescription] = {
- "==": IntComparisonOpDescription(ComparisonOp.EQ, int_equal_, False, False),
- "!=": IntComparisonOpDescription(ComparisonOp.NEQ, int_equal_, True, False),
- "<": IntComparisonOpDescription(ComparisonOp.SLT, int_less_than_, False, False),
- "<=": IntComparisonOpDescription(ComparisonOp.SLE, int_less_than_, True, True),
- ">": IntComparisonOpDescription(ComparisonOp.SGT, int_less_than_, False, True),
- ">=": IntComparisonOpDescription(ComparisonOp.SGE, int_less_than_, True, False),
- }
- int64_divide_op = custom_op(
- arg_types=[int64_rprimitive, int64_rprimitive],
- return_type=int64_rprimitive,
- c_function_name="CPyInt64_Divide",
- error_kind=ERR_MAGIC_OVERLAPPING,
- )
- int64_mod_op = custom_op(
- arg_types=[int64_rprimitive, int64_rprimitive],
- return_type=int64_rprimitive,
- c_function_name="CPyInt64_Remainder",
- error_kind=ERR_MAGIC_OVERLAPPING,
- )
- int32_divide_op = custom_op(
- arg_types=[int32_rprimitive, int32_rprimitive],
- return_type=int32_rprimitive,
- c_function_name="CPyInt32_Divide",
- error_kind=ERR_MAGIC_OVERLAPPING,
- )
- int32_mod_op = custom_op(
- arg_types=[int32_rprimitive, int32_rprimitive],
- return_type=int32_rprimitive,
- c_function_name="CPyInt32_Remainder",
- error_kind=ERR_MAGIC_OVERLAPPING,
- )
- int16_divide_op = custom_op(
- arg_types=[int16_rprimitive, int16_rprimitive],
- return_type=int16_rprimitive,
- c_function_name="CPyInt16_Divide",
- error_kind=ERR_MAGIC_OVERLAPPING,
- )
- int16_mod_op = custom_op(
- arg_types=[int16_rprimitive, int16_rprimitive],
- return_type=int16_rprimitive,
- c_function_name="CPyInt16_Remainder",
- error_kind=ERR_MAGIC_OVERLAPPING,
- )
- # Convert tagged int (as PyObject *) to i64
- int_to_int64_op = custom_op(
- arg_types=[object_rprimitive],
- return_type=int64_rprimitive,
- c_function_name="CPyLong_AsInt64",
- error_kind=ERR_MAGIC_OVERLAPPING,
- )
- ssize_t_to_int_op = custom_op(
- arg_types=[c_pyssize_t_rprimitive],
- return_type=int_rprimitive,
- c_function_name="CPyTagged_FromSsize_t",
- error_kind=ERR_MAGIC,
- )
- int64_to_int_op = custom_op(
- arg_types=[int64_rprimitive],
- return_type=int_rprimitive,
- c_function_name="CPyTagged_FromInt64",
- error_kind=ERR_MAGIC,
- )
- # Convert tagged int (as PyObject *) to i32
- int_to_int32_op = custom_op(
- arg_types=[object_rprimitive],
- return_type=int32_rprimitive,
- c_function_name="CPyLong_AsInt32",
- error_kind=ERR_MAGIC_OVERLAPPING,
- )
- int32_overflow = custom_op(
- arg_types=[],
- return_type=void_rtype,
- c_function_name="CPyInt32_Overflow",
- error_kind=ERR_ALWAYS,
- )
- int16_overflow = custom_op(
- arg_types=[],
- return_type=void_rtype,
- c_function_name="CPyInt16_Overflow",
- error_kind=ERR_ALWAYS,
- )
- uint8_overflow = custom_op(
- arg_types=[],
- return_type=void_rtype,
- c_function_name="CPyUInt8_Overflow",
- error_kind=ERR_ALWAYS,
- )
|