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