Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested Data Proposal (SDFG only) #1324

Merged
merged 46 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
57abd28
Added NestedDataClassProperty for nested data.
alexnick83 Jul 18, 2023
09465d2
Added Structures and StructArrays.
alexnick83 Jul 18, 2023
51776a1
Break array lengths down to their symbolic tokents.
alexnick83 Jul 18, 2023
b23ed86
Allow structures to have fields whose name doesn't start with undersc…
alexnick83 Jul 18, 2023
777821f
Structures now have a "members" dictionary. Their dtype is a pointer …
alexnick83 Jul 19, 2023
ebf7206
dtype.structs store their ctype in `_FFI_CTYPES`.
alexnick83 Jul 19, 2023
c52a482
Reverted underscore exception for Structures.
alexnick83 Jul 19, 2023
40cc858
Small fixes.
alexnick83 Jul 19, 2023
dd73aaa
WIP: Replace ',' with '->' to quickly support nested data.
alexnick83 Jul 19, 2023
623a7f8
Recursively add to arglist nested data descriptors.
alexnick83 Jul 19, 2023
1e5badd
Recursively look into nested data to emit definitions.
alexnick83 Jul 19, 2023
36d4e82
SDFG data (_arrays) are now stored in a NestedDict.
alexnick83 Jul 19, 2023
38a4265
Adjusted the matching check for memlet data and src/dst nodes to not …
alexnick83 Jul 19, 2023
479cb2a
Added tests.
alexnick83 Jul 19, 2023
8365ab3
Serialization fixes.
alexnick83 Jul 19, 2023
14ba665
Fixed NestedDict for non-str keys.
alexnick83 Jul 19, 2023
80d6f10
Added support for transient Structures.
alexnick83 Jul 20, 2023
9658c22
Edited tests.
alexnick83 Jul 20, 2023
b1dbb6b
Structures have name attribute (instead of subclassing).
alexnick83 Jul 20, 2023
5de2ae3
Updated tests.
alexnick83 Jul 20, 2023
1fbc45f
Removed nested data connectors.
alexnick83 Jul 20, 2023
6fa7e53
Added support for direct access to nested data.
alexnick83 Jul 20, 2023
71d7c3d
WIP: Add nested data free symbols to SDFG.
alexnick83 Jul 20, 2023
e0a4409
Added test for direct nested data access.
alexnick83 Jul 20, 2023
0593ea4
Added test for direct double-nested data accesses.
alexnick83 Jul 20, 2023
0df9c35
Added free-symbols and repr.
alexnick83 Jul 21, 2023
909c1aa
Recursively add free symbols from nested data.
alexnick83 Jul 21, 2023
e2b0d8b
Updated tests.
alexnick83 Jul 21, 2023
52afc72
Scrapped structure private symbols for now.
alexnick83 Jul 21, 2023
a0e3458
Merge branch 'master' into nested-data-sdfg
alexnick83 Jul 21, 2023
0924644
Updated tests.
alexnick83 Jul 21, 2023
8296a6d
Added setitem.
alexnick83 Jul 21, 2023
61d2ddc
Merge branch 'master' into nested-data-sdfg
alexnick83 Jul 27, 2023
a98fce0
Serialize Structure members and struct data/length as list of tuples.
alexnick83 Jul 27, 2023
f431a8d
Switched Structures and structs to OrderedDicts.
alexnick83 Jul 28, 2023
86d9cf2
Removed order from properties.
alexnick83 Jul 28, 2023
76d6266
`_argminmax` now creates a struct with the members ordered as accesse…
alexnick83 Jul 28, 2023
4c25648
Merge branch 'master' into nested-data-sdfg
alexnick83 Jul 28, 2023
9c97365
Merge branch 'master' into nested-data-sdfg
alexnick83 Aug 3, 2023
1cb9f9f
Added support for StructureViews.
alexnick83 Aug 17, 2023
5a2c460
Added tests for StructArrays.
alexnick83 Aug 17, 2023
230f799
Merge branch 'master' into nested-data-sdfg
alexnick83 Aug 17, 2023
f1b0c73
Fixed serialization.
alexnick83 Aug 17, 2023
c5889a4
Addressed comments.
alexnick83 Aug 21, 2023
b80be92
Merge branch 'master' into nested-data-sdfg
alexnick83 Aug 22, 2023
eabbd1d
Addressed comments.
alexnick83 Aug 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions dace/codegen/compiled_sdfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,9 +452,10 @@ def _construct_args(self, kwargs) -> Tuple[Tuple[Any], Tuple[Any]]:
# GPU scalars are pointers, so this is fine
if atype.storage != dtypes.StorageType.GPU_Global:
raise TypeError('Passing an array to a scalar (type %s) in argument "%s"' % (atype.dtype.ctype, a))
elif not isinstance(atype, dt.Array) and not isinstance(atype.dtype, dtypes.callback) and not isinstance(
arg,
(atype.dtype.type, sp.Basic)) and not (isinstance(arg, symbolic.symbol) and arg.dtype == atype.dtype):
elif (not isinstance(atype, (dt.Array, dt.Structure)) and
not isinstance(atype.dtype, dtypes.callback) and
not isinstance(arg, (atype.dtype.type, sp.Basic)) and
not (isinstance(arg, symbolic.symbol) and arg.dtype == atype.dtype)):
if isinstance(arg, int) and atype.dtype.type == np.int64:
pass
elif isinstance(arg, float) and atype.dtype.type == np.float64:
Expand Down Expand Up @@ -521,7 +522,7 @@ def _construct_args(self, kwargs) -> Tuple[Tuple[Any], Tuple[Any]]:
# Construct init args, which only consist of the symbols
symbols = self._free_symbols
initargs = tuple(
actype(arg) if (not isinstance(arg, ctypes._SimpleCData)) else arg
actype(arg) if not isinstance(arg, ctypes._SimpleCData) else arg
for arg, actype, atype, aname in callparams if aname in symbols)

