| /* |
| * 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 <ian.d.romanick@intel.com> |
| */ |
| #include "main/core.h" |
| #include "ir.h" |
| #include "glsl_parser_extras.h" |
| #include "linker.h" |
| #include "program/hash_table.h" |
| #include "program.h" |
| |
| struct call_node : public exec_node { |
| class function *func; |
| }; |
| |
| class function { |
| public: |
| function(ir_function_signature *sig) |
| : sig(sig) |
| { |
| /* empty */ |
| } |
| |
| |
| /* Callers of this ralloc-based new need not call delete. It's |
| * easier to just ralloc_free 'ctx' (or any of its ancestors). */ |
| static void* operator new(size_t size, void *ctx) |
| { |
| void *node; |
| |
| node = ralloc_size(ctx, size); |
| assert(node != NULL); |
| |
| return node; |
| } |
| |
| /* If the user *does* call delete, that's OK, we will just |
| * ralloc_free in that case. */ |
| static void operator delete(void *node) |
| { |
| ralloc_free(node); |
| } |
| |
| 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) |
| { |
| 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; |
| }; |
| |
| 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); |
| prog->LinkStatus = false; |
| } |
| |
| |
| 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); |
| } |