Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions yateto/codegen/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,12 @@ def generateInitH(self, header):
with header.Namespace(namespace), header.Namespace(self.INIT_NAMESPACE):
for (base_name, base_name_without_namespace), tensors in tensor_dict.items():
self._init(header, base_name, base_name_without_namespace, '', tensors, False)
for namespace, scalar_dict in self.iterate_collect_scalar():
with header.Namespace(namespace), header.Namespace(self.INIT_NAMESPACE):
for (baseName, baseNameWithoutNamespace), scalars in scalar_dict.items():
with header.Struct('{0} : {1}::{0}'.format(baseNameWithoutNamespace, self.TENSOR_NAMESPACE)):
# empty forward declaration
pass

def generateInitCpp(self, cpp):
for namespace, tensor_dict in self.iterate_collect():
Expand Down
8 changes: 7 additions & 1 deletion yateto/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,13 @@ def unit_test_body(cpp, testFramework):
cpp.include(fInit.hName)
with cpp.Namespace(namespace):
initGen.generateInitCpp(cpp)


prefixnsp = lambda a: a.name if a.namespace == '' else f'{a.namespace}::{a.name}'
return {
'namespace': namespace,
'tensors': set(tensor.baseNameWithNamespace() for tensor in tensors.values()) | set(scalar.baseNameWithNamespace() for scalar in scalars),
'kernels': set(prefixnsp(kernel) for kernel in self._kernels) | set(prefixnsp(family) for family in self._kernelFamilies.values())
}

class NamespacedGenerator(object):
def __init__(self, generator, namespace):
Expand Down
142 changes: 142 additions & 0 deletions yateto/metagen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
from .arch import fixArchitectureGlobal
from .codegen.code import Cpp

import os

class MetaGenerator:
def __init__(self, templateType):
self.templateType = templateType
self.generators = []

def add_generator(self, template, generator, *args, **kwargs):
assert len(self.templateType) == len(template)
self.generators += [{
'name': kwargs["name"] if "name" in kwargs else str(len(self.generators)),
'template': template,
'generator': generator,
'args': args,
'kwargs': kwargs
}]

def compile_list(self, outputDir=''):
outfiles = []
for gendata in self.generators:
outdirname = f'metagen_{gendata["name"]}'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a review comment, but a general query: how do you choose which variable name to be camel case, and which variable to be normal like this? For example: outdirname is not camelcase, but the function argument outputDir is camelcase. Is it that all internal variables are not camel cases, and then class members, and function arguments are camel cases?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's say... It's a bit arbitrary. The outputDir being camel case is more a relic since all of Yateto uses that convention quite a bit; especially in the older commits it seems.
And so, outputDir is usually in camel case within Yateto for now.

Usually, per the official Python style guide, snake case is the "official" way to name things; though snake case can sometimes appear a bit hard to read IMO (it just gives you less to focus on—visually at least).

... that made me think; at some point we might wanna apply the whole usual swath of Python linters on Yateto. I've started setting up a pre-commit in #109.

outdir = os.path.join(outputDir, outdirname)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is outputDir defined in this scope? If it is intended to be a global variable, can we please avoid that and pass it as a function argument?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops; missed that one. Made it a function arg.


genout = []
for file in ['tensor', 'init', 'kernel', 'test-kernel']:
genout += [file]
outfiles += [genout]
return outfiles

def generate_single(self, index, outputDir='', namespace='yateto'):
namespacepfx = 'yatetometagen'
gendata = self.generators[index]
subnamespace = f'{namespace}::{namespacepfx}_{gendata["name"]}'
outdirname = f'metagen_{gendata["name"]}'
outdir = os.path.join(outputDir, outdirname)
os.makedirs(outdir, exist_ok=True)

generator = gendata['generator']
template = gendata['template']
args = gendata['args']
kwargs = gendata['kwargs']

fixArchitectureGlobal(generator.arch())
result = generator.generate(*args, **kwargs, namespace=subnamespace, outputDir=outdir)

tensors = {}
kernels = {}

