Add tests for finally clause and reraising.

This commit is contained in:
whitequark 2015-07-27 12:36:21 +03:00
parent a83e7e2248
commit 2939d4f0f3
20 changed files with 285 additions and 76 deletions

View File

@ -979,15 +979,14 @@ class Raise(Terminator):
""" """
:param value: (:class:`Value`) exception value :param value: (:class:`Value`) exception value
:param exn: (:class:`BasicBlock`) exceptional target :param exn: (:class:`BasicBlock` or None) exceptional target
""" """
def __init__(self, value, exn=None, name=""): def __init__(self, value=None, exn=None, name=""):
assert isinstance(value, Value) assert isinstance(value, Value)
operands = [value]
if exn is not None: if exn is not None:
assert isinstance(exn, BasicBlock) assert isinstance(exn, BasicBlock)
operands = [value, exn] operands.append(exn)
else:
operands = [value]
super().__init__(operands, builtins.TNone(), name) super().__init__(operands, builtins.TNone(), name)
def opcode(self): def opcode(self):
@ -1000,6 +999,28 @@ class Raise(Terminator):
if len(self.operands) > 1: if len(self.operands) > 1:
return self.operands[1] return self.operands[1]
class Reraise(Terminator):
"""
A reraise instruction.
"""
"""
:param exn: (:class:`BasicBlock` or None) exceptional target
"""
def __init__(self, exn=None, name=""):
operands = []
if exn is not None:
assert isinstance(exn, BasicBlock)
operands.append(exn)
super().__init__(operands, builtins.TNone(), name)
def opcode(self):
return "reraise"
def exception_target(self):
if len(self.operands) > 0:
return self.operands[0]
class Invoke(Terminator): class Invoke(Terminator):
""" """
A function call operation that supports exception handling. A function call operation that supports exception handling.
@ -1051,12 +1072,18 @@ class LandingPad(Terminator):
exception types corresponding to the basic block operands exception types corresponding to the basic block operands
""" """
def __init__(self, name=""): def __init__(self, cleanup, name=""):
super().__init__([], builtins.TException(), name) super().__init__([cleanup], builtins.TException(), name)
self.types = [] self.types = []
def opcode(self):
return "landingpad"
def cleanup(self):
return self.operands[0]
def clauses(self): def clauses(self):
return zip(self.operands, self.types) return zip(self.operands[1:], self.types)
def add_clause(self, target, typ): def add_clause(self, target, typ):
assert isinstance(target, BasicBlock) assert isinstance(target, BasicBlock)
@ -1065,14 +1092,11 @@ class LandingPad(Terminator):
self.types.append(typ.find() if typ is not None else None) self.types.append(typ.find() if typ is not None else None)
target.uses.add(self) target.uses.add(self)
def opcode(self):
return "landingpad"
def _operands_as_string(self): def _operands_as_string(self):
table = [] table = []
for typ, target in zip(self.types, self.operands): for target, typ in self.clauses():
if typ is None: if typ is None:
table.append("... => {}".format(target.as_operand())) table.append("... => {}".format(target.as_operand()))
else: else:
table.append("{} => {}".format(types.TypePrinter().name(typ), target.as_operand())) table.append("{} => {}".format(types.TypePrinter().name(typ), target.as_operand()))
return "[{}]".format(", ".join(table)) return "cleanup {}, [{}]".format(self.cleanup().as_operand(), ", ".join(table))

View File

