]> de.git.xonotic.org Git - xonotic/gmqcc.git/blob - intrin.c
Use '#' in nullfun identifier to prevent it from being used in code
[xonotic/gmqcc.git] / intrin.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 "parser.h"
25
26 /*
27  * Provides all the "intrinsics" / "builtins" for GMQCC. These can do
28  * a few things, they can provide fall back implementations for math
29  * functions if the definitions don't exist for some given engine. Or
30  * then can determine definitions for existing builtins, and simply
31  * wrap back to them instead.  This is like a "portable" intrface that
32  * is entered when -fintrin is used (causing all existing builtins to
33  * be ignored by the compiler and instead interface through here.
34  */
35 #define intrin_ctx(I) parser_ctx((I)->parser)
36
37 static GMQCC_INLINE ast_function *intrin_value(intrin_t *intrin, ast_value **out, const char *name, qcint_t vtype) {
38     ast_value    *value = NULL;
39     ast_function *func  = NULL;
40     char          buffer[1024];
41     char          stype [1024];
42
43     util_snprintf(buffer, sizeof(buffer), "__builtin_%s", name);
44     util_snprintf(stype,  sizeof(stype),   "<%s>",        type_name[vtype]);
45
46     value                    = ast_value_new(intrin_ctx(intrin), buffer, TYPE_FUNCTION);
47     value->intrinsic         = true;
48     value->expression.next   = (ast_expression*)ast_value_new(intrin_ctx(intrin), stype, vtype);
49     func                     = ast_function_new(intrin_ctx(intrin), buffer, value);
50     value->expression.flags |= AST_FLAG_ERASEABLE;
51
52     *out = value;
53     return func;
54 }
55
56 static GMQCC_INLINE void intrin_reg(intrin_t *intrin, ast_value *const value, ast_function *const func) {
57     vec_push(intrin->parser->functions, func);
58     vec_push(intrin->parser->globals,   (ast_expression*)value);
59 }
60
61 #define QC_M_E         2.718281828459045f
62 #define QC_POW_EPSILON 0.00001f
63
64 /*
65  * since some intrinsics depend on each other there is the possibility
66  * that an intrinsic will fail to get a 'depended' function that a
67  * builtin needs, causing some dependency in the chain to have a NULL
68  * function. This will cause a segmentation fault at code generation,
69  * even though an error was raised. To contiue to allow it (instead
70  * of stopping compilation right away). We need to return from the
71  * parser, before compilation stops after all the collected errors.
72  */
73 static ast_expression *intrin_func_self(intrin_t *intrin, const char *name, const char *from);
74 static ast_expression *intrin_nullfunc(intrin_t *intrin) {
75     ast_value    *value = NULL;
76     ast_function *func  = intrin_value(intrin, &value, "nil", TYPE_VOID);
77
78     vec_push(func->blocks, ast_block_new(intrin_ctx(intrin)));
79
80     intrin_reg(intrin, value, func);
81     return (ast_expression*)value;
82 }
83
84 static ast_expression *intrin_pow(intrin_t *intrin) {
85     /*
86      *
87      * float pow(float base, float exp) {
88      *     float result;
89      *     float low;
90      *     float high;
91      *     float mid;
92      *     float square;
93      *     float accumulate;
94      *
95      *     if (exp == 0.0)
96      *         return base;
97      *     if (exp < 0)
98      *         return 1.0 / pow(base, -exp);
99      *     if (exp >= 1) {
100      *         result = pow(base, exp / 2);
101      *         return result * result;
102      *     }
103      *
104      *     low        = 0.0f;
105      *     high       = 1.0f;
106      *     square     = sqrt(base);
107      *     accumulate = square;
108      *     mid        = high / 2.0f
109      *
110      *     while (fabs(mid - exp) > QC_POW_EPSILON) {
111      *         square = sqrt(square);
112      *         if (mid < exp) {
113      *             low         = mid;
114      *             accumulate *= square;
115      *         } else {
116      *             high        = mid;
117      *             accumulate *= (1.0f / square);
118      *         }
119      *         mid = (low + high) / 2;
120      *     }
121      *     return accumulate;
122      * }
123      */
124     ast_value    *value = NULL;
125     ast_function *func = intrin_value(intrin, &value, "pow", TYPE_FLOAT);
126
127     /* prepare some calls for later */
128     ast_call *callpow1  = ast_call_new(intrin_ctx(intrin), (ast_expression*)value);      /* for pow(base, -exp)    */
129     ast_call *callpow2  = ast_call_new(intrin_ctx(intrin), (ast_expression*)value);      /* for pow(vase, exp / 2) */
130     ast_call *callsqrt1 = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "pow")); /* for sqrt(base)         */
131     ast_call *callsqrt2 = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "pow")); /* for sqrt(square)       */
132     ast_call *callfabs  = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "fabs", "pow")); /* for fabs(mid - exp)    */
133
134     /* prepare some blocks for later */
135     ast_block *expgt1       = ast_block_new(intrin_ctx(intrin));
136     ast_block *midltexp     = ast_block_new(intrin_ctx(intrin));
137     ast_block *midltexpelse = ast_block_new(intrin_ctx(intrin));
138     ast_block *whileblock   = ast_block_new(intrin_ctx(intrin));
139
140     /* float pow(float base, float exp) */
141     ast_value    *base = ast_value_new(intrin_ctx(intrin), "base", TYPE_FLOAT);
142     ast_value    *exp  = ast_value_new(intrin_ctx(intrin), "exp",  TYPE_FLOAT);
143     /* { */
144     ast_block    *body = ast_block_new(intrin_ctx(intrin));
145
146     /*
147      * float result;
148      * float low;
149      * float high;
150      * float square;
151      * float accumulate;
152      * float mid;
153      */
154     ast_value *result     = ast_value_new(intrin_ctx(intrin), "result",     TYPE_FLOAT);
155     ast_value *low        = ast_value_new(intrin_ctx(intrin), "low",        TYPE_FLOAT);
156     ast_value *high       = ast_value_new(intrin_ctx(intrin), "high",       TYPE_FLOAT);
157     ast_value *square     = ast_value_new(intrin_ctx(intrin), "square",     TYPE_FLOAT);
158     ast_value *accumulate = ast_value_new(intrin_ctx(intrin), "accumulate", TYPE_FLOAT);
159     ast_value *mid        = ast_value_new(intrin_ctx(intrin), "mid",        TYPE_FLOAT);
160     vec_push(body->locals, result);
161     vec_push(body->locals, low);
162     vec_push(body->locals, high);
163     vec_push(body->locals, square);
164     vec_push(body->locals, accumulate);
165     vec_push(body->locals, mid);
166
167     vec_push(value->expression.params, base);
168     vec_push(value->expression.params, exp);
169
170     /*
171      * if (exp == 0.0)
172      *     return base;
173      */
174     vec_push(body->exprs,
175         (ast_expression*)ast_ifthen_new(
176             intrin_ctx(intrin),
177             (ast_expression*)ast_binary_new(
178                 intrin_ctx(intrin),
179                 INSTR_EQ_F,
180                 (ast_expression*)exp,
181                 (ast_expression*)intrin->fold->imm_float[0]
182             ),
183             (ast_expression*)ast_return_new(
184                 intrin_ctx(intrin),
185                 (ast_expression*)base
186             ),
187             NULL
188         )
189     );
190
191     /* <callpow1> = pow(base, -exp) */
192     vec_push(callpow1->params, (ast_expression*)base);
193     vec_push(callpow1->params,
194         (ast_expression*)ast_unary_new(
195             intrin_ctx(intrin),
196             VINSTR_NEG_F,
197             (ast_expression*)exp
198         )
199     );
200
201     /*
202      * if (exp < 0)
203      *     return 1.0 / <callpow1>;
204      */
205     vec_push(body->exprs,
206         (ast_expression*)ast_ifthen_new(
207             intrin_ctx(intrin),
208             (ast_expression*)ast_binary_new(
209                 intrin_ctx(intrin),
210                 INSTR_LT,
211                 (ast_expression*)exp,
212                 (ast_expression*)intrin->fold->imm_float[0]
213             ),
214             (ast_expression*)ast_return_new(
215                 intrin_ctx(intrin),
216                 (ast_expression*)ast_binary_new(
217                     intrin_ctx(intrin),
218                     INSTR_DIV_F,
219                     (ast_expression*)intrin->fold->imm_float[1],
220                     (ast_expression*)callpow1
221                 )
222             ),
223             NULL
224         )
225     );
226
227     /* <callpow2> = pow(base, exp / 2) */
228     vec_push(callpow2->params, (ast_expression*)base);
229     vec_push(callpow2->params,
230         (ast_expression*)ast_binary_new(
231             intrin_ctx(intrin),
232             INSTR_DIV_F,
233             (ast_expression*)exp,
234             (ast_expression*)fold_constgen_float(intrin->fold, 2.0f)
235         )
236     );
237
238     /*
239      * <expgt1> = {
240      *     result = <callpow2>;
241      *     return result * result;
242      * }
243      */
244     vec_push(expgt1->exprs,
245         (ast_expression*)ast_store_new(
246             intrin_ctx(intrin),
247             INSTR_STORE_F,
248             (ast_expression*)result,
249             (ast_expression*)callpow2
250         )
251     );
252     vec_push(expgt1->exprs,
253         (ast_expression*)ast_return_new(
254             intrin_ctx(intrin),
255             (ast_expression*)ast_binary_new(
256                 intrin_ctx(intrin),
257                 INSTR_MUL_F,
258                 (ast_expression*)result,
259                 (ast_expression*)result
260             )
261         )
262     );
263
264     /*
265      * if (exp >= 1) {
266      *     <expgt1>
267      * }
268      */
269     vec_push(body->exprs,
270         (ast_expression*)ast_ifthen_new(
271             intrin_ctx(intrin),
272             (ast_expression*)ast_binary_new(
273                 intrin_ctx(intrin),
274                 INSTR_GE,
275                 (ast_expression*)exp,
276                 (ast_expression*)intrin->fold->imm_float[1]
277             ),
278             (ast_expression*)expgt1,
279             NULL
280         )
281     );
282
283     /*
284      * <callsqrt1> = sqrt(base)
285      */
286     vec_push(callsqrt1->params, (ast_expression*)base);
287
288     /*
289      * low        = 0.0f;
290      * high       = 1.0f;
291      * square     = sqrt(base);
292      * accumulate = square;
293      * mid        = high / 2.0f;
294      */
295     vec_push(body->exprs,
296         (ast_expression*)ast_store_new(intrin_ctx(intrin),
297             INSTR_STORE_F,
298             (ast_expression*)low,
299             (ast_expression*)intrin->fold->imm_float[0]
300         )
301     );
302     vec_push(body->exprs,
303         (ast_expression*)ast_store_new(
304             intrin_ctx(intrin),
305             INSTR_STORE_F,
306             (ast_expression*)high,
307             (ast_expression*)intrin->fold->imm_float[1]
308         )
309     );
310     vec_push(body->exprs,
311         (ast_expression*)ast_store_new(
312             intrin_ctx(intrin),
313             INSTR_STORE_F,
314             (ast_expression*)square,
315             (ast_expression*)callsqrt1
316         )
317     );
318     vec_push(body->exprs,
319         (ast_expression*)ast_store_new(
320             intrin_ctx(intrin),
321             INSTR_STORE_F,
322             (ast_expression*)accumulate,
323             (ast_expression*)square
324         )
325     );
326     vec_push(body->exprs,
327         (ast_expression*)ast_store_new(
328             intrin_ctx(intrin),
329             INSTR_STORE_F,
330             (ast_expression*)mid,
331             (ast_expression*)ast_binary_new(
332                 intrin_ctx(intrin),
333                 INSTR_DIV_F,
334                 (ast_expression*)high,
335                 (ast_expression*)fold_constgen_float(intrin->fold, 2.0f)
336             )
337         )
338     );
339
340     /*
341      * <midltexp> = {
342      *     low         = mid;
343      *     accumulate *= square;
344      * }
345      */
346     vec_push(midltexp->exprs,
347         (ast_expression*)ast_store_new(
348             intrin_ctx(intrin),
349             INSTR_STORE_F,
350             (ast_expression*)low,
351             (ast_expression*)mid
352         )
353     );
354     vec_push(midltexp->exprs,
355         (ast_expression*)ast_binstore_new(
356             intrin_ctx(intrin),
357             INSTR_STORE_F,
358             INSTR_MUL_F,
359             (ast_expression*)accumulate,
360             (ast_expression*)square
361         )
362     );
363
364     /*
365      * <midltexpelse> = {
366      *     high        = mid;
367      *     accumulate *= (1.0 / square);
368      * }
369      */
370     vec_push(midltexpelse->exprs,
371         (ast_expression*)ast_store_new(
372             intrin_ctx(intrin),
373             INSTR_STORE_F,
374             (ast_expression*)high,
375             (ast_expression*)mid
376         )
377     );
378     vec_push(midltexpelse->exprs,
379         (ast_expression*)ast_binstore_new(
380             intrin_ctx(intrin),
381             INSTR_STORE_F,
382             INSTR_MUL_F,
383             (ast_expression*)accumulate,
384             (ast_expression*)ast_binary_new(
385                 intrin_ctx(intrin),
386                 INSTR_DIV_F,
387                 (ast_expression*)intrin->fold->imm_float[1],
388                 (ast_expression*)square
389             )
390         )
391     );
392
393     /*
394      * <callsqrt2> = sqrt(square)
395      */
396     vec_push(callsqrt2->params, (ast_expression*)square);
397
398     /*
399      * <whileblock> = {
400      *     square = <callsqrt2>;
401      *     if (mid < exp)
402      *          <midltexp>;
403      *     else
404      *          <midltexpelse>;
405      *
406      *     mid = (low + high) / 2;
407      * }
408      */
409     vec_push(whileblock->exprs,
410         (ast_expression*)ast_store_new(
411             intrin_ctx(intrin),
412             INSTR_STORE_F,
413             (ast_expression*)square,
414             (ast_expression*)callsqrt2
415         )
416     );
417     vec_push(whileblock->exprs,
418         (ast_expression*)ast_ifthen_new(
419             intrin_ctx(intrin),
420             (ast_expression*)ast_binary_new(
421                 intrin_ctx(intrin),
422                 INSTR_LT,
423                 (ast_expression*)mid,
424                 (ast_expression*)exp
425             ),
426             (ast_expression*)midltexp,
427             (ast_expression*)midltexpelse
428         )
429     );
430     vec_push(whileblock->exprs,
431         (ast_expression*)ast_store_new(
432             intrin_ctx(intrin),
433             INSTR_STORE_F,
434             (ast_expression*)mid,
435             (ast_expression*)ast_binary_new(
436                 intrin_ctx(intrin),
437                 INSTR_DIV_F,
438                 (ast_expression*)ast_binary_new(
439                     intrin_ctx(intrin),
440                     INSTR_ADD_F,
441                     (ast_expression*)low,
442                     (ast_expression*)high
443                 ),
444                 (ast_expression*)fold_constgen_float(intrin->fold, 2.0f)
445             )
446         )
447     );
448
449     /*
450      * <callabs> = fabs(mid - exp)
451      */
452     vec_push(callfabs->params,
453         (ast_expression*)ast_binary_new(
454             intrin_ctx(intrin),
455             INSTR_SUB_F,
456             (ast_expression*)mid,
457             (ast_expression*)exp
458         )
459     );
460
461     /*
462      * while (<callfabs>  > epsilon)
463      *     <whileblock>
464      */
465     vec_push(body->exprs,
466         (ast_expression*)ast_loop_new(
467             intrin_ctx(intrin),
468             /* init */
469             NULL,
470             /* pre condition */
471             (ast_expression*)ast_binary_new(
472                 intrin_ctx(intrin),
473                 INSTR_GT,
474                 (ast_expression*)callfabs,
475                 (ast_expression*)fold_constgen_float(intrin->fold, QC_POW_EPSILON)
476             ),
477             /* pre not */
478             false,
479             /* post condition */
480             NULL,
481             /* post not */
482             false,
483             /* increment expression */
484             NULL,
485             /* code block */
486             (ast_expression*)whileblock
487         )
488     );
489
490     /* return midvalue */
491     vec_push(body->exprs,
492         (ast_expression*)ast_return_new(
493             intrin_ctx(intrin),
494             (ast_expression*)accumulate
495         )
496     );
497
498     /* } */
499     vec_push(func->blocks, body);
500
501     intrin_reg(intrin, value, func);
502     return (ast_expression*)value;
503 }
504
505 static ast_expression *intrin_mod(intrin_t *intrin) {
506     /*
507      * float mod(float a, float b) {
508      *     float div = a / b;
509      *     float sign = (div < 0.0f) ? -1 : 1;
510      *     return a - b * sign * floor(sign * div);
511      * }
512      */
513     ast_value    *value = NULL;
514     ast_call     *call  = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "floor", "mod"));
515     ast_value    *a     = ast_value_new(intrin_ctx(intrin), "a",    TYPE_FLOAT);
516     ast_value    *b     = ast_value_new(intrin_ctx(intrin), "b",    TYPE_FLOAT);
517     ast_value    *div   = ast_value_new(intrin_ctx(intrin), "div",  TYPE_FLOAT);
518     ast_value    *sign  = ast_value_new(intrin_ctx(intrin), "sign", TYPE_FLOAT);
519     ast_block    *body  = ast_block_new(intrin_ctx(intrin));
520     ast_function *func  = intrin_value(intrin, &value, "mod", TYPE_FLOAT);
521
522     vec_push(value->expression.params, a);
523     vec_push(value->expression.params, b);
524
525     vec_push(body->locals, div);
526     vec_push(body->locals, sign);
527
528     /* div = a / b; */
529     vec_push(body->exprs,
530         (ast_expression*)ast_store_new(
531             intrin_ctx(intrin),
532             INSTR_STORE_F,
533             (ast_expression*)div,
534             (ast_expression*)ast_binary_new(
535                 intrin_ctx(intrin),
536                 INSTR_DIV_F,
537                 (ast_expression*)a,
538                 (ast_expression*)b
539             )
540         )
541     );
542
543     /* sign = (div < 0.0f) ? -1 : 1; */
544     vec_push(body->exprs,
545         (ast_expression*)ast_store_new(
546             intrin_ctx(intrin),
547             INSTR_STORE_F,
548             (ast_expression*)sign,
549             (ast_expression*)ast_ternary_new(
550                 intrin_ctx(intrin),
551                 (ast_expression*)ast_binary_new(
552                     intrin_ctx(intrin),
553                     INSTR_LT,
554                     (ast_expression*)div,
555                     (ast_expression*)intrin->fold->imm_float[0]
556                 ),
557                 (ast_expression*)intrin->fold->imm_float[2],
558                 (ast_expression*)intrin->fold->imm_float[1]
559             )
560         )
561     );
562
563     /* floor(sign * div) */
564     vec_push(call->params,
565         (ast_expression*)ast_binary_new(
566             intrin_ctx(intrin),
567             INSTR_MUL_F,
568             (ast_expression*)sign,
569             (ast_expression*)div
570         )
571     );
572
573     /* return a - b * sign * <call> */
574     vec_push(body->exprs,
575         (ast_expression*)ast_return_new(
576             intrin_ctx(intrin),
577             (ast_expression*)ast_binary_new(
578                 intrin_ctx(intrin),
579                 INSTR_SUB_F,
580                 (ast_expression*)a,
581                 (ast_expression*)ast_binary_new(
582                     intrin_ctx(intrin),
583                     INSTR_MUL_F,
584                     (ast_expression*)b,
585                     (ast_expression*)ast_binary_new(
586                         intrin_ctx(intrin),
587                         INSTR_MUL_F,
588                         (ast_expression*)sign,
589                         (ast_expression*)call
590                     )
591                 )
592             )
593         )
594     );
595
596     vec_push(func->blocks, body); /* {{{ body }}} */
597     intrin_reg(intrin, value, func);
598
599     return (ast_expression*)value;
600 }
601
602 static ast_expression *intrin_exp(intrin_t *intrin) {
603     /*
604      * float exp(float x) {
605      *     // mul 10 to round increments of 0.1f
606      *     return floor((pow(QC_M_E, x) * 10) + 0.5) / 10;
607      * }
608      */
609     ast_value    *value     = NULL;
610     ast_call     *callpow   = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "pow", "exp"));
611     ast_call     *callfloor = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "floor", "exp"));
612     ast_value    *arg1      = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
613     ast_block    *body      = ast_block_new(intrin_ctx(intrin));
614     ast_function *func      = intrin_value(intrin, &value, "exp", TYPE_FLOAT);
615
616     vec_push(value->expression.params, arg1);
617
618     vec_push(callpow->params, (ast_expression*)fold_constgen_float(intrin->fold, QC_M_E));
619     vec_push(callpow->params, (ast_expression*)arg1);
620     vec_push(callfloor->params,
621         (ast_expression*)ast_binary_new(
622             intrin_ctx(intrin),
623             INSTR_ADD_F,
624             (ast_expression*)ast_binary_new(
625                 intrin_ctx(intrin),
626                 INSTR_MUL_F,
627                 (ast_expression*)callpow,
628                 (ast_expression*)fold_constgen_float(intrin->fold, 10.0f)
629             ),
630             (ast_expression*)fold_constgen_float(intrin->fold, 0.5f)
631         )
632     );
633
634     /* return <callfloor> / 10.0f */
635     vec_push(body->exprs,
636         (ast_expression*)ast_return_new(
637             intrin_ctx(intrin),
638             (ast_expression*)ast_binary_new(
639                 intrin_ctx(intrin),
640                 INSTR_DIV_F,
641                 (ast_expression*)callfloor,
642                 (ast_expression*)fold_constgen_float(intrin->fold, 10.0f)
643             )
644         )
645     );
646
647     vec_push(func->blocks, body); /* {{{ body }}} */
648
649     intrin_reg(intrin, value, func);
650     return (ast_expression*)value;
651 }
652
653 static ast_expression *intrin_isnan(intrin_t *intrin) {
654     /*
655      * float isnan(float x) {
656      *   float local;
657      *   local = x;
658      *
659      *   return (x != local);
660      * }
661      */
662     ast_value    *value  = NULL;
663     ast_value    *arg1   = ast_value_new(intrin_ctx(intrin), "x",     TYPE_FLOAT);
664     ast_value    *local  = ast_value_new(intrin_ctx(intrin), "local", TYPE_FLOAT);
665     ast_block    *body   = ast_block_new(intrin_ctx(intrin));
666     ast_function *func   = intrin_value(intrin, &value, "isnan", TYPE_FLOAT);
667
668     vec_push(body->locals, local);
669     vec_push(body->exprs,
670         (ast_expression*)ast_store_new(
671             intrin_ctx(intrin),
672             INSTR_STORE_F,
673             (ast_expression*)local,
674             (ast_expression*)arg1
675         )
676     );
677
678     vec_push(body->exprs,
679         (ast_expression*)ast_return_new(
680             intrin_ctx(intrin),
681             (ast_expression*)ast_binary_new(
682                 intrin_ctx(intrin),
683                 INSTR_NE_F,
684                 (ast_expression*)arg1,
685                 (ast_expression*)local
686             )
687         )
688     );
689
690     vec_push(value->expression.params, arg1);
691     vec_push(func->blocks, body);
692
693     intrin_reg(intrin, value, func);
694
695     return (ast_expression*)value;
696 }
697
698 static ast_expression *intrin_fabs(intrin_t *intrin) {
699     /*
700      * float fabs(float x) {
701      *     return x < 0 ? -x : x;
702      * }
703      */
704     ast_value    *value  = NULL;
705     ast_value    *arg1   = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
706     ast_block    *body   = ast_block_new(intrin_ctx(intrin));
707     ast_function *func   = intrin_value(intrin, &value, "fabs", TYPE_FLOAT);
708
709     vec_push(body->exprs,
710         (ast_expression*)ast_return_new(
711             intrin_ctx(intrin),
712             (ast_expression*)ast_ternary_new(
713                 intrin_ctx(intrin),
714                 (ast_expression*)ast_binary_new(
715                     intrin_ctx(intrin),
716                     INSTR_LE,
717                     (ast_expression*)arg1,
718                     (ast_expression*)intrin->fold->imm_float[0]
719                 ),
720                 (ast_expression*)ast_unary_new(
721                     intrin_ctx(intrin),
722                     VINSTR_NEG_F,
723                     (ast_expression*)arg1
724                 ),
725                 (ast_expression*)arg1
726             )
727         )
728     );
729
730     vec_push(value->expression.params, arg1);
731     vec_push(func->blocks, body);
732
733     intrin_reg(intrin, value, func);
734
735     return (ast_expression*)value;
736 }
737
738 /*
739  * TODO: make static (and handle ast_type_string) here for the builtin
740  * instead of in SYA parse close.
741  */
742 ast_expression *intrin_debug_typestring(intrin_t *intrin) {
743     (void)intrin;
744     return (ast_expression*)0x1;
745 }
746
747 static const intrin_func_t intrinsics[] = {
748     {&intrin_exp,              "__builtin_exp",              "exp",      1},
749     {&intrin_mod,              "__builtin_mod",              "mod",      2},
750     {&intrin_pow,              "__builtin_pow",              "pow",      2},
751     {&intrin_isnan,            "__builtin_isnan",            "isnan",    1},
752     {&intrin_fabs,             "__builtin_fabs",             "fabs",     1},
753     {&intrin_debug_typestring, "__builtin_debug_typestring", "",         0},
754     {&intrin_nullfunc,         "#nullfunc",                  "",         0}
755 };
756
757 static void intrin_error(intrin_t *intrin, const char *fmt, ...) {
758     va_list ap;
759     va_start(ap, fmt);
760     vcompile_error(intrin->parser->lex->tok.ctx, fmt, ap);
761     va_end(ap);
762 }
763
764 /* exposed */
765 intrin_t *intrin_init(parser_t *parser) {
766     intrin_t *intrin = (intrin_t*)mem_a(sizeof(intrin_t));
767     size_t    i;
768
769     intrin->parser     = parser;
770     intrin->fold       = parser->fold;
771     intrin->intrinsics = NULL;
772     intrin->generated  = NULL;
773
774     vec_append(intrin->intrinsics, GMQCC_ARRAY_COUNT(intrinsics), intrinsics);
775
776     /* populate with null pointers for tracking generation */
777     for (i = 0; i < GMQCC_ARRAY_COUNT(intrinsics); i++)
778         vec_push(intrin->generated, NULL);
779
780     return intrin;
781 }
782
783 void intrin_cleanup(intrin_t *intrin) {
784     vec_free(intrin->intrinsics);
785     vec_free(intrin->generated);
786     mem_d(intrin);
787 }
788
789 ast_expression *intrin_fold(intrin_t *intrin, ast_value *value, ast_expression **exprs) {
790     size_t i;
791     if (!value || !value->name)
792         return NULL;
793     for (i = 0; i < vec_size(intrin->intrinsics); i++)
794         if (!strcmp(value->name, intrin->intrinsics[i].name))
795             return (vec_size(exprs) != intrin->intrinsics[i].args)
796                         ? NULL
797                         : fold_intrin(intrin->fold, value->name + 10, exprs);
798     return NULL;
799 }
800
801 static GMQCC_INLINE ast_expression *intrin_func_try(intrin_t *intrin, size_t offset, const char *compare) {
802     size_t i;
803     for (i = 0; i < vec_size(intrin->intrinsics); i++) {
804         if (strcmp(*(char **)((char *)&intrin->intrinsics[i] + offset), compare))
805             continue;
806         if (intrin->generated[i])
807             return intrin->generated[i];
808         return intrin->generated[i] = intrin->intrinsics[i].intrin(intrin);
809     }
810     return NULL;
811 }
812
813 static ast_expression *intrin_func_self(intrin_t *intrin, const char *name, const char *from) {
814     size_t           i;
815     ast_expression  *find;
816
817     /* try current first */
818     if ((find = parser_find_global(intrin->parser, name)) && ((ast_value*)find)->expression.vtype == TYPE_FUNCTION)
819         for (i = 0; i < vec_size(intrin->parser->functions); ++i)
820             if (((ast_value*)find)->name && !strcmp(intrin->parser->functions[i]->name, ((ast_value*)find)->name) && intrin->parser->functions[i]->builtin < 0)
821                 return find;
822     /* try name second */
823     if ((find = intrin_func_try(intrin, offsetof(intrin_func_t, name),  name)))
824         return find;
825     /* try alias third */
826     if ((find = intrin_func_try(intrin, offsetof(intrin_func_t, alias), name)))
827         return find;
828
829     if (from)
830         intrin_error(intrin, "need function `%s', compiler depends on it for `__builtin_%s'", name, from);
831     else
832         intrin_error(intrin, "need function `%s', compiler depends on it", name);
833
834     return intrin_func(intrin, "#nullfunc");
835 }
836
837 ast_expression *intrin_func(intrin_t *intrin, const char *name) {
838     return intrin_func_self(intrin, name, NULL);
839 }