vkd3d-shader/ir: Flatten LOOP/BREAK/CONTINUE/ENDLOOP control flow instructions.

This commit is contained in:
Conor McCarthy 2023-12-12 15:34:51 +10:00 committed by Alexandre Julliard
parent e1dddc01b7
commit dcb8527327
Notes: Alexandre Julliard 2024-01-17 22:42:55 +01:00
Approved-by: Giovanni Mascellani (@giomasce)
Approved-by: Henri Verbeet (@hverbeet)
Approved-by: Alexandre Julliard (@julliard)
Merge-Request: https://gitlab.winehq.org/wine/vkd3d/-/merge_requests/450
2 changed files with 197 additions and 154 deletions

View File

@ -1515,16 +1515,26 @@ struct cf_flattener_if_info
unsigned int else_block_id;
};
struct cf_flattener_loop_info
{
unsigned int header_block_id;
unsigned int continue_block_id;
uint32_t merge_block_id;
};
struct cf_flattener_info
{
union
{
struct cf_flattener_if_info if_;
struct cf_flattener_loop_info loop;
} u;
enum
{
VKD3D_BLOCK_IF,
VKD3D_BLOCK_LOOP,
VKD3D_BLOCK_SWITCH,
} current_block;
bool inside_block;
};
@ -1608,8 +1618,8 @@ static void cf_flattener_emit_label(struct cf_flattener *flattener, unsigned int
/* For conditional branches, this returns the false target branch parameter. */
static struct vkd3d_shader_src_param *cf_flattener_emit_branch(struct cf_flattener *flattener,
unsigned int merge_block_id, const struct vkd3d_shader_src_param *condition,
unsigned int true_id, unsigned int false_id,
unsigned int merge_block_id, unsigned int continue_block_id,
const struct vkd3d_shader_src_param *condition, unsigned int true_id, unsigned int false_id,
unsigned int flags)
{
struct vkd3d_shader_src_param *src_params, *false_branch_param;
@ -1621,7 +1631,7 @@ static struct vkd3d_shader_src_param *cf_flattener_emit_branch(struct cf_flatten
if (condition)
{
if (!(src_params = instruction_src_params_alloc(ins, 4, flattener)))
if (!(src_params = instruction_src_params_alloc(ins, 4 + !!continue_block_id, flattener)))
return NULL;
src_params[0] = *condition;
if (flags == VKD3D_SHADER_CONDITIONAL_OP_Z)
@ -1637,12 +1647,21 @@ static struct vkd3d_shader_src_param *cf_flattener_emit_branch(struct cf_flatten
false_branch_param = &src_params[2];
}
vsir_src_param_init_label(&src_params[3], merge_block_id);
if (continue_block_id)
vsir_src_param_init_label(&src_params[4], continue_block_id);
}
else
{
if (!(src_params = instruction_src_params_alloc(ins, 1, flattener)))
if (!(src_params = instruction_src_params_alloc(ins, merge_block_id ? 3 : 1, flattener)))
return NULL;
vsir_src_param_init_label(&src_params[0], true_id);
if (merge_block_id)
{
/* An unconditional branch may only have merge information for a loop, which
* must have both a merge block and continue block. */
vsir_src_param_init_label(&src_params[1], merge_block_id);
vsir_src_param_init_label(&src_params[2], continue_block_id);
}
false_branch_param = NULL;
}
@ -1651,9 +1670,19 @@ static struct vkd3d_shader_src_param *cf_flattener_emit_branch(struct cf_flatten
return false_branch_param;
}
static void cf_flattener_emit_conditional_branch_and_merge(struct cf_flattener *flattener,
const struct vkd3d_shader_src_param *condition, unsigned int true_id, unsigned int flags)
{
unsigned int merge_block_id;
merge_block_id = cf_flattener_alloc_block_id(flattener);
cf_flattener_emit_branch(flattener, merge_block_id, 0, condition, true_id, merge_block_id, flags);
cf_flattener_emit_label(flattener, merge_block_id);
}
static void cf_flattener_emit_unconditional_branch(struct cf_flattener *flattener, unsigned int target_block_id)
{
cf_flattener_emit_branch(flattener, 0, NULL, target_block_id, 0, 0);
cf_flattener_emit_branch(flattener, 0, 0, NULL, target_block_id, 0, 0);
}
static struct cf_flattener_info *cf_flattener_push_control_flow_level(struct cf_flattener *flattener)
@ -1677,12 +1706,38 @@ static void cf_flattener_pop_control_flow_level(struct cf_flattener *flattener)
memset(cf_info, 0, sizeof(*cf_info));
}
static struct cf_flattener_info *cf_flattener_find_innermost_loop(struct cf_flattener *flattener)
{
int depth;
for (depth = flattener->control_flow_depth - 1; depth >= 0; --depth)
{
if (flattener->control_flow_info[depth].current_block == VKD3D_BLOCK_LOOP)
return &flattener->control_flow_info[depth];
}
return NULL;
}
static struct cf_flattener_info *cf_flattener_find_innermost_breakable_cf_construct(struct cf_flattener *flattener)
{
int depth;
for (depth = flattener->control_flow_depth - 1; depth >= 0; --depth)
{
if (flattener->control_flow_info[depth].current_block == VKD3D_BLOCK_LOOP
|| flattener->control_flow_info[depth].current_block == VKD3D_BLOCK_SWITCH)
return &flattener->control_flow_info[depth];
}
return NULL;
}
static enum vkd3d_result cf_flattener_iterate_instruction_array(struct cf_flattener *flattener)
{
struct vkd3d_shader_parser *parser = flattener->parser;
struct vkd3d_shader_instruction_array *instructions;
struct vkd3d_shader_instruction *dst_ins;
unsigned int depth = 0;
bool main_block_open;
size_t i;
@ -1694,9 +1749,9 @@ static enum vkd3d_result cf_flattener_iterate_instruction_array(struct cf_flatte
for (i = 0; i < instructions->count; ++i)
{
unsigned int loop_header_block_id, loop_body_block_id, continue_block_id, merge_block_id, true_block_id;
const struct vkd3d_shader_instruction *instruction = &instructions->elements[i];
const struct vkd3d_shader_src_param *src = instruction->src;
unsigned int merge_block_id, true_block_id;
struct cf_flattener_info *cf_info;
flattener->location = instruction->location;
@ -1717,7 +1772,7 @@ static enum vkd3d_result cf_flattener_iterate_instruction_array(struct cf_flatte
true_block_id = cf_flattener_alloc_block_id(flattener);
merge_block_id = cf_flattener_alloc_block_id(flattener);
cf_info->u.if_.false_param = cf_flattener_emit_branch(flattener, merge_block_id,
cf_info->u.if_.false_param = cf_flattener_emit_branch(flattener, merge_block_id, 0,
src, true_block_id, merge_block_id, instruction->flags);
if (!cf_info->u.if_.false_param)
return VKD3D_ERROR_OUT_OF_MEMORY;
@ -1752,20 +1807,126 @@ static enum vkd3d_result cf_flattener_iterate_instruction_array(struct cf_flatte
break;
case VKD3DSIH_LOOP:
if (!(cf_info = cf_flattener_push_control_flow_level(flattener)))
return VKD3D_ERROR_OUT_OF_MEMORY;
loop_header_block_id = cf_flattener_alloc_block_id(flattener);
loop_body_block_id = cf_flattener_alloc_block_id(flattener);
continue_block_id = cf_flattener_alloc_block_id(flattener);
merge_block_id = cf_flattener_alloc_block_id(flattener);
cf_flattener_emit_unconditional_branch(flattener, loop_header_block_id);
cf_flattener_emit_label(flattener, loop_header_block_id);
cf_flattener_emit_branch(flattener, merge_block_id, continue_block_id,
NULL, loop_body_block_id, 0, 0);
cf_flattener_emit_label(flattener, loop_body_block_id);
cf_info->u.loop.header_block_id = loop_header_block_id;
cf_info->u.loop.continue_block_id = continue_block_id;
cf_info->u.loop.merge_block_id = merge_block_id;
cf_info->current_block = VKD3D_BLOCK_LOOP;
cf_info->inside_block = true;
break;
case VKD3DSIH_ENDLOOP:
if (cf_info->inside_block)
cf_flattener_emit_unconditional_branch(flattener, cf_info->u.loop.continue_block_id);
cf_flattener_emit_label(flattener, cf_info->u.loop.continue_block_id);
cf_flattener_emit_unconditional_branch(flattener, cf_info->u.loop.header_block_id);
cf_flattener_emit_label(flattener, cf_info->u.loop.merge_block_id);
cf_flattener_pop_control_flow_level(flattener);
break;
case VKD3DSIH_SWITCH:
++depth;
if (!(cf_info = cf_flattener_push_control_flow_level(flattener)))
return VKD3D_ERROR_OUT_OF_MEMORY;
if (!cf_flattener_copy_instruction(flattener, instruction))
return VKD3D_ERROR_OUT_OF_MEMORY;
cf_info->current_block = VKD3D_BLOCK_SWITCH;
break;
case VKD3DSIH_ENDSWITCH:
cf_flattener_pop_control_flow_level(flattener);
if (!cf_flattener_copy_instruction(flattener, instruction))
return VKD3D_ERROR_OUT_OF_MEMORY;
break;
case VKD3DSIH_ENDLOOP:
case VKD3DSIH_ENDSWITCH:
--depth;
if (cf_info)
cf_info->inside_block = true; /* Dead code removal ensures this is correct. */
if (!cf_flattener_copy_instruction(flattener, instruction))
return VKD3D_ERROR_OUT_OF_MEMORY;
case VKD3DSIH_BREAK:
{
struct cf_flattener_info *breakable_cf_info;
if (!(breakable_cf_info = cf_flattener_find_innermost_breakable_cf_construct(flattener)))
{
FIXME("Unhandled break instruction.\n");
return VKD3D_ERROR_INVALID_SHADER;
}
if (breakable_cf_info->current_block == VKD3D_BLOCK_LOOP)
{
cf_flattener_emit_unconditional_branch(flattener, breakable_cf_info->u.loop.merge_block_id);
}
else if (breakable_cf_info->current_block == VKD3D_BLOCK_SWITCH)
{
if (!cf_flattener_copy_instruction(flattener, instruction))
return VKD3D_ERROR_OUT_OF_MEMORY;
}
cf_info->inside_block = false;
break;
}
case VKD3DSIH_BREAKP:
{
struct cf_flattener_info *loop_cf_info;
if (!(loop_cf_info = cf_flattener_find_innermost_loop(flattener)))
{
ERR("Invalid 'breakc' instruction outside loop.\n");
return VKD3D_ERROR_INVALID_SHADER;
}
cf_flattener_emit_conditional_branch_and_merge(flattener,
src, loop_cf_info->u.loop.merge_block_id, instruction->flags);
break;
}
case VKD3DSIH_CONTINUE:
{
struct cf_flattener_info *loop_cf_info;
if (!(loop_cf_info = cf_flattener_find_innermost_loop(flattener)))
{
ERR("Invalid 'continue' instruction outside loop.\n");
return VKD3D_ERROR_INVALID_SHADER;
}
cf_flattener_emit_unconditional_branch(flattener, loop_cf_info->u.loop.continue_block_id);
cf_info->inside_block = false;
break;
}
case VKD3DSIH_CONTINUEP:
{
struct cf_flattener_info *loop_cf_info;
if (!(loop_cf_info = cf_flattener_find_innermost_loop(flattener)))
{
ERR("Invalid 'continuec' instruction outside loop.\n");
return VKD3D_ERROR_INVALID_SHADER;
}
cf_flattener_emit_conditional_branch_and_merge(flattener,
src, loop_cf_info->u.loop.continue_block_id, instruction->flags);
break;
}
case VKD3DSIH_RET:
if (!cf_flattener_copy_instruction(flattener, instruction))
@ -1773,15 +1934,10 @@ static enum vkd3d_result cf_flattener_iterate_instruction_array(struct cf_flatte
if (cf_info)
cf_info->inside_block = false;
else if (!depth)
else
main_block_open = false;
break;
case VKD3DSIH_BREAK:
case VKD3DSIH_CONTINUE:
if (cf_info)
cf_info->inside_block = false;
/* fall through */
default:
if (!cf_flattener_copy_instruction(flattener, instruction))
return VKD3D_ERROR_OUT_OF_MEMORY;

View File

@ -2253,13 +2253,6 @@ static const char *debug_vkd3d_symbol(const struct vkd3d_symbol *symbol)
}
}
struct vkd3d_loop_cf_info
{
uint32_t header_block_id;
uint32_t continue_block_id;
uint32_t merge_block_id;
};
struct vkd3d_switch_cf_info
{
size_t stream_location;
@ -2276,13 +2269,11 @@ struct vkd3d_control_flow_info
{
union
{
struct vkd3d_loop_cf_info loop;
struct vkd3d_switch_cf_info switch_;
} u;
enum
{
VKD3D_BLOCK_LOOP,
VKD3D_BLOCK_SWITCH,
} current_block;
bool inside_block;
@ -2341,7 +2332,6 @@ struct spirv_compiler
enum vkd3d_shader_type shader_type;
unsigned int loop_id;
unsigned int switch_id;
unsigned int control_flow_depth;
struct vkd3d_control_flow_info *control_flow_info;
@ -7622,20 +7612,6 @@ static void spirv_compiler_pop_control_flow_level(struct spirv_compiler *compile
memset(cf_info, 0, sizeof(*cf_info));
}
static struct vkd3d_control_flow_info *spirv_compiler_find_innermost_loop(
struct spirv_compiler *compiler)
{
int depth;
for (depth = compiler->control_flow_depth - 1; depth >= 0; --depth)
{
if (compiler->control_flow_info[depth].current_block == VKD3D_BLOCK_LOOP)
return &compiler->control_flow_info[depth];
}
return NULL;
}
static struct vkd3d_control_flow_info *spirv_compiler_find_innermost_breakable_cf_construct(
struct spirv_compiler *compiler)
{
@ -7643,8 +7619,7 @@ static struct vkd3d_control_flow_info *spirv_compiler_find_innermost_breakable_c
for (depth = compiler->control_flow_depth - 1; depth >= 0; --depth)
{
if (compiler->control_flow_info[depth].current_block == VKD3D_BLOCK_LOOP
|| compiler->control_flow_info[depth].current_block == VKD3D_BLOCK_SWITCH)
if (compiler->control_flow_info[depth].current_block == VKD3D_BLOCK_SWITCH)
return &compiler->control_flow_info[depth];
}
@ -7657,7 +7632,6 @@ static void spirv_compiler_emit_label(struct spirv_compiler *compiler,
static int spirv_compiler_emit_control_flow_instruction(struct spirv_compiler *compiler,
const struct vkd3d_shader_instruction *instruction)
{
uint32_t loop_header_block_id, loop_body_block_id, continue_block_id;
struct vkd3d_spirv_builder *builder = &compiler->spirv_builder;
const struct vkd3d_shader_src_param *src = instruction->src;
struct vkd3d_control_flow_info *cf_info;
@ -7674,51 +7648,6 @@ static int spirv_compiler_emit_control_flow_instruction(struct spirv_compiler *c
spirv_compiler_emit_label(compiler, instruction);
break;
case VKD3DSIH_LOOP:
if (!(cf_info = spirv_compiler_push_control_flow_level(compiler)))
return VKD3D_ERROR_OUT_OF_MEMORY;
loop_header_block_id = vkd3d_spirv_alloc_id(builder);
loop_body_block_id = vkd3d_spirv_alloc_id(builder);
continue_block_id = vkd3d_spirv_alloc_id(builder);
merge_block_id = vkd3d_spirv_alloc_id(builder);
vkd3d_spirv_build_op_branch(builder, loop_header_block_id);
vkd3d_spirv_build_op_label(builder, loop_header_block_id);
vkd3d_spirv_build_op_loop_merge(builder, merge_block_id, continue_block_id, SpvLoopControlMaskNone);
vkd3d_spirv_build_op_branch(builder, loop_body_block_id);
vkd3d_spirv_build_op_label(builder, loop_body_block_id);
cf_info->u.loop.header_block_id = loop_header_block_id;
cf_info->u.loop.continue_block_id = continue_block_id;
cf_info->u.loop.merge_block_id = merge_block_id;
cf_info->current_block = VKD3D_BLOCK_LOOP;
cf_info->inside_block = true;
vkd3d_spirv_build_op_name(builder, loop_header_block_id, "loop%u_header", compiler->loop_id);
vkd3d_spirv_build_op_name(builder, loop_body_block_id, "loop%u_body", compiler->loop_id);
vkd3d_spirv_build_op_name(builder, continue_block_id, "loop%u_continue", compiler->loop_id);
vkd3d_spirv_build_op_name(builder, merge_block_id, "loop%u_merge", compiler->loop_id);
++compiler->loop_id;
break;
case VKD3DSIH_ENDLOOP:
assert(compiler->control_flow_depth);
assert(cf_info->current_block == VKD3D_BLOCK_LOOP);
/* The loop block may have already been ended by an unconditional
* break instruction right before the end of the loop. */
if (cf_info->inside_block)
vkd3d_spirv_build_op_branch(builder, cf_info->u.loop.continue_block_id);
vkd3d_spirv_build_op_label(builder, cf_info->u.loop.continue_block_id);
vkd3d_spirv_build_op_branch(builder, cf_info->u.loop.header_block_id);
vkd3d_spirv_build_op_label(builder, cf_info->u.loop.merge_block_id);
spirv_compiler_pop_control_flow_level(compiler);
break;
case VKD3DSIH_SWITCH:
if (!(cf_info = spirv_compiler_push_control_flow_level(compiler)))
return VKD3D_ERROR_OUT_OF_MEMORY;
@ -7835,11 +7764,7 @@ static int spirv_compiler_emit_control_flow_instruction(struct spirv_compiler *c
return VKD3D_ERROR_INVALID_SHADER;
}
if (breakable_cf_info->current_block == VKD3D_BLOCK_LOOP)
{
vkd3d_spirv_build_op_branch(builder, breakable_cf_info->u.loop.merge_block_id);
}
else if (breakable_cf_info->current_block == VKD3D_BLOCK_SWITCH)
if (breakable_cf_info->current_block == VKD3D_BLOCK_SWITCH)
{
/* The current case block may have already been ended by an
* unconditional continue instruction. */
@ -7851,58 +7776,6 @@ static int spirv_compiler_emit_control_flow_instruction(struct spirv_compiler *c
break;
}
case VKD3DSIH_BREAKP:
{
struct vkd3d_control_flow_info *loop_cf_info;
assert(compiler->control_flow_depth);
if (!(loop_cf_info = spirv_compiler_find_innermost_loop(compiler)))
{
ERR("Invalid 'breakc' instruction outside loop.\n");
return VKD3D_ERROR_INVALID_SHADER;
}
merge_block_id = spirv_compiler_emit_conditional_branch(compiler,
instruction, loop_cf_info->u.loop.merge_block_id);
vkd3d_spirv_build_op_label(builder, merge_block_id);
break;
}
case VKD3DSIH_CONTINUE:
{
struct vkd3d_control_flow_info *loop_cf_info;
assert(compiler->control_flow_depth);
if (!(loop_cf_info = spirv_compiler_find_innermost_loop(compiler)))
{
ERR("Invalid 'continue' instruction outside loop.\n");
return VKD3D_ERROR_INVALID_SHADER;
}
vkd3d_spirv_build_op_branch(builder, loop_cf_info->u.loop.continue_block_id);
cf_info->inside_block = false;
break;
}
case VKD3DSIH_CONTINUEP:
{
struct vkd3d_control_flow_info *loop_cf_info;
if (!(loop_cf_info = spirv_compiler_find_innermost_loop(compiler)))
{
ERR("Invalid 'continuec' instruction outside loop.\n");
return VKD3D_ERROR_INVALID_SHADER;
}
merge_block_id = spirv_compiler_emit_conditional_branch(compiler,
instruction, loop_cf_info->u.loop.continue_block_id);
vkd3d_spirv_build_op_label(builder, merge_block_id);
break;
}
case VKD3DSIH_RET:
spirv_compiler_emit_return(compiler, instruction);
@ -7941,7 +7814,7 @@ static void spirv_compiler_emit_label(struct spirv_compiler *compiler,
}
static void spirv_compiler_emit_merge(struct spirv_compiler *compiler,
uint32_t merge_block_id)
uint32_t merge_block_id, uint32_t continue_block_id)
{
struct vkd3d_spirv_builder *builder = &compiler->spirv_builder;
@ -7949,7 +7822,15 @@ static void spirv_compiler_emit_merge(struct spirv_compiler *compiler,
return;
merge_block_id = spirv_compiler_get_label_id(compiler, merge_block_id);
vkd3d_spirv_build_op_selection_merge(builder, merge_block_id, SpvSelectionControlMaskNone);
if (!continue_block_id)
{
vkd3d_spirv_build_op_selection_merge(builder, merge_block_id, SpvSelectionControlMaskNone);
}
else
{
continue_block_id = spirv_compiler_get_label_id(compiler, continue_block_id);
vkd3d_spirv_build_op_loop_merge(builder, merge_block_id, continue_block_id, SpvLoopControlMaskNone);
}
}
static void spirv_compiler_emit_branch(struct spirv_compiler *compiler,
@ -7961,6 +7842,11 @@ static void spirv_compiler_emit_branch(struct spirv_compiler *compiler,
if (vsir_register_is_label(&src[0].reg))
{
if (instruction->src_count > 1)
{
/* Loop merge only. Must have a merge block and a continue block. */
spirv_compiler_emit_merge(compiler, src[1].reg.idx[0].offset, src[2].reg.idx[0].offset);
}
vkd3d_spirv_build_op_branch(builder, spirv_compiler_get_label_id(compiler, src[0].reg.idx[0].offset));
return;
}
@ -7976,7 +7862,8 @@ static void spirv_compiler_emit_branch(struct spirv_compiler *compiler,
condition_id = spirv_compiler_emit_int_to_bool(compiler,
VKD3D_SHADER_CONDITIONAL_OP_NZ, src[0].reg.data_type, 1, condition_id);
/* Emit the merge immediately before the branch instruction. */
spirv_compiler_emit_merge(compiler, src[3].reg.idx[0].offset);
spirv_compiler_emit_merge(compiler, src[3].reg.idx[0].offset,
(instruction->src_count > 4) ? src[4].reg.idx[0].offset : 0);
vkd3d_spirv_build_op_branch_conditional(builder, condition_id,
spirv_compiler_get_label_id(compiler, src[1].reg.idx[0].offset),
spirv_compiler_get_label_id(compiler, src[2].reg.idx[0].offset));