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:
David Nadlinger 2023-10-08 14:10:00 +01:00 committed by Sébastien Bourdeauducq
parent 7ab52af603
commit 08eea09d44
5 changed files with 62 additions and 20 deletions

View File

@ -102,8 +102,20 @@ class RegionOf(algorithm.Visitor):
if types.is_external_function(node.func.type, "cache_get"):
# The cache is borrow checked dynamically
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,
# or else forever

View 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()

View 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()

View 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()

View File

@ -95,24 +95,6 @@ tracked across function calls (see `#1497 <https://github.com/m-labs/artiq/issue
def run(self):
# results in memory corruption
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.