diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 73035600d..903900540 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -72,6 +72,26 @@ class TRange(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"): super().__init__(name) @@ -170,8 +190,12 @@ def is_range(typ, elt=None): else: return types.is_mono(typ, "range") -def is_exception(typ): - return isinstance(typ.find(), TException) +def is_exception(typ, name=None): + if name is None: + return isinstance(typ.find(), TException) + else: + return isinstance(typ.find(), TException) and \ + typ.name == name def is_iterable(typ): typ = typ.find() @@ -189,4 +213,5 @@ def is_collection(typ): def is_allocated(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)) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index f94df0ed4..8e042e14e 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -29,6 +29,13 @@ class TOption(types.TMono): def is_option(typ): 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: """ An SSA value that keeps track of its uses. @@ -620,7 +627,7 @@ class SetAttr(Instruction): assert value.type == obj.type.elts[attr] else: assert value.type == obj.type.attributes[attr] - super().__init__([obj, value], typ, name) + super().__init__([obj, value], builtins.TNone(), name) self.attr = attr def opcode(self): @@ -1004,7 +1011,7 @@ class Invoke(Terminator): def opcode(self): return "invoke" - def function(self): + def target_function(self): return self.operands[0] def arguments(self): @@ -1038,11 +1045,15 @@ class LandingPad(Terminator): super().__init__([], builtins.TException(), name) self.types = [] + def clauses(self): + return zip(self.operands, self.types) + def add_clause(self, target, typ): assert isinstance(target, BasicBlock) - assert builtins.is_exception(typ) + assert typ is None or builtins.is_exception(typ) 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): return "landingpad" @@ -1050,5 +1061,8 @@ class LandingPad(Terminator): def _operands_as_string(self): table = [] 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)) diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 38bccda92..9903eaaeb 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -33,7 +33,7 @@ class Module: escape_validator.visit(self.typedtree) self.artiq_ir = artiq_ir_generator.visit(self.typedtree) 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) @classmethod diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index b4d803134..62f7f995f 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -465,8 +465,17 @@ class ARTIQIRGenerator(algorithm.Visitor): def visit_Continue(self, node): 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): - self.append(ir.Raise(self.visit(node.exc))) + self.raise_exn(self.visit(node.exc)) def visit_Try(self, node): dispatcher = self.add_block("try.dispatch") @@ -521,15 +530,25 @@ class ARTIQIRGenerator(algorithm.Visitor): self.return_target = old_return handlers = [] + has_catchall = False 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 handlers.append(handler) - landingpad.add_clause(handler, handler_node.name_type) if handler_node.name is not None: exn = self.append(ir.Builtin("exncast", [landingpad], handler_node.name_type)) self._set_local(handler_node.name, exn) + self.visit(handler_node.body) if any(node.finalbody): @@ -537,36 +556,44 @@ class ARTIQIRGenerator(algorithm.Visitor): self.current_block = finalizer self.visit(node.finalbody) + post_finalizer = self.current_block - if not self.current_block.is_terminated(): - dest = self.append(ir.GetLocal(final_state, ".k")) - self.append(ir.IndirectBranch(dest, final_targets)) - - tail = self.add_block("try.tail") + self.current_block = tail = self.add_block("try.tail") if any(node.finalbody): + final_targets.append(tail) + if self.break_target: break_proxy.append(ir.Branch(finalizer)) if self.continue_target: continue_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.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()): - self.current_block = tail + 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) + + 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: - 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 @@ -655,15 +682,16 @@ class ARTIQIRGenerator(algorithm.Visitor): mapped_lt_len = self.append(ir.Compare(end_cmpop, mapped_index, length)) in_bounds = self.append(ir.Select(mapped_ge_0, mapped_lt_len, ir.Constant(False, builtins.TBool()))) + head = self.current_block - out_of_bounds_block = self.add_block() - exn = out_of_bounds_block.append(ir.Alloc([], builtins.TIndexError())) - out_of_bounds_block.append(ir.Raise(exn)) + self.current_block = out_of_bounds_block = self.add_block() + exn = self.alloc_exn(builtins.TIndexError(), + ir.Constant("index {0} out of bounds 0:{1}", builtins.TStr()), + index, length) + self.raise_exn(exn) - in_bounds_block = self.add_block() - - self.append(ir.BranchIf(in_bounds, in_bounds_block, out_of_bounds_block)) - self.current_block = in_bounds_block + self.current_block = in_bounds_block = self.add_block() + head.append(ir.BranchIf(in_bounds, in_bounds_block, out_of_bounds_block)) return mapped_index @@ -673,7 +701,7 @@ class ARTIQIRGenerator(algorithm.Visitor): cond_block = self.current_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() 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: step = self.visit(node.slice.step) - self._make_check(self.append(ir.Compare(ast.NotEq(loc=None), step, - ir.Constant(0, step.type))), - lambda: self.append(ir.Alloc([], builtins.TValueError()))) + self._make_check( + self.append(ir.Compare(ast.NotEq(loc=None), step, ir.Constant(0, step.type))), + lambda: self.alloc_exn(builtins.TValueError(), + ir.Constant("step cannot be zero", builtins.TStr()))) else: step = ir.Constant(1, node.slice.type) 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_c, slice_size_a, name="slice.size")) - self._make_check(self.append(ir.Compare(ast.LtE(loc=None), slice_size, length)), - lambda: self.append(ir.Alloc([], builtins.TValueError()))) + self._make_check( + 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: is_neg_size = self.append(ir.Compare(ast.Lt(loc=None), @@ -832,9 +865,12 @@ class ARTIQIRGenerator(algorithm.Visitor): return lst else: length = self.iterable_len(self.current_assign) - self._make_check(self.append(ir.Compare(ast.Eq(loc=None), length, - ir.Constant(len(node.elts), self._size_type))), - lambda: self.append(ir.Alloc([], builtins.TValueError()))) + self._make_check( + self.append(ir.Compare(ast.Eq(loc=None), length, + 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): elt = self.append(ir.GetElem(self.current_assign, @@ -937,13 +973,15 @@ class ARTIQIRGenerator(algorithm.Visitor): rhs = self.visit(node.right) if isinstance(node.op, (ast.LShift, ast.RShift)): # Check for negative shift amount. - self._make_check(self.append(ir.Compare(ast.GtE(loc=None), rhs, - ir.Constant(0, rhs.type))), - lambda: self.append(ir.Alloc([], builtins.TValueError()))) + self._make_check( + self.append(ir.Compare(ast.GtE(loc=None), rhs, ir.Constant(0, rhs.type))), + lambda: self.alloc_exn(builtins.TValueError(), + ir.Constant("shift amount must be nonnegative", builtins.TStr()))) elif isinstance(node.op, (ast.Div, ast.FloorDiv)): - self._make_check(self.append(ir.Compare(ast.NotEq(loc=None), rhs, - ir.Constant(0, rhs.type))), - lambda: self.append(ir.Alloc([], builtins.TZeroDivisionError()))) + self._make_check( + self.append(ir.Compare(ast.NotEq(loc=None), rhs, ir.Constant(0, rhs.type))), + 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)) elif isinstance(node.op, ast.Add): # list + list, tuple + tuple @@ -1179,6 +1217,31 @@ class ARTIQIRGenerator(algorithm.Visitor): result_tail.append(ir.Branch(tail)) 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("", 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): # A builtin by any other name... Ignore node.func, just use the type. typ = node.func.type @@ -1275,7 +1338,7 @@ class ARTIQIRGenerator(algorithm.Visitor): separator=" ", suffix="\n") return ir.Constant(None, builtins.TNone()) 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: assert False @@ -1485,8 +1548,14 @@ class ARTIQIRGenerator(algorithm.Visitor): format_string += ")" elif builtins.is_exception(value.type): - # TODO: print exceptions - assert False + name = self.append(ir.GetAttr(value, "__name__")) + 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: assert False diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 3422abf85..15604e5b8 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -440,23 +440,32 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) if types.is_exn_constructor(typ): - exns = { - "IndexError": builtins.TIndexError, - "ValueError": builtins.TValueError, - } - for exn in exns: - if types.is_exn_constructor(typ, exn): - valid_forms = lambda: [ - valid_form("{exn}() -> {exn}".format(exn=exn)) - ] + valid_forms = lambda: [ + valid_form("{exn}() -> {exn}".format(exn=typ.name)), + valid_form("{exn}(message:str) -> {exn}".format(exn=typ.name)), + valid_form("{exn}(message:str, param1:int(width=64)) -> {exn}".format(exn=typ.name)), + valid_form("{exn}(message:str, param1:int(width=64), " + "param2:int(width=64)) -> {exn}".format(exn=typ.name)), + valid_form("{exn}(message:str, param1:int(width=64), " + "param2:int(width=64), param3:int(width=64)) " + "-> {exn}".format(exn=typ.name)), + ] - if len(node.args) == 0 and len(node.keywords) == 0: - pass # False - else: - diagnose(valid_forms()) + if len(node.args) == 0 and len(node.keywords) == 0: + pass # Default message, zeroes as parameters + elif len(node.args) >= 1 and len(node.args) <= 4 and len(node.keywords) == 0: + message, *params = node.args - self._unify(node.type, exns[exn](), - node.loc, None) + self._unify(message.type, builtins.TStr(), + 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"): valid_forms = lambda: [ valid_form("bool() -> bool"), @@ -829,26 +838,27 @@ class Inferencer(algorithm.Visitor): def visit_ExceptHandlerT(self, node): self.generic_visit(node) - if not types.is_exn_constructor(node.filter.type): - diag = diagnostic.Diagnostic("error", - "this expression must refer to an exception constructor", - {"type": types.TypePrinter().name(node.filter.type)}, - node.filter.loc) - self.engine.process(diag) - else: - def makenotes(printer, typea, typeb, loca, locb): - return [ - diagnostic.Diagnostic("note", - "expression of type {typea}", - {"typea": printer.name(typea)}, - loca), - diagnostic.Diagnostic("note", - "constructor of an exception of type {typeb}", - {"typeb": printer.name(typeb)}, - locb) - ] - self._unify(node.name_type, builtins.TException(node.filter.type.name), - node.name_loc, node.filter.loc, makenotes) + if node.filter is not None: + if not types.is_exn_constructor(node.filter.type): + diag = diagnostic.Diagnostic("error", + "this expression must refer to an exception constructor", + {"type": types.TypePrinter().name(node.filter.type)}, + node.filter.loc) + self.engine.process(diag) + else: + def makenotes(printer, typea, typeb, loca, locb): + return [ + diagnostic.Diagnostic("note", + "expression of type {typea}", + {"typea": printer.name(typea)}, + loca), + diagnostic.Diagnostic("note", + "constructor of an exception of type {typeb}", + {"typeb": printer.name(typeb)}, + locb) + ] + 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): self.generic_visit(node) @@ -943,6 +953,17 @@ class Inferencer(algorithm.Visitor): self._unify(self.function.return_type, node.value.type, 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): self.generic_visit(node) self._unify(node.test.type, builtins.TBool(), diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index de7a75307..79f214097 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -43,7 +43,7 @@ class LLVMIRGenerator: return ll.IntType(builtins.get_int_width(typ)) elif builtins.is_float(typ): 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() elif builtins.is_list(typ): lleltty = self.llty_of_type(builtins.get_iterable_elt(typ)) @@ -51,11 +51,8 @@ class LLVMIRGenerator: elif builtins.is_range(typ): lleltty = self.llty_of_type(builtins.get_iterable_elt(typ)) 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): - return ll.LabelType() + return ll.IntType(8).as_pointer() elif ir.is_option(typ): return ll.LiteralStructType([ll.IntType(1), self.llty_of_type(typ.params["inner"])]) elif ir.is_environment(typ): @@ -65,8 +62,21 @@ class LLVMIRGenerator: return llty else: return llty.as_pointer() - else: - assert False + else: # Catch-all for exceptions and custom classes + 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): llty = self.llty_of_type(const.type) @@ -79,14 +89,27 @@ class LLVMIRGenerator: elif isinstance(const.value, (int, float)): return ll.Constant(llty, const.value) 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)) - llconst = ll.GlobalVariable(self.llmodule, llstrty, - name=self.llmodule.get_unique_name("str")) + llconst = ll.GlobalVariable(self.llmodule, llstrty, name) llconst.global_constant = True - llconst.unnamed_addr = True - llconst.linkage = 'internal' llconst.initializer = ll.Constant(llstrty, bytearray(as_bytes)) + llconst.linkage = linkage + llconst.unnamed_addr = unnamed_addr + return llconst.bitcast(ll.IntType(8).as_pointer()) else: assert False @@ -112,6 +135,10 @@ 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) else: assert False return ll.Function(self.llmodule, llty, name) @@ -124,8 +151,10 @@ class LLVMIRGenerator: elif isinstance(value, ir.Function): llfun = self.llmodule.get_global(value.name) if llfun is None: - return ll.Function(self.llmodule, self.llty_of_type(value.type, bare=True), - value.name) + llfun = ll.Function(self.llmodule, self.llty_of_type(value.type, bare=True), + value.name) + llfun.linkage = 'internal' + return llfun else: return llfun else: @@ -186,6 +215,9 @@ class LLVMIRGenerator: self.fixups.append(fixup) return llinsn + def llindex(self, index): + return ll.Constant(ll.IntType(32), index) + def process_Alloc(self, insn): if ir.is_environment(insn.type): return self.llbuilder.alloca(self.llty_of_type(insn.type, bare=True), @@ -210,6 +242,13 @@ class LLVMIRGenerator: size=llsize) llvalue = self.llbuilder.insert_value(llvalue, llalloc, 1, name=insn.name) 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): assert False else: # immutable @@ -219,9 +258,6 @@ class LLVMIRGenerator: llvalue.name = insn.name return llvalue - def llindex(self, index): - return ll.Constant(ll.IntType(32), index) - def llptr_to_var(self, llenv, env_ty, var_name): if var_name in env_ty.params: var_index = list(env_ty.params.keys()).index(var_name) @@ -241,6 +277,8 @@ class LLVMIRGenerator: env = insn.environment() llptr = self.llptr_to_var(self.map(env), env.type, insn.var_name) llvalue = self.map(insn.value()) + if isinstance(llvalue, ll.Block): + llvalue = ll.BlockAddress(self.llfunction, llvalue) if llptr.type.pointee != llvalue.type: # The environment argument is an i8*, so that all closures can # unify with each other regardless of environment type or size. @@ -266,11 +304,11 @@ class LLVMIRGenerator: return self.llbuilder.load(llptr) 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()), [self.llindex(0), self.llindex(self.attr_index(insn))], 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): llelts = self.llbuilder.extract_value(self.map(insn.list()), 1) @@ -483,7 +521,9 @@ class LLVMIRGenerator: llargs = map(self.map, insn.operands) return self.llbuilder.call(self.llbuiltin("printf"), llargs, 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: assert False @@ -495,13 +535,24 @@ class LLVMIRGenerator: name=insn.name) return llvalue - def process_Call(self, insn): + def prepare_call(self, insn): llclosure, llargs = self.map(insn.target_function()), map(self.map, insn.arguments()) llenv = self.llbuilder.extract_value(llclosure, 0) 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) + 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): return self.llbuilder.select(self.map(insn.condition()), self.map(insn.if_true()), self.map(insn.if_false())) @@ -513,8 +564,11 @@ class LLVMIRGenerator: return self.llbuilder.cbranch(self.map(insn.condition()), self.map(insn.if_true()), self.map(insn.if_false())) - # def process_IndirectBranch(self, insn): - # pass + def process_IndirectBranch(self, insn): + 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): if builtins.is_none(insn.value().type): @@ -526,15 +580,33 @@ class LLVMIRGenerator: return self.llbuilder.unreachable() def process_Raise(self, insn): - # TODO: hack before EH is working - llinsn = self.llbuilder.call(self.llbuiltin("llvm.trap"), [], + arg = self.map(insn.operands[0]) + llinsn = self.llbuilder.call(self.llbuiltin("__artiq_raise"), [arg], name=insn.name) + llinsn.attributes.add('noreturn') self.llbuilder.unreachable() return llinsn - # def process_Invoke(self, insn): - # pass + def process_LandingPad(self, insn): + 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): - # pass + for target, typ in insn.clauses(): + 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 diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index a84c1777e..a614ce637 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -85,7 +85,9 @@ class TMono(Type): A monomorphic type, possibly parametric. :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() diff --git a/artiq/compiler/validators/escape.py b/artiq/compiler/validators/escape.py index 9f73b936e..df87c6afc 100644 --- a/artiq/compiler/validators/escape.py +++ b/artiq/compiler/validators/escape.py @@ -239,13 +239,17 @@ class EscapeValidator(algorithm.Visitor): # Only three ways for a pointer to escape: # * Assigning or op-assigning it (we ensure an outlives relationship) # * 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 # outlived by all its constituents. # Closures don't count: see above. # Calling functions doesn't count: arguments never outlive # 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): target_region = self._region_of(target) @@ -298,14 +302,3 @@ class EscapeValidator(algorithm.Visitor): "cannot return a mutable value that does not live forever", {}, node.value.loc, notes=self._diagnostics_for(region, node.value.loc) + [note]) 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) diff --git a/lit-test/compiler/inferencer/unify.py b/lit-test/compiler/inferencer/unify.py index 48681fdcb..c6f41b495 100644 --- a/lit-test/compiler/inferencer/unify.py +++ b/lit-test/compiler/inferencer/unify.py @@ -56,3 +56,18 @@ lambda x, y=1: x k = "x" # CHECK-L: k:str + +IndexError() +# CHECK-L: IndexError:():IndexError + +IndexError("x") +# CHECK-L: IndexError:("x":str):IndexError + +IndexError("x", 1) +# CHECK-L: IndexError:("x":str, 1:int(width=64)):IndexError + +IndexError("x", 1, 1) +# CHECK-L: IndexError:("x":str, 1:int(width=64), 1:int(width=64)):IndexError + +IndexError("x", 1, 1, 1) +# CHECK-L: IndexError:("x":str, 1:int(width=64), 1:int(width=64), 1:int(width=64)):IndexError