nir: move to compiler/
Signed-off-by: Emil Velikov <emil.velikov@collabora.com> Acked-by: Matt Turner <mattst88@gmail.com> Acked-by: Jose Fonseca <jfonseca@vmware.com>
This commit is contained in:
committed by
Emil Velikov
parent
f694da80c7
commit
a39a8fbbaa
@@ -0,0 +1,5 @@
|
||||
nir_builder_opcodes.h
|
||||
nir_opt_algebraic.c
|
||||
nir_opcodes.c
|
||||
nir_opcodes.h
|
||||
nir_constant_expressions.c
|
||||
@@ -0,0 +1,118 @@
|
||||
New IR, or NIR, is an IR for Mesa intended to sit below GLSL IR and Mesa IR.
|
||||
Its design inherits from the various IR's that Mesa has used in the past, as
|
||||
well as Direct3D assembly, and it includes a few new ideas as well. It is a
|
||||
flat (in terms of using instructions instead of expressions), typeless IR,
|
||||
similar to TGSI and Mesa IR. It also supports SSA (although it doesn't require
|
||||
it).
|
||||
|
||||
Variables
|
||||
=========
|
||||
|
||||
NIR includes support for source-level GLSL variables through a structure mostly
|
||||
copied from GLSL IR. These will be used for linking and conversion from GLSL IR
|
||||
(and later, from an AST), but for the most part, they will be lowered to
|
||||
registers (see below) and loads/stores.
|
||||
|
||||
Registers
|
||||
=========
|
||||
|
||||
Registers are light-weight; they consist of a structure that only contains its
|
||||
size, its index for liveness analysis, and an optional name for debugging. In
|
||||
addition, registers can be local to a function or global to the entire shader;
|
||||
the latter will be used in ARB_shader_subroutine for passing parameters and
|
||||
getting return values from subroutines. Registers can also be an array, in which
|
||||
case they can be accessed indirectly. Each ALU instruction (add, subtract, etc.)
|
||||
works directly with registers or SSA values (see below).
|
||||
|
||||
SSA
|
||||
========
|
||||
|
||||
Everywhere a register can be loaded/stored, an SSA value can be used instead.
|
||||
The only exception is that arrays/indirect addressing are not supported with
|
||||
SSA; although research has been done on extensions of SSA to arrays before, it's
|
||||
usually for the purpose of parallelization (which we're not interested in), and
|
||||
adds some overhead in the form of adding copies or extra arrays (which is much
|
||||
more expensive than introducing copies between non-array registers). SSA uses
|
||||
point directly to their corresponding definition, which in turn points to the
|
||||
instruction it is part of. This creates an implicit use-def chain and avoids the
|
||||
need for an external structure for each SSA register.
|
||||
|
||||
Functions
|
||||
=========
|
||||
|
||||
Support for function calls is mostly similar to GLSL IR. Each shader contains a
|
||||
list of functions, and each function has a list of overloads. Each overload
|
||||
contains a list of parameters, and may contain an implementation which specifies
|
||||
the variables that correspond to the parameters and return value. Inlining a
|
||||
function, assuming it has a single return point, is as simple as copying its
|
||||
instructions, registers, and local variables into the target function and then
|
||||
inserting copies to and from the new parameters as appropriate. After functions
|
||||
are inlined and any non-subroutine functions are deleted, parameters and return
|
||||
variables will be converted to global variables and then global registers. We
|
||||
don't do this lowering earlier (i.e. the fortranizer idea) for a few reasons:
|
||||
|
||||
- If we want to do optimizations before link time, we need to have the function
|
||||
signature available during link-time.
|
||||
|
||||
- If we do any inlining before link time, then we might wind up with the
|
||||
inlined function and the non-inlined function using the same global
|
||||
variables/registers which would preclude optimization.
|
||||
|
||||
Intrinsics
|
||||
=========
|
||||
|
||||
Any operation (other than function calls and textures) which touches a variable
|
||||
or is not referentially transparent is represented by an intrinsic. Intrinsics
|
||||
are similar to the idea of a "builtin function," i.e. a function declaration
|
||||
whose implementation is provided by the backend, except they are more powerful
|
||||
in the following ways:
|
||||
|
||||
- They can also load and store registers when appropriate, which limits the
|
||||
number of variables needed in later stages of the IR while obviating the need
|
||||
for a separate load/store variable instruction.
|
||||
|
||||
- Intrinsics can be marked as side-effect free, which permits them to be
|
||||
treated like any other instruction when it comes to optimizations. This allows
|
||||
load intrinsics to be represented as intrinsics while still being optimized
|
||||
away by dead code elimination, common subexpression elimination, etc.
|
||||
|
||||
Intrinsics are used for:
|
||||
|
||||
- Atomic operations
|
||||
- Memory barriers
|
||||
- Subroutine calls
|
||||
- Geometry shader emitVertex and endPrimitive
|
||||
- Loading and storing variables (before lowering)
|
||||
- Loading and storing uniforms, shader inputs and outputs, etc (after lowering)
|
||||
- Copying variables (cases where in GLSL the destination is a structure or
|
||||
array)
|
||||
- The kitchen sink
|
||||
- ...
|
||||
|
||||
Textures
|
||||
=========
|
||||
|
||||
Unfortunately, there are far too many texture operations to represent each one
|
||||
of them with an intrinsic, so there's a special texture instruction similar to
|
||||
the GLSL IR one. The biggest difference is that, while the texture instruction
|
||||
has a sampler dereference field used just like in GLSL IR, this gets lowered to
|
||||
a texture unit index (with a possible indirect offset) while the type
|
||||
information of the original sampler is kept around for backends. Also, all the
|
||||
non-constant sources are stored in a single array to make it easier for
|
||||
optimization passes to iterate over all the sources.
|
||||
|
||||
Control Flow
|
||||
=========
|
||||
|
||||
Like in GLSL IR, control flow consists of a tree of "control flow nodes", which
|
||||
include if statements and loops, and jump instructions (break, continue, and
|
||||
return). Unlike GLSL IR, though, the leaves of the tree aren't statements but
|
||||
basic blocks. Each basic block also keeps track of its successors and
|
||||
predecessors, and function implementations keep track of the beginning basic
|
||||
block (the first basic block of the function) and the ending basic block (a fake
|
||||
basic block that every return statement points to). Together, these elements
|
||||
make up the control flow graph, in this case a redundant piece of information on
|
||||
top of the control flow tree that will be used by almost all the optimizations.
|
||||
There are helper functions to add and remove control flow nodes that also update
|
||||
the control flow graph, and so usually it doesn't need to be touched by passes
|
||||
that modify control flow nodes.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright © 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "glsl/glsl_parser_extras.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
nir_shader *glsl_to_nir(const struct gl_shader_program *shader_prog,
|
||||
gl_shader_stage stage,
|
||||
const nir_shader_compiler_options *options);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,305 @@
|
||||
#! /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@jlekstrand.net)
|
||||
|
||||
import itertools
|
||||
import struct
|
||||
import sys
|
||||
import mako.template
|
||||
import re
|
||||
|
||||
# Represents a set of variables, each with a unique id
|
||||
class VarSet(object):
|
||||
def __init__(self):
|
||||
self.names = {}
|
||||
self.ids = itertools.count()
|
||||
self.immutable = False;
|
||||
|
||||
def __getitem__(self, name):
|
||||
if name not in self.names:
|
||||
assert not self.immutable, "Unknown replacement variable: " + name
|
||||
self.names[name] = self.ids.next()
|
||||
|
||||
return self.names[name]
|
||||
|
||||
def lock(self):
|
||||
self.immutable = True
|
||||
|
||||
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, 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} */
|
||||
${'true' if val.is_constant else 'false'},
|
||||
nir_type_${ val.required_type or 'invalid' },
|
||||
% 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
|
||||
|
||||
@property
|
||||
def type_enum(self):
|
||||
return "nir_search_value_" + self.type_str
|
||||
|
||||
@property
|
||||
def c_type(self):
|
||||
return "nir_search_" + self.type_str
|
||||
|
||||
@property
|
||||
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
|
||||
|
||||
_var_name_re = re.compile(r"(?P<const>#)?(?P<name>\w+)(?:@(?P<type>\w+))?")
|
||||
|
||||
class Variable(Value):
|
||||
def __init__(self, val, name, varset):
|
||||
Value.__init__(self, name, "variable")
|
||||
|
||||
m = _var_name_re.match(val)
|
||||
assert m and m.group('name') is not None
|
||||
|
||||
self.var_name = m.group('name')
|
||||
self.is_constant = m.group('const') is not None
|
||||
self.required_type = m.group('type')
|
||||
|
||||
if self.required_type is not None:
|
||||
assert self.required_type in ('float', 'bool', 'int', 'unsigned')
|
||||
|
||||
self.index = varset[self.var_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()
|
||||
|
||||
condition_list = ['true']
|
||||
|
||||
class SearchAndReplace(object):
|
||||
def __init__(self, transform):
|
||||
self.id = _optimization_ids.next()
|
||||
|
||||
search = transform[0]
|
||||
replace = transform[1]
|
||||
if len(transform) > 2:
|
||||
self.condition = transform[2]
|
||||
else:
|
||||
self.condition = 'true'
|
||||
|
||||
if self.condition not in condition_list:
|
||||
condition_list.append(self.condition)
|
||||
self.condition_index = condition_list.index(self.condition)
|
||||
|
||||
varset = VarSet()
|
||||
if isinstance(search, Expression):
|
||||
self.search = search
|
||||
else:
|
||||
self.search = Expression(search, "search{0}".format(self.id), varset)
|
||||
|
||||
varset.lock()
|
||||
|
||||
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"
|
||||
|
||||
#ifndef NIR_OPT_ALGEBRAIC_STRUCT_DEFS
|
||||
#define NIR_OPT_ALGEBRAIC_STRUCT_DEFS
|
||||
|
||||
struct transform {
|
||||
const nir_search_expression *search;
|
||||
const nir_search_value *replace;
|
||||
unsigned condition_offset;
|
||||
};
|
||||
|
||||
struct opt_state {
|
||||
void *mem_ctx;
|
||||
bool progress;
|
||||
const bool *condition_flags;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
% for (opcode, xform_list) in xform_dict.iteritems():
|
||||
% for xform in xform_list:
|
||||
${xform.search.render()}
|
||||
${xform.replace.render()}
|
||||
% endfor
|
||||
|
||||
static const struct transform ${pass_name}_${opcode}_xforms[] = {
|
||||
% for xform in xform_list:
|
||||
{ &${xform.search.name}, ${xform.replace.c_ptr}, ${xform.condition_index} },
|
||||
% endfor
|
||||
};
|
||||
% endfor
|
||||
|
||||
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():
|
||||
case nir_op_${opcode}:
|
||||
for (unsigned i = 0; i < ARRAY_SIZE(${pass_name}_${opcode}_xforms); i++) {
|
||||
const struct transform *xform = &${pass_name}_${opcode}_xforms[i];
|
||||
if (state->condition_flags[xform->condition_offset] &&
|
||||
nir_replace_instr(alu, xform->search, xform->replace,
|
||||
state->mem_ctx)) {
|
||||
state->progress = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
% endfor
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
${pass_name}_impl(nir_function_impl *impl, const bool *condition_flags)
|
||||
{
|
||||
struct opt_state state;
|
||||
|
||||
state.mem_ctx = ralloc_parent(impl);
|
||||
state.progress = false;
|
||||
state.condition_flags = condition_flags;
|
||||
|
||||
nir_foreach_block(impl, ${pass_name}_block, &state);
|
||||
|
||||
if (state.progress)
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
|
||||
return state.progress;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
${pass_name}(nir_shader *shader)
|
||||
{
|
||||
bool progress = false;
|
||||
bool condition_flags[${len(condition_list)}];
|
||||
const nir_shader_compiler_options *options = shader->options;
|
||||
|
||||
% for index, condition in enumerate(condition_list):
|
||||
condition_flags[${index}] = ${condition};
|
||||
% endfor
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
progress |= ${pass_name}_impl(function->impl, condition_flags);
|
||||
}
|
||||
|
||||
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] = []
|
||||
|
||||
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,
|
||||
condition_list=condition_list)
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright © 2015 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@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
void *mem_ctx;
|
||||
size_t size;
|
||||
size_t alloc;
|
||||
void *data;
|
||||
} nir_array;
|
||||
|
||||
static inline void
|
||||
nir_array_init(nir_array *arr, void *mem_ctx)
|
||||
{
|
||||
arr->mem_ctx = mem_ctx;
|
||||
arr->size = 0;
|
||||
arr->alloc = 0;
|
||||
arr->data = NULL;
|
||||
}
|
||||
|
||||
static inline void
|
||||
nir_array_fini(nir_array *arr)
|
||||
{
|
||||
if (arr->mem_ctx)
|
||||
ralloc_free(arr->data);
|
||||
else
|
||||
free(arr->data);
|
||||
}
|
||||
|
||||
#define NIR_ARRAY_INITIAL_SIZE 64
|
||||
|
||||
/* Increments the size of the array by the given ammount and returns a
|
||||
* pointer to the beginning of the newly added space.
|
||||
*/
|
||||
static inline void *
|
||||
nir_array_grow(nir_array *arr, size_t additional)
|
||||
{
|
||||
size_t new_size = arr->size + additional;
|
||||
if (new_size > arr->alloc) {
|
||||
if (arr->alloc == 0)
|
||||
arr->alloc = NIR_ARRAY_INITIAL_SIZE;
|
||||
|
||||
while (new_size > arr->alloc)
|
||||
arr->alloc *= 2;
|
||||
|
||||
if (arr->mem_ctx)
|
||||
arr->data = reralloc_size(arr->mem_ctx, arr->data, arr->alloc);
|
||||
else
|
||||
arr->data = realloc(arr->data, arr->alloc);
|
||||
}
|
||||
|
||||
void *ptr = (void *)((char *)arr->data + arr->size);
|
||||
arr->size = new_size;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
#define nir_array_add(arr, type, elem) \
|
||||
*(type *)nir_array_grow(arr, sizeof(type)) = (elem)
|
||||
|
||||
#define nir_array_foreach(arr, type, elem) \
|
||||
for (type *elem = (type *)(arr)->data; \
|
||||
elem < (type *)((char *)(arr)->data + (arr)->size); elem++)
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
@@ -0,0 +1,364 @@
|
||||
/*
|
||||
* Copyright © 2014-2015 Broadcom
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef NIR_BUILDER_H
|
||||
#define NIR_BUILDER_H
|
||||
|
||||
#include "nir_control_flow.h"
|
||||
|
||||
struct exec_list;
|
||||
|
||||
typedef struct nir_builder {
|
||||
nir_cursor cursor;
|
||||
|
||||
nir_shader *shader;
|
||||
nir_function_impl *impl;
|
||||
} nir_builder;
|
||||
|
||||
static inline void
|
||||
nir_builder_init(nir_builder *build, nir_function_impl *impl)
|
||||
{
|
||||
memset(build, 0, sizeof(*build));
|
||||
build->impl = impl;
|
||||
build->shader = impl->function->shader;
|
||||
}
|
||||
|
||||
static inline void
|
||||
nir_builder_init_simple_shader(nir_builder *build, void *mem_ctx,
|
||||
gl_shader_stage stage,
|
||||
const nir_shader_compiler_options *options)
|
||||
{
|
||||
build->shader = nir_shader_create(mem_ctx, stage, options);
|
||||
nir_function *func = nir_function_create(build->shader, "main");
|
||||
build->impl = nir_function_impl_create(func);
|
||||
build->cursor = nir_after_cf_list(&build->impl->body);
|
||||
}
|
||||
|
||||
static inline void
|
||||
nir_builder_instr_insert(nir_builder *build, nir_instr *instr)
|
||||
{
|
||||
nir_instr_insert(build->cursor, instr);
|
||||
|
||||
/* Move the cursor forward. */
|
||||
build->cursor = nir_after_instr(instr);
|
||||
}
|
||||
|
||||
static inline void
|
||||
nir_builder_cf_insert(nir_builder *build, nir_cf_node *cf)
|
||||
{
|
||||
nir_cf_node_insert(build->cursor, cf);
|
||||
}
|
||||
|
||||
static inline nir_ssa_def *
|
||||
nir_build_imm(nir_builder *build, unsigned num_components, nir_const_value value)
|
||||
{
|
||||
nir_load_const_instr *load_const =
|
||||
nir_load_const_instr_create(build->shader, num_components);
|
||||
if (!load_const)
|
||||
return NULL;
|
||||
|
||||
load_const->value = value;
|
||||
|
||||
nir_builder_instr_insert(build, &load_const->instr);
|
||||
|
||||
return &load_const->def;
|
||||
}
|
||||
|
||||
static inline nir_ssa_def *
|
||||
nir_imm_float(nir_builder *build, float x)
|
||||
{
|
||||
nir_const_value v;
|
||||
|
||||
memset(&v, 0, sizeof(v));
|
||||
v.f[0] = x;
|
||||
|
||||
return nir_build_imm(build, 1, v);
|
||||
}
|
||||
|
||||
static inline nir_ssa_def *
|
||||
nir_imm_vec4(nir_builder *build, float x, float y, float z, float w)
|
||||
{
|
||||
nir_const_value v;
|
||||
|
||||
memset(&v, 0, sizeof(v));
|
||||
v.f[0] = x;
|
||||
v.f[1] = y;
|
||||
v.f[2] = z;
|
||||
v.f[3] = w;
|
||||
|
||||
return nir_build_imm(build, 4, v);
|
||||
}
|
||||
|
||||
static inline nir_ssa_def *
|
||||
nir_imm_int(nir_builder *build, int x)
|
||||
{
|
||||
nir_const_value v;
|
||||
|
||||
memset(&v, 0, sizeof(v));
|
||||
v.i[0] = x;
|
||||
|
||||
return nir_build_imm(build, 1, v);
|
||||
}
|
||||
|
||||
static inline nir_ssa_def *
|
||||
nir_imm_ivec4(nir_builder *build, int x, int y, int z, int w)
|
||||
{
|
||||
nir_const_value v;
|
||||
|
||||
memset(&v, 0, sizeof(v));
|
||||
v.i[0] = x;
|
||||
v.i[1] = y;
|
||||
v.i[2] = z;
|
||||
v.i[3] = w;
|
||||
|
||||
return nir_build_imm(build, 4, v);
|
||||
}
|
||||
|
||||
static inline nir_ssa_def *
|
||||
nir_build_alu(nir_builder *build, nir_op op, nir_ssa_def *src0,
|
||||
nir_ssa_def *src1, nir_ssa_def *src2, nir_ssa_def *src3)
|
||||
{
|
||||
const nir_op_info *op_info = &nir_op_infos[op];
|
||||
nir_alu_instr *instr = nir_alu_instr_create(build->shader, op);
|
||||
if (!instr)
|
||||
return NULL;
|
||||
|
||||
instr->src[0].src = nir_src_for_ssa(src0);
|
||||
if (src1)
|
||||
instr->src[1].src = nir_src_for_ssa(src1);
|
||||
if (src2)
|
||||
instr->src[2].src = nir_src_for_ssa(src2);
|
||||
if (src3)
|
||||
instr->src[3].src = nir_src_for_ssa(src3);
|
||||
|
||||
/* Guess the number of components the destination temporary should have
|
||||
* based on our input sizes, if it's not fixed for the op.
|
||||
*/
|
||||
unsigned num_components = op_info->output_size;
|
||||
if (num_components == 0) {
|
||||
for (unsigned i = 0; i < op_info->num_inputs; i++) {
|
||||
if (op_info->input_sizes[i] == 0)
|
||||
num_components = MAX2(num_components,
|
||||
instr->src[i].src.ssa->num_components);
|
||||
}
|
||||
}
|
||||
assert(num_components != 0);
|
||||
|
||||
/* Make sure we don't swizzle from outside of our source vector (like if a
|
||||
* scalar value was passed into a multiply with a vector).
|
||||
*/
|
||||
for (unsigned i = 0; i < op_info->num_inputs; i++) {
|
||||
for (unsigned j = instr->src[i].src.ssa->num_components; j < 4; j++) {
|
||||
instr->src[i].swizzle[j] = instr->src[i].src.ssa->num_components - 1;
|
||||
}
|
||||
}
|
||||
|
||||
nir_ssa_dest_init(&instr->instr, &instr->dest.dest, num_components, NULL);
|
||||
instr->dest.write_mask = (1 << num_components) - 1;
|
||||
|
||||
nir_builder_instr_insert(build, &instr->instr);
|
||||
|
||||
return &instr->dest.dest.ssa;
|
||||
}
|
||||
|
||||
#define ALU1(op) \
|
||||
static inline nir_ssa_def * \
|
||||
nir_##op(nir_builder *build, nir_ssa_def *src0) \
|
||||
{ \
|
||||
return nir_build_alu(build, nir_op_##op, src0, NULL, NULL, NULL); \
|
||||
}
|
||||
|
||||
#define ALU2(op) \
|
||||
static inline nir_ssa_def * \
|
||||
nir_##op(nir_builder *build, nir_ssa_def *src0, nir_ssa_def *src1) \
|
||||
{ \
|
||||
return nir_build_alu(build, nir_op_##op, src0, src1, NULL, NULL); \
|
||||
}
|
||||
|
||||
#define ALU3(op) \
|
||||
static inline nir_ssa_def * \
|
||||
nir_##op(nir_builder *build, nir_ssa_def *src0, \
|
||||
nir_ssa_def *src1, nir_ssa_def *src2) \
|
||||
{ \
|
||||
return nir_build_alu(build, nir_op_##op, src0, src1, src2, NULL); \
|
||||
}
|
||||
|
||||
#define ALU4(op) \
|
||||
static inline nir_ssa_def * \
|
||||
nir_##op(nir_builder *build, nir_ssa_def *src0, \
|
||||
nir_ssa_def *src1, nir_ssa_def *src2, nir_ssa_def *src3) \
|
||||
{ \
|
||||
return nir_build_alu(build, nir_op_##op, src0, src1, src2, src3); \
|
||||
}
|
||||
|
||||
#include "nir_builder_opcodes.h"
|
||||
|
||||
static inline nir_ssa_def *
|
||||
nir_vec(nir_builder *build, nir_ssa_def **comp, unsigned num_components)
|
||||
{
|
||||
switch (num_components) {
|
||||
case 4:
|
||||
return nir_vec4(build, comp[0], comp[1], comp[2], comp[3]);
|
||||
case 3:
|
||||
return nir_vec3(build, comp[0], comp[1], comp[2]);
|
||||
case 2:
|
||||
return nir_vec2(build, comp[0], comp[1]);
|
||||
case 1:
|
||||
return comp[0];
|
||||
default:
|
||||
unreachable("bad component count");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to nir_fmov, but takes a nir_alu_src instead of a nir_ssa_def.
|
||||
*/
|
||||
static inline nir_ssa_def *
|
||||
nir_fmov_alu(nir_builder *build, nir_alu_src src, unsigned num_components)
|
||||
{
|
||||
nir_alu_instr *mov = nir_alu_instr_create(build->shader, nir_op_fmov);
|
||||
nir_ssa_dest_init(&mov->instr, &mov->dest.dest, num_components, NULL);
|
||||
mov->dest.write_mask = (1 << num_components) - 1;
|
||||
mov->src[0] = src;
|
||||
nir_builder_instr_insert(build, &mov->instr);
|
||||
|
||||
return &mov->dest.dest.ssa;
|
||||
}
|
||||
|
||||
static inline nir_ssa_def *
|
||||
nir_imov_alu(nir_builder *build, nir_alu_src src, unsigned num_components)
|
||||
{
|
||||
nir_alu_instr *mov = nir_alu_instr_create(build->shader, nir_op_imov);
|
||||
nir_ssa_dest_init(&mov->instr, &mov->dest.dest, num_components, NULL);
|
||||
mov->dest.write_mask = (1 << num_components) - 1;
|
||||
mov->src[0] = src;
|
||||
nir_builder_instr_insert(build, &mov->instr);
|
||||
|
||||
return &mov->dest.dest.ssa;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an fmov or imov that reswizzles the source's components.
|
||||
*/
|
||||
static inline nir_ssa_def *
|
||||
nir_swizzle(nir_builder *build, nir_ssa_def *src, unsigned swiz[4],
|
||||
unsigned num_components, bool use_fmov)
|
||||
{
|
||||
nir_alu_src alu_src = { NIR_SRC_INIT };
|
||||
alu_src.src = nir_src_for_ssa(src);
|
||||
for (unsigned i = 0; i < num_components; i++)
|
||||
alu_src.swizzle[i] = swiz[i];
|
||||
|
||||
return use_fmov ? nir_fmov_alu(build, alu_src, num_components) :
|
||||
nir_imov_alu(build, alu_src, num_components);
|
||||
}
|
||||
|
||||
static inline nir_ssa_def *
|
||||
nir_channel(nir_builder *b, nir_ssa_def *def, unsigned c)
|
||||
{
|
||||
unsigned swizzle[4] = {c, c, c, c};
|
||||
return nir_swizzle(b, def, swizzle, 1, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a nir_src into a nir_ssa_def * so it can be passed to
|
||||
* nir_build_alu()-based builder calls.
|
||||
*
|
||||
* See nir_ssa_for_alu_src() for alu instructions.
|
||||
*/
|
||||
static inline nir_ssa_def *
|
||||
nir_ssa_for_src(nir_builder *build, nir_src src, int num_components)
|
||||
{
|
||||
if (src.is_ssa && src.ssa->num_components == num_components)
|
||||
return src.ssa;
|
||||
|
||||
nir_alu_src alu = { NIR_SRC_INIT };
|
||||
alu.src = src;
|
||||
for (int j = 0; j < 4; j++)
|
||||
alu.swizzle[j] = j;
|
||||
|
||||
return nir_imov_alu(build, alu, num_components);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to nir_ssa_for_src(), but for alu src's, respecting the
|
||||
* nir_alu_src's swizzle.
|
||||
*/
|
||||
static inline nir_ssa_def *
|
||||
nir_ssa_for_alu_src(nir_builder *build, nir_alu_instr *instr, unsigned srcn)
|
||||
{
|
||||
static uint8_t trivial_swizzle[4] = { 0, 1, 2, 3 };
|
||||
nir_alu_src *src = &instr->src[srcn];
|
||||
unsigned num_components = nir_ssa_alu_instr_src_components(instr, srcn);
|
||||
|
||||
if (src->src.is_ssa && (src->src.ssa->num_components == num_components) &&
|
||||
!src->abs && !src->negate &&
|
||||
(memcmp(src->swizzle, trivial_swizzle, num_components) == 0))
|
||||
return src->src.ssa;
|
||||
|
||||
return nir_imov_alu(build, *src, num_components);
|
||||
}
|
||||
|
||||
static inline nir_ssa_def *
|
||||
nir_load_var(nir_builder *build, nir_variable *var)
|
||||
{
|
||||
const unsigned num_components = glsl_get_vector_elements(var->type);
|
||||
|
||||
nir_intrinsic_instr *load =
|
||||
nir_intrinsic_instr_create(build->shader, nir_intrinsic_load_var);
|
||||
load->num_components = num_components;
|
||||
load->variables[0] = nir_deref_var_create(load, var);
|
||||
nir_ssa_dest_init(&load->instr, &load->dest, num_components, NULL);
|
||||
nir_builder_instr_insert(build, &load->instr);
|
||||
return &load->dest.ssa;
|
||||
}
|
||||
|
||||
static inline void
|
||||
nir_store_var(nir_builder *build, nir_variable *var, nir_ssa_def *value,
|
||||
unsigned writemask)
|
||||
{
|
||||
const unsigned num_components = glsl_get_vector_elements(var->type);
|
||||
|
||||
nir_intrinsic_instr *store =
|
||||
nir_intrinsic_instr_create(build->shader, nir_intrinsic_store_var);
|
||||
store->num_components = num_components;
|
||||
store->const_index[0] = writemask;
|
||||
store->variables[0] = nir_deref_var_create(store, var);
|
||||
store->src[0] = nir_src_for_ssa(value);
|
||||
nir_builder_instr_insert(build, &store->instr);
|
||||
}
|
||||
|
||||
static inline nir_ssa_def *
|
||||
nir_load_system_value(nir_builder *build, nir_intrinsic_op op, int index)
|
||||
{
|
||||
nir_intrinsic_instr *load = nir_intrinsic_instr_create(build->shader, op);
|
||||
load->num_components = nir_intrinsic_infos[op].dest_components;
|
||||
load->const_index[0] = index;
|
||||
nir_ssa_dest_init(&load->instr, &load->dest,
|
||||
nir_intrinsic_infos[op].dest_components, NULL);
|
||||
nir_builder_instr_insert(build, &load->instr);
|
||||
return &load->dest.ssa;
|
||||
}
|
||||
|
||||
#endif /* NIR_BUILDER_H */
|
||||
@@ -0,0 +1,38 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
template = """\
|
||||
/* Copyright (C) 2015 Broadcom
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _NIR_BUILDER_OPCODES_
|
||||
#define _NIR_BUILDER_OPCODES_
|
||||
|
||||
% for name, opcode in sorted(opcodes.iteritems()):
|
||||
ALU${opcode.num_inputs}(${name});
|
||||
% endfor
|
||||
|
||||
#endif /* _NIR_BUILDER_OPCODES_ */"""
|
||||
|
||||
from nir_opcodes import opcodes
|
||||
from mako.template import Template
|
||||
|
||||
print Template(template).render(opcodes=opcodes)
|
||||
@@ -0,0 +1,659 @@
|
||||
/*
|
||||
* Copyright © 2015 Red Hat
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "nir_control_flow_private.h"
|
||||
|
||||
/* Secret Decoder Ring:
|
||||
* clone_foo():
|
||||
* Allocate and clone a foo.
|
||||
* __clone_foo():
|
||||
* Clone body of foo (ie. parent class, embedded struct, etc)
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
/* maps orig ptr -> cloned ptr: */
|
||||
struct hash_table *ptr_table;
|
||||
|
||||
/* List of phi sources. */
|
||||
struct list_head phi_srcs;
|
||||
|
||||
/* new shader object, used as memctx for just about everything else: */
|
||||
nir_shader *ns;
|
||||
} clone_state;
|
||||
|
||||
static void
|
||||
init_clone_state(clone_state *state)
|
||||
{
|
||||
state->ptr_table = _mesa_hash_table_create(NULL, _mesa_hash_pointer,
|
||||
_mesa_key_pointer_equal);
|
||||
list_inithead(&state->phi_srcs);
|
||||
}
|
||||
|
||||
static void
|
||||
free_clone_state(clone_state *state)
|
||||
{
|
||||
_mesa_hash_table_destroy(state->ptr_table, NULL);
|
||||
}
|
||||
|
||||
static void *
|
||||
lookup_ptr(clone_state *state, const void *ptr)
|
||||
{
|
||||
struct hash_entry *entry;
|
||||
|
||||
if (!ptr)
|
||||
return NULL;
|
||||
|
||||
entry = _mesa_hash_table_search(state->ptr_table, ptr);
|
||||
assert(entry && "Failed to find pointer!");
|
||||
if (!entry)
|
||||
return NULL;
|
||||
|
||||
return entry->data;
|
||||
}
|
||||
|
||||
static void
|
||||
store_ptr(clone_state *state, void *nptr, const void *ptr)
|
||||
{
|
||||
_mesa_hash_table_insert(state->ptr_table, ptr, nptr);
|
||||
}
|
||||
|
||||
static nir_constant *
|
||||
clone_constant(clone_state *state, const nir_constant *c, nir_variable *nvar)
|
||||
{
|
||||
nir_constant *nc = ralloc(nvar, nir_constant);
|
||||
|
||||
nc->value = c->value;
|
||||
nc->num_elements = c->num_elements;
|
||||
nc->elements = ralloc_array(nvar, nir_constant *, c->num_elements);
|
||||
for (unsigned i = 0; i < c->num_elements; i++) {
|
||||
nc->elements[i] = clone_constant(state, c->elements[i], nvar);
|
||||
}
|
||||
|
||||
return nc;
|
||||
}
|
||||
|
||||
/* NOTE: for cloning nir_variable's, bypass nir_variable_create to avoid
|
||||
* having to deal with locals and globals separately:
|
||||
*/
|
||||
static nir_variable *
|
||||
clone_variable(clone_state *state, const nir_variable *var)
|
||||
{
|
||||
nir_variable *nvar = rzalloc(state->ns, nir_variable);
|
||||
store_ptr(state, nvar, var);
|
||||
|
||||
nvar->type = var->type;
|
||||
nvar->name = ralloc_strdup(nvar, var->name);
|
||||
nvar->data = var->data;
|
||||
nvar->num_state_slots = var->num_state_slots;
|
||||
nvar->state_slots = ralloc_array(nvar, nir_state_slot, var->num_state_slots);
|
||||
memcpy(nvar->state_slots, var->state_slots,
|
||||
var->num_state_slots * sizeof(nir_state_slot));
|
||||
if (var->constant_initializer) {
|
||||
nvar->constant_initializer =
|
||||
clone_constant(state, var->constant_initializer, nvar);
|
||||
}
|
||||
nvar->interface_type = var->interface_type;
|
||||
|
||||
return nvar;
|
||||
}
|
||||
|
||||
/* clone list of nir_variable: */
|
||||
static void
|
||||
clone_var_list(clone_state *state, struct exec_list *dst,
|
||||
const struct exec_list *list)
|
||||
{
|
||||
exec_list_make_empty(dst);
|
||||
foreach_list_typed(nir_variable, var, node, list) {
|
||||
nir_variable *nvar = clone_variable(state, var);
|
||||
exec_list_push_tail(dst, &nvar->node);
|
||||
}
|
||||
}
|
||||
|
||||
/* NOTE: for cloning nir_register's, bypass nir_global/local_reg_create()
|
||||
* to avoid having to deal with locals and globals separately:
|
||||
*/
|
||||
static nir_register *
|
||||
clone_register(clone_state *state, const nir_register *reg)
|
||||
{
|
||||
nir_register *nreg = rzalloc(state->ns, nir_register);
|
||||
store_ptr(state, nreg, reg);
|
||||
|
||||
nreg->num_components = reg->num_components;
|
||||
nreg->num_array_elems = reg->num_array_elems;
|
||||
nreg->index = reg->index;
|
||||
nreg->name = ralloc_strdup(nreg, reg->name);
|
||||
nreg->is_global = reg->is_global;
|
||||
nreg->is_packed = reg->is_packed;
|
||||
|
||||
/* reconstructing uses/defs/if_uses handled by nir_instr_insert() */
|
||||
list_inithead(&nreg->uses);
|
||||
list_inithead(&nreg->defs);
|
||||
list_inithead(&nreg->if_uses);
|
||||
|
||||
return nreg;
|
||||
}
|
||||
|
||||
/* clone list of nir_register: */
|
||||
static void
|
||||
clone_reg_list(clone_state *state, struct exec_list *dst,
|
||||
const struct exec_list *list)
|
||||
{
|
||||
exec_list_make_empty(dst);
|
||||
foreach_list_typed(nir_register, reg, node, list) {
|
||||
nir_register *nreg = clone_register(state, reg);
|
||||
exec_list_push_tail(dst, &nreg->node);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
__clone_src(clone_state *state, void *ninstr_or_if,
|
||||
nir_src *nsrc, const nir_src *src)
|
||||
{
|
||||
nsrc->is_ssa = src->is_ssa;
|
||||
if (src->is_ssa) {
|
||||
nsrc->ssa = lookup_ptr(state, src->ssa);
|
||||
} else {
|
||||
nsrc->reg.reg = lookup_ptr(state, src->reg.reg);
|
||||
if (src->reg.indirect) {
|
||||
nsrc->reg.indirect = ralloc(ninstr_or_if, nir_src);
|
||||
__clone_src(state, ninstr_or_if, nsrc->reg.indirect, src->reg.indirect);
|
||||
}
|
||||
nsrc->reg.base_offset = src->reg.base_offset;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
__clone_dst(clone_state *state, nir_instr *ninstr,
|
||||
nir_dest *ndst, const nir_dest *dst)
|
||||
{
|
||||
ndst->is_ssa = dst->is_ssa;
|
||||
if (dst->is_ssa) {
|
||||
nir_ssa_dest_init(ninstr, ndst, dst->ssa.num_components, dst->ssa.name);
|
||||
store_ptr(state, &ndst->ssa, &dst->ssa);
|
||||
} else {
|
||||
ndst->reg.reg = lookup_ptr(state, dst->reg.reg);
|
||||
if (dst->reg.indirect) {
|
||||
ndst->reg.indirect = ralloc(ninstr, nir_src);
|
||||
__clone_src(state, ninstr, ndst->reg.indirect, dst->reg.indirect);
|
||||
}
|
||||
ndst->reg.base_offset = dst->reg.base_offset;
|
||||
}
|
||||
}
|
||||
|
||||
static nir_deref *clone_deref(clone_state *state, const nir_deref *deref,
|
||||
nir_instr *ninstr, nir_deref *parent);
|
||||
|
||||
static nir_deref_var *
|
||||
clone_deref_var(clone_state *state, const nir_deref_var *dvar,
|
||||
nir_instr *ninstr)
|
||||
{
|
||||
nir_variable *nvar = lookup_ptr(state, dvar->var);
|
||||
nir_deref_var *ndvar = nir_deref_var_create(ninstr, nvar);
|
||||
|
||||
if (dvar->deref.child)
|
||||
ndvar->deref.child = clone_deref(state, dvar->deref.child,
|
||||
ninstr, &ndvar->deref);
|
||||
|
||||
return ndvar;
|
||||
}
|
||||
|
||||
static nir_deref_array *
|
||||
clone_deref_array(clone_state *state, const nir_deref_array *darr,
|
||||
nir_instr *ninstr, nir_deref *parent)
|
||||
{
|
||||
nir_deref_array *ndarr = nir_deref_array_create(parent);
|
||||
|
||||
ndarr->deref.type = darr->deref.type;
|
||||
if (darr->deref.child)
|
||||
ndarr->deref.child = clone_deref(state, darr->deref.child,
|
||||
ninstr, &ndarr->deref);
|
||||
|
||||
ndarr->deref_array_type = darr->deref_array_type;
|
||||
ndarr->base_offset = darr->base_offset;
|
||||
if (ndarr->deref_array_type == nir_deref_array_type_indirect)
|
||||
__clone_src(state, ninstr, &ndarr->indirect, &darr->indirect);
|
||||
|
||||
return ndarr;
|
||||
}
|
||||
|
||||
static nir_deref_struct *
|
||||
clone_deref_struct(clone_state *state, const nir_deref_struct *dstr,
|
||||
nir_instr *ninstr, nir_deref *parent)
|
||||
{
|
||||
nir_deref_struct *ndstr = nir_deref_struct_create(parent, dstr->index);
|
||||
|
||||
ndstr->deref.type = dstr->deref.type;
|
||||
if (dstr->deref.child)
|
||||
ndstr->deref.child = clone_deref(state, dstr->deref.child,
|
||||
ninstr, &ndstr->deref);
|
||||
|
||||
return ndstr;
|
||||
}
|
||||
|
||||
static nir_deref *
|
||||
clone_deref(clone_state *state, const nir_deref *dref,
|
||||
nir_instr *ninstr, nir_deref *parent)
|
||||
{
|
||||
switch (dref->deref_type) {
|
||||
case nir_deref_type_array:
|
||||
return &clone_deref_array(state, nir_deref_as_array(dref),
|
||||
ninstr, parent)->deref;
|
||||
case nir_deref_type_struct:
|
||||
return &clone_deref_struct(state, nir_deref_as_struct(dref),
|
||||
ninstr, parent)->deref;
|
||||
default:
|
||||
unreachable("bad deref type");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static nir_alu_instr *
|
||||
clone_alu(clone_state *state, const nir_alu_instr *alu)
|
||||
{
|
||||
nir_alu_instr *nalu = nir_alu_instr_create(state->ns, alu->op);
|
||||
|
||||
__clone_dst(state, &nalu->instr, &nalu->dest.dest, &alu->dest.dest);
|
||||
nalu->dest.saturate = alu->dest.saturate;
|
||||
nalu->dest.write_mask = alu->dest.write_mask;
|
||||
|
||||
for (unsigned i = 0; i < nir_op_infos[alu->op].num_inputs; i++) {
|
||||
__clone_src(state, &nalu->instr, &nalu->src[i].src, &alu->src[i].src);
|
||||
nalu->src[i].negate = alu->src[i].negate;
|
||||
nalu->src[i].abs = alu->src[i].abs;
|
||||
memcpy(nalu->src[i].swizzle, alu->src[i].swizzle,
|
||||
sizeof(nalu->src[i].swizzle));
|
||||
}
|
||||
|
||||
return nalu;
|
||||
}
|
||||
|
||||
static nir_intrinsic_instr *
|
||||
clone_intrinsic(clone_state *state, const nir_intrinsic_instr *itr)
|
||||
{
|
||||
nir_intrinsic_instr *nitr =
|
||||
nir_intrinsic_instr_create(state->ns, itr->intrinsic);
|
||||
|
||||
unsigned num_variables = nir_intrinsic_infos[itr->intrinsic].num_variables;
|
||||
unsigned num_srcs = nir_intrinsic_infos[itr->intrinsic].num_srcs;
|
||||
|
||||
if (nir_intrinsic_infos[itr->intrinsic].has_dest)
|
||||
__clone_dst(state, &nitr->instr, &nitr->dest, &itr->dest);
|
||||
|
||||
nitr->num_components = itr->num_components;
|
||||
memcpy(nitr->const_index, itr->const_index, sizeof(nitr->const_index));
|
||||
|
||||
for (unsigned i = 0; i < num_variables; i++) {
|
||||
nitr->variables[i] = clone_deref_var(state, itr->variables[i],
|
||||
&nitr->instr);
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < num_srcs; i++)
|
||||
__clone_src(state, &nitr->instr, &nitr->src[i], &itr->src[i]);
|
||||
|
||||
return nitr;
|
||||
}
|
||||
|
||||
static nir_load_const_instr *
|
||||
clone_load_const(clone_state *state, const nir_load_const_instr *lc)
|
||||
{
|
||||
nir_load_const_instr *nlc =
|
||||
nir_load_const_instr_create(state->ns, lc->def.num_components);
|
||||
|
||||
memcpy(&nlc->value, &lc->value, sizeof(nlc->value));
|
||||
|
||||
store_ptr(state, &nlc->def, &lc->def);
|
||||
|
||||
return nlc;
|
||||
}
|
||||
|
||||
static nir_ssa_undef_instr *
|
||||
clone_ssa_undef(clone_state *state, const nir_ssa_undef_instr *sa)
|
||||
{
|
||||
nir_ssa_undef_instr *nsa =
|
||||
nir_ssa_undef_instr_create(state->ns, sa->def.num_components);
|
||||
|
||||
store_ptr(state, &nsa->def, &sa->def);
|
||||
|
||||
return nsa;
|
||||
}
|
||||
|
||||
static nir_tex_instr *
|
||||
clone_tex(clone_state *state, const nir_tex_instr *tex)
|
||||
{
|
||||
nir_tex_instr *ntex = nir_tex_instr_create(state->ns, tex->num_srcs);
|
||||
|
||||
ntex->sampler_dim = tex->sampler_dim;
|
||||
ntex->dest_type = tex->dest_type;
|
||||
ntex->op = tex->op;
|
||||
__clone_dst(state, &ntex->instr, &ntex->dest, &tex->dest);
|
||||
for (unsigned i = 0; i < ntex->num_srcs; i++) {
|
||||
ntex->src[i].src_type = tex->src[i].src_type;
|
||||
__clone_src(state, &ntex->instr, &ntex->src[i].src, &tex->src[i].src);
|
||||
}
|
||||
ntex->coord_components = tex->coord_components;
|
||||
ntex->is_array = tex->is_array;
|
||||
ntex->is_shadow = tex->is_shadow;
|
||||
ntex->is_new_style_shadow = tex->is_new_style_shadow;
|
||||
memcpy(ntex->const_offset, tex->const_offset, sizeof(ntex->const_offset));
|
||||
ntex->component = tex->component;
|
||||
ntex->sampler_index = tex->sampler_index;
|
||||
ntex->sampler_array_size = tex->sampler_array_size;
|
||||
if (tex->sampler)
|
||||
ntex->sampler = clone_deref_var(state, tex->sampler, &ntex->instr);
|
||||
|
||||
return ntex;
|
||||
}
|
||||
|
||||
static nir_phi_instr *
|
||||
clone_phi(clone_state *state, const nir_phi_instr *phi, nir_block *nblk)
|
||||
{
|
||||
nir_phi_instr *nphi = nir_phi_instr_create(state->ns);
|
||||
|
||||
__clone_dst(state, &nphi->instr, &nphi->dest, &phi->dest);
|
||||
|
||||
/* Cloning a phi node is a bit different from other instructions. The
|
||||
* sources of phi instructions are the only time where we can use an SSA
|
||||
* def before it is defined. In order to handle this, we just copy over
|
||||
* the sources from the old phi instruction directly and then fix them up
|
||||
* in a second pass once all the instrutions in the function have been
|
||||
* properly cloned.
|
||||
*
|
||||
* In order to ensure that the copied sources (which are the same as the
|
||||
* old phi instruction's sources for now) don't get inserted into the old
|
||||
* shader's use-def lists, we have to add the phi instruction *before* we
|
||||
* set up its sources.
|
||||
*/
|
||||
nir_instr_insert_after_block(nblk, &nphi->instr);
|
||||
|
||||
foreach_list_typed(nir_phi_src, src, node, &phi->srcs) {
|
||||
nir_phi_src *nsrc = ralloc(nphi, nir_phi_src);
|
||||
|
||||
/* Just copy the old source for now. */
|
||||
memcpy(nsrc, src, sizeof(*src));
|
||||
|
||||
/* Since we're not letting nir_insert_instr handle use/def stuff for us,
|
||||
* we have to set the parent_instr manually. It doesn't really matter
|
||||
* when we do it, so we might as well do it here.
|
||||
*/
|
||||
nsrc->src.parent_instr = &nphi->instr;
|
||||
|
||||
/* Stash it in the list of phi sources. We'll walk this list and fix up
|
||||
* sources at the very end of clone_function_impl.
|
||||
*/
|
||||
list_add(&nsrc->src.use_link, &state->phi_srcs);
|
||||
|
||||
exec_list_push_tail(&nphi->srcs, &nsrc->node);
|
||||
}
|
||||
|
||||
return nphi;
|
||||
}
|
||||
|
||||
static nir_jump_instr *
|
||||
clone_jump(clone_state *state, const nir_jump_instr *jmp)
|
||||
{
|
||||
nir_jump_instr *njmp = nir_jump_instr_create(state->ns, jmp->type);
|
||||
|
||||
return njmp;
|
||||
}
|
||||
|
||||
static nir_call_instr *
|
||||
clone_call(clone_state *state, const nir_call_instr *call)
|
||||
{
|
||||
nir_function *ncallee = lookup_ptr(state, call->callee);
|
||||
nir_call_instr *ncall = nir_call_instr_create(state->ns, ncallee);
|
||||
|
||||
for (unsigned i = 0; i < ncall->num_params; i++)
|
||||
ncall->params[i] = clone_deref_var(state, call->params[i], &ncall->instr);
|
||||
|
||||
ncall->return_deref = clone_deref_var(state, call->return_deref,
|
||||
&ncall->instr);
|
||||
|
||||
return ncall;
|
||||
}
|
||||
|
||||
static nir_instr *
|
||||
clone_instr(clone_state *state, const nir_instr *instr)
|
||||
{
|
||||
switch (instr->type) {
|
||||
case nir_instr_type_alu:
|
||||
return &clone_alu(state, nir_instr_as_alu(instr))->instr;
|
||||
case nir_instr_type_intrinsic:
|
||||
return &clone_intrinsic(state, nir_instr_as_intrinsic(instr))->instr;
|
||||
case nir_instr_type_load_const:
|
||||
return &clone_load_const(state, nir_instr_as_load_const(instr))->instr;
|
||||
case nir_instr_type_ssa_undef:
|
||||
return &clone_ssa_undef(state, nir_instr_as_ssa_undef(instr))->instr;
|
||||
case nir_instr_type_tex:
|
||||
return &clone_tex(state, nir_instr_as_tex(instr))->instr;
|
||||
case nir_instr_type_phi:
|
||||
unreachable("Cannot clone phis with clone_instr");
|
||||
case nir_instr_type_jump:
|
||||
return &clone_jump(state, nir_instr_as_jump(instr))->instr;
|
||||
case nir_instr_type_call:
|
||||
return &clone_call(state, nir_instr_as_call(instr))->instr;
|
||||
case nir_instr_type_parallel_copy:
|
||||
unreachable("Cannot clone parallel copies");
|
||||
default:
|
||||
unreachable("bad instr type");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static nir_block *
|
||||
clone_block(clone_state *state, struct exec_list *cf_list, const nir_block *blk)
|
||||
{
|
||||
/* Don't actually create a new block. Just use the one from the tail of
|
||||
* the list. NIR guarantees that the tail of the list is a block and that
|
||||
* no two blocks are side-by-side in the IR; It should be empty.
|
||||
*/
|
||||
nir_block *nblk =
|
||||
exec_node_data(nir_block, exec_list_get_tail(cf_list), cf_node.node);
|
||||
assert(nblk->cf_node.type == nir_cf_node_block);
|
||||
assert(exec_list_is_empty(&nblk->instr_list));
|
||||
|
||||
/* We need this for phi sources */
|
||||
store_ptr(state, nblk, blk);
|
||||
|
||||
nir_foreach_instr(blk, instr) {
|
||||
if (instr->type == nir_instr_type_phi) {
|
||||
/* Phi instructions are a bit of a special case when cloning because
|
||||
* we don't want inserting the instruction to automatically handle
|
||||
* use/defs for us. Instead, we need to wait until all the
|
||||
* blocks/instructions are in so that we can set their sources up.
|
||||
*/
|
||||
clone_phi(state, nir_instr_as_phi(instr), nblk);
|
||||
} else {
|
||||
nir_instr *ninstr = clone_instr(state, instr);
|
||||
nir_instr_insert_after_block(nblk, ninstr);
|
||||
}
|
||||
}
|
||||
|
||||
return nblk;
|
||||
}
|
||||
|
||||
static void
|
||||
clone_cf_list(clone_state *state, struct exec_list *dst,
|
||||
const struct exec_list *list);
|
||||
|
||||
static nir_if *
|
||||
clone_if(clone_state *state, struct exec_list *cf_list, const nir_if *i)
|
||||
{
|
||||
nir_if *ni = nir_if_create(state->ns);
|
||||
|
||||
__clone_src(state, ni, &ni->condition, &i->condition);
|
||||
|
||||
nir_cf_node_insert_end(cf_list, &ni->cf_node);
|
||||
|
||||
clone_cf_list(state, &ni->then_list, &i->then_list);
|
||||
clone_cf_list(state, &ni->else_list, &i->else_list);
|
||||
|
||||
return ni;
|
||||
}
|
||||
|
||||
static nir_loop *
|
||||
clone_loop(clone_state *state, struct exec_list *cf_list, const nir_loop *loop)
|
||||
{
|
||||
nir_loop *nloop = nir_loop_create(state->ns);
|
||||
|
||||
nir_cf_node_insert_end(cf_list, &nloop->cf_node);
|
||||
|
||||
clone_cf_list(state, &nloop->body, &loop->body);
|
||||
|
||||
return nloop;
|
||||
}
|
||||
|
||||
/* clone list of nir_cf_node: */
|
||||
static void
|
||||
clone_cf_list(clone_state *state, struct exec_list *dst,
|
||||
const struct exec_list *list)
|
||||
{
|
||||
foreach_list_typed(nir_cf_node, cf, node, list) {
|
||||
switch (cf->type) {
|
||||
case nir_cf_node_block:
|
||||
clone_block(state, dst, nir_cf_node_as_block(cf));
|
||||
break;
|
||||
case nir_cf_node_if:
|
||||
clone_if(state, dst, nir_cf_node_as_if(cf));
|
||||
break;
|
||||
case nir_cf_node_loop:
|
||||
clone_loop(state, dst, nir_cf_node_as_loop(cf));
|
||||
break;
|
||||
default:
|
||||
unreachable("bad cf type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static nir_function_impl *
|
||||
clone_function_impl(clone_state *state, const nir_function_impl *fi,
|
||||
nir_function *nfxn)
|
||||
{
|
||||
nir_function_impl *nfi = nir_function_impl_create(nfxn);
|
||||
|
||||
clone_var_list(state, &nfi->locals, &fi->locals);
|
||||
clone_reg_list(state, &nfi->registers, &fi->registers);
|
||||
nfi->reg_alloc = fi->reg_alloc;
|
||||
|
||||
nfi->num_params = fi->num_params;
|
||||
nfi->params = ralloc_array(state->ns, nir_variable *, fi->num_params);
|
||||
for (unsigned i = 0; i < fi->num_params; i++) {
|
||||
nfi->params[i] = lookup_ptr(state, fi->params[i]);
|
||||
}
|
||||
nfi->return_var = lookup_ptr(state, fi->return_var);
|
||||
|
||||
assert(list_empty(&state->phi_srcs));
|
||||
|
||||
clone_cf_list(state, &nfi->body, &fi->body);
|
||||
|
||||
/* After we've cloned almost everything, we have to walk the list of phi
|
||||
* sources and fix them up. Thanks to loops, the block and SSA value for a
|
||||
* phi source may not be defined when we first encounter it. Instead, we
|
||||
* add it to the phi_srcs list and we fix it up here.
|
||||
*/
|
||||
list_for_each_entry_safe(nir_phi_src, src, &state->phi_srcs, src.use_link) {
|
||||
src->pred = lookup_ptr(state, src->pred);
|
||||
assert(src->src.is_ssa);
|
||||
src->src.ssa = lookup_ptr(state, src->src.ssa);
|
||||
|
||||
/* Remove from this list and place in the uses of the SSA def */
|
||||
list_del(&src->src.use_link);
|
||||
list_addtail(&src->src.use_link, &src->src.ssa->uses);
|
||||
}
|
||||
assert(list_empty(&state->phi_srcs));
|
||||
|
||||
/* All metadata is invalidated in the cloning process */
|
||||
nfi->valid_metadata = 0;
|
||||
|
||||
return nfi;
|
||||
}
|
||||
|
||||
static nir_function *
|
||||
clone_function(clone_state *state, const nir_function *fxn, nir_shader *ns)
|
||||
{
|
||||
assert(ns == state->ns);
|
||||
nir_function *nfxn = nir_function_create(ns, fxn->name);
|
||||
|
||||
/* Needed for call instructions */
|
||||
store_ptr(state, nfxn, fxn);
|
||||
|
||||
nfxn->num_params = fxn->num_params;
|
||||
nfxn->params = ralloc_array(state->ns, nir_parameter, fxn->num_params);
|
||||
memcpy(nfxn->params, fxn->params, sizeof(nir_parameter) * fxn->num_params);
|
||||
|
||||
nfxn->return_type = fxn->return_type;
|
||||
|
||||
/* At first glance, it looks like we should clone the function_impl here.
|
||||
* However, call instructions need to be able to reference at least the
|
||||
* function and those will get processed as we clone the function_impl's.
|
||||
* We stop here and do function_impls as a second pass.
|
||||
*/
|
||||
|
||||
return nfxn;
|
||||
}
|
||||
|
||||
nir_shader *
|
||||
nir_shader_clone(void *mem_ctx, const nir_shader *s)
|
||||
{
|
||||
clone_state state;
|
||||
init_clone_state(&state);
|
||||
|
||||
nir_shader *ns = nir_shader_create(mem_ctx, s->stage, s->options);
|
||||
state.ns = ns;
|
||||
|
||||
clone_var_list(&state, &ns->uniforms, &s->uniforms);
|
||||
clone_var_list(&state, &ns->inputs, &s->inputs);
|
||||
clone_var_list(&state, &ns->outputs, &s->outputs);
|
||||
clone_var_list(&state, &ns->globals, &s->globals);
|
||||
clone_var_list(&state, &ns->system_values, &s->system_values);
|
||||
|
||||
/* Go through and clone functions */
|
||||
foreach_list_typed(nir_function, fxn, node, &s->functions)
|
||||
clone_function(&state, fxn, ns);
|
||||
|
||||
/* Only after all functions are cloned can we clone the actual function
|
||||
* implementations. This is because nir_call_instr's need to reference the
|
||||
* functions of other functions and we don't know what order the functions
|
||||
* will have in the list.
|
||||
*/
|
||||
nir_foreach_function(s, fxn) {
|
||||
nir_function *nfxn = lookup_ptr(&state, fxn);
|
||||
clone_function_impl(&state, fxn->impl, nfxn);
|
||||
}
|
||||
|
||||
clone_reg_list(&state, &ns->registers, &s->registers);
|
||||
ns->reg_alloc = s->reg_alloc;
|
||||
|
||||
ns->info = s->info;
|
||||
ns->info.name = ralloc_strdup(ns, ns->info.name);
|
||||
if (ns->info.label)
|
||||
ns->info.label = ralloc_strdup(ns, ns->info.label);
|
||||
|
||||
ns->num_inputs = s->num_inputs;
|
||||
ns->num_uniforms = s->num_uniforms;
|
||||
ns->num_outputs = s->num_outputs;
|
||||
|
||||
free_clone_state(&state);
|
||||
|
||||
return ns;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright © 2014 Connor Abbott
|
||||
*
|
||||
* 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
nir_const_value nir_eval_const_opcode(nir_op op, unsigned num_components,
|
||||
nir_const_value *src);
|
||||
@@ -0,0 +1,336 @@
|
||||
#! /usr/bin/python2
|
||||
template = """\
|
||||
/*
|
||||
* 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@jlekstrand.net)
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include "main/core.h"
|
||||
#include "util/rounding.h" /* for _mesa_roundeven */
|
||||
#include "util/half_float.h"
|
||||
#include "nir_constant_expressions.h"
|
||||
|
||||
/**
|
||||
* Evaluate one component of packSnorm4x8.
|
||||
*/
|
||||
static uint8_t
|
||||
pack_snorm_1x8(float x)
|
||||
{
|
||||
/* From section 8.4 of the GLSL 4.30 spec:
|
||||
*
|
||||
* packSnorm4x8
|
||||
* ------------
|
||||
* The conversion for component c of v to fixed point is done as
|
||||
* follows:
|
||||
*
|
||||
* packSnorm4x8: round(clamp(c, -1, +1) * 127.0)
|
||||
*
|
||||
* We must first cast the float to an int, because casting a negative
|
||||
* float to a uint is undefined.
|
||||
*/
|
||||
return (uint8_t) (int)
|
||||
_mesa_roundevenf(CLAMP(x, -1.0f, +1.0f) * 127.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate one component of packSnorm2x16.
|
||||
*/
|
||||
static uint16_t
|
||||
pack_snorm_1x16(float x)
|
||||
{
|
||||
/* From section 8.4 of the GLSL ES 3.00 spec:
|
||||
*
|
||||
* packSnorm2x16
|
||||
* -------------
|
||||
* The conversion for component c of v to fixed point is done as
|
||||
* follows:
|
||||
*
|
||||
* packSnorm2x16: round(clamp(c, -1, +1) * 32767.0)
|
||||
*
|
||||
* We must first cast the float to an int, because casting a negative
|
||||
* float to a uint is undefined.
|
||||
*/
|
||||
return (uint16_t) (int)
|
||||
_mesa_roundevenf(CLAMP(x, -1.0f, +1.0f) * 32767.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate one component of unpackSnorm4x8.
|
||||
*/
|
||||
static float
|
||||
unpack_snorm_1x8(uint8_t u)
|
||||
{
|
||||
/* From section 8.4 of the GLSL 4.30 spec:
|
||||
*
|
||||
* unpackSnorm4x8
|
||||
* --------------
|
||||
* The conversion for unpacked fixed-point value f to floating point is
|
||||
* done as follows:
|
||||
*
|
||||
* unpackSnorm4x8: clamp(f / 127.0, -1, +1)
|
||||
*/
|
||||
return CLAMP((int8_t) u / 127.0f, -1.0f, +1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate one component of unpackSnorm2x16.
|
||||
*/
|
||||
static float
|
||||
unpack_snorm_1x16(uint16_t u)
|
||||
{
|
||||
/* From section 8.4 of the GLSL ES 3.00 spec:
|
||||
*
|
||||
* unpackSnorm2x16
|
||||
* ---------------
|
||||
* The conversion for unpacked fixed-point value f to floating point is
|
||||
* done as follows:
|
||||
*
|
||||
* unpackSnorm2x16: clamp(f / 32767.0, -1, +1)
|
||||
*/
|
||||
return CLAMP((int16_t) u / 32767.0f, -1.0f, +1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate one component packUnorm4x8.
|
||||
*/
|
||||
static uint8_t
|
||||
pack_unorm_1x8(float x)
|
||||
{
|
||||
/* From section 8.4 of the GLSL 4.30 spec:
|
||||
*
|
||||
* packUnorm4x8
|
||||
* ------------
|
||||
* The conversion for component c of v to fixed point is done as
|
||||
* follows:
|
||||
*
|
||||
* packUnorm4x8: round(clamp(c, 0, +1) * 255.0)
|
||||
*/
|
||||
return (uint8_t) (int)
|
||||
_mesa_roundevenf(CLAMP(x, 0.0f, 1.0f) * 255.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate one component packUnorm2x16.
|
||||
*/
|
||||
static uint16_t
|
||||
pack_unorm_1x16(float x)
|
||||
{
|
||||
/* From section 8.4 of the GLSL ES 3.00 spec:
|
||||
*
|
||||
* packUnorm2x16
|
||||
* -------------
|
||||
* The conversion for component c of v to fixed point is done as
|
||||
* follows:
|
||||
*
|
||||
* packUnorm2x16: round(clamp(c, 0, +1) * 65535.0)
|
||||
*/
|
||||
return (uint16_t) (int)
|
||||
_mesa_roundevenf(CLAMP(x, 0.0f, 1.0f) * 65535.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate one component of unpackUnorm4x8.
|
||||
*/
|
||||
static float
|
||||
unpack_unorm_1x8(uint8_t u)
|
||||
{
|
||||
/* From section 8.4 of the GLSL 4.30 spec:
|
||||
*
|
||||
* unpackUnorm4x8
|
||||
* --------------
|
||||
* The conversion for unpacked fixed-point value f to floating point is
|
||||
* done as follows:
|
||||
*
|
||||
* unpackUnorm4x8: f / 255.0
|
||||
*/
|
||||
return (float) u / 255.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate one component of unpackUnorm2x16.
|
||||
*/
|
||||
static float
|
||||
unpack_unorm_1x16(uint16_t u)
|
||||
{
|
||||
/* From section 8.4 of the GLSL ES 3.00 spec:
|
||||
*
|
||||
* unpackUnorm2x16
|
||||
* ---------------
|
||||
* The conversion for unpacked fixed-point value f to floating point is
|
||||
* done as follows:
|
||||
*
|
||||
* unpackUnorm2x16: f / 65535.0
|
||||
*/
|
||||
return (float) u / 65535.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate one component of packHalf2x16.
|
||||
*/
|
||||
static uint16_t
|
||||
pack_half_1x16(float x)
|
||||
{
|
||||
return _mesa_float_to_half(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate one component of unpackHalf2x16.
|
||||
*/
|
||||
static float
|
||||
unpack_half_1x16(uint16_t u)
|
||||
{
|
||||
return _mesa_half_to_float(u);
|
||||
}
|
||||
|
||||
/* Some typed vector structures to make things like src0.y work */
|
||||
% for type in ["float", "int", "uint", "bool"]:
|
||||
struct ${type}_vec {
|
||||
${type} x;
|
||||
${type} y;
|
||||
${type} z;
|
||||
${type} w;
|
||||
};
|
||||
% endfor
|
||||
|
||||
% for name, op in sorted(opcodes.iteritems()):
|
||||
static nir_const_value
|
||||
evaluate_${name}(unsigned num_components, nir_const_value *_src)
|
||||
{
|
||||
nir_const_value _dst_val = { { {0, 0, 0, 0} } };
|
||||
|
||||
## For each non-per-component input, create a variable srcN that
|
||||
## contains x, y, z, and w elements which are filled in with the
|
||||
## appropriately-typed values.
|
||||
% for j in range(op.num_inputs):
|
||||
% if op.input_sizes[j] == 0:
|
||||
<% continue %>
|
||||
% elif "src" + str(j) not in op.const_expr:
|
||||
## Avoid unused variable warnings
|
||||
<% continue %>
|
||||
%endif
|
||||
|
||||
struct ${op.input_types[j]}_vec src${j} = {
|
||||
% for k in range(op.input_sizes[j]):
|
||||
% if op.input_types[j] == "bool":
|
||||
_src[${j}].u[${k}] != 0,
|
||||
% else:
|
||||
_src[${j}].${op.input_types[j][:1]}[${k}],
|
||||
% endif
|
||||
% endfor
|
||||
};
|
||||
% endfor
|
||||
|
||||
% if op.output_size == 0:
|
||||
## For per-component instructions, we need to iterate over the
|
||||
## components and apply the constant expression one component
|
||||
## at a time.
|
||||
for (unsigned _i = 0; _i < num_components; _i++) {
|
||||
## For each per-component input, create a variable srcN that
|
||||
## contains the value of the current (_i'th) component.
|
||||
% for j in range(op.num_inputs):
|
||||
% if op.input_sizes[j] != 0:
|
||||
<% continue %>
|
||||
% elif "src" + str(j) not in op.const_expr:
|
||||
## Avoid unused variable warnings
|
||||
<% continue %>
|
||||
% elif op.input_types[j] == "bool":
|
||||
bool src${j} = _src[${j}].u[_i] != 0;
|
||||
% else:
|
||||
${op.input_types[j]} src${j} = _src[${j}].${op.input_types[j][:1]}[_i];
|
||||
% endif
|
||||
% endfor
|
||||
|
||||
## Create an appropriately-typed variable dst and assign the
|
||||
## result of the const_expr to it. If const_expr already contains
|
||||
## writes to dst, just include const_expr directly.
|
||||
% if "dst" in op.const_expr:
|
||||
${op.output_type} dst;
|
||||
${op.const_expr}
|
||||
% else:
|
||||
${op.output_type} dst = ${op.const_expr};
|
||||
% endif
|
||||
|
||||
## Store the current component of the actual destination to the
|
||||
## value of dst.
|
||||
% if op.output_type == "bool":
|
||||
## Sanitize the C value to a proper NIR bool
|
||||
_dst_val.u[_i] = dst ? NIR_TRUE : NIR_FALSE;
|
||||
% else:
|
||||
_dst_val.${op.output_type[:1]}[_i] = dst;
|
||||
% endif
|
||||
}
|
||||
% else:
|
||||
## In the non-per-component case, create a struct dst with
|
||||
## appropriately-typed elements x, y, z, and w and assign the result
|
||||
## of the const_expr to all components of dst, or include the
|
||||
## const_expr directly if it writes to dst already.
|
||||
struct ${op.output_type}_vec dst;
|
||||
|
||||
% if "dst" in op.const_expr:
|
||||
${op.const_expr}
|
||||
% else:
|
||||
## Splat the value to all components. This way expressions which
|
||||
## write the same value to all components don't need to explicitly
|
||||
## write to dest. One such example is fnoise which has a
|
||||
## const_expr of 0.0f.
|
||||
dst.x = dst.y = dst.z = dst.w = ${op.const_expr};
|
||||
% endif
|
||||
|
||||
## For each component in the destination, copy the value of dst to
|
||||
## the actual destination.
|
||||
% for k in range(op.output_size):
|
||||
% if op.output_type == "bool":
|
||||
## Sanitize the C value to a proper NIR bool
|
||||
_dst_val.u[${k}] = dst.${"xyzw"[k]} ? NIR_TRUE : NIR_FALSE;
|
||||
% else:
|
||||
_dst_val.${op.output_type[:1]}[${k}] = dst.${"xyzw"[k]};
|
||||
% endif
|
||||
% endfor
|
||||
% endif
|
||||
|
||||
return _dst_val;
|
||||
}
|
||||
% endfor
|
||||
|
||||
nir_const_value
|
||||
nir_eval_const_opcode(nir_op op, unsigned num_components,
|
||||
nir_const_value *src)
|
||||
{
|
||||
switch (op) {
|
||||
% for name in sorted(opcodes.iterkeys()):
|
||||
case nir_op_${name}: {
|
||||
return evaluate_${name}(num_components, src);
|
||||
break;
|
||||
}
|
||||
% endfor
|
||||
default:
|
||||
unreachable("shouldn't get here");
|
||||
}
|
||||
}"""
|
||||
|
||||
from nir_opcodes import opcodes
|
||||
from mako.template import Template
|
||||
|
||||
print Template(template).render(opcodes=opcodes)
|
||||
@@ -0,0 +1,808 @@
|
||||
/*
|
||||
* Copyright © 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir_control_flow_private.h"
|
||||
|
||||
/**
|
||||
* \name Control flow modification
|
||||
*
|
||||
* These functions modify the control flow tree while keeping the control flow
|
||||
* graph up-to-date. The invariants respected are:
|
||||
* 1. Each then statement, else statement, or loop body must have at least one
|
||||
* control flow node.
|
||||
* 2. Each if-statement and loop must have one basic block before it and one
|
||||
* after.
|
||||
* 3. Two basic blocks cannot be directly next to each other.
|
||||
* 4. If a basic block has a jump instruction, there must be only one and it
|
||||
* must be at the end of the block.
|
||||
* 5. The CFG must always be connected - this means that we must insert a fake
|
||||
* CFG edge for loops with no break statement.
|
||||
*
|
||||
* The purpose of the second one is so that we have places to insert code during
|
||||
* GCM, as well as eliminating the possibility of critical edges.
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
static bool
|
||||
block_ends_in_jump(nir_block *block)
|
||||
{
|
||||
return !exec_list_is_empty(&block->instr_list) &&
|
||||
nir_block_last_instr(block)->type == nir_instr_type_jump;
|
||||
}
|
||||
|
||||
static inline void
|
||||
block_add_pred(nir_block *block, nir_block *pred)
|
||||
{
|
||||
_mesa_set_add(block->predecessors, pred);
|
||||
}
|
||||
|
||||
static inline void
|
||||
block_remove_pred(nir_block *block, nir_block *pred)
|
||||
{
|
||||
struct set_entry *entry = _mesa_set_search(block->predecessors, pred);
|
||||
|
||||
assert(entry);
|
||||
|
||||
_mesa_set_remove(block->predecessors, entry);
|
||||
}
|
||||
|
||||
static void
|
||||
link_blocks(nir_block *pred, nir_block *succ1, nir_block *succ2)
|
||||
{
|
||||
pred->successors[0] = succ1;
|
||||
if (succ1 != NULL)
|
||||
block_add_pred(succ1, pred);
|
||||
|
||||
pred->successors[1] = succ2;
|
||||
if (succ2 != NULL)
|
||||
block_add_pred(succ2, pred);
|
||||
}
|
||||
|
||||
static void
|
||||
unlink_blocks(nir_block *pred, nir_block *succ)
|
||||
{
|
||||
if (pred->successors[0] == succ) {
|
||||
pred->successors[0] = pred->successors[1];
|
||||
pred->successors[1] = NULL;
|
||||
} else {
|
||||
assert(pred->successors[1] == succ);
|
||||
pred->successors[1] = NULL;
|
||||
}
|
||||
|
||||
block_remove_pred(succ, pred);
|
||||
}
|
||||
|
||||
static void
|
||||
unlink_block_successors(nir_block *block)
|
||||
{
|
||||
if (block->successors[1] != NULL)
|
||||
unlink_blocks(block, block->successors[1]);
|
||||
if (block->successors[0] != NULL)
|
||||
unlink_blocks(block, block->successors[0]);
|
||||
}
|
||||
|
||||
static void
|
||||
link_non_block_to_block(nir_cf_node *node, nir_block *block)
|
||||
{
|
||||
if (node->type == nir_cf_node_if) {
|
||||
/*
|
||||
* We're trying to link an if to a block after it; this just means linking
|
||||
* the last block of the then and else branches.
|
||||
*/
|
||||
|
||||
nir_if *if_stmt = nir_cf_node_as_if(node);
|
||||
|
||||
nir_cf_node *last_then = nir_if_last_then_node(if_stmt);
|
||||
assert(last_then->type == nir_cf_node_block);
|
||||
nir_block *last_then_block = nir_cf_node_as_block(last_then);
|
||||
|
||||
nir_cf_node *last_else = nir_if_last_else_node(if_stmt);
|
||||
assert(last_else->type == nir_cf_node_block);
|
||||
nir_block *last_else_block = nir_cf_node_as_block(last_else);
|
||||
|
||||
if (!block_ends_in_jump(last_then_block)) {
|
||||
unlink_block_successors(last_then_block);
|
||||
link_blocks(last_then_block, block, NULL);
|
||||
}
|
||||
|
||||
if (!block_ends_in_jump(last_else_block)) {
|
||||
unlink_block_successors(last_else_block);
|
||||
link_blocks(last_else_block, block, NULL);
|
||||
}
|
||||
} else {
|
||||
assert(node->type == nir_cf_node_loop);
|
||||
|
||||
/*
|
||||
* We can only get to this codepath if we're inserting a new loop, or
|
||||
* at least a loop with no break statements; we can't insert break
|
||||
* statements into a loop when we haven't inserted it into the CFG
|
||||
* because we wouldn't know which block comes after the loop
|
||||
* and therefore, which block should be the successor of the block with
|
||||
* the break). Therefore, we need to insert a fake edge (see invariant
|
||||
* #5).
|
||||
*/
|
||||
|
||||
nir_loop *loop = nir_cf_node_as_loop(node);
|
||||
|
||||
nir_cf_node *last = nir_loop_last_cf_node(loop);
|
||||
assert(last->type == nir_cf_node_block);
|
||||
nir_block *last_block = nir_cf_node_as_block(last);
|
||||
|
||||
last_block->successors[1] = block;
|
||||
block_add_pred(block, last_block);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
link_block_to_non_block(nir_block *block, nir_cf_node *node)
|
||||
{
|
||||
if (node->type == nir_cf_node_if) {
|
||||
/*
|
||||
* We're trying to link a block to an if after it; this just means linking
|
||||
* the block to the first block of the then and else branches.
|
||||
*/
|
||||
|
||||
nir_if *if_stmt = nir_cf_node_as_if(node);
|
||||
|
||||
nir_cf_node *first_then = nir_if_first_then_node(if_stmt);
|
||||
assert(first_then->type == nir_cf_node_block);
|
||||
nir_block *first_then_block = nir_cf_node_as_block(first_then);
|
||||
|
||||
nir_cf_node *first_else = nir_if_first_else_node(if_stmt);
|
||||
assert(first_else->type == nir_cf_node_block);
|
||||
nir_block *first_else_block = nir_cf_node_as_block(first_else);
|
||||
|
||||
unlink_block_successors(block);
|
||||
link_blocks(block, first_then_block, first_else_block);
|
||||
} else {
|
||||
/*
|
||||
* For similar reasons as the corresponding case in
|
||||
* link_non_block_to_block(), don't worry about if the loop header has
|
||||
* any predecessors that need to be unlinked.
|
||||
*/
|
||||
|
||||
assert(node->type == nir_cf_node_loop);
|
||||
|
||||
nir_loop *loop = nir_cf_node_as_loop(node);
|
||||
|
||||
nir_cf_node *loop_header = nir_loop_first_cf_node(loop);
|
||||
assert(loop_header->type == nir_cf_node_block);
|
||||
nir_block *loop_header_block = nir_cf_node_as_block(loop_header);
|
||||
|
||||
unlink_block_successors(block);
|
||||
link_blocks(block, loop_header_block, NULL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a block's successor with a different one.
|
||||
*/
|
||||
static void
|
||||
replace_successor(nir_block *block, nir_block *old_succ, nir_block *new_succ)
|
||||
{
|
||||
if (block->successors[0] == old_succ) {
|
||||
block->successors[0] = new_succ;
|
||||
} else {
|
||||
assert(block->successors[1] == old_succ);
|
||||
block->successors[1] = new_succ;
|
||||
}
|
||||
|
||||
block_remove_pred(old_succ, block);
|
||||
block_add_pred(new_succ, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a basic block and inserts a new empty basic block before it, making its
|
||||
* predecessors point to the new block. This essentially splits the block into
|
||||
* an empty header and a body so that another non-block CF node can be inserted
|
||||
* between the two. Note that this does *not* link the two basic blocks, so
|
||||
* some kind of cleanup *must* be performed after this call.
|
||||
*/
|
||||
|
||||
static nir_block *
|
||||
split_block_beginning(nir_block *block)
|
||||
{
|
||||
nir_block *new_block = nir_block_create(ralloc_parent(block));
|
||||
new_block->cf_node.parent = block->cf_node.parent;
|
||||
exec_node_insert_node_before(&block->cf_node.node, &new_block->cf_node.node);
|
||||
|
||||
struct set_entry *entry;
|
||||
set_foreach(block->predecessors, entry) {
|
||||
nir_block *pred = (nir_block *) entry->key;
|
||||
replace_successor(pred, block, new_block);
|
||||
}
|
||||
|
||||
/* Any phi nodes must stay part of the new block, or else their
|
||||
* sourcse will be messed up. This will reverse the order of the phi's, but
|
||||
* order shouldn't matter.
|
||||
*/
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type != nir_instr_type_phi)
|
||||
break;
|
||||
|
||||
exec_node_remove(&instr->node);
|
||||
instr->block = new_block;
|
||||
exec_list_push_head(&new_block->instr_list, &instr->node);
|
||||
}
|
||||
|
||||
return new_block;
|
||||
}
|
||||
|
||||
static void
|
||||
rewrite_phi_preds(nir_block *block, nir_block *old_pred, nir_block *new_pred)
|
||||
{
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type != nir_instr_type_phi)
|
||||
break;
|
||||
|
||||
nir_phi_instr *phi = nir_instr_as_phi(instr);
|
||||
nir_foreach_phi_src(phi, src) {
|
||||
if (src->pred == old_pred) {
|
||||
src->pred = new_pred;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
insert_phi_undef(nir_block *block, nir_block *pred)
|
||||
{
|
||||
nir_function_impl *impl = nir_cf_node_get_function(&block->cf_node);
|
||||
nir_foreach_instr(block, instr) {
|
||||
if (instr->type != nir_instr_type_phi)
|
||||
break;
|
||||
|
||||
nir_phi_instr *phi = nir_instr_as_phi(instr);
|
||||
nir_ssa_undef_instr *undef =
|
||||
nir_ssa_undef_instr_create(ralloc_parent(phi),
|
||||
phi->dest.ssa.num_components);
|
||||
nir_instr_insert_before_cf_list(&impl->body, &undef->instr);
|
||||
nir_phi_src *src = ralloc(phi, nir_phi_src);
|
||||
src->pred = pred;
|
||||
src->src.parent_instr = &phi->instr;
|
||||
src->src.is_ssa = true;
|
||||
src->src.ssa = &undef->def;
|
||||
|
||||
list_addtail(&src->src.use_link, &undef->def.uses);
|
||||
|
||||
exec_list_push_tail(&phi->srcs, &src->node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the successors of source to the successors of dest, leaving both
|
||||
* successors of source NULL.
|
||||
*/
|
||||
|
||||
static void
|
||||
move_successors(nir_block *source, nir_block *dest)
|
||||
{
|
||||
nir_block *succ1 = source->successors[0];
|
||||
nir_block *succ2 = source->successors[1];
|
||||
|
||||
if (succ1) {
|
||||
unlink_blocks(source, succ1);
|
||||
rewrite_phi_preds(succ1, source, dest);
|
||||
}
|
||||
|
||||
if (succ2) {
|
||||
unlink_blocks(source, succ2);
|
||||
rewrite_phi_preds(succ2, source, dest);
|
||||
}
|
||||
|
||||
unlink_block_successors(dest);
|
||||
link_blocks(dest, succ1, succ2);
|
||||
}
|
||||
|
||||
/* Given a basic block with no successors that has been inserted into the
|
||||
* control flow tree, gives it the successors it would normally have assuming
|
||||
* it doesn't end in a jump instruction. Also inserts phi sources with undefs
|
||||
* if necessary.
|
||||
*/
|
||||
static void
|
||||
block_add_normal_succs(nir_block *block)
|
||||
{
|
||||
if (exec_node_is_tail_sentinel(block->cf_node.node.next)) {
|
||||
nir_cf_node *parent = block->cf_node.parent;
|
||||
if (parent->type == nir_cf_node_if) {
|
||||
nir_cf_node *next = nir_cf_node_next(parent);
|
||||
assert(next->type == nir_cf_node_block);
|
||||
nir_block *next_block = nir_cf_node_as_block(next);
|
||||
|
||||
link_blocks(block, next_block, NULL);
|
||||
} else {
|
||||
assert(parent->type == nir_cf_node_loop);
|
||||
nir_loop *loop = nir_cf_node_as_loop(parent);
|
||||
|
||||
nir_cf_node *head = nir_loop_first_cf_node(loop);
|
||||
assert(head->type == nir_cf_node_block);
|
||||
nir_block *head_block = nir_cf_node_as_block(head);
|
||||
|
||||
link_blocks(block, head_block, NULL);
|
||||
insert_phi_undef(head_block, block);
|
||||
}
|
||||
} else {
|
||||
nir_cf_node *next = nir_cf_node_next(&block->cf_node);
|
||||
if (next->type == nir_cf_node_if) {
|
||||
nir_if *next_if = nir_cf_node_as_if(next);
|
||||
|
||||
nir_cf_node *first_then = nir_if_first_then_node(next_if);
|
||||
assert(first_then->type == nir_cf_node_block);
|
||||
nir_block *first_then_block = nir_cf_node_as_block(first_then);
|
||||
|
||||
nir_cf_node *first_else = nir_if_first_else_node(next_if);
|
||||
assert(first_else->type == nir_cf_node_block);
|
||||
nir_block *first_else_block = nir_cf_node_as_block(first_else);
|
||||
|
||||
link_blocks(block, first_then_block, first_else_block);
|
||||
} else {
|
||||
assert(next->type == nir_cf_node_loop);
|
||||
nir_loop *next_loop = nir_cf_node_as_loop(next);
|
||||
|
||||
nir_cf_node *first = nir_loop_first_cf_node(next_loop);
|
||||
assert(first->type == nir_cf_node_block);
|
||||
nir_block *first_block = nir_cf_node_as_block(first);
|
||||
|
||||
link_blocks(block, first_block, NULL);
|
||||
insert_phi_undef(first_block, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static nir_block *
|
||||
split_block_end(nir_block *block)
|
||||
{
|
||||
nir_block *new_block = nir_block_create(ralloc_parent(block));
|
||||
new_block->cf_node.parent = block->cf_node.parent;
|
||||
exec_node_insert_after(&block->cf_node.node, &new_block->cf_node.node);
|
||||
|
||||
if (block_ends_in_jump(block)) {
|
||||
/* Figure out what successor block would've had if it didn't have a jump
|
||||
* instruction, and make new_block have that successor.
|
||||
*/
|
||||
block_add_normal_succs(new_block);
|
||||
} else {
|
||||
move_successors(block, new_block);
|
||||
}
|
||||
|
||||
return new_block;
|
||||
}
|
||||
|
||||
static nir_block *
|
||||
split_block_before_instr(nir_instr *instr)
|
||||
{
|
||||
assert(instr->type != nir_instr_type_phi);
|
||||
nir_block *new_block = split_block_beginning(instr->block);
|
||||
|
||||
nir_foreach_instr_safe(instr->block, cur_instr) {
|
||||
if (cur_instr == instr)
|
||||
break;
|
||||
|
||||
exec_node_remove(&cur_instr->node);
|
||||
cur_instr->block = new_block;
|
||||
exec_list_push_tail(&new_block->instr_list, &cur_instr->node);
|
||||
}
|
||||
|
||||
return new_block;
|
||||
}
|
||||
|
||||
/* Splits a basic block at the point specified by the cursor. The "before" and
|
||||
* "after" arguments are filled out with the blocks resulting from the split
|
||||
* if non-NULL. Note that the "beginning" of the block is actually interpreted
|
||||
* as before the first non-phi instruction, and it's illegal to split a block
|
||||
* before a phi instruction.
|
||||
*/
|
||||
|
||||
static void
|
||||
split_block_cursor(nir_cursor cursor,
|
||||
nir_block **_before, nir_block **_after)
|
||||
{
|
||||
nir_block *before, *after;
|
||||
switch (cursor.option) {
|
||||
case nir_cursor_before_block:
|
||||
after = cursor.block;
|
||||
before = split_block_beginning(cursor.block);
|
||||
break;
|
||||
|
||||
case nir_cursor_after_block:
|
||||
before = cursor.block;
|
||||
after = split_block_end(cursor.block);
|
||||
break;
|
||||
|
||||
case nir_cursor_before_instr:
|
||||
after = cursor.instr->block;
|
||||
before = split_block_before_instr(cursor.instr);
|
||||
break;
|
||||
|
||||
case nir_cursor_after_instr:
|
||||
/* We lower this to split_block_before_instr() so that we can keep the
|
||||
* after-a-jump-instr case contained to split_block_end().
|
||||
*/
|
||||
if (nir_instr_is_last(cursor.instr)) {
|
||||
before = cursor.instr->block;
|
||||
after = split_block_end(cursor.instr->block);
|
||||
} else {
|
||||
after = cursor.instr->block;
|
||||
before = split_block_before_instr(nir_instr_next(cursor.instr));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
unreachable("not reached");
|
||||
}
|
||||
|
||||
if (_before)
|
||||
*_before = before;
|
||||
if (_after)
|
||||
*_after = after;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a non-basic block between two basic blocks and links them together.
|
||||
*/
|
||||
|
||||
static void
|
||||
insert_non_block(nir_block *before, nir_cf_node *node, nir_block *after)
|
||||
{
|
||||
node->parent = before->cf_node.parent;
|
||||
exec_node_insert_after(&before->cf_node.node, &node->node);
|
||||
link_block_to_non_block(before, node);
|
||||
link_non_block_to_block(node, after);
|
||||
}
|
||||
|
||||
/* walk up the control flow tree to find the innermost enclosed loop */
|
||||
static nir_loop *
|
||||
nearest_loop(nir_cf_node *node)
|
||||
{
|
||||
while (node->type != nir_cf_node_loop) {
|
||||
node = node->parent;
|
||||
}
|
||||
|
||||
return nir_cf_node_as_loop(node);
|
||||
}
|
||||
|
||||
/*
|
||||
* update the CFG after a jump instruction has been added to the end of a block
|
||||
*/
|
||||
|
||||
void
|
||||
nir_handle_add_jump(nir_block *block)
|
||||
{
|
||||
nir_instr *instr = nir_block_last_instr(block);
|
||||
nir_jump_instr *jump_instr = nir_instr_as_jump(instr);
|
||||
|
||||
unlink_block_successors(block);
|
||||
|
||||
nir_function_impl *impl = nir_cf_node_get_function(&block->cf_node);
|
||||
nir_metadata_preserve(impl, nir_metadata_none);
|
||||
|
||||
if (jump_instr->type == nir_jump_break ||
|
||||
jump_instr->type == nir_jump_continue) {
|
||||
nir_loop *loop = nearest_loop(&block->cf_node);
|
||||
|
||||
if (jump_instr->type == nir_jump_continue) {
|
||||
nir_cf_node *first_node = nir_loop_first_cf_node(loop);
|
||||
assert(first_node->type == nir_cf_node_block);
|
||||
nir_block *first_block = nir_cf_node_as_block(first_node);
|
||||
link_blocks(block, first_block, NULL);
|
||||
} else {
|
||||
nir_cf_node *after = nir_cf_node_next(&loop->cf_node);
|
||||
assert(after->type == nir_cf_node_block);
|
||||
nir_block *after_block = nir_cf_node_as_block(after);
|
||||
link_blocks(block, after_block, NULL);
|
||||
|
||||
/* If we inserted a fake link, remove it */
|
||||
nir_cf_node *last = nir_loop_last_cf_node(loop);
|
||||
assert(last->type == nir_cf_node_block);
|
||||
nir_block *last_block = nir_cf_node_as_block(last);
|
||||
if (last_block->successors[1] != NULL)
|
||||
unlink_blocks(last_block, after_block);
|
||||
}
|
||||
} else {
|
||||
assert(jump_instr->type == nir_jump_return);
|
||||
link_blocks(block, impl->end_block, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
remove_phi_src(nir_block *block, nir_block *pred)
|
||||
{
|
||||
nir_foreach_instr(block, instr) {
|
||||
if (instr->type != nir_instr_type_phi)
|
||||
break;
|
||||
|
||||
nir_phi_instr *phi = nir_instr_as_phi(instr);
|
||||
nir_foreach_phi_src_safe(phi, src) {
|
||||
if (src->pred == pred) {
|
||||
list_del(&src->src.use_link);
|
||||
exec_node_remove(&src->node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Removes the successor of a block with a jump, and inserts a fake edge for
|
||||
* infinite loops. Note that the jump to be eliminated may be free-floating.
|
||||
*/
|
||||
|
||||
static void
|
||||
unlink_jump(nir_block *block, nir_jump_type type, bool add_normal_successors)
|
||||
{
|
||||
nir_block *next = block->successors[0];
|
||||
|
||||
if (block->successors[0])
|
||||
remove_phi_src(block->successors[0], block);
|
||||
if (block->successors[1])
|
||||
remove_phi_src(block->successors[1], block);
|
||||
|
||||
unlink_block_successors(block);
|
||||
if (add_normal_successors)
|
||||
block_add_normal_succs(block);
|
||||
|
||||
/* If we've just removed a break, and the block we were jumping to (after
|
||||
* the loop) now has zero predecessors, we've created a new infinite loop.
|
||||
*
|
||||
* NIR doesn't allow blocks (other than the start block) to have zero
|
||||
* predecessors. In particular, dominance assumes all blocks are reachable.
|
||||
* So, we insert a "fake link" by making successors[1] point after the loop.
|
||||
*
|
||||
* Note that we have to do this after unlinking/recreating the block's
|
||||
* successors. If we removed a "break" at the end of the loop, then
|
||||
* block == last_block, so block->successors[0] would already be "next",
|
||||
* and adding a fake link would create two identical successors. Doing
|
||||
* this afterward works, as we'll have changed block->successors[0] to
|
||||
* be the top of the loop.
|
||||
*/
|
||||
if (type == nir_jump_break && next->predecessors->entries == 0) {
|
||||
nir_loop *loop =
|
||||
nir_cf_node_as_loop(nir_cf_node_prev(&next->cf_node));
|
||||
|
||||
/* insert fake link */
|
||||
nir_cf_node *last = nir_loop_last_cf_node(loop);
|
||||
assert(last->type == nir_cf_node_block);
|
||||
nir_block *last_block = nir_cf_node_as_block(last);
|
||||
|
||||
last_block->successors[1] = next;
|
||||
block_add_pred(next, last_block);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nir_handle_remove_jump(nir_block *block, nir_jump_type type)
|
||||
{
|
||||
unlink_jump(block, type, true);
|
||||
|
||||
nir_function_impl *impl = nir_cf_node_get_function(&block->cf_node);
|
||||
nir_metadata_preserve(impl, nir_metadata_none);
|
||||
}
|
||||
|
||||
static void
|
||||
update_if_uses(nir_cf_node *node)
|
||||
{
|
||||
if (node->type != nir_cf_node_if)
|
||||
return;
|
||||
|
||||
nir_if *if_stmt = nir_cf_node_as_if(node);
|
||||
|
||||
if_stmt->condition.parent_if = if_stmt;
|
||||
if (if_stmt->condition.is_ssa) {
|
||||
list_addtail(&if_stmt->condition.use_link,
|
||||
&if_stmt->condition.ssa->if_uses);
|
||||
} else {
|
||||
list_addtail(&if_stmt->condition.use_link,
|
||||
&if_stmt->condition.reg.reg->if_uses);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stitch two basic blocks together into one. The aggregate must have the same
|
||||
* predecessors as the first and the same successors as the second.
|
||||
*/
|
||||
|
||||
static void
|
||||
stitch_blocks(nir_block *before, nir_block *after)
|
||||
{
|
||||
/*
|
||||
* We move after into before, so we have to deal with up to 2 successors vs.
|
||||
* possibly a large number of predecessors.
|
||||
*
|
||||
* TODO: special case when before is empty and after isn't?
|
||||
*/
|
||||
|
||||
if (block_ends_in_jump(before)) {
|
||||
assert(exec_list_is_empty(&after->instr_list));
|
||||
if (after->successors[0])
|
||||
remove_phi_src(after->successors[0], after);
|
||||
if (after->successors[1])
|
||||
remove_phi_src(after->successors[1], after);
|
||||
unlink_block_successors(after);
|
||||
exec_node_remove(&after->cf_node.node);
|
||||
} else {
|
||||
move_successors(after, before);
|
||||
|
||||
foreach_list_typed(nir_instr, instr, node, &after->instr_list) {
|
||||
instr->block = before;
|
||||
}
|
||||
|
||||
exec_list_append(&before->instr_list, &after->instr_list);
|
||||
exec_node_remove(&after->cf_node.node);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nir_cf_node_insert(nir_cursor cursor, nir_cf_node *node)
|
||||
{
|
||||
nir_block *before, *after;
|
||||
|
||||
split_block_cursor(cursor, &before, &after);
|
||||
|
||||
if (node->type == nir_cf_node_block) {
|
||||
nir_block *block = nir_cf_node_as_block(node);
|
||||
exec_node_insert_after(&before->cf_node.node, &block->cf_node.node);
|
||||
block->cf_node.parent = before->cf_node.parent;
|
||||
/* stitch_blocks() assumes that any block that ends with a jump has
|
||||
* already been setup with the correct successors, so we need to set
|
||||
* up jumps here as the block is being inserted.
|
||||
*/
|
||||
if (block_ends_in_jump(block))
|
||||
nir_handle_add_jump(block);
|
||||
|
||||
stitch_blocks(block, after);
|
||||
stitch_blocks(before, block);
|
||||
} else {
|
||||
update_if_uses(node);
|
||||
insert_non_block(before, node, after);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
replace_ssa_def_uses(nir_ssa_def *def, void *void_impl)
|
||||
{
|
||||
nir_function_impl *impl = void_impl;
|
||||
void *mem_ctx = ralloc_parent(impl);
|
||||
|
||||
nir_ssa_undef_instr *undef =
|
||||
nir_ssa_undef_instr_create(mem_ctx, def->num_components);
|
||||
nir_instr_insert_before_cf_list(&impl->body, &undef->instr);
|
||||
nir_ssa_def_rewrite_uses(def, nir_src_for_ssa(&undef->def));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
cleanup_cf_node(nir_cf_node *node, nir_function_impl *impl)
|
||||
{
|
||||
switch (node->type) {
|
||||
case nir_cf_node_block: {
|
||||
nir_block *block = nir_cf_node_as_block(node);
|
||||
/* We need to walk the instructions and clean up defs/uses */
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type == nir_instr_type_jump) {
|
||||
nir_jump_type jump_type = nir_instr_as_jump(instr)->type;
|
||||
unlink_jump(block, jump_type, false);
|
||||
} else {
|
||||
nir_foreach_ssa_def(instr, replace_ssa_def_uses, impl);
|
||||
nir_instr_remove(instr);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case nir_cf_node_if: {
|
||||
nir_if *if_stmt = nir_cf_node_as_if(node);
|
||||
foreach_list_typed(nir_cf_node, child, node, &if_stmt->then_list)
|
||||
cleanup_cf_node(child, impl);
|
||||
foreach_list_typed(nir_cf_node, child, node, &if_stmt->else_list)
|
||||
cleanup_cf_node(child, impl);
|
||||
|
||||
list_del(&if_stmt->condition.use_link);
|
||||
break;
|
||||
}
|
||||
|
||||
case nir_cf_node_loop: {
|
||||
nir_loop *loop = nir_cf_node_as_loop(node);
|
||||
foreach_list_typed(nir_cf_node, child, node, &loop->body)
|
||||
cleanup_cf_node(child, impl);
|
||||
break;
|
||||
}
|
||||
case nir_cf_node_function: {
|
||||
nir_function_impl *impl = nir_cf_node_as_function(node);
|
||||
foreach_list_typed(nir_cf_node, child, node, &impl->body)
|
||||
cleanup_cf_node(child, impl);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable("Invalid CF node type");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nir_cf_extract(nir_cf_list *extracted, nir_cursor begin, nir_cursor end)
|
||||
{
|
||||
nir_block *block_begin, *block_end, *block_before, *block_after;
|
||||
|
||||
/* In the case where begin points to an instruction in some basic block and
|
||||
* end points to the end of the same basic block, we rely on the fact that
|
||||
* splitting on an instruction moves earlier instructions into a new basic
|
||||
* block. If the later instructions were moved instead, then the end cursor
|
||||
* would be pointing to the same place that begin used to point to, which
|
||||
* is obviously not what we want.
|
||||
*/
|
||||
split_block_cursor(begin, &block_before, &block_begin);
|
||||
split_block_cursor(end, &block_end, &block_after);
|
||||
|
||||
extracted->impl = nir_cf_node_get_function(&block_begin->cf_node);
|
||||
exec_list_make_empty(&extracted->list);
|
||||
|
||||
/* Dominance and other block-related information is toast. */
|
||||
nir_metadata_preserve(extracted->impl, nir_metadata_none);
|
||||
|
||||
nir_cf_node *cf_node = &block_begin->cf_node;
|
||||
nir_cf_node *cf_node_end = &block_end->cf_node;
|
||||
while (true) {
|
||||
nir_cf_node *next = nir_cf_node_next(cf_node);
|
||||
|
||||
exec_node_remove(&cf_node->node);
|
||||
cf_node->parent = NULL;
|
||||
exec_list_push_tail(&extracted->list, &cf_node->node);
|
||||
|
||||
if (cf_node == cf_node_end)
|
||||
break;
|
||||
|
||||
cf_node = next;
|
||||
}
|
||||
|
||||
stitch_blocks(block_before, block_after);
|
||||
}
|
||||
|
||||
void
|
||||
nir_cf_reinsert(nir_cf_list *cf_list, nir_cursor cursor)
|
||||
{
|
||||
nir_block *before, *after;
|
||||
|
||||
split_block_cursor(cursor, &before, &after);
|
||||
|
||||
foreach_list_typed_safe(nir_cf_node, node, node, &cf_list->list) {
|
||||
exec_node_remove(&node->node);
|
||||
node->parent = before->cf_node.parent;
|
||||
exec_node_insert_node_before(&after->cf_node.node, &node->node);
|
||||
}
|
||||
|
||||
stitch_blocks(before,
|
||||
nir_cf_node_as_block(nir_cf_node_next(&before->cf_node)));
|
||||
stitch_blocks(nir_cf_node_as_block(nir_cf_node_prev(&after->cf_node)),
|
||||
after);
|
||||
}
|
||||
|
||||
void
|
||||
nir_cf_delete(nir_cf_list *cf_list)
|
||||
{
|
||||
foreach_list_typed(nir_cf_node, node, node, &cf_list->list) {
|
||||
cleanup_cf_node(node, cf_list->impl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright © 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** NIR Control Flow Modification
|
||||
*
|
||||
* This file contains various API's that make modifying control flow in NIR,
|
||||
* while maintaining the invariants checked by the validator, much easier.
|
||||
* There are two parts to this:
|
||||
*
|
||||
* 1. Inserting control flow (if's and loops) in various places, for creating
|
||||
* IR either from scratch or as part of some lowering pass.
|
||||
* 2. Taking existing pieces of the IR and either moving them around or
|
||||
* deleting them.
|
||||
*/
|
||||
|
||||
/** Control flow insertion. */
|
||||
|
||||
/** puts a control flow node where the cursor is */
|
||||
void nir_cf_node_insert(nir_cursor cursor, nir_cf_node *node);
|
||||
|
||||
/** puts a control flow node immediately after another control flow node */
|
||||
static inline void
|
||||
nir_cf_node_insert_after(nir_cf_node *node, nir_cf_node *after)
|
||||
{
|
||||
nir_cf_node_insert(nir_after_cf_node(node), after);
|
||||
}
|
||||
|
||||
/** puts a control flow node immediately before another control flow node */
|
||||
static inline void
|
||||
nir_cf_node_insert_before(nir_cf_node *node, nir_cf_node *before)
|
||||
{
|
||||
nir_cf_node_insert(nir_before_cf_node(node), before);
|
||||
}
|
||||
|
||||
/** puts a control flow node at the beginning of a list from an if, loop, or function */
|
||||
static inline void
|
||||
nir_cf_node_insert_begin(struct exec_list *list, nir_cf_node *node)
|
||||
{
|
||||
nir_cf_node_insert(nir_before_cf_list(list), node);
|
||||
}
|
||||
|
||||
/** puts a control flow node at the end of a list from an if, loop, or function */
|
||||
static inline void
|
||||
nir_cf_node_insert_end(struct exec_list *list, nir_cf_node *node)
|
||||
{
|
||||
nir_cf_node_insert(nir_after_cf_list(list), node);
|
||||
}
|
||||
|
||||
|
||||
/** Control flow motion.
|
||||
*
|
||||
* These functions let you take a part of a control flow list (basically
|
||||
* equivalent to a series of statement in GLSL) and "extract" it from the IR,
|
||||
* so that it's a free-floating piece of IR that can be either re-inserted
|
||||
* somewhere else or deleted entirely. A few notes on using it:
|
||||
*
|
||||
* 1. Phi nodes are considered attached to the piece of control flow that
|
||||
* their sources come from. There are three places where phi nodes can
|
||||
* occur, which are the three places where a block can have multiple
|
||||
* predecessors:
|
||||
*
|
||||
* 1) After an if statement, if neither branch ends in a jump.
|
||||
* 2) After a loop, if there are multiple break's.
|
||||
* 3) At the beginning of a loop.
|
||||
*
|
||||
* For #1, the phi node is considered to be part of the if, and for #2 and
|
||||
* #3 the phi node is considered to be part of the loop. This allows us to
|
||||
* keep phi's intact, but it means that phi nodes cannot be separated from
|
||||
* the control flow they come from. For example, extracting an if without
|
||||
* extracting all the phi nodes after it is not allowed, and neither is
|
||||
* extracting only some of the phi nodes at the beginning of a block. It
|
||||
* also means that extracting from the beginning of a basic block actually
|
||||
* means extracting from the first non-phi instruction, since there's no
|
||||
* situation where extracting phi nodes without extracting what comes
|
||||
* before them makes any sense.
|
||||
*
|
||||
* 2. Phi node sources are guaranteed to remain valid, meaning that they still
|
||||
* correspond one-to-one with the predecessors of the basic block they're
|
||||
* part of. In addition, the original sources will be preserved unless they
|
||||
* correspond to a break or continue that was deleted. However, no attempt
|
||||
* is made to ensure that SSA form is maintained. In particular, it is
|
||||
* *not* guaranteed that definitions of SSA values will dominate all their
|
||||
* uses after all is said and done. Either the caller must ensure that this
|
||||
* is the case, or it must insert extra phi nodes to restore SSA.
|
||||
*
|
||||
* 3. It is invalid to move a piece of IR with a break/continue outside of the
|
||||
* loop it references. Doing this will result in invalid
|
||||
* successors/predecessors and phi node sources.
|
||||
*
|
||||
* 4. It is invalid to move a piece of IR from one function implementation to
|
||||
* another.
|
||||
*
|
||||
* 5. Extracting a control flow list will leave lots of dangling references to
|
||||
* and from other pieces of the IR. It also leaves things in a not 100%
|
||||
* consistent state. This means that some things (e.g. inserting
|
||||
* instructions) might not work reliably on the extracted control flow. It
|
||||
* also means that extracting control flow without re-inserting it or
|
||||
* deleting it is a Bad Thing (tm).
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
struct exec_list list;
|
||||
nir_function_impl *impl; /* for cleaning up if the list is deleted */
|
||||
} nir_cf_list;
|
||||
|
||||
void nir_cf_extract(nir_cf_list *extracted, nir_cursor begin, nir_cursor end);
|
||||
|
||||
void nir_cf_reinsert(nir_cf_list *cf_list, nir_cursor cursor);
|
||||
|
||||
void nir_cf_delete(nir_cf_list *cf_list);
|
||||
|
||||
static inline void
|
||||
nir_cf_list_extract(nir_cf_list *extracted, struct exec_list *cf_list)
|
||||
{
|
||||
nir_cf_extract(extracted, nir_before_cf_list(cf_list),
|
||||
nir_after_cf_list(cf_list));
|
||||
}
|
||||
|
||||
/** removes a control flow node, doing any cleanup necessary */
|
||||
static inline void
|
||||
nir_cf_node_remove(nir_cf_node *node)
|
||||
{
|
||||
nir_cf_list list;
|
||||
nir_cf_extract(&list, nir_before_cf_node(node), nir_after_cf_node(node));
|
||||
nir_cf_delete(&list);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright © 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir_control_flow.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
/* Internal control-flow modification functions used when inserting/removing
|
||||
* instructions.
|
||||
*/
|
||||
|
||||
void nir_handle_add_jump(nir_block *block);
|
||||
void nir_handle_remove_jump(nir_block *block, nir_jump_type type);
|
||||
@@ -0,0 +1,350 @@
|
||||
/*
|
||||
* Copyright © 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
/*
|
||||
* Implements the algorithms for computing the dominance tree and the
|
||||
* dominance frontier from "A Simple, Fast Dominance Algorithm" by Cooper,
|
||||
* Harvey, and Kennedy.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
nir_function_impl *impl;
|
||||
bool progress;
|
||||
} dom_state;
|
||||
|
||||
static bool
|
||||
init_block_cb(nir_block *block, void *_state)
|
||||
{
|
||||
dom_state *state = (dom_state *) _state;
|
||||
if (block == nir_start_block(state->impl))
|
||||
block->imm_dom = block;
|
||||
else
|
||||
block->imm_dom = NULL;
|
||||
block->num_dom_children = 0;
|
||||
|
||||
struct set_entry *entry;
|
||||
set_foreach(block->dom_frontier, entry) {
|
||||
_mesa_set_remove(block->dom_frontier, entry);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static nir_block *
|
||||
intersect(nir_block *b1, nir_block *b2)
|
||||
{
|
||||
while (b1 != b2) {
|
||||
/*
|
||||
* Note, the comparisons here are the opposite of what the paper says
|
||||
* because we index blocks from beginning -> end (i.e. reverse
|
||||
* post-order) instead of post-order like they assume.
|
||||
*/
|
||||
while (b1->index > b2->index)
|
||||
b1 = b1->imm_dom;
|
||||
while (b2->index > b1->index)
|
||||
b2 = b2->imm_dom;
|
||||
}
|
||||
|
||||
return b1;
|
||||
}
|
||||
|
||||
static bool
|
||||
calc_dominance_cb(nir_block *block, void *_state)
|
||||
{
|
||||
dom_state *state = (dom_state *) _state;
|
||||
if (block == nir_start_block(state->impl))
|
||||
return true;
|
||||
|
||||
nir_block *new_idom = NULL;
|
||||
struct set_entry *entry;
|
||||
set_foreach(block->predecessors, entry) {
|
||||
nir_block *pred = (nir_block *) entry->key;
|
||||
|
||||
if (pred->imm_dom) {
|
||||
if (new_idom)
|
||||
new_idom = intersect(pred, new_idom);
|
||||
else
|
||||
new_idom = pred;
|
||||
}
|
||||
}
|
||||
|
||||
assert(new_idom);
|
||||
if (block->imm_dom != new_idom) {
|
||||
block->imm_dom = new_idom;
|
||||
state->progress = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
calc_dom_frontier_cb(nir_block *block, void *state)
|
||||
{
|
||||
(void) state;
|
||||
|
||||
if (block->predecessors->entries > 1) {
|
||||
struct set_entry *entry;
|
||||
set_foreach(block->predecessors, entry) {
|
||||
nir_block *runner = (nir_block *) entry->key;
|
||||
while (runner != block->imm_dom) {
|
||||
_mesa_set_add(runner->dom_frontier, block);
|
||||
runner = runner->imm_dom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute each node's children in the dominance tree from the immediate
|
||||
* dominator information. We do this in three stages:
|
||||
*
|
||||
* 1. Calculate the number of children each node has
|
||||
* 2. Allocate arrays, setting the number of children to 0 again
|
||||
* 3. For each node, add itself to its parent's list of children, using
|
||||
* num_dom_children as an index - at the end of this step, num_dom_children
|
||||
* for each node will be the same as it was at the end of step #1.
|
||||
*/
|
||||
|
||||
static bool
|
||||
block_count_children(nir_block *block, void *state)
|
||||
{
|
||||
(void) state;
|
||||
|
||||
if (block->imm_dom)
|
||||
block->imm_dom->num_dom_children++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
block_alloc_children(nir_block *block, void *state)
|
||||
{
|
||||
void *mem_ctx = state;
|
||||
|
||||
block->dom_children = ralloc_array(mem_ctx, nir_block *,
|
||||
block->num_dom_children);
|
||||
block->num_dom_children = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
block_add_child(nir_block *block, void *state)
|
||||
{
|
||||
(void) state;
|
||||
|
||||
if (block->imm_dom)
|
||||
block->imm_dom->dom_children[block->imm_dom->num_dom_children++] = block;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
calc_dom_children(nir_function_impl* impl)
|
||||
{
|
||||
void *mem_ctx = ralloc_parent(impl);
|
||||
|
||||
nir_foreach_block(impl, block_count_children, NULL);
|
||||
nir_foreach_block(impl, block_alloc_children, mem_ctx);
|
||||
nir_foreach_block(impl, block_add_child, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
calc_dfs_indicies(nir_block *block, unsigned *index)
|
||||
{
|
||||
block->dom_pre_index = (*index)++;
|
||||
|
||||
for (unsigned i = 0; i < block->num_dom_children; i++)
|
||||
calc_dfs_indicies(block->dom_children[i], index);
|
||||
|
||||
block->dom_post_index = (*index)++;
|
||||
}
|
||||
|
||||
void
|
||||
nir_calc_dominance_impl(nir_function_impl *impl)
|
||||
{
|
||||
if (impl->valid_metadata & nir_metadata_dominance)
|
||||
return;
|
||||
|
||||
nir_metadata_require(impl, nir_metadata_block_index);
|
||||
|
||||
dom_state state;
|
||||
state.impl = impl;
|
||||
state.progress = true;
|
||||
|
||||
nir_foreach_block(impl, init_block_cb, &state);
|
||||
|
||||
while (state.progress) {
|
||||
state.progress = false;
|
||||
nir_foreach_block(impl, calc_dominance_cb, &state);
|
||||
}
|
||||
|
||||
nir_foreach_block(impl, calc_dom_frontier_cb, &state);
|
||||
|
||||
nir_block *start_block = nir_start_block(impl);
|
||||
start_block->imm_dom = NULL;
|
||||
|
||||
calc_dom_children(impl);
|
||||
|
||||
unsigned dfs_index = 0;
|
||||
calc_dfs_indicies(start_block, &dfs_index);
|
||||
}
|
||||
|
||||
void
|
||||
nir_calc_dominance(nir_shader *shader)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
nir_calc_dominance_impl(function->impl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the least common anscestor of two blocks. If one of the blocks
|
||||
* is null, the other block is returned.
|
||||
*/
|
||||
nir_block *
|
||||
nir_dominance_lca(nir_block *b1, nir_block *b2)
|
||||
{
|
||||
if (b1 == NULL)
|
||||
return b2;
|
||||
|
||||
if (b2 == NULL)
|
||||
return b1;
|
||||
|
||||
assert(nir_cf_node_get_function(&b1->cf_node) ==
|
||||
nir_cf_node_get_function(&b2->cf_node));
|
||||
|
||||
assert(nir_cf_node_get_function(&b1->cf_node)->valid_metadata &
|
||||
nir_metadata_dominance);
|
||||
|
||||
return intersect(b1, b2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if parent dominates child
|
||||
*/
|
||||
bool
|
||||
nir_block_dominates(nir_block *parent, nir_block *child)
|
||||
{
|
||||
assert(nir_cf_node_get_function(&parent->cf_node) ==
|
||||
nir_cf_node_get_function(&child->cf_node));
|
||||
|
||||
assert(nir_cf_node_get_function(&parent->cf_node)->valid_metadata &
|
||||
nir_metadata_dominance);
|
||||
|
||||
return child->dom_pre_index >= parent->dom_pre_index &&
|
||||
child->dom_post_index <= parent->dom_post_index;
|
||||
}
|
||||
|
||||
static bool
|
||||
dump_block_dom(nir_block *block, void *state)
|
||||
{
|
||||
FILE *fp = state;
|
||||
if (block->imm_dom)
|
||||
fprintf(fp, "\t%u -> %u\n", block->imm_dom->index, block->index);
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
nir_dump_dom_tree_impl(nir_function_impl *impl, FILE *fp)
|
||||
{
|
||||
fprintf(fp, "digraph doms_%s {\n", impl->function->name);
|
||||
nir_foreach_block(impl, dump_block_dom, fp);
|
||||
fprintf(fp, "}\n\n");
|
||||
}
|
||||
|
||||
void
|
||||
nir_dump_dom_tree(nir_shader *shader, FILE *fp)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
nir_dump_dom_tree_impl(function->impl, fp);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
dump_block_dom_frontier(nir_block *block, void *state)
|
||||
{
|
||||
FILE *fp = state;
|
||||
|
||||
fprintf(fp, "DF(%u) = {", block->index);
|
||||
struct set_entry *entry;
|
||||
set_foreach(block->dom_frontier, entry) {
|
||||
nir_block *df = (nir_block *) entry->key;
|
||||
fprintf(fp, "%u, ", df->index);
|
||||
}
|
||||
fprintf(fp, "}\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
nir_dump_dom_frontier_impl(nir_function_impl *impl, FILE *fp)
|
||||
{
|
||||
nir_foreach_block(impl, dump_block_dom_frontier, fp);
|
||||
}
|
||||
|
||||
void
|
||||
nir_dump_dom_frontier(nir_shader *shader, FILE *fp)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
nir_dump_dom_frontier_impl(function->impl, fp);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
dump_block_succs(nir_block *block, void *state)
|
||||
{
|
||||
FILE *fp = state;
|
||||
if (block->successors[0])
|
||||
fprintf(fp, "\t%u -> %u\n", block->index, block->successors[0]->index);
|
||||
if (block->successors[1])
|
||||
fprintf(fp, "\t%u -> %u\n", block->index, block->successors[1]->index);
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
nir_dump_cfg_impl(nir_function_impl *impl, FILE *fp)
|
||||
{
|
||||
fprintf(fp, "digraph cfg_%s {\n", impl->function->name);
|
||||
nir_foreach_block(impl, dump_block_succs, fp);
|
||||
fprintf(fp, "}\n\n");
|
||||
}
|
||||
|
||||
void
|
||||
nir_dump_cfg(nir_shader *shader, FILE *fp)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
nir_dump_cfg_impl(function->impl, fp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,805 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "nir_vla.h"
|
||||
|
||||
/*
|
||||
* This file implements an out-of-SSA pass as described in "Revisiting
|
||||
* Out-of-SSA Translation for Correctness, Code Quality, and Efficiency" by
|
||||
* Boissinot et. al.
|
||||
*/
|
||||
|
||||
struct from_ssa_state {
|
||||
void *mem_ctx;
|
||||
void *dead_ctx;
|
||||
bool phi_webs_only;
|
||||
struct hash_table *merge_node_table;
|
||||
nir_instr *instr;
|
||||
nir_function_impl *impl;
|
||||
};
|
||||
|
||||
/* Returns true if a dominates b */
|
||||
static bool
|
||||
ssa_def_dominates(nir_ssa_def *a, nir_ssa_def *b)
|
||||
{
|
||||
if (a->live_index == 0) {
|
||||
/* SSA undefs always dominate */
|
||||
return true;
|
||||
} else if (b->live_index < a->live_index) {
|
||||
return false;
|
||||
} else if (a->parent_instr->block == b->parent_instr->block) {
|
||||
return a->live_index <= b->live_index;
|
||||
} else {
|
||||
return nir_block_dominates(a->parent_instr->block,
|
||||
b->parent_instr->block);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* The following data structure, which I have named merge_set is a way of
|
||||
* representing a set registers of non-interfering registers. This is
|
||||
* based on the concept of a "dominence forest" presented in "Fast Copy
|
||||
* Coalescing and Live-Range Identification" by Budimlic et. al. but the
|
||||
* implementation concept is taken from "Revisiting Out-of-SSA Translation
|
||||
* for Correctness, Code Quality, and Efficiency" by Boissinot et. al..
|
||||
*
|
||||
* Each SSA definition is associated with a merge_node and the association
|
||||
* is represented by a combination of a hash table and the "def" parameter
|
||||
* in the merge_node structure. The merge_set stores a linked list of
|
||||
* merge_node's in dominence order of the ssa definitions. (Since the
|
||||
* liveness analysis pass indexes the SSA values in dominence order for us,
|
||||
* this is an easy thing to keep up.) It is assumed that no pair of the
|
||||
* nodes in a given set interfere. Merging two sets or checking for
|
||||
* interference can be done in a single linear-time merge-sort walk of the
|
||||
* two lists of nodes.
|
||||
*/
|
||||
struct merge_set;
|
||||
|
||||
typedef struct {
|
||||
struct exec_node node;
|
||||
struct merge_set *set;
|
||||
nir_ssa_def *def;
|
||||
} merge_node;
|
||||
|
||||
typedef struct merge_set {
|
||||
struct exec_list nodes;
|
||||
unsigned size;
|
||||
nir_register *reg;
|
||||
} merge_set;
|
||||
|
||||
#if 0
|
||||
static void
|
||||
merge_set_dump(merge_set *set, FILE *fp)
|
||||
{
|
||||
nir_ssa_def *dom[set->size];
|
||||
int dom_idx = -1;
|
||||
|
||||
foreach_list_typed(merge_node, node, node, &set->nodes) {
|
||||
while (dom_idx >= 0 && !ssa_def_dominates(dom[dom_idx], node->def))
|
||||
dom_idx--;
|
||||
|
||||
for (int i = 0; i <= dom_idx; i++)
|
||||
fprintf(fp, " ");
|
||||
|
||||
if (node->def->name)
|
||||
fprintf(fp, "ssa_%d /* %s */\n", node->def->index, node->def->name);
|
||||
else
|
||||
fprintf(fp, "ssa_%d\n", node->def->index);
|
||||
|
||||
dom[++dom_idx] = node->def;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static merge_node *
|
||||
get_merge_node(nir_ssa_def *def, struct from_ssa_state *state)
|
||||
{
|
||||
struct hash_entry *entry =
|
||||
_mesa_hash_table_search(state->merge_node_table, def);
|
||||
if (entry)
|
||||
return entry->data;
|
||||
|
||||
merge_set *set = ralloc(state->dead_ctx, merge_set);
|
||||
exec_list_make_empty(&set->nodes);
|
||||
set->size = 1;
|
||||
set->reg = NULL;
|
||||
|
||||
merge_node *node = ralloc(state->dead_ctx, merge_node);
|
||||
node->set = set;
|
||||
node->def = def;
|
||||
exec_list_push_head(&set->nodes, &node->node);
|
||||
|
||||
_mesa_hash_table_insert(state->merge_node_table, def, node);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static bool
|
||||
merge_nodes_interfere(merge_node *a, merge_node *b)
|
||||
{
|
||||
return nir_ssa_defs_interfere(a->def, b->def);
|
||||
}
|
||||
|
||||
/* Merges b into a */
|
||||
static merge_set *
|
||||
merge_merge_sets(merge_set *a, merge_set *b)
|
||||
{
|
||||
struct exec_node *an = exec_list_get_head(&a->nodes);
|
||||
struct exec_node *bn = exec_list_get_head(&b->nodes);
|
||||
while (!exec_node_is_tail_sentinel(bn)) {
|
||||
merge_node *a_node = exec_node_data(merge_node, an, node);
|
||||
merge_node *b_node = exec_node_data(merge_node, bn, node);
|
||||
|
||||
if (exec_node_is_tail_sentinel(an) ||
|
||||
a_node->def->live_index > b_node->def->live_index) {
|
||||
struct exec_node *next = bn->next;
|
||||
exec_node_remove(bn);
|
||||
exec_node_insert_node_before(an, bn);
|
||||
exec_node_data(merge_node, bn, node)->set = a;
|
||||
bn = next;
|
||||
} else {
|
||||
an = an->next;
|
||||
}
|
||||
}
|
||||
|
||||
a->size += b->size;
|
||||
b->size = 0;
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
/* Checks for any interference between two merge sets
|
||||
*
|
||||
* This is an implementation of Algorithm 2 in "Revisiting Out-of-SSA
|
||||
* Translation for Correctness, Code Quality, and Efficiency" by
|
||||
* Boissinot et. al.
|
||||
*/
|
||||
static bool
|
||||
merge_sets_interfere(merge_set *a, merge_set *b)
|
||||
{
|
||||
NIR_VLA(merge_node *, dom, a->size + b->size);
|
||||
int dom_idx = -1;
|
||||
|
||||
struct exec_node *an = exec_list_get_head(&a->nodes);
|
||||
struct exec_node *bn = exec_list_get_head(&b->nodes);
|
||||
while (!exec_node_is_tail_sentinel(an) ||
|
||||
!exec_node_is_tail_sentinel(bn)) {
|
||||
|
||||
merge_node *current;
|
||||
if (exec_node_is_tail_sentinel(an)) {
|
||||
current = exec_node_data(merge_node, bn, node);
|
||||
bn = bn->next;
|
||||
} else if (exec_node_is_tail_sentinel(bn)) {
|
||||
current = exec_node_data(merge_node, an, node);
|
||||
an = an->next;
|
||||
} else {
|
||||
merge_node *a_node = exec_node_data(merge_node, an, node);
|
||||
merge_node *b_node = exec_node_data(merge_node, bn, node);
|
||||
|
||||
if (a_node->def->live_index <= b_node->def->live_index) {
|
||||
current = a_node;
|
||||
an = an->next;
|
||||
} else {
|
||||
current = b_node;
|
||||
bn = bn->next;
|
||||
}
|
||||
}
|
||||
|
||||
while (dom_idx >= 0 &&
|
||||
!ssa_def_dominates(dom[dom_idx]->def, current->def))
|
||||
dom_idx--;
|
||||
|
||||
if (dom_idx >= 0 && merge_nodes_interfere(current, dom[dom_idx]))
|
||||
return true;
|
||||
|
||||
dom[++dom_idx] = current;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
add_parallel_copy_to_end_of_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct from_ssa_state *state = void_state;
|
||||
|
||||
bool need_end_copy = false;
|
||||
if (block->successors[0]) {
|
||||
nir_instr *instr = nir_block_first_instr(block->successors[0]);
|
||||
if (instr && instr->type == nir_instr_type_phi)
|
||||
need_end_copy = true;
|
||||
}
|
||||
|
||||
if (block->successors[1]) {
|
||||
nir_instr *instr = nir_block_first_instr(block->successors[1]);
|
||||
if (instr && instr->type == nir_instr_type_phi)
|
||||
need_end_copy = true;
|
||||
}
|
||||
|
||||
if (need_end_copy) {
|
||||
/* If one of our successors has at least one phi node, we need to
|
||||
* create a parallel copy at the end of the block but before the jump
|
||||
* (if there is one).
|
||||
*/
|
||||
nir_parallel_copy_instr *pcopy =
|
||||
nir_parallel_copy_instr_create(state->dead_ctx);
|
||||
|
||||
nir_instr_insert(nir_after_block_before_jump(block), &pcopy->instr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static nir_parallel_copy_instr *
|
||||
get_parallel_copy_at_end_of_block(nir_block *block)
|
||||
{
|
||||
nir_instr *last_instr = nir_block_last_instr(block);
|
||||
if (last_instr == NULL)
|
||||
return NULL;
|
||||
|
||||
/* The last instruction may be a jump in which case the parallel copy is
|
||||
* right before it.
|
||||
*/
|
||||
if (last_instr->type == nir_instr_type_jump)
|
||||
last_instr = nir_instr_prev(last_instr);
|
||||
|
||||
if (last_instr && last_instr->type == nir_instr_type_parallel_copy)
|
||||
return nir_instr_as_parallel_copy(last_instr);
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** Isolate phi nodes with parallel copies
|
||||
*
|
||||
* In order to solve the dependency problems with the sources and
|
||||
* destinations of phi nodes, we first isolate them by adding parallel
|
||||
* copies to the beginnings and ends of basic blocks. For every block with
|
||||
* phi nodes, we add a parallel copy immediately following the last phi
|
||||
* node that copies the destinations of all of the phi nodes to new SSA
|
||||
* values. We also add a parallel copy to the end of every block that has
|
||||
* a successor with phi nodes that, for each phi node in each successor,
|
||||
* copies the corresponding sorce of the phi node and adjust the phi to
|
||||
* used the destination of the parallel copy.
|
||||
*
|
||||
* In SSA form, each value has exactly one definition. What this does is
|
||||
* ensure that each value used in a phi also has exactly one use. The
|
||||
* destinations of phis are only used by the parallel copy immediately
|
||||
* following the phi nodes and. Thanks to the parallel copy at the end of
|
||||
* the predecessor block, the sources of phi nodes are are the only use of
|
||||
* that value. This allows us to immediately assign all the sources and
|
||||
* destinations of any given phi node to the same register without worrying
|
||||
* about interference at all. We do coalescing to get rid of the parallel
|
||||
* copies where possible.
|
||||
*
|
||||
* Before this pass can be run, we have to iterate over the blocks with
|
||||
* add_parallel_copy_to_end_of_block to ensure that the parallel copies at
|
||||
* the ends of blocks exist. We can create the ones at the beginnings as
|
||||
* we go, but the ones at the ends of blocks need to be created ahead of
|
||||
* time because of potential back-edges in the CFG.
|
||||
*/
|
||||
static bool
|
||||
isolate_phi_nodes_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct from_ssa_state *state = void_state;
|
||||
|
||||
nir_instr *last_phi_instr = NULL;
|
||||
nir_foreach_instr(block, instr) {
|
||||
/* Phi nodes only ever come at the start of a block */
|
||||
if (instr->type != nir_instr_type_phi)
|
||||
break;
|
||||
|
||||
last_phi_instr = instr;
|
||||
}
|
||||
|
||||
/* If we don't have any phi's, then there's nothing for us to do. */
|
||||
if (last_phi_instr == NULL)
|
||||
return true;
|
||||
|
||||
/* If we have phi nodes, we need to create a parallel copy at the
|
||||
* start of this block but after the phi nodes.
|
||||
*/
|
||||
nir_parallel_copy_instr *block_pcopy =
|
||||
nir_parallel_copy_instr_create(state->dead_ctx);
|
||||
nir_instr_insert_after(last_phi_instr, &block_pcopy->instr);
|
||||
|
||||
nir_foreach_instr(block, instr) {
|
||||
/* Phi nodes only ever come at the start of a block */
|
||||
if (instr->type != nir_instr_type_phi)
|
||||
break;
|
||||
|
||||
nir_phi_instr *phi = nir_instr_as_phi(instr);
|
||||
assert(phi->dest.is_ssa);
|
||||
nir_foreach_phi_src(phi, src) {
|
||||
nir_parallel_copy_instr *pcopy =
|
||||
get_parallel_copy_at_end_of_block(src->pred);
|
||||
assert(pcopy);
|
||||
|
||||
nir_parallel_copy_entry *entry = rzalloc(state->dead_ctx,
|
||||
nir_parallel_copy_entry);
|
||||
nir_ssa_dest_init(&pcopy->instr, &entry->dest,
|
||||
phi->dest.ssa.num_components, src->src.ssa->name);
|
||||
exec_list_push_tail(&pcopy->entries, &entry->node);
|
||||
|
||||
assert(src->src.is_ssa);
|
||||
nir_instr_rewrite_src(&pcopy->instr, &entry->src, src->src);
|
||||
|
||||
nir_instr_rewrite_src(&phi->instr, &src->src,
|
||||
nir_src_for_ssa(&entry->dest.ssa));
|
||||
}
|
||||
|
||||
nir_parallel_copy_entry *entry = rzalloc(state->dead_ctx,
|
||||
nir_parallel_copy_entry);
|
||||
nir_ssa_dest_init(&block_pcopy->instr, &entry->dest,
|
||||
phi->dest.ssa.num_components, phi->dest.ssa.name);
|
||||
exec_list_push_tail(&block_pcopy->entries, &entry->node);
|
||||
|
||||
nir_ssa_def_rewrite_uses(&phi->dest.ssa,
|
||||
nir_src_for_ssa(&entry->dest.ssa));
|
||||
|
||||
nir_instr_rewrite_src(&block_pcopy->instr, &entry->src,
|
||||
nir_src_for_ssa(&phi->dest.ssa));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
coalesce_phi_nodes_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct from_ssa_state *state = void_state;
|
||||
|
||||
nir_foreach_instr(block, instr) {
|
||||
/* Phi nodes only ever come at the start of a block */
|
||||
if (instr->type != nir_instr_type_phi)
|
||||
break;
|
||||
|
||||
nir_phi_instr *phi = nir_instr_as_phi(instr);
|
||||
|
||||
assert(phi->dest.is_ssa);
|
||||
merge_node *dest_node = get_merge_node(&phi->dest.ssa, state);
|
||||
|
||||
nir_foreach_phi_src(phi, src) {
|
||||
assert(src->src.is_ssa);
|
||||
merge_node *src_node = get_merge_node(src->src.ssa, state);
|
||||
if (src_node->set != dest_node->set)
|
||||
merge_merge_sets(dest_node->set, src_node->set);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
aggressive_coalesce_parallel_copy(nir_parallel_copy_instr *pcopy,
|
||||
struct from_ssa_state *state)
|
||||
{
|
||||
nir_foreach_parallel_copy_entry(pcopy, entry) {
|
||||
if (!entry->src.is_ssa)
|
||||
continue;
|
||||
|
||||
/* Since load_const instructions are SSA only, we can't replace their
|
||||
* destinations with registers and, therefore, can't coalesce them.
|
||||
*/
|
||||
if (entry->src.ssa->parent_instr->type == nir_instr_type_load_const)
|
||||
continue;
|
||||
|
||||
/* Don't try and coalesce these */
|
||||
if (entry->dest.ssa.num_components != entry->src.ssa->num_components)
|
||||
continue;
|
||||
|
||||
merge_node *src_node = get_merge_node(entry->src.ssa, state);
|
||||
merge_node *dest_node = get_merge_node(&entry->dest.ssa, state);
|
||||
|
||||
if (src_node->set == dest_node->set)
|
||||
continue;
|
||||
|
||||
if (!merge_sets_interfere(src_node->set, dest_node->set))
|
||||
merge_merge_sets(src_node->set, dest_node->set);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
aggressive_coalesce_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct from_ssa_state *state = void_state;
|
||||
|
||||
nir_parallel_copy_instr *start_pcopy = NULL;
|
||||
nir_foreach_instr(block, instr) {
|
||||
/* Phi nodes only ever come at the start of a block */
|
||||
if (instr->type != nir_instr_type_phi) {
|
||||
if (instr->type != nir_instr_type_parallel_copy)
|
||||
break; /* The parallel copy must be right after the phis */
|
||||
|
||||
start_pcopy = nir_instr_as_parallel_copy(instr);
|
||||
|
||||
aggressive_coalesce_parallel_copy(start_pcopy, state);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nir_parallel_copy_instr *end_pcopy =
|
||||
get_parallel_copy_at_end_of_block(block);
|
||||
|
||||
if (end_pcopy && end_pcopy != start_pcopy)
|
||||
aggressive_coalesce_parallel_copy(end_pcopy, state);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
rewrite_ssa_def(nir_ssa_def *def, void *void_state)
|
||||
{
|
||||
struct from_ssa_state *state = void_state;
|
||||
nir_register *reg;
|
||||
|
||||
struct hash_entry *entry =
|
||||
_mesa_hash_table_search(state->merge_node_table, def);
|
||||
if (entry) {
|
||||
/* In this case, we're part of a phi web. Use the web's register. */
|
||||
merge_node *node = (merge_node *)entry->data;
|
||||
|
||||
/* If it doesn't have a register yet, create one. Note that all of
|
||||
* the things in the merge set should be the same so it doesn't
|
||||
* matter which node's definition we use.
|
||||
*/
|
||||
if (node->set->reg == NULL) {
|
||||
node->set->reg = nir_local_reg_create(state->impl);
|
||||
node->set->reg->name = def->name;
|
||||
node->set->reg->num_components = def->num_components;
|
||||
node->set->reg->num_array_elems = 0;
|
||||
}
|
||||
|
||||
reg = node->set->reg;
|
||||
} else {
|
||||
if (state->phi_webs_only)
|
||||
return true;
|
||||
|
||||
/* We leave load_const SSA values alone. They act as immediates to
|
||||
* the backend. If it got coalesced into a phi, that's ok.
|
||||
*/
|
||||
if (def->parent_instr->type == nir_instr_type_load_const)
|
||||
return true;
|
||||
|
||||
reg = nir_local_reg_create(state->impl);
|
||||
reg->name = def->name;
|
||||
reg->num_components = def->num_components;
|
||||
reg->num_array_elems = 0;
|
||||
}
|
||||
|
||||
nir_ssa_def_rewrite_uses(def, nir_src_for_reg(reg));
|
||||
assert(list_empty(&def->uses) && list_empty(&def->if_uses));
|
||||
|
||||
if (def->parent_instr->type == nir_instr_type_ssa_undef) {
|
||||
/* If it's an ssa_undef instruction, remove it since we know we just got
|
||||
* rid of all its uses.
|
||||
*/
|
||||
nir_instr *parent_instr = def->parent_instr;
|
||||
nir_instr_remove(parent_instr);
|
||||
ralloc_steal(state->dead_ctx, parent_instr);
|
||||
return true;
|
||||
}
|
||||
|
||||
assert(def->parent_instr->type != nir_instr_type_load_const);
|
||||
|
||||
/* At this point we know a priori that this SSA def is part of a
|
||||
* nir_dest. We can use exec_node_data to get the dest pointer.
|
||||
*/
|
||||
nir_dest *dest = exec_node_data(nir_dest, def, ssa);
|
||||
|
||||
nir_instr_rewrite_dest(state->instr, dest, nir_dest_for_reg(reg));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Resolves ssa definitions to registers. While we're at it, we also
|
||||
* remove phi nodes.
|
||||
*/
|
||||
static bool
|
||||
resolve_registers_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct from_ssa_state *state = void_state;
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
state->instr = instr;
|
||||
nir_foreach_ssa_def(instr, rewrite_ssa_def, state);
|
||||
|
||||
if (instr->type == nir_instr_type_phi) {
|
||||
nir_instr_remove(instr);
|
||||
ralloc_steal(state->dead_ctx, instr);
|
||||
}
|
||||
}
|
||||
state->instr = NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
emit_copy(nir_parallel_copy_instr *pcopy, nir_src src, nir_src dest_src,
|
||||
void *mem_ctx)
|
||||
{
|
||||
assert(!dest_src.is_ssa &&
|
||||
dest_src.reg.indirect == NULL &&
|
||||
dest_src.reg.base_offset == 0);
|
||||
|
||||
if (src.is_ssa)
|
||||
assert(src.ssa->num_components >= dest_src.reg.reg->num_components);
|
||||
else
|
||||
assert(src.reg.reg->num_components >= dest_src.reg.reg->num_components);
|
||||
|
||||
nir_alu_instr *mov = nir_alu_instr_create(mem_ctx, nir_op_imov);
|
||||
nir_src_copy(&mov->src[0].src, &src, mov);
|
||||
mov->dest.dest = nir_dest_for_reg(dest_src.reg.reg);
|
||||
mov->dest.write_mask = (1 << dest_src.reg.reg->num_components) - 1;
|
||||
|
||||
nir_instr_insert_before(&pcopy->instr, &mov->instr);
|
||||
}
|
||||
|
||||
/* Resolves a single parallel copy operation into a sequence of mov's
|
||||
*
|
||||
* This is based on Algorithm 1 from "Revisiting Out-of-SSA Translation for
|
||||
* Correctness, Code Quality, and Efficiency" by Boissinot et. al..
|
||||
* However, I never got the algorithm to work as written, so this version
|
||||
* is slightly modified.
|
||||
*
|
||||
* The algorithm works by playing this little shell game with the values.
|
||||
* We start by recording where every source value is and which source value
|
||||
* each destination value should receive. We then grab any copy whose
|
||||
* destination is "empty", i.e. not used as a source, and do the following:
|
||||
* - Find where its source value currently lives
|
||||
* - Emit the move instruction
|
||||
* - Set the location of the source value to the destination
|
||||
* - Mark the location containing the source value
|
||||
* - Mark the destination as no longer needing to be copied
|
||||
*
|
||||
* When we run out of "empty" destinations, we have a cycle and so we
|
||||
* create a temporary register, copy to that register, and mark the value
|
||||
* we copied as living in that temporary. Now, the cycle is broken, so we
|
||||
* can continue with the above steps.
|
||||
*/
|
||||
static void
|
||||
resolve_parallel_copy(nir_parallel_copy_instr *pcopy,
|
||||
struct from_ssa_state *state)
|
||||
{
|
||||
unsigned num_copies = 0;
|
||||
nir_foreach_parallel_copy_entry(pcopy, entry) {
|
||||
/* Sources may be SSA */
|
||||
if (!entry->src.is_ssa && entry->src.reg.reg == entry->dest.reg.reg)
|
||||
continue;
|
||||
|
||||
num_copies++;
|
||||
}
|
||||
|
||||
if (num_copies == 0) {
|
||||
/* Hooray, we don't need any copies! */
|
||||
nir_instr_remove(&pcopy->instr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* The register/source corresponding to the given index */
|
||||
NIR_VLA_ZERO(nir_src, values, num_copies * 2);
|
||||
|
||||
/* The current location of a given piece of data. We will use -1 for "null" */
|
||||
NIR_VLA_FILL(int, loc, num_copies * 2, -1);
|
||||
|
||||
/* The piece of data that the given piece of data is to be copied from. We will use -1 for "null" */
|
||||
NIR_VLA_FILL(int, pred, num_copies * 2, -1);
|
||||
|
||||
/* The destinations we have yet to properly fill */
|
||||
NIR_VLA(int, to_do, num_copies * 2);
|
||||
int to_do_idx = -1;
|
||||
|
||||
/* Now we set everything up:
|
||||
* - All values get assigned a temporary index
|
||||
* - Current locations are set from sources
|
||||
* - Predicessors are recorded from sources and destinations
|
||||
*/
|
||||
int num_vals = 0;
|
||||
nir_foreach_parallel_copy_entry(pcopy, entry) {
|
||||
/* Sources may be SSA */
|
||||
if (!entry->src.is_ssa && entry->src.reg.reg == entry->dest.reg.reg)
|
||||
continue;
|
||||
|
||||
int src_idx = -1;
|
||||
for (int i = 0; i < num_vals; ++i) {
|
||||
if (nir_srcs_equal(values[i], entry->src))
|
||||
src_idx = i;
|
||||
}
|
||||
if (src_idx < 0) {
|
||||
src_idx = num_vals++;
|
||||
values[src_idx] = entry->src;
|
||||
}
|
||||
|
||||
nir_src dest_src = nir_src_for_reg(entry->dest.reg.reg);
|
||||
|
||||
int dest_idx = -1;
|
||||
for (int i = 0; i < num_vals; ++i) {
|
||||
if (nir_srcs_equal(values[i], dest_src)) {
|
||||
/* Each destination of a parallel copy instruction should be
|
||||
* unique. A destination may get used as a source, so we still
|
||||
* have to walk the list. However, the predecessor should not,
|
||||
* at this point, be set yet, so we should have -1 here.
|
||||
*/
|
||||
assert(pred[i] == -1);
|
||||
dest_idx = i;
|
||||
}
|
||||
}
|
||||
if (dest_idx < 0) {
|
||||
dest_idx = num_vals++;
|
||||
values[dest_idx] = dest_src;
|
||||
}
|
||||
|
||||
loc[src_idx] = src_idx;
|
||||
pred[dest_idx] = src_idx;
|
||||
|
||||
to_do[++to_do_idx] = dest_idx;
|
||||
}
|
||||
|
||||
/* Currently empty destinations we can go ahead and fill */
|
||||
NIR_VLA(int, ready, num_copies * 2);
|
||||
int ready_idx = -1;
|
||||
|
||||
/* Mark the ones that are ready for copying. We know an index is a
|
||||
* destination if it has a predecessor and it's ready for copying if
|
||||
* it's not marked as containing data.
|
||||
*/
|
||||
for (int i = 0; i < num_vals; i++) {
|
||||
if (pred[i] != -1 && loc[i] == -1)
|
||||
ready[++ready_idx] = i;
|
||||
}
|
||||
|
||||
while (to_do_idx >= 0) {
|
||||
while (ready_idx >= 0) {
|
||||
int b = ready[ready_idx--];
|
||||
int a = pred[b];
|
||||
emit_copy(pcopy, values[loc[a]], values[b], state->mem_ctx);
|
||||
|
||||
/* If any other copies want a they can find it at b */
|
||||
loc[a] = b;
|
||||
|
||||
/* b has been filled, mark it as not needing to be copied */
|
||||
pred[b] = -1;
|
||||
|
||||
/* If a needs to be filled, it's ready for copying now */
|
||||
if (pred[a] != -1)
|
||||
ready[++ready_idx] = a;
|
||||
}
|
||||
int b = to_do[to_do_idx--];
|
||||
if (pred[b] == -1)
|
||||
continue;
|
||||
|
||||
/* If we got here, then we don't have any more trivial copies that we
|
||||
* can do. We have to break a cycle, so we create a new temporary
|
||||
* register for that purpose. Normally, if going out of SSA after
|
||||
* register allocation, you would want to avoid creating temporary
|
||||
* registers. However, we are going out of SSA before register
|
||||
* allocation, so we would rather not create extra register
|
||||
* dependencies for the backend to deal with. If it wants, the
|
||||
* backend can coalesce the (possibly multiple) temporaries.
|
||||
*/
|
||||
assert(num_vals < num_copies * 2);
|
||||
nir_register *reg = nir_local_reg_create(state->impl);
|
||||
reg->name = "copy_temp";
|
||||
reg->num_array_elems = 0;
|
||||
if (values[b].is_ssa)
|
||||
reg->num_components = values[b].ssa->num_components;
|
||||
else
|
||||
reg->num_components = values[b].reg.reg->num_components;
|
||||
values[num_vals].is_ssa = false;
|
||||
values[num_vals].reg.reg = reg;
|
||||
|
||||
emit_copy(pcopy, values[b], values[num_vals], state->mem_ctx);
|
||||
loc[b] = num_vals;
|
||||
ready[++ready_idx] = b;
|
||||
num_vals++;
|
||||
}
|
||||
|
||||
nir_instr_remove(&pcopy->instr);
|
||||
}
|
||||
|
||||
/* Resolves the parallel copies in a block. Each block can have at most
|
||||
* two: One at the beginning, right after all the phi noces, and one at
|
||||
* the end (or right before the final jump if it exists).
|
||||
*/
|
||||
static bool
|
||||
resolve_parallel_copies_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct from_ssa_state *state = void_state;
|
||||
|
||||
/* At this point, we have removed all of the phi nodes. If a parallel
|
||||
* copy existed right after the phi nodes in this block, it is now the
|
||||
* first instruction.
|
||||
*/
|
||||
nir_instr *first_instr = nir_block_first_instr(block);
|
||||
if (first_instr == NULL)
|
||||
return true; /* Empty, nothing to do. */
|
||||
|
||||
if (first_instr->type == nir_instr_type_parallel_copy) {
|
||||
nir_parallel_copy_instr *pcopy = nir_instr_as_parallel_copy(first_instr);
|
||||
|
||||
resolve_parallel_copy(pcopy, state);
|
||||
}
|
||||
|
||||
/* It's possible that the above code already cleaned up the end parallel
|
||||
* copy. However, doing so removed it form the instructions list so we
|
||||
* won't find it here. Therefore, it's safe to go ahead and just look
|
||||
* for one and clean it up if it exists.
|
||||
*/
|
||||
nir_parallel_copy_instr *end_pcopy =
|
||||
get_parallel_copy_at_end_of_block(block);
|
||||
if (end_pcopy)
|
||||
resolve_parallel_copy(end_pcopy, state);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
nir_convert_from_ssa_impl(nir_function_impl *impl, bool phi_webs_only)
|
||||
{
|
||||
struct from_ssa_state state;
|
||||
|
||||
state.mem_ctx = ralloc_parent(impl);
|
||||
state.dead_ctx = ralloc_context(NULL);
|
||||
state.impl = impl;
|
||||
state.phi_webs_only = phi_webs_only;
|
||||
state.merge_node_table = _mesa_hash_table_create(NULL, _mesa_hash_pointer,
|
||||
_mesa_key_pointer_equal);
|
||||
|
||||
nir_foreach_block(impl, add_parallel_copy_to_end_of_block, &state);
|
||||
nir_foreach_block(impl, isolate_phi_nodes_block, &state);
|
||||
|
||||
/* Mark metadata as dirty before we ask for liveness analysis */
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
|
||||
nir_metadata_require(impl, nir_metadata_live_ssa_defs |
|
||||
nir_metadata_dominance);
|
||||
|
||||
nir_foreach_block(impl, coalesce_phi_nodes_block, &state);
|
||||
nir_foreach_block(impl, aggressive_coalesce_block, &state);
|
||||
|
||||
nir_foreach_block(impl, resolve_registers_block, &state);
|
||||
|
||||
nir_foreach_block(impl, resolve_parallel_copies_block, &state);
|
||||
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
|
||||
/* Clean up dead instructions and the hash tables */
|
||||
_mesa_hash_table_destroy(state.merge_node_table, NULL);
|
||||
ralloc_free(state.dead_ctx);
|
||||
}
|
||||
|
||||
void
|
||||
nir_convert_from_ssa(nir_shader *shader, bool phi_webs_only)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
nir_convert_from_ssa_impl(function->impl, phi_webs_only);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright © 2015 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.
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "nir_builder.h"
|
||||
|
||||
static nir_intrinsic_instr *
|
||||
as_intrinsic(nir_instr *instr, nir_intrinsic_op op)
|
||||
{
|
||||
if (instr->type != nir_instr_type_intrinsic)
|
||||
return NULL;
|
||||
|
||||
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
|
||||
if (intrin->intrinsic != op)
|
||||
return NULL;
|
||||
|
||||
return intrin;
|
||||
}
|
||||
|
||||
static nir_intrinsic_instr *
|
||||
as_set_vertex_count(nir_instr *instr)
|
||||
{
|
||||
return as_intrinsic(instr, nir_intrinsic_set_vertex_count);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a geometry shader emits a constant number of vertices, return the
|
||||
* number of vertices. Otherwise, return -1 (unknown).
|
||||
*
|
||||
* This only works if you've used nir_lower_gs_intrinsics() to do vertex
|
||||
* counting at the NIR level.
|
||||
*/
|
||||
int
|
||||
nir_gs_count_vertices(const nir_shader *shader)
|
||||
{
|
||||
int count = -1;
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (!function->impl)
|
||||
continue;
|
||||
|
||||
/* set_vertex_count intrinsics only appear in predecessors of the
|
||||
* end block. So we don't need to walk all of them.
|
||||
*/
|
||||
struct set_entry *entry;
|
||||
set_foreach(function->impl->end_block->predecessors, entry) {
|
||||
nir_block *block = (nir_block *) entry->key;
|
||||
|
||||
nir_foreach_instr_reverse(block, instr) {
|
||||
nir_intrinsic_instr *intrin = as_set_vertex_count(instr);
|
||||
if (!intrin)
|
||||
continue;
|
||||
|
||||
nir_const_value *val = nir_src_as_const_value(intrin->src[0]);
|
||||
/* We've found a non-constant value. Bail. */
|
||||
if (!val)
|
||||
return -1;
|
||||
|
||||
if (count == -1)
|
||||
count = val->i[0];
|
||||
|
||||
/* We've found contradictory set_vertex_count intrinsics.
|
||||
* This can happen if there are early-returns in main() and
|
||||
* different paths emit different numbers of vertices.
|
||||
*/
|
||||
if (count != val->i[0])
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
@@ -0,0 +1,519 @@
|
||||
/*
|
||||
* Copyright © 2014 Connor Abbott
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "nir_instr_set.h"
|
||||
#include "nir_vla.h"
|
||||
|
||||
#define HASH(hash, data) _mesa_fnv32_1a_accumulate((hash), (data))
|
||||
|
||||
static uint32_t
|
||||
hash_src(uint32_t hash, const nir_src *src)
|
||||
{
|
||||
assert(src->is_ssa);
|
||||
hash = HASH(hash, src->ssa);
|
||||
return hash;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
hash_alu_src(uint32_t hash, const nir_alu_src *src, unsigned num_components)
|
||||
{
|
||||
hash = HASH(hash, src->abs);
|
||||
hash = HASH(hash, src->negate);
|
||||
|
||||
for (unsigned i = 0; i < num_components; i++)
|
||||
hash = HASH(hash, src->swizzle[i]);
|
||||
|
||||
hash = hash_src(hash, &src->src);
|
||||
return hash;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
hash_alu(uint32_t hash, const nir_alu_instr *instr)
|
||||
{
|
||||
hash = HASH(hash, instr->op);
|
||||
hash = HASH(hash, instr->dest.dest.ssa.num_components);
|
||||
|
||||
if (nir_op_infos[instr->op].algebraic_properties & NIR_OP_IS_COMMUTATIVE) {
|
||||
assert(nir_op_infos[instr->op].num_inputs == 2);
|
||||
uint32_t hash0 = hash_alu_src(hash, &instr->src[0],
|
||||
nir_ssa_alu_instr_src_components(instr, 0));
|
||||
uint32_t hash1 = hash_alu_src(hash, &instr->src[1],
|
||||
nir_ssa_alu_instr_src_components(instr, 1));
|
||||
/* For commutative operations, we need some commutative way of
|
||||
* combining the hashes. One option would be to XOR them but that
|
||||
* means that anything with two identical sources will hash to 0 and
|
||||
* that's common enough we probably don't want the guaranteed
|
||||
* collision. Either addition or multiplication will also work.
|
||||
*/
|
||||
hash = hash0 * hash1;
|
||||
} else {
|
||||
for (unsigned i = 0; i < nir_op_infos[instr->op].num_inputs; i++) {
|
||||
hash = hash_alu_src(hash, &instr->src[i],
|
||||
nir_ssa_alu_instr_src_components(instr, i));
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
hash_load_const(uint32_t hash, const nir_load_const_instr *instr)
|
||||
{
|
||||
hash = HASH(hash, instr->def.num_components);
|
||||
|
||||
hash = _mesa_fnv32_1a_accumulate_block(hash, instr->value.f,
|
||||
instr->def.num_components
|
||||
* sizeof(instr->value.f[0]));
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static int
|
||||
cmp_phi_src(const void *data1, const void *data2)
|
||||
{
|
||||
nir_phi_src *src1 = *(nir_phi_src **)data1;
|
||||
nir_phi_src *src2 = *(nir_phi_src **)data2;
|
||||
return src1->pred - src2->pred;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
hash_phi(uint32_t hash, const nir_phi_instr *instr)
|
||||
{
|
||||
hash = HASH(hash, instr->instr.block);
|
||||
|
||||
/* sort sources by predecessor, since the order shouldn't matter */
|
||||
unsigned num_preds = instr->instr.block->predecessors->entries;
|
||||
NIR_VLA(nir_phi_src *, srcs, num_preds);
|
||||
unsigned i = 0;
|
||||
nir_foreach_phi_src(instr, src) {
|
||||
srcs[i++] = src;
|
||||
}
|
||||
|
||||
qsort(srcs, num_preds, sizeof(nir_phi_src *), cmp_phi_src);
|
||||
|
||||
for (i = 0; i < num_preds; i++) {
|
||||
hash = hash_src(hash, &srcs[i]->src);
|
||||
hash = HASH(hash, srcs[i]->pred);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
hash_intrinsic(uint32_t hash, const nir_intrinsic_instr *instr)
|
||||
{
|
||||
const nir_intrinsic_info *info = &nir_intrinsic_infos[instr->intrinsic];
|
||||
hash = HASH(hash, instr->intrinsic);
|
||||
|
||||
if (info->has_dest)
|
||||
hash = HASH(hash, instr->dest.ssa.num_components);
|
||||
|
||||
assert(info->num_variables == 0);
|
||||
|
||||
hash = _mesa_fnv32_1a_accumulate_block(hash, instr->const_index,
|
||||
info->num_indices
|
||||
* sizeof(instr->const_index[0]));
|
||||
return hash;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
hash_tex(uint32_t hash, const nir_tex_instr *instr)
|
||||
{
|
||||
hash = HASH(hash, instr->op);
|
||||
hash = HASH(hash, instr->num_srcs);
|
||||
|
||||
for (unsigned i = 0; i < instr->num_srcs; i++) {
|
||||
hash = HASH(hash, instr->src[i].src_type);
|
||||
hash = hash_src(hash, &instr->src[i].src);
|
||||
}
|
||||
|
||||
hash = HASH(hash, instr->coord_components);
|
||||
hash = HASH(hash, instr->sampler_dim);
|
||||
hash = HASH(hash, instr->is_array);
|
||||
hash = HASH(hash, instr->is_shadow);
|
||||
hash = HASH(hash, instr->is_new_style_shadow);
|
||||
hash = HASH(hash, instr->const_offset);
|
||||
unsigned component = instr->component;
|
||||
hash = HASH(hash, component);
|
||||
hash = HASH(hash, instr->sampler_index);
|
||||
hash = HASH(hash, instr->sampler_array_size);
|
||||
|
||||
assert(!instr->sampler);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/* Computes a hash of an instruction for use in a hash table. Note that this
|
||||
* will only work for instructions where instr_can_rewrite() returns true, and
|
||||
* it should return identical hashes for two instructions that are the same
|
||||
* according nir_instrs_equal().
|
||||
*/
|
||||
|
||||
static uint32_t
|
||||
hash_instr(const void *data)
|
||||
{
|
||||
const nir_instr *instr = data;
|
||||
uint32_t hash = _mesa_fnv32_1a_offset_bias;
|
||||
|
||||
switch (instr->type) {
|
||||
case nir_instr_type_alu:
|
||||
hash = hash_alu(hash, nir_instr_as_alu(instr));
|
||||
break;
|
||||
case nir_instr_type_load_const:
|
||||
hash = hash_load_const(hash, nir_instr_as_load_const(instr));
|
||||
break;
|
||||
case nir_instr_type_phi:
|
||||
hash = hash_phi(hash, nir_instr_as_phi(instr));
|
||||
break;
|
||||
case nir_instr_type_intrinsic:
|
||||
hash = hash_intrinsic(hash, nir_instr_as_intrinsic(instr));
|
||||
break;
|
||||
case nir_instr_type_tex:
|
||||
hash = hash_tex(hash, nir_instr_as_tex(instr));
|
||||
break;
|
||||
default:
|
||||
unreachable("Invalid instruction type");
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool
|
||||
nir_srcs_equal(nir_src src1, nir_src src2)
|
||||
{
|
||||
if (src1.is_ssa) {
|
||||
if (src2.is_ssa) {
|
||||
return src1.ssa == src2.ssa;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (src2.is_ssa) {
|
||||
return false;
|
||||
} else {
|
||||
if ((src1.reg.indirect == NULL) != (src2.reg.indirect == NULL))
|
||||
return false;
|
||||
|
||||
if (src1.reg.indirect) {
|
||||
if (!nir_srcs_equal(*src1.reg.indirect, *src2.reg.indirect))
|
||||
return false;
|
||||
}
|
||||
|
||||
return src1.reg.reg == src2.reg.reg &&
|
||||
src1.reg.base_offset == src2.reg.base_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
nir_alu_srcs_equal(const nir_alu_instr *alu1, const nir_alu_instr *alu2,
|
||||
unsigned src1, unsigned src2)
|
||||
{
|
||||
if (alu1->src[src1].abs != alu2->src[src2].abs ||
|
||||
alu1->src[src1].negate != alu2->src[src2].negate)
|
||||
return false;
|
||||
|
||||
for (unsigned i = 0; i < nir_ssa_alu_instr_src_components(alu1, src1); i++) {
|
||||
if (alu1->src[src1].swizzle[i] != alu2->src[src2].swizzle[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return nir_srcs_equal(alu1->src[src1].src, alu2->src[src2].src);
|
||||
}
|
||||
|
||||
/* Returns "true" if two instructions are equal. Note that this will only
|
||||
* work for the subset of instructions defined by instr_can_rewrite(). Also,
|
||||
* it should only return "true" for instructions that hash_instr() will return
|
||||
* the same hash for (ignoring collisions, of course).
|
||||
*/
|
||||
|
||||
static bool
|
||||
nir_instrs_equal(const nir_instr *instr1, const nir_instr *instr2)
|
||||
{
|
||||
if (instr1->type != instr2->type)
|
||||
return false;
|
||||
|
||||
switch (instr1->type) {
|
||||
case nir_instr_type_alu: {
|
||||
nir_alu_instr *alu1 = nir_instr_as_alu(instr1);
|
||||
nir_alu_instr *alu2 = nir_instr_as_alu(instr2);
|
||||
|
||||
if (alu1->op != alu2->op)
|
||||
return false;
|
||||
|
||||
/* TODO: We can probably acutally do something more inteligent such
|
||||
* as allowing different numbers and taking a maximum or something
|
||||
* here */
|
||||
if (alu1->dest.dest.ssa.num_components != alu2->dest.dest.ssa.num_components)
|
||||
return false;
|
||||
|
||||
if (nir_op_infos[alu1->op].algebraic_properties & NIR_OP_IS_COMMUTATIVE) {
|
||||
assert(nir_op_infos[alu1->op].num_inputs == 2);
|
||||
return (nir_alu_srcs_equal(alu1, alu2, 0, 0) &&
|
||||
nir_alu_srcs_equal(alu1, alu2, 1, 1)) ||
|
||||
(nir_alu_srcs_equal(alu1, alu2, 0, 1) &&
|
||||
nir_alu_srcs_equal(alu1, alu2, 1, 0));
|
||||
} else {
|
||||
for (unsigned i = 0; i < nir_op_infos[alu1->op].num_inputs; i++) {
|
||||
if (!nir_alu_srcs_equal(alu1, alu2, i, i))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case nir_instr_type_tex: {
|
||||
nir_tex_instr *tex1 = nir_instr_as_tex(instr1);
|
||||
nir_tex_instr *tex2 = nir_instr_as_tex(instr2);
|
||||
|
||||
if (tex1->op != tex2->op)
|
||||
return false;
|
||||
|
||||
if (tex1->num_srcs != tex2->num_srcs)
|
||||
return false;
|
||||
for (unsigned i = 0; i < tex1->num_srcs; i++) {
|
||||
if (tex1->src[i].src_type != tex2->src[i].src_type ||
|
||||
!nir_srcs_equal(tex1->src[i].src, tex2->src[i].src)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (tex1->coord_components != tex2->coord_components ||
|
||||
tex1->sampler_dim != tex2->sampler_dim ||
|
||||
tex1->is_array != tex2->is_array ||
|
||||
tex1->is_shadow != tex2->is_shadow ||
|
||||
tex1->is_new_style_shadow != tex2->is_new_style_shadow ||
|
||||
memcmp(tex1->const_offset, tex2->const_offset,
|
||||
sizeof(tex1->const_offset)) != 0 ||
|
||||
tex1->component != tex2->component ||
|
||||
tex1->sampler_index != tex2->sampler_index ||
|
||||
tex1->sampler_array_size != tex2->sampler_array_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Don't support un-lowered sampler derefs currently. */
|
||||
assert(!tex1->sampler && !tex2->sampler);
|
||||
|
||||
return true;
|
||||
}
|
||||
case nir_instr_type_load_const: {
|
||||
nir_load_const_instr *load1 = nir_instr_as_load_const(instr1);
|
||||
nir_load_const_instr *load2 = nir_instr_as_load_const(instr2);
|
||||
|
||||
if (load1->def.num_components != load2->def.num_components)
|
||||
return false;
|
||||
|
||||
return memcmp(load1->value.f, load2->value.f,
|
||||
load1->def.num_components * sizeof(*load2->value.f)) == 0;
|
||||
}
|
||||
case nir_instr_type_phi: {
|
||||
nir_phi_instr *phi1 = nir_instr_as_phi(instr1);
|
||||
nir_phi_instr *phi2 = nir_instr_as_phi(instr2);
|
||||
|
||||
if (phi1->instr.block != phi2->instr.block)
|
||||
return false;
|
||||
|
||||
nir_foreach_phi_src(phi1, src1) {
|
||||
nir_foreach_phi_src(phi2, src2) {
|
||||
if (src1->pred == src2->pred) {
|
||||
if (!nir_srcs_equal(src1->src, src2->src))
|
||||
return false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
case nir_instr_type_intrinsic: {
|
||||
nir_intrinsic_instr *intrinsic1 = nir_instr_as_intrinsic(instr1);
|
||||
nir_intrinsic_instr *intrinsic2 = nir_instr_as_intrinsic(instr2);
|
||||
const nir_intrinsic_info *info =
|
||||
&nir_intrinsic_infos[intrinsic1->intrinsic];
|
||||
|
||||
if (intrinsic1->intrinsic != intrinsic2->intrinsic ||
|
||||
intrinsic1->num_components != intrinsic2->num_components)
|
||||
return false;
|
||||
|
||||
if (info->has_dest && intrinsic1->dest.ssa.num_components !=
|
||||
intrinsic2->dest.ssa.num_components)
|
||||
return false;
|
||||
|
||||
for (unsigned i = 0; i < info->num_srcs; i++) {
|
||||
if (!nir_srcs_equal(intrinsic1->src[i], intrinsic2->src[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(info->num_variables == 0);
|
||||
|
||||
for (unsigned i = 0; i < info->num_indices; i++) {
|
||||
if (intrinsic1->const_index[i] != intrinsic2->const_index[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
case nir_instr_type_call:
|
||||
case nir_instr_type_jump:
|
||||
case nir_instr_type_ssa_undef:
|
||||
case nir_instr_type_parallel_copy:
|
||||
default:
|
||||
unreachable("Invalid instruction type");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
src_is_ssa(nir_src *src, void *data)
|
||||
{
|
||||
(void) data;
|
||||
return src->is_ssa;
|
||||
}
|
||||
|
||||
static bool
|
||||
dest_is_ssa(nir_dest *dest, void *data)
|
||||
{
|
||||
(void) data;
|
||||
return dest->is_ssa;
|
||||
}
|
||||
|
||||
/* This function determines if uses of an instruction can safely be rewritten
|
||||
* to use another identical instruction instead. Note that this function must
|
||||
* be kept in sync with hash_instr() and nir_instrs_equal() -- only
|
||||
* instructions that pass this test will be handed on to those functions, and
|
||||
* conversely they must handle everything that this function returns true for.
|
||||
*/
|
||||
|
||||
static bool
|
||||
instr_can_rewrite(nir_instr *instr)
|
||||
{
|
||||
/* We only handle SSA. */
|
||||
if (!nir_foreach_dest(instr, dest_is_ssa, NULL) ||
|
||||
!nir_foreach_src(instr, src_is_ssa, NULL))
|
||||
return false;
|
||||
|
||||
switch (instr->type) {
|
||||
case nir_instr_type_alu:
|
||||
case nir_instr_type_load_const:
|
||||
case nir_instr_type_phi:
|
||||
return true;
|
||||
case nir_instr_type_tex: {
|
||||
nir_tex_instr *tex = nir_instr_as_tex(instr);
|
||||
|
||||
/* Don't support un-lowered sampler derefs currently. */
|
||||
if (tex->sampler)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
case nir_instr_type_intrinsic: {
|
||||
const nir_intrinsic_info *info =
|
||||
&nir_intrinsic_infos[nir_instr_as_intrinsic(instr)->intrinsic];
|
||||
return (info->flags & NIR_INTRINSIC_CAN_ELIMINATE) &&
|
||||
(info->flags & NIR_INTRINSIC_CAN_REORDER) &&
|
||||
info->num_variables == 0; /* not implemented yet */
|
||||
}
|
||||
case nir_instr_type_call:
|
||||
case nir_instr_type_jump:
|
||||
case nir_instr_type_ssa_undef:
|
||||
return false;
|
||||
case nir_instr_type_parallel_copy:
|
||||
default:
|
||||
unreachable("Invalid instruction type");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static nir_ssa_def *
|
||||
nir_instr_get_dest_ssa_def(nir_instr *instr)
|
||||
{
|
||||
switch (instr->type) {
|
||||
case nir_instr_type_alu:
|
||||
assert(nir_instr_as_alu(instr)->dest.dest.is_ssa);
|
||||
return &nir_instr_as_alu(instr)->dest.dest.ssa;
|
||||
case nir_instr_type_load_const:
|
||||
return &nir_instr_as_load_const(instr)->def;
|
||||
case nir_instr_type_phi:
|
||||
assert(nir_instr_as_phi(instr)->dest.is_ssa);
|
||||
return &nir_instr_as_phi(instr)->dest.ssa;
|
||||
case nir_instr_type_intrinsic:
|
||||
assert(nir_instr_as_intrinsic(instr)->dest.is_ssa);
|
||||
return &nir_instr_as_intrinsic(instr)->dest.ssa;
|
||||
case nir_instr_type_tex:
|
||||
assert(nir_instr_as_tex(instr)->dest.is_ssa);
|
||||
return &nir_instr_as_tex(instr)->dest.ssa;
|
||||
default:
|
||||
unreachable("We never ask for any of these");
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
cmp_func(const void *data1, const void *data2)
|
||||
{
|
||||
return nir_instrs_equal(data1, data2);
|
||||
}
|
||||
|
||||
struct set *
|
||||
nir_instr_set_create(void *mem_ctx)
|
||||
{
|
||||
return _mesa_set_create(mem_ctx, hash_instr, cmp_func);
|
||||
}
|
||||
|
||||
void
|
||||
nir_instr_set_destroy(struct set *instr_set)
|
||||
{
|
||||
_mesa_set_destroy(instr_set, NULL);
|
||||
}
|
||||
|
||||
bool
|
||||
nir_instr_set_add_or_rewrite(struct set *instr_set, nir_instr *instr)
|
||||
{
|
||||
if (!instr_can_rewrite(instr))
|
||||
return false;
|
||||
|
||||
struct set_entry *entry = _mesa_set_search(instr_set, instr);
|
||||
if (entry) {
|
||||
nir_ssa_def *def = nir_instr_get_dest_ssa_def(instr);
|
||||
nir_ssa_def *new_def =
|
||||
nir_instr_get_dest_ssa_def((nir_instr *) entry->key);
|
||||
nir_ssa_def_rewrite_uses(def, nir_src_for_ssa(new_def));
|
||||
return true;
|
||||
}
|
||||
|
||||
_mesa_set_add(instr_set, instr);
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
nir_instr_set_remove(struct set *instr_set, nir_instr *instr)
|
||||
{
|
||||
if (!instr_can_rewrite(instr))
|
||||
return;
|
||||
|
||||
struct set_entry *entry = _mesa_set_search(instr_set, instr);
|
||||
if (entry)
|
||||
_mesa_set_remove(instr_set, entry);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright © 2014 Connor Abbott
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
/**
|
||||
* This file defines functions for creating, destroying, and manipulating an
|
||||
* "instruction set," which is an abstraction for finding duplicate
|
||||
* instructions using a hash set. Note that the question of whether an
|
||||
* instruction is actually a duplicate (e.g. whether it has any side effects)
|
||||
* is handled transparently. The user can pass any instruction to
|
||||
* nir_instr_set_add_or_rewrite() and nir_instr_set_remove(), and if the
|
||||
* instruction isn't safe to rewrite or isn't supported, it's silently
|
||||
* removed.
|
||||
*/
|
||||
|
||||
/*@{*/
|
||||
|
||||
/** Creates an instruction set, using a given ralloc mem_ctx */
|
||||
struct set *nir_instr_set_create(void *mem_ctx);
|
||||
|
||||
/** Destroys an instruction set. */
|
||||
void nir_instr_set_destroy(struct set *instr_set);
|
||||
|
||||
/**
|
||||
* Adds an instruction to an instruction set if it doesn't exist, or if it
|
||||
* does already exist, rewrites all uses of it to point to the other
|
||||
* already-inserted instruction. Returns 'true' if the uses of the instruction
|
||||
* were rewritten.
|
||||
*/
|
||||
bool nir_instr_set_add_or_rewrite(struct set *instr_set, nir_instr *instr);
|
||||
|
||||
/**
|
||||
* Removes an instruction from an instruction set, so that other instructions
|
||||
* won't be merged with it.
|
||||
*/
|
||||
void nir_instr_set_remove(struct set *instr_set, nir_instr *instr);
|
||||
|
||||
/*@}*/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright © 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
#define OPCODE(name) nir_intrinsic_##name
|
||||
|
||||
#define INTRINSIC(_name, _num_srcs, _src_components, _has_dest, \
|
||||
_dest_components, _num_variables, _num_indices, _flags) \
|
||||
{ \
|
||||
.name = #_name, \
|
||||
.num_srcs = _num_srcs, \
|
||||
.src_components = _src_components, \
|
||||
.has_dest = _has_dest, \
|
||||
.dest_components = _dest_components, \
|
||||
.num_variables = _num_variables, \
|
||||
.num_indices = _num_indices, \
|
||||
.flags = _flags \
|
||||
},
|
||||
|
||||
#define LAST_INTRINSIC(name)
|
||||
|
||||
const nir_intrinsic_info nir_intrinsic_infos[nir_num_intrinsics] = {
|
||||
#include "nir_intrinsics.h"
|
||||
};
|
||||
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* Copyright © 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* This header file defines all the available intrinsics in one place. It
|
||||
* expands to a list of macros of the form:
|
||||
*
|
||||
* INTRINSIC(name, num_srcs, src_components, has_dest, dest_components,
|
||||
* num_variables, num_indices, flags)
|
||||
*
|
||||
* Which should correspond one-to-one with the nir_intrinsic_info structure. It
|
||||
* is included in both ir.h to create the nir_intrinsic enum (with members of
|
||||
* the form nir_intrinsic_(name)) and and in opcodes.c to create
|
||||
* nir_intrinsic_infos, which is a const array of nir_intrinsic_info structures
|
||||
* for each intrinsic.
|
||||
*/
|
||||
|
||||
#define ARR(...) { __VA_ARGS__ }
|
||||
|
||||
|
||||
INTRINSIC(load_var, 0, ARR(), true, 0, 1, 0, NIR_INTRINSIC_CAN_ELIMINATE)
|
||||
INTRINSIC(store_var, 1, ARR(0), false, 0, 1, 1, 0)
|
||||
INTRINSIC(copy_var, 0, ARR(), false, 0, 2, 0, 0)
|
||||
|
||||
/*
|
||||
* Interpolation of input. The interp_var_at* intrinsics are similar to the
|
||||
* load_var intrinsic acting an a shader input except that they interpolate
|
||||
* the input differently. The at_sample and at_offset intrinsics take an
|
||||
* aditional source that is a integer sample id or a vec2 position offset
|
||||
* respectively.
|
||||
*/
|
||||
|
||||
INTRINSIC(interp_var_at_centroid, 0, ARR(0), true, 0, 1, 0,
|
||||
NIR_INTRINSIC_CAN_ELIMINATE | NIR_INTRINSIC_CAN_REORDER)
|
||||
INTRINSIC(interp_var_at_sample, 1, ARR(1), true, 0, 1, 0,
|
||||
NIR_INTRINSIC_CAN_ELIMINATE | NIR_INTRINSIC_CAN_REORDER)
|
||||
INTRINSIC(interp_var_at_offset, 1, ARR(2), true, 0, 1, 0,
|
||||
NIR_INTRINSIC_CAN_ELIMINATE | NIR_INTRINSIC_CAN_REORDER)
|
||||
|
||||
/*
|
||||
* Ask the driver for the size of a given buffer. It takes the buffer index
|
||||
* as source.
|
||||
*/
|
||||
INTRINSIC(get_buffer_size, 1, ARR(1), true, 1, 0, 0,
|
||||
NIR_INTRINSIC_CAN_ELIMINATE | NIR_INTRINSIC_CAN_REORDER)
|
||||
|
||||
/*
|
||||
* a barrier is an intrinsic with no inputs/outputs but which can't be moved
|
||||
* around/optimized in general
|
||||
*/
|
||||
#define BARRIER(name) INTRINSIC(name, 0, ARR(), false, 0, 0, 0, 0)
|
||||
|
||||
BARRIER(barrier)
|
||||
BARRIER(discard)
|
||||
|
||||
/*
|
||||
* Memory barrier with semantics analogous to the memoryBarrier() GLSL
|
||||
* intrinsic.
|
||||
*/
|
||||
BARRIER(memory_barrier)
|
||||
|
||||
/*
|
||||
* Shader clock intrinsic with semantics analogous to the clock2x32ARB()
|
||||
* GLSL intrinsic.
|
||||
* The latter can be used as code motion barrier, which is currently not
|
||||
* feasible with NIR.
|
||||
*/
|
||||
INTRINSIC(shader_clock, 0, ARR(), true, 1, 0, 0, NIR_INTRINSIC_CAN_ELIMINATE)
|
||||
|
||||
/*
|
||||
* Memory barrier with semantics analogous to the compute shader
|
||||
* groupMemoryBarrier(), memoryBarrierAtomicCounter(), memoryBarrierBuffer(),
|
||||
* memoryBarrierImage() and memoryBarrierShared() GLSL intrinsics.
|
||||
*/
|
||||
BARRIER(group_memory_barrier)
|
||||
BARRIER(memory_barrier_atomic_counter)
|
||||
BARRIER(memory_barrier_buffer)
|
||||
BARRIER(memory_barrier_image)
|
||||
BARRIER(memory_barrier_shared)
|
||||
|
||||
/** A conditional discard, with a single boolean source. */
|
||||
INTRINSIC(discard_if, 1, ARR(1), false, 0, 0, 0, 0)
|
||||
|
||||
/**
|
||||
* Basic Geometry Shader intrinsics.
|
||||
*
|
||||
* emit_vertex implements GLSL's EmitStreamVertex() built-in. It takes a single
|
||||
* index, which is the stream ID to write to.
|
||||
*
|
||||
* end_primitive implements GLSL's EndPrimitive() built-in.
|
||||
*/
|
||||
INTRINSIC(emit_vertex, 0, ARR(), false, 0, 0, 1, 0)
|
||||
INTRINSIC(end_primitive, 0, ARR(), false, 0, 0, 1, 0)
|
||||
|
||||
/**
|
||||
* Geometry Shader intrinsics with a vertex count.
|
||||
*
|
||||
* Alternatively, drivers may implement these intrinsics, and use
|
||||
* nir_lower_gs_intrinsics() to convert from the basic intrinsics.
|
||||
*
|
||||
* These maintain a count of the number of vertices emitted, as an additional
|
||||
* unsigned integer source.
|
||||
*/
|
||||
INTRINSIC(emit_vertex_with_counter, 1, ARR(1), false, 0, 0, 1, 0)
|
||||
INTRINSIC(end_primitive_with_counter, 1, ARR(1), false, 0, 0, 1, 0)
|
||||
INTRINSIC(set_vertex_count, 1, ARR(1), false, 0, 0, 0, 0)
|
||||
|
||||
/*
|
||||
* Atomic counters
|
||||
*
|
||||
* The *_var variants take an atomic_uint nir_variable, while the other,
|
||||
* lowered, variants take a constant buffer index and register offset.
|
||||
*/
|
||||
|
||||
#define ATOMIC(name, flags) \
|
||||
INTRINSIC(atomic_counter_##name##_var, 0, ARR(), true, 1, 1, 0, flags) \
|
||||
INTRINSIC(atomic_counter_##name, 1, ARR(1), true, 1, 0, 1, flags)
|
||||
|
||||
ATOMIC(inc, 0)
|
||||
ATOMIC(dec, 0)
|
||||
ATOMIC(read, NIR_INTRINSIC_CAN_ELIMINATE)
|
||||
|
||||
/*
|
||||
* Image load, store and atomic intrinsics.
|
||||
*
|
||||
* All image intrinsics take an image target passed as a nir_variable. Image
|
||||
* variables contain a number of memory and layout qualifiers that influence
|
||||
* the semantics of the intrinsic.
|
||||
*
|
||||
* All image intrinsics take a four-coordinate vector and a sample index as
|
||||
* first two sources, determining the location within the image that will be
|
||||
* accessed by the intrinsic. Components not applicable to the image target
|
||||
* in use are undefined. Image store takes an additional four-component
|
||||
* argument with the value to be written, and image atomic operations take
|
||||
* either one or two additional scalar arguments with the same meaning as in
|
||||
* the ARB_shader_image_load_store specification.
|
||||
*/
|
||||
INTRINSIC(image_load, 2, ARR(4, 1), true, 4, 1, 0,
|
||||
NIR_INTRINSIC_CAN_ELIMINATE)
|
||||
INTRINSIC(image_store, 3, ARR(4, 1, 4), false, 0, 1, 0, 0)
|
||||
INTRINSIC(image_atomic_add, 3, ARR(4, 1, 1), true, 1, 1, 0, 0)
|
||||
INTRINSIC(image_atomic_min, 3, ARR(4, 1, 1), true, 1, 1, 0, 0)
|
||||
INTRINSIC(image_atomic_max, 3, ARR(4, 1, 1), true, 1, 1, 0, 0)
|
||||
INTRINSIC(image_atomic_and, 3, ARR(4, 1, 1), true, 1, 1, 0, 0)
|
||||
INTRINSIC(image_atomic_or, 3, ARR(4, 1, 1), true, 1, 1, 0, 0)
|
||||
INTRINSIC(image_atomic_xor, 3, ARR(4, 1, 1), true, 1, 1, 0, 0)
|
||||
INTRINSIC(image_atomic_exchange, 3, ARR(4, 1, 1), true, 1, 1, 0, 0)
|
||||
INTRINSIC(image_atomic_comp_swap, 4, ARR(4, 1, 1, 1), true, 1, 1, 0, 0)
|
||||
INTRINSIC(image_size, 0, ARR(), true, 4, 1, 0,
|
||||
NIR_INTRINSIC_CAN_ELIMINATE | NIR_INTRINSIC_CAN_REORDER)
|
||||
INTRINSIC(image_samples, 0, ARR(), true, 1, 1, 0,
|
||||
NIR_INTRINSIC_CAN_ELIMINATE | NIR_INTRINSIC_CAN_REORDER)
|
||||
|
||||
/*
|
||||
* SSBO atomic intrinsics
|
||||
*
|
||||
* All of the SSBO atomic memory operations read a value from memory,
|
||||
* compute a new value using one of the operations below, write the new
|
||||
* value to memory, and return the original value read.
|
||||
*
|
||||
* All operations take 3 sources except CompSwap that takes 4. These
|
||||
* sources represent:
|
||||
*
|
||||
* 0: The SSBO buffer index.
|
||||
* 1: The offset into the SSBO buffer of the variable that the atomic
|
||||
* operation will operate on.
|
||||
* 2: The data parameter to the atomic function (i.e. the value to add
|
||||
* in ssbo_atomic_add, etc).
|
||||
* 3: For CompSwap only: the second data parameter.
|
||||
*/
|
||||
INTRINSIC(ssbo_atomic_add, 3, ARR(1, 1, 1), true, 1, 0, 0, 0)
|
||||
INTRINSIC(ssbo_atomic_imin, 3, ARR(1, 1, 1), true, 1, 0, 0, 0)
|
||||
INTRINSIC(ssbo_atomic_umin, 3, ARR(1, 1, 1), true, 1, 0, 0, 0)
|
||||
INTRINSIC(ssbo_atomic_imax, 3, ARR(1, 1, 1), true, 1, 0, 0, 0)
|
||||
INTRINSIC(ssbo_atomic_umax, 3, ARR(1, 1, 1), true, 1, 0, 0, 0)
|
||||
INTRINSIC(ssbo_atomic_and, 3, ARR(1, 1, 1), true, 1, 0, 0, 0)
|
||||
INTRINSIC(ssbo_atomic_or, 3, ARR(1, 1, 1), true, 1, 0, 0, 0)
|
||||
INTRINSIC(ssbo_atomic_xor, 3, ARR(1, 1, 1), true, 1, 0, 0, 0)
|
||||
INTRINSIC(ssbo_atomic_exchange, 3, ARR(1, 1, 1), true, 1, 0, 0, 0)
|
||||
INTRINSIC(ssbo_atomic_comp_swap, 4, ARR(1, 1, 1, 1), true, 1, 0, 0, 0)
|
||||
|
||||
/*
|
||||
* CS shared variable atomic intrinsics
|
||||
*
|
||||
* All of the shared variable atomic memory operations read a value from
|
||||
* memory, compute a new value using one of the operations below, write the
|
||||
* new value to memory, and return the original value read.
|
||||
*
|
||||
* All operations take 2 sources except CompSwap that takes 3. These
|
||||
* sources represent:
|
||||
*
|
||||
* 0: The offset into the shared variable storage region that the atomic
|
||||
* operation will operate on.
|
||||
* 1: The data parameter to the atomic function (i.e. the value to add
|
||||
* in shared_atomic_add, etc).
|
||||
* 2: For CompSwap only: the second data parameter.
|
||||
*/
|
||||
INTRINSIC(shared_atomic_add, 2, ARR(1, 1), true, 1, 0, 0, 0)
|
||||
INTRINSIC(shared_atomic_imin, 2, ARR(1, 1), true, 1, 0, 0, 0)
|
||||
INTRINSIC(shared_atomic_umin, 2, ARR(1, 1), true, 1, 0, 0, 0)
|
||||
INTRINSIC(shared_atomic_imax, 2, ARR(1, 1), true, 1, 0, 0, 0)
|
||||
INTRINSIC(shared_atomic_umax, 2, ARR(1, 1), true, 1, 0, 0, 0)
|
||||
INTRINSIC(shared_atomic_and, 2, ARR(1, 1), true, 1, 0, 0, 0)
|
||||
INTRINSIC(shared_atomic_or, 2, ARR(1, 1), true, 1, 0, 0, 0)
|
||||
INTRINSIC(shared_atomic_xor, 2, ARR(1, 1), true, 1, 0, 0, 0)
|
||||
INTRINSIC(shared_atomic_exchange, 2, ARR(1, 1), true, 1, 0, 0, 0)
|
||||
INTRINSIC(shared_atomic_comp_swap, 3, ARR(1, 1, 1), true, 1, 0, 0, 0)
|
||||
|
||||
#define SYSTEM_VALUE(name, components, num_indices) \
|
||||
INTRINSIC(load_##name, 0, ARR(), true, components, 0, num_indices, \
|
||||
NIR_INTRINSIC_CAN_ELIMINATE | NIR_INTRINSIC_CAN_REORDER)
|
||||
|
||||
SYSTEM_VALUE(front_face, 1, 0)
|
||||
SYSTEM_VALUE(vertex_id, 1, 0)
|
||||
SYSTEM_VALUE(vertex_id_zero_base, 1, 0)
|
||||
SYSTEM_VALUE(base_vertex, 1, 0)
|
||||
SYSTEM_VALUE(instance_id, 1, 0)
|
||||
SYSTEM_VALUE(base_instance, 1, 0)
|
||||
SYSTEM_VALUE(draw_id, 1, 0)
|
||||
SYSTEM_VALUE(sample_id, 1, 0)
|
||||
SYSTEM_VALUE(sample_pos, 2, 0)
|
||||
SYSTEM_VALUE(sample_mask_in, 1, 0)
|
||||
SYSTEM_VALUE(primitive_id, 1, 0)
|
||||
SYSTEM_VALUE(invocation_id, 1, 0)
|
||||
SYSTEM_VALUE(tess_coord, 3, 0)
|
||||
SYSTEM_VALUE(tess_level_outer, 4, 0)
|
||||
SYSTEM_VALUE(tess_level_inner, 2, 0)
|
||||
SYSTEM_VALUE(patch_vertices_in, 1, 0)
|
||||
SYSTEM_VALUE(local_invocation_id, 3, 0)
|
||||
SYSTEM_VALUE(work_group_id, 3, 0)
|
||||
SYSTEM_VALUE(user_clip_plane, 4, 1) /* const_index[0] is user_clip_plane[idx] */
|
||||
SYSTEM_VALUE(num_work_groups, 3, 0)
|
||||
SYSTEM_VALUE(helper_invocation, 1, 0)
|
||||
|
||||
/*
|
||||
* Load operations pull data from some piece of GPU memory. All load
|
||||
* operations operate in terms of offsets into some piece of theoretical
|
||||
* memory. Loads from externally visible memory (UBO and SSBO) simply take a
|
||||
* byte offset as a source. Loads from opaque memory (uniforms, inputs, etc.)
|
||||
* take a base+offset pair where the base (const_index[0]) gives the location
|
||||
* of the start of the variable being loaded and and the offset source is a
|
||||
* offset into that variable.
|
||||
*
|
||||
* Some load operations such as UBO/SSBO load and per_vertex loads take an
|
||||
* additional source to specify which UBO/SSBO/vertex to load from.
|
||||
*
|
||||
* The exact address type depends on the lowering pass that generates the
|
||||
* load/store intrinsics. Typically, this is vec4 units for things such as
|
||||
* varying slots and float units for fragment shader inputs. UBO and SSBO
|
||||
* offsets are always in bytes.
|
||||
*/
|
||||
|
||||
#define LOAD(name, srcs, indices, flags) \
|
||||
INTRINSIC(load_##name, srcs, ARR(1, 1, 1, 1), true, 0, 0, indices, flags)
|
||||
|
||||
/* src[] = { offset }. const_index[] = { base } */
|
||||
LOAD(uniform, 1, 1, NIR_INTRINSIC_CAN_ELIMINATE | NIR_INTRINSIC_CAN_REORDER)
|
||||
/* src[] = { buffer_index, offset }. No const_index */
|
||||
LOAD(ubo, 2, 0, NIR_INTRINSIC_CAN_ELIMINATE | NIR_INTRINSIC_CAN_REORDER)
|
||||
/* src[] = { offset }. const_index[] = { base } */
|
||||
LOAD(input, 1, 1, NIR_INTRINSIC_CAN_ELIMINATE | NIR_INTRINSIC_CAN_REORDER)
|
||||
/* src[] = { vertex, offset }. const_index[] = { base } */
|
||||
LOAD(per_vertex_input, 2, 1, NIR_INTRINSIC_CAN_ELIMINATE | NIR_INTRINSIC_CAN_REORDER)
|
||||
/* src[] = { buffer_index, offset }. No const_index */
|
||||
LOAD(ssbo, 2, 0, NIR_INTRINSIC_CAN_ELIMINATE)
|
||||
/* src[] = { offset }. const_index[] = { base } */
|
||||
LOAD(output, 1, 1, NIR_INTRINSIC_CAN_ELIMINATE)
|
||||
/* src[] = { vertex, offset }. const_index[] = { base } */
|
||||
LOAD(per_vertex_output, 2, 1, NIR_INTRINSIC_CAN_ELIMINATE)
|
||||
/* src[] = { offset }. const_index[] = { base } */
|
||||
LOAD(shared, 1, 1, NIR_INTRINSIC_CAN_ELIMINATE)
|
||||
|
||||
/*
|
||||
* Stores work the same way as loads, except now the first source is the value
|
||||
* to store and the second (and possibly third) source specify where to store
|
||||
* the value. SSBO and shared memory stores also have a write mask as
|
||||
* const_index[0].
|
||||
*/
|
||||
|
||||
#define STORE(name, srcs, indices, flags) \
|
||||
INTRINSIC(store_##name, srcs, ARR(0, 1, 1, 1), false, 0, 0, indices, flags)
|
||||
|
||||
/* src[] = { value, offset }. const_index[] = { base, write_mask } */
|
||||
STORE(output, 2, 2, 0)
|
||||
/* src[] = { value, vertex, offset }. const_index[] = { base, write_mask } */
|
||||
STORE(per_vertex_output, 3, 2, 0)
|
||||
/* src[] = { value, block_index, offset }. const_index[] = { write_mask } */
|
||||
STORE(ssbo, 3, 1, 0)
|
||||
/* src[] = { value, offset }. const_index[] = { base, write_mask } */
|
||||
STORE(shared, 2, 2, 0)
|
||||
|
||||
LAST_INTRINSIC(store_shared)
|
||||
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "nir_worklist.h"
|
||||
#include "nir_vla.h"
|
||||
|
||||
/*
|
||||
* Basic liveness analysis. This works only in SSA form.
|
||||
*
|
||||
* This liveness pass treats phi nodes as being melded to the space between
|
||||
* blocks so that the destinations of a phi are in the livein of the block
|
||||
* in which it resides and the sources are in the liveout of the
|
||||
* corresponding block. By formulating the liveness information in this
|
||||
* way, we ensure that the definition of any variable dominates its entire
|
||||
* live range. This is true because the only way that the definition of an
|
||||
* SSA value may not dominate a use is if the use is in a phi node and the
|
||||
* uses in phi no are in the live-out of the corresponding predecessor
|
||||
* block but not in the live-in of the block containing the phi node.
|
||||
*/
|
||||
|
||||
struct live_ssa_defs_state {
|
||||
unsigned num_ssa_defs;
|
||||
unsigned bitset_words;
|
||||
|
||||
nir_block_worklist worklist;
|
||||
};
|
||||
|
||||
static bool
|
||||
index_ssa_def(nir_ssa_def *def, void *void_state)
|
||||
{
|
||||
struct live_ssa_defs_state *state = void_state;
|
||||
|
||||
if (def->parent_instr->type == nir_instr_type_ssa_undef)
|
||||
def->live_index = 0;
|
||||
else
|
||||
def->live_index = state->num_ssa_defs++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
index_ssa_definitions_block(nir_block *block, void *state)
|
||||
{
|
||||
nir_foreach_instr(block, instr)
|
||||
nir_foreach_ssa_def(instr, index_ssa_def, state);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Initialize the liveness data to zero and add the given block to the
|
||||
* worklist.
|
||||
*/
|
||||
static bool
|
||||
init_liveness_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct live_ssa_defs_state *state = void_state;
|
||||
|
||||
block->live_in = reralloc(block, block->live_in, BITSET_WORD,
|
||||
state->bitset_words);
|
||||
memset(block->live_in, 0, state->bitset_words * sizeof(BITSET_WORD));
|
||||
|
||||
block->live_out = reralloc(block, block->live_out, BITSET_WORD,
|
||||
state->bitset_words);
|
||||
memset(block->live_out, 0, state->bitset_words * sizeof(BITSET_WORD));
|
||||
|
||||
nir_block_worklist_push_head(&state->worklist, block);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
set_src_live(nir_src *src, void *void_live)
|
||||
{
|
||||
BITSET_WORD *live = void_live;
|
||||
|
||||
if (!src->is_ssa)
|
||||
return true;
|
||||
|
||||
if (src->ssa->live_index == 0)
|
||||
return true; /* undefined variables are never live */
|
||||
|
||||
BITSET_SET(live, src->ssa->live_index);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
set_ssa_def_dead(nir_ssa_def *def, void *void_live)
|
||||
{
|
||||
BITSET_WORD *live = void_live;
|
||||
|
||||
BITSET_CLEAR(live, def->live_index);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Propagates the live in of succ across the edge to the live out of pred
|
||||
*
|
||||
* Phi nodes exist "between" blocks and all the phi nodes at the start of a
|
||||
* block act "in parallel". When we propagate from the live_in of one
|
||||
* block to the live out of the other, we have to kill any writes from phis
|
||||
* and make live any sources.
|
||||
*
|
||||
* Returns true if updating live out of pred added anything
|
||||
*/
|
||||
static bool
|
||||
propagate_across_edge(nir_block *pred, nir_block *succ,
|
||||
struct live_ssa_defs_state *state)
|
||||
{
|
||||
NIR_VLA(BITSET_WORD, live, state->bitset_words);
|
||||
memcpy(live, succ->live_in, state->bitset_words * sizeof *live);
|
||||
|
||||
nir_foreach_instr(succ, instr) {
|
||||
if (instr->type != nir_instr_type_phi)
|
||||
break;
|
||||
nir_phi_instr *phi = nir_instr_as_phi(instr);
|
||||
|
||||
assert(phi->dest.is_ssa);
|
||||
set_ssa_def_dead(&phi->dest.ssa, live);
|
||||
}
|
||||
|
||||
nir_foreach_instr(succ, instr) {
|
||||
if (instr->type != nir_instr_type_phi)
|
||||
break;
|
||||
nir_phi_instr *phi = nir_instr_as_phi(instr);
|
||||
|
||||
nir_foreach_phi_src(phi, src) {
|
||||
if (src->pred == pred) {
|
||||
set_src_live(&src->src, live);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BITSET_WORD progress = 0;
|
||||
for (unsigned i = 0; i < state->bitset_words; ++i) {
|
||||
progress |= live[i] & ~pred->live_out[i];
|
||||
pred->live_out[i] |= live[i];
|
||||
}
|
||||
return progress != 0;
|
||||
}
|
||||
|
||||
void
|
||||
nir_live_ssa_defs_impl(nir_function_impl *impl)
|
||||
{
|
||||
struct live_ssa_defs_state state;
|
||||
|
||||
/* We start at 1 because we reserve the index value of 0 for ssa_undef
|
||||
* instructions. Those are never live, so their liveness information
|
||||
* can be compacted into a single bit.
|
||||
*/
|
||||
state.num_ssa_defs = 1;
|
||||
nir_foreach_block(impl, index_ssa_definitions_block, &state);
|
||||
|
||||
nir_block_worklist_init(&state.worklist, impl->num_blocks, NULL);
|
||||
|
||||
/* We now know how many unique ssa definitions we have and we can go
|
||||
* ahead and allocate live_in and live_out sets and add all of the
|
||||
* blocks to the worklist.
|
||||
*/
|
||||
state.bitset_words = BITSET_WORDS(state.num_ssa_defs);
|
||||
nir_foreach_block(impl, init_liveness_block, &state);
|
||||
|
||||
/* We're now ready to work through the worklist and update the liveness
|
||||
* sets of each of the blocks. By the time we get to this point, every
|
||||
* block in the function implementation has been pushed onto the
|
||||
* worklist in reverse order. As long as we keep the worklist
|
||||
* up-to-date as we go, everything will get covered.
|
||||
*/
|
||||
while (!nir_block_worklist_is_empty(&state.worklist)) {
|
||||
/* We pop them off in the reverse order we pushed them on. This way
|
||||
* the first walk of the instructions is backwards so we only walk
|
||||
* once in the case of no control flow.
|
||||
*/
|
||||
nir_block *block = nir_block_worklist_pop_head(&state.worklist);
|
||||
|
||||
memcpy(block->live_in, block->live_out,
|
||||
state.bitset_words * sizeof(BITSET_WORD));
|
||||
|
||||
nir_if *following_if = nir_block_get_following_if(block);
|
||||
if (following_if)
|
||||
set_src_live(&following_if->condition, block->live_in);
|
||||
|
||||
nir_foreach_instr_reverse(block, instr) {
|
||||
/* Phi nodes are handled seperately so we want to skip them. Since
|
||||
* we are going backwards and they are at the beginning, we can just
|
||||
* break as soon as we see one.
|
||||
*/
|
||||
if (instr->type == nir_instr_type_phi)
|
||||
break;
|
||||
|
||||
nir_foreach_ssa_def(instr, set_ssa_def_dead, block->live_in);
|
||||
nir_foreach_src(instr, set_src_live, block->live_in);
|
||||
}
|
||||
|
||||
/* Walk over all of the predecessors of the current block updating
|
||||
* their live in with the live out of this one. If anything has
|
||||
* changed, add the predecessor to the work list so that we ensure
|
||||
* that the new information is used.
|
||||
*/
|
||||
struct set_entry *entry;
|
||||
set_foreach(block->predecessors, entry) {
|
||||
nir_block *pred = (nir_block *)entry->key;
|
||||
if (propagate_across_edge(pred, block, &state))
|
||||
nir_block_worklist_push_tail(&state.worklist, pred);
|
||||
}
|
||||
}
|
||||
|
||||
nir_block_worklist_fini(&state.worklist);
|
||||
}
|
||||
|
||||
static bool
|
||||
src_does_not_use_def(nir_src *src, void *def)
|
||||
{
|
||||
return !src->is_ssa || src->ssa != (nir_ssa_def *)def;
|
||||
}
|
||||
|
||||
static bool
|
||||
search_for_use_after_instr(nir_instr *start, nir_ssa_def *def)
|
||||
{
|
||||
/* Only look for a use strictly after the given instruction */
|
||||
struct exec_node *node = start->node.next;
|
||||
while (!exec_node_is_tail_sentinel(node)) {
|
||||
nir_instr *instr = exec_node_data(nir_instr, node, node);
|
||||
if (!nir_foreach_src(instr, src_does_not_use_def, def))
|
||||
return true;
|
||||
node = node->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Returns true if def is live at instr assuming that def comes before
|
||||
* instr in a pre DFS search of the dominance tree.
|
||||
*/
|
||||
static bool
|
||||
nir_ssa_def_is_live_at(nir_ssa_def *def, nir_instr *instr)
|
||||
{
|
||||
if (BITSET_TEST(instr->block->live_out, def->live_index)) {
|
||||
/* Since def dominates instr, if def is in the liveout of the block,
|
||||
* it's live at instr
|
||||
*/
|
||||
return true;
|
||||
} else {
|
||||
if (BITSET_TEST(instr->block->live_in, def->live_index) ||
|
||||
def->parent_instr->block == instr->block) {
|
||||
/* In this case it is either live coming into instr's block or it
|
||||
* is defined in the same block. In this case, we simply need to
|
||||
* see if it is used after instr.
|
||||
*/
|
||||
return search_for_use_after_instr(instr, def);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
nir_ssa_defs_interfere(nir_ssa_def *a, nir_ssa_def *b)
|
||||
{
|
||||
if (a->parent_instr == b->parent_instr) {
|
||||
/* Two variables defined at the same time interfere assuming at
|
||||
* least one isn't dead.
|
||||
*/
|
||||
return true;
|
||||
} else if (a->live_index == 0 || b->live_index == 0) {
|
||||
/* If either variable is an ssa_undef, then there's no interference */
|
||||
return false;
|
||||
} else if (a->live_index < b->live_index) {
|
||||
return nir_ssa_def_is_live_at(a, b->parent_instr);
|
||||
} else {
|
||||
return nir_ssa_def_is_live_at(b, a->parent_instr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright © 2014-2015 Broadcom
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "nir_builder.h"
|
||||
|
||||
/** @file nir_lower_alu_to_scalar.c
|
||||
*
|
||||
* Replaces nir_alu_instr operations with more than one channel used in the
|
||||
* arguments with individual per-channel operations.
|
||||
*/
|
||||
|
||||
static void
|
||||
nir_alu_ssa_dest_init(nir_alu_instr *instr, unsigned num_components)
|
||||
{
|
||||
nir_ssa_dest_init(&instr->instr, &instr->dest.dest, num_components, NULL);
|
||||
instr->dest.write_mask = (1 << num_components) - 1;
|
||||
}
|
||||
|
||||
static void
|
||||
lower_reduction(nir_alu_instr *instr, nir_op chan_op, nir_op merge_op,
|
||||
nir_builder *builder)
|
||||
{
|
||||
unsigned num_components = nir_op_infos[instr->op].input_sizes[0];
|
||||
|
||||
nir_ssa_def *last = NULL;
|
||||
for (unsigned i = 0; i < num_components; i++) {
|
||||
nir_alu_instr *chan = nir_alu_instr_create(builder->shader, chan_op);
|
||||
nir_alu_ssa_dest_init(chan, 1);
|
||||
nir_alu_src_copy(&chan->src[0], &instr->src[0], chan);
|
||||
chan->src[0].swizzle[0] = chan->src[0].swizzle[i];
|
||||
if (nir_op_infos[chan_op].num_inputs > 1) {
|
||||
assert(nir_op_infos[chan_op].num_inputs == 2);
|
||||
nir_alu_src_copy(&chan->src[1], &instr->src[1], chan);
|
||||
chan->src[1].swizzle[0] = chan->src[1].swizzle[i];
|
||||
}
|
||||
|
||||
nir_builder_instr_insert(builder, &chan->instr);
|
||||
|
||||
if (i == 0) {
|
||||
last = &chan->dest.dest.ssa;
|
||||
} else {
|
||||
last = nir_build_alu(builder, merge_op,
|
||||
last, &chan->dest.dest.ssa, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
assert(instr->dest.write_mask == 1);
|
||||
nir_ssa_def_rewrite_uses(&instr->dest.dest.ssa, nir_src_for_ssa(last));
|
||||
nir_instr_remove(&instr->instr);
|
||||
}
|
||||
|
||||
static void
|
||||
lower_alu_instr_scalar(nir_alu_instr *instr, nir_builder *b)
|
||||
{
|
||||
unsigned num_src = nir_op_infos[instr->op].num_inputs;
|
||||
unsigned i, chan;
|
||||
|
||||
assert(instr->dest.dest.is_ssa);
|
||||
assert(instr->dest.write_mask != 0);
|
||||
|
||||
b->cursor = nir_before_instr(&instr->instr);
|
||||
|
||||
#define LOWER_REDUCTION(name, chan, merge) \
|
||||
case name##2: \
|
||||
case name##3: \
|
||||
case name##4: \
|
||||
lower_reduction(instr, chan, merge, b); \
|
||||
return;
|
||||
|
||||
switch (instr->op) {
|
||||
case nir_op_vec4:
|
||||
case nir_op_vec3:
|
||||
case nir_op_vec2:
|
||||
/* We don't need to scalarize these ops, they're the ones generated to
|
||||
* group up outputs into a value that can be SSAed.
|
||||
*/
|
||||
return;
|
||||
|
||||
case nir_op_unpack_unorm_4x8:
|
||||
case nir_op_unpack_snorm_4x8:
|
||||
case nir_op_unpack_unorm_2x16:
|
||||
case nir_op_unpack_snorm_2x16:
|
||||
/* There is no scalar version of these ops, unless we were to break it
|
||||
* down to bitshifts and math (which is definitely not intended).
|
||||
*/
|
||||
return;
|
||||
|
||||
case nir_op_unpack_half_2x16:
|
||||
/* We could split this into unpack_half_2x16_split_[xy], but should
|
||||
* we?
|
||||
*/
|
||||
return;
|
||||
|
||||
case nir_op_fdph: {
|
||||
nir_ssa_def *sum[4];
|
||||
for (unsigned i = 0; i < 3; i++) {
|
||||
sum[i] = nir_fmul(b, nir_channel(b, instr->src[0].src.ssa,
|
||||
instr->src[0].swizzle[i]),
|
||||
nir_channel(b, instr->src[1].src.ssa,
|
||||
instr->src[1].swizzle[i]));
|
||||
}
|
||||
sum[3] = nir_channel(b, instr->src[1].src.ssa, instr->src[1].swizzle[3]);
|
||||
|
||||
nir_ssa_def *val = nir_fadd(b, nir_fadd(b, sum[0], sum[1]),
|
||||
nir_fadd(b, sum[2], sum[3]));
|
||||
|
||||
nir_ssa_def_rewrite_uses(&instr->dest.dest.ssa, nir_src_for_ssa(val));
|
||||
nir_instr_remove(&instr->instr);
|
||||
return;
|
||||
}
|
||||
|
||||
LOWER_REDUCTION(nir_op_fdot, nir_op_fmul, nir_op_fadd);
|
||||
LOWER_REDUCTION(nir_op_ball_fequal, nir_op_feq, nir_op_iand);
|
||||
LOWER_REDUCTION(nir_op_ball_iequal, nir_op_ieq, nir_op_iand);
|
||||
LOWER_REDUCTION(nir_op_bany_fnequal, nir_op_fne, nir_op_ior);
|
||||
LOWER_REDUCTION(nir_op_bany_inequal, nir_op_ine, nir_op_ior);
|
||||
LOWER_REDUCTION(nir_op_fall_equal, nir_op_seq, nir_op_fand);
|
||||
LOWER_REDUCTION(nir_op_fany_nequal, nir_op_sne, nir_op_for);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (instr->dest.dest.ssa.num_components == 1)
|
||||
return;
|
||||
|
||||
unsigned num_components = instr->dest.dest.ssa.num_components;
|
||||
nir_ssa_def *comps[] = { NULL, NULL, NULL, NULL };
|
||||
|
||||
for (chan = 0; chan < 4; chan++) {
|
||||
if (!(instr->dest.write_mask & (1 << chan)))
|
||||
continue;
|
||||
|
||||
nir_alu_instr *lower = nir_alu_instr_create(b->shader, instr->op);
|
||||
for (i = 0; i < num_src; i++) {
|
||||
/* We only handle same-size-as-dest (input_sizes[] == 0) or scalar
|
||||
* args (input_sizes[] == 1).
|
||||
*/
|
||||
assert(nir_op_infos[instr->op].input_sizes[i] < 2);
|
||||
unsigned src_chan = (nir_op_infos[instr->op].input_sizes[i] == 1 ?
|
||||
0 : chan);
|
||||
|
||||
nir_alu_src_copy(&lower->src[i], &instr->src[i], lower);
|
||||
for (int j = 0; j < 4; j++)
|
||||
lower->src[i].swizzle[j] = instr->src[i].swizzle[src_chan];
|
||||
}
|
||||
|
||||
nir_alu_ssa_dest_init(lower, 1);
|
||||
lower->dest.saturate = instr->dest.saturate;
|
||||
comps[chan] = &lower->dest.dest.ssa;
|
||||
|
||||
nir_builder_instr_insert(b, &lower->instr);
|
||||
}
|
||||
|
||||
nir_ssa_def *vec = nir_vec(b, comps, num_components);
|
||||
|
||||
nir_ssa_def_rewrite_uses(&instr->dest.dest.ssa, nir_src_for_ssa(vec));
|
||||
|
||||
nir_instr_remove(&instr->instr);
|
||||
}
|
||||
|
||||
static bool
|
||||
lower_alu_to_scalar_block(nir_block *block, void *builder)
|
||||
{
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type == nir_instr_type_alu)
|
||||
lower_alu_instr_scalar(nir_instr_as_alu(instr), builder);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
nir_lower_alu_to_scalar_impl(nir_function_impl *impl)
|
||||
{
|
||||
nir_builder builder;
|
||||
nir_builder_init(&builder, impl);
|
||||
|
||||
nir_foreach_block(impl, lower_alu_to_scalar_block, &builder);
|
||||
}
|
||||
|
||||
void
|
||||
nir_lower_alu_to_scalar(nir_shader *shader)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
nir_lower_alu_to_scalar_impl(function->impl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright © 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "glsl/ir_uniform.h"
|
||||
#include "nir.h"
|
||||
#include "main/config.h"
|
||||
#include <assert.h>
|
||||
|
||||
typedef struct {
|
||||
const struct gl_shader_program *shader_program;
|
||||
nir_shader *shader;
|
||||
} lower_atomic_state;
|
||||
|
||||
/*
|
||||
* replace atomic counter intrinsics that use a variable with intrinsics
|
||||
* that directly store the buffer index and byte offset
|
||||
*/
|
||||
|
||||
static void
|
||||
lower_instr(nir_intrinsic_instr *instr,
|
||||
lower_atomic_state *state)
|
||||
{
|
||||
nir_intrinsic_op op;
|
||||
switch (instr->intrinsic) {
|
||||
case nir_intrinsic_atomic_counter_read_var:
|
||||
op = nir_intrinsic_atomic_counter_read;
|
||||
break;
|
||||
|
||||
case nir_intrinsic_atomic_counter_inc_var:
|
||||
op = nir_intrinsic_atomic_counter_inc;
|
||||
break;
|
||||
|
||||
case nir_intrinsic_atomic_counter_dec_var:
|
||||
op = nir_intrinsic_atomic_counter_dec;
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (instr->variables[0]->var->data.mode != nir_var_uniform &&
|
||||
instr->variables[0]->var->data.mode != nir_var_shader_storage)
|
||||
return; /* atomics passed as function arguments can't be lowered */
|
||||
|
||||
void *mem_ctx = ralloc_parent(instr);
|
||||
unsigned uniform_loc = instr->variables[0]->var->data.location;
|
||||
|
||||
nir_intrinsic_instr *new_instr = nir_intrinsic_instr_create(mem_ctx, op);
|
||||
new_instr->const_index[0] =
|
||||
state->shader_program->UniformStorage[uniform_loc].opaque[state->shader->stage].index;
|
||||
|
||||
nir_load_const_instr *offset_const = nir_load_const_instr_create(mem_ctx, 1);
|
||||
offset_const->value.u[0] = instr->variables[0]->var->data.offset;
|
||||
|
||||
nir_instr_insert_before(&instr->instr, &offset_const->instr);
|
||||
|
||||
nir_ssa_def *offset_def = &offset_const->def;
|
||||
|
||||
nir_deref *tail = &instr->variables[0]->deref;
|
||||
while (tail->child != NULL) {
|
||||
assert(tail->child->deref_type == nir_deref_type_array);
|
||||
nir_deref_array *deref_array = nir_deref_as_array(tail->child);
|
||||
tail = tail->child;
|
||||
|
||||
unsigned child_array_elements = tail->child != NULL ?
|
||||
glsl_get_aoa_size(tail->type) : 1;
|
||||
|
||||
offset_const->value.u[0] += deref_array->base_offset *
|
||||
child_array_elements * ATOMIC_COUNTER_SIZE;
|
||||
|
||||
if (deref_array->deref_array_type == nir_deref_array_type_indirect) {
|
||||
nir_load_const_instr *atomic_counter_size =
|
||||
nir_load_const_instr_create(mem_ctx, 1);
|
||||
atomic_counter_size->value.u[0] = child_array_elements * ATOMIC_COUNTER_SIZE;
|
||||
nir_instr_insert_before(&instr->instr, &atomic_counter_size->instr);
|
||||
|
||||
nir_alu_instr *mul = nir_alu_instr_create(mem_ctx, nir_op_imul);
|
||||
nir_ssa_dest_init(&mul->instr, &mul->dest.dest, 1, NULL);
|
||||
mul->dest.write_mask = 0x1;
|
||||
nir_src_copy(&mul->src[0].src, &deref_array->indirect, mul);
|
||||
mul->src[1].src.is_ssa = true;
|
||||
mul->src[1].src.ssa = &atomic_counter_size->def;
|
||||
nir_instr_insert_before(&instr->instr, &mul->instr);
|
||||
|
||||
nir_alu_instr *add = nir_alu_instr_create(mem_ctx, nir_op_iadd);
|
||||
nir_ssa_dest_init(&add->instr, &add->dest.dest, 1, NULL);
|
||||
add->dest.write_mask = 0x1;
|
||||
add->src[0].src.is_ssa = true;
|
||||
add->src[0].src.ssa = &mul->dest.dest.ssa;
|
||||
add->src[1].src.is_ssa = true;
|
||||
add->src[1].src.ssa = offset_def;
|
||||
nir_instr_insert_before(&instr->instr, &add->instr);
|
||||
|
||||
offset_def = &add->dest.dest.ssa;
|
||||
}
|
||||
}
|
||||
|
||||
new_instr->src[0].is_ssa = true;
|
||||
new_instr->src[0].ssa = offset_def;
|
||||
|
||||
if (instr->dest.is_ssa) {
|
||||
nir_ssa_dest_init(&new_instr->instr, &new_instr->dest,
|
||||
instr->dest.ssa.num_components, NULL);
|
||||
nir_ssa_def_rewrite_uses(&instr->dest.ssa,
|
||||
nir_src_for_ssa(&new_instr->dest.ssa));
|
||||
} else {
|
||||
nir_dest_copy(&new_instr->dest, &instr->dest, mem_ctx);
|
||||
}
|
||||
|
||||
nir_instr_insert_before(&instr->instr, &new_instr->instr);
|
||||
nir_instr_remove(&instr->instr);
|
||||
}
|
||||
|
||||
static bool
|
||||
lower_block(nir_block *block, void *state)
|
||||
{
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type == nir_instr_type_intrinsic)
|
||||
lower_instr(nir_instr_as_intrinsic(instr),
|
||||
(lower_atomic_state *) state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
nir_lower_atomics(nir_shader *shader,
|
||||
const struct gl_shader_program *shader_program)
|
||||
{
|
||||
lower_atomic_state state = {
|
||||
.shader = shader,
|
||||
.shader_program = shader_program,
|
||||
};
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl) {
|
||||
nir_foreach_block(function->impl, lower_block, (void *) &state);
|
||||
nir_metadata_preserve(function->impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
* Copyright © 2015 Red Hat
|
||||
*
|
||||
* 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:
|
||||
* Rob Clark <robclark@freedesktop.org>
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "nir_builder.h"
|
||||
|
||||
#define MAX_CLIP_PLANES 8
|
||||
|
||||
/* Generates the lowering code for user-clip-planes, generating CLIPDIST
|
||||
* from UCP[n] + CLIPVERTEX or POSITION. Additionally, an optional pass
|
||||
* for fragment shaders to insert conditional kill's based on the inter-
|
||||
* polated CLIPDIST
|
||||
*
|
||||
* NOTE: should be run after nir_lower_outputs_to_temporaries() (or at
|
||||
* least in scenarios where you can count on each output written once
|
||||
* and only once).
|
||||
*/
|
||||
|
||||
|
||||
static nir_variable *
|
||||
create_clipdist_var(nir_shader *shader, unsigned drvloc,
|
||||
bool output, gl_varying_slot slot)
|
||||
{
|
||||
nir_variable *var = rzalloc(shader, nir_variable);
|
||||
|
||||
var->data.driver_location = drvloc;
|
||||
var->type = glsl_vec4_type();
|
||||
var->data.mode = output ? nir_var_shader_out : nir_var_shader_in;
|
||||
var->name = ralloc_asprintf(var, "clipdist_%d", drvloc);
|
||||
var->data.index = 0;
|
||||
var->data.location = slot;
|
||||
|
||||
if (output) {
|
||||
exec_list_push_tail(&shader->outputs, &var->node);
|
||||
shader->num_outputs++; /* TODO use type_size() */
|
||||
}
|
||||
else {
|
||||
exec_list_push_tail(&shader->inputs, &var->node);
|
||||
shader->num_inputs++; /* TODO use type_size() */
|
||||
}
|
||||
return var;
|
||||
}
|
||||
|
||||
static void
|
||||
store_clipdist_output(nir_builder *b, nir_variable *out, nir_ssa_def **val)
|
||||
{
|
||||
nir_intrinsic_instr *store;
|
||||
|
||||
store = nir_intrinsic_instr_create(b->shader, nir_intrinsic_store_output);
|
||||
store->num_components = 4;
|
||||
store->const_index[0] = out->data.driver_location;
|
||||
store->const_index[1] = 0xf; /* wrmask */
|
||||
store->src[0].ssa = nir_vec4(b, val[0], val[1], val[2], val[3]);
|
||||
store->src[0].is_ssa = true;
|
||||
store->src[1] = nir_src_for_ssa(nir_imm_int(b, 0));
|
||||
nir_builder_instr_insert(b, &store->instr);
|
||||
}
|
||||
|
||||
static void
|
||||
load_clipdist_input(nir_builder *b, nir_variable *in, nir_ssa_def **val)
|
||||
{
|
||||
nir_intrinsic_instr *load;
|
||||
|
||||
load = nir_intrinsic_instr_create(b->shader, nir_intrinsic_load_input);
|
||||
load->num_components = 4;
|
||||
load->const_index[0] = in->data.driver_location;
|
||||
load->src[0] = nir_src_for_ssa(nir_imm_int(b, 0));
|
||||
nir_ssa_dest_init(&load->instr, &load->dest, 4, NULL);
|
||||
nir_builder_instr_insert(b, &load->instr);
|
||||
|
||||
val[0] = nir_channel(b, &load->dest.ssa, 0);
|
||||
val[1] = nir_channel(b, &load->dest.ssa, 1);
|
||||
val[2] = nir_channel(b, &load->dest.ssa, 2);
|
||||
val[3] = nir_channel(b, &load->dest.ssa, 3);
|
||||
}
|
||||
|
||||
struct find_output_state
|
||||
{
|
||||
unsigned drvloc;
|
||||
nir_ssa_def *def;
|
||||
};
|
||||
|
||||
static bool
|
||||
find_output_in_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct find_output_state *state = void_state;
|
||||
nir_foreach_instr(block, instr) {
|
||||
|
||||
if (instr->type == nir_instr_type_intrinsic) {
|
||||
nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr);
|
||||
if ((intr->intrinsic == nir_intrinsic_store_output) &&
|
||||
intr->const_index[0] == state->drvloc) {
|
||||
assert(state->def == NULL);
|
||||
assert(intr->src[0].is_ssa);
|
||||
assert(nir_src_as_const_value(intr->src[1]));
|
||||
state->def = intr->src[0].ssa;
|
||||
|
||||
#if !defined(DEBUG)
|
||||
/* for debug builds, scan entire shader to assert
|
||||
* if output is written multiple times. For release
|
||||
* builds just assume all is well and bail when we
|
||||
* find first:
|
||||
*/
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* TODO: maybe this would be a useful helper?
|
||||
* NOTE: assumes each output is written exactly once (and unconditionally)
|
||||
* so if needed nir_lower_outputs_to_temporaries()
|
||||
*/
|
||||
static nir_ssa_def *
|
||||
find_output(nir_shader *shader, unsigned drvloc)
|
||||
{
|
||||
struct find_output_state state = {
|
||||
.drvloc = drvloc,
|
||||
};
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl) {
|
||||
nir_foreach_block_reverse(function->impl,
|
||||
find_output_in_block, &state);
|
||||
}
|
||||
}
|
||||
|
||||
return state.def;
|
||||
}
|
||||
|
||||
/*
|
||||
* VS lowering
|
||||
*/
|
||||
|
||||
static void
|
||||
lower_clip_vs(nir_function_impl *impl, unsigned ucp_enables,
|
||||
nir_ssa_def *cv, nir_variable **out)
|
||||
{
|
||||
nir_ssa_def *clipdist[MAX_CLIP_PLANES];
|
||||
nir_builder b;
|
||||
|
||||
nir_builder_init(&b, impl);
|
||||
|
||||
/* NIR should ensure that, even in case of loops/if-else, there
|
||||
* should be only a single predecessor block to end_block, which
|
||||
* makes the perfect place to insert the clipdist calculations.
|
||||
*
|
||||
* NOTE: in case of early return's, these would have to be lowered
|
||||
* to jumps to end_block predecessor in a previous pass. Not sure
|
||||
* if there is a good way to sanity check this, but for now the
|
||||
* users of this pass don't support sub-routines.
|
||||
*/
|
||||
assert(impl->end_block->predecessors->entries == 1);
|
||||
b.cursor = nir_after_cf_list(&impl->body);
|
||||
|
||||
for (int plane = 0; plane < MAX_CLIP_PLANES; plane++) {
|
||||
if (ucp_enables & (1 << plane)) {
|
||||
nir_ssa_def *ucp =
|
||||
nir_load_system_value(&b, nir_intrinsic_load_user_clip_plane, plane);
|
||||
|
||||
/* calculate clipdist[plane] - dot(ucp, cv): */
|
||||
clipdist[plane] = nir_fdot4(&b, ucp, cv);
|
||||
}
|
||||
else {
|
||||
/* 0.0 == don't-clip == disabled: */
|
||||
clipdist[plane] = nir_imm_float(&b, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
if (ucp_enables & 0x0f)
|
||||
store_clipdist_output(&b, out[0], &clipdist[0]);
|
||||
if (ucp_enables & 0xf0)
|
||||
store_clipdist_output(&b, out[1], &clipdist[4]);
|
||||
|
||||
nir_metadata_preserve(impl, nir_metadata_dominance);
|
||||
}
|
||||
|
||||
/* ucp_enables is bitmask of enabled ucp's. Actual ucp values are
|
||||
* passed in to shader via user_clip_plane system-values
|
||||
*/
|
||||
void
|
||||
nir_lower_clip_vs(nir_shader *shader, unsigned ucp_enables)
|
||||
{
|
||||
int clipvertex = -1;
|
||||
int position = -1;
|
||||
int maxloc = -1;
|
||||
nir_ssa_def *cv;
|
||||
nir_variable *out[2] = { NULL };
|
||||
|
||||
if (!ucp_enables)
|
||||
return;
|
||||
|
||||
/* find clipvertex/position outputs: */
|
||||
nir_foreach_variable(var, &shader->outputs) {
|
||||
int loc = var->data.driver_location;
|
||||
|
||||
/* keep track of last used driver-location.. we'll be
|
||||
* appending CLIP_DIST0/CLIP_DIST1 after last existing
|
||||
* output:
|
||||
*/
|
||||
maxloc = MAX2(maxloc, loc);
|
||||
|
||||
switch (var->data.location) {
|
||||
case VARYING_SLOT_POS:
|
||||
position = loc;
|
||||
break;
|
||||
case VARYING_SLOT_CLIP_VERTEX:
|
||||
clipvertex = loc;
|
||||
break;
|
||||
case VARYING_SLOT_CLIP_DIST0:
|
||||
case VARYING_SLOT_CLIP_DIST1:
|
||||
/* if shader is already writing CLIPDIST, then
|
||||
* there should be no user-clip-planes to deal
|
||||
* with.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (clipvertex != -1)
|
||||
cv = find_output(shader, clipvertex);
|
||||
else if (position != -1)
|
||||
cv = find_output(shader, position);
|
||||
else
|
||||
return;
|
||||
|
||||
/* insert CLIPDIST outputs: */
|
||||
if (ucp_enables & 0x0f)
|
||||
out[0] =
|
||||
create_clipdist_var(shader, ++maxloc, true, VARYING_SLOT_CLIP_DIST0);
|
||||
if (ucp_enables & 0xf0)
|
||||
out[1] =
|
||||
create_clipdist_var(shader, ++maxloc, true, VARYING_SLOT_CLIP_DIST1);
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (!strcmp(function->name, "main"))
|
||||
lower_clip_vs(function->impl, ucp_enables, cv, out);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* FS lowering
|
||||
*/
|
||||
|
||||
static void
|
||||
lower_clip_fs(nir_function_impl *impl, unsigned ucp_enables,
|
||||
nir_variable **in)
|
||||
{
|
||||
nir_ssa_def *clipdist[MAX_CLIP_PLANES];
|
||||
nir_builder b;
|
||||
|
||||
nir_builder_init(&b, impl);
|
||||
b.cursor = nir_before_cf_list(&impl->body);
|
||||
|
||||
if (ucp_enables & 0x0f)
|
||||
load_clipdist_input(&b, in[0], &clipdist[0]);
|
||||
if (ucp_enables & 0xf0)
|
||||
load_clipdist_input(&b, in[1], &clipdist[4]);
|
||||
|
||||
for (int plane = 0; plane < MAX_CLIP_PLANES; plane++) {
|
||||
if (ucp_enables & (1 << plane)) {
|
||||
nir_intrinsic_instr *discard;
|
||||
nir_ssa_def *cond;
|
||||
|
||||
cond = nir_flt(&b, clipdist[plane], nir_imm_float(&b, 0.0));
|
||||
|
||||
discard = nir_intrinsic_instr_create(b.shader,
|
||||
nir_intrinsic_discard_if);
|
||||
discard->src[0] = nir_src_for_ssa(cond);
|
||||
nir_builder_instr_insert(&b, &discard->instr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* insert conditional kill based on interpolated CLIPDIST
|
||||
*/
|
||||
void
|
||||
nir_lower_clip_fs(nir_shader *shader, unsigned ucp_enables)
|
||||
{
|
||||
nir_variable *in[2];
|
||||
int maxloc = -1;
|
||||
|
||||
if (!ucp_enables)
|
||||
return;
|
||||
|
||||
nir_foreach_variable(var, &shader->inputs) {
|
||||
int loc = var->data.driver_location;
|
||||
|
||||
/* keep track of last used driver-location.. we'll be
|
||||
* appending CLIP_DIST0/CLIP_DIST1 after last existing
|
||||
* input:
|
||||
*/
|
||||
maxloc = MAX2(maxloc, loc);
|
||||
}
|
||||
|
||||
/* The shader won't normally have CLIPDIST inputs, so we
|
||||
* must add our own:
|
||||
*/
|
||||
/* insert CLIPDIST outputs: */
|
||||
if (ucp_enables & 0x0f)
|
||||
in[0] =
|
||||
create_clipdist_var(shader, ++maxloc, false,
|
||||
VARYING_SLOT_CLIP_DIST0);
|
||||
if (ucp_enables & 0xf0)
|
||||
in[1] =
|
||||
create_clipdist_var(shader, ++maxloc, false,
|
||||
VARYING_SLOT_CLIP_DIST1);
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (!strcmp(function->name, "main"))
|
||||
lower_clip_fs(function->impl, ucp_enables, in);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This lowering pass detects when a global variable is only being used by
|
||||
* one function and makes it local to that function
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
struct global_to_local_state {
|
||||
nir_function_impl *impl;
|
||||
/* A hash table keyed on variable pointers that stores the unique
|
||||
* nir_function_impl that uses the given variable. If a variable is
|
||||
* used in multiple functions, the data for the given key will be NULL.
|
||||
*/
|
||||
struct hash_table *var_func_table;
|
||||
};
|
||||
|
||||
static bool
|
||||
mark_global_var_uses_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct global_to_local_state *state = void_state;
|
||||
|
||||
nir_foreach_instr(block, instr) {
|
||||
if (instr->type != nir_instr_type_intrinsic)
|
||||
continue;
|
||||
|
||||
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
|
||||
unsigned num_vars = nir_intrinsic_infos[intrin->intrinsic].num_variables;
|
||||
|
||||
for (unsigned i = 0; i < num_vars; i++) {
|
||||
nir_variable *var = intrin->variables[i]->var;
|
||||
if (var->data.mode != nir_var_global)
|
||||
continue;
|
||||
|
||||
struct hash_entry *entry =
|
||||
_mesa_hash_table_search(state->var_func_table, var);
|
||||
|
||||
if (entry) {
|
||||
if (entry->data != state->impl)
|
||||
entry->data = NULL;
|
||||
} else {
|
||||
_mesa_hash_table_insert(state->var_func_table, var, state->impl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
nir_lower_global_vars_to_local(nir_shader *shader)
|
||||
{
|
||||
struct global_to_local_state state;
|
||||
bool progress = false;
|
||||
|
||||
state.var_func_table = _mesa_hash_table_create(NULL, _mesa_hash_pointer,
|
||||
_mesa_key_pointer_equal);
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl) {
|
||||
state.impl = function->impl;
|
||||
nir_foreach_block(function->impl, mark_global_var_uses_block, &state);
|
||||
}
|
||||
}
|
||||
|
||||
struct hash_entry *entry;
|
||||
hash_table_foreach(state.var_func_table, entry) {
|
||||
nir_variable *var = (void *)entry->key;
|
||||
nir_function_impl *impl = entry->data;
|
||||
|
||||
assert(var->data.mode == nir_var_global);
|
||||
|
||||
if (impl != NULL) {
|
||||
exec_node_remove(&var->node);
|
||||
var->data.mode = nir_var_local;
|
||||
exec_list_push_tail(&impl->locals, &var->node);
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance |
|
||||
nir_metadata_live_ssa_defs);
|
||||
progress = true;
|
||||
}
|
||||
}
|
||||
|
||||
_mesa_hash_table_destroy(state.var_func_table, NULL);
|
||||
|
||||
return progress;
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright © 2015 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.
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "nir_builder.h"
|
||||
|
||||
/**
|
||||
* \file nir_lower_gs_intrinsics.c
|
||||
*
|
||||
* Geometry Shaders can call EmitVertex()/EmitStreamVertex() to output an
|
||||
* arbitrary number of vertices. However, the shader must declare the maximum
|
||||
* number of vertices that it will ever output - further attempts to emit
|
||||
* vertices result in undefined behavior according to the GLSL specification.
|
||||
*
|
||||
* Drivers might use this maximum number of vertices to allocate enough space
|
||||
* to hold the geometry shader's output. Some drivers (such as i965) need to
|
||||
* implement "safety checks" which ensure that the shader hasn't emitted too
|
||||
* many vertices, to avoid overflowing that space and trashing other memory.
|
||||
*
|
||||
* The count of emitted vertices can also be useful in buffer offset
|
||||
* calculations, so drivers know where to write the GS output.
|
||||
*
|
||||
* However, for simple geometry shaders that emit a statically determinable
|
||||
* number of vertices, this extra bookkeeping is unnecessary and inefficient.
|
||||
* By tracking the vertex count in NIR, we allow constant folding/propagation
|
||||
* and dead control flow optimizations to eliminate most of it where possible.
|
||||
*
|
||||
* This pass introduces a new global variable which stores the current vertex
|
||||
* count (initialized to 0), and converts emit_vertex/end_primitive intrinsics
|
||||
* to their *_with_counter variants. emit_vertex is also wrapped in a safety
|
||||
* check to avoid buffer overflows. Finally, it adds a set_vertex_count
|
||||
* intrinsic at the end of the program, informing the driver of the final
|
||||
* vertex count.
|
||||
*/
|
||||
|
||||
struct state {
|
||||
nir_builder *builder;
|
||||
nir_variable *vertex_count_var;
|
||||
bool progress;
|
||||
};
|
||||
|
||||
/**
|
||||
* Replace emit_vertex intrinsics with:
|
||||
*
|
||||
* if (vertex_count < max_vertices) {
|
||||
* emit_vertex_with_counter vertex_count ...
|
||||
* vertex_count += 1
|
||||
* }
|
||||
*/
|
||||
static void
|
||||
rewrite_emit_vertex(nir_intrinsic_instr *intrin, struct state *state)
|
||||
{
|
||||
nir_builder *b = state->builder;
|
||||
|
||||
/* Load the vertex count */
|
||||
b->cursor = nir_before_instr(&intrin->instr);
|
||||
nir_ssa_def *count = nir_load_var(b, state->vertex_count_var);
|
||||
|
||||
nir_ssa_def *max_vertices = nir_imm_int(b, b->shader->info.gs.vertices_out);
|
||||
|
||||
/* Create: if (vertex_count < max_vertices) and insert it.
|
||||
*
|
||||
* The new if statement needs to be hooked up to the control flow graph
|
||||
* before we start inserting instructions into it.
|
||||
*/
|
||||
nir_if *if_stmt = nir_if_create(b->shader);
|
||||
if_stmt->condition = nir_src_for_ssa(nir_ilt(b, count, max_vertices));
|
||||
nir_builder_cf_insert(b, &if_stmt->cf_node);
|
||||
|
||||
/* Fill out the new then-block */
|
||||
b->cursor = nir_after_cf_list(&if_stmt->then_list);
|
||||
|
||||
nir_intrinsic_instr *lowered =
|
||||
nir_intrinsic_instr_create(b->shader,
|
||||
nir_intrinsic_emit_vertex_with_counter);
|
||||
lowered->const_index[0] = intrin->const_index[0];
|
||||
lowered->src[0] = nir_src_for_ssa(count);
|
||||
nir_builder_instr_insert(b, &lowered->instr);
|
||||
|
||||
/* Increment the vertex count by 1 */
|
||||
nir_store_var(b, state->vertex_count_var,
|
||||
nir_iadd(b, count, nir_imm_int(b, 1)),
|
||||
0x1); /* .x */
|
||||
|
||||
nir_instr_remove(&intrin->instr);
|
||||
|
||||
state->progress = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace end_primitive with end_primitive_with_counter.
|
||||
*/
|
||||
static void
|
||||
rewrite_end_primitive(nir_intrinsic_instr *intrin, struct state *state)
|
||||
{
|
||||
nir_builder *b = state->builder;
|
||||
|
||||
b->cursor = nir_before_instr(&intrin->instr);
|
||||
nir_ssa_def *count = nir_load_var(b, state->vertex_count_var);
|
||||
|
||||
nir_intrinsic_instr *lowered =
|
||||
nir_intrinsic_instr_create(b->shader,
|
||||
nir_intrinsic_end_primitive_with_counter);
|
||||
lowered->const_index[0] = intrin->const_index[0];
|
||||
lowered->src[0] = nir_src_for_ssa(count);
|
||||
nir_builder_instr_insert(b, &lowered->instr);
|
||||
|
||||
nir_instr_remove(&intrin->instr);
|
||||
|
||||
state->progress = true;
|
||||
}
|
||||
|
||||
static bool
|
||||
rewrite_intrinsics(nir_block *block, void *closure)
|
||||
{
|
||||
struct state *state = closure;
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type != nir_instr_type_intrinsic)
|
||||
continue;
|
||||
|
||||
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
|
||||
switch (intrin->intrinsic) {
|
||||
case nir_intrinsic_emit_vertex:
|
||||
rewrite_emit_vertex(intrin, state);
|
||||
break;
|
||||
case nir_intrinsic_end_primitive:
|
||||
rewrite_end_primitive(intrin, state);
|
||||
break;
|
||||
default:
|
||||
/* not interesting; skip this */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a set_vertex_count intrinsic at the end of the program
|
||||
* (representing the final vertex count).
|
||||
*/
|
||||
static void
|
||||
append_set_vertex_count(nir_block *end_block, struct state *state)
|
||||
{
|
||||
nir_builder *b = state->builder;
|
||||
nir_shader *shader = state->builder->shader;
|
||||
|
||||
/* Insert the new intrinsic in all of the predecessors of the end block,
|
||||
* but before any jump instructions (return).
|
||||
*/
|
||||
struct set_entry *entry;
|
||||
set_foreach(end_block->predecessors, entry) {
|
||||
nir_block *pred = (nir_block *) entry->key;
|
||||
b->cursor = nir_after_block_before_jump(pred);
|
||||
|
||||
nir_ssa_def *count = nir_load_var(b, state->vertex_count_var);
|
||||
|
||||
nir_intrinsic_instr *set_vertex_count =
|
||||
nir_intrinsic_instr_create(shader, nir_intrinsic_set_vertex_count);
|
||||
set_vertex_count->src[0] = nir_src_for_ssa(count);
|
||||
|
||||
nir_builder_instr_insert(b, &set_vertex_count->instr);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
nir_lower_gs_intrinsics(nir_shader *shader)
|
||||
{
|
||||
struct state state;
|
||||
state.progress = false;
|
||||
|
||||
/* Create the counter variable */
|
||||
nir_variable *var = rzalloc(shader, nir_variable);
|
||||
var->data.mode = nir_var_global;
|
||||
var->type = glsl_uint_type();
|
||||
var->name = "vertex_count";
|
||||
var->constant_initializer = rzalloc(shader, nir_constant); /* initialize to 0 */
|
||||
|
||||
exec_list_push_tail(&shader->globals, &var->node);
|
||||
state.vertex_count_var = var;
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl) {
|
||||
nir_builder b;
|
||||
nir_builder_init(&b, function->impl);
|
||||
state.builder = &b;
|
||||
|
||||
nir_foreach_block(function->impl, rewrite_intrinsics, &state);
|
||||
|
||||
/* This only works because we have a single main() function. */
|
||||
append_set_vertex_count(function->impl->end_block, &state);
|
||||
|
||||
nir_metadata_preserve(function->impl, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return state.progress;
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright © 2015 Red Hat
|
||||
*
|
||||
* 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:
|
||||
* Rob Clark <robclark@freedesktop.org>
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "nir_builder.h"
|
||||
|
||||
/* Lowers idiv/udiv/umod
|
||||
* Based on NV50LegalizeSSA::handleDIV()
|
||||
*
|
||||
* Note that this is probably not enough precision for compute shaders.
|
||||
* Perhaps we want a second higher precision (looping) version of this?
|
||||
* Or perhaps we assume if you can do compute shaders you can also
|
||||
* branch out to a pre-optimized shader library routine..
|
||||
*/
|
||||
|
||||
static void
|
||||
convert_instr(nir_builder *bld, nir_alu_instr *alu)
|
||||
{
|
||||
nir_ssa_def *numer, *denom, *af, *bf, *a, *b, *q, *r;
|
||||
nir_op op = alu->op;
|
||||
bool is_signed;
|
||||
|
||||
if ((op != nir_op_idiv) &&
|
||||
(op != nir_op_udiv) &&
|
||||
(op != nir_op_umod))
|
||||
return;
|
||||
|
||||
is_signed = (op == nir_op_idiv);
|
||||
|
||||
bld->cursor = nir_before_instr(&alu->instr);
|
||||
|
||||
numer = nir_ssa_for_alu_src(bld, alu, 0);
|
||||
denom = nir_ssa_for_alu_src(bld, alu, 1);
|
||||
|
||||
if (is_signed) {
|
||||
af = nir_i2f(bld, numer);
|
||||
bf = nir_i2f(bld, denom);
|
||||
af = nir_fabs(bld, af);
|
||||
bf = nir_fabs(bld, bf);
|
||||
a = nir_iabs(bld, numer);
|
||||
b = nir_iabs(bld, denom);
|
||||
} else {
|
||||
af = nir_u2f(bld, numer);
|
||||
bf = nir_u2f(bld, denom);
|
||||
a = numer;
|
||||
b = denom;
|
||||
}
|
||||
|
||||
/* get first result: */
|
||||
bf = nir_frcp(bld, bf);
|
||||
bf = nir_isub(bld, bf, nir_imm_int(bld, 2)); /* yes, really */
|
||||
q = nir_fmul(bld, af, bf);
|
||||
|
||||
if (is_signed) {
|
||||
q = nir_f2i(bld, q);
|
||||
} else {
|
||||
q = nir_f2u(bld, q);
|
||||
}
|
||||
|
||||
/* get error of first result: */
|
||||
r = nir_imul(bld, q, b);
|
||||
r = nir_isub(bld, a, r);
|
||||
r = nir_u2f(bld, r);
|
||||
r = nir_fmul(bld, r, bf);
|
||||
r = nir_f2u(bld, r);
|
||||
|
||||
/* add quotients: */
|
||||
q = nir_iadd(bld, q, r);
|
||||
|
||||
/* correction: if modulus >= divisor, add 1 */
|
||||
r = nir_imul(bld, q, b);
|
||||
r = nir_isub(bld, a, r);
|
||||
|
||||
r = nir_uge(bld, r, b);
|
||||
r = nir_b2i(bld, r);
|
||||
|
||||
q = nir_iadd(bld, q, r);
|
||||
if (is_signed) {
|
||||
/* fix the sign: */
|
||||
r = nir_ixor(bld, numer, denom);
|
||||
r = nir_ushr(bld, r, nir_imm_int(bld, 31));
|
||||
r = nir_i2b(bld, r);
|
||||
b = nir_ineg(bld, q);
|
||||
q = nir_bcsel(bld, r, b, q);
|
||||
}
|
||||
|
||||
if (op == nir_op_umod) {
|
||||
/* division result in q */
|
||||
r = nir_imul(bld, q, b);
|
||||
q = nir_isub(bld, a, r);
|
||||
}
|
||||
|
||||
assert(alu->dest.dest.is_ssa);
|
||||
nir_ssa_def_rewrite_uses(&alu->dest.dest.ssa, nir_src_for_ssa(q));
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_block(nir_block *block, void *state)
|
||||
{
|
||||
nir_builder *b = state;
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type == nir_instr_type_alu)
|
||||
convert_instr(b, nir_instr_as_alu(instr));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
convert_impl(nir_function_impl *impl)
|
||||
{
|
||||
nir_builder b;
|
||||
nir_builder_init(&b, impl);
|
||||
|
||||
nir_foreach_block(impl, convert_block, &b);
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
}
|
||||
|
||||
void
|
||||
nir_lower_idiv(nir_shader *shader)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
convert_impl(function->impl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
/*
|
||||
* Copyright © 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
* Jason Ekstrand (jason@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This lowering pass converts references to input/output variables with
|
||||
* loads/stores to actual input/output intrinsics.
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "nir_builder.h"
|
||||
|
||||
struct lower_io_state {
|
||||
nir_builder builder;
|
||||
void *mem_ctx;
|
||||
int (*type_size)(const struct glsl_type *type);
|
||||
nir_variable_mode mode;
|
||||
};
|
||||
|
||||
void
|
||||
nir_assign_var_locations(struct exec_list *var_list, unsigned *size,
|
||||
int (*type_size)(const struct glsl_type *))
|
||||
{
|
||||
unsigned location = 0;
|
||||
|
||||
nir_foreach_variable(var, var_list) {
|
||||
/*
|
||||
* UBO's have their own address spaces, so don't count them towards the
|
||||
* number of global uniforms
|
||||
*/
|
||||
if ((var->data.mode == nir_var_uniform || var->data.mode == nir_var_shader_storage) &&
|
||||
var->interface_type != NULL)
|
||||
continue;
|
||||
|
||||
var->data.driver_location = location;
|
||||
location += type_size(var->type);
|
||||
}
|
||||
|
||||
*size = location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we're processing a stage whose inputs are arrays indexed
|
||||
* by a vertex number (such as geometry shader inputs).
|
||||
*/
|
||||
static bool
|
||||
is_per_vertex_input(struct lower_io_state *state, nir_variable *var)
|
||||
{
|
||||
gl_shader_stage stage = state->builder.shader->stage;
|
||||
|
||||
return var->data.mode == nir_var_shader_in && !var->data.patch &&
|
||||
(stage == MESA_SHADER_TESS_CTRL ||
|
||||
stage == MESA_SHADER_TESS_EVAL ||
|
||||
stage == MESA_SHADER_GEOMETRY);
|
||||
}
|
||||
|
||||
static bool
|
||||
is_per_vertex_output(struct lower_io_state *state, nir_variable *var)
|
||||
{
|
||||
gl_shader_stage stage = state->builder.shader->stage;
|
||||
return var->data.mode == nir_var_shader_out && !var->data.patch &&
|
||||
stage == MESA_SHADER_TESS_CTRL;
|
||||
}
|
||||
|
||||
static nir_ssa_def *
|
||||
get_io_offset(nir_builder *b, nir_deref_var *deref,
|
||||
nir_ssa_def **vertex_index,
|
||||
int (*type_size)(const struct glsl_type *))
|
||||
{
|
||||
nir_deref *tail = &deref->deref;
|
||||
|
||||
/* For per-vertex input arrays (i.e. geometry shader inputs), keep the
|
||||
* outermost array index separate. Process the rest normally.
|
||||
*/
|
||||
if (vertex_index != NULL) {
|
||||
tail = tail->child;
|
||||
assert(tail->deref_type == nir_deref_type_array);
|
||||
nir_deref_array *deref_array = nir_deref_as_array(tail);
|
||||
|
||||
nir_ssa_def *vtx = nir_imm_int(b, deref_array->base_offset);
|
||||
if (deref_array->deref_array_type == nir_deref_array_type_indirect) {
|
||||
vtx = nir_iadd(b, vtx, nir_ssa_for_src(b, deref_array->indirect, 1));
|
||||
}
|
||||
*vertex_index = vtx;
|
||||
}
|
||||
|
||||
/* Just emit code and let constant-folding go to town */
|
||||
nir_ssa_def *offset = nir_imm_int(b, 0);
|
||||
|
||||
while (tail->child != NULL) {
|
||||
const struct glsl_type *parent_type = tail->type;
|
||||
tail = tail->child;
|
||||
|
||||
if (tail->deref_type == nir_deref_type_array) {
|
||||
nir_deref_array *deref_array = nir_deref_as_array(tail);
|
||||
unsigned size = type_size(tail->type);
|
||||
|
||||
offset = nir_iadd(b, offset,
|
||||
nir_imm_int(b, size * deref_array->base_offset));
|
||||
|
||||
if (deref_array->deref_array_type == nir_deref_array_type_indirect) {
|
||||
nir_ssa_def *mul =
|
||||
nir_imul(b, nir_imm_int(b, size),
|
||||
nir_ssa_for_src(b, deref_array->indirect, 1));
|
||||
|
||||
offset = nir_iadd(b, offset, mul);
|
||||
}
|
||||
} else if (tail->deref_type == nir_deref_type_struct) {
|
||||
nir_deref_struct *deref_struct = nir_deref_as_struct(tail);
|
||||
|
||||
unsigned field_offset = 0;
|
||||
for (unsigned i = 0; i < deref_struct->index; i++) {
|
||||
field_offset += type_size(glsl_get_struct_field(parent_type, i));
|
||||
}
|
||||
offset = nir_iadd(b, offset, nir_imm_int(b, field_offset));
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static nir_intrinsic_op
|
||||
load_op(struct lower_io_state *state,
|
||||
nir_variable_mode mode, bool per_vertex)
|
||||
{
|
||||
nir_intrinsic_op op;
|
||||
switch (mode) {
|
||||
case nir_var_shader_in:
|
||||
op = per_vertex ? nir_intrinsic_load_per_vertex_input :
|
||||
nir_intrinsic_load_input;
|
||||
break;
|
||||
case nir_var_shader_out:
|
||||
op = per_vertex ? nir_intrinsic_load_per_vertex_output :
|
||||
nir_intrinsic_load_output;
|
||||
break;
|
||||
case nir_var_uniform:
|
||||
op = nir_intrinsic_load_uniform;
|
||||
break;
|
||||
default:
|
||||
unreachable("Unknown variable mode");
|
||||
}
|
||||
return op;
|
||||
}
|
||||
|
||||
static bool
|
||||
nir_lower_io_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct lower_io_state *state = void_state;
|
||||
|
||||
nir_builder *b = &state->builder;
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type != nir_instr_type_intrinsic)
|
||||
continue;
|
||||
|
||||
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
|
||||
|
||||
if (intrin->intrinsic != nir_intrinsic_load_var &&
|
||||
intrin->intrinsic != nir_intrinsic_store_var)
|
||||
continue;
|
||||
|
||||
nir_variable_mode mode = intrin->variables[0]->var->data.mode;
|
||||
|
||||
if (state->mode != nir_var_all && state->mode != mode)
|
||||
continue;
|
||||
|
||||
if (mode != nir_var_shader_in &&
|
||||
mode != nir_var_shader_out &&
|
||||
mode != nir_var_uniform)
|
||||
continue;
|
||||
|
||||
b->cursor = nir_before_instr(instr);
|
||||
|
||||
switch (intrin->intrinsic) {
|
||||
case nir_intrinsic_load_var: {
|
||||
bool per_vertex =
|
||||
is_per_vertex_input(state, intrin->variables[0]->var) ||
|
||||
is_per_vertex_output(state, intrin->variables[0]->var);
|
||||
|
||||
nir_ssa_def *offset;
|
||||
nir_ssa_def *vertex_index;
|
||||
|
||||
offset = get_io_offset(b, intrin->variables[0],
|
||||
per_vertex ? &vertex_index : NULL,
|
||||
state->type_size);
|
||||
|
||||
nir_intrinsic_instr *load =
|
||||
nir_intrinsic_instr_create(state->mem_ctx,
|
||||
load_op(state, mode, per_vertex));
|
||||
load->num_components = intrin->num_components;
|
||||
|
||||
load->const_index[0] =
|
||||
intrin->variables[0]->var->data.driver_location;
|
||||
|
||||
if (per_vertex)
|
||||
load->src[0] = nir_src_for_ssa(vertex_index);
|
||||
|
||||
load->src[per_vertex ? 1 : 0] = nir_src_for_ssa(offset);
|
||||
|
||||
if (intrin->dest.is_ssa) {
|
||||
nir_ssa_dest_init(&load->instr, &load->dest,
|
||||
intrin->num_components, NULL);
|
||||
nir_ssa_def_rewrite_uses(&intrin->dest.ssa,
|
||||
nir_src_for_ssa(&load->dest.ssa));
|
||||
} else {
|
||||
nir_dest_copy(&load->dest, &intrin->dest, state->mem_ctx);
|
||||
}
|
||||
|
||||
nir_instr_insert_before(&intrin->instr, &load->instr);
|
||||
nir_instr_remove(&intrin->instr);
|
||||
break;
|
||||
}
|
||||
|
||||
case nir_intrinsic_store_var: {
|
||||
assert(mode == nir_var_shader_out);
|
||||
|
||||
nir_ssa_def *offset;
|
||||
nir_ssa_def *vertex_index;
|
||||
|
||||
bool per_vertex =
|
||||
is_per_vertex_output(state, intrin->variables[0]->var);
|
||||
|
||||
offset = get_io_offset(b, intrin->variables[0],
|
||||
per_vertex ? &vertex_index : NULL,
|
||||
state->type_size);
|
||||
|
||||
nir_intrinsic_op store_op =
|
||||
per_vertex ? nir_intrinsic_store_per_vertex_output :
|
||||
nir_intrinsic_store_output;
|
||||
|
||||
nir_intrinsic_instr *store = nir_intrinsic_instr_create(state->mem_ctx,
|
||||
store_op);
|
||||
store->num_components = intrin->num_components;
|
||||
|
||||
nir_src_copy(&store->src[0], &intrin->src[0], store);
|
||||
|
||||
store->const_index[0] =
|
||||
intrin->variables[0]->var->data.driver_location;
|
||||
|
||||
/* Copy the writemask */
|
||||
store->const_index[1] = intrin->const_index[0];
|
||||
|
||||
if (per_vertex)
|
||||
store->src[1] = nir_src_for_ssa(vertex_index);
|
||||
|
||||
store->src[per_vertex ? 2 : 1] = nir_src_for_ssa(offset);
|
||||
|
||||
nir_instr_insert_before(&intrin->instr, &store->instr);
|
||||
nir_instr_remove(&intrin->instr);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
nir_lower_io_impl(nir_function_impl *impl,
|
||||
nir_variable_mode mode,
|
||||
int (*type_size)(const struct glsl_type *))
|
||||
{
|
||||
struct lower_io_state state;
|
||||
|
||||
nir_builder_init(&state.builder, impl);
|
||||
state.mem_ctx = ralloc_parent(impl);
|
||||
state.mode = mode;
|
||||
state.type_size = type_size;
|
||||
|
||||
nir_foreach_block(impl, nir_lower_io_block, &state);
|
||||
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
}
|
||||
|
||||
void
|
||||
nir_lower_io(nir_shader *shader, nir_variable_mode mode,
|
||||
int (*type_size)(const struct glsl_type *))
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
nir_lower_io_impl(function->impl, mode, type_size);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the offset soruce for a load/store intrinsic.
|
||||
*/
|
||||
nir_src *
|
||||
nir_get_io_offset_src(nir_intrinsic_instr *instr)
|
||||
{
|
||||
switch (instr->intrinsic) {
|
||||
case nir_intrinsic_load_input:
|
||||
case nir_intrinsic_load_output:
|
||||
case nir_intrinsic_load_uniform:
|
||||
return &instr->src[0];
|
||||
case nir_intrinsic_load_per_vertex_input:
|
||||
case nir_intrinsic_load_per_vertex_output:
|
||||
case nir_intrinsic_store_output:
|
||||
return &instr->src[1];
|
||||
case nir_intrinsic_store_per_vertex_output:
|
||||
return &instr->src[2];
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the vertex index source for a load/store per_vertex intrinsic.
|
||||
*/
|
||||
nir_src *
|
||||
nir_get_io_vertex_index_src(nir_intrinsic_instr *instr)
|
||||
{
|
||||
switch (instr->intrinsic) {
|
||||
case nir_intrinsic_load_per_vertex_input:
|
||||
case nir_intrinsic_load_per_vertex_output:
|
||||
return &instr->src[0];
|
||||
case nir_intrinsic_store_per_vertex_output:
|
||||
return &instr->src[1];
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright © 2015 Broadcom
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "util/macros.h"
|
||||
#include "nir.h"
|
||||
#include "nir_builder.h"
|
||||
|
||||
/** @file nir_lower_load_const_to_scalar.c
|
||||
*
|
||||
* Replaces vector nir_load_const instructions with a series of loads and a
|
||||
* vec[234] to reconstruct the original vector (on the assumption that
|
||||
* nir_lower_alu_to_scalar() will then be used to split it up).
|
||||
*
|
||||
* This gives NIR a chance to CSE more operations on a scalar shader, when the
|
||||
* same value was used in different vector contant loads.
|
||||
*/
|
||||
|
||||
static void
|
||||
lower_load_const_instr_scalar(nir_load_const_instr *lower)
|
||||
{
|
||||
if (lower->def.num_components == 1)
|
||||
return;
|
||||
|
||||
nir_builder b;
|
||||
nir_builder_init(&b, nir_cf_node_get_function(&lower->instr.block->cf_node));
|
||||
b.cursor = nir_before_instr(&lower->instr);
|
||||
|
||||
/* Emit the individual loads. */
|
||||
nir_ssa_def *loads[4];
|
||||
for (unsigned i = 0; i < lower->def.num_components; i++) {
|
||||
nir_load_const_instr *load_comp = nir_load_const_instr_create(b.shader, 1);
|
||||
load_comp->value.u[0] = lower->value.u[i];
|
||||
nir_builder_instr_insert(&b, &load_comp->instr);
|
||||
loads[i] = &load_comp->def;
|
||||
}
|
||||
|
||||
/* Batch things back together into a vector. */
|
||||
nir_ssa_def *vec = nir_vec(&b, loads, lower->def.num_components);
|
||||
|
||||
/* Replace the old load with a reference to our reconstructed vector. */
|
||||
nir_ssa_def_rewrite_uses(&lower->def, nir_src_for_ssa(vec));
|
||||
nir_instr_remove(&lower->instr);
|
||||
}
|
||||
|
||||
static bool
|
||||
lower_load_const_to_scalar_block(nir_block *block, void *data)
|
||||
{
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type == nir_instr_type_load_const)
|
||||
lower_load_const_instr_scalar(nir_instr_as_load_const(instr));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
nir_lower_load_const_to_scalar_impl(nir_function_impl *impl)
|
||||
{
|
||||
nir_foreach_block(impl, lower_load_const_to_scalar_block, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
nir_lower_load_const_to_scalar(nir_shader *shader)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
nir_lower_load_const_to_scalar_impl(function->impl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "nir_array.h"
|
||||
|
||||
struct locals_to_regs_state {
|
||||
nir_shader *shader;
|
||||
nir_function_impl *impl;
|
||||
|
||||
/* A hash table mapping derefs to registers */
|
||||
struct hash_table *regs_table;
|
||||
|
||||
/* A growing array of derefs that we have encountered. There is exactly
|
||||
* one element of this array per element in the hash table. This is
|
||||
* used to make adding register initialization code deterministic.
|
||||
*/
|
||||
nir_array derefs_array;
|
||||
|
||||
bool progress;
|
||||
};
|
||||
|
||||
/* The following two functions implement a hash and equality check for
|
||||
* variable dreferences. When the hash or equality function encounters an
|
||||
* array, it ignores the offset and whether it is direct or indirect
|
||||
* entirely.
|
||||
*/
|
||||
static uint32_t
|
||||
hash_deref(const void *void_deref)
|
||||
{
|
||||
uint32_t hash = _mesa_fnv32_1a_offset_bias;
|
||||
|
||||
const nir_deref_var *deref_var = void_deref;
|
||||
hash = _mesa_fnv32_1a_accumulate(hash, deref_var->var);
|
||||
|
||||
for (const nir_deref *deref = deref_var->deref.child;
|
||||
deref; deref = deref->child) {
|
||||
if (deref->deref_type == nir_deref_type_struct) {
|
||||
const nir_deref_struct *deref_struct = nir_deref_as_struct(deref);
|
||||
hash = _mesa_fnv32_1a_accumulate(hash, deref_struct->index);
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static bool
|
||||
derefs_equal(const void *void_a, const void *void_b)
|
||||
{
|
||||
const nir_deref_var *a_var = void_a;
|
||||
const nir_deref_var *b_var = void_b;
|
||||
|
||||
if (a_var->var != b_var->var)
|
||||
return false;
|
||||
|
||||
for (const nir_deref *a = a_var->deref.child, *b = b_var->deref.child;
|
||||
a != NULL; a = a->child, b = b->child) {
|
||||
if (a->deref_type != b->deref_type)
|
||||
return false;
|
||||
|
||||
if (a->deref_type == nir_deref_type_struct) {
|
||||
if (nir_deref_as_struct(a)->index != nir_deref_as_struct(b)->index)
|
||||
return false;
|
||||
}
|
||||
/* Do nothing for arrays. They're all the same. */
|
||||
|
||||
assert((a->child == NULL) == (b->child == NULL));
|
||||
if((a->child == NULL) != (b->child == NULL))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static nir_register *
|
||||
get_reg_for_deref(nir_deref_var *deref, struct locals_to_regs_state *state)
|
||||
{
|
||||
uint32_t hash = hash_deref(deref);
|
||||
|
||||
struct hash_entry *entry =
|
||||
_mesa_hash_table_search_pre_hashed(state->regs_table, hash, deref);
|
||||
if (entry)
|
||||
return entry->data;
|
||||
|
||||
unsigned array_size = 1;
|
||||
nir_deref *tail = &deref->deref;
|
||||
while (tail->child) {
|
||||
if (tail->child->deref_type == nir_deref_type_array)
|
||||
array_size *= glsl_get_length(tail->type);
|
||||
tail = tail->child;
|
||||
}
|
||||
|
||||
assert(glsl_type_is_vector(tail->type) || glsl_type_is_scalar(tail->type));
|
||||
|
||||
nir_register *reg = nir_local_reg_create(state->impl);
|
||||
reg->num_components = glsl_get_vector_elements(tail->type);
|
||||
reg->num_array_elems = array_size > 1 ? array_size : 0;
|
||||
|
||||
_mesa_hash_table_insert_pre_hashed(state->regs_table, hash, deref, reg);
|
||||
nir_array_add(&state->derefs_array, nir_deref_var *, deref);
|
||||
|
||||
return reg;
|
||||
}
|
||||
|
||||
static nir_src
|
||||
get_deref_reg_src(nir_deref_var *deref, nir_instr *instr,
|
||||
struct locals_to_regs_state *state)
|
||||
{
|
||||
nir_src src;
|
||||
|
||||
src.is_ssa = false;
|
||||
src.reg.reg = get_reg_for_deref(deref, state);
|
||||
src.reg.base_offset = 0;
|
||||
src.reg.indirect = NULL;
|
||||
|
||||
/* It is possible for a user to create a shader that has an array with a
|
||||
* single element and then proceed to access it indirectly. Indirectly
|
||||
* accessing a non-array register is not allowed in NIR. In order to
|
||||
* handle this case we just convert it to a direct reference.
|
||||
*/
|
||||
if (src.reg.reg->num_array_elems == 0)
|
||||
return src;
|
||||
|
||||
nir_deref *tail = &deref->deref;
|
||||
while (tail->child != NULL) {
|
||||
const struct glsl_type *parent_type = tail->type;
|
||||
tail = tail->child;
|
||||
|
||||
if (tail->deref_type != nir_deref_type_array)
|
||||
continue;
|
||||
|
||||
nir_deref_array *deref_array = nir_deref_as_array(tail);
|
||||
|
||||
src.reg.base_offset *= glsl_get_length(parent_type);
|
||||
src.reg.base_offset += deref_array->base_offset;
|
||||
|
||||
if (src.reg.indirect) {
|
||||
nir_load_const_instr *load_const =
|
||||
nir_load_const_instr_create(state->shader, 1);
|
||||
load_const->value.u[0] = glsl_get_length(parent_type);
|
||||
nir_instr_insert_before(instr, &load_const->instr);
|
||||
|
||||
nir_alu_instr *mul = nir_alu_instr_create(state->shader, nir_op_imul);
|
||||
mul->src[0].src = *src.reg.indirect;
|
||||
mul->src[1].src.is_ssa = true;
|
||||
mul->src[1].src.ssa = &load_const->def;
|
||||
mul->dest.write_mask = 1;
|
||||
nir_ssa_dest_init(&mul->instr, &mul->dest.dest, 1, NULL);
|
||||
nir_instr_insert_before(instr, &mul->instr);
|
||||
|
||||
src.reg.indirect->is_ssa = true;
|
||||
src.reg.indirect->ssa = &mul->dest.dest.ssa;
|
||||
}
|
||||
|
||||
if (deref_array->deref_array_type == nir_deref_array_type_indirect) {
|
||||
if (src.reg.indirect == NULL) {
|
||||
src.reg.indirect = ralloc(state->shader, nir_src);
|
||||
nir_src_copy(src.reg.indirect, &deref_array->indirect,
|
||||
state->shader);
|
||||
} else {
|
||||
nir_alu_instr *add = nir_alu_instr_create(state->shader,
|
||||
nir_op_iadd);
|
||||
add->src[0].src = *src.reg.indirect;
|
||||
nir_src_copy(&add->src[1].src, &deref_array->indirect, add);
|
||||
add->dest.write_mask = 1;
|
||||
nir_ssa_dest_init(&add->instr, &add->dest.dest, 1, NULL);
|
||||
nir_instr_insert_before(instr, &add->instr);
|
||||
|
||||
src.reg.indirect->is_ssa = true;
|
||||
src.reg.indirect->ssa = &add->dest.dest.ssa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
static bool
|
||||
lower_locals_to_regs_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct locals_to_regs_state *state = void_state;
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type != nir_instr_type_intrinsic)
|
||||
continue;
|
||||
|
||||
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
|
||||
|
||||
switch (intrin->intrinsic) {
|
||||
case nir_intrinsic_load_var: {
|
||||
if (intrin->variables[0]->var->data.mode != nir_var_local)
|
||||
continue;
|
||||
|
||||
nir_alu_instr *mov = nir_alu_instr_create(state->shader, nir_op_imov);
|
||||
mov->src[0].src = get_deref_reg_src(intrin->variables[0],
|
||||
&intrin->instr, state);
|
||||
mov->dest.write_mask = (1 << intrin->num_components) - 1;
|
||||
if (intrin->dest.is_ssa) {
|
||||
nir_ssa_dest_init(&mov->instr, &mov->dest.dest,
|
||||
intrin->num_components, NULL);
|
||||
nir_ssa_def_rewrite_uses(&intrin->dest.ssa,
|
||||
nir_src_for_ssa(&mov->dest.dest.ssa));
|
||||
} else {
|
||||
nir_dest_copy(&mov->dest.dest, &intrin->dest, &mov->instr);
|
||||
}
|
||||
nir_instr_insert_before(&intrin->instr, &mov->instr);
|
||||
|
||||
nir_instr_remove(&intrin->instr);
|
||||
state->progress = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case nir_intrinsic_store_var: {
|
||||
if (intrin->variables[0]->var->data.mode != nir_var_local)
|
||||
continue;
|
||||
|
||||
nir_src reg_src = get_deref_reg_src(intrin->variables[0],
|
||||
&intrin->instr, state);
|
||||
|
||||
nir_alu_instr *mov = nir_alu_instr_create(state->shader, nir_op_imov);
|
||||
nir_src_copy(&mov->src[0].src, &intrin->src[0], mov);
|
||||
mov->dest.write_mask = intrin->const_index[0];
|
||||
mov->dest.dest.is_ssa = false;
|
||||
mov->dest.dest.reg.reg = reg_src.reg.reg;
|
||||
mov->dest.dest.reg.base_offset = reg_src.reg.base_offset;
|
||||
mov->dest.dest.reg.indirect = reg_src.reg.indirect;
|
||||
|
||||
nir_instr_insert_before(&intrin->instr, &mov->instr);
|
||||
|
||||
nir_instr_remove(&intrin->instr);
|
||||
state->progress = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case nir_intrinsic_copy_var:
|
||||
unreachable("There should be no copies whatsoever at this point");
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static nir_block *
|
||||
compute_reg_usedef_lca(nir_register *reg)
|
||||
{
|
||||
nir_block *lca = NULL;
|
||||
|
||||
list_for_each_entry(nir_dest, def_dest, ®->defs, reg.def_link)
|
||||
lca = nir_dominance_lca(lca, def_dest->reg.parent_instr->block);
|
||||
|
||||
list_for_each_entry(nir_src, use_src, ®->uses, use_link)
|
||||
lca = nir_dominance_lca(lca, use_src->parent_instr->block);
|
||||
|
||||
list_for_each_entry(nir_src, use_src, ®->if_uses, use_link) {
|
||||
nir_cf_node *prev_node = nir_cf_node_prev(&use_src->parent_if->cf_node);
|
||||
assert(prev_node->type == nir_cf_node_block);
|
||||
lca = nir_dominance_lca(lca, nir_cf_node_as_block(prev_node));
|
||||
}
|
||||
|
||||
return lca;
|
||||
}
|
||||
|
||||
static void
|
||||
insert_constant_initializer(nir_deref_var *deref_head, nir_deref *deref_tail,
|
||||
nir_block *block,
|
||||
struct locals_to_regs_state *state)
|
||||
{
|
||||
if (deref_tail->child) {
|
||||
switch (deref_tail->child->deref_type) {
|
||||
case nir_deref_type_array: {
|
||||
unsigned array_elems = glsl_get_length(deref_tail->type);
|
||||
|
||||
nir_deref_array arr_deref;
|
||||
arr_deref.deref = *deref_tail->child;
|
||||
arr_deref.deref_array_type = nir_deref_array_type_direct;
|
||||
|
||||
nir_deref *old_child = deref_tail->child;
|
||||
deref_tail->child = &arr_deref.deref;
|
||||
for (unsigned i = 0; i < array_elems; i++) {
|
||||
arr_deref.base_offset = i;
|
||||
insert_constant_initializer(deref_head, &arr_deref.deref,
|
||||
block, state);
|
||||
}
|
||||
deref_tail->child = old_child;
|
||||
return;
|
||||
}
|
||||
|
||||
case nir_deref_type_struct:
|
||||
insert_constant_initializer(deref_head, deref_tail->child,
|
||||
block, state);
|
||||
return;
|
||||
|
||||
default:
|
||||
unreachable("Invalid deref child type");
|
||||
}
|
||||
}
|
||||
|
||||
assert(deref_tail->child == NULL);
|
||||
|
||||
nir_load_const_instr *load =
|
||||
nir_deref_get_const_initializer_load(state->shader, deref_head);
|
||||
nir_instr_insert_before_block(block, &load->instr);
|
||||
|
||||
nir_src reg_src = get_deref_reg_src(deref_head, &load->instr, state);
|
||||
|
||||
nir_alu_instr *mov = nir_alu_instr_create(state->shader, nir_op_imov);
|
||||
mov->src[0].src = nir_src_for_ssa(&load->def);
|
||||
mov->dest.write_mask = (1 << load->def.num_components) - 1;
|
||||
mov->dest.dest.is_ssa = false;
|
||||
mov->dest.dest.reg.reg = reg_src.reg.reg;
|
||||
mov->dest.dest.reg.base_offset = reg_src.reg.base_offset;
|
||||
mov->dest.dest.reg.indirect = reg_src.reg.indirect;
|
||||
|
||||
nir_instr_insert_after(&load->instr, &mov->instr);
|
||||
state->progress = true;
|
||||
}
|
||||
|
||||
static bool
|
||||
nir_lower_locals_to_regs_impl(nir_function_impl *impl)
|
||||
{
|
||||
struct locals_to_regs_state state;
|
||||
|
||||
state.shader = impl->function->shader;
|
||||
state.impl = impl;
|
||||
state.progress = false;
|
||||
state.regs_table = _mesa_hash_table_create(NULL, hash_deref, derefs_equal);
|
||||
nir_array_init(&state.derefs_array, NULL);
|
||||
|
||||
nir_metadata_require(impl, nir_metadata_dominance);
|
||||
|
||||
nir_foreach_block(impl, lower_locals_to_regs_block, &state);
|
||||
|
||||
nir_array_foreach(&state.derefs_array, nir_deref_var *, deref_ptr) {
|
||||
nir_deref_var *deref = *deref_ptr;
|
||||
struct hash_entry *deref_entry =
|
||||
_mesa_hash_table_search(state.regs_table, deref);
|
||||
assert(deref_entry && deref_entry->key == deref);
|
||||
nir_register *reg = (nir_register *)deref_entry->data;
|
||||
|
||||
if (deref->var->constant_initializer == NULL)
|
||||
continue;
|
||||
|
||||
nir_block *usedef_lca = compute_reg_usedef_lca(reg);
|
||||
|
||||
insert_constant_initializer(deref, &deref->deref, usedef_lca, &state);
|
||||
}
|
||||
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
|
||||
nir_array_fini(&state.derefs_array);
|
||||
_mesa_hash_table_destroy(state.regs_table, NULL);
|
||||
|
||||
return state.progress;
|
||||
}
|
||||
|
||||
bool
|
||||
nir_lower_locals_to_regs(nir_shader *shader)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
progress = nir_lower_locals_to_regs_impl(function->impl) || progress;
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright © 2015 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Implements a pass that lowers output variables to a temporary plus an
|
||||
* output variable with a single copy at each exit point of the shader.
|
||||
* This way the output variable is only ever written.
|
||||
*
|
||||
* Because valid NIR requires that output variables are never read, this
|
||||
* pass is more of a helper for NIR producers and must be run before the
|
||||
* shader is ever validated.
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
struct lower_outputs_state {
|
||||
nir_shader *shader;
|
||||
struct exec_list old_outputs;
|
||||
};
|
||||
|
||||
static void
|
||||
emit_output_copies(nir_cursor cursor, struct lower_outputs_state *state)
|
||||
{
|
||||
assert(exec_list_length(&state->shader->outputs) ==
|
||||
exec_list_length(&state->old_outputs));
|
||||
|
||||
foreach_two_lists(out_node, &state->shader->outputs,
|
||||
temp_node, &state->old_outputs) {
|
||||
nir_variable *output = exec_node_data(nir_variable, out_node, node);
|
||||
nir_variable *temp = exec_node_data(nir_variable, temp_node, node);
|
||||
|
||||
nir_intrinsic_instr *copy =
|
||||
nir_intrinsic_instr_create(state->shader, nir_intrinsic_copy_var);
|
||||
copy->variables[0] = nir_deref_var_create(copy, output);
|
||||
copy->variables[1] = nir_deref_var_create(copy, temp);
|
||||
|
||||
nir_instr_insert(cursor, ©->instr);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
emit_output_copies_block(nir_block *block, void *state)
|
||||
{
|
||||
nir_foreach_instr(block, instr) {
|
||||
if (instr->type != nir_instr_type_intrinsic)
|
||||
continue;
|
||||
|
||||
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
|
||||
if (intrin->intrinsic == nir_intrinsic_emit_vertex)
|
||||
emit_output_copies(nir_before_instr(&intrin->instr), state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
nir_lower_outputs_to_temporaries(nir_shader *shader)
|
||||
{
|
||||
struct lower_outputs_state state;
|
||||
|
||||
if (shader->stage == MESA_SHADER_TESS_CTRL)
|
||||
return;
|
||||
|
||||
state.shader = shader;
|
||||
exec_list_move_nodes_to(&shader->outputs, &state.old_outputs);
|
||||
|
||||
/* Walk over all of the outputs turn each output into a temporary and
|
||||
* make a new variable for the actual output.
|
||||
*/
|
||||
nir_foreach_variable(var, &state.old_outputs) {
|
||||
nir_variable *output = ralloc(shader, nir_variable);
|
||||
memcpy(output, var, sizeof *output);
|
||||
|
||||
/* The orignal is now the temporary */
|
||||
nir_variable *temp = var;
|
||||
|
||||
/* Reparent the name to the new variable */
|
||||
ralloc_steal(output, output->name);
|
||||
|
||||
/* Give the output a new name with @out-temp appended */
|
||||
temp->name = ralloc_asprintf(var, "%s@out-temp", output->name);
|
||||
temp->data.mode = nir_var_global;
|
||||
temp->constant_initializer = NULL;
|
||||
|
||||
exec_list_push_tail(&shader->outputs, &output->node);
|
||||
}
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl == NULL)
|
||||
continue;
|
||||
|
||||
if (shader->stage == MESA_SHADER_GEOMETRY) {
|
||||
/* For geometry shaders, we have to emit the output copies right
|
||||
* before each EmitVertex call.
|
||||
*/
|
||||
nir_foreach_block(function->impl, emit_output_copies_block, &state);
|
||||
} else if (strcmp(function->name, "main") == 0) {
|
||||
/* For all other shader types, we need to do the copies right before
|
||||
* the jumps to the end block.
|
||||
*/
|
||||
struct set_entry *block_entry;
|
||||
set_foreach(function->impl->end_block->predecessors, block_entry) {
|
||||
struct nir_block *block = (void *)block_entry->key;
|
||||
emit_output_copies(nir_after_block_before_jump(block), &state);
|
||||
}
|
||||
}
|
||||
|
||||
nir_metadata_preserve(function->impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
}
|
||||
|
||||
exec_list_append(&shader->globals, &state.old_outputs);
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright © 2015 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@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
/*
|
||||
* Implements a pass that lowers vector phi nodes to scalar phi nodes when
|
||||
* we don't think it will hurt anything.
|
||||
*/
|
||||
|
||||
struct lower_phis_to_scalar_state {
|
||||
void *mem_ctx;
|
||||
void *dead_ctx;
|
||||
|
||||
/* Hash table marking which phi nodes are scalarizable. The key is
|
||||
* pointers to phi instructions and the entry is either NULL for not
|
||||
* scalarizable or non-null for scalarizable.
|
||||
*/
|
||||
struct hash_table *phi_table;
|
||||
};
|
||||
|
||||
static bool
|
||||
should_lower_phi(nir_phi_instr *phi, struct lower_phis_to_scalar_state *state);
|
||||
|
||||
static bool
|
||||
is_phi_src_scalarizable(nir_phi_src *src,
|
||||
struct lower_phis_to_scalar_state *state)
|
||||
{
|
||||
/* Don't know what to do with non-ssa sources */
|
||||
if (!src->src.is_ssa)
|
||||
return false;
|
||||
|
||||
nir_instr *src_instr = src->src.ssa->parent_instr;
|
||||
switch (src_instr->type) {
|
||||
case nir_instr_type_alu: {
|
||||
nir_alu_instr *src_alu = nir_instr_as_alu(src_instr);
|
||||
|
||||
/* ALU operations with output_size == 0 should be scalarized. We
|
||||
* will also see a bunch of vecN operations from scalarizing ALU
|
||||
* operations and, since they can easily be copy-propagated, they
|
||||
* are ok too.
|
||||
*/
|
||||
return nir_op_infos[src_alu->op].output_size == 0 ||
|
||||
src_alu->op == nir_op_vec2 ||
|
||||
src_alu->op == nir_op_vec3 ||
|
||||
src_alu->op == nir_op_vec4;
|
||||
}
|
||||
|
||||
case nir_instr_type_phi:
|
||||
/* A phi is scalarizable if we're going to lower it */
|
||||
return should_lower_phi(nir_instr_as_phi(src_instr), state);
|
||||
|
||||
case nir_instr_type_load_const:
|
||||
case nir_instr_type_ssa_undef:
|
||||
/* These are trivially scalarizable */
|
||||
return true;
|
||||
|
||||
case nir_instr_type_intrinsic: {
|
||||
nir_intrinsic_instr *src_intrin = nir_instr_as_intrinsic(src_instr);
|
||||
|
||||
switch (src_intrin->intrinsic) {
|
||||
case nir_intrinsic_load_var:
|
||||
return src_intrin->variables[0]->var->data.mode == nir_var_shader_in ||
|
||||
src_intrin->variables[0]->var->data.mode == nir_var_uniform;
|
||||
|
||||
case nir_intrinsic_interp_var_at_centroid:
|
||||
case nir_intrinsic_interp_var_at_sample:
|
||||
case nir_intrinsic_interp_var_at_offset:
|
||||
case nir_intrinsic_load_uniform:
|
||||
case nir_intrinsic_load_ubo:
|
||||
case nir_intrinsic_load_ssbo:
|
||||
case nir_intrinsic_load_input:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
/* We can't scalarize this type of instruction */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the given phi node should be lowered. The only phi nodes
|
||||
* we will scalarize at the moment are those where all of the sources are
|
||||
* scalarizable.
|
||||
*
|
||||
* The reason for this comes down to coalescing. Since phi sources can't
|
||||
* swizzle, swizzles on phis have to be resolved by inserting a mov right
|
||||
* before the phi. The choice then becomes between movs to pick off
|
||||
* components for a scalar phi or potentially movs to recombine components
|
||||
* for a vector phi. The problem is that the movs generated to pick off
|
||||
* the components are almost uncoalescable. We can't coalesce them in NIR
|
||||
* because we need them to pick off components and we can't coalesce them
|
||||
* in the backend because the source register is a vector and the
|
||||
* destination is a scalar that may be used at other places in the program.
|
||||
* On the other hand, if we have a bunch of scalars going into a vector
|
||||
* phi, the situation is much better. In this case, if the SSA def is
|
||||
* generated in the predecessor block to the corresponding phi source, the
|
||||
* backend code will be an ALU op into a temporary and then a mov into the
|
||||
* given vector component; this move can almost certainly be coalesced
|
||||
* away.
|
||||
*/
|
||||
static bool
|
||||
should_lower_phi(nir_phi_instr *phi, struct lower_phis_to_scalar_state *state)
|
||||
{
|
||||
/* Already scalar */
|
||||
if (phi->dest.ssa.num_components == 1)
|
||||
return false;
|
||||
|
||||
struct hash_entry *entry = _mesa_hash_table_search(state->phi_table, phi);
|
||||
if (entry)
|
||||
return entry->data != NULL;
|
||||
|
||||
/* Insert an entry and mark it as scalarizable for now. That way
|
||||
* we don't recurse forever and a cycle in the dependence graph
|
||||
* won't automatically make us fail to scalarize.
|
||||
*/
|
||||
entry = _mesa_hash_table_insert(state->phi_table, phi, (void *)(intptr_t)1);
|
||||
|
||||
bool scalarizable = true;
|
||||
|
||||
nir_foreach_phi_src(phi, src) {
|
||||
scalarizable = is_phi_src_scalarizable(src, state);
|
||||
if (!scalarizable)
|
||||
break;
|
||||
}
|
||||
|
||||
/* The hash table entry for 'phi' may have changed while recursing the
|
||||
* dependence graph, so we need to reset it */
|
||||
entry = _mesa_hash_table_search(state->phi_table, phi);
|
||||
assert(entry);
|
||||
|
||||
entry->data = (void *)(intptr_t)scalarizable;
|
||||
|
||||
return scalarizable;
|
||||
}
|
||||
|
||||
static bool
|
||||
lower_phis_to_scalar_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct lower_phis_to_scalar_state *state = void_state;
|
||||
|
||||
/* Find the last phi node in the block */
|
||||
nir_phi_instr *last_phi = NULL;
|
||||
nir_foreach_instr(block, instr) {
|
||||
if (instr->type != nir_instr_type_phi)
|
||||
break;
|
||||
|
||||
last_phi = nir_instr_as_phi(instr);
|
||||
}
|
||||
|
||||
/* We have to handle the phi nodes in their own pass due to the way
|
||||
* we're modifying the linked list of instructions.
|
||||
*/
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type != nir_instr_type_phi)
|
||||
break;
|
||||
|
||||
nir_phi_instr *phi = nir_instr_as_phi(instr);
|
||||
|
||||
if (!should_lower_phi(phi, state))
|
||||
continue;
|
||||
|
||||
/* Create a vecN operation to combine the results. Most of these
|
||||
* will be redundant, but copy propagation should clean them up for
|
||||
* us. No need to add the complexity here.
|
||||
*/
|
||||
nir_op vec_op;
|
||||
switch (phi->dest.ssa.num_components) {
|
||||
case 2: vec_op = nir_op_vec2; break;
|
||||
case 3: vec_op = nir_op_vec3; break;
|
||||
case 4: vec_op = nir_op_vec4; break;
|
||||
default: unreachable("Invalid number of components");
|
||||
}
|
||||
|
||||
nir_alu_instr *vec = nir_alu_instr_create(state->mem_ctx, vec_op);
|
||||
nir_ssa_dest_init(&vec->instr, &vec->dest.dest,
|
||||
phi->dest.ssa.num_components, NULL);
|
||||
vec->dest.write_mask = (1 << phi->dest.ssa.num_components) - 1;
|
||||
|
||||
for (unsigned i = 0; i < phi->dest.ssa.num_components; i++) {
|
||||
nir_phi_instr *new_phi = nir_phi_instr_create(state->mem_ctx);
|
||||
nir_ssa_dest_init(&new_phi->instr, &new_phi->dest, 1, NULL);
|
||||
|
||||
vec->src[i].src = nir_src_for_ssa(&new_phi->dest.ssa);
|
||||
|
||||
nir_foreach_phi_src(phi, src) {
|
||||
/* We need to insert a mov to grab the i'th component of src */
|
||||
nir_alu_instr *mov = nir_alu_instr_create(state->mem_ctx,
|
||||
nir_op_imov);
|
||||
nir_ssa_dest_init(&mov->instr, &mov->dest.dest, 1, NULL);
|
||||
mov->dest.write_mask = 1;
|
||||
nir_src_copy(&mov->src[0].src, &src->src, state->mem_ctx);
|
||||
mov->src[0].swizzle[0] = i;
|
||||
|
||||
/* Insert at the end of the predecessor but before the jump */
|
||||
nir_instr *pred_last_instr = nir_block_last_instr(src->pred);
|
||||
if (pred_last_instr && pred_last_instr->type == nir_instr_type_jump)
|
||||
nir_instr_insert_before(pred_last_instr, &mov->instr);
|
||||
else
|
||||
nir_instr_insert_after_block(src->pred, &mov->instr);
|
||||
|
||||
nir_phi_src *new_src = ralloc(new_phi, nir_phi_src);
|
||||
new_src->pred = src->pred;
|
||||
new_src->src = nir_src_for_ssa(&mov->dest.dest.ssa);
|
||||
|
||||
exec_list_push_tail(&new_phi->srcs, &new_src->node);
|
||||
}
|
||||
|
||||
nir_instr_insert_before(&phi->instr, &new_phi->instr);
|
||||
}
|
||||
|
||||
nir_instr_insert_after(&last_phi->instr, &vec->instr);
|
||||
|
||||
nir_ssa_def_rewrite_uses(&phi->dest.ssa,
|
||||
nir_src_for_ssa(&vec->dest.dest.ssa));
|
||||
|
||||
ralloc_steal(state->dead_ctx, phi);
|
||||
nir_instr_remove(&phi->instr);
|
||||
|
||||
/* We're using the safe iterator and inserting all the newly
|
||||
* scalarized phi nodes before their non-scalarized version so that's
|
||||
* ok. However, we are also inserting vec operations after all of
|
||||
* the last phi node so once we get here, we can't trust even the
|
||||
* safe iterator to stop properly. We have to break manually.
|
||||
*/
|
||||
if (instr == &last_phi->instr)
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
lower_phis_to_scalar_impl(nir_function_impl *impl)
|
||||
{
|
||||
struct lower_phis_to_scalar_state state;
|
||||
|
||||
state.mem_ctx = ralloc_parent(impl);
|
||||
state.dead_ctx = ralloc_context(NULL);
|
||||
state.phi_table = _mesa_hash_table_create(state.dead_ctx, _mesa_hash_pointer,
|
||||
_mesa_key_pointer_equal);
|
||||
|
||||
nir_foreach_block(impl, lower_phis_to_scalar_block, &state);
|
||||
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
|
||||
ralloc_free(state.dead_ctx);
|
||||
}
|
||||
|
||||
/** A pass that lowers vector phi nodes to scalar
|
||||
*
|
||||
* This pass loops through the blocks and lowers looks for vector phi nodes
|
||||
* it can lower to scalar phi nodes. Not all phi nodes are lowered. For
|
||||
* instance, if one of the sources is a non-scalarizable vector, then we
|
||||
* don't bother lowering because that would generate hard-to-coalesce movs.
|
||||
*/
|
||||
void
|
||||
nir_lower_phis_to_scalar(nir_shader *shader)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
lower_phis_to_scalar_impl(function->impl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2007 Brian Paul All Rights Reserved.
|
||||
* Copyright (C) 2008 VMware, Inc. All Rights Reserved.
|
||||
* Copyright © 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.
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "nir_builder.h"
|
||||
#include "program/hash_table.h"
|
||||
#include "glsl/ir_uniform.h"
|
||||
|
||||
#include "main/compiler.h"
|
||||
#include "main/mtypes.h"
|
||||
#include "program/prog_parameter.h"
|
||||
#include "program/program.h"
|
||||
|
||||
/* Calculate the sampler index based on array indicies and also
|
||||
* calculate the base uniform location for struct members.
|
||||
*/
|
||||
static void
|
||||
calc_sampler_offsets(nir_deref *tail, nir_tex_instr *instr,
|
||||
unsigned *array_elements, nir_ssa_def **indirect,
|
||||
nir_builder *b, unsigned *location)
|
||||
{
|
||||
if (tail->child == NULL)
|
||||
return;
|
||||
|
||||
switch (tail->child->deref_type) {
|
||||
case nir_deref_type_array: {
|
||||
nir_deref_array *deref_array = nir_deref_as_array(tail->child);
|
||||
|
||||
assert(deref_array->deref_array_type != nir_deref_array_type_wildcard);
|
||||
|
||||
calc_sampler_offsets(tail->child, instr, array_elements,
|
||||
indirect, b, location);
|
||||
instr->sampler_index += deref_array->base_offset * *array_elements;
|
||||
|
||||
if (deref_array->deref_array_type == nir_deref_array_type_indirect) {
|
||||
nir_ssa_def *mul =
|
||||
nir_imul(b, nir_imm_int(b, *array_elements),
|
||||
nir_ssa_for_src(b, deref_array->indirect, 1));
|
||||
|
||||
nir_instr_rewrite_src(&instr->instr, &deref_array->indirect,
|
||||
NIR_SRC_INIT);
|
||||
|
||||
if (*indirect) {
|
||||
*indirect = nir_iadd(b, *indirect, mul);
|
||||
} else {
|
||||
*indirect = mul;
|
||||
}
|
||||
}
|
||||
|
||||
*array_elements *= glsl_get_length(tail->type);
|
||||
break;
|
||||
}
|
||||
|
||||
case nir_deref_type_struct: {
|
||||
nir_deref_struct *deref_struct = nir_deref_as_struct(tail->child);
|
||||
*location += glsl_get_record_location_offset(tail->type, deref_struct->index);
|
||||
calc_sampler_offsets(tail->child, instr, array_elements,
|
||||
indirect, b, location);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
unreachable("Invalid deref type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
lower_sampler(nir_tex_instr *instr, const struct gl_shader_program *shader_program,
|
||||
gl_shader_stage stage, nir_builder *builder)
|
||||
{
|
||||
if (instr->sampler == NULL)
|
||||
return;
|
||||
|
||||
instr->sampler_index = 0;
|
||||
unsigned location = instr->sampler->var->data.location;
|
||||
unsigned array_elements = 1;
|
||||
nir_ssa_def *indirect = NULL;
|
||||
|
||||
builder->cursor = nir_before_instr(&instr->instr);
|
||||
calc_sampler_offsets(&instr->sampler->deref, instr, &array_elements,
|
||||
&indirect, builder, &location);
|
||||
|
||||
if (indirect) {
|
||||
/* First, we have to resize the array of texture sources */
|
||||
nir_tex_src *new_srcs = rzalloc_array(instr, nir_tex_src,
|
||||
instr->num_srcs + 1);
|
||||
|
||||
for (unsigned i = 0; i < instr->num_srcs; i++) {
|
||||
new_srcs[i].src_type = instr->src[i].src_type;
|
||||
nir_instr_move_src(&instr->instr, &new_srcs[i].src,
|
||||
&instr->src[i].src);
|
||||
}
|
||||
|
||||
ralloc_free(instr->src);
|
||||
instr->src = new_srcs;
|
||||
|
||||
/* Now we can go ahead and move the source over to being a
|
||||
* first-class texture source.
|
||||
*/
|
||||
instr->src[instr->num_srcs].src_type = nir_tex_src_sampler_offset;
|
||||
instr->num_srcs++;
|
||||
nir_instr_rewrite_src(&instr->instr,
|
||||
&instr->src[instr->num_srcs - 1].src,
|
||||
nir_src_for_ssa(indirect));
|
||||
|
||||
instr->sampler_array_size = array_elements;
|
||||
}
|
||||
|
||||
if (location > shader_program->NumUniformStorage - 1 ||
|
||||
!shader_program->UniformStorage[location].opaque[stage].active) {
|
||||
assert(!"cannot return a sampler");
|
||||
return;
|
||||
}
|
||||
|
||||
instr->sampler_index +=
|
||||
shader_program->UniformStorage[location].opaque[stage].index;
|
||||
|
||||
instr->sampler = NULL;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
nir_builder builder;
|
||||
const struct gl_shader_program *shader_program;
|
||||
gl_shader_stage stage;
|
||||
} lower_state;
|
||||
|
||||
static bool
|
||||
lower_block_cb(nir_block *block, void *_state)
|
||||
{
|
||||
lower_state *state = (lower_state *) _state;
|
||||
|
||||
nir_foreach_instr(block, instr) {
|
||||
if (instr->type == nir_instr_type_tex) {
|
||||
nir_tex_instr *tex_instr = nir_instr_as_tex(instr);
|
||||
lower_sampler(tex_instr, state->shader_program, state->stage,
|
||||
&state->builder);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
lower_impl(nir_function_impl *impl, const struct gl_shader_program *shader_program,
|
||||
gl_shader_stage stage)
|
||||
{
|
||||
lower_state state;
|
||||
|
||||
nir_builder_init(&state.builder, impl);
|
||||
state.shader_program = shader_program;
|
||||
state.stage = stage;
|
||||
|
||||
nir_foreach_block(impl, lower_block_cb, &state);
|
||||
}
|
||||
|
||||
void
|
||||
nir_lower_samplers(nir_shader *shader,
|
||||
const struct gl_shader_program *shader_program)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
lower_impl(function->impl, shader_program, shader->stage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright © 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "nir_builder.h"
|
||||
|
||||
struct lower_system_values_state {
|
||||
nir_builder builder;
|
||||
bool progress;
|
||||
};
|
||||
|
||||
static bool
|
||||
convert_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct lower_system_values_state *state = void_state;
|
||||
|
||||
nir_builder *b = &state->builder;
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type != nir_instr_type_intrinsic)
|
||||
continue;
|
||||
|
||||
nir_intrinsic_instr *load_var = nir_instr_as_intrinsic(instr);
|
||||
|
||||
if (load_var->intrinsic != nir_intrinsic_load_var)
|
||||
continue;
|
||||
|
||||
nir_variable *var = load_var->variables[0]->var;
|
||||
if (var->data.mode != nir_var_system_value)
|
||||
continue;
|
||||
|
||||
b->cursor = nir_after_instr(&load_var->instr);
|
||||
|
||||
nir_intrinsic_op sysval_op =
|
||||
nir_intrinsic_from_system_value(var->data.location);
|
||||
nir_ssa_def *sysval = nir_load_system_value(b, sysval_op, 0);
|
||||
|
||||
nir_ssa_def_rewrite_uses(&load_var->dest.ssa, nir_src_for_ssa(sysval));
|
||||
nir_instr_remove(&load_var->instr);
|
||||
|
||||
state->progress = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_impl(nir_function_impl *impl)
|
||||
{
|
||||
struct lower_system_values_state state;
|
||||
|
||||
state.progress = false;
|
||||
nir_builder_init(&state.builder, impl);
|
||||
|
||||
nir_foreach_block(impl, convert_block, &state);
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
return state.progress;
|
||||
}
|
||||
|
||||
bool
|
||||
nir_lower_system_values(nir_shader *shader)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
progress = convert_impl(function->impl) || progress;
|
||||
}
|
||||
|
||||
exec_list_make_empty(&shader->system_values);
|
||||
|
||||
return progress;
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* Copyright © 2015 Broadcom
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This lowering pass supports (as configured via nir_lower_tex_options)
|
||||
* various texture related conversions:
|
||||
* + texture projector lowering: converts the coordinate division for
|
||||
* texture projection to be done in ALU instructions instead of
|
||||
* asking the texture operation to do so.
|
||||
* + lowering RECT: converts the un-normalized RECT texture coordinates
|
||||
* to normalized coordinates with txs plus ALU instructions
|
||||
* + saturate s/t/r coords: to emulate certain texture clamp/wrap modes,
|
||||
* inserts instructions to clamp specified coordinates to [0.0, 1.0].
|
||||
* Note that this automatically triggers texture projector lowering if
|
||||
* needed, since clamping must happen after projector lowering.
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "nir_builder.h"
|
||||
|
||||
typedef struct {
|
||||
nir_builder b;
|
||||
const nir_lower_tex_options *options;
|
||||
bool progress;
|
||||
} lower_tex_state;
|
||||
|
||||
static void
|
||||
project_src(nir_builder *b, nir_tex_instr *tex)
|
||||
{
|
||||
/* Find the projector in the srcs list, if present. */
|
||||
unsigned proj_index;
|
||||
for (proj_index = 0; proj_index < tex->num_srcs; proj_index++) {
|
||||
if (tex->src[proj_index].src_type == nir_tex_src_projector)
|
||||
break;
|
||||
}
|
||||
if (proj_index == tex->num_srcs)
|
||||
return;
|
||||
|
||||
b->cursor = nir_before_instr(&tex->instr);
|
||||
|
||||
nir_ssa_def *inv_proj =
|
||||
nir_frcp(b, nir_ssa_for_src(b, tex->src[proj_index].src, 1));
|
||||
|
||||
/* Walk through the sources projecting the arguments. */
|
||||
for (unsigned i = 0; i < tex->num_srcs; i++) {
|
||||
switch (tex->src[i].src_type) {
|
||||
case nir_tex_src_coord:
|
||||
case nir_tex_src_comparitor:
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
nir_ssa_def *unprojected =
|
||||
nir_ssa_for_src(b, tex->src[i].src, nir_tex_instr_src_size(tex, i));
|
||||
nir_ssa_def *projected = nir_fmul(b, unprojected, inv_proj);
|
||||
|
||||
/* Array indices don't get projected, so make an new vector with the
|
||||
* coordinate's array index untouched.
|
||||
*/
|
||||
if (tex->is_array && tex->src[i].src_type == nir_tex_src_coord) {
|
||||
switch (tex->coord_components) {
|
||||
case 4:
|
||||
projected = nir_vec4(b,
|
||||
nir_channel(b, projected, 0),
|
||||
nir_channel(b, projected, 1),
|
||||
nir_channel(b, projected, 2),
|
||||
nir_channel(b, unprojected, 3));
|
||||
break;
|
||||
case 3:
|
||||
projected = nir_vec3(b,
|
||||
nir_channel(b, projected, 0),
|
||||
nir_channel(b, projected, 1),
|
||||
nir_channel(b, unprojected, 2));
|
||||
break;
|
||||
case 2:
|
||||
projected = nir_vec2(b,
|
||||
nir_channel(b, projected, 0),
|
||||
nir_channel(b, unprojected, 1));
|
||||
break;
|
||||
default:
|
||||
unreachable("bad texture coord count for array");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nir_instr_rewrite_src(&tex->instr,
|
||||
&tex->src[i].src,
|
||||
nir_src_for_ssa(projected));
|
||||
}
|
||||
|
||||
/* Now move the later tex sources down the array so that the projector
|
||||
* disappears.
|
||||
*/
|
||||
nir_instr_rewrite_src(&tex->instr, &tex->src[proj_index].src,
|
||||
NIR_SRC_INIT);
|
||||
for (unsigned i = proj_index + 1; i < tex->num_srcs; i++) {
|
||||
tex->src[i-1].src_type = tex->src[i].src_type;
|
||||
nir_instr_move_src(&tex->instr, &tex->src[i-1].src, &tex->src[i].src);
|
||||
}
|
||||
tex->num_srcs--;
|
||||
}
|
||||
|
||||
static nir_ssa_def *
|
||||
get_texture_size(nir_builder *b, nir_tex_instr *tex)
|
||||
{
|
||||
b->cursor = nir_before_instr(&tex->instr);
|
||||
|
||||
/* RECT textures should not be array: */
|
||||
assert(!tex->is_array);
|
||||
|
||||
nir_tex_instr *txs;
|
||||
|
||||
txs = nir_tex_instr_create(b->shader, 1);
|
||||
txs->op = nir_texop_txs;
|
||||
txs->sampler_dim = GLSL_SAMPLER_DIM_RECT;
|
||||
txs->sampler_index = tex->sampler_index;
|
||||
txs->dest_type = nir_type_int;
|
||||
|
||||
/* only single src, the lod: */
|
||||
txs->src[0].src = nir_src_for_ssa(nir_imm_int(b, 0));
|
||||
txs->src[0].src_type = nir_tex_src_lod;
|
||||
|
||||
nir_ssa_dest_init(&txs->instr, &txs->dest, 2, NULL);
|
||||
nir_builder_instr_insert(b, &txs->instr);
|
||||
|
||||
return nir_i2f(b, &txs->dest.ssa);
|
||||
}
|
||||
|
||||
static void
|
||||
lower_rect(nir_builder *b, nir_tex_instr *tex)
|
||||
{
|
||||
nir_ssa_def *txs = get_texture_size(b, tex);
|
||||
nir_ssa_def *scale = nir_frcp(b, txs);
|
||||
|
||||
/* Walk through the sources normalizing the requested arguments. */
|
||||
for (unsigned i = 0; i < tex->num_srcs; i++) {
|
||||
if (tex->src[i].src_type != nir_tex_src_coord)
|
||||
continue;
|
||||
|
||||
nir_ssa_def *coords =
|
||||
nir_ssa_for_src(b, tex->src[i].src, tex->coord_components);
|
||||
nir_instr_rewrite_src(&tex->instr,
|
||||
&tex->src[i].src,
|
||||
nir_src_for_ssa(nir_fmul(b, coords, scale)));
|
||||
}
|
||||
|
||||
tex->sampler_dim = GLSL_SAMPLER_DIM_2D;
|
||||
}
|
||||
|
||||
static void
|
||||
saturate_src(nir_builder *b, nir_tex_instr *tex, unsigned sat_mask)
|
||||
{
|
||||
b->cursor = nir_before_instr(&tex->instr);
|
||||
|
||||
/* Walk through the sources saturating the requested arguments. */
|
||||
for (unsigned i = 0; i < tex->num_srcs; i++) {
|
||||
if (tex->src[i].src_type != nir_tex_src_coord)
|
||||
continue;
|
||||
|
||||
nir_ssa_def *src =
|
||||
nir_ssa_for_src(b, tex->src[i].src, tex->coord_components);
|
||||
|
||||
/* split src into components: */
|
||||
nir_ssa_def *comp[4];
|
||||
|
||||
for (unsigned j = 0; j < tex->coord_components; j++)
|
||||
comp[j] = nir_channel(b, src, j);
|
||||
|
||||
/* clamp requested components, array index does not get clamped: */
|
||||
unsigned ncomp = tex->coord_components;
|
||||
if (tex->is_array)
|
||||
ncomp--;
|
||||
|
||||
for (unsigned j = 0; j < ncomp; j++) {
|
||||
if ((1 << j) & sat_mask) {
|
||||
if (tex->sampler_dim == GLSL_SAMPLER_DIM_RECT) {
|
||||
/* non-normalized texture coords, so clamp to texture
|
||||
* size rather than [0.0, 1.0]
|
||||
*/
|
||||
nir_ssa_def *txs = get_texture_size(b, tex);
|
||||
comp[j] = nir_fmax(b, comp[j], nir_imm_float(b, 0.0));
|
||||
comp[j] = nir_fmin(b, comp[j], nir_channel(b, txs, j));
|
||||
} else {
|
||||
comp[j] = nir_fsat(b, comp[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* and move the result back into a single vecN: */
|
||||
src = nir_vec(b, comp, tex->coord_components);
|
||||
|
||||
nir_instr_rewrite_src(&tex->instr,
|
||||
&tex->src[i].src,
|
||||
nir_src_for_ssa(src));
|
||||
}
|
||||
}
|
||||
|
||||
static nir_ssa_def *
|
||||
get_zero_or_one(nir_builder *b, nir_alu_type type, uint8_t swizzle_val)
|
||||
{
|
||||
nir_const_value v;
|
||||
|
||||
memset(&v, 0, sizeof(v));
|
||||
|
||||
if (swizzle_val == 4) {
|
||||
v.u[0] = v.u[1] = v.u[2] = v.u[3] = 0;
|
||||
} else {
|
||||
assert(swizzle_val == 5);
|
||||
if (type == nir_type_float)
|
||||
v.f[0] = v.f[1] = v.f[2] = v.f[3] = 1.0;
|
||||
else
|
||||
v.u[0] = v.u[1] = v.u[2] = v.u[3] = 1;
|
||||
}
|
||||
|
||||
return nir_build_imm(b, 4, v);
|
||||
}
|
||||
|
||||
static void
|
||||
swizzle_result(nir_builder *b, nir_tex_instr *tex, const uint8_t swizzle[4])
|
||||
{
|
||||
assert(tex->dest.is_ssa);
|
||||
|
||||
b->cursor = nir_after_instr(&tex->instr);
|
||||
|
||||
nir_ssa_def *swizzled;
|
||||
if (tex->op == nir_texop_tg4) {
|
||||
if (swizzle[tex->component] < 4) {
|
||||
/* This one's easy */
|
||||
tex->component = swizzle[tex->component];
|
||||
return;
|
||||
} else {
|
||||
swizzled = get_zero_or_one(b, tex->dest_type, swizzle[tex->component]);
|
||||
}
|
||||
} else {
|
||||
assert(nir_tex_instr_dest_size(tex) == 4);
|
||||
if (swizzle[0] < 4 && swizzle[1] < 4 &&
|
||||
swizzle[2] < 4 && swizzle[3] < 4) {
|
||||
unsigned swiz[4] = { swizzle[0], swizzle[1], swizzle[2], swizzle[3] };
|
||||
/* We have no 0's or 1's, just emit a swizzling MOV */
|
||||
swizzled = nir_swizzle(b, &tex->dest.ssa, swiz, 4, false);
|
||||
} else {
|
||||
nir_ssa_def *srcs[4];
|
||||
for (unsigned i = 0; i < 4; i++) {
|
||||
if (swizzle[i] < 4) {
|
||||
srcs[i] = nir_channel(b, &tex->dest.ssa, swizzle[i]);
|
||||
} else {
|
||||
srcs[i] = get_zero_or_one(b, tex->dest_type, swizzle[i]);
|
||||
}
|
||||
}
|
||||
swizzled = nir_vec(b, srcs, 4);
|
||||
}
|
||||
}
|
||||
|
||||
nir_ssa_def_rewrite_uses_after(&tex->dest.ssa, nir_src_for_ssa(swizzled),
|
||||
swizzled->parent_instr);
|
||||
}
|
||||
|
||||
static bool
|
||||
nir_lower_tex_block(nir_block *block, void *void_state)
|
||||
{
|
||||
lower_tex_state *state = void_state;
|
||||
nir_builder *b = &state->b;
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type != nir_instr_type_tex)
|
||||
continue;
|
||||
|
||||
nir_tex_instr *tex = nir_instr_as_tex(instr);
|
||||
bool lower_txp = !!(state->options->lower_txp & (1 << tex->sampler_dim));
|
||||
|
||||
/* mask of src coords to saturate (clamp): */
|
||||
unsigned sat_mask = 0;
|
||||
|
||||
if ((1 << tex->sampler_index) & state->options->saturate_r)
|
||||
sat_mask |= (1 << 2); /* .z */
|
||||
if ((1 << tex->sampler_index) & state->options->saturate_t)
|
||||
sat_mask |= (1 << 1); /* .y */
|
||||
if ((1 << tex->sampler_index) & state->options->saturate_s)
|
||||
sat_mask |= (1 << 0); /* .x */
|
||||
|
||||
/* If we are clamping any coords, we must lower projector first
|
||||
* as clamping happens *after* projection:
|
||||
*/
|
||||
if (lower_txp || sat_mask) {
|
||||
project_src(b, tex);
|
||||
state->progress = true;
|
||||
}
|
||||
|
||||
if ((tex->sampler_dim == GLSL_SAMPLER_DIM_RECT) &&
|
||||
state->options->lower_rect) {
|
||||
lower_rect(b, tex);
|
||||
state->progress = true;
|
||||
}
|
||||
|
||||
if (sat_mask) {
|
||||
saturate_src(b, tex, sat_mask);
|
||||
state->progress = true;
|
||||
}
|
||||
|
||||
if (((1 << tex->sampler_index) & state->options->swizzle_result) &&
|
||||
!nir_tex_instr_is_query(tex) &&
|
||||
!(tex->is_shadow && tex->is_new_style_shadow)) {
|
||||
swizzle_result(b, tex, state->options->swizzles[tex->sampler_index]);
|
||||
state->progress = true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
nir_lower_tex_impl(nir_function_impl *impl, lower_tex_state *state)
|
||||
{
|
||||
nir_builder_init(&state->b, impl);
|
||||
|
||||
nir_foreach_block(impl, nir_lower_tex_block, state);
|
||||
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
}
|
||||
|
||||
bool
|
||||
nir_lower_tex(nir_shader *shader, const nir_lower_tex_options *options)
|
||||
{
|
||||
lower_tex_state state;
|
||||
state.options = options;
|
||||
state.progress = false;
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
nir_lower_tex_impl(function->impl, &state);
|
||||
}
|
||||
|
||||
return state.progress;
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
/*
|
||||
* This pass lowers the neg, abs, and sat operations to source modifiers on
|
||||
* ALU operations to make things nicer for the backend. It's just much
|
||||
* easier to not have them when we're doing optimizations.
|
||||
*/
|
||||
|
||||
static bool
|
||||
nir_lower_to_source_mods_block(nir_block *block, void *state)
|
||||
{
|
||||
nir_foreach_instr(block, instr) {
|
||||
if (instr->type != nir_instr_type_alu)
|
||||
continue;
|
||||
|
||||
nir_alu_instr *alu = nir_instr_as_alu(instr);
|
||||
|
||||
for (unsigned i = 0; i < nir_op_infos[alu->op].num_inputs; i++) {
|
||||
if (!alu->src[i].src.is_ssa)
|
||||
continue;
|
||||
|
||||
if (alu->src[i].src.ssa->parent_instr->type != nir_instr_type_alu)
|
||||
continue;
|
||||
|
||||
nir_alu_instr *parent = nir_instr_as_alu(alu->src[i].src.ssa->parent_instr);
|
||||
|
||||
if (parent->dest.saturate)
|
||||
continue;
|
||||
|
||||
switch (nir_op_infos[alu->op].input_types[i]) {
|
||||
case nir_type_float:
|
||||
if (parent->op != nir_op_fmov)
|
||||
continue;
|
||||
break;
|
||||
case nir_type_int:
|
||||
if (parent->op != nir_op_imov)
|
||||
continue;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We can only do a rewrite if the source we are copying is SSA.
|
||||
* Otherwise, moving the read might invalidly reorder reads/writes
|
||||
* on a register.
|
||||
*/
|
||||
if (!parent->src[0].src.is_ssa)
|
||||
continue;
|
||||
|
||||
nir_instr_rewrite_src(instr, &alu->src[i].src, parent->src[0].src);
|
||||
if (alu->src[i].abs) {
|
||||
/* abs trumps both neg and abs, do nothing */
|
||||
} else {
|
||||
alu->src[i].negate = (alu->src[i].negate != parent->src[0].negate);
|
||||
alu->src[i].abs |= parent->src[0].abs;
|
||||
}
|
||||
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
if (!nir_alu_instr_channel_used(alu, i, j))
|
||||
continue;
|
||||
alu->src[i].swizzle[j] = parent->src[0].swizzle[alu->src[i].swizzle[j]];
|
||||
}
|
||||
|
||||
if (list_empty(&parent->dest.dest.ssa.uses) &&
|
||||
list_empty(&parent->dest.dest.ssa.if_uses))
|
||||
nir_instr_remove(&parent->instr);
|
||||
}
|
||||
|
||||
switch (alu->op) {
|
||||
case nir_op_fsat:
|
||||
alu->op = nir_op_fmov;
|
||||
alu->dest.saturate = true;
|
||||
break;
|
||||
case nir_op_ineg:
|
||||
alu->op = nir_op_imov;
|
||||
alu->src[0].negate = !alu->src[0].negate;
|
||||
break;
|
||||
case nir_op_fneg:
|
||||
alu->op = nir_op_fmov;
|
||||
alu->src[0].negate = !alu->src[0].negate;
|
||||
break;
|
||||
case nir_op_iabs:
|
||||
alu->op = nir_op_imov;
|
||||
alu->src[0].abs = true;
|
||||
alu->src[0].negate = false;
|
||||
break;
|
||||
case nir_op_fabs:
|
||||
alu->op = nir_op_fmov;
|
||||
alu->src[0].abs = true;
|
||||
alu->src[0].negate = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* We've covered sources. Now we're going to try and saturate the
|
||||
* destination if we can.
|
||||
*/
|
||||
|
||||
if (!alu->dest.dest.is_ssa)
|
||||
continue;
|
||||
|
||||
/* We can only saturate float destinations */
|
||||
if (nir_op_infos[alu->op].output_type != nir_type_float)
|
||||
continue;
|
||||
|
||||
if (!list_empty(&alu->dest.dest.ssa.if_uses))
|
||||
continue;
|
||||
|
||||
bool all_children_are_sat = true;
|
||||
nir_foreach_use(&alu->dest.dest.ssa, child_src) {
|
||||
assert(child_src->is_ssa);
|
||||
nir_instr *child = child_src->parent_instr;
|
||||
if (child->type != nir_instr_type_alu) {
|
||||
all_children_are_sat = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
nir_alu_instr *child_alu = nir_instr_as_alu(child);
|
||||
if (child_alu->src[0].negate || child_alu->src[0].abs) {
|
||||
all_children_are_sat = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child_alu->op != nir_op_fsat &&
|
||||
!(child_alu->op == nir_op_fmov && child_alu->dest.saturate)) {
|
||||
all_children_are_sat = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!all_children_are_sat)
|
||||
continue;
|
||||
|
||||
alu->dest.saturate = true;
|
||||
|
||||
nir_foreach_use(&alu->dest.dest.ssa, child_src) {
|
||||
assert(child_src->is_ssa);
|
||||
nir_instr *child = child_src->parent_instr;
|
||||
assert(child->type == nir_instr_type_alu);
|
||||
nir_alu_instr *child_alu = nir_instr_as_alu(child);
|
||||
|
||||
child_alu->op = nir_op_fmov;
|
||||
child_alu->dest.saturate = false;
|
||||
/* We could propagate the dest of our instruction to the
|
||||
* destinations of the uses here. However, one quick round of
|
||||
* copy propagation will clean that all up and then we don't have
|
||||
* the complexity.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
nir_lower_to_source_mods_impl(nir_function_impl *impl)
|
||||
{
|
||||
nir_foreach_block(impl, nir_lower_to_source_mods_block, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
nir_lower_to_source_mods(nir_shader *shader)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
nir_lower_to_source_mods_impl(function->impl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright © 2015 Red Hat
|
||||
*
|
||||
* 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:
|
||||
* Rob Clark <robclark@freedesktop.org>
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "nir_builder.h"
|
||||
|
||||
#define MAX_COLORS 2 /* VARYING_SLOT_COL0/COL1 */
|
||||
|
||||
typedef struct {
|
||||
nir_builder b;
|
||||
nir_shader *shader;
|
||||
nir_variable *face;
|
||||
struct {
|
||||
nir_variable *front; /* COLn */
|
||||
nir_variable *back; /* BFCn */
|
||||
} colors[MAX_COLORS];
|
||||
int colors_count;
|
||||
} lower_2side_state;
|
||||
|
||||
|
||||
/* Lowering pass for fragment shaders to emulated two-sided-color. For
|
||||
* each COLOR input, a corresponding BCOLOR input is created, and bcsel
|
||||
* instruction used to select front or back color based on FACE.
|
||||
*/
|
||||
|
||||
static nir_variable *
|
||||
create_input(nir_shader *shader, unsigned drvloc, gl_varying_slot slot)
|
||||
{
|
||||
nir_variable *var = rzalloc(shader, nir_variable);
|
||||
|
||||
var->data.driver_location = drvloc;
|
||||
var->type = glsl_vec4_type();
|
||||
var->data.mode = nir_var_shader_in;
|
||||
var->name = ralloc_asprintf(var, "in_%d", drvloc);
|
||||
var->data.index = 0;
|
||||
var->data.location = slot;
|
||||
|
||||
exec_list_push_tail(&shader->inputs, &var->node);
|
||||
|
||||
shader->num_inputs++; /* TODO use type_size() */
|
||||
|
||||
return var;
|
||||
}
|
||||
|
||||
static nir_ssa_def *
|
||||
load_input(nir_builder *b, nir_variable *in)
|
||||
{
|
||||
nir_intrinsic_instr *load;
|
||||
|
||||
load = nir_intrinsic_instr_create(b->shader, nir_intrinsic_load_input);
|
||||
load->num_components = 4;
|
||||
load->const_index[0] = in->data.driver_location;
|
||||
load->src[0] = nir_src_for_ssa(nir_imm_int(b, 0));
|
||||
nir_ssa_dest_init(&load->instr, &load->dest, 4, NULL);
|
||||
nir_builder_instr_insert(b, &load->instr);
|
||||
|
||||
return &load->dest.ssa;
|
||||
}
|
||||
|
||||
static int
|
||||
setup_inputs(lower_2side_state *state)
|
||||
{
|
||||
int maxloc = -1;
|
||||
|
||||
/* find color/face inputs: */
|
||||
nir_foreach_variable(var, &state->shader->inputs) {
|
||||
int loc = var->data.driver_location;
|
||||
|
||||
/* keep track of last used driver-location.. we'll be
|
||||
* appending BCLr/FACE after last existing input:
|
||||
*/
|
||||
maxloc = MAX2(maxloc, loc);
|
||||
|
||||
switch (var->data.location) {
|
||||
case VARYING_SLOT_COL0:
|
||||
case VARYING_SLOT_COL1:
|
||||
assert(state->colors_count < ARRAY_SIZE(state->colors));
|
||||
state->colors[state->colors_count].front = var;
|
||||
state->colors_count++;
|
||||
break;
|
||||
case VARYING_SLOT_FACE:
|
||||
state->face = var;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* if we don't have any color inputs, nothing to do: */
|
||||
if (state->colors_count == 0)
|
||||
return -1;
|
||||
|
||||
/* if we don't already have one, insert a FACE input: */
|
||||
if (!state->face) {
|
||||
state->face = create_input(state->shader, ++maxloc, VARYING_SLOT_FACE);
|
||||
state->face->data.interpolation = INTERP_QUALIFIER_FLAT;
|
||||
}
|
||||
|
||||
/* add required back-face color inputs: */
|
||||
for (int i = 0; i < state->colors_count; i++) {
|
||||
gl_varying_slot slot;
|
||||
|
||||
if (state->colors[i].front->data.location == VARYING_SLOT_COL0)
|
||||
slot = VARYING_SLOT_BFC0;
|
||||
else
|
||||
slot = VARYING_SLOT_BFC1;
|
||||
|
||||
state->colors[i].back = create_input(state->shader, ++maxloc, slot);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
nir_lower_two_sided_color_block(nir_block *block, void *void_state)
|
||||
{
|
||||
lower_2side_state *state = void_state;
|
||||
nir_builder *b = &state->b;
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type != nir_instr_type_intrinsic)
|
||||
continue;
|
||||
|
||||
nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr);
|
||||
|
||||
if (intr->intrinsic != nir_intrinsic_load_input)
|
||||
continue;
|
||||
|
||||
int idx;
|
||||
for (idx = 0; idx < state->colors_count; idx++) {
|
||||
unsigned drvloc =
|
||||
state->colors[idx].front->data.driver_location;
|
||||
if (intr->const_index[0] == drvloc) {
|
||||
assert(nir_src_as_const_value(intr->src[0]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx == state->colors_count)
|
||||
continue;
|
||||
|
||||
/* replace load_input(COLn) with
|
||||
* bcsel(load_input(FACE), load_input(COLn), load_input(BFCn))
|
||||
*/
|
||||
b->cursor = nir_before_instr(&intr->instr);
|
||||
nir_ssa_def *face = nir_channel(b, load_input(b, state->face), 0);
|
||||
nir_ssa_def *front = load_input(b, state->colors[idx].front);
|
||||
nir_ssa_def *back = load_input(b, state->colors[idx].back);
|
||||
nir_ssa_def *cond = nir_flt(b, face, nir_imm_float(b, 0.0));
|
||||
nir_ssa_def *color = nir_bcsel(b, cond, back, front);
|
||||
|
||||
assert(intr->dest.is_ssa);
|
||||
nir_ssa_def_rewrite_uses(&intr->dest.ssa, nir_src_for_ssa(color));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
nir_lower_two_sided_color_impl(nir_function_impl *impl,
|
||||
lower_2side_state *state)
|
||||
{
|
||||
nir_builder *b = &state->b;
|
||||
|
||||
nir_builder_init(b, impl);
|
||||
|
||||
nir_foreach_block(impl, nir_lower_two_sided_color_block, state);
|
||||
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
}
|
||||
|
||||
void
|
||||
nir_lower_two_sided_color(nir_shader *shader)
|
||||
{
|
||||
lower_2side_state state = {
|
||||
.shader = shader,
|
||||
};
|
||||
|
||||
if (shader->stage != MESA_SHADER_FRAGMENT)
|
||||
return;
|
||||
|
||||
if (setup_inputs(&state) != 0)
|
||||
return;
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
nir_lower_two_sided_color_impl(function->impl, &state);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "compiler/nir_types.h"
|
||||
|
||||
/*
|
||||
* Lowers all copy intrinsics to sequences of load/store intrinsics.
|
||||
*/
|
||||
|
||||
/* Walks down the deref chain and returns the next deref in the chain whose
|
||||
* child is a wildcard. In other words, given the chain a[1].foo[*].bar,
|
||||
* this function will return the deref to foo. Calling it a second time
|
||||
* with the [*].bar, it will return NULL.
|
||||
*/
|
||||
static nir_deref *
|
||||
deref_next_wildcard_parent(nir_deref *deref)
|
||||
{
|
||||
for (nir_deref *tail = deref; tail->child; tail = tail->child) {
|
||||
if (tail->child->deref_type != nir_deref_type_array)
|
||||
continue;
|
||||
|
||||
nir_deref_array *arr = nir_deref_as_array(tail->child);
|
||||
|
||||
if (arr->deref_array_type == nir_deref_array_type_wildcard)
|
||||
return tail;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* This function recursively walks the given deref chain and replaces the
|
||||
* given copy instruction with an equivalent sequence load/store
|
||||
* operations.
|
||||
*
|
||||
* @copy_instr The copy instruction to replace; new instructions will be
|
||||
* inserted before this one
|
||||
*
|
||||
* @dest_head The head of the destination variable deref chain
|
||||
*
|
||||
* @src_head The head of the source variable deref chain
|
||||
*
|
||||
* @dest_tail The current tail of the destination variable deref chain;
|
||||
* this is used for recursion and external callers of this
|
||||
* function should call it with tail == head
|
||||
*
|
||||
* @src_tail The current tail of the source variable deref chain;
|
||||
* this is used for recursion and external callers of this
|
||||
* function should call it with tail == head
|
||||
*
|
||||
* @state The current variable lowering state
|
||||
*/
|
||||
static void
|
||||
emit_copy_load_store(nir_intrinsic_instr *copy_instr,
|
||||
nir_deref_var *dest_head, nir_deref_var *src_head,
|
||||
nir_deref *dest_tail, nir_deref *src_tail, void *mem_ctx)
|
||||
{
|
||||
/* Find the next pair of wildcards */
|
||||
nir_deref *src_arr_parent = deref_next_wildcard_parent(src_tail);
|
||||
nir_deref *dest_arr_parent = deref_next_wildcard_parent(dest_tail);
|
||||
|
||||
if (src_arr_parent || dest_arr_parent) {
|
||||
/* Wildcards had better come in matched pairs */
|
||||
assert(dest_arr_parent && dest_arr_parent);
|
||||
|
||||
nir_deref_array *src_arr = nir_deref_as_array(src_arr_parent->child);
|
||||
nir_deref_array *dest_arr = nir_deref_as_array(dest_arr_parent->child);
|
||||
|
||||
unsigned length = glsl_get_length(src_arr_parent->type);
|
||||
/* The wildcards should represent the same number of elements */
|
||||
assert(length == glsl_get_length(dest_arr_parent->type));
|
||||
assert(length > 0);
|
||||
|
||||
/* Walk over all of the elements that this wildcard refers to and
|
||||
* call emit_copy_load_store on each one of them */
|
||||
src_arr->deref_array_type = nir_deref_array_type_direct;
|
||||
dest_arr->deref_array_type = nir_deref_array_type_direct;
|
||||
for (unsigned i = 0; i < length; i++) {
|
||||
src_arr->base_offset = i;
|
||||
dest_arr->base_offset = i;
|
||||
emit_copy_load_store(copy_instr, dest_head, src_head,
|
||||
&dest_arr->deref, &src_arr->deref, mem_ctx);
|
||||
}
|
||||
src_arr->deref_array_type = nir_deref_array_type_wildcard;
|
||||
dest_arr->deref_array_type = nir_deref_array_type_wildcard;
|
||||
} else {
|
||||
/* In this case, we have no wildcards anymore, so all we have to do
|
||||
* is just emit the load and store operations. */
|
||||
src_tail = nir_deref_tail(src_tail);
|
||||
dest_tail = nir_deref_tail(dest_tail);
|
||||
|
||||
assert(src_tail->type == dest_tail->type);
|
||||
|
||||
unsigned num_components = glsl_get_vector_elements(src_tail->type);
|
||||
|
||||
nir_intrinsic_instr *load =
|
||||
nir_intrinsic_instr_create(mem_ctx, nir_intrinsic_load_var);
|
||||
load->num_components = num_components;
|
||||
load->variables[0] = nir_deref_as_var(nir_copy_deref(load, &src_head->deref));
|
||||
nir_ssa_dest_init(&load->instr, &load->dest, num_components, NULL);
|
||||
|
||||
nir_instr_insert_before(©_instr->instr, &load->instr);
|
||||
|
||||
nir_intrinsic_instr *store =
|
||||
nir_intrinsic_instr_create(mem_ctx, nir_intrinsic_store_var);
|
||||
store->num_components = num_components;
|
||||
store->const_index[0] = (1 << num_components) - 1;
|
||||
store->variables[0] = nir_deref_as_var(nir_copy_deref(store, &dest_head->deref));
|
||||
|
||||
store->src[0].is_ssa = true;
|
||||
store->src[0].ssa = &load->dest.ssa;
|
||||
|
||||
nir_instr_insert_before(©_instr->instr, &store->instr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Lowers a copy instruction to a sequence of load/store instructions
|
||||
*
|
||||
* The new instructions are placed before the copy instruction in the IR.
|
||||
*/
|
||||
void
|
||||
nir_lower_var_copy_instr(nir_intrinsic_instr *copy, void *mem_ctx)
|
||||
{
|
||||
assert(copy->intrinsic == nir_intrinsic_copy_var);
|
||||
emit_copy_load_store(copy, copy->variables[0], copy->variables[1],
|
||||
©->variables[0]->deref,
|
||||
©->variables[1]->deref, mem_ctx);
|
||||
}
|
||||
|
||||
static bool
|
||||
lower_var_copies_block(nir_block *block, void *mem_ctx)
|
||||
{
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type != nir_instr_type_intrinsic)
|
||||
continue;
|
||||
|
||||
nir_intrinsic_instr *copy = nir_instr_as_intrinsic(instr);
|
||||
if (copy->intrinsic != nir_intrinsic_copy_var)
|
||||
continue;
|
||||
|
||||
nir_lower_var_copy_instr(copy, mem_ctx);
|
||||
|
||||
nir_instr_remove(©->instr);
|
||||
ralloc_free(copy);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
lower_var_copies_impl(nir_function_impl *impl)
|
||||
{
|
||||
nir_foreach_block(impl, lower_var_copies_block, ralloc_parent(impl));
|
||||
}
|
||||
|
||||
/* Lowers every copy_var instruction in the program to a sequence of
|
||||
* load/store instructions.
|
||||
*/
|
||||
void
|
||||
nir_lower_var_copies(nir_shader *shader)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
lower_var_copies_impl(function->impl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,973 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "nir_builder.h"
|
||||
#include "nir_vla.h"
|
||||
|
||||
|
||||
struct deref_node {
|
||||
struct deref_node *parent;
|
||||
const struct glsl_type *type;
|
||||
|
||||
bool lower_to_ssa;
|
||||
|
||||
/* Only valid for things that end up in the direct list.
|
||||
* Note that multiple nir_deref_vars may correspond to this node, but they
|
||||
* will all be equivalent, so any is as good as the other.
|
||||
*/
|
||||
nir_deref_var *deref;
|
||||
struct exec_node direct_derefs_link;
|
||||
|
||||
struct set *loads;
|
||||
struct set *stores;
|
||||
struct set *copies;
|
||||
|
||||
nir_ssa_def **def_stack;
|
||||
nir_ssa_def **def_stack_tail;
|
||||
|
||||
struct deref_node *wildcard;
|
||||
struct deref_node *indirect;
|
||||
struct deref_node *children[0];
|
||||
};
|
||||
|
||||
struct lower_variables_state {
|
||||
nir_shader *shader;
|
||||
void *dead_ctx;
|
||||
nir_function_impl *impl;
|
||||
|
||||
/* A hash table mapping variables to deref_node data */
|
||||
struct hash_table *deref_var_nodes;
|
||||
|
||||
/* A hash table mapping fully-qualified direct dereferences, i.e.
|
||||
* dereferences with no indirect or wildcard array dereferences, to
|
||||
* deref_node data.
|
||||
*
|
||||
* At the moment, we only lower loads, stores, and copies that can be
|
||||
* trivially lowered to loads and stores, i.e. copies with no indirects
|
||||
* and no wildcards. If a part of a variable that is being loaded from
|
||||
* and/or stored into is also involved in a copy operation with
|
||||
* wildcards, then we lower that copy operation to loads and stores, but
|
||||
* otherwise we leave copies with wildcards alone. Since the only derefs
|
||||
* used in these loads, stores, and trivial copies are ones with no
|
||||
* wildcards and no indirects, these are precisely the derefs that we
|
||||
* can actually consider lowering.
|
||||
*/
|
||||
struct exec_list direct_deref_nodes;
|
||||
|
||||
/* Controls whether get_deref_node will add variables to the
|
||||
* direct_deref_nodes table. This is turned on when we are initially
|
||||
* scanning for load/store instructions. It is then turned off so we
|
||||
* don't accidentally change the direct_deref_nodes table while we're
|
||||
* iterating throug it.
|
||||
*/
|
||||
bool add_to_direct_deref_nodes;
|
||||
|
||||
/* A hash table mapping phi nodes to deref_state data */
|
||||
struct hash_table *phi_table;
|
||||
};
|
||||
|
||||
static struct deref_node *
|
||||
deref_node_create(struct deref_node *parent,
|
||||
const struct glsl_type *type, nir_shader *shader)
|
||||
{
|
||||
size_t size = sizeof(struct deref_node) +
|
||||
glsl_get_length(type) * sizeof(struct deref_node *);
|
||||
|
||||
struct deref_node *node = rzalloc_size(shader, size);
|
||||
node->type = type;
|
||||
node->parent = parent;
|
||||
node->deref = NULL;
|
||||
exec_node_init(&node->direct_derefs_link);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/* Returns the deref node associated with the given variable. This will be
|
||||
* the root of the tree representing all of the derefs of the given variable.
|
||||
*/
|
||||
static struct deref_node *
|
||||
get_deref_node_for_var(nir_variable *var, struct lower_variables_state *state)
|
||||
{
|
||||
struct deref_node *node;
|
||||
|
||||
struct hash_entry *var_entry =
|
||||
_mesa_hash_table_search(state->deref_var_nodes, var);
|
||||
|
||||
if (var_entry) {
|
||||
return var_entry->data;
|
||||
} else {
|
||||
node = deref_node_create(NULL, var->type, state->dead_ctx);
|
||||
_mesa_hash_table_insert(state->deref_var_nodes, var, node);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
/* Gets the deref_node for the given deref chain and creates it if it
|
||||
* doesn't yet exist. If the deref is fully-qualified and direct and
|
||||
* state->add_to_direct_deref_nodes is true, it will be added to the hash
|
||||
* table of of fully-qualified direct derefs.
|
||||
*/
|
||||
static struct deref_node *
|
||||
get_deref_node(nir_deref_var *deref, struct lower_variables_state *state)
|
||||
{
|
||||
bool is_direct = true;
|
||||
|
||||
/* Start at the base of the chain. */
|
||||
struct deref_node *node = get_deref_node_for_var(deref->var, state);
|
||||
assert(deref->deref.type == node->type);
|
||||
|
||||
for (nir_deref *tail = deref->deref.child; tail; tail = tail->child) {
|
||||
switch (tail->deref_type) {
|
||||
case nir_deref_type_struct: {
|
||||
nir_deref_struct *deref_struct = nir_deref_as_struct(tail);
|
||||
|
||||
assert(deref_struct->index < glsl_get_length(node->type));
|
||||
|
||||
if (node->children[deref_struct->index] == NULL)
|
||||
node->children[deref_struct->index] =
|
||||
deref_node_create(node, tail->type, state->dead_ctx);
|
||||
|
||||
node = node->children[deref_struct->index];
|
||||
break;
|
||||
}
|
||||
|
||||
case nir_deref_type_array: {
|
||||
nir_deref_array *arr = nir_deref_as_array(tail);
|
||||
|
||||
switch (arr->deref_array_type) {
|
||||
case nir_deref_array_type_direct:
|
||||
/* This is possible if a loop unrolls and generates an
|
||||
* out-of-bounds offset. We need to handle this at least
|
||||
* somewhat gracefully.
|
||||
*/
|
||||
if (arr->base_offset >= glsl_get_length(node->type))
|
||||
return NULL;
|
||||
|
||||
if (node->children[arr->base_offset] == NULL)
|
||||
node->children[arr->base_offset] =
|
||||
deref_node_create(node, tail->type, state->dead_ctx);
|
||||
|
||||
node = node->children[arr->base_offset];
|
||||
break;
|
||||
|
||||
case nir_deref_array_type_indirect:
|
||||
if (node->indirect == NULL)
|
||||
node->indirect = deref_node_create(node, tail->type,
|
||||
state->dead_ctx);
|
||||
|
||||
node = node->indirect;
|
||||
is_direct = false;
|
||||
break;
|
||||
|
||||
case nir_deref_array_type_wildcard:
|
||||
if (node->wildcard == NULL)
|
||||
node->wildcard = deref_node_create(node, tail->type,
|
||||
state->dead_ctx);
|
||||
|
||||
node = node->wildcard;
|
||||
is_direct = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
unreachable("Invalid array deref type");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable("Invalid deref type");
|
||||
}
|
||||
}
|
||||
|
||||
assert(node);
|
||||
|
||||
/* Only insert if it isn't already in the list. */
|
||||
if (is_direct && state->add_to_direct_deref_nodes &&
|
||||
node->direct_derefs_link.next == NULL) {
|
||||
node->deref = deref;
|
||||
assert(deref->var != NULL);
|
||||
exec_list_push_tail(&state->direct_deref_nodes,
|
||||
&node->direct_derefs_link);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/* \sa foreach_deref_node_match */
|
||||
static bool
|
||||
foreach_deref_node_worker(struct deref_node *node, nir_deref *deref,
|
||||
bool (* cb)(struct deref_node *node,
|
||||
struct lower_variables_state *state),
|
||||
struct lower_variables_state *state)
|
||||
{
|
||||
if (deref->child == NULL) {
|
||||
return cb(node, state);
|
||||
} else {
|
||||
switch (deref->child->deref_type) {
|
||||
case nir_deref_type_array: {
|
||||
nir_deref_array *arr = nir_deref_as_array(deref->child);
|
||||
assert(arr->deref_array_type == nir_deref_array_type_direct);
|
||||
if (node->children[arr->base_offset] &&
|
||||
!foreach_deref_node_worker(node->children[arr->base_offset],
|
||||
deref->child, cb, state))
|
||||
return false;
|
||||
|
||||
if (node->wildcard &&
|
||||
!foreach_deref_node_worker(node->wildcard,
|
||||
deref->child, cb, state))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case nir_deref_type_struct: {
|
||||
nir_deref_struct *str = nir_deref_as_struct(deref->child);
|
||||
return foreach_deref_node_worker(node->children[str->index],
|
||||
deref->child, cb, state);
|
||||
}
|
||||
|
||||
default:
|
||||
unreachable("Invalid deref child type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Walks over every "matching" deref_node and calls the callback. A node
|
||||
* is considered to "match" if either refers to that deref or matches up t
|
||||
* a wildcard. In other words, the following would match a[6].foo[3].bar:
|
||||
*
|
||||
* a[6].foo[3].bar
|
||||
* a[*].foo[3].bar
|
||||
* a[6].foo[*].bar
|
||||
* a[*].foo[*].bar
|
||||
*
|
||||
* The given deref must be a full-length and fully qualified (no wildcards
|
||||
* or indirects) deref chain.
|
||||
*/
|
||||
static bool
|
||||
foreach_deref_node_match(nir_deref_var *deref,
|
||||
bool (* cb)(struct deref_node *node,
|
||||
struct lower_variables_state *state),
|
||||
struct lower_variables_state *state)
|
||||
{
|
||||
nir_deref_var var_deref = *deref;
|
||||
var_deref.deref.child = NULL;
|
||||
struct deref_node *node = get_deref_node(&var_deref, state);
|
||||
|
||||
if (node == NULL)
|
||||
return false;
|
||||
|
||||
return foreach_deref_node_worker(node, &deref->deref, cb, state);
|
||||
}
|
||||
|
||||
/* \sa deref_may_be_aliased */
|
||||
static bool
|
||||
deref_may_be_aliased_node(struct deref_node *node, nir_deref *deref,
|
||||
struct lower_variables_state *state)
|
||||
{
|
||||
if (deref->child == NULL) {
|
||||
return false;
|
||||
} else {
|
||||
switch (deref->child->deref_type) {
|
||||
case nir_deref_type_array: {
|
||||
nir_deref_array *arr = nir_deref_as_array(deref->child);
|
||||
if (arr->deref_array_type == nir_deref_array_type_indirect)
|
||||
return true;
|
||||
|
||||
/* If there is an indirect at this level, we're aliased. */
|
||||
if (node->indirect)
|
||||
return true;
|
||||
|
||||
assert(arr->deref_array_type == nir_deref_array_type_direct);
|
||||
|
||||
if (node->children[arr->base_offset] &&
|
||||
deref_may_be_aliased_node(node->children[arr->base_offset],
|
||||
deref->child, state))
|
||||
return true;
|
||||
|
||||
if (node->wildcard &&
|
||||
deref_may_be_aliased_node(node->wildcard, deref->child, state))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
case nir_deref_type_struct: {
|
||||
nir_deref_struct *str = nir_deref_as_struct(deref->child);
|
||||
if (node->children[str->index]) {
|
||||
return deref_may_be_aliased_node(node->children[str->index],
|
||||
deref->child, state);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
unreachable("Invalid nir_deref child type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns true if there are no indirects that can ever touch this deref.
|
||||
*
|
||||
* For example, if the given deref is a[6].foo, then any uses of a[i].foo
|
||||
* would cause this to return false, but a[i].bar would not affect it
|
||||
* because it's a different structure member. A var_copy involving of
|
||||
* a[*].bar also doesn't affect it because that can be lowered to entirely
|
||||
* direct load/stores.
|
||||
*
|
||||
* We only support asking this question about fully-qualified derefs.
|
||||
* Obviously, it's pointless to ask this about indirects, but we also
|
||||
* rule-out wildcards. Handling Wildcard dereferences would involve
|
||||
* checking each array index to make sure that there aren't any indirect
|
||||
* references.
|
||||
*/
|
||||
static bool
|
||||
deref_may_be_aliased(nir_deref_var *deref,
|
||||
struct lower_variables_state *state)
|
||||
{
|
||||
return deref_may_be_aliased_node(get_deref_node_for_var(deref->var, state),
|
||||
&deref->deref, state);
|
||||
}
|
||||
|
||||
static void
|
||||
register_load_instr(nir_intrinsic_instr *load_instr,
|
||||
struct lower_variables_state *state)
|
||||
{
|
||||
struct deref_node *node = get_deref_node(load_instr->variables[0], state);
|
||||
if (node == NULL)
|
||||
return;
|
||||
|
||||
if (node->loads == NULL)
|
||||
node->loads = _mesa_set_create(state->dead_ctx, _mesa_hash_pointer,
|
||||
_mesa_key_pointer_equal);
|
||||
|
||||
_mesa_set_add(node->loads, load_instr);
|
||||
}
|
||||
|
||||
static void
|
||||
register_store_instr(nir_intrinsic_instr *store_instr,
|
||||
struct lower_variables_state *state)
|
||||
{
|
||||
struct deref_node *node = get_deref_node(store_instr->variables[0], state);
|
||||
if (node == NULL)
|
||||
return;
|
||||
|
||||
if (node->stores == NULL)
|
||||
node->stores = _mesa_set_create(state->dead_ctx, _mesa_hash_pointer,
|
||||
_mesa_key_pointer_equal);
|
||||
|
||||
_mesa_set_add(node->stores, store_instr);
|
||||
}
|
||||
|
||||
static void
|
||||
register_copy_instr(nir_intrinsic_instr *copy_instr,
|
||||
struct lower_variables_state *state)
|
||||
{
|
||||
for (unsigned idx = 0; idx < 2; idx++) {
|
||||
struct deref_node *node =
|
||||
get_deref_node(copy_instr->variables[idx], state);
|
||||
|
||||
if (node == NULL)
|
||||
continue;
|
||||
|
||||
if (node->copies == NULL)
|
||||
node->copies = _mesa_set_create(state->dead_ctx, _mesa_hash_pointer,
|
||||
_mesa_key_pointer_equal);
|
||||
|
||||
_mesa_set_add(node->copies, copy_instr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Registers all variable uses in the given block. */
|
||||
static bool
|
||||
register_variable_uses_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct lower_variables_state *state = void_state;
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type != nir_instr_type_intrinsic)
|
||||
continue;
|
||||
|
||||
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
|
||||
|
||||
switch (intrin->intrinsic) {
|
||||
case nir_intrinsic_load_var:
|
||||
register_load_instr(intrin, state);
|
||||
break;
|
||||
|
||||
case nir_intrinsic_store_var:
|
||||
register_store_instr(intrin, state);
|
||||
break;
|
||||
|
||||
case nir_intrinsic_copy_var:
|
||||
register_copy_instr(intrin, state);
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Walks over all of the copy instructions to or from the given deref_node
|
||||
* and lowers them to load/store intrinsics.
|
||||
*/
|
||||
static bool
|
||||
lower_copies_to_load_store(struct deref_node *node,
|
||||
struct lower_variables_state *state)
|
||||
{
|
||||
if (!node->copies)
|
||||
return true;
|
||||
|
||||
struct set_entry *copy_entry;
|
||||
set_foreach(node->copies, copy_entry) {
|
||||
nir_intrinsic_instr *copy = (void *)copy_entry->key;
|
||||
|
||||
nir_lower_var_copy_instr(copy, state->shader);
|
||||
|
||||
for (unsigned i = 0; i < 2; ++i) {
|
||||
struct deref_node *arg_node =
|
||||
get_deref_node(copy->variables[i], state);
|
||||
|
||||
/* Only bother removing copy entries for other nodes */
|
||||
if (arg_node == NULL || arg_node == node)
|
||||
continue;
|
||||
|
||||
struct set_entry *arg_entry = _mesa_set_search(arg_node->copies, copy);
|
||||
assert(arg_entry);
|
||||
_mesa_set_remove(node->copies, arg_entry);
|
||||
}
|
||||
|
||||
nir_instr_remove(©->instr);
|
||||
}
|
||||
|
||||
node->copies = NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Pushes an SSA def onto the def stack for the given node
|
||||
*
|
||||
* Each node is potentially associated with a stack of SSA definitions.
|
||||
* This stack is used for determining what SSA definition reaches a given
|
||||
* point in the program for variable renaming. The stack is always kept in
|
||||
* dominance-order with at most one SSA def per block. If the SSA
|
||||
* definition on the top of the stack is in the same block as the one being
|
||||
* pushed, the top element is replaced.
|
||||
*/
|
||||
static void
|
||||
def_stack_push(struct deref_node *node, nir_ssa_def *def,
|
||||
struct lower_variables_state *state)
|
||||
{
|
||||
if (node->def_stack == NULL) {
|
||||
node->def_stack = ralloc_array(state->dead_ctx, nir_ssa_def *,
|
||||
state->impl->num_blocks);
|
||||
node->def_stack_tail = node->def_stack - 1;
|
||||
}
|
||||
|
||||
if (node->def_stack_tail >= node->def_stack) {
|
||||
nir_ssa_def *top_def = *node->def_stack_tail;
|
||||
|
||||
if (def->parent_instr->block == top_def->parent_instr->block) {
|
||||
/* They're in the same block, just replace the top */
|
||||
*node->def_stack_tail = def;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
*(++node->def_stack_tail) = def;
|
||||
}
|
||||
|
||||
/* Pop the top of the def stack if it's in the given block */
|
||||
static void
|
||||
def_stack_pop_if_in_block(struct deref_node *node, nir_block *block)
|
||||
{
|
||||
/* If we're popping, then we have presumably pushed at some time in the
|
||||
* past so this should exist.
|
||||
*/
|
||||
assert(node->def_stack != NULL);
|
||||
|
||||
/* The stack is already empty. Do nothing. */
|
||||
if (node->def_stack_tail < node->def_stack)
|
||||
return;
|
||||
|
||||
nir_ssa_def *def = *node->def_stack_tail;
|
||||
if (def->parent_instr->block == block)
|
||||
node->def_stack_tail--;
|
||||
}
|
||||
|
||||
/** Retrieves the SSA definition on the top of the stack for the given
|
||||
* node, if one exists. If the stack is empty, then we return the constant
|
||||
* initializer (if it exists) or an SSA undef.
|
||||
*/
|
||||
static nir_ssa_def *
|
||||
get_ssa_def_for_block(struct deref_node *node, nir_block *block,
|
||||
struct lower_variables_state *state)
|
||||
{
|
||||
/* If we have something on the stack, go ahead and return it. We're
|
||||
* assuming that the top of the stack dominates the given block.
|
||||
*/
|
||||
if (node->def_stack && node->def_stack_tail >= node->def_stack)
|
||||
return *node->def_stack_tail;
|
||||
|
||||
/* If we got here then we don't have a definition that dominates the
|
||||
* given block. This means that we need to add an undef and use that.
|
||||
*/
|
||||
nir_ssa_undef_instr *undef =
|
||||
nir_ssa_undef_instr_create(state->shader,
|
||||
glsl_get_vector_elements(node->type));
|
||||
nir_instr_insert_before_cf_list(&state->impl->body, &undef->instr);
|
||||
def_stack_push(node, &undef->def, state);
|
||||
return &undef->def;
|
||||
}
|
||||
|
||||
/* Given a block and one of its predecessors, this function fills in the
|
||||
* souces of the phi nodes to take SSA defs from the given predecessor.
|
||||
* This function must be called exactly once per block/predecessor pair.
|
||||
*/
|
||||
static void
|
||||
add_phi_sources(nir_block *block, nir_block *pred,
|
||||
struct lower_variables_state *state)
|
||||
{
|
||||
nir_foreach_instr(block, instr) {
|
||||
if (instr->type != nir_instr_type_phi)
|
||||
break;
|
||||
|
||||
nir_phi_instr *phi = nir_instr_as_phi(instr);
|
||||
|
||||
struct hash_entry *entry =
|
||||
_mesa_hash_table_search(state->phi_table, phi);
|
||||
if (!entry)
|
||||
continue;
|
||||
|
||||
struct deref_node *node = entry->data;
|
||||
|
||||
nir_phi_src *src = ralloc(phi, nir_phi_src);
|
||||
src->pred = pred;
|
||||
src->src.parent_instr = &phi->instr;
|
||||
src->src.is_ssa = true;
|
||||
src->src.ssa = get_ssa_def_for_block(node, pred, state);
|
||||
|
||||
list_addtail(&src->src.use_link, &src->src.ssa->uses);
|
||||
|
||||
exec_list_push_tail(&phi->srcs, &src->node);
|
||||
}
|
||||
}
|
||||
|
||||
/* Performs variable renaming by doing a DFS of the dominance tree
|
||||
*
|
||||
* This algorithm is very similar to the one outlined in "Efficiently
|
||||
* Computing Static Single Assignment Form and the Control Dependence
|
||||
* Graph" by Cytron et. al. The primary difference is that we only put one
|
||||
* SSA def on the stack per block.
|
||||
*/
|
||||
static bool
|
||||
rename_variables_block(nir_block *block, struct lower_variables_state *state)
|
||||
{
|
||||
nir_builder b;
|
||||
nir_builder_init(&b, state->impl);
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type == nir_instr_type_phi) {
|
||||
nir_phi_instr *phi = nir_instr_as_phi(instr);
|
||||
|
||||
struct hash_entry *entry =
|
||||
_mesa_hash_table_search(state->phi_table, phi);
|
||||
|
||||
/* This can happen if we already have phi nodes in the program
|
||||
* that were not created in this pass.
|
||||
*/
|
||||
if (!entry)
|
||||
continue;
|
||||
|
||||
struct deref_node *node = entry->data;
|
||||
|
||||
def_stack_push(node, &phi->dest.ssa, state);
|
||||
} else if (instr->type == nir_instr_type_intrinsic) {
|
||||
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
|
||||
|
||||
switch (intrin->intrinsic) {
|
||||
case nir_intrinsic_load_var: {
|
||||
struct deref_node *node =
|
||||
get_deref_node(intrin->variables[0], state);
|
||||
|
||||
if (node == NULL) {
|
||||
/* If we hit this path then we are referencing an invalid
|
||||
* value. Most likely, we unrolled something and are
|
||||
* reading past the end of some array. In any case, this
|
||||
* should result in an undefined value.
|
||||
*/
|
||||
nir_ssa_undef_instr *undef =
|
||||
nir_ssa_undef_instr_create(state->shader,
|
||||
intrin->num_components);
|
||||
|
||||
nir_instr_insert_before(&intrin->instr, &undef->instr);
|
||||
nir_instr_remove(&intrin->instr);
|
||||
|
||||
nir_ssa_def_rewrite_uses(&intrin->dest.ssa,
|
||||
nir_src_for_ssa(&undef->def));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!node->lower_to_ssa)
|
||||
continue;
|
||||
|
||||
nir_alu_instr *mov = nir_alu_instr_create(state->shader,
|
||||
nir_op_imov);
|
||||
mov->src[0].src.is_ssa = true;
|
||||
mov->src[0].src.ssa = get_ssa_def_for_block(node, block, state);
|
||||
for (unsigned i = intrin->num_components; i < 4; i++)
|
||||
mov->src[0].swizzle[i] = 0;
|
||||
|
||||
assert(intrin->dest.is_ssa);
|
||||
|
||||
mov->dest.write_mask = (1 << intrin->num_components) - 1;
|
||||
nir_ssa_dest_init(&mov->instr, &mov->dest.dest,
|
||||
intrin->num_components, NULL);
|
||||
|
||||
nir_instr_insert_before(&intrin->instr, &mov->instr);
|
||||
nir_instr_remove(&intrin->instr);
|
||||
|
||||
nir_ssa_def_rewrite_uses(&intrin->dest.ssa,
|
||||
nir_src_for_ssa(&mov->dest.dest.ssa));
|
||||
break;
|
||||
}
|
||||
|
||||
case nir_intrinsic_store_var: {
|
||||
struct deref_node *node =
|
||||
get_deref_node(intrin->variables[0], state);
|
||||
|
||||
if (node == NULL) {
|
||||
/* Probably an out-of-bounds array store. That should be a
|
||||
* no-op. */
|
||||
nir_instr_remove(&intrin->instr);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!node->lower_to_ssa)
|
||||
continue;
|
||||
|
||||
assert(intrin->num_components ==
|
||||
glsl_get_vector_elements(node->type));
|
||||
|
||||
assert(intrin->src[0].is_ssa);
|
||||
|
||||
nir_ssa_def *new_def;
|
||||
b.cursor = nir_before_instr(&intrin->instr);
|
||||
|
||||
if (intrin->const_index[0] == (1 << intrin->num_components) - 1) {
|
||||
/* Whole variable store - just copy the source. Note that
|
||||
* intrin->num_components and intrin->src[0].ssa->num_components
|
||||
* may differ.
|
||||
*/
|
||||
unsigned swiz[4];
|
||||
for (unsigned i = 0; i < 4; i++)
|
||||
swiz[i] = i < intrin->num_components ? i : 0;
|
||||
|
||||
new_def = nir_swizzle(&b, intrin->src[0].ssa, swiz,
|
||||
intrin->num_components, false);
|
||||
} else {
|
||||
nir_ssa_def *old_def = get_ssa_def_for_block(node, block, state);
|
||||
/* For writemasked store_var intrinsics, we combine the newly
|
||||
* written values with the existing contents of unwritten
|
||||
* channels, creating a new SSA value for the whole vector.
|
||||
*/
|
||||
nir_ssa_def *srcs[4];
|
||||
for (unsigned i = 0; i < intrin->num_components; i++) {
|
||||
if (intrin->const_index[0] & (1 << i)) {
|
||||
srcs[i] = nir_channel(&b, intrin->src[0].ssa, i);
|
||||
} else {
|
||||
srcs[i] = nir_channel(&b, old_def, i);
|
||||
}
|
||||
}
|
||||
new_def = nir_vec(&b, srcs, intrin->num_components);
|
||||
}
|
||||
|
||||
assert(new_def->num_components == intrin->num_components);
|
||||
|
||||
def_stack_push(node, new_def, state);
|
||||
|
||||
/* We'll wait to remove the instruction until the next pass
|
||||
* where we pop the node we just pushed back off the stack.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (block->successors[0])
|
||||
add_phi_sources(block->successors[0], block, state);
|
||||
if (block->successors[1])
|
||||
add_phi_sources(block->successors[1], block, state);
|
||||
|
||||
for (unsigned i = 0; i < block->num_dom_children; ++i)
|
||||
rename_variables_block(block->dom_children[i], state);
|
||||
|
||||
/* Now we iterate over the instructions and pop off any SSA defs that we
|
||||
* pushed in the first loop.
|
||||
*/
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type == nir_instr_type_phi) {
|
||||
nir_phi_instr *phi = nir_instr_as_phi(instr);
|
||||
|
||||
struct hash_entry *entry =
|
||||
_mesa_hash_table_search(state->phi_table, phi);
|
||||
|
||||
/* This can happen if we already have phi nodes in the program
|
||||
* that were not created in this pass.
|
||||
*/
|
||||
if (!entry)
|
||||
continue;
|
||||
|
||||
struct deref_node *node = entry->data;
|
||||
|
||||
def_stack_pop_if_in_block(node, block);
|
||||
} else if (instr->type == nir_instr_type_intrinsic) {
|
||||
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
|
||||
|
||||
if (intrin->intrinsic != nir_intrinsic_store_var)
|
||||
continue;
|
||||
|
||||
struct deref_node *node = get_deref_node(intrin->variables[0], state);
|
||||
if (!node)
|
||||
continue;
|
||||
|
||||
if (!node->lower_to_ssa)
|
||||
continue;
|
||||
|
||||
def_stack_pop_if_in_block(node, block);
|
||||
nir_instr_remove(&intrin->instr);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Inserts phi nodes for all variables marked lower_to_ssa
|
||||
*
|
||||
* This is the same algorithm as presented in "Efficiently Computing Static
|
||||
* Single Assignment Form and the Control Dependence Graph" by Cytron et.
|
||||
* al.
|
||||
*/
|
||||
static void
|
||||
insert_phi_nodes(struct lower_variables_state *state)
|
||||
{
|
||||
NIR_VLA_ZERO(unsigned, work, state->impl->num_blocks);
|
||||
NIR_VLA_ZERO(unsigned, has_already, state->impl->num_blocks);
|
||||
|
||||
/*
|
||||
* Since the work flags already prevent us from inserting a node that has
|
||||
* ever been inserted into W, we don't need to use a set to represent W.
|
||||
* Also, since no block can ever be inserted into W more than once, we know
|
||||
* that the maximum size of W is the number of basic blocks in the
|
||||
* function. So all we need to handle W is an array and a pointer to the
|
||||
* next element to be inserted and the next element to be removed.
|
||||
*/
|
||||
NIR_VLA(nir_block *, W, state->impl->num_blocks);
|
||||
|
||||
unsigned w_start, w_end;
|
||||
unsigned iter_count = 0;
|
||||
|
||||
foreach_list_typed(struct deref_node, node, direct_derefs_link,
|
||||
&state->direct_deref_nodes) {
|
||||
if (node->stores == NULL)
|
||||
continue;
|
||||
|
||||
if (!node->lower_to_ssa)
|
||||
continue;
|
||||
|
||||
w_start = w_end = 0;
|
||||
iter_count++;
|
||||
|
||||
struct set_entry *store_entry;
|
||||
set_foreach(node->stores, store_entry) {
|
||||
nir_intrinsic_instr *store = (nir_intrinsic_instr *)store_entry->key;
|
||||
if (work[store->instr.block->index] < iter_count)
|
||||
W[w_end++] = store->instr.block;
|
||||
work[store->instr.block->index] = iter_count;
|
||||
}
|
||||
|
||||
while (w_start != w_end) {
|
||||
nir_block *cur = W[w_start++];
|
||||
struct set_entry *dom_entry;
|
||||
set_foreach(cur->dom_frontier, dom_entry) {
|
||||
nir_block *next = (nir_block *) dom_entry->key;
|
||||
|
||||
/*
|
||||
* If there's more than one return statement, then the end block
|
||||
* can be a join point for some definitions. However, there are
|
||||
* no instructions in the end block, so nothing would use those
|
||||
* phi nodes. Of course, we couldn't place those phi nodes
|
||||
* anyways due to the restriction of having no instructions in the
|
||||
* end block...
|
||||
*/
|
||||
if (next == state->impl->end_block)
|
||||
continue;
|
||||
|
||||
if (has_already[next->index] < iter_count) {
|
||||
nir_phi_instr *phi = nir_phi_instr_create(state->shader);
|
||||
nir_ssa_dest_init(&phi->instr, &phi->dest,
|
||||
glsl_get_vector_elements(node->type), NULL);
|
||||
nir_instr_insert_before_block(next, &phi->instr);
|
||||
|
||||
_mesa_hash_table_insert(state->phi_table, phi, node);
|
||||
|
||||
has_already[next->index] = iter_count;
|
||||
if (work[next->index] < iter_count) {
|
||||
work[next->index] = iter_count;
|
||||
W[w_end++] = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Implements a pass to lower variable uses to SSA values
|
||||
*
|
||||
* This path walks the list of instructions and tries to lower as many
|
||||
* local variable load/store operations to SSA defs and uses as it can.
|
||||
* The process involves four passes:
|
||||
*
|
||||
* 1) Iterate over all of the instructions and mark where each local
|
||||
* variable deref is used in a load, store, or copy. While we're at
|
||||
* it, we keep track of all of the fully-qualified (no wildcards) and
|
||||
* fully-direct references we see and store them in the
|
||||
* direct_deref_nodes hash table.
|
||||
*
|
||||
* 2) Walk over the the list of fully-qualified direct derefs generated in
|
||||
* the previous pass. For each deref, we determine if it can ever be
|
||||
* aliased, i.e. if there is an indirect reference anywhere that may
|
||||
* refer to it. If it cannot be aliased, we mark it for lowering to an
|
||||
* SSA value. At this point, we lower any var_copy instructions that
|
||||
* use the given deref to load/store operations and, if the deref has a
|
||||
* constant initializer, we go ahead and add a load_const value at the
|
||||
* beginning of the function with the initialized value.
|
||||
*
|
||||
* 3) Walk over the list of derefs we plan to lower to SSA values and
|
||||
* insert phi nodes as needed.
|
||||
*
|
||||
* 4) Perform "variable renaming" by replacing the load/store instructions
|
||||
* with SSA definitions and SSA uses.
|
||||
*/
|
||||
static bool
|
||||
nir_lower_vars_to_ssa_impl(nir_function_impl *impl)
|
||||
{
|
||||
struct lower_variables_state state;
|
||||
|
||||
state.shader = impl->function->shader;
|
||||
state.dead_ctx = ralloc_context(state.shader);
|
||||
state.impl = impl;
|
||||
|
||||
state.deref_var_nodes = _mesa_hash_table_create(state.dead_ctx,
|
||||
_mesa_hash_pointer,
|
||||
_mesa_key_pointer_equal);
|
||||
exec_list_make_empty(&state.direct_deref_nodes);
|
||||
state.phi_table = _mesa_hash_table_create(state.dead_ctx,
|
||||
_mesa_hash_pointer,
|
||||
_mesa_key_pointer_equal);
|
||||
|
||||
/* Build the initial deref structures and direct_deref_nodes table */
|
||||
state.add_to_direct_deref_nodes = true;
|
||||
nir_foreach_block(impl, register_variable_uses_block, &state);
|
||||
|
||||
bool progress = false;
|
||||
|
||||
nir_metadata_require(impl, nir_metadata_block_index);
|
||||
|
||||
/* We're about to iterate through direct_deref_nodes. Don't modify it. */
|
||||
state.add_to_direct_deref_nodes = false;
|
||||
|
||||
foreach_list_typed_safe(struct deref_node, node, direct_derefs_link,
|
||||
&state.direct_deref_nodes) {
|
||||
nir_deref_var *deref = node->deref;
|
||||
|
||||
if (deref->var->data.mode != nir_var_local) {
|
||||
exec_node_remove(&node->direct_derefs_link);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (deref_may_be_aliased(deref, &state)) {
|
||||
exec_node_remove(&node->direct_derefs_link);
|
||||
continue;
|
||||
}
|
||||
|
||||
node->lower_to_ssa = true;
|
||||
progress = true;
|
||||
|
||||
if (deref->var->constant_initializer) {
|
||||
nir_load_const_instr *load =
|
||||
nir_deref_get_const_initializer_load(state.shader, deref);
|
||||
nir_ssa_def_init(&load->instr, &load->def,
|
||||
glsl_get_vector_elements(node->type), NULL);
|
||||
nir_instr_insert_before_cf_list(&impl->body, &load->instr);
|
||||
def_stack_push(node, &load->def, &state);
|
||||
}
|
||||
|
||||
foreach_deref_node_match(deref, lower_copies_to_load_store, &state);
|
||||
}
|
||||
|
||||
if (!progress)
|
||||
return false;
|
||||
|
||||
nir_metadata_require(impl, nir_metadata_dominance);
|
||||
|
||||
/* We may have lowered some copy instructions to load/store
|
||||
* instructions. The uses from the copy instructions hav already been
|
||||
* removed but we need to rescan to ensure that the uses from the newly
|
||||
* added load/store instructions are registered. We need this
|
||||
* information for phi node insertion below.
|
||||
*/
|
||||
nir_foreach_block(impl, register_variable_uses_block, &state);
|
||||
|
||||
insert_phi_nodes(&state);
|
||||
rename_variables_block(nir_start_block(impl), &state);
|
||||
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
|
||||
ralloc_free(state.dead_ctx);
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
void
|
||||
nir_lower_vars_to_ssa(nir_shader *shader)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
nir_lower_vars_to_ssa_impl(function->impl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
/*
|
||||
* Implements a simple pass that lowers vecN instructions to a series of
|
||||
* moves with partial writes.
|
||||
*/
|
||||
|
||||
struct vec_to_movs_state {
|
||||
nir_function_impl *impl;
|
||||
bool progress;
|
||||
};
|
||||
|
||||
static bool
|
||||
src_matches_dest_reg(nir_dest *dest, nir_src *src)
|
||||
{
|
||||
if (dest->is_ssa || src->is_ssa)
|
||||
return false;
|
||||
|
||||
return (dest->reg.reg == src->reg.reg &&
|
||||
dest->reg.base_offset == src->reg.base_offset &&
|
||||
!dest->reg.indirect &&
|
||||
!src->reg.indirect);
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given starting writemask channel and corresponding source index in
|
||||
* the vec instruction, insert a MOV to the vec instruction's dest of all the
|
||||
* writemask channels that get read from the same src reg.
|
||||
*
|
||||
* Returns the writemask of our MOV, so the parent loop calling this knows
|
||||
* which ones have been processed.
|
||||
*/
|
||||
static unsigned
|
||||
insert_mov(nir_alu_instr *vec, unsigned start_idx, nir_shader *shader)
|
||||
{
|
||||
assert(start_idx < nir_op_infos[vec->op].num_inputs);
|
||||
|
||||
nir_alu_instr *mov = nir_alu_instr_create(shader, nir_op_imov);
|
||||
nir_alu_src_copy(&mov->src[0], &vec->src[start_idx], mov);
|
||||
nir_alu_dest_copy(&mov->dest, &vec->dest, mov);
|
||||
|
||||
mov->dest.write_mask = (1u << start_idx);
|
||||
mov->src[0].swizzle[start_idx] = vec->src[start_idx].swizzle[0];
|
||||
mov->src[0].negate = vec->src[start_idx].negate;
|
||||
mov->src[0].abs = vec->src[start_idx].abs;
|
||||
|
||||
for (unsigned i = start_idx + 1; i < 4; i++) {
|
||||
if (!(vec->dest.write_mask & (1 << i)))
|
||||
continue;
|
||||
|
||||
if (nir_srcs_equal(vec->src[i].src, vec->src[start_idx].src) &&
|
||||
vec->src[i].negate == vec->src[start_idx].negate &&
|
||||
vec->src[i].abs == vec->src[start_idx].abs) {
|
||||
mov->dest.write_mask |= (1 << i);
|
||||
mov->src[0].swizzle[i] = vec->src[i].swizzle[0];
|
||||
}
|
||||
}
|
||||
|
||||
/* In some situations (if the vecN is involved in a phi-web), we can end
|
||||
* up with a mov from a register to itself. Some of those channels may end
|
||||
* up doing nothing and there's no reason to have them as part of the mov.
|
||||
*/
|
||||
if (src_matches_dest_reg(&mov->dest.dest, &mov->src[0].src) &&
|
||||
!mov->src[0].abs && !mov->src[0].negate) {
|
||||
for (unsigned i = 0; i < 4; i++) {
|
||||
if (mov->src[0].swizzle[i] == i) {
|
||||
mov->dest.write_mask &= ~(1 << i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Only emit the instruction if it actually does something */
|
||||
if (mov->dest.write_mask) {
|
||||
nir_instr_insert_before(&vec->instr, &mov->instr);
|
||||
} else {
|
||||
ralloc_free(mov);
|
||||
}
|
||||
|
||||
return mov->dest.write_mask;
|
||||
}
|
||||
|
||||
static bool
|
||||
has_replicated_dest(nir_alu_instr *alu)
|
||||
{
|
||||
return alu->op == nir_op_fdot_replicated2 ||
|
||||
alu->op == nir_op_fdot_replicated3 ||
|
||||
alu->op == nir_op_fdot_replicated4 ||
|
||||
alu->op == nir_op_fdph_replicated;
|
||||
}
|
||||
|
||||
/* Attempts to coalesce the "move" from the given source of the vec to the
|
||||
* destination of the instruction generating the value. If, for whatever
|
||||
* reason, we cannot coalesce the mmove, it does nothing and returns 0. We
|
||||
* can then call insert_mov as normal.
|
||||
*/
|
||||
static unsigned
|
||||
try_coalesce(nir_alu_instr *vec, unsigned start_idx, nir_shader *shader)
|
||||
{
|
||||
assert(start_idx < nir_op_infos[vec->op].num_inputs);
|
||||
|
||||
/* We will only even try if the source is SSA */
|
||||
if (!vec->src[start_idx].src.is_ssa)
|
||||
return 0;
|
||||
|
||||
assert(vec->src[start_idx].src.ssa);
|
||||
|
||||
/* If we are going to do a reswizzle, then the vecN operation must be the
|
||||
* only use of the source value. We also can't have any source modifiers.
|
||||
*/
|
||||
nir_foreach_use(vec->src[start_idx].src.ssa, src) {
|
||||
if (src->parent_instr != &vec->instr)
|
||||
return 0;
|
||||
|
||||
nir_alu_src *alu_src = exec_node_data(nir_alu_src, src, src);
|
||||
if (alu_src->abs || alu_src->negate)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!list_empty(&vec->src[start_idx].src.ssa->if_uses))
|
||||
return 0;
|
||||
|
||||
if (vec->src[start_idx].src.ssa->parent_instr->type != nir_instr_type_alu)
|
||||
return 0;
|
||||
|
||||
nir_alu_instr *src_alu =
|
||||
nir_instr_as_alu(vec->src[start_idx].src.ssa->parent_instr);
|
||||
|
||||
if (has_replicated_dest(src_alu)) {
|
||||
/* The fdot instruction is special: It replicates its result to all
|
||||
* components. This means that we can always rewrite its destination
|
||||
* and we don't need to swizzle anything.
|
||||
*/
|
||||
} else {
|
||||
/* We only care about being able to re-swizzle the instruction if it is
|
||||
* something that we can reswizzle. It must be per-component. The one
|
||||
* exception to this is the fdotN instructions which implicitly splat
|
||||
* their result out to all channels.
|
||||
*/
|
||||
if (nir_op_infos[src_alu->op].output_size != 0)
|
||||
return 0;
|
||||
|
||||
/* If we are going to reswizzle the instruction, we can't have any
|
||||
* non-per-component sources either.
|
||||
*/
|
||||
for (unsigned j = 0; j < nir_op_infos[src_alu->op].num_inputs; j++)
|
||||
if (nir_op_infos[src_alu->op].input_sizes[j] != 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Stash off all of the ALU instruction's swizzles. */
|
||||
uint8_t swizzles[4][4];
|
||||
for (unsigned j = 0; j < nir_op_infos[src_alu->op].num_inputs; j++)
|
||||
for (unsigned i = 0; i < 4; i++)
|
||||
swizzles[j][i] = src_alu->src[j].swizzle[i];
|
||||
|
||||
unsigned write_mask = 0;
|
||||
for (unsigned i = start_idx; i < 4; i++) {
|
||||
if (!(vec->dest.write_mask & (1 << i)))
|
||||
continue;
|
||||
|
||||
if (!vec->src[i].src.is_ssa ||
|
||||
vec->src[i].src.ssa != &src_alu->dest.dest.ssa)
|
||||
continue;
|
||||
|
||||
/* At this point, the give vec source matchese up with the ALU
|
||||
* instruction so we can re-swizzle that component to match.
|
||||
*/
|
||||
write_mask |= 1 << i;
|
||||
if (has_replicated_dest(src_alu)) {
|
||||
/* Since the destination is a single replicated value, we don't need
|
||||
* to do any reswizzling
|
||||
*/
|
||||
} else {
|
||||
for (unsigned j = 0; j < nir_op_infos[src_alu->op].num_inputs; j++)
|
||||
src_alu->src[j].swizzle[i] = swizzles[j][vec->src[i].swizzle[0]];
|
||||
}
|
||||
|
||||
/* Clear the no longer needed vec source */
|
||||
nir_instr_rewrite_src(&vec->instr, &vec->src[i].src, NIR_SRC_INIT);
|
||||
}
|
||||
|
||||
nir_instr_rewrite_dest(&src_alu->instr, &src_alu->dest.dest, vec->dest.dest);
|
||||
src_alu->dest.write_mask = write_mask;
|
||||
|
||||
return write_mask;
|
||||
}
|
||||
|
||||
static bool
|
||||
lower_vec_to_movs_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct vec_to_movs_state *state = void_state;
|
||||
nir_function_impl *impl = state->impl;
|
||||
nir_shader *shader = impl->function->shader;
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type != nir_instr_type_alu)
|
||||
continue;
|
||||
|
||||
nir_alu_instr *vec = nir_instr_as_alu(instr);
|
||||
|
||||
switch (vec->op) {
|
||||
case nir_op_vec2:
|
||||
case nir_op_vec3:
|
||||
case nir_op_vec4:
|
||||
break;
|
||||
default:
|
||||
continue; /* The loop */
|
||||
}
|
||||
|
||||
if (vec->dest.dest.is_ssa) {
|
||||
/* Since we insert multiple MOVs, we have a register destination. */
|
||||
nir_register *reg = nir_local_reg_create(impl);
|
||||
reg->num_components = vec->dest.dest.ssa.num_components;
|
||||
|
||||
nir_ssa_def_rewrite_uses(&vec->dest.dest.ssa, nir_src_for_reg(reg));
|
||||
|
||||
nir_instr_rewrite_dest(&vec->instr, &vec->dest.dest,
|
||||
nir_dest_for_reg(reg));
|
||||
}
|
||||
|
||||
unsigned finished_write_mask = 0;
|
||||
|
||||
/* First, emit a MOV for all the src channels that are in the
|
||||
* destination reg, in case other values we're populating in the dest
|
||||
* might overwrite them.
|
||||
*/
|
||||
for (unsigned i = 0; i < 4; i++) {
|
||||
if (!(vec->dest.write_mask & (1 << i)))
|
||||
continue;
|
||||
|
||||
if (src_matches_dest_reg(&vec->dest.dest, &vec->src[i].src)) {
|
||||
finished_write_mask |= insert_mov(vec, i, shader);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now, emit MOVs for all the other src channels. */
|
||||
for (unsigned i = 0; i < 4; i++) {
|
||||
if (!(vec->dest.write_mask & (1 << i)))
|
||||
continue;
|
||||
|
||||
if (!(finished_write_mask & (1 << i)))
|
||||
finished_write_mask |= try_coalesce(vec, i, shader);
|
||||
|
||||
if (!(finished_write_mask & (1 << i)))
|
||||
finished_write_mask |= insert_mov(vec, i, shader);
|
||||
}
|
||||
|
||||
nir_instr_remove(&vec->instr);
|
||||
ralloc_free(vec);
|
||||
state->progress = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
nir_lower_vec_to_movs_impl(nir_function_impl *impl)
|
||||
{
|
||||
struct vec_to_movs_state state = { impl, false };
|
||||
|
||||
nir_foreach_block(impl, lower_vec_to_movs_block, &state);
|
||||
|
||||
if (state.progress) {
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
}
|
||||
|
||||
return state.progress;
|
||||
}
|
||||
|
||||
bool
|
||||
nir_lower_vec_to_movs(nir_shader *shader)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
progress = nir_lower_vec_to_movs_impl(function->impl) || progress;
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
/*
|
||||
* Handles management of the metadata.
|
||||
*/
|
||||
|
||||
void
|
||||
nir_metadata_require(nir_function_impl *impl, nir_metadata required)
|
||||
{
|
||||
#define NEEDS_UPDATE(X) ((required & ~impl->valid_metadata) & (X))
|
||||
|
||||
if (NEEDS_UPDATE(nir_metadata_block_index))
|
||||
nir_index_blocks(impl);
|
||||
if (NEEDS_UPDATE(nir_metadata_dominance))
|
||||
nir_calc_dominance_impl(impl);
|
||||
if (NEEDS_UPDATE(nir_metadata_live_ssa_defs))
|
||||
nir_live_ssa_defs_impl(impl);
|
||||
|
||||
#undef NEEDS_UPDATE
|
||||
|
||||
impl->valid_metadata |= required;
|
||||
}
|
||||
|
||||
void
|
||||
nir_metadata_preserve(nir_function_impl *impl, nir_metadata preserved)
|
||||
{
|
||||
impl->valid_metadata &= preserved;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
/**
|
||||
* Make sure passes properly invalidate metadata (part 1).
|
||||
*
|
||||
* Call this before running a pass to set a bogus metadata flag, which will
|
||||
* only be preserved if the pass forgets to call nir_metadata_preserve().
|
||||
*/
|
||||
void
|
||||
nir_metadata_set_validation_flag(nir_shader *shader)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl) {
|
||||
function->impl->valid_metadata |= nir_metadata_not_properly_reset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure passes properly invalidate metadata (part 2).
|
||||
*
|
||||
* Call this after a pass makes progress to verify that the bogus metadata set by
|
||||
* the earlier function was properly thrown away. Note that passes may not call
|
||||
* nir_metadata_preserve() if they don't actually make any changes at all.
|
||||
*/
|
||||
void
|
||||
nir_metadata_check_validation_flag(nir_shader *shader)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl) {
|
||||
assert(!(function->impl->valid_metadata &
|
||||
nir_metadata_not_properly_reset));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
/*
|
||||
* Implements a pass that tries to move uses vecN sources to their
|
||||
* destinations. This is kind of like an inverse copy-propagation pass.
|
||||
* For instance, if you have
|
||||
*
|
||||
* ssa_1 = vec4(a, b, c, d)
|
||||
* ssa_2 = fadd(a, b)
|
||||
*
|
||||
* This will be turned into
|
||||
*
|
||||
* ssa_1 = vec4(a, b, c, d)
|
||||
* ssa_2 = fadd(ssa_1.x, ssa_1.y)
|
||||
*
|
||||
* While this is "worse" because it adds a bunch of unneeded dependencies, it
|
||||
* actually makes it much easier for vec4-based backends to coalesce the MOV's
|
||||
* that result from the vec4 operation because it doesn't have to worry about
|
||||
* quite as many reads.
|
||||
*/
|
||||
|
||||
/* Returns true if the given SSA def dominates the instruction. An SSA def is
|
||||
* considered to *not* dominate the instruction that defines it.
|
||||
*/
|
||||
static bool
|
||||
ssa_def_dominates_instr(nir_ssa_def *def, nir_instr *instr)
|
||||
{
|
||||
if (instr->index <= def->parent_instr->index) {
|
||||
return false;
|
||||
} else if (def->parent_instr->block == instr->block) {
|
||||
return def->parent_instr->index < instr->index;
|
||||
} else {
|
||||
return nir_block_dominates(def->parent_instr->block, instr->block);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
move_vec_src_uses_to_dest_block(nir_block *block, void *shader)
|
||||
{
|
||||
nir_foreach_instr(block, instr) {
|
||||
if (instr->type != nir_instr_type_alu)
|
||||
continue;
|
||||
|
||||
nir_alu_instr *vec = nir_instr_as_alu(instr);
|
||||
|
||||
switch (vec->op) {
|
||||
case nir_op_vec2:
|
||||
case nir_op_vec3:
|
||||
case nir_op_vec4:
|
||||
break;
|
||||
default:
|
||||
continue; /* The loop */
|
||||
}
|
||||
|
||||
/* Can't handle non-SSA vec operations */
|
||||
if (!vec->dest.dest.is_ssa)
|
||||
continue;
|
||||
|
||||
/* Can't handle saturation */
|
||||
if (vec->dest.saturate)
|
||||
continue;
|
||||
|
||||
/* First, mark all of the sources we are going to consider for rewriting
|
||||
* to the destination
|
||||
*/
|
||||
int srcs_remaining = 0;
|
||||
for (unsigned i = 0; i < nir_op_infos[vec->op].num_inputs; i++) {
|
||||
/* We can't rewrite a source if it's not in SSA form */
|
||||
if (!vec->src[i].src.is_ssa)
|
||||
continue;
|
||||
|
||||
/* We can't rewrite a source if it has modifiers */
|
||||
if (vec->src[i].abs || vec->src[i].negate)
|
||||
continue;
|
||||
|
||||
srcs_remaining |= 1 << i;
|
||||
}
|
||||
|
||||
/* We can't actually do anything with this instruction */
|
||||
if (srcs_remaining == 0)
|
||||
continue;
|
||||
|
||||
for (unsigned i; i = ffs(srcs_remaining) - 1, srcs_remaining;) {
|
||||
int8_t swizzle[4] = { -1, -1, -1, -1 };
|
||||
|
||||
for (unsigned j = i; j < nir_op_infos[vec->op].num_inputs; j++) {
|
||||
if (vec->src[j].src.ssa != vec->src[i].src.ssa)
|
||||
continue;
|
||||
|
||||
/* Mark the given chanle as having been handled */
|
||||
srcs_remaining &= ~(1 << j);
|
||||
|
||||
/* Mark the appropreate channel as coming from src j */
|
||||
swizzle[vec->src[j].swizzle[0]] = j;
|
||||
}
|
||||
|
||||
nir_foreach_use_safe(vec->src[i].src.ssa, use) {
|
||||
if (use->parent_instr == &vec->instr)
|
||||
continue;
|
||||
|
||||
/* We need to dominate the use if we are going to rewrite it */
|
||||
if (!ssa_def_dominates_instr(&vec->dest.dest.ssa, use->parent_instr))
|
||||
continue;
|
||||
|
||||
/* For now, we'll just rewrite ALU instructions */
|
||||
if (use->parent_instr->type != nir_instr_type_alu)
|
||||
continue;
|
||||
|
||||
assert(use->is_ssa);
|
||||
|
||||
nir_alu_instr *use_alu = nir_instr_as_alu(use->parent_instr);
|
||||
|
||||
/* Figure out which source we're actually looking at */
|
||||
nir_alu_src *use_alu_src = exec_node_data(nir_alu_src, use, src);
|
||||
unsigned src_idx = use_alu_src - use_alu->src;
|
||||
assert(src_idx < nir_op_infos[use_alu->op].num_inputs);
|
||||
|
||||
bool can_reswizzle = true;
|
||||
for (unsigned j = 0; j < 4; j++) {
|
||||
if (!nir_alu_instr_channel_used(use_alu, src_idx, j))
|
||||
continue;
|
||||
|
||||
if (swizzle[use_alu_src->swizzle[j]] == -1) {
|
||||
can_reswizzle = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!can_reswizzle)
|
||||
continue;
|
||||
|
||||
/* At this point, we have determined that the given use can be
|
||||
* reswizzled to actually use the destination of the vecN operation.
|
||||
* Go ahead and rewrite it as needed.
|
||||
*/
|
||||
nir_instr_rewrite_src(use->parent_instr, use,
|
||||
nir_src_for_ssa(&vec->dest.dest.ssa));
|
||||
for (unsigned j = 0; j < 4; j++) {
|
||||
if (!nir_alu_instr_channel_used(use_alu, src_idx, j))
|
||||
continue;
|
||||
|
||||
use_alu_src->swizzle[j] = swizzle[use_alu_src->swizzle[j]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
nir_move_vec_src_uses_to_dest_impl(nir_shader *shader, nir_function_impl *impl)
|
||||
{
|
||||
nir_metadata_require(impl, nir_metadata_dominance);
|
||||
|
||||
nir_index_instrs(impl);
|
||||
nir_foreach_block(impl, move_vec_src_uses_to_dest_block, shader);
|
||||
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
}
|
||||
|
||||
void
|
||||
nir_move_vec_src_uses_to_dest(nir_shader *shader)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
nir_move_vec_src_uses_to_dest_impl(shader, function->impl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright © 2015 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@jlekstrand.net>
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "nir_builder.h"
|
||||
|
||||
/**
|
||||
* This file implements a NIR lowering pass to perform the normalization of
|
||||
* the cubemap coordinates to have the largest magnitude component be -1.0
|
||||
* or 1.0. This is based on the old GLSL IR based pass by Eric.
|
||||
*/
|
||||
|
||||
struct normalize_cubemap_state {
|
||||
nir_builder b;
|
||||
bool progress;
|
||||
};
|
||||
|
||||
static bool
|
||||
normalize_cubemap_coords_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct normalize_cubemap_state *state = void_state;
|
||||
nir_builder *b = &state->b;
|
||||
|
||||
nir_foreach_instr(block, instr) {
|
||||
if (instr->type != nir_instr_type_tex)
|
||||
continue;
|
||||
|
||||
nir_tex_instr *tex = nir_instr_as_tex(instr);
|
||||
if (tex->sampler_dim != GLSL_SAMPLER_DIM_CUBE)
|
||||
continue;
|
||||
|
||||
b->cursor = nir_before_instr(&tex->instr);
|
||||
|
||||
for (unsigned i = 0; i < tex->num_srcs; i++) {
|
||||
if (tex->src[i].src_type != nir_tex_src_coord)
|
||||
continue;
|
||||
|
||||
nir_ssa_def *orig_coord =
|
||||
nir_ssa_for_src(b, tex->src[i].src, nir_tex_instr_src_size(tex, i));
|
||||
assert(orig_coord->num_components >= 3);
|
||||
|
||||
nir_ssa_def *abs = nir_fabs(b, orig_coord);
|
||||
nir_ssa_def *norm = nir_fmax(b, nir_channel(b, abs, 0),
|
||||
nir_fmax(b, nir_channel(b, abs, 1),
|
||||
nir_channel(b, abs, 2)));
|
||||
|
||||
nir_ssa_def *normalized = nir_fmul(b, orig_coord, nir_frcp(b, norm));
|
||||
|
||||
/* Array indices don't have to be normalized, so make a new vector
|
||||
* with the coordinate's array index untouched.
|
||||
*/
|
||||
if (tex->coord_components == 4) {
|
||||
normalized = nir_vec4(b,
|
||||
nir_channel(b, normalized, 0),
|
||||
nir_channel(b, normalized, 1),
|
||||
nir_channel(b, normalized, 2),
|
||||
nir_channel(b, orig_coord, 3));
|
||||
}
|
||||
|
||||
nir_instr_rewrite_src(&tex->instr,
|
||||
&tex->src[i].src,
|
||||
nir_src_for_ssa(normalized));
|
||||
|
||||
state->progress = true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
normalize_cubemap_coords_impl(nir_function_impl *impl)
|
||||
{
|
||||
struct normalize_cubemap_state state;
|
||||
nir_builder_init(&state.b, impl);
|
||||
state.progress = false;
|
||||
|
||||
nir_foreach_block(impl, normalize_cubemap_coords_block, &state);
|
||||
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
|
||||
return state.progress;
|
||||
}
|
||||
|
||||
bool
|
||||
nir_normalize_cubemap_coords(nir_shader *shader)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
progress = normalize_cubemap_coords_impl(function->impl) || progress;
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
@@ -0,0 +1,668 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2014 Connor Abbott
|
||||
#
|
||||
# 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:
|
||||
# Connor Abbott (cwabbott0@gmail.com)
|
||||
|
||||
|
||||
# Class that represents all the information we have about the opcode
|
||||
# NOTE: this must be kept in sync with nir_op_info
|
||||
|
||||
class Opcode(object):
|
||||
"""Class that represents all the information we have about the opcode
|
||||
NOTE: this must be kept in sync with nir_op_info
|
||||
"""
|
||||
def __init__(self, name, output_size, output_type, input_sizes,
|
||||
input_types, algebraic_properties, const_expr):
|
||||
"""Parameters:
|
||||
|
||||
- name is the name of the opcode (prepend nir_op_ for the enum name)
|
||||
- all types are strings that get nir_type_ prepended to them
|
||||
- input_types is a list of types
|
||||
- algebraic_properties is a space-seperated string, where nir_op_is_ is
|
||||
prepended before each entry
|
||||
- const_expr is an expression or series of statements that computes the
|
||||
constant value of the opcode given the constant values of its inputs.
|
||||
|
||||
Constant expressions are formed from the variables src0, src1, ...,
|
||||
src(N-1), where N is the number of arguments. The output of the
|
||||
expression should be stored in the dst variable. Per-component input
|
||||
and output variables will be scalars and non-per-component input and
|
||||
output variables will be a struct with fields named x, y, z, and w
|
||||
all of the correct type. Input and output variables can be assumed
|
||||
to already be of the correct type and need no conversion. In
|
||||
particular, the conversion from the C bool type to/from NIR_TRUE and
|
||||
NIR_FALSE happens automatically.
|
||||
|
||||
For per-component instructions, the entire expression will be
|
||||
executed once for each component. For non-per-component
|
||||
instructions, the expression is expected to store the correct values
|
||||
in dst.x, dst.y, etc. If "dst" does not exist anywhere in the
|
||||
constant expression, an assignment to dst will happen automatically
|
||||
and the result will be equivalent to "dst = <expression>" for
|
||||
per-component instructions and "dst.x = dst.y = ... = <expression>"
|
||||
for non-per-component instructions.
|
||||
"""
|
||||
assert isinstance(name, str)
|
||||
assert isinstance(output_size, int)
|
||||
assert isinstance(output_type, str)
|
||||
assert isinstance(input_sizes, list)
|
||||
assert isinstance(input_sizes[0], int)
|
||||
assert isinstance(input_types, list)
|
||||
assert isinstance(input_types[0], str)
|
||||
assert isinstance(algebraic_properties, str)
|
||||
assert isinstance(const_expr, str)
|
||||
assert len(input_sizes) == len(input_types)
|
||||
assert 0 <= output_size <= 4
|
||||
for size in input_sizes:
|
||||
assert 0 <= size <= 4
|
||||
if output_size != 0:
|
||||
assert size != 0
|
||||
self.name = name
|
||||
self.num_inputs = len(input_sizes)
|
||||
self.output_size = output_size
|
||||
self.output_type = output_type
|
||||
self.input_sizes = input_sizes
|
||||
self.input_types = input_types
|
||||
self.algebraic_properties = algebraic_properties
|
||||
self.const_expr = const_expr
|
||||
|
||||
# helper variables for strings
|
||||
tfloat = "float"
|
||||
tint = "int"
|
||||
tbool = "bool"
|
||||
tuint = "uint"
|
||||
|
||||
commutative = "commutative "
|
||||
associative = "associative "
|
||||
|
||||
# global dictionary of opcodes
|
||||
opcodes = {}
|
||||
|
||||
def opcode(name, output_size, output_type, input_sizes, input_types,
|
||||
algebraic_properties, const_expr):
|
||||
assert name not in opcodes
|
||||
opcodes[name] = Opcode(name, output_size, output_type, input_sizes,
|
||||
input_types, algebraic_properties, const_expr)
|
||||
|
||||
def unop_convert(name, in_type, out_type, const_expr):
|
||||
opcode(name, 0, out_type, [0], [in_type], "", const_expr)
|
||||
|
||||
def unop(name, ty, const_expr):
|
||||
opcode(name, 0, ty, [0], [ty], "", const_expr)
|
||||
|
||||
def unop_horiz(name, output_size, output_type, input_size, input_type,
|
||||
const_expr):
|
||||
opcode(name, output_size, output_type, [input_size], [input_type], "",
|
||||
const_expr)
|
||||
|
||||
def unop_reduce(name, output_size, output_type, input_type, prereduce_expr,
|
||||
reduce_expr, final_expr):
|
||||
def prereduce(src):
|
||||
return "(" + prereduce_expr.format(src=src) + ")"
|
||||
def final(src):
|
||||
return final_expr.format(src="(" + src + ")")
|
||||
def reduce_(src0, src1):
|
||||
return reduce_expr.format(src0=src0, src1=src1)
|
||||
src0 = prereduce("src0.x")
|
||||
src1 = prereduce("src0.y")
|
||||
src2 = prereduce("src0.z")
|
||||
src3 = prereduce("src0.w")
|
||||
unop_horiz(name + "2", output_size, output_type, 2, input_type,
|
||||
final(reduce_(src0, src1)))
|
||||
unop_horiz(name + "3", output_size, output_type, 3, input_type,
|
||||
final(reduce_(reduce_(src0, src1), src2)))
|
||||
unop_horiz(name + "4", output_size, output_type, 4, input_type,
|
||||
final(reduce_(reduce_(src0, src1), reduce_(src2, src3))))
|
||||
|
||||
|
||||
# These two move instructions differ in what modifiers they support and what
|
||||
# the negate modifier means. Otherwise, they are identical.
|
||||
unop("fmov", tfloat, "src0")
|
||||
unop("imov", tint, "src0")
|
||||
|
||||
unop("ineg", tint, "-src0")
|
||||
unop("fneg", tfloat, "-src0")
|
||||
unop("inot", tint, "~src0") # invert every bit of the integer
|
||||
unop("fnot", tfloat, "(src0 == 0.0f) ? 1.0f : 0.0f")
|
||||
unop("fsign", tfloat, "(src0 == 0.0f) ? 0.0f : ((src0 > 0.0f) ? 1.0f : -1.0f)")
|
||||
unop("isign", tint, "(src0 == 0) ? 0 : ((src0 > 0) ? 1 : -1)")
|
||||
unop("iabs", tint, "(src0 < 0) ? -src0 : src0")
|
||||
unop("fabs", tfloat, "fabsf(src0)")
|
||||
unop("fsat", tfloat, "(src0 > 1.0f) ? 1.0f : ((src0 <= 0.0f) ? 0.0f : src0)")
|
||||
unop("frcp", tfloat, "1.0f / src0")
|
||||
unop("frsq", tfloat, "1.0f / sqrtf(src0)")
|
||||
unop("fsqrt", tfloat, "sqrtf(src0)")
|
||||
unop("fexp2", tfloat, "exp2f(src0)")
|
||||
unop("flog2", tfloat, "log2f(src0)")
|
||||
unop_convert("f2i", tfloat, tint, "src0") # Float-to-integer conversion.
|
||||
unop_convert("f2u", tfloat, tuint, "src0") # Float-to-unsigned conversion
|
||||
unop_convert("i2f", tint, tfloat, "src0") # Integer-to-float conversion.
|
||||
# Float-to-boolean conversion
|
||||
unop_convert("f2b", tfloat, tbool, "src0 != 0.0f")
|
||||
# Boolean-to-float conversion
|
||||
unop_convert("b2f", tbool, tfloat, "src0 ? 1.0f : 0.0f")
|
||||
# Int-to-boolean conversion
|
||||
unop_convert("i2b", tint, tbool, "src0 != 0")
|
||||
unop_convert("b2i", tbool, tint, "src0 ? 1 : 0") # Boolean-to-int conversion
|
||||
unop_convert("u2f", tuint, tfloat, "src0") # Unsigned-to-float conversion.
|
||||
|
||||
# Unary floating-point rounding operations.
|
||||
|
||||
|
||||
unop("ftrunc", tfloat, "truncf(src0)")
|
||||
unop("fceil", tfloat, "ceilf(src0)")
|
||||
unop("ffloor", tfloat, "floorf(src0)")
|
||||
unop("ffract", tfloat, "src0 - floorf(src0)")
|
||||
unop("fround_even", tfloat, "_mesa_roundevenf(src0)")
|
||||
|
||||
|
||||
# Trigonometric operations.
|
||||
|
||||
|
||||
unop("fsin", tfloat, "sinf(src0)")
|
||||
unop("fcos", tfloat, "cosf(src0)")
|
||||
|
||||
|
||||
# Partial derivatives.
|
||||
|
||||
|
||||
unop("fddx", tfloat, "0.0f") # the derivative of a constant is 0.
|
||||
unop("fddy", tfloat, "0.0f")
|
||||
unop("fddx_fine", tfloat, "0.0f")
|
||||
unop("fddy_fine", tfloat, "0.0f")
|
||||
unop("fddx_coarse", tfloat, "0.0f")
|
||||
unop("fddy_coarse", tfloat, "0.0f")
|
||||
|
||||
|
||||
# Floating point pack and unpack operations.
|
||||
|
||||
def pack_2x16(fmt):
|
||||
unop_horiz("pack_" + fmt + "_2x16", 1, tuint, 2, tfloat, """
|
||||
dst.x = (uint32_t) pack_fmt_1x16(src0.x);
|
||||
dst.x |= ((uint32_t) pack_fmt_1x16(src0.y)) << 16;
|
||||
""".replace("fmt", fmt))
|
||||
|
||||
def pack_4x8(fmt):
|
||||
unop_horiz("pack_" + fmt + "_4x8", 1, tuint, 4, tfloat, """
|
||||
dst.x = (uint32_t) pack_fmt_1x8(src0.x);
|
||||
dst.x |= ((uint32_t) pack_fmt_1x8(src0.y)) << 8;
|
||||
dst.x |= ((uint32_t) pack_fmt_1x8(src0.z)) << 16;
|
||||
dst.x |= ((uint32_t) pack_fmt_1x8(src0.w)) << 24;
|
||||
""".replace("fmt", fmt))
|
||||
|
||||
def unpack_2x16(fmt):
|
||||
unop_horiz("unpack_" + fmt + "_2x16", 2, tfloat, 1, tuint, """
|
||||
dst.x = unpack_fmt_1x16((uint16_t)(src0.x & 0xffff));
|
||||
dst.y = unpack_fmt_1x16((uint16_t)(src0.x << 16));
|
||||
""".replace("fmt", fmt))
|
||||
|
||||
def unpack_4x8(fmt):
|
||||
unop_horiz("unpack_" + fmt + "_4x8", 4, tfloat, 1, tuint, """
|
||||
dst.x = unpack_fmt_1x8((uint8_t)(src0.x & 0xff));
|
||||
dst.y = unpack_fmt_1x8((uint8_t)((src0.x >> 8) & 0xff));
|
||||
dst.z = unpack_fmt_1x8((uint8_t)((src0.x >> 16) & 0xff));
|
||||
dst.w = unpack_fmt_1x8((uint8_t)(src0.x >> 24));
|
||||
""".replace("fmt", fmt))
|
||||
|
||||
|
||||
pack_2x16("snorm")
|
||||
pack_4x8("snorm")
|
||||
pack_2x16("unorm")
|
||||
pack_4x8("unorm")
|
||||
pack_2x16("half")
|
||||
unpack_2x16("snorm")
|
||||
unpack_4x8("snorm")
|
||||
unpack_2x16("unorm")
|
||||
unpack_4x8("unorm")
|
||||
unpack_2x16("half")
|
||||
|
||||
|
||||
# Lowered floating point unpacking operations.
|
||||
|
||||
|
||||
unop_horiz("unpack_half_2x16_split_x", 1, tfloat, 1, tuint,
|
||||
"unpack_half_1x16((uint16_t)(src0.x & 0xffff))")
|
||||
unop_horiz("unpack_half_2x16_split_y", 1, tfloat, 1, tuint,
|
||||
"unpack_half_1x16((uint16_t)(src0.x >> 16))")
|
||||
|
||||
|
||||
# Bit operations, part of ARB_gpu_shader5.
|
||||
|
||||
|
||||
unop("bitfield_reverse", tuint, """
|
||||
/* we're not winning any awards for speed here, but that's ok */
|
||||
dst = 0;
|
||||
for (unsigned bit = 0; bit < 32; bit++)
|
||||
dst |= ((src0 >> bit) & 1) << (31 - bit);
|
||||
""")
|
||||
unop("bit_count", tuint, """
|
||||
dst = 0;
|
||||
for (unsigned bit = 0; bit < 32; bit++) {
|
||||
if ((src0 >> bit) & 1)
|
||||
dst++;
|
||||
}
|
||||
""")
|
||||
|
||||
unop_convert("ufind_msb", tuint, tint, """
|
||||
dst = -1;
|
||||
for (int bit = 31; bit > 0; bit--) {
|
||||
if ((src0 >> bit) & 1) {
|
||||
dst = bit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
unop("ifind_msb", tint, """
|
||||
dst = -1;
|
||||
for (int bit = 31; bit >= 0; bit--) {
|
||||
/* If src0 < 0, we're looking for the first 0 bit.
|
||||
* if src0 >= 0, we're looking for the first 1 bit.
|
||||
*/
|
||||
if ((((src0 >> bit) & 1) && (src0 >= 0)) ||
|
||||
(!((src0 >> bit) & 1) && (src0 < 0))) {
|
||||
dst = bit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
unop("find_lsb", tint, """
|
||||
dst = -1;
|
||||
for (unsigned bit = 0; bit < 32; bit++) {
|
||||
if ((src0 >> bit) & 1) {
|
||||
dst = bit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
|
||||
for i in xrange(1, 5):
|
||||
for j in xrange(1, 5):
|
||||
unop_horiz("fnoise{0}_{1}".format(i, j), i, tfloat, j, tfloat, "0.0f")
|
||||
|
||||
def binop_convert(name, out_type, in_type, alg_props, const_expr):
|
||||
opcode(name, 0, out_type, [0, 0], [in_type, in_type], alg_props, const_expr)
|
||||
|
||||
def binop(name, ty, alg_props, const_expr):
|
||||
binop_convert(name, ty, ty, alg_props, const_expr)
|
||||
|
||||
def binop_compare(name, ty, alg_props, const_expr):
|
||||
binop_convert(name, tbool, ty, alg_props, const_expr)
|
||||
|
||||
def binop_horiz(name, out_size, out_type, src1_size, src1_type, src2_size,
|
||||
src2_type, const_expr):
|
||||
opcode(name, out_size, out_type, [src1_size, src2_size], [src1_type, src2_type],
|
||||
"", const_expr)
|
||||
|
||||
def binop_reduce(name, output_size, output_type, src_type, prereduce_expr,
|
||||
reduce_expr, final_expr):
|
||||
def final(src):
|
||||
return final_expr.format(src= "(" + src + ")")
|
||||
def reduce_(src0, src1):
|
||||
return reduce_expr.format(src0=src0, src1=src1)
|
||||
def prereduce(src0, src1):
|
||||
return "(" + prereduce_expr.format(src0=src0, src1=src1) + ")"
|
||||
src0 = prereduce("src0.x", "src1.x")
|
||||
src1 = prereduce("src0.y", "src1.y")
|
||||
src2 = prereduce("src0.z", "src1.z")
|
||||
src3 = prereduce("src0.w", "src1.w")
|
||||
opcode(name + "2", output_size, output_type,
|
||||
[2, 2], [src_type, src_type], commutative,
|
||||
final(reduce_(src0, src1)))
|
||||
opcode(name + "3", output_size, output_type,
|
||||
[3, 3], [src_type, src_type], commutative,
|
||||
final(reduce_(reduce_(src0, src1), src2)))
|
||||
opcode(name + "4", output_size, output_type,
|
||||
[4, 4], [src_type, src_type], commutative,
|
||||
final(reduce_(reduce_(src0, src1), reduce_(src2, src3))))
|
||||
|
||||
binop("fadd", tfloat, commutative + associative, "src0 + src1")
|
||||
binop("iadd", tint, commutative + associative, "src0 + src1")
|
||||
binop("fsub", tfloat, "", "src0 - src1")
|
||||
binop("isub", tint, "", "src0 - src1")
|
||||
|
||||
binop("fmul", tfloat, commutative + associative, "src0 * src1")
|
||||
# low 32-bits of signed/unsigned integer multiply
|
||||
binop("imul", tint, commutative + associative, "src0 * src1")
|
||||
# high 32-bits of signed integer multiply
|
||||
binop("imul_high", tint, commutative,
|
||||
"(int32_t)(((int64_t) src0 * (int64_t) src1) >> 32)")
|
||||
# high 32-bits of unsigned integer multiply
|
||||
binop("umul_high", tuint, commutative,
|
||||
"(uint32_t)(((uint64_t) src0 * (uint64_t) src1) >> 32)")
|
||||
|
||||
binop("fdiv", tfloat, "", "src0 / src1")
|
||||
binop("idiv", tint, "", "src0 / src1")
|
||||
binop("udiv", tuint, "", "src0 / src1")
|
||||
|
||||
# returns a boolean representing the carry resulting from the addition of
|
||||
# the two unsigned arguments.
|
||||
|
||||
binop_convert("uadd_carry", tuint, tuint, commutative, "src0 + src1 < src0")
|
||||
|
||||
# returns a boolean representing the borrow resulting from the subtraction
|
||||
# of the two unsigned arguments.
|
||||
|
||||
binop_convert("usub_borrow", tuint, tuint, "", "src0 < src1")
|
||||
|
||||
binop("fmod", tfloat, "", "src0 - src1 * floorf(src0 / src1)")
|
||||
binop("umod", tuint, "", "src1 == 0 ? 0 : src0 % src1")
|
||||
|
||||
#
|
||||
# Comparisons
|
||||
#
|
||||
|
||||
|
||||
# these integer-aware comparisons return a boolean (0 or ~0)
|
||||
|
||||
binop_compare("flt", tfloat, "", "src0 < src1")
|
||||
binop_compare("fge", tfloat, "", "src0 >= src1")
|
||||
binop_compare("feq", tfloat, commutative, "src0 == src1")
|
||||
binop_compare("fne", tfloat, commutative, "src0 != src1")
|
||||
binop_compare("ilt", tint, "", "src0 < src1")
|
||||
binop_compare("ige", tint, "", "src0 >= src1")
|
||||
binop_compare("ieq", tint, commutative, "src0 == src1")
|
||||
binop_compare("ine", tint, commutative, "src0 != src1")
|
||||
binop_compare("ult", tuint, "", "src0 < src1")
|
||||
binop_compare("uge", tuint, "", "src0 >= src1")
|
||||
|
||||
# integer-aware GLSL-style comparisons that compare floats and ints
|
||||
|
||||
binop_reduce("ball_fequal", 1, tbool, tfloat, "{src0} == {src1}",
|
||||
"{src0} && {src1}", "{src}")
|
||||
binop_reduce("bany_fnequal", 1, tbool, tfloat, "{src0} != {src1}",
|
||||
"{src0} || {src1}", "{src}")
|
||||
binop_reduce("ball_iequal", 1, tbool, tint, "{src0} == {src1}",
|
||||
"{src0} && {src1}", "{src}")
|
||||
binop_reduce("bany_inequal", 1, tbool, tint, "{src0} != {src1}",
|
||||
"{src0} || {src1}", "{src}")
|
||||
|
||||
# non-integer-aware GLSL-style comparisons that return 0.0 or 1.0
|
||||
|
||||
binop_reduce("fall_equal", 1, tfloat, tfloat, "{src0} == {src1}",
|
||||
"{src0} && {src1}", "{src} ? 1.0f : 0.0f")
|
||||
binop_reduce("fany_nequal", 1, tfloat, tfloat, "{src0} != {src1}",
|
||||
"{src0} || {src1}", "{src} ? 1.0f : 0.0f")
|
||||
|
||||
# These comparisons for integer-less hardware return 1.0 and 0.0 for true
|
||||
# and false respectively
|
||||
|
||||
binop("slt", tfloat, "", "(src0 < src1) ? 1.0f : 0.0f") # Set on Less Than
|
||||
binop("sge", tfloat, "", "(src0 >= src1) ? 1.0f : 0.0f") # Set on Greater or Equal
|
||||
binop("seq", tfloat, commutative, "(src0 == src1) ? 1.0f : 0.0f") # Set on Equal
|
||||
binop("sne", tfloat, commutative, "(src0 != src1) ? 1.0f : 0.0f") # Set on Not Equal
|
||||
|
||||
|
||||
binop("ishl", tint, "", "src0 << src1")
|
||||
binop("ishr", tint, "", "src0 >> src1")
|
||||
binop("ushr", tuint, "", "src0 >> src1")
|
||||
|
||||
# bitwise logic operators
|
||||
#
|
||||
# These are also used as boolean and, or, xor for hardware supporting
|
||||
# integers.
|
||||
|
||||
|
||||
binop("iand", tuint, commutative + associative, "src0 & src1")
|
||||
binop("ior", tuint, commutative + associative, "src0 | src1")
|
||||
binop("ixor", tuint, commutative + associative, "src0 ^ src1")
|
||||
|
||||
|
||||
# floating point logic operators
|
||||
#
|
||||
# These use (src != 0.0) for testing the truth of the input, and output 1.0
|
||||
# for true and 0.0 for false
|
||||
|
||||
binop("fand", tfloat, commutative,
|
||||
"((src0 != 0.0f) && (src1 != 0.0f)) ? 1.0f : 0.0f")
|
||||
binop("for", tfloat, commutative,
|
||||
"((src0 != 0.0f) || (src1 != 0.0f)) ? 1.0f : 0.0f")
|
||||
binop("fxor", tfloat, commutative,
|
||||
"(src0 != 0.0f && src1 == 0.0f) || (src0 == 0.0f && src1 != 0.0f) ? 1.0f : 0.0f")
|
||||
|
||||
binop_reduce("fdot", 1, tfloat, tfloat, "{src0} * {src1}", "{src0} + {src1}",
|
||||
"{src}")
|
||||
|
||||
binop_reduce("fdot_replicated", 4, tfloat, tfloat,
|
||||
"{src0} * {src1}", "{src0} + {src1}", "{src}")
|
||||
|
||||
opcode("fdph", 1, tfloat, [3, 4], [tfloat, tfloat], "",
|
||||
"src0.x * src1.x + src0.y * src1.y + src0.z * src1.z + src1.w")
|
||||
opcode("fdph_replicated", 4, tfloat, [3, 4], [tfloat, tfloat], "",
|
||||
"src0.x * src1.x + src0.y * src1.y + src0.z * src1.z + src1.w")
|
||||
|
||||
binop("fmin", tfloat, "", "fminf(src0, src1)")
|
||||
binop("imin", tint, commutative + associative, "src1 > src0 ? src0 : src1")
|
||||
binop("umin", tuint, commutative + associative, "src1 > src0 ? src0 : src1")
|
||||
binop("fmax", tfloat, "", "fmaxf(src0, src1)")
|
||||
binop("imax", tint, commutative + associative, "src1 > src0 ? src1 : src0")
|
||||
binop("umax", tuint, commutative + associative, "src1 > src0 ? src1 : src0")
|
||||
|
||||
# Saturated vector add for 4 8bit ints.
|
||||
binop("usadd_4x8", tint, commutative + associative, """
|
||||
dst = 0;
|
||||
for (int i = 0; i < 32; i += 8) {
|
||||
dst |= MIN2(((src0 >> i) & 0xff) + ((src1 >> i) & 0xff), 0xff) << i;
|
||||
}
|
||||
""")
|
||||
|
||||
# Saturated vector subtract for 4 8bit ints.
|
||||
binop("ussub_4x8", tint, "", """
|
||||
dst = 0;
|
||||
for (int i = 0; i < 32; i += 8) {
|
||||
int src0_chan = (src0 >> i) & 0xff;
|
||||
int src1_chan = (src1 >> i) & 0xff;
|
||||
if (src0_chan > src1_chan)
|
||||
dst |= (src0_chan - src1_chan) << i;
|
||||
}
|
||||
""")
|
||||
|
||||
# vector min for 4 8bit ints.
|
||||
binop("umin_4x8", tint, commutative + associative, """
|
||||
dst = 0;
|
||||
for (int i = 0; i < 32; i += 8) {
|
||||
dst |= MIN2((src0 >> i) & 0xff, (src1 >> i) & 0xff) << i;
|
||||
}
|
||||
""")
|
||||
|
||||
# vector max for 4 8bit ints.
|
||||
binop("umax_4x8", tint, commutative + associative, """
|
||||
dst = 0;
|
||||
for (int i = 0; i < 32; i += 8) {
|
||||
dst |= MAX2((src0 >> i) & 0xff, (src1 >> i) & 0xff) << i;
|
||||
}
|
||||
""")
|
||||
|
||||
# unorm multiply: (a * b) / 255.
|
||||
binop("umul_unorm_4x8", tint, commutative + associative, """
|
||||
dst = 0;
|
||||
for (int i = 0; i < 32; i += 8) {
|
||||
int src0_chan = (src0 >> i) & 0xff;
|
||||
int src1_chan = (src1 >> i) & 0xff;
|
||||
dst |= ((src0_chan * src1_chan) / 255) << i;
|
||||
}
|
||||
""")
|
||||
|
||||
binop("fpow", tfloat, "", "powf(src0, src1)")
|
||||
|
||||
binop_horiz("pack_half_2x16_split", 1, tuint, 1, tfloat, 1, tfloat,
|
||||
"pack_half_1x16(src0.x) | (pack_half_1x16(src1.x) << 16)")
|
||||
|
||||
# bfm implements the behavior of the first operation of the SM5 "bfi" assembly
|
||||
# and that of the "bfi1" i965 instruction. That is, it has undefined behavior
|
||||
# if either of its arguments are 32.
|
||||
binop_convert("bfm", tuint, tint, "", """
|
||||
int bits = src0, offset = src1;
|
||||
if (offset < 0 || bits < 0 || offset > 31 || bits > 31 || offset + bits > 32)
|
||||
dst = 0; /* undefined */
|
||||
else
|
||||
dst = ((1u << bits) - 1) << offset;
|
||||
""")
|
||||
|
||||
opcode("ldexp", 0, tfloat, [0, 0], [tfloat, tint], "", """
|
||||
dst = ldexpf(src0, src1);
|
||||
/* flush denormals to zero. */
|
||||
if (!isnormal(dst))
|
||||
dst = copysignf(0.0f, src0);
|
||||
""")
|
||||
|
||||
# Combines the first component of each input to make a 2-component vector.
|
||||
|
||||
binop_horiz("vec2", 2, tuint, 1, tuint, 1, tuint, """
|
||||
dst.x = src0.x;
|
||||
dst.y = src1.x;
|
||||
""")
|
||||
|
||||
def triop(name, ty, const_expr):
|
||||
opcode(name, 0, ty, [0, 0, 0], [ty, ty, ty], "", const_expr)
|
||||
def triop_horiz(name, output_size, src1_size, src2_size, src3_size, const_expr):
|
||||
opcode(name, output_size, tuint,
|
||||
[src1_size, src2_size, src3_size],
|
||||
[tuint, tuint, tuint], "", const_expr)
|
||||
|
||||
triop("ffma", tfloat, "src0 * src1 + src2")
|
||||
|
||||
triop("flrp", tfloat, "src0 * (1 - src2) + src1 * src2")
|
||||
|
||||
# Conditional Select
|
||||
#
|
||||
# A vector conditional select instruction (like ?:, but operating per-
|
||||
# component on vectors). There are two versions, one for floating point
|
||||
# bools (0.0 vs 1.0) and one for integer bools (0 vs ~0).
|
||||
|
||||
|
||||
triop("fcsel", tfloat, "(src0 != 0.0f) ? src1 : src2")
|
||||
opcode("bcsel", 0, tuint, [0, 0, 0],
|
||||
[tbool, tuint, tuint], "", "src0 ? src1 : src2")
|
||||
|
||||
# SM5 bfi assembly
|
||||
triop("bfi", tuint, """
|
||||
unsigned mask = src0, insert = src1, base = src2;
|
||||
if (mask == 0) {
|
||||
dst = base;
|
||||
} else {
|
||||
unsigned tmp = mask;
|
||||
while (!(tmp & 1)) {
|
||||
tmp >>= 1;
|
||||
insert <<= 1;
|
||||
}
|
||||
dst = (base & ~mask) | (insert & mask);
|
||||
}
|
||||
""")
|
||||
|
||||
# SM5 ubfe/ibfe assembly
|
||||
opcode("ubfe", 0, tuint,
|
||||
[0, 0, 0], [tuint, tint, tint], "", """
|
||||
unsigned base = src0;
|
||||
int offset = src1, bits = src2;
|
||||
if (bits == 0) {
|
||||
dst = 0;
|
||||
} else if (bits < 0 || offset < 0) {
|
||||
dst = 0; /* undefined */
|
||||
} else if (offset + bits < 32) {
|
||||
dst = (base << (32 - bits - offset)) >> (32 - bits);
|
||||
} else {
|
||||
dst = base >> offset;
|
||||
}
|
||||
""")
|
||||
opcode("ibfe", 0, tint,
|
||||
[0, 0, 0], [tint, tint, tint], "", """
|
||||
int base = src0;
|
||||
int offset = src1, bits = src2;
|
||||
if (bits == 0) {
|
||||
dst = 0;
|
||||
} else if (bits < 0 || offset < 0) {
|
||||
dst = 0; /* undefined */
|
||||
} else if (offset + bits < 32) {
|
||||
dst = (base << (32 - bits - offset)) >> (32 - bits);
|
||||
} else {
|
||||
dst = base >> offset;
|
||||
}
|
||||
""")
|
||||
|
||||
# GLSL bitfieldExtract()
|
||||
opcode("ubitfield_extract", 0, tuint,
|
||||
[0, 0, 0], [tuint, tint, tint], "", """
|
||||
unsigned base = src0;
|
||||
int offset = src1, bits = src2;
|
||||
if (bits == 0) {
|
||||
dst = 0;
|
||||
} else if (bits < 0 || offset < 0 || offset + bits > 32) {
|
||||
dst = 0; /* undefined per the spec */
|
||||
} else {
|
||||
dst = (base >> offset) & ((1ull << bits) - 1);
|
||||
}
|
||||
""")
|
||||
opcode("ibitfield_extract", 0, tint,
|
||||
[0, 0, 0], [tint, tint, tint], "", """
|
||||
int base = src0;
|
||||
int offset = src1, bits = src2;
|
||||
if (bits == 0) {
|
||||
dst = 0;
|
||||
} else if (offset < 0 || bits < 0 || offset + bits > 32) {
|
||||
dst = 0;
|
||||
} else {
|
||||
dst = (base << (32 - offset - bits)) >> offset; /* use sign-extending shift */
|
||||
}
|
||||
""")
|
||||
|
||||
# Combines the first component of each input to make a 3-component vector.
|
||||
|
||||
triop_horiz("vec3", 3, 1, 1, 1, """
|
||||
dst.x = src0.x;
|
||||
dst.y = src1.x;
|
||||
dst.z = src2.x;
|
||||
""")
|
||||
|
||||
def quadop_horiz(name, output_size, src1_size, src2_size, src3_size,
|
||||
src4_size, const_expr):
|
||||
opcode(name, output_size, tuint,
|
||||
[src1_size, src2_size, src3_size, src4_size],
|
||||
[tuint, tuint, tuint, tuint],
|
||||
"", const_expr)
|
||||
|
||||
opcode("bitfield_insert", 0, tuint, [0, 0, 0, 0],
|
||||
[tuint, tuint, tint, tint], "", """
|
||||
unsigned base = src0, insert = src1;
|
||||
int offset = src2, bits = src3;
|
||||
if (bits == 0) {
|
||||
dst = 0;
|
||||
} else if (offset < 0 || bits < 0 || bits + offset > 32) {
|
||||
dst = 0;
|
||||
} else {
|
||||
unsigned mask = ((1ull << bits) - 1) << offset;
|
||||
dst = (base & ~mask) | ((insert << bits) & mask);
|
||||
}
|
||||
""")
|
||||
|
||||
quadop_horiz("vec4", 4, 1, 1, 1, 1, """
|
||||
dst.x = src0.x;
|
||||
dst.y = src1.x;
|
||||
dst.z = src2.x;
|
||||
dst.w = src3.x;
|
||||
""")
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2014 Connor Abbott
|
||||
#
|
||||
# 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:
|
||||
# Connor Abbott (cwabbott0@gmail.com)
|
||||
|
||||
from nir_opcodes import opcodes
|
||||
from mako.template import Template
|
||||
|
||||
template = Template("""
|
||||
#include "nir.h"
|
||||
|
||||
const nir_op_info nir_op_infos[nir_num_opcodes] = {
|
||||
% for name, opcode in sorted(opcodes.iteritems()):
|
||||
{
|
||||
.name = "${name}",
|
||||
.num_inputs = ${opcode.num_inputs},
|
||||
.output_size = ${opcode.output_size},
|
||||
.output_type = ${"nir_type_" + opcode.output_type},
|
||||
.input_sizes = {
|
||||
${ ", ".join(str(size) for size in opcode.input_sizes) }
|
||||
},
|
||||
.input_types = {
|
||||
${ ", ".join("nir_type_" + type for type in opcode.input_types) }
|
||||
},
|
||||
.algebraic_properties =
|
||||
${ "0" if opcode.algebraic_properties == "" else " | ".join(
|
||||
"NIR_OP_IS_" + prop.upper() for prop in
|
||||
opcode.algebraic_properties.strip().split(" ")) }
|
||||
},
|
||||
% endfor
|
||||
};
|
||||
""")
|
||||
|
||||
print template.render(opcodes=opcodes)
|
||||
@@ -0,0 +1,47 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
template = """\
|
||||
/* Copyright (C) 2014 Connor Abbott
|
||||
*
|
||||
* 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*/
|
||||
|
||||
#ifndef _NIR_OPCODES_
|
||||
#define _NIR_OPCODES_
|
||||
|
||||
<% opcode_names = sorted(opcodes.iterkeys()) %>
|
||||
|
||||
typedef enum {
|
||||
% for name in opcode_names:
|
||||
nir_op_${name},
|
||||
% endfor
|
||||
nir_last_opcode = nir_op_${opcode_names[-1]},
|
||||
nir_num_opcodes = nir_last_opcode + 1
|
||||
} nir_op;
|
||||
|
||||
#endif /* _NIR_OPCODES_ */"""
|
||||
|
||||
from nir_opcodes import opcodes
|
||||
from mako.template import Template
|
||||
|
||||
print Template(template).render(opcodes=opcodes)
|
||||
@@ -0,0 +1,285 @@
|
||||
#! /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@jlekstrand.net)
|
||||
|
||||
import nir_algebraic
|
||||
|
||||
# Convenience variables
|
||||
a = 'a'
|
||||
b = 'b'
|
||||
c = 'c'
|
||||
d = 'd'
|
||||
|
||||
# Written in the form (<search>, <replace>) where <search> is an expression
|
||||
# and <replace> is either an expression or a value. An expression is
|
||||
# defined as a tuple of the form (<op>, <src0>, <src1>, <src2>, <src3>)
|
||||
# where each source is either an expression or a value. A value can be
|
||||
# either a numeric constant or a string representing a variable name.
|
||||
#
|
||||
# Variable names are specified as "[#]name[@type]" where "#" inicates that
|
||||
# the given variable will only match constants and the type indicates that
|
||||
# the given variable will only match values from ALU instructions with the
|
||||
# given output type.
|
||||
#
|
||||
# For constants, you have to be careful to make sure that it is the right
|
||||
# type because python is unaware of the source and destination types of the
|
||||
# opcodes.
|
||||
|
||||
optimizations = [
|
||||
(('fneg', ('fneg', a)), a),
|
||||
(('ineg', ('ineg', a)), a),
|
||||
(('fabs', ('fabs', a)), ('fabs', a)),
|
||||
(('fabs', ('fneg', a)), ('fabs', a)),
|
||||
(('iabs', ('iabs', a)), ('iabs', a)),
|
||||
(('iabs', ('ineg', a)), ('iabs', a)),
|
||||
(('fadd', a, 0.0), a),
|
||||
(('iadd', a, 0), a),
|
||||
(('usadd_4x8', a, 0), a),
|
||||
(('usadd_4x8', a, ~0), ~0),
|
||||
(('fadd', ('fmul', a, b), ('fmul', a, c)), ('fmul', a, ('fadd', b, c))),
|
||||
(('iadd', ('imul', a, b), ('imul', a, c)), ('imul', a, ('iadd', b, c))),
|
||||
(('fadd', ('fneg', a), a), 0.0),
|
||||
(('iadd', ('ineg', a), a), 0),
|
||||
(('iadd', ('ineg', a), ('iadd', a, b)), b),
|
||||
(('iadd', a, ('iadd', ('ineg', a), b)), b),
|
||||
(('fadd', ('fneg', a), ('fadd', a, b)), b),
|
||||
(('fadd', a, ('fadd', ('fneg', a), b)), b),
|
||||
(('fmul', a, 0.0), 0.0),
|
||||
(('imul', a, 0), 0),
|
||||
(('umul_unorm_4x8', a, 0), 0),
|
||||
(('umul_unorm_4x8', a, ~0), a),
|
||||
(('fmul', a, 1.0), a),
|
||||
(('imul', a, 1), a),
|
||||
(('fmul', a, -1.0), ('fneg', a)),
|
||||
(('imul', a, -1), ('ineg', a)),
|
||||
(('ffma', 0.0, a, b), b),
|
||||
(('ffma', a, 0.0, b), b),
|
||||
(('ffma', a, b, 0.0), ('fmul', a, b)),
|
||||
(('ffma', a, 1.0, b), ('fadd', a, b)),
|
||||
(('ffma', 1.0, a, b), ('fadd', a, b)),
|
||||
(('flrp', a, b, 0.0), a),
|
||||
(('flrp', a, b, 1.0), b),
|
||||
(('flrp', a, a, b), a),
|
||||
(('flrp', 0.0, a, b), ('fmul', a, b)),
|
||||
(('flrp', a, b, c), ('fadd', ('fmul', c, ('fsub', b, a)), a), 'options->lower_flrp'),
|
||||
(('ffract', a), ('fsub', a, ('ffloor', a)), 'options->lower_ffract'),
|
||||
(('fadd', ('fmul', a, ('fadd', 1.0, ('fneg', c))), ('fmul', b, c)), ('flrp', a, b, c), '!options->lower_flrp'),
|
||||
(('fadd', a, ('fmul', c, ('fadd', b, ('fneg', a)))), ('flrp', a, b, c), '!options->lower_flrp'),
|
||||
(('ffma', a, b, c), ('fadd', ('fmul', a, b), c), 'options->lower_ffma'),
|
||||
(('fadd', ('fmul', a, b), c), ('ffma', a, b, c), '!options->lower_ffma'),
|
||||
# Comparison simplifications
|
||||
(('inot', ('flt', a, b)), ('fge', a, b)),
|
||||
(('inot', ('fge', a, b)), ('flt', a, b)),
|
||||
(('inot', ('feq', a, b)), ('fne', a, b)),
|
||||
(('inot', ('fne', a, b)), ('feq', a, b)),
|
||||
(('inot', ('ilt', a, b)), ('ige', a, b)),
|
||||
(('inot', ('ige', a, b)), ('ilt', a, b)),
|
||||
(('inot', ('ieq', a, b)), ('ine', a, b)),
|
||||
(('inot', ('ine', a, b)), ('ieq', a, b)),
|
||||
(('fge', ('fneg', ('fabs', a)), 0.0), ('feq', a, 0.0)),
|
||||
(('bcsel', ('flt', a, b), a, b), ('fmin', a, b)),
|
||||
(('bcsel', ('flt', a, b), b, a), ('fmax', a, b)),
|
||||
(('bcsel', ('inot', 'a@bool'), b, c), ('bcsel', a, c, b)),
|
||||
(('bcsel', a, ('bcsel', a, b, c), d), ('bcsel', a, b, d)),
|
||||
(('fmin', a, a), a),
|
||||
(('fmax', a, a), a),
|
||||
(('imin', a, a), a),
|
||||
(('imax', a, a), a),
|
||||
(('umin', a, a), a),
|
||||
(('umax', a, a), a),
|
||||
(('fmin', ('fmax', a, 0.0), 1.0), ('fsat', a), '!options->lower_fsat'),
|
||||
(('fmax', ('fmin', a, 1.0), 0.0), ('fsat', a), '!options->lower_fsat'),
|
||||
(('fsat', a), ('fmin', ('fmax', a, 0.0), 1.0), 'options->lower_fsat'),
|
||||
(('fsat', ('fsat', a)), ('fsat', a)),
|
||||
(('fmin', ('fmax', ('fmin', ('fmax', a, 0.0), 1.0), 0.0), 1.0), ('fmin', ('fmax', a, 0.0), 1.0)),
|
||||
(('ior', ('flt', a, b), ('flt', a, c)), ('flt', a, ('fmax', b, c))),
|
||||
(('ior', ('flt', a, c), ('flt', b, c)), ('flt', ('fmin', a, b), c)),
|
||||
(('ior', ('fge', a, b), ('fge', a, c)), ('fge', a, ('fmin', b, c))),
|
||||
(('ior', ('fge', a, c), ('fge', b, c)), ('fge', ('fmax', a, b), c)),
|
||||
(('slt', a, b), ('b2f', ('flt', a, b)), 'options->lower_scmp'),
|
||||
(('sge', a, b), ('b2f', ('fge', a, b)), 'options->lower_scmp'),
|
||||
(('seq', a, b), ('b2f', ('feq', a, b)), 'options->lower_scmp'),
|
||||
(('sne', a, b), ('b2f', ('fne', a, b)), 'options->lower_scmp'),
|
||||
(('fne', ('fneg', a), a), ('fne', a, 0.0)),
|
||||
(('feq', ('fneg', a), a), ('feq', a, 0.0)),
|
||||
# Emulating booleans
|
||||
(('imul', ('b2i', a), ('b2i', b)), ('b2i', ('iand', a, b))),
|
||||
(('fmul', ('b2f', a), ('b2f', b)), ('b2f', ('iand', a, b))),
|
||||
(('fsat', ('fadd', ('b2f', a), ('b2f', b))), ('b2f', ('ior', a, b))),
|
||||
(('iand', 'a@bool', 1.0), ('b2f', a)),
|
||||
(('flt', ('fneg', ('b2f', a)), 0), a), # Generated by TGSI KILL_IF.
|
||||
(('flt', ('fsub', 0.0, ('b2f', a)), 0), a), # Generated by TGSI KILL_IF.
|
||||
# Comparison with the same args. Note that these are not done for
|
||||
# the float versions because NaN always returns false on float
|
||||
# inequalities.
|
||||
(('ilt', a, a), False),
|
||||
(('ige', a, a), True),
|
||||
(('ieq', a, a), True),
|
||||
(('ine', a, a), False),
|
||||
(('ult', a, a), False),
|
||||
(('uge', a, a), True),
|
||||
# Logical and bit operations
|
||||
(('fand', a, 0.0), 0.0),
|
||||
(('iand', a, a), a),
|
||||
(('iand', a, ~0), a),
|
||||
(('iand', a, 0), 0),
|
||||
(('ior', a, a), a),
|
||||
(('ior', a, 0), a),
|
||||
(('fxor', a, a), 0.0),
|
||||
(('ixor', a, a), 0),
|
||||
(('inot', ('inot', a)), a),
|
||||
# DeMorgan's Laws
|
||||
(('iand', ('inot', a), ('inot', b)), ('inot', ('ior', a, b))),
|
||||
(('ior', ('inot', a), ('inot', b)), ('inot', ('iand', a, b))),
|
||||
# Shift optimizations
|
||||
(('ishl', 0, a), 0),
|
||||
(('ishl', a, 0), a),
|
||||
(('ishr', 0, a), 0),
|
||||
(('ishr', a, 0), a),
|
||||
(('ushr', 0, a), 0),
|
||||
(('ushr', a, 0), a),
|
||||
# Exponential/logarithmic identities
|
||||
(('fexp2', ('flog2', a)), a), # 2^lg2(a) = a
|
||||
(('flog2', ('fexp2', a)), a), # lg2(2^a) = a
|
||||
(('fpow', a, b), ('fexp2', ('fmul', ('flog2', a), b)), 'options->lower_fpow'), # a^b = 2^(lg2(a)*b)
|
||||
(('fexp2', ('fmul', ('flog2', a), b)), ('fpow', a, b), '!options->lower_fpow'), # 2^(lg2(a)*b) = a^b
|
||||
(('fpow', a, 1.0), a),
|
||||
(('fpow', a, 2.0), ('fmul', a, a)),
|
||||
(('fpow', a, 4.0), ('fmul', ('fmul', a, a), ('fmul', a, a))),
|
||||
(('fpow', 2.0, a), ('fexp2', a)),
|
||||
(('fpow', ('fpow', a, 2.2), 0.454545), a),
|
||||
(('fpow', ('fabs', ('fpow', a, 2.2)), 0.454545), ('fabs', a)),
|
||||
(('fsqrt', ('fexp2', a)), ('fexp2', ('fmul', 0.5, a))),
|
||||
(('frcp', ('fexp2', a)), ('fexp2', ('fneg', a))),
|
||||
(('frsq', ('fexp2', a)), ('fexp2', ('fmul', -0.5, a))),
|
||||
(('flog2', ('fsqrt', a)), ('fmul', 0.5, ('flog2', a))),
|
||||
(('flog2', ('frcp', a)), ('fneg', ('flog2', a))),
|
||||
(('flog2', ('frsq', a)), ('fmul', -0.5, ('flog2', a))),
|
||||
(('flog2', ('fpow', a, b)), ('fmul', b, ('flog2', a))),
|
||||
(('fadd', ('flog2', a), ('flog2', b)), ('flog2', ('fmul', a, b))),
|
||||
(('fadd', ('flog2', a), ('fneg', ('flog2', b))), ('flog2', ('fdiv', a, b))),
|
||||
(('fmul', ('fexp2', a), ('fexp2', b)), ('fexp2', ('fadd', a, b))),
|
||||
# Division and reciprocal
|
||||
(('fdiv', 1.0, a), ('frcp', a)),
|
||||
(('fdiv', a, b), ('fmul', a, ('frcp', b)), 'options->lower_fdiv'),
|
||||
(('frcp', ('frcp', a)), a),
|
||||
(('frcp', ('fsqrt', a)), ('frsq', a)),
|
||||
(('fsqrt', a), ('frcp', ('frsq', a)), 'options->lower_fsqrt'),
|
||||
(('frcp', ('frsq', a)), ('fsqrt', a), '!options->lower_fsqrt'),
|
||||
# Boolean simplifications
|
||||
(('ieq', 'a@bool', True), a),
|
||||
(('ine', 'a@bool', True), ('inot', a)),
|
||||
(('ine', 'a@bool', False), a),
|
||||
(('ieq', 'a@bool', False), ('inot', 'a')),
|
||||
(('bcsel', a, True, False), ('ine', a, 0)),
|
||||
(('bcsel', a, False, True), ('ieq', a, 0)),
|
||||
(('bcsel', True, b, c), b),
|
||||
(('bcsel', False, b, c), c),
|
||||
# The result of this should be hit by constant propagation and, in the
|
||||
# next round of opt_algebraic, get picked up by one of the above two.
|
||||
(('bcsel', '#a', b, c), ('bcsel', ('ine', 'a', 0), b, c)),
|
||||
|
||||
(('bcsel', a, b, b), b),
|
||||
(('fcsel', a, b, b), b),
|
||||
|
||||
# Conversions
|
||||
(('i2b', ('b2i', a)), a),
|
||||
(('f2i', ('ftrunc', a)), ('f2i', a)),
|
||||
(('f2u', ('ftrunc', a)), ('f2u', a)),
|
||||
|
||||
# Subtracts
|
||||
(('fsub', a, ('fsub', 0.0, b)), ('fadd', a, b)),
|
||||
(('isub', a, ('isub', 0, b)), ('iadd', a, b)),
|
||||
(('ussub_4x8', a, 0), a),
|
||||
(('ussub_4x8', a, ~0), 0),
|
||||
(('fsub', a, b), ('fadd', a, ('fneg', b)), 'options->lower_sub'),
|
||||
(('isub', a, b), ('iadd', a, ('ineg', b)), 'options->lower_sub'),
|
||||
(('fneg', a), ('fsub', 0.0, a), 'options->lower_negate'),
|
||||
(('ineg', a), ('isub', 0, a), 'options->lower_negate'),
|
||||
(('fadd', a, ('fsub', 0.0, b)), ('fsub', a, b)),
|
||||
(('iadd', a, ('isub', 0, b)), ('isub', a, b)),
|
||||
(('fabs', ('fsub', 0.0, a)), ('fabs', a)),
|
||||
(('iabs', ('isub', 0, a)), ('iabs', a)),
|
||||
|
||||
# Misc. lowering
|
||||
(('fmod', a, b), ('fsub', a, ('fmul', b, ('ffloor', ('fdiv', a, b)))), 'options->lower_fmod'),
|
||||
(('uadd_carry', a, b), ('b2i', ('ult', ('iadd', a, b), a)), 'options->lower_uadd_carry'),
|
||||
(('usub_borrow', a, b), ('b2i', ('ult', a, b)), 'options->lower_usub_borrow'),
|
||||
|
||||
(('bitfield_insert', 'base', 'insert', 'offset', 'bits'),
|
||||
('bcsel', ('ilt', 31, 'bits'), 'insert',
|
||||
('bfi', ('bfm', 'bits', 'offset'), 'insert', 'base')),
|
||||
'options->lower_bitfield_insert'),
|
||||
|
||||
(('ibitfield_extract', 'value', 'offset', 'bits'),
|
||||
('bcsel', ('ilt', 31, 'bits'), 'value',
|
||||
('ibfe', 'value', 'offset', 'bits')),
|
||||
'options->lower_bitfield_extract'),
|
||||
|
||||
(('ubitfield_extract', 'value', 'offset', 'bits'),
|
||||
('bcsel', ('ult', 31, 'bits'), 'value',
|
||||
('ubfe', 'value', 'offset', 'bits')),
|
||||
'options->lower_bitfield_extract'),
|
||||
]
|
||||
|
||||
# Add optimizations to handle the case where the result of a ternary is
|
||||
# compared to a constant. This way we can take things like
|
||||
#
|
||||
# (a ? 0 : 1) > 0
|
||||
#
|
||||
# and turn it into
|
||||
#
|
||||
# a ? (0 > 0) : (1 > 0)
|
||||
#
|
||||
# which constant folding will eat for lunch. The resulting ternary will
|
||||
# further get cleaned up by the boolean reductions above and we will be
|
||||
# left with just the original variable "a".
|
||||
for op in ['flt', 'fge', 'feq', 'fne',
|
||||
'ilt', 'ige', 'ieq', 'ine', 'ult', 'uge']:
|
||||
optimizations += [
|
||||
((op, ('bcsel', 'a', '#b', '#c'), '#d'),
|
||||
('bcsel', 'a', (op, 'b', 'd'), (op, 'c', 'd'))),
|
||||
((op, '#d', ('bcsel', a, '#b', '#c')),
|
||||
('bcsel', 'a', (op, 'd', 'b'), (op, 'd', 'c'))),
|
||||
]
|
||||
|
||||
# This section contains "late" optimizations that should be run after the
|
||||
# regular optimizations have finished. Optimizations should go here if
|
||||
# they help code generation but do not necessarily produce code that is
|
||||
# more easily optimizable.
|
||||
late_optimizations = [
|
||||
(('flt', ('fadd', a, b), 0.0), ('flt', a, ('fneg', b))),
|
||||
(('fge', ('fadd', a, b), 0.0), ('fge', a, ('fneg', b))),
|
||||
(('feq', ('fadd', a, b), 0.0), ('feq', a, ('fneg', b))),
|
||||
(('fne', ('fadd', a, b), 0.0), ('fne', a, ('fneg', b))),
|
||||
(('fdot2', a, b), ('fdot_replicated2', a, b), 'options->fdot_replicates'),
|
||||
(('fdot3', a, b), ('fdot_replicated3', a, b), 'options->fdot_replicates'),
|
||||
(('fdot4', a, b), ('fdot_replicated4', a, b), 'options->fdot_replicates'),
|
||||
(('fdph', a, b), ('fdph_replicated', a, b), 'options->fdot_replicates'),
|
||||
]
|
||||
|
||||
print nir_algebraic.AlgebraicPass("nir_opt_algebraic", optimizations).render()
|
||||
print nir_algebraic.AlgebraicPass("nir_opt_algebraic_late",
|
||||
late_optimizations).render()
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir_constant_expressions.h"
|
||||
#include <math.h>
|
||||
|
||||
/*
|
||||
* Implements SSA-based constant folding.
|
||||
*/
|
||||
|
||||
struct constant_fold_state {
|
||||
void *mem_ctx;
|
||||
nir_function_impl *impl;
|
||||
bool progress;
|
||||
};
|
||||
|
||||
static bool
|
||||
constant_fold_alu_instr(nir_alu_instr *instr, void *mem_ctx)
|
||||
{
|
||||
nir_const_value src[4];
|
||||
|
||||
if (!instr->dest.dest.is_ssa)
|
||||
return false;
|
||||
|
||||
for (unsigned i = 0; i < nir_op_infos[instr->op].num_inputs; i++) {
|
||||
if (!instr->src[i].src.is_ssa)
|
||||
return false;
|
||||
|
||||
nir_instr *src_instr = instr->src[i].src.ssa->parent_instr;
|
||||
|
||||
if (src_instr->type != nir_instr_type_load_const)
|
||||
return false;
|
||||
nir_load_const_instr* load_const = nir_instr_as_load_const(src_instr);
|
||||
|
||||
for (unsigned j = 0; j < nir_ssa_alu_instr_src_components(instr, i);
|
||||
j++) {
|
||||
src[i].u[j] = load_const->value.u[instr->src[i].swizzle[j]];
|
||||
}
|
||||
|
||||
/* We shouldn't have any source modifiers in the optimization loop. */
|
||||
assert(!instr->src[i].abs && !instr->src[i].negate);
|
||||
}
|
||||
|
||||
/* We shouldn't have any saturate modifiers in the optimization loop. */
|
||||
assert(!instr->dest.saturate);
|
||||
|
||||
nir_const_value dest =
|
||||
nir_eval_const_opcode(instr->op, instr->dest.dest.ssa.num_components,
|
||||
src);
|
||||
|
||||
nir_load_const_instr *new_instr =
|
||||
nir_load_const_instr_create(mem_ctx,
|
||||
instr->dest.dest.ssa.num_components);
|
||||
|
||||
new_instr->value = dest;
|
||||
|
||||
nir_instr_insert_before(&instr->instr, &new_instr->instr);
|
||||
|
||||
nir_ssa_def_rewrite_uses(&instr->dest.dest.ssa,
|
||||
nir_src_for_ssa(&new_instr->def));
|
||||
|
||||
nir_instr_remove(&instr->instr);
|
||||
ralloc_free(instr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
constant_fold_deref(nir_instr *instr, nir_deref_var *deref)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
for (nir_deref *tail = deref->deref.child; tail; tail = tail->child) {
|
||||
if (tail->deref_type != nir_deref_type_array)
|
||||
continue;
|
||||
|
||||
nir_deref_array *arr = nir_deref_as_array(tail);
|
||||
|
||||
if (arr->deref_array_type == nir_deref_array_type_indirect &&
|
||||
arr->indirect.is_ssa &&
|
||||
arr->indirect.ssa->parent_instr->type == nir_instr_type_load_const) {
|
||||
nir_load_const_instr *indirect =
|
||||
nir_instr_as_load_const(arr->indirect.ssa->parent_instr);
|
||||
|
||||
arr->base_offset += indirect->value.u[0];
|
||||
|
||||
/* Clear out the source */
|
||||
nir_instr_rewrite_src(instr, &arr->indirect, nir_src_for_ssa(NULL));
|
||||
|
||||
arr->deref_array_type = nir_deref_array_type_direct;
|
||||
|
||||
progress = true;
|
||||
}
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
static bool
|
||||
constant_fold_intrinsic_instr(nir_intrinsic_instr *instr)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
unsigned num_vars = nir_intrinsic_infos[instr->intrinsic].num_variables;
|
||||
for (unsigned i = 0; i < num_vars; i++) {
|
||||
progress |= constant_fold_deref(&instr->instr, instr->variables[i]);
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
static bool
|
||||
constant_fold_tex_instr(nir_tex_instr *instr)
|
||||
{
|
||||
if (instr->sampler)
|
||||
return constant_fold_deref(&instr->instr, instr->sampler);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
constant_fold_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct constant_fold_state *state = void_state;
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
switch (instr->type) {
|
||||
case nir_instr_type_alu:
|
||||
state->progress |= constant_fold_alu_instr(nir_instr_as_alu(instr),
|
||||
state->mem_ctx);
|
||||
break;
|
||||
case nir_instr_type_intrinsic:
|
||||
state->progress |=
|
||||
constant_fold_intrinsic_instr(nir_instr_as_intrinsic(instr));
|
||||
break;
|
||||
case nir_instr_type_tex:
|
||||
state->progress |= constant_fold_tex_instr(nir_instr_as_tex(instr));
|
||||
break;
|
||||
default:
|
||||
/* Don't know how to constant fold */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
nir_opt_constant_folding_impl(nir_function_impl *impl)
|
||||
{
|
||||
struct constant_fold_state state;
|
||||
|
||||
state.mem_ctx = ralloc_parent(impl);
|
||||
state.impl = impl;
|
||||
state.progress = false;
|
||||
|
||||
nir_foreach_block(impl, constant_fold_block, &state);
|
||||
|
||||
if (state.progress)
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
|
||||
return state.progress;
|
||||
}
|
||||
|
||||
bool
|
||||
nir_opt_constant_folding(nir_shader *shader)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
progress |= nir_opt_constant_folding_impl(function->impl);
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* Copyright © 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include <main/imports.h>
|
||||
|
||||
/**
|
||||
* SSA-based copy propagation
|
||||
*/
|
||||
|
||||
static bool is_move(nir_alu_instr *instr)
|
||||
{
|
||||
if (instr->op != nir_op_fmov &&
|
||||
instr->op != nir_op_imov)
|
||||
return false;
|
||||
|
||||
if (instr->dest.saturate)
|
||||
return false;
|
||||
|
||||
/* we handle modifiers in a separate pass */
|
||||
|
||||
if (instr->src[0].abs || instr->src[0].negate)
|
||||
return false;
|
||||
|
||||
if (!instr->src[0].src.is_ssa)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
static bool is_vec(nir_alu_instr *instr)
|
||||
{
|
||||
for (unsigned i = 0; i < nir_op_infos[instr->op].num_inputs; i++) {
|
||||
if (!instr->src[i].src.is_ssa)
|
||||
return false;
|
||||
|
||||
/* we handle modifiers in a separate pass */
|
||||
if (instr->src[i].abs || instr->src[i].negate)
|
||||
return false;
|
||||
}
|
||||
|
||||
return instr->op == nir_op_vec2 ||
|
||||
instr->op == nir_op_vec3 ||
|
||||
instr->op == nir_op_vec4;
|
||||
}
|
||||
|
||||
static bool
|
||||
is_swizzleless_move(nir_alu_instr *instr)
|
||||
{
|
||||
if (is_move(instr)) {
|
||||
for (unsigned i = 0; i < 4; i++) {
|
||||
if (!((instr->dest.write_mask >> i) & 1))
|
||||
break;
|
||||
if (instr->src[0].swizzle[i] != i)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else if (is_vec(instr)) {
|
||||
nir_ssa_def *def = NULL;
|
||||
for (unsigned i = 0; i < nir_op_infos[instr->op].num_inputs; i++) {
|
||||
if (instr->src[i].swizzle[0] != i)
|
||||
return false;
|
||||
|
||||
if (def == NULL) {
|
||||
def = instr->src[i].src.ssa;
|
||||
} else if (instr->src[i].src.ssa != def) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
copy_prop_src(nir_src *src, nir_instr *parent_instr, nir_if *parent_if)
|
||||
{
|
||||
if (!src->is_ssa) {
|
||||
if (src->reg.indirect)
|
||||
return copy_prop_src(src, parent_instr, parent_if);
|
||||
return false;
|
||||
}
|
||||
|
||||
nir_instr *src_instr = src->ssa->parent_instr;
|
||||
if (src_instr->type != nir_instr_type_alu)
|
||||
return false;
|
||||
|
||||
nir_alu_instr *alu_instr = nir_instr_as_alu(src_instr);
|
||||
if (!is_swizzleless_move(alu_instr))
|
||||
return false;
|
||||
|
||||
/* Don't let copy propagation land us with a phi that has more
|
||||
* components in its source than it has in its destination. That badly
|
||||
* messes up out-of-ssa.
|
||||
*/
|
||||
if (parent_instr && parent_instr->type == nir_instr_type_phi) {
|
||||
nir_phi_instr *phi = nir_instr_as_phi(parent_instr);
|
||||
assert(phi->dest.is_ssa);
|
||||
if (phi->dest.ssa.num_components !=
|
||||
alu_instr->src[0].src.ssa->num_components)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parent_instr) {
|
||||
nir_instr_rewrite_src(parent_instr, src,
|
||||
nir_src_for_ssa(alu_instr->src[0].src.ssa));
|
||||
} else {
|
||||
assert(src == &parent_if->condition);
|
||||
nir_if_rewrite_condition(parent_if,
|
||||
nir_src_for_ssa(alu_instr->src[0].src.ssa));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
copy_prop_alu_src(nir_alu_instr *parent_alu_instr, unsigned index)
|
||||
{
|
||||
nir_alu_src *src = &parent_alu_instr->src[index];
|
||||
if (!src->src.is_ssa) {
|
||||
if (src->src.reg.indirect)
|
||||
return copy_prop_src(src->src.reg.indirect, &parent_alu_instr->instr,
|
||||
NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
nir_instr *src_instr = src->src.ssa->parent_instr;
|
||||
if (src_instr->type != nir_instr_type_alu)
|
||||
return false;
|
||||
|
||||
nir_alu_instr *alu_instr = nir_instr_as_alu(src_instr);
|
||||
if (!is_move(alu_instr) && !is_vec(alu_instr))
|
||||
return false;
|
||||
|
||||
nir_ssa_def *def;
|
||||
unsigned new_swizzle[4] = {0, 0, 0, 0};
|
||||
|
||||
if (alu_instr->op == nir_op_fmov ||
|
||||
alu_instr->op == nir_op_imov) {
|
||||
for (unsigned i = 0; i < 4; i++)
|
||||
new_swizzle[i] = alu_instr->src[0].swizzle[src->swizzle[i]];
|
||||
def = alu_instr->src[0].src.ssa;
|
||||
} else {
|
||||
def = NULL;
|
||||
|
||||
for (unsigned i = 0; i < 4; i++) {
|
||||
if (!nir_alu_instr_channel_used(parent_alu_instr, index, i))
|
||||
continue;
|
||||
|
||||
nir_ssa_def *new_def = alu_instr->src[src->swizzle[i]].src.ssa;
|
||||
if (def == NULL)
|
||||
def = new_def;
|
||||
else {
|
||||
if (def != new_def)
|
||||
return false;
|
||||
}
|
||||
new_swizzle[i] = alu_instr->src[src->swizzle[i]].swizzle[0];
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < 4; i++)
|
||||
src->swizzle[i] = new_swizzle[i];
|
||||
|
||||
nir_instr_rewrite_src(&parent_alu_instr->instr, &src->src,
|
||||
nir_src_for_ssa(def));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
nir_instr *parent_instr;
|
||||
bool progress;
|
||||
} copy_prop_state;
|
||||
|
||||
static bool
|
||||
copy_prop_src_cb(nir_src *src, void *_state)
|
||||
{
|
||||
copy_prop_state *state = (copy_prop_state *) _state;
|
||||
while (copy_prop_src(src, state->parent_instr, NULL))
|
||||
state->progress = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
copy_prop_instr(nir_instr *instr)
|
||||
{
|
||||
if (instr->type == nir_instr_type_alu) {
|
||||
nir_alu_instr *alu_instr = nir_instr_as_alu(instr);
|
||||
bool progress = false;
|
||||
|
||||
for (unsigned i = 0; i < nir_op_infos[alu_instr->op].num_inputs; i++)
|
||||
while (copy_prop_alu_src(alu_instr, i))
|
||||
progress = true;
|
||||
|
||||
if (!alu_instr->dest.dest.is_ssa && alu_instr->dest.dest.reg.indirect)
|
||||
while (copy_prop_src(alu_instr->dest.dest.reg.indirect, instr, NULL))
|
||||
progress = true;
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
copy_prop_state state;
|
||||
state.parent_instr = instr;
|
||||
state.progress = false;
|
||||
nir_foreach_src(instr, copy_prop_src_cb, &state);
|
||||
|
||||
return state.progress;
|
||||
}
|
||||
|
||||
static bool
|
||||
copy_prop_if(nir_if *if_stmt)
|
||||
{
|
||||
return copy_prop_src(&if_stmt->condition, NULL, if_stmt);
|
||||
}
|
||||
|
||||
static bool
|
||||
copy_prop_block(nir_block *block, void *_state)
|
||||
{
|
||||
bool *progress = (bool *) _state;
|
||||
|
||||
nir_foreach_instr(block, instr) {
|
||||
if (copy_prop_instr(instr))
|
||||
*progress = true;
|
||||
}
|
||||
|
||||
if (block->cf_node.node.next != NULL && /* check that we aren't the end node */
|
||||
!nir_cf_node_is_last(&block->cf_node) &&
|
||||
nir_cf_node_next(&block->cf_node)->type == nir_cf_node_if) {
|
||||
nir_if *if_stmt = nir_cf_node_as_if(nir_cf_node_next(&block->cf_node));
|
||||
if (copy_prop_if(if_stmt))
|
||||
*progress = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
nir_copy_prop_impl(nir_function_impl *impl)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
nir_foreach_block(impl, copy_prop_block, &progress);
|
||||
|
||||
if (progress) {
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
bool
|
||||
nir_copy_prop(nir_shader *shader)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl && nir_copy_prop_impl(function->impl))
|
||||
progress = true;
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir_instr_set.h"
|
||||
|
||||
/*
|
||||
* Implements common subexpression elimination
|
||||
*/
|
||||
|
||||
/*
|
||||
* Visits and CSE's the given block and all its descendants in the dominance
|
||||
* tree recursively. Note that the instr_set is guaranteed to only ever
|
||||
* contain instructions that dominate the current block.
|
||||
*/
|
||||
|
||||
static bool
|
||||
cse_block(nir_block *block, struct set *instr_set)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (nir_instr_set_add_or_rewrite(instr_set, instr)) {
|
||||
progress = true;
|
||||
nir_instr_remove(instr);
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < block->num_dom_children; i++) {
|
||||
nir_block *child = block->dom_children[i];
|
||||
progress |= cse_block(child, instr_set);
|
||||
}
|
||||
|
||||
nir_foreach_instr(block, instr)
|
||||
nir_instr_set_remove(instr_set, instr);
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
static bool
|
||||
nir_opt_cse_impl(nir_function_impl *impl)
|
||||
{
|
||||
struct set *instr_set = nir_instr_set_create(NULL);
|
||||
|
||||
nir_metadata_require(impl, nir_metadata_dominance);
|
||||
|
||||
bool progress = cse_block(nir_start_block(impl), instr_set);
|
||||
|
||||
if (progress)
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
|
||||
nir_instr_set_destroy(instr_set);
|
||||
return progress;
|
||||
}
|
||||
|
||||
bool
|
||||
nir_opt_cse(nir_shader *shader)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
progress |= nir_opt_cse_impl(function->impl);
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright © 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
/* SSA-based mark-and-sweep dead code elimination */
|
||||
|
||||
typedef struct {
|
||||
struct exec_node node;
|
||||
nir_instr *instr;
|
||||
} worklist_elem;
|
||||
|
||||
static void
|
||||
worklist_push(struct exec_list *worklist, nir_instr *instr)
|
||||
{
|
||||
worklist_elem *elem = ralloc(worklist, worklist_elem);
|
||||
elem->instr = instr;
|
||||
instr->pass_flags = 1;
|
||||
exec_list_push_tail(worklist, &elem->node);
|
||||
}
|
||||
|
||||
static nir_instr *
|
||||
worklist_pop(struct exec_list *worklist)
|
||||
{
|
||||
struct exec_node *node = exec_list_pop_head(worklist);
|
||||
worklist_elem *elem = exec_node_data(worklist_elem, node, node);
|
||||
return elem->instr;
|
||||
}
|
||||
|
||||
static bool
|
||||
mark_live_cb(nir_src *src, void *_state)
|
||||
{
|
||||
struct exec_list *worklist = (struct exec_list *) _state;
|
||||
|
||||
if (src->is_ssa && !src->ssa->parent_instr->pass_flags) {
|
||||
worklist_push(worklist, src->ssa->parent_instr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
init_instr(nir_instr *instr, struct exec_list *worklist)
|
||||
{
|
||||
nir_alu_instr *alu_instr;
|
||||
nir_intrinsic_instr *intrin_instr;
|
||||
nir_tex_instr *tex_instr;
|
||||
|
||||
/* We use the pass_flags to store the live/dead information. In DCE, we
|
||||
* just treat it as a zero/non-zerl boolean for whether or not the
|
||||
* instruction is live.
|
||||
*/
|
||||
instr->pass_flags = 0;
|
||||
|
||||
switch (instr->type) {
|
||||
case nir_instr_type_call:
|
||||
case nir_instr_type_jump:
|
||||
worklist_push(worklist, instr);
|
||||
break;
|
||||
|
||||
case nir_instr_type_alu:
|
||||
alu_instr = nir_instr_as_alu(instr);
|
||||
if (!alu_instr->dest.dest.is_ssa)
|
||||
worklist_push(worklist, instr);
|
||||
break;
|
||||
|
||||
case nir_instr_type_intrinsic:
|
||||
intrin_instr = nir_instr_as_intrinsic(instr);
|
||||
if (nir_intrinsic_infos[intrin_instr->intrinsic].flags &
|
||||
NIR_INTRINSIC_CAN_ELIMINATE) {
|
||||
if (nir_intrinsic_infos[intrin_instr->intrinsic].has_dest &&
|
||||
!intrin_instr->dest.is_ssa) {
|
||||
worklist_push(worklist, instr);
|
||||
}
|
||||
} else {
|
||||
worklist_push(worklist, instr);
|
||||
}
|
||||
break;
|
||||
|
||||
case nir_instr_type_tex:
|
||||
tex_instr = nir_instr_as_tex(instr);
|
||||
if (!tex_instr->dest.is_ssa)
|
||||
worklist_push(worklist, instr);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
init_block_cb(nir_block *block, void *_state)
|
||||
{
|
||||
struct exec_list *worklist = (struct exec_list *) _state;
|
||||
|
||||
nir_foreach_instr(block, instr)
|
||||
init_instr(instr, worklist);
|
||||
|
||||
nir_if *following_if = nir_block_get_following_if(block);
|
||||
if (following_if) {
|
||||
if (following_if->condition.is_ssa &&
|
||||
!following_if->condition.ssa->parent_instr->pass_flags)
|
||||
worklist_push(worklist, following_if->condition.ssa->parent_instr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
delete_block_cb(nir_block *block, void *_state)
|
||||
{
|
||||
bool *progress = (bool *) _state;
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (!instr->pass_flags) {
|
||||
nir_instr_remove(instr);
|
||||
*progress = true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
nir_opt_dce_impl(nir_function_impl *impl)
|
||||
{
|
||||
struct exec_list *worklist = ralloc(NULL, struct exec_list);
|
||||
exec_list_make_empty(worklist);
|
||||
|
||||
nir_foreach_block(impl, init_block_cb, worklist);
|
||||
|
||||
while (!exec_list_is_empty(worklist)) {
|
||||
nir_instr *instr = worklist_pop(worklist);
|
||||
nir_foreach_src(instr, mark_live_cb, worklist);
|
||||
}
|
||||
|
||||
ralloc_free(worklist);
|
||||
|
||||
bool progress = false;
|
||||
nir_foreach_block(impl, delete_block_cb, &progress);
|
||||
|
||||
if (progress)
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
bool
|
||||
nir_opt_dce(nir_shader *shader)
|
||||
{
|
||||
bool progress = false;
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl && nir_opt_dce_impl(function->impl))
|
||||
progress = true;
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright © 2014 Connor Abbott
|
||||
*
|
||||
* 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "nir_control_flow.h"
|
||||
|
||||
/*
|
||||
* This file implements an optimization that deletes statically
|
||||
* unreachable/dead code. In NIR, one way this can happen if if an if
|
||||
* statement has a constant condition:
|
||||
*
|
||||
* if (true) {
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* We delete the if statement and paste the contents of the always-executed
|
||||
* branch into the surrounding control flow, possibly removing more code if
|
||||
* the branch had a jump at the end.
|
||||
*
|
||||
* Another way is that control flow can end in a jump so that code after it
|
||||
* never gets executed. In particular, this can happen after optimizing
|
||||
* something like:
|
||||
*
|
||||
* if (true) {
|
||||
* ...
|
||||
* break;
|
||||
* }
|
||||
* ...
|
||||
*
|
||||
* We also consider the case where both branches of an if end in a jump, e.g.:
|
||||
*
|
||||
* if (...) {
|
||||
* break;
|
||||
* } else {
|
||||
* continue;
|
||||
* }
|
||||
* ...
|
||||
*
|
||||
* Finally, we also handle removing useless loops, i.e. loops with no side
|
||||
* effects and without any definitions that are used elsewhere. This case is a
|
||||
* little different from the first two in that the code is actually run (it
|
||||
* just never does anything), but there are similar issues with needing to
|
||||
* be careful with restarting after deleting the cf_node (see dead_cf_list())
|
||||
* so this is a convenient place to remove them.
|
||||
*/
|
||||
|
||||
static void
|
||||
remove_after_cf_node(nir_cf_node *node)
|
||||
{
|
||||
nir_cf_node *end = node;
|
||||
while (!nir_cf_node_is_last(end))
|
||||
end = nir_cf_node_next(end);
|
||||
|
||||
nir_cf_list list;
|
||||
nir_cf_extract(&list, nir_after_cf_node(node), nir_after_cf_node(end));
|
||||
nir_cf_delete(&list);
|
||||
}
|
||||
|
||||
static void
|
||||
opt_constant_if(nir_if *if_stmt, bool condition)
|
||||
{
|
||||
/* First, we need to remove any phi nodes after the if by rewriting uses to
|
||||
* point to the correct source.
|
||||
*/
|
||||
nir_block *after = nir_cf_node_as_block(nir_cf_node_next(&if_stmt->cf_node));
|
||||
nir_block *last_block =
|
||||
nir_cf_node_as_block(condition ? nir_if_last_then_node(if_stmt)
|
||||
: nir_if_last_else_node(if_stmt));
|
||||
|
||||
nir_foreach_instr_safe(after, instr) {
|
||||
if (instr->type != nir_instr_type_phi)
|
||||
break;
|
||||
|
||||
nir_phi_instr *phi = nir_instr_as_phi(instr);
|
||||
nir_ssa_def *def = NULL;
|
||||
nir_foreach_phi_src(phi, phi_src) {
|
||||
if (phi_src->pred != last_block)
|
||||
continue;
|
||||
|
||||
assert(phi_src->src.is_ssa);
|
||||
def = phi_src->src.ssa;
|
||||
}
|
||||
|
||||
assert(def);
|
||||
assert(phi->dest.is_ssa);
|
||||
nir_ssa_def_rewrite_uses(&phi->dest.ssa, nir_src_for_ssa(def));
|
||||
nir_instr_remove(instr);
|
||||
}
|
||||
|
||||
/* The control flow list we're about to paste in may include a jump at the
|
||||
* end, and in that case we have to delete the rest of the control flow
|
||||
* list after the if since it's unreachable and the validator will balk if
|
||||
* we don't.
|
||||
*/
|
||||
|
||||
if (!exec_list_is_empty(&last_block->instr_list)) {
|
||||
nir_instr *last_instr = nir_block_last_instr(last_block);
|
||||
if (last_instr->type == nir_instr_type_jump)
|
||||
remove_after_cf_node(&if_stmt->cf_node);
|
||||
}
|
||||
|
||||
/* Finally, actually paste in the then or else branch and delete the if. */
|
||||
struct exec_list *cf_list = condition ? &if_stmt->then_list
|
||||
: &if_stmt->else_list;
|
||||
|
||||
nir_cf_list list;
|
||||
nir_cf_extract(&list, nir_before_cf_list(cf_list),
|
||||
nir_after_cf_list(cf_list));
|
||||
nir_cf_reinsert(&list, nir_after_cf_node(&if_stmt->cf_node));
|
||||
nir_cf_node_remove(&if_stmt->cf_node);
|
||||
}
|
||||
|
||||
static bool
|
||||
block_has_no_side_effects(nir_block *block, void *state)
|
||||
{
|
||||
(void) state;
|
||||
|
||||
nir_foreach_instr(block, instr) {
|
||||
if (instr->type == nir_instr_type_call)
|
||||
return false;
|
||||
|
||||
/* Return instructions can cause us to skip over other side-effecting
|
||||
* instructions after the loop, so consider them to have side effects
|
||||
* here.
|
||||
*/
|
||||
|
||||
if (instr->type == nir_instr_type_jump &&
|
||||
nir_instr_as_jump(instr)->type == nir_jump_return)
|
||||
return false;
|
||||
|
||||
if (instr->type != nir_instr_type_intrinsic)
|
||||
continue;
|
||||
|
||||
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
|
||||
if (!nir_intrinsic_infos[intrin->intrinsic].flags &
|
||||
NIR_INTRINSIC_CAN_ELIMINATE)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
def_not_live_out(nir_ssa_def *def, void *state)
|
||||
{
|
||||
nir_block *after = state;
|
||||
|
||||
return !BITSET_TEST(after->live_in, def->live_index);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test if a loop is dead. A loop is dead if:
|
||||
*
|
||||
* 1) It has no side effects (i.e. intrinsics which could possibly affect the
|
||||
* state of the program aside from producing an SSA value, indicated by a lack
|
||||
* of NIR_INTRINSIC_CAN_ELIMINATE).
|
||||
*
|
||||
* 2) It has no phi nodes after it, since those indicate values inside the
|
||||
* loop being used after the loop.
|
||||
*
|
||||
* 3) If there are no phi nodes after the loop, then the only way a value
|
||||
* defined inside the loop can be used outside the loop is if its definition
|
||||
* dominates the block after the loop. If none of the definitions that
|
||||
* dominate the loop exit are used outside the loop, then the loop is dead
|
||||
* and it can be deleted.
|
||||
*/
|
||||
|
||||
static bool
|
||||
loop_is_dead(nir_loop *loop)
|
||||
{
|
||||
nir_block *before = nir_cf_node_as_block(nir_cf_node_prev(&loop->cf_node));
|
||||
nir_block *after = nir_cf_node_as_block(nir_cf_node_next(&loop->cf_node));
|
||||
|
||||
if (!exec_list_is_empty(&after->instr_list) &&
|
||||
nir_block_first_instr(after)->type == nir_instr_type_phi)
|
||||
return false;
|
||||
|
||||
if (!nir_foreach_block_in_cf_node(&loop->cf_node, block_has_no_side_effects,
|
||||
NULL))
|
||||
return false;
|
||||
|
||||
nir_function_impl *impl = nir_cf_node_get_function(&loop->cf_node);
|
||||
nir_metadata_require(impl, nir_metadata_live_ssa_defs |
|
||||
nir_metadata_dominance);
|
||||
|
||||
for (nir_block *cur = after->imm_dom; cur != before; cur = cur->imm_dom) {
|
||||
nir_foreach_instr(cur, instr) {
|
||||
if (!nir_foreach_ssa_def(instr, def_not_live_out, after))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
dead_cf_block(nir_block *block)
|
||||
{
|
||||
nir_if *following_if = nir_block_get_following_if(block);
|
||||
if (following_if) {
|
||||
nir_const_value *const_value =
|
||||
nir_src_as_const_value(following_if->condition);
|
||||
|
||||
if (!const_value)
|
||||
return false;
|
||||
|
||||
opt_constant_if(following_if, const_value->u[0] != 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
nir_loop *following_loop = nir_block_get_following_loop(block);
|
||||
if (!following_loop)
|
||||
return false;
|
||||
|
||||
if (!loop_is_dead(following_loop))
|
||||
return false;
|
||||
|
||||
nir_cf_node_remove(&following_loop->cf_node);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ends_in_jump(nir_block *block)
|
||||
{
|
||||
if (exec_list_is_empty(&block->instr_list))
|
||||
return false;
|
||||
|
||||
nir_instr *instr = nir_block_last_instr(block);
|
||||
return instr->type == nir_instr_type_jump;
|
||||
}
|
||||
|
||||
static bool
|
||||
dead_cf_list(struct exec_list *list, bool *list_ends_in_jump)
|
||||
{
|
||||
bool progress = false;
|
||||
*list_ends_in_jump = false;
|
||||
|
||||
nir_cf_node *prev = NULL;
|
||||
|
||||
foreach_list_typed(nir_cf_node, cur, node, list) {
|
||||
switch (cur->type) {
|
||||
case nir_cf_node_block: {
|
||||
nir_block *block = nir_cf_node_as_block(cur);
|
||||
if (dead_cf_block(block)) {
|
||||
/* We just deleted the if or loop after this block, so we may have
|
||||
* deleted the block before or after it -- which one is an
|
||||
* implementation detail. Therefore, to recover the place we were
|
||||
* at, we have to use the previous cf_node.
|
||||
*/
|
||||
|
||||
if (prev) {
|
||||
cur = nir_cf_node_next(prev);
|
||||
} else {
|
||||
cur = exec_node_data(nir_cf_node, exec_list_get_head(list),
|
||||
node);
|
||||
}
|
||||
|
||||
block = nir_cf_node_as_block(cur);
|
||||
|
||||
progress = true;
|
||||
}
|
||||
|
||||
if (ends_in_jump(block)) {
|
||||
*list_ends_in_jump = true;
|
||||
|
||||
if (!exec_node_is_tail_sentinel(cur->node.next)) {
|
||||
remove_after_cf_node(cur);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case nir_cf_node_if: {
|
||||
nir_if *if_stmt = nir_cf_node_as_if(cur);
|
||||
bool then_ends_in_jump, else_ends_in_jump;
|
||||
progress |= dead_cf_list(&if_stmt->then_list, &then_ends_in_jump);
|
||||
progress |= dead_cf_list(&if_stmt->else_list, &else_ends_in_jump);
|
||||
|
||||
if (then_ends_in_jump && else_ends_in_jump) {
|
||||
*list_ends_in_jump = true;
|
||||
nir_block *next = nir_cf_node_as_block(nir_cf_node_next(cur));
|
||||
if (!exec_list_is_empty(&next->instr_list) ||
|
||||
!exec_node_is_tail_sentinel(next->cf_node.node.next)) {
|
||||
remove_after_cf_node(cur);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case nir_cf_node_loop: {
|
||||
nir_loop *loop = nir_cf_node_as_loop(cur);
|
||||
bool dummy;
|
||||
progress |= dead_cf_list(&loop->body, &dummy);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
unreachable("unknown cf node type");
|
||||
}
|
||||
|
||||
prev = cur;
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
static bool
|
||||
opt_dead_cf_impl(nir_function_impl *impl)
|
||||
{
|
||||
bool dummy;
|
||||
bool progress = dead_cf_list(&impl->body, &dummy);
|
||||
|
||||
if (progress)
|
||||
nir_metadata_preserve(impl, nir_metadata_none);
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
bool
|
||||
nir_opt_dead_cf(nir_shader *shader)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
nir_foreach_function(shader, function)
|
||||
if (function->impl)
|
||||
progress |= opt_dead_cf_impl(function->impl);
|
||||
|
||||
return progress;
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
/*
|
||||
* Implements Global Code Motion. A description of GCM can be found in
|
||||
* "Global Code Motion; Global Value Numbering" by Cliff Click.
|
||||
* Unfortunately, the algorithm presented in the paper is broken in a
|
||||
* number of ways. The algorithm used here differs substantially from the
|
||||
* one in the paper but it is, in my opinion, much easier to read and
|
||||
* verify correcness.
|
||||
*/
|
||||
|
||||
struct gcm_block_info {
|
||||
/* Number of loops this block is inside */
|
||||
unsigned loop_depth;
|
||||
|
||||
/* The last instruction inserted into this block. This is used as we
|
||||
* traverse the instructions and insert them back into the program to
|
||||
* put them in the right order.
|
||||
*/
|
||||
nir_instr *last_instr;
|
||||
};
|
||||
|
||||
/* Flags used in the instr->pass_flags field for various instruction states */
|
||||
enum {
|
||||
GCM_INSTR_PINNED = (1 << 0),
|
||||
GCM_INSTR_SCHEDULED_EARLY = (1 << 1),
|
||||
GCM_INSTR_SCHEDULED_LATE = (1 << 2),
|
||||
GCM_INSTR_PLACED = (1 << 3),
|
||||
};
|
||||
|
||||
struct gcm_state {
|
||||
nir_function_impl *impl;
|
||||
nir_instr *instr;
|
||||
|
||||
/* The list of non-pinned instructions. As we do the late scheduling,
|
||||
* we pull non-pinned instructions out of their blocks and place them in
|
||||
* this list. This saves us from having linked-list problems when we go
|
||||
* to put instructions back in their blocks.
|
||||
*/
|
||||
struct exec_list instrs;
|
||||
|
||||
struct gcm_block_info *blocks;
|
||||
};
|
||||
|
||||
/* Recursively walks the CFG and builds the block_info structure */
|
||||
static void
|
||||
gcm_build_block_info(struct exec_list *cf_list, struct gcm_state *state,
|
||||
unsigned loop_depth)
|
||||
{
|
||||
foreach_list_typed(nir_cf_node, node, node, cf_list) {
|
||||
switch (node->type) {
|
||||
case nir_cf_node_block: {
|
||||
nir_block *block = nir_cf_node_as_block(node);
|
||||
state->blocks[block->index].loop_depth = loop_depth;
|
||||
break;
|
||||
}
|
||||
case nir_cf_node_if: {
|
||||
nir_if *if_stmt = nir_cf_node_as_if(node);
|
||||
gcm_build_block_info(&if_stmt->then_list, state, loop_depth);
|
||||
gcm_build_block_info(&if_stmt->else_list, state, loop_depth);
|
||||
break;
|
||||
}
|
||||
case nir_cf_node_loop: {
|
||||
nir_loop *loop = nir_cf_node_as_loop(node);
|
||||
gcm_build_block_info(&loop->body, state, loop_depth + 1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable("Invalid CF node type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Walks the instruction list and marks immovable instructions as pinned
|
||||
*
|
||||
* This function also serves to initialize the instr->pass_flags field.
|
||||
* After this is completed, all instructions' pass_flags fields will be set
|
||||
* to either GCM_INSTR_PINNED or 0.
|
||||
*/
|
||||
static bool
|
||||
gcm_pin_instructions_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct gcm_state *state = void_state;
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
switch (instr->type) {
|
||||
case nir_instr_type_alu:
|
||||
switch (nir_instr_as_alu(instr)->op) {
|
||||
case nir_op_fddx:
|
||||
case nir_op_fddy:
|
||||
case nir_op_fddx_fine:
|
||||
case nir_op_fddy_fine:
|
||||
case nir_op_fddx_coarse:
|
||||
case nir_op_fddy_coarse:
|
||||
/* These can only go in uniform control flow; pin them for now */
|
||||
instr->pass_flags = GCM_INSTR_PINNED;
|
||||
break;
|
||||
|
||||
default:
|
||||
instr->pass_flags = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case nir_instr_type_tex:
|
||||
switch (nir_instr_as_tex(instr)->op) {
|
||||
case nir_texop_tex:
|
||||
case nir_texop_txb:
|
||||
case nir_texop_lod:
|
||||
/* These two take implicit derivatives so they need to be pinned */
|
||||
instr->pass_flags = GCM_INSTR_PINNED;
|
||||
break;
|
||||
|
||||
default:
|
||||
instr->pass_flags = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case nir_instr_type_load_const:
|
||||
instr->pass_flags = 0;
|
||||
break;
|
||||
|
||||
case nir_instr_type_intrinsic: {
|
||||
const nir_intrinsic_info *info =
|
||||
&nir_intrinsic_infos[nir_instr_as_intrinsic(instr)->intrinsic];
|
||||
|
||||
if ((info->flags & NIR_INTRINSIC_CAN_ELIMINATE) &&
|
||||
(info->flags & NIR_INTRINSIC_CAN_REORDER)) {
|
||||
instr->pass_flags = 0;
|
||||
} else {
|
||||
instr->pass_flags = GCM_INSTR_PINNED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case nir_instr_type_jump:
|
||||
case nir_instr_type_ssa_undef:
|
||||
case nir_instr_type_phi:
|
||||
instr->pass_flags = GCM_INSTR_PINNED;
|
||||
break;
|
||||
|
||||
default:
|
||||
unreachable("Invalid instruction type in GCM");
|
||||
}
|
||||
|
||||
if (!(instr->pass_flags & GCM_INSTR_PINNED)) {
|
||||
/* If this is an unpinned instruction, go ahead and pull it out of
|
||||
* the program and put it on the instrs list. This has a couple
|
||||
* of benifits. First, it makes the scheduling algorithm more
|
||||
* efficient because we can avoid walking over basic blocks and
|
||||
* pinned instructions. Second, it keeps us from causing linked
|
||||
* list confusion when we're trying to put everything in its
|
||||
* proper place at the end of the pass.
|
||||
*
|
||||
* Note that we don't use nir_instr_remove here because that also
|
||||
* cleans up uses and defs and we want to keep that information.
|
||||
*/
|
||||
exec_node_remove(&instr->node);
|
||||
exec_list_push_tail(&state->instrs, &instr->node);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
gcm_schedule_early_instr(nir_instr *instr, struct gcm_state *state);
|
||||
|
||||
/** Update an instructions schedule for the given source
|
||||
*
|
||||
* This function is called iteratively as we walk the sources of an
|
||||
* instruction. It ensures that the given source instruction has been
|
||||
* scheduled and then update this instruction's block if the source
|
||||
* instruction is lower down the tree.
|
||||
*/
|
||||
static bool
|
||||
gcm_schedule_early_src(nir_src *src, void *void_state)
|
||||
{
|
||||
struct gcm_state *state = void_state;
|
||||
nir_instr *instr = state->instr;
|
||||
|
||||
assert(src->is_ssa);
|
||||
|
||||
gcm_schedule_early_instr(src->ssa->parent_instr, void_state);
|
||||
|
||||
/* While the index isn't a proper dominance depth, it does have the
|
||||
* property that if A dominates B then A->index <= B->index. Since we
|
||||
* know that this instruction must have been dominated by all of its
|
||||
* sources at some point (even if it's gone through value-numbering),
|
||||
* all of the sources must lie on the same branch of the dominance tree.
|
||||
* Therefore, we can just go ahead and just compare indices.
|
||||
*/
|
||||
if (instr->block->index < src->ssa->parent_instr->block->index)
|
||||
instr->block = src->ssa->parent_instr->block;
|
||||
|
||||
/* We need to restore the state instruction because it may have been
|
||||
* changed through the gcm_schedule_early_instr call above. Since we
|
||||
* may still be iterating through sources and future calls to
|
||||
* gcm_schedule_early_src for the same instruction will still need it.
|
||||
*/
|
||||
state->instr = instr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Schedules an instruction early
|
||||
*
|
||||
* This function performs a recursive depth-first search starting at the
|
||||
* given instruction and proceeding through the sources to schedule
|
||||
* instructions as early as they can possibly go in the dominance tree.
|
||||
* The instructions are "scheduled" by updating their instr->block field.
|
||||
*/
|
||||
static void
|
||||
gcm_schedule_early_instr(nir_instr *instr, struct gcm_state *state)
|
||||
{
|
||||
if (instr->pass_flags & GCM_INSTR_SCHEDULED_EARLY)
|
||||
return;
|
||||
|
||||
instr->pass_flags |= GCM_INSTR_SCHEDULED_EARLY;
|
||||
|
||||
/* Pinned instructions are already scheduled so we don't need to do
|
||||
* anything. Also, bailing here keeps us from ever following the
|
||||
* sources of phi nodes which can be back-edges.
|
||||
*/
|
||||
if (instr->pass_flags & GCM_INSTR_PINNED)
|
||||
return;
|
||||
|
||||
/* Start with the instruction at the top. As we iterate over the
|
||||
* sources, it will get moved down as needed.
|
||||
*/
|
||||
instr->block = nir_start_block(state->impl);
|
||||
state->instr = instr;
|
||||
|
||||
nir_foreach_src(instr, gcm_schedule_early_src, state);
|
||||
}
|
||||
|
||||
static void
|
||||
gcm_schedule_late_instr(nir_instr *instr, struct gcm_state *state);
|
||||
|
||||
/** Schedules the instruction associated with the given SSA def late
|
||||
*
|
||||
* This function works by first walking all of the uses of the given SSA
|
||||
* definition, ensuring that they are scheduled, and then computing the LCA
|
||||
* (least common ancestor) of its uses. It then schedules this instruction
|
||||
* as close to the LCA as possible while trying to stay out of loops.
|
||||
*/
|
||||
static bool
|
||||
gcm_schedule_late_def(nir_ssa_def *def, void *void_state)
|
||||
{
|
||||
struct gcm_state *state = void_state;
|
||||
|
||||
nir_block *lca = NULL;
|
||||
|
||||
nir_foreach_use(def, use_src) {
|
||||
nir_instr *use_instr = use_src->parent_instr;
|
||||
|
||||
gcm_schedule_late_instr(use_instr, state);
|
||||
|
||||
/* Phi instructions are a bit special. SSA definitions don't have to
|
||||
* dominate the sources of the phi nodes that use them; instead, they
|
||||
* have to dominate the predecessor block corresponding to the phi
|
||||
* source. We handle this by looking through the sources, finding
|
||||
* any that are usingg this SSA def, and using those blocks instead
|
||||
* of the one the phi lives in.
|
||||
*/
|
||||
if (use_instr->type == nir_instr_type_phi) {
|
||||
nir_phi_instr *phi = nir_instr_as_phi(use_instr);
|
||||
|
||||
nir_foreach_phi_src(phi, phi_src) {
|
||||
if (phi_src->src.ssa == def)
|
||||
lca = nir_dominance_lca(lca, phi_src->pred);
|
||||
}
|
||||
} else {
|
||||
lca = nir_dominance_lca(lca, use_instr->block);
|
||||
}
|
||||
}
|
||||
|
||||
nir_foreach_if_use(def, use_src) {
|
||||
nir_if *if_stmt = use_src->parent_if;
|
||||
|
||||
/* For if statements, we consider the block to be the one immediately
|
||||
* preceding the if CF node.
|
||||
*/
|
||||
nir_block *pred_block =
|
||||
nir_cf_node_as_block(nir_cf_node_prev(&if_stmt->cf_node));
|
||||
|
||||
lca = nir_dominance_lca(lca, pred_block);
|
||||
}
|
||||
|
||||
/* Some instructions may never be used. We'll just leave them scheduled
|
||||
* early and let dead code clean them up.
|
||||
*/
|
||||
if (lca == NULL)
|
||||
return true;
|
||||
|
||||
/* We know have the LCA of all of the uses. If our invariants hold,
|
||||
* this is dominated by the block that we chose when scheduling early.
|
||||
* We now walk up the dominance tree and pick the lowest block that is
|
||||
* as far outside loops as we can get.
|
||||
*/
|
||||
nir_block *best = lca;
|
||||
while (lca != def->parent_instr->block) {
|
||||
assert(lca);
|
||||
if (state->blocks[lca->index].loop_depth <
|
||||
state->blocks[best->index].loop_depth)
|
||||
best = lca;
|
||||
lca = lca->imm_dom;
|
||||
}
|
||||
def->parent_instr->block = best;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Schedules an instruction late
|
||||
*
|
||||
* This function performs a depth-first search starting at the given
|
||||
* instruction and proceeding through its uses to schedule instructions as
|
||||
* late as they can reasonably go in the dominance tree. The instructions
|
||||
* are "scheduled" by updating their instr->block field.
|
||||
*
|
||||
* The name of this function is actually a bit of a misnomer as it doesn't
|
||||
* schedule them "as late as possible" as the paper implies. Instead, it
|
||||
* first finds the lates possible place it can schedule the instruction and
|
||||
* then possibly schedules it earlier than that. The actual location is as
|
||||
* far down the tree as we can go while trying to stay out of loops.
|
||||
*/
|
||||
static void
|
||||
gcm_schedule_late_instr(nir_instr *instr, struct gcm_state *state)
|
||||
{
|
||||
if (instr->pass_flags & GCM_INSTR_SCHEDULED_LATE)
|
||||
return;
|
||||
|
||||
instr->pass_flags |= GCM_INSTR_SCHEDULED_LATE;
|
||||
|
||||
/* Pinned instructions are already scheduled so we don't need to do
|
||||
* anything. Also, bailing here keeps us from ever following phi nodes
|
||||
* which can be back-edges.
|
||||
*/
|
||||
if (instr->pass_flags & GCM_INSTR_PINNED)
|
||||
return;
|
||||
|
||||
nir_foreach_ssa_def(instr, gcm_schedule_late_def, state);
|
||||
}
|
||||
|
||||
static void
|
||||
gcm_place_instr(nir_instr *instr, struct gcm_state *state);
|
||||
|
||||
static bool
|
||||
gcm_place_instr_def(nir_ssa_def *def, void *state)
|
||||
{
|
||||
nir_foreach_use(def, use_src)
|
||||
gcm_place_instr(use_src->parent_instr, state);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Places an instrution back into the program
|
||||
*
|
||||
* The earlier passes of GCM simply choose blocks for each instruction and
|
||||
* otherwise leave them alone. This pass actually places the instructions
|
||||
* into their chosen blocks.
|
||||
*
|
||||
* To do so, we use a standard post-order depth-first search linearization
|
||||
* algorithm. We walk over the uses of the given instruction and ensure
|
||||
* that they are placed and then place this instruction. Because we are
|
||||
* working on multiple blocks at a time, we keep track of the last inserted
|
||||
* instruction per-block in the state structure's block_info array. When
|
||||
* we insert an instruction in a block we insert it before the last
|
||||
* instruction inserted in that block rather than the last instruction
|
||||
* inserted globally.
|
||||
*/
|
||||
static void
|
||||
gcm_place_instr(nir_instr *instr, struct gcm_state *state)
|
||||
{
|
||||
if (instr->pass_flags & GCM_INSTR_PLACED)
|
||||
return;
|
||||
|
||||
instr->pass_flags |= GCM_INSTR_PLACED;
|
||||
|
||||
/* Phi nodes are our once source of back-edges. Since right now we are
|
||||
* only doing scheduling within blocks, we don't need to worry about
|
||||
* them since they are always at the top. Just skip them completely.
|
||||
*/
|
||||
if (instr->type == nir_instr_type_phi) {
|
||||
assert(instr->pass_flags & GCM_INSTR_PINNED);
|
||||
return;
|
||||
}
|
||||
|
||||
nir_foreach_ssa_def(instr, gcm_place_instr_def, state);
|
||||
|
||||
if (instr->pass_flags & GCM_INSTR_PINNED) {
|
||||
/* Pinned instructions have an implicit dependence on the pinned
|
||||
* instructions that come after them in the block. Since the pinned
|
||||
* instructions will naturally "chain" together, we only need to
|
||||
* explicitly visit one of them.
|
||||
*/
|
||||
for (nir_instr *after = nir_instr_next(instr);
|
||||
after;
|
||||
after = nir_instr_next(after)) {
|
||||
if (after->pass_flags & GCM_INSTR_PINNED) {
|
||||
gcm_place_instr(after, state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct gcm_block_info *block_info = &state->blocks[instr->block->index];
|
||||
if (!(instr->pass_flags & GCM_INSTR_PINNED)) {
|
||||
exec_node_remove(&instr->node);
|
||||
|
||||
if (block_info->last_instr) {
|
||||
exec_node_insert_node_before(&block_info->last_instr->node,
|
||||
&instr->node);
|
||||
} else {
|
||||
/* Schedule it at the end of the block */
|
||||
nir_instr *jump_instr = nir_block_last_instr(instr->block);
|
||||
if (jump_instr && jump_instr->type == nir_instr_type_jump) {
|
||||
exec_node_insert_node_before(&jump_instr->node, &instr->node);
|
||||
} else {
|
||||
exec_list_push_tail(&instr->block->instr_list, &instr->node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
block_info->last_instr = instr;
|
||||
}
|
||||
|
||||
static void
|
||||
opt_gcm_impl(nir_function_impl *impl)
|
||||
{
|
||||
struct gcm_state state;
|
||||
|
||||
state.impl = impl;
|
||||
state.instr = NULL;
|
||||
exec_list_make_empty(&state.instrs);
|
||||
state.blocks = rzalloc_array(NULL, struct gcm_block_info, impl->num_blocks);
|
||||
|
||||
nir_metadata_require(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
|
||||
gcm_build_block_info(&impl->body, &state, 0);
|
||||
nir_foreach_block(impl, gcm_pin_instructions_block, &state);
|
||||
|
||||
foreach_list_typed(nir_instr, instr, node, &state.instrs)
|
||||
gcm_schedule_early_instr(instr, &state);
|
||||
|
||||
foreach_list_typed(nir_instr, instr, node, &state.instrs)
|
||||
gcm_schedule_late_instr(instr, &state);
|
||||
|
||||
while (!exec_list_is_empty(&state.instrs)) {
|
||||
nir_instr *instr = exec_node_data(nir_instr,
|
||||
state.instrs.tail_pred, node);
|
||||
gcm_place_instr(instr, &state);
|
||||
}
|
||||
|
||||
ralloc_free(state.blocks);
|
||||
}
|
||||
|
||||
void
|
||||
nir_opt_gcm(nir_shader *shader)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
opt_gcm_impl(function->impl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright © 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
static bool
|
||||
global_to_local(nir_register *reg)
|
||||
{
|
||||
nir_function_impl *impl = NULL;
|
||||
|
||||
assert(reg->is_global);
|
||||
|
||||
nir_foreach_def(reg, def_dest) {
|
||||
nir_instr *instr = def_dest->reg.parent_instr;
|
||||
nir_function_impl *instr_impl =
|
||||
nir_cf_node_get_function(&instr->block->cf_node);
|
||||
if (impl != NULL) {
|
||||
if (impl != instr_impl)
|
||||
return false;
|
||||
} else {
|
||||
impl = instr_impl;
|
||||
}
|
||||
}
|
||||
|
||||
nir_foreach_use(reg, use_src) {
|
||||
nir_instr *instr = use_src->parent_instr;
|
||||
nir_function_impl *instr_impl =
|
||||
nir_cf_node_get_function(&instr->block->cf_node);
|
||||
if (impl != NULL) {
|
||||
if (impl != instr_impl)
|
||||
return false;
|
||||
} else {
|
||||
impl = instr_impl;
|
||||
}
|
||||
}
|
||||
|
||||
nir_foreach_if_use(reg, use_src) {
|
||||
nir_if *if_stmt = use_src->parent_if;
|
||||
nir_function_impl *if_impl = nir_cf_node_get_function(&if_stmt->cf_node);
|
||||
if (impl != NULL) {
|
||||
if (impl != if_impl)
|
||||
return false;
|
||||
} else {
|
||||
impl = if_impl;
|
||||
}
|
||||
}
|
||||
|
||||
if (impl == NULL) {
|
||||
/* this instruction is never used/defined, delete it */
|
||||
nir_reg_remove(reg);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* if we've gotten to this point, the register is always used/defined in
|
||||
* the same implementation so we can move it to be local to that
|
||||
* implementation.
|
||||
*/
|
||||
|
||||
exec_node_remove(®->node);
|
||||
exec_list_push_tail(&impl->registers, ®->node);
|
||||
reg->index = impl->reg_alloc++;
|
||||
reg->is_global = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
nir_opt_global_to_local(nir_shader *shader)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
foreach_list_typed_safe(nir_register, reg, node, &shader->registers) {
|
||||
if (global_to_local(reg))
|
||||
progress = true;
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include "nir_control_flow.h"
|
||||
|
||||
/*
|
||||
* Implements a small peephole optimization that looks for
|
||||
*
|
||||
* if (cond) {
|
||||
* <empty>
|
||||
* } else {
|
||||
* <empty>
|
||||
* }
|
||||
* phi
|
||||
* ...
|
||||
* phi
|
||||
*
|
||||
* and replaces it with a series of selects. It can also handle the case
|
||||
* where, instead of being empty, the if may contain some move operations
|
||||
* whose only use is one of the following phi nodes. This happens all the
|
||||
* time when the SSA form comes from a conditional assignment with a
|
||||
* swizzle.
|
||||
*/
|
||||
|
||||
struct peephole_select_state {
|
||||
void *mem_ctx;
|
||||
bool progress;
|
||||
};
|
||||
|
||||
static bool
|
||||
block_check_for_allowed_instrs(nir_block *block)
|
||||
{
|
||||
nir_foreach_instr(block, instr) {
|
||||
switch (instr->type) {
|
||||
case nir_instr_type_intrinsic: {
|
||||
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
|
||||
|
||||
switch (intrin->intrinsic) {
|
||||
case nir_intrinsic_load_var:
|
||||
switch (intrin->variables[0]->var->data.mode) {
|
||||
case nir_var_shader_in:
|
||||
case nir_var_uniform:
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case nir_instr_type_load_const:
|
||||
break;
|
||||
|
||||
case nir_instr_type_alu: {
|
||||
nir_alu_instr *mov = nir_instr_as_alu(instr);
|
||||
switch (mov->op) {
|
||||
case nir_op_fmov:
|
||||
case nir_op_imov:
|
||||
case nir_op_fneg:
|
||||
case nir_op_ineg:
|
||||
case nir_op_fabs:
|
||||
case nir_op_iabs:
|
||||
case nir_op_vec2:
|
||||
case nir_op_vec3:
|
||||
case nir_op_vec4:
|
||||
/* It must be a move-like operation. */
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Can't handle saturate */
|
||||
if (mov->dest.saturate)
|
||||
return false;
|
||||
|
||||
/* It must be SSA */
|
||||
if (!mov->dest.dest.is_ssa)
|
||||
return false;
|
||||
|
||||
/* It cannot have any if-uses */
|
||||
if (!list_empty(&mov->dest.dest.ssa.if_uses))
|
||||
return false;
|
||||
|
||||
/* The only uses of this definition must be phi's in the successor */
|
||||
nir_foreach_use(&mov->dest.dest.ssa, use) {
|
||||
if (use->parent_instr->type != nir_instr_type_phi ||
|
||||
use->parent_instr->block != block->successors[0])
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
nir_opt_peephole_select_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct peephole_select_state *state = void_state;
|
||||
|
||||
/* If the block is empty, then it certainly doesn't have any phi nodes,
|
||||
* so we can skip it. This also ensures that we do an early skip on the
|
||||
* end block of the function which isn't actually attached to the CFG.
|
||||
*/
|
||||
if (exec_list_is_empty(&block->instr_list))
|
||||
return true;
|
||||
|
||||
if (nir_cf_node_is_first(&block->cf_node))
|
||||
return true;
|
||||
|
||||
nir_cf_node *prev_node = nir_cf_node_prev(&block->cf_node);
|
||||
if (prev_node->type != nir_cf_node_if)
|
||||
return true;
|
||||
|
||||
nir_if *if_stmt = nir_cf_node_as_if(prev_node);
|
||||
nir_cf_node *then_node = nir_if_first_then_node(if_stmt);
|
||||
nir_cf_node *else_node = nir_if_first_else_node(if_stmt);
|
||||
|
||||
/* We can only have one block in each side ... */
|
||||
if (nir_if_last_then_node(if_stmt) != then_node ||
|
||||
nir_if_last_else_node(if_stmt) != else_node)
|
||||
return true;
|
||||
|
||||
nir_block *then_block = nir_cf_node_as_block(then_node);
|
||||
nir_block *else_block = nir_cf_node_as_block(else_node);
|
||||
|
||||
/* ... and those blocks must only contain "allowed" instructions. */
|
||||
if (!block_check_for_allowed_instrs(then_block) ||
|
||||
!block_check_for_allowed_instrs(else_block))
|
||||
return true;
|
||||
|
||||
/* At this point, we know that the previous CFG node is an if-then
|
||||
* statement containing only moves to phi nodes in this block. We can
|
||||
* just remove that entire CF node and replace all of the phi nodes with
|
||||
* selects.
|
||||
*/
|
||||
|
||||
nir_block *prev_block = nir_cf_node_as_block(nir_cf_node_prev(prev_node));
|
||||
assert(prev_block->cf_node.type == nir_cf_node_block);
|
||||
|
||||
/* First, we move the remaining instructions from the blocks to the
|
||||
* block before. We have already guaranteed that this is safe by
|
||||
* calling block_check_for_allowed_instrs()
|
||||
*/
|
||||
nir_foreach_instr_safe(then_block, instr) {
|
||||
exec_node_remove(&instr->node);
|
||||
instr->block = prev_block;
|
||||
exec_list_push_tail(&prev_block->instr_list, &instr->node);
|
||||
}
|
||||
|
||||
nir_foreach_instr_safe(else_block, instr) {
|
||||
exec_node_remove(&instr->node);
|
||||
instr->block = prev_block;
|
||||
exec_list_push_tail(&prev_block->instr_list, &instr->node);
|
||||
}
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type != nir_instr_type_phi)
|
||||
break;
|
||||
|
||||
nir_phi_instr *phi = nir_instr_as_phi(instr);
|
||||
nir_alu_instr *sel = nir_alu_instr_create(state->mem_ctx, nir_op_bcsel);
|
||||
nir_src_copy(&sel->src[0].src, &if_stmt->condition, sel);
|
||||
/* Splat the condition to all channels */
|
||||
memset(sel->src[0].swizzle, 0, sizeof sel->src[0].swizzle);
|
||||
|
||||
assert(exec_list_length(&phi->srcs) == 2);
|
||||
nir_foreach_phi_src(phi, src) {
|
||||
assert(src->pred == then_block || src->pred == else_block);
|
||||
assert(src->src.is_ssa);
|
||||
|
||||
unsigned idx = src->pred == then_block ? 1 : 2;
|
||||
nir_src_copy(&sel->src[idx].src, &src->src, sel);
|
||||
}
|
||||
|
||||
nir_ssa_dest_init(&sel->instr, &sel->dest.dest,
|
||||
phi->dest.ssa.num_components, phi->dest.ssa.name);
|
||||
sel->dest.write_mask = (1 << phi->dest.ssa.num_components) - 1;
|
||||
|
||||
nir_ssa_def_rewrite_uses(&phi->dest.ssa,
|
||||
nir_src_for_ssa(&sel->dest.dest.ssa));
|
||||
|
||||
nir_instr_insert_before(&phi->instr, &sel->instr);
|
||||
nir_instr_remove(&phi->instr);
|
||||
}
|
||||
|
||||
nir_cf_node_remove(&if_stmt->cf_node);
|
||||
state->progress = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
nir_opt_peephole_select_impl(nir_function_impl *impl)
|
||||
{
|
||||
struct peephole_select_state state;
|
||||
|
||||
state.mem_ctx = ralloc_parent(impl);
|
||||
state.progress = false;
|
||||
|
||||
nir_foreach_block(impl, nir_opt_peephole_select_block, &state);
|
||||
|
||||
if (state.progress)
|
||||
nir_metadata_preserve(impl, nir_metadata_none);
|
||||
|
||||
return state.progress;
|
||||
}
|
||||
|
||||
bool
|
||||
nir_opt_peephole_select(nir_shader *shader)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
progress |= nir_opt_peephole_select_impl(function->impl);
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright © 2015 Connor Abbott
|
||||
*
|
||||
* 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
/*
|
||||
* This is a pass for removing phi nodes that look like:
|
||||
* a = phi(b, b, b, ...)
|
||||
*
|
||||
* Note that we can't ignore undef sources here, or else we may create a
|
||||
* situation where the definition of b isn't dominated by its uses. We're
|
||||
* allowed to do this since the definition of b must dominate all of the
|
||||
* phi node's predecessors, which means it must dominate the phi node as well
|
||||
* as all of the phi node's uses. In essence, the phi node acts as a copy
|
||||
* instruction. b can't be another phi node in the same block, since the only
|
||||
* time when phi nodes can source other phi nodes defined in the same block is
|
||||
* at the loop header, and in that case one of the sources of the phi has to
|
||||
* be from before the loop and that source can't be b.
|
||||
*/
|
||||
|
||||
static bool
|
||||
remove_phis_block(nir_block *block, void *state)
|
||||
{
|
||||
bool *progress = state;
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type != nir_instr_type_phi)
|
||||
break;
|
||||
|
||||
nir_phi_instr *phi = nir_instr_as_phi(instr);
|
||||
|
||||
nir_ssa_def *def = NULL;
|
||||
bool srcs_same = true;
|
||||
|
||||
nir_foreach_phi_src(phi, src) {
|
||||
assert(src->src.is_ssa);
|
||||
|
||||
/* For phi nodes at the beginning of loops, we may encounter some
|
||||
* sources from backedges that point back to the destination of the
|
||||
* same phi, i.e. something like:
|
||||
*
|
||||
* a = phi(a, b, ...)
|
||||
*
|
||||
* We can safely ignore these sources, since if all of the normal
|
||||
* sources point to the same definition, then that definition must
|
||||
* still dominate the phi node, and the phi will still always take
|
||||
* the value of that definition.
|
||||
*/
|
||||
if (src->src.ssa == &phi->dest.ssa)
|
||||
continue;
|
||||
|
||||
if (def == NULL) {
|
||||
def = src->src.ssa;
|
||||
} else {
|
||||
if (src->src.ssa != def) {
|
||||
srcs_same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!srcs_same)
|
||||
continue;
|
||||
|
||||
/* We must have found at least one definition, since there must be at
|
||||
* least one forward edge.
|
||||
*/
|
||||
assert(def != NULL);
|
||||
|
||||
assert(phi->dest.is_ssa);
|
||||
nir_ssa_def_rewrite_uses(&phi->dest.ssa, nir_src_for_ssa(def));
|
||||
nir_instr_remove(instr);
|
||||
|
||||
*progress = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
remove_phis_impl(nir_function_impl *impl)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
nir_foreach_block(impl, remove_phis_block, &progress);
|
||||
|
||||
if (progress) {
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
bool
|
||||
nir_opt_remove_phis(nir_shader *shader)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
nir_foreach_function(shader, function)
|
||||
if (function->impl)
|
||||
progress = remove_phis_impl(function->impl) || progress;
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright © 2015 Broadcom
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
/** @file nir_opt_undef.c
|
||||
*
|
||||
* Handles optimization of operations involving ssa_undef. For now, we just
|
||||
* make sure that csels between undef and some other value just give the other
|
||||
* value (on the assumption that the condition's going to be choosing the
|
||||
* defined value). This reduces work after if flattening when each side of
|
||||
* the if is defining a variable.
|
||||
*
|
||||
* Some day, we may find some use for making other operations consuming an
|
||||
* undef arg output undef, but I don't know of any cases currently.
|
||||
*/
|
||||
|
||||
static bool
|
||||
opt_undef_alu(nir_alu_instr *instr)
|
||||
{
|
||||
if (instr->op != nir_op_bcsel && instr->op != nir_op_fcsel)
|
||||
return false;
|
||||
|
||||
assert(instr->dest.dest.is_ssa);
|
||||
|
||||
for (int i = 1; i <= 2; i++) {
|
||||
if (!instr->src[i].src.is_ssa)
|
||||
continue;
|
||||
|
||||
nir_instr *parent = instr->src[i].src.ssa->parent_instr;
|
||||
if (parent->type != nir_instr_type_ssa_undef)
|
||||
continue;
|
||||
|
||||
/* We can't just use nir_alu_src_copy, because we need the def/use
|
||||
* updated.
|
||||
*/
|
||||
nir_instr_rewrite_src(&instr->instr, &instr->src[0].src,
|
||||
instr->src[i == 1 ? 2 : 1].src);
|
||||
nir_alu_src_copy(&instr->src[0], &instr->src[i == 1 ? 2 : 1],
|
||||
ralloc_parent(instr));
|
||||
|
||||
nir_src empty_src;
|
||||
memset(&empty_src, 0, sizeof(empty_src));
|
||||
nir_instr_rewrite_src(&instr->instr, &instr->src[1].src, empty_src);
|
||||
nir_instr_rewrite_src(&instr->instr, &instr->src[2].src, empty_src);
|
||||
instr->op = nir_op_imov;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
opt_undef_block(nir_block *block, void *data)
|
||||
{
|
||||
bool *progress = data;
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type == nir_instr_type_alu)
|
||||
if (opt_undef_alu(nir_instr_as_alu(instr)))
|
||||
(*progress) = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
nir_opt_undef(nir_shader *shader)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl) {
|
||||
nir_foreach_block(function->impl, opt_undef_block, &progress);
|
||||
if (progress)
|
||||
nir_metadata_preserve(function->impl,
|
||||
nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
}
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright © 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
static void
|
||||
add_var_use_intrinsic(nir_intrinsic_instr *instr, struct set *live)
|
||||
{
|
||||
unsigned num_vars = nir_intrinsic_infos[instr->intrinsic].num_variables;
|
||||
for (unsigned i = 0; i < num_vars; i++) {
|
||||
nir_variable *var = instr->variables[i]->var;
|
||||
_mesa_set_add(live, var);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
add_var_use_call(nir_call_instr *instr, struct set *live)
|
||||
{
|
||||
if (instr->return_deref != NULL) {
|
||||
nir_variable *var = instr->return_deref->var;
|
||||
_mesa_set_add(live, var);
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < instr->num_params; i++) {
|
||||
nir_variable *var = instr->params[i]->var;
|
||||
_mesa_set_add(live, var);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
add_var_use_tex(nir_tex_instr *instr, struct set *live)
|
||||
{
|
||||
if (instr->sampler != NULL) {
|
||||
nir_variable *var = instr->sampler->var;
|
||||
_mesa_set_add(live, var);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
add_var_use_block(nir_block *block, void *state)
|
||||
{
|
||||
struct set *live = state;
|
||||
|
||||
nir_foreach_instr(block, instr) {
|
||||
switch(instr->type) {
|
||||
case nir_instr_type_intrinsic:
|
||||
add_var_use_intrinsic(nir_instr_as_intrinsic(instr), live);
|
||||
break;
|
||||
|
||||
case nir_instr_type_call:
|
||||
add_var_use_call(nir_instr_as_call(instr), live);
|
||||
break;
|
||||
|
||||
case nir_instr_type_tex:
|
||||
add_var_use_tex(nir_instr_as_tex(instr), live);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
add_var_use_shader(nir_shader *shader, struct set *live)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl) {
|
||||
nir_foreach_block(function->impl, add_var_use_block, live);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
remove_dead_vars(struct exec_list *var_list, struct set *live)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
foreach_list_typed_safe(nir_variable, var, node, var_list) {
|
||||
struct set_entry *entry = _mesa_set_search(live, var);
|
||||
if (entry == NULL) {
|
||||
exec_node_remove(&var->node);
|
||||
ralloc_free(var);
|
||||
progress = true;
|
||||
}
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
bool
|
||||
nir_remove_dead_variables(nir_shader *shader)
|
||||
{
|
||||
bool progress = false;
|
||||
struct set *live =
|
||||
_mesa_set_create(NULL, _mesa_hash_pointer, _mesa_key_pointer_equal);
|
||||
|
||||
add_var_use_shader(shader, live);
|
||||
|
||||
progress = remove_dead_vars(&shader->globals, live) || progress;
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl) {
|
||||
if (remove_dead_vars(&function->impl->locals, live)) {
|
||||
nir_metadata_preserve(function->impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance |
|
||||
nir_metadata_live_ssa_defs);
|
||||
progress = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_mesa_set_destroy(live, NULL);
|
||||
return progress;
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir_search.h"
|
||||
|
||||
struct match_state {
|
||||
unsigned variables_seen;
|
||||
nir_alu_src variables[NIR_SEARCH_MAX_VARIABLES];
|
||||
};
|
||||
|
||||
static bool
|
||||
match_expression(const nir_search_expression *expr, nir_alu_instr *instr,
|
||||
unsigned num_components, const uint8_t *swizzle,
|
||||
struct match_state *state);
|
||||
|
||||
static const uint8_t identity_swizzle[] = { 0, 1, 2, 3 };
|
||||
|
||||
static bool alu_instr_is_bool(nir_alu_instr *instr);
|
||||
|
||||
static bool
|
||||
src_is_bool(nir_src src)
|
||||
{
|
||||
if (!src.is_ssa)
|
||||
return false;
|
||||
if (src.ssa->parent_instr->type != nir_instr_type_alu)
|
||||
return false;
|
||||
return alu_instr_is_bool(nir_instr_as_alu(src.ssa->parent_instr));
|
||||
}
|
||||
|
||||
static bool
|
||||
alu_instr_is_bool(nir_alu_instr *instr)
|
||||
{
|
||||
switch (instr->op) {
|
||||
case nir_op_iand:
|
||||
case nir_op_ior:
|
||||
case nir_op_ixor:
|
||||
return src_is_bool(instr->src[0].src) && src_is_bool(instr->src[1].src);
|
||||
case nir_op_inot:
|
||||
return src_is_bool(instr->src[0].src);
|
||||
default:
|
||||
return nir_op_infos[instr->op].output_type == nir_type_bool;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
match_value(const nir_search_value *value, nir_alu_instr *instr, unsigned src,
|
||||
unsigned num_components, const uint8_t *swizzle,
|
||||
struct match_state *state)
|
||||
{
|
||||
uint8_t new_swizzle[4];
|
||||
|
||||
/* If the source is an explicitly sized source, then we need to reset
|
||||
* both the number of components and the swizzle.
|
||||
*/
|
||||
if (nir_op_infos[instr->op].input_sizes[src] != 0) {
|
||||
num_components = nir_op_infos[instr->op].input_sizes[src];
|
||||
swizzle = identity_swizzle;
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < num_components; ++i)
|
||||
new_swizzle[i] = instr->src[src].swizzle[swizzle[i]];
|
||||
|
||||
switch (value->type) {
|
||||
case nir_search_value_expression:
|
||||
if (!instr->src[src].src.is_ssa)
|
||||
return false;
|
||||
|
||||
if (instr->src[src].src.ssa->parent_instr->type != nir_instr_type_alu)
|
||||
return false;
|
||||
|
||||
return match_expression(nir_search_value_as_expression(value),
|
||||
nir_instr_as_alu(instr->src[src].src.ssa->parent_instr),
|
||||
num_components, new_swizzle, state);
|
||||
|
||||
case nir_search_value_variable: {
|
||||
nir_search_variable *var = nir_search_value_as_variable(value);
|
||||
assert(var->variable < NIR_SEARCH_MAX_VARIABLES);
|
||||
|
||||
if (state->variables_seen & (1 << var->variable)) {
|
||||
if (!nir_srcs_equal(state->variables[var->variable].src,
|
||||
instr->src[src].src))
|
||||
return false;
|
||||
|
||||
assert(!instr->src[src].abs && !instr->src[src].negate);
|
||||
|
||||
for (unsigned i = 0; i < num_components; ++i) {
|
||||
if (state->variables[var->variable].swizzle[i] != new_swizzle[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
if (var->is_constant &&
|
||||
instr->src[src].src.ssa->parent_instr->type != nir_instr_type_load_const)
|
||||
return false;
|
||||
|
||||
if (var->type != nir_type_invalid) {
|
||||
if (instr->src[src].src.ssa->parent_instr->type != nir_instr_type_alu)
|
||||
return false;
|
||||
|
||||
nir_alu_instr *src_alu =
|
||||
nir_instr_as_alu(instr->src[src].src.ssa->parent_instr);
|
||||
|
||||
if (nir_op_infos[src_alu->op].output_type != var->type &&
|
||||
!(var->type == nir_type_bool && alu_instr_is_bool(src_alu)))
|
||||
return false;
|
||||
}
|
||||
|
||||
state->variables_seen |= (1 << var->variable);
|
||||
state->variables[var->variable].src = instr->src[src].src;
|
||||
state->variables[var->variable].abs = false;
|
||||
state->variables[var->variable].negate = false;
|
||||
|
||||
for (unsigned i = 0; i < 4; ++i) {
|
||||
if (i < num_components)
|
||||
state->variables[var->variable].swizzle[i] = new_swizzle[i];
|
||||
else
|
||||
state->variables[var->variable].swizzle[i] = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
case nir_search_value_constant: {
|
||||
nir_search_constant *const_val = nir_search_value_as_constant(value);
|
||||
|
||||
if (!instr->src[src].src.is_ssa)
|
||||
return false;
|
||||
|
||||
if (instr->src[src].src.ssa->parent_instr->type != nir_instr_type_load_const)
|
||||
return false;
|
||||
|
||||
nir_load_const_instr *load =
|
||||
nir_instr_as_load_const(instr->src[src].src.ssa->parent_instr);
|
||||
|
||||
switch (nir_op_infos[instr->op].input_types[src]) {
|
||||
case nir_type_float:
|
||||
for (unsigned i = 0; i < num_components; ++i) {
|
||||
if (load->value.f[new_swizzle[i]] != const_val->data.f)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
case nir_type_int:
|
||||
case nir_type_uint:
|
||||
case nir_type_bool:
|
||||
for (unsigned i = 0; i < num_components; ++i) {
|
||||
if (load->value.i[new_swizzle[i]] != const_val->data.i)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
unreachable("Invalid alu source type");
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
unreachable("Invalid search value type");
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
match_expression(const nir_search_expression *expr, nir_alu_instr *instr,
|
||||
unsigned num_components, const uint8_t *swizzle,
|
||||
struct match_state *state)
|
||||
{
|
||||
if (instr->op != expr->opcode)
|
||||
return false;
|
||||
|
||||
assert(!instr->dest.saturate);
|
||||
assert(nir_op_infos[instr->op].num_inputs > 0);
|
||||
|
||||
/* If we have an explicitly sized destination, we can only handle the
|
||||
* identity swizzle. While dot(vec3(a, b, c).zxy) is a valid
|
||||
* expression, we don't have the information right now to propagate that
|
||||
* swizzle through. We can only properly propagate swizzles if the
|
||||
* instruction is vectorized.
|
||||
*/
|
||||
if (nir_op_infos[instr->op].output_size != 0) {
|
||||
for (unsigned i = 0; i < num_components; i++) {
|
||||
if (swizzle[i] != i)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Stash off the current variables_seen bitmask. This way we can
|
||||
* restore it prior to matching in the commutative case below.
|
||||
*/
|
||||
unsigned variables_seen_stash = state->variables_seen;
|
||||
|
||||
bool matched = true;
|
||||
for (unsigned i = 0; i < nir_op_infos[instr->op].num_inputs; i++) {
|
||||
if (!match_value(expr->srcs[i], instr, i, num_components,
|
||||
swizzle, state)) {
|
||||
matched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched)
|
||||
return true;
|
||||
|
||||
if (nir_op_infos[instr->op].algebraic_properties & NIR_OP_IS_COMMUTATIVE) {
|
||||
assert(nir_op_infos[instr->op].num_inputs == 2);
|
||||
|
||||
/* Restore the variables_seen bitmask. If we don't do this, then we
|
||||
* could end up with an erroneous failure due to variables found in the
|
||||
* first match attempt above not matching those in the second.
|
||||
*/
|
||||
state->variables_seen = variables_seen_stash;
|
||||
|
||||
if (!match_value(expr->srcs[0], instr, 1, num_components,
|
||||
swizzle, state))
|
||||
return false;
|
||||
|
||||
return match_value(expr->srcs[1], instr, 0, num_components,
|
||||
swizzle, state);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static nir_alu_src
|
||||
construct_value(const nir_search_value *value, nir_alu_type type,
|
||||
unsigned num_components, struct match_state *state,
|
||||
nir_instr *instr, void *mem_ctx)
|
||||
{
|
||||
switch (value->type) {
|
||||
case nir_search_value_expression: {
|
||||
const nir_search_expression *expr = nir_search_value_as_expression(value);
|
||||
|
||||
if (nir_op_infos[expr->opcode].output_size != 0)
|
||||
num_components = nir_op_infos[expr->opcode].output_size;
|
||||
|
||||
nir_alu_instr *alu = nir_alu_instr_create(mem_ctx, expr->opcode);
|
||||
nir_ssa_dest_init(&alu->instr, &alu->dest.dest, num_components, NULL);
|
||||
alu->dest.write_mask = (1 << num_components) - 1;
|
||||
alu->dest.saturate = false;
|
||||
|
||||
for (unsigned i = 0; i < nir_op_infos[expr->opcode].num_inputs; i++) {
|
||||
/* If the source is an explicitly sized source, then we need to reset
|
||||
* the number of components to match.
|
||||
*/
|
||||
if (nir_op_infos[alu->op].input_sizes[i] != 0)
|
||||
num_components = nir_op_infos[alu->op].input_sizes[i];
|
||||
|
||||
alu->src[i] = construct_value(expr->srcs[i],
|
||||
nir_op_infos[alu->op].input_types[i],
|
||||
num_components,
|
||||
state, instr, mem_ctx);
|
||||
}
|
||||
|
||||
nir_instr_insert_before(instr, &alu->instr);
|
||||
|
||||
nir_alu_src val;
|
||||
val.src = nir_src_for_ssa(&alu->dest.dest.ssa);
|
||||
val.negate = false;
|
||||
val.abs = false,
|
||||
memcpy(val.swizzle, identity_swizzle, sizeof val.swizzle);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
case nir_search_value_variable: {
|
||||
const nir_search_variable *var = nir_search_value_as_variable(value);
|
||||
assert(state->variables_seen & (1 << var->variable));
|
||||
|
||||
nir_alu_src val = { NIR_SRC_INIT };
|
||||
nir_alu_src_copy(&val, &state->variables[var->variable], mem_ctx);
|
||||
|
||||
assert(!var->is_constant);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
case nir_search_value_constant: {
|
||||
const nir_search_constant *c = nir_search_value_as_constant(value);
|
||||
nir_load_const_instr *load = nir_load_const_instr_create(mem_ctx, 1);
|
||||
|
||||
switch (type) {
|
||||
case nir_type_float:
|
||||
load->def.name = ralloc_asprintf(mem_ctx, "%f", c->data.f);
|
||||
load->value.f[0] = c->data.f;
|
||||
break;
|
||||
case nir_type_int:
|
||||
load->def.name = ralloc_asprintf(mem_ctx, "%d", c->data.i);
|
||||
load->value.i[0] = c->data.i;
|
||||
break;
|
||||
case nir_type_uint:
|
||||
case nir_type_bool:
|
||||
load->value.u[0] = c->data.u;
|
||||
break;
|
||||
default:
|
||||
unreachable("Invalid alu source type");
|
||||
}
|
||||
|
||||
nir_instr_insert_before(instr, &load->instr);
|
||||
|
||||
nir_alu_src val;
|
||||
val.src = nir_src_for_ssa(&load->def);
|
||||
val.negate = false;
|
||||
val.abs = false,
|
||||
memset(val.swizzle, 0, sizeof val.swizzle);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
default:
|
||||
unreachable("Invalid search value type");
|
||||
}
|
||||
}
|
||||
|
||||
nir_alu_instr *
|
||||
nir_replace_instr(nir_alu_instr *instr, const nir_search_expression *search,
|
||||
const nir_search_value *replace, void *mem_ctx)
|
||||
{
|
||||
uint8_t swizzle[4] = { 0, 0, 0, 0 };
|
||||
|
||||
for (unsigned i = 0; i < instr->dest.dest.ssa.num_components; ++i)
|
||||
swizzle[i] = i;
|
||||
|
||||
assert(instr->dest.dest.is_ssa);
|
||||
|
||||
struct match_state state;
|
||||
state.variables_seen = 0;
|
||||
|
||||
if (!match_expression(search, instr, instr->dest.dest.ssa.num_components,
|
||||
swizzle, &state))
|
||||
return NULL;
|
||||
|
||||
/* Inserting a mov may be unnecessary. However, it's much easier to
|
||||
* simply let copy propagation clean this up than to try to go through
|
||||
* and rewrite swizzles ourselves.
|
||||
*/
|
||||
nir_alu_instr *mov = nir_alu_instr_create(mem_ctx, nir_op_imov);
|
||||
mov->dest.write_mask = instr->dest.write_mask;
|
||||
nir_ssa_dest_init(&mov->instr, &mov->dest.dest,
|
||||
instr->dest.dest.ssa.num_components, NULL);
|
||||
|
||||
mov->src[0] = construct_value(replace, nir_op_infos[instr->op].output_type,
|
||||
instr->dest.dest.ssa.num_components, &state,
|
||||
&instr->instr, mem_ctx);
|
||||
nir_instr_insert_before(&instr->instr, &mov->instr);
|
||||
|
||||
nir_ssa_def_rewrite_uses(&instr->dest.dest.ssa,
|
||||
nir_src_for_ssa(&mov->dest.dest.ssa));
|
||||
|
||||
/* We know this one has no more uses because we just rewrote them all,
|
||||
* so we can remove it. The rest of the matched expression, however, we
|
||||
* don't know so much about. We'll just let dead code clean them up.
|
||||
*/
|
||||
nir_instr_remove(&instr->instr);
|
||||
|
||||
return mov;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _NIR_SEARCH_
|
||||
#define _NIR_SEARCH_
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
#define NIR_SEARCH_MAX_VARIABLES 16
|
||||
|
||||
typedef enum {
|
||||
nir_search_value_expression,
|
||||
nir_search_value_variable,
|
||||
nir_search_value_constant,
|
||||
} nir_search_value_type;
|
||||
|
||||
typedef struct {
|
||||
nir_search_value_type type;
|
||||
} nir_search_value;
|
||||
|
||||
typedef struct {
|
||||
nir_search_value value;
|
||||
|
||||
/** The variable index; Must be less than NIR_SEARCH_MAX_VARIABLES */
|
||||
unsigned variable;
|
||||
|
||||
/** Indicates that the given variable must be a constant
|
||||
*
|
||||
* This is only alloed in search expressions and indicates that the
|
||||
* given variable is only allowed to match constant values.
|
||||
*/
|
||||
bool is_constant;
|
||||
|
||||
/** Indicates that the given variable must have a certain type
|
||||
*
|
||||
* This is only allowed in search expressions and indicates that the
|
||||
* given variable is only allowed to match values that come from an ALU
|
||||
* instruction with the given output type. A type of nir_type_void
|
||||
* means it can match any type.
|
||||
*
|
||||
* Note: A variable that is both constant and has a non-void type will
|
||||
* never match anything.
|
||||
*/
|
||||
nir_alu_type type;
|
||||
} nir_search_variable;
|
||||
|
||||
typedef struct {
|
||||
nir_search_value value;
|
||||
|
||||
union {
|
||||
uint32_t u;
|
||||
int32_t i;
|
||||
float f;
|
||||
} data;
|
||||
} nir_search_constant;
|
||||
|
||||
typedef struct {
|
||||
nir_search_value value;
|
||||
|
||||
nir_op opcode;
|
||||
const nir_search_value *srcs[4];
|
||||
} nir_search_expression;
|
||||
|
||||
NIR_DEFINE_CAST(nir_search_value_as_variable, nir_search_value,
|
||||
nir_search_variable, value)
|
||||
NIR_DEFINE_CAST(nir_search_value_as_constant, nir_search_value,
|
||||
nir_search_constant, value)
|
||||
NIR_DEFINE_CAST(nir_search_value_as_expression, nir_search_value,
|
||||
nir_search_expression, value)
|
||||
|
||||
nir_alu_instr *
|
||||
nir_replace_instr(nir_alu_instr *instr, const nir_search_expression *search,
|
||||
const nir_search_value *replace, void *mem_ctx);
|
||||
|
||||
#endif /* _NIR_SEARCH_ */
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
/*
|
||||
* Implements "copy splitting" which is similar to structure splitting only
|
||||
* it works on copy operations rather than the datatypes themselves. The
|
||||
* GLSL language allows you to copy one variable to another an entire
|
||||
* structure (which may contain arrays or other structures) at a time.
|
||||
* Normally, in a language such as C this would be handled by a "structure
|
||||
* splitting" pass that breaks up the structures. Unfortunately for us,
|
||||
* structures used in inputs or outputs can't be split. Therefore,
|
||||
* regardlesss of what we do, we have to be able to copy to/from
|
||||
* structures.
|
||||
*
|
||||
* The primary purpose of structure splitting is to allow you to better
|
||||
* optimize variable access and lower things to registers where you can.
|
||||
* The primary issue here is that, if you lower the copy to a bunch of
|
||||
* loads and stores, you loose a lot of information about the copy
|
||||
* operation that you would like to keep around. To solve this problem, we
|
||||
* have a "copy splitting" pass that, instead of splitting the structures
|
||||
* or lowering the copy into loads and storres, splits the copy operation
|
||||
* into a bunch of copy operations one for each leaf of the structure tree.
|
||||
* If an intermediate array is encountered, it is referenced with a
|
||||
* wildcard reference to indicate that the entire array is to be copied.
|
||||
*
|
||||
* As things become direct, array copies may be able to be losslessly
|
||||
* lowered to having fewer and fewer wildcards. However, until that
|
||||
* happens we want to keep the information about the arrays intact.
|
||||
*
|
||||
* Prior to the copy splitting pass, there are no wildcard references but
|
||||
* there may be incomplete references where the tail of the deref chain is
|
||||
* an array or a structure and not a specific element. After the copy
|
||||
* splitting pass has completed, every variable deref will be a full-length
|
||||
* dereference pointing to a single leaf in the structure type tree with
|
||||
* possibly a few wildcard array dereferences.
|
||||
*/
|
||||
|
||||
struct split_var_copies_state {
|
||||
void *mem_ctx;
|
||||
void *dead_ctx;
|
||||
bool progress;
|
||||
};
|
||||
|
||||
/* Recursively constructs deref chains to split a copy instruction into
|
||||
* multiple (if needed) copy instructions with full-length deref chains.
|
||||
* External callers of this function should pass the tail and head of the
|
||||
* deref chains found as the source and destination of the copy instruction
|
||||
* into this function.
|
||||
*
|
||||
* \param old_copy The copy instruction we are splitting
|
||||
* \param dest_head The head of the destination deref chain we are building
|
||||
* \param src_head The head of the source deref chain we are building
|
||||
* \param dest_tail The tail of the destination deref chain we are building
|
||||
* \param src_tail The tail of the source deref chain we are building
|
||||
* \param state The current split_var_copies_state object
|
||||
*/
|
||||
static void
|
||||
split_var_copy_instr(nir_intrinsic_instr *old_copy,
|
||||
nir_deref *dest_head, nir_deref *src_head,
|
||||
nir_deref *dest_tail, nir_deref *src_tail,
|
||||
struct split_var_copies_state *state)
|
||||
{
|
||||
assert(src_tail->type == dest_tail->type);
|
||||
|
||||
/* Make sure these really are the tails of the deref chains */
|
||||
assert(dest_tail->child == NULL);
|
||||
assert(src_tail->child == NULL);
|
||||
|
||||
switch (glsl_get_base_type(src_tail->type)) {
|
||||
case GLSL_TYPE_ARRAY: {
|
||||
/* Make a wildcard dereference */
|
||||
nir_deref_array *deref = nir_deref_array_create(state->dead_ctx);
|
||||
deref->deref.type = glsl_get_array_element(src_tail->type);
|
||||
deref->deref_array_type = nir_deref_array_type_wildcard;
|
||||
|
||||
/* Set the tail of both as the newly created wildcard deref. It is
|
||||
* safe to use the same wildcard in both places because a) we will be
|
||||
* copying it before we put it in an actual instruction and b)
|
||||
* everything that will potentially add another link in the deref
|
||||
* chain will also add the same thing to both chains.
|
||||
*/
|
||||
src_tail->child = &deref->deref;
|
||||
dest_tail->child = &deref->deref;
|
||||
|
||||
split_var_copy_instr(old_copy, dest_head, src_head,
|
||||
dest_tail->child, src_tail->child, state);
|
||||
|
||||
/* Set it back to the way we found it */
|
||||
src_tail->child = NULL;
|
||||
dest_tail->child = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
case GLSL_TYPE_STRUCT:
|
||||
/* This is the only part that actually does any interesting
|
||||
* splitting. For array types, we just use wildcards and resolve
|
||||
* them later. For structure types, we need to emit one copy
|
||||
* instruction for every structure element. Because we may have
|
||||
* structs inside structs, we just recurse and let the next level
|
||||
* take care of any additional structures.
|
||||
*/
|
||||
for (unsigned i = 0; i < glsl_get_length(src_tail->type); i++) {
|
||||
nir_deref_struct *deref = nir_deref_struct_create(state->dead_ctx, i);
|
||||
deref->deref.type = glsl_get_struct_field(src_tail->type, i);
|
||||
|
||||
/* Set the tail of both as the newly created structure deref. It
|
||||
* is safe to use the same wildcard in both places because a) we
|
||||
* will be copying it before we put it in an actual instruction
|
||||
* and b) everything that will potentially add another link in the
|
||||
* deref chain will also add the same thing to both chains.
|
||||
*/
|
||||
src_tail->child = &deref->deref;
|
||||
dest_tail->child = &deref->deref;
|
||||
|
||||
split_var_copy_instr(old_copy, dest_head, src_head,
|
||||
dest_tail->child, src_tail->child, state);
|
||||
}
|
||||
/* Set it back to the way we found it */
|
||||
src_tail->child = NULL;
|
||||
dest_tail->child = NULL;
|
||||
break;
|
||||
|
||||
case GLSL_TYPE_UINT:
|
||||
case GLSL_TYPE_INT:
|
||||
case GLSL_TYPE_FLOAT:
|
||||
case GLSL_TYPE_BOOL:
|
||||
if (glsl_type_is_matrix(src_tail->type)) {
|
||||
nir_deref_array *deref = nir_deref_array_create(state->dead_ctx);
|
||||
deref->deref.type = glsl_get_column_type(src_tail->type);
|
||||
deref->deref_array_type = nir_deref_array_type_wildcard;
|
||||
|
||||
/* Set the tail of both as the newly created wildcard deref. It
|
||||
* is safe to use the same wildcard in both places because a) we
|
||||
* will be copying it before we put it in an actual instruction
|
||||
* and b) everything that will potentially add another link in the
|
||||
* deref chain will also add the same thing to both chains.
|
||||
*/
|
||||
src_tail->child = &deref->deref;
|
||||
dest_tail->child = &deref->deref;
|
||||
|
||||
split_var_copy_instr(old_copy, dest_head, src_head,
|
||||
dest_tail->child, src_tail->child, state);
|
||||
|
||||
/* Set it back to the way we found it */
|
||||
src_tail->child = NULL;
|
||||
dest_tail->child = NULL;
|
||||
} else {
|
||||
/* At this point, we have fully built our deref chains and can
|
||||
* actually add the new copy instruction.
|
||||
*/
|
||||
nir_intrinsic_instr *new_copy =
|
||||
nir_intrinsic_instr_create(state->mem_ctx, nir_intrinsic_copy_var);
|
||||
|
||||
/* We need to make copies because a) this deref chain actually
|
||||
* belongs to the copy instruction and b) the deref chains may
|
||||
* have some of the same links due to the way we constructed them
|
||||
*/
|
||||
nir_deref *src = nir_copy_deref(new_copy, src_head);
|
||||
nir_deref *dest = nir_copy_deref(new_copy, dest_head);
|
||||
|
||||
new_copy->variables[0] = nir_deref_as_var(dest);
|
||||
new_copy->variables[1] = nir_deref_as_var(src);
|
||||
|
||||
/* Emit the copy instruction after the old instruction. We'll
|
||||
* remove the old one later.
|
||||
*/
|
||||
nir_instr_insert_after(&old_copy->instr, &new_copy->instr);
|
||||
state->progress = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case GLSL_TYPE_SAMPLER:
|
||||
case GLSL_TYPE_IMAGE:
|
||||
case GLSL_TYPE_ATOMIC_UINT:
|
||||
case GLSL_TYPE_INTERFACE:
|
||||
default:
|
||||
unreachable("Cannot copy these types");
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
split_var_copies_block(nir_block *block, void *void_state)
|
||||
{
|
||||
struct split_var_copies_state *state = void_state;
|
||||
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
if (instr->type != nir_instr_type_intrinsic)
|
||||
continue;
|
||||
|
||||
nir_intrinsic_instr *intrinsic = nir_instr_as_intrinsic(instr);
|
||||
if (intrinsic->intrinsic != nir_intrinsic_copy_var)
|
||||
continue;
|
||||
|
||||
nir_deref *dest_head = &intrinsic->variables[0]->deref;
|
||||
nir_deref *src_head = &intrinsic->variables[1]->deref;
|
||||
nir_deref *dest_tail = nir_deref_tail(dest_head);
|
||||
nir_deref *src_tail = nir_deref_tail(src_head);
|
||||
|
||||
switch (glsl_get_base_type(src_tail->type)) {
|
||||
case GLSL_TYPE_ARRAY:
|
||||
case GLSL_TYPE_STRUCT:
|
||||
split_var_copy_instr(intrinsic, dest_head, src_head,
|
||||
dest_tail, src_tail, state);
|
||||
nir_instr_remove(&intrinsic->instr);
|
||||
ralloc_steal(state->dead_ctx, instr);
|
||||
break;
|
||||
case GLSL_TYPE_FLOAT:
|
||||
case GLSL_TYPE_INT:
|
||||
case GLSL_TYPE_UINT:
|
||||
case GLSL_TYPE_BOOL:
|
||||
if (glsl_type_is_matrix(src_tail->type)) {
|
||||
split_var_copy_instr(intrinsic, dest_head, src_head,
|
||||
dest_tail, src_tail, state);
|
||||
nir_instr_remove(&intrinsic->instr);
|
||||
ralloc_steal(state->dead_ctx, instr);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
unreachable("Invalid type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
split_var_copies_impl(nir_function_impl *impl)
|
||||
{
|
||||
struct split_var_copies_state state;
|
||||
|
||||
state.mem_ctx = ralloc_parent(impl);
|
||||
state.dead_ctx = ralloc_context(NULL);
|
||||
state.progress = false;
|
||||
|
||||
nir_foreach_block(impl, split_var_copies_block, &state);
|
||||
|
||||
ralloc_free(state.dead_ctx);
|
||||
|
||||
if (state.progress) {
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
}
|
||||
|
||||
return state.progress;
|
||||
}
|
||||
|
||||
bool
|
||||
nir_split_var_copies(nir_shader *shader)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
progress = split_var_copies_impl(function->impl) || progress;
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright © 2015 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.
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
/**
|
||||
* \file nir_sweep.c
|
||||
*
|
||||
* The nir_sweep() pass performs a mark and sweep pass over a nir_shader's associated
|
||||
* memory - anything still connected to the program will be kept, and any dead memory
|
||||
* we dropped on the floor will be freed.
|
||||
*
|
||||
* The expectation is that drivers should call this when finished compiling the shader
|
||||
* (after any optimization, lowering, and so on). However, it's also fine to call it
|
||||
* earlier, and even many times, trading CPU cycles for memory savings.
|
||||
*/
|
||||
|
||||
#define steal_list(mem_ctx, type, list) \
|
||||
foreach_list_typed(type, obj, node, list) { ralloc_steal(mem_ctx, obj); }
|
||||
|
||||
static void sweep_cf_node(nir_shader *nir, nir_cf_node *cf_node);
|
||||
|
||||
static bool
|
||||
sweep_src_indirect(nir_src *src, void *nir)
|
||||
{
|
||||
if (!src->is_ssa && src->reg.indirect)
|
||||
ralloc_steal(nir, src->reg.indirect);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sweep_dest_indirect(nir_dest *dest, void *nir)
|
||||
{
|
||||
if (!dest->is_ssa && dest->reg.indirect)
|
||||
ralloc_steal(nir, dest->reg.indirect);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
sweep_block(nir_shader *nir, nir_block *block)
|
||||
{
|
||||
ralloc_steal(nir, block);
|
||||
|
||||
nir_foreach_instr(block, instr) {
|
||||
ralloc_steal(nir, instr);
|
||||
|
||||
nir_foreach_src(instr, sweep_src_indirect, nir);
|
||||
nir_foreach_dest(instr, sweep_dest_indirect, nir);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sweep_if(nir_shader *nir, nir_if *iff)
|
||||
{
|
||||
ralloc_steal(nir, iff);
|
||||
|
||||
foreach_list_typed(nir_cf_node, cf_node, node, &iff->then_list) {
|
||||
sweep_cf_node(nir, cf_node);
|
||||
}
|
||||
|
||||
foreach_list_typed(nir_cf_node, cf_node, node, &iff->else_list) {
|
||||
sweep_cf_node(nir, cf_node);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sweep_loop(nir_shader *nir, nir_loop *loop)
|
||||
{
|
||||
ralloc_steal(nir, loop);
|
||||
|
||||
foreach_list_typed(nir_cf_node, cf_node, node, &loop->body) {
|
||||
sweep_cf_node(nir, cf_node);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sweep_cf_node(nir_shader *nir, nir_cf_node *cf_node)
|
||||
{
|
||||
switch (cf_node->type) {
|
||||
case nir_cf_node_block:
|
||||
sweep_block(nir, nir_cf_node_as_block(cf_node));
|
||||
break;
|
||||
case nir_cf_node_if:
|
||||
sweep_if(nir, nir_cf_node_as_if(cf_node));
|
||||
break;
|
||||
case nir_cf_node_loop:
|
||||
sweep_loop(nir, nir_cf_node_as_loop(cf_node));
|
||||
break;
|
||||
default:
|
||||
unreachable("Invalid CF node type");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sweep_impl(nir_shader *nir, nir_function_impl *impl)
|
||||
{
|
||||
ralloc_steal(nir, impl);
|
||||
|
||||
ralloc_steal(nir, impl->params);
|
||||
ralloc_steal(nir, impl->return_var);
|
||||
steal_list(nir, nir_variable, &impl->locals);
|
||||
steal_list(nir, nir_register, &impl->registers);
|
||||
|
||||
foreach_list_typed(nir_cf_node, cf_node, node, &impl->body) {
|
||||
sweep_cf_node(nir, cf_node);
|
||||
}
|
||||
|
||||
sweep_block(nir, impl->end_block);
|
||||
|
||||
/* Wipe out all the metadata, if any. */
|
||||
nir_metadata_preserve(impl, nir_metadata_none);
|
||||
}
|
||||
|
||||
static void
|
||||
sweep_function(nir_shader *nir, nir_function *f)
|
||||
{
|
||||
ralloc_steal(nir, f);
|
||||
ralloc_steal(nir, f->params);
|
||||
|
||||
if (f->impl)
|
||||
sweep_impl(nir, f->impl);
|
||||
}
|
||||
|
||||
void
|
||||
nir_sweep(nir_shader *nir)
|
||||
{
|
||||
void *rubbish = ralloc_context(NULL);
|
||||
|
||||
/* First, move ownership of all the memory to a temporary context; assume dead. */
|
||||
ralloc_adopt(rubbish, nir);
|
||||
|
||||
ralloc_steal(nir, (char *)nir->info.name);
|
||||
if (nir->info.label)
|
||||
ralloc_steal(nir, (char *)nir->info.label);
|
||||
|
||||
/* Variables and registers are not dead. Steal them back. */
|
||||
steal_list(nir, nir_variable, &nir->uniforms);
|
||||
steal_list(nir, nir_variable, &nir->inputs);
|
||||
steal_list(nir, nir_variable, &nir->outputs);
|
||||
steal_list(nir, nir_variable, &nir->globals);
|
||||
steal_list(nir, nir_variable, &nir->system_values);
|
||||
steal_list(nir, nir_register, &nir->registers);
|
||||
|
||||
/* Recurse into functions, stealing their contents back. */
|
||||
foreach_list_typed(nir_function, func, node, &nir->functions) {
|
||||
sweep_function(nir, func);
|
||||
}
|
||||
|
||||
/* Free everything we didn't steal back. */
|
||||
ralloc_free(rubbish);
|
||||
}
|
||||
@@ -0,0 +1,536 @@
|
||||
/*
|
||||
* Copyright © 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:
|
||||
* Connor Abbott (cwabbott0@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir.h"
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/*
|
||||
* Implements the classic to-SSA algorithm described by Cytron et. al. in
|
||||
* "Efficiently Computing Static Single Assignment Form and the Control
|
||||
* Dependence Graph."
|
||||
*/
|
||||
|
||||
/* inserts a phi node of the form reg = phi(reg, reg, reg, ...) */
|
||||
|
||||
static void
|
||||
insert_trivial_phi(nir_register *reg, nir_block *block, void *mem_ctx)
|
||||
{
|
||||
nir_phi_instr *instr = nir_phi_instr_create(mem_ctx);
|
||||
|
||||
instr->dest.reg.reg = reg;
|
||||
struct set_entry *entry;
|
||||
set_foreach(block->predecessors, entry) {
|
||||
nir_block *pred = (nir_block *) entry->key;
|
||||
|
||||
nir_phi_src *src = ralloc(instr, nir_phi_src);
|
||||
src->pred = pred;
|
||||
src->src.is_ssa = false;
|
||||
src->src.reg.base_offset = 0;
|
||||
src->src.reg.indirect = NULL;
|
||||
src->src.reg.reg = reg;
|
||||
exec_list_push_tail(&instr->srcs, &src->node);
|
||||
}
|
||||
|
||||
nir_instr_insert_before_block(block, &instr->instr);
|
||||
}
|
||||
|
||||
static void
|
||||
insert_phi_nodes(nir_function_impl *impl)
|
||||
{
|
||||
void *mem_ctx = ralloc_parent(impl);
|
||||
|
||||
unsigned *work = calloc(impl->num_blocks, sizeof(unsigned));
|
||||
unsigned *has_already = calloc(impl->num_blocks, sizeof(unsigned));
|
||||
|
||||
/*
|
||||
* Since the work flags already prevent us from inserting a node that has
|
||||
* ever been inserted into W, we don't need to use a set to represent W.
|
||||
* Also, since no block can ever be inserted into W more than once, we know
|
||||
* that the maximum size of W is the number of basic blocks in the
|
||||
* function. So all we need to handle W is an array and a pointer to the
|
||||
* next element to be inserted and the next element to be removed.
|
||||
*/
|
||||
nir_block **W = malloc(impl->num_blocks * sizeof(nir_block *));
|
||||
unsigned w_start, w_end;
|
||||
|
||||
unsigned iter_count = 0;
|
||||
|
||||
nir_index_blocks(impl);
|
||||
|
||||
foreach_list_typed(nir_register, reg, node, &impl->registers) {
|
||||
if (reg->num_array_elems != 0)
|
||||
continue;
|
||||
|
||||
w_start = w_end = 0;
|
||||
iter_count++;
|
||||
|
||||
nir_foreach_def(reg, dest) {
|
||||
nir_instr *def = dest->reg.parent_instr;
|
||||
if (work[def->block->index] < iter_count)
|
||||
W[w_end++] = def->block;
|
||||
work[def->block->index] = iter_count;
|
||||
}
|
||||
|
||||
while (w_start != w_end) {
|
||||
nir_block *cur = W[w_start++];
|
||||
struct set_entry *entry;
|
||||
set_foreach(cur->dom_frontier, entry) {
|
||||
nir_block *next = (nir_block *) entry->key;
|
||||
|
||||
/*
|
||||
* If there's more than one return statement, then the end block
|
||||
* can be a join point for some definitions. However, there are
|
||||
* no instructions in the end block, so nothing would use those
|
||||
* phi nodes. Of course, we couldn't place those phi nodes
|
||||
* anyways due to the restriction of having no instructions in the
|
||||
* end block...
|
||||
*/
|
||||
if (next == impl->end_block)
|
||||
continue;
|
||||
|
||||
if (has_already[next->index] < iter_count) {
|
||||
insert_trivial_phi(reg, next, mem_ctx);
|
||||
has_already[next->index] = iter_count;
|
||||
if (work[next->index] < iter_count) {
|
||||
work[next->index] = iter_count;
|
||||
W[w_end++] = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(work);
|
||||
free(has_already);
|
||||
free(W);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
nir_ssa_def **stack;
|
||||
int index;
|
||||
unsigned num_defs; /** < used to add indices to debug names */
|
||||
#ifndef NDEBUG
|
||||
unsigned stack_size;
|
||||
#endif
|
||||
} reg_state;
|
||||
|
||||
typedef struct {
|
||||
reg_state *states;
|
||||
void *mem_ctx;
|
||||
nir_instr *parent_instr;
|
||||
nir_if *parent_if;
|
||||
nir_function_impl *impl;
|
||||
|
||||
/* map from SSA value -> original register */
|
||||
struct hash_table *ssa_map;
|
||||
} rewrite_state;
|
||||
|
||||
static nir_ssa_def *get_ssa_src(nir_register *reg, rewrite_state *state)
|
||||
{
|
||||
unsigned index = reg->index;
|
||||
|
||||
if (state->states[index].index == -1) {
|
||||
/*
|
||||
* We're using an undefined register, create a new undefined SSA value
|
||||
* to preserve the information that this source is undefined
|
||||
*/
|
||||
nir_ssa_undef_instr *instr =
|
||||
nir_ssa_undef_instr_create(state->mem_ctx, reg->num_components);
|
||||
|
||||
/*
|
||||
* We could just insert the undefined instruction before the instruction
|
||||
* we're rewriting, but we could be rewriting a phi source in which case
|
||||
* we can't do that, so do the next easiest thing - insert it at the
|
||||
* beginning of the program. In the end, it doesn't really matter where
|
||||
* the undefined instructions are because they're going to be ignored
|
||||
* in the backend.
|
||||
*/
|
||||
nir_instr_insert_before_cf_list(&state->impl->body, &instr->instr);
|
||||
return &instr->def;
|
||||
}
|
||||
|
||||
return state->states[index].stack[state->states[index].index];
|
||||
}
|
||||
|
||||
static bool
|
||||
rewrite_use(nir_src *src, void *_state)
|
||||
{
|
||||
rewrite_state *state = (rewrite_state *) _state;
|
||||
|
||||
if (src->is_ssa)
|
||||
return true;
|
||||
|
||||
unsigned index = src->reg.reg->index;
|
||||
|
||||
if (state->states[index].stack == NULL)
|
||||
return true;
|
||||
|
||||
nir_ssa_def *def = get_ssa_src(src->reg.reg, state);
|
||||
if (state->parent_instr)
|
||||
nir_instr_rewrite_src(state->parent_instr, src, nir_src_for_ssa(def));
|
||||
else
|
||||
nir_if_rewrite_condition(state->parent_if, nir_src_for_ssa(def));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
rewrite_def_forwards(nir_dest *dest, void *_state)
|
||||
{
|
||||
rewrite_state *state = (rewrite_state *) _state;
|
||||
|
||||
if (dest->is_ssa)
|
||||
return true;
|
||||
|
||||
nir_register *reg = dest->reg.reg;
|
||||
unsigned index = reg->index;
|
||||
|
||||
if (state->states[index].stack == NULL)
|
||||
return true;
|
||||
|
||||
char *name = NULL;
|
||||
if (dest->reg.reg->name)
|
||||
name = ralloc_asprintf(state->mem_ctx, "%s_%u", dest->reg.reg->name,
|
||||
state->states[index].num_defs);
|
||||
|
||||
list_del(&dest->reg.def_link);
|
||||
nir_ssa_dest_init(state->parent_instr, dest, reg->num_components, name);
|
||||
|
||||
/* push our SSA destination on the stack */
|
||||
state->states[index].index++;
|
||||
assert(state->states[index].index < state->states[index].stack_size);
|
||||
state->states[index].stack[state->states[index].index] = &dest->ssa;
|
||||
state->states[index].num_defs++;
|
||||
|
||||
_mesa_hash_table_insert(state->ssa_map, &dest->ssa, reg);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
rewrite_alu_instr_forward(nir_alu_instr *instr, rewrite_state *state)
|
||||
{
|
||||
state->parent_instr = &instr->instr;
|
||||
|
||||
nir_foreach_src(&instr->instr, rewrite_use, state);
|
||||
|
||||
if (instr->dest.dest.is_ssa)
|
||||
return;
|
||||
|
||||
nir_register *reg = instr->dest.dest.reg.reg;
|
||||
unsigned index = reg->index;
|
||||
|
||||
if (state->states[index].stack == NULL)
|
||||
return;
|
||||
|
||||
unsigned write_mask = instr->dest.write_mask;
|
||||
if (write_mask != (1 << instr->dest.dest.reg.reg->num_components) - 1) {
|
||||
/*
|
||||
* Calculate the number of components the final instruction, which for
|
||||
* per-component things is the number of output components of the
|
||||
* instruction and non-per-component things is the number of enabled
|
||||
* channels in the write mask.
|
||||
*/
|
||||
unsigned num_components;
|
||||
if (nir_op_infos[instr->op].output_size == 0) {
|
||||
unsigned temp = (write_mask & 0x5) + ((write_mask >> 1) & 0x5);
|
||||
num_components = (temp & 0x3) + ((temp >> 2) & 0x3);
|
||||
} else {
|
||||
num_components = nir_op_infos[instr->op].output_size;
|
||||
}
|
||||
|
||||
char *name = NULL;
|
||||
if (instr->dest.dest.reg.reg->name)
|
||||
name = ralloc_asprintf(state->mem_ctx, "%s_%u",
|
||||
reg->name, state->states[index].num_defs);
|
||||
|
||||
instr->dest.write_mask = (1 << num_components) - 1;
|
||||
list_del(&instr->dest.dest.reg.def_link);
|
||||
nir_ssa_dest_init(&instr->instr, &instr->dest.dest, num_components, name);
|
||||
|
||||
if (nir_op_infos[instr->op].output_size == 0) {
|
||||
/*
|
||||
* When we change the output writemask, we need to change the
|
||||
* swizzles for per-component inputs too
|
||||
*/
|
||||
for (unsigned i = 0; i < nir_op_infos[instr->op].num_inputs; i++) {
|
||||
if (nir_op_infos[instr->op].input_sizes[i] != 0)
|
||||
continue;
|
||||
|
||||
unsigned new_swizzle[4] = {0, 0, 0, 0};
|
||||
|
||||
/*
|
||||
* We keep two indices:
|
||||
* 1. The index of the original (non-SSA) component
|
||||
* 2. The index of the post-SSA, compacted, component
|
||||
*
|
||||
* We need to map the swizzle component at index 1 to the swizzle
|
||||
* component at index 2.
|
||||
*/
|
||||
|
||||
unsigned ssa_index = 0;
|
||||
for (unsigned index = 0; index < 4; index++) {
|
||||
if (!((write_mask >> index) & 1))
|
||||
continue;
|
||||
|
||||
new_swizzle[ssa_index] = instr->src[i].swizzle[index];
|
||||
ssa_index++;
|
||||
}
|
||||
|
||||
for (unsigned j = 0; j < 4; j++)
|
||||
instr->src[i].swizzle[j] = new_swizzle[j];
|
||||
}
|
||||
}
|
||||
|
||||
nir_op op;
|
||||
switch (reg->num_components) {
|
||||
case 2: op = nir_op_vec2; break;
|
||||
case 3: op = nir_op_vec3; break;
|
||||
case 4: op = nir_op_vec4; break;
|
||||
default: unreachable("not reached");
|
||||
}
|
||||
|
||||
nir_alu_instr *vec = nir_alu_instr_create(state->mem_ctx, op);
|
||||
|
||||
vec->dest.dest.reg.reg = reg;
|
||||
vec->dest.write_mask = (1 << reg->num_components) - 1;
|
||||
|
||||
nir_ssa_def *old_src = get_ssa_src(reg, state);
|
||||
nir_ssa_def *new_src = &instr->dest.dest.ssa;
|
||||
|
||||
unsigned ssa_index = 0;
|
||||
for (unsigned i = 0; i < reg->num_components; i++) {
|
||||
vec->src[i].src.is_ssa = true;
|
||||
if ((write_mask >> i) & 1) {
|
||||
vec->src[i].src.ssa = new_src;
|
||||
if (nir_op_infos[instr->op].output_size == 0)
|
||||
vec->src[i].swizzle[0] = ssa_index;
|
||||
else
|
||||
vec->src[i].swizzle[0] = i;
|
||||
ssa_index++;
|
||||
} else {
|
||||
vec->src[i].src.ssa = old_src;
|
||||
vec->src[i].swizzle[0] = i;
|
||||
}
|
||||
}
|
||||
|
||||
nir_instr_insert_after(&instr->instr, &vec->instr);
|
||||
|
||||
state->parent_instr = &vec->instr;
|
||||
rewrite_def_forwards(&vec->dest.dest, state);
|
||||
} else {
|
||||
rewrite_def_forwards(&instr->dest.dest, state);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
rewrite_phi_instr(nir_phi_instr *instr, rewrite_state *state)
|
||||
{
|
||||
state->parent_instr = &instr->instr;
|
||||
rewrite_def_forwards(&instr->dest, state);
|
||||
}
|
||||
|
||||
static void
|
||||
rewrite_instr_forward(nir_instr *instr, rewrite_state *state)
|
||||
{
|
||||
if (instr->type == nir_instr_type_alu) {
|
||||
rewrite_alu_instr_forward(nir_instr_as_alu(instr), state);
|
||||
return;
|
||||
}
|
||||
|
||||
if (instr->type == nir_instr_type_phi) {
|
||||
rewrite_phi_instr(nir_instr_as_phi(instr), state);
|
||||
return;
|
||||
}
|
||||
|
||||
state->parent_instr = instr;
|
||||
|
||||
nir_foreach_src(instr, rewrite_use, state);
|
||||
nir_foreach_dest(instr, rewrite_def_forwards, state);
|
||||
}
|
||||
|
||||
static void
|
||||
rewrite_phi_sources(nir_block *block, nir_block *pred, rewrite_state *state)
|
||||
{
|
||||
nir_foreach_instr(block, instr) {
|
||||
if (instr->type != nir_instr_type_phi)
|
||||
break;
|
||||
|
||||
nir_phi_instr *phi_instr = nir_instr_as_phi(instr);
|
||||
|
||||
state->parent_instr = instr;
|
||||
|
||||
nir_foreach_phi_src(phi_instr, src) {
|
||||
if (src->pred == pred) {
|
||||
rewrite_use(&src->src, state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
rewrite_def_backwards(nir_dest *dest, void *_state)
|
||||
{
|
||||
rewrite_state *state = (rewrite_state *) _state;
|
||||
|
||||
if (!dest->is_ssa)
|
||||
return true;
|
||||
|
||||
struct hash_entry *entry =
|
||||
_mesa_hash_table_search(state->ssa_map, &dest->ssa);
|
||||
|
||||
if (!entry)
|
||||
return true;
|
||||
|
||||
nir_register *reg = (nir_register *) entry->data;
|
||||
unsigned index = reg->index;
|
||||
|
||||
state->states[index].index--;
|
||||
assert(state->states[index].index >= -1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
rewrite_instr_backwards(nir_instr *instr, rewrite_state *state)
|
||||
{
|
||||
nir_foreach_dest(instr, rewrite_def_backwards, state);
|
||||
}
|
||||
|
||||
static void
|
||||
rewrite_block(nir_block *block, rewrite_state *state)
|
||||
{
|
||||
/* This will skip over any instructions after the current one, which is
|
||||
* what we want because those instructions (vector gather, conditional
|
||||
* select) will already be in SSA form.
|
||||
*/
|
||||
nir_foreach_instr_safe(block, instr) {
|
||||
rewrite_instr_forward(instr, state);
|
||||
}
|
||||
|
||||
if (block != state->impl->end_block &&
|
||||
!nir_cf_node_is_last(&block->cf_node) &&
|
||||
nir_cf_node_next(&block->cf_node)->type == nir_cf_node_if) {
|
||||
nir_if *if_stmt = nir_cf_node_as_if(nir_cf_node_next(&block->cf_node));
|
||||
state->parent_instr = NULL;
|
||||
state->parent_if = if_stmt;
|
||||
rewrite_use(&if_stmt->condition, state);
|
||||
}
|
||||
|
||||
if (block->successors[0])
|
||||
rewrite_phi_sources(block->successors[0], block, state);
|
||||
if (block->successors[1])
|
||||
rewrite_phi_sources(block->successors[1], block, state);
|
||||
|
||||
for (unsigned i = 0; i < block->num_dom_children; i++)
|
||||
rewrite_block(block->dom_children[i], state);
|
||||
|
||||
nir_foreach_instr_reverse(block, instr) {
|
||||
rewrite_instr_backwards(instr, state);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
remove_unused_regs(nir_function_impl *impl, rewrite_state *state)
|
||||
{
|
||||
foreach_list_typed_safe(nir_register, reg, node, &impl->registers) {
|
||||
if (state->states[reg->index].stack != NULL)
|
||||
exec_node_remove(®->node);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
init_rewrite_state(nir_function_impl *impl, rewrite_state *state)
|
||||
{
|
||||
state->impl = impl;
|
||||
state->mem_ctx = ralloc_parent(impl);
|
||||
state->ssa_map = _mesa_hash_table_create(NULL, _mesa_hash_pointer,
|
||||
_mesa_key_pointer_equal);
|
||||
state->states = ralloc_array(NULL, reg_state, impl->reg_alloc);
|
||||
|
||||
foreach_list_typed(nir_register, reg, node, &impl->registers) {
|
||||
assert(reg->index < impl->reg_alloc);
|
||||
if (reg->num_array_elems > 0) {
|
||||
state->states[reg->index].stack = NULL;
|
||||
} else {
|
||||
/*
|
||||
* Calculate a conservative estimate of the stack size based on the
|
||||
* number of definitions there are. Note that this function *must* be
|
||||
* called after phi nodes are inserted so we can count phi node
|
||||
* definitions too.
|
||||
*/
|
||||
unsigned stack_size = list_length(®->defs);
|
||||
|
||||
state->states[reg->index].stack = ralloc_array(state->states,
|
||||
nir_ssa_def *,
|
||||
stack_size);
|
||||
#ifndef NDEBUG
|
||||
state->states[reg->index].stack_size = stack_size;
|
||||
#endif
|
||||
state->states[reg->index].index = -1;
|
||||
state->states[reg->index].num_defs = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
destroy_rewrite_state(rewrite_state *state)
|
||||
{
|
||||
_mesa_hash_table_destroy(state->ssa_map, NULL);
|
||||
ralloc_free(state->states);
|
||||
}
|
||||
|
||||
void
|
||||
nir_convert_to_ssa_impl(nir_function_impl *impl)
|
||||
{
|
||||
nir_metadata_require(impl, nir_metadata_dominance);
|
||||
|
||||
insert_phi_nodes(impl);
|
||||
|
||||
rewrite_state state;
|
||||
init_rewrite_state(impl, &state);
|
||||
|
||||
rewrite_block(nir_start_block(impl), &state);
|
||||
|
||||
remove_unused_regs(impl, &state);
|
||||
|
||||
nir_metadata_preserve(impl, nir_metadata_block_index |
|
||||
nir_metadata_dominance);
|
||||
|
||||
destroy_rewrite_state(&state);
|
||||
}
|
||||
|
||||
void
|
||||
nir_convert_to_ssa(nir_shader *shader)
|
||||
{
|
||||
nir_foreach_function(shader, function) {
|
||||
if (function->impl)
|
||||
nir_convert_to_ssa_impl(function->impl);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright 2015 VMware, Inc.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* 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, sub license, 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 NON-INFRINGEMENT.
|
||||
* IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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.
|
||||
*
|
||||
**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "c99_alloca.h"
|
||||
|
||||
|
||||
/* Declare a variable length array, with no initialization */
|
||||
#define NIR_VLA(_type, _name, _length) \
|
||||
_type *_name = alloca((_length) * sizeof *_name)
|
||||
|
||||
|
||||
/* Declare a variable length array, and initialize it with the given byte.
|
||||
*
|
||||
* _length is evaluated twice, so expressions with side-effects must be
|
||||
* avoided.
|
||||
*/
|
||||
#define NIR_VLA_FILL(_type, _name, _length, _byte) \
|
||||
_type *_name = memset(alloca((_length) * sizeof *_name), _byte, (_length) * sizeof *_name)
|
||||
|
||||
|
||||
/* Declare a variable length array, and zero it.
|
||||
*
|
||||
* Just like NIR_VLA_FILL, _length is evaluated twice, so expressions with
|
||||
* side-effects must be avoided.
|
||||
*/
|
||||
#define NIR_VLA_ZERO(_type, _name, _length) \
|
||||
NIR_VLA_FILL(_type, _name, _length, 0)
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nir_worklist.h"
|
||||
|
||||
void
|
||||
nir_block_worklist_init(nir_block_worklist *w, unsigned num_blocks,
|
||||
void *mem_ctx)
|
||||
{
|
||||
w->size = num_blocks;
|
||||
w->count = 0;
|
||||
w->start = 0;
|
||||
|
||||
w->blocks_present = rzalloc_array(mem_ctx, BITSET_WORD,
|
||||
BITSET_WORDS(num_blocks));
|
||||
w->blocks = ralloc_array(mem_ctx, nir_block *, num_blocks);
|
||||
}
|
||||
|
||||
void
|
||||
nir_block_worklist_fini(nir_block_worklist *w)
|
||||
{
|
||||
ralloc_free(w->blocks_present);
|
||||
ralloc_free(w->blocks);
|
||||
}
|
||||
|
||||
static bool
|
||||
worklist_add_block(nir_block *block, void *w)
|
||||
{
|
||||
nir_block_worklist_push_tail(w, block);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
nir_block_worklist_add_all(nir_block_worklist *w, nir_function_impl *impl)
|
||||
{
|
||||
nir_foreach_block(impl, worklist_add_block, w);
|
||||
}
|
||||
|
||||
void
|
||||
nir_block_worklist_push_head(nir_block_worklist *w, nir_block *block)
|
||||
{
|
||||
/* Pushing a block we already have is a no-op */
|
||||
if (BITSET_TEST(w->blocks_present, block->index))
|
||||
return;
|
||||
|
||||
assert(w->count < w->size);
|
||||
|
||||
if (w->start == 0)
|
||||
w->start = w->size - 1;
|
||||
else
|
||||
w->start--;
|
||||
|
||||
w->count++;
|
||||
|
||||
w->blocks[w->start] = block;
|
||||
BITSET_SET(w->blocks_present, block->index);
|
||||
}
|
||||
|
||||
nir_block *
|
||||
nir_block_worklist_peek_head(const nir_block_worklist *w)
|
||||
{
|
||||
assert(w->count > 0);
|
||||
|
||||
return w->blocks[w->start];
|
||||
}
|
||||
|
||||
nir_block *
|
||||
nir_block_worklist_pop_head(nir_block_worklist *w)
|
||||
{
|
||||
assert(w->count > 0);
|
||||
|
||||
unsigned head = w->start;
|
||||
|
||||
w->start = (w->start + 1) % w->size;
|
||||
w->count--;
|
||||
|
||||
BITSET_CLEAR(w->blocks_present, w->blocks[head]->index);
|
||||
return w->blocks[head];
|
||||
}
|
||||
|
||||
void
|
||||
nir_block_worklist_push_tail(nir_block_worklist *w, nir_block *block)
|
||||
{
|
||||
/* Pushing a block we already have is a no-op */
|
||||
if (BITSET_TEST(w->blocks_present, block->index))
|
||||
return;
|
||||
|
||||
assert(w->count < w->size);
|
||||
|
||||
w->count++;
|
||||
|
||||
unsigned tail = (w->start + w->count - 1) % w->size;
|
||||
|
||||
w->blocks[tail] = block;
|
||||
BITSET_SET(w->blocks_present, block->index);
|
||||
}
|
||||
|
||||
nir_block *
|
||||
nir_block_worklist_peek_tail(const nir_block_worklist *w)
|
||||
{
|
||||
assert(w->count > 0);
|
||||
|
||||
unsigned tail = (w->start + w->count - 1) % w->size;
|
||||
|
||||
return w->blocks[tail];
|
||||
}
|
||||
|
||||
nir_block *
|
||||
nir_block_worklist_pop_tail(nir_block_worklist *w)
|
||||
{
|
||||
assert(w->count > 0);
|
||||
|
||||
unsigned tail = (w->start + w->count - 1) % w->size;
|
||||
|
||||
w->count--;
|
||||
|
||||
BITSET_CLEAR(w->blocks_present, w->blocks[tail]->index);
|
||||
return w->blocks[tail];
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright © 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@jlekstrand.net)
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef _NIR_WORKLIST_
|
||||
#define _NIR_WORKLIST_
|
||||
|
||||
#include "nir.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Represents a double-ended queue of unique blocks
|
||||
*
|
||||
* The worklist datastructure guarantees that eacy block is in the queue at
|
||||
* most once. Pushing a block onto either end of the queue is a no-op if
|
||||
* the block is already in the queue. In order for this to work, the
|
||||
* caller must ensure that the blocks are properly indexed.
|
||||
*/
|
||||
typedef struct {
|
||||
/* The total size of the worklist */
|
||||
unsigned size;
|
||||
|
||||
/* The number of blocks currently in the worklist */
|
||||
unsigned count;
|
||||
|
||||
/* The offset in the array of blocks at which the list starts */
|
||||
unsigned start;
|
||||
|
||||
/* A bitset of all of the blocks currently present in the worklist */
|
||||
BITSET_WORD *blocks_present;
|
||||
|
||||
/* The actual worklist */
|
||||
nir_block **blocks;
|
||||
} nir_block_worklist;
|
||||
|
||||
void nir_block_worklist_init(nir_block_worklist *w, unsigned num_blocks,
|
||||
void *mem_ctx);
|
||||
void nir_block_worklist_fini(nir_block_worklist *w);
|
||||
|
||||
void nir_block_worklist_add_all(nir_block_worklist *w, nir_function_impl *impl);
|
||||
|
||||
static inline bool
|
||||
nir_block_worklist_is_empty(const nir_block_worklist *w)
|
||||
{
|
||||
return w->count == 0;
|
||||
}
|
||||
|
||||
void nir_block_worklist_push_head(nir_block_worklist *w, nir_block *block);
|
||||
|
||||
nir_block *nir_block_worklist_peek_head(const nir_block_worklist *w);
|
||||
|
||||
nir_block *nir_block_worklist_pop_head(nir_block_worklist *w);
|
||||
|
||||
void nir_block_worklist_push_tail(nir_block_worklist *w, nir_block *block);
|
||||
|
||||
nir_block *nir_block_worklist_peek_tail(const nir_block_worklist *w);
|
||||
|
||||
nir_block *nir_block_worklist_pop_tail(nir_block_worklist *w);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* _NIR_WORKLIST_ */
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright © 2015 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.
|
||||
*/
|
||||
#include <gtest/gtest.h>
|
||||
#include "nir.h"
|
||||
#include "nir_builder.h"
|
||||
|
||||
class nir_cf_test : public ::testing::Test {
|
||||
protected:
|
||||
nir_cf_test();
|
||||
~nir_cf_test();
|
||||
|
||||
nir_builder b;
|
||||
};
|
||||
|
||||
nir_cf_test::nir_cf_test()
|
||||
{
|
||||
static const nir_shader_compiler_options options = { };
|
||||
nir_builder_init_simple_shader(&b, NULL, MESA_SHADER_VERTEX, &options);
|
||||
}
|
||||
|
||||
nir_cf_test::~nir_cf_test()
|
||||
{
|
||||
ralloc_free(b.shader);
|
||||
}
|
||||
|
||||
TEST_F(nir_cf_test, delete_break_in_loop)
|
||||
{
|
||||
/* Create IR:
|
||||
*
|
||||
* while (...) { break; }
|
||||
*/
|
||||
nir_loop *loop = nir_loop_create(b.shader);
|
||||
nir_cf_node_insert(nir_after_cf_list(&b.impl->body), &loop->cf_node);
|
||||
|
||||
b.cursor = nir_after_cf_list(&loop->body);
|
||||
|
||||
nir_jump_instr *jump = nir_jump_instr_create(b.shader, nir_jump_break);
|
||||
nir_builder_instr_insert(&b, &jump->instr);
|
||||
|
||||
/* At this point, we should have:
|
||||
*
|
||||
* impl main {
|
||||
* block block_0:
|
||||
* // preds:
|
||||
* // succs: block_1
|
||||
* loop {
|
||||
* block block_1:
|
||||
* // preds: block_0
|
||||
* break
|
||||
* // succs: block_2
|
||||
* }
|
||||
* block block_2:
|
||||
* // preds: block_1
|
||||
* // succs: block_3
|
||||
* block block_3:
|
||||
* }
|
||||
*/
|
||||
nir_block *block_0 = nir_start_block(b.impl);
|
||||
nir_block *block_1 = nir_cf_node_as_block(nir_loop_first_cf_node(loop));
|
||||
nir_block *block_2 = nir_cf_node_as_block(nir_cf_node_next(&loop->cf_node));
|
||||
nir_block *block_3 = b.impl->end_block;
|
||||
ASSERT_EQ(nir_cf_node_block, block_0->cf_node.type);
|
||||
ASSERT_EQ(nir_cf_node_block, block_1->cf_node.type);
|
||||
ASSERT_EQ(nir_cf_node_block, block_2->cf_node.type);
|
||||
ASSERT_EQ(nir_cf_node_block, block_3->cf_node.type);
|
||||
|
||||
/* Verify the successors and predecessors. */
|
||||
EXPECT_EQ(block_1, block_0->successors[0]);
|
||||
EXPECT_EQ(NULL, block_0->successors[1]);
|
||||
EXPECT_EQ(block_2, block_1->successors[0]);
|
||||
EXPECT_EQ(NULL, block_1->successors[1]);
|
||||
EXPECT_EQ(block_3, block_2->successors[0]);
|
||||
EXPECT_EQ(NULL, block_2->successors[1]);
|
||||
EXPECT_EQ(NULL, block_3->successors[0]);
|
||||
EXPECT_EQ(NULL, block_3->successors[1]);
|
||||
EXPECT_EQ(0, block_0->predecessors->entries);
|
||||
EXPECT_EQ(1, block_1->predecessors->entries);
|
||||
EXPECT_EQ(1, block_2->predecessors->entries);
|
||||
EXPECT_EQ(1, block_3->predecessors->entries);
|
||||
EXPECT_TRUE(_mesa_set_search(block_1->predecessors, block_0));
|
||||
EXPECT_TRUE(_mesa_set_search(block_2->predecessors, block_1));
|
||||
EXPECT_TRUE(_mesa_set_search(block_3->predecessors, block_2));
|
||||
|
||||
nir_print_shader(b.shader, stderr);
|
||||
|
||||
/* Now remove the break. */
|
||||
nir_instr_remove(&jump->instr);
|
||||
|
||||
nir_print_shader(b.shader, stderr);
|
||||
|
||||
/* At this point, we should have:
|
||||
*
|
||||
* impl main {
|
||||
* block block_0:
|
||||
* // preds:
|
||||
* // succs: block_1
|
||||
* loop {
|
||||
* block block_1:
|
||||
* // preds: block_0 block_1
|
||||
* // succs: block_1
|
||||
* }
|
||||
* block block_2:
|
||||
* // preds: block_1
|
||||
* // succs: block_3
|
||||
* block block_3:
|
||||
* }
|
||||
*
|
||||
* Re-verify the predecessors and successors.
|
||||
*/
|
||||
EXPECT_EQ(block_1, block_0->successors[0]);
|
||||
EXPECT_EQ(NULL, block_0->successors[1]);
|
||||
EXPECT_EQ(block_1, block_1->successors[0]); /* back to itself */
|
||||
EXPECT_EQ(block_2, block_1->successors[1]); /* fake successor */
|
||||
EXPECT_EQ(block_3, block_2->successors[0]);
|
||||
EXPECT_EQ(NULL, block_2->successors[1]);
|
||||
EXPECT_EQ(NULL, block_3->successors[0]);
|
||||
EXPECT_EQ(NULL, block_3->successors[1]);
|
||||
EXPECT_EQ(0, block_0->predecessors->entries);
|
||||
EXPECT_EQ(2, block_1->predecessors->entries);
|
||||
EXPECT_EQ(1, block_2->predecessors->entries);
|
||||
EXPECT_EQ(1, block_3->predecessors->entries);
|
||||
EXPECT_TRUE(_mesa_set_search(block_1->predecessors, block_0));
|
||||
EXPECT_TRUE(_mesa_set_search(block_1->predecessors, block_1));
|
||||
EXPECT_TRUE(_mesa_set_search(block_2->predecessors, block_1));
|
||||
EXPECT_TRUE(_mesa_set_search(block_3->predecessors, block_2));
|
||||
|
||||
nir_metadata_require(b.impl, nir_metadata_dominance);
|
||||
}
|
||||
Reference in New Issue
Block a user