@ -466,23 +466,29 @@ class ARTIQIRGenerator(algorithm.Visitor):
self.append(ir.Branch(self.continue_target)) self.append(ir.Branch(self.continue_target))
def raise_exn(self, exn): def raise_exn(self, exn):
loc_file = ir.Constant(self.current_loc.source_buffer.name, builtins.TStr()) if exn is not None:
loc_line = ir.Constant(self.current_loc.line(), builtins.TInt(types.TValue(32))) loc_file = ir.Constant(self.current_loc.source_buffer.name, builtins.TStr())
loc_column = ir.Constant(self.current_loc.column(), builtins.TInt(types.TValue(32))) loc_line = ir.Constant(self.current_loc.line(), builtins.TInt(types.TValue(32)))
self.append(ir.SetAttr(exn, "__file__", loc_file)) loc_column = ir.Constant(self.current_loc.column(), builtins.TInt(types.TValue(32)))
self.append(ir.SetAttr(exn, "__line__", loc_line)) self.append(ir.SetAttr(exn, "__file__", loc_file))
self.append(ir.SetAttr(exn, "__col__", loc_column)) self.append(ir.SetAttr(exn, "__line__", loc_line))
if self.unwind_target: self.append(ir.SetAttr(exn, "__col__", loc_column))
self.append(ir.Raise(exn, self.unwind_target))
if self.unwind_target is not None:
self.append(ir.Raise(exn, self.unwind_target))
else:
self.append(ir.Raise(exn))
else: else:
self.append(ir.Raise(exn)) if self.unwind_target is not None:
self.append(ir.Reraise(self.unwind_target))
else:
self.append(ir.Reraise())
def visit_Raise(self, node): def visit_Raise(self, node):
self.raise_exn(self.visit(node.exc)) self.raise_exn(self.visit(node.exc))
def visit_Try(self, node): def visit_Try(self, node):
dispatcher = self.add_block("try.dispatch") dispatcher = self.add_block("try.dispatch")
landingpad = dispatcher.append(ir.LandingPad())
if any(node.finalbody): if any(node.finalbody):
# k for continuation # k for continuation
@ -532,8 +538,10 @@ class ARTIQIRGenerator(algorithm.Visitor):
self.continue_target = old_continue self.continue_target = old_continue
self.return_target = old_return self.return_target = old_return
cleanup = self.add_block('handler.cleanup')
landingpad = dispatcher.append(ir.LandingPad(cleanup))
handlers = [] handlers = []
has_catchall = False
for handler_node in node.handlers: for handler_node in node.handlers:
exn_type = handler_node.name_type.find() exn_type = handler_node.name_type.find()
if handler_node.filter is not None and \ if handler_node.filter is not None and \
@ -543,7 +551,6 @@ class ARTIQIRGenerator(algorithm.Visitor):
else: else:
handler = self.add_block("handler.catchall") handler = self.add_block("handler.catchall")
landingpad.add_clause(handler, None) landingpad.add_clause(handler, None)
has_catchall = True
self.current_block = handler self.current_block = handler
if handler_node.name is not None: if handler_node.name is not None:
@ -561,9 +568,13 @@ class ARTIQIRGenerator(algorithm.Visitor):
self.visit(node.finalbody) self.visit(node.finalbody)
post_finalizer = self.current_block post_finalizer = self.current_block
reraise = self.add_block('try.reraise')
reraise.append(ir.Reraise(self.unwind_target))
self.current_block = tail = self.add_block("try.tail") self.current_block = tail = self.add_block("try.tail")
if any(node.finalbody): if any(node.finalbody):
final_targets.append(tail) final_targets.append(tail)
final_targets.append(reraise)
if self.break_target: if self.break_target:
break_proxy.append(ir.Branch(finalizer)) break_proxy.append(ir.Branch(finalizer))
@ -575,17 +586,13 @@ class ARTIQIRGenerator(algorithm.Visitor):
body.append(ir.SetLocal(final_state, ".k", tail)) body.append(ir.SetLocal(final_state, ".k", tail))
body.append(ir.Branch(finalizer)) body.append(ir.Branch(finalizer))
if not has_catchall: cleanup.append(ir.SetLocal(final_state, ".k", reraise))
# Add a catch-all handler so that finally would have a chance cleanup.append(ir.Branch(finalizer))
# to execute.
handler = self.add_block("handler.catchall")
landingpad.add_clause(handler, None)
handlers.append((handler, handler))
for handler, post_handler in handlers: for handler, post_handler in handlers:
if not post_handler.is_terminated(): if not post_handler.is_terminated():
post_handler.append(ir.SetLocal(final_state, ".k", tail)) post_handler.append(ir.SetLocal(final_state, ".k", tail))
post_handler.append(ir.Branch(tail)) post_handler.append(ir.Branch(finalizer))
if not post_finalizer.is_terminated(): if not post_finalizer.is_terminated():
dest = post_finalizer.append(ir.GetLocal(final_state, ".k")) dest = post_finalizer.append(ir.GetLocal(final_state, ".k"))
@ -594,6 +601,8 @@ class ARTIQIRGenerator(algorithm.Visitor):
if not body.is_terminated(): if not body.is_terminated():
body.append(ir.Branch(tail)) body.append(ir.Branch(tail))
cleanup.append(ir.Reraise(self.unwind_target))
for handler, post_handler in handlers: for handler, post_handler in handlers:
if not post_handler.is_terminated(): if not post_handler.is_terminated():
post_handler.append(ir.Branch(tail)) post_handler.append(ir.Branch(tail))

