From dea3c0c572fc47f301699eff5f8c4b4c5747fc9c Mon Sep 17 00:00:00 2001 From: David Nadlinger Date: Mon, 27 Jul 2020 23:21:23 +0100 Subject: [PATCH] compiler: Don't store redundant ndarray buffer length, match list layout This adds `elt` to _TPointer and the ir.Offset IR instruction, which is like GetElem but without the final load. --- artiq/compiler/builtins.py | 4 +- artiq/compiler/ir.py | 32 +++++++++++- .../compiler/transforms/artiq_ir_generator.py | 7 ++- .../transforms/dead_code_eliminator.py | 3 +- .../compiler/transforms/llvm_ir_generator.py | 52 ++++++++----------- artiq/compiler/types.py | 8 ++- 6 files changed, 66 insertions(+), 40 deletions(-) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index ae7e61055..af0201765 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -93,8 +93,8 @@ class TArray(types.TMono): super().__init__("array", {"elt": elt, "num_dims": num_dims}) self.attributes = OrderedDict([ + ("buffer", types._TPointer(elt)), ("shape", types.TTuple([TInt32()] * num_dims.value)), - ("buffer", TList(elt)), ]) def _array_printer(typ, printer, depth, max_depth): @@ -317,7 +317,7 @@ def is_iterable(typ): def get_iterable_elt(typ): if is_str(typ) or is_bytes(typ) or is_bytearray(typ): return TInt(types.TValue(8)) - elif is_iterable(typ): + elif types._is_pointer(typ) or is_iterable(typ): return typ.find()["elt"].find() else: assert False diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index c20359962..a411c036f 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -738,6 +738,33 @@ class SetAttr(Instruction): def value(self): return self.operands[1] +class Offset(Instruction): + """ + An intruction that adds an offset to a pointer (indexes into a list). + + This is used to represent internally generated pointer arithmetic, and must + remain inside the same object (see :class:`GetElem` and LLVM's GetElementPtr). + """ + + """ + :param lst: (:class:`Value`) list + :param index: (:class:`Value`) index + """ + def __init__(self, base, offset, name=""): + assert isinstance(base, Value) + assert isinstance(offset, Value) + typ = types._TPointer(builtins.get_iterable_elt(base.type)) + super().__init__([base, offset], typ, name) + + def opcode(self): + return "offset" + + def base(self): + return self.operands[0] + + def index(self): + return self.operands[1] + class GetElem(Instruction): """ An intruction that loads an element from a list. @@ -755,7 +782,7 @@ class GetElem(Instruction): def opcode(self): return "getelem" - def list(self): + def base(self): return self.operands[0] def index(self): @@ -781,7 +808,7 @@ class SetElem(Instruction): def opcode(self): return "setelem" - def list(self): + def base(self): return self.operands[0] def index(self): @@ -840,6 +867,7 @@ class Arith(Instruction): def rhs(self): return self.operands[1] + class Compare(Instruction): """ A comparison operation on numbers. diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 8a1981279..c4d469cd7 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -523,12 +523,11 @@ class ARTIQIRGenerator(algorithm.Visitor): lengths[1:], lengths[0]) offset = self.append(ir.Arith(ast.Mult(loc=None), stride, index)) old_buffer = self.append(ir.GetAttr(value, "buffer")) - # KLUDGE: Represent offsetting by Alloc with two arguments. - new_buffer = self.append(ir.Alloc([old_buffer, offset], old_buffer.type)) + new_buffer = self.append(ir.Offset(old_buffer, offset)) result_type = builtins.TArray(value.type.find()["elt"], types.TValue(num_dims - 1)) - return self.append(ir.Alloc([new_shape, new_buffer], result_type)) + return self.append(ir.Alloc([new_buffer, new_shape], result_type)) else: buffer = self.append(ir.GetAttr(value, "buffer")) return self.append(ir.GetElem(buffer, index)) @@ -1740,7 +1739,7 @@ class ARTIQIRGenerator(algorithm.Visitor): ir.Constant(0, self._size_type), lambda index: self.append( ir.Compare(ast.Lt(loc=None), index, num_total_elts)), body_gen) - return self.append(ir.Alloc([shape, buffer], node.type)) + return self.append(ir.Alloc([buffer, shape], node.type)) else: assert False elif types.is_builtin(typ, "range"): diff --git a/artiq/compiler/transforms/dead_code_eliminator.py b/artiq/compiler/transforms/dead_code_eliminator.py index 2aceebeec..608a46d55 100644 --- a/artiq/compiler/transforms/dead_code_eliminator.py +++ b/artiq/compiler/transforms/dead_code_eliminator.py @@ -33,7 +33,8 @@ class DeadCodeEliminator: # it also has to run after the interleaver, but interleaver # doesn't like to work with IR before DCE. if isinstance(insn, (ir.Phi, ir.Alloc, ir.GetAttr, ir.GetElem, ir.Coerce, - ir.Arith, ir.Compare, ir.Select, ir.Quote, ir.Closure)) \ + ir.Arith, ir.Compare, ir.Select, ir.Quote, ir.Closure, + ir.Offset)) \ and not any(insn.uses): insn.erase() modified = True diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index b8e7e8457..dcc2cc03e 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -211,7 +211,7 @@ class LLVMIRGenerator: else: return llunit elif types._is_pointer(typ): - return llptr + return ll.PointerType(self.llty_of_type(typ["elt"])) elif types.is_function(typ): sretarg = [] llretty = self.llty_of_type(typ.ret, for_return=True) @@ -249,7 +249,7 @@ class LLVMIRGenerator: elif builtins.is_array(typ): llshapety = self.llty_of_type(typ.attributes["shape"]) llbufferty = self.llty_of_type(typ.attributes["buffer"]) - return ll.LiteralStructType([llshapety, llbufferty]) + return ll.LiteralStructType([llbufferty, llshapety]) elif builtins.is_listish(typ): lleltty = self.llty_of_type(builtins.get_iterable_elt(typ)) return ll.LiteralStructType([lleltty.as_pointer(), lli32]) @@ -737,28 +737,13 @@ class LLVMIRGenerator: name=insn.name) else: assert False - elif builtins.is_listish(insn.type) and not builtins.is_array(insn.type): - if builtins.is_listish(insn.operands[0].type): - # KLUDGE: Offsetting is represented as Alloc with base list in the first - # argument and offset in the second. Should probably move this to a - # seprate node type (or make it possible to construct lists from - # pointer/length). - llbase = self.map(insn.operands[0]) - lloldbase = self.llbuilder.extract_value(llbase, 0) - lloldsize = self.llbuilder.extract_value(llbase, 1) - - lloffset = self.map(insn.operands[1]) - llbase = self.llbuilder.gep(lloldbase, [lloffset], inbounds=True) - llsize = self.llbuilder.sub(lloldsize, lloffset) - - llvalue = ll.Constant(self.llty_of_type(insn.type), ll.Undefined) - llvalue = self.llbuilder.insert_value(llvalue, llbase, 0) - llvalue = self.llbuilder.insert_value(llvalue, llsize, 1) - return llvalue - + elif types._is_pointer(insn.type) or (builtins.is_listish(insn.type) + and not builtins.is_array(insn.type)): llsize = self.map(insn.operands[0]) lleltty = self.llty_of_type(builtins.get_iterable_elt(insn.type)) llalloc = self.llbuilder.alloca(lleltty, size=llsize) + if types._is_pointer(insn.type): + return llalloc llvalue = ll.Constant(self.llty_of_type(insn.type), ll.Undefined) llvalue = self.llbuilder.insert_value(llvalue, llalloc, 0, name=insn.name) llvalue = self.llbuilder.insert_value(llvalue, llsize, 1) @@ -962,20 +947,28 @@ class LLVMIRGenerator: inbounds=True, name=insn.name) return self.llbuilder.store(llvalue, llptr) - def process_GetElem(self, insn): - lst, idx = insn.list(), insn.index() - lllst, llidx = map(self.map, (lst, idx)) - llelts = self.llbuilder.extract_value(lllst, 0) + def process_Offset(self, insn): + base, idx = insn.base(), insn.index() + llelts, llidx = map(self.map, (base, idx)) + if not types._is_pointer(base.type): + # This is list-ish. + llelts = self.llbuilder.extract_value(llelts, 0) llelt = self.llbuilder.gep(llelts, [llidx], inbounds=True) + return llelt + + def process_GetElem(self, insn): + llelt = self.process_Offset(insn) llvalue = self.llbuilder.load(llelt) if isinstance(llvalue.type, ll.PointerType): self.mark_dereferenceable(llvalue) return llvalue def process_SetElem(self, insn): - lst, idx = insn.list(), insn.index() - lllst, llidx = map(self.map, (lst, idx)) - llelts = self.llbuilder.extract_value(lllst, 0) + base, idx = insn.base(), insn.index() + llelts, llidx = map(self.map, (base, idx)) + if not types._is_pointer(base.type): + # This is list-ish. + llelts = self.llbuilder.extract_value(llelts, 0) llelt = self.llbuilder.gep(llelts, [llidx], inbounds=True) return self.llbuilder.store(self.map(insn.value()), llelt) @@ -1190,7 +1183,8 @@ class LLVMIRGenerator: collection, = insn.operands if builtins.is_array(collection.type): # Return length of outermost dimension. - shape = self.llbuilder.extract_value(self.map(collection), 0) + shape = self.llbuilder.extract_value(self.map(collection), + self.attr_index(collection.type, "shape")) return self.llbuilder.extract_value(shape, 0) return self.llbuilder.extract_value(self.map(collection), 1) elif insn.op in ("printf", "rtio_log"): diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 5299822c3..206f040fb 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -204,8 +204,10 @@ class TTuple(Type): return hash(tuple(self.elts)) class _TPointer(TMono): - def __init__(self): - super().__init__("pointer") + def __init__(self, elt=None): + if elt is None: + elt = TMono("int", {"width": 8}) # i8* + super().__init__("pointer", params={"elt": elt}) class TFunction(Type): """ @@ -735,6 +737,8 @@ class TypePrinter(object): else: return "%s(%s)" % (typ.name, ", ".join( ["%s=%s" % (k, self.name(typ.params[k], depth + 1)) for k in typ.params])) + elif isinstance(typ, _TPointer): + return "{}*".format(self.name(typ["elt"], depth + 1)) elif isinstance(typ, TTuple): if len(typ.elts) == 1: return "(%s,)" % self.name(typ.elts[0], depth + 1)