[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