View File

@ -15,7 +15,9 @@ class DeadCodeEliminator:
def process_function(self, func): def process_function(self, func):
for block in func.basic_blocks: for block in func.basic_blocks:
if not any(block.predecessors()) and block != func.entry(): if not any(block.predecessors()) and \
not any([isinstance(use, ir.SetLocal) for use in block.uses]) and \
block != func.entry():
self.remove_block(block) self.remove_block(block)
def remove_block(self, block): def remove_block(self, block):
@ -25,10 +27,6 @@ class DeadCodeEliminator:
use.remove_incoming_block(block) use.remove_incoming_block(block)
if not any(use.operands): if not any(use.operands):
self.remove_instruction(use) self.remove_instruction(use)
elif isinstance(use, ir.SetLocal):
# Setting the target for `finally` resumption, e.g.
# setlocal(.k) %v.4, label %try.doreturn
use.erase()
else: else:
assert False assert False

View File

@ -956,13 +956,14 @@ class Inferencer(algorithm.Visitor):
def visit_Raise(self, node): def visit_Raise(self, node):
self.generic_visit(node) self.generic_visit(node)
exc_type = node.exc.type if node.exc is not None:
if not types.is_var(exc_type) and not builtins.is_exception(exc_type): exc_type = node.exc.type
diag = diagnostic.Diagnostic("error", if not types.is_var(exc_type) and not builtins.is_exception(exc_type):
"cannot raise a value of type {type}, which is not an exception", diag = diagnostic.Diagnostic("error",
{"type": types.TypePrinter().name(exc_type)}, "cannot raise a value of type {type}, which is not an exception",
node.exc.loc) {"type": types.TypePrinter().name(exc_type)},
self.engine.process(diag) node.exc.loc)
self.engine.process(diag)
def visit_Assert(self, node): def visit_Assert(self, node):
self.generic_visit(node) self.generic_visit(node)

View File

@ -137,13 +137,19 @@ class LLVMIRGenerator:
llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.DoubleType()]) llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.DoubleType()])
elif name == "printf": elif name == "printf":
llty = ll.FunctionType(ll.VoidType(), [ll.IntType(8).as_pointer()], var_arg=True) llty = ll.FunctionType(ll.VoidType(), [ll.IntType(8).as_pointer()], var_arg=True)
elif name == "__artiq_raise":
llty = ll.FunctionType(ll.VoidType(), [self.llty_of_type(builtins.TException())])
elif name == "__artiq_personality": elif name == "__artiq_personality":
llty = ll.FunctionType(ll.IntType(32), [], var_arg=True) llty = ll.FunctionType(ll.IntType(32), [], var_arg=True)
elif name == "__artiq_raise":
llty = ll.FunctionType(ll.VoidType(), [self.llty_of_type(builtins.TException())])
elif name == "__artiq_reraise":
llty = ll.FunctionType(ll.VoidType(), [])
else: else:
assert False assert False
return ll.Function(self.llmodule, llty, name)
llfun = ll.Function(self.llmodule, llty, name)
if name in ("__artiq_raise", "__artiq_reraise", "llvm.trap"):
llfun.attributes.add("noreturn")
return llfun
def map(self, value): def map(self, value):
if isinstance(value, (ir.Argument, ir.Instruction, ir.BasicBlock)): if isinstance(value, (ir.Argument, ir.Instruction, ir.BasicBlock)):
@ -599,12 +605,20 @@ class LLVMIRGenerator:
llinsn.attributes.add('noreturn') llinsn.attributes.add('noreturn')
return llinsn return llinsn
def process_Reraise(self, insn):
llinsn = self.llbuilder.call(self.llbuiltin("__artiq_reraise"), [],
name=insn.name)
llinsn.attributes.add('noreturn')
self.llbuilder.unreachable()
return llinsn
def process_LandingPad(self, insn): def process_LandingPad(self, insn):
# Layout on return from landing pad: {%_Unwind_Exception*, %Exception*} # Layout on return from landing pad: {%_Unwind_Exception*, %Exception*}
lllandingpadty = ll.LiteralStructType([ll.IntType(8).as_pointer(), lllandingpadty = ll.LiteralStructType([ll.IntType(8).as_pointer(),
ll.IntType(8).as_pointer()]) ll.IntType(8).as_pointer()])
lllandingpad = self.llbuilder.landingpad(lllandingpadty, lllandingpad = self.llbuilder.landingpad(lllandingpadty,
self.llbuiltin("__artiq_personality")) self.llbuiltin("__artiq_personality"),
cleanup=True)
llrawexn = self.llbuilder.extract_value(lllandingpad, 1) llrawexn = self.llbuilder.extract_value(lllandingpad, 1)
llexn = self.llbuilder.bitcast(llrawexn, self.llty_of_type(insn.type)) llexn = self.llbuilder.bitcast(llrawexn, self.llty_of_type(insn.type))
llexnnameptr = self.llbuilder.gep(llexn, [self.llindex(0), self.llindex(0)]) llexnnameptr = self.llbuilder.gep(llexn, [self.llindex(0), self.llindex(0)])
@ -627,7 +641,7 @@ class LLVMIRGenerator:
self.llbuilder.branch(self.map(target)) self.llbuilder.branch(self.map(target))
if self.llbuilder.basic_block.terminator is None: if self.llbuilder.basic_block.terminator is None:
self.llbuilder.resume(lllandingpad) self.llbuilder.branch(self.map(insn.cleanup()))
return llexn return llexn

