compiler: Parametrize TArray in number of dimensions

This commit is contained in:
David Nadlinger 2020-07-26 01:07:03 +01:00
parent 632c5bc937
commit bc17bb4d1a
7 changed files with 107 additions and 76 deletions

View File

@ -82,17 +82,24 @@ class TList(types.TMono):
super().__init__("list", {"elt": elt}) super().__init__("list", {"elt": elt})
class TArray(types.TMono): class TArray(types.TMono):
def __init__(self, elt=None): def __init__(self, elt=None, num_dims=types.TValue(1)):
if elt is None: if elt is None:
elt = types.TVar() elt = types.TVar()
super().__init__("array", {"elt": elt}) # For now, enforce number of dimensions to be known, as we'd otherwise
# need to implement custom unification logic for the type of `shape`.
# Default to 1 to keep compatibility with old user code from before
# multidimensional array support.
assert isinstance(num_dims.value, int), "Number of dimensions must be resolved"
super().__init__("array", {"elt": elt, "num_dims": num_dims})
self.attributes = OrderedDict([ self.attributes = OrderedDict([
("shape", TList(TInt32())), ("shape", types.TTuple([TInt32()] * num_dims.value)),
("buffer", TList(elt)), ("buffer", TList(elt)),
]) ])
def _array_printer(typ, printer, depth, max_depth): def _array_printer(typ, printer, depth, max_depth):
return "numpy.array(elt={})".format(printer.name(typ["elt"], depth, max_depth)) return "numpy.array(elt={}, num_dims={})".format(
printer.name(typ["elt"], depth, max_depth), typ["num_dims"].value)
types.TypePrinter.custom_printers["array"] = _array_printer types.TypePrinter.custom_printers["array"] = _array_printer
class TRange(types.TMono): class TRange(types.TMono):

View File

