spirv: Rework structured control flow handling
The new code splits the work into a few passes instead of trying to do everything with a single pass. This helps to apply the new clarified rules for structured control flow in the SPIR-V specification, in particular the "exit construct" rules. First find an appropriate ordering for the blocks, based on the approach taken by Tint (WebGPU compiler). Then, with those blocks in order, identify the SPIR-V constructs start and end positions. Finally, walk the blocks again to emit NIR for each of them, "opening" and "closing" the necessary NIR constructs as we reach the start and end positions of the SPIR-V constructs. There are a couple of interesting choices when mapping the constructs to NIR: - NIR doesn't have something like a switch, so like the previous code, we lower the switch construct to a series of conditionals for each case. - And, unlike the previous code, when there's a need to perform a break from a construct that NIR doesn't directly support (e.g. inside a case construct, conditionally breaking early from the switch), we now use a combination of a NIR loop and an NIR if. Extra code is added to ensure that loop_break and loop_continues are propagated to the right loop. This should fix various issues with valid SPIR-V that previously resulted in "Invalid back or cross-edge in the CFG" errors. Thanks to Alan Baker and David Neto for their explanations of ordering the blocks, in the Tint code and in presentations to the SPIR-V WG. Thanks to Jack Clark for providing a lot of valuable tests used to validate this MR. Closes: #5973, #6369 Reviewed-by: Faith Ekstrand <faith.ekstrand@collabora.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/17922>
This commit is contained in:
@@ -310,6 +310,7 @@ files_libnir = files(
|
||||
'../spirv/vtn_alu.c',
|
||||
'../spirv/vtn_amd.c',
|
||||
'../spirv/vtn_cfg.c',
|
||||
'../spirv/vtn_structured_cfg.c',
|
||||
'../spirv/vtn_glsl450.c',
|
||||
'../spirv/vtn_opencl.c',
|
||||
'../spirv/vtn_private.h',
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
uint32_t mesa_spirv_debug = 0;
|
||||
|
||||
static const struct debug_named_value mesa_spirv_debug_control[] = {
|
||||
{ "structured", MESA_SPIRV_DEBUG_STRUCTURED,
|
||||
"Print information of the SPIR-V structured control flow parsing" },
|
||||
DEBUG_NAMED_VALUE_END,
|
||||
};
|
||||
|
||||
@@ -6694,8 +6696,7 @@ spirv_to_nir(const uint32_t *words, size_t word_count,
|
||||
bool progress;
|
||||
do {
|
||||
progress = false;
|
||||
vtn_foreach_cf_node(node, &b->functions) {
|
||||
struct vtn_function *func = vtn_cf_node_as_function(node);
|
||||
vtn_foreach_function(func, &b->functions) {
|
||||
if ((options->create_library || func->referenced) && !func->emitted) {
|
||||
b->const_table = _mesa_pointer_hash_table_create(b);
|
||||
|
||||
@@ -6719,6 +6720,9 @@ spirv_to_nir(const uint32_t *words, size_t word_count,
|
||||
|
||||
/* structurize the CFG */
|
||||
nir_lower_goto_ifs(b->shader);
|
||||
|
||||
nir_validate_shader(b->shader, "after spirv cfg");
|
||||
|
||||
nir_lower_continue_constructs(b->shader);
|
||||
|
||||
/* A SPIR-V module can have multiple shaders stages and also multiple
|
||||
|
||||
@@ -137,7 +137,7 @@ OpFunctionEnd
|
||||
ASSERT_TRUE(shader);
|
||||
}
|
||||
|
||||
TEST_F(ControlFlow, DISABLED_EarlyMerge)
|
||||
TEST_F(ControlFlow, EarlyMerge)
|
||||
{
|
||||
// From https://gitlab.khronos.org/spirv/SPIR-V/-/issues/640.
|
||||
|
||||
|
||||
+18
-826
@@ -26,12 +26,6 @@
|
||||
#include "nir/nir_vla.h"
|
||||
#include "util/u_debug.h"
|
||||
|
||||
static struct vtn_block *
|
||||
vtn_block(struct vtn_builder *b, uint32_t value_id)
|
||||
{
|
||||
return vtn_value(b, value_id, vtn_value_type_block)->block;
|
||||
}
|
||||
|
||||
static unsigned
|
||||
glsl_type_count_function_params(const struct glsl_type *type)
|
||||
{
|
||||
@@ -171,7 +165,7 @@ function_decoration_cb(struct vtn_builder *b, struct vtn_value *val, int member,
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
bool
|
||||
vtn_cfg_handle_prepass_instruction(struct vtn_builder *b, SpvOp opcode,
|
||||
const uint32_t *w, unsigned count)
|
||||
{
|
||||
@@ -180,11 +174,10 @@ vtn_cfg_handle_prepass_instruction(struct vtn_builder *b, SpvOp opcode,
|
||||
vtn_assert(b->func == NULL);
|
||||
b->func = rzalloc(b, struct vtn_function);
|
||||
|
||||
b->func->node.type = vtn_cf_node_type_function;
|
||||
b->func->node.parent = NULL;
|
||||
list_inithead(&b->func->body);
|
||||
b->func->linkage = SpvLinkageTypeMax;
|
||||
b->func->control = w[3];
|
||||
list_inithead(&b->func->constructs);
|
||||
|
||||
UNUSED const struct glsl_type *result_type = vtn_get_type(b, w[1])->type;
|
||||
struct vtn_value *val = vtn_push_value(b, w[2], vtn_value_type_function);
|
||||
@@ -276,17 +269,18 @@ vtn_cfg_handle_prepass_instruction(struct vtn_builder *b, SpvOp opcode,
|
||||
case SpvOpLabel: {
|
||||
vtn_assert(b->block == NULL);
|
||||
b->block = rzalloc(b, struct vtn_block);
|
||||
b->block->node.type = vtn_cf_node_type_block;
|
||||
b->block->label = w;
|
||||
vtn_push_value(b, w[1], vtn_value_type_block)->block = b->block;
|
||||
|
||||
b->func->block_count++;
|
||||
|
||||
if (b->func->start_block == NULL) {
|
||||
/* This is the first block encountered for this function. In this
|
||||
* case, we set the start block and add it to the list of
|
||||
* implemented functions that we'll walk later.
|
||||
*/
|
||||
b->func->start_block = b->block;
|
||||
list_addtail(&b->func->node.link, &b->functions);
|
||||
list_addtail(&b->func->link, &b->functions);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -327,217 +321,9 @@ vtn_cfg_handle_prepass_instruction(struct vtn_builder *b, SpvOp opcode,
|
||||
return true;
|
||||
}
|
||||
|
||||
/* This function performs a depth-first search of the cases and puts them
|
||||
* in fall-through order.
|
||||
*/
|
||||
static void
|
||||
vtn_order_case(struct vtn_switch *swtch, struct vtn_case *cse)
|
||||
{
|
||||
if (cse->visited)
|
||||
return;
|
||||
|
||||
cse->visited = true;
|
||||
|
||||
list_del(&cse->node.link);
|
||||
|
||||
if (cse->fallthrough) {
|
||||
vtn_order_case(swtch, cse->fallthrough);
|
||||
|
||||
/* If we have a fall-through, place this case right before the case it
|
||||
* falls through to. This ensures that fallthroughs come one after
|
||||
* the other. These two can never get separated because that would
|
||||
* imply something else falling through to the same case. Also, this
|
||||
* can't break ordering because the DFS ensures that this case is
|
||||
* visited before anything that falls through to it.
|
||||
*/
|
||||
list_addtail(&cse->node.link, &cse->fallthrough->node.link);
|
||||
} else {
|
||||
list_add(&cse->node.link, &swtch->cases);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
vtn_switch_order_cases(struct vtn_switch *swtch)
|
||||
{
|
||||
struct list_head cases;
|
||||
list_replace(&swtch->cases, &cases);
|
||||
list_inithead(&swtch->cases);
|
||||
while (!list_is_empty(&cases)) {
|
||||
struct vtn_case *cse =
|
||||
list_first_entry(&cases, struct vtn_case, node.link);
|
||||
vtn_order_case(swtch, cse);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
vtn_block_set_merge_cf_node(struct vtn_builder *b, struct vtn_block *block,
|
||||
struct vtn_cf_node *cf_node)
|
||||
{
|
||||
vtn_fail_if(block->merge_cf_node != NULL,
|
||||
"The merge block declared by a header block cannot be a "
|
||||
"merge block declared by any other header block.");
|
||||
|
||||
block->merge_cf_node = cf_node;
|
||||
}
|
||||
|
||||
#define VTN_DECL_CF_NODE_FIND(_type) \
|
||||
static inline struct vtn_##_type * \
|
||||
vtn_cf_node_find_##_type(struct vtn_cf_node *node) \
|
||||
{ \
|
||||
while (node && node->type != vtn_cf_node_type_##_type) \
|
||||
node = node->parent; \
|
||||
return (struct vtn_##_type *)node; \
|
||||
}
|
||||
|
||||
UNUSED VTN_DECL_CF_NODE_FIND(if)
|
||||
VTN_DECL_CF_NODE_FIND(loop)
|
||||
VTN_DECL_CF_NODE_FIND(case)
|
||||
VTN_DECL_CF_NODE_FIND(switch)
|
||||
VTN_DECL_CF_NODE_FIND(function)
|
||||
|
||||
static enum vtn_branch_type
|
||||
vtn_handle_branch(struct vtn_builder *b,
|
||||
struct vtn_cf_node *cf_parent,
|
||||
struct vtn_block *target_block)
|
||||
{
|
||||
struct vtn_loop *loop = vtn_cf_node_find_loop(cf_parent);
|
||||
|
||||
/* Detect a loop back-edge first. That way none of the code below
|
||||
* accidentally operates on a loop back-edge.
|
||||
*/
|
||||
if (loop && target_block == loop->header_block)
|
||||
return vtn_branch_type_loop_back_edge;
|
||||
|
||||
/* Try to detect fall-through */
|
||||
if (target_block->switch_case) {
|
||||
/* When it comes to handling switch cases, we can break calls to
|
||||
* vtn_handle_branch into two cases: calls from within a case construct
|
||||
* and calls for the jump to each case construct. In the second case,
|
||||
* cf_parent is the vtn_switch itself and vtn_cf_node_find_case() will
|
||||
* return the outer switch case in which this switch is contained. It's
|
||||
* fine if the target block is a switch case from an outer switch as
|
||||
* long as it is also the switch break for this switch.
|
||||
*/
|
||||
struct vtn_case *switch_case = vtn_cf_node_find_case(cf_parent);
|
||||
|
||||
/* This doesn't get called for the OpSwitch */
|
||||
vtn_fail_if(switch_case == NULL,
|
||||
"A switch case can only be entered through an OpSwitch or "
|
||||
"falling through from another switch case.");
|
||||
|
||||
/* Because block->switch_case is only set on the entry block for a given
|
||||
* switch case, we only ever get here if we're jumping to the start of a
|
||||
* switch case. It's possible, however, that a switch case could jump
|
||||
* to itself via a back-edge. That *should* get caught by the loop
|
||||
* handling case above but if we have a back edge without a loop merge,
|
||||
* we could en up here.
|
||||
*/
|
||||
vtn_fail_if(target_block->switch_case == switch_case,
|
||||
"A switch cannot fall-through to itself. Likely, there is "
|
||||
"a back-edge which is not to a loop header.");
|
||||
|
||||
vtn_fail_if(target_block->switch_case->node.parent !=
|
||||
switch_case->node.parent,
|
||||
"A switch case fall-through must come from the same "
|
||||
"OpSwitch construct");
|
||||
|
||||
vtn_fail_if(switch_case->fallthrough != NULL &&
|
||||
switch_case->fallthrough != target_block->switch_case,
|
||||
"Each case construct can have at most one branch to "
|
||||
"another case construct");
|
||||
|
||||
switch_case->fallthrough = target_block->switch_case;
|
||||
|
||||
/* We don't immediately return vtn_branch_type_switch_fallthrough
|
||||
* because it may also be a loop or switch break for an inner loop or
|
||||
* switch and that takes precedence.
|
||||
*/
|
||||
}
|
||||
|
||||
if (loop && target_block == loop->cont_block)
|
||||
return vtn_branch_type_loop_continue;
|
||||
|
||||
/* We walk blocks as a breadth-first search on the control-flow construct
|
||||
* tree where, when we find a construct, we add the vtn_cf_node for that
|
||||
* construct and continue iterating at the merge target block (if any).
|
||||
* Therefore, we want merges whose with parent == cf_parent to be treated
|
||||
* as regular branches. We only want to consider merges if they break out
|
||||
* of the current CF construct.
|
||||
*/
|
||||
if (target_block->merge_cf_node != NULL &&
|
||||
target_block->merge_cf_node->parent != cf_parent) {
|
||||
switch (target_block->merge_cf_node->type) {
|
||||
case vtn_cf_node_type_if:
|
||||
for (struct vtn_cf_node *node = cf_parent;
|
||||
node != target_block->merge_cf_node; node = node->parent) {
|
||||
vtn_fail_if(node == NULL || node->type != vtn_cf_node_type_if,
|
||||
"Branching to the merge block of a selection "
|
||||
"construct can only be used to break out of a "
|
||||
"selection construct");
|
||||
|
||||
struct vtn_if *if_stmt = vtn_cf_node_as_if(node);
|
||||
|
||||
/* This should be guaranteed by our iteration */
|
||||
assert(if_stmt->merge_block != target_block);
|
||||
|
||||
vtn_fail_if(if_stmt->merge_block != NULL,
|
||||
"Branching to the merge block of a selection "
|
||||
"construct can only be used to break out of the "
|
||||
"inner most nested selection level");
|
||||
}
|
||||
return vtn_branch_type_if_merge;
|
||||
|
||||
case vtn_cf_node_type_loop:
|
||||
vtn_fail_if(target_block->merge_cf_node != &loop->node,
|
||||
"Loop breaks can only break out of the inner most "
|
||||
"nested loop level");
|
||||
return vtn_branch_type_loop_break;
|
||||
|
||||
case vtn_cf_node_type_switch: {
|
||||
struct vtn_switch *swtch = vtn_cf_node_find_switch(cf_parent);
|
||||
vtn_fail_if(target_block->merge_cf_node != &swtch->node,
|
||||
"Switch breaks can only break out of the inner most "
|
||||
"nested switch level");
|
||||
return vtn_branch_type_switch_break;
|
||||
}
|
||||
|
||||
default:
|
||||
unreachable("Invalid CF node type for a merge");
|
||||
}
|
||||
}
|
||||
|
||||
if (target_block->switch_case)
|
||||
return vtn_branch_type_switch_fallthrough;
|
||||
|
||||
return vtn_branch_type_none;
|
||||
}
|
||||
|
||||
struct vtn_cfg_work_item {
|
||||
struct list_head link;
|
||||
|
||||
struct vtn_cf_node *cf_parent;
|
||||
struct list_head *cf_list;
|
||||
struct vtn_block *start_block;
|
||||
};
|
||||
|
||||
static void
|
||||
vtn_add_cfg_work_item(struct vtn_builder *b,
|
||||
struct list_head *work_list,
|
||||
struct vtn_cf_node *cf_parent,
|
||||
struct list_head *cf_list,
|
||||
struct vtn_block *start_block)
|
||||
{
|
||||
struct vtn_cfg_work_item *work = ralloc(b, struct vtn_cfg_work_item);
|
||||
work->cf_parent = cf_parent;
|
||||
work->cf_list = cf_list;
|
||||
work->start_block = start_block;
|
||||
list_addtail(&work->link, work_list);
|
||||
}
|
||||
|
||||
/* returns the default block */
|
||||
static void
|
||||
void
|
||||
vtn_parse_switch(struct vtn_builder *b,
|
||||
struct vtn_switch *swtch,
|
||||
const uint32_t *branch,
|
||||
struct list_head *case_list)
|
||||
{
|
||||
@@ -579,14 +365,11 @@ vtn_parse_switch(struct vtn_builder *b,
|
||||
cse = case_entry->data;
|
||||
} else {
|
||||
cse = rzalloc(b, struct vtn_case);
|
||||
|
||||
cse->node.type = vtn_cf_node_type_case;
|
||||
cse->node.parent = swtch ? &swtch->node : NULL;
|
||||
cse->block = case_block;
|
||||
list_inithead(&cse->body);
|
||||
cse->block->switch_case = cse;
|
||||
util_dynarray_init(&cse->values, b);
|
||||
|
||||
list_addtail(&cse->node.link, case_list);
|
||||
list_addtail(&cse->link, case_list);
|
||||
_mesa_hash_table_insert(block_to_case, case_block, cse);
|
||||
}
|
||||
|
||||
@@ -602,278 +385,6 @@ vtn_parse_switch(struct vtn_builder *b,
|
||||
_mesa_hash_table_destroy(block_to_case, NULL);
|
||||
}
|
||||
|
||||
/* Processes a block and returns the next block to process or NULL if we've
|
||||
* reached the end of the construct.
|
||||
*/
|
||||
static struct vtn_block *
|
||||
vtn_process_block(struct vtn_builder *b,
|
||||
struct list_head *work_list,
|
||||
struct vtn_cf_node *cf_parent,
|
||||
struct list_head *cf_list,
|
||||
struct vtn_block *block)
|
||||
{
|
||||
if (!list_is_empty(cf_list)) {
|
||||
/* vtn_process_block() acts like an iterator: it processes the given
|
||||
* block and then returns the next block to process. For a given
|
||||
* control-flow construct, vtn_build_cfg() calls vtn_process_block()
|
||||
* repeatedly until it finally returns NULL. Therefore, we know that
|
||||
* the only blocks on which vtn_process_block() can be called are either
|
||||
* the first block in a construct or a block that vtn_process_block()
|
||||
* returned for the current construct. If cf_list is empty then we know
|
||||
* that we're processing the first block in the construct and we have to
|
||||
* add it to the list.
|
||||
*
|
||||
* If cf_list is not empty, then it must be the block returned by the
|
||||
* previous call to vtn_process_block(). We know a priori that
|
||||
* vtn_process_block only returns either normal branches
|
||||
* (vtn_branch_type_none) or merge target blocks.
|
||||
*/
|
||||
switch (vtn_handle_branch(b, cf_parent, block)) {
|
||||
case vtn_branch_type_none:
|
||||
/* For normal branches, we want to process them and add them to the
|
||||
* current construct. Merge target blocks also look like normal
|
||||
* branches from the perspective of this construct. See also
|
||||
* vtn_handle_branch().
|
||||
*/
|
||||
break;
|
||||
|
||||
case vtn_branch_type_loop_continue:
|
||||
case vtn_branch_type_switch_fallthrough:
|
||||
/* The two cases where we can get early exits from a construct that
|
||||
* are not to that construct's merge target are loop continues and
|
||||
* switch fall-throughs. In these cases, we need to break out of the
|
||||
* current construct by returning NULL.
|
||||
*/
|
||||
return NULL;
|
||||
|
||||
default:
|
||||
/* The only way we can get here is if something was used as two kinds
|
||||
* of merges at the same time and that's illegal.
|
||||
*/
|
||||
vtn_fail("A block was used as a merge target from two or more "
|
||||
"structured control-flow constructs");
|
||||
}
|
||||
}
|
||||
|
||||
/* Once a block has been processed, it is placed into and the list link
|
||||
* will point to something non-null. If we see a node we've already
|
||||
* processed here, it either exists in multiple functions or it's an
|
||||
* invalid back-edge.
|
||||
*/
|
||||
if (block->node.parent != NULL) {
|
||||
vtn_fail_if(vtn_cf_node_find_function(&block->node) !=
|
||||
vtn_cf_node_find_function(cf_parent),
|
||||
"A block cannot exist in two functions at the "
|
||||
"same time");
|
||||
|
||||
vtn_fail("Invalid back or cross-edge in the CFG");
|
||||
}
|
||||
|
||||
if (block->merge && (*block->merge & SpvOpCodeMask) == SpvOpLoopMerge &&
|
||||
block->loop == NULL) {
|
||||
vtn_fail_if((*block->branch & SpvOpCodeMask) != SpvOpBranch &&
|
||||
(*block->branch & SpvOpCodeMask) != SpvOpBranchConditional,
|
||||
"An OpLoopMerge instruction must immediately precede "
|
||||
"either an OpBranch or OpBranchConditional instruction.");
|
||||
|
||||
struct vtn_loop *loop = rzalloc(b, struct vtn_loop);
|
||||
|
||||
loop->node.type = vtn_cf_node_type_loop;
|
||||
loop->node.parent = cf_parent;
|
||||
list_inithead(&loop->body);
|
||||
list_inithead(&loop->cont_body);
|
||||
loop->header_block = block;
|
||||
loop->break_block = vtn_block(b, block->merge[1]);
|
||||
loop->cont_block = vtn_block(b, block->merge[2]);
|
||||
loop->control = block->merge[3];
|
||||
|
||||
list_addtail(&loop->node.link, cf_list);
|
||||
block->loop = loop;
|
||||
|
||||
/* Note: The work item for the main loop body will start with the
|
||||
* current block as its start block. If we weren't careful, we would
|
||||
* get here again and end up in an infinite loop. This is why we set
|
||||
* block->loop above and check for it before creating one. This way,
|
||||
* we only create the loop once and the second iteration that tries to
|
||||
* handle this loop goes to the cases below and gets handled as a
|
||||
* regular block.
|
||||
*/
|
||||
vtn_add_cfg_work_item(b, work_list, &loop->node,
|
||||
&loop->body, loop->header_block);
|
||||
|
||||
/* For continue targets, SPIR-V guarantees the following:
|
||||
*
|
||||
* - the Continue Target must dominate the back-edge block
|
||||
* - the back-edge block must post dominate the Continue Target
|
||||
*
|
||||
* If the header block is the same as the continue target, this
|
||||
* condition is trivially satisfied and there is no real continue
|
||||
* section.
|
||||
*/
|
||||
if (loop->cont_block != loop->header_block) {
|
||||
vtn_add_cfg_work_item(b, work_list, &loop->node,
|
||||
&loop->cont_body, loop->cont_block);
|
||||
}
|
||||
|
||||
vtn_block_set_merge_cf_node(b, loop->break_block, &loop->node);
|
||||
|
||||
return loop->break_block;
|
||||
}
|
||||
|
||||
/* Add the block to the CF list */
|
||||
block->node.parent = cf_parent;
|
||||
list_addtail(&block->node.link, cf_list);
|
||||
|
||||
switch (*block->branch & SpvOpCodeMask) {
|
||||
case SpvOpBranch: {
|
||||
struct vtn_block *branch_block = vtn_block(b, block->branch[1]);
|
||||
|
||||
block->branch_type = vtn_handle_branch(b, cf_parent, branch_block);
|
||||
|
||||
if (block->branch_type == vtn_branch_type_none)
|
||||
return branch_block;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
case SpvOpReturn:
|
||||
case SpvOpReturnValue:
|
||||
block->branch_type = vtn_branch_type_return;
|
||||
return NULL;
|
||||
|
||||
case SpvOpKill:
|
||||
block->branch_type = vtn_branch_type_discard;
|
||||
return NULL;
|
||||
|
||||
case SpvOpTerminateInvocation:
|
||||
block->branch_type = vtn_branch_type_terminate_invocation;
|
||||
return NULL;
|
||||
|
||||
case SpvOpIgnoreIntersectionKHR:
|
||||
block->branch_type = vtn_branch_type_ignore_intersection;
|
||||
return NULL;
|
||||
|
||||
case SpvOpTerminateRayKHR:
|
||||
block->branch_type = vtn_branch_type_terminate_ray;
|
||||
return NULL;
|
||||
|
||||
case SpvOpEmitMeshTasksEXT:
|
||||
block->branch_type = vtn_branch_type_emit_mesh_tasks;
|
||||
return NULL;
|
||||
|
||||
case SpvOpBranchConditional: {
|
||||
struct vtn_value *cond_val = vtn_untyped_value(b, block->branch[1]);
|
||||
vtn_fail_if(!cond_val->type ||
|
||||
cond_val->type->base_type != vtn_base_type_scalar ||
|
||||
cond_val->type->type != glsl_bool_type(),
|
||||
"Condition must be a Boolean type scalar");
|
||||
|
||||
struct vtn_if *if_stmt = rzalloc(b, struct vtn_if);
|
||||
|
||||
if_stmt->node.type = vtn_cf_node_type_if;
|
||||
if_stmt->node.parent = cf_parent;
|
||||
if_stmt->header_block = block;
|
||||
list_inithead(&if_stmt->then_body);
|
||||
list_inithead(&if_stmt->else_body);
|
||||
|
||||
list_addtail(&if_stmt->node.link, cf_list);
|
||||
|
||||
if (block->merge &&
|
||||
(*block->merge & SpvOpCodeMask) == SpvOpSelectionMerge) {
|
||||
/* We may not always have a merge block and that merge doesn't
|
||||
* technically have to be an OpSelectionMerge. We could have a block
|
||||
* with an OpLoopMerge which ends in an OpBranchConditional.
|
||||
*/
|
||||
if_stmt->merge_block = vtn_block(b, block->merge[1]);
|
||||
vtn_block_set_merge_cf_node(b, if_stmt->merge_block, &if_stmt->node);
|
||||
|
||||
if_stmt->control = block->merge[2];
|
||||
}
|
||||
|
||||
struct vtn_block *then_block = vtn_block(b, block->branch[2]);
|
||||
if_stmt->then_type = vtn_handle_branch(b, &if_stmt->node, then_block);
|
||||
if (if_stmt->then_type == vtn_branch_type_none) {
|
||||
vtn_add_cfg_work_item(b, work_list, &if_stmt->node,
|
||||
&if_stmt->then_body, then_block);
|
||||
}
|
||||
|
||||
struct vtn_block *else_block = vtn_block(b, block->branch[3]);
|
||||
if (then_block != else_block) {
|
||||
if_stmt->else_type = vtn_handle_branch(b, &if_stmt->node, else_block);
|
||||
if (if_stmt->else_type == vtn_branch_type_none) {
|
||||
vtn_add_cfg_work_item(b, work_list, &if_stmt->node,
|
||||
&if_stmt->else_body, else_block);
|
||||
}
|
||||
}
|
||||
|
||||
return if_stmt->merge_block;
|
||||
}
|
||||
|
||||
case SpvOpSwitch: {
|
||||
struct vtn_switch *swtch = rzalloc(b, struct vtn_switch);
|
||||
|
||||
swtch->node.type = vtn_cf_node_type_switch;
|
||||
swtch->node.parent = cf_parent;
|
||||
swtch->selector = block->branch[1];
|
||||
list_inithead(&swtch->cases);
|
||||
|
||||
list_addtail(&swtch->node.link, cf_list);
|
||||
|
||||
/* We may not always have a merge block */
|
||||
if (block->merge) {
|
||||
vtn_fail_if((*block->merge & SpvOpCodeMask) != SpvOpSelectionMerge,
|
||||
"An OpLoopMerge instruction must immediately precede "
|
||||
"either an OpBranch or OpBranchConditional "
|
||||
"instruction.");
|
||||
swtch->break_block = vtn_block(b, block->merge[1]);
|
||||
vtn_block_set_merge_cf_node(b, swtch->break_block, &swtch->node);
|
||||
}
|
||||
|
||||
/* First, we go through and record all of the cases. */
|
||||
vtn_parse_switch(b, swtch, block->branch, &swtch->cases);
|
||||
|
||||
/* Gather the branch types for the switch */
|
||||
vtn_foreach_cf_node(case_node, &swtch->cases) {
|
||||
struct vtn_case *cse = vtn_cf_node_as_case(case_node);
|
||||
|
||||
cse->type = vtn_handle_branch(b, &swtch->node, cse->block);
|
||||
switch (cse->type) {
|
||||
case vtn_branch_type_none:
|
||||
/* This is a "real" cases which has stuff in it */
|
||||
vtn_fail_if(cse->block->switch_case != NULL,
|
||||
"OpSwitch has a case which is also in another "
|
||||
"OpSwitch construct");
|
||||
cse->block->switch_case = cse;
|
||||
vtn_add_cfg_work_item(b, work_list, &cse->node,
|
||||
&cse->body, cse->block);
|
||||
break;
|
||||
|
||||
case vtn_branch_type_switch_break:
|
||||
case vtn_branch_type_loop_break:
|
||||
case vtn_branch_type_loop_continue:
|
||||
/* Switch breaks as well as loop breaks and continues can be
|
||||
* used to break out of a switch construct or as direct targets
|
||||
* of the OpSwitch.
|
||||
*/
|
||||
break;
|
||||
|
||||
default:
|
||||
vtn_fail("Target of OpSwitch is not a valid structured exit "
|
||||
"from the switch construct.");
|
||||
}
|
||||
}
|
||||
|
||||
return swtch->break_block;
|
||||
}
|
||||
|
||||
case SpvOpUnreachable:
|
||||
return NULL;
|
||||
|
||||
default:
|
||||
vtn_fail("Block did not end with a valid branch instruction");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
vtn_build_cfg(struct vtn_builder *b, const uint32_t *words, const uint32_t *end)
|
||||
{
|
||||
@@ -883,34 +394,10 @@ vtn_build_cfg(struct vtn_builder *b, const uint32_t *words, const uint32_t *end)
|
||||
if (b->shader->info.stage == MESA_SHADER_KERNEL)
|
||||
return;
|
||||
|
||||
vtn_foreach_cf_node(func_node, &b->functions) {
|
||||
struct vtn_function *func = vtn_cf_node_as_function(func_node);
|
||||
|
||||
/* We build the CFG for each function by doing a breadth-first search on
|
||||
* the control-flow graph. We keep track of our state using a worklist.
|
||||
* Doing a BFS ensures that we visit each structured control-flow
|
||||
* construct and its merge node before we visit the stuff inside the
|
||||
* construct.
|
||||
*/
|
||||
struct list_head work_list;
|
||||
list_inithead(&work_list);
|
||||
vtn_add_cfg_work_item(b, &work_list, &func->node, &func->body,
|
||||
func->start_block);
|
||||
|
||||
while (!list_is_empty(&work_list)) {
|
||||
struct vtn_cfg_work_item *work =
|
||||
list_first_entry(&work_list, struct vtn_cfg_work_item, link);
|
||||
list_del(&work->link);
|
||||
|
||||
for (struct vtn_block *block = work->start_block; block; ) {
|
||||
block = vtn_process_block(b, &work_list, work->cf_parent,
|
||||
work->cf_list, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
vtn_build_structured_cfg(b, words, end);
|
||||
}
|
||||
|
||||
static bool
|
||||
bool
|
||||
vtn_handle_phis_first_pass(struct vtn_builder *b, SpvOp opcode,
|
||||
const uint32_t *w, unsigned count)
|
||||
{
|
||||
@@ -984,7 +471,7 @@ vtn_handle_phi_second_pass(struct vtn_builder *b, SpvOp opcode,
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
void
|
||||
vtn_emit_ret_store(struct vtn_builder *b, const struct vtn_block *block)
|
||||
{
|
||||
if ((*block->branch & SpvOpCodeMask) != SpvOpReturnValue)
|
||||
@@ -1001,299 +488,6 @@ vtn_emit_ret_store(struct vtn_builder *b, const struct vtn_block *block)
|
||||
vtn_local_store(b, src, ret_deref, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
vtn_emit_branch(struct vtn_builder *b, enum vtn_branch_type branch_type,
|
||||
const struct vtn_block *block,
|
||||
nir_variable *switch_fall_var, bool *has_switch_break)
|
||||
{
|
||||
switch (branch_type) {
|
||||
case vtn_branch_type_if_merge:
|
||||
break; /* Nothing to do */
|
||||
case vtn_branch_type_switch_break:
|
||||
nir_store_var(&b->nb, switch_fall_var, nir_imm_false(&b->nb), 1);
|
||||
*has_switch_break = true;
|
||||
break;
|
||||
case vtn_branch_type_switch_fallthrough:
|
||||
break; /* Nothing to do */
|
||||
case vtn_branch_type_loop_break:
|
||||
nir_jump(&b->nb, nir_jump_break);
|
||||
break;
|
||||
case vtn_branch_type_loop_continue:
|
||||
nir_jump(&b->nb, nir_jump_continue);
|
||||
break;
|
||||
case vtn_branch_type_loop_back_edge:
|
||||
break;
|
||||
case vtn_branch_type_return:
|
||||
vtn_assert(block);
|
||||
vtn_emit_ret_store(b, block);
|
||||
nir_jump(&b->nb, nir_jump_return);
|
||||
break;
|
||||
case vtn_branch_type_discard:
|
||||
if (b->convert_discard_to_demote)
|
||||
nir_demote(&b->nb);
|
||||
else
|
||||
nir_discard(&b->nb);
|
||||
break;
|
||||
case vtn_branch_type_terminate_invocation:
|
||||
nir_terminate(&b->nb);
|
||||
break;
|
||||
case vtn_branch_type_ignore_intersection:
|
||||
nir_ignore_ray_intersection(&b->nb);
|
||||
nir_jump(&b->nb, nir_jump_halt);
|
||||
break;
|
||||
case vtn_branch_type_terminate_ray:
|
||||
nir_terminate_ray(&b->nb);
|
||||
nir_jump(&b->nb, nir_jump_halt);
|
||||
break;
|
||||
case vtn_branch_type_emit_mesh_tasks: {
|
||||
assert(block);
|
||||
assert(block->branch);
|
||||
|
||||
const uint32_t *w = block->branch;
|
||||
vtn_assert((w[0] & SpvOpCodeMask) == SpvOpEmitMeshTasksEXT);
|
||||
|
||||
/* Launches mesh shader workgroups from the task shader.
|
||||
* Arguments are: vec(x, y, z), payload pointer
|
||||
*/
|
||||
nir_ssa_def *dimensions =
|
||||
nir_vec3(&b->nb, vtn_get_nir_ssa(b, w[1]),
|
||||
vtn_get_nir_ssa(b, w[2]),
|
||||
vtn_get_nir_ssa(b, w[3]));
|
||||
|
||||
/* The payload variable is optional.
|
||||
* We don't have a NULL deref in NIR, so just emit the explicit
|
||||
* intrinsic when there is no payload.
|
||||
*/
|
||||
const unsigned count = w[0] >> SpvWordCountShift;
|
||||
if (count == 4)
|
||||
nir_launch_mesh_workgroups(&b->nb, dimensions);
|
||||
else if (count == 5)
|
||||
nir_launch_mesh_workgroups_with_payload_deref(&b->nb, dimensions,
|
||||
vtn_get_nir_ssa(b, w[4]));
|
||||
else
|
||||
vtn_fail("Invalid EmitMeshTasksEXT.");
|
||||
|
||||
nir_jump(&b->nb, nir_jump_halt);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
vtn_fail("Invalid branch type");
|
||||
}
|
||||
}
|
||||
|
||||
static nir_ssa_def *
|
||||
vtn_switch_case_condition(struct vtn_builder *b, struct vtn_switch *swtch,
|
||||
nir_ssa_def *sel, struct vtn_case *cse)
|
||||
{
|
||||
if (cse->is_default) {
|
||||
nir_ssa_def *any = nir_imm_false(&b->nb);
|
||||
vtn_foreach_cf_node(other_node, &swtch->cases) {
|
||||
struct vtn_case *other = vtn_cf_node_as_case(other_node);
|
||||
if (other->is_default)
|
||||
continue;
|
||||
|
||||
any = nir_ior(&b->nb, any,
|
||||
vtn_switch_case_condition(b, swtch, sel, other));
|
||||
}
|
||||
return nir_inot(&b->nb, any);
|
||||
} else {
|
||||
nir_ssa_def *cond = nir_imm_false(&b->nb);
|
||||
util_dynarray_foreach(&cse->values, uint64_t, val)
|
||||
cond = nir_ior(&b->nb, cond, nir_ieq_imm(&b->nb, sel, *val));
|
||||
return cond;
|
||||
}
|
||||
}
|
||||
|
||||
static nir_loop_control
|
||||
vtn_loop_control(struct vtn_builder *b, struct vtn_loop *vtn_loop)
|
||||
{
|
||||
if (vtn_loop->control == SpvLoopControlMaskNone)
|
||||
return nir_loop_control_none;
|
||||
else if (vtn_loop->control & SpvLoopControlDontUnrollMask)
|
||||
return nir_loop_control_dont_unroll;
|
||||
else if (vtn_loop->control & SpvLoopControlUnrollMask)
|
||||
return nir_loop_control_unroll;
|
||||
else if (vtn_loop->control & SpvLoopControlDependencyInfiniteMask ||
|
||||
vtn_loop->control & SpvLoopControlDependencyLengthMask ||
|
||||
vtn_loop->control & SpvLoopControlMinIterationsMask ||
|
||||
vtn_loop->control & SpvLoopControlMaxIterationsMask ||
|
||||
vtn_loop->control & SpvLoopControlIterationMultipleMask ||
|
||||
vtn_loop->control & SpvLoopControlPeelCountMask ||
|
||||
vtn_loop->control & SpvLoopControlPartialCountMask) {
|
||||
/* We do not do anything special with these yet. */
|
||||
return nir_loop_control_none;
|
||||
} else {
|
||||
vtn_fail("Invalid loop control");
|
||||
}
|
||||
}
|
||||
|
||||
static nir_selection_control
|
||||
vtn_selection_control(struct vtn_builder *b, struct vtn_if *vtn_if)
|
||||
{
|
||||
if (vtn_if->control == SpvSelectionControlMaskNone)
|
||||
return nir_selection_control_none;
|
||||
else if (vtn_if->control & SpvSelectionControlDontFlattenMask)
|
||||
return nir_selection_control_dont_flatten;
|
||||
else if (vtn_if->control & SpvSelectionControlFlattenMask)
|
||||
return nir_selection_control_flatten;
|
||||
else
|
||||
vtn_fail("Invalid selection control");
|
||||
}
|
||||
|
||||
static void
|
||||
vtn_emit_cf_list_structured(struct vtn_builder *b, struct list_head *cf_list,
|
||||
nir_variable *switch_fall_var,
|
||||
bool *has_switch_break,
|
||||
vtn_instruction_handler handler)
|
||||
{
|
||||
vtn_foreach_cf_node(node, cf_list) {
|
||||
switch (node->type) {
|
||||
case vtn_cf_node_type_block: {
|
||||
struct vtn_block *block = vtn_cf_node_as_block(node);
|
||||
|
||||
const uint32_t *block_start = block->label;
|
||||
const uint32_t *block_end = block->merge ? block->merge :
|
||||
block->branch;
|
||||
|
||||
block_start = vtn_foreach_instruction(b, block_start, block_end,
|
||||
vtn_handle_phis_first_pass);
|
||||
|
||||
vtn_foreach_instruction(b, block_start, block_end, handler);
|
||||
|
||||
block->end_nop = nir_nop(&b->nb);
|
||||
|
||||
if (block->branch_type != vtn_branch_type_none) {
|
||||
vtn_emit_branch(b, block->branch_type, block,
|
||||
switch_fall_var, has_switch_break);
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case vtn_cf_node_type_if: {
|
||||
struct vtn_if *vtn_if = vtn_cf_node_as_if(node);
|
||||
const uint32_t *branch = vtn_if->header_block->branch;
|
||||
vtn_assert((branch[0] & SpvOpCodeMask) == SpvOpBranchConditional);
|
||||
|
||||
bool sw_break = false;
|
||||
/* If both branches are the same, just emit the first block, which is
|
||||
* the only one we filled when building the CFG.
|
||||
*/
|
||||
if (branch[2] == branch[3]) {
|
||||
if (vtn_if->then_type == vtn_branch_type_none) {
|
||||
vtn_emit_cf_list_structured(b, &vtn_if->then_body,
|
||||
switch_fall_var, &sw_break, handler);
|
||||
} else {
|
||||
vtn_emit_branch(b, vtn_if->then_type, NULL, switch_fall_var, &sw_break);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
nir_if *nif =
|
||||
nir_push_if(&b->nb, vtn_get_nir_ssa(b, branch[1]));
|
||||
|
||||
nif->control = vtn_selection_control(b, vtn_if);
|
||||
|
||||
if (vtn_if->then_type == vtn_branch_type_none) {
|
||||
vtn_emit_cf_list_structured(b, &vtn_if->then_body,
|
||||
switch_fall_var, &sw_break, handler);
|
||||
} else {
|
||||
vtn_emit_branch(b, vtn_if->then_type, NULL, switch_fall_var, &sw_break);
|
||||
}
|
||||
|
||||
nir_push_else(&b->nb, nif);
|
||||
if (vtn_if->else_type == vtn_branch_type_none) {
|
||||
vtn_emit_cf_list_structured(b, &vtn_if->else_body,
|
||||
switch_fall_var, &sw_break, handler);
|
||||
} else {
|
||||
vtn_emit_branch(b, vtn_if->else_type, NULL, switch_fall_var, &sw_break);
|
||||
}
|
||||
|
||||
nir_pop_if(&b->nb, nif);
|
||||
|
||||
/* If we encountered a switch break somewhere inside of the if,
|
||||
* then it would have been handled correctly by calling
|
||||
* emit_cf_list or emit_branch for the interrior. However, we
|
||||
* need to predicate everything following on wether or not we're
|
||||
* still going.
|
||||
*/
|
||||
if (sw_break) {
|
||||
*has_switch_break = true;
|
||||
nir_push_if(&b->nb, nir_load_var(&b->nb, switch_fall_var));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case vtn_cf_node_type_loop: {
|
||||
struct vtn_loop *vtn_loop = vtn_cf_node_as_loop(node);
|
||||
|
||||
nir_loop *loop = nir_push_loop(&b->nb);
|
||||
loop->control = vtn_loop_control(b, vtn_loop);
|
||||
vtn_emit_cf_list_structured(b, &vtn_loop->body, NULL, NULL, handler);
|
||||
|
||||
nir_push_continue(&b->nb, loop);
|
||||
vtn_emit_cf_list_structured(b, &vtn_loop->cont_body, NULL, NULL, handler);
|
||||
|
||||
nir_pop_loop(&b->nb, loop);
|
||||
break;
|
||||
}
|
||||
|
||||
case vtn_cf_node_type_switch: {
|
||||
struct vtn_switch *vtn_switch = vtn_cf_node_as_switch(node);
|
||||
|
||||
/* Before we can emit anything, we need to sort the list of cases in
|
||||
* fall-through order.
|
||||
*/
|
||||
vtn_switch_order_cases(vtn_switch);
|
||||
|
||||
/* First, we create a variable to keep track of whether or not the
|
||||
* switch is still going at any given point. Any switch breaks
|
||||
* will set this variable to false.
|
||||
*/
|
||||
nir_variable *fall_var =
|
||||
nir_local_variable_create(b->nb.impl, glsl_bool_type(), "fall");
|
||||
nir_store_var(&b->nb, fall_var, nir_imm_false(&b->nb), 1);
|
||||
|
||||
nir_ssa_def *sel = vtn_get_nir_ssa(b, vtn_switch->selector);
|
||||
|
||||
/* Now we can walk the list of cases and actually emit code */
|
||||
vtn_foreach_cf_node(case_node, &vtn_switch->cases) {
|
||||
struct vtn_case *cse = vtn_cf_node_as_case(case_node);
|
||||
|
||||
/* If this case jumps directly to the break block, we don't have
|
||||
* to handle the case as the body is empty and doesn't fall
|
||||
* through.
|
||||
*/
|
||||
if (cse->block == vtn_switch->break_block)
|
||||
continue;
|
||||
|
||||
/* Figure out the condition */
|
||||
nir_ssa_def *cond =
|
||||
vtn_switch_case_condition(b, vtn_switch, sel, cse);
|
||||
/* Take fallthrough into account */
|
||||
cond = nir_ior(&b->nb, cond, nir_load_var(&b->nb, fall_var));
|
||||
|
||||
nir_if *case_if = nir_push_if(&b->nb, cond);
|
||||
|
||||
bool has_break = false;
|
||||
nir_store_var(&b->nb, fall_var, nir_imm_true(&b->nb), 1);
|
||||
vtn_emit_cf_list_structured(b, &cse->body, fall_var, &has_break,
|
||||
handler);
|
||||
(void)has_break; /* We don't care */
|
||||
|
||||
nir_pop_if(&b->nb, case_if);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
vtn_fail("Invalid CF node type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static struct nir_block *
|
||||
vtn_new_unstructured_block(struct vtn_builder *b, struct vtn_function *func)
|
||||
{
|
||||
@@ -1311,7 +505,7 @@ vtn_add_unstructured_block(struct vtn_builder *b,
|
||||
{
|
||||
if (!block->block) {
|
||||
block->block = vtn_new_unstructured_block(b, func);
|
||||
list_addtail(&block->node.link, work_list);
|
||||
list_addtail(&block->link, work_list);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1323,11 +517,11 @@ vtn_emit_cf_func_unstructured(struct vtn_builder *b, struct vtn_function *func,
|
||||
list_inithead(&work_list);
|
||||
|
||||
func->start_block->block = nir_start_block(func->nir_func->impl);
|
||||
list_addtail(&func->start_block->node.link, &work_list);
|
||||
list_addtail(&func->start_block->link, &work_list);
|
||||
while (!list_is_empty(&work_list)) {
|
||||
struct vtn_block *block =
|
||||
list_first_entry(&work_list, struct vtn_block, node.link);
|
||||
list_del(&block->node.link);
|
||||
list_first_entry(&work_list, struct vtn_block, link);
|
||||
list_del(&block->link);
|
||||
|
||||
vtn_assert(block->block);
|
||||
|
||||
@@ -1369,13 +563,12 @@ vtn_emit_cf_func_unstructured(struct vtn_builder *b, struct vtn_function *func,
|
||||
case SpvOpSwitch: {
|
||||
struct list_head cases;
|
||||
list_inithead(&cases);
|
||||
vtn_parse_switch(b, NULL, block->branch, &cases);
|
||||
vtn_parse_switch(b, block->branch, &cases);
|
||||
|
||||
nir_ssa_def *sel = vtn_get_nir_ssa(b, block->branch[1]);
|
||||
|
||||
struct vtn_case *def = NULL;
|
||||
vtn_foreach_cf_node(case_node, &cases) {
|
||||
struct vtn_case *cse = vtn_cf_node_as_case(case_node);
|
||||
vtn_foreach_case(cse, &cases) {
|
||||
if (cse->is_default) {
|
||||
assert(def == NULL);
|
||||
def = cse;
|
||||
@@ -1444,8 +637,7 @@ vtn_function_emit(struct vtn_builder *b, struct vtn_function *func,
|
||||
impl->structured = false;
|
||||
vtn_emit_cf_func_unstructured(b, func, instruction_handler);
|
||||
} else {
|
||||
vtn_emit_cf_list_structured(b, &func->body, NULL, NULL,
|
||||
instruction_handler);
|
||||
vtn_emit_cf_func_structured(b, func, instruction_handler);
|
||||
}
|
||||
|
||||
vtn_foreach_instruction(b, func->start_block->label, func->end,
|
||||
|
||||
@@ -41,6 +41,8 @@ extern uint32_t mesa_spirv_debug;
|
||||
#define MESA_SPIRV_DEBUG(flag) false
|
||||
#endif
|
||||
|
||||
#define MESA_SPIRV_DEBUG_STRUCTURED (1u << 0)
|
||||
|
||||
struct vtn_builder;
|
||||
struct vtn_decoration;
|
||||
|
||||
@@ -134,81 +136,11 @@ enum vtn_value_type {
|
||||
vtn_value_type_image_pointer,
|
||||
};
|
||||
|
||||
enum vtn_branch_type {
|
||||
vtn_branch_type_none,
|
||||
vtn_branch_type_if_merge,
|
||||
vtn_branch_type_switch_break,
|
||||
vtn_branch_type_switch_fallthrough,
|
||||
vtn_branch_type_loop_break,
|
||||
vtn_branch_type_loop_continue,
|
||||
vtn_branch_type_loop_back_edge,
|
||||
vtn_branch_type_discard,
|
||||
vtn_branch_type_terminate_invocation,
|
||||
vtn_branch_type_ignore_intersection,
|
||||
vtn_branch_type_terminate_ray,
|
||||
vtn_branch_type_emit_mesh_tasks,
|
||||
vtn_branch_type_return,
|
||||
};
|
||||
|
||||
enum vtn_cf_node_type {
|
||||
vtn_cf_node_type_block,
|
||||
vtn_cf_node_type_if,
|
||||
vtn_cf_node_type_loop,
|
||||
vtn_cf_node_type_case,
|
||||
vtn_cf_node_type_switch,
|
||||
vtn_cf_node_type_function,
|
||||
};
|
||||
|
||||
struct vtn_cf_node {
|
||||
struct list_head link;
|
||||
struct vtn_cf_node *parent;
|
||||
enum vtn_cf_node_type type;
|
||||
};
|
||||
|
||||
struct vtn_loop {
|
||||
struct vtn_cf_node node;
|
||||
|
||||
/* The main body of the loop */
|
||||
struct list_head body;
|
||||
|
||||
/* The "continue" part of the loop. This gets executed after the body
|
||||
* and is where you go when you hit a continue.
|
||||
*/
|
||||
struct list_head cont_body;
|
||||
|
||||
struct vtn_block *header_block;
|
||||
struct vtn_block *cont_block;
|
||||
struct vtn_block *break_block;
|
||||
|
||||
SpvLoopControlMask control;
|
||||
};
|
||||
|
||||
struct vtn_if {
|
||||
struct vtn_cf_node node;
|
||||
|
||||
enum vtn_branch_type then_type;
|
||||
struct list_head then_body;
|
||||
|
||||
enum vtn_branch_type else_type;
|
||||
struct list_head else_body;
|
||||
|
||||
struct vtn_block *header_block;
|
||||
struct vtn_block *merge_block;
|
||||
|
||||
SpvSelectionControlMask control;
|
||||
};
|
||||
|
||||
struct vtn_case {
|
||||
struct vtn_cf_node node;
|
||||
struct list_head link;
|
||||
|
||||
struct vtn_block *block;
|
||||
|
||||
enum vtn_branch_type type;
|
||||
struct list_head body;
|
||||
|
||||
/* The fallthrough case, if any */
|
||||
struct vtn_case *fallthrough;
|
||||
|
||||
/* The uint32_t values that map to this case */
|
||||
struct util_dynarray values;
|
||||
|
||||
@@ -219,18 +151,8 @@ struct vtn_case {
|
||||
bool visited;
|
||||
};
|
||||
|
||||
struct vtn_switch {
|
||||
struct vtn_cf_node node;
|
||||
|
||||
uint32_t selector;
|
||||
|
||||
struct list_head cases;
|
||||
|
||||
struct vtn_block *break_block;
|
||||
};
|
||||
|
||||
struct vtn_block {
|
||||
struct vtn_cf_node node;
|
||||
struct list_head link;
|
||||
|
||||
/** A pointer to the label instruction */
|
||||
const uint32_t *label;
|
||||
@@ -241,19 +163,6 @@ struct vtn_block {
|
||||
/** A pointer to the branch instruction that ends this block */
|
||||
const uint32_t *branch;
|
||||
|
||||
enum vtn_branch_type branch_type;
|
||||
|
||||
/* The CF node for which this is a merge target
|
||||
*
|
||||
* The SPIR-V spec requires that any given block can be the merge target
|
||||
* for at most one merge instruction. If this block is a merge target,
|
||||
* this points back to the block containing that merge instruction.
|
||||
*/
|
||||
struct vtn_cf_node *merge_cf_node;
|
||||
|
||||
/** Points to the loop that this block starts (if it starts a loop) */
|
||||
struct vtn_loop *loop;
|
||||
|
||||
/** Points to the switch case started by this block (if any) */
|
||||
struct vtn_case *switch_case;
|
||||
|
||||
@@ -262,10 +171,22 @@ struct vtn_block {
|
||||
|
||||
/** attached nir_block */
|
||||
struct nir_block *block;
|
||||
|
||||
/* Inner-most construct that this block is part of. */
|
||||
struct vtn_construct *parent;
|
||||
|
||||
/* Blocks that succeed this block. Used by structured control flow. */
|
||||
struct vtn_successor *successors;
|
||||
unsigned successors_count;
|
||||
|
||||
/* Position of this block in the structured post-order traversal. */
|
||||
unsigned pos;
|
||||
|
||||
bool visited;
|
||||
};
|
||||
|
||||
struct vtn_function {
|
||||
struct vtn_cf_node node;
|
||||
struct list_head link;
|
||||
|
||||
struct vtn_type *type;
|
||||
|
||||
@@ -281,25 +202,27 @@ struct vtn_function {
|
||||
|
||||
SpvLinkageType linkage;
|
||||
SpvFunctionControlMask control;
|
||||
|
||||
unsigned block_count;
|
||||
|
||||
/* Ordering of blocks to be processed by structured control flow. See
|
||||
* vtn_structured_cfg.c for details.
|
||||
*/
|
||||
unsigned ordered_blocks_count;
|
||||
struct vtn_block **ordered_blocks;
|
||||
|
||||
/* Structured control flow constructs. See struct vtn_construct. */
|
||||
struct list_head constructs;
|
||||
};
|
||||
|
||||
#define VTN_DECL_CF_NODE_CAST(_type) \
|
||||
static inline struct vtn_##_type * \
|
||||
vtn_cf_node_as_##_type(struct vtn_cf_node *node) \
|
||||
{ \
|
||||
assert(node->type == vtn_cf_node_type_##_type); \
|
||||
return (struct vtn_##_type *)node; \
|
||||
}
|
||||
#define vtn_foreach_function(func, func_list) \
|
||||
list_for_each_entry(struct vtn_function, func, func_list, link)
|
||||
|
||||
VTN_DECL_CF_NODE_CAST(block)
|
||||
VTN_DECL_CF_NODE_CAST(loop)
|
||||
VTN_DECL_CF_NODE_CAST(if)
|
||||
VTN_DECL_CF_NODE_CAST(case)
|
||||
VTN_DECL_CF_NODE_CAST(switch)
|
||||
VTN_DECL_CF_NODE_CAST(function)
|
||||
#define vtn_foreach_case(cse, case_list) \
|
||||
list_for_each_entry(struct vtn_case, cse, case_list, link)
|
||||
|
||||
#define vtn_foreach_cf_node(node, cf_list) \
|
||||
list_for_each_entry(struct vtn_cf_node, node, cf_list, link)
|
||||
#define vtn_foreach_case_safe(cse, case_list) \
|
||||
list_for_each_entry_safe(struct vtn_case, cse, case_list, link)
|
||||
|
||||
typedef bool (*vtn_instruction_handler)(struct vtn_builder *, SpvOp,
|
||||
const uint32_t *, unsigned);
|
||||
@@ -311,6 +234,16 @@ void vtn_function_emit(struct vtn_builder *b, struct vtn_function *func,
|
||||
void vtn_handle_function_call(struct vtn_builder *b, SpvOp opcode,
|
||||
const uint32_t *w, unsigned count);
|
||||
|
||||
bool vtn_cfg_handle_prepass_instruction(struct vtn_builder *b, SpvOp opcode,
|
||||
const uint32_t *w, unsigned count);
|
||||
void vtn_emit_cf_func_structured(struct vtn_builder *b, struct vtn_function *func,
|
||||
vtn_instruction_handler handler);
|
||||
bool vtn_handle_phis_first_pass(struct vtn_builder *b, SpvOp opcode,
|
||||
const uint32_t *w, unsigned count);
|
||||
void vtn_emit_ret_store(struct vtn_builder *b, const struct vtn_block *block);
|
||||
void vtn_build_structured_cfg(struct vtn_builder *b, const uint32_t *words,
|
||||
const uint32_t *end);
|
||||
|
||||
const uint32_t *
|
||||
vtn_foreach_instruction(struct vtn_builder *b, const uint32_t *start,
|
||||
const uint32_t *end, vtn_instruction_handler handler);
|
||||
@@ -906,6 +839,12 @@ vtn_get_type(struct vtn_builder *b, uint32_t value_id)
|
||||
return vtn_value(b, value_id, vtn_value_type_type)->type;
|
||||
}
|
||||
|
||||
static inline struct vtn_block *
|
||||
vtn_block(struct vtn_builder *b, uint32_t value_id)
|
||||
{
|
||||
return vtn_value(b, value_id, vtn_value_type_block)->block;
|
||||
}
|
||||
|
||||
struct vtn_ssa_value *vtn_ssa_value(struct vtn_builder *b, uint32_t value_id);
|
||||
struct vtn_value *vtn_push_ssa_value(struct vtn_builder *b, uint32_t value_id,
|
||||
struct vtn_ssa_value *ssa);
|
||||
@@ -1081,4 +1020,10 @@ cmp_uint32_t(const void *pa, const void *pb)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
vtn_parse_switch(struct vtn_builder *b,
|
||||
const uint32_t *branch,
|
||||
struct list_head *case_list);
|
||||
|
||||
|
||||
#endif /* _VTN_PRIVATE_H_ */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -490,13 +490,7 @@ Test:SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultBody_
|
||||
Test:SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultBody_Unconditional.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_ClassifyCFGEdges_LoopContinue_LoopBodyToContinue.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_ClassifyCFGEdges_Pathological_Forward_LoopHeadSplitBody.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_ClassifyCFGEdges_Pathological_Forward_Premerge.spvasm:main|Fragment: Fail
|
||||
SPIR-V parsing FAILED:
|
||||
|
||||
Invalid back or cross-edge in the CFG
|
||||
0 bytes into the SPIR-V binary
|
||||
|
||||
|
||||
Test:SpvParserCFGTest_ClassifyCFGEdges_Pathological_Forward_Premerge.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_ClassifyCFGEdges_Pathological_Forward_Regardless.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromNestedIf_Conditional.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_ClassifyCFGEdges_SwitchBreak_FromNestedIf_Unconditional.spvasm:main|Fragment: Pass
|
||||
@@ -536,13 +530,7 @@ Test:SpvParserCFGTest_ComputeBlockOrder_Nest_If_In_SwitchCase.spvasm:main|Fragme
|
||||
Test:SpvParserCFGTest_ComputeBlockOrder_Nest_IfBreak_In_SwitchCase.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_ComputeBlockOrder_Nest_IfFallthrough_In_SwitchCase.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_ComputeBlockOrder_OneBlock.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_ComputeBlockOrder_ReorderSequence.spvasm:main|Fragment: Fail
|
||||
SPIR-V parsing FAILED:
|
||||
|
||||
Invalid back or cross-edge in the CFG
|
||||
0 bytes into the SPIR-V binary
|
||||
|
||||
|
||||
Test:SpvParserCFGTest_ComputeBlockOrder_ReorderSequence.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_ComputeBlockOrder_RespectConditionalBranchOrder.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_ComputeBlockOrder_RespectSwitchCaseFallthrough.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_ComputeBlockOrder_RespectSwitchCaseFallthrough_FromCaseToDefaultToCase.spvasm:main|Fragment: Pass
|
||||
@@ -602,41 +590,17 @@ Test:SpvParserCFGTest_EmitBody_BranchConditional_SwitchBreak_SwitchBreak_NotLast
|
||||
Test:SpvParserCFGTest_EmitBody_FalseBranch_LoopBreak.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_EmitBody_FalseBranch_LoopContinue.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_EmitBody_FalseBranch_SwitchBreak.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_EmitBody_If_Else_Premerge.spvasm:main|Fragment: Fail
|
||||
SPIR-V parsing FAILED:
|
||||
|
||||
Invalid back or cross-edge in the CFG
|
||||
0 bytes into the SPIR-V binary
|
||||
|
||||
|
||||
Test:SpvParserCFGTest_EmitBody_If_Else_Premerge.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_EmitBody_If_Empty.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_EmitBody_If_Nest_If.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_EmitBody_If_NoThen_Else.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_EmitBody_If_Then_Else.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_EmitBody_If_Then_Else_Premerge.spvasm:main|Fragment: Fail
|
||||
SPIR-V parsing FAILED:
|
||||
|
||||
Invalid back or cross-edge in the CFG
|
||||
0 bytes into the SPIR-V binary
|
||||
|
||||
|
||||
Test:SpvParserCFGTest_EmitBody_If_Then_Else_Premerge.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_EmitBody_If_Then_NoElse.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_EmitBody_If_Then_Premerge.spvasm:main|Fragment: Fail
|
||||
SPIR-V parsing FAILED:
|
||||
|
||||
Invalid back or cross-edge in the CFG
|
||||
0 bytes into the SPIR-V binary
|
||||
|
||||
|
||||
Test:SpvParserCFGTest_EmitBody_If_Then_Premerge.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_EmitBody_IfBreak_FromElse_ForwardWithinElse.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_EmitBody_IfBreak_FromThen_ForwardWithinThen.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_EmitBody_IfBreak_FromThenWithForward_FromElseWithForward_AlsoPremerge.spvasm:main|Fragment: Fail
|
||||
SPIR-V parsing FAILED:
|
||||
|
||||
Invalid back or cross-edge in the CFG
|
||||
0 bytes into the SPIR-V binary
|
||||
|
||||
|
||||
Test:SpvParserCFGTest_EmitBody_IfBreak_FromThenWithForward_FromElseWithForward_AlsoPremerge.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_EmitBody_IfSelection_TrueBranch_LoopBreak.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_EmitBody_Kill_InsideIf.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_EmitBody_Kill_InsideLoop.spvasm:main|Fragment: Pass
|
||||
@@ -685,27 +649,9 @@ Test:SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok
|
||||
Test:SpvParserCFGTest_FindIfSelectionInternalHeaders_FalseBranch_SwitchBreak_Ok.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_FindIfSelectionInternalHeaders_IfOnly.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_FindIfSelectionInternalHeaders_NoIf.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_FindIfSelectionInternalHeaders_Premerge_ElseDirectToThen.spvasm:main|Fragment: Fail
|
||||
SPIR-V parsing FAILED:
|
||||
|
||||
Invalid back or cross-edge in the CFG
|
||||
0 bytes into the SPIR-V binary
|
||||
|
||||
|
||||
Test:SpvParserCFGTest_FindIfSelectionInternalHeaders_Premerge_Simple.spvasm:main|Fragment: Fail
|
||||
SPIR-V parsing FAILED:
|
||||
|
||||
Invalid back or cross-edge in the CFG
|
||||
0 bytes into the SPIR-V binary
|
||||
|
||||
|
||||
Test:SpvParserCFGTest_FindIfSelectionInternalHeaders_Premerge_ThenDirectToElse.spvasm:main|Fragment: Fail
|
||||
SPIR-V parsing FAILED:
|
||||
|
||||
Invalid back or cross-edge in the CFG
|
||||
0 bytes into the SPIR-V binary
|
||||
|
||||
|
||||
Test:SpvParserCFGTest_FindIfSelectionInternalHeaders_Premerge_ElseDirectToThen.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_FindIfSelectionInternalHeaders_Premerge_Simple.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_FindIfSelectionInternalHeaders_Premerge_ThenDirectToElse.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_FindIfSelectionInternalHeaders_Regardless.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_FindIfSelectionInternalHeaders_ThenElse.spvasm:main|Fragment: Pass
|
||||
Test:SpvParserCFGTest_FindIfSelectionInternalHeaders_TrueBranch_LoopBreak_Ok.spvasm:main|Fragment: Pass
|
||||
|
||||
Reference in New Issue
Block a user