pca006132 24a85c472e | ||
---|---|---|
toy-impl | ||
.gitignore | ||
README.md |
README.md
NAC3 Specification
Specification and discussions about language design.
A toy implementation is in toy-impl
, requires python 3.9.
Referencing Host Variables from Kernel
Host variable to be accessed must be declared as global
in the kernel
function. This is to simplify and speed-up implementation, and also warn the
user about the variable being global. (prevent calling the interpreter many
times during compilation if there are many references to host variables)
Kernel cannot modify host variables, this would be checked by the compiler. Value that can be observed by the kernel would be frozen once the kernel has been compiled, subsequence modification within the host would not affect the kernel.
Only types supported in the kernel can be referenced.
Examples:
FOO = 0
@kernel
def correct() -> int:
global FOO
return FOO + 1
@kernel
def fail_without_global() -> int:
return FOO + 2
@kernel
def fail_write() -> None:
FOO += 1
Class and Functions
-
Instance variables must be annotated: (Issue #1)
class Foo: a: int b: int def __init__(self, a: int, b: int): self.a = a self.b = b
-
Three types of instance variables: (Issue #5)
- Host only variables: Do not add type annotation for it in the class.
- Kernel Invariants: Immutable in the kernel and in the host while the kernel
is executing. Type:
KernelInvariant[T]
. The types must be immutable. (use tuple instead of list in the host, but the type annotation should still be list?) - Normal Variables: The host can only assign to them in the
__init__
function. Not accessible afterwards.
-
Functions require full type signature, including type annotation to every parameter and return type.
def add(a: int, b: int) -> int: return a + b
-
RPCs: optional parameter type signature, require return type signature.
-
Function default parameters must be immutable.
-
Function pointers are supported, and lambda expression is not supported currently. (maybe support lambda after implementing type inference?)
Its type is denoted by the typing library, e.g.
Call[[int32, int32], int32]
.
Built-in Types
- Primitive types include:
bool
byte
int32
int64
uint32
uint64
float
str
bytes
- Collections include:
list
: homogeneous (elements must be of the same type) fixed-size (no append) list.tuple
: inhomogeneous immutable list, only pattern matching (e.g.a, b, c = (1, True, 1.2)
) and constant indexing is supported:t = (1, True) # OK a, b = t # OK a = t[0] # Not OK i = 0 a = t[i]
range
(over numerical types)
Numerical Types
- All binary operations expect the values to have the same type.
- Casting can be done by
T(v)
whereT
is the target type, andv
is the original value. Examples:int64(123)
- Integers are treated as
int32
by default. Floating point numbers are double by default. - No implicit coercion, require implicit cast.
For integers that don't fit in int32, users should cast them to
int64
explicitly, i.e.int64(2147483648)
. If the compiler found that the integer does not fit into int32, it would raise an error. (Issue #2) - Only
uint32
,int32
(and range of them) can be used as index.
Kernel Only class
- Annotate the class with
@kernel
/@portable
. - The instance can be created from within kernel functions, or the host if it is portable. It can be passed into kernels.
- All methods, including the constructor, are treated as kernel/portable functions that would be compiled by the compiler, no RPC function is allowed.
- If the instance is passed into the kernel, the host is not allowed to access the instance data. Access would raise exception.
Generics
We use type variable for denoting generics.
Example:
from typing import TypeVar
T = TypeVar('T')
class Foo(EnvExperiment):
@kernel
# type of a is the same as type of b
def run(self, a: T, b: T) -> bool:
return a == b
- Type variable can be limited to a fixed set of types.
- Type variables are invariant, same as the default in Python. We disallow covariant or contravariant. The compiler should mark as error if it encounters a type variable used in kernel that is declared covariant or contravariant.
- Code region protected by a type check, such as
if type(x) == int:
, would treatx
asint
, similar to how typescript type guard works.def add1(x: Union[int, bool]) -> int: if type(x) == int: # x is int return x + 1 else: # x must be bool return 2 if x else 1
- Generics are instantiated at compile time, all the type checks like
type(x) == int
would be evaluated as constants. Type checks are not allowed in area outside generics. - Type variable cannot occur alone in the result type, i.e. must be bound to the input parameters.
Lifetime
Probably need more discussions...