1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
|
"""Utilities for defining primitive ops.
Most of the ops can be automatically generated by matching against AST
nodes and types. For example, a func_op is automatically generated when
a specific function is called with the specific positional argument
count and argument types.
Example op definition:
list_len_op = func_op(name='builtins.len',
arg_types=[list_rprimitive],
result_type=short_int_rprimitive,
error_kind=ERR_NEVER,
emit=emit_len)
This op is automatically generated for calls to len() with a single
list argument. The result type is short_int_rprimitive, and this
never raises an exception (ERR_NEVER). The function emit_len is used
to generate C for this op. The op can also be manually generated using
"list_len_op". Ops that are only generated automatically don't need to
be assigned to a module attribute.
Ops defined with custom_op are only explicitly generated in
mypyc.irbuild and won't be generated automatically. They are always
assigned to a module attribute, as otherwise they won't be accessible.
The actual ops are defined in other submodules of this package, grouped
by category.
Most operations have fallback implementations that apply to all possible
arguments and types. For example, there are generic implementations of
arbitrary function and method calls, and binary operators. These generic
implementations are typically slower than specialized ones, but we tend
to rely on them for infrequently used ops. It's impractical to have
optimized implementations of all ops.
"""
from __future__ import annotations
from typing import Final, NamedTuple
from mypyc.ir.ops import PrimitiveDescription, StealsDescription
from mypyc.ir.rtypes import RType
# Error kind for functions that return negative integer on exception. This
# is only used for primitives. We translate it away during IR building.
ERR_NEG_INT: Final = 10
class CFunctionDescription(NamedTuple):
name: str
arg_types: list[RType]
return_type: RType
var_arg_type: RType | None
truncated_type: RType | None
c_function_name: str
error_kind: int
steals: StealsDescription
is_borrowed: bool
ordering: list[int] | None
extra_int_constants: list[tuple[int, RType]]
priority: int
is_pure: bool
# A description for C load operations including LoadGlobal and LoadAddress
class LoadAddressDescription(NamedTuple):
name: str
type: RType
src: str # name of the target to load
# Primitive ops for method call (such as 'str.join')
method_call_ops: dict[str, list[PrimitiveDescription]] = {}
# Primitive ops for top level function call (such as 'builtins.list')
function_ops: dict[str, list[PrimitiveDescription]] = {}
# Primitive ops for binary operations
binary_ops: dict[str, list[PrimitiveDescription]] = {}
# Primitive ops for unary ops
unary_ops: dict[str, list[PrimitiveDescription]] = {}
builtin_names: dict[str, tuple[RType, str]] = {}
def method_op(
name: str,
arg_types: list[RType],
return_type: RType,
c_function_name: str,
error_kind: int,
var_arg_type: RType | None = None,
truncated_type: RType | None = None,
ordering: list[int] | None = None,
extra_int_constants: list[tuple[int, RType]] | None = None,
steals: StealsDescription = False,
is_borrowed: bool = False,
priority: int = 1,
is_pure: bool = False,
) -> PrimitiveDescription:
"""Define a c function call op that replaces a method call.
This will be automatically generated by matching against the AST.
Args:
name: short name of the method (for example, 'append')
arg_types: argument types; the receiver is always the first argument
return_type: type of the return value. Use void_rtype to represent void.
c_function_name: name of the C function to call
error_kind: how errors are represented in the result (one of ERR_*)
var_arg_type: type of all variable arguments
truncated_type: type to truncated to(See Truncate for info)
if it's defined both return_type and it should be non-referenced
integer types or bool type
ordering: optional ordering of the arguments, if defined,
reorders the arguments accordingly.
should never be used together with var_arg_type.
all the other arguments(such as arg_types) are in the order
accepted by the python syntax(before reordering)
extra_int_constants: optional extra integer constants as the last arguments to a C call
steals: description of arguments that this steals (ref count wise)
is_borrowed: if True, returned value is borrowed (no need to decrease refcount)
priority: if multiple ops match, the one with the highest priority is picked
is_pure: if True, declare that the C function has no side effects, takes immutable
arguments, and never raises an exception
"""
if extra_int_constants is None:
extra_int_constants = []
ops = method_call_ops.setdefault(name, [])
desc = PrimitiveDescription(
name,
arg_types,
return_type,
var_arg_type,
truncated_type,
c_function_name,
error_kind,
steals,
is_borrowed,
ordering,
extra_int_constants,
priority,
is_pure=is_pure,
)
ops.append(desc)
return desc
def function_op(
name: str,
arg_types: list[RType],
return_type: RType,
c_function_name: str,
error_kind: int,
var_arg_type: RType | None = None,
truncated_type: RType | None = None,
ordering: list[int] | None = None,
extra_int_constants: list[tuple[int, RType]] | None = None,
steals: StealsDescription = False,
is_borrowed: bool = False,
priority: int = 1,
) -> PrimitiveDescription:
"""Define a C function call op that replaces a function call.
This will be automatically generated by matching against the AST.
Most arguments are similar to method_op().
Args:
name: full name of the function
arg_types: positional argument types for which this applies
"""
if extra_int_constants is None:
extra_int_constants = []
ops = function_ops.setdefault(name, [])
desc = PrimitiveDescription(
name,
arg_types,
return_type,
var_arg_type=var_arg_type,
truncated_type=truncated_type,
c_function_name=c_function_name,
error_kind=error_kind,
steals=steals,
is_borrowed=is_borrowed,
ordering=ordering,
extra_int_constants=extra_int_constants,
priority=priority,
is_pure=False,
)
ops.append(desc)
return desc
def binary_op(
name: str,
arg_types: list[RType],
return_type: RType,
error_kind: int,
c_function_name: str | None = None,
primitive_name: str | None = None,
var_arg_type: RType | None = None,
truncated_type: RType | None = None,
ordering: list[int] | None = None,
extra_int_constants: list[tuple[int, RType]] | None = None,
steals: StealsDescription = False,
is_borrowed: bool = False,
priority: int = 1,
) -> PrimitiveDescription:
"""Define a c function call op for a binary operation.
This will be automatically generated by matching against the AST.
Most arguments are similar to method_op(), but exactly two argument types
are expected.
"""
assert c_function_name is not None or primitive_name is not None
assert not (c_function_name is not None and primitive_name is not None)
if extra_int_constants is None:
extra_int_constants = []
ops = binary_ops.setdefault(name, [])
desc = PrimitiveDescription(
name=primitive_name or name,
arg_types=arg_types,
return_type=return_type,
var_arg_type=var_arg_type,
truncated_type=truncated_type,
c_function_name=c_function_name,
error_kind=error_kind,
steals=steals,
is_borrowed=is_borrowed,
ordering=ordering,
extra_int_constants=extra_int_constants,
priority=priority,
is_pure=False,
)
ops.append(desc)
return desc
def custom_op(
arg_types: list[RType],
return_type: RType,
c_function_name: str,
error_kind: int,
var_arg_type: RType | None = None,
truncated_type: RType | None = None,
ordering: list[int] | None = None,
extra_int_constants: list[tuple[int, RType]] | None = None,
steals: StealsDescription = False,
is_borrowed: bool = False,
*,
is_pure: bool = False,
) -> CFunctionDescription:
"""Create a one-off CallC op that can't be automatically generated from the AST.
Most arguments are similar to method_op().
"""
if extra_int_constants is None:
extra_int_constants = []
return CFunctionDescription(
"<custom>",
arg_types,
return_type,
var_arg_type,
truncated_type,
c_function_name,
error_kind,
steals,
is_borrowed,
ordering,
extra_int_constants,
0,
is_pure=is_pure,
)
def custom_primitive_op(
name: str,
arg_types: list[RType],
return_type: RType,
error_kind: int,
c_function_name: str | None = None,
var_arg_type: RType | None = None,
truncated_type: RType | None = None,
ordering: list[int] | None = None,
extra_int_constants: list[tuple[int, RType]] | None = None,
steals: StealsDescription = False,
is_borrowed: bool = False,
is_pure: bool = False,
) -> PrimitiveDescription:
"""Define a primitive op that can't be automatically generated based on the AST.
Most arguments are similar to method_op().
"""
if extra_int_constants is None:
extra_int_constants = []
return PrimitiveDescription(
name=name,
arg_types=arg_types,
return_type=return_type,
var_arg_type=var_arg_type,
truncated_type=truncated_type,
c_function_name=c_function_name,
error_kind=error_kind,
steals=steals,
is_borrowed=is_borrowed,
ordering=ordering,
extra_int_constants=extra_int_constants,
priority=0,
is_pure=is_pure,
)
def unary_op(
name: str,
arg_type: RType,
return_type: RType,
c_function_name: str,
error_kind: int,
truncated_type: RType | None = None,
ordering: list[int] | None = None,
extra_int_constants: list[tuple[int, RType]] | None = None,
steals: StealsDescription = False,
is_borrowed: bool = False,
priority: int = 1,
is_pure: bool = False,
) -> PrimitiveDescription:
"""Define a primitive op for an unary operation.
This will be automatically generated by matching against the AST.
Most arguments are similar to method_op(), but exactly one argument type
is expected.
"""
if extra_int_constants is None:
extra_int_constants = []
ops = unary_ops.setdefault(name, [])
desc = PrimitiveDescription(
name,
[arg_type],
return_type,
var_arg_type=None,
truncated_type=truncated_type,
c_function_name=c_function_name,
error_kind=error_kind,
steals=steals,
is_borrowed=is_borrowed,
ordering=ordering,
extra_int_constants=extra_int_constants,
priority=priority,
is_pure=is_pure,
)
ops.append(desc)
return desc
def load_address_op(name: str, type: RType, src: str) -> LoadAddressDescription:
assert name not in builtin_names, "already defined: %s" % name
builtin_names[name] = (type, src)
return LoadAddressDescription(name, type, src)
# Import various modules that set up global state.
import mypyc.primitives.bytes_ops
import mypyc.primitives.dict_ops
import mypyc.primitives.float_ops
import mypyc.primitives.int_ops
import mypyc.primitives.list_ops
import mypyc.primitives.misc_ops
import mypyc.primitives.str_ops
import mypyc.primitives.tuple_ops # noqa: F401
|