View File

@ -1,4 +1,5 @@
# RUN: %python -m artiq.compiler.testbench.jit %s # RUN: %python -m artiq.compiler.testbench.jit %s >%t
# RUN: OutputCheck %s --file-to-check=%t
# REQUIRES: exceptions # REQUIRES: exceptions
try: try:

View File

@ -1,4 +1,5 @@
# RUN: %python -m artiq.compiler.testbench.jit %s # RUN: %python -m artiq.compiler.testbench.jit %s >%t
# RUN: OutputCheck %s --file-to-check=%t
# REQUIRES: exceptions # REQUIRES: exceptions
def catch(f): def catch(f):

View File

@ -1,4 +1,5 @@
# RUN: %python -m artiq.compiler.testbench.jit %s # RUN: %python -m artiq.compiler.testbench.jit %s >%t
# RUN: OutputCheck %s --file-to-check=%t
# REQUIRES: exceptions # REQUIRES: exceptions
def catch(f): def catch(f):

View File

@ -1,4 +1,5 @@
# RUN: %python -m artiq.compiler.testbench.jit %s # RUN: %python -m artiq.compiler.testbench.jit %s >%t
# RUN: OutputCheck %s --file-to-check=%t
# REQUIRES: exceptions # REQUIRES: exceptions
def f(): def f():

View File

@ -0,0 +1,21 @@
# RUN: %python -m artiq.compiler.testbench.jit %s >%t
# RUN: OutputCheck %s --file-to-check=%t
# REQUIRES: exceptions
def f():
try:
1/0
finally:
print("f-fin")
print("f-out")
def g():
try:
f()
except:
print("g-except")
# CHECK-L: f-fin
# CHECK-NOT-L: f-out
# CHECK-L: g-except
g()

View File

@ -0,0 +1,17 @@
# RUN: %python -m artiq.compiler.testbench.jit %s >%t
# RUN: OutputCheck %s --file-to-check=%t
# REQUIRES: exceptions
def f():
try:
1/0
except:
print("f-except")
finally:
print("f-fin")
print("f-out")
# CHECK-L: f-except
# CHECK-L: f-fin
# CHECK-L: f-out
f()

View File

@ -0,0 +1,23 @@
# RUN: %python -m artiq.compiler.testbench.jit %s >%t
# RUN: OutputCheck %s --file-to-check=%t
# REQUIRES: exceptions
def f():
try:
1/0
finally:
print("f-fin")
raise ValueError()
def g():
try:
f()
except ZeroDivisionError:
print("g-except-zde")
except ValueError:
print("g-except-ve")
# CHECK-L: f-fin
# CHECK-L: g-except-ve
# CHECK-NOT-L: g-except-zde
g()

