From cbeac3e5f2e3128ea9828bfd61d4d450c9a002a6 Mon Sep 17 00:00:00 2001 From: Dale Weiler Date: Sat, 9 Mar 2013 08:53:39 +0000 Subject: [PATCH] Implemented smart intrinsic / builtin system. When you use trivial math functions like "pow", if they don't exist as a builtin, the compiler will implement its own versions, likewise, if a compiler builtin depends on a function that exists, it will use it, likewise if it doesn't it will implement it. If you explicitally use __builtin_ (prefixed) versions, the compiler again will select the best option it can, be it a combination of both builtins and compiler builtins, all compiler builtins, OR, all builtins (most performant). --- Makefile | 8 +- intrin.c | 193 ++++++++++++++++++++++++++++++++++++++ parser.c | 276 ++++--------------------------------------------------- 3 files changed, 216 insertions(+), 261 deletions(-) create mode 100644 intrin.c diff --git a/Makefile b/Makefile index fb17d6b..d1ca589 100644 --- a/Makefile +++ b/Makefile @@ -219,9 +219,15 @@ fs.o: gmqcc.h opts.def main.o: gmqcc.h opts.def lexer.h lexer.o: gmqcc.h opts.def lexer.h -parser.o: gmqcc.h opts.def lexer.h ast.h ir.h +parser.o: gmqcc.h opts.def lexer.h ast.h ir.h intrin.h fs.o: gmqcc.h opts.def util.o: gmqcc.h opts.def conout.o: gmqcc.h opts.def fs.o: gmqcc.h opts.def + +util.o: gmqcc.h opts.def +fs.o: gmqcc.h opts.def +conout.o: gmqcc.h opts.def +opts.o: gmqcc.h opts.def +pak.o: gmqcc.h opts.def diff --git a/intrin.c b/intrin.c new file mode 100644 index 0000000..dbf9670 --- /dev/null +++ b/intrin.c @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2012, 2013 + * Dale Weiler + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "ast.h" + + +/* + * Provides all the "intrinsics" / "builtins" for GMQCC. These can do + * a few things, they can provide fall back implementations for math + * functions if the definitions don't exist for some given engine. Or + * then can determine definitions for existing builtins, and simply + * wrap back to them instead. This is like a "portable" intrface that + * is entered when -fintrin is used (causing all existing builtins to + * be ignored by the compiler and instead interface through here. + */ +typedef struct { + ast_expression (*intrin)(parser_t *); + const char *name; + const char *alias; +} intrin_t; + + +/* + * Some helper macros for generating def, doing ast_value from func, with + * assignment, and a nice aggregate builder for generating the intrinsic + * table at the bottom of this file, and for registering functions and + * globals with the parser (so that they can be codegen'ed) + */ +#define INTRIN_IMP(NAME) \ + ast_expression intrin_##NAME (parser_t *parser) + +/* + * For intrinsics that are not to take precedence over a builtin, leave + * ALIAS as an empty string. + */ +#define INTRIN_DEF(NAME, ALIAS) \ + { &intrin_##NAME, #NAME, ALIAS } + + +#define INTRIN_VAL(NAME, FUNC, STYPE, VTYPE) \ + do { \ + (NAME) = ast_value_new ( \ + parser_ctx(parser), \ + "__builtin_" #NAME, \ + TYPE_FUNCTION \ + ); \ + (NAME)->expression.next = (ast_expression*)ast_value_new ( \ + parser_ctx(parser), \ + STYPE, \ + VTYPE \ + ); \ + (FUNC) = ast_function_new ( \ + parser_ctx(parser), \ + "__builtin_" #NAME, \ + (NAME) \ + ); \ + } while (0) + +#define INTRIN_REG(FUNC, VALUE) \ + do { \ + vec_push(parser->functions, (FUNC)); \ + vec_push(parser->globals, (ast_expression*)(VALUE)); \ + } while (0) + + +typedef enum { + QC_FP_NAN, + QC_FP_INFINITE, + QC_FP_ZERO, + QC_FP_SUBNORMAL, + QC_FP_NORMAL +} intrin_fp_t; + + +#if 0 +/* + * Implementation of intrinsics. Each new intrinsic needs to be added + * to the intrinsic table below. + */ +INTRIN_IMP(isnan) { + /* + * float isnan(float x) { + * float y; + * + * y = x; + * return (x != y); + */ +} + +INTRIN_IMP(isinf) { + /* + * float isinf(float x) { + * return (x != 0) && (x + x == x); + * } + */ +} + +INTRIN_IMP(fpclassify) { + /* + * float __builtin_fpclassify(float x) { + * if (isnan(x)) { + * return QC_FP_NAN; + * } + * if (isinf(x)) { + * return QC_FP_INFINITE; + * } + * if (x == 0.0f) { + * return QC_FP_ZERO; + * } + * return QC_FP_NORMAL; + * } + */ +} +#endif + + +INTRIN_IMP(exp) { + /* + * float __builtin_exp(float x) { + * return __builtin_pow(QC_M_E, x); + * } + */ + static ast_value *value = NULL; + + if (!value) { + ast_call *call = ast_call_new (parser_ctx(parser), intrin_func(parser, "pow")); + ast_value *arg1 = ast_value_new (parser_ctx(parser), "x", TYPE_FLOAT); + ast_block *body = ast_block_new (parser_ctx(parser)); + ast_function *func = NULL; + + INTRIN_VAL("exp", func, "", TYPE_FLOAT); + + /* push arguments for params to call */ + vec_push(call->params, (ast_expression*)parser_const_float(parser, QC_M_E)); + vec_push(call->params, (ast_expression*)arg1); + + /* return pow(QC_M_E, x) */ + vec_push(body->exprs, + (ast_expression*)ast_return_new( + parser_ctx(parser), + (ast_expression*)call + ) + ); + + vec_push(value->expressions.param, arg1); /* float x (for param) */ + vec_push(func ->blocks, body); /* {{{ body }}} */ + + INTRIN_REG(func, value); + } + + return (ast_expression*)value; +} + +#if 0 +INTRIN_IMP(exp2) { + /* + * float __builtin_exp2(float x) { + * return __builin_pow(2, x); + * } + */ +} + +INTRIN_IMP(expm1) { + /* + * float __builtin_expm1(float x) { + * return __builtin_exp(x) - 1; + * } + */ +} +#endif + +intrin_t intrin_intrinsics[] = { + INTRIN_DEF(exp, "exp") +}; diff --git a/parser.c b/parser.c index a5c039a..3cae34c 100644 --- a/parser.c +++ b/parser.c @@ -121,10 +121,6 @@ static ast_value* parser_create_array_setter_proto(parser_t *parser, ast_value * static ast_value* parser_create_array_getter_proto(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname); static ast_value *parse_typename(parser_t *parser, ast_value **storebase, ast_value *cached_typedef); -static ast_expression *parser_builtin_pow(parser_t *); -static ast_expression *parser_builtin_exp(parser_t *); -static ast_expression *parser_builtin_mod(parser_t *); - static void parseerror(parser_t *parser, const char *fmt, ...) { va_list ap; @@ -384,6 +380,9 @@ static ast_value* parser_find_typedef(parser_t *parser, const char *name, size_t return NULL; } +/* include intrinsics */ +#include "intrin.h" + typedef struct { size_t etype; /* 0 = expression, others are operators */ @@ -979,7 +978,7 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy) (float)(((qcint)ConstF(0)) % ((qcint)ConstF(1)))); } else { /* generate a call to __builtin_mod */ - ast_expression *mod = parser_builtin_mod(parser); + ast_expression *mod = intrin_func(parser, "mod"); ast_call *call = NULL; if (!mod) return false; /* can return null for missing floor */ @@ -1114,7 +1113,7 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy) if (CanConstFold(exprs[0], exprs[1])) { out = (ast_expression*)parser_const_float(parser, powf(ConstF(0), ConstF(1))); } else { - ast_call *gencall = ast_call_new(parser_ctx(parser), parser_builtin_pow(parser)); + ast_call *gencall = ast_call_new(parser_ctx(parser), intrin_func(parser, "pow")); vec_push(gencall->params, exprs[0]); vec_push(gencall->params, exprs[1]); out = (ast_expression*)gencall; @@ -1881,12 +1880,15 @@ static bool parse_sya_operand(parser_t *parser, shunt *sy, bool with_labels) if (!strcmp(parser_tokval(parser), "__builtin_debug_typestring")) { var = (ast_expression*)intrinsic_debug_typestring; } - if (!strcmp(parser_tokval(parser), "__builtin_pow")) - var = parser_builtin_pow(parser); - if (!strcmp(parser_tokval(parser), "__builtin_exp")) - var = parser_builtin_exp(parser); - if (!strcmp(parser_tokval(parser), "__builtin_mod")) - var = parser_builtin_mod(parser); + /* now we try for the real intrinsic hashtable. If the string + * begins with __builtin, we simply skip past it, otherwise we + * use the identifier as is. + */ + else if (!strncmp(parser_tokval(parser), "__builtin_", 10)) { + var = intrin_func(parser, parser_tokval(parser) + 10 /* skip __builtin */); + } else { + var = intrin_func(parser, parser_tokval(parser)); + } if (!var) { char *correct = NULL; @@ -3351,254 +3353,6 @@ static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression * return true; } -ast_expression *parser_builtin_pow(parser_t *parser) { - /* - * float __builtin_pow(float x, float y) { - * float value = 1.0f; - * while (y > 0) { - * while (!(y&1)) { - * y *= 0.25f; - * x *= x; - * } - * y--; - * value *= x; - * } - * return value; - * } - */ - static ast_function *pow_func = NULL; - static ast_value *pow_func_val = NULL; - if (!pow_func) { - ast_value *pow_arguments[2]; - ast_value *pow_value = ast_value_new (parser_ctx(parser), "value", TYPE_FLOAT); - ast_block *pow_body = ast_block_new (parser_ctx(parser)); - ast_block *pow_loop_body = ast_block_new (parser_ctx(parser)); - ast_block *pow_loop_nest_body = ast_block_new (parser_ctx(parser)); - ast_loop *pow_loop = NULL; - ast_loop *pow_loop_nest = NULL; - - pow_arguments[0] = ast_value_new (parser_ctx(parser), "x", TYPE_FLOAT); - pow_arguments[1] = ast_value_new (parser_ctx(parser), "x", TYPE_FLOAT); - pow_func_val = ast_value_new (parser_ctx(parser), "__builtin_pow", TYPE_FUNCTION); - pow_func_val->expression.next = (ast_expression*)ast_value_new(parser_ctx(parser), "", TYPE_FLOAT); - - vec_push(pow_func_val->expression.params, pow_arguments[0]); - vec_push(pow_func_val->expression.params, pow_arguments[1]); - - pow_func = ast_function_new(parser_ctx(parser), "__builtin_pow", pow_func_val); - - /* float value; */ - vec_push(pow_body->locals, pow_value); - /* value = 1.0f; */ - vec_push(pow_body->exprs, - (ast_expression*)ast_store_new( - parser_ctx(parser), - INSTR_STORE_F, - (ast_expression*)pow_value, - (ast_expression*)parser_const_float_1(parser) - ) - ); - - /* y >>= 2 */ - vec_push(pow_loop_nest_body->exprs, - (ast_expression*)ast_binstore_new( - parser_ctx(parser), - INSTR_STORE_F, - INSTR_MUL_F, - (ast_expression*)pow_arguments[1], - (ast_expression*)parser_const_float(parser, 0.25f) - ) - ); - vec_push(pow_loop_nest_body->exprs, - (ast_expression*)ast_binstore_new( - parser_ctx(parser), - INSTR_STORE_F, - INSTR_MUL_F, - (ast_expression*)pow_arguments[0], - (ast_expression*)pow_arguments[0] - ) - ); - - /* while (!(y&1)) */ - pow_loop_nest = ast_loop_new ( - parser_ctx(parser), - NULL, - (ast_expression*)ast_binary_new( - parser_ctx(parser), - INSTR_AND, - (ast_expression*)pow_arguments[1], - (ast_expression*)parser_const_float_1(parser) - ), - true, - NULL, - false, - NULL, - (ast_expression*)pow_loop_nest_body - ); - - vec_push(pow_loop_body->exprs, (ast_expression*)pow_loop_nest); - vec_push(pow_loop_body->exprs, - (ast_expression*)ast_binstore_new( - parser_ctx(parser), - INSTR_STORE_F, - INSTR_SUB_F, - (ast_expression*)pow_arguments[1], - (ast_expression*)parser_const_float_1(parser) - ) - ); - vec_push(pow_loop_body->exprs, - (ast_expression*)ast_binstore_new( - parser_ctx(parser), - INSTR_STORE_F, - INSTR_MUL_F, - (ast_expression*)pow_value, - (ast_expression*)pow_arguments[0] - ) - ); - - /* while (y > 0) { */ - pow_loop = ast_loop_new( - parser_ctx(parser), - NULL, - (ast_expression*)ast_binary_new( - parser_ctx(parser), - INSTR_GT, - (ast_expression*)pow_arguments[1], - (ast_expression*)parser_const_float_0(parser) - ), - false, - NULL, - false, - NULL, - (ast_expression*)pow_loop_body - ); - /* } */ - vec_push(pow_body->exprs, (ast_expression*)pow_loop); - /* return value; */ - vec_push(pow_body->exprs, - (ast_expression*)ast_return_new( - parser_ctx(parser), - (ast_expression*)pow_value - ) - ); - - vec_push(pow_func->blocks, pow_body); - vec_push(parser->globals, (ast_expression*)pow_func_val); - vec_push(parser->functions, pow_func); - } - - return (ast_expression*)pow_func_val; -} - -#ifndef M_E -#define M_E 2.71828182845905 -#endif -static ast_expression *parser_builtin_exp(parser_t *parser) { - /* - * float __builtin_exp(float x) { - * return __builtin_exp(E, x); - * } - */ - static ast_value *exp_func_val = NULL; - - if (!exp_func_val) { - ast_function *exp_func = NULL; - ast_value *arg = ast_value_new (parser_ctx(parser), "x", TYPE_FLOAT); - ast_block *exp_body = ast_block_new (parser_ctx(parser)); - ast_call *exp_call = ast_call_new (parser_ctx(parser), parser_builtin_pow(parser)); - exp_func_val = ast_value_new (parser_ctx(parser), "__builtin_exp", TYPE_FUNCTION); - exp_func_val->expression.next = (ast_expression*)ast_value_new(parser_ctx(parser), "", TYPE_FLOAT); - exp_func = ast_function_new(parser_ctx(parser), "__builtin_exp", exp_func_val); - - vec_push(exp_call->params, (ast_expression*)parser_const_float(parser, M_E)); - vec_push(exp_call->params, (ast_expression*)arg); - - vec_push(exp_body->exprs, - (ast_expression*)ast_return_new( - parser_ctx(parser), - (ast_expression*)exp_call - ) - ); - - vec_push(exp_func_val->expression.params, arg); - vec_push(exp_func->blocks, exp_body); - - vec_push(parser->functions, exp_func); - vec_push(parser->globals, (ast_expression*)exp_func_val); - } - - return (ast_expression*)exp_func_val; -} - -static ast_expression *parser_builtin_mod(parser_t *parser) { - /* - * float __builtin_mod(float x, float y) { - * return x - y * floor(x / y); - * } - */ - static ast_value *mod_func_val = NULL; - static ast_expression *mod_floor = NULL; - - if (!mod_floor) { - if (!(mod_floor = parser_find_global(parser, "floor"))) { - parseerror(parser, - "internal error: no suitable definition found for `floor`" - "(required for `%%` operator and `__builtin_mod` intrinsic)" - ); - return NULL; - } - } - - if (!mod_func_val) { - ast_value *mod_args[2]; - ast_function *mod_func = NULL; - ast_block *mod_body = ast_block_new (parser_ctx(parser)); - ast_call *mod_call = ast_call_new (parser_ctx(parser), mod_floor); - mod_func_val = ast_value_new (parser_ctx(parser), "__builtin_mod", TYPE_FUNCTION); - mod_func_val->expression.next = (ast_expression*)ast_value_new(parser_ctx(parser), "", TYPE_FLOAT); - mod_func = ast_function_new(parser_ctx(parser), "__builtin_mod", mod_func_val); - mod_args[0] = ast_value_new (parser_ctx(parser), "x", TYPE_FLOAT); - mod_args[1] = ast_value_new (parser_ctx(parser), "x", TYPE_FLOAT); - - /* floor(x/y) */ - vec_push(mod_call->params, - (ast_expression*)ast_binary_new( - parser_ctx(parser), - INSTR_DIV_F, - (ast_expression*)mod_args[0], - (ast_expression*)mod_args[1] - ) - ); - - vec_push(mod_body->exprs, - (ast_expression*)ast_return_new( - parser_ctx(parser), - (ast_expression*)ast_binary_new( - parser_ctx(parser), - INSTR_SUB_F, - (ast_expression*)mod_args[0], - (ast_expression*)ast_binary_new( - parser_ctx(parser), - INSTR_MUL_F, - (ast_expression*)mod_args[1], - (ast_expression*)mod_call - ) - ) - ) - ); - - vec_push(mod_func_val->expression.params, mod_args[0]); - vec_push(mod_func_val->expression.params, mod_args[1]); - - vec_push(mod_func->blocks, mod_body); - - vec_push(parser->functions, mod_func); - vec_push(parser->globals, (ast_expression*)mod_func_val); - } - - return (ast_expression*)mod_func_val; -} - /* parse computed goto sides */ static ast_expression *parse_goto_computed(parser_t *parser, ast_expression **side) { ast_expression *on_true; @@ -6339,6 +6093,8 @@ void parser_cleanup() util_htdel(parser->aliases); + intrin_intrinsics_destroy(); + mem_d(parser); } -- 2.39.2