Implement dead variable elimination.

This commit is contained in:
Hans-Kristian Arntzen 2016-08-26 12:58:50 +02:00
parent bd5eb1d2c6
commit f61a5d1e5d
13 changed files with 230 additions and 31 deletions

View File

@ -381,6 +381,7 @@ struct CLIArguments
bool cpp = false;
bool metal = false;
bool vulkan_semantics = false;
bool remove_unused = false;
};
static void print_help()
@ -389,7 +390,7 @@ static void print_help()
"version>] [--dump-resources] [--help] [--force-temporary] [--cpp] [--cpp-interface-name <name>] "
"[--metal] [--vulkan-semantics] [--flatten-ubo] [--fixup-clipspace] [--iterations iter] [--pls-in "
"format input-name] [--pls-out format output-name] [--remap source_name target_name components] "
"[--extension ext] [--entry name]\n");
"[--extension ext] [--entry name] [--remove-unused-variables]\n");
}
static bool remap_generic(Compiler &compiler, const vector<Resource> &resources, const Remap &remap)
@ -527,6 +528,8 @@ int main(int argc, char *argv[])
args.pls_out.push_back({ move(fmt), move(name) });
});
cbs.add("--remove-unused-variables", [&args](CLIParser &) { args.remove_unused = true; });
cbs.default_handler = [&args](const char *value) { args.input = value; };
cbs.error_handler = [] { print_help(); };
@ -580,7 +583,15 @@ int main(int argc, char *argv[])
opts.vertex.fixup_clipspace = args.fixup;
compiler->set_options(opts);
auto res = compiler->get_shader_resources();
ShaderResources res;
if (args.remove_unused)
{
auto active = compiler->get_active_interface_variables();
res = compiler->get_shader_resources(active);
compiler->set_enabled_interface_variables(move(active));
}
else
res = compiler->get_shader_resources();
if (args.flatten_ubo)
for (auto &ubo : res.uniform_buffers)

View File

@ -77,5 +77,8 @@ void main()
k = lessThanEqual(a, a);
k = greaterThan(a, a);
k = greaterThanEqual(a, a);
ssbo_1.b.x = (ssbo_1.b.x + 1.0lf);
ssbo_2.b[0].x = (ssbo_2.b[0].x + 1.0lf);
ssbo_3.b[0].x = (ssbo_3.b[0].x + 1.0lf);
}

View File

@ -46,5 +46,7 @@ void main()
ssbo_1.b = (ssbo_1.b - u64vec4(i64vec4(1l)));
ssbo_1.b = doubleBitsToUint64(int64BitsToDouble(ssbo_0.a));
ssbo_0.a = doubleBitsToInt64(uint64BitsToDouble(ssbo_1.b));
ssbo_2.a[0] = (ssbo_2.a[0] + 1l);
ssbo_3.a[0] = (ssbo_3.a[0] + 2l);
}

View File

@ -84,4 +84,8 @@ void main()
k = lessThanEqual(a, a);
k = greaterThan(a, a);
k = greaterThanEqual(a, a);
ssbo_1.b.x += 1.0lf;
ssbo_2.b[0].x += 1.0lf;
ssbo_3.b[0].x += 1.0lf;
}

View File

@ -49,4 +49,7 @@ void main()
ssbo_1.b = doubleBitsToUint64(int64BitsToDouble(ssbo_0.a));
ssbo_0.a = doubleBitsToInt64(uint64BitsToDouble(ssbo_1.b));
ssbo_2.a[0] += 1l;
ssbo_3.a[0] += 2l;
}

View File