@ -7,6 +7,7 @@ semantics explicitly.
""" """
from collections import OrderedDict, defaultdict from collections import OrderedDict, defaultdict
from functools import reduce
from pythonparser import algorithm, diagnostic, ast from pythonparser import algorithm, diagnostic, ast
from .. import types, builtins, asttyped, ir, iodelay from .. import types, builtins, asttyped, ir, iodelay
@ -1665,47 +1666,32 @@ class ARTIQIRGenerator(algorithm.Visitor):
result_type = node.type.find() result_type = node.type.find()
arg = self.visit(node.args[0]) arg = self.visit(node.args[0])
num_dims = 0
result_elt = result_type["elt"].find() result_elt = result_type["elt"].find()
inner_type = arg.type.find() num_dims = result_type["num_dims"].value
while True:
if inner_type == result_elt:
# TODO: What about types needing coercion (e.g. int32 to int64)?
break
assert builtins.is_iterable(inner_type)
num_dims += 1
inner_type = builtins.get_iterable_elt(inner_type)
# Derive shape from first element on each level (currently, type # Derive shape from first element on each level (currently, type
# inference make sure arrays are always rectangular; in the future, we # inference make sure arrays are always rectangular; in the future, we
# might want to insert a runtime check here). # might want to insert a runtime check here).
# first_elt = None
# While we are at it, also total up overall number of elements lengths = []
shape = self.append( for dim_idx in range(num_dims):
ir.Alloc([ir.Constant(num_dims, self._size_type)], if first_elt is None:
result_type.attributes["shape"]))
first_elt = arg first_elt = arg
dim_idx = 0
num_total_elts = None
while True:
length = self.iterable_len(first_elt)
self.append(
ir.SetElem(shape, ir.Constant(dim_idx, length.type), length))
if num_total_elts is None:
num_total_elts = length
else: else:
num_total_elts = self.append(
ir.Arith(ast.Mult(loc=None), num_total_elts, length))
dim_idx += 1
if dim_idx == num_dims:
break
first_elt = self.iterable_get(first_elt, first_elt = self.iterable_get(first_elt,
ir.Constant(0, length.type)) ir.Constant(0, self._size_type))
lengths.append(self.iterable_len(first_elt))
num_total_elts = reduce(
lambda l, r: self.append(ir.Arith(ast.Mult(loc=None), l, r)),
lengths[1:], lengths[0])
shape = self.append(ir.Alloc(lengths, result_type.attributes["shape"]))
# Assign buffer from nested iterables. # Assign buffer from nested iterables.
buffer = self.append( buffer = self.append(
ir.Alloc([num_total_elts], result_type.attributes["buffer"])) ir.Alloc([num_total_elts], result_type.attributes["buffer"]))
def body_gen(index): def body_gen(index):
# TODO: This is hilariously inefficient; we really want to emit a # TODO: This is hilariously inefficient; we really want to emit a
# nested loop for the source and keep one running index for the # nested loop for the source and keep one running index for the
@ -1713,9 +1699,11 @@ class ARTIQIRGenerator(algorithm.Visitor):
indices = [] indices = []
mod_idx = index mod_idx = index
for dim_idx in reversed(range(1, num_dims)): for dim_idx in reversed(range(1, num_dims)):
dim_len = self.append(ir.GetElem(shape, ir.Constant(dim_idx, self._size_type))) dim_len = self.append(ir.GetAttr(shape, dim_idx))
indices.append(self.append(ir.Arith(ast.Mod(loc=None), mod_idx, dim_len))) indices.append(
mod_idx = self.append(ir.Arith(ast.FloorDiv(loc=None), mod_idx, dim_len)) self.append(ir.Arith(ast.Mod(loc=None), mod_idx, dim_len)))
mod_idx = self.append(
ir.Arith(ast.FloorDiv(loc=None), mod_idx, dim_len))
indices.append(mod_idx) indices.append(mod_idx)
elt = arg elt = arg
@ -1723,9 +1711,11 @@ class ARTIQIRGenerator(algorithm.Visitor):
elt = self.iterable_get(elt, idx) elt = self.iterable_get(elt, idx)
self.append(ir.SetElem(buffer, index, elt)) self.append(ir.SetElem(buffer, index, elt))
return self.append( return self.append(
ir.Arith(ast.Add(loc=None), index, ir.Constant(1, length.type))) ir.Arith(ast.Add(loc=None), index,
ir.Constant(1, self._size_type)))
self._make_loop( self._make_loop(
ir.Constant(0, length.type), lambda index: self.append( ir.Constant(0, self._size_type), lambda index: self.append(
ir.Compare(ast.Lt(loc=None), index, num_total_elts)), body_gen) 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([shape, buffer], node.type))

View File

@ -8,18 +8,28 @@ from .. import asttyped, types, builtins
from .typedtree_printer import TypedtreePrinter from .typedtree_printer import TypedtreePrinter
def is_rectangular_2d_list(node): def match_rectangular_list(elts):
if not isinstance(node, asttyped.ListT):
return False
num_elts = None num_elts = None
for e in node.elts: elt_type = None
all_child_elts = []
for e in elts:
if elt_type is None:
elt_type = e.type.find()
if not isinstance(e, asttyped.ListT): if not isinstance(e, asttyped.ListT):
return False return elt_type, 0
if num_elts is None: if num_elts is None:
num_elts = len(e.elts) num_elts = len(e.elts)
elif num_elts != len(e.elts): elif num_elts != len(e.elts):
return False return elt_type, 0
return True all_child_elts += e.elts
if not all_child_elts:
# This ultimately turned out to be a list (of list, of ...) of empty lists.
return elt_type["elt"], 1
elt, num_dims = match_rectangular_list(all_child_elts)
return elt, num_dims + 1
class Inferencer(algorithm.Visitor): class Inferencer(algorithm.Visitor):
@ -710,29 +720,45 @@ class Inferencer(algorithm.Visitor):
"strings currently cannot be constructed", {}, "strings currently cannot be constructed", {},
node.loc) node.loc)
self.engine.process(diag) self.engine.process(diag)
elif types.is_builtin(typ, "list") or types.is_builtin(typ, "array"):
if types.is_builtin(typ, "list"):
valid_forms = lambda: [
valid_form("list() -> list(elt='a)"),
valid_form("list(x:'a) -> list(elt='b) where 'a is iterable")
]
self._unify(node.type, builtins.TList(),
node.loc, None)
elif types.is_builtin(typ, "array"): elif types.is_builtin(typ, "array"):
valid_forms = lambda: [ valid_forms = lambda: [
valid_form("array(x:'a) -> array(elt='b) where 'a is iterable") valid_form("array(x:'a) -> array(elt='b) where 'a is iterable")
] ]
self._unify(node.type, builtins.TArray(), if len(node.args) == 1 and len(node.keywords) == 0:
node.loc, None) arg, = node.args
else:
assert False
if (types.is_builtin(typ, "list") and len(node.args) == 0 and if builtins.is_iterable(arg.type):
len(node.keywords) == 0): # KLUDGE: Support multidimensional arary creation if lexically
# Mimic numpy and don't allow array() (but []). # specified as a rectangular array of lists.
pass elt, num_dims = match_rectangular_list([arg])
self._unify(node.type,
builtins.TArray(elt, types.TValue(num_dims)),
node.loc, arg.loc)
elif types.is_var(arg.type):
pass # undetermined yet
else:
note = diagnostic.Diagnostic("note",
"this expression has type {type}",
{"type": types.TypePrinter().name(arg.type)},
arg.loc)
diag = diagnostic.Diagnostic("error",
"the argument of {builtin}() must be of an iterable type",
{"builtin": typ.find().name},
node.func.loc, notes=[note])
self.engine.process(diag)
else:
diagnose(valid_forms())
elif types.is_builtin(typ, "list"):
valid_forms = lambda: [
valid_form("list() -> list(elt='a)"),
valid_form("list(x:'a) -> list(elt='b) where 'a is iterable")
]
self._unify(node.type, builtins.TList(), node.loc, None)
if len(node.args) == 0 and len(node.keywords) == 0:
pass # []
elif len(node.args) == 1 and len(node.keywords) == 0: elif len(node.args) == 1 and len(node.keywords) == 0:
arg, = node.args arg, = node.args
@ -748,14 +774,8 @@ class Inferencer(algorithm.Visitor):
{"typeb": printer.name(typeb)}, {"typeb": printer.name(typeb)},
locb) locb)
] ]
elt = arg.type.find().params["elt"]
if types.is_builtin(typ, "array") and builtins.is_listish(elt):
# KLUDGE: Support 2D arary creation if lexically specified
# as a rectangular array of lists.
if is_rectangular_2d_list(arg):
elt = elt.find().params["elt"]
self._unify(node.type.find().params["elt"], self._unify(node.type.find().params["elt"],
elt, arg.type.find().params["elt"],
node.loc, arg.loc, makenotes=makenotes) node.loc, arg.loc, makenotes=makenotes)
elif types.is_var(arg.type): elif types.is_var(arg.type):
pass # undetermined yet pass # undetermined yet

