glsl: move validate_{stage}_shader_executable() to the nir linker

Reviewed-by: Alejandro Piñeiro <apinheiro@igalia.com>
Reviewed-by: Marek Olšák <marek.olsak@amd.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/28690>
This commit is contained in:
Timothy Arceri 2024-04-10 09:53:15 +10:00
parent 316165afec
commit 875ca9ec11
4 changed files with 346 additions and 395 deletions

View file

@ -31,6 +31,7 @@
#include "string_to_uint_map.h"
#include "main/shader_types.h"
#include "main/consts_exts.h"
#include "main/context.h"
#include "main/shaderobj.h"
#include "ir_uniform.h" /* for gl_uniform_storage */
#include "util/glheader.h"
@ -302,6 +303,40 @@ gl_nir_mode_string(const nir_variable *var)
return "invalid variable";
}
static void
remove_dead_functions(nir_shader *shader)
{
struct set *fn_set =
_mesa_set_create(NULL, _mesa_hash_pointer, _mesa_key_pointer_equal);
/* Find all function prototypes adding them to a list then removing them
* if they are ever called.
*/
nir_foreach_function_impl(impl, shader) {
_mesa_set_add(fn_set, impl->function);
}
nir_foreach_function_impl(impl, shader) {
nir_foreach_block(block, impl) {
nir_foreach_instr(instr, block) {
if (instr->type == nir_instr_type_call) {
nir_call_instr *call = nir_instr_as_call(instr);
_mesa_set_remove_key(fn_set, call->callee);
}
}
}
}
/* Any functions remaining in the list must be unused so remove them. */
set_foreach(fn_set, entry) {
nir_function *func = (nir_function *) entry->key;
if (!func->is_entrypoint)
exec_node_remove(&func->node);
}
_mesa_set_destroy(fn_set, NULL);
}
bool
gl_nir_can_add_pointsize_to_program(const struct gl_constants *consts,
struct gl_program *prog)
@ -2396,17 +2431,320 @@ validate_invariant_builtins(const struct gl_constants *consts,
return true;
}
bool
gl_nir_link_glsl(const struct gl_constants *consts,
const struct gl_extensions *exts,
gl_api api,
struct gl_shader_program *prog)
static void
find_assignments(nir_shader *shader, nir_variable *var1, nir_variable *var2,
nir_variable *var3, bool *var1_written, bool *var2_written,
bool *var3_written)
{
nir_foreach_function_impl(impl, shader) {
nir_foreach_block(block, impl) {
nir_foreach_instr(instr, block) {
if (instr->type == nir_instr_type_intrinsic) {
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
if (intrin->intrinsic == nir_intrinsic_store_deref ||
intrin->intrinsic == nir_intrinsic_copy_deref) {
nir_deref_instr *deref = nir_src_as_deref(intrin->src[0]);
nir_variable *var = nir_deref_instr_get_variable(deref);
if (!var)
continue;
if (var == var1)
*var1_written = true;
else if (var == var2)
*var2_written = true;
else if (var == var3)
*var3_written = true;
}
}
}
}
}
}
/**
* Set clip_distance_array_size based and cull_distance_array_size on the given
* shader.
*
* Also check for errors based on incorrect usage of gl_ClipVertex and
* gl_ClipDistance and gl_CullDistance.
* Additionally test whether the arrays gl_ClipDistance and gl_CullDistance
* exceed the maximum size defined by gl_MaxCombinedClipAndCullDistances.
*/
static void
analyze_clip_cull_usage(struct gl_shader_program *prog, nir_shader *shader,
const struct gl_constants *consts,
struct shader_info *info)
{
if (consts->DoDCEBeforeClipCullAnalysis) {
/* Remove dead functions to avoid raising an error (eg: dead function
* writes to gl_ClipVertex, and main() writes to gl_ClipDistance).
*/
remove_dead_functions(shader);
}
info->clip_distance_array_size = 0;
info->cull_distance_array_size = 0;
if (prog->GLSL_Version >= (prog->IsES ? 300 : 130)) {
/* From section 7.1 (Vertex Shader Special Variables) of the
* GLSL 1.30 spec:
*
* "It is an error for a shader to statically write both
* gl_ClipVertex and gl_ClipDistance."
*
* This does not apply to GLSL ES shaders, since GLSL ES defines neither
* gl_ClipVertex nor gl_ClipDistance. However with
* GL_EXT_clip_cull_distance, this functionality is exposed in ES 3.0.
*/
nir_variable *clip_dist =
nir_find_variable_with_location(shader,
nir_var_shader_out,
VARYING_SLOT_CLIP_DIST0);
nir_variable *cull_dist =
nir_find_variable_with_location(shader,
nir_var_shader_out,
VARYING_SLOT_CULL_DIST0);
nir_variable *clip_vert =
nir_find_variable_with_location(shader,
nir_var_shader_out,
VARYING_SLOT_CLIP_VERTEX);
bool clip_dist_written = false;
bool cull_dist_written = false;
bool clip_vert_written = false;
find_assignments(shader, clip_dist, cull_dist, clip_vert,
&clip_dist_written, &cull_dist_written,
&clip_vert_written);
/* From the ARB_cull_distance spec:
*
* It is a compile-time or link-time error for the set of shaders forming
* a program to statically read or write both gl_ClipVertex and either
* gl_ClipDistance or gl_CullDistance.
*
* This does not apply to GLSL ES shaders, since GLSL ES doesn't define
* gl_ClipVertex.
*/
if (!prog->IsES) {
if (clip_vert_written && clip_dist_written) {
linker_error(prog, "%s shader writes to both `gl_ClipVertex' "
"and `gl_ClipDistance'\n",
_mesa_shader_stage_to_string(info->stage));
return;
}
if (clip_vert_written && cull_dist_written) {
linker_error(prog, "%s shader writes to both `gl_ClipVertex' "
"and `gl_CullDistance'\n",
_mesa_shader_stage_to_string(info->stage));
return;
}
}
if (clip_dist_written)
info->clip_distance_array_size = glsl_get_length(clip_dist->type);
if (cull_dist_written)
info->cull_distance_array_size = glsl_get_length(cull_dist->type);
/* From the ARB_cull_distance spec:
*
* It is a compile-time or link-time error for the set of shaders forming
* a program to have the sum of the sizes of the gl_ClipDistance and
* gl_CullDistance arrays to be larger than
* gl_MaxCombinedClipAndCullDistances.
*/
if ((uint32_t)(info->clip_distance_array_size + info->cull_distance_array_size) >
consts->MaxClipPlanes) {
linker_error(prog, "%s shader: the combined size of "
"'gl_ClipDistance' and 'gl_CullDistance' size cannot "
"be larger than "
"gl_MaxCombinedClipAndCullDistances (%u)",
_mesa_shader_stage_to_string(info->stage),
consts->MaxClipPlanes);
}
}
}
/**
* Verify that a vertex shader executable meets all semantic requirements.
*
* Also sets info.clip_distance_array_size and
* info.cull_distance_array_size as a side effect.
*
* \param shader Vertex shader executable to be verified
*/
static void
validate_vertex_shader_executable(struct gl_shader_program *prog,
nir_shader *shader,
const struct gl_constants *consts)
{
if (shader == NULL)
return;
/* From the GLSL 1.10 spec, page 48:
*
* "The variable gl_Position is available only in the vertex
* language and is intended for writing the homogeneous vertex
* position. All executions of a well-formed vertex shader
* executable must write a value into this variable. [...] The
* variable gl_Position is available only in the vertex
* language and is intended for writing the homogeneous vertex
* position. All executions of a well-formed vertex shader
* executable must write a value into this variable."
*
* while in GLSL 1.40 this text is changed to:
*
* "The variable gl_Position is available only in the vertex
* language and is intended for writing the homogeneous vertex
* position. It can be written at any time during shader
* execution. It may also be read back by a vertex shader
* after being written. This value will be used by primitive
* assembly, clipping, culling, and other fixed functionality
* operations, if present, that operate on primitives after
* vertex processing has occurred. Its value is undefined if
* the vertex shader executable does not write gl_Position."
*
* All GLSL ES Versions are similar to GLSL 1.40--failing to write to
* gl_Position is not an error.
*/
if (prog->GLSL_Version < (prog->IsES ? 300 : 140)) {
nir_variable *gl_position =
nir_find_variable_with_location(shader,
nir_var_shader_out,
VARYING_SLOT_POS);
bool gl_position_written = false;
find_assignments(shader, gl_position, NULL, NULL, &gl_position_written,
NULL, NULL);
if (!gl_position_written) {
if (prog->IsES) {
linker_warning(prog,
"vertex shader does not write to `gl_Position'. "
"Its value is undefined. \n");
} else {
linker_error(prog,
"vertex shader does not write to `gl_Position'. \n");
}
return;
}
}
analyze_clip_cull_usage(prog, shader, consts, &shader->info);
}
static void
validate_tess_eval_shader_executable(struct gl_shader_program *prog,
nir_shader *shader,
const struct gl_constants *consts)
{
if (shader == NULL)
return;
analyze_clip_cull_usage(prog, shader, consts, &shader->info);
}
/**
* Verify that a fragment shader executable meets all semantic requirements
*
* \param shader Fragment shader executable to be verified
*/
static void
validate_fragment_shader_executable(struct gl_shader_program *prog,
nir_shader *shader)
{
if (shader == NULL)
return;
nir_variable *gl_frag_color =
nir_find_variable_with_location(shader,
nir_var_shader_out,
FRAG_RESULT_COLOR);
nir_variable *gl_frag_data =
nir_find_variable_with_location(shader,
nir_var_shader_out,
FRAG_RESULT_DATA0);
bool gl_frag_color_written = false;
bool gl_frag_data_written = false;
find_assignments(shader, gl_frag_color, gl_frag_data, NULL,
&gl_frag_color_written, &gl_frag_data_written, NULL);
if (gl_frag_color_written && gl_frag_data_written) {
linker_error(prog, "fragment shader writes to both "
"`gl_FragColor' and `gl_FragData'\n");
}
}
/**
* Verify that a geometry shader executable meets all semantic requirements
*
* Also sets prog->Geom.VerticesIn, and info.clip_distance_array_sizeand
* info.cull_distance_array_size as a side effect.
*
* \param shader Geometry shader executable to be verified
*/
static void
validate_geometry_shader_executable(struct gl_shader_program *prog,
nir_shader *shader,
const struct gl_constants *consts)
{
if (shader == NULL)
return;
unsigned num_vertices =
mesa_vertices_per_prim(shader->info.gs.input_primitive);
shader->info.gs.vertices_in = num_vertices;
analyze_clip_cull_usage(prog, shader, consts, &shader->info);
}
bool
gl_nir_link_glsl(struct gl_context *ctx, struct gl_shader_program *prog)
{
const struct gl_constants *consts = &ctx->Const;
const struct gl_extensions *exts = &ctx->Extensions;
gl_api api = ctx->API;
if (prog->NumShaders == 0)
return true;
MESA_TRACE_FUNC();
/* Link all shaders for a particular stage and validate the result.
*/
for (int stage = 0; stage < MESA_SHADER_STAGES; stage++) {
struct gl_linked_shader *sh = prog->_LinkedShaders[stage];
if (sh) {
nir_shader *shader = sh->Program->nir;
switch (stage) {
case MESA_SHADER_VERTEX:
validate_vertex_shader_executable(prog, shader, consts);
break;
case MESA_SHADER_TESS_CTRL:
/* nothing to be done */
break;
case MESA_SHADER_TESS_EVAL:
validate_tess_eval_shader_executable(prog, shader, consts);
break;
case MESA_SHADER_GEOMETRY:
validate_geometry_shader_executable(prog, shader, consts);
break;
case MESA_SHADER_FRAGMENT:
validate_fragment_shader_executable(prog, shader);
break;
}
if (!prog->data->LinkStatus) {
_mesa_delete_linked_shader(ctx, sh);
prog->_LinkedShaders[stage] = NULL;
prog->data->linked_stages ^= 1 << stage;
return false;
}
}
}
/* Here begins the inter-stage linking phase. Some initial validation is
* performed, then locations are assigned for uniforms, attributes, and
* varyings.

View file

@ -35,6 +35,7 @@ extern "C" {
#endif
struct gl_constants;
struct gl_context;
struct gl_extensions;
struct gl_linked_shader;
struct gl_shader_program;
@ -63,9 +64,7 @@ bool gl_nir_link_spirv(const struct gl_constants *consts,
struct gl_shader_program *prog,
const struct gl_nir_linker_options *options);
bool gl_nir_link_glsl(const struct gl_constants *consts,
const struct gl_extensions *exts,
gl_api api,
bool gl_nir_link_glsl(struct gl_context *ctx,
struct gl_shader_program *prog);
bool gl_nir_link_uniforms(const struct gl_constants *consts,

View file

@ -95,143 +95,6 @@
namespace {
struct find_variable {
const char *name;
bool found;
find_variable(const char *name) : name(name), found(false) {}
};
/**
* Visitor that determines whether or not a variable is ever written.
* Note: this is only considering if the variable is statically written
* (= regardless of the runtime flow of control)
*
* Use \ref find_assignments for convenience.
*/
class find_assignment_visitor : public ir_hierarchical_visitor {
public:
find_assignment_visitor(unsigned num_vars,
find_variable * const *vars)
: num_variables(num_vars), num_found(0), variables(vars)
{
}
virtual ir_visitor_status visit_enter(ir_assignment *ir)
{
ir_variable *const var = ir->lhs->variable_referenced();
return check_variable_name(var->name);
}
virtual ir_visitor_status visit_enter(ir_call *ir)
{
foreach_two_lists(formal_node, &ir->callee->parameters,
actual_node, &ir->actual_parameters) {
ir_rvalue *param_rval = (ir_rvalue *) actual_node;
ir_variable *sig_param = (ir_variable *) formal_node;
if (sig_param->data.mode == ir_var_function_out ||
sig_param->data.mode == ir_var_function_inout) {
ir_variable *var = param_rval->variable_referenced();
if (var && check_variable_name(var->name) == visit_stop)
return visit_stop;
}
}
if (ir->return_deref != NULL) {
ir_variable *const var = ir->return_deref->variable_referenced();
if (check_variable_name(var->name) == visit_stop)
return visit_stop;
}
return visit_continue_with_parent;
}
private:
ir_visitor_status check_variable_name(const char *name)
{
for (unsigned i = 0; i < num_variables; ++i) {
if (strcmp(variables[i]->name, name) == 0) {
if (!variables[i]->found) {
variables[i]->found = true;
assert(num_found < num_variables);
if (++num_found == num_variables)
return visit_stop;
}
break;
}
}
return visit_continue_with_parent;
}
private:
unsigned num_variables; /**< Number of variables to find */
unsigned num_found; /**< Number of variables already found */
find_variable * const *variables; /**< Variables to find */
};
/**
* Determine whether or not any of NULL-terminated list of variables is ever
* written to.
*/
static void
find_assignments(exec_list *ir, find_variable * const *vars)
{
unsigned num_variables = 0;
for (find_variable * const *v = vars; *v; ++v)
num_variables++;
find_assignment_visitor visitor(num_variables, vars);
visitor.run(ir);
}
/**
* Determine whether or not the given variable is ever written to.
*/
static void
find_assignments(exec_list *ir, find_variable *var)
{
find_assignment_visitor visitor(1, &var);
visitor.run(ir);
}
/**
* Visitor that determines whether or not a variable is ever read.
*/
class find_deref_visitor : public ir_hierarchical_visitor {
public:
find_deref_visitor(const char *name)
: name(name), found(false)
{
/* empty */
}
virtual ir_visitor_status visit(ir_dereference_variable *ir)
{
if (strcmp(this->name, ir->var->name) == 0) {
this->found = true;
return visit_stop;
}
return visit_continue;
}
bool variable_found() const
{
return this->found;
}
private:
const char *name; /**< Find writes to a variable with this name. */
bool found; /**< Was a write to the variable found? */
};
/**
* A visitor helper that provides methods for updating the types of
* ir_dereferences. Classes that update variable types (say, updating
@ -385,231 +248,6 @@ linker_warning(gl_shader_program *prog, const char *fmt, ...)
}
/**
* Set clip_distance_array_size based and cull_distance_array_size on the given
* shader.
*
* Also check for errors based on incorrect usage of gl_ClipVertex and
* gl_ClipDistance and gl_CullDistance.
* Additionally test whether the arrays gl_ClipDistance and gl_CullDistance
* exceed the maximum size defined by gl_MaxCombinedClipAndCullDistances.
*
* Return false if an error was reported.
*/
static void
analyze_clip_cull_usage(struct gl_shader_program *prog,
struct gl_linked_shader *shader,
const struct gl_constants *consts,
struct shader_info *info)
{
if (consts->DoDCEBeforeClipCullAnalysis) {
/* Remove dead functions to avoid raising an error (eg: dead function
* writes to gl_ClipVertex, and main() writes to gl_ClipDistance).
*/
do_dead_functions(shader->ir);
}
info->clip_distance_array_size = 0;
info->cull_distance_array_size = 0;
if (prog->GLSL_Version >= (prog->IsES ? 300 : 130)) {
/* From section 7.1 (Vertex Shader Special Variables) of the
* GLSL 1.30 spec:
*
* "It is an error for a shader to statically write both
* gl_ClipVertex and gl_ClipDistance."
*
* This does not apply to GLSL ES shaders, since GLSL ES defines neither
* gl_ClipVertex nor gl_ClipDistance. However with
* GL_EXT_clip_cull_distance, this functionality is exposed in ES 3.0.
*/
find_variable gl_ClipDistance("gl_ClipDistance");
find_variable gl_CullDistance("gl_CullDistance");
find_variable gl_ClipVertex("gl_ClipVertex");
find_variable * const variables[] = {
&gl_ClipDistance,
&gl_CullDistance,
!prog->IsES ? &gl_ClipVertex : NULL,
NULL
};
find_assignments(shader->ir, variables);
/* From the ARB_cull_distance spec:
*
* It is a compile-time or link-time error for the set of shaders forming
* a program to statically read or write both gl_ClipVertex and either
* gl_ClipDistance or gl_CullDistance.
*
* This does not apply to GLSL ES shaders, since GLSL ES doesn't define
* gl_ClipVertex.
*/
if (!prog->IsES) {
if (gl_ClipVertex.found && gl_ClipDistance.found) {
linker_error(prog, "%s shader writes to both `gl_ClipVertex' "
"and `gl_ClipDistance'\n",
_mesa_shader_stage_to_string(shader->Stage));
return;
}
if (gl_ClipVertex.found && gl_CullDistance.found) {
linker_error(prog, "%s shader writes to both `gl_ClipVertex' "
"and `gl_CullDistance'\n",
_mesa_shader_stage_to_string(shader->Stage));
return;
}
}
if (gl_ClipDistance.found) {
ir_variable *clip_distance_var =
shader->symbols->get_variable("gl_ClipDistance");
assert(clip_distance_var);
info->clip_distance_array_size = clip_distance_var->type->length;
}
if (gl_CullDistance.found) {
ir_variable *cull_distance_var =
shader->symbols->get_variable("gl_CullDistance");
assert(cull_distance_var);
info->cull_distance_array_size = cull_distance_var->type->length;
}
/* From the ARB_cull_distance spec:
*
* It is a compile-time or link-time error for the set of shaders forming
* a program to have the sum of the sizes of the gl_ClipDistance and
* gl_CullDistance arrays to be larger than
* gl_MaxCombinedClipAndCullDistances.
*/
if ((uint32_t)(info->clip_distance_array_size + info->cull_distance_array_size) >
consts->MaxClipPlanes) {
linker_error(prog, "%s shader: the combined size of "
"'gl_ClipDistance' and 'gl_CullDistance' size cannot "
"be larger than "
"gl_MaxCombinedClipAndCullDistances (%u)",
_mesa_shader_stage_to_string(shader->Stage),
consts->MaxClipPlanes);
}
}
}
/**
* Verify that a vertex shader executable meets all semantic requirements.
*
* Also sets info.clip_distance_array_size and
* info.cull_distance_array_size as a side effect.
*
* \param shader Vertex shader executable to be verified
*/
static void
validate_vertex_shader_executable(struct gl_shader_program *prog,
struct gl_linked_shader *shader,
const struct gl_constants *consts)
{
if (shader == NULL)
return;
/* From the GLSL 1.10 spec, page 48:
*
* "The variable gl_Position is available only in the vertex
* language and is intended for writing the homogeneous vertex
* position. All executions of a well-formed vertex shader
* executable must write a value into this variable. [...] The
* variable gl_Position is available only in the vertex
* language and is intended for writing the homogeneous vertex
* position. All executions of a well-formed vertex shader
* executable must write a value into this variable."
*
* while in GLSL 1.40 this text is changed to:
*
* "The variable gl_Position is available only in the vertex
* language and is intended for writing the homogeneous vertex
* position. It can be written at any time during shader
* execution. It may also be read back by a vertex shader
* after being written. This value will be used by primitive
* assembly, clipping, culling, and other fixed functionality
* operations, if present, that operate on primitives after
* vertex processing has occurred. Its value is undefined if
* the vertex shader executable does not write gl_Position."
*
* All GLSL ES Versions are similar to GLSL 1.40--failing to write to
* gl_Position is not an error.
*/
if (prog->GLSL_Version < (prog->IsES ? 300 : 140)) {
find_variable gl_Position("gl_Position");
find_assignments(shader->ir, &gl_Position);
if (!gl_Position.found) {
if (prog->IsES) {
linker_warning(prog,
"vertex shader does not write to `gl_Position'. "
"Its value is undefined. \n");
} else {
linker_error(prog,
"vertex shader does not write to `gl_Position'. \n");
}
return;
}
}
analyze_clip_cull_usage(prog, shader, consts, &shader->Program->info);
}
static void
validate_tess_eval_shader_executable(struct gl_shader_program *prog,
struct gl_linked_shader *shader,
const struct gl_constants *consts)
{
if (shader == NULL)
return;
analyze_clip_cull_usage(prog, shader, consts, &shader->Program->info);
}
/**
* Verify that a fragment shader executable meets all semantic requirements
*
* \param shader Fragment shader executable to be verified
*/
static void
validate_fragment_shader_executable(struct gl_shader_program *prog,
struct gl_linked_shader *shader)
{
if (shader == NULL)
return;
find_variable gl_FragColor("gl_FragColor");
find_variable gl_FragData("gl_FragData");
find_variable * const variables[] = { &gl_FragColor, &gl_FragData, NULL };
find_assignments(shader->ir, variables);
if (gl_FragColor.found && gl_FragData.found) {
linker_error(prog, "fragment shader writes to both "
"`gl_FragColor' and `gl_FragData'\n");
}
}
/**
* Verify that a geometry shader executable meets all semantic requirements
*
* Also sets prog->Geom.VerticesIn, and info.clip_distance_array_sizeand
* info.cull_distance_array_size as a side effect.
*
* \param shader Geometry shader executable to be verified
*/
static void
validate_geometry_shader_executable(struct gl_shader_program *prog,
struct gl_linked_shader *shader,
const struct gl_constants *consts)
{
if (shader == NULL)
return;
unsigned num_vertices =
mesa_vertices_per_prim(shader->Program->info.gs.input_primitive);
shader->Program->info.gs.vertices_in = num_vertices;
analyze_clip_cull_usage(prog, shader, consts, &shader->Program->info);
}
bool
validate_intrastage_arrays(struct gl_shader_program *prog,
ir_variable *const var,
@ -2332,29 +1970,6 @@ link_shaders(struct gl_context *ctx, struct gl_shader_program *prog)
goto done;
}
switch (stage) {
case MESA_SHADER_VERTEX:
validate_vertex_shader_executable(prog, sh, consts);
break;
case MESA_SHADER_TESS_CTRL:
/* nothing to be done */
break;
case MESA_SHADER_TESS_EVAL:
validate_tess_eval_shader_executable(prog, sh, consts);
break;
case MESA_SHADER_GEOMETRY:
validate_geometry_shader_executable(prog, sh, consts);
break;
case MESA_SHADER_FRAGMENT:
validate_fragment_shader_executable(prog, sh);
break;
}
if (!prog->data->LinkStatus) {
if (sh)
_mesa_delete_linked_shader(ctx, sh);
goto done;
}
prog->_LinkedShaders[stage] = sh;
prog->data->linked_stages |= 1 << stage;
}

View file

@ -568,8 +568,7 @@ st_link_glsl_to_nir(struct gl_context *ctx,
&opts))
return GL_FALSE;
} else {
if (!gl_nir_link_glsl(&ctx->Const, &ctx->Extensions, ctx->API,
shader_program))
if (!gl_nir_link_glsl(ctx, shader_program))
return GL_FALSE;
}