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 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)
operands = [value]
if exn is not None:
assert isinstance(exn, BasicBlock)
operands = [value, exn]
else:
operands = [value]
operands.append(exn)
super().__init__(operands, builtins.TNone(), name)
def opcode(self):
@ -1000,6 +999,28 @@ class Raise(Terminator):
if len(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):
"""
A function call operation that supports exception handling.
@ -1051,12 +1072,18 @@ class LandingPad(Terminator):
exception types corresponding to the basic block operands
"""
def __init__(self, name=""):
super().__init__([], builtins.TException(), name)
def __init__(self, cleanup, name=""):
super().__init__([cleanup], builtins.TException(), name)
self.types = []
def opcode(self):
return "landingpad"
def cleanup(self):
return self.operands[0]
def clauses(self):
return zip(self.operands, self.types)
return zip(self.operands[1:], self.types)
def add_clause(self, target, typ):
assert isinstance(target, BasicBlock)
@ -1065,14 +1092,11 @@ class LandingPad(Terminator):
self.types.append(typ.find() if typ is not None else None)
target.uses.add(self)
def opcode(self):
return "landingpad"
def _operands_as_string(self):
table = []
for typ, target in zip(self.types, self.operands):
for target, typ in self.clauses():
if typ is None:
table.append("... => {}".format(target.as_operand()))
else:
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))
def raise_exn(self, exn):
loc_file = ir.Constant(self.current_loc.source_buffer.name, builtins.TStr())
loc_line = ir.Constant(self.current_loc.line(), builtins.TInt(types.TValue(32)))
loc_column = ir.Constant(self.current_loc.column(), builtins.TInt(types.TValue(32)))
self.append(ir.SetAttr(exn, "__file__", loc_file))
self.append(ir.SetAttr(exn, "__line__", loc_line))
self.append(ir.SetAttr(exn, "__col__", loc_column))
if self.unwind_target:
self.append(ir.Raise(exn, self.unwind_target))
if exn is not None:
loc_file = ir.Constant(self.current_loc.source_buffer.name, builtins.TStr())
loc_line = ir.Constant(self.current_loc.line(), builtins.TInt(types.TValue(32)))
loc_column = ir.Constant(self.current_loc.column(), builtins.TInt(types.TValue(32)))
self.append(ir.SetAttr(exn, "__file__", loc_file))
self.append(ir.SetAttr(exn, "__line__", loc_line))
self.append(ir.SetAttr(exn, "__col__", loc_column))
if self.unwind_target is not None:
self.append(ir.Raise(exn, self.unwind_target))
else:
self.append(ir.Raise(exn))
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):
self.raise_exn(self.visit(node.exc))
def visit_Try(self, node):
dispatcher = self.add_block("try.dispatch")
landingpad = dispatcher.append(ir.LandingPad())
if any(node.finalbody):
# k for continuation
@ -532,8 +538,10 @@ class ARTIQIRGenerator(algorithm.Visitor):
self.continue_target = old_continue
self.return_target = old_return
cleanup = self.add_block('handler.cleanup')
landingpad = dispatcher.append(ir.LandingPad(cleanup))
handlers = []
has_catchall = False
for handler_node in node.handlers:
exn_type = handler_node.name_type.find()
if handler_node.filter is not None and \
@ -543,7 +551,6 @@ class ARTIQIRGenerator(algorithm.Visitor):
else:
handler = self.add_block("handler.catchall")
landingpad.add_clause(handler, None)
has_catchall = True
self.current_block = handler
if handler_node.name is not None:
@ -561,9 +568,13 @@ class ARTIQIRGenerator(algorithm.Visitor):
self.visit(node.finalbody)
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")
if any(node.finalbody):
final_targets.append(tail)
final_targets.append(reraise)
if self.break_target:
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.Branch(finalizer))
if not has_catchall:
# Add a catch-all handler so that finally would have a chance
# to execute.
handler = self.add_block("handler.catchall")
landingpad.add_clause(handler, None)
handlers.append((handler, handler))
cleanup.append(ir.SetLocal(final_state, ".k", reraise))
cleanup.append(ir.Branch(finalizer))
for handler, post_handler in handlers:
if not post_handler.is_terminated():
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():
dest = post_finalizer.append(ir.GetLocal(final_state, ".k"))
@ -594,6 +601,8 @@ class ARTIQIRGenerator(algorithm.Visitor):
if not body.is_terminated():
body.append(ir.Branch(tail))
cleanup.append(ir.Reraise(self.unwind_target))
for handler, post_handler in handlers:
if not post_handler.is_terminated():
post_handler.append(ir.Branch(tail))

