glsl: overhaul 'return' statement handling

A new node type (SLANG_OPER_RETURN_INLINED) is used to denote 'return'
statements inside inlined functions which need special handling.

All glean glsl1 tests pass for EmitContReturn=FALSE and TRUE.
This commit is contained in:
Brian Paul
2009-06-26 11:33:05 -06:00
parent aa48becb82
commit 8e6dd8bf79
3 changed files with 214 additions and 217 deletions
+204 -216
View File
@@ -818,6 +818,7 @@ _slang_is_noop(const slang_operation *oper)
/**
* Recursively search tree for a node of the given type.
*/
#if 0
static slang_operation *
_slang_find_node_type(slang_operation *oper, slang_operation_type type)
{
@@ -831,6 +832,7 @@ _slang_find_node_type(slang_operation *oper, slang_operation_type type)
}
return NULL;
}
#endif
/**
@@ -932,41 +934,43 @@ slang_resolve_variable(slang_operation *oper)
/**
* Generate code for "return expr;"
* Rewrite AST code for "return expression;".
*
* We return values from functions by assinging the returned value to
* the hidden __retVal variable which is an extra 'out' parameter we add
* to the function signature.
* This code basically converts "return expr;" into "__retVal = expr; return;"
*
* \return the new AST code.
*/
static slang_operation *
gen_return_expression(slang_assemble_ctx *A, slang_operation *oper)
gen_return_with_expression(slang_assemble_ctx *A, slang_operation *oper)
{
slang_operation *blockOper;
slang_operation *blockOper, *assignOper;
assert(oper->type == SLANG_OPER_RETURN);
blockOper = slang_operation_new(1);
blockOper->type = SLANG_OPER_BLOCK_NO_NEW_SCOPE;
blockOper->locals->outer_scope = oper->locals->outer_scope;
slang_operation_add_children(blockOper, 2);
/* if EmitContReturn:
* if (!__returnFlag) {
* build: __retVal = expr;
* }
* otherwise:
* build: __retVal = expr;
*/
{
slang_operation *assignOper;
if (A->UseReturnFlag) {
if (A->UseReturnFlag) {
/* Emit:
* {
* if (__notRetFlag)
* __retVal = expr;
* __notRetFlag = 0;
* }
*/
{
slang_operation *ifOper = slang_oper_child(blockOper, 0);
ifOper->type = SLANG_OPER_IF;
slang_operation_add_children(ifOper, 3);
{
slang_operation *cond = slang_oper_child(ifOper, 0);
cond->type = SLANG_OPER_IDENTIFIER;
cond->a_id = slang_atom_pool_atom(A->atoms, "__returnFlag");
cond->a_id = slang_atom_pool_atom(A->atoms, "__notRetFlag");
}
{
slang_operation *elseOper = slang_oper_child(ifOper, 2);
@@ -974,34 +978,103 @@ gen_return_expression(slang_assemble_ctx *A, slang_operation *oper)
}
assignOper = slang_oper_child(ifOper, 1);
}
else {
assignOper = slang_oper_child(blockOper, 0);
}
assignOper->type = SLANG_OPER_ASSIGN;
slang_operation_add_children(assignOper, 2);
{
slang_operation *lhs = slang_oper_child(assignOper, 0);
lhs->type = SLANG_OPER_IDENTIFIER;
lhs->a_id = slang_atom_pool_atom(A->atoms, "__retVal");
slang_operation *setOper = slang_oper_child(blockOper, 1);
setOper->type = SLANG_OPER_ASSIGN;
slang_operation_add_children(setOper, 2);
{
slang_operation *lhs = slang_oper_child(setOper, 0);
lhs->type = SLANG_OPER_IDENTIFIER;
lhs->a_id = slang_atom_pool_atom(A->atoms, "__notRetFlag");
}
{
slang_operation *rhs = slang_oper_child(setOper, 1);
slang_operation_literal_bool(rhs, GL_FALSE);
}
}
}
else {
/* Emit:
* {
* __retVal = expr;
* return_inlined;
* }
*/
assignOper = slang_oper_child(blockOper, 0);
{
slang_operation *rhs = slang_oper_child(assignOper, 1);
slang_operation_copy(rhs, &oper->children[0]);
slang_operation *returnOper = slang_oper_child(blockOper, 1);
returnOper->type = SLANG_OPER_RETURN_INLINED;
assert(returnOper->num_children == 0);
}
}
/* build: return; (with no return value) */
/* __retVal = expression; */
assignOper->type = SLANG_OPER_ASSIGN;
slang_operation_add_children(assignOper, 2);
{
slang_operation *returnOper = slang_oper_child(blockOper, 1);
returnOper->type = SLANG_OPER_RETURN; /* return w/ no value */
assert(returnOper->num_children == 0);
slang_operation *lhs = slang_oper_child(assignOper, 0);
lhs->type = SLANG_OPER_IDENTIFIER;
lhs->a_id = slang_atom_pool_atom(A->atoms, "__retVal");
}
{
slang_operation *rhs = slang_oper_child(assignOper, 1);
slang_operation_copy(rhs, &oper->children[0]);
}
///blockOper->locals->outer_scope = oper->locals->outer_scope;
/*slang_print_tree(blockOper, 0);*/
return blockOper;
}
/**
* Rewrite AST code for "return;" (no expression).
*/
static slang_operation *
gen_return_without_expression(slang_assemble_ctx *A, slang_operation *oper)
{
slang_operation *newRet;
assert(oper->type == SLANG_OPER_RETURN);
if (A->UseReturnFlag) {
/* Emit:
* __notRetFlag = 0;
*/
{
newRet = slang_operation_new(1);
newRet->locals->outer_scope = oper->locals->outer_scope;
newRet->type = SLANG_OPER_ASSIGN;
slang_operation_add_children(newRet, 2);
{
slang_operation *lhs = slang_oper_child(newRet, 0);
lhs->type = SLANG_OPER_IDENTIFIER;
lhs->a_id = slang_atom_pool_atom(A->atoms, "__notRetFlag");
}
{
slang_operation *rhs = slang_oper_child(newRet, 1);
slang_operation_literal_bool(rhs, GL_FALSE);
}
}
}
else {
/* Emit:
* return_inlined;
*/
newRet = slang_operation_new(1);
newRet->locals->outer_scope = oper->locals->outer_scope;
newRet->type = SLANG_OPER_RETURN_INLINED;
}
/*slang_print_tree(newRet, 0);*/
return newRet;
}
/**
* Replace particular variables (SLANG_OPER_IDENTIFIER) with new expressions.
@@ -1038,7 +1111,7 @@ slang_substitute(slang_assemble_ctx *A, slang_operation *oper,
GLuint i;
v = _slang_variable_locate(oper->locals, id, GL_TRUE);
if (!v) {
if (_mesa_strcmp((char *) oper->a_id, "__returnFlag"))
if (_mesa_strcmp((char *) oper->a_id, "__notRetFlag"))
_mesa_problem(NULL, "var %s not found!\n", (char *) oper->a_id);
return;
}
@@ -1069,37 +1142,22 @@ slang_substitute(slang_assemble_ctx *A, slang_operation *oper,
break;
case SLANG_OPER_RETURN:
/* do return replacement here too */
assert(oper->num_children == 0 || oper->num_children == 1);
if (oper->num_children == 1 && !_slang_is_noop(&oper->children[0])) {
{
slang_operation *newReturn;
/* check if function actually has a return type */
assert(A->CurFunction);
if (A->CurFunction->header.type.specifier.type == SLANG_SPEC_VOID) {
slang_info_log_error(A->log, "illegal return expression");
return;
}
/* generate new 'return' code' */
newReturn = gen_return_expression(A, oper);
if (slang_oper_child(oper, 0)->type == SLANG_OPER_VOID)
newReturn = gen_return_without_expression(A, oper);
else
newReturn = gen_return_with_expression(A, oper);
/* do substitutions on the "__retVal = expr" sub-tree */
slang_substitute(A, slang_oper_child(newReturn, 0),
/* do substitutions on the new 'return' code */
slang_substitute(A, newReturn,
substCount, substOld, substNew, GL_FALSE);
/* install new 'return' code */
slang_operation_copy(oper, newReturn);
slang_operation_destruct(newReturn);
}
else {
/* check if return value was expected */
assert(A->CurFunction);
if (A->CurFunction->header.type.specifier.type != SLANG_SPEC_VOID) {
slang_info_log_error(A->log, "return statement requires an expression");
return;
}
}
break;
case SLANG_OPER_ASSIGN:
@@ -1494,7 +1552,7 @@ slang_inline_function_call(slang_assemble_ctx * A, slang_function *fun,
/**
* Insert declaration for "bool __returnFlag" in given block operation.
* Insert declaration for "bool __notRetFlag" in given block operation.
* This is used when we can't emit "early" return statements in subroutines.
*/
static void
@@ -1508,44 +1566,31 @@ declare_return_flag(slang_assemble_ctx *A, slang_operation *oper)
decl = slang_operation_insert_child(oper, 1);
slang_generate_declaration(A, oper->locals, decl,
SLANG_SPEC_BOOL, "__returnFlag", GL_TRUE);
SLANG_SPEC_BOOL, "__notRetFlag", GL_TRUE);
/*slang_print_tree(oper, 0);*/
}
/**
* Replace 'return' with '__returnFlag = false'.
* This is used to remove 'early returns' from functions.
* Recursively replace instances of the old node type with the new type.
*/
static void
replace_return_with_flag_set(slang_assemble_ctx *A, slang_operation *oper)
replace_node_type(slang_operation *oper, slang_operation_type oldType,
slang_operation_type newType)
{
slang_atom id = slang_atom_pool_atom(A->atoms, "__returnFlag");
assert(oper->type == SLANG_OPER_RETURN);
GLuint i;
/* replace 'return' with __returnFlag = false' */
slang_operation_free_children(oper);
oper->type = SLANG_OPER_ASSIGN;
slang_operation_add_children(oper, 2);
{
slang_operation *lhs = slang_oper_child(oper, 0);
lhs->type = SLANG_OPER_IDENTIFIER;
lhs->a_id = id;
}
{
slang_operation *rhs = slang_oper_child(oper, 1);
slang_operation_literal_bool(rhs, GL_FALSE);
}
if (oper->type == oldType)
oper->type = newType;
{
slang_variable *var;
var = _slang_variable_locate(oper->locals, id, GL_TRUE);
assert(var);
for (i = 0; i < slang_oper_num_children(oper); i++) {
replace_node_type(slang_oper_child(oper, i), oldType, newType);
}
}
/**
* Test if the given function body has an "early return". That is, there's
* a 'return' statement that's not the very last instruction in the body.
@@ -1573,7 +1618,7 @@ _slang_gen_function_call(slang_assemble_ctx *A, slang_function *fun,
slang_operation *oper, slang_operation *dest)
{
slang_ir_node *n;
slang_operation *inlined;
slang_operation *instance;
slang_label *prevFuncEndLabel;
char name[200];
@@ -1582,9 +1627,14 @@ _slang_gen_function_call(slang_assemble_ctx *A, slang_function *fun,
A->curFuncEndLabel = _slang_label_new(name);
assert(A->curFuncEndLabel);
/*
* 'instance' is basically a copy of the function's body with various
* transformations.
*/
if (slang_is_asm_function(fun) && !dest) {
/* assemble assembly function - tree style */
inlined = slang_inline_asm_function(A, fun, oper);
instance = slang_inline_asm_function(A, fun, oper);
}
else {
/* non-assembly function */
@@ -1594,73 +1644,81 @@ _slang_gen_function_call(slang_assemble_ctx *A, slang_function *fun,
* 2. Generate a call to the "inline" code as a subroutine
*/
const GLboolean earlyReturn = has_early_return(fun->body);
slang_operation *ret = NULL;
if (earlyReturn && !A->EmitContReturn) {
A->UseReturnFlag = GL_TRUE;
}
inlined = slang_inline_function_call(A, fun, oper, dest);
if (!inlined)
instance = slang_inline_function_call(A, fun, oper, dest);
if (!instance)
return NULL;
ret = _slang_find_node_type(inlined, SLANG_OPER_RETURN);
if (ret) {
/* check if this is a "tail" return */
if (!earlyReturn) {
/* The only RETURN is the last stmt in the function, no-op it
* and inline the function body.
*/
ret->type = SLANG_OPER_NONE;
if (earlyReturn) {
/* The function we're calling has one or more 'return' statements
* that prevent us from inlining the function's code.
*
* In this case, change the function's body type from
* SLANG_OPER_BLOCK_NEW_SCOPE to SLANG_OPER_NON_INLINED_CALL.
* During code emit this will result in a true subroutine call.
*
* Also, convert SLANG_OPER_RETURN_INLINED nodes to SLANG_OPER_RETURN.
*/
slang_operation *callOper;
assert(instance->type == SLANG_OPER_BLOCK_NEW_SCOPE ||
instance->type == SLANG_OPER_SEQUENCE);
if (_slang_function_has_return_value(fun) && !dest) {
assert(instance->children[0].type == SLANG_OPER_VARIABLE_DECL);
assert(instance->children[2].type == SLANG_OPER_IDENTIFIER);
callOper = &instance->children[1];
}
else {
slang_operation *callOper;
/* The function we're calling has one or more 'return' statements.
* So, we can't truly inline this function because we need to
* implement 'return' with RET (and CAL).
* Nevertheless, we performed "inlining" to make a new instance
* of the function body to deal with static register allocation.
*/
assert(inlined->type == SLANG_OPER_BLOCK_NEW_SCOPE ||
inlined->type == SLANG_OPER_SEQUENCE);
if (_slang_function_has_return_value(fun) && !dest) {
assert(inlined->children[0].type == SLANG_OPER_VARIABLE_DECL);
assert(inlined->children[2].type == SLANG_OPER_IDENTIFIER);
callOper = &inlined->children[1];
}
else {
callOper = inlined;
}
if (A->UseReturnFlag) {
/* Early returns not supported. Create a _returnFlag variable
* that's set upon 'return' and tested elsewhere to no-op any
* remaining instructions in the subroutine.
*/
assert(callOper->type == SLANG_OPER_BLOCK_NEW_SCOPE ||
callOper->type == SLANG_OPER_SEQUENCE);
declare_return_flag(A, callOper);
}
callOper->type = SLANG_OPER_NON_INLINED_CALL;
callOper->fun = fun;
callOper->label = _slang_label_new_unique((char*) fun->header.a_name);
callOper = instance;
}
if (A->UseReturnFlag) {
/* Early returns not supported. Create a _returnFlag variable
* that's set upon 'return' and tested elsewhere to no-op any
* remaining instructions in the subroutine.
*/
assert(callOper->type == SLANG_OPER_BLOCK_NEW_SCOPE ||
callOper->type == SLANG_OPER_SEQUENCE);
declare_return_flag(A, callOper);
}
else {
/* We can emit real 'return' statements. If we generated any
* 'inline return' statements during function instantiation,
* change them back to regular 'return' statements.
*/
replace_node_type(instance, SLANG_OPER_RETURN_INLINED,
SLANG_OPER_RETURN);
}
callOper->type = SLANG_OPER_NON_INLINED_CALL;
callOper->fun = fun;
callOper->label = _slang_label_new_unique((char*) fun->header.a_name);
}
else {
/* If there are any 'return' statements remaining, they're at the
* very end of the function and can effectively become no-ops.
*/
replace_node_type(instance, SLANG_OPER_RETURN_INLINED,
SLANG_OPER_VOID);
}
}
if (!inlined)
if (!instance)
return NULL;
/* Replace the function call with the inlined block (or new CALL stmt) */
/* Replace the function call with the instance block (or new CALL stmt) */
slang_operation_destruct(oper);
*oper = *inlined;
_slang_free(inlined);
*oper = *instance;
_slang_free(instance);
#if 0
assert(inlined->locals);
printf("*** Inlined code for call to %s:\n",
(char*) fun->header.a_name);
assert(instance->locals);
printf("*** Inlined code for call to %s:\n", (char*) fun->header.a_name);
slang_print_tree(oper, 10);
printf("\n");
#endif
@@ -4005,98 +4063,26 @@ _slang_gen_return(slang_assemble_ctx * A, slang_operation *oper)
const GLboolean haveReturnValue
= (oper->num_children == 1 && oper->children[0].type != SLANG_OPER_VOID);
assert(oper->type == SLANG_OPER_RETURN);
assert(oper->type == SLANG_OPER_RETURN ||
oper->type == SLANG_OPER_RETURN_INLINED);
/* error checking */
assert(A->CurFunction);
if (haveReturnValue &&
A->CurFunction->header.type.specifier.type == SLANG_SPEC_VOID) {
slang_info_log_error(A->log, "illegal return expression");
return NULL;
}
else if (!haveReturnValue &&
A->CurFunction->header.type.specifier.type != SLANG_SPEC_VOID) {
slang_info_log_error(A->log, "return statement requires an expression");
return NULL;
}
if (oper->type == SLANG_OPER_RETURN) {
assert(A->CurFunction);
if (!haveReturnValue) {
if (A->EmitContReturn) {
return new_return(A->curFuncEndLabel);
if (haveReturnValue &&
A->CurFunction->header.type.specifier.type == SLANG_SPEC_VOID) {
slang_info_log_error(A->log, "illegal return expression");
return NULL;
}
else {
replace_return_with_flag_set(A, oper);
return _slang_gen_operation(A, oper);
else if (!haveReturnValue &&
A->CurFunction->header.type.specifier.type != SLANG_SPEC_VOID) {
slang_info_log_error(A->log, "return statement requires an expression");
return NULL;
}
}
else {
/*
* Convert from:
* return expr;
* To:
* __retVal = expr;
* return; // goto __endOfFunction
*/
slang_operation *assign;
slang_atom a_retVal;
slang_ir_node *n;
a_retVal = slang_atom_pool_atom(A->atoms, "__retVal");
assert(a_retVal);
#if 1 /* DEBUG */
{
slang_variable *v =
_slang_variable_locate(oper->locals, a_retVal, GL_TRUE);
if (!v) {
/* trying to return a value in a void-valued function */
return NULL;
}
}
#endif
/* XXX use the gen_return_expression() function here */
assign = slang_operation_new(1);
assign->type = SLANG_OPER_ASSIGN;
assign->num_children = 2;
assign->children = slang_operation_new(2);
/* lhs (__retVal) */
assign->children[0].type = SLANG_OPER_IDENTIFIER;
assign->children[0].a_id = a_retVal;
assign->children[0].locals->outer_scope = assign->locals;
/* rhs (expr) */
/* XXX we might be able to avoid this copy someday */
slang_operation_copy(&assign->children[1], &oper->children[0]);
/* assemble the new code */
if (A->EmitContReturn) {
n = new_seq(_slang_gen_operation(A, assign),
new_return(A->curFuncEndLabel));
}
else if (A->UseReturnFlag) {
/* set __returnFlag = false; */
slang_operation *setFlag = slang_operation_new(1);
setFlag->type = SLANG_OPER_ASSIGN;
setFlag->locals->outer_scope = oper->locals;
slang_operation_add_children(setFlag, 2);
{
slang_operation *lhs = slang_oper_child(setFlag, 0);
lhs->type = SLANG_OPER_IDENTIFIER;
lhs->a_id = slang_atom_pool_atom(A->atoms, "__returnFlag");
}
{
slang_operation *rhs = slang_oper_child(setFlag, 1);
slang_operation_literal_bool(rhs, GL_FALSE);
}
n = new_seq(_slang_gen_operation(A, assign),
_slang_gen_operation(A, setFlag));
slang_operation_delete(setFlag);
}
slang_operation_delete(assign);
return n;
}
return new_return(A->curFuncEndLabel);
}
@@ -4382,14 +4368,14 @@ _slang_gen_assignment(slang_assemble_ctx * A, slang_operation *oper)
return NULL;
}
/* check if we need to predicate this assignment based on __returnFlag */
/* check if we need to predicate this assignment based on __notRetFlag */
if ((var->is_global ||
var->type.qualifier == SLANG_QUAL_OUT ||
var->type.qualifier == SLANG_QUAL_INOUT) && A->UseReturnFlag) {
/* create predicate, used below */
pred = slang_operation_new(1);
pred->type = SLANG_OPER_IDENTIFIER;
pred->a_id = slang_atom_pool_atom(A->atoms, "__returnFlag");
pred->a_id = slang_atom_pool_atom(A->atoms, "__notRetFlag");
pred->locals->outer_scope = oper->locals->outer_scope;
}
}
@@ -4455,7 +4441,7 @@ _slang_gen_assignment(slang_assemble_ctx * A, slang_operation *oper)
}
if (n && pred) {
/* predicate the assignment code on __returnFlag */
/* predicate the assignment code on __notRetFlag */
slang_ir_node *top, *cond;
cond = _slang_gen_operation(A, pred);
@@ -4947,6 +4933,8 @@ _slang_gen_operation(slang_assemble_ctx * A, slang_operation *oper)
return _slang_gen_method_call(A, oper);
case SLANG_OPER_RETURN:
return _slang_gen_return(A, oper);
case SLANG_OPER_RETURN_INLINED:
return _slang_gen_return(A, oper);
case SLANG_OPER_LABEL:
return new_label(oper->label);
case SLANG_OPER_IDENTIFIER:
@@ -42,6 +42,7 @@ typedef enum slang_operation_type_
SLANG_OPER_CONTINUE, /* "continue" statement */
SLANG_OPER_DISCARD, /* "discard" (kill fragment) statement */
SLANG_OPER_RETURN, /* "return" [expr] */
SLANG_OPER_RETURN_INLINED, /* "return" [expr] from inlined function */
SLANG_OPER_LABEL, /* a jump target */
SLANG_OPER_EXPRESSION, /* [expr] */
SLANG_OPER_IF, /* "if" [0] then [1] else [2] */
+9 -1
View File
@@ -261,6 +261,7 @@ slang_print_tree(const slang_operation *op, int indent)
break;
case SLANG_OPER_BLOCK_NEW_SCOPE:
case SLANG_OPER_NON_INLINED_CALL:
spaces(indent);
printf("{{ // new scope locals=%p outer=%p: ",
(void *) op->locals,
@@ -348,6 +349,13 @@ slang_print_tree(const slang_operation *op, int indent)
slang_print_tree(&op->children[0], indent + 3);
break;
case SLANG_OPER_RETURN_INLINED:
spaces(indent);
printf("RETURN_INLINED\n");
if (op->num_children > 0)
slang_print_tree(&op->children[0], indent + 3);
break;
case SLANG_OPER_LABEL:
spaces(indent);
printf("LABEL %s\n", (char *) op->a_id);
@@ -478,7 +486,7 @@ slang_print_tree(const slang_operation *op, int indent)
(void *) scope,
(void *) op->locals,
(void *) op->locals->outer_scope);
assert(scope);
/*assert(scope);*/
}
}
break;