Skip to content

Commit

Permalink
Generalize StructArrays to ContainerArrays and refactor View class st…
Browse files Browse the repository at this point in the history
…ructure (#1504)

This PR enables the use of an array data descriptor that contains a
nested data descriptor (e.g., ContainerArray of Arrays). Its contents
can then be viewed normally with View or StructureView.
With this, concepts such as jagged arrays are natively supported in DaCe
(see test for example).
Also adds support for using ctypes pointers and arrays as arguments to
SDFGs.

This PR also refactors the notion of views to a View interface, and
provides views to arrays, structures, and container arrays. It also adds
a syntactic-sugar/helper API to define a view of an existing data
descriptor.

---------

Co-authored-by: Alexandros Nikolaos Ziogas <[email protected]>
  • Loading branch information
tbennun and alexnick83 authored Feb 23, 2024
1 parent 8e2e131 commit 9ee470c
Show file tree
Hide file tree
Showing 14 changed files with 494 additions and 141 deletions.
14 changes: 9 additions & 5 deletions dace/codegen/compiled_sdfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ def _array_interface_ptr(array: Any, storage: dtypes.StorageType) -> int:
"""
if hasattr(array, 'data_ptr'):
return array.data_ptr()
if isinstance(array, ctypes.Array):
return ctypes.addressof(array)

if storage == dtypes.StorageType.GPU_Global:
try:
Expand Down Expand Up @@ -508,13 +510,15 @@ def _construct_args(self, kwargs) -> Tuple[Tuple[Any], Tuple[Any]]:
if atype.optional is False: # If array cannot be None
raise TypeError(f'Passing a None value to a non-optional array in argument "{a}"')
# Otherwise, None values are passed as null pointers below
elif isinstance(arg, ctypes._Pointer):
pass
else:
raise TypeError(f'Passing an object (type {type(arg).__name__}) to an array in argument "{a}"')
elif is_array and not is_dtArray:
# GPU scalars and return values are pointers, so this is fine
if atype.storage != dtypes.StorageType.GPU_Global and not a.startswith('__return'):
raise TypeError(f'Passing an array to a scalar (type {atype.dtype.ctype}) in argument "{a}"')
elif (is_dtArray and is_ndarray and not isinstance(atype, dt.StructArray)
elif (is_dtArray and is_ndarray and not isinstance(atype, dt.ContainerArray)
and atype.dtype.as_numpy_dtype() != arg.dtype):
# Make exception for vector types
if (isinstance(atype.dtype, dtypes.vector) and atype.dtype.vtype.as_numpy_dtype() == arg.dtype):
Expand Down Expand Up @@ -565,14 +569,14 @@ def _construct_args(self, kwargs) -> Tuple[Tuple[Any], Tuple[Any]]:
arg_ctypes = tuple(at.dtype.as_ctypes() for at in argtypes)

constants = self.sdfg.constants
callparams = tuple((arg, actype, atype, aname)
callparams = tuple((actype(arg.get()) if isinstance(arg, symbolic.symbol) else arg, actype, atype, aname)
for arg, actype, atype, aname in zip(arglist, arg_ctypes, argtypes, argnames)
if not (symbolic.issymbolic(arg) and (hasattr(arg, 'name') and arg.name in constants)))

symbols = self._free_symbols
initargs = tuple(
actype(arg) if not isinstance(arg, ctypes._SimpleCData) else arg for arg, actype, atype, aname in callparams
if aname in symbols)
actype(arg) if not isinstance(arg, (ctypes._SimpleCData, ctypes._Pointer)) else arg
for arg, actype, atype, aname in callparams if aname in symbols)

try:
# Replace arrays with their base host/device pointers
Expand All @@ -581,7 +585,7 @@ def _construct_args(self, kwargs) -> Tuple[Tuple[Any], Tuple[Any]]:
if dtypes.is_array(arg):
newargs[i] = ctypes.c_void_p(_array_interface_ptr(
arg, atype.storage)) # `c_void_p` is subclass of `ctypes._SimpleCData`.
elif not isinstance(arg, (ctypes._SimpleCData)):
elif not isinstance(arg, (ctypes._SimpleCData, ctypes._Pointer)):
newargs[i] = actype(arg)
else:
newargs[i] = arg
Expand Down
4 changes: 2 additions & 2 deletions dace/codegen/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -505,11 +505,11 @@ def get_copy_dispatcher(self, src_node, dst_node, edge, sdfg, state):
dst_is_data = True

# Skip copies to/from views where edge matches
if src_is_data and isinstance(src_node.desc(sdfg), (dt.StructureView, dt.View)):
if src_is_data and isinstance(src_node.desc(sdfg), dt.View):
e = sdutil.get_view_edge(state, src_node)
if e is edge:
return None
if dst_is_data and isinstance(dst_node.desc(sdfg), (dt.StructureView, dt.View)):
if dst_is_data and isinstance(dst_node.desc(sdfg), dt.View):
e = sdutil.get_view_edge(state, dst_node)
if e is edge:
return None
Expand Down
42 changes: 36 additions & 6 deletions dace/codegen/targets/cpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ class CPUCodeGen(TargetCodeGenerator):

def _define_sdfg_arguments(self, sdfg, arglist):

# NOTE: Multi-nesting with StructArrays must be further investigated.
# NOTE: Multi-nesting with container arrays must be further investigated.
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.StructArray):
elif isinstance(v, data.ContainerArray):
_visit_structure(v.stype, args, f'{prefix}->{k}')
elif isinstance(v, data.Data):
args[f'{prefix}->{k}'] = v
Expand All @@ -49,10 +49,11 @@ def _visit_structure(struct: data.Structure, args: dict, prefix: str = ''):
if isinstance(arg_type, data.Structure):
desc = sdfg.arrays[name]
_visit_structure(arg_type, args, name)
elif isinstance(arg_type, data.StructArray):
elif isinstance(arg_type, data.ContainerArray):
desc = sdfg.arrays[name]
desc = desc.stype
_visit_structure(desc, args, name)
if isinstance(desc, data.Structure):
_visit_structure(desc, args, name)

for name, arg_type in args.items():
if isinstance(arg_type, data.Scalar):
Expand Down Expand Up @@ -221,6 +222,35 @@ def allocate_view(self, sdfg: SDFG, dfg: SDFGState, state_id: int, node: nodes.A
dtypes.pointer(nodedesc.dtype),
ancestor=0,
is_write=is_write)

# Test for views of container arrays and structs
if isinstance(sdfg.arrays[viewed_dnode.data], (data.Structure, data.ContainerArray, data.ContainerView)):
vdesc = sdfg.arrays[viewed_dnode.data]
ptrname = cpp.ptr(memlet.data, vdesc, sdfg, self._dispatcher.frame)
field_name = None
if is_write and mpath[-1].dst_conn:
field_name = mpath[-1].dst_conn
elif not is_write and mpath[0].src_conn:
field_name = mpath[0].src_conn

# Plain view into a container array
if isinstance(vdesc, data.ContainerArray) and not isinstance(vdesc.stype, data.Structure):
offset = cpp.cpp_offset_expr(vdesc, memlet.subset)
value = f'{ptrname}[{offset}]'
else:
if field_name is not None:
if isinstance(vdesc, data.ContainerArray):
offset = cpp.cpp_offset_expr(vdesc, memlet.subset)
arrexpr = f'{ptrname}[{offset}]'
stype = vdesc.stype
else:
arrexpr = f'{ptrname}'
stype = vdesc

value = f'{arrexpr}->{field_name}'
if isinstance(stype.members[field_name], data.Scalar):
value = '&' + value

if not declared:
ctypedef = dtypes.pointer(nodedesc.dtype).ctype
self._dispatcher.declared_arrays.add(aname, DefinedType.Pointer, ctypedef)
Expand Down Expand Up @@ -358,7 +388,7 @@ def allocate_array(self, sdfg, dfg, state_id, node, nodedesc, function_stream, d
self.allocate_array(sdfg, dfg, state_id, nodes.AccessNode(f"{name}.{k}"), v, function_stream,
declaration_stream, allocation_stream)
return
if isinstance(nodedesc, (data.StructureView, data.View)):
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):
return self.allocate_reference(sdfg, dfg, state_id, node, function_stream, declaration_stream,
Expand Down Expand Up @@ -523,7 +553,7 @@ def deallocate_array(self, sdfg, dfg, state_id, node, nodedesc, function_stream,
dtypes.AllocationLifetime.External)
self._dispatcher.declared_arrays.remove(alloc_name, is_global=is_global)

if isinstance(nodedesc, (data.Scalar, data.StructureView, data.View, data.Stream, data.Reference)):
if isinstance(nodedesc, (data.Scalar, data.View, data.Stream, data.Reference)):
return
elif (nodedesc.storage == dtypes.StorageType.CPU_Heap
or (nodedesc.storage == dtypes.StorageType.Register and symbolic.issymbolic(arrsize, sdfg.constants))):
Expand Down
2 changes: 1 addition & 1 deletion dace/codegen/targets/framecode.py
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@ def determine_allocation_lifetime(self, top_sdfg: SDFG):
instances = access_instances[sdfg.sdfg_id][name]

# A view gets "allocated" everywhere it appears
if isinstance(desc, (data.StructureView, data.View)):
if isinstance(desc, data.View):
for s, n in instances:
self.to_allocate[s].append((sdfg, s, n, False, True, False))
self.to_allocate[s].append((sdfg, s, n, False, False, True))
Expand Down
Loading

0 comments on commit 9ee470c

Please sign in to comment.