2020-12-15 11:27:47 +08:00
|
|
|
# NAC3 Specification
|
|
|
|
|
2020-12-15 13:09:32 +08:00
|
|
|
Specification and discussions about language design.
|
|
|
|
|
2020-12-22 16:56:40 +08:00
|
|
|
A toy implementation is in [`toy-impl`](./toy-impl), requires python 3.9.
|
2020-12-17 14:52:51 +08:00
|
|
|
|
|
|
|
|
2021-04-11 23:29:43 +08:00
|
|
|
## 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)
|
2020-12-15 13:09:32 +08:00
|
|
|
|
2021-04-11 23:29:43 +08:00
|
|
|
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.
|
2021-01-19 15:35:11 +08:00
|
|
|
|
2021-04-11 23:29:43 +08:00
|
|
|
Only types supported in the kernel can be referenced.
|
2020-12-15 13:09:32 +08:00
|
|
|
|
2021-04-12 19:58:29 +08:00
|
|
|
Examples:
|
|
|
|
```python
|
|
|
|
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
|
|
|
|
|
|
|
|
```
|
|
|
|
|
2020-12-15 17:33:31 +08:00
|
|
|
## Class and Functions
|
2021-04-11 23:29:43 +08:00
|
|
|
* Instance variables must be annotated: (Issue #1)
|
|
|
|
```python
|
2020-12-15 17:33:31 +08:00
|
|
|
class Foo:
|
|
|
|
a: int
|
|
|
|
b: int
|
|
|
|
def __init__(self, a: int, b: int):
|
|
|
|
self.a = a
|
|
|
|
self.b = b
|
|
|
|
```
|
2021-06-09 09:48:07 +08:00
|
|
|
* Instance variables not used would be warned by the compiler, except for those
|
|
|
|
preceded by the pseudocomment `# nac3:no_warn_unused`. (#9) The comment can
|
|
|
|
either be placed on top of the variable or next to the variable. Example:
|
|
|
|
```python
|
|
|
|
class Foo:
|
|
|
|
# nac3:no_warn_unused
|
|
|
|
a: int
|
|
|
|
b: int # nac3:no_warn_unused
|
|
|
|
def __init__(self):
|
|
|
|
pass
|
|
|
|
```
|
|
|
|
* Use-before-define:
|
|
|
|
* Host-only constructor: Error if a certain field `f` is
|
|
|
|
used in other kernel methods but missing from the object constructed in the
|
|
|
|
host.
|
|
|
|
* `@portable`/`@kernel` constructor: Error if a certain
|
|
|
|
field `f` is used in other kernel methods but not defined in the
|
|
|
|
constructor, or used in the constructor before definition.
|
2021-04-11 23:29:43 +08:00
|
|
|
* Three types of instance variables: (Issue #5)
|
|
|
|
* Host only variables: Do not add type annotation for it in the class.
|
2021-06-08 12:03:49 +08:00
|
|
|
* Kernel only variables: Denoted with type `Kernel[T]`.
|
2021-04-11 23:29:43 +08:00
|
|
|
* Kernel Invariants: Immutable in the kernel and in the host while the kernel
|
2021-06-08 12:03:49 +08:00
|
|
|
is executing. Type: `KernelImmutable[T]`. The types must be immutable.
|
|
|
|
In particular, the attribute cannot be modified during RPC calls.
|
2021-04-11 23:29:43 +08:00
|
|
|
* Normal Variables: The host can only assign to them in the `__init__`
|
|
|
|
function. Not accessible afterwards.
|
2020-12-15 17:33:31 +08:00
|
|
|
* Functions require full type signature, including type annotation to every
|
|
|
|
parameter and return type.
|
2021-04-11 23:29:43 +08:00
|
|
|
```python
|
2020-12-15 17:33:31 +08:00
|
|
|
def add(a: int, b: int) -> int:
|
|
|
|
return a + b
|
|
|
|
```
|
|
|
|
* RPCs: optional parameter type signature, require return type signature.
|
2021-06-08 12:03:49 +08:00
|
|
|
* Classes with constructor annotated with `kernel/portable` can be constructed
|
|
|
|
within kernel functions. RPC calls for those objects would pass the whole
|
|
|
|
object back to the host.
|
2021-04-11 23:29:43 +08:00
|
|
|
* Function default parameters must be immutable.
|
|
|
|
* Function pointers are supported, and lambda expression is not supported
|
|
|
|
currently. (maybe support lambda after implementing type inference?)
|
2020-12-15 17:33:31 +08:00
|
|
|
|
2021-04-12 19:58:29 +08:00
|
|
|
Its type is denoted by the typing library, e.g. `Call[[int32, int32], int32]`.
|
|
|
|
|
2020-12-17 10:22:46 +08:00
|
|
|
## Built-in Types
|
|
|
|
* Primitive types include:
|
|
|
|
* `bool`
|
2020-12-17 14:52:24 +08:00
|
|
|
* `byte`
|
2020-12-17 10:22:46 +08:00
|
|
|
* `int32`
|
|
|
|
* `int64`
|
2020-12-17 10:27:11 +08:00
|
|
|
* `uint32`
|
|
|
|
* `uint64`
|
2020-12-17 10:22:46 +08:00
|
|
|
* `float`
|
2021-04-11 23:29:43 +08:00
|
|
|
* `str`
|
|
|
|
* `bytes`
|
2020-12-17 10:22:46 +08:00
|
|
|
* Collections include:
|
|
|
|
* `list`: homogeneous (elements must be of the same type) fixed-size (no
|
|
|
|
append) list.
|
2021-04-11 23:29:43 +08:00
|
|
|
* `tuple`: inhomogeneous immutable list, only pattern
|
|
|
|
matching (e.g. `a, b, c = (1, True, 1.2)`) and constant indexing is
|
|
|
|
supported:
|
2020-12-17 10:22:46 +08:00
|
|
|
```
|
|
|
|
t = (1, True)
|
|
|
|
# OK
|
|
|
|
a, b = t
|
|
|
|
# OK
|
|
|
|
a = t[0]
|
|
|
|
# Not OK
|
|
|
|
i = 0
|
|
|
|
a = t[i]
|
|
|
|
```
|
2021-04-11 23:29:43 +08:00
|
|
|
* `range` (over numerical types)
|
2020-12-17 10:22:46 +08:00
|
|
|
|
2020-12-17 14:52:24 +08:00
|
|
|
### Numerical Types
|
2021-04-11 23:29:43 +08:00
|
|
|
* All binary operations expect the values to have the same type.
|
2020-12-17 14:52:24 +08:00
|
|
|
* Casting can be done by `T(v)` where `T` is the target type, and `v` is the
|
|
|
|
original value. Examples: `int64(123)`
|
2021-04-11 23:29:43 +08:00
|
|
|
* 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)
|
2020-12-17 14:52:24 +08:00
|
|
|
* Only `uint32`, `int32` (and range of them) can be used as index.
|
|
|
|
|
2021-04-11 23:36:18 +08:00
|
|
|
### Kernel Only class
|
2021-04-12 19:58:45 +08:00
|
|
|
* 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.
|
2021-04-11 23:36:18 +08:00
|
|
|
|
2020-12-15 17:33:31 +08:00
|
|
|
## Generics
|
|
|
|
We use [type variable](https://docs.python.org/3/library/typing.html#typing.TypeVar) for denoting generics.
|
2020-12-15 13:09:32 +08:00
|
|
|
|
2020-12-15 17:33:31 +08:00
|
|
|
Example:
|
2021-04-11 23:29:43 +08:00
|
|
|
```python
|
2020-12-15 17:33:31 +08:00
|
|
|
from typing import TypeVar
|
|
|
|
T = TypeVar('T')
|
2020-12-15 13:09:32 +08:00
|
|
|
|
|
|
|
class Foo(EnvExperiment):
|
|
|
|
@kernel
|
2020-12-15 17:33:31 +08:00
|
|
|
# type of a is the same as type of b
|
|
|
|
def run(self, a: T, b: T) -> bool:
|
|
|
|
return a == b
|
2020-12-15 13:09:32 +08:00
|
|
|
```
|
|
|
|
|
2021-01-19 15:35:11 +08:00
|
|
|
* Type variable can be limited to a fixed set of types.
|
2020-12-15 17:33:31 +08:00
|
|
|
* 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.
|
2021-06-09 09:45:13 +08:00
|
|
|
* A custom function `is_type(x, T)` would be provided to check whether `x` is an
|
|
|
|
instance of `T`, other methods like `type(x) == int` or `isinstance(x, int)`
|
|
|
|
would not compile. The function would be able to check generic types for
|
|
|
|
`list` and `tuple`. When running on the host, user can specify whether to use
|
|
|
|
a debug mode checking (recursively check all elements, which would be slower
|
|
|
|
for large lists) or performance mode which only check the first element of
|
|
|
|
each list. (#15)
|
|
|
|
* Code region protected by a type check, such as `if is_type(x, int):`, would
|
2020-12-15 17:33:31 +08:00
|
|
|
treat `x` as `int`, similar to how [typescript type guard](https://www.typescripttutorial.net/typescript-tutorial/typescript-type-guards/) works.
|
2021-04-11 23:29:43 +08:00
|
|
|
```python
|
2020-12-15 17:33:31 +08:00
|
|
|
def add1(x: Union[int, bool]) -> int:
|
2021-06-09 09:45:13 +08:00
|
|
|
if is_type(x, int):
|
2020-12-15 17:33:31 +08:00
|
|
|
# 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
|
2021-06-09 09:45:13 +08:00
|
|
|
`is_type(x, int)` would be evaluated as constants. Type checks are not allowed
|
2020-12-15 17:33:31 +08:00
|
|
|
in area outside generics.
|
2021-01-19 15:35:11 +08:00
|
|
|
* Type variable cannot occur alone in the result type, i.e. must be bound to the
|
|
|
|
input parameters.
|
2020-12-15 17:33:31 +08:00
|
|
|
|
2021-06-09 09:45:05 +08:00
|
|
|
## For loop unrolling (#12)
|
|
|
|
A pseudocomment can be used for unrolling for loops that iterates a fixed amount
|
|
|
|
of time. This can be used for iterating over inhomogeneous tuples. Example:
|
|
|
|
|
|
|
|
```python
|
|
|
|
params = (1, 1.5, "foo")
|
|
|
|
# nac3:unroll
|
|
|
|
for p in params:
|
|
|
|
print(p)
|
|
|
|
```
|
|
|
|
|
2021-04-11 23:29:43 +08:00
|
|
|
## Lifetime
|
|
|
|
Probably need more discussions...
|
2020-12-15 11:27:47 +08:00
|
|
|
|