5f5c12774c9a30bc68262a23dda1315e75f54f72
[xonotic/gmqcc.git] / fold.c
1 /*
2  * Copyright (C) 2012, 2013
3  *     Dale Weiler
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a copy of
6  * this software and associated documentation files (the "Software"), to deal in
7  * the Software without restriction, including without limitation the rights to
8  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9  * of the Software, and to permit persons to whom the Software is furnished to do
10  * so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in all
13  * copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21  * SOFTWARE.
22  */
23 #include <string.h>
24 #include <math.h>
25
26 #include "ast.h"
27 #include "parser.h"
28
29 #define FOLD_STRING_UNTRANSLATE_HTSIZE 1024
30 #define FOLD_STRING_DOTRANSLATE_HTSIZE 1024
31
32 /*
33  * There is two stages to constant folding in GMQCC: there is the parse
34  * stage constant folding, where, witht he help of the AST, operator
35  * usages can be constant folded. Then there is the constant folding
36  * in the IR for things like eliding if statements, can occur.
37  * 
38  * This file is thus, split into two parts.
39  */
40
41 #define isfloat(X)      (((ast_expression*)(X))->vtype == TYPE_FLOAT)
42 #define isvector(X)     (((ast_expression*)(X))->vtype == TYPE_VECTOR)
43 #define isstring(X)     (((ast_expression*)(X))->vtype == TYPE_STRING)
44 #define isfloats(X,Y)   (isfloat  (X) && isfloat (Y))
45
46 /*
47  * Implementation of basic vector math for vec3_t, for trivial constant
48  * folding.
49  * 
50  * TODO: gcc/clang hinting for autovectorization
51  */
52 static GMQCC_INLINE vec3_t vec3_add(vec3_t a, vec3_t b) {
53     vec3_t out;
54     out.x = a.x + b.x;
55     out.y = a.y + b.y;
56     out.z = a.z + b.z;
57     return out;
58 }
59
60 static GMQCC_INLINE vec3_t vec3_sub(vec3_t a, vec3_t b) {
61     vec3_t out;
62     out.x = a.x + b.x;
63     out.y = a.y + b.y;
64     out.z = a.z + b.z;
65     return out;
66 }
67
68 static GMQCC_INLINE vec3_t vec3_neg(vec3_t a) {
69     vec3_t out;
70     out.x = -a.x;
71     out.y = -a.y;
72     out.z = -a.z;
73     return out;
74 }
75
76 static GMQCC_INLINE vec3_t vec3_xor(vec3_t a, vec3_t b) {
77     vec3_t out;
78     out.x = (qcfloat_t)((qcint_t)a.x ^ (qcint_t)b.x);
79     out.y = (qcfloat_t)((qcint_t)a.y ^ (qcint_t)b.y);
80     out.z = (qcfloat_t)((qcint_t)a.z ^ (qcint_t)b.z);
81     return out;
82 }
83
84 static GMQCC_INLINE vec3_t vec3_xorvf(vec3_t a, qcfloat_t b) {
85     vec3_t out;
86     out.x = (qcfloat_t)((qcint_t)a.x ^ (qcint_t)b);
87     out.y = (qcfloat_t)((qcint_t)a.y ^ (qcint_t)b);
88     out.z = (qcfloat_t)((qcint_t)a.z ^ (qcint_t)b);
89     return out;
90 }
91
92 static GMQCC_INLINE qcfloat_t vec3_mulvv(vec3_t a, vec3_t b) {
93     return (a.x * b.x + a.y * b.y + a.z * b.z);
94 }
95
96 static GMQCC_INLINE vec3_t vec3_mulvf(vec3_t a, qcfloat_t b) {
97     vec3_t out;
98     out.x = a.x * b;
99     out.y = a.y * b;
100     out.z = a.z * b;
101     return out;
102 }
103
104 static GMQCC_INLINE bool vec3_cmp(vec3_t a, vec3_t b) {
105     return a.x == b.x &&
106            a.y == b.y &&
107            a.z == b.z;
108 }
109
110 static GMQCC_INLINE vec3_t vec3_create(float x, float y, float z) {
111     vec3_t out;
112     out.x = x;
113     out.y = y;
114     out.z = z;
115     return out;
116 }
117
118 static GMQCC_INLINE qcfloat_t vec3_notf(vec3_t a) {
119     return (!a.x && !a.y && !a.z);
120 }
121
122 static GMQCC_INLINE bool vec3_pbool(vec3_t a) {
123     return (a.x && a.y && a.z);
124 }
125
126 static GMQCC_INLINE bool fold_can_1(const ast_value *val) {
127     return  (ast_istype((ast_expression*)val, ast_value) && val->hasvalue && val->cvq == CV_CONST && ((ast_expression*)val)->vtype != TYPE_FUNCTION);
128 }
129
130 static GMQCC_INLINE bool fold_can_2(const ast_value *v1, const ast_value *v2) {
131     return fold_can_1(v1) && fold_can_1(v2);
132 }
133
134 static lex_ctx_t fold_ctx(fold_t *fold) {
135     lex_ctx_t ctx;
136     if (fold->parser->lex)
137         return parser_ctx(fold->parser);
138
139     memset(&ctx, 0, sizeof(ctx));
140     return ctx;
141 }
142
143 static GMQCC_INLINE bool fold_immediate_true(fold_t *fold, ast_value *v) {
144     switch (v->expression.vtype) {
145         case TYPE_FLOAT:
146             return !!v->constval.vfloat;
147         case TYPE_INTEGER:
148             return !!v->constval.vint;
149         case TYPE_VECTOR: 
150             if (OPTS_FLAG(CORRECT_LOGIC))
151                 return vec3_pbool(v->constval.vvec);
152             return !!v->constval.vvec.x;
153         case TYPE_STRING:
154             if (!v->constval.vstring)
155                 return false;
156             if (OPTS_FLAG(TRUE_EMPTY_STRINGS))
157                 return true;
158             return !!v->constval.vstring[0];
159         default:
160             compile_error(fold_ctx(fold), "internal error: fold_immediate_true on invalid type");
161             break;
162     }
163     return !!v->constval.vfunc;
164 }
165
166 #define fold_immvalue_float(E)  ((E)->constval.vfloat)
167 #define fold_immvalue_vector(E) ((E)->constval.vvec)
168 #define fold_immvalue_string(E) ((E)->constval.vstring)
169
170 fold_t *fold_init(parser_t *parser) {
171     fold_t *fold                 = (fold_t*)mem_a(sizeof(fold_t));
172     fold->parser                 = parser;
173     fold->imm_float              = NULL;
174     fold->imm_vector             = NULL;
175     fold->imm_string             = NULL;
176     fold->imm_string_untranslate = util_htnew(FOLD_STRING_UNTRANSLATE_HTSIZE);
177     fold->imm_string_dotranslate = util_htnew(FOLD_STRING_DOTRANSLATE_HTSIZE);
178
179     /*
180      * prime the tables with common constant values at constant
181      * locations.
182      */
183     (void)fold_constgen_float (fold,  0.0f);
184     (void)fold_constgen_float (fold,  1.0f);
185     (void)fold_constgen_float (fold, -1.0f);
186
187     (void)fold_constgen_vector(fold, vec3_create(0.0f, 0.0f, 0.0f));
188
189     return fold;
190 }
191
192 bool fold_generate(fold_t *fold, ir_builder *ir) {
193     /* generate globals for immediate folded values */
194     size_t     i;
195     ast_value *cur;
196
197     for (i = 0; i < vec_size(fold->imm_float);   ++i)
198         if (!ast_global_codegen ((cur = fold->imm_float[i]), ir, false)) goto err;
199     for (i = 0; i < vec_size(fold->imm_vector);  ++i)
200         if (!ast_global_codegen((cur = fold->imm_vector[i]), ir, false)) goto err;
201     for (i = 0; i < vec_size(fold->imm_string);  ++i)
202         if (!ast_global_codegen((cur = fold->imm_string[i]), ir, false)) goto err;
203
204     return true;
205
206 err:
207     con_out("failed to generate global %s\n", cur->name);
208     ir_builder_delete(ir);
209     return false;
210 }
211
212 void fold_cleanup(fold_t *fold) {
213     size_t i;
214
215     for (i = 0; i < vec_size(fold->imm_float);  ++i) ast_delete(fold->imm_float[i]);
216     for (i = 0; i < vec_size(fold->imm_vector); ++i) ast_delete(fold->imm_vector[i]);
217     for (i = 0; i < vec_size(fold->imm_string); ++i) ast_delete(fold->imm_string[i]);
218
219     vec_free(fold->imm_float);
220     vec_free(fold->imm_vector);
221     vec_free(fold->imm_string);
222
223     util_htdel(fold->imm_string_untranslate);
224     util_htdel(fold->imm_string_dotranslate);
225
226     mem_d(fold);
227 }
228
229 ast_expression *fold_constgen_float(fold_t *fold, qcfloat_t value) {
230     ast_value  *out = NULL;
231     size_t      i;
232
233     for (i = 0; i < vec_size(fold->imm_float); i++) {
234         if (fold->imm_float[i]->constval.vfloat == value)
235             return (ast_expression*)fold->imm_float[i];
236     }
237
238     out                  = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_FLOAT);
239     out->cvq             = CV_CONST;
240     out->hasvalue        = true;
241     out->constval.vfloat = value;
242
243     vec_push(fold->imm_float, out);
244
245     return (ast_expression*)out;
246 }
247
248 ast_expression *fold_constgen_vector(fold_t *fold, vec3_t value) {
249     ast_value *out;
250     size_t     i;
251
252     for (i = 0; i < vec_size(fold->imm_vector); i++) {
253         if (vec3_cmp(fold->imm_vector[i]->constval.vvec, value))
254             return (ast_expression*)fold->imm_vector[i];
255     }
256
257     out                = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_VECTOR);
258     out->cvq           = CV_CONST;
259     out->hasvalue      = true;
260     out->constval.vvec = value;
261
262     vec_push(fold->imm_vector, out);
263
264     return (ast_expression*)out;
265 }
266
267 ast_expression *fold_constgen_string(fold_t *fold, const char *str, bool translate) {
268     hash_table_t *table = (translate) ? fold->imm_string_untranslate : fold->imm_string_dotranslate;
269     ast_value    *out   = NULL;
270     size_t        hash  = util_hthash(table, str);
271
272     if ((out = (ast_value*)util_htgeth(table, str, hash)))
273         return (ast_expression*)out;
274
275     if (translate) {
276         char name[32];
277         util_snprintf(name, sizeof(name), "dotranslate_%lu", (unsigned long)(fold->parser->translated++));
278         out                    = ast_value_new(parser_ctx(fold->parser), name, TYPE_STRING);
279         out->expression.flags |= AST_FLAG_INCLUDE_DEF; /* def needs to be included for translatables */
280     } else
281         out                    = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_STRING);
282
283     out->cvq              = CV_CONST;
284     out->hasvalue         = true;
285     out->isimm            = true;
286     out->constval.vstring = parser_strdup(str);
287
288     vec_push(fold->imm_string, out);
289     util_htseth(table, str, hash, out);
290
291     return (ast_expression*)out;
292 }
293
294
295 static GMQCC_INLINE ast_expression *fold_op_mul_vec(fold_t *fold, vec3_t vec, ast_value *sel, const char *set) {
296     /*
297      * vector-component constant folding works by matching the component sets
298      * to eliminate expensive operations on whole-vectors (3 components at runtime).
299      * to achive this effect in a clean manner this function generalizes the 
300      * values through the use of a set paramater, which is used as an indexing method
301      * for creating the elided ast binary expression.
302      *
303      * Consider 'n 0 0' where y, and z need to be tested for 0, and x is
304      * used as the value in a binary operation generating an INSTR_MUL instruction
305      * to acomplish the indexing of the correct component value we use set[0], set[1], set[2]
306      * as x, y, z, where the values of those operations return 'x', 'y', 'z'. Because
307      * of how ASCII works we can easily deliniate:
308      * vec.z is the same as set[2]-'x' for when set[2] is 'z', 'z'-'x' results in a
309      * literal value of 2, using this 2, we know that taking the address of vec->x (float)
310      * and indxing it with this literal will yeild the immediate address of that component
311      * 
312      * Of course more work needs to be done to generate the correct index for the ast_member_new
313      * call, which is no problem: set[0]-'x' suffices that job.
314      */
315     qcfloat_t x = (&vec.x)[set[0]-'x'];
316     qcfloat_t y = (&vec.x)[set[1]-'x'];
317     qcfloat_t z = (&vec.x)[set[2]-'x'];
318
319     if (!y && !z) {
320         ast_expression *out;
321         ++opts_optimizationcount[OPTIM_VECTOR_COMPONENTS];
322         out                        = (ast_expression*)ast_member_new(fold_ctx(fold), (ast_expression*)sel, set[0]-'x', NULL);
323         out->node.keep             = false;
324         ((ast_member*)out)->rvalue = true;
325         if (!x != -1)
326             return (ast_expression*)ast_binary_new(fold_ctx(fold), INSTR_MUL_F, fold_constgen_float(fold, x), out);
327     }
328     return NULL;
329 }
330
331
332 static GMQCC_INLINE ast_expression *fold_op_neg(fold_t *fold, ast_value *a) {
333     if (isfloat(a)) {
334         if (fold_can_1(a))
335             return fold_constgen_float(fold, -fold_immvalue_float(a));
336     } else if (isvector(a)) {
337         if (fold_can_1(a))
338             return fold_constgen_vector(fold, vec3_neg(fold_immvalue_vector(a)));
339     }
340     return NULL;
341 }
342
343 static GMQCC_INLINE ast_expression *fold_op_not(fold_t *fold, ast_value *a) {
344     if (isfloat(a)) {
345         if (fold_can_1(a))
346             return fold_constgen_float(fold, !fold_immvalue_float(a));
347     } else if (isvector(a)) {
348         if (fold_can_1(a))
349             return fold_constgen_float(fold, vec3_notf(fold_immvalue_vector(a)));
350     } else if (isstring(a)) {
351         if (fold_can_1(a)) {
352             if (OPTS_FLAG(TRUE_EMPTY_STRINGS))
353                 return fold_constgen_float(fold, !fold_immvalue_string(a));
354             else
355                 return fold_constgen_float(fold, !fold_immvalue_string(a) || !*fold_immvalue_string(a));
356         }
357     }
358     return NULL;
359 }
360
361 static GMQCC_INLINE ast_expression *fold_op_add(fold_t *fold, ast_value *a, ast_value *b) {
362     if (isfloat(a)) {
363         if (fold_can_2(a, b))
364             return fold_constgen_float(fold, fold_immvalue_float(a) + fold_immvalue_float(b));
365     } else if (isvector(a)) {
366         if (fold_can_2(a, b))
367             return fold_constgen_vector(fold, vec3_add(fold_immvalue_vector(a), fold_immvalue_vector(b)));
368     }
369     return NULL;
370 }
371
372 static GMQCC_INLINE ast_expression *fold_op_sub(fold_t *fold, ast_value *a, ast_value *b) {
373     if (isfloat(a)) {
374         if (fold_can_2(a, b))
375             return fold_constgen_float(fold, fold_immvalue_float(a) - fold_immvalue_float(b));
376     } else if (isvector(a)) {
377         if (fold_can_2(a, b))
378             return fold_constgen_vector(fold, vec3_sub(fold_immvalue_vector(a), fold_immvalue_vector(b)));
379     }
380     return NULL;
381 }
382
383 static GMQCC_INLINE ast_expression *fold_op_mul(fold_t *fold, ast_value *a, ast_value *b) {
384     if (isfloat(a)) {
385         if (isfloat(b) && fold_can_2(a, b))
386             return fold_constgen_vector(fold, vec3_mulvf(fold_immvalue_vector(b), fold_immvalue_float(a)));
387         else if (fold_can_2(a, b))
388             return fold_constgen_float(fold, fold_immvalue_float(a) * fold_immvalue_float(b));
389     } else if (isvector(a)) {
390         if (isfloat(b) && fold_can_2(a, b)) {
391             return fold_constgen_vector(fold, vec3_mulvf(fold_immvalue_vector(a), fold_immvalue_float(b)));
392         } else {
393             if (fold_can_2(a, b)) {
394                 return fold_constgen_float(fold, vec3_mulvv(fold_immvalue_vector(a), fold_immvalue_vector(b)));
395             } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(a)) {
396                 ast_expression *out;
397                 if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "xyz"))) return out;
398                 if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "yxz"))) return out;
399                 if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "zxy"))) return out;
400             } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(b)) {
401                 ast_expression *out;
402                 if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "xyz"))) return out;
403                 if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "yxz"))) return out;
404                 if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "zxy"))) return out;
405             }
406         }
407     }
408     return NULL;
409 }
410
411 static GMQCC_INLINE ast_expression *fold_op_div(fold_t *fold, ast_value *a, ast_value *b) {
412     if (isfloat(a)) {
413         if (fold_can_2(a, b))
414             return fold_constgen_float(fold, fold_immvalue_float(a) / fold_immvalue_float(b));
415     } else if (isvector(a)) {
416         if (fold_can_2(a, b))
417             return fold_constgen_vector(fold, vec3_mulvf(fold_immvalue_vector(a), 1.0f / fold_immvalue_float(b)));
418         else if (fold_can_1(b))
419             return fold_constgen_float (fold, 1.0f / fold_immvalue_float(b));
420     }
421     return NULL;
422 }
423
424 static GMQCC_INLINE ast_expression *fold_op_mod(fold_t *fold, ast_value *a, ast_value *b) {
425     if (fold_can_2(a, b))
426         return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) % ((qcint_t)fold_immvalue_float(b))));
427     return NULL;
428 }
429
430 static GMQCC_INLINE ast_expression *fold_op_bor(fold_t *fold, ast_value *a, ast_value *b) {
431     if (fold_can_2(a, b))
432         return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) | ((qcint_t)fold_immvalue_float(b))));
433     return NULL;
434 }
435
436 static GMQCC_INLINE ast_expression *fold_op_band(fold_t *fold, ast_value *a, ast_value *b) {
437     if (fold_can_2(a, b))
438         return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) & ((qcint_t)fold_immvalue_float(b))));
439     return NULL;
440 }
441
442 static GMQCC_INLINE ast_expression *fold_op_xor(fold_t *fold, ast_value *a, ast_value *b) {
443     if (isfloat(a)) {
444         if (fold_can_2(a, b))
445             return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) ^ ((qcint_t)fold_immvalue_float(b))));
446     } else {
447         if (isvector(b)) {
448             if (fold_can_2(a, b))
449                 return fold_constgen_vector(fold, vec3_xor(fold_immvalue_vector(a), fold_immvalue_vector(b)));
450         } else {
451             if (fold_can_2(a, b))
452                 return fold_constgen_vector(fold, vec3_xorvf(fold_immvalue_vector(a), fold_immvalue_float(b)));
453         }
454     }
455     return NULL;
456 }
457
458 static GMQCC_INLINE ast_expression *fold_op_lshift(fold_t *fold, ast_value *a, ast_value *b) {
459     if (fold_can_2(a, b) && isfloats(a, b))
460         return fold_constgen_float(fold, (qcfloat_t)((qcuint_t)(fold_immvalue_float(a)) << (qcuint_t)(fold_immvalue_float(b))));
461     return NULL;
462 }
463
464 static GMQCC_INLINE ast_expression *fold_op_rshift(fold_t *fold, ast_value *a, ast_value *b) {
465     if (fold_can_2(a, b) && isfloats(a, b))
466         return fold_constgen_float(fold, (qcfloat_t)((qcuint_t)(fold_immvalue_float(a)) >> (qcuint_t)(fold_immvalue_float(b))));
467     return NULL;
468 }
469
470 static GMQCC_INLINE ast_expression *fold_op_andor(fold_t *fold, ast_value *a, ast_value *b, float or) {
471     if (fold_can_2(a, b)) {
472         if (OPTS_FLAG(PERL_LOGIC)) {
473             if (fold_immediate_true(fold, a))
474                 return (ast_expression*)b;
475         } else {
476             return fold_constgen_float (
477                 fold, 
478                 ((or) ? (fold_immediate_true(fold, a) || fold_immediate_true(fold, b))
479                       : (fold_immediate_true(fold, a) && fold_immediate_true(fold, b)))
480                             ? 1.0f
481                             : 0.0f
482             );
483         }
484     }
485     return NULL;
486 }
487
488 static GMQCC_INLINE ast_expression *fold_op_tern(fold_t *fold, ast_value *a, ast_value *b, ast_value *c) {
489     if (fold_can_1(a)) {
490         return fold_immediate_true(fold, a)
491                     ? (ast_expression*)b
492                     : (ast_expression*)c;
493     }
494     return NULL;
495 }
496
497 static GMQCC_INLINE ast_expression *fold_op_exp(fold_t *fold, ast_value *a, ast_value *b) {
498     if (fold_can_2(a, b))
499         return fold_constgen_float(fold, (qcfloat_t)powf(fold_immvalue_float(a), fold_immvalue_float(b)));
500     return NULL;
501 }
502
503 static GMQCC_INLINE ast_expression *fold_op_lteqgt(fold_t *fold, ast_value *a, ast_value *b) {
504     if (fold_can_2(a,b)) {
505         if (fold_immvalue_float(a) <  fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[2];
506         if (fold_immvalue_float(a) == fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[0];
507         if (fold_immvalue_float(a) >  fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[1];
508     }
509     return NULL;
510 }
511
512 static GMQCC_INLINE ast_expression *fold_op_cmp(fold_t *fold, ast_value *a, ast_value *b, bool ne) {
513     if (fold_can_2(a, b)) {
514         return fold_constgen_float(
515                     fold,
516                     (ne) ? (fold_immvalue_float(a) != fold_immvalue_float(b))
517                          : (fold_immvalue_float(a) == fold_immvalue_float(b))
518                 );
519     }
520     return NULL;
521 }
522
523 static GMQCC_INLINE ast_expression *fold_op_bnot(fold_t *fold, ast_value *a) {
524     if (fold_can_1(a))
525         return fold_constgen_float(fold, ~((qcint_t)fold_immvalue_float(a)));
526     return NULL;
527 }
528
529 ast_expression *fold_op(fold_t *fold, const oper_info *info, ast_expression **opexprs) {
530     ast_value *a = (ast_value*)opexprs[0];
531     ast_value *b = (ast_value*)opexprs[1];
532     ast_value *c = (ast_value*)opexprs[2];
533
534     /* can a fold operation be applied to this operator usage? */
535     if (!info->folds)
536         return NULL;
537
538     switch(info->operands) {
539         case 3: if(!c) return NULL;
540         case 2: if(!b) return NULL;
541         case 1:
542         if(!a) {
543             compile_error(fold_ctx(fold), "interal error: fold_op no operands to fold\n");
544             return NULL;
545         }
546     }
547
548     switch(info->id) {
549         case opid2('-','P'):     return fold_op_neg    (fold, a);
550         case opid2('!','P'):     return fold_op_not    (fold, a);
551         case opid1('+'):         return fold_op_add    (fold, a, b);
552         case opid1('-'):         return fold_op_sub    (fold, a, b);
553         case opid1('*'):         return fold_op_mul    (fold, a, b);
554         case opid1('/'):         return fold_op_div    (fold, a, b);
555         case opid1('%'):         return fold_op_mod    (fold, a, b);
556         case opid1('|'):         return fold_op_bor    (fold, a, b);
557         case opid1('&'):         return fold_op_band   (fold, a, b);
558         case opid1('^'):         return fold_op_xor    (fold, a, b);
559         case opid2('<','<'):     return fold_op_lshift (fold, a, b);
560         case opid2('>','>'):     return fold_op_rshift (fold, a, b);
561         case opid2('|','|'):     return fold_op_andor  (fold, a, b, true);
562         case opid2('&','&'):     return fold_op_andor  (fold, a, b, false);
563         case opid2('?',':'):     return fold_op_tern   (fold, a, b, c);
564         case opid2('*','*'):     return fold_op_exp    (fold, a, b);
565         case opid3('<','=','>'): return fold_op_lteqgt (fold, a, b);
566         case opid2('!','='):     return fold_op_cmp    (fold, a, b, true);
567         case opid2('=','='):     return fold_op_cmp    (fold, a, b, false);
568         case opid2('~','P'):     return fold_op_bnot   (fold, a);
569     }
570     return NULL;
571 }