Kernel only attribute annotation (#76) #127
|
@ -1,73 +0,0 @@
|
||||||
from min_artiq import *
|
|
||||||
from numpy import int32
|
|
||||||
|
|
||||||
@nac3
|
|
||||||
class Base:
|
|
||||||
c: Kernel[int32]
|
|
||||||
def __init__(self):
|
|
||||||
self.c = 4
|
|
||||||
self.a = 3
|
|
||||||
self.a = 5
|
|
||||||
|
|
||||||
@nac3
|
|
||||||
class D:
|
|
||||||
da: KernelInvariant[int32]
|
|
||||||
def __init__(self):
|
|
||||||
self.da = 321
|
|
||||||
|
|
||||||
@nac3
|
|
||||||
class C:
|
|
||||||
ca: KernelInvariant[int32]
|
|
||||||
cb: KernelInvariant[D]
|
|
||||||
def __init__(self, d: D):
|
|
||||||
self.ca = 123
|
|
||||||
self.cb = d
|
|
||||||
|
|
||||||
@nac3
|
|
||||||
class A(Base):
|
|
||||||
core: KernelInvariant[Core]
|
|
||||||
led0: KernelInvariant[TTLOut]
|
|
||||||
led1: KernelInvariant[TTLOut]
|
|
||||||
d: Kernel[bool]
|
|
||||||
cc: Kernel[C]
|
|
||||||
|
|
||||||
def __init__(self, c):
|
|
||||||
super().__init__()
|
|
||||||
self.core = Core()
|
|
||||||
self.led0 = TTLOut(self.core, 18)
|
|
||||||
self.led1 = TTLOut(self.core, 19)
|
|
||||||
self.b = 3
|
|
||||||
self.d = False
|
|
||||||
self.cc = c
|
|
||||||
|
|
||||||
@kernel
|
|
||||||
def run(self):
|
|
||||||
print_int32(self.cc.cb.da)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
d = D()
|
|
||||||
print(d.da)
|
|
||||||
c = C(d)
|
|
||||||
print(d.da)
|
|
||||||
print(c.cb.da)
|
|
||||||
|
|
||||||
a = A(c)
|
|
||||||
print(a.a)
|
|
||||||
print(a.b)
|
|
||||||
# print(c.ca) # fail
|
|
||||||
# print(c.cb.da) # fail
|
|
||||||
|
|
||||||
a.run()
|
|
||||||
|
|
||||||
# d = D() # redefine, ok
|
|
||||||
# c = C(d) # redefine, ok
|
|
||||||
|
|
||||||
# print(a.c) # fail
|
|
||||||
# a.c = 2 # fail
|
|
||||||
# a.d = 1 # fail
|
|
||||||
# a.cc = 1 # fail
|
|
||||||
# c.ca = 1 # fail
|
|
||||||
# c.cb = 1 # fail
|
|
||||||
# c.cb.da = 1 # fail
|
|
||||||
# d.da = 1 # fail
|
|
|
@ -1,15 +1,15 @@
|
||||||
from inspect import getfullargspec, isclass, getmro
|
from inspect import getfullargspec
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from numpy import int32, int64
|
from numpy import int32, int64
|
||||||
from typing import Generic, TypeVar, get_origin
|
from typing import Generic, TypeVar
|
||||||
from math import floor, ceil
|
from math import floor, ceil
|
||||||
|
|
||||||
import nac3artiq
|
import nac3artiq
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"KernelInvariant", "Kernel", "virtual",
|
"KernelInvariant", "virtual",
|
||||||
"round64", "floor64", "ceil64",
|
"round64", "floor64", "ceil64",
|
||||||
"extern", "kernel", "portable", "nac3",
|
"extern", "kernel", "portable", "nac3",
|
||||||
"ms", "us", "ns",
|
"ms", "us", "ns",
|
||||||
|
@ -24,13 +24,11 @@ T = TypeVar('T')
|
||||||
class KernelInvariant(Generic[T]):
|
class KernelInvariant(Generic[T]):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class Kernel(Generic[T]):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# The virtual class must exist before nac3artiq.NAC3 is created.
|
# The virtual class must exist before nac3artiq.NAC3 is created.
|
||||||
class virtual(Generic[T]):
|
class virtual(Generic[T]):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def round64(x):
|
def round64(x):
|
||||||
return round(x)
|
return round(x)
|
||||||
|
|
||||||
|
@ -46,7 +44,6 @@ core_arguments = device_db.device_db["core"]["arguments"]
|
||||||
|
|
||||||
compiler = nac3artiq.NAC3(core_arguments["target"])
|
compiler = nac3artiq.NAC3(core_arguments["target"])
|
||||||
allow_registration = True
|
allow_registration = True
|
||||||
allow_kernel_read = False
|
|
||||||
# Delay NAC3 analysis until all referenced variables are supposed to exist on the CPython side.
|
# Delay NAC3 analysis until all referenced variables are supposed to exist on the CPython side.
|
||||||
registered_functions = set()
|
registered_functions = set()
|
||||||
registered_classes = set()
|
registered_classes = set()
|
||||||
|
@ -93,65 +90,7 @@ def nac3(cls):
|
||||||
Decorates a class to be analyzed by NAC3.
|
Decorates a class to be analyzed by NAC3.
|
||||||
All classes containing kernels or portable methods must use this decorator.
|
All classes containing kernels or portable methods must use this decorator.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# python does not allow setting magic method on specific instances
|
|
||||||
# (https://docs.python.org/3/reference/datamodel.html#special-method-lookup).
|
|
||||||
# use this set to keep track of those custom class instances that are
|
|
||||||
# assigned to the `Kernel` fields of a class
|
|
||||||
cls.__nac3_kernel_only_instances__ = set()
|
|
||||||
|
|
||||||
def apply_kernel_only_constraints(val):
|
|
||||||
kernel_only_set = getattr(type(val), '__nac3_kernel_only_instances__', None)
|
|
||||||
if kernel_only_set is None:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
for (_, attr_val) in val.__dict__.items():
|
|
||||||
if not (attr_val == val):
|
|
||||||
apply_kernel_only_constraints(attr_val)
|
|
||||||
kernel_only_set.add(val)
|
|
||||||
|
|
||||||
if not isclass(cls):
|
|
||||||
raise ValueError("nac3 annotation decorator should only be applied to classes")
|
|
||||||
if not cls.__setattr__ in {base.__setattr__ for base in cls.__bases__}:
|
|
||||||
raise ValueError("custom __setattr__ is not supported in kernel classes")
|
|
||||||
|
|
||||||
register_class(cls)
|
register_class(cls)
|
||||||
|
|
||||||
immutable_fields = {
|
|
||||||
n for b in getmro(cls)
|
|
||||||
for (n, ty) in b.__dict__.get('__annotations__', {}).items() if get_origin(ty) == Kernel
|
|
||||||
}
|
|
||||||
def __setattr__(obj, key, value):
|
|
||||||
if obj in type(obj).__nac3_kernel_only_instances__:
|
|
||||||
raise TypeError("attempting to write to kernel only variable")
|
|
||||||
# should allow init to set value, if no attribute then allow to set attr, then
|
|
||||||
# recursively apply constraint to all the fields of that specific object,
|
|
||||||
# regardless of whether they are marked with `Kernel` or not
|
|
||||||
if key in immutable_fields:
|
|
||||||
if hasattr(obj, key):
|
|
||||||
raise TypeError("attempting to write to kernel only variable")
|
|
||||||
else:
|
|
||||||
apply_kernel_only_constraints(value)
|
|
||||||
object.__setattr__(obj, key, value)
|
|
||||||
|
|
||||||
def __getattribute__(obj, key):
|
|
||||||
# need to use `object.__getattribute__` to get attr before checking
|
|
||||||
# the key in immutable_fields for `__init__`.
|
|
||||||
# since that in `__init__` when setting a instance variable like `self.a = 3`
|
|
||||||
# the sequence of internal magic call is still calling cls.__getattribute__(self, 'a')
|
|
||||||
# first, and if only "AttributeError" is raised, it will then call `__setattr__`
|
|
||||||
# if we raise `TypeError` too early, python will just halt at this `TypeError`.
|
|
||||||
attr = object.__getattribute__(obj, key)
|
|
||||||
if not allow_kernel_read:
|
|
||||||
if obj in type(obj).__nac3_kernel_only_instances__:
|
|
||||||
raise TypeError("attempting to read kernel only variable")
|
|
||||||
if key in immutable_fields:
|
|
||||||
raise TypeError("attempting to read kernel only variable")
|
|
||||||
return attr
|
|
||||||
|
|
||||||
cls.__setattr__ = __setattr__
|
|
||||||
cls.__getattribute__ = __getattribute__
|
|
||||||
|
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
@ -203,7 +142,6 @@ class Core:
|
||||||
self.ref_period = core_arguments["ref_period"]
|
self.ref_period = core_arguments["ref_period"]
|
||||||
|
|
||||||
def run(self, method, *args, **kwargs):
|
def run(self, method, *args, **kwargs):
|
||||||
global allow_kernel_read
|
|
||||||
global allow_registration
|
global allow_registration
|
||||||
if allow_registration:
|
if allow_registration:
|
||||||
compiler.analyze(registered_functions, registered_classes)
|
compiler.analyze(registered_functions, registered_classes)
|
||||||
|
@ -215,9 +153,8 @@ class Core:
|
||||||
else:
|
else:
|
||||||
obj = method
|
obj = method
|
||||||
name = ""
|
name = ""
|
||||||
allow_kernel_read = True
|
|
||||||
compiler.compile_method_to_file(obj, name, args, "module.elf")
|
compiler.compile_method_to_file(obj, name, args, "module.elf")
|
||||||
allow_kernel_read = False
|
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def reset(self):
|
def reset(self):
|
||||||
|
|
|
@ -1058,27 +1058,20 @@ impl TopLevelComposer {
|
||||||
let dummy_field_type = unifier.get_fresh_var().0;
|
let dummy_field_type = unifier.get_fresh_var().0;
|
||||||
|
|
||||||
// handle Kernel[T], KernelInvariant[T]
|
// handle Kernel[T], KernelInvariant[T]
|
||||||
let (annotation, mutable) = match &annotation.node {
|
let (annotation, mutable) = {
|
||||||
ast::ExprKind::Subscript { value, slice, .. }
|
let mut result = None;
|
||||||
if matches!(&value.node, ast::ExprKind::Name { id, .. } if id == &"Kernel".into()) =>
|
if let ast::ExprKind::Subscript { value, slice, .. } = &annotation.as_ref().node {
|
||||||
|
|||||||
{
|
if let ast::ExprKind::Name { id, .. } = &value.node {
|
||||||
match &slice.node {
|
result = if id == &"Kernel".into() {
|
||||||
ast::ExprKind::Subscript { value, .. }
|
Some((slice, true))
|
||||||
if matches!(&value.node, ast::ExprKind::Name { id, .. } if id == &"list".into()) =>
|
} else if id == &"KernelInvariant".into() {
|
||||||
{
|
Some((slice, false))
|
||||||
return Err(format!("list is not allowed to be `Kernel` at {}", value.location))
|
} else {
|
||||||
sb10q
commented
This is too restrictive. The restriction is worse than the possibility of bugs from the user mutating the list unexpectedly. This is too restrictive. The restriction is worse than the possibility of bugs from the user mutating the list unexpectedly.
|
|||||||
|
None
|
||||||
}
|
}
|
||||||
_ => (slice, true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast::ExprKind::Subscript { value, slice, .. }
|
result.unwrap_or((annotation, true))
|
||||||
if matches!(&value.node, ast::ExprKind::Name { id, .. } if id == &"KernelInvariant".into()) => {
|
|
||||||
(slice, false)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
eprintln!("attributes not annotated with `Kernel` or `KernelInvariants` at {}", &annotation.location);
|
|
||||||
(annotation, true)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
class_fields_def.push((*attr, dummy_field_type, mutable));
|
class_fields_def.push((*attr, dummy_field_type, mutable));
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Again there is quite some inconsistency with match-by-name vs match-by-id, the
virtual
class seems to be to some extent match-by-id...