225 lines
7.9 KiB
Python
225 lines
7.9 KiB
Python
|
"""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 typing import Dict, NamedTuple
|
||
|
from mypyc.ir.ops import ERR_NEVER, ERR_MAGIC, ERR_MAGIC_OVERLAPPING, ERR_ALWAYS, ComparisonOp
|
||
|
from mypyc.ir.rtypes import (
|
||
|
int_rprimitive, bool_rprimitive, float_rprimitive, object_rprimitive,
|
||
|
str_rprimitive, bit_rprimitive, int64_rprimitive, int32_rprimitive, void_rtype, RType,
|
||
|
c_pyssize_t_rprimitive
|
||
|
)
|
||
|
from mypyc.primitives.registry import (
|
||
|
load_address_op, unary_op, CFunctionDescription, function_op, binary_op, custom_op
|
||
|
)
|
||
|
|
||
|
# 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'.
|
||
|
# For ordinary calls to int() we use a load_address to the type
|
||
|
load_address_op(
|
||
|
name='builtins.int',
|
||
|
type=object_rprimitive,
|
||
|
src='PyLong_Type')
|
||
|
|
||
|
# int(float). We could do a bit better directly.
|
||
|
function_op(
|
||
|
name='builtins.int',
|
||
|
arg_types=[float_rprimitive],
|
||
|
return_type=object_rprimitive,
|
||
|
c_function_name='CPyLong_FromFloat',
|
||
|
error_kind=ERR_MAGIC)
|
||
|
|
||
|
# int(string)
|
||
|
function_op(
|
||
|
name='builtins.int',
|
||
|
arg_types=[str_rprimitive],
|
||
|
return_type=object_rprimitive,
|
||
|
c_function_name='CPyLong_FromStr',
|
||
|
error_kind=ERR_MAGIC)
|
||
|
|
||
|
# int(string, base)
|
||
|
function_op(
|
||
|
name='builtins.int',
|
||
|
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)
|
||
|
|
||
|
# 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
|
||
|
IntComparisonOpDescription = NamedTuple(
|
||
|
'IntComparisonOpDescription', [('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)
|
||
|
|
||
|
# 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)
|