# Replace arrays with their base host/device pointers
Expand All @@ -531,7 +532,8 @@ def _construct_args(self, kwargs) -> Tuple[Tuple[Any], Tuple[Any]]:

try:
newargs = tuple(
actype(arg) if (not isinstance(arg, ctypes._SimpleCData)) else arg for arg, actype, atype in newargs)
actype(arg) if not isinstance(arg, (ctypes._SimpleCData)) else arg
for arg, actype, atype in newargs)
except TypeError:
# Pinpoint bad argument
for i, (arg, actype, _) in enumerate(newargs):
Expand Down
2 changes: 2 additions & 0 deletions dace/codegen/targets/cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,8 @@ def make_const(expr: str) -> str:
# Register defined variable
dispatcher.defined_vars.add(pointer_name, defined_type, typedef, allow_shadowing=True)

expr = expr.replace('.', '->')
alexnick83 marked this conversation as resolved.
Show resolved Hide resolved

return (typedef + ref, pointer_name, expr)


Expand Down
43 changes: 38 additions & 5 deletions dace/codegen/targets/cpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,30 @@ def __init__(self, frame_codegen, sdfg):
# Keep track of generated NestedSDG, and the name of the assigned function
self._generated_nested_sdfg = dict()

# Keeps track of generated connectors, so we know how to access them in
# nested scopes
def _visit_structure(struct: data.Structure, args: dict, prefix: str = ''):
for k, v in struct.members.items():
if isinstance(v, data.Structure):
_visit_structure(v, args, f'{prefix}.{k}')
elif isinstance(v, data.Data):
args[f'{prefix}.{k}'] = v