View File

@ -0,0 +1,21 @@
# RUN: %python -m artiq.compiler.testbench.jit %s >%t
# RUN: OutputCheck %s --file-to-check=%t
# REQUIRES: exceptions
def f():
try:
1/0
finally:
print("f-fin")
return
def g():
try:
f()
except:
print("g-except")
# CHECK-L: f-fin
# CHECK-NOT-L: f-out
# CHECK-NOT-L: g-except
g()

View File

@ -0,0 +1,16 @@
# RUN: %not %python -m artiq.compiler.testbench.jit %s >%t
# RUN: OutputCheck %s --file-to-check=%t
# REQUIRES: exceptions
def f():
# CHECK-L: Uncaught ZeroDivisionError
# CHECK-L: at input.py:${LINE:+1}:
1/0
def g():
try:
f()
except:
raise
g()

View File

@ -0,0 +1,16 @@
# RUN: %not %python -m artiq.compiler.testbench.jit %s >%t
# RUN: OutputCheck %s --file-to-check=%t
# REQUIRES: exceptions
def f():
1/0
def g():
try:
f()
except Exception as e:
# CHECK-L: Uncaught ZeroDivisionError
# CHECK-L: at input.py:${LINE:+1}:
raise e
g()

View File

@ -1,6 +1,7 @@
# RUN: %not %python -m artiq.compiler.testbench.jit %s # RUN: %not %python -m artiq.compiler.testbench.jit %s >%t
# RUN: OutputCheck %s --file-to-check=%t
# REQUIRES: exceptions # REQUIRES: exceptions
# CHECK-L: Uncaught ZeroDivisionError: cannot divide by zero (0, 0, 0) # CHECK-L: Uncaught ZeroDivisionError: cannot divide by zero (0, 0, 0)
# CHECK-L: at input.py:${LINE:+1}:0 # CHECK-L: at input.py:${LINE:+1}:
1/0 1/0

View File

@ -62,3 +62,15 @@ def i():
# CHECK-NOT-L: i-out # CHECK-NOT-L: i-out
# CHECK-L: i 30 # CHECK-L: i 30
print("i", i()) print("i", i())
def j():
try:
print("j-try")
finally:
print("j-finally")
print("j-out")
# CHECK-L: j-try
# CHECK-L: j-finally
# CHECK-L: j-out
print("j", j())

View File

