From ac5061c205df8fc4a1bce202060924f5c246b44d Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 18 Mar 2016 03:08:14 +0000 Subject: [PATCH] validators.escape: track region of arguments. Fixes #232. --- artiq/compiler/validators/escape.py | 70 ++++++++++++++------- artiq/test/lit/escape/device_db.pyon | 14 +++++ artiq/test/lit/escape/error_mutable_attr.py | 14 +++++ 3 files changed, 77 insertions(+), 21 deletions(-) create mode 100644 artiq/test/lit/escape/device_db.pyon create mode 100644 artiq/test/lit/escape/error_mutable_attr.py diff --git a/artiq/compiler/validators/escape.py b/artiq/compiler/validators/escape.py index 857fcd02a..00df17786 100644 --- a/artiq/compiler/validators/escape.py +++ b/artiq/compiler/validators/escape.py @@ -10,6 +10,17 @@ from .. import asttyped, types, builtins def has_region(typ): return typ.fold(False, lambda accum, typ: accum or builtins.is_allocated(typ)) +class Global: + def __repr__(self): + return "Global()" + +class Argument: + def __init__(self, loc): + self.loc = loc + + def __repr__(self): + return "Argument()" + class Region: """ A last-in-first-out allocation region. Tied to lexical scoping @@ -45,9 +56,9 @@ class Region: self.range = other.range def outlives(lhs, rhs): - if lhs is None: # lhs lives forever + if not isinstance(lhs, Region): # lhs lives nonlexically return True - elif rhs is None: # rhs lives forever, lhs does not + elif not isinstance(rhs, Region): # rhs lives nonlexically, lhs does not return False else: assert not lhs.intersects(rhs) @@ -74,7 +85,7 @@ class RegionOf(algorithm.Visitor): # Then, look at the global region of this module if node.id in self.env_stack[0]: - return None + return Global() assert False @@ -84,14 +95,14 @@ class RegionOf(algorithm.Visitor): if has_region(node.type): return self.youngest_region else: - return None + return Global() visit_BinOpT = visit_sometimes_allocating def visit_CallT(self, node): if types.is_c_function(node.func.type, "cache_get"): # The cache is borrow checked dynamically - return None + return Global() else: self.visit_sometimes_allocating(node) @@ -101,7 +112,7 @@ class RegionOf(algorithm.Visitor): if has_region(node.type): return self.visit(node.value) else: - return None + return Global() visit_AttributeT = visit_accessor visit_SubscriptT = visit_accessor @@ -114,7 +125,7 @@ class RegionOf(algorithm.Visitor): regions.sort(key=functools.cmp_to_key(Region.outlives), reverse=True) return regions[0] else: - return None + return Global() def visit_BoolOpT(self, node): return self.visit_selecting(node.values) @@ -141,7 +152,7 @@ class RegionOf(algorithm.Visitor): # Value lives forever def visit_immutable(self, node): assert not has_region(node.type) - return None + return Global() visit_NameConstantT = visit_immutable visit_NumT = visit_immutable @@ -149,9 +160,12 @@ class RegionOf(algorithm.Visitor): visit_UnaryOpT = visit_immutable visit_CompareT = visit_immutable - # Value is mutable, but still lives forever - def visit_StrT(self, node): - return None + # Value lives forever + def visit_global(self, node): + return Global() + + visit_StrT = visit_global + visit_QuoteT = visit_global # Not implemented def visit_unimplemented(self, node): @@ -168,9 +182,12 @@ class AssignedNamesOf(algorithm.Visitor): on the lhs of assignment, directly or through an accessor. """ - def visit_NameT(self, node): + def visit_name(self, node): return [node] + visit_NameT = visit_name + visit_QuoteT = visit_name + def visit_accessor(self, node): return self.visit(node.value) @@ -190,7 +207,7 @@ class AssignedNamesOf(algorithm.Visitor): class EscapeValidator(algorithm.Visitor): def __init__(self, engine): self.engine = engine - self.youngest_region = None + self.youngest_region = Global() self.env_stack = [] self.youngest_env = None @@ -201,7 +218,7 @@ class EscapeValidator(algorithm.Visitor): return AssignedNamesOf().visit(expr) def _diagnostics_for(self, region, loc, descr="the value of the expression"): - if region: + if isinstance(region, Region): return [ diagnostic.Diagnostic("note", "{descr} is alive from this point...", {"descr": descr}, @@ -210,12 +227,23 @@ class EscapeValidator(algorithm.Visitor): "... to this point", {}, region.range.end()) ] - else: + elif isinstance(region, Global): return [ diagnostic.Diagnostic("note", "{descr} is alive forever", {"descr": descr}, loc) ] + elif isinstance(region, Argument): + return [ + diagnostic.Diagnostic("note", + "{descr} is still alive after this function returns", {"descr": descr}, + loc), + diagnostic.Diagnostic("note", + "{descr} is introduced here as a formal argument", {"descr": descr}, + region.loc) + ] + else: + assert False def visit_in_region(self, node, region, typing_env, args=[]): try: @@ -228,11 +256,11 @@ class EscapeValidator(algorithm.Visitor): for name in typing_env: if has_region(typing_env[name]): if name in args: - self.youngest_env[name] = self.youngest_region + self.youngest_env[name] = args[name] else: self.youngest_env[name] = Region(None) # not yet known else: - self.youngest_env[name] = None # lives forever + self.youngest_env[name] = Global() self.env_stack.append(self.youngest_env) self.generic_visit(node) @@ -247,7 +275,7 @@ class EscapeValidator(algorithm.Visitor): def visit_FunctionDefT(self, node): self.youngest_env[node.name] = self.youngest_region self.visit_in_region(node, Region(node.loc), node.typing_env, - args=node.signature_type.find().arg_names()) + args={ arg.arg: Argument(arg.loc) for arg in node.args.args }) def visit_ClassDefT(self, node): self.youngest_env[node.name] = self.youngest_region @@ -272,10 +300,10 @@ class EscapeValidator(algorithm.Visitor): value_region = self._region_of(value) if not is_aug_assign else self.youngest_region # If this is a variable, we might need to contract the live range. - if value_region is not None: + if isinstance(value_region, Region): for name in self._names_of(target): region = self._region_of(name) - if region is not None: + if isinstance(region, Region): region.contract(value_region) # If we assign to an attribute of a quoted value, there will be no names @@ -315,7 +343,7 @@ class EscapeValidator(algorithm.Visitor): def visit_Return(self, node): region = self._region_of(node.value) - if region: + if isinstance(region, Region): note = diagnostic.Diagnostic("note", "this expression has type {type}", {"type": types.TypePrinter().name(node.value.type)}, diff --git a/artiq/test/lit/escape/device_db.pyon b/artiq/test/lit/escape/device_db.pyon new file mode 100644 index 000000000..02d4e06bb --- /dev/null +++ b/artiq/test/lit/escape/device_db.pyon @@ -0,0 +1,14 @@ +{ + "comm": { + "type": "local", + "module": "artiq.coredevice.comm_dummy", + "class": "Comm", + "arguments": {} + }, + "core": { + "type": "local", + "module": "artiq.coredevice.core", + "class": "Core", + "arguments": {"ref_period": 1e-9} + } +} diff --git a/artiq/test/lit/escape/error_mutable_attr.py b/artiq/test/lit/escape/error_mutable_attr.py new file mode 100644 index 000000000..f626cb24c --- /dev/null +++ b/artiq/test/lit/escape/error_mutable_attr.py @@ -0,0 +1,14 @@ +# RUN: %python -m artiq.compiler.testbench.embedding +diag %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +from artiq.experiment import * + +class c: + x = [] + +cc = c() + +@kernel +def entrypoint(): + # CHECK-L: ${LINE:+1}: error: the assigned value does not outlive the assignment target + cc.x = [1]