From 1a518ea7ebbc87a9061e84468e5367f51e7d9b09 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 8 Aug 2016 04:05:52 +0000 Subject: [PATCH] compiler.embedding: implement string concatenation. Fixes #526. --- artiq/compiler/builtins.py | 6 +++- .../compiler/transforms/artiq_ir_generator.py | 16 +++++++-- artiq/compiler/transforms/inferencer.py | 4 +++ .../compiler/transforms/llvm_ir_generator.py | 36 ++++++++++++++----- artiq/compiler/validators/escape.py | 2 +- artiq/test/lit/escape/const_string.py | 11 ++++++ artiq/test/lit/escape/error_string.py | 19 ++++++++++ artiq/test/lit/integration/str.py | 4 +++ 8 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 artiq/test/lit/escape/const_string.py create mode 100644 artiq/test/lit/escape/error_string.py create mode 100644 artiq/test/lit/integration/str.py diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 830769eec..ee7306e3c 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -260,7 +260,7 @@ def is_array(typ, elt=None): return types.is_mono(typ, "array") def is_listish(typ, elt=None): - return is_list(typ, elt) or is_array(typ, elt) + return is_list(typ, elt) or is_array(typ, elt) or (elt is None and is_str(typ)) def is_range(typ, elt=None): if elt is not None: @@ -283,6 +283,10 @@ def is_iterable(typ): def get_iterable_elt(typ): if is_iterable(typ): return typ.find()["elt"].find() + elif is_str(typ): + return TInt(types.TValue(8)) + else: + assert False def is_collection(typ): typ = typ.find() diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index ee0232fc6..0c753e984 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -478,8 +478,12 @@ class ARTIQIRGenerator(algorithm.Visitor): def iterable_len(self, value, typ=_size_type): if builtins.is_listish(value.type): + if isinstance(value, ir.Constant): + name = None + else: + name = "{}.len".format(value.name) return self.append(ir.Builtin("len", [value], typ, - name="{}.len".format(value.name))) + name=name)) elif builtins.is_range(value.type): start = self.append(ir.GetAttr(value, "start")) stop = self.append(ir.GetAttr(value, "stop")) @@ -1313,7 +1317,7 @@ class ARTIQIRGenerator(algorithm.Visitor): loc=node.right.loc) return self.append(ir.Arith(node.op, lhs, rhs)) - elif isinstance(node.op, ast.Add): # list + list, tuple + tuple + elif isinstance(node.op, ast.Add): # list + list, tuple + tuple, str + str lhs, rhs = self.visit(node.left), self.visit(node.right) if types.is_tuple(node.left.type) and types.is_tuple(node.right.type): elts = [] @@ -1327,6 +1331,10 @@ class ARTIQIRGenerator(algorithm.Visitor): rhs_length = self.iterable_len(rhs) result_length = self.append(ir.Arith(ast.Add(loc=None), lhs_length, rhs_length)) + if builtins.is_str(node.left.type): + result_last = result_length + result_length = self.append(ir.Arith(ast.Add(loc=None), result_length, + ir.Constant(1, self._size_type))) result = self.append(ir.Alloc([result_length], node.type)) # Copy lhs @@ -1350,6 +1358,10 @@ class ARTIQIRGenerator(algorithm.Visitor): lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, rhs_length)), body_gen) + if builtins.is_str(node.left.type): + self.append(ir.SetElem(result, result_last, + ir.Constant(0, builtins.TInt(types.TValue(8))))) + return result else: assert False diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index ea488f64d..920343b44 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -394,6 +394,10 @@ class Inferencer(algorithm.Visitor): self._unify(left.type, right.type, left.loc, right.loc) return left.type, left.type, right.type + elif builtins.is_str(left.type) or builtins.is_str(right.type): + self._unify(left.type, right.type, + left.loc, right.loc) + return left.type, left.type, right.type else: return self._coerce_numeric((left, right), lambda typ: (typ, typ, typ)) elif isinstance(op, ast.Mult): diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 0544dc17c..826db2b4d 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -344,6 +344,8 @@ class LLVMIRGenerator: llty = ll.FunctionType(llvoid, [self.llty_of_type(builtins.TException())]) elif name == "__artiq_reraise": llty = ll.FunctionType(llvoid, []) + elif name == "strlen": + llty = ll.FunctionType(lli32, [llptr]) elif name == "strcmp": llty = ll.FunctionType(lli32, [llptr, llptr]) elif name == "send_rpc": @@ -610,6 +612,10 @@ class LLVMIRGenerator: name=insn.name) else: assert False + elif builtins.is_str(insn.type): + llsize = self.map(insn.operands[0]) + llvalue = self.llbuilder.alloca(lli8, size=llsize) + return llvalue elif builtins.is_listish(insn.type): llsize = self.map(insn.operands[0]) llvalue = ll.Constant(self.llty_of_type(insn.type), ll.Undefined) @@ -818,18 +824,27 @@ class LLVMIRGenerator: return self.llbuilder.store(llvalue, llptr) def process_GetElem(self, insn): - llelts = self.llbuilder.extract_value(self.map(insn.list()), 1) - llelt = self.llbuilder.gep(llelts, [self.map(insn.index())], - inbounds=True) - llvalue = self.llbuilder.load(llelt) + lst, idx = insn.list(), insn.index() + lllst, llidx = map(self.map, (lst, idx)) + if builtins.is_str(lst.type): + llelt = self.llbuilder.gep(lllst, [llidx], inbounds=True) + llvalue = self.llbuilder.load(llelt) + else: + llelts = self.llbuilder.extract_value(lllst, 1) + llelt = self.llbuilder.gep(llelts, [llidx], inbounds=True) + llvalue = self.llbuilder.load(llelt) if isinstance(llvalue.type, ll.PointerType): self.mark_dereferenceable(llvalue) return llvalue def process_SetElem(self, insn): - llelts = self.llbuilder.extract_value(self.map(insn.list()), 1) - llelt = self.llbuilder.gep(llelts, [self.map(insn.index())], - inbounds=True) + lst, idx = insn.list(), insn.index() + lllst, llidx = map(self.map, (lst, idx)) + if builtins.is_str(lst.type): + llelt = self.llbuilder.gep(lllst, [llidx], inbounds=True) + else: + llelts = self.llbuilder.extract_value(lllst, 1) + llelt = self.llbuilder.gep(llelts, [llidx], inbounds=True) return self.llbuilder.store(self.map(insn.value()), llelt) def process_Coerce(self, insn): @@ -1045,8 +1060,11 @@ class LLVMIRGenerator: env, = insn.operands return get_outer(self.map(env), env.type) elif insn.op == "len": - lst, = insn.operands - return self.llbuilder.extract_value(self.map(lst), 0) + collection, = insn.operands + if builtins.is_str(collection.type): + return self.llbuilder.call(self.llbuiltin("strlen"), [self.map(collection)]) + else: + return self.llbuilder.extract_value(self.map(collection), 0) elif insn.op in ("printf", "rtio_log"): # We only get integers, floats, pointers and strings here. llargs = map(self.map, insn.operands) diff --git a/artiq/compiler/validators/escape.py b/artiq/compiler/validators/escape.py index b2a9d47bc..e2b8b4621 100644 --- a/artiq/compiler/validators/escape.py +++ b/artiq/compiler/validators/escape.py @@ -351,6 +351,6 @@ class EscapeValidator(algorithm.Visitor): {"type": types.TypePrinter().name(node.value.type)}, node.value.loc) diag = diagnostic.Diagnostic("error", - "cannot return a mutable value that does not live forever", {}, + "cannot return an allocated value that does not live forever", {}, node.value.loc, notes=self._diagnostics_for(region, node.value.loc) + [note]) self.engine.process(diag) diff --git a/artiq/test/lit/escape/const_string.py b/artiq/test/lit/escape/const_string.py new file mode 100644 index 000000000..f5f405c86 --- /dev/null +++ b/artiq/test/lit/escape/const_string.py @@ -0,0 +1,11 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s + +from artiq.experiment import * + +@kernel +def foo(): + return "x" + +@kernel +def entrypoint(): + foo() diff --git a/artiq/test/lit/escape/error_string.py b/artiq/test/lit/escape/error_string.py new file mode 100644 index 000000000..33fbb71a6 --- /dev/null +++ b/artiq/test/lit/escape/error_string.py @@ -0,0 +1,19 @@ +# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t +# RUN: OutputCheck %s --file-to-check=%t + +from artiq.experiment import * + +@kernel +def foo(): + # CHECK-NOT-L: ${LINE:+1}: error: + return "x" + +@kernel +def bar(): + # CHECK-L: ${LINE:+1}: error: cannot return an allocated value that does not live forever + return "x" + "y" + +@kernel +def entrypoint(): + foo() + bar() diff --git a/artiq/test/lit/integration/str.py b/artiq/test/lit/integration/str.py new file mode 100644 index 000000000..ab4e7866d --- /dev/null +++ b/artiq/test/lit/integration/str.py @@ -0,0 +1,4 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python %s + +assert ("x" + "y") == "xy"