Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

compiler: Catch escaping numpy.{array, full, transpose}() results #2239

Merged
merged 1 commit into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions artiq/compiler/validators/escape.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,20 @@ def visit_CallT(self, node):
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
Expand Down
15 changes: 15 additions & 0 deletions artiq/test/lit/escape/error_numpy_array.py
Original file line number Diff line number Diff line change
@@ -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 changes: 16 additions & 0 deletions artiq/test/lit/escape/error_numpy_full.py
Original file line number Diff line number Diff line change
@@ -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 changes: 17 additions & 0 deletions artiq/test/lit/escape/error_numpy_transpose.py
Original file line number Diff line number Diff line change
@@ -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()
18 changes: 0 additions & 18 deletions doc/manual/compiler.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down