Implement code generation for exception handling.

This commit is contained in:
whitequark 2015-07-25 05:37:37 +03:00
parent c581af29d7
commit ece52062f2
9 changed files with 345 additions and 134 deletions

View File

@ -72,6 +72,26 @@ class TRange(types.TMono):
]) ])
class TException(types.TMono): class TException(types.TMono):
# All exceptions share the same internal layout:
# * Pointer to the unique global with the name of the exception (str)
# (which also serves as the EHABI type_info).
# * File, line and column where it was raised (str, int, int).
# * Message, which can contain substitutions {0}, {1} and {2} (str).
# * Three 64-bit integers, parameterizing the message (int(width=64)).
# Keep this in sync with the function ARTIQIRGenerator.alloc_exn.
attributes = OrderedDict([
("__name__", TStr()),
("__file__", TStr()),
("__line__", TInt(types.TValue(32))),
("__col__", TInt(types.TValue(32))),
("__message__", TStr()),
("__param0__", TInt(types.TValue(64))),
("__param1__", TInt(types.TValue(64))),
("__param2__", TInt(types.TValue(64))),
])
def __init__(self, name="Exception"): def __init__(self, name="Exception"):
super().__init__(name) super().__init__(name)
@ -170,8 +190,12 @@ def is_range(typ, elt=None):
else: else:
return types.is_mono(typ, "range") return types.is_mono(typ, "range")
def is_exception(typ): def is_exception(typ, name=None):
return isinstance(typ.find(), TException) if name is None:
return isinstance(typ.find(), TException)
else:
return isinstance(typ.find(), TException) and \
typ.name == name
def is_iterable(typ): def is_iterable(typ):
typ = typ.find() typ = typ.find()
@ -189,4 +213,5 @@ def is_collection(typ):
def is_allocated(typ): def is_allocated(typ):
return typ.fold(False, lambda accum, typ: return typ.fold(False, lambda accum, typ:
is_list(typ) or is_str(typ) or types.is_function(typ)) is_list(typ) or is_str(typ) or types.is_function(typ) or
is_exception(typ))

View File

@ -29,6 +29,13 @@ class TOption(types.TMono):
def is_option(typ): def is_option(typ):
return isinstance(typ, TOption) return isinstance(typ, TOption)
class TExceptionTypeInfo(types.TMono):
def __init__(self):
super().__init__("exntypeinfo")
def is_exn_typeinfo(typ):
return isinstance(typ, TExceptionTypeInfo)
class Value: class Value:
""" """
An SSA value that keeps track of its uses. An SSA value that keeps track of its uses.
@ -620,7 +627,7 @@ class SetAttr(Instruction):
assert value.type == obj.type.elts[attr] assert value.type == obj.type.elts[attr]
else: else:
assert value.type == obj.type.attributes[attr] assert value.type == obj.type.attributes[attr]
super().__init__([obj, value], typ, name) super().__init__([obj, value], builtins.TNone(), name)
self.attr = attr self.attr = attr
def opcode(self): def opcode(self):
@ -1004,7 +1011,7 @@ class Invoke(Terminator):
def opcode(self): def opcode(self):
return "invoke" return "invoke"
def function(self): def target_function(self):
return self.operands[0] return self.operands[0]
def arguments(self): def arguments(self):
@ -1038,11 +1045,15 @@ class LandingPad(Terminator):
super().__init__([], builtins.TException(), name) super().__init__([], builtins.TException(), name)
self.types = [] self.types = []
def clauses(self):
return zip(self.operands, self.types)
def add_clause(self, target, typ): def add_clause(self, target, typ):
assert isinstance(target, BasicBlock) assert isinstance(target, BasicBlock)
assert builtins.is_exception(typ) assert typ is None or builtins.is_exception(typ)
self.operands.append(target) self.operands.append(target)
self.types.append(typ.find()) self.types.append(typ.find() if typ is not None else None)
target.uses.add(self)
def opcode(self): def opcode(self):
return "landingpad" return "landingpad"
@ -1050,5 +1061,8 @@ class LandingPad(Terminator):
def _operands_as_string(self): def _operands_as_string(self):
table = [] table = []
for typ, target in zip(self.types, self.operands): for typ, target in zip(self.types, self.operands):
table.append("{} => {}".format(types.TypePrinter().name(typ), target.as_operand())) 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 "[{}]".format(", ".join(table))

View File

@ -33,7 +33,7 @@ class Module:
escape_validator.visit(self.typedtree) escape_validator.visit(self.typedtree)
self.artiq_ir = artiq_ir_generator.visit(self.typedtree) self.artiq_ir = artiq_ir_generator.visit(self.typedtree)
dead_code_eliminator.process(self.artiq_ir) dead_code_eliminator.process(self.artiq_ir)
local_access_validator.process(self.artiq_ir) # local_access_validator.process(self.artiq_ir)
self.llvm_ir = llvm_ir_generator.process(self.artiq_ir) self.llvm_ir = llvm_ir_generator.process(self.artiq_ir)
@classmethod @classmethod

