xref: /aosp_15_r20/external/mesa3d/src/compiler/glsl/gl_nir_detect_function_recursion.c (revision 6104692788411f58d303aa86923a9ff6ecaded22)
1 /*
2  * Copyright © 2011 Intel Corporation
3  * Copyright © 2024 Valve Corporation
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice (including the next
13  * paragraph) shall be included in all copies or substantial portions of the
14  * Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22  * DEALINGS IN THE SOFTWARE.
23  */
24 
25 /**
26  * Determine whether a shader contains static recursion.
27  *
28  * Consider the (possibly disjoint) graph of function calls in a shader.  If a
29  * program contains recursion, this graph will contain a cycle.  If a function
30  * is part of a cycle, it will have a caller and it will have a callee (it
31  * calls another function).
32  *
33  * To detect recursion, the function call graph is constructed.  The graph is
34  * repeatedly reduced by removing any function that either has no callees
35  * (leaf functions) or has no caller.  Eventually the only functions that
36  * remain will be the functions in the cycles.
37  *
38  * The GLSL spec is a bit wishy-washy about recursion.
39  *
40  * From page 39 (page 45 of the PDF) of the GLSL 1.10 spec:
41  *
42  *     "Behavior is undefined if recursion is used. Recursion means having any
43  *     function appearing more than once at any one time in the run-time stack
44  *     of function calls. That is, a function may not call itself either
45  *     directly or indirectly. Compilers may give diagnostic messages when
46  *     this is detectable at compile time, but not all such cases can be
47  *     detected at compile time."
48  *
49  * From page 79 (page 85 of the PDF):
50  *
51  *     "22) Should recursion be supported?
52  *
53  *      DISCUSSION: Probably not necessary, but another example of limiting
54  *      the language based on how it would directly map to hardware. One
55  *      thought is that recursion would benefit ray tracing shaders. On the
56  *      other hand, many recursion operations can also be implemented with the
57  *      user managing the recursion through arrays. RenderMan doesn't support
58  *      recursion. This could be added at a later date, if it proved to be
59  *      necessary.
60  *
61  *      RESOLVED on September 10, 2002: Implementations are not required to
62  *      support recursion.
63  *
64  *      CLOSED on September 10, 2002."
65  *
66  * From page 79 (page 85 of the PDF):
67  *
68  *     "56) Is it an error for an implementation to support recursion if the
69  *     specification says recursion is not supported?
70  *
71  *     ADDED on September 10, 2002.
72  *
73  *     DISCUSSION: This issues is related to Issue (22). If we say that
74  *     recursion (or some other piece of functionality) is not supported, is
75  *     it an error for an implementation to support it? Perhaps the
76  *     specification should remain silent on these kind of things so that they
77  *     could be gracefully added later as an extension or as part of the
78  *     standard.
79  *
80  *     RESOLUTION: Languages, in general, have programs that are not
81  *     well-formed in ways a compiler cannot detect. Portability is only
82  *     ensured for well-formed programs. Detecting recursion is an example of
83  *     this. The language will say a well-formed program may not recurse, but
84  *     compilers are not forced to detect that recursion may happen.
85  *
86  *     CLOSED: November 29, 2002."
87  *
88  * In GLSL 1.10 the behavior of recursion is undefined.  Compilers don't have
89  * to reject shaders (at compile-time or link-time) that contain recursion.
90  * Instead they could work, or crash.
91  *
92  * From page 44 (page 50 of the PDF) of the GLSL 1.20 spec:
93  *
94  *     "Recursion is not allowed, not even statically. Static recursion is
95  *     present if the static function call graph of the program contains
96  *     cycles."
97  *
98  * This langauge clears things up a bit, but it still leaves a lot of
99  * questions unanswered.
100  *
101  *     - Is the error generated at compile-time or link-time?
102  *
103  *     - Is it an error to have a recursive function that is never statically
104  *       called by main or any function called directly or indirectly by main?
105  *       Technically speaking, such a function is not in the "static function
106  *       call graph of the program" at all.
107  *
108  * \bug
109  * If a shader has multiple cycles, this algorithm may erroneously complain
110  * about functions that aren't in any cycle, but are in the part of the call
111  * tree that connects them.  For example, if the call graph consists of a
112  * cycle between A and B, and a cycle between D and E, and B also calls C
113  * which calls D, then this algorithm will report C as a function which "has
114  * static recursion" even though it is not part of any cycle.
115  *
116  * A better algorithm for cycle detection that doesn't have this drawback can
117  * be found here:
118  *
119  * http://en.wikipedia.org/wiki/Tarjan%E2%80%99s_strongly_connected_components_algorithm
120  *
121  */
122 
123 #include "gl_nir_linker.h"
124 #include "linker_util.h"
125 #include "nir.h"
126 #include "util/hash_table.h"
127 
128 struct function_state {
129    nir_function *sig;
130 
131    /** List of functions called by this function. */
132    struct list_head callees;
133 
134    /** List of functions that call this function. */
135    struct list_head callers;
136 };
137 
138 struct has_recursion_state {
139    struct hash_table *function_hash;
140    bool progress;
141 };
142 
143 struct call_node {
144    struct list_head call_link;
145 
146    struct function_state *func;
147 };
148 
149 static struct function_state *
get_function(void * mem_ctx,nir_function * function_sig,struct hash_table * function_hash)150 get_function(void *mem_ctx, nir_function *function_sig,
151              struct hash_table *function_hash)
152 {
153       struct function_state *f;
154       struct hash_entry *entry =
155          _mesa_hash_table_search(function_hash, function_sig);
156       if (entry == NULL) {
157          f = ralloc(mem_ctx, struct function_state);
158          f->sig = function_sig;
159          list_inithead(&f->callers);
160          list_inithead(&f->callees);
161          _mesa_hash_table_insert(function_hash, function_sig, f);
162       } else {
163          f = (struct function_state *) entry->data;
164       }
165 
166       return f;
167 }
168 
169 static void
find_recursion(void * mem_ctx,nir_shader * shader,struct hash_table * function_hash)170 find_recursion(void *mem_ctx, nir_shader *shader,
171                struct hash_table *function_hash)
172 {
173    nir_foreach_function_impl(impl, shader) {
174       struct function_state *current =
175           get_function(mem_ctx, impl->function, function_hash);
176 
177       nir_foreach_block(block, impl) {
178          nir_foreach_instr(instr, block) {
179             if (instr->type == nir_instr_type_call) {
180                nir_call_instr *call = nir_instr_as_call(instr);
181                struct function_state *target =
182                   get_function(mem_ctx, call->callee, function_hash);
183 
184                /* Create a link from the caller to the callee. */
185                struct call_node *node = ralloc(mem_ctx, struct call_node);
186                node->func = target;
187                list_addtail(&node->call_link, &current->callees);
188 
189                /* Create a link from the callee to the caller. */
190                node = ralloc(mem_ctx, struct call_node);
191                node->func = current;
192                list_addtail(&node->call_link, &target->callers);
193             }
194          }
195       }
196    }
197 }
198 
199 /**
200  * Generate a ralloced string representing the prototype of the function.
201  */
202 static char *
prototype_string(nir_function * sig)203 prototype_string(nir_function *sig)
204 {
205    char *str = NULL;
206 
207    unsigned i = 0;
208    if (sig->params && sig->params[0].is_return) {
209       str = ralloc_asprintf(NULL, "%s ",
210                             glsl_get_type_name(sig->params[i].type));
211       i = 1;
212    }
213 
214    ralloc_asprintf_append(&str, "%s(", sig->name);
215 
216    const char *comma = "";
217    for ( ; i < sig->num_params; i++) {
218       ralloc_asprintf_append(&str, "%s%s", comma,
219                              glsl_get_type_name(sig->params[i].type));
220       comma = ", ";
221    }
222 
223    ralloc_strcat(&str, ")");
224    return str;
225 }
226 
227 static void
destroy_links(struct list_head * list,struct function_state * f)228 destroy_links(struct list_head *list, struct function_state *f)
229 {
230    list_for_each_entry_safe(struct call_node, n, list, call_link) {
231       /* If this is the right function, remove it.  Note that the loop cannot
232        * terminate now.  There can be multiple links to a function if it is
233        * either called multiple times or calls multiple times.
234        */
235       if (n->func == f)
236          list_del(&n->call_link);
237    }
238 }
239 
240 /**
241  * Remove a function if it has either no in or no out links
242  */
243 static void
remove_unlinked_functions(const void * key,void * data,void * closure)244 remove_unlinked_functions(const void *key, void *data, void *closure)
245 {
246    struct has_recursion_state *state = (struct has_recursion_state *) closure;
247    struct function_state *f = (struct function_state *) data;
248 
249    if (list_is_empty(&f->callers) || list_is_empty(&f->callees)) {
250       list_for_each_entry_safe(struct call_node, n, &f->callers, call_link) {
251          list_del(&n->call_link);
252          ralloc_free(n);
253       }
254 
255       list_for_each_entry_safe(struct call_node, n, &f->callees, call_link) {
256          destroy_links(&n->func->callers, f);
257       }
258 
259       struct hash_entry *entry =
260          _mesa_hash_table_search(state->function_hash, key);
261       _mesa_hash_table_remove(state->function_hash, entry);
262       state->progress = true;
263    }
264 }
265 
266 static void
emit_errors_linked(const void * key,void * data,void * closure)267 emit_errors_linked(const void *key, void *data, void *closure)
268 {
269    struct gl_shader_program *prog =
270       (struct gl_shader_program *) closure;
271    struct function_state *f = (struct function_state *) data;
272 
273    (void) key;
274 
275    char *proto = prototype_string(f->sig);
276 
277    linker_error(prog, "function `%s' has static recursion.\n", proto);
278    ralloc_free(proto);
279 }
280 
281 void
gl_nir_detect_recursion_linked(struct gl_shader_program * prog,nir_shader * shader)282 gl_nir_detect_recursion_linked(struct gl_shader_program *prog,
283                                nir_shader *shader)
284 {
285    void *mem_ctx = ralloc_context(NULL);
286    struct hash_table *function_hash =
287       _mesa_pointer_hash_table_create(mem_ctx);
288 
289    /* Collect all of the information about which functions call which other
290     * functions.
291     */
292    find_recursion(mem_ctx, shader, function_hash);
293 
294    /* Remove from the set all of the functions that either have no caller or
295     * call no other functions.  Repeat until no functions are removed.
296     */
297    struct has_recursion_state state;
298    state.function_hash = function_hash;
299    do {
300       state.progress = false;
301       hash_table_call_foreach(state.function_hash, remove_unlinked_functions,
302                               &state);
303    } while (state.progress);
304 
305 
306    /* At this point any functions still in the hash must be part of a cycle.
307     */
308    hash_table_call_foreach(state.function_hash, emit_errors_linked, prog);
309 
310    ralloc_free(mem_ctx);
311 }
312