@ -8,8 +8,8 @@
/* Logging */ /* Logging */
#ifndef NDEBUG #ifndef NDEBUG
#define EH_LOG0(fmt) fprintf(stderr, "__artiq_personality: " fmt "\n") #define EH_LOG0(fmt) fprintf(stderr, "%s: " fmt "\n", __func__)
#define EH_LOG(fmt, ...) fprintf(stderr, "__artiq_personality: " fmt "\n", __VA_ARGS__) #define EH_LOG(fmt, ...) fprintf(stderr, "%s: " fmt "\n", __func__, __VA_ARGS__)
#else #else
#define EH_LOG0(fmt) #define EH_LOG0(fmt)
#define EH_LOG(fmt, ...) #define EH_LOG(fmt, ...)
@ -17,7 +17,7 @@
#define EH_FAIL(err) \ #define EH_FAIL(err) \
do { \ do { \
fprintf(stderr, "__artiq_personality fatal: %s\n", err); \ fprintf(stderr, "%s fatal: %s\n", __func__, err); \
abort(); \ abort(); \
} while(0) } while(0)
@ -195,37 +195,61 @@ static uintptr_t readEncodedPointer(const uint8_t **data, uint8_t encoding) {
} }
/* Raising and catching */ /* Raising */
#define ARTIQ_EXCEPTION_CLASS 0x4152545141525451LL // 'ARTQARTQ' #define ARTIQ_EXCEPTION_CLASS 0x4152545141525451LL // 'ARTQARTQ'
static void __artiq_cleanup(_Unwind_Reason_Code reason, struct _Unwind_Exception *exc);
struct artiq_raised_exception { struct artiq_raised_exception {
struct _Unwind_Exception unwind; struct _Unwind_Exception unwind;
struct artiq_exception artiq; struct artiq_exception artiq;
int handled;
}; };
static struct artiq_raised_exception inflight;
void __artiq_raise(struct artiq_exception *artiq_exn) {
EH_LOG("===> raise (name=%s)", artiq_exn->name);
memmove(&inflight.artiq, artiq_exn, sizeof(struct artiq_exception));
inflight.unwind.exception_class = ARTIQ_EXCEPTION_CLASS;
inflight.unwind.exception_cleanup = &__artiq_cleanup;
inflight.handled = 0;
_Unwind_Reason_Code result = _Unwind_RaiseException(&inflight.unwind);
EH_ASSERT((result == _URC_END_OF_STACK) &&
"Unexpected error during unwinding");
// If we're here, there are no handlers, only cleanups.
__artiq_terminate(&inflight.artiq);
}
void __artiq_reraise() {
if(inflight.handled) {
EH_LOG0("===> reraise");
__artiq_raise(&inflight.artiq);
} else {
EH_LOG0("===> resume");
EH_ASSERT((inflight.artiq.typeinfo != 0) &&
"Need an exception to reraise");
_Unwind_Resume(&inflight.unwind);
abort();
}
}
/* Catching */
// The code below does not refer to the `inflight` global.
static void __artiq_cleanup(_Unwind_Reason_Code reason, struct _Unwind_Exception *exc) { static void __artiq_cleanup(_Unwind_Reason_Code reason, struct _Unwind_Exception *exc) {
EH_LOG0("===> cleanup");
struct artiq_raised_exception *inflight = (struct artiq_raised_exception*) exc; struct artiq_raised_exception *inflight = (struct artiq_raised_exception*) exc;
// The in-flight exception is statically allocated, so we don't need to free it. // The in-flight exception is statically allocated, so we don't need to free it.
// But, we clear it to mark it as processed. // But, we clear it to mark it as processed.
memset(&inflight->artiq, 0, sizeof(struct artiq_exception)); memset(&inflight->artiq, 0, sizeof(struct artiq_exception));
} }
void __artiq_raise(struct artiq_exception *artiq_exn) {
static struct artiq_raised_exception inflight;
memcpy(&inflight.artiq, artiq_exn, sizeof(struct artiq_exception));
inflight.unwind.exception_class = ARTIQ_EXCEPTION_CLASS;
inflight.unwind.exception_cleanup = &__artiq_cleanup;
_Unwind_Reason_Code result = _Unwind_RaiseException(&inflight.unwind);
if(result == _URC_END_OF_STACK) {
__artiq_terminate(&inflight.artiq);
} else {
fprintf(stderr, "__artiq_raise: unexpected error (%d)\n", result);
abort();
}
}
_Unwind_Reason_Code __artiq_personality( _Unwind_Reason_Code __artiq_personality(
int version, _Unwind_Action actions, uint64_t exceptionClass, int version, _Unwind_Action actions, uint64_t exceptionClass,
struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context) { struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context) {
@ -345,6 +369,9 @@ _Unwind_Reason_Code __artiq_personality(
if(!(actions & _UA_SEARCH_PHASE)) { if(!(actions & _UA_SEARCH_PHASE)) {
EH_LOG0("=> jumping to landing pad"); EH_LOG0("=> jumping to landing pad");
if(actions & _UA_HANDLER_FRAME)
inflight->handled = 1;
_Unwind_SetGR(context, __builtin_eh_return_data_regno(0), _Unwind_SetGR(context, __builtin_eh_return_data_regno(0),
(uintptr_t)exceptionObject); (uintptr_t)exceptionObject);
_Unwind_SetGR(context, __builtin_eh_return_data_regno(1), _Unwind_SetGR(context, __builtin_eh_return_data_regno(1),

View File

@ -19,10 +19,15 @@ struct artiq_exception {
extern "C" { extern "C" {
#endif #endif
void __artiq_terminate(struct artiq_exception *artiq_exn) /* Provided by the runtime */
void __artiq_raise(struct artiq_exception *artiq_exn)
__attribute__((noreturn));
void __artiq_reraise()
__attribute__((noreturn)); __attribute__((noreturn));
void __artiq_raise(struct artiq_exception *artiq_exn); /* Called by the runtime */
void __artiq_terminate(struct artiq_exception *artiq_exn)
__attribute__((noreturn));
#ifdef __cplusplus #ifdef __cplusplus
} }

0
t.py Normal file
View File