View File

@ -1173,7 +1173,7 @@ class LLVMIRGenerator:
if builtins.is_array(collection.type): if builtins.is_array(collection.type):
# Return length of outermost dimension. # Return length of outermost dimension.
shape = self.llbuilder.extract_value(self.map(collection), 0) shape = self.llbuilder.extract_value(self.map(collection), 0)
return self.llbuilder.load(self.llbuilder.extract_value(shape, 0)) return self.llbuilder.extract_value(shape, 0)
return self.llbuilder.extract_value(self.map(collection), 1) return self.llbuilder.extract_value(self.map(collection), 1)
elif insn.op in ("printf", "rtio_log"): elif insn.op in ("printf", "rtio_log"):
# We only get integers, floats, pointers and strings here. # We only get integers, floats, pointers and strings here.

View File

@ -50,3 +50,9 @@ class ConstnessValidator(algorithm.Visitor):
node.loc) node.loc)
self.engine.process(diag) self.engine.process(diag)
return return
if builtins.is_array(typ):
diag = diagnostic.Diagnostic("error",
"array attributes cannot be assigned to",
{}, node.loc)
self.engine.process(diag)
return

View File

@ -3,3 +3,7 @@
# CHECK-L: ${LINE:+1}: error: array cannot be invoked with the arguments () # CHECK-L: ${LINE:+1}: error: array cannot be invoked with the arguments ()
a = array() a = array()
b = array([1, 2, 3])
# CHECK-L: ${LINE:+1}: error: array attributes cannot be assigned to
b.shape = (5, )

View File

@ -3,7 +3,7 @@
ary = array([1, 2, 3]) ary = array([1, 2, 3])
assert len(ary) == 3 assert len(ary) == 3
assert ary.shape == [3] assert ary.shape == (3,)
# FIXME: Implement ndarray indexing # FIXME: Implement ndarray indexing
# assert [x*x for x in ary] == [1, 4, 9] # assert [x*x for x in ary] == [1, 4, 9]
@ -11,8 +11,12 @@ assert ary.shape == [3]
empty_array = array([1]) empty_array = array([1])
empty_array = array([]) empty_array = array([])
assert len(empty_array) == 0 assert len(empty_array) == 0
assert empty_array.shape == [0] assert empty_array.shape == (0,)
matrix = array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) matrix = array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
assert len(matrix) == 2 assert len(matrix) == 2
assert matrix.shape == [2, 3] assert matrix.shape == (2, 3)
three_tensor = array([[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]])
assert len(three_tensor) == 1
assert three_tensor.shape == (1, 2, 3)