From: Dale Weiler Date: Sun, 3 Jan 2016 03:30:35 +0000 (-0500) Subject: Merge branch 'master' into cleanup X-Git-Tag: xonotic-v0.8.2~6 X-Git-Url: https://git.xonotic.org/?p=xonotic%2Fgmqcc.git;a=commitdiff_plain;h=1a18ff529420930cb4116484991c2dabe3a70654;hp=3f4659b5d5a4827602fcbddd322ac1afebd5ccfe Merge branch 'master' into cleanup --- diff --git a/.travis.yml b/.travis.yml index 4b6f959..830f373 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,27 @@ -language: c +language: cpp + compiler: - gcc - clang -# Change this to your needs -script: make && make check + +before_install: + # g++4.8.1 + - if [ "$CXX" == "g++" ]; then sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test; fi + # clang 3.4 + - if [ "$CXX" == "clang++" ]; then sudo add-apt-repository -y ppa:h-rayflood/llvm; fi + - sudo apt-get update -qq + +install: + # g++4.8.1 + - if [ "$CXX" = "g++" ]; then sudo apt-get install -qq g++-4.8; fi + - if [ "$CXX" = "g++" ]; then export CXX="g++-4.8"; fi + # clang 3.4 + - if [ "$CXX" == "clang++" ]; then sudo apt-get install --allow-unauthenticated -qq clang-3.4; fi + - if [ "$CXX" == "clang++" ]; then export CXX="clang++-3.4"; fi + +script: + - make test + notifications: irc: channels: diff --git a/BSDmakefile b/BSDmakefile deleted file mode 100644 index 44a742a..0000000 --- a/BSDmakefile +++ /dev/null @@ -1,130 +0,0 @@ -# -# This is the Makefile for the BSD flavor -# -.include "include.mk" - -GITTEST != git describe --always 2>/dev/null -VALTEST != valgrind --version 2>/dev/null -GITINFO := - -.if $(GITTEST) - GITINFO != git describe --always -.endif - -.if $(CC) == clang - CFLAGS += -Weverything\ - -Wno-padded\ - -Wno-format-nonliteral\ - -Wno-disabled-macro-expansion\ - -Wno-conversion\ - -Wno-float-equal\ - -Wno-unknown-warning-option\ - -Wno-cast-align\ - -Wno-assign-enum\ - -Wno-empty-body\ - -Wno-date-time\ - -pedantic-errors -.else -. if $(CC) != g++ - CFLAGS += -Wmissing-prototypes -Wstrict-prototypes -. endif - -. if $(CC) != tcc - CFLAGS += -pedantic-errors -. else - CFLAGS += -Wno-pointer-sign -fno-common -. endif -.endif - -.if !$(VALTEST) - CFLAGS += -DNVALGRIND -.endif - - -CFLAGS += -DGMQCC_GITINFO=\"$(GITINFO)\" $(OPTIONAL) -DEPS != for i in $(OBJ_C) $(OBJ_P) $(OBJ_T) $(OBJ_X); do echo $$i; done | sort | uniq - -QCVM = qcvm -GMQCC = gmqcc -TESTSUITE = testsuite -PAK = gmqpak - -#standard rules -c.o: ${.IMPSRC} - $(CC) -c ${.IMPSRC} -o ${.TARGET} $(CFLAGS) $(CPPFLAGS) - -$(QCVM): $(OBJ_X) - $(CC) -o ${.TARGET} ${.IMPSRC} $(LDFLAGS) $(LIBS) $(OBJ_X) - -$(GMQCC): $(OBJ_C) - $(CC) -o ${.TARGET} ${.IMPSRC} $(LDFLAGS) $(LIBS) $(OBJ_C) - -$(TESTSUITE): $(OBJ_T) - $(CC) -o ${.TARGET} ${.IMPSRC} $(LDFLAGS) $(LIBS) $(OBJ_T) - -$(PAK): $(OBJ_P) - $(CC) -o ${.TARGET} ${.IMPSRC} $(LDFLAGS) $(OBJ_P) - -all: $(GMQCC) $(QCVM) $(TESTSUITE) $(PAK) - -check: all - @ ./$(TESTSUITE) -test: all - @ ./$(TESTSUITE) - -strip: $(GMQCC) $(QCVM) $(TESTSUITE) - strip $(GMQCC) - strip $(QCVM) - strip $(TESTSUITE) - -clean: - rm -rf *.o $(GMQCC) $(QCVM) $(TESTSUITE) $(PAK) *.dat gource.mp4 *.exe gm-qcc.tgz ./cov-int - -depend: - @makedepend -Y -f BSDmakefile -w 65536 2> /dev/null ${DEPS:C/\.o/.c/g} - -coverity: - @cov-build --dir cov-int $(MAKE) -f BSDmakefile - @tar czf gm-qcc.tgz cov-int - @rm -rf cov-int - @echo gm-qcc.tgz generated, submit for analysis - -install: install-gmqcc install-qcvm install-gmqpak install-doc -install-gmqcc: $(GMQCC) - install -d -m755 $(DESTDIR)$(BINDIR) - install -m755 $(GMQCC) $(DESTDIR)$(BINDIR)/$(GMQCC) -install-qcvm: $(QCVM) - install -d -m755 $(DESTDIR)$(BINDIR) - install -m755 $(QCVM) $(DESTDIR)$(BINDIR)/$(QCVM) -install-gmqpak: $(PAK) - install -d -m755 $(DESTDIR)$(BINDIR) - install -m755 $(PAK) $(DESTDIR)$(BINDIR)/$(PAK) -install-doc: - install -d -m755 $(DESTDIR)$(MANDIR)/man1 - install -m644 doc/gmqcc.1 $(DESTDIR)$(MANDIR)/man1/ - install -m644 doc/qcvm.1 $(DESTDIR)$(MANDIR)/man1/ - install -m644 doc/gmqpak.1 $(DESTDIR)$(MANDIR)/man1/ - -# DO NOT DELETE - -ansi.o: platform.h gmqcc.h opts.def -ast.o: gmqcc.h opts.def ast.h ir.h parser.h lexer.h -code.o: gmqcc.h opts.def -conout.o: gmqcc.h opts.def -correct.o: gmqcc.h opts.def -exec.o: gmqcc.h opts.def -fold.o: ast.h ir.h gmqcc.h opts.def parser.h lexer.h -fs.o: gmqcc.h opts.def platform.h -ftepp.o: gmqcc.h opts.def lexer.h -hash.o: gmqcc.h opts.def -intrin.o: parser.h gmqcc.h opts.def lexer.h ast.h ir.h -ir.o: gmqcc.h opts.def ir.h -lexer.o: gmqcc.h opts.def lexer.h -main.o: gmqcc.h opts.def lexer.h -opts.o: gmqcc.h opts.def -pak.o: gmqcc.h opts.def -parser.o: parser.h gmqcc.h opts.def lexer.h ast.h ir.h -stat.o: gmqcc.h opts.def -test.o: gmqcc.h opts.def platform.h -utf8.o: gmqcc.h opts.def -util.o: gmqcc.h opts.def platform.h diff --git a/CHANGES b/CHANGES deleted file mode 100644 index 1dfa59d..0000000 --- a/CHANGES +++ /dev/null @@ -1,195 +0,0 @@ -Release 0.3.5: - * Preprocessor: - - Added warning for when preprocessor directives are used in - a macro. - * Language: - - Added vector bit operations. - - Added vector cross product operator `><` for -std=gmqcc. - - Added `[[eraseable]]` and `[[accumulation]]` attributes. - - Removed &~= operator for -std=gmqcc. - - #pragmas which are ignored are actually ignored to EOL. - * Compilation: - - Fixed constant folding. - - Fixed column printing in diagnostics. - - Added support for columns in LNOF. - - Shadowed locals fix. - - Improved warning diagnostics for precedence usage and other - various diagnostics. - - Added constant folding logic for builtin functions. - - Prevent divide by zero in constant folding. - - Added unary elision optimization, cases where a unary expression - cancels itself out, e.g `-(-a)` now simplifies to `a`. - - Added strength reduce optimization, for divisions e.g `(A / 10)` - now strength reduces to `(A * (1 / 10)` where `(1 / 10)` is - evaluated at compile time. - - Added superfluous expression elision optimization, e.g `(A + 0), - (A - 0), (A * 1), (A / 1)` can be simplified to just `A`. - - Quake world now compiles with -std=qcc - - Constant folding for `if than` now works for ternary expressions - too. - - Fixed `[[alias]]` attribute type checking, now you can't alias - invalid types. - * QCVM: - - Properly exits on error now. - * Lexer: - - Now prints the character the lexer fails on instead of the - token value. - * Testsuite: - - Important fixes to the testsuite (removal of false positives - and false negitives). - - Added a new utility `check-proj.sh` which downloads various Quake - mods and attempts to compile them. - * Commandline: - - Made -fshort-logic, -funtyped-nil and -fvariadic-args default - for -std=gmqcc. - * Build: - - Generate PDFs of man documents for Windows releases. - - Archlinux/Archbsd package now generates MTREE. - * Documentation: - - Fixed mdoc(s). - * Misc: - - Added some export scripts for xonotic and nexuiz which generate - specially crafted packages that are used by the check-proj script. - -2013-08-20 v0.3.0 - * Language: - - Return assignments, the ability to assign to the return keyword - as if it were a local variable. - - Added bitwise XOR operator (^) - - Array initializers: e.g float a[] = {1, 2, 3}; - - Fix bug that dissalowed language operators to be used in strings. - * Compilation: - - Optimized memory usage (now uses on average %3 less memory for - compilation). - - Fixed dotranslate (translatable strings) - - Rewrote constant folding optimization pass for the parser. - - New additional dead-code-elimination-consatant-fold pass for - if statements whos expression can be evaluated at compile-time - (allowing the if/else branch to be entierly elided at compile-time). - - Added support for columns in error diagnostics. - - Limit corrector to <= 16 byte strings. - - Improved hash function for hashtable (old hash function had 15% error, - this speeds up compilation) - - Improved performance of in-house allocator with branch-hinting, speeds - up compilation of Xonotic by 3 seconds! - * QCVM: - - Escape strings for -printdefs - * Commandline: - - Added statistic dumps, gives information about the number of used - hashtables, vectors, and number of unique sizes of vectors and - hashtables. The amount of memory used for vectors. As well as the - number of strdups used in total for compilation. - - Added compile statistic dumps, gives information about the compiled - binary, and LNO, such as the size, CRC, the number of times a - specific optimization was applied, etc. - - Make -std=qcc default. - * Testsuite: - - Fixed a floating point exception rasied by modulo operation in - -memchk. - - Added support for the test-suite to source tests and task-template - files from subdirectories in the test/ directory. - - Now prints the number of failed tests (if any) after all tests - are attempted. - - Fixed some bugs with error handling resulting in false-positives. - * Build: - - Can now be compiled with TCC (Tiny C compiler) and function as - intended (previously couldn't due to bug in TCC codegen). - - Added Gentoo ebuilds. - - Added Win32 Makefile for building Win32 packages. - - Added Slackware pkg build files. - - Added Fedora spec files. - - Added Makefile for the BSD make variant. - * Misc: - - Added valgrind memcheck hook to in-house allocator to help aid - in finding invalid reads/writes, and more accurate leaks. - -2012-04-27 v0.2.9 - * Preprocessor: - - __VA_ARGS__ support - _ __VA_ARGS__ indexing - - Predefined macros like __DATE__, __TIME__, ... - (check the manpage for a full list). - - Signed numbers as single token in the. - - Fixes some issues with #if operations on macros. - - Speed improvements. - * Language: - - Untyped `nil` keyword. - - Removed the `noreturn` keyword. - - Added generic attribute syntax and reintroduced `noreturn` - as [[noreturn]]. - - Added [[deprecated]] and [[deprecated("message")]]. - - Support for `static` variables in functions. - - Support for labeled loops. - - UTF-8 Support. - - enum support: without enum-types - (ie no `typedef enum { } foo;`). - - Accessing vector components via the dot operator on all - expressions. Eg: `(3 * v).y`. - - Type restricted variadict parameters: - ie: `void print(string...);`. - - Accessing varargs from QC via: `...(index, type)` - - New operators: ** (exponentiation), % (modulo), etc. - - Enumeration attributes: `flag`, `reverse`. - * Compilation: - - Various optimizations and progs-size reductions. - - A new spell-checking algorithm tries to hint you at existing - variables on error. - - Some problems with VM related vector-instructions issues - have been solved in both DP and our own executor. A new - compatbility option (enabled by default) has been added for - now: -flegacy-vector-maths. - - Compiler intrinsics: __builtin_floor, __builtin_mod, - __builtin_exp, __builtin_isnan. - - Improved memory tracing. - - Speed improvements. - * QCVM: - - Improved commandline argument handling. - - More builtins: sqrt(), normalize(), floor(). - * Commandline: - - Nicer memory dumps. - - Support for making individual warnings an error - - via -Werror-. - - added --add-info. - * Testsuite: - - Support for QCFLAGS to run tests with several additional - flags. - - Added support for preprocessor tests. - - Added preprocessor tests. - - Added defs.qh (auto included) for qcvm definitions. - * Syntax Highlighting: - - Added various syntax highlighting description files for - various text editors / integrated development envirorments, - including support for: geany, kate, kwrite, kdevelop, QtCreator, - gtksourceview, gedit, sany, nano, jedit. - * Build: - - Build scripts for building debian, archlinux and archbsd - packages for x86, and x86_64. - - Makefile targets for gource visualization, and render of - gource visualization. - - -2012-12-27 Hotfix v0.2.2 - * Liferanges - * Crashes - -2012-12-23 Hotfix v0.2.1 - * General bugfixes - -2012-12-23 Release 0.2 - * Preprocessor: - - Added xonotic compatible preprocessor. - * Language - - Basic xonotic compatibility. - - Array support. - - Added fteqcc's string escape sequences. - - Support for `noref`. - - Support for `goto` with labels like in fteqcc. - - `break` and `continue`. - - Short circuit logic. - - Support for translatable strings via _("str") like in - fteqcc. - * Compilation - - Warnings about uninitialized values. - -2012-11-17 Release 0.1 - * Compiles id1 code. diff --git a/INSTALL b/INSTALL deleted file mode 100644 index ffd45bd..0000000 --- a/INSTALL +++ /dev/null @@ -1,62 +0,0 @@ - Installing gmqcc - -1. Prerequisites - - A C-Compiler such as gcc or clang - - GNU Make or BSD Make - -2. Compilation - If using GNU make program - make - - If using BSD make program, the BSDmakefile should be - used instead when you invoke make, if however it ignores BSDmakefile - you can supply it with -f. - - make -f BSDmakefile - - If no error appears, the following binary files will have been - created: - - gmqcc - - qcvm - - gmqpak - -3. Installation - The `install' target will install the 2 binaries to /usr/local/bin - by default. - The Makefile honors the following variables: - - - DESTDIR: The installation directory root. - - PREFIX: The installation prefix, default: /usr/local - - BINDIR: Directory for binary executables, - deafult: $PREFIX/bin - - To install to /usr/local run: - - make install - - To install to /usr run: - - make PREFIX=/usr install - - To install to a package-staging directory such as $pkgdir when - writing a build script file: - - make DESTDIR=$pkgdir install - - - ArchLinux PKGBUILDs (release and git build) can be found in the - respective folders in ./distro/archlinux - - ArchBSD PKGBUILDs (release and git build) can be found in the - respective folders in ./distro/archbsd - - Slackware SlackBuilds (git build) can be found in ./distro/slackware - - Gentoo ebuilds (release) can be found in ./distro/gentoo, as well - as a README explaining how to build them - - Debian archives (git build) can be created invoking make in - ./distro/deb - - Fedora spec files (release) can be found in ./distro/fedora, as well - as a README explaining how to build them. diff --git a/Makefile b/Makefile index 530bd4c..c6a6327 100644 --- a/Makefile +++ b/Makefile @@ -1,167 +1,74 @@ -include include.mk - -UNAME ?= $(shell uname) -CYGWIN = $(findstring CYGWIN, $(UNAME)) -MINGW = $(findstring MINGW, $(UNAME)) - -# turn on tons of warnings if clang is present -# but also turn off the STUPID ONES -ifeq ($(CC), clang) - CFLAGS += \ - -Weverything \ - -Wno-padded \ - -Wno-format-nonliteral \ - -Wno-disabled-macro-expansion \ - -Wno-conversion \ - -Wno-float-equal \ - -Wno-unknown-warning-option \ - -Wno-cast-align \ - -Wno-assign-enum \ - -Wno-empty-body \ - -Wno-date-time \ - -pedantic-errors -else - ifneq ($(CC), g++) - CFLAGS += -Wmissing-prototypes -Wstrict-prototypes - endif - - ifneq ($(CC), tcc) - CFLAGS += -pedantic-errors - else - CFLAGS += -Wno-pointer-sign -fno-common - endif -endif - -ifneq ($(shell git describe --always 2>/dev/null),) - CFLAGS += -DGMQCC_GITINFO="\"$(shell git describe --always)\"" -endif - -ifeq ($(shell valgrind --version 2>/dev/null),) - CFLAGS += -DNVALGRIND -endif - -# do this last otherwise there is whitespace in the command output and -# it makes my OCD act up -CFLAGS += $(OPTIONAL_CFLAGS) -LDFLAGS += $(OPTIONAL_LDFLAGS) - -#we have duplicate object files when dealing with creating a simple list -#for dependinces. To combat this we use some clever recrusive-make to -#filter the list and remove duplicates which we use for make depend -RMDUP = $(if $1,$(firstword $1) $(call RMDUP,$(filter-out $(firstword $1),$1))) -DEPS := $(call RMDUP, $(OBJ_P) $(OBJ_T) $(OBJ_C) $(OBJ_X)) - -ifneq ("$(CYGWIN)", "") - #nullify the common variables that - #most *nix systems have (for windows) - PREFIX := - BINDIR := - DATADIR := - MANDIR := - QCVM = qcvm.exe - GMQCC = gmqcc.exe - TESTSUITE = testsuite.exe - PAK = gmqpak.exe - CFLAGS += -DNVALGRIND -else -ifneq ("$(MINGW)", "") - #nullify the common variables that - #most *nix systems have (for windows) - PREFIX := - BINDIR := - DATADIR := - MANDIR := - QCVM = qcvm.exe - GMQCC = gmqcc.exe - TESTSUITE = testsuite.exe - PAK = gmqpak.exe - CFLAGS += -DNVALGRIND -else - QCVM = qcvm - GMQCC = gmqcc - TESTSUITE = testsuite - PAK = gmqpak -endif -endif - -#standard rules -c.o: - $(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS) - -$(QCVM): $(OBJ_X) - $(CC) -o $@ $^ $(LDFLAGS) $(LIBS) - -$(GMQCC): $(OBJ_C) $(OBJ_D) - $(CC) -o $@ $^ $(LDFLAGS) $(LIBS) - -$(TESTSUITE): $(OBJ_T) - $(CC) -o $@ $^ $(LDFLAGS) $(LIBS) - -$(PAK): $(OBJ_P) - $(CC) -o $@ $^ $(LDFLAGS) - -all: $(GMQCC) $(QCVM) $(TESTSUITE) $(PAK) - -check: all - @ ./$(TESTSUITE) -test: all - @ ./$(TESTSUITE) - -strip: $(GMQCC) $(QCVM) $(TESTSUITE) - strip $(GMQCC) - strip $(QCVM) - strip $(TESTSUITE) +CXX ?= clang++ +CXXFLAGS = \ + -std=c++11 \ + -Wall \ + -Wextra \ + -fno-exceptions \ + -fno-rtti \ + -MD \ + -g3 + +CSRCS = \ + ast.cpp \ + code.cpp \ + conout.cpp \ + fold.cpp \ + ftepp.cpp \ + intrin.cpp \ + ir.cpp \ + lexer.cpp \ + main.cpp \ + opts.cpp \ + parser.cpp \ + stat.cpp \ + utf8.cpp \ + util.cpp + +TSRCS = \ + conout.cpp \ + opts.cpp \ + stat.cpp \ + test.cpp \ + util.cpp + +VSRCS = \ + exec.cpp \ + stat.cpp \ + util.cpp + +COBJS = $(CSRCS:.cpp=.o) +TOBJS = $(TSRCS:.cpp=.o) +VOBJS = $(VSRCS:.cpp=.o) + +CDEPS = $(CSRCS:.cpp=.d) +TDEPS = $(TSRCS:.cpp=.d) +VDEPS = $(VSRCS:.cpp=.d) + +CBIN = gmqcc +TBIN = testsuite +VBIN = qcvm + +all: $(CBIN) $(TBIN) $(VBIN) + +$(CBIN): $(COBJS) + $(CXX) $(COBJS) -o $@ + +$(TBIN): $(TOBJS) + $(CXX) $(TOBJS) -o $@ + +$(VBIN): $(VOBJS) + $(CXX) $(VOBJS) -o $@ + +.cpp.o: + $(CXX) -c $(CXXFLAGS) $< -o $@ + +test: $(CBIN) $(TBIN) $(VBIN) + @./$(TBIN) clean: - rm -rf *.o $(GMQCC) $(QCVM) $(TESTSUITE) $(PAK) *.dat gource.mp4 *.exe gm-qcc.tgz ./cov-int - -depend: - @ makedepend -Y -w 65536 2> /dev/null $(subst .o,.c,$(DEPS)) - - -coverity: - @cov-build --dir cov-int $(MAKE) - @tar czf gm-qcc.tgz cov-int - @rm -rf cov-int - @echo gm-qcc.tgz generated, submit for analysis - -#install rules -install: install-gmqcc install-qcvm install-gmqpak install-doc -install-gmqcc: $(GMQCC) - install -d -m755 $(DESTDIR)$(BINDIR) - install -m755 $(GMQCC) $(DESTDIR)$(BINDIR)/$(GMQCC) -install-qcvm: $(QCVM) - install -d -m755 $(DESTDIR)$(BINDIR) - install -m755 $(QCVM) $(DESTDIR)$(BINDIR)/$(QCVM) -install-gmqpak: $(PAK) - install -d -m755 $(DESTDIR)$(BINDIR) - install -m755 $(PAK) $(DESTDIR)$(BINDIR)/$(PAK) -install-doc: - install -d -m755 $(DESTDIR)$(MANDIR)/man1 - install -m644 doc/gmqcc.1 $(DESTDIR)$(MANDIR)/man1/ - install -m644 doc/qcvm.1 $(DESTDIR)$(MANDIR)/man1/ - install -m644 doc/gmqpak.1 $(DESTDIR)$(MANDIR)/man1/ - -# DO NOT DELETE + rm -f *.d + rm -f $(COBJS) $(CDEPS) $(CBIN) + rm -f $(TOBJS) $(TDEPS) $(TBIN) + rm -f $(VOBJS) $(VDEPS) $(VBIN) -ansi.o: platform.h gmqcc.h opts.def -util.o: gmqcc.h opts.def platform.h -hash.o: gmqcc.h opts.def -stat.o: gmqcc.h opts.def -fs.o: gmqcc.h opts.def platform.h -opts.o: gmqcc.h opts.def -conout.o: gmqcc.h opts.def -pak.o: gmqcc.h opts.def -test.o: gmqcc.h opts.def platform.h -main.o: gmqcc.h opts.def lexer.h -lexer.o: gmqcc.h opts.def lexer.h -parser.o: parser.h gmqcc.h opts.def lexer.h ast.h ir.h -code.o: gmqcc.h opts.def -ast.o: gmqcc.h opts.def ast.h ir.h parser.h lexer.h -ir.o: gmqcc.h opts.def ir.h -ftepp.o: gmqcc.h opts.def lexer.h -utf8.o: gmqcc.h opts.def -correct.o: gmqcc.h opts.def -fold.o: ast.h ir.h gmqcc.h opts.def parser.h lexer.h -intrin.o: parser.h gmqcc.h opts.def lexer.h ast.h ir.h -exec.o: gmqcc.h opts.def +-include *.d diff --git a/PORTING b/PORTING deleted file mode 100644 index bbdb274..0000000 --- a/PORTING +++ /dev/null @@ -1,4 +0,0 @@ -Porting gmqcc to a new platform is farily trivial, in most cases ansi.c -will be sufficent enough to get it to run on your favorite platform. If -however it isn't you can duplicate ansi.c and change it accordingly. -Changes to platform.h may also be required. diff --git a/README b/README index 36e1cf7..993b8fb 100644 --- a/README +++ b/README @@ -1,18 +1 @@ -GMQCC: An improved Quake C compiler - -For licensing: see the LICENSE file. -For installation notes: see the INSTALL file. -For a list of authors: see the AUTHORS file. -For a list of changes: see the CHANGES file. - -For documentation: - See the manpages, or visit the documentation online at - http://graphitemaster.github.com/gmqcc/doc.html - -For syntax highlighting description files, or information -regarding how to install them: - See the README in syntax directory - -For description on porting GMQCC to other platforms, or information -on how to approach porting GMQCC to more 'exotic' platforms: - See the PORTING file. +An improved QuakeC compiler diff --git a/TODO b/TODO deleted file mode 100644 index 356c4b8..0000000 --- a/TODO +++ /dev/null @@ -1,64 +0,0 @@ -GMQCC is quite feature complete. But that doesn't address the fact that -it can be improved. This is a list of things that we'd like to support -in the distant future. When the time comes, we can just select a topic -from here and open a ticket for it on the issue tracker. But for the -meantime, this is sort of a cultivating flat file database. - -Optimizations: - The following are optimizations that can be implemented after the - transformation into static-single assignment (SSA). - - Global Value Numbering: - Eliminate redundancy by constructing a value graph of the source - then determining which values are computed by equivalent expressions. - Similar to Common Subexpression Elimination (CSE), however expressions - are determined via underlying equivalence, opposed to lexically identical - expressions (CSE). - - The following are optimizations that can be implemented before the - transformation into a binary (code generator). - - Code factoring: - The process of finding sequences of code that are identical, - or can be parameterized or reordered to be identical. - Which can be replaced with calls to a shared subroutine. To - reduce duplicated code. (Size optimization) - -Language Features: - The following are language features that we'd like to see implemented in the - future. - - AST Macros: - Macros with sanity. Not textual substiution. - - Classes: - Like C++, but minus the stupidity: - - No type operator overloads - - Keep operator overloading for basic operators though. - - No inheritance - - No virtuals / pure virtuals - - Essentially "C structs but with operators" :) - - Overloaded Functions: - Ability to make individual functions with the same name, but take - different amount of arguments or type of arguments. - - Default Argument Substiution: - Ability to specify default values for arguments in functions. - void foo(string bar, string baz="default"); - Supplying just one argument will expand the second argument to - become "default", otherwise if two arguments are specified then - the "default" string is overrode with what ever the user passes. - - Namespaces: - There is already a ticket open on this. They'd work just like C++ - identically even. - -Testsuite: - The following are things we'd like to see added to the testsuite - in the distant future: - - Interface: - Ability to select individual tests, or set parameters manually - opposed to using the static task-template files. (A method to - override them rather). diff --git a/algo.h b/algo.h new file mode 100644 index 0000000..3218b1b --- /dev/null +++ b/algo.h @@ -0,0 +1,18 @@ +#ifndef GMQCC_ALGO_HDR +#define GMQCC_ALGO_HDR + +namespace algo { + +template +void shiftback(ITER element, ITER end) { + //typename ITER::value_type backup(move(*element)); // hold the element + typename std::remove_reference::type backup(move(*element)); // hold the element + ITER p = element++; + for (; element != end; p = element++) + *p = move(*element); + *p = move(backup); +} + +} // ::algo + +#endif diff --git a/ansi.c b/ansi.c deleted file mode 100644 index 1317e15..0000000 --- a/ansi.c +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * 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. - */ -#define GMQCC_PLATFORM_HEADER -#include -#include - -#include "platform.h" -#include "gmqcc.h" - -int platform_vsnprintf(char *buffer, size_t bytes, const char *format, va_list arg) { - return vsnprintf(buffer, bytes, format, arg); -} - -int platform_vsscanf(const char *str, const char *format, va_list arg) { - return vsscanf(str, format, arg); -} - -const struct tm *platform_localtime(const time_t *timer) { - return localtime(timer); -} - -const char *platform_ctime(const time_t *timer) { - return ctime(timer); -} - -char *platform_strncat(char *dest, const char *src, size_t num) { - return strncat(dest, src, num); -} - -const char *platform_getenv(const char *var) { - return getenv(var); -} - -int platform_vasprintf(char **dat, const char *fmt, va_list args) { - int ret; - int len; - char *tmp = NULL; - char buf[128]; - va_list cpy; - - va_copy(cpy, args); - len = vsnprintf(buf, sizeof(buf), fmt, cpy); - va_end (cpy); - - if (len < 0) - return len; - - if (len < (int)sizeof(buf)) { - *dat = util_strdup(buf); - return len; - } - - tmp = (char*)mem_a(len + 1); - if ((ret = vsnprintf(tmp, len + 1, fmt, args)) != len) { - mem_d(tmp); - *dat = NULL; - return -1; - } - - *dat = tmp; - return len; -} - -char *platform_strcat(char *dest, const char *src) { - return strcat(dest, src); -} - -char *platform_strncpy(char *dest, const char *src, size_t num) { - return strncpy(dest, src, num); -} - -const char *platform_strerror(int err) { - return strerror(err); -} - -FILE *platform_fopen(const char *filename, const char *mode) { - return fopen(filename, mode); -} - -size_t platform_fread(void *ptr, size_t size, size_t count, FILE *stream) { - return fread(ptr, size, count, stream); -} - -size_t platform_fwrite(const void *ptr, size_t size, size_t count, FILE *stream) { - return fwrite(ptr, size, count, stream); -} - -int platform_fflush(FILE *stream) { - return fflush(stream); -} - -int platform_vfprintf(FILE *stream, const char *format, va_list arg) { - return vfprintf(stream, format, arg); -} - -int platform_fclose(FILE *stream) { - return fclose(stream); -} - -int platform_ferror(FILE *stream) { - return ferror(stream); -} - -int platform_fgetc(FILE *stream) { - return fgetc(stream); -} - -int platform_fputs(const char *str, FILE *stream) { - return fputs(str, stream); -} - -int platform_fseek(FILE *stream, long offset, int origin) { - return fseek(stream, offset, origin); -} - -long platform_ftell(FILE *stream) { - return ftell(stream); -} - -int platform_mkdir(const char *path, int mode) { - /* - * For some reason mingw32 just doesn't have a correct mkdir impl - * so we handle that here. - */ -# ifdef _WIN32 - (void)mode; - return mkdir(path); -# else - return mkdir(path, mode); -# endif /*!_WIN32*/ -} - -DIR *platform_opendir(const char *path) { - return opendir(path); -} - -int platform_closedir(DIR *dir) { - return closedir(dir); -} - -struct dirent *platform_readdir(DIR *dir) { - return readdir(dir); -} - -int platform_isatty(int fd) { - return isatty(fd); -} diff --git a/ast.c b/ast.c deleted file mode 100644 index 4a07cc2..0000000 --- a/ast.c +++ /dev/null @@ -1,3486 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * Wolfgang Bumiller - * 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 -#include - -#include "gmqcc.h" -#include "ast.h" -#include "parser.h" - -#define ast_instantiate(T, ctx, destroyfn) \ - T* self = (T*)mem_a(sizeof(T)); \ - if (!self) { \ - return NULL; \ - } \ - ast_node_init((ast_node*)self, ctx, TYPE_##T); \ - ( (ast_node*)self )->destroy = (ast_node_delete*)destroyfn - -/* - * forward declarations, these need not be in ast.h for obvious - * static reasons. - */ -static bool ast_member_codegen(ast_member*, ast_function*, bool lvalue, ir_value**); -static void ast_array_index_delete(ast_array_index*); -static bool ast_array_index_codegen(ast_array_index*, ast_function*, bool lvalue, ir_value**); -static void ast_argpipe_delete(ast_argpipe*); -static bool ast_argpipe_codegen(ast_argpipe*, ast_function*, bool lvalue, ir_value**); -static void ast_store_delete(ast_store*); -static bool ast_store_codegen(ast_store*, ast_function*, bool lvalue, ir_value**); -static void ast_ifthen_delete(ast_ifthen*); -static bool ast_ifthen_codegen(ast_ifthen*, ast_function*, bool lvalue, ir_value**); -static void ast_ternary_delete(ast_ternary*); -static bool ast_ternary_codegen(ast_ternary*, ast_function*, bool lvalue, ir_value**); -static void ast_loop_delete(ast_loop*); -static bool ast_loop_codegen(ast_loop*, ast_function*, bool lvalue, ir_value**); -static void ast_breakcont_delete(ast_breakcont*); -static bool ast_breakcont_codegen(ast_breakcont*, ast_function*, bool lvalue, ir_value**); -static void ast_switch_delete(ast_switch*); -static bool ast_switch_codegen(ast_switch*, ast_function*, bool lvalue, ir_value**); -static void ast_label_delete(ast_label*); -static void ast_label_register_goto(ast_label*, ast_goto*); -static bool ast_label_codegen(ast_label*, ast_function*, bool lvalue, ir_value**); -static bool ast_goto_codegen(ast_goto*, ast_function*, bool lvalue, ir_value**); -static void ast_goto_delete(ast_goto*); -static void ast_call_delete(ast_call*); -static bool ast_call_codegen(ast_call*, ast_function*, bool lvalue, ir_value**); -static bool ast_block_codegen(ast_block*, ast_function*, bool lvalue, ir_value**); -static void ast_unary_delete(ast_unary*); -static bool ast_unary_codegen(ast_unary*, ast_function*, bool lvalue, ir_value**); -static void ast_entfield_delete(ast_entfield*); -static bool ast_entfield_codegen(ast_entfield*, ast_function*, bool lvalue, ir_value**); -static void ast_return_delete(ast_return*); -static bool ast_return_codegen(ast_return*, ast_function*, bool lvalue, ir_value**); -static void ast_binstore_delete(ast_binstore*); -static bool ast_binstore_codegen(ast_binstore*, ast_function*, bool lvalue, ir_value**); -static void ast_binary_delete(ast_binary*); -static bool ast_binary_codegen(ast_binary*, ast_function*, bool lvalue, ir_value**); -static bool ast_state_codegen(ast_state*, ast_function*, bool lvalue, ir_value**); - -/* It must not be possible to get here. */ -static GMQCC_NORETURN void _ast_node_destroy(ast_node *self) -{ - (void)self; - con_err("ast node missing destroy()\n"); - exit(EXIT_FAILURE); -} - -/* Initialize main ast node aprts */ -static void ast_node_init(ast_node *self, lex_ctx_t ctx, int nodetype) -{ - self->context = ctx; - self->destroy = &_ast_node_destroy; - self->keep = false; - self->nodetype = nodetype; - self->side_effects = false; -} - -/* weight and side effects */ -static void _ast_propagate_effects(ast_node *self, ast_node *other) -{ - if (ast_side_effects(other)) - ast_side_effects(self) = true; -} -#define ast_propagate_effects(s,o) _ast_propagate_effects(((ast_node*)(s)), ((ast_node*)(o))) - -/* General expression initialization */ -static void ast_expression_init(ast_expression *self, - ast_expression_codegen *codegen) -{ - self->codegen = codegen; - self->vtype = TYPE_VOID; - self->next = NULL; - self->outl = NULL; - self->outr = NULL; - self->params = NULL; - self->count = 0; - self->varparam = NULL; - self->flags = 0; - if (OPTS_OPTION_BOOL(OPTION_COVERAGE)) - self->flags |= AST_FLAG_BLOCK_COVERAGE; -} - -static void ast_expression_delete(ast_expression *self) -{ - size_t i; - if (self->next) - ast_delete(self->next); - for (i = 0; i < vec_size(self->params); ++i) { - ast_delete(self->params[i]); - } - vec_free(self->params); - if (self->varparam) - ast_delete(self->varparam); -} - -static void ast_expression_delete_full(ast_expression *self) -{ - ast_expression_delete(self); - mem_d(self); -} - -ast_value* ast_value_copy(const ast_value *self) -{ - size_t i; - const ast_expression *fromex; - ast_expression *selfex; - ast_value *cp = ast_value_new(self->expression.node.context, self->name, self->expression.vtype); - if (self->expression.next) { - cp->expression.next = ast_type_copy(self->expression.node.context, self->expression.next); - } - fromex = &self->expression; - selfex = &cp->expression; - selfex->count = fromex->count; - selfex->flags = fromex->flags; - for (i = 0; i < vec_size(fromex->params); ++i) { - ast_value *v = ast_value_copy(fromex->params[i]); - vec_push(selfex->params, v); - } - return cp; -} - -void ast_type_adopt_impl(ast_expression *self, const ast_expression *other) -{ - size_t i; - const ast_expression *fromex; - ast_expression *selfex; - self->vtype = other->vtype; - if (other->next) { - self->next = (ast_expression*)ast_type_copy(ast_ctx(self), other->next); - } - fromex = other; - selfex = self; - selfex->count = fromex->count; - selfex->flags = fromex->flags; - for (i = 0; i < vec_size(fromex->params); ++i) { - ast_value *v = ast_value_copy(fromex->params[i]); - vec_push(selfex->params, v); - } -} - -static ast_expression* ast_shallow_type(lex_ctx_t ctx, int vtype) -{ - ast_instantiate(ast_expression, ctx, ast_expression_delete_full); - ast_expression_init(self, NULL); - self->codegen = NULL; - self->next = NULL; - self->vtype = vtype; - return self; -} - -ast_expression* ast_type_copy(lex_ctx_t ctx, const ast_expression *ex) -{ - size_t i; - const ast_expression *fromex; - ast_expression *selfex; - - if (!ex) - return NULL; - else - { - ast_instantiate(ast_expression, ctx, ast_expression_delete_full); - ast_expression_init(self, NULL); - - fromex = ex; - selfex = self; - - /* This may never be codegen()d */ - selfex->codegen = NULL; - - selfex->vtype = fromex->vtype; - if (fromex->next) - selfex->next = ast_type_copy(ctx, fromex->next); - else - selfex->next = NULL; - - selfex->count = fromex->count; - selfex->flags = fromex->flags; - for (i = 0; i < vec_size(fromex->params); ++i) { - ast_value *v = ast_value_copy(fromex->params[i]); - vec_push(selfex->params, v); - } - - return self; - } -} - -bool ast_compare_type(ast_expression *a, ast_expression *b) -{ - if (a->vtype == TYPE_NIL || - b->vtype == TYPE_NIL) - return true; - if (a->vtype != b->vtype) - return false; - if (!a->next != !b->next) - return false; - if (vec_size(a->params) != vec_size(b->params)) - return false; - if ((a->flags & AST_FLAG_TYPE_MASK) != - (b->flags & AST_FLAG_TYPE_MASK) ) - { - return false; - } - if (vec_size(a->params)) { - size_t i; - for (i = 0; i < vec_size(a->params); ++i) { - if (!ast_compare_type((ast_expression*)a->params[i], - (ast_expression*)b->params[i])) - return false; - } - } - if (a->next) - return ast_compare_type(a->next, b->next); - return true; -} - -static size_t ast_type_to_string_impl(ast_expression *e, char *buf, size_t bufsize, size_t pos) -{ - const char *typestr; - size_t typelen; - size_t i; - - if (!e) { - if (pos + 6 >= bufsize) - goto full; - util_strncpy(buf + pos, "(null)", 6); - return pos + 6; - } - - if (pos + 1 >= bufsize) - goto full; - - switch (e->vtype) { - case TYPE_VARIANT: - util_strncpy(buf + pos, "(variant)", 9); - return pos + 9; - - case TYPE_FIELD: - buf[pos++] = '.'; - return ast_type_to_string_impl(e->next, buf, bufsize, pos); - - case TYPE_POINTER: - if (pos + 3 >= bufsize) - goto full; - buf[pos++] = '*'; - buf[pos++] = '('; - pos = ast_type_to_string_impl(e->next, buf, bufsize, pos); - if (pos + 1 >= bufsize) - goto full; - buf[pos++] = ')'; - return pos; - - case TYPE_FUNCTION: - pos = ast_type_to_string_impl(e->next, buf, bufsize, pos); - if (pos + 2 >= bufsize) - goto full; - if (!vec_size(e->params)) { - buf[pos++] = '('; - buf[pos++] = ')'; - return pos; - } - buf[pos++] = '('; - pos = ast_type_to_string_impl((ast_expression*)(e->params[0]), buf, bufsize, pos); - for (i = 1; i < vec_size(e->params); ++i) { - if (pos + 2 >= bufsize) - goto full; - buf[pos++] = ','; - buf[pos++] = ' '; - pos = ast_type_to_string_impl((ast_expression*)(e->params[i]), buf, bufsize, pos); - } - if (pos + 1 >= bufsize) - goto full; - buf[pos++] = ')'; - return pos; - - case TYPE_ARRAY: - pos = ast_type_to_string_impl(e->next, buf, bufsize, pos); - if (pos + 1 >= bufsize) - goto full; - buf[pos++] = '['; - pos += util_snprintf(buf + pos, bufsize - pos - 1, "%i", (int)e->count); - if (pos + 1 >= bufsize) - goto full; - buf[pos++] = ']'; - return pos; - - default: - typestr = type_name[e->vtype]; - typelen = strlen(typestr); - if (pos + typelen >= bufsize) - goto full; - util_strncpy(buf + pos, typestr, typelen); - return pos + typelen; - } - -full: - buf[bufsize-3] = '.'; - buf[bufsize-2] = '.'; - buf[bufsize-1] = '.'; - return bufsize; -} - -void ast_type_to_string(ast_expression *e, char *buf, size_t bufsize) -{ - size_t pos = ast_type_to_string_impl(e, buf, bufsize-1, 0); - buf[pos] = 0; -} - -static bool ast_value_codegen(ast_value *self, ast_function *func, bool lvalue, ir_value **out); -ast_value* ast_value_new(lex_ctx_t ctx, const char *name, int t) -{ - ast_instantiate(ast_value, ctx, ast_value_delete); - ast_expression_init((ast_expression*)self, - (ast_expression_codegen*)&ast_value_codegen); - self->expression.node.keep = true; /* keep */ - - self->name = name ? util_strdup(name) : NULL; - self->expression.vtype = t; - self->expression.next = NULL; - self->isfield = false; - self->cvq = CV_NONE; - self->hasvalue = false; - self->isimm = false; - self->inexact = false; - self->uses = 0; - memset(&self->constval, 0, sizeof(self->constval)); - self->initlist = NULL; - - self->ir_v = NULL; - self->ir_values = NULL; - self->ir_value_count = 0; - - self->setter = NULL; - self->getter = NULL; - self->desc = NULL; - - self->argcounter = NULL; - self->intrinsic = false; - - return self; -} - -void ast_value_delete(ast_value* self) -{ - if (self->name) - mem_d((void*)self->name); - if (self->argcounter) - mem_d((void*)self->argcounter); - if (self->hasvalue) { - switch (self->expression.vtype) - { - case TYPE_STRING: - mem_d((void*)self->constval.vstring); - break; - case TYPE_FUNCTION: - /* unlink us from the function node */ - self->constval.vfunc->vtype = NULL; - break; - /* NOTE: delete function? currently collected in - * the parser structure - */ - default: - break; - } - } - if (self->ir_values) - mem_d(self->ir_values); - - if (self->desc) - mem_d(self->desc); - - if (self->initlist) { - if (self->expression.next->vtype == TYPE_STRING) { - /* strings are allocated, free them */ - size_t i, len = vec_size(self->initlist); - /* in theory, len should be expression.count - * but let's not take any chances */ - for (i = 0; i < len; ++i) { - if (self->initlist[i].vstring) - mem_d(self->initlist[i].vstring); - } - } - vec_free(self->initlist); - } - - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -void ast_value_params_add(ast_value *self, ast_value *p) -{ - vec_push(self->expression.params, p); -} - -bool ast_value_set_name(ast_value *self, const char *name) -{ - if (self->name) - mem_d((void*)self->name); - self->name = util_strdup(name); - return !!self->name; -} - -ast_binary* ast_binary_new(lex_ctx_t ctx, int op, - ast_expression* left, ast_expression* right) -{ - ast_instantiate(ast_binary, ctx, ast_binary_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_binary_codegen); - - if (ast_istype(right, ast_unary) && OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) { - ast_unary *unary = ((ast_unary*)right); - ast_expression *normal = unary->operand; - - /* make a-(-b) => a + b */ - if (unary->op == VINSTR_NEG_F || unary->op == VINSTR_NEG_V) { - if (op == INSTR_SUB_F) { - op = INSTR_ADD_F; - right = normal; - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - } else if (op == INSTR_SUB_V) { - op = INSTR_ADD_V; - right = normal; - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - } - } - } - - self->op = op; - self->left = left; - self->right = right; - self->right_first = false; - - ast_propagate_effects(self, left); - ast_propagate_effects(self, right); - - if (op >= INSTR_EQ_F && op <= INSTR_GT) - self->expression.vtype = TYPE_FLOAT; - else if (op == INSTR_AND || op == INSTR_OR) { - if (OPTS_FLAG(PERL_LOGIC)) - ast_type_adopt(self, right); - else - self->expression.vtype = TYPE_FLOAT; - } - else if (op == INSTR_BITAND || op == INSTR_BITOR) - self->expression.vtype = TYPE_FLOAT; - else if (op == INSTR_MUL_VF || op == INSTR_MUL_FV) - self->expression.vtype = TYPE_VECTOR; - else if (op == INSTR_MUL_V) - self->expression.vtype = TYPE_FLOAT; - else - self->expression.vtype = left->vtype; - - /* references all */ - self->refs = AST_REF_ALL; - - return self; -} - -void ast_binary_delete(ast_binary *self) -{ - if (self->refs & AST_REF_LEFT) ast_unref(self->left); - if (self->refs & AST_REF_RIGHT) ast_unref(self->right); - - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_binstore* ast_binstore_new(lex_ctx_t ctx, int storop, int op, - ast_expression* left, ast_expression* right) -{ - ast_instantiate(ast_binstore, ctx, ast_binstore_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_binstore_codegen); - - ast_side_effects(self) = true; - - self->opstore = storop; - self->opbin = op; - self->dest = left; - self->source = right; - - self->keep_dest = false; - - ast_type_adopt(self, left); - return self; -} - -void ast_binstore_delete(ast_binstore *self) -{ - if (!self->keep_dest) - ast_unref(self->dest); - ast_unref(self->source); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_unary* ast_unary_new(lex_ctx_t ctx, int op, - ast_expression *expr) -{ - ast_instantiate(ast_unary, ctx, ast_unary_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_unary_codegen); - - self->op = op; - self->operand = expr; - - - if (ast_istype(expr, ast_unary) && OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) { - ast_unary *prev = (ast_unary*)((ast_unary*)expr)->operand; - - /* Handle for double negation */ - if (((ast_unary*)expr)->op == op) - prev = (ast_unary*)((ast_unary*)expr)->operand; - - if (ast_istype(prev, ast_unary)) { - ast_expression_delete((ast_expression*)self); - mem_d(self); - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - return prev; - } - } - - ast_propagate_effects(self, expr); - - if ((op >= INSTR_NOT_F && op <= INSTR_NOT_FNC) || op == VINSTR_NEG_F) { - self->expression.vtype = TYPE_FLOAT; - } else if (op == VINSTR_NEG_V) { - self->expression.vtype = TYPE_VECTOR; - } else { - compile_error(ctx, "cannot determine type of unary operation %s", util_instr_str[op]); - } - - return self; -} - -void ast_unary_delete(ast_unary *self) -{ - if (self->operand) ast_unref(self->operand); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_return* ast_return_new(lex_ctx_t ctx, ast_expression *expr) -{ - ast_instantiate(ast_return, ctx, ast_return_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_return_codegen); - - self->operand = expr; - - if (expr) - ast_propagate_effects(self, expr); - - return self; -} - -void ast_return_delete(ast_return *self) -{ - if (self->operand) - ast_unref(self->operand); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_entfield* ast_entfield_new(lex_ctx_t ctx, ast_expression *entity, ast_expression *field) -{ - if (field->vtype != TYPE_FIELD) { - compile_error(ctx, "ast_entfield_new with expression not of type field"); - return NULL; - } - return ast_entfield_new_force(ctx, entity, field, field->next); -} - -ast_entfield* ast_entfield_new_force(lex_ctx_t ctx, ast_expression *entity, ast_expression *field, const ast_expression *outtype) -{ - ast_instantiate(ast_entfield, ctx, ast_entfield_delete); - - if (!outtype) { - mem_d(self); - /* Error: field has no type... */ - return NULL; - } - - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_entfield_codegen); - - self->entity = entity; - self->field = field; - ast_propagate_effects(self, entity); - ast_propagate_effects(self, field); - - ast_type_adopt(self, outtype); - return self; -} - -void ast_entfield_delete(ast_entfield *self) -{ - ast_unref(self->entity); - ast_unref(self->field); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_member* ast_member_new(lex_ctx_t ctx, ast_expression *owner, unsigned int field, const char *name) -{ - ast_instantiate(ast_member, ctx, ast_member_delete); - if (field >= 3) { - mem_d(self); - return NULL; - } - - if (owner->vtype != TYPE_VECTOR && - owner->vtype != TYPE_FIELD) { - compile_error(ctx, "member-access on an invalid owner of type %s", type_name[owner->vtype]); - mem_d(self); - return NULL; - } - - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_member_codegen); - self->expression.node.keep = true; /* keep */ - - if (owner->vtype == TYPE_VECTOR) { - self->expression.vtype = TYPE_FLOAT; - self->expression.next = NULL; - } else { - self->expression.vtype = TYPE_FIELD; - self->expression.next = ast_shallow_type(ctx, TYPE_FLOAT); - } - - self->rvalue = false; - self->owner = owner; - ast_propagate_effects(self, owner); - - self->field = field; - if (name) - self->name = util_strdup(name); - else - self->name = NULL; - - return self; -} - -void ast_member_delete(ast_member *self) -{ - /* The owner is always an ast_value, which has .keep=true, - * also: ast_members are usually deleted after the owner, thus - * this will cause invalid access - ast_unref(self->owner); - * once we allow (expression).x to access a vector-member, we need - * to change this: preferably by creating an alternate ast node for this - * purpose that is not garbage-collected. - */ - ast_expression_delete((ast_expression*)self); - mem_d(self->name); - mem_d(self); -} - -bool ast_member_set_name(ast_member *self, const char *name) -{ - if (self->name) - mem_d((void*)self->name); - self->name = util_strdup(name); - return !!self->name; -} - -ast_array_index* ast_array_index_new(lex_ctx_t ctx, ast_expression *array, ast_expression *index) -{ - ast_expression *outtype; - ast_instantiate(ast_array_index, ctx, ast_array_index_delete); - - outtype = array->next; - if (!outtype) { - mem_d(self); - /* Error: field has no type... */ - return NULL; - } - - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_array_index_codegen); - - self->array = array; - self->index = index; - ast_propagate_effects(self, array); - ast_propagate_effects(self, index); - - ast_type_adopt(self, outtype); - if (array->vtype == TYPE_FIELD && outtype->vtype == TYPE_ARRAY) { - if (self->expression.vtype != TYPE_ARRAY) { - compile_error(ast_ctx(self), "array_index node on type"); - ast_array_index_delete(self); - return NULL; - } - self->array = outtype; - self->expression.vtype = TYPE_FIELD; - } - - return self; -} - -void ast_array_index_delete(ast_array_index *self) -{ - if (self->array) - ast_unref(self->array); - if (self->index) - ast_unref(self->index); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_argpipe* ast_argpipe_new(lex_ctx_t ctx, ast_expression *index) -{ - ast_instantiate(ast_argpipe, ctx, ast_argpipe_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_argpipe_codegen); - self->index = index; - self->expression.vtype = TYPE_NOEXPR; - return self; -} - -void ast_argpipe_delete(ast_argpipe *self) -{ - if (self->index) - ast_unref(self->index); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_ifthen* ast_ifthen_new(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse) -{ - ast_instantiate(ast_ifthen, ctx, ast_ifthen_delete); - if (!ontrue && !onfalse) { - /* because it is invalid */ - mem_d(self); - return NULL; - } - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_ifthen_codegen); - - self->cond = cond; - self->on_true = ontrue; - self->on_false = onfalse; - ast_propagate_effects(self, cond); - if (ontrue) - ast_propagate_effects(self, ontrue); - if (onfalse) - ast_propagate_effects(self, onfalse); - - return self; -} - -void ast_ifthen_delete(ast_ifthen *self) -{ - ast_unref(self->cond); - if (self->on_true) - ast_unref(self->on_true); - if (self->on_false) - ast_unref(self->on_false); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_ternary* ast_ternary_new(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse) -{ - ast_expression *exprtype = ontrue; - ast_instantiate(ast_ternary, ctx, ast_ternary_delete); - /* This time NEITHER must be NULL */ - if (!ontrue || !onfalse) { - mem_d(self); - return NULL; - } - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_ternary_codegen); - - self->cond = cond; - self->on_true = ontrue; - self->on_false = onfalse; - ast_propagate_effects(self, cond); - ast_propagate_effects(self, ontrue); - ast_propagate_effects(self, onfalse); - - if (ontrue->vtype == TYPE_NIL) - exprtype = onfalse; - ast_type_adopt(self, exprtype); - - return self; -} - -void ast_ternary_delete(ast_ternary *self) -{ - /* the if()s are only there because computed-gotos can set them - * to NULL - */ - if (self->cond) ast_unref(self->cond); - if (self->on_true) ast_unref(self->on_true); - if (self->on_false) ast_unref(self->on_false); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_loop* ast_loop_new(lex_ctx_t ctx, - ast_expression *initexpr, - ast_expression *precond, bool pre_not, - ast_expression *postcond, bool post_not, - ast_expression *increment, - ast_expression *body) -{ - ast_instantiate(ast_loop, ctx, ast_loop_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_loop_codegen); - - self->initexpr = initexpr; - self->precond = precond; - self->postcond = postcond; - self->increment = increment; - self->body = body; - - self->pre_not = pre_not; - self->post_not = post_not; - - if (initexpr) - ast_propagate_effects(self, initexpr); - if (precond) - ast_propagate_effects(self, precond); - if (postcond) - ast_propagate_effects(self, postcond); - if (increment) - ast_propagate_effects(self, increment); - if (body) - ast_propagate_effects(self, body); - - return self; -} - -void ast_loop_delete(ast_loop *self) -{ - if (self->initexpr) - ast_unref(self->initexpr); - if (self->precond) - ast_unref(self->precond); - if (self->postcond) - ast_unref(self->postcond); - if (self->increment) - ast_unref(self->increment); - if (self->body) - ast_unref(self->body); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_breakcont* ast_breakcont_new(lex_ctx_t ctx, bool iscont, unsigned int levels) -{ - ast_instantiate(ast_breakcont, ctx, ast_breakcont_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_breakcont_codegen); - - self->is_continue = iscont; - self->levels = levels; - - return self; -} - -void ast_breakcont_delete(ast_breakcont *self) -{ - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_switch* ast_switch_new(lex_ctx_t ctx, ast_expression *op) -{ - ast_instantiate(ast_switch, ctx, ast_switch_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_switch_codegen); - - self->operand = op; - self->cases = NULL; - - ast_propagate_effects(self, op); - - return self; -} - -void ast_switch_delete(ast_switch *self) -{ - size_t i; - ast_unref(self->operand); - - for (i = 0; i < vec_size(self->cases); ++i) { - if (self->cases[i].value) - ast_unref(self->cases[i].value); - ast_unref(self->cases[i].code); - } - vec_free(self->cases); - - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_label* ast_label_new(lex_ctx_t ctx, const char *name, bool undefined) -{ - ast_instantiate(ast_label, ctx, ast_label_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_label_codegen); - - self->expression.vtype = TYPE_NOEXPR; - - self->name = util_strdup(name); - self->irblock = NULL; - self->gotos = NULL; - self->undefined = undefined; - - return self; -} - -void ast_label_delete(ast_label *self) -{ - mem_d((void*)self->name); - vec_free(self->gotos); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -static void ast_label_register_goto(ast_label *self, ast_goto *g) -{ - vec_push(self->gotos, g); -} - -ast_goto* ast_goto_new(lex_ctx_t ctx, const char *name) -{ - ast_instantiate(ast_goto, ctx, ast_goto_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_goto_codegen); - - self->name = util_strdup(name); - self->target = NULL; - self->irblock_from = NULL; - - return self; -} - -void ast_goto_delete(ast_goto *self) -{ - mem_d((void*)self->name); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -void ast_goto_set_label(ast_goto *self, ast_label *label) -{ - self->target = label; -} - -ast_state* ast_state_new(lex_ctx_t ctx, ast_expression *frame, ast_expression *think) -{ - ast_instantiate(ast_state, ctx, ast_state_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_state_codegen); - self->framenum = frame; - self->nextthink = think; - return self; -} - -void ast_state_delete(ast_state *self) -{ - if (self->framenum) - ast_unref(self->framenum); - if (self->nextthink) - ast_unref(self->nextthink); - - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_call* ast_call_new(lex_ctx_t ctx, - ast_expression *funcexpr) -{ - ast_instantiate(ast_call, ctx, ast_call_delete); - if (!funcexpr->next) { - compile_error(ctx, "not a function"); - mem_d(self); - return NULL; - } - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_call_codegen); - - ast_side_effects(self) = true; - - self->params = NULL; - self->func = funcexpr; - self->va_count = NULL; - - ast_type_adopt(self, funcexpr->next); - - return self; -} - -void ast_call_delete(ast_call *self) -{ - size_t i; - for (i = 0; i < vec_size(self->params); ++i) - ast_unref(self->params[i]); - vec_free(self->params); - - if (self->func) - ast_unref(self->func); - - if (self->va_count) - ast_unref(self->va_count); - - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -static bool ast_call_check_vararg(ast_call *self, ast_expression *va_type, ast_expression *exp_type) -{ - char texp[1024]; - char tgot[1024]; - if (!exp_type) - return true; - if (!va_type || !ast_compare_type(va_type, exp_type)) - { - if (va_type && exp_type) - { - ast_type_to_string(va_type, tgot, sizeof(tgot)); - ast_type_to_string(exp_type, texp, sizeof(texp)); - if (OPTS_FLAG(UNSAFE_VARARGS)) { - if (compile_warning(ast_ctx(self), WARN_UNSAFE_TYPES, - "piped variadic argument differs in type: constrained to type %s, expected type %s", - tgot, texp)) - return false; - } else { - compile_error(ast_ctx(self), - "piped variadic argument differs in type: constrained to type %s, expected type %s", - tgot, texp); - return false; - } - } - else - { - ast_type_to_string(exp_type, texp, sizeof(texp)); - if (OPTS_FLAG(UNSAFE_VARARGS)) { - if (compile_warning(ast_ctx(self), WARN_UNSAFE_TYPES, - "piped variadic argument may differ in type: expected type %s", - texp)) - return false; - } else { - compile_error(ast_ctx(self), - "piped variadic argument may differ in type: expected type %s", - texp); - return false; - } - } - } - return true; -} - -bool ast_call_check_types(ast_call *self, ast_expression *va_type) -{ - char texp[1024]; - char tgot[1024]; - size_t i; - bool retval = true; - const ast_expression *func = self->func; - size_t count = vec_size(self->params); - if (count > vec_size(func->params)) - count = vec_size(func->params); - - for (i = 0; i < count; ++i) { - if (ast_istype(self->params[i], ast_argpipe)) { - /* warn about type safety instead */ - if (i+1 != count) { - compile_error(ast_ctx(self), "argpipe must be the last parameter to a function call"); - return false; - } - if (!ast_call_check_vararg(self, va_type, (ast_expression*)func->params[i])) - retval = false; - } - else if (!ast_compare_type(self->params[i], (ast_expression*)(func->params[i]))) - { - ast_type_to_string(self->params[i], tgot, sizeof(tgot)); - ast_type_to_string((ast_expression*)func->params[i], texp, sizeof(texp)); - compile_error(ast_ctx(self), "invalid type for parameter %u in function call: expected %s, got %s", - (unsigned int)(i+1), texp, tgot); - /* we don't immediately return */ - retval = false; - } - } - count = vec_size(self->params); - if (count > vec_size(func->params) && func->varparam) { - for (; i < count; ++i) { - if (ast_istype(self->params[i], ast_argpipe)) { - /* warn about type safety instead */ - if (i+1 != count) { - compile_error(ast_ctx(self), "argpipe must be the last parameter to a function call"); - return false; - } - if (!ast_call_check_vararg(self, va_type, func->varparam)) - retval = false; - } - else if (!ast_compare_type(self->params[i], func->varparam)) - { - ast_type_to_string(self->params[i], tgot, sizeof(tgot)); - ast_type_to_string(func->varparam, texp, sizeof(texp)); - compile_error(ast_ctx(self), "invalid type for variadic parameter %u in function call: expected %s, got %s", - (unsigned int)(i+1), texp, tgot); - /* we don't immediately return */ - retval = false; - } - } - } - return retval; -} - -ast_store* ast_store_new(lex_ctx_t ctx, int op, - ast_expression *dest, ast_expression *source) -{ - ast_instantiate(ast_store, ctx, ast_store_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_store_codegen); - - ast_side_effects(self) = true; - - self->op = op; - self->dest = dest; - self->source = source; - - ast_type_adopt(self, dest); - - return self; -} - -void ast_store_delete(ast_store *self) -{ - ast_unref(self->dest); - ast_unref(self->source); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_block* ast_block_new(lex_ctx_t ctx) -{ - ast_instantiate(ast_block, ctx, ast_block_delete); - ast_expression_init((ast_expression*)self, - (ast_expression_codegen*)&ast_block_codegen); - - self->locals = NULL; - self->exprs = NULL; - self->collect = NULL; - - return self; -} - -bool ast_block_add_expr(ast_block *self, ast_expression *e) -{ - ast_propagate_effects(self, e); - vec_push(self->exprs, e); - if (self->expression.next) { - ast_delete(self->expression.next); - self->expression.next = NULL; - } - ast_type_adopt(self, e); - return true; -} - -void ast_block_collect(ast_block *self, ast_expression *expr) -{ - vec_push(self->collect, expr); - expr->node.keep = true; -} - -void ast_block_delete(ast_block *self) -{ - size_t i; - for (i = 0; i < vec_size(self->exprs); ++i) - ast_unref(self->exprs[i]); - vec_free(self->exprs); - for (i = 0; i < vec_size(self->locals); ++i) - ast_delete(self->locals[i]); - vec_free(self->locals); - for (i = 0; i < vec_size(self->collect); ++i) - ast_delete(self->collect[i]); - vec_free(self->collect); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -void ast_block_set_type(ast_block *self, ast_expression *from) -{ - if (self->expression.next) - ast_delete(self->expression.next); - ast_type_adopt(self, from); -} - -ast_function* ast_function_new(lex_ctx_t ctx, const char *name, ast_value *vtype) -{ - ast_instantiate(ast_function, ctx, ast_function_delete); - - if (!vtype) { - compile_error(ast_ctx(self), "internal error: ast_function_new condition 0"); - goto cleanup; - } else if (vtype->hasvalue || vtype->expression.vtype != TYPE_FUNCTION) { - compile_error(ast_ctx(self), "internal error: ast_function_new condition %i %i type=%i (probably 2 bodies?)", - (int)!vtype, - (int)vtype->hasvalue, - vtype->expression.vtype); - goto cleanup; - } - - self->vtype = vtype; - self->name = name ? util_strdup(name) : NULL; - self->blocks = NULL; - - self->labelcount = 0; - self->builtin = 0; - - self->ir_func = NULL; - self->curblock = NULL; - - self->breakblocks = NULL; - self->continueblocks = NULL; - - vtype->hasvalue = true; - vtype->constval.vfunc = self; - - self->varargs = NULL; - self->argc = NULL; - self->fixedparams = NULL; - self->return_value = NULL; - - self->static_names = NULL; - self->static_count = 0; - - return self; - -cleanup: - mem_d(self); - return NULL; -} - -void ast_function_delete(ast_function *self) -{ - size_t i; - if (self->name) - mem_d((void*)self->name); - if (self->vtype) { - /* ast_value_delete(self->vtype); */ - self->vtype->hasvalue = false; - self->vtype->constval.vfunc = NULL; - /* We use unref - if it was stored in a global table it is supposed - * to be deleted from *there* - */ - ast_unref(self->vtype); - } - for (i = 0; i < vec_size(self->static_names); ++i) - mem_d(self->static_names[i]); - vec_free(self->static_names); - for (i = 0; i < vec_size(self->blocks); ++i) - ast_delete(self->blocks[i]); - vec_free(self->blocks); - vec_free(self->breakblocks); - vec_free(self->continueblocks); - if (self->varargs) - ast_delete(self->varargs); - if (self->argc) - ast_delete(self->argc); - if (self->fixedparams) - ast_unref(self->fixedparams); - if (self->return_value) - ast_unref(self->return_value); - mem_d(self); -} - -const char* ast_function_label(ast_function *self, const char *prefix) -{ - size_t id; - size_t len; - char *from; - - if (!OPTS_OPTION_BOOL(OPTION_DUMP) && - !OPTS_OPTION_BOOL(OPTION_DUMPFIN) && - !OPTS_OPTION_BOOL(OPTION_DEBUG)) - { - return NULL; - } - - id = (self->labelcount++); - len = strlen(prefix); - - from = self->labelbuf + sizeof(self->labelbuf)-1; - *from-- = 0; - do { - *from-- = (id%10) + '0'; - id /= 10; - } while (id); - ++from; - memcpy(from - len, prefix, len); - return from - len; -} - -/*********************************************************************/ -/* AST codegen part - * by convention you must never pass NULL to the 'ir_value **out' - * parameter. If you really don't care about the output, pass a dummy. - * But I can't imagine a pituation where the output is truly unnecessary. - */ - -static void _ast_codegen_output_type(ast_expression *self, ir_value *out) -{ - if (out->vtype == TYPE_FIELD) - out->fieldtype = self->next->vtype; - if (out->vtype == TYPE_FUNCTION) - out->outtype = self->next->vtype; -} - -#define codegen_output_type(a,o) (_ast_codegen_output_type(&((a)->expression),(o))) - -bool ast_value_codegen(ast_value *self, ast_function *func, bool lvalue, ir_value **out) -{ - (void)func; - (void)lvalue; - if (self->expression.vtype == TYPE_NIL) { - *out = func->ir_func->owner->nil; - return true; - } - /* NOTE: This is the codegen for a variable used in an expression. - * It is not the codegen to generate the value. For this purpose, - * ast_local_codegen and ast_global_codegen are to be used before this - * is executed. ast_function_codegen should take care of its locals, - * and the ast-user should take care of ast_global_codegen to be used - * on all the globals. - */ - if (!self->ir_v) { - char tname[1024]; /* typename is reserved in C++ */ - ast_type_to_string((ast_expression*)self, tname, sizeof(tname)); - compile_error(ast_ctx(self), "ast_value used before generated %s %s", tname, self->name); - return false; - } - *out = self->ir_v; - return true; -} - -static bool ast_global_array_set(ast_value *self) -{ - size_t count = vec_size(self->initlist); - size_t i; - - if (count > self->expression.count) { - compile_error(ast_ctx(self), "too many elements in initializer"); - count = self->expression.count; - } - else if (count < self->expression.count) { - /* add this? - compile_warning(ast_ctx(self), "not all elements are initialized"); - */ - } - - for (i = 0; i != count; ++i) { - switch (self->expression.next->vtype) { - case TYPE_FLOAT: - if (!ir_value_set_float(self->ir_values[i], self->initlist[i].vfloat)) - return false; - break; - case TYPE_VECTOR: - if (!ir_value_set_vector(self->ir_values[i], self->initlist[i].vvec)) - return false; - break; - case TYPE_STRING: - if (!ir_value_set_string(self->ir_values[i], self->initlist[i].vstring)) - return false; - break; - case TYPE_ARRAY: - /* we don't support them in any other place yet either */ - compile_error(ast_ctx(self), "TODO: nested arrays"); - return false; - case TYPE_FUNCTION: - /* this requiers a bit more work - similar to the fields I suppose */ - compile_error(ast_ctx(self), "global of type function not properly generated"); - return false; - case TYPE_FIELD: - if (!self->initlist[i].vfield) { - compile_error(ast_ctx(self), "field constant without vfield set"); - return false; - } - if (!self->initlist[i].vfield->ir_v) { - compile_error(ast_ctx(self), "field constant generated before its field"); - return false; - } - if (!ir_value_set_field(self->ir_values[i], self->initlist[i].vfield->ir_v)) - return false; - break; - default: - compile_error(ast_ctx(self), "TODO: global constant type %i", self->expression.vtype); - break; - } - } - return true; -} - -static bool check_array(ast_value *self, ast_value *array) -{ - if (array->expression.flags & AST_FLAG_ARRAY_INIT && !array->initlist) { - compile_error(ast_ctx(self), "array without size: %s", self->name); - return false; - } - /* we are lame now - considering the way QC works we won't tolerate arrays > 1024 elements */ - if (!array->expression.count || array->expression.count > OPTS_OPTION_U32(OPTION_MAX_ARRAY_SIZE)) { - compile_error(ast_ctx(self), "Invalid array of size %lu", (unsigned long)array->expression.count); - return false; - } - return true; -} - -bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield) -{ - ir_value *v = NULL; - - if (self->expression.vtype == TYPE_NIL) { - compile_error(ast_ctx(self), "internal error: trying to generate a variable of TYPE_NIL"); - return false; - } - - if (self->hasvalue && self->expression.vtype == TYPE_FUNCTION) - { - ir_function *func = ir_builder_create_function(ir, self->name, self->expression.next->vtype); - if (!func) - return false; - func->context = ast_ctx(self); - func->value->context = ast_ctx(self); - - self->constval.vfunc->ir_func = func; - self->ir_v = func->value; - if (self->expression.flags & AST_FLAG_INCLUDE_DEF) - self->ir_v->flags |= IR_FLAG_INCLUDE_DEF; - if (self->expression.flags & AST_FLAG_ERASEABLE) - self->ir_v->flags |= IR_FLAG_ERASABLE; - if (self->expression.flags & AST_FLAG_BLOCK_COVERAGE) - func->flags |= IR_FLAG_BLOCK_COVERAGE; - /* The function is filled later on ast_function_codegen... */ - return true; - } - - if (isfield && self->expression.vtype == TYPE_FIELD) { - ast_expression *fieldtype = self->expression.next; - - if (self->hasvalue) { - compile_error(ast_ctx(self), "TODO: constant field pointers with value"); - goto error; - } - - if (fieldtype->vtype == TYPE_ARRAY) { - size_t ai; - char *name; - size_t namelen; - - ast_expression *elemtype; - int vtype; - ast_value *array = (ast_value*)fieldtype; - - if (!ast_istype(fieldtype, ast_value)) { - compile_error(ast_ctx(self), "internal error: ast_value required"); - return false; - } - - if (!check_array(self, array)) - return false; - - elemtype = array->expression.next; - vtype = elemtype->vtype; - - v = ir_builder_create_field(ir, self->name, vtype); - if (!v) { - compile_error(ast_ctx(self), "ir_builder_create_global failed on `%s`", self->name); - return false; - } - v->context = ast_ctx(self); - v->unique_life = true; - v->locked = true; - array->ir_v = self->ir_v = v; - - if (self->expression.flags & AST_FLAG_INCLUDE_DEF) - self->ir_v->flags |= IR_FLAG_INCLUDE_DEF; - if (self->expression.flags & AST_FLAG_ERASEABLE) - self->ir_v->flags |= IR_FLAG_ERASABLE; - - namelen = strlen(self->name); - name = (char*)mem_a(namelen + 16); - util_strncpy(name, self->name, namelen); - - array->ir_values = (ir_value**)mem_a(sizeof(array->ir_values[0]) * array->expression.count); - array->ir_values[0] = v; - for (ai = 1; ai < array->expression.count; ++ai) { - util_snprintf(name + namelen, 16, "[%u]", (unsigned int)ai); - array->ir_values[ai] = ir_builder_create_field(ir, name, vtype); - if (!array->ir_values[ai]) { - mem_d(name); - compile_error(ast_ctx(self), "ir_builder_create_global failed on `%s`", name); - return false; - } - array->ir_values[ai]->context = ast_ctx(self); - array->ir_values[ai]->unique_life = true; - array->ir_values[ai]->locked = true; - if (self->expression.flags & AST_FLAG_INCLUDE_DEF) - self->ir_values[ai]->flags |= IR_FLAG_INCLUDE_DEF; - } - mem_d(name); - } - else - { - v = ir_builder_create_field(ir, self->name, self->expression.next->vtype); - if (!v) - return false; - v->context = ast_ctx(self); - self->ir_v = v; - if (self->expression.flags & AST_FLAG_INCLUDE_DEF) - self->ir_v->flags |= IR_FLAG_INCLUDE_DEF; - - if (self->expression.flags & AST_FLAG_ERASEABLE) - self->ir_v->flags |= IR_FLAG_ERASABLE; - } - return true; - } - - if (self->expression.vtype == TYPE_ARRAY) { - size_t ai; - char *name; - size_t namelen; - - ast_expression *elemtype = self->expression.next; - int vtype = elemtype->vtype; - - if (self->expression.flags & AST_FLAG_ARRAY_INIT && !self->expression.count) { - compile_error(ast_ctx(self), "array `%s' has no size", self->name); - return false; - } - - /* same as with field arrays */ - if (!check_array(self, self)) - return false; - - v = ir_builder_create_global(ir, self->name, vtype); - if (!v) { - compile_error(ast_ctx(self), "ir_builder_create_global failed `%s`", self->name); - return false; - } - v->context = ast_ctx(self); - v->unique_life = true; - v->locked = true; - - if (self->expression.flags & AST_FLAG_INCLUDE_DEF) - v->flags |= IR_FLAG_INCLUDE_DEF; - if (self->expression.flags & AST_FLAG_ERASEABLE) - self->ir_v->flags |= IR_FLAG_ERASABLE; - - namelen = strlen(self->name); - name = (char*)mem_a(namelen + 16); - util_strncpy(name, self->name, namelen); - - self->ir_values = (ir_value**)mem_a(sizeof(self->ir_values[0]) * self->expression.count); - self->ir_values[0] = v; - for (ai = 1; ai < self->expression.count; ++ai) { - util_snprintf(name + namelen, 16, "[%u]", (unsigned int)ai); - self->ir_values[ai] = ir_builder_create_global(ir, name, vtype); - if (!self->ir_values[ai]) { - mem_d(name); - compile_error(ast_ctx(self), "ir_builder_create_global failed `%s`", name); - return false; - } - self->ir_values[ai]->context = ast_ctx(self); - self->ir_values[ai]->unique_life = true; - self->ir_values[ai]->locked = true; - if (self->expression.flags & AST_FLAG_INCLUDE_DEF) - self->ir_values[ai]->flags |= IR_FLAG_INCLUDE_DEF; - } - mem_d(name); - } - else - { - /* Arrays don't do this since there's no "array" value which spans across the - * whole thing. - */ - v = ir_builder_create_global(ir, self->name, self->expression.vtype); - if (!v) { - compile_error(ast_ctx(self), "ir_builder_create_global failed on `%s`", self->name); - return false; - } - codegen_output_type(self, v); - v->context = ast_ctx(self); - } - - /* link us to the ir_value */ - v->cvq = self->cvq; - self->ir_v = v; - - if (self->expression.flags & AST_FLAG_INCLUDE_DEF) - self->ir_v->flags |= IR_FLAG_INCLUDE_DEF; - if (self->expression.flags & AST_FLAG_ERASEABLE) - self->ir_v->flags |= IR_FLAG_ERASABLE; - - /* initialize */ - if (self->hasvalue) { - switch (self->expression.vtype) - { - case TYPE_FLOAT: - if (!ir_value_set_float(v, self->constval.vfloat)) - goto error; - break; - case TYPE_VECTOR: - if (!ir_value_set_vector(v, self->constval.vvec)) - goto error; - break; - case TYPE_STRING: - if (!ir_value_set_string(v, self->constval.vstring)) - goto error; - break; - case TYPE_ARRAY: - ast_global_array_set(self); - break; - case TYPE_FUNCTION: - compile_error(ast_ctx(self), "global of type function not properly generated"); - goto error; - /* Cannot generate an IR value for a function, - * need a pointer pointing to a function rather. - */ - case TYPE_FIELD: - if (!self->constval.vfield) { - compile_error(ast_ctx(self), "field constant without vfield set"); - goto error; - } - if (!self->constval.vfield->ir_v) { - compile_error(ast_ctx(self), "field constant generated before its field"); - goto error; - } - if (!ir_value_set_field(v, self->constval.vfield->ir_v)) - goto error; - break; - default: - compile_error(ast_ctx(self), "TODO: global constant type %i", self->expression.vtype); - break; - } - } - return true; - -error: /* clean up */ - if(v) ir_value_delete(v); - return false; -} - -static bool ast_local_codegen(ast_value *self, ir_function *func, bool param) -{ - ir_value *v = NULL; - - if (self->expression.vtype == TYPE_NIL) { - compile_error(ast_ctx(self), "internal error: trying to generate a variable of TYPE_NIL"); - return false; - } - - if (self->hasvalue && self->expression.vtype == TYPE_FUNCTION) - { - /* Do we allow local functions? I think not... - * this is NOT a function pointer atm. - */ - return false; - } - - if (self->expression.vtype == TYPE_ARRAY) { - size_t ai; - char *name; - size_t namelen; - - ast_expression *elemtype = self->expression.next; - int vtype = elemtype->vtype; - - func->flags |= IR_FLAG_HAS_ARRAYS; - - if (param && !(self->expression.flags & AST_FLAG_IS_VARARG)) { - compile_error(ast_ctx(self), "array-parameters are not supported"); - return false; - } - - /* we are lame now - considering the way QC works we won't tolerate arrays > 1024 elements */ - if (!check_array(self, self)) - return false; - - self->ir_values = (ir_value**)mem_a(sizeof(self->ir_values[0]) * self->expression.count); - if (!self->ir_values) { - compile_error(ast_ctx(self), "failed to allocate array values"); - return false; - } - - v = ir_function_create_local(func, self->name, vtype, param); - if (!v) { - compile_error(ast_ctx(self), "internal error: ir_function_create_local failed"); - return false; - } - v->context = ast_ctx(self); - v->unique_life = true; - v->locked = true; - - namelen = strlen(self->name); - name = (char*)mem_a(namelen + 16); - util_strncpy(name, self->name, namelen); - - self->ir_values[0] = v; - for (ai = 1; ai < self->expression.count; ++ai) { - util_snprintf(name + namelen, 16, "[%u]", (unsigned int)ai); - self->ir_values[ai] = ir_function_create_local(func, name, vtype, param); - if (!self->ir_values[ai]) { - compile_error(ast_ctx(self), "internal_error: ir_builder_create_global failed on `%s`", name); - return false; - } - self->ir_values[ai]->context = ast_ctx(self); - self->ir_values[ai]->unique_life = true; - self->ir_values[ai]->locked = true; - } - mem_d(name); - } - else - { - v = ir_function_create_local(func, self->name, self->expression.vtype, param); - if (!v) - return false; - codegen_output_type(self, v); - v->context = ast_ctx(self); - } - - /* A constant local... hmmm... - * I suppose the IR will have to deal with this - */ - if (self->hasvalue) { - switch (self->expression.vtype) - { - case TYPE_FLOAT: - if (!ir_value_set_float(v, self->constval.vfloat)) - goto error; - break; - case TYPE_VECTOR: - if (!ir_value_set_vector(v, self->constval.vvec)) - goto error; - break; - case TYPE_STRING: - if (!ir_value_set_string(v, self->constval.vstring)) - goto error; - break; - default: - compile_error(ast_ctx(self), "TODO: global constant type %i", self->expression.vtype); - break; - } - } - - /* link us to the ir_value */ - v->cvq = self->cvq; - self->ir_v = v; - - if (!ast_generate_accessors(self, func->owner)) - return false; - return true; - -error: /* clean up */ - ir_value_delete(v); - return false; -} - -bool ast_generate_accessors(ast_value *self, ir_builder *ir) -{ - size_t i; - bool warn = OPTS_WARN(WARN_USED_UNINITIALIZED); - if (!self->setter || !self->getter) - return true; - for (i = 0; i < self->expression.count; ++i) { - if (!self->ir_values) { - compile_error(ast_ctx(self), "internal error: no array values generated for `%s`", self->name); - return false; - } - if (!self->ir_values[i]) { - compile_error(ast_ctx(self), "internal error: not all array values have been generated for `%s`", self->name); - return false; - } - if (self->ir_values[i]->life) { - compile_error(ast_ctx(self), "internal error: function containing `%s` already generated", self->name); - return false; - } - } - - opts_set(opts.warn, WARN_USED_UNINITIALIZED, false); - if (self->setter) { - if (!ast_global_codegen (self->setter, ir, false) || - !ast_function_codegen(self->setter->constval.vfunc, ir) || - !ir_function_finalize(self->setter->constval.vfunc->ir_func)) - { - compile_error(ast_ctx(self), "internal error: failed to generate setter for `%s`", self->name); - opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn); - return false; - } - } - if (self->getter) { - if (!ast_global_codegen (self->getter, ir, false) || - !ast_function_codegen(self->getter->constval.vfunc, ir) || - !ir_function_finalize(self->getter->constval.vfunc->ir_func)) - { - compile_error(ast_ctx(self), "internal error: failed to generate getter for `%s`", self->name); - opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn); - return false; - } - } - for (i = 0; i < self->expression.count; ++i) { - vec_free(self->ir_values[i]->life); - } - opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn); - return true; -} - -bool ast_function_codegen(ast_function *self, ir_builder *ir) -{ - ir_function *irf; - ir_value *dummy; - ast_expression *ec; - ast_expression_codegen *cgen; - - size_t i; - - (void)ir; - - irf = self->ir_func; - if (!irf) { - compile_error(ast_ctx(self), "internal error: ast_function's related ast_value was not generated yet"); - return false; - } - - /* fill the parameter list */ - ec = &self->vtype->expression; - for (i = 0; i < vec_size(ec->params); ++i) - { - if (ec->params[i]->expression.vtype == TYPE_FIELD) - vec_push(irf->params, ec->params[i]->expression.next->vtype); - else - vec_push(irf->params, ec->params[i]->expression.vtype); - if (!self->builtin) { - if (!ast_local_codegen(ec->params[i], self->ir_func, true)) - return false; - } - } - - if (self->varargs) { - if (!ast_local_codegen(self->varargs, self->ir_func, true)) - return false; - irf->max_varargs = self->varargs->expression.count; - } - - if (self->builtin) { - irf->builtin = self->builtin; - return true; - } - - /* have a local return value variable? */ - if (self->return_value) { - if (!ast_local_codegen(self->return_value, self->ir_func, false)) - return false; - } - - if (!vec_size(self->blocks)) { - compile_error(ast_ctx(self), "function `%s` has no body", self->name); - return false; - } - - irf->first = self->curblock = ir_function_create_block(ast_ctx(self), irf, "entry"); - if (!self->curblock) { - compile_error(ast_ctx(self), "failed to allocate entry block for `%s`", self->name); - return false; - } - - if (self->argc) { - ir_value *va_count; - ir_value *fixed; - ir_value *sub; - if (!ast_local_codegen(self->argc, self->ir_func, true)) - return false; - cgen = self->argc->expression.codegen; - if (!(*cgen)((ast_expression*)(self->argc), self, false, &va_count)) - return false; - cgen = self->fixedparams->expression.codegen; - if (!(*cgen)((ast_expression*)(self->fixedparams), self, false, &fixed)) - return false; - sub = ir_block_create_binop(self->curblock, ast_ctx(self), - ast_function_label(self, "va_count"), INSTR_SUB_F, - ir_builder_get_va_count(ir), fixed); - if (!sub) - return false; - if (!ir_block_create_store_op(self->curblock, ast_ctx(self), INSTR_STORE_F, - va_count, sub)) - { - return false; - } - } - - for (i = 0; i < vec_size(self->blocks); ++i) { - cgen = self->blocks[i]->expression.codegen; - if (!(*cgen)((ast_expression*)self->blocks[i], self, false, &dummy)) - return false; - } - - /* TODO: check return types */ - if (!self->curblock->final) - { - if (!self->vtype->expression.next || - self->vtype->expression.next->vtype == TYPE_VOID) - { - return ir_block_create_return(self->curblock, ast_ctx(self), NULL); - } - else if (vec_size(self->curblock->entries) || self->curblock == irf->first) - { - if (self->return_value) { - cgen = self->return_value->expression.codegen; - if (!(*cgen)((ast_expression*)(self->return_value), self, false, &dummy)) - return false; - return ir_block_create_return(self->curblock, ast_ctx(self), dummy); - } - else if (compile_warning(ast_ctx(self), WARN_MISSING_RETURN_VALUES, - "control reaches end of non-void function (`%s`) via %s", - self->name, self->curblock->label)) - { - return false; - } - return ir_block_create_return(self->curblock, ast_ctx(self), NULL); - } - } - return true; -} - -static bool starts_a_label(ast_expression *ex) -{ - while (ex && ast_istype(ex, ast_block)) { - ast_block *b = (ast_block*)ex; - ex = b->exprs[0]; - } - if (!ex) - return false; - return ast_istype(ex, ast_label); -} - -/* Note, you will not see ast_block_codegen generate ir_blocks. - * To the AST and the IR, blocks are 2 different things. - * In the AST it represents a block of code, usually enclosed in - * curly braces {...}. - * While in the IR it represents a block in terms of control-flow. - */ -bool ast_block_codegen(ast_block *self, ast_function *func, bool lvalue, ir_value **out) -{ - size_t i; - - /* We don't use this - * Note: an ast-representation using the comma-operator - * of the form: (a, b, c) = x should not assign to c... - */ - if (lvalue) { - compile_error(ast_ctx(self), "not an l-value (code-block)"); - return false; - } - - if (self->expression.outr) { - *out = self->expression.outr; - return true; - } - - /* output is NULL at first, we'll have each expression - * assign to out output, thus, a comma-operator represention - * using an ast_block will return the last generated value, - * so: (b, c) + a executed both b and c, and returns c, - * which is then added to a. - */ - *out = NULL; - - /* generate locals */ - for (i = 0; i < vec_size(self->locals); ++i) - { - if (!ast_local_codegen(self->locals[i], func->ir_func, false)) { - if (OPTS_OPTION_BOOL(OPTION_DEBUG)) - compile_error(ast_ctx(self), "failed to generate local `%s`", self->locals[i]->name); - return false; - } - } - - for (i = 0; i < vec_size(self->exprs); ++i) - { - ast_expression_codegen *gen; - if (func->curblock->final && !starts_a_label(self->exprs[i])) { - if (compile_warning(ast_ctx(self->exprs[i]), WARN_UNREACHABLE_CODE, "unreachable statement")) - return false; - continue; - } - gen = self->exprs[i]->codegen; - if (!(*gen)(self->exprs[i], func, false, out)) - return false; - } - - self->expression.outr = *out; - - return true; -} - -bool ast_store_codegen(ast_store *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - ir_value *left = NULL; - ir_value *right = NULL; - - ast_value *arr; - ast_value *idx = 0; - ast_array_index *ai = NULL; - - if (lvalue && self->expression.outl) { - *out = self->expression.outl; - return true; - } - - if (!lvalue && self->expression.outr) { - *out = self->expression.outr; - return true; - } - - if (ast_istype(self->dest, ast_array_index)) - { - - ai = (ast_array_index*)self->dest; - idx = (ast_value*)ai->index; - - if (ast_istype(ai->index, ast_value) && idx->hasvalue && idx->cvq == CV_CONST) - ai = NULL; - } - - if (ai) { - /* we need to call the setter */ - ir_value *iridx, *funval; - ir_instr *call; - - if (lvalue) { - compile_error(ast_ctx(self), "array-subscript assignment cannot produce lvalues"); - return false; - } - - arr = (ast_value*)ai->array; - if (!ast_istype(ai->array, ast_value) || !arr->setter) { - compile_error(ast_ctx(self), "value has no setter (%s)", arr->name); - return false; - } - - cgen = idx->expression.codegen; - if (!(*cgen)((ast_expression*)(idx), func, false, &iridx)) - return false; - - cgen = arr->setter->expression.codegen; - if (!(*cgen)((ast_expression*)(arr->setter), func, true, &funval)) - return false; - - cgen = self->source->codegen; - if (!(*cgen)((ast_expression*)(self->source), func, false, &right)) - return false; - - call = ir_block_create_call(func->curblock, ast_ctx(self), ast_function_label(func, "store"), funval, false); - if (!call) - return false; - ir_call_param(call, iridx); - ir_call_param(call, right); - self->expression.outr = right; - } - else - { - /* regular code */ - - cgen = self->dest->codegen; - /* lvalue! */ - if (!(*cgen)((ast_expression*)(self->dest), func, true, &left)) - return false; - self->expression.outl = left; - - cgen = self->source->codegen; - /* rvalue! */ - if (!(*cgen)((ast_expression*)(self->source), func, false, &right)) - return false; - - if (!ir_block_create_store_op(func->curblock, ast_ctx(self), self->op, left, right)) - return false; - self->expression.outr = right; - } - - /* Theoretically, an assinment returns its left side as an - * lvalue, if we don't need an lvalue though, we return - * the right side as an rvalue, otherwise we have to - * somehow know whether or not we need to dereference the pointer - * on the left side - that is: OP_LOAD if it was an address. - * Also: in original QC we cannot OP_LOADP *anyway*. - */ - *out = (lvalue ? left : right); - - return true; -} - -bool ast_binary_codegen(ast_binary *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - ir_value *left, *right; - - /* A binary operation cannot yield an l-value */ - if (lvalue) { - compile_error(ast_ctx(self), "not an l-value (binop)"); - return false; - } - - if (self->expression.outr) { - *out = self->expression.outr; - return true; - } - - if ((OPTS_FLAG(SHORT_LOGIC) || OPTS_FLAG(PERL_LOGIC)) && - (self->op == INSTR_AND || self->op == INSTR_OR)) - { - /* NOTE: The short-logic path will ignore right_first */ - - /* short circuit evaluation */ - ir_block *other, *merge; - ir_block *from_left, *from_right; - ir_instr *phi; - size_t merge_id; - - /* prepare end-block */ - merge_id = vec_size(func->ir_func->blocks); - merge = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "sce_merge")); - - /* generate the left expression */ - cgen = self->left->codegen; - if (!(*cgen)((ast_expression*)(self->left), func, false, &left)) - return false; - /* remember the block */ - from_left = func->curblock; - - /* create a new block for the right expression */ - other = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "sce_other")); - if (self->op == INSTR_AND) { - /* on AND: left==true -> other */ - if (!ir_block_create_if(func->curblock, ast_ctx(self), left, other, merge)) - return false; - } else { - /* on OR: left==false -> other */ - if (!ir_block_create_if(func->curblock, ast_ctx(self), left, merge, other)) - return false; - } - /* use the likely flag */ - vec_last(func->curblock->instr)->likely = true; - - /* enter the right-expression's block */ - func->curblock = other; - /* generate */ - cgen = self->right->codegen; - if (!(*cgen)((ast_expression*)(self->right), func, false, &right)) - return false; - /* remember block */ - from_right = func->curblock; - - /* jump to the merge block */ - if (!ir_block_create_jump(func->curblock, ast_ctx(self), merge)) - return false; - - vec_remove(func->ir_func->blocks, merge_id, 1); - vec_push(func->ir_func->blocks, merge); - - func->curblock = merge; - phi = ir_block_create_phi(func->curblock, ast_ctx(self), - ast_function_label(func, "sce_value"), - self->expression.vtype); - ir_phi_add(phi, from_left, left); - ir_phi_add(phi, from_right, right); - *out = ir_phi_value(phi); - if (!*out) - return false; - - if (!OPTS_FLAG(PERL_LOGIC)) { - /* cast-to-bool */ - if (OPTS_FLAG(CORRECT_LOGIC) && (*out)->vtype == TYPE_VECTOR) { - *out = ir_block_create_unary(func->curblock, ast_ctx(self), - ast_function_label(func, "sce_bool_v"), - INSTR_NOT_V, *out); - if (!*out) - return false; - *out = ir_block_create_unary(func->curblock, ast_ctx(self), - ast_function_label(func, "sce_bool"), - INSTR_NOT_F, *out); - if (!*out) - return false; - } - else if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && (*out)->vtype == TYPE_STRING) { - *out = ir_block_create_unary(func->curblock, ast_ctx(self), - ast_function_label(func, "sce_bool_s"), - INSTR_NOT_S, *out); - if (!*out) - return false; - *out = ir_block_create_unary(func->curblock, ast_ctx(self), - ast_function_label(func, "sce_bool"), - INSTR_NOT_F, *out); - if (!*out) - return false; - } - else { - *out = ir_block_create_binop(func->curblock, ast_ctx(self), - ast_function_label(func, "sce_bool"), - INSTR_AND, *out, *out); - if (!*out) - return false; - } - } - - self->expression.outr = *out; - codegen_output_type(self, *out); - return true; - } - - if (self->right_first) { - cgen = self->right->codegen; - if (!(*cgen)((ast_expression*)(self->right), func, false, &right)) - return false; - cgen = self->left->codegen; - if (!(*cgen)((ast_expression*)(self->left), func, false, &left)) - return false; - } else { - cgen = self->left->codegen; - if (!(*cgen)((ast_expression*)(self->left), func, false, &left)) - return false; - cgen = self->right->codegen; - if (!(*cgen)((ast_expression*)(self->right), func, false, &right)) - return false; - } - - *out = ir_block_create_binop(func->curblock, ast_ctx(self), ast_function_label(func, "bin"), - self->op, left, right); - if (!*out) - return false; - self->expression.outr = *out; - codegen_output_type(self, *out); - - return true; -} - -bool ast_binstore_codegen(ast_binstore *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - ir_value *leftl = NULL, *leftr, *right, *bin; - - ast_value *arr; - ast_value *idx = 0; - ast_array_index *ai = NULL; - ir_value *iridx = NULL; - - if (lvalue && self->expression.outl) { - *out = self->expression.outl; - return true; - } - - if (!lvalue && self->expression.outr) { - *out = self->expression.outr; - return true; - } - - if (ast_istype(self->dest, ast_array_index)) - { - - ai = (ast_array_index*)self->dest; - idx = (ast_value*)ai->index; - - if (ast_istype(ai->index, ast_value) && idx->hasvalue && idx->cvq == CV_CONST) - ai = NULL; - } - - /* for a binstore we need both an lvalue and an rvalue for the left side */ - /* rvalue of destination! */ - if (ai) { - cgen = idx->expression.codegen; - if (!(*cgen)((ast_expression*)(idx), func, false, &iridx)) - return false; - } - cgen = self->dest->codegen; - if (!(*cgen)((ast_expression*)(self->dest), func, false, &leftr)) - return false; - - /* source as rvalue only */ - cgen = self->source->codegen; - if (!(*cgen)((ast_expression*)(self->source), func, false, &right)) - return false; - - /* now the binary */ - bin = ir_block_create_binop(func->curblock, ast_ctx(self), ast_function_label(func, "binst"), - self->opbin, leftr, right); - self->expression.outr = bin; - - - if (ai) { - /* we need to call the setter */ - ir_value *funval; - ir_instr *call; - - if (lvalue) { - compile_error(ast_ctx(self), "array-subscript assignment cannot produce lvalues"); - return false; - } - - arr = (ast_value*)ai->array; - if (!ast_istype(ai->array, ast_value) || !arr->setter) { - compile_error(ast_ctx(self), "value has no setter (%s)", arr->name); - return false; - } - - cgen = arr->setter->expression.codegen; - if (!(*cgen)((ast_expression*)(arr->setter), func, true, &funval)) - return false; - - call = ir_block_create_call(func->curblock, ast_ctx(self), ast_function_label(func, "store"), funval, false); - if (!call) - return false; - ir_call_param(call, iridx); - ir_call_param(call, bin); - self->expression.outr = bin; - } else { - /* now store them */ - cgen = self->dest->codegen; - /* lvalue of destination */ - if (!(*cgen)((ast_expression*)(self->dest), func, true, &leftl)) - return false; - self->expression.outl = leftl; - - if (!ir_block_create_store_op(func->curblock, ast_ctx(self), self->opstore, leftl, bin)) - return false; - self->expression.outr = bin; - } - - /* Theoretically, an assinment returns its left side as an - * lvalue, if we don't need an lvalue though, we return - * the right side as an rvalue, otherwise we have to - * somehow know whether or not we need to dereference the pointer - * on the left side - that is: OP_LOAD if it was an address. - * Also: in original QC we cannot OP_LOADP *anyway*. - */ - *out = (lvalue ? leftl : bin); - - return true; -} - -bool ast_unary_codegen(ast_unary *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - ir_value *operand; - - /* An unary operation cannot yield an l-value */ - if (lvalue) { - compile_error(ast_ctx(self), "not an l-value (binop)"); - return false; - } - - if (self->expression.outr) { - *out = self->expression.outr; - return true; - } - - cgen = self->operand->codegen; - /* lvalue! */ - if (!(*cgen)((ast_expression*)(self->operand), func, false, &operand)) - return false; - - *out = ir_block_create_unary(func->curblock, ast_ctx(self), ast_function_label(func, "unary"), - self->op, operand); - if (!*out) - return false; - self->expression.outr = *out; - - return true; -} - -bool ast_return_codegen(ast_return *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - ir_value *operand; - - *out = NULL; - - /* In the context of a return operation, we don't actually return - * anything... - */ - if (lvalue) { - compile_error(ast_ctx(self), "return-expression is not an l-value"); - return false; - } - - if (self->expression.outr) { - compile_error(ast_ctx(self), "internal error: ast_return cannot be reused, it bears no result!"); - return false; - } - self->expression.outr = (ir_value*)1; - - if (self->operand) { - cgen = self->operand->codegen; - /* lvalue! */ - if (!(*cgen)((ast_expression*)(self->operand), func, false, &operand)) - return false; - - if (!ir_block_create_return(func->curblock, ast_ctx(self), operand)) - return false; - } else { - if (!ir_block_create_return(func->curblock, ast_ctx(self), NULL)) - return false; - } - - return true; -} - -bool ast_entfield_codegen(ast_entfield *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - ir_value *ent, *field; - - /* This function needs to take the 'lvalue' flag into account! - * As lvalue we provide a field-pointer, as rvalue we provide the - * value in a temp. - */ - - if (lvalue && self->expression.outl) { - *out = self->expression.outl; - return true; - } - - if (!lvalue && self->expression.outr) { - *out = self->expression.outr; - return true; - } - - cgen = self->entity->codegen; - if (!(*cgen)((ast_expression*)(self->entity), func, false, &ent)) - return false; - - cgen = self->field->codegen; - if (!(*cgen)((ast_expression*)(self->field), func, false, &field)) - return false; - - if (lvalue) { - /* address! */ - *out = ir_block_create_fieldaddress(func->curblock, ast_ctx(self), ast_function_label(func, "efa"), - ent, field); - } else { - *out = ir_block_create_load_from_ent(func->curblock, ast_ctx(self), ast_function_label(func, "efv"), - ent, field, self->expression.vtype); - /* Done AFTER error checking: - codegen_output_type(self, *out); - */ - } - if (!*out) { - compile_error(ast_ctx(self), "failed to create %s instruction (output type %s)", - (lvalue ? "ADDRESS" : "FIELD"), - type_name[self->expression.vtype]); - return false; - } - if (!lvalue) - codegen_output_type(self, *out); - - if (lvalue) - self->expression.outl = *out; - else - self->expression.outr = *out; - - /* Hm that should be it... */ - return true; -} - -bool ast_member_codegen(ast_member *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - ir_value *vec; - - /* in QC this is always an lvalue */ - if (lvalue && self->rvalue) { - compile_error(ast_ctx(self), "not an l-value (member access)"); - return false; - } - if (self->expression.outl) { - *out = self->expression.outl; - return true; - } - - cgen = self->owner->codegen; - if (!(*cgen)((ast_expression*)(self->owner), func, false, &vec)) - return false; - - if (vec->vtype != TYPE_VECTOR && - !(vec->vtype == TYPE_FIELD && self->owner->next->vtype == TYPE_VECTOR)) - { - return false; - } - - *out = ir_value_vector_member(vec, self->field); - self->expression.outl = *out; - - return (*out != NULL); -} - -bool ast_array_index_codegen(ast_array_index *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_value *arr; - ast_value *idx; - - if (!lvalue && self->expression.outr) { - *out = self->expression.outr; - return true; - } - if (lvalue && self->expression.outl) { - *out = self->expression.outl; - return true; - } - - if (!ast_istype(self->array, ast_value)) { - compile_error(ast_ctx(self), "array indexing this way is not supported"); - /* note this would actually be pointer indexing because the left side is - * not an actual array but (hopefully) an indexable expression. - * Once we get integer arithmetic, and GADDRESS/GSTORE/GLOAD instruction - * support this path will be filled. - */ - return false; - } - - arr = (ast_value*)self->array; - idx = (ast_value*)self->index; - - if (!ast_istype(self->index, ast_value) || !idx->hasvalue || idx->cvq != CV_CONST) { - /* Time to use accessor functions */ - ast_expression_codegen *cgen; - ir_value *iridx, *funval; - ir_instr *call; - - if (lvalue) { - compile_error(ast_ctx(self), "(.2) array indexing here needs a compile-time constant"); - return false; - } - - if (!arr->getter) { - compile_error(ast_ctx(self), "value has no getter, don't know how to index it"); - return false; - } - - cgen = self->index->codegen; - if (!(*cgen)((ast_expression*)(self->index), func, false, &iridx)) - return false; - - cgen = arr->getter->expression.codegen; - if (!(*cgen)((ast_expression*)(arr->getter), func, true, &funval)) - return false; - - call = ir_block_create_call(func->curblock, ast_ctx(self), ast_function_label(func, "fetch"), funval, false); - if (!call) - return false; - ir_call_param(call, iridx); - - *out = ir_call_value(call); - self->expression.outr = *out; - (*out)->vtype = self->expression.vtype; - codegen_output_type(self, *out); - return true; - } - - if (idx->expression.vtype == TYPE_FLOAT) { - unsigned int arridx = idx->constval.vfloat; - if (arridx >= self->array->count) - { - compile_error(ast_ctx(self), "array index out of bounds: %i", arridx); - return false; - } - *out = arr->ir_values[arridx]; - } - else if (idx->expression.vtype == TYPE_INTEGER) { - unsigned int arridx = idx->constval.vint; - if (arridx >= self->array->count) - { - compile_error(ast_ctx(self), "array index out of bounds: %i", arridx); - return false; - } - *out = arr->ir_values[arridx]; - } - else { - compile_error(ast_ctx(self), "array indexing here needs an integer constant"); - return false; - } - (*out)->vtype = self->expression.vtype; - codegen_output_type(self, *out); - return true; -} - -bool ast_argpipe_codegen(ast_argpipe *self, ast_function *func, bool lvalue, ir_value **out) -{ - *out = NULL; - if (lvalue) { - compile_error(ast_ctx(self), "argpipe node: not an lvalue"); - return false; - } - (void)func; - (void)out; - compile_error(ast_ctx(self), "TODO: argpipe codegen not implemented"); - return false; -} - -bool ast_ifthen_codegen(ast_ifthen *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - - ir_value *condval; - ir_value *dummy; - - ir_block *cond; - ir_block *ontrue; - ir_block *onfalse; - ir_block *ontrue_endblock = NULL; - ir_block *onfalse_endblock = NULL; - ir_block *merge = NULL; - int fold = 0; - - /* We don't output any value, thus also don't care about r/lvalue */ - (void)out; - (void)lvalue; - - if (self->expression.outr) { - compile_error(ast_ctx(self), "internal error: ast_ifthen cannot be reused, it bears no result!"); - return false; - } - self->expression.outr = (ir_value*)1; - - /* generate the condition */ - cgen = self->cond->codegen; - if (!(*cgen)((ast_expression*)(self->cond), func, false, &condval)) - return false; - /* update the block which will get the jump - because short-logic or ternaries may have changed this */ - cond = func->curblock; - - /* try constant folding away the condition */ - if ((fold = fold_cond_ifthen(condval, func, self)) != -1) - return fold; - - if (self->on_true) { - /* create on-true block */ - ontrue = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "ontrue")); - if (!ontrue) - return false; - - /* enter the block */ - func->curblock = ontrue; - - /* generate */ - cgen = self->on_true->codegen; - if (!(*cgen)((ast_expression*)(self->on_true), func, false, &dummy)) - return false; - - /* we now need to work from the current endpoint */ - ontrue_endblock = func->curblock; - } else - ontrue = NULL; - - /* on-false path */ - if (self->on_false) { - /* create on-false block */ - onfalse = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "onfalse")); - if (!onfalse) - return false; - - /* enter the block */ - func->curblock = onfalse; - - /* generate */ - cgen = self->on_false->codegen; - if (!(*cgen)((ast_expression*)(self->on_false), func, false, &dummy)) - return false; - - /* we now need to work from the current endpoint */ - onfalse_endblock = func->curblock; - } else - onfalse = NULL; - - /* Merge block were they all merge in to */ - if (!ontrue || !onfalse || !ontrue_endblock->final || !onfalse_endblock->final) - { - merge = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "endif")); - if (!merge) - return false; - /* add jumps ot the merge block */ - if (ontrue && !ontrue_endblock->final && !ir_block_create_jump(ontrue_endblock, ast_ctx(self), merge)) - return false; - if (onfalse && !onfalse_endblock->final && !ir_block_create_jump(onfalse_endblock, ast_ctx(self), merge)) - return false; - - /* Now enter the merge block */ - func->curblock = merge; - } - - /* we create the if here, that way all blocks are ordered :) - */ - if (!ir_block_create_if(cond, ast_ctx(self), condval, - (ontrue ? ontrue : merge), - (onfalse ? onfalse : merge))) - { - return false; - } - - return true; -} - -bool ast_ternary_codegen(ast_ternary *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - - ir_value *condval; - ir_value *trueval, *falseval; - ir_instr *phi; - - ir_block *cond = func->curblock; - ir_block *cond_out = NULL; - ir_block *ontrue, *ontrue_out = NULL; - ir_block *onfalse, *onfalse_out = NULL; - ir_block *merge; - int fold = 0; - - /* Ternary can never create an lvalue... */ - if (lvalue) - return false; - - /* In theory it shouldn't be possible to pass through a node twice, but - * in case we add any kind of optimization pass for the AST itself, it - * may still happen, thus we remember a created ir_value and simply return one - * if it already exists. - */ - if (self->expression.outr) { - *out = self->expression.outr; - return true; - } - - /* In the following, contraty to ast_ifthen, we assume both paths exist. */ - - /* generate the condition */ - func->curblock = cond; - cgen = self->cond->codegen; - if (!(*cgen)((ast_expression*)(self->cond), func, false, &condval)) - return false; - cond_out = func->curblock; - - /* try constant folding away the condition */ - if ((fold = fold_cond_ternary(condval, func, self)) != -1) - return fold; - - /* create on-true block */ - ontrue = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "tern_T")); - if (!ontrue) - return false; - else - { - /* enter the block */ - func->curblock = ontrue; - - /* generate */ - cgen = self->on_true->codegen; - if (!(*cgen)((ast_expression*)(self->on_true), func, false, &trueval)) - return false; - - ontrue_out = func->curblock; - } - - /* create on-false block */ - onfalse = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "tern_F")); - if (!onfalse) - return false; - else - { - /* enter the block */ - func->curblock = onfalse; - - /* generate */ - cgen = self->on_false->codegen; - if (!(*cgen)((ast_expression*)(self->on_false), func, false, &falseval)) - return false; - - onfalse_out = func->curblock; - } - - /* create merge block */ - merge = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "tern_out")); - if (!merge) - return false; - /* jump to merge block */ - if (!ir_block_create_jump(ontrue_out, ast_ctx(self), merge)) - return false; - if (!ir_block_create_jump(onfalse_out, ast_ctx(self), merge)) - return false; - - /* create if instruction */ - if (!ir_block_create_if(cond_out, ast_ctx(self), condval, ontrue, onfalse)) - return false; - - /* Now enter the merge block */ - func->curblock = merge; - - /* Here, now, we need a PHI node - * but first some sanity checking... - */ - if (trueval->vtype != falseval->vtype && trueval->vtype != TYPE_NIL && falseval->vtype != TYPE_NIL) { - /* error("ternary with different types on the two sides"); */ - compile_error(ast_ctx(self), "internal error: ternary operand types invalid"); - return false; - } - - /* create PHI */ - phi = ir_block_create_phi(merge, ast_ctx(self), ast_function_label(func, "phi"), self->expression.vtype); - if (!phi) { - compile_error(ast_ctx(self), "internal error: failed to generate phi node"); - return false; - } - ir_phi_add(phi, ontrue_out, trueval); - ir_phi_add(phi, onfalse_out, falseval); - - self->expression.outr = ir_phi_value(phi); - *out = self->expression.outr; - - codegen_output_type(self, *out); - - return true; -} - -bool ast_loop_codegen(ast_loop *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - - ir_value *dummy = NULL; - ir_value *precond = NULL; - ir_value *postcond = NULL; - - /* Since we insert some jumps "late" so we have blocks - * ordered "nicely", we need to keep track of the actual end-blocks - * of expressions to add the jumps to. - */ - ir_block *bbody = NULL, *end_bbody = NULL; - ir_block *bprecond = NULL, *end_bprecond = NULL; - ir_block *bpostcond = NULL, *end_bpostcond = NULL; - ir_block *bincrement = NULL, *end_bincrement = NULL; - ir_block *bout = NULL, *bin = NULL; - - /* let's at least move the outgoing block to the end */ - size_t bout_id; - - /* 'break' and 'continue' need to be able to find the right blocks */ - ir_block *bcontinue = NULL; - ir_block *bbreak = NULL; - - ir_block *tmpblock = NULL; - - (void)lvalue; - (void)out; - - if (self->expression.outr) { - compile_error(ast_ctx(self), "internal error: ast_loop cannot be reused, it bears no result!"); - return false; - } - self->expression.outr = (ir_value*)1; - - /* NOTE: - * Should we ever need some kind of block ordering, better make this function - * move blocks around than write a block ordering algorithm later... after all - * the ast and ir should work together, not against each other. - */ - - /* initexpr doesn't get its own block, it's pointless, it could create more blocks - * anyway if for example it contains a ternary. - */ - if (self->initexpr) - { - cgen = self->initexpr->codegen; - if (!(*cgen)((ast_expression*)(self->initexpr), func, false, &dummy)) - return false; - } - - /* Store the block from which we enter this chaos */ - bin = func->curblock; - - /* The pre-loop condition needs its own block since we - * need to be able to jump to the start of that expression. - */ - if (self->precond) - { - bprecond = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "pre_loop_cond")); - if (!bprecond) - return false; - - /* the pre-loop-condition the least important place to 'continue' at */ - bcontinue = bprecond; - - /* enter */ - func->curblock = bprecond; - - /* generate */ - cgen = self->precond->codegen; - if (!(*cgen)((ast_expression*)(self->precond), func, false, &precond)) - return false; - - end_bprecond = func->curblock; - } else { - bprecond = end_bprecond = NULL; - } - - /* Now the next blocks won't be ordered nicely, but we need to - * generate them this early for 'break' and 'continue'. - */ - if (self->increment) { - bincrement = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "loop_increment")); - if (!bincrement) - return false; - bcontinue = bincrement; /* increment comes before the pre-loop-condition */ - } else { - bincrement = end_bincrement = NULL; - } - - if (self->postcond) { - bpostcond = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "post_loop_cond")); - if (!bpostcond) - return false; - bcontinue = bpostcond; /* postcond comes before the increment */ - } else { - bpostcond = end_bpostcond = NULL; - } - - bout_id = vec_size(func->ir_func->blocks); - bout = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "after_loop")); - if (!bout) - return false; - bbreak = bout; - - /* The loop body... */ - /* if (self->body) */ - { - bbody = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "loop_body")); - if (!bbody) - return false; - - /* enter */ - func->curblock = bbody; - - vec_push(func->breakblocks, bbreak); - if (bcontinue) - vec_push(func->continueblocks, bcontinue); - else - vec_push(func->continueblocks, bbody); - - /* generate */ - if (self->body) { - cgen = self->body->codegen; - if (!(*cgen)((ast_expression*)(self->body), func, false, &dummy)) - return false; - } - - end_bbody = func->curblock; - vec_pop(func->breakblocks); - vec_pop(func->continueblocks); - } - - /* post-loop-condition */ - if (self->postcond) - { - /* enter */ - func->curblock = bpostcond; - - /* generate */ - cgen = self->postcond->codegen; - if (!(*cgen)((ast_expression*)(self->postcond), func, false, &postcond)) - return false; - - end_bpostcond = func->curblock; - } - - /* The incrementor */ - if (self->increment) - { - /* enter */ - func->curblock = bincrement; - - /* generate */ - cgen = self->increment->codegen; - if (!(*cgen)((ast_expression*)(self->increment), func, false, &dummy)) - return false; - - end_bincrement = func->curblock; - } - - /* In any case now, we continue from the outgoing block */ - func->curblock = bout; - - /* Now all blocks are in place */ - /* From 'bin' we jump to whatever comes first */ - if (bprecond) tmpblock = bprecond; - else tmpblock = bbody; /* can never be null */ - - /* DEAD CODE - else if (bpostcond) tmpblock = bpostcond; - else tmpblock = bout; - */ - - if (!ir_block_create_jump(bin, ast_ctx(self), tmpblock)) - return false; - - /* From precond */ - if (bprecond) - { - ir_block *ontrue, *onfalse; - ontrue = bbody; /* can never be null */ - - /* all of this is dead code - else if (bincrement) ontrue = bincrement; - else ontrue = bpostcond; - */ - - onfalse = bout; - if (self->pre_not) { - tmpblock = ontrue; - ontrue = onfalse; - onfalse = tmpblock; - } - if (!ir_block_create_if(end_bprecond, ast_ctx(self), precond, ontrue, onfalse)) - return false; - } - - /* from body */ - if (bbody) - { - if (bincrement) tmpblock = bincrement; - else if (bpostcond) tmpblock = bpostcond; - else if (bprecond) tmpblock = bprecond; - else tmpblock = bbody; - if (!end_bbody->final && !ir_block_create_jump(end_bbody, ast_ctx(self), tmpblock)) - return false; - } - - /* from increment */ - if (bincrement) - { - if (bpostcond) tmpblock = bpostcond; - else if (bprecond) tmpblock = bprecond; - else if (bbody) tmpblock = bbody; - else tmpblock = bout; - if (!ir_block_create_jump(end_bincrement, ast_ctx(self), tmpblock)) - return false; - } - - /* from postcond */ - if (bpostcond) - { - ir_block *ontrue, *onfalse; - if (bprecond) ontrue = bprecond; - else ontrue = bbody; /* can never be null */ - - /* all of this is dead code - else if (bincrement) ontrue = bincrement; - else ontrue = bpostcond; - */ - - onfalse = bout; - if (self->post_not) { - tmpblock = ontrue; - ontrue = onfalse; - onfalse = tmpblock; - } - if (!ir_block_create_if(end_bpostcond, ast_ctx(self), postcond, ontrue, onfalse)) - return false; - } - - /* Move 'bout' to the end */ - vec_remove(func->ir_func->blocks, bout_id, 1); - vec_push(func->ir_func->blocks, bout); - - return true; -} - -bool ast_breakcont_codegen(ast_breakcont *self, ast_function *func, bool lvalue, ir_value **out) -{ - ir_block *target; - - *out = NULL; - - if (lvalue) { - compile_error(ast_ctx(self), "break/continue expression is not an l-value"); - return false; - } - - if (self->expression.outr) { - compile_error(ast_ctx(self), "internal error: ast_breakcont cannot be reused!"); - return false; - } - self->expression.outr = (ir_value*)1; - - if (self->is_continue) - target = func->continueblocks[vec_size(func->continueblocks)-1-self->levels]; - else - target = func->breakblocks[vec_size(func->breakblocks)-1-self->levels]; - - if (!target) { - compile_error(ast_ctx(self), "%s is lacking a target block", (self->is_continue ? "continue" : "break")); - return false; - } - - if (!ir_block_create_jump(func->curblock, ast_ctx(self), target)) - return false; - return true; -} - -bool ast_switch_codegen(ast_switch *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - - ast_switch_case *def_case = NULL; - ir_block *def_bfall = NULL; - ir_block *def_bfall_to = NULL; - bool set_def_bfall_to = false; - - ir_value *dummy = NULL; - ir_value *irop = NULL; - ir_block *bout = NULL; - ir_block *bfall = NULL; - size_t bout_id; - size_t c; - - char typestr[1024]; - uint16_t cmpinstr; - - if (lvalue) { - compile_error(ast_ctx(self), "switch expression is not an l-value"); - return false; - } - - if (self->expression.outr) { - compile_error(ast_ctx(self), "internal error: ast_switch cannot be reused!"); - return false; - } - self->expression.outr = (ir_value*)1; - - (void)lvalue; - (void)out; - - cgen = self->operand->codegen; - if (!(*cgen)((ast_expression*)(self->operand), func, false, &irop)) - return false; - - if (!vec_size(self->cases)) - return true; - - cmpinstr = type_eq_instr[irop->vtype]; - if (cmpinstr >= VINSTR_END) { - ast_type_to_string(self->operand, typestr, sizeof(typestr)); - compile_error(ast_ctx(self), "invalid type to perform a switch on: %s", typestr); - return false; - } - - bout_id = vec_size(func->ir_func->blocks); - bout = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "after_switch")); - if (!bout) - return false; - - /* setup the break block */ - vec_push(func->breakblocks, bout); - - /* Now create all cases */ - for (c = 0; c < vec_size(self->cases); ++c) { - ir_value *cond, *val; - ir_block *bcase, *bnot; - size_t bnot_id; - - ast_switch_case *swcase = &self->cases[c]; - - if (swcase->value) { - /* A regular case */ - /* generate the condition operand */ - cgen = swcase->value->codegen; - if (!(*cgen)((ast_expression*)(swcase->value), func, false, &val)) - return false; - /* generate the condition */ - cond = ir_block_create_binop(func->curblock, ast_ctx(self), ast_function_label(func, "switch_eq"), cmpinstr, irop, val); - if (!cond) - return false; - - bcase = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "case")); - bnot_id = vec_size(func->ir_func->blocks); - bnot = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "not_case")); - if (!bcase || !bnot) - return false; - if (set_def_bfall_to) { - set_def_bfall_to = false; - def_bfall_to = bcase; - } - if (!ir_block_create_if(func->curblock, ast_ctx(self), cond, bcase, bnot)) - return false; - - /* Make the previous case-end fall through */ - if (bfall && !bfall->final) { - if (!ir_block_create_jump(bfall, ast_ctx(self), bcase)) - return false; - } - - /* enter the case */ - func->curblock = bcase; - cgen = swcase->code->codegen; - if (!(*cgen)((ast_expression*)swcase->code, func, false, &dummy)) - return false; - - /* remember this block to fall through from */ - bfall = func->curblock; - - /* enter the else and move it down */ - func->curblock = bnot; - vec_remove(func->ir_func->blocks, bnot_id, 1); - vec_push(func->ir_func->blocks, bnot); - } else { - /* The default case */ - /* Remember where to fall through from: */ - def_bfall = bfall; - bfall = NULL; - /* remember which case it was */ - def_case = swcase; - /* And the next case will be remembered */ - set_def_bfall_to = true; - } - } - - /* Jump from the last bnot to bout */ - if (bfall && !bfall->final && !ir_block_create_jump(bfall, ast_ctx(self), bout)) { - /* - astwarning(ast_ctx(bfall), WARN_???, "missing break after last case"); - */ - return false; - } - - /* If there was a default case, put it down here */ - if (def_case) { - ir_block *bcase; - - /* No need to create an extra block */ - bcase = func->curblock; - - /* Insert the fallthrough jump */ - if (def_bfall && !def_bfall->final) { - if (!ir_block_create_jump(def_bfall, ast_ctx(self), bcase)) - return false; - } - - /* Now generate the default code */ - cgen = def_case->code->codegen; - if (!(*cgen)((ast_expression*)def_case->code, func, false, &dummy)) - return false; - - /* see if we need to fall through */ - if (def_bfall_to && !func->curblock->final) - { - if (!ir_block_create_jump(func->curblock, ast_ctx(self), def_bfall_to)) - return false; - } - } - - /* Jump from the last bnot to bout */ - if (!func->curblock->final && !ir_block_create_jump(func->curblock, ast_ctx(self), bout)) - return false; - /* enter the outgoing block */ - func->curblock = bout; - - /* restore the break block */ - vec_pop(func->breakblocks); - - /* Move 'bout' to the end, it's nicer */ - vec_remove(func->ir_func->blocks, bout_id, 1); - vec_push(func->ir_func->blocks, bout); - - return true; -} - -bool ast_label_codegen(ast_label *self, ast_function *func, bool lvalue, ir_value **out) -{ - size_t i; - ir_value *dummy; - - if (self->undefined) { - compile_error(ast_ctx(self), "internal error: ast_label never defined"); - return false; - } - - *out = NULL; - if (lvalue) { - compile_error(ast_ctx(self), "internal error: ast_label cannot be an lvalue"); - return false; - } - - /* simply create a new block and jump to it */ - self->irblock = ir_function_create_block(ast_ctx(self), func->ir_func, self->name); - if (!self->irblock) { - compile_error(ast_ctx(self), "failed to allocate label block `%s`", self->name); - return false; - } - if (!func->curblock->final) { - if (!ir_block_create_jump(func->curblock, ast_ctx(self), self->irblock)) - return false; - } - - /* enter the new block */ - func->curblock = self->irblock; - - /* Generate all the leftover gotos */ - for (i = 0; i < vec_size(self->gotos); ++i) { - if (!ast_goto_codegen(self->gotos[i], func, false, &dummy)) - return false; - } - - return true; -} - -bool ast_goto_codegen(ast_goto *self, ast_function *func, bool lvalue, ir_value **out) -{ - *out = NULL; - if (lvalue) { - compile_error(ast_ctx(self), "internal error: ast_goto cannot be an lvalue"); - return false; - } - - if (self->target->irblock) { - if (self->irblock_from) { - /* we already tried once, this is the callback */ - self->irblock_from->final = false; - if (!ir_block_create_goto(self->irblock_from, ast_ctx(self), self->target->irblock)) { - compile_error(ast_ctx(self), "failed to generate goto to `%s`", self->name); - return false; - } - } - else - { - if (!ir_block_create_goto(func->curblock, ast_ctx(self), self->target->irblock)) { - compile_error(ast_ctx(self), "failed to generate goto to `%s`", self->name); - return false; - } - } - } - else - { - /* the target has not yet been created... - * close this block in a sneaky way: - */ - func->curblock->final = true; - self->irblock_from = func->curblock; - ast_label_register_goto(self->target, self); - } - - return true; -} - -#include -bool ast_state_codegen(ast_state *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - - ir_value *frameval, *thinkval; - - if (lvalue) { - compile_error(ast_ctx(self), "not an l-value (state operation)"); - return false; - } - if (self->expression.outr) { - compile_error(ast_ctx(self), "internal error: ast_state cannot be reused!"); - return false; - } - *out = NULL; - - cgen = self->framenum->codegen; - if (!(*cgen)((ast_expression*)(self->framenum), func, false, &frameval)) - return false; - if (!frameval) - return false; - - cgen = self->nextthink->codegen; - if (!(*cgen)((ast_expression*)(self->nextthink), func, false, &thinkval)) - return false; - if (!frameval) - return false; - - if (!ir_block_create_state_op(func->curblock, ast_ctx(self), frameval, thinkval)) { - compile_error(ast_ctx(self), "failed to create STATE instruction"); - return false; - } - - self->expression.outr = (ir_value*)1; - return true; -} - -bool ast_call_codegen(ast_call *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - ir_value **params; - ir_instr *callinstr; - size_t i; - - ir_value *funval = NULL; - - /* return values are never lvalues */ - if (lvalue) { - compile_error(ast_ctx(self), "not an l-value (function call)"); - return false; - } - - if (self->expression.outr) { - *out = self->expression.outr; - return true; - } - - cgen = self->func->codegen; - if (!(*cgen)((ast_expression*)(self->func), func, false, &funval)) - return false; - if (!funval) - return false; - - params = NULL; - - /* parameters */ - for (i = 0; i < vec_size(self->params); ++i) - { - ir_value *param; - ast_expression *expr = self->params[i]; - - cgen = expr->codegen; - if (!(*cgen)(expr, func, false, ¶m)) - goto error; - if (!param) - goto error; - vec_push(params, param); - } - - /* varargs counter */ - if (self->va_count) { - ir_value *va_count; - ir_builder *builder = func->curblock->owner->owner; - cgen = self->va_count->codegen; - if (!(*cgen)((ast_expression*)(self->va_count), func, false, &va_count)) - return false; - if (!ir_block_create_store_op(func->curblock, ast_ctx(self), INSTR_STORE_F, - ir_builder_get_va_count(builder), va_count)) - { - return false; - } - } - - callinstr = ir_block_create_call(func->curblock, ast_ctx(self), - ast_function_label(func, "call"), - funval, !!(self->func->flags & AST_FLAG_NORETURN)); - if (!callinstr) - goto error; - - for (i = 0; i < vec_size(params); ++i) { - ir_call_param(callinstr, params[i]); - } - - *out = ir_call_value(callinstr); - self->expression.outr = *out; - - codegen_output_type(self, *out); - - vec_free(params); - return true; -error: - vec_free(params); - return false; -} diff --git a/ast.cpp b/ast.cpp new file mode 100644 index 0000000..cf8ffc7 --- /dev/null +++ b/ast.cpp @@ -0,0 +1,3051 @@ +#include + +#include +#include + +#include "gmqcc.h" +#include "ast.h" +#include "fold.h" +//#include "parser.h" + +#include "algo.h" + +/* Initialize main ast node aprts */ +ast_node::ast_node(lex_ctx_t ctx, int node_type) + : m_context(ctx) + , m_node_type(node_type) + , m_keep_node(false) + , m_side_effects(false) +{ +} + +ast_node::~ast_node() +{ +} + +/* weight and side effects */ +void ast_node::propagateSideEffects(const ast_node *other) +{ + if (other->m_side_effects) + m_side_effects = true; +} + +/* General expression initialization */ +ast_expression::ast_expression(lex_ctx_t ctx, int nodetype, qc_type type) + : ast_node(ctx, nodetype) + , m_vtype(type) +{ + if (OPTS_OPTION_BOOL(OPTION_COVERAGE)) + m_flags |= AST_FLAG_BLOCK_COVERAGE; +} +ast_expression::ast_expression(lex_ctx_t ctx, int nodetype) + : ast_expression(ctx, nodetype, TYPE_VOID) +{} + +ast_expression::~ast_expression() +{ + if (m_next) + delete m_next; + if (m_varparam) + delete m_varparam; +} + +ast_expression::ast_expression(ast_copy_type_t, const ast_expression &other) + : ast_expression(ast_copy_type, other.m_context, other) +{} + +ast_expression::ast_expression(ast_copy_type_t, lex_ctx_t ctx, const ast_expression &other) + : ast_expression(ast_copy_type, TYPE_ast_expression, ctx, other) +{} + +ast_expression::ast_expression(ast_copy_type_t, int nodetype, const ast_expression &other) + : ast_expression(ast_copy_type, nodetype, other.m_context, other) +{} + +ast_expression::ast_expression(ast_copy_type_t, int nodetype, lex_ctx_t ctx, const ast_expression &other) + : ast_expression(ctx, nodetype) +{ + m_vtype = other.m_vtype; + m_count = other.m_count; + m_flags = other.m_flags; + if (other.m_next) + m_next = new ast_expression(ast_copy_type, *other.m_next); + m_type_params.reserve(other.m_type_params.size()); + for (auto &it : other.m_type_params) + m_type_params.emplace_back(new ast_value(ast_copy_type, *it)); +} + + +ast_expression *ast_expression::shallowType(lex_ctx_t ctx, qc_type vtype) { + auto expr = new ast_expression(ctx, TYPE_ast_expression); + expr->m_vtype = vtype; + return expr; +} + +void ast_expression::adoptType(const ast_expression &other) +{ + m_vtype = other.m_vtype; + if (other.m_next) + m_next = new ast_expression(ast_copy_type, *other.m_next); + m_count = other.m_count; + m_flags = other.m_flags; + m_type_params.clear(); + m_type_params.reserve(other.m_type_params.size()); + for (auto &it : other.m_type_params) + m_type_params.emplace_back(new ast_value(ast_copy_type, *it)); +} + +bool ast_expression::compareType(const ast_expression &other) const +{ + if (m_vtype == TYPE_NIL || + other.m_vtype == TYPE_NIL) + return true; + if (m_vtype != other.m_vtype) + return false; + if (!m_next != !other.m_next) + return false; + if (m_type_params.size() != other.m_type_params.size()) + return false; + if ((m_flags & AST_FLAG_TYPE_MASK) != + (other.m_flags & AST_FLAG_TYPE_MASK) ) + { + return false; + } + if (m_type_params.size()) { + size_t i; + for (i = 0; i < m_type_params.size(); ++i) { + if (!m_type_params[i]->compareType(*other.m_type_params[i])) + return false; + } + } + if (m_next) + return m_next->compareType(*other.m_next); + return true; +} + +bool ast_expression::codegen(ast_function*, bool, ir_value**) { + compile_error(m_context, "ast_expression::codegen called!"); + abort(); + return false; +} + +ast_value::ast_value(ast_copy_type_t, const ast_value &other, const std::string &name) + : ast_value(ast_copy_type, static_cast(other), name) +{ + m_keep_node = true; // keep values, always + memset(&m_constval, 0, sizeof(m_constval)); +} + +ast_value::ast_value(ast_copy_type_t, const ast_value &other) + : ast_value(ast_copy_type, static_cast(other), other.m_name) +{ + m_keep_node = true; // keep values, always + memset(&m_constval, 0, sizeof(m_constval)); +} + +ast_value::ast_value(ast_copy_type_t, const ast_expression &other, const std::string &name) + : ast_expression(ast_copy_type, TYPE_ast_value, other) + , m_name(name) +{ + m_keep_node = true; // keep values, always + memset(&m_constval, 0, sizeof(m_constval)); +} + +ast_value::ast_value(lex_ctx_t ctx, const std::string &name, qc_type t) + : ast_expression(ctx, TYPE_ast_value, t) + , m_name(name) +{ + m_keep_node = true; // keep values, always + memset(&m_constval, 0, sizeof(m_constval)); +} + +ast_value::~ast_value() +{ + if (m_argcounter) + mem_d((void*)m_argcounter); + if (m_hasvalue) { + switch (m_vtype) + { + case TYPE_STRING: + mem_d((void*)m_constval.vstring); + break; + case TYPE_FUNCTION: + // unlink us from the function node + m_constval.vfunc->m_function_type = nullptr; + break; + // NOTE: delete function? currently collected in + // the parser structure + default: + break; + } + } + + // initlist imples an array which implies .next in the expression exists. + if (m_initlist.size() && m_next->m_vtype == TYPE_STRING) { + for (auto &it : m_initlist) + if (it.vstring) + mem_d(it.vstring); + } +} + +static size_t ast_type_to_string_impl(const ast_expression *e, char *buf, size_t bufsize, size_t pos) +{ + const char *typestr; + size_t typelen; + size_t i; + + if (!e) { + if (pos + 6 >= bufsize) + goto full; + util_strncpy(buf + pos, "(null)", 6); + return pos + 6; + } + + if (pos + 1 >= bufsize) + goto full; + + switch (e->m_vtype) { + case TYPE_VARIANT: + util_strncpy(buf + pos, "(variant)", 9); + return pos + 9; + + case TYPE_FIELD: + buf[pos++] = '.'; + return ast_type_to_string_impl(e->m_next, buf, bufsize, pos); + + case TYPE_POINTER: + if (pos + 3 >= bufsize) + goto full; + buf[pos++] = '*'; + buf[pos++] = '('; + pos = ast_type_to_string_impl(e->m_next, buf, bufsize, pos); + if (pos + 1 >= bufsize) + goto full; + buf[pos++] = ')'; + return pos; + + case TYPE_FUNCTION: + pos = ast_type_to_string_impl(e->m_next, buf, bufsize, pos); + if (pos + 2 >= bufsize) + goto full; + if (e->m_type_params.empty()) { + buf[pos++] = '('; + buf[pos++] = ')'; + return pos; + } + buf[pos++] = '('; + pos = ast_type_to_string_impl(e->m_type_params[0].get(), buf, bufsize, pos); + for (i = 1; i < e->m_type_params.size(); ++i) { + if (pos + 2 >= bufsize) + goto full; + buf[pos++] = ','; + buf[pos++] = ' '; + pos = ast_type_to_string_impl(e->m_type_params[i].get(), buf, bufsize, pos); + } + if (pos + 1 >= bufsize) + goto full; + buf[pos++] = ')'; + return pos; + + case TYPE_ARRAY: + pos = ast_type_to_string_impl(e->m_next, buf, bufsize, pos); + if (pos + 1 >= bufsize) + goto full; + buf[pos++] = '['; + pos += util_snprintf(buf + pos, bufsize - pos - 1, "%i", (int)e->m_count); + if (pos + 1 >= bufsize) + goto full; + buf[pos++] = ']'; + return pos; + + default: + typestr = type_name[e->m_vtype]; + typelen = strlen(typestr); + if (pos + typelen >= bufsize) + goto full; + util_strncpy(buf + pos, typestr, typelen); + return pos + typelen; + } + +full: + buf[bufsize-3] = '.'; + buf[bufsize-2] = '.'; + buf[bufsize-1] = '.'; + return bufsize; +} + +void ast_type_to_string(const ast_expression *e, char *buf, size_t bufsize) +{ + size_t pos = ast_type_to_string_impl(e, buf, bufsize-1, 0); + buf[pos] = 0; +} + +void ast_value::addParam(ast_value *p) +{ + m_type_params.emplace_back(p); +} + +ast_binary::ast_binary(lex_ctx_t ctx, int op, + ast_expression* left, ast_expression* right) + : ast_expression(ctx, TYPE_ast_binary) + , m_op(op) + // m_left/m_right happen after the peephole step right below + , m_right_first(false) +{ + if (ast_istype(right, ast_unary) && OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) { + ast_unary *unary = ((ast_unary*)right); + ast_expression *normal = unary->m_operand; + + /* make a-(-b) => a + b */ + if (unary->m_op == VINSTR_NEG_F || unary->m_op == VINSTR_NEG_V) { + if (op == INSTR_SUB_F) { + op = INSTR_ADD_F; + right = normal; + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + } else if (op == INSTR_SUB_V) { + op = INSTR_ADD_V; + right = normal; + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + } + } + } + + m_left = left; + m_right = right; + + propagateSideEffects(left); + propagateSideEffects(right); + + if (op >= INSTR_EQ_F && op <= INSTR_GT) + m_vtype = TYPE_FLOAT; + else if (op == INSTR_AND || op == INSTR_OR) { + if (OPTS_FLAG(PERL_LOGIC)) + adoptType(*right); + else + m_vtype = TYPE_FLOAT; + } + else if (op == INSTR_BITAND || op == INSTR_BITOR) + m_vtype = TYPE_FLOAT; + else if (op == INSTR_MUL_VF || op == INSTR_MUL_FV) + m_vtype = TYPE_VECTOR; + else if (op == INSTR_MUL_V) + m_vtype = TYPE_FLOAT; + else + m_vtype = left->m_vtype; + + // references all + m_refs = AST_REF_ALL; +} + +ast_binary::~ast_binary() +{ + if (m_refs & AST_REF_LEFT) ast_unref(m_left); + if (m_refs & AST_REF_RIGHT) ast_unref(m_right); +} + +ast_binstore::ast_binstore(lex_ctx_t ctx, int storop, int mathop, + ast_expression* left, ast_expression* right) + : ast_expression(ctx, TYPE_ast_binstore) + , m_opstore(storop) + , m_opbin(mathop) + , m_dest(left) + , m_source(right) + , m_keep_dest(false) +{ + m_side_effects = true; + adoptType(*left); +} + +ast_binstore::~ast_binstore() +{ + if (!m_keep_dest) + ast_unref(m_dest); + ast_unref(m_source); +} + +ast_unary* ast_unary::make(lex_ctx_t ctx, int op, ast_expression *expr) +{ + // handle double negation, double bitwise or logical not + if (op == opid2('!','P') || + op == opid2('~','P') || + op == opid2('-','P')) + { + if (ast_istype(expr, ast_unary) && OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) { + ast_unary *unary = reinterpret_cast(expr); + if (unary->m_op == op) { + auto out = reinterpret_cast(unary->m_operand); + unary->m_operand = nullptr; + delete unary; + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + return out; + } + } + } + + return new ast_unary(ctx, op, expr); +} + +ast_unary::ast_unary(lex_ctx_t ctx, int op, ast_expression *expr) + : ast_expression(ctx, TYPE_ast_unary) + , m_op(op) + , m_operand(expr) +{ + propagateSideEffects(expr); + if ((op >= INSTR_NOT_F && op <= INSTR_NOT_FNC) || op == VINSTR_NEG_F) { + m_vtype = TYPE_FLOAT; + } else if (op == VINSTR_NEG_V) { + m_vtype = TYPE_VECTOR; + } else { + compile_error(ctx, "cannot determine type of unary operation %s", util_instr_str[op]); + } +} + +ast_unary::~ast_unary() +{ + if (m_operand) + ast_unref(m_operand); +} + +ast_return::ast_return(lex_ctx_t ctx, ast_expression *expr) + : ast_expression(ctx, TYPE_ast_return) + , m_operand(expr) +{ + if (expr) + propagateSideEffects(expr); +} + +ast_return::~ast_return() +{ + if (m_operand) + ast_unref(m_operand); +} + +ast_entfield::ast_entfield(lex_ctx_t ctx, ast_expression *entity, ast_expression *field) + : ast_entfield(ctx, entity, field, field->m_next) +{ + if (field->m_vtype != TYPE_FIELD) + compile_error(ctx, "ast_entfield with expression not of type field"); +} + +ast_entfield::ast_entfield(lex_ctx_t ctx, ast_expression *entity, ast_expression *field, const ast_expression *outtype) + : ast_expression(ctx, TYPE_ast_entfield) + , m_entity(entity) + , m_field(field) +{ + propagateSideEffects(m_entity); + propagateSideEffects(m_field); + + if (!outtype) { + compile_error(ctx, "ast_entfield: field has no type"); + m_vtype = TYPE_VOID; + } + else + adoptType(*outtype); +} + +ast_entfield::~ast_entfield() +{ + ast_unref(m_entity); + ast_unref(m_field); +} + +ast_member *ast_member::make(lex_ctx_t ctx, ast_expression *owner, unsigned int field, const std::string &name) +{ + if (field >= 3) { + compile_error(ctx, "ast_member: invalid field (>=3): %u", field); + return nullptr; + } + if (owner->m_vtype != TYPE_VECTOR && + owner->m_vtype != TYPE_FIELD) + { + compile_error(ctx, "member-access on an invalid owner of type %s", type_name[owner->m_vtype]); + return nullptr; + } + return new ast_member(ctx, owner, field, name); +} + +ast_member::ast_member(lex_ctx_t ctx, ast_expression *owner, unsigned int field, const std::string &name) + : ast_expression(ctx, TYPE_ast_member) + , m_owner(owner) + , m_field(field) + , m_name(name) + , m_rvalue(false) +{ + m_keep_node = true; + + if (m_owner->m_vtype == TYPE_VECTOR) { + m_vtype = TYPE_FLOAT; + m_next = nullptr; + } else { + m_vtype = TYPE_FIELD; + m_next = ast_expression::shallowType(ctx, TYPE_FLOAT); + } + + propagateSideEffects(owner); +} + +ast_member::~ast_member() +{ + // The owner is always an ast_value, which has .keep_node=true, + // also: ast_members are usually deleted after the owner, thus + // this will cause invalid access + //ast_unref(self->m_owner); + // once we allow (expression).x to access a vector-member, we need + // to change this: preferably by creating an alternate ast node for this + // purpose that is not garbage-collected. +} + +ast_array_index* ast_array_index::make(lex_ctx_t ctx, ast_expression *array, ast_expression *index) +{ + ast_expression *outtype = array->m_next; + if (!outtype) { + // field has no type + return nullptr; + } + + return new ast_array_index(ctx, array, index); +} + +ast_array_index::ast_array_index(lex_ctx_t ctx, ast_expression *array, ast_expression *index) + : ast_expression(ctx, TYPE_ast_array_index) + , m_array(array) + , m_index(index) +{ + propagateSideEffects(array); + propagateSideEffects(index); + + ast_expression *outtype = m_array->m_next; + adoptType(*outtype); + + if (array->m_vtype == TYPE_FIELD && outtype->m_vtype == TYPE_ARRAY) { + // FIXME: investigate - this is not possible after adoptType + //if (m_vtype != TYPE_ARRAY) { + // compile_error(self->m_context, "array_index node on type"); + // ast_array_index_delete(self); + // return nullptr; + //} + + m_array = outtype; + m_vtype = TYPE_FIELD; + } +} + +ast_array_index::~ast_array_index() +{ + if (m_array) + ast_unref(m_array); + if (m_index) + ast_unref(m_index); +} + +ast_argpipe::ast_argpipe(lex_ctx_t ctx, ast_expression *index) + : ast_expression(ctx, TYPE_ast_argpipe) + , m_index(index) +{ + m_vtype = TYPE_NOEXPR; +} + +ast_argpipe::~ast_argpipe() +{ + if (m_index) + ast_unref(m_index); +} + +ast_store::ast_store(lex_ctx_t ctx, int op, ast_expression *dest, ast_expression *source) + : ast_expression(ctx, TYPE_ast_store) + , m_op(op) + , m_dest(dest) + , m_source(source) +{ + m_side_effects = true; + adoptType(*dest); +} + +ast_store::~ast_store() +{ + ast_unref(m_dest); + ast_unref(m_source); +} + +ast_ifthen::ast_ifthen(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse) + : ast_expression(ctx, TYPE_ast_ifthen) + , m_cond(cond) + , m_on_true(ontrue) + , m_on_false(onfalse) +{ + propagateSideEffects(cond); + if (ontrue) + propagateSideEffects(ontrue); + if (onfalse) + propagateSideEffects(onfalse); +} + +ast_ifthen::~ast_ifthen() +{ + ast_unref(m_cond); + if (m_on_true) + ast_unref(m_on_true); + if (m_on_false) + ast_unref(m_on_false); +} + +ast_ternary::ast_ternary(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse) + : ast_expression(ctx, TYPE_ast_ternary) + , m_cond(cond) + , m_on_true(ontrue) + , m_on_false(onfalse) +{ + propagateSideEffects(cond); + propagateSideEffects(ontrue); + propagateSideEffects(onfalse); + + if (ontrue->m_vtype == TYPE_NIL) + adoptType(*onfalse); + else + adoptType(*ontrue); +} + +ast_ternary::~ast_ternary() +{ + /* the if()s are only there because computed-gotos can set them + * to nullptr + */ + if (m_cond) ast_unref(m_cond); + if (m_on_true) ast_unref(m_on_true); + if (m_on_false) ast_unref(m_on_false); +} + +ast_loop::ast_loop(lex_ctx_t ctx, + ast_expression *initexpr, + ast_expression *precond, bool pre_not, + ast_expression *postcond, bool post_not, + ast_expression *increment, + ast_expression *body) + : ast_expression(ctx, TYPE_ast_loop) + , m_initexpr(initexpr) + , m_precond(precond) + , m_postcond(postcond) + , m_increment(increment) + , m_body(body) + , m_pre_not(pre_not) + , m_post_not(post_not) +{ + if (initexpr) + propagateSideEffects(initexpr); + if (precond) + propagateSideEffects(precond); + if (postcond) + propagateSideEffects(postcond); + if (increment) + propagateSideEffects(increment); + if (body) + propagateSideEffects(body); +} + +ast_loop::~ast_loop() +{ + if (m_initexpr) + ast_unref(m_initexpr); + if (m_precond) + ast_unref(m_precond); + if (m_postcond) + ast_unref(m_postcond); + if (m_increment) + ast_unref(m_increment); + if (m_body) + ast_unref(m_body); +} + +ast_breakcont::ast_breakcont(lex_ctx_t ctx, bool iscont, unsigned int levels) + : ast_expression(ctx, TYPE_ast_breakcont) + , m_is_continue(iscont) + , m_levels(levels) +{ +} + +ast_breakcont::~ast_breakcont() +{ +} + +ast_switch::ast_switch(lex_ctx_t ctx, ast_expression *op) + : ast_expression(ctx, TYPE_ast_switch) + , m_operand(op) +{ + propagateSideEffects(op); +} + +ast_switch::~ast_switch() +{ + ast_unref(m_operand); + + for (auto &it : m_cases) { + if (it.m_value) + ast_unref(it.m_value); + ast_unref(it.m_code); + } +} + +ast_label::ast_label(lex_ctx_t ctx, const std::string &name, bool undefined) + : ast_expression(ctx, TYPE_ast_label) + , m_name(name) + , m_irblock(nullptr) + , m_undefined(undefined) +{ + m_vtype = TYPE_NOEXPR; +} + +ast_label::~ast_label() +{ +} + +void ast_label::registerGoto(ast_goto *g) +{ + m_gotos.push_back(g); +} + +ast_goto::ast_goto(lex_ctx_t ctx, const std::string &name) + : ast_expression(ctx, TYPE_ast_goto) + , m_name(name) + , m_target(nullptr) + , m_irblock_from(nullptr) +{ +} + +ast_goto::~ast_goto() +{ +} + +void ast_goto::setLabel(ast_label *label) +{ + m_target = label; +} + +ast_state::ast_state(lex_ctx_t ctx, ast_expression *frame, ast_expression *think) + : ast_expression(ctx, TYPE_ast_expression) + , m_framenum(frame) + , m_nextthink(think) +{ +} + +ast_state::~ast_state() +{ + if (m_framenum) + ast_unref(m_framenum); + if (m_nextthink) + ast_unref(m_nextthink); +} + +ast_call *ast_call::make(lex_ctx_t ctx, ast_expression *funcexpr) +{ + if (!funcexpr->m_next) { + compile_error(ctx, "not a function"); + return nullptr; + } + return new ast_call(ctx, funcexpr); +} + +ast_call::ast_call(lex_ctx_t ctx, ast_expression *funcexpr) + : ast_expression(ctx, TYPE_ast_call) + , m_func(funcexpr) + , m_va_count(nullptr) +{ + m_side_effects = true; + adoptType(*funcexpr->m_next); +} + +ast_call::~ast_call() +{ + for (auto &it : m_params) + ast_unref(it); + + if (m_func) + ast_unref(m_func); + + if (m_va_count) + ast_unref(m_va_count); +} + +bool ast_call::checkVararg(ast_expression *va_type, ast_expression *exp_type) const +{ + char texp[1024]; + char tgot[1024]; + if (!exp_type) + return true; + if (!va_type || !va_type->compareType(*exp_type)) + { + if (va_type && exp_type) + { + ast_type_to_string(va_type, tgot, sizeof(tgot)); + ast_type_to_string(exp_type, texp, sizeof(texp)); + if (OPTS_FLAG(UNSAFE_VARARGS)) { + if (compile_warning(m_context, WARN_UNSAFE_TYPES, + "piped variadic argument differs in type: constrained to type %s, expected type %s", + tgot, texp)) + return false; + } else { + compile_error(m_context, + "piped variadic argument differs in type: constrained to type %s, expected type %s", + tgot, texp); + return false; + } + } + else + { + ast_type_to_string(exp_type, texp, sizeof(texp)); + if (OPTS_FLAG(UNSAFE_VARARGS)) { + if (compile_warning(m_context, WARN_UNSAFE_TYPES, + "piped variadic argument may differ in type: expected type %s", + texp)) + return false; + } else { + compile_error(m_context, + "piped variadic argument may differ in type: expected type %s", + texp); + return false; + } + } + } + return true; +} + +bool ast_call::checkTypes(ast_expression *va_type) const +{ + char texp[1024]; + char tgot[1024]; + size_t i; + bool retval = true; + + size_t count = m_params.size(); + if (count > m_func->m_type_params.size()) + count = m_func->m_type_params.size(); + + for (i = 0; i < count; ++i) { + if (ast_istype(m_params[i], ast_argpipe)) { + /* warn about type safety instead */ + if (i+1 != count) { + compile_error(m_context, "argpipe must be the last parameter to a function call"); + return false; + } + if (!checkVararg(va_type, m_func->m_type_params[i].get())) + retval = false; + } + else if (!m_params[i]->compareType(*m_func->m_type_params[i])) + { + ast_type_to_string(m_params[i], tgot, sizeof(tgot)); + ast_type_to_string(m_func->m_type_params[i].get(), texp, sizeof(texp)); + compile_error(m_context, "invalid type for parameter %u in function call: expected %s, got %s", + (unsigned int)(i+1), texp, tgot); + /* we don't immediately return */ + retval = false; + } + } + count = m_params.size(); + if (count > m_func->m_type_params.size() && m_func->m_varparam) { + for (; i < count; ++i) { + if (ast_istype(m_params[i], ast_argpipe)) { + /* warn about type safety instead */ + if (i+1 != count) { + compile_error(m_context, "argpipe must be the last parameter to a function call"); + return false; + } + if (!checkVararg(va_type, m_func->m_varparam)) + retval = false; + } + else if (!m_params[i]->compareType(*m_func->m_varparam)) + { + ast_type_to_string(m_params[i], tgot, sizeof(tgot)); + ast_type_to_string(m_func->m_varparam, texp, sizeof(texp)); + compile_error(m_context, "invalid type for variadic parameter %u in function call: expected %s, got %s", + (unsigned int)(i+1), texp, tgot); + /* we don't immediately return */ + retval = false; + } + } + } + return retval; +} + +ast_block::ast_block(lex_ctx_t ctx) + : ast_expression(ctx, TYPE_ast_block) +{ +} + +ast_block::~ast_block() +{ + for (auto &it : m_exprs) ast_unref(it); + for (auto &it : m_locals) delete it; + for (auto &it : m_collect) delete it; +} + +void ast_block::setType(const ast_expression &from) +{ + if (m_next) + delete m_next; + adoptType(from); +} + + +bool ast_block::addExpr(ast_expression *e) +{ + propagateSideEffects(e); + m_exprs.push_back(e); + if (m_next) { + delete m_next; + m_next = nullptr; + } + adoptType(*e); + return true; +} + +void ast_block::collect(ast_expression *expr) +{ + m_collect.push_back(expr); + expr->m_keep_node = true; +} + +ast_function *ast_function::make(lex_ctx_t ctx, const std::string &name, ast_value *vtype) +{ + if (!vtype) { + compile_error(ctx, "internal error: ast_function_new condition 0"); + return nullptr; + } else if (vtype->m_hasvalue || vtype->m_vtype != TYPE_FUNCTION) { + compile_error(ctx, "internal error: ast_function_new condition %i %i type=%i (probably 2 bodies?)", + (int)!vtype, + (int)vtype->m_hasvalue, + vtype->m_vtype); + return nullptr; + } + return new ast_function(ctx, name, vtype); +} + +ast_function::ast_function(lex_ctx_t ctx, const std::string &name, ast_value *vtype) + : ast_node(ctx, TYPE_ast_function) + , m_function_type(vtype) + , m_name(name) + , m_builtin(0) + , m_static_count(0) + , m_ir_func(nullptr) + , m_curblock(nullptr) + , m_labelcount(0) + , m_varargs(nullptr) + , m_argc(nullptr) + , m_fixedparams(nullptr) + , m_return_value(nullptr) +{ + vtype->m_hasvalue = true; + vtype->m_constval.vfunc = this; +} + +ast_function::~ast_function() +{ + if (m_function_type) { + // ast_value_delete(m_function_type); + m_function_type->m_hasvalue = false; + m_function_type->m_constval.vfunc = nullptr; + // We use unref - if it was stored in a global table it is supposed + // to be deleted from *there* + ast_unref(m_function_type); + } + + if (m_fixedparams) + ast_unref(m_fixedparams); + if (m_return_value) + ast_unref(m_return_value); + + // force this to be cleared before m_varargs/m_argc as blocks might + // try to access them via ast_unref() + m_blocks.clear(); +} + +const char* ast_function::makeLabel(const char *prefix) +{ + size_t id; + size_t len; + char *from; + + if (!OPTS_OPTION_BOOL(OPTION_DUMP) && + !OPTS_OPTION_BOOL(OPTION_DUMPFIN) && + !OPTS_OPTION_BOOL(OPTION_DEBUG)) + { + return nullptr; + } + + id = (m_labelcount++); + len = strlen(prefix); + + from = m_labelbuf + sizeof(m_labelbuf)-1; + *from-- = 0; + do { + *from-- = (id%10) + '0'; + id /= 10; + } while (id); + ++from; + memcpy(from - len, prefix, len); + return from - len; +} + +/*********************************************************************/ +/* AST codegen part + * by convention you must never pass nullptr to the 'ir_value **out' + * parameter. If you really don't care about the output, pass a dummy. + * But I can't imagine a pituation where the output is truly unnecessary. + */ + +static void codegen_output_type(ast_expression *self, ir_value *out) +{ + if (out->m_vtype == TYPE_FIELD) + out->m_fieldtype = self->m_next->m_vtype; + if (out->m_vtype == TYPE_FUNCTION) + out->m_outtype = self->m_next->m_vtype; +} + +bool ast_value::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + (void)func; + (void)lvalue; + if (m_vtype == TYPE_NIL) { + *out = func->m_ir_func->m_owner->m_nil; + return true; + } + // NOTE: This is the codegen for a variable used in an expression. + // It is not the codegen to generate the value storage. For this purpose, + // generateLocal and generateGlobal are to be used before this + // is executed. ast_function::generateFunction should take care of its + // locals, and the ast-user should take care of generateGlobal to be used + // on all the globals. + if (!m_ir_v) { + char tname[1024]; /* typename is reserved in C++ */ + ast_type_to_string(this, tname, sizeof(tname)); + compile_error(m_context, "ast_value used before generated %s %s", tname, m_name); + return false; + } + *out = m_ir_v; + return true; +} + +bool ast_value::setGlobalArray() +{ + size_t count = m_initlist.size(); + size_t i; + + if (count > m_count) { + compile_error(m_context, "too many elements in initializer"); + count = m_count; + } + else if (count < m_count) { + /* add this? + compile_warning(m_context, "not all elements are initialized"); + */ + } + + for (i = 0; i != count; ++i) { + switch (m_next->m_vtype) { + case TYPE_FLOAT: + if (!m_ir_values[i]->setFloat(m_initlist[i].vfloat)) + return false; + break; + case TYPE_VECTOR: + if (!m_ir_values[i]->setVector(m_initlist[i].vvec)) + return false; + break; + case TYPE_STRING: + if (!m_ir_values[i]->setString(m_initlist[i].vstring)) + return false; + break; + case TYPE_ARRAY: + /* we don't support them in any other place yet either */ + compile_error(m_context, "TODO: nested arrays"); + return false; + case TYPE_FUNCTION: + /* this requiers a bit more work - similar to the fields I suppose */ + compile_error(m_context, "global of type function not properly generated"); + return false; + case TYPE_FIELD: + if (!m_initlist[i].vfield) { + compile_error(m_context, "field constant without vfield set"); + return false; + } + if (!m_initlist[i].vfield->m_ir_v) { + compile_error(m_context, "field constant generated before its field"); + return false; + } + if (!m_ir_values[i]->setField(m_initlist[i].vfield->m_ir_v)) + return false; + break; + default: + compile_error(m_context, "TODO: global constant type %i", m_vtype); + break; + } + } + return true; +} + +bool ast_value::checkArray(const ast_value &array) const +{ + if (array.m_flags & AST_FLAG_ARRAY_INIT && array.m_initlist.empty()) { + compile_error(m_context, "array without size: %s", m_name); + return false; + } + // we are lame now - considering the way QC works we won't tolerate arrays > 1024 elements + if (!array.m_count || array.m_count > OPTS_OPTION_U32(OPTION_MAX_ARRAY_SIZE)) { + compile_error(m_context, "Invalid array of size %lu", (unsigned long)array.m_count); + return false; + } + return true; +} + +bool ast_value::generateGlobal(ir_builder *ir, bool isfield) +{ + if (m_vtype == TYPE_NIL) { + compile_error(m_context, "internal error: trying to generate a variable of TYPE_NIL"); + return false; + } + + if (m_hasvalue && m_vtype == TYPE_FUNCTION) + return generateGlobalFunction(ir); + + if (isfield && m_vtype == TYPE_FIELD) + return generateGlobalField(ir); + + ir_value *v = nullptr; + if (m_vtype == TYPE_ARRAY) { + v = prepareGlobalArray(ir); + if (!v) + return false; + } else { + // Arrays don't do this since there's no "array" value which spans across the + // whole thing. + v = ir->createGlobal(m_name, m_vtype); + if (!v) { + compile_error(m_context, "ir_builder::createGlobal failed on `%s`", m_name); + return false; + } + codegen_output_type(this, v); + v->m_context = m_context; + } + + /* link us to the ir_value */ + v->m_cvq = m_cvq; + m_ir_v = v; + + if (m_flags & AST_FLAG_INCLUDE_DEF) + m_ir_v->m_flags |= IR_FLAG_INCLUDE_DEF; + if (m_flags & AST_FLAG_ERASEABLE) + m_ir_v->m_flags |= IR_FLAG_ERASABLE; + + /* initialize */ + if (m_hasvalue) { + switch (m_vtype) + { + case TYPE_FLOAT: + if (!v->setFloat(m_constval.vfloat)) + return false; + break; + case TYPE_VECTOR: + if (!v->setVector(m_constval.vvec)) + return false; + break; + case TYPE_STRING: + if (!v->setString(m_constval.vstring)) + return false; + break; + case TYPE_ARRAY: + if (!setGlobalArray()) + return false; + break; + case TYPE_FUNCTION: + compile_error(m_context, "global of type function not properly generated"); + return false; + /* Cannot generate an IR value for a function, + * need a pointer pointing to a function rather. + */ + case TYPE_FIELD: + if (!m_constval.vfield) { + compile_error(m_context, "field constant without vfield set"); + return false; + } + if (!m_constval.vfield->m_ir_v) { + compile_error(m_context, "field constant generated before its field"); + return false; + } + if (!v->setField(m_constval.vfield->m_ir_v)) + return false; + break; + default: + compile_error(m_context, "TODO: global constant type %i", m_vtype); + break; + } + } + + return true; +} + +bool ast_value::generateGlobalFunction(ir_builder *ir) +{ + ir_function *func = ir->createFunction(m_name, m_next->m_vtype); + if (!func) + return false; + func->m_context = m_context; + func->m_value->m_context = m_context; + + m_constval.vfunc->m_ir_func = func; + m_ir_v = func->m_value; + if (m_flags & AST_FLAG_INCLUDE_DEF) + m_ir_v->m_flags |= IR_FLAG_INCLUDE_DEF; + if (m_flags & AST_FLAG_ERASEABLE) + m_ir_v->m_flags |= IR_FLAG_ERASABLE; + if (m_flags & AST_FLAG_BLOCK_COVERAGE) + func->m_flags |= IR_FLAG_BLOCK_COVERAGE; + // The function is filled later on ast_function::generateFunction... + return true; +} + +bool ast_value::generateGlobalField(ir_builder *ir) +{ + ast_expression *fieldtype = m_next; + + if (m_hasvalue) { + compile_error(m_context, "TODO: constant field pointers with value"); + return false; + } + + if (fieldtype->m_vtype == TYPE_ARRAY) { + if (!ast_istype(fieldtype, ast_value)) { + compile_error(m_context, "internal error: ast_value required"); + return false; + } + ast_value *array = reinterpret_cast(fieldtype); + + if (!checkArray(*array)) + return false; + + ast_expression *elemtype = array->m_next; + qc_type vtype = elemtype->m_vtype; + + ir_value *v = ir->createField(m_name, vtype); + if (!v) { + compile_error(m_context, "ir_builder::createGlobal failed on `%s`", m_name); + return false; + } + v->m_context = m_context; + v->m_unique_life = true; + v->m_locked = true; + array->m_ir_v = m_ir_v = v; + + if (m_flags & AST_FLAG_INCLUDE_DEF) + m_ir_v->m_flags |= IR_FLAG_INCLUDE_DEF; + if (m_flags & AST_FLAG_ERASEABLE) + m_ir_v->m_flags |= IR_FLAG_ERASABLE; + + const size_t namelen = m_name.length(); + std::unique_ptr name(new char[namelen+16]); + util_strncpy(name.get(), m_name.c_str(), namelen); + + array->m_ir_values.resize(array->m_count); + array->m_ir_values[0] = v; + for (size_t ai = 1; ai < array->m_count; ++ai) { + util_snprintf(name.get() + namelen, 16, "[%u]", (unsigned int)ai); + array->m_ir_values[ai] = ir->createField(name.get(), vtype); + if (!array->m_ir_values[ai]) { + compile_error(m_context, "ir_builder::createGlobal failed on `%s`", name.get()); + return false; + } + array->m_ir_values[ai]->m_context = m_context; + array->m_ir_values[ai]->m_unique_life = true; + array->m_ir_values[ai]->m_locked = true; + if (m_flags & AST_FLAG_INCLUDE_DEF) + m_ir_values[ai]->m_flags |= IR_FLAG_INCLUDE_DEF; + } + } + else + { + ir_value *v = ir->createField(m_name, m_next->m_vtype); + if (!v) + return false; + v->m_context = m_context; + m_ir_v = v; + if (m_flags & AST_FLAG_INCLUDE_DEF) + m_ir_v->m_flags |= IR_FLAG_INCLUDE_DEF; + + if (m_flags & AST_FLAG_ERASEABLE) + m_ir_v->m_flags |= IR_FLAG_ERASABLE; + } + return true; +} + +ir_value *ast_value::prepareGlobalArray(ir_builder *ir) +{ + ast_expression *elemtype = m_next; + qc_type vtype = elemtype->m_vtype; + + if (m_flags & AST_FLAG_ARRAY_INIT && !m_count) { + compile_error(m_context, "array `%s' has no size", m_name); + return nullptr; + } + + /* same as with field arrays */ + if (!checkArray(*this)) + return nullptr; + + ir_value *v = ir->createGlobal(m_name, vtype); + if (!v) { + compile_error(m_context, "ir_builder::createGlobal failed `%s`", m_name); + return nullptr; + } + v->m_context = m_context; + v->m_unique_life = true; + v->m_locked = true; + + if (m_flags & AST_FLAG_INCLUDE_DEF) + v->m_flags |= IR_FLAG_INCLUDE_DEF; + if (m_flags & AST_FLAG_ERASEABLE) + m_ir_v->m_flags |= IR_FLAG_ERASABLE; + + const size_t namelen = m_name.length(); + std::unique_ptr name(new char[namelen+16]); + util_strncpy(name.get(), m_name.c_str(), namelen); + + m_ir_values.resize(m_count); + m_ir_values[0] = v; + for (size_t ai = 1; ai < m_count; ++ai) { + util_snprintf(name.get() + namelen, 16, "[%u]", (unsigned int)ai); + m_ir_values[ai] = ir->createGlobal(name.get(), vtype); + if (!m_ir_values[ai]) { + compile_error(m_context, "ir_builder::createGlobal failed `%s`", name.get()); + return nullptr; + } + m_ir_values[ai]->m_context = m_context; + m_ir_values[ai]->m_unique_life = true; + m_ir_values[ai]->m_locked = true; + if (m_flags & AST_FLAG_INCLUDE_DEF) + m_ir_values[ai]->m_flags |= IR_FLAG_INCLUDE_DEF; + } + + return v; +} + +bool ast_value::generateLocal(ir_function *func, bool param) +{ + if (m_vtype == TYPE_NIL) { + compile_error(m_context, "internal error: trying to generate a variable of TYPE_NIL"); + return false; + } + + if (m_hasvalue && m_vtype == TYPE_FUNCTION) + { + /* Do we allow local functions? I think not... + * this is NOT a function pointer atm. + */ + return false; + } + + ir_value *v = nullptr; + if (m_vtype == TYPE_ARRAY) { + ast_expression *elemtype = m_next; + qc_type vtype = elemtype->m_vtype; + + func->m_flags |= IR_FLAG_HAS_ARRAYS; + + if (param && !(m_flags & AST_FLAG_IS_VARARG)) { + compile_error(m_context, "array-parameters are not supported"); + return false; + } + + /* we are lame now - considering the way QC works we won't tolerate arrays > 1024 elements */ + if (!checkArray(*this)) + return false; + + m_ir_values.resize(m_count); + v = ir_function_create_local(func, m_name, vtype, param); + if (!v) { + compile_error(m_context, "internal error: ir_function_create_local failed"); + return false; + } + v->m_context = m_context; + v->m_unique_life = true; + v->m_locked = true; + + const size_t namelen = m_name.length(); + std::unique_ptr name(new char[namelen+16]); + util_strncpy(name.get(), m_name.c_str(), namelen); + + m_ir_values[0] = v; + for (size_t ai = 1; ai < m_count; ++ai) { + util_snprintf(name.get() + namelen, 16, "[%u]", (unsigned int)ai); + m_ir_values[ai] = ir_function_create_local(func, name.get(), vtype, param); + if (!m_ir_values[ai]) { + compile_error(m_context, "internal_error: ir_builder::createGlobal failed on `%s`", name.get()); + return false; + } + m_ir_values[ai]->m_context = m_context; + m_ir_values[ai]->m_unique_life = true; + m_ir_values[ai]->m_locked = true; + } + } + else + { + v = ir_function_create_local(func, m_name, m_vtype, param); + if (!v) + return false; + codegen_output_type(this, v); + v->m_context = m_context; + } + + // A constant local... hmmm... + // I suppose the IR will have to deal with this + if (m_hasvalue) { + switch (m_vtype) + { + case TYPE_FLOAT: + if (!v->setFloat(m_constval.vfloat)) + goto error; + break; + case TYPE_VECTOR: + if (!v->setVector(m_constval.vvec)) + goto error; + break; + case TYPE_STRING: + if (!v->setString(m_constval.vstring)) + goto error; + break; + default: + compile_error(m_context, "TODO: global constant type %i", m_vtype); + break; + } + } + + // link us to the ir_value + v->m_cvq = m_cvq; + m_ir_v = v; + + if (!generateAccessors(func->m_owner)) + return false; + return true; + +error: /* clean up */ + delete v; + return false; +} + +bool ast_value::generateAccessors(ir_builder *ir) +{ + size_t i; + bool warn = OPTS_WARN(WARN_USED_UNINITIALIZED); + if (!m_setter || !m_getter) + return true; + if (m_count && m_ir_values.empty()) { + compile_error(m_context, "internal error: no array values generated for `%s`", m_name); + return false; + } + for (i = 0; i < m_count; ++i) { + if (!m_ir_values[i]) { + compile_error(m_context, "internal error: not all array values have been generated for `%s`", m_name); + return false; + } + if (!m_ir_values[i]->m_life.empty()) { + compile_error(m_context, "internal error: function containing `%s` already generated", m_name); + return false; + } + } + + opts_set(opts.warn, WARN_USED_UNINITIALIZED, false); + if (m_setter) { + if (!m_setter->generateGlobal(ir, false) || + !m_setter->m_constval.vfunc->generateFunction(ir) || + !ir_function_finalize(m_setter->m_constval.vfunc->m_ir_func)) + { + compile_error(m_context, "internal error: failed to generate setter for `%s`", m_name); + opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn); + return false; + } + } + if (m_getter) { + if (!m_getter->generateGlobal(ir, false) || + !m_getter->m_constval.vfunc->generateFunction(ir) || + !ir_function_finalize(m_getter->m_constval.vfunc->m_ir_func)) + { + compile_error(m_context, "internal error: failed to generate getter for `%s`", m_name); + opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn); + return false; + } + } + for (i = 0; i < m_count; ++i) + m_ir_values[i]->m_life.clear(); + opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn); + return true; +} + +bool ast_function::generateFunction(ir_builder *ir) +{ + (void)ir; + + ir_value *dummy; + + ir_function *irf = m_ir_func; + if (!irf) { + compile_error(m_context, "internal error: ast_function's related ast_value was not generated yet"); + return false; + } + + /* fill the parameter list */ + for (auto &it : m_function_type->m_type_params) { + if (it->m_vtype == TYPE_FIELD) + vec_push(irf->m_params, it->m_next->m_vtype); + else + vec_push(irf->m_params, it->m_vtype); + if (!m_builtin) { + if (!it->generateLocal(m_ir_func, true)) + return false; + } + } + + if (m_varargs) { + if (!m_varargs->generateLocal(m_ir_func, true)) + return false; + irf->m_max_varargs = m_varargs->m_count; + } + + if (m_builtin) { + irf->m_builtin = m_builtin; + return true; + } + + /* have a local return value variable? */ + if (m_return_value) { + if (!m_return_value->generateLocal(m_ir_func, false)) + return false; + } + + if (m_blocks.empty()) { + compile_error(m_context, "function `%s` has no body", m_name); + return false; + } + + irf->m_first = m_curblock = ir_function_create_block(m_context, irf, "entry"); + if (!m_curblock) { + compile_error(m_context, "failed to allocate entry block for `%s`", m_name); + return false; + } + + if (m_argc) { + ir_value *va_count; + ir_value *fixed; + ir_value *sub; + if (!m_argc->generateLocal(m_ir_func, true)) + return false; + if (!m_argc->codegen(this, false, &va_count)) + return false; + if (!m_fixedparams->codegen(this, false, &fixed)) + return false; + sub = ir_block_create_binop(m_curblock, m_context, + makeLabel("va_count"), INSTR_SUB_F, + ir->get_va_count(), fixed); + if (!sub) + return false; + if (!ir_block_create_store_op(m_curblock, m_context, INSTR_STORE_F, + va_count, sub)) + { + return false; + } + } + + for (auto &it : m_blocks) { + if (!it->codegen(this, false, &dummy)) + return false; + } + + /* TODO: check return types */ + if (!m_curblock->m_final) + { + if (!m_function_type->m_next || + m_function_type->m_next->m_vtype == TYPE_VOID) + { + return ir_block_create_return(m_curblock, m_context, nullptr); + } + else if (vec_size(m_curblock->m_entries) || m_curblock == irf->m_first) + { + if (m_return_value) { + if (!m_return_value->codegen(this, false, &dummy)) + return false; + return ir_block_create_return(m_curblock, m_context, dummy); + } + else if (compile_warning(m_context, WARN_MISSING_RETURN_VALUES, + "control reaches end of non-void function (`%s`) via %s", + m_name.c_str(), m_curblock->m_label.c_str())) + { + return false; + } + return ir_block_create_return(m_curblock, m_context, nullptr); + } + } + return true; +} + +static bool starts_a_label(const ast_expression *ex) +{ + while (ex && ast_istype(ex, ast_block)) { + auto b = reinterpret_cast(ex); + ex = b->m_exprs[0]; + } + if (!ex) + return false; + return ast_istype(ex, ast_label); +} + +/* Note, you will not see ast_block_codegen generate ir_blocks. + * To the AST and the IR, blocks are 2 different things. + * In the AST it represents a block of code, usually enclosed in + * curly braces {...}. + * While in the IR it represents a block in terms of control-flow. + */ +bool ast_block::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + /* We don't use this + * Note: an ast-representation using the comma-operator + * of the form: (a, b, c) = x should not assign to c... + */ + if (lvalue) { + compile_error(m_context, "not an l-value (code-block)"); + return false; + } + + if (m_outr) { + *out = m_outr; + return true; + } + + /* output is nullptr at first, we'll have each expression + * assign to out output, thus, a comma-operator represention + * using an ast_block will return the last generated value, + * so: (b, c) + a executed both b and c, and returns c, + * which is then added to a. + */ + *out = nullptr; + + /* generate locals */ + for (auto &it : m_locals) { + if (!it->generateLocal(func->m_ir_func, false)) { + if (OPTS_OPTION_BOOL(OPTION_DEBUG)) + compile_error(m_context, "failed to generate local `%s`", it->m_name); + return false; + } + } + + for (auto &it : m_exprs) { + if (func->m_curblock->m_final && !starts_a_label(it)) { + if (compile_warning(it->m_context, WARN_UNREACHABLE_CODE, "unreachable statement")) + return false; + continue; + } + if (!it->codegen(func, false, out)) + return false; + } + + m_outr = *out; + + return true; +} + +bool ast_store::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + ir_value *left = nullptr; + ir_value *right = nullptr; + + ast_value *idx = 0; + ast_array_index *ai = nullptr; + + if (lvalue && m_outl) { + *out = m_outl; + return true; + } + + if (!lvalue && m_outr) { + *out = m_outr; + return true; + } + + if (ast_istype(m_dest, ast_array_index)) + { + + ai = (ast_array_index*)m_dest; + idx = (ast_value*)ai->m_index; + + if (ast_istype(ai->m_index, ast_value) && idx->m_hasvalue && idx->m_cvq == CV_CONST) + ai = nullptr; + } + + if (ai) { + /* we need to call the setter */ + ir_value *iridx, *funval; + ir_instr *call; + + if (lvalue) { + compile_error(m_context, "array-subscript assignment cannot produce lvalues"); + return false; + } + + auto arr = reinterpret_cast(ai->m_array); + if (!ast_istype(ai->m_array, ast_value) || !arr->m_setter) { + compile_error(m_context, "value has no setter (%s)", arr->m_name); + return false; + } + + if (!idx->codegen(func, false, &iridx)) + return false; + + if (!arr->m_setter->codegen(func, true, &funval)) + return false; + + if (!m_source->codegen(func, false, &right)) + return false; + + call = ir_block_create_call(func->m_curblock, m_context, func->makeLabel("store"), funval, false); + if (!call) + return false; + ir_call_param(call, iridx); + ir_call_param(call, right); + m_outr = right; + } + else + { + // regular code + + // lvalue! + if (!m_dest->codegen(func, true, &left)) + return false; + m_outl = left; + + /* rvalue! */ + if (!m_source->codegen(func, false, &right)) + return false; + + if (!ir_block_create_store_op(func->m_curblock, m_context, m_op, left, right)) + return false; + m_outr = right; + } + + /* Theoretically, an assinment returns its left side as an + * lvalue, if we don't need an lvalue though, we return + * the right side as an rvalue, otherwise we have to + * somehow know whether or not we need to dereference the pointer + * on the left side - that is: OP_LOAD if it was an address. + * Also: in original QC we cannot OP_LOADP *anyway*. + */ + *out = (lvalue ? left : right); + + return true; +} + +bool ast_binary::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + ir_value *left, *right; + + /* A binary operation cannot yield an l-value */ + if (lvalue) { + compile_error(m_context, "not an l-value (binop)"); + return false; + } + + if (m_outr) { + *out = m_outr; + return true; + } + + if ((OPTS_FLAG(SHORT_LOGIC) || OPTS_FLAG(PERL_LOGIC)) && + (m_op == INSTR_AND || m_op == INSTR_OR)) + { + /* NOTE: The short-logic path will ignore right_first */ + + /* short circuit evaluation */ + ir_block *other, *merge; + ir_block *from_left, *from_right; + ir_instr *phi; + size_t merge_id; + + /* prepare end-block */ + merge_id = func->m_ir_func->m_blocks.size(); + merge = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("sce_merge")); + + /* generate the left expression */ + if (!m_left->codegen(func, false, &left)) + return false; + /* remember the block */ + from_left = func->m_curblock; + + /* create a new block for the right expression */ + other = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("sce_other")); + if (m_op == INSTR_AND) { + /* on AND: left==true -> other */ + if (!ir_block_create_if(func->m_curblock, m_context, left, other, merge)) + return false; + } else { + /* on OR: left==false -> other */ + if (!ir_block_create_if(func->m_curblock, m_context, left, merge, other)) + return false; + } + /* use the likely flag */ + vec_last(func->m_curblock->m_instr)->m_likely = true; + + /* enter the right-expression's block */ + func->m_curblock = other; + /* generate */ + if (!m_right->codegen(func, false, &right)) + return false; + /* remember block */ + from_right = func->m_curblock; + + /* jump to the merge block */ + if (!ir_block_create_jump(func->m_curblock, m_context, merge)) + return false; + + algo::shiftback(func->m_ir_func->m_blocks.begin() + merge_id, + func->m_ir_func->m_blocks.end()); + // FIXME::DELME:: + //func->m_ir_func->m_blocks[merge_id].release(); + //func->m_ir_func->m_blocks.erase(func->m_ir_func->m_blocks.begin() + merge_id); + //func->m_ir_func->m_blocks.emplace_back(merge); + + func->m_curblock = merge; + phi = ir_block_create_phi(func->m_curblock, m_context, + func->makeLabel("sce_value"), + m_vtype); + ir_phi_add(phi, from_left, left); + ir_phi_add(phi, from_right, right); + *out = ir_phi_value(phi); + if (!*out) + return false; + + if (!OPTS_FLAG(PERL_LOGIC)) { + /* cast-to-bool */ + if (OPTS_FLAG(CORRECT_LOGIC) && (*out)->m_vtype == TYPE_VECTOR) { + *out = ir_block_create_unary(func->m_curblock, m_context, + func->makeLabel("sce_bool_v"), + INSTR_NOT_V, *out); + if (!*out) + return false; + *out = ir_block_create_unary(func->m_curblock, m_context, + func->makeLabel("sce_bool"), + INSTR_NOT_F, *out); + if (!*out) + return false; + } + else if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && (*out)->m_vtype == TYPE_STRING) { + *out = ir_block_create_unary(func->m_curblock, m_context, + func->makeLabel("sce_bool_s"), + INSTR_NOT_S, *out); + if (!*out) + return false; + *out = ir_block_create_unary(func->m_curblock, m_context, + func->makeLabel("sce_bool"), + INSTR_NOT_F, *out); + if (!*out) + return false; + } + else { + *out = ir_block_create_binop(func->m_curblock, m_context, + func->makeLabel("sce_bool"), + INSTR_AND, *out, *out); + if (!*out) + return false; + } + } + + m_outr = *out; + codegen_output_type(this, *out); + return true; + } + + if (m_right_first) { + if (!m_right->codegen(func, false, &right)) + return false; + if (!m_left->codegen(func, false, &left)) + return false; + } else { + if (!m_left->codegen(func, false, &left)) + return false; + if (!m_right->codegen(func, false, &right)) + return false; + } + + *out = ir_block_create_binop(func->m_curblock, m_context, func->makeLabel("bin"), + m_op, left, right); + if (!*out) + return false; + m_outr = *out; + codegen_output_type(this, *out); + + return true; +} + +bool ast_binstore::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + ir_value *leftl = nullptr, *leftr, *right, *bin; + + ast_value *arr; + ast_value *idx = 0; + ast_array_index *ai = nullptr; + ir_value *iridx = nullptr; + + if (lvalue && m_outl) { + *out = m_outl; + return true; + } + + if (!lvalue && m_outr) { + *out = m_outr; + return true; + } + + if (ast_istype(m_dest, ast_array_index)) + { + + ai = (ast_array_index*)m_dest; + idx = (ast_value*)ai->m_index; + + if (ast_istype(ai->m_index, ast_value) && idx->m_hasvalue && idx->m_cvq == CV_CONST) + ai = nullptr; + } + + /* for a binstore we need both an lvalue and an rvalue for the left side */ + /* rvalue of destination! */ + if (ai) { + if (!idx->codegen(func, false, &iridx)) + return false; + } + if (!m_dest->codegen(func, false, &leftr)) + return false; + + /* source as rvalue only */ + if (!m_source->codegen(func, false, &right)) + return false; + + /* now the binary */ + bin = ir_block_create_binop(func->m_curblock, m_context, func->makeLabel("binst"), + m_opbin, leftr, right); + m_outr = bin; + + if (ai) { + /* we need to call the setter */ + ir_value *funval; + ir_instr *call; + + if (lvalue) { + compile_error(m_context, "array-subscript assignment cannot produce lvalues"); + return false; + } + + arr = (ast_value*)ai->m_array; + if (!ast_istype(ai->m_array, ast_value) || !arr->m_setter) { + compile_error(m_context, "value has no setter (%s)", arr->m_name); + return false; + } + + if (!arr->m_setter->codegen(func, true, &funval)) + return false; + + call = ir_block_create_call(func->m_curblock, m_context, func->makeLabel("store"), funval, false); + if (!call) + return false; + ir_call_param(call, iridx); + ir_call_param(call, bin); + m_outr = bin; + } else { + // now store them + // lvalue of destination + if (!m_dest->codegen(func, true, &leftl)) + return false; + m_outl = leftl; + + if (!ir_block_create_store_op(func->m_curblock, m_context, m_opstore, leftl, bin)) + return false; + m_outr = bin; + } + + /* Theoretically, an assinment returns its left side as an + * lvalue, if we don't need an lvalue though, we return + * the right side as an rvalue, otherwise we have to + * somehow know whether or not we need to dereference the pointer + * on the left side - that is: OP_LOAD if it was an address. + * Also: in original QC we cannot OP_LOADP *anyway*. + */ + *out = (lvalue ? leftl : bin); + + return true; +} + +bool ast_unary::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + ir_value *operand; + + /* An unary operation cannot yield an l-value */ + if (lvalue) { + compile_error(m_context, "not an l-value (binop)"); + return false; + } + + if (m_outr) { + *out = m_outr; + return true; + } + + /* lvalue! */ + if (!m_operand->codegen(func, false, &operand)) + return false; + + *out = ir_block_create_unary(func->m_curblock, m_context, func->makeLabel("unary"), + m_op, operand); + if (!*out) + return false; + m_outr = *out; + + return true; +} + +bool ast_return::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + ir_value *operand; + + *out = nullptr; + + /* In the context of a return operation, we don't actually return + * anything... + */ + if (lvalue) { + compile_error(m_context, "return-expression is not an l-value"); + return false; + } + + if (m_outr) { + compile_error(m_context, "internal error: ast_return cannot be reused, it bears no result!"); + return false; + } + m_outr = (ir_value*)1; + + if (m_operand) { + /* lvalue! */ + if (!m_operand->codegen(func, false, &operand)) + return false; + + if (!ir_block_create_return(func->m_curblock, m_context, operand)) + return false; + } else { + if (!ir_block_create_return(func->m_curblock, m_context, nullptr)) + return false; + } + + return true; +} + +bool ast_entfield::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + ir_value *ent, *field; + + // This function needs to take the 'lvalue' flag into account! + // As lvalue we provide a field-pointer, as rvalue we provide the + // value in a temp. + + if (lvalue && m_outl) { + *out = m_outl; + return true; + } + + if (!lvalue && m_outr) { + *out = m_outr; + return true; + } + + if (!m_entity->codegen(func, false, &ent)) + return false; + + if (!m_field->codegen(func, false, &field)) + return false; + + if (lvalue) { + /* address! */ + *out = ir_block_create_fieldaddress(func->m_curblock, m_context, func->makeLabel("efa"), + ent, field); + } else { + *out = ir_block_create_load_from_ent(func->m_curblock, m_context, func->makeLabel("efv"), + ent, field, m_vtype); + /* Done AFTER error checking: + codegen_output_type(this, *out); + */ + } + if (!*out) { + compile_error(m_context, "failed to create %s instruction (output type %s)", + (lvalue ? "ADDRESS" : "FIELD"), + type_name[m_vtype]); + return false; + } + if (!lvalue) + codegen_output_type(this, *out); + + if (lvalue) + m_outl = *out; + else + m_outr = *out; + + // Hm that should be it... + return true; +} + +bool ast_member::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + ir_value *vec; + + /* in QC this is always an lvalue */ + if (lvalue && m_rvalue) { + compile_error(m_context, "not an l-value (member access)"); + return false; + } + if (m_outl) { + *out = m_outl; + return true; + } + + if (!m_owner->codegen(func, false, &vec)) + return false; + + if (vec->m_vtype != TYPE_VECTOR && + !(vec->m_vtype == TYPE_FIELD && m_owner->m_next->m_vtype == TYPE_VECTOR)) + { + return false; + } + + *out = vec->vectorMember(m_field); + m_outl = *out; + + return (*out != nullptr); +} + +bool ast_array_index::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + ast_value *arr; + ast_value *idx; + + if (!lvalue && m_outr) { + *out = m_outr; + return true; + } + if (lvalue && m_outl) { + *out = m_outl; + return true; + } + + if (!ast_istype(m_array, ast_value)) { + compile_error(m_context, "array indexing this way is not supported"); + /* note this would actually be pointer indexing because the left side is + * not an actual array but (hopefully) an indexable expression. + * Once we get integer arithmetic, and GADDRESS/GSTORE/GLOAD instruction + * support this path will be filled. + */ + return false; + } + + arr = reinterpret_cast(m_array); + idx = reinterpret_cast(m_index); + + if (!ast_istype(m_index, ast_value) || !idx->m_hasvalue || idx->m_cvq != CV_CONST) { + /* Time to use accessor functions */ + ir_value *iridx, *funval; + ir_instr *call; + + if (lvalue) { + compile_error(m_context, "(.2) array indexing here needs a compile-time constant"); + return false; + } + + if (!arr->m_getter) { + compile_error(m_context, "value has no getter, don't know how to index it"); + return false; + } + + if (!m_index->codegen(func, false, &iridx)) + return false; + + if (!arr->m_getter->codegen(func, true, &funval)) + return false; + + call = ir_block_create_call(func->m_curblock, m_context, func->makeLabel("fetch"), funval, false); + if (!call) + return false; + ir_call_param(call, iridx); + + *out = ir_call_value(call); + m_outr = *out; + (*out)->m_vtype = m_vtype; + codegen_output_type(this, *out); + return true; + } + + if (idx->m_vtype == TYPE_FLOAT) { + unsigned int arridx = idx->m_constval.vfloat; + if (arridx >= m_array->m_count) + { + compile_error(m_context, "array index out of bounds: %i", arridx); + return false; + } + *out = arr->m_ir_values[arridx]; + } + else if (idx->m_vtype == TYPE_INTEGER) { + unsigned int arridx = idx->m_constval.vint; + if (arridx >= m_array->m_count) + { + compile_error(m_context, "array index out of bounds: %i", arridx); + return false; + } + *out = arr->m_ir_values[arridx]; + } + else { + compile_error(m_context, "array indexing here needs an integer constant"); + return false; + } + (*out)->m_vtype = m_vtype; + codegen_output_type(this, *out); + return true; +} + +bool ast_argpipe::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + *out = nullptr; + if (lvalue) { + compile_error(m_context, "argpipe node: not an lvalue"); + return false; + } + (void)func; + (void)out; + compile_error(m_context, "TODO: argpipe codegen not implemented"); + return false; +} + +bool ast_ifthen::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + ir_value *condval; + ir_value *dummy; + + ir_block *cond; + ir_block *ontrue; + ir_block *onfalse; + ir_block *ontrue_endblock = nullptr; + ir_block *onfalse_endblock = nullptr; + ir_block *merge = nullptr; + int folded = 0; + + /* We don't output any value, thus also don't care about r/lvalue */ + (void)out; + (void)lvalue; + + if (m_outr) { + compile_error(m_context, "internal error: ast_ifthen cannot be reused, it bears no result!"); + return false; + } + m_outr = (ir_value*)1; + + /* generate the condition */ + if (!m_cond->codegen(func, false, &condval)) + return false; + /* update the block which will get the jump - because short-logic or ternaries may have changed this */ + cond = func->m_curblock; + + /* try constant folding away the condition */ + if ((folded = fold::cond_ifthen(condval, func, this)) != -1) + return folded; + + if (m_on_true) { + /* create on-true block */ + ontrue = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("ontrue")); + if (!ontrue) + return false; + + /* enter the block */ + func->m_curblock = ontrue; + + /* generate */ + if (!m_on_true->codegen(func, false, &dummy)) + return false; + + /* we now need to work from the current endpoint */ + ontrue_endblock = func->m_curblock; + } else + ontrue = nullptr; + + /* on-false path */ + if (m_on_false) { + /* create on-false block */ + onfalse = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("onfalse")); + if (!onfalse) + return false; + + /* enter the block */ + func->m_curblock = onfalse; + + /* generate */ + if (!m_on_false->codegen(func, false, &dummy)) + return false; + + /* we now need to work from the current endpoint */ + onfalse_endblock = func->m_curblock; + } else + onfalse = nullptr; + + /* Merge block were they all merge in to */ + if (!ontrue || !onfalse || !ontrue_endblock->m_final || !onfalse_endblock->m_final) + { + merge = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("endif")); + if (!merge) + return false; + /* add jumps ot the merge block */ + if (ontrue && !ontrue_endblock->m_final && !ir_block_create_jump(ontrue_endblock, m_context, merge)) + return false; + if (onfalse && !onfalse_endblock->m_final && !ir_block_create_jump(onfalse_endblock, m_context, merge)) + return false; + + /* Now enter the merge block */ + func->m_curblock = merge; + } + + /* we create the if here, that way all blocks are ordered :) + */ + if (!ir_block_create_if(cond, m_context, condval, + (ontrue ? ontrue : merge), + (onfalse ? onfalse : merge))) + { + return false; + } + + return true; +} + +bool ast_ternary::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + ir_value *condval; + ir_value *trueval, *falseval; + ir_instr *phi; + + ir_block *cond = func->m_curblock; + ir_block *cond_out = nullptr; + ir_block *ontrue, *ontrue_out = nullptr; + ir_block *onfalse, *onfalse_out = nullptr; + ir_block *merge; + int folded = 0; + + /* Ternary can never create an lvalue... */ + if (lvalue) + return false; + + /* In theory it shouldn't be possible to pass through a node twice, but + * in case we add any kind of optimization pass for the AST itself, it + * may still happen, thus we remember a created ir_value and simply return one + * if it already exists. + */ + if (m_outr) { + *out = m_outr; + return true; + } + + /* In the following, contraty to ast_ifthen, we assume both paths exist. */ + + /* generate the condition */ + func->m_curblock = cond; + if (!m_cond->codegen(func, false, &condval)) + return false; + cond_out = func->m_curblock; + + /* try constant folding away the condition */ + if ((folded = fold::cond_ternary(condval, func, this)) != -1) + return folded; + + /* create on-true block */ + ontrue = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("tern_T")); + if (!ontrue) + return false; + else + { + /* enter the block */ + func->m_curblock = ontrue; + + /* generate */ + if (!m_on_true->codegen(func, false, &trueval)) + return false; + + ontrue_out = func->m_curblock; + } + + /* create on-false block */ + onfalse = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("tern_F")); + if (!onfalse) + return false; + else + { + /* enter the block */ + func->m_curblock = onfalse; + + /* generate */ + if (!m_on_false->codegen(func, false, &falseval)) + return false; + + onfalse_out = func->m_curblock; + } + + /* create merge block */ + merge = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("tern_out")); + if (!merge) + return false; + /* jump to merge block */ + if (!ir_block_create_jump(ontrue_out, m_context, merge)) + return false; + if (!ir_block_create_jump(onfalse_out, m_context, merge)) + return false; + + /* create if instruction */ + if (!ir_block_create_if(cond_out, m_context, condval, ontrue, onfalse)) + return false; + + /* Now enter the merge block */ + func->m_curblock = merge; + + /* Here, now, we need a PHI node + * but first some sanity checking... + */ + if (trueval->m_vtype != falseval->m_vtype && trueval->m_vtype != TYPE_NIL && falseval->m_vtype != TYPE_NIL) { + /* error("ternary with different types on the two sides"); */ + compile_error(m_context, "internal error: ternary operand types invalid"); + return false; + } + + /* create PHI */ + phi = ir_block_create_phi(merge, m_context, func->makeLabel("phi"), m_vtype); + if (!phi) { + compile_error(m_context, "internal error: failed to generate phi node"); + return false; + } + ir_phi_add(phi, ontrue_out, trueval); + ir_phi_add(phi, onfalse_out, falseval); + + m_outr = ir_phi_value(phi); + *out = m_outr; + + codegen_output_type(this, *out); + + return true; +} + +bool ast_loop::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + ir_value *dummy = nullptr; + ir_value *precond = nullptr; + ir_value *postcond = nullptr; + + /* Since we insert some jumps "late" so we have blocks + * ordered "nicely", we need to keep track of the actual end-blocks + * of expressions to add the jumps to. + */ + ir_block *bbody = nullptr, *end_bbody = nullptr; + ir_block *bprecond = nullptr, *end_bprecond = nullptr; + ir_block *bpostcond = nullptr, *end_bpostcond = nullptr; + ir_block *bincrement = nullptr, *end_bincrement = nullptr; + ir_block *bout = nullptr, *bin = nullptr; + + /* let's at least move the outgoing block to the end */ + size_t bout_id; + + /* 'break' and 'continue' need to be able to find the right blocks */ + ir_block *bcontinue = nullptr; + ir_block *bbreak = nullptr; + + ir_block *tmpblock = nullptr; + + (void)lvalue; + (void)out; + + if (m_outr) { + compile_error(m_context, "internal error: ast_loop cannot be reused, it bears no result!"); + return false; + } + m_outr = (ir_value*)1; + + /* NOTE: + * Should we ever need some kind of block ordering, better make this function + * move blocks around than write a block ordering algorithm later... after all + * the ast and ir should work together, not against each other. + */ + + /* initexpr doesn't get its own block, it's pointless, it could create more blocks + * anyway if for example it contains a ternary. + */ + if (m_initexpr) + { + if (!m_initexpr->codegen(func, false, &dummy)) + return false; + } + + /* Store the block from which we enter this chaos */ + bin = func->m_curblock; + + /* The pre-loop condition needs its own block since we + * need to be able to jump to the start of that expression. + */ + if (m_precond) + { + bprecond = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("pre_loop_cond")); + if (!bprecond) + return false; + + /* the pre-loop-condition the least important place to 'continue' at */ + bcontinue = bprecond; + + /* enter */ + func->m_curblock = bprecond; + + /* generate */ + if (!m_precond->codegen(func, false, &precond)) + return false; + + end_bprecond = func->m_curblock; + } else { + bprecond = end_bprecond = nullptr; + } + + /* Now the next blocks won't be ordered nicely, but we need to + * generate them this early for 'break' and 'continue'. + */ + if (m_increment) { + bincrement = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("loop_increment")); + if (!bincrement) + return false; + bcontinue = bincrement; /* increment comes before the pre-loop-condition */ + } else { + bincrement = end_bincrement = nullptr; + } + + if (m_postcond) { + bpostcond = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("post_loop_cond")); + if (!bpostcond) + return false; + bcontinue = bpostcond; /* postcond comes before the increment */ + } else { + bpostcond = end_bpostcond = nullptr; + } + + bout_id = func->m_ir_func->m_blocks.size(); + bout = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("after_loop")); + if (!bout) + return false; + bbreak = bout; + + /* The loop body... */ + /* if (m_body) */ + { + bbody = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("loop_body")); + if (!bbody) + return false; + + /* enter */ + func->m_curblock = bbody; + + func->m_breakblocks.push_back(bbreak); + if (bcontinue) + func->m_continueblocks.push_back(bcontinue); + else + func->m_continueblocks.push_back(bbody); + + /* generate */ + if (m_body) { + if (!m_body->codegen(func, false, &dummy)) + return false; + } + + end_bbody = func->m_curblock; + func->m_breakblocks.pop_back(); + func->m_continueblocks.pop_back(); + } + + /* post-loop-condition */ + if (m_postcond) + { + /* enter */ + func->m_curblock = bpostcond; + + /* generate */ + if (!m_postcond->codegen(func, false, &postcond)) + return false; + + end_bpostcond = func->m_curblock; + } + + /* The incrementor */ + if (m_increment) + { + /* enter */ + func->m_curblock = bincrement; + + /* generate */ + if (!m_increment->codegen(func, false, &dummy)) + return false; + + end_bincrement = func->m_curblock; + } + + /* In any case now, we continue from the outgoing block */ + func->m_curblock = bout; + + /* Now all blocks are in place */ + /* From 'bin' we jump to whatever comes first */ + if (bprecond) tmpblock = bprecond; + else tmpblock = bbody; /* can never be null */ + + /* DEAD CODE + else if (bpostcond) tmpblock = bpostcond; + else tmpblock = bout; + */ + + if (!ir_block_create_jump(bin, m_context, tmpblock)) + return false; + + /* From precond */ + if (bprecond) + { + ir_block *ontrue, *onfalse; + ontrue = bbody; /* can never be null */ + + /* all of this is dead code + else if (bincrement) ontrue = bincrement; + else ontrue = bpostcond; + */ + + onfalse = bout; + if (m_pre_not) { + tmpblock = ontrue; + ontrue = onfalse; + onfalse = tmpblock; + } + if (!ir_block_create_if(end_bprecond, m_context, precond, ontrue, onfalse)) + return false; + } + + /* from body */ + if (bbody) + { + if (bincrement) tmpblock = bincrement; + else if (bpostcond) tmpblock = bpostcond; + else if (bprecond) tmpblock = bprecond; + else tmpblock = bbody; + if (!end_bbody->m_final && !ir_block_create_jump(end_bbody, m_context, tmpblock)) + return false; + } + + /* from increment */ + if (bincrement) + { + if (bpostcond) tmpblock = bpostcond; + else if (bprecond) tmpblock = bprecond; + else if (bbody) tmpblock = bbody; + else tmpblock = bout; + if (!ir_block_create_jump(end_bincrement, m_context, tmpblock)) + return false; + } + + /* from postcond */ + if (bpostcond) + { + ir_block *ontrue, *onfalse; + if (bprecond) ontrue = bprecond; + else ontrue = bbody; /* can never be null */ + + /* all of this is dead code + else if (bincrement) ontrue = bincrement; + else ontrue = bpostcond; + */ + + onfalse = bout; + if (m_post_not) { + tmpblock = ontrue; + ontrue = onfalse; + onfalse = tmpblock; + } + if (!ir_block_create_if(end_bpostcond, m_context, postcond, ontrue, onfalse)) + return false; + } + + /* Move 'bout' to the end */ + algo::shiftback(func->m_ir_func->m_blocks.begin() + bout_id, + func->m_ir_func->m_blocks.end()); + // FIXME::DELME:: + //func->m_ir_func->m_blocks[bout_id].release(); // it's a vector> + //func->m_ir_func->m_blocks.erase(func->m_ir_func->m_blocks.begin() + bout_id); + //func->m_ir_func->m_blocks.emplace_back(bout); + + return true; +} + +bool ast_breakcont::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + ir_block *target; + + *out = nullptr; + + if (lvalue) { + compile_error(m_context, "break/continue expression is not an l-value"); + return false; + } + + if (m_outr) { + compile_error(m_context, "internal error: ast_breakcont cannot be reused!"); + return false; + } + m_outr = (ir_value*)1; + + if (m_is_continue) + target = func->m_continueblocks[func->m_continueblocks.size()-1-m_levels]; + else + target = func->m_breakblocks[func->m_breakblocks.size()-1-m_levels]; + + if (!target) { + compile_error(m_context, "%s is lacking a target block", (m_is_continue ? "continue" : "break")); + return false; + } + + if (!ir_block_create_jump(func->m_curblock, m_context, target)) + return false; + return true; +} + +bool ast_switch::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + ast_switch_case *def_case = nullptr; + ir_block *def_bfall = nullptr; + ir_block *def_bfall_to = nullptr; + bool set_def_bfall_to = false; + + ir_value *dummy = nullptr; + ir_value *irop = nullptr; + ir_block *bout = nullptr; + ir_block *bfall = nullptr; + size_t bout_id; + + char typestr[1024]; + uint16_t cmpinstr; + + if (lvalue) { + compile_error(m_context, "switch expression is not an l-value"); + return false; + } + + if (m_outr) { + compile_error(m_context, "internal error: ast_switch cannot be reused!"); + return false; + } + m_outr = (ir_value*)1; + + (void)lvalue; + (void)out; + + if (!m_operand->codegen(func, false, &irop)) + return false; + + if (m_cases.empty()) + return true; + + cmpinstr = type_eq_instr[irop->m_vtype]; + if (cmpinstr >= VINSTR_END) { + ast_type_to_string(m_operand, typestr, sizeof(typestr)); + compile_error(m_context, "invalid type to perform a switch on: %s", typestr); + return false; + } + + bout_id = func->m_ir_func->m_blocks.size(); + bout = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("after_switch")); + if (!bout) + return false; + + /* setup the break block */ + func->m_breakblocks.push_back(bout); + + /* Now create all cases */ + for (auto &it : m_cases) { + ir_value *cond, *val; + ir_block *bcase, *bnot; + size_t bnot_id; + + ast_switch_case *swcase = ⁢ + + if (swcase->m_value) { + /* A regular case */ + /* generate the condition operand */ + if (!swcase->m_value->codegen(func, false, &val)) + return false; + /* generate the condition */ + cond = ir_block_create_binop(func->m_curblock, m_context, func->makeLabel("switch_eq"), cmpinstr, irop, val); + if (!cond) + return false; + + bcase = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("case")); + bnot_id = func->m_ir_func->m_blocks.size(); + bnot = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("not_case")); + if (!bcase || !bnot) + return false; + if (set_def_bfall_to) { + set_def_bfall_to = false; + def_bfall_to = bcase; + } + if (!ir_block_create_if(func->m_curblock, m_context, cond, bcase, bnot)) + return false; + + /* Make the previous case-end fall through */ + if (bfall && !bfall->m_final) { + if (!ir_block_create_jump(bfall, m_context, bcase)) + return false; + } + + /* enter the case */ + func->m_curblock = bcase; + if (!swcase->m_code->codegen(func, false, &dummy)) + return false; + + /* remember this block to fall through from */ + bfall = func->m_curblock; + + /* enter the else and move it down */ + func->m_curblock = bnot; + algo::shiftback(func->m_ir_func->m_blocks.begin() + bnot_id, + func->m_ir_func->m_blocks.end()); + // FIXME::DELME:: + //func->m_ir_func->m_blocks[bnot_id].release(); + //func->m_ir_func->m_blocks.erase(func->m_ir_func->m_blocks.begin() + bnot_id); + //func->m_ir_func->m_blocks.emplace_back(bnot); + } else { + /* The default case */ + /* Remember where to fall through from: */ + def_bfall = bfall; + bfall = nullptr; + /* remember which case it was */ + def_case = swcase; + /* And the next case will be remembered */ + set_def_bfall_to = true; + } + } + + /* Jump from the last bnot to bout */ + if (bfall && !bfall->m_final && !ir_block_create_jump(bfall, m_context, bout)) { + /* + astwarning(bfall->m_context, WARN_???, "missing break after last case"); + */ + return false; + } + + /* If there was a default case, put it down here */ + if (def_case) { + ir_block *bcase; + + /* No need to create an extra block */ + bcase = func->m_curblock; + + /* Insert the fallthrough jump */ + if (def_bfall && !def_bfall->m_final) { + if (!ir_block_create_jump(def_bfall, m_context, bcase)) + return false; + } + + /* Now generate the default code */ + if (!def_case->m_code->codegen(func, false, &dummy)) + return false; + + /* see if we need to fall through */ + if (def_bfall_to && !func->m_curblock->m_final) + { + if (!ir_block_create_jump(func->m_curblock, m_context, def_bfall_to)) + return false; + } + } + + /* Jump from the last bnot to bout */ + if (!func->m_curblock->m_final && !ir_block_create_jump(func->m_curblock, m_context, bout)) + return false; + /* enter the outgoing block */ + func->m_curblock = bout; + + /* restore the break block */ + func->m_breakblocks.pop_back(); + + /* Move 'bout' to the end, it's nicer */ + algo::shiftback(func->m_ir_func->m_blocks.begin() + bout_id, + func->m_ir_func->m_blocks.end()); + // FIXME::DELME:: + //func->m_ir_func->m_blocks[bout_id].release(); + //func->m_ir_func->m_blocks.erase(func->m_ir_func->m_blocks.begin() + bout_id); + //func->m_ir_func->m_blocks.emplace_back(bout); + + return true; +} + +bool ast_label::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + ir_value *dummy; + + if (m_undefined) { + compile_error(m_context, "internal error: ast_label never defined"); + return false; + } + + *out = nullptr; + if (lvalue) { + compile_error(m_context, "internal error: ast_label cannot be an lvalue"); + return false; + } + + /* simply create a new block and jump to it */ + m_irblock = ir_function_create_block(m_context, func->m_ir_func, m_name.c_str()); + if (!m_irblock) { + compile_error(m_context, "failed to allocate label block `%s`", m_name); + return false; + } + if (!func->m_curblock->m_final) { + if (!ir_block_create_jump(func->m_curblock, m_context, m_irblock)) + return false; + } + + /* enter the new block */ + func->m_curblock = m_irblock; + + /* Generate all the leftover gotos */ + for (auto &it : m_gotos) { + if (!it->codegen(func, false, &dummy)) + return false; + } + + return true; +} + +bool ast_goto::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + *out = nullptr; + if (lvalue) { + compile_error(m_context, "internal error: ast_goto cannot be an lvalue"); + return false; + } + + if (m_target->m_irblock) { + if (m_irblock_from) { + /* we already tried once, this is the callback */ + m_irblock_from->m_final = false; + if (!ir_block_create_goto(m_irblock_from, m_context, m_target->m_irblock)) { + compile_error(m_context, "failed to generate goto to `%s`", m_name); + return false; + } + } + else + { + if (!ir_block_create_goto(func->m_curblock, m_context, m_target->m_irblock)) { + compile_error(m_context, "failed to generate goto to `%s`", m_name); + return false; + } + } + } + else + { + /* the target has not yet been created... + * close this block in a sneaky way: + */ + func->m_curblock->m_final = true; + m_irblock_from = func->m_curblock; + m_target->registerGoto(this); + } + + return true; +} + +bool ast_state::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + ir_value *frameval, *thinkval; + + if (lvalue) { + compile_error(m_context, "not an l-value (state operation)"); + return false; + } + if (m_outr) { + compile_error(m_context, "internal error: ast_state cannot be reused!"); + return false; + } + *out = nullptr; + + if (!m_framenum->codegen(func, false, &frameval)) + return false; + if (!frameval) + return false; + + if (!m_nextthink->codegen(func, false, &thinkval)) + return false; + if (!frameval) + return false; + + if (!ir_block_create_state_op(func->m_curblock, m_context, frameval, thinkval)) { + compile_error(m_context, "failed to create STATE instruction"); + return false; + } + + m_outr = (ir_value*)1; + return true; +} + +bool ast_call::codegen(ast_function *func, bool lvalue, ir_value **out) +{ + std::vector params; + ir_instr *callinstr; + + ir_value *funval = nullptr; + + /* return values are never lvalues */ + if (lvalue) { + compile_error(m_context, "not an l-value (function call)"); + return false; + } + + if (m_outr) { + *out = m_outr; + return true; + } + + if (!m_func->codegen(func, false, &funval)) + return false; + if (!funval) + return false; + + /* parameters */ + for (auto &it : m_params) { + ir_value *param; + if (!it->codegen(func, false, ¶m)) + return false; + if (!param) + return false; + params.push_back(param); + } + + /* varargs counter */ + if (m_va_count) { + ir_value *va_count; + ir_builder *builder = func->m_curblock->m_owner->m_owner; + if (!m_va_count->codegen(func, false, &va_count)) + return false; + if (!ir_block_create_store_op(func->m_curblock, m_context, INSTR_STORE_F, + builder->get_va_count(), va_count)) + { + return false; + } + } + + callinstr = ir_block_create_call(func->m_curblock, m_context, + func->makeLabel("call"), + funval, !!(m_func->m_flags & AST_FLAG_NORETURN)); + if (!callinstr) + return false; + + for (auto &it : params) + ir_call_param(callinstr, it); + + *out = ir_call_value(callinstr); + m_outr = *out; + + codegen_output_type(this, *out); + + return true; +} diff --git a/ast.h b/ast.h index 5b556e1..b305dac 100644 --- a/ast.h +++ b/ast.h @@ -1,28 +1,6 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * Wolfgang Bumiller - * 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. - */ #ifndef GMQCC_AST_HDR #define GMQCC_AST_HDR +#include #include "ir.h" typedef uint16_t ast_flag_t; @@ -31,30 +9,29 @@ typedef uint16_t ast_flag_t; * "main" ast node types for now. */ -typedef struct ast_node_common ast_node; -typedef struct ast_expression_common ast_expression; - -typedef struct ast_value_s ast_value; -typedef struct ast_function_s ast_function; -typedef struct ast_block_s ast_block; -typedef struct ast_binary_s ast_binary; -typedef struct ast_store_s ast_store; -typedef struct ast_binstore_s ast_binstore; -typedef struct ast_entfield_s ast_entfield; -typedef struct ast_ifthen_s ast_ifthen; -typedef struct ast_ternary_s ast_ternary; -typedef struct ast_loop_s ast_loop; -typedef struct ast_call_s ast_call; -typedef struct ast_unary_s ast_unary; -typedef struct ast_return_s ast_return; -typedef struct ast_member_s ast_member; -typedef struct ast_array_index_s ast_array_index; -typedef struct ast_breakcont_s ast_breakcont; -typedef struct ast_switch_s ast_switch; -typedef struct ast_label_s ast_label; -typedef struct ast_goto_s ast_goto; -typedef struct ast_argpipe_s ast_argpipe; -typedef struct ast_state_s ast_state; +struct ast_node; +struct ast_expression; +struct ast_value; +struct ast_function; +struct ast_block; +struct ast_binary; +struct ast_store; +struct ast_binstore; +struct ast_entfield; +struct ast_ifthen; +struct ast_ternary; +struct ast_loop; +struct ast_call; +struct ast_unary; +struct ast_return; +struct ast_member; +struct ast_array_index; +struct ast_breakcont; +struct ast_switch; +struct ast_label; +struct ast_goto; +struct ast_argpipe; +struct ast_state; enum { AST_FLAG_VARIADIC = 1 << 0, @@ -116,43 +93,36 @@ enum { TYPE_ast_state /* 22 */ }; -#define ast_istype(x, t) ( ((ast_node*)x)->nodetype == (TYPE_##t) ) -#define ast_ctx(node) (((ast_node*)(node))->context) -#define ast_side_effects(node) (((ast_node*)(node))->side_effects) +#define ast_istype(x, t) ( (x)->m_node_type == (TYPE_##t) ) -/* Node interface with common components - */ -typedef void ast_node_delete(ast_node*); -struct ast_node_common +struct ast_node { - lex_ctx_t context; + ast_node() = delete; + ast_node(lex_ctx_t, int nodetype); + virtual ~ast_node(); + + lex_ctx_t m_context; /* I don't feel comfortable using keywords like 'delete' as names... */ - ast_node_delete *destroy; - int nodetype; - /* keep: if a node contains this node, 'keep' + int m_node_type; + /* keep_node: if a node contains this node, 'keep_node' * prevents its dtor from destroying this node as well. */ - bool keep; - bool side_effects; + bool m_keep_node; + bool m_side_effects; + + void propagateSideEffects(const ast_node *other); }; -#define ast_delete(x) (*( ((ast_node*)(x))->destroy ))((ast_node*)(x)) -#define ast_unref(x) do \ -{ \ - if (! (((ast_node*)(x))->keep) ) { \ - ast_delete(x); \ - } \ +#define ast_unref(x) do \ +{ \ + if (! (x)->m_keep_node ) { \ + delete (x); \ + } \ } while(0) -/* Expression interface - * - * Any expression or block returns an ir_value, and needs - * to know the current function. - */ -typedef bool ast_expression_codegen(ast_expression*, - ast_function*, - bool lvalue, - ir_value**); +enum class ast_copy_type_t { value }; +static const ast_copy_type_t ast_copy_type = ast_copy_type_t::value; + /* TODO: the codegen function should take an output-type parameter * indicating whether a variable, type, label etc. is expected, and * an environment! @@ -163,27 +133,42 @@ typedef bool ast_expression_codegen(ast_expression*, * type `expression`, so the ast_ident's codegen would search for * variables through the environment (or functions, constants...). */ -struct ast_expression_common -{ - ast_node node; - ast_expression_codegen *codegen; - int vtype; - ast_expression *next; +struct ast_expression : ast_node { + ast_expression() = delete; + ast_expression(lex_ctx_t ctx, int nodetype, qc_type vtype); + ast_expression(lex_ctx_t ctx, int nodetype); + ~ast_expression(); + + ast_expression(ast_copy_type_t, const ast_expression&); + ast_expression(ast_copy_type_t, lex_ctx_t ctx, const ast_expression&); + ast_expression(ast_copy_type_t, int nodetype, const ast_expression&); + ast_expression(ast_copy_type_t, int nodetype, lex_ctx_t ctx, const ast_expression&); + + static ast_expression *shallowType(lex_ctx_t ctx, qc_type vtype); + + bool compareType(const ast_expression &other) const; + void adoptType(const ast_expression &other); + + qc_type m_vtype = TYPE_VOID; + ast_expression *m_next = nullptr; /* arrays get a member-count */ - size_t count; - ast_value* *params; - ast_flag_t flags; + size_t m_count = 0; + std::vector> m_type_params; + + ast_flag_t m_flags = 0; /* void foo(string...) gets varparam set as a restriction * for variadic parameters */ - ast_expression *varparam; + ast_expression *m_varparam = nullptr; /* The codegen functions should store their output values * so we can call it multiple times without re-evaluating. * Store lvalue and rvalue seperately though. So that * ast_entfield for example can generate both if required. */ - ir_value *outl; - ir_value *outr; + ir_value *m_outl = nullptr; + ir_value *m_outr = nullptr; + + virtual bool codegen(ast_function *current, bool lvalue, ir_value **out); }; /* Value @@ -193,7 +178,7 @@ struct ast_expression_common * typedef float foo; * is like creating a 'float foo', foo serving as the type's name. */ -typedef union { +union basic_value_t { qcfloat_t vfloat; int vint; vec3_t vvec; @@ -201,129 +186,133 @@ typedef union { int ventity; ast_function *vfunc; ast_value *vfield; -} basic_value_t; +}; -struct ast_value_s +struct ast_value : ast_expression { - ast_expression expression; + ast_value() = delete; + ast_value(lex_ctx_t ctx, const std::string &name, qc_type qctype); + ~ast_value(); + + ast_value(ast_copy_type_t, const ast_expression&, const std::string&); + ast_value(ast_copy_type_t, const ast_value&); + ast_value(ast_copy_type_t, const ast_value&, const std::string&); + + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; - const char *name; - const char *desc; + void addParam(ast_value*); - const char *argcounter; + bool generateGlobal(ir_builder*, bool isfield); + bool generateLocal(ir_function*, bool param); + bool generateAccessors(ir_builder*); - int cvq; /* const/var qualifier */ - bool isfield; /* this declares a field */ - bool isimm; /* an immediate, not just const */ - bool hasvalue; - bool inexact; /* inexact coming from folded expression */ - basic_value_t constval; + std::string m_name; + std::string m_desc; + + const char *m_argcounter = nullptr; + + int m_cvq = CV_NONE; /* const/var qualifier */ + bool m_isfield = false; /* this declares a field */ + bool m_isimm = false; /* an immediate, not just const */ + bool m_hasvalue = false; + bool m_inexact = false; /* inexact coming from folded expression */ + basic_value_t m_constval; /* for TYPE_ARRAY we have an optional vector * of constants when an initializer list * was provided. */ - basic_value_t *initlist; + std::vector m_initlist; /* usecount for the parser */ - size_t uses; + size_t m_uses = 0; - ir_value *ir_v; - ir_value **ir_values; - size_t ir_value_count; + ir_value *m_ir_v = nullptr; + std::vector m_ir_values; + size_t m_ir_value_count = 0; /* ONLY for arrays in progs version up to 6 */ - ast_value *setter; - ast_value *getter; + ast_value *m_setter = nullptr; + ast_value *m_getter = nullptr; + bool m_intrinsic = false; /* true if associated with intrinsic */ - bool intrinsic; /* true if associated with intrinsic */ +private: + bool generateGlobalFunction(ir_builder*); + bool generateGlobalField(ir_builder*); + ir_value *prepareGlobalArray(ir_builder*); + bool setGlobalArray(); + bool checkArray(const ast_value &array) const; }; -ast_value* ast_value_new(lex_ctx_t ctx, const char *name, int qctype); -ast_value* ast_value_copy(const ast_value *self); -/* This will NOT delete an underlying ast_function */ -void ast_value_delete(ast_value*); - -bool ast_value_set_name(ast_value*, const char *name); - -/* -bool ast_value_codegen(ast_value*, ast_function*, bool lvalue, ir_value**); -bool ast_local_codegen(ast_value *self, ir_function *func, bool isparam); -*/ - -bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield); +void ast_type_to_string(const ast_expression *e, char *buf, size_t bufsize); -void ast_value_params_add(ast_value*, ast_value*); - -bool ast_compare_type(ast_expression *a, ast_expression *b); -ast_expression* ast_type_copy(lex_ctx_t ctx, const ast_expression *ex); -#define ast_type_adopt(a, b) ast_type_adopt_impl((ast_expression*)(a), (ast_expression*)(b)) -void ast_type_adopt_impl(ast_expression *self, const ast_expression *other); -void ast_type_to_string(ast_expression *e, char *buf, size_t bufsize); - -typedef enum ast_binary_ref_s { +enum ast_binary_ref { AST_REF_NONE = 0, AST_REF_LEFT = 1 << 1, AST_REF_RIGHT = 1 << 2, AST_REF_ALL = (AST_REF_LEFT | AST_REF_RIGHT) -} ast_binary_ref; +}; /* Binary * * A value-returning binary expression. */ -struct ast_binary_s +struct ast_binary : ast_expression { - ast_expression expression; + ast_binary() = delete; + ast_binary(lex_ctx_t ctx, int op, ast_expression *l, ast_expression *r); + ~ast_binary(); - int op; - ast_expression *left; - ast_expression *right; - ast_binary_ref refs; - bool right_first; + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; + + int m_op; + ast_expression *m_left; + ast_expression *m_right; + ast_binary_ref m_refs; + bool m_right_first; }; -ast_binary* ast_binary_new(lex_ctx_t ctx, - int op, - ast_expression *left, - ast_expression *right); /* Binstore * * An assignment including a binary expression with the source as left operand. * Eg. a += b; is a binstore { INSTR_STORE, INSTR_ADD, a, b } */ -struct ast_binstore_s +struct ast_binstore : ast_expression { - ast_expression expression; + ast_binstore() = delete; + ast_binstore(lex_ctx_t ctx, int storeop, int mathop, ast_expression *l, ast_expression *r); + ~ast_binstore(); + + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; - int opstore; - int opbin; - ast_expression *dest; - ast_expression *source; + int m_opstore; + int m_opbin; + ast_expression *m_dest; + ast_expression *m_source; /* for &~= which uses the destination in a binary in source we can use this */ - bool keep_dest; + bool m_keep_dest; }; -ast_binstore* ast_binstore_new(lex_ctx_t ctx, - int storeop, - int op, - ast_expression *left, - ast_expression *right); /* Unary * * Regular unary expressions: not,neg */ -struct ast_unary_s +struct ast_unary : ast_expression { - ast_expression expression; + ast_unary() = delete; + ~ast_unary(); - int op; - ast_expression *operand; + static ast_unary* make(lex_ctx_t ctx, int op, ast_expression *expr); + + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; + + int m_op; + ast_expression *m_operand; + +private: + ast_unary(lex_ctx_t ctx, int op, ast_expression *expr); }; -ast_unary* ast_unary_new(lex_ctx_t ctx, - int op, - ast_expression *expr); /* Return * @@ -331,13 +320,16 @@ ast_unary* ast_unary_new(lex_ctx_t ctx, * will refuse to create further instructions. * This should be honored by the parser. */ -struct ast_return_s +struct ast_return : ast_expression { - ast_expression expression; - ast_expression *operand; + ast_return() = delete; + ast_return(lex_ctx_t ctx, ast_expression *expr); + ~ast_return(); + + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; + + ast_expression *m_operand; }; -ast_return* ast_return_new(lex_ctx_t ctx, - ast_expression *expr); /* Entity-field * @@ -352,34 +344,42 @@ ast_return* ast_return_new(lex_ctx_t ctx, * For this we will have to extend the codegen() functions with * a flag saying whether or not we need an L or an R-value. */ -struct ast_entfield_s +struct ast_entfield : ast_expression { - ast_expression expression; - /* The entity can come from an expression of course. */ - ast_expression *entity; - /* As can the field, it just must result in a value of TYPE_FIELD */ - ast_expression *field; + ast_entfield() = delete; + ast_entfield(lex_ctx_t ctx, ast_expression *entity, ast_expression *field); + ast_entfield(lex_ctx_t ctx, ast_expression *entity, ast_expression *field, const ast_expression *outtype); + ~ast_entfield(); + + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; + + // The entity can come from an expression of course. + ast_expression *m_entity; + // As can the field, it just must result in a value of TYPE_FIELD + ast_expression *m_field; }; -ast_entfield* ast_entfield_new(lex_ctx_t ctx, ast_expression *entity, ast_expression *field); -ast_entfield* ast_entfield_new_force(lex_ctx_t ctx, ast_expression *entity, ast_expression *field, const ast_expression *outtype); /* Member access: * * For now used for vectors. If we get structs or unions * we can have them handled here as well. */ -struct ast_member_s +struct ast_member : ast_expression { - ast_expression expression; - ast_expression *owner; - unsigned int field; - const char *name; - bool rvalue; -}; -ast_member* ast_member_new(lex_ctx_t ctx, ast_expression *owner, unsigned int field, const char *name); -void ast_member_delete(ast_member*); -bool ast_member_set_name(ast_member*, const char *name); + static ast_member *make(lex_ctx_t ctx, ast_expression *owner, unsigned int field, const std::string &name); + ~ast_member(); + + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; + + ast_expression *m_owner; + unsigned int m_field; + std::string m_name; + bool m_rvalue; +private: + ast_member() = delete; + ast_member(lex_ctx_t ctx, ast_expression *owner, unsigned int field, const std::string &name); +}; /* Array index access: * @@ -391,60 +391,77 @@ bool ast_member_set_name(ast_member*, const char *name); * In any case, accessing an element via a compiletime-constant index will * result in quick access to that variable. */ -struct ast_array_index_s +struct ast_array_index : ast_expression { - ast_expression expression; - ast_expression *array; - ast_expression *index; + static ast_array_index* make(lex_ctx_t ctx, ast_expression *array, ast_expression *index); + ~ast_array_index(); + + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; + + ast_expression *m_array; + ast_expression *m_index; +private: + ast_array_index() = delete; + ast_array_index(lex_ctx_t ctx, ast_expression *array, ast_expression *index); }; -ast_array_index* ast_array_index_new(lex_ctx_t ctx, ast_expression *array, ast_expression *index); /* Vararg pipe node: * * copy all varargs starting from a specific index */ -struct ast_argpipe_s +struct ast_argpipe : ast_expression { - ast_expression expression; - ast_expression *index; + ast_argpipe() = delete; + ast_argpipe(lex_ctx_t ctx, ast_expression *index); + + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; + + ~ast_argpipe(); + ast_expression *m_index; }; -ast_argpipe* ast_argpipe_new(lex_ctx_t ctx, ast_expression *index); /* Store * * Stores left<-right and returns left. * Specialized binary expression node */ -struct ast_store_s +struct ast_store : ast_expression { - ast_expression expression; - int op; - ast_expression *dest; - ast_expression *source; + ast_store() = delete; + ast_store(lex_ctx_t ctx, int op, ast_expression *d, ast_expression *s); + ~ast_store(); + + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; + + int m_op; + ast_expression *m_dest; + ast_expression *m_source; }; -ast_store* ast_store_new(lex_ctx_t ctx, int op, - ast_expression *d, ast_expression *s); /* If * - * A general 'if then else' statement, either side can be NULL and will - * thus be omitted. It is an error for *both* cases to be NULL at once. + * A general 'if then else' statement, either side can be nullptr and will + * thus be omitted. It is an error for *both* cases to be nullptr at once. * * During its 'codegen' it'll be changing the ast_function's block. * - * An if is also an "expression". Its codegen will put NULL into the + * An if is also an "expression". Its codegen will put nullptr into the * output field though. For ternary expressions an ast_ternary will be * added. */ -struct ast_ifthen_s +struct ast_ifthen : ast_expression { - ast_expression expression; - ast_expression *cond; + ast_ifthen() = delete; + ast_ifthen(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse); + ~ast_ifthen(); + + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; + + ast_expression *m_cond; /* It's all just 'expressions', since an ast_block is one too. */ - ast_expression *on_true; - ast_expression *on_false; + ast_expression *m_on_true; + ast_expression *m_on_false; }; -ast_ifthen* ast_ifthen_new(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse); /* Ternary expressions... * @@ -454,20 +471,24 @@ ast_ifthen* ast_ifthen_new(lex_ctx_t ctx, ast_expression *cond, ast_expression * * a PHI node. * * The other difference is that in an ast_ternary, NEITHER side - * must be NULL, there's ALWAYS an else branch. + * must be nullptr, there's ALWAYS an else branch. * * This is the only ast_node beside ast_value which contains * an ir_value. Theoretically we don't need to remember it though. */ -struct ast_ternary_s +struct ast_ternary : ast_expression { - ast_expression expression; - ast_expression *cond; + ast_ternary() = delete; + ast_ternary(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse); + ~ast_ternary(); + + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; + + ast_expression *m_cond; /* It's all just 'expressions', since an ast_block is one too. */ - ast_expression *on_true; - ast_expression *on_false; + ast_expression *m_on_true; + ast_expression *m_on_false; }; -ast_ternary* ast_ternary_new(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse); /* A general loop node * @@ -492,39 +513,48 @@ continue: // a 'continue' will jump here {inc}; } */ -struct ast_loop_s +struct ast_loop : ast_expression { - ast_expression expression; - ast_expression *initexpr; - ast_expression *precond; - ast_expression *postcond; - ast_expression *increment; - ast_expression *body; + ast_loop() = delete; + ast_loop(lex_ctx_t ctx, + ast_expression *initexpr, + ast_expression *precond, bool pre_not, + ast_expression *postcond, bool post_not, + ast_expression *increment, + ast_expression *body); + ~ast_loop(); + + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; + + ast_expression *m_initexpr; + ast_expression *m_precond; + ast_expression *m_postcond; + ast_expression *m_increment; + ast_expression *m_body; /* For now we allow a seperate flag on whether or not the condition * is supposed to be true or false. * That way, the parser can generate a 'while not(!x)' for `while(x)` * if desired, which is useful for the new -f{true,false}-empty-strings * flag. */ - bool pre_not; - bool post_not; + bool m_pre_not; + bool m_post_not; }; -ast_loop* ast_loop_new(lex_ctx_t ctx, - ast_expression *initexpr, - ast_expression *precond, bool pre_not, - ast_expression *postcond, bool post_not, - ast_expression *increment, - ast_expression *body); /* Break/Continue */ -struct ast_breakcont_s +struct ast_breakcont : ast_expression { - ast_expression expression; - bool is_continue; - unsigned int levels; + ast_breakcont() = delete; + ast_breakcont(lex_ctx_t ctx, bool iscont, unsigned int levels); + ~ast_breakcont(); + + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; + + + bool m_is_continue; + unsigned int m_levels; }; -ast_breakcont* ast_breakcont_new(lex_ctx_t ctx, bool iscont, unsigned int levels); /* Switch Statements * @@ -536,64 +566,82 @@ ast_breakcont* ast_breakcont_new(lex_ctx_t ctx, bool iscont, unsigned int levels * be expected from it. * TODO: Ticket #20 */ -typedef struct { - ast_expression *value; /* #20 will replace this */ - ast_expression *code; -} ast_switch_case; -struct ast_switch_s +struct ast_switch_case { + ast_expression *m_value; /* #20 will replace this */ + ast_expression *m_code; +}; + +struct ast_switch : ast_expression { - ast_expression expression; + ast_switch() = delete; + ast_switch(lex_ctx_t ctx, ast_expression *op); + ~ast_switch(); - ast_expression *operand; - ast_switch_case *cases; + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; + + ast_expression *m_operand; + std::vector m_cases; }; -ast_switch* ast_switch_new(lex_ctx_t ctx, ast_expression *op); /* Label nodes * * Introduce a label which can be used together with 'goto' */ -struct ast_label_s +struct ast_label : ast_expression { - ast_expression expression; - const char *name; - ir_block *irblock; - ast_goto **gotos; + ast_label() = delete; + ast_label(lex_ctx_t ctx, const std::string &name, bool undefined); + ~ast_label(); + + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; + + std::string m_name; + ir_block *m_irblock; + std::vector m_gotos; /* means it has not yet been defined */ - bool undefined; -}; + bool m_undefined; -ast_label* ast_label_new(lex_ctx_t ctx, const char *name, bool undefined); +private: + void registerGoto(ast_goto*); + friend struct ast_goto; +}; /* GOTO nodes * * Go to a label, the label node is filled in at a later point! */ -struct ast_goto_s +struct ast_goto : ast_expression { - ast_expression expression; - const char *name; - ast_label *target; - ir_block *irblock_from; -}; + ast_goto() = delete; + ast_goto(lex_ctx_t ctx, const std::string &name); + ~ast_goto(); + + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; + + void setLabel(ast_label*); -ast_goto* ast_goto_new(lex_ctx_t ctx, const char *name); -void ast_goto_set_label(ast_goto*, ast_label*); + std::string m_name; + ast_label *m_target; + ir_block *m_irblock_from; +}; /* STATE node * * For frame/think state updates: void foo() [framenum, nextthink] {} */ -struct ast_state_s +struct ast_state : ast_expression { - ast_expression expression; - ast_expression *framenum; - ast_expression *nextthink; + ast_state() = delete; + ast_state(lex_ctx_t ctx, ast_expression *frame, ast_expression *think); + ~ast_state(); + + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; + + ast_expression *m_framenum; + ast_expression *m_nextthink; }; -ast_state* ast_state_new(lex_ctx_t ctx, ast_expression *frame, ast_expression *think); -void ast_state_delete(ast_state*); /* CALL node * @@ -605,34 +653,44 @@ void ast_state_delete(ast_state*); * Additionally it contains a list of ast_expressions as parameters. * Since calls can return values, an ast_call is also an ast_expression. */ -struct ast_call_s +struct ast_call : ast_expression { - ast_expression expression; - ast_expression *func; - ast_expression **params; - ast_expression *va_count; + ast_call() = delete; + static ast_call *make(lex_ctx_t, ast_expression*); + ~ast_call(); + + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; + + bool checkTypes(ast_expression *this_func_va_type) const; + + ast_expression *m_func; + std::vector m_params; + ast_expression *m_va_count; + +private: + ast_call(lex_ctx_t ctx, ast_expression *funcexpr); + bool checkVararg(ast_expression *va_type, ast_expression *exp_type) const; }; -ast_call* ast_call_new(lex_ctx_t ctx, - ast_expression *funcexpr); -bool ast_call_check_types(ast_call*, ast_expression *this_func_va_type); /* Blocks * */ -struct ast_block_s +struct ast_block : ast_expression { - ast_expression expression; + ast_block() = delete; + ast_block(lex_ctx_t ctx); + ~ast_block(); - ast_value* *locals; - ast_expression* *exprs; - ast_expression* *collect; -}; -ast_block* ast_block_new(lex_ctx_t ctx); -void ast_block_delete(ast_block*); -void ast_block_set_type(ast_block*, ast_expression *from); -void ast_block_collect(ast_block*, ast_expression*); + bool codegen(ast_function *current, bool lvalue, ir_value **out) override; -bool GMQCC_WARN ast_block_add_expr(ast_block*, ast_expression*); + std::vector m_locals; + std::vector m_exprs; + std::vector m_collect; + + void setType(const ast_expression &from); + bool GMQCC_WARN addExpr(ast_expression*); + void collect(ast_expression*); +}; /* Function * @@ -643,62 +701,53 @@ bool GMQCC_WARN ast_block_add_expr(ast_block*, ast_expression*); * neither functions inside functions, nor lambdas, and function * pointers could just work with a name. However, this way could be * more flexible, and adds no real complexity. + * + * The destructor will NOT delete the underlying ast_value + * */ -struct ast_function_s +struct ast_function : ast_node { - ast_node node; + ast_function() = delete; + static ast_function *make(lex_ctx_t ctx, const std::string &name, ast_value *vtype); + ~ast_function(); + + const char* makeLabel(const char *prefix); + virtual bool generateFunction(ir_builder*); - ast_value *vtype; - const char *name; + ast_value *m_function_type = nullptr; + std::string m_name; - int builtin; + int m_builtin = 0; /* list of used-up names for statics without the count suffix */ - char **static_names; + std::vector m_static_names; /* number of static variables, by convention this includes the * ones without the count-suffix - remember this when dealing * with savegames. uint instead of size_t as %zu in printf is * C99, so no windows support. */ - unsigned int static_count; + unsigned int m_static_count = 0; - ir_function *ir_func; - ir_block *curblock; - ir_block **breakblocks; - ir_block **continueblocks; + ir_function *m_ir_func = nullptr; + ir_block *m_curblock = nullptr; + std::vector m_breakblocks; + std::vector m_continueblocks; -#if 0 - /* In order for early-out logic not to go over - * excessive jumps, we remember their target - * blocks... - */ - ir_block *iftrue; - ir_block *iffalse; -#endif - - size_t labelcount; + size_t m_labelcount = 0; /* in order for thread safety - for the optional * channel abesed multithreading... keeping a buffer * here to use in ast_function_label. */ - char labelbuf[64]; + std::vector> m_blocks; + std::unique_ptr m_varargs; + std::unique_ptr m_argc; + ast_value *m_fixedparams = nullptr; // these use unref() + ast_value *m_return_value = nullptr; - ast_block* *blocks; +private: + ast_function(lex_ctx_t ctx, const std::string &name, ast_value *vtype); - ast_value *varargs; - ast_value *argc; - ast_value *fixedparams; - ast_value *return_value; + char m_labelbuf[64]; }; -ast_function* ast_function_new(lex_ctx_t ctx, const char *name, ast_value *vtype); -/* This will NOT delete the underlying ast_value */ -void ast_function_delete(ast_function*); -/* For "optimized" builds this can just keep returning "foo"... - * or whatever... - */ -const char* ast_function_label(ast_function*, const char *prefix); - -bool ast_function_codegen(ast_function *self, ir_builder *builder); -bool ast_generate_accessors(ast_value *asvalue, ir_builder *ir); /* * If the condition creates a situation where this becomes -1 size it means there are diff --git a/code.c b/code.c deleted file mode 100644 index 6f040ce..0000000 --- a/code.c +++ /dev/null @@ -1,461 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * Dale Weiler - * Wolfgang Bumiller - * - * 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 -#include "gmqcc.h" - -/* - * We could use the old method of casting to uintptr_t then to void* - * or qcint_t; however, it's incredibly unsafe for two reasons. - * 1) The compilers aliasing optimization can legally make it unstable - * (it's undefined behaviour). - * - * 2) The cast itself depends on fresh storage (newly allocated in which - * ever function is using the cast macros), the contents of which are - * transferred in a way that the obligation to release storage is not - * propagated. - */ -typedef union { - void *enter; - qcint_t leave; -} code_hash_entry_t; - -/* Some sanity macros */ -#define CODE_HASH_ENTER(ENTRY) ((ENTRY).enter) -#define CODE_HASH_LEAVE(ENTRY) ((ENTRY).leave) - -void code_push_statement(code_t *code, prog_section_statement_t *stmt_in, lex_ctx_t ctx) -{ - prog_section_statement_t stmt = *stmt_in; - - if (OPTS_FLAG(TYPELESS_STORES)) { - switch (stmt.opcode) { - case INSTR_LOAD_S: - case INSTR_LOAD_ENT: - case INSTR_LOAD_FLD: - case INSTR_LOAD_FNC: - stmt.opcode = INSTR_LOAD_F; - break; - case INSTR_STORE_S: - case INSTR_STORE_ENT: - case INSTR_STORE_FLD: - case INSTR_STORE_FNC: - stmt.opcode = INSTR_STORE_F; - break; - case INSTR_STOREP_S: - case INSTR_STOREP_ENT: - case INSTR_STOREP_FLD: - case INSTR_STOREP_FNC: - stmt.opcode = INSTR_STOREP_F; - break; - } - } - - - if (OPTS_FLAG(SORT_OPERANDS)) { - uint16_t pair; - - switch (stmt.opcode) { - case INSTR_MUL_F: - case INSTR_MUL_V: - case INSTR_ADD_F: - case INSTR_EQ_F: - case INSTR_EQ_S: - case INSTR_EQ_E: - case INSTR_EQ_FNC: - case INSTR_NE_F: - case INSTR_NE_V: - case INSTR_NE_S: - case INSTR_NE_E: - case INSTR_NE_FNC: - case INSTR_AND: - case INSTR_OR: - case INSTR_BITAND: - case INSTR_BITOR: - if (stmt.o1.u1 < stmt.o2.u1) { - uint16_t a = stmt.o2.u1; - stmt.o1.u1 = stmt.o2.u1; - stmt.o2.u1 = a; - } - break; - - case INSTR_MUL_VF: pair = INSTR_MUL_FV; goto case_pair_gen; - case INSTR_MUL_FV: pair = INSTR_MUL_VF; goto case_pair_gen; - case INSTR_LT: pair = INSTR_GT; goto case_pair_gen; - case INSTR_GT: pair = INSTR_LT; goto case_pair_gen; - case INSTR_LE: pair = INSTR_GT; goto case_pair_gen; - case INSTR_GE: pair = INSTR_LE; - - case_pair_gen: - if (stmt.o1.u1 < stmt.o2.u1) { - uint16_t x = stmt.o1.u1; - stmt.o1.u1 = stmt.o2.u1; - stmt.o2.u1 = x; - stmt.opcode = pair; - } - break; - } - } - - vec_push(code->statements, stmt); - vec_push(code->linenums, (int)ctx.line); - vec_push(code->columnnums, (int)ctx.column); -} - -void code_pop_statement(code_t *code) -{ - vec_pop(code->statements); - vec_pop(code->linenums); - vec_pop(code->columnnums); -} - -code_t *code_init() { - static lex_ctx_t empty_ctx = {0, 0, 0}; - static prog_section_function_t empty_function = {0,0,0,0,0,0,0,{0,0,0,0,0,0,0,0}}; - static prog_section_statement_t empty_statement = {0,{0},{0},{0}}; - static prog_section_def_t empty_def = {0, 0, 0}; - - code_t *code = (code_t*)mem_a(sizeof(code_t)); - int i = 0; - - memset(code, 0, sizeof(code_t)); - code->entfields = 0; - code->string_cache = util_htnew(OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS) ? 0x100 : 1024); - - /* - * The way progs.dat is suppose to work is odd, there needs to be - * some null (empty) statements, functions, and 28 globals - */ - for(; i < 28; i++) - vec_push(code->globals, 0); - - vec_push(code->chars, '\0'); - vec_push(code->functions, empty_function); - - code_push_statement(code, &empty_statement, empty_ctx); - - vec_push(code->defs, empty_def); - vec_push(code->fields, empty_def); - - return code; -} - -void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin); - -uint32_t code_genstring(code_t *code, const char *str) { - size_t hash; - code_hash_entry_t existing; - - if (!str) - return 0; - - if (!*str) { - if (!code->string_cached_empty) { - code->string_cached_empty = vec_size(code->chars); - vec_push(code->chars, 0); - } - return code->string_cached_empty; - } - - if (OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS)) { - hash = ((unsigned char*)str)[strlen(str)-1]; - CODE_HASH_ENTER(existing) = code_util_str_htgeth(code->string_cache, str, hash); - } else { - hash = util_hthash(code->string_cache, str); - CODE_HASH_ENTER(existing) = util_htgeth(code->string_cache, str, hash); - } - - if (CODE_HASH_ENTER(existing)) - return CODE_HASH_LEAVE(existing); - - CODE_HASH_LEAVE(existing) = vec_size(code->chars); - vec_append(code->chars, strlen(str)+1, str); - - util_htseth(code->string_cache, str, hash, CODE_HASH_ENTER(existing)); - return CODE_HASH_LEAVE(existing); -} - -qcint_t code_alloc_field (code_t *code, size_t qcsize) -{ - qcint_t pos = (qcint_t)code->entfields; - code->entfields += qcsize; - return pos; -} - -static size_t code_size_generic(code_t *code, prog_header_t *code_header, bool lno) { - size_t size = 0; - if (lno) { - size += 4; /* LNOF */ - size += sizeof(uint32_t); /* version */ - size += sizeof(code_header->defs.length); - size += sizeof(code_header->globals.length); - size += sizeof(code_header->fields.length); - size += sizeof(code_header->statements.length); - size += sizeof(code->linenums[0]) * vec_size(code->linenums); - size += sizeof(code->columnnums[0]) * vec_size(code->columnnums); - } else { - size += sizeof(prog_header_t); - size += sizeof(prog_section_statement_t) * vec_size(code->statements); - size += sizeof(prog_section_def_t) * vec_size(code->defs); - size += sizeof(prog_section_field_t) * vec_size(code->fields); - size += sizeof(prog_section_function_t) * vec_size(code->functions); - size += sizeof(int32_t) * vec_size(code->globals); - size += 1 * vec_size(code->chars); - } - return size; -} - -#define code_size_binary(C, H) code_size_generic((C), (H), false) -#define code_size_debug(C, H) code_size_generic((C), (H), true) - -static void code_create_header(code_t *code, prog_header_t *code_header, const char *filename, const char *lnofile) { - size_t i; - - code_header->statements.offset = sizeof(prog_header_t); - code_header->statements.length = vec_size(code->statements); - code_header->defs.offset = code_header->statements.offset + (sizeof(prog_section_statement_t) * vec_size(code->statements)); - code_header->defs.length = vec_size(code->defs); - code_header->fields.offset = code_header->defs.offset + (sizeof(prog_section_def_t) * vec_size(code->defs)); - code_header->fields.length = vec_size(code->fields); - code_header->functions.offset = code_header->fields.offset + (sizeof(prog_section_field_t) * vec_size(code->fields)); - code_header->functions.length = vec_size(code->functions); - code_header->globals.offset = code_header->functions.offset + (sizeof(prog_section_function_t) * vec_size(code->functions)); - code_header->globals.length = vec_size(code->globals); - code_header->strings.offset = code_header->globals.offset + (sizeof(int32_t) * vec_size(code->globals)); - code_header->strings.length = vec_size(code->chars); - code_header->version = 6; - code_header->skip = 0; - - if (OPTS_OPTION_BOOL(OPTION_FORCECRC)) - code_header->crc16 = OPTS_OPTION_U16(OPTION_FORCED_CRC); - else - code_header->crc16 = code->crc; - code_header->entfield = code->entfields; - - if (OPTS_FLAG(DARKPLACES_STRING_TABLE_BUG)) { - /* >= + P */ - vec_push(code->chars, '\0'); /* > */ - vec_push(code->chars, '\0'); /* = */ - vec_push(code->chars, '\0'); /* P */ - } - - /* ensure all data is in LE format */ - util_swap_header(code_header); - - /* - * These are not part of the header but we ensure LE format here to save on duplicated - * code. - */ - - util_swap_statements (code->statements); - util_swap_defs_fields(code->defs); - util_swap_defs_fields(code->fields); - util_swap_functions (code->functions); - util_swap_globals (code->globals); - - if (!OPTS_OPTION_BOOL(OPTION_QUIET)) { - if (lnofile) - con_out("writing '%s' and '%s'...\n", filename, lnofile); - else - con_out("writing '%s'\n", filename); - } - - if (!OPTS_OPTION_BOOL(OPTION_QUIET) && - !OPTS_OPTION_BOOL(OPTION_PP_ONLY)) - { - char buffer[1024]; - con_out("\nOptimizations:\n"); - for (i = 0; i < COUNT_OPTIMIZATIONS; ++i) { - if (opts_optimizationcount[i]) { - util_optimizationtostr(opts_opt_list[i].name, buffer, sizeof(buffer)); - con_out( - " %s: %u\n", - buffer, - (unsigned int)opts_optimizationcount[i] - ); - } - } - } -} - -static void code_stats(const char *filename, const char *lnofile, code_t *code, prog_header_t *code_header) { - if (OPTS_OPTION_BOOL(OPTION_QUIET) || - OPTS_OPTION_BOOL(OPTION_PP_ONLY)) - return; - - con_out("\nFile statistics:\n"); - con_out(" dat:\n"); - con_out(" name: %s\n", filename); - con_out(" size: %u (bytes)\n", code_size_binary(code, code_header)); - con_out(" crc: 0x%04X\n", code->crc); - - if (lnofile) { - con_out(" lno:\n"); - con_out(" name: %s\n", lnofile); - con_out(" size: %u (bytes)\n", code_size_debug(code, code_header)); - } - - con_out("\n"); -} - -/* - * Same principle except this one allocates memory and writes the lno(optional) and the dat file - * directly out to allocated memory. Which is actually very useful for the future library support - * we're going to add. - */ -#if 0 -static bool code_write_memory(code_t *code, uint8_t **datmem, size_t *sizedat, uint8_t **lnomem, size_t *sizelno) GMQCC_UNUSED { - prog_header_t code_header; - uint32_t offset = 0; - - if (!datmem) - return false; - - code_create_header(code, &code_header, "<>", "<>"); - - #define WRITE_CHUNK(C,X,S) \ - do { \ - memcpy((void*)(&(*C)[offset]), (const void*)(X), (S)); \ - offset += (S); \ - } while (0) - - /* Calculate size required to store entire file out to memory */ - if (lnomem) { - uint32_t version = 1; - - *sizelno = code_size_debug(code, &code_header); - *lnomem = (uint8_t*)mem_a(*sizelno); - - WRITE_CHUNK(lnomem, "LNOF", 4); - WRITE_CHUNK(lnomem, &version, sizeof(version)); - WRITE_CHUNK(lnomem, &code_header.defs.length, sizeof(code_header.defs.length)); - WRITE_CHUNK(lnomem, &code_header.globals.length, sizeof(code_header.globals.length)); - WRITE_CHUNK(lnomem, &code_header.fields.length, sizeof(code_header.fields.length)); - WRITE_CHUNK(lnomem, &code_header.statements.length, sizeof(code_header.statements.length)); - - /* something went terribly wrong */ - if (offset != *sizelno) { - mem_d(*lnomem); - *sizelno = 0; - return false; - } - offset = 0; - } - - /* Write out the dat */ - *sizedat = code_size_binary(code, &code_header); - *datmem = (uint8_t*)mem_a(*sizedat); - - WRITE_CHUNK(datmem, &code_header, sizeof(prog_header_t)); - WRITE_CHUNK(datmem, code->statements, sizeof(prog_section_statement_t) * vec_size(code->statements)); - WRITE_CHUNK(datmem, code->defs, sizeof(prog_section_def_t) * vec_size(code->defs)); - WRITE_CHUNK(datmem, code->fields, sizeof(prog_section_field_t) * vec_size(code->fields)); - WRITE_CHUNK(datmem, code->functions, sizeof(prog_section_function_t) * vec_size(code->functions)); - WRITE_CHUNK(datmem, code->globals, sizeof(int32_t) * vec_size(code->globals)); - WRITE_CHUNK(datmem, code->chars, 1 * vec_size(code->chars)); - - vec_free(code->statements); - vec_free(code->linenums); - vec_free(code->columnnums); - vec_free(code->defs); - vec_free(code->fields); - vec_free(code->functions); - vec_free(code->globals); - vec_free(code->chars); - - util_htdel(code->string_cache); - mem_d(code); - code_stats("<>", (lnomem) ? "<>" : NULL, code, &code_header); - return true; -} -#endif /*!#if 0 reenable when ready to be used */ -#undef WRITE_CHUNK - -bool code_write(code_t *code, const char *filename, const char *lnofile) { - prog_header_t code_header; - fs_file_t *fp = NULL; - - code_create_header(code, &code_header, filename, lnofile); - - if (lnofile) { - uint32_t version = 1; - - fp = fs_file_open(lnofile, "wb"); - if (!fp) - return false; - - util_endianswap(&version, 1, sizeof(version)); - util_endianswap(code->linenums, vec_size(code->linenums), sizeof(code->linenums[0])); - util_endianswap(code->columnnums, vec_size(code->columnnums), sizeof(code->columnnums[0])); - - if (fs_file_write("LNOF", 4, 1, fp) != 1 || - fs_file_write(&version, sizeof(version), 1, fp) != 1 || - fs_file_write(&code_header.defs.length, sizeof(code_header.defs.length), 1, fp) != 1 || - fs_file_write(&code_header.globals.length, sizeof(code_header.globals.length), 1, fp) != 1 || - fs_file_write(&code_header.fields.length, sizeof(code_header.fields.length), 1, fp) != 1 || - fs_file_write(&code_header.statements.length, sizeof(code_header.statements.length), 1, fp) != 1 || - fs_file_write(code->linenums, sizeof(code->linenums[0]), vec_size(code->linenums), fp) != vec_size(code->linenums) || - fs_file_write(code->columnnums, sizeof(code->columnnums[0]), vec_size(code->columnnums), fp) != vec_size(code->columnnums)) - { - con_err("failed to write lno file\n"); - } - - fs_file_close(fp); - fp = NULL; - } - - fp = fs_file_open(filename, "wb"); - if (!fp) - return false; - - if (1 != fs_file_write(&code_header, sizeof(prog_header_t) , 1 , fp) || - vec_size(code->statements) != fs_file_write(code->statements, sizeof(prog_section_statement_t), vec_size(code->statements), fp) || - vec_size(code->defs) != fs_file_write(code->defs, sizeof(prog_section_def_t) , vec_size(code->defs) , fp) || - vec_size(code->fields) != fs_file_write(code->fields, sizeof(prog_section_field_t) , vec_size(code->fields) , fp) || - vec_size(code->functions) != fs_file_write(code->functions, sizeof(prog_section_function_t) , vec_size(code->functions) , fp) || - vec_size(code->globals) != fs_file_write(code->globals, sizeof(int32_t) , vec_size(code->globals) , fp) || - vec_size(code->chars) != fs_file_write(code->chars, 1 , vec_size(code->chars) , fp)) - { - fs_file_close(fp); - return false; - } - - fs_file_close(fp); - code_stats(filename, lnofile, code, &code_header); - return true; -} - -void code_cleanup(code_t *code) { - vec_free(code->statements); - vec_free(code->linenums); - vec_free(code->columnnums); - vec_free(code->defs); - vec_free(code->fields); - vec_free(code->functions); - vec_free(code->globals); - vec_free(code->chars); - - util_htdel(code->string_cache); - - mem_d(code); -} diff --git a/code.cpp b/code.cpp new file mode 100644 index 0000000..645ad25 --- /dev/null +++ b/code.cpp @@ -0,0 +1,348 @@ +#include +#include "gmqcc.h" + +/* + * We could use the old method of casting to uintptr_t then to void* + * or qcint_t; however, it's incredibly unsafe for two reasons. + * 1) The compilers aliasing optimization can legally make it unstable + * (it's undefined behaviour). + * + * 2) The cast itself depends on fresh storage (newly allocated in which + * ever function is using the cast macros), the contents of which are + * transferred in a way that the obligation to release storage is not + * propagated. + */ +typedef union { + void *enter; + qcint_t leave; +} code_hash_entry_t; + +/* Some sanity macros */ +#define CODE_HASH_ENTER(ENTRY) ((ENTRY).enter) +#define CODE_HASH_LEAVE(ENTRY) ((ENTRY).leave) + +void code_push_statement(code_t *code, prog_section_statement_t *stmt_in, lex_ctx_t ctx) +{ + prog_section_statement_t stmt = *stmt_in; + + if (OPTS_FLAG(TYPELESS_STORES)) { + switch (stmt.opcode) { + case INSTR_LOAD_S: + case INSTR_LOAD_ENT: + case INSTR_LOAD_FLD: + case INSTR_LOAD_FNC: + stmt.opcode = INSTR_LOAD_F; + break; + case INSTR_STORE_S: + case INSTR_STORE_ENT: + case INSTR_STORE_FLD: + case INSTR_STORE_FNC: + stmt.opcode = INSTR_STORE_F; + break; + case INSTR_STOREP_S: + case INSTR_STOREP_ENT: + case INSTR_STOREP_FLD: + case INSTR_STOREP_FNC: + stmt.opcode = INSTR_STOREP_F; + break; + } + } + + + if (OPTS_FLAG(SORT_OPERANDS)) { + uint16_t pair; + + switch (stmt.opcode) { + case INSTR_MUL_F: + case INSTR_MUL_V: + case INSTR_ADD_F: + case INSTR_EQ_F: + case INSTR_EQ_S: + case INSTR_EQ_E: + case INSTR_EQ_FNC: + case INSTR_NE_F: + case INSTR_NE_V: + case INSTR_NE_S: + case INSTR_NE_E: + case INSTR_NE_FNC: + case INSTR_AND: + case INSTR_OR: + case INSTR_BITAND: + case INSTR_BITOR: + if (stmt.o1.u1 < stmt.o2.u1) { + uint16_t a = stmt.o2.u1; + stmt.o1.u1 = stmt.o2.u1; + stmt.o2.u1 = a; + } + break; + + case INSTR_MUL_VF: pair = INSTR_MUL_FV; goto case_pair_gen; + case INSTR_MUL_FV: pair = INSTR_MUL_VF; goto case_pair_gen; + case INSTR_LT: pair = INSTR_GT; goto case_pair_gen; + case INSTR_GT: pair = INSTR_LT; goto case_pair_gen; + case INSTR_LE: pair = INSTR_GT; goto case_pair_gen; + case INSTR_GE: pair = INSTR_LE; + + case_pair_gen: + if (stmt.o1.u1 < stmt.o2.u1) { + uint16_t x = stmt.o1.u1; + stmt.o1.u1 = stmt.o2.u1; + stmt.o2.u1 = x; + stmt.opcode = pair; + } + break; + } + } + + code->statements.push_back(stmt); + code->linenums.push_back(ctx.line); + code->columnnums.push_back(ctx.column); +} + +void code_pop_statement(code_t *code) +{ + code->statements.pop_back(); + code->linenums.pop_back(); + code->columnnums.pop_back(); +} + +void *code_t::operator new(std::size_t bytes) { + return mem_a(bytes); +} + +void code_t::operator delete(void *ptr) { + mem_d(ptr); +} + +code_t::code_t() +{ + static lex_ctx_t empty_ctx = {0, 0, 0}; + static prog_section_function_t empty_function = {0,0,0,0,0,0,0,{0,0,0,0,0,0,0,0}}; + static prog_section_statement_t empty_statement = {0,{0},{0},{0}}; + static prog_section_def_t empty_def = {0, 0, 0}; + + string_cache = util_htnew(OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS) ? 0x100 : 1024); + + // The way progs.dat is suppose to work is odd, there needs to be + // some null (empty) statements, functions, and 28 globals + globals.insert(globals.begin(), 28, 0); + + chars.push_back('\0'); + functions.push_back(empty_function); + + code_push_statement(this, &empty_statement, empty_ctx); + + defs.push_back(empty_def); + fields.push_back(empty_def); +} + +code_t::~code_t() +{ + util_htdel(string_cache); +} + +void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin); + +uint32_t code_genstring(code_t *code, const char *str) { + size_t hash; + code_hash_entry_t existing; + + if (!str) + return 0; + + if (!*str) { + if (!code->string_cached_empty) { + code->string_cached_empty = code->chars.size(); + code->chars.push_back(0); + } + return code->string_cached_empty; + } + + if (OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS)) { + hash = ((unsigned char*)str)[strlen(str)-1]; + CODE_HASH_ENTER(existing) = code_util_str_htgeth(code->string_cache, str, hash); + } else { + hash = util_hthash(code->string_cache, str); + CODE_HASH_ENTER(existing) = util_htgeth(code->string_cache, str, hash); + } + + if (CODE_HASH_ENTER(existing)) + return CODE_HASH_LEAVE(existing); + + CODE_HASH_LEAVE(existing) = code->chars.size(); + code->chars.insert(code->chars.end(), str, str + strlen(str) + 1); + + util_htseth(code->string_cache, str, hash, CODE_HASH_ENTER(existing)); + return CODE_HASH_LEAVE(existing); +} + +qcint_t code_alloc_field (code_t *code, size_t qcsize) +{ + qcint_t pos = (qcint_t)code->entfields; + code->entfields += qcsize; + return pos; +} + +static size_t code_size_generic(code_t *code, prog_header_t *code_header, bool lno) { + size_t size = 0; + if (lno) { + size += 4; /* LNOF */ + size += sizeof(uint32_t); /* version */ + size += sizeof(code_header->defs.length); + size += sizeof(code_header->globals.length); + size += sizeof(code_header->fields.length); + size += sizeof(code_header->statements.length); + size += sizeof(code->linenums[0]) * code->linenums.size(); + size += sizeof(code->columnnums[0]) * code->columnnums.size(); + } else { + size += sizeof(prog_header_t); + size += sizeof(prog_section_statement_t) * code->statements.size(); + size += sizeof(prog_section_def_t) * code->defs.size(); + size += sizeof(prog_section_field_t) * code->fields.size(); + size += sizeof(prog_section_function_t) * code->functions.size(); + size += sizeof(int32_t) * code->globals.size(); + size += 1 * code->chars.size(); + } + return size; +} + +#define code_size_binary(C, H) code_size_generic((C), (H), false) +#define code_size_debug(C, H) code_size_generic((C), (H), true) + +static void code_create_header(code_t *code, prog_header_t *code_header, const char *filename, const char *lnofile) { + size_t i; + + code_header->statements.offset = sizeof(prog_header_t); + code_header->statements.length = code->statements.size(); + code_header->defs.offset = code_header->statements.offset + (sizeof(prog_section_statement_t) * code->statements.size()); + code_header->defs.length = code->defs.size(); + code_header->fields.offset = code_header->defs.offset + (sizeof(prog_section_def_t) * code->defs.size()); + code_header->fields.length = code->fields.size(); + code_header->functions.offset = code_header->fields.offset + (sizeof(prog_section_field_t) * code->fields.size()); + code_header->functions.length = code->functions.size(); + code_header->globals.offset = code_header->functions.offset + (sizeof(prog_section_function_t) * code->functions.size()); + code_header->globals.length = code->globals.size(); + code_header->strings.offset = code_header->globals.offset + (sizeof(int32_t) * code->globals.size()); + code_header->strings.length = code->chars.size(); + code_header->version = 6; + code_header->skip = 0; + + if (OPTS_OPTION_BOOL(OPTION_FORCECRC)) + code_header->crc16 = OPTS_OPTION_U16(OPTION_FORCED_CRC); + else + code_header->crc16 = code->crc; + code_header->entfield = code->entfields; + + if (OPTS_FLAG(DARKPLACES_STRING_TABLE_BUG)) { + /* >= + P */ + code->chars.push_back('\0'); /* > */ + code->chars.push_back('\0'); /* = */ + code->chars.push_back('\0'); /* P */ + } + + /* ensure all data is in LE format */ + util_swap_header(*code_header); + util_swap_statements(code->statements); + util_swap_defs_fields(code->defs); + util_swap_defs_fields(code->fields); + util_swap_functions(code->functions); + util_swap_globals(code->globals); + + if (!OPTS_OPTION_BOOL(OPTION_QUIET)) { + if (lnofile) + con_out("writing '%s' and '%s'...\n", filename, lnofile); + else + con_out("writing '%s'\n", filename); + } + + if (!OPTS_OPTION_BOOL(OPTION_QUIET) && + !OPTS_OPTION_BOOL(OPTION_PP_ONLY)) + { + char buffer[1024]; + con_out("\nOptimizations:\n"); + for (i = 0; i < COUNT_OPTIMIZATIONS; ++i) { + if (opts_optimizationcount[i]) { + util_optimizationtostr(opts_opt_list[i].name, buffer, sizeof(buffer)); + con_out( + " %s: %u\n", + buffer, + (unsigned int)opts_optimizationcount[i] + ); + } + } + } +} + +static void code_stats(const char *filename, const char *lnofile, code_t *code, prog_header_t *code_header) { + if (OPTS_OPTION_BOOL(OPTION_QUIET) || + OPTS_OPTION_BOOL(OPTION_PP_ONLY)) + return; + + con_out("\nFile statistics:\n"); + con_out(" dat:\n"); + con_out(" name: %s\n", filename); + con_out(" size: %u (bytes)\n", code_size_binary(code, code_header)); + con_out(" crc: 0x%04X\n", code->crc); + + if (lnofile) { + con_out(" lno:\n"); + con_out(" name: %s\n", lnofile); + con_out(" size: %u (bytes)\n", code_size_debug(code, code_header)); + } + + con_out("\n"); +} + +bool code_write(code_t *code, const char *filename, const char *lnofile) { + prog_header_t code_header; + FILE *fp = nullptr; + + code_create_header(code, &code_header, filename, lnofile); + + if (lnofile) { + uint32_t version = 1; + + fp = fopen(lnofile, "wb"); + if (!fp) + return false; + + util_endianswap(&version, 1, sizeof(version)); + util_endianswap(&code->linenums[0], code->linenums.size(), sizeof(code->linenums[0])); + util_endianswap(&code->columnnums[0], code->columnnums.size(), sizeof(code->columnnums[0])); + + if (fwrite("LNOF", 4, 1, fp) != 1 || + fwrite(&version, sizeof(version), 1, fp) != 1 || + fwrite(&code_header.defs.length, sizeof(code_header.defs.length), 1, fp) != 1 || + fwrite(&code_header.globals.length, sizeof(code_header.globals.length), 1, fp) != 1 || + fwrite(&code_header.fields.length, sizeof(code_header.fields.length), 1, fp) != 1 || + fwrite(&code_header.statements.length, sizeof(code_header.statements.length), 1, fp) != 1 || + fwrite(&code->linenums[0], sizeof(code->linenums[0]), code->linenums.size(), fp) != code->linenums.size() || + fwrite(&code->columnnums[0], sizeof(code->columnnums[0]), code->columnnums.size(), fp) != code->columnnums.size()) + { + con_err("failed to write lno file\n"); + } + + fclose(fp); + fp = nullptr; + } + + fp = fopen(filename, "wb"); + if (!fp) + return false; + + if (1 != fwrite(&code_header, sizeof(prog_header_t) , 1 , fp) || + code->statements.size() != fwrite(&code->statements[0], sizeof(prog_section_statement_t), code->statements.size(), fp) || + code->defs.size() != fwrite(&code->defs[0], sizeof(prog_section_def_t) , code->defs.size() , fp) || + code->fields.size() != fwrite(&code->fields[0], sizeof(prog_section_field_t) , code->fields.size() , fp) || + code->functions.size() != fwrite(&code->functions[0], sizeof(prog_section_function_t) , code->functions.size() , fp) || + code->globals.size() != fwrite(&code->globals[0], sizeof(int32_t) , code->globals.size() , fp) || + code->chars.size() != fwrite(&code->chars[0], 1 , code->chars.size() , fp)) + { + fclose(fp); + return false; + } + + fclose(fp); + code_stats(filename, lnofile, code, &code_header); + return true; +} diff --git a/conout.c b/conout.c deleted file mode 100644 index c4c285b..0000000 --- a/conout.c +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * 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 -#include "gmqcc.h" - -#define GMQCC_IS_STDOUT(X) ((fs_file_t*)((void*)X) == (fs_file_t*)stdout) -#define GMQCC_IS_STDERR(X) ((fs_file_t*)((void*)X) == (fs_file_t*)stderr) -#define GMQCC_IS_DEFINE(X) (GMQCC_IS_STDERR(X) || GMQCC_IS_STDOUT(X)) - -typedef struct { - fs_file_t *handle_err; - fs_file_t *handle_out; - int color_err; - int color_out; -} con_t; - -/* - * We use standard files as default. These can be changed at any time - * with con_change(F, F) - */ -static con_t console; - -/* - * Enables color on output if supported. - * NOTE: The support for checking colors is NULL. On windows this will - * always work, on *nix it depends if the term has colors. - * - * NOTE: This prevents colored output to piped stdout/err via isatty - * checks. - */ -static void con_enablecolor(void) { - console.color_err = util_isatty(console.handle_err); - console.color_out = util_isatty(console.handle_out); -} - -/* - * Does a write to the handle with the format string and list of - * arguments. This colorizes for windows as well via translate - * step. - */ -static int con_write(fs_file_t *handle, const char *fmt, va_list va) { - return vfprintf((FILE*)handle, fmt, va); -} - -/********************************************************************** - * EXPOSED INTERFACE BEGINS - *********************************************************************/ - -void con_close() { - if (!GMQCC_IS_DEFINE(console.handle_err)) - fs_file_close(console.handle_err); - if (!GMQCC_IS_DEFINE(console.handle_out)) - fs_file_close(console.handle_out); -} - -void con_color(int state) { - if (state) - con_enablecolor(); - else { - console.color_err = 0; - console.color_out = 0; - } -} - -void con_init() { - console.handle_err = (fs_file_t*)stderr; - console.handle_out = (fs_file_t*)stdout; - con_enablecolor(); -} - -void con_reset() { - con_close(); - con_init (); -} - -/* - * This is clever, say you want to change the console to use two - * files for out/err. You pass in two strings, it will properly - * close the existing handles (if they're not std* handles) and - * open them. Now say you want TO use stdout and stderr, this - * allows you to do that so long as you cast them to (char*). - * Say you need stdout for out, but want a file for error, you can - * do this too, just cast the stdout for (char*) and stick to a - * string for the error file. - */ -int con_change(const char *out, const char *err) { - con_close(); - - if (!out) out = (const char *)((!console.handle_out) ? (fs_file_t*)stdout : console.handle_out); - if (!err) err = (const char *)((!console.handle_err) ? (fs_file_t*)stderr : console.handle_err); - - if (GMQCC_IS_DEFINE(out)) { - console.handle_out = (fs_file_t*)(GMQCC_IS_STDOUT(out) ? stdout : stderr); - con_enablecolor(); - } else if (!(console.handle_out = fs_file_open(out, "w"))) return 0; - - if (GMQCC_IS_DEFINE(err)) { - console.handle_err = (fs_file_t*)(GMQCC_IS_STDOUT(err) ? stdout : stderr); - con_enablecolor(); - } else if (!(console.handle_err = fs_file_open(err, "w"))) return 0; - - return 1; -} - -/* - * Defaultizer because stdio.h shouldn't be used anywhere except here - * and inside file.c To prevent mis-match of wrapper-interfaces. - */ -fs_file_t *con_default_out() { - return (fs_file_t*)(console.handle_out = (fs_file_t*)stdout); -} -fs_file_t *con_default_err() { - return (fs_file_t*)(console.handle_err = (fs_file_t*)stderr); -} - -int con_verr(const char *fmt, va_list va) { - return con_write(console.handle_err, fmt, va); -} -int con_vout(const char *fmt, va_list va) { - return con_write(console.handle_out, fmt, va); -} - -/* - * Standard stdout/stderr printf functions used generally where they need - * to be used. - */ -int con_err(const char *fmt, ...) { - va_list va; - int ln = 0; - va_start(va, fmt); - con_verr(fmt, va); - va_end (va); - return ln; -} -int con_out(const char *fmt, ...) { - va_list va; - int ln = 0; - va_start(va, fmt); - con_vout(fmt, va); - va_end (va); - return ln; -} - -/* - * Utility console message writes for lexer contexts. These will allow - * for reporting of file:line based on lexer context, These are used - * heavily in the parser/ir/ast. - */ -static void con_vprintmsg_c(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap, const char *condname) { - /* color selection table */ - static int sel[] = { - CON_WHITE, - CON_CYAN, - CON_RED - }; - - int err = !!(level == LVL_ERROR); - int color = (err) ? console.color_err : console.color_out; - int (*print) (const char *, ...) = (err) ? &con_err : &con_out; - int (*vprint)(const char *, va_list) = (err) ? &con_verr : &con_vout; - - if (color) - print("\033[0;%dm%s:%d:%d: \033[0;%dm%s: \033[0m", CON_CYAN, name, (int)line, (int)column, sel[level], msgtype); - else - print("%s:%d:%d: %s: ", name, (int)line, (int)column, msgtype); - - vprint(msg, ap); - if (condname) - print(" [%s]\n", condname); - else - print("\n"); -} - -void con_vprintmsg(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap) { - con_vprintmsg_c(level, name, line, column, msgtype, msg, ap, NULL); -} - -void con_printmsg(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, ...) { - va_list va; - va_start(va, msg); - con_vprintmsg(level, name, line, column, msgtype, msg, va); - va_end (va); -} - -void con_cvprintmsg(lex_ctx_t ctx, int lvl, const char *msgtype, const char *msg, va_list ap) { - con_vprintmsg(lvl, ctx.file, ctx.line, ctx.column, msgtype, msg, ap); -} - -void con_cprintmsg(lex_ctx_t ctx, int lvl, const char *msgtype, const char *msg, ...) { - va_list va; - va_start(va, msg); - con_cvprintmsg(ctx, lvl, msgtype, msg, va); - va_end (va); -} - -/* General error interface: TODO seperate as part of the compiler front-end */ -size_t compile_errors = 0; -size_t compile_warnings = 0; -size_t compile_Werrors = 0; -static lex_ctx_t first_werror; - -void compile_show_werrors() -{ - con_cprintmsg(first_werror, LVL_ERROR, "first warning", "was here"); -} - -void vcompile_error(lex_ctx_t ctx, const char *msg, va_list ap) -{ - ++compile_errors; - con_cvprintmsg(ctx, LVL_ERROR, "error", msg, ap); -} - -void compile_error(lex_ctx_t ctx, const char *msg, ...) -{ - va_list ap; - va_start(ap, msg); - vcompile_error(ctx, msg, ap); - va_end(ap); -} - -bool GMQCC_WARN vcompile_warning(lex_ctx_t ctx, int warntype, const char *fmt, va_list ap) -{ - const char *msgtype = "warning"; - int lvl = LVL_WARNING; - char warn_name[1024]; - - if (!OPTS_WARN(warntype)) - return false; - - warn_name[0] = '-'; - warn_name[1] = 'W'; - (void)util_strtononcmd(opts_warn_list[warntype].name, warn_name+2, sizeof(warn_name)-2); - - ++compile_warnings; - if (OPTS_WERROR(warntype)) { - if (!compile_Werrors) - first_werror = ctx; - ++compile_Werrors; - msgtype = "Werror"; - if (OPTS_FLAG(BAIL_ON_WERROR)) { - msgtype = "error"; - ++compile_errors; - } - lvl = LVL_ERROR; - } - - con_vprintmsg_c(lvl, ctx.file, ctx.line, ctx.column, msgtype, fmt, ap, warn_name); - - return OPTS_WERROR(warntype) && OPTS_FLAG(BAIL_ON_WERROR); -} - -bool GMQCC_WARN compile_warning(lex_ctx_t ctx, int warntype, const char *fmt, ...) -{ - bool r; - va_list ap; - va_start(ap, fmt); - r = vcompile_warning(ctx, warntype, fmt, ap); - va_end(ap); - return r; -} diff --git a/conout.cpp b/conout.cpp new file mode 100644 index 0000000..5eb81db --- /dev/null +++ b/conout.cpp @@ -0,0 +1,226 @@ +#include +#include "gmqcc.h" + +#define GMQCC_IS_STDOUT(X) ((X) == stdout) +#define GMQCC_IS_STDERR(X) ((X) == stderr) +#define GMQCC_IS_DEFINE(X) (GMQCC_IS_STDERR(X) || GMQCC_IS_STDOUT(X)) + +struct con_t { + FILE *handle_err; + FILE *handle_out; + int color_err; + int color_out; +}; + +static con_t console; + +/* + * Enables color on output if supported. + * NOTE: The support for checking colors is nullptr. On windows this will + * always work, on *nix it depends if the term has colors. + * + * NOTE: This prevents colored output to piped stdout/err via isatty + * checks. + */ +static void con_enablecolor(void) { + console.color_err = util_isatty(console.handle_err); + console.color_out = util_isatty(console.handle_out); +} + +/* + * Does a write to the handle with the format string and list of + * arguments. This colorizes for windows as well via translate + * step. + */ +static int con_write(FILE *handle, const char *fmt, va_list va) { + return vfprintf(handle, fmt, va); +} + +/********************************************************************** + * EXPOSED INTERFACE BEGINS + *********************************************************************/ + +void con_close() { + if (!GMQCC_IS_DEFINE(console.handle_err)) + fclose(console.handle_err); + if (!GMQCC_IS_DEFINE(console.handle_out)) + fclose(console.handle_out); +} + +void con_color(int state) { + if (state) + con_enablecolor(); + else { + console.color_err = 0; + console.color_out = 0; + } +} + +void con_init() { + console.handle_err = stderr; + console.handle_out = stdout; + con_enablecolor(); +} + +void con_reset() { + con_close(); + con_init(); +} + +/* + * Defaultizer because stdio.h shouldn't be used anywhere except here + * and inside file.c To prevent mis-match of wrapper-interfaces. + */ +FILE *con_default_out() { + return console.handle_out = stdout; +} + +FILE *con_default_err() { + return console.handle_err = stderr; +} + +int con_verr(const char *fmt, va_list va) { + return con_write(console.handle_err, fmt, va); +} +int con_vout(const char *fmt, va_list va) { + return con_write(console.handle_out, fmt, va); +} + +/* + * Standard stdout/stderr printf functions used generally where they need + * to be used. + */ +int con_err(const char *fmt, ...) { + va_list va; + int ln = 0; + va_start(va, fmt); + con_verr(fmt, va); + va_end(va); + return ln; +} +int con_out(const char *fmt, ...) { + va_list va; + int ln = 0; + va_start(va, fmt); + con_vout(fmt, va); + va_end (va); + return ln; +} + +/* + * Utility console message writes for lexer contexts. These will allow + * for reporting of file:line based on lexer context, These are used + * heavily in the parser/ir/ast. + */ +static void con_vprintmsg_c(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap, const char *condname) { + /* color selection table */ + static int sel[] = { + CON_WHITE, + CON_CYAN, + CON_RED + }; + + int err = !!(level == LVL_ERROR); + int color = (err) ? console.color_err : console.color_out; + int (*print) (const char *, ...) = (err) ? &con_err : &con_out; + int (*vprint)(const char *, va_list) = (err) ? &con_verr : &con_vout; + + if (color) + print("\033[0;%dm%s:%d:%d: \033[0;%dm%s: \033[0m", CON_CYAN, name, (int)line, (int)column, sel[level], msgtype); + else + print("%s:%d:%d: %s: ", name, (int)line, (int)column, msgtype); + + vprint(msg, ap); + if (condname) + print(" [%s]\n", condname); + else + print("\n"); +} + +void con_vprintmsg(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap) { + con_vprintmsg_c(level, name, line, column, msgtype, msg, ap, nullptr); +} + +void con_printmsg(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, ...) { + va_list va; + va_start(va, msg); + con_vprintmsg(level, name, line, column, msgtype, msg, va); + va_end (va); +} + +void con_cvprintmsg(lex_ctx_t ctx, int lvl, const char *msgtype, const char *msg, va_list ap) { + con_vprintmsg(lvl, ctx.file, ctx.line, ctx.column, msgtype, msg, ap); +} + +void con_cprintmsg(lex_ctx_t ctx, int lvl, const char *msgtype, const char *msg, ...) { + va_list va; + va_start(va, msg); + con_cvprintmsg(ctx, lvl, msgtype, msg, va); + va_end (va); +} + +/* General error interface: TODO seperate as part of the compiler front-end */ +size_t compile_errors = 0; +size_t compile_warnings = 0; +size_t compile_Werrors = 0; +static lex_ctx_t first_werror; + +void compile_show_werrors() +{ + con_cprintmsg(first_werror, LVL_ERROR, "first warning", "was here"); +} + +void vcompile_error(lex_ctx_t ctx, const char *msg, va_list ap) +{ + ++compile_errors; + con_cvprintmsg(ctx, LVL_ERROR, "error", msg, ap); +} + +void compile_error_(lex_ctx_t ctx, const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + vcompile_error(ctx, msg, ap); + va_end(ap); +} + +bool GMQCC_WARN vcompile_warning(lex_ctx_t ctx, int warntype, const char *fmt, va_list ap) +{ + const char *msgtype = "warning"; + int lvl = LVL_WARNING; + char warn_name[1024]; + + if (!OPTS_WARN(warntype)) + return false; + + warn_name[0] = '-'; + warn_name[1] = 'W'; + (void)util_strtononcmd(opts_warn_list[warntype].name, warn_name+2, sizeof(warn_name)-2); + + ++compile_warnings; + if (OPTS_WERROR(warntype)) { + if (!compile_Werrors) + first_werror = ctx; + ++compile_Werrors; + msgtype = "Werror"; + if (OPTS_FLAG(BAIL_ON_WERROR)) { + msgtype = "error"; + ++compile_errors; + } + lvl = LVL_ERROR; + } + + con_vprintmsg_c(lvl, ctx.file, ctx.line, ctx.column, msgtype, fmt, ap, warn_name); + + return OPTS_WERROR(warntype) && OPTS_FLAG(BAIL_ON_WERROR); +} + +bool GMQCC_WARN compile_warning_(lex_ctx_t ctx, int warntype, const char *fmt, ...) +{ + bool r; + va_list ap; + va_start(ap, fmt); + r = vcompile_warning(ctx, warntype, fmt, ap); + va_end(ap); + return r; +} diff --git a/correct.c b/correct.c deleted file mode 100644 index 1f7a381..0000000 --- a/correct.c +++ /dev/null @@ -1,548 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * Dale Weiler - * Wolfgang Bumiller - * - * 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 -#include "gmqcc.h" - -/* - * This is a very clever method for correcting mistakes in QuakeC code - * most notably when invalid identifiers are used or inproper assignments; - * we can proprly lookup in multiple dictonaries (depening on the rules - * of what the task is trying to acomplish) to find the best possible - * match. - * - * - * A little about how it works, and probability theory: - * - * When given an identifier (which we will denote I), we're essentially - * just trying to choose the most likely correction for that identifier. - * (the actual "correction" can very well be the identifier itself). - * There is actually no way to know for sure that certian identifers - * such as "lates", need to be corrected to "late" or "latest" or any - * other permutations that look lexically the same. This is why we - * must advocate the usage of probabilities. This means that instead of - * just guessing, instead we're trying to find the correction for C, - * out of all possible corrections that maximizes the probability of C - * for the original identifer I. - * - * Thankfully there exists some theroies for probalistic interpretations - * of data. Since we're operating on two distictive intepretations, the - * transposition from I to C. We need something that can express how much - * degree of I should rationally change to become C. this is called the - * Bayesian interpretation. You can read more about it from here: - * http://www.celiagreen.com/charlesmccreery/statistics/bayestutorial.pdf - * (which is probably the only good online documentation for bayes theroy - * no lie. Everything else just sucks ..) - * - * Bayes' Thereom suggests something like the following: - * AC P(I|C) P(C) / P(I) - * - * However since P(I) is the same for every possibility of I, we can - * completley ignore it giving just: - * AC P(I|C) P(C) - * - * This greatly helps visualize how the parts of the expression are performed - * there is essentially three, from right to left we perform the following: - * - * 1: P(C), the probability that a proposed correction C will stand on its - * own. This is called the language model. - * - * 2: P(I|C), the probability that I would be used, when the programmer - * really meant C. This is the error model. - * - * 3: AC, the control mechanisim, an enumerator if you will, one that - * enumerates all feasible values of C, to determine the one that - * gives the greatest probability score. - * - * In reality the requirement for a more complex expression involving - * two seperate models is considerably a waste. But one must recognize - * that P(C|I) is already conflating two factors. It's just much simpler - * to seperate the two models and deal with them explicitaly. To properly - * estimate P(C|I) you have to consider both the probability of C and - * probability of the transposition from C to I. It's simply much more - * cleaner, and direct to seperate the two factors. - * - * Research tells us that 80% to 95% of all spelling errors have an edit - * distance no greater than one. Knowing this we can optimize for most - * cases of mistakes without taking a performance hit. Which is what we - * base longer edit distances off of. Opposed to the original method of - * I had concieved of checking everything. - * - * A little information on additional algorithms used: - * - * Initially when I implemented this corrector, it was very slow. - * Need I remind you this is essentially a brute force attack on strings, - * and since every transformation requires dynamic memory allocations, - * you can easily imagine where most of the runtime conflated. Yes - * It went right to malloc. More than THREE MILLION malloc calls are - * performed for an identifier about 16 bytes long. This was such a - * shock to me. A forward allocator (or as some call it a bump-point - * allocator, or just a memory pool) was implemented. To combat this. - * - * But of course even other factors were making it slow. Initially - * this used a hashtable. And hashtables have a good constant lookup - * time complexity. But the problem wasn't in the hashtable, it was - * in the hashing (despite having one of the fastest hash functions - * known). Remember those 3 million mallocs? Well for every malloc - * there is also a hash. After 3 million hashes .. you start to get - * very slow. To combat this I had suggested burst tries to Blub. - * The next day he had implemented them. Sure enough this brought - * down the runtime by a factor > 100% - * - * The trie initially was designed to work on all strings, but later it - * became aparent that not only was this not a requirement. It was also - * slowing down get/sets' for the trie. To fully understand, only - * correct_alpha needs to be understood by the trie system, knowing this - * We can combat the slowness using a very clever but evil optimization. - * By Setting a fixed sized amount of branches for the trie using a - * char-to-index map into the branches. We've complelty made the trie - * accesses entierly constant in lookup time. No really, a lookup is - * literally trie[str[0]] [str[1]] [2] .... .value. - * - * - * Future Work (If we really need it) - * - * Currently we can only distinguish one source of error in the - * language model we use. This could become an issue for identifiers - * that have close colliding rates, e.g colate->coat yields collate. - * - * Currently the error model has been fairly trivial, the smaller the - * edit distance the smaller the error. This usually causes some un- - * expected problems. e.g reciet->recite yields recipt. For QuakeC - * this could become a problem when lots of identifiers are involved. - */ - - -#define CORRECT_POOL_SIZE (128*1024*1024) -/* - * A forward allcator for the corrector. This corrector requires a lot - * of allocations. This forward allocator combats all those allocations - * and speeds us up a little. It also saves us space in a way since each - * allocation isn't wasting a little header space for when NOTRACK isn't - * defined. - */ -static unsigned char **correct_pool_data = NULL; -static unsigned char *correct_pool_this = NULL; -static size_t correct_pool_addr = 0; - -static GMQCC_INLINE void correct_pool_new(void) { - correct_pool_addr = 0; - correct_pool_this = (unsigned char *)mem_a(CORRECT_POOL_SIZE); - - vec_push(correct_pool_data, correct_pool_this); -} - -static GMQCC_INLINE void *correct_pool_alloc(size_t bytes) { - void *data; - if (correct_pool_addr + bytes>= CORRECT_POOL_SIZE) - correct_pool_new(); - - data = (void*)correct_pool_this; - correct_pool_this += bytes; - correct_pool_addr += bytes; - return data; -} - -static GMQCC_INLINE void correct_pool_delete(void) { - size_t i; - for (i = 0; i < vec_size(correct_pool_data); ++i) - mem_d(correct_pool_data[i]); - - correct_pool_data = NULL; - correct_pool_this = NULL; - correct_pool_addr = 0; -} - - -static GMQCC_INLINE char *correct_pool_claim(const char *data) { - char *claim = util_strdup(data); - return claim; -} - -/* - * _ is valid in identifiers. I've yet to implement numerics however - * because they're only valid after the first character is of a _, or - * alpha character. - */ -static const char correct_alpha[] = "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "_"; /* TODO: Numbers ... */ - -static const size_t correct_alpha_index[0x80] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 52, - 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0 -}; - -/* - * A fast space efficent trie for a dictionary of identifiers. This is - * faster than a hashtable for one reason. A hashtable itself may have - * fast constant lookup time, but the hash itself must be very fast. We - * have one of the fastest hash functions for strings, but if you do a - * lost of hashing (which we do, almost 3 million hashes per identifier) - * a hashtable becomes slow. - */ -correct_trie_t* correct_trie_new() { - correct_trie_t *t = (correct_trie_t*)mem_a(sizeof(correct_trie_t)); - t->value = NULL; - t->entries = NULL; - return t; -} - -static GMQCC_INLINE void correct_trie_del_sub(correct_trie_t *t) { - size_t i; - if (!t->entries) - return; - for (i = 0; i < sizeof(correct_alpha)-1; ++i) { - correct_trie_del_sub(&t->entries[i]); - } - mem_d(t->entries); -} - -static GMQCC_INLINE void correct_trie_del(correct_trie_t *t) { - size_t i; - if (t->entries) { - for (i = 0; i < sizeof(correct_alpha)-1; ++i) - correct_trie_del_sub(&t->entries[i]); - mem_d(t->entries); - } - mem_d(t); -} - -static GMQCC_INLINE void* correct_trie_get(const correct_trie_t *t, const char *key) { - const unsigned char *data = (const unsigned char*)key; - - while (*data) { - if (!t->entries) - return NULL; - t = t->entries + correct_alpha_index[*data]; - ++data; - } - return t->value; -} - -static GMQCC_INLINE void correct_trie_set(correct_trie_t *t, const char *key, void * const value) { - const unsigned char *data = (const unsigned char*)key; - while (*data) { - if (!t->entries) { - t->entries = (correct_trie_t*)mem_a(sizeof(correct_trie_t)*(sizeof(correct_alpha)-1)); - memset(t->entries, 0, sizeof(correct_trie_t)*(sizeof(correct_alpha)-1)); - } - t = t->entries + correct_alpha_index[*data]; - ++data; - } - t->value = value; -} - - -/* - * Implementation of the corrector algorithm commences. A very efficent - * brute-force attack (thanks to tries and mempool :-)). - */ -static GMQCC_INLINE size_t *correct_find(correct_trie_t *table, const char *word) { - return (size_t*)correct_trie_get(table, word); -} - -static GMQCC_INLINE bool correct_update(correct_trie_t* *table, const char *word) { - size_t *data = correct_find(*table, word); - if (!data) - return false; - - (*data)++; - return true; -} - -void correct_add(correct_trie_t* table, size_t ***size, const char *ident) { - size_t *data = NULL; - const char *add = ident; - - if (!correct_update(&table, add)) { - data = (size_t*)mem_a(sizeof(size_t)); - *data = 1; - - vec_push((*size), data); - correct_trie_set(table, add, data); - } -} - -void correct_del(correct_trie_t* dictonary, size_t **data) { - size_t i; - const size_t vs = vec_size(data); - - for (i = 0; i < vs; i++) - mem_d(data[i]); - - vec_free(data); - correct_trie_del(dictonary); -} - -/* - * correcting logic for the following forms of transformations: - * 1) deletion - * 2) transposition - * 3) alteration - * 4) insertion - * - * These functions could take an additional size_t **size paramater - * and store back the results of their new length in an array that - * is the same as **array for the memcmp in correct_exists. I'm just - * not able to figure out how to do that just yet. As my brain is - * not in the mood to figure out that logic. This is a reminder to - * do it, or for someone else to :-) correct_edit however would also - * need to take a size_t ** to carry it along (would all the argument - * overhead be worth it?) - */ -static GMQCC_INLINE size_t correct_deletion(const char *ident, char **array) { - size_t itr = 0; - const size_t len = strlen(ident); - - for (; itr < len; itr++) { - char *a = (char*)correct_pool_alloc(len+1); - memcpy(a, ident, itr); - memcpy(a + itr, ident + itr + 1, len - itr); - array[itr] = a; - } - - return itr; -} - -static GMQCC_INLINE size_t correct_transposition(const char *ident, char **array) { - size_t itr = 0; - const size_t len = strlen(ident); - - for (; itr < len - 1; itr++) { - char tmp; - char *a = (char*)correct_pool_alloc(len+1); - memcpy(a, ident, len+1); - tmp = a[itr]; - a[itr ] = a[itr+1]; - a[itr+1] = tmp; - array[itr] = a; - } - - return itr; -} - -static GMQCC_INLINE size_t correct_alteration(const char *ident, char **array) { - size_t itr = 0; - size_t jtr = 0; - size_t ktr = 0; - const size_t len = strlen(ident); - - for (; itr < len; itr++) { - for (jtr = 0; jtr < sizeof(correct_alpha)-1; jtr++, ktr++) { - char *a = (char*)correct_pool_alloc(len+1); - memcpy(a, ident, len+1); - a[itr] = correct_alpha[jtr]; - array[ktr] = a; - } - } - - return ktr; -} - -static GMQCC_INLINE size_t correct_insertion(const char *ident, char **array) { - size_t itr = 0; - size_t jtr = 0; - const size_t len = strlen(ident); - - for (; itr <= len; itr++) { - for (jtr = 0; jtr < sizeof(correct_alpha)-1; jtr++) { - char *a = (char*)correct_pool_alloc(len+2); - memcpy(a, ident, itr); - memcpy(a + itr + 1, ident + itr, len - itr + 1); - a[itr] = correct_alpha[jtr]; - array[itr * (sizeof(correct_alpha)-1) + jtr] = a; - } - } - - return (len+1)*(sizeof(correct_alpha)-1); -} - -static GMQCC_INLINE size_t correct_size(const char *ident) { - /* - * deletion = len - * transposition = len - 1 - * alteration = len * sizeof(correct_alpha) - * insertion = (len + 1) * sizeof(correct_alpha) - */ - - register size_t len = strlen(ident); - return (len) + (len - 1) + (len * (sizeof(correct_alpha)-1)) + ((len + 1) * (sizeof(correct_alpha)-1)); -} - -static GMQCC_INLINE char **correct_edit(const char *ident, size_t **lens) { - size_t next; - size_t size = correct_size(ident); - char **find = (char**)correct_pool_alloc(size * sizeof(char*)); - - if (!find || !(*lens = (size_t*)correct_pool_alloc(size * sizeof(size_t)))) - return NULL; - - next = correct_deletion (ident, find); - next += correct_transposition(ident, find+next); - next += correct_alteration (ident, find+next); - /*****/ correct_insertion (ident, find+next); - - /* precompute lengths */ - for (next = 0; next < size; next++) - (*lens)[next] = strlen(find[next]); - - return find; -} - -static GMQCC_INLINE int correct_exist(char **array, register size_t *sizes, size_t rows, char *ident, register size_t len) { - size_t itr; - for (itr = 0; itr < rows; itr++) { - /* - * We can save tons of calls to memcmp if we simply ignore comparisions - * that we know cannot contain the same length. - */ - if (sizes[itr] == len && !memcmp(array[itr], ident, len)) - return 1; - } - - return 0; -} - -static GMQCC_INLINE char **correct_known_resize(char **res, size_t *allocated, size_t size) { - size_t oldallocated = *allocated; - char **out; - if (size < oldallocated) - return res; - - out = (char**)correct_pool_alloc(sizeof(*res) * oldallocated + 32); - memcpy(out, res, sizeof(*res) * oldallocated); - - *allocated += 32; - return out; -} - -static char **correct_known(correction_t *corr, correct_trie_t* table, char **array, size_t rows, size_t *next) { - size_t itr = 0; - size_t jtr = 0; - size_t len = 0; - size_t row = 0; - size_t nxt = 8; - char **res = (char**)correct_pool_alloc(sizeof(char *) * nxt); - char **end = NULL; - size_t *bit = NULL; - - for (; itr < rows; itr++) { - if (!array[itr][0]) - continue; - if (vec_size(corr->edits) > itr+1) { - end = corr->edits[itr+1]; - bit = corr->lens [itr+1]; - } else { - end = correct_edit(array[itr], &bit); - vec_push(corr->edits, end); - vec_push(corr->lens, bit); - } - row = correct_size(array[itr]); - - for (jtr = 0; jtr < row; jtr++) { - if (correct_find(table, end[jtr]) && !correct_exist(res, bit, len, end[jtr], bit[jtr])) { - res = correct_known_resize(res, &nxt, len+1); - res[len++] = end[jtr]; - } - } - } - - *next = len; - return res; -} - -static GMQCC_INLINE char *correct_maximum(correct_trie_t* table, char **array, size_t rows) { - char *str = NULL; - size_t *itm = NULL; - size_t itr = 0; - size_t top = 0; - - for (; itr < rows; itr++) { - if ((itm = correct_find(table, array[itr])) && (*itm > top)) { - top = *itm; - str = array[itr]; - } - } - - return str; -} - -/* - * This is the exposed interface: - * takes a table for the dictonary a vector of sizes (used for internal - * probability calculation), and an identifier to "correct". - */ -void correct_init(correction_t *c) -{ - correct_pool_new(); - c->edits = NULL; - c->lens = NULL; -} - -void correct_free(correction_t *c) -{ - vec_free(c->edits); - vec_free(c->lens); - correct_pool_delete(); -} - -char *correct_str(correction_t *corr, correct_trie_t* table, const char *ident) { - char **e1 = NULL; - char **e2 = NULL; - char *e1ident = NULL; - char *e2ident = NULL; - size_t e1rows = 0; - size_t e2rows = 0; - size_t *bits = NULL; - - /* needs to be allocated for free later */ - if (correct_find(table, ident)) - return correct_pool_claim(ident); - - if ((e1rows = correct_size(ident))) { - if (vec_size(corr->edits) > 0) - e1 = corr->edits[0]; - else { - e1 = correct_edit(ident, &bits); - vec_push(corr->edits, e1); - vec_push(corr->lens, bits); - } - - if ((e1ident = correct_maximum(table, e1, e1rows))) - return correct_pool_claim(e1ident); - } - - e2 = correct_known(corr, table, e1, e1rows, &e2rows); - if (e2rows && ((e2ident = correct_maximum(table, e2, e2rows)))) - return correct_pool_claim(e2ident); - - - return util_strdup(ident); -} diff --git a/distro/Makefile b/distro/Makefile deleted file mode 100644 index 3436cca..0000000 --- a/distro/Makefile +++ /dev/null @@ -1,92 +0,0 @@ -DROPBOX := dropbox_uploader.sh -UNAME := $(shell uname -m) -DOWNLOAD:= ../doc/html/download.c -BRANCH := $(shell git rev-parse --abbrev-ref HEAD) -ifneq ($(shell uname -m), x86_64) - $(error Cannot build packages without an x86_64 capable CPU) -endif - -.NOTPARALLEL: base -.NOTPARALLEL: upload - -HEADER=\e[5;32;40m -RESET=\e[0;37;40m -INFO=\e[5;33;40m - -base: - @echo -e "\n$(HEADER)Building Debian packages ...$(RESET)" - @echo -e " $(INFO)=> building 64-bit package$(RESET)" - @$(MAKE) -C deb/ >/dev/null - @echo -e " $(INFO)=> building 32-bit package$(RESET)" - @$(MAKE) -C deb/ CARCH=i686 OPTIONAL_CFLAGS=-m32 OPTIONAL_LDFLAGS=-m32 >/dev/null - - @echo -e "\n$(HEADER)Building ArchLinux packages ...$(RESET)" - @echo -e " $(INFO)=> building 64-bit package$(RESET)" - @$(MAKE) -C archlinux/this/ >/dev/null - @echo -e " $(INFO)=> building 32-bit package$(RESET)" - @$(MAKE) -C archlinux/this/ CARCH=i686 OPTIONAL_CFLAGS=-m32 OPTIONAL_LDFLAGS=-m32 >/dev/null - - @echo -e "\n$(HEADER)Building ArchBSD packages ...$(RESET)" - @echo -e " $(INFO)=> building 64-bit package$(RESET)" - @$(MAKE) -C archbsd/this/ >/dev/null - @echo -e " $(INFO)=> building 32-bit package$(RESET)" - @$(MAKE) -C archbsd/this/ CARCH=i686 OPTIONAL_CFLAGS=-m32 OPTIONAL_LDFLAGS=-m32 >/dev/null - - @echo -e "\n$(HEADER)Building Slackware packages ...$(RESET)" - @echo -e " $(INFO)=> building 64-bit package$(RESET)" - @$(MAKE) -C slackware/this/ >/dev/null - @echo -e " $(INFO)=> building 32-bit package$(RESET)" - @$(MAKE) -C slackware/this/ CARCH=i686 OPTIONAL_CFLAGS=-m32 OPTIONAL_LDFLAGS=-m32 >/dev/null - - @echo -e "\n$(HEADER)Building Fedora packages ...$(RESET)" - @echo -e " $(INFO)=> building 64-bit package$(RESET)" - @$(MAKE) -C fedora/this/ >/dev/null - - @echo -e "\n\$(HEADER)Building Windows packages ...$(RESET)" - @echo -e " $(INFO)=> building 64-bit package$(RESET)" - @$(MAKE) -C win64/ >/dev/null - @echo -e " $(INFO)=> building 32-bit package$(RESET)" - @$(MAKE) -C win32/ >/dev/null - - @rm -rf pkgs/ - @mkdir pkgs/ - @mv deb/*.deb ./pkgs/ - @mv archlinux/this/*pkg.tar.xz ./pkgs/ - @mv archbsd/this/*pkg.tar.xz ./pkgs/ - @mv win32/*.zip ./pkgs/ - @mv win64/*.zip ./pkgs/ - @mv slackware/this/*.txz ./pkgs/ - @mv fedora/this/*.rpm ./pkgs/ - - @echo -e "\n\n$(HEADER)Completed:$(RESET)" - @find ./pkgs/ -type f -regex ".*/.*\.\(xz\|deb\|zip\|txz\|rpm\)" -exec echo -e " $(INFO)=>$(RESET) {}" \; - -upload: - @echo "APPKEY:76vh3q42hnvmzm3" > dropbox_config - @echo "APPSECRET:tmeecht2cmh72xa" >> dropbox_config - @echo "ACCESS_LEVEL:sandbox" >> dropbox_config - @echo "OAUTH_ACCESS_TOKEN:w0bxzf0dft8edfq" >> dropbox_config - @echo "OAUTH_ACCESS_TOKEN_SECRET:9vosx7x8gy4kgjk" >> dropbox_config - @wget -q "http://raw.github.com/andreafabrizi/Dropbox-Uploader/master/dropbox_uploader.sh" - @chmod +x dropbox_uploader.sh - @sed -i -e "s/~\/.dropbox_uploader/.\/dropbox_config/g" $$(basename $(DROPBOX)) - @find ./pkgs -type f -regex ".*/.*\.\(xz\|deb\|zip\|txz\|rpm\)" -exec ./$$(basename $(DROPBOX)) upload {} \; - @rm dropbox_config dropbox_uploader.sh - -website: - $(CC) $(DOWNLOAD) -o html.gen - @./html.gen ../ - @rm html.gen - @git stash - @git checkout gh-pages - @rm -f ../download.html - @mv -f download.html ../download.html - @cd ..; git add download.html; git commit -m 'update download page'; git push origin gh-pages; - @git checkout $(BRANCH) - @git stash apply - -clean: - @rm -rf pkgs/ - @rm -f *.html - -all: base upload diff --git a/distro/archbsd/git/PKGBUILD b/distro/archbsd/git/PKGBUILD deleted file mode 100644 index f1ec819..0000000 --- a/distro/archbsd/git/PKGBUILD +++ /dev/null @@ -1,55 +0,0 @@ -# Contributor: matthiaskrgr -# Contributor: Wolfgang Bumiller - -pkgname=gmqcc-git -pkgver=20131031 -pkgrel=1 -pkgdesc="An Improved Quake C Compiler" -arch=('i686' 'x86_64') -depends=() -conflicts=('gmqcc') -provides=('gmqcc=0.3.5') -makedepends=('git') -url="https://github.com/graphitemaster/gmqcc.git" -license=('MIT') - -_gitroot="git://github.com/graphitemaster/gmqcc.git" -_gitname="gmqcc" - -build() { - cd $srcdir - msg "Connecting to the GIT server..." - if [[ -d $srcdir/$_gitname ]] ; then - cd $_gitname - msg "Removing build files..." - git clean -dfx - msg "Updating..." - git pull --no-tags - msg "The local files are updated." - else - msg "Cloning..." - git clone $_gitroot $_gitname --depth 1 - msg "Clone done." - fi - - msg "Starting compilation..." - cd "$srcdir"/"$_gitname" - - msg "Compiling..." - gmake -} - -check() { - cd "$srcdir"/"$_gitname" - gmake check -} - -package() { - cd "$srcdir"/"$_gitname" - msg "Compiling and installing to pkgdir this time..." - gmake install DESTDIR=$pkgdir PREFIX=/usr - msg "Compiling done." - - install -dm755 ${pkgdir}/usr/share/licenses/gmqcc - install -m644 LICENSE ${pkgdir}/usr/share/licenses/gmqcc/LICENSE -} diff --git a/distro/archbsd/release/PKGBUILD b/distro/archbsd/release/PKGBUILD deleted file mode 100644 index a57846c..0000000 --- a/distro/archbsd/release/PKGBUILD +++ /dev/null @@ -1,38 +0,0 @@ -# Contributor: matthiaskrgr -# Contributor: Wolfgang Bumiller - -pkgname=gmqcc -pkgver=0.3.5 -pkgrel=1 -pkgdesc="An Improved Quake C Compiler" -arch=('i686' 'x86_64') -depends=() -url="https://github.com/graphitemaster/gmqcc.git" -license=('MIT') -source=(gmqcc-$pkgver.zip::https://github.com/graphitemaster/gmqcc/zipball/$pkgver) -sha1sums=('69085478f48f04eefbd2a088c1bd8c266b59f952') - -_gitname=graphitemaster-gmqcc-7f2b206/ - -build() { - msg "Starting compilation..." - cd "$srcdir"/"$_gitname" - - msg "Compiling..." - gmake -} - -check() { - cd "$srcdir"/"$_gitname" - gmake check -} - -package() { - cd "$srcdir"/"$_gitname" - msg "Compiling and installing to pkgdir this time..." - gmake install DESTDIR=$pkgdir PREFIX=/usr - msg "Compiling done." - - install -dm755 ${pkgdir}/usr/share/licenses/gmqcc - install -m644 LICENSE ${pkgdir}/usr/share/licenses/gmqcc/LICENSE -} diff --git a/distro/archbsd/this/Makefile b/distro/archbsd/this/Makefile deleted file mode 100644 index 6bfe650..0000000 --- a/distro/archbsd/this/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -all: - $(MAKE) -f ../../archlinux/this/Makefile \ - LIBC_DEPEND=libc \ - DESTDIR=distro/archbsd/this \ - SUFFIX=archbsd diff --git a/distro/archlinux/git/PKGBUILD b/distro/archlinux/git/PKGBUILD deleted file mode 100644 index 7fbe857..0000000 --- a/distro/archlinux/git/PKGBUILD +++ /dev/null @@ -1,60 +0,0 @@ -# Contributor: matthiaskrgr - -pkgname=gmqcc-git -pkgver=0.3.5 -pkgver(){ - cd gmqcc - git describe --tags | sed -e 's/^gmqcc\-//' -e 's/-/./g' -} -pkgrel=1 -pkgdesc="An Improved Quake C Compiler" -arch=('i686' 'x86_64') -depends=('glibc') -conflicts=('gmqcc') -provides=('gmqcc=0.3.5') -makedepends=('git') -url="https://github.com/graphitemaster/gmqcc.git" -license=('MIT') -source=('gmqcc::git://github.com/graphitemaster/gmqcc.git') -sha1sums=('SKIP') - - -build() { - msg "Starting compilation..." - cd "$srcdir"/"gmqcc" - - msg "Compiling..." - make -} - -check() { - cd "$srcdir"/"gmqcc" - make check -} - -package() { - cd "$srcdir"/"gmqcc" - msg "Compiling and installing to pkgdir this time..." - make install DESTDIR=$pkgdir PREFIX=/usr - msg "Compiling done." - - install -dm755 ${pkgdir}/usr/share/geany - install -m644 syntax/geany/filetypes.qc \ - ${pkgdir}/usr/share/geany/filetypes.qc - - install -dm755 ${pkgdir}/usr/share/gtksourceview-3.0/language-specs - install -m644 syntax/gtksourceview/qc.lang \ - ${pkgdir}/usr/share/gtksourceview-3.0/language-specs/qc.lang - - install -dm755 ${pkgdir}/usr/share/apps/katepart/syntax - install -m644 syntax/kate/qc.xml \ - ${pkgdir}/usr/share/apps/katepart/syntax/qc.xml - - install -dm755 ${pkgdir}/usr/share/nano - install -m644 syntax/nano/qc.nanorc \ - ${pkgdir}/usr/share/nano/qc.nanorc - - install -dm755 ${pkgdir}/usr/share/licenses/gmqcc - install -m644 LICENSE \ - ${pkgdir}/usr/share/licenses/gmqcc/LICENSE -} diff --git a/distro/archlinux/release/PKGBUILD b/distro/archlinux/release/PKGBUILD deleted file mode 100644 index 980ca55..0000000 --- a/distro/archlinux/release/PKGBUILD +++ /dev/null @@ -1,38 +0,0 @@ -# Contributor: matthiaskrgr -# Contributor: Wolfgang Bumiller - -pkgname=gmqcc -pkgver=0.3.5 -pkgrel=1 -pkgdesc="An Improved Quake C Compiler" -arch=('i686' 'x86_64') -depends=('glibc') -url="https://github.com/graphitemaster/gmqcc.git" -license=('MIT') -source=(gmqcc-$pkgver.zip::https://github.com/graphitemaster/gmqcc/zipball/$pkgver) -sha1sums=('69085478f48f04eefbd2a088c1bd8c266b59f952') - -_gitname=graphitemaster-gmqcc-7f2b206/ - -build() { - msg "Starting compilation..." - cd "$srcdir"/"$_gitname" - - msg "Compiling..." - make -} - -check() { - cd "$srcdir"/"$_gitname" - make check -} - -package() { - cd "$srcdir"/"$_gitname" - msg "Compiling and installing to pkgdir this time..." - make install DESTDIR=$pkgdir PREFIX=/usr - msg "Compiling done." - - install -dm755 ${pkgdir}/usr/share/licenses/gmqcc - install -m644 LICENSE ${pkgdir}/usr/share/licenses/gmqcc/LICENSE -} diff --git a/distro/archlinux/this/Makefile b/distro/archlinux/this/Makefile deleted file mode 100644 index 91693a4..0000000 --- a/distro/archlinux/this/Makefile +++ /dev/null @@ -1,52 +0,0 @@ -BASEDIR := ../../../ -PREFIX := /usr -HEADER := $(BASEDIR)/gmqcc.h -MAJOR := $(shell sed -n -e '/GMQCC_VERSION_MAJOR/{s/.* .* //;p;q;}' $(HEADER)) -MINOR := $(shell sed -n -e '/GMQCC_VERSION_MINOR/{s/.* .* //;p;q;}' $(HEADER)) -PATCH := $(shell sed -n -e '/GMQCC_VERSION_PATCH/{s/.* .* //;p;q;}' $(HEADER)) -PKGREL := 1 -SUFFIX ?= archlinux -CARCH := $(shell uname -m) -PKGDIR := gmqcc-$(MAJOR).$(MINOR).$(PATCH)-$(PKGREL)-$(CARCH)-$(SUFFIX) -TARCOMP := -J -PKG := $(PKGDIR).pkg.tar.xz -PKGINFO := $(PKGDIR)/.PKGINFO -DESTDIR := distro/archlinux/this -CFLAGS := - -LIBC_DEPEND := glibc - -base: - $(MAKE) -C $(BASEDIR) clean - CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" \ - $(MAKE) -C $(BASEDIR) "DESTDIR=$(DESTDIR)/$(PKGDIR)" "PREFIX=$(PREFIX)" strip install - @echo "pkgname = gmqcc" > $(PKGINFO) - @echo "pkgver = $(MAJOR).$(MINOR).$(PATCH)-$(PKGREL)" >> $(PKGINFO) - @echo "pkgdesc = An Improved Quake C Compiler" >> $(PKGINFO) - @echo "url = https://github.com/graphitemaster/gmqcc.git" >> $(PKGINFO) - @echo "builddate = `date -u \"+%s\"`" >> $(PKGINFO) - @echo "packager = Unknown Packager" >> $(PKGINFO) - @echo "size = `du -sk $(PKGDIR) | awk '{print $$1 * 1024}'`" >> $(PKGINFO) - @echo "arch = $(CARCH)" >> $(PKGINFO) - @echo "license = MIT" >> $(PKGINFO) - @echo "conflict = gmqcc" >> $(PKGINFO) - @echo "depend = $(LIBC_DEPEND)" >> $(PKGINFO) - @echo "makepkgopt = strip" >> $(PKGINFO) - @echo "makepkgopt = docs" >> $(PKGINFO) - @echo "makepkgopt = libtool" >> $(PKGINFO) - @echo "makepkgopt = emptydirs" >> $(PKGINFO) - @echo "makepkgopt = zipman" >> $(PKGINFO) - @echo "makepkgopt = purge" >> $(PKGINFO) - @echo "makepkgopt = !upx" >> $(PKGINFO) - @bsdtar -C $(PKGDIR) -czf $(PKGDIR)/.MTREE \ - --format=mtree \ - --options='!all,use-set,type,uid,gid,mode,time,size,md5,sha256,link' \ - .PKGINFO usr/ 2>&1 >/dev/null - @bsdtar $(TARCOMP) -cvf $(PKG) -C $(PKGDIR)/ .PKGINFO .MTREE usr/ 2>&1 >/dev/null - @rm -rf $(PKGDIR) - -clean: - $(MAKE) -C $(BASEDIR) clean - @rm -f *.pkg.tar.xz - -all: base diff --git a/distro/deb/Makefile b/distro/deb/Makefile deleted file mode 100644 index fefb3e5..0000000 --- a/distro/deb/Makefile +++ /dev/null @@ -1,45 +0,0 @@ -BASEDIR := ../.. -PREFIX := /usr -HEADER := $(BASEDIR)/gmqcc.h -MAJOR := `sed -n -e '/GMQCC_VERSION_MAJOR/{s/.* .* //;p;q;}' $(HEADER)` -MINOR := `sed -n -e '/GMQCC_VERSION_MINOR/{s/.* .* //;p;q;}' $(HEADER)` -PATCH := `sed -n -e '/GMQCC_VERSION_PATCH/{s/.* .* //;p;q;}' $(HEADER)` -DEBDIR := gmqcc-$(MAJOR).$(MINOR).$(PATCH) -CARCH := $(shell uname -m) -DEB := $(DEBDIR)-$(CARCH).deb -CONTROL := $(DEBDIR)/DEBIAN/control - -ifneq (, $(findstring i686, $(CARCH))) - CFLAGS += -m32 - LDFLAGS += -m32 -endif - -base: - $(MAKE) -C $(BASEDIR) clean - CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" \ - $(MAKE) -C $(BASEDIR) DESTDIR=distro/deb/$(DEBDIR) PREFIX=$(PREFIX) strip install - @install -d -m755 $(DEBDIR)/DEBIAN - @echo "Package: gmqcc" > $(CONTROL) - @echo "Version: $(MAJOR).$(MINOR).$(PATCH)" >> $(CONTROL) - @echo "Section: user/hidden" >> $(CONTROL) - @echo "Priority: optional" >> $(CONTROL) - @echo "Architecture: $(CARCH)" >> $(CONTROL) - @echo "Installed-Size: `du -ks $($(DEBDIR)/usr) | cut -f 1`" >> $(CONTROL) - @echo "Maintainer: Dale Weiler " >> $(CONTROL) - @echo "Description: An improved Quake C Compiler" >> $(CONTROL) - @echo " For an enduring period of time the options for a decent compiler for the Quake C programming language" >> $(CONTROL) - @echo " were confined to a specific compiler known as QCC. Attempts were made to extend and improve upon the" >> $(CONTROL) - @echo " design of QCC, but many foreseen the consequences of building on a broken foundation. The solution" >> $(CONTROL) - @echo " was obvious, a new compiler; one born from the NIH realm of sarcastic wit. We welcome you. You won't" >> $(CONTROL) - @echo " find a better Quake C compiler." >> $(CONTROL) - @tar czf data.tar.gz -C $(DEBDIR)/ . --exclude=DEBIAN - @tar czf control.tar.gz -C $(DEBDIR)/DEBIAN/ . - @echo 2.0 > debian-binary - @ar r $(DEB) debian-binary control.tar.gz data.tar.gz 2>&1 >/dev/null - @rm -rf debian-binary control.tar.gz data.tar.gz $(DEBDIR) - -clean: - $(MAKE) -C $(BASEDIR) clean - @rm -f *.deb - -all: base diff --git a/distro/fedora/spec/INSTALL b/distro/fedora/spec/INSTALL deleted file mode 100644 index 8540670..0000000 --- a/distro/fedora/spec/INSTALL +++ /dev/null @@ -1,31 +0,0 @@ -File is constantly updated! - -Since Fedora 19 we have this package in main repos. Please use it! - -For install gmqcc do this: -# yum install gmqcc - -For install qcvm do this: -# yum install qcvm - -For install gmqpak do this: -# yum install gmqpak - -To use the spec files in this fedora directory you need the core -development tools , building enviroment for the user, and -the directory structure for it. If you don't already have these, issue -the following commands. - -# yum groupinstall "Development Tools" -# yum install rpmdevtools -$ rm -rf ~/rpmbuild -$ rpmdev-setuptree - -Prepare and build the RPMs. - -$ cp gmqcc.spec ~/rpmbuild/SPECS/ -$ wget https://github.com/graphitemaster/gmqcc/archive/0.3.5.tar.gz -o ~/rpmbuild/SOURCES/gmqcc-0.3.5.tar.gz -$ rpmbuild -ba ~/rpmbuild/SPECS/gmqcc.spec - -Now we have RPMs in ~/rpmbuild/RPMS/ and you can install it via yum. - diff --git a/distro/fedora/spec/gmqcc.spec b/distro/fedora/spec/gmqcc.spec deleted file mode 100644 index 95fb611..0000000 --- a/distro/fedora/spec/gmqcc.spec +++ /dev/null @@ -1,96 +0,0 @@ -Name: gmqcc -Version: 0.3.6 -Release: 2%{?dist} -Summary: Improved Quake C Compiler -License: MIT -URL: http://graphitemaster.github.io/gmqcc/ -Source0: https://github.com/graphitemaster/%{name}/archive/%{version}.tar.gz#/%{name}-%{version}.tar.gz - -# tests fail on big endians -ExclusiveArch: %{ix86} x86_64 %{arm} - -%description -Modern written-from-scratch compiler for the QuakeC language with -support for many common features found in other QC compilers. - -%package -n qcvm -Summary: Standalone QuakeC VM binary executor - -%description -n qcvm -Executor for QuakeC VM binary files created using a QC compiler such -as gmqcc or fteqcc. It provides a small set of built-in functions, and -by default executes the main function if there is one. Some options -useful for debugging are available as well. - -%package -n gmqpak -Summary: Standalone Quake PAK file utility - -%description -n gmqpak -Standalone Quake PAK file utility supporting the extraction of files, -directories, or whole PAKs, as well as the opposite (creation of PAK files). - -%prep -%setup -q -echo '#!/bin/sh' > ./configure -chmod +x ./configure - -# and for all for all of those switches they increase the runtime of the compile -# making compiles of code slower - -# we don't need compile time buffer protection, we test with clang's address -# sanatizer and valgrind before releases -%global optflags %(echo %{optflags} | sed 's/-D_FORTIFY_SOURCE=2 //') -# there is no exceptions in C -%global optflags %(echo %{optflags} | sed 's/-fexceptions //') -# same with clangs address sanatizer and valgrind testing -%global optflags %(echo %{optflags} | sed 's/-fstack-protector-strong //') -# buffer overflow protection is unrequired since most (if not all) allocations -# happen dynamically and we have our own memory allocator which checks this -# (with valgrind integration), also clang's address santatizer cathes it as -# for grecord-gcc-switches, that just adds pointless information to the binary -# increasing its size -%global optflags %(echo %{optflags} | sed 's/--param=ssp-buffer-size=4 //') - -%build -%configure -make %{?_smp_mflags} - -%install -%make_install PREFIX=%{_prefix} - -%check -make check - -%files -%doc LICENSE README AUTHORS CHANGES TODO -%doc gmqcc.ini.example -%{_mandir}/man1/gmqcc.1* -%{_bindir}/gmqcc - -%files -n qcvm -%doc LICENSE README AUTHORS CHANGES TODO -%{_mandir}/man1/qcvm.1* -%{_bindir}/qcvm - -%files -n gmqpak -%doc LICENSE README AUTHORS CHANGES TODO -%{_mandir}/man1/gmqpak.1* -%{_bindir}/gmqpak - -%changelog -* Sat Nov 16 2013 Dan Horák - 0.3.5-2 -- fix build on big endian arches -- use the standard wildcarded filename for man pages -- and make it Exclusive for little endians because tests fail on big endians - -* Thu Nov 14 2013 Igor Gnatenko - 0.3.5-1 -- 0.3.5 upstream release - -* Thu Sep 26 2013 Igor Gnatenko - 0.3.0-2 -- Optimizing compile flags - -* Fri Sep 20 2013 Igor Gnatenko - 0.3.0-1 -- Update to 0.3.0 (improved new package: gmqpak) - -* Sat Jul 27 2013 Igor Gnatenko - 0.2.9-1 -- Initial release diff --git a/distro/fedora/this/Makefile b/distro/fedora/this/Makefile deleted file mode 100644 index 49b752f..0000000 --- a/distro/fedora/this/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -BASEDIR := $(CURDIR)/../../.. -HEADER := $(BASEDIR)/gmqcc.h -MAJOR := `sed -n -e '/GMQCC_VERSION_MAJOR/{s/.* .* //;p;q;}' $(HEADER)` -MINOR := `sed -n -e '/GMQCC_VERSION_MINOR/{s/.* .* //;p;q;}' $(HEADER)` -PATCH := `sed -n -e '/GMQCC_VERSION_PATCH/{s/.* .* //;p;q;}' $(HEADER)` -NAME := gmqcc-$(MAJOR).$(MINOR).$(PATCH) -TARFILE := $(NAME).tar.gz - -all: - @mkdir -p ~/rpmbuild/SPECS - @mkdir -p ~/rpmbuild/SOURCES - @cp ../spec/gmqcc.spec ~/rpmbuild/SPECS - @mkdir -p /tmp/$(NAME) - @cp -R $(BASEDIR) /tmp/$(NAME)/ - @cd /tmp && tar -zcf ~/rpmbuild/SOURCES/$(TARFILE) $(NAME)/ - @rm -rf /tmp/$(NAME) - @rpmbuild -ba ../spec/gmqcc.spec 2>&1 >/dev/null - - @mv ~/rpmbuild/RPMS/x86_64/gmqcc*.rpm . 2>/dev/null; true - @mv ~/rpmbuild/RPMS/x86_64/qcvm*.rpm . 2>/dev/null; true - @mv ~/rpmbuild/RPMS/x86_64/gmqpak*.rpm . 2>/dev/null; true diff --git a/distro/gentoo/INSTALL b/distro/gentoo/INSTALL deleted file mode 100644 index 7a4f778..0000000 --- a/distro/gentoo/INSTALL +++ /dev/null @@ -1,35 +0,0 @@ -To use the ebuilds provided in this gentoo directory you first must -create a directory in your overlay tree. - -If you don't already have your own directory for custom ebuilds, you can -create one. If you already have one, and that directory is set in your -/etc/make.conf for PORTDIR_OVERLAY, this step can be skiped. Otherwise -if you don't already, you can create one as such. - -# mkdir -p /usr/local/portage -# vim /etc/make.conf - Set PORTDIR_OVERLAY=/usr/local/portage - Then save and exit - -Once that is completed, or you skiped that step, you need to create a -directory in your overlay tree for gmqcc, this can be done as such: -(subsitute [[PORTDIR_OVERLAY]] with the one set in /etc/make.conf) - -# mkdir -p [[PORTDIR_OVERLAY]]/gmqcc - -After the directory is created you need to move the correct version ebuild -into that directory depending on which version of GMQCC you want. For -instance, if you want gmqcc 0.3.0, you move gmqcc-0.3.0.ebuild into that -directory. - -# mv gmqcc-{version}.ebuild [[PORTDIR_OVERLAY]]/gmqcc/ - -After the file is moved into your newly created portage overlay tree, you'll -need to build a digest for it with ebuild. A digest is simply a Manifest and -digital signature for the source files used. - -# ebuild gmqcc-0.3.0.ebuild digest - -After the digest is built, you can emerge gmqcc as usual. - -# emerge gmqcc diff --git a/distro/gentoo/gmqcc-0.3.0.ebuild b/distro/gentoo/gmqcc-0.3.0.ebuild deleted file mode 100644 index 28630d3..0000000 --- a/distro/gentoo/gmqcc-0.3.0.ebuild +++ /dev/null @@ -1,20 +0,0 @@ -EAPI=5 - -DESCRIPTION="An Improved Quake C Compiler" -HOMEPAGE="http://graphitemaster.github.com/gmqcc/" -SRC_URI="https://github.com/graphitemaster/${PN}/archive/${PV}.tar.gz -> ${P}.tar.gz" - -LICENSE="MIT" - -SLOT="0" -IUSE="" -KEYWORDS="~amd64 ~x86" - -src_prepare() { - sed -i -e "s:-Werror ::" Makefile || die -} - -src_install() { - emake install PREFIX="${D}/usr" - dodoc README -} diff --git a/distro/slackware/this/Makefile b/distro/slackware/this/Makefile deleted file mode 100644 index 3c28cb0..0000000 --- a/distro/slackware/this/Makefile +++ /dev/null @@ -1,35 +0,0 @@ -BASEDIR := ../../../ -PREFIX := /usr -HEADER := $(BASEDIR)/gmqcc.h -MAJOR := $(shell sed -n -e '/GMQCC_VERSION_MAJOR/{s/.* .* //;p;q;}' $(HEADER)) -MINOR := $(shell sed -n -e '/GMQCC_VERSION_MINOR/{s/.* .* //;p;q;}' $(HEADER)) -PATCH := $(shell sed -n -e '/GMQCC_VERSION_PATCH/{s/.* .* //;p;q;}' $(HEADER)) -CARCH := $(shell uname -m) -PKGDIR := gmqcc-$(MAJOR).$(MINOR).$(PATCH)-$(CARCH) -PKG := $(PKGDIR).txz -PKGINFO := $(PKGDIR)/.PKGINFO -DESTDIR := distro/slackware/this/$(PKGDIR) -CFLAGS := - - -ifneq (, $(findstring i686, $(CARCH))) - CFLAGS += -m32 - LDFLAGS += -m32 -endif - -base: - $(MAKE) -C $(BASEDIR) clean - CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" \ - $(MAKE) -C $(BASEDIR) "DESTDIR=$(DESTDIR)" "PREFIX=$(PREFIX)" strip install - gzip -9 $(PKGDIR)/usr/share/man/man?/*.? - strip -s $(PKGDIR)/usr/bin/* - mkdir $(PKGDIR)/install - cp slack-desc $(PKGDIR)/install - @tar -cJvf $(PKG) -C $(PKGDIR)/ install/ usr/ - @rm -rf $(PKGDIR) - -clean: - $(MAKE) -C $(BASEDIR) clean - @rm -f *.txz - -all: base diff --git a/distro/slackware/this/slack-desc b/distro/slackware/this/slack-desc deleted file mode 100644 index 3384ef9..0000000 --- a/distro/slackware/this/slack-desc +++ /dev/null @@ -1,12 +0,0 @@ - |-----handy-ruler------------------------------------------------------| -gmqcc: gmqcc (Quake C compiler) -gmqcc: -gmqcc: A modern written-from-scratch compiler for the QuakeC language with -gmqcc: support for many common features found in other QC compilers. -gmqcc: Additionally contains a standalone QCVM executor, and a tool to deal -gmqcc: with .pak archive files. -gmqcc: -gmqcc: -gmqcc: github page: -gmqcc: http://github.com/graphitemaster/gmqcc -gmqcc: diff --git a/distro/win32/Makefile b/distro/win32/Makefile deleted file mode 100644 index 20f580c..0000000 --- a/distro/win32/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -BASEDIR := ../.. -HEADER := $(BASEDIR)/gmqcc.h -MAJOR := `sed -n -e '/GMQCC_VERSION_MAJOR/{s/.* .* //;p;q;}' $(HEADER)` -MINOR := `sed -n -e '/GMQCC_VERSION_MINOR/{s/.* .* //;p;q;}' $(HEADER)` -PATCH := `sed -n -e '/GMQCC_VERSION_PATCH/{s/.* .* //;p;q;}' $(HEADER)` -BINDIR := gmqcc-$(MAJOR).$(MINOR).$(PATCH) - -base: - $(MAKE) CC=i686-w64-mingw32-gcc UNAME=MINGW -C $(BASEDIR) clean - $(MAKE) CC=i686-w64-mingw32-gcc UNAME=MINGW -C $(BASEDIR) DESTDIR=distro/win32/$(BINDIR) PREFIX=/ strip install - @mkdir -p $(BINDIR)/doc - @groff -mandoc $(BINDIR)/man1/gmqpak.1 | ps2pdf - $(BINDIR)/doc/gmqpak.pdf - @groff -mandoc $(BINDIR)/man1/qcvm.1 | ps2pdf - $(BINDIR)/doc/qcvm.pdf - @groff -mandoc $(BINDIR)/man1/gmqcc.1 | ps2pdf - $(BINDIR)/doc/gmqcc.pdf - @rm -rf $(BINDIR)/man1/ - @zip -r $(BINDIR)-win32.zip $(BINDIR) - @rm -rf $(BINDIR) -clean: - $(MAKE) -C $(BASEDIR) clean - @rm -f *.zip - -all: base diff --git a/distro/win64/Makefile b/distro/win64/Makefile deleted file mode 100644 index f45f351..0000000 --- a/distro/win64/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -BASEDIR := ../.. -HEADER := $(BASEDIR)/gmqcc.h -MAJOR := `sed -n -e '/GMQCC_VERSION_MAJOR/{s/.* .* //;p;q;}' $(HEADER)` -MINOR := `sed -n -e '/GMQCC_VERSION_MINOR/{s/.* .* //;p;q;}' $(HEADER)` -PATCH := `sed -n -e '/GMQCC_VERSION_PATCH/{s/.* .* //;p;q;}' $(HEADER)` -BINDIR := gmqcc-$(MAJOR).$(MINOR).$(PATCH) - -base: - $(MAKE) CC=x86_64-w64-mingw32-gcc UNAME=MINGW -C $(BASEDIR) clean - $(MAKE) CC=x86_64-w64-mingw32-gcc UNAME=MINGW -C $(BASEDIR) DESTDIR=distro/win64/$(BINDIR) PREFIX=/ strip install - @mkdir -p $(BINDIR)/doc - @groff -mandoc $(BINDIR)/man1/gmqpak.1 | ps2pdf - $(BINDIR)/doc/gmqpak.pdf - @groff -mandoc $(BINDIR)/man1/qcvm.1 | ps2pdf - $(BINDIR)/doc/qcvm.pdf - @groff -mandoc $(BINDIR)/man1/gmqcc.1 | ps2pdf - $(BINDIR)/doc/gmqcc.pdf - @rm -rf $(BINDIR)/man1/ - @zip -r $(BINDIR)-win64.zip $(BINDIR) - @rm -rf $(BINDIR) -clean: - $(MAKE) -C $(BASEDIR) clean - @rm -f *.zip - -all: base diff --git a/doc/gmqpak.1 b/doc/gmqpak.1 deleted file mode 100644 index f4ef65d..0000000 --- a/doc/gmqpak.1 +++ /dev/null @@ -1,39 +0,0 @@ -.\" gmqpak mdoc manpage -.Dd April 27, 2013 -.Dt GMQPAK 2 PRM -.Os -.Sh NAME -.Nm gmqpak -.Nd A standalone Quake PAK utility -.Sh SYNOPSIS -.Nm gmqpak -.Op Cm options -.Op Cm files -.Sh DESCRIPTION -.Nm gmqpak -Is a standalone Quake PAK file utility supporting the extraction of files, -directories, or whole PAKs, as well as the opposite (creation of PAK files). -.Sh OPTIONS -.Bl -tag -width indent -.It Fl -file Ar file -Specify the PAK file to create or extract -.It Fl -e -Used to denote the extraction operation on a PAK file. -.It Fl -c -Used to denote the creation operation on a PAK file. -.El -.Sh EXAMPLES -Here's some examples of how to use the utility to manipulate PAK files. -.Bl -ohang -.It Li gmqpak -file id1.pak -e -.D1 extracts a PAK to ./ -.It Li gmqpak -file new.pak -c file1 dir/file2 -.D1 creates a PAK with the files specified -.It Li gmqpak -file new1.pak -c directory. -.D1 creates a PAK from files within the directory, including subdirectories and files. -.El -.Sh AUTHOR -See . -.Sh BUGS -Please report bugs on , -or see on how to contact us. diff --git a/doc/html/download.c b/doc/html/download.c deleted file mode 100644 index f31545c..0000000 --- a/doc/html/download.c +++ /dev/null @@ -1,293 +0,0 @@ -#include -#include -#include -#include - -/* - * protect some information, not that I care, but this is just to stay - * safer. - */ -#define SECURITY_BASE "\ -ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -#define SECURITY_TOKEN "\ -P29hdXRoX2NvbnN1bWVyX2tleT03NnZoM3E0Mmhudm16bTMmb2F1dGhfdG9rZW49\ -dzBieHpmMGRmdDhlZGZxJm9hdXRoX3NpZ25hdHVyZV9tZXRob2Q9UExBSU5URVhU\ -Jm9hdXRoX3NpZ25hdHVyZT10bWVlY2h0MmNtaDcyeGElMjY5dm9zeDd4OGd5NGtn\ -amsmb2F1dGhfdGltZXN0YW1wPSZvYXV0aF9ub25jZT0xMjE2NQo=" - -int isbase64(char c) { - return !!(c && strchr(SECURITY_BASE, c) != NULL); -} -char value(char c) { - const char *load = SECURITY_BASE; - const char *find = strchr(load, c); - - return (find) ? find - load : 0; -} - -int security_decode(unsigned char *dest, const unsigned char *src, int srclen) { - unsigned char *p; - - if(!*src) - return 0; - - *dest = 0; - p = dest; - - do { - *p++ = (value(src[0]) << 2) | (value(src[1]) >> 4); - *p++ = (value(src[1]) << 4) | (value(src[2]) >> 2); - *p++ = (value(src[2]) << 6) | (value(src[3]) >> 0); - - if(!isbase64(src[1])) { - p -= 2; - break; - } - else if(!isbase64(src[2])) { - p -= 2; - break; - } - else if(!isbase64(src[3])) { - p--; - break; - } - src += 4; - - while(*src && (*src == 13 || *src == 10)) - src++; - } while(srclen-= 4); - - *p = 0; - return p-dest; -} - -#define BASEURL " https://api-content.dropbox.com/1/files/sandbox/" - -/* - * If more platforms are supported add the entries between the start - * tag here, and the end tag below. Nothing else needs to be done - * (the table needs to match the HTML too) - */ -#define ARCHLINUX_32_REF "%sgmqcc-%c.%c.%c-1-i686.pkg.tar.xz%s" -#define ARCHLINUX_64_REF "%sgmqcc-%c.%c.%c-1-x86_64.pkg.tar.xz%s" -#define DEBIAN_32_REF "%sgmqcc-%c.%c.%c-i686.deb%s" -#define DEBIAN_64_REF "%sgmqcc-%c.%c.%c-x86_64.deb%s" -#define WINDOWS_32_REF "%sgmqcc-%c.%c.%c-win32.zip%s" -#define WINDOWS_64_REF "%sgmqcc-%c.%c.%c-win64.zip%s" -#define SLACKWARE_32_REF "%sgmqcc-%c.%c.%c-i686.txz%s" -#define SLACKWARE_64_REF "%sgmqcc-%c.%c.%c-x86_64.txz%s" - - -#define HTML "\ -\ -\ -\ - \ - \ - GMQCC\ - \ - \ - \ - \ - \ -\ -\ -
\ -
\ -
\ -

GMQCC

\ -

An Improved Quake C Compiler

\ - \ -
\ -
\ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ -
Operating System / Distributionx86 Architecturex86_64 Architecture
ArchlinuxDownloadDownload
DebianDownloadDownload
SlackwareDownloadDownload
WindowsDownloadDownload
\ -
\ -
\ - \ -
\ -
\ - \ -\ -\ -" - -static char build_table[][4096] = { - ARCHLINUX_32_REF, ARCHLINUX_64_REF, - DEBIAN_32_REF, DEBIAN_64_REF, - SLACKWARE_32_REF, SLACKWARE_64_REF, - WINDOWS_32_REF, WINDOWS_64_REF -}; -/*
*/ - -#define ISXDIGIT(c) ((c >= 48 && c <= 57) || ((c & ~0x20) >= 65 && (c & ~0x20) <= 70)) -typedef struct { - char *data; - unsigned int len; - unsigned int size; -} url_t; -void escape(url_t *str) { - char *p, *ptr; - char hexstr[3]; - unsigned int i=0; - unsigned long l=0; - - p = str->data; - for(i=0; i < str->len; i++) { - if((p - str->data) >= str->len) - break; - if(*p == '%' && - ((p - str->data)+2) < str->len && - ISXDIGIT(*(p+1)) && - ISXDIGIT(*(p+2)) - ) { - p++; - hexstr[0] = *p++; - hexstr[1] = *p++; - hexstr[2] = 0; - l = strtoul(hexstr, &ptr, 16); - str->data[i] = (char)(l & 0x7f); - continue; - } - if(*p == '+') { - *p = ' '; - } - str->data[i] = *p++; - } - str->data[i] = 0; - str->len = i; -} - -void version(const char *directory, char *major, char *minor, char *patch) { - FILE *handle; - char file[4096]; - size_t size = 0; - char *data = NULL; - snprintf(file, sizeof(file), "%s/gmqcc.h", directory); - - handle = fopen(file, "r"); - if (!handle) { - fprintf(stderr, "failed to open %s for reading version (%s)\n", - file, strerror(errno) - ); - abort(); - } - - while (getline(&data, &size, handle) != EOF) { - - #define TEST(TYPE, STORE) \ - if (strstr(data, "#define GMQCC_VERSION_" TYPE )) { \ - char *get = data; \ - while (!isdigit(*get)) \ - get++; \ - *STORE = *get; \ - } - - TEST("MAJOR", major) - TEST("MINOR", minor) - TEST("PATCH", patch) - - #undef TEST - } - - free(data); -} - -void genhtml() { - FILE *fp = fopen("download.html", "w"); - if (!fp) { - fprintf(stderr, "failed to generate HTML: %s\n", strerror(errno)); - abort(); - } - - fprintf(fp, HTML, - build_table[0], build_table[1], - build_table[2], build_table[3], - build_table[4], build_table[5], - build_table[6], build_table[7] - ); - fclose (fp); -} - -/* - * Builds a list of download links with the right version and handles the - * rest of the magic. - */ -void build(const char *directory) { - /* Figure out version number */ - char find[3]; - char decode[4096]; - size_t size; - version(directory, &find[0], &find[1], &find[2]); - - /* - * decode the secuity stuff for preparing the URLs which will be used - * as links. - */ - memset(decode, 0, sizeof(decode)); - security_decode(decode, SECURITY_TOKEN, strlen(SECURITY_TOKEN)); - - for (size = 0; size < sizeof(build_table) / sizeof(*build_table); size++) { - char *load = strdup(build_table[size]); - url_t esc = { NULL, 0 }; - - snprintf(build_table[size], 4096, load, BASEURL, find[0], find[1], find[2], decode); - esc.data = strdup(build_table[size]); - esc.size = strlen(build_table[size]); - esc.len = esc.size; - - /* Yes we also need to escape URLs just incase */ - escape(&esc); - free(load); - } - - /* - * Now generate the HTML file for those download links by asking tinyurl to - */ - genhtml(); -} - -int main(int argc, char **argv) { - size_t itr; - - argc--; - argv++; - if (!argc) { - printf("usage: %s [gmqcc.h location]\n", argv[-1]); - return 0; - } - - build(*argv); -} diff --git a/doc/html/gmqcc.png b/doc/html/gmqcc.png deleted file mode 100644 index 0d9b79f..0000000 Binary files a/doc/html/gmqcc.png and /dev/null differ diff --git a/doc/html/style.css b/doc/html/style.css deleted file mode 100644 index fa67901..0000000 --- a/doc/html/style.css +++ /dev/null @@ -1,92 +0,0 @@ -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font: inherit; - vertical-align: baseline; -} - -body { - font-family:"Helvetica Neue", Helvetica, Arial, sans-serif; - margin: 0; - line-height: 1.8em; - -webkit-font-smoothing: antialiased; - background: #CDC9C9; -} - -h1, h2, h3, h4, h5, h6 { - color:#232323; - margin:36px 0 10px; -} - -.head-ltitle, .head-rtitle, .head-vol { - font: bold 0.8em Arial, Helvectia, sans-serif; -} - -.head-vol { - visibility: hidden; -} - -.name { - font: bold 0.8em Monospace, serif; -} - -.ftype { - font: normal 1em Monospace, serif; -} - -h1 { - color: #c30000; - margin-top: 0.3em; - margin-bottom: 0.3em; - line-height: 1.3; - font: normal 1.4em Arvo, Monaco, sans-serif; - /*font: bold 1.4em Arial, Helvetica, sans-serif*/ -} - -.section { - margin-bottom: 1em; - margin-top: 1em; - padding-left: 1em; - border-top: 1px #cccccc solid; - font: normal 0.9em Arial, Helvetica, sans-serif; - text-align: justify; - border: 0; - padding-bottom: 1em; - border-bottom: 1px solid #f0e0e0; -} - -.list-tag { - padding-left: 1em; -} - -pre { - border: 1px dashed #ffffff; - background: #ddd8d8; -} - -.flag { - font: normal 1em Monospace, serif; -} - -a { - color:#C30000; - font-weight:200; - text-decoration:none; -} - -a:hover { - text-decoration: underline; -} diff --git a/doc/specification.tex b/doc/specification.tex deleted file mode 100644 index 74190fc..0000000 --- a/doc/specification.tex +++ /dev/null @@ -1,759 +0,0 @@ -\documentclass{article} - -%%% PACKAGES -\usepackage{geometry} -\usepackage[utf8]{inputenc} -\usepackage[parfill]{parskip} -\usepackage{subfig} -\usepackage{listings} -\usepackage{color} -\usepackage{sectsty} - -%%% GEOMETRY FOR DOCUMENT -\geometry{a4paper} - -%%% HEADERS/FOOTERS APPEARANCE -\usepackage{fancyhdr} % This should be set AFTER setting up the page geometry -\pagestyle{fancy} % options: empty , plain , fancy -\renewcommand{\headrulewidth}{0pt} % customise the layout... -\lhead{}\chead{}\rhead{} -\lfoot{}\cfoot{\thepage}\rfoot{} - -%%% SECTION TITLE APPEARANCE -\allsectionsfont{\sffamily\mdseries\upshape} % (See the fntguide.pdf for font help) - -%%% ToC APPEARANCE -\usepackage[nottoc,notlof,notlot]{tocbibind} % Put the bibliography in the ToC -\usepackage[titles,subfigure]{tocloft} % Alter the style of the Table of Contents -\renewcommand{\cftsecfont}{\rmfamily\mdseries\upshape} -\renewcommand{\cftsecpagefont}{\rmfamily\mdseries\upshape} % No bold! - -%%% listing language definitions -%%% BNF for now, QuakeC will be later -\definecolor{keyword1}{RGB}{0,102,153} -\definecolor{keyword2}{RGB}{0,153,102} -\definecolor{keyword3}{RGB}{0,153,255} -\definecolor{comment}{RGB}{204,0,0} -\definecolor{function}{RGB}{153,102,255} -\definecolor{digit}{RGB}{255,0,0} -\definecolor{string}{RGB}{255,0,204} -\definecolor{rule}{RGB}{192,192,192} -\definecolor{back}{RGB}{250,250,250} - -\lstdefinelanguage{bnf}{ - keywordstyle={\color{keyword2}\bfseries}, - keywords={}, - otherkeywords={::=,|}, - morecomment=[s][\color{comment}]{(*}{*)}, - stringstyle=\color{string}, - showstringspaces=false, - frame=none, - rulecolor=\color{rule}, - backgroundcolor=\color{back} -} - -%% Title Information %% -\title{The GMQCC QuakeC Programming Language} -\author{Dale Weiler} -\date{\today} - -\begin{document} - -%% Title Page %% -\maketitle -\thispagestyle{empty} -\raggedright -\abstract -This document specifies the form and establishes the interpretation of programs written in -the GMQCC QuakeC programming language variant (refereed simply as QuakeC throughout this -document). It specifies: -\begin{itemize} - \item the representation of QuakeC programs; - \item the syntax and constraints of the QuakeC language; - \item the semantic rules for interpreting QuakeC programs; - \item the representation of input data to be processed by QuakeC programs; - \item the representation of output data produced by QuakeC programs; - \item the restrictions and limits imposed by a conforming implementation of QuakeC. -\end{itemize} -This document does not specify -\begin{itemize} - \item the mechanism by which QuakeC programs are transformed for use by a data- - processing system; - \item the mechanism by which QuakeC programs are invoked for use by a data-processing - system; - \item the mechanism by which input data are transformed for use by a QuakeC program; - \item the size or complexity of a program and its data that will exceed the capacity - of any specific data-processing system or the capacity of a particular - execution environment; - \item all minimal requirements of a data-processing system that is capable of - supporting a conforming implementation. -\end{itemize} - -%% Table Of Contents %% -\newpage -\thispagestyle{empty} -\tableofcontents -\newpage - -%% Begin Contents %% -\raggedright % No weird TEX spacing on lines to fill page - -%% -> Terms, definitions, and symbols %% -\section{Terms, definitions, and symbols} -\subsection*{argument} -Expression in the comma-separated list bounded by the parentheses in a function call -expression, or a sequence of preprocessing tokens in the comma-separated list bounded -by the parentheses in a function-like macro invocation. - -\subsection*{behavior} -External appearance or action - -\subsection*{implementation-defined behavior} -Unspecified behavior where each implementation documents how the choice is made. - -\subsection*{undefined behavior} -Behavior, upon use of a non-portable or erroneous program construct or of erroneous data, -for which this document imposes no actual requirements. - -\subsection*{unspecified behavior} -Use of an unspecified value, or other behavior where this document provides two or more -possibilities and imposes no further requirements on which is chosen in any instance. - -\subsection*{constraint} -Restriction, either syntactic or semantic, by which the exposition of language elements -is to be interpreted. - -\subsection*{diagnostic message} -Message belonging to an implementation-defined subset of the implementation's message -output. - -\subsection*{object} -Region of data storage in the execution environment, the contents of which can represent -values. - -\subsection*{parameter} -Object declared as part of a function declaration or definition that acquires a value on -entry to the function, or an identifier from the comma-separated list bounded by the -parentheses immediately following the macro name in a function-like macro definition. - -\subsection*{recommended practice} -Specification that is strongly recommended as being in keeping with the intent of this -document, but that may be impractical for some implementations. - -\subsection*{value} -Precise meaning of the contents of an object when interpreted as having a specific type. - -\subsection*{implementation} -Particular set of software, running in a particular translation environment under -particular control options, that performs translation of programs for, and supports -execution of functions in, a particular execution environment. - -\subsection*{implementation-defined value} -Unspecified value where each implementation documents how the choice is made. - -\subsection*{unspecified value} -Valid value of the relevant type where this document imposes no requirements on which -value is chosen in any instance. - -%% -> Conformance %% -\section{Conformance} -In this document, "shall" is to be interpreted as a requirement on an implementation -or on a program; conversely, "shall not" is to be interpreted as a prohibition. \\ -If a "shall" or "shall not" requirement that appears outside of a constraint is violated, -the behavior is undefined. Undefined behavior is otherwise indicated in this document by -the words "undefined behavior" or by the omission of any explicit definition of behavior. -There is no difference in emphasis among these three; they all describe "behavior that is -undefined". - -%% -> Enviroment %% -\section{Environment} -An implementation that translates QuakeC source files and executes QuakeC programs in two -data processing-system environments, which will be called the translation environment and -the execution environment in this document. Their characteristics define and constrain the -results of executing QuakeC programs constructed according to the syntactic and semantic -rules for conforming implementations. -\subsection{Conceptual models} -\subsubsection{Translation environment} -\paragraph*{Translation steps} -The precedence among the syntax rules of translation is specified by the following steps -\begin{enumerate} - \item Physical source file characters are mapped, in an implementation-defined manner, - to the source character set (introducing new-line characters for end-of-line - indicators) if necessary. Trigraph and digraph sequences are replaced by their - corresponding single-character internal representations. - \item The source file is decomposed into preprocessing tokens and sequences of white- - space characters (including comments). A source file shall not end in a partial - preprocessing token or in a partial comment. Each comment is replaced by one - space character. New-line characters are retained. Whether each nonempty - sequences of white-space characters other than new-line is retained or replaced - by one space character is implementation-defined. - \item Preprocessing directives are executed, macro invocations are expanded - recursively. A \#include preprocessing directive causes the named header or - source file to be processed from step one through step three, recursively. All - preprocessing directives are then deleted. - \item Each source character set member and escape sequence in character constants and - string literals is converted to the corresponding member of the execution - character set; if there is no corresponding member, it is converted to an - implementation-defined member other than the null character. - \item Adjacent string literal tokens are concatenated. - \item White-space characters seperating tokens are no longer significant. Each - preprocessing token is converted into a token. The resulting tokens are then - syntactically and semantically analyzed and translated. -\end{enumerate} -\subparagraph*{Footnotes} -Implementations shall behave as if these steps occur separately, even though many are likely -to be folded together in practice. Source files need not be stored as file, nor need there -be any one-to-one correspondence between these items and any external representation. The -description is conceptual only, and does not specify any particular implementation. - -\paragraph*{Diagnostics} -A conforming implementation shall produce at least on diagnostic message(identified in an -implementation-defined manner) if a source file contains a violation of any syntax rule or -constraint, even if the behavior is also explicitly specified as undefined or -implementation-defined. Diagnostic messages need not be produced in other circumstances. - -%% ->-> Execution environments %% -\subsubsection{Execution environment} -A conforming execution environment shall provide at minimal the following 15 definitions -for built in functions, with an accompanying header or source file that defines them. -\begin{enumerate} - \item entity () spawn - \item void (entity) remove - - \item string (float) ftos - \item string (vector) vtos - \item string (entity) etos - \item float (string) stof - - \item void (string, ...) dprint - \item void (entity) eprint - - \item float (float) rint - \item float (float) floor - \item float (float) ceil - \item float (float) fabs - \item float (float) sin - \item float (float) cos - \item float (float) sqrt -\end{enumerate} -The numbers of which these built-ins are assigned is implementation-defined; -an implementation is allowed to use these built-ins however it sees fit. - -\pagebreak -%% -> Language %% -\section{Language} -\subsection{Notation} -The syntax notation used in this document is that of a BNF specification. A set of -derivation rules, often written as: -\begin{lstlisting}[language=bnf] - symbol ::= expression -\end{lstlisting} -Where symbol is a nonterminal, and the expression consists of one or more sequences of -symbols; more sequences are separated by a vertical bar \textbar, indicating a choice, -the whole being a possible substitution for the symbol on the left. Symbols that never -appear on the left side are terminals. -\linebreak - -This document defines language syntax throughout it's way at defining language -constructs If you're interested in a summary of the language syntax, one is given in -annex A. - -%% -> Concepts %% -\subsection{Concepts} -%% ->-> Scopes of identifiers %% -\subsubsection{Scopes of identifiers} -An identifier can denote an object; a function, or enumeration; a label name; a macro -name; or a macro parameter. The same identifier can denote different items at different -points in the program. A member of an enumeration is called an enumeration constant. -Macro names and macro parameters are not considered further here, because prior to the -semantic phase of program translation any occurrences of macro names in the source file -are replaced by the preprocessing token sequences that constitute their macro definitions. -\linebreak - -For each different item that an identifier designates, the identifier is visible (i.e, -can be used) only within a region of program text called its scope. Different items -designated by the same identifier either have different scopes, or are in different name -spaces. There are four kinds of scopes: function, file, block and function prototype. -(A function prototype is a declaration of a function that declares the types of its -parameters.) -\linebreak - -A label name is the only kind of identifier that has function scope. It can be used (in -a goto statement) anywhere in the function in which it appears, and is declared -implicitly by its syntactic appearance (prefixed by a colon :, and suffixed with a -statement). -\linebreak - -Every other identifier has scope determined by the placement of its declaration (in a -declarator or type specifier). If the declarator or type specifier that declares the -identifier appears outside any block or list of parameters, the identifier has file -scope, which terminates at the end of the file. If the declartor or type specifier that -declares the identifier appears inside a block or within the list of parameter -declarations in a function definition, the identifier has block scope, which terminates -at the end of the associated block. If the declarator or type specifier that declares -the identifier appears within the list of parameter declarations in a function prototype -(not part of a function definition), the identifier has function prototype scope, which -terminates at the end of the function declarator. If an identifier designates two -different items in the same name space, the scopes might overlap. If so, the scope of -one item (the inner scope) will be a strict subset of the scope of the other item (the -outer scope). Within the inner scope, the identifier designates the item declared in the -inner scope; the item declared in the outer scope is hidden (and not visible) within -the inner scope. -\linebreak - -Unless explicitly stated otherwise, where this document uses the term "identifier" to -refer to some item (as opposed to the syntactic construct), it refers to the item in the -relevant name space whose declaration is visible at the point the identifier occurs. -\linebreak - -Two identifiers have the same scope if and only if their scopes terminate at the same -point. -\linebreak - -Each enumeration constant has scope that begins just after the appearance of its defining -enumerator in an enumerator list. Any other identifier has scope that begins just after -the completion of its declarator. - -%% ->-> Name spaces of identifiers %% -\subsubsection{Name spaces of identifiers} -If more than one declaration of a particular identifier is visible at any point in a -source file, the syntactic context disambiguates uses that refer to different items. -Thus, there are separate name spaces for various categories of identifiers, as follows: -\linebreak -\begin{itemize} - \item Label names (disambiguated by the syntax of the label declaration and use); - \item Enumerations (disambiguated by following the keyword enum); - \item All other identifiers, called ordinary identifiers (declared in ordinary - declarators or as enumeration constants). -\end{itemize} - -%% ->-> Types %% -\subsubsection{Types} -The meaning of a value stored in an object returned by a function is determined by the -type of the expression used to access it. (An identifier declared to be an object is the simplest -such expression; the type is specified in the declaration of the identifier.) Types are -partitioned into object types (types that fully describe objects), function types(types -that describe functions), and incomplete types(types that describe objects but lack -information). -\linebreak - -An object declared type bool is large enough to store the values 0 and 1. -\linebreak - -An object declared type float is a real type; An object declared type vector is a -comprised set of three floats that respectively represent the \underline{x,y,z} -components of a three-dimensional vector. -\linebreak - -An enumeration comprises a set of named integer constant values. Each distinct -enumeration constitutes a different enumerated type. -\linebreak - -Enumeration types and float are collectively called arithmetic types. Each arithmetic -type belongs to one type domain. -\linebreak - -The void type comprises an empty set of values; it is an incomplete type that cannot be -completed. -\linebreak - -A number of derived types can be constructed from the object, function and incomplete -types, as follows: -\linebreak - -\begin{itemize} - \item An array type describes a contiguously allocated nonempty set of objects with a - particular object type, called the element type. Array types are characterized - by their element type and by the number of elements in the array. An array type - is said to be derived from its element type, and if its element is type T, the - array type is sometimes called "array of T". The construction of an array type - from an element type is called "array type derivation". - \item A function type describes a function with a specified return type. A function - type is characterized by its return type and the number and types of its - parameters. A function type is said to be derived from its return type, and if - its return type is T, the function type is sometimes called "function returning - T". The construction of a function type from a return type is called "function - type derivation". -\end{itemize} - -Arithmetic types are collectively called scalar types. Arrays and vectors are -collectively called aggregate types. -\linebreak - -An array of unknown size is an incomplete type. It is completed, for an identifier of -that type, by specifying the size in a later declaration. Arrays are required to have -known constant size. -\linebreak - -A type is characterized by its type category, which is either the outermost derivation -of a derived type (as noted above in the construction of derived types), or the type -itself if the type consists of no derived types. -\linebreak - -Any type so far mentioned is an unqualified type. Each unqualified type has several -qualified versions of its type, corresponding to the combinations of one, two, or all -two of const and volatile qualifiers. The qualified or unqualified versions of a type -are distinct types that belong to the same type category and have the same representation. -A derived type is not qualified by the qualifiers (if any) of the type from which it -is derived. -\linebreak - -%% ->-> Compatible types and composite type %% -\subsubsection{Compatible types and composite type} -Two types have compatible type if their types are the same. -\linebreak - -All declarations that refer to the same object or function shall have compatible type; -otherwise the behavior is undefined. -\linebreak - -A composite type can be constructed from two types that are compatible; it is a type that -is compatible with both of the two types and satisfies the following conditions: -\begin{itemize} - \item If one type is an array, the composite type is an array of that size. - \item If only one type is a function type with a parameter type list(a function - prototype), the composite type is a function prototype with the parameter type - list. - \item If both types are function types with parameter type lists, the type of each - parameter in the composite parameter type list is the composite type of the - corresponding parameters. -\end{itemize} -These rules apply recursively to types from which the two types are derived. -\linebreak - -%% ->Conversions %% -\subsection{Conversions} -Several operators convert operand values from one type to another automatically. This -sub-clause specifies the result required from such an implicit conversion. -\linebreak - -Conversion from an operand value to a compatible type causes no change to the value or -the representation. -\linebreak - -TODO: Specify all implicit conversions. - -%% ->->Aritmetic operands %% -\subsubsection{Arithmetic operands} -\paragraph*{Boolean type} -When any scalar value is converted to bool, the result is 0 if the value compares equal -to 0; otherwise the result is 1. - -%% ->->Other operands %% -\subsubsection{Other operands} -\paragraph{Lvalues, arrays and function designators} -An lvalue is an expression with an object type or an incomplete type other than void; -if an lvalue does not designate an object when it is evaluated, the behavior is undefined. -When an object is said to have a particular type, the type is specified by the lvalue -used to designate the object. A modifiable lvalue is an lvalue that does not have an -array type, does not have an incomplete type, and does not have a const-qualified type. -\linebreak - -Except when it is the operand of the unary \& operator, the ++ operator, the -- operator, -or the left operand of the . operator or an assignment operator, an lvalue that does not -have array type is converted to the value stored in the designated object (and is no -longer an lvalue). If the lvalue has qualified type, the value has the unqualified -version of the type of the lvalue; otherwise, the value has the type of the lvalue. If -the lvalue has an incomplete type and does not have array type, the behavior is undefined. -\linebreak - -A function designator is an expression that has function type. - -\paragraph*{void} -The (nonexistent) value of a void expression (an expression that has type void) shall not -be used in any way, and implicit conversions (except to void) shall not be applied to -such an expression. If an expression of any other type is evaluated as a void expression, -its value or designator is discarded. (A void expression is only evaluated for its -side effects.) -\pagebreak - -\subsection{Lexical elements} -\paragraph*{Syntax} -\begin{lstlisting}[language=bnf] -token ::= keyword - | identifier - | constant - | string-literal - | punctuator -preprocessing-token ::= header-name - | identifier - | pp-number - | string-literal - | punctuator -\end{lstlisting} -\paragraph*{Constraints} -Each preprocessing token that is converted to a token shall have the lexical form of a -keyword, an identifier, a constant, a string literal, or a punctuator. - -\paragraph*{Semantics} -A token is the minimal lexical element of the language in translation steps six and seven. -The categories of tokens are: keywords, identifiers, constants, string literals, and -punctuators. A preprocessing token is the minimal lexical element of the language in -translation steps three through five. The categories of preprocessing tokens are: header -names, identifiers, preprocessing numbers, string literals, punctuators and other single -non-white-space characters that do not lexically match the other preprocessing token -categories. If a ' or a " character matches the last category, the behavior is undefined. -Preprocessing tokens can be separated by white space; this consists of comments (described -later), or white-space characters (space, horizontal tab, new-line, vertical tab, and form --feed), or both. In certain circumstances during translation step four, white space (or -the absence thereof) serves as more than preprocessing token separation. White space may -appear within a preprocessing token only as part of a header name or between the quotation -characters in a string literal. -\linebreak - -If the input stream has been parsed into preprocessing tokens up to a given character, the -next preprocessing token is the longest sequence of characters that could constitute a -preprocessing token. There is one exception to this rule: header name preprocessing tokens -are recognized only within \#include preprocessing directives and in implementation-defined -locations within \#pragma directives. In such contexts, a sequence of characters that -could be either a header name or string literal is recognized as the former. - -%% ->-> Keywords %% -\subsubsection{Keywords} -\paragraph*{Syntax} -\begin{lstlisting}[language=bnf] -keyword ::= enum | break | return | void - | case | float | volatile | for - | while | const | goto | bool - | continue | if | static | default - | inline | do | switch | else - | vector | entity -\end{lstlisting} -\paragraph*{Semantics} -The above tokens (case sensitive) are reserved (in translation step seven and eight) for -use as keywords, and shall not be used otherwise. - -%% ->->Identifiers %% -\subsubsection{Identifiers} -\begin{lstlisting}[language=bnf] -identifier ::= nondigit - | identifier nondigit - | identifier digit - -nondigit ::= _ | a | b | c | d | e | f | g | h | i - | j | k | l | m | n | o | p | q | r | s - | t | u | v | w | x | y | z | A | B | C - | D | E | F | G | H | I | J | K | L | M - | N | P | Q | R | S | T | U | V | W | X - | Y | Z - -digit ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 -\end{lstlisting} -\paragraph*{Semantics} -An identifier is a sequence of nondigit characters (including the underscore \_, the lower -case and upper case Latin letters, and other characters) and digits, which designates one -or more items. Lowercase and uppercase letters are distinct. There is a specific limit of -65535 characters for an identifier. -\linebreak - -When preprocessing tokens are converted to tokens during translation step six, if a -preprocessing token could not be converted to either a keyword or an identifier, it is -converted to a keyword. - -\paragraph*{Predefined identifiers} -Any identifiers that begin with the prefix \_\_builtin, or are within the reserved name -space are reserved by the implementation. - -%% ->->Constants %% -\subsubsection{Constants} -\begin{lstlisting}[language=bnf] -constant ::= integer-constant - | floating-constant - | enumeration-constant - | character-constant - | vector-constant - -integer-constant ::= decimal-constant - | octal-constant - | hexadecimal-constant - -decimal-constant ::= nonzero-digit - | decimal-constant digit - -octal-constant ::= 0 - | octal-constant octal-digit - -hexadecimal-constant ::= hexdecimal-prefix - hexadecimal-digit - | hexadecimal-digit - hexadecimal-constant - -hexadecimal-prefix: ::= 0x | 0X - -nonzero-digit ::= 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 - | 9 - -octal-digit ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 - -hexadecimal-digit ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 - | 8 | 9 | a | b | c | d | e | f - | A | B | C | D | E | F -\end{lstlisting} - -%% ->-> String literals %% -\subsubsection{String literals} -\begin{lstlisting}[language=bnf] -string-literal := " s-char-sequence " - -s-char-sequence := s-char - | s-char-sequence s-char - -s-char := ` | ! | @ | # | $ | % | ^ | & | * - | ( | ) | _ | - | + | = | { | } | [ - | ] | | | : | ; | ' | < | , | > | . - | ? | / | 1 | 2 | 3 | 4 | 5 | 6 | 7 - | 8 | 9 | 0 | q | w | e | r | t | y - | u | i | o | p | a | s | d | f | g - | h | j | k | l | z | x | c | v | b - | n | m | Q | W | E | R | T | Y | U - | I | O | P | A | S | D | F | G | | - | H | J | K | L | Z | X | C | V | B - | N | M -\end{lstlisting} -\paragraph*{Description} -A character string literal is a sequence of zero or more characters enclosed in -double-quotes, as in "xyz". -\linebreak - -The same considerations apply to each element of the sequence in a character string -literal as if it where an integer character constant, except that the single-quote -' is representable either by itself or by the escape sequence \textbackslash', but -the double-quote " shall be represented by the escape sequence \textbackslash". - -\paragraph*{Semantics} -In translation stage six, the character sequences specified by any sequence of adjacent -character string literal tokens are concatenated into a single character sequence. - -%% ->-> Punctuators %% -\subsubsection{Punctuators} -TODO: BNF - -A punctuator is a symbol that has independent syntactic and semantic significance. -Depending on context, it may specify an operation to be performed (which in turn -may yield a value or a function designator, produce a side effect, or some combination -thereof) in which case it is known as an operator (other forms of operator also exist -in some contexts). An operand is an item on which an operator acts. -\linebreak - -TODO: Trigraphs \& Digraphs - -\subsubsection{Header names} -TODO -\subsubsection{Preprocessing numbers} -TODO -\subsubsection{Comments} -Except within a character constant, a string literal, or a comment, the characters /* -introduce a comment. The contents of such a comment are examined only to identify -characters and to find the characters */ that terminate it. -\linebreak - -Except within a character constant, a string literal, or a comment, the characters // -introduce a comment that includes all characters up to, but not including, the next -new-line character. The contents of such a comment are examined only to identify -characters and to find the terminating new-line character. -\linebreak - -%% -> Expressions %% -\subsection{Expressions} -An expression is a sequence of operators and operands that specifies computation of a -value, or that designates an object or function, or that generates side effects, or that -performs a combination thereof. -\linebreak - -Between the previous and next sequence point an object shall have its stored value -modified at most once by the evaluation of an expression. Furthermore, the prior value -shall be read only to determine the value to be stored. -\linebreak - -The grouping of operators and operands is indicated by the syntax. Except as specified -later (for the function call (), \&\&, \textbar\textbar ?:, and comma operators), the -order of evaluation of sub-expressions and the order in which side effects take place -are both unspecified. -\linebreak - -Some operators (the unary \textasciitilde operator, and the binary operators \textless -\textless, \textgreater\textgreater, \&, \^, and \textbar, collectively describe bitwise -operators) are required to have operands that are either integer, or floating point with -zero points of decimal precision. -\linebreak - -If an exceptional condition occurs during the evaluation of an expression (that is, if -the result is not mathematically defined or not in the range or representable values for -its type), the behavior is undefined. - -%% ->-> Primary expressions %% -\subsubsection{Primary expressions} -\paragraph*{Syntax} -\begin{lstlisting}[language=bnf] -primary-expression ::= identifier - | constant - | string-literal - ( expression ) -\end{lstlisting} -\paragraph*{Semantics} -An identifier is a primary expression, provided it has been declared as designating an -object(in which case it is an lvalue) or a function(in which case it is a function -designator). -\linebreak - -A constant is a primary expression. Its type depends on its form and value. -\linebreak - -A string literal is a primary expression. It is an lvalue. -\linebreak - -A parenthesized expression is a primary expression. Its type and value identical to -those of the unparenthesized expression. It is an lvalue, a function designator, or a -void expression if the unparenthesized expression is, respectively, an lvalue, a -function designator, or a void expression. - -%% ->-> Constant expressions %% -\subsubsection{Constant expressions} -\paragraph*{Syntax} -\begin{lstlisting}[language=bnf] -constant-expression ::= conditional-expression -\end{lstlisting} -\paragraph*{Description} -A constant expression can be evaluated during translation rather than runtime, and -accordingly may be used in any place that a constant may be. -\paragraph*{Constraints} -\begin{itemize} - \item Constant expressions shall not contain assignment, increment, decrement, - function-call, or comma operators, except when contained within a subexpression - that is not evaluated. - \item Each constant expression shall evaluate to a constant that is in range of - representable values for its type. -\end{itemize} -\paragraph*{Semantics} -An expression that evaluates to a constant is required in several contexts. If a floating -point expression is evaluated in the translation environment, the arithmetic precision range -shall be as great is if the expression were being evaluated in the execution environment. -\linebreak - -An integer constant expression shall have integer type and shall only have operands that -are integer constants, enumeration constants, character constants, and floating constants -that are the immediate operand of casts. Cast operators in an integer constant expression -shall only convert arithmetic types to integer types. -\linebreak - -More latitude is permitted for constant expressions in initializers. Such a constant expression -shall be, or evaluate to an arithmetic constant expression. -\linebreak - -An arithmetic constant expression shall have arithmetic type and shall only have operands that -are integer constants, floating constants, enumeration constants, and character constants. Cast -operators in an arithmetic constant expression shall only convert arithmetic types to arithmetic -types. -\linebreak - -An implementation may accept other forms of constant expressions. -\linebreak - -The semantic rules for the evaluation of a constant expression are the same as for nonconstant -expressions. - - -\bibliographystyle{abbrv} -\bibliography{main} - -\end{document} diff --git a/exec.c b/exec.c deleted file mode 100644 index b24cda1..0000000 --- a/exec.c +++ /dev/null @@ -1,1660 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * Wolfgang Bumiller - * 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. - */ -#ifndef QCVM_LOOP -#include -#include -#include -#include - -#include "gmqcc.h" - -static void loaderror(const char *fmt, ...) -{ - int err = errno; - va_list ap; - va_start(ap, fmt); - vprintf(fmt, ap); - va_end(ap); - printf(": %s\n", util_strerror(err)); -} - -static void qcvmerror(qc_program_t *prog, const char *fmt, ...) -{ - va_list ap; - - prog->vmerror++; - - va_start(ap, fmt); - vprintf(fmt, ap); - va_end(ap); - putchar('\n'); -} - -qc_program_t* prog_load(const char *filename, bool skipversion) -{ - prog_header_t header; - qc_program_t *prog; - size_t i; - fs_file_t *file = fs_file_open(filename, "rb"); - - /* we need all those in order to support INSTR_STATE: */ - bool has_self = false, - has_time = false, - has_think = false, - has_nextthink = false, - has_frame = false; - - if (!file) - return NULL; - - if (fs_file_read(&header, sizeof(header), 1, file) != 1) { - loaderror("failed to read header from '%s'", filename); - fs_file_close(file); - return NULL; - } - - util_swap_header(&header); - - if (!skipversion && header.version != 6) { - loaderror("header says this is a version %i progs, we need version 6\n", header.version); - fs_file_close(file); - return NULL; - } - - prog = (qc_program_t*)mem_a(sizeof(qc_program_t)); - if (!prog) { - fs_file_close(file); - fprintf(stderr, "failed to allocate program data\n"); - return NULL; - } - memset(prog, 0, sizeof(*prog)); - - prog->entityfields = header.entfield; - prog->crc16 = header.crc16; - - prog->filename = util_strdup(filename); - if (!prog->filename) { - loaderror("failed to store program name"); - goto error; - } - -#define read_data(hdrvar, progvar, reserved) \ - if (fs_file_seek(file, header.hdrvar.offset, SEEK_SET) != 0) { \ - loaderror("seek failed"); \ - goto error; \ - } \ - if (fs_file_read ( \ - vec_add(prog->progvar, header.hdrvar.length + reserved), \ - sizeof(*prog->progvar), \ - header.hdrvar.length, \ - file \ - )!= header.hdrvar.length \ - ) { \ - loaderror("read failed"); \ - goto error; \ - } -#define read_data1(x) read_data(x, x, 0) -#define read_data2(x, y) read_data(x, x, y) - - read_data (statements, code, 0); - read_data1(defs); - read_data1(fields); - read_data1(functions); - read_data1(strings); - read_data2(globals, 2); /* reserve more in case a RETURN using with the global at "the end" exists */ - - util_swap_statements (prog->code); - util_swap_defs_fields(prog->defs); - util_swap_defs_fields(prog->fields); - util_swap_functions (prog->functions); - util_swap_globals (prog->globals); - - fs_file_close(file); - - /* profile counters */ - memset(vec_add(prog->profile, vec_size(prog->code)), 0, sizeof(prog->profile[0]) * vec_size(prog->code)); - - /* Add tempstring area */ - prog->tempstring_start = vec_size(prog->strings); - prog->tempstring_at = vec_size(prog->strings); - memset(vec_add(prog->strings, 16*1024), 0, 16*1024); - - /* spawn the world entity */ - vec_push(prog->entitypool, true); - memset(vec_add(prog->entitydata, prog->entityfields), 0, prog->entityfields * sizeof(prog->entitydata[0])); - prog->entities = 1; - - /* cache some globals and fields from names */ - for (i = 0; i < vec_size(prog->defs); ++i) { - const char *name = prog_getstring(prog, prog->defs[i].name); - if (!strcmp(name, "self")) { - prog->cached_globals.self = prog->defs[i].offset; - has_self = true; - } - else if (!strcmp(name, "time")) { - prog->cached_globals.time = prog->defs[i].offset; - has_time = true; - } - } - for (i = 0; i < vec_size(prog->fields); ++i) { - const char *name = prog_getstring(prog, prog->fields[i].name); - if (!strcmp(name, "think")) { - prog->cached_fields.think = prog->fields[i].offset; - has_think = true; - } - else if (!strcmp(name, "nextthink")) { - prog->cached_fields.nextthink = prog->fields[i].offset; - has_nextthink = true; - } - else if (!strcmp(name, "frame")) { - prog->cached_fields.frame = prog->fields[i].offset; - has_frame = true; - } - } - if (has_self && has_time && has_think && has_nextthink && has_frame) - prog->supports_state = true; - - return prog; - -error: - if (prog->filename) - mem_d(prog->filename); - vec_free(prog->code); - vec_free(prog->defs); - vec_free(prog->fields); - vec_free(prog->functions); - vec_free(prog->strings); - vec_free(prog->globals); - vec_free(prog->entitydata); - vec_free(prog->entitypool); - mem_d(prog); - - fs_file_close(file); - return NULL; -} - -void prog_delete(qc_program_t *prog) -{ - if (prog->filename) mem_d(prog->filename); - vec_free(prog->code); - vec_free(prog->defs); - vec_free(prog->fields); - vec_free(prog->functions); - vec_free(prog->strings); - vec_free(prog->globals); - vec_free(prog->entitydata); - vec_free(prog->entitypool); - vec_free(prog->localstack); - vec_free(prog->stack); - vec_free(prog->profile); - mem_d(prog); -} - -/*********************************************************************** - * VM code - */ - -const char* prog_getstring(qc_program_t *prog, qcint_t str) { - /* cast for return required for C++ */ - if (str < 0 || str >= (qcint_t)vec_size(prog->strings)) - return "<<>>"; - - return prog->strings + str; -} - -prog_section_def_t* prog_entfield(qc_program_t *prog, qcint_t off) { - size_t i; - for (i = 0; i < vec_size(prog->fields); ++i) { - if (prog->fields[i].offset == off) - return (prog->fields + i); - } - return NULL; -} - -prog_section_def_t* prog_getdef(qc_program_t *prog, qcint_t off) -{ - size_t i; - for (i = 0; i < vec_size(prog->defs); ++i) { - if (prog->defs[i].offset == off) - return (prog->defs + i); - } - return NULL; -} - -qcany_t* prog_getedict(qc_program_t *prog, qcint_t e) { - if (e >= (qcint_t)vec_size(prog->entitypool)) { - prog->vmerror++; - fprintf(stderr, "Accessing out of bounds edict %i\n", (int)e); - e = 0; - } - return (qcany_t*)(prog->entitydata + (prog->entityfields * e)); -} - -static qcint_t prog_spawn_entity(qc_program_t *prog) { - char *data; - qcint_t e; - for (e = 0; e < (qcint_t)vec_size(prog->entitypool); ++e) { - if (!prog->entitypool[e]) { - data = (char*)(prog->entitydata + (prog->entityfields * e)); - memset(data, 0, prog->entityfields * sizeof(qcint_t)); - return e; - } - } - vec_push(prog->entitypool, true); - prog->entities++; - data = (char*)vec_add(prog->entitydata, prog->entityfields); - memset(data, 0, prog->entityfields * sizeof(qcint_t)); - return e; -} - -static void prog_free_entity(qc_program_t *prog, qcint_t e) { - if (!e) { - prog->vmerror++; - fprintf(stderr, "Trying to free world entity\n"); - return; - } - if (e >= (qcint_t)vec_size(prog->entitypool)) { - prog->vmerror++; - fprintf(stderr, "Trying to free out of bounds entity\n"); - return; - } - if (!prog->entitypool[e]) { - prog->vmerror++; - fprintf(stderr, "Double free on entity\n"); - return; - } - prog->entitypool[e] = false; -} - -qcint_t prog_tempstring(qc_program_t *prog, const char *str) { - size_t len = strlen(str); - size_t at = prog->tempstring_at; - - /* when we reach the end we start over */ - if (at + len >= vec_size(prog->strings)) - at = prog->tempstring_start; - - /* when it doesn't fit, reallocate */ - if (at + len >= vec_size(prog->strings)) - { - (void)vec_add(prog->strings, len+1); - memcpy(prog->strings + at, str, len+1); - return at; - } - - /* when it fits, just copy */ - memcpy(prog->strings + at, str, len+1); - prog->tempstring_at += len+1; - return at; -} - -static size_t print_escaped_string(const char *str, size_t maxlen) { - size_t len = 2; - putchar('"'); - --maxlen; /* because we're lazy and have escape sequences */ - while (*str) { - if (len >= maxlen) { - putchar('.'); - putchar('.'); - putchar('.'); - len += 3; - break; - } - switch (*str) { - case '\a': len += 2; putchar('\\'); putchar('a'); break; - case '\b': len += 2; putchar('\\'); putchar('b'); break; - case '\r': len += 2; putchar('\\'); putchar('r'); break; - case '\n': len += 2; putchar('\\'); putchar('n'); break; - case '\t': len += 2; putchar('\\'); putchar('t'); break; - case '\f': len += 2; putchar('\\'); putchar('f'); break; - case '\v': len += 2; putchar('\\'); putchar('v'); break; - case '\\': len += 2; putchar('\\'); putchar('\\'); break; - case '"': len += 2; putchar('\\'); putchar('"'); break; - default: - ++len; - putchar(*str); - break; - } - ++str; - } - putchar('"'); - return len; -} - -static void trace_print_global(qc_program_t *prog, unsigned int glob, int vtype) { - static char spaces[28+1] = " "; - prog_section_def_t *def; - qcany_t *value; - int len; - - if (!glob) { - if ((len = printf(",")) == -1) - len = 0; - - goto done; - } - - def = prog_getdef(prog, glob); - value = (qcany_t*)(&prog->globals[glob]); - - len = printf("[@%u] ", glob); - if (def) { - const char *name = prog_getstring(prog, def->name); - if (name[0] == '#') - len += printf("$"); - else - len += printf("%s ", name); - vtype = def->type & DEF_TYPEMASK; - } - - switch (vtype) { - case TYPE_VOID: - case TYPE_ENTITY: - case TYPE_FIELD: - case TYPE_FUNCTION: - case TYPE_POINTER: - len += printf("(%i),", value->_int); - break; - case TYPE_VECTOR: - len += printf("'%g %g %g',", value->vector[0], - value->vector[1], - value->vector[2]); - break; - case TYPE_STRING: - if (value->string) - len += print_escaped_string(prog_getstring(prog, value->string), sizeof(spaces)-len-5); - else - len += printf("(null)"); - len += printf(","); - /* len += printf("\"%s\",", prog_getstring(prog, value->string)); */ - break; - case TYPE_FLOAT: - default: - len += printf("%g,", value->_float); - break; - } -done: - if (len < (int)sizeof(spaces)-1) { - spaces[sizeof(spaces)-1-len] = 0; - fs_file_puts((fs_file_t*)stdout, spaces); - spaces[sizeof(spaces)-1-len] = ' '; - } -} - -static void prog_print_statement(qc_program_t *prog, prog_section_statement_t *st) { - if (st->opcode >= VINSTR_END) { - printf("\n", st->opcode); - return; - } - if ((prog->xflags & VMXF_TRACE) && vec_size(prog->function_stack)) { - size_t i; - for (i = 0; i < vec_size(prog->function_stack); ++i) - printf("->"); - printf("%s:", vec_last(prog->function_stack)); - } - printf(" <> %-12s", util_instr_str[st->opcode]); - if (st->opcode >= INSTR_IF && - st->opcode <= INSTR_IFNOT) - { - trace_print_global(prog, st->o1.u1, TYPE_FLOAT); - printf("%d\n", st->o2.s1); - } - else if (st->opcode >= INSTR_CALL0 && - st->opcode <= INSTR_CALL8) - { - trace_print_global(prog, st->o1.u1, TYPE_FUNCTION); - printf("\n"); - } - else if (st->opcode == INSTR_GOTO) - { - printf("%i\n", st->o1.s1); - } - else - { - int t[3] = { TYPE_FLOAT, TYPE_FLOAT, TYPE_FLOAT }; - switch (st->opcode) - { - case INSTR_MUL_FV: - t[1] = t[2] = TYPE_VECTOR; - break; - case INSTR_MUL_VF: - t[0] = t[2] = TYPE_VECTOR; - break; - case INSTR_MUL_V: - t[0] = t[1] = TYPE_VECTOR; - break; - case INSTR_ADD_V: - case INSTR_SUB_V: - case INSTR_EQ_V: - case INSTR_NE_V: - t[0] = t[1] = t[2] = TYPE_VECTOR; - break; - case INSTR_EQ_S: - case INSTR_NE_S: - t[0] = t[1] = TYPE_STRING; - break; - case INSTR_STORE_F: - case INSTR_STOREP_F: - t[2] = -1; - break; - case INSTR_STORE_V: - t[0] = t[1] = TYPE_VECTOR; t[2] = -1; - break; - case INSTR_STORE_S: - t[0] = t[1] = TYPE_STRING; t[2] = -1; - break; - case INSTR_STORE_ENT: - t[0] = t[1] = TYPE_ENTITY; t[2] = -1; - break; - case INSTR_STORE_FLD: - t[0] = t[1] = TYPE_FIELD; t[2] = -1; - break; - case INSTR_STORE_FNC: - t[0] = t[1] = TYPE_FUNCTION; t[2] = -1; - break; - case INSTR_STOREP_V: - t[0] = TYPE_VECTOR; t[1] = TYPE_ENTITY; t[2] = -1; - break; - case INSTR_STOREP_S: - t[0] = TYPE_STRING; t[1] = TYPE_ENTITY; t[2] = -1; - break; - case INSTR_STOREP_ENT: - t[0] = TYPE_ENTITY; t[1] = TYPE_ENTITY; t[2] = -1; - break; - case INSTR_STOREP_FLD: - t[0] = TYPE_FIELD; t[1] = TYPE_ENTITY; t[2] = -1; - break; - case INSTR_STOREP_FNC: - t[0] = TYPE_FUNCTION; t[1] = TYPE_ENTITY; t[2] = -1; - break; - } - if (t[0] >= 0) trace_print_global(prog, st->o1.u1, t[0]); - else printf("(none), "); - if (t[1] >= 0) trace_print_global(prog, st->o2.u1, t[1]); - else printf("(none), "); - if (t[2] >= 0) trace_print_global(prog, st->o3.u1, t[2]); - else printf("(none)"); - printf("\n"); - } -} - -static qcint_t prog_enterfunction(qc_program_t *prog, prog_section_function_t *func) { - qc_exec_stack_t st; - size_t parampos; - int32_t p; - - /* back up locals */ - st.localsp = vec_size(prog->localstack); - st.stmt = prog->statement; - st.function = func; - - if (prog->xflags & VMXF_TRACE) { - const char *str = prog_getstring(prog, func->name); - vec_push(prog->function_stack, str); - } - -#ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS - if (vec_size(prog->stack)) - { - prog_section_function_t *cur; - cur = prog->stack[vec_size(prog->stack)-1].function; - if (cur) - { - qcint_t *globals = prog->globals + cur->firstlocal; - vec_append(prog->localstack, cur->locals, globals); - } - } -#else - { - qcint_t *globals = prog->globals + func->firstlocal; - vec_append(prog->localstack, func->locals, globals); - } -#endif - - /* copy parameters */ - parampos = func->firstlocal; - for (p = 0; p < func->nargs; ++p) - { - size_t s; - for (s = 0; s < func->argsize[p]; ++s) { - prog->globals[parampos] = prog->globals[OFS_PARM0 + 3*p + s]; - ++parampos; - } - } - - vec_push(prog->stack, st); - - return func->entry; -} - -static qcint_t prog_leavefunction(qc_program_t *prog) { - prog_section_function_t *prev = NULL; - size_t oldsp; - - qc_exec_stack_t st = vec_last(prog->stack); - - if (prog->xflags & VMXF_TRACE) { - if (vec_size(prog->function_stack)) - vec_pop(prog->function_stack); - } - -#ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS - if (vec_size(prog->stack) > 1) { - prev = prog->stack[vec_size(prog->stack)-2].function; - oldsp = prog->stack[vec_size(prog->stack)-2].localsp; - } -#else - prev = prog->stack[vec_size(prog->stack)-1].function; - oldsp = prog->stack[vec_size(prog->stack)-1].localsp; -#endif - if (prev) { - qcint_t *globals = prog->globals + prev->firstlocal; - memcpy(globals, prog->localstack + oldsp, prev->locals * sizeof(prog->localstack[0])); - /* vec_remove(prog->localstack, oldsp, vec_size(prog->localstack)-oldsp); */ - vec_shrinkto(prog->localstack, oldsp); - } - - vec_pop(prog->stack); - - return st.stmt - 1; /* offset the ++st */ -} - -bool prog_exec(qc_program_t *prog, prog_section_function_t *func, size_t flags, long maxjumps) { - long jumpcount = 0; - size_t oldxflags = prog->xflags; - prog_section_statement_t *st; - - prog->vmerror = 0; - prog->xflags = flags; - - st = prog->code + prog_enterfunction(prog, func); - --st; - switch (flags) - { - default: - case 0: - { -#define QCVM_LOOP 1 -#define QCVM_PROFILE 0 -#define QCVM_TRACE 0 -# include __FILE__ - } - case (VMXF_TRACE): - { -#define QCVM_PROFILE 0 -#define QCVM_TRACE 1 -# include __FILE__ - } - case (VMXF_PROFILE): - { -#define QCVM_PROFILE 1 -#define QCVM_TRACE 0 -# include __FILE__ - } - case (VMXF_TRACE|VMXF_PROFILE): - { -#define QCVM_PROFILE 1 -#define QCVM_TRACE 1 -# include __FILE__ - } - }; - -cleanup: - prog->xflags = oldxflags; - vec_free(prog->localstack); - vec_free(prog->stack); - if (prog->vmerror) - return false; - return true; -} - -/*********************************************************************** - * main for when building the standalone executor - */ - -#include - -const char *type_name[TYPE_COUNT] = { - "void", - "string", - "float", - "vector", - "entity", - "field", - "function", - "pointer", - "integer", - - "variant", - - "struct", - "union", - "array", - - "nil", - "noexpr" -}; - -typedef struct { - int vtype; - const char *value; -} qcvm_parameter; - -static qcvm_parameter *main_params = NULL; - -#define CheckArgs(num) do { \ - if (prog->argc != (num)) { \ - prog->vmerror++; \ - fprintf(stderr, "ERROR: invalid number of arguments for %s: %i, expected %i\n", \ - __func__, prog->argc, (num)); \ - return -1; \ - } \ -} while (0) - -#define GetGlobal(idx) ((qcany_t*)(prog->globals + (idx))) -#define GetArg(num) GetGlobal(OFS_PARM0 + 3*(num)) -#define Return(any) *(GetGlobal(OFS_RETURN)) = (any) - -static int qc_print(qc_program_t *prog) { - size_t i; - const char *laststr = NULL; - for (i = 0; i < (size_t)prog->argc; ++i) { - qcany_t *str = (qcany_t*)(prog->globals + OFS_PARM0 + 3*i); - laststr = prog_getstring(prog, str->string); - printf("%s", laststr); - } - if (laststr && (prog->xflags & VMXF_TRACE)) { - size_t len = strlen(laststr); - if (!len || laststr[len-1] != '\n') - printf("\n"); - } - return 0; -} - -static int qc_error(qc_program_t *prog) { - fprintf(stderr, "*** VM raised an error:\n"); - qc_print(prog); - prog->vmerror++; - return -1; -} - -static int qc_ftos(qc_program_t *prog) { - char buffer[512]; - qcany_t *num; - qcany_t str; - CheckArgs(1); - num = GetArg(0); - util_snprintf(buffer, sizeof(buffer), "%g", num->_float); - str.string = prog_tempstring(prog, buffer); - Return(str); - return 0; -} - -static int qc_stof(qc_program_t *prog) { - qcany_t *str; - qcany_t num; - CheckArgs(1); - str = GetArg(0); - num._float = (float)strtod(prog_getstring(prog, str->string), NULL); - Return(num); - return 0; -} - -static int qc_vtos(qc_program_t *prog) { - char buffer[512]; - qcany_t *num; - qcany_t str; - CheckArgs(1); - num = GetArg(0); - util_snprintf(buffer, sizeof(buffer), "'%g %g %g'", num->vector[0], num->vector[1], num->vector[2]); - str.string = prog_tempstring(prog, buffer); - Return(str); - return 0; -} - -static int qc_etos(qc_program_t *prog) { - char buffer[512]; - qcany_t *num; - qcany_t str; - CheckArgs(1); - num = GetArg(0); - util_snprintf(buffer, sizeof(buffer), "%i", num->_int); - str.string = prog_tempstring(prog, buffer); - Return(str); - return 0; -} - -static int qc_spawn(qc_program_t *prog) { - qcany_t ent; - CheckArgs(0); - ent.edict = prog_spawn_entity(prog); - Return(ent); - return (ent.edict ? 0 : -1); -} - -static int qc_kill(qc_program_t *prog) { - qcany_t *ent; - CheckArgs(1); - ent = GetArg(0); - prog_free_entity(prog, ent->edict); - return 0; -} - -static int qc_sqrt(qc_program_t *prog) { - qcany_t *num, out; - CheckArgs(1); - num = GetArg(0); - out._float = sqrt(num->_float); - Return(out); - return 0; -} - -static int qc_vlen(qc_program_t *prog) { - qcany_t *vec, len; - CheckArgs(1); - vec = GetArg(0); - len._float = sqrt(vec->vector[0] * vec->vector[0] + - vec->vector[1] * vec->vector[1] + - vec->vector[2] * vec->vector[2]); - Return(len); - return 0; -} - -static int qc_normalize(qc_program_t *prog) { - double len; - qcany_t *vec; - qcany_t out; - CheckArgs(1); - vec = GetArg(0); - len = sqrt(vec->vector[0] * vec->vector[0] + - vec->vector[1] * vec->vector[1] + - vec->vector[2] * vec->vector[2]); - if (len) - len = 1.0 / len; - else - len = 0; - out.vector[0] = len * vec->vector[0]; - out.vector[1] = len * vec->vector[1]; - out.vector[2] = len * vec->vector[2]; - Return(out); - return 0; -} - -static int qc_strcat(qc_program_t *prog) { - char *buffer; - size_t len1, len2; - qcany_t *str1, *str2; - qcany_t out; - - const char *cstr1; - const char *cstr2; - - CheckArgs(2); - str1 = GetArg(0); - str2 = GetArg(1); - cstr1 = prog_getstring(prog, str1->string); - cstr2 = prog_getstring(prog, str2->string); - len1 = strlen(cstr1); - len2 = strlen(cstr2); - buffer = (char*)mem_a(len1 + len2 + 1); - memcpy(buffer, cstr1, len1); - memcpy(buffer+len1, cstr2, len2+1); - out.string = prog_tempstring(prog, buffer); - mem_d(buffer); - Return(out); - return 0; -} - -static int qc_strcmp(qc_program_t *prog) { - qcany_t *str1, *str2; - qcany_t out; - - const char *cstr1; - const char *cstr2; - - if (prog->argc != 2 && prog->argc != 3) { - fprintf(stderr, "ERROR: invalid number of arguments for strcmp/strncmp: %i, expected 2 or 3\n", - prog->argc); - return -1; - } - - str1 = GetArg(0); - str2 = GetArg(1); - cstr1 = prog_getstring(prog, str1->string); - cstr2 = prog_getstring(prog, str2->string); - if (prog->argc == 3) - out._float = strncmp(cstr1, cstr2, GetArg(2)->_float); - else - out._float = strcmp(cstr1, cstr2); - Return(out); - return 0; -} - -static int qc_floor(qc_program_t *prog) { - qcany_t *num, out; - CheckArgs(1); - num = GetArg(0); - out._float = floor(num->_float); - Return(out); - return 0; -} - -static int qc_pow(qc_program_t *prog) { - qcany_t *base, *exp, out; - CheckArgs(2); - base = GetArg(0); - exp = GetArg(1); - out._float = powf(base->_float, exp->_float); - Return(out); - return 0; -} - -static prog_builtin_t qc_builtins[] = { - NULL, - &qc_print, /* 1 */ - &qc_ftos, /* 2 */ - &qc_spawn, /* 3 */ - &qc_kill, /* 4 */ - &qc_vtos, /* 5 */ - &qc_error, /* 6 */ - &qc_vlen, /* 7 */ - &qc_etos, /* 8 */ - &qc_stof, /* 9 */ - &qc_strcat, /* 10 */ - &qc_strcmp, /* 11 */ - &qc_normalize, /* 12 */ - &qc_sqrt, /* 13 */ - &qc_floor, /* 14 */ - &qc_pow /* 15 */ -}; - -static const char *arg0 = NULL; - -static void version(void) { - printf("GMQCC-QCVM %d.%d.%d Built %s %s\n", - GMQCC_VERSION_MAJOR, - GMQCC_VERSION_MINOR, - GMQCC_VERSION_PATCH, - __DATE__, - __TIME__ - ); -} - -static void usage(void) { - printf("usage: %s [options] [parameters] file\n", arg0); - printf("options:\n"); - printf(" -h, --help print this message\n" - " -trace trace the execution\n" - " -profile perform profiling during execution\n" - " -info print information from the prog's header\n" - " -disasm disassemble and exit\n" - " -disasm-func func disassemble and exit\n" - " -printdefs list the defs section\n" - " -printfields list the field section\n" - " -printfuns list functions information\n" - " -v be verbose\n" - " -vv be even more verbose\n"); - printf("parameters:\n"); - printf(" -vector pass a vector parameter to main()\n" - " -float pass a float parameter to main()\n" - " -string pass a string parameter to main() \n"); -} - -static void prog_main_setparams(qc_program_t *prog) { - size_t i; - qcany_t *arg; - - for (i = 0; i < vec_size(main_params); ++i) { - arg = GetGlobal(OFS_PARM0 + 3*i); - arg->vector[0] = 0; - arg->vector[1] = 0; - arg->vector[2] = 0; - switch (main_params[i].vtype) { - case TYPE_VECTOR: - (void)util_sscanf(main_params[i].value, " %f %f %f ", - &arg->vector[0], - &arg->vector[1], - &arg->vector[2]); - break; - case TYPE_FLOAT: - arg->_float = atof(main_params[i].value); - break; - case TYPE_STRING: - arg->string = prog_tempstring(prog, main_params[i].value); - break; - default: - fprintf(stderr, "error: unhandled parameter type: %i\n", main_params[i].vtype); - break; - } - } -} - -static void prog_disasm_function(qc_program_t *prog, size_t id); - -int main(int argc, char **argv) { - size_t i; - qcint_t fnmain = -1; - qc_program_t *prog; - size_t xflags = VMXF_DEFAULT; - bool opts_printfields = false; - bool opts_printdefs = false; - bool opts_printfuns = false; - bool opts_disasm = false; - bool opts_info = false; - bool noexec = false; - const char *progsfile = NULL; - const char **dis_list = NULL; - int opts_v = 0; - - arg0 = argv[0]; - - if (argc < 2) { - usage(); - exit(EXIT_FAILURE); - } - - while (argc > 1) { - if (!strcmp(argv[1], "-h") || - !strcmp(argv[1], "-help") || - !strcmp(argv[1], "--help")) - { - usage(); - exit(EXIT_SUCCESS); - } - else if (!strcmp(argv[1], "-v")) { - ++opts_v; - --argc; - ++argv; - } - else if (!strncmp(argv[1], "-vv", 3)) { - const char *av = argv[1]+1; - for (; *av; ++av) { - if (*av == 'v') - ++opts_v; - else { - usage(); - exit(EXIT_FAILURE); - } - } - --argc; - ++argv; - } - else if (!strcmp(argv[1], "-version") || - !strcmp(argv[1], "--version")) - { - version(); - exit(EXIT_SUCCESS); - } - else if (!strcmp(argv[1], "-trace")) { - --argc; - ++argv; - xflags |= VMXF_TRACE; - } - else if (!strcmp(argv[1], "-profile")) { - --argc; - ++argv; - xflags |= VMXF_PROFILE; - } - else if (!strcmp(argv[1], "-info")) { - --argc; - ++argv; - opts_info = true; - noexec = true; - } - else if (!strcmp(argv[1], "-disasm")) { - --argc; - ++argv; - opts_disasm = true; - noexec = true; - } - else if (!strcmp(argv[1], "-disasm-func")) { - --argc; - ++argv; - if (argc <= 1) { - usage(); - exit(EXIT_FAILURE); - } - vec_push(dis_list, argv[1]); - --argc; - ++argv; - noexec = true; - } - else if (!strcmp(argv[1], "-printdefs")) { - --argc; - ++argv; - opts_printdefs = true; - noexec = true; - } - else if (!strcmp(argv[1], "-printfuns")) { - --argc; - ++argv; - opts_printfuns = true; - noexec = true; - } - else if (!strcmp(argv[1], "-printfields")) { - --argc; - ++argv; - opts_printfields = true; - noexec = true; - } - else if (!strcmp(argv[1], "-vector") || - !strcmp(argv[1], "-string") || - !strcmp(argv[1], "-float") ) - { - qcvm_parameter p; - if (argv[1][1] == 'f') - p.vtype = TYPE_FLOAT; - else if (argv[1][1] == 's') - p.vtype = TYPE_STRING; - else if (argv[1][1] == 'v') - p.vtype = TYPE_VECTOR; - else - p.vtype = TYPE_VOID; - - --argc; - ++argv; - if (argc < 2) { - usage(); - exit(EXIT_FAILURE); - } - p.value = argv[1]; - - vec_push(main_params, p); - --argc; - ++argv; - } - else if (!strcmp(argv[1], "--")) { - --argc; - ++argv; - break; - } - else if (argv[1][0] != '-') { - if (progsfile) { - fprintf(stderr, "only 1 program file may be specified\n"); - usage(); - exit(EXIT_FAILURE); - } - progsfile = argv[1]; - --argc; - ++argv; - } - else - { - fprintf(stderr, "unknown parameter: %s\n", argv[1]); - usage(); - exit(EXIT_FAILURE); - } - } - - if (argc == 2 && !progsfile) { - progsfile = argv[1]; - --argc; - ++argv; - } - - if (!progsfile) { - fprintf(stderr, "must specify a program to execute\n"); - usage(); - exit(EXIT_FAILURE); - } - - prog = prog_load(progsfile, noexec); - if (!prog) { - fprintf(stderr, "failed to load program '%s'\n", progsfile); - exit(EXIT_FAILURE); - } - - prog->builtins = qc_builtins; - prog->builtins_count = GMQCC_ARRAY_COUNT(qc_builtins); - - if (opts_info) { - printf("Program's system-checksum = 0x%04x\n", (unsigned int)prog->crc16); - printf("Entity field space: %u\n", (unsigned int)prog->entityfields); - printf("Globals: %u\n", (unsigned int)vec_size(prog->globals)); - printf("Counts:\n" - " code: %lu\n" - " defs: %lu\n" - " fields: %lu\n" - " functions: %lu\n" - " strings: %lu\n", - (unsigned long)vec_size(prog->code), - (unsigned long)vec_size(prog->defs), - (unsigned long)vec_size(prog->fields), - (unsigned long)vec_size(prog->functions), - (unsigned long)vec_size(prog->strings)); - } - - if (opts_info) { - prog_delete(prog); - return 0; - } - for (i = 0; i < vec_size(dis_list); ++i) { - size_t k; - printf("Looking for `%s`\n", dis_list[i]); - for (k = 1; k < vec_size(prog->functions); ++k) { - const char *name = prog_getstring(prog, prog->functions[k].name); - if (!strcmp(name, dis_list[i])) { - prog_disasm_function(prog, k); - break; - } - } - } - if (opts_disasm) { - for (i = 1; i < vec_size(prog->functions); ++i) - prog_disasm_function(prog, i); - return 0; - } - if (opts_printdefs) { - const char *getstring = NULL; - for (i = 0; i < vec_size(prog->defs); ++i) { - printf("Global: %8s %-16s at %u%s", - type_name[prog->defs[i].type & DEF_TYPEMASK], - prog_getstring(prog, prog->defs[i].name), - (unsigned int)prog->defs[i].offset, - ((prog->defs[i].type & DEF_SAVEGLOBAL) ? " [SAVE]" : "")); - if (opts_v) { - switch (prog->defs[i].type & DEF_TYPEMASK) { - case TYPE_FLOAT: - printf(" [init: %g]", ((qcany_t*)(prog->globals + prog->defs[i].offset))->_float); - break; - case TYPE_INTEGER: - printf(" [init: %i]", (int)( ((qcany_t*)(prog->globals + prog->defs[i].offset))->_int )); - break; - case TYPE_ENTITY: - case TYPE_FUNCTION: - case TYPE_FIELD: - case TYPE_POINTER: - printf(" [init: %u]", (unsigned)( ((qcany_t*)(prog->globals + prog->defs[i].offset))->_int )); - break; - case TYPE_STRING: - getstring = prog_getstring(prog, ((qcany_t*)(prog->globals + prog->defs[i].offset))->string); - printf(" [init: `"); - print_escaped_string(getstring, strlen(getstring)); - printf("`]\n"); - break; - default: - break; - } - } - printf("\n"); - } - } - if (opts_printfields) { - for (i = 0; i < vec_size(prog->fields); ++i) { - printf("Field: %8s %-16s at %u%s\n", - type_name[prog->fields[i].type], - prog_getstring(prog, prog->fields[i].name), - (unsigned int)prog->fields[i].offset, - ((prog->fields[i].type & DEF_SAVEGLOBAL) ? " [SAVE]" : "")); - } - } - if (opts_printfuns) { - for (i = 0; i < vec_size(prog->functions); ++i) { - int32_t a; - printf("Function: %-16s taking %u parameters:(", - prog_getstring(prog, prog->functions[i].name), - (unsigned int)prog->functions[i].nargs); - for (a = 0; a < prog->functions[i].nargs; ++a) { - printf(" %i", prog->functions[i].argsize[a]); - } - if (opts_v > 1) { - int32_t start = prog->functions[i].entry; - if (start < 0) - printf(") builtin %i\n", (int)-start); - else { - size_t funsize = 0; - prog_section_statement_t *st = prog->code + start; - for (;st->opcode != INSTR_DONE; ++st) - ++funsize; - printf(") - %lu instructions", (unsigned long)funsize); - if (opts_v > 2) { - printf(" - locals: %i + %i\n", - prog->functions[i].firstlocal, - prog->functions[i].locals); - } - else - printf("\n"); - } - } - else if (opts_v) { - printf(") locals: %i + %i\n", - prog->functions[i].firstlocal, - prog->functions[i].locals); - } - else - printf(")\n"); - } - } - if (!noexec) { - for (i = 1; i < vec_size(prog->functions); ++i) { - const char *name = prog_getstring(prog, prog->functions[i].name); - if (!strcmp(name, "main")) - fnmain = (qcint_t)i; - } - if (fnmain > 0) - { - prog_main_setparams(prog); - prog_exec(prog, &prog->functions[fnmain], xflags, VM_JUMPS_DEFAULT); - } - else - fprintf(stderr, "No main function found\n"); - } - - prog_delete(prog); - return 0; -} - -static void prog_disasm_function(qc_program_t *prog, size_t id) { - prog_section_function_t *fdef = prog->functions + id; - prog_section_statement_t *st; - - if (fdef->entry < 0) { - printf("FUNCTION \"%s\" = builtin #%i\n", prog_getstring(prog, fdef->name), (int)-fdef->entry); - return; - } - else - printf("FUNCTION \"%s\"\n", prog_getstring(prog, fdef->name)); - - st = prog->code + fdef->entry; - while (st->opcode != INSTR_DONE) { - prog_print_statement(prog, st); - ++st; - } -} -#else /* !QCVM_LOOP */ -/* - * Everything from here on is not including into the compilation of the - * executor. This is simply code that is #included via #include __FILE__ - * see when QCVM_LOOP is defined, the rest of the code above do not get - * re-included. So this really just acts like one large macro, but it - * sort of isn't, which makes it nicer looking. - */ - -#define OPA ( (qcany_t*) (prog->globals + st->o1.u1) ) -#define OPB ( (qcany_t*) (prog->globals + st->o2.u1) ) -#define OPC ( (qcany_t*) (prog->globals + st->o3.u1) ) - -#define GLOBAL(x) ( (qcany_t*) (prog->globals + (x)) ) - -/* to be consistent with current darkplaces behaviour */ -#if !defined(FLOAT_IS_TRUE_FOR_INT) -# define FLOAT_IS_TRUE_FOR_INT(x) ( (x) & 0x7FFFFFFF ) -#endif - -while (prog->vmerror == 0) { - prog_section_function_t *newf; - qcany_t *ed; - qcany_t *ptr; - - ++st; - -#if QCVM_PROFILE - prog->profile[st - prog->code]++; -#endif - -#if QCVM_TRACE - prog_print_statement(prog, st); -#endif - - switch (st->opcode) - { - default: - qcvmerror(prog, "Illegal instruction in %s\n", prog->filename); - goto cleanup; - - case INSTR_DONE: - case INSTR_RETURN: - /* TODO: add instruction count to function profile count */ - GLOBAL(OFS_RETURN)->ivector[0] = OPA->ivector[0]; - GLOBAL(OFS_RETURN)->ivector[1] = OPA->ivector[1]; - GLOBAL(OFS_RETURN)->ivector[2] = OPA->ivector[2]; - - st = prog->code + prog_leavefunction(prog); - if (!vec_size(prog->stack)) - goto cleanup; - - break; - - case INSTR_MUL_F: - OPC->_float = OPA->_float * OPB->_float; - break; - case INSTR_MUL_V: - OPC->_float = OPA->vector[0]*OPB->vector[0] + - OPA->vector[1]*OPB->vector[1] + - OPA->vector[2]*OPB->vector[2]; - break; - case INSTR_MUL_FV: - { - qcfloat_t f = OPA->_float; - OPC->vector[0] = f * OPB->vector[0]; - OPC->vector[1] = f * OPB->vector[1]; - OPC->vector[2] = f * OPB->vector[2]; - break; - } - case INSTR_MUL_VF: - { - qcfloat_t f = OPB->_float; - OPC->vector[0] = f * OPA->vector[0]; - OPC->vector[1] = f * OPA->vector[1]; - OPC->vector[2] = f * OPA->vector[2]; - break; - } - case INSTR_DIV_F: - if (OPB->_float != 0.0f) - OPC->_float = OPA->_float / OPB->_float; - else - OPC->_float = 0; - break; - - case INSTR_ADD_F: - OPC->_float = OPA->_float + OPB->_float; - break; - case INSTR_ADD_V: - OPC->vector[0] = OPA->vector[0] + OPB->vector[0]; - OPC->vector[1] = OPA->vector[1] + OPB->vector[1]; - OPC->vector[2] = OPA->vector[2] + OPB->vector[2]; - break; - case INSTR_SUB_F: - OPC->_float = OPA->_float - OPB->_float; - break; - case INSTR_SUB_V: - OPC->vector[0] = OPA->vector[0] - OPB->vector[0]; - OPC->vector[1] = OPA->vector[1] - OPB->vector[1]; - OPC->vector[2] = OPA->vector[2] - OPB->vector[2]; - break; - - case INSTR_EQ_F: - OPC->_float = (OPA->_float == OPB->_float); - break; - case INSTR_EQ_V: - OPC->_float = ((OPA->vector[0] == OPB->vector[0]) && - (OPA->vector[1] == OPB->vector[1]) && - (OPA->vector[2] == OPB->vector[2]) ); - break; - case INSTR_EQ_S: - OPC->_float = !strcmp(prog_getstring(prog, OPA->string), - prog_getstring(prog, OPB->string)); - break; - case INSTR_EQ_E: - OPC->_float = (OPA->_int == OPB->_int); - break; - case INSTR_EQ_FNC: - OPC->_float = (OPA->function == OPB->function); - break; - case INSTR_NE_F: - OPC->_float = (OPA->_float != OPB->_float); - break; - case INSTR_NE_V: - OPC->_float = ((OPA->vector[0] != OPB->vector[0]) || - (OPA->vector[1] != OPB->vector[1]) || - (OPA->vector[2] != OPB->vector[2]) ); - break; - case INSTR_NE_S: - OPC->_float = !!strcmp(prog_getstring(prog, OPA->string), - prog_getstring(prog, OPB->string)); - break; - case INSTR_NE_E: - OPC->_float = (OPA->_int != OPB->_int); - break; - case INSTR_NE_FNC: - OPC->_float = (OPA->function != OPB->function); - break; - - case INSTR_LE: - OPC->_float = (OPA->_float <= OPB->_float); - break; - case INSTR_GE: - OPC->_float = (OPA->_float >= OPB->_float); - break; - case INSTR_LT: - OPC->_float = (OPA->_float < OPB->_float); - break; - case INSTR_GT: - OPC->_float = (OPA->_float > OPB->_float); - break; - - case INSTR_LOAD_F: - case INSTR_LOAD_S: - case INSTR_LOAD_FLD: - case INSTR_LOAD_ENT: - case INSTR_LOAD_FNC: - if (OPA->edict < 0 || OPA->edict >= prog->entities) { - qcvmerror(prog, "progs `%s` attempted to read an out of bounds entity", prog->filename); - goto cleanup; - } - if ((unsigned int)(OPB->_int) >= (unsigned int)(prog->entityfields)) { - qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)", - prog->filename, - OPB->_int); - goto cleanup; - } - ed = prog_getedict(prog, OPA->edict); - OPC->_int = ((qcany_t*)( ((qcint_t*)ed) + OPB->_int ))->_int; - break; - case INSTR_LOAD_V: - if (OPA->edict < 0 || OPA->edict >= prog->entities) { - qcvmerror(prog, "progs `%s` attempted to read an out of bounds entity", prog->filename); - goto cleanup; - } - if (OPB->_int < 0 || OPB->_int + 3 > (qcint_t)prog->entityfields) - { - qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)", - prog->filename, - OPB->_int + 2); - goto cleanup; - } - ed = prog_getedict(prog, OPA->edict); - ptr = (qcany_t*)( ((qcint_t*)ed) + OPB->_int ); - OPC->ivector[0] = ptr->ivector[0]; - OPC->ivector[1] = ptr->ivector[1]; - OPC->ivector[2] = ptr->ivector[2]; - break; - - case INSTR_ADDRESS: - if (OPA->edict < 0 || OPA->edict >= prog->entities) { - qcvmerror(prog, "prog `%s` attempted to address an out of bounds entity %i", prog->filename, OPA->edict); - goto cleanup; - } - if ((unsigned int)(OPB->_int) >= (unsigned int)(prog->entityfields)) - { - qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)", - prog->filename, - OPB->_int); - goto cleanup; - } - - ed = prog_getedict(prog, OPA->edict); - OPC->_int = ((qcint_t*)ed) - prog->entitydata + OPB->_int; - break; - - case INSTR_STORE_F: - case INSTR_STORE_S: - case INSTR_STORE_ENT: - case INSTR_STORE_FLD: - case INSTR_STORE_FNC: - OPB->_int = OPA->_int; - break; - case INSTR_STORE_V: - OPB->ivector[0] = OPA->ivector[0]; - OPB->ivector[1] = OPA->ivector[1]; - OPB->ivector[2] = OPA->ivector[2]; - break; - - case INSTR_STOREP_F: - case INSTR_STOREP_S: - case INSTR_STOREP_ENT: - case INSTR_STOREP_FLD: - case INSTR_STOREP_FNC: - if (OPB->_int < 0 || OPB->_int >= (qcint_t)vec_size(prog->entitydata)) { - qcvmerror(prog, "`%s` attempted to write to an out of bounds edict (%i)", prog->filename, OPB->_int); - goto cleanup; - } - if (OPB->_int < (qcint_t)prog->entityfields && !prog->allowworldwrites) - qcvmerror(prog, "`%s` tried to assign to world.%s (field %i)\n", - prog->filename, - prog_getstring(prog, prog_entfield(prog, OPB->_int)->name), - OPB->_int); - ptr = (qcany_t*)(prog->entitydata + OPB->_int); - ptr->_int = OPA->_int; - break; - case INSTR_STOREP_V: - if (OPB->_int < 0 || OPB->_int + 2 >= (qcint_t)vec_size(prog->entitydata)) { - qcvmerror(prog, "`%s` attempted to write to an out of bounds edict (%i)", prog->filename, OPB->_int); - goto cleanup; - } - if (OPB->_int < (qcint_t)prog->entityfields && !prog->allowworldwrites) - qcvmerror(prog, "`%s` tried to assign to world.%s (field %i)\n", - prog->filename, - prog_getstring(prog, prog_entfield(prog, OPB->_int)->name), - OPB->_int); - ptr = (qcany_t*)(prog->entitydata + OPB->_int); - ptr->ivector[0] = OPA->ivector[0]; - ptr->ivector[1] = OPA->ivector[1]; - ptr->ivector[2] = OPA->ivector[2]; - break; - - case INSTR_NOT_F: - OPC->_float = !FLOAT_IS_TRUE_FOR_INT(OPA->_int); - break; - case INSTR_NOT_V: - OPC->_float = !OPA->vector[0] && - !OPA->vector[1] && - !OPA->vector[2]; - break; - case INSTR_NOT_S: - OPC->_float = !OPA->string || - !*prog_getstring(prog, OPA->string); - break; - case INSTR_NOT_ENT: - OPC->_float = (OPA->edict == 0); - break; - case INSTR_NOT_FNC: - OPC->_float = !OPA->function; - break; - - case INSTR_IF: - /* this is consistent with darkplaces' behaviour */ - if(FLOAT_IS_TRUE_FOR_INT(OPA->_int)) - { - st += st->o2.s1 - 1; /* offset the s++ */ - if (++jumpcount >= maxjumps) - qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount); - } - break; - case INSTR_IFNOT: - if(!FLOAT_IS_TRUE_FOR_INT(OPA->_int)) - { - st += st->o2.s1 - 1; /* offset the s++ */ - if (++jumpcount >= maxjumps) - qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount); - } - break; - - case INSTR_CALL0: - case INSTR_CALL1: - case INSTR_CALL2: - case INSTR_CALL3: - case INSTR_CALL4: - case INSTR_CALL5: - case INSTR_CALL6: - case INSTR_CALL7: - case INSTR_CALL8: - prog->argc = st->opcode - INSTR_CALL0; - if (!OPA->function) - qcvmerror(prog, "NULL function in `%s`", prog->filename); - - if(!OPA->function || OPA->function >= (qcint_t)vec_size(prog->functions)) - { - qcvmerror(prog, "CALL outside the program in `%s`", prog->filename); - goto cleanup; - } - - newf = &prog->functions[OPA->function]; - newf->profile++; - - prog->statement = (st - prog->code) + 1; - - if (newf->entry < 0) - { - /* negative statements are built in functions */ - qcint_t builtinnumber = -newf->entry; - if (builtinnumber < (qcint_t)prog->builtins_count && prog->builtins[builtinnumber]) - prog->builtins[builtinnumber](prog); - else - qcvmerror(prog, "No such builtin #%i in %s! Try updating your gmqcc sources", - builtinnumber, prog->filename); - } - else - st = prog->code + prog_enterfunction(prog, newf) - 1; /* offset st++ */ - if (prog->vmerror) - goto cleanup; - break; - - case INSTR_STATE: - { - qcfloat_t *nextthink; - qcfloat_t *time; - qcfloat_t *frame; - if (!prog->supports_state) { - qcvmerror(prog, "`%s` tried to execute a STATE operation but misses its defs!", prog->filename); - goto cleanup; - } - ed = prog_getedict(prog, prog->globals[prog->cached_globals.self]); - ((qcint_t*)ed)[prog->cached_fields.think] = OPB->function; - - frame = (qcfloat_t*)&((qcint_t*)ed)[prog->cached_fields.frame]; - *frame = OPA->_float; - nextthink = (qcfloat_t*)&((qcint_t*)ed)[prog->cached_fields.nextthink]; - time = (qcfloat_t*)(prog->globals + prog->cached_globals.time); - *nextthink = *time + 0.1; - break; - } - - case INSTR_GOTO: - st += st->o1.s1 - 1; /* offset the s++ */ - if (++jumpcount == 10000000) - qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount); - break; - - case INSTR_AND: - OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) && - FLOAT_IS_TRUE_FOR_INT(OPB->_int); - break; - case INSTR_OR: - OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) || - FLOAT_IS_TRUE_FOR_INT(OPB->_int); - break; - - case INSTR_BITAND: - OPC->_float = ((int)OPA->_float) & ((int)OPB->_float); - break; - case INSTR_BITOR: - OPC->_float = ((int)OPA->_float) | ((int)OPB->_float); - break; - } -} - -#undef QCVM_PROFILE -#undef QCVM_TRACE -#endif /* !QCVM_LOOP */ diff --git a/exec.cpp b/exec.cpp new file mode 100644 index 0000000..e76004e --- /dev/null +++ b/exec.cpp @@ -0,0 +1,1622 @@ +#ifndef QCVM_LOOP +#include +#include +#include +#include + +#include "gmqcc.h" + +static void loaderror(const char *fmt, ...) +{ + int err = errno; + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + printf(": %s\n", util_strerror(err)); +} + +static void qcvmerror(qc_program_t *prog, const char *fmt, ...) +{ + va_list ap; + + prog->vmerror++; + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + putchar('\n'); +} + +qc_program_t* prog_load(const char *filename, bool skipversion) +{ + prog_header_t header; + qc_program_t *prog; + FILE *file = fopen(filename, "rb"); + + /* we need all those in order to support INSTR_STATE: */ + bool has_self = false, + has_time = false, + has_think = false, + has_nextthink = false, + has_frame = false; + + if (!file) + return nullptr; + + if (fread(&header, sizeof(header), 1, file) != 1) { + loaderror("failed to read header from '%s'", filename); + fclose(file); + return nullptr; + } + + util_swap_header(header); + + if (!skipversion && header.version != 6) { + loaderror("header says this is a version %i progs, we need version 6\n", header.version); + fclose(file); + return nullptr; + } + + prog = (qc_program_t*)mem_a(sizeof(qc_program_t)); + if (!prog) { + fclose(file); + fprintf(stderr, "failed to allocate program data\n"); + return nullptr; + } + memset(prog, 0, sizeof(*prog)); + + prog->entityfields = header.entfield; + prog->crc16 = header.crc16; + + prog->filename = util_strdup(filename); + if (!prog->filename) { + loaderror("failed to store program name"); + goto error; + } + +#define read_data(hdrvar, progvar, reserved) \ + if (fseek(file, header.hdrvar.offset, SEEK_SET) != 0) { \ + loaderror("seek failed"); \ + goto error; \ + } \ + prog->progvar.resize(header.hdrvar.length + reserved); \ + if (fread( \ + &prog->progvar[0], \ + sizeof(prog->progvar[0]), \ + header.hdrvar.length, \ + file \ + )!= header.hdrvar.length \ + ) { \ + loaderror("read failed"); \ + goto error; \ + } +#define read_data1(x) read_data(x, x, 0) +#define read_data2(x, y) read_data(x, x, y) + + read_data (statements, code, 0); + read_data1(defs); + read_data1(fields); + read_data1(functions); + read_data1(strings); + read_data2(globals, 2); /* reserve more in case a RETURN using with the global at "the end" exists */ + + util_swap_statements(prog->code); + util_swap_defs_fields(prog->defs); + util_swap_defs_fields(prog->fields); + util_swap_functions(prog->functions); + util_swap_globals(prog->globals); + + fclose(file); + + /* profile counters */ + memset(vec_add(prog->profile, prog->code.size()), 0, sizeof(prog->profile[0]) * prog->code.size()); + + /* Add tempstring area */ + prog->tempstring_start = prog->strings.size(); + prog->tempstring_at = prog->strings.size(); + + prog->strings.resize(prog->strings.size() + 16*1024, '\0'); + + /* spawn the world entity */ + vec_push(prog->entitypool, true); + memset(vec_add(prog->entitydata, prog->entityfields), 0, prog->entityfields * sizeof(prog->entitydata[0])); + prog->entities = 1; + + /* cache some globals and fields from names */ + for (auto &it : prog->defs) { + const char *name = prog_getstring(prog, it.name); + if (!strcmp(name, "self")) { + prog->cached_globals.self = it.offset; + has_self = true; + } + else if (!strcmp(name, "time")) { + prog->cached_globals.time = it.offset; + has_time = true; + } + } + for (auto &it : prog->fields) { + const char *name = prog_getstring(prog, it.name); + if (!strcmp(name, "think")) { + prog->cached_fields.think = it.offset; + has_think = true; + } + else if (!strcmp(name, "nextthink")) { + prog->cached_fields.nextthink = it.offset; + has_nextthink = true; + } + else if (!strcmp(name, "frame")) { + prog->cached_fields.frame = it.offset; + has_frame = true; + } + } + if (has_self && has_time && has_think && has_nextthink && has_frame) + prog->supports_state = true; + + return prog; + +error: + if (prog->filename) + mem_d(prog->filename); + vec_free(prog->entitydata); + vec_free(prog->entitypool); + mem_d(prog); + + fclose(file); + return nullptr; +} + +void prog_delete(qc_program_t *prog) +{ + if (prog->filename) mem_d(prog->filename); + vec_free(prog->entitydata); + vec_free(prog->entitypool); + vec_free(prog->localstack); + vec_free(prog->stack); + vec_free(prog->profile); + mem_d(prog); +} + +/*********************************************************************** + * VM code + */ + +const char* prog_getstring(qc_program_t *prog, qcint_t str) { + /* cast for return required for C++ */ + if (str < 0 || str >= (qcint_t)prog->strings.size()) + return "<<>>"; + + return &prog->strings[0] + str; +} + +prog_section_def_t* prog_entfield(qc_program_t *prog, qcint_t off) { + for (auto &it : prog->fields) + if (it.offset == off) + return ⁢ + return nullptr; +} + +prog_section_def_t* prog_getdef(qc_program_t *prog, qcint_t off) +{ + for (auto &it : prog->defs) + if (it.offset == off) + return ⁢ + return nullptr; +} + +qcany_t* prog_getedict(qc_program_t *prog, qcint_t e) { + if (e >= (qcint_t)vec_size(prog->entitypool)) { + prog->vmerror++; + fprintf(stderr, "Accessing out of bounds edict %i\n", (int)e); + e = 0; + } + return (qcany_t*)(prog->entitydata + (prog->entityfields * e)); +} + +static qcint_t prog_spawn_entity(qc_program_t *prog) { + char *data; + qcint_t e; + for (e = 0; e < (qcint_t)vec_size(prog->entitypool); ++e) { + if (!prog->entitypool[e]) { + data = (char*)(prog->entitydata + (prog->entityfields * e)); + memset(data, 0, prog->entityfields * sizeof(qcint_t)); + return e; + } + } + vec_push(prog->entitypool, true); + prog->entities++; + data = (char*)vec_add(prog->entitydata, prog->entityfields); + memset(data, 0, prog->entityfields * sizeof(qcint_t)); + return e; +} + +static void prog_free_entity(qc_program_t *prog, qcint_t e) { + if (!e) { + prog->vmerror++; + fprintf(stderr, "Trying to free world entity\n"); + return; + } + if (e >= (qcint_t)vec_size(prog->entitypool)) { + prog->vmerror++; + fprintf(stderr, "Trying to free out of bounds entity\n"); + return; + } + if (!prog->entitypool[e]) { + prog->vmerror++; + fprintf(stderr, "Double free on entity\n"); + return; + } + prog->entitypool[e] = false; +} + +qcint_t prog_tempstring(qc_program_t *prog, const char *str) { + size_t len = strlen(str); + size_t at = prog->tempstring_at; + + /* when we reach the end we start over */ + if (at + len >= prog->strings.size()) + at = prog->tempstring_start; + + /* when it doesn't fit, reallocate */ + if (at + len >= prog->strings.size()) + { + prog->strings.resize(prog->strings.size() + len+1); + memcpy(&prog->strings[0] + at, str, len+1); + return at; + } + + /* when it fits, just copy */ + memcpy(&prog->strings[0] + at, str, len+1); + prog->tempstring_at += len+1; + return at; +} + +static size_t print_escaped_string(const char *str, size_t maxlen) { + size_t len = 2; + putchar('"'); + --maxlen; /* because we're lazy and have escape sequences */ + while (*str) { + if (len >= maxlen) { + putchar('.'); + putchar('.'); + putchar('.'); + len += 3; + break; + } + switch (*str) { + case '\a': len += 2; putchar('\\'); putchar('a'); break; + case '\b': len += 2; putchar('\\'); putchar('b'); break; + case '\r': len += 2; putchar('\\'); putchar('r'); break; + case '\n': len += 2; putchar('\\'); putchar('n'); break; + case '\t': len += 2; putchar('\\'); putchar('t'); break; + case '\f': len += 2; putchar('\\'); putchar('f'); break; + case '\v': len += 2; putchar('\\'); putchar('v'); break; + case '\\': len += 2; putchar('\\'); putchar('\\'); break; + case '"': len += 2; putchar('\\'); putchar('"'); break; + default: + ++len; + putchar(*str); + break; + } + ++str; + } + putchar('"'); + return len; +} + +static void trace_print_global(qc_program_t *prog, unsigned int glob, int vtype) { + static char spaces[28+1] = " "; + prog_section_def_t *def; + qcany_t *value; + int len; + + if (!glob) { + if ((len = printf(",")) == -1) + len = 0; + + goto done; + } + + def = prog_getdef(prog, glob); + value = (qcany_t*)(&prog->globals[glob]); + + len = printf("[@%u] ", glob); + if (def) { + const char *name = prog_getstring(prog, def->name); + if (name[0] == '#') + len += printf("$"); + else + len += printf("%s ", name); + vtype = def->type & DEF_TYPEMASK; + } + + switch (vtype) { + case TYPE_VOID: + case TYPE_ENTITY: + case TYPE_FIELD: + case TYPE_FUNCTION: + case TYPE_POINTER: + len += printf("(%i),", value->_int); + break; + case TYPE_VECTOR: + len += printf("'%g %g %g',", value->vector[0], + value->vector[1], + value->vector[2]); + break; + case TYPE_STRING: + if (value->string) + len += print_escaped_string(prog_getstring(prog, value->string), sizeof(spaces)-len-5); + else + len += printf("(null)"); + len += printf(","); + /* len += printf("\"%s\",", prog_getstring(prog, value->string)); */ + break; + case TYPE_FLOAT: + default: + len += printf("%g,", value->_float); + break; + } +done: + if (len < (int)sizeof(spaces)-1) { + spaces[sizeof(spaces)-1-len] = 0; + fputs(spaces, stdout); + spaces[sizeof(spaces)-1-len] = ' '; + } +} + +static void prog_print_statement(qc_program_t *prog, prog_section_statement_t *st) { + if (st->opcode >= VINSTR_END) { + printf("\n", st->opcode); + return; + } + if ((prog->xflags & VMXF_TRACE) && vec_size(prog->function_stack)) { + size_t i; + for (i = 0; i < vec_size(prog->function_stack); ++i) + printf("->"); + printf("%s:", vec_last(prog->function_stack)); + } + printf(" <> %-12s", util_instr_str[st->opcode]); + if (st->opcode >= INSTR_IF && + st->opcode <= INSTR_IFNOT) + { + trace_print_global(prog, st->o1.u1, TYPE_FLOAT); + printf("%d\n", st->o2.s1); + } + else if (st->opcode >= INSTR_CALL0 && + st->opcode <= INSTR_CALL8) + { + trace_print_global(prog, st->o1.u1, TYPE_FUNCTION); + printf("\n"); + } + else if (st->opcode == INSTR_GOTO) + { + printf("%i\n", st->o1.s1); + } + else + { + int t[3] = { TYPE_FLOAT, TYPE_FLOAT, TYPE_FLOAT }; + switch (st->opcode) + { + case INSTR_MUL_FV: + t[1] = t[2] = TYPE_VECTOR; + break; + case INSTR_MUL_VF: + t[0] = t[2] = TYPE_VECTOR; + break; + case INSTR_MUL_V: + t[0] = t[1] = TYPE_VECTOR; + break; + case INSTR_ADD_V: + case INSTR_SUB_V: + case INSTR_EQ_V: + case INSTR_NE_V: + t[0] = t[1] = t[2] = TYPE_VECTOR; + break; + case INSTR_EQ_S: + case INSTR_NE_S: + t[0] = t[1] = TYPE_STRING; + break; + case INSTR_STORE_F: + case INSTR_STOREP_F: + t[2] = -1; + break; + case INSTR_STORE_V: + t[0] = t[1] = TYPE_VECTOR; t[2] = -1; + break; + case INSTR_STORE_S: + t[0] = t[1] = TYPE_STRING; t[2] = -1; + break; + case INSTR_STORE_ENT: + t[0] = t[1] = TYPE_ENTITY; t[2] = -1; + break; + case INSTR_STORE_FLD: + t[0] = t[1] = TYPE_FIELD; t[2] = -1; + break; + case INSTR_STORE_FNC: + t[0] = t[1] = TYPE_FUNCTION; t[2] = -1; + break; + case INSTR_STOREP_V: + t[0] = TYPE_VECTOR; t[1] = TYPE_ENTITY; t[2] = -1; + break; + case INSTR_STOREP_S: + t[0] = TYPE_STRING; t[1] = TYPE_ENTITY; t[2] = -1; + break; + case INSTR_STOREP_ENT: + t[0] = TYPE_ENTITY; t[1] = TYPE_ENTITY; t[2] = -1; + break; + case INSTR_STOREP_FLD: + t[0] = TYPE_FIELD; t[1] = TYPE_ENTITY; t[2] = -1; + break; + case INSTR_STOREP_FNC: + t[0] = TYPE_FUNCTION; t[1] = TYPE_ENTITY; t[2] = -1; + break; + } + if (t[0] >= 0) trace_print_global(prog, st->o1.u1, t[0]); + else printf("(none), "); + if (t[1] >= 0) trace_print_global(prog, st->o2.u1, t[1]); + else printf("(none), "); + if (t[2] >= 0) trace_print_global(prog, st->o3.u1, t[2]); + else printf("(none)"); + printf("\n"); + } +} + +static qcint_t prog_enterfunction(qc_program_t *prog, prog_section_function_t *func) { + qc_exec_stack_t st; + size_t parampos; + int32_t p; + + /* back up locals */ + st.localsp = vec_size(prog->localstack); + st.stmt = prog->statement; + st.function = func; + + if (prog->xflags & VMXF_TRACE) { + const char *str = prog_getstring(prog, func->name); + vec_push(prog->function_stack, str); + } + +#ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS + if (vec_size(prog->stack)) + { + prog_section_function_t *cur; + cur = prog->stack[vec_size(prog->stack)-1].function; + if (cur) + { + qcint_t *globals = &prog->globals[0] + cur->firstlocal; + vec_append(prog->localstack, cur->locals, globals); + } + } +#else + { + qcint_t *globals = &prog->globals[0] + func->firstlocal; + vec_append(prog->localstack, func->locals, globals); + } +#endif + + /* copy parameters */ + parampos = func->firstlocal; + for (p = 0; p < func->nargs; ++p) + { + size_t s; + for (s = 0; s < func->argsize[p]; ++s) { + prog->globals[parampos] = prog->globals[OFS_PARM0 + 3*p + s]; + ++parampos; + } + } + + vec_push(prog->stack, st); + + return func->entry; +} + +static qcint_t prog_leavefunction(qc_program_t *prog) { + prog_section_function_t *prev = nullptr; + size_t oldsp; + + qc_exec_stack_t st = vec_last(prog->stack); + + if (prog->xflags & VMXF_TRACE) { + if (vec_size(prog->function_stack)) + vec_pop(prog->function_stack); + } + +#ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS + if (vec_size(prog->stack) > 1) { + prev = prog->stack[vec_size(prog->stack)-2].function; + oldsp = prog->stack[vec_size(prog->stack)-2].localsp; + } +#else + prev = prog->stack[vec_size(prog->stack)-1].function; + oldsp = prog->stack[vec_size(prog->stack)-1].localsp; +#endif + if (prev) { + qcint_t *globals = &prog->globals[0] + prev->firstlocal; + memcpy(globals, prog->localstack + oldsp, prev->locals * sizeof(prog->localstack[0])); + /* vec_remove(prog->localstack, oldsp, vec_size(prog->localstack)-oldsp); */ + vec_shrinkto(prog->localstack, oldsp); + } + + vec_pop(prog->stack); + + return st.stmt - 1; /* offset the ++st */ +} + +bool prog_exec(qc_program_t *prog, prog_section_function_t *func, size_t flags, long maxjumps) { + long jumpcount = 0; + size_t oldxflags = prog->xflags; + prog_section_statement_t *st; + + prog->vmerror = 0; + prog->xflags = flags; + + st = &prog->code[0] + prog_enterfunction(prog, func); + --st; + switch (flags) + { + default: + case 0: + { +#define QCVM_LOOP 1 +#define QCVM_PROFILE 0 +#define QCVM_TRACE 0 +# include __FILE__ + } + case (VMXF_TRACE): + { +#define QCVM_PROFILE 0 +#define QCVM_TRACE 1 +# include __FILE__ + } + case (VMXF_PROFILE): + { +#define QCVM_PROFILE 1 +#define QCVM_TRACE 0 +# include __FILE__ + } + case (VMXF_TRACE|VMXF_PROFILE): + { +#define QCVM_PROFILE 1 +#define QCVM_TRACE 1 +# include __FILE__ + } + }; + +cleanup: + prog->xflags = oldxflags; + vec_free(prog->localstack); + vec_free(prog->stack); + if (prog->vmerror) + return false; + return true; +} + +/*********************************************************************** + * main for when building the standalone executor + */ + +#include + +const char *type_name[TYPE_COUNT] = { + "void", + "string", + "float", + "vector", + "entity", + "field", + "function", + "pointer", + "integer", + + "variant", + + "struct", + "union", + "array", + + "nil", + "noexpr" +}; + +struct qcvm_parameter { + int vtype; + const char *value; +}; + +static qcvm_parameter *main_params = nullptr; + +#define CheckArgs(num) do { \ + if (prog->argc != (num)) { \ + prog->vmerror++; \ + fprintf(stderr, "ERROR: invalid number of arguments for %s: %i, expected %i\n", \ + __func__, prog->argc, (num)); \ + return -1; \ + } \ +} while (0) + +#define GetGlobal(idx) ((qcany_t*)(&prog->globals[0] + (idx))) +#define GetArg(num) GetGlobal(OFS_PARM0 + 3*(num)) +#define Return(any) *(GetGlobal(OFS_RETURN)) = (any) + +static int qc_print(qc_program_t *prog) { + size_t i; + const char *laststr = nullptr; + for (i = 0; i < (size_t)prog->argc; ++i) { + qcany_t *str = (qcany_t*)(&prog->globals[0] + OFS_PARM0 + 3*i); + laststr = prog_getstring(prog, str->string); + printf("%s", laststr); + } + if (laststr && (prog->xflags & VMXF_TRACE)) { + size_t len = strlen(laststr); + if (!len || laststr[len-1] != '\n') + printf("\n"); + } + return 0; +} + +static int qc_error(qc_program_t *prog) { + fprintf(stderr, "*** VM raised an error:\n"); + qc_print(prog); + prog->vmerror++; + return -1; +} + +static int qc_ftos(qc_program_t *prog) { + char buffer[512]; + qcany_t *num; + qcany_t str; + CheckArgs(1); + num = GetArg(0); + util_snprintf(buffer, sizeof(buffer), "%g", num->_float); + str.string = prog_tempstring(prog, buffer); + Return(str); + return 0; +} + +static int qc_stof(qc_program_t *prog) { + qcany_t *str; + qcany_t num; + CheckArgs(1); + str = GetArg(0); + num._float = (float)strtod(prog_getstring(prog, str->string), nullptr); + Return(num); + return 0; +} + +static int qc_vtos(qc_program_t *prog) { + char buffer[512]; + qcany_t *num; + qcany_t str; + CheckArgs(1); + num = GetArg(0); + util_snprintf(buffer, sizeof(buffer), "'%g %g %g'", num->vector[0], num->vector[1], num->vector[2]); + str.string = prog_tempstring(prog, buffer); + Return(str); + return 0; +} + +static int qc_etos(qc_program_t *prog) { + char buffer[512]; + qcany_t *num; + qcany_t str; + CheckArgs(1); + num = GetArg(0); + util_snprintf(buffer, sizeof(buffer), "%i", num->_int); + str.string = prog_tempstring(prog, buffer); + Return(str); + return 0; +} + +static int qc_spawn(qc_program_t *prog) { + qcany_t ent; + CheckArgs(0); + ent.edict = prog_spawn_entity(prog); + Return(ent); + return (ent.edict ? 0 : -1); +} + +static int qc_kill(qc_program_t *prog) { + qcany_t *ent; + CheckArgs(1); + ent = GetArg(0); + prog_free_entity(prog, ent->edict); + return 0; +} + +static int qc_sqrt(qc_program_t *prog) { + qcany_t *num, out; + CheckArgs(1); + num = GetArg(0); + out._float = sqrt(num->_float); + Return(out); + return 0; +} + +static int qc_vlen(qc_program_t *prog) { + qcany_t *vec, len; + CheckArgs(1); + vec = GetArg(0); + len._float = sqrt(vec->vector[0] * vec->vector[0] + + vec->vector[1] * vec->vector[1] + + vec->vector[2] * vec->vector[2]); + Return(len); + return 0; +} + +static int qc_normalize(qc_program_t *prog) { + double len; + qcany_t *vec; + qcany_t out; + CheckArgs(1); + vec = GetArg(0); + len = sqrt(vec->vector[0] * vec->vector[0] + + vec->vector[1] * vec->vector[1] + + vec->vector[2] * vec->vector[2]); + if (len) + len = 1.0 / len; + else + len = 0; + out.vector[0] = len * vec->vector[0]; + out.vector[1] = len * vec->vector[1]; + out.vector[2] = len * vec->vector[2]; + Return(out); + return 0; +} + +static int qc_strcat(qc_program_t *prog) { + char *buffer; + size_t len1, len2; + qcany_t *str1, *str2; + qcany_t out; + + const char *cstr1; + const char *cstr2; + + CheckArgs(2); + str1 = GetArg(0); + str2 = GetArg(1); + cstr1 = prog_getstring(prog, str1->string); + cstr2 = prog_getstring(prog, str2->string); + len1 = strlen(cstr1); + len2 = strlen(cstr2); + buffer = (char*)mem_a(len1 + len2 + 1); + memcpy(buffer, cstr1, len1); + memcpy(buffer+len1, cstr2, len2+1); + out.string = prog_tempstring(prog, buffer); + mem_d(buffer); + Return(out); + return 0; +} + +static int qc_strcmp(qc_program_t *prog) { + qcany_t *str1, *str2; + qcany_t out; + + const char *cstr1; + const char *cstr2; + + if (prog->argc != 2 && prog->argc != 3) { + fprintf(stderr, "ERROR: invalid number of arguments for strcmp/strncmp: %i, expected 2 or 3\n", + prog->argc); + return -1; + } + + str1 = GetArg(0); + str2 = GetArg(1); + cstr1 = prog_getstring(prog, str1->string); + cstr2 = prog_getstring(prog, str2->string); + if (prog->argc == 3) + out._float = strncmp(cstr1, cstr2, GetArg(2)->_float); + else + out._float = strcmp(cstr1, cstr2); + Return(out); + return 0; +} + +static int qc_floor(qc_program_t *prog) { + qcany_t *num, out; + CheckArgs(1); + num = GetArg(0); + out._float = floor(num->_float); + Return(out); + return 0; +} + +static int qc_pow(qc_program_t *prog) { + qcany_t *base, *exp, out; + CheckArgs(2); + base = GetArg(0); + exp = GetArg(1); + out._float = powf(base->_float, exp->_float); + Return(out); + return 0; +} + +static prog_builtin_t qc_builtins[] = { + nullptr, + &qc_print, /* 1 */ + &qc_ftos, /* 2 */ + &qc_spawn, /* 3 */ + &qc_kill, /* 4 */ + &qc_vtos, /* 5 */ + &qc_error, /* 6 */ + &qc_vlen, /* 7 */ + &qc_etos, /* 8 */ + &qc_stof, /* 9 */ + &qc_strcat, /* 10 */ + &qc_strcmp, /* 11 */ + &qc_normalize, /* 12 */ + &qc_sqrt, /* 13 */ + &qc_floor, /* 14 */ + &qc_pow /* 15 */ +}; + +static const char *arg0 = nullptr; + +static void version(void) { + printf("GMQCC-QCVM %d.%d.%d Built %s %s\n", + GMQCC_VERSION_MAJOR, + GMQCC_VERSION_MINOR, + GMQCC_VERSION_PATCH, + __DATE__, + __TIME__ + ); +} + +static void usage(void) { + printf("usage: %s [options] [parameters] file\n", arg0); + printf("options:\n"); + printf(" -h, --help print this message\n" + " -trace trace the execution\n" + " -profile perform profiling during execution\n" + " -info print information from the prog's header\n" + " -disasm disassemble and exit\n" + " -disasm-func func disassemble and exit\n" + " -printdefs list the defs section\n" + " -printfields list the field section\n" + " -printfuns list functions information\n" + " -v be verbose\n" + " -vv be even more verbose\n"); + printf("parameters:\n"); + printf(" -vector pass a vector parameter to main()\n" + " -float pass a float parameter to main()\n" + " -string pass a string parameter to main() \n"); +} + +static void prog_main_setparams(qc_program_t *prog) { + size_t i; + qcany_t *arg; + + for (i = 0; i < vec_size(main_params); ++i) { + arg = GetGlobal(OFS_PARM0 + 3*i); + arg->vector[0] = 0; + arg->vector[1] = 0; + arg->vector[2] = 0; + switch (main_params[i].vtype) { + case TYPE_VECTOR: + (void)util_sscanf(main_params[i].value, " %f %f %f ", + &arg->vector[0], + &arg->vector[1], + &arg->vector[2]); + break; + case TYPE_FLOAT: + arg->_float = atof(main_params[i].value); + break; + case TYPE_STRING: + arg->string = prog_tempstring(prog, main_params[i].value); + break; + default: + fprintf(stderr, "error: unhandled parameter type: %i\n", main_params[i].vtype); + break; + } + } +} + +static void prog_disasm_function(qc_program_t *prog, size_t id); + +int main(int argc, char **argv) { + size_t i; + qcint_t fnmain = -1; + qc_program_t *prog; + size_t xflags = VMXF_DEFAULT; + bool opts_printfields = false; + bool opts_printdefs = false; + bool opts_printfuns = false; + bool opts_disasm = false; + bool opts_info = false; + bool noexec = false; + const char *progsfile = nullptr; + const char **dis_list = nullptr; + int opts_v = 0; + + arg0 = argv[0]; + + if (argc < 2) { + usage(); + exit(EXIT_FAILURE); + } + + while (argc > 1) { + if (!strcmp(argv[1], "-h") || + !strcmp(argv[1], "-help") || + !strcmp(argv[1], "--help")) + { + usage(); + exit(EXIT_SUCCESS); + } + else if (!strcmp(argv[1], "-v")) { + ++opts_v; + --argc; + ++argv; + } + else if (!strncmp(argv[1], "-vv", 3)) { + const char *av = argv[1]+1; + for (; *av; ++av) { + if (*av == 'v') + ++opts_v; + else { + usage(); + exit(EXIT_FAILURE); + } + } + --argc; + ++argv; + } + else if (!strcmp(argv[1], "-version") || + !strcmp(argv[1], "--version")) + { + version(); + exit(EXIT_SUCCESS); + } + else if (!strcmp(argv[1], "-trace")) { + --argc; + ++argv; + xflags |= VMXF_TRACE; + } + else if (!strcmp(argv[1], "-profile")) { + --argc; + ++argv; + xflags |= VMXF_PROFILE; + } + else if (!strcmp(argv[1], "-info")) { + --argc; + ++argv; + opts_info = true; + noexec = true; + } + else if (!strcmp(argv[1], "-disasm")) { + --argc; + ++argv; + opts_disasm = true; + noexec = true; + } + else if (!strcmp(argv[1], "-disasm-func")) { + --argc; + ++argv; + if (argc <= 1) { + usage(); + exit(EXIT_FAILURE); + } + vec_push(dis_list, argv[1]); + --argc; + ++argv; + noexec = true; + } + else if (!strcmp(argv[1], "-printdefs")) { + --argc; + ++argv; + opts_printdefs = true; + noexec = true; + } + else if (!strcmp(argv[1], "-printfuns")) { + --argc; + ++argv; + opts_printfuns = true; + noexec = true; + } + else if (!strcmp(argv[1], "-printfields")) { + --argc; + ++argv; + opts_printfields = true; + noexec = true; + } + else if (!strcmp(argv[1], "-vector") || + !strcmp(argv[1], "-string") || + !strcmp(argv[1], "-float") ) + { + qcvm_parameter p; + if (argv[1][1] == 'f') + p.vtype = TYPE_FLOAT; + else if (argv[1][1] == 's') + p.vtype = TYPE_STRING; + else if (argv[1][1] == 'v') + p.vtype = TYPE_VECTOR; + else + p.vtype = TYPE_VOID; + + --argc; + ++argv; + if (argc < 2) { + usage(); + exit(EXIT_FAILURE); + } + p.value = argv[1]; + + vec_push(main_params, p); + --argc; + ++argv; + } + else if (!strcmp(argv[1], "--")) { + --argc; + ++argv; + break; + } + else if (argv[1][0] != '-') { + if (progsfile) { + fprintf(stderr, "only 1 program file may be specified\n"); + usage(); + exit(EXIT_FAILURE); + } + progsfile = argv[1]; + --argc; + ++argv; + } + else + { + fprintf(stderr, "unknown parameter: %s\n", argv[1]); + usage(); + exit(EXIT_FAILURE); + } + } + + if (argc == 2 && !progsfile) { + progsfile = argv[1]; + --argc; + ++argv; + } + + if (!progsfile) { + fprintf(stderr, "must specify a program to execute\n"); + usage(); + exit(EXIT_FAILURE); + } + + prog = prog_load(progsfile, noexec); + if (!prog) { + fprintf(stderr, "failed to load program '%s'\n", progsfile); + exit(EXIT_FAILURE); + } + + prog->builtins = qc_builtins; + prog->builtins_count = GMQCC_ARRAY_COUNT(qc_builtins); + + if (opts_info) { + printf("Program's system-checksum = 0x%04x\n", (unsigned int)prog->crc16); + printf("Entity field space: %u\n", (unsigned int)prog->entityfields); + printf("Globals: %zu\n", prog->globals.size()); + printf("Counts:\n" + " code: %zu\n" + " defs: %zu\n" + " fields: %zu\n" + " functions: %zu\n" + " strings: %zu\n", + prog->code.size(), + prog->defs.size(), + prog->fields.size(), + prog->functions.size(), + prog->strings.size()); + } + + if (opts_info) { + prog_delete(prog); + return 0; + } + for (i = 0; i < vec_size(dis_list); ++i) { + size_t k; + printf("Looking for `%s`\n", dis_list[i]); + for (k = 1; k < prog->functions.size(); ++k) { + const char *name = prog_getstring(prog, prog->functions[k].name); + if (!strcmp(name, dis_list[i])) { + prog_disasm_function(prog, k); + break; + } + } + } + if (opts_disasm) { + for (i = 1; i < prog->functions.size(); ++i) + prog_disasm_function(prog, i); + return 0; + } + if (opts_printdefs) { + const char *getstring = nullptr; + for (auto &it : prog->defs) { + printf("Global: %8s %-16s at %u%s", + type_name[it.type & DEF_TYPEMASK], + prog_getstring(prog, it.name), + (unsigned int)it.offset, + ((it.type & DEF_SAVEGLOBAL) ? " [SAVE]" : "")); + if (opts_v) { + switch (it.type & DEF_TYPEMASK) { + case TYPE_FLOAT: + printf(" [init: %g]", ((qcany_t*)(&prog->globals[0] + it.offset))->_float); + break; + case TYPE_INTEGER: + printf(" [init: %i]", (int)( ((qcany_t*)(&prog->globals[0] + it.offset))->_int )); + break; + case TYPE_ENTITY: + case TYPE_FUNCTION: + case TYPE_FIELD: + case TYPE_POINTER: + printf(" [init: %u]", (unsigned)( ((qcany_t*)(&prog->globals[0] + it.offset))->_int )); + break; + case TYPE_STRING: + getstring = prog_getstring(prog, ((qcany_t*)(&prog->globals[0] + it.offset))->string); + printf(" [init: `"); + print_escaped_string(getstring, strlen(getstring)); + printf("`]\n"); + break; + default: + break; + } + } + printf("\n"); + } + } + if (opts_printfields) { + for (auto &it : prog->fields) { + printf("Field: %8s %-16s at %d%s\n", + type_name[it.type], + prog_getstring(prog, it.name), + it.offset, + ((it.type & DEF_SAVEGLOBAL) ? " [SAVE]" : "")); + } + } + if (opts_printfuns) { + for (auto &it : prog->functions) { + int32_t a; + printf("Function: %-16s taking %u parameters:(", + prog_getstring(prog, it.name), + (unsigned int)it.nargs); + for (a = 0; a < it.nargs; ++a) { + printf(" %i", it.argsize[a]); + } + if (opts_v > 1) { + int32_t start = it.entry; + if (start < 0) + printf(") builtin %i\n", (int)-start); + else { + size_t funsize = 0; + prog_section_statement_t *st = &prog->code[0] + start; + for (;st->opcode != INSTR_DONE; ++st) + ++funsize; + printf(") - %zu instructions", funsize); + if (opts_v > 2) { + printf(" - locals: %i + %i\n", + it.firstlocal, + it.locals); + } + else + printf("\n"); + } + } + else if (opts_v) { + printf(") locals: %i + %i\n", + it.firstlocal, + it.locals); + } + else + printf(")\n"); + } + } + if (!noexec) { + for (i = 1; i < prog->functions.size(); ++i) { + const char *name = prog_getstring(prog, prog->functions[i].name); + if (!strcmp(name, "main")) + fnmain = (qcint_t)i; + } + if (fnmain > 0) + { + prog_main_setparams(prog); + prog_exec(prog, &prog->functions[fnmain], xflags, VM_JUMPS_DEFAULT); + } + else + fprintf(stderr, "No main function found\n"); + } + + prog_delete(prog); + return 0; +} + +static void prog_disasm_function(qc_program_t *prog, size_t id) { + prog_section_function_t *fdef = &prog->functions[0] + id; + prog_section_statement_t *st; + + if (fdef->entry < 0) { + printf("FUNCTION \"%s\" = builtin #%i\n", prog_getstring(prog, fdef->name), (int)-fdef->entry); + return; + } + else + printf("FUNCTION \"%s\"\n", prog_getstring(prog, fdef->name)); + + st = &prog->code[0] + fdef->entry; + while (st->opcode != INSTR_DONE) { + prog_print_statement(prog, st); + ++st; + } +} +#else /* !QCVM_LOOP */ +/* + * Everything from here on is not including into the compilation of the + * executor. This is simply code that is #included via #include __FILE__ + * see when QCVM_LOOP is defined, the rest of the code above do not get + * re-included. So this really just acts like one large macro, but it + * sort of isn't, which makes it nicer looking. + */ + +#define OPA ( (qcany_t*) (&prog->globals[0] + st->o1.u1) ) +#define OPB ( (qcany_t*) (&prog->globals[0] + st->o2.u1) ) +#define OPC ( (qcany_t*) (&prog->globals[0] + st->o3.u1) ) + +#define GLOBAL(x) ( (qcany_t*) (&prog->globals[0] + (x)) ) + +/* to be consistent with current darkplaces behaviour */ +#if !defined(FLOAT_IS_TRUE_FOR_INT) +# define FLOAT_IS_TRUE_FOR_INT(x) ( (x) & 0x7FFFFFFF ) +#endif + +while (prog->vmerror == 0) { + prog_section_function_t *newf; + qcany_t *ed; + qcany_t *ptr; + + ++st; + +#if QCVM_PROFILE + prog->profile[st - &prog->code[0]]++; +#endif + +#if QCVM_TRACE + prog_print_statement(prog, st); +#endif + + switch (st->opcode) + { + default: + qcvmerror(prog, "Illegal instruction in %s\n", prog->filename); + goto cleanup; + + case INSTR_DONE: + case INSTR_RETURN: + /* TODO: add instruction count to function profile count */ + GLOBAL(OFS_RETURN)->ivector[0] = OPA->ivector[0]; + GLOBAL(OFS_RETURN)->ivector[1] = OPA->ivector[1]; + GLOBAL(OFS_RETURN)->ivector[2] = OPA->ivector[2]; + + st = &prog->code[0] + prog_leavefunction(prog); + if (!vec_size(prog->stack)) + goto cleanup; + + break; + + case INSTR_MUL_F: + OPC->_float = OPA->_float * OPB->_float; + break; + case INSTR_MUL_V: + OPC->_float = OPA->vector[0]*OPB->vector[0] + + OPA->vector[1]*OPB->vector[1] + + OPA->vector[2]*OPB->vector[2]; + break; + case INSTR_MUL_FV: + { + qcfloat_t f = OPA->_float; + OPC->vector[0] = f * OPB->vector[0]; + OPC->vector[1] = f * OPB->vector[1]; + OPC->vector[2] = f * OPB->vector[2]; + break; + } + case INSTR_MUL_VF: + { + qcfloat_t f = OPB->_float; + OPC->vector[0] = f * OPA->vector[0]; + OPC->vector[1] = f * OPA->vector[1]; + OPC->vector[2] = f * OPA->vector[2]; + break; + } + case INSTR_DIV_F: + if (OPB->_float != 0.0f) + OPC->_float = OPA->_float / OPB->_float; + else + OPC->_float = 0; + break; + + case INSTR_ADD_F: + OPC->_float = OPA->_float + OPB->_float; + break; + case INSTR_ADD_V: + OPC->vector[0] = OPA->vector[0] + OPB->vector[0]; + OPC->vector[1] = OPA->vector[1] + OPB->vector[1]; + OPC->vector[2] = OPA->vector[2] + OPB->vector[2]; + break; + case INSTR_SUB_F: + OPC->_float = OPA->_float - OPB->_float; + break; + case INSTR_SUB_V: + OPC->vector[0] = OPA->vector[0] - OPB->vector[0]; + OPC->vector[1] = OPA->vector[1] - OPB->vector[1]; + OPC->vector[2] = OPA->vector[2] - OPB->vector[2]; + break; + + case INSTR_EQ_F: + OPC->_float = (OPA->_float == OPB->_float); + break; + case INSTR_EQ_V: + OPC->_float = ((OPA->vector[0] == OPB->vector[0]) && + (OPA->vector[1] == OPB->vector[1]) && + (OPA->vector[2] == OPB->vector[2]) ); + break; + case INSTR_EQ_S: + OPC->_float = !strcmp(prog_getstring(prog, OPA->string), + prog_getstring(prog, OPB->string)); + break; + case INSTR_EQ_E: + OPC->_float = (OPA->_int == OPB->_int); + break; + case INSTR_EQ_FNC: + OPC->_float = (OPA->function == OPB->function); + break; + case INSTR_NE_F: + OPC->_float = (OPA->_float != OPB->_float); + break; + case INSTR_NE_V: + OPC->_float = ((OPA->vector[0] != OPB->vector[0]) || + (OPA->vector[1] != OPB->vector[1]) || + (OPA->vector[2] != OPB->vector[2]) ); + break; + case INSTR_NE_S: + OPC->_float = !!strcmp(prog_getstring(prog, OPA->string), + prog_getstring(prog, OPB->string)); + break; + case INSTR_NE_E: + OPC->_float = (OPA->_int != OPB->_int); + break; + case INSTR_NE_FNC: + OPC->_float = (OPA->function != OPB->function); + break; + + case INSTR_LE: + OPC->_float = (OPA->_float <= OPB->_float); + break; + case INSTR_GE: + OPC->_float = (OPA->_float >= OPB->_float); + break; + case INSTR_LT: + OPC->_float = (OPA->_float < OPB->_float); + break; + case INSTR_GT: + OPC->_float = (OPA->_float > OPB->_float); + break; + + case INSTR_LOAD_F: + case INSTR_LOAD_S: + case INSTR_LOAD_FLD: + case INSTR_LOAD_ENT: + case INSTR_LOAD_FNC: + if (OPA->edict < 0 || OPA->edict >= prog->entities) { + qcvmerror(prog, "progs `%s` attempted to read an out of bounds entity", prog->filename); + goto cleanup; + } + if ((unsigned int)(OPB->_int) >= (unsigned int)(prog->entityfields)) { + qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)", + prog->filename, + OPB->_int); + goto cleanup; + } + ed = prog_getedict(prog, OPA->edict); + OPC->_int = ((qcany_t*)( ((qcint_t*)ed) + OPB->_int ))->_int; + break; + case INSTR_LOAD_V: + if (OPA->edict < 0 || OPA->edict >= prog->entities) { + qcvmerror(prog, "progs `%s` attempted to read an out of bounds entity", prog->filename); + goto cleanup; + } + if (OPB->_int < 0 || OPB->_int + 3 > (qcint_t)prog->entityfields) + { + qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)", + prog->filename, + OPB->_int + 2); + goto cleanup; + } + ed = prog_getedict(prog, OPA->edict); + ptr = (qcany_t*)( ((qcint_t*)ed) + OPB->_int ); + OPC->ivector[0] = ptr->ivector[0]; + OPC->ivector[1] = ptr->ivector[1]; + OPC->ivector[2] = ptr->ivector[2]; + break; + + case INSTR_ADDRESS: + if (OPA->edict < 0 || OPA->edict >= prog->entities) { + qcvmerror(prog, "prog `%s` attempted to address an out of bounds entity %i", prog->filename, OPA->edict); + goto cleanup; + } + if ((unsigned int)(OPB->_int) >= (unsigned int)(prog->entityfields)) + { + qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)", + prog->filename, + OPB->_int); + goto cleanup; + } + + ed = prog_getedict(prog, OPA->edict); + OPC->_int = ((qcint_t*)ed) - prog->entitydata + OPB->_int; + break; + + case INSTR_STORE_F: + case INSTR_STORE_S: + case INSTR_STORE_ENT: + case INSTR_STORE_FLD: + case INSTR_STORE_FNC: + OPB->_int = OPA->_int; + break; + case INSTR_STORE_V: + OPB->ivector[0] = OPA->ivector[0]; + OPB->ivector[1] = OPA->ivector[1]; + OPB->ivector[2] = OPA->ivector[2]; + break; + + case INSTR_STOREP_F: + case INSTR_STOREP_S: + case INSTR_STOREP_ENT: + case INSTR_STOREP_FLD: + case INSTR_STOREP_FNC: + if (OPB->_int < 0 || OPB->_int >= (qcint_t)vec_size(prog->entitydata)) { + qcvmerror(prog, "`%s` attempted to write to an out of bounds edict (%i)", prog->filename, OPB->_int); + goto cleanup; + } + if (OPB->_int < (qcint_t)prog->entityfields && !prog->allowworldwrites) + qcvmerror(prog, "`%s` tried to assign to world.%s (field %i)\n", + prog->filename, + prog_getstring(prog, prog_entfield(prog, OPB->_int)->name), + OPB->_int); + ptr = (qcany_t*)(prog->entitydata + OPB->_int); + ptr->_int = OPA->_int; + break; + case INSTR_STOREP_V: + if (OPB->_int < 0 || OPB->_int + 2 >= (qcint_t)vec_size(prog->entitydata)) { + qcvmerror(prog, "`%s` attempted to write to an out of bounds edict (%i)", prog->filename, OPB->_int); + goto cleanup; + } + if (OPB->_int < (qcint_t)prog->entityfields && !prog->allowworldwrites) + qcvmerror(prog, "`%s` tried to assign to world.%s (field %i)\n", + prog->filename, + prog_getstring(prog, prog_entfield(prog, OPB->_int)->name), + OPB->_int); + ptr = (qcany_t*)(prog->entitydata + OPB->_int); + ptr->ivector[0] = OPA->ivector[0]; + ptr->ivector[1] = OPA->ivector[1]; + ptr->ivector[2] = OPA->ivector[2]; + break; + + case INSTR_NOT_F: + OPC->_float = !FLOAT_IS_TRUE_FOR_INT(OPA->_int); + break; + case INSTR_NOT_V: + OPC->_float = !OPA->vector[0] && + !OPA->vector[1] && + !OPA->vector[2]; + break; + case INSTR_NOT_S: + OPC->_float = !OPA->string || + !*prog_getstring(prog, OPA->string); + break; + case INSTR_NOT_ENT: + OPC->_float = (OPA->edict == 0); + break; + case INSTR_NOT_FNC: + OPC->_float = !OPA->function; + break; + + case INSTR_IF: + /* this is consistent with darkplaces' behaviour */ + if(FLOAT_IS_TRUE_FOR_INT(OPA->_int)) + { + st += st->o2.s1 - 1; /* offset the s++ */ + if (++jumpcount >= maxjumps) + qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount); + } + break; + case INSTR_IFNOT: + if(!FLOAT_IS_TRUE_FOR_INT(OPA->_int)) + { + st += st->o2.s1 - 1; /* offset the s++ */ + if (++jumpcount >= maxjumps) + qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount); + } + break; + + case INSTR_CALL0: + case INSTR_CALL1: + case INSTR_CALL2: + case INSTR_CALL3: + case INSTR_CALL4: + case INSTR_CALL5: + case INSTR_CALL6: + case INSTR_CALL7: + case INSTR_CALL8: + prog->argc = st->opcode - INSTR_CALL0; + if (!OPA->function) + qcvmerror(prog, "nullptr function in `%s`", prog->filename); + + if(!OPA->function || OPA->function >= (qcint_t)prog->functions.size()) + { + qcvmerror(prog, "CALL outside the program in `%s`", prog->filename); + goto cleanup; + } + + newf = &prog->functions[OPA->function]; + newf->profile++; + + prog->statement = (st - &prog->code[0]) + 1; + + if (newf->entry < 0) + { + /* negative statements are built in functions */ + qcint_t builtinnumber = -newf->entry; + if (builtinnumber < (qcint_t)prog->builtins_count && prog->builtins[builtinnumber]) + prog->builtins[builtinnumber](prog); + else + qcvmerror(prog, "No such builtin #%i in %s! Try updating your gmqcc sources", + builtinnumber, prog->filename); + } + else + st = &prog->code[0] + prog_enterfunction(prog, newf) - 1; /* offset st++ */ + if (prog->vmerror) + goto cleanup; + break; + + case INSTR_STATE: + { + qcfloat_t *nextthink; + qcfloat_t *time; + qcfloat_t *frame; + if (!prog->supports_state) { + qcvmerror(prog, "`%s` tried to execute a STATE operation but misses its defs!", prog->filename); + goto cleanup; + } + ed = prog_getedict(prog, prog->globals[prog->cached_globals.self]); + ((qcint_t*)ed)[prog->cached_fields.think] = OPB->function; + + frame = (qcfloat_t*)&((qcint_t*)ed)[prog->cached_fields.frame]; + *frame = OPA->_float; + nextthink = (qcfloat_t*)&((qcint_t*)ed)[prog->cached_fields.nextthink]; + time = (qcfloat_t*)(&prog->globals[0] + prog->cached_globals.time); + *nextthink = *time + 0.1; + break; + } + + case INSTR_GOTO: + st += st->o1.s1 - 1; /* offset the s++ */ + if (++jumpcount == 10000000) + qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount); + break; + + case INSTR_AND: + OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) && + FLOAT_IS_TRUE_FOR_INT(OPB->_int); + break; + case INSTR_OR: + OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) || + FLOAT_IS_TRUE_FOR_INT(OPB->_int); + break; + + case INSTR_BITAND: + OPC->_float = ((int)OPA->_float) & ((int)OPB->_float); + break; + case INSTR_BITOR: + OPC->_float = ((int)OPA->_float) | ((int)OPB->_float); + break; + } +} + +#undef QCVM_PROFILE +#undef QCVM_TRACE +#endif /* !QCVM_LOOP */ diff --git a/fold.c b/fold.c deleted file mode 100644 index 10145bc..0000000 --- a/fold.c +++ /dev/null @@ -1,1694 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * 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 -#include - -#include "ast.h" -#include "parser.h" - -#define FOLD_STRING_UNTRANSLATE_HTSIZE 1024 -#define FOLD_STRING_DOTRANSLATE_HTSIZE 1024 - -/* The options to use for inexact and arithmetic exceptions */ -#define FOLD_ROUNDING SFLOAT_ROUND_NEAREST_EVEN -#define FOLD_TINYNESS SFLOAT_TBEFORE - -/* - * Comparing float values is an unsafe operation when the operands to the - * comparison are floating point values that are inexact. For instance 1/3 is an - * inexact value. The FPU is meant to raise exceptions when these sorts of things - * happen, including division by zero, underflows and overflows. The C standard - * library provides us with the header to gain access to the floating- - * point environment and lets us set the rounding mode and check for these exceptions. - * The problem is the standard C library allows an implementation to leave these - * stubbed out and does not require they be implemented. Furthermore, depending - * on implementations there is no control over the FPU. This is an IEE 754 - * conforming implementation in software to compensate. - */ -typedef uint32_t sfloat_t; - -typedef union { - qcfloat_t f; - sfloat_t s; -} sfloat_cast_t; - -/* Exception flags */ -typedef enum { - SFLOAT_NOEXCEPT = 0, - SFLOAT_INVALID = 1, - SFLOAT_DIVBYZERO = 4, - SFLOAT_OVERFLOW = 8, - SFLOAT_UNDERFLOW = 16, - SFLOAT_INEXACT = 32 -} sfloat_exceptionflags_t; - -/* Rounding modes */ -typedef enum { - SFLOAT_ROUND_NEAREST_EVEN, - SFLOAT_ROUND_DOWN, - SFLOAT_ROUND_UP, - SFLOAT_ROUND_TO_ZERO -} sfloat_roundingmode_t; - -/* Underflow tininess-detection mode */ -typedef enum { - SFLOAT_TAFTER, - SFLOAT_TBEFORE -} sfloat_tdetect_t; - -typedef struct { - sfloat_roundingmode_t roundingmode; - sfloat_exceptionflags_t exceptionflags; - sfloat_tdetect_t tiny; -} sfloat_state_t; - -/* Counts the number of leading zero bits before the most-significand one bit. */ -#ifdef _MSC_VER -/* MSVC has an intrinsic for this */ - static GMQCC_INLINE uint32_t sfloat_clz(uint32_t x) { - int r = 0; - _BitScanForward(&r, x); - return r; - } -# define SFLOAT_CLZ(X, SUB) \ - (sfloat_clz((X)) - (SUB)) -#elif defined(__GNUC__) || defined(__CLANG__) -/* Clang and GCC have a builtin for this */ -# define SFLOAT_CLZ(X, SUB) \ - (__builtin_clz((X)) - (SUB)) -#else -/* Native fallback */ - static GMQCC_INLINE uint32_t sfloat_popcnt(uint32_t x) { - x -= ((x >> 1) & 0x55555555); - x = (((x >> 2) & 0x33333333) + (x & 0x33333333)); - x = (((x >> 4) + x) & 0x0F0F0F0F); - x += x >> 8; - x += x >> 16; - return x & 0x0000003F; - } - static GMQCC_INLINE uint32_t sfloat_clz(uint32_t x) { - x |= (x >> 1); - x |= (x >> 2); - x |= (x >> 4); - x |= (x >> 8); - x |= (x >> 16); - return 32 - sfloat_popcnt(x); - } -# define SFLOAT_CLZ(X, SUB) \ - (sfloat_clz((X) - (SUB))) -#endif - -/* The value of a NaN */ -#define SFLOAT_NAN 0xFFFFFFFF -/* Test if NaN */ -#define SFLOAT_ISNAN(A) \ - (0xFF000000 < (uint32_t)((A) << 1)) -/* Test if signaling NaN */ -#define SFLOAT_ISSNAN(A) \ - (((((A) >> 22) & 0x1FF) == 0x1FE) && ((A) & 0x003FFFFF)) -/* Raise exception */ -#define SFLOAT_RAISE(STATE, FLAGS) \ - ((STATE)->exceptionflags = (sfloat_exceptionflags_t)((STATE)->exceptionflags | (FLAGS))) -/* - * Shifts `A' right by the number of bits given in `COUNT'. If any non-zero bits - * are shifted off they are forced into the least significand bit of the result - * by setting it to one. As a result of this, the value of `COUNT' can be - * arbitrarily large; if `COUNT' is greater than 32, the result will be either - * zero or one, depending on whether `A' is a zero or non-zero. The result is - * stored into the value pointed by `Z'. - */ -#define SFLOAT_SHIFT(SIZE, A, COUNT, Z) \ - *(Z) = ((COUNT) == 0) \ - ? 1 \ - : (((COUNT) < (SIZE)) \ - ? ((A) >> (COUNT)) | (((A) << ((-(COUNT)) & ((SIZE) - 1))) != 0) \ - : ((A) != 0)) - -/* Extract fractional component */ -#define SFLOAT_EXTRACT_FRAC(X) \ - ((uint32_t)((X) & 0x007FFFFF)) -/* Extract exponent component */ -#define SFLOAT_EXTRACT_EXP(X) \ - ((int16_t)((X) >> 23) & 0xFF) -/* Extract sign bit */ -#define SFLOAT_EXTRACT_SIGN(X) \ - ((X) >> 31) -/* - * Normalizes the subnormal value represented by the denormalized significand - * `SA'. The normalized exponent and significand are stored at the locations - * pointed by `Z' and `SZ' respectively. - */ -#define SFLOAT_SUBNORMALIZE(SA, Z, SZ) \ - (void)(*(SZ) = (SA) << SFLOAT_CLZ((SA), 8), *(Z) = 1 - SFLOAT_CLZ((SA), 8)) -/* - * Packs the sign `SIGN', exponent `EXP' and significand `SIG' into the value - * giving the result. - * - * After the shifting into their proper positions, the fields are added together - * to form the result. This means any integer portion of `SIG' will be added - * to the exponent. Similarly, because a properly normalized significand will - * always have an integer portion equal to one, the exponent input `EXP' should - * be one less than the desired result exponent whenever the significant input - * `SIG' is a complete, normalized significand. - */ -#define SFLOAT_PACK(SIGN, EXP, SIG) \ - (sfloat_t)((((uint32_t)(SIGN)) << 31) + (((uint32_t)(EXP)) << 23) + (SIG)) - -/* - * Takes two values `a' and `b', one of which is a NaN, and returns the appropriate - * NaN result. If either `a' or `b' is a signaling NaN than an invalid exception is - * raised. - */ -static sfloat_t sfloat_propagate_nan(sfloat_state_t *state, sfloat_t a, sfloat_t b) { - bool isnan_a = SFLOAT_ISNAN(a); - bool issnan_a = SFLOAT_ISSNAN(a); - bool isnan_b = SFLOAT_ISNAN(b); - bool issnan_b = SFLOAT_ISSNAN(b); - - a |= 0x00400000; - b |= 0x00400000; - - if (issnan_a | issnan_b) - SFLOAT_RAISE(state, SFLOAT_INVALID); - if (isnan_a) - return (issnan_a & isnan_b) ? b : a; - return b; -} - -/* - * Takes an abstract value having sign `sign_z', exponent `exp_z', and significand - * `sig_z' and returns the appropriate value corresponding to the abstract input. - * - * The abstract value is simply rounded and packed into the format. If the abstract - * input cannot be represented exactly an inexact exception is raised. If the - * abstract input is too large, the overflow and inexact exceptions are both raised - * and an infinity or maximal finite value is returned. If the abstract value is - * too small, the value is rounded to a subnormal and the underflow and inexact - * exceptions are only raised if the value cannot be represented exactly with - * a subnormal. - * - * The input significand `sig_z' has it's binary point between bits 30 and 29, - * this is seven bits to the left of its usual location. The shifted significand - * must be normalized or smaller than this. If it's not normalized then the exponent - * `exp_z' must be zero; in that case, the result returned is a subnormal number - * which must not require rounding. In the more usual case where the significand - * is normalized, the exponent must be one less than the *true* exponent. - * - * The handling of underflow and overflow is otherwise in alignment with IEC/IEEE. - */ -static sfloat_t SFLOAT_PACK_round(sfloat_state_t *state, bool sign_z, int16_t exp_z, uint32_t sig_z) { - sfloat_roundingmode_t mode = state->roundingmode; - bool even = !!(mode == SFLOAT_ROUND_NEAREST_EVEN); - unsigned char increment = 0x40; - unsigned char bits = sig_z & 0x7F; - - if (!even) { - if (mode == SFLOAT_ROUND_TO_ZERO) - increment = 0; - else { - increment = 0x7F; - if (sign_z) { - if (mode == SFLOAT_ROUND_UP) - increment = 0; - } else { - if (mode == SFLOAT_ROUND_DOWN) - increment = 0; - } - } - } - - if (0xFD <= (uint16_t)exp_z) { - if ((0xFD < exp_z) || ((exp_z == 0xFD) && ((int32_t)(sig_z + increment) < 0))) { - SFLOAT_RAISE(state, SFLOAT_OVERFLOW | SFLOAT_INEXACT); - return SFLOAT_PACK(sign_z, 0xFF, 0) - (increment == 0); - } - if (exp_z < 0) { - /* Check for underflow */ - bool tiny = (state->tiny == SFLOAT_TBEFORE) || (exp_z < -1) || (sig_z + increment < 0x80000000); - SFLOAT_SHIFT(32, sig_z, -exp_z, &sig_z); - exp_z = 0; - bits = sig_z & 0x7F; - if (tiny && bits) - SFLOAT_RAISE(state, SFLOAT_UNDERFLOW); - } - } - if (bits) - SFLOAT_RAISE(state, SFLOAT_INEXACT); - sig_z = (sig_z + increment) >> 7; - sig_z &= ~(((bits ^ 0x40) == 0) & even); - if (sig_z == 0) - exp_z = 0; - return SFLOAT_PACK(sign_z, exp_z, sig_z); -} - -/* - * Takes an abstract value having sign `sign_z', exponent `exp_z' and significand - * `sig_z' and returns the appropriate value corresponding to the abstract input. - * This function is exactly like `PACK_round' except the significand does not have - * to be normalized. - * - * Bit 31 of the significand must be zero and the exponent must be one less than - * the *true* exponent. - */ -static sfloat_t SFLOAT_PACK_normal(sfloat_state_t *state, bool sign_z, int16_t exp_z, uint32_t sig_z) { - unsigned char c = SFLOAT_CLZ(sig_z, 1); - return SFLOAT_PACK_round(state, sign_z, exp_z - c, sig_z << c); -} - -/* - * Returns the result of adding the absolute values of `a' and `b'. The sign - * `sign_z' is ignored if the result is a NaN. - */ -static sfloat_t sfloat_add_impl(sfloat_state_t *state, sfloat_t a, sfloat_t b, bool sign_z) { - int16_t exp_a = SFLOAT_EXTRACT_EXP(a); - int16_t exp_b = SFLOAT_EXTRACT_EXP(b); - int16_t exp_z = 0; - int16_t exp_d = exp_a - exp_b; - uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a) << 6; - uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b) << 6; - uint32_t sig_z = 0; - - if (0 < exp_d) { - if (exp_a == 0xFF) - return sig_a ? sfloat_propagate_nan(state, a, b) : a; - if (exp_b == 0) - --exp_d; - else - sig_b |= 0x20000000; - SFLOAT_SHIFT(32, sig_b, exp_d, &sig_b); - exp_z = exp_a; - } else if (exp_d < 0) { - if (exp_b == 0xFF) - return sig_b ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z, 0xFF, 0); - if (exp_a == 0) - ++exp_d; - else - sig_a |= 0x20000000; - SFLOAT_SHIFT(32, sig_a, -exp_d, &sig_a); - exp_z = exp_b; - } else { - if (exp_a == 0xFF) - return (sig_a | sig_b) ? sfloat_propagate_nan(state, a, b) : a; - if (exp_a == 0) - return SFLOAT_PACK(sign_z, 0, (sig_a + sig_b) >> 6); - sig_z = 0x40000000 + sig_a + sig_b; - exp_z = exp_a; - goto end; - } - sig_a |= 0x20000000; - sig_z = (sig_a + sig_b) << 1; - --exp_z; - if ((int32_t)sig_z < 0) { - sig_z = sig_a + sig_b; - ++exp_z; - } -end: - return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z); -} - -/* - * Returns the result of subtracting the absolute values of `a' and `b'. If the - * sign `sign_z' is one, the difference is negated before being returned. The - * sign is ignored if the result is a NaN. - */ -static sfloat_t sfloat_sub_impl(sfloat_state_t *state, sfloat_t a, sfloat_t b, bool sign_z) { - int16_t exp_a = SFLOAT_EXTRACT_EXP(a); - int16_t exp_b = SFLOAT_EXTRACT_EXP(b); - int16_t exp_z = 0; - int16_t exp_d = exp_a - exp_b; - uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a) << 7; - uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b) << 7; - uint32_t sig_z = 0; - - if (0 < exp_d) goto exp_greater_a; - if (exp_d < 0) goto exp_greater_b; - - if (exp_a == 0xFF) { - if (sig_a | sig_b) - return sfloat_propagate_nan(state, a, b); - SFLOAT_RAISE(state, SFLOAT_INVALID); - return SFLOAT_NAN; - } - - if (exp_a == 0) - exp_a = exp_b = 1; - - if (sig_b < sig_a) goto greater_a; - if (sig_a < sig_b) goto greater_b; - - return SFLOAT_PACK(state->roundingmode == SFLOAT_ROUND_DOWN, 0, 0); - -exp_greater_b: - if (exp_b == 0xFF) - return (sig_b) ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z ^ 1, 0xFF, 0); - if (exp_a == 0) - ++exp_d; - else - sig_a |= 0x40000000; - SFLOAT_SHIFT(32, sig_a, -exp_d, &sig_a); - sig_b |= 0x40000000; -greater_b: - sig_z = sig_b - sig_a; - exp_z = exp_b; - sign_z ^= 1; - goto end; - -exp_greater_a: - if (exp_a == 0xFF) - return (sig_a) ? sfloat_propagate_nan(state, a, b) : a; - if (exp_b == 0) - --exp_d; - else - sig_b |= 0x40000000; - SFLOAT_SHIFT(32, sig_b, exp_d, &sig_b); - sig_a |= 0x40000000; -greater_a: - sig_z = sig_a - sig_b; - exp_z = exp_a; - -end: - --exp_z; - return SFLOAT_PACK_normal(state, sign_z, exp_z, sig_z); -} - -static GMQCC_INLINE sfloat_t sfloat_add(sfloat_state_t *state, sfloat_t a, sfloat_t b) { - bool sign_a = SFLOAT_EXTRACT_SIGN(a); - bool sign_b = SFLOAT_EXTRACT_SIGN(b); - return (sign_a == sign_b) ? sfloat_add_impl(state, a, b, sign_a) - : sfloat_sub_impl(state, a, b, sign_a); -} - -static GMQCC_INLINE sfloat_t sfloat_sub(sfloat_state_t *state, sfloat_t a, sfloat_t b) { - bool sign_a = SFLOAT_EXTRACT_SIGN(a); - bool sign_b = SFLOAT_EXTRACT_SIGN(b); - return (sign_a == sign_b) ? sfloat_sub_impl(state, a, b, sign_a) - : sfloat_add_impl(state, a, b, sign_a); -} - -static sfloat_t sfloat_mul(sfloat_state_t *state, sfloat_t a, sfloat_t b) { - int16_t exp_a = SFLOAT_EXTRACT_EXP(a); - int16_t exp_b = SFLOAT_EXTRACT_EXP(b); - int16_t exp_z = 0; - uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a); - uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b); - uint32_t sig_z = 0; - uint64_t sig_z64 = 0; - bool sign_a = SFLOAT_EXTRACT_SIGN(a); - bool sign_b = SFLOAT_EXTRACT_SIGN(b); - bool sign_z = sign_a ^ sign_b; - - if (exp_a == 0xFF) { - if (sig_a || ((exp_b == 0xFF) && sig_b)) - return sfloat_propagate_nan(state, a, b); - if ((exp_b | sig_b) == 0) { - SFLOAT_RAISE(state, SFLOAT_INVALID); - return SFLOAT_NAN; - } - return SFLOAT_PACK(sign_z, 0xFF, 0); - } - if (exp_b == 0xFF) { - if (sig_b) - return sfloat_propagate_nan(state, a, b); - if ((exp_a | sig_a) == 0) { - SFLOAT_RAISE(state, SFLOAT_INVALID); - return SFLOAT_NAN; - } - return SFLOAT_PACK(sign_z, 0xFF, 0); - } - if (exp_a == 0) { - if (sig_a == 0) - return SFLOAT_PACK(sign_z, 0, 0); - SFLOAT_SUBNORMALIZE(sig_a, &exp_a, &sig_a); - } - if (exp_b == 0) { - if (sig_b == 0) - return SFLOAT_PACK(sign_z, 0, 0); - SFLOAT_SUBNORMALIZE(sig_b, &exp_b, &sig_b); - } - exp_z = exp_a + exp_b - 0x7F; - sig_a = (sig_a | 0x00800000) << 7; - sig_b = (sig_b | 0x00800000) << 8; - SFLOAT_SHIFT(64, ((uint64_t)sig_a) * sig_b, 32, &sig_z64); - sig_z = sig_z64; - if (0 <= (int32_t)(sig_z << 1)) { - sig_z <<= 1; - --exp_z; - } - return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z); -} - -static sfloat_t sfloat_div(sfloat_state_t *state, sfloat_t a, sfloat_t b) { - int16_t exp_a = SFLOAT_EXTRACT_EXP(a); - int16_t exp_b = SFLOAT_EXTRACT_EXP(b); - int16_t exp_z = 0; - uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a); - uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b); - uint32_t sig_z = 0; - bool sign_a = SFLOAT_EXTRACT_SIGN(a); - bool sign_b = SFLOAT_EXTRACT_SIGN(b); - bool sign_z = sign_a ^ sign_b; - - if (exp_a == 0xFF) { - if (sig_a) - return sfloat_propagate_nan(state, a, b); - if (exp_b == 0xFF) { - if (sig_b) - return sfloat_propagate_nan(state, a, b); - SFLOAT_RAISE(state, SFLOAT_INVALID); - return SFLOAT_NAN; - } - return SFLOAT_PACK(sign_z, 0xFF, 0); - } - if (exp_b == 0xFF) - return (sig_b) ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z, 0, 0); - if (exp_b == 0) { - if (sig_b == 0) { - if ((exp_a | sig_a) == 0) { - SFLOAT_RAISE(state, SFLOAT_INVALID); - return SFLOAT_NAN; - } - SFLOAT_RAISE(state, SFLOAT_DIVBYZERO); - return SFLOAT_PACK(sign_z, 0xFF, 0); - } - SFLOAT_SUBNORMALIZE(sig_b, &exp_b, &sig_b); - } - if (exp_a == 0) { - if (sig_a == 0) - return SFLOAT_PACK(sign_z, 0, 0); - SFLOAT_SUBNORMALIZE(sig_a, &exp_a, &sig_a); - } - exp_z = exp_a - exp_b + 0x7D; - sig_a = (sig_a | 0x00800000) << 7; - sig_b = (sig_b | 0x00800000) << 8; - if (sig_b <= (sig_a + sig_a)) { - sig_a >>= 1; - ++exp_z; - } - sig_z = (((uint64_t)sig_a) << 32) / sig_b; - if ((sig_z & 0x3F) == 0) - sig_z |= ((uint64_t)sig_b * sig_z != ((uint64_t)sig_a) << 32); - return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z); -} - -static sfloat_t sfloat_neg(sfloat_state_t *state, sfloat_t a) { - sfloat_cast_t neg; - neg.f = -1; - return sfloat_mul(state, a, neg.s); -} - -static GMQCC_INLINE void sfloat_check(lex_ctx_t ctx, sfloat_state_t *state, const char *vec) { - /* Exception comes from vector component */ - if (vec) { - if (state->exceptionflags & SFLOAT_DIVBYZERO) - compile_error(ctx, "division by zero in `%s' component", vec); - if (state->exceptionflags & SFLOAT_INVALID) - compile_error(ctx, "undefined (inf) in `%s' component", vec); - if (state->exceptionflags & SFLOAT_OVERFLOW) - compile_error(ctx, "arithmetic overflow in `%s' component", vec); - if (state->exceptionflags & SFLOAT_UNDERFLOW) - compile_error(ctx, "arithmetic underflow in `%s' component", vec); - return; - } - if (state->exceptionflags & SFLOAT_DIVBYZERO) - compile_error(ctx, "division by zero"); - if (state->exceptionflags & SFLOAT_INVALID) - compile_error(ctx, "undefined (inf)"); - if (state->exceptionflags & SFLOAT_OVERFLOW) - compile_error(ctx, "arithmetic overflow"); - if (state->exceptionflags & SFLOAT_UNDERFLOW) - compile_error(ctx, "arithmetic underflow"); -} - -static GMQCC_INLINE void sfloat_init(sfloat_state_t *state) { - state->exceptionflags = SFLOAT_NOEXCEPT; - state->roundingmode = FOLD_ROUNDING; - state->tiny = FOLD_TINYNESS; -} - -/* - * There is two stages to constant folding in GMQCC: there is the parse - * stage constant folding, where, with the help of the AST, operator - * usages can be constant folded. Then there is the constant folding - * in the IR for things like eliding if statements, can occur. - * - * This file is thus, split into two parts. - */ - -#define isfloat(X) (((ast_expression*)(X))->vtype == TYPE_FLOAT) -#define isvector(X) (((ast_expression*)(X))->vtype == TYPE_VECTOR) -#define isstring(X) (((ast_expression*)(X))->vtype == TYPE_STRING) -#define isarray(X) (((ast_expression*)(X))->vtype == TYPE_ARRAY) -#define isfloats(X,Y) (isfloat (X) && isfloat (Y)) - -/* - * Implementation of basic vector math for vec3_t, for trivial constant - * folding. - * - * TODO: gcc/clang hinting for autovectorization - */ -typedef enum { - VEC_COMP_X = 1 << 0, - VEC_COMP_Y = 1 << 1, - VEC_COMP_Z = 1 << 2 -} vec3_comp_t; - -typedef struct { - sfloat_cast_t x; - sfloat_cast_t y; - sfloat_cast_t z; -} vec3_soft_t; - -typedef struct { - vec3_comp_t faults; - sfloat_state_t state[3]; -} vec3_soft_state_t; - -static GMQCC_INLINE vec3_soft_t vec3_soft_convert(vec3_t vec) { - vec3_soft_t soft; - soft.x.f = vec.x; - soft.y.f = vec.y; - soft.z.f = vec.z; - return soft; -} - -static GMQCC_INLINE bool vec3_soft_exception(vec3_soft_state_t *vstate, size_t index) { - sfloat_exceptionflags_t flags = vstate->state[index].exceptionflags; - if (flags & SFLOAT_DIVBYZERO) return true; - if (flags & SFLOAT_INVALID) return true; - if (flags & SFLOAT_OVERFLOW) return true; - if (flags & SFLOAT_UNDERFLOW) return true; - return false; -} - -static GMQCC_INLINE void vec3_soft_eval(vec3_soft_state_t *state, - sfloat_t (*callback)(sfloat_state_t *, sfloat_t, sfloat_t), - vec3_t a, - vec3_t b) -{ - vec3_soft_t sa = vec3_soft_convert(a); - vec3_soft_t sb = vec3_soft_convert(b); - callback(&state->state[0], sa.x.s, sb.x.s); - if (vec3_soft_exception(state, 0)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_X); - callback(&state->state[1], sa.y.s, sb.y.s); - if (vec3_soft_exception(state, 1)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_Y); - callback(&state->state[2], sa.z.s, sb.z.s); - if (vec3_soft_exception(state, 2)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_Z); -} - -static GMQCC_INLINE void vec3_check_except(vec3_t a, - vec3_t b, - lex_ctx_t ctx, - sfloat_t (*callback)(sfloat_state_t *, sfloat_t, sfloat_t)) -{ - vec3_soft_state_t state; - - if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) - return; - - sfloat_init(&state.state[0]); - sfloat_init(&state.state[1]); - sfloat_init(&state.state[2]); - - vec3_soft_eval(&state, callback, a, b); - if (state.faults & VEC_COMP_X) sfloat_check(ctx, &state.state[0], "x"); - if (state.faults & VEC_COMP_Y) sfloat_check(ctx, &state.state[1], "y"); - if (state.faults & VEC_COMP_Z) sfloat_check(ctx, &state.state[2], "z"); -} - -static GMQCC_INLINE vec3_t vec3_add(lex_ctx_t ctx, vec3_t a, vec3_t b) { - vec3_t out; - vec3_check_except(a, b, ctx, &sfloat_add); - out.x = a.x + b.x; - out.y = a.y + b.y; - out.z = a.z + b.z; - return out; -} - -static GMQCC_INLINE vec3_t vec3_sub(lex_ctx_t ctx, vec3_t a, vec3_t b) { - vec3_t out; - vec3_check_except(a, b, ctx, &sfloat_sub); - out.x = a.x - b.x; - out.y = a.y - b.y; - out.z = a.z - b.z; - return out; -} - -static GMQCC_INLINE vec3_t vec3_neg(lex_ctx_t ctx, vec3_t a) { - vec3_t out; - sfloat_cast_t v[3]; - sfloat_state_t s[3]; - - if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) - goto end; - - v[0].f = a.x; - v[1].f = a.y; - v[2].f = a.z; - - sfloat_init(&s[0]); - sfloat_init(&s[1]); - sfloat_init(&s[2]); - - sfloat_neg(&s[0], v[0].s); - sfloat_neg(&s[1], v[1].s); - sfloat_neg(&s[2], v[2].s); - - sfloat_check(ctx, &s[0], NULL); - sfloat_check(ctx, &s[1], NULL); - sfloat_check(ctx, &s[2], NULL); - -end: - out.x = -a.x; - out.y = -a.y; - out.z = -a.z; - return out; -} - -static GMQCC_INLINE vec3_t vec3_or(vec3_t a, vec3_t b) { - vec3_t out; - out.x = (qcfloat_t)(((qcint_t)a.x) | ((qcint_t)b.x)); - out.y = (qcfloat_t)(((qcint_t)a.y) | ((qcint_t)b.y)); - out.z = (qcfloat_t)(((qcint_t)a.z) | ((qcint_t)b.z)); - return out; -} - -static GMQCC_INLINE vec3_t vec3_orvf(vec3_t a, qcfloat_t b) { - vec3_t out; - out.x = (qcfloat_t)(((qcint_t)a.x) | ((qcint_t)b)); - out.y = (qcfloat_t)(((qcint_t)a.y) | ((qcint_t)b)); - out.z = (qcfloat_t)(((qcint_t)a.z) | ((qcint_t)b)); - return out; -} - -static GMQCC_INLINE vec3_t vec3_and(vec3_t a, vec3_t b) { - vec3_t out; - out.x = (qcfloat_t)(((qcint_t)a.x) & ((qcint_t)b.x)); - out.y = (qcfloat_t)(((qcint_t)a.y) & ((qcint_t)b.y)); - out.z = (qcfloat_t)(((qcint_t)a.z) & ((qcint_t)b.z)); - return out; -} - -static GMQCC_INLINE vec3_t vec3_andvf(vec3_t a, qcfloat_t b) { - vec3_t out; - out.x = (qcfloat_t)(((qcint_t)a.x) & ((qcint_t)b)); - out.y = (qcfloat_t)(((qcint_t)a.y) & ((qcint_t)b)); - out.z = (qcfloat_t)(((qcint_t)a.z) & ((qcint_t)b)); - return out; -} - -static GMQCC_INLINE vec3_t vec3_xor(vec3_t a, vec3_t b) { - vec3_t out; - out.x = (qcfloat_t)(((qcint_t)a.x) ^ ((qcint_t)b.x)); - out.y = (qcfloat_t)(((qcint_t)a.y) ^ ((qcint_t)b.y)); - out.z = (qcfloat_t)(((qcint_t)a.z) ^ ((qcint_t)b.z)); - return out; -} - -static GMQCC_INLINE vec3_t vec3_xorvf(vec3_t a, qcfloat_t b) { - vec3_t out; - out.x = (qcfloat_t)(((qcint_t)a.x) ^ ((qcint_t)b)); - out.y = (qcfloat_t)(((qcint_t)a.y) ^ ((qcint_t)b)); - out.z = (qcfloat_t)(((qcint_t)a.z) ^ ((qcint_t)b)); - return out; -} - -static GMQCC_INLINE vec3_t vec3_not(vec3_t a) { - vec3_t out; - out.x = -1-a.x; - out.y = -1-a.y; - out.z = -1-a.z; - return out; -} - -static GMQCC_INLINE qcfloat_t vec3_mulvv(lex_ctx_t ctx, vec3_t a, vec3_t b) { - vec3_soft_t sa; - vec3_soft_t sb; - sfloat_state_t s[5]; - sfloat_t r[5]; - - if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) - goto end; - - sa = vec3_soft_convert(a); - sb = vec3_soft_convert(b); - - sfloat_init(&s[0]); - sfloat_init(&s[1]); - sfloat_init(&s[2]); - sfloat_init(&s[3]); - sfloat_init(&s[4]); - - r[0] = sfloat_mul(&s[0], sa.x.s, sb.x.s); - r[1] = sfloat_mul(&s[1], sa.y.s, sb.y.s); - r[2] = sfloat_mul(&s[2], sa.z.s, sb.z.s); - r[3] = sfloat_add(&s[3], r[0], r[1]); - r[4] = sfloat_add(&s[4], r[3], r[2]); - - sfloat_check(ctx, &s[0], NULL); - sfloat_check(ctx, &s[1], NULL); - sfloat_check(ctx, &s[2], NULL); - sfloat_check(ctx, &s[3], NULL); - sfloat_check(ctx, &s[4], NULL); - -end: - return (a.x * b.x + a.y * b.y + a.z * b.z); -} - -static GMQCC_INLINE vec3_t vec3_mulvf(lex_ctx_t ctx, vec3_t a, qcfloat_t b) { - vec3_t out; - vec3_soft_t sa; - sfloat_cast_t sb; - sfloat_state_t s[3]; - - if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) - goto end; - - sa = vec3_soft_convert(a); - sb.f = b; - sfloat_init(&s[0]); - sfloat_init(&s[1]); - sfloat_init(&s[2]); - - sfloat_mul(&s[0], sa.x.s, sb.s); - sfloat_mul(&s[1], sa.y.s, sb.s); - sfloat_mul(&s[2], sa.z.s, sb.s); - - sfloat_check(ctx, &s[0], "x"); - sfloat_check(ctx, &s[1], "y"); - sfloat_check(ctx, &s[2], "z"); - -end: - out.x = a.x * b; - out.y = a.y * b; - out.z = a.z * b; - return out; -} - -static GMQCC_INLINE bool vec3_cmp(vec3_t a, vec3_t b) { - return a.x == b.x && - a.y == b.y && - a.z == b.z; -} - -static GMQCC_INLINE vec3_t vec3_create(float x, float y, float z) { - vec3_t out; - out.x = x; - out.y = y; - out.z = z; - return out; -} - -static GMQCC_INLINE qcfloat_t vec3_notf(vec3_t a) { - return (!a.x && !a.y && !a.z); -} - -static GMQCC_INLINE bool vec3_pbool(vec3_t a) { - return (a.x || a.y || a.z); -} - -static GMQCC_INLINE vec3_t vec3_cross(lex_ctx_t ctx, vec3_t a, vec3_t b) { - vec3_t out; - vec3_soft_t sa; - vec3_soft_t sb; - sfloat_t r[9]; - sfloat_state_t s[9]; - - if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) - goto end; - - sa = vec3_soft_convert(a); - sb = vec3_soft_convert(b); - - sfloat_init(&s[0]); - sfloat_init(&s[1]); - sfloat_init(&s[2]); - sfloat_init(&s[3]); - sfloat_init(&s[4]); - sfloat_init(&s[5]); - sfloat_init(&s[6]); - sfloat_init(&s[7]); - sfloat_init(&s[8]); - - r[0] = sfloat_mul(&s[0], sa.y.s, sb.z.s); - r[1] = sfloat_mul(&s[1], sa.z.s, sb.y.s); - r[2] = sfloat_mul(&s[2], sa.z.s, sb.x.s); - r[3] = sfloat_mul(&s[3], sa.x.s, sb.z.s); - r[4] = sfloat_mul(&s[4], sa.x.s, sb.y.s); - r[5] = sfloat_mul(&s[5], sa.y.s, sb.x.s); - r[6] = sfloat_sub(&s[6], r[0], r[1]); - r[7] = sfloat_sub(&s[7], r[2], r[3]); - r[8] = sfloat_sub(&s[8], r[4], r[5]); - - sfloat_check(ctx, &s[0], NULL); - sfloat_check(ctx, &s[1], NULL); - sfloat_check(ctx, &s[2], NULL); - sfloat_check(ctx, &s[3], NULL); - sfloat_check(ctx, &s[4], NULL); - sfloat_check(ctx, &s[5], NULL); - sfloat_check(ctx, &s[6], "x"); - sfloat_check(ctx, &s[7], "y"); - sfloat_check(ctx, &s[8], "z"); - -end: - out.x = a.y * b.z - a.z * b.y; - out.y = a.z * b.x - a.x * b.z; - out.z = a.x * b.y - a.y * b.x; - return out; -} - -static lex_ctx_t fold_ctx(fold_t *fold) { - lex_ctx_t ctx; - if (fold->parser->lex) - return parser_ctx(fold->parser); - - memset(&ctx, 0, sizeof(ctx)); - return ctx; -} - -static GMQCC_INLINE bool fold_immediate_true(fold_t *fold, ast_value *v) { - switch (v->expression.vtype) { - case TYPE_FLOAT: - return !!v->constval.vfloat; - case TYPE_INTEGER: - return !!v->constval.vint; - case TYPE_VECTOR: - if (OPTS_FLAG(CORRECT_LOGIC)) - return vec3_pbool(v->constval.vvec); - return !!(v->constval.vvec.x); - case TYPE_STRING: - if (!v->constval.vstring) - return false; - if (OPTS_FLAG(TRUE_EMPTY_STRINGS)) - return true; - return !!v->constval.vstring[0]; - default: - compile_error(fold_ctx(fold), "internal error: fold_immediate_true on invalid type"); - break; - } - return !!v->constval.vfunc; -} - -/* Handy macros to determine if an ast_value can be constant folded. */ -#define fold_can_1(X) \ - (ast_istype(((ast_expression*)(X)), ast_value) && (X)->hasvalue && ((X)->cvq == CV_CONST) && \ - ((ast_expression*)(X))->vtype != TYPE_FUNCTION) - -#define fold_can_2(X, Y) (fold_can_1(X) && fold_can_1(Y)) - -#define fold_immvalue_float(E) ((E)->constval.vfloat) -#define fold_immvalue_vector(E) ((E)->constval.vvec) -#define fold_immvalue_string(E) ((E)->constval.vstring) - -fold_t *fold_init(parser_t *parser) { - fold_t *fold = (fold_t*)mem_a(sizeof(fold_t)); - fold->parser = parser; - fold->imm_float = NULL; - fold->imm_vector = NULL; - fold->imm_string = NULL; - fold->imm_string_untranslate = util_htnew(FOLD_STRING_UNTRANSLATE_HTSIZE); - fold->imm_string_dotranslate = util_htnew(FOLD_STRING_DOTRANSLATE_HTSIZE); - - /* - * prime the tables with common constant values at constant - * locations. - */ - (void)fold_constgen_float (fold, 0.0f, false); - (void)fold_constgen_float (fold, 1.0f, false); - (void)fold_constgen_float (fold, -1.0f, false); - (void)fold_constgen_float (fold, 2.0f, false); - - (void)fold_constgen_vector(fold, vec3_create(0.0f, 0.0f, 0.0f)); - (void)fold_constgen_vector(fold, vec3_create(-1.0f, -1.0f, -1.0f)); - - return fold; -} - -bool fold_generate(fold_t *fold, ir_builder *ir) { - /* generate globals for immediate folded values */ - size_t i; - ast_value *cur; - - for (i = 0; i < vec_size(fold->imm_float); ++i) - if (!ast_global_codegen ((cur = fold->imm_float[i]), ir, false)) goto err; - for (i = 0; i < vec_size(fold->imm_vector); ++i) - if (!ast_global_codegen((cur = fold->imm_vector[i]), ir, false)) goto err; - for (i = 0; i < vec_size(fold->imm_string); ++i) - if (!ast_global_codegen((cur = fold->imm_string[i]), ir, false)) goto err; - - return true; - -err: - con_out("failed to generate global %s\n", cur->name); - ir_builder_delete(ir); - return false; -} - -void fold_cleanup(fold_t *fold) { - size_t i; - - for (i = 0; i < vec_size(fold->imm_float); ++i) ast_delete(fold->imm_float[i]); - for (i = 0; i < vec_size(fold->imm_vector); ++i) ast_delete(fold->imm_vector[i]); - for (i = 0; i < vec_size(fold->imm_string); ++i) ast_delete(fold->imm_string[i]); - - vec_free(fold->imm_float); - vec_free(fold->imm_vector); - vec_free(fold->imm_string); - - util_htdel(fold->imm_string_untranslate); - util_htdel(fold->imm_string_dotranslate); - - mem_d(fold); -} - -ast_expression *fold_constgen_float(fold_t *fold, qcfloat_t value, bool inexact) { - ast_value *out = NULL; - size_t i; - - for (i = 0; i < vec_size(fold->imm_float); i++) { - if (!memcmp(&fold->imm_float[i]->constval.vfloat, &value, sizeof(qcfloat_t))) - return (ast_expression*)fold->imm_float[i]; - } - - out = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_FLOAT); - out->cvq = CV_CONST; - out->hasvalue = true; - out->inexact = inexact; - out->constval.vfloat = value; - - vec_push(fold->imm_float, out); - - return (ast_expression*)out; -} - -ast_expression *fold_constgen_vector(fold_t *fold, vec3_t value) { - ast_value *out; - size_t i; - - for (i = 0; i < vec_size(fold->imm_vector); i++) { - if (vec3_cmp(fold->imm_vector[i]->constval.vvec, value)) - return (ast_expression*)fold->imm_vector[i]; - } - - out = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_VECTOR); - out->cvq = CV_CONST; - out->hasvalue = true; - out->constval.vvec = value; - - vec_push(fold->imm_vector, out); - - return (ast_expression*)out; -} - -ast_expression *fold_constgen_string(fold_t *fold, const char *str, bool translate) { - hash_table_t *table = (translate) ? fold->imm_string_untranslate : fold->imm_string_dotranslate; - ast_value *out = NULL; - size_t hash = util_hthash(table, str); - - if ((out = (ast_value*)util_htgeth(table, str, hash))) - return (ast_expression*)out; - - if (translate) { - char name[32]; - util_snprintf(name, sizeof(name), "dotranslate_%lu", (unsigned long)(fold->parser->translated++)); - out = ast_value_new(parser_ctx(fold->parser), name, TYPE_STRING); - out->expression.flags |= AST_FLAG_INCLUDE_DEF; /* def needs to be included for translatables */ - } else - out = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_STRING); - - out->cvq = CV_CONST; - out->hasvalue = true; - out->isimm = true; - out->constval.vstring = parser_strdup(str); - - vec_push(fold->imm_string, out); - util_htseth(table, str, hash, out); - - return (ast_expression*)out; -} - -typedef union { - void (*callback)(void); - sfloat_t (*binary)(sfloat_state_t *, sfloat_t, sfloat_t); - sfloat_t (*unary)(sfloat_state_t *, sfloat_t); -} float_check_callback_t; - -static bool fold_check_except_float_impl(void (*callback)(void), - fold_t *fold, - ast_value *a, - ast_value *b) -{ - float_check_callback_t call; - sfloat_state_t s; - sfloat_cast_t ca; - - if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS) && !OPTS_WARN(WARN_INEXACT_COMPARES)) - return false; - - call.callback = callback; - sfloat_init(&s); - ca.f = fold_immvalue_float(a); - if (b) { - sfloat_cast_t cb; - cb.f = fold_immvalue_float(b); - call.binary(&s, ca.s, cb.s); - } else { - call.unary(&s, ca.s); - } - - if (s.exceptionflags == 0) - return false; - - if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) - goto inexact_possible; - - sfloat_check(fold_ctx(fold), &s, NULL); - -inexact_possible: - return s.exceptionflags & SFLOAT_INEXACT; -} - -#define fold_check_except_float(CALLBACK, FOLD, A, B) \ - fold_check_except_float_impl(((void (*)(void))(CALLBACK)), (FOLD), (A), (B)) - -static bool fold_check_inexact_float(fold_t *fold, ast_value *a, ast_value *b) { - lex_ctx_t ctx = fold_ctx(fold); - if (!OPTS_WARN(WARN_INEXACT_COMPARES)) - return false; - if (!a->inexact && !b->inexact) - return false; - return compile_warning(ctx, WARN_INEXACT_COMPARES, "inexact value in comparison"); -} - -static GMQCC_INLINE ast_expression *fold_op_mul_vec(fold_t *fold, vec3_t vec, ast_value *sel, const char *set) { - qcfloat_t x = (&vec.x)[set[0]-'x']; - qcfloat_t y = (&vec.x)[set[1]-'x']; - qcfloat_t z = (&vec.x)[set[2]-'x']; - if (!y && !z) { - ast_expression *out; - ++opts_optimizationcount[OPTIM_VECTOR_COMPONENTS]; - out = (ast_expression*)ast_member_new(fold_ctx(fold), (ast_expression*)sel, set[0]-'x', NULL); - out->node.keep = false; - ((ast_member*)out)->rvalue = true; - if (x != -1.0f) - return (ast_expression*)ast_binary_new(fold_ctx(fold), INSTR_MUL_F, fold_constgen_float(fold, x, false), out); - } - return NULL; -} - - -static GMQCC_INLINE ast_expression *fold_op_neg(fold_t *fold, ast_value *a) { - if (isfloat(a)) { - if (fold_can_1(a)) { - /* Negation can produce inexact as well */ - bool inexact = fold_check_except_float(&sfloat_neg, fold, a, NULL); - return fold_constgen_float(fold, -fold_immvalue_float(a), inexact); - } - } else if (isvector(a)) { - if (fold_can_1(a)) - return fold_constgen_vector(fold, vec3_neg(fold_ctx(fold), fold_immvalue_vector(a))); - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_not(fold_t *fold, ast_value *a) { - if (isfloat(a)) { - if (fold_can_1(a)) - return fold_constgen_float(fold, !fold_immvalue_float(a), false); - } else if (isvector(a)) { - if (fold_can_1(a)) - return fold_constgen_float(fold, vec3_notf(fold_immvalue_vector(a)), false); - } else if (isstring(a)) { - if (fold_can_1(a)) { - if (OPTS_FLAG(TRUE_EMPTY_STRINGS)) - return fold_constgen_float(fold, !fold_immvalue_string(a), false); - else - return fold_constgen_float(fold, !fold_immvalue_string(a) || !*fold_immvalue_string(a), false); - } - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_add(fold_t *fold, ast_value *a, ast_value *b) { - if (isfloat(a)) { - if (fold_can_2(a, b)) { - bool inexact = fold_check_except_float(&sfloat_add, fold, a, b); - return fold_constgen_float(fold, fold_immvalue_float(a) + fold_immvalue_float(b), inexact); - } - } else if (isvector(a)) { - if (fold_can_2(a, b)) - return fold_constgen_vector(fold, vec3_add(fold_ctx(fold), - fold_immvalue_vector(a), - fold_immvalue_vector(b))); - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_sub(fold_t *fold, ast_value *a, ast_value *b) { - if (isfloat(a)) { - if (fold_can_2(a, b)) { - bool inexact = fold_check_except_float(&sfloat_sub, fold, a, b); - return fold_constgen_float(fold, fold_immvalue_float(a) - fold_immvalue_float(b), inexact); - } - } else if (isvector(a)) { - if (fold_can_2(a, b)) - return fold_constgen_vector(fold, vec3_sub(fold_ctx(fold), - fold_immvalue_vector(a), - fold_immvalue_vector(b))); - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_mul(fold_t *fold, ast_value *a, ast_value *b) { - if (isfloat(a)) { - if (isvector(b)) { - if (fold_can_2(a, b)) - return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(b), fold_immvalue_float(a))); - } else { - if (fold_can_2(a, b)) { - bool inexact = fold_check_except_float(&sfloat_mul, fold, a, b); - return fold_constgen_float(fold, fold_immvalue_float(a) * fold_immvalue_float(b), inexact); - } - } - } else if (isvector(a)) { - if (isfloat(b)) { - if (fold_can_2(a, b)) - return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(a), fold_immvalue_float(b))); - } else { - if (fold_can_2(a, b)) { - return fold_constgen_float(fold, vec3_mulvv(fold_ctx(fold), fold_immvalue_vector(a), fold_immvalue_vector(b)), false); - } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(a)) { - ast_expression *out; - if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "xyz"))) return out; - if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "yxz"))) return out; - if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "zxy"))) return out; - } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(b)) { - ast_expression *out; - if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "xyz"))) return out; - if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "yxz"))) return out; - if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "zxy"))) return out; - } - } - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_div(fold_t *fold, ast_value *a, ast_value *b) { - if (isfloat(a)) { - if (fold_can_2(a, b)) { - bool inexact = fold_check_except_float(&sfloat_div, fold, a, b); - return fold_constgen_float(fold, fold_immvalue_float(a) / fold_immvalue_float(b), inexact); - } else if (fold_can_1(b)) { - return (ast_expression*)ast_binary_new( - fold_ctx(fold), - INSTR_MUL_F, - (ast_expression*)a, - fold_constgen_float(fold, 1.0f / fold_immvalue_float(b), false) - ); - } - } else if (isvector(a)) { - if (fold_can_2(a, b)) { - return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(a), 1.0f / fold_immvalue_float(b))); - } else { - return (ast_expression*)ast_binary_new( - fold_ctx(fold), - INSTR_MUL_VF, - (ast_expression*)a, - (fold_can_1(b)) - ? (ast_expression*)fold_constgen_float(fold, 1.0f / fold_immvalue_float(b), false) - : (ast_expression*)ast_binary_new( - fold_ctx(fold), - INSTR_DIV_F, - (ast_expression*)fold->imm_float[1], - (ast_expression*)b - ) - ); - } - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_mod(fold_t *fold, ast_value *a, ast_value *b) { - return (fold_can_2(a, b)) - ? fold_constgen_float(fold, fmod(fold_immvalue_float(a), fold_immvalue_float(b)), false) - : NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_bor(fold_t *fold, ast_value *a, ast_value *b) { - if (isfloat(a)) { - if (fold_can_2(a, b)) - return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) | ((qcint_t)fold_immvalue_float(b))), false); - } else { - if (isvector(b)) { - if (fold_can_2(a, b)) - return fold_constgen_vector(fold, vec3_or(fold_immvalue_vector(a), fold_immvalue_vector(b))); - } else { - if (fold_can_2(a, b)) - return fold_constgen_vector(fold, vec3_orvf(fold_immvalue_vector(a), fold_immvalue_float(b))); - } - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_band(fold_t *fold, ast_value *a, ast_value *b) { - if (isfloat(a)) { - if (fold_can_2(a, b)) - return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) & ((qcint_t)fold_immvalue_float(b))), false); - } else { - if (isvector(b)) { - if (fold_can_2(a, b)) - return fold_constgen_vector(fold, vec3_and(fold_immvalue_vector(a), fold_immvalue_vector(b))); - } else { - if (fold_can_2(a, b)) - return fold_constgen_vector(fold, vec3_andvf(fold_immvalue_vector(a), fold_immvalue_float(b))); - } - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_xor(fold_t *fold, ast_value *a, ast_value *b) { - if (isfloat(a)) { - if (fold_can_2(a, b)) - return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) ^ ((qcint_t)fold_immvalue_float(b))), false); - } else { - if (fold_can_2(a, b)) { - if (isvector(b)) - return fold_constgen_vector(fold, vec3_xor(fold_immvalue_vector(a), fold_immvalue_vector(b))); - else - return fold_constgen_vector(fold, vec3_xorvf(fold_immvalue_vector(a), fold_immvalue_float(b))); - } - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_lshift(fold_t *fold, ast_value *a, ast_value *b) { - if (fold_can_2(a, b) && isfloats(a, b)) - return fold_constgen_float(fold, (qcfloat_t)floorf(fold_immvalue_float(a) * powf(2.0f, fold_immvalue_float(b))), false); - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_rshift(fold_t *fold, ast_value *a, ast_value *b) { - if (fold_can_2(a, b) && isfloats(a, b)) - return fold_constgen_float(fold, (qcfloat_t)floorf(fold_immvalue_float(a) / powf(2.0f, fold_immvalue_float(b))), false); - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_andor(fold_t *fold, ast_value *a, ast_value *b, float expr) { - if (fold_can_2(a, b)) { - if (OPTS_FLAG(PERL_LOGIC)) { - if (expr) - return (fold_immediate_true(fold, a)) ? (ast_expression*)a : (ast_expression*)b; - else - return (fold_immediate_true(fold, a)) ? (ast_expression*)b : (ast_expression*)a; - } else { - return fold_constgen_float ( - fold, - ((expr) ? (fold_immediate_true(fold, a) || fold_immediate_true(fold, b)) - : (fold_immediate_true(fold, a) && fold_immediate_true(fold, b))) - ? 1 - : 0, - false - ); - } - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_tern(fold_t *fold, ast_value *a, ast_value *b, ast_value *c) { - if (fold_can_1(a)) { - return fold_immediate_true(fold, a) - ? (ast_expression*)b - : (ast_expression*)c; - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_exp(fold_t *fold, ast_value *a, ast_value *b) { - if (fold_can_2(a, b)) - return fold_constgen_float(fold, (qcfloat_t)powf(fold_immvalue_float(a), fold_immvalue_float(b)), false); - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_lteqgt(fold_t *fold, ast_value *a, ast_value *b) { - if (fold_can_2(a,b)) { - fold_check_inexact_float(fold, a, b); - if (fold_immvalue_float(a) < fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[2]; - if (fold_immvalue_float(a) == fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[0]; - if (fold_immvalue_float(a) > fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[1]; - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_ltgt(fold_t *fold, ast_value *a, ast_value *b, bool lt) { - if (fold_can_2(a, b)) { - fold_check_inexact_float(fold, a, b); - return (lt) ? (ast_expression*)fold->imm_float[!!(fold_immvalue_float(a) < fold_immvalue_float(b))] - : (ast_expression*)fold->imm_float[!!(fold_immvalue_float(a) > fold_immvalue_float(b))]; - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_cmp(fold_t *fold, ast_value *a, ast_value *b, bool ne) { - if (fold_can_2(a, b)) { - if (isfloat(a) && isfloat(b)) { - float la = fold_immvalue_float(a); - float lb = fold_immvalue_float(b); - fold_check_inexact_float(fold, a, b); - return (ast_expression*)fold->imm_float[ne ? la != lb : la == lb]; - } else if (isvector(a) && isvector(b)) { - vec3_t la = fold_immvalue_vector(a); - vec3_t lb = fold_immvalue_vector(b); - bool compare = vec3_cmp(la, lb); - return (ast_expression*)fold->imm_float[ne ? !compare : compare]; - } else if (isstring(a) && isstring(b)) { - bool compare = !strcmp(fold_immvalue_string(a), fold_immvalue_string(b)); - return (ast_expression*)fold->imm_float[ne ? !compare : compare]; - } - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_bnot(fold_t *fold, ast_value *a) { - if (isfloat(a)) { - if (fold_can_1(a)) - return fold_constgen_float(fold, -1-fold_immvalue_float(a), false); - } else { - if (isvector(a)) { - if (fold_can_1(a)) - return fold_constgen_vector(fold, vec3_not(fold_immvalue_vector(a))); - } - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_cross(fold_t *fold, ast_value *a, ast_value *b) { - if (fold_can_2(a, b)) - return fold_constgen_vector(fold, vec3_cross(fold_ctx(fold), - fold_immvalue_vector(a), - fold_immvalue_vector(b))); - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_length(fold_t *fold, ast_value *a) { - if (fold_can_1(a) && isstring(a)) - return fold_constgen_float(fold, strlen(fold_immvalue_string(a)), false); - if (isarray(a)) - return fold_constgen_float(fold, vec_size(a->initlist), false); - return NULL; -} - -ast_expression *fold_op(fold_t *fold, const oper_info *info, ast_expression **opexprs) { - ast_value *a = (ast_value*)opexprs[0]; - ast_value *b = (ast_value*)opexprs[1]; - ast_value *c = (ast_value*)opexprs[2]; - ast_expression *e = NULL; - - /* can a fold operation be applied to this operator usage? */ - if (!info->folds) - return NULL; - - switch(info->operands) { - case 3: if(!c) return NULL; - case 2: if(!b) return NULL; - case 1: - if(!a) { - compile_error(fold_ctx(fold), "internal error: fold_op no operands to fold\n"); - return NULL; - } - } - - /* - * we could use a boolean and default case but ironically gcc produces - * invalid broken assembly from that operation. clang/tcc get it right, - * but interestingly ignore compiling this to a jump-table when I do that, - * this happens to be the most efficent method, since you have per-level - * granularity on the pointer check happening only for the case you check - * it in. Opposed to the default method which would involve a boolean and - * pointer check after wards. - */ - #define fold_op_case(ARGS, ARGS_OPID, OP, ARGS_FOLD) \ - case opid##ARGS ARGS_OPID: \ - if ((e = fold_op_##OP ARGS_FOLD)) { \ - ++opts_optimizationcount[OPTIM_CONST_FOLD]; \ - } \ - return e - - switch(info->id) { - fold_op_case(2, ('-', 'P'), neg, (fold, a)); - fold_op_case(2, ('!', 'P'), not, (fold, a)); - fold_op_case(1, ('+'), add, (fold, a, b)); - fold_op_case(1, ('-'), sub, (fold, a, b)); - fold_op_case(1, ('*'), mul, (fold, a, b)); - fold_op_case(1, ('/'), div, (fold, a, b)); - fold_op_case(1, ('%'), mod, (fold, a, b)); - fold_op_case(1, ('|'), bor, (fold, a, b)); - fold_op_case(1, ('&'), band, (fold, a, b)); - fold_op_case(1, ('^'), xor, (fold, a, b)); - fold_op_case(1, ('<'), ltgt, (fold, a, b, true)); - fold_op_case(1, ('>'), ltgt, (fold, a, b, false)); - fold_op_case(2, ('<', '<'), lshift, (fold, a, b)); - fold_op_case(2, ('>', '>'), rshift, (fold, a, b)); - fold_op_case(2, ('|', '|'), andor, (fold, a, b, true)); - fold_op_case(2, ('&', '&'), andor, (fold, a, b, false)); - fold_op_case(2, ('?', ':'), tern, (fold, a, b, c)); - fold_op_case(2, ('*', '*'), exp, (fold, a, b)); - fold_op_case(3, ('<','=','>'), lteqgt, (fold, a, b)); - fold_op_case(2, ('!', '='), cmp, (fold, a, b, true)); - fold_op_case(2, ('=', '='), cmp, (fold, a, b, false)); - fold_op_case(2, ('~', 'P'), bnot, (fold, a)); - fold_op_case(2, ('>', '<'), cross, (fold, a, b)); - fold_op_case(3, ('l', 'e', 'n'), length, (fold, a)); - } - #undef fold_op_case - compile_error(fold_ctx(fold), "internal error: attempted to constant-fold for unsupported operator"); - return NULL; -} - -/* - * Constant folding for compiler intrinsics, similar approach to operator - * folding, primarily: individual functions for each intrinsics to fold, - * and a generic selection function. - */ -static GMQCC_INLINE ast_expression *fold_intrin_isfinite(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, isfinite(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_isinf(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, isinf(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_isnan(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, isnan(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_isnormal(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, isnormal(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_signbit(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, signbit(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intirn_acosh(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, acoshf(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_asinh(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, asinhf(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_atanh(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, (float)atanh(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_exp(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, expf(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_exp2(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, exp2f(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_expm1(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, expm1f(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_mod(fold_t *fold, ast_value *lhs, ast_value *rhs) { - return fold_constgen_float(fold, fmodf(fold_immvalue_float(lhs), fold_immvalue_float(rhs)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_pow(fold_t *fold, ast_value *lhs, ast_value *rhs) { - return fold_constgen_float(fold, powf(fold_immvalue_float(lhs), fold_immvalue_float(rhs)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_fabs(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, fabsf(fold_immvalue_float(a)), false); -} - - -ast_expression *fold_intrin(fold_t *fold, const char *intrin, ast_expression **arg) { - ast_expression *ret = NULL; - ast_value *a = (ast_value*)arg[0]; - ast_value *b = (ast_value*)arg[1]; - - if (!strcmp(intrin, "isfinite")) ret = fold_intrin_isfinite(fold, a); - if (!strcmp(intrin, "isinf")) ret = fold_intrin_isinf(fold, a); - if (!strcmp(intrin, "isnan")) ret = fold_intrin_isnan(fold, a); - if (!strcmp(intrin, "isnormal")) ret = fold_intrin_isnormal(fold, a); - if (!strcmp(intrin, "signbit")) ret = fold_intrin_signbit(fold, a); - if (!strcmp(intrin, "acosh")) ret = fold_intirn_acosh(fold, a); - if (!strcmp(intrin, "asinh")) ret = fold_intrin_asinh(fold, a); - if (!strcmp(intrin, "atanh")) ret = fold_intrin_atanh(fold, a); - if (!strcmp(intrin, "exp")) ret = fold_intrin_exp(fold, a); - if (!strcmp(intrin, "exp2")) ret = fold_intrin_exp2(fold, a); - if (!strcmp(intrin, "expm1")) ret = fold_intrin_expm1(fold, a); - if (!strcmp(intrin, "mod")) ret = fold_intrin_mod(fold, a, b); - if (!strcmp(intrin, "pow")) ret = fold_intrin_pow(fold, a, b); - if (!strcmp(intrin, "fabs")) ret = fold_intrin_fabs(fold, a); - - if (ret) - ++opts_optimizationcount[OPTIM_CONST_FOLD]; - - return ret; -} - -/* - * These are all the actual constant folding methods that happen in between - * the AST/IR stage of the compiler , i.e eliminating branches for const - * expressions, which is the only supported thing so far. We undefine the - * testing macros here because an ir_value is differant than an ast_value. - */ -#undef expect -#undef isfloat -#undef isstring -#undef isvector -#undef fold_immvalue_float -#undef fold_immvalue_string -#undef fold_immvalue_vector -#undef fold_can_1 -#undef fold_can_2 - -#define isfloat(X) ((X)->vtype == TYPE_FLOAT) -/*#define isstring(X) ((X)->vtype == TYPE_STRING)*/ -/*#define isvector(X) ((X)->vtype == TYPE_VECTOR)*/ -#define fold_immvalue_float(X) ((X)->constval.vfloat) -#define fold_immvalue_vector(X) ((X)->constval.vvec) -/*#define fold_immvalue_string(X) ((X)->constval.vstring)*/ -#define fold_can_1(X) ((X)->hasvalue && (X)->cvq == CV_CONST) -/*#define fold_can_2(X,Y) (fold_can_1(X) && fold_can_1(Y))*/ - -static ast_expression *fold_superfluous(ast_expression *left, ast_expression *right, int op) { - ast_expression *swapped = NULL; /* using this as bool */ - ast_value *load; - - if (!ast_istype(right, ast_value) || !fold_can_1((load = (ast_value*)right))) { - swapped = left; - left = right; - right = swapped; - } - - if (!ast_istype(right, ast_value) || !fold_can_1((load = (ast_value*)right))) - return NULL; - - switch (op) { - case INSTR_DIV_F: - if (swapped) - return NULL; - case INSTR_MUL_F: - if (fold_immvalue_float(load) == 1.0f) { - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - ast_unref(right); - return left; - } - break; - - - case INSTR_SUB_F: - if (swapped) - return NULL; - case INSTR_ADD_F: - if (fold_immvalue_float(load) == 0.0f) { - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - ast_unref(right); - return left; - } - break; - - case INSTR_MUL_V: - if (vec3_cmp(fold_immvalue_vector(load), vec3_create(1, 1, 1))) { - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - ast_unref(right); - return left; - } - break; - - case INSTR_SUB_V: - if (swapped) - return NULL; - case INSTR_ADD_V: - if (vec3_cmp(fold_immvalue_vector(load), vec3_create(0, 0, 0))) { - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - ast_unref(right); - return left; - } - break; - } - - return NULL; -} - -ast_expression *fold_binary(lex_ctx_t ctx, int op, ast_expression *left, ast_expression *right) { - ast_expression *ret = fold_superfluous(left, right, op); - if (ret) - return ret; - return (ast_expression*)ast_binary_new(ctx, op, left, right); -} - -static GMQCC_INLINE int fold_cond(ir_value *condval, ast_function *func, ast_ifthen *branch) { - if (isfloat(condval) && fold_can_1(condval) && OPTS_OPTIMIZATION(OPTIM_CONST_FOLD_DCE)) { - ast_expression_codegen *cgen; - ir_block *elide; - ir_value *dummy; - bool istrue = (fold_immvalue_float(condval) != 0.0f && branch->on_true); - bool isfalse = (fold_immvalue_float(condval) == 0.0f && branch->on_false); - ast_expression *path = (istrue) ? branch->on_true : - (isfalse) ? branch->on_false : NULL; - if (!path) { - /* - * no path to take implies that the evaluation is if(0) and there - * is no else block. so eliminate all the code. - */ - ++opts_optimizationcount[OPTIM_CONST_FOLD_DCE]; - return true; - } - - if (!(elide = ir_function_create_block(ast_ctx(branch), func->ir_func, ast_function_label(func, ((istrue) ? "ontrue" : "onfalse"))))) - return false; - if (!(*(cgen = path->codegen))((ast_expression*)path, func, false, &dummy)) - return false; - if (!ir_block_create_jump(func->curblock, ast_ctx(branch), elide)) - return false; - /* - * now the branch has been eliminated and the correct block for the constant evaluation - * is expanded into the current block for the function. - */ - func->curblock = elide; - ++opts_optimizationcount[OPTIM_CONST_FOLD_DCE]; - return true; - } - return -1; /* nothing done */ -} - -int fold_cond_ternary(ir_value *condval, ast_function *func, ast_ternary *branch) { - return fold_cond(condval, func, (ast_ifthen*)branch); -} - -int fold_cond_ifthen(ir_value *condval, ast_function *func, ast_ifthen *branch) { - return fold_cond(condval, func, branch); -} diff --git a/fold.cpp b/fold.cpp new file mode 100644 index 0000000..6d56d8a --- /dev/null +++ b/fold.cpp @@ -0,0 +1,1653 @@ +#include +#include + +#include "fold.h" +#include "ast.h" +#include "ir.h" + +#include "parser.h" + +#define FOLD_STRING_UNTRANSLATE_HTSIZE 1024 +#define FOLD_STRING_DOTRANSLATE_HTSIZE 1024 + +/* The options to use for inexact and arithmetic exceptions */ +#define FOLD_ROUNDING SFLOAT_ROUND_NEAREST_EVEN +#define FOLD_TINYNESS SFLOAT_TBEFORE + +/* + * Comparing float values is an unsafe operation when the operands to the + * comparison are floating point values that are inexact. For instance 1/3 is an + * inexact value. The FPU is meant to raise exceptions when these sorts of things + * happen, including division by zero, underflows and overflows. The C standard + * library provides us with the header to gain access to the floating- + * point environment and lets us set the rounding mode and check for these exceptions. + * The problem is the standard C library allows an implementation to leave these + * stubbed out and does not require they be implemented. Furthermore, depending + * on implementations there is no control over the FPU. This is an IEE 754 + * conforming implementation in software to compensate. + */ +typedef uint32_t sfloat_t; + +union sfloat_cast_t { + qcfloat_t f; + sfloat_t s; +}; + +/* Exception flags */ +enum sfloat_exceptionflags_t { + SFLOAT_NOEXCEPT = 0, + SFLOAT_INVALID = 1, + SFLOAT_DIVBYZERO = 4, + SFLOAT_OVERFLOW = 8, + SFLOAT_UNDERFLOW = 16, + SFLOAT_INEXACT = 32 +}; + +/* Rounding modes */ +enum sfloat_roundingmode_t { + SFLOAT_ROUND_NEAREST_EVEN, + SFLOAT_ROUND_DOWN, + SFLOAT_ROUND_UP, + SFLOAT_ROUND_TO_ZERO +}; + +/* Underflow tininess-detection mode */ +enum sfloat_tdetect_t { + SFLOAT_TAFTER, + SFLOAT_TBEFORE +}; + +struct sfloat_state_t { + sfloat_roundingmode_t roundingmode; + sfloat_exceptionflags_t exceptionflags; + sfloat_tdetect_t tiny; +}; + +/* Counts the number of leading zero bits before the most-significand one bit. */ +#ifdef _MSC_VER +/* MSVC has an intrinsic for this */ + static GMQCC_INLINE uint32_t sfloat_clz(uint32_t x) { + int r = 0; + _BitScanForward(&r, x); + return r; + } +# define SFLOAT_CLZ(X, SUB) \ + (sfloat_clz((X)) - (SUB)) +#elif defined(__GNUC__) || defined(__CLANG__) +/* Clang and GCC have a builtin for this */ +# define SFLOAT_CLZ(X, SUB) \ + (__builtin_clz((X)) - (SUB)) +#else +/* Native fallback */ + static GMQCC_INLINE uint32_t sfloat_popcnt(uint32_t x) { + x -= ((x >> 1) & 0x55555555); + x = (((x >> 2) & 0x33333333) + (x & 0x33333333)); + x = (((x >> 4) + x) & 0x0F0F0F0F); + x += x >> 8; + x += x >> 16; + return x & 0x0000003F; + } + static GMQCC_INLINE uint32_t sfloat_clz(uint32_t x) { + x |= (x >> 1); + x |= (x >> 2); + x |= (x >> 4); + x |= (x >> 8); + x |= (x >> 16); + return 32 - sfloat_popcnt(x); + } +# define SFLOAT_CLZ(X, SUB) \ + (sfloat_clz((X) - (SUB))) +#endif + +/* The value of a NaN */ +#define SFLOAT_NAN 0xFFFFFFFF +/* Test if NaN */ +#define SFLOAT_ISNAN(A) \ + (0xFF000000 < (uint32_t)((A) << 1)) +/* Test if signaling NaN */ +#define SFLOAT_ISSNAN(A) \ + (((((A) >> 22) & 0x1FF) == 0x1FE) && ((A) & 0x003FFFFF)) +/* Raise exception */ +#define SFLOAT_RAISE(STATE, FLAGS) \ + ((STATE)->exceptionflags = (sfloat_exceptionflags_t)((STATE)->exceptionflags | (FLAGS))) +/* + * Shifts `A' right by the number of bits given in `COUNT'. If any non-zero bits + * are shifted off they are forced into the least significand bit of the result + * by setting it to one. As a result of this, the value of `COUNT' can be + * arbitrarily large; if `COUNT' is greater than 32, the result will be either + * zero or one, depending on whether `A' is a zero or non-zero. The result is + * stored into the value pointed by `Z'. + */ +#define SFLOAT_SHIFT(SIZE, A, COUNT, Z) \ + *(Z) = ((COUNT) == 0) \ + ? 1 \ + : (((COUNT) < (SIZE)) \ + ? ((A) >> (COUNT)) | (((A) << ((-(COUNT)) & ((SIZE) - 1))) != 0) \ + : ((A) != 0)) + +/* Extract fractional component */ +#define SFLOAT_EXTRACT_FRAC(X) \ + ((uint32_t)((X) & 0x007FFFFF)) +/* Extract exponent component */ +#define SFLOAT_EXTRACT_EXP(X) \ + ((int16_t)((X) >> 23) & 0xFF) +/* Extract sign bit */ +#define SFLOAT_EXTRACT_SIGN(X) \ + ((X) >> 31) +/* + * Normalizes the subnormal value represented by the denormalized significand + * `SA'. The normalized exponent and significand are stored at the locations + * pointed by `Z' and `SZ' respectively. + */ +#define SFLOAT_SUBNORMALIZE(SA, Z, SZ) \ + (void)(*(SZ) = (SA) << SFLOAT_CLZ((SA), 8), *(Z) = 1 - SFLOAT_CLZ((SA), 8)) +/* + * Packs the sign `SIGN', exponent `EXP' and significand `SIG' into the value + * giving the result. + * + * After the shifting into their proper positions, the fields are added together + * to form the result. This means any integer portion of `SIG' will be added + * to the exponent. Similarly, because a properly normalized significand will + * always have an integer portion equal to one, the exponent input `EXP' should + * be one less than the desired result exponent whenever the significant input + * `SIG' is a complete, normalized significand. + */ +#define SFLOAT_PACK(SIGN, EXP, SIG) \ + (sfloat_t)((((uint32_t)(SIGN)) << 31) + (((uint32_t)(EXP)) << 23) + (SIG)) + +/* + * Takes two values `a' and `b', one of which is a NaN, and returns the appropriate + * NaN result. If either `a' or `b' is a signaling NaN than an invalid exception is + * raised. + */ +static sfloat_t sfloat_propagate_nan(sfloat_state_t *state, sfloat_t a, sfloat_t b) { + bool isnan_a = SFLOAT_ISNAN(a); + bool issnan_a = SFLOAT_ISSNAN(a); + bool isnan_b = SFLOAT_ISNAN(b); + bool issnan_b = SFLOAT_ISSNAN(b); + + a |= 0x00400000; + b |= 0x00400000; + + if (issnan_a | issnan_b) + SFLOAT_RAISE(state, SFLOAT_INVALID); + if (isnan_a) + return (issnan_a & isnan_b) ? b : a; + return b; +} + +/* + * Takes an abstract value having sign `sign_z', exponent `exp_z', and significand + * `sig_z' and returns the appropriate value corresponding to the abstract input. + * + * The abstract value is simply rounded and packed into the format. If the abstract + * input cannot be represented exactly an inexact exception is raised. If the + * abstract input is too large, the overflow and inexact exceptions are both raised + * and an infinity or maximal finite value is returned. If the abstract value is + * too small, the value is rounded to a subnormal and the underflow and inexact + * exceptions are only raised if the value cannot be represented exactly with + * a subnormal. + * + * The input significand `sig_z' has it's binary point between bits 30 and 29, + * this is seven bits to the left of its usual location. The shifted significand + * must be normalized or smaller than this. If it's not normalized then the exponent + * `exp_z' must be zero; in that case, the result returned is a subnormal number + * which must not require rounding. In the more usual case where the significand + * is normalized, the exponent must be one less than the *true* exponent. + * + * The handling of underflow and overflow is otherwise in alignment with IEC/IEEE. + */ +static sfloat_t SFLOAT_PACK_round(sfloat_state_t *state, bool sign_z, int16_t exp_z, uint32_t sig_z) { + sfloat_roundingmode_t mode = state->roundingmode; + bool even = !!(mode == SFLOAT_ROUND_NEAREST_EVEN); + unsigned char increment = 0x40; + unsigned char bits = sig_z & 0x7F; + + if (!even) { + if (mode == SFLOAT_ROUND_TO_ZERO) + increment = 0; + else { + increment = 0x7F; + if (sign_z) { + if (mode == SFLOAT_ROUND_UP) + increment = 0; + } else { + if (mode == SFLOAT_ROUND_DOWN) + increment = 0; + } + } + } + + if (0xFD <= (uint16_t)exp_z) { + if ((0xFD < exp_z) || ((exp_z == 0xFD) && ((int32_t)(sig_z + increment) < 0))) { + SFLOAT_RAISE(state, SFLOAT_OVERFLOW | SFLOAT_INEXACT); + return SFLOAT_PACK(sign_z, 0xFF, 0) - (increment == 0); + } + if (exp_z < 0) { + /* Check for underflow */ + bool tiny = (state->tiny == SFLOAT_TBEFORE) || (exp_z < -1) || (sig_z + increment < 0x80000000); + SFLOAT_SHIFT(32, sig_z, -exp_z, &sig_z); + exp_z = 0; + bits = sig_z & 0x7F; + if (tiny && bits) + SFLOAT_RAISE(state, SFLOAT_UNDERFLOW); + } + } + if (bits) + SFLOAT_RAISE(state, SFLOAT_INEXACT); + sig_z = (sig_z + increment) >> 7; + sig_z &= ~(((bits ^ 0x40) == 0) & even); + if (sig_z == 0) + exp_z = 0; + return SFLOAT_PACK(sign_z, exp_z, sig_z); +} + +/* + * Takes an abstract value having sign `sign_z', exponent `exp_z' and significand + * `sig_z' and returns the appropriate value corresponding to the abstract input. + * This function is exactly like `PACK_round' except the significand does not have + * to be normalized. + * + * Bit 31 of the significand must be zero and the exponent must be one less than + * the *true* exponent. + */ +static sfloat_t SFLOAT_PACK_normal(sfloat_state_t *state, bool sign_z, int16_t exp_z, uint32_t sig_z) { + unsigned char c = SFLOAT_CLZ(sig_z, 1); + return SFLOAT_PACK_round(state, sign_z, exp_z - c, sig_z << c); +} + +/* + * Returns the result of adding the absolute values of `a' and `b'. The sign + * `sign_z' is ignored if the result is a NaN. + */ +static sfloat_t sfloat_add_impl(sfloat_state_t *state, sfloat_t a, sfloat_t b, bool sign_z) { + int16_t exp_a = SFLOAT_EXTRACT_EXP(a); + int16_t exp_b = SFLOAT_EXTRACT_EXP(b); + int16_t exp_z = 0; + int16_t exp_d = exp_a - exp_b; + uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a) << 6; + uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b) << 6; + uint32_t sig_z = 0; + + if (0 < exp_d) { + if (exp_a == 0xFF) + return sig_a ? sfloat_propagate_nan(state, a, b) : a; + if (exp_b == 0) + --exp_d; + else + sig_b |= 0x20000000; + SFLOAT_SHIFT(32, sig_b, exp_d, &sig_b); + exp_z = exp_a; + } else if (exp_d < 0) { + if (exp_b == 0xFF) + return sig_b ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z, 0xFF, 0); + if (exp_a == 0) + ++exp_d; + else + sig_a |= 0x20000000; + SFLOAT_SHIFT(32, sig_a, -exp_d, &sig_a); + exp_z = exp_b; + } else { + if (exp_a == 0xFF) + return (sig_a | sig_b) ? sfloat_propagate_nan(state, a, b) : a; + if (exp_a == 0) + return SFLOAT_PACK(sign_z, 0, (sig_a + sig_b) >> 6); + sig_z = 0x40000000 + sig_a + sig_b; + exp_z = exp_a; + goto end; + } + sig_a |= 0x20000000; + sig_z = (sig_a + sig_b) << 1; + --exp_z; + if ((int32_t)sig_z < 0) { + sig_z = sig_a + sig_b; + ++exp_z; + } +end: + return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z); +} + +/* + * Returns the result of subtracting the absolute values of `a' and `b'. If the + * sign `sign_z' is one, the difference is negated before being returned. The + * sign is ignored if the result is a NaN. + */ +static sfloat_t sfloat_sub_impl(sfloat_state_t *state, sfloat_t a, sfloat_t b, bool sign_z) { + int16_t exp_a = SFLOAT_EXTRACT_EXP(a); + int16_t exp_b = SFLOAT_EXTRACT_EXP(b); + int16_t exp_z = 0; + int16_t exp_d = exp_a - exp_b; + uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a) << 7; + uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b) << 7; + uint32_t sig_z = 0; + + if (0 < exp_d) goto exp_greater_a; + if (exp_d < 0) goto exp_greater_b; + + if (exp_a == 0xFF) { + if (sig_a | sig_b) + return sfloat_propagate_nan(state, a, b); + SFLOAT_RAISE(state, SFLOAT_INVALID); + return SFLOAT_NAN; + } + + if (exp_a == 0) + exp_a = exp_b = 1; + + if (sig_b < sig_a) goto greater_a; + if (sig_a < sig_b) goto greater_b; + + return SFLOAT_PACK(state->roundingmode == SFLOAT_ROUND_DOWN, 0, 0); + +exp_greater_b: + if (exp_b == 0xFF) + return (sig_b) ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z ^ 1, 0xFF, 0); + if (exp_a == 0) + ++exp_d; + else + sig_a |= 0x40000000; + SFLOAT_SHIFT(32, sig_a, -exp_d, &sig_a); + sig_b |= 0x40000000; +greater_b: + sig_z = sig_b - sig_a; + exp_z = exp_b; + sign_z ^= 1; + goto end; + +exp_greater_a: + if (exp_a == 0xFF) + return (sig_a) ? sfloat_propagate_nan(state, a, b) : a; + if (exp_b == 0) + --exp_d; + else + sig_b |= 0x40000000; + SFLOAT_SHIFT(32, sig_b, exp_d, &sig_b); + sig_a |= 0x40000000; +greater_a: + sig_z = sig_a - sig_b; + exp_z = exp_a; + +end: + --exp_z; + return SFLOAT_PACK_normal(state, sign_z, exp_z, sig_z); +} + +static GMQCC_INLINE sfloat_t sfloat_add(sfloat_state_t *state, sfloat_t a, sfloat_t b) { + bool sign_a = SFLOAT_EXTRACT_SIGN(a); + bool sign_b = SFLOAT_EXTRACT_SIGN(b); + return (sign_a == sign_b) ? sfloat_add_impl(state, a, b, sign_a) + : sfloat_sub_impl(state, a, b, sign_a); +} + +static GMQCC_INLINE sfloat_t sfloat_sub(sfloat_state_t *state, sfloat_t a, sfloat_t b) { + bool sign_a = SFLOAT_EXTRACT_SIGN(a); + bool sign_b = SFLOAT_EXTRACT_SIGN(b); + return (sign_a == sign_b) ? sfloat_sub_impl(state, a, b, sign_a) + : sfloat_add_impl(state, a, b, sign_a); +} + +static sfloat_t sfloat_mul(sfloat_state_t *state, sfloat_t a, sfloat_t b) { + int16_t exp_a = SFLOAT_EXTRACT_EXP(a); + int16_t exp_b = SFLOAT_EXTRACT_EXP(b); + int16_t exp_z = 0; + uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a); + uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b); + uint32_t sig_z = 0; + uint64_t sig_z64 = 0; + bool sign_a = SFLOAT_EXTRACT_SIGN(a); + bool sign_b = SFLOAT_EXTRACT_SIGN(b); + bool sign_z = sign_a ^ sign_b; + + if (exp_a == 0xFF) { + if (sig_a || ((exp_b == 0xFF) && sig_b)) + return sfloat_propagate_nan(state, a, b); + if ((exp_b | sig_b) == 0) { + SFLOAT_RAISE(state, SFLOAT_INVALID); + return SFLOAT_NAN; + } + return SFLOAT_PACK(sign_z, 0xFF, 0); + } + if (exp_b == 0xFF) { + if (sig_b) + return sfloat_propagate_nan(state, a, b); + if ((exp_a | sig_a) == 0) { + SFLOAT_RAISE(state, SFLOAT_INVALID); + return SFLOAT_NAN; + } + return SFLOAT_PACK(sign_z, 0xFF, 0); + } + if (exp_a == 0) { + if (sig_a == 0) + return SFLOAT_PACK(sign_z, 0, 0); + SFLOAT_SUBNORMALIZE(sig_a, &exp_a, &sig_a); + } + if (exp_b == 0) { + if (sig_b == 0) + return SFLOAT_PACK(sign_z, 0, 0); + SFLOAT_SUBNORMALIZE(sig_b, &exp_b, &sig_b); + } + exp_z = exp_a + exp_b - 0x7F; + sig_a = (sig_a | 0x00800000) << 7; + sig_b = (sig_b | 0x00800000) << 8; + SFLOAT_SHIFT(64, ((uint64_t)sig_a) * sig_b, 32, &sig_z64); + sig_z = sig_z64; + if (0 <= (int32_t)(sig_z << 1)) { + sig_z <<= 1; + --exp_z; + } + return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z); +} + +static sfloat_t sfloat_div(sfloat_state_t *state, sfloat_t a, sfloat_t b) { + int16_t exp_a = SFLOAT_EXTRACT_EXP(a); + int16_t exp_b = SFLOAT_EXTRACT_EXP(b); + int16_t exp_z = 0; + uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a); + uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b); + uint32_t sig_z = 0; + bool sign_a = SFLOAT_EXTRACT_SIGN(a); + bool sign_b = SFLOAT_EXTRACT_SIGN(b); + bool sign_z = sign_a ^ sign_b; + + if (exp_a == 0xFF) { + if (sig_a) + return sfloat_propagate_nan(state, a, b); + if (exp_b == 0xFF) { + if (sig_b) + return sfloat_propagate_nan(state, a, b); + SFLOAT_RAISE(state, SFLOAT_INVALID); + return SFLOAT_NAN; + } + return SFLOAT_PACK(sign_z, 0xFF, 0); + } + if (exp_b == 0xFF) + return (sig_b) ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z, 0, 0); + if (exp_b == 0) { + if (sig_b == 0) { + if ((exp_a | sig_a) == 0) { + SFLOAT_RAISE(state, SFLOAT_INVALID); + return SFLOAT_NAN; + } + SFLOAT_RAISE(state, SFLOAT_DIVBYZERO); + return SFLOAT_PACK(sign_z, 0xFF, 0); + } + SFLOAT_SUBNORMALIZE(sig_b, &exp_b, &sig_b); + } + if (exp_a == 0) { + if (sig_a == 0) + return SFLOAT_PACK(sign_z, 0, 0); + SFLOAT_SUBNORMALIZE(sig_a, &exp_a, &sig_a); + } + exp_z = exp_a - exp_b + 0x7D; + sig_a = (sig_a | 0x00800000) << 7; + sig_b = (sig_b | 0x00800000) << 8; + if (sig_b <= (sig_a + sig_a)) { + sig_a >>= 1; + ++exp_z; + } + sig_z = (((uint64_t)sig_a) << 32) / sig_b; + if ((sig_z & 0x3F) == 0) + sig_z |= ((uint64_t)sig_b * sig_z != ((uint64_t)sig_a) << 32); + return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z); +} + +static sfloat_t sfloat_neg(sfloat_state_t *state, sfloat_t a) { + sfloat_cast_t neg; + neg.f = -1; + return sfloat_mul(state, a, neg.s); +} + +static GMQCC_INLINE void sfloat_check(lex_ctx_t ctx, sfloat_state_t *state, const char *vec) { + /* Exception comes from vector component */ + if (vec) { + if (state->exceptionflags & SFLOAT_DIVBYZERO) + compile_error(ctx, "division by zero in `%s' component", vec); + if (state->exceptionflags & SFLOAT_INVALID) + compile_error(ctx, "undefined (inf) in `%s' component", vec); + if (state->exceptionflags & SFLOAT_OVERFLOW) + compile_error(ctx, "arithmetic overflow in `%s' component", vec); + if (state->exceptionflags & SFLOAT_UNDERFLOW) + compile_error(ctx, "arithmetic underflow in `%s' component", vec); + return; + } + if (state->exceptionflags & SFLOAT_DIVBYZERO) + compile_error(ctx, "division by zero"); + if (state->exceptionflags & SFLOAT_INVALID) + compile_error(ctx, "undefined (inf)"); + if (state->exceptionflags & SFLOAT_OVERFLOW) + compile_error(ctx, "arithmetic overflow"); + if (state->exceptionflags & SFLOAT_UNDERFLOW) + compile_error(ctx, "arithmetic underflow"); +} + +static GMQCC_INLINE void sfloat_init(sfloat_state_t *state) { + state->exceptionflags = SFLOAT_NOEXCEPT; + state->roundingmode = FOLD_ROUNDING; + state->tiny = FOLD_TINYNESS; +} + +/* + * There is two stages to constant folding in GMQCC: there is the parse + * stage constant folding, where, with the help of the AST, operator + * usages can be constant folded. Then there is the constant folding + * in the IR for things like eliding if statements, can occur. + * + * This file is thus, split into two parts. + */ + +#define isfloat(X) (((X))->m_vtype == TYPE_FLOAT) +#define isvector(X) (((X))->m_vtype == TYPE_VECTOR) +#define isstring(X) (((X))->m_vtype == TYPE_STRING) +#define isarray(X) (((X))->m_vtype == TYPE_ARRAY) +#define isfloats(X,Y) (isfloat (X) && isfloat (Y)) + +/* + * Implementation of basic vector math for vec3_t, for trivial constant + * folding. + * + * TODO: gcc/clang hinting for autovectorization + */ +enum vec3_comp_t { + VEC_COMP_X = 1 << 0, + VEC_COMP_Y = 1 << 1, + VEC_COMP_Z = 1 << 2 +}; + +struct vec3_soft_t { + sfloat_cast_t x; + sfloat_cast_t y; + sfloat_cast_t z; +}; + +struct vec3_soft_state_t { + vec3_comp_t faults; + sfloat_state_t state[3]; +}; + +static GMQCC_INLINE vec3_soft_t vec3_soft_convert(vec3_t vec) { + vec3_soft_t soft; + soft.x.f = vec.x; + soft.y.f = vec.y; + soft.z.f = vec.z; + return soft; +} + +static GMQCC_INLINE bool vec3_soft_exception(vec3_soft_state_t *vstate, size_t index) { + sfloat_exceptionflags_t flags = vstate->state[index].exceptionflags; + if (flags & SFLOAT_DIVBYZERO) return true; + if (flags & SFLOAT_INVALID) return true; + if (flags & SFLOAT_OVERFLOW) return true; + if (flags & SFLOAT_UNDERFLOW) return true; + return false; +} + +static GMQCC_INLINE void vec3_soft_eval(vec3_soft_state_t *state, + sfloat_t (*callback)(sfloat_state_t *, sfloat_t, sfloat_t), + vec3_t a, + vec3_t b) +{ + vec3_soft_t sa = vec3_soft_convert(a); + vec3_soft_t sb = vec3_soft_convert(b); + callback(&state->state[0], sa.x.s, sb.x.s); + if (vec3_soft_exception(state, 0)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_X); + callback(&state->state[1], sa.y.s, sb.y.s); + if (vec3_soft_exception(state, 1)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_Y); + callback(&state->state[2], sa.z.s, sb.z.s); + if (vec3_soft_exception(state, 2)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_Z); +} + +static GMQCC_INLINE void vec3_check_except(vec3_t a, + vec3_t b, + lex_ctx_t ctx, + sfloat_t (*callback)(sfloat_state_t *, sfloat_t, sfloat_t)) +{ + vec3_soft_state_t state; + + if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) + return; + + sfloat_init(&state.state[0]); + sfloat_init(&state.state[1]); + sfloat_init(&state.state[2]); + + vec3_soft_eval(&state, callback, a, b); + if (state.faults & VEC_COMP_X) sfloat_check(ctx, &state.state[0], "x"); + if (state.faults & VEC_COMP_Y) sfloat_check(ctx, &state.state[1], "y"); + if (state.faults & VEC_COMP_Z) sfloat_check(ctx, &state.state[2], "z"); +} + +static GMQCC_INLINE vec3_t vec3_add(lex_ctx_t ctx, vec3_t a, vec3_t b) { + vec3_t out; + vec3_check_except(a, b, ctx, &sfloat_add); + out.x = a.x + b.x; + out.y = a.y + b.y; + out.z = a.z + b.z; + return out; +} + +static GMQCC_INLINE vec3_t vec3_sub(lex_ctx_t ctx, vec3_t a, vec3_t b) { + vec3_t out; + vec3_check_except(a, b, ctx, &sfloat_sub); + out.x = a.x - b.x; + out.y = a.y - b.y; + out.z = a.z - b.z; + return out; +} + +static GMQCC_INLINE vec3_t vec3_neg(lex_ctx_t ctx, vec3_t a) { + vec3_t out; + sfloat_cast_t v[3]; + sfloat_state_t s[3]; + + if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) + goto end; + + v[0].f = a.x; + v[1].f = a.y; + v[2].f = a.z; + + sfloat_init(&s[0]); + sfloat_init(&s[1]); + sfloat_init(&s[2]); + + sfloat_neg(&s[0], v[0].s); + sfloat_neg(&s[1], v[1].s); + sfloat_neg(&s[2], v[2].s); + + sfloat_check(ctx, &s[0], nullptr); + sfloat_check(ctx, &s[1], nullptr); + sfloat_check(ctx, &s[2], nullptr); + +end: + out.x = -a.x; + out.y = -a.y; + out.z = -a.z; + return out; +} + +static GMQCC_INLINE vec3_t vec3_or(vec3_t a, vec3_t b) { + vec3_t out; + out.x = (qcfloat_t)(((qcint_t)a.x) | ((qcint_t)b.x)); + out.y = (qcfloat_t)(((qcint_t)a.y) | ((qcint_t)b.y)); + out.z = (qcfloat_t)(((qcint_t)a.z) | ((qcint_t)b.z)); + return out; +} + +static GMQCC_INLINE vec3_t vec3_orvf(vec3_t a, qcfloat_t b) { + vec3_t out; + out.x = (qcfloat_t)(((qcint_t)a.x) | ((qcint_t)b)); + out.y = (qcfloat_t)(((qcint_t)a.y) | ((qcint_t)b)); + out.z = (qcfloat_t)(((qcint_t)a.z) | ((qcint_t)b)); + return out; +} + +static GMQCC_INLINE vec3_t vec3_and(vec3_t a, vec3_t b) { + vec3_t out; + out.x = (qcfloat_t)(((qcint_t)a.x) & ((qcint_t)b.x)); + out.y = (qcfloat_t)(((qcint_t)a.y) & ((qcint_t)b.y)); + out.z = (qcfloat_t)(((qcint_t)a.z) & ((qcint_t)b.z)); + return out; +} + +static GMQCC_INLINE vec3_t vec3_andvf(vec3_t a, qcfloat_t b) { + vec3_t out; + out.x = (qcfloat_t)(((qcint_t)a.x) & ((qcint_t)b)); + out.y = (qcfloat_t)(((qcint_t)a.y) & ((qcint_t)b)); + out.z = (qcfloat_t)(((qcint_t)a.z) & ((qcint_t)b)); + return out; +} + +static GMQCC_INLINE vec3_t vec3_xor(vec3_t a, vec3_t b) { + vec3_t out; + out.x = (qcfloat_t)(((qcint_t)a.x) ^ ((qcint_t)b.x)); + out.y = (qcfloat_t)(((qcint_t)a.y) ^ ((qcint_t)b.y)); + out.z = (qcfloat_t)(((qcint_t)a.z) ^ ((qcint_t)b.z)); + return out; +} + +static GMQCC_INLINE vec3_t vec3_xorvf(vec3_t a, qcfloat_t b) { + vec3_t out; + out.x = (qcfloat_t)(((qcint_t)a.x) ^ ((qcint_t)b)); + out.y = (qcfloat_t)(((qcint_t)a.y) ^ ((qcint_t)b)); + out.z = (qcfloat_t)(((qcint_t)a.z) ^ ((qcint_t)b)); + return out; +} + +static GMQCC_INLINE vec3_t vec3_not(vec3_t a) { + vec3_t out; + out.x = -1-a.x; + out.y = -1-a.y; + out.z = -1-a.z; + return out; +} + +static GMQCC_INLINE qcfloat_t vec3_mulvv(lex_ctx_t ctx, vec3_t a, vec3_t b) { + vec3_soft_t sa; + vec3_soft_t sb; + sfloat_state_t s[5]; + sfloat_t r[5]; + + if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) + goto end; + + sa = vec3_soft_convert(a); + sb = vec3_soft_convert(b); + + sfloat_init(&s[0]); + sfloat_init(&s[1]); + sfloat_init(&s[2]); + sfloat_init(&s[3]); + sfloat_init(&s[4]); + + r[0] = sfloat_mul(&s[0], sa.x.s, sb.x.s); + r[1] = sfloat_mul(&s[1], sa.y.s, sb.y.s); + r[2] = sfloat_mul(&s[2], sa.z.s, sb.z.s); + r[3] = sfloat_add(&s[3], r[0], r[1]); + r[4] = sfloat_add(&s[4], r[3], r[2]); + + sfloat_check(ctx, &s[0], nullptr); + sfloat_check(ctx, &s[1], nullptr); + sfloat_check(ctx, &s[2], nullptr); + sfloat_check(ctx, &s[3], nullptr); + sfloat_check(ctx, &s[4], nullptr); + +end: + return (a.x * b.x + a.y * b.y + a.z * b.z); +} + +static GMQCC_INLINE vec3_t vec3_mulvf(lex_ctx_t ctx, vec3_t a, qcfloat_t b) { + vec3_t out; + vec3_soft_t sa; + sfloat_cast_t sb; + sfloat_state_t s[3]; + + if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) + goto end; + + sa = vec3_soft_convert(a); + sb.f = b; + sfloat_init(&s[0]); + sfloat_init(&s[1]); + sfloat_init(&s[2]); + + sfloat_mul(&s[0], sa.x.s, sb.s); + sfloat_mul(&s[1], sa.y.s, sb.s); + sfloat_mul(&s[2], sa.z.s, sb.s); + + sfloat_check(ctx, &s[0], "x"); + sfloat_check(ctx, &s[1], "y"); + sfloat_check(ctx, &s[2], "z"); + +end: + out.x = a.x * b; + out.y = a.y * b; + out.z = a.z * b; + return out; +} + +static GMQCC_INLINE bool vec3_cmp(vec3_t a, vec3_t b) { + return a.x == b.x && + a.y == b.y && + a.z == b.z; +} + +static GMQCC_INLINE vec3_t vec3_create(float x, float y, float z) { + vec3_t out; + out.x = x; + out.y = y; + out.z = z; + return out; +} + +static GMQCC_INLINE qcfloat_t vec3_notf(vec3_t a) { + return (!a.x && !a.y && !a.z); +} + +static GMQCC_INLINE bool vec3_pbool(vec3_t a) { + return (a.x || a.y || a.z); +} + +static GMQCC_INLINE vec3_t vec3_cross(lex_ctx_t ctx, vec3_t a, vec3_t b) { + vec3_t out; + vec3_soft_t sa; + vec3_soft_t sb; + sfloat_t r[9]; + sfloat_state_t s[9]; + + if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) + goto end; + + sa = vec3_soft_convert(a); + sb = vec3_soft_convert(b); + + sfloat_init(&s[0]); + sfloat_init(&s[1]); + sfloat_init(&s[2]); + sfloat_init(&s[3]); + sfloat_init(&s[4]); + sfloat_init(&s[5]); + sfloat_init(&s[6]); + sfloat_init(&s[7]); + sfloat_init(&s[8]); + + r[0] = sfloat_mul(&s[0], sa.y.s, sb.z.s); + r[1] = sfloat_mul(&s[1], sa.z.s, sb.y.s); + r[2] = sfloat_mul(&s[2], sa.z.s, sb.x.s); + r[3] = sfloat_mul(&s[3], sa.x.s, sb.z.s); + r[4] = sfloat_mul(&s[4], sa.x.s, sb.y.s); + r[5] = sfloat_mul(&s[5], sa.y.s, sb.x.s); + r[6] = sfloat_sub(&s[6], r[0], r[1]); + r[7] = sfloat_sub(&s[7], r[2], r[3]); + r[8] = sfloat_sub(&s[8], r[4], r[5]); + + sfloat_check(ctx, &s[0], nullptr); + sfloat_check(ctx, &s[1], nullptr); + sfloat_check(ctx, &s[2], nullptr); + sfloat_check(ctx, &s[3], nullptr); + sfloat_check(ctx, &s[4], nullptr); + sfloat_check(ctx, &s[5], nullptr); + sfloat_check(ctx, &s[6], "x"); + sfloat_check(ctx, &s[7], "y"); + sfloat_check(ctx, &s[8], "z"); + +end: + out.x = a.y * b.z - a.z * b.y; + out.y = a.z * b.x - a.x * b.z; + out.z = a.x * b.y - a.y * b.x; + return out; +} + +qcfloat_t fold::immvalue_float(ast_value *value) { + return value->m_constval.vfloat; +} + +vec3_t fold::immvalue_vector(ast_value *value) { + return value->m_constval.vvec; +} + +const char *fold::immvalue_string(ast_value *value) { + return value->m_constval.vstring; +} + +lex_ctx_t fold::ctx() { + lex_ctx_t ctx; + if (m_parser->lex) + return parser_ctx(m_parser); + memset(&ctx, 0, sizeof(ctx)); + return ctx; +} + +bool fold::immediate_true(ast_value *v) { + switch (v->m_vtype) { + case TYPE_FLOAT: + return !!v->m_constval.vfloat; + case TYPE_INTEGER: + return !!v->m_constval.vint; + case TYPE_VECTOR: + if (OPTS_FLAG(CORRECT_LOGIC)) + return vec3_pbool(v->m_constval.vvec); + return !!(v->m_constval.vvec.x); + case TYPE_STRING: + if (!v->m_constval.vstring) + return false; + if (OPTS_FLAG(TRUE_EMPTY_STRINGS)) + return true; + return !!v->m_constval.vstring[0]; + default: + compile_error(ctx(), "internal error: fold_immediate_true on invalid type"); + break; + } + return !!v->m_constval.vfunc; +} + +/* Handy macros to determine if an ast_value can be constant folded. */ +#define fold_can_1(X) \ + (ast_istype(((X)), ast_value) && (X)->m_hasvalue && ((X)->m_cvq == CV_CONST) && \ + ((X))->m_vtype != TYPE_FUNCTION) + +#define fold_can_2(X, Y) (fold_can_1(X) && fold_can_1(Y)) + +fold::fold() + : m_parser(nullptr) +{ +} + +fold::fold(parser_t *parser) + : m_parser(parser) +{ + m_imm_string_untranslate = util_htnew(FOLD_STRING_UNTRANSLATE_HTSIZE); + m_imm_string_dotranslate = util_htnew(FOLD_STRING_DOTRANSLATE_HTSIZE); + + constgen_float(0.0f, false); + constgen_float(1.0f, false); + constgen_float(-1.0f, false); + constgen_float(2.0f, false); + + constgen_vector(vec3_create(0.0f, 0.0f, 0.0f)); + constgen_vector(vec3_create(-1.0f, -1.0f, -1.0f)); +} + +bool fold::generate(ir_builder *ir) { + // generate globals for immediate folded values + ast_value *cur; + for (auto &it : m_imm_float) + if (!(cur = it)->generateGlobal(ir, false)) goto err; + for (auto &it : m_imm_vector) + if (!(cur = it)->generateGlobal(ir, false)) goto err; + for (auto &it : m_imm_string) + if (!(cur = it)->generateGlobal(ir, false)) goto err; + return true; +err: + con_out("failed to generate global %s\n", cur->m_name.c_str()); + delete ir; + return false; +} + +fold::~fold() { +// TODO: parser lifetime so this is called when it should be +#if 0 + for (auto &it : m_imm_float) ast_delete(it); + for (auto &it : m_imm_vector) ast_delete(it); + for (auto &it : m_imm_string) ast_delete(it); + + util_htdel(m_imm_string_untranslate); + util_htdel(m_imm_string_dotranslate); +#endif +} + +ast_expression *fold::constgen_float(qcfloat_t value, bool inexact) { + for (auto &it : m_imm_float) + if (!memcmp(&it->m_constval.vfloat, &value, sizeof(qcfloat_t))) + return it; + + ast_value *out = new ast_value(ctx(), "#IMMEDIATE", TYPE_FLOAT); + out->m_cvq = CV_CONST; + out->m_hasvalue = true; + out->m_inexact = inexact; + out->m_constval.vfloat = value; + + m_imm_float.push_back(out); + + return out; +} + +ast_expression *fold::constgen_vector(vec3_t value) { + for (auto &it : m_imm_vector) + if (vec3_cmp(it->m_constval.vvec, value)) + return it; + + ast_value *out = new ast_value(ctx(), "#IMMEDIATE", TYPE_VECTOR); + out->m_cvq = CV_CONST; + out->m_hasvalue = true; + out->m_constval.vvec = value; + + m_imm_vector.push_back(out); + + return out; +} + +ast_expression *fold::constgen_string(const char *str, bool translate) { + hash_table_t *table = translate ? m_imm_string_untranslate : m_imm_string_dotranslate; + ast_value *out = nullptr; + size_t hash = util_hthash(table, str); + + if ((out = (ast_value*)util_htgeth(table, str, hash))) + return out; + + if (translate) { + char name[32]; + util_snprintf(name, sizeof(name), "dotranslate_%zu", m_parser->translated++); + out = new ast_value(ctx(), name, TYPE_STRING); + out->m_flags |= AST_FLAG_INCLUDE_DEF; /* def needs to be included for translatables */ + } else { + out = new ast_value(ctx(), "#IMMEDIATE", TYPE_STRING); + } + + out->m_cvq = CV_CONST; + out->m_hasvalue = true; + out->m_isimm = true; + out->m_constval.vstring = parser_strdup(str); + + m_imm_string.push_back(out); + util_htseth(table, str, hash, out); + + return out; +} + +ast_expression *fold::constgen_string(const std::string &str, bool translate) { + return constgen_string(str.c_str(), translate); +} + +typedef union { + void (*callback)(void); + sfloat_t (*binary)(sfloat_state_t *, sfloat_t, sfloat_t); + sfloat_t (*unary)(sfloat_state_t *, sfloat_t); +} float_check_callback_t; + +bool fold::check_except_float_impl(void (*callback)(void), ast_value *a, ast_value *b) { + float_check_callback_t call; + sfloat_state_t s; + sfloat_cast_t ca; + + if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS) && !OPTS_WARN(WARN_INEXACT_COMPARES)) + return false; + + call.callback = callback; + sfloat_init(&s); + ca.f = immvalue_float(a); + if (b) { + sfloat_cast_t cb; + cb.f = immvalue_float(b); + call.binary(&s, ca.s, cb.s); + } else { + call.unary(&s, ca.s); + } + + if (s.exceptionflags == 0) + return false; + + if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) + goto inexact_possible; + + sfloat_check(ctx(), &s, nullptr); + +inexact_possible: + return s.exceptionflags & SFLOAT_INEXACT; +} + +#define check_except_float(CALLBACK, A, B) \ + check_except_float_impl(((void (*)(void))(CALLBACK)), (A), (B)) + +bool fold::check_inexact_float(ast_value *a, ast_value *b) { + if (!OPTS_WARN(WARN_INEXACT_COMPARES)) + return false; + if (!a->m_inexact && !b->m_inexact) + return false; + return compile_warning(ctx(), WARN_INEXACT_COMPARES, "inexact value in comparison"); +} + +ast_expression *fold::op_mul_vec(vec3_t vec, ast_value *sel, const char *set) { + qcfloat_t x = (&vec.x)[set[0]-'x']; + qcfloat_t y = (&vec.x)[set[1]-'x']; + qcfloat_t z = (&vec.x)[set[2]-'x']; + if (!y && !z) { + ast_expression *out; + ++opts_optimizationcount[OPTIM_VECTOR_COMPONENTS]; + out = ast_member::make(ctx(), sel, set[0]-'x', ""); + out->m_keep_node = false; + ((ast_member*)out)->m_rvalue = true; + if (x != -1.0f) + return new ast_binary(ctx(), INSTR_MUL_F, constgen_float(x, false), out); + } + return nullptr; +} + + +ast_expression *fold::op_neg(ast_value *a) { + if (isfloat(a)) { + if (fold_can_1(a)) { + /* Negation can produce inexact as well */ + bool inexact = check_except_float(&sfloat_neg, a, nullptr); + return constgen_float(-immvalue_float(a), inexact); + } + } else if (isvector(a)) { + if (fold_can_1(a)) + return constgen_vector(vec3_neg(ctx(), immvalue_vector(a))); + } + return nullptr; +} + +ast_expression *fold::op_not(ast_value *a) { + if (isfloat(a)) { + if (fold_can_1(a)) + return constgen_float(!immvalue_float(a), false); + } else if (isvector(a)) { + if (fold_can_1(a)) + return constgen_float(vec3_notf(immvalue_vector(a)), false); + } else if (isstring(a)) { + if (fold_can_1(a)) { + if (OPTS_FLAG(TRUE_EMPTY_STRINGS)) + return constgen_float(!immvalue_string(a), false); + else + return constgen_float(!immvalue_string(a) || !*immvalue_string(a), false); + } + } + return nullptr; +} + +ast_expression *fold::op_add(ast_value *a, ast_value *b) { + if (isfloat(a)) { + if (fold_can_2(a, b)) { + bool inexact = check_except_float(&sfloat_add, a, b); + return constgen_float(immvalue_float(a) + immvalue_float(b), inexact); + } + } else if (isvector(a)) { + if (fold_can_2(a, b)) + return constgen_vector(vec3_add(ctx(), + immvalue_vector(a), + immvalue_vector(b))); + } + return nullptr; +} + +ast_expression *fold::op_sub(ast_value *a, ast_value *b) { + if (isfloat(a)) { + if (fold_can_2(a, b)) { + bool inexact = check_except_float(&sfloat_sub, a, b); + return constgen_float(immvalue_float(a) - immvalue_float(b), inexact); + } + } else if (isvector(a)) { + if (fold_can_2(a, b)) + return constgen_vector(vec3_sub(ctx(), + immvalue_vector(a), + immvalue_vector(b))); + } + return nullptr; +} + +ast_expression *fold::op_mul(ast_value *a, ast_value *b) { + if (isfloat(a)) { + if (isvector(b)) { + if (fold_can_2(a, b)) + return constgen_vector(vec3_mulvf(ctx(), immvalue_vector(b), immvalue_float(a))); + } else { + if (fold_can_2(a, b)) { + bool inexact = check_except_float(&sfloat_mul, a, b); + return constgen_float(immvalue_float(a) * immvalue_float(b), inexact); + } + } + } else if (isvector(a)) { + if (isfloat(b)) { + if (fold_can_2(a, b)) + return constgen_vector(vec3_mulvf(ctx(), immvalue_vector(a), immvalue_float(b))); + } else { + if (fold_can_2(a, b)) { + return constgen_float(vec3_mulvv(ctx(), immvalue_vector(a), immvalue_vector(b)), false); + } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(a)) { + ast_expression *out; + if ((out = op_mul_vec(immvalue_vector(a), b, "xyz"))) return out; + if ((out = op_mul_vec(immvalue_vector(a), b, "yxz"))) return out; + if ((out = op_mul_vec(immvalue_vector(a), b, "zxy"))) return out; + } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(b)) { + ast_expression *out; + if ((out = op_mul_vec(immvalue_vector(b), a, "xyz"))) return out; + if ((out = op_mul_vec(immvalue_vector(b), a, "yxz"))) return out; + if ((out = op_mul_vec(immvalue_vector(b), a, "zxy"))) return out; + } + } + } + return nullptr; +} + +ast_expression *fold::op_div(ast_value *a, ast_value *b) { + if (isfloat(a)) { + if (fold_can_2(a, b)) { + bool inexact = check_except_float(&sfloat_div, a, b); + return constgen_float(immvalue_float(a) / immvalue_float(b), inexact); + } else if (fold_can_1(b)) { + return new ast_binary( + ctx(), + INSTR_MUL_F, + a, + constgen_float(1.0f / immvalue_float(b), false) + ); + } + } else if (isvector(a)) { + if (fold_can_2(a, b)) { + return constgen_vector(vec3_mulvf(ctx(), immvalue_vector(a), 1.0f / immvalue_float(b))); + } else { + return new ast_binary( + ctx(), + INSTR_MUL_VF, + a, + (fold_can_1(b)) + ? constgen_float(1.0f / immvalue_float(b), false) + : new ast_binary(ctx(), + INSTR_DIV_F, + m_imm_float[1], + b + ) + ); + } + } + return nullptr; +} + +ast_expression *fold::op_mod(ast_value *a, ast_value *b) { + return (fold_can_2(a, b)) + ? constgen_float(fmod(immvalue_float(a), immvalue_float(b)), false) + : nullptr; +} + +ast_expression *fold::op_bor(ast_value *a, ast_value *b) { + if (isfloat(a)) { + if (fold_can_2(a, b)) + return constgen_float((qcfloat_t)(((qcint_t)immvalue_float(a)) | ((qcint_t)immvalue_float(b))), false); + } else { + if (isvector(b)) { + if (fold_can_2(a, b)) + return constgen_vector(vec3_or(immvalue_vector(a), immvalue_vector(b))); + } else { + if (fold_can_2(a, b)) + return constgen_vector(vec3_orvf(immvalue_vector(a), immvalue_float(b))); + } + } + return nullptr; +} + +ast_expression *fold::op_band(ast_value *a, ast_value *b) { + if (isfloat(a)) { + if (fold_can_2(a, b)) + return constgen_float((qcfloat_t)(((qcint_t)immvalue_float(a)) & ((qcint_t)immvalue_float(b))), false); + } else { + if (isvector(b)) { + if (fold_can_2(a, b)) + return constgen_vector(vec3_and(immvalue_vector(a), immvalue_vector(b))); + } else { + if (fold_can_2(a, b)) + return constgen_vector(vec3_andvf(immvalue_vector(a), immvalue_float(b))); + } + } + return nullptr; +} + +ast_expression *fold::op_xor(ast_value *a, ast_value *b) { + if (isfloat(a)) { + if (fold_can_2(a, b)) + return constgen_float((qcfloat_t)(((qcint_t)immvalue_float(a)) ^ ((qcint_t)immvalue_float(b))), false); + } else { + if (fold_can_2(a, b)) { + if (isvector(b)) + return constgen_vector(vec3_xor(immvalue_vector(a), immvalue_vector(b))); + else + return constgen_vector(vec3_xorvf(immvalue_vector(a), immvalue_float(b))); + } + } + return nullptr; +} + +ast_expression *fold::op_lshift(ast_value *a, ast_value *b) { + if (fold_can_2(a, b) && isfloats(a, b)) + return constgen_float((qcfloat_t)floorf(immvalue_float(a) * powf(2.0f, immvalue_float(b))), false); + return nullptr; +} + +ast_expression *fold::op_rshift(ast_value *a, ast_value *b) { + if (fold_can_2(a, b) && isfloats(a, b)) + return constgen_float((qcfloat_t)floorf(immvalue_float(a) / powf(2.0f, immvalue_float(b))), false); + return nullptr; +} + +ast_expression *fold::op_andor(ast_value *a, ast_value *b, float expr) { + if (fold_can_2(a, b)) { + if (OPTS_FLAG(PERL_LOGIC)) { + if (expr) + return immediate_true(a) ? a : b; + else + return immediate_true(a) ? b : a; + } else { + return constgen_float( + ((expr) ? (immediate_true(a) || immediate_true(b)) + : (immediate_true(a) && immediate_true(b))) + ? 1 + : 0, + false + ); + } + } + return nullptr; +} + +ast_expression *fold::op_tern(ast_value *a, ast_value *b, ast_value *c) { + if (fold_can_1(a)) { + return immediate_true(a) + ? b + : c; + } + return nullptr; +} + +ast_expression *fold::op_exp(ast_value *a, ast_value *b) { + if (fold_can_2(a, b)) + return constgen_float((qcfloat_t)powf(immvalue_float(a), immvalue_float(b)), false); + return nullptr; +} + +ast_expression *fold::op_lteqgt(ast_value *a, ast_value *b) { + if (fold_can_2(a,b)) { + check_inexact_float(a, b); + if (immvalue_float(a) < immvalue_float(b)) return m_imm_float[2]; + if (immvalue_float(a) == immvalue_float(b)) return m_imm_float[0]; + if (immvalue_float(a) > immvalue_float(b)) return m_imm_float[1]; + } + return nullptr; +} + +ast_expression *fold::op_ltgt(ast_value *a, ast_value *b, bool lt) { + if (fold_can_2(a, b)) { + check_inexact_float(a, b); + return (lt) ? m_imm_float[!!(immvalue_float(a) < immvalue_float(b))] + : m_imm_float[!!(immvalue_float(a) > immvalue_float(b))]; + } + return nullptr; +} + +ast_expression *fold::op_cmp(ast_value *a, ast_value *b, bool ne) { + if (fold_can_2(a, b)) { + if (isfloat(a) && isfloat(b)) { + float la = immvalue_float(a); + float lb = immvalue_float(b); + check_inexact_float(a, b); + return m_imm_float[ne ? la != lb : la == lb]; + } else if (isvector(a) && isvector(b)) { + vec3_t la = immvalue_vector(a); + vec3_t lb = immvalue_vector(b); + bool compare = vec3_cmp(la, lb); + return m_imm_float[ne ? !compare : compare]; + } else if (isstring(a) && isstring(b)) { + bool compare = !strcmp(immvalue_string(a), immvalue_string(b)); + return m_imm_float[ne ? !compare : compare]; + } + } + return nullptr; +} + +ast_expression *fold::op_bnot(ast_value *a) { + if (isfloat(a)) { + if (fold_can_1(a)) + return constgen_float(-1-immvalue_float(a), false); + } else { + if (isvector(a)) { + if (fold_can_1(a)) + return constgen_vector(vec3_not(immvalue_vector(a))); + } + } + return nullptr; +} + +ast_expression *fold::op_cross(ast_value *a, ast_value *b) { + if (fold_can_2(a, b)) + return constgen_vector(vec3_cross(ctx(), + immvalue_vector(a), + immvalue_vector(b))); + return nullptr; +} + +ast_expression *fold::op_length(ast_value *a) { + if (fold_can_1(a) && isstring(a)) + return constgen_float(strlen(immvalue_string(a)), false); + if (isarray(a)) + return constgen_float(a->m_initlist.size(), false); + return nullptr; +} + +ast_expression *fold::op(const oper_info *info, ast_expression **opexprs) { + ast_value *a = (ast_value*)opexprs[0]; + ast_value *b = (ast_value*)opexprs[1]; + ast_value *c = (ast_value*)opexprs[2]; + ast_expression *e = nullptr; + + /* can a fold operation be applied to this operator usage? */ + if (!info->folds) + return nullptr; + + switch(info->operands) { + case 3: if(!c) return nullptr; + case 2: if(!b) return nullptr; + case 1: + if(!a) { + compile_error(ctx(), "internal error: fold_op no operands to fold\n"); + return nullptr; + } + } + + #define fold_op_case(ARGS, ARGS_OPID, OP, ARGS_FOLD) \ + case opid##ARGS ARGS_OPID: \ + if ((e = op_##OP ARGS_FOLD)) { \ + ++opts_optimizationcount[OPTIM_CONST_FOLD]; \ + } \ + return e + + switch(info->id) { + fold_op_case(2, ('-', 'P'), neg, (a)); + fold_op_case(2, ('!', 'P'), not, (a)); + fold_op_case(1, ('+'), add, (a, b)); + fold_op_case(1, ('-'), sub, (a, b)); + fold_op_case(1, ('*'), mul, (a, b)); + fold_op_case(1, ('/'), div, (a, b)); + fold_op_case(1, ('%'), mod, (a, b)); + fold_op_case(1, ('|'), bor, (a, b)); + fold_op_case(1, ('&'), band, (a, b)); + fold_op_case(1, ('^'), xor, (a, b)); + fold_op_case(1, ('<'), ltgt, (a, b, true)); + fold_op_case(1, ('>'), ltgt, (a, b, false)); + fold_op_case(2, ('<', '<'), lshift, (a, b)); + fold_op_case(2, ('>', '>'), rshift, (a, b)); + fold_op_case(2, ('|', '|'), andor, (a, b, true)); + fold_op_case(2, ('&', '&'), andor, (a, b, false)); + fold_op_case(2, ('?', ':'), tern, (a, b, c)); + fold_op_case(2, ('*', '*'), exp, (a, b)); + fold_op_case(3, ('<','=','>'), lteqgt, (a, b)); + fold_op_case(2, ('!', '='), cmp, (a, b, true)); + fold_op_case(2, ('=', '='), cmp, (a, b, false)); + fold_op_case(2, ('~', 'P'), bnot, (a)); + fold_op_case(2, ('>', '<'), cross, (a, b)); + fold_op_case(3, ('l', 'e', 'n'), length, (a)); + } + #undef fold_op_case + compile_error(ctx(), "internal error: attempted to constant-fold for unsupported operator"); + return nullptr; +} + +/* + * Constant folding for compiler intrinsics, similar approach to operator + * folding, primarily: individual functions for each intrinsics to fold, + * and a generic selection function. + */ +ast_expression *fold::intrinsic_isfinite(ast_value *a) { + return constgen_float(isfinite(immvalue_float(a)), false); +} +ast_expression *fold::intrinsic_isinf(ast_value *a) { + return constgen_float(isinf(immvalue_float(a)), false); +} +ast_expression *fold::intrinsic_isnan(ast_value *a) { + return constgen_float(isnan(immvalue_float(a)), false); +} +ast_expression *fold::intrinsic_isnormal(ast_value *a) { + return constgen_float(isnormal(immvalue_float(a)), false); +} +ast_expression *fold::intrinsic_signbit(ast_value *a) { + return constgen_float(signbit(immvalue_float(a)), false); +} +ast_expression *fold::intrinsic_acosh(ast_value *a) { + return constgen_float(acoshf(immvalue_float(a)), false); +} +ast_expression *fold::intrinsic_asinh(ast_value *a) { + return constgen_float(asinhf(immvalue_float(a)), false); +} +ast_expression *fold::intrinsic_atanh(ast_value *a) { + return constgen_float((float)atanh(immvalue_float(a)), false); +} +ast_expression *fold::intrinsic_exp(ast_value *a) { + return constgen_float(expf(immvalue_float(a)), false); +} +ast_expression *fold::intrinsic_exp2(ast_value *a) { + return constgen_float(exp2f(immvalue_float(a)), false); +} +ast_expression *fold::intrinsic_expm1(ast_value *a) { + return constgen_float(expm1f(immvalue_float(a)), false); +} +ast_expression *fold::intrinsic_mod(ast_value *lhs, ast_value *rhs) { + return constgen_float(fmodf(immvalue_float(lhs), immvalue_float(rhs)), false); +} +ast_expression *fold::intrinsic_pow(ast_value *lhs, ast_value *rhs) { + return constgen_float(powf(immvalue_float(lhs), immvalue_float(rhs)), false); +} +ast_expression *fold::intrinsic_fabs(ast_value *a) { + return constgen_float(fabsf(immvalue_float(a)), false); +} + +ast_expression *fold::intrinsic(const char *intrinsic, ast_expression **arg) { + ast_expression *ret = nullptr; + ast_value *a = (ast_value*)arg[0]; + ast_value *b = (ast_value*)arg[1]; + + if (!strcmp(intrinsic, "isfinite")) ret = intrinsic_isfinite(a); + if (!strcmp(intrinsic, "isinf")) ret = intrinsic_isinf(a); + if (!strcmp(intrinsic, "isnan")) ret = intrinsic_isnan(a); + if (!strcmp(intrinsic, "isnormal")) ret = intrinsic_isnormal(a); + if (!strcmp(intrinsic, "signbit")) ret = intrinsic_signbit(a); + if (!strcmp(intrinsic, "acosh")) ret = intrinsic_acosh(a); + if (!strcmp(intrinsic, "asinh")) ret = intrinsic_asinh(a); + if (!strcmp(intrinsic, "atanh")) ret = intrinsic_atanh(a); + if (!strcmp(intrinsic, "exp")) ret = intrinsic_exp(a); + if (!strcmp(intrinsic, "exp2")) ret = intrinsic_exp2(a); + if (!strcmp(intrinsic, "expm1")) ret = intrinsic_expm1(a); + if (!strcmp(intrinsic, "mod")) ret = intrinsic_mod(a, b); + if (!strcmp(intrinsic, "pow")) ret = intrinsic_pow(a, b); + if (!strcmp(intrinsic, "fabs")) ret = intrinsic_fabs(a); + + if (ret) + ++opts_optimizationcount[OPTIM_CONST_FOLD]; + + return ret; +} + +/* + * These are all the actual constant folding methods that happen in between + * the AST/IR stage of the compiler , i.e eliminating branches for const + * expressions, which is the only supported thing so far. We undefine the + * testing macros here because an ir_value is differant than an ast_value. + */ +#undef expect +#undef isfloat +#undef isstring +#undef isvector +#undef fold__immvalue_float +#undef fold__immvalue_string +#undef fold__immvalue_vector +#undef fold_can_1 +#undef fold_can_2 + +#define isfloat(X) ((X)->m_vtype == TYPE_FLOAT) +/*#define isstring(X) ((X)->m_vtype == TYPE_STRING)*/ +/*#define isvector(X) ((X)->m_vtype == TYPE_VECTOR)*/ +#define fold_can_1(X) ((X)->m_hasvalue && (X)->m_cvq == CV_CONST) +/*#define fold_can_2(X,Y) (fold_can_1(X) && fold_can_1(Y))*/ + +qcfloat_t fold::immvalue_float(ir_value *value) { + return value->m_constval.vfloat; +} + +vec3_t fold::immvalue_vector(ir_value *value) { + return value->m_constval.vvec; +} + +ast_expression *fold::superfluous(ast_expression *left, ast_expression *right, int op) { + ast_expression *swapped = nullptr; /* using this as bool */ + ast_value *load; + + if (!ast_istype(right, ast_value) || !fold_can_1((load = (ast_value*)right))) { + swapped = left; + left = right; + right = swapped; + } + + if (!ast_istype(right, ast_value) || !fold_can_1((load = (ast_value*)right))) + return nullptr; + + switch (op) { + case INSTR_DIV_F: + if (swapped) + return nullptr; + case INSTR_MUL_F: + if (immvalue_float(load) == 1.0f) { + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + ast_unref(right); + return left; + } + break; + + + case INSTR_SUB_F: + if (swapped) + return nullptr; + case INSTR_ADD_F: + if (immvalue_float(load) == 0.0f) { + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + ast_unref(right); + return left; + } + break; + + case INSTR_MUL_V: + if (vec3_cmp(immvalue_vector(load), vec3_create(1, 1, 1))) { + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + ast_unref(right); + return left; + } + break; + + case INSTR_SUB_V: + if (swapped) + return nullptr; + case INSTR_ADD_V: + if (vec3_cmp(immvalue_vector(load), vec3_create(0, 0, 0))) { + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + ast_unref(right); + return left; + } + break; + } + + return nullptr; +} + +ast_expression *fold::binary(lex_ctx_t ctx, int op, ast_expression *left, ast_expression *right) { + ast_expression *ret = superfluous(left, right, op); + if (ret) + return ret; + return new ast_binary(ctx, op, left, right); +} + +int fold::cond(ir_value *condval, ast_function *func, ast_ifthen *branch) { + if (isfloat(condval) && fold_can_1(condval) && OPTS_OPTIMIZATION(OPTIM_CONST_FOLD_DCE)) { + ir_block *elide; + ir_value *dummy; + bool istrue = (immvalue_float(condval) != 0.0f && branch->m_on_true); + bool isfalse = (immvalue_float(condval) == 0.0f && branch->m_on_false); + ast_expression *path = (istrue) ? branch->m_on_true : + (isfalse) ? branch->m_on_false : nullptr; + if (!path) { + /* + * no path to take implies that the evaluation is if(0) and there + * is no else block. so eliminate all the code. + */ + ++opts_optimizationcount[OPTIM_CONST_FOLD_DCE]; + return true; + } + + if (!(elide = ir_function_create_block(branch->m_context, func->m_ir_func, func->makeLabel((istrue) ? "ontrue" : "onfalse")))) + return false; + if (!path->codegen(func, false, &dummy)) + return false; + if (!ir_block_create_jump(func->m_curblock, branch->m_context, elide)) + return false; + /* + * now the branch has been eliminated and the correct block for the constant evaluation + * is expanded into the current block for the function. + */ + func->m_curblock = elide; + ++opts_optimizationcount[OPTIM_CONST_FOLD_DCE]; + return true; + } + return -1; /* nothing done */ +} + +int fold::cond_ternary(ir_value *condval, ast_function *func, ast_ternary *branch) { + return cond(condval, func, (ast_ifthen*)branch); +} + +int fold::cond_ifthen(ir_value *condval, ast_function *func, ast_ifthen *branch) { + return cond(condval, func, branch); +} diff --git a/fold.h b/fold.h new file mode 100644 index 0000000..1d096e8 --- /dev/null +++ b/fold.h @@ -0,0 +1,107 @@ +#ifndef GMQCC_FOLD_HDR +#define GMQCC_FOLD_HDR +#include "lexer.h" +#include "gmqcc.h" + +struct ir_builder; +struct ir_value; + +struct ast_function; +struct ast_ifthen; +struct ast_ternary; +struct ast_expression; +struct ast_value; + +struct parser_t; + +struct fold { + fold(); + fold(parser_t *parser); + ~fold(); + + bool generate(ir_builder *ir); + ast_expression *op(const oper_info *info, ast_expression **opexprs); + ast_expression *intrinsic(const char *intrinsic, ast_expression **arg); + + static int cond_ternary(ir_value *condval, ast_function *func, ast_ternary *branch); + static int cond_ifthen(ir_value *condval, ast_function *func, ast_ifthen *branch); + + static ast_expression *superfluous(ast_expression *left, ast_expression *right, int op); + static ast_expression *binary(lex_ctx_t ctx, int op, ast_expression *left, ast_expression *right); + + ast_expression *constgen_float(qcfloat_t value, bool inexact); + ast_expression *constgen_vector(vec3_t value); + ast_expression *constgen_string(const char *str, bool translate); + ast_expression *constgen_string(const std::string &str, bool translate); + + ast_value *imm_float(size_t index) const { return m_imm_float[index]; } + ast_value *imm_vector(size_t index) const { return m_imm_vector[index]; } + +protected: + static qcfloat_t immvalue_float(ast_value *value); + static vec3_t immvalue_vector(ast_value *value); + static const char *immvalue_string(ast_value *value); + + lex_ctx_t ctx(); + + bool immediate_true(ast_value *v); + + bool check_except_float_impl(void (*callback)(void), ast_value *a, ast_value *b); + bool check_inexact_float(ast_value *a, ast_value *b); + + ast_expression *op_mul_vec(vec3_t vec, ast_value *sel, const char *set); + ast_expression *op_neg(ast_value *a); + ast_expression *op_not(ast_value *a); + ast_expression *op_add(ast_value *a, ast_value *b); + ast_expression *op_sub(ast_value *a, ast_value *b); + ast_expression *op_mul(ast_value *a, ast_value *b); + ast_expression *op_div(ast_value *a, ast_value *b); + ast_expression *op_mod(ast_value *a, ast_value *b); + ast_expression *op_bor(ast_value *a, ast_value *b); + ast_expression *op_band(ast_value *a, ast_value *b); + ast_expression *op_xor(ast_value *a, ast_value *b); + ast_expression *op_lshift(ast_value *a, ast_value *b); + ast_expression *op_rshift(ast_value *a, ast_value *b); + ast_expression *op_andor(ast_value *a, ast_value *b, float expr); + ast_expression *op_tern(ast_value *a, ast_value *b, ast_value *c); + ast_expression *op_exp(ast_value *a, ast_value *b); + ast_expression *op_lteqgt(ast_value *a, ast_value *b); + ast_expression *op_ltgt(ast_value *a, ast_value *b, bool lt); + ast_expression *op_cmp(ast_value *a, ast_value *b, bool ne); + ast_expression *op_bnot(ast_value *a); + ast_expression *op_cross(ast_value *a, ast_value *b); + ast_expression *op_length(ast_value *a); + + ast_expression *intrinsic_isfinite(ast_value *a); + ast_expression *intrinsic_isinf(ast_value *a); + ast_expression *intrinsic_isnan(ast_value *a); + ast_expression *intrinsic_isnormal(ast_value *a); + ast_expression *intrinsic_signbit(ast_value *a); + ast_expression *intrinsic_acosh(ast_value *a); + ast_expression *intrinsic_asinh(ast_value *a); + ast_expression *intrinsic_atanh(ast_value *a); + ast_expression *intrinsic_exp(ast_value *a); + ast_expression *intrinsic_exp2(ast_value *a); + ast_expression *intrinsic_expm1(ast_value *a); + ast_expression *intrinsic_mod(ast_value *lhs, ast_value *rhs); + ast_expression *intrinsic_pow(ast_value *lhs, ast_value *rhs); + ast_expression *intrinsic_fabs(ast_value *a); + + static qcfloat_t immvalue_float(ir_value *value); + static vec3_t immvalue_vector(ir_value *value); + + static int cond(ir_value *condval, ast_function *func, ast_ifthen *branch); + +private: + friend struct intrin; + + std::vector m_imm_float; + std::vector m_imm_vector; + std::vector m_imm_string; + hash_table_t *m_imm_string_untranslate; /* map */ + hash_table_t *m_imm_string_dotranslate; /* map */ + parser_t *m_parser; + bool m_initialized; +}; + +#endif diff --git a/fs.c b/fs.c deleted file mode 100644 index 1e06ecc..0000000 --- a/fs.c +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * 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. - */ -#define GMQCC_PLATFORM_HEADER -#include "gmqcc.h" -#include "platform.h" - -fs_file_t *fs_file_open(const char *filename, const char *mode) { - return (fs_file_t*)platform_fopen(filename, mode); -} - -size_t fs_file_read(void *buffer, size_t size, size_t count, fs_file_t *fp) { - return platform_fread(buffer, size, count, (FILE*)fp); -} - -int fs_file_printf(fs_file_t *fp, const char *format, ...) { - int rt; - va_list va; - va_start(va, format); - rt = platform_vfprintf((FILE*)fp, format, va); - va_end (va); - - return rt; -} - -void fs_file_close(fs_file_t *fp) { - platform_fclose((FILE*)fp); -} - -size_t fs_file_write ( - const void *buffer, - size_t size, - size_t count, - fs_file_t *fp -) { - return platform_fwrite(buffer, size, count, (FILE*)fp); -} - -int fs_file_error(fs_file_t *fp) { - return platform_ferror((FILE*)fp); -} - -int fs_file_getc(fs_file_t *fp) { - int get = platform_fgetc((FILE*)fp); - return (get == EOF) ? FS_FILE_EOF : get; -} - -int fs_file_puts(fs_file_t *fp, const char *str) { - return platform_fputs(str, (FILE*)fp); -} - -int fs_file_seek(fs_file_t *fp, long int off, int whence) { - switch(whence) { - case FS_FILE_SEEK_CUR: whence = SEEK_CUR; break; - case FS_FILE_SEEK_SET: whence = SEEK_SET; break; - case FS_FILE_SEEK_END: whence = SEEK_END; break; - } - return platform_fseek((FILE*)fp, off, whence); -} - -long int fs_file_tell(fs_file_t *fp) { - return platform_ftell((FILE*)fp); -} - -int fs_file_flush(fs_file_t *fp) { - return platform_fflush((FILE*)fp); -} - -/* - * Implements libc getline for systems that don't have it, which is - * assmed all. This works the same as getline(). - */ -int fs_file_getline(char **lineptr, size_t *n, fs_file_t *stream) { - int chr; - int ret; - char *pos; - - if (!lineptr || !n || !stream) - return -1; - if (!*lineptr) { - if (!(*lineptr = (char*)mem_a((*n=64)))) - return -1; - } - - chr = *n; - pos = *lineptr; - - for (;;) { - int c = fs_file_getc(stream); - - if (chr < 2) { - *n += (*n > 16) ? *n : 64; - chr = *n + *lineptr - pos; - if (!(*lineptr = (char*)mem_r(*lineptr,*n))) - return -1; - pos = *n - chr + *lineptr; - } - - if (fs_file_error(stream)) - return -1; - if (c == EOF) { - if (pos == *lineptr) - return -1; - else - break; - } - - *pos++ = c; - chr--; - if (c == '\n') - break; - } - *pos = '\0'; - return (ret = pos - *lineptr); -} - -int fs_dir_make(const char *path) { - return platform_mkdir(path, 0700); -} - -fs_dir_t *fs_dir_open(const char *name) { - return (fs_dir_t*)platform_opendir(name); -} - -int fs_dir_close(fs_dir_t *dir) { - return platform_closedir((DIR*)dir); -} - -fs_dirent_t *fs_dir_read(fs_dir_t *dir) { - return (fs_dirent_t*)platform_readdir((DIR*)dir); -} diff --git a/ftepp.c b/ftepp.c deleted file mode 100644 index d2c01df..0000000 --- a/ftepp.c +++ /dev/null @@ -1,1987 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * Wolfgang Bumiller - * 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 -#include -#include - -#include "gmqcc.h" -#include "lexer.h" - -#define HT_MACROS 1024 - -typedef struct { - bool on; - bool was_on; - bool had_else; -} ppcondition; - -typedef struct { - int token; - char *value; - /* a copy from the lexer */ - union { - vec3_t v; - int i; - double f; - int t; /* type */ - } constval; -} pptoken; - -typedef struct { - lex_ctx_t ctx; - - char *name; - char **params; - /* yes we need an extra flag since `#define FOO x` is not the same as `#define FOO() x` */ - bool has_params; - bool variadic; - - pptoken **output; -} ppmacro; - -typedef struct ftepp_s { - lex_file *lex; - int token; - unsigned int errors; - - bool output_on; - ppcondition *conditions; - /*ppmacro **macros;*/ - ht macros; /* hashtable */ - char *output_string; - - char *itemname; - char *includename; - bool in_macro; - - uint32_t predef_countval; - uint32_t predef_randval; -} ftepp_t; - -/* __DATE__ */ -static char *ftepp_predef_date(ftepp_t *context) { - const struct tm *itime = NULL; - char *value = (char*)mem_a(82); - time_t rtime; - - (void)context; - - time (&rtime); - itime = util_localtime(&rtime); - strftime(value, 82, "\"%b %d %Y\"", itime); - - return value; -} - -/* __TIME__ */ -static char *ftepp_predef_time(ftepp_t *context) { - const struct tm *itime = NULL; - char *value = (char*)mem_a(82); - time_t rtime; - - (void)context; - - time (&rtime); - itime = util_localtime(&rtime); - strftime(value, 82, "\"%X\"", itime); - - return value; -} - -/* __LINE__ */ -static char *ftepp_predef_line(ftepp_t *context) { - char *value; - - util_asprintf(&value, "%d", (int)context->lex->line); - return value; -} -/* __FILE__ */ -static char *ftepp_predef_file(ftepp_t *context) { - size_t length = strlen(context->lex->name) + 3; /* two quotes and a terminator */ - char *value = (char*)mem_a(length); - - util_snprintf(value, length, "\"%s\"", context->lex->name); - return value; -} -/* __COUNTER_LAST__ */ -static char *ftepp_predef_counterlast(ftepp_t *context) { - char *value; - util_asprintf(&value, "%u", context->predef_countval); - return value; -} -/* __COUNTER__ */ -static char *ftepp_predef_counter(ftepp_t *context) { - char *value; - - context->predef_countval ++; - util_asprintf(&value, "%u", context->predef_countval); - - return value; -} -/* __RANDOM__ */ -static char *ftepp_predef_random(ftepp_t *context) { - char *value; - - context->predef_randval = (util_rand() % 0xFF) + 1; - util_asprintf(&value, "%u", context->predef_randval); - return value; -} -/* __RANDOM_LAST__ */ -static char *ftepp_predef_randomlast(ftepp_t *context) { - char *value; - - util_asprintf(&value, "%u", context->predef_randval); - return value; -} -/* __TIMESTAMP__ */ -static char *ftepp_predef_timestamp(ftepp_t *context) { - struct stat finfo; - const char *find; - char *value; - size_t size; - - if (stat(context->lex->name, &finfo)) - return util_strdup("\"\""); - - find = util_ctime(&finfo.st_mtime); - value = (char*)mem_a(strlen(find) + 1); - memcpy(&value[1], find, (size = strlen(find)) - 1); - - value[0] = '"'; - value[size] = '"'; - - return value; -} - -typedef struct { - const char *name; - char *(*func)(ftepp_t *); -} ftepp_predef_t; - -static const ftepp_predef_t ftepp_predefs[] = { - { "__LINE__", &ftepp_predef_line }, - { "__FILE__", &ftepp_predef_file }, - { "__COUNTER__", &ftepp_predef_counter }, - { "__COUNTER_LAST__", &ftepp_predef_counterlast }, - { "__RANDOM__", &ftepp_predef_random }, - { "__RANDOM_LAST__", &ftepp_predef_randomlast }, - { "__DATE__", &ftepp_predef_date }, - { "__TIME__", &ftepp_predef_time }, - { "__TIME_STAMP__", &ftepp_predef_timestamp } -}; - -static GMQCC_INLINE size_t ftepp_predef_index(const char *name) { - /* no hashtable here, we simply check for one to exist the naive way */ - size_t i; - for(i = 1; i < GMQCC_ARRAY_COUNT(ftepp_predefs) + 1; i++) - if (!strcmp(ftepp_predefs[i-1].name, name)) - return i; - return 0; -} - -bool ftepp_predef_exists(const char *name); -bool ftepp_predef_exists(const char *name) { - return ftepp_predef_index(name) != 0; -} - -/* singleton because we're allowed */ -static GMQCC_INLINE char *(*ftepp_predef(const char *name))(ftepp_t *context) { - size_t i = ftepp_predef_index(name); - return (i != 0) ? ftepp_predefs[i-1].func : NULL; -} - -#define ftepp_tokval(f) ((f)->lex->tok.value) -#define ftepp_ctx(f) ((f)->lex->tok.ctx) - -static void ftepp_errorat(ftepp_t *ftepp, lex_ctx_t ctx, const char *fmt, ...) -{ - va_list ap; - - ftepp->errors++; - - va_start(ap, fmt); - con_cvprintmsg(ctx, LVL_ERROR, "error", fmt, ap); - va_end(ap); -} - -static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...) -{ - va_list ap; - - ftepp->errors++; - - va_start(ap, fmt); - con_cvprintmsg(ftepp->lex->tok.ctx, LVL_ERROR, "error", fmt, ap); - va_end(ap); -} - -static bool GMQCC_WARN ftepp_warn(ftepp_t *ftepp, int warntype, const char *fmt, ...) -{ - bool r; - va_list ap; - - va_start(ap, fmt); - r = vcompile_warning(ftepp->lex->tok.ctx, warntype, fmt, ap); - va_end(ap); - return r; -} - -static pptoken *pptoken_make(ftepp_t *ftepp) -{ - pptoken *token = (pptoken*)mem_a(sizeof(pptoken)); - token->token = ftepp->token; -#if 0 - if (token->token == TOKEN_WHITE) - token->value = util_strdup(" "); - else -#else - token->value = util_strdup(ftepp_tokval(ftepp)); -#endif - memcpy(&token->constval, &ftepp->lex->tok.constval, sizeof(token->constval)); - return token; -} - -static GMQCC_INLINE void pptoken_delete(pptoken *self) -{ - mem_d(self->value); - mem_d(self); -} - -static ppmacro *ppmacro_new(lex_ctx_t ctx, const char *name) -{ - ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro)); - - (void)ctx; - memset(macro, 0, sizeof(*macro)); - macro->name = util_strdup(name); - return macro; -} - -static void ppmacro_delete(ppmacro *self) -{ - size_t i; - for (i = 0; i < vec_size(self->params); ++i) - mem_d(self->params[i]); - vec_free(self->params); - for (i = 0; i < vec_size(self->output); ++i) - pptoken_delete(self->output[i]); - vec_free(self->output); - mem_d(self->name); - mem_d(self); -} - -static ftepp_t* ftepp_new(void) -{ - ftepp_t *ftepp; - - ftepp = (ftepp_t*)mem_a(sizeof(*ftepp)); - memset(ftepp, 0, sizeof(*ftepp)); - - ftepp->macros = util_htnew(HT_MACROS); - ftepp->output_on = true; - ftepp->predef_countval = 0; - ftepp->predef_randval = 0; - - return ftepp; -} - -static GMQCC_INLINE void ftepp_flush_do(ftepp_t *self) -{ - vec_free(self->output_string); -} - -static void ftepp_delete(ftepp_t *self) -{ - ftepp_flush_do(self); - if (self->itemname) - mem_d(self->itemname); - if (self->includename) - vec_free(self->includename); - - util_htrem(self->macros, (void (*)(void*))&ppmacro_delete); - - vec_free(self->conditions); - if (self->lex) - lex_close(self->lex); - mem_d(self); -} - -static void ftepp_out(ftepp_t *ftepp, const char *str, bool ignore_cond) -{ - if (ignore_cond || ftepp->output_on) - { - size_t len; - char *data; - len = strlen(str); - data = vec_add(ftepp->output_string, len); - memcpy(data, str, len); - } -} - -static GMQCC_INLINE void ftepp_update_output_condition(ftepp_t *ftepp) -{ - size_t i; - ftepp->output_on = true; - for (i = 0; i < vec_size(ftepp->conditions); ++i) - ftepp->output_on = ftepp->output_on && ftepp->conditions[i].on; -} - -static GMQCC_INLINE ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name) -{ - return (ppmacro*)util_htget(ftepp->macros, name); -} - -static GMQCC_INLINE void ftepp_macro_delete(ftepp_t *ftepp, const char *name) -{ - util_htrm(ftepp->macros, name, (void (*)(void*))&ppmacro_delete); -} - -static GMQCC_INLINE int ftepp_next(ftepp_t *ftepp) -{ - return (ftepp->token = lex_do(ftepp->lex)); -} - -/* Important: this does not skip newlines! */ -static bool ftepp_skipspace(ftepp_t *ftepp) -{ - if (ftepp->token != TOKEN_WHITE) - return true; - while (ftepp_next(ftepp) == TOKEN_WHITE) {} - if (ftepp->token >= TOKEN_EOF) { - ftepp_error(ftepp, "unexpected end of preprocessor directive"); - return false; - } - return true; -} - -/* this one skips EOLs as well */ -static bool ftepp_skipallwhite(ftepp_t *ftepp) -{ - if (ftepp->token != TOKEN_WHITE && ftepp->token != TOKEN_EOL) - return true; - do { - ftepp_next(ftepp); - } while (ftepp->token == TOKEN_WHITE || ftepp->token == TOKEN_EOL); - if (ftepp->token >= TOKEN_EOF) { - ftepp_error(ftepp, "unexpected end of preprocessor directive"); - return false; - } - return true; -} - -/** - * The huge macro parsing code... - */ -static bool ftepp_define_params(ftepp_t *ftepp, ppmacro *macro) -{ - do { - ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - if (ftepp->token == ')') - break; - switch (ftepp->token) { - case TOKEN_IDENT: - case TOKEN_TYPENAME: - case TOKEN_KEYWORD: - vec_push(macro->params, util_strdup(ftepp_tokval(ftepp))); - break; - case TOKEN_DOTS: - macro->variadic = true; - break; - default: - ftepp_error(ftepp, "unexpected token in parameter list"); - return false; - } - ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - if (macro->variadic && ftepp->token != ')') { - ftepp_error(ftepp, "cannot have parameters after the variadic parameters"); - return false; - } - } while (ftepp->token == ','); - - if (ftepp->token != ')') { - ftepp_error(ftepp, "expected closing paren after macro parameter list"); - return false; - } - ftepp_next(ftepp); - /* skipspace happens in ftepp_define */ - return true; -} - -static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro) -{ - pptoken *ptok; - while (ftepp->token != TOKEN_EOL && ftepp->token < TOKEN_EOF) { - bool subscript = false; - size_t index = 0; - if (macro->variadic && !strcmp(ftepp_tokval(ftepp), "__VA_ARGS__")) { - subscript = !!(ftepp_next(ftepp) == '#'); - - if (subscript && ftepp_next(ftepp) != '#') { - ftepp_error(ftepp, "expected `##` in __VA_ARGS__ for subscripting"); - return false; - } else if (subscript) { - if (ftepp_next(ftepp) == '[') { - if (ftepp_next(ftepp) != TOKEN_INTCONST) { - ftepp_error(ftepp, "expected index for __VA_ARGS__ subscript"); - return false; - } - - index = (int)strtol(ftepp_tokval(ftepp), NULL, 10); - - if (ftepp_next(ftepp) != ']') { - ftepp_error(ftepp, "expected `]` in __VA_ARGS__ subscript"); - return false; - } - - /* - * mark it as an array to be handled later as such and not - * as traditional __VA_ARGS__ - */ - ftepp->token = TOKEN_VA_ARGS_ARRAY; - ptok = pptoken_make(ftepp); - ptok->constval.i = index; - vec_push(macro->output, ptok); - ftepp_next(ftepp); - } else { - ftepp_error(ftepp, "expected `[` for subscripting of __VA_ARGS__"); - return false; - } - } else { - int old = ftepp->token; - ftepp->token = TOKEN_VA_ARGS; - ptok = pptoken_make(ftepp); - vec_push(macro->output, ptok); - ftepp->token = old; - } - } - else if (macro->variadic && !strcmp(ftepp_tokval(ftepp), "__VA_COUNT__")) { - ftepp->token = TOKEN_VA_COUNT; - ptok = pptoken_make(ftepp); - vec_push(macro->output, ptok); - ftepp_next(ftepp); - } else { - ptok = pptoken_make(ftepp); - vec_push(macro->output, ptok); - ftepp_next(ftepp); - } - } - /* recursive expansion can cause EOFs here */ - if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) { - ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file"); - return false; - } - return true; -} - -static const char *ftepp_math_constants[][2] = { - { "M_E", "2.7182818284590452354" }, /* e */ - { "M_LOG2E", "1.4426950408889634074" }, /* log_2 e */ - { "M_LOG10E", "0.43429448190325182765" }, /* log_10 e */ - { "M_LN2", "0.69314718055994530942" }, /* log_e 2 */ - { "M_LN10", "2.30258509299404568402" }, /* log_e 10 */ - { "M_PI", "3.14159265358979323846" }, /* pi */ - { "M_PI_2", "1.57079632679489661923" }, /* pi/2 */ - { "M_PI_4", "0.78539816339744830962" }, /* pi/4 */ - { "M_1_PI", "0.31830988618379067154" }, /* 1/pi */ - { "M_2_PI", "0.63661977236758134308" }, /* 2/pi */ - { "M_2_SQRTPI", "1.12837916709551257390" }, /* 2/sqrt(pi) */ - { "M_SQRT2", "1.41421356237309504880" }, /* sqrt(2) */ - { "M_SQRT1_2", "0.70710678118654752440" }, /* 1/sqrt(2) */ - { "M_TAU", "6.28318530717958647692" } /* pi*2 */ -}; - -static bool ftepp_define(ftepp_t *ftepp) -{ - ppmacro *macro = NULL; - size_t l = ftepp_ctx(ftepp).line; - size_t i; - bool mathconstant = false; - - (void)ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - - switch (ftepp->token) { - case TOKEN_IDENT: - case TOKEN_TYPENAME: - case TOKEN_KEYWORD: - if (OPTS_FLAG(FTEPP_MATHDEFS)) { - for (i = 0; i < GMQCC_ARRAY_COUNT(ftepp_math_constants); i++) { - if (!strcmp(ftepp_math_constants[i][0], ftepp_tokval(ftepp))) { - mathconstant = true; - break; - } - } - } - - macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); - - if (OPTS_FLAG(FTEPP_MATHDEFS)) { - /* user defined ones take precedence */ - if (macro && mathconstant) { - ftepp_macro_delete(ftepp, ftepp_tokval(ftepp)); - macro = NULL; - } - } - - if (macro && ftepp->output_on) { - if (ftepp_warn(ftepp, WARN_CPP, "redefining `%s`", ftepp_tokval(ftepp))) - return false; - ftepp_macro_delete(ftepp, ftepp_tokval(ftepp)); - } - macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp)); - break; - default: - ftepp_error(ftepp, "expected macro name"); - return false; - } - - (void)ftepp_next(ftepp); - - if (ftepp->token == '(') { - macro->has_params = true; - if (!ftepp_define_params(ftepp, macro)) { - ppmacro_delete(macro); - return false; - } - } - - if (!ftepp_skipspace(ftepp)) { - ppmacro_delete(macro); - return false; - } - - if (!ftepp_define_body(ftepp, macro)) { - ppmacro_delete(macro); - return false; - } - - if (ftepp->output_on) - util_htset(ftepp->macros, macro->name, (void*)macro); - else { - ppmacro_delete(macro); - } - - for (; l < ftepp_ctx(ftepp).line; ++l) - ftepp_out(ftepp, "\n", true); - return true; -} - -/** - * When a macro is used we have to handle parameters as well - * as special-concatenation via ## or stringification via # - * - * Note: parenthesis can nest, so FOO((a),b) is valid, but only - * this kind of parens. Curly braces or [] don't count towards the - * paren-level. - */ -typedef struct { - pptoken **tokens; -} macroparam; - -static void macroparam_clean(macroparam *self) -{ - size_t i; - for (i = 0; i < vec_size(self->tokens); ++i) - pptoken_delete(self->tokens[i]); - vec_free(self->tokens); -} - -/* need to leave the last token up */ -static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params) -{ - macroparam *params = NULL; - pptoken *ptok; - macroparam mp; - size_t parens = 0; - size_t i; - - if (!ftepp_skipallwhite(ftepp)) - return false; - while (ftepp->token != ')') { - mp.tokens = NULL; - if (!ftepp_skipallwhite(ftepp)) - return false; - while (parens || ftepp->token != ',') { - if (ftepp->token == '(') - ++parens; - else if (ftepp->token == ')') { - if (!parens) - break; - --parens; - } - ptok = pptoken_make(ftepp); - vec_push(mp.tokens, ptok); - if (ftepp_next(ftepp) >= TOKEN_EOF) { - ftepp_error(ftepp, "unexpected end of file in macro call"); - goto on_error; - } - } - vec_push(params, mp); - mp.tokens = NULL; - if (ftepp->token == ')') - break; - if (ftepp->token != ',') { - ftepp_error(ftepp, "expected closing paren or comma in macro call"); - goto on_error; - } - if (ftepp_next(ftepp) >= TOKEN_EOF) { - ftepp_error(ftepp, "unexpected end of file in macro call"); - goto on_error; - } - } - *out_params = params; - return true; - -on_error: - if (mp.tokens) - macroparam_clean(&mp); - for (i = 0; i < vec_size(params); ++i) - macroparam_clean(¶ms[i]); - vec_free(params); - return false; -} - -static bool macro_params_find(ppmacro *macro, const char *name, size_t *idx) -{ - size_t i; - for (i = 0; i < vec_size(macro->params); ++i) { - if (!strcmp(macro->params[i], name)) { - *idx = i; - return true; - } - } - return false; -} - -static void ftepp_stringify_token(ftepp_t *ftepp, pptoken *token) -{ - char chs[2]; - const char *ch; - chs[1] = 0; - switch (token->token) { - case TOKEN_STRINGCONST: - ch = token->value; - while (*ch) { - /* in preprocessor mode strings already are string, - * so we don't get actual newline bytes here. - * Still need to escape backslashes and quotes. - */ - switch (*ch) { - case '\\': ftepp_out(ftepp, "\\\\", false); break; - case '"': ftepp_out(ftepp, "\\\"", false); break; - default: - chs[0] = *ch; - ftepp_out(ftepp, chs, false); - break; - } - ++ch; - } - break; - /*case TOKEN_WHITE: - ftepp_out(ftepp, " ", false); - break;*/ - case TOKEN_EOL: - ftepp_out(ftepp, "\\n", false); - break; - default: - ftepp_out(ftepp, token->value, false); - break; - } -} - -static void ftepp_stringify(ftepp_t *ftepp, macroparam *param) -{ - size_t i; - ftepp_out(ftepp, "\"", false); - for (i = 0; i < vec_size(param->tokens); ++i) - ftepp_stringify_token(ftepp, param->tokens[i]); - ftepp_out(ftepp, "\"", false); -} - -static void ftepp_recursion_header(ftepp_t *ftepp) -{ - ftepp_out(ftepp, "\n#pragma push(line)\n", false); -} - -static void ftepp_recursion_footer(ftepp_t *ftepp) -{ - ftepp_out(ftepp, "\n#pragma pop(line)\n", false); -} - -static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params, bool resetline); -static void ftepp_param_out(ftepp_t *ftepp, macroparam *param) -{ - size_t i; - pptoken *out; - for (i = 0; i < vec_size(param->tokens); ++i) { - out = param->tokens[i]; - if (out->token == TOKEN_EOL) - ftepp_out(ftepp, "\n", false); - else { - ppmacro *find = ftepp_macro_find(ftepp, out->value); - if (OPTS_FLAG(FTEPP_INDIRECT_EXPANSION) && find && !find->has_params) - ftepp_macro_expand(ftepp, find, NULL, false); - else - ftepp_out(ftepp, out->value, false); - } - } -} - -static bool ftepp_preprocess(ftepp_t *ftepp); -static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params, bool resetline) -{ - char *buffer = NULL; - char *old_string = ftepp->output_string; - char *inner_string; - lex_file *old_lexer = ftepp->lex; - size_t vararg_start = vec_size(macro->params); - bool retval = true; - bool has_newlines; - size_t varargs; - - size_t o, pi; - lex_file *inlex; - - bool old_inmacro; - bool strip = false; - - int nextok; - - if (vararg_start < vec_size(params)) - varargs = vec_size(params) - vararg_start; - else - varargs = 0; - - /* really ... */ - if (!vec_size(macro->output)) - return true; - - ftepp->output_string = NULL; - for (o = 0; o < vec_size(macro->output); ++o) { - pptoken *out = macro->output[o]; - switch (out->token) { - case TOKEN_VA_ARGS: - if (!macro->variadic) { - ftepp_error(ftepp, "internal preprocessor error: TOKEN_VA_ARGS in non-variadic macro"); - vec_free(old_string); - return false; - } - if (!varargs) - break; - - pi = 0; - ftepp_param_out(ftepp, ¶ms[pi + vararg_start]); - for (++pi; pi < varargs; ++pi) { - ftepp_out(ftepp, ", ", false); - ftepp_param_out(ftepp, ¶ms[pi + vararg_start]); - } - break; - - case TOKEN_VA_ARGS_ARRAY: - if ((size_t)out->constval.i >= varargs) { - ftepp_error(ftepp, "subscript of `[%u]` is out of bounds for `__VA_ARGS__`", out->constval.i); - vec_free(old_string); - return false; - } - - ftepp_param_out(ftepp, ¶ms[out->constval.i + vararg_start]); - break; - - case TOKEN_VA_COUNT: - util_asprintf(&buffer, "%d", varargs); - ftepp_out(ftepp, buffer, false); - mem_d(buffer); - break; - - case TOKEN_IDENT: - case TOKEN_TYPENAME: - case TOKEN_KEYWORD: - if (!macro_params_find(macro, out->value, &pi)) { - ftepp_out(ftepp, out->value, false); - break; - } else - ftepp_param_out(ftepp, ¶ms[pi]); - break; - case '#': - if (o + 1 < vec_size(macro->output)) { - nextok = macro->output[o+1]->token; - if (nextok == '#') { - /* raw concatenation */ - ++o; - strip = true; - break; - } - if ( (nextok == TOKEN_IDENT || - nextok == TOKEN_KEYWORD || - nextok == TOKEN_TYPENAME) && - macro_params_find(macro, macro->output[o+1]->value, &pi)) - { - ++o; - - ftepp_stringify(ftepp, ¶ms[pi]); - break; - } - } - ftepp_out(ftepp, "#", false); - break; - case TOKEN_EOL: - ftepp_out(ftepp, "\n", false); - break; - default: - buffer = out->value; - #define buffer_stripable(X) ((X) == ' ' || (X) == '\t') - if (vec_size(macro->output) > o + 1 && macro->output[o+1]->token == '#' && buffer_stripable(*buffer)) - buffer++; - if (strip) { - while (buffer_stripable(*buffer)) buffer++; - strip = false; - } - ftepp_out(ftepp, buffer, false); - break; - } - } - vec_push(ftepp->output_string, 0); - /* Now run the preprocessor recursively on this string buffer */ - /* - printf("__________\n%s\n=========\n", ftepp->output_string); - */ - inlex = lex_open_string(ftepp->output_string, vec_size(ftepp->output_string)-1, ftepp->lex->name); - if (!inlex) { - ftepp_error(ftepp, "internal error: failed to instantiate lexer"); - retval = false; - goto cleanup; - } - - inlex->line = ftepp->lex->line; - inlex->sline = ftepp->lex->sline; - ftepp->lex = inlex; - - old_inmacro = ftepp->in_macro; - ftepp->in_macro = true; - ftepp->output_string = NULL; - if (!ftepp_preprocess(ftepp)) { - ftepp->in_macro = old_inmacro; - vec_free(ftepp->lex->open_string); - vec_free(ftepp->output_string); - lex_close(ftepp->lex); - retval = false; - goto cleanup; - } - ftepp->in_macro = old_inmacro; - vec_free(ftepp->lex->open_string); - lex_close(ftepp->lex); - - inner_string = ftepp->output_string; - ftepp->output_string = old_string; - - has_newlines = (strchr(inner_string, '\n') != NULL); - - if (has_newlines && !old_inmacro) - ftepp_recursion_header(ftepp); - - vec_append(ftepp->output_string, vec_size(inner_string), inner_string); - vec_free(inner_string); - - if (has_newlines && !old_inmacro) - ftepp_recursion_footer(ftepp); - - if (resetline && !ftepp->in_macro) { - char lineno[128]; - util_snprintf(lineno, 128, "\n#pragma line(%lu)\n", (unsigned long)(old_lexer->sline)); - ftepp_out(ftepp, lineno, false); - } - - old_string = ftepp->output_string; -cleanup: - ftepp->lex = old_lexer; - ftepp->output_string = old_string; - return retval; -} - -static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro) -{ - size_t o; - macroparam *params = NULL; - bool retval = true; - size_t paramline; - - if (!macro->has_params) { - if (!ftepp_macro_expand(ftepp, macro, NULL, false)) - return false; - ftepp_next(ftepp); - return true; - } - ftepp_next(ftepp); - - if (!ftepp_skipallwhite(ftepp)) - return false; - - if (ftepp->token != '(') { - ftepp_error(ftepp, "expected macro parameters in parenthesis"); - return false; - } - - ftepp_next(ftepp); - paramline = ftepp->lex->sline; - if (!ftepp_macro_call_params(ftepp, ¶ms)) - return false; - - if ( vec_size(params) < vec_size(macro->params) || - (vec_size(params) > vec_size(macro->params) && !macro->variadic) ) - { - ftepp_error(ftepp, "macro %s expects%s %u paramteters, %u provided", macro->name, - (macro->variadic ? " at least" : ""), - (unsigned int)vec_size(macro->params), - (unsigned int)vec_size(params)); - retval = false; - goto cleanup; - } - - if (!ftepp_macro_expand(ftepp, macro, params, (paramline != ftepp->lex->sline))) - retval = false; - ftepp_next(ftepp); - -cleanup: - for (o = 0; o < vec_size(params); ++o) - macroparam_clean(¶ms[o]); - vec_free(params); - return retval; -} - -/** - * #if - the FTEQCC way: - * defined(FOO) => true if FOO was #defined regardless of parameters or contents - * => True if the number is not 0 - * ! => True if the factor yields false - * !! => ERROR on 2 or more unary nots - * => becomes the macro's FIRST token regardless of parameters - * && => True if both expressions are true - * || => True if either expression is true - * => False - * => False (remember for macros the rule applies instead) - * Unary + and - are weird and wrong in fteqcc so we don't allow them - * parenthesis in expressions are allowed - * parameter lists on macros are errors - * No mathematical calculations are executed - */ -static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out); -static bool ftepp_if_op(ftepp_t *ftepp) -{ - ftepp->lex->flags.noops = false; - ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - ftepp->lex->flags.noops = true; - return true; -} -static bool ftepp_if_value(ftepp_t *ftepp, bool *out, double *value_out) -{ - ppmacro *macro; - bool wasnot = false; - bool wasneg = false; - - if (!ftepp_skipspace(ftepp)) - return false; - - while (ftepp->token == '!') { - wasnot = true; - ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - } - - if (ftepp->token == TOKEN_OPERATOR && !strcmp(ftepp_tokval(ftepp), "-")) - { - wasneg = true; - ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - } - - switch (ftepp->token) { - case TOKEN_IDENT: - case TOKEN_TYPENAME: - case TOKEN_KEYWORD: - if (!strcmp(ftepp_tokval(ftepp), "defined")) { - ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - if (ftepp->token != '(') { - ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis"); - return false; - } - ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - if (ftepp->token != TOKEN_IDENT && - ftepp->token != TOKEN_TYPENAME && - ftepp->token != TOKEN_KEYWORD) - { - ftepp_error(ftepp, "defined() used on an unexpected token type"); - return false; - } - macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); - *out = !!macro; - ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - if (ftepp->token != ')') { - ftepp_error(ftepp, "expected closing paren"); - return false; - } - break; - } - - macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); - if (!macro || !vec_size(macro->output)) { - *out = false; - *value_out = 0; - } else { - /* This does not expand recursively! */ - switch (macro->output[0]->token) { - case TOKEN_INTCONST: - *value_out = macro->output[0]->constval.i; - *out = !!(macro->output[0]->constval.i); - break; - case TOKEN_FLOATCONST: - *value_out = macro->output[0]->constval.f; - *out = !!(macro->output[0]->constval.f); - break; - default: - *out = false; - break; - } - } - break; - case TOKEN_STRINGCONST: - *value_out = 0; - *out = false; - break; - case TOKEN_INTCONST: - *value_out = ftepp->lex->tok.constval.i; - *out = !!(ftepp->lex->tok.constval.i); - break; - case TOKEN_FLOATCONST: - *value_out = ftepp->lex->tok.constval.f; - *out = !!(ftepp->lex->tok.constval.f); - break; - - case '(': - ftepp_next(ftepp); - if (!ftepp_if_expr(ftepp, out, value_out)) - return false; - if (ftepp->token != ')') { - ftepp_error(ftepp, "expected closing paren in #if expression"); - return false; - } - break; - - default: - ftepp_error(ftepp, "junk in #if: `%s` ...", ftepp_tokval(ftepp)); - if (OPTS_OPTION_BOOL(OPTION_DEBUG)) - ftepp_error(ftepp, "internal: token %i\n", ftepp->token); - return false; - } - if (wasneg) - *value_out = -*value_out; - if (wasnot) { - *out = !*out; - *value_out = (*out ? 1 : 0); - } - return true; -} - -/* -static bool ftepp_if_nextvalue(ftepp_t *ftepp, bool *out, double *value_out) -{ - if (!ftepp_next(ftepp)) - return false; - return ftepp_if_value(ftepp, out, value_out); -} -*/ - -static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out) -{ - if (!ftepp_if_value(ftepp, out, value_out)) - return false; - - if (!ftepp_if_op(ftepp)) - return false; - - if (ftepp->token == ')' || ftepp->token != TOKEN_OPERATOR) - return true; - - /* FTEQCC is all right-associative and no precedence here */ - if (!strcmp(ftepp_tokval(ftepp), "&&") || - !strcmp(ftepp_tokval(ftepp), "||")) - { - bool next = false; - char opc = ftepp_tokval(ftepp)[0]; - double nextvalue; - - (void)nextvalue; - if (!ftepp_next(ftepp)) - return false; - if (!ftepp_if_expr(ftepp, &next, &nextvalue)) - return false; - - if (opc == '&') - *out = *out && next; - else - *out = *out || next; - - *value_out = (*out ? 1 : 0); - return true; - } - else if (!strcmp(ftepp_tokval(ftepp), "==") || - !strcmp(ftepp_tokval(ftepp), "!=") || - !strcmp(ftepp_tokval(ftepp), ">=") || - !strcmp(ftepp_tokval(ftepp), "<=") || - !strcmp(ftepp_tokval(ftepp), ">") || - !strcmp(ftepp_tokval(ftepp), "<")) - { - bool next = false; - const char opc0 = ftepp_tokval(ftepp)[0]; - const char opc1 = ftepp_tokval(ftepp)[1]; - double other; - - if (!ftepp_next(ftepp)) - return false; - if (!ftepp_if_expr(ftepp, &next, &other)) - return false; - - if (opc0 == '=') - *out = (*value_out == other); - else if (opc0 == '!') - *out = (*value_out != other); - else if (opc0 == '>') { - if (opc1 == '=') *out = (*value_out >= other); - else *out = (*value_out > other); - } - else if (opc0 == '<') { - if (opc1 == '=') *out = (*value_out <= other); - else *out = (*value_out < other); - } - *value_out = (*out ? 1 : 0); - - return true; - } - else { - ftepp_error(ftepp, "junk after #if"); - return false; - } -} - -static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond) -{ - bool result = false; - double dummy = 0; - - memset(cond, 0, sizeof(*cond)); - (void)ftepp_next(ftepp); - - if (!ftepp_skipspace(ftepp)) - return false; - if (ftepp->token == TOKEN_EOL) { - ftepp_error(ftepp, "expected expression for #if-directive"); - return false; - } - - if (!ftepp_if_expr(ftepp, &result, &dummy)) - return false; - - cond->on = result; - return true; -} - -/** - * ifdef is rather simple - */ -static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond) -{ - ppmacro *macro; - memset(cond, 0, sizeof(*cond)); - (void)ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - - switch (ftepp->token) { - case TOKEN_IDENT: - case TOKEN_TYPENAME: - case TOKEN_KEYWORD: - macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); - break; - default: - ftepp_error(ftepp, "expected macro name"); - return false; - } - - (void)ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - /* relaxing this condition - if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) { - ftepp_error(ftepp, "stray tokens after #ifdef"); - return false; - } - */ - cond->on = !!macro; - return true; -} - -/** - * undef is also simple - */ -static bool ftepp_undef(ftepp_t *ftepp) -{ - (void)ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - - if (ftepp->output_on) { - switch (ftepp->token) { - case TOKEN_IDENT: - case TOKEN_TYPENAME: - case TOKEN_KEYWORD: - ftepp_macro_delete(ftepp, ftepp_tokval(ftepp)); - break; - default: - ftepp_error(ftepp, "expected macro name"); - return false; - } - } - - (void)ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - /* relaxing this condition - if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) { - ftepp_error(ftepp, "stray tokens after #ifdef"); - return false; - } - */ - return true; -} - -/* Special unescape-string function which skips a leading quote - * and stops at a quote, not just at \0 - */ -static void unescape(const char *str, char *out) { - ++str; - while (*str && *str != '"') { - if (*str == '\\') { - ++str; - switch (*str) { - case '\\': *out++ = *str; break; - case '"': *out++ = *str; break; - case 'a': *out++ = '\a'; break; - case 'b': *out++ = '\b'; break; - case 'r': *out++ = '\r'; break; - case 'n': *out++ = '\n'; break; - case 't': *out++ = '\t'; break; - case 'f': *out++ = '\f'; break; - case 'v': *out++ = '\v'; break; - default: - *out++ = '\\'; - *out++ = *str; - break; - } - ++str; - continue; - } - - *out++ = *str++; - } - *out = 0; -} - -static char *ftepp_include_find_path(const char *file, const char *pathfile) -{ - fs_file_t *fp; - char *filename = NULL; - const char *last_slash; - size_t len; - - if (!pathfile) - return NULL; - - last_slash = strrchr(pathfile, '/'); - - if (last_slash) { - len = last_slash - pathfile; - memcpy(vec_add(filename, len), pathfile, len); - vec_push(filename, '/'); - } - - len = strlen(file); - memcpy(vec_add(filename, len+1), file, len); - vec_last(filename) = 0; - - fp = fs_file_open(filename, "rb"); - if (fp) { - fs_file_close(fp); - return filename; - } - vec_free(filename); - return NULL; -} - -static char *ftepp_include_find(ftepp_t *ftepp, const char *file) -{ - char *filename = NULL; - - filename = ftepp_include_find_path(file, ftepp->includename); - if (!filename) - filename = ftepp_include_find_path(file, ftepp->itemname); - return filename; -} - -static bool ftepp_directive_warning(ftepp_t *ftepp) { - char *message = NULL; - - if (!ftepp_skipspace(ftepp)) - return false; - - /* handle the odd non string constant case so it works like C */ - if (ftepp->token != TOKEN_STRINGCONST) { - bool store = false; - vec_append(message, 8, "#warning"); - ftepp_next(ftepp); - while (ftepp->token != TOKEN_EOL) { - vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp)); - ftepp_next(ftepp); - } - vec_push(message, '\0'); - if (ftepp->output_on) - store = ftepp_warn(ftepp, WARN_CPP, message); - else - store = false; - vec_free(message); - return store; - } - - if (!ftepp->output_on) - return false; - - unescape (ftepp_tokval(ftepp), ftepp_tokval(ftepp)); - return ftepp_warn(ftepp, WARN_CPP, "#warning %s", ftepp_tokval(ftepp)); -} - -static void ftepp_directive_error(ftepp_t *ftepp) { - char *message = NULL; - - if (!ftepp_skipspace(ftepp)) - return; - - /* handle the odd non string constant case so it works like C */ - if (ftepp->token != TOKEN_STRINGCONST) { - vec_append(message, 6, "#error"); - ftepp_next(ftepp); - while (ftepp->token != TOKEN_EOL) { - vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp)); - ftepp_next(ftepp); - } - vec_push(message, '\0'); - if (ftepp->output_on) - ftepp_error(ftepp, message); - vec_free(message); - return; - } - - if (!ftepp->output_on) - return; - - unescape (ftepp_tokval(ftepp), ftepp_tokval(ftepp)); - ftepp_error(ftepp, "#error %s", ftepp_tokval(ftepp)); -} - -static void ftepp_directive_message(ftepp_t *ftepp) { - char *message = NULL; - - if (!ftepp_skipspace(ftepp)) - return; - - /* handle the odd non string constant case so it works like C */ - if (ftepp->token != TOKEN_STRINGCONST) { - vec_append(message, 8, "#message"); - ftepp_next(ftepp); - while (ftepp->token != TOKEN_EOL) { - vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp)); - ftepp_next(ftepp); - } - vec_push(message, '\0'); - if (ftepp->output_on) - con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message", message); - vec_free(message); - return; - } - - if (!ftepp->output_on) - return; - - unescape (ftepp_tokval(ftepp), ftepp_tokval(ftepp)); - con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message", ftepp_tokval(ftepp)); -} - -/** - * Include a file. - * FIXME: do we need/want a -I option? - * FIXME: what about when dealing with files in subdirectories coming from a progs.src? - */ -static bool ftepp_include(ftepp_t *ftepp) -{ - lex_file *old_lexer = ftepp->lex; - lex_file *inlex; - lex_ctx_t ctx; - char lineno[128]; - char *filename; - char *parsename = NULL; - char *old_includename; - - (void)ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - - if (ftepp->token != TOKEN_STRINGCONST) { - ppmacro *macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); - if (macro) { - char *backup = ftepp->output_string; - ftepp->output_string = NULL; - if (ftepp_macro_expand(ftepp, macro, NULL, true)) { - parsename = util_strdup(ftepp->output_string); - vec_free(ftepp->output_string); - ftepp->output_string = backup; - } else { - ftepp->output_string = backup; - ftepp_error(ftepp, "expected filename to include"); - return false; - } - } else if (OPTS_FLAG(FTEPP_PREDEFS)) { - /* Well it could be a predefine like __LINE__ */ - char *(*predef)(ftepp_t*) = ftepp_predef(ftepp_tokval(ftepp)); - if (predef) { - parsename = predef(ftepp); - } else { - ftepp_error(ftepp, "expected filename to include"); - return false; - } - } - } - - if (!ftepp->output_on) { - (void)ftepp_next(ftepp); - return true; - } - - if (parsename) - unescape(parsename, parsename); - else { - char *tokval = ftepp_tokval(ftepp); - unescape(tokval, tokval); - parsename = util_strdup(tokval); - } - - ctx = ftepp_ctx(ftepp); - ftepp_out(ftepp, "\n#pragma file(", false); - ftepp_out(ftepp, parsename, false); - ftepp_out(ftepp, ")\n#pragma line(1)\n", false); - - filename = ftepp_include_find(ftepp, parsename); - if (!filename) { - ftepp_error(ftepp, "failed to open include file `%s`", parsename); - mem_d(parsename); - return false; - } - mem_d(parsename); - inlex = lex_open(filename); - if (!inlex) { - ftepp_error(ftepp, "open failed on include file `%s`", filename); - vec_free(filename); - return false; - } - ftepp->lex = inlex; - old_includename = ftepp->includename; - ftepp->includename = filename; - if (!ftepp_preprocess(ftepp)) { - vec_free(ftepp->includename); - ftepp->includename = old_includename; - lex_close(ftepp->lex); - ftepp->lex = old_lexer; - return false; - } - vec_free(ftepp->includename); - ftepp->includename = old_includename; - lex_close(ftepp->lex); - ftepp->lex = old_lexer; - - ftepp_out(ftepp, "\n#pragma file(", false); - ftepp_out(ftepp, ctx.file, false); - util_snprintf(lineno, sizeof(lineno), ")\n#pragma line(%lu)\n", (unsigned long)(ctx.line+1)); - ftepp_out(ftepp, lineno, false); - - /* skip the line */ - (void)ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - if (ftepp->token != TOKEN_EOL) { - ftepp_error(ftepp, "stray tokens after #include"); - return false; - } - (void)ftepp_next(ftepp); - - return true; -} - -/* Basic structure handlers */ -static bool ftepp_else_allowed(ftepp_t *ftepp) -{ - if (!vec_size(ftepp->conditions)) { - ftepp_error(ftepp, "#else without #if"); - return false; - } - if (vec_last(ftepp->conditions).had_else) { - ftepp_error(ftepp, "multiple #else for a single #if"); - return false; - } - return true; -} - -static GMQCC_INLINE void ftepp_inmacro(ftepp_t *ftepp, const char *hash) { - if (ftepp->in_macro) - (void)!ftepp_warn(ftepp, WARN_DIRECTIVE_INMACRO, "`#%s` directive in macro", hash); -} - -static bool ftepp_hash(ftepp_t *ftepp) -{ - ppcondition cond; - ppcondition *pc; - - lex_ctx_t ctx = ftepp_ctx(ftepp); - - if (!ftepp_skipspace(ftepp)) - return false; - - switch (ftepp->token) { - case TOKEN_KEYWORD: - case TOKEN_IDENT: - case TOKEN_TYPENAME: - if (!strcmp(ftepp_tokval(ftepp), "define")) { - ftepp_inmacro(ftepp, "define"); - return ftepp_define(ftepp); - } - else if (!strcmp(ftepp_tokval(ftepp), "undef")) { - ftepp_inmacro(ftepp, "undef"); - return ftepp_undef(ftepp); - } - else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) { - ftepp_inmacro(ftepp, "ifdef"); - if (!ftepp_ifdef(ftepp, &cond)) - return false; - cond.was_on = cond.on; - vec_push(ftepp->conditions, cond); - ftepp->output_on = ftepp->output_on && cond.on; - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) { - ftepp_inmacro(ftepp, "ifndef"); - if (!ftepp_ifdef(ftepp, &cond)) - return false; - cond.on = !cond.on; - cond.was_on = cond.on; - vec_push(ftepp->conditions, cond); - ftepp->output_on = ftepp->output_on && cond.on; - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) { - ftepp_inmacro(ftepp, "elifdef"); - if (!ftepp_else_allowed(ftepp)) - return false; - if (!ftepp_ifdef(ftepp, &cond)) - return false; - pc = &vec_last(ftepp->conditions); - pc->on = !pc->was_on && cond.on; - pc->was_on = pc->was_on || pc->on; - ftepp_update_output_condition(ftepp); - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) { - ftepp_inmacro(ftepp, "elifndef"); - if (!ftepp_else_allowed(ftepp)) - return false; - if (!ftepp_ifdef(ftepp, &cond)) - return false; - cond.on = !cond.on; - pc = &vec_last(ftepp->conditions); - pc->on = !pc->was_on && cond.on; - pc->was_on = pc->was_on || pc->on; - ftepp_update_output_condition(ftepp); - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "elif")) { - ftepp_inmacro(ftepp, "elif"); - if (!ftepp_else_allowed(ftepp)) - return false; - if (!ftepp_if(ftepp, &cond)) - return false; - pc = &vec_last(ftepp->conditions); - pc->on = !pc->was_on && cond.on; - pc->was_on = pc->was_on || pc->on; - ftepp_update_output_condition(ftepp); - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "if")) { - ftepp_inmacro(ftepp, "if"); - if (!ftepp_if(ftepp, &cond)) - return false; - cond.was_on = cond.on; - vec_push(ftepp->conditions, cond); - ftepp->output_on = ftepp->output_on && cond.on; - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "else")) { - ftepp_inmacro(ftepp, "else"); - if (!ftepp_else_allowed(ftepp)) - return false; - pc = &vec_last(ftepp->conditions); - pc->on = !pc->was_on; - pc->had_else = true; - ftepp_next(ftepp); - ftepp_update_output_condition(ftepp); - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "endif")) { - ftepp_inmacro(ftepp, "endif"); - if (!vec_size(ftepp->conditions)) { - ftepp_error(ftepp, "#endif without #if"); - return false; - } - vec_pop(ftepp->conditions); - ftepp_next(ftepp); - ftepp_update_output_condition(ftepp); - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "include")) { - ftepp_inmacro(ftepp, "include"); - return ftepp_include(ftepp); - } - else if (!strcmp(ftepp_tokval(ftepp), "pragma")) { - ftepp_out(ftepp, "#", false); - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "warning")) { - ftepp_directive_warning(ftepp); - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "error")) { - ftepp_directive_error(ftepp); - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "message")) { - ftepp_directive_message(ftepp); - break; - } - else { - if (ftepp->output_on) { - ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp)); - return false; - } else { - ftepp_next(ftepp); - break; - } - } - /* break; never reached */ - default: - ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp)); - return false; - case TOKEN_EOL: - ftepp_errorat(ftepp, ctx, "empty preprocessor directive"); - return false; - case TOKEN_EOF: - ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp)); - return false; - - /* Builtins! Don't forget the builtins! */ - case TOKEN_INTCONST: - case TOKEN_FLOATCONST: - ftepp_out(ftepp, "#", false); - return true; - } - if (!ftepp_skipspace(ftepp)) - return false; - return true; -} - -static bool ftepp_preprocess(ftepp_t *ftepp) -{ - ppmacro *macro; - bool newline = true; - - /* predef stuff */ - char *expand = NULL; - - ftepp->lex->flags.preprocessing = true; - ftepp->lex->flags.mergelines = false; - ftepp->lex->flags.noops = true; - - ftepp_next(ftepp); - do - { - if (ftepp->token >= TOKEN_EOF) - break; -#if 0 - newline = true; -#endif - - switch (ftepp->token) { - case TOKEN_KEYWORD: - case TOKEN_IDENT: - case TOKEN_TYPENAME: - /* is it a predef? */ - if (OPTS_FLAG(FTEPP_PREDEFS)) { - char *(*predef)(ftepp_t*) = ftepp_predef(ftepp_tokval(ftepp)); - if (predef) { - expand = predef(ftepp); - ftepp_out (ftepp, expand, false); - ftepp_next(ftepp); - - mem_d(expand); - break; - } - } - - if (ftepp->output_on) - macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); - else - macro = NULL; - - if (!macro) { - ftepp_out(ftepp, ftepp_tokval(ftepp), false); - ftepp_next(ftepp); - break; - } - if (!ftepp_macro_call(ftepp, macro)) - ftepp->token = TOKEN_ERROR; - break; - case '#': - if (!newline) { - ftepp_out(ftepp, ftepp_tokval(ftepp), false); - ftepp_next(ftepp); - break; - } - ftepp->lex->flags.mergelines = true; - if (ftepp_next(ftepp) >= TOKEN_EOF) { - ftepp_error(ftepp, "error in preprocessor directive"); - ftepp->token = TOKEN_ERROR; - break; - } - if (!ftepp_hash(ftepp)) - ftepp->token = TOKEN_ERROR; - ftepp->lex->flags.mergelines = false; - break; - case TOKEN_EOL: - newline = true; - ftepp_out(ftepp, "\n", true); - ftepp_next(ftepp); - break; - case TOKEN_WHITE: - /* same as default but don't set newline=false */ - ftepp_out(ftepp, ftepp_tokval(ftepp), true); - ftepp_next(ftepp); - break; - default: - newline = false; - ftepp_out(ftepp, ftepp_tokval(ftepp), false); - ftepp_next(ftepp); - break; - } - } while (!ftepp->errors && ftepp->token < TOKEN_EOF); - - /* force a 0 at the end but don't count it as added to the output */ - vec_push(ftepp->output_string, 0); - vec_shrinkby(ftepp->output_string, 1); - - return (ftepp->token == TOKEN_EOF); -} - -/* Like in parser.c - files keep the previous state so we have one global - * preprocessor. Except here we will want to warn about dangling #ifs. - */ -static bool ftepp_preprocess_done(ftepp_t *ftepp) -{ - bool retval = true; - if (vec_size(ftepp->conditions)) { - if (ftepp_warn(ftepp, WARN_MULTIFILE_IF, "#if spanning multiple files, is this intended?")) - retval = false; - } - lex_close(ftepp->lex); - ftepp->lex = NULL; - if (ftepp->itemname) { - mem_d(ftepp->itemname); - ftepp->itemname = NULL; - } - return retval; -} - -bool ftepp_preprocess_file(ftepp_t *ftepp, const char *filename) -{ - ftepp->lex = lex_open(filename); - ftepp->itemname = util_strdup(filename); - if (!ftepp->lex) { - con_out("failed to open file \"%s\"\n", filename); - return false; - } - if (!ftepp_preprocess(ftepp)) - return false; - return ftepp_preprocess_done(ftepp); -} - -bool ftepp_preprocess_string(ftepp_t *ftepp, const char *name, const char *str) -{ - ftepp->lex = lex_open_string(str, strlen(str), name); - ftepp->itemname = util_strdup(name); - if (!ftepp->lex) { - con_out("failed to create lexer for string \"%s\"\n", name); - return false; - } - if (!ftepp_preprocess(ftepp)) - return false; - return ftepp_preprocess_done(ftepp); -} - - -void ftepp_add_macro(ftepp_t *ftepp, const char *name, const char *value) { - char *create = NULL; - - /* use saner path for empty macros */ - if (!value) { - ftepp_add_define(ftepp, "__builtin__", name); - return; - } - - vec_append(create, 8, "#define "); - vec_append(create, strlen(name), name); - vec_push (create, ' '); - vec_append(create, strlen(value), value); - vec_push (create, 0); - - ftepp_preprocess_string(ftepp, "__builtin__", create); - vec_free (create); -} - -ftepp_t *ftepp_create() -{ - ftepp_t *ftepp; - char minor[32]; - char major[32]; - size_t i; - - ftepp = ftepp_new(); - if (!ftepp) - return NULL; - - memset(minor, 0, sizeof(minor)); - memset(major, 0, sizeof(major)); - - /* set the right macro based on the selected standard */ - ftepp_add_define(ftepp, NULL, "GMQCC"); - if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) { - ftepp_add_define(ftepp, NULL, "__STD_FTEQCC__"); - /* 1.00 */ - major[0] = '"'; - major[1] = '1'; - major[2] = '"'; - - minor[0] = '"'; - minor[1] = '0'; - minor[2] = '"'; - } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) { - ftepp_add_define(ftepp, NULL, "__STD_GMQCC__"); - util_snprintf(major, 32, "\"%d\"", GMQCC_VERSION_MAJOR); - util_snprintf(minor, 32, "\"%d\"", GMQCC_VERSION_MINOR); - } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCCX) { - ftepp_add_define(ftepp, NULL, "__STD_QCCX__"); - util_snprintf(major, 32, "\"%d\"", GMQCC_VERSION_MAJOR); - util_snprintf(minor, 32, "\"%d\"", GMQCC_VERSION_MINOR); - } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { - ftepp_add_define(ftepp, NULL, "__STD_QCC__"); - /* 1.0 */ - major[0] = '"'; - major[1] = '1'; - major[2] = '"'; - - minor[0] = '"'; - minor[1] = '0'; - minor[2] = '"'; - } - - ftepp_add_macro(ftepp, "__STD_VERSION_MINOR__", minor); - ftepp_add_macro(ftepp, "__STD_VERSION_MAJOR__", major); - - /* - * We're going to just make __NULL__ nil, which works for 60% of the - * cases of __NULL_ for fteqcc. - */ - ftepp_add_macro(ftepp, "__NULL__", "nil"); - - /* add all the math constants if they can be */ - if (OPTS_FLAG(FTEPP_MATHDEFS)) { - for (i = 0; i < GMQCC_ARRAY_COUNT(ftepp_math_constants); i++) - if (!ftepp_macro_find(ftepp, ftepp_math_constants[i][0])) - ftepp_add_macro(ftepp, ftepp_math_constants[i][0], ftepp_math_constants[i][1]); - } - - return ftepp; -} - -void ftepp_add_define(ftepp_t *ftepp, const char *source, const char *name) -{ - ppmacro *macro; - lex_ctx_t ctx = { "__builtin__", 0, 0 }; - ctx.file = source; - macro = ppmacro_new(ctx, name); - /*vec_push(ftepp->macros, macro);*/ - util_htset(ftepp->macros, name, macro); -} - -const char *ftepp_get(ftepp_t *ftepp) -{ - return ftepp->output_string; -} - -void ftepp_flush(ftepp_t *ftepp) -{ - ftepp_flush_do(ftepp); -} - -void ftepp_finish(ftepp_t *ftepp) -{ - if (!ftepp) - return; - ftepp_delete(ftepp); -} diff --git a/ftepp.cpp b/ftepp.cpp new file mode 100644 index 0000000..f1d5f36 --- /dev/null +++ b/ftepp.cpp @@ -0,0 +1,1947 @@ +#include +#include +#include + +#include "gmqcc.h" +#include "lexer.h" + +#define HT_MACROS 1024 + +struct ppcondition { + bool on; + bool was_on; + bool had_else; +}; + +struct pptoken { + int token; + char *value; + /* a copy from the lexer */ + union { + vec3_t v; + int i; + double f; + int t; /* type */ + } constval; +}; + +struct ppmacro { + lex_ctx_t ctx; + char *name; + char **params; + /* yes we need an extra flag since `#define FOO x` is not the same as `#define FOO() x` */ + bool has_params; + bool variadic; + pptoken **output; +}; + +struct ftepp_t { + lex_file *lex; + int token; + unsigned int errors; + bool output_on; + ppcondition *conditions; + ht macros; /* hashtable */ + char *output_string; + char *itemname; + char *includename; + bool in_macro; + uint32_t predef_countval; + uint32_t predef_randval; +}; + +/* __DATE__ */ +static char *ftepp_predef_date(ftepp_t *context) { + const struct tm *itime = nullptr; + char *value = (char*)mem_a(82); + time_t rtime; + + (void)context; + + time (&rtime); + itime = util_localtime(&rtime); + strftime(value, 82, "\"%b %d %Y\"", itime); + + return value; +} + +/* __TIME__ */ +static char *ftepp_predef_time(ftepp_t *context) { + const struct tm *itime = nullptr; + char *value = (char*)mem_a(82); + time_t rtime; + + (void)context; + + time (&rtime); + itime = util_localtime(&rtime); + strftime(value, 82, "\"%X\"", itime); + + return value; +} + +/* __LINE__ */ +static char *ftepp_predef_line(ftepp_t *context) { + char *value; + + util_asprintf(&value, "%d", (int)context->lex->line); + return value; +} +/* __FILE__ */ +static char *ftepp_predef_file(ftepp_t *context) { + size_t length = strlen(context->lex->name) + 3; /* two quotes and a terminator */ + char *value = (char*)mem_a(length); + + util_snprintf(value, length, "\"%s\"", context->lex->name); + return value; +} +/* __COUNTER_LAST__ */ +static char *ftepp_predef_counterlast(ftepp_t *context) { + char *value; + util_asprintf(&value, "%u", context->predef_countval); + return value; +} +/* __COUNTER__ */ +static char *ftepp_predef_counter(ftepp_t *context) { + char *value; + + context->predef_countval ++; + util_asprintf(&value, "%u", context->predef_countval); + + return value; +} +/* __RANDOM__ */ +static char *ftepp_predef_random(ftepp_t *context) { + char *value; + + context->predef_randval = (util_rand() % 0xFF) + 1; + util_asprintf(&value, "%u", context->predef_randval); + return value; +} +/* __RANDOM_LAST__ */ +static char *ftepp_predef_randomlast(ftepp_t *context) { + char *value; + + util_asprintf(&value, "%u", context->predef_randval); + return value; +} +/* __TIMESTAMP__ */ +static char *ftepp_predef_timestamp(ftepp_t *context) { + struct stat finfo; + const char *find; + char *value; + size_t size; + + if (stat(context->lex->name, &finfo)) + return util_strdup("\"\""); + + find = util_ctime(&finfo.st_mtime); + value = (char*)mem_a(strlen(find) + 1); + memcpy(&value[1], find, (size = strlen(find)) - 1); + + value[0] = '"'; + value[size] = '"'; + + return value; +} + +struct ftepp_predef_t { + const char *name; + char *(*func)(ftepp_t *); +}; + +static const ftepp_predef_t ftepp_predefs[] = { + { "__LINE__", &ftepp_predef_line }, + { "__FILE__", &ftepp_predef_file }, + { "__COUNTER__", &ftepp_predef_counter }, + { "__COUNTER_LAST__", &ftepp_predef_counterlast }, + { "__RANDOM__", &ftepp_predef_random }, + { "__RANDOM_LAST__", &ftepp_predef_randomlast }, + { "__DATE__", &ftepp_predef_date }, + { "__TIME__", &ftepp_predef_time }, + { "__TIME_STAMP__", &ftepp_predef_timestamp } +}; + +static GMQCC_INLINE size_t ftepp_predef_index(const char *name) { + /* no hashtable here, we simply check for one to exist the naive way */ + size_t i; + for(i = 1; i < GMQCC_ARRAY_COUNT(ftepp_predefs) + 1; i++) + if (!strcmp(ftepp_predefs[i-1].name, name)) + return i; + return 0; +} + +bool ftepp_predef_exists(const char *name); +bool ftepp_predef_exists(const char *name) { + return ftepp_predef_index(name) != 0; +} + +/* singleton because we're allowed */ +static GMQCC_INLINE char *(*ftepp_predef(const char *name))(ftepp_t *context) { + size_t i = ftepp_predef_index(name); + return (i != 0) ? ftepp_predefs[i-1].func : nullptr; +} + +#define ftepp_tokval(f) ((f)->lex->tok.value) +#define ftepp_ctx(f) ((f)->lex->tok.ctx) + +static void ftepp_errorat(ftepp_t *ftepp, lex_ctx_t ctx, const char *fmt, ...) +{ + va_list ap; + + ftepp->errors++; + + va_start(ap, fmt); + con_cvprintmsg(ctx, LVL_ERROR, "error", fmt, ap); + va_end(ap); +} + +static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...) +{ + va_list ap; + + ftepp->errors++; + + va_start(ap, fmt); + con_cvprintmsg(ftepp->lex->tok.ctx, LVL_ERROR, "error", fmt, ap); + va_end(ap); +} + +static bool GMQCC_WARN ftepp_warn(ftepp_t *ftepp, int warntype, const char *fmt, ...) +{ + bool r; + va_list ap; + + va_start(ap, fmt); + r = vcompile_warning(ftepp->lex->tok.ctx, warntype, fmt, ap); + va_end(ap); + return r; +} + +static pptoken *pptoken_make(ftepp_t *ftepp) +{ + pptoken *token = (pptoken*)mem_a(sizeof(pptoken)); + token->token = ftepp->token; + token->value = util_strdup(ftepp_tokval(ftepp)); + memcpy(&token->constval, &ftepp->lex->tok.constval, sizeof(token->constval)); + return token; +} + +static GMQCC_INLINE void pptoken_delete(pptoken *self) +{ + mem_d(self->value); + mem_d(self); +} + +static ppmacro *ppmacro_new(lex_ctx_t ctx, const char *name) +{ + ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro)); + + (void)ctx; + memset(macro, 0, sizeof(*macro)); + macro->name = util_strdup(name); + return macro; +} + +static void ppmacro_delete(ppmacro *self) +{ + size_t i; + for (i = 0; i < vec_size(self->params); ++i) + mem_d(self->params[i]); + vec_free(self->params); + for (i = 0; i < vec_size(self->output); ++i) + pptoken_delete(self->output[i]); + vec_free(self->output); + mem_d(self->name); + mem_d(self); +} + +static ftepp_t* ftepp_new(void) +{ + ftepp_t *ftepp; + + ftepp = (ftepp_t*)mem_a(sizeof(*ftepp)); + memset(ftepp, 0, sizeof(*ftepp)); + + ftepp->macros = util_htnew(HT_MACROS); + ftepp->output_on = true; + ftepp->predef_countval = 0; + ftepp->predef_randval = 0; + + return ftepp; +} + +static GMQCC_INLINE void ftepp_flush_do(ftepp_t *self) +{ + vec_free(self->output_string); +} + +static void ftepp_delete(ftepp_t *self) +{ + ftepp_flush_do(self); + if (self->itemname) + mem_d(self->itemname); + if (self->includename) + vec_free(self->includename); + + util_htrem(self->macros, (void (*)(void*))&ppmacro_delete); + + vec_free(self->conditions); + if (self->lex) + lex_close(self->lex); + mem_d(self); +} + +static void ftepp_out(ftepp_t *ftepp, const char *str, bool ignore_cond) +{ + if (ignore_cond || ftepp->output_on) + { + size_t len; + char *data; + len = strlen(str); + data = vec_add(ftepp->output_string, len); + memcpy(data, str, len); + } +} + +static GMQCC_INLINE void ftepp_update_output_condition(ftepp_t *ftepp) +{ + size_t i; + ftepp->output_on = true; + for (i = 0; i < vec_size(ftepp->conditions); ++i) + ftepp->output_on = ftepp->output_on && ftepp->conditions[i].on; +} + +static GMQCC_INLINE ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name) +{ + return (ppmacro*)util_htget(ftepp->macros, name); +} + +static GMQCC_INLINE void ftepp_macro_delete(ftepp_t *ftepp, const char *name) +{ + util_htrm(ftepp->macros, name, (void (*)(void*))&ppmacro_delete); +} + +static GMQCC_INLINE int ftepp_next(ftepp_t *ftepp) +{ + return (ftepp->token = lex_do(ftepp->lex)); +} + +/* Important: this does not skip newlines! */ +static bool ftepp_skipspace(ftepp_t *ftepp) +{ + if (ftepp->token != TOKEN_WHITE) + return true; + while (ftepp_next(ftepp) == TOKEN_WHITE) {} + if (ftepp->token >= TOKEN_EOF) { + ftepp_error(ftepp, "unexpected end of preprocessor directive"); + return false; + } + return true; +} + +/* this one skips EOLs as well */ +static bool ftepp_skipallwhite(ftepp_t *ftepp) +{ + if (ftepp->token != TOKEN_WHITE && ftepp->token != TOKEN_EOL) + return true; + do { + ftepp_next(ftepp); + } while (ftepp->token == TOKEN_WHITE || ftepp->token == TOKEN_EOL); + if (ftepp->token >= TOKEN_EOF) { + ftepp_error(ftepp, "unexpected end of preprocessor directive"); + return false; + } + return true; +} + +/** + * The huge macro parsing code... + */ +static bool ftepp_define_params(ftepp_t *ftepp, ppmacro *macro) +{ + do { + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token == ')') + break; + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + vec_push(macro->params, util_strdup(ftepp_tokval(ftepp))); + break; + case TOKEN_DOTS: + macro->variadic = true; + break; + default: + ftepp_error(ftepp, "unexpected token in parameter list"); + return false; + } + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (macro->variadic && ftepp->token != ')') { + ftepp_error(ftepp, "cannot have parameters after the variadic parameters"); + return false; + } + } while (ftepp->token == ','); + + if (ftepp->token != ')') { + ftepp_error(ftepp, "expected closing paren after macro parameter list"); + return false; + } + ftepp_next(ftepp); + /* skipspace happens in ftepp_define */ + return true; +} + +static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro) +{ + pptoken *ptok; + while (ftepp->token != TOKEN_EOL && ftepp->token < TOKEN_EOF) { + bool subscript = false; + size_t index = 0; + if (macro->variadic && !strcmp(ftepp_tokval(ftepp), "__VA_ARGS__")) { + subscript = !!(ftepp_next(ftepp) == '#'); + + if (subscript && ftepp_next(ftepp) != '#') { + ftepp_error(ftepp, "expected `##` in __VA_ARGS__ for subscripting"); + return false; + } else if (subscript) { + if (ftepp_next(ftepp) == '[') { + if (ftepp_next(ftepp) != TOKEN_INTCONST) { + ftepp_error(ftepp, "expected index for __VA_ARGS__ subscript"); + return false; + } + + index = (int)strtol(ftepp_tokval(ftepp), nullptr, 10); + + if (ftepp_next(ftepp) != ']') { + ftepp_error(ftepp, "expected `]` in __VA_ARGS__ subscript"); + return false; + } + + /* + * mark it as an array to be handled later as such and not + * as traditional __VA_ARGS__ + */ + ftepp->token = TOKEN_VA_ARGS_ARRAY; + ptok = pptoken_make(ftepp); + ptok->constval.i = index; + vec_push(macro->output, ptok); + ftepp_next(ftepp); + } else { + ftepp_error(ftepp, "expected `[` for subscripting of __VA_ARGS__"); + return false; + } + } else { + int old = ftepp->token; + ftepp->token = TOKEN_VA_ARGS; + ptok = pptoken_make(ftepp); + vec_push(macro->output, ptok); + ftepp->token = old; + } + } + else if (macro->variadic && !strcmp(ftepp_tokval(ftepp), "__VA_COUNT__")) { + ftepp->token = TOKEN_VA_COUNT; + ptok = pptoken_make(ftepp); + vec_push(macro->output, ptok); + ftepp_next(ftepp); + } else { + ptok = pptoken_make(ftepp); + vec_push(macro->output, ptok); + ftepp_next(ftepp); + } + } + /* recursive expansion can cause EOFs here */ + if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) { + ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file"); + return false; + } + return true; +} + +static const char *ftepp_math_constants[][2] = { + { "M_E", "2.7182818284590452354" }, /* e */ + { "M_LOG2E", "1.4426950408889634074" }, /* log_2 e */ + { "M_LOG10E", "0.43429448190325182765" }, /* log_10 e */ + { "M_LN2", "0.69314718055994530942" }, /* log_e 2 */ + { "M_LN10", "2.30258509299404568402" }, /* log_e 10 */ + { "M_PI", "3.14159265358979323846" }, /* pi */ + { "M_PI_2", "1.57079632679489661923" }, /* pi/2 */ + { "M_PI_4", "0.78539816339744830962" }, /* pi/4 */ + { "M_1_PI", "0.31830988618379067154" }, /* 1/pi */ + { "M_2_PI", "0.63661977236758134308" }, /* 2/pi */ + { "M_2_SQRTPI", "1.12837916709551257390" }, /* 2/sqrt(pi) */ + { "M_SQRT2", "1.41421356237309504880" }, /* sqrt(2) */ + { "M_SQRT1_2", "0.70710678118654752440" }, /* 1/sqrt(2) */ + { "M_TAU", "6.28318530717958647692" } /* pi*2 */ +}; + +static bool ftepp_define(ftepp_t *ftepp) +{ + ppmacro *macro = nullptr; + size_t l = ftepp_ctx(ftepp).line; + size_t i; + bool mathconstant = false; + + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + if (OPTS_FLAG(FTEPP_MATHDEFS)) { + for (i = 0; i < GMQCC_ARRAY_COUNT(ftepp_math_constants); i++) { + if (!strcmp(ftepp_math_constants[i][0], ftepp_tokval(ftepp))) { + mathconstant = true; + break; + } + } + } + + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + + if (OPTS_FLAG(FTEPP_MATHDEFS)) { + /* user defined ones take precedence */ + if (macro && mathconstant) { + ftepp_macro_delete(ftepp, ftepp_tokval(ftepp)); + macro = nullptr; + } + } + + if (macro && ftepp->output_on) { + if (ftepp_warn(ftepp, WARN_CPP, "redefining `%s`", ftepp_tokval(ftepp))) + return false; + ftepp_macro_delete(ftepp, ftepp_tokval(ftepp)); + } + macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp)); + break; + default: + ftepp_error(ftepp, "expected macro name"); + return false; + } + + (void)ftepp_next(ftepp); + + if (ftepp->token == '(') { + macro->has_params = true; + if (!ftepp_define_params(ftepp, macro)) { + ppmacro_delete(macro); + return false; + } + } + + if (!ftepp_skipspace(ftepp)) { + ppmacro_delete(macro); + return false; + } + + if (!ftepp_define_body(ftepp, macro)) { + ppmacro_delete(macro); + return false; + } + + if (ftepp->output_on) + util_htset(ftepp->macros, macro->name, (void*)macro); + else { + ppmacro_delete(macro); + } + + for (; l < ftepp_ctx(ftepp).line; ++l) + ftepp_out(ftepp, "\n", true); + return true; +} + +/** + * When a macro is used we have to handle parameters as well + * as special-concatenation via ## or stringification via # + * + * Note: parenthesis can nest, so FOO((a),b) is valid, but only + * this kind of parens. Curly braces or [] don't count towards the + * paren-level. + */ +struct macroparam { + pptoken **tokens; +}; + +static void macroparam_clean(macroparam *self) +{ + size_t i; + for (i = 0; i < vec_size(self->tokens); ++i) + pptoken_delete(self->tokens[i]); + vec_free(self->tokens); +} + +/* need to leave the last token up */ +static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params) +{ + macroparam *params = nullptr; + pptoken *ptok; + macroparam mp; + size_t parens = 0; + size_t i; + + if (!ftepp_skipallwhite(ftepp)) + return false; + while (ftepp->token != ')') { + mp.tokens = nullptr; + if (!ftepp_skipallwhite(ftepp)) + return false; + while (parens || ftepp->token != ',') { + if (ftepp->token == '(') + ++parens; + else if (ftepp->token == ')') { + if (!parens) + break; + --parens; + } + ptok = pptoken_make(ftepp); + vec_push(mp.tokens, ptok); + if (ftepp_next(ftepp) >= TOKEN_EOF) { + ftepp_error(ftepp, "unexpected end of file in macro call"); + goto on_error; + } + } + vec_push(params, mp); + mp.tokens = nullptr; + if (ftepp->token == ')') + break; + if (ftepp->token != ',') { + ftepp_error(ftepp, "expected closing paren or comma in macro call"); + goto on_error; + } + if (ftepp_next(ftepp) >= TOKEN_EOF) { + ftepp_error(ftepp, "unexpected end of file in macro call"); + goto on_error; + } + } + *out_params = params; + return true; + +on_error: + if (mp.tokens) + macroparam_clean(&mp); + for (i = 0; i < vec_size(params); ++i) + macroparam_clean(¶ms[i]); + vec_free(params); + return false; +} + +static bool macro_params_find(ppmacro *macro, const char *name, size_t *idx) +{ + size_t i; + for (i = 0; i < vec_size(macro->params); ++i) { + if (!strcmp(macro->params[i], name)) { + *idx = i; + return true; + } + } + return false; +} + +static void ftepp_stringify_token(ftepp_t *ftepp, pptoken *token) +{ + char chs[2]; + const char *ch; + chs[1] = 0; + switch (token->token) { + case TOKEN_STRINGCONST: + ch = token->value; + while (*ch) { + /* in preprocessor mode strings already are string, + * so we don't get actual newline bytes here. + * Still need to escape backslashes and quotes. + */ + switch (*ch) { + case '\\': ftepp_out(ftepp, "\\\\", false); break; + case '"': ftepp_out(ftepp, "\\\"", false); break; + default: + chs[0] = *ch; + ftepp_out(ftepp, chs, false); + break; + } + ++ch; + } + break; + /*case TOKEN_WHITE: + ftepp_out(ftepp, " ", false); + break;*/ + case TOKEN_EOL: + ftepp_out(ftepp, "\\n", false); + break; + default: + ftepp_out(ftepp, token->value, false); + break; + } +} + +static void ftepp_stringify(ftepp_t *ftepp, macroparam *param) +{ + size_t i; + ftepp_out(ftepp, "\"", false); + for (i = 0; i < vec_size(param->tokens); ++i) + ftepp_stringify_token(ftepp, param->tokens[i]); + ftepp_out(ftepp, "\"", false); +} + +static void ftepp_recursion_header(ftepp_t *ftepp) +{ + ftepp_out(ftepp, "\n#pragma push(line)\n", false); +} + +static void ftepp_recursion_footer(ftepp_t *ftepp) +{ + ftepp_out(ftepp, "\n#pragma pop(line)\n", false); +} + +static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params, bool resetline); +static void ftepp_param_out(ftepp_t *ftepp, macroparam *param) +{ + size_t i; + pptoken *out; + for (i = 0; i < vec_size(param->tokens); ++i) { + out = param->tokens[i]; + if (out->token == TOKEN_EOL) + ftepp_out(ftepp, "\n", false); + else { + ppmacro *find = ftepp_macro_find(ftepp, out->value); + if (OPTS_FLAG(FTEPP_INDIRECT_EXPANSION) && find && !find->has_params) + ftepp_macro_expand(ftepp, find, nullptr, false); + else + ftepp_out(ftepp, out->value, false); + } + } +} + +static bool ftepp_preprocess(ftepp_t *ftepp); +static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params, bool resetline) +{ + char *buffer = nullptr; + char *old_string = ftepp->output_string; + char *inner_string; + lex_file *old_lexer = ftepp->lex; + size_t vararg_start = vec_size(macro->params); + bool retval = true; + bool has_newlines; + size_t varargs; + + size_t o, pi; + lex_file *inlex; + + bool old_inmacro; + bool strip = false; + + int nextok; + + if (vararg_start < vec_size(params)) + varargs = vec_size(params) - vararg_start; + else + varargs = 0; + + /* really ... */ + if (!vec_size(macro->output)) + return true; + + ftepp->output_string = nullptr; + for (o = 0; o < vec_size(macro->output); ++o) { + pptoken *out = macro->output[o]; + switch (out->token) { + case TOKEN_VA_ARGS: + if (!macro->variadic) { + ftepp_error(ftepp, "internal preprocessor error: TOKEN_VA_ARGS in non-variadic macro"); + vec_free(old_string); + return false; + } + if (!varargs) + break; + + pi = 0; + ftepp_param_out(ftepp, ¶ms[pi + vararg_start]); + for (++pi; pi < varargs; ++pi) { + ftepp_out(ftepp, ", ", false); + ftepp_param_out(ftepp, ¶ms[pi + vararg_start]); + } + break; + + case TOKEN_VA_ARGS_ARRAY: + if ((size_t)out->constval.i >= varargs) { + ftepp_error(ftepp, "subscript of `[%u]` is out of bounds for `__VA_ARGS__`", out->constval.i); + vec_free(old_string); + return false; + } + + ftepp_param_out(ftepp, ¶ms[out->constval.i + vararg_start]); + break; + + case TOKEN_VA_COUNT: + util_asprintf(&buffer, "%d", varargs); + ftepp_out(ftepp, buffer, false); + mem_d(buffer); + break; + + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + if (!macro_params_find(macro, out->value, &pi)) { + ftepp_out(ftepp, out->value, false); + break; + } else + ftepp_param_out(ftepp, ¶ms[pi]); + break; + case '#': + if (o + 1 < vec_size(macro->output)) { + nextok = macro->output[o+1]->token; + if (nextok == '#') { + /* raw concatenation */ + ++o; + strip = true; + break; + } + if ( (nextok == TOKEN_IDENT || + nextok == TOKEN_KEYWORD || + nextok == TOKEN_TYPENAME) && + macro_params_find(macro, macro->output[o+1]->value, &pi)) + { + ++o; + + ftepp_stringify(ftepp, ¶ms[pi]); + break; + } + } + ftepp_out(ftepp, "#", false); + break; + case TOKEN_EOL: + ftepp_out(ftepp, "\n", false); + break; + default: + buffer = out->value; + #define buffer_stripable(X) ((X) == ' ' || (X) == '\t') + if (vec_size(macro->output) > o + 1 && macro->output[o+1]->token == '#' && buffer_stripable(*buffer)) + buffer++; + if (strip) { + while (buffer_stripable(*buffer)) buffer++; + strip = false; + } + ftepp_out(ftepp, buffer, false); + break; + } + } + vec_push(ftepp->output_string, 0); + /* Now run the preprocessor recursively on this string buffer */ + /* + printf("__________\n%s\n=========\n", ftepp->output_string); + */ + inlex = lex_open_string(ftepp->output_string, vec_size(ftepp->output_string)-1, ftepp->lex->name); + if (!inlex) { + ftepp_error(ftepp, "internal error: failed to instantiate lexer"); + retval = false; + goto cleanup; + } + + inlex->line = ftepp->lex->line; + inlex->sline = ftepp->lex->sline; + ftepp->lex = inlex; + + old_inmacro = ftepp->in_macro; + ftepp->in_macro = true; + ftepp->output_string = nullptr; + if (!ftepp_preprocess(ftepp)) { + ftepp->in_macro = old_inmacro; + vec_free(ftepp->lex->open_string); + vec_free(ftepp->output_string); + lex_close(ftepp->lex); + retval = false; + goto cleanup; + } + ftepp->in_macro = old_inmacro; + vec_free(ftepp->lex->open_string); + lex_close(ftepp->lex); + + inner_string = ftepp->output_string; + ftepp->output_string = old_string; + + has_newlines = (strchr(inner_string, '\n') != nullptr); + + if (has_newlines && !old_inmacro) + ftepp_recursion_header(ftepp); + + vec_append(ftepp->output_string, vec_size(inner_string), inner_string); + vec_free(inner_string); + + if (has_newlines && !old_inmacro) + ftepp_recursion_footer(ftepp); + + if (resetline && !ftepp->in_macro) { + char lineno[128]; + util_snprintf(lineno, 128, "\n#pragma line(%lu)\n", (unsigned long)(old_lexer->sline)); + ftepp_out(ftepp, lineno, false); + } + + old_string = ftepp->output_string; +cleanup: + ftepp->lex = old_lexer; + ftepp->output_string = old_string; + return retval; +} + +static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro) +{ + size_t o; + macroparam *params = nullptr; + bool retval = true; + size_t paramline; + + if (!macro->has_params) { + if (!ftepp_macro_expand(ftepp, macro, nullptr, false)) + return false; + ftepp_next(ftepp); + return true; + } + ftepp_next(ftepp); + + if (!ftepp_skipallwhite(ftepp)) + return false; + + if (ftepp->token != '(') { + ftepp_error(ftepp, "expected macro parameters in parenthesis"); + return false; + } + + ftepp_next(ftepp); + paramline = ftepp->lex->sline; + if (!ftepp_macro_call_params(ftepp, ¶ms)) + return false; + + if ( vec_size(params) < vec_size(macro->params) || + (vec_size(params) > vec_size(macro->params) && !macro->variadic) ) + { + ftepp_error(ftepp, "macro %s expects%s %u paramteters, %u provided", macro->name, + (macro->variadic ? " at least" : ""), + (unsigned int)vec_size(macro->params), + (unsigned int)vec_size(params)); + retval = false; + goto cleanup; + } + + if (!ftepp_macro_expand(ftepp, macro, params, (paramline != ftepp->lex->sline))) + retval = false; + ftepp_next(ftepp); + +cleanup: + for (o = 0; o < vec_size(params); ++o) + macroparam_clean(¶ms[o]); + vec_free(params); + return retval; +} + +/** + * #if - the FTEQCC way: + * defined(FOO) => true if FOO was #defined regardless of parameters or contents + * => True if the number is not 0 + * ! => True if the factor yields false + * !! => ERROR on 2 or more unary nots + * => becomes the macro's FIRST token regardless of parameters + * && => True if both expressions are true + * || => True if either expression is true + * => False + * => False (remember for macros the rule applies instead) + * Unary + and - are weird and wrong in fteqcc so we don't allow them + * parenthesis in expressions are allowed + * parameter lists on macros are errors + * No mathematical calculations are executed + */ +static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out); +static bool ftepp_if_op(ftepp_t *ftepp) +{ + ftepp->lex->flags.noops = false; + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + ftepp->lex->flags.noops = true; + return true; +} +static bool ftepp_if_value(ftepp_t *ftepp, bool *out, double *value_out) +{ + ppmacro *macro; + bool wasnot = false; + bool wasneg = false; + + if (!ftepp_skipspace(ftepp)) + return false; + + while (ftepp->token == '!') { + wasnot = true; + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + } + + if (ftepp->token == TOKEN_OPERATOR && !strcmp(ftepp_tokval(ftepp), "-")) + { + wasneg = true; + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + } + + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + if (!strcmp(ftepp_tokval(ftepp), "defined")) { + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token != '(') { + ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis"); + return false; + } + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token != TOKEN_IDENT && + ftepp->token != TOKEN_TYPENAME && + ftepp->token != TOKEN_KEYWORD) + { + ftepp_error(ftepp, "defined() used on an unexpected token type"); + return false; + } + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + *out = !!macro; + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token != ')') { + ftepp_error(ftepp, "expected closing paren"); + return false; + } + break; + } + + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + if (!macro || !vec_size(macro->output)) { + *out = false; + *value_out = 0; + } else { + /* This does not expand recursively! */ + switch (macro->output[0]->token) { + case TOKEN_INTCONST: + *value_out = macro->output[0]->constval.i; + *out = !!(macro->output[0]->constval.i); + break; + case TOKEN_FLOATCONST: + *value_out = macro->output[0]->constval.f; + *out = !!(macro->output[0]->constval.f); + break; + default: + *out = false; + break; + } + } + break; + case TOKEN_STRINGCONST: + *value_out = 0; + *out = false; + break; + case TOKEN_INTCONST: + *value_out = ftepp->lex->tok.constval.i; + *out = !!(ftepp->lex->tok.constval.i); + break; + case TOKEN_FLOATCONST: + *value_out = ftepp->lex->tok.constval.f; + *out = !!(ftepp->lex->tok.constval.f); + break; + + case '(': + ftepp_next(ftepp); + if (!ftepp_if_expr(ftepp, out, value_out)) + return false; + if (ftepp->token != ')') { + ftepp_error(ftepp, "expected closing paren in #if expression"); + return false; + } + break; + + default: + ftepp_error(ftepp, "junk in #if: `%s` ...", ftepp_tokval(ftepp)); + if (OPTS_OPTION_BOOL(OPTION_DEBUG)) + ftepp_error(ftepp, "internal: token %i\n", ftepp->token); + return false; + } + if (wasneg) + *value_out = -*value_out; + if (wasnot) { + *out = !*out; + *value_out = (*out ? 1 : 0); + } + return true; +} + +/* +static bool ftepp_if_nextvalue(ftepp_t *ftepp, bool *out, double *value_out) +{ + if (!ftepp_next(ftepp)) + return false; + return ftepp_if_value(ftepp, out, value_out); +} +*/ + +static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out) +{ + if (!ftepp_if_value(ftepp, out, value_out)) + return false; + + if (!ftepp_if_op(ftepp)) + return false; + + if (ftepp->token == ')' || ftepp->token != TOKEN_OPERATOR) + return true; + + /* FTEQCC is all right-associative and no precedence here */ + if (!strcmp(ftepp_tokval(ftepp), "&&") || + !strcmp(ftepp_tokval(ftepp), "||")) + { + bool next = false; + char opc = ftepp_tokval(ftepp)[0]; + double nextvalue; + + (void)nextvalue; + if (!ftepp_next(ftepp)) + return false; + if (!ftepp_if_expr(ftepp, &next, &nextvalue)) + return false; + + if (opc == '&') + *out = *out && next; + else + *out = *out || next; + + *value_out = (*out ? 1 : 0); + return true; + } + else if (!strcmp(ftepp_tokval(ftepp), "==") || + !strcmp(ftepp_tokval(ftepp), "!=") || + !strcmp(ftepp_tokval(ftepp), ">=") || + !strcmp(ftepp_tokval(ftepp), "<=") || + !strcmp(ftepp_tokval(ftepp), ">") || + !strcmp(ftepp_tokval(ftepp), "<")) + { + bool next = false; + const char opc0 = ftepp_tokval(ftepp)[0]; + const char opc1 = ftepp_tokval(ftepp)[1]; + double other; + + if (!ftepp_next(ftepp)) + return false; + if (!ftepp_if_expr(ftepp, &next, &other)) + return false; + + if (opc0 == '=') + *out = (*value_out == other); + else if (opc0 == '!') + *out = (*value_out != other); + else if (opc0 == '>') { + if (opc1 == '=') *out = (*value_out >= other); + else *out = (*value_out > other); + } + else if (opc0 == '<') { + if (opc1 == '=') *out = (*value_out <= other); + else *out = (*value_out < other); + } + *value_out = (*out ? 1 : 0); + + return true; + } + else { + ftepp_error(ftepp, "junk after #if"); + return false; + } +} + +static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond) +{ + bool result = false; + double dummy = 0; + + memset(cond, 0, sizeof(*cond)); + (void)ftepp_next(ftepp); + + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token == TOKEN_EOL) { + ftepp_error(ftepp, "expected expression for #if-directive"); + return false; + } + + if (!ftepp_if_expr(ftepp, &result, &dummy)) + return false; + + cond->on = result; + return true; +} + +/** + * ifdef is rather simple + */ +static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond) +{ + ppmacro *macro; + memset(cond, 0, sizeof(*cond)); + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + break; + default: + ftepp_error(ftepp, "expected macro name"); + return false; + } + + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + /* relaxing this condition + if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) { + ftepp_error(ftepp, "stray tokens after #ifdef"); + return false; + } + */ + cond->on = !!macro; + return true; +} + +/** + * undef is also simple + */ +static bool ftepp_undef(ftepp_t *ftepp) +{ + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + + if (ftepp->output_on) { + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + ftepp_macro_delete(ftepp, ftepp_tokval(ftepp)); + break; + default: + ftepp_error(ftepp, "expected macro name"); + return false; + } + } + + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + /* relaxing this condition + if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) { + ftepp_error(ftepp, "stray tokens after #ifdef"); + return false; + } + */ + return true; +} + +/* Special unescape-string function which skips a leading quote + * and stops at a quote, not just at \0 + */ +static void unescape(const char *str, char *out) { + ++str; + while (*str && *str != '"') { + if (*str == '\\') { + ++str; + switch (*str) { + case '\\': *out++ = *str; break; + case '"': *out++ = *str; break; + case 'a': *out++ = '\a'; break; + case 'b': *out++ = '\b'; break; + case 'r': *out++ = '\r'; break; + case 'n': *out++ = '\n'; break; + case 't': *out++ = '\t'; break; + case 'f': *out++ = '\f'; break; + case 'v': *out++ = '\v'; break; + default: + *out++ = '\\'; + *out++ = *str; + break; + } + ++str; + continue; + } + + *out++ = *str++; + } + *out = 0; +} + +static char *ftepp_include_find_path(const char *file, const char *pathfile) +{ + FILE *fp; + char *filename = nullptr; + const char *last_slash; + size_t len; + + if (!pathfile) + return nullptr; + + last_slash = strrchr(pathfile, '/'); + + if (last_slash) { + len = last_slash - pathfile; + memcpy(vec_add(filename, len), pathfile, len); + vec_push(filename, '/'); + } + + len = strlen(file); + memcpy(vec_add(filename, len+1), file, len); + vec_last(filename) = 0; + + fp = fopen(filename, "rb"); + if (fp) { + fclose(fp); + return filename; + } + vec_free(filename); + return nullptr; +} + +static char *ftepp_include_find(ftepp_t *ftepp, const char *file) +{ + char *filename = nullptr; + + filename = ftepp_include_find_path(file, ftepp->includename); + if (!filename) + filename = ftepp_include_find_path(file, ftepp->itemname); + return filename; +} + +static bool ftepp_directive_warning(ftepp_t *ftepp) { + char *message = nullptr; + + if (!ftepp_skipspace(ftepp)) + return false; + + /* handle the odd non string constant case so it works like C */ + if (ftepp->token != TOKEN_STRINGCONST) { + bool store = false; + vec_append(message, 8, "#warning"); + ftepp_next(ftepp); + while (ftepp->token != TOKEN_EOL) { + vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp)); + ftepp_next(ftepp); + } + vec_push(message, '\0'); + if (ftepp->output_on) + store = ftepp_warn(ftepp, WARN_CPP, message); + else + store = false; + vec_free(message); + return store; + } + + if (!ftepp->output_on) + return false; + + unescape (ftepp_tokval(ftepp), ftepp_tokval(ftepp)); + return ftepp_warn(ftepp, WARN_CPP, "#warning %s", ftepp_tokval(ftepp)); +} + +static void ftepp_directive_error(ftepp_t *ftepp) { + char *message = nullptr; + + if (!ftepp_skipspace(ftepp)) + return; + + /* handle the odd non string constant case so it works like C */ + if (ftepp->token != TOKEN_STRINGCONST) { + vec_append(message, 6, "#error"); + ftepp_next(ftepp); + while (ftepp->token != TOKEN_EOL) { + vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp)); + ftepp_next(ftepp); + } + vec_push(message, '\0'); + if (ftepp->output_on) + ftepp_error(ftepp, message); + vec_free(message); + return; + } + + if (!ftepp->output_on) + return; + + unescape (ftepp_tokval(ftepp), ftepp_tokval(ftepp)); + ftepp_error(ftepp, "#error %s", ftepp_tokval(ftepp)); +} + +static void ftepp_directive_message(ftepp_t *ftepp) { + char *message = nullptr; + + if (!ftepp_skipspace(ftepp)) + return; + + /* handle the odd non string constant case so it works like C */ + if (ftepp->token != TOKEN_STRINGCONST) { + vec_append(message, 8, "#message"); + ftepp_next(ftepp); + while (ftepp->token != TOKEN_EOL) { + vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp)); + ftepp_next(ftepp); + } + vec_push(message, '\0'); + if (ftepp->output_on) + con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message", message); + vec_free(message); + return; + } + + if (!ftepp->output_on) + return; + + unescape (ftepp_tokval(ftepp), ftepp_tokval(ftepp)); + con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message", ftepp_tokval(ftepp)); +} + +/** + * Include a file. + * FIXME: do we need/want a -I option? + * FIXME: what about when dealing with files in subdirectories coming from a progs.src? + */ +static bool ftepp_include(ftepp_t *ftepp) +{ + lex_file *old_lexer = ftepp->lex; + lex_file *inlex; + lex_ctx_t ctx; + char lineno[128]; + char *filename; + char *parsename = nullptr; + char *old_includename; + + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + + if (ftepp->token != TOKEN_STRINGCONST) { + ppmacro *macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + if (macro) { + char *backup = ftepp->output_string; + ftepp->output_string = nullptr; + if (ftepp_macro_expand(ftepp, macro, nullptr, true)) { + parsename = util_strdup(ftepp->output_string); + vec_free(ftepp->output_string); + ftepp->output_string = backup; + } else { + ftepp->output_string = backup; + ftepp_error(ftepp, "expected filename to include"); + return false; + } + } else if (OPTS_FLAG(FTEPP_PREDEFS)) { + /* Well it could be a predefine like __LINE__ */ + char *(*predef)(ftepp_t*) = ftepp_predef(ftepp_tokval(ftepp)); + if (predef) { + parsename = predef(ftepp); + } else { + ftepp_error(ftepp, "expected filename to include"); + return false; + } + } + } + + if (!ftepp->output_on) { + (void)ftepp_next(ftepp); + return true; + } + + if (parsename) + unescape(parsename, parsename); + else { + char *tokval = ftepp_tokval(ftepp); + unescape(tokval, tokval); + parsename = util_strdup(tokval); + } + + ctx = ftepp_ctx(ftepp); + ftepp_out(ftepp, "\n#pragma file(", false); + ftepp_out(ftepp, parsename, false); + ftepp_out(ftepp, ")\n#pragma line(1)\n", false); + + filename = ftepp_include_find(ftepp, parsename); + if (!filename) { + ftepp_error(ftepp, "failed to open include file `%s`", parsename); + mem_d(parsename); + return false; + } + mem_d(parsename); + inlex = lex_open(filename); + if (!inlex) { + ftepp_error(ftepp, "open failed on include file `%s`", filename); + vec_free(filename); + return false; + } + ftepp->lex = inlex; + old_includename = ftepp->includename; + ftepp->includename = filename; + if (!ftepp_preprocess(ftepp)) { + vec_free(ftepp->includename); + ftepp->includename = old_includename; + lex_close(ftepp->lex); + ftepp->lex = old_lexer; + return false; + } + vec_free(ftepp->includename); + ftepp->includename = old_includename; + lex_close(ftepp->lex); + ftepp->lex = old_lexer; + + ftepp_out(ftepp, "\n#pragma file(", false); + ftepp_out(ftepp, ctx.file, false); + util_snprintf(lineno, sizeof(lineno), ")\n#pragma line(%lu)\n", (unsigned long)(ctx.line+1)); + ftepp_out(ftepp, lineno, false); + + /* skip the line */ + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token != TOKEN_EOL) { + ftepp_error(ftepp, "stray tokens after #include"); + return false; + } + (void)ftepp_next(ftepp); + + return true; +} + +/* Basic structure handlers */ +static bool ftepp_else_allowed(ftepp_t *ftepp) +{ + if (!vec_size(ftepp->conditions)) { + ftepp_error(ftepp, "#else without #if"); + return false; + } + if (vec_last(ftepp->conditions).had_else) { + ftepp_error(ftepp, "multiple #else for a single #if"); + return false; + } + return true; +} + +static GMQCC_INLINE void ftepp_inmacro(ftepp_t *ftepp, const char *hash) { + if (ftepp->in_macro) + (void)!ftepp_warn(ftepp, WARN_DIRECTIVE_INMACRO, "`#%s` directive in macro", hash); +} + +static bool ftepp_hash(ftepp_t *ftepp) +{ + ppcondition cond; + ppcondition *pc; + + lex_ctx_t ctx = ftepp_ctx(ftepp); + + if (!ftepp_skipspace(ftepp)) + return false; + + switch (ftepp->token) { + case TOKEN_KEYWORD: + case TOKEN_IDENT: + case TOKEN_TYPENAME: + if (!strcmp(ftepp_tokval(ftepp), "define")) { + ftepp_inmacro(ftepp, "define"); + return ftepp_define(ftepp); + } + else if (!strcmp(ftepp_tokval(ftepp), "undef")) { + ftepp_inmacro(ftepp, "undef"); + return ftepp_undef(ftepp); + } + else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) { + ftepp_inmacro(ftepp, "ifdef"); + if (!ftepp_ifdef(ftepp, &cond)) + return false; + cond.was_on = cond.on; + vec_push(ftepp->conditions, cond); + ftepp->output_on = ftepp->output_on && cond.on; + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) { + ftepp_inmacro(ftepp, "ifndef"); + if (!ftepp_ifdef(ftepp, &cond)) + return false; + cond.on = !cond.on; + cond.was_on = cond.on; + vec_push(ftepp->conditions, cond); + ftepp->output_on = ftepp->output_on && cond.on; + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) { + ftepp_inmacro(ftepp, "elifdef"); + if (!ftepp_else_allowed(ftepp)) + return false; + if (!ftepp_ifdef(ftepp, &cond)) + return false; + pc = &vec_last(ftepp->conditions); + pc->on = !pc->was_on && cond.on; + pc->was_on = pc->was_on || pc->on; + ftepp_update_output_condition(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) { + ftepp_inmacro(ftepp, "elifndef"); + if (!ftepp_else_allowed(ftepp)) + return false; + if (!ftepp_ifdef(ftepp, &cond)) + return false; + cond.on = !cond.on; + pc = &vec_last(ftepp->conditions); + pc->on = !pc->was_on && cond.on; + pc->was_on = pc->was_on || pc->on; + ftepp_update_output_condition(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "elif")) { + ftepp_inmacro(ftepp, "elif"); + if (!ftepp_else_allowed(ftepp)) + return false; + if (!ftepp_if(ftepp, &cond)) + return false; + pc = &vec_last(ftepp->conditions); + pc->on = !pc->was_on && cond.on; + pc->was_on = pc->was_on || pc->on; + ftepp_update_output_condition(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "if")) { + ftepp_inmacro(ftepp, "if"); + if (!ftepp_if(ftepp, &cond)) + return false; + cond.was_on = cond.on; + vec_push(ftepp->conditions, cond); + ftepp->output_on = ftepp->output_on && cond.on; + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "else")) { + ftepp_inmacro(ftepp, "else"); + if (!ftepp_else_allowed(ftepp)) + return false; + pc = &vec_last(ftepp->conditions); + pc->on = !pc->was_on; + pc->had_else = true; + ftepp_next(ftepp); + ftepp_update_output_condition(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "endif")) { + ftepp_inmacro(ftepp, "endif"); + if (!vec_size(ftepp->conditions)) { + ftepp_error(ftepp, "#endif without #if"); + return false; + } + vec_pop(ftepp->conditions); + ftepp_next(ftepp); + ftepp_update_output_condition(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "include")) { + ftepp_inmacro(ftepp, "include"); + return ftepp_include(ftepp); + } + else if (!strcmp(ftepp_tokval(ftepp), "pragma")) { + ftepp_out(ftepp, "#", false); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "warning")) { + ftepp_directive_warning(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "error")) { + ftepp_directive_error(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "message")) { + ftepp_directive_message(ftepp); + break; + } + else { + if (ftepp->output_on) { + ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp)); + return false; + } else { + ftepp_next(ftepp); + break; + } + } + /* break; never reached */ + default: + ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp)); + return false; + case TOKEN_EOL: + ftepp_errorat(ftepp, ctx, "empty preprocessor directive"); + return false; + case TOKEN_EOF: + ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp)); + return false; + + /* Builtins! Don't forget the builtins! */ + case TOKEN_INTCONST: + case TOKEN_FLOATCONST: + ftepp_out(ftepp, "#", false); + return true; + } + if (!ftepp_skipspace(ftepp)) + return false; + return true; +} + +static bool ftepp_preprocess(ftepp_t *ftepp) +{ + ppmacro *macro; + bool newline = true; + + /* predef stuff */ + char *expand = nullptr; + + ftepp->lex->flags.preprocessing = true; + ftepp->lex->flags.mergelines = false; + ftepp->lex->flags.noops = true; + + ftepp_next(ftepp); + do + { + if (ftepp->token >= TOKEN_EOF) + break; + switch (ftepp->token) { + case TOKEN_KEYWORD: + case TOKEN_IDENT: + case TOKEN_TYPENAME: + /* is it a predef? */ + if (OPTS_FLAG(FTEPP_PREDEFS)) { + char *(*predef)(ftepp_t*) = ftepp_predef(ftepp_tokval(ftepp)); + if (predef) { + expand = predef(ftepp); + ftepp_out (ftepp, expand, false); + ftepp_next(ftepp); + + mem_d(expand); + break; + } + } + + if (ftepp->output_on) + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + else + macro = nullptr; + + if (!macro) { + ftepp_out(ftepp, ftepp_tokval(ftepp), false); + ftepp_next(ftepp); + break; + } + if (!ftepp_macro_call(ftepp, macro)) + ftepp->token = TOKEN_ERROR; + break; + case '#': + if (!newline) { + ftepp_out(ftepp, ftepp_tokval(ftepp), false); + ftepp_next(ftepp); + break; + } + ftepp->lex->flags.mergelines = true; + if (ftepp_next(ftepp) >= TOKEN_EOF) { + ftepp_error(ftepp, "error in preprocessor directive"); + ftepp->token = TOKEN_ERROR; + break; + } + if (!ftepp_hash(ftepp)) + ftepp->token = TOKEN_ERROR; + ftepp->lex->flags.mergelines = false; + break; + case TOKEN_EOL: + newline = true; + ftepp_out(ftepp, "\n", true); + ftepp_next(ftepp); + break; + case TOKEN_WHITE: + /* same as default but don't set newline=false */ + ftepp_out(ftepp, ftepp_tokval(ftepp), true); + ftepp_next(ftepp); + break; + default: + newline = false; + ftepp_out(ftepp, ftepp_tokval(ftepp), false); + ftepp_next(ftepp); + break; + } + } while (!ftepp->errors && ftepp->token < TOKEN_EOF); + + /* force a 0 at the end but don't count it as added to the output */ + vec_push(ftepp->output_string, 0); + vec_shrinkby(ftepp->output_string, 1); + + return (ftepp->token == TOKEN_EOF); +} + +/* Like in parser.c - files keep the previous state so we have one global + * preprocessor. Except here we will want to warn about dangling #ifs. + */ +static bool ftepp_preprocess_done(ftepp_t *ftepp) +{ + bool retval = true; + if (vec_size(ftepp->conditions)) { + if (ftepp_warn(ftepp, WARN_MULTIFILE_IF, "#if spanning multiple files, is this intended?")) + retval = false; + } + lex_close(ftepp->lex); + ftepp->lex = nullptr; + if (ftepp->itemname) { + mem_d(ftepp->itemname); + ftepp->itemname = nullptr; + } + return retval; +} + +bool ftepp_preprocess_file(ftepp_t *ftepp, const char *filename) +{ + ftepp->lex = lex_open(filename); + ftepp->itemname = util_strdup(filename); + if (!ftepp->lex) { + con_out("failed to open file \"%s\"\n", filename); + return false; + } + if (!ftepp_preprocess(ftepp)) + return false; + return ftepp_preprocess_done(ftepp); +} + +bool ftepp_preprocess_string(ftepp_t *ftepp, const char *name, const char *str) +{ + ftepp->lex = lex_open_string(str, strlen(str), name); + ftepp->itemname = util_strdup(name); + if (!ftepp->lex) { + con_out("failed to create lexer for string \"%s\"\n", name); + return false; + } + if (!ftepp_preprocess(ftepp)) + return false; + return ftepp_preprocess_done(ftepp); +} + + +void ftepp_add_macro(ftepp_t *ftepp, const char *name, const char *value) { + char *create = nullptr; + + /* use saner path for empty macros */ + if (!value) { + ftepp_add_define(ftepp, "__builtin__", name); + return; + } + + vec_append(create, 8, "#define "); + vec_append(create, strlen(name), name); + vec_push (create, ' '); + vec_append(create, strlen(value), value); + vec_push (create, 0); + + ftepp_preprocess_string(ftepp, "__builtin__", create); + vec_free (create); +} + +ftepp_t *ftepp_create() +{ + ftepp_t *ftepp; + char minor[32]; + char major[32]; + size_t i; + + ftepp = ftepp_new(); + if (!ftepp) + return nullptr; + + memset(minor, 0, sizeof(minor)); + memset(major, 0, sizeof(major)); + + /* set the right macro based on the selected standard */ + ftepp_add_define(ftepp, nullptr, "GMQCC"); + if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) { + ftepp_add_define(ftepp, nullptr, "__STD_FTEQCC__"); + /* 1.00 */ + major[0] = '"'; + major[1] = '1'; + major[2] = '"'; + + minor[0] = '"'; + minor[1] = '0'; + minor[2] = '"'; + } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) { + ftepp_add_define(ftepp, nullptr, "__STD_GMQCC__"); + util_snprintf(major, 32, "\"%d\"", GMQCC_VERSION_MAJOR); + util_snprintf(minor, 32, "\"%d\"", GMQCC_VERSION_MINOR); + } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCCX) { + ftepp_add_define(ftepp, nullptr, "__STD_QCCX__"); + util_snprintf(major, 32, "\"%d\"", GMQCC_VERSION_MAJOR); + util_snprintf(minor, 32, "\"%d\"", GMQCC_VERSION_MINOR); + } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { + ftepp_add_define(ftepp, nullptr, "__STD_QCC__"); + /* 1.0 */ + major[0] = '"'; + major[1] = '1'; + major[2] = '"'; + + minor[0] = '"'; + minor[1] = '0'; + minor[2] = '"'; + } + + ftepp_add_macro(ftepp, "__STD_VERSION_MINOR__", minor); + ftepp_add_macro(ftepp, "__STD_VERSION_MAJOR__", major); + + /* + * We're going to just make __NULL__ nil, which works for 60% of the + * cases of __NULL_ for fteqcc. + */ + ftepp_add_macro(ftepp, "__NULL__", "nil"); + + /* add all the math constants if they can be */ + if (OPTS_FLAG(FTEPP_MATHDEFS)) { + for (i = 0; i < GMQCC_ARRAY_COUNT(ftepp_math_constants); i++) + if (!ftepp_macro_find(ftepp, ftepp_math_constants[i][0])) + ftepp_add_macro(ftepp, ftepp_math_constants[i][0], ftepp_math_constants[i][1]); + } + + return ftepp; +} + +void ftepp_add_define(ftepp_t *ftepp, const char *source, const char *name) +{ + ppmacro *macro; + lex_ctx_t ctx = { "__builtin__", 0, 0 }; + ctx.file = source; + macro = ppmacro_new(ctx, name); + util_htset(ftepp->macros, name, macro); +} + +const char *ftepp_get(ftepp_t *ftepp) +{ + return ftepp->output_string; +} + +void ftepp_flush(ftepp_t *ftepp) +{ + ftepp_flush_do(ftepp); +} + +void ftepp_finish(ftepp_t *ftepp) +{ + if (!ftepp) + return; + ftepp_delete(ftepp); +} diff --git a/gmqcc.h b/gmqcc.h index 43153e9..4e3fb39 100644 --- a/gmqcc.h +++ b/gmqcc.h @@ -1,30 +1,14 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * Dale Weiler - * Wolfgang Bumiller - * - * 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. - */ #ifndef GMQCC_HDR #define GMQCC_HDR +#include +#include +#include +#include +using std::move; #include #include +#include +#include #include #define GMQCC_VERSION_MAJOR 0 @@ -178,34 +162,15 @@ GMQCC_IND_STRING(GMQCC_VERSION_PATCH) \ #define GMQCC_ARRAY_COUNT(X) (sizeof(X) / sizeof((X)[0])) /* stat.c */ -void stat_info (void); -char *stat_mem_strdup (const char *, size_t, const char *, bool); -void stat_mem_deallocate(void *, size_t, const char *); -void *stat_mem_reallocate(void *, size_t, size_t, const char *, const char *); -void *stat_mem_allocate (size_t, size_t, const char *, const char *); +char *stat_mem_strdup(const char *, bool); -#define mem_a(SIZE) stat_mem_allocate ((SIZE), __LINE__, __FILE__, #SIZE) -#define mem_d(PTRN) stat_mem_deallocate((void*)(PTRN), __LINE__, __FILE__) -#define mem_r(PTRN, SIZE) stat_mem_reallocate((void*)(PTRN), (SIZE), __LINE__, __FILE__, #SIZE) -#define mem_af(SIZE, FILE, LINE) stat_mem_allocate ((SIZE), (LINE), (FILE), #SIZE) +#define mem_a(SIZE) malloc(SIZE) +#define mem_d(PTRN) free((void*)PTRN) +#define mem_r(PTRN, SIZE) realloc((void*)PTRN, SIZE) -/* TODO: rename to mem variations */ -#define util_strdup(SRC) stat_mem_strdup((char*)(SRC), __LINE__, __FILE__, false) -#define util_strdupe(SRC) stat_mem_strdup((char*)(SRC), __LINE__, __FILE__, true) +#define util_strdup(SRC) stat_mem_strdup((char*)(SRC), false) +#define util_strdupe(SRC) stat_mem_strdup((char*)(SRC), true) -/* util.c */ - -/* - * Microsoft implements against the spec versions of ctype.h. Which - * means what ever the current set locale is will render the actual - * results of say isalpha('A') wrong for what ever retarded locale - * is used. Simalerly these are also implemented inefficently on - * some toolchains and end up becoming actual library calls. Perhaps - * this is why tools like yacc provide their own? Regardless implementing - * these as functions is equally as silly, the call overhead is not - * justified when this could happen on every character from an input - * stream. We provide our own as macros for absolute inlinability. - */ #define util_isalpha(a) ((((unsigned)(a)|32)-'a') < 26) #define util_isdigit(a) (((unsigned)(a)-'0') < 10) #define util_islower(a) (((unsigned)(a)-'a') < 26) @@ -237,9 +202,7 @@ const char *util_strerror(int err); const struct tm *util_localtime(const time_t *timer); const char *util_ctime (const time_t *timer); -typedef struct fs_file_s fs_file_t; - -bool util_isatty(fs_file_t *); +bool util_isatty(FILE *); size_t hash(const char *key); /* @@ -249,26 +212,26 @@ size_t hash(const char *key); * us to use the array [] to access individual elements from the vector * opposed to using set/get methods. */ -typedef struct { +struct vector_t { size_t allocated; size_t used; /* can be extended now! whoot */ -} vector_t; +}; /* hidden interface */ void _util_vec_grow(void **a, size_t i, size_t s); -void _util_vec_delete(void *vec, size_t line, const char *file); +void _util_vec_delete(void *vec); -#define GMQCC_VEC_WILLGROW(X,Y) ( \ +#define GMQCC_VEC_WILLGROW(X, Y) ( \ ((!(X) || vec_meta(X)->used + Y >= vec_meta(X)->allocated)) ? \ (void)_util_vec_grow(((void**)&(X)), (Y), sizeof(*(X))) : \ (void)0 \ ) /* exposed interface */ -#define vec_meta(A) ((vector_t*)(((char *)(A)) - (sizeof(vector_t) + 4))) -#define vec_free(A) ((void)((A) ? (_util_vec_delete((void *)(A), __LINE__, __FILE__), (A) = NULL) : 0)) +#define vec_meta(A) ((vector_t*)(((char *)(A)) - sizeof(vector_t))) +#define vec_free(A) ((void)((A) ? (_util_vec_delete((void *)(A)), (A) = nullptr) : 0)) #define vec_push(A,V) (GMQCC_VEC_WILLGROW((A),1), (A)[vec_meta(A)->used++] = (V)) #define vec_size(A) ((A) ? vec_meta(A)->used : 0) #define vec_add(A,N) (GMQCC_VEC_WILLGROW((A),(N)), vec_meta(A)->used += (N), &(A)[vec_meta(A)->used-(N)]) @@ -284,106 +247,23 @@ typedef struct hash_table_s { struct hash_node_t **table; } hash_table_t, *ht; -/* - * hashtable implementation: - * - * Note: - * This was designed for pointers: you manage the life of the object yourself - * if you do use this for non-pointers please be warned that the object may not - * be valid if the duration of it exceeds (i.e on stack). So you need to allocate - * yourself, or put those in global scope to ensure duration is for the whole - * runtime. - * - * util_htnew(size) -- to make a new hashtable - * util_htset(table, key, value, sizeof(value)) -- to set something in the table - * util_htget(table, key) -- to get something from the table - * util_htdel(table) -- to delete the table - * - * example of use: - * - * ht foo = util_htnew(1024); - * int data = 100; - * char *test = "hello world\n"; - * util_htset(foo, "foo", (void*)&data); - * util_gtset(foo, "bar", (void*)test); - * - * printf("foo: %d, bar %s", - * *((int *)util_htget(foo, "foo")), - * ((char*)util_htget(foo, "bar")) - * ); - * - * util_htdel(foo); - */ hash_table_t *util_htnew (size_t size); -void util_htrem (hash_table_t *ht, void (*callback)(void *data)); -void util_htset (hash_table_t *ht, const char *key, void *value); -void util_htdel (hash_table_t *ht); -size_t util_hthash(hash_table_t *ht, const char *key); -void util_htseth(hash_table_t *ht, const char *key, size_t hash, void *value); -void util_htrmh (hash_table_t *ht, const char *key, size_t bin, void (*cb)(void*)); -void util_htrm (hash_table_t *ht, const char *key, void (*cb)(void*)); - -void *util_htget (hash_table_t *ht, const char *key); -void *util_htgeth(hash_table_t *ht, const char *key, size_t hash); - -int util_snprintf(char *str, size_t, const char *fmt, ...); - - -/* fs.c */ -#define FS_FILE_SEEK_SET 0 -#define FS_FILE_SEEK_CUR 1 -#define FS_FILE_SEEK_END 2 -#define FS_FILE_EOF -1 - -typedef struct fs_dir_s fs_dir_t; -/*typedef struct fs_file_s fs_file_t;*/ -typedef struct dirent fs_dirent_t; - -void fs_file_close (fs_file_t *); -int fs_file_error (fs_file_t *); -int fs_file_getc (fs_file_t *); -int fs_file_printf (fs_file_t *, const char *, ...); -int fs_file_puts (fs_file_t *, const char *); -int fs_file_seek (fs_file_t *, long int, int); -long fs_file_tell (fs_file_t *); -int fs_file_flush (fs_file_t *); - -size_t fs_file_read (void *, size_t, size_t, fs_file_t *); -size_t fs_file_write (const void *, size_t, size_t, fs_file_t *); - -fs_file_t *fs_file_open (const char *, const char *); -int fs_file_getline(char **, size_t *, fs_file_t *); - -int fs_dir_make (const char *); -fs_dir_t *fs_dir_open (const char *); -int fs_dir_close (fs_dir_t *); -fs_dirent_t *fs_dir_read (fs_dir_t *); - - -/* correct.c */ -typedef struct correct_trie_s { - void *value; - struct correct_trie_s *entries; -} correct_trie_t; - -correct_trie_t* correct_trie_new(void); - -typedef struct { - char ***edits; - size_t **lens; -} correction_t; - -void correct_del (correct_trie_t*, size_t **); -void correct_add (correct_trie_t*, size_t ***, const char *); -char *correct_str (correction_t *, correct_trie_t*, const char *); -void correct_init(correction_t *); -void correct_free(correction_t *); - +void util_htrem(hash_table_t *ht, void (*callback)(void *data)); +void util_htset(hash_table_t *ht, const char *key, void *value); +void util_htdel(hash_table_t *ht); +size_t util_hthash(hash_table_t *ht, const char *key); +void util_htseth(hash_table_t *ht, const char *key, size_t hash, void *value); +void util_htrmh(hash_table_t *ht, const char *key, size_t bin, void (*cb)(void*)); +void util_htrm(hash_table_t *ht, const char *key, void (*cb)(void*)); +void *util_htget(hash_table_t *ht, const char *key); +void *util_htgeth(hash_table_t *ht, const char *key, size_t hash); +int util_snprintf(char *str, size_t, const char *fmt, ...); +int util_getline(char **, size_t *, FILE *); /* code.c */ /* Note: if you change the order, fix type_sizeof in ir.c */ -enum { +enum qc_type { TYPE_VOID , TYPE_STRING , TYPE_FLOAT , @@ -425,12 +305,12 @@ extern const uint16_t type_eq_instr [TYPE_COUNT]; extern const uint16_t type_ne_instr [TYPE_COUNT]; extern const uint16_t type_not_instr [TYPE_COUNT]; -typedef struct { +struct prog_section_t { uint32_t offset; /* Offset in file of where data begins */ uint32_t length; /* Length of section (how many of) */ -} prog_section_t; +}; -typedef struct { +struct prog_header_t { uint32_t version; /* Program version (6) */ uint16_t crc16; uint16_t skip; @@ -442,7 +322,7 @@ typedef struct { prog_section_t strings; prog_section_t globals; uint32_t entfield; /* Number of entity fields */ -} prog_header_t; +}; /* * Each paramater incerements by 3 since vector types hold @@ -459,37 +339,19 @@ typedef struct { #define OFS_PARM6 (OFS_PARM5 +3) #define OFS_PARM7 (OFS_PARM6 +3) -typedef struct { - uint16_t opcode; - - /* operand 1 */ - union { - int16_t s1; /* signed */ - uint16_t u1; /* unsigned */ - } o1; - /* operand 2 */ - union { - int16_t s1; /* signed */ - uint16_t u1; /* unsigned */ - } o2; - /* operand 3 */ - union { - int16_t s1; /* signed */ - uint16_t u1; /* unsigned */ - } o3; +union operand_t { + int16_t s1; + uint16_t u1; +}; - /* - * This is the same as the structure in darkplaces - * { - * unsigned short op; - * short a,b,c; - * } - * But this one is more sane to work with, and the - * type sizes are guranteed. - */ -} prog_section_statement_t; +struct prog_section_statement_t { + uint16_t opcode; + operand_t o1; + operand_t o2; + operand_t o3; +}; -typedef struct { +struct prog_section_both_t { /* * The types: * 0 = ev_void @@ -505,7 +367,7 @@ typedef struct { uint16_t type; uint16_t offset; uint32_t name; -} prog_section_both_t; +}; typedef prog_section_both_t prog_section_def_t; typedef prog_section_both_t prog_section_field_t; @@ -514,7 +376,7 @@ typedef prog_section_both_t prog_section_field_t; #define DEF_SAVEGLOBAL (1<<15) #define DEF_TYPEMASK ((1<<15)-1) -typedef struct { +struct prog_section_function_t { int32_t entry; /* in statement table for instructions */ uint32_t firstlocal; /* First local in local table */ uint32_t locals; /* Total ints of params + locals */ @@ -523,7 +385,7 @@ typedef struct { uint32_t file; /* file of the source file */ int32_t nargs; /* number of arguments */ uint8_t argsize[8]; /* size of arguments (keep 8 always?) */ -} prog_section_function_t; +}; /* * Instructions @@ -629,40 +491,44 @@ enum { /* TODO: elide */ extern const char *util_instr_str[VINSTR_END]; -void util_swap_header (prog_header_t *code_header); -void util_swap_statements (prog_section_statement_t *statements); -void util_swap_defs_fields(prog_section_both_t *section); -void util_swap_functions (prog_section_function_t *functions); -void util_swap_globals (int32_t *globals); +void util_swap_header(prog_header_t &code_header); +void util_swap_statements(std::vector &statements); +void util_swap_defs_fields(std::vector §ion); +void util_swap_functions(std::vector &functions); +void util_swap_globals(std::vector &globals); -typedef float qcfloat_t; -typedef int32_t qcint_t; +typedef float qcfloat_t; +typedef int32_t qcint_t; typedef uint32_t qcuint_t; -typedef struct { - prog_section_statement_t *statements; - int *linenums; - int *columnnums; - prog_section_def_t *defs; - prog_section_field_t *fields; - prog_section_function_t *functions; - int *globals; - char *chars; - uint16_t crc; - uint32_t entfields; - ht string_cache; - qcint_t string_cached_empty; -} code_t; +struct code_t { + void* operator new(std::size_t); + void operator delete(void*); + code_t(); + ~code_t(); + std::vector statements; + std::vector linenums; + std::vector columnnums; + std::vector defs; + std::vector fields; + std::vector functions; + std::vector globals; + std::vector chars; + uint16_t crc = 0; + uint32_t entfields = 0; + ht string_cache; + qcint_t string_cached_empty = 0; +}; /* * A shallow copy of a lex_file to remember where which ast node * came from. */ -typedef struct { +struct lex_ctx_t { const char *file; - size_t line; - size_t column; -} lex_ctx_t; + size_t line; + size_t column; +}; /* * code_write -- writes out the compiled file @@ -701,8 +567,8 @@ enum { LVL_ERROR }; -fs_file_t *con_default_out(void); -fs_file_t *con_default_err(void); +FILE *con_default_out(void); +FILE *con_default_err(void); void con_vprintmsg (int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap); void con_printmsg (int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, ...); @@ -713,7 +579,6 @@ void con_close (void); void con_init (void); void con_reset (void); void con_color (int); -int con_change(const char *, const char *); int con_verr (const char *, va_list); int con_vout (const char *, va_list); int con_err (const char *, ...); @@ -724,15 +589,31 @@ extern size_t compile_errors; extern size_t compile_Werrors; extern size_t compile_warnings; -void /********/ compile_error (lex_ctx_t ctx, /*LVL_ERROR*/ const char *msg, ...); +void /********/ compile_error_ (lex_ctx_t ctx, /*LVL_ERROR*/ const char *msg, ...); void /********/ vcompile_error (lex_ctx_t ctx, /*LVL_ERROR*/ const char *msg, va_list ap); -bool GMQCC_WARN compile_warning (lex_ctx_t ctx, int warntype, const char *fmt, ...); +bool GMQCC_WARN compile_warning_(lex_ctx_t ctx, int warntype, const char *fmt, ...); bool GMQCC_WARN vcompile_warning(lex_ctx_t ctx, int warntype, const char *fmt, va_list ap); void compile_show_werrors(void); +template +inline constexpr const T formatNormalize(const T argument) { return argument; } + +inline const char *formatNormalize(const std::string &argument) { + return argument.c_str(); +} + +template +inline bool GMQCC_WARN compile_warning(lex_ctx_t ctx, int warntype, const char *fmt, const Ts&... ts) { + return compile_warning_(ctx, warntype, fmt, formatNormalize(ts)...); +} +template +inline void /********/ compile_error (lex_ctx_t ctx, /*LVL_ERROR*/ const char *msg, const Ts&... ts) { + return compile_error_(ctx, msg, formatNormalize(ts)...); +} + /* ir.c */ /* TODO: cleanup */ -enum store_types { +enum store_type { store_global, store_local, /* local, assignable for now, should get promoted later */ store_param, /* parameters, they are locals with a fixed position */ @@ -740,9 +621,9 @@ enum store_types { store_return /* unassignable, at OFS_RETURN */ }; -typedef struct { +struct vec3_t { qcfloat_t x, y, z; -} vec3_t; +}; /* exec.c */ @@ -779,27 +660,27 @@ enum { #define VMXF_TRACE 0x0001 /* trace: print statements before executing */ #define VMXF_PROFILE 0x0002 /* profile: increment the profile counters */ -struct qc_program_s; -typedef int (*prog_builtin_t)(struct qc_program_s *prog); +struct qc_program_t; +typedef int (*prog_builtin_t)(qc_program_t *prog); -typedef struct { - qcint_t stmt; - size_t localsp; +struct qc_exec_stack_t { + qcint_t stmt; + size_t localsp; prog_section_function_t *function; -} qc_exec_stack_t; +}; -typedef struct qc_program_s { - char *filename; - prog_section_statement_t *code; - prog_section_def_t *defs; - prog_section_def_t *fields; - prog_section_function_t *functions; - char *strings; - qcint_t *globals; - qcint_t *entitydata; - bool *entitypool; +struct qc_program_t { + char *filename; + std::vector code; + std::vector defs; + std::vector fields; + std::vector functions; + std::vector strings; + std::vector globals; + qcint_t *entitydata; + bool *entitypool; - const char* *function_stack; + const char* *function_stack; uint16_t crc16; @@ -839,7 +720,7 @@ typedef struct qc_program_s { } cached_globals; bool supports_state; /* is INSTR_STATE supported? */ -} qc_program_t; +}; qc_program_t* prog_load (const char *filename, bool ignoreversion); void prog_delete (qc_program_t *prog); @@ -848,27 +729,27 @@ const char* prog_getstring (qc_program_t *prog, qcint_t str); prog_section_def_t* prog_entfield (qc_program_t *prog, qcint_t off); prog_section_def_t* prog_getdef (qc_program_t *prog, qcint_t off); qcany_t* prog_getedict (qc_program_t *prog, qcint_t e); -qcint_t prog_tempstring(qc_program_t *prog, const char *_str); +qcint_t prog_tempstring(qc_program_t *prog, const char *_str); /* parser.c */ -struct parser_s; -struct parser_s *parser_create (void); -bool parser_compile_file (struct parser_s *parser, const char *); -bool parser_compile_string(struct parser_s *parser, const char *, const char *, size_t); -bool parser_finish (struct parser_s *parser, const char *); -void parser_cleanup (struct parser_s *parser); +struct parser_t; +parser_t *parser_create(void); +bool parser_compile_file(parser_t *parser, const char *); +bool parser_compile_string(parser_t *parser, const char *, const char *, size_t); +bool parser_finish(parser_t *parser, const char *); +void parser_cleanup(parser_t *parser); /* ftepp.c */ -struct ftepp_s; -struct ftepp_s *ftepp_create (void); -bool ftepp_preprocess_file (struct ftepp_s *ftepp, const char *filename); -bool ftepp_preprocess_string(struct ftepp_s *ftepp, const char *name, const char *str); -void ftepp_finish (struct ftepp_s *ftepp); -const char *ftepp_get (struct ftepp_s *ftepp); -void ftepp_flush (struct ftepp_s *ftepp); -void ftepp_add_define (struct ftepp_s *ftepp, const char *source, const char *name); -void ftepp_add_macro (struct ftepp_s *ftepp, const char *name, const char *value); +struct ftepp_t; +ftepp_t *ftepp_create (void); +bool ftepp_preprocess_file (ftepp_t *ftepp, const char *filename); +bool ftepp_preprocess_string(ftepp_t *ftepp, const char *name, const char *str); +void ftepp_finish(ftepp_t *ftepp); +const char *ftepp_get(ftepp_t *ftepp); +void ftepp_flush(ftepp_t *ftepp); +void ftepp_add_define(ftepp_t *ftepp, const char *source, const char *name); +void ftepp_add_macro(ftepp_t *ftepp, const char *name, const char *value); /* main.c */ @@ -876,10 +757,10 @@ void ftepp_add_macro (struct ftepp_s *ftepp, const char *name, /* Helpers to allow for a whole lot of flags. Otherwise we'd limit * to 32 or 64 -f options... */ -typedef struct { +struct longbit { size_t idx; /* index into an array of 32 bit words */ uint8_t bit; /* bit index for the 8 bit group idx points to */ -} longbit; +}; #define LONGBIT(bit) { ((bit)/32), ((bit)%32) } #define LONGBIT_SET(B, I) ((B).idx = (I)/32, (B).bit = ((I)%32)) #else @@ -894,10 +775,10 @@ int utf8_from(char *, utf8ch_t); int utf8_to(utf8ch_t *, const unsigned char *, size_t); /* opts.c */ -typedef struct { +struct opts_flag_def_t { const char *name; longbit bit; -} opts_flag_def_t; +}; bool opts_setflag (const char *, bool); bool opts_setwarn (const char *, bool); @@ -951,30 +832,27 @@ extern const unsigned int opts_opt_oflag[COUNT_OPTIMIZATIONS+1]; extern unsigned int opts_optimizationcount[COUNT_OPTIMIZATIONS]; /* other options: */ -typedef enum { +enum { COMPILER_QCC, /* circa QuakeC */ COMPILER_FTEQCC, /* fteqcc QuakeC */ COMPILER_QCCX, /* qccx QuakeC */ COMPILER_GMQCC /* this QuakeC */ -} opts_std_t; +}; -typedef struct { +struct opt_value_t { union { - bool b; + bool b; uint16_t u16; uint32_t u32; - union { - char *p; + char *p; const char *c; } str; } data; - bool allocated; -} opt_value_t; - +}; -typedef struct { +struct opts_cmd_t { opt_value_t options [OPTION_COUNT]; uint32_t flags [1 + (COUNT_FLAGS / 32)]; uint32_t warn [1 + (COUNT_WARNINGS / 32)]; @@ -983,7 +861,7 @@ typedef struct { uint32_t werror_backup[1 + (COUNT_WARNINGS / 32)]; uint32_t optimization [1 + (COUNT_OPTIMIZATIONS / 32)]; bool optimizeoff; /* True when -O0 */ -} opts_cmd_t; +}; extern opts_cmd_t opts; diff --git a/hash.c b/hash.c deleted file mode 100644 index 9aba831..0000000 --- a/hash.c +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * 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 "gmqcc.h" -#include - -#if defined(_MSC_VER) -# define HASH_ROTL32(X, Y) _rotl((X), (Y)) -#elif defined (__GNUC__) && (defined(__i386__) || defined(__amd64__)) - static GMQCC_FORCEINLINE uint32_t hash_rotl32(volatile uint32_t x, int8_t r) { - __asm__ __volatile__ ("roll %1,%0" : "+r"(x) : "c"(r)); - return x; - } -# define HASH_ROTL32(X, Y) hash_rotl32((volatile uint32_t)(X), (Y)) -#else -# define HASH_ROTL32(X, Y) (((X) << (Y)) | ((X) >> (32 - (Y)))) -#endif - -/* - * This is a version of the Murmur3 hashing function optimized for various - * compilers/architectures. It uses the traditional Murmur2 mix staging - * but fixes the mix staging inner loops. - * - * Murmur 2 contains an inner loop such as: - * while (l >= 4) { - * u32 k = *(u32*)d; - * k *= m; - * k ^= k >> r; - * k *= m; - * - * h *= m; - * h ^= k; - * d += 4; - * l -= 4; - * } - * - * The two u32s that form the key are the same value for x - * this premix stage will perform the same results for both values. Unrolled - * this produces just: - * x *= m; - * x ^= x >> r; - * x *= m; - * - * h *= m; - * h ^= x; - * h *= m; - * h ^= x; - * - * This appears to be fine, except what happens when m == 1? well x - * cancels out entierly, leaving just: - * x ^= x >> r; - * h ^= x; - * h ^= x; - * - * So all keys hash to the same value, but how often does m == 1? - * well, it turns out testing x for all possible values yeilds only - * 172,013,942 unique results instead of 2^32. So nearly ~4.6 bits - * are cancelled out on average! - * - * This means we have a 14.5% higher chance of collision. This is where - * Murmur3 comes in to save the day. - */ -static GMQCC_FORCEINLINE uint32_t hash_murmur_mix32(uint32_t hash) { - hash ^= hash >> 16; - hash *= 0x85EBCA6B; - hash ^= hash >> 13; - hash *= 0xC2B2AE35; - hash ^= hash >> 16; - return hash; -} - -/* - * These constants were calculated with SMHasher to determine the best - * case senario for Murmur3: - * http://code.google.com/p/smhasher/ - */ -#define HASH_MURMUR_MASK1 0xCC9E2D51 -#define HASH_MURMUR_MASK2 0x1B873593 -#define HASH_MURMUR_SEED 0x9747B28C - -#if PLATFORM_BYTE_ORDER == GMQCC_BYTE_ORDER_LITTLE -# define HASH_MURMUR_SAFEREAD(PTR) (*((uint32_t*)(PTR))) -#elif PLATFORM_BYTE_ORDER == GMQCC_BYTE_ORDER_BIG -# if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR >= 3)) -# define HASH_MURMUR_SAFEREAD(PTR) (__builtin_bswap32(*((uint32_t*)(PTR)))) -# endif -#endif -/* Process individual bytes at this point since the endianess isn't known. */ -#ifndef HASH_MURMUR_SAFEREAD -# define HASH_MURMUR_SAFEREAD(PTR) ((PTR)[0] | (PTR)[1] << 8 | (PTR)[2] << 16 | (PTR)[3] << 24) -#endif - -#define HASH_MURMUR_BLOCK(H, K) \ - do { \ - K *= HASH_MURMUR_MASK1; \ - K = HASH_ROTL32(K, 15); \ - K *= HASH_MURMUR_MASK2; \ - H ^= K; \ - H = HASH_ROTL32(H, 13); \ - H = H * 5 + 0xE6546B64; \ - } while (0) -#define HASH_MURMUR_BYTES(COUNT, H, C, N, PTR, LENGTH) \ - do { \ - int i = COUNT; \ - while (i--) { \ - C = C >> 8 | *PTR++ << 24; \ - N++; \ - LENGTH--; \ - if (N == 4) { \ - HASH_MURMUR_BLOCK(H, C); \ - N = 0; \ - } \ - } \ - } while (0) -#define HASH_MURMUR_TAIL(P, Z, H, C, N, PTR, LEN) \ - do { \ - LEN -= LEN/4*4; \ - HASH_MURMUR_BYTES(LEN, H, C, N, PTR, LEN); \ - *P = H; \ - *Z = ((C) & ~0xFF) | (N); \ - } while (0) - -#if PLATFORM_BYTE_ORDER == GMQCC_BYTE_ORDER_LITTLE -static GMQCC_FORCEINLINE void hash_murmur_process(uint32_t *ph1, uint32_t *carry, const void *key, int length) { - uint32_t h1 = *ph1; - uint32_t c = *carry; - - const uint8_t *ptr = (uint8_t*)key; - const uint8_t *end; - - int n = c & 3; - int it = (4 - n) & 3; - if (it && it <= length) - HASH_MURMUR_BYTES(it, h1, c, n, ptr, length); - - end = ptr + length/4*4; - for (; ptr < end; ptr += 4) { - uint32_t k1 = HASH_MURMUR_SAFEREAD(ptr); - HASH_MURMUR_BLOCK(h1, k1); - } - HASH_MURMUR_TAIL(ph1, carry, h1, c, n, ptr, length); -} -#else -static GMQCC_FORCEINLINE void hash_murmur_process(uint32_t *ph1, uint32_t *carry, const void *key, int length) { - uint32_t k1; - uint32_t h1 = *ph1; - uint32_t c = *carry; - - const uint8_t *ptr = (uint8_t*)key; - const uint8_t *end; - - int n = c & 3; - int it = -(long)ptr & 3; - if (it && it <= length) - HASH_MURMUR_BYTES(it, h1, c, n, ptr, length); - - end = ptr + length / 4 * 4; - switch (n) { - case 0: - for (; ptr < end; ptr += 4) { - k1 = HASH_MURMUR_SAFEREAD(ptr); - HASH_MURMUR_BLOCK(h1, k1); - } - break; - -# define NEXT(N, RIGHT, LEFT) \ - case N: \ - for (; ptr < end; ptr += 4) { \ - k1 = c >> RIGHT; \ - c = HASH_MURMUR_SAFEREAD(ptr); \ - k1 |= c << LEFT; \ - HASH_MURMUR_BLOCK(h1, k1); \ - } \ - break - NEXT(1, 24, 8); - NEXT(2, 16, 16); - NEXT(3, 8, 24); - #undef NEXT - } - HASH_MURMUR_TAIL(ph1, carry, h1, c, n, ptr, length); -} -#endif - -static GMQCC_FORCEINLINE uint32_t hash_murmur_result(uint32_t hash, uint32_t carry, size_t length) { - uint32_t k1; - int n = carry & 3; - if (GMQCC_LIKELY(n)) { - k1 = carry >> (4 - n) * 8; - k1 *= HASH_MURMUR_MASK1; - k1 = HASH_ROTL32(k1, 15); - k1 *= HASH_MURMUR_MASK2; - hash ^= k1; - } - hash ^= length; - hash = hash_murmur_mix32(hash); - - return hash; -} - -static GMQCC_FORCEINLINE uint32_t hash_murmur(const void *GMQCC_RESTRICT key, size_t length) { - uint32_t hash = HASH_MURMUR_SEED; - uint32_t carry = 0; - hash_murmur_process(&hash, &carry, key, length); - return hash_murmur_result(hash, carry, length); -} - -/* - * The following hash function implements it's own strlen to avoid using libc's - * which isn't always slow but isn't always fastest either. - * - * Some things to note about this strlen that are otherwise confusing to grasp - * at first is that it does intentionally depend on undefined behavior. - * - * The first step to the strlen is to ensure alignment before checking words, - * without this step we risk crossing a page boundry with the word check and - * that would cause a crash. - * - * The second step to the strlen contains intentional undefined behavior. When - * accessing a word of any size, the first byte of that word is accessible if - * and only if the whole word is accessible because words are aligned. This is - * indicated by the fact that size / alignment always divides the page size. - * One could argue that an architecture exists where size_t and alignment are - * different, if that were the case, the alignment will always assume to be the - * size of the type (size_t). So it's always safe in that regard. - * - * In other words, an aligned 2^n load cannot cross a page boundry unless - * n > log2(PAGE_SIZE). There are no known architectures which support such - * a wide load larger than PAGE_SIZE. - * - * Valgrind and address sanatizer may choke on this because they're strictly - * trying to find bugs, it's a false positive to assume this is a bug when it's - * intentional. To prevent these false positives, both things need to be taught - * about the intentional behavior; for address sanatizer this can be done with - * a compiler attribute, effectively preventing the function from being - * instrumented. Valgrind requires a little more work as there is no way to - * downright prevent a function from being instrumented, instead we can mark - * + sizeof(size_t) bytes ahead of each byte we're reading as we calculate - * the length of the string, then we can make that additional + sizeof(size_t) - * on the end undefined after the length has been calculated. - * - * If the compiler doesn't have the attribute to disable address sanatizer - * instrumentation we fall back to using libc's strlen instead. This isn't the - * best solution. On windows we can assume this method always because neither - * address sanatizer or valgrind exist. - */ - -/* Some compilers expose this */ -#if defined(__has_feature) -# if __has_feature(address_sanitizer) -# define ASAN_DISABLE __attribute__((no_sanitize_address)) -# define HAS_ASAN_DISABLE -# endif -#endif - -/* If they don't try to find by version the attriubte was introduces */ -#if defined(__GNUC__) && __GNUC__ >= 4 -# define ASAN_DISABLE __attribute__((no_sanitize_address)) -# define HAS_ASAN_DISABLE -#elif defined(__clang__) && __clang_major__ >= 3 -# define ASAN_DISABLE __attribute__((no_sanatize_address)) -# define HAS_ASAN_DISABLE -/* On windows asan doesn't exist */ -#elif defined(_WIN32) -# define ASAN_DISABLE /* nothing */ -# define HAS_ASAN_DISABLE -#endif - -#ifndef HAS_ASAN_DISABLE -# include -#endif - -#ifndef NVALGRIND -# include -# include -#else -# define VALGRIND_MAKE_MEM_DEFINED(PTR, REDZONE_SIZE) -# define VALGRIND_MAKE_MEM_NOACCESS(PTR, REDZONE_SIZE) -#endif - -#ifdef HAS_ASAN_DISABLE -#define STRLEN_ALIGN (sizeof(size_t)) -#define STRLEN_ONES ((size_t)-1/UCHAR_MAX) -#define STRLEN_HIGHS (STRLEN_ONES * (UCHAR_MAX/2+1)) -#define STRLEN_HASZERO(X) (((X)-STRLEN_ONES) & ~(X) & STRLEN_HIGHS) - -static ASAN_DISABLE size_t hash_strlen(const char *key) { - const char *s = key; - const char *a = s; - const size_t *w; - - for (; (uintptr_t)s % STRLEN_ALIGN; s++) { - if (*s) - continue; - return s-a; - } - - VALGRIND_MAKE_MEM_DEFINED(s, STRLEN_ALIGN); - for (w = (const size_t *)s; !STRLEN_HASZERO(*w); w++) { - /* Make the next word legal to access */ - VALGRIND_MAKE_MEM_DEFINED(w + STRLEN_ALIGN, STRLEN_ALIGN); - } - - for (s = (const char *)w; *s; s++); - - /* It's not legal to access this area anymore */ - VALGRIND_MAKE_MEM_NOACCESS(s + 1, STRLEN_ALIGN); - return s-a; -} -#else -static GMQCC_INLINE size_t hash_strlen(const char *key) { - return strlen(key); -} -#endif - -size_t hash(const char *key) { - return hash_murmur((const void *)key, hash_strlen(key)); -} diff --git a/include.mk b/include.mk deleted file mode 100644 index 1f90111..0000000 --- a/include.mk +++ /dev/null @@ -1,144 +0,0 @@ -# default directories and paths -DESTDIR := -PREFIX := /usr/local -BINDIR := $(PREFIX)/bin -DATADIR := $(PREFIX)/share -MANDIR := $(DATADIR)/man - -# default flags -CFLAGS += -Wall -Wextra -Werror -Wstrict-aliasing -Wno-attributes -O2 - -# compiler -CC ?= clang - -# linker flags and optional additional libraries if required -LDFLAGS += -LIBS += -lm - -#common objects -COMMON = ansi.o util.o hash.o stat.o fs.o opts.o conout.o - -#optional flags -OPTIONAL_CFLAGS := -OPTIONAL_LDFLAGS := - -#objects -OBJ_C = $(COMMON) main.o lexer.o parser.o code.o ast.o ir.o ftepp.o utf8.o correct.o fold.o intrin.o -OBJ_P = $(COMMON) pak.o -OBJ_T = $(COMMON) test.o -OBJ_X = $(COMMON) exec.o - -#gource flags -GOURCEFLAGS = \ - --date-format "%d %B, %Y" \ - --seconds-per-day 0.01 \ - --auto-skip-seconds 1 \ - --title "GMQCC" \ - --key \ - --camera-mode overview \ - --highlight-all-users \ - --file-idle-time 0 \ - --hide progress,mouse \ - --stop-at-end \ - --max-files 99999999999 \ - --max-file-lag 0.000001 \ - --bloom-multiplier 1.3 \ - --logo doc/html/gmqcc.png \ - -1280x720 - -#ffmpeg flags for gource -FFMPEGFLAGS= \ - -y \ - -r 60 \ - -f image2pipe \ - -vcodec ppm \ - -i - \ - -vcodec libx264 \ - -preset ultrafast \ - -crf 1 \ - -threads 0 \ - -bf 0 - -#splint flags -SPLINTFLAGS = \ - -preproc \ - -redef \ - -noeffect \ - -nullderef \ - -usedef \ - -type \ - -mustfreeonly \ - -nullstate \ - -varuse \ - -mustfreefresh \ - -compdestroy \ - -compmempass \ - -nullpass \ - -onlytrans \ - -predboolint \ - -boolops \ - -incondefs \ - -macroredef \ - -retvalint \ - -nullret \ - -predboolothers \ - -globstate \ - -dependenttrans \ - -branchstate \ - -compdef \ - -temptrans \ - -usereleased \ - -warnposix \ - +charindex \ - -kepttrans \ - -unqualifiedtrans \ - +matchanyintegral \ - +voidabstract \ - -nullassign \ - -unrecog \ - -casebreak \ - -retvalbool \ - -retvalother \ - -mayaliasunique \ - -realcompare \ - -observertrans \ - -abstract \ - -statictrans \ - -castfcnptr \ - -shiftimplementation \ - -shiftnegative \ - -boolcompare \ - -infloops \ - -sysunrecog - -#always the right rule -default: all - -#uninstall rule -uninstall: - rm -f $(DESTDIR)$(BINDIR)/gmqcc - rm -f $(DESTDIR)$(BINDIR)/qcvm - rm -f $(DESTDIR)$(BINDIR)/gmqpak - rm -f $(DESTDIR)$(MANDIR)/man1/gmqcc.1 - rm -f $(DESTDIR)$(MANDIR)/man1/qcvm.1 - rm -f $(DESTDIR)$(MANDIR)/man1/gmqpak.1 - -#style rule -STYLE_MATCH = \( -name '*.[ch]' -or -name '*.def' -or -name '*.qc' \) - -# splint cannot parse the MSVC source -SPLINT_MATCH = \( -name '*.[ch]' -and ! -name 'msvc.c' -and ! -path './doc/*' \) - -style: - find . -type f $(STYLE_MATCH) -exec sed -i 's/ *$$//' '{}' ';' - find . -type f $(STYLE_MATCH) -exec sed -i -e '$$a\' '{}' ';' - find . -type f $(STYLE_MATCH) -exec sed -i 's/\t/ /g' '{}' ';' - -splint: - @splint $(SPLINTFLAGS) `find . -type f $(SPLINT_MATCH)` - -gource: - @gource $(GOURCEFLAGS) - -gource-record: - @gource $(GOURCEFLAGS) -o - | ffmpeg $(FFMPEGFLAGS) gource.mp4 diff --git a/intrin.c b/intrin.c deleted file mode 100644 index 9ddb1a2..0000000 --- a/intrin.c +++ /dev/null @@ -1,2113 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * 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 -#include "parser.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. - */ -#define intrin_ctx(I) parser_ctx((I)->parser) - -static GMQCC_INLINE ast_function *intrin_value(intrin_t *intrin, ast_value **out, const char *name, qcint_t vtype) { - ast_value *value = NULL; - ast_function *func = NULL; - char buffer[1024]; - char stype [1024]; - - util_snprintf(buffer, sizeof(buffer), "__builtin_%s", name); - util_snprintf(stype, sizeof(stype), "<%s>", type_name[vtype]); - - value = ast_value_new(intrin_ctx(intrin), buffer, TYPE_FUNCTION); - value->intrinsic = true; - value->expression.next = (ast_expression*)ast_value_new(intrin_ctx(intrin), stype, vtype); - func = ast_function_new(intrin_ctx(intrin), buffer, value); - value->expression.flags |= AST_FLAG_ERASEABLE; - - *out = value; - return func; -} - -static GMQCC_INLINE void intrin_reg(intrin_t *intrin, ast_value *const value, ast_function *const func) { - vec_push(intrin->parser->functions, func); - vec_push(intrin->parser->globals, (ast_expression*)value); -} - -#define QC_POW_EPSILON 0.00001f - -/* - * since some intrinsics depend on each other there is the possibility - * that an intrinsic will fail to get a 'depended' function that a - * builtin needs, causing some dependency in the chain to have a NULL - * function. This will cause a segmentation fault at code generation, - * even though an error was raised. To contiue to allow it (instead - * of stopping compilation right away). We need to return from the - * parser, before compilation stops after all the collected errors. - */ -static ast_expression *intrin_func_self(intrin_t *intrin, const char *name, const char *from); -static ast_expression *intrin_nullfunc(intrin_t *intrin) { - ast_value *value = NULL; - ast_function *func = intrin_value(intrin, &value, NULL, TYPE_VOID); - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_isfinite(intrin_t *intrin) { - /* - * float isfinite(float x) { - * return !(isnan(x) || isinf(x)); - * } - */ - ast_value *value = NULL; - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_function *func = intrin_value(intrin, &value, "isfinite", TYPE_FLOAT); - ast_call *callisnan = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "isnan", "isfinite")); - ast_call *callisinf = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "isinf", "isfinite")); - ast_block *block = ast_block_new(intrin_ctx(intrin)); - - /* float x; */ - vec_push(value->expression.params, x); - - /* = isnan(x); */ - vec_push(callisnan->params, (ast_expression*)x); - - /* = isinf(x); */ - vec_push(callisinf->params, (ast_expression*)x); - - /* return (! || ); */ - vec_push(block->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_unary_new( - intrin_ctx(intrin), - INSTR_NOT_F, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_OR, - (ast_expression*)callisnan, - (ast_expression*)callisinf - ) - ) - ) - ); - - vec_push(func->blocks, block); - intrin_reg(intrin, value, func); - - return (ast_expression*)value;; -} - -static ast_expression *intrin_isinf(intrin_t *intrin) { - /* - * float isinf(float x) { - * return (x != 0.0) && (x + x == x); - * } - */ - ast_value *value = NULL; - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "isinf", TYPE_FLOAT); - - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_AND, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_NE_F, - (ast_expression*)x, - (ast_expression*)intrin->fold->imm_float[0] - ), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_EQ_F, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_ADD_F, - (ast_expression*)x, - (ast_expression*)x - ), - (ast_expression*)x - ) - ) - ) - ); - - vec_push(value->expression.params, x); - vec_push(func->blocks, body); - - intrin_reg(intrin, value, func); - - return (ast_expression*)value; -} - -static ast_expression *intrin_isnan(intrin_t *intrin) { - /* - * float isnan(float x) { - * float local; - * local = x; - * - * return (x != local); - * } - */ - ast_value *value = NULL; - ast_value *arg1 = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_value *local = ast_value_new(intrin_ctx(intrin), "local", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "isnan", TYPE_FLOAT); - - vec_push(body->locals, local); - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)local, - (ast_expression*)arg1 - ) - ); - - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_NE_F, - (ast_expression*)arg1, - (ast_expression*)local - ) - ) - ); - - vec_push(value->expression.params, arg1); - vec_push(func->blocks, body); - - intrin_reg(intrin, value, func); - - return (ast_expression*)value; -} - -static ast_expression *intrin_isnormal(intrin_t *intrin) { - /* - * float isnormal(float x) { - * return isfinite(x); - * } - */ - ast_value *value = NULL; - ast_call *callisfinite = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "isfinite", "isnormal")); - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "isnormal", TYPE_FLOAT); - - vec_push(value->expression.params, x); - vec_push(callisfinite->params, (ast_expression*)x); - - /* return */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)callisfinite - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_signbit(intrin_t *intrin) { - /* - * float signbit(float x) { - * return (x < 0); - * } - */ - ast_value *value = NULL; - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "signbit", TYPE_FLOAT); - - vec_push(value->expression.params, x); - - /* return (x < 0); */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_ternary_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LT, - (ast_expression*)x, - (ast_expression*)intrin->fold->imm_float[0] - ), - (ast_expression*)intrin->fold->imm_float[1], - (ast_expression*)intrin->fold->imm_float[0] - ) - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_acosh(intrin_t *intrin) { - /* - * float acosh(float x) { - * return log(x + sqrt((x * x) - 1)); - * } - */ - ast_value *value = NULL; - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_call *calllog = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "log", "acosh")); - ast_call *callsqrt = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "acosh")); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "acosh", TYPE_FLOAT); - - vec_push(value->expression.params, x); - - /* = sqrt((x * x) - 1); */ - vec_push(callsqrt->params, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_SUB_F, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)x, - (ast_expression*)x - ), - (ast_expression*)intrin->fold->imm_float[1] - ) - ); - - /* = log(x + ); */ - vec_push(calllog->params, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_ADD_F, - (ast_expression*)x, - (ast_expression*)callsqrt - ) - ); - - /* return ; */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)calllog - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_asinh(intrin_t *intrin) { - /* - * float asinh(float x) { - * return log(x + sqrt((x * x) + 1)); - * } - */ - ast_value *value = NULL; - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_call *calllog = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "log", "asinh")); - ast_call *callsqrt = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "asinh")); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "asinh", TYPE_FLOAT); - - vec_push(value->expression.params, x); - - /* = sqrt((x * x) + 1); */ - vec_push(callsqrt->params, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_ADD_F, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)x, - (ast_expression*)x - ), - (ast_expression*)intrin->fold->imm_float[1] - ) - ); - - /* = log(x + ); */ - vec_push(calllog->params, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_ADD_F, - (ast_expression*)x, - (ast_expression*)callsqrt - ) - ); - - /* return ; */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)calllog - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_atanh(intrin_t *intrin) { - /* - * float atanh(float x) { - * return 0.5 * log((1 + x) / (1 - x)) - * } - */ - ast_value *value = NULL; - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_call *calllog = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "log", "atanh")); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "atanh", TYPE_FLOAT); - - vec_push(value->expression.params, x); - - /* = log((1 + x) / (1 - x)); */ - vec_push(calllog->params, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_ADD_F, - (ast_expression*)intrin->fold->imm_float[1], - (ast_expression*)x - ), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_SUB_F, - (ast_expression*)intrin->fold->imm_float[1], - (ast_expression*)x - ) - ) - ); - - /* return 0.5 * ; */ - vec_push(body->exprs, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)fold_constgen_float(intrin->fold, 0.5, false), - (ast_expression*)calllog - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_exp(intrin_t *intrin) { - /* - * float exp(float x) { - * float sum = 1.0; - * float acc = 1.0; - * float i; - * for (i = 1; i < 200; ++i) - * sum += (acc *= x / i); - * - * return sum; - * } - */ - ast_value *value = NULL; - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_value *sum = ast_value_new(intrin_ctx(intrin), "sum", TYPE_FLOAT); - ast_value *acc = ast_value_new(intrin_ctx(intrin), "acc", TYPE_FLOAT); - ast_value *i = ast_value_new(intrin_ctx(intrin), "i", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "exp", TYPE_FLOAT); - - vec_push(value->expression.params, x); - vec_push(body->locals, sum); - vec_push(body->locals, acc); - vec_push(body->locals, i); - - /* sum = 1.0; */ - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)sum, - (ast_expression*)intrin->fold->imm_float[1] - ) - ); - - /* acc = 1.0; */ - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)acc, - (ast_expression*)intrin->fold->imm_float[1] - ) - ); - - /* - * for (i = 1; i < 200; ++i) - * sum += (acc *= x / i); - */ - vec_push(body->exprs, - (ast_expression*)ast_loop_new( - intrin_ctx(intrin), - /* i = 1; */ - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)i, - (ast_expression*)intrin->fold->imm_float[1] - ), - /* i < 200; */ - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LT, - (ast_expression*)i, - (ast_expression*)fold_constgen_float(intrin->fold, 200.0f, false) - ), - false, - NULL, - false, - /* ++i; */ - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_ADD_F, - (ast_expression*)i, - (ast_expression*)intrin->fold->imm_float[1] - ), - /* sum += (acc *= (x / i)) */ - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_ADD_F, - (ast_expression*)sum, - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_MUL_F, - (ast_expression*)acc, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)x, - (ast_expression*)i - ) - ) - ) - ) - ); - - /* return sum; */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)sum - ) - ); - - vec_push(func->blocks, body); - - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_exp2(intrin_t *intrin) { - /* - * float exp2(float x) { - * return pow(2, x); - * } - */ - ast_value *value = NULL; - ast_call *callpow = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "pow", "exp2")); - ast_value *arg1 = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "exp2", TYPE_FLOAT); - - vec_push(value->expression.params, arg1); - - vec_push(callpow->params, (ast_expression*)intrin->fold->imm_float[3]); - vec_push(callpow->params, (ast_expression*)arg1); - - /* return */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)callpow - ) - ); - - vec_push(func->blocks, body); - - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_expm1(intrin_t *intrin) { - /* - * float expm1(float x) { - * return exp(x) - 1; - * } - */ - ast_value *value = NULL; - ast_call *callexp = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "exp", "expm1")); - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "expm1", TYPE_FLOAT); - - vec_push(value->expression.params, x); - - /* = exp(x); */ - vec_push(callexp->params, (ast_expression*)x); - - /* return - 1; */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_SUB_F, - (ast_expression*)callexp, - (ast_expression*)intrin->fold->imm_float[1] - ) - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_pow(intrin_t *intrin) { - /* - * - * float pow(float base, float exp) { - * float result; - * float low; - * float high; - * float mid; - * float square; - * float accumulate; - * - * if (exp == 0.0) - * return 1; - * if (exp == 1.0) - * return base; - * if (exp < 0) - * return 1.0 / pow(base, -exp); - * if (exp >= 1) { - * result = pow(base, exp / 2); - * return result * result; - * } - * - * low = 0.0f; - * high = 1.0f; - * square = sqrt(base); - * accumulate = square; - * mid = high / 2.0f - * - * while (fabs(mid - exp) > QC_POW_EPSILON) { - * square = sqrt(square); - * if (mid < exp) { - * low = mid; - * accumulate *= square; - * } else { - * high = mid; - * accumulate *= (1.0f / square); - * } - * mid = (low + high) / 2; - * } - * return accumulate; - * } - */ - ast_value *value = NULL; - ast_function *func = intrin_value(intrin, &value, "pow", TYPE_FLOAT); - - /* prepare some calls for later */ - ast_call *callpow1 = ast_call_new(intrin_ctx(intrin), (ast_expression*)value); /* for pow(base, -exp) */ - ast_call *callpow2 = ast_call_new(intrin_ctx(intrin), (ast_expression*)value); /* for pow(vase, exp / 2) */ - ast_call *callsqrt1 = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "pow")); /* for sqrt(base) */ - ast_call *callsqrt2 = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "pow")); /* for sqrt(square) */ - ast_call *callfabs = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "fabs", "pow")); /* for fabs(mid - exp) */ - - /* prepare some blocks for later */ - ast_block *expgt1 = ast_block_new(intrin_ctx(intrin)); - ast_block *midltexp = ast_block_new(intrin_ctx(intrin)); - ast_block *midltexpelse = ast_block_new(intrin_ctx(intrin)); - ast_block *whileblock = ast_block_new(intrin_ctx(intrin)); - - /* float pow(float base, float exp) */ - ast_value *base = ast_value_new(intrin_ctx(intrin), "base", TYPE_FLOAT); - ast_value *exp = ast_value_new(intrin_ctx(intrin), "exp", TYPE_FLOAT); - /* { */ - ast_block *body = ast_block_new(intrin_ctx(intrin)); - - /* - * float result; - * float low; - * float high; - * float square; - * float accumulate; - * float mid; - */ - ast_value *result = ast_value_new(intrin_ctx(intrin), "result", TYPE_FLOAT); - ast_value *low = ast_value_new(intrin_ctx(intrin), "low", TYPE_FLOAT); - ast_value *high = ast_value_new(intrin_ctx(intrin), "high", TYPE_FLOAT); - ast_value *square = ast_value_new(intrin_ctx(intrin), "square", TYPE_FLOAT); - ast_value *accumulate = ast_value_new(intrin_ctx(intrin), "accumulate", TYPE_FLOAT); - ast_value *mid = ast_value_new(intrin_ctx(intrin), "mid", TYPE_FLOAT); - vec_push(body->locals, result); - vec_push(body->locals, low); - vec_push(body->locals, high); - vec_push(body->locals, square); - vec_push(body->locals, accumulate); - vec_push(body->locals, mid); - - vec_push(value->expression.params, base); - vec_push(value->expression.params, exp); - - /* - * if (exp == 0.0) - * return 1; - */ - vec_push(body->exprs, - (ast_expression*)ast_ifthen_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_EQ_F, - (ast_expression*)exp, - (ast_expression*)intrin->fold->imm_float[0] - ), - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)intrin->fold->imm_float[1] - ), - NULL - ) - ); - - /* - * if (exp == 1.0) - * return base; - */ - vec_push(body->exprs, - (ast_expression*)ast_ifthen_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_EQ_F, - (ast_expression*)exp, - (ast_expression*)intrin->fold->imm_float[1] - ), - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)base - ), - NULL - ) - ); - - /* = pow(base, -exp) */ - vec_push(callpow1->params, (ast_expression*)base); - vec_push(callpow1->params, - (ast_expression*)ast_unary_new( - intrin_ctx(intrin), - VINSTR_NEG_F, - (ast_expression*)exp - ) - ); - - /* - * if (exp < 0) - * return 1.0 / ; - */ - vec_push(body->exprs, - (ast_expression*)ast_ifthen_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LT, - (ast_expression*)exp, - (ast_expression*)intrin->fold->imm_float[0] - ), - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)intrin->fold->imm_float[1], - (ast_expression*)callpow1 - ) - ), - NULL - ) - ); - - /* = pow(base, exp / 2) */ - vec_push(callpow2->params, (ast_expression*)base); - vec_push(callpow2->params, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)exp, - (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */ - ) - ); - - /* - * = { - * result = ; - * return result * result; - * } - */ - vec_push(expgt1->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)result, - (ast_expression*)callpow2 - ) - ); - vec_push(expgt1->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)result, - (ast_expression*)result - ) - ) - ); - - /* - * if (exp >= 1) { - * - * } - */ - vec_push(body->exprs, - (ast_expression*)ast_ifthen_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_GE, - (ast_expression*)exp, - (ast_expression*)intrin->fold->imm_float[1] - ), - (ast_expression*)expgt1, - NULL - ) - ); - - /* - * = sqrt(base) - */ - vec_push(callsqrt1->params, (ast_expression*)base); - - /* - * low = 0.0f; - * high = 1.0f; - * square = sqrt(base); - * accumulate = square; - * mid = high / 2.0f; - */ - vec_push(body->exprs, - (ast_expression*)ast_store_new(intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)low, - (ast_expression*)intrin->fold->imm_float[0] - ) - ); - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)high, - (ast_expression*)intrin->fold->imm_float[1] - ) - ); - - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)square, - (ast_expression*)callsqrt1 - ) - ); - - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)accumulate, - (ast_expression*)square - ) - ); - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)mid, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)high, - (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */ - ) - ) - ); - - /* - * = { - * low = mid; - * accumulate *= square; - * } - */ - vec_push(midltexp->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)low, - (ast_expression*)mid - ) - ); - vec_push(midltexp->exprs, - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_MUL_F, - (ast_expression*)accumulate, - (ast_expression*)square - ) - ); - - /* - * = { - * high = mid; - * accumulate *= (1.0 / square); - * } - */ - vec_push(midltexpelse->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)high, - (ast_expression*)mid - ) - ); - vec_push(midltexpelse->exprs, - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_MUL_F, - (ast_expression*)accumulate, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)intrin->fold->imm_float[1], - (ast_expression*)square - ) - ) - ); - - /* - * = sqrt(square) - */ - vec_push(callsqrt2->params, (ast_expression*)square); - - /* - * = { - * square = ; - * if (mid < exp) - * ; - * else - * ; - * - * mid = (low + high) / 2; - * } - */ - vec_push(whileblock->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)square, - (ast_expression*)callsqrt2 - ) - ); - vec_push(whileblock->exprs, - (ast_expression*)ast_ifthen_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LT, - (ast_expression*)mid, - (ast_expression*)exp - ), - (ast_expression*)midltexp, - (ast_expression*)midltexpelse - ) - ); - vec_push(whileblock->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)mid, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_ADD_F, - (ast_expression*)low, - (ast_expression*)high - ), - (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */ - ) - ) - ); - - /* - * = fabs(mid - exp) - */ - vec_push(callfabs->params, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_SUB_F, - (ast_expression*)mid, - (ast_expression*)exp - ) - ); - - /* - * while ( > epsilon) - * - */ - vec_push(body->exprs, - (ast_expression*)ast_loop_new( - intrin_ctx(intrin), - /* init */ - NULL, - /* pre condition */ - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_GT, - (ast_expression*)callfabs, - (ast_expression*)fold_constgen_float(intrin->fold, QC_POW_EPSILON, false) - ), - /* pre not */ - false, - /* post condition */ - NULL, - /* post not */ - false, - /* increment expression */ - NULL, - /* code block */ - (ast_expression*)whileblock - ) - ); - - /* return accumulate */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)accumulate - ) - ); - - /* } */ - vec_push(func->blocks, body); - - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_mod(intrin_t *intrin) { - /* - * float mod(float a, float b) { - * float div = a / b; - * float sign = (div < 0.0f) ? -1 : 1; - * return a - b * sign * floor(sign * div); - * } - */ - ast_value *value = NULL; - ast_call *call = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "floor", "mod")); - ast_value *a = ast_value_new(intrin_ctx(intrin), "a", TYPE_FLOAT); - ast_value *b = ast_value_new(intrin_ctx(intrin), "b", TYPE_FLOAT); - ast_value *div = ast_value_new(intrin_ctx(intrin), "div", TYPE_FLOAT); - ast_value *sign = ast_value_new(intrin_ctx(intrin), "sign", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "mod", TYPE_FLOAT); - - vec_push(value->expression.params, a); - vec_push(value->expression.params, b); - - vec_push(body->locals, div); - vec_push(body->locals, sign); - - /* div = a / b; */ - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)div, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)a, - (ast_expression*)b - ) - ) - ); - - /* sign = (div < 0.0f) ? -1 : 1; */ - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)sign, - (ast_expression*)ast_ternary_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LT, - (ast_expression*)div, - (ast_expression*)intrin->fold->imm_float[0] - ), - (ast_expression*)intrin->fold->imm_float[2], - (ast_expression*)intrin->fold->imm_float[1] - ) - ) - ); - - /* floor(sign * div) */ - vec_push(call->params, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)sign, - (ast_expression*)div - ) - ); - - /* return a - b * sign * */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_SUB_F, - (ast_expression*)a, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)b, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)sign, - (ast_expression*)call - ) - ) - ) - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - - return (ast_expression*)value; -} - -static ast_expression *intrin_fabs(intrin_t *intrin) { - /* - * float fabs(float x) { - * return x < 0 ? -x : x; - * } - */ - ast_value *value = NULL; - ast_value *arg1 = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "fabs", TYPE_FLOAT); - - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_ternary_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LE, - (ast_expression*)arg1, - (ast_expression*)intrin->fold->imm_float[0] - ), - (ast_expression*)ast_unary_new( - intrin_ctx(intrin), - VINSTR_NEG_F, - (ast_expression*)arg1 - ), - (ast_expression*)arg1 - ) - ) - ); - - vec_push(value->expression.params, arg1); - vec_push(func->blocks, body); - - intrin_reg(intrin, value, func); - - return (ast_expression*)value; -} - -static ast_expression *intrin_epsilon(intrin_t *intrin) { - /* - * float epsilon(void) { - * float eps = 1.0f; - * do { eps /= 2.0f; } while ((1.0f + (eps / 2.0f)) != 1.0f); - * return eps; - * } - */ - ast_value *value = NULL; - ast_value *eps = ast_value_new(intrin_ctx(intrin), "eps", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "epsilon", TYPE_FLOAT); - - vec_push(body->locals, eps); - - /* eps = 1.0f; */ - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)eps, - (ast_expression*)intrin->fold->imm_float[0] - ) - ); - - vec_push(body->exprs, - (ast_expression*)ast_loop_new( - intrin_ctx(intrin), - NULL, - NULL, - false, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_NE_F, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_ADD_F, - (ast_expression*)intrin->fold->imm_float[1], - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)eps, - (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */ - ) - ), - (ast_expression*)intrin->fold->imm_float[1] - ), - false, - NULL, - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_DIV_F, - (ast_expression*)eps, - (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */ - ) - ) - ); - - /* return eps; */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)eps - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - - return (ast_expression*)value; -} - -static ast_expression *intrin_nan(intrin_t *intrin) { - /* - * float nan(void) { - * float x = 0.0f; - * return x / x; - * } - */ - ast_value *value = NULL; - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_function *func = intrin_value(intrin, &value, "nan", TYPE_FLOAT); - ast_block *block = ast_block_new(intrin_ctx(intrin)); - - vec_push(block->locals, x); - - vec_push(block->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)x, - (ast_expression*)intrin->fold->imm_float[0] - ) - ); - - vec_push(block->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)x, - (ast_expression*)x - ) - ) - ); - - vec_push(func->blocks, block); - intrin_reg(intrin, value, func); - - return (ast_expression*)value; -} - -static ast_expression *intrin_inf(intrin_t *intrin) { - /* - * float inf(void) { - * float x = 1.0f; - * float y = 0.0f; - * return x / y; - * } - */ - ast_value *value = NULL; - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_value *y = ast_value_new(intrin_ctx(intrin), "y", TYPE_FLOAT); - ast_function *func = intrin_value(intrin, &value, "inf", TYPE_FLOAT); - ast_block *block = ast_block_new(intrin_ctx(intrin)); - size_t i; - - vec_push(block->locals, x); - vec_push(block->locals, y); - - /* to keep code size down */ - for (i = 0; i <= 1; i++) { - vec_push(block->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)((i == 0) ? x : y), - (ast_expression*)intrin->fold->imm_float[i] - ) - ); - } - - vec_push(block->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)x, - (ast_expression*)y - ) - ) - ); - - vec_push(func->blocks, block); - intrin_reg(intrin, value, func); - - return (ast_expression*)value; -} - -static ast_expression *intrin_ln(intrin_t *intrin) { - /* - * float log(float power, float base) { - * float whole; - * float nth - * float sign = 1.0f; - * float eps = epsilon(); - * - * if (power <= 1.0f || bbase <= 1.0) { - * if (power <= 0.0f || base <= 0.0f) - * return nan(); - * - * if (power < 1.0f) { - * power = 1.0f / power; - * sign *= -1.0f; - * } - * - * if (base < 1.0f) { - * sign *= -1.0f; - * base = 1.0f / base; - * } - * } - * - * float A_i = 1; - * float B_i = 0; - * float A_iminus1 = 0; - * float B_iminus1 = 1; - * - * for (;;) { - * whole = power; - * nth = 0.0f; - * - * while (whole >= base) { - * float base2 = base; - * float n2 = 1.0f; - * float newbase2 = base2 * base2; - * - * while (whole >= newbase2) { - * base2 = newbase2; - * n2 *= 2; - * newbase2 *= newbase2; - * } - * - * whole /= base2; - * nth += n2; - * } - * - * float b_iplus1 = n; - * float A_iplus1 = b_iplus1 * A_i + A_iminus1; - * float B_iplus1 = b_iplus1 * B_i + B_iminus1; - * - * A_iminus1 = A_i; - * B_iminus1 = B_i; - * A_i = A_iplus1; - * B_i = B_iplus1; - * - * if (whole <= 1.0f + eps) - * break; - * - * power = base; - * bower = whole; - * } - * return sign * A_i / B_i; - * } - */ - - ast_value *value = NULL; - ast_value *power = ast_value_new(intrin_ctx(intrin), "power", TYPE_FLOAT); - ast_value *base = ast_value_new(intrin_ctx(intrin), "base", TYPE_FLOAT); - ast_value *whole = ast_value_new(intrin_ctx(intrin), "whole", TYPE_FLOAT); - ast_value *nth = ast_value_new(intrin_ctx(intrin), "nth", TYPE_FLOAT); - ast_value *sign = ast_value_new(intrin_ctx(intrin), "sign", TYPE_FLOAT); - ast_value *A_i = ast_value_new(intrin_ctx(intrin), "A_i", TYPE_FLOAT); - ast_value *B_i = ast_value_new(intrin_ctx(intrin), "B_i", TYPE_FLOAT); - ast_value *A_iminus1 = ast_value_new(intrin_ctx(intrin), "A_iminus1", TYPE_FLOAT); - ast_value *B_iminus1 = ast_value_new(intrin_ctx(intrin), "B_iminus1", TYPE_FLOAT); - ast_value *b_iplus1 = ast_value_new(intrin_ctx(intrin), "b_iplus1", TYPE_FLOAT); - ast_value *A_iplus1 = ast_value_new(intrin_ctx(intrin), "A_iplus1", TYPE_FLOAT); - ast_value *B_iplus1 = ast_value_new(intrin_ctx(intrin), "B_iplus1", TYPE_FLOAT); - ast_value *eps = ast_value_new(intrin_ctx(intrin), "eps", TYPE_FLOAT); - ast_value *base2 = ast_value_new(intrin_ctx(intrin), "base2", TYPE_FLOAT); - ast_value *n2 = ast_value_new(intrin_ctx(intrin), "n2", TYPE_FLOAT); - ast_value *newbase2 = ast_value_new(intrin_ctx(intrin), "newbase2", TYPE_FLOAT); - ast_block *block = ast_block_new(intrin_ctx(intrin)); - ast_block *plt1orblt1 = ast_block_new(intrin_ctx(intrin)); /* (power <= 1.0f || base <= 1.0f) */ - ast_block *plt1 = ast_block_new(intrin_ctx(intrin)); /* (power < 1.0f) */ - ast_block *blt1 = ast_block_new(intrin_ctx(intrin)); /* (base < 1.0f) */ - ast_block *forloop = ast_block_new(intrin_ctx(intrin)); /* for(;;) */ - ast_block *whileloop = ast_block_new(intrin_ctx(intrin)); /* while (whole >= base) */ - ast_block *nestwhile = ast_block_new(intrin_ctx(intrin)); /* while (whole >= newbase2) */ - ast_function *func = intrin_value(intrin, &value, "ln", TYPE_FLOAT); - size_t i; - - vec_push(value->expression.params, power); - vec_push(value->expression.params, base); - - vec_push(block->locals, whole); - vec_push(block->locals, nth); - vec_push(block->locals, sign); - vec_push(block->locals, eps); - vec_push(block->locals, A_i); - vec_push(block->locals, B_i); - vec_push(block->locals, A_iminus1); - vec_push(block->locals, B_iminus1); - - /* sign = 1.0f; */ - vec_push(block->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)sign, - (ast_expression*)intrin->fold->imm_float[1] - ) - ); - - /* eps = __builtin_epsilon(); */ - vec_push(block->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)eps, - (ast_expression*)ast_call_new( - intrin_ctx(intrin), - intrin_func_self(intrin, "__builtin_epsilon", "ln") - ) - ) - ); - - /* - * A_i = 1; - * B_i = 0; - * A_iminus1 = 0; - * B_iminus1 = 1; - */ - for (i = 0; i <= 1; i++) { - int j; - for (j = 1; j >= 0; j--) { - vec_push(block->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)((j) ? ((i) ? B_iminus1 : A_i) - : ((i) ? A_iminus1 : B_i)), - (ast_expression*)intrin->fold->imm_float[j] - ) - ); - } - } - - /* - * = { - * power = 1.0f / power; - * sign *= -1.0f; - * } - * = { - * base = 1.0f / base; - * sign *= -1.0f; - * } - */ - for (i = 0; i <= 1; i++) { - vec_push(((i) ? blt1 : plt1)->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)((i) ? base : power), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)intrin->fold->imm_float[1], - (ast_expression*)((i) ? base : power) - ) - ) - ); - vec_push(plt1->exprs, - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_MUL_F, - (ast_expression*)sign, - (ast_expression*)intrin->fold->imm_float[2] - ) - ); - } - - /* - * = { - * if (power <= 0.0 || base <= 0.0f) - * return __builtin_nan(); - * if (power < 1.0f) - * - * if (base < 1.0f) - * - * } - */ - vec_push(plt1orblt1->exprs, - (ast_expression*)ast_ifthen_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_OR, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LE, - (ast_expression*)power, - (ast_expression*)intrin->fold->imm_float[0] - ), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LE, - (ast_expression*)base, - (ast_expression*)intrin->fold->imm_float[0] - ) - ), - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_call_new( - intrin_ctx(intrin), - intrin_func_self(intrin, "__builtin_nan", "ln") - ) - ), - NULL - ) - ); - - for (i = 0; i <= 1; i++) { - vec_push(plt1orblt1->exprs, - (ast_expression*)ast_ifthen_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LT, - (ast_expression*)((i) ? base : power), - (ast_expression*)intrin->fold->imm_float[1] - ), - (ast_expression*)((i) ? blt1 : plt1), - NULL - ) - ); - } - - vec_push(block->exprs, (ast_expression*)plt1orblt1); - - - /* whole = power; */ - vec_push(forloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)whole, - (ast_expression*)power - ) - ); - - /* nth = 0.0f; */ - vec_push(forloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)nth, - (ast_expression*)intrin->fold->imm_float[0] - ) - ); - - /* base2 = base; */ - vec_push(whileloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)base2, - (ast_expression*)base - ) - ); - - /* n2 = 1.0f; */ - vec_push(whileloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)n2, - (ast_expression*)intrin->fold->imm_float[1] - ) - ); - - /* newbase2 = base2 * base2; */ - vec_push(whileloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)newbase2, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)base2, - (ast_expression*)base2 - ) - ) - ); - - /* while loop locals */ - vec_push(whileloop->locals, base2); - vec_push(whileloop->locals, n2); - vec_push(whileloop->locals, newbase2); - - /* base2 = newbase2; */ - vec_push(nestwhile->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)base2, - (ast_expression*)newbase2 - ) - ); - - /* n2 *= 2; */ - vec_push(nestwhile->exprs, - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_MUL_F, - (ast_expression*)n2, - (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */ - ) - ); - - /* newbase2 *= newbase2; */ - vec_push(nestwhile->exprs, - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_MUL_F, - (ast_expression*)newbase2, - (ast_expression*)newbase2 - ) - ); - - /* while (whole >= newbase2) */ - vec_push(whileloop->exprs, - (ast_expression*)ast_loop_new( - intrin_ctx(intrin), - NULL, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_GE, - (ast_expression*)whole, - (ast_expression*)newbase2 - ), - false, - NULL, - false, - NULL, - (ast_expression*)nestwhile - ) - ); - - /* whole /= base2; */ - vec_push(whileloop->exprs, - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_DIV_F, - (ast_expression*)whole, - (ast_expression*)base2 - ) - ); - - /* nth += n2; */ - vec_push(whileloop->exprs, - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_ADD_F, - (ast_expression*)nth, - (ast_expression*)n2 - ) - ); - - /* while (whole >= base) */ - vec_push(forloop->exprs, - (ast_expression*)ast_loop_new( - intrin_ctx(intrin), - NULL, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_GE, - (ast_expression*)whole, - (ast_expression*)base - ), - false, - NULL, - false, - NULL, - (ast_expression*)whileloop - ) - ); - - vec_push(forloop->locals, b_iplus1); - vec_push(forloop->locals, A_iplus1); - vec_push(forloop->locals, B_iplus1); - - /* b_iplus1 = nth; */ - vec_push(forloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)b_iplus1, - (ast_expression*)nth - ) - ); - - /* - * A_iplus1 = b_iplus1 * A_i + A_iminus1; - * B_iplus1 = b_iplus1 * B_i + B_iminus1; - */ - for (i = 0; i <= 1; i++) { - vec_push(forloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)((i) ? B_iplus1 : A_iplus1), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_ADD_F, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)b_iplus1, - (ast_expression*) ((i) ? B_i : A_i) - ), - (ast_expression*)((i) ? B_iminus1 : A_iminus1) - ) - ) - ); - } - - /* - * A_iminus1 = A_i; - * B_iminus1 = B_i; - */ - for (i = 0; i <= 1; i++) { - vec_push(forloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)((i) ? B_iminus1 : A_iminus1), - (ast_expression*)((i) ? B_i : A_i) - ) - ); - } - - /* - * A_i = A_iplus1; - * B_i = B_iplus1; - */ - for (i = 0; i <= 1; i++) { - vec_push(forloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)((i) ? B_i : A_i), - (ast_expression*)((i) ? B_iplus1 : A_iplus1) - ) - ); - } - - /* - * if (whole <= 1.0f + eps) - * break; - */ - vec_push(forloop->exprs, - (ast_expression*)ast_ifthen_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LE, - (ast_expression*)whole, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_ADD_F, - (ast_expression*)intrin->fold->imm_float[1], - (ast_expression*)eps - ) - ), - (ast_expression*)ast_breakcont_new( - intrin_ctx(intrin), - false, - 0 - ), - NULL - ) - ); - - /* - * power = base; - * base = whole; - */ - for (i = 0; i <= 1; i++) { - vec_push(forloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)((i) ? base : power), - (ast_expression*)((i) ? whole : base) - ) - ); - } - - /* add the for loop block */ - vec_push(block->exprs, - (ast_expression*)ast_loop_new( - intrin_ctx(intrin), - NULL, - /* for(; 1; ) ?? (can this be NULL too?) */ - (ast_expression*)intrin->fold->imm_float[1], - false, - NULL, - false, - NULL, - (ast_expression*)forloop - ) - ); - - /* return sign * A_i / B_il */ - vec_push(block->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)sign, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)A_i, - (ast_expression*)B_i - ) - ) - ) - ); - - vec_push(func->blocks, block); - intrin_reg(intrin, value, func); - - return (ast_expression*)value; -} - -static ast_expression *intrin_log_variant(intrin_t *intrin, const char *name, float base) { - ast_value *value = NULL; - ast_call *callln = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "__builtin_ln", name)); - ast_value *arg1 = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, name, TYPE_FLOAT); - - vec_push(value->expression.params, arg1); - - vec_push(callln->params, (ast_expression*)arg1); - vec_push(callln->params, (ast_expression*)fold_constgen_float(intrin->fold, base, false)); - - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)callln - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_log(intrin_t *intrin) { - return intrin_log_variant(intrin, "log", 2.7182818284590452354); -} -static ast_expression *intrin_log10(intrin_t *intrin) { - return intrin_log_variant(intrin, "log10", 10); -} -static ast_expression *intrin_log2(intrin_t *intrin) { - return intrin_log_variant(intrin, "log2", 2); -} -static ast_expression *intrin_logb(intrin_t *intrin) { - /* FLT_RADIX == 2 for now */ - return intrin_log_variant(intrin, "log2", 2); -} - -static ast_expression *intrin_shift_variant(intrin_t *intrin, const char *name, size_t instr) { - /* - * float [shift] (float a, float b) { - * return floor(a [instr] pow(2, b)); - */ - ast_value *value = NULL; - ast_call *callpow = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "pow", name)); - ast_call *callfloor = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "floor", name)); - ast_value *a = ast_value_new(intrin_ctx(intrin), "a", TYPE_FLOAT); - ast_value *b = ast_value_new(intrin_ctx(intrin), "b", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, name, TYPE_FLOAT); - - vec_push(value->expression.params, a); - vec_push(value->expression.params, b); - - /* = pow(2, b) */ - vec_push(callpow->params, (ast_expression*)intrin->fold->imm_float[3]); - vec_push(callpow->params, (ast_expression*)b); - - /* = floor(a [instr] ) */ - vec_push( - callfloor->params, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - instr, - (ast_expression*)a, - (ast_expression*)callpow - ) - ); - - /* return */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)callfloor - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_lshift(intrin_t *intrin) { - return intrin_shift_variant(intrin, "lshift", INSTR_MUL_F); -} - -static ast_expression *intrin_rshift(intrin_t *intrin) { - return intrin_shift_variant(intrin, "rshift", INSTR_DIV_F); -} - -/* - * TODO: make static (and handle ast_type_string) here for the builtin - * instead of in SYA parse close. - */ -ast_expression *intrin_debug_typestring(intrin_t *intrin) { - (void)intrin; - return (ast_expression*)0x1; -} - -static const intrin_func_t intrinsics[] = { - {&intrin_isfinite, "__builtin_isfinite", "isfinite", 1}, - {&intrin_isinf, "__builtin_isinf", "isinf", 1}, - {&intrin_isnan, "__builtin_isnan", "isnan", 1}, - {&intrin_isnormal, "__builtin_isnormal", "isnormal", 1}, - {&intrin_signbit, "__builtin_signbit", "signbit", 1}, - {&intrin_acosh, "__builtin_acosh", "acosh", 1}, - {&intrin_asinh, "__builtin_asinh", "asinh", 1}, - {&intrin_atanh, "__builtin_atanh", "atanh", 1}, - {&intrin_exp, "__builtin_exp", "exp", 1}, - {&intrin_exp2, "__builtin_exp2", "exp2", 1}, - {&intrin_expm1, "__builtin_expm1", "expm1", 1}, - {&intrin_mod, "__builtin_mod", "mod", 2}, - {&intrin_pow, "__builtin_pow", "pow", 2}, - {&intrin_fabs, "__builtin_fabs", "fabs", 1}, - {&intrin_log, "__builtin_log", "log", 1}, - {&intrin_log10, "__builtin_log10", "log10", 1}, - {&intrin_log2, "__builtin_log2", "log2", 1}, - {&intrin_logb, "__builtin_logb", "logb", 1}, - {&intrin_lshift, "__builtin_lshift", "", 2}, - {&intrin_rshift, "__builtin_rshift", "", 2}, - {&intrin_epsilon, "__builtin_epsilon", "", 0}, - {&intrin_nan, "__builtin_nan", "", 0}, - {&intrin_inf, "__builtin_inf", "", 0}, - {&intrin_ln, "__builtin_ln", "", 2}, - {&intrin_debug_typestring, "__builtin_debug_typestring", "", 0}, - {&intrin_nullfunc, "#nullfunc", "", 0} -}; - -static void intrin_error(intrin_t *intrin, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - vcompile_error(intrin->parser->lex->tok.ctx, fmt, ap); - va_end(ap); -} - -/* exposed */ -intrin_t *intrin_init(parser_t *parser) { - intrin_t *intrin = (intrin_t*)mem_a(sizeof(intrin_t)); - size_t i; - - intrin->parser = parser; - intrin->fold = parser->fold; - intrin->intrinsics = NULL; - intrin->generated = NULL; - - vec_append(intrin->intrinsics, GMQCC_ARRAY_COUNT(intrinsics), intrinsics); - - /* populate with null pointers for tracking generation */ - for (i = 0; i < GMQCC_ARRAY_COUNT(intrinsics); i++) - vec_push(intrin->generated, NULL); - - return intrin; -} - -void intrin_cleanup(intrin_t *intrin) { - vec_free(intrin->intrinsics); - vec_free(intrin->generated); - mem_d(intrin); -} - -ast_expression *intrin_fold(intrin_t *intrin, ast_value *value, ast_expression **exprs) { - size_t i; - if (!value || !value->name) - return NULL; - for (i = 0; i < vec_size(intrin->intrinsics); i++) - if (!strcmp(value->name, intrin->intrinsics[i].name)) - return (vec_size(exprs) != intrin->intrinsics[i].args) - ? NULL - : fold_intrin(intrin->fold, value->name + 10, exprs); - return NULL; -} - -static GMQCC_INLINE ast_expression *intrin_func_try(intrin_t *intrin, size_t offset, const char *compare) { - size_t i; - for (i = 0; i < vec_size(intrin->intrinsics); i++) { - if (strcmp(*(char **)((char *)&intrin->intrinsics[i] + offset), compare)) - continue; - if (intrin->generated[i]) - return intrin->generated[i]; - return intrin->generated[i] = intrin->intrinsics[i].intrin(intrin); - } - return NULL; -} - -static ast_expression *intrin_func_self(intrin_t *intrin, const char *name, const char *from) { - size_t i; - ast_expression *find; - - /* try current first */ - if ((find = parser_find_global(intrin->parser, name)) && ((ast_value*)find)->expression.vtype == TYPE_FUNCTION) - for (i = 0; i < vec_size(intrin->parser->functions); ++i) - if (((ast_value*)find)->name && !strcmp(intrin->parser->functions[i]->name, ((ast_value*)find)->name) && intrin->parser->functions[i]->builtin < 0) - return find; - /* try name second */ - if ((find = intrin_func_try(intrin, offsetof(intrin_func_t, name), name))) - return find; - /* try alias third */ - if ((find = intrin_func_try(intrin, offsetof(intrin_func_t, alias), name))) - return find; - - if (from) { - intrin_error(intrin, "need function `%s', compiler depends on it for `__builtin_%s'", name, from); - return intrin_func_self(intrin, "#nullfunc", NULL); - } - return NULL; -} - -ast_expression *intrin_func(intrin_t *intrin, const char *name) { - return intrin_func_self(intrin, name, NULL); -} diff --git a/intrin.cpp b/intrin.cpp new file mode 100644 index 0000000..b37b50f --- /dev/null +++ b/intrin.cpp @@ -0,0 +1,2048 @@ +#include + +#include "ast.h" +#include "fold.h" +#include "parser.h" + +lex_ctx_t intrin::ctx() const { + return parser_ctx(m_parser); +} + +ast_function *intrin::value(ast_value **out, const char *name, qc_type vtype) { + ast_value *value = nullptr; + ast_function *func = nullptr; + char buffer[1024]; + char stype [1024]; + + util_snprintf(buffer, sizeof(buffer), "__builtin_%s", name); + util_snprintf(stype, sizeof(stype), "<%s>", type_name[vtype]); + + value = new ast_value(ctx(), buffer, TYPE_FUNCTION); + value->m_intrinsic = true; + value->m_next = new ast_value(ctx(), stype, vtype); + func = ast_function::make(ctx(), buffer, value); + value->m_flags |= AST_FLAG_ERASEABLE; + + *out = value; + return func; +} + +void intrin::reg(ast_value *const value, ast_function *const func) { + m_parser->functions.push_back(func); + m_parser->globals.push_back(value); +} + +#define QC_POW_EPSILON 0.00001f + +ast_expression *intrin::nullfunc() { + ast_value *val = nullptr; + ast_function *func = value(&val, nullptr, TYPE_VOID); + reg(val, func); + return val; +} + +ast_expression *intrin::isfinite_() { + /* + * float isfinite(float x) { + * return !(isnan(x) || isinf(x)); + * } + */ + ast_value *val = nullptr; + ast_value *x = new ast_value(ctx(), "x", TYPE_FLOAT); + ast_function *func = value(&val, "isfinite", TYPE_FLOAT); + ast_call *callisnan = ast_call::make(ctx(), func_self("isnan", "isfinite")); + ast_call *callisinf = ast_call::make(ctx(), func_self("isinf", "isfinite")); + ast_block *block = new ast_block(ctx()); + + /* float x; */ + val->m_type_params.emplace_back(x); + + /* = isnan(x); */ + callisnan->m_params.push_back(x); + + /* = isinf(x); */ + callisinf->m_params.push_back(x); + + /* return (! || ); */ + block->m_exprs.push_back( + new ast_return( + ctx(), + ast_unary::make( + ctx(), + INSTR_NOT_F, + new ast_binary( + ctx(), + INSTR_OR, + callisnan, + callisinf + ) + ) + ) + ); + + func->m_blocks.emplace_back(block); + reg(val, func); + + return val;; +} + +ast_expression *intrin::isinf_() { + /* + * float isinf(float x) { + * return (x != 0.0) && (x + x == x); + * } + */ + ast_value *val = nullptr; + ast_value *x = new ast_value(ctx(), "x", TYPE_FLOAT); + ast_block *body = new ast_block(ctx()); + ast_function *func = value(&val, "isinf", TYPE_FLOAT); + + body->m_exprs.push_back( + new ast_return( + ctx(), + new ast_binary( + ctx(), + INSTR_AND, + new ast_binary( + ctx(), + INSTR_NE_F, + x, + m_fold->m_imm_float[0] + ), + new ast_binary( + ctx(), + INSTR_EQ_F, + new ast_binary( + ctx(), + INSTR_ADD_F, + x, + x + ), + x + ) + ) + ) + ); + + val->m_type_params.emplace_back(x); + func->m_blocks.emplace_back(body); + + reg(val, func); + + return val; +} + +ast_expression *intrin::isnan_() { + /* + * float isnan(float x) { + * float local; + * local = x; + * + * return (x != local); + * } + */ + ast_value *val = nullptr; + ast_value *arg1 = new ast_value(ctx(), "x",TYPE_FLOAT); + ast_value *local = new ast_value(ctx(), "local", TYPE_FLOAT); + ast_block *body = new ast_block(ctx()); + ast_function *func = value(&val, "isnan", TYPE_FLOAT); + + body->m_locals.push_back(local); + body->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + local, + arg1 + ) + ); + + body->m_exprs.push_back( + new ast_return( + ctx(), + new ast_binary( + ctx(), + INSTR_NE_F, + arg1, + local + ) + ) + ); + + val->m_type_params.emplace_back(arg1); + func->m_blocks.emplace_back(body); + + reg(val, func); + + return val; +} + +ast_expression *intrin::isnormal_() { + /* + * float isnormal(float x) { + * return isfinite(x); + * } + */ + ast_value *val = nullptr; + ast_call *callisfinite = ast_call::make(ctx(), func_self("isfinite", "isnormal")); + ast_value *x = new ast_value(ctx(), "x", TYPE_FLOAT); + ast_block *body = new ast_block(ctx()); + ast_function *func = value(&val, "isnormal", TYPE_FLOAT); + + val->m_type_params.emplace_back(x); + callisfinite->m_params.push_back(x); + + /* return */ + body->m_exprs.push_back( + new ast_return( + ctx(), + callisfinite + ) + ); + + func->m_blocks.emplace_back(body); + reg(val, func); + return val; +} + +ast_expression *intrin::signbit_() { + /* + * float signbit(float x) { + * return (x < 0); + * } + */ + ast_value *val = nullptr; + ast_value *x = new ast_value(ctx(), "x", TYPE_FLOAT); + ast_block *body = new ast_block(ctx()); + ast_function *func = value(&val, "signbit", TYPE_FLOAT); + + val->m_type_params.emplace_back(x); + + /* return (x < 0); */ + body->m_exprs.push_back( + new ast_return( + ctx(), + new ast_ternary( + ctx(), + new ast_binary( + ctx(), + INSTR_LT, + x, + m_fold->m_imm_float[0] + ), + m_fold->m_imm_float[1], + m_fold->m_imm_float[0] + ) + ) + ); + + func->m_blocks.emplace_back(body); + reg(val, func); + return val; +} + +ast_expression *intrin::acosh_() { + /* + * float acosh(float x) { + * return log(x + sqrt((x * x) - 1)); + * } + */ + ast_value *val = nullptr; + ast_value *x = new ast_value(ctx(), "x", TYPE_FLOAT); + ast_call *calllog = ast_call::make(ctx(), func_self("log", "acosh")); + ast_call *callsqrt = ast_call::make(ctx(), func_self("sqrt", "acosh")); + ast_block *body = new ast_block(ctx()); + ast_function *func = value(&val, "acosh", TYPE_FLOAT); + + val->m_type_params.emplace_back(x); + + /* = sqrt((x * x) - 1); */ + callsqrt->m_params.push_back( + new ast_binary( + ctx(), + INSTR_SUB_F, + new ast_binary( + ctx(), + INSTR_MUL_F, + x, + x + ), + m_fold->m_imm_float[1] + ) + ); + + /* = log(x + ); */ + calllog->m_params.push_back( + new ast_binary( + ctx(), + INSTR_ADD_F, + x, + callsqrt + ) + ); + + /* return ; */ + body->m_exprs.push_back( + new ast_return( + ctx(), + calllog + ) + ); + + func->m_blocks.emplace_back(body); + reg(val, func); + return val; +} + +ast_expression *intrin::asinh_() { + /* + * float asinh(float x) { + * return log(x + sqrt((x * x) + 1)); + * } + */ + ast_value *val = nullptr; + ast_value *x = new ast_value(ctx(), "x", TYPE_FLOAT); + ast_call *calllog = ast_call::make(ctx(), func_self("log", "asinh")); + ast_call *callsqrt = ast_call::make(ctx(), func_self("sqrt", "asinh")); + ast_block *body = new ast_block(ctx()); + ast_function *func = value(&val, "asinh", TYPE_FLOAT); + + val->m_type_params.emplace_back(x); + + /* = sqrt((x * x) + 1); */ + callsqrt->m_params.push_back( + new ast_binary( + ctx(), + INSTR_ADD_F, + new ast_binary( + ctx(), + INSTR_MUL_F, + x, + x + ), + m_fold->m_imm_float[1] + ) + ); + + /* = log(x + ); */ + calllog->m_params.push_back( + new ast_binary( + ctx(), + INSTR_ADD_F, + x, + callsqrt + ) + ); + + /* return ; */ + body->m_exprs.push_back( + new ast_return( + ctx(), + calllog + ) + ); + + func->m_blocks.emplace_back(body); + reg(val, func); + return val; +} + +ast_expression *intrin::atanh_() { + /* + * float atanh(float x) { + * return 0.5 * log((1 + x) / (1 - x)) + * } + */ + ast_value *val = nullptr; + ast_value *x = new ast_value(ctx(), "x", TYPE_FLOAT); + ast_call *calllog = ast_call::make(ctx(), func_self("log", "atanh")); + ast_block *body = new ast_block(ctx()); + ast_function *func = value(&val, "atanh", TYPE_FLOAT); + + val->m_type_params.emplace_back(x); + + /* = log((1 + x) / (1 - x)); */ + calllog->m_params.push_back( + new ast_binary( + ctx(), + INSTR_DIV_F, + new ast_binary( + ctx(), + INSTR_ADD_F, + m_fold->m_imm_float[1], + x + ), + new ast_binary( + ctx(), + INSTR_SUB_F, + m_fold->m_imm_float[1], + x + ) + ) + ); + + /* return 0.5 * ; */ + body->m_exprs.push_back( + new ast_binary( + ctx(), + INSTR_MUL_F, + m_fold->constgen_float(0.5, false), + calllog + ) + ); + + func->m_blocks.emplace_back(body); + reg(val, func); + return val; +} + +ast_expression *intrin::exp_() { + /* + * float exp(float x) { + * float sum = 1.0; + * float acc = 1.0; + * float i; + * for (i = 1; i < 200; ++i) + * sum += (acc *= x / i); + * + * return sum; + * } + */ + ast_value *val = nullptr; + ast_value *x = new ast_value(ctx(), "x", TYPE_FLOAT); + ast_value *sum = new ast_value(ctx(), "sum", TYPE_FLOAT); + ast_value *acc = new ast_value(ctx(), "acc", TYPE_FLOAT); + ast_value *i = new ast_value(ctx(), "i", TYPE_FLOAT); + ast_block *body = new ast_block(ctx()); + ast_function *func = value(&val, "exp", TYPE_FLOAT); + + val->m_type_params.emplace_back(x); + + body->m_locals.push_back(sum); + body->m_locals.push_back(acc); + body->m_locals.push_back(i); + + /* sum = 1.0; */ + body->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + sum, + m_fold->m_imm_float[1] + ) + ); + + /* acc = 1.0; */ + body->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + acc, + m_fold->m_imm_float[1] + ) + ); + + /* + * for (i = 1; i < 200; ++i) + * sum += (acc *= x / i); + */ + body->m_exprs.push_back( + new ast_loop( + ctx(), + /* i = 1; */ + new ast_store( + ctx(), + INSTR_STORE_F, + i, + m_fold->m_imm_float[1] + ), + /* i < 200; */ + new ast_binary( + ctx(), + INSTR_LT, + i, + m_fold->constgen_float(200.0f, false) + ), + false, + nullptr, + false, + /* ++i; */ + new ast_binstore( + ctx(), + INSTR_STORE_F, + INSTR_ADD_F, + i, + m_fold->m_imm_float[1] + ), + /* sum += (acc *= (x / i)) */ + new ast_binstore( + ctx(), + INSTR_STORE_F, + INSTR_ADD_F, + sum, + new ast_binstore( + ctx(), + INSTR_STORE_F, + INSTR_MUL_F, + acc, + new ast_binary( + ctx(), + INSTR_DIV_F, + x, + i + ) + ) + ) + ) + ); + + /* return sum; */ + body->m_exprs.push_back( + new ast_return( + ctx(), + sum + ) + ); + + func->m_blocks.emplace_back(body); + reg(val, func); + return val; +} + +ast_expression *intrin::exp2_() { + /* + * float exp2(float x) { + * return pow(2, x); + * } + */ + ast_value *val = nullptr; + ast_call *callpow = ast_call::make(ctx(), func_self("pow", "exp2")); + ast_value *arg1 = new ast_value(ctx(), "x", TYPE_FLOAT); + ast_block *body = new ast_block(ctx()); + ast_function *func = value(&val, "exp2", TYPE_FLOAT); + + val->m_type_params.emplace_back(arg1); + + callpow->m_params.push_back(m_fold->m_imm_float[3]); + callpow->m_params.push_back(arg1); + + /* return */ + body->m_exprs.push_back( + new ast_return( + ctx(), + callpow + ) + ); + + func->m_blocks.emplace_back(body); + reg(val, func); + return val; +} + +ast_expression *intrin::expm1_() { + /* + * float expm1(float x) { + * return exp(x) - 1; + * } + */ + ast_value *val = nullptr; + ast_call *callexp = ast_call::make(ctx(), func_self("exp", "expm1")); + ast_value *x = new ast_value(ctx(), "x", TYPE_FLOAT); + ast_block *body = new ast_block(ctx()); + ast_function *func = value(&val, "expm1", TYPE_FLOAT); + + val->m_type_params.emplace_back(x); + + /* = exp(x); */ + callexp->m_params.push_back(x); + + /* return - 1; */ + body->m_exprs.push_back( + new ast_return( + ctx(), + new ast_binary( + ctx(), + INSTR_SUB_F, + callexp, + m_fold->m_imm_float[1] + ) + ) + ); + + func->m_blocks.emplace_back(body); + reg(val, func); + return val; +} + +ast_expression *intrin::pow_() { + /* + * + * float pow(float base, float exp) { + * float result; + * float low; + * float high; + * float mid; + * float square; + * float accumulate; + * + * if (exp == 0.0) + * return 1; + * if (exp == 1.0) + * return base; + * if (exp < 0) + * return 1.0 / pow(base, -exp); + * if (exp >= 1) { + * result = pow(base, exp / 2); + * return result * result; + * } + * + * low = 0.0f; + * high = 1.0f; + * square = sqrt(base); + * accumulate = square; + * mid = high / 2.0f + * + * while (fabs(mid - exp) > QC_POW_EPSILON) { + * square = sqrt(square); + * if (mid < exp) { + * low = mid; + * accumulate *= square; + * } else { + * high = mid; + * accumulate *= (1.0f / square); + * } + * mid = (low + high) / 2; + * } + * return accumulate; + * } + */ + ast_value *val = nullptr; + ast_function *func = value(&val, "pow", TYPE_FLOAT); + + /* prepare some calls for later */ + ast_call *callpow1 = ast_call::make(ctx(), val); /* for pow(base, -exp) */ + ast_call *callpow2 = ast_call::make(ctx(), val); /* for pow(vase, exp / 2) */ + ast_call *callsqrt1 = ast_call::make(ctx(), func_self("sqrt", "pow")); /* for sqrt(base) */ + ast_call *callsqrt2 = ast_call::make(ctx(), func_self("sqrt", "pow")); /* for sqrt(square) */ + ast_call *callfabs = ast_call::make(ctx(), func_self("fabs", "pow")); /* for fabs(mid - exp) */ + + /* prepare some blocks for later */ + ast_block *expgt1 = new ast_block(ctx()); + ast_block *midltexp = new ast_block(ctx()); + ast_block *midltexpelse = new ast_block(ctx()); + ast_block *whileblock = new ast_block(ctx()); + + /* float pow(float base, float exp) */ + ast_value *base = new ast_value(ctx(), "base", TYPE_FLOAT); + ast_value *exp = new ast_value(ctx(), "exp", TYPE_FLOAT); + /* { */ + ast_block *body = new ast_block(ctx()); + + /* + * float result; + * float low; + * float high; + * float square; + * float accumulate; + * float mid; + */ + ast_value *result = new ast_value(ctx(), "result", TYPE_FLOAT); + ast_value *low = new ast_value(ctx(), "low", TYPE_FLOAT); + ast_value *high = new ast_value(ctx(), "high", TYPE_FLOAT); + ast_value *square = new ast_value(ctx(), "square", TYPE_FLOAT); + ast_value *accumulate = new ast_value(ctx(), "accumulate", TYPE_FLOAT); + ast_value *mid = new ast_value(ctx(), "mid", TYPE_FLOAT); + body->m_locals.push_back(result); + body->m_locals.push_back(low); + body->m_locals.push_back(high); + body->m_locals.push_back(square); + body->m_locals.push_back(accumulate); + body->m_locals.push_back(mid); + + val->m_type_params.emplace_back(base); + val->m_type_params.emplace_back(exp); + + /* + * if (exp == 0.0) + * return 1; + */ + body->m_exprs.push_back( + new ast_ifthen( + ctx(), + new ast_binary( + ctx(), + INSTR_EQ_F, + exp, + m_fold->m_imm_float[0] + ), + new ast_return( + ctx(), + m_fold->m_imm_float[1] + ), + nullptr + ) + ); + + /* + * if (exp == 1.0) + * return base; + */ + body->m_exprs.push_back( + new ast_ifthen( + ctx(), + new ast_binary( + ctx(), + INSTR_EQ_F, + exp, + m_fold->m_imm_float[1] + ), + new ast_return( + ctx(), + base + ), + nullptr + ) + ); + + /* = pow(base, -exp) */ + callpow1->m_params.push_back(base); + callpow1->m_params.push_back( + ast_unary::make( + ctx(), + VINSTR_NEG_F, + exp + ) + ); + + /* + * if (exp < 0) + * return 1.0 / ; + */ + body->m_exprs.push_back( + new ast_ifthen( + ctx(), + new ast_binary( + ctx(), + INSTR_LT, + exp, + m_fold->m_imm_float[0] + ), + new ast_return( + ctx(), + new ast_binary( + ctx(), + INSTR_DIV_F, + m_fold->m_imm_float[1], + callpow1 + ) + ), + nullptr + ) + ); + + /* = pow(base, exp / 2) */ + callpow2->m_params.push_back(base); + callpow2->m_params.push_back( + new ast_binary( + ctx(), + INSTR_DIV_F, + exp, + m_fold->m_imm_float[3] /* 2.0f */ + ) + ); + + /* + * = { + * result = ; + * return result * result; + * } + */ + expgt1->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + result, + callpow2 + ) + ); + expgt1->m_exprs.push_back( + new ast_return( + ctx(), + new ast_binary( + ctx(), + INSTR_MUL_F, + result, + result + ) + ) + ); + + /* + * if (exp >= 1) { + * + * } + */ + body->m_exprs.push_back( + new ast_ifthen( + ctx(), + new ast_binary( + ctx(), + INSTR_GE, + exp, + m_fold->m_imm_float[1] + ), + expgt1, + nullptr + ) + ); + + /* + * = sqrt(base) + */ + callsqrt1->m_params.push_back(base); + + /* + * low = 0.0f; + * high = 1.0f; + * square = sqrt(base); + * accumulate = square; + * mid = high / 2.0f; + */ + body->m_exprs.push_back( + new ast_store(ctx(), + INSTR_STORE_F, + low, + m_fold->m_imm_float[0] + ) + ); + body->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + high, + m_fold->m_imm_float[1] + ) + ); + + body->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + square, + callsqrt1 + ) + ); + + body->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + accumulate, + square + ) + ); + body->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + mid, + new ast_binary( + ctx(), + INSTR_DIV_F, + high, + m_fold->m_imm_float[3] /* 2.0f */ + ) + ) + ); + + /* + * = { + * low = mid; + * accumulate *= square; + * } + */ + midltexp->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + low, + mid + ) + ); + midltexp->m_exprs.push_back( + new ast_binstore( + ctx(), + INSTR_STORE_F, + INSTR_MUL_F, + accumulate, + square + ) + ); + + /* + * = { + * high = mid; + * accumulate *= (1.0 / square); + * } + */ + midltexpelse->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + high, + mid + ) + ); + midltexpelse->m_exprs.push_back( + new ast_binstore( + ctx(), + INSTR_STORE_F, + INSTR_MUL_F, + accumulate, + new ast_binary( + ctx(), + INSTR_DIV_F, + m_fold->m_imm_float[1], + square + ) + ) + ); + + /* + * = sqrt(square) + */ + callsqrt2->m_params.push_back(square); + + /* + * = { + * square = ; + * if (mid < exp) + * ; + * else + * ; + * + * mid = (low + high) / 2; + * } + */ + whileblock->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + square, + callsqrt2 + ) + ); + whileblock->m_exprs.push_back( + new ast_ifthen( + ctx(), + new ast_binary( + ctx(), + INSTR_LT, + mid, + exp + ), + midltexp, + midltexpelse + ) + ); + whileblock->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + mid, + new ast_binary( + ctx(), + INSTR_DIV_F, + new ast_binary( + ctx(), + INSTR_ADD_F, + low, + high + ), + m_fold->m_imm_float[3] /* 2.0f */ + ) + ) + ); + + /* + * = fabs(mid - exp) + */ + callfabs->m_params.push_back( + new ast_binary( + ctx(), + INSTR_SUB_F, + mid, + exp + ) + ); + + /* + * while ( > epsilon) + * + */ + body->m_exprs.push_back( + new ast_loop( + ctx(), + /* init */ + nullptr, + /* pre condition */ + new ast_binary( + ctx(), + INSTR_GT, + callfabs, + m_fold->constgen_float(QC_POW_EPSILON, false) + ), + /* pre not */ + false, + /* post condition */ + nullptr, + /* post not */ + false, + /* increment expression */ + nullptr, + /* code block */ + whileblock + ) + ); + + /* return accumulate */ + body->m_exprs.push_back( + new ast_return( + ctx(), + accumulate + ) + ); + + /* } */ + func->m_blocks.emplace_back(body); + reg(val, func); + return val; +} + +ast_expression *intrin::mod_() { + /* + * float mod(float a, float b) { + * float div = a / b; + * float sign = (div < 0.0f) ? -1 : 1; + * return a - b * sign * floor(sign * div); + * } + */ + ast_value *val = nullptr; + ast_call *call = ast_call::make(ctx(), func_self("floor", "mod")); + ast_value *a = new ast_value(ctx(), "a", TYPE_FLOAT); + ast_value *b = new ast_value(ctx(), "b", TYPE_FLOAT); + ast_value *div = new ast_value(ctx(), "div", TYPE_FLOAT); + ast_value *sign = new ast_value(ctx(), "sign", TYPE_FLOAT); + ast_block *body = new ast_block(ctx()); + ast_function *func = value(&val, "mod", TYPE_FLOAT); + + val->m_type_params.emplace_back(a); + val->m_type_params.emplace_back(b); + + body->m_locals.push_back(div); + body->m_locals.push_back(sign); + + /* div = a / b; */ + body->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + div, + new ast_binary( + ctx(), + INSTR_DIV_F, + a, + b + ) + ) + ); + + /* sign = (div < 0.0f) ? -1 : 1; */ + body->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + sign, + new ast_ternary( + ctx(), + new ast_binary( + ctx(), + INSTR_LT, + div, + m_fold->m_imm_float[0] + ), + m_fold->m_imm_float[2], + m_fold->m_imm_float[1] + ) + ) + ); + + /* floor(sign * div) */ + call->m_params.push_back( + new ast_binary( + ctx(), + INSTR_MUL_F, + sign, + div + ) + ); + + /* return a - b * sign * */ + body->m_exprs.push_back( + new ast_return( + ctx(), + new ast_binary( + ctx(), + INSTR_SUB_F, + a, + new ast_binary( + ctx(), + INSTR_MUL_F, + b, + new ast_binary( + ctx(), + INSTR_MUL_F, + sign, + call + ) + ) + ) + ) + ); + + func->m_blocks.emplace_back(body); + reg(val, func); + return val; +} + +ast_expression *intrin::fabs_() { + /* + * float fabs(float x) { + * return x < 0 ? -x : x; + * } + */ + ast_value *val = nullptr; + ast_value *arg1 = new ast_value(ctx(), "x", TYPE_FLOAT); + ast_block *body = new ast_block(ctx()); + ast_function *func = value(&val, "fabs", TYPE_FLOAT); + + body->m_exprs.push_back( + new ast_return( + ctx(), + new ast_ternary( + ctx(), + new ast_binary( + ctx(), + INSTR_LE, + arg1, + m_fold->m_imm_float[0] + ), + ast_unary::make( + ctx(), + VINSTR_NEG_F, + arg1 + ), + arg1 + ) + ) + ); + + val->m_type_params.emplace_back(arg1); + + func->m_blocks.emplace_back(body); + reg(val, func); + return val; +} + +ast_expression *intrin::epsilon_() { + /* + * float epsilon(void) { + * float eps = 1.0f; + * do { eps /= 2.0f; } while ((1.0f + (eps / 2.0f)) != 1.0f); + * return eps; + * } + */ + ast_value *val = nullptr; + ast_value *eps = new ast_value(ctx(), "eps", TYPE_FLOAT); + ast_block *body = new ast_block(ctx()); + ast_function *func = value(&val, "epsilon", TYPE_FLOAT); + + body->m_locals.push_back(eps); + + /* eps = 1.0f; */ + body->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + eps, + m_fold->m_imm_float[0] + ) + ); + + body->m_exprs.push_back( + new ast_loop( + ctx(), + nullptr, + nullptr, + false, + new ast_binary( + ctx(), + INSTR_NE_F, + new ast_binary( + ctx(), + INSTR_ADD_F, + m_fold->m_imm_float[1], + new ast_binary( + ctx(), + INSTR_MUL_F, + eps, + m_fold->m_imm_float[3] /* 2.0f */ + ) + ), + m_fold->m_imm_float[1] + ), + false, + nullptr, + new ast_binstore( + ctx(), + INSTR_STORE_F, + INSTR_DIV_F, + eps, + m_fold->m_imm_float[3] /* 2.0f */ + ) + ) + ); + + /* return eps; */ + body->m_exprs.push_back( + new ast_return( + ctx(), + eps + ) + ); + + func->m_blocks.emplace_back(body); + reg(val, func); + return val; +} + +ast_expression *intrin::nan_() { + /* + * float nan(void) { + * float x = 0.0f; + * return x / x; + * } + */ + ast_value *val = nullptr; + ast_value *x = new ast_value(ctx(), "x", TYPE_FLOAT); + ast_function *func = value(&val, "nan", TYPE_FLOAT); + ast_block *block = new ast_block(ctx()); + + block->m_locals.push_back(x); + + block->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + x, + m_fold->m_imm_float[0] + ) + ); + + block->m_exprs.push_back( + new ast_return( + ctx(), + new ast_binary( + ctx(), + INSTR_DIV_F, + x, + x + ) + ) + ); + + func->m_blocks.emplace_back(block); + reg(val, func); + return val; +} + +ast_expression *intrin::inf_() { + /* + * float inf(void) { + * float x = 1.0f; + * float y = 0.0f; + * return x / y; + * } + */ + ast_value *val = nullptr; + ast_value *x = new ast_value(ctx(), "x", TYPE_FLOAT); + ast_value *y = new ast_value(ctx(), "y", TYPE_FLOAT); + ast_function *func = value(&val, "inf", TYPE_FLOAT); + ast_block *block = new ast_block(ctx()); + size_t i; + + block->m_locals.push_back(x); + block->m_locals.push_back(y); + + /* to keep code size down */ + for (i = 0; i <= 1; i++) { + block->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + ((i == 0) ? x : y), + m_fold->m_imm_float[i] + ) + ); + } + + block->m_exprs.push_back( + new ast_return( + ctx(), + new ast_binary( + ctx(), + INSTR_DIV_F, + x, + y + ) + ) + ); + + func->m_blocks.emplace_back(block); + reg(val, func); + return val; +} + +ast_expression *intrin::ln_() { + /* + * float log(float power, float base) { + * float whole; + * float nth + * float sign = 1.0f; + * float eps = epsilon(); + * + * if (power <= 1.0f || bbase <= 1.0) { + * if (power <= 0.0f || base <= 0.0f) + * return nan(); + * + * if (power < 1.0f) { + * power = 1.0f / power; + * sign *= -1.0f; + * } + * + * if (base < 1.0f) { + * sign *= -1.0f; + * base = 1.0f / base; + * } + * } + * + * float A_i = 1; + * float B_i = 0; + * float A_iminus1 = 0; + * float B_iminus1 = 1; + * + * for (;;) { + * whole = power; + * nth = 0.0f; + * + * while (whole >= base) { + * float base2 = base; + * float n2 = 1.0f; + * float newbase2 = base2 * base2; + * + * while (whole >= newbase2) { + * base2 = newbase2; + * n2 *= 2; + * newbase2 *= newbase2; + * } + * + * whole /= base2; + * nth += n2; + * } + * + * float b_iplus1 = n; + * float A_iplus1 = b_iplus1 * A_i + A_iminus1; + * float B_iplus1 = b_iplus1 * B_i + B_iminus1; + * + * A_iminus1 = A_i; + * B_iminus1 = B_i; + * A_i = A_iplus1; + * B_i = B_iplus1; + * + * if (whole <= 1.0f + eps) + * break; + * + * power = base; + * bower = whole; + * } + * return sign * A_i / B_i; + * } + */ + + ast_value *val = nullptr; + ast_value *power = new ast_value(ctx(), "power", TYPE_FLOAT); + ast_value *base = new ast_value(ctx(), "base",TYPE_FLOAT); + ast_value *whole= new ast_value(ctx(), "whole", TYPE_FLOAT); + ast_value *nth = new ast_value(ctx(), "nth", TYPE_FLOAT); + ast_value *sign = new ast_value(ctx(), "sign", TYPE_FLOAT); + ast_value *A_i = new ast_value(ctx(), "A_i", TYPE_FLOAT); + ast_value *B_i = new ast_value(ctx(), "B_i", TYPE_FLOAT); + ast_value *A_iminus1 = new ast_value(ctx(), "A_iminus1", TYPE_FLOAT); + ast_value *B_iminus1 = new ast_value(ctx(), "B_iminus1", TYPE_FLOAT); + ast_value *b_iplus1 = new ast_value(ctx(), "b_iplus1", TYPE_FLOAT); + ast_value *A_iplus1 = new ast_value(ctx(), "A_iplus1", TYPE_FLOAT); + ast_value *B_iplus1 = new ast_value(ctx(), "B_iplus1", TYPE_FLOAT); + ast_value *eps = new ast_value(ctx(), "eps", TYPE_FLOAT); + ast_value *base2 = new ast_value(ctx(), "base2", TYPE_FLOAT); + ast_value *n2 = new ast_value(ctx(), "n2",TYPE_FLOAT); + ast_value *newbase2 = new ast_value(ctx(), "newbase2", TYPE_FLOAT); + ast_block *block = new ast_block(ctx()); + ast_block *plt1orblt1 = new ast_block(ctx()); // (power <= 1.0f || base <= 1.0f) + ast_block *plt1 = new ast_block(ctx()); // (power < 1.0f) + ast_block *blt1 = new ast_block(ctx()); // (base< 1.0f) + ast_block *forloop = new ast_block(ctx()); // for(;;) + ast_block *whileloop = new ast_block(ctx()); // while (whole >= base) + ast_block *nestwhile= new ast_block(ctx()); // while (whole >= newbase2) + ast_function *func = value(&val, "ln", TYPE_FLOAT); + size_t i; + + val->m_type_params.emplace_back(power); + val->m_type_params.emplace_back(base); + + block->m_locals.push_back(whole); + block->m_locals.push_back(nth); + block->m_locals.push_back(sign); + block->m_locals.push_back(eps); + block->m_locals.push_back(A_i); + block->m_locals.push_back(B_i); + block->m_locals.push_back(A_iminus1); + block->m_locals.push_back(B_iminus1); + + /* sign = 1.0f; */ + block->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + sign, + m_fold->m_imm_float[1] + ) + ); + + /* eps = __builtin_epsilon(); */ + block->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + eps, + ast_call::make( + ctx(), + func_self("__builtin_epsilon", "ln") + ) + ) + ); + + /* + * A_i = 1; + * B_i = 0; + * A_iminus1 = 0; + * B_iminus1 = 1; + */ + for (i = 0; i <= 1; i++) { + int j; + for (j = 1; j >= 0; j--) { + block->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + ((j) ? ((i) ? B_iminus1 : A_i) + : ((i) ? A_iminus1 : B_i)), + m_fold->m_imm_float[j] + ) + ); + } + } + + /* + * = { + * power = 1.0f / power; + * sign *= -1.0f; + * } + * = { + * base = 1.0f / base; + * sign *= -1.0f; + * } + */ + for (i = 0; i <= 1; i++) { + ((i) ? blt1 : plt1)->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + ((i) ? base : power), + new ast_binary( + ctx(), + INSTR_DIV_F, + m_fold->m_imm_float[1], + ((i) ? base : power) + ) + ) + ); + plt1->m_exprs.push_back( + new ast_binstore( + ctx(), + INSTR_STORE_F, + INSTR_MUL_F, + sign, + m_fold->m_imm_float[2] + ) + ); + } + + /* + * = { + * if (power <= 0.0 || base <= 0.0f) + * return __builtin_nan(); + * if (power < 1.0f) + * + * if (base < 1.0f) + * + * } + */ + plt1orblt1->m_exprs.push_back( + new ast_ifthen( + ctx(), + new ast_binary( + ctx(), + INSTR_OR, + new ast_binary( + ctx(), + INSTR_LE, + power, + m_fold->m_imm_float[0] + ), + new ast_binary( + ctx(), + INSTR_LE, + base, + m_fold->m_imm_float[0] + ) + ), + new ast_return( + ctx(), + ast_call::make( + ctx(), + func_self("__builtin_nan", "ln") + ) + ), + nullptr + ) + ); + + for (i = 0; i <= 1; i++) { + plt1orblt1->m_exprs.push_back( + new ast_ifthen( + ctx(), + new ast_binary( + ctx(), + INSTR_LT, + ((i) ? base : power), + m_fold->m_imm_float[1] + ), + ((i) ? blt1 : plt1), + nullptr + ) + ); + } + + block->m_exprs.push_back(plt1orblt1); + + + /* whole = power; */ + forloop->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + whole, + power + ) + ); + + /* nth = 0.0f; */ + forloop->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + nth, + m_fold->m_imm_float[0] + ) + ); + + /* base2 = base; */ + whileloop->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + base2, + base + ) + ); + + /* n2 = 1.0f; */ + whileloop->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + n2, + m_fold->m_imm_float[1] + ) + ); + + /* newbase2 = base2 * base2; */ + whileloop->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + newbase2, + new ast_binary( + ctx(), + INSTR_MUL_F, + base2, + base2 + ) + ) + ); + + /* while loop locals */ + whileloop->m_locals.push_back(base2); + whileloop->m_locals.push_back(n2); + whileloop->m_locals.push_back(newbase2); + + /* base2 = newbase2; */ + nestwhile->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + base2, + newbase2 + ) + ); + + /* n2 *= 2; */ + nestwhile->m_exprs.push_back( + new ast_binstore( + ctx(), + INSTR_STORE_F, + INSTR_MUL_F, + n2, + m_fold->m_imm_float[3] /* 2.0f */ + ) + ); + + /* newbase2 *= newbase2; */ + nestwhile->m_exprs.push_back( + new ast_binstore( + ctx(), + INSTR_STORE_F, + INSTR_MUL_F, + newbase2, + newbase2 + ) + ); + + /* while (whole >= newbase2) */ + whileloop->m_exprs.push_back( + new ast_loop( + ctx(), + nullptr, + new ast_binary( + ctx(), + INSTR_GE, + whole, + newbase2 + ), + false, + nullptr, + false, + nullptr, + nestwhile + ) + ); + + /* whole /= base2; */ + whileloop->m_exprs.push_back( + new ast_binstore( + ctx(), + INSTR_STORE_F, + INSTR_DIV_F, + whole, + base2 + ) + ); + + /* nth += n2; */ + whileloop->m_exprs.push_back( + new ast_binstore( + ctx(), + INSTR_STORE_F, + INSTR_ADD_F, + nth, + n2 + ) + ); + + /* while (whole >= base) */ + forloop->m_exprs.push_back( + new ast_loop( + ctx(), + nullptr, + new ast_binary( + ctx(), + INSTR_GE, + whole, + base + ), + false, + nullptr, + false, + nullptr, + whileloop + ) + ); + + forloop->m_locals.push_back(b_iplus1); + forloop->m_locals.push_back(A_iplus1); + forloop->m_locals.push_back(B_iplus1); + + /* b_iplus1 = nth; */ + forloop->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + b_iplus1, + nth + ) + ); + + /* + * A_iplus1 = b_iplus1 * A_i + A_iminus1; + * B_iplus1 = b_iplus1 * B_i + B_iminus1; + */ + for (i = 0; i <= 1; i++) { + forloop->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + ((i) ? B_iplus1 : A_iplus1), + new ast_binary( + ctx(), + INSTR_ADD_F, + new ast_binary( + ctx(), + INSTR_MUL_F, + b_iplus1, + ((i) ? B_i : A_i) + ), + ((i) ? B_iminus1 : A_iminus1) + ) + ) + ); + } + + /* + * A_iminus1 = A_i; + * B_iminus1 = B_i; + */ + for (i = 0; i <= 1; i++) { + forloop->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + ((i) ? B_iminus1 : A_iminus1), + ((i) ? B_i : A_i) + ) + ); + } + + /* + * A_i = A_iplus1; + * B_i = B_iplus1; + */ + for (i = 0; i <= 1; i++) { + forloop->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + ((i) ? B_i : A_i), + ((i) ? B_iplus1 : A_iplus1) + ) + ); + } + + /* + * if (whole <= 1.0f + eps) + * break; + */ + forloop->m_exprs.push_back( + new ast_ifthen( + ctx(), + new ast_binary( + ctx(), + INSTR_LE, + whole, + new ast_binary( + ctx(), + INSTR_ADD_F, + m_fold->m_imm_float[1], + eps + ) + ), + new ast_breakcont( + ctx(), + false, + 0 + ), + nullptr + ) + ); + + /* + * power = base; + * base = whole; + */ + for (i = 0; i <= 1; i++) { + forloop->m_exprs.push_back( + new ast_store( + ctx(), + INSTR_STORE_F, + ((i) ? base : power), + ((i) ? whole : base) + ) + ); + } + + /* add the for loop block */ + block->m_exprs.push_back( + new ast_loop( + ctx(), + nullptr, + /* for(; 1; ) ?? (can this be nullptr too?) */ + m_fold->m_imm_float[1], + false, + nullptr, + false, + nullptr, + forloop + ) + ); + + /* return sign * A_i / B_il */ + block->m_exprs.push_back( + new ast_return( + ctx(), + new ast_binary( + ctx(), + INSTR_MUL_F, + sign, + new ast_binary( + ctx(), + INSTR_DIV_F, + A_i, + B_i + ) + ) + ) + ); + + func->m_blocks.emplace_back(block); + reg(val, func); + return val; +} + +ast_expression *intrin::log_variant(const char *name, float base) { + ast_value *val = nullptr; + ast_call *callln = ast_call::make(ctx(), func_self("__builtin_ln", name)); + ast_value *arg1 = new ast_value(ctx(), "x", TYPE_FLOAT); + ast_block *body = new ast_block(ctx()); + ast_function *func = value(&val, name, TYPE_FLOAT); + + val->m_type_params.emplace_back(arg1); + + callln->m_params.push_back(arg1); + callln->m_params.push_back(m_fold->constgen_float(base, false)); + + body->m_exprs.push_back( + new ast_return( + ctx(), + callln + ) + ); + + func->m_blocks.emplace_back(body); + reg(val, func); + return val; +} + +ast_expression *intrin::log_() { + return log_variant("log", 2.7182818284590452354); +} +ast_expression *intrin::log10_() { + return log_variant("log10", 10); +} +ast_expression *intrin::log2_() { + return log_variant("log2", 2); +} +ast_expression *intrin::logb_() { + /* FLT_RADIX == 2 for now */ + return log_variant("log2", 2); +} + +ast_expression *intrin::shift_variant(const char *name, size_t instr) { + /* + * float [shift] (float a, float b) { + * return floor(a [instr] pow(2, b)); + */ + ast_value *val = nullptr; + ast_call *callpow = ast_call::make(ctx(), func_self("pow", name)); + ast_call *callfloor = ast_call::make(ctx(), func_self("floor", name)); + ast_value *a = new ast_value(ctx(), "a", TYPE_FLOAT); + ast_value *b = new ast_value(ctx(), "b", TYPE_FLOAT); + ast_block *body = new ast_block(ctx()); + ast_function *func = value(&val, name, TYPE_FLOAT); + + val->m_type_params.emplace_back(a); + val->m_type_params.emplace_back(b); + + /* = pow(2, b) */ + callpow->m_params.push_back(m_fold->m_imm_float[3]); + callpow->m_params.push_back(b); + + /* = floor(a [instr] ) */ + callfloor->m_params.push_back( + new ast_binary( + ctx(), + instr, + a, + callpow + ) + ); + + /* return */ + body->m_exprs.push_back( + new ast_return( + ctx(), + callfloor + ) + ); + + func->m_blocks.emplace_back(body); + reg(val, func); + return val; +} + +ast_expression *intrin::lshift() { + return shift_variant("lshift", INSTR_MUL_F); +} + +ast_expression *intrin::rshift() { + return shift_variant("rshift", INSTR_DIV_F); +} + +void intrin::error(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vcompile_error(ctx(), fmt, ap); + va_end(ap); +} + +/* exposed */ +ast_expression *intrin::debug_typestring() { + return (ast_expression*)0x1; +} + +intrin::intrin(parser_t *parser) + : m_parser(parser) + , m_fold(&parser->m_fold) +{ + static const intrin_func_t intrinsics[] = { + {&intrin::isfinite_, "__builtin_isfinite", "isfinite", 1}, + {&intrin::isinf_, "__builtin_isinf", "isinf", 1}, + {&intrin::isnan_, "__builtin_isnan", "isnan", 1}, + {&intrin::isnormal_, "__builtin_isnormal", "isnormal", 1}, + {&intrin::signbit_, "__builtin_signbit", "signbit", 1}, + {&intrin::acosh_, "__builtin_acosh", "acosh", 1}, + {&intrin::asinh_, "__builtin_asinh", "asinh", 1}, + {&intrin::atanh_, "__builtin_atanh", "atanh", 1}, + {&intrin::exp_, "__builtin_exp", "exp", 1}, + {&intrin::exp2_, "__builtin_exp2", "exp2", 1}, + {&intrin::expm1_, "__builtin_expm1", "expm1", 1}, + {&intrin::mod_, "__builtin_mod", "mod", 2}, + {&intrin::pow_, "__builtin_pow", "pow", 2}, + {&intrin::fabs_, "__builtin_fabs", "fabs", 1}, + {&intrin::log_, "__builtin_log", "log", 1}, + {&intrin::log10_, "__builtin_log10", "log10", 1}, + {&intrin::log2_, "__builtin_log2", "log2", 1}, + {&intrin::logb_, "__builtin_logb", "logb", 1}, + {&intrin::lshift, "__builtin_lshift", "", 2}, + {&intrin::rshift, "__builtin_rshift", "", 2}, + {&intrin::epsilon_, "__builtin_epsilon", "", 0}, + {&intrin::nan_, "__builtin_nan", "", 0}, + {&intrin::inf_, "__builtin_inf", "", 0}, + {&intrin::ln_, "__builtin_ln", "", 2}, + {&intrin::debug_typestring, "__builtin_debug_typestring", "", 0}, + {&intrin::nullfunc, "#nullfunc", "", 0} + }; + + for (auto &it : intrinsics) { + m_intrinsics.push_back(it); + m_generated.push_back(nullptr); + } +} + +ast_expression *intrin::do_fold(ast_value *val, ast_expression **exprs) { + if (!val || !val->m_name.length()) + return nullptr; + static constexpr size_t kPrefixLength = 10; // "__builtin_" + for (auto &it : m_intrinsics) { + if (val->m_name == it.name) + return (vec_size(exprs) != it.args) + ? nullptr + : m_fold->intrinsic(val->m_name.c_str() + kPrefixLength, exprs); + } + return nullptr; +} + +ast_expression *intrin::func_try(size_t offset, const char *compare) { + for (auto &it : m_intrinsics) { + const size_t index = &it - &m_intrinsics[0]; + if (strcmp(*(char **)((char *)&it + offset), compare)) + continue; + if (m_generated[index]) + return m_generated[index]; + return m_generated[index] = (this->*it.intrin_func_t::function)(); + } + return nullptr; +} + +ast_expression *intrin::func_self(const char *name, const char *from) { + ast_expression *find; + /* try current first */ + if ((find = parser_find_global(m_parser, name)) && ((ast_value*)find)->m_vtype == TYPE_FUNCTION) + for (auto &it : m_parser->functions) + if (reinterpret_cast(find)->m_name.length() && it->m_name == reinterpret_cast(find)->m_name && it->m_builtin < 0) + return find; + /* try name second */ + if ((find = func_try(offsetof(intrin_func_t, name), name))) + return find; + /* try alias third */ + if ((find = func_try(offsetof(intrin_func_t, alias), name))) + return find; + + if (from) { + error("need function `%s', compiler depends on it for `__builtin_%s'", name, from); + return func_self("#nullfunc", nullptr); + } + return nullptr; +} + +ast_expression *intrin::func(const char *name) { + return func_self(name, nullptr); +} diff --git a/intrin.h b/intrin.h new file mode 100644 index 0000000..fe9ee78 --- /dev/null +++ b/intrin.h @@ -0,0 +1,74 @@ +#ifndef GMQCC_INTRIN_HDR +#define GMQCC_INTRIN_HDR +#include "gmqcc.h" + +struct fold; +struct parser_t; + +struct ast_function; +struct ast_expression; +struct ast_value; + +struct intrin; + +struct intrin_func_t { + ast_expression *(intrin::*function)(); + const char *name; + const char *alias; + size_t args; +}; + +struct intrin { + intrin() = default; + intrin(parser_t *parser); + + ast_expression *debug_typestring(); + ast_expression *do_fold(ast_value *val, ast_expression **exprs); + ast_expression *func_try(size_t offset, const char *compare); + ast_expression *func_self(const char *name, const char *from); + ast_expression *func(const char *name); + +protected: + lex_ctx_t ctx() const; + ast_function *value(ast_value **out, const char *name, qc_type vtype); + void reg(ast_value *const value, ast_function *const func); + + ast_expression *nullfunc(); + ast_expression *isfinite_(); + ast_expression *isinf_(); + ast_expression *isnan_(); + ast_expression *isnormal_(); + ast_expression *signbit_(); + ast_expression *acosh_(); + ast_expression *asinh_(); + ast_expression *atanh_(); + ast_expression *exp_(); + ast_expression *exp2_(); + ast_expression *expm1_(); + ast_expression *pow_(); + ast_expression *mod_(); + ast_expression *fabs_(); + ast_expression *epsilon_(); + ast_expression *nan_(); + ast_expression *inf_(); + ast_expression *ln_(); + ast_expression *log_variant(const char *name, float base); + ast_expression *log_(); + ast_expression *log10_(); + ast_expression *log2_(); + ast_expression *logb_(); + ast_expression *shift_variant(const char *name, size_t instr); + ast_expression *lshift(); + ast_expression *rshift(); + + void error(const char *fmt, ...); + +private: + parser_t *m_parser; + fold *m_fold; + std::vector m_intrinsics; + std::vector m_generated; +}; + + +#endif diff --git a/ir.c b/ir.c deleted file mode 100644 index 73cbc9a..0000000 --- a/ir.c +++ /dev/null @@ -1,4468 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * Wolfgang Bumiller - * 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 -#include - -#include "gmqcc.h" -#include "ir.h" - -/*********************************************************************** - * Type sizes used at multiple points in the IR codegen - */ - -const char *type_name[TYPE_COUNT] = { - "void", - "string", - "float", - "vector", - "entity", - "field", - "function", - "pointer", - "integer", - "variant", - "struct", - "union", - "array", - - "nil", - "" -}; - -static size_t type_sizeof_[TYPE_COUNT] = { - 1, /* TYPE_VOID */ - 1, /* TYPE_STRING */ - 1, /* TYPE_FLOAT */ - 3, /* TYPE_VECTOR */ - 1, /* TYPE_ENTITY */ - 1, /* TYPE_FIELD */ - 1, /* TYPE_FUNCTION */ - 1, /* TYPE_POINTER */ - 1, /* TYPE_INTEGER */ - 3, /* TYPE_VARIANT */ - 0, /* TYPE_STRUCT */ - 0, /* TYPE_UNION */ - 0, /* TYPE_ARRAY */ - 0, /* TYPE_NIL */ - 0, /* TYPE_NOESPR */ -}; - -const uint16_t type_store_instr[TYPE_COUNT] = { - INSTR_STORE_F, /* should use I when having integer support */ - INSTR_STORE_S, - INSTR_STORE_F, - INSTR_STORE_V, - INSTR_STORE_ENT, - INSTR_STORE_FLD, - INSTR_STORE_FNC, - INSTR_STORE_ENT, /* should use I */ -#if 0 - INSTR_STORE_I, /* integer type */ -#else - INSTR_STORE_F, -#endif - - INSTR_STORE_V, /* variant, should never be accessed */ - - VINSTR_END, /* struct */ - VINSTR_END, /* union */ - VINSTR_END, /* array */ - VINSTR_END, /* nil */ - VINSTR_END, /* noexpr */ -}; - -const uint16_t field_store_instr[TYPE_COUNT] = { - INSTR_STORE_FLD, - INSTR_STORE_FLD, - INSTR_STORE_FLD, - INSTR_STORE_V, - INSTR_STORE_FLD, - INSTR_STORE_FLD, - INSTR_STORE_FLD, - INSTR_STORE_FLD, -#if 0 - INSTR_STORE_FLD, /* integer type */ -#else - INSTR_STORE_FLD, -#endif - - INSTR_STORE_V, /* variant, should never be accessed */ - - VINSTR_END, /* struct */ - VINSTR_END, /* union */ - VINSTR_END, /* array */ - VINSTR_END, /* nil */ - VINSTR_END, /* noexpr */ -}; - -const uint16_t type_storep_instr[TYPE_COUNT] = { - INSTR_STOREP_F, /* should use I when having integer support */ - INSTR_STOREP_S, - INSTR_STOREP_F, - INSTR_STOREP_V, - INSTR_STOREP_ENT, - INSTR_STOREP_FLD, - INSTR_STOREP_FNC, - INSTR_STOREP_ENT, /* should use I */ -#if 0 - INSTR_STOREP_ENT, /* integer type */ -#else - INSTR_STOREP_F, -#endif - - INSTR_STOREP_V, /* variant, should never be accessed */ - - VINSTR_END, /* struct */ - VINSTR_END, /* union */ - VINSTR_END, /* array */ - VINSTR_END, /* nil */ - VINSTR_END, /* noexpr */ -}; - -const uint16_t type_eq_instr[TYPE_COUNT] = { - INSTR_EQ_F, /* should use I when having integer support */ - INSTR_EQ_S, - INSTR_EQ_F, - INSTR_EQ_V, - INSTR_EQ_E, - INSTR_EQ_E, /* FLD has no comparison */ - INSTR_EQ_FNC, - INSTR_EQ_E, /* should use I */ -#if 0 - INSTR_EQ_I, -#else - INSTR_EQ_F, -#endif - - INSTR_EQ_V, /* variant, should never be accessed */ - - VINSTR_END, /* struct */ - VINSTR_END, /* union */ - VINSTR_END, /* array */ - VINSTR_END, /* nil */ - VINSTR_END, /* noexpr */ -}; - -const uint16_t type_ne_instr[TYPE_COUNT] = { - INSTR_NE_F, /* should use I when having integer support */ - INSTR_NE_S, - INSTR_NE_F, - INSTR_NE_V, - INSTR_NE_E, - INSTR_NE_E, /* FLD has no comparison */ - INSTR_NE_FNC, - INSTR_NE_E, /* should use I */ -#if 0 - INSTR_NE_I, -#else - INSTR_NE_F, -#endif - - INSTR_NE_V, /* variant, should never be accessed */ - - VINSTR_END, /* struct */ - VINSTR_END, /* union */ - VINSTR_END, /* array */ - VINSTR_END, /* nil */ - VINSTR_END, /* noexpr */ -}; - -const uint16_t type_not_instr[TYPE_COUNT] = { - INSTR_NOT_F, /* should use I when having integer support */ - VINSTR_END, /* not to be used, depends on string related -f flags */ - INSTR_NOT_F, - INSTR_NOT_V, - INSTR_NOT_ENT, - INSTR_NOT_ENT, - INSTR_NOT_FNC, - INSTR_NOT_ENT, /* should use I */ -#if 0 - INSTR_NOT_I, /* integer type */ -#else - INSTR_NOT_F, -#endif - - INSTR_NOT_V, /* variant, should never be accessed */ - - VINSTR_END, /* struct */ - VINSTR_END, /* union */ - VINSTR_END, /* array */ - VINSTR_END, /* nil */ - VINSTR_END, /* noexpr */ -}; - -/* protos */ -static ir_value* ir_value_var(const char *name, int st, int vtype); -static bool ir_value_set_name(ir_value*, const char *name); -static void ir_value_dump(ir_value*, int (*oprintf)(const char*,...)); - -static ir_value* ir_gen_extparam_proto(ir_builder *ir); -static void ir_gen_extparam (ir_builder *ir); - -static bool ir_builder_set_name(ir_builder *self, const char *name); - -static ir_function* ir_function_new(struct ir_builder_s *owner, int returntype); -static bool ir_function_set_name(ir_function*, const char *name); -static void ir_function_delete(ir_function*); -static void ir_function_dump(ir_function*, char *ind, int (*oprintf)(const char*,...)); - -static ir_value* ir_block_create_general_instr(ir_block *self, lex_ctx_t, const char *label, - int op, ir_value *a, ir_value *b, int outype); -static void ir_block_delete(ir_block*); -static ir_block* ir_block_new(struct ir_function_s *owner, const char *label); -static bool GMQCC_WARN ir_block_create_store(ir_block*, lex_ctx_t, ir_value *target, ir_value *what); -static bool ir_block_set_label(ir_block*, const char *label); -static void ir_block_dump(ir_block*, char *ind, int (*oprintf)(const char*,...)); - -static bool ir_instr_op(ir_instr*, int op, ir_value *value, bool writing); -static void ir_instr_delete(ir_instr*); -static void ir_instr_dump(ir_instr* in, char *ind, int (*oprintf)(const char*,...)); -/* error functions */ - -static void irerror(lex_ctx_t ctx, const char *msg, ...) -{ - va_list ap; - va_start(ap, msg); - con_cvprintmsg(ctx, LVL_ERROR, "internal error", msg, ap); - va_end(ap); -} - -static bool GMQCC_WARN irwarning(lex_ctx_t ctx, int warntype, const char *fmt, ...) -{ - bool r; - va_list ap; - va_start(ap, fmt); - r = vcompile_warning(ctx, warntype, fmt, ap); - va_end(ap); - return r; -} - -/*********************************************************************** - * Vector utility functions - */ - -static bool GMQCC_WARN vec_ir_value_find(ir_value **vec, const ir_value *what, size_t *idx) -{ - size_t i; - size_t len = vec_size(vec); - for (i = 0; i < len; ++i) { - if (vec[i] == what) { - if (idx) *idx = i; - return true; - } - } - return false; -} - -static bool GMQCC_WARN vec_ir_block_find(ir_block **vec, ir_block *what, size_t *idx) -{ - size_t i; - size_t len = vec_size(vec); - for (i = 0; i < len; ++i) { - if (vec[i] == what) { - if (idx) *idx = i; - return true; - } - } - return false; -} - -static bool GMQCC_WARN vec_ir_instr_find(ir_instr **vec, ir_instr *what, size_t *idx) -{ - size_t i; - size_t len = vec_size(vec); - for (i = 0; i < len; ++i) { - if (vec[i] == what) { - if (idx) *idx = i; - return true; - } - } - return false; -} - -/*********************************************************************** - * IR Builder - */ - -static void ir_block_delete_quick(ir_block* self); -static void ir_instr_delete_quick(ir_instr *self); -static void ir_function_delete_quick(ir_function *self); - -ir_builder* ir_builder_new(const char *modulename) -{ - ir_builder* self; - size_t i; - - self = (ir_builder*)mem_a(sizeof(*self)); - if (!self) - return NULL; - - self->functions = NULL; - self->globals = NULL; - self->fields = NULL; - self->filenames = NULL; - self->filestrings = NULL; - self->htglobals = util_htnew(IR_HT_SIZE); - self->htfields = util_htnew(IR_HT_SIZE); - self->htfunctions = util_htnew(IR_HT_SIZE); - - self->extparams = NULL; - self->extparam_protos = NULL; - - self->first_common_globaltemp = 0; - self->max_globaltemps = 0; - self->first_common_local = 0; - self->max_locals = 0; - - self->str_immediate = 0; - self->name = NULL; - if (!ir_builder_set_name(self, modulename)) { - mem_d(self); - return NULL; - } - - self->nil = ir_value_var("nil", store_value, TYPE_NIL); - self->nil->cvq = CV_CONST; - - for (i = 0; i != IR_MAX_VINSTR_TEMPS; ++i) { - /* we write to them, but they're not supposed to be used outside the IR, so - * let's not allow the generation of ir_instrs which use these. - * So it's a constant noexpr. - */ - self->vinstr_temp[i] = ir_value_var("vinstr_temp", store_value, TYPE_NOEXPR); - self->vinstr_temp[i]->cvq = CV_CONST; - } - - self->reserved_va_count = NULL; - self->coverage_func = NULL; - - self->code = code_init(); - - return self; -} - -void ir_builder_delete(ir_builder* self) -{ - size_t i; - util_htdel(self->htglobals); - util_htdel(self->htfields); - util_htdel(self->htfunctions); - mem_d((void*)self->name); - for (i = 0; i != vec_size(self->functions); ++i) { - ir_function_delete_quick(self->functions[i]); - } - vec_free(self->functions); - for (i = 0; i != vec_size(self->extparams); ++i) { - ir_value_delete(self->extparams[i]); - } - vec_free(self->extparams); - vec_free(self->extparam_protos); - for (i = 0; i != vec_size(self->globals); ++i) { - ir_value_delete(self->globals[i]); - } - vec_free(self->globals); - for (i = 0; i != vec_size(self->fields); ++i) { - ir_value_delete(self->fields[i]); - } - ir_value_delete(self->nil); - for (i = 0; i != IR_MAX_VINSTR_TEMPS; ++i) { - ir_value_delete(self->vinstr_temp[i]); - } - vec_free(self->fields); - vec_free(self->filenames); - vec_free(self->filestrings); - - code_cleanup(self->code); - mem_d(self); -} - -bool ir_builder_set_name(ir_builder *self, const char *name) -{ - if (self->name) - mem_d((void*)self->name); - self->name = util_strdup(name); - return !!self->name; -} - -static ir_function* ir_builder_get_function(ir_builder *self, const char *name) -{ - return (ir_function*)util_htget(self->htfunctions, name); -} - -ir_function* ir_builder_create_function(ir_builder *self, const char *name, int outtype) -{ - ir_function *fn = ir_builder_get_function(self, name); - if (fn) { - return NULL; - } - - fn = ir_function_new(self, outtype); - if (!ir_function_set_name(fn, name)) - { - ir_function_delete(fn); - return NULL; - } - vec_push(self->functions, fn); - util_htset(self->htfunctions, name, fn); - - fn->value = ir_builder_create_global(self, fn->name, TYPE_FUNCTION); - if (!fn->value) { - ir_function_delete(fn); - return NULL; - } - - fn->value->hasvalue = true; - fn->value->outtype = outtype; - fn->value->constval.vfunc = fn; - fn->value->context = fn->context; - - return fn; -} - -static ir_value* ir_builder_get_global(ir_builder *self, const char *name) -{ - return (ir_value*)util_htget(self->htglobals, name); -} - -ir_value* ir_builder_create_global(ir_builder *self, const char *name, int vtype) -{ - ir_value *ve; - - if (name[0] != '#') - { - ve = ir_builder_get_global(self, name); - if (ve) { - return NULL; - } - } - - ve = ir_value_var(name, store_global, vtype); - vec_push(self->globals, ve); - util_htset(self->htglobals, name, ve); - return ve; -} - -ir_value* ir_builder_get_va_count(ir_builder *self) -{ - if (self->reserved_va_count) - return self->reserved_va_count; - return (self->reserved_va_count = ir_builder_create_global(self, "reserved:va_count", TYPE_FLOAT)); -} - -static ir_value* ir_builder_get_field(ir_builder *self, const char *name) -{ - return (ir_value*)util_htget(self->htfields, name); -} - - -ir_value* ir_builder_create_field(ir_builder *self, const char *name, int vtype) -{ - ir_value *ve = ir_builder_get_field(self, name); - if (ve) { - return NULL; - } - - ve = ir_value_var(name, store_global, TYPE_FIELD); - ve->fieldtype = vtype; - vec_push(self->fields, ve); - util_htset(self->htfields, name, ve); - return ve; -} - -/*********************************************************************** - *IR Function - */ - -static bool ir_function_naive_phi(ir_function*); -static void ir_function_enumerate(ir_function*); -static bool ir_function_calculate_liferanges(ir_function*); -static bool ir_function_allocate_locals(ir_function*); - -ir_function* ir_function_new(ir_builder* owner, int outtype) -{ - ir_function *self; - self = (ir_function*)mem_a(sizeof(*self)); - - if (!self) - return NULL; - - memset(self, 0, sizeof(*self)); - - self->name = NULL; - if (!ir_function_set_name(self, "<@unnamed>")) { - mem_d(self); - return NULL; - } - self->flags = 0; - - self->owner = owner; - self->context.file = "<@no context>"; - self->context.line = 0; - self->outtype = outtype; - self->value = NULL; - self->builtin = 0; - - self->params = NULL; - self->blocks = NULL; - self->values = NULL; - self->locals = NULL; - - self->max_varargs = 0; - - self->code_function_def = -1; - self->allocated_locals = 0; - self->globaltemps = 0; - - self->run_id = 0; - return self; -} - -bool ir_function_set_name(ir_function *self, const char *name) -{ - if (self->name) - mem_d((void*)self->name); - self->name = util_strdup(name); - return !!self->name; -} - -static void ir_function_delete_quick(ir_function *self) -{ - size_t i; - mem_d((void*)self->name); - - for (i = 0; i != vec_size(self->blocks); ++i) - ir_block_delete_quick(self->blocks[i]); - vec_free(self->blocks); - - vec_free(self->params); - - for (i = 0; i != vec_size(self->values); ++i) - ir_value_delete(self->values[i]); - vec_free(self->values); - - for (i = 0; i != vec_size(self->locals); ++i) - ir_value_delete(self->locals[i]); - vec_free(self->locals); - - /* self->value is deleted by the builder */ - - mem_d(self); -} - -void ir_function_delete(ir_function *self) -{ - size_t i; - mem_d((void*)self->name); - - for (i = 0; i != vec_size(self->blocks); ++i) - ir_block_delete(self->blocks[i]); - vec_free(self->blocks); - - vec_free(self->params); - - for (i = 0; i != vec_size(self->values); ++i) - ir_value_delete(self->values[i]); - vec_free(self->values); - - for (i = 0; i != vec_size(self->locals); ++i) - ir_value_delete(self->locals[i]); - vec_free(self->locals); - - /* self->value is deleted by the builder */ - - mem_d(self); -} - -static void ir_function_collect_value(ir_function *self, ir_value *v) -{ - vec_push(self->values, v); -} - -ir_block* ir_function_create_block(lex_ctx_t ctx, ir_function *self, const char *label) -{ - ir_block* bn = ir_block_new(self, label); - bn->context = ctx; - vec_push(self->blocks, bn); - - if ((self->flags & IR_FLAG_BLOCK_COVERAGE) && self->owner->coverage_func) - (void)ir_block_create_call(bn, ctx, NULL, self->owner->coverage_func, false); - - return bn; -} - -static bool instr_is_operation(uint16_t op) -{ - return ( (op >= INSTR_MUL_F && op <= INSTR_GT) || - (op >= INSTR_LOAD_F && op <= INSTR_LOAD_FNC) || - (op == INSTR_ADDRESS) || - (op >= INSTR_NOT_F && op <= INSTR_NOT_FNC) || - (op >= INSTR_AND && op <= INSTR_BITOR) || - (op >= INSTR_CALL0 && op <= INSTR_CALL8) || - (op >= VINSTR_BITAND_V && op <= VINSTR_NEG_V) ); -} - -static bool ir_function_pass_peephole(ir_function *self) -{ - size_t b; - - for (b = 0; b < vec_size(self->blocks); ++b) { - size_t i; - ir_block *block = self->blocks[b]; - - for (i = 0; i < vec_size(block->instr); ++i) { - ir_instr *inst; - inst = block->instr[i]; - - if (i >= 1 && - (inst->opcode >= INSTR_STORE_F && - inst->opcode <= INSTR_STORE_FNC)) - { - ir_instr *store; - ir_instr *oper; - ir_value *value; - - store = inst; - - oper = block->instr[i-1]; - if (!instr_is_operation(oper->opcode)) - continue; - - /* Don't change semantics of MUL_VF in engines where these may not alias. */ - if (OPTS_FLAG(LEGACY_VECTOR_MATHS)) { - if (oper->opcode == INSTR_MUL_VF && oper->_ops[2]->memberof == oper->_ops[1]) - continue; - if (oper->opcode == INSTR_MUL_FV && oper->_ops[1]->memberof == oper->_ops[2]) - continue; - } - - value = oper->_ops[0]; - - /* only do it for SSA values */ - if (value->store != store_value) - continue; - - /* don't optimize out the temp if it's used later again */ - if (vec_size(value->reads) != 1) - continue; - - /* The very next store must use this value */ - if (value->reads[0] != store) - continue; - - /* And of course the store must _read_ from it, so it's in - * OP 1 */ - if (store->_ops[1] != value) - continue; - - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - (void)!ir_instr_op(oper, 0, store->_ops[0], true); - - vec_remove(block->instr, i, 1); - ir_instr_delete(store); - } - else if (inst->opcode == VINSTR_COND) - { - /* COND on a value resulting from a NOT could - * remove the NOT and swap its operands - */ - while (true) { - ir_block *tmp; - size_t inotid; - ir_instr *inot; - ir_value *value; - value = inst->_ops[0]; - - if (value->store != store_value || - vec_size(value->reads) != 1 || - value->reads[0] != inst) - { - break; - } - - inot = value->writes[0]; - if (inot->_ops[0] != value || - inot->opcode < INSTR_NOT_F || - inot->opcode > INSTR_NOT_FNC || - inot->opcode == INSTR_NOT_V || /* can't do these */ - inot->opcode == INSTR_NOT_S) - { - break; - } - - /* count */ - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - /* change operand */ - (void)!ir_instr_op(inst, 0, inot->_ops[1], false); - /* remove NOT */ - tmp = inot->owner; - for (inotid = 0; inotid < vec_size(tmp->instr); ++inotid) { - if (tmp->instr[inotid] == inot) - break; - } - if (inotid >= vec_size(tmp->instr)) { - compile_error(inst->context, "sanity-check failed: failed to find instruction to optimize out"); - return false; - } - vec_remove(tmp->instr, inotid, 1); - ir_instr_delete(inot); - /* swap ontrue/onfalse */ - tmp = inst->bops[0]; - inst->bops[0] = inst->bops[1]; - inst->bops[1] = tmp; - } - continue; - } - } - } - - return true; -} - -static bool ir_function_pass_tailrecursion(ir_function *self) -{ - size_t b, p; - - for (b = 0; b < vec_size(self->blocks); ++b) { - ir_value *funcval; - ir_instr *ret, *call, *store = NULL; - ir_block *block = self->blocks[b]; - - if (!block->final || vec_size(block->instr) < 2) - continue; - - ret = block->instr[vec_size(block->instr)-1]; - if (ret->opcode != INSTR_DONE && ret->opcode != INSTR_RETURN) - continue; - - call = block->instr[vec_size(block->instr)-2]; - if (call->opcode >= INSTR_STORE_F && call->opcode <= INSTR_STORE_FNC) { - /* account for the unoptimized - * CALL - * STORE %return, %tmp - * RETURN %tmp - * version - */ - if (vec_size(block->instr) < 3) - continue; - - store = call; - call = block->instr[vec_size(block->instr)-3]; - } - - if (call->opcode < INSTR_CALL0 || call->opcode > INSTR_CALL8) - continue; - - if (store) { - /* optimize out the STORE */ - if (ret->_ops[0] && - ret->_ops[0] == store->_ops[0] && - store->_ops[1] == call->_ops[0]) - { - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - call->_ops[0] = store->_ops[0]; - vec_remove(block->instr, vec_size(block->instr) - 2, 1); - ir_instr_delete(store); - } - else - continue; - } - - if (!call->_ops[0]) - continue; - - funcval = call->_ops[1]; - if (!funcval) - continue; - if (funcval->vtype != TYPE_FUNCTION || funcval->constval.vfunc != self) - continue; - - /* now we have a CALL and a RET, check if it's a tailcall */ - if (ret->_ops[0] && call->_ops[0] != ret->_ops[0]) - continue; - - ++opts_optimizationcount[OPTIM_TAIL_RECURSION]; - vec_shrinkby(block->instr, 2); - - block->final = false; /* open it back up */ - - /* emite parameter-stores */ - for (p = 0; p < vec_size(call->params); ++p) { - /* assert(call->params_count <= self->locals_count); */ - if (!ir_block_create_store(block, call->context, self->locals[p], call->params[p])) { - irerror(call->context, "failed to create tailcall store instruction for parameter %i", (int)p); - return false; - } - } - if (!ir_block_create_jump(block, call->context, self->blocks[0])) { - irerror(call->context, "failed to create tailcall jump"); - return false; - } - - ir_instr_delete(call); - ir_instr_delete(ret); - } - - return true; -} - -bool ir_function_finalize(ir_function *self) -{ - size_t i; - - if (self->builtin) - return true; - - if (OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) { - if (!ir_function_pass_peephole(self)) { - irerror(self->context, "generic optimization pass broke something in `%s`", self->name); - return false; - } - } - - if (OPTS_OPTIMIZATION(OPTIM_TAIL_RECURSION)) { - if (!ir_function_pass_tailrecursion(self)) { - irerror(self->context, "tail-recursion optimization pass broke something in `%s`", self->name); - return false; - } - } - - if (!ir_function_naive_phi(self)) { - irerror(self->context, "internal error: ir_function_naive_phi failed"); - return false; - } - - for (i = 0; i < vec_size(self->locals); ++i) { - ir_value *v = self->locals[i]; - if (v->vtype == TYPE_VECTOR || - (v->vtype == TYPE_FIELD && v->outtype == TYPE_VECTOR)) - { - ir_value_vector_member(v, 0); - ir_value_vector_member(v, 1); - ir_value_vector_member(v, 2); - } - } - for (i = 0; i < vec_size(self->values); ++i) { - ir_value *v = self->values[i]; - if (v->vtype == TYPE_VECTOR || - (v->vtype == TYPE_FIELD && v->outtype == TYPE_VECTOR)) - { - ir_value_vector_member(v, 0); - ir_value_vector_member(v, 1); - ir_value_vector_member(v, 2); - } - } - - ir_function_enumerate(self); - - if (!ir_function_calculate_liferanges(self)) - return false; - if (!ir_function_allocate_locals(self)) - return false; - return true; -} - -ir_value* ir_function_create_local(ir_function *self, const char *name, int vtype, bool param) -{ - ir_value *ve; - - if (param && - vec_size(self->locals) && - self->locals[vec_size(self->locals)-1]->store != store_param) { - irerror(self->context, "cannot add parameters after adding locals"); - return NULL; - } - - ve = ir_value_var(name, (param ? store_param : store_local), vtype); - if (param) - ve->locked = true; - vec_push(self->locals, ve); - return ve; -} - -/*********************************************************************** - *IR Block - */ - -ir_block* ir_block_new(ir_function* owner, const char *name) -{ - ir_block *self; - self = (ir_block*)mem_a(sizeof(*self)); - if (!self) - return NULL; - - memset(self, 0, sizeof(*self)); - - self->label = NULL; - if (name && !ir_block_set_label(self, name)) { - mem_d(self); - return NULL; - } - self->owner = owner; - self->context.file = "<@no context>"; - self->context.line = 0; - self->final = false; - - self->instr = NULL; - self->entries = NULL; - self->exits = NULL; - - self->eid = 0; - self->is_return = false; - - self->living = NULL; - - self->generated = false; - - return self; -} - -static void ir_block_delete_quick(ir_block* self) -{ - size_t i; - if (self->label) mem_d(self->label); - for (i = 0; i != vec_size(self->instr); ++i) - ir_instr_delete_quick(self->instr[i]); - vec_free(self->instr); - vec_free(self->entries); - vec_free(self->exits); - vec_free(self->living); - mem_d(self); -} - -void ir_block_delete(ir_block* self) -{ - size_t i; - if (self->label) mem_d(self->label); - for (i = 0; i != vec_size(self->instr); ++i) - ir_instr_delete(self->instr[i]); - vec_free(self->instr); - vec_free(self->entries); - vec_free(self->exits); - vec_free(self->living); - mem_d(self); -} - -bool ir_block_set_label(ir_block *self, const char *name) -{ - if (self->label) - mem_d((void*)self->label); - self->label = util_strdup(name); - return !!self->label; -} - -/*********************************************************************** - *IR Instructions - */ - -static ir_instr* ir_instr_new(lex_ctx_t ctx, ir_block* owner, int op) -{ - ir_instr *self; - self = (ir_instr*)mem_a(sizeof(*self)); - if (!self) - return NULL; - - self->owner = owner; - self->context = ctx; - self->opcode = op; - self->_ops[0] = NULL; - self->_ops[1] = NULL; - self->_ops[2] = NULL; - self->bops[0] = NULL; - self->bops[1] = NULL; - - self->phi = NULL; - self->params = NULL; - - self->eid = 0; - - self->likely = true; - return self; -} - -static void ir_instr_delete_quick(ir_instr *self) -{ - vec_free(self->phi); - vec_free(self->params); - mem_d(self); -} - -static void ir_instr_delete(ir_instr *self) -{ - size_t i; - /* The following calls can only delete from - * vectors, we still want to delete this instruction - * so ignore the return value. Since with the warn_unused_result attribute - * gcc doesn't care about an explicit: (void)foo(); to ignore the result, - * I have to improvise here and use if(foo()); - */ - for (i = 0; i < vec_size(self->phi); ++i) { - size_t idx; - if (vec_ir_instr_find(self->phi[i].value->writes, self, &idx)) - vec_remove(self->phi[i].value->writes, idx, 1); - if (vec_ir_instr_find(self->phi[i].value->reads, self, &idx)) - vec_remove(self->phi[i].value->reads, idx, 1); - } - vec_free(self->phi); - for (i = 0; i < vec_size(self->params); ++i) { - size_t idx; - if (vec_ir_instr_find(self->params[i]->writes, self, &idx)) - vec_remove(self->params[i]->writes, idx, 1); - if (vec_ir_instr_find(self->params[i]->reads, self, &idx)) - vec_remove(self->params[i]->reads, idx, 1); - } - vec_free(self->params); - (void)!ir_instr_op(self, 0, NULL, false); - (void)!ir_instr_op(self, 1, NULL, false); - (void)!ir_instr_op(self, 2, NULL, false); - mem_d(self); -} - -static bool ir_instr_op(ir_instr *self, int op, ir_value *v, bool writing) -{ - if (v && v->vtype == TYPE_NOEXPR) { - irerror(self->context, "tried to use a NOEXPR value"); - return false; - } - - if (self->_ops[op]) { - size_t idx; - if (writing && vec_ir_instr_find(self->_ops[op]->writes, self, &idx)) - vec_remove(self->_ops[op]->writes, idx, 1); - else if (vec_ir_instr_find(self->_ops[op]->reads, self, &idx)) - vec_remove(self->_ops[op]->reads, idx, 1); - } - if (v) { - if (writing) - vec_push(v->writes, self); - else - vec_push(v->reads, self); - } - self->_ops[op] = v; - return true; -} - -/*********************************************************************** - *IR Value - */ - -static void ir_value_code_setaddr(ir_value *self, int32_t gaddr) -{ - self->code.globaladdr = gaddr; - if (self->members[0]) self->members[0]->code.globaladdr = gaddr; - if (self->members[1]) self->members[1]->code.globaladdr = gaddr; - if (self->members[2]) self->members[2]->code.globaladdr = gaddr; -} - -static int32_t ir_value_code_addr(const ir_value *self) -{ - if (self->store == store_return) - return OFS_RETURN + self->code.addroffset; - return self->code.globaladdr + self->code.addroffset; -} - -ir_value* ir_value_var(const char *name, int storetype, int vtype) -{ - ir_value *self; - self = (ir_value*)mem_a(sizeof(*self)); - self->vtype = vtype; - self->fieldtype = TYPE_VOID; - self->outtype = TYPE_VOID; - self->store = storetype; - self->flags = 0; - - self->reads = NULL; - self->writes = NULL; - - self->cvq = CV_NONE; - self->hasvalue = false; - self->context.file = "<@no context>"; - self->context.line = 0; - self->name = NULL; - if (name && !ir_value_set_name(self, name)) { - irerror(self->context, "out of memory"); - mem_d(self); - return NULL; - } - - memset(&self->constval, 0, sizeof(self->constval)); - memset(&self->code, 0, sizeof(self->code)); - - self->members[0] = NULL; - self->members[1] = NULL; - self->members[2] = NULL; - self->memberof = NULL; - - self->unique_life = false; - self->locked = false; - self->callparam = false; - - self->life = NULL; - return self; -} - -/* helper function */ -static ir_value* ir_builder_imm_float(ir_builder *self, float value, bool add_to_list) { - ir_value *v = ir_value_var("#IMMEDIATE", store_global, TYPE_FLOAT); - v->flags |= IR_FLAG_ERASABLE; - v->hasvalue = true; - v->cvq = CV_CONST; - v->constval.vfloat = value; - - vec_push(self->globals, v); - if (add_to_list) - vec_push(self->const_floats, v); - return v; -} - -ir_value* ir_value_vector_member(ir_value *self, unsigned int member) -{ - char *name; - size_t len; - ir_value *m; - if (member >= 3) - return NULL; - - if (self->members[member]) - return self->members[member]; - - if (self->name) { - len = strlen(self->name); - name = (char*)mem_a(len + 3); - memcpy(name, self->name, len); - name[len+0] = '_'; - name[len+1] = 'x' + member; - name[len+2] = '\0'; - } - else - name = NULL; - - if (self->vtype == TYPE_VECTOR) - { - m = ir_value_var(name, self->store, TYPE_FLOAT); - if (name) - mem_d(name); - if (!m) - return NULL; - m->context = self->context; - - self->members[member] = m; - m->code.addroffset = member; - } - else if (self->vtype == TYPE_FIELD) - { - if (self->fieldtype != TYPE_VECTOR) - return NULL; - m = ir_value_var(name, self->store, TYPE_FIELD); - if (name) - mem_d(name); - if (!m) - return NULL; - m->fieldtype = TYPE_FLOAT; - m->context = self->context; - - self->members[member] = m; - m->code.addroffset = member; - } - else - { - irerror(self->context, "invalid member access on %s", self->name); - return NULL; - } - - m->memberof = self; - return m; -} - -static GMQCC_INLINE size_t ir_value_sizeof(const ir_value *self) -{ - if (self->vtype == TYPE_FIELD && self->fieldtype == TYPE_VECTOR) - return type_sizeof_[TYPE_VECTOR]; - return type_sizeof_[self->vtype]; -} - -static ir_value* ir_value_out(ir_function *owner, const char *name, int storetype, int vtype) -{ - ir_value *v = ir_value_var(name, storetype, vtype); - if (!v) - return NULL; - ir_function_collect_value(owner, v); - return v; -} - -void ir_value_delete(ir_value* self) -{ - size_t i; - if (self->name) - mem_d((void*)self->name); - if (self->hasvalue) - { - if (self->vtype == TYPE_STRING) - mem_d((void*)self->constval.vstring); - } - if (!(self->flags & IR_FLAG_SPLIT_VECTOR)) { - for (i = 0; i < 3; ++i) { - if (self->members[i]) - ir_value_delete(self->members[i]); - } - } - vec_free(self->reads); - vec_free(self->writes); - vec_free(self->life); - mem_d(self); -} - -bool ir_value_set_name(ir_value *self, const char *name) -{ - if (self->name) - mem_d((void*)self->name); - self->name = util_strdup(name); - return !!self->name; -} - -bool ir_value_set_float(ir_value *self, float f) -{ - if (self->vtype != TYPE_FLOAT) - return false; - self->constval.vfloat = f; - self->hasvalue = true; - return true; -} - -bool ir_value_set_func(ir_value *self, int f) -{ - if (self->vtype != TYPE_FUNCTION) - return false; - self->constval.vint = f; - self->hasvalue = true; - return true; -} - -bool ir_value_set_vector(ir_value *self, vec3_t v) -{ - if (self->vtype != TYPE_VECTOR) - return false; - self->constval.vvec = v; - self->hasvalue = true; - return true; -} - -bool ir_value_set_field(ir_value *self, ir_value *fld) -{ - if (self->vtype != TYPE_FIELD) - return false; - self->constval.vpointer = fld; - self->hasvalue = true; - return true; -} - -bool ir_value_set_string(ir_value *self, const char *str) -{ - if (self->vtype != TYPE_STRING) - return false; - self->constval.vstring = util_strdupe(str); - self->hasvalue = true; - return true; -} - -#if 0 -bool ir_value_set_int(ir_value *self, int i) -{ - if (self->vtype != TYPE_INTEGER) - return false; - self->constval.vint = i; - self->hasvalue = true; - return true; -} -#endif - -bool ir_value_lives(ir_value *self, size_t at) -{ - size_t i; - for (i = 0; i < vec_size(self->life); ++i) - { - ir_life_entry_t *life = &self->life[i]; - if (life->start <= at && at <= life->end) - return true; - if (life->start > at) /* since it's ordered */ - return false; - } - return false; -} - -static bool ir_value_life_insert(ir_value *self, size_t idx, ir_life_entry_t e) -{ - size_t k; - vec_push(self->life, e); - for (k = vec_size(self->life)-1; k > idx; --k) - self->life[k] = self->life[k-1]; - self->life[idx] = e; - return true; -} - -static bool ir_value_life_merge(ir_value *self, size_t s) -{ - size_t i; - const size_t vs = vec_size(self->life); - ir_life_entry_t *life = NULL; - ir_life_entry_t *before = NULL; - ir_life_entry_t new_entry; - - /* Find the first range >= s */ - for (i = 0; i < vs; ++i) - { - before = life; - life = &self->life[i]; - if (life->start > s) - break; - } - /* nothing found? append */ - if (i == vs) { - ir_life_entry_t e; - if (life && life->end+1 == s) - { - /* previous life range can be merged in */ - life->end++; - return true; - } - if (life && life->end >= s) - return false; - e.start = e.end = s; - vec_push(self->life, e); - return true; - } - /* found */ - if (before) - { - if (before->end + 1 == s && - life->start - 1 == s) - { - /* merge */ - before->end = life->end; - vec_remove(self->life, i, 1); - return true; - } - if (before->end + 1 == s) - { - /* extend before */ - before->end++; - return true; - } - /* already contained */ - if (before->end >= s) - return false; - } - /* extend */ - if (life->start - 1 == s) - { - life->start--; - return true; - } - /* insert a new entry */ - new_entry.start = new_entry.end = s; - return ir_value_life_insert(self, i, new_entry); -} - -static bool ir_value_life_merge_into(ir_value *self, const ir_value *other) -{ - size_t i, myi; - - if (!vec_size(other->life)) - return true; - - if (!vec_size(self->life)) { - size_t count = vec_size(other->life); - ir_life_entry_t *life = vec_add(self->life, count); - memcpy(life, other->life, count * sizeof(*life)); - return true; - } - - myi = 0; - for (i = 0; i < vec_size(other->life); ++i) - { - const ir_life_entry_t *life = &other->life[i]; - while (true) - { - ir_life_entry_t *entry = &self->life[myi]; - - if (life->end+1 < entry->start) - { - /* adding an interval before entry */ - if (!ir_value_life_insert(self, myi, *life)) - return false; - ++myi; - break; - } - - if (life->start < entry->start && - life->end+1 >= entry->start) - { - /* starts earlier and overlaps */ - entry->start = life->start; - } - - if (life->end > entry->end && - life->start <= entry->end+1) - { - /* ends later and overlaps */ - entry->end = life->end; - } - - /* see if our change combines it with the next ranges */ - while (myi+1 < vec_size(self->life) && - entry->end+1 >= self->life[1+myi].start) - { - /* overlaps with (myi+1) */ - if (entry->end < self->life[1+myi].end) - entry->end = self->life[1+myi].end; - vec_remove(self->life, myi+1, 1); - entry = &self->life[myi]; - } - - /* see if we're after the entry */ - if (life->start > entry->end) - { - ++myi; - /* append if we're at the end */ - if (myi >= vec_size(self->life)) { - vec_push(self->life, *life); - break; - } - /* otherweise check the next range */ - continue; - } - break; - } - } - return true; -} - -static bool ir_values_overlap(const ir_value *a, const ir_value *b) -{ - /* For any life entry in A see if it overlaps with - * any life entry in B. - * Note that the life entries are orderes, so we can make a - * more efficient algorithm there than naively translating the - * statement above. - */ - - ir_life_entry_t *la, *lb, *enda, *endb; - - /* first of all, if either has no life range, they cannot clash */ - if (!vec_size(a->life) || !vec_size(b->life)) - return false; - - la = a->life; - lb = b->life; - enda = la + vec_size(a->life); - endb = lb + vec_size(b->life); - while (true) - { - /* check if the entries overlap, for that, - * both must start before the other one ends. - */ - if (la->start < lb->end && - lb->start < la->end) - { - return true; - } - - /* entries are ordered - * one entry is earlier than the other - * that earlier entry will be moved forward - */ - if (la->start < lb->start) - { - /* order: A B, move A forward - * check if we hit the end with A - */ - if (++la == enda) - break; - } - else /* if (lb->start < la->start) actually <= */ - { - /* order: B A, move B forward - * check if we hit the end with B - */ - if (++lb == endb) - break; - } - } - return false; -} - -/*********************************************************************** - *IR main operations - */ - -static bool ir_check_unreachable(ir_block *self) -{ - /* The IR should never have to deal with unreachable code */ - if (!self->final/* || OPTS_FLAG(ALLOW_UNREACHABLE_CODE)*/) - return true; - irerror(self->context, "unreachable statement (%s)", self->label); - return false; -} - -bool ir_block_create_store_op(ir_block *self, lex_ctx_t ctx, int op, ir_value *target, ir_value *what) -{ - ir_instr *in; - if (!ir_check_unreachable(self)) - return false; - - if (target->store == store_value && - (op < INSTR_STOREP_F || op > INSTR_STOREP_FNC)) - { - irerror(self->context, "cannot store to an SSA value"); - irerror(self->context, "trying to store: %s <- %s", target->name, what->name); - irerror(self->context, "instruction: %s", util_instr_str[op]); - return false; - } - - in = ir_instr_new(ctx, self, op); - if (!in) - return false; - - if (!ir_instr_op(in, 0, target, (op < INSTR_STOREP_F || op > INSTR_STOREP_FNC)) || - !ir_instr_op(in, 1, what, false)) - { - ir_instr_delete(in); - return false; - } - vec_push(self->instr, in); - return true; -} - -bool ir_block_create_state_op(ir_block *self, lex_ctx_t ctx, ir_value *frame, ir_value *think) -{ - ir_instr *in; - if (!ir_check_unreachable(self)) - return false; - - in = ir_instr_new(ctx, self, INSTR_STATE); - if (!in) - return false; - - if (!ir_instr_op(in, 0, frame, false) || - !ir_instr_op(in, 1, think, false)) - { - ir_instr_delete(in); - return false; - } - vec_push(self->instr, in); - return true; -} - -static bool ir_block_create_store(ir_block *self, lex_ctx_t ctx, ir_value *target, ir_value *what) -{ - int op = 0; - int vtype; - if (target->vtype == TYPE_VARIANT) - vtype = what->vtype; - else - vtype = target->vtype; - -#if 0 - if (vtype == TYPE_FLOAT && what->vtype == TYPE_INTEGER) - op = INSTR_CONV_ITOF; - else if (vtype == TYPE_INTEGER && what->vtype == TYPE_FLOAT) - op = INSTR_CONV_FTOI; -#endif - op = type_store_instr[vtype]; - - if (OPTS_FLAG(ADJUST_VECTOR_FIELDS)) { - if (op == INSTR_STORE_FLD && what->fieldtype == TYPE_VECTOR) - op = INSTR_STORE_V; - } - - return ir_block_create_store_op(self, ctx, op, target, what); -} - -bool ir_block_create_storep(ir_block *self, lex_ctx_t ctx, ir_value *target, ir_value *what) -{ - int op = 0; - int vtype; - - if (target->vtype != TYPE_POINTER) - return false; - - /* storing using pointer - target is a pointer, type must be - * inferred from source - */ - vtype = what->vtype; - - op = type_storep_instr[vtype]; - if (OPTS_FLAG(ADJUST_VECTOR_FIELDS)) { - if (op == INSTR_STOREP_FLD && what->fieldtype == TYPE_VECTOR) - op = INSTR_STOREP_V; - } - - return ir_block_create_store_op(self, ctx, op, target, what); -} - -bool ir_block_create_return(ir_block *self, lex_ctx_t ctx, ir_value *v) -{ - ir_instr *in; - if (!ir_check_unreachable(self)) - return false; - - self->final = true; - - self->is_return = true; - in = ir_instr_new(ctx, self, INSTR_RETURN); - if (!in) - return false; - - if (v && !ir_instr_op(in, 0, v, false)) { - ir_instr_delete(in); - return false; - } - - vec_push(self->instr, in); - return true; -} - -bool ir_block_create_if(ir_block *self, lex_ctx_t ctx, ir_value *v, - ir_block *ontrue, ir_block *onfalse) -{ - ir_instr *in; - if (!ir_check_unreachable(self)) - return false; - self->final = true; - /*in = ir_instr_new(ctx, self, (v->vtype == TYPE_STRING ? INSTR_IF_S : INSTR_IF_F));*/ - in = ir_instr_new(ctx, self, VINSTR_COND); - if (!in) - return false; - - if (!ir_instr_op(in, 0, v, false)) { - ir_instr_delete(in); - return false; - } - - in->bops[0] = ontrue; - in->bops[1] = onfalse; - - vec_push(self->instr, in); - - vec_push(self->exits, ontrue); - vec_push(self->exits, onfalse); - vec_push(ontrue->entries, self); - vec_push(onfalse->entries, self); - return true; -} - -bool ir_block_create_jump(ir_block *self, lex_ctx_t ctx, ir_block *to) -{ - ir_instr *in; - if (!ir_check_unreachable(self)) - return false; - self->final = true; - in = ir_instr_new(ctx, self, VINSTR_JUMP); - if (!in) - return false; - - in->bops[0] = to; - vec_push(self->instr, in); - - vec_push(self->exits, to); - vec_push(to->entries, self); - return true; -} - -bool ir_block_create_goto(ir_block *self, lex_ctx_t ctx, ir_block *to) -{ - self->owner->flags |= IR_FLAG_HAS_GOTO; - return ir_block_create_jump(self, ctx, to); -} - -ir_instr* ir_block_create_phi(ir_block *self, lex_ctx_t ctx, const char *label, int ot) -{ - ir_value *out; - ir_instr *in; - if (!ir_check_unreachable(self)) - return NULL; - in = ir_instr_new(ctx, self, VINSTR_PHI); - if (!in) - return NULL; - out = ir_value_out(self->owner, label, store_value, ot); - if (!out) { - ir_instr_delete(in); - return NULL; - } - if (!ir_instr_op(in, 0, out, true)) { - ir_instr_delete(in); - ir_value_delete(out); - return NULL; - } - vec_push(self->instr, in); - return in; -} - -ir_value* ir_phi_value(ir_instr *self) -{ - return self->_ops[0]; -} - -void ir_phi_add(ir_instr* self, ir_block *b, ir_value *v) -{ - ir_phi_entry_t pe; - - if (!vec_ir_block_find(self->owner->entries, b, NULL)) { - /* Must not be possible to cause this, otherwise the AST - * is doing something wrong. - */ - irerror(self->context, "Invalid entry block for PHI"); - exit(EXIT_FAILURE); - } - - pe.value = v; - pe.from = b; - vec_push(v->reads, self); - vec_push(self->phi, pe); -} - -/* call related code */ -ir_instr* ir_block_create_call(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *func, bool noreturn) -{ - ir_value *out; - ir_instr *in; - if (!ir_check_unreachable(self)) - return NULL; - in = ir_instr_new(ctx, self, (noreturn ? VINSTR_NRCALL : INSTR_CALL0)); - if (!in) - return NULL; - if (noreturn) { - self->final = true; - self->is_return = true; - } - out = ir_value_out(self->owner, label, (func->outtype == TYPE_VOID) ? store_return : store_value, func->outtype); - if (!out) { - ir_instr_delete(in); - return NULL; - } - if (!ir_instr_op(in, 0, out, true) || - !ir_instr_op(in, 1, func, false)) - { - ir_instr_delete(in); - ir_value_delete(out); - return NULL; - } - vec_push(self->instr, in); - /* - if (noreturn) { - if (!ir_block_create_return(self, ctx, NULL)) { - compile_error(ctx, "internal error: failed to generate dummy-return instruction"); - ir_instr_delete(in); - return NULL; - } - } - */ - return in; -} - -ir_value* ir_call_value(ir_instr *self) -{ - return self->_ops[0]; -} - -void ir_call_param(ir_instr* self, ir_value *v) -{ - vec_push(self->params, v); - vec_push(v->reads, self); -} - -/* binary op related code */ - -ir_value* ir_block_create_binop(ir_block *self, lex_ctx_t ctx, - const char *label, int opcode, - ir_value *left, ir_value *right) -{ - int ot = TYPE_VOID; - switch (opcode) { - case INSTR_ADD_F: - case INSTR_SUB_F: - case INSTR_DIV_F: - case INSTR_MUL_F: - case INSTR_MUL_V: - case INSTR_AND: - case INSTR_OR: -#if 0 - case INSTR_AND_I: - case INSTR_AND_IF: - case INSTR_AND_FI: - case INSTR_OR_I: - case INSTR_OR_IF: - case INSTR_OR_FI: -#endif - case INSTR_BITAND: - case INSTR_BITOR: - case VINSTR_BITXOR: -#if 0 - case INSTR_SUB_S: /* -- offset of string as float */ - case INSTR_MUL_IF: - case INSTR_MUL_FI: - case INSTR_DIV_IF: - case INSTR_DIV_FI: - case INSTR_BITOR_IF: - case INSTR_BITOR_FI: - case INSTR_BITAND_FI: - case INSTR_BITAND_IF: - case INSTR_EQ_I: - case INSTR_NE_I: -#endif - ot = TYPE_FLOAT; - break; -#if 0 - case INSTR_ADD_I: - case INSTR_ADD_IF: - case INSTR_ADD_FI: - case INSTR_SUB_I: - case INSTR_SUB_FI: - case INSTR_SUB_IF: - case INSTR_MUL_I: - case INSTR_DIV_I: - case INSTR_BITAND_I: - case INSTR_BITOR_I: - case INSTR_XOR_I: - case INSTR_RSHIFT_I: - case INSTR_LSHIFT_I: - ot = TYPE_INTEGER; - break; -#endif - case INSTR_ADD_V: - case INSTR_SUB_V: - case INSTR_MUL_VF: - case INSTR_MUL_FV: - case VINSTR_BITAND_V: - case VINSTR_BITOR_V: - case VINSTR_BITXOR_V: - case VINSTR_BITAND_VF: - case VINSTR_BITOR_VF: - case VINSTR_BITXOR_VF: - case VINSTR_CROSS: -#if 0 - case INSTR_DIV_VF: - case INSTR_MUL_IV: - case INSTR_MUL_VI: -#endif - ot = TYPE_VECTOR; - break; -#if 0 - case INSTR_ADD_SF: - ot = TYPE_POINTER; - break; -#endif - /* - * after the following default case, the value of opcode can never - * be 1, 2, 3, 4, 5, 6, 7, 8, 9, 62, 63, 64, 65 - */ - default: - /* ranges: */ - /* boolean operations result in floats */ - - /* - * opcode >= 10 takes true branch opcode is at least 10 - * opcode <= 23 takes false branch opcode is at least 24 - */ - if (opcode >= INSTR_EQ_F && opcode <= INSTR_GT) - ot = TYPE_FLOAT; - - /* - * At condition "opcode <= 23", the value of "opcode" must be - * at least 24. - * At condition "opcode <= 23", the value of "opcode" cannot be - * equal to any of {1, 2, 3, 4, 5, 6, 7, 8, 9, 62, 63, 64, 65}. - * The condition "opcode <= 23" cannot be true. - * - * Thus ot=2 (TYPE_FLOAT) can never be true - */ -#if 0 - else if (opcode >= INSTR_LE && opcode <= INSTR_GT) - ot = TYPE_FLOAT; - else if (opcode >= INSTR_LE_I && opcode <= INSTR_EQ_FI) - ot = TYPE_FLOAT; -#endif - break; - }; - if (ot == TYPE_VOID) { - /* The AST or parser were supposed to check this! */ - return NULL; - } - - return ir_block_create_general_instr(self, ctx, label, opcode, left, right, ot); -} - -ir_value* ir_block_create_unary(ir_block *self, lex_ctx_t ctx, - const char *label, int opcode, - ir_value *operand) -{ - int ot = TYPE_FLOAT; - ir_value *minus_1 = NULL; - if (opcode == VINSTR_NEG_F || opcode == VINSTR_NEG_V) - minus_1 = ir_builder_imm_float(self->owner->owner, -1.0f, false); - switch (opcode) { - case INSTR_NOT_F: - case INSTR_NOT_V: - case INSTR_NOT_S: - case INSTR_NOT_ENT: - case INSTR_NOT_FNC: /* - case INSTR_NOT_I: */ - ot = TYPE_FLOAT; - break; - /* Negation is implemented as -1 * */ - case VINSTR_NEG_F: - return ir_block_create_general_instr(self, ctx, label, INSTR_MUL_F, minus_1, operand, TYPE_FLOAT); - case VINSTR_NEG_V: - return ir_block_create_general_instr(self, ctx, label, INSTR_MUL_FV, minus_1, operand, TYPE_VECTOR); - - default: - ot = operand->vtype; - break; - }; - if (ot == TYPE_VOID) { - /* The AST or parser were supposed to check this! */ - return NULL; - } - - /* let's use the general instruction creator and pass NULL for OPB */ - return ir_block_create_general_instr(self, ctx, label, opcode, operand, NULL, ot); -} - -static ir_value* ir_block_create_general_instr(ir_block *self, lex_ctx_t ctx, const char *label, - int op, ir_value *a, ir_value *b, int outype) -{ - ir_instr *instr; - ir_value *out; - - out = ir_value_out(self->owner, label, store_value, outype); - if (!out) - return NULL; - - instr = ir_instr_new(ctx, self, op); - if (!instr) { - ir_value_delete(out); - return NULL; - } - - if (!ir_instr_op(instr, 0, out, true) || - !ir_instr_op(instr, 1, a, false) || - !ir_instr_op(instr, 2, b, false) ) - { - goto on_error; - } - - vec_push(self->instr, instr); - - return out; -on_error: - ir_instr_delete(instr); - ir_value_delete(out); - return NULL; -} - -ir_value* ir_block_create_fieldaddress(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *ent, ir_value *field) -{ - ir_value *v; - - /* Support for various pointer types todo if so desired */ - if (ent->vtype != TYPE_ENTITY) - return NULL; - - if (field->vtype != TYPE_FIELD) - return NULL; - - v = ir_block_create_general_instr(self, ctx, label, INSTR_ADDRESS, ent, field, TYPE_POINTER); - v->fieldtype = field->fieldtype; - return v; -} - -ir_value* ir_block_create_load_from_ent(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *ent, ir_value *field, int outype) -{ - int op; - if (ent->vtype != TYPE_ENTITY) - return NULL; - - /* at some point we could redirect for TYPE_POINTER... but that could lead to carelessness */ - if (field->vtype != TYPE_FIELD) - return NULL; - - switch (outype) - { - case TYPE_FLOAT: op = INSTR_LOAD_F; break; - case TYPE_VECTOR: op = INSTR_LOAD_V; break; - case TYPE_STRING: op = INSTR_LOAD_S; break; - case TYPE_FIELD: op = INSTR_LOAD_FLD; break; - case TYPE_ENTITY: op = INSTR_LOAD_ENT; break; - case TYPE_FUNCTION: op = INSTR_LOAD_FNC; break; -#if 0 - case TYPE_POINTER: op = INSTR_LOAD_I; break; - case TYPE_INTEGER: op = INSTR_LOAD_I; break; -#endif - default: - irerror(self->context, "invalid type for ir_block_create_load_from_ent: %s", type_name[outype]); - return NULL; - } - - return ir_block_create_general_instr(self, ctx, label, op, ent, field, outype); -} - -/* PHI resolving breaks the SSA, and must thus be the last - * step before life-range calculation. - */ - -static bool ir_block_naive_phi(ir_block *self); -bool ir_function_naive_phi(ir_function *self) -{ - size_t i; - - for (i = 0; i < vec_size(self->blocks); ++i) - { - if (!ir_block_naive_phi(self->blocks[i])) - return false; - } - return true; -} - -static bool ir_block_naive_phi(ir_block *self) -{ - size_t i, p; /*, w;*/ - /* FIXME: optionally, create_phi can add the phis - * to a list so we don't need to loop through blocks - * - anyway: "don't optimize YET" - */ - for (i = 0; i < vec_size(self->instr); ++i) - { - ir_instr *instr = self->instr[i]; - if (instr->opcode != VINSTR_PHI) - continue; - - vec_remove(self->instr, i, 1); - --i; /* NOTE: i+1 below */ - - for (p = 0; p < vec_size(instr->phi); ++p) - { - ir_value *v = instr->phi[p].value; - ir_block *b = instr->phi[p].from; - - if (v->store == store_value && - vec_size(v->reads) == 1 && - vec_size(v->writes) == 1) - { - /* replace the value */ - if (!ir_instr_op(v->writes[0], 0, instr->_ops[0], true)) - return false; - } - else - { - /* force a move instruction */ - ir_instr *prevjump = vec_last(b->instr); - vec_pop(b->instr); - b->final = false; - instr->_ops[0]->store = store_global; - if (!ir_block_create_store(b, instr->context, instr->_ops[0], v)) - return false; - instr->_ops[0]->store = store_value; - vec_push(b->instr, prevjump); - b->final = true; - } - } - ir_instr_delete(instr); - } - return true; -} - -/*********************************************************************** - *IR Temp allocation code - * Propagating value life ranges by walking through the function backwards - * until no more changes are made. - * In theory this should happen once more than once for every nested loop - * level. - * Though this implementation might run an additional time for if nests. - */ - -/* Enumerate instructions used by value's life-ranges - */ -static void ir_block_enumerate(ir_block *self, size_t *_eid) -{ - size_t i; - size_t eid = *_eid; - for (i = 0; i < vec_size(self->instr); ++i) - { - self->instr[i]->eid = eid++; - } - *_eid = eid; -} - -/* Enumerate blocks and instructions. - * The block-enumeration is unordered! - * We do not really use the block enumreation, however - * the instruction enumeration is important for life-ranges. - */ -void ir_function_enumerate(ir_function *self) -{ - size_t i; - size_t instruction_id = 0; - for (i = 0; i < vec_size(self->blocks); ++i) - { - /* each block now gets an additional "entry" instruction id - * we can use to avoid point-life issues - */ - self->blocks[i]->entry_id = instruction_id; - ++instruction_id; - - self->blocks[i]->eid = i; - ir_block_enumerate(self->blocks[i], &instruction_id); - } -} - -/* Local-value allocator - * After finishing creating the liferange of all values used in a function - * we can allocate their global-positions. - * This is the counterpart to register-allocation in register machines. - */ -typedef struct { - ir_value **locals; - size_t *sizes; - size_t *positions; - bool *unique; -} function_allocator; - -static bool function_allocator_alloc(function_allocator *alloc, ir_value *var) -{ - ir_value *slot; - size_t vsize = ir_value_sizeof(var); - - var->code.local = vec_size(alloc->locals); - - slot = ir_value_var("reg", store_global, var->vtype); - if (!slot) - return false; - - if (!ir_value_life_merge_into(slot, var)) - goto localerror; - - vec_push(alloc->locals, slot); - vec_push(alloc->sizes, vsize); - vec_push(alloc->unique, var->unique_life); - - return true; - -localerror: - ir_value_delete(slot); - return false; -} - -static bool ir_function_allocator_assign(ir_function *self, function_allocator *alloc, ir_value *v) -{ - size_t a; - ir_value *slot; - - if (v->unique_life) - return function_allocator_alloc(alloc, v); - - for (a = 0; a < vec_size(alloc->locals); ++a) - { - /* if it's reserved for a unique liferange: skip */ - if (alloc->unique[a]) - continue; - - slot = alloc->locals[a]; - - /* never resize parameters - * will be required later when overlapping temps + locals - */ - if (a < vec_size(self->params) && - alloc->sizes[a] < ir_value_sizeof(v)) - { - continue; - } - - if (ir_values_overlap(v, slot)) - continue; - - if (!ir_value_life_merge_into(slot, v)) - return false; - - /* adjust size for this slot */ - if (alloc->sizes[a] < ir_value_sizeof(v)) - alloc->sizes[a] = ir_value_sizeof(v); - - v->code.local = a; - return true; - } - if (a >= vec_size(alloc->locals)) { - if (!function_allocator_alloc(alloc, v)) - return false; - } - return true; -} - -bool ir_function_allocate_locals(ir_function *self) -{ - size_t i; - bool retval = true; - size_t pos; - bool opt_gt = OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS); - - ir_value *v; - - function_allocator lockalloc, globalloc; - - if (!vec_size(self->locals) && !vec_size(self->values)) - return true; - - globalloc.locals = NULL; - globalloc.sizes = NULL; - globalloc.positions = NULL; - globalloc.unique = NULL; - lockalloc.locals = NULL; - lockalloc.sizes = NULL; - lockalloc.positions = NULL; - lockalloc.unique = NULL; - - for (i = 0; i < vec_size(self->locals); ++i) - { - v = self->locals[i]; - if ((self->flags & IR_FLAG_MASK_NO_LOCAL_TEMPS) || !OPTS_OPTIMIZATION(OPTIM_LOCAL_TEMPS)) { - v->locked = true; - v->unique_life = true; - } - else if (i >= vec_size(self->params)) - break; - else - v->locked = true; /* lock parameters locals */ - if (!function_allocator_alloc((v->locked || !opt_gt ? &lockalloc : &globalloc), v)) - goto error; - } - for (; i < vec_size(self->locals); ++i) - { - v = self->locals[i]; - if (!vec_size(v->life)) - continue; - if (!ir_function_allocator_assign(self, (v->locked || !opt_gt ? &lockalloc : &globalloc), v)) - goto error; - } - - /* Allocate a slot for any value that still exists */ - for (i = 0; i < vec_size(self->values); ++i) - { - v = self->values[i]; - - if (!vec_size(v->life)) - continue; - - /* CALL optimization: - * If the value is a parameter-temp: 1 write, 1 read from a CALL - * and it's not "locked", write it to the OFS_PARM directly. - */ - if (OPTS_OPTIMIZATION(OPTIM_CALL_STORES) && !v->locked && !v->unique_life) { - if (vec_size(v->reads) == 1 && vec_size(v->writes) == 1 && - (v->reads[0]->opcode == VINSTR_NRCALL || - (v->reads[0]->opcode >= INSTR_CALL0 && v->reads[0]->opcode <= INSTR_CALL8) - ) - ) - { - size_t param; - ir_instr *call = v->reads[0]; - if (!vec_ir_value_find(call->params, v, ¶m)) { - irerror(call->context, "internal error: unlocked parameter %s not found", v->name); - goto error; - } - ++opts_optimizationcount[OPTIM_CALL_STORES]; - v->callparam = true; - if (param < 8) - ir_value_code_setaddr(v, OFS_PARM0 + 3*param); - else { - size_t nprotos = vec_size(self->owner->extparam_protos); - ir_value *ep; - param -= 8; - if (nprotos > param) - ep = self->owner->extparam_protos[param]; - else - { - ep = ir_gen_extparam_proto(self->owner); - while (++nprotos <= param) - ep = ir_gen_extparam_proto(self->owner); - } - ir_instr_op(v->writes[0], 0, ep, true); - call->params[param+8] = ep; - } - continue; - } - if (vec_size(v->writes) == 1 && v->writes[0]->opcode == INSTR_CALL0) - { - v->store = store_return; - if (v->members[0]) v->members[0]->store = store_return; - if (v->members[1]) v->members[1]->store = store_return; - if (v->members[2]) v->members[2]->store = store_return; - ++opts_optimizationcount[OPTIM_CALL_STORES]; - continue; - } - } - - if (!ir_function_allocator_assign(self, (v->locked || !opt_gt ? &lockalloc : &globalloc), v)) - goto error; - } - - if (!lockalloc.sizes && !globalloc.sizes) { - goto cleanup; - } - vec_push(lockalloc.positions, 0); - vec_push(globalloc.positions, 0); - - /* Adjust slot positions based on sizes */ - if (lockalloc.sizes) { - pos = (vec_size(lockalloc.sizes) ? lockalloc.positions[0] : 0); - for (i = 1; i < vec_size(lockalloc.sizes); ++i) - { - pos = lockalloc.positions[i-1] + lockalloc.sizes[i-1]; - vec_push(lockalloc.positions, pos); - } - self->allocated_locals = pos + vec_last(lockalloc.sizes); - } - if (globalloc.sizes) { - pos = (vec_size(globalloc.sizes) ? globalloc.positions[0] : 0); - for (i = 1; i < vec_size(globalloc.sizes); ++i) - { - pos = globalloc.positions[i-1] + globalloc.sizes[i-1]; - vec_push(globalloc.positions, pos); - } - self->globaltemps = pos + vec_last(globalloc.sizes); - } - - /* Locals need to know their new position */ - for (i = 0; i < vec_size(self->locals); ++i) { - v = self->locals[i]; - if (v->locked || !opt_gt) - v->code.local = lockalloc.positions[v->code.local]; - else - v->code.local = globalloc.positions[v->code.local]; - } - /* Take over the actual slot positions on values */ - for (i = 0; i < vec_size(self->values); ++i) { - v = self->values[i]; - if (v->locked || !opt_gt) - v->code.local = lockalloc.positions[v->code.local]; - else - v->code.local = globalloc.positions[v->code.local]; - } - - goto cleanup; - -error: - retval = false; -cleanup: - for (i = 0; i < vec_size(lockalloc.locals); ++i) - ir_value_delete(lockalloc.locals[i]); - for (i = 0; i < vec_size(globalloc.locals); ++i) - ir_value_delete(globalloc.locals[i]); - vec_free(globalloc.unique); - vec_free(globalloc.locals); - vec_free(globalloc.sizes); - vec_free(globalloc.positions); - vec_free(lockalloc.unique); - vec_free(lockalloc.locals); - vec_free(lockalloc.sizes); - vec_free(lockalloc.positions); - return retval; -} - -/* Get information about which operand - * is read from, or written to. - */ -static void ir_op_read_write(int op, size_t *read, size_t *write) -{ - switch (op) - { - case VINSTR_JUMP: - case INSTR_GOTO: - *write = 0; - *read = 0; - break; - case INSTR_IF: - case INSTR_IFNOT: -#if 0 - case INSTR_IF_S: - case INSTR_IFNOT_S: -#endif - case INSTR_RETURN: - case VINSTR_COND: - *write = 0; - *read = 1; - break; - case INSTR_STOREP_F: - case INSTR_STOREP_V: - case INSTR_STOREP_S: - case INSTR_STOREP_ENT: - case INSTR_STOREP_FLD: - case INSTR_STOREP_FNC: - *write = 0; - *read = 7; - break; - default: - *write = 1; - *read = 6; - break; - }; -} - -static bool ir_block_living_add_instr(ir_block *self, size_t eid) -{ - size_t i; - const size_t vs = vec_size(self->living); - bool changed = false; - for (i = 0; i != vs; ++i) - { - if (ir_value_life_merge(self->living[i], eid)) - changed = true; - } - return changed; -} - -static bool ir_block_living_lock(ir_block *self) -{ - size_t i; - bool changed = false; - for (i = 0; i != vec_size(self->living); ++i) - { - if (!self->living[i]->locked) { - self->living[i]->locked = true; - changed = true; - } - } - return changed; -} - -static bool ir_block_life_propagate(ir_block *self, bool *changed) -{ - ir_instr *instr; - ir_value *value; - size_t i, o, p, mem, cnt; - /* bitmasks which operands are read from or written to */ - size_t read, write; - char dbg_ind[16]; - dbg_ind[0] = '#'; - dbg_ind[1] = '0'; - (void)dbg_ind; - - vec_free(self->living); - - p = vec_size(self->exits); - for (i = 0; i < p; ++i) { - ir_block *prev = self->exits[i]; - cnt = vec_size(prev->living); - for (o = 0; o < cnt; ++o) { - if (!vec_ir_value_find(self->living, prev->living[o], NULL)) - vec_push(self->living, prev->living[o]); - } - } - - i = vec_size(self->instr); - while (i) - { --i; - instr = self->instr[i]; - - /* See which operands are read and write operands */ - ir_op_read_write(instr->opcode, &read, &write); - - /* Go through the 3 main operands - * writes first, then reads - */ - for (o = 0; o < 3; ++o) - { - if (!instr->_ops[o]) /* no such operand */ - continue; - - value = instr->_ops[o]; - - /* We only care about locals */ - /* we also calculate parameter liferanges so that locals - * can take up parameter slots */ - if (value->store != store_value && - value->store != store_local && - value->store != store_param) - continue; - - /* write operands */ - /* When we write to a local, we consider it "dead" for the - * remaining upper part of the function, since in SSA a value - * can only be written once (== created) - */ - if (write & (1<living, value, &idx); - if (!in_living) - { - /* If the value isn't alive it hasn't been read before... */ - /* TODO: See if the warning can be emitted during parsing or AST processing - * otherwise have warning printed here. - * IF printing a warning here: include filecontext_t, - * and make sure it's only printed once - * since this function is run multiple times. - */ - /* con_err( "Value only written %s\n", value->name); */ - if (ir_value_life_merge(value, instr->eid)) - *changed = true; - } else { - /* since 'living' won't contain it - * anymore, merge the value, since - * (A) doesn't. - */ - if (ir_value_life_merge(value, instr->eid)) - *changed = true; - /* Then remove */ - vec_remove(self->living, idx, 1); - } - /* Removing a vector removes all members */ - for (mem = 0; mem < 3; ++mem) { - if (value->members[mem] && vec_ir_value_find(self->living, value->members[mem], &idx)) { - if (ir_value_life_merge(value->members[mem], instr->eid)) - *changed = true; - vec_remove(self->living, idx, 1); - } - } - /* Removing the last member removes the vector */ - if (value->memberof) { - value = value->memberof; - for (mem = 0; mem < 3; ++mem) { - if (value->members[mem] && vec_ir_value_find(self->living, value->members[mem], NULL)) - break; - } - if (mem == 3 && vec_ir_value_find(self->living, value, &idx)) { - if (ir_value_life_merge(value, instr->eid)) - *changed = true; - vec_remove(self->living, idx, 1); - } - } - } - } - - /* These operations need a special case as they can break when using - * same source and destination operand otherwise, as the engine may - * read the source multiple times. */ - if (instr->opcode == INSTR_MUL_VF || - instr->opcode == VINSTR_BITAND_VF || - instr->opcode == VINSTR_BITOR_VF || - instr->opcode == VINSTR_BITXOR || - instr->opcode == VINSTR_BITXOR_VF || - instr->opcode == VINSTR_BITXOR_V || - instr->opcode == VINSTR_CROSS) - { - value = instr->_ops[2]; - /* the float source will get an additional lifetime */ - if (ir_value_life_merge(value, instr->eid+1)) - *changed = true; - if (value->memberof && ir_value_life_merge(value->memberof, instr->eid+1)) - *changed = true; - } - - if (instr->opcode == INSTR_MUL_FV || - instr->opcode == INSTR_LOAD_V || - instr->opcode == VINSTR_BITXOR || - instr->opcode == VINSTR_BITXOR_VF || - instr->opcode == VINSTR_BITXOR_V || - instr->opcode == VINSTR_CROSS) - { - value = instr->_ops[1]; - /* the float source will get an additional lifetime */ - if (ir_value_life_merge(value, instr->eid+1)) - *changed = true; - if (value->memberof && ir_value_life_merge(value->memberof, instr->eid+1)) - *changed = true; - } - - for (o = 0; o < 3; ++o) - { - if (!instr->_ops[o]) /* no such operand */ - continue; - - value = instr->_ops[o]; - - /* We only care about locals */ - /* we also calculate parameter liferanges so that locals - * can take up parameter slots */ - if (value->store != store_value && - value->store != store_local && - value->store != store_param) - continue; - - /* read operands */ - if (read & (1<living, value, NULL)) - vec_push(self->living, value); - /* reading adds the full vector */ - if (value->memberof && !vec_ir_value_find(self->living, value->memberof, NULL)) - vec_push(self->living, value->memberof); - for (mem = 0; mem < 3; ++mem) { - if (value->members[mem] && !vec_ir_value_find(self->living, value->members[mem], NULL)) - vec_push(self->living, value->members[mem]); - } - } - } - /* PHI operands are always read operands */ - for (p = 0; p < vec_size(instr->phi); ++p) - { - value = instr->phi[p].value; - if (!vec_ir_value_find(self->living, value, NULL)) - vec_push(self->living, value); - /* reading adds the full vector */ - if (value->memberof && !vec_ir_value_find(self->living, value->memberof, NULL)) - vec_push(self->living, value->memberof); - for (mem = 0; mem < 3; ++mem) { - if (value->members[mem] && !vec_ir_value_find(self->living, value->members[mem], NULL)) - vec_push(self->living, value->members[mem]); - } - } - - /* on a call, all these values must be "locked" */ - if (instr->opcode >= INSTR_CALL0 && instr->opcode <= INSTR_CALL8) { - if (ir_block_living_lock(self)) - *changed = true; - } - /* call params are read operands too */ - for (p = 0; p < vec_size(instr->params); ++p) - { - value = instr->params[p]; - if (!vec_ir_value_find(self->living, value, NULL)) - vec_push(self->living, value); - /* reading adds the full vector */ - if (value->memberof && !vec_ir_value_find(self->living, value->memberof, NULL)) - vec_push(self->living, value->memberof); - for (mem = 0; mem < 3; ++mem) { - if (value->members[mem] && !vec_ir_value_find(self->living, value->members[mem], NULL)) - vec_push(self->living, value->members[mem]); - } - } - - /* (A) */ - if (ir_block_living_add_instr(self, instr->eid)) - *changed = true; - } - /* the "entry" instruction ID */ - if (ir_block_living_add_instr(self, self->entry_id)) - *changed = true; - - return true; -} - -bool ir_function_calculate_liferanges(ir_function *self) -{ - size_t i, s; - bool changed; - - /* parameters live at 0 */ - for (i = 0; i < vec_size(self->params); ++i) - if (!ir_value_life_merge(self->locals[i], 0)) - compile_error(self->context, "internal error: failed value-life merging"); - - do { - self->run_id++; - changed = false; - i = vec_size(self->blocks); - while (i--) { - ir_block_life_propagate(self->blocks[i], &changed); - } - } while (changed); - - if (vec_size(self->blocks)) { - ir_block *block = self->blocks[0]; - for (i = 0; i < vec_size(block->living); ++i) { - ir_value *v = block->living[i]; - if (v->store != store_local) - continue; - if (v->vtype == TYPE_VECTOR) - continue; - self->flags |= IR_FLAG_HAS_UNINITIALIZED; - /* find the instruction reading from it */ - for (s = 0; s < vec_size(v->reads); ++s) { - if (v->reads[s]->eid == v->life[0].end) - break; - } - if (s < vec_size(v->reads)) { - if (irwarning(v->context, WARN_USED_UNINITIALIZED, - "variable `%s` may be used uninitialized in this function\n" - " -> %s:%i", - v->name, - v->reads[s]->context.file, v->reads[s]->context.line) - ) - { - return false; - } - continue; - } - if (v->memberof) { - ir_value *vec = v->memberof; - for (s = 0; s < vec_size(vec->reads); ++s) { - if (vec->reads[s]->eid == v->life[0].end) - break; - } - if (s < vec_size(vec->reads)) { - if (irwarning(v->context, WARN_USED_UNINITIALIZED, - "variable `%s` may be used uninitialized in this function\n" - " -> %s:%i", - v->name, - vec->reads[s]->context.file, vec->reads[s]->context.line) - ) - { - return false; - } - continue; - } - } - if (irwarning(v->context, WARN_USED_UNINITIALIZED, - "variable `%s` may be used uninitialized in this function", v->name)) - { - return false; - } - } - } - return true; -} - -/*********************************************************************** - *IR Code-Generation - * - * Since the IR has the convention of putting 'write' operands - * at the beginning, we have to rotate the operands of instructions - * properly in order to generate valid QCVM code. - * - * Having destinations at a fixed position is more convenient. In QC - * this is *mostly* OPC, but FTE adds at least 2 instructions which - * read from from OPA, and store to OPB rather than OPC. Which is - * partially the reason why the implementation of these instructions - * in darkplaces has been delayed for so long. - * - * Breaking conventions is annoying... - */ -static bool ir_builder_gen_global(ir_builder *self, ir_value *global, bool islocal); - -static bool gen_global_field(code_t *code, ir_value *global) -{ - if (global->hasvalue) - { - ir_value *fld = global->constval.vpointer; - if (!fld) { - irerror(global->context, "Invalid field constant with no field: %s", global->name); - return false; - } - - /* copy the field's value */ - ir_value_code_setaddr(global, vec_size(code->globals)); - vec_push(code->globals, fld->code.fieldaddr); - if (global->fieldtype == TYPE_VECTOR) { - vec_push(code->globals, fld->code.fieldaddr+1); - vec_push(code->globals, fld->code.fieldaddr+2); - } - } - else - { - ir_value_code_setaddr(global, vec_size(code->globals)); - vec_push(code->globals, 0); - if (global->fieldtype == TYPE_VECTOR) { - vec_push(code->globals, 0); - vec_push(code->globals, 0); - } - } - if (global->code.globaladdr < 0) - return false; - return true; -} - -static bool gen_global_pointer(code_t *code, ir_value *global) -{ - if (global->hasvalue) - { - ir_value *target = global->constval.vpointer; - if (!target) { - irerror(global->context, "Invalid pointer constant: %s", global->name); - /* NULL pointers are pointing to the NULL constant, which also - * sits at address 0, but still has an ir_value for itself. - */ - return false; - } - - /* Here, relocations ARE possible - in fteqcc-enhanced-qc: - * void() foo; <- proto - * void() *fooptr = &foo; - * void() foo = { code } - */ - if (!target->code.globaladdr) { - /* FIXME: Check for the constant nullptr ir_value! - * because then code.globaladdr being 0 is valid. - */ - irerror(global->context, "FIXME: Relocation support"); - return false; - } - - ir_value_code_setaddr(global, vec_size(code->globals)); - vec_push(code->globals, target->code.globaladdr); - } - else - { - ir_value_code_setaddr(global, vec_size(code->globals)); - vec_push(code->globals, 0); - } - if (global->code.globaladdr < 0) - return false; - return true; -} - -static bool gen_blocks_recursive(code_t *code, ir_function *func, ir_block *block) -{ - prog_section_statement_t stmt; - ir_instr *instr; - ir_block *target; - ir_block *ontrue; - ir_block *onfalse; - size_t stidx; - size_t i; - int j; - - block->generated = true; - block->code_start = vec_size(code->statements); - for (i = 0; i < vec_size(block->instr); ++i) - { - instr = block->instr[i]; - - if (instr->opcode == VINSTR_PHI) { - irerror(block->context, "cannot generate virtual instruction (phi)"); - return false; - } - - if (instr->opcode == VINSTR_JUMP) { - target = instr->bops[0]; - /* for uncoditional jumps, if the target hasn't been generated - * yet, we generate them right here. - */ - if (!target->generated) - return gen_blocks_recursive(code, func, target); - - /* otherwise we generate a jump instruction */ - stmt.opcode = INSTR_GOTO; - stmt.o1.s1 = (target->code_start) - vec_size(code->statements); - stmt.o2.s1 = 0; - stmt.o3.s1 = 0; - if (stmt.o1.s1 != 1) - code_push_statement(code, &stmt, instr->context); - - /* no further instructions can be in this block */ - return true; - } - - if (instr->opcode == VINSTR_BITXOR) { - stmt.opcode = INSTR_BITOR; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); - code_push_statement(code, &stmt, instr->context); - stmt.opcode = INSTR_BITAND; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); - stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); - code_push_statement(code, &stmt, instr->context); - stmt.opcode = INSTR_SUB_F; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]); - stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); - code_push_statement(code, &stmt, instr->context); - - /* instruction generated */ - continue; - } - - if (instr->opcode == VINSTR_BITAND_V) { - stmt.opcode = INSTR_BITAND; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); - code_push_statement(code, &stmt, instr->context); - ++stmt.o1.s1; - ++stmt.o2.s1; - ++stmt.o3.s1; - code_push_statement(code, &stmt, instr->context); - ++stmt.o1.s1; - ++stmt.o2.s1; - ++stmt.o3.s1; - code_push_statement(code, &stmt, instr->context); - - /* instruction generated */ - continue; - } - - if (instr->opcode == VINSTR_BITOR_V) { - stmt.opcode = INSTR_BITOR; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); - code_push_statement(code, &stmt, instr->context); - ++stmt.o1.s1; - ++stmt.o2.s1; - ++stmt.o3.s1; - code_push_statement(code, &stmt, instr->context); - ++stmt.o1.s1; - ++stmt.o2.s1; - ++stmt.o3.s1; - code_push_statement(code, &stmt, instr->context); - - /* instruction generated */ - continue; - } - - if (instr->opcode == VINSTR_BITXOR_V) { - for (j = 0; j < 3; ++j) { - stmt.opcode = INSTR_BITOR; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j; - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + j; - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j; - code_push_statement(code, &stmt, instr->context); - stmt.opcode = INSTR_BITAND; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j; - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + j; - stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j; - code_push_statement(code, &stmt, instr->context); - } - stmt.opcode = INSTR_SUB_V; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]); - stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); - code_push_statement(code, &stmt, instr->context); - - /* instruction generated */ - continue; - } - - if (instr->opcode == VINSTR_BITAND_VF) { - stmt.opcode = INSTR_BITAND; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); - code_push_statement(code, &stmt, instr->context); - ++stmt.o1.s1; - ++stmt.o3.s1; - code_push_statement(code, &stmt, instr->context); - ++stmt.o1.s1; - ++stmt.o3.s1; - code_push_statement(code, &stmt, instr->context); - - /* instruction generated */ - continue; - } - - if (instr->opcode == VINSTR_BITOR_VF) { - stmt.opcode = INSTR_BITOR; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); - code_push_statement(code, &stmt, instr->context); - ++stmt.o1.s1; - ++stmt.o3.s1; - code_push_statement(code, &stmt, instr->context); - ++stmt.o1.s1; - ++stmt.o3.s1; - code_push_statement(code, &stmt, instr->context); - - /* instruction generated */ - continue; - } - - if (instr->opcode == VINSTR_BITXOR_VF) { - for (j = 0; j < 3; ++j) { - stmt.opcode = INSTR_BITOR; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j; - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j; - code_push_statement(code, &stmt, instr->context); - stmt.opcode = INSTR_BITAND; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j; - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); - stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j; - code_push_statement(code, &stmt, instr->context); - } - stmt.opcode = INSTR_SUB_V; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]); - stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); - code_push_statement(code, &stmt, instr->context); - - /* instruction generated */ - continue; - } - - if (instr->opcode == VINSTR_CROSS) { - stmt.opcode = INSTR_MUL_F; - for (j = 0; j < 3; ++j) { - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + (j + 1) % 3; - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + (j + 2) % 3; - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j; - code_push_statement(code, &stmt, instr->context); - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + (j + 2) % 3; - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + (j + 1) % 3; - stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j; - code_push_statement(code, &stmt, instr->context); - } - stmt.opcode = INSTR_SUB_V; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]); - stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); - code_push_statement(code, &stmt, instr->context); - - /* instruction generated */ - continue; - } - - if (instr->opcode == VINSTR_COND) { - ontrue = instr->bops[0]; - onfalse = instr->bops[1]; - /* TODO: have the AST signal which block should - * come first: eg. optimize IFs without ELSE... - */ - - stmt.o1.u1 = ir_value_code_addr(instr->_ops[0]); - stmt.o2.u1 = 0; - stmt.o3.s1 = 0; - - if (ontrue->generated) { - stmt.opcode = INSTR_IF; - stmt.o2.s1 = (ontrue->code_start) - vec_size(code->statements); - if (stmt.o2.s1 != 1) - code_push_statement(code, &stmt, instr->context); - } - if (onfalse->generated) { - stmt.opcode = INSTR_IFNOT; - stmt.o2.s1 = (onfalse->code_start) - vec_size(code->statements); - if (stmt.o2.s1 != 1) - code_push_statement(code, &stmt, instr->context); - } - if (!ontrue->generated) { - if (onfalse->generated) - return gen_blocks_recursive(code, func, ontrue); - } - if (!onfalse->generated) { - if (ontrue->generated) - return gen_blocks_recursive(code, func, onfalse); - } - /* neither ontrue nor onfalse exist */ - stmt.opcode = INSTR_IFNOT; - if (!instr->likely) { - /* Honor the likelyhood hint */ - ir_block *tmp = onfalse; - stmt.opcode = INSTR_IF; - onfalse = ontrue; - ontrue = tmp; - } - stidx = vec_size(code->statements); - code_push_statement(code, &stmt, instr->context); - /* on false we jump, so add ontrue-path */ - if (!gen_blocks_recursive(code, func, ontrue)) - return false; - /* fixup the jump address */ - code->statements[stidx].o2.s1 = vec_size(code->statements) - stidx; - /* generate onfalse path */ - if (onfalse->generated) { - /* fixup the jump address */ - code->statements[stidx].o2.s1 = (onfalse->code_start) - (stidx); - if (stidx+2 == vec_size(code->statements) && code->statements[stidx].o2.s1 == 1) { - code->statements[stidx] = code->statements[stidx+1]; - if (code->statements[stidx].o1.s1 < 0) - code->statements[stidx].o1.s1++; - code_pop_statement(code); - } - stmt.opcode = vec_last(code->statements).opcode; - if (stmt.opcode == INSTR_GOTO || - stmt.opcode == INSTR_IF || - stmt.opcode == INSTR_IFNOT || - stmt.opcode == INSTR_RETURN || - stmt.opcode == INSTR_DONE) - { - /* no use jumping from here */ - return true; - } - /* may have been generated in the previous recursive call */ - stmt.opcode = INSTR_GOTO; - stmt.o1.s1 = (onfalse->code_start) - vec_size(code->statements); - stmt.o2.s1 = 0; - stmt.o3.s1 = 0; - if (stmt.o1.s1 != 1) - code_push_statement(code, &stmt, instr->context); - return true; - } - else if (stidx+2 == vec_size(code->statements) && code->statements[stidx].o2.s1 == 1) { - code->statements[stidx] = code->statements[stidx+1]; - if (code->statements[stidx].o1.s1 < 0) - code->statements[stidx].o1.s1++; - code_pop_statement(code); - } - /* if not, generate now */ - return gen_blocks_recursive(code, func, onfalse); - } - - if ( (instr->opcode >= INSTR_CALL0 && instr->opcode <= INSTR_CALL8) - || instr->opcode == VINSTR_NRCALL) - { - size_t p, first; - ir_value *retvalue; - - first = vec_size(instr->params); - if (first > 8) - first = 8; - for (p = 0; p < first; ++p) - { - ir_value *param = instr->params[p]; - if (param->callparam) - continue; - - stmt.opcode = INSTR_STORE_F; - stmt.o3.u1 = 0; - - if (param->vtype == TYPE_FIELD) - stmt.opcode = field_store_instr[param->fieldtype]; - else if (param->vtype == TYPE_NIL) - stmt.opcode = INSTR_STORE_V; - else - stmt.opcode = type_store_instr[param->vtype]; - stmt.o1.u1 = ir_value_code_addr(param); - stmt.o2.u1 = OFS_PARM0 + 3 * p; - - if (param->vtype == TYPE_VECTOR && (param->flags & IR_FLAG_SPLIT_VECTOR)) { - /* fetch 3 separate floats */ - stmt.opcode = INSTR_STORE_F; - stmt.o1.u1 = ir_value_code_addr(param->members[0]); - code_push_statement(code, &stmt, instr->context); - stmt.o2.u1++; - stmt.o1.u1 = ir_value_code_addr(param->members[1]); - code_push_statement(code, &stmt, instr->context); - stmt.o2.u1++; - stmt.o1.u1 = ir_value_code_addr(param->members[2]); - code_push_statement(code, &stmt, instr->context); - } - else - code_push_statement(code, &stmt, instr->context); - } - /* Now handle extparams */ - first = vec_size(instr->params); - for (; p < first; ++p) - { - ir_builder *ir = func->owner; - ir_value *param = instr->params[p]; - ir_value *targetparam; - - if (param->callparam) - continue; - - if (p-8 >= vec_size(ir->extparams)) - ir_gen_extparam(ir); - - targetparam = ir->extparams[p-8]; - - stmt.opcode = INSTR_STORE_F; - stmt.o3.u1 = 0; - - if (param->vtype == TYPE_FIELD) - stmt.opcode = field_store_instr[param->fieldtype]; - else if (param->vtype == TYPE_NIL) - stmt.opcode = INSTR_STORE_V; - else - stmt.opcode = type_store_instr[param->vtype]; - stmt.o1.u1 = ir_value_code_addr(param); - stmt.o2.u1 = ir_value_code_addr(targetparam); - if (param->vtype == TYPE_VECTOR && (param->flags & IR_FLAG_SPLIT_VECTOR)) { - /* fetch 3 separate floats */ - stmt.opcode = INSTR_STORE_F; - stmt.o1.u1 = ir_value_code_addr(param->members[0]); - code_push_statement(code, &stmt, instr->context); - stmt.o2.u1++; - stmt.o1.u1 = ir_value_code_addr(param->members[1]); - code_push_statement(code, &stmt, instr->context); - stmt.o2.u1++; - stmt.o1.u1 = ir_value_code_addr(param->members[2]); - code_push_statement(code, &stmt, instr->context); - } - else - code_push_statement(code, &stmt, instr->context); - } - - stmt.opcode = INSTR_CALL0 + vec_size(instr->params); - if (stmt.opcode > INSTR_CALL8) - stmt.opcode = INSTR_CALL8; - stmt.o1.u1 = ir_value_code_addr(instr->_ops[1]); - stmt.o2.u1 = 0; - stmt.o3.u1 = 0; - code_push_statement(code, &stmt, instr->context); - - retvalue = instr->_ops[0]; - if (retvalue && retvalue->store != store_return && - (retvalue->store == store_global || vec_size(retvalue->life))) - { - /* not to be kept in OFS_RETURN */ - if (retvalue->vtype == TYPE_FIELD && OPTS_FLAG(ADJUST_VECTOR_FIELDS)) - stmt.opcode = field_store_instr[retvalue->fieldtype]; - else - stmt.opcode = type_store_instr[retvalue->vtype]; - stmt.o1.u1 = OFS_RETURN; - stmt.o2.u1 = ir_value_code_addr(retvalue); - stmt.o3.u1 = 0; - code_push_statement(code, &stmt, instr->context); - } - continue; - } - - if (instr->opcode == INSTR_STATE) { - stmt.opcode = instr->opcode; - if (instr->_ops[0]) - stmt.o1.u1 = ir_value_code_addr(instr->_ops[0]); - if (instr->_ops[1]) - stmt.o2.u1 = ir_value_code_addr(instr->_ops[1]); - stmt.o3.u1 = 0; - code_push_statement(code, &stmt, instr->context); - continue; - } - - stmt.opcode = instr->opcode; - stmt.o1.u1 = 0; - stmt.o2.u1 = 0; - stmt.o3.u1 = 0; - - /* This is the general order of operands */ - if (instr->_ops[0]) - stmt.o3.u1 = ir_value_code_addr(instr->_ops[0]); - - if (instr->_ops[1]) - stmt.o1.u1 = ir_value_code_addr(instr->_ops[1]); - - if (instr->_ops[2]) - stmt.o2.u1 = ir_value_code_addr(instr->_ops[2]); - - if (stmt.opcode == INSTR_RETURN || stmt.opcode == INSTR_DONE) - { - stmt.o1.u1 = stmt.o3.u1; - stmt.o3.u1 = 0; - } - else if ((stmt.opcode >= INSTR_STORE_F && - stmt.opcode <= INSTR_STORE_FNC) || - (stmt.opcode >= INSTR_STOREP_F && - stmt.opcode <= INSTR_STOREP_FNC)) - { - /* 2-operand instructions with A -> B */ - stmt.o2.u1 = stmt.o3.u1; - stmt.o3.u1 = 0; - - /* tiny optimization, don't output - * STORE a, a - */ - if (stmt.o2.u1 == stmt.o1.u1 && - OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) - { - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - continue; - } - } - code_push_statement(code, &stmt, instr->context); - } - return true; -} - -static bool gen_function_code(code_t *code, ir_function *self) -{ - ir_block *block; - prog_section_statement_t stmt, *retst; - - /* Starting from entry point, we generate blocks "as they come" - * for now. Dead blocks will not be translated obviously. - */ - if (!vec_size(self->blocks)) { - irerror(self->context, "Function '%s' declared without body.", self->name); - return false; - } - - block = self->blocks[0]; - if (block->generated) - return true; - - if (!gen_blocks_recursive(code, self, block)) { - irerror(self->context, "failed to generate blocks for '%s'", self->name); - return false; - } - - /* code_write and qcvm -disasm need to know that the function ends here */ - retst = &vec_last(code->statements); - if (OPTS_OPTIMIZATION(OPTIM_VOID_RETURN) && - self->outtype == TYPE_VOID && - retst->opcode == INSTR_RETURN && - !retst->o1.u1 && !retst->o2.u1 && !retst->o3.u1) - { - retst->opcode = INSTR_DONE; - ++opts_optimizationcount[OPTIM_VOID_RETURN]; - } else { - lex_ctx_t last; - - stmt.opcode = INSTR_DONE; - stmt.o1.u1 = 0; - stmt.o2.u1 = 0; - stmt.o3.u1 = 0; - last.line = vec_last(code->linenums); - last.column = vec_last(code->columnnums); - - code_push_statement(code, &stmt, last); - } - return true; -} - -static qcint_t ir_builder_filestring(ir_builder *ir, const char *filename) -{ - /* NOTE: filename pointers are copied, we never strdup them, - * thus we can use pointer-comparison to find the string. - */ - size_t i; - qcint_t str; - - for (i = 0; i < vec_size(ir->filenames); ++i) { - if (ir->filenames[i] == filename) - return ir->filestrings[i]; - } - - str = code_genstring(ir->code, filename); - vec_push(ir->filenames, filename); - vec_push(ir->filestrings, str); - return str; -} - -static bool gen_global_function(ir_builder *ir, ir_value *global) -{ - prog_section_function_t fun; - ir_function *irfun; - - size_t i; - - if (!global->hasvalue || (!global->constval.vfunc)) - { - irerror(global->context, "Invalid state of function-global: not constant: %s", global->name); - return false; - } - - irfun = global->constval.vfunc; - - fun.name = global->code.name; - fun.file = ir_builder_filestring(ir, global->context.file); - fun.profile = 0; /* always 0 */ - fun.nargs = vec_size(irfun->params); - if (fun.nargs > 8) - fun.nargs = 8; - - for (i = 0;i < 8; ++i) { - if ((int32_t)i >= fun.nargs) - fun.argsize[i] = 0; - else - fun.argsize[i] = type_sizeof_[irfun->params[i]]; - } - - fun.firstlocal = 0; - fun.locals = irfun->allocated_locals; - - if (irfun->builtin) - fun.entry = irfun->builtin+1; - else { - irfun->code_function_def = vec_size(ir->code->functions); - fun.entry = vec_size(ir->code->statements); - } - - vec_push(ir->code->functions, fun); - return true; -} - -static ir_value* ir_gen_extparam_proto(ir_builder *ir) -{ - ir_value *global; - char name[128]; - - util_snprintf(name, sizeof(name), "EXTPARM#%i", (int)(vec_size(ir->extparam_protos))); - global = ir_value_var(name, store_global, TYPE_VECTOR); - - vec_push(ir->extparam_protos, global); - return global; -} - -static void ir_gen_extparam(ir_builder *ir) -{ - prog_section_def_t def; - ir_value *global; - - if (vec_size(ir->extparam_protos) < vec_size(ir->extparams)+1) - global = ir_gen_extparam_proto(ir); - else - global = ir->extparam_protos[vec_size(ir->extparams)]; - - def.name = code_genstring(ir->code, global->name); - def.type = TYPE_VECTOR; - def.offset = vec_size(ir->code->globals); - - vec_push(ir->code->defs, def); - - ir_value_code_setaddr(global, def.offset); - - vec_push(ir->code->globals, 0); - vec_push(ir->code->globals, 0); - vec_push(ir->code->globals, 0); - - vec_push(ir->extparams, global); -} - -static bool gen_function_extparam_copy(code_t *code, ir_function *self) -{ - size_t i, ext, numparams; - - ir_builder *ir = self->owner; - ir_value *ep; - prog_section_statement_t stmt; - - numparams = vec_size(self->params); - if (!numparams) - return true; - - stmt.opcode = INSTR_STORE_F; - stmt.o3.s1 = 0; - for (i = 8; i < numparams; ++i) { - ext = i - 8; - if (ext >= vec_size(ir->extparams)) - ir_gen_extparam(ir); - - ep = ir->extparams[ext]; - - stmt.opcode = type_store_instr[self->locals[i]->vtype]; - if (self->locals[i]->vtype == TYPE_FIELD && - self->locals[i]->fieldtype == TYPE_VECTOR) - { - stmt.opcode = INSTR_STORE_V; - } - stmt.o1.u1 = ir_value_code_addr(ep); - stmt.o2.u1 = ir_value_code_addr(self->locals[i]); - code_push_statement(code, &stmt, self->context); - } - - return true; -} - -static bool gen_function_varargs_copy(code_t *code, ir_function *self) -{ - size_t i, ext, numparams, maxparams; - - ir_builder *ir = self->owner; - ir_value *ep; - prog_section_statement_t stmt; - - numparams = vec_size(self->params); - if (!numparams) - return true; - - stmt.opcode = INSTR_STORE_V; - stmt.o3.s1 = 0; - maxparams = numparams + self->max_varargs; - for (i = numparams; i < maxparams; ++i) { - if (i < 8) { - stmt.o1.u1 = OFS_PARM0 + 3*i; - stmt.o2.u1 = ir_value_code_addr(self->locals[i]); - code_push_statement(code, &stmt, self->context); - continue; - } - ext = i - 8; - while (ext >= vec_size(ir->extparams)) - ir_gen_extparam(ir); - - ep = ir->extparams[ext]; - - stmt.o1.u1 = ir_value_code_addr(ep); - stmt.o2.u1 = ir_value_code_addr(self->locals[i]); - code_push_statement(code, &stmt, self->context); - } - - return true; -} - -static bool gen_function_locals(ir_builder *ir, ir_value *global) -{ - prog_section_function_t *def; - ir_function *irfun; - size_t i; - uint32_t firstlocal, firstglobal; - - irfun = global->constval.vfunc; - def = ir->code->functions + irfun->code_function_def; - - if (OPTS_OPTION_BOOL(OPTION_G) || - !OPTS_OPTIMIZATION(OPTIM_OVERLAP_LOCALS) || - (irfun->flags & IR_FLAG_MASK_NO_OVERLAP)) - { - firstlocal = def->firstlocal = vec_size(ir->code->globals); - } else { - firstlocal = def->firstlocal = ir->first_common_local; - ++opts_optimizationcount[OPTIM_OVERLAP_LOCALS]; - } - - firstglobal = (OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS) ? ir->first_common_globaltemp : firstlocal); - - for (i = vec_size(ir->code->globals); i < firstlocal + irfun->allocated_locals; ++i) - vec_push(ir->code->globals, 0); - for (i = 0; i < vec_size(irfun->locals); ++i) { - ir_value *v = irfun->locals[i]; - if (v->locked || !OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS)) { - ir_value_code_setaddr(v, firstlocal + v->code.local); - if (!ir_builder_gen_global(ir, irfun->locals[i], true)) { - irerror(irfun->locals[i]->context, "failed to generate local %s", irfun->locals[i]->name); - return false; - } - } - else - ir_value_code_setaddr(v, firstglobal + v->code.local); - } - for (i = 0; i < vec_size(irfun->values); ++i) - { - ir_value *v = irfun->values[i]; - if (v->callparam) - continue; - if (v->locked) - ir_value_code_setaddr(v, firstlocal + v->code.local); - else - ir_value_code_setaddr(v, firstglobal + v->code.local); - } - return true; -} - -static bool gen_global_function_code(ir_builder *ir, ir_value *global) -{ - prog_section_function_t *fundef; - ir_function *irfun; - - (void)ir; - - irfun = global->constval.vfunc; - if (!irfun) { - if (global->cvq == CV_NONE) { - if (irwarning(global->context, WARN_IMPLICIT_FUNCTION_POINTER, - "function `%s` has no body and in QC implicitly becomes a function-pointer", - global->name)) - { - /* Not bailing out just now. If this happens a lot you don't want to have - * to rerun gmqcc for each such function. - */ - - /* return false; */ - } - } - /* this was a function pointer, don't generate code for those */ - return true; - } - - if (irfun->builtin) - return true; - - /* - * If there is no definition and the thing is eraseable, we can ignore - * outputting the function to begin with. - */ - if (global->flags & IR_FLAG_ERASABLE && irfun->code_function_def < 0) { - return true; - } - - if (irfun->code_function_def < 0) { - irerror(irfun->context, "`%s`: IR global wasn't generated, failed to access function-def", irfun->name); - return false; - } - fundef = &ir->code->functions[irfun->code_function_def]; - - fundef->entry = vec_size(ir->code->statements); - if (!gen_function_locals(ir, global)) { - irerror(irfun->context, "Failed to generate locals for function %s", irfun->name); - return false; - } - if (!gen_function_extparam_copy(ir->code, irfun)) { - irerror(irfun->context, "Failed to generate extparam-copy code for function %s", irfun->name); - return false; - } - if (irfun->max_varargs && !gen_function_varargs_copy(ir->code, irfun)) { - irerror(irfun->context, "Failed to generate vararg-copy code for function %s", irfun->name); - return false; - } - if (!gen_function_code(ir->code, irfun)) { - irerror(irfun->context, "Failed to generate code for function %s", irfun->name); - return false; - } - return true; -} - -static void gen_vector_defs(code_t *code, prog_section_def_t def, const char *name) -{ - char *component; - size_t len, i; - - if (!name || name[0] == '#' || OPTS_FLAG(SINGLE_VECTOR_DEFS)) - return; - - def.type = TYPE_FLOAT; - - len = strlen(name); - - component = (char*)mem_a(len+3); - memcpy(component, name, len); - len += 2; - component[len-0] = 0; - component[len-2] = '_'; - - component[len-1] = 'x'; - - for (i = 0; i < 3; ++i) { - def.name = code_genstring(code, component); - vec_push(code->defs, def); - def.offset++; - component[len-1]++; - } - - mem_d(component); -} - -static void gen_vector_fields(code_t *code, prog_section_field_t fld, const char *name) -{ - char *component; - size_t len, i; - - if (!name || OPTS_FLAG(SINGLE_VECTOR_DEFS)) - return; - - fld.type = TYPE_FLOAT; - - len = strlen(name); - - component = (char*)mem_a(len+3); - memcpy(component, name, len); - len += 2; - component[len-0] = 0; - component[len-2] = '_'; - - component[len-1] = 'x'; - - for (i = 0; i < 3; ++i) { - fld.name = code_genstring(code, component); - vec_push(code->fields, fld); - fld.offset++; - component[len-1]++; - } - - mem_d(component); -} - -static bool ir_builder_gen_global(ir_builder *self, ir_value *global, bool islocal) -{ - size_t i; - int32_t *iptr; - prog_section_def_t def; - bool pushdef = opts.optimizeoff; - - /* we don't generate split-vectors */ - if (global->vtype == TYPE_VECTOR && (global->flags & IR_FLAG_SPLIT_VECTOR)) - return true; - - def.type = global->vtype; - def.offset = vec_size(self->code->globals); - def.name = 0; - if (OPTS_OPTION_BOOL(OPTION_G) || !islocal) - { - pushdef = true; - - /* - * if we're eraseable and the function isn't referenced ignore outputting - * the function. - */ - if (global->flags & IR_FLAG_ERASABLE && vec_size(global->reads) == 0) { - return true; - } - - if (OPTS_OPTIMIZATION(OPTIM_STRIP_CONSTANT_NAMES) && - !(global->flags & IR_FLAG_INCLUDE_DEF) && - (global->name[0] == '#' || global->cvq == CV_CONST)) - { - pushdef = false; - } - - if (pushdef) { - if (global->name[0] == '#') { - if (!self->str_immediate) - self->str_immediate = code_genstring(self->code, "IMMEDIATE"); - def.name = global->code.name = self->str_immediate; - } - else - def.name = global->code.name = code_genstring(self->code, global->name); - } - else - def.name = 0; - if (islocal) { - def.offset = ir_value_code_addr(global); - vec_push(self->code->defs, def); - if (global->vtype == TYPE_VECTOR) - gen_vector_defs(self->code, def, global->name); - else if (global->vtype == TYPE_FIELD && global->fieldtype == TYPE_VECTOR) - gen_vector_defs(self->code, def, global->name); - return true; - } - } - if (islocal) - return true; - - switch (global->vtype) - { - case TYPE_VOID: - if (!strcmp(global->name, "end_sys_globals")) { - /* TODO: remember this point... all the defs before this one - * should be checksummed and added to progdefs.h when we generate it. - */ - } - else if (!strcmp(global->name, "end_sys_fields")) { - /* TODO: same as above but for entity-fields rather than globsl - */ - } - else if(irwarning(global->context, WARN_VOID_VARIABLES, "unrecognized variable of type void `%s`", - global->name)) - { - /* Not bailing out */ - /* return false; */ - } - /* I'd argue setting it to 0 is sufficient, but maybe some depend on knowing how far - * the system fields actually go? Though the engine knows this anyway... - * Maybe this could be an -foption - * fteqcc creates data for end_sys_* - of size 1, so let's do the same - */ - ir_value_code_setaddr(global, vec_size(self->code->globals)); - vec_push(self->code->globals, 0); - /* Add the def */ - if (pushdef) vec_push(self->code->defs, def); - return true; - case TYPE_POINTER: - if (pushdef) vec_push(self->code->defs, def); - return gen_global_pointer(self->code, global); - case TYPE_FIELD: - if (pushdef) { - vec_push(self->code->defs, def); - if (global->fieldtype == TYPE_VECTOR) - gen_vector_defs(self->code, def, global->name); - } - return gen_global_field(self->code, global); - case TYPE_ENTITY: - /* fall through */ - case TYPE_FLOAT: - { - ir_value_code_setaddr(global, vec_size(self->code->globals)); - if (global->hasvalue) { - if (global->cvq == CV_CONST && !vec_size(global->reads)) - return true; - iptr = (int32_t*)&global->constval.ivec[0]; - vec_push(self->code->globals, *iptr); - } else { - vec_push(self->code->globals, 0); - } - if (!islocal && global->cvq != CV_CONST) - def.type |= DEF_SAVEGLOBAL; - if (pushdef) vec_push(self->code->defs, def); - - return global->code.globaladdr >= 0; - } - case TYPE_STRING: - { - ir_value_code_setaddr(global, vec_size(self->code->globals)); - if (global->hasvalue) { - uint32_t load; - if (global->cvq == CV_CONST && !vec_size(global->reads)) - return true; - load = code_genstring(self->code, global->constval.vstring); - vec_push(self->code->globals, load); - } else { - vec_push(self->code->globals, 0); - } - if (!islocal && global->cvq != CV_CONST) - def.type |= DEF_SAVEGLOBAL; - if (pushdef) vec_push(self->code->defs, def); - return global->code.globaladdr >= 0; - } - case TYPE_VECTOR: - { - size_t d; - ir_value_code_setaddr(global, vec_size(self->code->globals)); - if (global->hasvalue) { - iptr = (int32_t*)&global->constval.ivec[0]; - vec_push(self->code->globals, iptr[0]); - if (global->code.globaladdr < 0) - return false; - for (d = 1; d < type_sizeof_[global->vtype]; ++d) { - vec_push(self->code->globals, iptr[d]); - } - } else { - vec_push(self->code->globals, 0); - if (global->code.globaladdr < 0) - return false; - for (d = 1; d < type_sizeof_[global->vtype]; ++d) { - vec_push(self->code->globals, 0); - } - } - if (!islocal && global->cvq != CV_CONST) - def.type |= DEF_SAVEGLOBAL; - - if (pushdef) { - vec_push(self->code->defs, def); - def.type &= ~DEF_SAVEGLOBAL; - gen_vector_defs(self->code, def, global->name); - } - return global->code.globaladdr >= 0; - } - case TYPE_FUNCTION: - ir_value_code_setaddr(global, vec_size(self->code->globals)); - if (!global->hasvalue) { - vec_push(self->code->globals, 0); - if (global->code.globaladdr < 0) - return false; - } else { - vec_push(self->code->globals, vec_size(self->code->functions)); - if (!gen_global_function(self, global)) - return false; - } - if (!islocal && global->cvq != CV_CONST) - def.type |= DEF_SAVEGLOBAL; - if (pushdef) vec_push(self->code->defs, def); - return true; - case TYPE_VARIANT: - /* assume biggest type */ - ir_value_code_setaddr(global, vec_size(self->code->globals)); - vec_push(self->code->globals, 0); - for (i = 1; i < type_sizeof_[TYPE_VARIANT]; ++i) - vec_push(self->code->globals, 0); - return true; - default: - /* refuse to create 'void' type or any other fancy business. */ - irerror(global->context, "Invalid type for global variable `%s`: %s", - global->name, type_name[global->vtype]); - return false; - } -} - -static GMQCC_INLINE void ir_builder_prepare_field(code_t *code, ir_value *field) -{ - field->code.fieldaddr = code_alloc_field(code, type_sizeof_[field->fieldtype]); -} - -static bool ir_builder_gen_field(ir_builder *self, ir_value *field) -{ - prog_section_def_t def; - prog_section_field_t fld; - - (void)self; - - def.type = (uint16_t)field->vtype; - def.offset = (uint16_t)vec_size(self->code->globals); - - /* create a global named the same as the field */ - if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) { - /* in our standard, the global gets a dot prefix */ - size_t len = strlen(field->name); - char name[1024]; - - /* we really don't want to have to allocate this, and 1024 - * bytes is more than enough for a variable/field name - */ - if (len+2 >= sizeof(name)) { - irerror(field->context, "invalid field name size: %u", (unsigned int)len); - return false; - } - - name[0] = '.'; - memcpy(name+1, field->name, len); /* no strncpy - we used strlen above */ - name[len+1] = 0; - - def.name = code_genstring(self->code, name); - fld.name = def.name + 1; /* we reuse that string table entry */ - } else { - /* in plain QC, there cannot be a global with the same name, - * and so we also name the global the same. - * FIXME: fteqcc should create a global as well - * check if it actually uses the same name. Probably does - */ - def.name = code_genstring(self->code, field->name); - fld.name = def.name; - } - - field->code.name = def.name; - - vec_push(self->code->defs, def); - - fld.type = field->fieldtype; - - if (fld.type == TYPE_VOID) { - irerror(field->context, "field is missing a type: %s - don't know its size", field->name); - return false; - } - - fld.offset = field->code.fieldaddr; - - vec_push(self->code->fields, fld); - - ir_value_code_setaddr(field, vec_size(self->code->globals)); - vec_push(self->code->globals, fld.offset); - if (fld.type == TYPE_VECTOR) { - vec_push(self->code->globals, fld.offset+1); - vec_push(self->code->globals, fld.offset+2); - } - - if (field->fieldtype == TYPE_VECTOR) { - gen_vector_defs (self->code, def, field->name); - gen_vector_fields(self->code, fld, field->name); - } - - return field->code.globaladdr >= 0; -} - -static void ir_builder_collect_reusables(ir_builder *builder) { - size_t i; - ir_value **reusables = NULL; - for (i = 0; i < vec_size(builder->globals); ++i) { - ir_value *value = builder->globals[i]; - if (value->vtype != TYPE_FLOAT || !value->hasvalue) - continue; - if (value->cvq == CV_CONST || (value->name && value->name[0] == '#')) { - vec_push(reusables, value); - } - } - builder->const_floats = reusables; -} - -static void ir_builder_split_vector(ir_builder *self, ir_value *vec) { - size_t i, count; - ir_value* found[3] = { NULL, NULL, NULL }; - - /* must not be written to */ - if (vec_size(vec->writes)) - return; - /* must not be trying to access individual members */ - if (vec->members[0] || vec->members[1] || vec->members[2]) - return; - /* should be actually used otherwise it won't be generated anyway */ - count = vec_size(vec->reads); - if (!count) - return; - - /* may only be used directly as function parameters, so if we find some other instruction cancel */ - for (i = 0; i != count; ++i) { - /* we only split vectors if they're used directly as parameter to a call only! */ - ir_instr *user = vec->reads[i]; - if ((user->opcode < INSTR_CALL0 || user->opcode > INSTR_CALL8) && user->opcode != VINSTR_NRCALL) - return; - } - - vec->flags |= IR_FLAG_SPLIT_VECTOR; - - /* find existing floats making up the split */ - count = vec_size(self->const_floats); - for (i = 0; i != count; ++i) { - ir_value *c = self->const_floats[i]; - if (!found[0] && c->constval.vfloat == vec->constval.vvec.x) - found[0] = c; - if (!found[1] && c->constval.vfloat == vec->constval.vvec.y) - found[1] = c; - if (!found[2] && c->constval.vfloat == vec->constval.vvec.z) - found[2] = c; - if (found[0] && found[1] && found[2]) - break; - } - - /* generate floats for not yet found components */ - if (!found[0]) - found[0] = ir_builder_imm_float(self, vec->constval.vvec.x, true); - if (!found[1]) { - if (vec->constval.vvec.y == vec->constval.vvec.x) - found[1] = found[0]; - else - found[1] = ir_builder_imm_float(self, vec->constval.vvec.y, true); - } - if (!found[2]) { - if (vec->constval.vvec.z == vec->constval.vvec.x) - found[2] = found[0]; - else if (vec->constval.vvec.z == vec->constval.vvec.y) - found[2] = found[1]; - else - found[2] = ir_builder_imm_float(self, vec->constval.vvec.z, true); - } - - /* the .members array should be safe to use here. */ - vec->members[0] = found[0]; - vec->members[1] = found[1]; - vec->members[2] = found[2]; - - /* register the readers for these floats */ - count = vec_size(vec->reads); - for (i = 0; i != count; ++i) { - vec_push(found[0]->reads, vec->reads[i]); - vec_push(found[1]->reads, vec->reads[i]); - vec_push(found[2]->reads, vec->reads[i]); - } -} - -static void ir_builder_split_vectors(ir_builder *self) { - size_t i, count = vec_size(self->globals); - for (i = 0; i != count; ++i) { - ir_value *v = self->globals[i]; - if (v->vtype != TYPE_VECTOR || !v->name || v->name[0] != '#') - continue; - ir_builder_split_vector(self, self->globals[i]); - } -} - -bool ir_builder_generate(ir_builder *self, const char *filename) -{ - prog_section_statement_t stmt; - size_t i; - char *lnofile = NULL; - - if (OPTS_FLAG(SPLIT_VECTOR_PARAMETERS)) { - ir_builder_collect_reusables(self); - if (vec_size(self->const_floats) > 0) - ir_builder_split_vectors(self); - } - - for (i = 0; i < vec_size(self->fields); ++i) - { - ir_builder_prepare_field(self->code, self->fields[i]); - } - - for (i = 0; i < vec_size(self->globals); ++i) - { - if (!ir_builder_gen_global(self, self->globals[i], false)) { - return false; - } - if (self->globals[i]->vtype == TYPE_FUNCTION) { - ir_function *func = self->globals[i]->constval.vfunc; - if (func && self->max_locals < func->allocated_locals && - !(func->flags & IR_FLAG_MASK_NO_OVERLAP)) - { - self->max_locals = func->allocated_locals; - } - if (func && self->max_globaltemps < func->globaltemps) - self->max_globaltemps = func->globaltemps; - } - } - - for (i = 0; i < vec_size(self->fields); ++i) - { - if (!ir_builder_gen_field(self, self->fields[i])) { - return false; - } - } - - /* generate nil */ - ir_value_code_setaddr(self->nil, vec_size(self->code->globals)); - vec_push(self->code->globals, 0); - vec_push(self->code->globals, 0); - vec_push(self->code->globals, 0); - - /* generate virtual-instruction temps */ - for (i = 0; i < IR_MAX_VINSTR_TEMPS; ++i) { - ir_value_code_setaddr(self->vinstr_temp[i], vec_size(self->code->globals)); - vec_push(self->code->globals, 0); - vec_push(self->code->globals, 0); - vec_push(self->code->globals, 0); - } - - /* generate global temps */ - self->first_common_globaltemp = vec_size(self->code->globals); - for (i = 0; i < self->max_globaltemps; ++i) { - vec_push(self->code->globals, 0); - } - /* generate common locals */ - self->first_common_local = vec_size(self->code->globals); - for (i = 0; i < self->max_locals; ++i) { - vec_push(self->code->globals, 0); - } - - /* generate function code */ - for (i = 0; i < vec_size(self->globals); ++i) - { - if (self->globals[i]->vtype == TYPE_FUNCTION) { - if (!gen_global_function_code(self, self->globals[i])) { - return false; - } - } - } - - if (vec_size(self->code->globals) >= 65536) { - irerror(vec_last(self->globals)->context, "This progs file would require more globals than the metadata can handle (%u). Bailing out.", (unsigned int)vec_size(self->code->globals)); - return false; - } - - /* DP errors if the last instruction is not an INSTR_DONE. */ - if (vec_last(self->code->statements).opcode != INSTR_DONE) - { - lex_ctx_t last; - - stmt.opcode = INSTR_DONE; - stmt.o1.u1 = 0; - stmt.o2.u1 = 0; - stmt.o3.u1 = 0; - last.line = vec_last(self->code->linenums); - last.column = vec_last(self->code->columnnums); - - code_push_statement(self->code, &stmt, last); - } - - if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) - return true; - - if (vec_size(self->code->statements) != vec_size(self->code->linenums)) { - con_err("Linecounter wrong: %lu != %lu\n", - (unsigned long)vec_size(self->code->statements), - (unsigned long)vec_size(self->code->linenums)); - } else if (OPTS_FLAG(LNO)) { - char *dot; - size_t filelen = strlen(filename); - - memcpy(vec_add(lnofile, filelen+1), filename, filelen+1); - dot = strrchr(lnofile, '.'); - if (!dot) { - vec_pop(lnofile); - } else { - vec_shrinkto(lnofile, dot - lnofile); - } - memcpy(vec_add(lnofile, 5), ".lno", 5); - } - - if (!code_write(self->code, filename, lnofile)) { - vec_free(lnofile); - return false; - } - - vec_free(lnofile); - return true; -} - -/*********************************************************************** - *IR DEBUG Dump functions... - */ - -#define IND_BUFSZ 1024 - -static const char *qc_opname(int op) -{ - if (op < 0) return ""; - if (op < VINSTR_END) - return util_instr_str[op]; - switch (op) { - case VINSTR_END: return "END"; - case VINSTR_PHI: return "PHI"; - case VINSTR_JUMP: return "JUMP"; - case VINSTR_COND: return "COND"; - case VINSTR_BITXOR: return "BITXOR"; - case VINSTR_BITAND_V: return "BITAND_V"; - case VINSTR_BITOR_V: return "BITOR_V"; - case VINSTR_BITXOR_V: return "BITXOR_V"; - case VINSTR_BITAND_VF: return "BITAND_VF"; - case VINSTR_BITOR_VF: return "BITOR_VF"; - case VINSTR_BITXOR_VF: return "BITXOR_VF"; - case VINSTR_CROSS: return "CROSS"; - case VINSTR_NEG_F: return "NEG_F"; - case VINSTR_NEG_V: return "NEG_V"; - default: return ""; - } -} - -void ir_builder_dump(ir_builder *b, int (*oprintf)(const char*, ...)) -{ - size_t i; - char indent[IND_BUFSZ]; - indent[0] = '\t'; - indent[1] = 0; - - oprintf("module %s\n", b->name); - for (i = 0; i < vec_size(b->globals); ++i) - { - oprintf("global "); - if (b->globals[i]->hasvalue) - oprintf("%s = ", b->globals[i]->name); - ir_value_dump(b->globals[i], oprintf); - oprintf("\n"); - } - for (i = 0; i < vec_size(b->functions); ++i) - ir_function_dump(b->functions[i], indent, oprintf); - oprintf("endmodule %s\n", b->name); -} - -static const char *storenames[] = { - "[global]", "[local]", "[param]", "[value]", "[return]" -}; - -void ir_function_dump(ir_function *f, char *ind, - int (*oprintf)(const char*, ...)) -{ - size_t i; - if (f->builtin != 0) { - oprintf("%sfunction %s = builtin %i\n", ind, f->name, -f->builtin); - return; - } - oprintf("%sfunction %s\n", ind, f->name); - util_strncat(ind, "\t", IND_BUFSZ-1); - if (vec_size(f->locals)) - { - oprintf("%s%i locals:\n", ind, (int)vec_size(f->locals)); - for (i = 0; i < vec_size(f->locals); ++i) { - oprintf("%s\t", ind); - ir_value_dump(f->locals[i], oprintf); - oprintf("\n"); - } - } - oprintf("%sliferanges:\n", ind); - for (i = 0; i < vec_size(f->locals); ++i) { - const char *attr = ""; - size_t l, m; - ir_value *v = f->locals[i]; - if (v->unique_life && v->locked) - attr = "unique,locked "; - else if (v->unique_life) - attr = "unique "; - else if (v->locked) - attr = "locked "; - oprintf("%s\t%s: %s %s %s%s@%i ", ind, v->name, type_name[v->vtype], - storenames[v->store], - attr, (v->callparam ? "callparam " : ""), - (int)v->code.local); - if (!v->life) - oprintf("[null]"); - for (l = 0; l < vec_size(v->life); ++l) { - oprintf("[%i,%i] ", v->life[l].start, v->life[l].end); - } - oprintf("\n"); - for (m = 0; m < 3; ++m) { - ir_value *vm = v->members[m]; - if (!vm) - continue; - oprintf("%s\t%s: @%i ", ind, vm->name, (int)vm->code.local); - for (l = 0; l < vec_size(vm->life); ++l) { - oprintf("[%i,%i] ", vm->life[l].start, vm->life[l].end); - } - oprintf("\n"); - } - } - for (i = 0; i < vec_size(f->values); ++i) { - const char *attr = ""; - size_t l, m; - ir_value *v = f->values[i]; - if (v->unique_life && v->locked) - attr = "unique,locked "; - else if (v->unique_life) - attr = "unique "; - else if (v->locked) - attr = "locked "; - oprintf("%s\t%s: %s %s %s%s@%i ", ind, v->name, type_name[v->vtype], - storenames[v->store], - attr, (v->callparam ? "callparam " : ""), - (int)v->code.local); - if (!v->life) - oprintf("[null]"); - for (l = 0; l < vec_size(v->life); ++l) { - oprintf("[%i,%i] ", v->life[l].start, v->life[l].end); - } - oprintf("\n"); - for (m = 0; m < 3; ++m) { - ir_value *vm = v->members[m]; - if (!vm) - continue; - if (vm->unique_life && vm->locked) - attr = "unique,locked "; - else if (vm->unique_life) - attr = "unique "; - else if (vm->locked) - attr = "locked "; - oprintf("%s\t%s: %s@%i ", ind, vm->name, attr, (int)vm->code.local); - for (l = 0; l < vec_size(vm->life); ++l) { - oprintf("[%i,%i] ", vm->life[l].start, vm->life[l].end); - } - oprintf("\n"); - } - } - if (vec_size(f->blocks)) - { - oprintf("%slife passes: %i\n", ind, (int)f->run_id); - for (i = 0; i < vec_size(f->blocks); ++i) { - ir_block_dump(f->blocks[i], ind, oprintf); - } - - } - ind[strlen(ind)-1] = 0; - oprintf("%sendfunction %s\n", ind, f->name); -} - -void ir_block_dump(ir_block* b, char *ind, - int (*oprintf)(const char*, ...)) -{ - size_t i; - oprintf("%s:%s\n", ind, b->label); - util_strncat(ind, "\t", IND_BUFSZ-1); - - if (b->instr && b->instr[0]) - oprintf("%s (%i) [entry]\n", ind, (int)(b->instr[0]->eid-1)); - for (i = 0; i < vec_size(b->instr); ++i) - ir_instr_dump(b->instr[i], ind, oprintf); - ind[strlen(ind)-1] = 0; -} - -static void dump_phi(ir_instr *in, int (*oprintf)(const char*, ...)) -{ - size_t i; - oprintf("%s <- phi ", in->_ops[0]->name); - for (i = 0; i < vec_size(in->phi); ++i) - { - oprintf("([%s] : %s) ", in->phi[i].from->label, - in->phi[i].value->name); - } - oprintf("\n"); -} - -void ir_instr_dump(ir_instr *in, char *ind, - int (*oprintf)(const char*, ...)) -{ - size_t i; - const char *comma = NULL; - - oprintf("%s (%i) ", ind, (int)in->eid); - - if (in->opcode == VINSTR_PHI) { - dump_phi(in, oprintf); - return; - } - - util_strncat(ind, "\t", IND_BUFSZ-1); - - if (in->_ops[0] && (in->_ops[1] || in->_ops[2])) { - ir_value_dump(in->_ops[0], oprintf); - if (in->_ops[1] || in->_ops[2]) - oprintf(" <- "); - } - if (in->opcode == INSTR_CALL0 || in->opcode == VINSTR_NRCALL) { - oprintf("CALL%i\t", vec_size(in->params)); - } else - oprintf("%s\t", qc_opname(in->opcode)); - - if (in->_ops[0] && !(in->_ops[1] || in->_ops[2])) { - ir_value_dump(in->_ops[0], oprintf); - comma = ",\t"; - } - else - { - for (i = 1; i != 3; ++i) { - if (in->_ops[i]) { - if (comma) - oprintf(comma); - ir_value_dump(in->_ops[i], oprintf); - comma = ",\t"; - } - } - } - if (in->bops[0]) { - if (comma) - oprintf(comma); - oprintf("[%s]", in->bops[0]->label); - comma = ",\t"; - } - if (in->bops[1]) - oprintf("%s[%s]", comma, in->bops[1]->label); - if (vec_size(in->params)) { - oprintf("\tparams: "); - for (i = 0; i != vec_size(in->params); ++i) { - oprintf("%s, ", in->params[i]->name); - } - } - oprintf("\n"); - ind[strlen(ind)-1] = 0; -} - -static void ir_value_dump_string(const char *str, int (*oprintf)(const char*, ...)) -{ - oprintf("\""); - for (; *str; ++str) { - switch (*str) { - case '\n': oprintf("\\n"); break; - case '\r': oprintf("\\r"); break; - case '\t': oprintf("\\t"); break; - case '\v': oprintf("\\v"); break; - case '\f': oprintf("\\f"); break; - case '\b': oprintf("\\b"); break; - case '\a': oprintf("\\a"); break; - case '\\': oprintf("\\\\"); break; - case '"': oprintf("\\\""); break; - default: oprintf("%c", *str); break; - } - } - oprintf("\""); -} - -void ir_value_dump(ir_value* v, int (*oprintf)(const char*, ...)) -{ - if (v->hasvalue) { - switch (v->vtype) { - default: - case TYPE_VOID: - oprintf("(void)"); - break; - case TYPE_FUNCTION: - oprintf("fn:%s", v->name); - break; - case TYPE_FLOAT: - oprintf("%g", v->constval.vfloat); - break; - case TYPE_VECTOR: - oprintf("'%g %g %g'", - v->constval.vvec.x, - v->constval.vvec.y, - v->constval.vvec.z); - break; - case TYPE_ENTITY: - oprintf("(entity)"); - break; - case TYPE_STRING: - ir_value_dump_string(v->constval.vstring, oprintf); - break; -#if 0 - case TYPE_INTEGER: - oprintf("%i", v->constval.vint); - break; -#endif - case TYPE_POINTER: - oprintf("&%s", - v->constval.vpointer->name); - break; - } - } else { - oprintf("%s", v->name); - } -} - -void ir_value_dump_life(const ir_value *self, int (*oprintf)(const char*,...)) -{ - size_t i; - oprintf("Life of %12s:", self->name); - for (i = 0; i < vec_size(self->life); ++i) - { - oprintf(" + [%i, %i]\n", self->life[i].start, self->life[i].end); - } -} diff --git a/ir.cpp b/ir.cpp new file mode 100644 index 0000000..7ac8477 --- /dev/null +++ b/ir.cpp @@ -0,0 +1,4101 @@ +#include +#include + +#include "gmqcc.h" +#include "ir.h" + +/*********************************************************************** + * Type sizes used at multiple points in the IR codegen + */ + +const char *type_name[TYPE_COUNT] = { + "void", + "string", + "float", + "vector", + "entity", + "field", + "function", + "pointer", + "integer", + "variant", + "struct", + "union", + "array", + + "nil", + "" +}; + +static size_t type_sizeof_[TYPE_COUNT] = { + 1, /* TYPE_VOID */ + 1, /* TYPE_STRING */ + 1, /* TYPE_FLOAT */ + 3, /* TYPE_VECTOR */ + 1, /* TYPE_ENTITY */ + 1, /* TYPE_FIELD */ + 1, /* TYPE_FUNCTION */ + 1, /* TYPE_POINTER */ + 1, /* TYPE_INTEGER */ + 3, /* TYPE_VARIANT */ + 0, /* TYPE_STRUCT */ + 0, /* TYPE_UNION */ + 0, /* TYPE_ARRAY */ + 0, /* TYPE_NIL */ + 0, /* TYPE_NOESPR */ +}; + +const uint16_t type_store_instr[TYPE_COUNT] = { + INSTR_STORE_F, /* should use I when having integer support */ + INSTR_STORE_S, + INSTR_STORE_F, + INSTR_STORE_V, + INSTR_STORE_ENT, + INSTR_STORE_FLD, + INSTR_STORE_FNC, + INSTR_STORE_ENT, /* should use I */ +#if 0 + INSTR_STORE_I, /* integer type */ +#else + INSTR_STORE_F, +#endif + + INSTR_STORE_V, /* variant, should never be accessed */ + + VINSTR_END, /* struct */ + VINSTR_END, /* union */ + VINSTR_END, /* array */ + VINSTR_END, /* nil */ + VINSTR_END, /* noexpr */ +}; + +const uint16_t field_store_instr[TYPE_COUNT] = { + INSTR_STORE_FLD, + INSTR_STORE_FLD, + INSTR_STORE_FLD, + INSTR_STORE_V, + INSTR_STORE_FLD, + INSTR_STORE_FLD, + INSTR_STORE_FLD, + INSTR_STORE_FLD, +#if 0 + INSTR_STORE_FLD, /* integer type */ +#else + INSTR_STORE_FLD, +#endif + + INSTR_STORE_V, /* variant, should never be accessed */ + + VINSTR_END, /* struct */ + VINSTR_END, /* union */ + VINSTR_END, /* array */ + VINSTR_END, /* nil */ + VINSTR_END, /* noexpr */ +}; + +const uint16_t type_storep_instr[TYPE_COUNT] = { + INSTR_STOREP_F, /* should use I when having integer support */ + INSTR_STOREP_S, + INSTR_STOREP_F, + INSTR_STOREP_V, + INSTR_STOREP_ENT, + INSTR_STOREP_FLD, + INSTR_STOREP_FNC, + INSTR_STOREP_ENT, /* should use I */ +#if 0 + INSTR_STOREP_ENT, /* integer type */ +#else + INSTR_STOREP_F, +#endif + + INSTR_STOREP_V, /* variant, should never be accessed */ + + VINSTR_END, /* struct */ + VINSTR_END, /* union */ + VINSTR_END, /* array */ + VINSTR_END, /* nil */ + VINSTR_END, /* noexpr */ +}; + +const uint16_t type_eq_instr[TYPE_COUNT] = { + INSTR_EQ_F, /* should use I when having integer support */ + INSTR_EQ_S, + INSTR_EQ_F, + INSTR_EQ_V, + INSTR_EQ_E, + INSTR_EQ_E, /* FLD has no comparison */ + INSTR_EQ_FNC, + INSTR_EQ_E, /* should use I */ +#if 0 + INSTR_EQ_I, +#else + INSTR_EQ_F, +#endif + + INSTR_EQ_V, /* variant, should never be accessed */ + + VINSTR_END, /* struct */ + VINSTR_END, /* union */ + VINSTR_END, /* array */ + VINSTR_END, /* nil */ + VINSTR_END, /* noexpr */ +}; + +const uint16_t type_ne_instr[TYPE_COUNT] = { + INSTR_NE_F, /* should use I when having integer support */ + INSTR_NE_S, + INSTR_NE_F, + INSTR_NE_V, + INSTR_NE_E, + INSTR_NE_E, /* FLD has no comparison */ + INSTR_NE_FNC, + INSTR_NE_E, /* should use I */ +#if 0 + INSTR_NE_I, +#else + INSTR_NE_F, +#endif + + INSTR_NE_V, /* variant, should never be accessed */ + + VINSTR_END, /* struct */ + VINSTR_END, /* union */ + VINSTR_END, /* array */ + VINSTR_END, /* nil */ + VINSTR_END, /* noexpr */ +}; + +const uint16_t type_not_instr[TYPE_COUNT] = { + INSTR_NOT_F, /* should use I when having integer support */ + VINSTR_END, /* not to be used, depends on string related -f flags */ + INSTR_NOT_F, + INSTR_NOT_V, + INSTR_NOT_ENT, + INSTR_NOT_ENT, + INSTR_NOT_FNC, + INSTR_NOT_ENT, /* should use I */ +#if 0 + INSTR_NOT_I, /* integer type */ +#else + INSTR_NOT_F, +#endif + + INSTR_NOT_V, /* variant, should never be accessed */ + + VINSTR_END, /* struct */ + VINSTR_END, /* union */ + VINSTR_END, /* array */ + VINSTR_END, /* nil */ + VINSTR_END, /* noexpr */ +}; + +/* protos */ +static void ir_function_dump(ir_function*, char *ind, int (*oprintf)(const char*,...)); + +static ir_value* ir_block_create_general_instr(ir_block *self, lex_ctx_t, const char *label, + int op, ir_value *a, ir_value *b, qc_type outype); +static bool GMQCC_WARN ir_block_create_store(ir_block*, lex_ctx_t, ir_value *target, ir_value *what); +static void ir_block_dump(ir_block*, char *ind, int (*oprintf)(const char*,...)); + +static bool ir_instr_op(ir_instr*, int op, ir_value *value, bool writing); +static void ir_instr_dump(ir_instr* in, char *ind, int (*oprintf)(const char*,...)); +/* error functions */ + +static void irerror(lex_ctx_t ctx, const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + con_cvprintmsg(ctx, LVL_ERROR, "internal error", msg, ap); + va_end(ap); +} + +static bool GMQCC_WARN irwarning(lex_ctx_t ctx, int warntype, const char *fmt, ...) +{ + bool r; + va_list ap; + va_start(ap, fmt); + r = vcompile_warning(ctx, warntype, fmt, ap); + va_end(ap); + return r; +} + +/*********************************************************************** + * Vector utility functions + */ + +static bool GMQCC_WARN vec_ir_value_find(std::vector &vec, const ir_value *what, size_t *idx) +{ + for (auto &it : vec) { + if (it != what) + continue; + if (idx) + *idx = &it - &vec[0]; + return true; + } + return false; +} + +static bool GMQCC_WARN vec_ir_block_find(ir_block **vec, ir_block *what, size_t *idx) +{ + size_t i; + size_t len = vec_size(vec); + for (i = 0; i < len; ++i) { + if (vec[i] == what) { + if (idx) *idx = i; + return true; + } + } + return false; +} + +static bool GMQCC_WARN vec_ir_instr_find(std::vector &vec, ir_instr *what, size_t *idx) +{ + for (auto &it : vec) { + if (it != what) + continue; + if (idx) + *idx = &it - &vec[0]; + return true; + } + return false; +} + +/*********************************************************************** + * IR Builder + */ + +static void ir_block_delete_quick(ir_block* self); +static void ir_instr_delete_quick(ir_instr *self); +static void ir_function_delete_quick(ir_function *self); + +ir_builder::ir_builder(const std::string& modulename) +: m_name(modulename), + m_code(new code_t) +{ + m_htglobals = util_htnew(IR_HT_SIZE); + m_htfields = util_htnew(IR_HT_SIZE); + m_htfunctions = util_htnew(IR_HT_SIZE); + + m_nil = new ir_value("nil", store_value, TYPE_NIL); + m_nil->m_cvq = CV_CONST; + + for (size_t i = 0; i != IR_MAX_VINSTR_TEMPS; ++i) { + /* we write to them, but they're not supposed to be used outside the IR, so + * let's not allow the generation of ir_instrs which use these. + * So it's a constant noexpr. + */ + m_vinstr_temp[i] = new ir_value("vinstr_temp", store_value, TYPE_NOEXPR); + m_vinstr_temp[i]->m_cvq = CV_CONST; + } +} + +ir_builder::~ir_builder() +{ + util_htdel(m_htglobals); + util_htdel(m_htfields); + util_htdel(m_htfunctions); + for (auto& f : m_functions) + ir_function_delete_quick(f.release()); + m_functions.clear(); // delete them now before deleting the rest: + + delete m_nil; + + for (size_t i = 0; i != IR_MAX_VINSTR_TEMPS; ++i) { + delete m_vinstr_temp[i]; + } + + m_extparams.clear(); + m_extparam_protos.clear(); +} + +ir_function* ir_builder::createFunction(const std::string& name, qc_type outtype) +{ + ir_function *fn = (ir_function*)util_htget(m_htfunctions, name.c_str()); + if (fn) + return nullptr; + + fn = new ir_function(this, outtype); + fn->m_name = name; + m_functions.emplace_back(fn); + util_htset(m_htfunctions, name.c_str(), fn); + + fn->m_value = createGlobal(fn->m_name, TYPE_FUNCTION); + if (!fn->m_value) { + delete fn; + return nullptr; + } + + fn->m_value->m_hasvalue = true; + fn->m_value->m_outtype = outtype; + fn->m_value->m_constval.vfunc = fn; + fn->m_value->m_context = fn->m_context; + + return fn; +} + +ir_value* ir_builder::createGlobal(const std::string& name, qc_type vtype) +{ + ir_value *ve; + + if (name[0] != '#') + { + ve = (ir_value*)util_htget(m_htglobals, name.c_str()); + if (ve) { + return nullptr; + } + } + + ve = new ir_value(std::string(name), store_global, vtype); + m_globals.emplace_back(ve); + util_htset(m_htglobals, name.c_str(), ve); + return ve; +} + +ir_value* ir_builder::get_va_count() +{ + if (m_reserved_va_count) + return m_reserved_va_count; + return (m_reserved_va_count = createGlobal("reserved:va_count", TYPE_FLOAT)); +} + +ir_value* ir_builder::createField(const std::string& name, qc_type vtype) +{ + ir_value *ve = (ir_value*)util_htget(m_htfields, name.c_str()); + if (ve) { + return nullptr; + } + + ve = new ir_value(std::string(name), store_global, TYPE_FIELD); + ve->m_fieldtype = vtype; + m_fields.emplace_back(ve); + util_htset(m_htfields, name.c_str(), ve); + return ve; +} + +/*********************************************************************** + *IR Function + */ + +static bool ir_function_naive_phi(ir_function*); +static void ir_function_enumerate(ir_function*); +static bool ir_function_calculate_liferanges(ir_function*); +static bool ir_function_allocate_locals(ir_function*); + +ir_function::ir_function(ir_builder* owner_, qc_type outtype_) +: m_owner(owner_), + m_name("<@unnamed>"), + m_outtype(outtype_) +{ + m_context.file = "<@no context>"; + m_context.line = 0; +} + +ir_function::~ir_function() +{ +} + +static void ir_function_delete_quick(ir_function *self) +{ + for (auto& b : self->m_blocks) + ir_block_delete_quick(b.release()); + delete self; +} + +static void ir_function_collect_value(ir_function *self, ir_value *v) +{ + self->m_values.emplace_back(v); +} + +ir_block* ir_function_create_block(lex_ctx_t ctx, ir_function *self, const char *label) +{ + ir_block* bn = new ir_block(self, label ? std::string(label) : std::string()); + bn->m_context = ctx; + self->m_blocks.emplace_back(bn); + + if ((self->m_flags & IR_FLAG_BLOCK_COVERAGE) && self->m_owner->m_coverage_func) + (void)ir_block_create_call(bn, ctx, nullptr, self->m_owner->m_coverage_func, false); + + return bn; +} + +static bool instr_is_operation(uint16_t op) +{ + return ( (op >= INSTR_MUL_F && op <= INSTR_GT) || + (op >= INSTR_LOAD_F && op <= INSTR_LOAD_FNC) || + (op == INSTR_ADDRESS) || + (op >= INSTR_NOT_F && op <= INSTR_NOT_FNC) || + (op >= INSTR_AND && op <= INSTR_BITOR) || + (op >= INSTR_CALL0 && op <= INSTR_CALL8) || + (op >= VINSTR_BITAND_V && op <= VINSTR_NEG_V) ); +} + +static bool ir_function_pass_peephole(ir_function *self) +{ + for (auto& bp : self->m_blocks) { + ir_block *block = bp.get(); + for (size_t i = 0; i < vec_size(block->m_instr); ++i) { + ir_instr *inst; + inst = block->m_instr[i]; + + if (i >= 1 && + (inst->m_opcode >= INSTR_STORE_F && + inst->m_opcode <= INSTR_STORE_FNC)) + { + ir_instr *store; + ir_instr *oper; + ir_value *value; + + store = inst; + + oper = block->m_instr[i-1]; + if (!instr_is_operation(oper->m_opcode)) + continue; + + /* Don't change semantics of MUL_VF in engines where these may not alias. */ + if (OPTS_FLAG(LEGACY_VECTOR_MATHS)) { + if (oper->m_opcode == INSTR_MUL_VF && oper->_m_ops[2]->m_memberof == oper->_m_ops[1]) + continue; + if (oper->m_opcode == INSTR_MUL_FV && oper->_m_ops[1]->m_memberof == oper->_m_ops[2]) + continue; + } + + value = oper->_m_ops[0]; + + /* only do it for SSA values */ + if (value->m_store != store_value) + continue; + + /* don't optimize out the temp if it's used later again */ + if (value->m_reads.size() != 1) + continue; + + /* The very next store must use this value */ + if (value->m_reads[0] != store) + continue; + + /* And of course the store must _read_ from it, so it's in + * OP 1 */ + if (store->_m_ops[1] != value) + continue; + + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + (void)!ir_instr_op(oper, 0, store->_m_ops[0], true); + + vec_remove(block->m_instr, i, 1); + delete store; + } + else if (inst->m_opcode == VINSTR_COND) + { + /* COND on a value resulting from a NOT could + * remove the NOT and swap its operands + */ + while (true) { + ir_block *tmp; + size_t inotid; + ir_instr *inot; + ir_value *value; + value = inst->_m_ops[0]; + + if (value->m_store != store_value || value->m_reads.size() != 1 || value->m_reads[0] != inst) + break; + + inot = value->m_writes[0]; + if (inot->_m_ops[0] != value || + inot->m_opcode < INSTR_NOT_F || + inot->m_opcode > INSTR_NOT_FNC || + inot->m_opcode == INSTR_NOT_V || /* can't do these */ + inot->m_opcode == INSTR_NOT_S) + { + break; + } + + /* count */ + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + /* change operand */ + (void)!ir_instr_op(inst, 0, inot->_m_ops[1], false); + /* remove NOT */ + tmp = inot->m_owner; + for (inotid = 0; inotid < vec_size(tmp->m_instr); ++inotid) { + if (tmp->m_instr[inotid] == inot) + break; + } + if (inotid >= vec_size(tmp->m_instr)) { + compile_error(inst->m_context, "sanity-check failed: failed to find instruction to optimize out"); + return false; + } + vec_remove(tmp->m_instr, inotid, 1); + delete inot; + /* swap ontrue/onfalse */ + tmp = inst->m_bops[0]; + inst->m_bops[0] = inst->m_bops[1]; + inst->m_bops[1] = tmp; + } + continue; + } + } + } + + return true; +} + +static bool ir_function_pass_tailrecursion(ir_function *self) +{ + size_t p; + + for (auto& bp : self->m_blocks) { + ir_block *block = bp.get(); + + ir_value *funcval; + ir_instr *ret, *call, *store = nullptr; + + if (!block->m_final || vec_size(block->m_instr) < 2) + continue; + + ret = block->m_instr[vec_size(block->m_instr)-1]; + if (ret->m_opcode != INSTR_DONE && ret->m_opcode != INSTR_RETURN) + continue; + + call = block->m_instr[vec_size(block->m_instr)-2]; + if (call->m_opcode >= INSTR_STORE_F && call->m_opcode <= INSTR_STORE_FNC) { + /* account for the unoptimized + * CALL + * STORE %return, %tmp + * RETURN %tmp + * version + */ + if (vec_size(block->m_instr) < 3) + continue; + + store = call; + call = block->m_instr[vec_size(block->m_instr)-3]; + } + + if (call->m_opcode < INSTR_CALL0 || call->m_opcode > INSTR_CALL8) + continue; + + if (store) { + /* optimize out the STORE */ + if (ret->_m_ops[0] && + ret->_m_ops[0] == store->_m_ops[0] && + store->_m_ops[1] == call->_m_ops[0]) + { + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + call->_m_ops[0] = store->_m_ops[0]; + vec_remove(block->m_instr, vec_size(block->m_instr) - 2, 1); + delete store; + } + else + continue; + } + + if (!call->_m_ops[0]) + continue; + + funcval = call->_m_ops[1]; + if (!funcval) + continue; + if (funcval->m_vtype != TYPE_FUNCTION || funcval->m_constval.vfunc != self) + continue; + + /* now we have a CALL and a RET, check if it's a tailcall */ + if (ret->_m_ops[0] && call->_m_ops[0] != ret->_m_ops[0]) + continue; + + ++opts_optimizationcount[OPTIM_TAIL_RECURSION]; + vec_shrinkby(block->m_instr, 2); + + block->m_final = false; /* open it back up */ + + /* emite parameter-stores */ + for (p = 0; p < call->m_params.size(); ++p) { + /* assert(call->params_count <= self->locals_count); */ + if (!ir_block_create_store(block, call->m_context, self->m_locals[p].get(), call->m_params[p])) { + irerror(call->m_context, "failed to create tailcall store instruction for parameter %i", (int)p); + return false; + } + } + if (!ir_block_create_jump(block, call->m_context, self->m_blocks[0].get())) { + irerror(call->m_context, "failed to create tailcall jump"); + return false; + } + + delete call; + delete ret; + } + + return true; +} + +bool ir_function_finalize(ir_function *self) +{ + if (self->m_builtin) + return true; + + if (OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) { + if (!ir_function_pass_peephole(self)) { + irerror(self->m_context, "generic optimization pass broke something in `%s`", self->m_name.c_str()); + return false; + } + } + + if (OPTS_OPTIMIZATION(OPTIM_TAIL_RECURSION)) { + if (!ir_function_pass_tailrecursion(self)) { + irerror(self->m_context, "tail-recursion optimization pass broke something in `%s`", self->m_name.c_str()); + return false; + } + } + + if (!ir_function_naive_phi(self)) { + irerror(self->m_context, "internal error: ir_function_naive_phi failed"); + return false; + } + + for (auto& lp : self->m_locals) { + ir_value *v = lp.get(); + if (v->m_vtype == TYPE_VECTOR || + (v->m_vtype == TYPE_FIELD && v->m_outtype == TYPE_VECTOR)) + { + v->vectorMember(0); + v->vectorMember(1); + v->vectorMember(2); + } + } + for (auto& vp : self->m_values) { + ir_value *v = vp.get(); + if (v->m_vtype == TYPE_VECTOR || + (v->m_vtype == TYPE_FIELD && v->m_outtype == TYPE_VECTOR)) + { + v->vectorMember(0); + v->vectorMember(1); + v->vectorMember(2); + } + } + + ir_function_enumerate(self); + + if (!ir_function_calculate_liferanges(self)) + return false; + if (!ir_function_allocate_locals(self)) + return false; + return true; +} + +ir_value* ir_function_create_local(ir_function *self, const std::string& name, qc_type vtype, bool param) +{ + ir_value *ve; + + if (param && + !self->m_locals.empty() && + self->m_locals.back()->m_store != store_param) + { + irerror(self->m_context, "cannot add parameters after adding locals"); + return nullptr; + } + + ve = new ir_value(std::string(name), (param ? store_param : store_local), vtype); + if (param) + ve->m_locked = true; + self->m_locals.emplace_back(ve); + return ve; +} + +/*********************************************************************** + *IR Block + */ + +ir_block::ir_block(ir_function* owner, const std::string& name) +: m_owner(owner), + m_label(name) +{ + m_context.file = "<@no context>"; + m_context.line = 0; +} + +ir_block::~ir_block() +{ + for (size_t i = 0; i != vec_size(m_instr); ++i) + delete m_instr[i]; + vec_free(m_instr); + vec_free(m_entries); + vec_free(m_exits); +} + +static void ir_block_delete_quick(ir_block* self) +{ + size_t i; + for (i = 0; i != vec_size(self->m_instr); ++i) + ir_instr_delete_quick(self->m_instr[i]); + vec_free(self->m_instr); + delete self; +} + +/*********************************************************************** + *IR Instructions + */ + +ir_instr::ir_instr(lex_ctx_t ctx, ir_block* owner_, int op) +: m_opcode(op), + m_context(ctx), + m_owner(owner_) +{ +} + +ir_instr::~ir_instr() +{ + // The following calls can only delete from + // vectors, we still want to delete this instruction + // so ignore the return value. Since with the warn_unused_result attribute + // gcc doesn't care about an explicit: (void)foo(); to ignore the result, + // I have to improvise here and use if(foo()); + for (auto &it : m_phi) { + size_t idx; + if (vec_ir_instr_find(it.value->m_writes, this, &idx)) + it.value->m_writes.erase(it.value->m_writes.begin() + idx); + if (vec_ir_instr_find(it.value->m_reads, this, &idx)) + it.value->m_reads.erase(it.value->m_reads.begin() + idx); + } + for (auto &it : m_params) { + size_t idx; + if (vec_ir_instr_find(it->m_writes, this, &idx)) + it->m_writes.erase(it->m_writes.begin() + idx); + if (vec_ir_instr_find(it->m_reads, this, &idx)) + it->m_reads.erase(it->m_reads.begin() + idx); + } + (void)!ir_instr_op(this, 0, nullptr, false); + (void)!ir_instr_op(this, 1, nullptr, false); + (void)!ir_instr_op(this, 2, nullptr, false); +} + +static void ir_instr_delete_quick(ir_instr *self) +{ + self->m_phi.clear(); + self->m_params.clear(); + self->_m_ops[0] = nullptr; + self->_m_ops[1] = nullptr; + self->_m_ops[2] = nullptr; + delete self; +} + +static bool ir_instr_op(ir_instr *self, int op, ir_value *v, bool writing) +{ + if (v && v->m_vtype == TYPE_NOEXPR) { + irerror(self->m_context, "tried to use a NOEXPR value"); + return false; + } + + if (self->_m_ops[op]) { + size_t idx; + if (writing && vec_ir_instr_find(self->_m_ops[op]->m_writes, self, &idx)) + self->_m_ops[op]->m_writes.erase(self->_m_ops[op]->m_writes.begin() + idx); + else if (vec_ir_instr_find(self->_m_ops[op]->m_reads, self, &idx)) + self->_m_ops[op]->m_reads.erase(self->_m_ops[op]->m_reads.begin() + idx); + } + if (v) { + if (writing) + v->m_writes.push_back(self); + else + v->m_reads.push_back(self); + } + self->_m_ops[op] = v; + return true; +} + +/*********************************************************************** + *IR Value + */ + +void ir_value::setCodeAddress(int32_t gaddr) +{ + m_code.globaladdr = gaddr; + if (m_members[0]) m_members[0]->m_code.globaladdr = gaddr; + if (m_members[1]) m_members[1]->m_code.globaladdr = gaddr; + if (m_members[2]) m_members[2]->m_code.globaladdr = gaddr; +} + +int32_t ir_value::codeAddress() const +{ + if (m_store == store_return) + return OFS_RETURN + m_code.addroffset; + return m_code.globaladdr + m_code.addroffset; +} + +ir_value::ir_value(std::string&& name_, store_type store_, qc_type vtype_) + : m_name(move(name_)) + , m_vtype(vtype_) + , m_store(store_) +{ + m_fieldtype = TYPE_VOID; + m_outtype = TYPE_VOID; + m_flags = 0; + + m_cvq = CV_NONE; + m_hasvalue = false; + m_context.file = "<@no context>"; + m_context.line = 0; + + memset(&m_constval, 0, sizeof(m_constval)); + memset(&m_code, 0, sizeof(m_code)); + + m_members[0] = nullptr; + m_members[1] = nullptr; + m_members[2] = nullptr; + m_memberof = nullptr; + + m_unique_life = false; + m_locked = false; + m_callparam = false; +} + +ir_value::ir_value(ir_function *owner, std::string&& name, store_type storetype, qc_type vtype) + : ir_value(move(name), storetype, vtype) +{ + ir_function_collect_value(owner, this); +} + +ir_value::~ir_value() +{ + size_t i; + if (m_hasvalue) { + if (m_vtype == TYPE_STRING) + mem_d((void*)m_constval.vstring); + } + if (!(m_flags & IR_FLAG_SPLIT_VECTOR)) { + for (i = 0; i < 3; ++i) { + if (m_members[i]) + delete m_members[i]; + } + } +} + + +/* helper function */ +ir_value* ir_builder::literalFloat(float value, bool add_to_list) { + ir_value *v = new ir_value("#IMMEDIATE", store_global, TYPE_FLOAT); + v->m_flags |= IR_FLAG_ERASABLE; + v->m_hasvalue = true; + v->m_cvq = CV_CONST; + v->m_constval.vfloat = value; + + m_globals.emplace_back(v); + if (add_to_list) + m_const_floats.emplace_back(v); + return v; +} + +ir_value* ir_value::vectorMember(unsigned int member) +{ + std::string name; + ir_value *m; + if (member >= 3) + return nullptr; + + if (m_members[member]) + return m_members[member]; + + if (!m_name.empty()) { + char member_name[3] = { '_', char('x' + member), 0 }; + name = m_name + member_name; + } + + if (m_vtype == TYPE_VECTOR) + { + m = new ir_value(move(name), m_store, TYPE_FLOAT); + if (!m) + return nullptr; + m->m_context = m_context; + + m_members[member] = m; + m->m_code.addroffset = member; + } + else if (m_vtype == TYPE_FIELD) + { + if (m_fieldtype != TYPE_VECTOR) + return nullptr; + m = new ir_value(move(name), m_store, TYPE_FIELD); + if (!m) + return nullptr; + m->m_fieldtype = TYPE_FLOAT; + m->m_context = m_context; + + m_members[member] = m; + m->m_code.addroffset = member; + } + else + { + irerror(m_context, "invalid member access on %s", m_name.c_str()); + return nullptr; + } + + m->m_memberof = this; + return m; +} + +size_t ir_value::size() const { + if (m_vtype == TYPE_FIELD && m_fieldtype == TYPE_VECTOR) + return type_sizeof_[TYPE_VECTOR]; + return type_sizeof_[m_vtype]; +} + +bool ir_value::setFloat(float f) +{ + if (m_vtype != TYPE_FLOAT) + return false; + m_constval.vfloat = f; + m_hasvalue = true; + return true; +} + +bool ir_value::setFunc(int f) +{ + if (m_vtype != TYPE_FUNCTION) + return false; + m_constval.vint = f; + m_hasvalue = true; + return true; +} + +bool ir_value::setVector(vec3_t v) +{ + if (m_vtype != TYPE_VECTOR) + return false; + m_constval.vvec = v; + m_hasvalue = true; + return true; +} + +bool ir_value::setField(ir_value *fld) +{ + if (m_vtype != TYPE_FIELD) + return false; + m_constval.vpointer = fld; + m_hasvalue = true; + return true; +} + +bool ir_value::setString(const char *str) +{ + if (m_vtype != TYPE_STRING) + return false; + m_constval.vstring = util_strdupe(str); + m_hasvalue = true; + return true; +} + +#if 0 +bool ir_value::setInt(int i) +{ + if (m_vtype != TYPE_INTEGER) + return false; + m_constval.vint = i; + m_hasvalue = true; + return true; +} +#endif + +bool ir_value::lives(size_t at) +{ + for (auto& l : m_life) { + if (l.start <= at && at <= l.end) + return true; + if (l.start > at) /* since it's ordered */ + return false; + } + return false; +} + +bool ir_value::insertLife(size_t idx, ir_life_entry_t e) +{ + m_life.insert(m_life.begin() + idx, e); + return true; +} + +bool ir_value::setAlive(size_t s) +{ + size_t i; + const size_t vs = m_life.size(); + ir_life_entry_t *life_found = nullptr; + ir_life_entry_t *before = nullptr; + ir_life_entry_t new_entry; + + /* Find the first range >= s */ + for (i = 0; i < vs; ++i) + { + before = life_found; + life_found = &m_life[i]; + if (life_found->start > s) + break; + } + /* nothing found? append */ + if (i == vs) { + ir_life_entry_t e; + if (life_found && life_found->end+1 == s) + { + /* previous life range can be merged in */ + life_found->end++; + return true; + } + if (life_found && life_found->end >= s) + return false; + e.start = e.end = s; + m_life.emplace_back(e); + return true; + } + /* found */ + if (before) + { + if (before->end + 1 == s && + life_found->start - 1 == s) + { + /* merge */ + before->end = life_found->end; + m_life.erase(m_life.begin()+i); + return true; + } + if (before->end + 1 == s) + { + /* extend before */ + before->end++; + return true; + } + /* already contained */ + if (before->end >= s) + return false; + } + /* extend */ + if (life_found->start - 1 == s) + { + life_found->start--; + return true; + } + /* insert a new entry */ + new_entry.start = new_entry.end = s; + return insertLife(i, new_entry); +} + +bool ir_value::mergeLife(const ir_value *other) +{ + size_t i, myi; + + if (other->m_life.empty()) + return true; + + if (m_life.empty()) { + m_life = other->m_life; + return true; + } + + myi = 0; + for (i = 0; i < other->m_life.size(); ++i) + { + const ir_life_entry_t &otherlife = other->m_life[i]; + while (true) + { + ir_life_entry_t *entry = &m_life[myi]; + + if (otherlife.end+1 < entry->start) + { + /* adding an interval before entry */ + if (!insertLife(myi, otherlife)) + return false; + ++myi; + break; + } + + if (otherlife.start < entry->start && + otherlife.end+1 >= entry->start) + { + /* starts earlier and overlaps */ + entry->start = otherlife.start; + } + + if (otherlife.end > entry->end && + otherlife.start <= entry->end+1) + { + /* ends later and overlaps */ + entry->end = otherlife.end; + } + + /* see if our change combines it with the next ranges */ + while (myi+1 < m_life.size() && + entry->end+1 >= m_life[1+myi].start) + { + /* overlaps with (myi+1) */ + if (entry->end < m_life[1+myi].end) + entry->end = m_life[1+myi].end; + m_life.erase(m_life.begin() + (myi + 1)); + entry = &m_life[myi]; + } + + /* see if we're after the entry */ + if (otherlife.start > entry->end) + { + ++myi; + /* append if we're at the end */ + if (myi >= m_life.size()) { + m_life.emplace_back(otherlife); + break; + } + /* otherweise check the next range */ + continue; + } + break; + } + } + return true; +} + +static bool ir_values_overlap(const ir_value *a, const ir_value *b) +{ + /* For any life entry in A see if it overlaps with + * any life entry in B. + * Note that the life entries are orderes, so we can make a + * more efficient algorithm there than naively translating the + * statement above. + */ + + const ir_life_entry_t *la, *lb, *enda, *endb; + + /* first of all, if either has no life range, they cannot clash */ + if (a->m_life.empty() || b->m_life.empty()) + return false; + + la = &a->m_life.front(); + lb = &b->m_life.front(); + enda = &a->m_life.back() + 1; + endb = &b->m_life.back() + 1; + while (true) + { + /* check if the entries overlap, for that, + * both must start before the other one ends. + */ + if (la->start < lb->end && + lb->start < la->end) + { + return true; + } + + /* entries are ordered + * one entry is earlier than the other + * that earlier entry will be moved forward + */ + if (la->start < lb->start) + { + /* order: A B, move A forward + * check if we hit the end with A + */ + if (++la == enda) + break; + } + else /* if (lb->start < la->start) actually <= */ + { + /* order: B A, move B forward + * check if we hit the end with B + */ + if (++lb == endb) + break; + } + } + return false; +} + +/*********************************************************************** + *IR main operations + */ + +static bool ir_check_unreachable(ir_block *self) +{ + /* The IR should never have to deal with unreachable code */ + if (!self->m_final/* || OPTS_FLAG(ALLOW_UNREACHABLE_CODE)*/) + return true; + irerror(self->m_context, "unreachable statement (%s)", self->m_label.c_str()); + return false; +} + +bool ir_block_create_store_op(ir_block *self, lex_ctx_t ctx, int op, ir_value *target, ir_value *what) +{ + ir_instr *in; + if (!ir_check_unreachable(self)) + return false; + + if (target->m_store == store_value && + (op < INSTR_STOREP_F || op > INSTR_STOREP_FNC)) + { + irerror(self->m_context, "cannot store to an SSA value"); + irerror(self->m_context, "trying to store: %s <- %s", target->m_name.c_str(), what->m_name.c_str()); + irerror(self->m_context, "instruction: %s", util_instr_str[op]); + return false; + } + + in = new ir_instr(ctx, self, op); + if (!in) + return false; + + if (!ir_instr_op(in, 0, target, (op < INSTR_STOREP_F || op > INSTR_STOREP_FNC)) || + !ir_instr_op(in, 1, what, false)) + { + delete in; + return false; + } + vec_push(self->m_instr, in); + return true; +} + +bool ir_block_create_state_op(ir_block *self, lex_ctx_t ctx, ir_value *frame, ir_value *think) +{ + ir_instr *in; + if (!ir_check_unreachable(self)) + return false; + + in = new ir_instr(ctx, self, INSTR_STATE); + if (!in) + return false; + + if (!ir_instr_op(in, 0, frame, false) || + !ir_instr_op(in, 1, think, false)) + { + delete in; + return false; + } + vec_push(self->m_instr, in); + return true; +} + +static bool ir_block_create_store(ir_block *self, lex_ctx_t ctx, ir_value *target, ir_value *what) +{ + int op = 0; + qc_type vtype; + if (target->m_vtype == TYPE_VARIANT) + vtype = what->m_vtype; + else + vtype = target->m_vtype; + +#if 0 + if (vtype == TYPE_FLOAT && what->m_vtype == TYPE_INTEGER) + op = INSTR_CONV_ITOF; + else if (vtype == TYPE_INTEGER && what->m_vtype == TYPE_FLOAT) + op = INSTR_CONV_FTOI; +#endif + op = type_store_instr[vtype]; + + if (OPTS_FLAG(ADJUST_VECTOR_FIELDS)) { + if (op == INSTR_STORE_FLD && what->m_fieldtype == TYPE_VECTOR) + op = INSTR_STORE_V; + } + + return ir_block_create_store_op(self, ctx, op, target, what); +} + +bool ir_block_create_storep(ir_block *self, lex_ctx_t ctx, ir_value *target, ir_value *what) +{ + int op = 0; + qc_type vtype; + + if (target->m_vtype != TYPE_POINTER) + return false; + + /* storing using pointer - target is a pointer, type must be + * inferred from source + */ + vtype = what->m_vtype; + + op = type_storep_instr[vtype]; + if (OPTS_FLAG(ADJUST_VECTOR_FIELDS)) { + if (op == INSTR_STOREP_FLD && what->m_fieldtype == TYPE_VECTOR) + op = INSTR_STOREP_V; + } + + return ir_block_create_store_op(self, ctx, op, target, what); +} + +bool ir_block_create_return(ir_block *self, lex_ctx_t ctx, ir_value *v) +{ + ir_instr *in; + if (!ir_check_unreachable(self)) + return false; + + self->m_final = true; + + self->m_is_return = true; + in = new ir_instr(ctx, self, INSTR_RETURN); + if (!in) + return false; + + if (v && !ir_instr_op(in, 0, v, false)) { + delete in; + return false; + } + + vec_push(self->m_instr, in); + return true; +} + +bool ir_block_create_if(ir_block *self, lex_ctx_t ctx, ir_value *v, + ir_block *ontrue, ir_block *onfalse) +{ + ir_instr *in; + if (!ir_check_unreachable(self)) + return false; + self->m_final = true; + /*in = new ir_instr(ctx, self, (v->m_vtype == TYPE_STRING ? INSTR_IF_S : INSTR_IF_F));*/ + in = new ir_instr(ctx, self, VINSTR_COND); + if (!in) + return false; + + if (!ir_instr_op(in, 0, v, false)) { + delete in; + return false; + } + + in->m_bops[0] = ontrue; + in->m_bops[1] = onfalse; + + vec_push(self->m_instr, in); + + vec_push(self->m_exits, ontrue); + vec_push(self->m_exits, onfalse); + vec_push(ontrue->m_entries, self); + vec_push(onfalse->m_entries, self); + return true; +} + +bool ir_block_create_jump(ir_block *self, lex_ctx_t ctx, ir_block *to) +{ + ir_instr *in; + if (!ir_check_unreachable(self)) + return false; + self->m_final = true; + in = new ir_instr(ctx, self, VINSTR_JUMP); + if (!in) + return false; + + in->m_bops[0] = to; + vec_push(self->m_instr, in); + + vec_push(self->m_exits, to); + vec_push(to->m_entries, self); + return true; +} + +bool ir_block_create_goto(ir_block *self, lex_ctx_t ctx, ir_block *to) +{ + self->m_owner->m_flags |= IR_FLAG_HAS_GOTO; + return ir_block_create_jump(self, ctx, to); +} + +ir_instr* ir_block_create_phi(ir_block *self, lex_ctx_t ctx, const char *label, qc_type ot) +{ + ir_value *out; + ir_instr *in; + if (!ir_check_unreachable(self)) + return nullptr; + in = new ir_instr(ctx, self, VINSTR_PHI); + if (!in) + return nullptr; + out = new ir_value(self->m_owner, label ? label : "", store_value, ot); + if (!out) { + delete in; + return nullptr; + } + if (!ir_instr_op(in, 0, out, true)) { + delete in; + return nullptr; + } + vec_push(self->m_instr, in); + return in; +} + +ir_value* ir_phi_value(ir_instr *self) +{ + return self->_m_ops[0]; +} + +void ir_phi_add(ir_instr* self, ir_block *b, ir_value *v) +{ + ir_phi_entry_t pe; + + if (!vec_ir_block_find(self->m_owner->m_entries, b, nullptr)) { + // Must not be possible to cause this, otherwise the AST + // is doing something wrong. + irerror(self->m_context, "Invalid entry block for PHI"); + exit(EXIT_FAILURE); + } + + pe.value = v; + pe.from = b; + v->m_reads.push_back(self); + self->m_phi.push_back(pe); +} + +/* call related code */ +ir_instr* ir_block_create_call(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *func, bool noreturn) +{ + ir_value *out; + ir_instr *in; + if (!ir_check_unreachable(self)) + return nullptr; + in = new ir_instr(ctx, self, (noreturn ? VINSTR_NRCALL : INSTR_CALL0)); + if (!in) + return nullptr; + if (noreturn) { + self->m_final = true; + self->m_is_return = true; + } + out = new ir_value(self->m_owner, label ? label : "", (func->m_outtype == TYPE_VOID) ? store_return : store_value, func->m_outtype); + if (!out) { + delete in; + return nullptr; + } + if (!ir_instr_op(in, 0, out, true) || + !ir_instr_op(in, 1, func, false)) + { + delete in; + return nullptr; + } + vec_push(self->m_instr, in); + /* + if (noreturn) { + if (!ir_block_create_return(self, ctx, nullptr)) { + compile_error(ctx, "internal error: failed to generate dummy-return instruction"); + delete in; + return nullptr; + } + } + */ + return in; +} + +ir_value* ir_call_value(ir_instr *self) +{ + return self->_m_ops[0]; +} + +void ir_call_param(ir_instr* self, ir_value *v) +{ + self->m_params.push_back(v); + v->m_reads.push_back(self); +} + +/* binary op related code */ + +ir_value* ir_block_create_binop(ir_block *self, lex_ctx_t ctx, + const char *label, int opcode, + ir_value *left, ir_value *right) +{ + qc_type ot = TYPE_VOID; + switch (opcode) { + case INSTR_ADD_F: + case INSTR_SUB_F: + case INSTR_DIV_F: + case INSTR_MUL_F: + case INSTR_MUL_V: + case INSTR_AND: + case INSTR_OR: +#if 0 + case INSTR_AND_I: + case INSTR_AND_IF: + case INSTR_AND_FI: + case INSTR_OR_I: + case INSTR_OR_IF: + case INSTR_OR_FI: +#endif + case INSTR_BITAND: + case INSTR_BITOR: + case VINSTR_BITXOR: +#if 0 + case INSTR_SUB_S: /* -- offset of string as float */ + case INSTR_MUL_IF: + case INSTR_MUL_FI: + case INSTR_DIV_IF: + case INSTR_DIV_FI: + case INSTR_BITOR_IF: + case INSTR_BITOR_FI: + case INSTR_BITAND_FI: + case INSTR_BITAND_IF: + case INSTR_EQ_I: + case INSTR_NE_I: +#endif + ot = TYPE_FLOAT; + break; +#if 0 + case INSTR_ADD_I: + case INSTR_ADD_IF: + case INSTR_ADD_FI: + case INSTR_SUB_I: + case INSTR_SUB_FI: + case INSTR_SUB_IF: + case INSTR_MUL_I: + case INSTR_DIV_I: + case INSTR_BITAND_I: + case INSTR_BITOR_I: + case INSTR_XOR_I: + case INSTR_RSHIFT_I: + case INSTR_LSHIFT_I: + ot = TYPE_INTEGER; + break; +#endif + case INSTR_ADD_V: + case INSTR_SUB_V: + case INSTR_MUL_VF: + case INSTR_MUL_FV: + case VINSTR_BITAND_V: + case VINSTR_BITOR_V: + case VINSTR_BITXOR_V: + case VINSTR_BITAND_VF: + case VINSTR_BITOR_VF: + case VINSTR_BITXOR_VF: + case VINSTR_CROSS: +#if 0 + case INSTR_DIV_VF: + case INSTR_MUL_IV: + case INSTR_MUL_VI: +#endif + ot = TYPE_VECTOR; + break; +#if 0 + case INSTR_ADD_SF: + ot = TYPE_POINTER; + break; +#endif + /* + * after the following default case, the value of opcode can never + * be 1, 2, 3, 4, 5, 6, 7, 8, 9, 62, 63, 64, 65 + */ + default: + /* ranges: */ + /* boolean operations result in floats */ + + /* + * opcode >= 10 takes true branch opcode is at least 10 + * opcode <= 23 takes false branch opcode is at least 24 + */ + if (opcode >= INSTR_EQ_F && opcode <= INSTR_GT) + ot = TYPE_FLOAT; + + /* + * At condition "opcode <= 23", the value of "opcode" must be + * at least 24. + * At condition "opcode <= 23", the value of "opcode" cannot be + * equal to any of {1, 2, 3, 4, 5, 6, 7, 8, 9, 62, 63, 64, 65}. + * The condition "opcode <= 23" cannot be true. + * + * Thus ot=2 (TYPE_FLOAT) can never be true + */ +#if 0 + else if (opcode >= INSTR_LE && opcode <= INSTR_GT) + ot = TYPE_FLOAT; + else if (opcode >= INSTR_LE_I && opcode <= INSTR_EQ_FI) + ot = TYPE_FLOAT; +#endif + break; + }; + if (ot == TYPE_VOID) { + /* The AST or parser were supposed to check this! */ + return nullptr; + } + + return ir_block_create_general_instr(self, ctx, label, opcode, left, right, ot); +} + +ir_value* ir_block_create_unary(ir_block *self, lex_ctx_t ctx, + const char *label, int opcode, + ir_value *operand) +{ + qc_type ot = TYPE_FLOAT; + switch (opcode) { + case INSTR_NOT_F: + case INSTR_NOT_V: + case INSTR_NOT_S: + case INSTR_NOT_ENT: + case INSTR_NOT_FNC: /* + case INSTR_NOT_I: */ + ot = TYPE_FLOAT; + break; + + /* + * Negation for virtual instructions is emulated with 0-value. Thankfully + * the operand for 0 already exists so we just source it from here. + */ + case VINSTR_NEG_F: + return ir_block_create_general_instr(self, ctx, label, INSTR_SUB_F, nullptr, operand, ot); + case VINSTR_NEG_V: + return ir_block_create_general_instr(self, ctx, label, INSTR_SUB_V, nullptr, operand, TYPE_VECTOR); + + default: + ot = operand->m_vtype; + break; + }; + if (ot == TYPE_VOID) { + /* The AST or parser were supposed to check this! */ + return nullptr; + } + + /* let's use the general instruction creator and pass nullptr for OPB */ + return ir_block_create_general_instr(self, ctx, label, opcode, operand, nullptr, ot); +} + +static ir_value* ir_block_create_general_instr(ir_block *self, lex_ctx_t ctx, const char *label, + int op, ir_value *a, ir_value *b, qc_type outype) +{ + ir_instr *instr; + ir_value *out; + + out = new ir_value(self->m_owner, label ? label : "", store_value, outype); + if (!out) + return nullptr; + + instr = new ir_instr(ctx, self, op); + if (!instr) { + return nullptr; + } + + if (!ir_instr_op(instr, 0, out, true) || + !ir_instr_op(instr, 1, a, false) || + !ir_instr_op(instr, 2, b, false) ) + { + goto on_error; + } + + vec_push(self->m_instr, instr); + + return out; +on_error: + delete instr; + return nullptr; +} + +ir_value* ir_block_create_fieldaddress(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *ent, ir_value *field) +{ + ir_value *v; + + /* Support for various pointer types todo if so desired */ + if (ent->m_vtype != TYPE_ENTITY) + return nullptr; + + if (field->m_vtype != TYPE_FIELD) + return nullptr; + + v = ir_block_create_general_instr(self, ctx, label, INSTR_ADDRESS, ent, field, TYPE_POINTER); + v->m_fieldtype = field->m_fieldtype; + return v; +} + +ir_value* ir_block_create_load_from_ent(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *ent, ir_value *field, qc_type outype) +{ + int op; + if (ent->m_vtype != TYPE_ENTITY) + return nullptr; + + /* at some point we could redirect for TYPE_POINTER... but that could lead to carelessness */ + if (field->m_vtype != TYPE_FIELD) + return nullptr; + + switch (outype) + { + case TYPE_FLOAT: op = INSTR_LOAD_F; break; + case TYPE_VECTOR: op = INSTR_LOAD_V; break; + case TYPE_STRING: op = INSTR_LOAD_S; break; + case TYPE_FIELD: op = INSTR_LOAD_FLD; break; + case TYPE_ENTITY: op = INSTR_LOAD_ENT; break; + case TYPE_FUNCTION: op = INSTR_LOAD_FNC; break; +#if 0 + case TYPE_POINTER: op = INSTR_LOAD_I; break; + case TYPE_INTEGER: op = INSTR_LOAD_I; break; +#endif + default: + irerror(self->m_context, "invalid type for ir_block_create_load_from_ent: %s", type_name[outype]); + return nullptr; + } + + return ir_block_create_general_instr(self, ctx, label, op, ent, field, outype); +} + +/* PHI resolving breaks the SSA, and must thus be the last + * step before life-range calculation. + */ + +static bool ir_block_naive_phi(ir_block *self); +bool ir_function_naive_phi(ir_function *self) +{ + for (auto& b : self->m_blocks) + if (!ir_block_naive_phi(b.get())) + return false; + return true; +} + +static bool ir_block_naive_phi(ir_block *self) +{ + size_t i; + /* FIXME: optionally, create_phi can add the phis + * to a list so we don't need to loop through blocks + * - anyway: "don't optimize YET" + */ + for (i = 0; i < vec_size(self->m_instr); ++i) + { + ir_instr *instr = self->m_instr[i]; + if (instr->m_opcode != VINSTR_PHI) + continue; + + vec_remove(self->m_instr, i, 1); + --i; /* NOTE: i+1 below */ + + for (auto &it : instr->m_phi) { + ir_value *v = it.value; + ir_block *b = it.from; + if (v->m_store == store_value && v->m_reads.size() == 1 && v->m_writes.size() == 1) { + /* replace the value */ + if (!ir_instr_op(v->m_writes[0], 0, instr->_m_ops[0], true)) + return false; + } else { + /* force a move instruction */ + ir_instr *prevjump = vec_last(b->m_instr); + vec_pop(b->m_instr); + b->m_final = false; + instr->_m_ops[0]->m_store = store_global; + if (!ir_block_create_store(b, instr->m_context, instr->_m_ops[0], v)) + return false; + instr->_m_ops[0]->m_store = store_value; + vec_push(b->m_instr, prevjump); + b->m_final = true; + } + } + delete instr; + } + return true; +} + +/*********************************************************************** + *IR Temp allocation code + * Propagating value life ranges by walking through the function backwards + * until no more changes are made. + * In theory this should happen once more than once for every nested loop + * level. + * Though this implementation might run an additional time for if nests. + */ + +/* Enumerate instructions used by value's life-ranges + */ +static void ir_block_enumerate(ir_block *self, size_t *_eid) +{ + size_t i; + size_t eid = *_eid; + for (i = 0; i < vec_size(self->m_instr); ++i) + { + self->m_instr[i]->m_eid = eid++; + } + *_eid = eid; +} + +/* Enumerate blocks and instructions. + * The block-enumeration is unordered! + * We do not really use the block enumreation, however + * the instruction enumeration is important for life-ranges. + */ +void ir_function_enumerate(ir_function *self) +{ + size_t instruction_id = 0; + size_t block_eid = 0; + for (auto& block : self->m_blocks) + { + /* each block now gets an additional "entry" instruction id + * we can use to avoid point-life issues + */ + block->m_entry_id = instruction_id; + block->m_eid = block_eid; + ++instruction_id; + ++block_eid; + + ir_block_enumerate(block.get(), &instruction_id); + } +} + +/* Local-value allocator + * After finishing creating the liferange of all values used in a function + * we can allocate their global-positions. + * This is the counterpart to register-allocation in register machines. + */ +struct function_allocator { + ir_value **locals; + size_t *sizes; + size_t *positions; + bool *unique; +}; + +static bool function_allocator_alloc(function_allocator *alloc, ir_value *var) +{ + ir_value *slot; + size_t vsize = var->size(); + + var->m_code.local = vec_size(alloc->locals); + + slot = new ir_value("reg", store_global, var->m_vtype); + if (!slot) + return false; + + if (!slot->mergeLife(var)) + goto localerror; + + vec_push(alloc->locals, slot); + vec_push(alloc->sizes, vsize); + vec_push(alloc->unique, var->m_unique_life); + + return true; + +localerror: + delete slot; + return false; +} + +static bool ir_function_allocator_assign(ir_function *self, function_allocator *alloc, ir_value *v) +{ + size_t a; + ir_value *slot; + + if (v->m_unique_life) + return function_allocator_alloc(alloc, v); + + for (a = 0; a < vec_size(alloc->locals); ++a) + { + /* if it's reserved for a unique liferange: skip */ + if (alloc->unique[a]) + continue; + + slot = alloc->locals[a]; + + /* never resize parameters + * will be required later when overlapping temps + locals + */ + if (a < vec_size(self->m_params) && + alloc->sizes[a] < v->size()) + { + continue; + } + + if (ir_values_overlap(v, slot)) + continue; + + if (!slot->mergeLife(v)) + return false; + + /* adjust size for this slot */ + if (alloc->sizes[a] < v->size()) + alloc->sizes[a] = v->size(); + + v->m_code.local = a; + return true; + } + if (a >= vec_size(alloc->locals)) { + if (!function_allocator_alloc(alloc, v)) + return false; + } + return true; +} + +bool ir_function_allocate_locals(ir_function *self) +{ + bool retval = true; + size_t pos; + bool opt_gt = OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS); + + function_allocator lockalloc, globalloc; + + if (self->m_locals.empty() && self->m_values.empty()) + return true; + + globalloc.locals = nullptr; + globalloc.sizes = nullptr; + globalloc.positions = nullptr; + globalloc.unique = nullptr; + lockalloc.locals = nullptr; + lockalloc.sizes = nullptr; + lockalloc.positions = nullptr; + lockalloc.unique = nullptr; + + size_t i; + for (i = 0; i < self->m_locals.size(); ++i) + { + ir_value *v = self->m_locals[i].get(); + if ((self->m_flags & IR_FLAG_MASK_NO_LOCAL_TEMPS) || !OPTS_OPTIMIZATION(OPTIM_LOCAL_TEMPS)) { + v->m_locked = true; + v->m_unique_life = true; + } + else if (i >= vec_size(self->m_params)) + break; + else + v->m_locked = true; /* lock parameters locals */ + if (!function_allocator_alloc((v->m_locked || !opt_gt ? &lockalloc : &globalloc), v)) + goto error; + } + for (; i < self->m_locals.size(); ++i) + { + ir_value *v = self->m_locals[i].get(); + if (v->m_life.empty()) + continue; + if (!ir_function_allocator_assign(self, (v->m_locked || !opt_gt ? &lockalloc : &globalloc), v)) + goto error; + } + + /* Allocate a slot for any value that still exists */ + for (i = 0; i < self->m_values.size(); ++i) + { + ir_value *v = self->m_values[i].get(); + + if (v->m_life.empty()) + continue; + + /* CALL optimization: + * If the value is a parameter-temp: 1 write, 1 read from a CALL + * and it's not "locked", write it to the OFS_PARM directly. + */ + if (OPTS_OPTIMIZATION(OPTIM_CALL_STORES) && !v->m_locked && !v->m_unique_life) { + if (v->m_reads.size() == 1 && v->m_writes.size() == 1 && + (v->m_reads[0]->m_opcode == VINSTR_NRCALL || + (v->m_reads[0]->m_opcode >= INSTR_CALL0 && v->m_reads[0]->m_opcode <= INSTR_CALL8) + ) + ) + { + size_t param; + ir_instr *call = v->m_reads[0]; + if (!vec_ir_value_find(call->m_params, v, ¶m)) { + irerror(call->m_context, "internal error: unlocked parameter %s not found", v->m_name.c_str()); + goto error; + } + ++opts_optimizationcount[OPTIM_CALL_STORES]; + v->m_callparam = true; + if (param < 8) + v->setCodeAddress(OFS_PARM0 + 3*param); + else { + size_t nprotos = self->m_owner->m_extparam_protos.size(); + ir_value *ep; + param -= 8; + if (nprotos > param) + ep = self->m_owner->m_extparam_protos[param].get(); + else + { + ep = self->m_owner->generateExtparamProto(); + while (++nprotos <= param) + ep = self->m_owner->generateExtparamProto(); + } + ir_instr_op(v->m_writes[0], 0, ep, true); + call->m_params[param+8] = ep; + } + continue; + } + if (v->m_writes.size() == 1 && v->m_writes[0]->m_opcode == INSTR_CALL0) { + v->m_store = store_return; + if (v->m_members[0]) v->m_members[0]->m_store = store_return; + if (v->m_members[1]) v->m_members[1]->m_store = store_return; + if (v->m_members[2]) v->m_members[2]->m_store = store_return; + ++opts_optimizationcount[OPTIM_CALL_STORES]; + continue; + } + } + + if (!ir_function_allocator_assign(self, (v->m_locked || !opt_gt ? &lockalloc : &globalloc), v)) + goto error; + } + + if (!lockalloc.sizes && !globalloc.sizes) { + goto cleanup; + } + vec_push(lockalloc.positions, 0); + vec_push(globalloc.positions, 0); + + /* Adjust slot positions based on sizes */ + if (lockalloc.sizes) { + pos = (vec_size(lockalloc.sizes) ? lockalloc.positions[0] : 0); + for (i = 1; i < vec_size(lockalloc.sizes); ++i) + { + pos = lockalloc.positions[i-1] + lockalloc.sizes[i-1]; + vec_push(lockalloc.positions, pos); + } + self->m_allocated_locals = pos + vec_last(lockalloc.sizes); + } + if (globalloc.sizes) { + pos = (vec_size(globalloc.sizes) ? globalloc.positions[0] : 0); + for (i = 1; i < vec_size(globalloc.sizes); ++i) + { + pos = globalloc.positions[i-1] + globalloc.sizes[i-1]; + vec_push(globalloc.positions, pos); + } + self->m_globaltemps = pos + vec_last(globalloc.sizes); + } + + /* Locals need to know their new position */ + for (auto& local : self->m_locals) { + if (local->m_locked || !opt_gt) + local->m_code.local = lockalloc.positions[local->m_code.local]; + else + local->m_code.local = globalloc.positions[local->m_code.local]; + } + /* Take over the actual slot positions on values */ + for (auto& value : self->m_values) { + if (value->m_locked || !opt_gt) + value->m_code.local = lockalloc.positions[value->m_code.local]; + else + value->m_code.local = globalloc.positions[value->m_code.local]; + } + + goto cleanup; + +error: + retval = false; +cleanup: + for (i = 0; i < vec_size(lockalloc.locals); ++i) + delete lockalloc.locals[i]; + for (i = 0; i < vec_size(globalloc.locals); ++i) + delete globalloc.locals[i]; + vec_free(globalloc.unique); + vec_free(globalloc.locals); + vec_free(globalloc.sizes); + vec_free(globalloc.positions); + vec_free(lockalloc.unique); + vec_free(lockalloc.locals); + vec_free(lockalloc.sizes); + vec_free(lockalloc.positions); + return retval; +} + +/* Get information about which operand + * is read from, or written to. + */ +static void ir_op_read_write(int op, size_t *read, size_t *write) +{ + switch (op) + { + case VINSTR_JUMP: + case INSTR_GOTO: + *write = 0; + *read = 0; + break; + case INSTR_IF: + case INSTR_IFNOT: +#if 0 + case INSTR_IF_S: + case INSTR_IFNOT_S: +#endif + case INSTR_RETURN: + case VINSTR_COND: + *write = 0; + *read = 1; + break; + case INSTR_STOREP_F: + case INSTR_STOREP_V: + case INSTR_STOREP_S: + case INSTR_STOREP_ENT: + case INSTR_STOREP_FLD: + case INSTR_STOREP_FNC: + *write = 0; + *read = 7; + break; + default: + *write = 1; + *read = 6; + break; + }; +} + +static bool ir_block_living_add_instr(ir_block *self, size_t eid) { + bool changed = false; + for (auto &it : self->m_living) + if (it->setAlive(eid)) + changed = true; + return changed; +} + +static bool ir_block_living_lock(ir_block *self) { + bool changed = false; + for (auto &it : self->m_living) { + if (it->m_locked) + continue; + it->m_locked = true; + changed = true; + } + return changed; +} + +static bool ir_block_life_propagate(ir_block *self, bool *changed) +{ + ir_instr *instr; + ir_value *value; + size_t i, o, p, mem; + // bitmasks which operands are read from or written to + size_t read, write; + + self->m_living.clear(); + + p = vec_size(self->m_exits); + for (i = 0; i < p; ++i) { + ir_block *prev = self->m_exits[i]; + for (auto &it : prev->m_living) + if (!vec_ir_value_find(self->m_living, it, nullptr)) + self->m_living.push_back(it); + } + + i = vec_size(self->m_instr); + while (i) + { --i; + instr = self->m_instr[i]; + + /* See which operands are read and write operands */ + ir_op_read_write(instr->m_opcode, &read, &write); + + /* Go through the 3 main operands + * writes first, then reads + */ + for (o = 0; o < 3; ++o) + { + if (!instr->_m_ops[o]) /* no such operand */ + continue; + + value = instr->_m_ops[o]; + + /* We only care about locals */ + /* we also calculate parameter liferanges so that locals + * can take up parameter slots */ + if (value->m_store != store_value && + value->m_store != store_local && + value->m_store != store_param) + continue; + + /* write operands */ + /* When we write to a local, we consider it "dead" for the + * remaining upper part of the function, since in SSA a value + * can only be written once (== created) + */ + if (write & (1<m_living, value, &idx); + if (!in_living) + { + /* If the value isn't alive it hasn't been read before... */ + /* TODO: See if the warning can be emitted during parsing or AST processing + * otherwise have warning printed here. + * IF printing a warning here: include filecontext_t, + * and make sure it's only printed once + * since this function is run multiple times. + */ + /* con_err( "Value only written %s\n", value->m_name); */ + if (value->setAlive(instr->m_eid)) + *changed = true; + } else { + /* since 'living' won't contain it + * anymore, merge the value, since + * (A) doesn't. + */ + if (value->setAlive(instr->m_eid)) + *changed = true; + // Then remove + self->m_living.erase(self->m_living.begin() + idx); + } + /* Removing a vector removes all members */ + for (mem = 0; mem < 3; ++mem) { + if (value->m_members[mem] && vec_ir_value_find(self->m_living, value->m_members[mem], &idx)) { + if (value->m_members[mem]->setAlive(instr->m_eid)) + *changed = true; + self->m_living.erase(self->m_living.begin() + idx); + } + } + /* Removing the last member removes the vector */ + if (value->m_memberof) { + value = value->m_memberof; + for (mem = 0; mem < 3; ++mem) { + if (value->m_members[mem] && vec_ir_value_find(self->m_living, value->m_members[mem], nullptr)) + break; + } + if (mem == 3 && vec_ir_value_find(self->m_living, value, &idx)) { + if (value->setAlive(instr->m_eid)) + *changed = true; + self->m_living.erase(self->m_living.begin() + idx); + } + } + } + } + + /* These operations need a special case as they can break when using + * same source and destination operand otherwise, as the engine may + * read the source multiple times. */ + if (instr->m_opcode == INSTR_MUL_VF || + instr->m_opcode == VINSTR_BITAND_VF || + instr->m_opcode == VINSTR_BITOR_VF || + instr->m_opcode == VINSTR_BITXOR || + instr->m_opcode == VINSTR_BITXOR_VF || + instr->m_opcode == VINSTR_BITXOR_V || + instr->m_opcode == VINSTR_CROSS) + { + value = instr->_m_ops[2]; + /* the float source will get an additional lifetime */ + if (value->setAlive(instr->m_eid+1)) + *changed = true; + if (value->m_memberof && value->m_memberof->setAlive(instr->m_eid+1)) + *changed = true; + } + + if (instr->m_opcode == INSTR_MUL_FV || + instr->m_opcode == INSTR_LOAD_V || + instr->m_opcode == VINSTR_BITXOR || + instr->m_opcode == VINSTR_BITXOR_VF || + instr->m_opcode == VINSTR_BITXOR_V || + instr->m_opcode == VINSTR_CROSS) + { + value = instr->_m_ops[1]; + /* the float source will get an additional lifetime */ + if (value->setAlive(instr->m_eid+1)) + *changed = true; + if (value->m_memberof && value->m_memberof->setAlive(instr->m_eid+1)) + *changed = true; + } + + for (o = 0; o < 3; ++o) + { + if (!instr->_m_ops[o]) /* no such operand */ + continue; + + value = instr->_m_ops[o]; + + /* We only care about locals */ + /* we also calculate parameter liferanges so that locals + * can take up parameter slots */ + if (value->m_store != store_value && + value->m_store != store_local && + value->m_store != store_param) + continue; + + /* read operands */ + if (read & (1<m_living, value, nullptr)) + self->m_living.push_back(value); + /* reading adds the full vector */ + if (value->m_memberof && !vec_ir_value_find(self->m_living, value->m_memberof, nullptr)) + self->m_living.push_back(value->m_memberof); + for (mem = 0; mem < 3; ++mem) { + if (value->m_members[mem] && !vec_ir_value_find(self->m_living, value->m_members[mem], nullptr)) + self->m_living.push_back(value->m_members[mem]); + } + } + } + /* PHI operands are always read operands */ + for (auto &it : instr->m_phi) { + value = it.value; + if (!vec_ir_value_find(self->m_living, value, nullptr)) + self->m_living.push_back(value); + /* reading adds the full vector */ + if (value->m_memberof && !vec_ir_value_find(self->m_living, value->m_memberof, nullptr)) + self->m_living.push_back(value->m_memberof); + for (mem = 0; mem < 3; ++mem) { + if (value->m_members[mem] && !vec_ir_value_find(self->m_living, value->m_members[mem], nullptr)) + self->m_living.push_back(value->m_members[mem]); + } + } + + /* on a call, all these values must be "locked" */ + if (instr->m_opcode >= INSTR_CALL0 && instr->m_opcode <= INSTR_CALL8) { + if (ir_block_living_lock(self)) + *changed = true; + } + /* call params are read operands too */ + for (auto &it : instr->m_params) { + value = it; + if (!vec_ir_value_find(self->m_living, value, nullptr)) + self->m_living.push_back(value); + /* reading adds the full vector */ + if (value->m_memberof && !vec_ir_value_find(self->m_living, value->m_memberof, nullptr)) + self->m_living.push_back(value->m_memberof); + for (mem = 0; mem < 3; ++mem) { + if (value->m_members[mem] && !vec_ir_value_find(self->m_living, value->m_members[mem], nullptr)) + self->m_living.push_back(value->m_members[mem]); + } + } + + /* (A) */ + if (ir_block_living_add_instr(self, instr->m_eid)) + *changed = true; + } + /* the "entry" instruction ID */ + if (ir_block_living_add_instr(self, self->m_entry_id)) + *changed = true; + + return true; +} + +bool ir_function_calculate_liferanges(ir_function *self) +{ + /* parameters live at 0 */ + for (size_t i = 0; i < vec_size(self->m_params); ++i) + if (!self->m_locals[i].get()->setAlive(0)) + compile_error(self->m_context, "internal error: failed value-life merging"); + + bool changed; + do { + self->m_run_id++; + changed = false; + for (auto i = self->m_blocks.rbegin(); i != self->m_blocks.rend(); ++i) + ir_block_life_propagate(i->get(), &changed); + } while (changed); + + if (self->m_blocks.size()) { + ir_block *block = self->m_blocks[0].get(); + for (auto &it : block->m_living) { + ir_value *v = it; + if (v->m_store != store_local) + continue; + if (v->m_vtype == TYPE_VECTOR) + continue; + self->m_flags |= IR_FLAG_HAS_UNINITIALIZED; + /* find the instruction reading from it */ + size_t s = 0; + for (; s < v->m_reads.size(); ++s) { + if (v->m_reads[s]->m_eid == v->m_life[0].end) + break; + } + if (s < v->m_reads.size()) { + if (irwarning(v->m_context, WARN_USED_UNINITIALIZED, + "variable `%s` may be used uninitialized in this function\n" + " -> %s:%i", + v->m_name.c_str(), + v->m_reads[s]->m_context.file, v->m_reads[s]->m_context.line) + ) + { + return false; + } + continue; + } + if (v->m_memberof) { + ir_value *vec = v->m_memberof; + for (s = 0; s < vec->m_reads.size(); ++s) { + if (vec->m_reads[s]->m_eid == v->m_life[0].end) + break; + } + if (s < vec->m_reads.size()) { + if (irwarning(v->m_context, WARN_USED_UNINITIALIZED, + "variable `%s` may be used uninitialized in this function\n" + " -> %s:%i", + v->m_name.c_str(), + vec->m_reads[s]->m_context.file, vec->m_reads[s]->m_context.line) + ) + { + return false; + } + continue; + } + } + if (irwarning(v->m_context, WARN_USED_UNINITIALIZED, + "variable `%s` may be used uninitialized in this function", v->m_name.c_str())) + { + return false; + } + } + } + return true; +} + +/*********************************************************************** + *IR Code-Generation + * + * Since the IR has the convention of putting 'write' operands + * at the beginning, we have to rotate the operands of instructions + * properly in order to generate valid QCVM code. + * + * Having destinations at a fixed position is more convenient. In QC + * this is *mostly* OPC, but FTE adds at least 2 instructions which + * read from from OPA, and store to OPB rather than OPC. Which is + * partially the reason why the implementation of these instructions + * in darkplaces has been delayed for so long. + * + * Breaking conventions is annoying... + */ +static bool gen_global_field(code_t *code, ir_value *global) +{ + if (global->m_hasvalue) + { + ir_value *fld = global->m_constval.vpointer; + if (!fld) { + irerror(global->m_context, "Invalid field constant with no field: %s", global->m_name.c_str()); + return false; + } + + /* copy the field's value */ + global->setCodeAddress(code->globals.size()); + code->globals.push_back(fld->m_code.fieldaddr); + if (global->m_fieldtype == TYPE_VECTOR) { + code->globals.push_back(fld->m_code.fieldaddr+1); + code->globals.push_back(fld->m_code.fieldaddr+2); + } + } + else + { + global->setCodeAddress(code->globals.size()); + code->globals.push_back(0); + if (global->m_fieldtype == TYPE_VECTOR) { + code->globals.push_back(0); + code->globals.push_back(0); + } + } + if (global->m_code.globaladdr < 0) + return false; + return true; +} + +static bool gen_global_pointer(code_t *code, ir_value *global) +{ + if (global->m_hasvalue) + { + ir_value *target = global->m_constval.vpointer; + if (!target) { + irerror(global->m_context, "Invalid pointer constant: %s", global->m_name.c_str()); + /* nullptr pointers are pointing to the nullptr constant, which also + * sits at address 0, but still has an ir_value for itself. + */ + return false; + } + + /* Here, relocations ARE possible - in fteqcc-enhanced-qc: + * void() foo; <- proto + * void() *fooptr = &foo; + * void() foo = { code } + */ + if (!target->m_code.globaladdr) { + /* FIXME: Check for the constant nullptr ir_value! + * because then code.globaladdr being 0 is valid. + */ + irerror(global->m_context, "FIXME: Relocation support"); + return false; + } + + global->setCodeAddress(code->globals.size()); + code->globals.push_back(target->m_code.globaladdr); + } + else + { + global->setCodeAddress(code->globals.size()); + code->globals.push_back(0); + } + if (global->m_code.globaladdr < 0) + return false; + return true; +} + +static bool gen_blocks_recursive(code_t *code, ir_function *func, ir_block *block) +{ + prog_section_statement_t stmt; + ir_instr *instr; + ir_block *target; + ir_block *ontrue; + ir_block *onfalse; + size_t stidx; + size_t i; + int j; + + block->m_generated = true; + block->m_code_start = code->statements.size(); + for (i = 0; i < vec_size(block->m_instr); ++i) + { + instr = block->m_instr[i]; + + if (instr->m_opcode == VINSTR_PHI) { + irerror(block->m_context, "cannot generate virtual instruction (phi)"); + return false; + } + + if (instr->m_opcode == VINSTR_JUMP) { + target = instr->m_bops[0]; + /* for uncoditional jumps, if the target hasn't been generated + * yet, we generate them right here. + */ + if (!target->m_generated) + return gen_blocks_recursive(code, func, target); + + /* otherwise we generate a jump instruction */ + stmt.opcode = INSTR_GOTO; + stmt.o1.s1 = target->m_code_start - code->statements.size(); + stmt.o2.s1 = 0; + stmt.o3.s1 = 0; + if (stmt.o1.s1 != 1) + code_push_statement(code, &stmt, instr->m_context); + + /* no further instructions can be in this block */ + return true; + } + + if (instr->m_opcode == VINSTR_BITXOR) { + stmt.opcode = INSTR_BITOR; + stmt.o1.s1 = instr->_m_ops[1]->codeAddress(); + stmt.o2.s1 = instr->_m_ops[2]->codeAddress(); + stmt.o3.s1 = instr->_m_ops[0]->codeAddress(); + code_push_statement(code, &stmt, instr->m_context); + stmt.opcode = INSTR_BITAND; + stmt.o1.s1 = instr->_m_ops[1]->codeAddress(); + stmt.o2.s1 = instr->_m_ops[2]->codeAddress(); + stmt.o3.s1 = func->m_owner->m_vinstr_temp[0]->codeAddress(); + code_push_statement(code, &stmt, instr->m_context); + stmt.opcode = INSTR_SUB_F; + stmt.o1.s1 = instr->_m_ops[0]->codeAddress(); + stmt.o2.s1 = func->m_owner->m_vinstr_temp[0]->codeAddress(); + stmt.o3.s1 = instr->_m_ops[0]->codeAddress(); + code_push_statement(code, &stmt, instr->m_context); + + /* instruction generated */ + continue; + } + + if (instr->m_opcode == VINSTR_BITAND_V) { + stmt.opcode = INSTR_BITAND; + stmt.o1.s1 = instr->_m_ops[1]->codeAddress(); + stmt.o2.s1 = instr->_m_ops[2]->codeAddress(); + stmt.o3.s1 = instr->_m_ops[0]->codeAddress(); + code_push_statement(code, &stmt, instr->m_context); + ++stmt.o1.s1; + ++stmt.o2.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->m_context); + ++stmt.o1.s1; + ++stmt.o2.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->m_context); + + /* instruction generated */ + continue; + } + + if (instr->m_opcode == VINSTR_BITOR_V) { + stmt.opcode = INSTR_BITOR; + stmt.o1.s1 = instr->_m_ops[1]->codeAddress(); + stmt.o2.s1 = instr->_m_ops[2]->codeAddress(); + stmt.o3.s1 = instr->_m_ops[0]->codeAddress(); + code_push_statement(code, &stmt, instr->m_context); + ++stmt.o1.s1; + ++stmt.o2.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->m_context); + ++stmt.o1.s1; + ++stmt.o2.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->m_context); + + /* instruction generated */ + continue; + } + + if (instr->m_opcode == VINSTR_BITXOR_V) { + for (j = 0; j < 3; ++j) { + stmt.opcode = INSTR_BITOR; + stmt.o1.s1 = instr->_m_ops[1]->codeAddress() + j; + stmt.o2.s1 = instr->_m_ops[2]->codeAddress() + j; + stmt.o3.s1 = instr->_m_ops[0]->codeAddress() + j; + code_push_statement(code, &stmt, instr->m_context); + stmt.opcode = INSTR_BITAND; + stmt.o1.s1 = instr->_m_ops[1]->codeAddress() + j; + stmt.o2.s1 = instr->_m_ops[2]->codeAddress() + j; + stmt.o3.s1 = func->m_owner->m_vinstr_temp[0]->codeAddress() + j; + code_push_statement(code, &stmt, instr->m_context); + } + stmt.opcode = INSTR_SUB_V; + stmt.o1.s1 = instr->_m_ops[0]->codeAddress(); + stmt.o2.s1 = func->m_owner->m_vinstr_temp[0]->codeAddress(); + stmt.o3.s1 = instr->_m_ops[0]->codeAddress(); + code_push_statement(code, &stmt, instr->m_context); + + /* instruction generated */ + continue; + } + + if (instr->m_opcode == VINSTR_BITAND_VF) { + stmt.opcode = INSTR_BITAND; + stmt.o1.s1 = instr->_m_ops[1]->codeAddress(); + stmt.o2.s1 = instr->_m_ops[2]->codeAddress(); + stmt.o3.s1 = instr->_m_ops[0]->codeAddress(); + code_push_statement(code, &stmt, instr->m_context); + ++stmt.o1.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->m_context); + ++stmt.o1.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->m_context); + + /* instruction generated */ + continue; + } + + if (instr->m_opcode == VINSTR_BITOR_VF) { + stmt.opcode = INSTR_BITOR; + stmt.o1.s1 = instr->_m_ops[1]->codeAddress(); + stmt.o2.s1 = instr->_m_ops[2]->codeAddress(); + stmt.o3.s1 = instr->_m_ops[0]->codeAddress(); + code_push_statement(code, &stmt, instr->m_context); + ++stmt.o1.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->m_context); + ++stmt.o1.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->m_context); + + /* instruction generated */ + continue; + } + + if (instr->m_opcode == VINSTR_BITXOR_VF) { + for (j = 0; j < 3; ++j) { + stmt.opcode = INSTR_BITOR; + stmt.o1.s1 = instr->_m_ops[1]->codeAddress() + j; + stmt.o2.s1 = instr->_m_ops[2]->codeAddress(); + stmt.o3.s1 = instr->_m_ops[0]->codeAddress() + j; + code_push_statement(code, &stmt, instr->m_context); + stmt.opcode = INSTR_BITAND; + stmt.o1.s1 = instr->_m_ops[1]->codeAddress() + j; + stmt.o2.s1 = instr->_m_ops[2]->codeAddress(); + stmt.o3.s1 = func->m_owner->m_vinstr_temp[0]->codeAddress() + j; + code_push_statement(code, &stmt, instr->m_context); + } + stmt.opcode = INSTR_SUB_V; + stmt.o1.s1 = instr->_m_ops[0]->codeAddress(); + stmt.o2.s1 = func->m_owner->m_vinstr_temp[0]->codeAddress(); + stmt.o3.s1 = instr->_m_ops[0]->codeAddress(); + code_push_statement(code, &stmt, instr->m_context); + + /* instruction generated */ + continue; + } + + if (instr->m_opcode == VINSTR_CROSS) { + stmt.opcode = INSTR_MUL_F; + for (j = 0; j < 3; ++j) { + stmt.o1.s1 = instr->_m_ops[1]->codeAddress() + (j + 1) % 3; + stmt.o2.s1 = instr->_m_ops[2]->codeAddress() + (j + 2) % 3; + stmt.o3.s1 = instr->_m_ops[0]->codeAddress() + j; + code_push_statement(code, &stmt, instr->m_context); + stmt.o1.s1 = instr->_m_ops[1]->codeAddress() + (j + 2) % 3; + stmt.o2.s1 = instr->_m_ops[2]->codeAddress() + (j + 1) % 3; + stmt.o3.s1 = func->m_owner->m_vinstr_temp[0]->codeAddress() + j; + code_push_statement(code, &stmt, instr->m_context); + } + stmt.opcode = INSTR_SUB_V; + stmt.o1.s1 = instr->_m_ops[0]->codeAddress(); + stmt.o2.s1 = func->m_owner->m_vinstr_temp[0]->codeAddress(); + stmt.o3.s1 = instr->_m_ops[0]->codeAddress(); + code_push_statement(code, &stmt, instr->m_context); + + /* instruction generated */ + continue; + } + + if (instr->m_opcode == VINSTR_COND) { + ontrue = instr->m_bops[0]; + onfalse = instr->m_bops[1]; + /* TODO: have the AST signal which block should + * come first: eg. optimize IFs without ELSE... + */ + + stmt.o1.u1 = instr->_m_ops[0]->codeAddress(); + stmt.o2.u1 = 0; + stmt.o3.s1 = 0; + + if (ontrue->m_generated) { + stmt.opcode = INSTR_IF; + stmt.o2.s1 = ontrue->m_code_start - code->statements.size(); + if (stmt.o2.s1 != 1) + code_push_statement(code, &stmt, instr->m_context); + } + if (onfalse->m_generated) { + stmt.opcode = INSTR_IFNOT; + stmt.o2.s1 = onfalse->m_code_start - code->statements.size(); + if (stmt.o2.s1 != 1) + code_push_statement(code, &stmt, instr->m_context); + } + if (!ontrue->m_generated) { + if (onfalse->m_generated) + return gen_blocks_recursive(code, func, ontrue); + } + if (!onfalse->m_generated) { + if (ontrue->m_generated) + return gen_blocks_recursive(code, func, onfalse); + } + /* neither ontrue nor onfalse exist */ + stmt.opcode = INSTR_IFNOT; + if (!instr->m_likely) { + /* Honor the likelyhood hint */ + ir_block *tmp = onfalse; + stmt.opcode = INSTR_IF; + onfalse = ontrue; + ontrue = tmp; + } + stidx = code->statements.size(); + code_push_statement(code, &stmt, instr->m_context); + /* on false we jump, so add ontrue-path */ + if (!gen_blocks_recursive(code, func, ontrue)) + return false; + /* fixup the jump address */ + code->statements[stidx].o2.s1 = code->statements.size() - stidx; + /* generate onfalse path */ + if (onfalse->m_generated) { + /* fixup the jump address */ + code->statements[stidx].o2.s1 = onfalse->m_code_start - stidx; + if (stidx+2 == code->statements.size() && code->statements[stidx].o2.s1 == 1) { + code->statements[stidx] = code->statements[stidx+1]; + if (code->statements[stidx].o1.s1 < 0) + code->statements[stidx].o1.s1++; + code_pop_statement(code); + } + stmt.opcode = code->statements.back().opcode; + if (stmt.opcode == INSTR_GOTO || + stmt.opcode == INSTR_IF || + stmt.opcode == INSTR_IFNOT || + stmt.opcode == INSTR_RETURN || + stmt.opcode == INSTR_DONE) + { + /* no use jumping from here */ + return true; + } + /* may have been generated in the previous recursive call */ + stmt.opcode = INSTR_GOTO; + stmt.o1.s1 = onfalse->m_code_start - code->statements.size(); + stmt.o2.s1 = 0; + stmt.o3.s1 = 0; + if (stmt.o1.s1 != 1) + code_push_statement(code, &stmt, instr->m_context); + return true; + } + else if (stidx+2 == code->statements.size() && code->statements[stidx].o2.s1 == 1) { + code->statements[stidx] = code->statements[stidx+1]; + if (code->statements[stidx].o1.s1 < 0) + code->statements[stidx].o1.s1++; + code_pop_statement(code); + } + /* if not, generate now */ + return gen_blocks_recursive(code, func, onfalse); + } + + if ( (instr->m_opcode >= INSTR_CALL0 && instr->m_opcode <= INSTR_CALL8) + || instr->m_opcode == VINSTR_NRCALL) + { + size_t p, first; + ir_value *retvalue; + + first = instr->m_params.size(); + if (first > 8) + first = 8; + for (p = 0; p < first; ++p) + { + ir_value *param = instr->m_params[p]; + if (param->m_callparam) + continue; + + stmt.opcode = INSTR_STORE_F; + stmt.o3.u1 = 0; + + if (param->m_vtype == TYPE_FIELD) + stmt.opcode = field_store_instr[param->m_fieldtype]; + else if (param->m_vtype == TYPE_NIL) + stmt.opcode = INSTR_STORE_V; + else + stmt.opcode = type_store_instr[param->m_vtype]; + stmt.o1.u1 = param->codeAddress(); + stmt.o2.u1 = OFS_PARM0 + 3 * p; + + if (param->m_vtype == TYPE_VECTOR && (param->m_flags & IR_FLAG_SPLIT_VECTOR)) { + /* fetch 3 separate floats */ + stmt.opcode = INSTR_STORE_F; + stmt.o1.u1 = param->m_members[0]->codeAddress(); + code_push_statement(code, &stmt, instr->m_context); + stmt.o2.u1++; + stmt.o1.u1 = param->m_members[1]->codeAddress(); + code_push_statement(code, &stmt, instr->m_context); + stmt.o2.u1++; + stmt.o1.u1 = param->m_members[2]->codeAddress(); + code_push_statement(code, &stmt, instr->m_context); + } + else + code_push_statement(code, &stmt, instr->m_context); + } + /* Now handle extparams */ + first = instr->m_params.size(); + for (; p < first; ++p) + { + ir_builder *ir = func->m_owner; + ir_value *param = instr->m_params[p]; + ir_value *targetparam; + + if (param->m_callparam) + continue; + + if (p-8 >= ir->m_extparams.size()) + ir->generateExtparam(); + + targetparam = ir->m_extparams[p-8]; + + stmt.opcode = INSTR_STORE_F; + stmt.o3.u1 = 0; + + if (param->m_vtype == TYPE_FIELD) + stmt.opcode = field_store_instr[param->m_fieldtype]; + else if (param->m_vtype == TYPE_NIL) + stmt.opcode = INSTR_STORE_V; + else + stmt.opcode = type_store_instr[param->m_vtype]; + stmt.o1.u1 = param->codeAddress(); + stmt.o2.u1 = targetparam->codeAddress(); + if (param->m_vtype == TYPE_VECTOR && (param->m_flags & IR_FLAG_SPLIT_VECTOR)) { + /* fetch 3 separate floats */ + stmt.opcode = INSTR_STORE_F; + stmt.o1.u1 = param->m_members[0]->codeAddress(); + code_push_statement(code, &stmt, instr->m_context); + stmt.o2.u1++; + stmt.o1.u1 = param->m_members[1]->codeAddress(); + code_push_statement(code, &stmt, instr->m_context); + stmt.o2.u1++; + stmt.o1.u1 = param->m_members[2]->codeAddress(); + code_push_statement(code, &stmt, instr->m_context); + } + else + code_push_statement(code, &stmt, instr->m_context); + } + + stmt.opcode = INSTR_CALL0 + instr->m_params.size(); + if (stmt.opcode > INSTR_CALL8) + stmt.opcode = INSTR_CALL8; + stmt.o1.u1 = instr->_m_ops[1]->codeAddress(); + stmt.o2.u1 = 0; + stmt.o3.u1 = 0; + code_push_statement(code, &stmt, instr->m_context); + + retvalue = instr->_m_ops[0]; + if (retvalue && retvalue->m_store != store_return && + (retvalue->m_store == store_global || retvalue->m_life.size())) + { + /* not to be kept in OFS_RETURN */ + if (retvalue->m_vtype == TYPE_FIELD && OPTS_FLAG(ADJUST_VECTOR_FIELDS)) + stmt.opcode = field_store_instr[retvalue->m_fieldtype]; + else + stmt.opcode = type_store_instr[retvalue->m_vtype]; + stmt.o1.u1 = OFS_RETURN; + stmt.o2.u1 = retvalue->codeAddress(); + stmt.o3.u1 = 0; + code_push_statement(code, &stmt, instr->m_context); + } + continue; + } + + if (instr->m_opcode == INSTR_STATE) { + stmt.opcode = instr->m_opcode; + if (instr->_m_ops[0]) + stmt.o1.u1 = instr->_m_ops[0]->codeAddress(); + if (instr->_m_ops[1]) + stmt.o2.u1 = instr->_m_ops[1]->codeAddress(); + stmt.o3.u1 = 0; + code_push_statement(code, &stmt, instr->m_context); + continue; + } + + stmt.opcode = instr->m_opcode; + stmt.o1.u1 = 0; + stmt.o2.u1 = 0; + stmt.o3.u1 = 0; + + /* This is the general order of operands */ + if (instr->_m_ops[0]) + stmt.o3.u1 = instr->_m_ops[0]->codeAddress(); + + if (instr->_m_ops[1]) + stmt.o1.u1 = instr->_m_ops[1]->codeAddress(); + + if (instr->_m_ops[2]) + stmt.o2.u1 = instr->_m_ops[2]->codeAddress(); + + if (stmt.opcode == INSTR_RETURN || stmt.opcode == INSTR_DONE) + { + stmt.o1.u1 = stmt.o3.u1; + stmt.o3.u1 = 0; + } + else if ((stmt.opcode >= INSTR_STORE_F && + stmt.opcode <= INSTR_STORE_FNC) || + (stmt.opcode >= INSTR_STOREP_F && + stmt.opcode <= INSTR_STOREP_FNC)) + { + /* 2-operand instructions with A -> B */ + stmt.o2.u1 = stmt.o3.u1; + stmt.o3.u1 = 0; + + /* tiny optimization, don't output + * STORE a, a + */ + if (stmt.o2.u1 == stmt.o1.u1 && + OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) + { + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + continue; + } + } + code_push_statement(code, &stmt, instr->m_context); + } + return true; +} + +static bool gen_function_code(code_t *code, ir_function *self) +{ + ir_block *block; + prog_section_statement_t stmt, *retst; + + /* Starting from entry point, we generate blocks "as they come" + * for now. Dead blocks will not be translated obviously. + */ + if (self->m_blocks.empty()) { + irerror(self->m_context, "Function '%s' declared without body.", self->m_name.c_str()); + return false; + } + + block = self->m_blocks[0].get(); + if (block->m_generated) + return true; + + if (!gen_blocks_recursive(code, self, block)) { + irerror(self->m_context, "failed to generate blocks for '%s'", self->m_name.c_str()); + return false; + } + + /* code_write and qcvm -disasm need to know that the function ends here */ + retst = &code->statements.back(); + if (OPTS_OPTIMIZATION(OPTIM_VOID_RETURN) && + self->m_outtype == TYPE_VOID && + retst->opcode == INSTR_RETURN && + !retst->o1.u1 && !retst->o2.u1 && !retst->o3.u1) + { + retst->opcode = INSTR_DONE; + ++opts_optimizationcount[OPTIM_VOID_RETURN]; + } else { + lex_ctx_t last; + + stmt.opcode = INSTR_DONE; + stmt.o1.u1 = 0; + stmt.o2.u1 = 0; + stmt.o3.u1 = 0; + last.line = code->linenums.back(); + last.column = code->columnnums.back(); + + code_push_statement(code, &stmt, last); + } + return true; +} + +qcint_t ir_builder::filestring(const char *filename) +{ + /* NOTE: filename pointers are copied, we never strdup them, + * thus we can use pointer-comparison to find the string. + */ + qcint_t str; + + for (size_t i = 0; i != m_filenames.size(); ++i) { + if (!strcmp(m_filenames[i], filename)) + return i; + } + + str = code_genstring(m_code.get(), filename); + m_filenames.push_back(filename); + m_filestrings.push_back(str); + return str; +} + +bool ir_builder::generateGlobalFunction(ir_value *global) +{ + prog_section_function_t fun; + ir_function *irfun; + + size_t i; + + if (!global->m_hasvalue || (!global->m_constval.vfunc)) { + irerror(global->m_context, "Invalid state of function-global: not constant: %s", global->m_name.c_str()); + return false; + } + + irfun = global->m_constval.vfunc; + fun.name = global->m_code.name; + fun.file = filestring(global->m_context.file); + fun.profile = 0; /* always 0 */ + fun.nargs = vec_size(irfun->m_params); + if (fun.nargs > 8) + fun.nargs = 8; + + for (i = 0; i < 8; ++i) { + if ((int32_t)i >= fun.nargs) + fun.argsize[i] = 0; + else + fun.argsize[i] = type_sizeof_[irfun->m_params[i]]; + } + + fun.firstlocal = 0; + fun.locals = irfun->m_allocated_locals; + + if (irfun->m_builtin) + fun.entry = irfun->m_builtin+1; + else { + irfun->m_code_function_def = m_code->functions.size(); + fun.entry = m_code->statements.size(); + } + + m_code->functions.push_back(fun); + return true; +} + +ir_value* ir_builder::generateExtparamProto() +{ + char name[128]; + + util_snprintf(name, sizeof(name), "EXTPARM#%i", (int)(m_extparam_protos.size())); + ir_value *global = new ir_value(name, store_global, TYPE_VECTOR); + m_extparam_protos.emplace_back(global); + + return global; +} + +void ir_builder::generateExtparam() +{ + prog_section_def_t def; + ir_value *global; + + if (m_extparam_protos.size() < m_extparams.size()+1) + global = generateExtparamProto(); + else + global = m_extparam_protos[m_extparams.size()].get(); + + def.name = code_genstring(m_code.get(), global->m_name.c_str()); + def.type = TYPE_VECTOR; + def.offset = m_code->globals.size(); + + m_code->defs.push_back(def); + + global->setCodeAddress(def.offset); + + m_code->globals.push_back(0); + m_code->globals.push_back(0); + m_code->globals.push_back(0); + + m_extparams.emplace_back(global); +} + +static bool gen_function_extparam_copy(code_t *code, ir_function *self) +{ + ir_builder *ir = self->m_owner; + + size_t numparams = vec_size(self->m_params); + if (!numparams) + return true; + + prog_section_statement_t stmt; + stmt.opcode = INSTR_STORE_F; + stmt.o3.s1 = 0; + for (size_t i = 8; i < numparams; ++i) { + size_t ext = i - 8; + if (ext >= ir->m_extparams.size()) + ir->generateExtparam(); + + ir_value *ep = ir->m_extparams[ext]; + + stmt.opcode = type_store_instr[self->m_locals[i]->m_vtype]; + if (self->m_locals[i]->m_vtype == TYPE_FIELD && + self->m_locals[i]->m_fieldtype == TYPE_VECTOR) + { + stmt.opcode = INSTR_STORE_V; + } + stmt.o1.u1 = ep->codeAddress(); + stmt.o2.u1 = self->m_locals[i].get()->codeAddress(); + code_push_statement(code, &stmt, self->m_context); + } + + return true; +} + +static bool gen_function_varargs_copy(code_t *code, ir_function *self) +{ + size_t i, ext, numparams, maxparams; + + ir_builder *ir = self->m_owner; + ir_value *ep; + prog_section_statement_t stmt; + + numparams = vec_size(self->m_params); + if (!numparams) + return true; + + stmt.opcode = INSTR_STORE_V; + stmt.o3.s1 = 0; + maxparams = numparams + self->m_max_varargs; + for (i = numparams; i < maxparams; ++i) { + if (i < 8) { + stmt.o1.u1 = OFS_PARM0 + 3*i; + stmt.o2.u1 = self->m_locals[i].get()->codeAddress(); + code_push_statement(code, &stmt, self->m_context); + continue; + } + ext = i - 8; + while (ext >= ir->m_extparams.size()) + ir->generateExtparam(); + + ep = ir->m_extparams[ext]; + + stmt.o1.u1 = ep->codeAddress(); + stmt.o2.u1 = self->m_locals[i].get()->codeAddress(); + code_push_statement(code, &stmt, self->m_context); + } + + return true; +} + +bool ir_builder::generateFunctionLocals(ir_value *global) +{ + prog_section_function_t *def; + ir_function *irfun; + uint32_t firstlocal, firstglobal; + + irfun = global->m_constval.vfunc; + def = &m_code->functions[0] + irfun->m_code_function_def; + + if (OPTS_OPTION_BOOL(OPTION_G) || + !OPTS_OPTIMIZATION(OPTIM_OVERLAP_LOCALS) || + (irfun->m_flags & IR_FLAG_MASK_NO_OVERLAP)) + { + firstlocal = def->firstlocal = m_code->globals.size(); + } else { + firstlocal = def->firstlocal = m_first_common_local; + ++opts_optimizationcount[OPTIM_OVERLAP_LOCALS]; + } + + firstglobal = (OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS) ? m_first_common_globaltemp : firstlocal); + + for (size_t i = m_code->globals.size(); i < firstlocal + irfun->m_allocated_locals; ++i) + m_code->globals.push_back(0); + + for (auto& lp : irfun->m_locals) { + ir_value *v = lp.get(); + if (v->m_locked || !OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS)) { + v->setCodeAddress(firstlocal + v->m_code.local); + if (!generateGlobal(v, true)) { + irerror(v->m_context, "failed to generate local %s", v->m_name.c_str()); + return false; + } + } + else + v->setCodeAddress(firstglobal + v->m_code.local); + } + for (auto& vp : irfun->m_values) { + ir_value *v = vp.get(); + if (v->m_callparam) + continue; + if (v->m_locked) + v->setCodeAddress(firstlocal + v->m_code.local); + else + v->setCodeAddress(firstglobal + v->m_code.local); + } + return true; +} + +bool ir_builder::generateGlobalFunctionCode(ir_value *global) +{ + prog_section_function_t *fundef; + ir_function *irfun; + + irfun = global->m_constval.vfunc; + if (!irfun) { + if (global->m_cvq == CV_NONE) { + if (irwarning(global->m_context, WARN_IMPLICIT_FUNCTION_POINTER, + "function `%s` has no body and in QC implicitly becomes a function-pointer", + global->m_name.c_str())) + { + /* Not bailing out just now. If this happens a lot you don't want to have + * to rerun gmqcc for each such function. + */ + + /* return false; */ + } + } + /* this was a function pointer, don't generate code for those */ + return true; + } + + if (irfun->m_builtin) + return true; + + /* + * If there is no definition and the thing is eraseable, we can ignore + * outputting the function to begin with. + */ + if (global->m_flags & IR_FLAG_ERASABLE && irfun->m_code_function_def < 0) { + return true; + } + + if (irfun->m_code_function_def < 0) { + irerror(irfun->m_context, "`%s`: IR global wasn't generated, failed to access function-def", irfun->m_name.c_str()); + return false; + } + fundef = &m_code->functions[irfun->m_code_function_def]; + + fundef->entry = m_code->statements.size(); + if (!generateFunctionLocals(global)) { + irerror(irfun->m_context, "Failed to generate locals for function %s", irfun->m_name.c_str()); + return false; + } + if (!gen_function_extparam_copy(m_code.get(), irfun)) { + irerror(irfun->m_context, "Failed to generate extparam-copy code for function %s", irfun->m_name.c_str()); + return false; + } + if (irfun->m_max_varargs && !gen_function_varargs_copy(m_code.get(), irfun)) { + irerror(irfun->m_context, "Failed to generate vararg-copy code for function %s", irfun->m_name.c_str()); + return false; + } + if (!gen_function_code(m_code.get(), irfun)) { + irerror(irfun->m_context, "Failed to generate code for function %s", irfun->m_name.c_str()); + return false; + } + return true; +} + +static void gen_vector_defs(code_t *code, prog_section_def_t def, const char *name) +{ + char *component; + size_t len, i; + + if (!name || name[0] == '#' || OPTS_FLAG(SINGLE_VECTOR_DEFS)) + return; + + def.type = TYPE_FLOAT; + + len = strlen(name); + + component = (char*)mem_a(len+3); + memcpy(component, name, len); + len += 2; + component[len-0] = 0; + component[len-2] = '_'; + + component[len-1] = 'x'; + + for (i = 0; i < 3; ++i) { + def.name = code_genstring(code, component); + code->defs.push_back(def); + def.offset++; + component[len-1]++; + } + + mem_d(component); +} + +static void gen_vector_fields(code_t *code, prog_section_field_t fld, const char *name) +{ + char *component; + size_t len, i; + + if (!name || OPTS_FLAG(SINGLE_VECTOR_DEFS)) + return; + + fld.type = TYPE_FLOAT; + + len = strlen(name); + + component = (char*)mem_a(len+3); + memcpy(component, name, len); + len += 2; + component[len-0] = 0; + component[len-2] = '_'; + + component[len-1] = 'x'; + + for (i = 0; i < 3; ++i) { + fld.name = code_genstring(code, component); + code->fields.push_back(fld); + fld.offset++; + component[len-1]++; + } + + mem_d(component); +} + +bool ir_builder::generateGlobal(ir_value *global, bool islocal) +{ + size_t i; + int32_t *iptr; + prog_section_def_t def; + bool pushdef = opts.optimizeoff; + + /* we don't generate split-vectors */ + if (global->m_vtype == TYPE_VECTOR && (global->m_flags & IR_FLAG_SPLIT_VECTOR)) + return true; + + def.type = global->m_vtype; + def.offset = m_code->globals.size(); + def.name = 0; + if (OPTS_OPTION_BOOL(OPTION_G) || !islocal) + { + pushdef = true; + + /* + * if we're eraseable and the function isn't referenced ignore outputting + * the function. + */ + if (global->m_flags & IR_FLAG_ERASABLE && global->m_reads.empty()) { + return true; + } + + if (OPTS_OPTIMIZATION(OPTIM_STRIP_CONSTANT_NAMES) && + !(global->m_flags & IR_FLAG_INCLUDE_DEF) && + (global->m_name[0] == '#' || global->m_cvq == CV_CONST)) + { + pushdef = false; + } + + if (pushdef) { + if (global->m_name[0] == '#') { + if (!m_str_immediate) + m_str_immediate = code_genstring(m_code.get(), "IMMEDIATE"); + def.name = global->m_code.name = m_str_immediate; + } + else + def.name = global->m_code.name = code_genstring(m_code.get(), global->m_name.c_str()); + } + else + def.name = 0; + if (islocal) { + def.offset = global->codeAddress(); + m_code->defs.push_back(def); + if (global->m_vtype == TYPE_VECTOR) + gen_vector_defs(m_code.get(), def, global->m_name.c_str()); + else if (global->m_vtype == TYPE_FIELD && global->m_fieldtype == TYPE_VECTOR) + gen_vector_defs(m_code.get(), def, global->m_name.c_str()); + return true; + } + } + if (islocal) + return true; + + switch (global->m_vtype) + { + case TYPE_VOID: + if (0 == global->m_name.compare("end_sys_globals")) { + // TODO: remember this point... all the defs before this one + // should be checksummed and added to progdefs.h when we generate it. + } + else if (0 == global->m_name.compare("end_sys_fields")) { + // TODO: same as above but for entity-fields rather than globsl + } + else if(irwarning(global->m_context, WARN_VOID_VARIABLES, "unrecognized variable of type void `%s`", + global->m_name.c_str())) + { + /* Not bailing out */ + /* return false; */ + } + /* I'd argue setting it to 0 is sufficient, but maybe some depend on knowing how far + * the system fields actually go? Though the engine knows this anyway... + * Maybe this could be an -foption + * fteqcc creates data for end_sys_* - of size 1, so let's do the same + */ + global->setCodeAddress(m_code->globals.size()); + m_code->globals.push_back(0); + /* Add the def */ + if (pushdef) + m_code->defs.push_back(def); + return true; + case TYPE_POINTER: + if (pushdef) + m_code->defs.push_back(def); + return gen_global_pointer(m_code.get(), global); + case TYPE_FIELD: + if (pushdef) { + m_code->defs.push_back(def); + if (global->m_fieldtype == TYPE_VECTOR) + gen_vector_defs(m_code.get(), def, global->m_name.c_str()); + } + return gen_global_field(m_code.get(), global); + case TYPE_ENTITY: + /* fall through */ + case TYPE_FLOAT: + { + global->setCodeAddress(m_code->globals.size()); + if (global->m_hasvalue) { + if (global->m_cvq == CV_CONST && global->m_reads.empty()) + return true; + iptr = (int32_t*)&global->m_constval.ivec[0]; + m_code->globals.push_back(*iptr); + } else { + m_code->globals.push_back(0); + } + if (!islocal && global->m_cvq != CV_CONST) + def.type |= DEF_SAVEGLOBAL; + if (pushdef) + m_code->defs.push_back(def); + + return global->m_code.globaladdr >= 0; + } + case TYPE_STRING: + { + global->setCodeAddress(m_code->globals.size()); + if (global->m_hasvalue) { + if (global->m_cvq == CV_CONST && global->m_reads.empty()) + return true; + uint32_t load = code_genstring(m_code.get(), global->m_constval.vstring); + m_code->globals.push_back(load); + } else { + m_code->globals.push_back(0); + } + if (!islocal && global->m_cvq != CV_CONST) + def.type |= DEF_SAVEGLOBAL; + if (pushdef) + m_code->defs.push_back(def); + return global->m_code.globaladdr >= 0; + } + case TYPE_VECTOR: + { + size_t d; + global->setCodeAddress(m_code->globals.size()); + if (global->m_hasvalue) { + iptr = (int32_t*)&global->m_constval.ivec[0]; + m_code->globals.push_back(iptr[0]); + if (global->m_code.globaladdr < 0) + return false; + for (d = 1; d < type_sizeof_[global->m_vtype]; ++d) { + m_code->globals.push_back(iptr[d]); + } + } else { + m_code->globals.push_back(0); + if (global->m_code.globaladdr < 0) + return false; + for (d = 1; d < type_sizeof_[global->m_vtype]; ++d) { + m_code->globals.push_back(0); + } + } + if (!islocal && global->m_cvq != CV_CONST) + def.type |= DEF_SAVEGLOBAL; + + if (pushdef) { + m_code->defs.push_back(def); + def.type &= ~DEF_SAVEGLOBAL; + gen_vector_defs(m_code.get(), def, global->m_name.c_str()); + } + return global->m_code.globaladdr >= 0; + } + case TYPE_FUNCTION: + global->setCodeAddress(m_code->globals.size()); + if (!global->m_hasvalue) { + m_code->globals.push_back(0); + if (global->m_code.globaladdr < 0) + return false; + } else { + m_code->globals.push_back(m_code->functions.size()); + if (!generateGlobalFunction(global)) + return false; + } + if (!islocal && global->m_cvq != CV_CONST) + def.type |= DEF_SAVEGLOBAL; + if (pushdef) + m_code->defs.push_back(def); + return true; + case TYPE_VARIANT: + /* assume biggest type */ + global->setCodeAddress(m_code->globals.size()); + m_code->globals.push_back(0); + for (i = 1; i < type_sizeof_[TYPE_VARIANT]; ++i) + m_code->globals.push_back(0); + return true; + default: + /* refuse to create 'void' type or any other fancy business. */ + irerror(global->m_context, "Invalid type for global variable `%s`: %s", + global->m_name.c_str(), type_name[global->m_vtype]); + return false; + } +} + +static GMQCC_INLINE void ir_builder_prepare_field(code_t *code, ir_value *field) +{ + field->m_code.fieldaddr = code_alloc_field(code, type_sizeof_[field->m_fieldtype]); +} + +static bool ir_builder_gen_field(ir_builder *self, ir_value *field) +{ + prog_section_def_t def; + prog_section_field_t fld; + + (void)self; + + def.type = (uint16_t)field->m_vtype; + def.offset = (uint16_t)self->m_code->globals.size(); + + /* create a global named the same as the field */ + if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) { + /* in our standard, the global gets a dot prefix */ + size_t len = field->m_name.length(); + char name[1024]; + + /* we really don't want to have to allocate this, and 1024 + * bytes is more than enough for a variable/field name + */ + if (len+2 >= sizeof(name)) { + irerror(field->m_context, "invalid field name size: %u", (unsigned int)len); + return false; + } + + name[0] = '.'; + memcpy(name+1, field->m_name.c_str(), len); // no strncpy - we used strlen above + name[len+1] = 0; + + def.name = code_genstring(self->m_code.get(), name); + fld.name = def.name + 1; /* we reuse that string table entry */ + } else { + /* in plain QC, there cannot be a global with the same name, + * and so we also name the global the same. + * FIXME: fteqcc should create a global as well + * check if it actually uses the same name. Probably does + */ + def.name = code_genstring(self->m_code.get(), field->m_name.c_str()); + fld.name = def.name; + } + + field->m_code.name = def.name; + + self->m_code->defs.push_back(def); + + fld.type = field->m_fieldtype; + + if (fld.type == TYPE_VOID) { + irerror(field->m_context, "field is missing a type: %s - don't know its size", field->m_name.c_str()); + return false; + } + + fld.offset = field->m_code.fieldaddr; + + self->m_code->fields.push_back(fld); + + field->setCodeAddress(self->m_code->globals.size()); + self->m_code->globals.push_back(fld.offset); + if (fld.type == TYPE_VECTOR) { + self->m_code->globals.push_back(fld.offset+1); + self->m_code->globals.push_back(fld.offset+2); + } + + if (field->m_fieldtype == TYPE_VECTOR) { + gen_vector_defs (self->m_code.get(), def, field->m_name.c_str()); + gen_vector_fields(self->m_code.get(), fld, field->m_name.c_str()); + } + + return field->m_code.globaladdr >= 0; +} + +static void ir_builder_collect_reusables(ir_builder *builder) { + std::vector reusables; + + for (auto& gp : builder->m_globals) { + ir_value *value = gp.get(); + if (value->m_vtype != TYPE_FLOAT || !value->m_hasvalue) + continue; + if (value->m_cvq == CV_CONST || (value->m_name.length() >= 1 && value->m_name[0] == '#')) + reusables.emplace_back(value); + } + builder->m_const_floats = move(reusables); +} + +static void ir_builder_split_vector(ir_builder *self, ir_value *vec) { + ir_value* found[3] = { nullptr, nullptr, nullptr }; + + // must not be written to + if (vec->m_writes.size()) + return; + // must not be trying to access individual members + if (vec->m_members[0] || vec->m_members[1] || vec->m_members[2]) + return; + // should be actually used otherwise it won't be generated anyway + if (vec->m_reads.empty()) + return; + //size_t count = vec->m_reads.size(); + //if (!count) + // return; + + // may only be used directly as function parameters, so if we find some other instruction cancel + for (ir_instr *user : vec->m_reads) { + // we only split vectors if they're used directly as parameter to a call only! + if ((user->m_opcode < INSTR_CALL0 || user->m_opcode > INSTR_CALL8) && user->m_opcode != VINSTR_NRCALL) + return; + } + + vec->m_flags |= IR_FLAG_SPLIT_VECTOR; + + // find existing floats making up the split + for (ir_value *c : self->m_const_floats) { + if (!found[0] && c->m_constval.vfloat == vec->m_constval.vvec.x) + found[0] = c; + if (!found[1] && c->m_constval.vfloat == vec->m_constval.vvec.y) + found[1] = c; + if (!found[2] && c->m_constval.vfloat == vec->m_constval.vvec.z) + found[2] = c; + if (found[0] && found[1] && found[2]) + break; + } + + // generate floats for not yet found components + if (!found[0]) + found[0] = self->literalFloat(vec->m_constval.vvec.x, true); + if (!found[1]) { + if (vec->m_constval.vvec.y == vec->m_constval.vvec.x) + found[1] = found[0]; + else + found[1] = self->literalFloat(vec->m_constval.vvec.y, true); + } + if (!found[2]) { + if (vec->m_constval.vvec.z == vec->m_constval.vvec.x) + found[2] = found[0]; + else if (vec->m_constval.vvec.z == vec->m_constval.vvec.y) + found[2] = found[1]; + else + found[2] = self->literalFloat(vec->m_constval.vvec.z, true); + } + + // the .members array should be safe to use here + vec->m_members[0] = found[0]; + vec->m_members[1] = found[1]; + vec->m_members[2] = found[2]; + + // register the readers for these floats + found[0]->m_reads.insert(found[0]->m_reads.end(), vec->m_reads.begin(), vec->m_reads.end()); + found[1]->m_reads.insert(found[1]->m_reads.end(), vec->m_reads.begin(), vec->m_reads.end()); + found[2]->m_reads.insert(found[2]->m_reads.end(), vec->m_reads.begin(), vec->m_reads.end()); +} + +static void ir_builder_split_vectors(ir_builder *self) { + // member values may be added to self->m_globals during this operation, but + // no new vectors will be added, we need to iterate via an index as + // c++ iterators would be invalidated + const size_t count = self->m_globals.size(); + for (size_t i = 0; i != count; ++i) { + ir_value *v = self->m_globals[i].get(); + if (v->m_vtype != TYPE_VECTOR || !v->m_name.length() || v->m_name[0] != '#') + continue; + ir_builder_split_vector(self, v); + } +} + +bool ir_builder::generate(const char *filename) +{ + prog_section_statement_t stmt; + char *lnofile = nullptr; + + if (OPTS_FLAG(SPLIT_VECTOR_PARAMETERS)) { + ir_builder_collect_reusables(this); + if (!m_const_floats.empty()) + ir_builder_split_vectors(this); + } + + for (auto& fp : m_fields) + ir_builder_prepare_field(m_code.get(), fp.get()); + + for (auto& gp : m_globals) { + ir_value *global = gp.get(); + if (!generateGlobal(global, false)) { + return false; + } + if (global->m_vtype == TYPE_FUNCTION) { + ir_function *func = global->m_constval.vfunc; + if (func && m_max_locals < func->m_allocated_locals && + !(func->m_flags & IR_FLAG_MASK_NO_OVERLAP)) + { + m_max_locals = func->m_allocated_locals; + } + if (func && m_max_globaltemps < func->m_globaltemps) + m_max_globaltemps = func->m_globaltemps; + } + } + + for (auto& fp : m_fields) { + if (!ir_builder_gen_field(this, fp.get())) + return false; + } + + // generate nil + m_nil->setCodeAddress(m_code->globals.size()); + m_code->globals.push_back(0); + m_code->globals.push_back(0); + m_code->globals.push_back(0); + + // generate virtual-instruction temps + for (size_t i = 0; i < IR_MAX_VINSTR_TEMPS; ++i) { + m_vinstr_temp[i]->setCodeAddress(m_code->globals.size()); + m_code->globals.push_back(0); + m_code->globals.push_back(0); + m_code->globals.push_back(0); + } + + // generate global temps + m_first_common_globaltemp = m_code->globals.size(); + m_code->globals.insert(m_code->globals.end(), m_max_globaltemps, 0); + // FIXME:DELME: + //for (size_t i = 0; i < m_max_globaltemps; ++i) { + // m_code->globals.push_back(0); + //} + // generate common locals + m_first_common_local = m_code->globals.size(); + m_code->globals.insert(m_code->globals.end(), m_max_locals, 0); + // FIXME:DELME: + //for (i = 0; i < m_max_locals; ++i) { + // m_code->globals.push_back(0); + //} + + // generate function code + + for (auto& gp : m_globals) { + ir_value *global = gp.get(); + if (global->m_vtype == TYPE_FUNCTION) { + if (!this->generateGlobalFunctionCode(global)) + return false; + } + } + + if (m_code->globals.size() >= 65536) { + irerror(m_globals.back()->m_context, + "This progs file would require more globals than the metadata can handle (%zu). Bailing out.", + m_code->globals.size()); + return false; + } + + /* DP errors if the last instruction is not an INSTR_DONE. */ + if (m_code->statements.back().opcode != INSTR_DONE) + { + lex_ctx_t last; + + stmt.opcode = INSTR_DONE; + stmt.o1.u1 = 0; + stmt.o2.u1 = 0; + stmt.o3.u1 = 0; + last.line = m_code->linenums.back(); + last.column = m_code->columnnums.back(); + + code_push_statement(m_code.get(), &stmt, last); + } + + if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) + return true; + + if (m_code->statements.size() != m_code->linenums.size()) { + con_err("Linecounter wrong: %lu != %lu\n", + m_code->statements.size(), + m_code->linenums.size()); + } else if (OPTS_FLAG(LNO)) { + char *dot; + size_t filelen = strlen(filename); + + memcpy(vec_add(lnofile, filelen+1), filename, filelen+1); + dot = strrchr(lnofile, '.'); + if (!dot) { + vec_pop(lnofile); + } else { + vec_shrinkto(lnofile, dot - lnofile); + } + memcpy(vec_add(lnofile, 5), ".lno", 5); + } + + if (!code_write(m_code.get(), filename, lnofile)) { + vec_free(lnofile); + return false; + } + + vec_free(lnofile); + return true; +} + +/*********************************************************************** + *IR DEBUG Dump functions... + */ + +#define IND_BUFSZ 1024 + +static const char *qc_opname(int op) +{ + if (op < 0) return ""; + if (op < VINSTR_END) + return util_instr_str[op]; + switch (op) { + case VINSTR_END: return "END"; + case VINSTR_PHI: return "PHI"; + case VINSTR_JUMP: return "JUMP"; + case VINSTR_COND: return "COND"; + case VINSTR_BITXOR: return "BITXOR"; + case VINSTR_BITAND_V: return "BITAND_V"; + case VINSTR_BITOR_V: return "BITOR_V"; + case VINSTR_BITXOR_V: return "BITXOR_V"; + case VINSTR_BITAND_VF: return "BITAND_VF"; + case VINSTR_BITOR_VF: return "BITOR_VF"; + case VINSTR_BITXOR_VF: return "BITXOR_VF"; + case VINSTR_CROSS: return "CROSS"; + case VINSTR_NEG_F: return "NEG_F"; + case VINSTR_NEG_V: return "NEG_V"; + default: return ""; + } +} + +void ir_builder::dump(int (*oprintf)(const char*, ...)) const +{ + size_t i; + char indent[IND_BUFSZ]; + indent[0] = '\t'; + indent[1] = 0; + + oprintf("module %s\n", m_name.c_str()); + for (i = 0; i < m_globals.size(); ++i) + { + oprintf("global "); + if (m_globals[i]->m_hasvalue) + oprintf("%s = ", m_globals[i]->m_name.c_str()); + m_globals[i].get()->dump(oprintf); + oprintf("\n"); + } + for (i = 0; i < m_functions.size(); ++i) + ir_function_dump(m_functions[i].get(), indent, oprintf); + oprintf("endmodule %s\n", m_name.c_str()); +} + +static const char *storenames[] = { + "[global]", "[local]", "[param]", "[value]", "[return]" +}; + +void ir_function_dump(ir_function *f, char *ind, + int (*oprintf)(const char*, ...)) +{ + size_t i; + if (f->m_builtin != 0) { + oprintf("%sfunction %s = builtin %i\n", ind, f->m_name.c_str(), -f->m_builtin); + return; + } + oprintf("%sfunction %s\n", ind, f->m_name.c_str()); + util_strncat(ind, "\t", IND_BUFSZ-1); + if (f->m_locals.size()) + { + oprintf("%s%i locals:\n", ind, (int)f->m_locals.size()); + for (i = 0; i < f->m_locals.size(); ++i) { + oprintf("%s\t", ind); + f->m_locals[i].get()->dump(oprintf); + oprintf("\n"); + } + } + oprintf("%sliferanges:\n", ind); + for (i = 0; i < f->m_locals.size(); ++i) { + const char *attr = ""; + size_t l, m; + ir_value *v = f->m_locals[i].get(); + if (v->m_unique_life && v->m_locked) + attr = "unique,locked "; + else if (v->m_unique_life) + attr = "unique "; + else if (v->m_locked) + attr = "locked "; + oprintf("%s\t%s: %s %s %s%s@%i ", ind, v->m_name.c_str(), type_name[v->m_vtype], + storenames[v->m_store], + attr, (v->m_callparam ? "callparam " : ""), + (int)v->m_code.local); + if (v->m_life.empty()) + oprintf("[null]"); + for (l = 0; l < v->m_life.size(); ++l) { + oprintf("[%i,%i] ", v->m_life[l].start, v->m_life[l].end); + } + oprintf("\n"); + for (m = 0; m < 3; ++m) { + ir_value *vm = v->m_members[m]; + if (!vm) + continue; + oprintf("%s\t%s: @%i ", ind, vm->m_name.c_str(), (int)vm->m_code.local); + for (l = 0; l < vm->m_life.size(); ++l) { + oprintf("[%i,%i] ", vm->m_life[l].start, vm->m_life[l].end); + } + oprintf("\n"); + } + } + for (i = 0; i < f->m_values.size(); ++i) { + const char *attr = ""; + size_t l, m; + ir_value *v = f->m_values[i].get(); + if (v->m_unique_life && v->m_locked) + attr = "unique,locked "; + else if (v->m_unique_life) + attr = "unique "; + else if (v->m_locked) + attr = "locked "; + oprintf("%s\t%s: %s %s %s%s@%i ", ind, v->m_name.c_str(), type_name[v->m_vtype], + storenames[v->m_store], + attr, (v->m_callparam ? "callparam " : ""), + (int)v->m_code.local); + if (v->m_life.empty()) + oprintf("[null]"); + for (l = 0; l < v->m_life.size(); ++l) { + oprintf("[%i,%i] ", v->m_life[l].start, v->m_life[l].end); + } + oprintf("\n"); + for (m = 0; m < 3; ++m) { + ir_value *vm = v->m_members[m]; + if (!vm) + continue; + if (vm->m_unique_life && vm->m_locked) + attr = "unique,locked "; + else if (vm->m_unique_life) + attr = "unique "; + else if (vm->m_locked) + attr = "locked "; + oprintf("%s\t%s: %s@%i ", ind, vm->m_name.c_str(), attr, (int)vm->m_code.local); + for (l = 0; l < vm->m_life.size(); ++l) { + oprintf("[%i,%i] ", vm->m_life[l].start, vm->m_life[l].end); + } + oprintf("\n"); + } + } + if (f->m_blocks.size()) + { + oprintf("%slife passes: %i\n", ind, (int)f->m_run_id); + for (i = 0; i < f->m_blocks.size(); ++i) { + ir_block_dump(f->m_blocks[i].get(), ind, oprintf); + } + + } + ind[strlen(ind)-1] = 0; + oprintf("%sendfunction %s\n", ind, f->m_name.c_str()); +} + +void ir_block_dump(ir_block* b, char *ind, + int (*oprintf)(const char*, ...)) +{ + size_t i; + oprintf("%s:%s\n", ind, b->m_label.c_str()); + util_strncat(ind, "\t", IND_BUFSZ-1); + + if (b->m_instr && b->m_instr[0]) + oprintf("%s (%i) [entry]\n", ind, (int)(b->m_instr[0]->m_eid-1)); + for (i = 0; i < vec_size(b->m_instr); ++i) + ir_instr_dump(b->m_instr[i], ind, oprintf); + ind[strlen(ind)-1] = 0; +} + +static void dump_phi(ir_instr *in, int (*oprintf)(const char*, ...)) +{ + oprintf("%s <- phi ", in->_m_ops[0]->m_name.c_str()); + for (auto &it : in->m_phi) { + oprintf("([%s] : %s) ", it.from->m_label.c_str(), + it.value->m_name.c_str()); + } + oprintf("\n"); +} + +void ir_instr_dump(ir_instr *in, char *ind, + int (*oprintf)(const char*, ...)) +{ + size_t i; + const char *comma = nullptr; + + oprintf("%s (%i) ", ind, (int)in->m_eid); + + if (in->m_opcode == VINSTR_PHI) { + dump_phi(in, oprintf); + return; + } + + util_strncat(ind, "\t", IND_BUFSZ-1); + + if (in->_m_ops[0] && (in->_m_ops[1] || in->_m_ops[2])) { + in->_m_ops[0]->dump(oprintf); + if (in->_m_ops[1] || in->_m_ops[2]) + oprintf(" <- "); + } + if (in->m_opcode == INSTR_CALL0 || in->m_opcode == VINSTR_NRCALL) { + oprintf("CALL%i\t", in->m_params.size()); + } else + oprintf("%s\t", qc_opname(in->m_opcode)); + + if (in->_m_ops[0] && !(in->_m_ops[1] || in->_m_ops[2])) { + in->_m_ops[0]->dump(oprintf); + comma = ",\t"; + } + else + { + for (i = 1; i != 3; ++i) { + if (in->_m_ops[i]) { + if (comma) + oprintf(comma); + in->_m_ops[i]->dump(oprintf); + comma = ",\t"; + } + } + } + if (in->m_bops[0]) { + if (comma) + oprintf(comma); + oprintf("[%s]", in->m_bops[0]->m_label.c_str()); + comma = ",\t"; + } + if (in->m_bops[1]) + oprintf("%s[%s]", comma, in->m_bops[1]->m_label.c_str()); + if (in->m_params.size()) { + oprintf("\tparams: "); + for (auto &it : in->m_params) + oprintf("%s, ", it->m_name.c_str()); + } + oprintf("\n"); + ind[strlen(ind)-1] = 0; +} + +static void ir_value_dump_string(const char *str, int (*oprintf)(const char*, ...)) +{ + oprintf("\""); + for (; *str; ++str) { + switch (*str) { + case '\n': oprintf("\\n"); break; + case '\r': oprintf("\\r"); break; + case '\t': oprintf("\\t"); break; + case '\v': oprintf("\\v"); break; + case '\f': oprintf("\\f"); break; + case '\b': oprintf("\\b"); break; + case '\a': oprintf("\\a"); break; + case '\\': oprintf("\\\\"); break; + case '"': oprintf("\\\""); break; + default: oprintf("%c", *str); break; + } + } + oprintf("\""); +} + +void ir_value::dump(int (*oprintf)(const char*, ...)) const +{ + if (m_hasvalue) { + switch (m_vtype) { + default: + case TYPE_VOID: + oprintf("(void)"); + break; + case TYPE_FUNCTION: + oprintf("fn:%s", m_name.c_str()); + break; + case TYPE_FLOAT: + oprintf("%g", m_constval.vfloat); + break; + case TYPE_VECTOR: + oprintf("'%g %g %g'", + m_constval.vvec.x, + m_constval.vvec.y, + m_constval.vvec.z); + break; + case TYPE_ENTITY: + oprintf("(entity)"); + break; + case TYPE_STRING: + ir_value_dump_string(m_constval.vstring, oprintf); + break; +#if 0 + case TYPE_INTEGER: + oprintf("%i", m_constval.vint); + break; +#endif + case TYPE_POINTER: + oprintf("&%s", + m_constval.vpointer->m_name.c_str()); + break; + } + } else { + oprintf("%s", m_name.c_str()); + } +} + +void ir_value::dumpLife(int (*oprintf)(const char*,...)) const +{ + oprintf("Life of %12s:", m_name.c_str()); + for (size_t i = 0; i < m_life.size(); ++i) + { + oprintf(" + [%i, %i]\n", m_life[i].start, m_life[i].end); + } +} diff --git a/ir.h b/ir.h index cdf097b..9fa8ab5 100644 --- a/ir.h +++ b/ir.h @@ -1,25 +1,3 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * Wolfgang Bumiller - * - * 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. - */ #ifndef GMQCC_IR_HDR #define GMQCC_IR_HDR #include "gmqcc.h" @@ -30,17 +8,17 @@ */ typedef uint8_t ir_flag_t; -typedef struct ir_value_s ir_value; -typedef struct ir_instr_s ir_instr; -typedef struct ir_block_s ir_block; -typedef struct ir_function_s ir_function; -typedef struct ir_builder_s ir_builder; +struct ir_value; +struct ir_instr; +struct ir_block; +struct ir_function; +struct ir_builder; -typedef struct { +struct ir_life_entry_t { /* both inclusive */ size_t start; size_t end; -} ir_life_entry_t; +}; enum { IR_FLAG_HAS_ARRAYS = 1 << 0, @@ -57,127 +35,145 @@ enum { IR_FLAG_MASK_NO_LOCAL_TEMPS = (IR_FLAG_HAS_ARRAYS | IR_FLAG_HAS_UNINITIALIZED) }; -struct ir_value_s { - char *name; - int vtype; - int store; - lex_ctx_t context; +struct ir_value { + ir_value(std::string&& name, store_type storetype, qc_type vtype); + ir_value(ir_function *owner, std::string&& name, store_type storetype, qc_type vtype); + ~ir_value(); + + ir_value *vectorMember(unsigned int member); + + bool GMQCC_WARN setFloat(float); + bool GMQCC_WARN setFunc(int); + bool GMQCC_WARN setString(const char*); + bool GMQCC_WARN setVector(vec3_t); + bool GMQCC_WARN setField(ir_value*); +#if 0 + bool GMQCC_WARN setInt(int); +#endif + + bool lives(size_t at); + void dumpLife(int (*oprintf)(const char*, ...)) const; + + void setCodeAddress(int32_t gaddr); + int32_t codeAddress() const; + + bool insertLife(size_t idx, ir_life_entry_t); + bool setAlive(size_t position); + bool mergeLife(const ir_value *other); + std::string m_name; - int fieldtype; /* even the IR knows the subtype of a field */ - int outtype; /* and the output type of a function */ - int cvq; /* 'const' vs 'var' qualifier */ - ir_flag_t flags; + qc_type m_vtype; + store_type m_store; + lex_ctx_t m_context; + qc_type m_fieldtype; // even the IR knows the subtype of a field + qc_type m_outtype; // and the output type of a function + int m_cvq; // 'const' vs 'var' qualifier + ir_flag_t m_flags; - ir_instr **reads; - ir_instr **writes; + std::vector m_reads; + std::vector m_writes; - /* constantvalues */ - bool hasvalue; + // constant values + bool m_hasvalue; union { - qcfloat_t vfloat; - int vint; - vec3_t vvec; - int32_t ivec[3]; + qcfloat_t vfloat; + int vint; + vec3_t vvec; + int32_t ivec[3]; char *vstring; ir_value *vpointer; ir_function *vfunc; - } constval; + } m_constval; struct { int32_t globaladdr; int32_t name; - int32_t local; /* filled by the local-allocator */ - int32_t addroffset; /* added for members */ - int32_t fieldaddr; /* to generate field-addresses early */ - } code; + int32_t local; // filled by the local-allocator + int32_t addroffset; // added for members + int32_t fieldaddr; // to generate field-addresses early + } m_code; - /* for acessing vectors */ - ir_value *members[3]; - ir_value *memberof; + // for accessing vectors + ir_value *m_members[3]; + ir_value *m_memberof; + bool m_unique_life; // arrays will never overlap with temps + bool m_locked; // temps living during a CALL must be locked + bool m_callparam; - bool unique_life; /* arrays will never overlap with temps */ - bool locked; /* temps living during a CALL must be locked */ - bool callparam; + std::vector m_life; // For the temp allocator - ir_life_entry_t *life; /* For the temp allocator */ -}; + size_t size() const; -/* - * ir_value can be a variable, or created by an operation - * if a result of an operation: the function should store - * it to remember to delete it / garbage collect it - */ -void ir_value_delete(ir_value*); -ir_value* ir_value_vector_member(ir_value*, unsigned int member); -bool GMQCC_WARN ir_value_set_float(ir_value*, float f); -bool GMQCC_WARN ir_value_set_func(ir_value*, int f); -bool GMQCC_WARN ir_value_set_string(ir_value*, const char *s); -bool GMQCC_WARN ir_value_set_vector(ir_value*, vec3_t v); -bool GMQCC_WARN ir_value_set_field(ir_value*, ir_value *fld); -bool ir_value_lives(ir_value*, size_t); -void ir_value_dump_life(const ir_value *self, int (*oprintf)(const char*,...)); + void dump(int (*oprintf)(const char*, ...)) const; +}; /* PHI data */ -typedef struct ir_phi_entry_s { +struct ir_phi_entry_t { ir_value *value; ir_block *from; -} ir_phi_entry_t; +}; /* instruction */ -struct ir_instr_s { - int opcode; - lex_ctx_t context; - ir_value* (_ops[3]); - ir_block* (bops[2]); +struct ir_instr { + ir_instr(lex_ctx_t, ir_block *owner, int opcode); + ~ir_instr(); - ir_phi_entry_t *phi; - ir_value **params; + int m_opcode; + lex_ctx_t m_context; + ir_value *(_m_ops[3]) = { nullptr, nullptr, nullptr }; + ir_block *(m_bops[2]) = { nullptr, nullptr }; - /* For the temp-allocation */ - size_t eid; + std::vector m_phi; + std::vector m_params; + + // For the temp-allocation + size_t m_eid = 0; - /* For IFs */ - bool likely; + // For IFs + bool m_likely = true; - ir_block *owner; + ir_block *m_owner; }; /* block */ -struct ir_block_s { - char *label; - lex_ctx_t context; - bool final; /* once a jump is added we're done */ +struct ir_block { + ir_block(ir_function *owner, const std::string& name); + ~ir_block(); - ir_instr **instr; - ir_block **entries; - ir_block **exits; - ir_value **living; + ir_function *m_owner; + std::string m_label; - /* For the temp-allocation */ - size_t entry_id; - size_t eid; - bool is_return; + lex_ctx_t m_context; + bool m_final = false; /* once a jump is added we're done */ + + ir_instr **m_instr = nullptr; + ir_block **m_entries = nullptr; + ir_block **m_exits = nullptr; + std::vector m_living; - ir_function *owner; + /* For the temp-allocation */ + size_t m_entry_id = 0; + size_t m_eid = 0; + bool m_is_return = false; - bool generated; - size_t code_start; + bool m_generated = false; + size_t m_code_start = 0; }; ir_value* ir_block_create_binop(ir_block*, lex_ctx_t, const char *label, int op, ir_value *left, ir_value *right); ir_value* ir_block_create_unary(ir_block*, lex_ctx_t, const char *label, int op, ir_value *operand); bool GMQCC_WARN ir_block_create_store_op(ir_block*, lex_ctx_t, int op, ir_value *target, ir_value *what); bool GMQCC_WARN ir_block_create_storep(ir_block*, lex_ctx_t, ir_value *target, ir_value *what); -ir_value* ir_block_create_load_from_ent(ir_block*, lex_ctx_t, const char *label, ir_value *ent, ir_value *field, int outype); +ir_value* ir_block_create_load_from_ent(ir_block*, lex_ctx_t, const char *label, ir_value *ent, ir_value *field, qc_type outype); ir_value* ir_block_create_fieldaddress(ir_block*, lex_ctx_t, const char *label, ir_value *entity, ir_value *field); bool GMQCC_WARN ir_block_create_state_op(ir_block*, lex_ctx_t, ir_value *frame, ir_value *think); /* This is to create an instruction of the form * %label := opcode a, b */ -ir_instr* ir_block_create_phi(ir_block*, lex_ctx_t, const char *label, int vtype); +ir_instr* ir_block_create_phi(ir_block*, lex_ctx_t, const char *label, qc_type vtype); ir_value* ir_phi_value(ir_instr*); void ir_phi_add(ir_instr*, ir_block *b, ir_value *v); ir_instr* ir_block_create_call(ir_block*, lex_ctx_t, const char *label, ir_value *func, bool noreturn); @@ -199,30 +195,36 @@ bool GMQCC_WARN ir_block_create_jump(ir_block*, lex_ctx_t, ir_block *to); bool GMQCC_WARN ir_block_create_goto(ir_block*, lex_ctx_t, ir_block *to); /* function */ -struct ir_function_s { - char *name; - int outtype; - int *params; - ir_block **blocks; - ir_flag_t flags; - int builtin; +struct ir_function { + ir_function(ir_builder *owner, qc_type returntype); + ~ir_function(); + + ir_builder *m_owner; + + std::string m_name; + qc_type m_outtype; + int *m_params = nullptr; + ir_flag_t m_flags = 0; + int m_builtin = 0; + + std::vector> m_blocks; /* * values generated from operations * which might get optimized away, so anything * in there needs to be deleted in the dtor. */ - ir_value **values; - ir_value **locals; /* locally defined variables */ - ir_value *value; + std::vector> m_values; + std::vector> m_locals; /* locally defined variables */ + ir_value *m_value = nullptr; - size_t allocated_locals; - size_t globaltemps; + size_t m_allocated_locals = 0; + size_t m_globaltemps = 0; - ir_block* first; - ir_block* last; + ir_block* m_first = nullptr; + ir_block* m_last = nullptr; - lex_ctx_t context; + lex_ctx_t m_context; /* * for prototypes - first we generate all the @@ -231,19 +233,17 @@ struct ir_function_s { * * remember the ID: */ - qcint_t code_function_def; + qcint_t m_code_function_def = -1; /* for temp allocation */ - size_t run_id; - - ir_builder *owner; + size_t m_run_id = 0; /* vararg support: */ - size_t max_varargs; + size_t m_max_varargs = 0; }; -ir_value* ir_function_create_local(ir_function *self, const char *name, int vtype, bool param); +ir_value* ir_function_create_local(ir_function *self, const std::string& name, qc_type vtype, bool param); bool GMQCC_WARN ir_function_finalize(ir_function*); ir_block* ir_function_create_block(lex_ctx_t ctx, ir_function*, const char *label); @@ -251,52 +251,70 @@ ir_block* ir_function_create_block(lex_ctx_t ctx, ir_function*, const char #define IR_HT_SIZE 1024 #define IR_MAX_VINSTR_TEMPS 1 -struct ir_builder_s { - char *name; - ir_function **functions; - ir_value **globals; - ir_value **fields; - ir_value **const_floats; /* for reusing them in vector-splits, TODO: sort this or use a radix-tree */ - - ht htfunctions; - ht htglobals; - ht htfields; - - ir_value **extparams; - ir_value **extparam_protos; - - /* the highest func->allocated_locals */ - size_t max_locals; - size_t max_globaltemps; - uint32_t first_common_local; - uint32_t first_common_globaltemp; - - const char **filenames; - qcint_t *filestrings; - /* we cache the #IMMEDIATE string here */ - qcint_t str_immediate; - /* there should just be this one nil */ - ir_value *nil; - ir_value *reserved_va_count; - ir_value *coverage_func; +struct ir_builder { + ir_builder(const std::string& modulename); + ~ir_builder(); + + ir_function *createFunction(const std::string &name, qc_type outtype); + ir_value *createGlobal(const std::string &name, qc_type vtype); + ir_value *createField(const std::string &name, qc_type vtype); + ir_value *get_va_count(); + bool generate(const char *filename); + void dump(int (*oprintf)(const char*, ...)) const; + + ir_value *generateExtparamProto(); + void generateExtparam(); + + ir_value *literalFloat(float value, bool add_to_list); + + std::string m_name; + std::vector> m_functions; + std::vector> m_globals; + std::vector> m_fields; + // for reusing them in vector-splits, TODO: sort this or use a radix-tree + std::vector m_const_floats; + + ht m_htfunctions; + ht m_htglobals; + ht m_htfields; + + // extparams' ir_values reference the ones from extparam_protos + std::vector> m_extparam_protos; + std::vector m_extparams; + + // the highest func->allocated_locals + size_t m_max_locals = 0; + size_t m_max_globaltemps = 0; + uint32_t m_first_common_local = 0; + uint32_t m_first_common_globaltemp = 0; + + std::vector m_filenames; + std::vector m_filestrings; + + // we cache the #IMMEDIATE string here + qcint_t m_str_immediate = 0; + + // there should just be this one nil + ir_value *m_nil; + ir_value *m_reserved_va_count = nullptr; + ir_value *m_coverage_func = nullptr; + /* some virtual instructions require temps, and their code is isolated * so that we don't need to keep track of their liveness. */ - ir_value *vinstr_temp[IR_MAX_VINSTR_TEMPS]; + ir_value *m_vinstr_temp[IR_MAX_VINSTR_TEMPS]; /* code generator */ - code_t *code; + std::unique_ptr m_code; + +private: + qcint_t filestring(const char *filename); + bool generateGlobal(ir_value*, bool is_local); + bool generateGlobalFunction(ir_value*); + bool generateGlobalFunctionCode(ir_value*); + bool generateFunctionLocals(ir_value*); }; -ir_builder* ir_builder_new(const char *modulename); -void ir_builder_delete(ir_builder*); -ir_function* ir_builder_create_function(ir_builder*, const char *name, int outtype); -ir_value* ir_builder_create_global(ir_builder*, const char *name, int vtype); -ir_value* ir_builder_create_field(ir_builder*, const char *name, int vtype); -ir_value* ir_builder_get_va_count(ir_builder*); -bool ir_builder_generate(ir_builder *self, const char *filename); -void ir_builder_dump(ir_builder*, int (*oprintf)(const char*, ...)); - /* * This code assumes 32 bit floats while generating binary * Blub: don't use extern here, it's annoying and shows up in nm diff --git a/lexer.c b/lexer.c deleted file mode 100644 index a495d27..0000000 --- a/lexer.c +++ /dev/null @@ -1,1445 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * Wolfgang Bumiller - * - * 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 -#include - -#include "gmqcc.h" -#include "lexer.h" - -/* - * List of Keywords - */ - -/* original */ -static const char *keywords_qc[] = { - "for", "do", "while", - "if", "else", - "local", - "return", - "const" -}; -/* For fte/gmgqcc */ -static const char *keywords_fg[] = { - "switch", "case", "default", - "struct", "union", - "break", "continue", - "typedef", - "goto", - - "__builtin_debug_printtype" -}; - -/* - * Lexer code - */ -static char* *lex_filenames; - -static void lexerror(lex_file *lex, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - if (lex) - con_vprintmsg(LVL_ERROR, lex->name, lex->sline, lex->column, "parse error", fmt, ap); - else - con_vprintmsg(LVL_ERROR, "", 0, 0, "parse error", fmt, ap); - va_end(ap); -} - -static bool lexwarn(lex_file *lex, int warntype, const char *fmt, ...) -{ - bool r; - lex_ctx_t ctx; - va_list ap; - - ctx.file = lex->name; - ctx.line = lex->sline; - ctx.column = lex->column; - - va_start(ap, fmt); - r = vcompile_warning(ctx, warntype, fmt, ap); - va_end(ap); - return r; -} - -static void lex_token_new(lex_file *lex) -{ - if (lex->tok.value) - vec_shrinkto(lex->tok.value, 0); - - lex->tok.constval.t = 0; - lex->tok.ctx.line = lex->sline; - lex->tok.ctx.file = lex->name; - lex->tok.ctx.column = lex->column; -} - -static void lex_ungetch(lex_file *lex, int ch); -static int lex_getch(lex_file *lex); - -lex_file* lex_open(const char *file) -{ - lex_file *lex; - fs_file_t *in = fs_file_open(file, "rb"); - uint32_t read; - - if (!in) { - lexerror(NULL, "open failed: '%s'\n", file); - return NULL; - } - - lex = (lex_file*)mem_a(sizeof(*lex)); - if (!lex) { - fs_file_close(in); - lexerror(NULL, "out of memory\n"); - return NULL; - } - - memset(lex, 0, sizeof(*lex)); - - lex->file = in; - lex->name = util_strdup(file); - lex->line = 1; /* we start counting at 1 */ - lex->column = 0; - lex->peekpos = 0; - lex->eof = false; - - /* handle BOM */ - if ((read = (lex_getch(lex) << 16) | (lex_getch(lex) << 8) | lex_getch(lex)) != 0xEFBBBF) { - lex_ungetch(lex, (read & 0x0000FF)); - lex_ungetch(lex, (read & 0x00FF00) >> 8); - lex_ungetch(lex, (read & 0xFF0000) >> 16); - } else { - /* - * otherwise the lexer has advanced 3 bytes for the BOM, we need - * to set the column back to 0 - */ - lex->column = 0; - } - - vec_push(lex_filenames, lex->name); - return lex; -} - -lex_file* lex_open_string(const char *str, size_t len, const char *name) -{ - lex_file *lex; - - lex = (lex_file*)mem_a(sizeof(*lex)); - if (!lex) { - lexerror(NULL, "out of memory\n"); - return NULL; - } - - memset(lex, 0, sizeof(*lex)); - - lex->file = NULL; - lex->open_string = str; - lex->open_string_length = len; - lex->open_string_pos = 0; - - lex->name = util_strdup(name ? name : ""); - lex->line = 1; /* we start counting at 1 */ - lex->peekpos = 0; - lex->eof = false; - lex->column = 0; - - vec_push(lex_filenames, lex->name); - - return lex; -} - -void lex_cleanup(void) -{ - size_t i; - for (i = 0; i < vec_size(lex_filenames); ++i) - mem_d(lex_filenames[i]); - vec_free(lex_filenames); -} - -void lex_close(lex_file *lex) -{ - size_t i; - for (i = 0; i < vec_size(lex->frames); ++i) - mem_d(lex->frames[i].name); - vec_free(lex->frames); - - if (lex->modelname) - vec_free(lex->modelname); - - if (lex->file) - fs_file_close(lex->file); - - vec_free(lex->tok.value); - - /* mem_d(lex->name); collected in lex_filenames */ - mem_d(lex); -} - - - -static int lex_fgetc(lex_file *lex) -{ - if (lex->file) { - lex->column++; - return fs_file_getc(lex->file); - } - if (lex->open_string) { - if (lex->open_string_pos >= lex->open_string_length) - return FS_FILE_EOF; - lex->column++; - return lex->open_string[lex->open_string_pos++]; - } - return FS_FILE_EOF; -} - -/* Get or put-back data - * The following to functions do NOT understand what kind of data they - * are working on. - * The are merely wrapping get/put in order to count line numbers. - */ -static int lex_try_trigraph(lex_file *lex, int old) -{ - int c2, c3; - c2 = lex_fgetc(lex); - if (!lex->push_line && c2 == '\n') { - lex->line++; - lex->column = 0; - } - - if (c2 != '?') { - lex_ungetch(lex, c2); - return old; - } - - c3 = lex_fgetc(lex); - if (!lex->push_line && c3 == '\n') { - lex->line++; - lex->column = 0; - } - - switch (c3) { - case '=': return '#'; - case '/': return '\\'; - case '\'': return '^'; - case '(': return '['; - case ')': return ']'; - case '!': return '|'; - case '<': return '{'; - case '>': return '}'; - case '-': return '~'; - default: - lex_ungetch(lex, c3); - lex_ungetch(lex, c2); - return old; - } -} - -static int lex_try_digraph(lex_file *lex, int ch) -{ - int c2; - c2 = lex_fgetc(lex); - /* we just used fgetc() so count lines - * need to offset a \n the ungetch would recognize - */ - if (!lex->push_line && c2 == '\n') - lex->line++; - if (ch == '<' && c2 == ':') - return '['; - else if (ch == ':' && c2 == '>') - return ']'; - else if (ch == '<' && c2 == '%') - return '{'; - else if (ch == '%' && c2 == '>') - return '}'; - else if (ch == '%' && c2 == ':') - return '#'; - lex_ungetch(lex, c2); - return ch; -} - -static int lex_getch(lex_file *lex) -{ - int ch; - - if (lex->peekpos) { - lex->peekpos--; - if (!lex->push_line && lex->peek[lex->peekpos] == '\n') { - lex->line++; - lex->column = 0; - } - return lex->peek[lex->peekpos]; - } - - ch = lex_fgetc(lex); - if (!lex->push_line && ch == '\n') { - lex->line++; - lex->column = 0; - } - else if (ch == '?') - return lex_try_trigraph(lex, ch); - else if (!lex->flags.nodigraphs && (ch == '<' || ch == ':' || ch == '%')) - return lex_try_digraph(lex, ch); - return ch; -} - -static void lex_ungetch(lex_file *lex, int ch) -{ - lex->peek[lex->peekpos++] = ch; - lex->column--; - if (!lex->push_line && ch == '\n') { - lex->line--; - lex->column = 0; - } -} - -/* classify characters - * some additions to the is*() functions of ctype.h - */ - -/* Idents are alphanumberic, but they start with alpha or _ */ -static bool isident_start(int ch) -{ - return util_isalpha(ch) || ch == '_'; -} - -static bool isident(int ch) -{ - return isident_start(ch) || util_isdigit(ch); -} - -/* isxdigit_only is used when we already know it's not a digit - * and want to see if it's a hex digit anyway. - */ -static bool isxdigit_only(int ch) -{ - return (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); -} - -/* Append a character to the token buffer */ -static void lex_tokench(lex_file *lex, int ch) -{ - vec_push(lex->tok.value, ch); -} - -/* Append a trailing null-byte */ -static void lex_endtoken(lex_file *lex) -{ - vec_push(lex->tok.value, 0); - vec_shrinkby(lex->tok.value, 1); -} - -static bool lex_try_pragma(lex_file *lex) -{ - int ch; - char *pragma = NULL; - char *command = NULL; - char *param = NULL; - size_t line; - - if (lex->flags.preprocessing) - return false; - - line = lex->line; - - ch = lex_getch(lex); - if (ch != '#') { - lex_ungetch(lex, ch); - return false; - } - - for (ch = lex_getch(lex); vec_size(pragma) < 8 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex)) - vec_push(pragma, ch); - vec_push(pragma, 0); - - if (ch != ' ' || strcmp(pragma, "pragma")) { - lex_ungetch(lex, ch); - goto unroll; - } - - for (ch = lex_getch(lex); vec_size(command) < 32 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex)) - vec_push(command, ch); - vec_push(command, 0); - - if (ch != '(') { - lex_ungetch(lex, ch); - goto unroll; - } - - for (ch = lex_getch(lex); vec_size(param) < 1024 && ch != ')' && ch != '\n'; ch = lex_getch(lex)) - vec_push(param, ch); - vec_push(param, 0); - - if (ch != ')') { - lex_ungetch(lex, ch); - goto unroll; - } - - if (!strcmp(command, "push")) { - if (!strcmp(param, "line")) { - lex->push_line++; - if (lex->push_line == 1) - --line; - } - else - goto unroll; - } - else if (!strcmp(command, "pop")) { - if (!strcmp(param, "line")) { - if (lex->push_line) - lex->push_line--; - if (lex->push_line == 0) - --line; - } - else - goto unroll; - } - else if (!strcmp(command, "file")) { - lex->name = util_strdup(param); - vec_push(lex_filenames, lex->name); - } - else if (!strcmp(command, "line")) { - line = strtol(param, NULL, 0)-1; - } - else - goto unroll; - - lex->line = line; - while (ch != '\n' && ch != FS_FILE_EOF) - ch = lex_getch(lex); - vec_free(command); - vec_free(param); - vec_free(pragma); - return true; - -unroll: - if (command) { - vec_pop(command); - while (vec_size(command)) { - lex_ungetch(lex, (unsigned char)vec_last(command)); - vec_pop(command); - } - vec_free(command); - lex_ungetch(lex, ' '); - } - if (param) { - vec_pop(param); - while (vec_size(param)) { - lex_ungetch(lex, (unsigned char)vec_last(param)); - vec_pop(param); - } - vec_free(param); - lex_ungetch(lex, ' '); - } - if (pragma) { - vec_pop(pragma); - while (vec_size(pragma)) { - lex_ungetch(lex, (unsigned char)vec_last(pragma)); - vec_pop(pragma); - } - vec_free(pragma); - } - lex_ungetch(lex, '#'); - - lex->line = line; - return false; -} - -/* Skip whitespace and comments and return the first - * non-white character. - * As this makes use of the above getch() ungetch() functions, - * we don't need to care at all about line numbering anymore. - * - * In theory, this function should only be used at the beginning - * of lexing, or when we *know* the next character is part of the token. - * Otherwise, if the parser throws an error, the linenumber may not be - * the line of the error, but the line of the next token AFTER the error. - * - * This is currently only problematic when using c-like string-continuation, - * since comments and whitespaces are allowed between 2 such strings. - * Example: -printf( "line one\n" -// A comment - "A continuation of the previous string" -// This line is skipped - , foo); - - * In this case, if the parse decides it didn't actually want a string, - * and uses lex->line to print an error, it will show the ', foo);' line's - * linenumber. - * - * On the other hand, the parser is supposed to remember the line of the next - * token's beginning. In this case we would want skipwhite() to be called - * AFTER reading a token, so that the parser, before reading the NEXT token, - * doesn't store teh *comment's* linenumber, but the actual token's linenumber. - * - * THIS SOLUTION - * here is to store the line of the first character after skipping - * the initial whitespace in lex->sline, this happens in lex_do. - */ -static int lex_skipwhite(lex_file *lex, bool hadwhite) -{ - int ch = 0; - bool haswhite = hadwhite; - - do - { - ch = lex_getch(lex); - while (ch != FS_FILE_EOF && util_isspace(ch)) { - if (ch == '\n') { - if (lex_try_pragma(lex)) - continue; - } - if (lex->flags.preprocessing) { - if (ch == '\n') { - /* end-of-line */ - /* see if there was whitespace first */ - if (haswhite) { /* (vec_size(lex->tok.value)) { */ - lex_ungetch(lex, ch); - lex_endtoken(lex); - return TOKEN_WHITE; - } - /* otherwise return EOL */ - return TOKEN_EOL; - } - haswhite = true; - lex_tokench(lex, ch); - } - ch = lex_getch(lex); - } - - if (ch == '/') { - ch = lex_getch(lex); - if (ch == '/') - { - /* one line comment */ - ch = lex_getch(lex); - - if (lex->flags.preprocessing) { - haswhite = true; - lex_tokench(lex, ' '); - lex_tokench(lex, ' '); - } - - while (ch != FS_FILE_EOF && ch != '\n') { - if (lex->flags.preprocessing) - lex_tokench(lex, ' '); /* ch); */ - ch = lex_getch(lex); - } - if (lex->flags.preprocessing) { - lex_ungetch(lex, '\n'); - lex_endtoken(lex); - return TOKEN_WHITE; - } - continue; - } - if (ch == '*') - { - /* multiline comment */ - if (lex->flags.preprocessing) { - haswhite = true; - lex_tokench(lex, ' '); - lex_tokench(lex, ' '); - } - - while (ch != FS_FILE_EOF) - { - ch = lex_getch(lex); - if (ch == '*') { - ch = lex_getch(lex); - if (ch == '/') { - if (lex->flags.preprocessing) { - lex_tokench(lex, ' '); - lex_tokench(lex, ' '); - } - break; - } - lex_ungetch(lex, ch); - } - if (lex->flags.preprocessing) { - if (ch == '\n') - lex_tokench(lex, '\n'); - else - lex_tokench(lex, ' '); - } - } - ch = ' '; /* cause TRUE in the isspace check */ - continue; - } - /* Otherwise roll back to the slash and break out of the loop */ - lex_ungetch(lex, ch); - ch = '/'; - break; - } - } while (ch != FS_FILE_EOF && util_isspace(ch)); - - if (haswhite) { - lex_endtoken(lex); - lex_ungetch(lex, ch); - return TOKEN_WHITE; - } - return ch; -} - -/* Get a token */ -static bool GMQCC_WARN lex_finish_ident(lex_file *lex) -{ - int ch; - - ch = lex_getch(lex); - while (ch != FS_FILE_EOF && isident(ch)) - { - lex_tokench(lex, ch); - ch = lex_getch(lex); - } - - /* last ch was not an ident ch: */ - lex_ungetch(lex, ch); - - return true; -} - -/* read one ident for the frame list */ -static int lex_parse_frame(lex_file *lex) -{ - int ch; - - lex_token_new(lex); - - ch = lex_getch(lex); - while (ch != FS_FILE_EOF && ch != '\n' && util_isspace(ch)) - ch = lex_getch(lex); - - if (ch == '\n') - return 1; - - if (!isident_start(ch)) { - lexerror(lex, "invalid framename, must start with one of a-z or _, got %c", ch); - return -1; - } - - lex_tokench(lex, ch); - if (!lex_finish_ident(lex)) - return -1; - lex_endtoken(lex); - return 0; -} - -/* read a list of $frames */ -static bool lex_finish_frames(lex_file *lex) -{ - do { - size_t i; - int rc; - frame_macro m; - - rc = lex_parse_frame(lex); - if (rc > 0) /* end of line */ - return true; - if (rc < 0) /* error */ - return false; - - for (i = 0; i < vec_size(lex->frames); ++i) { - if (!strcmp(lex->tok.value, lex->frames[i].name)) { - lex->frames[i].value = lex->framevalue++; - if (lexwarn(lex, WARN_FRAME_MACROS, "duplicate frame macro defined: `%s`", lex->tok.value)) - return false; - break; - } - } - if (i < vec_size(lex->frames)) - continue; - - m.value = lex->framevalue++; - m.name = util_strdup(lex->tok.value); - vec_shrinkto(lex->tok.value, 0); - vec_push(lex->frames, m); - } while (true); - - return false; -} - -static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote) -{ - utf8ch_t chr = 0; - int ch = 0, texttype = 0; - int nextch; - bool hex; - bool oct; - char u8buf[8]; /* way more than enough */ - int u8len, uc; - - while (ch != FS_FILE_EOF) - { - ch = lex_getch(lex); - if (ch == quote) - return TOKEN_STRINGCONST; - - if (lex->flags.preprocessing && ch == '\\') { - lex_tokench(lex, ch); - ch = lex_getch(lex); - if (ch == FS_FILE_EOF) { - lexerror(lex, "unexpected end of file"); - lex_ungetch(lex, FS_FILE_EOF); /* next token to be TOKEN_EOF */ - return (lex->tok.ttype = TOKEN_ERROR); - } - lex_tokench(lex, ch); - } - else if (ch == '\\') { - ch = lex_getch(lex); - if (ch == FS_FILE_EOF) { - lexerror(lex, "unexpected end of file"); - lex_ungetch(lex, FS_FILE_EOF); /* next token to be TOKEN_EOF */ - return (lex->tok.ttype = TOKEN_ERROR); - } - - switch (ch) { - case '\\': break; - case '\'': break; - case '"': break; - case 'a': ch = '\a'; break; - case 'r': ch = '\r'; break; - case 'n': ch = '\n'; break; - case 't': ch = '\t'; break; - case 'f': ch = '\f'; break; - case 'v': ch = '\v'; break; - case 'x': - case 'X': - /* same procedure as in fteqcc */ - ch = 0; - nextch = lex_getch(lex); - if (nextch >= '0' && nextch <= '9') - ch += nextch - '0'; - else if (nextch >= 'a' && nextch <= 'f') - ch += nextch - 'a' + 10; - else if (nextch >= 'A' && nextch <= 'F') - ch += nextch - 'A' + 10; - else { - lexerror(lex, "bad character code"); - lex_ungetch(lex, nextch); - return (lex->tok.ttype = TOKEN_ERROR); - } - - ch *= 0x10; - nextch = lex_getch(lex); - if (nextch >= '0' && nextch <= '9') - ch += nextch - '0'; - else if (nextch >= 'a' && nextch <= 'f') - ch += nextch - 'a' + 10; - else if (nextch >= 'A' && nextch <= 'F') - ch += nextch - 'A' + 10; - else { - lexerror(lex, "bad character code"); - lex_ungetch(lex, nextch); - return (lex->tok.ttype = TOKEN_ERROR); - } - break; - - /* fteqcc support */ - case '0': case '1': case '2': case '3': - case '4': case '5': case '6': case '7': - case '8': case '9': - ch = 18 + ch - '0'; - break; - case '<': ch = 29; break; - case '-': ch = 30; break; - case '>': ch = 31; break; - case '[': ch = 16; break; - case ']': ch = 17; break; - case '{': - chr = 0; - nextch = lex_getch(lex); - hex = (nextch == 'x'); - oct = (nextch == '0'); - if (!hex && !oct) - lex_ungetch(lex, nextch); - for (nextch = lex_getch(lex); nextch != '}'; nextch = lex_getch(lex)) { - if (!hex && !oct) { - if (nextch >= '0' && nextch <= '9') - chr = chr * 10 + nextch - '0'; - else { - lexerror(lex, "bad character code"); - return (lex->tok.ttype = TOKEN_ERROR); - } - } else if (!oct) { - if (nextch >= '0' && nextch <= '9') - chr = chr * 0x10 + nextch - '0'; - else if (nextch >= 'a' && nextch <= 'f') - chr = chr * 0x10 + nextch - 'a' + 10; - else if (nextch >= 'A' && nextch <= 'F') - chr = chr * 0x10 + nextch - 'A' + 10; - else { - lexerror(lex, "bad character code"); - return (lex->tok.ttype = TOKEN_ERROR); - } - } else { - if (nextch >= '0' && nextch <= '9') - chr = chr * 8 + chr - '0'; - else { - lexerror(lex, "bad character code"); - return (lex->tok.ttype = TOKEN_ERROR); - } - } - if (chr > 0x10FFFF || (!OPTS_FLAG(UTF8) && chr > 255)) - { - lexerror(lex, "character code out of range"); - return (lex->tok.ttype = TOKEN_ERROR); - } - } - if (OPTS_FLAG(UTF8) && chr >= 128) { - u8len = utf8_from(u8buf, chr); - if (!u8len) - ch = 0; - else { - --u8len; - lex->column += u8len; - for (uc = 0; uc < u8len; ++uc) - lex_tokench(lex, u8buf[uc]); - /* - * the last character will be inserted with the tokench() call - * below the switch - */ - ch = u8buf[uc]; - } - } - else - ch = chr; - break; - - /* high bit text */ - case 'b': case 's': - texttype ^= 128; - continue; - - case '\n': - ch = '\n'; - break; - - default: - lexwarn(lex, WARN_UNKNOWN_CONTROL_SEQUENCE, "unrecognized control sequence: \\%c", ch); - /* so we just add the character plus backslash no matter what it actually is */ - lex_tokench(lex, '\\'); - } - /* add the character finally */ - lex_tokench(lex, ch | texttype); - } - else - lex_tokench(lex, ch); - } - lexerror(lex, "unexpected end of file within string constant"); - lex_ungetch(lex, FS_FILE_EOF); /* next token to be TOKEN_EOF */ - return (lex->tok.ttype = TOKEN_ERROR); -} - -static int GMQCC_WARN lex_finish_digit(lex_file *lex, int lastch) -{ - bool ishex = false; - - int ch = lastch; - - /* parse a number... */ - if (ch == '.') - lex->tok.ttype = TOKEN_FLOATCONST; - else - lex->tok.ttype = TOKEN_INTCONST; - - lex_tokench(lex, ch); - - ch = lex_getch(lex); - if (ch != '.' && !util_isdigit(ch)) - { - if (lastch != '0' || ch != 'x') - { - /* end of the number or EOF */ - lex_ungetch(lex, ch); - lex_endtoken(lex); - - lex->tok.constval.i = lastch - '0'; - return lex->tok.ttype; - } - - ishex = true; - } - - /* EOF would have been caught above */ - - if (ch != '.') - { - lex_tokench(lex, ch); - ch = lex_getch(lex); - while (util_isdigit(ch) || (ishex && isxdigit_only(ch))) - { - lex_tokench(lex, ch); - ch = lex_getch(lex); - } - } - /* NOT else, '.' can come from above as well */ - if (lex->tok.ttype != TOKEN_FLOATCONST && ch == '.' && !ishex) - { - /* Allow floating comma in non-hex mode */ - lex->tok.ttype = TOKEN_FLOATCONST; - lex_tokench(lex, ch); - - /* continue digits-only */ - ch = lex_getch(lex); - while (util_isdigit(ch)) - { - lex_tokench(lex, ch); - ch = lex_getch(lex); - } - } - /* put back the last character */ - /* but do not put back the trailing 'f' or a float */ - if (lex->tok.ttype == TOKEN_FLOATCONST && ch == 'f') - ch = lex_getch(lex); - - /* generally we don't want words to follow numbers: */ - if (isident(ch)) { - lexerror(lex, "unexpected trailing characters after number"); - return (lex->tok.ttype = TOKEN_ERROR); - } - lex_ungetch(lex, ch); - - lex_endtoken(lex); - if (lex->tok.ttype == TOKEN_FLOATCONST) - lex->tok.constval.f = strtod(lex->tok.value, NULL); - else - lex->tok.constval.i = strtol(lex->tok.value, NULL, 0); - return lex->tok.ttype; -} - -int lex_do(lex_file *lex) -{ - int ch, nextch, thirdch; - bool hadwhite = false; - - lex_token_new(lex); - - while (true) { - ch = lex_skipwhite(lex, hadwhite); - hadwhite = true; - if (!lex->flags.mergelines || ch != '\\') - break; - ch = lex_getch(lex); - if (ch == '\r') - ch = lex_getch(lex); - if (ch != '\n') { - lex_ungetch(lex, ch); - ch = '\\'; - break; - } - /* we reached a linemerge */ - lex_tokench(lex, '\n'); - continue; - } - - if (lex->flags.preprocessing && (ch == TOKEN_WHITE || ch == TOKEN_EOL || ch == TOKEN_FATAL)) { - return (lex->tok.ttype = ch); - } - - lex->sline = lex->line; - lex->tok.ctx.line = lex->sline; - lex->tok.ctx.file = lex->name; - - if (lex->eof) - return (lex->tok.ttype = TOKEN_FATAL); - - if (ch == FS_FILE_EOF) { - lex->eof = true; - return (lex->tok.ttype = TOKEN_EOF); - } - - /* modelgen / spiritgen commands */ - if (ch == '$' && !lex->flags.preprocessing) { - const char *v; - size_t frame; - - ch = lex_getch(lex); - if (!isident_start(ch)) { - lexerror(lex, "hanging '$' modelgen/spritegen command line"); - return lex_do(lex); - } - lex_tokench(lex, ch); - if (!lex_finish_ident(lex)) - return (lex->tok.ttype = TOKEN_ERROR); - lex_endtoken(lex); - /* skip the known commands */ - v = lex->tok.value; - - if (!strcmp(v, "frame") || !strcmp(v, "framesave")) - { - /* frame/framesave command works like an enum - * similar to fteqcc we handle this in the lexer. - * The reason for this is that it is sensitive to newlines, - * which the parser is unaware of - */ - if (!lex_finish_frames(lex)) - return (lex->tok.ttype = TOKEN_ERROR); - return lex_do(lex); - } - - if (!strcmp(v, "framevalue")) - { - ch = lex_getch(lex); - while (ch != FS_FILE_EOF && util_isspace(ch) && ch != '\n') - ch = lex_getch(lex); - - if (!util_isdigit(ch)) { - lexerror(lex, "$framevalue requires an integer parameter"); - return lex_do(lex); - } - - lex_token_new(lex); - lex->tok.ttype = lex_finish_digit(lex, ch); - lex_endtoken(lex); - if (lex->tok.ttype != TOKEN_INTCONST) { - lexerror(lex, "$framevalue requires an integer parameter"); - return lex_do(lex); - } - lex->framevalue = lex->tok.constval.i; - return lex_do(lex); - } - - if (!strcmp(v, "framerestore")) - { - int rc; - - lex_token_new(lex); - - rc = lex_parse_frame(lex); - - if (rc > 0) { - lexerror(lex, "$framerestore requires a framename parameter"); - return lex_do(lex); - } - if (rc < 0) - return (lex->tok.ttype = TOKEN_FATAL); - - v = lex->tok.value; - for (frame = 0; frame < vec_size(lex->frames); ++frame) { - if (!strcmp(v, lex->frames[frame].name)) { - lex->framevalue = lex->frames[frame].value; - return lex_do(lex); - } - } - lexerror(lex, "unknown framename `%s`", v); - return lex_do(lex); - } - - if (!strcmp(v, "modelname")) - { - int rc; - - lex_token_new(lex); - - rc = lex_parse_frame(lex); - - if (rc > 0) { - lexerror(lex, "$modelname requires a parameter"); - return lex_do(lex); - } - if (rc < 0) - return (lex->tok.ttype = TOKEN_FATAL); - - if (lex->modelname) { - frame_macro m; - m.value = lex->framevalue; - m.name = lex->modelname; - lex->modelname = NULL; - vec_push(lex->frames, m); - } - lex->modelname = lex->tok.value; - lex->tok.value = NULL; - return lex_do(lex); - } - - if (!strcmp(v, "flush")) - { - size_t fi; - for (fi = 0; fi < vec_size(lex->frames); ++fi) - mem_d(lex->frames[fi].name); - vec_free(lex->frames); - /* skip line (fteqcc does it too) */ - ch = lex_getch(lex); - while (ch != FS_FILE_EOF && ch != '\n') - ch = lex_getch(lex); - return lex_do(lex); - } - - if (!strcmp(v, "cd") || - !strcmp(v, "origin") || - !strcmp(v, "base") || - !strcmp(v, "flags") || - !strcmp(v, "scale") || - !strcmp(v, "skin")) - { - /* skip line */ - ch = lex_getch(lex); - while (ch != FS_FILE_EOF && ch != '\n') - ch = lex_getch(lex); - return lex_do(lex); - } - - for (frame = 0; frame < vec_size(lex->frames); ++frame) { - if (!strcmp(v, lex->frames[frame].name)) { - lex->tok.constval.i = lex->frames[frame].value; - return (lex->tok.ttype = TOKEN_INTCONST); - } - } - - lexerror(lex, "invalid frame macro"); - return lex_do(lex); - } - - /* single-character tokens */ - switch (ch) - { - case '[': - nextch = lex_getch(lex); - if (nextch == '[') { - lex_tokench(lex, ch); - lex_tokench(lex, nextch); - lex_endtoken(lex); - return (lex->tok.ttype = TOKEN_ATTRIBUTE_OPEN); - } - lex_ungetch(lex, nextch); - /* FALL THROUGH */ - case '(': - case ':': - case '?': - lex_tokench(lex, ch); - lex_endtoken(lex); - if (lex->flags.noops) - return (lex->tok.ttype = ch); - else - return (lex->tok.ttype = TOKEN_OPERATOR); - - case ']': - if (lex->flags.noops) { - nextch = lex_getch(lex); - if (nextch == ']') { - lex_tokench(lex, ch); - lex_tokench(lex, nextch); - lex_endtoken(lex); - return (lex->tok.ttype = TOKEN_ATTRIBUTE_CLOSE); - } - lex_ungetch(lex, nextch); - } - /* FALL THROUGH */ - case ')': - case ';': - case '{': - case '}': - - case '#': - lex_tokench(lex, ch); - lex_endtoken(lex); - return (lex->tok.ttype = ch); - default: - break; - } - - if (ch == '.') { - nextch = lex_getch(lex); - /* digits starting with a dot */ - if (util_isdigit(nextch)) { - lex_ungetch(lex, nextch); - lex->tok.ttype = lex_finish_digit(lex, ch); - lex_endtoken(lex); - return lex->tok.ttype; - } - lex_ungetch(lex, nextch); - } - - if (lex->flags.noops) - { - /* Detect characters early which are normally - * operators OR PART of an operator. - */ - switch (ch) - { - case '*': - case '/': - case '<': - case '>': - case '=': - case '&': - case '|': - case '^': - case '~': - case ',': - case '!': - lex_tokench(lex, ch); - lex_endtoken(lex); - return (lex->tok.ttype = ch); - default: - break; - } - } - - if (ch == '.') - { - lex_tokench(lex, ch); - /* peak ahead once */ - nextch = lex_getch(lex); - if (nextch != '.') { - lex_ungetch(lex, nextch); - lex_endtoken(lex); - if (lex->flags.noops) - return (lex->tok.ttype = ch); - else - return (lex->tok.ttype = TOKEN_OPERATOR); - } - /* peak ahead again */ - nextch = lex_getch(lex); - if (nextch != '.') { - lex_ungetch(lex, nextch); - lex_ungetch(lex, '.'); - lex_endtoken(lex); - if (lex->flags.noops) - return (lex->tok.ttype = ch); - else - return (lex->tok.ttype = TOKEN_OPERATOR); - } - /* fill the token to be "..." */ - lex_tokench(lex, ch); - lex_tokench(lex, ch); - lex_endtoken(lex); - return (lex->tok.ttype = TOKEN_DOTS); - } - - if (ch == ',' || ch == '.') { - lex_tokench(lex, ch); - lex_endtoken(lex); - return (lex->tok.ttype = TOKEN_OPERATOR); - } - - if (ch == '+' || ch == '-' || /* ++, --, +=, -= and -> as well! */ - ch == '>' || ch == '<' || /* <<, >>, <=, >= and >< as well! */ - ch == '=' || ch == '!' || /* <=>, ==, != */ - ch == '&' || ch == '|' || /* &&, ||, &=, |= */ - ch == '~' || ch == '^' /* ~=, ~, ^ */ - ) { - lex_tokench(lex, ch); - nextch = lex_getch(lex); - - if ((nextch == '=' && ch != '<') || (nextch == '<' && ch == '>')) - lex_tokench(lex, nextch); - else if (nextch == ch && ch != '!') { - lex_tokench(lex, nextch); - if ((thirdch = lex_getch(lex)) == '=') - lex_tokench(lex, thirdch); - else - lex_ungetch(lex, thirdch); - } else if (ch == '<' && nextch == '=') { - lex_tokench(lex, nextch); - if ((thirdch = lex_getch(lex)) == '>') - lex_tokench(lex, thirdch); - else - lex_ungetch(lex, thirdch); - - } else if (ch == '-' && nextch == '>') { - lex_tokench(lex, nextch); - } else if (ch == '&' && nextch == '~') { - thirdch = lex_getch(lex); - if (thirdch != '=') { - lex_ungetch(lex, thirdch); - lex_ungetch(lex, nextch); - } - else { - lex_tokench(lex, nextch); - lex_tokench(lex, thirdch); - } - } - else if (lex->flags.preprocessing && - ch == '-' && util_isdigit(nextch)) - { - lex->tok.ttype = lex_finish_digit(lex, nextch); - if (lex->tok.ttype == TOKEN_INTCONST) - lex->tok.constval.i = -lex->tok.constval.i; - else - lex->tok.constval.f = -lex->tok.constval.f; - lex_endtoken(lex); - return lex->tok.ttype; - } else { - lex_ungetch(lex, nextch); - } - - lex_endtoken(lex); - return (lex->tok.ttype = TOKEN_OPERATOR); - } - - if (ch == '*' || ch == '/') /* *=, /= */ - { - lex_tokench(lex, ch); - - nextch = lex_getch(lex); - if (nextch == '=' || nextch == '*') { - lex_tokench(lex, nextch); - } else - lex_ungetch(lex, nextch); - - lex_endtoken(lex); - return (lex->tok.ttype = TOKEN_OPERATOR); - } - - if (ch == '%') { - lex_tokench(lex, ch); - lex_endtoken(lex); - return (lex->tok.ttype = TOKEN_OPERATOR); - } - - if (isident_start(ch)) - { - const char *v; - - lex_tokench(lex, ch); - if (!lex_finish_ident(lex)) { - /* error? */ - return (lex->tok.ttype = TOKEN_ERROR); - } - lex_endtoken(lex); - lex->tok.ttype = TOKEN_IDENT; - - v = lex->tok.value; - if (!strcmp(v, "void")) { - lex->tok.ttype = TOKEN_TYPENAME; - lex->tok.constval.t = TYPE_VOID; - } else if (!strcmp(v, "int")) { - lex->tok.ttype = TOKEN_TYPENAME; - lex->tok.constval.t = TYPE_INTEGER; - } else if (!strcmp(v, "float")) { - lex->tok.ttype = TOKEN_TYPENAME; - lex->tok.constval.t = TYPE_FLOAT; - } else if (!strcmp(v, "string")) { - lex->tok.ttype = TOKEN_TYPENAME; - lex->tok.constval.t = TYPE_STRING; - } else if (!strcmp(v, "entity")) { - lex->tok.ttype = TOKEN_TYPENAME; - lex->tok.constval.t = TYPE_ENTITY; - } else if (!strcmp(v, "vector")) { - lex->tok.ttype = TOKEN_TYPENAME; - lex->tok.constval.t = TYPE_VECTOR; - } else if (!strcmp(v, "_length")) { - lex->tok.ttype = TOKEN_OPERATOR; - } else { - size_t kw; - for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_qc); ++kw) { - if (!strcmp(v, keywords_qc[kw])) - return (lex->tok.ttype = TOKEN_KEYWORD); - } - if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC) { - for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_fg); ++kw) { - if (!strcmp(v, keywords_fg[kw])) - return (lex->tok.ttype = TOKEN_KEYWORD); - } - } - } - - return lex->tok.ttype; - } - - if (ch == '"') - { - lex->flags.nodigraphs = true; - if (lex->flags.preprocessing) - lex_tokench(lex, ch); - lex->tok.ttype = lex_finish_string(lex, '"'); - if (lex->flags.preprocessing) - lex_tokench(lex, ch); - while (!lex->flags.preprocessing && lex->tok.ttype == TOKEN_STRINGCONST) - { - /* Allow c style "string" "continuation" */ - ch = lex_skipwhite(lex, false); - if (ch != '"') { - lex_ungetch(lex, ch); - break; - } - - lex->tok.ttype = lex_finish_string(lex, '"'); - } - lex->flags.nodigraphs = false; - lex_endtoken(lex); - return lex->tok.ttype; - } - - if (ch == '\'') - { - /* we parse character constants like string, - * but return TOKEN_CHARCONST, or a vector type if it fits... - * Likewise actual unescaping has to be done by the parser. - * The difference is we don't allow 'char' 'continuation'. - */ - if (lex->flags.preprocessing) - lex_tokench(lex, ch); - lex->tok.ttype = lex_finish_string(lex, '\''); - if (lex->flags.preprocessing) - lex_tokench(lex, ch); - lex_endtoken(lex); - - lex->tok.ttype = TOKEN_CHARCONST; - - /* It's a vector if we can successfully scan 3 floats */ - if (util_sscanf(lex->tok.value, " %f %f %f ", - &lex->tok.constval.v.x, &lex->tok.constval.v.y, &lex->tok.constval.v.z) == 3) - - { - lex->tok.ttype = TOKEN_VECTORCONST; - } - else - { - if (!lex->flags.preprocessing && strlen(lex->tok.value) > 1) { - utf8ch_t u8char; - /* check for a valid utf8 character */ - if (!OPTS_FLAG(UTF8) || !utf8_to(&u8char, (const unsigned char *)lex->tok.value, 8)) { - if (lexwarn(lex, WARN_MULTIBYTE_CHARACTER, - ( OPTS_FLAG(UTF8) ? "invalid multibyte character sequence `%s`" - : "multibyte character: `%s`" ), - lex->tok.value)) - return (lex->tok.ttype = TOKEN_ERROR); - } - else - lex->tok.constval.i = u8char; - } - else - lex->tok.constval.i = lex->tok.value[0]; - } - - return lex->tok.ttype; - } - - if (util_isdigit(ch)) - { - lex->tok.ttype = lex_finish_digit(lex, ch); - lex_endtoken(lex); - return lex->tok.ttype; - } - - if (lex->flags.preprocessing) { - lex_tokench(lex, ch); - lex_endtoken(lex); - return (lex->tok.ttype = ch); - } - - lexerror(lex, "unknown token: `%c`", ch); - return (lex->tok.ttype = TOKEN_ERROR); -} diff --git a/lexer.cpp b/lexer.cpp new file mode 100644 index 0000000..34fc71b --- /dev/null +++ b/lexer.cpp @@ -0,0 +1,1423 @@ +#include +#include + +#include "gmqcc.h" +#include "lexer.h" + +/* + * List of Keywords + */ + +/* original */ +static const char *keywords_qc[] = { + "for", "do", "while", + "if", "else", + "local", + "return", + "const" +}; +/* For fte/gmgqcc */ +static const char *keywords_fg[] = { + "switch", "case", "default", + "struct", "union", + "break", "continue", + "typedef", + "goto", + + "__builtin_debug_printtype" +}; + +/* + * Lexer code + */ +static char* *lex_filenames; + +static void lexerror(lex_file *lex, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (lex) + con_vprintmsg(LVL_ERROR, lex->name, lex->sline, lex->column, "parse error", fmt, ap); + else + con_vprintmsg(LVL_ERROR, "", 0, 0, "parse error", fmt, ap); + va_end(ap); +} + +static bool lexwarn(lex_file *lex, int warntype, const char *fmt, ...) +{ + bool r; + lex_ctx_t ctx; + va_list ap; + + ctx.file = lex->name; + ctx.line = lex->sline; + ctx.column = lex->column; + + va_start(ap, fmt); + r = vcompile_warning(ctx, warntype, fmt, ap); + va_end(ap); + return r; +} + +static void lex_token_new(lex_file *lex) +{ + if (lex->tok.value) + vec_shrinkto(lex->tok.value, 0); + + lex->tok.constval.t = TYPE_VOID; + lex->tok.ctx.line = lex->sline; + lex->tok.ctx.file = lex->name; + lex->tok.ctx.column = lex->column; +} + +static void lex_ungetch(lex_file *lex, int ch); +static int lex_getch(lex_file *lex); + +lex_file* lex_open(const char *file) +{ + lex_file *lex; + FILE *in = fopen(file, "rb"); + uint32_t read; + + if (!in) { + lexerror(nullptr, "open failed: '%s'\n", file); + return nullptr; + } + + lex = (lex_file*)mem_a(sizeof(*lex)); + if (!lex) { + fclose(in); + lexerror(nullptr, "out of memory\n"); + return nullptr; + } + + memset(lex, 0, sizeof(*lex)); + + lex->file = in; + lex->name = util_strdup(file); + lex->line = 1; /* we start counting at 1 */ + lex->column = 0; + lex->peekpos = 0; + lex->eof = false; + + /* handle BOM */ + if ((read = (lex_getch(lex) << 16) | (lex_getch(lex) << 8) | lex_getch(lex)) != 0xEFBBBF) { + lex_ungetch(lex, (read & 0x0000FF)); + lex_ungetch(lex, (read & 0x00FF00) >> 8); + lex_ungetch(lex, (read & 0xFF0000) >> 16); + } else { + /* + * otherwise the lexer has advanced 3 bytes for the BOM, we need + * to set the column back to 0 + */ + lex->column = 0; + } + + vec_push(lex_filenames, lex->name); + return lex; +} + +lex_file* lex_open_string(const char *str, size_t len, const char *name) +{ + lex_file *lex; + + lex = (lex_file*)mem_a(sizeof(*lex)); + if (!lex) { + lexerror(nullptr, "out of memory\n"); + return nullptr; + } + + memset(lex, 0, sizeof(*lex)); + + lex->file = nullptr; + lex->open_string = str; + lex->open_string_length = len; + lex->open_string_pos = 0; + + lex->name = util_strdup(name ? name : ""); + lex->line = 1; /* we start counting at 1 */ + lex->peekpos = 0; + lex->eof = false; + lex->column = 0; + + vec_push(lex_filenames, lex->name); + + return lex; +} + +void lex_cleanup(void) +{ + size_t i; + for (i = 0; i < vec_size(lex_filenames); ++i) + mem_d(lex_filenames[i]); + vec_free(lex_filenames); +} + +void lex_close(lex_file *lex) +{ + size_t i; + for (i = 0; i < vec_size(lex->frames); ++i) + mem_d(lex->frames[i].name); + vec_free(lex->frames); + + if (lex->modelname) + vec_free(lex->modelname); + + if (lex->file) + fclose(lex->file); + + vec_free(lex->tok.value); + + /* mem_d(lex->name); collected in lex_filenames */ + mem_d(lex); +} + + + +static int lex_fgetc(lex_file *lex) +{ + if (lex->file) { + lex->column++; + return fgetc(lex->file); + } + if (lex->open_string) { + if (lex->open_string_pos >= lex->open_string_length) + return EOF; + lex->column++; + return lex->open_string[lex->open_string_pos++]; + } + return EOF; +} + +/* Get or put-back data + * The following to functions do NOT understand what kind of data they + * are working on. + * The are merely wrapping get/put in order to count line numbers. + */ +static int lex_try_trigraph(lex_file *lex, int old) +{ + int c2, c3; + c2 = lex_fgetc(lex); + if (!lex->push_line && c2 == '\n') { + lex->line++; + lex->column = 0; + } + + if (c2 != '?') { + lex_ungetch(lex, c2); + return old; + } + + c3 = lex_fgetc(lex); + if (!lex->push_line && c3 == '\n') { + lex->line++; + lex->column = 0; + } + + switch (c3) { + case '=': return '#'; + case '/': return '\\'; + case '\'': return '^'; + case '(': return '['; + case ')': return ']'; + case '!': return '|'; + case '<': return '{'; + case '>': return '}'; + case '-': return '~'; + default: + lex_ungetch(lex, c3); + lex_ungetch(lex, c2); + return old; + } +} + +static int lex_try_digraph(lex_file *lex, int ch) +{ + int c2; + c2 = lex_fgetc(lex); + /* we just used fgetc() so count lines + * need to offset a \n the ungetch would recognize + */ + if (!lex->push_line && c2 == '\n') + lex->line++; + if (ch == '<' && c2 == ':') + return '['; + else if (ch == ':' && c2 == '>') + return ']'; + else if (ch == '<' && c2 == '%') + return '{'; + else if (ch == '%' && c2 == '>') + return '}'; + else if (ch == '%' && c2 == ':') + return '#'; + lex_ungetch(lex, c2); + return ch; +} + +static int lex_getch(lex_file *lex) +{ + int ch; + + if (lex->peekpos) { + lex->peekpos--; + if (!lex->push_line && lex->peek[lex->peekpos] == '\n') { + lex->line++; + lex->column = 0; + } + return lex->peek[lex->peekpos]; + } + + ch = lex_fgetc(lex); + if (!lex->push_line && ch == '\n') { + lex->line++; + lex->column = 0; + } + else if (ch == '?') + return lex_try_trigraph(lex, ch); + else if (!lex->flags.nodigraphs && (ch == '<' || ch == ':' || ch == '%')) + return lex_try_digraph(lex, ch); + return ch; +} + +static void lex_ungetch(lex_file *lex, int ch) +{ + lex->peek[lex->peekpos++] = ch; + lex->column--; + if (!lex->push_line && ch == '\n') { + lex->line--; + lex->column = 0; + } +} + +/* classify characters + * some additions to the is*() functions of ctype.h + */ + +/* Idents are alphanumberic, but they start with alpha or _ */ +static bool isident_start(int ch) +{ + return util_isalpha(ch) || ch == '_'; +} + +static bool isident(int ch) +{ + return isident_start(ch) || util_isdigit(ch); +} + +/* isxdigit_only is used when we already know it's not a digit + * and want to see if it's a hex digit anyway. + */ +static bool isxdigit_only(int ch) +{ + return (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); +} + +/* Append a character to the token buffer */ +static void lex_tokench(lex_file *lex, int ch) +{ + vec_push(lex->tok.value, ch); +} + +/* Append a trailing null-byte */ +static void lex_endtoken(lex_file *lex) +{ + vec_push(lex->tok.value, 0); + vec_shrinkby(lex->tok.value, 1); +} + +static bool lex_try_pragma(lex_file *lex) +{ + int ch; + char *pragma = nullptr; + char *command = nullptr; + char *param = nullptr; + size_t line; + + if (lex->flags.preprocessing) + return false; + + line = lex->line; + + ch = lex_getch(lex); + if (ch != '#') { + lex_ungetch(lex, ch); + return false; + } + + for (ch = lex_getch(lex); vec_size(pragma) < 8 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex)) + vec_push(pragma, ch); + vec_push(pragma, 0); + + if (ch != ' ' || strcmp(pragma, "pragma")) { + lex_ungetch(lex, ch); + goto unroll; + } + + for (ch = lex_getch(lex); vec_size(command) < 32 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex)) + vec_push(command, ch); + vec_push(command, 0); + + if (ch != '(') { + lex_ungetch(lex, ch); + goto unroll; + } + + for (ch = lex_getch(lex); vec_size(param) < 1024 && ch != ')' && ch != '\n'; ch = lex_getch(lex)) + vec_push(param, ch); + vec_push(param, 0); + + if (ch != ')') { + lex_ungetch(lex, ch); + goto unroll; + } + + if (!strcmp(command, "push")) { + if (!strcmp(param, "line")) { + lex->push_line++; + if (lex->push_line == 1) + --line; + } + else + goto unroll; + } + else if (!strcmp(command, "pop")) { + if (!strcmp(param, "line")) { + if (lex->push_line) + lex->push_line--; + if (lex->push_line == 0) + --line; + } + else + goto unroll; + } + else if (!strcmp(command, "file")) { + lex->name = util_strdup(param); + vec_push(lex_filenames, lex->name); + } + else if (!strcmp(command, "line")) { + line = strtol(param, nullptr, 0)-1; + } + else + goto unroll; + + lex->line = line; + while (ch != '\n' && ch != EOF) + ch = lex_getch(lex); + vec_free(command); + vec_free(param); + vec_free(pragma); + return true; + +unroll: + if (command) { + vec_pop(command); + while (vec_size(command)) { + lex_ungetch(lex, (unsigned char)vec_last(command)); + vec_pop(command); + } + vec_free(command); + lex_ungetch(lex, ' '); + } + if (param) { + vec_pop(param); + while (vec_size(param)) { + lex_ungetch(lex, (unsigned char)vec_last(param)); + vec_pop(param); + } + vec_free(param); + lex_ungetch(lex, ' '); + } + if (pragma) { + vec_pop(pragma); + while (vec_size(pragma)) { + lex_ungetch(lex, (unsigned char)vec_last(pragma)); + vec_pop(pragma); + } + vec_free(pragma); + } + lex_ungetch(lex, '#'); + + lex->line = line; + return false; +} + +/* Skip whitespace and comments and return the first + * non-white character. + * As this makes use of the above getch() ungetch() functions, + * we don't need to care at all about line numbering anymore. + * + * In theory, this function should only be used at the beginning + * of lexing, or when we *know* the next character is part of the token. + * Otherwise, if the parser throws an error, the linenumber may not be + * the line of the error, but the line of the next token AFTER the error. + * + * This is currently only problematic when using c-like string-continuation, + * since comments and whitespaces are allowed between 2 such strings. + * Example: +printf( "line one\n" +// A comment + "A continuation of the previous string" +// This line is skipped + , foo); + + * In this case, if the parse decides it didn't actually want a string, + * and uses lex->line to print an error, it will show the ', foo);' line's + * linenumber. + * + * On the other hand, the parser is supposed to remember the line of the next + * token's beginning. In this case we would want skipwhite() to be called + * AFTER reading a token, so that the parser, before reading the NEXT token, + * doesn't store teh *comment's* linenumber, but the actual token's linenumber. + * + * THIS SOLUTION + * here is to store the line of the first character after skipping + * the initial whitespace in lex->sline, this happens in lex_do. + */ +static int lex_skipwhite(lex_file *lex, bool hadwhite) +{ + int ch = 0; + bool haswhite = hadwhite; + + do + { + ch = lex_getch(lex); + while (ch != EOF && util_isspace(ch)) { + if (ch == '\n') { + if (lex_try_pragma(lex)) + continue; + } + if (lex->flags.preprocessing) { + if (ch == '\n') { + /* end-of-line */ + /* see if there was whitespace first */ + if (haswhite) { /* (vec_size(lex->tok.value)) { */ + lex_ungetch(lex, ch); + lex_endtoken(lex); + return TOKEN_WHITE; + } + /* otherwise return EOL */ + return TOKEN_EOL; + } + haswhite = true; + lex_tokench(lex, ch); + } + ch = lex_getch(lex); + } + + if (ch == '/') { + ch = lex_getch(lex); + if (ch == '/') + { + /* one line comment */ + ch = lex_getch(lex); + + if (lex->flags.preprocessing) { + haswhite = true; + lex_tokench(lex, ' '); + lex_tokench(lex, ' '); + } + + while (ch != EOF && ch != '\n') { + if (lex->flags.preprocessing) + lex_tokench(lex, ' '); /* ch); */ + ch = lex_getch(lex); + } + if (lex->flags.preprocessing) { + lex_ungetch(lex, '\n'); + lex_endtoken(lex); + return TOKEN_WHITE; + } + continue; + } + if (ch == '*') + { + /* multiline comment */ + if (lex->flags.preprocessing) { + haswhite = true; + lex_tokench(lex, ' '); + lex_tokench(lex, ' '); + } + + while (ch != EOF) + { + ch = lex_getch(lex); + if (ch == '*') { + ch = lex_getch(lex); + if (ch == '/') { + if (lex->flags.preprocessing) { + lex_tokench(lex, ' '); + lex_tokench(lex, ' '); + } + break; + } + lex_ungetch(lex, ch); + } + if (lex->flags.preprocessing) { + if (ch == '\n') + lex_tokench(lex, '\n'); + else + lex_tokench(lex, ' '); + } + } + ch = ' '; /* cause TRUE in the isspace check */ + continue; + } + /* Otherwise roll back to the slash and break out of the loop */ + lex_ungetch(lex, ch); + ch = '/'; + break; + } + } while (ch != EOF && util_isspace(ch)); + + if (haswhite) { + lex_endtoken(lex); + lex_ungetch(lex, ch); + return TOKEN_WHITE; + } + return ch; +} + +/* Get a token */ +static bool GMQCC_WARN lex_finish_ident(lex_file *lex) +{ + int ch; + + ch = lex_getch(lex); + while (ch != EOF && isident(ch)) + { + lex_tokench(lex, ch); + ch = lex_getch(lex); + } + + /* last ch was not an ident ch: */ + lex_ungetch(lex, ch); + + return true; +} + +/* read one ident for the frame list */ +static int lex_parse_frame(lex_file *lex) +{ + int ch; + + lex_token_new(lex); + + ch = lex_getch(lex); + while (ch != EOF && ch != '\n' && util_isspace(ch)) + ch = lex_getch(lex); + + if (ch == '\n') + return 1; + + if (!isident_start(ch)) { + lexerror(lex, "invalid framename, must start with one of a-z or _, got %c", ch); + return -1; + } + + lex_tokench(lex, ch); + if (!lex_finish_ident(lex)) + return -1; + lex_endtoken(lex); + return 0; +} + +/* read a list of $frames */ +static bool lex_finish_frames(lex_file *lex) +{ + do { + size_t i; + int rc; + frame_macro m; + + rc = lex_parse_frame(lex); + if (rc > 0) /* end of line */ + return true; + if (rc < 0) /* error */ + return false; + + for (i = 0; i < vec_size(lex->frames); ++i) { + if (!strcmp(lex->tok.value, lex->frames[i].name)) { + lex->frames[i].value = lex->framevalue++; + if (lexwarn(lex, WARN_FRAME_MACROS, "duplicate frame macro defined: `%s`", lex->tok.value)) + return false; + break; + } + } + if (i < vec_size(lex->frames)) + continue; + + m.value = lex->framevalue++; + m.name = util_strdup(lex->tok.value); + vec_shrinkto(lex->tok.value, 0); + vec_push(lex->frames, m); + } while (true); + + return false; +} + +static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote) +{ + utf8ch_t chr = 0; + int ch = 0, texttype = 0; + int nextch; + bool hex; + bool oct; + char u8buf[8]; /* way more than enough */ + int u8len, uc; + + while (ch != EOF) + { + ch = lex_getch(lex); + if (ch == quote) + return TOKEN_STRINGCONST; + + if (lex->flags.preprocessing && ch == '\\') { + lex_tokench(lex, ch); + ch = lex_getch(lex); + if (ch == EOF) { + lexerror(lex, "unexpected end of file"); + lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */ + return (lex->tok.ttype = TOKEN_ERROR); + } + lex_tokench(lex, ch); + } + else if (ch == '\\') { + ch = lex_getch(lex); + if (ch == EOF) { + lexerror(lex, "unexpected end of file"); + lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */ + return (lex->tok.ttype = TOKEN_ERROR); + } + + switch (ch) { + case '\\': break; + case '\'': break; + case '"': break; + case 'a': ch = '\a'; break; + case 'r': ch = '\r'; break; + case 'n': ch = '\n'; break; + case 't': ch = '\t'; break; + case 'f': ch = '\f'; break; + case 'v': ch = '\v'; break; + case 'x': + case 'X': + /* same procedure as in fteqcc */ + ch = 0; + nextch = lex_getch(lex); + if (nextch >= '0' && nextch <= '9') + ch += nextch - '0'; + else if (nextch >= 'a' && nextch <= 'f') + ch += nextch - 'a' + 10; + else if (nextch >= 'A' && nextch <= 'F') + ch += nextch - 'A' + 10; + else { + lexerror(lex, "bad character code"); + lex_ungetch(lex, nextch); + return (lex->tok.ttype = TOKEN_ERROR); + } + + ch *= 0x10; + nextch = lex_getch(lex); + if (nextch >= '0' && nextch <= '9') + ch += nextch - '0'; + else if (nextch >= 'a' && nextch <= 'f') + ch += nextch - 'a' + 10; + else if (nextch >= 'A' && nextch <= 'F') + ch += nextch - 'A' + 10; + else { + lexerror(lex, "bad character code"); + lex_ungetch(lex, nextch); + return (lex->tok.ttype = TOKEN_ERROR); + } + break; + + /* fteqcc support */ + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + case '8': case '9': + ch = 18 + ch - '0'; + break; + case '<': ch = 29; break; + case '-': ch = 30; break; + case '>': ch = 31; break; + case '[': ch = 16; break; + case ']': ch = 17; break; + case '{': + chr = 0; + nextch = lex_getch(lex); + hex = (nextch == 'x'); + oct = (nextch == '0'); + if (!hex && !oct) + lex_ungetch(lex, nextch); + for (nextch = lex_getch(lex); nextch != '}'; nextch = lex_getch(lex)) { + if (!hex && !oct) { + if (nextch >= '0' && nextch <= '9') + chr = chr * 10 + nextch - '0'; + else { + lexerror(lex, "bad character code"); + return (lex->tok.ttype = TOKEN_ERROR); + } + } else if (!oct) { + if (nextch >= '0' && nextch <= '9') + chr = chr * 0x10 + nextch - '0'; + else if (nextch >= 'a' && nextch <= 'f') + chr = chr * 0x10 + nextch - 'a' + 10; + else if (nextch >= 'A' && nextch <= 'F') + chr = chr * 0x10 + nextch - 'A' + 10; + else { + lexerror(lex, "bad character code"); + return (lex->tok.ttype = TOKEN_ERROR); + } + } else { + if (nextch >= '0' && nextch <= '9') + chr = chr * 8 + chr - '0'; + else { + lexerror(lex, "bad character code"); + return (lex->tok.ttype = TOKEN_ERROR); + } + } + if (chr > 0x10FFFF || (!OPTS_FLAG(UTF8) && chr > 255)) + { + lexerror(lex, "character code out of range"); + return (lex->tok.ttype = TOKEN_ERROR); + } + } + if (OPTS_FLAG(UTF8) && chr >= 128) { + u8len = utf8_from(u8buf, chr); + if (!u8len) + ch = 0; + else { + --u8len; + lex->column += u8len; + for (uc = 0; uc < u8len; ++uc) + lex_tokench(lex, u8buf[uc]); + /* + * the last character will be inserted with the tokench() call + * below the switch + */ + ch = u8buf[uc]; + } + } + else + ch = chr; + break; + + /* high bit text */ + case 'b': case 's': + texttype ^= 128; + continue; + + case '\n': + ch = '\n'; + break; + + default: + lexwarn(lex, WARN_UNKNOWN_CONTROL_SEQUENCE, "unrecognized control sequence: \\%c", ch); + /* so we just add the character plus backslash no matter what it actually is */ + lex_tokench(lex, '\\'); + } + /* add the character finally */ + lex_tokench(lex, ch | texttype); + } + else + lex_tokench(lex, ch); + } + lexerror(lex, "unexpected end of file within string constant"); + lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */ + return (lex->tok.ttype = TOKEN_ERROR); +} + +static int GMQCC_WARN lex_finish_digit(lex_file *lex, int lastch) +{ + bool ishex = false; + + int ch = lastch; + + /* parse a number... */ + if (ch == '.') + lex->tok.ttype = TOKEN_FLOATCONST; + else + lex->tok.ttype = TOKEN_INTCONST; + + lex_tokench(lex, ch); + + ch = lex_getch(lex); + if (ch != '.' && !util_isdigit(ch)) + { + if (lastch != '0' || ch != 'x') + { + /* end of the number or EOF */ + lex_ungetch(lex, ch); + lex_endtoken(lex); + + lex->tok.constval.i = lastch - '0'; + return lex->tok.ttype; + } + + ishex = true; + } + + /* EOF would have been caught above */ + + if (ch != '.') + { + lex_tokench(lex, ch); + ch = lex_getch(lex); + while (util_isdigit(ch) || (ishex && isxdigit_only(ch))) + { + lex_tokench(lex, ch); + ch = lex_getch(lex); + } + } + /* NOT else, '.' can come from above as well */ + if (lex->tok.ttype != TOKEN_FLOATCONST && ch == '.' && !ishex) + { + /* Allow floating comma in non-hex mode */ + lex->tok.ttype = TOKEN_FLOATCONST; + lex_tokench(lex, ch); + + /* continue digits-only */ + ch = lex_getch(lex); + while (util_isdigit(ch)) + { + lex_tokench(lex, ch); + ch = lex_getch(lex); + } + } + /* put back the last character */ + /* but do not put back the trailing 'f' or a float */ + if (lex->tok.ttype == TOKEN_FLOATCONST && ch == 'f') + ch = lex_getch(lex); + + /* generally we don't want words to follow numbers: */ + if (isident(ch)) { + lexerror(lex, "unexpected trailing characters after number"); + return (lex->tok.ttype = TOKEN_ERROR); + } + lex_ungetch(lex, ch); + + lex_endtoken(lex); + if (lex->tok.ttype == TOKEN_FLOATCONST) + lex->tok.constval.f = strtod(lex->tok.value, nullptr); + else + lex->tok.constval.i = strtol(lex->tok.value, nullptr, 0); + return lex->tok.ttype; +} + +int lex_do(lex_file *lex) +{ + int ch, nextch, thirdch; + bool hadwhite = false; + + lex_token_new(lex); + + while (true) { + ch = lex_skipwhite(lex, hadwhite); + hadwhite = true; + if (!lex->flags.mergelines || ch != '\\') + break; + ch = lex_getch(lex); + if (ch == '\r') + ch = lex_getch(lex); + if (ch != '\n') { + lex_ungetch(lex, ch); + ch = '\\'; + break; + } + /* we reached a linemerge */ + lex_tokench(lex, '\n'); + continue; + } + + if (lex->flags.preprocessing && (ch == TOKEN_WHITE || ch == TOKEN_EOL || ch == TOKEN_FATAL)) { + return (lex->tok.ttype = ch); + } + + lex->sline = lex->line; + lex->tok.ctx.line = lex->sline; + lex->tok.ctx.file = lex->name; + + if (lex->eof) + return (lex->tok.ttype = TOKEN_FATAL); + + if (ch == EOF) { + lex->eof = true; + return (lex->tok.ttype = TOKEN_EOF); + } + + /* modelgen / spiritgen commands */ + if (ch == '$' && !lex->flags.preprocessing) { + const char *v; + size_t frame; + + ch = lex_getch(lex); + if (!isident_start(ch)) { + lexerror(lex, "hanging '$' modelgen/spritegen command line"); + return lex_do(lex); + } + lex_tokench(lex, ch); + if (!lex_finish_ident(lex)) + return (lex->tok.ttype = TOKEN_ERROR); + lex_endtoken(lex); + /* skip the known commands */ + v = lex->tok.value; + + if (!strcmp(v, "frame") || !strcmp(v, "framesave")) + { + /* frame/framesave command works like an enum + * similar to fteqcc we handle this in the lexer. + * The reason for this is that it is sensitive to newlines, + * which the parser is unaware of + */ + if (!lex_finish_frames(lex)) + return (lex->tok.ttype = TOKEN_ERROR); + return lex_do(lex); + } + + if (!strcmp(v, "framevalue")) + { + ch = lex_getch(lex); + while (ch != EOF && util_isspace(ch) && ch != '\n') + ch = lex_getch(lex); + + if (!util_isdigit(ch)) { + lexerror(lex, "$framevalue requires an integer parameter"); + return lex_do(lex); + } + + lex_token_new(lex); + lex->tok.ttype = lex_finish_digit(lex, ch); + lex_endtoken(lex); + if (lex->tok.ttype != TOKEN_INTCONST) { + lexerror(lex, "$framevalue requires an integer parameter"); + return lex_do(lex); + } + lex->framevalue = lex->tok.constval.i; + return lex_do(lex); + } + + if (!strcmp(v, "framerestore")) + { + int rc; + + lex_token_new(lex); + + rc = lex_parse_frame(lex); + + if (rc > 0) { + lexerror(lex, "$framerestore requires a framename parameter"); + return lex_do(lex); + } + if (rc < 0) + return (lex->tok.ttype = TOKEN_FATAL); + + v = lex->tok.value; + for (frame = 0; frame < vec_size(lex->frames); ++frame) { + if (!strcmp(v, lex->frames[frame].name)) { + lex->framevalue = lex->frames[frame].value; + return lex_do(lex); + } + } + lexerror(lex, "unknown framename `%s`", v); + return lex_do(lex); + } + + if (!strcmp(v, "modelname")) + { + int rc; + + lex_token_new(lex); + + rc = lex_parse_frame(lex); + + if (rc > 0) { + lexerror(lex, "$modelname requires a parameter"); + return lex_do(lex); + } + if (rc < 0) + return (lex->tok.ttype = TOKEN_FATAL); + + if (lex->modelname) { + frame_macro m; + m.value = lex->framevalue; + m.name = lex->modelname; + lex->modelname = nullptr; + vec_push(lex->frames, m); + } + lex->modelname = lex->tok.value; + lex->tok.value = nullptr; + return lex_do(lex); + } + + if (!strcmp(v, "flush")) + { + size_t fi; + for (fi = 0; fi < vec_size(lex->frames); ++fi) + mem_d(lex->frames[fi].name); + vec_free(lex->frames); + /* skip line (fteqcc does it too) */ + ch = lex_getch(lex); + while (ch != EOF && ch != '\n') + ch = lex_getch(lex); + return lex_do(lex); + } + + if (!strcmp(v, "cd") || + !strcmp(v, "origin") || + !strcmp(v, "base") || + !strcmp(v, "flags") || + !strcmp(v, "scale") || + !strcmp(v, "skin")) + { + /* skip line */ + ch = lex_getch(lex); + while (ch != EOF && ch != '\n') + ch = lex_getch(lex); + return lex_do(lex); + } + + for (frame = 0; frame < vec_size(lex->frames); ++frame) { + if (!strcmp(v, lex->frames[frame].name)) { + lex->tok.constval.i = lex->frames[frame].value; + return (lex->tok.ttype = TOKEN_INTCONST); + } + } + + lexerror(lex, "invalid frame macro"); + return lex_do(lex); + } + + /* single-character tokens */ + switch (ch) + { + case '[': + nextch = lex_getch(lex); + if (nextch == '[') { + lex_tokench(lex, ch); + lex_tokench(lex, nextch); + lex_endtoken(lex); + return (lex->tok.ttype = TOKEN_ATTRIBUTE_OPEN); + } + lex_ungetch(lex, nextch); + /* FALL THROUGH */ + case '(': + case ':': + case '?': + lex_tokench(lex, ch); + lex_endtoken(lex); + if (lex->flags.noops) + return (lex->tok.ttype = ch); + else + return (lex->tok.ttype = TOKEN_OPERATOR); + + case ']': + if (lex->flags.noops) { + nextch = lex_getch(lex); + if (nextch == ']') { + lex_tokench(lex, ch); + lex_tokench(lex, nextch); + lex_endtoken(lex); + return (lex->tok.ttype = TOKEN_ATTRIBUTE_CLOSE); + } + lex_ungetch(lex, nextch); + } + /* FALL THROUGH */ + case ')': + case ';': + case '{': + case '}': + + case '#': + lex_tokench(lex, ch); + lex_endtoken(lex); + return (lex->tok.ttype = ch); + default: + break; + } + + if (ch == '.') { + nextch = lex_getch(lex); + /* digits starting with a dot */ + if (util_isdigit(nextch)) { + lex_ungetch(lex, nextch); + lex->tok.ttype = lex_finish_digit(lex, ch); + lex_endtoken(lex); + return lex->tok.ttype; + } + lex_ungetch(lex, nextch); + } + + if (lex->flags.noops) + { + /* Detect characters early which are normally + * operators OR PART of an operator. + */ + switch (ch) + { + case '*': + case '/': + case '<': + case '>': + case '=': + case '&': + case '|': + case '^': + case '~': + case ',': + case '!': + lex_tokench(lex, ch); + lex_endtoken(lex); + return (lex->tok.ttype = ch); + default: + break; + } + } + + if (ch == '.') + { + lex_tokench(lex, ch); + /* peak ahead once */ + nextch = lex_getch(lex); + if (nextch != '.') { + lex_ungetch(lex, nextch); + lex_endtoken(lex); + if (lex->flags.noops) + return (lex->tok.ttype = ch); + else + return (lex->tok.ttype = TOKEN_OPERATOR); + } + /* peak ahead again */ + nextch = lex_getch(lex); + if (nextch != '.') { + lex_ungetch(lex, nextch); + lex_ungetch(lex, '.'); + lex_endtoken(lex); + if (lex->flags.noops) + return (lex->tok.ttype = ch); + else + return (lex->tok.ttype = TOKEN_OPERATOR); + } + /* fill the token to be "..." */ + lex_tokench(lex, ch); + lex_tokench(lex, ch); + lex_endtoken(lex); + return (lex->tok.ttype = TOKEN_DOTS); + } + + if (ch == ',' || ch == '.') { + lex_tokench(lex, ch); + lex_endtoken(lex); + return (lex->tok.ttype = TOKEN_OPERATOR); + } + + if (ch == '+' || ch == '-' || /* ++, --, +=, -= and -> as well! */ + ch == '>' || ch == '<' || /* <<, >>, <=, >= and >< as well! */ + ch == '=' || ch == '!' || /* <=>, ==, != */ + ch == '&' || ch == '|' || /* &&, ||, &=, |= */ + ch == '~' || ch == '^' /* ~=, ~, ^ */ + ) { + lex_tokench(lex, ch); + nextch = lex_getch(lex); + + if ((nextch == '=' && ch != '<') || (nextch == '<' && ch == '>')) + lex_tokench(lex, nextch); + else if (nextch == ch && ch != '!') { + lex_tokench(lex, nextch); + if ((thirdch = lex_getch(lex)) == '=') + lex_tokench(lex, thirdch); + else + lex_ungetch(lex, thirdch); + } else if (ch == '<' && nextch == '=') { + lex_tokench(lex, nextch); + if ((thirdch = lex_getch(lex)) == '>') + lex_tokench(lex, thirdch); + else + lex_ungetch(lex, thirdch); + + } else if (ch == '-' && nextch == '>') { + lex_tokench(lex, nextch); + } else if (ch == '&' && nextch == '~') { + thirdch = lex_getch(lex); + if (thirdch != '=') { + lex_ungetch(lex, thirdch); + lex_ungetch(lex, nextch); + } + else { + lex_tokench(lex, nextch); + lex_tokench(lex, thirdch); + } + } + else if (lex->flags.preprocessing && + ch == '-' && util_isdigit(nextch)) + { + lex->tok.ttype = lex_finish_digit(lex, nextch); + if (lex->tok.ttype == TOKEN_INTCONST) + lex->tok.constval.i = -lex->tok.constval.i; + else + lex->tok.constval.f = -lex->tok.constval.f; + lex_endtoken(lex); + return lex->tok.ttype; + } else { + lex_ungetch(lex, nextch); + } + + lex_endtoken(lex); + return (lex->tok.ttype = TOKEN_OPERATOR); + } + + if (ch == '*' || ch == '/') /* *=, /= */ + { + lex_tokench(lex, ch); + + nextch = lex_getch(lex); + if (nextch == '=' || nextch == '*') { + lex_tokench(lex, nextch); + } else + lex_ungetch(lex, nextch); + + lex_endtoken(lex); + return (lex->tok.ttype = TOKEN_OPERATOR); + } + + if (ch == '%') { + lex_tokench(lex, ch); + lex_endtoken(lex); + return (lex->tok.ttype = TOKEN_OPERATOR); + } + + if (isident_start(ch)) + { + const char *v; + + lex_tokench(lex, ch); + if (!lex_finish_ident(lex)) { + /* error? */ + return (lex->tok.ttype = TOKEN_ERROR); + } + lex_endtoken(lex); + lex->tok.ttype = TOKEN_IDENT; + + v = lex->tok.value; + if (!strcmp(v, "void")) { + lex->tok.ttype = TOKEN_TYPENAME; + lex->tok.constval.t = TYPE_VOID; + } else if (!strcmp(v, "int")) { + lex->tok.ttype = TOKEN_TYPENAME; + lex->tok.constval.t = TYPE_INTEGER; + } else if (!strcmp(v, "float")) { + lex->tok.ttype = TOKEN_TYPENAME; + lex->tok.constval.t = TYPE_FLOAT; + } else if (!strcmp(v, "string")) { + lex->tok.ttype = TOKEN_TYPENAME; + lex->tok.constval.t = TYPE_STRING; + } else if (!strcmp(v, "entity")) { + lex->tok.ttype = TOKEN_TYPENAME; + lex->tok.constval.t = TYPE_ENTITY; + } else if (!strcmp(v, "vector")) { + lex->tok.ttype = TOKEN_TYPENAME; + lex->tok.constval.t = TYPE_VECTOR; + } else if (!strcmp(v, "_length")) { + lex->tok.ttype = TOKEN_OPERATOR; + } else { + size_t kw; + for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_qc); ++kw) { + if (!strcmp(v, keywords_qc[kw])) + return (lex->tok.ttype = TOKEN_KEYWORD); + } + if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC) { + for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_fg); ++kw) { + if (!strcmp(v, keywords_fg[kw])) + return (lex->tok.ttype = TOKEN_KEYWORD); + } + } + } + + return lex->tok.ttype; + } + + if (ch == '"') + { + lex->flags.nodigraphs = true; + if (lex->flags.preprocessing) + lex_tokench(lex, ch); + lex->tok.ttype = lex_finish_string(lex, '"'); + if (lex->flags.preprocessing) + lex_tokench(lex, ch); + while (!lex->flags.preprocessing && lex->tok.ttype == TOKEN_STRINGCONST) + { + /* Allow c style "string" "continuation" */ + ch = lex_skipwhite(lex, false); + if (ch != '"') { + lex_ungetch(lex, ch); + break; + } + + lex->tok.ttype = lex_finish_string(lex, '"'); + } + lex->flags.nodigraphs = false; + lex_endtoken(lex); + return lex->tok.ttype; + } + + if (ch == '\'') + { + /* we parse character constants like string, + * but return TOKEN_CHARCONST, or a vector type if it fits... + * Likewise actual unescaping has to be done by the parser. + * The difference is we don't allow 'char' 'continuation'. + */ + if (lex->flags.preprocessing) + lex_tokench(lex, ch); + lex->tok.ttype = lex_finish_string(lex, '\''); + if (lex->flags.preprocessing) + lex_tokench(lex, ch); + lex_endtoken(lex); + + lex->tok.ttype = TOKEN_CHARCONST; + + /* It's a vector if we can successfully scan 3 floats */ + if (util_sscanf(lex->tok.value, " %f %f %f ", + &lex->tok.constval.v.x, &lex->tok.constval.v.y, &lex->tok.constval.v.z) == 3) + + { + lex->tok.ttype = TOKEN_VECTORCONST; + } + else + { + if (!lex->flags.preprocessing && strlen(lex->tok.value) > 1) { + utf8ch_t u8char; + /* check for a valid utf8 character */ + if (!OPTS_FLAG(UTF8) || !utf8_to(&u8char, (const unsigned char *)lex->tok.value, 8)) { + if (lexwarn(lex, WARN_MULTIBYTE_CHARACTER, + ( OPTS_FLAG(UTF8) ? "invalid multibyte character sequence `%s`" + : "multibyte character: `%s`" ), + lex->tok.value)) + return (lex->tok.ttype = TOKEN_ERROR); + } + else + lex->tok.constval.i = u8char; + } + else + lex->tok.constval.i = lex->tok.value[0]; + } + + return lex->tok.ttype; + } + + if (util_isdigit(ch)) + { + lex->tok.ttype = lex_finish_digit(lex, ch); + lex_endtoken(lex); + return lex->tok.ttype; + } + + if (lex->flags.preprocessing) { + lex_tokench(lex, ch); + lex_endtoken(lex); + return (lex->tok.ttype = ch); + } + + lexerror(lex, "unknown token: `%c`", ch); + return (lex->tok.ttype = TOKEN_ERROR); +} diff --git a/lexer.h b/lexer.h index 07fa5d7..8ed85a9 100644 --- a/lexer.h +++ b/lexer.h @@ -1,59 +1,19 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * Wolfgang Bumiller - * - * 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. - */ #ifndef GMQCC_LEXER_HDR #define GMQCC_LEXER_HDR #include "gmqcc.h" -typedef struct token_s token; - -struct token_s { +struct token { int ttype; - char *value; - union { - vec3_t v; - int i; + vec3_t v; + int i; qcfloat_t f; - int t; /* type */ + qc_type t; /* type */ } constval; - -#if 0 - struct token_s *next; - struct token_s *prev; -#endif - lex_ctx_t ctx; }; -#if 0 -token* token_new(); -void token_delete(token*); -token* token_copy(const token *cp); -void token_delete_all(token *t); -token* token_copy_all(const token *cp); -#endif - /* Lexer * */ @@ -101,13 +61,13 @@ enum { TOKEN_FATAL /* internal error, eg out of memory */ }; -typedef struct { +struct frame_macro { char *name; - int value; -} frame_macro; + int value; +}; -typedef struct lex_file_s { - fs_file_t *file; +struct lex_file { + FILE *file; const char *open_string; size_t open_string_length; size_t open_string_pos; @@ -136,7 +96,7 @@ typedef struct lex_file_s { char *modelname; size_t push_line; -} lex_file; +}; lex_file* lex_open (const char *file); lex_file* lex_open_string(const char *str, size_t len, const char *name); @@ -156,7 +116,7 @@ enum { #define OP_SUFFIX 1 #define OP_PREFIX 2 -typedef struct { +struct oper_info { const char *op; unsigned int operands; unsigned int id; @@ -164,7 +124,7 @@ typedef struct { signed int prec; unsigned int flags; bool folds; -} oper_info; +}; /* * Explicit uint8_t casts since the left operand of shift operator cannot diff --git a/main.c b/main.c deleted file mode 100644 index 9a027bd..0000000 --- a/main.c +++ /dev/null @@ -1,807 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * Dale Weiler - * Wolfgang Bumiller - * - * 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 -#include - -#include "gmqcc.h" -#include "lexer.h" - -/* TODO: cleanup this whole file .. it's a fuckign mess */ - -/* set by the standard */ -const oper_info *operators = NULL; -size_t operator_count = 0; -static bool opts_output_wasset = false; - -typedef struct { char *filename; int type; } argitem; -typedef struct { char *name; char *value; } ppitem; -static argitem *items = NULL; -static ppitem *ppems = NULL; - -#define TYPE_QC 0 -#define TYPE_ASM 1 -#define TYPE_SRC 2 - -static const char *app_name; - -static void version(void) { - con_out("GMQCC %d.%d.%d Built %s %s\n" GMQCC_DEV_VERSION_STRING, - GMQCC_VERSION_MAJOR, - GMQCC_VERSION_MINOR, - GMQCC_VERSION_PATCH, - __DATE__, - __TIME__ - ); -} - -static int usage(void) { - con_out("usage: %s [options] [files...]", app_name); - con_out("options:\n" - " -h, --help show this help message\n" - " -debug turns on compiler debug messages\n" - " -memchk turns on compiler memory leak check\n"); - con_out(" -o, --output=file output file, defaults to progs.dat\n" - " -s filename add a progs.src file to be used\n"); - con_out(" -E stop after preprocessing\n"); - con_out(" -q, --quiet be less verbose\n"); - con_out(" -config file use the specified ini file\n"); - con_out(" -std=standard select one of the following standards\n" - " -std=qcc original QuakeC\n" - " -std=fteqcc fteqcc QuakeC\n" - " -std=gmqcc this compiler (default)\n"); - con_out(" -f enable a flag\n" - " -fno- disable a flag\n" - " -fhelp list possible flags\n"); - con_out(" -W enable a warning\n" - " -Wno- disable a warning\n" - " -Wall enable all warnings\n"); - con_out(" -Werror treat warnings as errors\n" - " -Werror- treat a warning as error\n" - " -Wno-error- opposite of the above\n"); - con_out(" -Whelp list possible warnings\n"); - con_out(" -O optimization level\n" - " -O enable specific optimization\n" - " -Ono- disable specific optimization\n" - " -Ohelp list optimizations\n"); - con_out(" -force-crc=num force a specific checksum into the header\n"); - con_out(" -state-fps=num emulate OP_STATE with the specified FPS\n"); - con_out(" -coverage add coverage support\n"); - return -1; -} - -/* command line parsing */ -static bool options_witharg(int *argc_, char ***argv_, char **out) { - int argc = *argc_; - char **argv = *argv_; - - if (argv[0][2]) { - *out = argv[0]+2; - return true; - } - /* eat up the next */ - if (argc < 2) /* no parameter was provided */ - return false; - - *out = argv[1]; - --*argc_; - ++*argv_; - return true; -} - -static bool options_long_witharg_all(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) { - int argc = *argc_; - char **argv = *argv_; - - size_t len = strlen(optname); - - if (strncmp(argv[0]+ds, optname, len)) - return false; - - /* it's --optname, check how the parameter is supplied */ - if (argv[0][ds+len] == '=') { - /* using --opt=param */ - *out = argv[0]+ds+len+1; - return true; - } - - if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */ - return false; - - /* using --opt param */ - *out = argv[1]; - --*argc_; - ++*argv_; - return true; -} -static bool options_long_witharg(const char *optname, int *argc_, char ***argv_, char **out) { - return options_long_witharg_all(optname, argc_, argv_, out, 2, true); -} -static bool options_long_gcc(const char *optname, int *argc_, char ***argv_, char **out) { - return options_long_witharg_all(optname, argc_, argv_, out, 1, false); -} - -static bool options_parse(int argc, char **argv) { - bool argend = false; - size_t itr; - char buffer[1024]; - char *redirout = NULL; - char *redirerr = NULL; - char *config = NULL; - char *memdumpcols = NULL; - - while (!argend && argc > 1) { - char *argarg; - argitem item; - ppitem macro; - - ++argv; - --argc; - - if (argv[0][0] == '-') { - /* All gcc-type long options */ - if (options_long_gcc("std", &argc, &argv, &argarg)) { - if (!strcmp(argarg, "gmqcc") || !strcmp(argarg, "default")) { - - opts_set(opts.flags, ADJUST_VECTOR_FIELDS, true); - opts_set(opts.flags, CORRECT_LOGIC, true); - opts_set(opts.flags, SHORT_LOGIC, true); - opts_set(opts.flags, UNTYPED_NIL, true); - opts_set(opts.flags, VARIADIC_ARGS, true); - opts_set(opts.flags, FALSE_EMPTY_STRINGS, false); - opts_set(opts.flags, TRUE_EMPTY_STRINGS, true); - opts_set(opts.flags, LOOP_LABELS, true); - opts_set(opts.flags, TRANSLATABLE_STRINGS, true); - opts_set(opts.flags, INITIALIZED_NONCONSTANTS, true); - opts_set(opts.werror, WARN_INVALID_PARAMETER_COUNT, true); - opts_set(opts.werror, WARN_MISSING_RETURN_VALUES, true); - opts_set(opts.flags, EXPRESSIONS_FOR_BUILTINS, true); - opts_set(opts.warn, WARN_BREAKDEF, true); - - - - OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_GMQCC; - - } else if (!strcmp(argarg, "qcc")) { - - opts_set(opts.flags, ADJUST_VECTOR_FIELDS, false); - opts_set(opts.flags, ASSIGN_FUNCTION_TYPES, true); - - OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_QCC; - - } else if (!strcmp(argarg, "fte") || !strcmp(argarg, "fteqcc")) { - - opts_set(opts.flags, FTEPP, true); - opts_set(opts.flags, TRANSLATABLE_STRINGS, true); - opts_set(opts.flags, ADJUST_VECTOR_FIELDS, false); - opts_set(opts.flags, ASSIGN_FUNCTION_TYPES, true); - opts_set(opts.flags, CORRECT_TERNARY, false); - opts_set(opts.warn, WARN_TERNARY_PRECEDENCE, true); - opts_set(opts.warn, WARN_BREAKDEF, true); - - OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_FTEQCC; - - } else if (!strcmp(argarg, "qccx")) { - - opts_set(opts.flags, ADJUST_VECTOR_FIELDS, false); - OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_QCCX; - - } else { - con_out("Unknown standard: %s\n", argarg); - return false; - } - continue; - } - if (options_long_gcc("force-crc", &argc, &argv, &argarg)) { - - OPTS_OPTION_BOOL(OPTION_FORCECRC) = true; - OPTS_OPTION_U16 (OPTION_FORCED_CRC) = strtol(argarg, NULL, 0); - continue; - } - if (options_long_gcc("state-fps", &argc, &argv, &argarg)) { - OPTS_OPTION_U32(OPTION_STATE_FPS) = strtol(argarg, NULL, 0); - opts_set(opts.flags, EMULATE_STATE, true); - continue; - } - if (options_long_gcc("redirout", &argc, &argv, &redirout)) { - con_change(redirout, redirerr); - continue; - } - if (options_long_gcc("redirerr", &argc, &argv, &redirerr)) { - con_change(redirout, redirerr); - continue; - } - if (options_long_gcc("config", &argc, &argv, &argarg)) { - config = argarg; - continue; - } - if (options_long_gcc("memdumpcols", &argc, &argv, &memdumpcols)) { - OPTS_OPTION_U16(OPTION_MEMDUMPCOLS) = (uint16_t)strtol(memdumpcols, NULL, 10); - continue; - } - if (options_long_gcc("progsrc", &argc, &argv, &argarg)) { - OPTS_OPTION_STR(OPTION_PROGSRC) = argarg; - continue; - } - - /* show defaults (like pathscale) */ - if (!strcmp(argv[0]+1, "show-defaults")) { - for (itr = 0; itr < COUNT_FLAGS; ++itr) { - if (!OPTS_FLAG(itr)) - continue; - - memset(buffer, 0, sizeof(buffer)); - util_strtononcmd(opts_flag_list[itr].name, buffer, strlen(opts_flag_list[itr].name) + 1); - - con_out("-f%s ", buffer); - } - for (itr = 0; itr < COUNT_WARNINGS; ++itr) { - if (!OPTS_WARN(itr)) - continue; - - memset(buffer, 0, sizeof(buffer)); - util_strtononcmd(opts_warn_list[itr].name, buffer, strlen(opts_warn_list[itr].name) + 1); - con_out("-W%s ", buffer); - } - con_out("\n"); - exit(0); - } - - if (!strcmp(argv[0]+1, "debug")) { - OPTS_OPTION_BOOL(OPTION_DEBUG) = true; - continue; - } - if (!strcmp(argv[0]+1, "dump")) { - OPTS_OPTION_BOOL(OPTION_DUMP) = true; - continue; - } - if (!strcmp(argv[0]+1, "dumpfin")) { - OPTS_OPTION_BOOL(OPTION_DUMPFIN) = true; - continue; - } - if (!strcmp(argv[0]+1, "memchk")) { - OPTS_OPTION_BOOL(OPTION_MEMCHK) = true; - continue; - } - if (!strcmp(argv[0]+1, "nocolor")) { - con_color(0); - continue; - } - if (!strcmp(argv[0]+1, "coverage")) { - OPTS_OPTION_BOOL(OPTION_COVERAGE) = true; - continue; - } - - switch (argv[0][1]) { - /* -h, show usage but exit with 0 */ - case 'h': - usage(); - exit(0); - /* break; never reached because of exit(0) */ - - case 'v': - version(); - exit(0); - - case 'E': - OPTS_OPTION_BOOL(OPTION_PP_ONLY) = true; - opts_set(opts.flags, FTEPP_PREDEFS, true); /* predefs on for -E */ - break; - - /* debug turns on -flno */ - case 'g': - opts_setflag("LNO", true); - OPTS_OPTION_BOOL(OPTION_G) = true; - break; - - case 'q': - OPTS_OPTION_BOOL(OPTION_QUIET) = true; - break; - - case 'D': - if (!strlen(argv[0]+2)) { - con_err("expected name after -D\n"); - exit(0); - } - - if (!(argarg = strchr(argv[0] + 2, '='))) { - macro.name = util_strdup(argv[0]+2); - macro.value = NULL; - } else { - *argarg='\0'; /* terminate for name */ - macro.name = util_strdup(argv[0]+2); - macro.value = util_strdup(argarg+1); - } - vec_push(ppems, macro); - break; - - /* handle all -fflags */ - case 'f': - util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1); - if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') { - con_out("Possible flags:\n\n"); - for (itr = 0; itr < COUNT_FLAGS; ++itr) { - util_strtononcmd(opts_flag_list[itr].name, buffer, sizeof(buffer)); - con_out(" -f%s\n", buffer); - } - exit(0); - } - else if (!strncmp(argv[0]+2, "NO_", 3)) { - if (!opts_setflag(argv[0]+5, false)) { - con_out("unknown flag: %s\n", argv[0]+2); - return false; - } - } - else if (!opts_setflag(argv[0]+2, true)) { - con_out("unknown flag: %s\n", argv[0]+2); - return false; - } - break; - case 'W': - util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1); - if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') { - con_out("Possible warnings:\n"); - for (itr = 0; itr < COUNT_WARNINGS; ++itr) { - util_strtononcmd(opts_warn_list[itr].name, buffer, sizeof(buffer)); - con_out(" -W%s\n", buffer); - if (itr == WARN_DEBUG) - con_out(" Warnings included by -Wall:\n"); - } - exit(0); - } - else if (!strcmp(argv[0]+2, "NO_ERROR") || - !strcmp(argv[0]+2, "NO_ERROR_ALL")) - { - for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.werror); ++itr) - opts.werror[itr] = 0; - break; - } - else if (!strcmp(argv[0]+2, "ERROR") || - !strcmp(argv[0]+2, "ERROR_ALL")) - { - opts_backup_non_Werror_all(); - for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.werror); ++itr) - opts.werror[itr] = 0xFFFFFFFFL; - opts_restore_non_Werror_all(); - break; - } - else if (!strcmp(argv[0]+2, "NONE")) { - for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.warn); ++itr) - opts.warn[itr] = 0; - break; - } - else if (!strcmp(argv[0]+2, "ALL")) { - opts_backup_non_Wall(); - for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.warn); ++itr) - opts.warn[itr] = 0xFFFFFFFFL; - opts_restore_non_Wall(); - break; - } - else if (!strncmp(argv[0]+2, "ERROR_", 6)) { - if (!opts_setwerror(argv[0]+8, true)) { - con_out("unknown warning: %s\n", argv[0]+2); - return false; - } - } - else if (!strncmp(argv[0]+2, "NO_ERROR_", 9)) { - if (!opts_setwerror(argv[0]+11, false)) { - con_out("unknown warning: %s\n", argv[0]+2); - return false; - } - } - else if (!strncmp(argv[0]+2, "NO_", 3)) { - if (!opts_setwarn(argv[0]+5, false)) { - con_out("unknown warning: %s\n", argv[0]+2); - return false; - } - } - else if (!opts_setwarn(argv[0]+2, true)) { - con_out("unknown warning: %s\n", argv[0]+2); - return false; - } - break; - - case 'O': - if (!options_witharg(&argc, &argv, &argarg)) { - con_out("option -O requires a numerical argument, or optimization name with an optional 'no-' prefix\n"); - return false; - } - if (util_isdigit(argarg[0])) { - uint32_t val = (uint32_t)strtol(argarg, NULL, 10); - OPTS_OPTION_U32(OPTION_O) = val; - opts_setoptimlevel(val); - } else { - util_strtocmd(argarg, argarg, strlen(argarg)+1); - if (!strcmp(argarg, "HELP")) { - con_out("Possible optimizations:\n"); - for (itr = 0; itr < COUNT_OPTIMIZATIONS; ++itr) { - util_strtononcmd(opts_opt_list[itr].name, buffer, sizeof(buffer)); - con_out(" -O%-20s (-O%u)\n", buffer, opts_opt_oflag[itr]); - } - exit(0); - } - else if (!strcmp(argarg, "ALL")) - opts_setoptimlevel(OPTS_OPTION_U32(OPTION_O) = 9999); - else if (!strncmp(argarg, "NO_", 3)) { - /* constant folding cannot be turned off for obvious reasons */ - if (!strcmp(argarg, "NO_CONST_FOLD") || !opts_setoptim(argarg+3, false)) { - con_out("unknown optimization: %s\n", argarg+3); - return false; - } - } - else { - if (!opts_setoptim(argarg, true)) { - con_out("unknown optimization: %s\n", argarg); - return false; - } - } - } - break; - - case 'o': - if (!options_witharg(&argc, &argv, &argarg)) { - con_out("option -o requires an argument: the output file name\n"); - return false; - } - OPTS_OPTION_STR(OPTION_OUTPUT) = argarg; - opts_output_wasset = true; - break; - - case 'a': - case 's': - item.type = argv[0][1] == 'a' ? TYPE_ASM : TYPE_SRC; - if (!options_witharg(&argc, &argv, &argarg)) { - con_out("option -a requires a filename %s\n", - (argv[0][1] == 'a' ? "containing QC-asm" : "containing a progs.src formatted list")); - return false; - } - item.filename = argarg; - vec_push(items, item); - break; - - case '-': - if (!argv[0][2]) { - /* anything following -- is considered a non-option argument */ - argend = true; - break; - } - /* All long options without arguments */ - else if (!strcmp(argv[0]+2, "help")) { - usage(); - exit(0); - } - else if (!strcmp(argv[0]+2, "version")) { - version(); - exit(0); - } - else if (!strcmp(argv[0]+2, "quiet")) { - OPTS_OPTION_BOOL(OPTION_QUIET) = true; - break; - } - else if (!strcmp(argv[0]+2, "correct")) { - OPTS_OPTION_BOOL(OPTION_CORRECTION) = true; - break; - } - else if (!strcmp(argv[0]+2, "no-correct")) { - OPTS_OPTION_BOOL(OPTION_CORRECTION) = false; - break; - } - else if (!strcmp(argv[0]+2, "add-info")) { - OPTS_OPTION_BOOL(OPTION_ADD_INFO) = true; - break; - } - else { - /* All long options with arguments */ - if (options_long_witharg("output", &argc, &argv, &argarg)) { - OPTS_OPTION_STR(OPTION_OUTPUT) = argarg; - opts_output_wasset = true; - } else { - con_out("Unknown parameter: %s\n", argv[0]); - return false; - } - } - break; - - default: - con_out("Unknown parameter: %s\n", argv[0]); - return false; - } - } - else - { - /* it's a QC filename */ - item.filename = argv[0]; - item.type = TYPE_QC; - vec_push(items, item); - } - } - opts_ini_init(config); - return true; -} - -/* returns the line number, or -1 on error */ -static bool progs_nextline(char **out, size_t *alen, fs_file_t *src) { - int len; - char *line; - char *start; - char *end; - - line = *out; - len = fs_file_getline(&line, alen, src); - if (len == -1) - return false; - - /* start at first non-blank */ - for (start = line; util_isspace(*start); ++start) {} - /* end at the first non-blank */ - for (end = start; *end && !util_isspace(*end); ++end) {} - - *out = line; - /* move the actual filename to the beginning */ - while (start != end) { - *line++ = *start++; - } - *line = 0; - return true; -} - -int main(int argc, char **argv) { - size_t itr; - int retval = 0; - bool operators_free = false; - bool progs_src = false; - fs_file_t *outfile = NULL; - struct parser_s *parser = NULL; - struct ftepp_s *ftepp = NULL; - - app_name = argv[0]; - con_init (); - opts_init("progs.dat", COMPILER_QCC, (1024 << 3)); - - util_seed(time(0)); - - if (!options_parse(argc, argv)) { - return usage(); - } - - if (OPTS_FLAG(TRUE_EMPTY_STRINGS) && OPTS_FLAG(FALSE_EMPTY_STRINGS)) { - con_err("-ftrue-empty-strings and -ffalse-empty-strings are mutually exclusive"); - exit(EXIT_FAILURE); - } - - /* the standard decides which set of operators to use */ - if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) { - operators = c_operators; - operator_count = GMQCC_ARRAY_COUNT(c_operators); - } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) { - operators = fte_operators; - operator_count = GMQCC_ARRAY_COUNT(fte_operators); - } else { - operators = qcc_operators; - operator_count = GMQCC_ARRAY_COUNT(qcc_operators); - } - - if (operators == fte_operators) { - /* fix ternary? */ - if (OPTS_FLAG(CORRECT_TERNARY)) { - oper_info *newops; - if (operators[operator_count-2].id != opid1(',') || - operators[operator_count-1].id != opid2(':','?')) - { - con_err("internal error: operator precedence table wasn't updated correctly!\n"); - exit(EXIT_FAILURE); - } - operators_free = true; - newops = (oper_info*)mem_a(sizeof(operators[0]) * operator_count); - memcpy(newops, operators, sizeof(operators[0]) * operator_count); - memcpy(&newops[operator_count-2], &operators[operator_count-1], sizeof(newops[0])); - memcpy(&newops[operator_count-1], &operators[operator_count-2], sizeof(newops[0])); - newops[operator_count-2].prec = newops[operator_count-1].prec+1; - operators = newops; - } - } - - if (OPTS_OPTION_BOOL(OPTION_DUMP)) { - for (itr = 0; itr < COUNT_FLAGS; ++itr) - con_out("Flag %s = %i\n", opts_flag_list[itr].name, OPTS_FLAG(itr)); - for (itr = 0; itr < COUNT_WARNINGS; ++itr) - con_out("Warning %s = %i\n", opts_warn_list[itr].name, OPTS_WARN(itr)); - - con_out("output = %s\n", OPTS_OPTION_STR(OPTION_OUTPUT)); - con_out("optimization level = %u\n", OPTS_OPTION_U32(OPTION_O)); - con_out("standard = %u\n", OPTS_OPTION_U32(OPTION_STANDARD)); - } - - if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) { - if (opts_output_wasset) { - outfile = fs_file_open(OPTS_OPTION_STR(OPTION_OUTPUT), "wb"); - if (!outfile) { - con_err("failed to open `%s` for writing\n", OPTS_OPTION_STR(OPTION_OUTPUT)); - retval = 1; - goto cleanup; - } - } - else { - outfile = con_default_out(); - } - } - - if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) { - if (!(parser = parser_create())) { - con_err("failed to initialize parser\n"); - retval = 1; - goto cleanup; - } - } - - if (OPTS_OPTION_BOOL(OPTION_PP_ONLY) || OPTS_FLAG(FTEPP)) { - if (!(ftepp = ftepp_create())) { - con_err("failed to initialize parser\n"); - retval = 1; - goto cleanup; - } - } - - /* add macros */ - if (OPTS_OPTION_BOOL(OPTION_PP_ONLY) || OPTS_FLAG(FTEPP)) { - for (itr = 0; itr < vec_size(ppems); itr++) { - ftepp_add_macro(ftepp, ppems[itr].name, ppems[itr].value); - mem_d(ppems[itr].name); - - /* can be null */ - if (ppems[itr].value) - mem_d(ppems[itr].value); - } - } - - if (!vec_size(items)) { - fs_file_t *src; - char *line = NULL; - size_t linelen = 0; - bool hasline = false; - - progs_src = true; - - src = fs_file_open(OPTS_OPTION_STR(OPTION_PROGSRC), "rb"); - if (!src) { - con_err("failed to open `%s` for reading\n", OPTS_OPTION_STR(OPTION_PROGSRC)); - retval = 1; - goto cleanup; - } - - while (progs_nextline(&line, &linelen, src)) { - argitem item; - - if (!line[0] || (line[0] == '/' && line[1] == '/')) - continue; - - if (hasline) { - item.filename = util_strdup(line); - item.type = TYPE_QC; - vec_push(items, item); - } else if (!opts_output_wasset) { - OPTS_OPTION_DUP(OPTION_OUTPUT) = util_strdup(line); - hasline = true; - } - } - - fs_file_close(src); - mem_d(line); - } - - if (vec_size(items)) { - if (!OPTS_OPTION_BOOL(OPTION_QUIET) && - !OPTS_OPTION_BOOL(OPTION_PP_ONLY)) - { - con_out("Mode: %s\n", (progs_src ? "progs.src" : "manual")); - con_out("There are %lu items to compile:\n", (unsigned long)vec_size(items)); - } - - for (itr = 0; itr < vec_size(items); ++itr) { - if (!OPTS_OPTION_BOOL(OPTION_QUIET) && - !OPTS_OPTION_BOOL(OPTION_PP_ONLY)) - { - con_out(" item: %s (%s)\n", - items[itr].filename, - ( (items[itr].type == TYPE_QC ? "qc" : - (items[itr].type == TYPE_ASM ? "asm" : - (items[itr].type == TYPE_SRC ? "progs.src" : - ("unknown")))))); - } - - if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) { - const char *out; - if (!ftepp_preprocess_file(ftepp, items[itr].filename)) { - retval = 1; - goto cleanup; - } - out = ftepp_get(ftepp); - if (out) - fs_file_printf(outfile, "%s", out); - ftepp_flush(ftepp); - } - else { - if (OPTS_FLAG(FTEPP)) { - const char *data; - if (!ftepp_preprocess_file(ftepp, items[itr].filename)) { - retval = 1; - goto cleanup; - } - data = ftepp_get(ftepp); - if (vec_size(data)) { - if (!parser_compile_string(parser, items[itr].filename, data, vec_size(data))) { - retval = 1; - goto cleanup; - } - } - ftepp_flush(ftepp); - } - else { - if (!parser_compile_file(parser, items[itr].filename)) { - retval = 1; - goto cleanup; - } - } - } - - if (progs_src) { - mem_d(items[itr].filename); - items[itr].filename = NULL; - } - } - - ftepp_finish(ftepp); - ftepp = NULL; - if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) { - if (!parser_finish(parser, OPTS_OPTION_STR(OPTION_OUTPUT))) { - retval = 1; - goto cleanup; - } - } - } - -cleanup: - if (ftepp) - ftepp_finish(ftepp); - con_close(); - vec_free(items); - vec_free(ppems); - - if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) - if(parser) parser_cleanup(parser); - - /* free allocated option strings */ - for (itr = 0; itr < OPTION_COUNT; itr++) - if (OPTS_OPTION_DUPED(itr)) - mem_d(OPTS_OPTION_STR(itr)); - - if (operators_free) - mem_d((void*)operators); - - lex_cleanup(); - stat_info(); - - if (!retval && compile_errors) - retval = 1; - return retval; -} diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..449067d --- /dev/null +++ b/main.cpp @@ -0,0 +1,754 @@ +#include +#include + +#include "gmqcc.h" +#include "lexer.h" + +/* TODO: cleanup this whole file .. it's a fuckign mess */ + +/* set by the standard */ +const oper_info *operators = nullptr; +size_t operator_count = 0; +static bool opts_output_wasset = false; +struct argitem { char *filename; int type; }; +struct ppitem { char *name; char *value; }; +static argitem *items = nullptr; +static ppitem *ppems = nullptr; + +#define TYPE_QC 0 +#define TYPE_ASM 1 +#define TYPE_SRC 2 + +static const char *app_name; + +static void version(void) { + con_out("GMQCC %d.%d.%d Built %s %s\n" GMQCC_DEV_VERSION_STRING, + GMQCC_VERSION_MAJOR, + GMQCC_VERSION_MINOR, + GMQCC_VERSION_PATCH, + __DATE__, + __TIME__ + ); +} + +static int usage(void) { + con_out("usage: %s [options] [files...]", app_name); + con_out("options:\n" + " -h, --help show this help message\n" + " -debug turns on compiler debug messages\n"); + con_out(" -o, --output=file output file, defaults to progs.dat\n" + " -s filename add a progs.src file to be used\n"); + con_out(" -E stop after preprocessing\n"); + con_out(" -q, --quiet be less verbose\n"); + con_out(" -config file use the specified ini file\n"); + con_out(" -std=standard select one of the following standards\n" + " -std=qcc original QuakeC\n" + " -std=fteqcc fteqcc QuakeC\n" + " -std=gmqcc this compiler (default)\n"); + con_out(" -f enable a flag\n" + " -fno- disable a flag\n" + " -fhelp list possible flags\n"); + con_out(" -W enable a warning\n" + " -Wno- disable a warning\n" + " -Wall enable all warnings\n"); + con_out(" -Werror treat warnings as errors\n" + " -Werror- treat a warning as error\n" + " -Wno-error- opposite of the above\n"); + con_out(" -Whelp list possible warnings\n"); + con_out(" -O optimization level\n" + " -O enable specific optimization\n" + " -Ono- disable specific optimization\n" + " -Ohelp list optimizations\n"); + con_out(" -force-crc=num force a specific checksum into the header\n"); + con_out(" -state-fps=num emulate OP_STATE with the specified FPS\n"); + con_out(" -coverage add coverage support\n"); + return -1; +} + +/* command line parsing */ +static bool options_witharg(int *argc_, char ***argv_, char **out) { + int argc = *argc_; + char **argv = *argv_; + + if (argv[0][2]) { + *out = argv[0]+2; + return true; + } + /* eat up the next */ + if (argc < 2) /* no parameter was provided */ + return false; + + *out = argv[1]; + --*argc_; + ++*argv_; + return true; +} + +static bool options_long_witharg_all(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) { + int argc = *argc_; + char **argv = *argv_; + + size_t len = strlen(optname); + + if (strncmp(argv[0]+ds, optname, len)) + return false; + + /* it's --optname, check how the parameter is supplied */ + if (argv[0][ds+len] == '=') { + /* using --opt=param */ + *out = argv[0]+ds+len+1; + return true; + } + + if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */ + return false; + + /* using --opt param */ + *out = argv[1]; + --*argc_; + ++*argv_; + return true; +} +static bool options_long_witharg(const char *optname, int *argc_, char ***argv_, char **out) { + return options_long_witharg_all(optname, argc_, argv_, out, 2, true); +} +static bool options_long_gcc(const char *optname, int *argc_, char ***argv_, char **out) { + return options_long_witharg_all(optname, argc_, argv_, out, 1, false); +} + +static bool options_parse(int argc, char **argv) { + bool argend = false; + size_t itr; + char buffer[1024]; + char *config = nullptr; + + while (!argend && argc > 1) { + char *argarg; + argitem item; + ppitem macro; + + ++argv; + --argc; + + if (argv[0][0] == '-') { + /* All gcc-type long options */ + if (options_long_gcc("std", &argc, &argv, &argarg)) { + if (!strcmp(argarg, "gmqcc") || !strcmp(argarg, "default")) { + + opts_set(opts.flags, ADJUST_VECTOR_FIELDS, true); + opts_set(opts.flags, CORRECT_LOGIC, true); + opts_set(opts.flags, SHORT_LOGIC, true); + opts_set(opts.flags, UNTYPED_NIL, true); + opts_set(opts.flags, VARIADIC_ARGS, true); + opts_set(opts.flags, FALSE_EMPTY_STRINGS, false); + opts_set(opts.flags, TRUE_EMPTY_STRINGS, true); + opts_set(opts.flags, LOOP_LABELS, true); + opts_set(opts.flags, TRANSLATABLE_STRINGS, true); + opts_set(opts.flags, INITIALIZED_NONCONSTANTS, true); + opts_set(opts.werror, WARN_INVALID_PARAMETER_COUNT, true); + opts_set(opts.werror, WARN_MISSING_RETURN_VALUES, true); + opts_set(opts.flags, EXPRESSIONS_FOR_BUILTINS, true); + opts_set(opts.warn, WARN_BREAKDEF, true); + + + + OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_GMQCC; + + } else if (!strcmp(argarg, "qcc")) { + + opts_set(opts.flags, ADJUST_VECTOR_FIELDS, false); + opts_set(opts.flags, ASSIGN_FUNCTION_TYPES, true); + + OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_QCC; + + } else if (!strcmp(argarg, "fte") || !strcmp(argarg, "fteqcc")) { + + opts_set(opts.flags, FTEPP, true); + opts_set(opts.flags, TRANSLATABLE_STRINGS, true); + opts_set(opts.flags, ADJUST_VECTOR_FIELDS, false); + opts_set(opts.flags, ASSIGN_FUNCTION_TYPES, true); + opts_set(opts.flags, CORRECT_TERNARY, false); + opts_set(opts.warn, WARN_TERNARY_PRECEDENCE, true); + opts_set(opts.warn, WARN_BREAKDEF, true); + + OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_FTEQCC; + + } else if (!strcmp(argarg, "qccx")) { + + opts_set(opts.flags, ADJUST_VECTOR_FIELDS, false); + OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_QCCX; + + } else { + con_out("Unknown standard: %s\n", argarg); + return false; + } + continue; + } + if (options_long_gcc("force-crc", &argc, &argv, &argarg)) { + + OPTS_OPTION_BOOL(OPTION_FORCECRC) = true; + OPTS_OPTION_U16 (OPTION_FORCED_CRC) = strtol(argarg, nullptr, 0); + continue; + } + if (options_long_gcc("state-fps", &argc, &argv, &argarg)) { + OPTS_OPTION_U32(OPTION_STATE_FPS) = strtol(argarg, nullptr, 0); + opts_set(opts.flags, EMULATE_STATE, true); + continue; + } + if (options_long_gcc("config", &argc, &argv, &argarg)) { + config = argarg; + continue; + } + if (options_long_gcc("progsrc", &argc, &argv, &argarg)) { + OPTS_OPTION_STR(OPTION_PROGSRC) = argarg; + continue; + } + + /* show defaults (like pathscale) */ + if (!strcmp(argv[0]+1, "show-defaults")) { + for (itr = 0; itr < COUNT_FLAGS; ++itr) { + if (!OPTS_FLAG(itr)) + continue; + + memset(buffer, 0, sizeof(buffer)); + util_strtononcmd(opts_flag_list[itr].name, buffer, strlen(opts_flag_list[itr].name) + 1); + + con_out("-f%s ", buffer); + } + for (itr = 0; itr < COUNT_WARNINGS; ++itr) { + if (!OPTS_WARN(itr)) + continue; + + memset(buffer, 0, sizeof(buffer)); + util_strtononcmd(opts_warn_list[itr].name, buffer, strlen(opts_warn_list[itr].name) + 1); + con_out("-W%s ", buffer); + } + con_out("\n"); + exit(0); + } + + if (!strcmp(argv[0]+1, "debug")) { + OPTS_OPTION_BOOL(OPTION_DEBUG) = true; + continue; + } + if (!strcmp(argv[0]+1, "dump")) { + OPTS_OPTION_BOOL(OPTION_DUMP) = true; + continue; + } + if (!strcmp(argv[0]+1, "dumpfin")) { + OPTS_OPTION_BOOL(OPTION_DUMPFIN) = true; + continue; + } + if (!strcmp(argv[0]+1, "nocolor")) { + con_color(0); + continue; + } + if (!strcmp(argv[0]+1, "coverage")) { + OPTS_OPTION_BOOL(OPTION_COVERAGE) = true; + continue; + } + + switch (argv[0][1]) { + /* -h, show usage but exit with 0 */ + case 'h': + usage(); + exit(0); + /* break; never reached because of exit(0) */ + + case 'v': + version(); + exit(0); + + case 'E': + OPTS_OPTION_BOOL(OPTION_PP_ONLY) = true; + opts_set(opts.flags, FTEPP_PREDEFS, true); /* predefs on for -E */ + break; + + /* debug turns on -flno */ + case 'g': + opts_setflag("LNO", true); + OPTS_OPTION_BOOL(OPTION_G) = true; + break; + + case 'q': + OPTS_OPTION_BOOL(OPTION_QUIET) = true; + break; + + case 'D': + if (!strlen(argv[0]+2)) { + con_err("expected name after -D\n"); + exit(0); + } + + if (!(argarg = strchr(argv[0] + 2, '='))) { + macro.name = util_strdup(argv[0]+2); + macro.value = nullptr; + } else { + *argarg='\0'; /* terminate for name */ + macro.name = util_strdup(argv[0]+2); + macro.value = util_strdup(argarg+1); + } + vec_push(ppems, macro); + break; + + /* handle all -fflags */ + case 'f': + util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1); + if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') { + con_out("Possible flags:\n\n"); + for (itr = 0; itr < COUNT_FLAGS; ++itr) { + util_strtononcmd(opts_flag_list[itr].name, buffer, sizeof(buffer)); + con_out(" -f%s\n", buffer); + } + exit(0); + } + else if (!strncmp(argv[0]+2, "NO_", 3)) { + if (!opts_setflag(argv[0]+5, false)) { + con_out("unknown flag: %s\n", argv[0]+2); + return false; + } + } + else if (!opts_setflag(argv[0]+2, true)) { + con_out("unknown flag: %s\n", argv[0]+2); + return false; + } + break; + case 'W': + util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1); + if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') { + con_out("Possible warnings:\n"); + for (itr = 0; itr < COUNT_WARNINGS; ++itr) { + util_strtononcmd(opts_warn_list[itr].name, buffer, sizeof(buffer)); + con_out(" -W%s\n", buffer); + if (itr == WARN_DEBUG) + con_out(" Warnings included by -Wall:\n"); + } + exit(0); + } + else if (!strcmp(argv[0]+2, "NO_ERROR") || + !strcmp(argv[0]+2, "NO_ERROR_ALL")) + { + for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.werror); ++itr) + opts.werror[itr] = 0; + break; + } + else if (!strcmp(argv[0]+2, "ERROR") || + !strcmp(argv[0]+2, "ERROR_ALL")) + { + opts_backup_non_Werror_all(); + for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.werror); ++itr) + opts.werror[itr] = 0xFFFFFFFFL; + opts_restore_non_Werror_all(); + break; + } + else if (!strcmp(argv[0]+2, "NONE")) { + for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.warn); ++itr) + opts.warn[itr] = 0; + break; + } + else if (!strcmp(argv[0]+2, "ALL")) { + opts_backup_non_Wall(); + for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.warn); ++itr) + opts.warn[itr] = 0xFFFFFFFFL; + opts_restore_non_Wall(); + break; + } + else if (!strncmp(argv[0]+2, "ERROR_", 6)) { + if (!opts_setwerror(argv[0]+8, true)) { + con_out("unknown warning: %s\n", argv[0]+2); + return false; + } + } + else if (!strncmp(argv[0]+2, "NO_ERROR_", 9)) { + if (!opts_setwerror(argv[0]+11, false)) { + con_out("unknown warning: %s\n", argv[0]+2); + return false; + } + } + else if (!strncmp(argv[0]+2, "NO_", 3)) { + if (!opts_setwarn(argv[0]+5, false)) { + con_out("unknown warning: %s\n", argv[0]+2); + return false; + } + } + else if (!opts_setwarn(argv[0]+2, true)) { + con_out("unknown warning: %s\n", argv[0]+2); + return false; + } + break; + + case 'O': + if (!options_witharg(&argc, &argv, &argarg)) { + con_out("option -O requires a numerical argument, or optimization name with an optional 'no-' prefix\n"); + return false; + } + if (util_isdigit(argarg[0])) { + uint32_t val = (uint32_t)strtol(argarg, nullptr, 10); + OPTS_OPTION_U32(OPTION_O) = val; + opts_setoptimlevel(val); + } else { + util_strtocmd(argarg, argarg, strlen(argarg)+1); + if (!strcmp(argarg, "HELP")) { + con_out("Possible optimizations:\n"); + for (itr = 0; itr < COUNT_OPTIMIZATIONS; ++itr) { + util_strtononcmd(opts_opt_list[itr].name, buffer, sizeof(buffer)); + con_out(" -O%-20s (-O%u)\n", buffer, opts_opt_oflag[itr]); + } + exit(0); + } + else if (!strcmp(argarg, "ALL")) + opts_setoptimlevel(OPTS_OPTION_U32(OPTION_O) = 9999); + else if (!strncmp(argarg, "NO_", 3)) { + /* constant folding cannot be turned off for obvious reasons */ + if (!strcmp(argarg, "NO_CONST_FOLD") || !opts_setoptim(argarg+3, false)) { + con_out("unknown optimization: %s\n", argarg+3); + return false; + } + } + else { + if (!opts_setoptim(argarg, true)) { + con_out("unknown optimization: %s\n", argarg); + return false; + } + } + } + break; + + case 'o': + if (!options_witharg(&argc, &argv, &argarg)) { + con_out("option -o requires an argument: the output file name\n"); + return false; + } + OPTS_OPTION_STR(OPTION_OUTPUT) = argarg; + opts_output_wasset = true; + break; + + case 'a': + case 's': + item.type = argv[0][1] == 'a' ? TYPE_ASM : TYPE_SRC; + if (!options_witharg(&argc, &argv, &argarg)) { + con_out("option -a requires a filename %s\n", + (argv[0][1] == 'a' ? "containing QC-asm" : "containing a progs.src formatted list")); + return false; + } + item.filename = argarg; + vec_push(items, item); + break; + + case '-': + if (!argv[0][2]) { + /* anything following -- is considered a non-option argument */ + argend = true; + break; + } + /* All long options without arguments */ + else if (!strcmp(argv[0]+2, "help")) { + usage(); + exit(0); + } + else if (!strcmp(argv[0]+2, "version")) { + version(); + exit(0); + } + else if (!strcmp(argv[0]+2, "quiet")) { + OPTS_OPTION_BOOL(OPTION_QUIET) = true; + break; + } + else if (!strcmp(argv[0]+2, "add-info")) { + OPTS_OPTION_BOOL(OPTION_ADD_INFO) = true; + break; + } + else { + /* All long options with arguments */ + if (options_long_witharg("output", &argc, &argv, &argarg)) { + OPTS_OPTION_STR(OPTION_OUTPUT) = argarg; + opts_output_wasset = true; + } else { + con_out("Unknown parameter: %s\n", argv[0]); + return false; + } + } + break; + + default: + con_out("Unknown parameter: %s\n", argv[0]); + return false; + } + } + else + { + /* it's a QC filename */ + item.filename = argv[0]; + item.type = TYPE_QC; + vec_push(items, item); + } + } + opts_ini_init(config); + return true; +} + +/* returns the line number, or -1 on error */ +static bool progs_nextline(char **out, size_t *alen, FILE *src) { + int len; + char *line; + char *start; + char *end; + + line = *out; + len = util_getline(&line, alen, src); + if (len == -1) + return false; + + /* start at first non-blank */ + for (start = line; util_isspace(*start); ++start) {} + /* end at the first non-blank */ + for (end = start; *end && !util_isspace(*end); ++end) {} + + *out = line; + /* move the actual filename to the beginning */ + while (start != end) { + *line++ = *start++; + } + *line = 0; + return true; +} + +int main(int argc, char **argv) { + size_t itr; + int retval = 0; + bool operators_free = false; + bool progs_src = false; + FILE *outfile = nullptr; + parser_t *parser = nullptr; + ftepp_t *ftepp = nullptr; + + app_name = argv[0]; + con_init (); + opts_init("progs.dat", COMPILER_QCC, (1024 << 3)); + + util_seed(time(0)); + + if (!options_parse(argc, argv)) { + return usage(); + } + + if (OPTS_FLAG(TRUE_EMPTY_STRINGS) && OPTS_FLAG(FALSE_EMPTY_STRINGS)) { + con_err("-ftrue-empty-strings and -ffalse-empty-strings are mutually exclusive"); + exit(EXIT_FAILURE); + } + + /* the standard decides which set of operators to use */ + if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) { + operators = c_operators; + operator_count = GMQCC_ARRAY_COUNT(c_operators); + } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) { + operators = fte_operators; + operator_count = GMQCC_ARRAY_COUNT(fte_operators); + } else { + operators = qcc_operators; + operator_count = GMQCC_ARRAY_COUNT(qcc_operators); + } + + if (operators == fte_operators) { + /* fix ternary? */ + if (OPTS_FLAG(CORRECT_TERNARY)) { + oper_info *newops; + if (operators[operator_count-2].id != opid1(',') || + operators[operator_count-1].id != opid2(':','?')) + { + con_err("internal error: operator precedence table wasn't updated correctly!\n"); + exit(EXIT_FAILURE); + } + operators_free = true; + newops = (oper_info*)mem_a(sizeof(operators[0]) * operator_count); + memcpy(newops, operators, sizeof(operators[0]) * operator_count); + memcpy(&newops[operator_count-2], &operators[operator_count-1], sizeof(newops[0])); + memcpy(&newops[operator_count-1], &operators[operator_count-2], sizeof(newops[0])); + newops[operator_count-2].prec = newops[operator_count-1].prec+1; + operators = newops; + } + } + + if (OPTS_OPTION_BOOL(OPTION_DUMP)) { + for (itr = 0; itr < COUNT_FLAGS; ++itr) + con_out("Flag %s = %i\n", opts_flag_list[itr].name, OPTS_FLAG(itr)); + for (itr = 0; itr < COUNT_WARNINGS; ++itr) + con_out("Warning %s = %i\n", opts_warn_list[itr].name, OPTS_WARN(itr)); + + con_out("output = %s\n", OPTS_OPTION_STR(OPTION_OUTPUT)); + con_out("optimization level = %u\n", OPTS_OPTION_U32(OPTION_O)); + con_out("standard = %u\n", OPTS_OPTION_U32(OPTION_STANDARD)); + } + + if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) { + if (opts_output_wasset) { + outfile = fopen(OPTS_OPTION_STR(OPTION_OUTPUT), "wb"); + if (!outfile) { + con_err("failed to open `%s` for writing\n", OPTS_OPTION_STR(OPTION_OUTPUT)); + retval = 1; + goto cleanup; + } + } + else { + outfile = con_default_out(); + } + } + + if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) { + if (!(parser = parser_create())) { + con_err("failed to initialize parser\n"); + retval = 1; + goto cleanup; + } + } + + if (OPTS_OPTION_BOOL(OPTION_PP_ONLY) || OPTS_FLAG(FTEPP)) { + if (!(ftepp = ftepp_create())) { + con_err("failed to initialize parser\n"); + retval = 1; + goto cleanup; + } + } + + /* add macros */ + if (OPTS_OPTION_BOOL(OPTION_PP_ONLY) || OPTS_FLAG(FTEPP)) { + for (itr = 0; itr < vec_size(ppems); itr++) { + ftepp_add_macro(ftepp, ppems[itr].name, ppems[itr].value); + mem_d(ppems[itr].name); + + /* can be null */ + if (ppems[itr].value) + mem_d(ppems[itr].value); + } + } + + if (!vec_size(items)) { + FILE *src; + char *line = nullptr; + size_t linelen = 0; + bool hasline = false; + + progs_src = true; + + src = fopen(OPTS_OPTION_STR(OPTION_PROGSRC), "rb"); + if (!src) { + con_err("failed to open `%s` for reading\n", OPTS_OPTION_STR(OPTION_PROGSRC)); + retval = 1; + goto cleanup; + } + + while (progs_nextline(&line, &linelen, src)) { + argitem item; + + if (!line[0] || (line[0] == '/' && line[1] == '/')) + continue; + + if (hasline) { + item.filename = util_strdup(line); + item.type = TYPE_QC; + vec_push(items, item); + } else if (!opts_output_wasset) { + OPTS_OPTION_DUP(OPTION_OUTPUT) = util_strdup(line); + hasline = true; + } + } + + fclose(src); + mem_d(line); + } + + if (vec_size(items)) { + if (!OPTS_OPTION_BOOL(OPTION_QUIET) && + !OPTS_OPTION_BOOL(OPTION_PP_ONLY)) + { + con_out("Mode: %s\n", (progs_src ? "progs.src" : "manual")); + con_out("There are %lu items to compile:\n", (unsigned long)vec_size(items)); + } + + for (itr = 0; itr < vec_size(items); ++itr) { + if (!OPTS_OPTION_BOOL(OPTION_QUIET) && + !OPTS_OPTION_BOOL(OPTION_PP_ONLY)) + { + con_out(" item: %s (%s)\n", + items[itr].filename, + ( (items[itr].type == TYPE_QC ? "qc" : + (items[itr].type == TYPE_ASM ? "asm" : + (items[itr].type == TYPE_SRC ? "progs.src" : + ("unknown")))))); + } + + if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) { + const char *out; + if (!ftepp_preprocess_file(ftepp, items[itr].filename)) { + retval = 1; + goto cleanup; + } + out = ftepp_get(ftepp); + if (out) + fprintf(outfile, "%s", out); + ftepp_flush(ftepp); + } + else { + if (OPTS_FLAG(FTEPP)) { + const char *data; + if (!ftepp_preprocess_file(ftepp, items[itr].filename)) { + retval = 1; + goto cleanup; + } + data = ftepp_get(ftepp); + if (vec_size(data)) { + if (!parser_compile_string(parser, items[itr].filename, data, vec_size(data))) { + retval = 1; + goto cleanup; + } + } + ftepp_flush(ftepp); + } + else { + if (!parser_compile_file(parser, items[itr].filename)) { + retval = 1; + goto cleanup; + } + } + } + + if (progs_src) { + mem_d(items[itr].filename); + items[itr].filename = nullptr; + } + } + + ftepp_finish(ftepp); + ftepp = nullptr; + if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) { + if (!parser_finish(parser, OPTS_OPTION_STR(OPTION_OUTPUT))) { + retval = 1; + goto cleanup; + } + } + } + +cleanup: + if (ftepp) + ftepp_finish(ftepp); + con_close(); + vec_free(items); + vec_free(ppems); + + if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) + if(parser) parser_cleanup(parser); + + /* free allocated option strings */ + for (itr = 0; itr < OPTION_COUNT; itr++) + if (OPTS_OPTION_DUPED(itr)) + mem_d(OPTS_OPTION_STR(itr)); + + if (operators_free) + mem_d((void*)operators); + + lex_cleanup(); + + if (!retval && compile_errors) + retval = 1; + return retval; +} diff --git a/misc/check-proj.sh b/misc/check-proj.sh deleted file mode 100755 index b43ec7a..0000000 --- a/misc/check-proj.sh +++ /dev/null @@ -1,140 +0,0 @@ -#!/bin/sh - -host="gmqcc.qc.to" -location="$host/files" -list="$location/files" -hashes="$location/hashes" -options="$location/options" - -#download required things -download_list=$(wget -qO- ${list}) -download_hashes=$(wget -qO- ${hashes}) -download_options=$(wget -qO- ${options}) - -download() { - local old="$PWD" - cd ~/.gmqcc/testsuite - echo "$download_list" | while read -r line - do - echo "downloading $line ..." - wget -q "${location}/$line" - done - - echo "$download_hashes" > ~/.gmqcc/testsuite/hashes - echo "$download_options" > ~/.gmqcc/testsuite/options - - cd "$old" -} - -if [ -z "$download_list" -o -z "$download_hashes" -o -z "$download_options" ]; then - echo "failed to download required information to check projects." - - if [ "$(ping -q -c1 "${host}")" ]; then - echo "host ${host} seems to be up but missing required files." - echo "please file bug report at: github.com/graphitemaster/gmqcc" - else - echo "host ${host} seems to be down, please try again later." - fi - - echo "aborting" - exit 1 -fi - -# we have existing contents around -if [ -f ~/.gmqcc/testsuite/hashes -a -f ~/.gmqcc/testsuite/options ]; then - echo "$download_hashes" > /tmp/gmqcc_download_hashes - echo "$download_options" > /tmp/gmqcc_download_options - - diff -u ~/.gmqcc/testsuite/hashes /tmp/gmqcc_download_hashes > /dev/null - check_hash=$? - diff -u ~/.gmqcc/testsuite/options /tmp/gmqcc_download_options > /dev/null - check_opts=$? - - if [ $check_hash -ne 0 -o $check_opts -ne 0 ]; then - echo "consistency errors in hashes (possible update), obtaining fresh contents" - rm -rf ~/.gmqcc/testsuite/projects - rm ~/.gmqcc/testsuite/*.zip - - download - fi -else - # do we even have the directory - echo "preparing project testsuite for the first time" - if [ ! -d ~/.gmqcc/testsuite ]; then - mkdir -p ~/.gmqcc/testsuite - fi - - download -fi - -if [ ! -d ~/.gmqcc/testsuite/projects ]; then - mkdir -p ~/.gmqcc/testsuite/projects - old="$PWD" - cd ~/.gmqcc/testsuite/projects - echo "$(ls ../ | grep -v '^hashes$' | grep -v '^projects$' | grep -v '^options$')" | while read -r line - do - echo "extracting project $line" - mkdir "$(echo "$line" | sed 's/\(.*\)\..*/\1/')" - unzip -qq "../$line" -d $(echo "$line" | sed 's/\(.*\)\..*/\1/') - done - cd "$old" -else - echo "previous state exists, using it" -fi - -# compile projects in those directories -gmqcc_bin="gmqcc" -env -i type gmqcc 1>/dev/null 2>&1 || { - if [ -f ../gmqcc ]; then - echo "previous build of gmqcc exists, using it" - gmqcc_bin="$(pwd)/../gmqcc" - elif [ -f ./gmqcc ]; then - echo "previous build of gmqcc exists, using it" - gmqcc_bin="$(pwd)/gmqcc" - else - echo "gmqcc not installed and previous build doesn't exist" - echo "please run make, or make install" - exit 1 - fi -} - -end_dir="$PWD" -cd ~/.gmqcc/testsuite/projects -start="$PWD" -find . -maxdepth 1 -mindepth 1 -type d | while read -r line -do - line="${line#./}" - echo -n "compiling $line... " - cd "${start}/${line}" - - # does the project have multiple subprojects? - if [ -f dirs ]; then - echo "" - cat dirs | while read -r dir - do - # change to subproject - echo -n " compiling $dir... " - old="$PWD" - cd "$dir" - cmd="$(cat ../../../options | grep "$line:" | awk '{print substr($0, index($0, $2))}')" - "$gmqcc_bin" $cmd > /dev/null 2>&1 - if [ $? -ne 0 ]; then - echo "error" - else - echo "success" - fi - cd "$old" - done - # nope only one project - else - cmd="$(cat ../../options | grep "$line:" | awk '{print substr($0, index($0, $2))}')" - "$gmqcc_bin" $cmd > /dev/null 2>&1 - if [ $? -ne 0 ]; then - echo "error" - else - echo "success" - fi - fi -done - -cd "$end_dir" diff --git a/misc/nexuiz_export.sh b/misc/nexuiz_export.sh deleted file mode 100755 index a07a7af..0000000 --- a/misc/nexuiz_export.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/sh - -if [ ! -d qcsrc ]; then - echo "failed to find qcsrc directory in $(pwd), please run this script" - echo "from nexuiz data directory" - exit 1 -else - # ensure this is actually a xonotic repo - pushd qcsrc > /dev/null - if [ ! -d client -o ! -d common -o ! -d menu -o ! -d server -o ! -d warpzonelib ]; then - echo "this doesnt look like a nexuiz source tree, aborting" - popd > /dev/null - exit 1 - fi -fi - -echo -n "removing redundant files ..." -rm -f nexuiz.ncb -rm -f nexuiz.sln -rm -f nexuiz.suo -rm -f nexuiz.vcproj -rm -f nexuiz.vcproj.user -echo "complete" - -echo -n "creating projects ..." -echo "client" > dirs -echo "server" >> dirs -echo "menu" >> dirs - -echo "complete" - -echo -n "creating zip archive ..." -zip -r -9 ../nexuiz.zip * > /dev/null -echo "complete" - -popd > /dev/null -echo "finished!" diff --git a/misc/xonotic_export.sh b/misc/xonotic_export.sh deleted file mode 100755 index 5cd3921..0000000 --- a/misc/xonotic_export.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/sh - -if [ ! -d qcsrc ]; then - echo "failed to find qcsrc directory in $(pwd), please run this script" - echo "from xonotic-data.pk3dir" - exit 1 -else - # ensure this is actually a xonotic repo - pushd qcsrc > /dev/null - if [ ! -d client -o ! -d common -o ! -d dpdefs -o ! -d menu -o ! -d server -o ! -d warpzonelib ]; then - echo "this doesnt look like a xonotic source tree, aborting" - popd > /dev/null - exit 1 - fi -fi - -# force reset and update -git rev-parse -if [ $? -ne 0 ]; then - echo "not a git directory, continuing without rebase" -else - echo -n "resetting git state and updating ... " - git reset --hard HEAD > /dev/null 2>&1 - git pull > /dev/null 2>&1 - echo "complete" -fi - -echo -n "generate precache for csqc ..." -./collect-precache.sh > /dev/null 2>&1 -echo "complete" - -echo -n "removing redundant files ..." -rm -f Makefile -rm -f autocvarize-update.sh -rm -f autocvarize.pl -rm -f collect-precache.sh -rm -f fteqcc-bugs.qc -rm -f i18n-badwords.txt -rm -f i18n-guide.txt -echo "complete" - -echo -n "creating projects ..." -echo "client" > dirs -echo "server" >> dirs -echo "menu" >> dirs - -echo "complete" - -echo -n "creating zip archive ..." -zip -r -9 ../xonotic.zip * > /dev/null -echo "complete" - -popd > /dev/null -echo "finished!" diff --git a/msvc.c b/msvc.c deleted file mode 100644 index cb13cc2..0000000 --- a/msvc.c +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * 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. - */ -#define GMQCC_PLATFORM_HEADER -#include -#include - -#include "platform.h" - -#define CTIME_BUFFER 64 -#define GETENV_BUFFER 4096 -#define STRERROR_BUFFER 128 - -static void **platform_mem_pool = NULL; -static void platform_mem_atexit() { - size_t i; - for (i = 0; i < vec_size(platform_mem_pool); i++) - mem_d(platform_mem_pool[i]); - vec_free(platform_mem_pool); -} - -static void *platform_mem_allocate(size_t bytes) { - void *mem = NULL; - if (!platform_mem_pool) { - atexit(&platform_mem_atexit); - vec_push(platform_mem_pool, NULL); - } - - mem = mem_a(bytes); - vec_push(platform_mem_pool, mem); - - return mem; -} - -int platform_vsnprintf(char *buffer, size_t bytes, const char *format, va_list arg) { - return vsnprintf_s(buffer, bytes, bytes, format, arg); -} - -int platform_vsscanf(const char *str, const char *format, va_list va) { - return vsscanf_s(str, format, va); -} - -const struct tm *platform_localtime(const time_t *timer) { - struct tm *t; - t = (struct tm*)platform_mem_allocate(sizeof(struct tm)); - localtime_s(&t, timer); - return t; -} - -const char *platform_ctime(const time_t *timer) { - char *buffer = (char *)platform_mem_allocate(CTIME_BUFFER); - ctime_s(buffer, CTIME_BUFFER, timer); - return buffer; -} - -char *platform_strncat(char *dest, const char *src, size_t num) { - return strncat_s(dest, num, src, _TRUNCATE); -} - -const char *platform_getenv(const char *var) { - char *buffer = (char *)platform_mem_allocate(GETENV_BUFFER); - size_t size; - getenv_s(&size, buffer, GETENV_BUFFER, var); - return buffer; -} - -/* - * TODO: this isn't exactly 'accurate' for MSVC but it seems to work, - * at least to some extent. - */ -int platform_vasprintf(char **dat, const char *fmt, va_list args) { - int ret; - int len; - char *tmp = NULL; - - if ((len = _vscprintf(fmt, args)) < 0) { - *dat = NULL; - return -1; - } - - tmp = (char*)mem_a(len + 1); - if ((ret = _vsnprintf_s(tmp, len+1, len+1, fmt, args)) != len) { - mem_d(tmp); - *dat = NULL; - return -1; - } - *dat = tmp; - return len; -} - -char *platform_strcat(char *dest, const char *src) { - strcat_s(dest, strlen(src), src); - return dest; -} - -char *platform_strncpy(char *dest, const char *src, size_t num) { - strncpy_s(dest, num, src, num); - return dest; -} - -const char *platform_strerror(int err) { - char *buffer = (char*)platform_mem_allocate(STRERROR_BUFFER); - strerror_s(buffer, STRERROR_BUFFER, err); - return buffer; -} - -FILE *platform_fopen(const char *filename, const char *mode) { - FILE *handle; - return (fopen_s(&handle, filename, mode) != 0) ? NULL : handle; -} - -size_t platform_fread(void *ptr, size_t size, size_t count, FILE *stream) { - return fread_s(ptr, size, size, count, stream); -} - -size_t platform_fwrite(const void *ptr, size_t size, size_t count, FILE *stream) { - return fwrite(ptr, size, count, stream); -} - -int platform_fflush(FILE *stream) { - return fflush(stream); -} - -int platform_vfprintf(FILE *stream, const char *format, va_list arg) { - return vfprintf_s(stream, format, arg); -} - -int platform_fclose(FILE *stream) { - return fclose(stream); -} - -int platform_ferror(FILE *stream) { - return ferror(stream); -} - -int platform_fgetc(FILE *stream) { - return fgetc(stream); -} - -int platform_fputs(const char *str, FILE *stream) { - return fputs(str, stream); -} - -int platform_fseek(FILE *stream, long offset, int origin) { - return fseek(stream, offset, origin); -} - -long platform_ftell(FILE *stream) { - return ftell(stream); -} - -int platform_mkdir(const char *path, int mode) { - return mkdir(path, mode); -} - -DIR *platform_opendir(const char *path) { - DIR *dir = (DIR*)mem_a(sizeof(DIR) + strlen(path)); - if (!dir) - return NULL; - - platform_strncpy(dir->dd_name, path, strlen(path)); - return dir; -} - -int platform_closedir(DIR *dir) { - FindClose((HANDLE)dir->dd_handle); - mem_d((void*)dir); - return 0; -} - -struct dirent *platform_readdir(DIR *dir) { - WIN32_FIND_DATA info; - struct dirent *data; - int ret; - - if (!dir->dd_handle) { - char *dirname; - if (*dir->dd_name) { - size_t n = strlen(dir->dd_name); - if ((dir = (char*)mem_a(n+5))) { - platform_strncpy(dirname, dir->dd_name, n); - platform_strncpy(dirname + n, "\\*.*", 4); - } - } else { - if (!(dirname = util_strdup("\\*.*"))) - return NULL; - } - - dir->dd_handle = (long)FindFirstFile(dirname, &info); - mem_d(dirname); - ret = !(!dir->dd_handle); - } else if (dir->dd_handle != -11) { - ret = FindNextFile((HANDLE)dir->dd_handle, &info); - } else { - ret = 0; - } - - if (!ret) - return NULL; - - if ((data = (struct dirent*)mem_a(sizeof(struct dirent)))) { - platform_strncpy(data->d_name, info.cFileName, FILENAME_MAX - 1); - data->d_name[FILENAME_MAX - 1] = '\0'; - data->d_namelen = strlen(data->d_name); - } - - return data; -} - -int platform_istty(int fd) { - return _isatty(fd); -} diff --git a/msvc/gmqcc.sln b/msvc/gmqcc.sln deleted file mode 100755 index 39daed8..0000000 --- a/msvc/gmqcc.sln +++ /dev/null @@ -1,38 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gmqcc", "gmqcc\gmqcc.vcxproj", "{A6BD74E1-31BB-4D00-A9E0-09FF1BC76ED6}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "qcvm", "qcvm\qcvm.vcxproj", "{DC980E20-C7A8-4112-A517-631DBDA788E7}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pak", "pak\pak.vcxproj", "{A6F66BE9-57EF-4E93-AA9D-6E0C8B0990AD}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testsuite", "testsuite\testsuite.vcxproj", "{7E2839D9-9C1A-4489-9FF9-FDC854EBED3D}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Release|Win32 = Release|Win32 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A6BD74E1-31BB-4D00-A9E0-09FF1BC76ED6}.Debug|Win32.ActiveCfg = Debug|Win32 - {A6BD74E1-31BB-4D00-A9E0-09FF1BC76ED6}.Debug|Win32.Build.0 = Debug|Win32 - {A6BD74E1-31BB-4D00-A9E0-09FF1BC76ED6}.Release|Win32.ActiveCfg = Release|Win32 - {A6BD74E1-31BB-4D00-A9E0-09FF1BC76ED6}.Release|Win32.Build.0 = Release|Win32 - {DC980E20-C7A8-4112-A517-631DBDA788E7}.Debug|Win32.ActiveCfg = Debug|Win32 - {DC980E20-C7A8-4112-A517-631DBDA788E7}.Debug|Win32.Build.0 = Debug|Win32 - {DC980E20-C7A8-4112-A517-631DBDA788E7}.Release|Win32.ActiveCfg = Release|Win32 - {DC980E20-C7A8-4112-A517-631DBDA788E7}.Release|Win32.Build.0 = Release|Win32 - {A6F66BE9-57EF-4E93-AA9D-6E0C8B0990AD}.Debug|Win32.ActiveCfg = Debug|Win32 - {A6F66BE9-57EF-4E93-AA9D-6E0C8B0990AD}.Debug|Win32.Build.0 = Debug|Win32 - {A6F66BE9-57EF-4E93-AA9D-6E0C8B0990AD}.Release|Win32.ActiveCfg = Release|Win32 - {A6F66BE9-57EF-4E93-AA9D-6E0C8B0990AD}.Release|Win32.Build.0 = Release|Win32 - {7E2839D9-9C1A-4489-9FF9-FDC854EBED3D}.Debug|Win32.ActiveCfg = Debug|Win32 - {7E2839D9-9C1A-4489-9FF9-FDC854EBED3D}.Debug|Win32.Build.0 = Debug|Win32 - {7E2839D9-9C1A-4489-9FF9-FDC854EBED3D}.Release|Win32.ActiveCfg = Release|Win32 - {7E2839D9-9C1A-4489-9FF9-FDC854EBED3D}.Release|Win32.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/msvc/gmqcc/gmqcc.vcxproj b/msvc/gmqcc/gmqcc.vcxproj deleted file mode 100755 index 8335978..0000000 --- a/msvc/gmqcc/gmqcc.vcxproj +++ /dev/null @@ -1,95 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {A6BD74E1-31BB-4D00-A9E0-09FF1BC76ED6} - gmqcc - - - - Application - true - MultiByte - - - Application - false - true - MultiByte - - - - - - - - - - - - - - - Level3 - Disabled - NVALGRIND;%(PreprocessorDefinitions) - - - true - - - - - Level3 - MaxSpeed - true - true - NVALGRIND;%(PreprocessorDefinitions) - - - true - true - true - - - - - - \ No newline at end of file diff --git a/msvc/gmqcc/gmqcc.vcxproj.filters b/msvc/gmqcc/gmqcc.vcxproj.filters deleted file mode 100755 index 7797df3..0000000 --- a/msvc/gmqcc/gmqcc.vcxproj.filters +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/msvc/pak/pak.vcxproj b/msvc/pak/pak.vcxproj deleted file mode 100755 index 4ab71bf..0000000 --- a/msvc/pak/pak.vcxproj +++ /dev/null @@ -1,81 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - - - - - - - - - - - - - - - {A6F66BE9-57EF-4E93-AA9D-6E0C8B0990AD} - pak - - - - Application - true - MultiByte - - - Application - false - true - MultiByte - - - - - - - - - - - - - - - Level3 - Disabled - NVALGRIND;%(PreprocessorDefinitions) - - - true - - - - - Level3 - MaxSpeed - true - true - NVALGRIND;%(PreprocessorDefinitions) - - - true - true - true - - - - - - \ No newline at end of file diff --git a/msvc/pak/pak.vcxproj.filters b/msvc/pak/pak.vcxproj.filters deleted file mode 100755 index 8784b71..0000000 --- a/msvc/pak/pak.vcxproj.filters +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/msvc/qcvm/qcvm.vcxproj b/msvc/qcvm/qcvm.vcxproj deleted file mode 100755 index c7ce04f..0000000 --- a/msvc/qcvm/qcvm.vcxproj +++ /dev/null @@ -1,77 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - - - - - - - - - - - {DC980E20-C7A8-4112-A517-631DBDA788E7} - qcvm - - - - Application - true - MultiByte - - - Application - false - true - MultiByte - - - - - - - - - - - - - - - Level3 - Disabled - NVALGRIND;QCVM_EXECUTOR;%(PreprocessorDefinitions) - - - true - - - - - Level3 - MaxSpeed - true - true - NVALGRIND;QCVM_EXECUTOR;%(PreprocessorDefinitions) - - - true - true - true - - - - - - \ No newline at end of file diff --git a/msvc/qcvm/qcvm.vcxproj.filters b/msvc/qcvm/qcvm.vcxproj.filters deleted file mode 100755 index cbdf8a3..0000000 --- a/msvc/qcvm/qcvm.vcxproj.filters +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/msvc/testsuite/testsuite.vcxproj b/msvc/testsuite/testsuite.vcxproj deleted file mode 100755 index bad2db1..0000000 --- a/msvc/testsuite/testsuite.vcxproj +++ /dev/null @@ -1,81 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - - - - - - - - - - - - - - - {7E2839D9-9C1A-4489-9FF9-FDC854EBED3D} - testsuite - - - - Application - true - MultiByte - - - Application - false - true - MultiByte - - - - - - - - - - - - - - - Level3 - Disabled - NVALGRIND;%(PreprocessorDefinitions) - - - true - - - - - Level3 - MaxSpeed - true - true - NVALGRIND;%(PreprocessorDefinitions) - - - true - true - true - - - - - - \ No newline at end of file diff --git a/msvc/testsuite/testsuite.vcxproj.filters b/msvc/testsuite/testsuite.vcxproj.filters deleted file mode 100755 index fccab25..0000000 --- a/msvc/testsuite/testsuite.vcxproj.filters +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/opts.c b/opts.c deleted file mode 100644 index 7c59713..0000000 --- a/opts.c +++ /dev/null @@ -1,449 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * Wolfgang Bumiller - * 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 -#include - -#include "gmqcc.h" - -const unsigned int opts_opt_oflag[COUNT_OPTIMIZATIONS+1] = { -# define GMQCC_TYPE_OPTIMIZATIONS -# define GMQCC_DEFINE_FLAG(NAME, MIN_O) MIN_O, -# include "opts.def" - 0 -}; - -const opts_flag_def_t opts_opt_list[COUNT_OPTIMIZATIONS+1] = { -# define GMQCC_TYPE_OPTIMIZATIONS -# define GMQCC_DEFINE_FLAG(NAME, MIN_O) { #NAME, LONGBIT(OPTIM_##NAME) }, -# include "opts.def" - { NULL, LONGBIT(0) } -}; - -const opts_flag_def_t opts_warn_list[COUNT_WARNINGS+1] = { -# define GMQCC_TYPE_WARNS -# define GMQCC_DEFINE_FLAG(X) { #X, LONGBIT(WARN_##X) }, -# include "opts.def" - { NULL, LONGBIT(0) } -}; - -const opts_flag_def_t opts_flag_list[COUNT_FLAGS+1] = { -# define GMQCC_TYPE_FLAGS -# define GMQCC_DEFINE_FLAG(X) { #X, LONGBIT(X) }, -# include "opts.def" - { NULL, LONGBIT(0) } -}; - -unsigned int opts_optimizationcount[COUNT_OPTIMIZATIONS]; -opts_cmd_t opts; /* command line options */ - -static void opts_setdefault(void) { - memset(&opts, 0, sizeof(opts_cmd_t)); - OPTS_OPTION_BOOL(OPTION_CORRECTION) = true; - OPTS_OPTION_STR(OPTION_PROGSRC) = "progs.src"; - - /* warnings */ - opts_set(opts.warn, WARN_UNUSED_VARIABLE, true); - opts_set(opts.warn, WARN_USED_UNINITIALIZED, true); - opts_set(opts.warn, WARN_UNKNOWN_CONTROL_SEQUENCE, true); - opts_set(opts.warn, WARN_EXTENSIONS, true); - opts_set(opts.warn, WARN_FIELD_REDECLARED, true); - opts_set(opts.warn, WARN_MISSING_RETURN_VALUES, true); - opts_set(opts.warn, WARN_INVALID_PARAMETER_COUNT, true); - opts_set(opts.warn, WARN_LOCAL_CONSTANTS, true); - opts_set(opts.warn, WARN_VOID_VARIABLES, true); - opts_set(opts.warn, WARN_IMPLICIT_FUNCTION_POINTER, true); - opts_set(opts.warn, WARN_VARIADIC_FUNCTION, true); - opts_set(opts.warn, WARN_FRAME_MACROS, true); - opts_set(opts.warn, WARN_EFFECTLESS_STATEMENT, true); - opts_set(opts.warn, WARN_END_SYS_FIELDS, true); - opts_set(opts.warn, WARN_ASSIGN_FUNCTION_TYPES, true); - opts_set(opts.warn, WARN_CPP, true); - opts_set(opts.warn, WARN_MULTIFILE_IF, true); - opts_set(opts.warn, WARN_DOUBLE_DECLARATION, true); - opts_set(opts.warn, WARN_CONST_VAR, true); - opts_set(opts.warn, WARN_MULTIBYTE_CHARACTER, true); - opts_set(opts.warn, WARN_UNKNOWN_PRAGMAS, true); - opts_set(opts.warn, WARN_UNREACHABLE_CODE, true); - opts_set(opts.warn, WARN_UNKNOWN_ATTRIBUTE, true); - opts_set(opts.warn, WARN_RESERVED_NAMES, true); - opts_set(opts.warn, WARN_UNINITIALIZED_CONSTANT, true); - opts_set(opts.warn, WARN_DEPRECATED, true); - opts_set(opts.warn, WARN_PARENTHESIS, true); - opts_set(opts.warn, WARN_CONST_OVERWRITE, true); - opts_set(opts.warn, WARN_DIRECTIVE_INMACRO, true); - opts_set(opts.warn, WARN_BUILTINS, true); - opts_set(opts.warn, WARN_INEXACT_COMPARES, true); - - /* flags */ - opts_set(opts.flags, ADJUST_VECTOR_FIELDS, true); - opts_set(opts.flags, CORRECT_TERNARY, true); - opts_set(opts.flags, BAIL_ON_WERROR, true); - opts_set(opts.flags, LEGACY_VECTOR_MATHS, true); - opts_set(opts.flags, DARKPLACES_STRING_TABLE_BUG, true); - - /* options */ - OPTS_OPTION_U32(OPTION_STATE_FPS) = 10; -} - -void opts_backup_non_Wall() { - size_t i; - for (i = 0; i <= WARN_DEBUG; ++i) - opts_set(opts.warn_backup, i, OPTS_WARN(i)); -} - -void opts_restore_non_Wall() { - size_t i; - for (i = 0; i <= WARN_DEBUG; ++i) - opts_set(opts.warn, i, OPTS_GENERIC(opts.warn_backup, i)); -} - -void opts_backup_non_Werror_all() { - size_t i; - for (i = 0; i <= WARN_DEBUG; ++i) - opts_set(opts.werror_backup, i, OPTS_WERROR(i)); -} - -void opts_restore_non_Werror_all() { - size_t i; - for (i = 0; i <= WARN_DEBUG; ++i) - opts_set(opts.werror, i, OPTS_GENERIC(opts.werror_backup, i)); -} - -void opts_init(const char *output, int standard, size_t arraysize) { - opts_setdefault(); - - OPTS_OPTION_STR(OPTION_OUTPUT) = output; - OPTS_OPTION_U32(OPTION_STANDARD) = standard; - OPTS_OPTION_U32(OPTION_MAX_ARRAY_SIZE) = arraysize; - OPTS_OPTION_U16(OPTION_MEMDUMPCOLS) = 16; -} - -static bool opts_setflag_all(const char *name, bool on, uint32_t *flags, const opts_flag_def_t *list, size_t listsize) { - size_t i; - - for (i = 0; i < listsize; ++i) { - if (!strcmp(name, list[i].name)) { - longbit lb = list[i].bit; - - if (on) - flags[lb.idx] |= (1<<(lb.bit)); - else - flags[lb.idx] &= ~(1<<(lb.bit)); - - return true; - } - } - return false; -} -bool opts_setflag (const char *name, bool on) { - return opts_setflag_all(name, on, opts.flags, opts_flag_list, COUNT_FLAGS); -} -bool opts_setwarn (const char *name, bool on) { - return opts_setflag_all(name, on, opts.warn, opts_warn_list, COUNT_WARNINGS); -} -bool opts_setwerror(const char *name, bool on) { - return opts_setflag_all(name, on, opts.werror, opts_warn_list, COUNT_WARNINGS); -} -bool opts_setoptim (const char *name, bool on) { - return opts_setflag_all(name, on, opts.optimization, opts_opt_list, COUNT_OPTIMIZATIONS); -} - -void opts_set(uint32_t *flags, size_t idx, bool on) { - longbit lb; - LONGBIT_SET(lb, idx); - - if (on) - flags[lb.idx] |= (1<<(lb.bit)); - else - flags[lb.idx] &= ~(1<<(lb.bit)); -} - -void opts_setoptimlevel(unsigned int level) { - size_t i; - for (i = 0; i < COUNT_OPTIMIZATIONS; ++i) - opts_set(opts.optimization, i, level >= opts_opt_oflag[i]); - - if (!level) - opts.optimizeoff = true; -} - -/* - * Standard configuration parser and subsystem. Yes, optionally you may - * create ini files or cfg (the driver accepts both) for a project opposed - * to supplying just a progs.src (since you also may need to supply command - * line arguments or set the options of the compiler) [which cannot be done - * from a progs.src. - */ -static char *opts_ini_rstrip(char *s) { - char *p = s + strlen(s) - 1; - while (p > s && util_isspace(*p)) - *p = '\0', p--; - return s; -} - -static char *opts_ini_lskip(const char *s) { - while (*s && util_isspace(*s)) - s++; - return (char*)s; -} - -static char *opts_ini_next(const char *s, char c) { - bool last = false; - while (*s && *s != c && !(last && *s == ';')) - last = !!util_isspace(*s), s++; - - return (char*)s; -} - -static size_t opts_ini_parse ( - fs_file_t *filehandle, - char *(*loadhandle)(const char *, const char *, const char *, char **), - char **errorhandle, - char **parse_file -) { - size_t linesize; - size_t lineno = 1; - size_t error = 0; - char *line = NULL; - char section_data[2048] = ""; - char oldname_data[2048] = ""; - - /* parsing and reading variables */ - char *parse_beg; - char *parse_end; - char *read_name; - char *read_value; - - while (fs_file_getline(&line, &linesize, filehandle) != FS_FILE_EOF) { - parse_beg = line; - - /* handle BOM */ - if (lineno == 1 && ( - (unsigned char)parse_beg[0] == 0xEF && - (unsigned char)parse_beg[1] == 0xBB && - (unsigned char)parse_beg[2] == 0xBF - ) - ) { - parse_beg ++; /* 0xEF */ - parse_beg ++; /* 0xBB */ - parse_beg ++; /* 0xBF */ - } - - if (*(parse_beg = opts_ini_lskip(opts_ini_rstrip(parse_beg))) == ';' || *parse_beg == '#') { - /* ignore '#' is a perl extension */ - } else if (*parse_beg == '[') { - /* section found */ - if (*(parse_end = opts_ini_next(parse_beg + 1, ']')) == ']') { - * parse_end = '\0'; /* terminate bro */ - util_strncpy(section_data, parse_beg + 1, sizeof(section_data)); - section_data[sizeof(section_data) - 1] = '\0'; - *oldname_data = '\0'; - } else if (!error) { - /* otherwise set error to the current line number */ - error = lineno; - } - } else if (*parse_beg && *parse_beg != ';') { - /* not a comment, must be a name value pair :) */ - if (*(parse_end = opts_ini_next(parse_beg, '=')) != '=') - parse_end = opts_ini_next(parse_beg, ':'); - - if (*parse_end == '=' || *parse_end == ':') { - *parse_end = '\0'; /* terminate bro */ - read_name = opts_ini_rstrip(parse_beg); - read_value = opts_ini_lskip(parse_end + 1); - if (*(parse_end = opts_ini_next(read_value, '\0')) == ';') - * parse_end = '\0'; - opts_ini_rstrip(read_value); - - /* valid name value pair, lets call down to handler */ - util_strncpy(oldname_data, read_name, sizeof(oldname_data)); - oldname_data[sizeof(oldname_data) - 1] ='\0'; - - if ((*errorhandle = loadhandle(section_data, read_name, read_value, parse_file)) && !error) - error = lineno; - } else if (!strcmp(section_data, "includes")) { - /* Includes are special */ - if (*(parse_end = opts_ini_next(parse_beg, '=')) == '=' - || *(parse_end = opts_ini_next(parse_beg, ':')) == ':') { - static const char *invalid_include = "invalid use of include"; - vec_append(*errorhandle, strlen(invalid_include), invalid_include); - error = lineno; - } else { - read_name = opts_ini_rstrip(parse_beg); - if ((*errorhandle = loadhandle(section_data, read_name, read_name, parse_file)) && !error) - error = lineno; - } - } else if (!error) { - /* otherwise set error to the current line number */ - error = lineno; - } - } - lineno++; - } - mem_d(line); - return error; - -} - -/* - * returns true/false for a char that contains ("true" or "false" or numeric 0/1) - */ -static bool opts_ini_bool(const char *value) { - if (!strcmp(value, "true")) return true; - if (!strcmp(value, "false")) return false; - return !!strtol(value, NULL, 10); -} - -static char *opts_ini_load(const char *section, const char *name, const char *value, char **parse_file) { - char *error = NULL; - bool found = false; - - /* - * undef all of these because they may still be defined like in my - * case they where. - */ - #undef GMQCC_TYPE_FLAGS - #undef GMQCC_TYPE_OPTIMIZATIONS - #undef GMQCC_TYPE_WARNS - - /* deal with includes */ - if (!strcmp(section, "includes")) { - static const char *include_error_beg = "failed to open file `"; - static const char *include_error_end = "' for inclusion"; - fs_file_t *file = fs_file_open(value, "r"); - found = true; - if (!file) { - vec_append(error, strlen(include_error_beg), include_error_beg); - vec_append(error, strlen(value), value); - vec_append(error, strlen(include_error_end), include_error_end); - } else { - if (opts_ini_parse(file, &opts_ini_load, &error, parse_file) != 0) - found = false; - /* Change the file name */ - mem_d(*parse_file); - *parse_file = util_strdup(value); - fs_file_close(file); - } - } - - /* flags */ - #define GMQCC_TYPE_FLAGS - #define GMQCC_DEFINE_FLAG(X) \ - if (!strcmp(section, "flags") && !strcmp(name, #X)) { \ - opts_set(opts.flags, X, opts_ini_bool(value)); \ - found = true; \ - } - #include "opts.def" - - /* warnings */ - #define GMQCC_TYPE_WARNS - #define GMQCC_DEFINE_FLAG(X) \ - if (!strcmp(section, "warnings") && !strcmp(name, #X)) { \ - opts_set(opts.warn, WARN_##X, opts_ini_bool(value)); \ - found = true; \ - } - #include "opts.def" - - /* Werror-individuals */ - #define GMQCC_TYPE_WARNS - #define GMQCC_DEFINE_FLAG(X) \ - if (!strcmp(section, "errors") && !strcmp(name, #X)) { \ - opts_set(opts.werror, WARN_##X, opts_ini_bool(value)); \ - found = true; \ - } - #include "opts.def" - - /* optimizations */ - #define GMQCC_TYPE_OPTIMIZATIONS - #define GMQCC_DEFINE_FLAG(X,Y) \ - if (!strcmp(section, "optimizations") && !strcmp(name, #X)) { \ - opts_set(opts.optimization, OPTIM_##X, opts_ini_bool(value)); \ - found = true; \ - } - #include "opts.def" - - /* nothing was found ever! */ - if (!found) { - if (strcmp(section, "includes") && - strcmp(section, "flags") && - strcmp(section, "warnings") && - strcmp(section, "optimizations")) - { - static const char *invalid_section = "invalid_section `"; - vec_append(error, strlen(invalid_section), invalid_section); - vec_append(error, strlen(section), section); - vec_push(error, '`'); - } else if (strcmp(section, "includes")) { - static const char *invalid_variable = "invalid_variable `"; - static const char *in_section = "` in section: `"; - vec_append(error, strlen(invalid_variable), invalid_variable); - vec_append(error, strlen(name), name); - vec_append(error, strlen(in_section), in_section); - vec_append(error, strlen(section), section); - vec_push(error, '`'); - } else { - static const char *expected_something = "expected something"; - vec_append(error, strlen(expected_something), expected_something); - } - } - vec_push(error, '\0'); - return error; -} - -/* - * Actual loading subsystem, this finds the ini or cfg file, and properly - * loads it and executes it to set compiler options. - */ -void opts_ini_init(const char *file) { - /* - * Possible matches are: - * gmqcc.ini - * gmqcc.cfg - */ - char *error = NULL; - char *parse_file = NULL; - size_t line; - fs_file_t *ini; - - if (!file) { - /* try ini */ - if (!(ini = fs_file_open((file = "gmqcc.ini"), "r"))) - /* try cfg */ - if (!(ini = fs_file_open((file = "gmqcc.cfg"), "r"))) - return; - } else if (!(ini = fs_file_open(file, "r"))) - return; - - con_out("found ini file `%s`\n", file); - - parse_file = util_strdup(file); - if ((line = opts_ini_parse(ini, &opts_ini_load, &error, &parse_file)) != 0) { - /* there was a parse error with the ini file */ - con_printmsg(LVL_ERROR, parse_file, line, 0 /*TODO: column for ini error*/, "error", error); - vec_free(error); - } - mem_d(parse_file); - - fs_file_close(ini); -} diff --git a/opts.cpp b/opts.cpp new file mode 100644 index 0000000..b2517a0 --- /dev/null +++ b/opts.cpp @@ -0,0 +1,424 @@ +#include +#include + +#include "gmqcc.h" + +const unsigned int opts_opt_oflag[COUNT_OPTIMIZATIONS+1] = { +# define GMQCC_TYPE_OPTIMIZATIONS +# define GMQCC_DEFINE_FLAG(NAME, MIN_O) MIN_O, +# include "opts.def" + 0 +}; + +const opts_flag_def_t opts_opt_list[COUNT_OPTIMIZATIONS+1] = { +# define GMQCC_TYPE_OPTIMIZATIONS +# define GMQCC_DEFINE_FLAG(NAME, MIN_O) { #NAME, LONGBIT(OPTIM_##NAME) }, +# include "opts.def" + { nullptr, LONGBIT(0) } +}; + +const opts_flag_def_t opts_warn_list[COUNT_WARNINGS+1] = { +# define GMQCC_TYPE_WARNS +# define GMQCC_DEFINE_FLAG(X) { #X, LONGBIT(WARN_##X) }, +# include "opts.def" + { nullptr, LONGBIT(0) } +}; + +const opts_flag_def_t opts_flag_list[COUNT_FLAGS+1] = { +# define GMQCC_TYPE_FLAGS +# define GMQCC_DEFINE_FLAG(X) { #X, LONGBIT(X) }, +# include "opts.def" + { nullptr, LONGBIT(0) } +}; + +unsigned int opts_optimizationcount[COUNT_OPTIMIZATIONS]; +opts_cmd_t opts; /* command line options */ + +static void opts_setdefault(void) { + memset(&opts, 0, sizeof(opts_cmd_t)); + OPTS_OPTION_STR(OPTION_PROGSRC) = "progs.src"; + + /* warnings */ + opts_set(opts.warn, WARN_UNUSED_VARIABLE, true); + opts_set(opts.warn, WARN_USED_UNINITIALIZED, true); + opts_set(opts.warn, WARN_UNKNOWN_CONTROL_SEQUENCE, true); + opts_set(opts.warn, WARN_EXTENSIONS, true); + opts_set(opts.warn, WARN_FIELD_REDECLARED, true); + opts_set(opts.warn, WARN_MISSING_RETURN_VALUES, true); + opts_set(opts.warn, WARN_INVALID_PARAMETER_COUNT, true); + opts_set(opts.warn, WARN_LOCAL_CONSTANTS, true); + opts_set(opts.warn, WARN_VOID_VARIABLES, true); + opts_set(opts.warn, WARN_IMPLICIT_FUNCTION_POINTER, true); + opts_set(opts.warn, WARN_VARIADIC_FUNCTION, true); + opts_set(opts.warn, WARN_FRAME_MACROS, true); + opts_set(opts.warn, WARN_EFFECTLESS_STATEMENT, true); + opts_set(opts.warn, WARN_END_SYS_FIELDS, true); + opts_set(opts.warn, WARN_ASSIGN_FUNCTION_TYPES, true); + opts_set(opts.warn, WARN_CPP, true); + opts_set(opts.warn, WARN_MULTIFILE_IF, true); + opts_set(opts.warn, WARN_DOUBLE_DECLARATION, true); + opts_set(opts.warn, WARN_CONST_VAR, true); + opts_set(opts.warn, WARN_MULTIBYTE_CHARACTER, true); + opts_set(opts.warn, WARN_UNKNOWN_PRAGMAS, true); + opts_set(opts.warn, WARN_UNREACHABLE_CODE, true); + opts_set(opts.warn, WARN_UNKNOWN_ATTRIBUTE, true); + opts_set(opts.warn, WARN_RESERVED_NAMES, true); + opts_set(opts.warn, WARN_UNINITIALIZED_CONSTANT, true); + opts_set(opts.warn, WARN_DEPRECATED, true); + opts_set(opts.warn, WARN_PARENTHESIS, true); + opts_set(opts.warn, WARN_CONST_OVERWRITE, true); + opts_set(opts.warn, WARN_DIRECTIVE_INMACRO, true); + opts_set(opts.warn, WARN_BUILTINS, true); + opts_set(opts.warn, WARN_INEXACT_COMPARES, true); + + /* flags */ + opts_set(opts.flags, ADJUST_VECTOR_FIELDS, true); + opts_set(opts.flags, CORRECT_TERNARY, true); + opts_set(opts.flags, BAIL_ON_WERROR, true); + opts_set(opts.flags, LEGACY_VECTOR_MATHS, true); + opts_set(opts.flags, DARKPLACES_STRING_TABLE_BUG, true); + + /* options */ + OPTS_OPTION_U32(OPTION_STATE_FPS) = 10; +} + +void opts_backup_non_Wall() { + size_t i; + for (i = 0; i <= WARN_DEBUG; ++i) + opts_set(opts.warn_backup, i, OPTS_WARN(i)); +} + +void opts_restore_non_Wall() { + size_t i; + for (i = 0; i <= WARN_DEBUG; ++i) + opts_set(opts.warn, i, OPTS_GENERIC(opts.warn_backup, i)); +} + +void opts_backup_non_Werror_all() { + size_t i; + for (i = 0; i <= WARN_DEBUG; ++i) + opts_set(opts.werror_backup, i, OPTS_WERROR(i)); +} + +void opts_restore_non_Werror_all() { + size_t i; + for (i = 0; i <= WARN_DEBUG; ++i) + opts_set(opts.werror, i, OPTS_GENERIC(opts.werror_backup, i)); +} + +void opts_init(const char *output, int standard, size_t arraysize) { + opts_setdefault(); + + OPTS_OPTION_STR(OPTION_OUTPUT) = output; + OPTS_OPTION_U32(OPTION_STANDARD) = standard; + OPTS_OPTION_U32(OPTION_MAX_ARRAY_SIZE) = arraysize; +} + +static bool opts_setflag_all(const char *name, bool on, uint32_t *flags, const opts_flag_def_t *list, size_t listsize) { + size_t i; + + for (i = 0; i < listsize; ++i) { + if (!strcmp(name, list[i].name)) { + longbit lb = list[i].bit; + + if (on) + flags[lb.idx] |= (1<<(lb.bit)); + else + flags[lb.idx] &= ~(1<<(lb.bit)); + + return true; + } + } + return false; +} +bool opts_setflag (const char *name, bool on) { + return opts_setflag_all(name, on, opts.flags, opts_flag_list, COUNT_FLAGS); +} +bool opts_setwarn (const char *name, bool on) { + return opts_setflag_all(name, on, opts.warn, opts_warn_list, COUNT_WARNINGS); +} +bool opts_setwerror(const char *name, bool on) { + return opts_setflag_all(name, on, opts.werror, opts_warn_list, COUNT_WARNINGS); +} +bool opts_setoptim (const char *name, bool on) { + return opts_setflag_all(name, on, opts.optimization, opts_opt_list, COUNT_OPTIMIZATIONS); +} + +void opts_set(uint32_t *flags, size_t idx, bool on) { + longbit lb; + LONGBIT_SET(lb, idx); + + if (on) + flags[lb.idx] |= (1<<(lb.bit)); + else + flags[lb.idx] &= ~(1<<(lb.bit)); +} + +void opts_setoptimlevel(unsigned int level) { + size_t i; + for (i = 0; i < COUNT_OPTIMIZATIONS; ++i) + opts_set(opts.optimization, i, level >= opts_opt_oflag[i]); + + if (!level) + opts.optimizeoff = true; +} + +/* + * Standard configuration parser and subsystem. Yes, optionally you may + * create ini files or cfg (the driver accepts both) for a project opposed + * to supplying just a progs.src (since you also may need to supply command + * line arguments or set the options of the compiler) [which cannot be done + * from a progs.src. + */ +static char *opts_ini_rstrip(char *s) { + char *p = s + strlen(s) - 1; + while (p > s && util_isspace(*p)) + *p = '\0', p--; + return s; +} + +static char *opts_ini_lskip(const char *s) { + while (*s && util_isspace(*s)) + s++; + return (char*)s; +} + +static char *opts_ini_next(const char *s, char c) { + bool last = false; + while (*s && *s != c && !(last && *s == ';')) + last = !!util_isspace(*s), s++; + + return (char*)s; +} + +static size_t opts_ini_parse ( + FILE *filehandle, + char *(*loadhandle)(const char *, const char *, const char *, char **), + char **errorhandle, + char **parse_file +) { + size_t linesize; + size_t lineno = 1; + size_t error = 0; + char *line = nullptr; + char section_data[2048] = ""; + char oldname_data[2048] = ""; + + /* parsing and reading variables */ + char *parse_beg; + char *parse_end; + char *read_name; + char *read_value; + + while (util_getline(&line, &linesize, filehandle) != EOF) { + parse_beg = line; + + /* handle BOM */ + if (lineno == 1 && ( + (unsigned char)parse_beg[0] == 0xEF && + (unsigned char)parse_beg[1] == 0xBB && + (unsigned char)parse_beg[2] == 0xBF + ) + ) { + parse_beg ++; /* 0xEF */ + parse_beg ++; /* 0xBB */ + parse_beg ++; /* 0xBF */ + } + + if (*(parse_beg = opts_ini_lskip(opts_ini_rstrip(parse_beg))) == ';' || *parse_beg == '#') { + /* ignore '#' is a perl extension */ + } else if (*parse_beg == '[') { + /* section found */ + if (*(parse_end = opts_ini_next(parse_beg + 1, ']')) == ']') { + * parse_end = '\0'; /* terminate bro */ + util_strncpy(section_data, parse_beg + 1, sizeof(section_data)); + section_data[sizeof(section_data) - 1] = '\0'; + *oldname_data = '\0'; + } else if (!error) { + /* otherwise set error to the current line number */ + error = lineno; + } + } else if (*parse_beg && *parse_beg != ';') { + /* not a comment, must be a name value pair :) */ + if (*(parse_end = opts_ini_next(parse_beg, '=')) != '=') + parse_end = opts_ini_next(parse_beg, ':'); + + if (*parse_end == '=' || *parse_end == ':') { + *parse_end = '\0'; /* terminate bro */ + read_name = opts_ini_rstrip(parse_beg); + read_value = opts_ini_lskip(parse_end + 1); + if (*(parse_end = opts_ini_next(read_value, '\0')) == ';') + * parse_end = '\0'; + opts_ini_rstrip(read_value); + + /* valid name value pair, lets call down to handler */ + util_strncpy(oldname_data, read_name, sizeof(oldname_data)); + oldname_data[sizeof(oldname_data) - 1] ='\0'; + + if ((*errorhandle = loadhandle(section_data, read_name, read_value, parse_file)) && !error) + error = lineno; + } else if (!strcmp(section_data, "includes")) { + /* Includes are special */ + if (*(parse_end = opts_ini_next(parse_beg, '=')) == '=' + || *(parse_end = opts_ini_next(parse_beg, ':')) == ':') { + static const char *invalid_include = "invalid use of include"; + vec_append(*errorhandle, strlen(invalid_include), invalid_include); + error = lineno; + } else { + read_name = opts_ini_rstrip(parse_beg); + if ((*errorhandle = loadhandle(section_data, read_name, read_name, parse_file)) && !error) + error = lineno; + } + } else if (!error) { + /* otherwise set error to the current line number */ + error = lineno; + } + } + lineno++; + } + mem_d(line); + return error; + +} + +/* + * returns true/false for a char that contains ("true" or "false" or numeric 0/1) + */ +static bool opts_ini_bool(const char *value) { + if (!strcmp(value, "true")) return true; + if (!strcmp(value, "false")) return false; + return !!strtol(value, nullptr, 10); +} + +static char *opts_ini_load(const char *section, const char *name, const char *value, char **parse_file) { + char *error = nullptr; + bool found = false; + + /* + * undef all of these because they may still be defined like in my + * case they where. + */ + #undef GMQCC_TYPE_FLAGS + #undef GMQCC_TYPE_OPTIMIZATIONS + #undef GMQCC_TYPE_WARNS + + /* deal with includes */ + if (!strcmp(section, "includes")) { + static const char *include_error_beg = "failed to open file `"; + static const char *include_error_end = "' for inclusion"; + FILE *file = fopen(value, "r"); + found = true; + if (!file) { + vec_append(error, strlen(include_error_beg), include_error_beg); + vec_append(error, strlen(value), value); + vec_append(error, strlen(include_error_end), include_error_end); + } else { + if (opts_ini_parse(file, &opts_ini_load, &error, parse_file) != 0) + found = false; + /* Change the file name */ + mem_d(*parse_file); + *parse_file = util_strdup(value); + fclose(file); + } + } + + /* flags */ + #define GMQCC_TYPE_FLAGS + #define GMQCC_DEFINE_FLAG(X) \ + if (!strcmp(section, "flags") && !strcmp(name, #X)) { \ + opts_set(opts.flags, X, opts_ini_bool(value)); \ + found = true; \ + } + #include "opts.def" + + /* warnings */ + #define GMQCC_TYPE_WARNS + #define GMQCC_DEFINE_FLAG(X) \ + if (!strcmp(section, "warnings") && !strcmp(name, #X)) { \ + opts_set(opts.warn, WARN_##X, opts_ini_bool(value)); \ + found = true; \ + } + #include "opts.def" + + /* Werror-individuals */ + #define GMQCC_TYPE_WARNS + #define GMQCC_DEFINE_FLAG(X) \ + if (!strcmp(section, "errors") && !strcmp(name, #X)) { \ + opts_set(opts.werror, WARN_##X, opts_ini_bool(value)); \ + found = true; \ + } + #include "opts.def" + + /* optimizations */ + #define GMQCC_TYPE_OPTIMIZATIONS + #define GMQCC_DEFINE_FLAG(X,Y) \ + if (!strcmp(section, "optimizations") && !strcmp(name, #X)) { \ + opts_set(opts.optimization, OPTIM_##X, opts_ini_bool(value)); \ + found = true; \ + } + #include "opts.def" + + /* nothing was found ever! */ + if (!found) { + if (strcmp(section, "includes") && + strcmp(section, "flags") && + strcmp(section, "warnings") && + strcmp(section, "optimizations")) + { + static const char *invalid_section = "invalid_section `"; + vec_append(error, strlen(invalid_section), invalid_section); + vec_append(error, strlen(section), section); + vec_push(error, '`'); + } else if (strcmp(section, "includes")) { + static const char *invalid_variable = "invalid_variable `"; + static const char *in_section = "` in section: `"; + vec_append(error, strlen(invalid_variable), invalid_variable); + vec_append(error, strlen(name), name); + vec_append(error, strlen(in_section), in_section); + vec_append(error, strlen(section), section); + vec_push(error, '`'); + } else { + static const char *expected_something = "expected something"; + vec_append(error, strlen(expected_something), expected_something); + } + } + vec_push(error, '\0'); + return error; +} + +/* + * Actual loading subsystem, this finds the ini or cfg file, and properly + * loads it and executes it to set compiler options. + */ +void opts_ini_init(const char *file) { + /* + * Possible matches are: + * gmqcc.ini + * gmqcc.cfg + */ + char *error = nullptr; + char *parse_file = nullptr; + size_t line; + FILE *ini; + + if (!file) { + /* try ini */ + if (!(ini = fopen((file = "gmqcc.ini"), "r"))) + /* try cfg */ + if (!(ini = fopen((file = "gmqcc.cfg"), "r"))) + return; + } else if (!(ini = fopen(file, "r"))) + return; + + con_out("found ini file `%s`\n", file); + + parse_file = util_strdup(file); + if ((line = opts_ini_parse(ini, &opts_ini_load, &error, &parse_file)) != 0) { + /* there was a parse error with the ini file */ + con_printmsg(LVL_ERROR, parse_file, line, 0 /*TODO: column for ini error*/, "error", error); + vec_free(error); + } + mem_d(parse_file); + + fclose(ini); +} diff --git a/opts.def b/opts.def index 92cff14..f5ce0af 100644 --- a/opts.def +++ b/opts.def @@ -1,26 +1,3 @@ -/* - * Copyright (C) 2012, 2013, 2014 - * Wolfgang Bumiller - * 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. - */ #ifndef GMQCC_DEFINE_FLAG # error "bad opts.def usage" #endif @@ -127,8 +104,6 @@ GMQCC_DEFINE_FLAG(G) GMQCC_DEFINE_FLAG(STANDARD) GMQCC_DEFINE_FLAG(DEBUG) - GMQCC_DEFINE_FLAG(MEMDUMPCOLS) - GMQCC_DEFINE_FLAG(MEMCHK) GMQCC_DEFINE_FLAG(DUMPFIN) GMQCC_DEFINE_FLAG(DUMP) GMQCC_DEFINE_FLAG(FORCECRC) @@ -136,8 +111,6 @@ GMQCC_DEFINE_FLAG(PP_ONLY) GMQCC_DEFINE_FLAG(MAX_ARRAY_SIZE) GMQCC_DEFINE_FLAG(ADD_INFO) - GMQCC_DEFINE_FLAG(CORRECTION) - GMQCC_DEFINE_FLAG(STATISTICS) GMQCC_DEFINE_FLAG(PROGSRC) GMQCC_DEFINE_FLAG(COVERAGE) GMQCC_DEFINE_FLAG(STATE_FPS) diff --git a/pak.c b/pak.c deleted file mode 100644 index 9781c53..0000000 --- a/pak.c +++ /dev/null @@ -1,607 +0,0 @@ -/* - * Copyright (C) 2013, 2014, 2015 - * 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 -#include - -#include "gmqcc.h" - -/* - * The PAK format uses a FOURCC concept for storing the magic ident within - * the header as a uint32_t. - */ -#define PAK_FOURCC ((uint32_t)(((uint8_t)'P'|((uint8_t)'A'<<8)|((uint8_t)'C'<<16)|((uint8_t)'K'<<24)))) - -typedef struct { - uint32_t magic; /* "PACK" */ - - /* - * Offset to first directory entry in PAK file. It's often - * best to store the directories at the end of the file opposed - * to the front, since it allows easy insertion without having - * to load the entire file into memory again. - */ - uint32_t diroff; - uint32_t dirlen; -} pak_header_t; - -/* - * A directory, is sort of a "file entry". The concept of - * a directory in Quake world is a "file entry/record". This - * describes a file (with directories/nested ones too in it's - * file name). Hence it can be a file, file with directory, or - * file with directories. - */ -typedef struct { - char name[56]; - uint32_t pos; - uint32_t len; -} pak_directory_t; - -/* - * Used to get the next token from a string, where the - * strings themselfs are seperated by chracters from - * `sep`. This is essentially strsep. - */ -static char *pak_tree_sep(char **str, const char *sep) { - char *beg = *str; - char *end; - - if (!beg) - return NULL; - - if (*(end = beg + strcspn(beg, sep))) - * end++ = '\0'; /* null terminate */ - else - end = 0; - - *str = end; - return beg; -} - -/* - * When given a string like "a/b/c/d/e/file" - * this function will handle the creation of - * the directory structure, included nested - * directories. - */ -static void pak_tree_build(const char *entry) { - char *directory; - char *elements[28]; - char *pathsplit; - char *token; - - size_t itr; - size_t jtr; - - pathsplit = (char *)mem_a(56); - directory = (char *)mem_a(56); - - memset(pathsplit, 0, 56); - - util_strncpy(directory, entry, 56); - for (itr = 0; (token = pak_tree_sep(&directory, "/")) != NULL; itr++) { - elements[itr] = token; - } - - for (jtr = 0; jtr < itr - 1; jtr++) { - util_strcat(pathsplit, elements[jtr]); - util_strcat(pathsplit, "/"); - - if (fs_dir_make(pathsplit)) { - mem_d(pathsplit); - mem_d(directory); - - /* TODO: undo on fail */ - - return; - } - } - - mem_d(pathsplit); - mem_d(directory); -} - -typedef struct { - pak_directory_t *directories; - pak_header_t header; - fs_file_t *handle; - bool insert; -} pak_file_t; - -static pak_file_t *pak_open_read(const char *file) { - pak_file_t *pak; - size_t itr; - - if (!(pak = (pak_file_t*)mem_a(sizeof(pak_file_t)))) - return NULL; - - if (!(pak->handle = fs_file_open(file, "rb"))) { - mem_d(pak); - return NULL; - } - - pak->directories = NULL; - pak->insert = false; /* read doesn't allow insert */ - - memset (&pak->header, 0, sizeof(pak_header_t)); - fs_file_read (&pak->header, sizeof(pak_header_t), 1, pak->handle); - - util_endianswap(&pak->header.magic, 1, sizeof(pak->header.magic)); - util_endianswap(&pak->header.diroff, 1, sizeof(pak->header.diroff)); - util_endianswap(&pak->header.dirlen, 1, sizeof(pak->header.dirlen)); - - /* - * Every PAK file has "PACK" stored as FOURCC data in the - * header. If this data cannot compare (as checked here), it's - * probably not a PAK file. - */ - if (pak->header.magic != PAK_FOURCC) { - fs_file_close(pak->handle); - mem_d (pak); - return NULL; - } - - /* - * Time to read in the directory handles and prepare the directories - * vector. We're going to be reading some the file inwards soon. - */ - fs_file_seek(pak->handle, pak->header.diroff, FS_FILE_SEEK_SET); - - /* - * Read in all directories from the PAK file. These are considered - * to be the "file entries". - */ - for (itr = 0; itr < pak->header.dirlen / 64; itr++) { - pak_directory_t dir; - fs_file_read (&dir, sizeof(pak_directory_t), 1, pak->handle); - - /* Don't translate name - it's just an array of bytes. */ - util_endianswap(&dir.pos, 1, sizeof(dir.pos)); - util_endianswap(&dir.len, 1, sizeof(dir.len)); - - vec_push(pak->directories, dir); - } - return pak; -} - -static pak_file_t *pak_open_write(const char *file) { - pak_file_t *pak; - - if (!(pak = (pak_file_t*)mem_a(sizeof(pak_file_t)))) - return NULL; - - /* - * Generate the required directory structure / tree for - * writing this PAK file too. - */ - pak_tree_build(file); - - if (!(pak->handle = fs_file_open(file, "wb"))) { - /* - * The directory tree that was created, needs to be - * removed entierly if we failed to open a file. - */ - /* TODO backup directory clean */ - - mem_d(pak); - return NULL; - } - - memset(&(pak->header), 0, sizeof(pak_header_t)); - - /* - * We're in "insert" mode, we need to do things like header - * "patching" and writing the directories at the end of the - * file. - */ - pak->insert = true; - pak->header.magic = PAK_FOURCC; - - /* on BE systems we need to swap the byte order of the FOURCC */ - util_endianswap(&pak->header.magic, 1, sizeof(uint32_t)); - - /* - * We need to write out the header since files will be wrote out to - * this even with directory entries, and that not wrote. The header - * will need to be patched in later with a file_seek, and overwrite, - * we could use offsets and other trickery. This is just easier. - */ - fs_file_write(&(pak->header), sizeof(pak_header_t), 1, pak->handle); - - return pak; -} - -static pak_file_t *pak_open(const char *file, const char *mode) { - if (!file || !mode) - return NULL; - - switch (*mode) { - case 'r': return pak_open_read (file); - case 'w': return pak_open_write(file); - } - - return NULL; -} - -static bool pak_exists(pak_file_t *pak, const char *file, pak_directory_t **dir) { - size_t itr; - - if (!pak || !file) - return false; - - for (itr = 0; itr < vec_size(pak->directories); itr++) { - if (!strcmp(pak->directories[itr].name, file)) { - /* - * Store back a pointer to the directory that matches - * the request if requested (NULL is not allowed). - */ - if (dir) { - *dir = &(pak->directories[itr]); - } - return true; - } - } - - return false; -} - -/* - * Extraction abilities. These work as you expect them to. - */ -static bool pak_extract_one(pak_file_t *pak, const char *file, const char *outdir) { - pak_directory_t *dir = NULL; - unsigned char *dat = NULL; - char *local = NULL; - fs_file_t *out = NULL; - - if (!pak_exists(pak, file, &dir)) { - return false; - } - - if (!(dat = (unsigned char *)mem_a(dir->len))) - goto err; - - /* - * Generate the directory structure / tree that will be required - * to store the extracted file. - */ - pak_tree_build(file); - - /* TODO portable path seperators */ - util_asprintf(&local, "%s/%s", outdir, file); - - /* - * Now create the file, if this operation fails. Then abort - * It shouldn't fail though. - */ - if (!(out = fs_file_open(local, "wb"))) - goto err; - - /* free memory for directory string */ - mem_d(local); - - /* read */ - if (fs_file_seek (pak->handle, dir->pos, FS_FILE_SEEK_SET) != 0) - goto err; - - fs_file_read (dat, 1, dir->len, pak->handle); - fs_file_write(dat, 1, dir->len, out); - fs_file_close(out); - - mem_d(dat); - return true; - -err: - if (dat) mem_d(dat); - if (out) fs_file_close(out); - return false; -} - -static bool pak_extract_all(pak_file_t *pak, const char *dir) { - size_t itr; - - if (!fs_dir_make(dir)) - return false; - - for (itr = 0; itr < vec_size(pak->directories); itr++) { - if (!pak_extract_one(pak, pak->directories[itr].name, dir)) - return false; - } - - return true; -} - -/* - * Insertion functions (the opposite of extraction). Yes for generating - * PAKs. - */ -static bool pak_insert_one(pak_file_t *pak, const char *file) { - pak_directory_t dir; - unsigned char *dat; - long len; - fs_file_t *fp; - - /* - * We don't allow insertion on files that already exist within the - * pak file. Weird shit can happen if we allow that ;). We also - * don't allow insertion if the pak isn't opened in write mode. - */ - if (!pak || !file || !pak->insert || pak_exists(pak, file, NULL)) - return false; - - if (!(fp = fs_file_open(file, "rb"))) - return false; - - /* - * Calculate the total file length, since it will be wrote to - * the directory entry, and the actual contents of the file - * to the PAK file itself. - */ - if (fs_file_seek(fp, 0, FS_FILE_SEEK_END) != 0 || ((len = fs_file_tell(fp)) < 0)) - goto err; - if (fs_file_seek(fp, 0, FS_FILE_SEEK_SET) != 0) - goto err; - - dir.len = len; - dir.pos = fs_file_tell(pak->handle); - - /* - * We're limited to 56 bytes for a file name string, that INCLUDES - * the directory and '/' seperators. - */ - if (strlen(file) >= 56) - goto err; - - util_strncpy(dir.name, file, strlen(file)); - - /* - * Allocate some memory for loading in the data that will be - * redirected into the PAK file. - */ - if (!(dat = (unsigned char *)mem_a(dir.len))) - goto err; - - fs_file_read (dat, dir.len, 1, fp); - fs_file_close(fp); - fs_file_write(dat, dir.len, 1, pak->handle); - - /* - * Now add the directory to the directories vector, so pak_close - * can actually write it. - */ - vec_push(pak->directories, dir); - - return true; - -err: - fs_file_close(fp); - return false; -} - -/* - * Like pak_insert_one, except this collects files in all directories - * from a root directory, and inserts them all. - */ -#if 0 -static bool pak_insert_all(pak_file_t *pak, const char *dir) { - DIR *dp; - struct dirent *dirp; - - if (!(pak->insert)) - return false; - - if (!(dp = fs_dir_open(dir))) - return false; - - while ((dirp = fs_dir_read(dp))) { - if (!(pak_insert_one(pak, dirp->d_name))) { - fs_dir_close(dp); - return false; - } - } - - fs_dir_close(dp); - return true; -} -#endif /*!if 0 renable when ready to use */ - -static bool pak_close(pak_file_t *pak) { - size_t itr; - long tell; - - if (!pak) - return false; - - /* - * In insert mode we need to patch the header, and write - * our directory entries at the end of the file. - */ - if (pak->insert) { - if ((tell = fs_file_tell(pak->handle)) != 0) - goto err; - - pak->header.dirlen = vec_size(pak->directories) * 64; - pak->header.diroff = tell; - - /* patch header */ - if (fs_file_seek (pak->handle, 0, FS_FILE_SEEK_SET) != 0) - goto err; - - fs_file_write(&(pak->header), sizeof(pak_header_t), 1, pak->handle); - - /* write directories */ - if (fs_file_seek (pak->handle, pak->header.diroff, FS_FILE_SEEK_SET) != 0) - goto err; - - for (itr = 0; itr < vec_size(pak->directories); itr++) - fs_file_write(&(pak->directories[itr]), sizeof(pak_directory_t), 1, pak->handle); - } - - vec_free (pak->directories); - fs_file_close(pak->handle); - mem_d (pak); - - return true; - -err: - vec_free (pak->directories); - fs_file_close(pak->handle); - mem_d (pak); - - return false; -} - -/* - * Fancy GCC-like LONG parsing allows things like --opt=param with - * assignment operator. This is used for redirecting stdout/stderr - * console to specific files of your choice. - */ -static bool parsecmd(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) { - int argc = *argc_; - char **argv = *argv_; - - size_t len = strlen(optname); - - if (strncmp(argv[0]+ds, optname, len)) - return false; - - /* it's --optname, check how the parameter is supplied */ - if (argv[0][ds+len] == '=') { - *out = argv[0]+ds+len+1; - return true; - } - - if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */ - return false; - - /* using --opt param */ - *out = argv[1]; - --*argc_; - ++*argv_; - return true; -} - -#include -int main(int argc, char **argv) { - bool extract = true; - char *redirout = (char*)stdout; - char *redirerr = (char*)stderr; - char *file = NULL; - char **files = NULL; - pak_file_t *pak = NULL; - size_t iter = 0; - - con_init(); - - /* - * Command line option parsing commences now We only need to support - * a few things in the test suite. - */ - while (argc > 1) { - ++argv; - --argc; - - if (argv[0][0] == '-') { - if (parsecmd("redirout", &argc, &argv, &redirout, 1, false)) - continue; - if (parsecmd("redirerr", &argc, &argv, &redirerr, 1, false)) - continue; - if (parsecmd("file", &argc, &argv, &file, 1, false)) - continue; - - con_change(redirout, redirerr); - - switch (argv[0][1]) { - case 'e': extract = true; continue; - case 'c': extract = false; continue; - } - - if (!strcmp(argv[0]+1, "debug")) { - OPTS_OPTION_BOOL(OPTION_DEBUG) = true; - continue; - } - if (!strcmp(argv[0]+1, "memchk")) { - OPTS_OPTION_BOOL(OPTION_MEMCHK) = true; - continue; - } - if (!strcmp(argv[0]+1, "nocolor")) { - con_color(0); - continue; - } - } - - vec_push(files, argv[0]); - } - con_change(redirout, redirerr); - - - if (!file) { - con_err("-file must be specified for output/input PAK file\n"); - vec_free(files); - return EXIT_FAILURE; - } - - if (extract) { - if (!(pak = pak_open(file, "r"))) { - con_err("failed to open PAK file %s\n", file); - vec_free(files); - return EXIT_FAILURE; - } - - if (!pak_extract_all(pak, "./")) { - con_err("failed to extract PAK %s (files may be missing)\n", file); - pak_close(pak); - vec_free(files); - return EXIT_FAILURE; - } - - /* not possible */ - pak_close(pak); - vec_free(files); - stat_info(); - - return EXIT_SUCCESS; - } - - if (!(pak = pak_open(file, "w"))) { - con_err("failed to open PAK %s for writing\n", file); - vec_free(files); - return EXIT_FAILURE; - } - - for (iter = 0; iter < vec_size(files); iter++) { - if (!(pak_insert_one(pak, files[iter]))) { - con_err("failed inserting %s for PAK %s\n", files[iter], file); - pak_close(pak); - vec_free(files); - return EXIT_FAILURE; - } - } - - /* not possible */ - pak_close(pak); - vec_free(files); - - stat_info(); - return EXIT_SUCCESS; -} diff --git a/parser.c b/parser.c deleted file mode 100644 index 672ce86..0000000 --- a/parser.c +++ /dev/null @@ -1,6540 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * Wolfgang Bumiller - * 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 -#include - -#include "parser.h" - -#define PARSER_HT_LOCALS 2 -#define PARSER_HT_SIZE 512 -#define TYPEDEF_HT_SIZE 512 - -static void parser_enterblock(parser_t *parser); -static bool parser_leaveblock(parser_t *parser); -static void parser_addlocal(parser_t *parser, const char *name, ast_expression *e); -static void parser_addglobal(parser_t *parser, const char *name, ast_expression *e); -static bool parse_typedef(parser_t *parser); -static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofields, int qualifier, ast_value *cached_typedef, bool noref, bool is_static, uint32_t qflags, char *vstring); -static ast_block* parse_block(parser_t *parser); -static bool parse_block_into(parser_t *parser, ast_block *block); -static bool parse_statement_or_block(parser_t *parser, ast_expression **out); -static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases); -static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue, bool with_labels); -static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool with_labels); -static ast_value* parser_create_array_setter_proto(parser_t *parser, ast_value *array, const char *funcname); -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, bool *is_vararg); - -static void parseerror(parser_t *parser, const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - vcompile_error(parser->lex->tok.ctx, fmt, ap); - va_end(ap); -} - -/* returns true if it counts as an error */ -static bool GMQCC_WARN parsewarning(parser_t *parser, int warntype, const char *fmt, ...) -{ - bool r; - va_list ap; - va_start(ap, fmt); - r = vcompile_warning(parser->lex->tok.ctx, warntype, fmt, ap); - va_end(ap); - return r; -} - -/********************************************************************** - * parsing - */ - -static bool parser_next(parser_t *parser) -{ - /* lex_do kills the previous token */ - parser->tok = lex_do(parser->lex); - if (parser->tok == TOKEN_EOF) - return true; - if (parser->tok >= TOKEN_ERROR) { - parseerror(parser, "lex error"); - return false; - } - return true; -} - -#define parser_tokval(p) ((p)->lex->tok.value) -#define parser_token(p) (&((p)->lex->tok)) - -char *parser_strdup(const char *str) -{ - if (str && !*str) { - /* actually dup empty strings */ - char *out = (char*)mem_a(1); - *out = 0; - return out; - } - return util_strdup(str); -} - -static ast_expression* parser_find_field(parser_t *parser, const char *name) -{ - return ( ast_expression*)util_htget(parser->htfields, name); -} - -static ast_expression* parser_find_label(parser_t *parser, const char *name) -{ - size_t i; - for(i = 0; i < vec_size(parser->labels); i++) - if (!strcmp(parser->labels[i]->name, name)) - return (ast_expression*)parser->labels[i]; - return NULL; -} - -ast_expression* parser_find_global(parser_t *parser, const char *name) -{ - ast_expression *var = (ast_expression*)util_htget(parser->aliases, parser_tokval(parser)); - if (var) - return var; - return (ast_expression*)util_htget(parser->htglobals, name); -} - -static ast_expression* parser_find_param(parser_t *parser, const char *name) -{ - size_t i; - ast_value *fun; - if (!parser->function) - return NULL; - fun = parser->function->vtype; - for (i = 0; i < vec_size(fun->expression.params); ++i) { - if (!strcmp(fun->expression.params[i]->name, name)) - return (ast_expression*)(fun->expression.params[i]); - } - return NULL; -} - -static ast_expression* parser_find_local(parser_t *parser, const char *name, size_t upto, bool *isparam) -{ - size_t i, hash; - ast_expression *e; - - hash = util_hthash(parser->htglobals, name); - - *isparam = false; - for (i = vec_size(parser->variables); i > upto;) { - --i; - if ( (e = (ast_expression*)util_htgeth(parser->variables[i], name, hash)) ) - return e; - } - *isparam = true; - return parser_find_param(parser, name); -} - -static ast_expression* parser_find_var(parser_t *parser, const char *name) -{ - bool dummy; - ast_expression *v; - v = parser_find_local(parser, name, 0, &dummy); - if (!v) v = parser_find_global(parser, name); - return v; -} - -static ast_value* parser_find_typedef(parser_t *parser, const char *name, size_t upto) -{ - size_t i, hash; - ast_value *e; - hash = util_hthash(parser->typedefs[0], name); - - for (i = vec_size(parser->typedefs); i > upto;) { - --i; - if ( (e = (ast_value*)util_htgeth(parser->typedefs[i], name, hash)) ) - return e; - } - return NULL; -} - -typedef struct -{ - size_t etype; /* 0 = expression, others are operators */ - bool isparen; - size_t off; - ast_expression *out; - ast_block *block; /* for commas and function calls */ - lex_ctx_t ctx; -} sy_elem; - -enum { - PAREN_EXPR, - PAREN_FUNC, - PAREN_INDEX, - PAREN_TERNARY1, - PAREN_TERNARY2 -}; -typedef struct -{ - sy_elem *out; - sy_elem *ops; - size_t *argc; - unsigned int *paren; -} shunt; - -static sy_elem syexp(lex_ctx_t ctx, ast_expression *v) { - sy_elem e; - e.etype = 0; - e.off = 0; - e.out = v; - e.block = NULL; - e.ctx = ctx; - e.isparen = false; - return e; -} - -static sy_elem syblock(lex_ctx_t ctx, ast_block *v) { - sy_elem e; - e.etype = 0; - e.off = 0; - e.out = (ast_expression*)v; - e.block = v; - e.ctx = ctx; - e.isparen = false; - return e; -} - -static sy_elem syop(lex_ctx_t ctx, const oper_info *op) { - sy_elem e; - e.etype = 1 + (op - operators); - e.off = 0; - e.out = NULL; - e.block = NULL; - e.ctx = ctx; - e.isparen = false; - return e; -} - -static sy_elem syparen(lex_ctx_t ctx, size_t off) { - sy_elem e; - e.etype = 0; - e.off = off; - e.out = NULL; - e.block = NULL; - e.ctx = ctx; - e.isparen = true; - return e; -} - -/* With regular precedence rules, ent.foo[n] is the same as (ent.foo)[n], - * so we need to rotate it to become ent.(foo[n]). - */ -static bool rotate_entfield_array_index_nodes(ast_expression **out) -{ - ast_array_index *index, *oldindex; - ast_entfield *entfield; - - ast_value *field; - ast_expression *sub; - ast_expression *entity; - - lex_ctx_t ctx = ast_ctx(*out); - - if (!ast_istype(*out, ast_array_index)) - return false; - index = (ast_array_index*)*out; - - if (!ast_istype(index->array, ast_entfield)) - return false; - entfield = (ast_entfield*)index->array; - - if (!ast_istype(entfield->field, ast_value)) - return false; - field = (ast_value*)entfield->field; - - sub = index->index; - entity = entfield->entity; - - oldindex = index; - - index = ast_array_index_new(ctx, (ast_expression*)field, sub); - entfield = ast_entfield_new(ctx, entity, (ast_expression*)index); - *out = (ast_expression*)entfield; - - oldindex->array = NULL; - oldindex->index = NULL; - ast_delete(oldindex); - - return true; -} - -static bool check_write_to(lex_ctx_t ctx, ast_expression *expr) -{ - if (ast_istype(expr, ast_value)) { - ast_value *val = (ast_value*)expr; - if (val->cvq == CV_CONST) { - if (val->name[0] == '#') { - compile_error(ctx, "invalid assignment to a literal constant"); - return false; - } - /* - * To work around quakeworld we must elide the error and make it - * a warning instead. - */ - if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC) - compile_error(ctx, "assignment to constant `%s`", val->name); - else - (void)!compile_warning(ctx, WARN_CONST_OVERWRITE, "assignment to constant `%s`", val->name); - return false; - } - } - return true; -} - -static bool parser_sy_apply_operator(parser_t *parser, shunt *sy) -{ - const oper_info *op; - lex_ctx_t ctx; - ast_expression *out = NULL; - ast_expression *exprs[3]; - ast_block *blocks[3]; - ast_binstore *asbinstore; - size_t i, assignop, addop, subop; - qcint_t generated_op = 0; - - char ty1[1024]; - char ty2[1024]; - - if (!vec_size(sy->ops)) { - parseerror(parser, "internal error: missing operator"); - return false; - } - - if (vec_last(sy->ops).isparen) { - parseerror(parser, "unmatched parenthesis"); - return false; - } - - op = &operators[vec_last(sy->ops).etype - 1]; - ctx = vec_last(sy->ops).ctx; - - if (vec_size(sy->out) < op->operands) { - if (op->flags & OP_PREFIX) - compile_error(ctx, "expected expression after unary operator `%s`", op->op, (int)op->id); - else /* this should have errored previously already */ - compile_error(ctx, "expected expression after operator `%s`", op->op, (int)op->id); - return false; - } - - vec_shrinkby(sy->ops, 1); - - /* op(:?) has no input and no output */ - if (!op->operands) - return true; - - vec_shrinkby(sy->out, op->operands); - for (i = 0; i < op->operands; ++i) { - exprs[i] = sy->out[vec_size(sy->out)+i].out; - blocks[i] = sy->out[vec_size(sy->out)+i].block; - - if (exprs[i]->vtype == TYPE_NOEXPR && - !(i != 0 && op->id == opid2('?',':')) && - !(i == 1 && op->id == opid1('.'))) - { - if (ast_istype(exprs[i], ast_label)) - compile_error(ast_ctx(exprs[i]), "expected expression, got an unknown identifier"); - else - compile_error(ast_ctx(exprs[i]), "not an expression"); - (void)!compile_warning(ast_ctx(exprs[i]), WARN_DEBUG, "expression %u\n", (unsigned int)i); - } - } - - if (blocks[0] && !vec_size(blocks[0]->exprs) && op->id != opid1(',')) { - compile_error(ctx, "internal error: operator cannot be applied on empty blocks"); - return false; - } - -#define NotSameType(T) \ - (exprs[0]->vtype != exprs[1]->vtype || \ - exprs[0]->vtype != T) - - switch (op->id) - { - default: - compile_error(ctx, "internal error: unhandled operator: %s (%i)", op->op, (int)op->id); - return false; - - case opid1('.'): - if (exprs[0]->vtype == TYPE_VECTOR && - exprs[1]->vtype == TYPE_NOEXPR) - { - if (exprs[1] == (ast_expression*)parser->const_vec[0]) - out = (ast_expression*)ast_member_new(ctx, exprs[0], 0, NULL); - else if (exprs[1] == (ast_expression*)parser->const_vec[1]) - out = (ast_expression*)ast_member_new(ctx, exprs[0], 1, NULL); - else if (exprs[1] == (ast_expression*)parser->const_vec[2]) - out = (ast_expression*)ast_member_new(ctx, exprs[0], 2, NULL); - else { - compile_error(ctx, "access to invalid vector component"); - return false; - } - } - else if (exprs[0]->vtype == TYPE_ENTITY) { - if (exprs[1]->vtype != TYPE_FIELD) { - compile_error(ast_ctx(exprs[1]), "type error: right hand of member-operand should be an entity-field"); - return false; - } - out = (ast_expression*)ast_entfield_new(ctx, exprs[0], exprs[1]); - } - else if (exprs[0]->vtype == TYPE_VECTOR) { - compile_error(ast_ctx(exprs[1]), "vectors cannot be accessed this way"); - return false; - } - else { - compile_error(ast_ctx(exprs[1]), "type error: member-of operator on something that is not an entity or vector"); - return false; - } - break; - - case opid1('['): - if (exprs[0]->vtype != TYPE_ARRAY && - !(exprs[0]->vtype == TYPE_FIELD && - exprs[0]->next->vtype == TYPE_ARRAY)) - { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - compile_error(ast_ctx(exprs[0]), "cannot index value of type %s", ty1); - return false; - } - if (exprs[1]->vtype != TYPE_FLOAT) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - compile_error(ast_ctx(exprs[1]), "index must be of type float, not %s", ty1); - return false; - } - out = (ast_expression*)ast_array_index_new(ctx, exprs[0], exprs[1]); - if (rotate_entfield_array_index_nodes(&out)) - { -#if 0 - /* This is not broken in fteqcc anymore */ - if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_GMQCC) { - /* this error doesn't need to make us bail out */ - (void)!parsewarning(parser, WARN_EXTENSIONS, - "accessing array-field members of an entity without parenthesis\n" - " -> this is an extension from -std=gmqcc"); - } -#endif - } - break; - - case opid1(','): - if (vec_size(sy->paren) && vec_last(sy->paren) == PAREN_FUNC) { - vec_push(sy->out, syexp(ctx, exprs[0])); - vec_push(sy->out, syexp(ctx, exprs[1])); - vec_last(sy->argc)++; - return true; - } - if (blocks[0]) { - if (!ast_block_add_expr(blocks[0], exprs[1])) - return false; - } else { - blocks[0] = ast_block_new(ctx); - if (!ast_block_add_expr(blocks[0], exprs[0]) || - !ast_block_add_expr(blocks[0], exprs[1])) - { - return false; - } - } - ast_block_set_type(blocks[0], exprs[1]); - - vec_push(sy->out, syblock(ctx, blocks[0])); - return true; - - case opid2('+','P'): - out = exprs[0]; - break; - case opid2('-','P'): - if ((out = fold_op(parser->fold, op, exprs))) - break; - - if (exprs[0]->vtype != TYPE_FLOAT && - exprs[0]->vtype != TYPE_VECTOR) { - compile_error(ctx, "invalid types used in unary expression: cannot negate type %s", - type_name[exprs[0]->vtype]); - return false; - } - if (exprs[0]->vtype == TYPE_FLOAT) - out = (ast_expression*)ast_unary_new(ctx, VINSTR_NEG_F, exprs[0]); - else - out = (ast_expression*)ast_unary_new(ctx, VINSTR_NEG_V, exprs[0]); - break; - - case opid2('!','P'): - if (!(out = fold_op(parser->fold, op, exprs))) { - switch (exprs[0]->vtype) { - case TYPE_FLOAT: - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, exprs[0]); - break; - case TYPE_VECTOR: - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_V, exprs[0]); - break; - case TYPE_STRING: - if (OPTS_FLAG(TRUE_EMPTY_STRINGS)) - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, exprs[0]); - else - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_S, exprs[0]); - break; - /* we don't constant-fold NOT for these types */ - case TYPE_ENTITY: - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_ENT, exprs[0]); - break; - case TYPE_FUNCTION: - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_FNC, exprs[0]); - break; - default: - compile_error(ctx, "invalid types used in expression: cannot logically negate type %s", - type_name[exprs[0]->vtype]); - return false; - } - } - break; - - case opid1('+'): - if (exprs[0]->vtype != exprs[1]->vtype || - (exprs[0]->vtype != TYPE_VECTOR && exprs[0]->vtype != TYPE_FLOAT) ) - { - compile_error(ctx, "invalid types used in expression: cannot add type %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) { - switch (exprs[0]->vtype) { - case TYPE_FLOAT: - out = fold_binary(ctx, INSTR_ADD_F, exprs[0], exprs[1]); - break; - case TYPE_VECTOR: - out = fold_binary(ctx, INSTR_ADD_V, exprs[0], exprs[1]); - break; - default: - compile_error(ctx, "invalid types used in expression: cannot add type %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } - } - break; - case opid1('-'): - if (exprs[0]->vtype != exprs[1]->vtype || - (exprs[0]->vtype != TYPE_VECTOR && exprs[0]->vtype != TYPE_FLOAT)) - { - compile_error(ctx, "invalid types used in expression: cannot subtract type %s from %s", - type_name[exprs[1]->vtype], - type_name[exprs[0]->vtype]); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) { - switch (exprs[0]->vtype) { - case TYPE_FLOAT: - out = fold_binary(ctx, INSTR_SUB_F, exprs[0], exprs[1]); - break; - case TYPE_VECTOR: - out = fold_binary(ctx, INSTR_SUB_V, exprs[0], exprs[1]); - break; - default: - compile_error(ctx, "invalid types used in expression: cannot subtract type %s from %s", - type_name[exprs[1]->vtype], - type_name[exprs[0]->vtype]); - return false; - } - } - break; - case opid1('*'): - if (exprs[0]->vtype != exprs[1]->vtype && - !(exprs[0]->vtype == TYPE_VECTOR && - exprs[1]->vtype == TYPE_FLOAT) && - !(exprs[1]->vtype == TYPE_VECTOR && - exprs[0]->vtype == TYPE_FLOAT) - ) - { - compile_error(ctx, "invalid types used in expression: cannot multiply types %s and %s", - type_name[exprs[1]->vtype], - type_name[exprs[0]->vtype]); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) { - switch (exprs[0]->vtype) { - case TYPE_FLOAT: - if (exprs[1]->vtype == TYPE_VECTOR) - out = fold_binary(ctx, INSTR_MUL_FV, exprs[0], exprs[1]); - else - out = fold_binary(ctx, INSTR_MUL_F, exprs[0], exprs[1]); - break; - case TYPE_VECTOR: - if (exprs[1]->vtype == TYPE_FLOAT) - out = fold_binary(ctx, INSTR_MUL_VF, exprs[0], exprs[1]); - else - out = fold_binary(ctx, INSTR_MUL_V, exprs[0], exprs[1]); - break; - default: - compile_error(ctx, "invalid types used in expression: cannot multiply types %s and %s", - type_name[exprs[1]->vtype], - type_name[exprs[0]->vtype]); - return false; - } - } - break; - - case opid1('/'): - if (exprs[1]->vtype != TYPE_FLOAT) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types used in expression: cannot divide types %s and %s", ty1, ty2); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) { - if (exprs[0]->vtype == TYPE_FLOAT) - out = fold_binary(ctx, INSTR_DIV_F, exprs[0], exprs[1]); - else { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types used in expression: cannot divide types %s and %s", ty1, ty2); - return false; - } - } - break; - - case opid1('%'): - if (NotSameType(TYPE_FLOAT)) { - compile_error(ctx, "invalid types used in expression: cannot perform modulo operation between types %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } else if (!(out = fold_op(parser->fold, op, exprs))) { - /* generate a call to __builtin_mod */ - ast_expression *mod = intrin_func(parser->intrin, "mod"); - ast_call *call = NULL; - if (!mod) return false; /* can return null for missing floor */ - - call = ast_call_new(parser_ctx(parser), mod); - vec_push(call->params, exprs[0]); - vec_push(call->params, exprs[1]); - - out = (ast_expression*)call; - } - break; - - case opid2('%','='): - compile_error(ctx, "%= is unimplemented"); - return false; - - case opid1('|'): - case opid1('&'): - case opid1('^'): - if ( !(exprs[0]->vtype == TYPE_FLOAT && exprs[1]->vtype == TYPE_FLOAT) && - !(exprs[0]->vtype == TYPE_VECTOR && exprs[1]->vtype == TYPE_FLOAT) && - !(exprs[0]->vtype == TYPE_VECTOR && exprs[1]->vtype == TYPE_VECTOR)) - { - compile_error(ctx, "invalid types used in expression: cannot perform bit operations between types %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } - - if (!(out = fold_op(parser->fold, op, exprs))) { - /* - * IF the first expression is float, the following will be too - * since scalar ^ vector is not allowed. - */ - if (exprs[0]->vtype == TYPE_FLOAT) { - out = fold_binary(ctx, - (op->id == opid1('^') ? VINSTR_BITXOR : op->id == opid1('|') ? INSTR_BITOR : INSTR_BITAND), - exprs[0], exprs[1]); - } else { - /* - * The first is a vector: vector is allowed to bitop with vector and - * with scalar, branch here for the second operand. - */ - if (exprs[1]->vtype == TYPE_VECTOR) { - /* - * Bitop all the values of the vector components against the - * vectors components in question. - */ - out = fold_binary(ctx, - (op->id == opid1('^') ? VINSTR_BITXOR_V : op->id == opid1('|') ? VINSTR_BITOR_V : VINSTR_BITAND_V), - exprs[0], exprs[1]); - } else { - out = fold_binary(ctx, - (op->id == opid1('^') ? VINSTR_BITXOR_VF : op->id == opid1('|') ? VINSTR_BITOR_VF : VINSTR_BITAND_VF), - exprs[0], exprs[1]); - } - } - } - break; - - case opid2('<','<'): - case opid2('>','>'): - if (NotSameType(TYPE_FLOAT)) { - compile_error(ctx, "invalid types used in expression: cannot perform shift between types %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } - - if (!(out = fold_op(parser->fold, op, exprs))) { - ast_expression *shift = intrin_func(parser->intrin, (op->id == opid2('<','<')) ? "__builtin_lshift" : "__builtin_rshift"); - ast_call *call = ast_call_new(parser_ctx(parser), shift); - vec_push(call->params, exprs[0]); - vec_push(call->params, exprs[1]); - out = (ast_expression*)call; - } - break; - - case opid3('<','<','='): - case opid3('>','>','='): - if (NotSameType(TYPE_FLOAT)) { - compile_error(ctx, "invalid types used in expression: cannot perform shift operation between types %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } - - if(!(out = fold_op(parser->fold, op, exprs))) { - ast_expression *shift = intrin_func(parser->intrin, (op->id == opid3('<','<','=')) ? "__builtin_lshift" : "__builtin_rshift"); - ast_call *call = ast_call_new(parser_ctx(parser), shift); - vec_push(call->params, exprs[0]); - vec_push(call->params, exprs[1]); - out = (ast_expression*)ast_store_new( - parser_ctx(parser), - INSTR_STORE_F, - exprs[0], - (ast_expression*)call - ); - } - - break; - - case opid2('|','|'): - generated_op += 1; /* INSTR_OR */ - case opid2('&','&'): - generated_op += INSTR_AND; - if (!(out = fold_op(parser->fold, op, exprs))) { - if (OPTS_FLAG(PERL_LOGIC) && !ast_compare_type(exprs[0], exprs[1])) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types for logical operation with -fperl-logic: %s and %s", ty1, ty2); - return false; - } - for (i = 0; i < 2; ++i) { - if (OPTS_FLAG(CORRECT_LOGIC) && exprs[i]->vtype == TYPE_VECTOR) { - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_V, exprs[i]); - if (!out) break; - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, out); - if (!out) break; - exprs[i] = out; out = NULL; - if (OPTS_FLAG(PERL_LOGIC)) { - /* here we want to keep the right expressions' type */ - break; - } - } - else if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && exprs[i]->vtype == TYPE_STRING) { - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_S, exprs[i]); - if (!out) break; - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, out); - if (!out) break; - exprs[i] = out; out = NULL; - if (OPTS_FLAG(PERL_LOGIC)) { - /* here we want to keep the right expressions' type */ - break; - } - } - } - out = fold_binary(ctx, generated_op, exprs[0], exprs[1]); - } - break; - - case opid2('?',':'): - if (vec_last(sy->paren) != PAREN_TERNARY2) { - compile_error(ctx, "mismatched parenthesis/ternary"); - return false; - } - vec_pop(sy->paren); - if (!ast_compare_type(exprs[1], exprs[2])) { - ast_type_to_string(exprs[1], ty1, sizeof(ty1)); - ast_type_to_string(exprs[2], ty2, sizeof(ty2)); - compile_error(ctx, "operands of ternary expression must have the same type, got %s and %s", ty1, ty2); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) - out = (ast_expression*)ast_ternary_new(ctx, exprs[0], exprs[1], exprs[2]); - break; - - case opid2('*', '*'): - if (NotSameType(TYPE_FLOAT)) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types used in exponentiation: %s and %s", - ty1, ty2); - return false; - } - - if (!(out = fold_op(parser->fold, op, exprs))) { - ast_call *gencall = ast_call_new(parser_ctx(parser), intrin_func(parser->intrin, "pow")); - vec_push(gencall->params, exprs[0]); - vec_push(gencall->params, exprs[1]); - out = (ast_expression*)gencall; - } - break; - - case opid2('>', '<'): - if (NotSameType(TYPE_VECTOR)) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types used in cross product: %s and %s", - ty1, ty2); - return false; - } - - if (!(out = fold_op(parser->fold, op, exprs))) { - out = fold_binary( - parser_ctx(parser), - VINSTR_CROSS, - exprs[0], - exprs[1] - ); - } - - break; - - case opid3('<','=','>'): /* -1, 0, or 1 */ - if (NotSameType(TYPE_FLOAT)) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types used in comparision: %s and %s", - ty1, ty2); - - return false; - } - - if (!(out = fold_op(parser->fold, op, exprs))) { - /* This whole block is NOT fold_binary safe */ - ast_binary *eq = ast_binary_new(ctx, INSTR_EQ_F, exprs[0], exprs[1]); - - eq->refs = AST_REF_NONE; - - /* if (lt) { */ - out = (ast_expression*)ast_ternary_new(ctx, - (ast_expression*)ast_binary_new(ctx, INSTR_LT, exprs[0], exprs[1]), - /* out = -1 */ - (ast_expression*)parser->fold->imm_float[2], - /* } else { */ - /* if (eq) { */ - (ast_expression*)ast_ternary_new(ctx, (ast_expression*)eq, - /* out = 0 */ - (ast_expression*)parser->fold->imm_float[0], - /* } else { */ - /* out = 1 */ - (ast_expression*)parser->fold->imm_float[1] - /* } */ - ) - /* } */ - ); - - } - break; - - case opid1('>'): - generated_op += 1; /* INSTR_GT */ - case opid1('<'): - generated_op += 1; /* INSTR_LT */ - case opid2('>', '='): - generated_op += 1; /* INSTR_GE */ - case opid2('<', '='): - generated_op += INSTR_LE; - if (NotSameType(TYPE_FLOAT)) { - compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) - out = fold_binary(ctx, generated_op, exprs[0], exprs[1]); - break; - case opid2('!', '='): - if (exprs[0]->vtype != exprs[1]->vtype) { - compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) - out = fold_binary(ctx, type_ne_instr[exprs[0]->vtype], exprs[0], exprs[1]); - break; - case opid2('=', '='): - if (exprs[0]->vtype != exprs[1]->vtype) { - compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) - out = fold_binary(ctx, type_eq_instr[exprs[0]->vtype], exprs[0], exprs[1]); - break; - - case opid1('='): - if (ast_istype(exprs[0], ast_entfield)) { - ast_expression *field = ((ast_entfield*)exprs[0])->field; - if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) && - exprs[0]->vtype == TYPE_FIELD && - exprs[0]->next->vtype == TYPE_VECTOR) - { - assignop = type_storep_instr[TYPE_VECTOR]; - } - else - assignop = type_storep_instr[exprs[0]->vtype]; - if (assignop == VINSTR_END || !ast_compare_type(field->next, exprs[1])) - { - ast_type_to_string(field->next, ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - if (OPTS_FLAG(ASSIGN_FUNCTION_TYPES) && - field->next->vtype == TYPE_FUNCTION && - exprs[1]->vtype == TYPE_FUNCTION) - { - (void)!compile_warning(ctx, WARN_ASSIGN_FUNCTION_TYPES, - "invalid types in assignment: cannot assign %s to %s", ty2, ty1); - } - else - compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1); - } - } - else - { - if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) && - exprs[0]->vtype == TYPE_FIELD && - exprs[0]->next->vtype == TYPE_VECTOR) - { - assignop = type_store_instr[TYPE_VECTOR]; - } - else { - assignop = type_store_instr[exprs[0]->vtype]; - } - - if (assignop == VINSTR_END) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1); - } - else if (!ast_compare_type(exprs[0], exprs[1])) - { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - if (OPTS_FLAG(ASSIGN_FUNCTION_TYPES) && - exprs[0]->vtype == TYPE_FUNCTION && - exprs[1]->vtype == TYPE_FUNCTION) - { - (void)!compile_warning(ctx, WARN_ASSIGN_FUNCTION_TYPES, - "invalid types in assignment: cannot assign %s to %s", ty2, ty1); - } - else - compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1); - } - } - (void)check_write_to(ctx, exprs[0]); - /* When we're a vector of part of an entity field we use STOREP */ - if (ast_istype(exprs[0], ast_member) && ast_istype(((ast_member*)exprs[0])->owner, ast_entfield)) - assignop = INSTR_STOREP_F; - out = (ast_expression*)ast_store_new(ctx, assignop, exprs[0], exprs[1]); - break; - case opid3('+','+','P'): - case opid3('-','-','P'): - /* prefix ++ */ - if (exprs[0]->vtype != TYPE_FLOAT) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - compile_error(ast_ctx(exprs[0]), "invalid type for prefix increment: %s", ty1); - return false; - } - if (op->id == opid3('+','+','P')) - addop = INSTR_ADD_F; - else - addop = INSTR_SUB_F; - (void)check_write_to(ast_ctx(exprs[0]), exprs[0]); - if (ast_istype(exprs[0], ast_entfield)) { - out = (ast_expression*)ast_binstore_new(ctx, INSTR_STOREP_F, addop, - exprs[0], - (ast_expression*)parser->fold->imm_float[1]); - } else { - out = (ast_expression*)ast_binstore_new(ctx, INSTR_STORE_F, addop, - exprs[0], - (ast_expression*)parser->fold->imm_float[1]); - } - break; - case opid3('S','+','+'): - case opid3('S','-','-'): - /* prefix ++ */ - if (exprs[0]->vtype != TYPE_FLOAT) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - compile_error(ast_ctx(exprs[0]), "invalid type for suffix increment: %s", ty1); - return false; - } - if (op->id == opid3('S','+','+')) { - addop = INSTR_ADD_F; - subop = INSTR_SUB_F; - } else { - addop = INSTR_SUB_F; - subop = INSTR_ADD_F; - } - (void)check_write_to(ast_ctx(exprs[0]), exprs[0]); - if (ast_istype(exprs[0], ast_entfield)) { - out = (ast_expression*)ast_binstore_new(ctx, INSTR_STOREP_F, addop, - exprs[0], - (ast_expression*)parser->fold->imm_float[1]); - } else { - out = (ast_expression*)ast_binstore_new(ctx, INSTR_STORE_F, addop, - exprs[0], - (ast_expression*)parser->fold->imm_float[1]); - } - if (!out) - return false; - out = fold_binary(ctx, subop, - out, - (ast_expression*)parser->fold->imm_float[1]); - - break; - case opid2('+','='): - case opid2('-','='): - if (exprs[0]->vtype != exprs[1]->vtype || - (exprs[0]->vtype != TYPE_VECTOR && exprs[0]->vtype != TYPE_FLOAT) ) - { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s", - ty1, ty2); - return false; - } - (void)check_write_to(ctx, exprs[0]); - if (ast_istype(exprs[0], ast_entfield)) - assignop = type_storep_instr[exprs[0]->vtype]; - else - assignop = type_store_instr[exprs[0]->vtype]; - switch (exprs[0]->vtype) { - case TYPE_FLOAT: - out = (ast_expression*)ast_binstore_new(ctx, assignop, - (op->id == opid2('+','=') ? INSTR_ADD_F : INSTR_SUB_F), - exprs[0], exprs[1]); - break; - case TYPE_VECTOR: - out = (ast_expression*)ast_binstore_new(ctx, assignop, - (op->id == opid2('+','=') ? INSTR_ADD_V : INSTR_SUB_V), - exprs[0], exprs[1]); - break; - default: - compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - }; - break; - case opid2('*','='): - case opid2('/','='): - if (exprs[1]->vtype != TYPE_FLOAT || - !(exprs[0]->vtype == TYPE_FLOAT || - exprs[0]->vtype == TYPE_VECTOR)) - { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types used in expression: %s and %s", - ty1, ty2); - return false; - } - (void)check_write_to(ctx, exprs[0]); - if (ast_istype(exprs[0], ast_entfield)) - assignop = type_storep_instr[exprs[0]->vtype]; - else - assignop = type_store_instr[exprs[0]->vtype]; - switch (exprs[0]->vtype) { - case TYPE_FLOAT: - out = (ast_expression*)ast_binstore_new(ctx, assignop, - (op->id == opid2('*','=') ? INSTR_MUL_F : INSTR_DIV_F), - exprs[0], exprs[1]); - break; - case TYPE_VECTOR: - if (op->id == opid2('*','=')) { - out = (ast_expression*)ast_binstore_new(ctx, assignop, INSTR_MUL_VF, - exprs[0], exprs[1]); - } else { - out = fold_binary(ctx, INSTR_DIV_F, - (ast_expression*)parser->fold->imm_float[1], - exprs[1]); - if (!out) { - compile_error(ctx, "internal error: failed to generate division"); - return false; - } - out = (ast_expression*)ast_binstore_new(ctx, assignop, INSTR_MUL_VF, - exprs[0], out); - } - break; - default: - compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - }; - break; - case opid2('&','='): - case opid2('|','='): - case opid2('^','='): - if (NotSameType(TYPE_FLOAT) && NotSameType(TYPE_VECTOR)) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types used in expression: %s and %s", - ty1, ty2); - return false; - } - (void)check_write_to(ctx, exprs[0]); - if (ast_istype(exprs[0], ast_entfield)) - assignop = type_storep_instr[exprs[0]->vtype]; - else - assignop = type_store_instr[exprs[0]->vtype]; - if (exprs[0]->vtype == TYPE_FLOAT) - out = (ast_expression*)ast_binstore_new(ctx, assignop, - (op->id == opid2('^','=') ? VINSTR_BITXOR : op->id == opid2('&','=') ? INSTR_BITAND : INSTR_BITOR), - exprs[0], exprs[1]); - else - out = (ast_expression*)ast_binstore_new(ctx, assignop, - (op->id == opid2('^','=') ? VINSTR_BITXOR_V : op->id == opid2('&','=') ? VINSTR_BITAND_V : VINSTR_BITOR_V), - exprs[0], exprs[1]); - break; - case opid3('&','~','='): - /* This is like: a &= ~(b); - * But QC has no bitwise-not, so we implement it as - * a -= a & (b); - */ - if (NotSameType(TYPE_FLOAT) && NotSameType(TYPE_VECTOR)) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types used in expression: %s and %s", - ty1, ty2); - return false; - } - if (ast_istype(exprs[0], ast_entfield)) - assignop = type_storep_instr[exprs[0]->vtype]; - else - assignop = type_store_instr[exprs[0]->vtype]; - if (exprs[0]->vtype == TYPE_FLOAT) - out = fold_binary(ctx, INSTR_BITAND, exprs[0], exprs[1]); - else - out = fold_binary(ctx, VINSTR_BITAND_V, exprs[0], exprs[1]); - if (!out) - return false; - (void)check_write_to(ctx, exprs[0]); - if (exprs[0]->vtype == TYPE_FLOAT) - asbinstore = ast_binstore_new(ctx, assignop, INSTR_SUB_F, exprs[0], out); - else - asbinstore = ast_binstore_new(ctx, assignop, INSTR_SUB_V, exprs[0], out); - asbinstore->keep_dest = true; - out = (ast_expression*)asbinstore; - break; - - case opid3('l', 'e', 'n'): - if (exprs[0]->vtype != TYPE_STRING && exprs[0]->vtype != TYPE_ARRAY) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - compile_error(ast_ctx(exprs[0]), "invalid type for length operator: %s", ty1); - return false; - } - /* strings must be const, arrays are statically sized */ - if (exprs[0]->vtype == TYPE_STRING && - !(((ast_value*)exprs[0])->hasvalue && ((ast_value*)exprs[0])->cvq == CV_CONST)) - { - compile_error(ast_ctx(exprs[0]), "operand of length operator not a valid constant expression"); - return false; - } - out = fold_op(parser->fold, op, exprs); - break; - - case opid2('~', 'P'): - if (exprs[0]->vtype != TYPE_FLOAT && exprs[0]->vtype != TYPE_VECTOR) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - compile_error(ast_ctx(exprs[0]), "invalid type for bit not: %s", ty1); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) { - if (exprs[0]->vtype == TYPE_FLOAT) { - out = fold_binary(ctx, INSTR_SUB_F, (ast_expression*)parser->fold->imm_float[2], exprs[0]); - } else { - out = fold_binary(ctx, INSTR_SUB_V, (ast_expression*)parser->fold->imm_vector[1], exprs[0]); - } - } - break; - } -#undef NotSameType - if (!out) { - compile_error(ctx, "failed to apply operator %s", op->op); - return false; - } - - vec_push(sy->out, syexp(ctx, out)); - return true; -} - -static bool parser_close_call(parser_t *parser, shunt *sy) -{ - /* was a function call */ - ast_expression *fun; - ast_value *funval = NULL; - ast_call *call; - - size_t fid; - size_t paramcount, i; - bool fold = true; - - fid = vec_last(sy->ops).off; - vec_shrinkby(sy->ops, 1); - - /* out[fid] is the function - * everything above is parameters... - */ - if (!vec_size(sy->argc)) { - parseerror(parser, "internal error: no argument counter available"); - return false; - } - - paramcount = vec_last(sy->argc); - vec_pop(sy->argc); - - if (vec_size(sy->out) < fid) { - parseerror(parser, "internal error: broken function call%lu < %lu+%lu\n", - (unsigned long)vec_size(sy->out), - (unsigned long)fid, - (unsigned long)paramcount); - return false; - } - - /* - * TODO handle this at the intrinsic level with an ast_intrinsic - * node and codegen. - */ - if ((fun = sy->out[fid].out) == intrin_debug_typestring(parser->intrin)) { - char ty[1024]; - if (fid+2 != vec_size(sy->out) || - vec_last(sy->out).block) - { - parseerror(parser, "intrinsic __builtin_debug_typestring requires exactly 1 parameter"); - return false; - } - ast_type_to_string(vec_last(sy->out).out, ty, sizeof(ty)); - ast_unref(vec_last(sy->out).out); - sy->out[fid] = syexp(ast_ctx(vec_last(sy->out).out), - (ast_expression*)fold_constgen_string(parser->fold, ty, false)); - vec_shrinkby(sy->out, 1); - return true; - } - - /* - * Now we need to determine if the function that is being called is - * an intrinsic so we can evaluate if the arguments to it are constant - * and than fruitfully fold them. - */ -#define fold_can_1(X) \ - (ast_istype(((ast_expression*)(X)), ast_value) && (X)->hasvalue && ((X)->cvq == CV_CONST) && \ - ((ast_expression*)(X))->vtype != TYPE_FUNCTION) - - if (fid + 1 < vec_size(sy->out)) - ++paramcount; - - for (i = 0; i < paramcount; ++i) { - if (!fold_can_1((ast_value*)sy->out[fid + 1 + i].out)) { - fold = false; - break; - } - } - - /* - * All is well which ends well, if we make it into here we can ignore the - * intrinsic call and just evaluate it i.e constant fold it. - */ - if (fold && ast_istype(fun, ast_value) && ((ast_value*)fun)->intrinsic) { - ast_expression **exprs = NULL; - ast_expression *foldval = NULL; - - for (i = 0; i < paramcount; i++) - vec_push(exprs, sy->out[fid+1 + i].out); - - if (!(foldval = intrin_fold(parser->intrin, (ast_value*)fun, exprs))) { - vec_free(exprs); - goto fold_leave; - } - - /* - * Blub: what sorts of unreffing and resizing of - * sy->out should I be doing here? - */ - sy->out[fid] = syexp(foldval->node.context, foldval); - vec_shrinkby(sy->out, paramcount); - vec_free(exprs); - - return true; - } - - fold_leave: - call = ast_call_new(sy->ops[vec_size(sy->ops)].ctx, fun); - - if (!call) - return false; - - if (fid+1 + paramcount != vec_size(sy->out)) { - parseerror(parser, "internal error: parameter count mismatch: (%lu+1+%lu), %lu", - (unsigned long)fid, (unsigned long)paramcount, (unsigned long)vec_size(sy->out)); - return false; - } - - for (i = 0; i < paramcount; ++i) - vec_push(call->params, sy->out[fid+1 + i].out); - vec_shrinkby(sy->out, paramcount); - (void)!ast_call_check_types(call, parser->function->vtype->expression.varparam); - if (parser->max_param_count < paramcount) - parser->max_param_count = paramcount; - - if (ast_istype(fun, ast_value)) { - funval = (ast_value*)fun; - if ((fun->flags & AST_FLAG_VARIADIC) && - !(/*funval->cvq == CV_CONST && */ funval->hasvalue && funval->constval.vfunc->builtin)) - { - call->va_count = (ast_expression*)fold_constgen_float(parser->fold, (qcfloat_t)paramcount, false); - } - } - - /* overwrite fid, the function, with a call */ - sy->out[fid] = syexp(call->expression.node.context, (ast_expression*)call); - - if (fun->vtype != TYPE_FUNCTION) { - parseerror(parser, "not a function (%s)", type_name[fun->vtype]); - return false; - } - - if (!fun->next) { - parseerror(parser, "could not determine function return type"); - return false; - } else { - ast_value *fval = (ast_istype(fun, ast_value) ? ((ast_value*)fun) : NULL); - - if (fun->flags & AST_FLAG_DEPRECATED) { - if (!fval) { - return !parsewarning(parser, WARN_DEPRECATED, - "call to function (which is marked deprecated)\n", - "-> it has been declared here: %s:%i", - ast_ctx(fun).file, ast_ctx(fun).line); - } - if (!fval->desc) { - return !parsewarning(parser, WARN_DEPRECATED, - "call to `%s` (which is marked deprecated)\n" - "-> `%s` declared here: %s:%i", - fval->name, fval->name, ast_ctx(fun).file, ast_ctx(fun).line); - } - return !parsewarning(parser, WARN_DEPRECATED, - "call to `%s` (deprecated: %s)\n" - "-> `%s` declared here: %s:%i", - fval->name, fval->desc, fval->name, ast_ctx(fun).file, - ast_ctx(fun).line); - } - - if (vec_size(fun->params) != paramcount && - !((fun->flags & AST_FLAG_VARIADIC) && - vec_size(fun->params) < paramcount)) - { - const char *fewmany = (vec_size(fun->params) > paramcount) ? "few" : "many"; - if (fval) - return !parsewarning(parser, WARN_INVALID_PARAMETER_COUNT, - "too %s parameters for call to %s: expected %i, got %i\n" - " -> `%s` has been declared here: %s:%i", - fewmany, fval->name, (int)vec_size(fun->params), (int)paramcount, - fval->name, ast_ctx(fun).file, (int)ast_ctx(fun).line); - else - return !parsewarning(parser, WARN_INVALID_PARAMETER_COUNT, - "too %s parameters for function call: expected %i, got %i\n" - " -> it has been declared here: %s:%i", - fewmany, (int)vec_size(fun->params), (int)paramcount, - ast_ctx(fun).file, (int)ast_ctx(fun).line); - } - } - - return true; -} - -static bool parser_close_paren(parser_t *parser, shunt *sy) -{ - if (!vec_size(sy->ops)) { - parseerror(parser, "unmatched closing paren"); - return false; - } - - while (vec_size(sy->ops)) { - if (vec_last(sy->ops).isparen) { - if (vec_last(sy->paren) == PAREN_FUNC) { - vec_pop(sy->paren); - if (!parser_close_call(parser, sy)) - return false; - break; - } - if (vec_last(sy->paren) == PAREN_EXPR) { - vec_pop(sy->paren); - if (!vec_size(sy->out)) { - compile_error(vec_last(sy->ops).ctx, "empty paren expression"); - vec_shrinkby(sy->ops, 1); - return false; - } - vec_shrinkby(sy->ops, 1); - break; - } - if (vec_last(sy->paren) == PAREN_INDEX) { - vec_pop(sy->paren); - /* pop off the parenthesis */ - vec_shrinkby(sy->ops, 1); - /* then apply the index operator */ - if (!parser_sy_apply_operator(parser, sy)) - return false; - break; - } - if (vec_last(sy->paren) == PAREN_TERNARY1) { - vec_last(sy->paren) = PAREN_TERNARY2; - /* pop off the parenthesis */ - vec_shrinkby(sy->ops, 1); - break; - } - compile_error(vec_last(sy->ops).ctx, "invalid parenthesis"); - return false; - } - if (!parser_sy_apply_operator(parser, sy)) - return false; - } - return true; -} - -static void parser_reclassify_token(parser_t *parser) -{ - size_t i; - if (parser->tok >= TOKEN_START) - return; - for (i = 0; i < operator_count; ++i) { - if (!strcmp(parser_tokval(parser), operators[i].op)) { - parser->tok = TOKEN_OPERATOR; - return; - } - } -} - -static ast_expression* parse_vararg_do(parser_t *parser) -{ - ast_expression *idx, *out; - ast_value *typevar; - ast_value *funtype = parser->function->vtype; - lex_ctx_t ctx = parser_ctx(parser); - - if (!parser->function->varargs) { - parseerror(parser, "function has no variable argument list"); - return NULL; - } - - if (!parser_next(parser) || parser->tok != '(') { - parseerror(parser, "expected parameter index and type in parenthesis"); - return NULL; - } - if (!parser_next(parser)) { - parseerror(parser, "error parsing parameter index"); - return NULL; - } - - idx = parse_expression_leave(parser, true, false, false); - if (!idx) - return NULL; - - if (parser->tok != ',') { - if (parser->tok != ')') { - ast_unref(idx); - parseerror(parser, "expected comma after parameter index"); - return NULL; - } - /* vararg piping: ...(start) */ - out = (ast_expression*)ast_argpipe_new(ctx, idx); - return out; - } - - if (!parser_next(parser) || (parser->tok != TOKEN_IDENT && parser->tok != TOKEN_TYPENAME)) { - ast_unref(idx); - parseerror(parser, "expected typename for vararg"); - return NULL; - } - - typevar = parse_typename(parser, NULL, NULL, NULL); - if (!typevar) { - ast_unref(idx); - return NULL; - } - - if (parser->tok != ')') { - ast_unref(idx); - ast_delete(typevar); - parseerror(parser, "expected closing paren"); - return NULL; - } - - if (funtype->expression.varparam && - !ast_compare_type((ast_expression*)typevar, (ast_expression*)funtype->expression.varparam)) - { - char ty1[1024]; - char ty2[1024]; - ast_type_to_string((ast_expression*)typevar, ty1, sizeof(ty1)); - ast_type_to_string((ast_expression*)funtype->expression.varparam, ty2, sizeof(ty2)); - compile_error(ast_ctx(typevar), - "function was declared to take varargs of type `%s`, requested type is: %s", - ty2, ty1); - } - - out = (ast_expression*)ast_array_index_new(ctx, (ast_expression*)(parser->function->varargs), idx); - ast_type_adopt(out, typevar); - ast_delete(typevar); - return out; -} - -static ast_expression* parse_vararg(parser_t *parser) -{ - bool old_noops = parser->lex->flags.noops; - - ast_expression *out; - - parser->lex->flags.noops = true; - out = parse_vararg_do(parser); - - parser->lex->flags.noops = old_noops; - return out; -} - -/* not to be exposed */ -bool ftepp_predef_exists(const char *name); -static bool parse_sya_operand(parser_t *parser, shunt *sy, bool with_labels) -{ - if (OPTS_FLAG(TRANSLATABLE_STRINGS) && - parser->tok == TOKEN_IDENT && - !strcmp(parser_tokval(parser), "_")) - { - /* a translatable string */ - ast_value *val; - - parser->lex->flags.noops = true; - if (!parser_next(parser) || parser->tok != '(') { - parseerror(parser, "use _(\"string\") to create a translatable string constant"); - return false; - } - parser->lex->flags.noops = false; - if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) { - parseerror(parser, "expected a constant string in translatable-string extension"); - return false; - } - val = (ast_value*)fold_constgen_string(parser->fold, parser_tokval(parser), true); - if (!val) - return false; - vec_push(sy->out, syexp(parser_ctx(parser), (ast_expression*)val)); - - if (!parser_next(parser) || parser->tok != ')') { - parseerror(parser, "expected closing paren after translatable string"); - return false; - } - return true; - } - else if (parser->tok == TOKEN_DOTS) - { - ast_expression *va; - if (!OPTS_FLAG(VARIADIC_ARGS)) { - parseerror(parser, "cannot access varargs (try -fvariadic-args)"); - return false; - } - va = parse_vararg(parser); - if (!va) - return false; - vec_push(sy->out, syexp(parser_ctx(parser), va)); - return true; - } - else if (parser->tok == TOKEN_FLOATCONST) { - ast_expression *val = fold_constgen_float(parser->fold, (parser_token(parser)->constval.f), false); - if (!val) - return false; - vec_push(sy->out, syexp(parser_ctx(parser), val)); - return true; - } - else if (parser->tok == TOKEN_INTCONST || parser->tok == TOKEN_CHARCONST) { - ast_expression *val = fold_constgen_float(parser->fold, (qcfloat_t)(parser_token(parser)->constval.i), false); - if (!val) - return false; - vec_push(sy->out, syexp(parser_ctx(parser), val)); - return true; - } - else if (parser->tok == TOKEN_STRINGCONST) { - ast_expression *val = fold_constgen_string(parser->fold, parser_tokval(parser), false); - if (!val) - return false; - vec_push(sy->out, syexp(parser_ctx(parser), val)); - return true; - } - else if (parser->tok == TOKEN_VECTORCONST) { - ast_expression *val = fold_constgen_vector(parser->fold, parser_token(parser)->constval.v); - if (!val) - return false; - vec_push(sy->out, syexp(parser_ctx(parser), val)); - return true; - } - else if (parser->tok == TOKEN_IDENT) - { - const char *ctoken = parser_tokval(parser); - ast_expression *prev = vec_size(sy->out) ? vec_last(sy->out).out : NULL; - ast_expression *var; - /* a_vector.{x,y,z} */ - if (!vec_size(sy->ops) || - !vec_last(sy->ops).etype || - operators[vec_last(sy->ops).etype-1].id != opid1('.')) - { - /* When adding more intrinsics, fix the above condition */ - prev = NULL; - } - if (prev && prev->vtype == TYPE_VECTOR && ctoken[0] >= 'x' && ctoken[0] <= 'z' && !ctoken[1]) - { - var = (ast_expression*)parser->const_vec[ctoken[0]-'x']; - } else { - var = parser_find_var(parser, parser_tokval(parser)); - if (!var) - var = parser_find_field(parser, parser_tokval(parser)); - } - if (!var && with_labels) { - var = (ast_expression*)parser_find_label(parser, parser_tokval(parser)); - if (!with_labels) { - ast_label *lbl = ast_label_new(parser_ctx(parser), parser_tokval(parser), true); - var = (ast_expression*)lbl; - vec_push(parser->labels, lbl); - } - } - if (!var && !strcmp(parser_tokval(parser), "__FUNC__")) - var = (ast_expression*)fold_constgen_string(parser->fold, parser->function->name, false); - if (!var) { - /* - * 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. - */ - if (!strncmp(parser_tokval(parser), "__builtin_", 10)) { - var = intrin_func(parser->intrin, parser_tokval(parser)); - } - - /* - * Try it again, intrin_func deals with the alias method as well - * the first one masks for __builtin though, we emit warning here. - */ - if (!var) { - if ((var = intrin_func(parser->intrin, parser_tokval(parser)))) { - (void)!compile_warning( - parser_ctx(parser), - WARN_BUILTINS, - "using implicitly defined builtin `__builtin_%s' for `%s'", - parser_tokval(parser), - parser_tokval(parser) - ); - } - } - - - if (!var) { - char *correct = NULL; - size_t i; - - /* - * sometimes people use preprocessing predefs without enabling them - * i've done this thousands of times already myself. Lets check for - * it in the predef table. And diagnose it better :) - */ - if (!OPTS_FLAG(FTEPP_PREDEFS) && ftepp_predef_exists(parser_tokval(parser))) { - parseerror(parser, "unexpected identifier: %s (use -fftepp-predef to enable pre-defined macros)", parser_tokval(parser)); - return false; - } - - /* - * TODO: determine the best score for the identifier: be it - * a variable, a field. - * - * We should also consider adding correction tables for - * other things as well. - */ - if (OPTS_OPTION_BOOL(OPTION_CORRECTION) && strlen(parser_tokval(parser)) <= 16) { - correction_t corr; - correct_init(&corr); - - for (i = 0; i < vec_size(parser->correct_variables); i++) { - correct = correct_str(&corr, parser->correct_variables[i], parser_tokval(parser)); - if (strcmp(correct, parser_tokval(parser))) { - break; - } else { - mem_d(correct); - correct = NULL; - } - } - correct_free(&corr); - - if (correct) { - parseerror(parser, "unexpected identifier: %s (did you mean %s?)", parser_tokval(parser), correct); - mem_d(correct); - return false; - } - } - parseerror(parser, "unexpected identifier: %s", parser_tokval(parser)); - return false; - } - } - else - { - if (ast_istype(var, ast_value)) { - ((ast_value*)var)->uses++; - } - else if (ast_istype(var, ast_member)) { - ast_member *mem = (ast_member*)var; - if (ast_istype(mem->owner, ast_value)) - ((ast_value*)(mem->owner))->uses++; - } - } - vec_push(sy->out, syexp(parser_ctx(parser), var)); - return true; - } - parseerror(parser, "unexpected token `%s`", parser_tokval(parser)); - return false; -} - -static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue, bool with_labels) -{ - ast_expression *expr = NULL; - shunt sy; - size_t i; - bool wantop = false; - /* only warn once about an assignment in a truth value because the current code - * would trigger twice on: if(a = b && ...), once for the if-truth-value, once for the && part - */ - bool warn_parenthesis = true; - - /* count the parens because an if starts with one, so the - * end of a condition is an unmatched closing paren - */ - int ternaries = 0; - - memset(&sy, 0, sizeof(sy)); - - parser->lex->flags.noops = false; - - parser_reclassify_token(parser); - - while (true) - { - if (parser->tok == TOKEN_TYPENAME) { - parseerror(parser, "unexpected typename `%s`", parser_tokval(parser)); - goto onerr; - } - - if (parser->tok == TOKEN_OPERATOR) - { - /* classify the operator */ - const oper_info *op; - const oper_info *olast = NULL; - size_t o; - for (o = 0; o < operator_count; ++o) { - if (((!(operators[o].flags & OP_PREFIX) == !!wantop)) && - /* !(operators[o].flags & OP_SUFFIX) && / * remove this */ - !strcmp(parser_tokval(parser), operators[o].op)) - { - break; - } - } - if (o == operator_count) { - compile_error(parser_ctx(parser), "unexpected operator: %s", parser_tokval(parser)); - goto onerr; - } - /* found an operator */ - op = &operators[o]; - - /* when declaring variables, a comma starts a new variable */ - if (op->id == opid1(',') && !vec_size(sy.paren) && stopatcomma) { - /* fixup the token */ - parser->tok = ','; - break; - } - - /* a colon without a pervious question mark cannot be a ternary */ - if (!ternaries && op->id == opid2(':','?')) { - parser->tok = ':'; - break; - } - - if (op->id == opid1(',')) { - if (vec_size(sy.paren) && vec_last(sy.paren) == PAREN_TERNARY2) { - (void)!parsewarning(parser, WARN_TERNARY_PRECEDENCE, "suggesting parenthesis around ternary expression"); - } - } - - if (vec_size(sy.ops) && !vec_last(sy.ops).isparen) - olast = &operators[vec_last(sy.ops).etype-1]; - - /* first only apply higher precedences, assoc_left+equal comes after we warn about precedence rules */ - while (olast && op->prec < olast->prec) - { - if (!parser_sy_apply_operator(parser, &sy)) - goto onerr; - if (vec_size(sy.ops) && !vec_last(sy.ops).isparen) - olast = &operators[vec_last(sy.ops).etype-1]; - else - olast = NULL; - } - -#define IsAssignOp(x) (\ - (x) == opid1('=') || \ - (x) == opid2('+','=') || \ - (x) == opid2('-','=') || \ - (x) == opid2('*','=') || \ - (x) == opid2('/','=') || \ - (x) == opid2('%','=') || \ - (x) == opid2('&','=') || \ - (x) == opid2('|','=') || \ - (x) == opid3('&','~','=') \ - ) - if (warn_parenthesis) { - if ( (olast && IsAssignOp(olast->id) && (op->id == opid2('&','&') || op->id == opid2('|','|'))) || - (olast && IsAssignOp(op->id) && (olast->id == opid2('&','&') || olast->id == opid2('|','|'))) || - (truthvalue && !vec_size(sy.paren) && IsAssignOp(op->id)) - ) - { - (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around assignment used as truth value"); - warn_parenthesis = false; - } - - if (olast && olast->id != op->id) { - if ((op->id == opid1('&') || op->id == opid1('|') || op->id == opid1('^')) && - (olast->id == opid1('&') || olast->id == opid1('|') || olast->id == opid1('^'))) - { - (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around bitwise operations"); - warn_parenthesis = false; - } - else if ((op->id == opid2('&','&') || op->id == opid2('|','|')) && - (olast->id == opid2('&','&') || olast->id == opid2('|','|'))) - { - (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around logical operations"); - warn_parenthesis = false; - } - } - } - - while (olast && ( - (op->prec < olast->prec) || - (op->assoc == ASSOC_LEFT && op->prec <= olast->prec) ) ) - { - if (!parser_sy_apply_operator(parser, &sy)) - goto onerr; - if (vec_size(sy.ops) && !vec_last(sy.ops).isparen) - olast = &operators[vec_last(sy.ops).etype-1]; - else - olast = NULL; - } - - if (op->id == opid1('(')) { - if (wantop) { - size_t sycount = vec_size(sy.out); - /* we expected an operator, this is the function-call operator */ - vec_push(sy.paren, PAREN_FUNC); - vec_push(sy.ops, syparen(parser_ctx(parser), sycount-1)); - vec_push(sy.argc, 0); - } else { - vec_push(sy.paren, PAREN_EXPR); - vec_push(sy.ops, syparen(parser_ctx(parser), 0)); - } - wantop = false; - } else if (op->id == opid1('[')) { - if (!wantop) { - parseerror(parser, "unexpected array subscript"); - goto onerr; - } - vec_push(sy.paren, PAREN_INDEX); - /* push both the operator and the paren, this makes life easier */ - vec_push(sy.ops, syop(parser_ctx(parser), op)); - vec_push(sy.ops, syparen(parser_ctx(parser), 0)); - wantop = false; - } else if (op->id == opid2('?',':')) { - vec_push(sy.ops, syop(parser_ctx(parser), op)); - vec_push(sy.ops, syparen(parser_ctx(parser), 0)); - wantop = false; - ++ternaries; - vec_push(sy.paren, PAREN_TERNARY1); - } else if (op->id == opid2(':','?')) { - if (!vec_size(sy.paren)) { - parseerror(parser, "unexpected colon outside ternary expression (missing parenthesis?)"); - goto onerr; - } - if (vec_last(sy.paren) != PAREN_TERNARY1) { - parseerror(parser, "unexpected colon outside ternary expression (missing parenthesis?)"); - goto onerr; - } - if (!parser_close_paren(parser, &sy)) - goto onerr; - vec_push(sy.ops, syop(parser_ctx(parser), op)); - wantop = false; - --ternaries; - } else { - vec_push(sy.ops, syop(parser_ctx(parser), op)); - wantop = !!(op->flags & OP_SUFFIX); - } - } - else if (parser->tok == ')') { - while (vec_size(sy.paren) && vec_last(sy.paren) == PAREN_TERNARY2) { - if (!parser_sy_apply_operator(parser, &sy)) - goto onerr; - } - if (!vec_size(sy.paren)) - break; - if (wantop) { - if (vec_last(sy.paren) == PAREN_TERNARY1) { - parseerror(parser, "mismatched parentheses (closing paren in ternary expression?)"); - goto onerr; - } - if (!parser_close_paren(parser, &sy)) - goto onerr; - } else { - /* must be a function call without parameters */ - if (vec_last(sy.paren) != PAREN_FUNC) { - parseerror(parser, "closing paren in invalid position"); - goto onerr; - } - if (!parser_close_paren(parser, &sy)) - goto onerr; - } - wantop = true; - } - else if (parser->tok == '(') { - parseerror(parser, "internal error: '(' should be classified as operator"); - goto onerr; - } - else if (parser->tok == '[') { - parseerror(parser, "internal error: '[' should be classified as operator"); - goto onerr; - } - else if (parser->tok == ']') { - while (vec_size(sy.paren) && vec_last(sy.paren) == PAREN_TERNARY2) { - if (!parser_sy_apply_operator(parser, &sy)) - goto onerr; - } - if (!vec_size(sy.paren)) - break; - if (vec_last(sy.paren) != PAREN_INDEX) { - parseerror(parser, "mismatched parentheses, unexpected ']'"); - goto onerr; - } - if (!parser_close_paren(parser, &sy)) - goto onerr; - wantop = true; - } - else if (!wantop) { - if (!parse_sya_operand(parser, &sy, with_labels)) - goto onerr; -#if 0 - if (vec_size(sy.paren) && vec_last(sy.ops).isparen && vec_last(sy.paren) == PAREN_FUNC) - vec_last(sy.argc)++; -#endif - wantop = true; - } - else { - /* in this case we might want to allow constant string concatenation */ - bool concatenated = false; - if (parser->tok == TOKEN_STRINGCONST && vec_size(sy.out)) { - ast_expression *lexpr = vec_last(sy.out).out; - if (ast_istype(lexpr, ast_value)) { - ast_value *last = (ast_value*)lexpr; - if (last->isimm == true && last->cvq == CV_CONST && - last->hasvalue && last->expression.vtype == TYPE_STRING) - { - char *newstr = NULL; - util_asprintf(&newstr, "%s%s", last->constval.vstring, parser_tokval(parser)); - vec_last(sy.out).out = (ast_expression*)fold_constgen_string(parser->fold, newstr, false); - mem_d(newstr); - concatenated = true; - } - } - } - if (!concatenated) { - parseerror(parser, "expected operator or end of statement"); - goto onerr; - } - } - - if (!parser_next(parser)) { - goto onerr; - } - if (parser->tok == ';' || - ((!vec_size(sy.paren) || (vec_size(sy.paren) == 1 && vec_last(sy.paren) == PAREN_TERNARY2)) && - (parser->tok == ']' || parser->tok == ')' || parser->tok == '}'))) - { - break; - } - } - - while (vec_size(sy.ops)) { - if (!parser_sy_apply_operator(parser, &sy)) - goto onerr; - } - - parser->lex->flags.noops = true; - if (vec_size(sy.out) != 1) { - parseerror(parser, "expression expected"); - expr = NULL; - } else - expr = sy.out[0].out; - vec_free(sy.out); - vec_free(sy.ops); - if (vec_size(sy.paren)) { - parseerror(parser, "internal error: vec_size(sy.paren) = %lu", (unsigned long)vec_size(sy.paren)); - return NULL; - } - vec_free(sy.paren); - vec_free(sy.argc); - return expr; - -onerr: - parser->lex->flags.noops = true; - for (i = 0; i < vec_size(sy.out); ++i) { - if (sy.out[i].out) - ast_unref(sy.out[i].out); - } - vec_free(sy.out); - vec_free(sy.ops); - vec_free(sy.paren); - vec_free(sy.argc); - return NULL; -} - -static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool with_labels) -{ - ast_expression *e = parse_expression_leave(parser, stopatcomma, false, with_labels); - if (!e) - return NULL; - if (parser->tok != ';') { - parseerror(parser, "semicolon expected after expression"); - ast_unref(e); - return NULL; - } - if (!parser_next(parser)) { - ast_unref(e); - return NULL; - } - return e; -} - -static void parser_enterblock(parser_t *parser) -{ - vec_push(parser->variables, util_htnew(PARSER_HT_SIZE)); - vec_push(parser->_blocklocals, vec_size(parser->_locals)); - vec_push(parser->typedefs, util_htnew(TYPEDEF_HT_SIZE)); - vec_push(parser->_blocktypedefs, vec_size(parser->_typedefs)); - vec_push(parser->_block_ctx, parser_ctx(parser)); - - /* corrector */ - vec_push(parser->correct_variables, correct_trie_new()); - vec_push(parser->correct_variables_score, NULL); -} - -static bool parser_leaveblock(parser_t *parser) -{ - bool rv = true; - size_t locals, typedefs; - - if (vec_size(parser->variables) <= PARSER_HT_LOCALS) { - parseerror(parser, "internal error: parser_leaveblock with no block"); - return false; - } - - util_htdel(vec_last(parser->variables)); - correct_del(vec_last(parser->correct_variables), vec_last(parser->correct_variables_score)); - - vec_pop(parser->variables); - vec_pop(parser->correct_variables); - vec_pop(parser->correct_variables_score); - if (!vec_size(parser->_blocklocals)) { - parseerror(parser, "internal error: parser_leaveblock with no block (2)"); - return false; - } - - locals = vec_last(parser->_blocklocals); - vec_pop(parser->_blocklocals); - while (vec_size(parser->_locals) != locals) { - ast_expression *e = vec_last(parser->_locals); - ast_value *v = (ast_value*)e; - vec_pop(parser->_locals); - if (ast_istype(e, ast_value) && !v->uses) { - if (compile_warning(ast_ctx(v), WARN_UNUSED_VARIABLE, "unused variable: `%s`", v->name)) - rv = false; - } - } - - typedefs = vec_last(parser->_blocktypedefs); - while (vec_size(parser->_typedefs) != typedefs) { - ast_delete(vec_last(parser->_typedefs)); - vec_pop(parser->_typedefs); - } - util_htdel(vec_last(parser->typedefs)); - vec_pop(parser->typedefs); - - vec_pop(parser->_block_ctx); - - return rv; -} - -static void parser_addlocal(parser_t *parser, const char *name, ast_expression *e) -{ - vec_push(parser->_locals, e); - util_htset(vec_last(parser->variables), name, (void*)e); - - /* corrector */ - correct_add ( - vec_last(parser->correct_variables), - &vec_last(parser->correct_variables_score), - name - ); -} - -static void parser_addglobal(parser_t *parser, const char *name, ast_expression *e) -{ - vec_push(parser->globals, e); - util_htset(parser->htglobals, name, e); - - /* corrector */ - correct_add ( - parser->correct_variables[0], - &parser->correct_variables_score[0], - name - ); -} - -static ast_expression* process_condition(parser_t *parser, ast_expression *cond, bool *_ifnot) -{ - bool ifnot = false; - ast_unary *unary; - ast_expression *prev; - - if (cond->vtype == TYPE_VOID || cond->vtype >= TYPE_VARIANT) { - char ty[1024]; - ast_type_to_string(cond, ty, sizeof(ty)); - compile_error(ast_ctx(cond), "invalid type for if() condition: %s", ty); - } - - if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && cond->vtype == TYPE_STRING) - { - prev = cond; - cond = (ast_expression*)ast_unary_new(ast_ctx(cond), INSTR_NOT_S, cond); - if (!cond) { - ast_unref(prev); - parseerror(parser, "internal error: failed to process condition"); - return NULL; - } - ifnot = !ifnot; - } - else if (OPTS_FLAG(CORRECT_LOGIC) && cond->vtype == TYPE_VECTOR) - { - /* vector types need to be cast to true booleans */ - ast_binary *bin = (ast_binary*)cond; - if (!OPTS_FLAG(PERL_LOGIC) || !ast_istype(cond, ast_binary) || !(bin->op == INSTR_AND || bin->op == INSTR_OR)) - { - /* in perl-logic, AND and OR take care of the -fcorrect-logic */ - prev = cond; - cond = (ast_expression*)ast_unary_new(ast_ctx(cond), INSTR_NOT_V, cond); - if (!cond) { - ast_unref(prev); - parseerror(parser, "internal error: failed to process condition"); - return NULL; - } - ifnot = !ifnot; - } - } - - unary = (ast_unary*)cond; - /* ast_istype dereferences cond, should test here for safety */ - while (cond && ast_istype(cond, ast_unary) && unary->op == INSTR_NOT_F) - { - cond = unary->operand; - unary->operand = NULL; - ast_delete(unary); - ifnot = !ifnot; - unary = (ast_unary*)cond; - } - - if (!cond) - parseerror(parser, "internal error: failed to process condition"); - - if (ifnot) *_ifnot = !*_ifnot; - return cond; -} - -static bool parse_if(parser_t *parser, ast_block *block, ast_expression **out) -{ - ast_ifthen *ifthen; - ast_expression *cond, *ontrue = NULL, *onfalse = NULL; - bool ifnot = false; - - lex_ctx_t ctx = parser_ctx(parser); - - (void)block; /* not touching */ - - /* skip the 'if', parse an optional 'not' and check for an opening paren */ - if (!parser_next(parser)) { - parseerror(parser, "expected condition or 'not'"); - return false; - } - if (parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "not")) { - ifnot = true; - if (!parser_next(parser)) { - parseerror(parser, "expected condition in parenthesis"); - return false; - } - } - if (parser->tok != '(') { - parseerror(parser, "expected 'if' condition in parenthesis"); - return false; - } - /* parse into the expression */ - if (!parser_next(parser)) { - parseerror(parser, "expected 'if' condition after opening paren"); - return false; - } - /* parse the condition */ - cond = parse_expression_leave(parser, false, true, false); - if (!cond) - return false; - /* closing paren */ - if (parser->tok != ')') { - parseerror(parser, "expected closing paren after 'if' condition"); - ast_unref(cond); - return false; - } - /* parse into the 'then' branch */ - if (!parser_next(parser)) { - parseerror(parser, "expected statement for on-true branch of 'if'"); - ast_unref(cond); - return false; - } - if (!parse_statement_or_block(parser, &ontrue)) { - ast_unref(cond); - return false; - } - if (!ontrue) - ontrue = (ast_expression*)ast_block_new(parser_ctx(parser)); - /* check for an else */ - if (!strcmp(parser_tokval(parser), "else")) { - /* parse into the 'else' branch */ - if (!parser_next(parser)) { - parseerror(parser, "expected on-false branch after 'else'"); - ast_delete(ontrue); - ast_unref(cond); - return false; - } - if (!parse_statement_or_block(parser, &onfalse)) { - ast_delete(ontrue); - ast_unref(cond); - return false; - } - } - - cond = process_condition(parser, cond, &ifnot); - if (!cond) { - if (ontrue) ast_delete(ontrue); - if (onfalse) ast_delete(onfalse); - return false; - } - - if (ifnot) - ifthen = ast_ifthen_new(ctx, cond, onfalse, ontrue); - else - ifthen = ast_ifthen_new(ctx, cond, ontrue, onfalse); - *out = (ast_expression*)ifthen; - return true; -} - -static bool parse_while_go(parser_t *parser, ast_block *block, ast_expression **out); -static bool parse_while(parser_t *parser, ast_block *block, ast_expression **out) -{ - bool rv; - char *label = NULL; - - /* skip the 'while' and get the body */ - if (!parser_next(parser)) { - if (OPTS_FLAG(LOOP_LABELS)) - parseerror(parser, "expected loop label or 'while' condition in parenthesis"); - else - parseerror(parser, "expected 'while' condition in parenthesis"); - return false; - } - - if (parser->tok == ':') { - if (!OPTS_FLAG(LOOP_LABELS)) - parseerror(parser, "labeled loops not activated, try using -floop-labels"); - if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { - parseerror(parser, "expected loop label"); - return false; - } - label = util_strdup(parser_tokval(parser)); - if (!parser_next(parser)) { - mem_d(label); - parseerror(parser, "expected 'while' condition in parenthesis"); - return false; - } - } - - if (parser->tok != '(') { - parseerror(parser, "expected 'while' condition in parenthesis"); - return false; - } - - vec_push(parser->breaks, label); - vec_push(parser->continues, label); - - rv = parse_while_go(parser, block, out); - if (label) - mem_d(label); - if (vec_last(parser->breaks) != label || vec_last(parser->continues) != label) { - parseerror(parser, "internal error: label stack corrupted"); - rv = false; - ast_delete(*out); - *out = NULL; - } - else { - vec_pop(parser->breaks); - vec_pop(parser->continues); - } - return rv; -} - -static bool parse_while_go(parser_t *parser, ast_block *block, ast_expression **out) -{ - ast_loop *aloop; - ast_expression *cond, *ontrue; - - bool ifnot = false; - - lex_ctx_t ctx = parser_ctx(parser); - - (void)block; /* not touching */ - - /* parse into the expression */ - if (!parser_next(parser)) { - parseerror(parser, "expected 'while' condition after opening paren"); - return false; - } - /* parse the condition */ - cond = parse_expression_leave(parser, false, true, false); - if (!cond) - return false; - /* closing paren */ - if (parser->tok != ')') { - parseerror(parser, "expected closing paren after 'while' condition"); - ast_unref(cond); - return false; - } - /* parse into the 'then' branch */ - if (!parser_next(parser)) { - parseerror(parser, "expected while-loop body"); - ast_unref(cond); - return false; - } - if (!parse_statement_or_block(parser, &ontrue)) { - ast_unref(cond); - return false; - } - - cond = process_condition(parser, cond, &ifnot); - if (!cond) { - ast_unref(ontrue); - return false; - } - aloop = ast_loop_new(ctx, NULL, cond, ifnot, NULL, false, NULL, ontrue); - *out = (ast_expression*)aloop; - return true; -} - -static bool parse_dowhile_go(parser_t *parser, ast_block *block, ast_expression **out); -static bool parse_dowhile(parser_t *parser, ast_block *block, ast_expression **out) -{ - bool rv; - char *label = NULL; - - /* skip the 'do' and get the body */ - if (!parser_next(parser)) { - if (OPTS_FLAG(LOOP_LABELS)) - parseerror(parser, "expected loop label or body"); - else - parseerror(parser, "expected loop body"); - return false; - } - - if (parser->tok == ':') { - if (!OPTS_FLAG(LOOP_LABELS)) - parseerror(parser, "labeled loops not activated, try using -floop-labels"); - if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { - parseerror(parser, "expected loop label"); - return false; - } - label = util_strdup(parser_tokval(parser)); - if (!parser_next(parser)) { - mem_d(label); - parseerror(parser, "expected loop body"); - return false; - } - } - - vec_push(parser->breaks, label); - vec_push(parser->continues, label); - - rv = parse_dowhile_go(parser, block, out); - if (label) - mem_d(label); - if (vec_last(parser->breaks) != label || vec_last(parser->continues) != label) { - parseerror(parser, "internal error: label stack corrupted"); - rv = false; - /* - * Test for NULL otherwise ast_delete dereferences null pointer - * and boom. - */ - if (*out) - ast_delete(*out); - *out = NULL; - } - else { - vec_pop(parser->breaks); - vec_pop(parser->continues); - } - return rv; -} - -static bool parse_dowhile_go(parser_t *parser, ast_block *block, ast_expression **out) -{ - ast_loop *aloop; - ast_expression *cond, *ontrue; - - bool ifnot = false; - - lex_ctx_t ctx = parser_ctx(parser); - - (void)block; /* not touching */ - - if (!parse_statement_or_block(parser, &ontrue)) - return false; - - /* expect the "while" */ - if (parser->tok != TOKEN_KEYWORD || - strcmp(parser_tokval(parser), "while")) - { - parseerror(parser, "expected 'while' and condition"); - ast_delete(ontrue); - return false; - } - - /* skip the 'while' and check for opening paren */ - if (!parser_next(parser) || parser->tok != '(') { - parseerror(parser, "expected 'while' condition in parenthesis"); - ast_delete(ontrue); - return false; - } - /* parse into the expression */ - if (!parser_next(parser)) { - parseerror(parser, "expected 'while' condition after opening paren"); - ast_delete(ontrue); - return false; - } - /* parse the condition */ - cond = parse_expression_leave(parser, false, true, false); - if (!cond) - return false; - /* closing paren */ - if (parser->tok != ')') { - parseerror(parser, "expected closing paren after 'while' condition"); - ast_delete(ontrue); - ast_unref(cond); - return false; - } - /* parse on */ - if (!parser_next(parser) || parser->tok != ';') { - parseerror(parser, "expected semicolon after condition"); - ast_delete(ontrue); - ast_unref(cond); - return false; - } - - if (!parser_next(parser)) { - parseerror(parser, "parse error"); - ast_delete(ontrue); - ast_unref(cond); - return false; - } - - cond = process_condition(parser, cond, &ifnot); - if (!cond) { - ast_delete(ontrue); - return false; - } - aloop = ast_loop_new(ctx, NULL, NULL, false, cond, ifnot, NULL, ontrue); - *out = (ast_expression*)aloop; - return true; -} - -static bool parse_for_go(parser_t *parser, ast_block *block, ast_expression **out); -static bool parse_for(parser_t *parser, ast_block *block, ast_expression **out) -{ - bool rv; - char *label = NULL; - - /* skip the 'for' and check for opening paren */ - if (!parser_next(parser)) { - if (OPTS_FLAG(LOOP_LABELS)) - parseerror(parser, "expected loop label or 'for' expressions in parenthesis"); - else - parseerror(parser, "expected 'for' expressions in parenthesis"); - return false; - } - - if (parser->tok == ':') { - if (!OPTS_FLAG(LOOP_LABELS)) - parseerror(parser, "labeled loops not activated, try using -floop-labels"); - if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { - parseerror(parser, "expected loop label"); - return false; - } - label = util_strdup(parser_tokval(parser)); - if (!parser_next(parser)) { - mem_d(label); - parseerror(parser, "expected 'for' expressions in parenthesis"); - return false; - } - } - - if (parser->tok != '(') { - parseerror(parser, "expected 'for' expressions in parenthesis"); - return false; - } - - vec_push(parser->breaks, label); - vec_push(parser->continues, label); - - rv = parse_for_go(parser, block, out); - if (label) - mem_d(label); - if (vec_last(parser->breaks) != label || vec_last(parser->continues) != label) { - parseerror(parser, "internal error: label stack corrupted"); - rv = false; - ast_delete(*out); - *out = NULL; - } - else { - vec_pop(parser->breaks); - vec_pop(parser->continues); - } - return rv; -} -static bool parse_for_go(parser_t *parser, ast_block *block, ast_expression **out) -{ - ast_loop *aloop; - ast_expression *initexpr, *cond, *increment, *ontrue; - ast_value *typevar; - - bool ifnot = false; - - lex_ctx_t ctx = parser_ctx(parser); - - parser_enterblock(parser); - - initexpr = NULL; - cond = NULL; - increment = NULL; - ontrue = NULL; - - /* parse into the expression */ - if (!parser_next(parser)) { - parseerror(parser, "expected 'for' initializer after opening paren"); - goto onerr; - } - - typevar = NULL; - if (parser->tok == TOKEN_IDENT) - typevar = parser_find_typedef(parser, parser_tokval(parser), 0); - - if (typevar || parser->tok == TOKEN_TYPENAME) { -#if 0 - if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_GMQCC) { - if (parsewarning(parser, WARN_EXTENSIONS, - "current standard does not allow variable declarations in for-loop initializers")) - goto onerr; - } -#endif - if (!parse_variable(parser, block, true, CV_VAR, typevar, false, false, 0, NULL)) - goto onerr; - } - else if (parser->tok != ';') - { - initexpr = parse_expression_leave(parser, false, false, false); - if (!initexpr) - goto onerr; - - /* move on to condition */ - if (parser->tok != ';') { - parseerror(parser, "expected semicolon after for-loop initializer"); - goto onerr; - } - - if (!parser_next(parser)) { - parseerror(parser, "expected for-loop condition"); - goto onerr; - } - } else { - if (!parser_next(parser)) { - parseerror(parser, "expected for-loop condition"); - goto onerr; - } - } - - /* parse the condition */ - if (parser->tok != ';') { - cond = parse_expression_leave(parser, false, true, false); - if (!cond) - goto onerr; - } - /* move on to incrementor */ - if (parser->tok != ';') { - parseerror(parser, "expected semicolon after for-loop initializer"); - goto onerr; - } - if (!parser_next(parser)) { - parseerror(parser, "expected for-loop condition"); - goto onerr; - } - - /* parse the incrementor */ - if (parser->tok != ')') { - lex_ctx_t condctx = parser_ctx(parser); - increment = parse_expression_leave(parser, false, false, false); - if (!increment) - goto onerr; - if (!ast_side_effects(increment)) { - if (compile_warning(condctx, WARN_EFFECTLESS_STATEMENT, "statement has no effect")) - goto onerr; - } - } - - /* closing paren */ - if (parser->tok != ')') { - parseerror(parser, "expected closing paren after 'for-loop' incrementor"); - goto onerr; - } - /* parse into the 'then' branch */ - if (!parser_next(parser)) { - parseerror(parser, "expected for-loop body"); - goto onerr; - } - if (!parse_statement_or_block(parser, &ontrue)) - goto onerr; - - if (cond) { - cond = process_condition(parser, cond, &ifnot); - if (!cond) - goto onerr; - } - aloop = ast_loop_new(ctx, initexpr, cond, ifnot, NULL, false, increment, ontrue); - *out = (ast_expression*)aloop; - - if (!parser_leaveblock(parser)) { - ast_delete(aloop); - return false; - } - return true; -onerr: - if (initexpr) ast_unref(initexpr); - if (cond) ast_unref(cond); - if (increment) ast_unref(increment); - (void)!parser_leaveblock(parser); - return false; -} - -static bool parse_return(parser_t *parser, ast_block *block, ast_expression **out) -{ - ast_expression *exp = NULL; - ast_expression *var = NULL; - ast_return *ret = NULL; - ast_value *retval = parser->function->return_value; - ast_value *expected = parser->function->vtype; - - lex_ctx_t ctx = parser_ctx(parser); - - (void)block; /* not touching */ - - if (!parser_next(parser)) { - parseerror(parser, "expected return expression"); - return false; - } - - /* return assignments */ - if (parser->tok == '=') { - if (!OPTS_FLAG(RETURN_ASSIGNMENTS)) { - parseerror(parser, "return assignments not activated, try using -freturn-assigments"); - return false; - } - - if (type_store_instr[expected->expression.next->vtype] == VINSTR_END) { - char ty1[1024]; - ast_type_to_string(expected->expression.next, ty1, sizeof(ty1)); - parseerror(parser, "invalid return type: `%s'", ty1); - return false; - } - - if (!parser_next(parser)) { - parseerror(parser, "expected return assignment expression"); - return false; - } - - if (!(exp = parse_expression_leave(parser, false, false, false))) - return false; - - /* prepare the return value */ - if (!retval) { - retval = ast_value_new(ctx, "#LOCAL_RETURN", TYPE_VOID); - ast_type_adopt(retval, expected->expression.next); - parser->function->return_value = retval; - } - - if (!ast_compare_type(exp, (ast_expression*)retval)) { - char ty1[1024], ty2[1024]; - ast_type_to_string(exp, ty1, sizeof(ty1)); - ast_type_to_string(&retval->expression, ty2, sizeof(ty2)); - parseerror(parser, "invalid type for return value: `%s', expected `%s'", ty1, ty2); - } - - /* store to 'return' local variable */ - var = (ast_expression*)ast_store_new( - ctx, - type_store_instr[expected->expression.next->vtype], - (ast_expression*)retval, exp); - - if (!var) { - ast_unref(exp); - return false; - } - - if (parser->tok != ';') - parseerror(parser, "missing semicolon after return assignment"); - else if (!parser_next(parser)) - parseerror(parser, "parse error after return assignment"); - - *out = var; - return true; - } - - if (parser->tok != ';') { - exp = parse_expression(parser, false, false); - if (!exp) - return false; - - if (exp->vtype != TYPE_NIL && - exp->vtype != ((ast_expression*)expected)->next->vtype) - { - parseerror(parser, "return with invalid expression"); - } - - ret = ast_return_new(ctx, exp); - if (!ret) { - ast_unref(exp); - return false; - } - } else { - if (!parser_next(parser)) - parseerror(parser, "parse error"); - - if (!retval && expected->expression.next->vtype != TYPE_VOID) - { - (void)!parsewarning(parser, WARN_MISSING_RETURN_VALUES, "return without value"); - } - ret = ast_return_new(ctx, (ast_expression*)retval); - } - *out = (ast_expression*)ret; - return true; -} - -static bool parse_break_continue(parser_t *parser, ast_block *block, ast_expression **out, bool is_continue) -{ - size_t i; - unsigned int levels = 0; - lex_ctx_t ctx = parser_ctx(parser); - const char **loops = (is_continue ? parser->continues : parser->breaks); - - (void)block; /* not touching */ - if (!parser_next(parser)) { - parseerror(parser, "expected semicolon or loop label"); - return false; - } - - if (!vec_size(loops)) { - if (is_continue) - parseerror(parser, "`continue` can only be used inside loops"); - else - parseerror(parser, "`break` can only be used inside loops or switches"); - } - - if (parser->tok == TOKEN_IDENT) { - if (!OPTS_FLAG(LOOP_LABELS)) - parseerror(parser, "labeled loops not activated, try using -floop-labels"); - i = vec_size(loops); - while (i--) { - if (loops[i] && !strcmp(loops[i], parser_tokval(parser))) - break; - if (!i) { - parseerror(parser, "no such loop to %s: `%s`", - (is_continue ? "continue" : "break out of"), - parser_tokval(parser)); - return false; - } - ++levels; - } - if (!parser_next(parser)) { - parseerror(parser, "expected semicolon"); - return false; - } - } - - if (parser->tok != ';') { - parseerror(parser, "expected semicolon"); - return false; - } - - if (!parser_next(parser)) - parseerror(parser, "parse error"); - - *out = (ast_expression*)ast_breakcont_new(ctx, is_continue, levels); - return true; -} - -/* returns true when it was a variable qualifier, false otherwise! - * on error, cvq is set to CV_WRONG - */ -typedef struct { - const char *name; - size_t flag; -} attribute_t; - -static bool parse_qualifiers(parser_t *parser, bool with_local, int *cvq, bool *noref, bool *is_static, uint32_t *_flags, char **message) -{ - bool had_const = false; - bool had_var = false; - bool had_noref = false; - bool had_attrib = false; - bool had_static = false; - uint32_t flags = 0; - - static attribute_t attributes[] = { - { "noreturn", AST_FLAG_NORETURN }, - { "inline", AST_FLAG_INLINE }, - { "eraseable", AST_FLAG_ERASEABLE }, - { "accumulate", AST_FLAG_ACCUMULATE }, - { "last", AST_FLAG_FINAL_DECL } - }; - - *cvq = CV_NONE; - - for (;;) { - size_t i; - if (parser->tok == TOKEN_ATTRIBUTE_OPEN) { - had_attrib = true; - /* parse an attribute */ - if (!parser_next(parser)) { - parseerror(parser, "expected attribute after `[[`"); - *cvq = CV_WRONG; - return false; - } - - for (i = 0; i < GMQCC_ARRAY_COUNT(attributes); i++) { - if (!strcmp(parser_tokval(parser), attributes[i].name)) { - flags |= attributes[i].flag; - if (!parser_next(parser) || parser->tok != TOKEN_ATTRIBUTE_CLOSE) { - parseerror(parser, "`%s` attribute has no parameters, expected `]]`", - attributes[i].name); - *cvq = CV_WRONG; - return false; - } - break; - } - } - - if (i != GMQCC_ARRAY_COUNT(attributes)) - goto leave; - - - if (!strcmp(parser_tokval(parser), "noref")) { - had_noref = true; - if (!parser_next(parser) || parser->tok != TOKEN_ATTRIBUTE_CLOSE) { - parseerror(parser, "`noref` attribute has no parameters, expected `]]`"); - *cvq = CV_WRONG; - return false; - } - } - else if (!strcmp(parser_tokval(parser), "alias") && !(flags & AST_FLAG_ALIAS)) { - flags |= AST_FLAG_ALIAS; - *message = NULL; - - if (!parser_next(parser)) { - parseerror(parser, "parse error in attribute"); - goto argerr; - } - - if (parser->tok == '(') { - if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) { - parseerror(parser, "`alias` attribute missing parameter"); - goto argerr; - } - - *message = util_strdup(parser_tokval(parser)); - - if (!parser_next(parser)) { - parseerror(parser, "parse error in attribute"); - goto argerr; - } - - if (parser->tok != ')') { - parseerror(parser, "`alias` attribute expected `)` after parameter"); - goto argerr; - } - - if (!parser_next(parser)) { - parseerror(parser, "parse error in attribute"); - goto argerr; - } - } - - if (parser->tok != TOKEN_ATTRIBUTE_CLOSE) { - parseerror(parser, "`alias` attribute expected `]]`"); - goto argerr; - } - } - else if (!strcmp(parser_tokval(parser), "deprecated") && !(flags & AST_FLAG_DEPRECATED)) { - flags |= AST_FLAG_DEPRECATED; - *message = NULL; - - if (!parser_next(parser)) { - parseerror(parser, "parse error in attribute"); - goto argerr; - } - - if (parser->tok == '(') { - if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) { - parseerror(parser, "`deprecated` attribute missing parameter"); - goto argerr; - } - - *message = util_strdup(parser_tokval(parser)); - - if (!parser_next(parser)) { - parseerror(parser, "parse error in attribute"); - goto argerr; - } - - if(parser->tok != ')') { - parseerror(parser, "`deprecated` attribute expected `)` after parameter"); - goto argerr; - } - - if (!parser_next(parser)) { - parseerror(parser, "parse error in attribute"); - goto argerr; - } - } - /* no message */ - if (parser->tok != TOKEN_ATTRIBUTE_CLOSE) { - parseerror(parser, "`deprecated` attribute expected `]]`"); - - argerr: /* ugly */ - if (*message) mem_d(*message); - *message = NULL; - *cvq = CV_WRONG; - return false; - } - } - else if (!strcmp(parser_tokval(parser), "coverage") && !(flags & AST_FLAG_COVERAGE)) { - flags |= AST_FLAG_COVERAGE; - if (!parser_next(parser)) { - error_in_coverage: - parseerror(parser, "parse error in coverage attribute"); - *cvq = CV_WRONG; - return false; - } - if (parser->tok == '(') { - if (!parser_next(parser)) { - bad_coverage_arg: - parseerror(parser, "invalid parameter for coverage() attribute\n" - "valid are: block"); - *cvq = CV_WRONG; - return false; - } - if (parser->tok != ')') { - do { - if (parser->tok != TOKEN_IDENT) - goto bad_coverage_arg; - if (!strcmp(parser_tokval(parser), "block")) - flags |= AST_FLAG_BLOCK_COVERAGE; - else if (!strcmp(parser_tokval(parser), "none")) - flags &= ~(AST_FLAG_COVERAGE_MASK); - else - goto bad_coverage_arg; - if (!parser_next(parser)) - goto error_in_coverage; - if (parser->tok == ',') { - if (!parser_next(parser)) - goto error_in_coverage; - } - } while (parser->tok != ')'); - } - if (parser->tok != ')' || !parser_next(parser)) - goto error_in_coverage; - } else { - /* without parameter [[coverage]] equals [[coverage(block)]] */ - flags |= AST_FLAG_BLOCK_COVERAGE; - } - } - else - { - /* Skip tokens until we hit a ]] */ - (void)!parsewarning(parser, WARN_UNKNOWN_ATTRIBUTE, "unknown attribute starting with `%s`", parser_tokval(parser)); - while (parser->tok != TOKEN_ATTRIBUTE_CLOSE) { - if (!parser_next(parser)) { - parseerror(parser, "error inside attribute"); - *cvq = CV_WRONG; - return false; - } - } - } - } - else if (with_local && !strcmp(parser_tokval(parser), "static")) - had_static = true; - else if (!strcmp(parser_tokval(parser), "const")) - had_const = true; - else if (!strcmp(parser_tokval(parser), "var")) - had_var = true; - else if (with_local && !strcmp(parser_tokval(parser), "local")) - had_var = true; - else if (!strcmp(parser_tokval(parser), "noref")) - had_noref = true; - else if (!had_const && !had_var && !had_noref && !had_attrib && !had_static && !flags) { - return false; - } - else - break; - - leave: - if (!parser_next(parser)) - goto onerr; - } - if (had_const) - *cvq = CV_CONST; - else if (had_var) - *cvq = CV_VAR; - else - *cvq = CV_NONE; - *noref = had_noref; - *is_static = had_static; - *_flags = flags; - return true; -onerr: - parseerror(parser, "parse error after variable qualifier"); - *cvq = CV_WRONG; - return true; -} - -static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression **out); -static bool parse_switch(parser_t *parser, ast_block *block, ast_expression **out) -{ - bool rv; - char *label = NULL; - - /* skip the 'while' and get the body */ - if (!parser_next(parser)) { - if (OPTS_FLAG(LOOP_LABELS)) - parseerror(parser, "expected loop label or 'switch' operand in parenthesis"); - else - parseerror(parser, "expected 'switch' operand in parenthesis"); - return false; - } - - if (parser->tok == ':') { - if (!OPTS_FLAG(LOOP_LABELS)) - parseerror(parser, "labeled loops not activated, try using -floop-labels"); - if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { - parseerror(parser, "expected loop label"); - return false; - } - label = util_strdup(parser_tokval(parser)); - if (!parser_next(parser)) { - mem_d(label); - parseerror(parser, "expected 'switch' operand in parenthesis"); - return false; - } - } - - if (parser->tok != '(') { - parseerror(parser, "expected 'switch' operand in parenthesis"); - return false; - } - - vec_push(parser->breaks, label); - - rv = parse_switch_go(parser, block, out); - if (label) - mem_d(label); - if (vec_last(parser->breaks) != label) { - parseerror(parser, "internal error: label stack corrupted"); - rv = false; - ast_delete(*out); - *out = NULL; - } - else { - vec_pop(parser->breaks); - } - return rv; -} - -static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression **out) -{ - ast_expression *operand; - ast_value *opval; - ast_value *typevar; - ast_switch *switchnode; - ast_switch_case swcase; - - int cvq; - bool noref, is_static; - uint32_t qflags = 0; - - lex_ctx_t ctx = parser_ctx(parser); - - (void)block; /* not touching */ - (void)opval; - - /* parse into the expression */ - if (!parser_next(parser)) { - parseerror(parser, "expected switch operand"); - return false; - } - /* parse the operand */ - operand = parse_expression_leave(parser, false, false, false); - if (!operand) - return false; - - switchnode = ast_switch_new(ctx, operand); - - /* closing paren */ - if (parser->tok != ')') { - ast_delete(switchnode); - parseerror(parser, "expected closing paren after 'switch' operand"); - return false; - } - - /* parse over the opening paren */ - if (!parser_next(parser) || parser->tok != '{') { - ast_delete(switchnode); - parseerror(parser, "expected list of cases"); - return false; - } - - if (!parser_next(parser)) { - ast_delete(switchnode); - parseerror(parser, "expected 'case' or 'default'"); - return false; - } - - /* new block; allow some variables to be declared here */ - parser_enterblock(parser); - while (true) { - typevar = NULL; - if (parser->tok == TOKEN_IDENT) - typevar = parser_find_typedef(parser, parser_tokval(parser), 0); - if (typevar || parser->tok == TOKEN_TYPENAME) { - if (!parse_variable(parser, block, true, CV_NONE, typevar, false, false, 0, NULL)) { - ast_delete(switchnode); - return false; - } - continue; - } - if (parse_qualifiers(parser, true, &cvq, &noref, &is_static, &qflags, NULL)) - { - if (cvq == CV_WRONG) { - ast_delete(switchnode); - return false; - } - if (!parse_variable(parser, block, true, cvq, NULL, noref, is_static, qflags, NULL)) { - ast_delete(switchnode); - return false; - } - continue; - } - break; - } - - /* case list! */ - while (parser->tok != '}') { - ast_block *caseblock; - - if (!strcmp(parser_tokval(parser), "case")) { - if (!parser_next(parser)) { - ast_delete(switchnode); - parseerror(parser, "expected expression for case"); - return false; - } - swcase.value = parse_expression_leave(parser, false, false, false); - if (!swcase.value) { - ast_delete(switchnode); - parseerror(parser, "expected expression for case"); - return false; - } - if (!OPTS_FLAG(RELAXED_SWITCH)) { - if (!ast_istype(swcase.value, ast_value)) { /* || ((ast_value*)swcase.value)->cvq != CV_CONST) { */ - parseerror(parser, "case on non-constant values need to be explicitly enabled via -frelaxed-switch"); - ast_unref(operand); - return false; - } - } - } - else if (!strcmp(parser_tokval(parser), "default")) { - swcase.value = NULL; - if (!parser_next(parser)) { - ast_delete(switchnode); - parseerror(parser, "expected colon"); - return false; - } - } - else { - ast_delete(switchnode); - parseerror(parser, "expected 'case' or 'default'"); - return false; - } - - /* Now the colon and body */ - if (parser->tok != ':') { - if (swcase.value) ast_unref(swcase.value); - ast_delete(switchnode); - parseerror(parser, "expected colon"); - return false; - } - - if (!parser_next(parser)) { - if (swcase.value) ast_unref(swcase.value); - ast_delete(switchnode); - parseerror(parser, "expected statements or case"); - return false; - } - caseblock = ast_block_new(parser_ctx(parser)); - if (!caseblock) { - if (swcase.value) ast_unref(swcase.value); - ast_delete(switchnode); - return false; - } - swcase.code = (ast_expression*)caseblock; - vec_push(switchnode->cases, swcase); - while (true) { - ast_expression *expr; - if (parser->tok == '}') - break; - if (parser->tok == TOKEN_KEYWORD) { - if (!strcmp(parser_tokval(parser), "case") || - !strcmp(parser_tokval(parser), "default")) - { - break; - } - } - if (!parse_statement(parser, caseblock, &expr, true)) { - ast_delete(switchnode); - return false; - } - if (!expr) - continue; - if (!ast_block_add_expr(caseblock, expr)) { - ast_delete(switchnode); - return false; - } - } - } - - parser_leaveblock(parser); - - /* closing paren */ - if (parser->tok != '}') { - ast_delete(switchnode); - parseerror(parser, "expected closing paren of case list"); - return false; - } - if (!parser_next(parser)) { - ast_delete(switchnode); - parseerror(parser, "parse error after switch"); - return false; - } - *out = (ast_expression*)switchnode; - return true; -} - -/* parse computed goto sides */ -static ast_expression *parse_goto_computed(parser_t *parser, ast_expression **side) { - ast_expression *on_true; - ast_expression *on_false; - ast_expression *cond; - - if (!*side) - return NULL; - - if (ast_istype(*side, ast_ternary)) { - ast_ternary *tern = (ast_ternary*)*side; - on_true = parse_goto_computed(parser, &tern->on_true); - on_false = parse_goto_computed(parser, &tern->on_false); - - if (!on_true || !on_false) { - parseerror(parser, "expected label or expression in ternary"); - if (on_true) ast_unref(on_true); - if (on_false) ast_unref(on_false); - return NULL; - } - - cond = tern->cond; - tern->cond = NULL; - ast_delete(tern); - *side = NULL; - return (ast_expression*)ast_ifthen_new(parser_ctx(parser), cond, on_true, on_false); - } else if (ast_istype(*side, ast_label)) { - ast_goto *gt = ast_goto_new(parser_ctx(parser), ((ast_label*)*side)->name); - ast_goto_set_label(gt, ((ast_label*)*side)); - *side = NULL; - return (ast_expression*)gt; - } - return NULL; -} - -static bool parse_goto(parser_t *parser, ast_expression **out) -{ - ast_goto *gt = NULL; - ast_expression *lbl; - - if (!parser_next(parser)) - return false; - - if (parser->tok != TOKEN_IDENT) { - ast_expression *expression; - - /* could be an expression i.e computed goto :-) */ - if (parser->tok != '(') { - parseerror(parser, "expected label name after `goto`"); - return false; - } - - /* failed to parse expression for goto */ - if (!(expression = parse_expression(parser, false, true)) || - !(*out = parse_goto_computed(parser, &expression))) { - parseerror(parser, "invalid goto expression"); - if(expression) - ast_unref(expression); - return false; - } - - return true; - } - - /* not computed goto */ - gt = ast_goto_new(parser_ctx(parser), parser_tokval(parser)); - lbl = parser_find_label(parser, gt->name); - if (lbl) { - if (!ast_istype(lbl, ast_label)) { - parseerror(parser, "internal error: label is not an ast_label"); - ast_delete(gt); - return false; - } - ast_goto_set_label(gt, (ast_label*)lbl); - } - else - vec_push(parser->gotos, gt); - - if (!parser_next(parser) || parser->tok != ';') { - parseerror(parser, "semicolon expected after goto label"); - return false; - } - if (!parser_next(parser)) { - parseerror(parser, "parse error after goto"); - return false; - } - - *out = (ast_expression*)gt; - return true; -} - -static bool parse_skipwhite(parser_t *parser) -{ - do { - if (!parser_next(parser)) - return false; - } while (parser->tok == TOKEN_WHITE && parser->tok < TOKEN_ERROR); - return parser->tok < TOKEN_ERROR; -} - -static bool parse_eol(parser_t *parser) -{ - if (!parse_skipwhite(parser)) - return false; - return parser->tok == TOKEN_EOL; -} - -static bool parse_pragma_do(parser_t *parser) -{ - if (!parser_next(parser) || - parser->tok != TOKEN_IDENT || - strcmp(parser_tokval(parser), "pragma")) - { - parseerror(parser, "expected `pragma` keyword after `#`, got `%s`", parser_tokval(parser)); - return false; - } - if (!parse_skipwhite(parser) || parser->tok != TOKEN_IDENT) { - parseerror(parser, "expected pragma, got `%s`", parser_tokval(parser)); - return false; - } - - if (!strcmp(parser_tokval(parser), "noref")) { - if (!parse_skipwhite(parser) || parser->tok != TOKEN_INTCONST) { - parseerror(parser, "`noref` pragma requires an argument: 0 or 1"); - return false; - } - parser->noref = !!parser_token(parser)->constval.i; - if (!parse_eol(parser)) { - parseerror(parser, "parse error after `noref` pragma"); - return false; - } - } - else - { - (void)!parsewarning(parser, WARN_UNKNOWN_PRAGMAS, "ignoring #pragma %s", parser_tokval(parser)); - - /* skip to eol */ - while (!parse_eol(parser)) { - parser_next(parser); - } - - return true; - } - - return true; -} - -static bool parse_pragma(parser_t *parser) -{ - bool rv; - parser->lex->flags.preprocessing = true; - parser->lex->flags.mergelines = true; - rv = parse_pragma_do(parser); - if (parser->tok != TOKEN_EOL) { - parseerror(parser, "junk after pragma"); - rv = false; - } - parser->lex->flags.preprocessing = false; - parser->lex->flags.mergelines = false; - if (!parser_next(parser)) { - parseerror(parser, "parse error after pragma"); - rv = false; - } - return rv; -} - -static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases) -{ - bool noref, is_static; - int cvq = CV_NONE; - uint32_t qflags = 0; - ast_value *typevar = NULL; - char *vstring = NULL; - - *out = NULL; - - if (parser->tok == TOKEN_IDENT) - typevar = parser_find_typedef(parser, parser_tokval(parser), 0); - - if (typevar || parser->tok == TOKEN_TYPENAME || parser->tok == '.' || parser->tok == TOKEN_DOTS) - { - /* local variable */ - if (!block) { - parseerror(parser, "cannot declare a variable from here"); - return false; - } - if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { - if (parsewarning(parser, WARN_EXTENSIONS, "missing 'local' keyword when declaring a local variable")) - return false; - } - if (!parse_variable(parser, block, false, CV_NONE, typevar, false, false, 0, NULL)) - return false; - return true; - } - else if (parse_qualifiers(parser, !!block, &cvq, &noref, &is_static, &qflags, &vstring)) - { - if (cvq == CV_WRONG) - return false; - return parse_variable(parser, block, false, cvq, NULL, noref, is_static, qflags, vstring); - } - else if (parser->tok == TOKEN_KEYWORD) - { - if (!strcmp(parser_tokval(parser), "__builtin_debug_printtype")) - { - char ty[1024]; - ast_value *tdef; - - if (!parser_next(parser)) { - parseerror(parser, "parse error after __builtin_debug_printtype"); - return false; - } - - if (parser->tok == TOKEN_IDENT && (tdef = parser_find_typedef(parser, parser_tokval(parser), 0))) - { - ast_type_to_string((ast_expression*)tdef, ty, sizeof(ty)); - con_out("__builtin_debug_printtype: `%s`=`%s`\n", tdef->name, ty); - if (!parser_next(parser)) { - parseerror(parser, "parse error after __builtin_debug_printtype typename argument"); - return false; - } - } - else - { - if (!parse_statement(parser, block, out, allow_cases)) - return false; - if (!*out) - con_out("__builtin_debug_printtype: got no output node\n"); - else - { - ast_type_to_string(*out, ty, sizeof(ty)); - con_out("__builtin_debug_printtype: `%s`\n", ty); - } - } - return true; - } - else if (!strcmp(parser_tokval(parser), "return")) - { - return parse_return(parser, block, out); - } - else if (!strcmp(parser_tokval(parser), "if")) - { - return parse_if(parser, block, out); - } - else if (!strcmp(parser_tokval(parser), "while")) - { - return parse_while(parser, block, out); - } - else if (!strcmp(parser_tokval(parser), "do")) - { - return parse_dowhile(parser, block, out); - } - else if (!strcmp(parser_tokval(parser), "for")) - { - if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { - if (parsewarning(parser, WARN_EXTENSIONS, "for loops are not recognized in the original Quake C standard, to enable try an alternate standard --std=?")) - return false; - } - return parse_for(parser, block, out); - } - else if (!strcmp(parser_tokval(parser), "break")) - { - return parse_break_continue(parser, block, out, false); - } - else if (!strcmp(parser_tokval(parser), "continue")) - { - return parse_break_continue(parser, block, out, true); - } - else if (!strcmp(parser_tokval(parser), "switch")) - { - return parse_switch(parser, block, out); - } - else if (!strcmp(parser_tokval(parser), "case") || - !strcmp(parser_tokval(parser), "default")) - { - if (!allow_cases) { - parseerror(parser, "unexpected 'case' label"); - return false; - } - return true; - } - else if (!strcmp(parser_tokval(parser), "goto")) - { - return parse_goto(parser, out); - } - else if (!strcmp(parser_tokval(parser), "typedef")) - { - if (!parser_next(parser)) { - parseerror(parser, "expected type definition after 'typedef'"); - return false; - } - return parse_typedef(parser); - } - parseerror(parser, "Unexpected keyword: `%s'", parser_tokval(parser)); - return false; - } - else if (parser->tok == '{') - { - ast_block *inner; - inner = parse_block(parser); - if (!inner) - return false; - *out = (ast_expression*)inner; - return true; - } - else if (parser->tok == ':') - { - size_t i; - ast_label *label; - if (!parser_next(parser)) { - parseerror(parser, "expected label name"); - return false; - } - if (parser->tok != TOKEN_IDENT) { - parseerror(parser, "label must be an identifier"); - return false; - } - label = (ast_label*)parser_find_label(parser, parser_tokval(parser)); - if (label) { - if (!label->undefined) { - parseerror(parser, "label `%s` already defined", label->name); - return false; - } - label->undefined = false; - } - else { - label = ast_label_new(parser_ctx(parser), parser_tokval(parser), false); - vec_push(parser->labels, label); - } - *out = (ast_expression*)label; - if (!parser_next(parser)) { - parseerror(parser, "parse error after label"); - return false; - } - for (i = 0; i < vec_size(parser->gotos); ++i) { - if (!strcmp(parser->gotos[i]->name, label->name)) { - ast_goto_set_label(parser->gotos[i], label); - vec_remove(parser->gotos, i, 1); - --i; - } - } - return true; - } - else if (parser->tok == ';') - { - if (!parser_next(parser)) { - parseerror(parser, "parse error after empty statement"); - return false; - } - return true; - } - else - { - lex_ctx_t ctx = parser_ctx(parser); - ast_expression *exp = parse_expression(parser, false, false); - if (!exp) - return false; - *out = exp; - if (!ast_side_effects(exp)) { - if (compile_warning(ctx, WARN_EFFECTLESS_STATEMENT, "statement has no effect")) - return false; - } - return true; - } -} - -static bool parse_enum(parser_t *parser) -{ - bool flag = false; - bool reverse = false; - qcfloat_t num = 0; - ast_value **values = NULL; - ast_value *var = NULL; - ast_value *asvalue; - - ast_expression *old; - - if (!parser_next(parser) || (parser->tok != '{' && parser->tok != ':')) { - parseerror(parser, "expected `{` or `:` after `enum` keyword"); - return false; - } - - /* enumeration attributes (can add more later) */ - if (parser->tok == ':') { - if (!parser_next(parser) || parser->tok != TOKEN_IDENT){ - parseerror(parser, "expected `flag` or `reverse` for enumeration attribute"); - return false; - } - - /* attributes? */ - if (!strcmp(parser_tokval(parser), "flag")) { - num = 1; - flag = true; - } - else if (!strcmp(parser_tokval(parser), "reverse")) { - reverse = true; - } - else { - parseerror(parser, "invalid attribute `%s` for enumeration", parser_tokval(parser)); - return false; - } - - if (!parser_next(parser) || parser->tok != '{') { - parseerror(parser, "expected `{` after enum attribute "); - return false; - } - } - - while (true) { - if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { - if (parser->tok == '}') { - /* allow an empty enum */ - break; - } - parseerror(parser, "expected identifier or `}`"); - goto onerror; - } - - old = parser_find_field(parser, parser_tokval(parser)); - if (!old) - old = parser_find_global(parser, parser_tokval(parser)); - if (old) { - parseerror(parser, "value `%s` has already been declared here: %s:%i", - parser_tokval(parser), ast_ctx(old).file, ast_ctx(old).line); - goto onerror; - } - - var = ast_value_new(parser_ctx(parser), parser_tokval(parser), TYPE_FLOAT); - vec_push(values, var); - var->cvq = CV_CONST; - var->hasvalue = true; - - /* for flagged enumerations increment in POTs of TWO */ - var->constval.vfloat = (flag) ? (num *= 2) : (num ++); - parser_addglobal(parser, var->name, (ast_expression*)var); - - if (!parser_next(parser)) { - parseerror(parser, "expected `=`, `}` or comma after identifier"); - goto onerror; - } - - if (parser->tok == ',') - continue; - if (parser->tok == '}') - break; - if (parser->tok != '=') { - parseerror(parser, "expected `=`, `}` or comma after identifier"); - goto onerror; - } - - if (!parser_next(parser)) { - parseerror(parser, "expected expression after `=`"); - goto onerror; - } - - /* We got a value! */ - old = parse_expression_leave(parser, true, false, false); - asvalue = (ast_value*)old; - if (!ast_istype(old, ast_value) || asvalue->cvq != CV_CONST || !asvalue->hasvalue) { - compile_error(ast_ctx(var), "constant value or expression expected"); - goto onerror; - } - num = (var->constval.vfloat = asvalue->constval.vfloat) + 1; - - if (parser->tok == '}') - break; - if (parser->tok != ',') { - parseerror(parser, "expected `}` or comma after expression"); - goto onerror; - } - } - - /* patch them all (for reversed attribute) */ - if (reverse) { - size_t i; - for (i = 0; i < vec_size(values); i++) - values[i]->constval.vfloat = vec_size(values) - i - 1; - } - - if (parser->tok != '}') { - parseerror(parser, "internal error: breaking without `}`"); - goto onerror; - } - - if (!parser_next(parser) || parser->tok != ';') { - parseerror(parser, "expected semicolon after enumeration"); - goto onerror; - } - - if (!parser_next(parser)) { - parseerror(parser, "parse error after enumeration"); - goto onerror; - } - - vec_free(values); - return true; - -onerror: - vec_free(values); - return false; -} - -static bool parse_block_into(parser_t *parser, ast_block *block) -{ - bool retval = true; - - parser_enterblock(parser); - - if (!parser_next(parser)) { /* skip the '{' */ - parseerror(parser, "expected function body"); - goto cleanup; - } - - while (parser->tok != TOKEN_EOF && parser->tok < TOKEN_ERROR) - { - ast_expression *expr = NULL; - if (parser->tok == '}') - break; - - if (!parse_statement(parser, block, &expr, false)) { - /* parseerror(parser, "parse error"); */ - block = NULL; - goto cleanup; - } - if (!expr) - continue; - if (!ast_block_add_expr(block, expr)) { - ast_delete(block); - block = NULL; - goto cleanup; - } - } - - if (parser->tok != '}') { - block = NULL; - } else { - (void)parser_next(parser); - } - -cleanup: - if (!parser_leaveblock(parser)) - retval = false; - return retval && !!block; -} - -static ast_block* parse_block(parser_t *parser) -{ - ast_block *block; - block = ast_block_new(parser_ctx(parser)); - if (!block) - return NULL; - if (!parse_block_into(parser, block)) { - ast_block_delete(block); - return NULL; - } - return block; -} - -static bool parse_statement_or_block(parser_t *parser, ast_expression **out) -{ - if (parser->tok == '{') { - *out = (ast_expression*)parse_block(parser); - return !!*out; - } - return parse_statement(parser, NULL, out, false); -} - -static bool create_vector_members(ast_value *var, ast_member **me) -{ - size_t i; - size_t len = strlen(var->name); - - for (i = 0; i < 3; ++i) { - char *name = (char*)mem_a(len+3); - memcpy(name, var->name, len); - name[len+0] = '_'; - name[len+1] = 'x'+i; - name[len+2] = 0; - me[i] = ast_member_new(ast_ctx(var), (ast_expression*)var, i, name); - mem_d(name); - if (!me[i]) - break; - } - if (i == 3) - return true; - - /* unroll */ - do { ast_member_delete(me[--i]); } while(i); - return false; -} - -static bool parse_function_body(parser_t *parser, ast_value *var) -{ - ast_block *block = NULL; - ast_function *func; - ast_function *old; - size_t parami; - - ast_expression *framenum = NULL; - ast_expression *nextthink = NULL; - /* None of the following have to be deleted */ - ast_expression *fld_think = NULL, *fld_nextthink = NULL, *fld_frame = NULL; - ast_expression *gbl_time = NULL, *gbl_self = NULL; - bool has_frame_think; - - bool retval = true; - - has_frame_think = false; - old = parser->function; - - if (var->expression.flags & AST_FLAG_ALIAS) { - parseerror(parser, "function aliases cannot have bodies"); - return false; - } - - if (vec_size(parser->gotos) || vec_size(parser->labels)) { - parseerror(parser, "gotos/labels leaking"); - return false; - } - - if (!OPTS_FLAG(VARIADIC_ARGS) && var->expression.flags & AST_FLAG_VARIADIC) { - if (parsewarning(parser, WARN_VARIADIC_FUNCTION, - "variadic function with implementation will not be able to access additional parameters (try -fvariadic-args)")) - { - return false; - } - } - - if (parser->tok == '[') { - /* got a frame definition: [ framenum, nextthink ] - * this translates to: - * self.frame = framenum; - * self.nextthink = time + 0.1; - * self.think = nextthink; - */ - nextthink = NULL; - - fld_think = parser_find_field(parser, "think"); - fld_nextthink = parser_find_field(parser, "nextthink"); - fld_frame = parser_find_field(parser, "frame"); - if (!fld_think || !fld_nextthink || !fld_frame) { - parseerror(parser, "cannot use [frame,think] notation without the required fields"); - parseerror(parser, "please declare the following entityfields: `frame`, `think`, `nextthink`"); - return false; - } - gbl_time = parser_find_global(parser, "time"); - gbl_self = parser_find_global(parser, "self"); - if (!gbl_time || !gbl_self) { - parseerror(parser, "cannot use [frame,think] notation without the required globals"); - parseerror(parser, "please declare the following globals: `time`, `self`"); - return false; - } - - if (!parser_next(parser)) - return false; - - framenum = parse_expression_leave(parser, true, false, false); - if (!framenum) { - parseerror(parser, "expected a framenumber constant in[frame,think] notation"); - return false; - } - if (!ast_istype(framenum, ast_value) || !( (ast_value*)framenum )->hasvalue) { - ast_unref(framenum); - parseerror(parser, "framenumber in [frame,think] notation must be a constant"); - return false; - } - - if (parser->tok != ',') { - ast_unref(framenum); - parseerror(parser, "expected comma after frame number in [frame,think] notation"); - parseerror(parser, "Got a %i\n", parser->tok); - return false; - } - - if (!parser_next(parser)) { - ast_unref(framenum); - return false; - } - - if (parser->tok == TOKEN_IDENT && !parser_find_var(parser, parser_tokval(parser))) - { - /* qc allows the use of not-yet-declared functions here - * - this automatically creates a prototype */ - ast_value *thinkfunc; - ast_expression *functype = fld_think->next; - - thinkfunc = ast_value_new(parser_ctx(parser), parser_tokval(parser), functype->vtype); - if (!thinkfunc) { /* || !ast_type_adopt(thinkfunc, functype)*/ - ast_unref(framenum); - parseerror(parser, "failed to create implicit prototype for `%s`", parser_tokval(parser)); - return false; - } - ast_type_adopt(thinkfunc, functype); - - if (!parser_next(parser)) { - ast_unref(framenum); - ast_delete(thinkfunc); - return false; - } - - parser_addglobal(parser, thinkfunc->name, (ast_expression*)thinkfunc); - - nextthink = (ast_expression*)thinkfunc; - - } else { - nextthink = parse_expression_leave(parser, true, false, false); - if (!nextthink) { - ast_unref(framenum); - parseerror(parser, "expected a think-function in [frame,think] notation"); - return false; - } - } - - if (!ast_istype(nextthink, ast_value)) { - parseerror(parser, "think-function in [frame,think] notation must be a constant"); - retval = false; - } - - if (retval && parser->tok != ']') { - parseerror(parser, "expected closing `]` for [frame,think] notation"); - retval = false; - } - - if (retval && !parser_next(parser)) { - retval = false; - } - - if (retval && parser->tok != '{') { - parseerror(parser, "a function body has to be declared after a [frame,think] declaration"); - retval = false; - } - - if (!retval) { - ast_unref(nextthink); - ast_unref(framenum); - return false; - } - - has_frame_think = true; - } - - block = ast_block_new(parser_ctx(parser)); - if (!block) { - parseerror(parser, "failed to allocate block"); - if (has_frame_think) { - ast_unref(nextthink); - ast_unref(framenum); - } - return false; - } - - if (has_frame_think) { - if (!OPTS_FLAG(EMULATE_STATE)) { - ast_state *state_op = ast_state_new(parser_ctx(parser), framenum, nextthink); - if (!ast_block_add_expr(block, (ast_expression*)state_op)) { - parseerror(parser, "failed to generate state op for [frame,think]"); - ast_unref(nextthink); - ast_unref(framenum); - ast_delete(block); - return false; - } - } else { - /* emulate OP_STATE in code: */ - lex_ctx_t ctx; - ast_expression *self_frame; - ast_expression *self_nextthink; - ast_expression *self_think; - ast_expression *time_plus_1; - ast_store *store_frame; - ast_store *store_nextthink; - ast_store *store_think; - - float frame_delta = 1.0f / (float)OPTS_OPTION_U32(OPTION_STATE_FPS); - - ctx = parser_ctx(parser); - self_frame = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_frame); - self_nextthink = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_nextthink); - self_think = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_think); - - time_plus_1 = (ast_expression*)ast_binary_new(ctx, INSTR_ADD_F, - gbl_time, (ast_expression*)fold_constgen_float(parser->fold, frame_delta, false)); - - if (!self_frame || !self_nextthink || !self_think || !time_plus_1) { - if (self_frame) ast_delete(self_frame); - if (self_nextthink) ast_delete(self_nextthink); - if (self_think) ast_delete(self_think); - if (time_plus_1) ast_delete(time_plus_1); - retval = false; - } - - if (retval) - { - store_frame = ast_store_new(ctx, INSTR_STOREP_F, self_frame, framenum); - store_nextthink = ast_store_new(ctx, INSTR_STOREP_F, self_nextthink, time_plus_1); - store_think = ast_store_new(ctx, INSTR_STOREP_FNC, self_think, nextthink); - - if (!store_frame) { - ast_delete(self_frame); - retval = false; - } - if (!store_nextthink) { - ast_delete(self_nextthink); - retval = false; - } - if (!store_think) { - ast_delete(self_think); - retval = false; - } - if (!retval) { - if (store_frame) ast_delete(store_frame); - if (store_nextthink) ast_delete(store_nextthink); - if (store_think) ast_delete(store_think); - retval = false; - } - if (!ast_block_add_expr(block, (ast_expression*)store_frame) || - !ast_block_add_expr(block, (ast_expression*)store_nextthink) || - !ast_block_add_expr(block, (ast_expression*)store_think)) - { - retval = false; - } - } - - if (!retval) { - parseerror(parser, "failed to generate code for [frame,think]"); - ast_unref(nextthink); - ast_unref(framenum); - ast_delete(block); - return false; - } - } - } - - if (var->hasvalue) { - if (!(var->expression.flags & AST_FLAG_ACCUMULATE)) { - parseerror(parser, "function `%s` declared with multiple bodies", var->name); - ast_block_delete(block); - goto enderr; - } - func = var->constval.vfunc; - - if (!func) { - parseerror(parser, "internal error: NULL function: `%s`", var->name); - ast_block_delete(block); - goto enderr; - } - } else { - func = ast_function_new(ast_ctx(var), var->name, var); - - if (!func) { - parseerror(parser, "failed to allocate function for `%s`", var->name); - ast_block_delete(block); - goto enderr; - } - vec_push(parser->functions, func); - } - - parser_enterblock(parser); - - for (parami = 0; parami < vec_size(var->expression.params); ++parami) { - size_t e; - ast_value *param = var->expression.params[parami]; - ast_member *me[3]; - - if (param->expression.vtype != TYPE_VECTOR && - (param->expression.vtype != TYPE_FIELD || - param->expression.next->vtype != TYPE_VECTOR)) - { - continue; - } - - if (!create_vector_members(param, me)) { - ast_block_delete(block); - goto enderrfn; - } - - for (e = 0; e < 3; ++e) { - parser_addlocal(parser, me[e]->name, (ast_expression*)me[e]); - ast_block_collect(block, (ast_expression*)me[e]); - } - } - - if (var->argcounter && !func->argc) { - ast_value *argc = ast_value_new(ast_ctx(var), var->argcounter, TYPE_FLOAT); - parser_addlocal(parser, argc->name, (ast_expression*)argc); - func->argc = argc; - } - - if (OPTS_FLAG(VARIADIC_ARGS) && var->expression.flags & AST_FLAG_VARIADIC && !func->varargs) { - char name[1024]; - ast_value *varargs = ast_value_new(ast_ctx(var), "reserved:va_args", TYPE_ARRAY); - varargs->expression.flags |= AST_FLAG_IS_VARARG; - varargs->expression.next = (ast_expression*)ast_value_new(ast_ctx(var), NULL, TYPE_VECTOR); - varargs->expression.count = 0; - util_snprintf(name, sizeof(name), "%s##va##SET", var->name); - if (!parser_create_array_setter_proto(parser, varargs, name)) { - ast_delete(varargs); - ast_block_delete(block); - goto enderrfn; - } - util_snprintf(name, sizeof(name), "%s##va##GET", var->name); - if (!parser_create_array_getter_proto(parser, varargs, varargs->expression.next, name)) { - ast_delete(varargs); - ast_block_delete(block); - goto enderrfn; - } - func->varargs = varargs; - func->fixedparams = (ast_value*)fold_constgen_float(parser->fold, vec_size(var->expression.params), false); - } - - parser->function = func; - if (!parse_block_into(parser, block)) { - ast_block_delete(block); - goto enderrfn; - } - - vec_push(func->blocks, block); - - parser->function = old; - if (!parser_leaveblock(parser)) - retval = false; - if (vec_size(parser->variables) != PARSER_HT_LOCALS) { - parseerror(parser, "internal error: local scopes left"); - retval = false; - } - - if (parser->tok == ';') - return parser_next(parser); - else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) - parseerror(parser, "missing semicolon after function body (mandatory with -std=qcc)"); - return retval; - -enderrfn: - (void)!parser_leaveblock(parser); - vec_pop(parser->functions); - ast_function_delete(func); - var->constval.vfunc = NULL; - -enderr: - parser->function = old; - return false; -} - -static ast_expression *array_accessor_split( - parser_t *parser, - ast_value *array, - ast_value *index, - size_t middle, - ast_expression *left, - ast_expression *right - ) -{ - ast_ifthen *ifthen; - ast_binary *cmp; - - lex_ctx_t ctx = ast_ctx(array); - - if (!left || !right) { - if (left) ast_delete(left); - if (right) ast_delete(right); - return NULL; - } - - cmp = ast_binary_new(ctx, INSTR_LT, - (ast_expression*)index, - (ast_expression*)fold_constgen_float(parser->fold, middle, false)); - if (!cmp) { - ast_delete(left); - ast_delete(right); - parseerror(parser, "internal error: failed to create comparison for array setter"); - return NULL; - } - - ifthen = ast_ifthen_new(ctx, (ast_expression*)cmp, left, right); - if (!ifthen) { - ast_delete(cmp); /* will delete left and right */ - parseerror(parser, "internal error: failed to create conditional jump for array setter"); - return NULL; - } - - return (ast_expression*)ifthen; -} - -static ast_expression *array_setter_node(parser_t *parser, ast_value *array, ast_value *index, ast_value *value, size_t from, size_t afterend) -{ - lex_ctx_t ctx = ast_ctx(array); - - if (from+1 == afterend) { - /* set this value */ - ast_block *block; - ast_return *ret; - ast_array_index *subscript; - ast_store *st; - int assignop = type_store_instr[value->expression.vtype]; - - if (value->expression.vtype == TYPE_FIELD && value->expression.next->vtype == TYPE_VECTOR) - assignop = INSTR_STORE_V; - - subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false)); - if (!subscript) - return NULL; - - st = ast_store_new(ctx, assignop, (ast_expression*)subscript, (ast_expression*)value); - if (!st) { - ast_delete(subscript); - return NULL; - } - - block = ast_block_new(ctx); - if (!block) { - ast_delete(st); - return NULL; - } - - if (!ast_block_add_expr(block, (ast_expression*)st)) { - ast_delete(block); - return NULL; - } - - ret = ast_return_new(ctx, NULL); - if (!ret) { - ast_delete(block); - return NULL; - } - - if (!ast_block_add_expr(block, (ast_expression*)ret)) { - ast_delete(block); - return NULL; - } - - return (ast_expression*)block; - } else { - ast_expression *left, *right; - size_t diff = afterend - from; - size_t middle = from + diff/2; - left = array_setter_node(parser, array, index, value, from, middle); - right = array_setter_node(parser, array, index, value, middle, afterend); - return array_accessor_split(parser, array, index, middle, left, right); - } -} - -static ast_expression *array_field_setter_node( - parser_t *parser, - ast_value *array, - ast_value *entity, - ast_value *index, - ast_value *value, - size_t from, - size_t afterend) -{ - lex_ctx_t ctx = ast_ctx(array); - - if (from+1 == afterend) { - /* set this value */ - ast_block *block; - ast_return *ret; - ast_entfield *entfield; - ast_array_index *subscript; - ast_store *st; - int assignop = type_storep_instr[value->expression.vtype]; - - if (value->expression.vtype == TYPE_FIELD && value->expression.next->vtype == TYPE_VECTOR) - assignop = INSTR_STOREP_V; - - subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false)); - if (!subscript) - return NULL; - - subscript->expression.next = ast_type_copy(ast_ctx(subscript), (ast_expression*)subscript); - subscript->expression.vtype = TYPE_FIELD; - - entfield = ast_entfield_new_force(ctx, - (ast_expression*)entity, - (ast_expression*)subscript, - (ast_expression*)subscript); - if (!entfield) { - ast_delete(subscript); - return NULL; - } - - st = ast_store_new(ctx, assignop, (ast_expression*)entfield, (ast_expression*)value); - if (!st) { - ast_delete(entfield); - return NULL; - } - - block = ast_block_new(ctx); - if (!block) { - ast_delete(st); - return NULL; - } - - if (!ast_block_add_expr(block, (ast_expression*)st)) { - ast_delete(block); - return NULL; - } - - ret = ast_return_new(ctx, NULL); - if (!ret) { - ast_delete(block); - return NULL; - } - - if (!ast_block_add_expr(block, (ast_expression*)ret)) { - ast_delete(block); - return NULL; - } - - return (ast_expression*)block; - } else { - ast_expression *left, *right; - size_t diff = afterend - from; - size_t middle = from + diff/2; - left = array_field_setter_node(parser, array, entity, index, value, from, middle); - right = array_field_setter_node(parser, array, entity, index, value, middle, afterend); - return array_accessor_split(parser, array, index, middle, left, right); - } -} - -static ast_expression *array_getter_node(parser_t *parser, ast_value *array, ast_value *index, size_t from, size_t afterend) -{ - lex_ctx_t ctx = ast_ctx(array); - - if (from+1 == afterend) { - ast_return *ret; - ast_array_index *subscript; - - subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false)); - if (!subscript) - return NULL; - - ret = ast_return_new(ctx, (ast_expression*)subscript); - if (!ret) { - ast_delete(subscript); - return NULL; - } - - return (ast_expression*)ret; - } else { - ast_expression *left, *right; - size_t diff = afterend - from; - size_t middle = from + diff/2; - left = array_getter_node(parser, array, index, from, middle); - right = array_getter_node(parser, array, index, middle, afterend); - return array_accessor_split(parser, array, index, middle, left, right); - } -} - -static bool parser_create_array_accessor(parser_t *parser, ast_value *array, const char *funcname, ast_value **out) -{ - ast_function *func = NULL; - ast_value *fval = NULL; - ast_block *body = NULL; - - fval = ast_value_new(ast_ctx(array), funcname, TYPE_FUNCTION); - if (!fval) { - parseerror(parser, "failed to create accessor function value"); - return false; - } - fval->expression.flags &= ~(AST_FLAG_COVERAGE_MASK); - - func = ast_function_new(ast_ctx(array), funcname, fval); - if (!func) { - ast_delete(fval); - parseerror(parser, "failed to create accessor function node"); - return false; - } - - body = ast_block_new(ast_ctx(array)); - if (!body) { - parseerror(parser, "failed to create block for array accessor"); - ast_delete(fval); - ast_delete(func); - return false; - } - - vec_push(func->blocks, body); - *out = fval; - - vec_push(parser->accessors, fval); - - return true; -} - -static ast_value* parser_create_array_setter_proto(parser_t *parser, ast_value *array, const char *funcname) -{ - ast_value *index = NULL; - ast_value *value = NULL; - ast_function *func; - ast_value *fval; - - if (!ast_istype(array->expression.next, ast_value)) { - parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type"); - return NULL; - } - - if (!parser_create_array_accessor(parser, array, funcname, &fval)) - return NULL; - func = fval->constval.vfunc; - fval->expression.next = (ast_expression*)ast_value_new(ast_ctx(array), "", TYPE_VOID); - - index = ast_value_new(ast_ctx(array), "index", TYPE_FLOAT); - value = ast_value_copy((ast_value*)array->expression.next); - - if (!index || !value) { - parseerror(parser, "failed to create locals for array accessor"); - goto cleanup; - } - (void)!ast_value_set_name(value, "value"); /* not important */ - vec_push(fval->expression.params, index); - vec_push(fval->expression.params, value); - - array->setter = fval; - return fval; -cleanup: - if (index) ast_delete(index); - if (value) ast_delete(value); - ast_delete(func); - ast_delete(fval); - return NULL; -} - -static bool parser_create_array_setter_impl(parser_t *parser, ast_value *array) -{ - ast_expression *root = NULL; - root = array_setter_node(parser, array, - array->setter->expression.params[0], - array->setter->expression.params[1], - 0, array->expression.count); - if (!root) { - parseerror(parser, "failed to build accessor search tree"); - return false; - } - if (!ast_block_add_expr(array->setter->constval.vfunc->blocks[0], root)) { - ast_delete(root); - return false; - } - return true; -} - -static bool parser_create_array_setter(parser_t *parser, ast_value *array, const char *funcname) -{ - if (!parser_create_array_setter_proto(parser, array, funcname)) - return false; - return parser_create_array_setter_impl(parser, array); -} - -static bool parser_create_array_field_setter(parser_t *parser, ast_value *array, const char *funcname) -{ - ast_expression *root = NULL; - ast_value *entity = NULL; - ast_value *index = NULL; - ast_value *value = NULL; - ast_function *func; - ast_value *fval; - - if (!ast_istype(array->expression.next, ast_value)) { - parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type"); - return false; - } - - if (!parser_create_array_accessor(parser, array, funcname, &fval)) - return false; - func = fval->constval.vfunc; - fval->expression.next = (ast_expression*)ast_value_new(ast_ctx(array), "", TYPE_VOID); - - entity = ast_value_new(ast_ctx(array), "entity", TYPE_ENTITY); - index = ast_value_new(ast_ctx(array), "index", TYPE_FLOAT); - value = ast_value_copy((ast_value*)array->expression.next); - if (!entity || !index || !value) { - parseerror(parser, "failed to create locals for array accessor"); - goto cleanup; - } - (void)!ast_value_set_name(value, "value"); /* not important */ - vec_push(fval->expression.params, entity); - vec_push(fval->expression.params, index); - vec_push(fval->expression.params, value); - - root = array_field_setter_node(parser, array, entity, index, value, 0, array->expression.count); - if (!root) { - parseerror(parser, "failed to build accessor search tree"); - goto cleanup; - } - - array->setter = fval; - return ast_block_add_expr(func->blocks[0], root); -cleanup: - if (entity) ast_delete(entity); - if (index) ast_delete(index); - if (value) ast_delete(value); - if (root) ast_delete(root); - ast_delete(func); - ast_delete(fval); - return false; -} - -static ast_value* parser_create_array_getter_proto(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname) -{ - ast_value *index = NULL; - ast_value *fval; - ast_function *func; - - /* NOTE: checking array->expression.next rather than elemtype since - * for fields elemtype is a temporary fieldtype. - */ - if (!ast_istype(array->expression.next, ast_value)) { - parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type"); - return NULL; - } - - if (!parser_create_array_accessor(parser, array, funcname, &fval)) - return NULL; - func = fval->constval.vfunc; - fval->expression.next = ast_type_copy(ast_ctx(array), elemtype); - - index = ast_value_new(ast_ctx(array), "index", TYPE_FLOAT); - - if (!index) { - parseerror(parser, "failed to create locals for array accessor"); - goto cleanup; - } - vec_push(fval->expression.params, index); - - array->getter = fval; - return fval; -cleanup: - if (index) ast_delete(index); - ast_delete(func); - ast_delete(fval); - return NULL; -} - -static bool parser_create_array_getter_impl(parser_t *parser, ast_value *array) -{ - ast_expression *root = NULL; - - root = array_getter_node(parser, array, array->getter->expression.params[0], 0, array->expression.count); - if (!root) { - parseerror(parser, "failed to build accessor search tree"); - return false; - } - if (!ast_block_add_expr(array->getter->constval.vfunc->blocks[0], root)) { - ast_delete(root); - return false; - } - return true; -} - -static bool parser_create_array_getter(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname) -{ - if (!parser_create_array_getter_proto(parser, array, elemtype, funcname)) - return false; - return parser_create_array_getter_impl(parser, array); -} - -static ast_value *parse_parameter_list(parser_t *parser, ast_value *var) -{ - lex_ctx_t ctx; - size_t i; - ast_value **params; - ast_value *param; - ast_value *fval; - bool first = true; - bool variadic = false; - ast_value *varparam = NULL; - char *argcounter = NULL; - - ctx = parser_ctx(parser); - - /* for the sake of less code we parse-in in this function */ - if (!parser_next(parser)) { - ast_delete(var); - parseerror(parser, "expected parameter list"); - return NULL; - } - - params = NULL; - - /* parse variables until we hit a closing paren */ - while (parser->tok != ')') { - bool is_varargs = false; - - if (!first) { - /* there must be commas between them */ - if (parser->tok != ',') { - parseerror(parser, "expected comma or end of parameter list"); - goto on_error; - } - if (!parser_next(parser)) { - parseerror(parser, "expected parameter"); - goto on_error; - } - } - first = false; - - param = parse_typename(parser, NULL, NULL, &is_varargs); - if (!param && !is_varargs) - goto on_error; - if (is_varargs) { - /* '...' indicates a varargs function */ - variadic = true; - if (parser->tok != ')' && parser->tok != TOKEN_IDENT) { - parseerror(parser, "`...` must be the last parameter of a variadic function declaration"); - goto on_error; - } - if (parser->tok == TOKEN_IDENT) { - argcounter = util_strdup(parser_tokval(parser)); - if (!parser_next(parser) || parser->tok != ')') { - parseerror(parser, "`...` must be the last parameter of a variadic function declaration"); - goto on_error; - } - } - } else { - vec_push(params, param); - if (param->expression.vtype >= TYPE_VARIANT) { - char tname[1024]; /* typename is reserved in C++ */ - ast_type_to_string((ast_expression*)param, tname, sizeof(tname)); - parseerror(parser, "type not supported as part of a parameter list: %s", tname); - goto on_error; - } - /* type-restricted varargs */ - if (parser->tok == TOKEN_DOTS) { - variadic = true; - varparam = vec_last(params); - vec_pop(params); - if (!parser_next(parser) || (parser->tok != ')' && parser->tok != TOKEN_IDENT)) { - parseerror(parser, "`...` must be the last parameter of a variadic function declaration"); - goto on_error; - } - if (parser->tok == TOKEN_IDENT) { - argcounter = util_strdup(parser_tokval(parser)); - ast_value_set_name(param, argcounter); - if (!parser_next(parser) || parser->tok != ')') { - parseerror(parser, "`...` must be the last parameter of a variadic function declaration"); - goto on_error; - } - } - } - if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC && param->name[0] == '<') { - parseerror(parser, "parameter name omitted"); - goto on_error; - } - } - } - - if (vec_size(params) == 1 && params[0]->expression.vtype == TYPE_VOID) - vec_free(params); - - /* sanity check */ - if (vec_size(params) > 8 && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) - (void)!parsewarning(parser, WARN_EXTENSIONS, "more than 8 parameters are not supported by this standard"); - - /* parse-out */ - if (!parser_next(parser)) { - parseerror(parser, "parse error after typename"); - goto on_error; - } - - /* now turn 'var' into a function type */ - fval = ast_value_new(ctx, "", TYPE_FUNCTION); - fval->expression.next = (ast_expression*)var; - if (variadic) - fval->expression.flags |= AST_FLAG_VARIADIC; - var = fval; - - var->expression.params = params; - var->expression.varparam = (ast_expression*)varparam; - var->argcounter = argcounter; - params = NULL; - - return var; - -on_error: - if (argcounter) - mem_d(argcounter); - if (varparam) - ast_delete(varparam); - ast_delete(var); - for (i = 0; i < vec_size(params); ++i) - ast_delete(params[i]); - vec_free(params); - return NULL; -} - -static ast_value *parse_arraysize(parser_t *parser, ast_value *var) -{ - ast_expression *cexp; - ast_value *cval, *tmp; - lex_ctx_t ctx; - - ctx = parser_ctx(parser); - - if (!parser_next(parser)) { - ast_delete(var); - parseerror(parser, "expected array-size"); - return NULL; - } - - if (parser->tok != ']') { - cexp = parse_expression_leave(parser, true, false, false); - - if (!cexp || !ast_istype(cexp, ast_value)) { - if (cexp) - ast_unref(cexp); - ast_delete(var); - parseerror(parser, "expected array-size as constant positive integer"); - return NULL; - } - cval = (ast_value*)cexp; - } - else { - cexp = NULL; - cval = NULL; - } - - tmp = ast_value_new(ctx, "", TYPE_ARRAY); - tmp->expression.next = (ast_expression*)var; - var = tmp; - - if (cval) { - if (cval->expression.vtype == TYPE_INTEGER) - tmp->expression.count = cval->constval.vint; - else if (cval->expression.vtype == TYPE_FLOAT) - tmp->expression.count = cval->constval.vfloat; - else { - ast_unref(cexp); - ast_delete(var); - parseerror(parser, "array-size must be a positive integer constant"); - return NULL; - } - - ast_unref(cexp); - } else { - var->expression.count = -1; - var->expression.flags |= AST_FLAG_ARRAY_INIT; - } - - if (parser->tok != ']') { - ast_delete(var); - parseerror(parser, "expected ']' after array-size"); - return NULL; - } - if (!parser_next(parser)) { - ast_delete(var); - parseerror(parser, "error after parsing array size"); - return NULL; - } - return var; -} - -/* Parse a complete typename. - * for single-variables (ie. function parameters or typedefs) storebase should be NULL - * but when parsing variables separated by comma - * 'storebase' should point to where the base-type should be kept. - * The base type makes up every bit of type information which comes *before* the - * variable name. - * - * NOTE: The value must either be named, have a NULL name, or a name starting - * with '<'. In the first case, this will be the actual variable or type - * name, in the other cases it is assumed that the name will appear - * later, and an error is generated otherwise. - * - * The following will be parsed in its entirety: - * void() foo() - * The 'basetype' in this case is 'void()' - * and if there's a comma after it, say: - * void() foo(), bar - * then the type-information 'void()' can be stored in 'storebase' - */ -static ast_value *parse_typename(parser_t *parser, ast_value **storebase, ast_value *cached_typedef, bool *is_vararg) -{ - ast_value *var, *tmp; - lex_ctx_t ctx; - - const char *name = NULL; - bool isfield = false; - bool wasarray = false; - size_t morefields = 0; - - bool vararg = (parser->tok == TOKEN_DOTS); - - ctx = parser_ctx(parser); - - /* types may start with a dot */ - if (parser->tok == '.' || parser->tok == TOKEN_DOTS) { - isfield = true; - if (parser->tok == TOKEN_DOTS) - morefields += 2; - /* if we parsed a dot we need a typename now */ - if (!parser_next(parser)) { - parseerror(parser, "expected typename for field definition"); - return NULL; - } - - /* Further dots are handled seperately because they won't be part of the - * basetype - */ - while (true) { - if (parser->tok == '.') - ++morefields; - else if (parser->tok == TOKEN_DOTS) - morefields += 3; - else - break; - vararg = false; - if (!parser_next(parser)) { - parseerror(parser, "expected typename for field definition"); - return NULL; - } - } - } - if (parser->tok == TOKEN_IDENT) - cached_typedef = parser_find_typedef(parser, parser_tokval(parser), 0); - if (!cached_typedef && parser->tok != TOKEN_TYPENAME) { - if (vararg && is_vararg) { - *is_vararg = true; - return NULL; - } - parseerror(parser, "expected typename"); - return NULL; - } - - /* generate the basic type value */ - if (cached_typedef) { - var = ast_value_copy(cached_typedef); - ast_value_set_name(var, ""); - } else - var = ast_value_new(ctx, "", parser_token(parser)->constval.t); - - for (; morefields; --morefields) { - tmp = ast_value_new(ctx, "<.type>", TYPE_FIELD); - tmp->expression.next = (ast_expression*)var; - var = tmp; - } - - /* do not yet turn into a field - remember: - * .void() foo; is a field too - * .void()() foo; is a function - */ - - /* parse on */ - if (!parser_next(parser)) { - ast_delete(var); - parseerror(parser, "parse error after typename"); - return NULL; - } - - /* an opening paren now starts the parameter-list of a function - * this is where original-QC has parameter lists. - * We allow a single parameter list here. - * Much like fteqcc we don't allow `float()() x` - */ - if (parser->tok == '(') { - var = parse_parameter_list(parser, var); - if (!var) - return NULL; - } - - /* store the base if requested */ - if (storebase) { - *storebase = ast_value_copy(var); - if (isfield) { - tmp = ast_value_new(ctx, "", TYPE_FIELD); - tmp->expression.next = (ast_expression*)*storebase; - *storebase = tmp; - } - } - - /* there may be a name now */ - if (parser->tok == TOKEN_IDENT || parser->tok == TOKEN_KEYWORD) { - if (!strcmp(parser_tokval(parser), "break")) - (void)!parsewarning(parser, WARN_BREAKDEF, "break definition ignored (suggest removing it)"); - else if (parser->tok == TOKEN_KEYWORD) - goto leave; - - name = util_strdup(parser_tokval(parser)); - - /* parse on */ - if (!parser_next(parser)) { - ast_delete(var); - mem_d(name); - parseerror(parser, "error after variable or field declaration"); - return NULL; - } - } - - leave: - /* now this may be an array */ - if (parser->tok == '[') { - wasarray = true; - var = parse_arraysize(parser, var); - if (!var) { - if (name) mem_d(name); - return NULL; - } - } - - /* This is the point where we can turn it into a field */ - if (isfield) { - /* turn it into a field if desired */ - tmp = ast_value_new(ctx, "", TYPE_FIELD); - tmp->expression.next = (ast_expression*)var; - var = tmp; - } - - /* now there may be function parens again */ - if (parser->tok == '(' && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) - parseerror(parser, "C-style function syntax is not allowed in -std=qcc"); - if (parser->tok == '(' && wasarray) - parseerror(parser, "arrays as part of a return type is not supported"); - while (parser->tok == '(') { - var = parse_parameter_list(parser, var); - if (!var) { - if (name) mem_d(name); - return NULL; - } - } - - /* finally name it */ - if (name) { - if (!ast_value_set_name(var, name)) { - ast_delete(var); - mem_d(name); - parseerror(parser, "internal error: failed to set name"); - return NULL; - } - /* free the name, ast_value_set_name duplicates */ - mem_d(name); - } - - return var; -} - -static bool parse_typedef(parser_t *parser) -{ - ast_value *typevar, *oldtype; - ast_expression *old; - - typevar = parse_typename(parser, NULL, NULL, NULL); - - if (!typevar) - return false; - - /* while parsing types, the ast_value's get named '' */ - if (!typevar->name || typevar->name[0] == '<') { - parseerror(parser, "missing name in typedef"); - ast_delete(typevar); - return false; - } - - if ( (old = parser_find_var(parser, typevar->name)) ) { - parseerror(parser, "cannot define a type with the same name as a variable: %s\n" - " -> `%s` has been declared here: %s:%i", - typevar->name, ast_ctx(old).file, ast_ctx(old).line); - ast_delete(typevar); - return false; - } - - if ( (oldtype = parser_find_typedef(parser, typevar->name, vec_last(parser->_blocktypedefs))) ) { - parseerror(parser, "type `%s` has already been declared here: %s:%i", - typevar->name, ast_ctx(oldtype).file, ast_ctx(oldtype).line); - ast_delete(typevar); - return false; - } - - vec_push(parser->_typedefs, typevar); - util_htset(vec_last(parser->typedefs), typevar->name, typevar); - - if (parser->tok != ';') { - parseerror(parser, "expected semicolon after typedef"); - return false; - } - if (!parser_next(parser)) { - parseerror(parser, "parse error after typedef"); - return false; - } - - return true; -} - -static const char *cvq_to_str(int cvq) { - switch (cvq) { - case CV_NONE: return "none"; - case CV_VAR: return "`var`"; - case CV_CONST: return "`const`"; - default: return ""; - } -} - -static bool parser_check_qualifiers(parser_t *parser, const ast_value *var, const ast_value *proto) -{ - bool av, ao; - if (proto->cvq != var->cvq) { - if (!(proto->cvq == CV_CONST && var->cvq == CV_NONE && - !OPTS_FLAG(INITIALIZED_NONCONSTANTS) && - parser->tok == '=')) - { - return !parsewarning(parser, WARN_DIFFERENT_QUALIFIERS, - "`%s` declared with different qualifiers: %s\n" - " -> previous declaration here: %s:%i uses %s", - var->name, cvq_to_str(var->cvq), - ast_ctx(proto).file, ast_ctx(proto).line, - cvq_to_str(proto->cvq)); - } - } - av = (var ->expression.flags & AST_FLAG_NORETURN); - ao = (proto->expression.flags & AST_FLAG_NORETURN); - if (!av != !ao) { - return !parsewarning(parser, WARN_DIFFERENT_ATTRIBUTES, - "`%s` declared with different attributes%s\n" - " -> previous declaration here: %s:%i", - var->name, (av ? ": noreturn" : ""), - ast_ctx(proto).file, ast_ctx(proto).line, - (ao ? ": noreturn" : "")); - } - return true; -} - -static bool create_array_accessors(parser_t *parser, ast_value *var) -{ - char name[1024]; - util_snprintf(name, sizeof(name), "%s##SET", var->name); - if (!parser_create_array_setter(parser, var, name)) - return false; - util_snprintf(name, sizeof(name), "%s##GET", var->name); - if (!parser_create_array_getter(parser, var, var->expression.next, name)) - return false; - return true; -} - -static bool parse_array(parser_t *parser, ast_value *array) -{ - size_t i; - if (array->initlist) { - parseerror(parser, "array already initialized elsewhere"); - return false; - } - if (!parser_next(parser)) { - parseerror(parser, "parse error in array initializer"); - return false; - } - i = 0; - while (parser->tok != '}') { - ast_value *v = (ast_value*)parse_expression_leave(parser, true, false, false); - if (!v) - return false; - if (!ast_istype(v, ast_value) || !v->hasvalue || v->cvq != CV_CONST) { - ast_unref(v); - parseerror(parser, "initializing element must be a compile time constant"); - return false; - } - vec_push(array->initlist, v->constval); - if (v->expression.vtype == TYPE_STRING) { - array->initlist[i].vstring = util_strdupe(array->initlist[i].vstring); - ++i; - } - ast_unref(v); - if (parser->tok == '}') - break; - if (parser->tok != ',' || !parser_next(parser)) { - parseerror(parser, "expected comma or '}' in element list"); - return false; - } - } - if (!parser_next(parser) || parser->tok != ';') { - parseerror(parser, "expected semicolon after initializer, got %s"); - return false; - } - /* - if (!parser_next(parser)) { - parseerror(parser, "parse error after initializer"); - return false; - } - */ - - if (array->expression.flags & AST_FLAG_ARRAY_INIT) { - if (array->expression.count != (size_t)-1) { - parseerror(parser, "array `%s' has already been initialized with %u elements", - array->name, (unsigned)array->expression.count); - } - array->expression.count = vec_size(array->initlist); - if (!create_array_accessors(parser, array)) - return false; - } - return true; -} - -static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofields, int qualifier, ast_value *cached_typedef, bool noref, bool is_static, uint32_t qflags, char *vstring) -{ - ast_value *var; - ast_value *proto; - ast_expression *old; - bool was_end; - size_t i; - - ast_value *basetype = NULL; - bool retval = true; - bool isparam = false; - bool isvector = false; - bool cleanvar = true; - bool wasarray = false; - - ast_member *me[3] = { NULL, NULL, NULL }; - ast_member *last_me[3] = { NULL, NULL, NULL }; - - if (!localblock && is_static) - parseerror(parser, "`static` qualifier is not supported in global scope"); - - /* get the first complete variable */ - var = parse_typename(parser, &basetype, cached_typedef, NULL); - if (!var) { - if (basetype) - ast_delete(basetype); - return false; - } - - /* while parsing types, the ast_value's get named '' */ - if (!var->name || var->name[0] == '<') { - parseerror(parser, "declaration does not declare anything"); - if (basetype) - ast_delete(basetype); - return false; - } - - while (true) { - proto = NULL; - wasarray = false; - - /* Part 0: finish the type */ - if (parser->tok == '(') { - if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) - parseerror(parser, "C-style function syntax is not allowed in -std=qcc"); - var = parse_parameter_list(parser, var); - if (!var) { - retval = false; - goto cleanup; - } - } - /* we only allow 1-dimensional arrays */ - if (parser->tok == '[') { - wasarray = true; - var = parse_arraysize(parser, var); - if (!var) { - retval = false; - goto cleanup; - } - } - if (parser->tok == '(' && wasarray) { - parseerror(parser, "arrays as part of a return type is not supported"); - /* we'll still parse the type completely for now */ - } - /* for functions returning functions */ - while (parser->tok == '(') { - if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) - parseerror(parser, "C-style function syntax is not allowed in -std=qcc"); - var = parse_parameter_list(parser, var); - if (!var) { - retval = false; - goto cleanup; - } - } - - var->cvq = qualifier; - if (qflags & AST_FLAG_COVERAGE) /* specified in QC, drop our default */ - var->expression.flags &= ~(AST_FLAG_COVERAGE_MASK); - var->expression.flags |= qflags; - - /* - * store the vstring back to var for alias and - * deprecation messages. - */ - if (var->expression.flags & AST_FLAG_DEPRECATED || - var->expression.flags & AST_FLAG_ALIAS) - var->desc = vstring; - - if (parser_find_global(parser, var->name) && var->expression.flags & AST_FLAG_ALIAS) { - parseerror(parser, "function aliases cannot be forward declared"); - retval = false; - goto cleanup; - } - - - /* Part 1: - * check for validity: (end_sys_..., multiple-definitions, prototypes, ...) - * Also: if there was a prototype, `var` will be deleted and set to `proto` which - * is then filled with the previous definition and the parameter-names replaced. - */ - if (!strcmp(var->name, "nil")) { - if (OPTS_FLAG(UNTYPED_NIL)) { - if (!localblock || !OPTS_FLAG(PERMISSIVE)) - parseerror(parser, "name `nil` not allowed (try -fpermissive)"); - } else - (void)!parsewarning(parser, WARN_RESERVED_NAMES, "variable name `nil` is reserved"); - } - if (!localblock) { - /* Deal with end_sys_ vars */ - was_end = false; - if (!strcmp(var->name, "end_sys_globals")) { - var->uses++; - parser->crc_globals = vec_size(parser->globals); - was_end = true; - } - else if (!strcmp(var->name, "end_sys_fields")) { - var->uses++; - parser->crc_fields = vec_size(parser->fields); - was_end = true; - } - if (was_end && var->expression.vtype == TYPE_FIELD) { - if (parsewarning(parser, WARN_END_SYS_FIELDS, - "global '%s' hint should not be a field", - parser_tokval(parser))) - { - retval = false; - goto cleanup; - } - } - - if (!nofields && var->expression.vtype == TYPE_FIELD) - { - /* deal with field declarations */ - old = parser_find_field(parser, var->name); - if (old) { - if (parsewarning(parser, WARN_FIELD_REDECLARED, "field `%s` already declared here: %s:%i", - var->name, ast_ctx(old).file, (int)ast_ctx(old).line)) - { - retval = false; - goto cleanup; - } - ast_delete(var); - var = NULL; - goto skipvar; - /* - parseerror(parser, "field `%s` already declared here: %s:%i", - var->name, ast_ctx(old).file, ast_ctx(old).line); - retval = false; - goto cleanup; - */ - } - if ((OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC || OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) && - (old = parser_find_global(parser, var->name))) - { - parseerror(parser, "cannot declare a field and a global of the same name with -std=qcc"); - parseerror(parser, "field `%s` already declared here: %s:%i", - var->name, ast_ctx(old).file, ast_ctx(old).line); - retval = false; - goto cleanup; - } - } - else - { - /* deal with other globals */ - old = parser_find_global(parser, var->name); - if (old && var->expression.vtype == TYPE_FUNCTION && old->vtype == TYPE_FUNCTION) - { - /* This is a function which had a prototype */ - if (!ast_istype(old, ast_value)) { - parseerror(parser, "internal error: prototype is not an ast_value"); - retval = false; - goto cleanup; - } - proto = (ast_value*)old; - proto->desc = var->desc; - if (!ast_compare_type((ast_expression*)proto, (ast_expression*)var)) { - parseerror(parser, "conflicting types for `%s`, previous declaration was here: %s:%i", - proto->name, - ast_ctx(proto).file, ast_ctx(proto).line); - retval = false; - goto cleanup; - } - /* we need the new parameter-names */ - for (i = 0; i < vec_size(proto->expression.params); ++i) - ast_value_set_name(proto->expression.params[i], var->expression.params[i]->name); - if (!parser_check_qualifiers(parser, var, proto)) { - retval = false; - if (proto->desc) - mem_d(proto->desc); - proto = NULL; - goto cleanup; - } - proto->expression.flags |= var->expression.flags; - ast_delete(var); - var = proto; - } - else - { - /* other globals */ - if (old) { - if (parsewarning(parser, WARN_DOUBLE_DECLARATION, - "global `%s` already declared here: %s:%i", - var->name, ast_ctx(old).file, ast_ctx(old).line)) - { - retval = false; - goto cleanup; - } - if (old->flags & AST_FLAG_FINAL_DECL) { - parseerror(parser, "cannot redeclare variable `%s`, declared final here: %s:%i", - var->name, ast_ctx(old).file, ast_ctx(old).line); - retval = false; - goto cleanup; - } - proto = (ast_value*)old; - if (!ast_istype(old, ast_value)) { - parseerror(parser, "internal error: not an ast_value"); - retval = false; - proto = NULL; - goto cleanup; - } - if (!parser_check_qualifiers(parser, var, proto)) { - retval = false; - proto = NULL; - goto cleanup; - } - proto->expression.flags |= var->expression.flags; - /* copy the context for finals, - * so the error can show where it was actually made 'final' - */ - if (proto->expression.flags & AST_FLAG_FINAL_DECL) - ast_ctx(old) = ast_ctx(var); - ast_delete(var); - var = proto; - } - if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC && - (old = parser_find_field(parser, var->name))) - { - parseerror(parser, "cannot declare a field and a global of the same name with -std=qcc"); - parseerror(parser, "global `%s` already declared here: %s:%i", - var->name, ast_ctx(old).file, ast_ctx(old).line); - retval = false; - goto cleanup; - } - } - } - } - else /* it's not a global */ - { - old = parser_find_local(parser, var->name, vec_size(parser->variables)-1, &isparam); - if (old && !isparam) { - parseerror(parser, "local `%s` already declared here: %s:%i", - var->name, ast_ctx(old).file, (int)ast_ctx(old).line); - retval = false; - goto cleanup; - } - /* doing this here as the above is just for a single scope */ - old = parser_find_local(parser, var->name, 0, &isparam); - if (old && isparam) { - if (parsewarning(parser, WARN_LOCAL_SHADOWS, - "local `%s` is shadowing a parameter", var->name)) - { - parseerror(parser, "local `%s` already declared here: %s:%i", - var->name, ast_ctx(old).file, (int)ast_ctx(old).line); - retval = false; - goto cleanup; - } - if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_GMQCC) { - ast_delete(var); - if (ast_istype(old, ast_value)) - var = proto = (ast_value*)old; - else { - var = NULL; - goto skipvar; - } - } - } - } - - /* in a noref section we simply bump the usecount */ - if (noref || parser->noref) - var->uses++; - - /* Part 2: - * Create the global/local, and deal with vector types. - */ - if (!proto) { - if (var->expression.vtype == TYPE_VECTOR) - isvector = true; - else if (var->expression.vtype == TYPE_FIELD && - var->expression.next->vtype == TYPE_VECTOR) - isvector = true; - - if (isvector) { - if (!create_vector_members(var, me)) { - retval = false; - goto cleanup; - } - } - - if (!localblock) { - /* deal with global variables, fields, functions */ - if (!nofields && var->expression.vtype == TYPE_FIELD && parser->tok != '=') { - var->isfield = true; - vec_push(parser->fields, (ast_expression*)var); - util_htset(parser->htfields, var->name, var); - if (isvector) { - for (i = 0; i < 3; ++i) { - vec_push(parser->fields, (ast_expression*)me[i]); - util_htset(parser->htfields, me[i]->name, me[i]); - } - } - } - else { - if (!(var->expression.flags & AST_FLAG_ALIAS)) { - parser_addglobal(parser, var->name, (ast_expression*)var); - if (isvector) { - for (i = 0; i < 3; ++i) { - parser_addglobal(parser, me[i]->name, (ast_expression*)me[i]); - } - } - } else { - ast_expression *find = parser_find_global(parser, var->desc); - - if (!find) { - compile_error(parser_ctx(parser), "undeclared variable `%s` for alias `%s`", var->desc, var->name); - return false; - } - - if (!ast_compare_type((ast_expression*)var, find)) { - char ty1[1024]; - char ty2[1024]; - - ast_type_to_string(find, ty1, sizeof(ty1)); - ast_type_to_string((ast_expression*)var, ty2, sizeof(ty2)); - - compile_error(parser_ctx(parser), "incompatible types `%s` and `%s` for alias `%s`", - ty1, ty2, var->name - ); - return false; - } - - /* - * add alias to aliases table and to corrector - * so corrections can apply for aliases as well. - */ - util_htset(parser->aliases, var->name, find); - - /* - * add to corrector so corrections can work - * even for aliases too. - */ - correct_add ( - vec_last(parser->correct_variables), - &vec_last(parser->correct_variables_score), - var->name - ); - - /* generate aliases for vector components */ - if (isvector) { - char *buffer[3]; - - util_asprintf(&buffer[0], "%s_x", var->desc); - util_asprintf(&buffer[1], "%s_y", var->desc); - util_asprintf(&buffer[2], "%s_z", var->desc); - - util_htset(parser->aliases, me[0]->name, parser_find_global(parser, buffer[0])); - util_htset(parser->aliases, me[1]->name, parser_find_global(parser, buffer[1])); - util_htset(parser->aliases, me[2]->name, parser_find_global(parser, buffer[2])); - - mem_d(buffer[0]); - mem_d(buffer[1]); - mem_d(buffer[2]); - - /* - * add to corrector so corrections can work - * even for aliases too. - */ - correct_add ( - vec_last(parser->correct_variables), - &vec_last(parser->correct_variables_score), - me[0]->name - ); - correct_add ( - vec_last(parser->correct_variables), - &vec_last(parser->correct_variables_score), - me[1]->name - ); - correct_add ( - vec_last(parser->correct_variables), - &vec_last(parser->correct_variables_score), - me[2]->name - ); - } - } - } - } else { - if (is_static) { - /* a static adds itself to be generated like any other global - * but is added to the local namespace instead - */ - char *defname = NULL; - size_t prefix_len, ln; - size_t sn, sn_size; - - ln = strlen(parser->function->name); - vec_append(defname, ln, parser->function->name); - - vec_append(defname, 2, "::"); - /* remember the length up to here */ - prefix_len = vec_size(defname); - - /* Add it to the local scope */ - util_htset(vec_last(parser->variables), var->name, (void*)var); - - /* corrector */ - correct_add ( - vec_last(parser->correct_variables), - &vec_last(parser->correct_variables_score), - var->name - ); - - /* now rename the global */ - ln = strlen(var->name); - vec_append(defname, ln, var->name); - /* if a variable of that name already existed, add the - * counter value. - * The counter is incremented either way. - */ - sn_size = vec_size(parser->function->static_names); - for (sn = 0; sn != sn_size; ++sn) { - if (strcmp(parser->function->static_names[sn], var->name) == 0) - break; - } - if (sn != sn_size) { - char *num = NULL; - int len = util_asprintf(&num, "#%u", parser->function->static_count); - vec_append(defname, len, num); - mem_d(num); - } - else - vec_push(parser->function->static_names, util_strdup(var->name)); - parser->function->static_count++; - ast_value_set_name(var, defname); - - /* push it to the to-be-generated globals */ - vec_push(parser->globals, (ast_expression*)var); - - /* same game for the vector members */ - if (isvector) { - for (i = 0; i < 3; ++i) { - util_htset(vec_last(parser->variables), me[i]->name, (void*)(me[i])); - - /* corrector */ - correct_add( - vec_last(parser->correct_variables), - &vec_last(parser->correct_variables_score), - me[i]->name - ); - - vec_shrinkto(defname, prefix_len); - ln = strlen(me[i]->name); - vec_append(defname, ln, me[i]->name); - ast_member_set_name(me[i], defname); - - vec_push(parser->globals, (ast_expression*)me[i]); - } - } - vec_free(defname); - } else { - vec_push(localblock->locals, var); - parser_addlocal(parser, var->name, (ast_expression*)var); - if (isvector) { - for (i = 0; i < 3; ++i) { - parser_addlocal(parser, me[i]->name, (ast_expression*)me[i]); - ast_block_collect(localblock, (ast_expression*)me[i]); - } - } - } - } - } - memcpy(last_me, me, sizeof(me)); - me[0] = me[1] = me[2] = NULL; - cleanvar = false; - /* Part 2.2 - * deal with arrays - */ - if (var->expression.vtype == TYPE_ARRAY) { - if (var->expression.count != (size_t)-1) { - if (!create_array_accessors(parser, var)) - goto cleanup; - } - } - else if (!localblock && !nofields && - var->expression.vtype == TYPE_FIELD && - var->expression.next->vtype == TYPE_ARRAY) - { - char name[1024]; - ast_expression *telem; - ast_value *tfield; - ast_value *array = (ast_value*)var->expression.next; - - if (!ast_istype(var->expression.next, ast_value)) { - parseerror(parser, "internal error: field element type must be an ast_value"); - goto cleanup; - } - - util_snprintf(name, sizeof(name), "%s##SETF", var->name); - if (!parser_create_array_field_setter(parser, array, name)) - goto cleanup; - - telem = ast_type_copy(ast_ctx(var), array->expression.next); - tfield = ast_value_new(ast_ctx(var), "<.type>", TYPE_FIELD); - tfield->expression.next = telem; - util_snprintf(name, sizeof(name), "%s##GETFP", var->name); - if (!parser_create_array_getter(parser, array, (ast_expression*)tfield, name)) { - ast_delete(tfield); - goto cleanup; - } - ast_delete(tfield); - } - -skipvar: - if (parser->tok == ';') { - ast_delete(basetype); - if (!parser_next(parser)) { - parseerror(parser, "error after variable declaration"); - return false; - } - return true; - } - - if (parser->tok == ',') - goto another; - - /* - if (!var || (!localblock && !nofields && basetype->expression.vtype == TYPE_FIELD)) { - */ - if (!var) { - parseerror(parser, "missing comma or semicolon while parsing variables"); - break; - } - - if (localblock && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { - if (parsewarning(parser, WARN_LOCAL_CONSTANTS, - "initializing expression turns variable `%s` into a constant in this standard", - var->name) ) - { - break; - } - } - - if (parser->tok != '{' || var->expression.vtype != TYPE_FUNCTION) { - if (parser->tok != '=') { - parseerror(parser, "missing semicolon or initializer, got: `%s`", parser_tokval(parser)); - break; - } - - if (!parser_next(parser)) { - parseerror(parser, "error parsing initializer"); - break; - } - } - else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { - parseerror(parser, "expected '=' before function body in this standard"); - } - - if (parser->tok == '#') { - ast_function *func = NULL; - ast_value *number = NULL; - float fractional; - float integral; - int builtin_num; - - if (localblock) { - parseerror(parser, "cannot declare builtins within functions"); - break; - } - if (var->expression.vtype != TYPE_FUNCTION) { - parseerror(parser, "unexpected builtin number, '%s' is not a function", var->name); - break; - } - if (!parser_next(parser)) { - parseerror(parser, "expected builtin number"); - break; - } - - if (OPTS_FLAG(EXPRESSIONS_FOR_BUILTINS)) { - number = (ast_value*)parse_expression_leave(parser, true, false, false); - if (!number) { - parseerror(parser, "builtin number expected"); - break; - } - if (!ast_istype(number, ast_value) || !number->hasvalue || number->cvq != CV_CONST) - { - ast_unref(number); - parseerror(parser, "builtin number must be a compile time constant"); - break; - } - if (number->expression.vtype == TYPE_INTEGER) - builtin_num = number->constval.vint; - else if (number->expression.vtype == TYPE_FLOAT) - builtin_num = number->constval.vfloat; - else { - ast_unref(number); - parseerror(parser, "builtin number must be an integer constant"); - break; - } - ast_unref(number); - - fractional = modff(builtin_num, &integral); - if (builtin_num < 0 || fractional != 0) { - parseerror(parser, "builtin number must be an integer greater than zero"); - break; - } - - /* we only want the integral part anyways */ - builtin_num = integral; - } else if (parser->tok == TOKEN_INTCONST) { - builtin_num = parser_token(parser)->constval.i; - } else { - parseerror(parser, "builtin number must be a compile time constant"); - break; - } - - if (var->hasvalue) { - (void)!parsewarning(parser, WARN_DOUBLE_DECLARATION, - "builtin `%s` has already been defined\n" - " -> previous declaration here: %s:%i", - var->name, ast_ctx(var).file, (int)ast_ctx(var).line); - } - else - { - func = ast_function_new(ast_ctx(var), var->name, var); - if (!func) { - parseerror(parser, "failed to allocate function for `%s`", var->name); - break; - } - vec_push(parser->functions, func); - - func->builtin = -builtin_num-1; - } - - if (OPTS_FLAG(EXPRESSIONS_FOR_BUILTINS) - ? (parser->tok != ',' && parser->tok != ';') - : (!parser_next(parser))) - { - parseerror(parser, "expected comma or semicolon"); - if (func) - ast_function_delete(func); - var->constval.vfunc = NULL; - break; - } - } - else if (var->expression.vtype == TYPE_ARRAY && parser->tok == '{') - { - if (localblock) { - /* Note that fteqcc and most others don't even *have* - * local arrays, so this is not a high priority. - */ - parseerror(parser, "TODO: initializers for local arrays"); - break; - } - - var->hasvalue = true; - if (!parse_array(parser, var)) - break; - } - else if (var->expression.vtype == TYPE_FUNCTION && (parser->tok == '{' || parser->tok == '[')) - { - if (localblock) { - parseerror(parser, "cannot declare functions within functions"); - break; - } - - if (proto) - ast_ctx(proto) = parser_ctx(parser); - - if (!parse_function_body(parser, var)) - break; - ast_delete(basetype); - for (i = 0; i < vec_size(parser->gotos); ++i) - parseerror(parser, "undefined label: `%s`", parser->gotos[i]->name); - vec_free(parser->gotos); - vec_free(parser->labels); - return true; - } else { - ast_expression *cexp; - ast_value *cval; - bool folded_const = false; - - cexp = parse_expression_leave(parser, true, false, false); - if (!cexp) - break; - cval = ast_istype(cexp, ast_value) ? (ast_value*)cexp : NULL; - - /* deal with foldable constants: */ - if (localblock && - var->cvq == CV_CONST && cval && cval->hasvalue && cval->cvq == CV_CONST && !cval->isfield) - { - /* remove it from the current locals */ - if (isvector) { - for (i = 0; i < 3; ++i) { - vec_pop(parser->_locals); - vec_pop(localblock->collect); - } - } - /* do sanity checking, this function really needs refactoring */ - if (vec_last(parser->_locals) != (ast_expression*)var) - parseerror(parser, "internal error: unexpected change in local variable handling"); - else - vec_pop(parser->_locals); - if (vec_last(localblock->locals) != var) - parseerror(parser, "internal error: unexpected change in local variable handling (2)"); - else - vec_pop(localblock->locals); - /* push it to the to-be-generated globals */ - vec_push(parser->globals, (ast_expression*)var); - if (isvector) - for (i = 0; i < 3; ++i) - vec_push(parser->globals, (ast_expression*)last_me[i]); - folded_const = true; - } - - if (folded_const || !localblock || is_static) { - if (cval != parser->nil && - (!cval || ((!cval->hasvalue || cval->cvq != CV_CONST) && !cval->isfield)) - ) - { - parseerror(parser, "initializer is non constant"); - } - else - { - if (!is_static && - !OPTS_FLAG(INITIALIZED_NONCONSTANTS) && - qualifier != CV_VAR) - { - var->cvq = CV_CONST; - } - if (cval == parser->nil) - var->expression.flags |= AST_FLAG_INITIALIZED; - else - { - var->hasvalue = true; - if (cval->expression.vtype == TYPE_STRING) - var->constval.vstring = parser_strdup(cval->constval.vstring); - else if (cval->expression.vtype == TYPE_FIELD) - var->constval.vfield = cval; - else - memcpy(&var->constval, &cval->constval, sizeof(var->constval)); - ast_unref(cval); - } - } - } else { - int cvq; - shunt sy = { NULL, NULL, NULL, NULL }; - cvq = var->cvq; - var->cvq = CV_NONE; - vec_push(sy.out, syexp(ast_ctx(var), (ast_expression*)var)); - vec_push(sy.out, syexp(ast_ctx(cexp), (ast_expression*)cexp)); - vec_push(sy.ops, syop(ast_ctx(var), parser->assign_op)); - if (!parser_sy_apply_operator(parser, &sy)) - ast_unref(cexp); - else { - if (vec_size(sy.out) != 1 && vec_size(sy.ops) != 0) - parseerror(parser, "internal error: leaked operands"); - if (!ast_block_add_expr(localblock, (ast_expression*)sy.out[0].out)) - break; - } - vec_free(sy.out); - vec_free(sy.ops); - vec_free(sy.argc); - var->cvq = cvq; - } - /* a constant initialized to an inexact value should be marked inexact: - * const float x = ; should propagate the inexact flag - */ - if (var->cvq == CV_CONST && var->expression.vtype == TYPE_FLOAT) { - if (cval && cval->hasvalue && cval->cvq == CV_CONST) - var->inexact = cval->inexact; - } - } - -another: - if (parser->tok == ',') { - if (!parser_next(parser)) { - parseerror(parser, "expected another variable"); - break; - } - - if (parser->tok != TOKEN_IDENT) { - parseerror(parser, "expected another variable"); - break; - } - var = ast_value_copy(basetype); - cleanvar = true; - ast_value_set_name(var, parser_tokval(parser)); - if (!parser_next(parser)) { - parseerror(parser, "error parsing variable declaration"); - break; - } - continue; - } - - if (parser->tok != ';') { - parseerror(parser, "missing semicolon after variables"); - break; - } - - if (!parser_next(parser)) { - parseerror(parser, "parse error after variable declaration"); - break; - } - - ast_delete(basetype); - return true; - } - - if (cleanvar && var) - ast_delete(var); - ast_delete(basetype); - return false; - -cleanup: - ast_delete(basetype); - if (cleanvar && var) - ast_delete(var); - if (me[0]) ast_member_delete(me[0]); - if (me[1]) ast_member_delete(me[1]); - if (me[2]) ast_member_delete(me[2]); - return retval; -} - -static bool parser_global_statement(parser_t *parser) -{ - int cvq = CV_WRONG; - bool noref = false; - bool is_static = false; - uint32_t qflags = 0; - ast_value *istype = NULL; - char *vstring = NULL; - - if (parser->tok == TOKEN_IDENT) - istype = parser_find_typedef(parser, parser_tokval(parser), 0); - - if (istype || parser->tok == TOKEN_TYPENAME || parser->tok == '.' || parser->tok == TOKEN_DOTS) - { - return parse_variable(parser, NULL, false, CV_NONE, istype, false, false, 0, NULL); - } - else if (parse_qualifiers(parser, false, &cvq, &noref, &is_static, &qflags, &vstring)) - { - if (cvq == CV_WRONG) - return false; - return parse_variable(parser, NULL, false, cvq, NULL, noref, is_static, qflags, vstring); - } - else if (parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "enum")) - { - return parse_enum(parser); - } - else if (parser->tok == TOKEN_KEYWORD) - { - if (!strcmp(parser_tokval(parser), "typedef")) { - if (!parser_next(parser)) { - parseerror(parser, "expected type definition after 'typedef'"); - return false; - } - return parse_typedef(parser); - } - parseerror(parser, "unrecognized keyword `%s`", parser_tokval(parser)); - return false; - } - else if (parser->tok == '#') - { - return parse_pragma(parser); - } - else if (parser->tok == '$') - { - if (!parser_next(parser)) { - parseerror(parser, "parse error"); - return false; - } - } - else - { - parseerror(parser, "unexpected token: `%s`", parser->lex->tok.value); - return false; - } - return true; -} - -static uint16_t progdefs_crc_sum(uint16_t old, const char *str) -{ - return util_crc16(old, str, strlen(str)); -} - -static void progdefs_crc_file(const char *str) -{ - /* write to progdefs.h here */ - (void)str; -} - -static uint16_t progdefs_crc_both(uint16_t old, const char *str) -{ - old = progdefs_crc_sum(old, str); - progdefs_crc_file(str); - return old; -} - -static void generate_checksum(parser_t *parser, ir_builder *ir) -{ - uint16_t crc = 0xFFFF; - size_t i; - ast_value *value; - - crc = progdefs_crc_both(crc, "\n/* file generated by qcc, do not modify */\n\ntypedef struct\n{"); - crc = progdefs_crc_sum(crc, "\tint\tpad[28];\n"); - /* - progdefs_crc_file("\tint\tpad;\n"); - progdefs_crc_file("\tint\tofs_return[3];\n"); - progdefs_crc_file("\tint\tofs_parm0[3];\n"); - progdefs_crc_file("\tint\tofs_parm1[3];\n"); - progdefs_crc_file("\tint\tofs_parm2[3];\n"); - progdefs_crc_file("\tint\tofs_parm3[3];\n"); - progdefs_crc_file("\tint\tofs_parm4[3];\n"); - progdefs_crc_file("\tint\tofs_parm5[3];\n"); - progdefs_crc_file("\tint\tofs_parm6[3];\n"); - progdefs_crc_file("\tint\tofs_parm7[3];\n"); - */ - for (i = 0; i < parser->crc_globals; ++i) { - if (!ast_istype(parser->globals[i], ast_value)) - continue; - value = (ast_value*)(parser->globals[i]); - switch (value->expression.vtype) { - case TYPE_FLOAT: crc = progdefs_crc_both(crc, "\tfloat\t"); break; - case TYPE_VECTOR: crc = progdefs_crc_both(crc, "\tvec3_t\t"); break; - case TYPE_STRING: crc = progdefs_crc_both(crc, "\tstring_t\t"); break; - case TYPE_FUNCTION: crc = progdefs_crc_both(crc, "\tfunc_t\t"); break; - default: - crc = progdefs_crc_both(crc, "\tint\t"); - break; - } - crc = progdefs_crc_both(crc, value->name); - crc = progdefs_crc_both(crc, ";\n"); - } - crc = progdefs_crc_both(crc, "} globalvars_t;\n\ntypedef struct\n{\n"); - for (i = 0; i < parser->crc_fields; ++i) { - if (!ast_istype(parser->fields[i], ast_value)) - continue; - value = (ast_value*)(parser->fields[i]); - switch (value->expression.next->vtype) { - case TYPE_FLOAT: crc = progdefs_crc_both(crc, "\tfloat\t"); break; - case TYPE_VECTOR: crc = progdefs_crc_both(crc, "\tvec3_t\t"); break; - case TYPE_STRING: crc = progdefs_crc_both(crc, "\tstring_t\t"); break; - case TYPE_FUNCTION: crc = progdefs_crc_both(crc, "\tfunc_t\t"); break; - default: - crc = progdefs_crc_both(crc, "\tint\t"); - break; - } - crc = progdefs_crc_both(crc, value->name); - crc = progdefs_crc_both(crc, ";\n"); - } - crc = progdefs_crc_both(crc, "} entvars_t;\n\n"); - ir->code->crc = crc; -} - -parser_t *parser_create() -{ - parser_t *parser; - lex_ctx_t empty_ctx; - size_t i; - - parser = (parser_t*)mem_a(sizeof(parser_t)); - if (!parser) - return NULL; - - memset(parser, 0, sizeof(*parser)); - - for (i = 0; i < operator_count; ++i) { - if (operators[i].id == opid1('=')) { - parser->assign_op = operators+i; - break; - } - } - if (!parser->assign_op) { - con_err("internal error: initializing parser: failed to find assign operator\n"); - mem_d(parser); - return NULL; - } - - vec_push(parser->variables, parser->htfields = util_htnew(PARSER_HT_SIZE)); - vec_push(parser->variables, parser->htglobals = util_htnew(PARSER_HT_SIZE)); - vec_push(parser->typedefs, util_htnew(TYPEDEF_HT_SIZE)); - vec_push(parser->_blocktypedefs, 0); - - parser->aliases = util_htnew(PARSER_HT_SIZE); - - /* corrector */ - vec_push(parser->correct_variables, correct_trie_new()); - vec_push(parser->correct_variables_score, NULL); - - empty_ctx.file = ""; - empty_ctx.line = 0; - empty_ctx.column = 0; - parser->nil = ast_value_new(empty_ctx, "nil", TYPE_NIL); - parser->nil->cvq = CV_CONST; - if (OPTS_FLAG(UNTYPED_NIL)) - util_htset(parser->htglobals, "nil", (void*)parser->nil); - - parser->max_param_count = 1; - - parser->const_vec[0] = ast_value_new(empty_ctx, "", TYPE_NOEXPR); - parser->const_vec[1] = ast_value_new(empty_ctx, "", TYPE_NOEXPR); - parser->const_vec[2] = ast_value_new(empty_ctx, "", TYPE_NOEXPR); - - if (OPTS_OPTION_BOOL(OPTION_ADD_INFO)) { - parser->reserved_version = ast_value_new(empty_ctx, "reserved:version", TYPE_STRING); - parser->reserved_version->cvq = CV_CONST; - parser->reserved_version->hasvalue = true; - parser->reserved_version->expression.flags |= AST_FLAG_INCLUDE_DEF; - parser->reserved_version->constval.vstring = util_strdup(GMQCC_FULL_VERSION_STRING); - } else { - parser->reserved_version = NULL; - } - - parser->fold = fold_init (parser); - parser->intrin = intrin_init(parser); - return parser; -} - -static bool parser_compile(parser_t *parser) -{ - /* initial lexer/parser state */ - parser->lex->flags.noops = true; - - if (parser_next(parser)) - { - while (parser->tok != TOKEN_EOF && parser->tok < TOKEN_ERROR) - { - if (!parser_global_statement(parser)) { - if (parser->tok == TOKEN_EOF) - parseerror(parser, "unexpected end of file"); - else if (compile_errors) - parseerror(parser, "there have been errors, bailing out"); - lex_close(parser->lex); - parser->lex = NULL; - return false; - } - } - } else { - parseerror(parser, "parse error"); - lex_close(parser->lex); - parser->lex = NULL; - return false; - } - - lex_close(parser->lex); - parser->lex = NULL; - - return !compile_errors; -} - -bool parser_compile_file(parser_t *parser, const char *filename) -{ - parser->lex = lex_open(filename); - if (!parser->lex) { - con_err("failed to open file \"%s\"\n", filename); - return false; - } - return parser_compile(parser); -} - -bool parser_compile_string(parser_t *parser, const char *name, const char *str, size_t len) -{ - parser->lex = lex_open_string(str, len, name); - if (!parser->lex) { - con_err("failed to create lexer for string \"%s\"\n", name); - return false; - } - return parser_compile(parser); -} - -static void parser_remove_ast(parser_t *parser) -{ - size_t i; - if (parser->ast_cleaned) - return; - parser->ast_cleaned = true; - for (i = 0; i < vec_size(parser->accessors); ++i) { - ast_delete(parser->accessors[i]->constval.vfunc); - parser->accessors[i]->constval.vfunc = NULL; - ast_delete(parser->accessors[i]); - } - for (i = 0; i < vec_size(parser->functions); ++i) { - ast_delete(parser->functions[i]); - } - for (i = 0; i < vec_size(parser->fields); ++i) { - ast_delete(parser->fields[i]); - } - for (i = 0; i < vec_size(parser->globals); ++i) { - ast_delete(parser->globals[i]); - } - vec_free(parser->accessors); - vec_free(parser->functions); - vec_free(parser->globals); - vec_free(parser->fields); - - for (i = 0; i < vec_size(parser->variables); ++i) - util_htdel(parser->variables[i]); - vec_free(parser->variables); - vec_free(parser->_blocklocals); - vec_free(parser->_locals); - - /* corrector */ - for (i = 0; i < vec_size(parser->correct_variables); ++i) { - correct_del(parser->correct_variables[i], parser->correct_variables_score[i]); - } - vec_free(parser->correct_variables); - vec_free(parser->correct_variables_score); - - for (i = 0; i < vec_size(parser->_typedefs); ++i) - ast_delete(parser->_typedefs[i]); - vec_free(parser->_typedefs); - for (i = 0; i < vec_size(parser->typedefs); ++i) - util_htdel(parser->typedefs[i]); - vec_free(parser->typedefs); - vec_free(parser->_blocktypedefs); - - vec_free(parser->_block_ctx); - - vec_free(parser->labels); - vec_free(parser->gotos); - vec_free(parser->breaks); - vec_free(parser->continues); - - ast_value_delete(parser->nil); - - ast_value_delete(parser->const_vec[0]); - ast_value_delete(parser->const_vec[1]); - ast_value_delete(parser->const_vec[2]); - - if (parser->reserved_version) - ast_value_delete(parser->reserved_version); - - util_htdel(parser->aliases); - fold_cleanup(parser->fold); - intrin_cleanup(parser->intrin); -} - -void parser_cleanup(parser_t *parser) -{ - parser_remove_ast(parser); - mem_d(parser); -} - -static bool parser_set_coverage_func(parser_t *parser, ir_builder *ir) { - size_t i; - ast_expression *expr; - ast_value *cov; - ast_function *func; - - if (!OPTS_OPTION_BOOL(OPTION_COVERAGE)) - return true; - - func = NULL; - for (i = 0; i != vec_size(parser->functions); ++i) { - if (!strcmp(parser->functions[i]->name, "coverage")) { - func = parser->functions[i]; - break; - } - } - if (!func) { - if (OPTS_OPTION_BOOL(OPTION_COVERAGE)) { - con_out("coverage support requested but no coverage() builtin declared\n"); - ir_builder_delete(ir); - return false; - } - return true; - } - - cov = func->vtype; - expr = (ast_expression*)cov; - - if (expr->vtype != TYPE_FUNCTION || vec_size(expr->params) != 0) { - char ty[1024]; - ast_type_to_string(expr, ty, sizeof(ty)); - con_out("invalid type for coverage(): %s\n", ty); - ir_builder_delete(ir); - return false; - } - - ir->coverage_func = func->ir_func->value; - return true; -} - -bool parser_finish(parser_t *parser, const char *output) -{ - size_t i; - ir_builder *ir; - bool retval = true; - - if (compile_errors) { - con_out("*** there were compile errors\n"); - return false; - } - - ir = ir_builder_new("gmqcc_out"); - if (!ir) { - con_out("failed to allocate builder\n"); - return false; - } - - for (i = 0; i < vec_size(parser->fields); ++i) { - ast_value *field; - bool hasvalue; - if (!ast_istype(parser->fields[i], ast_value)) - continue; - field = (ast_value*)parser->fields[i]; - hasvalue = field->hasvalue; - field->hasvalue = false; - if (!ast_global_codegen((ast_value*)field, ir, true)) { - con_out("failed to generate field %s\n", field->name); - ir_builder_delete(ir); - return false; - } - if (hasvalue) { - ir_value *ifld; - ast_expression *subtype; - field->hasvalue = true; - subtype = field->expression.next; - ifld = ir_builder_create_field(ir, field->name, subtype->vtype); - if (subtype->vtype == TYPE_FIELD) - ifld->fieldtype = subtype->next->vtype; - else if (subtype->vtype == TYPE_FUNCTION) - ifld->outtype = subtype->next->vtype; - (void)!ir_value_set_field(field->ir_v, ifld); - } - } - for (i = 0; i < vec_size(parser->globals); ++i) { - ast_value *asvalue; - if (!ast_istype(parser->globals[i], ast_value)) - continue; - asvalue = (ast_value*)(parser->globals[i]); - if (!asvalue->uses && !asvalue->hasvalue && asvalue->expression.vtype != TYPE_FUNCTION) { - retval = retval && !compile_warning(ast_ctx(asvalue), WARN_UNUSED_VARIABLE, - "unused global: `%s`", asvalue->name); - } - if (!ast_global_codegen(asvalue, ir, false)) { - con_out("failed to generate global %s\n", asvalue->name); - ir_builder_delete(ir); - return false; - } - } - /* Build function vararg accessor ast tree now before generating - * immediates, because the accessors may add new immediates - */ - for (i = 0; i < vec_size(parser->functions); ++i) { - ast_function *f = parser->functions[i]; - if (f->varargs) { - if (parser->max_param_count > vec_size(f->vtype->expression.params)) { - f->varargs->expression.count = parser->max_param_count - vec_size(f->vtype->expression.params); - if (!parser_create_array_setter_impl(parser, f->varargs)) { - con_out("failed to generate vararg setter for %s\n", f->name); - ir_builder_delete(ir); - return false; - } - if (!parser_create_array_getter_impl(parser, f->varargs)) { - con_out("failed to generate vararg getter for %s\n", f->name); - ir_builder_delete(ir); - return false; - } - } else { - ast_delete(f->varargs); - f->varargs = NULL; - } - } - } - /* Now we can generate immediates */ - if (!fold_generate(parser->fold, ir)) - return false; - - /* before generating any functions we need to set the coverage_func */ - if (!parser_set_coverage_func(parser, ir)) - return false; - - for (i = 0; i < vec_size(parser->globals); ++i) { - ast_value *asvalue; - if (!ast_istype(parser->globals[i], ast_value)) - continue; - asvalue = (ast_value*)(parser->globals[i]); - if (!(asvalue->expression.flags & AST_FLAG_INITIALIZED)) - { - if (asvalue->cvq == CV_CONST && !asvalue->hasvalue) - (void)!compile_warning(ast_ctx(asvalue), WARN_UNINITIALIZED_CONSTANT, - "uninitialized constant: `%s`", - asvalue->name); - else if ((asvalue->cvq == CV_NONE || asvalue->cvq == CV_CONST) && !asvalue->hasvalue) - (void)!compile_warning(ast_ctx(asvalue), WARN_UNINITIALIZED_GLOBAL, - "uninitialized global: `%s`", - asvalue->name); - } - if (!ast_generate_accessors(asvalue, ir)) { - ir_builder_delete(ir); - return false; - } - } - for (i = 0; i < vec_size(parser->fields); ++i) { - ast_value *asvalue; - asvalue = (ast_value*)(parser->fields[i]->next); - - if (!ast_istype((ast_expression*)asvalue, ast_value)) - continue; - if (asvalue->expression.vtype != TYPE_ARRAY) - continue; - if (!ast_generate_accessors(asvalue, ir)) { - ir_builder_delete(ir); - return false; - } - } - if (parser->reserved_version && - !ast_global_codegen(parser->reserved_version, ir, false)) - { - con_out("failed to generate reserved::version"); - ir_builder_delete(ir); - return false; - } - for (i = 0; i < vec_size(parser->functions); ++i) { - ast_function *f = parser->functions[i]; - if (!ast_function_codegen(f, ir)) { - con_out("failed to generate function %s\n", f->name); - ir_builder_delete(ir); - return false; - } - } - - generate_checksum(parser, ir); - - if (OPTS_OPTION_BOOL(OPTION_DUMP)) - ir_builder_dump(ir, con_out); - for (i = 0; i < vec_size(parser->functions); ++i) { - if (!ir_function_finalize(parser->functions[i]->ir_func)) { - con_out("failed to finalize function %s\n", parser->functions[i]->name); - ir_builder_delete(ir); - return false; - } - } - parser_remove_ast(parser); - - if (compile_Werrors) { - con_out("*** there were warnings treated as errors\n"); - compile_show_werrors(); - retval = false; - } - - if (retval) { - if (OPTS_OPTION_BOOL(OPTION_DUMPFIN)) - ir_builder_dump(ir, con_out); - - if (!ir_builder_generate(ir, output)) { - con_out("*** failed to generate output file\n"); - ir_builder_delete(ir); - return false; - } - } - ir_builder_delete(ir); - return retval; -} diff --git a/parser.cpp b/parser.cpp new file mode 100644 index 0000000..51f7e1e --- /dev/null +++ b/parser.cpp @@ -0,0 +1,6345 @@ +#include +#include + +#include "intrin.h" +#include "fold.h" +#include "ast.h" +#include "parser.h" + +#define PARSER_HT_LOCALS 2 +#define PARSER_HT_SIZE 512 +#define TYPEDEF_HT_SIZE 512 + +static void parser_enterblock(parser_t *parser); +static bool parser_leaveblock(parser_t *parser); +static void parser_addlocal(parser_t *parser, const char *name, ast_expression *e); +static void parser_addlocal(parser_t *parser, const std::string &name, ast_expression *e); +static void parser_addglobal(parser_t *parser, const char *name, ast_expression *e); +static void parser_addglobal(parser_t *parser, const std::string &name, ast_expression *e); +static bool parse_typedef(parser_t *parser); +static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofields, int qualifier, ast_value *cached_typedef, bool noref, bool is_static, uint32_t qflags, char *vstring); +static ast_block* parse_block(parser_t *parser); +static bool parse_block_into(parser_t *parser, ast_block *block); +static bool parse_statement_or_block(parser_t *parser, ast_expression **out); +static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases); +static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue, bool with_labels); +static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool with_labels); +static ast_value* parser_create_array_setter_proto(parser_t *parser, ast_value *array, const char *funcname); +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, bool *is_vararg); + +static void parseerror_(parser_t *parser, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vcompile_error(parser->lex->tok.ctx, fmt, ap); + va_end(ap); +} + +template +static inline void parseerror(parser_t *parser, const char *fmt, const Ts&... ts) { + return parseerror_(parser, fmt, formatNormalize(ts)...); +} + +// returns true if it counts as an error +static bool GMQCC_WARN parsewarning_(parser_t *parser, int warntype, const char *fmt, ...) +{ + bool r; + va_list ap; + va_start(ap, fmt); + r = vcompile_warning(parser->lex->tok.ctx, warntype, fmt, ap); + va_end(ap); + return r; +} + +template +static inline bool GMQCC_WARN parsewarning(parser_t *parser, int warntype, const char *fmt, const Ts&... ts) { + return parsewarning_(parser, warntype, fmt, formatNormalize(ts)...); +} + +/********************************************************************** + * parsing + */ + +static bool parser_next(parser_t *parser) +{ + /* lex_do kills the previous token */ + parser->tok = lex_do(parser->lex); + if (parser->tok == TOKEN_EOF) + return true; + if (parser->tok >= TOKEN_ERROR) { + parseerror(parser, "lex error"); + return false; + } + return true; +} + +#define parser_tokval(p) ((p)->lex->tok.value) +#define parser_token(p) (&((p)->lex->tok)) + +char *parser_strdup(const char *str) +{ + if (str && !*str) { + /* actually dup empty strings */ + char *out = (char*)mem_a(1); + *out = 0; + return out; + } + return util_strdup(str); +} + +static ast_expression* parser_find_field(parser_t *parser, const char *name) { + return (ast_expression*)util_htget(parser->htfields, name); +} +static ast_expression* parser_find_field(parser_t *parser, const std::string &name) { + return parser_find_field(parser, name.c_str()); +} + +static ast_expression* parser_find_label(parser_t *parser, const char *name) +{ + for (auto &it : parser->labels) + if (it->m_name == name) + return it; + return nullptr; +} +static inline ast_expression* parser_find_label(parser_t *parser, const std::string &name) { + return parser_find_label(parser, name.c_str()); +} + +ast_expression* parser_find_global(parser_t *parser, const char *name) +{ + ast_expression *var = (ast_expression*)util_htget(parser->aliases, parser_tokval(parser)); + if (var) + return var; + return (ast_expression*)util_htget(parser->htglobals, name); +} + +ast_expression* parser_find_global(parser_t *parser, const std::string &name) { + return parser_find_global(parser, name.c_str()); +} + +static ast_expression* parser_find_param(parser_t *parser, const char *name) +{ + ast_value *fun; + if (!parser->function) + return nullptr; + fun = parser->function->m_function_type; + for (auto &it : fun->m_type_params) { + if (it->m_name == name) + return it.get(); + } + return nullptr; +} + +static ast_expression* parser_find_local(parser_t *parser, const char *name, size_t upto, bool *isparam) +{ + size_t i, hash; + ast_expression *e; + + hash = util_hthash(parser->htglobals, name); + + *isparam = false; + for (i = vec_size(parser->variables); i > upto;) { + --i; + if ( (e = (ast_expression*)util_htgeth(parser->variables[i], name, hash)) ) + return e; + } + *isparam = true; + return parser_find_param(parser, name); +} + +static ast_expression* parser_find_local(parser_t *parser, const std::string &name, size_t upto, bool *isparam) { + return parser_find_local(parser, name.c_str(), upto, isparam); +} + +static ast_expression* parser_find_var(parser_t *parser, const char *name) +{ + bool dummy; + ast_expression *v; + v = parser_find_local(parser, name, 0, &dummy); + if (!v) v = parser_find_global(parser, name); + return v; +} + +static inline ast_expression* parser_find_var(parser_t *parser, const std::string &name) { + return parser_find_var(parser, name.c_str()); +} + +static ast_value* parser_find_typedef(parser_t *parser, const char *name, size_t upto) +{ + size_t i, hash; + ast_value *e; + hash = util_hthash(parser->typedefs[0], name); + + for (i = vec_size(parser->typedefs); i > upto;) { + --i; + if ( (e = (ast_value*)util_htgeth(parser->typedefs[i], name, hash)) ) + return e; + } + return nullptr; +} + +static ast_value* parser_find_typedef(parser_t *parser, const std::string &name, size_t upto) { + return parser_find_typedef(parser, name.c_str(), upto); +} + +struct sy_elem { + size_t etype; /* 0 = expression, others are operators */ + bool isparen; + size_t off; + ast_expression *out; + ast_block *block; /* for commas and function calls */ + lex_ctx_t ctx; +}; + +enum { + PAREN_EXPR, + PAREN_FUNC, + PAREN_INDEX, + PAREN_TERNARY1, + PAREN_TERNARY2 +}; + +struct shunt { + std::vector out; + std::vector ops; + std::vector argc; + std::vector paren; +}; + +static sy_elem syexp(lex_ctx_t ctx, ast_expression *v) { + sy_elem e; + e.etype = 0; + e.off = 0; + e.out = v; + e.block = nullptr; + e.ctx = ctx; + e.isparen = false; + return e; +} + +static sy_elem syblock(lex_ctx_t ctx, ast_block *v) { + sy_elem e; + e.etype = 0; + e.off = 0; + e.out = v; + e.block = v; + e.ctx = ctx; + e.isparen = false; + return e; +} + +static sy_elem syop(lex_ctx_t ctx, const oper_info *op) { + sy_elem e; + e.etype = 1 + (op - operators); + e.off = 0; + e.out = nullptr; + e.block = nullptr; + e.ctx = ctx; + e.isparen = false; + return e; +} + +static sy_elem syparen(lex_ctx_t ctx, size_t off) { + sy_elem e; + e.etype = 0; + e.off = off; + e.out = nullptr; + e.block = nullptr; + e.ctx = ctx; + e.isparen = true; + return e; +} + +/* With regular precedence rules, ent.foo[n] is the same as (ent.foo)[n], + * so we need to rotate it to become ent.(foo[n]). + */ +static bool rotate_entfield_array_index_nodes(ast_expression **out) +{ + ast_array_index *index, *oldindex; + ast_entfield *entfield; + + ast_value *field; + ast_expression *sub; + ast_expression *entity; + + lex_ctx_t ctx = (*out)->m_context; + + if (!ast_istype(*out, ast_array_index)) + return false; + index = (ast_array_index*)*out; + + if (!ast_istype(index->m_array, ast_entfield)) + return false; + entfield = (ast_entfield*)index->m_array; + + if (!ast_istype(entfield->m_field, ast_value)) + return false; + field = (ast_value*)entfield->m_field; + + sub = index->m_index; + entity = entfield->m_entity; + + oldindex = index; + + index = ast_array_index::make(ctx, field, sub); + entfield = new ast_entfield(ctx, entity, index); + *out = entfield; + + oldindex->m_array = nullptr; + oldindex->m_index = nullptr; + delete oldindex; + + return true; +} + +static bool check_write_to(lex_ctx_t ctx, ast_expression *expr) +{ + if (ast_istype(expr, ast_value)) { + ast_value *val = (ast_value*)expr; + if (val->m_cvq == CV_CONST) { + if (val->m_name[0] == '#') { + compile_error(ctx, "invalid assignment to a literal constant"); + return false; + } + /* + * To work around quakeworld we must elide the error and make it + * a warning instead. + */ + if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC) + compile_error(ctx, "assignment to constant `%s`", val->m_name); + else + (void)!compile_warning(ctx, WARN_CONST_OVERWRITE, "assignment to constant `%s`", val->m_name); + return false; + } + } + return true; +} + +static bool parser_sy_apply_operator(parser_t *parser, shunt *sy) +{ + const oper_info *op; + lex_ctx_t ctx; + ast_expression *out = nullptr; + ast_expression *exprs[3]; + ast_block *blocks[3]; + ast_binstore *asbinstore; + size_t i, assignop, addop, subop; + qcint_t generated_op = 0; + + char ty1[1024]; + char ty2[1024]; + + if (sy->ops.empty()) { + parseerror(parser, "internal error: missing operator"); + return false; + } + + if (sy->ops.back().isparen) { + parseerror(parser, "unmatched parenthesis"); + return false; + } + + op = &operators[sy->ops.back().etype - 1]; + ctx = sy->ops.back().ctx; + + if (sy->out.size() < op->operands) { + if (op->flags & OP_PREFIX) + compile_error(ctx, "expected expression after unary operator `%s`", op->op, (int)op->id); + else /* this should have errored previously already */ + compile_error(ctx, "expected expression after operator `%s`", op->op, (int)op->id); + return false; + } + + sy->ops.pop_back(); + + /* op(:?) has no input and no output */ + if (!op->operands) + return true; + + sy->out.erase(sy->out.end() - op->operands, sy->out.end()); + for (i = 0; i < op->operands; ++i) { + exprs[i] = sy->out[sy->out.size()+i].out; + blocks[i] = sy->out[sy->out.size()+i].block; + + if (exprs[i]->m_vtype == TYPE_NOEXPR && + !(i != 0 && op->id == opid2('?',':')) && + !(i == 1 && op->id == opid1('.'))) + { + if (ast_istype(exprs[i], ast_label)) + compile_error(exprs[i]->m_context, "expected expression, got an unknown identifier"); + else + compile_error(exprs[i]->m_context, "not an expression"); + (void)!compile_warning(exprs[i]->m_context, WARN_DEBUG, "expression %u\n", (unsigned int)i); + } + } + + if (blocks[0] && blocks[0]->m_exprs.empty() && op->id != opid1(',')) { + compile_error(ctx, "internal error: operator cannot be applied on empty blocks"); + return false; + } + +#define NotSameType(T) \ + (exprs[0]->m_vtype != exprs[1]->m_vtype || \ + exprs[0]->m_vtype != T) + + switch (op->id) + { + default: + compile_error(ctx, "internal error: unhandled operator: %s (%i)", op->op, (int)op->id); + return false; + + case opid1('.'): + if (exprs[0]->m_vtype == TYPE_VECTOR && + exprs[1]->m_vtype == TYPE_NOEXPR) + { + if (exprs[1] == parser->const_vec[0]) + out = ast_member::make(ctx, exprs[0], 0, ""); + else if (exprs[1] == parser->const_vec[1]) + out = ast_member::make(ctx, exprs[0], 1, ""); + else if (exprs[1] == parser->const_vec[2]) + out = ast_member::make(ctx, exprs[0], 2, ""); + else { + compile_error(ctx, "access to invalid vector component"); + return false; + } + } + else if (exprs[0]->m_vtype == TYPE_ENTITY) { + if (exprs[1]->m_vtype != TYPE_FIELD) { + compile_error(exprs[1]->m_context, "type error: right hand of member-operand should be an entity-field"); + return false; + } + out = new ast_entfield(ctx, exprs[0], exprs[1]); + } + else if (exprs[0]->m_vtype == TYPE_VECTOR) { + compile_error(exprs[1]->m_context, "vectors cannot be accessed this way"); + return false; + } + else { + compile_error(exprs[1]->m_context, "type error: member-of operator on something that is not an entity or vector"); + return false; + } + break; + + case opid1('['): + if (exprs[0]->m_vtype != TYPE_ARRAY && + !(exprs[0]->m_vtype == TYPE_FIELD && + exprs[0]->m_next->m_vtype == TYPE_ARRAY)) + { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + compile_error(exprs[0]->m_context, "cannot index value of type %s", ty1); + return false; + } + if (exprs[1]->m_vtype != TYPE_FLOAT) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + compile_error(exprs[1]->m_context, "index must be of type float, not %s", ty1); + return false; + } + out = ast_array_index::make(ctx, exprs[0], exprs[1]); + rotate_entfield_array_index_nodes(&out); + break; + + case opid1(','): + if (sy->paren.size() && sy->paren.back() == PAREN_FUNC) { + sy->out.push_back(syexp(ctx, exprs[0])); + sy->out.push_back(syexp(ctx, exprs[1])); + sy->argc.back()++; + return true; + } + if (blocks[0]) { + if (!blocks[0]->addExpr(exprs[1])) + return false; + } else { + blocks[0] = new ast_block(ctx); + if (!blocks[0]->addExpr(exprs[0]) || + !blocks[0]->addExpr(exprs[1])) + { + return false; + } + } + blocks[0]->setType(*exprs[1]); + + sy->out.push_back(syblock(ctx, blocks[0])); + return true; + + case opid2('+','P'): + out = exprs[0]; + break; + case opid2('-','P'): + if ((out = parser->m_fold.op(op, exprs))) + break; + + if (exprs[0]->m_vtype != TYPE_FLOAT && + exprs[0]->m_vtype != TYPE_VECTOR) { + compile_error(ctx, "invalid types used in unary expression: cannot negate type %s", + type_name[exprs[0]->m_vtype]); + return false; + } + if (exprs[0]->m_vtype == TYPE_FLOAT) + out = ast_unary::make(ctx, VINSTR_NEG_F, exprs[0]); + else + out = ast_unary::make(ctx, VINSTR_NEG_V, exprs[0]); + break; + + case opid2('!','P'): + if (!(out = parser->m_fold.op(op, exprs))) { + switch (exprs[0]->m_vtype) { + case TYPE_FLOAT: + out = ast_unary::make(ctx, INSTR_NOT_F, exprs[0]); + break; + case TYPE_VECTOR: + out = ast_unary::make(ctx, INSTR_NOT_V, exprs[0]); + break; + case TYPE_STRING: + if (OPTS_FLAG(TRUE_EMPTY_STRINGS)) + out = ast_unary::make(ctx, INSTR_NOT_F, exprs[0]); + else + out = ast_unary::make(ctx, INSTR_NOT_S, exprs[0]); + break; + /* we don't constant-fold NOT for these types */ + case TYPE_ENTITY: + out = ast_unary::make(ctx, INSTR_NOT_ENT, exprs[0]); + break; + case TYPE_FUNCTION: + out = ast_unary::make(ctx, INSTR_NOT_FNC, exprs[0]); + break; + default: + compile_error(ctx, "invalid types used in expression: cannot logically negate type %s", + type_name[exprs[0]->m_vtype]); + return false; + } + } + break; + + case opid1('+'): + if (exprs[0]->m_vtype != exprs[1]->m_vtype || + (exprs[0]->m_vtype != TYPE_VECTOR && exprs[0]->m_vtype != TYPE_FLOAT) ) + { + compile_error(ctx, "invalid types used in expression: cannot add type %s and %s", + type_name[exprs[0]->m_vtype], + type_name[exprs[1]->m_vtype]); + return false; + } + if (!(out = parser->m_fold.op(op, exprs))) { + switch (exprs[0]->m_vtype) { + case TYPE_FLOAT: + out = fold::binary(ctx, INSTR_ADD_F, exprs[0], exprs[1]); + break; + case TYPE_VECTOR: + out = fold::binary(ctx, INSTR_ADD_V, exprs[0], exprs[1]); + break; + default: + compile_error(ctx, "invalid types used in expression: cannot add type %s and %s", + type_name[exprs[0]->m_vtype], + type_name[exprs[1]->m_vtype]); + return false; + } + } + break; + case opid1('-'): + if (exprs[0]->m_vtype != exprs[1]->m_vtype || + (exprs[0]->m_vtype != TYPE_VECTOR && exprs[0]->m_vtype != TYPE_FLOAT)) + { + compile_error(ctx, "invalid types used in expression: cannot subtract type %s from %s", + type_name[exprs[1]->m_vtype], + type_name[exprs[0]->m_vtype]); + return false; + } + if (!(out = parser->m_fold.op(op, exprs))) { + switch (exprs[0]->m_vtype) { + case TYPE_FLOAT: + out = fold::binary(ctx, INSTR_SUB_F, exprs[0], exprs[1]); + break; + case TYPE_VECTOR: + out = fold::binary(ctx, INSTR_SUB_V, exprs[0], exprs[1]); + break; + default: + compile_error(ctx, "invalid types used in expression: cannot subtract type %s from %s", + type_name[exprs[1]->m_vtype], + type_name[exprs[0]->m_vtype]); + return false; + } + } + break; + case opid1('*'): + if (exprs[0]->m_vtype != exprs[1]->m_vtype && + !(exprs[0]->m_vtype == TYPE_VECTOR && + exprs[1]->m_vtype == TYPE_FLOAT) && + !(exprs[1]->m_vtype == TYPE_VECTOR && + exprs[0]->m_vtype == TYPE_FLOAT) + ) + { + compile_error(ctx, "invalid types used in expression: cannot multiply types %s and %s", + type_name[exprs[1]->m_vtype], + type_name[exprs[0]->m_vtype]); + return false; + } + if (!(out = parser->m_fold.op(op, exprs))) { + switch (exprs[0]->m_vtype) { + case TYPE_FLOAT: + if (exprs[1]->m_vtype == TYPE_VECTOR) + out = fold::binary(ctx, INSTR_MUL_FV, exprs[0], exprs[1]); + else + out = fold::binary(ctx, INSTR_MUL_F, exprs[0], exprs[1]); + break; + case TYPE_VECTOR: + if (exprs[1]->m_vtype == TYPE_FLOAT) + out = fold::binary(ctx, INSTR_MUL_VF, exprs[0], exprs[1]); + else + out = fold::binary(ctx, INSTR_MUL_V, exprs[0], exprs[1]); + break; + default: + compile_error(ctx, "invalid types used in expression: cannot multiply types %s and %s", + type_name[exprs[1]->m_vtype], + type_name[exprs[0]->m_vtype]); + return false; + } + } + break; + + case opid1('/'): + if (exprs[1]->m_vtype != TYPE_FLOAT) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types used in expression: cannot divide types %s and %s", ty1, ty2); + return false; + } + if (!(out = parser->m_fold.op(op, exprs))) { + if (exprs[0]->m_vtype == TYPE_FLOAT) + out = fold::binary(ctx, INSTR_DIV_F, exprs[0], exprs[1]); + else { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types used in expression: cannot divide types %s and %s", ty1, ty2); + return false; + } + } + break; + + case opid1('%'): + if (NotSameType(TYPE_FLOAT)) { + compile_error(ctx, "invalid types used in expression: cannot perform modulo operation between types %s and %s", + type_name[exprs[0]->m_vtype], + type_name[exprs[1]->m_vtype]); + return false; + } else if (!(out = parser->m_fold.op(op, exprs))) { + /* generate a call to __builtin_mod */ + ast_expression *mod = parser->m_intrin.func("mod"); + ast_call *call = nullptr; + if (!mod) return false; /* can return null for missing floor */ + + call = ast_call::make(parser_ctx(parser), mod); + call->m_params.push_back(exprs[0]); + call->m_params.push_back(exprs[1]); + + out = call; + } + break; + + case opid2('%','='): + compile_error(ctx, "%= is unimplemented"); + return false; + + case opid1('|'): + case opid1('&'): + case opid1('^'): + if ( !(exprs[0]->m_vtype == TYPE_FLOAT && exprs[1]->m_vtype == TYPE_FLOAT) && + !(exprs[0]->m_vtype == TYPE_VECTOR && exprs[1]->m_vtype == TYPE_FLOAT) && + !(exprs[0]->m_vtype == TYPE_VECTOR && exprs[1]->m_vtype == TYPE_VECTOR)) + { + compile_error(ctx, "invalid types used in expression: cannot perform bit operations between types %s and %s", + type_name[exprs[0]->m_vtype], + type_name[exprs[1]->m_vtype]); + return false; + } + + if (!(out = parser->m_fold.op(op, exprs))) { + /* + * IF the first expression is float, the following will be too + * since scalar ^ vector is not allowed. + */ + if (exprs[0]->m_vtype == TYPE_FLOAT) { + out = fold::binary(ctx, + (op->id == opid1('^') ? VINSTR_BITXOR : op->id == opid1('|') ? INSTR_BITOR : INSTR_BITAND), + exprs[0], exprs[1]); + } else { + /* + * The first is a vector: vector is allowed to bitop with vector and + * with scalar, branch here for the second operand. + */ + if (exprs[1]->m_vtype == TYPE_VECTOR) { + /* + * Bitop all the values of the vector components against the + * vectors components in question. + */ + out = fold::binary(ctx, + (op->id == opid1('^') ? VINSTR_BITXOR_V : op->id == opid1('|') ? VINSTR_BITOR_V : VINSTR_BITAND_V), + exprs[0], exprs[1]); + } else { + out = fold::binary(ctx, + (op->id == opid1('^') ? VINSTR_BITXOR_VF : op->id == opid1('|') ? VINSTR_BITOR_VF : VINSTR_BITAND_VF), + exprs[0], exprs[1]); + } + } + } + break; + + case opid2('<','<'): + case opid2('>','>'): + if (NotSameType(TYPE_FLOAT)) { + compile_error(ctx, "invalid types used in expression: cannot perform shift between types %s and %s", + type_name[exprs[0]->m_vtype], + type_name[exprs[1]->m_vtype]); + return false; + } + + if (!(out = parser->m_fold.op(op, exprs))) { + ast_expression *shift = parser->m_intrin.func((op->id == opid2('<','<')) ? "__builtin_lshift" : "__builtin_rshift"); + ast_call *call = ast_call::make(parser_ctx(parser), shift); + call->m_params.push_back(exprs[0]); + call->m_params.push_back(exprs[1]); + out = call; + } + break; + + case opid3('<','<','='): + case opid3('>','>','='): + if (NotSameType(TYPE_FLOAT)) { + compile_error(ctx, "invalid types used in expression: cannot perform shift operation between types %s and %s", + type_name[exprs[0]->m_vtype], + type_name[exprs[1]->m_vtype]); + return false; + } + + if(!(out = parser->m_fold.op(op, exprs))) { + ast_expression *shift = parser->m_intrin.func((op->id == opid3('<','<','=')) ? "__builtin_lshift" : "__builtin_rshift"); + ast_call *call = ast_call::make(parser_ctx(parser), shift); + call->m_params.push_back(exprs[0]); + call->m_params.push_back(exprs[1]); + out = new ast_store( + parser_ctx(parser), + INSTR_STORE_F, + exprs[0], + call + ); + } + + break; + + case opid2('|','|'): + generated_op += 1; /* INSTR_OR */ + case opid2('&','&'): + generated_op += INSTR_AND; + if (!(out = parser->m_fold.op(op, exprs))) { + if (OPTS_FLAG(PERL_LOGIC) && !exprs[0]->compareType(*exprs[1])) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types for logical operation with -fperl-logic: %s and %s", ty1, ty2); + return false; + } + for (i = 0; i < 2; ++i) { + if (OPTS_FLAG(CORRECT_LOGIC) && exprs[i]->m_vtype == TYPE_VECTOR) { + out = ast_unary::make(ctx, INSTR_NOT_V, exprs[i]); + if (!out) break; + out = ast_unary::make(ctx, INSTR_NOT_F, out); + if (!out) break; + exprs[i] = out; out = nullptr; + if (OPTS_FLAG(PERL_LOGIC)) { + /* here we want to keep the right expressions' type */ + break; + } + } + else if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && exprs[i]->m_vtype == TYPE_STRING) { + out = ast_unary::make(ctx, INSTR_NOT_S, exprs[i]); + if (!out) break; + out = ast_unary::make(ctx, INSTR_NOT_F, out); + if (!out) break; + exprs[i] = out; out = nullptr; + if (OPTS_FLAG(PERL_LOGIC)) { + /* here we want to keep the right expressions' type */ + break; + } + } + } + out = fold::binary(ctx, generated_op, exprs[0], exprs[1]); + } + break; + + case opid2('?',':'): + if (sy->paren.back() != PAREN_TERNARY2) { + compile_error(ctx, "mismatched parenthesis/ternary"); + return false; + } + sy->paren.pop_back(); + if (!exprs[1]->compareType(*exprs[2])) { + ast_type_to_string(exprs[1], ty1, sizeof(ty1)); + ast_type_to_string(exprs[2], ty2, sizeof(ty2)); + compile_error(ctx, "operands of ternary expression must have the same type, got %s and %s", ty1, ty2); + return false; + } + if (!(out = parser->m_fold.op(op, exprs))) + out = new ast_ternary(ctx, exprs[0], exprs[1], exprs[2]); + break; + + case opid2('*', '*'): + if (NotSameType(TYPE_FLOAT)) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types used in exponentiation: %s and %s", + ty1, ty2); + return false; + } + + if (!(out = parser->m_fold.op(op, exprs))) { + ast_call *gencall = ast_call::make(parser_ctx(parser), parser->m_intrin.func("pow")); + gencall->m_params.push_back(exprs[0]); + gencall->m_params.push_back(exprs[1]); + out = gencall; + } + break; + + case opid2('>', '<'): + if (NotSameType(TYPE_VECTOR)) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types used in cross product: %s and %s", + ty1, ty2); + return false; + } + + if (!(out = parser->m_fold.op(op, exprs))) { + out = fold::binary( + parser_ctx(parser), + VINSTR_CROSS, + exprs[0], + exprs[1] + ); + } + + break; + + case opid3('<','=','>'): /* -1, 0, or 1 */ + if (NotSameType(TYPE_FLOAT)) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types used in comparision: %s and %s", + ty1, ty2); + + return false; + } + + if (!(out = parser->m_fold.op(op, exprs))) { + /* This whole block is NOT fold_binary safe */ + ast_binary *eq = new ast_binary(ctx, INSTR_EQ_F, exprs[0], exprs[1]); + + eq->m_refs = AST_REF_NONE; + + /* if (lt) { */ + out = new ast_ternary(ctx, + new ast_binary(ctx, INSTR_LT, exprs[0], exprs[1]), + /* out = -1 */ + parser->m_fold.imm_float(2), + /* } else { */ + /* if (eq) { */ + new ast_ternary(ctx, eq, + /* out = 0 */ + parser->m_fold.imm_float(0), + /* } else { */ + /* out = 1 */ + parser->m_fold.imm_float(1) + /* } */ + ) + /* } */ + ); + + } + break; + + case opid1('>'): + generated_op += 1; /* INSTR_GT */ + case opid1('<'): + generated_op += 1; /* INSTR_LT */ + case opid2('>', '='): + generated_op += 1; /* INSTR_GE */ + case opid2('<', '='): + generated_op += INSTR_LE; + if (NotSameType(TYPE_FLOAT)) { + compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s", + type_name[exprs[0]->m_vtype], + type_name[exprs[1]->m_vtype]); + return false; + } + if (!(out = parser->m_fold.op(op, exprs))) + out = fold::binary(ctx, generated_op, exprs[0], exprs[1]); + break; + case opid2('!', '='): + if (exprs[0]->m_vtype != exprs[1]->m_vtype) { + compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s", + type_name[exprs[0]->m_vtype], + type_name[exprs[1]->m_vtype]); + return false; + } + if (!(out = parser->m_fold.op(op, exprs))) + out = fold::binary(ctx, type_ne_instr[exprs[0]->m_vtype], exprs[0], exprs[1]); + break; + case opid2('=', '='): + if (exprs[0]->m_vtype != exprs[1]->m_vtype) { + compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s", + type_name[exprs[0]->m_vtype], + type_name[exprs[1]->m_vtype]); + return false; + } + if (!(out = parser->m_fold.op(op, exprs))) + out = fold::binary(ctx, type_eq_instr[exprs[0]->m_vtype], exprs[0], exprs[1]); + break; + + case opid1('='): + if (ast_istype(exprs[0], ast_entfield)) { + ast_expression *field = ((ast_entfield*)exprs[0])->m_field; + if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) && + exprs[0]->m_vtype == TYPE_FIELD && + exprs[0]->m_next->m_vtype == TYPE_VECTOR) + { + assignop = type_storep_instr[TYPE_VECTOR]; + } + else + assignop = type_storep_instr[exprs[0]->m_vtype]; + if (assignop == VINSTR_END || !field->m_next->compareType(*exprs[1])) + { + ast_type_to_string(field->m_next, ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + if (OPTS_FLAG(ASSIGN_FUNCTION_TYPES) && + field->m_next->m_vtype == TYPE_FUNCTION && + exprs[1]->m_vtype == TYPE_FUNCTION) + { + (void)!compile_warning(ctx, WARN_ASSIGN_FUNCTION_TYPES, + "invalid types in assignment: cannot assign %s to %s", ty2, ty1); + } + else + compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1); + } + } + else + { + if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) && + exprs[0]->m_vtype == TYPE_FIELD && + exprs[0]->m_next->m_vtype == TYPE_VECTOR) + { + assignop = type_store_instr[TYPE_VECTOR]; + } + else { + assignop = type_store_instr[exprs[0]->m_vtype]; + } + + if (assignop == VINSTR_END) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1); + } + else if (!exprs[0]->compareType(*exprs[1])) + { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + if (OPTS_FLAG(ASSIGN_FUNCTION_TYPES) && + exprs[0]->m_vtype == TYPE_FUNCTION && + exprs[1]->m_vtype == TYPE_FUNCTION) + { + (void)!compile_warning(ctx, WARN_ASSIGN_FUNCTION_TYPES, + "invalid types in assignment: cannot assign %s to %s", ty2, ty1); + } + else + compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1); + } + } + (void)check_write_to(ctx, exprs[0]); + /* When we're a vector of part of an entity field we use STOREP */ + if (ast_istype(exprs[0], ast_member) && ast_istype(((ast_member*)exprs[0])->m_owner, ast_entfield)) + assignop = INSTR_STOREP_F; + out = new ast_store(ctx, assignop, exprs[0], exprs[1]); + break; + case opid3('+','+','P'): + case opid3('-','-','P'): + /* prefix ++ */ + if (exprs[0]->m_vtype != TYPE_FLOAT) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + compile_error(exprs[0]->m_context, "invalid type for prefix increment: %s", ty1); + return false; + } + if (op->id == opid3('+','+','P')) + addop = INSTR_ADD_F; + else + addop = INSTR_SUB_F; + (void)check_write_to(exprs[0]->m_context, exprs[0]); + if (ast_istype(exprs[0], ast_entfield)) { + out = new ast_binstore(ctx, INSTR_STOREP_F, addop, + exprs[0], + parser->m_fold.imm_float(1)); + } else { + out = new ast_binstore(ctx, INSTR_STORE_F, addop, + exprs[0], + parser->m_fold.imm_float(1)); + } + break; + case opid3('S','+','+'): + case opid3('S','-','-'): + /* prefix ++ */ + if (exprs[0]->m_vtype != TYPE_FLOAT) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + compile_error(exprs[0]->m_context, "invalid type for suffix increment: %s", ty1); + return false; + } + if (op->id == opid3('S','+','+')) { + addop = INSTR_ADD_F; + subop = INSTR_SUB_F; + } else { + addop = INSTR_SUB_F; + subop = INSTR_ADD_F; + } + (void)check_write_to(exprs[0]->m_context, exprs[0]); + if (ast_istype(exprs[0], ast_entfield)) { + out = new ast_binstore(ctx, INSTR_STOREP_F, addop, + exprs[0], + parser->m_fold.imm_float(1)); + } else { + out = new ast_binstore(ctx, INSTR_STORE_F, addop, + exprs[0], + parser->m_fold.imm_float(1)); + } + if (!out) + return false; + out = fold::binary(ctx, subop, + out, + parser->m_fold.imm_float(1)); + + break; + case opid2('+','='): + case opid2('-','='): + if (exprs[0]->m_vtype != exprs[1]->m_vtype || + (exprs[0]->m_vtype != TYPE_VECTOR && exprs[0]->m_vtype != TYPE_FLOAT) ) + { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s", + ty1, ty2); + return false; + } + (void)check_write_to(ctx, exprs[0]); + if (ast_istype(exprs[0], ast_entfield)) + assignop = type_storep_instr[exprs[0]->m_vtype]; + else + assignop = type_store_instr[exprs[0]->m_vtype]; + switch (exprs[0]->m_vtype) { + case TYPE_FLOAT: + out = new ast_binstore(ctx, assignop, + (op->id == opid2('+','=') ? INSTR_ADD_F : INSTR_SUB_F), + exprs[0], exprs[1]); + break; + case TYPE_VECTOR: + out = new ast_binstore(ctx, assignop, + (op->id == opid2('+','=') ? INSTR_ADD_V : INSTR_SUB_V), + exprs[0], exprs[1]); + break; + default: + compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s", + type_name[exprs[0]->m_vtype], + type_name[exprs[1]->m_vtype]); + return false; + }; + break; + case opid2('*','='): + case opid2('/','='): + if (exprs[1]->m_vtype != TYPE_FLOAT || + !(exprs[0]->m_vtype == TYPE_FLOAT || + exprs[0]->m_vtype == TYPE_VECTOR)) + { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types used in expression: %s and %s", + ty1, ty2); + return false; + } + (void)check_write_to(ctx, exprs[0]); + if (ast_istype(exprs[0], ast_entfield)) + assignop = type_storep_instr[exprs[0]->m_vtype]; + else + assignop = type_store_instr[exprs[0]->m_vtype]; + switch (exprs[0]->m_vtype) { + case TYPE_FLOAT: + out = new ast_binstore(ctx, assignop, + (op->id == opid2('*','=') ? INSTR_MUL_F : INSTR_DIV_F), + exprs[0], exprs[1]); + break; + case TYPE_VECTOR: + if (op->id == opid2('*','=')) { + out = new ast_binstore(ctx, assignop, INSTR_MUL_VF, + exprs[0], exprs[1]); + } else { + out = fold::binary(ctx, INSTR_DIV_F, + parser->m_fold.imm_float(1), + exprs[1]); + if (!out) { + compile_error(ctx, "internal error: failed to generate division"); + return false; + } + out = new ast_binstore(ctx, assignop, INSTR_MUL_VF, + exprs[0], out); + } + break; + default: + compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s", + type_name[exprs[0]->m_vtype], + type_name[exprs[1]->m_vtype]); + return false; + }; + break; + case opid2('&','='): + case opid2('|','='): + case opid2('^','='): + if (NotSameType(TYPE_FLOAT) && NotSameType(TYPE_VECTOR)) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types used in expression: %s and %s", + ty1, ty2); + return false; + } + (void)check_write_to(ctx, exprs[0]); + if (ast_istype(exprs[0], ast_entfield)) + assignop = type_storep_instr[exprs[0]->m_vtype]; + else + assignop = type_store_instr[exprs[0]->m_vtype]; + if (exprs[0]->m_vtype == TYPE_FLOAT) + out = new ast_binstore(ctx, assignop, + (op->id == opid2('^','=') ? VINSTR_BITXOR : op->id == opid2('&','=') ? INSTR_BITAND : INSTR_BITOR), + exprs[0], exprs[1]); + else + out = new ast_binstore(ctx, assignop, + (op->id == opid2('^','=') ? VINSTR_BITXOR_V : op->id == opid2('&','=') ? VINSTR_BITAND_V : VINSTR_BITOR_V), + exprs[0], exprs[1]); + break; + case opid3('&','~','='): + /* This is like: a &= ~(b); + * But QC has no bitwise-not, so we implement it as + * a -= a & (b); + */ + if (NotSameType(TYPE_FLOAT) && NotSameType(TYPE_VECTOR)) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types used in expression: %s and %s", + ty1, ty2); + return false; + } + if (ast_istype(exprs[0], ast_entfield)) + assignop = type_storep_instr[exprs[0]->m_vtype]; + else + assignop = type_store_instr[exprs[0]->m_vtype]; + if (exprs[0]->m_vtype == TYPE_FLOAT) + out = fold::binary(ctx, INSTR_BITAND, exprs[0], exprs[1]); + else + out = fold::binary(ctx, VINSTR_BITAND_V, exprs[0], exprs[1]); + if (!out) + return false; + (void)check_write_to(ctx, exprs[0]); + if (exprs[0]->m_vtype == TYPE_FLOAT) + asbinstore = new ast_binstore(ctx, assignop, INSTR_SUB_F, exprs[0], out); + else + asbinstore = new ast_binstore(ctx, assignop, INSTR_SUB_V, exprs[0], out); + asbinstore->m_keep_dest = true; + out = asbinstore; + break; + + case opid3('l', 'e', 'n'): + if (exprs[0]->m_vtype != TYPE_STRING && exprs[0]->m_vtype != TYPE_ARRAY) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + compile_error(exprs[0]->m_context, "invalid type for length operator: %s", ty1); + return false; + } + /* strings must be const, arrays are statically sized */ + if (exprs[0]->m_vtype == TYPE_STRING && + !(((ast_value*)exprs[0])->m_hasvalue && ((ast_value*)exprs[0])->m_cvq == CV_CONST)) + { + compile_error(exprs[0]->m_context, "operand of length operator not a valid constant expression"); + return false; + } + out = parser->m_fold.op(op, exprs); + break; + + case opid2('~', 'P'): + if (exprs[0]->m_vtype != TYPE_FLOAT && exprs[0]->m_vtype != TYPE_VECTOR) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + compile_error(exprs[0]->m_context, "invalid type for bit not: %s", ty1); + return false; + } + if (!(out = parser->m_fold.op(op, exprs))) { + if (exprs[0]->m_vtype == TYPE_FLOAT) { + out = fold::binary(ctx, INSTR_SUB_F, parser->m_fold.imm_float(2), exprs[0]); + } else { + out = fold::binary(ctx, INSTR_SUB_V, parser->m_fold.imm_vector(1), exprs[0]); + } + } + break; + } +#undef NotSameType + if (!out) { + compile_error(ctx, "failed to apply operator %s", op->op); + return false; + } + + sy->out.push_back(syexp(ctx, out)); + return true; +} + +static bool parser_close_call(parser_t *parser, shunt *sy) +{ + /* was a function call */ + ast_expression *fun; + ast_value *funval = nullptr; + ast_call *call; + + size_t fid; + size_t paramcount, i; + bool fold = true; + + fid = sy->ops.back().off; + sy->ops.pop_back(); + + /* out[fid] is the function + * everything above is parameters... + */ + if (sy->argc.empty()) { + parseerror(parser, "internal error: no argument counter available"); + return false; + } + + paramcount = sy->argc.back(); + sy->argc.pop_back(); + + if (sy->out.size() < fid) { + parseerror(parser, "internal error: broken function call %zu < %zu+%zu\n", + sy->out.size(), + fid, + paramcount); + return false; + } + + /* + * TODO handle this at the intrinsic level with an ast_intrinsic + * node and codegen. + */ + if ((fun = sy->out[fid].out) == parser->m_intrin.debug_typestring()) { + char ty[1024]; + if (fid+2 != sy->out.size() || sy->out.back().block) { + parseerror(parser, "intrinsic __builtin_debug_typestring requires exactly 1 parameter"); + return false; + } + ast_type_to_string(sy->out.back().out, ty, sizeof(ty)); + ast_unref(sy->out.back().out); + sy->out[fid] = syexp(sy->out.back().out->m_context, + parser->m_fold.constgen_string(ty, false)); + sy->out.pop_back(); + return true; + } + + /* + * Now we need to determine if the function that is being called is + * an intrinsic so we can evaluate if the arguments to it are constant + * and than fruitfully fold them. + */ +#define fold_can_1(X) \ + (ast_istype(((X)), ast_value) && (X)->m_hasvalue && ((X)->m_cvq == CV_CONST) && \ + ((X))->m_vtype != TYPE_FUNCTION) + + if (fid + 1 < sy->out.size()) + ++paramcount; + + for (i = 0; i < paramcount; ++i) { + if (!fold_can_1((ast_value*)sy->out[fid + 1 + i].out)) { + fold = false; + break; + } + } + + /* + * All is well which ends well, if we make it into here we can ignore the + * intrinsic call and just evaluate it i.e constant fold it. + */ + if (fold && ast_istype(fun, ast_value) && ((ast_value*)fun)->m_intrinsic) { + ast_expression **exprs = nullptr; + ast_expression *foldval = nullptr; + + for (i = 0; i < paramcount; i++) + vec_push(exprs, sy->out[fid+1 + i].out); + + if (!(foldval = parser->m_intrin.do_fold((ast_value*)fun, exprs))) { + vec_free(exprs); + goto fold_leave; + } + + /* + * Blub: what sorts of unreffing and resizing of + * sy->out should I be doing here? + */ + sy->out[fid] = syexp(foldval->m_context, foldval); + sy->out.erase(sy->out.end() - paramcount, sy->out.end()); + vec_free(exprs); + + return true; + } + + fold_leave: + call = ast_call::make(sy->ops[sy->ops.size()].ctx, fun); + + if (!call) + return false; + + if (fid+1 + paramcount != sy->out.size()) { + parseerror(parser, "internal error: parameter count mismatch: (%zu+1+%zu), %zu", + fid, + paramcount, + sy->out.size()); + return false; + } + + for (i = 0; i < paramcount; ++i) + call->m_params.push_back(sy->out[fid+1 + i].out); + sy->out.erase(sy->out.end() - paramcount, sy->out.end()); + (void)!call->checkTypes(parser->function->m_function_type->m_varparam); + if (parser->max_param_count < paramcount) + parser->max_param_count = paramcount; + + if (ast_istype(fun, ast_value)) { + funval = (ast_value*)fun; + if ((fun->m_flags & AST_FLAG_VARIADIC) && + !(/*funval->m_cvq == CV_CONST && */ funval->m_hasvalue && funval->m_constval.vfunc->m_builtin)) + { + call->m_va_count = parser->m_fold.constgen_float((qcfloat_t)paramcount, false); + } + } + + /* overwrite fid, the function, with a call */ + sy->out[fid] = syexp(call->m_context, call); + + if (fun->m_vtype != TYPE_FUNCTION) { + parseerror(parser, "not a function (%s)", type_name[fun->m_vtype]); + return false; + } + + if (!fun->m_next) { + parseerror(parser, "could not determine function return type"); + return false; + } else { + ast_value *fval = (ast_istype(fun, ast_value) ? ((ast_value*)fun) : nullptr); + + if (fun->m_flags & AST_FLAG_DEPRECATED) { + if (!fval) { + return !parsewarning(parser, WARN_DEPRECATED, + "call to function (which is marked deprecated)\n", + "-> it has been declared here: %s:%i", + fun->m_context.file, fun->m_context.line); + } + if (!fval->m_desc.length()) { + return !parsewarning(parser, WARN_DEPRECATED, + "call to `%s` (which is marked deprecated)\n" + "-> `%s` declared here: %s:%i", + fval->m_name, fval->m_name, fun->m_context.file, fun->m_context.line); + } + return !parsewarning(parser, WARN_DEPRECATED, + "call to `%s` (deprecated: %s)\n" + "-> `%s` declared here: %s:%i", + fval->m_name, fval->m_desc, fval->m_name, fun->m_context.file, + fun->m_context.line); + } + + if (fun->m_type_params.size() != paramcount && + !((fun->m_flags & AST_FLAG_VARIADIC) && + fun->m_type_params.size() < paramcount)) + { + const char *fewmany = (fun->m_type_params.size() > paramcount) ? "few" : "many"; + if (fval) + return !parsewarning(parser, WARN_INVALID_PARAMETER_COUNT, + "too %s parameters for call to %s: expected %i, got %i\n" + " -> `%s` has been declared here: %s:%i", + fewmany, fval->m_name, (int)fun->m_type_params.size(), (int)paramcount, + fval->m_name, fun->m_context.file, (int)fun->m_context.line); + else + return !parsewarning(parser, WARN_INVALID_PARAMETER_COUNT, + "too %s parameters for function call: expected %i, got %i\n" + " -> it has been declared here: %s:%i", + fewmany, (int)fun->m_type_params.size(), (int)paramcount, + fun->m_context.file, (int)fun->m_context.line); + } + } + + return true; +} + +static bool parser_close_paren(parser_t *parser, shunt *sy) +{ + if (sy->ops.empty()) { + parseerror(parser, "unmatched closing paren"); + return false; + } + + while (sy->ops.size()) { + if (sy->ops.back().isparen) { + if (sy->paren.back() == PAREN_FUNC) { + sy->paren.pop_back(); + if (!parser_close_call(parser, sy)) + return false; + break; + } + if (sy->paren.back() == PAREN_EXPR) { + sy->paren.pop_back(); + if (sy->out.empty()) { + compile_error(sy->ops.back().ctx, "empty paren expression"); + sy->ops.pop_back(); + return false; + } + sy->ops.pop_back(); + break; + } + if (sy->paren.back() == PAREN_INDEX) { + sy->paren.pop_back(); + // pop off the parenthesis + sy->ops.pop_back(); + /* then apply the index operator */ + if (!parser_sy_apply_operator(parser, sy)) + return false; + break; + } + if (sy->paren.back() == PAREN_TERNARY1) { + sy->paren.back() = PAREN_TERNARY2; + // pop off the parenthesis + sy->ops.pop_back(); + break; + } + compile_error(sy->ops.back().ctx, "invalid parenthesis"); + return false; + } + if (!parser_sy_apply_operator(parser, sy)) + return false; + } + return true; +} + +static void parser_reclassify_token(parser_t *parser) +{ + size_t i; + if (parser->tok >= TOKEN_START) + return; + for (i = 0; i < operator_count; ++i) { + if (!strcmp(parser_tokval(parser), operators[i].op)) { + parser->tok = TOKEN_OPERATOR; + return; + } + } +} + +static ast_expression* parse_vararg_do(parser_t *parser) +{ + ast_expression *idx, *out; + ast_value *typevar; + ast_value *funtype = parser->function->m_function_type; + lex_ctx_t ctx = parser_ctx(parser); + + if (!parser->function->m_varargs) { + parseerror(parser, "function has no variable argument list"); + return nullptr; + } + + if (!parser_next(parser) || parser->tok != '(') { + parseerror(parser, "expected parameter index and type in parenthesis"); + return nullptr; + } + if (!parser_next(parser)) { + parseerror(parser, "error parsing parameter index"); + return nullptr; + } + + idx = parse_expression_leave(parser, true, false, false); + if (!idx) + return nullptr; + + if (parser->tok != ',') { + if (parser->tok != ')') { + ast_unref(idx); + parseerror(parser, "expected comma after parameter index"); + return nullptr; + } + // vararg piping: ...(start) + out = new ast_argpipe(ctx, idx); + return out; + } + + if (!parser_next(parser) || (parser->tok != TOKEN_IDENT && parser->tok != TOKEN_TYPENAME)) { + ast_unref(idx); + parseerror(parser, "expected typename for vararg"); + return nullptr; + } + + typevar = parse_typename(parser, nullptr, nullptr, nullptr); + if (!typevar) { + ast_unref(idx); + return nullptr; + } + + if (parser->tok != ')') { + ast_unref(idx); + delete typevar; + parseerror(parser, "expected closing paren"); + return nullptr; + } + + if (funtype->m_varparam && + !typevar->compareType(*funtype->m_varparam)) + { + char ty1[1024]; + char ty2[1024]; + ast_type_to_string(typevar, ty1, sizeof(ty1)); + ast_type_to_string(funtype->m_varparam, ty2, sizeof(ty2)); + compile_error(typevar->m_context, + "function was declared to take varargs of type `%s`, requested type is: %s", + ty2, ty1); + } + + out = ast_array_index::make(ctx, parser->function->m_varargs.get(), idx); + out->adoptType(*typevar); + delete typevar; + return out; +} + +static ast_expression* parse_vararg(parser_t *parser) +{ + bool old_noops = parser->lex->flags.noops; + + ast_expression *out; + + parser->lex->flags.noops = true; + out = parse_vararg_do(parser); + + parser->lex->flags.noops = old_noops; + return out; +} + +/* not to be exposed */ +bool ftepp_predef_exists(const char *name); +static bool parse_sya_operand(parser_t *parser, shunt *sy, bool with_labels) +{ + if (OPTS_FLAG(TRANSLATABLE_STRINGS) && + parser->tok == TOKEN_IDENT && + !strcmp(parser_tokval(parser), "_")) + { + /* a translatable string */ + ast_value *val; + + parser->lex->flags.noops = true; + if (!parser_next(parser) || parser->tok != '(') { + parseerror(parser, "use _(\"string\") to create a translatable string constant"); + return false; + } + parser->lex->flags.noops = false; + if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) { + parseerror(parser, "expected a constant string in translatable-string extension"); + return false; + } + val = (ast_value*)parser->m_fold.constgen_string(parser_tokval(parser), true); + if (!val) + return false; + sy->out.push_back(syexp(parser_ctx(parser), val)); + + if (!parser_next(parser) || parser->tok != ')') { + parseerror(parser, "expected closing paren after translatable string"); + return false; + } + return true; + } + else if (parser->tok == TOKEN_DOTS) + { + ast_expression *va; + if (!OPTS_FLAG(VARIADIC_ARGS)) { + parseerror(parser, "cannot access varargs (try -fvariadic-args)"); + return false; + } + va = parse_vararg(parser); + if (!va) + return false; + sy->out.push_back(syexp(parser_ctx(parser), va)); + return true; + } + else if (parser->tok == TOKEN_FLOATCONST) { + ast_expression *val = parser->m_fold.constgen_float((parser_token(parser)->constval.f), false); + if (!val) + return false; + sy->out.push_back(syexp(parser_ctx(parser), val)); + return true; + } + else if (parser->tok == TOKEN_INTCONST || parser->tok == TOKEN_CHARCONST) { + ast_expression *val = parser->m_fold.constgen_float((qcfloat_t)(parser_token(parser)->constval.i), false); + if (!val) + return false; + sy->out.push_back(syexp(parser_ctx(parser), val)); + return true; + } + else if (parser->tok == TOKEN_STRINGCONST) { + ast_expression *val = parser->m_fold.constgen_string(parser_tokval(parser), false); + if (!val) + return false; + sy->out.push_back(syexp(parser_ctx(parser), val)); + return true; + } + else if (parser->tok == TOKEN_VECTORCONST) { + ast_expression *val = parser->m_fold.constgen_vector(parser_token(parser)->constval.v); + if (!val) + return false; + sy->out.push_back(syexp(parser_ctx(parser), val)); + return true; + } + else if (parser->tok == TOKEN_IDENT) + { + const char *ctoken = parser_tokval(parser); + ast_expression *prev = sy->out.size() ? sy->out.back().out : nullptr; + ast_expression *var; + /* a_vector.{x,y,z} */ + if (sy->ops.empty() || + !sy->ops.back().etype || + operators[sy->ops.back().etype-1].id != opid1('.')) + { + /* When adding more intrinsics, fix the above condition */ + prev = nullptr; + } + if (prev && prev->m_vtype == TYPE_VECTOR && ctoken[0] >= 'x' && ctoken[0] <= 'z' && !ctoken[1]) + { + var = parser->const_vec[ctoken[0]-'x']; + } else { + var = parser_find_var(parser, parser_tokval(parser)); + if (!var) + var = parser_find_field(parser, parser_tokval(parser)); + } + if (!var && with_labels) { + var = parser_find_label(parser, parser_tokval(parser)); + if (!with_labels) { + ast_label *lbl = new ast_label(parser_ctx(parser), parser_tokval(parser), true); + var = lbl; + parser->labels.push_back(lbl); + } + } + if (!var && !strcmp(parser_tokval(parser), "__FUNC__")) + var = parser->m_fold.constgen_string(parser->function->m_name, false); + if (!var) { + /* + * 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. + */ + if (!strncmp(parser_tokval(parser), "__builtin_", 10)) { + var = parser->m_intrin.func(parser_tokval(parser)); + } + + /* + * Try it again, intrin_func deals with the alias method as well + * the first one masks for __builtin though, we emit warning here. + */ + if (!var) { + if ((var = parser->m_intrin.func(parser_tokval(parser)))) { + (void)!compile_warning( + parser_ctx(parser), + WARN_BUILTINS, + "using implicitly defined builtin `__builtin_%s' for `%s'", + parser_tokval(parser), + parser_tokval(parser) + ); + } + } + + + if (!var) { + /* + * sometimes people use preprocessing predefs without enabling them + * i've done this thousands of times already myself. Lets check for + * it in the predef table. And diagnose it better :) + */ + if (!OPTS_FLAG(FTEPP_PREDEFS) && ftepp_predef_exists(parser_tokval(parser))) { + parseerror(parser, "unexpected identifier: %s (use -fftepp-predef to enable pre-defined macros)", parser_tokval(parser)); + return false; + } + + parseerror(parser, "unexpected identifier: %s", parser_tokval(parser)); + return false; + } + } + else + { + if (ast_istype(var, ast_value)) { + ((ast_value*)var)->m_uses++; + } + else if (ast_istype(var, ast_member)) { + ast_member *mem = (ast_member*)var; + if (ast_istype(mem->m_owner, ast_value)) + ((ast_value*)(mem->m_owner))->m_uses++; + } + } + sy->out.push_back(syexp(parser_ctx(parser), var)); + return true; + } + parseerror(parser, "unexpected token `%s`", parser_tokval(parser)); + return false; +} + +static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue, bool with_labels) +{ + ast_expression *expr = nullptr; + shunt sy; + bool wantop = false; + /* only warn once about an assignment in a truth value because the current code + * would trigger twice on: if(a = b && ...), once for the if-truth-value, once for the && part + */ + bool warn_parenthesis = true; + + /* count the parens because an if starts with one, so the + * end of a condition is an unmatched closing paren + */ + int ternaries = 0; + + memset(&sy, 0, sizeof(sy)); + + parser->lex->flags.noops = false; + + parser_reclassify_token(parser); + + while (true) + { + if (parser->tok == TOKEN_TYPENAME) { + parseerror(parser, "unexpected typename `%s`", parser_tokval(parser)); + goto onerr; + } + + if (parser->tok == TOKEN_OPERATOR) + { + /* classify the operator */ + const oper_info *op; + const oper_info *olast = nullptr; + size_t o; + for (o = 0; o < operator_count; ++o) { + if (((!(operators[o].flags & OP_PREFIX) == !!wantop)) && + /* !(operators[o].flags & OP_SUFFIX) && / * remove this */ + !strcmp(parser_tokval(parser), operators[o].op)) + { + break; + } + } + if (o == operator_count) { + compile_error(parser_ctx(parser), "unexpected operator: %s", parser_tokval(parser)); + goto onerr; + } + /* found an operator */ + op = &operators[o]; + + /* when declaring variables, a comma starts a new variable */ + if (op->id == opid1(',') && sy.paren.empty() && stopatcomma) { + /* fixup the token */ + parser->tok = ','; + break; + } + + /* a colon without a pervious question mark cannot be a ternary */ + if (!ternaries && op->id == opid2(':','?')) { + parser->tok = ':'; + break; + } + + if (op->id == opid1(',')) { + if (sy.paren.size() && sy.paren.back() == PAREN_TERNARY2) { + (void)!parsewarning(parser, WARN_TERNARY_PRECEDENCE, "suggesting parenthesis around ternary expression"); + } + } + + if (sy.ops.size() && !sy.ops.back().isparen) + olast = &operators[sy.ops.back().etype-1]; + + /* first only apply higher precedences, assoc_left+equal comes after we warn about precedence rules */ + while (olast && op->prec < olast->prec) + { + if (!parser_sy_apply_operator(parser, &sy)) + goto onerr; + if (sy.ops.size() && !sy.ops.back().isparen) + olast = &operators[sy.ops.back().etype-1]; + else + olast = nullptr; + } + +#define IsAssignOp(x) (\ + (x) == opid1('=') || \ + (x) == opid2('+','=') || \ + (x) == opid2('-','=') || \ + (x) == opid2('*','=') || \ + (x) == opid2('/','=') || \ + (x) == opid2('%','=') || \ + (x) == opid2('&','=') || \ + (x) == opid2('|','=') || \ + (x) == opid3('&','~','=') \ + ) + if (warn_parenthesis) { + if ( (olast && IsAssignOp(olast->id) && (op->id == opid2('&','&') || op->id == opid2('|','|'))) || + (olast && IsAssignOp(op->id) && (olast->id == opid2('&','&') || olast->id == opid2('|','|'))) || + (truthvalue && sy.paren.empty() && IsAssignOp(op->id)) + ) + { + (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around assignment used as truth value"); + warn_parenthesis = false; + } + + if (olast && olast->id != op->id) { + if ((op->id == opid1('&') || op->id == opid1('|') || op->id == opid1('^')) && + (olast->id == opid1('&') || olast->id == opid1('|') || olast->id == opid1('^'))) + { + (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around bitwise operations"); + warn_parenthesis = false; + } + else if ((op->id == opid2('&','&') || op->id == opid2('|','|')) && + (olast->id == opid2('&','&') || olast->id == opid2('|','|'))) + { + (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around logical operations"); + warn_parenthesis = false; + } + } + } + + while (olast && ( + (op->prec < olast->prec) || + (op->assoc == ASSOC_LEFT && op->prec <= olast->prec) ) ) + { + if (!parser_sy_apply_operator(parser, &sy)) + goto onerr; + if (sy.ops.size() && !sy.ops.back().isparen) + olast = &operators[sy.ops.back().etype-1]; + else + olast = nullptr; + } + + if (op->id == opid1('(')) { + if (wantop) { + size_t sycount = sy.out.size(); + /* we expected an operator, this is the function-call operator */ + sy.paren.push_back(PAREN_FUNC); + sy.ops.push_back(syparen(parser_ctx(parser), sycount-1)); + sy.argc.push_back(0); + } else { + sy.paren.push_back(PAREN_EXPR); + sy.ops.push_back(syparen(parser_ctx(parser), 0)); + } + wantop = false; + } else if (op->id == opid1('[')) { + if (!wantop) { + parseerror(parser, "unexpected array subscript"); + goto onerr; + } + sy.paren.push_back(PAREN_INDEX); + /* push both the operator and the paren, this makes life easier */ + sy.ops.push_back(syop(parser_ctx(parser), op)); + sy.ops.push_back(syparen(parser_ctx(parser), 0)); + wantop = false; + } else if (op->id == opid2('?',':')) { + sy.ops.push_back(syop(parser_ctx(parser), op)); + sy.ops.push_back(syparen(parser_ctx(parser), 0)); + wantop = false; + ++ternaries; + sy.paren.push_back(PAREN_TERNARY1); + } else if (op->id == opid2(':','?')) { + if (sy.paren.empty()) { + parseerror(parser, "unexpected colon outside ternary expression (missing parenthesis?)"); + goto onerr; + } + if (sy.paren.back() != PAREN_TERNARY1) { + parseerror(parser, "unexpected colon outside ternary expression (missing parenthesis?)"); + goto onerr; + } + if (!parser_close_paren(parser, &sy)) + goto onerr; + sy.ops.push_back(syop(parser_ctx(parser), op)); + wantop = false; + --ternaries; + } else { + sy.ops.push_back(syop(parser_ctx(parser), op)); + wantop = !!(op->flags & OP_SUFFIX); + } + } + else if (parser->tok == ')') { + while (sy.paren.size() && sy.paren.back() == PAREN_TERNARY2) { + if (!parser_sy_apply_operator(parser, &sy)) + goto onerr; + } + if (sy.paren.empty()) + break; + if (wantop) { + if (sy.paren.back() == PAREN_TERNARY1) { + parseerror(parser, "mismatched parentheses (closing paren in ternary expression?)"); + goto onerr; + } + if (!parser_close_paren(parser, &sy)) + goto onerr; + } else { + /* must be a function call without parameters */ + if (sy.paren.back() != PAREN_FUNC) { + parseerror(parser, "closing paren in invalid position"); + goto onerr; + } + if (!parser_close_paren(parser, &sy)) + goto onerr; + } + wantop = true; + } + else if (parser->tok == '(') { + parseerror(parser, "internal error: '(' should be classified as operator"); + goto onerr; + } + else if (parser->tok == '[') { + parseerror(parser, "internal error: '[' should be classified as operator"); + goto onerr; + } + else if (parser->tok == ']') { + while (sy.paren.size() && sy.paren.back() == PAREN_TERNARY2) { + if (!parser_sy_apply_operator(parser, &sy)) + goto onerr; + } + if (sy.paren.empty()) + break; + if (sy.paren.back() != PAREN_INDEX) { + parseerror(parser, "mismatched parentheses, unexpected ']'"); + goto onerr; + } + if (!parser_close_paren(parser, &sy)) + goto onerr; + wantop = true; + } + else if (!wantop) { + if (!parse_sya_operand(parser, &sy, with_labels)) + goto onerr; + wantop = true; + } + else { + /* in this case we might want to allow constant string concatenation */ + bool concatenated = false; + if (parser->tok == TOKEN_STRINGCONST && sy.out.size()) { + ast_expression *lexpr = sy.out.back().out; + if (ast_istype(lexpr, ast_value)) { + ast_value *last = (ast_value*)lexpr; + if (last->m_isimm == true && last->m_cvq == CV_CONST && + last->m_hasvalue && last->m_vtype == TYPE_STRING) + { + char *newstr = nullptr; + util_asprintf(&newstr, "%s%s", last->m_constval.vstring, parser_tokval(parser)); + sy.out.back().out = parser->m_fold.constgen_string(newstr, false); + mem_d(newstr); + concatenated = true; + } + } + } + if (!concatenated) { + parseerror(parser, "expected operator or end of statement"); + goto onerr; + } + } + + if (!parser_next(parser)) { + goto onerr; + } + if (parser->tok == ';' || + ((sy.paren.empty() || (sy.paren.size() == 1 && sy.paren.back() == PAREN_TERNARY2)) && + (parser->tok == ']' || parser->tok == ')' || parser->tok == '}'))) + { + break; + } + } + + while (sy.ops.size()) { + if (!parser_sy_apply_operator(parser, &sy)) + goto onerr; + } + + parser->lex->flags.noops = true; + if (sy.out.size() != 1) { + parseerror(parser, "expression expected"); + expr = nullptr; + } else + expr = sy.out[0].out; + if (sy.paren.size()) { + parseerror(parser, "internal error: sy.paren.size() = %zu", sy.paren.size()); + return nullptr; + } + return expr; + +onerr: + parser->lex->flags.noops = true; + for (auto &it : sy.out) + if (it.out) ast_unref(it.out); + return nullptr; +} + +static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool with_labels) +{ + ast_expression *e = parse_expression_leave(parser, stopatcomma, false, with_labels); + if (!e) + return nullptr; + if (parser->tok != ';') { + parseerror(parser, "semicolon expected after expression"); + ast_unref(e); + return nullptr; + } + if (!parser_next(parser)) { + ast_unref(e); + return nullptr; + } + return e; +} + +static void parser_enterblock(parser_t *parser) +{ + vec_push(parser->variables, util_htnew(PARSER_HT_SIZE)); + vec_push(parser->_blocklocals, vec_size(parser->_locals)); + vec_push(parser->typedefs, util_htnew(TYPEDEF_HT_SIZE)); + vec_push(parser->_blocktypedefs, vec_size(parser->_typedefs)); + vec_push(parser->_block_ctx, parser_ctx(parser)); +} + +static bool parser_leaveblock(parser_t *parser) +{ + bool rv = true; + size_t locals, typedefs; + + if (vec_size(parser->variables) <= PARSER_HT_LOCALS) { + parseerror(parser, "internal error: parser_leaveblock with no block"); + return false; + } + + util_htdel(vec_last(parser->variables)); + + vec_pop(parser->variables); + if (!vec_size(parser->_blocklocals)) { + parseerror(parser, "internal error: parser_leaveblock with no block (2)"); + return false; + } + + locals = vec_last(parser->_blocklocals); + vec_pop(parser->_blocklocals); + while (vec_size(parser->_locals) != locals) { + ast_expression *e = vec_last(parser->_locals); + ast_value *v = (ast_value*)e; + vec_pop(parser->_locals); + if (ast_istype(e, ast_value) && !v->m_uses) { + if (compile_warning(v->m_context, WARN_UNUSED_VARIABLE, "unused variable: `%s`", v->m_name)) + rv = false; + } + } + + typedefs = vec_last(parser->_blocktypedefs); + while (vec_size(parser->_typedefs) != typedefs) { + delete vec_last(parser->_typedefs); + vec_pop(parser->_typedefs); + } + util_htdel(vec_last(parser->typedefs)); + vec_pop(parser->typedefs); + + vec_pop(parser->_block_ctx); + + return rv; +} + +static void parser_addlocal(parser_t *parser, const char *name, ast_expression *e) +{ + vec_push(parser->_locals, e); + util_htset(vec_last(parser->variables), name, (void*)e); +} +static void parser_addlocal(parser_t *parser, const std::string &name, ast_expression *e) { + return parser_addlocal(parser, name.c_str(), e); +} + +static void parser_addglobal(parser_t *parser, const char *name, ast_expression *e) +{ + parser->globals.push_back(e); + util_htset(parser->htglobals, name, e); +} +static void parser_addglobal(parser_t *parser, const std::string &name, ast_expression *e) { + return parser_addglobal(parser, name.c_str(), e); +} + +static ast_expression* process_condition(parser_t *parser, ast_expression *cond, bool *_ifnot) +{ + bool ifnot = false; + ast_unary *unary; + ast_expression *prev; + + if (cond->m_vtype == TYPE_VOID || cond->m_vtype >= TYPE_VARIANT) { + char ty[1024]; + ast_type_to_string(cond, ty, sizeof(ty)); + compile_error(cond->m_context, "invalid type for if() condition: %s", ty); + } + + if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && cond->m_vtype == TYPE_STRING) + { + prev = cond; + cond = ast_unary::make(cond->m_context, INSTR_NOT_S, cond); + if (!cond) { + ast_unref(prev); + parseerror(parser, "internal error: failed to process condition"); + return nullptr; + } + ifnot = !ifnot; + } + else if (OPTS_FLAG(CORRECT_LOGIC) && cond->m_vtype == TYPE_VECTOR) + { + /* vector types need to be cast to true booleans */ + ast_binary *bin = (ast_binary*)cond; + if (!OPTS_FLAG(PERL_LOGIC) || !ast_istype(cond, ast_binary) || !(bin->m_op == INSTR_AND || bin->m_op == INSTR_OR)) + { + /* in perl-logic, AND and OR take care of the -fcorrect-logic */ + prev = cond; + cond = ast_unary::make(cond->m_context, INSTR_NOT_V, cond); + if (!cond) { + ast_unref(prev); + parseerror(parser, "internal error: failed to process condition"); + return nullptr; + } + ifnot = !ifnot; + } + } + + unary = (ast_unary*)cond; + /* ast_istype dereferences cond, should test here for safety */ + while (cond && ast_istype(cond, ast_unary) && unary->m_op == INSTR_NOT_F) + { + cond = unary->m_operand; + unary->m_operand = nullptr; + delete unary; + ifnot = !ifnot; + unary = (ast_unary*)cond; + } + + if (!cond) + parseerror(parser, "internal error: failed to process condition"); + + if (ifnot) *_ifnot = !*_ifnot; + return cond; +} + +static bool parse_if(parser_t *parser, ast_block *block, ast_expression **out) +{ + ast_ifthen *ifthen; + ast_expression *cond, *ontrue = nullptr, *onfalse = nullptr; + bool ifnot = false; + + lex_ctx_t ctx = parser_ctx(parser); + + (void)block; /* not touching */ + + /* skip the 'if', parse an optional 'not' and check for an opening paren */ + if (!parser_next(parser)) { + parseerror(parser, "expected condition or 'not'"); + return false; + } + if (parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "not")) { + ifnot = true; + if (!parser_next(parser)) { + parseerror(parser, "expected condition in parenthesis"); + return false; + } + } + if (parser->tok != '(') { + parseerror(parser, "expected 'if' condition in parenthesis"); + return false; + } + /* parse into the expression */ + if (!parser_next(parser)) { + parseerror(parser, "expected 'if' condition after opening paren"); + return false; + } + /* parse the condition */ + cond = parse_expression_leave(parser, false, true, false); + if (!cond) + return false; + /* closing paren */ + if (parser->tok != ')') { + parseerror(parser, "expected closing paren after 'if' condition"); + ast_unref(cond); + return false; + } + /* parse into the 'then' branch */ + if (!parser_next(parser)) { + parseerror(parser, "expected statement for on-true branch of 'if'"); + ast_unref(cond); + return false; + } + if (!parse_statement_or_block(parser, &ontrue)) { + ast_unref(cond); + return false; + } + if (!ontrue) + ontrue = new ast_block(parser_ctx(parser)); + /* check for an else */ + if (!strcmp(parser_tokval(parser), "else")) { + /* parse into the 'else' branch */ + if (!parser_next(parser)) { + parseerror(parser, "expected on-false branch after 'else'"); + delete ontrue; + ast_unref(cond); + return false; + } + if (!parse_statement_or_block(parser, &onfalse)) { + delete ontrue; + ast_unref(cond); + return false; + } + } + + cond = process_condition(parser, cond, &ifnot); + if (!cond) { + if (ontrue) delete ontrue; + if (onfalse) delete onfalse; + return false; + } + + if (ifnot) + ifthen = new ast_ifthen(ctx, cond, onfalse, ontrue); + else + ifthen = new ast_ifthen(ctx, cond, ontrue, onfalse); + *out = ifthen; + return true; +} + +static bool parse_while_go(parser_t *parser, ast_block *block, ast_expression **out); +static bool parse_while(parser_t *parser, ast_block *block, ast_expression **out) +{ + bool rv; + char *label = nullptr; + + /* skip the 'while' and get the body */ + if (!parser_next(parser)) { + if (OPTS_FLAG(LOOP_LABELS)) + parseerror(parser, "expected loop label or 'while' condition in parenthesis"); + else + parseerror(parser, "expected 'while' condition in parenthesis"); + return false; + } + + if (parser->tok == ':') { + if (!OPTS_FLAG(LOOP_LABELS)) + parseerror(parser, "labeled loops not activated, try using -floop-labels"); + if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { + parseerror(parser, "expected loop label"); + return false; + } + label = util_strdup(parser_tokval(parser)); + if (!parser_next(parser)) { + mem_d(label); + parseerror(parser, "expected 'while' condition in parenthesis"); + return false; + } + } + + if (parser->tok != '(') { + parseerror(parser, "expected 'while' condition in parenthesis"); + return false; + } + + parser->breaks.push_back(label); + parser->continues.push_back(label); + + rv = parse_while_go(parser, block, out); + if (label) + mem_d(label); + if (parser->breaks.back() != label || parser->continues.back() != label) { + parseerror(parser, "internal error: label stack corrupted"); + rv = false; + delete *out; + *out = nullptr; + } + else { + parser->breaks.pop_back(); + parser->continues.pop_back(); + } + return rv; +} + +static bool parse_while_go(parser_t *parser, ast_block *block, ast_expression **out) +{ + ast_loop *aloop; + ast_expression *cond, *ontrue; + + bool ifnot = false; + + lex_ctx_t ctx = parser_ctx(parser); + + (void)block; /* not touching */ + + /* parse into the expression */ + if (!parser_next(parser)) { + parseerror(parser, "expected 'while' condition after opening paren"); + return false; + } + /* parse the condition */ + cond = parse_expression_leave(parser, false, true, false); + if (!cond) + return false; + /* closing paren */ + if (parser->tok != ')') { + parseerror(parser, "expected closing paren after 'while' condition"); + ast_unref(cond); + return false; + } + /* parse into the 'then' branch */ + if (!parser_next(parser)) { + parseerror(parser, "expected while-loop body"); + ast_unref(cond); + return false; + } + if (!parse_statement_or_block(parser, &ontrue)) { + ast_unref(cond); + return false; + } + + cond = process_condition(parser, cond, &ifnot); + if (!cond) { + ast_unref(ontrue); + return false; + } + aloop = new ast_loop(ctx, nullptr, cond, ifnot, nullptr, false, nullptr, ontrue); + *out = aloop; + return true; +} + +static bool parse_dowhile_go(parser_t *parser, ast_block *block, ast_expression **out); +static bool parse_dowhile(parser_t *parser, ast_block *block, ast_expression **out) +{ + bool rv; + char *label = nullptr; + + /* skip the 'do' and get the body */ + if (!parser_next(parser)) { + if (OPTS_FLAG(LOOP_LABELS)) + parseerror(parser, "expected loop label or body"); + else + parseerror(parser, "expected loop body"); + return false; + } + + if (parser->tok == ':') { + if (!OPTS_FLAG(LOOP_LABELS)) + parseerror(parser, "labeled loops not activated, try using -floop-labels"); + if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { + parseerror(parser, "expected loop label"); + return false; + } + label = util_strdup(parser_tokval(parser)); + if (!parser_next(parser)) { + mem_d(label); + parseerror(parser, "expected loop body"); + return false; + } + } + + parser->breaks.push_back(label); + parser->continues.push_back(label); + + rv = parse_dowhile_go(parser, block, out); + if (label) + mem_d(label); + if (parser->breaks.back() != label || parser->continues.back() != label) { + parseerror(parser, "internal error: label stack corrupted"); + rv = false; + delete *out; + *out = nullptr; + } + else { + parser->breaks.pop_back(); + parser->continues.pop_back(); + } + return rv; +} + +static bool parse_dowhile_go(parser_t *parser, ast_block *block, ast_expression **out) +{ + ast_loop *aloop; + ast_expression *cond, *ontrue; + + bool ifnot = false; + + lex_ctx_t ctx = parser_ctx(parser); + + (void)block; /* not touching */ + + if (!parse_statement_or_block(parser, &ontrue)) + return false; + + /* expect the "while" */ + if (parser->tok != TOKEN_KEYWORD || + strcmp(parser_tokval(parser), "while")) + { + parseerror(parser, "expected 'while' and condition"); + delete ontrue; + return false; + } + + /* skip the 'while' and check for opening paren */ + if (!parser_next(parser) || parser->tok != '(') { + parseerror(parser, "expected 'while' condition in parenthesis"); + delete ontrue; + return false; + } + /* parse into the expression */ + if (!parser_next(parser)) { + parseerror(parser, "expected 'while' condition after opening paren"); + delete ontrue; + return false; + } + /* parse the condition */ + cond = parse_expression_leave(parser, false, true, false); + if (!cond) + return false; + /* closing paren */ + if (parser->tok != ')') { + parseerror(parser, "expected closing paren after 'while' condition"); + delete ontrue; + ast_unref(cond); + return false; + } + /* parse on */ + if (!parser_next(parser) || parser->tok != ';') { + parseerror(parser, "expected semicolon after condition"); + delete ontrue; + ast_unref(cond); + return false; + } + + if (!parser_next(parser)) { + parseerror(parser, "parse error"); + delete ontrue; + ast_unref(cond); + return false; + } + + cond = process_condition(parser, cond, &ifnot); + if (!cond) { + delete ontrue; + return false; + } + aloop = new ast_loop(ctx, nullptr, nullptr, false, cond, ifnot, nullptr, ontrue); + *out = aloop; + return true; +} + +static bool parse_for_go(parser_t *parser, ast_block *block, ast_expression **out); +static bool parse_for(parser_t *parser, ast_block *block, ast_expression **out) +{ + bool rv; + char *label = nullptr; + + /* skip the 'for' and check for opening paren */ + if (!parser_next(parser)) { + if (OPTS_FLAG(LOOP_LABELS)) + parseerror(parser, "expected loop label or 'for' expressions in parenthesis"); + else + parseerror(parser, "expected 'for' expressions in parenthesis"); + return false; + } + + if (parser->tok == ':') { + if (!OPTS_FLAG(LOOP_LABELS)) + parseerror(parser, "labeled loops not activated, try using -floop-labels"); + if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { + parseerror(parser, "expected loop label"); + return false; + } + label = util_strdup(parser_tokval(parser)); + if (!parser_next(parser)) { + mem_d(label); + parseerror(parser, "expected 'for' expressions in parenthesis"); + return false; + } + } + + if (parser->tok != '(') { + parseerror(parser, "expected 'for' expressions in parenthesis"); + return false; + } + + parser->breaks.push_back(label); + parser->continues.push_back(label); + + rv = parse_for_go(parser, block, out); + if (label) + mem_d(label); + if (parser->breaks.back() != label || parser->continues.back() != label) { + parseerror(parser, "internal error: label stack corrupted"); + rv = false; + delete *out; + *out = nullptr; + } + else { + parser->breaks.pop_back(); + parser->continues.pop_back(); + } + return rv; +} +static bool parse_for_go(parser_t *parser, ast_block *block, ast_expression **out) +{ + ast_loop *aloop; + ast_expression *initexpr, *cond, *increment, *ontrue; + ast_value *typevar; + + bool ifnot = false; + + lex_ctx_t ctx = parser_ctx(parser); + + parser_enterblock(parser); + + initexpr = nullptr; + cond = nullptr; + increment = nullptr; + ontrue = nullptr; + + /* parse into the expression */ + if (!parser_next(parser)) { + parseerror(parser, "expected 'for' initializer after opening paren"); + goto onerr; + } + + typevar = nullptr; + if (parser->tok == TOKEN_IDENT) + typevar = parser_find_typedef(parser, parser_tokval(parser), 0); + + if (typevar || parser->tok == TOKEN_TYPENAME) { + if (!parse_variable(parser, block, true, CV_VAR, typevar, false, false, 0, nullptr)) + goto onerr; + } + else if (parser->tok != ';') + { + initexpr = parse_expression_leave(parser, false, false, false); + if (!initexpr) + goto onerr; + /* move on to condition */ + if (parser->tok != ';') { + parseerror(parser, "expected semicolon after for-loop initializer"); + goto onerr; + } + if (!parser_next(parser)) { + parseerror(parser, "expected for-loop condition"); + goto onerr; + } + } else if (!parser_next(parser)) { + parseerror(parser, "expected for-loop condition"); + goto onerr; + } + + /* parse the condition */ + if (parser->tok != ';') { + cond = parse_expression_leave(parser, false, true, false); + if (!cond) + goto onerr; + } + /* move on to incrementor */ + if (parser->tok != ';') { + parseerror(parser, "expected semicolon after for-loop initializer"); + goto onerr; + } + if (!parser_next(parser)) { + parseerror(parser, "expected for-loop condition"); + goto onerr; + } + + /* parse the incrementor */ + if (parser->tok != ')') { + lex_ctx_t condctx = parser_ctx(parser); + increment = parse_expression_leave(parser, false, false, false); + if (!increment) + goto onerr; + if (!increment->m_side_effects) { + if (compile_warning(condctx, WARN_EFFECTLESS_STATEMENT, "statement has no effect")) + goto onerr; + } + } + + /* closing paren */ + if (parser->tok != ')') { + parseerror(parser, "expected closing paren after 'for-loop' incrementor"); + goto onerr; + } + /* parse into the 'then' branch */ + if (!parser_next(parser)) { + parseerror(parser, "expected for-loop body"); + goto onerr; + } + if (!parse_statement_or_block(parser, &ontrue)) + goto onerr; + + if (cond) { + cond = process_condition(parser, cond, &ifnot); + if (!cond) + goto onerr; + } + aloop = new ast_loop(ctx, initexpr, cond, ifnot, nullptr, false, increment, ontrue); + *out = aloop; + + if (!parser_leaveblock(parser)) { + delete aloop; + return false; + } + return true; +onerr: + if (initexpr) ast_unref(initexpr); + if (cond) ast_unref(cond); + if (increment) ast_unref(increment); + (void)!parser_leaveblock(parser); + return false; +} + +static bool parse_return(parser_t *parser, ast_block *block, ast_expression **out) +{ + ast_expression *exp = nullptr; + ast_expression *var = nullptr; + ast_return *ret = nullptr; + ast_value *retval = parser->function->m_return_value; + ast_value *expected = parser->function->m_function_type; + + lex_ctx_t ctx = parser_ctx(parser); + + (void)block; /* not touching */ + + if (!parser_next(parser)) { + parseerror(parser, "expected return expression"); + return false; + } + + /* return assignments */ + if (parser->tok == '=') { + if (!OPTS_FLAG(RETURN_ASSIGNMENTS)) { + parseerror(parser, "return assignments not activated, try using -freturn-assigments"); + return false; + } + + if (type_store_instr[expected->m_next->m_vtype] == VINSTR_END) { + char ty1[1024]; + ast_type_to_string(expected->m_next, ty1, sizeof(ty1)); + parseerror(parser, "invalid return type: `%s'", ty1); + return false; + } + + if (!parser_next(parser)) { + parseerror(parser, "expected return assignment expression"); + return false; + } + + if (!(exp = parse_expression_leave(parser, false, false, false))) + return false; + + /* prepare the return value */ + if (!retval) { + retval = new ast_value(ctx, "#LOCAL_RETURN", TYPE_VOID); + retval->adoptType(*expected->m_next); + parser->function->m_return_value = retval; + } + + if (!exp->compareType(*retval)) { + char ty1[1024], ty2[1024]; + ast_type_to_string(exp, ty1, sizeof(ty1)); + ast_type_to_string(retval, ty2, sizeof(ty2)); + parseerror(parser, "invalid type for return value: `%s', expected `%s'", ty1, ty2); + } + + /* store to 'return' local variable */ + var = new ast_store( + ctx, + type_store_instr[expected->m_next->m_vtype], + retval, exp); + + if (!var) { + ast_unref(exp); + return false; + } + + if (parser->tok != ';') + parseerror(parser, "missing semicolon after return assignment"); + else if (!parser_next(parser)) + parseerror(parser, "parse error after return assignment"); + + *out = var; + return true; + } + + if (parser->tok != ';') { + exp = parse_expression(parser, false, false); + if (!exp) + return false; + + if (exp->m_vtype != TYPE_NIL && + exp->m_vtype != (expected)->m_next->m_vtype) + { + parseerror(parser, "return with invalid expression"); + } + + ret = new ast_return(ctx, exp); + if (!ret) { + ast_unref(exp); + return false; + } + } else { + if (!parser_next(parser)) + parseerror(parser, "parse error"); + + if (!retval && expected->m_next->m_vtype != TYPE_VOID) + { + (void)!parsewarning(parser, WARN_MISSING_RETURN_VALUES, "return without value"); + } + ret = new ast_return(ctx, retval); + } + *out = ret; + return true; +} + +static bool parse_break_continue(parser_t *parser, ast_block *block, ast_expression **out, bool is_continue) +{ + size_t i; + unsigned int levels = 0; + lex_ctx_t ctx = parser_ctx(parser); + auto &loops = (is_continue ? parser->continues : parser->breaks); + + (void)block; /* not touching */ + if (!parser_next(parser)) { + parseerror(parser, "expected semicolon or loop label"); + return false; + } + + if (loops.empty()) { + if (is_continue) + parseerror(parser, "`continue` can only be used inside loops"); + else + parseerror(parser, "`break` can only be used inside loops or switches"); + } + + if (parser->tok == TOKEN_IDENT) { + if (!OPTS_FLAG(LOOP_LABELS)) + parseerror(parser, "labeled loops not activated, try using -floop-labels"); + i = loops.size(); + while (i--) { + if (loops[i] && !strcmp(loops[i], parser_tokval(parser))) + break; + if (!i) { + parseerror(parser, "no such loop to %s: `%s`", + (is_continue ? "continue" : "break out of"), + parser_tokval(parser)); + return false; + } + ++levels; + } + if (!parser_next(parser)) { + parseerror(parser, "expected semicolon"); + return false; + } + } + + if (parser->tok != ';') { + parseerror(parser, "expected semicolon"); + return false; + } + + if (!parser_next(parser)) + parseerror(parser, "parse error"); + + *out = new ast_breakcont(ctx, is_continue, levels); + return true; +} + +/* returns true when it was a variable qualifier, false otherwise! + * on error, cvq is set to CV_WRONG + */ +struct attribute_t { + const char *name; + size_t flag; +}; + +static bool parse_qualifiers(parser_t *parser, bool with_local, int *cvq, bool *noref, bool *is_static, uint32_t *_flags, char **message) +{ + bool had_const = false; + bool had_var = false; + bool had_noref = false; + bool had_attrib = false; + bool had_static = false; + uint32_t flags = 0; + + static attribute_t attributes[] = { + { "noreturn", AST_FLAG_NORETURN }, + { "inline", AST_FLAG_INLINE }, + { "eraseable", AST_FLAG_ERASEABLE }, + { "accumulate", AST_FLAG_ACCUMULATE }, + { "last", AST_FLAG_FINAL_DECL } + }; + + *cvq = CV_NONE; + + for (;;) { + size_t i; + if (parser->tok == TOKEN_ATTRIBUTE_OPEN) { + had_attrib = true; + /* parse an attribute */ + if (!parser_next(parser)) { + parseerror(parser, "expected attribute after `[[`"); + *cvq = CV_WRONG; + return false; + } + + for (i = 0; i < GMQCC_ARRAY_COUNT(attributes); i++) { + if (!strcmp(parser_tokval(parser), attributes[i].name)) { + flags |= attributes[i].flag; + if (!parser_next(parser) || parser->tok != TOKEN_ATTRIBUTE_CLOSE) { + parseerror(parser, "`%s` attribute has no parameters, expected `]]`", + attributes[i].name); + *cvq = CV_WRONG; + return false; + } + break; + } + } + + if (i != GMQCC_ARRAY_COUNT(attributes)) + goto leave; + + + if (!strcmp(parser_tokval(parser), "noref")) { + had_noref = true; + if (!parser_next(parser) || parser->tok != TOKEN_ATTRIBUTE_CLOSE) { + parseerror(parser, "`noref` attribute has no parameters, expected `]]`"); + *cvq = CV_WRONG; + return false; + } + } + else if (!strcmp(parser_tokval(parser), "alias") && !(flags & AST_FLAG_ALIAS)) { + flags |= AST_FLAG_ALIAS; + *message = nullptr; + + if (!parser_next(parser)) { + parseerror(parser, "parse error in attribute"); + goto argerr; + } + + if (parser->tok == '(') { + if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) { + parseerror(parser, "`alias` attribute missing parameter"); + goto argerr; + } + + *message = util_strdup(parser_tokval(parser)); + + if (!parser_next(parser)) { + parseerror(parser, "parse error in attribute"); + goto argerr; + } + + if (parser->tok != ')') { + parseerror(parser, "`alias` attribute expected `)` after parameter"); + goto argerr; + } + + if (!parser_next(parser)) { + parseerror(parser, "parse error in attribute"); + goto argerr; + } + } + + if (parser->tok != TOKEN_ATTRIBUTE_CLOSE) { + parseerror(parser, "`alias` attribute expected `]]`"); + goto argerr; + } + } + else if (!strcmp(parser_tokval(parser), "deprecated") && !(flags & AST_FLAG_DEPRECATED)) { + flags |= AST_FLAG_DEPRECATED; + *message = nullptr; + + if (!parser_next(parser)) { + parseerror(parser, "parse error in attribute"); + goto argerr; + } + + if (parser->tok == '(') { + if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) { + parseerror(parser, "`deprecated` attribute missing parameter"); + goto argerr; + } + + *message = util_strdup(parser_tokval(parser)); + + if (!parser_next(parser)) { + parseerror(parser, "parse error in attribute"); + goto argerr; + } + + if(parser->tok != ')') { + parseerror(parser, "`deprecated` attribute expected `)` after parameter"); + goto argerr; + } + + if (!parser_next(parser)) { + parseerror(parser, "parse error in attribute"); + goto argerr; + } + } + /* no message */ + if (parser->tok != TOKEN_ATTRIBUTE_CLOSE) { + parseerror(parser, "`deprecated` attribute expected `]]`"); + + argerr: /* ugly */ + if (*message) mem_d(*message); + *message = nullptr; + *cvq = CV_WRONG; + return false; + } + } + else if (!strcmp(parser_tokval(parser), "coverage") && !(flags & AST_FLAG_COVERAGE)) { + flags |= AST_FLAG_COVERAGE; + if (!parser_next(parser)) { + error_in_coverage: + parseerror(parser, "parse error in coverage attribute"); + *cvq = CV_WRONG; + return false; + } + if (parser->tok == '(') { + if (!parser_next(parser)) { + bad_coverage_arg: + parseerror(parser, "invalid parameter for coverage() attribute\n" + "valid are: block"); + *cvq = CV_WRONG; + return false; + } + if (parser->tok != ')') { + do { + if (parser->tok != TOKEN_IDENT) + goto bad_coverage_arg; + if (!strcmp(parser_tokval(parser), "block")) + flags |= AST_FLAG_BLOCK_COVERAGE; + else if (!strcmp(parser_tokval(parser), "none")) + flags &= ~(AST_FLAG_COVERAGE_MASK); + else + goto bad_coverage_arg; + if (!parser_next(parser)) + goto error_in_coverage; + if (parser->tok == ',') { + if (!parser_next(parser)) + goto error_in_coverage; + } + } while (parser->tok != ')'); + } + if (parser->tok != ')' || !parser_next(parser)) + goto error_in_coverage; + } else { + /* without parameter [[coverage]] equals [[coverage(block)]] */ + flags |= AST_FLAG_BLOCK_COVERAGE; + } + } + else + { + /* Skip tokens until we hit a ]] */ + (void)!parsewarning(parser, WARN_UNKNOWN_ATTRIBUTE, "unknown attribute starting with `%s`", parser_tokval(parser)); + while (parser->tok != TOKEN_ATTRIBUTE_CLOSE) { + if (!parser_next(parser)) { + parseerror(parser, "error inside attribute"); + *cvq = CV_WRONG; + return false; + } + } + } + } + else if (with_local && !strcmp(parser_tokval(parser), "static")) + had_static = true; + else if (!strcmp(parser_tokval(parser), "const")) + had_const = true; + else if (!strcmp(parser_tokval(parser), "var")) + had_var = true; + else if (with_local && !strcmp(parser_tokval(parser), "local")) + had_var = true; + else if (!strcmp(parser_tokval(parser), "noref")) + had_noref = true; + else if (!had_const && !had_var && !had_noref && !had_attrib && !had_static && !flags) { + return false; + } + else + break; + + leave: + if (!parser_next(parser)) + goto onerr; + } + if (had_const) + *cvq = CV_CONST; + else if (had_var) + *cvq = CV_VAR; + else + *cvq = CV_NONE; + *noref = had_noref; + *is_static = had_static; + *_flags = flags; + return true; +onerr: + parseerror(parser, "parse error after variable qualifier"); + *cvq = CV_WRONG; + return true; +} + +static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression **out); +static bool parse_switch(parser_t *parser, ast_block *block, ast_expression **out) +{ + bool rv; + char *label = nullptr; + + /* skip the 'while' and get the body */ + if (!parser_next(parser)) { + if (OPTS_FLAG(LOOP_LABELS)) + parseerror(parser, "expected loop label or 'switch' operand in parenthesis"); + else + parseerror(parser, "expected 'switch' operand in parenthesis"); + return false; + } + + if (parser->tok == ':') { + if (!OPTS_FLAG(LOOP_LABELS)) + parseerror(parser, "labeled loops not activated, try using -floop-labels"); + if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { + parseerror(parser, "expected loop label"); + return false; + } + label = util_strdup(parser_tokval(parser)); + if (!parser_next(parser)) { + mem_d(label); + parseerror(parser, "expected 'switch' operand in parenthesis"); + return false; + } + } + + if (parser->tok != '(') { + parseerror(parser, "expected 'switch' operand in parenthesis"); + return false; + } + + parser->breaks.push_back(label); + + rv = parse_switch_go(parser, block, out); + if (label) + mem_d(label); + if (parser->breaks.back() != label) { + parseerror(parser, "internal error: label stack corrupted"); + rv = false; + delete *out; + *out = nullptr; + } + else { + parser->breaks.pop_back(); + } + return rv; +} + +static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression **out) +{ + ast_expression *operand; + ast_value *opval; + ast_value *typevar; + ast_switch *switchnode; + ast_switch_case swcase; + + int cvq; + bool noref, is_static; + uint32_t qflags = 0; + + lex_ctx_t ctx = parser_ctx(parser); + + (void)block; /* not touching */ + (void)opval; + + /* parse into the expression */ + if (!parser_next(parser)) { + parseerror(parser, "expected switch operand"); + return false; + } + /* parse the operand */ + operand = parse_expression_leave(parser, false, false, false); + if (!operand) + return false; + + switchnode = new ast_switch(ctx, operand); + + /* closing paren */ + if (parser->tok != ')') { + delete switchnode; + parseerror(parser, "expected closing paren after 'switch' operand"); + return false; + } + + /* parse over the opening paren */ + if (!parser_next(parser) || parser->tok != '{') { + delete switchnode; + parseerror(parser, "expected list of cases"); + return false; + } + + if (!parser_next(parser)) { + delete switchnode; + parseerror(parser, "expected 'case' or 'default'"); + return false; + } + + /* new block; allow some variables to be declared here */ + parser_enterblock(parser); + while (true) { + typevar = nullptr; + if (parser->tok == TOKEN_IDENT) + typevar = parser_find_typedef(parser, parser_tokval(parser), 0); + if (typevar || parser->tok == TOKEN_TYPENAME) { + if (!parse_variable(parser, block, true, CV_NONE, typevar, false, false, 0, nullptr)) { + delete switchnode; + return false; + } + continue; + } + if (parse_qualifiers(parser, true, &cvq, &noref, &is_static, &qflags, nullptr)) + { + if (cvq == CV_WRONG) { + delete switchnode; + return false; + } + if (!parse_variable(parser, block, true, cvq, nullptr, noref, is_static, qflags, nullptr)) { + delete switchnode; + return false; + } + continue; + } + break; + } + + /* case list! */ + while (parser->tok != '}') { + ast_block *caseblock; + + if (!strcmp(parser_tokval(parser), "case")) { + if (!parser_next(parser)) { + delete switchnode; + parseerror(parser, "expected expression for case"); + return false; + } + swcase.m_value = parse_expression_leave(parser, false, false, false); + if (!swcase.m_value) { + delete switchnode; + parseerror(parser, "expected expression for case"); + return false; + } + if (!OPTS_FLAG(RELAXED_SWITCH)) { + if (!ast_istype(swcase.m_value, ast_value)) { /* || ((ast_value*)swcase.m_value)->m_cvq != CV_CONST) { */ + parseerror(parser, "case on non-constant values need to be explicitly enabled via -frelaxed-switch"); + ast_unref(operand); + return false; + } + } + } + else if (!strcmp(parser_tokval(parser), "default")) { + swcase.m_value = nullptr; + if (!parser_next(parser)) { + delete switchnode; + parseerror(parser, "expected colon"); + return false; + } + } + else { + delete switchnode; + parseerror(parser, "expected 'case' or 'default'"); + return false; + } + + /* Now the colon and body */ + if (parser->tok != ':') { + if (swcase.m_value) ast_unref(swcase.m_value); + delete switchnode; + parseerror(parser, "expected colon"); + return false; + } + + if (!parser_next(parser)) { + if (swcase.m_value) ast_unref(swcase.m_value); + delete switchnode; + parseerror(parser, "expected statements or case"); + return false; + } + caseblock = new ast_block(parser_ctx(parser)); + if (!caseblock) { + if (swcase.m_value) ast_unref(swcase.m_value); + delete switchnode; + return false; + } + swcase.m_code = caseblock; + switchnode->m_cases.push_back(swcase); + while (true) { + ast_expression *expr; + if (parser->tok == '}') + break; + if (parser->tok == TOKEN_KEYWORD) { + if (!strcmp(parser_tokval(parser), "case") || + !strcmp(parser_tokval(parser), "default")) + { + break; + } + } + if (!parse_statement(parser, caseblock, &expr, true)) { + delete switchnode; + return false; + } + if (!expr) + continue; + if (!caseblock->addExpr(expr)) { + delete switchnode; + return false; + } + } + } + + parser_leaveblock(parser); + + /* closing paren */ + if (parser->tok != '}') { + delete switchnode; + parseerror(parser, "expected closing paren of case list"); + return false; + } + if (!parser_next(parser)) { + delete switchnode; + parseerror(parser, "parse error after switch"); + return false; + } + *out = switchnode; + return true; +} + +/* parse computed goto sides */ +static ast_expression *parse_goto_computed(parser_t *parser, ast_expression **side) { + ast_expression *on_true; + ast_expression *on_false; + ast_expression *cond; + + if (!*side) + return nullptr; + + if (ast_istype(*side, ast_ternary)) { + ast_ternary *tern = (ast_ternary*)*side; + on_true = parse_goto_computed(parser, &tern->m_on_true); + on_false = parse_goto_computed(parser, &tern->m_on_false); + + if (!on_true || !on_false) { + parseerror(parser, "expected label or expression in ternary"); + if (on_true) ast_unref(on_true); + if (on_false) ast_unref(on_false); + return nullptr; + } + + cond = tern->m_cond; + tern->m_cond = nullptr; + delete tern; + *side = nullptr; + return new ast_ifthen(parser_ctx(parser), cond, on_true, on_false); + } else if (ast_istype(*side, ast_label)) { + ast_goto *gt = new ast_goto(parser_ctx(parser), ((ast_label*)*side)->m_name); + gt->setLabel(reinterpret_cast(*side)); + *side = nullptr; + return gt; + } + return nullptr; +} + +static bool parse_goto(parser_t *parser, ast_expression **out) +{ + ast_goto *gt = nullptr; + ast_expression *lbl; + + if (!parser_next(parser)) + return false; + + if (parser->tok != TOKEN_IDENT) { + ast_expression *expression; + + /* could be an expression i.e computed goto :-) */ + if (parser->tok != '(') { + parseerror(parser, "expected label name after `goto`"); + return false; + } + + /* failed to parse expression for goto */ + if (!(expression = parse_expression(parser, false, true)) || + !(*out = parse_goto_computed(parser, &expression))) { + parseerror(parser, "invalid goto expression"); + if(expression) + ast_unref(expression); + return false; + } + + return true; + } + + /* not computed goto */ + gt = new ast_goto(parser_ctx(parser), parser_tokval(parser)); + lbl = parser_find_label(parser, gt->m_name); + if (lbl) { + if (!ast_istype(lbl, ast_label)) { + parseerror(parser, "internal error: label is not an ast_label"); + delete gt; + return false; + } + gt->setLabel(reinterpret_cast(lbl)); + } + else + parser->gotos.push_back(gt); + + if (!parser_next(parser) || parser->tok != ';') { + parseerror(parser, "semicolon expected after goto label"); + return false; + } + if (!parser_next(parser)) { + parseerror(parser, "parse error after goto"); + return false; + } + + *out = gt; + return true; +} + +static bool parse_skipwhite(parser_t *parser) +{ + do { + if (!parser_next(parser)) + return false; + } while (parser->tok == TOKEN_WHITE && parser->tok < TOKEN_ERROR); + return parser->tok < TOKEN_ERROR; +} + +static bool parse_eol(parser_t *parser) +{ + if (!parse_skipwhite(parser)) + return false; + return parser->tok == TOKEN_EOL; +} + +static bool parse_pragma_do(parser_t *parser) +{ + if (!parser_next(parser) || + parser->tok != TOKEN_IDENT || + strcmp(parser_tokval(parser), "pragma")) + { + parseerror(parser, "expected `pragma` keyword after `#`, got `%s`", parser_tokval(parser)); + return false; + } + if (!parse_skipwhite(parser) || parser->tok != TOKEN_IDENT) { + parseerror(parser, "expected pragma, got `%s`", parser_tokval(parser)); + return false; + } + + if (!strcmp(parser_tokval(parser), "noref")) { + if (!parse_skipwhite(parser) || parser->tok != TOKEN_INTCONST) { + parseerror(parser, "`noref` pragma requires an argument: 0 or 1"); + return false; + } + parser->noref = !!parser_token(parser)->constval.i; + if (!parse_eol(parser)) { + parseerror(parser, "parse error after `noref` pragma"); + return false; + } + } + else + { + (void)!parsewarning(parser, WARN_UNKNOWN_PRAGMAS, "ignoring #pragma %s", parser_tokval(parser)); + + /* skip to eol */ + while (!parse_eol(parser)) { + parser_next(parser); + } + + return true; + } + + return true; +} + +static bool parse_pragma(parser_t *parser) +{ + bool rv; + parser->lex->flags.preprocessing = true; + parser->lex->flags.mergelines = true; + rv = parse_pragma_do(parser); + if (parser->tok != TOKEN_EOL) { + parseerror(parser, "junk after pragma"); + rv = false; + } + parser->lex->flags.preprocessing = false; + parser->lex->flags.mergelines = false; + if (!parser_next(parser)) { + parseerror(parser, "parse error after pragma"); + rv = false; + } + return rv; +} + +static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases) +{ + bool noref, is_static; + int cvq = CV_NONE; + uint32_t qflags = 0; + ast_value *typevar = nullptr; + char *vstring = nullptr; + + *out = nullptr; + + if (parser->tok == TOKEN_IDENT) + typevar = parser_find_typedef(parser, parser_tokval(parser), 0); + + if (typevar || parser->tok == TOKEN_TYPENAME || parser->tok == '.' || parser->tok == TOKEN_DOTS) + { + /* local variable */ + if (!block) { + parseerror(parser, "cannot declare a variable from here"); + return false; + } + if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { + if (parsewarning(parser, WARN_EXTENSIONS, "missing 'local' keyword when declaring a local variable")) + return false; + } + if (!parse_variable(parser, block, false, CV_NONE, typevar, false, false, 0, nullptr)) + return false; + return true; + } + else if (parse_qualifiers(parser, !!block, &cvq, &noref, &is_static, &qflags, &vstring)) + { + if (cvq == CV_WRONG) + return false; + return parse_variable(parser, block, false, cvq, nullptr, noref, is_static, qflags, vstring); + } + else if (parser->tok == TOKEN_KEYWORD) + { + if (!strcmp(parser_tokval(parser), "__builtin_debug_printtype")) + { + char ty[1024]; + ast_value *tdef; + + if (!parser_next(parser)) { + parseerror(parser, "parse error after __builtin_debug_printtype"); + return false; + } + + if (parser->tok == TOKEN_IDENT && (tdef = parser_find_typedef(parser, parser_tokval(parser), 0))) + { + ast_type_to_string(tdef, ty, sizeof(ty)); + con_out("__builtin_debug_printtype: `%s`=`%s`\n", tdef->m_name.c_str(), ty); + if (!parser_next(parser)) { + parseerror(parser, "parse error after __builtin_debug_printtype typename argument"); + return false; + } + } + else + { + if (!parse_statement(parser, block, out, allow_cases)) + return false; + if (!*out) + con_out("__builtin_debug_printtype: got no output node\n"); + else + { + ast_type_to_string(*out, ty, sizeof(ty)); + con_out("__builtin_debug_printtype: `%s`\n", ty); + } + } + return true; + } + else if (!strcmp(parser_tokval(parser), "return")) + { + return parse_return(parser, block, out); + } + else if (!strcmp(parser_tokval(parser), "if")) + { + return parse_if(parser, block, out); + } + else if (!strcmp(parser_tokval(parser), "while")) + { + return parse_while(parser, block, out); + } + else if (!strcmp(parser_tokval(parser), "do")) + { + return parse_dowhile(parser, block, out); + } + else if (!strcmp(parser_tokval(parser), "for")) + { + if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { + if (parsewarning(parser, WARN_EXTENSIONS, "for loops are not recognized in the original Quake C standard, to enable try an alternate standard --std=?")) + return false; + } + return parse_for(parser, block, out); + } + else if (!strcmp(parser_tokval(parser), "break")) + { + return parse_break_continue(parser, block, out, false); + } + else if (!strcmp(parser_tokval(parser), "continue")) + { + return parse_break_continue(parser, block, out, true); + } + else if (!strcmp(parser_tokval(parser), "switch")) + { + return parse_switch(parser, block, out); + } + else if (!strcmp(parser_tokval(parser), "case") || + !strcmp(parser_tokval(parser), "default")) + { + if (!allow_cases) { + parseerror(parser, "unexpected 'case' label"); + return false; + } + return true; + } + else if (!strcmp(parser_tokval(parser), "goto")) + { + return parse_goto(parser, out); + } + else if (!strcmp(parser_tokval(parser), "typedef")) + { + if (!parser_next(parser)) { + parseerror(parser, "expected type definition after 'typedef'"); + return false; + } + return parse_typedef(parser); + } + parseerror(parser, "Unexpected keyword: `%s'", parser_tokval(parser)); + return false; + } + else if (parser->tok == '{') + { + ast_block *inner; + inner = parse_block(parser); + if (!inner) + return false; + *out = inner; + return true; + } + else if (parser->tok == ':') + { + size_t i; + ast_label *label; + if (!parser_next(parser)) { + parseerror(parser, "expected label name"); + return false; + } + if (parser->tok != TOKEN_IDENT) { + parseerror(parser, "label must be an identifier"); + return false; + } + label = (ast_label*)parser_find_label(parser, parser_tokval(parser)); + if (label) { + if (!label->m_undefined) { + parseerror(parser, "label `%s` already defined", label->m_name); + return false; + } + label->m_undefined = false; + } + else { + label = new ast_label(parser_ctx(parser), parser_tokval(parser), false); + parser->labels.push_back(label); + } + *out = label; + if (!parser_next(parser)) { + parseerror(parser, "parse error after label"); + return false; + } + for (i = 0; i < parser->gotos.size(); ++i) { + if (parser->gotos[i]->m_name == label->m_name) { + parser->gotos[i]->setLabel(label); + parser->gotos.erase(parser->gotos.begin() + i); + --i; + } + } + return true; + } + else if (parser->tok == ';') + { + if (!parser_next(parser)) { + parseerror(parser, "parse error after empty statement"); + return false; + } + return true; + } + else + { + lex_ctx_t ctx = parser_ctx(parser); + ast_expression *exp = parse_expression(parser, false, false); + if (!exp) + return false; + *out = exp; + if (!exp->m_side_effects) { + if (compile_warning(ctx, WARN_EFFECTLESS_STATEMENT, "statement has no effect")) + return false; + } + return true; + } +} + +static bool parse_enum(parser_t *parser) +{ + bool flag = false; + bool reverse = false; + qcfloat_t num = 0; + ast_value **values = nullptr; + ast_value *var = nullptr; + ast_value *asvalue; + + ast_expression *old; + + if (!parser_next(parser) || (parser->tok != '{' && parser->tok != ':')) { + parseerror(parser, "expected `{` or `:` after `enum` keyword"); + return false; + } + + /* enumeration attributes (can add more later) */ + if (parser->tok == ':') { + if (!parser_next(parser) || parser->tok != TOKEN_IDENT){ + parseerror(parser, "expected `flag` or `reverse` for enumeration attribute"); + return false; + } + + /* attributes? */ + if (!strcmp(parser_tokval(parser), "flag")) { + num = 1; + flag = true; + } + else if (!strcmp(parser_tokval(parser), "reverse")) { + reverse = true; + } + else { + parseerror(parser, "invalid attribute `%s` for enumeration", parser_tokval(parser)); + return false; + } + + if (!parser_next(parser) || parser->tok != '{') { + parseerror(parser, "expected `{` after enum attribute "); + return false; + } + } + + while (true) { + if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { + if (parser->tok == '}') { + /* allow an empty enum */ + break; + } + parseerror(parser, "expected identifier or `}`"); + goto onerror; + } + + old = parser_find_field(parser, parser_tokval(parser)); + if (!old) + old = parser_find_global(parser, parser_tokval(parser)); + if (old) { + parseerror(parser, "value `%s` has already been declared here: %s:%i", + parser_tokval(parser), old->m_context.file, old->m_context.line); + goto onerror; + } + + var = new ast_value(parser_ctx(parser), parser_tokval(parser), TYPE_FLOAT); + vec_push(values, var); + var->m_cvq = CV_CONST; + var->m_hasvalue = true; + + /* for flagged enumerations increment in POTs of TWO */ + var->m_constval.vfloat = (flag) ? (num *= 2) : (num ++); + parser_addglobal(parser, var->m_name, var); + + if (!parser_next(parser)) { + parseerror(parser, "expected `=`, `}` or comma after identifier"); + goto onerror; + } + + if (parser->tok == ',') + continue; + if (parser->tok == '}') + break; + if (parser->tok != '=') { + parseerror(parser, "expected `=`, `}` or comma after identifier"); + goto onerror; + } + + if (!parser_next(parser)) { + parseerror(parser, "expected expression after `=`"); + goto onerror; + } + + /* We got a value! */ + old = parse_expression_leave(parser, true, false, false); + asvalue = (ast_value*)old; + if (!ast_istype(old, ast_value) || asvalue->m_cvq != CV_CONST || !asvalue->m_hasvalue) { + compile_error(var->m_context, "constant value or expression expected"); + goto onerror; + } + num = (var->m_constval.vfloat = asvalue->m_constval.vfloat) + 1; + + if (parser->tok == '}') + break; + if (parser->tok != ',') { + parseerror(parser, "expected `}` or comma after expression"); + goto onerror; + } + } + + /* patch them all (for reversed attribute) */ + if (reverse) { + size_t i; + for (i = 0; i < vec_size(values); i++) + values[i]->m_constval.vfloat = vec_size(values) - i - 1; + } + + if (parser->tok != '}') { + parseerror(parser, "internal error: breaking without `}`"); + goto onerror; + } + + if (!parser_next(parser) || parser->tok != ';') { + parseerror(parser, "expected semicolon after enumeration"); + goto onerror; + } + + if (!parser_next(parser)) { + parseerror(parser, "parse error after enumeration"); + goto onerror; + } + + vec_free(values); + return true; + +onerror: + vec_free(values); + return false; +} + +static bool parse_block_into(parser_t *parser, ast_block *block) +{ + bool retval = true; + + parser_enterblock(parser); + + if (!parser_next(parser)) { /* skip the '{' */ + parseerror(parser, "expected function body"); + goto cleanup; + } + + while (parser->tok != TOKEN_EOF && parser->tok < TOKEN_ERROR) + { + ast_expression *expr = nullptr; + if (parser->tok == '}') + break; + + if (!parse_statement(parser, block, &expr, false)) { + /* parseerror(parser, "parse error"); */ + block = nullptr; + goto cleanup; + } + if (!expr) + continue; + if (!block->addExpr(expr)) { + delete block; + block = nullptr; + goto cleanup; + } + } + + if (parser->tok != '}') { + block = nullptr; + } else { + (void)parser_next(parser); + } + +cleanup: + if (!parser_leaveblock(parser)) + retval = false; + return retval && !!block; +} + +static ast_block* parse_block(parser_t *parser) +{ + ast_block *block; + block = new ast_block(parser_ctx(parser)); + if (!block) + return nullptr; + if (!parse_block_into(parser, block)) { + delete block; + return nullptr; + } + return block; +} + +static bool parse_statement_or_block(parser_t *parser, ast_expression **out) +{ + if (parser->tok == '{') { + *out = parse_block(parser); + return !!*out; + } + return parse_statement(parser, nullptr, out, false); +} + +static bool create_vector_members(ast_value *var, ast_member **me) +{ + size_t i; + size_t len = var->m_name.length(); + + for (i = 0; i < 3; ++i) { + char *name = (char*)mem_a(len+3); + memcpy(name, var->m_name.c_str(), len); + name[len+0] = '_'; + name[len+1] = 'x'+i; + name[len+2] = 0; + me[i] = ast_member::make(var->m_context, var, i, name); + mem_d(name); + if (!me[i]) + break; + } + if (i == 3) + return true; + + /* unroll */ + do { delete me[--i]; } while(i); + return false; +} + +static bool parse_function_body(parser_t *parser, ast_value *var) +{ + ast_block *block = nullptr; + ast_function *func; + ast_function *old; + + ast_expression *framenum = nullptr; + ast_expression *nextthink = nullptr; + /* None of the following have to be deleted */ + ast_expression *fld_think = nullptr, *fld_nextthink = nullptr, *fld_frame = nullptr; + ast_expression *gbl_time = nullptr, *gbl_self = nullptr; + bool has_frame_think; + + bool retval = true; + + has_frame_think = false; + old = parser->function; + + if (var->m_flags & AST_FLAG_ALIAS) { + parseerror(parser, "function aliases cannot have bodies"); + return false; + } + + if (parser->gotos.size() || parser->labels.size()) { + parseerror(parser, "gotos/labels leaking"); + return false; + } + + if (!OPTS_FLAG(VARIADIC_ARGS) && var->m_flags & AST_FLAG_VARIADIC) { + if (parsewarning(parser, WARN_VARIADIC_FUNCTION, + "variadic function with implementation will not be able to access additional parameters (try -fvariadic-args)")) + { + return false; + } + } + + if (parser->tok == '[') { + /* got a frame definition: [ framenum, nextthink ] + * this translates to: + * self.frame = framenum; + * self.nextthink = time + 0.1; + * self.think = nextthink; + */ + nextthink = nullptr; + + fld_think = parser_find_field(parser, "think"); + fld_nextthink = parser_find_field(parser, "nextthink"); + fld_frame = parser_find_field(parser, "frame"); + if (!fld_think || !fld_nextthink || !fld_frame) { + parseerror(parser, "cannot use [frame,think] notation without the required fields"); + parseerror(parser, "please declare the following entityfields: `frame`, `think`, `nextthink`"); + return false; + } + gbl_time = parser_find_global(parser, "time"); + gbl_self = parser_find_global(parser, "self"); + if (!gbl_time || !gbl_self) { + parseerror(parser, "cannot use [frame,think] notation without the required globals"); + parseerror(parser, "please declare the following globals: `time`, `self`"); + return false; + } + + if (!parser_next(parser)) + return false; + + framenum = parse_expression_leave(parser, true, false, false); + if (!framenum) { + parseerror(parser, "expected a framenumber constant in[frame,think] notation"); + return false; + } + if (!ast_istype(framenum, ast_value) || !( (ast_value*)framenum )->m_hasvalue) { + ast_unref(framenum); + parseerror(parser, "framenumber in [frame,think] notation must be a constant"); + return false; + } + + if (parser->tok != ',') { + ast_unref(framenum); + parseerror(parser, "expected comma after frame number in [frame,think] notation"); + parseerror(parser, "Got a %i\n", parser->tok); + return false; + } + + if (!parser_next(parser)) { + ast_unref(framenum); + return false; + } + + if (parser->tok == TOKEN_IDENT && !parser_find_var(parser, parser_tokval(parser))) + { + /* qc allows the use of not-yet-declared functions here + * - this automatically creates a prototype */ + ast_value *thinkfunc; + ast_expression *functype = fld_think->m_next; + + thinkfunc = new ast_value(parser_ctx(parser), parser_tokval(parser), functype->m_vtype); + if (!thinkfunc) { /* || !thinkfunc->adoptType(*functype)*/ + ast_unref(framenum); + parseerror(parser, "failed to create implicit prototype for `%s`", parser_tokval(parser)); + return false; + } + thinkfunc->adoptType(*functype); + + if (!parser_next(parser)) { + ast_unref(framenum); + delete thinkfunc; + return false; + } + + parser_addglobal(parser, thinkfunc->m_name, thinkfunc); + + nextthink = thinkfunc; + + } else { + nextthink = parse_expression_leave(parser, true, false, false); + if (!nextthink) { + ast_unref(framenum); + parseerror(parser, "expected a think-function in [frame,think] notation"); + return false; + } + } + + if (!ast_istype(nextthink, ast_value)) { + parseerror(parser, "think-function in [frame,think] notation must be a constant"); + retval = false; + } + + if (retval && parser->tok != ']') { + parseerror(parser, "expected closing `]` for [frame,think] notation"); + retval = false; + } + + if (retval && !parser_next(parser)) { + retval = false; + } + + if (retval && parser->tok != '{') { + parseerror(parser, "a function body has to be declared after a [frame,think] declaration"); + retval = false; + } + + if (!retval) { + ast_unref(nextthink); + ast_unref(framenum); + return false; + } + + has_frame_think = true; + } + + block = new ast_block(parser_ctx(parser)); + if (!block) { + parseerror(parser, "failed to allocate block"); + if (has_frame_think) { + ast_unref(nextthink); + ast_unref(framenum); + } + return false; + } + + if (has_frame_think) { + if (!OPTS_FLAG(EMULATE_STATE)) { + ast_state *state_op = new ast_state(parser_ctx(parser), framenum, nextthink); + if (!block->addExpr(state_op)) { + parseerror(parser, "failed to generate state op for [frame,think]"); + ast_unref(nextthink); + ast_unref(framenum); + delete block; + return false; + } + } else { + /* emulate OP_STATE in code: */ + lex_ctx_t ctx; + ast_expression *self_frame; + ast_expression *self_nextthink; + ast_expression *self_think; + ast_expression *time_plus_1; + ast_store *store_frame; + ast_store *store_nextthink; + ast_store *store_think; + + float frame_delta = 1.0f / (float)OPTS_OPTION_U32(OPTION_STATE_FPS); + + ctx = parser_ctx(parser); + self_frame = new ast_entfield(ctx, gbl_self, fld_frame); + self_nextthink = new ast_entfield(ctx, gbl_self, fld_nextthink); + self_think = new ast_entfield(ctx, gbl_self, fld_think); + + time_plus_1 = new ast_binary(ctx, INSTR_ADD_F, + gbl_time, parser->m_fold.constgen_float(frame_delta, false)); + + if (!self_frame || !self_nextthink || !self_think || !time_plus_1) { + if (self_frame) delete self_frame; + if (self_nextthink) delete self_nextthink; + if (self_think) delete self_think; + if (time_plus_1) delete time_plus_1; + retval = false; + } + + if (retval) + { + store_frame = new ast_store(ctx, INSTR_STOREP_F, self_frame, framenum); + store_nextthink = new ast_store(ctx, INSTR_STOREP_F, self_nextthink, time_plus_1); + store_think = new ast_store(ctx, INSTR_STOREP_FNC, self_think, nextthink); + + if (!store_frame) { + delete self_frame; + retval = false; + } + if (!store_nextthink) { + delete self_nextthink; + retval = false; + } + if (!store_think) { + delete self_think; + retval = false; + } + if (!retval) { + if (store_frame) delete store_frame; + if (store_nextthink) delete store_nextthink; + if (store_think) delete store_think; + retval = false; + } + if (!block->addExpr(store_frame) || + !block->addExpr(store_nextthink) || + !block->addExpr(store_think)) + { + retval = false; + } + } + + if (!retval) { + parseerror(parser, "failed to generate code for [frame,think]"); + ast_unref(nextthink); + ast_unref(framenum); + delete block; + return false; + } + } + } + + if (var->m_hasvalue) { + if (!(var->m_flags & AST_FLAG_ACCUMULATE)) { + parseerror(parser, "function `%s` declared with multiple bodies", var->m_name); + delete block; + goto enderr; + } + func = var->m_constval.vfunc; + + if (!func) { + parseerror(parser, "internal error: nullptr function: `%s`", var->m_name); + delete block; + goto enderr; + } + } else { + func = ast_function::make(var->m_context, var->m_name, var); + + if (!func) { + parseerror(parser, "failed to allocate function for `%s`", var->m_name); + delete block; + goto enderr; + } + parser->functions.push_back(func); + } + + parser_enterblock(parser); + + for (auto &it : var->m_type_params) { + size_t e; + ast_member *me[3]; + + if (it->m_vtype != TYPE_VECTOR && + (it->m_vtype != TYPE_FIELD || + it->m_next->m_vtype != TYPE_VECTOR)) + { + continue; + } + + if (!create_vector_members(it.get(), me)) { + delete block; + goto enderrfn; + } + + for (e = 0; e < 3; ++e) { + parser_addlocal(parser, me[e]->m_name, me[e]); + block->collect(me[e]); + } + } + + if (var->m_argcounter && !func->m_argc) { + ast_value *argc = new ast_value(var->m_context, var->m_argcounter, TYPE_FLOAT); + parser_addlocal(parser, argc->m_name, argc); + func->m_argc.reset(argc); + } + + if (OPTS_FLAG(VARIADIC_ARGS) && var->m_flags & AST_FLAG_VARIADIC && !func->m_varargs) { + char name[1024]; + ast_value *varargs = new ast_value(var->m_context, "reserved:va_args", TYPE_ARRAY); + varargs->m_flags |= AST_FLAG_IS_VARARG; + varargs->m_next = new ast_value(var->m_context, "", TYPE_VECTOR); + varargs->m_count = 0; + util_snprintf(name, sizeof(name), "%s##va##SET", var->m_name.c_str()); + if (!parser_create_array_setter_proto(parser, varargs, name)) { + delete varargs; + delete block; + goto enderrfn; + } + util_snprintf(name, sizeof(name), "%s##va##GET", var->m_name.c_str()); + if (!parser_create_array_getter_proto(parser, varargs, varargs->m_next, name)) { + delete varargs; + delete block; + goto enderrfn; + } + func->m_varargs.reset(varargs); + func->m_fixedparams = (ast_value*)parser->m_fold.constgen_float(var->m_type_params.size(), false); + } + + parser->function = func; + if (!parse_block_into(parser, block)) { + delete block; + goto enderrfn; + } + + func->m_blocks.emplace_back(block); + + parser->function = old; + if (!parser_leaveblock(parser)) + retval = false; + if (vec_size(parser->variables) != PARSER_HT_LOCALS) { + parseerror(parser, "internal error: local scopes left"); + retval = false; + } + + if (parser->tok == ';') + return parser_next(parser); + else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) + parseerror(parser, "missing semicolon after function body (mandatory with -std=qcc)"); + return retval; + +enderrfn: + (void)!parser_leaveblock(parser); + parser->functions.pop_back(); + delete func; + var->m_constval.vfunc = nullptr; + +enderr: + parser->function = old; + return false; +} + +static ast_expression *array_accessor_split( + parser_t *parser, + ast_value *array, + ast_value *index, + size_t middle, + ast_expression *left, + ast_expression *right + ) +{ + ast_ifthen *ifthen; + ast_binary *cmp; + + lex_ctx_t ctx = array->m_context; + + if (!left || !right) { + if (left) delete left; + if (right) delete right; + return nullptr; + } + + cmp = new ast_binary(ctx, INSTR_LT, + index, + parser->m_fold.constgen_float(middle, false)); + if (!cmp) { + delete left; + delete right; + parseerror(parser, "internal error: failed to create comparison for array setter"); + return nullptr; + } + + ifthen = new ast_ifthen(ctx, cmp, left, right); + if (!ifthen) { + delete cmp; /* will delete left and right */ + parseerror(parser, "internal error: failed to create conditional jump for array setter"); + return nullptr; + } + + return ifthen; +} + +static ast_expression *array_setter_node(parser_t *parser, ast_value *array, ast_value *index, ast_value *value, size_t from, size_t afterend) +{ + lex_ctx_t ctx = array->m_context; + + if (from+1 == afterend) { + /* set this value */ + ast_block *block; + ast_return *ret; + ast_array_index *subscript; + ast_store *st; + int assignop = type_store_instr[value->m_vtype]; + + if (value->m_vtype == TYPE_FIELD && value->m_next->m_vtype == TYPE_VECTOR) + assignop = INSTR_STORE_V; + + subscript = ast_array_index::make(ctx, array, parser->m_fold.constgen_float(from, false)); + if (!subscript) + return nullptr; + + st = new ast_store(ctx, assignop, subscript, value); + if (!st) { + delete subscript; + return nullptr; + } + + block = new ast_block(ctx); + if (!block) { + delete st; + return nullptr; + } + + if (!block->addExpr(st)) { + delete block; + return nullptr; + } + + ret = new ast_return(ctx, nullptr); + if (!ret) { + delete block; + return nullptr; + } + + if (!block->addExpr(ret)) { + delete block; + return nullptr; + } + + return block; + } else { + ast_expression *left, *right; + size_t diff = afterend - from; + size_t middle = from + diff/2; + left = array_setter_node(parser, array, index, value, from, middle); + right = array_setter_node(parser, array, index, value, middle, afterend); + return array_accessor_split(parser, array, index, middle, left, right); + } +} + +static ast_expression *array_field_setter_node( + parser_t *parser, + ast_value *array, + ast_value *entity, + ast_value *index, + ast_value *value, + size_t from, + size_t afterend) +{ + lex_ctx_t ctx = array->m_context; + + if (from+1 == afterend) { + /* set this value */ + ast_block *block; + ast_return *ret; + ast_entfield *entfield; + ast_array_index *subscript; + ast_store *st; + int assignop = type_storep_instr[value->m_vtype]; + + if (value->m_vtype == TYPE_FIELD && value->m_next->m_vtype == TYPE_VECTOR) + assignop = INSTR_STOREP_V; + + subscript = ast_array_index::make(ctx, array, parser->m_fold.constgen_float(from, false)); + if (!subscript) + return nullptr; + + subscript->m_next = new ast_expression(ast_copy_type, subscript->m_context, *subscript); + subscript->m_vtype = TYPE_FIELD; + + entfield = new ast_entfield(ctx, entity, subscript, subscript); + if (!entfield) { + delete subscript; + return nullptr; + } + + st = new ast_store(ctx, assignop, entfield, value); + if (!st) { + delete entfield; + return nullptr; + } + + block = new ast_block(ctx); + if (!block) { + delete st; + return nullptr; + } + + if (!block->addExpr(st)) { + delete block; + return nullptr; + } + + ret = new ast_return(ctx, nullptr); + if (!ret) { + delete block; + return nullptr; + } + + if (!block->addExpr(ret)) { + delete block; + return nullptr; + } + + return block; + } else { + ast_expression *left, *right; + size_t diff = afterend - from; + size_t middle = from + diff/2; + left = array_field_setter_node(parser, array, entity, index, value, from, middle); + right = array_field_setter_node(parser, array, entity, index, value, middle, afterend); + return array_accessor_split(parser, array, index, middle, left, right); + } +} + +static ast_expression *array_getter_node(parser_t *parser, ast_value *array, ast_value *index, size_t from, size_t afterend) +{ + lex_ctx_t ctx = array->m_context; + + if (from+1 == afterend) { + ast_return *ret; + ast_array_index *subscript; + + subscript = ast_array_index::make(ctx, array, parser->m_fold.constgen_float(from, false)); + if (!subscript) + return nullptr; + + ret = new ast_return(ctx, subscript); + if (!ret) { + delete subscript; + return nullptr; + } + + return ret; + } else { + ast_expression *left, *right; + size_t diff = afterend - from; + size_t middle = from + diff/2; + left = array_getter_node(parser, array, index, from, middle); + right = array_getter_node(parser, array, index, middle, afterend); + return array_accessor_split(parser, array, index, middle, left, right); + } +} + +static bool parser_create_array_accessor(parser_t *parser, ast_value *array, const char *funcname, ast_value **out) +{ + ast_function *func = nullptr; + ast_value *fval = nullptr; + ast_block *body = nullptr; + + fval = new ast_value(array->m_context, funcname, TYPE_FUNCTION); + if (!fval) { + parseerror(parser, "failed to create accessor function value"); + return false; + } + fval->m_flags &= ~(AST_FLAG_COVERAGE_MASK); + + func = ast_function::make(array->m_context, funcname, fval); + if (!func) { + delete fval; + parseerror(parser, "failed to create accessor function node"); + return false; + } + + body = new ast_block(array->m_context); + if (!body) { + parseerror(parser, "failed to create block for array accessor"); + delete fval; + delete func; + return false; + } + + func->m_blocks.emplace_back(body); + *out = fval; + + parser->accessors.push_back(fval); + + return true; +} + +static ast_value* parser_create_array_setter_proto(parser_t *parser, ast_value *array, const char *funcname) +{ + ast_value *index = nullptr; + ast_value *value = nullptr; + ast_function *func; + ast_value *fval; + + if (!ast_istype(array->m_next, ast_value)) { + parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type"); + return nullptr; + } + + if (!parser_create_array_accessor(parser, array, funcname, &fval)) + return nullptr; + func = fval->m_constval.vfunc; + fval->m_next = new ast_value(array->m_context, "", TYPE_VOID); + + index = new ast_value(array->m_context, "index", TYPE_FLOAT); + value = new ast_value(ast_copy_type, *(ast_value*)array->m_next); + + if (!index || !value) { + parseerror(parser, "failed to create locals for array accessor"); + goto cleanup; + } + value->m_name = "value"; // not important + fval->m_type_params.emplace_back(index); + fval->m_type_params.emplace_back(value); + + array->m_setter = fval; + return fval; +cleanup: + if (index) delete index; + if (value) delete value; + delete func; + delete fval; + return nullptr; +} + +static bool parser_create_array_setter_impl(parser_t *parser, ast_value *array) +{ + ast_expression *root = nullptr; + root = array_setter_node(parser, array, + array->m_setter->m_type_params[0].get(), + array->m_setter->m_type_params[1].get(), + 0, array->m_count); + if (!root) { + parseerror(parser, "failed to build accessor search tree"); + return false; + } + if (!array->m_setter->m_constval.vfunc->m_blocks[0].get()->addExpr(root)) { + delete root; + return false; + } + return true; +} + +static bool parser_create_array_setter(parser_t *parser, ast_value *array, const char *funcname) +{ + if (!parser_create_array_setter_proto(parser, array, funcname)) + return false; + return parser_create_array_setter_impl(parser, array); +} + +static bool parser_create_array_field_setter(parser_t *parser, ast_value *array, const char *funcname) +{ + ast_expression *root = nullptr; + ast_value *entity = nullptr; + ast_value *index = nullptr; + ast_value *value = nullptr; + ast_function *func; + ast_value *fval; + + if (!ast_istype(array->m_next, ast_value)) { + parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type"); + return false; + } + + if (!parser_create_array_accessor(parser, array, funcname, &fval)) + return false; + func = fval->m_constval.vfunc; + fval->m_next = new ast_value(array->m_context, "", TYPE_VOID); + + entity = new ast_value(array->m_context, "entity", TYPE_ENTITY); + index = new ast_value(array->m_context, "index", TYPE_FLOAT); + value = new ast_value(ast_copy_type, *(ast_value*)array->m_next); + if (!entity || !index || !value) { + parseerror(parser, "failed to create locals for array accessor"); + goto cleanup; + } + value->m_name = "value"; // not important + fval->m_type_params.emplace_back(entity); + fval->m_type_params.emplace_back(index); + fval->m_type_params.emplace_back(value); + + root = array_field_setter_node(parser, array, entity, index, value, 0, array->m_count); + if (!root) { + parseerror(parser, "failed to build accessor search tree"); + goto cleanup; + } + + array->m_setter = fval; + return func->m_blocks[0].get()->addExpr(root); +cleanup: + if (entity) delete entity; + if (index) delete index; + if (value) delete value; + if (root) delete root; + delete func; + delete fval; + return false; +} + +static ast_value* parser_create_array_getter_proto(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname) +{ + ast_value *index = nullptr; + ast_value *fval; + ast_function *func; + + /* NOTE: checking array->m_next rather than elemtype since + * for fields elemtype is a temporary fieldtype. + */ + if (!ast_istype(array->m_next, ast_value)) { + parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type"); + return nullptr; + } + + if (!parser_create_array_accessor(parser, array, funcname, &fval)) + return nullptr; + func = fval->m_constval.vfunc; + fval->m_next = new ast_expression(ast_copy_type, array->m_context, *elemtype); + + index = new ast_value(array->m_context, "index", TYPE_FLOAT); + + if (!index) { + parseerror(parser, "failed to create locals for array accessor"); + goto cleanup; + } + fval->m_type_params.emplace_back(index); + + array->m_getter = fval; + return fval; +cleanup: + if (index) delete index; + delete func; + delete fval; + return nullptr; +} + +static bool parser_create_array_getter_impl(parser_t *parser, ast_value *array) +{ + ast_expression *root = nullptr; + + root = array_getter_node(parser, array, array->m_getter->m_type_params[0].get(), 0, array->m_count); + if (!root) { + parseerror(parser, "failed to build accessor search tree"); + return false; + } + if (!array->m_getter->m_constval.vfunc->m_blocks[0].get()->addExpr(root)) { + delete root; + return false; + } + return true; +} + +static bool parser_create_array_getter(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname) +{ + if (!parser_create_array_getter_proto(parser, array, elemtype, funcname)) + return false; + return parser_create_array_getter_impl(parser, array); +} + +static ast_value *parse_parameter_list(parser_t *parser, ast_value *var) +{ + lex_ctx_t ctx = parser_ctx(parser); + std::vector> params; + ast_value *fval; + bool first = true; + bool variadic = false; + ast_value *varparam = nullptr; + char *argcounter = nullptr; + + /* for the sake of less code we parse-in in this function */ + if (!parser_next(parser)) { + delete var; + parseerror(parser, "expected parameter list"); + return nullptr; + } + + /* parse variables until we hit a closing paren */ + while (parser->tok != ')') { + bool is_varargs = false; + + if (!first) { + /* there must be commas between them */ + if (parser->tok != ',') { + parseerror(parser, "expected comma or end of parameter list"); + goto on_error; + } + if (!parser_next(parser)) { + parseerror(parser, "expected parameter"); + goto on_error; + } + } + first = false; + + ast_value *param = parse_typename(parser, nullptr, nullptr, &is_varargs); + if (!param && !is_varargs) + goto on_error; + if (is_varargs) { + /* '...' indicates a varargs function */ + variadic = true; + if (parser->tok != ')' && parser->tok != TOKEN_IDENT) { + parseerror(parser, "`...` must be the last parameter of a variadic function declaration"); + goto on_error; + } + if (parser->tok == TOKEN_IDENT) { + argcounter = util_strdup(parser_tokval(parser)); + if (!parser_next(parser) || parser->tok != ')') { + parseerror(parser, "`...` must be the last parameter of a variadic function declaration"); + goto on_error; + } + } + } else { + params.emplace_back(param); + if (param->m_vtype >= TYPE_VARIANT) { + char tname[1024]; /* typename is reserved in C++ */ + ast_type_to_string(param, tname, sizeof(tname)); + parseerror(parser, "type not supported as part of a parameter list: %s", tname); + goto on_error; + } + /* type-restricted varargs */ + if (parser->tok == TOKEN_DOTS) { + variadic = true; + varparam = params.back().release(); + params.pop_back(); + if (!parser_next(parser) || (parser->tok != ')' && parser->tok != TOKEN_IDENT)) { + parseerror(parser, "`...` must be the last parameter of a variadic function declaration"); + goto on_error; + } + if (parser->tok == TOKEN_IDENT) { + argcounter = util_strdup(parser_tokval(parser)); + param->m_name = argcounter; + if (!parser_next(parser) || parser->tok != ')') { + parseerror(parser, "`...` must be the last parameter of a variadic function declaration"); + goto on_error; + } + } + } + if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC && param->m_name[0] == '<') { + parseerror(parser, "parameter name omitted"); + goto on_error; + } + } + } + + if (params.size() == 1 && params[0]->m_vtype == TYPE_VOID) + params.clear(); + + /* sanity check */ + if (params.size() > 8 && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) + (void)!parsewarning(parser, WARN_EXTENSIONS, "more than 8 parameters are not supported by this standard"); + + /* parse-out */ + if (!parser_next(parser)) { + parseerror(parser, "parse error after typename"); + goto on_error; + } + + /* now turn 'var' into a function type */ + fval = new ast_value(ctx, "", TYPE_FUNCTION); + fval->m_next = var; + if (variadic) + fval->m_flags |= AST_FLAG_VARIADIC; + var = fval; + + var->m_type_params = move(params); + var->m_varparam = varparam; + var->m_argcounter = argcounter; + + return var; + +on_error: + if (argcounter) + mem_d(argcounter); + if (varparam) + delete varparam; + delete var; + return nullptr; +} + +static ast_value *parse_arraysize(parser_t *parser, ast_value *var) +{ + ast_expression *cexp; + ast_value *cval, *tmp; + lex_ctx_t ctx; + + ctx = parser_ctx(parser); + + if (!parser_next(parser)) { + delete var; + parseerror(parser, "expected array-size"); + return nullptr; + } + + if (parser->tok != ']') { + cexp = parse_expression_leave(parser, true, false, false); + + if (!cexp || !ast_istype(cexp, ast_value)) { + if (cexp) + ast_unref(cexp); + delete var; + parseerror(parser, "expected array-size as constant positive integer"); + return nullptr; + } + cval = (ast_value*)cexp; + } + else { + cexp = nullptr; + cval = nullptr; + } + + tmp = new ast_value(ctx, "", TYPE_ARRAY); + tmp->m_next = var; + var = tmp; + + if (cval) { + if (cval->m_vtype == TYPE_INTEGER) + tmp->m_count = cval->m_constval.vint; + else if (cval->m_vtype == TYPE_FLOAT) + tmp->m_count = cval->m_constval.vfloat; + else { + ast_unref(cexp); + delete var; + parseerror(parser, "array-size must be a positive integer constant"); + return nullptr; + } + + ast_unref(cexp); + } else { + var->m_count = -1; + var->m_flags |= AST_FLAG_ARRAY_INIT; + } + + if (parser->tok != ']') { + delete var; + parseerror(parser, "expected ']' after array-size"); + return nullptr; + } + if (!parser_next(parser)) { + delete var; + parseerror(parser, "error after parsing array size"); + return nullptr; + } + return var; +} + +/* Parse a complete typename. + * for single-variables (ie. function parameters or typedefs) storebase should be nullptr + * but when parsing variables separated by comma + * 'storebase' should point to where the base-type should be kept. + * The base type makes up every bit of type information which comes *before* the + * variable name. + * + * NOTE: The value must either be named, have a nullptr name, or a name starting + * with '<'. In the first case, this will be the actual variable or type + * name, in the other cases it is assumed that the name will appear + * later, and an error is generated otherwise. + * + * The following will be parsed in its entirety: + * void() foo() + * The 'basetype' in this case is 'void()' + * and if there's a comma after it, say: + * void() foo(), bar + * then the type-information 'void()' can be stored in 'storebase' + */ +static ast_value *parse_typename(parser_t *parser, ast_value **storebase, ast_value *cached_typedef, bool *is_vararg) +{ + ast_value *var, *tmp; + lex_ctx_t ctx; + + const char *name = nullptr; + bool isfield = false; + bool wasarray = false; + size_t morefields = 0; + + bool vararg = (parser->tok == TOKEN_DOTS); + + ctx = parser_ctx(parser); + + /* types may start with a dot */ + if (parser->tok == '.' || parser->tok == TOKEN_DOTS) { + isfield = true; + if (parser->tok == TOKEN_DOTS) + morefields += 2; + /* if we parsed a dot we need a typename now */ + if (!parser_next(parser)) { + parseerror(parser, "expected typename for field definition"); + return nullptr; + } + + /* Further dots are handled seperately because they won't be part of the + * basetype + */ + while (true) { + if (parser->tok == '.') + ++morefields; + else if (parser->tok == TOKEN_DOTS) + morefields += 3; + else + break; + vararg = false; + if (!parser_next(parser)) { + parseerror(parser, "expected typename for field definition"); + return nullptr; + } + } + } + if (parser->tok == TOKEN_IDENT) + cached_typedef = parser_find_typedef(parser, parser_tokval(parser), 0); + if (!cached_typedef && parser->tok != TOKEN_TYPENAME) { + if (vararg && is_vararg) { + *is_vararg = true; + return nullptr; + } + parseerror(parser, "expected typename"); + return nullptr; + } + + /* generate the basic type value */ + if (cached_typedef) { + var = new ast_value(ast_copy_type, *cached_typedef); + var->m_name = ""; + } else + var = new ast_value(ctx, "", parser_token(parser)->constval.t); + + for (; morefields; --morefields) { + tmp = new ast_value(ctx, "<.type>", TYPE_FIELD); + tmp->m_next = var; + var = tmp; + } + + /* do not yet turn into a field - remember: + * .void() foo; is a field too + * .void()() foo; is a function + */ + + /* parse on */ + if (!parser_next(parser)) { + delete var; + parseerror(parser, "parse error after typename"); + return nullptr; + } + + /* an opening paren now starts the parameter-list of a function + * this is where original-QC has parameter lists. + * We allow a single parameter list here. + * Much like fteqcc we don't allow `float()() x` + */ + if (parser->tok == '(') { + var = parse_parameter_list(parser, var); + if (!var) + return nullptr; + } + + /* store the base if requested */ + if (storebase) { + *storebase = new ast_value(ast_copy_type, *var); + if (isfield) { + tmp = new ast_value(ctx, "", TYPE_FIELD); + tmp->m_next = *storebase; + *storebase = tmp; + } + } + + /* there may be a name now */ + if (parser->tok == TOKEN_IDENT || parser->tok == TOKEN_KEYWORD) { + if (!strcmp(parser_tokval(parser), "break")) + (void)!parsewarning(parser, WARN_BREAKDEF, "break definition ignored (suggest removing it)"); + else if (parser->tok == TOKEN_KEYWORD) + goto leave; + + name = util_strdup(parser_tokval(parser)); + + /* parse on */ + if (!parser_next(parser)) { + delete var; + mem_d(name); + parseerror(parser, "error after variable or field declaration"); + return nullptr; + } + } + + leave: + /* now this may be an array */ + if (parser->tok == '[') { + wasarray = true; + var = parse_arraysize(parser, var); + if (!var) { + if (name) mem_d(name); + return nullptr; + } + } + + /* This is the point where we can turn it into a field */ + if (isfield) { + /* turn it into a field if desired */ + tmp = new ast_value(ctx, "", TYPE_FIELD); + tmp->m_next = var; + var = tmp; + } + + /* now there may be function parens again */ + if (parser->tok == '(' && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) + parseerror(parser, "C-style function syntax is not allowed in -std=qcc"); + if (parser->tok == '(' && wasarray) + parseerror(parser, "arrays as part of a return type is not supported"); + while (parser->tok == '(') { + var = parse_parameter_list(parser, var); + if (!var) { + if (name) mem_d(name); + return nullptr; + } + } + + /* finally name it */ + if (name) { + var->m_name = name; + // free the name, ast_value_set_name duplicates + mem_d(name); + } + + return var; +} + +static bool parse_typedef(parser_t *parser) +{ + ast_value *typevar, *oldtype; + ast_expression *old; + + typevar = parse_typename(parser, nullptr, nullptr, nullptr); + + if (!typevar) + return false; + + // while parsing types, the ast_value's get named '' + if (!typevar->m_name.length() || typevar->m_name[0] == '<') { + parseerror(parser, "missing name in typedef"); + delete typevar; + return false; + } + + if ( (old = parser_find_var(parser, typevar->m_name)) ) { + parseerror(parser, "cannot define a type with the same name as a variable: %s\n" + " -> `%s` has been declared here: %s:%i", + typevar->m_name, old->m_context.file, old->m_context.line); + delete typevar; + return false; + } + + if ( (oldtype = parser_find_typedef(parser, typevar->m_name, vec_last(parser->_blocktypedefs))) ) { + parseerror(parser, "type `%s` has already been declared here: %s:%i", + typevar->m_name, oldtype->m_context.file, oldtype->m_context.line); + delete typevar; + return false; + } + + vec_push(parser->_typedefs, typevar); + util_htset(vec_last(parser->typedefs), typevar->m_name.c_str(), typevar); + + if (parser->tok != ';') { + parseerror(parser, "expected semicolon after typedef"); + return false; + } + if (!parser_next(parser)) { + parseerror(parser, "parse error after typedef"); + return false; + } + + return true; +} + +static const char *cvq_to_str(int cvq) { + switch (cvq) { + case CV_NONE: return "none"; + case CV_VAR: return "`var`"; + case CV_CONST: return "`const`"; + default: return ""; + } +} + +static bool parser_check_qualifiers(parser_t *parser, const ast_value *var, const ast_value *proto) +{ + bool av, ao; + if (proto->m_cvq != var->m_cvq) { + if (!(proto->m_cvq == CV_CONST && var->m_cvq == CV_NONE && + !OPTS_FLAG(INITIALIZED_NONCONSTANTS) && + parser->tok == '=')) + { + return !parsewarning(parser, WARN_DIFFERENT_QUALIFIERS, + "`%s` declared with different qualifiers: %s\n" + " -> previous declaration here: %s:%i uses %s", + var->m_name, cvq_to_str(var->m_cvq), + proto->m_context.file, proto->m_context.line, + cvq_to_str(proto->m_cvq)); + } + } + av = (var ->m_flags & AST_FLAG_NORETURN); + ao = (proto->m_flags & AST_FLAG_NORETURN); + if (!av != !ao) { + return !parsewarning(parser, WARN_DIFFERENT_ATTRIBUTES, + "`%s` declared with different attributes%s\n" + " -> previous declaration here: %s:%i", + var->m_name, (av ? ": noreturn" : ""), + proto->m_context.file, proto->m_context.line, + (ao ? ": noreturn" : "")); + } + return true; +} + +static bool create_array_accessors(parser_t *parser, ast_value *var) +{ + char name[1024]; + util_snprintf(name, sizeof(name), "%s##SET", var->m_name.c_str()); + if (!parser_create_array_setter(parser, var, name)) + return false; + util_snprintf(name, sizeof(name), "%s##GET", var->m_name.c_str()); + if (!parser_create_array_getter(parser, var, var->m_next, name)) + return false; + return true; +} + +static bool parse_array(parser_t *parser, ast_value *array) +{ + size_t i; + if (array->m_initlist.size()) { + parseerror(parser, "array already initialized elsewhere"); + return false; + } + if (!parser_next(parser)) { + parseerror(parser, "parse error in array initializer"); + return false; + } + i = 0; + while (parser->tok != '}') { + ast_value *v = (ast_value*)parse_expression_leave(parser, true, false, false); + if (!v) + return false; + if (!ast_istype(v, ast_value) || !v->m_hasvalue || v->m_cvq != CV_CONST) { + ast_unref(v); + parseerror(parser, "initializing element must be a compile time constant"); + return false; + } + array->m_initlist.push_back(v->m_constval); + if (v->m_vtype == TYPE_STRING) { + array->m_initlist[i].vstring = util_strdupe(array->m_initlist[i].vstring); + ++i; + } + ast_unref(v); + if (parser->tok == '}') + break; + if (parser->tok != ',' || !parser_next(parser)) { + parseerror(parser, "expected comma or '}' in element list"); + return false; + } + } + if (!parser_next(parser) || parser->tok != ';') { + parseerror(parser, "expected semicolon after initializer, got %s"); + return false; + } + /* + if (!parser_next(parser)) { + parseerror(parser, "parse error after initializer"); + return false; + } + */ + + if (array->m_flags & AST_FLAG_ARRAY_INIT) { + if (array->m_count != (size_t)-1) { + parseerror(parser, "array `%s' has already been initialized with %u elements", + array->m_name, (unsigned)array->m_count); + } + array->m_count = array->m_initlist.size(); + if (!create_array_accessors(parser, array)) + return false; + } + return true; +} + +static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofields, int qualifier, ast_value *cached_typedef, bool noref, bool is_static, uint32_t qflags, char *vstring) +{ + ast_value *var; + ast_value *proto; + ast_expression *old; + bool was_end; + size_t i; + + ast_value *basetype = nullptr; + bool retval = true; + bool isparam = false; + bool isvector = false; + bool cleanvar = true; + bool wasarray = false; + + ast_member *me[3] = { nullptr, nullptr, nullptr }; + ast_member *last_me[3] = { nullptr, nullptr, nullptr }; + + if (!localblock && is_static) + parseerror(parser, "`static` qualifier is not supported in global scope"); + + /* get the first complete variable */ + var = parse_typename(parser, &basetype, cached_typedef, nullptr); + if (!var) { + if (basetype) + delete basetype; + return false; + } + + /* while parsing types, the ast_value's get named '' */ + if (!var->m_name.length() || var->m_name[0] == '<') { + parseerror(parser, "declaration does not declare anything"); + if (basetype) + delete basetype; + return false; + } + + while (true) { + proto = nullptr; + wasarray = false; + + /* Part 0: finish the type */ + if (parser->tok == '(') { + if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) + parseerror(parser, "C-style function syntax is not allowed in -std=qcc"); + var = parse_parameter_list(parser, var); + if (!var) { + retval = false; + goto cleanup; + } + } + /* we only allow 1-dimensional arrays */ + if (parser->tok == '[') { + wasarray = true; + var = parse_arraysize(parser, var); + if (!var) { + retval = false; + goto cleanup; + } + } + if (parser->tok == '(' && wasarray) { + parseerror(parser, "arrays as part of a return type is not supported"); + /* we'll still parse the type completely for now */ + } + /* for functions returning functions */ + while (parser->tok == '(') { + if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) + parseerror(parser, "C-style function syntax is not allowed in -std=qcc"); + var = parse_parameter_list(parser, var); + if (!var) { + retval = false; + goto cleanup; + } + } + + var->m_cvq = qualifier; + if (qflags & AST_FLAG_COVERAGE) /* specified in QC, drop our default */ + var->m_flags &= ~(AST_FLAG_COVERAGE_MASK); + var->m_flags |= qflags; + + /* + * store the vstring back to var for alias and + * deprecation messages. + */ + if (var->m_flags & AST_FLAG_DEPRECATED || + var->m_flags & AST_FLAG_ALIAS) + var->m_desc = vstring; + + if (parser_find_global(parser, var->m_name) && var->m_flags & AST_FLAG_ALIAS) { + parseerror(parser, "function aliases cannot be forward declared"); + retval = false; + goto cleanup; + } + + + /* Part 1: + * check for validity: (end_sys_..., multiple-definitions, prototypes, ...) + * Also: if there was a prototype, `var` will be deleted and set to `proto` which + * is then filled with the previous definition and the parameter-names replaced. + */ + if (var->m_name == "nil") { + if (OPTS_FLAG(UNTYPED_NIL)) { + if (!localblock || !OPTS_FLAG(PERMISSIVE)) + parseerror(parser, "name `nil` not allowed (try -fpermissive)"); + } else + (void)!parsewarning(parser, WARN_RESERVED_NAMES, "variable name `nil` is reserved"); + } + if (!localblock) { + /* Deal with end_sys_ vars */ + was_end = false; + if (var->m_name == "end_sys_globals") { + var->m_uses++; + parser->crc_globals = parser->globals.size(); + was_end = true; + } + else if (var->m_name == "end_sys_fields") { + var->m_uses++; + parser->crc_fields = parser->fields.size(); + was_end = true; + } + if (was_end && var->m_vtype == TYPE_FIELD) { + if (parsewarning(parser, WARN_END_SYS_FIELDS, + "global '%s' hint should not be a field", + parser_tokval(parser))) + { + retval = false; + goto cleanup; + } + } + + if (!nofields && var->m_vtype == TYPE_FIELD) + { + /* deal with field declarations */ + old = parser_find_field(parser, var->m_name); + if (old) { + if (parsewarning(parser, WARN_FIELD_REDECLARED, "field `%s` already declared here: %s:%i", + var->m_name, old->m_context.file, (int)old->m_context.line)) + { + retval = false; + goto cleanup; + } + delete var; + var = nullptr; + goto skipvar; + /* + parseerror(parser, "field `%s` already declared here: %s:%i", + var->m_name, old->m_context.file, old->m_context.line); + retval = false; + goto cleanup; + */ + } + if ((OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC || OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) && + (old = parser_find_global(parser, var->m_name))) + { + parseerror(parser, "cannot declare a field and a global of the same name with -std=qcc"); + parseerror(parser, "field `%s` already declared here: %s:%i", + var->m_name, old->m_context.file, old->m_context.line); + retval = false; + goto cleanup; + } + } + else + { + /* deal with other globals */ + old = parser_find_global(parser, var->m_name); + if (old && var->m_vtype == TYPE_FUNCTION && old->m_vtype == TYPE_FUNCTION) + { + /* This is a function which had a prototype */ + if (!ast_istype(old, ast_value)) { + parseerror(parser, "internal error: prototype is not an ast_value"); + retval = false; + goto cleanup; + } + proto = (ast_value*)old; + proto->m_desc = var->m_desc; + if (!proto->compareType(*var)) { + parseerror(parser, "conflicting types for `%s`, previous declaration was here: %s:%i", + proto->m_name, + proto->m_context.file, proto->m_context.line); + retval = false; + goto cleanup; + } + /* we need the new parameter-names */ + for (i = 0; i < proto->m_type_params.size(); ++i) + proto->m_type_params[i]->m_name = var->m_type_params[i]->m_name; + if (!parser_check_qualifiers(parser, var, proto)) { + retval = false; + proto = nullptr; + goto cleanup; + } + proto->m_flags |= var->m_flags; + delete var; + var = proto; + } + else + { + /* other globals */ + if (old) { + if (parsewarning(parser, WARN_DOUBLE_DECLARATION, + "global `%s` already declared here: %s:%i", + var->m_name, old->m_context.file, old->m_context.line)) + { + retval = false; + goto cleanup; + } + if (old->m_flags & AST_FLAG_FINAL_DECL) { + parseerror(parser, "cannot redeclare variable `%s`, declared final here: %s:%i", + var->m_name, old->m_context.file, old->m_context.line); + retval = false; + goto cleanup; + } + proto = (ast_value*)old; + if (!ast_istype(old, ast_value)) { + parseerror(parser, "internal error: not an ast_value"); + retval = false; + proto = nullptr; + goto cleanup; + } + if (!parser_check_qualifiers(parser, var, proto)) { + retval = false; + proto = nullptr; + goto cleanup; + } + proto->m_flags |= var->m_flags; + /* copy the context for finals, + * so the error can show where it was actually made 'final' + */ + if (proto->m_flags & AST_FLAG_FINAL_DECL) + old->m_context = var->m_context; + delete var; + var = proto; + } + if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC && + (old = parser_find_field(parser, var->m_name))) + { + parseerror(parser, "cannot declare a field and a global of the same name with -std=qcc"); + parseerror(parser, "global `%s` already declared here: %s:%i", + var->m_name, old->m_context.file, old->m_context.line); + retval = false; + goto cleanup; + } + } + } + } + else /* it's not a global */ + { + old = parser_find_local(parser, var->m_name, vec_size(parser->variables)-1, &isparam); + if (old && !isparam) { + parseerror(parser, "local `%s` already declared here: %s:%i", + var->m_name, old->m_context.file, (int)old->m_context.line); + retval = false; + goto cleanup; + } + /* doing this here as the above is just for a single scope */ + old = parser_find_local(parser, var->m_name, 0, &isparam); + if (old && isparam) { + if (parsewarning(parser, WARN_LOCAL_SHADOWS, + "local `%s` is shadowing a parameter", var->m_name)) + { + parseerror(parser, "local `%s` already declared here: %s:%i", + var->m_name, old->m_context.file, (int)old->m_context.line); + retval = false; + goto cleanup; + } + if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_GMQCC) { + delete var; + if (ast_istype(old, ast_value)) + var = proto = (ast_value*)old; + else { + var = nullptr; + goto skipvar; + } + } + } + } + + /* in a noref section we simply bump the usecount */ + if (noref || parser->noref) + var->m_uses++; + + /* Part 2: + * Create the global/local, and deal with vector types. + */ + if (!proto) { + if (var->m_vtype == TYPE_VECTOR) + isvector = true; + else if (var->m_vtype == TYPE_FIELD && + var->m_next->m_vtype == TYPE_VECTOR) + isvector = true; + + if (isvector) { + if (!create_vector_members(var, me)) { + retval = false; + goto cleanup; + } + } + + if (!localblock) { + /* deal with global variables, fields, functions */ + if (!nofields && var->m_vtype == TYPE_FIELD && parser->tok != '=') { + var->m_isfield = true; + parser->fields.push_back(var); + util_htset(parser->htfields, var->m_name.c_str(), var); + if (isvector) { + for (i = 0; i < 3; ++i) { + parser->fields.push_back(me[i]); + util_htset(parser->htfields, me[i]->m_name.c_str(), me[i]); + } + } + } + else { + if (!(var->m_flags & AST_FLAG_ALIAS)) { + parser_addglobal(parser, var->m_name, var); + if (isvector) { + for (i = 0; i < 3; ++i) { + parser_addglobal(parser, me[i]->m_name.c_str(), me[i]); + } + } + } else { + ast_expression *find = parser_find_global(parser, var->m_desc); + + if (!find) { + compile_error(parser_ctx(parser), "undeclared variable `%s` for alias `%s`", var->m_desc, var->m_name); + return false; + } + + if (!var->compareType(*find)) { + char ty1[1024]; + char ty2[1024]; + + ast_type_to_string(find, ty1, sizeof(ty1)); + ast_type_to_string(var, ty2, sizeof(ty2)); + + compile_error(parser_ctx(parser), "incompatible types `%s` and `%s` for alias `%s`", + ty1, ty2, var->m_name + ); + return false; + } + + util_htset(parser->aliases, var->m_name.c_str(), find); + + /* generate aliases for vector components */ + if (isvector) { + char *buffer[3]; + + util_asprintf(&buffer[0], "%s_x", var->m_desc.c_str()); + util_asprintf(&buffer[1], "%s_y", var->m_desc.c_str()); + util_asprintf(&buffer[2], "%s_z", var->m_desc.c_str()); + + util_htset(parser->aliases, me[0]->m_name.c_str(), parser_find_global(parser, buffer[0])); + util_htset(parser->aliases, me[1]->m_name.c_str(), parser_find_global(parser, buffer[1])); + util_htset(parser->aliases, me[2]->m_name.c_str(), parser_find_global(parser, buffer[2])); + + mem_d(buffer[0]); + mem_d(buffer[1]); + mem_d(buffer[2]); + } + } + } + } else { + if (is_static) { + // a static adds itself to be generated like any other global + // but is added to the local namespace instead + std::string defname; + size_t prefix_len; + size_t sn, sn_size; + + defname = parser->function->m_name; + defname.append(2, ':'); + + // remember the length up to here + prefix_len = defname.length(); + + // Add it to the local scope + util_htset(vec_last(parser->variables), var->m_name.c_str(), (void*)var); + + // now rename the global + defname.append(var->m_name); + // if a variable of that name already existed, add the + // counter value. + // The counter is incremented either way. + sn_size = parser->function->m_static_names.size(); + for (sn = 0; sn != sn_size; ++sn) { + if (parser->function->m_static_names[sn] == var->m_name.c_str()) + break; + } + if (sn != sn_size) { + char *num = nullptr; + int len = util_asprintf(&num, "#%u", parser->function->m_static_count); + defname.append(num, 0, len); + mem_d(num); + } + else + parser->function->m_static_names.emplace_back(var->m_name); + parser->function->m_static_count++; + var->m_name = defname; + + // push it to the to-be-generated globals + parser->globals.push_back(var); + + // same game for the vector members + if (isvector) { + defname.erase(prefix_len); + for (i = 0; i < 3; ++i) { + util_htset(vec_last(parser->variables), me[i]->m_name.c_str(), (void*)(me[i])); + me[i]->m_name = move(defname + me[i]->m_name); + parser->globals.push_back(me[i]); + } + } + } else { + localblock->m_locals.push_back(var); + parser_addlocal(parser, var->m_name, var); + if (isvector) { + for (i = 0; i < 3; ++i) { + parser_addlocal(parser, me[i]->m_name, me[i]); + localblock->collect(me[i]); + } + } + } + } + } + memcpy(last_me, me, sizeof(me)); + me[0] = me[1] = me[2] = nullptr; + cleanvar = false; + /* Part 2.2 + * deal with arrays + */ + if (var->m_vtype == TYPE_ARRAY) { + if (var->m_count != (size_t)-1) { + if (!create_array_accessors(parser, var)) + goto cleanup; + } + } + else if (!localblock && !nofields && + var->m_vtype == TYPE_FIELD && + var->m_next->m_vtype == TYPE_ARRAY) + { + char name[1024]; + ast_expression *telem; + ast_value *tfield; + ast_value *array = (ast_value*)var->m_next; + + if (!ast_istype(var->m_next, ast_value)) { + parseerror(parser, "internal error: field element type must be an ast_value"); + goto cleanup; + } + + util_snprintf(name, sizeof(name), "%s##SETF", var->m_name.c_str()); + if (!parser_create_array_field_setter(parser, array, name)) + goto cleanup; + + telem = new ast_expression(ast_copy_type, var->m_context, *array->m_next); + tfield = new ast_value(var->m_context, "<.type>", TYPE_FIELD); + tfield->m_next = telem; + util_snprintf(name, sizeof(name), "%s##GETFP", var->m_name.c_str()); + if (!parser_create_array_getter(parser, array, tfield, name)) { + delete tfield; + goto cleanup; + } + delete tfield; + } + +skipvar: + if (parser->tok == ';') { + delete basetype; + if (!parser_next(parser)) { + parseerror(parser, "error after variable declaration"); + return false; + } + return true; + } + + if (parser->tok == ',') + goto another; + + /* + if (!var || (!localblock && !nofields && basetype->m_vtype == TYPE_FIELD)) { + */ + if (!var) { + parseerror(parser, "missing comma or semicolon while parsing variables"); + break; + } + + if (localblock && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { + if (parsewarning(parser, WARN_LOCAL_CONSTANTS, + "initializing expression turns variable `%s` into a constant in this standard", + var->m_name) ) + { + break; + } + } + + if (parser->tok != '{' || var->m_vtype != TYPE_FUNCTION) { + if (parser->tok != '=') { + parseerror(parser, "missing semicolon or initializer, got: `%s`", parser_tokval(parser)); + break; + } + + if (!parser_next(parser)) { + parseerror(parser, "error parsing initializer"); + break; + } + } + else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { + parseerror(parser, "expected '=' before function body in this standard"); + } + + if (parser->tok == '#') { + ast_function *func = nullptr; + ast_value *number = nullptr; + float fractional; + float integral; + int builtin_num; + + if (localblock) { + parseerror(parser, "cannot declare builtins within functions"); + break; + } + if (var->m_vtype != TYPE_FUNCTION) { + parseerror(parser, "unexpected builtin number, '%s' is not a function", var->m_name); + break; + } + if (!parser_next(parser)) { + parseerror(parser, "expected builtin number"); + break; + } + + if (OPTS_FLAG(EXPRESSIONS_FOR_BUILTINS)) { + number = (ast_value*)parse_expression_leave(parser, true, false, false); + if (!number) { + parseerror(parser, "builtin number expected"); + break; + } + if (!ast_istype(number, ast_value) || !number->m_hasvalue || number->m_cvq != CV_CONST) + { + ast_unref(number); + parseerror(parser, "builtin number must be a compile time constant"); + break; + } + if (number->m_vtype == TYPE_INTEGER) + builtin_num = number->m_constval.vint; + else if (number->m_vtype == TYPE_FLOAT) + builtin_num = number->m_constval.vfloat; + else { + ast_unref(number); + parseerror(parser, "builtin number must be an integer constant"); + break; + } + ast_unref(number); + + fractional = modff(builtin_num, &integral); + if (builtin_num < 0 || fractional != 0) { + parseerror(parser, "builtin number must be an integer greater than zero"); + break; + } + + /* we only want the integral part anyways */ + builtin_num = integral; + } else if (parser->tok == TOKEN_INTCONST) { + builtin_num = parser_token(parser)->constval.i; + } else { + parseerror(parser, "builtin number must be a compile time constant"); + break; + } + + if (var->m_hasvalue) { + (void)!parsewarning(parser, WARN_DOUBLE_DECLARATION, + "builtin `%s` has already been defined\n" + " -> previous declaration here: %s:%i", + var->m_name, var->m_context.file, (int)var->m_context.line); + } + else + { + func = ast_function::make(var->m_context, var->m_name, var); + if (!func) { + parseerror(parser, "failed to allocate function for `%s`", var->m_name); + break; + } + parser->functions.push_back(func); + + func->m_builtin = -builtin_num-1; + } + + if (OPTS_FLAG(EXPRESSIONS_FOR_BUILTINS) + ? (parser->tok != ',' && parser->tok != ';') + : (!parser_next(parser))) + { + parseerror(parser, "expected comma or semicolon"); + delete func; + var->m_constval.vfunc = nullptr; + break; + } + } + else if (var->m_vtype == TYPE_ARRAY && parser->tok == '{') + { + if (localblock) { + /* Note that fteqcc and most others don't even *have* + * local arrays, so this is not a high priority. + */ + parseerror(parser, "TODO: initializers for local arrays"); + break; + } + + var->m_hasvalue = true; + if (!parse_array(parser, var)) + break; + } + else if (var->m_vtype == TYPE_FUNCTION && (parser->tok == '{' || parser->tok == '[')) + { + if (localblock) { + parseerror(parser, "cannot declare functions within functions"); + break; + } + + if (proto) + proto->m_context = parser_ctx(parser); + + if (!parse_function_body(parser, var)) + break; + delete basetype; + for (auto &it : parser->gotos) + parseerror(parser, "undefined label: `%s`", it->m_name); + parser->gotos.clear(); + parser->labels.clear(); + return true; + } else { + ast_expression *cexp; + ast_value *cval; + bool folded_const = false; + + cexp = parse_expression_leave(parser, true, false, false); + if (!cexp) + break; + cval = ast_istype(cexp, ast_value) ? (ast_value*)cexp : nullptr; + + /* deal with foldable constants: */ + if (localblock && + var->m_cvq == CV_CONST && cval && cval->m_hasvalue && cval->m_cvq == CV_CONST && !cval->m_isfield) + { + /* remove it from the current locals */ + if (isvector) { + for (i = 0; i < 3; ++i) { + vec_pop(parser->_locals); + localblock->m_collect.pop_back(); + } + } + /* do sanity checking, this function really needs refactoring */ + if (vec_last(parser->_locals) != var) + parseerror(parser, "internal error: unexpected change in local variable handling"); + else + vec_pop(parser->_locals); + if (localblock->m_locals.back() != var) + parseerror(parser, "internal error: unexpected change in local variable handling (2)"); + else + localblock->m_locals.pop_back(); + /* push it to the to-be-generated globals */ + parser->globals.push_back(var); + if (isvector) + for (i = 0; i < 3; ++i) + parser->globals.push_back(last_me[i]); + folded_const = true; + } + + if (folded_const || !localblock || is_static) { + if (cval != parser->nil && + (!cval || ((!cval->m_hasvalue || cval->m_cvq != CV_CONST) && !cval->m_isfield)) + ) + { + parseerror(parser, "initializer is non constant"); + } + else + { + if (!is_static && + !OPTS_FLAG(INITIALIZED_NONCONSTANTS) && + qualifier != CV_VAR) + { + var->m_cvq = CV_CONST; + } + if (cval == parser->nil) + var->m_flags |= AST_FLAG_INITIALIZED; + else + { + var->m_hasvalue = true; + if (cval->m_vtype == TYPE_STRING) + var->m_constval.vstring = parser_strdup(cval->m_constval.vstring); + else if (cval->m_vtype == TYPE_FIELD) + var->m_constval.vfield = cval; + else + memcpy(&var->m_constval, &cval->m_constval, sizeof(var->m_constval)); + ast_unref(cval); + } + } + } else { + int cvq; + shunt sy; + cvq = var->m_cvq; + var->m_cvq = CV_NONE; + sy.out.push_back(syexp(var->m_context, var)); + sy.out.push_back(syexp(cexp->m_context, cexp)); + sy.ops.push_back(syop(var->m_context, parser->assign_op)); + if (!parser_sy_apply_operator(parser, &sy)) + ast_unref(cexp); + else { + if (sy.out.size() != 1 && sy.ops.size() != 0) + parseerror(parser, "internal error: leaked operands"); + if (!localblock->addExpr(sy.out[0].out)) + break; + } + var->m_cvq = cvq; + } + /* a constant initialized to an inexact value should be marked inexact: + * const float x = ; should propagate the inexact flag + */ + if (var->m_cvq == CV_CONST && var->m_vtype == TYPE_FLOAT) { + if (cval && cval->m_hasvalue && cval->m_cvq == CV_CONST) + var->m_inexact = cval->m_inexact; + } + } + +another: + if (parser->tok == ',') { + if (!parser_next(parser)) { + parseerror(parser, "expected another variable"); + break; + } + + if (parser->tok != TOKEN_IDENT) { + parseerror(parser, "expected another variable"); + break; + } + var = new ast_value(ast_copy_type, *basetype); + cleanvar = true; + var->m_name = parser_tokval(parser); + if (!parser_next(parser)) { + parseerror(parser, "error parsing variable declaration"); + break; + } + continue; + } + + if (parser->tok != ';') { + parseerror(parser, "missing semicolon after variables"); + break; + } + + if (!parser_next(parser)) { + parseerror(parser, "parse error after variable declaration"); + break; + } + + delete basetype; + return true; + } + + if (cleanvar && var) + delete var; + delete basetype; + return false; + +cleanup: + delete basetype; + if (cleanvar && var) + delete var; + delete me[0]; + delete me[1]; + delete me[2]; + return retval; +} + +static bool parser_global_statement(parser_t *parser) +{ + int cvq = CV_WRONG; + bool noref = false; + bool is_static = false; + uint32_t qflags = 0; + ast_value *istype = nullptr; + char *vstring = nullptr; + + if (parser->tok == TOKEN_IDENT) + istype = parser_find_typedef(parser, parser_tokval(parser), 0); + + if (istype || parser->tok == TOKEN_TYPENAME || parser->tok == '.' || parser->tok == TOKEN_DOTS) + { + return parse_variable(parser, nullptr, false, CV_NONE, istype, false, false, 0, nullptr); + } + else if (parse_qualifiers(parser, false, &cvq, &noref, &is_static, &qflags, &vstring)) + { + if (cvq == CV_WRONG) + return false; + return parse_variable(parser, nullptr, false, cvq, nullptr, noref, is_static, qflags, vstring); + } + else if (parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "enum")) + { + return parse_enum(parser); + } + else if (parser->tok == TOKEN_KEYWORD) + { + if (!strcmp(parser_tokval(parser), "typedef")) { + if (!parser_next(parser)) { + parseerror(parser, "expected type definition after 'typedef'"); + return false; + } + return parse_typedef(parser); + } + parseerror(parser, "unrecognized keyword `%s`", parser_tokval(parser)); + return false; + } + else if (parser->tok == '#') + { + return parse_pragma(parser); + } + else if (parser->tok == '$') + { + if (!parser_next(parser)) { + parseerror(parser, "parse error"); + return false; + } + } + else + { + parseerror(parser, "unexpected token: `%s`", parser->lex->tok.value); + return false; + } + return true; +} + +static uint16_t progdefs_crc_sum(uint16_t old, const char *str) +{ + return util_crc16(old, str, strlen(str)); +} + +static void progdefs_crc_file(const char *str) +{ + /* write to progdefs.h here */ + (void)str; +} + +static uint16_t progdefs_crc_both(uint16_t old, const char *str) +{ + old = progdefs_crc_sum(old, str); + progdefs_crc_file(str); + return old; +} + +static void generate_checksum(parser_t *parser, ir_builder *ir) +{ + uint16_t crc = 0xFFFF; + size_t i; + ast_value *value; + + crc = progdefs_crc_both(crc, "\n/* file generated by qcc, do not modify */\n\ntypedef struct\n{"); + crc = progdefs_crc_sum(crc, "\tint\tpad[28];\n"); + /* + progdefs_crc_file("\tint\tpad;\n"); + progdefs_crc_file("\tint\tofs_return[3];\n"); + progdefs_crc_file("\tint\tofs_parm0[3];\n"); + progdefs_crc_file("\tint\tofs_parm1[3];\n"); + progdefs_crc_file("\tint\tofs_parm2[3];\n"); + progdefs_crc_file("\tint\tofs_parm3[3];\n"); + progdefs_crc_file("\tint\tofs_parm4[3];\n"); + progdefs_crc_file("\tint\tofs_parm5[3];\n"); + progdefs_crc_file("\tint\tofs_parm6[3];\n"); + progdefs_crc_file("\tint\tofs_parm7[3];\n"); + */ + for (i = 0; i < parser->crc_globals; ++i) { + if (!ast_istype(parser->globals[i], ast_value)) + continue; + value = (ast_value*)(parser->globals[i]); + switch (value->m_vtype) { + case TYPE_FLOAT: crc = progdefs_crc_both(crc, "\tfloat\t"); break; + case TYPE_VECTOR: crc = progdefs_crc_both(crc, "\tvec3_t\t"); break; + case TYPE_STRING: crc = progdefs_crc_both(crc, "\tstring_t\t"); break; + case TYPE_FUNCTION: crc = progdefs_crc_both(crc, "\tfunc_t\t"); break; + default: + crc = progdefs_crc_both(crc, "\tint\t"); + break; + } + crc = progdefs_crc_both(crc, value->m_name.c_str()); + crc = progdefs_crc_both(crc, ";\n"); + } + crc = progdefs_crc_both(crc, "} globalvars_t;\n\ntypedef struct\n{\n"); + for (i = 0; i < parser->crc_fields; ++i) { + if (!ast_istype(parser->fields[i], ast_value)) + continue; + value = (ast_value*)(parser->fields[i]); + switch (value->m_next->m_vtype) { + case TYPE_FLOAT: crc = progdefs_crc_both(crc, "\tfloat\t"); break; + case TYPE_VECTOR: crc = progdefs_crc_both(crc, "\tvec3_t\t"); break; + case TYPE_STRING: crc = progdefs_crc_both(crc, "\tstring_t\t"); break; + case TYPE_FUNCTION: crc = progdefs_crc_both(crc, "\tfunc_t\t"); break; + default: + crc = progdefs_crc_both(crc, "\tint\t"); + break; + } + crc = progdefs_crc_both(crc, value->m_name.c_str()); + crc = progdefs_crc_both(crc, ";\n"); + } + crc = progdefs_crc_both(crc, "} entvars_t;\n\n"); + ir->m_code->crc = crc; +} + +parser_t *parser_create() +{ + parser_t *parser; + lex_ctx_t empty_ctx; + size_t i; + + parser = (parser_t*)mem_a(sizeof(parser_t)); + if (!parser) + return nullptr; + + memset(parser, 0, sizeof(*parser)); + + // TODO: remove + new (parser) parser_t(); + + for (i = 0; i < operator_count; ++i) { + if (operators[i].id == opid1('=')) { + parser->assign_op = operators+i; + break; + } + } + if (!parser->assign_op) { + con_err("internal error: initializing parser: failed to find assign operator\n"); + mem_d(parser); + return nullptr; + } + + vec_push(parser->variables, parser->htfields = util_htnew(PARSER_HT_SIZE)); + vec_push(parser->variables, parser->htglobals = util_htnew(PARSER_HT_SIZE)); + vec_push(parser->typedefs, util_htnew(TYPEDEF_HT_SIZE)); + vec_push(parser->_blocktypedefs, 0); + + parser->aliases = util_htnew(PARSER_HT_SIZE); + + empty_ctx.file = ""; + empty_ctx.line = 0; + empty_ctx.column = 0; + parser->nil = new ast_value(empty_ctx, "nil", TYPE_NIL); + parser->nil->m_cvq = CV_CONST; + if (OPTS_FLAG(UNTYPED_NIL)) + util_htset(parser->htglobals, "nil", (void*)parser->nil); + + parser->max_param_count = 1; + + parser->const_vec[0] = new ast_value(empty_ctx, "", TYPE_NOEXPR); + parser->const_vec[1] = new ast_value(empty_ctx, "", TYPE_NOEXPR); + parser->const_vec[2] = new ast_value(empty_ctx, "", TYPE_NOEXPR); + + if (OPTS_OPTION_BOOL(OPTION_ADD_INFO)) { + parser->reserved_version = new ast_value(empty_ctx, "reserved:version", TYPE_STRING); + parser->reserved_version->m_cvq = CV_CONST; + parser->reserved_version->m_hasvalue = true; + parser->reserved_version->m_flags |= AST_FLAG_INCLUDE_DEF; + parser->reserved_version->m_constval.vstring = util_strdup(GMQCC_FULL_VERSION_STRING); + } else { + parser->reserved_version = nullptr; + } + + parser->m_fold = fold(parser); + parser->m_intrin = intrin(parser); + return parser; +} + +static bool parser_compile(parser_t *parser) +{ + /* initial lexer/parser state */ + parser->lex->flags.noops = true; + + if (parser_next(parser)) + { + while (parser->tok != TOKEN_EOF && parser->tok < TOKEN_ERROR) + { + if (!parser_global_statement(parser)) { + if (parser->tok == TOKEN_EOF) + parseerror(parser, "unexpected end of file"); + else if (compile_errors) + parseerror(parser, "there have been errors, bailing out"); + lex_close(parser->lex); + parser->lex = nullptr; + return false; + } + } + } else { + parseerror(parser, "parse error"); + lex_close(parser->lex); + parser->lex = nullptr; + return false; + } + + lex_close(parser->lex); + parser->lex = nullptr; + + return !compile_errors; +} + +bool parser_compile_file(parser_t *parser, const char *filename) +{ + parser->lex = lex_open(filename); + if (!parser->lex) { + con_err("failed to open file \"%s\"\n", filename); + return false; + } + return parser_compile(parser); +} + +bool parser_compile_string(parser_t *parser, const char *name, const char *str, size_t len) +{ + parser->lex = lex_open_string(str, len, name); + if (!parser->lex) { + con_err("failed to create lexer for string \"%s\"\n", name); + return false; + } + return parser_compile(parser); +} + +static void parser_remove_ast(parser_t *parser) +{ + size_t i; + if (parser->ast_cleaned) + return; + parser->ast_cleaned = true; + for (auto &it : parser->accessors) { + delete it->m_constval.vfunc; + it->m_constval.vfunc = nullptr; + delete it; + } + for (auto &it : parser->functions) delete it; + for (auto &it : parser->globals) delete it; + for (auto &it : parser->fields) delete it; + + for (i = 0; i < vec_size(parser->variables); ++i) + util_htdel(parser->variables[i]); + vec_free(parser->variables); + vec_free(parser->_blocklocals); + vec_free(parser->_locals); + + for (i = 0; i < vec_size(parser->_typedefs); ++i) + delete parser->_typedefs[i]; + vec_free(parser->_typedefs); + for (i = 0; i < vec_size(parser->typedefs); ++i) + util_htdel(parser->typedefs[i]); + vec_free(parser->typedefs); + vec_free(parser->_blocktypedefs); + + vec_free(parser->_block_ctx); + + delete parser->nil; + + delete parser->const_vec[0]; + delete parser->const_vec[1]; + delete parser->const_vec[2]; + + if (parser->reserved_version) + delete parser->reserved_version; + + util_htdel(parser->aliases); +} + +void parser_cleanup(parser_t *parser) +{ + parser_remove_ast(parser); + parser->~parser_t(); + mem_d(parser); +} + +static bool parser_set_coverage_func(parser_t *parser, ir_builder *ir) { + ast_expression *expr; + ast_value *cov; + ast_function *func; + + if (!OPTS_OPTION_BOOL(OPTION_COVERAGE)) + return true; + + func = nullptr; + for (auto &it : parser->functions) { + if (it->m_name == "coverage") { + func = it; + break; + } + } + if (!func) { + if (OPTS_OPTION_BOOL(OPTION_COVERAGE)) { + con_out("coverage support requested but no coverage() builtin declared\n"); + delete ir; + return false; + } + return true; + } + + cov = func->m_function_type; + expr = cov; + + if (expr->m_vtype != TYPE_FUNCTION || expr->m_type_params.size()) { + char ty[1024]; + ast_type_to_string(expr, ty, sizeof(ty)); + con_out("invalid type for coverage(): %s\n", ty); + delete ir; + return false; + } + + ir->m_coverage_func = func->m_ir_func->m_value; + return true; +} + +bool parser_finish(parser_t *parser, const char *output) +{ + ir_builder *ir; + bool retval = true; + + if (compile_errors) { + con_out("*** there were compile errors\n"); + return false; + } + + ir = new ir_builder("gmqcc_out"); + if (!ir) { + con_out("failed to allocate builder\n"); + return false; + } + + for (auto &it : parser->fields) { + bool hasvalue; + if (!ast_istype(it, ast_value)) + continue; + ast_value *field = (ast_value*)it; + hasvalue = field->m_hasvalue; + field->m_hasvalue = false; + if (!reinterpret_cast(field)->generateGlobal(ir, true)) { + con_out("failed to generate field %s\n", field->m_name.c_str()); + delete ir; + return false; + } + if (hasvalue) { + ir_value *ifld; + ast_expression *subtype; + field->m_hasvalue = true; + subtype = field->m_next; + ifld = ir->createField(field->m_name, subtype->m_vtype); + if (subtype->m_vtype == TYPE_FIELD) + ifld->m_fieldtype = subtype->m_next->m_vtype; + else if (subtype->m_vtype == TYPE_FUNCTION) + ifld->m_outtype = subtype->m_next->m_vtype; + (void)!field->m_ir_v->setField(ifld); + } + } + for (auto &it : parser->globals) { + ast_value *asvalue; + if (!ast_istype(it, ast_value)) + continue; + asvalue = (ast_value*)it; + if (!asvalue->m_uses && !asvalue->m_hasvalue && asvalue->m_vtype != TYPE_FUNCTION) { + retval = retval && !compile_warning(asvalue->m_context, WARN_UNUSED_VARIABLE, + "unused global: `%s`", asvalue->m_name); + } + if (!asvalue->generateGlobal(ir, false)) { + con_out("failed to generate global %s\n", asvalue->m_name.c_str()); + delete ir; + return false; + } + } + /* Build function vararg accessor ast tree now before generating + * immediates, because the accessors may add new immediates + */ + for (auto &f : parser->functions) { + if (f->m_varargs) { + if (parser->max_param_count > f->m_function_type->m_type_params.size()) { + f->m_varargs->m_count = parser->max_param_count - f->m_function_type->m_type_params.size(); + if (!parser_create_array_setter_impl(parser, f->m_varargs.get())) { + con_out("failed to generate vararg setter for %s\n", f->m_name.c_str()); + delete ir; + return false; + } + if (!parser_create_array_getter_impl(parser, f->m_varargs.get())) { + con_out("failed to generate vararg getter for %s\n", f->m_name.c_str()); + delete ir; + return false; + } + } else { + f->m_varargs = nullptr; + } + } + } + /* Now we can generate immediates */ + if (!parser->m_fold.generate(ir)) + return false; + + /* before generating any functions we need to set the coverage_func */ + if (!parser_set_coverage_func(parser, ir)) + return false; + for (auto &it : parser->globals) { + if (!ast_istype(it, ast_value)) + continue; + ast_value *asvalue = (ast_value*)it; + if (!(asvalue->m_flags & AST_FLAG_INITIALIZED)) + { + if (asvalue->m_cvq == CV_CONST && !asvalue->m_hasvalue) + (void)!compile_warning(asvalue->m_context, WARN_UNINITIALIZED_CONSTANT, + "uninitialized constant: `%s`", + asvalue->m_name); + else if ((asvalue->m_cvq == CV_NONE || asvalue->m_cvq == CV_CONST) && !asvalue->m_hasvalue) + (void)!compile_warning(asvalue->m_context, WARN_UNINITIALIZED_GLOBAL, + "uninitialized global: `%s`", + asvalue->m_name); + } + if (!asvalue->generateAccessors(ir)) { + delete ir; + return false; + } + } + for (auto &it : parser->fields) { + ast_value *asvalue = (ast_value*)it->m_next; + if (!ast_istype(asvalue, ast_value)) + continue; + if (asvalue->m_vtype != TYPE_ARRAY) + continue; + if (!asvalue->generateAccessors(ir)) { + delete ir; + return false; + } + } + if (parser->reserved_version && + !parser->reserved_version->generateGlobal(ir, false)) + { + con_out("failed to generate reserved::version"); + delete ir; + return false; + } + for (auto &f : parser->functions) { + if (!f->generateFunction(ir)) { + con_out("failed to generate function %s\n", f->m_name.c_str()); + delete ir; + return false; + } + } + + generate_checksum(parser, ir); + + if (OPTS_OPTION_BOOL(OPTION_DUMP)) + ir->dump(con_out); + for (auto &it : parser->functions) { + if (!ir_function_finalize(it->m_ir_func)) { + con_out("failed to finalize function %s\n", it->m_name.c_str()); + delete ir; + return false; + } + } + parser_remove_ast(parser); + + if (compile_Werrors) { + con_out("*** there were warnings treated as errors\n"); + compile_show_werrors(); + retval = false; + } + + if (retval) { + if (OPTS_OPTION_BOOL(OPTION_DUMPFIN)) + ir->dump(con_out); + + if (!ir->generate(output)) { + con_out("*** failed to generate output file\n"); + delete ir; + return false; + } + } + delete ir; + return retval; +} diff --git a/parser.h b/parser.h index c6fe91d..a09238d 100644 --- a/parser.h +++ b/parser.h @@ -1,73 +1,31 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * Wolfgang Bumiller - * 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. - */ #ifndef GMQCC_PARSER_HDR #define GMQCC_PARSER_HDR #include "gmqcc.h" #include "lexer.h" -#include "ast.h" - -typedef struct intrin_s intrin_t; -typedef struct parser_s parser_t; - -typedef struct { - struct parser_s *parser; - ast_value **imm_float; /* vector */ - ast_value **imm_vector; /* vector */ - ast_value **imm_string; /* vector */ - hash_table_t *imm_string_untranslate; /* map */ - hash_table_t *imm_string_dotranslate; /* map */ -} fold_t; - -typedef struct { - ast_expression *(*intrin)(intrin_t *); - const char *name; - const char *alias; - size_t args; -} intrin_func_t; - -struct intrin_s { - intrin_func_t *intrinsics; /* vector */ - ast_expression **generated; /* vector */ - parser_t *parser; - fold_t *fold; -}; +//#include "ast.h" + +#include "intrin.h" +#include "fold.h" + +struct parser_t; #define parser_ctx(p) ((p)->lex->tok.ctx) -struct parser_s { +struct parser_t { + parser_t() { } + lex_file *lex; - int tok; + int tok; - bool ast_cleaned; + bool ast_cleaned; - ast_expression **globals; - ast_expression **fields; - ast_function **functions; - size_t translated; + std::vector globals; + std::vector fields; + std::vector functions; + size_t translated; /* must be deleted first, they reference immediates and values */ - ast_value **accessors; + std::vector accessors; ast_value *nil; ast_value *reserved_version; @@ -76,15 +34,15 @@ struct parser_s { size_t crc_fields; ast_function *function; - ht aliases; + ht aliases; /* All the labels the function defined... * Should they be in ast_function instead? */ - ast_label **labels; - ast_goto **gotos; - const char **breaks; - const char **continues; + std::vector labels; + std::vector gotos; + std::vector breaks; + std::vector continues; /* A list of hashtables for each scope */ ht *variables; @@ -92,16 +50,12 @@ struct parser_s { ht htglobals; ht *typedefs; - /* same as above but for the spelling corrector */ - correct_trie_t **correct_variables; - size_t ***correct_variables_score; /* vector of vector of size_t* */ - /* not to be used directly, we use the hash table */ ast_expression **_locals; - size_t *_blocklocals; - ast_value **_typedefs; - size_t *_blocktypedefs; - lex_ctx_t *_block_ctx; + size_t *_blocklocals; + ast_value **_typedefs; + size_t *_blocktypedefs; + lex_ctx_t *_block_ctx; /* we store the '=' operator info */ const oper_info *assign_op; @@ -113,10 +67,10 @@ struct parser_s { bool noref; /* collected information */ - size_t max_param_count; + size_t max_param_count; - fold_t *fold; - intrin_t *intrin; + fold m_fold; + intrin m_intrin; }; @@ -124,25 +78,4 @@ struct parser_s { char *parser_strdup (const char *str); ast_expression *parser_find_global(parser_t *parser, const char *name); -/* fold.c */ -fold_t *fold_init (parser_t *); -void fold_cleanup (fold_t *); -ast_expression *fold_constgen_float (fold_t *, qcfloat_t, bool); -ast_expression *fold_constgen_vector(fold_t *, vec3_t); -ast_expression *fold_constgen_string(fold_t *, const char *, bool); -bool fold_generate (fold_t *, ir_builder *); -ast_expression *fold_op (fold_t *, const oper_info *, ast_expression **); -ast_expression *fold_intrin (fold_t *, const char *, ast_expression **); - -ast_expression *fold_binary (lex_ctx_t ctx, int, ast_expression *, ast_expression *); -int fold_cond_ifthen (ir_value *, ast_function *, ast_ifthen *); -int fold_cond_ternary (ir_value *, ast_function *, ast_ternary *); - -/* intrin.c */ -intrin_t *intrin_init (parser_t *parser); -void intrin_cleanup (intrin_t *intrin); -ast_expression *intrin_fold (intrin_t *intrin, ast_value *, ast_expression **); -ast_expression *intrin_func (intrin_t *intrin, const char *name); -ast_expression *intrin_debug_typestring(intrin_t *intrin); - #endif diff --git a/platform.h b/platform.h deleted file mode 100644 index 57ba352..0000000 --- a/platform.h +++ /dev/null @@ -1,554 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * 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. - */ - -#ifndef GMQCC_PLATFORM_HDR -#define GMQCC_PLATFORM_HDR - -#ifndef GMQCC_PLATFORM_HEADER -# error "This header shouldn't be included!" -#endif - -#undef GMQCC_PLATFORM_HEADER -#include -#include -#include - -#ifdef _WIN32 -# ifndef STDERR_FILENO -# define STDERR_FILENO 2 -# endif -# ifndef STDOUT_FILENO -# define STDOUT_FILENO 1 -# endif -# ifndef __MINGW32__ -# define _WIN32_LEAN_AND_MEAN -# include -# include -# include - - struct dirent { - long d_ino; - unsigned short d_reclen; - unsigned short d_namlen; - char d_name[FILENAME_MAX]; - }; - - typedef struct { - struct _finddata_t dd_dta; - struct dirent dd_dir; - long dd_handle; - int dd_stat; - char dd_name[1]; - } DIR; -# else -# include -# endif /*!__MINGW32__*/ - -# ifndef S_ISDIR -# define S_ISDIR(X) ((X)&_S_IFDIR) -# endif -#else -# include -# include -# include -# include -#endif /*!_WIN32*/ - -/* - * Function: platform_vsnprintf - * Write formatted output using a pointer to a lis of arguments. - * - * Parameters: - * buffer - Storage location for output. - * bytes - Maximum number of characters to write. - * format - Format specification. - * arg - Variable argument list. - * - * Returns: - * The number of characters written if the number of characters to write - * is less than or equal to `bytes`; if the number of characters to write - * is greater than `bytes`, this function returns -1 indicating that the - * output has been truncated. The return value does not include the - * terminating null, if one is written. - * - * Remarks: - * Function takes pointer to an argument list, then formats the data, - * and writes up to `bytes` characters to the memory pointed to by - * `buffer`. If there is room at the end (that is, if the number of - * character to write is less than `bytes`), the buffer will be null-terminated. - */ -int platform_vsnprintf(char *buffer, size_t bytes, const char *format, va_list arg); - -/* - * Function: platform_vsscanf - * Reads formatted data from a string. - * - * Parameters: - * buffer - Stored data to read. - * format - Format specification. - * arg - Variable argument list. - * - * Returns: - * The number of fields that are successfully converted and assigned; - * the return value does not include fields that were read but not - * assigned. A return vlaue of 0 indicated that no fields were assigned. - * The return value if EOF for error or if the end of the string is - * reached before the first conversion. - * - * Remarks: - * Reads data from `buffer` into the locations that are given by each - * argument in the `arg` argument list. Every argument in the list must - * be a pointer to a variable that has a type that corresponds to a - * type specifier in `format`. The `format` argument controls th - * interpretation of the input fields and has the same form and function - * as the `format` argument for the *scanf* function. If copying takes - * place between strings that overlap, the behaviour is undefined. - */ -int platform_vsscanf(const char *buffer, const char *format, va_list arg); - -/* - * Function: platform_localtime - * Convert a time value and correct for the local time zone. - * - * Parameters - * timer - Pointer to stored time. - * - * Returns: - * A pointer to a structure result, or NULL if the date passed to - * the function is before midnight, January 1, 1970. - */ -const struct tm *platform_localtime(const time_t *timer); - -/* - * Function: platform_ctime - * Convert a time value to a string and adjust for local time zone - * settings. - * - * Parameters: - * timer - Pointer to stored time. - * - * Returns: - * Pointer to the character string result. NULL will be returned if time - * represents a date before midnight, January 1, 1970, UTC. - * - * Remarks: - * Converts a time value stored as a `time_t` value into a chracter string. - * The `timer` value is usually obtained from a call to *time*, which returns - * the number of seconds since midnight, January 1, 1970 UTC. The return - * value of the string contains exactly 26 characters. A 24-hour clock is used. - * All fields have constant width. The newline chracter and the null character - * occupy the last two positions of the string. The converted character string - * is also adjusted according to the local time zone settings. - */ -const char *platform_ctime(const time_t *timer); - -/* - * Function: platform_strncat - * Append characters of a string. - * - * Parameters: - * dest - Null terminated destination string - * src - Source string - * num - Number of characters to append - * - * Returns: - * Pointer to the destination string. No return value is used to indicate - * an error. - * - * Remarks: - * Function appends, at mode, the first `num` characters of `src` to - * `dest`. The initial character of `src` overwrites the terminating - * null chracter of `dest`. If a null character appears in `src` before - * `num` chracters are appended, `platform_strncat` appends all chracters - * from `src`, up to the null chracter. If `num` is greater than the - * length of `src`, the length of `src` is used in place of `num`. - */ -char *platform_strncat(char *dest, const char *src, size_t num); - -/* - * Function: platform_getenv - * Get a value from the current enviroment. - * - * Parameters: - * var - Enviroment variable name - * - * Returns: - * A pointer to the enviroment table entry containing `var. It's not - * safe to modify the value of the enviroment variable using the returned - * pointer. The return value is *NULL* if `var` is not found in the - * enviroment table. - */ -const char *platform_getenv(const char *var); - -/* - * Function: platform_vasprintf - * Print to allocated string - * - * Parameters: - * dat - Pointer to pointer to store allocated data. - * fmt - Format specification. - * args - Variable argument list. - * - * Returns: - * Number of character written, -1 is used to indicate an error. - * - * Remarks: - * Allocate a string large enough to hold the output including - * the terminating null character than write formatted output - * to it using format specification. - */ -int platform_vasprintf(char **dat, const char *fmt, va_list args); - -/* - * Function: platform_vfprintf - * Write formatted output using a pointer to a list of arguments. - * - * Parameters: - * stream - Pointer to stream. - * format - Format specification. - * atrg - Variable argument list. - * - * Returns: - * Number of characters written, not including the terminating null - * character, or a negitive value if an output error occurs. -1 is - * also used to indicate an error. - * - * Remarks: - * Takes a pointer to an argument list, then formats and writes the - * given data to `stream`. - */ -int platform_vfprintf(FILE *stream, const char *format, va_list arg); - -/* - * Function: platform_strcat - * Append characters of a string. - * - * Parameters: - * dest - Null terminated destination string - * src - Source string - * - * Returns: - * Pointer to the destination string. No return value is used to indicate - * an error. - * - * Remarks: - * Appens `src` to `dest` and terminates with resulting null character. - * The initial character of `src` overwrites the terminating null - * character of `dest`. The behaviour of platform_strcat is undefined - * if the source and destination string overlap. - */ -char *platform_strcat(char *dest, const char *src); - -/* - * Function: platform_strncpy - * Copys characters of one string to another. - * - * Parameters: - * dest - Destination string. - * src - Source string. - * num - Number of characters to be copied. - * - * Returns: - * `dest`. No return value is reserved to indicate an error. - * - * Remarks: - * Copies the initial characters of `src` to `dest` and returns `dest`. - * If `num` is less than or equal to the length of `src1 a null character - * is not appended automatically to the copied string. If `num` is greater - * than the length of `src`, the destination string is padded with null - * characters up to length `num`. The behaviour of this function is undefined - * if the source and destination strings overlap. - */ -char *platform_strncpy(char *dest, const char *src, size_t num); - -/* - * Function: platform_strerror - * Get a system error message - * - * Parameters: - * err - Error number. - * - * Returns: - * A pointer to the error message - */ -const char *platform_strerror(int err); - -/* - * Function: platform_fopen - * Opens a file - * - * Parameters: - * filename - File name. - * mode - Kind of access that's enabled. - * - * Returns: - * A pointer to the open file. A null pointer value indicates an error. - */ -FILE *platform_fopen(const char *filename, const char *mode); - -/* - * Function: platform_fread - * Reads data from a stream - * - * Parameters: - * ptr - Storage location for data. - * size - Item size in bytes. - * count - Maximum number of items to be read. - * stream - Pointer to stream - * - * Returns: - * The number of full items actually read, which may be less than `count` - * if an error occurs or if the end of the file is encountered before - * reaching `count`. If `size` or `count` is 0, `platform_fread` - * returns 0 and the buffer contents are unchanged. - */ -size_t platform_fread(void *ptr, size_t size, size_t count, FILE *stream); - -/* - * Function: platform_fwrite - * Writes data to a stream - * - * Parameters: - * ptr - Pointer to data to be written. - * size - Item size in bytes. - * count - Maximum number of items to be read. - * stream - Pointer to stream - * - * Returns: - * The number of full items actually written, which may be less than - * `count` if an error occurs. Also, if an error occurs, the - * file-position indicator cannot be determined. - * - * Remarks: - * Writes up to `count` items, of `size` length each, from `ptr` to the - * output stream. The file pointer associated with stream (if there is one) - * is incremented by the number of bytes actually written. - */ -size_t platform_fwrite(const void *ptr, size_t size, size_t count, FILE *stream); - -/* - * Function: platform_fflush - * Flushes a stream - * - * Parameters: - * stream - Pointer to stream - * - * Returns: - * 0 value if the buffer was succesffuly flushed. The value 0 is also - * returned in cases in which the specified stream has no buffer or is - * open for reading only. A return value of *EOF* indicates an error. - * - * Remarks: - * Flushes a stream. If the file associated with stream is open for output, - * platform_fflush writes to that file the contents of the buffer - * associated with the stream. If the stream is open for input, - * platform_fflush clears the contents of the buffer. platform_fflush - * negates the effect of any prior call to ungetc against stream. Also, - * platform_fflush(NULL) flushes all streams opened for output. - * The stream remains open after the call. platform_fflush has no effect - * on an unbuffered stream. - */ -int platform_fflush(FILE *stream); - -/* - * Function: platform_fclose - * Closes a stream. - * - * Parameters: - * stream - Pointer to stream. - * - * Returns: - * 0 value. *EOF* is used to indicate an error. - * - * Remarks: - * Closes a stream. - */ -int platform_fclose(FILE *stream); - -/* - * Function: platform_ferror - * Tests for an error on a stream. - * - * Parameters: - * stream - Pointer to stream. - * - * Returns: - * If not error has occured on `stream`, 0 value is returned, otherwise - * it returns a nonzero value. - * - * Remarks: - * Tests for a reading or writing error on the file associated with `stream`. - * If an error has occured, the error indicator for the stream remains set - * until the stream is closed or rewound. - */ -int platform_ferror(FILE *stream); - -/* - * Function: platform_fgetc - * Read a character from a stream. - * - * Parameters: - * stream - Pointer to a stream. - * - * Returns: - * The chracter read as an int or EOF to indicate an error or end-of-file. - * - * Remarks: - * Reads a single chracter from the current position of the file associated - * with `stream`. Than increments that position. If the steam is at the end - * of the file, the end-of-file indicator for the stream is set. - */ -int platform_fgetc(FILE *stream); - -/* - * Function: platform_fputs - * Write a string to a stream - * - * Parameters: - * str - Output string. - * stream - Pointer to stream. - * - * Returns: - * Non-negative value if successful. EOF is used to indicate an error. - * - * Remarks: - * Copies `str` to the output stream at the current position. - */ -int platform_fputs(const char *str, FILE *stream); - -/* - * Function: platform_fseek - * Moves the file pointer to a specified location. - * - * Parameters: - * stream - Pointer to stream. - * offset - Number of bytes from origin to offset. - * origin - Initital position. - * - * Returns: - * 0 value, nonzero values are used to indicate an error. - * - * Remarks: - * Moves the file pointer (if any) associated with stream to a new - * location that is offset bytes from origin. - * The next operation on the stream takes place at the new location. - * On a stream open for update, the next operation can be either a - * read or a write. - */ -int platform_fseek(FILE *stream, long offset, int origin); - -/* - * Function: platform_ftell - * Gets the current position of a file pointer - * - * Parameters: - * stream - Pointer to stream - * - * Returns: - * Current file position. May not reflect physical byte offset, e.g - * systems where read-mode does carriage return-linefeed translation. - * -1 value is used to indivate an error. - */ -long platform_ftell(FILE *stream); - -/* - * Function: platform_mkdir - * Make a directory - * - * Parameters: - * path - Path to create - * mode - The mode of the directory (implementation defined) - * - * Returns: - * 0 value. -1 value is used to indicate an error. On error no - * directory shall be created. - * - * Remarks: - * Shall create a new empty directory with with the name path specified - * by argument `path. - */ -int platform_mkdir(const char *path, int mode); - -/* - * Function: platform_opendir - * Open a directory - * - * Parameters: - * path - Path to the directory to open - * - * Returns: - * Pointer to an object of type *DIR*. A null pointer value indicates - * an error. - * - * Remarks: - * Shall open a directory stream corresponding to the directory named by - * the `path` argument. The directory stream is positioned at the first entry. - */ -DIR *platform_opendir(const char *path); - -/* - * Function: platform_closedir - * Close a directory stream - * - * Parameters: - * dir - Pointer to directory stream - * - * Returns: - * 0 value. A -1 value indicated an error. - * - * Remarks: - * Shall close the directory stream referred to by the argument - * `dir`. Upon return, the value of `dir` may no longer point to - * an accessible object of the type *DIR*. - */ -int platform_closedir(DIR *dir); - -/* - * Function: platform_readdir - * Read directory - * - * Parameters: - * dir - Pointer to directory stream - * - * Returns: - * Pointer to an object of type `struct dirent`. A null pointer value - * indicates an error. - * - * Remarks: - * When the end of the directory is encountered, a null pointer is - * returned. - */ -struct dirent *platform_readdir(DIR *dir); - -/* - * Function: platform_isatty - * Determines whether a file descriptor is associated with a character - * device. - * - * Returns: - * A nonzero value if the descriptor is associated with a character - * device. Otherwise `platform_isatty` returns 0. - */ -int platform_isatty(int fd); - -#endif diff --git a/stat.c b/stat.c deleted file mode 100644 index 544ae96..0000000 --- a/stat.c +++ /dev/null @@ -1,769 +0,0 @@ -/* - * Copyright (C) 2012, 2013, 2014, 2015 - * Dale Weiler - * Wolfgang Bumiller - * - * 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 -#include - -#include "gmqcc.h" - -typedef struct stat_mem_block_s stat_mem_block_t; - -#define IDENT_SIZE 4 -#define IDENT_VEC "vec" -#define IDENT_MEM "mem" -#define IDENT_VEC_TOP (sizeof(vector_t) + IDENT_SIZE) -#define IDENT_MEM_TOP (sizeof(stat_mem_block_t) + IDENT_SIZE) - -/* - * For the valgrind integration of our allocator. This allows us to have - * more `accurate` valgrind output for our allocator, and also secures the - * possible underflows (where one could obtain access to the redzone that - * represents info about that allocation). - */ -#ifndef NVALGRIND -# include -# include -#else -# define VALGRIND_MALLOCLIKE_BLOCK(PTR, ALLOC_SIZE, REDZONE_SIZE, ZEROED) -# define VALGRIND_FREELIKE_BLOCK(PTR, REDZONE_SIZE) -# define VALGRIND_MAKE_MEM_DEFINED(PTR, REDZONE_SIZE) -# define VALGRIND_MAKE_MEM_NOACCESS(PTR, REDZONE_SIZE) -#endif - -/* - * GMQCC performs tons of allocations, constructions, and crazyness - * all around. When trying to optimizes systems, or just get fancy - * statistics out of the compiler, it's often printf mess. This file - * implements the statistics system of the compiler. I.E the allocator - * we use to track allocations, and other systems of interest. - */ -#define ST_SIZE 1024 - -struct stat_mem_block_s { - const char *file; - size_t line; - size_t size; - const char *expr; - struct stat_mem_block_s *next; - struct stat_mem_block_s *prev; -}; - -typedef struct { - size_t key; - size_t value; -} stat_size_entry_t, **stat_size_table_t; - -static uint64_t stat_mem_allocated = 0; -static uint64_t stat_mem_deallocated = 0; -static uint64_t stat_mem_allocated_total = 0; -static uint64_t stat_mem_deallocated_total = 0; -static uint64_t stat_mem_high = 0; -static uint64_t stat_mem_peak = 0; -static uint64_t stat_mem_strdups = 0; -static uint64_t stat_used_strdups = 0; -static uint64_t stat_used_vectors = 0; -static uint64_t stat_used_hashtables = 0; -static uint64_t stat_type_vectors = 0; -static uint64_t stat_type_hashtables = 0; -static stat_size_table_t stat_size_vectors = NULL; -static stat_size_table_t stat_size_hashtables = NULL; -static stat_mem_block_t *stat_mem_block_root = NULL; - -/* - * A tiny size_t key-value hashtbale for tracking vector and hashtable - * sizes. We can use it for other things too, if we need to. This is - * very TIGHT, and efficent in terms of space though. - */ -static stat_size_table_t stat_size_new(void) { - return (stat_size_table_t)memset( - mem_a(sizeof(stat_size_entry_t*) * ST_SIZE), - 0, ST_SIZE * sizeof(stat_size_entry_t*) - ); -} - -static void stat_size_del(stat_size_table_t table) { - size_t i = 0; - for (; i < ST_SIZE; i++) if(table[i]) mem_d(table[i]); - mem_d(table); -} - -static stat_size_entry_t *stat_size_get(stat_size_table_t table, size_t key) { - size_t hash = (key % ST_SIZE); - while (table[hash] && table[hash]->key != key) - hash = (hash + 1) % ST_SIZE; - return table[hash]; -} -static void stat_size_put(stat_size_table_t table, size_t key, size_t value) { - size_t hash = (key % ST_SIZE); - while (table[hash] && table[hash]->key != key) - hash = (hash + 1) % ST_SIZE; - table[hash] = (stat_size_entry_t*)mem_a(sizeof(stat_size_entry_t)); - table[hash]->key = key; - table[hash]->value = value; -} - -/* - * A basic header of information wrapper allocator. Simply stores - * information as a header, returns the memory + 1 past it, can be - * retrieved again with - 1. Where type is stat_mem_block_t*. - */ -void *stat_mem_allocate(size_t size, size_t line, const char *file, const char *expr) { - stat_mem_block_t *info = (stat_mem_block_t*)malloc(size + IDENT_MEM_TOP); - void *data = (void *)((char*)info + IDENT_MEM_TOP); - - if(GMQCC_UNLIKELY(!info)) - return NULL; - - info->line = line; - info->size = size; - info->file = file; - info->expr = expr; - info->prev = NULL; - info->next = stat_mem_block_root; - - /* Write identifier */ - memcpy(info + 1, IDENT_MEM, IDENT_SIZE); - - /* likely since it only happens once */ - if (GMQCC_LIKELY(stat_mem_block_root != NULL)) { - VALGRIND_MAKE_MEM_DEFINED(stat_mem_block_root, IDENT_MEM_TOP); - stat_mem_block_root->prev = info; - VALGRIND_MAKE_MEM_NOACCESS(stat_mem_block_root, IDENT_MEM_TOP); - } - - stat_mem_block_root = info; - stat_mem_allocated += size; - stat_mem_high += size; - stat_mem_allocated_total ++; - - if (stat_mem_high > stat_mem_peak) - stat_mem_peak = stat_mem_high; - - VALGRIND_MALLOCLIKE_BLOCK(data, size, IDENT_MEM_TOP, 0); - return data; -} - -void stat_mem_deallocate(void *ptr, size_t line, const char *file) { - stat_mem_block_t *info = NULL; - char *ident = (char *)ptr - IDENT_SIZE; - - if (GMQCC_UNLIKELY(!ptr)) - return; - - /* Validate usage */ - VALGRIND_MAKE_MEM_DEFINED(ident, IDENT_SIZE); - if (!strcmp(ident, IDENT_VEC)) { - vector_t *vec = (vector_t*)((char *)ptr - IDENT_VEC_TOP); - stat_mem_block_t *block = (stat_mem_block_t*)((char *)vec - IDENT_MEM_TOP); - - VALGRIND_MAKE_MEM_DEFINED(block, sizeof(stat_mem_block_t)); - con_err("internal warning: invalid use of mem_d:\n"); - con_err("internal warning: vector (used elements: %u, allocated elements: %u)\n", - (unsigned)vec->used, - (unsigned)vec->allocated - ); - con_err("internal warning: vector was last (re)allocated with (size: %u (bytes), at location: %s:%u)\n", - (unsigned)block->size, - block->file, - (unsigned)block->line - ); - con_err("internal warning: released with wrong routine at %s:%u\n", file, (unsigned)line); - con_err("internal warning: forwarding to vec_free, please fix it\n"); - VALGRIND_MAKE_MEM_NOACCESS(block, sizeof(stat_mem_block_t)); - VALGRIND_MAKE_MEM_NOACCESS(ident, IDENT_SIZE); - vec_free(ptr); - return; - } - VALGRIND_MAKE_MEM_NOACCESS(ident, IDENT_SIZE); - info = (stat_mem_block_t*)((char *)ptr - IDENT_MEM_TOP); - - /* - * we need access to the redzone that represents the info block - * so lets do that. - */ - VALGRIND_MAKE_MEM_DEFINED(info, IDENT_MEM_TOP); - - stat_mem_deallocated += info->size; - stat_mem_high -= info->size; - stat_mem_deallocated_total ++; - - if (info->prev) { - /* just need access for a short period */ - VALGRIND_MAKE_MEM_DEFINED(info->prev, IDENT_MEM_TOP); - info->prev->next = info->next; - /* don't need access anymore */ - VALGRIND_MAKE_MEM_NOACCESS(info->prev, IDENT_MEM_TOP); - } - if (info->next) { - /* just need access for a short period */ - VALGRIND_MAKE_MEM_DEFINED(info->next, IDENT_MEM_TOP); - info->next->prev = info->prev; - /* don't need access anymore */ - VALGRIND_MAKE_MEM_NOACCESS(info->next, IDENT_MEM_TOP); - } - - /* move ahead */ - if (info == stat_mem_block_root) - stat_mem_block_root = info->next; - - free(info); - VALGRIND_MAKE_MEM_NOACCESS(info, IDENT_MEM_TOP); - VALGRIND_FREELIKE_BLOCK(ptr, IDENT_MEM_TOP); -} - -void *stat_mem_reallocate(void *ptr, size_t size, size_t line, const char *file, const char *expr) { - stat_mem_block_t *oldinfo = NULL; - stat_mem_block_t *newinfo; - - if (GMQCC_UNLIKELY(!ptr)) - return stat_mem_allocate(size, line, file, expr); - - /* stay consistent with glibc */ - if (GMQCC_UNLIKELY(!size)) { - stat_mem_deallocate(ptr, line, file); - return NULL; - } - - oldinfo = (stat_mem_block_t*)((char *)ptr - IDENT_MEM_TOP); - newinfo = (stat_mem_block_t*)malloc(size + IDENT_MEM_TOP); - - if (GMQCC_UNLIKELY(!newinfo)) { - stat_mem_deallocate(ptr, line, file); - return NULL; - } - - VALGRIND_MALLOCLIKE_BLOCK((char *)newinfo + IDENT_MEM_TOP, size, IDENT_MEM_TOP, 0); - - /* we need access to the old info redzone */ - VALGRIND_MAKE_MEM_DEFINED(oldinfo, IDENT_MEM_TOP); - - /* We need access to the new info redzone */ - VALGRIND_MAKE_MEM_DEFINED(newinfo, IDENT_MEM_TOP); - memcpy((char *)(newinfo + 1), IDENT_MEM, IDENT_SIZE); - memcpy((char *)newinfo + IDENT_MEM_TOP, (char *)oldinfo + IDENT_MEM_TOP, oldinfo->size); - VALGRIND_MAKE_MEM_NOACCESS(newinfo, IDENT_MEM_TOP); - - if (oldinfo->prev) { - /* just need access for a short period */ - VALGRIND_MAKE_MEM_DEFINED(oldinfo->prev, IDENT_MEM_TOP); - oldinfo->prev->next = oldinfo->next; - /* don't need access anymore */ - VALGRIND_MAKE_MEM_NOACCESS(oldinfo->prev, IDENT_MEM_TOP); - } - - if (oldinfo->next) { - /* just need access for a short period */ - VALGRIND_MAKE_MEM_DEFINED(oldinfo->next, IDENT_MEM_TOP); - oldinfo->next->prev = oldinfo->prev; - /* don't need access anymore */ - VALGRIND_MAKE_MEM_NOACCESS(oldinfo->next, IDENT_MEM_TOP); - } - - /* move ahead */ - if (oldinfo == stat_mem_block_root) - stat_mem_block_root = oldinfo->next; - - /* we need access to the redzone for the newinfo block */ - VALGRIND_MAKE_MEM_DEFINED(newinfo, IDENT_MEM_TOP); - - newinfo->line = line; - newinfo->size = size; - newinfo->file = file; - newinfo->expr = expr; - newinfo->prev = NULL; - newinfo->next = stat_mem_block_root; - - /* - * likely since the only time there is no root is when it's - * being initialized first. - */ - if (GMQCC_LIKELY(stat_mem_block_root != NULL)) { - /* we need access to the root */ - VALGRIND_MAKE_MEM_DEFINED(stat_mem_block_root, IDENT_MEM_TOP); - stat_mem_block_root->prev = newinfo; - /* kill access */ - VALGRIND_MAKE_MEM_NOACCESS(stat_mem_block_root, IDENT_MEM_TOP); - } - - stat_mem_block_root = newinfo; - stat_mem_allocated -= oldinfo->size; - stat_mem_high -= oldinfo->size; - stat_mem_allocated += newinfo->size; - stat_mem_high += newinfo->size; - - /* - * we're finished with the redzones, lets kill the access - * to them. - */ - VALGRIND_MAKE_MEM_NOACCESS(newinfo, IDENT_MEM_TOP); - VALGRIND_MAKE_MEM_NOACCESS(oldinfo, IDENT_MEM_TOP); - - if (stat_mem_high > stat_mem_peak) - stat_mem_peak = stat_mem_high; - - free(oldinfo); - VALGRIND_FREELIKE_BLOCK(ptr, IDENT_MEM_TOP); - return (char *)newinfo + IDENT_MEM_TOP; -} - -/* - * strdup does it's own malloc, we need to track malloc. We don't want - * to overwrite malloc though, infact, we can't really hook it at all - * without library specific assumptions. So we re implement strdup. - */ -char *stat_mem_strdup(const char *src, size_t line, const char *file, bool empty) { - size_t len = 0; - char *ptr = NULL; - - if (!src) - return NULL; - - len = strlen(src); - if (((!empty) ? len : true) && (ptr = (char*)stat_mem_allocate(len + 1, line, file, "strdup"))) { - memcpy(ptr, src, len); - ptr[len] = '\0'; - } - - stat_used_strdups ++; - stat_mem_strdups += len; - return ptr; -} - -/* - * The reallocate function for resizing vectors. - */ -void _util_vec_grow(void **a, size_t i, size_t s) { - vector_t *d = (vector_t*)((char *)*a - IDENT_VEC_TOP); - size_t m = 0; - stat_size_entry_t *e = NULL; - void *p = NULL; - - if (*a) { - m = 2 * d->allocated + i; - p = mem_r(d, s * m + IDENT_VEC_TOP); - } else { - m = i + 1; - p = mem_a(s * m + IDENT_VEC_TOP); - ((vector_t*)p)->used = 0; - stat_used_vectors++; - } - - if (!stat_size_vectors) - stat_size_vectors = stat_size_new(); - - if ((e = stat_size_get(stat_size_vectors, s))) { - e->value ++; - } else { - stat_size_put(stat_size_vectors, s, 1); /* start off with 1 */ - stat_type_vectors++; - } - - d = (vector_t*)p; - d->allocated = m; - memcpy(d + 1, IDENT_VEC, IDENT_SIZE); - *a = (void *)((char *)d + IDENT_VEC_TOP); -} - -void _util_vec_delete(void *data, size_t line, const char *file) { - char *ident = (char *)data - IDENT_SIZE; - if (!strcmp(ident, IDENT_MEM)) { - stat_mem_block_t *block = (stat_mem_block_t*)((char *)data - IDENT_MEM_TOP); - VALGRIND_MAKE_MEM_DEFINED(block, sizeof(stat_mem_block_t)); - con_err("internal warning: invalid use of vec_free:\n"); - con_err("internal warning: memory block last allocated (size: %u (bytes), at %s:%u)\n", - (unsigned)block->size, - block->file, - (unsigned)block->line); - con_err("internal warning: released with with wrong routine at %s:%u\n", file, (unsigned)line); - con_err("internal warning: forwarding to mem_d, please fix it\n"); - VALGRIND_MAKE_MEM_NOACCESS(block, sizeof(stat_mem_block_t)); - mem_d(data); - return; - } - /* forward */ - stat_mem_deallocate((void*)(ident - sizeof(vector_t)), line, file); -} - -/* - * Hash table for generic data, based on dynamic memory allocations - * all around. This is the internal interface, please look for - * EXPOSED INTERFACE comment below - */ -typedef struct hash_node_t { - char *key; /* the key for this node in table */ - void *value; /* pointer to the data as void* */ - struct hash_node_t *next; /* next node (linked list) */ -} hash_node_t; - - -size_t hash(const char *key); -size_t util_hthash(hash_table_t *ht, const char *key) { - return hash(key) % ht->size; -} - -static hash_node_t *_util_htnewpair(const char *key, void *value) { - hash_node_t *node; - if (!(node = (hash_node_t*)mem_a(sizeof(hash_node_t)))) - return NULL; - - if (!(node->key = util_strdupe(key))) { - mem_d(node); - return NULL; - } - - node->value = value; - node->next = NULL; - - return node; -} - -/* - * EXPOSED INTERFACE for the hashtable implementation - * util_htnew(size) -- to make a new hashtable - * util_htset(table, key, value, sizeof(value)) -- to set something in the table - * util_htget(table, key) -- to get something from the table - * util_htdel(table) -- to delete the table - */ -hash_table_t *util_htnew(size_t size) { - hash_table_t *hashtable = NULL; - stat_size_entry_t *find = NULL; - - if (size < 1) - return NULL; - - if (!stat_size_hashtables) - stat_size_hashtables = stat_size_new(); - - if (!(hashtable = (hash_table_t*)mem_a(sizeof(hash_table_t)))) - return NULL; - - if (!(hashtable->table = (hash_node_t**)mem_a(sizeof(hash_node_t*) * size))) { - mem_d(hashtable); - return NULL; - } - - if ((find = stat_size_get(stat_size_hashtables, size))) - find->value++; - else { - stat_type_hashtables++; - stat_size_put(stat_size_hashtables, size, 1); - } - - hashtable->size = size; - memset(hashtable->table, 0, sizeof(hash_node_t*) * size); - - stat_used_hashtables++; - return hashtable; -} - -void util_htseth(hash_table_t *ht, const char *key, size_t bin, void *value) { - hash_node_t *newnode = NULL; - hash_node_t *next = NULL; - hash_node_t *last = NULL; - - next = ht->table[bin]; - - while (next && next->key && strcmp(key, next->key) > 0) - last = next, next = next->next; - - /* already in table, do a replace */ - if (next && next->key && strcmp(key, next->key) == 0) { - next->value = value; - } else { - /* not found, grow a pair man :P */ - newnode = _util_htnewpair(key, value); - if (next == ht->table[bin]) { - newnode->next = next; - ht->table[bin] = newnode; - } else if (!next) { - last->next = newnode; - } else { - newnode->next = next; - last->next = newnode; - } - } -} - -void util_htset(hash_table_t *ht, const char *key, void *value) { - util_htseth(ht, key, util_hthash(ht, key), value); -} - -void *util_htgeth(hash_table_t *ht, const char *key, size_t bin) { - hash_node_t *pair = ht->table[bin]; - - while (pair && pair->key && strcmp(key, pair->key) > 0) - pair = pair->next; - - if (!pair || !pair->key || strcmp(key, pair->key) != 0) - return NULL; - - return pair->value; -} - -void *util_htget(hash_table_t *ht, const char *key) { - return util_htgeth(ht, key, util_hthash(ht, key)); -} - -void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin); -void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin) { - hash_node_t *pair; - size_t len, keylen; - int cmp; - - keylen = strlen(key); - - pair = ht->table[bin]; - while (pair && pair->key) { - len = strlen(pair->key); - if (len < keylen) { - pair = pair->next; - continue; - } - if (keylen == len) { - cmp = strcmp(key, pair->key); - if (cmp == 0) - return pair->value; - if (cmp < 0) - return NULL; - pair = pair->next; - continue; - } - cmp = strcmp(key, pair->key + len - keylen); - if (cmp == 0) { - uintptr_t up = (uintptr_t)pair->value; - up += len - keylen; - return (void*)up; - } - pair = pair->next; - } - return NULL; -} - -/* - * Free all allocated data in a hashtable, this is quite the amount - * of work. - */ -void util_htrem(hash_table_t *ht, void (*callback)(void *data)) { - size_t i = 0; - - for (; i < ht->size; ++i) { - hash_node_t *n = ht->table[i]; - hash_node_t *p; - - /* free in list */ - while (n) { - if (n->key) - mem_d(n->key); - if (callback) - callback(n->value); - p = n; - n = p->next; - mem_d(p); - } - - } - /* free table */ - mem_d(ht->table); - mem_d(ht); -} - -void util_htrmh(hash_table_t *ht, const char *key, size_t bin, void (*cb)(void*)) { - hash_node_t **pair = &ht->table[bin]; - hash_node_t *tmp; - - while (*pair && (*pair)->key && strcmp(key, (*pair)->key) > 0) - pair = &(*pair)->next; - - tmp = *pair; - if (!tmp || !tmp->key || strcmp(key, tmp->key) != 0) - return; - - if (cb) - (*cb)(tmp->value); - - *pair = tmp->next; - mem_d(tmp->key); - mem_d(tmp); -} - -void util_htrm(hash_table_t *ht, const char *key, void (*cb)(void*)) { - util_htrmh(ht, key, util_hthash(ht, key), cb); -} - -void util_htdel(hash_table_t *ht) { - util_htrem(ht, NULL); -} - -/* - * The following functions below implement printing / dumping of statistical - * information. - */ -static void stat_dump_mem_contents(stat_mem_block_t *block, uint16_t cols) { - unsigned char *buffer = (unsigned char *)mem_a(cols); - unsigned char *memory = (unsigned char *)(block + 1); - size_t i; - - for (i = 0; i < block->size; i++) { - if (!(i % 16)) { - if (i != 0) - con_out(" %s\n", buffer); - con_out(" 0x%08X: ", i); - } - - con_out(" %02X", memory[i]); - - buffer[i % cols] = ((memory[i] < 0x20) || (memory[i] > 0x7E)) - ? '.' - : memory[i]; - - buffer[(i % cols) + 1] = '\0'; - } - - while ((i % cols) != 0) { - con_out(" "); - i++; - } - - con_out(" %s\n", buffer); - mem_d(buffer); -} - -static void stat_dump_mem_leaks(void) { - stat_mem_block_t *info; - /* we need access to the root for this */ - VALGRIND_MAKE_MEM_DEFINED(stat_mem_block_root, sizeof(stat_mem_block_t)); - for (info = stat_mem_block_root; info; info = info->next) { - /* we need access to the block */ - VALGRIND_MAKE_MEM_DEFINED(info, sizeof(stat_mem_block_t)); - con_out("lost: %u (bytes) at %s:%u from expression `%s`\n", - info->size, - info->file, - info->line, - info->expr - ); - - stat_dump_mem_contents(info, OPTS_OPTION_U16(OPTION_MEMDUMPCOLS)); - - /* - * we're finished with the access, the redzone should be marked - * inaccesible so that invalid read/writes that could 'step-into' - * those redzones will show up as invalid read/writes in valgrind. - */ - VALGRIND_MAKE_MEM_NOACCESS(info, sizeof(stat_mem_block_t)); - } - VALGRIND_MAKE_MEM_NOACCESS(stat_mem_block_root, sizeof(stat_mem_block_t)); -} - -static void stat_dump_mem_info(void) { - con_out("Memory Information:\n\ - Total allocations: %llu\n\ - Total deallocations: %llu\n\ - Total allocated: %f (MB)\n\ - Total deallocated: %f (MB)\n\ - Total peak memory: %f (MB)\n\ - Total leaked memory: %f (MB) in %llu allocations\n", - stat_mem_allocated_total, - stat_mem_deallocated_total, - (float)(stat_mem_allocated) / 1048576.0f, - (float)(stat_mem_deallocated) / 1048576.0f, - (float)(stat_mem_peak) / 1048576.0f, - (float)(stat_mem_allocated - stat_mem_deallocated) / 1048576.0f, - stat_mem_allocated_total - stat_mem_deallocated_total - ); -} - -static void stat_dump_stats_table(stat_size_table_t table, const char *string, uint64_t *size) { - size_t i,j; - - if (!table) - return; - - for (i = 0, j = 1; i < ST_SIZE; i++) { - stat_size_entry_t *entry; - - if (!(entry = table[i])) - continue; - - con_out(string, (unsigned)j, (unsigned)entry->key, (unsigned)entry->value); - j++; - - if (size) - *size += entry->key * entry->value; - } -} - -void stat_info() { - if (OPTS_OPTION_BOOL(OPTION_MEMCHK) || - OPTS_OPTION_BOOL(OPTION_STATISTICS)) { - uint64_t mem = 0; - - con_out("Memory Statistics:\n\ - Total vectors allocated: %llu\n\ - Total string duplicates: %llu\n\ - Total string duplicate memory: %f (MB)\n\ - Total hashtables allocated: %llu\n\ - Total unique vector sizes: %llu\n", - stat_used_vectors, - stat_used_strdups, - (float)(stat_mem_strdups) / 1048576.0f, - stat_used_hashtables, - stat_type_vectors - ); - - stat_dump_stats_table ( - stat_size_vectors, - " %2u| # of %5u byte vectors: %u\n", - &mem - ); - - con_out ( - " Total unique hashtable sizes: %llu\n", - stat_type_hashtables - ); - - stat_dump_stats_table ( - stat_size_hashtables, - " %2u| # of %5u element hashtables: %u\n", - NULL - ); - - con_out ( - " Total vector memory: %f (MB)\n\n", - (float)(mem) / 1048576.0f - ); - } - - if (stat_size_vectors) - stat_size_del(stat_size_vectors); - if (stat_size_hashtables) - stat_size_del(stat_size_hashtables); - - if (OPTS_OPTION_BOOL(OPTION_DEBUG) || - OPTS_OPTION_BOOL(OPTION_MEMCHK)) - stat_dump_mem_info(); - - if (OPTS_OPTION_BOOL(OPTION_DEBUG)) - stat_dump_mem_leaks(); -} -#undef ST_SIZE diff --git a/stat.cpp b/stat.cpp new file mode 100644 index 0000000..1797536 --- /dev/null +++ b/stat.cpp @@ -0,0 +1,249 @@ +#include +#include + +#include "gmqcc.h" + +/* + * strdup does it's own malloc, we need to track malloc. We don't want + * to overwrite malloc though, infact, we can't really hook it at all + * without library specific assumptions. So we re implement strdup. + */ +char *stat_mem_strdup(const char *src, bool empty) { + size_t len = 0; + char *ptr = nullptr; + + if (!src) + return nullptr; + + len = strlen(src); + if ((!empty ? len : true) && (ptr = (char*)mem_a(len + 1))) { + memcpy(ptr, src, len); + ptr[len] = '\0'; + } + + return ptr; +} + +/* + * The reallocate function for resizing vectors. + */ +void _util_vec_grow(void **a, size_t i, size_t s) { + vector_t *d = vec_meta(*a); + size_t m = 0; + void *p = nullptr; + + if (*a) { + m = 2 * d->allocated + i; + p = mem_r(d, s * m + sizeof(vector_t)); + } else { + m = i + 1; + p = mem_a(s * m + sizeof(vector_t)); + ((vector_t*)p)->used = 0; + } + + d = (vector_t*)p; + d->allocated = m; + *a = d + 1; +} + +void _util_vec_delete(void *data) { + mem_d(vec_meta(data)); +} + +/* + * Hash table for generic data, based on dynamic memory allocations + * all around. This is the internal interface, please look for + * EXPOSED INTERFACE comment below + */ +struct hash_node_t { + char *key; /* the key for this node in table */ + void *value; /* pointer to the data as void* */ + hash_node_t *next; /* next node (linked list) */ +}; + +size_t hash(const char *key); + +size_t util_hthash(hash_table_t *ht, const char *key) { + return hash(key) % ht->size; +} + +static hash_node_t *_util_htnewpair(const char *key, void *value) { + hash_node_t *node; + if (!(node = (hash_node_t*)mem_a(sizeof(hash_node_t)))) + return nullptr; + + if (!(node->key = util_strdupe(key))) { + mem_d(node); + return nullptr; + } + + node->value = value; + node->next = nullptr; + + return node; +} + +/* + * EXPOSED INTERFACE for the hashtable implementation + * util_htnew(size) -- to make a new hashtable + * util_htset(table, key, value, sizeof(value)) -- to set something in the table + * util_htget(table, key) -- to get something from the table + * util_htdel(table) -- to delete the table + */ +hash_table_t *util_htnew(size_t size) { + hash_table_t *hashtable = nullptr; + + if (size < 1) + return nullptr; + + if (!(hashtable = (hash_table_t*)mem_a(sizeof(hash_table_t)))) + return nullptr; + + if (!(hashtable->table = (hash_node_t**)mem_a(sizeof(hash_node_t*) * size))) { + mem_d(hashtable); + return nullptr; + } + + hashtable->size = size; + memset(hashtable->table, 0, sizeof(hash_node_t*) * size); + + return hashtable; +} + +void util_htseth(hash_table_t *ht, const char *key, size_t bin, void *value) { + hash_node_t *newnode = nullptr; + hash_node_t *next = nullptr; + hash_node_t *last = nullptr; + + next = ht->table[bin]; + + while (next && next->key && strcmp(key, next->key) > 0) + last = next, next = next->next; + + /* already in table, do a replace */ + if (next && next->key && strcmp(key, next->key) == 0) { + next->value = value; + } else { + /* not found, grow a pair man :P */ + newnode = _util_htnewpair(key, value); + if (next == ht->table[bin]) { + newnode->next = next; + ht->table[bin] = newnode; + } else if (!next) { + last->next = newnode; + } else { + newnode->next = next; + last->next = newnode; + } + } +} + +void util_htset(hash_table_t *ht, const char *key, void *value) { + util_htseth(ht, key, util_hthash(ht, key), value); +} + +void *util_htgeth(hash_table_t *ht, const char *key, size_t bin) { + hash_node_t *pair = ht->table[bin]; + + while (pair && pair->key && strcmp(key, pair->key) > 0) + pair = pair->next; + + if (!pair || !pair->key || strcmp(key, pair->key) != 0) + return nullptr; + + return pair->value; +} + +void *util_htget(hash_table_t *ht, const char *key) { + return util_htgeth(ht, key, util_hthash(ht, key)); +} + +void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin); +void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin) { + hash_node_t *pair; + size_t len, keylen; + int cmp; + + keylen = strlen(key); + + pair = ht->table[bin]; + while (pair && pair->key) { + len = strlen(pair->key); + if (len < keylen) { + pair = pair->next; + continue; + } + if (keylen == len) { + cmp = strcmp(key, pair->key); + if (cmp == 0) + return pair->value; + if (cmp < 0) + return nullptr; + pair = pair->next; + continue; + } + cmp = strcmp(key, pair->key + len - keylen); + if (cmp == 0) { + uintptr_t up = (uintptr_t)pair->value; + up += len - keylen; + return (void*)up; + } + pair = pair->next; + } + return nullptr; +} + +/* + * Free all allocated data in a hashtable, this is quite the amount + * of work. + */ +void util_htrem(hash_table_t *ht, void (*callback)(void *data)) { + size_t i = 0; + + for (; i < ht->size; ++i) { + hash_node_t *n = ht->table[i]; + hash_node_t *p; + + /* free in list */ + while (n) { + if (n->key) + mem_d(n->key); + if (callback) + callback(n->value); + p = n; + n = p->next; + mem_d(p); + } + + } + /* free table */ + mem_d(ht->table); + mem_d(ht); +} + +void util_htrmh(hash_table_t *ht, const char *key, size_t bin, void (*cb)(void*)) { + hash_node_t **pair = &ht->table[bin]; + hash_node_t *tmp; + + while (*pair && (*pair)->key && strcmp(key, (*pair)->key) > 0) + pair = &(*pair)->next; + + tmp = *pair; + if (!tmp || !tmp->key || strcmp(key, tmp->key) != 0) + return; + + if (cb) + (*cb)(tmp->value); + + *pair = tmp->next; + mem_d(tmp->key); + mem_d(tmp); +} + +void util_htrm(hash_table_t *ht, const char *key, void (*cb)(void*)) { + util_htrmh(ht, key, util_hthash(ht, key), cb); +} + +void util_htdel(hash_table_t *ht) { + util_htrem(ht, nullptr); +} diff --git a/syntax/README b/syntax/README deleted file mode 100644 index 5a5c4b4..0000000 --- a/syntax/README +++ /dev/null @@ -1,24 +0,0 @@ -Here exists some syntax highlighting configuration files for various -text editors. Inside each directory exists some documentaiton on how -you can install the configuration file correctly. - -Currently the supported text editors: - geany - kate - kwrite - uses kate syntax highlighting - kdevelop - uses kate syntax highlighting - QtCreator - supports kate syntax highlighting - gtksourceview - main source viewer in GNOME - gedit - uses gtksourceview - sandy - uses gtksourceview - nano - jedit - - -Other text editors we plan to provide syntax highlighting configuration -files for (but never got around to figuring out) - vim - emacs - -If your text editor is not supported and you'd like to create syntax -highlighting support for it, don't hesitate to share it with us. diff --git a/syntax/geany/README b/syntax/geany/README deleted file mode 100644 index c527d3f..0000000 --- a/syntax/geany/README +++ /dev/null @@ -1,8 +0,0 @@ -To use the geany syntax highlighting install filetypes.qc to the syntax -directory for geany. - -# Can be installed globally to -/usr/share/geany/ - -# Can be installed locally to -~/.config/geany/filedefs/ diff --git a/syntax/geany/filetypes.qc b/syntax/geany/filetypes.qc deleted file mode 100644 index d84bb7e..0000000 --- a/syntax/geany/filetypes.qc +++ /dev/null @@ -1,55 +0,0 @@ -[styling] -default=default -comment=comment -commentline=comment_line -commentdoc=comment_doc -preprocessorcomment=comment -number=number_1 -word=keyword_1 -word2=keyword_2 -string=string_1 -stringraw=string_2 -character=character -uuid=other -preprocessor=preprocessor -operator=operator -identifier=identifier_1 -stringeol=string_eol -verbatim=string_2 -regex=regex -commentlinedoc=comment_line_doc -commentdockeyword=comment_doc_keyword -commentdockeyworderror=comment_doc_keyword_error -globalclass=class -tripleverbatim=string_2 -hashquotedstring=string_2 - -[keywords] -primary=break case const continue string default do else enum float for goto if return switch typedef void while false nil true -secondary= -docComment= - -[lexer_properties] -styling.within.preprocessor=1 -lexer.cpp.track.preprocessor=0 -preprocessor.symbol.$(file.patterns.cpp)=# -preprocessor.start.$(file.patterns.cpp)=if ifdef ifndef -preprocessor.middle.$(file.patterns.cpp)=else elif -preprocessor.end.$(file.patterns.cpp)=endif - -[settings] -extension=qc -comment_single=// -comment_open=/* -comment_close=*/ -comment_use_indent=true -context_action_cmd= - -[indentation] -width=4 -type=0 - -[build_settings] -compiler=gmqcc -Wall "%f" -o "%e" -linker= -run_cmd=qcvm "./%e" diff --git a/syntax/gtksourceview/README b/syntax/gtksourceview/README deleted file mode 100644 index 73a6072..0000000 --- a/syntax/gtksourceview/README +++ /dev/null @@ -1,5 +0,0 @@ -To use the gtksourceview syntax highlighting install qc.lang to the syntax -directory for gtksourceview - -# Can be installed globally to -/usr/share/gtksourceview-[version]/language-specs/ diff --git a/syntax/gtksourceview/qc.lang b/syntax/gtksourceview/qc.lang deleted file mode 100644 index 827290f..0000000 --- a/syntax/gtksourceview/qc.lang +++ /dev/null @@ -1,173 +0,0 @@ - - - - *.qc - // - /* - */ - - - -