for tensor in result['tensors']:
tensors[tensor] = (subnamespace, template)
for kernel in result['kernels']:
kernels[kernel] = (subnamespace, template)

return tensors, kernels

def generate(self, outputDir='', namespace='yateto', includes=[], declarationsTensors=[], declarationsKernels=[], precompiled=None):
tensors = {}
kernels = {}

for tensor in declarationsTensors:
tensors[tensor] = []
for tensor in declarationsKernels:
kernels[tensor] = []

for index in range(len(self.generators)):
if precompiled is None:
local_tensors, local_kernels = self.generate_single(index, outputDir, namespace)
else:
local_tensors, local_kernels = precompiled[index]

for tensor in local_tensors:
if tensor not in tensors:
tensors[tensor] = []
tensors[tensor] += [local_tensors[tensor]]
for kernel in local_kernels:
if kernel not in kernels:
kernels[kernel] = []
kernels[kernel] += [local_kernels[kernel]]

nspuppercase = namespace.upper()

def headerForward(name, data):
upper = name.upper()
with Cpp(os.path.join(outputDir, f'{name}.h')) as header:
with header.HeaderGuard(f'METAGEN_{nspuppercase}_{upper}_H_'):
for path in includes:
header.include(path)
for gendata in self.generators:
outdirname = f'metagen_{gendata["name"]}'
header.include(f'{outdirname}/{name}.h')
with header.Namespace(namespace):
for entry in data:
self.template(header, entry, data[entry], f'{name}')


headerForward('tensor', tensors)
headerForward('init', tensors)
headerForward('kernel', kernels)

def cppForward(name):
with Cpp(os.path.join(outputDir, f'{name}.cpp')) as header:
for gendata in self.generators:
outdirname = f'metagen_{gendata["name"]}'
header.include(f'{outdirname}/{name}.cpp')

cppForward('tensor')
cppForward('init')
cppForward('kernel')
cppForward('test-kernel')

def namespacing(self, header, spaces, inner):
if len(spaces) == 0:
inner()
else:
with header.Namespace(spaces[0]):
self.namespacing(header, spaces[1:], inner)

def template(self, header, prename, foundin, subnsp):
splitname = prename.split('::')

assert len(splitname) > 0

def inner():
name = splitname[-1]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are we guaranteeing that splitname is never an empty list here? I see Line 122 has splitname = prename.split('::'). So we could either have a check that prename is always consistently formatted, or if splitname is never empty. You could choose which is a better check.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That actually shouldn't happen; since even without a '::' we'll just get a list with a single element, it being the whole string.
Looking it up for certainty (cf. here https://docs.python.org/3.6/library/stdtypes.html#str.split ): since we use split( ) with an argument, it doesn't happen. But it could if we had split(). I can add a comment for that.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if prename is an empty string? Or is it someone we can guarantee would never happen with absolute certainty? I still think it is better to have such checks in the function, so it is self-contained. But if you think it is too redundant, I would yield here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you mean about the split, then: prename = '' => prename.split('::') == [''] .

(i.e. we just get one element which is the whole string)

(can probably be confirmed in any Python console around if needed)

fullname = '::'.join(splitname[:-1] + [subnsp, splitname[-1]])
escname = name.replace(':', '_')
internalName = f'Internal_{escname}'

templatetypes = ', '.join(f'{typ} Arg{i}' for i, typ in enumerate(self.templateType))
templateargs = ', '.join(f'Arg{i}' for i, _ in enumerate(self.templateType))

with header.Namespace('internal'):
header(f'template<{templatetypes}> struct {internalName} {"{"} using Type = void; {"}"};')
for gnsp, spec in foundin:
spectext = ', '.join(str(specpart) for specpart in spec)
header(f'template<> struct {internalName}<{spectext}> {"{"} using Type = ::{gnsp}::{fullname}; {"}"};')
header(f'template<{templatetypes}> using {name} = typename internal::{internalName}<{templateargs}>::Type;')

self.namespacing(header, splitname[:-1] + [subnsp], inner)