/* * Copyright © 2011 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /** * \file ir_function_detect_recursion.cpp * Determine whether a shader contains static recursion. * * Consider the (possibly disjoint) graph of function calls in a shader. If a * program contains recursion, this graph will contain a cycle. If a function * is part of a cycle, it will have a caller and it will have a callee (it * calls another function). * * To detect recursion, the function call graph is constructed. The graph is * repeatedly reduced by removing any function that either has no callees * (leaf functions) or has no caller. Eventually the only functions that * remain will be the functions in the cycles. * * The GLSL spec is a bit wishy-washy about recursion. * * From page 39 (page 45 of the PDF) of the GLSL 1.10 spec: * * "Behavior is undefined if recursion is used. Recursion means having any * function appearing more than once at any one time in the run-time stack * of function calls. That is, a function may not call itself either * directly or indirectly. Compilers may give diagnostic messages when * this is detectable at compile time, but not all such cases can be * detected at compile time." * * From page 79 (page 85 of the PDF): * * "22) Should recursion be supported? * * DISCUSSION: Probably not necessary, but another example of limiting * the language based on how it would directly map to hardware. One * thought is that recursion would benefit ray tracing shaders. On the * other hand, many recursion operations can also be implemented with the * user managing the recursion through arrays. RenderMan doesn't support * recursion. This could be added at a later date, if it proved to be * necessary. * * RESOLVED on September 10, 2002: Implementations are not required to * support recursion. * * CLOSED on September 10, 2002." * * From page 79 (page 85 of the PDF): * * "56) Is it an error for an implementation to support recursion if the * specification says recursion is not supported? * * ADDED on September 10, 2002. * * DISCUSSION: This issues is related to Issue (22). If we say that * recursion (or some other piece of functionality) is not supported, is * it an error for an implementation to support it? Perhaps the * specification should remain silent on these kind of things so that they * could be gracefully added later as an extension or as part of the * standard. * * RESOLUTION: Languages, in general, have programs that are not * well-formed in ways a compiler cannot detect. Portability is only * ensured for well-formed programs. Detecting recursion is an example of * this. The language will say a well-formed program may not recurse, but * compilers are not forced to detect that recursion may happen. * * CLOSED: November 29, 2002." * * In GLSL 1.10 the behavior of recursion is undefined. Compilers don't have * to reject shaders (at compile-time or link-time) that contain recursion. * Instead they could work, or crash, or kill a kitten. * * From page 44 (page 50 of the PDF) of the GLSL 1.20 spec: * * "Recursion is not allowed, not even statically. Static recursion is * present if the static function call graph of the program contains * cycles." * * This langauge clears things up a bit, but it still leaves a lot of * questions unanswered. * * - Is the error generated at compile-time or link-time? * * - Is it an error to have a recursive function that is never statically * called by main or any function called directly or indirectly by main? * Technically speaking, such a function is not in the "static function * call graph of the program" at all. * * \bug * If a shader has multiple cycles, this algorithm may erroneously complain * about functions that aren't in any cycle, but are in the part of the call * tree that connects them. For example, if the call graph consists of a * cycle between A and B, and a cycle between D and E, and B also calls C * which calls D, then this algorithm will report C as a function which "has * static recursion" even though it is not part of any cycle. * * A better algorithm for cycle detection that doesn't have this drawback can * be found here: * * http://en.wikipedia.org/wiki/Tarjan%E2%80%99s_strongly_connected_components_algorithm * * \author Ian Romanick */ #include "main/core.h" #include "ir.h" #include "glsl_parser_extras.h" #include "linker.h" #include "program/hash_table.h" #include "program.h" namespace { struct call_node : public exec_node { class function *func; }; class function { public: function(ir_function_signature *sig) : sig(sig) { /* empty */ } DECLARE_RALLOC_CXX_OPERATORS(function) ir_function_signature *sig; /** List of functions called by this function. */ exec_list callees; /** List of functions that call this function. */ exec_list callers; }; class has_recursion_visitor : public ir_hierarchical_visitor { public: has_recursion_visitor() : current(NULL) { progress = false; this->mem_ctx = ralloc_context(NULL); this->function_hash = hash_table_ctor(0, hash_table_pointer_hash, hash_table_pointer_compare); } ~has_recursion_visitor() { hash_table_dtor(this->function_hash); ralloc_free(this->mem_ctx); } function *get_function(ir_function_signature *sig) { function *f = (function *) hash_table_find(this->function_hash, sig); if (f == NULL) { f = new(mem_ctx) function(sig); hash_table_insert(this->function_hash, f, sig); } return f; } virtual ir_visitor_status visit_enter(ir_function_signature *sig) { this->current = this->get_function(sig); return visit_continue; } virtual ir_visitor_status visit_leave(ir_function_signature *sig) { (void) sig; this->current = NULL; return visit_continue; } virtual ir_visitor_status visit_enter(ir_call *call) { /* At global scope this->current will be NULL. Since there is no way to * call global scope, it can never be part of a cycle. Don't bother * adding calls from global scope to the graph. */ if (this->current == NULL) return visit_continue; function *const target = this->get_function(call->callee); /* Create a link from the caller to the callee. */ call_node *node = new(mem_ctx) call_node; node->func = target; this->current->callees.push_tail(node); /* Create a link from the callee to the caller. */ node = new(mem_ctx) call_node; node->func = this->current; target->callers.push_tail(node); return visit_continue; } function *current; struct hash_table *function_hash; void *mem_ctx; bool progress; }; } /* anonymous namespace */ static void destroy_links(exec_list *list, function *f) { foreach_list_safe(node, list) { struct call_node *n = (struct call_node *) node; /* If this is the right function, remove it. Note that the loop cannot * terminate now. There can be multiple links to a function if it is * either called multiple times or calls multiple times. */ if (n->func == f) n->remove(); } } /** * Remove a function if it has either no in or no out links */ static void remove_unlinked_functions(const void *key, void *data, void *closure) { has_recursion_visitor *visitor = (has_recursion_visitor *) closure; function *f = (function *) data; if (f->callers.is_empty() || f->callees.is_empty()) { while (!f->callers.is_empty()) { struct call_node *n = (struct call_node *) f->callers.pop_head(); destroy_links(& n->func->callees, f); } while (!f->callees.is_empty()) { struct call_node *n = (struct call_node *) f->callees.pop_head(); destroy_links(& n->func->callers, f); } hash_table_remove(visitor->function_hash, key); visitor->progress = true; } } static void emit_errors_unlinked(const void *key, void *data, void *closure) { struct _mesa_glsl_parse_state *state = (struct _mesa_glsl_parse_state *) closure; function *f = (function *) data; YYLTYPE loc; (void) key; char *proto = prototype_string(f->sig->return_type, f->sig->function_name(), &f->sig->parameters); memset(&loc, 0, sizeof(loc)); _mesa_glsl_error(&loc, state, "function `%s' has static recursion", proto); ralloc_free(proto); } static void emit_errors_linked(const void *key, void *data, void *closure) { struct gl_shader_program *prog = (struct gl_shader_program *) closure; function *f = (function *) data; (void) key; char *proto = prototype_string(f->sig->return_type, f->sig->function_name(), &f->sig->parameters); linker_error(prog, "function `%s' has static recursion.\n", proto); ralloc_free(proto); } void detect_recursion_unlinked(struct _mesa_glsl_parse_state *state, exec_list *instructions) { has_recursion_visitor v; /* Collect all of the information about which functions call which other * functions. */ v.run(instructions); /* Remove from the set all of the functions that either have no caller or * call no other functions. Repeat until no functions are removed. */ do { v.progress = false; hash_table_call_foreach(v.function_hash, remove_unlinked_functions, & v); } while (v.progress); /* At this point any functions still in the hash must be part of a cycle. */ hash_table_call_foreach(v.function_hash, emit_errors_unlinked, state); } void detect_recursion_linked(struct gl_shader_program *prog, exec_list *instructions) { has_recursion_visitor v; /* Collect all of the information about which functions call which other * functions. */ v.run(instructions); /* Remove from the set all of the functions that either have no caller or * call no other functions. Repeat until no functions are removed. */ do { v.progress = false; hash_table_call_foreach(v.function_hash, remove_unlinked_functions, & v); } while (v.progress); /* At this point any functions still in the hash must be part of a cycle. */ hash_table_call_foreach(v.function_hash, emit_errors_linked, prog); }