# Keeps track of generated connectors, so we know how to access them in nested scopes
arglist = dict(self._frame.arglist)
for name, arg_type in self._frame.arglist.items():
if isinstance(arg_type, data.Scalar):
if isinstance(arg_type, data.Structure):
desc = sdfg.arrays[name]
_visit_structure(arg_type, arglist, name)
elif isinstance(arg_type, data.StructArray):
desc = sdfg.arrays[name]
desc = desc.stype
for attr in dir(desc):
value = getattr(desc, attr)
if isinstance(value, data.Data):
assert attr in sdfg.arrays
arglist[attr] = value
alexnick83 marked this conversation as resolved.
Show resolved Hide resolved

for name, arg_type in arglist.items():
if isinstance(arg_type, (data.Scalar, data.Structure)):
# GPU global memory is only accessed via pointers
# TODO(later): Fix workaround somehow
if arg_type.storage is dtypes.StorageType.GPU_Global:
Expand Down Expand Up @@ -266,16 +286,17 @@ def allocate_array(self, sdfg, dfg, state_id, node, nodedesc, function_stream, d
name = node.data
alloc_name = cpp.ptr(name, nodedesc, sdfg, self._frame)
name = alloc_name
alloc_name = alloc_name.replace('.', '->')
alexnick83 marked this conversation as resolved.
Show resolved Hide resolved

if nodedesc.transient is False:
return

# Check if array is already allocated
if self._dispatcher.defined_vars.has(alloc_name):
if self._dispatcher.defined_vars.has(name):
return

# Check if array is already declared
declared = self._dispatcher.declared_arrays.has(alloc_name)
declared = self._dispatcher.declared_arrays.has(name)

define_var = self._dispatcher.defined_vars.add
if nodedesc.lifetime in (dtypes.AllocationLifetime.Persistent, dtypes.AllocationLifetime.External):
Expand All @@ -288,6 +309,17 @@ def allocate_array(self, sdfg, dfg, state_id, node, nodedesc, function_stream, d
if not isinstance(nodedesc.dtype, dtypes.opaque):
arrsize_bytes = arrsize * nodedesc.dtype.bytes

if isinstance(nodedesc, data.Structure):
declaration_stream.write(f"{nodedesc.ctype} {name} = new {nodedesc.dtype.base_type}();\n")
alexnick83 marked this conversation as resolved.
Show resolved Hide resolved
define_var(name, DefinedType.Pointer, nodedesc.ctype)
for k, v in nodedesc.members.items():
if isinstance(v, data.Data):
ctypedef = dtypes.pointer(v.dtype).ctype if isinstance(v, data.Array) else v.dtype.ctype
defined_type = DefinedType.Scalar if isinstance(v, data.Scalar) else DefinedType.Pointer
self._dispatcher.declared_arrays.add(f"{name}.{k}", defined_type, ctypedef)
self.allocate_array(sdfg, dfg, state_id, nodes.AccessNode(f"{name}.{k}"), v, function_stream,
declaration_stream, allocation_stream)
return
if isinstance(nodedesc, data.View):
return self.allocate_view(sdfg, dfg, state_id, node, function_stream, declaration_stream, allocation_stream)
if isinstance(nodedesc, data.Reference):
Expand Down Expand Up @@ -1137,6 +1169,7 @@ def memlet_definition(self,
if not types:
types = self._dispatcher.defined_vars.get(ptr, is_global=True)
var_type, ctypedef = types
ptr = ptr.replace('.', '->')
tbennun marked this conversation as resolved.
Show resolved Hide resolved

if fpga.is_fpga_array(desc):
decouple_array_interfaces = Config.get_bool("compiler", "xilinx", "decouple_array_interfaces")
Expand Down
18 changes: 13 additions & 5 deletions dace/codegen/targets/framecode.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,23 @@ def generate_fileheader(self, sdfg: SDFG, global_stream: CodeIOStream, backend:
for _, arrname, arr in sdfg.arrays_recursive():
if arr is not None:
datatypes.add(arr.dtype)

def _emit_definitions(dtype: dtypes.typeclass, wrote_something: bool) -> bool:
if isinstance(dtype, dtypes.pointer):
wrote_something = _emit_definitions(dtype._typeclass, wrote_something)
elif isinstance(dtype, dtypes.struct):
for field in dtype.fields.values():
wrote_something = _emit_definitions(field, wrote_something)
if hasattr(dtype, 'emit_definition'):
if not wrote_something:
global_stream.write("", sdfg)
global_stream.write(dtype.emit_definition(), sdfg)
return wrote_something

# Emit unique definitions
wrote_something = False
for typ in datatypes:
if hasattr(typ, 'emit_definition'):
if not wrote_something:
global_stream.write("", sdfg)
wrote_something = True
global_stream.write(typ.emit_definition(), sdfg)
wrote_something = _emit_definitions(typ, wrote_something)
if wrote_something:
global_stream.write("", sdfg)

Expand Down
186 changes: 180 additions & 6 deletions dace/data.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Copyright 2019-2022 ETH Zurich and the DaCe authors. All rights reserved.
# Copyright 2019-2023 ETH Zurich and the DaCe authors. All rights reserved.
import copy as cp
import ctypes
import functools
import re

from numbers import Number
from typing import Any, Dict, Optional, Sequence, Set, Tuple
from typing import Any, Dict, Optional, Sequence, Set, Tuple, Union

import numpy
import sympy as sp
Expand All @@ -17,9 +17,8 @@
import dace.dtypes as dtypes
from dace import serialize, symbolic
from dace.codegen import cppunparse
from dace.properties import (CodeProperty, DebugInfoProperty, DictProperty, EnumProperty, ListProperty, Property,
ReferenceProperty, ShapeProperty, SubsetProperty, SymbolicProperty, TypeClassProperty,
make_properties)
from dace.properties import (DebugInfoProperty, DictProperty, EnumProperty, ListProperty, NestedDataClassProperty,
Property, ShapeProperty, SymbolicProperty, TypeClassProperty, make_properties)


def create_datadescriptor(obj, no_custom_desc=False):
Expand Down Expand Up @@ -342,6 +341,151 @@ def add(X: dace.float32[10, 10] @ dace.StorageType.GPU_Global):
return new_desc


def _arrays_to_json(arrays):
if arrays is None:
return None
return {k: serialize.to_json(v) for k, v in arrays.items()}


def _arrays_from_json(obj, context=None):
if obj is None:
return {}
return {k: serialize.from_json(v, context) for k, v in obj.items()}


@make_properties
class Structure(Data):
""" Base class for structures. """

members = Property(dtype=dict,
desc="Dictionary of structure members",
alexnick83 marked this conversation as resolved.
Show resolved Hide resolved
from_json=_arrays_from_json,
to_json=_arrays_to_json)
name = Property(dtype=str, desc="Structure name")

alexnick83 marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self,
members: Dict[str, Data],
name: str = 'Structure',
transient: bool = False,
storage: dtypes.StorageType = dtypes.StorageType.Default,
location: Dict[str, str] = None,
lifetime: dtypes.AllocationLifetime = dtypes.AllocationLifetime.Scope,
debuginfo: dtypes.DebugInfo = None):
# TODO: Should we make a deep-copy here?
self.members = members or {}
for k, v in self.members.items():
v.transient = transient
self.name = name
fields_and_types = dict()
symbols = set()
for k, v in members.items():
if isinstance(v, Structure):
symbols |= v.free_symbols
fields_and_types[k] = (v.dtype, str(v.total_size))
elif isinstance(v, Array):
symbols |= v.free_symbols
fields_and_types[k] = (dtypes.pointer(v.dtype), str(_prod(v.shape)))
elif isinstance(v, Scalar):
symbols |= v.free_symbols
fields_and_types[k] = v.dtype
elif isinstance(v, (sp.Basic, symbolic.SymExpr)):
symbols |= v.free_symbols
fields_and_types[k] = symbolic.symtype(v)
elif isinstance(v, (int, numpy.integer)):
fields_and_types[k] = dtypes.typeclass(type(v))
else:
raise TypeError(f"Attribute {k}'s value {v} has unsupported type: {type(v)}")
for s in symbols:
if str(s) in fields_and_types:
continue
if hasattr(s, "dtype"):
fields_and_types[str(s)] = s.dtype
else:
fields_and_types[str(s)] = dtypes.int32
dtype = dtypes.pointer(dtypes.struct(name, **fields_and_types))
shape = (1,)
super(Structure, self).__init__(dtype, shape, transient, storage, location, lifetime, debuginfo)

@staticmethod
def from_json(json_obj, context=None):
if json_obj['type'] != 'Structure':
raise TypeError("Invalid data type")

# Create dummy object
ret = Structure({})
serialize.set_properties_from_json(ret, json_obj, context=context)

return ret

@property
def total_size(self):
return -1

@property
def offset(self):
return [0]

@property
def start_offset(self):
return 0

@property
def strides(self):
return [1]

@property
def free_symbols(self) -> Set[symbolic.SymbolicType]:
""" Returns a set of undefined symbols in this data descriptor. """
result = set()
for k, v in self.members.items():
result |= v.free_symbols
return result

def __repr__(self):
return f"{self.name} ({', '.join([f'{k}: {v}' for k, v in self.members.items()])})"

def as_arg(self, with_types=True, for_call=False, name=None):
if self.storage is dtypes.StorageType.GPU_Global:
return Array(self.dtype, [1]).as_arg(with_types, for_call, name)
if not with_types or for_call:
return name
return self.dtype.as_arg(name)

def __getitem__(self, s):
""" This is syntactic sugar that allows us to define an array type
with the following syntax: ``Structure[N,M]``
:return: A ``data.Array`` data descriptor.
"""
alexnick83 marked this conversation as resolved.
Show resolved Hide resolved
if isinstance(s, list) or isinstance(s, tuple):
return StructArray(self, tuple(s))
return StructArray(self, (s, ))


@make_properties
class StructureView(Structure):
"""
Data descriptor that acts as a reference (or view) of another structure.
"""

@staticmethod
def from_json(json_obj, context=None):
if json_obj['type'] != 'StructureView':
raise TypeError("Invalid data type")

# Create dummy object
ret = StructureView({})
serialize.set_properties_from_json(ret, json_obj, context=context)

return ret

def validate(self):
super().validate()

# We ensure that allocation lifetime is always set to Scope, since the
# view is generated upon "allocation"
if self.lifetime != dtypes.AllocationLifetime.Scope:
raise ValueError('Only Scope allocation lifetime is supported for Views')

@make_properties
class Scalar(Data):
""" Data descriptor of a scalar value. """
Expand Down Expand Up @@ -902,6 +1046,36 @@ def free_symbols(self):
return result


@make_properties
class StructArray(Array):
""" Array of Structures. """

stype = NestedDataClassProperty(allow_none=True, default=None)

def __init__(self,
stype,
shape,
tbennun marked this conversation as resolved.
Show resolved Hide resolved
transient=False,
allow_conflicts=False,
storage=dtypes.StorageType.Default,
location=None,
strides=None,
offset=None,
may_alias=False,
lifetime=dtypes.AllocationLifetime.Scope,
alignment=0,
debuginfo=None,
total_size=-1,
start_offset=None,
optional=None,
pool=False):

self.stype = stype
dtype = stype.dtype
super(StructArray, self).__init__(dtype, shape, transient, allow_conflicts, storage, location, strides, offset,
may_alias, lifetime, alignment, debuginfo, total_size, start_offset, optional, pool)


@make_properties
class View(Array):
"""
Expand Down
Loading
Loading