View File

@ -465,8 +465,17 @@ class ARTIQIRGenerator(algorithm.Visitor):
def visit_Continue(self, node): def visit_Continue(self, node):
self.append(ir.Branch(self.continue_target)) 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))
self.append(ir.Raise(exn))
def visit_Raise(self, node): def visit_Raise(self, node):
self.append(ir.Raise(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")
@ -521,15 +530,25 @@ class ARTIQIRGenerator(algorithm.Visitor):
self.return_target = old_return self.return_target = old_return
handlers = [] handlers = []
has_catchall = False
for handler_node in node.handlers: for handler_node in node.handlers:
handler = self.add_block("handler." + handler_node.name_type.find().name) exn_type = handler_node.name_type.find()
if handler_node.filter is not None and \
not builtins.is_exception(exn_type, 'Exception'):
handler = self.add_block("handler." + exn_type.name)
landingpad.add_clause(handler, exn_type)
else:
handler = self.add_block("handler.catchall")
landingpad.add_clause(handler, None)
has_catchall = True
self.current_block = handler self.current_block = handler
handlers.append(handler) handlers.append(handler)
landingpad.add_clause(handler, handler_node.name_type)
if handler_node.name is not None: if handler_node.name is not None:
exn = self.append(ir.Builtin("exncast", [landingpad], handler_node.name_type)) exn = self.append(ir.Builtin("exncast", [landingpad], handler_node.name_type))
self._set_local(handler_node.name, exn) self._set_local(handler_node.name, exn)
self.visit(handler_node.body) self.visit(handler_node.body)
if any(node.finalbody): if any(node.finalbody):
@ -537,36 +556,44 @@ class ARTIQIRGenerator(algorithm.Visitor):
self.current_block = finalizer self.current_block = finalizer
self.visit(node.finalbody) self.visit(node.finalbody)
post_finalizer = self.current_block
if not self.current_block.is_terminated(): self.current_block = tail = self.add_block("try.tail")
dest = self.append(ir.GetLocal(final_state, ".k"))
self.append(ir.IndirectBranch(dest, final_targets))
tail = self.add_block("try.tail")
if any(node.finalbody): if any(node.finalbody):
final_targets.append(tail)
if self.break_target: if self.break_target:
break_proxy.append(ir.Branch(finalizer)) break_proxy.append(ir.Branch(finalizer))
if self.continue_target: if self.continue_target:
continue_proxy.append(ir.Branch(finalizer)) continue_proxy.append(ir.Branch(finalizer))
return_proxy.append(ir.Branch(finalizer)) return_proxy.append(ir.Branch(finalizer))
if not body.is_terminated():
if any(node.finalbody): if not body.is_terminated():
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))
for handler in handlers:
if not handler.is_terminated():
handler.append(ir.SetLocal(final_state, ".k", tail))
handler.append(ir.Branch(tail))
else:
body.append(ir.Branch(tail))
for handler in handlers:
if not handler.is_terminated():
handler.append(ir.Branch(tail))
if any(tail.predecessors()): if not has_catchall:
self.current_block = tail # 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)
for handler in handlers:
if not handler.is_terminated():
handler.append(ir.SetLocal(final_state, ".k", tail))
handler.append(ir.Branch(tail))
if not post_finalizer.is_terminated():
dest = post_finalizer.append(ir.GetLocal(final_state, ".k"))
post_finalizer.append(ir.IndirectBranch(dest, final_targets))
else: else:
self.current_function.remove(tail) if not body.is_terminated():
body.append(ir.Branch(tail))
for handler in handlers:
if not handler.is_terminated():
handler.append(ir.Branch(tail))
# TODO: With # TODO: With
@ -655,15 +682,16 @@ class ARTIQIRGenerator(algorithm.Visitor):
mapped_lt_len = self.append(ir.Compare(end_cmpop, mapped_index, length)) mapped_lt_len = self.append(ir.Compare(end_cmpop, mapped_index, length))
in_bounds = self.append(ir.Select(mapped_ge_0, mapped_lt_len, in_bounds = self.append(ir.Select(mapped_ge_0, mapped_lt_len,
ir.Constant(False, builtins.TBool()))) ir.Constant(False, builtins.TBool())))
head = self.current_block
out_of_bounds_block = self.add_block() self.current_block = out_of_bounds_block = self.add_block()
exn = out_of_bounds_block.append(ir.Alloc([], builtins.TIndexError())) exn = self.alloc_exn(builtins.TIndexError(),
out_of_bounds_block.append(ir.Raise(exn)) ir.Constant("index {0} out of bounds 0:{1}", builtins.TStr()),
index, length)
self.raise_exn(exn)
in_bounds_block = self.add_block() self.current_block = in_bounds_block = self.add_block()
head.append(ir.BranchIf(in_bounds, in_bounds_block, out_of_bounds_block))
self.append(ir.BranchIf(in_bounds, in_bounds_block, out_of_bounds_block))
self.current_block = in_bounds_block
return mapped_index return mapped_index
@ -673,7 +701,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
cond_block = self.current_block cond_block = self.current_block
self.current_block = body_block = self.add_block() self.current_block = body_block = self.add_block()
self.append(ir.Raise(exn_gen())) self.raise_exn(exn_gen())
self.current_block = tail_block = self.add_block() self.current_block = tail_block = self.add_block()
cond_block.append(ir.BranchIf(cond, tail_block, body_block)) cond_block.append(ir.BranchIf(cond, tail_block, body_block))
@ -736,9 +764,10 @@ class ARTIQIRGenerator(algorithm.Visitor):
if node.slice.step is not None: if node.slice.step is not None:
step = self.visit(node.slice.step) step = self.visit(node.slice.step)
self._make_check(self.append(ir.Compare(ast.NotEq(loc=None), step, self._make_check(
ir.Constant(0, step.type))), self.append(ir.Compare(ast.NotEq(loc=None), step, ir.Constant(0, step.type))),
lambda: self.append(ir.Alloc([], builtins.TValueError()))) lambda: self.alloc_exn(builtins.TValueError(),
ir.Constant("step cannot be zero", builtins.TStr())))
else: else:
step = ir.Constant(1, node.slice.type) step = ir.Constant(1, node.slice.type)
counting_up = self.append(ir.Compare(ast.Gt(loc=None), step, counting_up = self.append(ir.Compare(ast.Gt(loc=None), step,
@ -755,8 +784,12 @@ class ARTIQIRGenerator(algorithm.Visitor):
slice_size = self.append(ir.Select(rem_not_empty, slice_size = self.append(ir.Select(rem_not_empty,
slice_size_c, slice_size_a, slice_size_c, slice_size_a,
name="slice.size")) name="slice.size"))
self._make_check(self.append(ir.Compare(ast.LtE(loc=None), slice_size, length)), self._make_check(
lambda: self.append(ir.Alloc([], builtins.TValueError()))) self.append(ir.Compare(ast.LtE(loc=None), slice_size, length)),
lambda: self.alloc_exn(builtins.TValueError(),
ir.Constant("slice size {0} is larger than iterable length {1}",
builtins.TStr()),
slice_size, iterable_len))
if self.current_assign is None: if self.current_assign is None:
is_neg_size = self.append(ir.Compare(ast.Lt(loc=None), is_neg_size = self.append(ir.Compare(ast.Lt(loc=None),
@ -832,9 +865,12 @@ class ARTIQIRGenerator(algorithm.Visitor):
return lst return lst
else: else:
length = self.iterable_len(self.current_assign) length = self.iterable_len(self.current_assign)
self._make_check(self.append(ir.Compare(ast.Eq(loc=None), length, self._make_check(
ir.Constant(len(node.elts), self._size_type))), self.append(ir.Compare(ast.Eq(loc=None), length,
lambda: self.append(ir.Alloc([], builtins.TValueError()))) ir.Constant(len(node.elts), self._size_type))),
lambda: self.alloc_exn(builtins.TValueError(),
ir.Constant("list must be {0} elements long to decompose", builtins.TStr()),
length))
for index, elt_node in enumerate(node.elts): for index, elt_node in enumerate(node.elts):
elt = self.append(ir.GetElem(self.current_assign, elt = self.append(ir.GetElem(self.current_assign,
@ -937,13 +973,15 @@ class ARTIQIRGenerator(algorithm.Visitor):
rhs = self.visit(node.right) rhs = self.visit(node.right)
if isinstance(node.op, (ast.LShift, ast.RShift)): if isinstance(node.op, (ast.LShift, ast.RShift)):
# Check for negative shift amount. # Check for negative shift amount.
self._make_check(self.append(ir.Compare(ast.GtE(loc=None), rhs, self._make_check(
ir.Constant(0, rhs.type))), self.append(ir.Compare(ast.GtE(loc=None), rhs, ir.Constant(0, rhs.type))),
lambda: self.append(ir.Alloc([], builtins.TValueError()))) lambda: self.alloc_exn(builtins.TValueError(),
ir.Constant("shift amount must be nonnegative", builtins.TStr())))
elif isinstance(node.op, (ast.Div, ast.FloorDiv)): elif isinstance(node.op, (ast.Div, ast.FloorDiv)):
self._make_check(self.append(ir.Compare(ast.NotEq(loc=None), rhs, self._make_check(
ir.Constant(0, rhs.type))), self.append(ir.Compare(ast.NotEq(loc=None), rhs, ir.Constant(0, rhs.type))),
lambda: self.append(ir.Alloc([], builtins.TZeroDivisionError()))) lambda: self.alloc_exn(builtins.TZeroDivisionError(),
ir.Constant("cannot divide by zero", builtins.TStr())))
return self.append(ir.Arith(node.op, self.visit(node.left), rhs)) return self.append(ir.Arith(node.op, self.visit(node.left), rhs))
elif isinstance(node.op, ast.Add): # list + list, tuple + tuple elif isinstance(node.op, ast.Add): # list + list, tuple + tuple
@ -1179,6 +1217,31 @@ class ARTIQIRGenerator(algorithm.Visitor):
result_tail.append(ir.Branch(tail)) result_tail.append(ir.Branch(tail))
return phi return phi
# Keep this function with builtins.TException.attributes.
def alloc_exn(self, typ, message=None, param0=None, param1=None, param2=None):
attributes = [
ir.Constant(typ.find().name, ir.TExceptionTypeInfo()), # typeinfo
ir.Constant("<not thrown>", builtins.TStr()), # file
ir.Constant(0, builtins.TInt(types.TValue(32))), # line
ir.Constant(0, builtins.TInt(types.TValue(32))), # column
]
if message is None:
attributes.append(ir.Constant(typ.find().name, builtins.TStr()))
else:
attributes.append(message) # message
param_type = builtins.TInt(types.TValue(64))
for param in [param0, param1, param2]:
if param is None:
attributes.append(ir.Constant(0, builtins.TInt(types.TValue(64))))
else:
if param.type != param_type:
param = self.append(ir.Coerce(param, param_type))
attributes.append(param) # paramN, N=0:2
return self.append(ir.Alloc(attributes, typ))
def visit_builtin_call(self, node): def visit_builtin_call(self, node):
# A builtin by any other name... Ignore node.func, just use the type. # A builtin by any other name... Ignore node.func, just use the type.
typ = node.func.type typ = node.func.type
@ -1275,7 +1338,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
separator=" ", suffix="\n") separator=" ", suffix="\n")
return ir.Constant(None, builtins.TNone()) return ir.Constant(None, builtins.TNone())
elif types.is_exn_constructor(typ): elif types.is_exn_constructor(typ):
return self.append(ir.Alloc([self.visit(arg) for args in node.args], node.type)) return self.alloc_exn(node.type, *[self.visit(arg_node) for arg_node in node.args])
else: else:
assert False assert False
@ -1485,8 +1548,14 @@ class ARTIQIRGenerator(algorithm.Visitor):
format_string += ")" format_string += ")"
elif builtins.is_exception(value.type): elif builtins.is_exception(value.type):
# TODO: print exceptions name = self.append(ir.GetAttr(value, "__name__"))
assert False message = self.append(ir.GetAttr(value, "__message__"))
param1 = self.append(ir.GetAttr(value, "__param0__"))
param2 = self.append(ir.GetAttr(value, "__param1__"))
param3 = self.append(ir.GetAttr(value, "__param2__"))
format_string += "%s(%s, %lld, %lld, %lld)"
args += [name, message, param1, param2, param3]
else: else:
assert False assert False

View File

@ -440,23 +440,32 @@ class Inferencer(algorithm.Visitor):
self.engine.process(diag) self.engine.process(diag)
if types.is_exn_constructor(typ): if types.is_exn_constructor(typ):
exns = { valid_forms = lambda: [
"IndexError": builtins.TIndexError, valid_form("{exn}() -> {exn}".format(exn=typ.name)),
"ValueError": builtins.TValueError, valid_form("{exn}(message:str) -> {exn}".format(exn=typ.name)),
} valid_form("{exn}(message:str, param1:int(width=64)) -> {exn}".format(exn=typ.name)),
for exn in exns: valid_form("{exn}(message:str, param1:int(width=64), "
if types.is_exn_constructor(typ, exn): "param2:int(width=64)) -> {exn}".format(exn=typ.name)),
valid_forms = lambda: [ valid_form("{exn}(message:str, param1:int(width=64), "
valid_form("{exn}() -> {exn}".format(exn=exn)) "param2:int(width=64), param3:int(width=64)) "
] "-> {exn}".format(exn=typ.name)),
]
if len(node.args) == 0 and len(node.keywords) == 0: if len(node.args) == 0 and len(node.keywords) == 0:
pass # False pass # Default message, zeroes as parameters
else: elif len(node.args) >= 1 and len(node.args) <= 4 and len(node.keywords) == 0:
diagnose(valid_forms()) message, *params = node.args
self._unify(node.type, exns[exn](), self._unify(message.type, builtins.TStr(),
node.loc, None) message.loc, None)
for param in params:
self._unify(param.type, builtins.TInt(types.TValue(64)),
param.loc, None)
else:
diagnose(valid_forms())
self._unify(node.type, getattr(builtins, "T" + typ.name)(),
node.loc, None)
elif types.is_builtin(typ, "bool"): elif types.is_builtin(typ, "bool"):
valid_forms = lambda: [ valid_forms = lambda: [
valid_form("bool() -> bool"), valid_form("bool() -> bool"),
@ -829,26 +838,27 @@ class Inferencer(algorithm.Visitor):
def visit_ExceptHandlerT(self, node): def visit_ExceptHandlerT(self, node):
self.generic_visit(node) self.generic_visit(node)
if not types.is_exn_constructor(node.filter.type): if node.filter is not None:
diag = diagnostic.Diagnostic("error", if not types.is_exn_constructor(node.filter.type):
"this expression must refer to an exception constructor", diag = diagnostic.Diagnostic("error",
{"type": types.TypePrinter().name(node.filter.type)}, "this expression must refer to an exception constructor",
node.filter.loc) {"type": types.TypePrinter().name(node.filter.type)},
self.engine.process(diag) node.filter.loc)
else: self.engine.process(diag)
def makenotes(printer, typea, typeb, loca, locb): else:
return [ def makenotes(printer, typea, typeb, loca, locb):
diagnostic.Diagnostic("note", return [
"expression of type {typea}", diagnostic.Diagnostic("note",
{"typea": printer.name(typea)}, "expression of type {typea}",
loca), {"typea": printer.name(typea)},
diagnostic.Diagnostic("note", loca),
"constructor of an exception of type {typeb}", diagnostic.Diagnostic("note",
{"typeb": printer.name(typeb)}, "constructor of an exception of type {typeb}",
locb) {"typeb": printer.name(typeb)},
] locb)
self._unify(node.name_type, builtins.TException(node.filter.type.name), ]
node.name_loc, node.filter.loc, makenotes) self._unify(node.name_type, builtins.TException(node.filter.type.name),
node.name_loc, node.filter.loc, makenotes)
def _type_from_arguments(self, node, ret): def _type_from_arguments(self, node, ret):
self.generic_visit(node) self.generic_visit(node)
@ -943,6 +953,17 @@ class Inferencer(algorithm.Visitor):
self._unify(self.function.return_type, node.value.type, self._unify(self.function.return_type, node.value.type,
self.function.name_loc, node.value.loc, makenotes) self.function.name_loc, node.value.loc, makenotes)
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)
def visit_Assert(self, node): def visit_Assert(self, node):
self.generic_visit(node) self.generic_visit(node)
self._unify(node.test.type, builtins.TBool(), self._unify(node.test.type, builtins.TBool(),

View File

@ -43,7 +43,7 @@ class LLVMIRGenerator:
return ll.IntType(builtins.get_int_width(typ)) return ll.IntType(builtins.get_int_width(typ))
elif builtins.is_float(typ): elif builtins.is_float(typ):
return ll.DoubleType() return ll.DoubleType()
elif builtins.is_str(typ): elif builtins.is_str(typ) or ir.is_exn_typeinfo(typ):
return ll.IntType(8).as_pointer() return ll.IntType(8).as_pointer()
elif builtins.is_list(typ): elif builtins.is_list(typ):
lleltty = self.llty_of_type(builtins.get_iterable_elt(typ)) lleltty = self.llty_of_type(builtins.get_iterable_elt(typ))
@ -51,11 +51,8 @@ class LLVMIRGenerator:
elif builtins.is_range(typ): elif builtins.is_range(typ):
lleltty = self.llty_of_type(builtins.get_iterable_elt(typ)) lleltty = self.llty_of_type(builtins.get_iterable_elt(typ))
return ll.LiteralStructType([lleltty, lleltty, lleltty]) return ll.LiteralStructType([lleltty, lleltty, lleltty])
elif builtins.is_exception(typ):
# TODO: hack before EH is working
return ll.LiteralStructType([])
elif ir.is_basic_block(typ): elif ir.is_basic_block(typ):
return ll.LabelType() return ll.IntType(8).as_pointer()
elif ir.is_option(typ): elif ir.is_option(typ):
return ll.LiteralStructType([ll.IntType(1), self.llty_of_type(typ.params["inner"])]) return ll.LiteralStructType([ll.IntType(1), self.llty_of_type(typ.params["inner"])])
elif ir.is_environment(typ): elif ir.is_environment(typ):
@ -65,8 +62,21 @@ class LLVMIRGenerator:
return llty return llty
else: else:
return llty.as_pointer() return llty.as_pointer()
else: else: # Catch-all for exceptions and custom classes
assert False if builtins.is_exception(typ):
name = 'Exception' # they all share layout
else:
name = typ.name
llty = self.llcontext.get_identified_type(name)
if llty.elements is None:
llty.elements = [self.llty_of_type(attrtyp)
for attrtyp in typ.attributes.values()]
if bare or not builtins.is_allocated(typ):
return llty
else:
return llty.as_pointer()
def llconst_of_const(self, const): def llconst_of_const(self, const):
llty = self.llty_of_type(const.type) llty = self.llty_of_type(const.type)
@ -79,14 +89,27 @@ class LLVMIRGenerator:
elif isinstance(const.value, (int, float)): elif isinstance(const.value, (int, float)):
return ll.Constant(llty, const.value) return ll.Constant(llty, const.value)
elif isinstance(const.value, str): elif isinstance(const.value, str):
as_bytes = (const.value + '\0').encode('utf-8') assert "\0" not in const.value
as_bytes = (const.value + "\0").encode("utf-8")
if ir.is_exn_typeinfo(const.type):
# Exception typeinfo; should be merged with identical others
name = "__artiq_exn_" + const.value
linkage = "linkonce"
unnamed_addr = False
else:
# Just a string
name = self.llmodule.get_unique_name("str")
linkage = "private"
unnamed_addr = True
llstrty = ll.ArrayType(ll.IntType(8), len(as_bytes)) llstrty = ll.ArrayType(ll.IntType(8), len(as_bytes))
llconst = ll.GlobalVariable(self.llmodule, llstrty, llconst = ll.GlobalVariable(self.llmodule, llstrty, name)
name=self.llmodule.get_unique_name("str"))
llconst.global_constant = True llconst.global_constant = True
llconst.unnamed_addr = True
llconst.linkage = 'internal'
llconst.initializer = ll.Constant(llstrty, bytearray(as_bytes)) llconst.initializer = ll.Constant(llstrty, bytearray(as_bytes))
llconst.linkage = linkage
llconst.unnamed_addr = unnamed_addr
return llconst.bitcast(ll.IntType(8).as_pointer()) return llconst.bitcast(ll.IntType(8).as_pointer())
else: else:
assert False assert False
@ -112,6 +135,10 @@ 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":
llty = ll.FunctionType(ll.IntType(32), [], var_arg=True)
else: else:
assert False assert False
return ll.Function(self.llmodule, llty, name) return ll.Function(self.llmodule, llty, name)
@ -124,8 +151,10 @@ class LLVMIRGenerator:
elif isinstance(value, ir.Function): elif isinstance(value, ir.Function):
llfun = self.llmodule.get_global(value.name) llfun = self.llmodule.get_global(value.name)
if llfun is None: if llfun is None:
return ll.Function(self.llmodule, self.llty_of_type(value.type, bare=True), llfun = ll.Function(self.llmodule, self.llty_of_type(value.type, bare=True),
value.name) value.name)
llfun.linkage = 'internal'
return llfun
else: else:
return llfun return llfun
else: else:
@ -186,6 +215,9 @@ class LLVMIRGenerator:
self.fixups.append(fixup) self.fixups.append(fixup)
return llinsn return llinsn
def llindex(self, index):
return ll.Constant(ll.IntType(32), index)
def process_Alloc(self, insn): def process_Alloc(self, insn):
if ir.is_environment(insn.type): if ir.is_environment(insn.type):
return self.llbuilder.alloca(self.llty_of_type(insn.type, bare=True), return self.llbuilder.alloca(self.llty_of_type(insn.type, bare=True),
@ -210,6 +242,13 @@ class LLVMIRGenerator:
size=llsize) size=llsize)
llvalue = self.llbuilder.insert_value(llvalue, llalloc, 1, name=insn.name) llvalue = self.llbuilder.insert_value(llvalue, llalloc, 1, name=insn.name)
return llvalue return llvalue
elif builtins.is_exception(insn.type):
llalloc = self.llbuilder.alloca(self.llty_of_type(insn.type, bare=True))
for index, operand in enumerate(insn.operands):
lloperand = self.map(operand)
llfieldptr = self.llbuilder.gep(llalloc, [self.llindex(0), self.llindex(index)])
self.llbuilder.store(lloperand, llfieldptr)
return llalloc
elif builtins.is_allocated(insn.type): elif builtins.is_allocated(insn.type):
assert False assert False
else: # immutable else: # immutable
@ -219,9 +258,6 @@ class LLVMIRGenerator:
llvalue.name = insn.name llvalue.name = insn.name
return llvalue return llvalue
def llindex(self, index):
return ll.Constant(ll.IntType(32), index)
def llptr_to_var(self, llenv, env_ty, var_name): def llptr_to_var(self, llenv, env_ty, var_name):
if var_name in env_ty.params: if var_name in env_ty.params:
var_index = list(env_ty.params.keys()).index(var_name) var_index = list(env_ty.params.keys()).index(var_name)
@ -241,6 +277,8 @@ class LLVMIRGenerator:
env = insn.environment() env = insn.environment()
llptr = self.llptr_to_var(self.map(env), env.type, insn.var_name) llptr = self.llptr_to_var(self.map(env), env.type, insn.var_name)
llvalue = self.map(insn.value()) llvalue = self.map(insn.value())
if isinstance(llvalue, ll.Block):
llvalue = ll.BlockAddress(self.llfunction, llvalue)
if llptr.type.pointee != llvalue.type: if llptr.type.pointee != llvalue.type:
# The environment argument is an i8*, so that all closures can # The environment argument is an i8*, so that all closures can
# unify with each other regardless of environment type or size. # unify with each other regardless of environment type or size.
@ -266,11 +304,11 @@ class LLVMIRGenerator:
return self.llbuilder.load(llptr) return self.llbuilder.load(llptr)
def process_SetAttr(self, insn): def process_SetAttr(self, insn):
assert builtins.is_allocated(insns.object().type) assert builtins.is_allocated(insn.object().type)
llptr = self.llbuilder.gep(self.map(insn.object()), llptr = self.llbuilder.gep(self.map(insn.object()),
[self.llindex(0), self.llindex(self.attr_index(insn))], [self.llindex(0), self.llindex(self.attr_index(insn))],
name=insn.name) name=insn.name)
return self.llbuilder.store(llptr, self.map(insn.value())) return self.llbuilder.store(self.map(insn.value()), llptr)
def process_GetElem(self, insn): def process_GetElem(self, insn):
llelts = self.llbuilder.extract_value(self.map(insn.list()), 1) llelts = self.llbuilder.extract_value(self.map(insn.list()), 1)
@ -483,7 +521,9 @@ class LLVMIRGenerator:
llargs = map(self.map, insn.operands) llargs = map(self.map, insn.operands)
return self.llbuilder.call(self.llbuiltin("printf"), llargs, return self.llbuilder.call(self.llbuiltin("printf"), llargs,
name=insn.name) name=insn.name)
# elif insn.op == "exncast": elif insn.op == "exncast":
# This is an identity cast at LLVM IR level.
return self.map(insn.operands[0])
else: else:
assert False assert False
@ -495,13 +535,24 @@ class LLVMIRGenerator:
name=insn.name) name=insn.name)
return llvalue return llvalue
def process_Call(self, insn): def prepare_call(self, insn):
llclosure, llargs = self.map(insn.target_function()), map(self.map, insn.arguments()) llclosure, llargs = self.map(insn.target_function()), map(self.map, insn.arguments())
llenv = self.llbuilder.extract_value(llclosure, 0) llenv = self.llbuilder.extract_value(llclosure, 0)
llfun = self.llbuilder.extract_value(llclosure, 1) llfun = self.llbuilder.extract_value(llclosure, 1)
return self.llbuilder.call(llfun, [llenv] + list(llargs), return llfun, [llenv] + list(llargs)
def process_Call(self, insn):
llfun, llargs = self.prepare_call(insn)
return self.llbuilder.call(llfun, llargs,
name=insn.name) name=insn.name)
def process_Invoke(self, insn):
llfun, llargs = self.prepare_call(insn)
llnormalblock = self.map(insn.normal_target())
llunwindblock = self.map(insn.exception_target())
return self.llbuilder.invoke(llfun, llargs, llnormalblock, llunwindblock,
name=insn.name)
def process_Select(self, insn): def process_Select(self, insn):
return self.llbuilder.select(self.map(insn.condition()), return self.llbuilder.select(self.map(insn.condition()),
self.map(insn.if_true()), self.map(insn.if_false())) self.map(insn.if_true()), self.map(insn.if_false()))
@ -513,8 +564,11 @@ class LLVMIRGenerator:
return self.llbuilder.cbranch(self.map(insn.condition()), return self.llbuilder.cbranch(self.map(insn.condition()),
self.map(insn.if_true()), self.map(insn.if_false())) self.map(insn.if_true()), self.map(insn.if_false()))
# def process_IndirectBranch(self, insn): def process_IndirectBranch(self, insn):
# pass llinsn = self.llbuilder.branch_indirect(self.map(insn.target()))
for dest in insn.destinations():
llinsn.add_destination(self.map(dest))
return llinsn
def process_Return(self, insn): def process_Return(self, insn):
if builtins.is_none(insn.value().type): if builtins.is_none(insn.value().type):
@ -526,15 +580,33 @@ class LLVMIRGenerator:
return self.llbuilder.unreachable() return self.llbuilder.unreachable()
def process_Raise(self, insn): def process_Raise(self, insn):
# TODO: hack before EH is working arg = self.map(insn.operands[0])
llinsn = self.llbuilder.call(self.llbuiltin("llvm.trap"), [], llinsn = self.llbuilder.call(self.llbuiltin("__artiq_raise"), [arg],
name=insn.name) name=insn.name)
llinsn.attributes.add('noreturn')
self.llbuilder.unreachable() self.llbuilder.unreachable()
return llinsn return llinsn
# def process_Invoke(self, insn): def process_LandingPad(self, insn):
# pass lllandingpad = self.llbuilder.landingpad(self.llty_of_type(insn.type),
self.llbuiltin("__artiq_personality"))
llexnnameptr = self.llbuilder.gep(lllandingpad, [self.llindex(0), self.llindex(0)])
llexnname = self.llbuilder.load(llexnnameptr)
# def process_LandingPad(self, insn): for target, typ in insn.clauses():
# pass if typ is None:
llclauseexnname = ll.Constant(
self.llty_of_type(ir.TExceptionTypeInfo()), None)
else:
llclauseexnname = self.llconst_of_const(
ir.Constant(typ.name, ir.TExceptionTypeInfo()))
lllandingpad.add_clause(ll.CatchClause(llclauseexnname))
llmatchingclause = self.llbuilder.icmp_unsigned('==', llexnname, llclauseexnname)
with self.llbuilder.if_then(llmatchingclause):
self.llbuilder.branch(self.map(target))
self.llbuilder.resume(lllandingpad)
return lllandingpad

View File

@ -85,7 +85,9 @@ class TMono(Type):
A monomorphic type, possibly parametric. A monomorphic type, possibly parametric.
:class:`TMono` is supposed to be subclassed by builtin types, :class:`TMono` is supposed to be subclassed by builtin types,
unlike all other :class:`Type` descendants. unlike all other :class:`Type` descendants. Similarly,
instances of :class:`TMono` should never be allocated directly,
as that will break the type-sniffing code in :mod:`builtins`.
""" """
attributes = OrderedDict() attributes = OrderedDict()

View File

@ -239,13 +239,17 @@ class EscapeValidator(algorithm.Visitor):
# Only three ways for a pointer to escape: # Only three ways for a pointer to escape:
# * Assigning or op-assigning it (we ensure an outlives relationship) # * Assigning or op-assigning it (we ensure an outlives relationship)
# * Returning it (we only allow returning values that live forever) # * Returning it (we only allow returning values that live forever)
# * Raising it (we forbid raising mutable data) # * Raising it (we forbid allocating exceptions that refer to mutable data)¹
# #
# Literals doesn't count: a constructed object is always # Literals doesn't count: a constructed object is always
# outlived by all its constituents. # outlived by all its constituents.
# Closures don't count: see above. # Closures don't count: see above.
# Calling functions doesn't count: arguments never outlive # Calling functions doesn't count: arguments never outlive
# the function body. # the function body.
#
# ¹Strings are currently never allocated with a limited lifetime,
# and exceptions can only refer to strings, so we don't actually check
# this property. But we will need to, if string operations are ever added.
def visit_assignment(self, target, value, is_aug_assign=False): def visit_assignment(self, target, value, is_aug_assign=False):
target_region = self._region_of(target) target_region = self._region_of(target)
@ -298,14 +302,3 @@ class EscapeValidator(algorithm.Visitor):
"cannot return a mutable value that does not live forever", {}, "cannot return a mutable value that does not live forever", {},
node.value.loc, notes=self._diagnostics_for(region, node.value.loc) + [note]) node.value.loc, notes=self._diagnostics_for(region, node.value.loc) + [note])
self.engine.process(diag) self.engine.process(diag)
def visit_Raise(self, node):
if builtins.is_allocated(node.exc.type):
note = diagnostic.Diagnostic("note",
"this expression has type {type}",
{"type": types.TypePrinter().name(node.exc.type)},
node.exc.loc)
diag = diagnostic.Diagnostic("error",
"cannot raise a mutable value", {},
node.exc.loc, notes=[note])
self.engine.process(diag)

View File

@ -56,3 +56,18 @@ lambda x, y=1: x
k = "x" k = "x"
# CHECK-L: k:str # CHECK-L: k:str
IndexError()
# CHECK-L: IndexError:<constructor IndexError>():IndexError
IndexError("x")
# CHECK-L: IndexError:<constructor IndexError>("x":str):IndexError
IndexError("x", 1)
# CHECK-L: IndexError:<constructor IndexError>("x":str, 1:int(width=64)):IndexError
IndexError("x", 1, 1)
# CHECK-L: IndexError:<constructor IndexError>("x":str, 1:int(width=64), 1:int(width=64)):IndexError
IndexError("x", 1, 1, 1)
# CHECK-L: IndexError:<constructor IndexError>("x":str, 1:int(width=64), 1:int(width=64), 1:int(width=64)):IndexError