@ -164,8 +164,8 @@ void CompilerCPP::emit_resources()
auto &type = get<SPIRType>(var.basetype);
if (var.storage != StorageClassFunction && type.pointer && type.storage == StorageClassUniform &&
!is_builtin_variable(var) && (meta[type.self].decoration.decoration_flags &
((1ull << DecorationBlock) | (1ull << DecorationBufferBlock))))
!is_hidden_variable(var) && (meta[type.self].decoration.decoration_flags &
((1ull << DecorationBlock) | (1ull << DecorationBufferBlock))))
{
emit_buffer_block(var);
}
@ -179,8 +179,11 @@ void CompilerCPP::emit_resources()
{
auto &var = id.get<SPIRVariable>();
auto &type = get<SPIRType>(var.basetype);
if (var.storage != StorageClassFunction && type.pointer && type.storage == StorageClassPushConstant)
if (!is_hidden_variable(var) && var.storage != StorageClassFunction && type.pointer &&
type.storage == StorageClassPushConstant)
{
emit_push_constant_block(var);
}
}
}
@ -192,8 +195,8 @@ void CompilerCPP::emit_resources()
auto &var = id.get<SPIRVariable>();
auto &type = get<SPIRType>(var.basetype);
if (var.storage != StorageClassFunction && !is_builtin_variable(var) && !var.remapped_variable &&
type.pointer && (var.storage == StorageClassInput || var.storage == StorageClassOutput) &&
if (var.storage != StorageClassFunction && !is_hidden_variable(var) && type.pointer &&
(var.storage == StorageClassInput || var.storage == StorageClassOutput) &&
interface_variable_exists_in_entry_point(var.self))
{
emit_interface_block(var);
@ -209,8 +212,7 @@ void CompilerCPP::emit_resources()
auto &var = id.get<SPIRVariable>();
auto &type = get<SPIRType>(var.basetype);
if (var.storage != StorageClassFunction && !is_builtin_variable(var) && !var.remapped_variable &&
type.pointer &&
if (var.storage != StorageClassFunction && !is_hidden_variable(var) && type.pointer &&
(type.storage == StorageClassUniformConstant || type.storage == StorageClassAtomicCounter))
{
emit_uniform(var);

View File

@ -360,6 +360,34 @@ bool Compiler::is_immutable(uint32_t id) const
return false;
}
static inline bool storage_class_is_interface(spv::StorageClass storage)
{
switch (storage)
{
case StorageClassInput:
case StorageClassOutput:
case StorageClassUniform:
case StorageClassUniformConstant:
case StorageClassAtomicCounter:
case StorageClassPushConstant:
return true;
default:
return false;
}
}
bool Compiler::is_hidden_variable(const SPIRVariable &var, bool include_builtins) const
{
if ((is_builtin_variable(var) && !include_builtins) || var.remapped_variable)
return true;
bool hidden = false;
if (check_active_interface_variables && storage_class_is_interface(var.storage))
hidden = active_interface_variables.find(var.self) == end(active_interface_variables);
return hidden;
}
bool Compiler::is_builtin_variable(const SPIRVariable &var) const
{
if (var.compat_builtin || meta[var.self].decoration.builtin)
@ -402,6 +430,99 @@ bool Compiler::is_matrix(const SPIRType &type) const
}
ShaderResources Compiler::get_shader_resources() const
{
return get_shader_resources(nullptr);
}
ShaderResources Compiler::get_shader_resources(const unordered_set<uint32_t> &active_variables) const
{
return get_shader_resources(&active_variables);
}
bool Compiler::InterfaceVariableAccessHandler::handle(Op opcode, const uint32_t *args, uint32_t length)
{
uint32_t variable = 0;
switch (opcode)
{
// Need this first, otherwise, GCC complains about unhandled switch statements.
default:
break;
case OpFunctionCall:
{
// Invalid SPIR-V.
if (length < 3)
return false;
uint32_t count = length - 3;
args += 3;
for (uint32_t i = 0; i < count; i++)
{
auto *var = compiler.maybe_get<SPIRVariable>(args[i]);
if (var && storage_class_is_interface(var->storage))
variables.insert(args[i]);
}
break;
}
case OpAtomicStore:
case OpStore:
// Invalid SPIR-V.
if (length < 1)
return false;
variable = args[0];
break;
case OpAccessChain:
case OpInBoundsAccessChain:
case OpLoad:
case OpImageTexelPointer:
case OpAtomicLoad:
case OpAtomicExchange:
case OpAtomicCompareExchange:
case OpAtomicIIncrement:
case OpAtomicIDecrement:
case OpAtomicIAdd:
case OpAtomicISub:
case OpAtomicSMin:
case OpAtomicUMin:
case OpAtomicSMax:
case OpAtomicUMax:
case OpAtomicAnd:
case OpAtomicOr:
case OpAtomicXor:
// Invalid SPIR-V.
if (length < 3)
return false;
variable = args[2];
break;
}
if (variable)
{
auto *var = compiler.maybe_get<SPIRVariable>(variable);
if (var && storage_class_is_interface(var->storage))
variables.insert(variable);
}
return true;
}
unordered_set<uint32_t> Compiler::get_active_interface_variables() const
{
// Traverse the call graph and find all interface variables which are in use.
unordered_set<uint32_t> variables;
InterfaceVariableAccessHandler handler(*this, variables);
traverse_all_reachable_opcodes(get<SPIRFunction>(entry_point), handler);
return variables;
}
void Compiler::set_enabled_interface_variables(std::unordered_set<uint32_t> active_variables)
{
active_interface_variables = move(active_variables);
check_active_interface_variables = true;
}
ShaderResources Compiler::get_shader_resources(const unordered_set<uint32_t> *active_variables) const
{
ShaderResources res;
@ -418,6 +539,9 @@ ShaderResources Compiler::get_shader_resources() const
if (var.storage == StorageClassFunction || !type.pointer || is_builtin_variable(var))
continue;
if (active_variables && active_variables->find(var.self) == end(*active_variables))
continue;
// Input
if (var.storage == StorageClassInput && interface_variable_exists_in_entry_point(var.self))
{
@ -1777,8 +1901,7 @@ bool Compiler::traverse_all_reachable_opcodes(const SPIRBlock &block, OpcodeHand
if (!handler.handle(op, ops, i.length))
return false;
uint32_t func = ops[2];
if (op == OpFunctionCall && !traverse_all_reachable_opcodes(get<SPIRFunction>(func), handler))
if (op == OpFunctionCall && !traverse_all_reachable_opcodes(get<SPIRFunction>(ops[2]), handler))
return false;
}

View File

@ -176,9 +176,29 @@ public:
// The name of the uniform will be the same as the interface block name.
void flatten_interface_block(uint32_t id);
// Returns a set of all global variables which are statically accessed
// by the control flow graph from the current entry point.
// Only variables which change the interface for a shader are returned, that is,
// variables with storage class of Input, Output, Uniform, UniformConstant, PushConstant and AtomicCounter
// storage classes are returned.
//
// To use the returned set as the filter for which variables are used during compilation,
// this set can be moved to set_enabled_interface_variables().
std::unordered_set<uint32_t> get_active_interface_variables() const;
// Sets the interface variables which are used during compilation.
// By default, all variables are used.
// Once set, compile() will only consider the set in active_variables.
void set_enabled_interface_variables(std::unordered_set<uint32_t> active_variables);
// Query shader resources, use ids with reflection interface to modify or query binding points, etc.
ShaderResources get_shader_resources() const;
// Query shader resources, but only return the variables which are part of active_variables.
// E.g.: get_shader_resources(get_active_variables()) to only return the variables which are statically
// accessed.
ShaderResources get_shader_resources(const std::unordered_set<uint32_t> &active_variables) const;
// Remapped variables are considered built-in variables and a backend will
// not emit a declaration for this variable.
// This is mostly useful for making use of builtins which are dependent on extensions.
@ -237,6 +257,8 @@ protected:
SPIRBlock *current_block = nullptr;
std::vector<uint32_t> global_variables;
std::vector<uint32_t> aliased_variables;
std::unordered_set<uint32_t> active_interface_variables;
bool check_active_interface_variables = false;
// If our IDs are out of range here as part of opcodes, throw instead of
// undefined behavior.
@ -302,6 +324,7 @@ protected:
std::string to_name(uint32_t id, bool allow_alias = true);
bool is_builtin_variable(const SPIRVariable &var) const;
bool is_hidden_variable(const SPIRVariable &var, bool include_builtins = false) const;
bool is_immutable(uint32_t id) const;
bool is_member_builtin(const SPIRType &type, uint32_t index, spv::BuiltIn *builtin) const;
bool is_scalar(const SPIRType &type) const;
@ -399,10 +422,26 @@ private:
std::unordered_set<uint32_t> seen;
};
struct InterfaceVariableAccessHandler : OpcodeHandler
{
InterfaceVariableAccessHandler(const Compiler &compiler_, std::unordered_set<uint32_t> &variables_)
: compiler(compiler_)
, variables(variables_)
{
}
bool handle(spv::Op opcode, const uint32_t *args, uint32_t length) override;
const Compiler &compiler;
std::unordered_set<uint32_t> &variables;
};
bool traverse_all_reachable_opcodes(const SPIRBlock &block, OpcodeHandler &handler) const;
bool traverse_all_reachable_opcodes(const SPIRFunction &block, OpcodeHandler &handler) const;
// This must be an ordered data structure so we always pick the same type aliases.
std::vector<uint32_t> global_struct_cache;
ShaderResources get_shader_resources(const std::unordered_set<uint32_t> *active_variables) const;
};
}

View File

@ -1024,14 +1024,11 @@ void CompilerGLSL::replace_illegal_names()
if (id.get_type() == TypeVariable)
{
auto &var = id.get<SPIRVariable>();
if (!is_builtin_variable(var) && !var.remapped_variable)
if (!is_hidden_variable(var))
{
auto &m = meta[var.self].decoration;
if (m.alias.compare(0, 3, "gl_") == 0)
{
m.alias = join("_", m.alias);
}
}
}
}
@ -1178,8 +1175,8 @@ void CompilerGLSL::emit_resources()
auto &type = get<SPIRType>(var.basetype);
if (var.storage != StorageClassFunction && type.pointer && type.storage == StorageClassUniform &&
!is_builtin_variable(var) && (meta[type.self].decoration.decoration_flags &
((1ull << DecorationBlock) | (1ull << DecorationBufferBlock))))
!is_hidden_variable(var) && (meta[type.self].decoration.decoration_flags &
((1ull << DecorationBlock) | (1ull << DecorationBufferBlock))))
{
emit_buffer_block(var);
}
@ -1193,8 +1190,11 @@ void CompilerGLSL::emit_resources()
{
auto &var = id.get<SPIRVariable>();
auto &type = get<SPIRType>(var.basetype);
if (var.storage != StorageClassFunction && type.pointer && type.storage == StorageClassPushConstant)
if (!is_hidden_variable(var) && var.storage != StorageClassFunction && type.pointer &&
type.storage == StorageClassPushConstant)
{
emit_push_constant_block(var);
}
}
}
@ -1208,8 +1208,7 @@ void CompilerGLSL::emit_resources()
auto &var = id.get<SPIRVariable>();
auto &type = get<SPIRType>(var.basetype);
if (var.storage != StorageClassFunction && !is_builtin_variable(var) && !var.remapped_variable &&
type.pointer &&
if (var.storage != StorageClassFunction && !is_hidden_variable(var) && type.pointer &&
(type.storage == StorageClassUniformConstant || type.storage == StorageClassAtomicCounter))
{
emit_uniform(var);
@ -1230,8 +1229,8 @@ void CompilerGLSL::emit_resources()
auto &var = id.get<SPIRVariable>();
auto &type = get<SPIRType>(var.basetype);
if (var.storage != StorageClassFunction && !is_builtin_variable(var) && !var.remapped_variable &&
type.pointer && (var.storage == StorageClassInput || var.storage == StorageClassOutput) &&
if (var.storage != StorageClassFunction && !is_hidden_variable(var) && type.pointer &&
(var.storage == StorageClassInput || var.storage == StorageClassOutput) &&
interface_variable_exists_in_entry_point(var.self))
{
emit_interface_block(var);

View File

@ -184,7 +184,7 @@ void CompilerMSL::bind_vertex_attributes(std::set<uint32_t> &bindings)
auto &type = get<SPIRType>(var.basetype);
if (var.storage == StorageClassInput && interface_variable_exists_in_entry_point(var.self) &&
(!is_builtin_variable(var)) && !var.remapped_variable && type.pointer)
!is_hidden_variable(var) && type.pointer)
{
auto &dec = meta[var.self].decoration;
MSLVertexAttr *p_va = vtx_attrs_by_location[dec.location];
@ -226,8 +226,8 @@ uint32_t CompilerMSL::add_interface_struct(StorageClass storage, uint32_t vtx_bi
auto &dec = meta[var.self].decoration;
if (var.storage == storage && interface_variable_exists_in_entry_point(var.self) &&
(!is_builtin_variable(var) || incl_builtins) && (!match_binding || (vtx_binding == dec.binding)) &&
!var.remapped_variable && type.pointer)
!is_hidden_variable(var, incl_builtins) && (!match_binding || (vtx_binding == dec.binding)) &&
type.pointer)
{
vars.push_back(&var);
}
@ -427,8 +427,8 @@ void CompilerMSL::emit_resources()
if (var.storage != StorageClassFunction && type.pointer &&
(type.storage == StorageClassUniform || type.storage == StorageClassUniformConstant ||
type.storage == StorageClassPushConstant) &&
!is_builtin_variable(var) && (meta[type.self].decoration.decoration_flags &
((1ull << DecorationBlock) | (1ull << DecorationBufferBlock))))
!is_hidden_variable(var) && (meta[type.self].decoration.decoration_flags &
((1ull << DecorationBlock) | (1ull << DecorationBufferBlock))))
{
emit_struct(type);
}
@ -1095,6 +1095,9 @@ string CompilerMSL::entry_point_args(bool append_comma)
auto &var = id.get<SPIRVariable>();
auto &type = get<SPIRType>(var.basetype);
if (is_hidden_variable(var, true))
continue;
if (var.storage == StorageClassUniform || var.storage == StorageClassUniformConstant ||
var.storage == StorageClassPushConstant)
{

View File

@ -66,7 +66,7 @@ def validate_shader(shader, vulkan):
else:
subprocess.check_call(['glslangValidator', shader])
def cross_compile(shader, vulkan, spirv):
def cross_compile(shader, vulkan, spirv, eliminate):
spirv_f, spirv_path = tempfile.mkstemp()
glsl_f, glsl_path = tempfile.mkstemp(suffix = os.path.basename(shader))
os.close(spirv_f)
@ -86,14 +86,20 @@ def cross_compile(shader, vulkan, spirv):
# subprocess.check_call(['spirv-val', spirv_path])
spirv_cross_path = './spirv-cross'
subprocess.check_call([spirv_cross_path, '--entry', 'main', '--output', glsl_path, spirv_path])
if eliminate:
subprocess.check_call([spirv_cross_path, '--remove-unused-variables', '--entry', 'main', '--output', glsl_path, spirv_path])
else:
subprocess.check_call([spirv_cross_path, '--entry', 'main', '--output', glsl_path, spirv_path])
# A shader might not be possible to make valid GLSL from, skip validation for this case.
if (not ('nocompat' in glsl_path)) and (not spirv):
validate_shader(glsl_path, False)
if vulkan or spirv:
subprocess.check_call([spirv_cross_path, '--entry', 'main', '--vulkan-semantics', '--output', vulkan_glsl_path, spirv_path])
if eliminate:
subprocess.check_call([spirv_cross_path, '--remove-unused-variables', '--entry', 'main', '--vulkan-semantics', '--output', vulkan_glsl_path, spirv_path])
else:
subprocess.check_call([spirv_cross_path, '--entry', 'main', '--vulkan-semantics', '--output', vulkan_glsl_path, spirv_path])
validate_shader(vulkan_glsl_path, vulkan)
return (spirv_path, glsl_path, vulkan_glsl_path if vulkan else None)
@ -149,6 +155,9 @@ def shader_is_vulkan(shader):
def shader_is_desktop(shader):
return '.desktop.' in shader
def shader_is_eliminate_dead_variables(shader):
return '.noeliminate.' not in shader
def shader_is_spirv(shader):
return '.asm.' in shader
@ -156,10 +165,11 @@ def test_shader(stats, shader, update, keep):
joined_path = os.path.join(shader[0], shader[1])
vulkan = shader_is_vulkan(shader[1])
desktop = shader_is_desktop(shader[1])
eliminate = shader_is_eliminate_dead_variables(shader[1])
spirv = shader_is_spirv(shader[1])
print('Testing shader:', joined_path)
spirv, glsl, vulkan_glsl = cross_compile(joined_path, vulkan, spirv)
spirv, glsl, vulkan_glsl = cross_compile(joined_path, vulkan, spirv, eliminate)
# Only test GLSL stats if we have a shader following GL semantics.
if stats and (not vulkan) and (not spirv) and (not desktop):