Kernel only attribute annotation (#76) #127

Merged
sb10q merged 8 commits from kernel_only_annotation into master 2021-12-19 11:04:53 +08:00
3 changed files with 16 additions and 159 deletions
Showing only changes of commit 39220e8354 - Show all commits

View File

@ -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

View File

@ -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):

View File

@ -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 {
Outdated
Review

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...

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...
{ 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 {
Outdated
Review

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));