Notes on Literal Types
Note
This document describes an advanced feature designed to overcome some limitations of the compilation mechanism relating to types.
Some features need to specialize based on the literal value during
compilation to produce type stable code necessary for successful compilation in
Numba. This can be achieved by propagating the literal value through the type
system. Numba recognizes inline literal values as numba.types.Literal
.
For example:
def foo(x):
a = 123
return bar(x, a)
Numba will infer the type of a
as Literal[int](123)
. The definition of
bar()
can subsequently specialize its implementation knowing that the
second argument is an int
with the value 123
.
Literal
Type
Classes and methods related to the Literal
type.
- class numba.types.Literal(*args, **kwargs)
Base class for Literal types. Literal types contain the original Python value in the type.
A literal type should always be constructed from the literal(val) function.
- numba.types.literal(value)
Returns a Literal instance or raise LiteralTypingError
- numba.types.unliteral(lit_type)
Get base type from Literal type.
- numba.types.maybe_literal(value)
Get a Literal type for the value or None.
Specifying for Literal Typing
To specify a value as a Literal
type in code scheduled for JIT compilation,
use the following function:
- numba.literally(obj)
Forces Numba to interpret obj as an Literal value.
obj must be either a literal or an argument of the caller function, where the argument must be bound to a literal. The literal requirement propagates up the call stack.
This function is intercepted by the compiler to alter the compilation behavior to wrap the corresponding function parameters as
Literal
. It has no effect outside of nopython-mode (interpreter, and objectmode).The current implementation detects literal arguments in two ways:
Scans for uses of
literally
via a compiler pass.literally
is overloaded to raisenumba.errors.ForceLiteralArg
to signal the dispatcher to treat the corresponding parameter differently. This mode is to support indirect use (via a function call).
The execution semantic of this function is equivalent to an identity function.
See numba/tests/test_literal_dispatch.py for examples.
Code Example
1 import numba
2
3 def power(x, n):
4 raise NotImplementedError
5
6 @numba.extending.overload(power)
7 def ov_power(x, n):
8 if isinstance(n, numba.types.Literal):
9 # only if `n` is a literal
10 if n.literal_value == 2:
11 # special case: square
12 print("square")
13 return lambda x, n: x * x
14 elif n.literal_value == 3:
15 # special case: cubic
16 print("cubic")
17 return lambda x, n: x * x * x
18 else:
19 # If `n` is not literal, request literal dispatch
20 return lambda x, n: numba.literally(n)
21
22 print("generic")
23 return lambda x, n: x ** n
24
25 @numba.njit
26 def test_power(x, n):
27 return power(x, n)
28
29 # should print "square" and "9"
30 print(test_power(3, 2))
31
32 # should print "cubic" and "27"
33 print(test_power(3, 3))
34
35 # should print "generic" and "81"
36 print(test_power(3, 4))
37
Internal Details
Internally, the compiler raises a ForceLiteralArgs
exception to signal
the dispatcher to wrap specified arguments using the Literal
type.
- class numba.errors.ForceLiteralArg(arg_indices, fold_arguments=None, loc=None)
A Pseudo-exception to signal the dispatcher to type an argument literally
- Attributes
- requested_argsfrozenset[int]
requested positions of the arguments.
- __init__(arg_indices, fold_arguments=None, loc=None)
- Parameters
- arg_indicesSequence[int]
requested positions of the arguments.
- fold_arguments: callable
A function
(tuple, dict) -> tuple
that binds and flattens theargs
andkwargs
.- locnumba.ir.Loc or None
- __or__(other)
Same as self.combine(other)
- combine(other)
Returns a new instance by or’ing the requested_args.
Inside Extensions
@overload
extensions can use literally
inside the implementation body
like in normal jit-code.
Explicit handling of literal requirements is possible through use of the following:
- class numba.extending.SentryLiteralArgs(literal_args)
- Parameters
- literal_argsSequence[str]
A sequence of names for literal arguments
Examples
The following line:
>>> SentryLiteralArgs(literal_args).for_pysig(pysig).bind(*args, **kwargs)
is equivalent to:
>>> sentry_literal_args(pysig, literal_args, args, kwargs)
- for_function(func)
Bind the sentry to the signature of func.
- Parameters
- funcFunction
A python function.
- Returns
- objBoundLiteralArgs
- for_pysig(pysig)
Bind the sentry to the given signature pysig.
- Parameters
- pysiginspect.Signature
- Returns
- objBoundLiteralArgs
- class numba.extending.BoundLiteralArgs(pysig, literal_args)
This class is usually created by SentryLiteralArgs.
- bind(*args, **kwargs)
Bind to argument types.
- numba.extending.sentry_literal_args(pysig, literal_args, args, kwargs)
Ensures that the given argument types (in args and kwargs) are literally typed for a function with the python signature pysig and the list of literal argument names in literal_args.
Alternatively, this is the same as:
SentryLiteralArgs(literal_args).for_pysig(pysig).bind(*args, **kwargs)