View File

@ -15,7 +15,9 @@ class DeadCodeEliminator:
def process_function(self, func):
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)
def remove_block(self, block):
@ -25,10 +27,6 @@ class DeadCodeEliminator:
use.remove_incoming_block(block)
if not any(use.operands):
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:
assert False

View File

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

View File

@ -137,13 +137,19 @@ class LLVMIRGenerator:
llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.DoubleType()])
elif name == "printf":
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":
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:
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):
if isinstance(value, (ir.Argument, ir.Instruction, ir.BasicBlock)):
@ -599,12 +605,20 @@ class LLVMIRGenerator:
llinsn.attributes.add('noreturn')
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):
# Layout on return from landing pad: {%_Unwind_Exception*, %Exception*}
lllandingpadty = ll.LiteralStructType([ll.IntType(8).as_pointer(),
ll.IntType(8).as_pointer()])
lllandingpad = self.llbuilder.landingpad(lllandingpadty,
self.llbuiltin("__artiq_personality"))
self.llbuiltin("__artiq_personality"),
cleanup=True)
llrawexn = self.llbuilder.extract_value(lllandingpad, 1)
llexn = self.llbuilder.bitcast(llrawexn, self.llty_of_type(insn.type))
llexnnameptr = self.llbuilder.gep(llexn, [self.llindex(0), self.llindex(0)])
@ -627,7 +641,7 @@ class LLVMIRGenerator:
self.llbuilder.branch(self.map(target))
if self.llbuilder.basic_block.terminator is None:
self.llbuilder.resume(lllandingpad)
self.llbuilder.branch(self.map(insn.cleanup()))
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
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
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
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
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
# 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

View File

@ -62,3 +62,15 @@ def i():
# CHECK-NOT-L: i-out
# CHECK-L: i 30
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 */
#ifndef NDEBUG
#define EH_LOG0(fmt) fprintf(stderr, "__artiq_personality: " fmt "\n")
#define EH_LOG(fmt, ...) fprintf(stderr, "__artiq_personality: " fmt "\n", __VA_ARGS__)
#define EH_LOG0(fmt) fprintf(stderr, "%s: " fmt "\n", __func__)
#define EH_LOG(fmt, ...) fprintf(stderr, "%s: " fmt "\n", __func__, __VA_ARGS__)
#else
#define EH_LOG0(fmt)
#define EH_LOG(fmt, ...)
@ -17,7 +17,7 @@
#define EH_FAIL(err) \
do { \
fprintf(stderr, "__artiq_personality fatal: %s\n", err); \
fprintf(stderr, "%s fatal: %s\n", __func__, err); \
abort(); \
} 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'
static void __artiq_cleanup(_Unwind_Reason_Code reason, struct _Unwind_Exception *exc);
struct artiq_raised_exception {
struct _Unwind_Exception unwind;
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) {
EH_LOG0("===> cleanup");
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.
// But, we clear it to mark it as processed.
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(
int version, _Unwind_Action actions, uint64_t exceptionClass,
struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context) {
@ -345,6 +369,9 @@ _Unwind_Reason_Code __artiq_personality(
if(!(actions & _UA_SEARCH_PHASE)) {
EH_LOG0("=> jumping to landing pad");
if(actions & _UA_HANDLER_FRAME)
inflight->handled = 1;
_Unwind_SetGR(context, __builtin_eh_return_data_regno(0),
(uintptr_t)exceptionObject);
_Unwind_SetGR(context, __builtin_eh_return_data_regno(1),

View File

@ -19,10 +19,15 @@ struct artiq_exception {
extern "C" {
#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));
void __artiq_raise(struct artiq_exception *artiq_exn);
/* Called by the runtime */
void __artiq_terminate(struct artiq_exception *artiq_exn)
__attribute__((noreturn));
#ifdef __cplusplus
}

0
t.py Normal file
View File