[Mesa-dev] [PATCH 111/133] nir: Add infastructure for generating algebraic transformation passes
Dylan Baker
baker.dylan.c at gmail.com
Tue Jan 6 16:35:18 PST 2015
I have a couple of comments, but they're just suggestions.
On Monday, December 15, 2014 10:12:54 PM Jason Ekstrand wrote:
> This commit builds on the nir_search.h infastructure by adds a bit of
> python code that makes it stupid easy to write an algebraic transformation
> pass. The nir_algebraic.py file contains four python classes that
> correspond directly to the datastructures in nir_search.c and allow you to
> easily generate the C code to represent them. Given a list of
> search-and-replace operations, it can then generate a function that applies
> those transformations to a shader.
>
> The transformations can be specified manually, or they can be specified
> using nested tuples. The nested tuples make a neat little language for
> specifying expression trees and search-and-replace operations in a very
> readable and easy-to-edit fasion.
>
> The generated code is also fairly efficient. Insteady of blindly calling
> nir_replace_instr with every single transformation and on every single
> instruction, it uses a switch statement on the instruction opcode to do a
> first-order culling and only calls nir_replace_instr if the opcode is known
> to match the first opcode in the search expression.
> ---
> src/glsl/nir/nir_algebraic.py | 249 ++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 249 insertions(+)
> create mode 100644 src/glsl/nir/nir_algebraic.py
>
> diff --git a/src/glsl/nir/nir_algebraic.py b/src/glsl/nir/nir_algebraic.py
> new file mode 100644
> index 0000000..7718a6d
> --- /dev/null
> +++ b/src/glsl/nir/nir_algebraic.py
> @@ -0,0 +1,249 @@
> +#! /usr/bin/env python
> +#
> +# Copyright (C) 2014 Intel Corporation
> +#
> +# Permission is hereby granted, free of charge, to any person obtaining a
> +# copy of this software and associated documentation files (the "Software"),
> +# to deal in the Software without restriction, including without limitation
> +# the rights to use, copy, modify, merge, publish, distribute, sublicense,
> +# and/or sell copies of the Software, and to permit persons to whom the
> +# Software is furnished to do so, subject to the following conditions:
> +#
> +# The above copyright notice and this permission notice (including the next
> +# paragraph) shall be included in all copies or substantial portions of the
> +# Software.
> +#
> +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
> +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
> +# IN THE SOFTWARE.
> +#
> +# Authors:
> +# Jason Ekstrand (jason at jlekstrand.net)
> +
> +import itertools, struct, sys, mako.template
It's idiomatic to have one import per line
> +
> +# Represents a set of variables, each with a unique id
> +class VarSet(object):
> + def __init__(self):
> + self.names = {}
> + self.ids = itertools.count()
> +
> + def __getitem__(self, name):
> + if name not in self.names:
> + self.names[name] = self.ids.next()
> +
> + return self.names[name]
> +
> +class Value(object):
> + @staticmethod
> + def create(val, name_base, varset):
> + if isinstance(val, tuple):
> + return Expression(val, name_base, varset)
> + elif isinstance(val, Expression):
> + return val
> + elif isinstance(val, str):
(str, unicode)
> + return Variable(val, name_base, varset)
> + elif isinstance(val, (bool, int, long, float)):
> + return Constant(val, name_base)
> +
> + __template = mako.template.Template("""
> +static const ${val.c_type()} ${val.name} = {
> + { ${val.type_enum()} },
> +% if isinstance(val, Constant):
> + { ${hex(val)} /* ${val.value} */ },
> +% elif isinstance(val, Variable):
> + ${val.index}, /* ${val.var_name} */
> +% elif isinstance(val, Expression):
> + nir_op_${val.opcode},
> + { ${', '.join([src.c_ptr() for src in val.sources])} },
> +% endif
> +};""")
> +
> + def __init__(self, name, type_str):
> + self.name = name
> + self.type_str = type_str
> +
> + def type_enum(self):
> + return "nir_search_value_" + self.type_str
You could use the property decorator on these (the one above and two
below) to access them as attributes rather than as functions
> +
> + def c_type(self):
> + return "nir_search_" + self.type_str
> +
> + def c_ptr(self):
> + return "&{0}.value".format(self.name)
> +
> + def render(self):
> + return self.__template.render(val=self,
> + Constant=Constant,
> + Variable=Variable,
> + Expression=Expression)
> +
> +class Constant(Value):
> + def __init__(self, val, name):
> + Value.__init__(self, name, "constant")
> + self.value = val
> +
> + def __hex__(self):
> + # Even if it's an integer, we still need to unpack as an unsigned
> + # int. This is because, without C99, we can only assign to the first
> + # element of a union in an initializer.
> + if isinstance(self.value, (bool)):
> + return 'NIR_TRUE' if self.value else 'NIR_FALSE'
> + if isinstance(self.value, (int, long)):
> + return hex(struct.unpack('I', struct.pack('i', self.value))[0])
> + elif isinstance(self.value, float):
> + return hex(struct.unpack('I', struct.pack('f', self.value))[0])
> + else:
> + assert False
> +
> +class Variable(Value):
> + def __init__(self, val, name, varset):
> + Value.__init__(self, name, "variable")
> + self.var_name = val
> + self.index = varset[val]
> + self.name = name
> +
> +class Expression(Value):
> + def __init__(self, expr, name_base, varset):
> + Value.__init__(self, name_base, "expression")
> + assert isinstance(expr, tuple)
> +
> + self.opcode = expr[0]
> + self.sources = [ Value.create(src, "{0}_{1}".format(name_base, i), varset)
> + for (i, src) in enumerate(expr[1:]) ]
> +
> + def render(self):
> + srcs = "\n".join([src.render() for src in self.sources])
> + return srcs + super(Expression, self).render()
> +
> +_optimization_ids = itertools.count()
> +
> +class SearchAndReplace(object):
> + def __init__(self, search, replace):
> + self.id = _optimization_ids.next()
> +
> + varset = VarSet()
> + if isinstance(search, Expression):
> + self.search = search
> + else:
> + self.search = Expression(search, "search{0}".format(self.id), varset)
> +
> + if isinstance(replace, Value):
> + self.replace = replace
> + else:
> + self.replace = Value.create(replace, "replace{0}".format(self.id), varset)
> +
> +_algebraic_pass_template = mako.template.Template("""
> +#include "nir.h"
> +#include "nir_search.h"
> +
> +struct transform {
> + const nir_search_expression *search;
> + const nir_search_value *replace;
> +};
> +
> +% for (opcode, xform_list) in xform_dict.iteritems():
> +% for xform in xform_list:
mako doesn't care about spaces around it's control flow, you can indent
either before or after the % to make the code more readable
> + ${xform.search.render()}
> + ${xform.replace.render()}
> +% endfor
> +
> +static const struct {
> + const nir_search_expression *search;
> + const nir_search_value *replace;
> +} ${pass_name}_${opcode}_xforms[] = {
> +% for xform in xform_list:
> + { &${xform.search.name}, ${xform.replace.c_ptr()} },
> +% endfor
> +};
> +% endfor
> +
> +struct opt_state {
> + void *mem_ctx;
> + bool progress;
> +};
> +
> +static bool
> +${pass_name}_block(nir_block *block, void *void_state)
> +{
> + struct opt_state *state = void_state;
> +
> + nir_foreach_instr_safe(block, instr) {
> + if (instr->type != nir_instr_type_alu)
> + continue;
> +
> + nir_alu_instr *alu = nir_instr_as_alu(instr);
> + if (!alu->dest.dest.is_ssa)
> + continue;
> +
> + switch (alu->op) {
> + % for opcode in xform_dict.keys():
You could also dedent this by a space or two for readability
> + case nir_op_${opcode}:
> + for (unsigned i = 0; i < ARRAY_SIZE(${pass_name}_${opcode}_xforms); i++) {
> + if (nir_replace_instr(alu, ${pass_name}_${opcode}_xforms[i].search,
> + ${pass_name}_${opcode}_xforms[i].replace,
> + state->mem_ctx))
> + state->progress = true;
> + }
> + break;
> + % endfor
> + default:
> + break;
> + }
> + }
> +
> + return true;
> +}
> +
> +static bool
> +${pass_name}_impl(nir_function_impl *impl)
> +{
> + struct opt_state state;
> +
> + state.mem_ctx = ralloc_parent(impl);
> + state.progress = false;
> +
> + nir_foreach_block(impl, ${pass_name}_block, &state);
> +
> + if (state.progress)
> + nir_metadata_dirty(impl, nir_metadata_block_index |
> + nir_metadata_dominance);
> +
> + return state.progress;
> +}
> +
> +bool
> +${pass_name}(nir_shader *shader)
> +{
> + bool progress = false;
> +
> + nir_foreach_overload(shader, overload) {
> + if (overload->impl)
> + progress |= ${pass_name}_impl(overload->impl);
> + }
> +
> + return progress;
> +}
> +""")
> +
> +class AlgebraicPass(object):
> + def __init__(self, pass_name, transforms):
> + self.xform_dict = {}
> + self.pass_name = pass_name
> +
> + for xform in transforms:
> + if not isinstance(xform, SearchAndReplace):
> + xform = SearchAndReplace(*xform)
> +
> + if xform.search.opcode not in self.xform_dict:
> + self.xform_dict[xform.search.opcode] = []
Rather than doing this consider using collections.defaultdict,
> +
> + self.xform_dict[xform.search.opcode].append(xform)
> +
> + def render(self):
> + return _algebraic_pass_template.render(pass_name=self.pass_name,
> + xform_dict=self.xform_dict)
> --
> 2.2.0
>
> _______________________________________________
> mesa-dev mailing list
> mesa-dev at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/mesa-dev
>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 473 bytes
Desc: This is a digitally signed message part.
URL: <http://lists.freedesktop.org/archives/mesa-dev/attachments/20150106/e12991a0/attachment.sig>
More information about the mesa-dev
mailing list