From 5c02e2e2de75b9d18ca25b4f1cba38c4a89c5bd0 Mon Sep 17 00:00:00 2001 From: Dan McCabe Date: Mon, 7 Nov 2011 16:17:58 -0800 Subject: [PATCH] glsl: Generate IR for switch statements Up until now modifying the GLSL compiler has been pretty straightforward. This is where things get interesting. But still pretty straightforward. Switch statements can be thought of a series of if/then/else statements. Case labels are compared with the value of a test expression and the case statements are executed if the comparison is true. There are a couple of aspects of switch statements that complicate this simple view of the world. The primary one is that cases can fall through sequentially to subsequent case, unless a break statement is encountered, in which case, the switch statement exits completely. But break handling is further complicated by the fact that a break statement can impact the exit of a loop. Thus, we need to coordinate break processing between switch statements and loop statements. The code generated by a switch statement maintains three temporary state variables: int test_value; bool is_fallthru; bool is_break; test_value is initialized to the value of the test expression at the head of the switch statement. This is the value that case labels are compared against. is_fallthru is used to sequentially fall through to subsequent cases and is initialized to false. When a case label matches the test expression, this state variable is set to true. It will also be forced to false if a break statement has been encountered. This forcing to false on break MUST be after every case test. In practice, we defer that forcing to immediately after the last case comparison prior to executing a case statement, but that is an optimization. is_break is used to indicate that a break statement has been executed and is initialized to false. When a break statement is encountered, it is set to true. This state variable is then used to conditionally force is_fallthru to to false to prevent subsequent case statements from executing. Code generation for break statements depends on whether the break statement is inside a switch statement or inside a loop statement. If it inside a loop statement is inside a break statement, the same code as before gets generated. But if a switch statement is inside a loop statement, code is emitted to set the is_break state to true. Just as ASTs for loop statements are managed in a stack-like manner to handle nesting, we also add a bool to capture the innermost switch or loop condition. Note that we still need to maintain a loop AST stack to properly handle for-loop code generation on a continue statement. Technically, we don't (yet) need a switch AST stack, but I am using one for orthogonality with loop statements, in anticipation of future use. Note that a simple boolean stack would have sufficed. We will illustrate a switch statement with its analogous conditional code that a switch statement corresponds to by examining an example. Consider the following switch statement: switch (42) { case 0: case 1: gl_FragColor = vec4(1.0, 2.0, 3.0, 4.0); case 2: case 3: gl_FragColor = vec4(4.0, 3.0, 2.0, 1.0); break; case 4: default: gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); } Note that case 0 and case 1 fall through to cases 2 and 3 if they occur. Note that case 4 and the default case must be reached explicitly, since cases 2 and 3 break at the end of their case. Finally, note that case 4 and the default case don't break but simply fall through to the end of the switch. For this code, the equivalent code can be expressed as: int test_val = 42; // capture value of test expression bool is_fallthru = false; // prevent initial fall through bool is_break = false; // capture the execution of a break stmt is_fallthru |= (test_val == 0); // enable fallthru on case 0 is_fallthru |= (test_val == 1); // enable fallthru on case 1 is_fallthru &= !is_break; // inhibit fallthru on previous break if (is_fallthru) { gl_FragColor = vec4(1.0, 2.0, 3.0, 4.0); } is_fallthru |= (test_val == 2); // enable fallthru on case 2 is_fallthru |= (test_val == 3); // enable fallthru on case 3 is_fallthru &= !is_break; // inhibit fallthru on previous break if (is_fallthru) { gl_FragColor = vec4(4.0, 3.0, 2.0, 1.0); is_break = true; // inhibit all subsequent fallthru for break } is_fallthru |= (test_val == 4); // enable fallthru on case 4 is_fallthru = true; // enable fallthru for default case is_fallthru &= !is_break; // inhibit fallthru on previous break if (is_fallthru) { gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); } The code generate for |= and &= uses the conditional assignment capabilities of the IR. Reviewed-by: Kenneth Graunke --- src/glsl/ast_to_hir.cpp | 286 +++++++++++++++++++++++++++----- src/glsl/glsl_parser_extras.cpp | 3 +- src/glsl/glsl_parser_extras.h | 10 +- 3 files changed, 254 insertions(+), 45 deletions(-) diff --git a/src/glsl/ast_to_hir.cpp b/src/glsl/ast_to_hir.cpp index 200d6596fab..ac090c315e7 100644 --- a/src/glsl/ast_to_hir.cpp +++ b/src/glsl/ast_to_hir.cpp @@ -3355,34 +3355,49 @@ ast_jump_statement::hir(exec_list *instructions, case ast_break: case ast_continue: - /* FINISHME: Handle switch-statements. They cannot contain 'continue', - * FINISHME: and they use a different IR instruction for 'break'. - */ - /* FINISHME: Correctly handle the nesting. If a switch-statement is - * FINISHME: inside a loop, a 'continue' is valid and will bind to the - * FINISHME: loop. - */ - if (state->loop_or_switch_nesting == NULL) { + if (mode == ast_continue && + state->loop_nesting_ast == NULL) { YYLTYPE loc = this->get_location(); _mesa_glsl_error(& loc, state, - "`%s' may only appear in a loop", - (mode == ast_break) ? "break" : "continue"); - } else { - ir_loop *const loop = state->loop_or_switch_nesting->as_loop(); + "continue may only appear in a loop"); + } else if (mode == ast_break && + state->loop_nesting_ast == NULL && + state->switch_nesting_ast == NULL) { + YYLTYPE loc = this->get_location(); - /* Inline the for loop expression again, since we don't know - * where near the end of the loop body the normal copy of it + _mesa_glsl_error(& loc, state, + "break may only appear in a loop or a switch"); + } else { + /* For a loop, inline the for loop expression again, + * since we don't know where near the end of + * the loop body the normal copy of it * is going to be placed. */ - if (mode == ast_continue && - state->loop_or_switch_nesting_ast->rest_expression) { - state->loop_or_switch_nesting_ast->rest_expression->hir(instructions, - state); + if (state->loop_nesting_ast != NULL && + mode == ast_continue && + state->loop_nesting_ast->rest_expression) { + state->loop_nesting_ast->rest_expression->hir(instructions, + state); } - if (loop != NULL) { - ir_loop_jump *const jump = + if (state->is_switch_innermost && + mode == ast_break) { + /* Force break out of switch by setting is_break switch state. + */ + ir_variable *const is_break_var = state->is_break_var; + ir_dereference_variable *const deref_is_break_var = + new(ctx) ir_dereference_variable(is_break_var); + ir_constant *const true_val = new(ctx) ir_constant(true); + ir_assignment *const set_break_var = + new(ctx) ir_assignment(deref_is_break_var, + true_val, + NULL); + + instructions->push_tail(set_break_var); + } + else { + ir_loop_jump *const jump = new(ctx) ir_loop_jump((mode == ast_break) ? ir_loop_jump::jump_break : ir_loop_jump::jump_continue); @@ -3449,52 +3464,235 @@ ir_rvalue * ast_switch_statement::hir(exec_list *instructions, struct _mesa_glsl_parse_state *state) { - // FINISHME + void *ctx = state; + + ir_rvalue *const test_expression = + this->test_expression->hir(instructions, state); + + /* From page 66 (page 55 of the PDF) of the GLSL 1.50 spec: + * + * "The type of init-expression in a switch statement must be a + * scalar integer." + * + * The checks are separated so that higher quality diagnostics can be + * generated for cases where the rule is violated. + */ + if (!test_expression->type->is_integer()) { + YYLTYPE loc = this->test_expression->get_location(); + + _mesa_glsl_error(& loc, + state, + "switch-statement expression must be scalar " + "integer"); + } + + /* Track the switch-statement nesting in a stack-like manner. + */ + ir_variable *saved_test_var = state->test_var; + ir_variable *saved_is_fallthru_var = state->is_fallthru_var; + + bool save_is_switch_innermost = state->is_switch_innermost; + ast_switch_statement *saved_nesting_ast = state->switch_nesting_ast; + + state->is_switch_innermost = true; + state->switch_nesting_ast = this; + + /* Initalize is_fallthru state to false. + */ + ir_rvalue *const is_fallthru_val = new (ctx) ir_constant(false); + state->is_fallthru_var = new(ctx) ir_variable(glsl_type::bool_type, + "switch_is_fallthru_tmp", + ir_var_temporary); + instructions->push_tail(state->is_fallthru_var); + + ir_dereference_variable *deref_is_fallthru_var = + new(ctx) ir_dereference_variable(state->is_fallthru_var); + instructions->push_tail(new(ctx) ir_assignment(deref_is_fallthru_var, + is_fallthru_val, + NULL)); + + /* Initalize is_break state to false. + */ + ir_rvalue *const is_break_val = new (ctx) ir_constant(false); + state->is_break_var = new(ctx) ir_variable(glsl_type::bool_type, + "switch_is_break_tmp", + ir_var_temporary); + instructions->push_tail(state->is_break_var); + + ir_dereference_variable *deref_is_break_var = + new(ctx) ir_dereference_variable(state->is_break_var); + instructions->push_tail(new(ctx) ir_assignment(deref_is_break_var, + is_break_val, + NULL)); + + /* Cache test expression. + */ + test_to_hir(instructions, state); + + /* Emit code for body of switch stmt. + */ + body->hir(instructions, state); + + /* Restore previous nesting before returning. + */ + state->switch_nesting_ast = saved_nesting_ast; + state->is_switch_innermost = save_is_switch_innermost; + + state->test_var = saved_test_var; + state->is_fallthru_var = saved_is_fallthru_var; + + /* Switch statements do not have r-values. + */ return NULL; } +void +ast_switch_statement::test_to_hir(exec_list *instructions, + struct _mesa_glsl_parse_state *state) +{ + void *ctx = state; + + /* Cache value of test expression. + */ + ir_rvalue *const test_val = + test_expression->hir(instructions, + state); + + state->test_var = new(ctx) ir_variable(glsl_type::int_type, + "switch_test_tmp", + ir_var_temporary); + ir_dereference_variable *deref_test_var = + new(ctx) ir_dereference_variable(state->test_var); + + instructions->push_tail(state->test_var); + instructions->push_tail(new(ctx) ir_assignment(deref_test_var, + test_val, + NULL)); +} + + ir_rvalue * ast_switch_body::hir(exec_list *instructions, - struct _mesa_glsl_parse_state *state) + struct _mesa_glsl_parse_state *state) { - // FINISHME + if (stmts != NULL) + stmts->hir(instructions, state); + + /* Switch bodies do not have r-values. + */ return NULL; } ir_rvalue * -ast_case_statement::hir(exec_list *instructions, - struct _mesa_glsl_parse_state *state) +ast_case_statement_list::hir(exec_list *instructions, + struct _mesa_glsl_parse_state *state) { - // FINISHME + foreach_list_typed (ast_case_statement, case_stmt, link, & this->cases) + case_stmt->hir(instructions, state); + + /* Case statements do not have r-values. + */ return NULL; } ir_rvalue * -ast_case_statement_list::hir(exec_list *instructions, - struct _mesa_glsl_parse_state *state) +ast_case_statement::hir(exec_list *instructions, + struct _mesa_glsl_parse_state *state) { - // FINISHME + labels->hir(instructions, state); + + /* Conditionally set fallthru state based on break state. + */ + ir_constant *const false_val = new(state) ir_constant(false); + ir_dereference_variable *const deref_is_fallthru_var = + new(state) ir_dereference_variable(state->is_fallthru_var); + ir_dereference_variable *const deref_is_break_var = + new(state) ir_dereference_variable(state->is_break_var); + ir_assignment *const reset_fallthru_on_break = + new(state) ir_assignment(deref_is_fallthru_var, + false_val, + deref_is_break_var); + instructions->push_tail(reset_fallthru_on_break); + + /* Guard case statements depending on fallthru state. + */ + ir_dereference_variable *const deref_fallthru_guard = + new(state) ir_dereference_variable(state->is_fallthru_var); + ir_if *const test_fallthru = new(state) ir_if(deref_fallthru_guard); + + foreach_list_typed (ast_node, stmt, link, & this->stmts) + stmt->hir(& test_fallthru->then_instructions, state); + + instructions->push_tail(test_fallthru); + + /* Case statements do not have r-values. + */ return NULL; } ir_rvalue * -ast_case_label::hir(exec_list *instructions, - struct _mesa_glsl_parse_state *state) +ast_case_label_list::hir(exec_list *instructions, + struct _mesa_glsl_parse_state *state) { - // FINISHME + foreach_list_typed (ast_case_label, label, link, & this->labels) + label->hir(instructions, state); + + /* Case labels do not have r-values. + */ return NULL; } ir_rvalue * -ast_case_label_list::hir(exec_list *instructions, - struct _mesa_glsl_parse_state *state) +ast_case_label::hir(exec_list *instructions, + struct _mesa_glsl_parse_state *state) { - // FINISHME + void *ctx = state; + + ir_dereference_variable *deref_fallthru_var = + new(ctx) ir_dereference_variable(state->is_fallthru_var); + + ir_rvalue *const true_val = new(ctx) ir_constant(true); + + /* If not default case, ... + */ + if (this->test_value != NULL) { + /* Conditionally set fallthru state based on + * comparison of cached test expression value to case label. + */ + ir_rvalue *const test_val = this->test_value->hir(instructions, state); + + ir_dereference_variable *deref_test_var = + new(ctx) ir_dereference_variable(state->test_var); + + ir_rvalue *const test_cond = new(ctx) ir_expression(ir_binop_all_equal, + glsl_type::bool_type, + test_val, + deref_test_var); + + ir_assignment *set_fallthru_on_test = + new(ctx) ir_assignment(deref_fallthru_var, + true_val, + test_cond); + + instructions->push_tail(set_fallthru_on_test); + } else { /* default case */ + /* Set falltrhu state. + */ + ir_assignment *set_fallthru = + new(ctx) ir_assignment(deref_fallthru_var, + true_val, + NULL); + + instructions->push_tail(set_fallthru); + } + + /* Case statements do not have r-values. + */ return NULL; } @@ -3552,13 +3750,17 @@ ast_iteration_statement::hir(exec_list *instructions, ir_loop *const stmt = new(ctx) ir_loop(); instructions->push_tail(stmt); - /* Track the current loop and / or switch-statement nesting. + /* Track the current loop nesting. */ - ir_instruction *const nesting = state->loop_or_switch_nesting; - ast_iteration_statement *nesting_ast = state->loop_or_switch_nesting_ast; + ast_iteration_statement *nesting_ast = state->loop_nesting_ast; - state->loop_or_switch_nesting = stmt; - state->loop_or_switch_nesting_ast = this; + state->loop_nesting_ast = this; + + /* Likewise, indicate that following code is closest to a loop, + * NOT closest to a switch. + */ + bool saved_is_switch_innermost = state->is_switch_innermost; + state->is_switch_innermost = false; if (mode != ast_do_while) condition_to_hir(stmt, state); @@ -3577,8 +3779,8 @@ ast_iteration_statement::hir(exec_list *instructions, /* Restore previous nesting before returning. */ - state->loop_or_switch_nesting = nesting; - state->loop_or_switch_nesting_ast = nesting_ast; + state->loop_nesting_ast = nesting_ast; + state->is_switch_innermost = saved_is_switch_innermost; /* Loops do not have r-values. */ diff --git a/src/glsl/glsl_parser_extras.cpp b/src/glsl/glsl_parser_extras.cpp index 1f3d23a8c73..23aadb143ef 100644 --- a/src/glsl/glsl_parser_extras.cpp +++ b/src/glsl/glsl_parser_extras.cpp @@ -50,7 +50,8 @@ _mesa_glsl_parse_state::_mesa_glsl_parse_state(struct gl_context *ctx, this->symbols = new(mem_ctx) glsl_symbol_table; this->info_log = ralloc_strdup(mem_ctx, ""); this->error = false; - this->loop_or_switch_nesting = NULL; + this->loop_nesting_ast = NULL; + this->switch_nesting_ast = NULL; this->num_builtins_to_link = 0; diff --git a/src/glsl/glsl_parser_extras.h b/src/glsl/glsl_parser_extras.h index 1f3404c9deb..dd932951f4e 100644 --- a/src/glsl/glsl_parser_extras.h +++ b/src/glsl/glsl_parser_extras.h @@ -149,8 +149,14 @@ struct _mesa_glsl_parse_state { bool all_invariant; /** Loop or switch statement containing the current instructions. */ - class ir_instruction *loop_or_switch_nesting; - class ast_iteration_statement *loop_or_switch_nesting_ast; + class ast_iteration_statement *loop_nesting_ast; + class ast_switch_statement *switch_nesting_ast; + bool is_switch_innermost; // if switch stmt is closest to break, ... + + /** Temporary variables needed for switch statement. */ + ir_variable *test_var; + ir_variable *is_fallthru_var; + ir_variable *is_break_var; /** List of structures defined in user code. */ const glsl_type **user_structures; -- 2.30.2