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