mirror of
https://github.com/m-labs/artiq.git
synced 2024-12-22 18:04:03 +08:00
compiler: Catch escaping numpy.{array, full, transpose}() results
Function calls in general can still be used to hide escaping allocations from the compiler (issue #1497), but these calls in particular always allocate, so we can easily and accurately handle them.
This commit is contained in:
parent
7ab52af603
commit
08eea09d44
artiq
compiler/validators
test/lit/escape
doc/manual
@ -102,8 +102,20 @@ class RegionOf(algorithm.Visitor):
|
|||||||
if types.is_external_function(node.func.type, "cache_get"):
|
if types.is_external_function(node.func.type, "cache_get"):
|
||||||
# The cache is borrow checked dynamically
|
# The cache is borrow checked dynamically
|
||||||
return Global()
|
return Global()
|
||||||
else:
|
|
||||||
self.visit_sometimes_allocating(node)
|
if (types.is_builtin_function(node.func.type, "array")
|
||||||
|
or types.is_builtin_function(node.func.type, "make_array")
|
||||||
|
or types.is_builtin_function(node.func.type, "numpy.transpose")):
|
||||||
|
# While lifetime tracking across function calls in general is currently
|
||||||
|
# broken (see below), these special builtins that allocate an array on
|
||||||
|
# the stack of the caller _always_ allocate regardless of the parameters,
|
||||||
|
# and we can thus handle them without running into the precision issue
|
||||||
|
# mentioned in commit ae999db.
|
||||||
|
return self.visit_allocating(node)
|
||||||
|
|
||||||
|
# FIXME: Return statement missing here, but see m-labs/artiq#1497 and
|
||||||
|
# commit ae999db.
|
||||||
|
self.visit_sometimes_allocating(node)
|
||||||
|
|
||||||
# Value lives as long as the object/container, if it's mutable,
|
# Value lives as long as the object/container, if it's mutable,
|
||||||
# or else forever
|
# or else forever
|
||||||
|
15
artiq/test/lit/escape/error_numpy_array.py
Normal file
15
artiq/test/lit/escape/error_numpy_array.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t
|
||||||
|
# RUN: OutputCheck %s --file-to-check=%t
|
||||||
|
|
||||||
|
from artiq.experiment import *
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def a():
|
||||||
|
# CHECK-L: ${LINE:+2}: error: cannot return an allocated value that does not live forever
|
||||||
|
# CHECK-L: ${LINE:+1}: note: ... to this point
|
||||||
|
return np.array([0, 1])
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def entrypoint():
|
||||||
|
a()
|
16
artiq/test/lit/escape/error_numpy_full.py
Normal file
16
artiq/test/lit/escape/error_numpy_full.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t
|
||||||
|
# RUN: OutputCheck %s --file-to-check=%t
|
||||||
|
|
||||||
|
from artiq.experiment import *
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def a():
|
||||||
|
# CHECK-L: ${LINE:+2}: error: cannot return an allocated value that does not live forever
|
||||||
|
# CHECK-L: ${LINE:+1}: note: ... to this point
|
||||||
|
return np.full(10, 42.0)
|
||||||
|
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def entrypoint():
|
||||||
|
a()
|
17
artiq/test/lit/escape/error_numpy_transpose.py
Normal file
17
artiq/test/lit/escape/error_numpy_transpose.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t
|
||||||
|
# RUN: OutputCheck %s --file-to-check=%t
|
||||||
|
|
||||||
|
from artiq.experiment import *
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
data = np.array([[0, 1], [2, 3]])
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def a():
|
||||||
|
# CHECK-L: ${LINE:+2}: error: cannot return an allocated value that does not live forever
|
||||||
|
# CHECK-L: ${LINE:+1}: note: ... to this point
|
||||||
|
return np.transpose(data)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def entrypoint():
|
||||||
|
a()
|
@ -95,24 +95,6 @@ tracked across function calls (see `#1497 <https://github.com/m-labs/artiq/issue
|
|||||||
def run(self):
|
def run(self):
|
||||||
# results in memory corruption
|
# results in memory corruption
|
||||||
return func([1, 2, 3])
|
return func([1, 2, 3])
|
||||||
or if the return value is obfuscated by an if-statement like here: ::
|
|
||||||
|
|
||||||
class ProblemReturn2(EnvExperiment):
|
|
||||||
def build(self):
|
|
||||||
self.setattr_device("core")
|
|
||||||
|
|
||||||
@kernel
|
|
||||||
def meth(self):
|
|
||||||
# if statement for obfuscation
|
|
||||||
if self.core.get_rtio_counter_mu() % 2:
|
|
||||||
return np.array([1,2,3])
|
|
||||||
else:
|
|
||||||
return np.array([4,5,6])
|
|
||||||
|
|
||||||
@kernel
|
|
||||||
def run(self):
|
|
||||||
# also results in memory corrption
|
|
||||||
return self.meth()
|
|
||||||
|
|
||||||
This results in memory corruption at runtime.
|
This results in memory corruption at runtime.
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user