Compare commits

..

No commits in common. "main" and "0.1-rc1" have entirely different histories.

270 changed files with 16044 additions and 32727 deletions

15
.gitignore vendored
View file

@ -1,15 +0,0 @@
*.o
*.d
*.orig
*.rej
*.patch
*.diff
*.exe
gmqcc
gmqpak
qcvm
testsuite
build/
.idea/

13
AUTHORS
View file

@ -1,10 +1,3 @@
Authors:
Dale `graphitemaster` Weiler - Charismatic Visionary / Programmer
Wolfgang `Blub\w` Bumiller - Main Programmer
Thanks to:
Forest `LordHavoc` Hale - Technical support and assistance
Rudolf `divVerent` Polzer - Technical support and assistance
Matthias `matthiaskrgr` Krüger - Miscellaneous assistance
Samual `Samual` Lenks - Preprocessor assistance
Igor `ignatenkobrain` Gnatenko - Fedora packages
gmqcc brought to you by:
Dale Weiler
Wolfgang Bumiller

View file

@ -1,35 +0,0 @@
cmake_minimum_required(VERSION 2.8)
project(gmqcc)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
set(SOURCE_FILES
algo.h
ast.cpp ast.h
code.cpp
conout.cpp
fold.cpp fold.h
ftepp.cpp
gmqcc.h
intrin.cpp intrin.h
ir.cpp
ir.h
lexer.cpp lexer.h
opts.cpp
parser.cpp parser.h
stat.cpp
utf8.cpp
util.cpp)
add_library(gmqcclib ${SOURCE_FILES})
add_executable(gmqcc main.cpp)
target_link_libraries(gmqcc gmqcclib)
add_executable(testsuite test.cpp)
target_link_libraries(testsuite gmqcclib)
add_executable(qcvm exec.cpp)
target_link_libraries(qcvm gmqcclib)

21
LICENSE
View file

@ -1,21 +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.

245
Makefile
View file

@ -1,204 +1,63 @@
# Compilation options:
# * LTO - Link time optimization [default=0]
# * ASAN - Address sanitizer [default=0]
# * UBSAN - Undefined behavior sanitizer [default=0]
# * DEBUG - Debug build [default=0]
# * UNUSED - Remove unused references [default=1]
# * SRCDIR - Out of tree builds [default=./]
LTO ?= 0
ASAN ?= 0
UBSAN ?= 0
DEBUG ?= 0
UNUSED ?= 1
SRCDIR ?= ./
CC ?= clang
CFLAGS += -Wall -I. -pedantic-errors
# Determine if we're building for Windows or not so we can set the right file
# extensions for the binaries and exclude the testsuite because it doesn't build
# for that platform.
ifeq ($(OS),Windows_NT)
GMQCC := gmqcc.exe
QCVM := qcvm.exe
else
GMQCC := gmqcc
QCVM := qcvm
TESTSUITE := testsuite
#turn on tons of warnings if clang is present
ifeq ($(CC), clang)
CFLAGS += \
-Weverything \
-Wno-missing-prototypes \
-Wno-unused-parameter \
-Wno-sign-compare \
-Wno-implicit-fallthrough \
-Wno-sign-conversion \
-Wno-conversion \
-Wno-disabled-macro-expansion \
-Wno-padded \
-Wno-format-nonliteral
endif
ifeq ($(track), no)
CFLAGS += -DNOTRACK
endif
# C++ compiler
CXX ?= clang++
OBJ = \
util.o \
code.o \
ast.o \
ir.o \
error.o
OBJ_A = test/ast-test.o
OBJ_I = test/ir-test.o
OBJ_C = main.o lexer.o parser.o
OBJ_X = exec-standalone.o util.o
# Build artifact directories
OBJDIR := .build/objs
DEPDIR := .build/deps
#default is compiler only
default: gmqcc
%.o: %.c
$(CC) -c $< -o $@ $(CFLAGS)
# Collect all the source files for GMQCC.
GSRCS := ast.cpp
GSRCS += code.cpp
GSRCS += conout.cpp
GSRCS += fold.cpp
GSRCS += ftepp.cpp
GSRCS += intrin.cpp
GSRCS += ir.cpp
GSRCS += lexer.cpp
GSRCS += main.cpp
GSRCS += opts.cpp
GSRCS += parser.cpp
GSRCS += stat.cpp
GSRCS += utf8.cpp
GSRCS += util.cpp
exec-standalone.o: exec.c
$(CC) -c $< -o $@ $(CFLAGS) -DQCVM_EXECUTOR=1
# Collect all the source files for QCVM.
QSRCS := exec.cpp
QSRCS += stat.cpp
QSRCS += util.cpp
# test targets
test_ast: $(OBJ_A) $(OBJ)
$(CC) -o $@ $^ $(CFLAGS)
test_ir: $(OBJ_I) $(OBJ)
$(CC) -o $@ $^ $(CFLAGS)
qcvm: $(OBJ_X)
$(CC) -o $@ $^ $(CFLAGS) -lm
exec.o: execloop.h
exec-standalone.o: execloop.h
test: test_ast test_ir
# Collect all the source files for TESTSUITE.
TSRCS := conout.cpp
TSRCS += opts.cpp
TSRCS += stat.cpp
TSRCS += test.cpp
TSRCS += util.cpp
# compiler target
gmqcc: $(OBJ_C) $(OBJ)
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
#
# Compilation flags
#
CXXFLAGS := -Wall
CXXFLAGS += -Wextra
CXXFLAGS += -Wno-parentheses
CXXFLAGS += -Wno-class-memaccess
CXXFLAGS += -Wno-implicit-fallthrough
CXXFLAGS += -std=c++11
# Disable some unneeded features.
CXXFLAGS += -fno-exceptions
CXXFLAGS += -fno-rtti
CXXFLAGS += -fno-asynchronous-unwind-tables
# Give each function and data it's own section so the linker can remove unused
# references to each, producing smaller, tighter binaries.
ifeq ($(UNUSED),1)
CXXFLAGS += -ffunction-sections
CXXFLAGS += -fdata-sections
endif
# Enable link-time optimizations if requested.
ifeq ($(LTO),1)
CXXFLAGS += -flto
endif
ifeq ($(DEBUG),1)
# Ensure there is a frame-pointer in debug builds.
CXXFLAGS += -fno-omit-frame-pointer
# Disable all optimizations in debug builds.
CXXFLAGS += -O0
# Enable debug symbols.
CXXFLAGS += -g
else
# Disable all the stack protection features in release builds.
CXXFLAGS += -fno-stack-protector
CXXFLAGS += -fno-stack-check
# Disable frame pointer in release builds when AddressSanitizer isn't present.
ifeq ($(ASAN),1)
CXXFLAGS += -fno-omit-frame-pointer
else
CXXFLAGS += -fomit-frame-pointer
endif
# Highest optimization flag in release builds.
CXXFLAGS += -O3
endif
# Sanitizer selection
ifeq ($(ASAN),1)
CXXFLAGS += -fsanitize=address
endif
ifeq ($(UBSAN),1)
CXXFLAGS += -fsanitize=undefined
endif
#
# Dependency flags
#
DEPFLAGS := -MMD
DEPFLAGS += -MP
#
# Linker flags
#
LDFLAGS :=
# Remove unreferenced sections
ifeq ($(UNUSED),1)
LDFLAGS += -Wl,--gc-sections
endif
# Enable link-time optimizations if request.
ifeq ($(LTO),1)
LDFLAGS += -flto
endif
# Sanitizer selection
ifeq ($(ASAN),1)
LDFLAGS += -fsanitize=address
endif
ifeq ($(UBSAN),1)
LDFLAGS += -fsanitize=undefined
endif
# Strip the binaries when not a debug build
ifneq (,$(findstring -g,$(CXXFLAGS)))
STRIP := true
else
STRIP := strip
endif
all: $(GMQCC) $(QCVM) $(TESTSUITE)
# Build artifact directories.
$(DEPDIR):
@mkdir -p $(DEPDIR)
$(OBJDIR):
@mkdir -p $(OBJDIR)
$(OBJDIR)/%.o: %.cpp $(DEPDIR)/%.d | $(OBJDIR) $(DEPDIR)
$(CXX) -MT $@ $(DEPFLAGS) -MF $(DEPDIR)/$*.Td $(CXXFLAGS) -c -o $@ $<
@mv -f $(DEPDIR)/$*.Td $(DEPDIR)/$*.d
$(GMQCC): $(filter %.o,$(GSRCS:%.cpp=$(OBJDIR)/%.o))
$(CXX) $^ $(LDFLAGS) -o $@
$(STRIP) $@
$(QCVM): $(filter %.o,$(QSRCS:%.cpp=$(OBJDIR)/%.o))
$(CXX) $^ $(LDFLAGS) -o $@
$(STRIP) $@
$(TESTSUITE): $(filter %.o,$(TSRCS:%.cpp=$(OBJDIR)/%.o))
$(CXX) $^ $(LDFLAGS) -o $@
$(STRIP) $@
# Determine if the tests should be run.
RUNTESTS := true
ifdef TESTSUITE
RUNTESTS := ./$(TESTSUITE)
endif
test: $(QCVM) $(TESTSUITE)
@$(RUNTESTS)
#all target is test and all
all: test gmqcc
clean:
rm -rf $(DEPDIR) $(OBJDIR)
rm -f *.o gmqcc qcvm test_ast test_ir test/*.o
.PHONY: test clean $(DEPDIR) $(OBJDIR)
# Dependencies
$(filter %.d,$(GSRCS:%.cpp=$(DEPDIR)/%.d)):
include $(wildcard $@)
$(filter %.d,$(QSRCS:%.cpp=$(DEPDIR)/%.d)):
include $(wildcard $@)
$(filter %.d,$(TSRCS:%.cpp=$(DEPDIR)/%.d)):
include $(wildcard $@)

240
README
View file

@ -1 +1,239 @@
An improved QuakeC compiler
This is a work in progress Quake C compiler. There are very few good QC
compilers out there on the internet that can be used in the opensource
community. There are a lot of mediocre compilers, but no one wants those.
This is the solution for that, for once a proper Quake C compiler that is
capable of doing proper optimization.
The compiler is intended to implement modern day compiler design princibles
and support modifications through extensions that are provided for the
user through a low-level syntax specific-language inside the language itself
to implement language functionality.
The design goals of the compiler are very large, it's intended the compiler
supports a multitude of things, these things along with the status of
completeness is represented below in a table.
+-------------------+-----------------------------+------------------+
| Feature | What's it for? | Complete Factor |
+-------------------+-----------------------------+------------------+
. Lexical analysis . Tokenization . 90% .
.-------------------.-----------------------------.------------------.
. Tokenization . Parsing . 90% .
.-------------------.-----------------------------.------------------.
. Parsing / SYA . AST Generation . 09% .
.-------------------.-----------------------------.------------------.
. AST Generation . IR Generation . ??% .
.-------------------.-----------------------------.------------------.
. IR Generation . Code Generation . ??% .
.-------------------.-----------------------------.------------------.
. Code Generation . Binary Generation . ??% .
.-------------------.-----------------------------.------------------.
. Binary Generation . Binary . 100% .
+-------------------+-----------------------------+------------------+
Design tree:
The compiler is intended to work in the following order:
Lexical analysis ->
Tokenization ->
Parsing:
Operator precedence:
Shynting yard algorithm
Inline assembly:
Usage of the assembler subsystem:
top-down parsing and assemblation no optimization
Other parsing:
recrusive decent
->
Abstract syntax tree generation ->
Immediate representation (SSA):
Optimizations:
Constant propagation
Value range propogation
Sparse conditional constant propagation (possibly?)
Dead code elimination
Constant folding
Global value numbering
Partial redundancy elimination
Strength reduction
Common subexpression elimination
Peephole optimizations
Loop-invariant code motion
Inline expansion
Constant folding
Induction variable recognition and elimination
Dead store elimination
Jump threading
->
Code Generation:
Optimizations:
Rematerialization
Code Factoring
Recrusion Elimination
Loop unrolling
Deforestation
->
Binary Generation
File tree and explination:
gmqcc.h
This is the common header with all definitions, structures, and
constants for everything.
error.c
This is the error subsystem, this handles the output of good detailed
error messages (not currently, but will), with colors and such.
lex.c
This is the lexer, a very small basic step-seek lexer that can be easily
changed to add new tokens, very retargetable.
main.c
This is the core compiler entry, handles switches (will) to toggle on
and off certian compiler features.
parse.c
This is the parser which goes over all tokens and generates a parse tree
and check for syntax correctness.
typedef.c
This is the typedef system, this is a seperate file because it's a lot more
complicated than it sounds. This handles all typedefs, and even recrusive
typedefs.
util.c
These are utilities for the compiler, some things in here include a
allocator used for debugging, and some string functions.
assembler.c
This implements support for assembling Quake assembler (which doesn't
actually exist untill now: documentation of the Quake assembler is below.
This also implements (will) inline assembly for the C compiler.
README
This is the file you're currently reading
Makefile
The makefile, when sources are added you should add them to the SRC=
line otherwise the build will not pick it up. Trivial stuff, small
easy to manage makefile, no need to complicate it.
Some targets:
#make gmqcc
Builds gmqcc, creating a `gmqcc` binary file in the current
directory as the makefile.
#make test
Builds the ir and ast tests, creating a `test_ir` and `test_ast`
binary file in the current directory as the makefile.
#make test_ir
Builds the ir test, creating a `test_ir` binary file in the
current directory as the makefile.
#make test_ast
Builds the asr test, creating a `test_ast` binary file in the
current directory as the makefile.
#make clean
Cleans the build files left behind by a previous build, as
well as all the binary files.
#make all
Builds the tests and the compiler binary all in the current
directory of the makefile.
////////////////////////////////////////////////////////////////////////
///////////////////// Quake Assembler Documentation ////////////////////
////////////////////////////////////////////////////////////////////////
Quake assembler is quite simple: it's just an annotated version of the binary
produced by any existing QuakeC compiler, but made cleaner to use, (so that
the location of various globals or strings are not required to be known).
Constants:
Using one of the following valid constant typenames, you can declare
a constant {FLOAT,VECTOR,FUNCTION,FIELD,ENTITY}, all typenames are
proceeded by a colon, and the name (white space doesn't matter).
Examples:
FLOAT: foo 1
VECTOR: bar 1 2 1
STRING: hello "hello world"
Comments:
Commenting assembly requires the use of either # or ; on the line
that you'd like to be ignored by the assembler. You can only comment
blank lines, and not lines assembly already exists on.
Examples:
; this is allowed
# as is this
FLOAT: foo 1 ; this is not allowed
FLOAT: bar 2 # neither is this
Functions:
Creating functions is the same as declaring a constant, simply use
FUNCTION followed by a colon, and the name (white space doesn't matter)
and start the statements for that function on the line after it
Examples:
FLOAT: foo 1
FLOAT: bar 2
FUNCTION: test1
ADD foo, bar, OFS_RETURN
RETURN
FUNCTION: test2
CALL0 test1
DONE
Internal:
The Quake engine provides some internal functions such as print, to
access these you first must declare them and their names. To do this
you create a FUNCTION as you currently do. Adding a $ followed by the
number of the engine builtin (negated).
Examples:
FUNCTION: print $4
FUNCTION: error $3
Misc:
There are some rules as to what your identifiers can be for functions
and constants. All indentifiers mustn't begin with a numeric digit,
identifiers cannot include spaces, or tabs; they cannot contain symbols,
and they cannot exceed 32768 characters. Identifiers cannot be all
capitalized either, as all capatilized identifiers are reserved by the
assembler.
Numeric constants cannot contain special notation such as `1-e10`, all
numeric constants have to be numeric, they can contain decmial points
and signs (+, -) however.
Constants cannot be assigned values of other constants, their value must
be fully expressed inspot of the declartion.
No two identifiers can be the same name, this applies for variables allocated
inside a function scope (despite it being considered local).
There exists one other keyword that is considered sugar, and that
is AUTHOR, this keyword will allow you to speciy the AUTHOR(S) of
the assembly being assembled. The string represented for each usage
of AUTHOR is wrote to the end of the string table. Simaler to the
usage of constants and functions the AUTHOR keyword must be proceeded
by a colon.
Examples:
AUTHOR: "Dale Weiler"
AUTHOR: "Wolfgang Bumiller"
Colons exist for the sole reason of not having to use spaces after
keyword usage (however spaces are allowed). To understand the
following examples below are equivlent.
Example 1:
FLOAT:foo 1
Example 2:
FLOAT: foo 1
Example 3:
FLOAT: foo 2
variable amounts of whitespace is allowed anywhere (as it should be).
think of `:` as a delimiter (which is what it's used for during assembly).
////////////////////////////////////////////////////////////////////////
/////////////////////// Quake C Documentation //////////////////////////
////////////////////////////////////////////////////////////////////////
TODO ....

18
algo.h
View file

@ -1,18 +0,0 @@
#ifndef GMQCC_ALGO_HDR
#define GMQCC_ALGO_HDR
namespace algo {
template<typename ITER>
void shiftback(ITER element, ITER end) {
//typename ITER::value_type backup(move(*element)); // hold the element
typename std::remove_reference<decltype(*element)>::type backup(move(*element)); // hold the element
ITER p = element++;
for (; element != end; p = element++)
*p = move(*element);
*p = move(backup);
}
} // ::algo
#endif

620
asm.c Normal file
View file

@ -0,0 +1,620 @@
/*
* Copyright (C) 2012
* 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"
/*
* Following parse states:
* ASM_FUNCTION -- in a function accepting input statements
* ....
*/
typedef enum {
ASM_NULL,
ASM_FUNCTION
} asm_state;
typedef struct {
char *name;
char type; /* type, float, vector, string, function*/
char elem; /* 0=x, 1=y, or 2=Z? */
int offset; /* location in globals */
bool isconst;
} asm_sym;
VECTOR_MAKE(asm_sym, asm_symbols);
/*
* Assembly text processing: this handles the internal collection
* of text to allow parsing and assemblation.
*/
static char* asm_getline(size_t *byte, FILE *fp) {
char *line = NULL;
size_t read = util_getline(&line, byte, fp);
*byte = read;
if (read == -1) {
mem_d (line);
return NULL;
}
return line;
}
/*
* Entire external interface for main.c - to perform actual assemblation
* of assembly files.
*/
void asm_init(const char *file, FILE **fp) {
*fp = fopen(file, "r");
code_init();
}
void asm_close(FILE *fp) {
fclose(fp);
code_write("program.dat");
}
void asm_clear() {
size_t i = 0;
for (; i < asm_symbols_elements; i++)
mem_d(asm_symbols_data[i].name);
mem_d(asm_symbols_data);
}
/*
* Dumps all values of all constants and assembly related
* information obtained during the assembly procedure.
*/
void asm_dumps() {
size_t i = 0;
for (; i < asm_symbols_elements; i++) {
asm_sym *g = &asm_symbols_data[i];
if (!g->isconst) continue;
switch (g->type) {
case TYPE_VECTOR: {
util_debug("ASM", "vector %s %c[%f]\n", g->name,
(g->elem == 0) ? 'X' :(
(g->elem == 1) ? 'Y' :
(g->elem == 2) ? 'Z' :' '),
INT2FLT(code_globals_data[g->offset])
);
break;
}
case TYPE_FUNCTION: {
util_debug("ASM", "function %s\n", g->name);
}
}
}
}
/*
* Parses a type, could be global or not depending on the
* assembly state: global scope with assignments are constants.
* globals with no assignments are globals. Function body types
* are locals.
*/
static GMQCC_INLINE bool asm_parse_type(const char *skip, size_t line, asm_state *state) {
if ((strstr(skip, "FLOAT:") != &skip[0]) &&
(strstr(skip, "VECTOR:") != &skip[0]) &&
(strstr(skip, "ENTITY:") != &skip[0]) &&
(strstr(skip, "FIELD:") != &skip[0]) &&
(strstr(skip, "STRING:") != &skip[0])) return false;
/* TODO: determine if constant, global, or local */
switch (*skip) {
/* VECTOR */ case 'V': {
float val1;
float val2;
float val3;
asm_sym sym;
char *find = (char*)skip + 7;
char *name = (char*)skip + 7;
while (*find == ' ' || *find == '\t') find++;
/* constant? */
if (strchr(find, ',')) {
/* strip name */
*strchr((name = util_strdup(find)), ',')='\0';
/* find data */
find += strlen(name) + 1;
while (*find == ' ' || *find == '\t') find++;
/* valid name */
if (util_strupper(name) || isdigit(*name)) {
printf("invalid name for vector variable\n");
mem_d(name);
}
/*
* Parse all three elements of the vector. This will only
* pass the first try if we hit a constant, otherwise it's
* a global.
*/
#define PARSE_ELEMENT(X,Y,Z) \
if (isdigit(*X) || *X == '-'||*X == '+') { \
bool negated = (*X == '-'); \
if (negated || *X == '+') { X++; } \
Y = (negated)?-atof(X):atof(X); \
X = strchr(X, ','); \
Z \
}
PARSE_ELEMENT(find, val1, { find ++; while (*find == ' ') { find ++; } });
PARSE_ELEMENT(find, val2, { find ++; while (*find == ' ') { find ++; } });
PARSE_ELEMENT(find, val3, { find ++; /* no need to do anything here */ });
#undef PARSE_ELEMENT
#define BUILD_ELEMENT(X,Y) \
sym.type = TYPE_VECTOR; \
sym.name = util_strdup(name); \
sym.elem = (X); \
sym.offset = code_globals_elements; \
asm_symbols_add(sym); \
code_globals_add(FLT2INT(Y))
BUILD_ELEMENT(0, val1);
BUILD_ELEMENT(1, val2);
BUILD_ELEMENT(2, val3);
#undef BUILD_ELEMENT
mem_d(name);
} else {
/* TODO global not constant */
}
break;
}
/* ENTITY */ case 'E': {
const char *find = skip + 7;
while (*find == ' ' || *find == '\t') find++;
printf("found ENTITY %s\n", find);
break;
}
/* STRING */ case 'S': {
const char *find = skip + 7;
while (*find == ' ' || *find == '\t') find++;
printf("found STRING %s\n", find);
break;
}
}
return false;
}
/*
* Parses a function: trivial case, handles occurances of duplicated
* names among other things. Ensures valid name as well, and even
* internal engine function selection.
*/
static GMQCC_INLINE bool asm_parse_func(const char *skip, size_t line, asm_state *state) {
if (*state == ASM_FUNCTION)
return false;
if (strstr(skip, "FUNCTION:") == &skip[0]) {
asm_sym sym;
char *look = util_strdup(skip+10);
char *copy = look;
char *name = NULL;
while (*copy == ' ' || *copy == '\t') copy++;
memset(&sym, 0, sizeof(asm_sym));
/*
* Chop the function name out of the string, this allocates
* a new string.
*/
name = util_strchp(copy, strchr(copy, '\0'));
/* TODO: failure system, missing name */
if (!name) {
printf("expected name on function\n");
mem_d(copy);
mem_d(name);
return false;
}
/* TODO: failure system, invalid name */
if (!isalpha(*name) || util_strupper(name)) {
printf("invalid identifer for function name\n");
mem_d(copy);
mem_d(name);
return false;
}
/*
* Function could be internal function, look for $
* to determine this.
*/
if (strchr(name, ',')) {
char *find = strchr(name, ',') + 1;
prog_section_function function;
prog_section_def def;
memset(&function, 0, sizeof(prog_section_function));
memset(&def, 0, sizeof(prog_section_def));
/* skip whitespace */
while (*find == ' ' || *find == '\t')
find++;
if (*find != '$') {
printf("expected $ for internal function selection, got %s instead\n", find);
mem_d(copy);
mem_d(name);
return false;
}
find ++;
if (!isdigit(*find)) {
printf("invalid internal identifier, expected valid number\n");
mem_d(copy);
mem_d(name);
return false;
}
*strchr(name, ',')='\0';
/*
* Now add the following items to the code system:
* function
* definition (optional)
* global (optional)
* name
*/
function.entry = -atoi(find);
function.firstlocal = 0;
function.locals = 0;
function.profile = 0;
function.name = code_chars_elements;
function.file = 0;
function.nargs = 0;
def.type = TYPE_FUNCTION;
def.offset = code_globals_elements;
def.name = code_chars_elements;
code_functions_add(function);
code_defs_add (def);
code_chars_put (name, strlen(name));
code_chars_add ('\0');
sym.type = TYPE_FUNCTION;
sym.name = util_strdup(name);
sym.offset = function.entry;
asm_symbols_add(sym);
util_debug("ASM", "added internal function %s to function table\n", name);
/*
* Sanatize the numerical constant used to select the
* internal function. Must ensure it's all numeric, since
* atoi can silently drop characters from a string and still
* produce a valid constant that would lead to runtime problems.
*/
if (util_strdigit(find))
util_debug("ASM", "found internal function %s, -%d\n", name, atoi(find));
else
printf("invalid internal function identifier, must be all numeric\n");
} else {
/*
* The function isn't an internal one. Determine the name and
* amount of arguments the function accepts by searching for
* the `#` (pound sign).
*/
int args = 0;
int size = 0;
char *find = strchr(name, '#');
char *peek = find;
/*
* Code structures for filling after determining the correct
* information to add to the code write system.
*/
prog_section_function function;
prog_section_def def;
memset(&function, 0, sizeof(prog_section_function));
memset(&def, 0, sizeof(prog_section_def));
if (find) {
find ++;
/* skip whitespace */
if (*find == ' ' || *find == '\t')
find++;
/*
* If the input is larger than eight, it's considered
* invalid and shouldn't be allowed. The QuakeC VM only
* allows a maximum of eight arguments.
*/
if (*find == '9') {
printf("invalid number of arguments, must be a valid number from 0-8\n");
mem_d(copy);
mem_d(name);
return false;
}
if (*find != '0') {
/*
* if we made it this far we have a valid number for the
* argument count, so fall through a switch statement and
* do it.
*/
switch (*find) {
case '8': args++; case '7': args++;
case '6': args++; case '5': args++;
case '4': args++; case '3': args++;
case '2': args++; case '1': args++;
}
}
/*
* We need to parse the argument size now by determining
* the argument identifer list used after the amount of
* arguments.
*/
memset(function.argsize, 0, sizeof(function.argsize));
find ++; /* skip the number */
while (*find == ' ' || *find == '\t') find++;
while (size < args) {
switch (*find) {
case 'V': case 'v': function.argsize[size]=3; break;
case 'S': case 's':
case 'F': case 'f':
case 'E': case 'e': function.argsize[size]=1; break;
case '\0':
printf("missing argument identifer, expected %d\n", args);
return false;
default:
printf("error invalid function argument identifier\n");
return false;
}
size++,find++;
}
while (*find == ' ' || *find == '\t') find++;
if (*find != '\0') {
printf("too many function argument identifers expected %d\n", args);
return false;
}
} else {
printf("missing number of argument count in function %s\n", name);
return false;
}
/*
* Now we need to strip the name apart into it's exact size
* by working in the peek buffer till we hit the name again.
*/
if (*peek == '#') {
peek --; /* '#' */
peek --; /* number */
}
while (*peek == ' ' || *peek == '\t') peek--;
/*
* We're guranteed to be exactly where we need to be in the
* peek buffer to null terminate and get our name from name
* without any garbage before or after it.
*/
*++peek='\0';
/*
* We got valid function structure information now. Lets add
* the function to the code writer function table.
*/
function.entry = code_statements_elements;
function.firstlocal = 0;
function.locals = 0;
function.profile = 0;
function.name = code_chars_elements;
function.file = 0;
function.nargs = args;
def.type = TYPE_FUNCTION;
def.offset = code_globals_elements;
def.name = code_chars_elements;
code_functions_add(function);
code_globals_add (code_statements_elements);
code_chars_put (name, strlen(name));
code_chars_add ('\0');
sym.type = TYPE_FUNCTION;
sym.name = util_strdup(name);
sym.offset = function.entry;
asm_symbols_add(sym);
/* update assembly state */
*state = ASM_FUNCTION;
util_debug("ASM", "added context function %s to function table\n", name);
}
mem_d(copy);
mem_d(name);
return true;
}
return false;
}
static GMQCC_INLINE bool asm_parse_stmt(const char *skip, size_t line, asm_state *state) {
/*
* This parses a valid statement in assembly and adds it to the code
* table to be wrote. This needs to handle correct checking of all
* statements to ensure the correct amount of operands are passed to
* the menomic. This must also check for valid function calls (ensure
* the names selected exist in the program scope) and ensure the correct
* CALL* is used (depending on the amount of arguments the function
* is expected to take)
*/
enum {
EXPECT_FUNCTION = 1,
EXPECT_VARIABLE = 2,
EXPECT_VALUE = 3
};
char *c = (char*)skip;
size_t i = 0;
char expect = 0;
prog_section_statement s;
memset(&s, 0, sizeof(prog_section_statement));
/*
* statements are only allowed when inside a function body
* otherwise the assembly is invalid.
*/
if (*state != ASM_FUNCTION)
return false;
/*
* Skip any possible whitespace, it's not wanted we're searching
* for an instruction. TODO: recrusive decent parser skip on line
* entry instead of pre-op.
*/
while (*skip == ' ' || *skip == '\t')
skip++;
for (; i < sizeof(asm_instr)/sizeof(*asm_instr); i++) {
/*
* Iterate all possible instructions and check if the selected
* instructure in the input stream `skip` is actually a valid
* instruction.
*/
if (!strncmp(skip, asm_instr[i].m, asm_instr[i].l)) {
/*
* We hit the end of a function scope, retarget the state
* and add a DONE statement to the statment table.
*/
if (i == AINSTR_END) {
s.opcode = i;
code_statements_add(s);
*state = ASM_NULL;
return true;
}
/*
* Check the instruction type to see what sort of data
* it's expected to have.
*/
if (i >= INSTR_CALL0 && i <= INSTR_CALL8)
expect = EXPECT_FUNCTION;
else
expect = EXPECT_VARIABLE;
util_debug(
"ASM",
"found statement %s expecting: `%s` (%ld operand(s))\n",
asm_instr[i].m,
(expect == EXPECT_FUNCTION)?"function name":(
(expect == EXPECT_VARIABLE)?"variable name":(
(expect == EXPECT_VALUE ?"value" : "unknown"))),
asm_instr[i].o
);
/*
* Parse the operands for `i` (the instruction). The order
* of asm_instr is in the order of the menomic encoding so
* `i` == menomic encoding.
*/
s.opcode = i;
switch (asm_instr[i].o) {
/*
* Each instruction can have from 0-3 operands; and can
* be used with less or more operands depending on it's
* selected use.
*
* DONE for example can use either 0 operands, or 1 (to
* emulate the effect of RETURN)
*
* TODO: parse operands correctly figure out what it is
* that the assembly is trying to do, i.e string table
* lookup, function calls etc.
*
* This needs to have a fall state, we start from the
* end of the string and work backwards.
*/
#define OPEATS(X,Y) X##Y
#define OPCCAT(X,Y) OPEATS(X,Y)
#define OPLOAD(X,Y) \
do { \
util_debug("ASM", "loading operand data ...\n"); \
if (expect == EXPECT_VARIABLE) { \
size_t f=0; \
for (; f<asm_symbols_elements; f++) { \
if (!strncmp(asm_symbols_data[f].name, (Y), strlen(Y)) && \
asm_symbols_data[f].type != TYPE_FUNCTION) { \
(X)=asm_symbols_data[f].offset; \
goto OPCCAT(foundv, __LINE__); \
} \
} \
printf("no variable named %s\n", (Y)); \
break; \
OPCCAT(foundv,__LINE__) : \
printf("operand loaded for %s\n", (Y)); \
} else if (expect == EXPECT_FUNCTION) { \
/* \
* It's a function call not a variable association with an instruction \
* these are harder to handle. \
*/ \
size_t f=0; \
if (strchr(Y, ' ')) { \
*strchr(Y, ' ')='\0'; \
} \
for (; f<asm_symbols_elements; f++) { \
if (!strncmp(asm_symbols_data[f].name, (Y), strlen(Y)) && \
asm_symbols_data[f].type == TYPE_FUNCTION) { \
(X)=asm_symbols_data[f].offset; \
goto OPCCAT(foundf, __LINE__); \
} \
} \
printf("no function named [%s]\n", (Y)); \
break; \
OPCCAT(foundf,__LINE__) : \
printf("operand loaded for [%s]\n", (Y)); \
} \
} while (0)
case 3: { OPLOAD(s.o3.s1,c); break; }
case 2: { OPLOAD(s.o2.s1,c); break; }
case 1: {
while (*c == ' ' || *c == '\t') c++;
c += asm_instr[i].l;
while (*c == ' ' || *c == '\t') c++;
OPLOAD(s.o1.s1, c);
break;
}
#undef OPLOAD
#undef OPCCAT
}
/* add the statement now */
code_statements_add(s);
}
}
return true;
}
void asm_parse(FILE *fp) {
char *data = NULL;
long line = 1; /* current line */
size_t size = 0; /* size of line */
asm_state state = ASM_NULL;
#define asm_end(x) \
do { \
mem_d(data); \
line ++; \
util_debug("ASM", x); \
} while (0); continue
while ((data = asm_getline (&size, fp)) != NULL) {
char *copy = data;
char *skip = copy;
while (*copy == ' ' || *copy == '\t') copy++;
while (*skip != '\n') skip++;
*skip='\0';
if (asm_parse_type(copy, line, &state)){ asm_end("asm_parse_type\n"); }
if (asm_parse_func(copy, line, &state)){ asm_end("asm_parse_func\n"); }
if (asm_parse_stmt(copy, line, &state)){ asm_end("asm_parse_stmt\n"); }
asm_end("asm_parse_white\n");
}
#undef asm_end
asm_dumps();
asm_clear();
}

1897
ast.c Normal file

File diff suppressed because it is too large Load diff

3125
ast.cpp

File diff suppressed because it is too large Load diff

852
ast.h

File diff suppressed because it is too large Load diff

291
code.c Normal file
View file

@ -0,0 +1,291 @@
/*
* Copyright (C) 2012
* 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 "gmqcc.h"
/*
* The macros below expand to a typesafe vector implementation, which
* can be viewed in gmqcc.h
*
* code_statements_data -- raw prog_section_statement array
* code_statements_elements -- number of elements
* code_statements_allocated -- size of the array allocated
* code_statements_add(T) -- add element (returns -1 on error)
*
* code_vars_data -- raw prog_section_var array
* code_vars_elements -- number of elements
* code_vars_allocated -- size of the array allocated
* code_vars_add(T) -- add element (returns -1 on error)
*
* code_fields_data -- raw prog_section_field array
* code_fields_elements -- number of elements
* code_fields_allocated -- size of the array allocated
* code_fields_add(T) -- add element (returns -1 on error)
*
* code_functions_data -- raw prog_section_function array
* code_functions_elements -- number of elements
* code_functions_allocated -- size of the array allocated
* code_functions_add(T) -- add element (returns -1 on error)
*
* code_globals_data -- raw prog_section_def array
* code_globals_elements -- number of elements
* code_globals_allocated -- size of the array allocated
* code_globals_add(T) -- add element (returns -1 on error)
*
* code_chars_data -- raw char* array
* code_chars_elements -- number of elements
* code_chars_allocated -- size of the array allocated
* code_chars_add(T) -- add element (returns -1 on error)
*/
VECTOR_MAKE(prog_section_statement, code_statements);
VECTOR_MAKE(prog_section_def, code_defs );
VECTOR_MAKE(prog_section_field, code_fields );
VECTOR_MAKE(prog_section_function, code_functions );
VECTOR_MAKE(int, code_globals );
VECTOR_MAKE(char, code_chars );
uint16_t code_crc;
uint32_t code_entfields;
void code_init() {
prog_section_function empty_function = {0,0,0,0,0,0,0,{0}};
prog_section_statement empty_statement = {0,{0},{0},{0}};
prog_section_def empty_def = {0, 0, 0};
int i = 0;
code_entfields = 0;
/* omit creation of null code */
if (OPTS_FLAG(OMIT_NULL_BYTES))
return;
/*
* 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++)
code_globals_add(0);
code_chars_add ('\0');
code_functions_add (empty_function);
code_statements_add(empty_statement);
code_defs_add (empty_def);
code_fields_add (empty_def);
}
uint32_t code_genstring(const char *str)
{
uint32_t off = code_chars_elements;
while (*str) {
code_chars_add(*str);
++str;
}
code_chars_add(0);
return off;
}
uint32_t code_cachedstring(const char *str)
{
size_t s = 0;
/* We could implement knuth-morris-pratt or something
* and also take substrings, but I'm uncomfortable with
* pointing to subparts of strings for the sake of clarity...
*/
while (s < code_chars_elements) {
if (!strcmp(str, code_chars_data + s))
return s;
while (code_chars_data[s]) ++s;
++s;
}
return code_genstring(str);
}
void code_test() {
prog_section_def d1 = { TYPE_VOID, 28, 1 };
prog_section_def d2 = { TYPE_FUNCTION, 29, 8 };
prog_section_def d3 = { TYPE_STRING, 30, 14};
prog_section_function f1 = { 1, 0, 0, 0, 1, 0,0, {0}};
prog_section_function f2 = {-4, 0, 0, 0, 8, 0,0, {0}};
prog_section_function f3 = { 0, 0, 0, 0, 14+13, 0,0, {0}};
prog_section_function f4 = { 0, 0, 0, 0, 14+13+10, 0,0, {0}};
prog_section_function f5 = { 0, 0, 0, 0, 14+13+10+7, 0,0, {0}};
prog_section_function f6 = { 0, 0, 0, 0, 14+13+10+7+9, 0,0, {0}};
prog_section_statement s1 = { INSTR_STORE_F, {30}, {OFS_PARM0}, {0}};
prog_section_statement s2 = { INSTR_CALL1, {29}, {0}, {0}};
prog_section_statement s3 = { INSTR_RETURN, {0}, {0}, {0}};
code_chars_put("m_init", 0x6);
code_chars_put("print", 0x5);
code_chars_put("hello world\n", 0xC);
code_chars_put("m_keydown", 0x9);
code_chars_put("m_draw", 0x6);
code_chars_put("m_toggle", 0x8);
code_chars_put("m_shutdown", 0xA);
code_globals_add(1); /* m_init */
code_globals_add(2); /* print */
code_globals_add(14); /* hello world in string table */
/* now the defs */
code_defs_add (d1); /* m_init */
code_defs_add (d2); /* print */
code_defs_add (d3); /*hello_world*/
code_functions_add (f1); /* m_init */
code_functions_add (f2); /* print */
code_functions_add (f3); /* m_keydown */
code_functions_add (f4);
code_functions_add (f5);
code_functions_add (f6);
code_statements_add(s1);
code_statements_add(s2);
code_statements_add(s3);
}
qcint code_alloc_field (size_t qcsize)
{
qcint pos = (qcint)code_entfields;
code_entfields += qcsize;
return pos;
}
bool code_write(const char *filename) {
prog_header code_header;
FILE *fp = NULL;
size_t it = 2;
/* see proposal.txt */
if (OPTS_FLAG(OMIT_NULL_BYTES)) {}
code_header.statements.offset = sizeof(prog_header);
code_header.statements.length = code_statements_elements;
code_header.defs.offset = code_header.statements.offset + (sizeof(prog_section_statement) * code_statements_elements);
code_header.defs.length = code_defs_elements;
code_header.fields.offset = code_header.defs.offset + (sizeof(prog_section_def) * code_defs_elements);
code_header.fields.length = code_fields_elements;
code_header.functions.offset = code_header.fields.offset + (sizeof(prog_section_field) * code_fields_elements);
code_header.functions.length = code_functions_elements;
code_header.globals.offset = code_header.functions.offset + (sizeof(prog_section_function) * code_functions_elements);
code_header.globals.length = code_globals_elements;
code_header.strings.offset = code_header.globals.offset + (sizeof(int32_t) * code_globals_elements);
code_header.strings.length = code_chars_elements;
code_header.version = 6;
if (opts_forcecrc)
code_header.crc16 = opts_forced_crc;
else
code_header.crc16 = code_crc;
code_header.entfield = code_entfields;
if (OPTS_FLAG(DARKPLACES_STRING_TABLE_BUG)) {
util_debug("GEN", "Patching stringtable for -fdarkplaces-stringtablebug\n");
/* >= + P */
code_chars_add('\0'); /* > */
code_chars_add('\0'); /* = */
code_chars_add('\0'); /* P */
}
/* ensure all data is in LE format */
util_endianswap(&code_header, 1, sizeof(prog_header));
util_endianswap(code_statements_data, code_statements_elements, sizeof(prog_section_statement));
util_endianswap(code_defs_data, code_defs_elements, sizeof(prog_section_def));
util_endianswap(code_fields_data, code_fields_elements, sizeof(prog_section_field));
util_endianswap(code_functions_data, code_functions_elements, sizeof(prog_section_function));
util_endianswap(code_globals_data, code_globals_elements, sizeof(int32_t));
fp = util_fopen(filename, "wb");
if (!fp)
return false;
if (1 != fwrite(&code_header, sizeof(prog_header), 1, fp) ||
code_statements_elements != fwrite(code_statements_data, sizeof(prog_section_statement), code_statements_elements, fp) ||
code_defs_elements != fwrite(code_defs_data, sizeof(prog_section_def) , code_defs_elements , fp) ||
code_fields_elements != fwrite(code_fields_data, sizeof(prog_section_field) , code_fields_elements , fp) ||
code_functions_elements != fwrite(code_functions_data, sizeof(prog_section_function) , code_functions_elements , fp) ||
code_globals_elements != fwrite(code_globals_data, sizeof(int32_t) , code_globals_elements , fp) ||
code_chars_elements != fwrite(code_chars_data, 1 , code_chars_elements , fp))
{
fclose(fp);
return false;
}
util_debug("GEN","HEADER:\n");
util_debug("GEN"," version: = %d\n", code_header.version );
util_debug("GEN"," crc16: = %d\n", code_header.crc16 );
util_debug("GEN"," entfield: = %d\n", code_header.entfield);
util_debug("GEN"," statements = {.offset = % 8d, .length = % 8d}\n", code_header.statements.offset, code_header.statements.length);
util_debug("GEN"," defs = {.offset = % 8d, .length = % 8d}\n", code_header.defs .offset, code_header.defs .length);
util_debug("GEN"," fields = {.offset = % 8d, .length = % 8d}\n", code_header.fields .offset, code_header.fields .length);
util_debug("GEN"," functions = {.offset = % 8d, .length = % 8d}\n", code_header.functions .offset, code_header.functions .length);
util_debug("GEN"," globals = {.offset = % 8d, .length = % 8d}\n", code_header.globals .offset, code_header.globals .length);
util_debug("GEN"," strings = {.offset = % 8d, .length = % 8d}\n", code_header.strings .offset, code_header.strings .length);
/* FUNCTIONS */
util_debug("GEN", "FUNCTIONS:\n");
for (; it < code_functions_elements; it++) {
size_t j = code_functions_data[it].entry;
util_debug("GEN", " {.entry =% 5d, .firstlocal =% 5d, .locals =% 5d, .profile =% 5d, .name =% 5d, .file =% 5d, .nargs =% 5d, .argsize ={%d,%d,%d,%d,%d,%d,%d,%d} }\n",
code_functions_data[it].entry,
code_functions_data[it].firstlocal,
code_functions_data[it].locals,
code_functions_data[it].profile,
code_functions_data[it].name,
code_functions_data[it].file,
code_functions_data[it].nargs,
code_functions_data[it].argsize[0],
code_functions_data[it].argsize[1],
code_functions_data[it].argsize[2],
code_functions_data[it].argsize[3],
code_functions_data[it].argsize[4],
code_functions_data[it].argsize[5],
code_functions_data[it].argsize[6],
code_functions_data[it].argsize[7]
);
util_debug("GEN", " NAME: %s\n", &code_chars_data[code_functions_data[it].name]);
/* Internal functions have no code */
if (code_functions_data[it].entry >= 0) {
util_debug("GEN", " CODE:\n");
for (;;) {
if (code_statements_data[j].opcode != AINSTR_END)
util_debug("GEN", " %-12s {% 5i,% 5i,% 5i}\n",
asm_instr[code_statements_data[j].opcode].m,
code_statements_data[j].o1.s1,
code_statements_data[j].o2.s1,
code_statements_data[j].o3.s1
);
else {
util_debug("GEN", " DONE {0x00000,0x00000,0x00000}\n");
break;
}
j++;
}
}
}
mem_d(code_statements_data);
mem_d(code_defs_data);
mem_d(code_fields_data);
mem_d(code_functions_data);
mem_d(code_globals_data);
mem_d(code_chars_data);
fclose(fp);
return true;
}

348
code.cpp
View file

@ -1,348 +0,0 @@
#include <string.h>
#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_GE; 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;
}

View file

@ -1,226 +0,0 @@
#include <stdio.h>
#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;
}

4
data/constant.qc Normal file
View file

@ -0,0 +1,4 @@
float constant_1 = 1;
string constant_2 = "hello world";
vector constant_3 = { -0, +0, 0 };
entity constant_4 = 0

15
data/consts.qc Normal file
View file

@ -0,0 +1,15 @@
/* this is the WIP test for the parser...
* constantly adding stuff here to see if things break
*/
void(string) print = #1;
void(string,string) print2 = #1;
void(string,string,string) print3 = #1;
string(float) ftos = #2;
float CONST_1 = 1;
float CONST_2 = CONST_1 + 1;
void() main = {
print3("CONST_1 = ", ftos(CONST_1), "\n");
print3("CONST_2 = ", ftos(CONST_2), "\n");
};

48
data/fields.qc Normal file
View file

@ -0,0 +1,48 @@
/* this is the WIP test for the parser...
* constantly adding stuff here to see if things break
*/
void(string) print = #1;
void(string,string) print2 = #1;
void(string,string,string) print3 = #1;
string(float) ftos = #2;
entity() spawn = #3;
void(entity) kill = #4;
.float mema;
.float memb;
.vector memv;
.void() fun;
void(entity a, .float f) printfield = {
print3("The field is ", ftos(a.f), "\n");
};
void() funny = {
print("FUNNY\n");
};
void() main = {
local entity pawn;
pawn = spawn();
pawn.mema = 9;
pawn.memv = '1 2 3';
pawn.memb = 10;
print3("x = ", ftos(pawn.memv_x), "\n");
print3("y = ", ftos(pawn.memv_y), "\n");
print3("z = ", ftos(pawn.memv_z), "\n");
print3("a = ", ftos(pawn.mema), "\n");
print3("b = ", ftos(pawn.memb), "\n");
pawn.memv_y += 3;
print3("x = ", ftos(pawn.memv_x), "\n");
print3("y = ", ftos(pawn.memv_y), "\n");
print3("z = ", ftos(pawn.memv_z), "\n");
printfield(pawn, memv_z);
pawn.fun = funny;
pawn.fun();
};

56
data/frames.qc Normal file
View file

@ -0,0 +1,56 @@
/* this is the WIP test for the parser...
* constantly adding stuff here to see if things break
*/
void(string) print = #1;
void(string,string) print2 = #1;
void(string,string,string) print3 = #1;
string(float) ftos = #2;
entity() spawn = #3;
void(entity) kill = #4;
$frame stand1 stand2 standX
.float frame;
.float nextthink;
.void() think;
entity self;
float time;
// void() stand2; this is auto-prototyped
void() stand1 = [ 0, stand2 ] {
// expands to:
//self.frame = 0;
//self.nextthink = time + 0.1;
//self.think = stand2
print("In stand 1...\n");
print3("--> self.frame should be 0, is ", ftos(self.frame), "\n");
};
void() stand2 = [ 1, stand1 ] {
print("In stand 2...\n");
print3("--> self.frame should be 1, is ", ftos(self.frame), "\n");
};
void() standm = {
local string bar;
bar = ftos(self);
print3("Foo ", ftos(self), "\n");
self.frame = 0;
self.nextthink = time + 0.1;
};
void() main = {
self = spawn();
time = 10;
print("Setting think\n");
self.think = stand1;
print("Running think\n");
standm();
print("Running from 'self'\n");
self.think();
self.think();
self.think();
};

43
data/functions.qc Normal file
View file

@ -0,0 +1,43 @@
/* this is the WIP test for the parser...
* constantly adding stuff here to see if things break
*/
void(string) print = #1;
void(string,string) print2 = #1;
void(string,string,string) print3 = #1;
string(float) ftos = #2;
entity() spawn = #3;
void(entity) kill = #4;
.float vis;
.entity other;
float(entity targ) visible = {
return targ.vis;
};
void() printworking = {
print("Working\n");
};
void(void() callback) testcallback = {
callback();
};
void(float) has1param = {};
void() main = {
local entity pawn, pawn2;
pawn = spawn();
pawn2 = spawn();
pawn.other = pawn2;
pawn.other.vis = 0;
if (!visible(pawn.other))
print("Yes\n");
testcallback(printworking);
has1param();
};

5
data/include.qc Normal file
View file

@ -0,0 +1,5 @@
/*
* all of these includes should work. No matter what the spacing
* is, we rely on it.
*/
#include "test/include2.qc"

1
data/include2.qc Normal file
View file

@ -0,0 +1 @@
float foo;

22
data/numbers.qc Normal file
View file

@ -0,0 +1,22 @@
/* this is the WIP test for the parser...
* constantly adding stuff here to see if things break
*/
void(string) print = #1;
void(string,string) print2 = #1;
void(string,string,string) print3 = #1;
string(float) ftos = #2;
void() main = {
float a, b, c;
a = 3 + 4 + 5;
b = (5 * 2) + 1;
c = 3 & 1;
c = 1 | 2;
a = 3 && 4;
b = 0 && 4;
c = 4 && 0;
a = 1 || 1;
b = 1 || 0;
c = 0 || 1;
a = 0 || 0;
};

77
data/parsing.qc Normal file
View file

@ -0,0 +1,77 @@
/* this is the WIP test for the parser...
* constantly adding stuff here to see if things break
*/
void(string,...) print = #1;
string(float) ftos = #2;
entity() spawn = #3;
void(entity) kill = #4;
.float mema;
.float memb;
$framevalue 0
$frame stand1 stand2 standX
$framerestore stand2
$frame stand3
$modelname foobar
$modelname foobar3
void(string a, ...) hasvaria = {
};
void() main = {
entity pawn;
vector vec;
float a;
vec = '3 4 5';
vec_z = 5;
a = 5;
if (a) {
print("a != 0\n");
} else {
print("not a\n");
}
a = 19;
print("Hello, World\n");
pawn = spawn();
pawn.mema = 3;
pawn.memb = 5;
print(ftos(pawn.mema), "\n");
print(ftos(pawn.memb), "\n");
print("SECOND TEST\n");
for (a = 0; a < 3; a = a + 1) {
print("LOOP ", ftos(a), "\n");
}
print("DO-WHILE test\n");
a = 2;
do {
print("Foo\n");
a = a - 1;
} while (a);
float b;
a = 5;
print("a = ", ftos(a), "\n");
b = a += 7;
print("adding\n");
print("a = ", ftos(a), "\n");
print("b = ", ftos(b), "\n");
print("memb = ", ftos(pawn.memb), "\n");
pawn.memb += -1;
print("memb = ", ftos(pawn.memb), "\n");
print("Frame stand3 is ", ftos($stand3), " wooh\n");
};
float() exprtest = {
local float x;
x = 3;
return (x); /* parens */
}

10
data/parth.qc Normal file
View file

@ -0,0 +1,10 @@
void test_parth() {
if (1) { }
if (2) { }
if (3) { }
if (4) { }
if (5) { }
if (6) { }
if (7) { }
if (8) { }
}

12
data/proto.qc Normal file
View file

@ -0,0 +1,12 @@
void(string) print = #1;
void() correct;
void(string) incorrect;
void() correct = {
print("Hello\n");
}
void() incorrect = {
printf("The compiler should error about this function having a wrong type\n");
}

103
data/test.qs Normal file
View file

@ -0,0 +1,103 @@
; these are builtin functions
FUNCTION: makevectors, $1
FUNCTION: setorigin, $2
FUNCTION: setmodel, $3
FUNCTION: setsize, $4
FUNCTION: break, $6
FUNCTION: random, $7
FUNCTION: sound, $8
FUNCTION: normalize, $9
FUNCTION: error, $10
FUNCTION: objerror, $11
FUNCTION: vlen, $12
FUNCTION: vectoyaw, $13
FUNCTION: spawn, $14
FUNCTION: remove, $15
FUNCTION: traceline, $16
FUNCTION: find, $18
FUNCTION: precache_sound, $19
FUNCTION: precache_model, $20
FUNCTION: findradius, $22
FUNCTION: dprint, $25
FUNCTION: ftos, $26
FUNCTION: vtos, $27
FUNCTION: coredump, $28
FUNCTION: traceon, $29
FUNCTION: traceoff, $30
FUNCTION: eprint, $31
FUNCTION: walkmove, $32
FUNCTION: droptofloor, $34
FUNCTION: lightstyle, $35
FUNCTION: rint, $36
FUNCTION: floor, $37
FUNCTION: ceil, $38
FUNCTION: checkbottom, $40
FUNCTION: pointcontents, $41
FUNCTION: fabs, $43
FUNCTION: cvar, $45
FUNCTION: localcmd, $46
FUNCTION: nextent, $47
FUNCTION: particle, $48
FUNCTION: ChangeYaw, $49
FUNCTION: vectoangles, $51
FUNCTION: vectoangles2, $51
FUNCTION: sin, $60
FUNCTION: cos, $61
FUNCTION: sqrt, $62
FUNCTION: changepitch, $63
FUNCTION: tracetoss, $64
FUNCTION: etos, $65
FUNCTION: precache_file, $68
FUNCTION: makestatic, $69
FUNCTION: cvar_set, $72
FUNCTION: ambientsound, $74
FUNCTION: precache_model2,$75
FUNCTION: precache_sound2,$76
FUNCTION: precache_file2, $77
FUNCTION: stof, $81
FUNCTION: tracebox, $90
FUNCTION: randomvec, $91
FUNCTION: getlight, $92
FUNCTION: getlight2, $92
FUNCTION: registercvar, $93
FUNCTION: min, $94
FUNCTION: max, $95
FUNCTION: bound, $96
FUNCTION: pow, $97
FUNCTION: findfloat, $98
FUNCTION: checkextension, $99
FUNCTION: test #0
FLOAT: x, 100
FLOAT: y, 200
FLOAT: r_mul
FLOAT: r_div
FLOAT: r_add
FLOAT: r_sub
MUL_V x,y,r_mul
DIV_V x,y,r_div
ADD_V x,y,r_add
SUV_V x,y,r_sub
STORE_V r_mul
CALL0 dprint
STORE_V
END

11
data/typedef.qc Normal file
View file

@ -0,0 +1,11 @@
typedef float my_float;
typedef vector my_vector;
typedef string my_string;
typedef entity my_entity;
typedef void my_void;
my_float type_float;
my_vector type_vector;
my_string type_string;
my_entity type_entity;
my_void type_void;

5
data/types.qc Normal file
View file

@ -0,0 +1,5 @@
float typef;
vector typev;
string types;
entity typee;
void typev;

19
data/uninit.qc Normal file
View file

@ -0,0 +1,19 @@
void(string) print = #1;
void(float) ftos = #2;
void() main = {
local float uninit, unused, setonly;
local vector invec;
print("foo\n");
setonly = 3;
invec = '1 2 3';
if (0)
uninit = 3;
ftos(uninit);
ftos(invec_x);
};
void(float par) partest = {
ftos(par);
};

43
data/vars.qc Normal file
View file

@ -0,0 +1,43 @@
/* this is the WIP test for the parser...
* constantly adding stuff here to see if things break
*/
void(string) print = #1;
void(string,string) print2 = #1;
void(string,string,string) print3 = #1;
string(float) ftos = #2;
entity() spawn = #3;
void(entity) kill = #4;
float multi, decla, ration;
.vector memvec;
.void(string x) printit;
float(vector different_name, vector b) dot;
float(vector a, vector b) dot = {
return a * b;
};
void(string x) myprintit = {
print3("-> ", x, "\n");
};
void(vector par) vecpar = {
// vector-parameters need _x, _y, _z as well
print3("par_y should be 5... = ", ftos(par_y), "\n");
};
void() main = {
local entity pawn;
local vector foovec;
print3("should be 1: ", ftos(dot('1 1 0', '1 0 0')), "\n");
foovec = '3 4 5';
foovec_y = 9;
pawn = spawn();
pawn.printit = myprintit;
pawn.printit("Hello");
vecpar('1 5 9');
};

10
data/vector.qc Normal file
View file

@ -0,0 +1,10 @@
vector vec1 = {-0 +0 0 };
vector vec2 = {.0 .0 .0 };
vector vec3 = {-.0 +.0 +0.1 };
vector vec4 = {1.1 2.2 3.3 };
vector vec5 = {2. 3. 4. };
vector vec6 = {-2. +3. -4. };
/*
* These are just comments: Ideally there is still some broken things
* for the vector yet. which sort of sucks.
*/

View file

@ -1,771 +1,49 @@
.\"mdoc
.Dd January 24, 2013
.Dt GMQCC 1 PRM
.Os
.Sh NAME
.Nm gmqcc
.Nd A Quake C compiler built from the NIH realm of sarcastic wit
.Sh SYNOPSIS
.Nm gmqcc
.Op Cm options
.Op Ar files...
.Sh DESCRIPTION
Traditionally, a QC compiler reads the file
.Pa progs.src
which in its first line contains the output filename, and the rest is a
.\" Process with groff -man -Tascii file.3
.TH GMQCC 1 2012-07-12 "" "gmqcc Manual"
.SH NAME
gmqcc \- A Quake C compiler which tries to reduce suckiness.
.SH SYNOPSIS
.B gmqcc
[\fIOPTIONS\fR] [\fIfiles...\fR]
.SH DESCRIPTION
Traditionally, a QC compiler reads the file \fIprogs.src\fR which
in its first line contains the output filename, and the rest is a
list of QC source files that are to be compiled in order.
.Nm gmqcc
optionally takes options to specify the output and
\fBgmqcc\fR optionally takes options to specify the output and
input files on the commandline, and also accepts assembly files.
.Sh OPTIONS
.Nm gmqcc
mostly tries to mimic gcc's commandline handling, though
.SH OPTIONS
\fBgmqcc\fR mostly tries to mimick gcc's commandline handling, though
there are also traditional long-options available.
.Bl -tag -width Ds
.It Fl h , Fl -help
.TP
.B "-h, --help"
Show a usage message and exit.
.It Fl o , Fl -output= Ns Ar filename
.TP
.BI "-o, --output=" filename
Specify the output filename. Defaults to progs.dat. This will overwrite
the output file listed in a
.Pa progs.src
file in case such a file is used.
.Bl -tag -width indent
.It Fl O Ns Ar number
Specify the optimization level
.It Ar 3
Highest optimization level
.It Ar 2
Default optimization level
.It Ar 1
Minimal optimization level
.It Ar 0
Disable optimization entirely
.El
.Pp
.It Fl O Ns Ar name , Fl Ono- Ns Ar name
Enable or disable a specific optimization. Note that these options
must be used after setting the optimization level, otherwise they'll
be overwritten.
.It Fl O Ns Cm help
List all possible optimizations and the optimization level they're
activated at.
.It Fl q , Fl -quiet
Be less verbose. In particular removes the messages about which files
are being processed, and which compilation mode is being used, and
some others. Warnings and errors will of course still be displayed.
.It Fl D Ns Ar macroname , Fl D Ns Ar macroname Ns = Ns Ar value
Predefine a macro, optionally with a optional value.
.It Fl E
Run only the preprocessor as if
.Fl f Ns Cm ftepp
was used and print the preprocessed code to stdout.
.It Fl W Ns Ar warning , Fl Wno- Ns Ar warning
the output file listed in a \fIprogs.src\fR file in case such a file is used.
.TP
.BI "-O" n
Specify the optimization level, similar to gcc.
.TP
.BI "-a" filename
Append the specified files to the list of files to assemble using the QC-Assembler.
.TP
.BI "-s" filename
Append the specified file which is to be interpreted as a \fIprogs.src\fR file.
.TP
.BI "-std=" standard
Use the specified standard for parsing QC code. The following standards are available:
.IR gmqcc , qcc , fteqcc
.TP
.BI -W warning "\fR, " "" -Wno- warning
Enable or disable a warning.
.It Fl W Ns Cm all
Enable almost all warnings. Overrides preceding
.Fl W
parameters.
.Pp
The following warnings will
.Em not
be enabled:
.Bl -tag -width indent -offset indent
.It Fl W Ns Cm uninitialized-global
.El
.It Fl W Ns Cm error , Fl Wno- Ns Cm error
Controls whether or not all warnings should be treated as errors.
.It Fl Werror- Ns Ar warning , Fl Wno-error- Ns Ar warning
Controls whether a specific warning should be an error.
.It Fl W Ns Cm help
List all possible warn flags.
.It Fl f Ns Ar flag , Fl fno- Ns Ar flag
Enable or disable a specific compile flag. See the list of flags
below.
.It Fl f Ns Cm help
List all possible compile flags.
.It Fl nocolor
Disables colored output
.It Fl config= Ns Ar file
Use an ini file to read all the
.Fl O , Fl W
and
.Fl f
flag from. See the
.It Fl "debug"
Turn on some compiler debugging mechanisms.
.It Fl memchk
Turn on compiler mem-check. (Shows allocations and checks for leaks.)
.It Fl -memdumpcols Ns Ar columns
Changes the number of columns to use for the debug memory dump, defaults to 16.
.Sx CONFIG
section about the file format.
.It Fl redirout= Ns Ar file
Redirects standard output to a
.Ar file
.It Fl redirerr= Ns Ar file
Redirects standard error to a
.Ar file
.It Fl std= Ns Ar standard
Use the specified standard for parsing QC code. The following standards
are available:
.Ar gmqcc , Ar qcc , Ar fteqcc
Selecting a standard also implies some
.Fl f
options and behaves as if
those options have been written right after the
.Fl std
option, meaning
if you changed them before the
.Fl -std
option, you're now overwriting them.
.Pp
.Fl std= Ns Cm gmqcc No includes:
.Bl -tag -width indent -compact -offset Ds
.It Fl f Ns Cm adjust-vector-fields
.It Fl f Ns Cm correct-logic
.It Fl f Ns Cm true-empty-strings
.It Fl f Ns Cm loop-labels
.It Fl f Ns Cm initialized-nonconstants
.It Fl f Ns Cm translatable-strings
.It Fl fno- Ns Cm false-empty-strings
.It Fl W Ns Cm invalid-parameter-count
.It Fl W Ns Cm missing-returnvalues
.It Fl f Ns Cm correct-ternary Li (cannot be turned off)
.El
.Pp
.Fl std= Ns Cm qcc No includes:
.Bl -tag -width indent -compact -offset Ds
.It Fl f Ns Cm assign-function-types
.It Fl fIno- Ns Cm adjust-vector-fields
.El
.Pp
.Fl std= Ns Cm fteqcc No includes:
.Bl -tag -width indent -compact -offset Ds
.It Fl f Ns Cm ftepp
.It Fl f Ns Cm translatable-strings
.It Fl f Ns Cm assign-function-types
.It Fl W Ns Cm ternary-precedence
.It Fl fno- Ns Cm adjust-vector-fields
.It Fl fno- Ns Cm correct-ternary
.El
.It Fl -add-info
Adds compiler information to the generated binary file. Currently
this includes the following globals:
.Bl -tag -width indent -compact
.It Li reserved:version
String containing the compiler version as printed by the \-\-version
parameter.
.El
.It Fl -correct , Fl -no-correct
When enabled, errors about undefined values try to suggest an existing
value via spell checking.
.It Fl dump
DEBUG OPTION. Print the code's intermediate representation before the
optimization and finalization passes to stdout before generating the
binary.
.It Fl dumpfin
DEBUG OPTION. Print the code's intermediate representation after the
optimization and finalization passes to stdout before generating the
binary. The instructions will be enumerated, and values will contain a
list of liferanges.
.It Fl force-crc= Ns Ar CRC
Force the produced progs file to use the specified CRC.
.It Fl state-fps= Ns Ar NUM
Activate \-femulate-state and set the emulated FPS to
.Ar NUM Ns .
.El
.Sh COMPILE WARNINGS
.Bl -tag -width Ds
.It Fl W Ns Cm unused-variable
Generate a warning about variables which are declared but never used.
This can be avoided by adding the
.Ql noref
keyword in front of the
variable declaration. Additionally a complete section of unreferenced
variables can be opened using
.Ql #pragma noref 1
and closed via
.Ql #pragma noref 0 Ns .
.It Fl W Ns Cm unused-component
Generate a warning about vector variables which are declared but not all their
components are used.
.It Fl W Ns Cm used-uninitialized
Generate a warning if it is possible that a variable can be used
without prior initialization. Note that this warning is not
necessarily reliable if the initialization happens only under certain
conditions. The other way is
.Em not
possible: that the warning is
.Em not
generated when uninitialized use
.Em is
possible.
.It Fl W Ns Cm unknown-control-sequence
Generate an error when an unrecognized control sequence in a string is
used. Meaning: when there's a character after a backslash in a string
which has no known meaning.
.It Fl W Ns Cm extensions
Warn when using special extensions which are not part of the selected
standard.
.It Fl W Ns Cm field-redeclared
Generally QC compilers ignore redeclaration of fields. Here you can
optionally enable a warning.
.It Fl W Ns Cm missing-return-values
Functions which aren't of type
.Ft void
will warn if it possible to
reach the end without returning an actual value.
.It Fl W Ns Cm invalid-parameter-count
Warn about a function call with an invalid number of parameters.
.It Fl W Ns Cm local-shadows
Warn when a locally declared variable shadows variable.
.It Fl W Ns Cm local-constants
Warn when the initialization of a local variable turns the variable
into a constant. This is default behaviour unless
.Fl f Ns Cm initialized-nonconstants
is used.
.It Fl W Ns Cm void-variables
There are only 2 known global variables of type void:
.Ql end_sys_globals
and
.Ql end_sys_fields Ns .
Any other void-variable will warn.
.It Fl W Ns Cm implicit-function-pointer
A global function which is not declared with the
.Ql var
keyword is
expected to have an implementing body, or be a builtin. If neither is
the case, it implicitly becomes a function pointer, and a warning is
generated.
.It Fl W Ns Cm variadic-function
Currently there's no way for an in QC implemented function to access
variadic parameters. If a function with variadic parameters has an
implementing body, a warning will be generated.
.It Fl W Ns Cm frame-macros
Generate warnings about
.Ql $frame
commands, for instance about
duplicate frame definitions.
.It Fl W Ns Cm effectless-statement
Warn about statements which have no effect. Any expression which does
not call a function or assigns a variable.
.It Fl W Ns Cm end-sys-fields
The
.Ql end_sys_fields
variable is supposed to be a global variable
of type
.Ft void Ns .
It is also recognized as a \fIfield\fR but this
will generate a warning.
.It Fl W Ns Cm assign-function-types
Warn when assigning to a function pointer with an unmatching
signature. This usually happens in cases like assigning the null
function to an entity's .think function pointer.
.It Fl W Ns Cm cpp
Show warnings created using the preprocessor's '#warning' directive.
.It Fl W Ns Cm multifile-if
Warn if there's a preprocessor \fI#if\fR spanning across several
files.
.It Fl W Ns Cm double-declaration
Warn about multiple declarations of globals. This seems pretty common
in QC code so you probably do not want this unless you want to clean
up your code.
.It Fl W Ns Cm const-var
The combination of \fIconst\fR and \fIvar\fR is not illegal, however
different compilers may handle them differently. We were told, the
intention is to create a function-pointer which is not assignable.
This is exactly how we interpret it. However for this interpretation
the
.Ql var
keyword is considered superfluous (and philosophically
wrong), so it is possible to generate a warning about this.
.It Fl W Ns Cm multibyte-character
Warn about multibyte character constants, they do not work right now.
.It Fl W Ns Cm ternary-precedence
Warn if a ternary expression which contains a comma operator is used
without enclosing parenthesis, since this is most likely not what you
actually want. We recommend the
.Fl f Ns Cm correct-ternary
option.
.It Fl W Ns Cm unknown-pragmas
Warn when encountering an unrecognized
.Ql #pragma
line.
.It Fl W Ns Cm unreachable-code
Warn about unreachable code. That is: code after a return statement,
or code after a call to a function marked as 'noreturn'.
.It Fl W Ns Cm debug
Enable some warnings added in order to help debugging in the compiler.
You won't need this.
.It Fl W Ns Cm unknown-attribute
Warn on an unknown attribute. The warning will inlclude only the first
token inside the enclosing attribute-brackets. This may change when
the actual attribute syntax is better defined.
.It Fl W Ns Cm reserved-names
Warn when using reserved names such as
.Ql nil Ns .
.It Fl W Ns Cm uninitialized-constant
Warn about global constants (using the
.Ql const
keyword) with no
assigned value.
.It Fl W Ns Cm uninitialized-global
Warn about global variables with no initializing value. This is off by
default, and is added mostly to help find null-values which are
supposed to be replaced by the untyped 'nil' constant.
.It Fl W Ns Cm different-qualifiers
Warn when a variables is redeclared with a different qualifier. For
example when redeclaring a variable as \'var\' which was previously
marked \'const\'.
.It Fl W Ns Cm different-attributes
Similar to the above but for attributes like
.Ql [[noreturn]] Ns .
.It Fl W Ns Cm deprecated
Warn when a function is marked with the attribute
"[[deprecated]]". This flag enables a warning on calls to functions
marked as such.
.It Fl W Ns Cm parenthesis
Warn about possible mistakes caused by missing or wrong parenthesis,
like an assignment in an 'if' condition when there's no additional set
of parens around the assignment.
.It Fl W Ns Cm unsafe-types
When passing variadic parameters via
.Li ...(N)
it can happen that incompatible types are passed to functions. This
enables several warnings when static typechecking cannot guarantee
consistent behavior.
.It Fl W Ns Cm breakdef
When compiling original id1 QC there is a definition for `break`
which conflicts with the 'break' keyword in GMQCC. Enabling this
will print a warning when the definition occurs. The definition is
ignored for both cases.
.It Fl W Ns Cm const-overwrite
When compiling original QuakeWorld QC there are instances where
code overwrites constants. This is considered an error, however
for QuakeWorld to compile it needs to be treated as a warning
instead, as such this warning only works when \-std=qcc.
.It Fl W Ns Cm directive-inmacro
Warn about the use of preprocessor directives inside macros.
.It Fl W Ns Cm builtins
When using a function that is not explicitly defined, the compiler
will search its intrinsics table for something that matches that
function name by appending "__builtin_" to it. This behaviour may
be unexpected, so enabling this will produce a diagnostic when
such a function is resolved to a builtin.
.It Fl W Ns Cm inexact-compares
When comparing an inexact value such as `1.0/3.0' the result is
pathologically wrong. Enabling this will trigger a compiler warning
on such expressions.
.El
.Sh COMPILE FLAGS
.Bl -tag -width Ds
.It Fl f Ns Cm darkplaces-string-table-bug
Add some additional characters to the string table in order to
compensate for a wrong boundcheck in some specific version of the
darkplaces engine.
.It Fl f Ns Cm adjust-vector-fields
When assigning to field pointers of type \fI.vector\fR the common
behaviour in compilers like \fIfteqcc\fR is to only assign the
x-component of the pointer. This means that you can use the vector as
such, but you cannot use its y and z components directly. This flag
fixes this behaviour. Before using it make sure your code does not
depend on the buggy behaviour.
.It Fl f Ns Cm ftepp
Enable a partially fteqcc-compatible preprocessor. It supports all the
features used in the Xonotic codebase. If you need more, write a
ticket.
.It Fl f Ns Cm ftepp-predefs
Enable some predefined macros. This only works in combination with
\'\-fftepp' and is currently not included by '\-std=fteqcc'. The
following macros will be added:
.Bd -literal -offset indent
__LINE__
__FILE__
__COUNTER__
__COUNTER_LAST__
__RANDOM__
__RANDOM_LAST__
__DATE__
__TIME__
__FUNC__
.Ed
.Pp
Note that
.Li __FUNC__
is not actually a preprocessor macro, but is recognized by the parser
even with the preprocessor disabled.
.Pp
Note that fteqcc also defines
.Li __NULL__
which becomes the first global. Assigning it to a vector does not
yield the same result as in gmqcc where
.Li __NULL__
is defined to
.Li nil
(See
.Fl f Ns Cm untyped-nil
), which will cause the vector to be zero in all components. With fteqcc
only the first component will be 0, while the other two will become
the first to of the global return value. This behavior is odd and
relying on it should be discouraged, and thus is not supported by
gmqcc.
.It Fl f Ns Cm ftepp-mathdefs
Enable math constant definitions. This only works in combination
with \'\-fftepp' and is currently not included by '\-std=fteqcc'.
The following macros will be added:
.Bd -literal -offset indent
M_E
M_LOG2E
M_LOG10E
M_LN2
M_LN10
M_PI
M_PI_2
M_PI_4
M_1_PI
M_2_PI
M_2_SQRTPI
M_SQRT2
M_SQRT1_2
M_TAU
.Ed
.It Fl f Ns Cm ftepp-indirect-expansion
Enable indirect macro expansion. This only works in combination
with '-fftepp' and is currently not included by '-std=fteqcc'.
Enabling this behavior will allow the preprocessor to operate more
like the standard C preprocessor in that it will allow arguments
of macros which are macro-expanded to be substituted into the
definition of the macro.
.Pp
As an example:
.Bd -literal -offset indent
#define STR1(x) #x
#define STR2(x) STR1(x)
#define THE_ANSWER 42
#define THE_ANSWER_STR STR2(THE_ANSWER) /* "42" */
.Ed
With this enabled, an expansion of THE_ANSWER_STR will yield
the string "42". With this disabled an expansion of THE_ANSWER_STR
will yield "THE_ANSWER"
.It Fl f Ns Cm relaxed-switch
Allow switch cases to use non constant variables.
.It Fl f Ns Cm short-logic
Perform early out in logical AND and OR expressions. The final result
will be either a 0 or a 1, see the next flag for more possibilities.
.It Fl f Ns Cm perl-logic
In many languages, logical expressions perform early out in a special
way: If the left operand of an AND yeilds true, or the one of an OR
yields false, the complete expression evaluates to the right side.
Thus
.Ql true && 5
evaluates to 5 rather than 1.
.It Fl f Ns Cm translatable-strings
Enable the underscore intrinsic: Using
.Ql _("A string constant")
will cause the string immediate to get a name with a "dotranslate_"
prefix. The darkplaces engine recognizes these and translates them in
a way similar to how gettext works.
.It Fl f Ns Cm initialized-nonconstants
Don't implicitly convert initialized variables to constants. With this
flag, the \fIconst\fR keyword is required to make a constant.
.It Fl f Ns Cm assign-function-types
If this flag is not set, (and it is set by default in the qcc and
fteqcc standards), assigning function pointers of mismatching
signatures will result in an error rather than a warning.
.It Fl f Ns Cm lno
Produce a linenumber file along with the output .dat file.
.It Fl f Ns Cm correct-ternary
Use C's operator precedence for ternary expressions. Unless your code
depends on fteqcc-compatible behaviour, you'll want to use thi
soption.
.It Fl f Ns Cm single-vector-defs
Normally vectors generate 4 defs, once for the vector, and once for
its components with _x, _y, _z suffixes. This option
prevents components from being listed.
.It Fl f Ns Cm correct-logic
Most QC compilers translate
.Ql if(a_vector)
directly as an IF on the
vector, which means only the x-component is checked. This option causes
vectors to be cast to actual booleans via a NOT_V and, if necessary, a
NOT_F chained to it.
.Bd -literal -offset indent
if (a_vector) // becomes
if not(!a_vector)
// likewise
a = a_vector && a_float // becomes
a = !!a_vector && a_float
.Ed
.It Fl f Ns Cm true-empty-strings
An empty string is considered to be true everywhere. The NOT_S
instruction usually considers an empty string to be false, this option
effectively causes the unary not in strings to use NOT_F instead.
.It Fl f Ns Cm false-empty-strings
An empty string is considered to be false everywhere. This means loops
and if statements which depend on a string will perform a NOT_S
instruction on the string before using it.
.It Fl f Ns Cm utf8
Enable utf8 characters. This allows utf-8 encoded character constants,
and escape sequence codepoints in the valid utf-8 range. Effectively
enabling escape sequences like '\\{x2211}'.
.It Fl f Ns Cm bail-on-werror
When a warning is treated as an error, and this option is set (which
it is by default), it is like any other error and will cause
compilation to stop. When disabling this flag by using
\-fno-bail-on-werror, compilation will continue until the end, but no
output is generated. Instead the first such error message's context is
shown.
.It Fl f Ns Cm loop-labels
Allow loops to be labeled, and allow 'break' and 'continue' to take an
optional label to decide which loop to actually jump out of or
continue.
.Bd -literal -offset indent
for :outer (i = 0; i < n; ++i) {
while (inner) {
...;
if (something)
continue outer;
}
}
.Ed
.It Fl f Ns Cm untyped-nil
Adds a global named 'nil' which is of no type and can be assigned to
anything. No typechecking will be performed on assignments. Assigning
to it is forbidden, using it in any other kind of expression is also
not allowed.
.sp
Note that this is different from fteqcc's __NULL__: In fteqcc,
__NULL__ maps to the integer written as '0i'. It's can be assigned to
function pointers and integers, but it'll error about invalid
instructions when assigning it to floats without enabling the FTE
instruction set. There's also a bug which allows it to be assigned to
vectors, for which the source will be the global at offset 0, meaning
the vector's y and z components will contain the OFS_RETURN x and y
components.
.sp
In that gmqcc the nil global is an actual global filled with zeroes,
and can be assigned to anything including fields, vectors or function
pointers, and they end up becoming zeroed.
.It Fl f Ns Cm permissive
Various effects, usually to weaken some conditions.
.Bl -tag -width indent -offset indent
.It with Fl f Ns Cm untyped-nil
Allow local variables named
.Ql nil Ns .
(This will not allow declaring a global of that name.)
.El
.It Fl f Ns Cm variadic-args
Allow variadic parameters to be accessed by QC code. This can be
achieved via the '...' function, which takes a parameter index and a
typename.
.Pp
Example:
.Bd -literal -offset indent
void vafunc(string...count) {
float i;
for (i = 0; i < count; ++i)
print(...(i, string), "\\n");
}
.Ed
.It Fl f Ns Cm legacy-vector-maths
Most Quake VMs, including the one from FTEQW or up till recently
Darkplaces, do not cope well with vector instructions with overlapping
input and output. This option will avoid producing such code.
.It Fl f Ns Cm expressions-for-builtins
Usually builtin-numbers are just immediate constants. With this flag
expressions can be used, as long as they are compile-time constant.
.Pp
Example:
.Bd -literal -offset indent
void printA() = #1; // the usual way
void printB() = #2-1; // with a constant expression
.Ed
.It Fl f Ns Cm return-assignments
Enabiling this option will allow assigning values or expressions to the
return keyword as if it were a local variable of the same type as the
function's signature's return type.
.Pp
Example:
.Bd -literal -offset indent
float bar() { return 1024; }
float fun() {
return = bar();
return; // returns value of bar
}
.Ed
.It Fl f Ns Cm unsafe-varargs
When passing on varargs to a different functions, this turns some
static error cases into warnings. Like when the caller's varargs are
restricted to a different type than the callee's parameter. Or a list
of unrestricted varargs is passed into restricted varargs.
.It Fl f Ns Cm typeless-stores
Always use STORE_F, LOAD_F, STOREP_F when accessing scalar variables.
This is somewhat incorrect assembly instruction use, but in all engines
they do exactly the same. This makes disassembly output harder to read,
breaks decompilers, but causes the output file to be better compressible.
.It Fl f Ns Cm sort-operands
In commutative instructions, always put the lower-numbered operand first.
This shaves off 1 byte of entropy from all these instructions, reducing
compressed size of the output file.
.It Fl f Ns Cm emulate-state
Emulate OP_STATE operations in code rather than using the instruction.
The desired fps can be set via -state-fps=NUM, defaults to 10.
Specifying \-state-fps implicitly sets this flag. Defaults to off in all
standards.
.It Fl f Ns Cm arithmetic-exceptions
Turn on arithmetic exception tests in the compiler. In constant expressions
which trigger exceptions like division by zero, overflow, underflow, etc,
the following flag will produce diagnostics for what triggered that
exception.
.It Fl f Ns Cm split-vector-parameters
With this flag immediate vector literals which only ever appear as function
parameters won't be stored as vector immediates. Instead, the 3 floats making
up the vector will be copied separately. Essentially this turns a vector-store
instruction into 3 float-store instructions for such cases. This increases
code size but can dramatically reduce the amount of vector globals, which is
after all limited to 64k. There's at least one known codebase where this
lowers the number of globals from over 80k down to around 3k. In other code
bases it doesn't reduce the globals at all but only increases code size.
Just try it and see whether it helps you.
.It Fl f Ns Cm default-eraseable
Force all expressions to be "eraseable" which permits the compiler to
remove unused functions, variables and statements. This is equivlant to
putting [[eraseable]] on all definitions. This is dangerous as it breaks
auto cvars, definitions for functions the engine may be looking for and
translatable strings. Instead, you can mark a definition with [[noerase]]
to prevent this from happening.
.El
.Sh OPTIMIZATIONS
.Bl -tag -width Ds
.It Fl O Ns Cm peephole
Some general peephole optimizations. For instance the code `a = b + c`
typically generates 2 instructions, an ADD and a STORE. This
optimization removes the STORE and lets the ADD write directly into A.
.It Fl O Ns Cm tail-recursion
Tail recursive function calls will be turned into loops to avoid the
overhead of the CALL and RETURN instructions.
.It Fl O Ns Cm overlap-locals
Make all functions which use neither local arrays nor have locals
which are seen as possibly uninitialized use the same local section.
This should be pretty safe compared to other compilers which do not
check for uninitialized values properly. The problem is that there's
QC code out there which really doesn't initialize some values. This is
fine as long as this kind of optimization isn't used, but also, only
as long as the functions cannot be called in a recursive manner. Since
it's hard to know whether or not an array is actually fully
initialized, especially when initializing it via a loop, we assume
functions with arrays to be too dangerous for this optimization.
.It Fl O Ns Cm local-temps
This promotes locally declared variables to "temps". Meaning when a
temporary result of an operation has to be stored somewhere, a local
variable which is not 'alive' at that point can be used to keep the
result. This can reduce the size of the global section.
This will not have declared variables overlap, even if it was
possible.
.It Fl O Ns Cm global-temps
Causes temporary values which do not need to be backed up on a CALL to
not be stored in the function's locals-area. With this, a CALL to a
function may need to back up fewer values and thus execute faster.
.It Fl O Ns Cm strip-constant-names
Don't generate defs for immediate values or even declared constants.
Meaning variables which are implicitly constant or qualified as such
using the 'const' keyword.
.It Fl O Ns Cm overlap-strings
Aggressively reuse strings in the string section. When a string should
be added which is the trailing substring of an already existing
string, the existing string's tail will be returned instead of the new
string being added.
.Pp
For example the following code will only generate 1 string:
.Bd -literal -offset indent
print("Hello you!\\n");
print("you!\\n"); // trailing substring of "Hello you!\\n"
.Ed
.Pp
There's however one limitation. Strings are still processed in order,
so if the above print statements were reversed, this optimization
would not happen.
.It Fl O Ns Cm call-stores
By default, all parameters of a CALL are copied into the
parameter-globals right before the CALL instructions. This is the
easiest and safest way to translate calls, but also adds a lot of
unnecessary copying and unnecessary temporary values. This
optimization makes operations which are used as a parameter evaluate
directly into the parameter-global if that is possible, which is when
there's no other CALL instruction in between.
.It Fl O Ns Cm void-return
Usually an empty RETURN instruction is added to the end of a void
typed function. However, additionally after every function a DONE
instruction is added for several reasons. (For example the qcvm's
disassemble switch uses it to know when the function ends.). This
optimization replaces that last RETURN with DONE rather than adding
the DONE additionally.
.It Fl O Ns Cm vector-components
Because traditional QC code doesn't allow you to access individual
vector components of a computed vector without storing it in a local
first, sometimes people multiply it by a constant like
.Ql '0 1 0'
to get,
in this case, the y component of a vector. This optimization will turn
such a multiplication into a direct component access. If the factor is
anything other than 1, a float-multiplication will be added, which is
still faster than a vector multiplication.
.It Fl O Ns Cm const-fold-dce
For constant expressions that result in dead code (such as a branch whos
condition can be evaluated at compile-time), this will eliminate the branch
and else body (if present) to produce more optimal code.
.El
.Sh CONFIG
The configuration file is similar to regular .ini files. Comments
start with hashtags or semicolons, sections are written in square
brackets and in each section there can be arbitrary many key-value
pairs.
.Pp
There are 3 sections currently:
.Ql flags Ns ,
.Ql warnings Ns ,
.Ql optimizations Ns .
They contain a list of boolean values of the form
.Ql VARNAME = true
or
.Ql VARNAME = false Ns .
The variable names are the same as for the
corresponding
.Fl W , Fl f
or
.Fl O
flag written with only capital letters and
dashes replaced by underscores.
.Pp
Here's an example:
.Bd -literal -offset indent
# a GMQCC configuration file
[flags]
FTEPP = true
ADJUST_VECTOR_FIELDS = false
LNO = true
[warnings]
UNUSED_VARIABLE = false
USED_UNINITIALIZED = true
[optimizations]
PEEPHOLE = true
TAIL_RECURSION = true
.Ed
.Sh FILES
.Bl -tag -width Ds
.It gmqcc.ini.example
A documented example for a gmqcc.ini file.
.El
.Sh SEE ALSO
.Xr qcvm 1
.Sh AUTHOR
See <http://graphitemaster.github.com/gmqcc>.
.Sh BUGS
Currently the '\-fftepp-predefs' flag is not included by '\-std=fteqcc',
partially because it is not entirely conformant to fteqcc.
.Pp
Please report bugs on <http://github.com/graphitemaster/gmqcc/issues>,
or see <http://graphitemaster.github.com/gmqcc> on how to contact us.
.TP
.B -Wall
Enable all warnings. Overrides preceding -W parameters.
.TP
.B -fdarkplaces-string-table-bug
Patch the output file to work around a string-table bug in certain darkplaces versions.
.TP
.B -fomit-nullbytes
Changes the output format to be more efficient. Requires a patched engine. See the
proposal for a better file structure in the gmqcc source tree.

View file

@ -1,107 +0,0 @@
.\" qcvm mdoc manpage
.Dd January 31, 2013
.Dt QCVM 1 PRM
.Os
.Sh NAME
.Nm qcvm
.Nd A standalone QuakeC VM binary executor
.Sh SYNOPSIS
.Nm qcvm
.Op Cm options
.Op Cm parameters
.Ar program-file
.Sh DESCRIPTION
.Nm qcvm
is an executor for QuakeC VM binary files created using a QC
compiler such as gmqcc(1) or fteqcc. It provides a small set of
builtin functions, and by default executes the
.Fn main
function if there is one. Some options useful for debugging are
available as well.
.Sh OPTIONS
There are 2 types of options. Options for the executor, and parameter
options used to add parameters which are passed to the main function
on execution.
.Bl -tag -width Ds
.It Fl h , Fl -help
Show a usage message and exit.
.It Fl trace
Trace the execution. Each instruction will be printed to stdout before
executing it.
.It Fl profile
Perform some profiling. This is currently not really implemented, the
option is available nonetheless.
.It Fl info
Print information from the program's header instead of executing.
.It Fl disasm
Disassemble the program by function instead of executing.
.It Fl disasm-func Ar function
Search for and disassemble the given function.
.It Fl printdefs
List all entries from the program's defs-section. Effectively
listing all the global variables of the program.
This option disables execution.
.It Fl printfields
List all entries from the program's fields-section. Listing all
entity-fields declared in the program.
This option disables execution.
.It Fl printfuns
List functions and some information about their parameters.
This option disables execution. With a verbosity level of 1, builtin
numbers are printed. With a verbosity of 2, the function's sizes are
printed as well. This takes a little longer since the size is found by
searching for a
.Ql DONE
instruction in the code.
.It Fl v
Increase verbosity level, can be used multiple times.
.It Fl vector Ar 'x y z'
Append a vector parameter to be passed to
.Fn main Ns .
.It Fl float Ar number
Append a float parameter to be passed to
.Fn main Ns .
.It Fl string Ar 'text'
Append a string parameter to be passed to
.Fn main Ns .
.El
.Sh BUILTINS
The following builtin functions are available:
.Bl -ohang
.It Li 1) void print(string...) = #1;
.Bd -unfilled -offset indent -compact
Print the passed strings to stdout. At most 8 strings are allowed.
.Ed
.It Li 2) string ftos(float) = #2;
.D1 Convert a float to a string.
.It Li 3) entity spawn() = #3;
.D1 Spawn an entity.
.It Li 4) void remove(entity) = #4;
.D1 Remove an entity.
.It Li 5) string vtos(vector) = #5;
.D1 Convert a vector to a string.
.It Li 6) void error(string...) = #6;
.D1 Print strings to stdout and then exit with an error (limited to 8 arguments)
.It Li 7) float vlen(vector) = #7;
.D1 Get the length of a vector.
.It Li 8) string etos(entity) = #8;
.D1 Get the entity ID as string.
.It Li 9) float stof(string) = #9;
.D1 Convert a string to a float.
.It Li 10) string strcat(string, string) = #10;
.D1 Concatenate two strings, returning a tempstring.
.It Li 11) float strcmp(string, string) = #11;
.Li 12) float strncmp(string, string, float) = #11;
.D1 Compare two strings. Returns the same as the corresponding C functions.
.It Li 12) vector normalize(vector) = #12;
.D1 Normalize a vector so its length is 1.
.It Li 13) float sqrt(float) = #13;
.D1 Get a value's square root.
.El
.Sh SEE ALSO
.Xr gmqcc 1
.Sh AUTHOR
See <http://graphitemaster.github.com/gmqcc>.
.Sh BUGS
Please report bugs on <http://github.com/graphitemaster/gmqcc/issues>,
or see <http://graphitemaster.github.com/gmqcc> on how to contact us.

70
error.c Normal file
View file

@ -0,0 +1,70 @@
/*
* Copyright (C) 2012
* 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 "gmqcc.h"
/*
* Compiler error system, this handles the error printing, and managing
* such as after so many errors just stop the compilation, and other
* intereting like colors for the console.
*/
#ifndef WIN32
int levelcolor[] = {
CON_WHITE,
CON_CYAN,
CON_RED
};
#endif
void vprintmsg(int level, const char *name, size_t line, const char *msgtype, const char *msg, va_list ap)
{
#ifndef WIN32
fprintf (stderr, "\033[0;%dm%s:%d: \033[0;%dm%s: \033[0m", CON_CYAN, name, (int)line, levelcolor[level], msgtype);
#else
fprintf (stderr, "%s:%d: %s: ", name, line, msgtype);
#endif
vfprintf(stderr, msg, ap);
fprintf (stderr, "\n");
}
void printmsg(int level, const char *name, size_t line, const char *msgtype, const char *msg, ...)
{
va_list va;
va_start(va, msg);
vprintmsg(level, name, line, msgtype, msg, va);
va_end (va);
}
void cvprintmsg(lex_ctx ctx, int lvl, const char *msgtype, const char *msg, va_list ap)
{
vprintmsg(lvl, ctx.file, ctx.line, msgtype, msg, ap);
}
void cprintmsg (lex_ctx ctx, int lvl, const char *msgtype, const char *msg, ...)
{
va_list va;
va_start(va, msg);
cvprintmsg(ctx, lvl, msgtype, msg, va);
va_end (va);
}

947
exec.c Normal file
View file

@ -0,0 +1,947 @@
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "gmqcc.h"
MEM_VEC_FUNCTIONS(qc_program, prog_section_statement, code)
MEM_VEC_FUNCTIONS(qc_program, prog_section_def, defs)
MEM_VEC_FUNCTIONS(qc_program, prog_section_def, fields)
MEM_VEC_FUNCTIONS(qc_program, prog_section_function, functions)
MEM_VEC_FUNCTIONS(qc_program, char, strings)
MEM_VEC_FUN_APPEND(qc_program, char, strings)
MEM_VEC_FUN_RESIZE(qc_program, char, strings)
MEM_VEC_FUNCTIONS(qc_program, qcint, globals)
MEM_VEC_FUNCTIONS(qc_program, qcint, entitydata)
MEM_VEC_FUNCTIONS(qc_program, bool, entitypool)
MEM_VEC_FUNCTIONS(qc_program, qcint, localstack)
MEM_VEC_FUN_APPEND(qc_program, qcint, localstack)
MEM_VEC_FUN_RESIZE(qc_program, qcint, localstack)
MEM_VEC_FUNCTIONS(qc_program, qc_exec_stack, stack)
MEM_VEC_FUNCTIONS(qc_program, size_t, profile)
MEM_VEC_FUN_RESIZE(qc_program, size_t, profile)
MEM_VEC_FUNCTIONS(qc_program, prog_builtin, builtins)
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", strerror(err));
}
static void qcvmerror(qc_program *prog, const char *fmt, ...)
{
va_list ap;
prog->vmerror++;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
putchar('\n');
}
qc_program* prog_load(const char *filename)
{
qc_program *prog;
prog_header header;
size_t i;
FILE *file;
file = util_fopen(filename, "rb");
if (!file)
return NULL;
if (fread(&header, sizeof(header), 1, file) != 1) {
loaderror("failed to read header from '%s'", filename);
fclose(file);
return NULL;
}
if (header.version != 6) {
loaderror("header says this is a version %i progs, we need version 6\n", header.version);
fclose(file);
return NULL;
}
prog = (qc_program*)mem_a(sizeof(qc_program));
if (!prog) {
fclose(file);
printf("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, type) \
if (fseek(file, header.hdrvar.offset, SEEK_SET) != 0) { \
loaderror("seek failed"); \
goto error; \
} \
prog->progvar##_alloc = header.hdrvar.length; \
prog->progvar##_count = header.hdrvar.length; \
prog->progvar = (type*)mem_a(header.hdrvar.length * sizeof(*prog->progvar)); \
if (!prog->progvar) \
goto error; \
if (fread(prog->progvar, sizeof(*prog->progvar), header.hdrvar.length, file) \
!= header.hdrvar.length) { \
loaderror("read failed"); \
goto error; \
}
#define read_data1(x, y) read_data(x, x, y)
read_data (statements, code, prog_section_statement);
read_data1(defs, prog_section_def);
read_data1(fields, prog_section_def);
read_data1(functions, prog_section_function);
read_data1(strings, char);
read_data1(globals, qcint);
fclose(file);
/* profile counters */
if (!qc_program_profile_resize(prog, prog->code_count))
goto error;
/* Add tempstring area */
prog->tempstring_start = prog->strings_count;
prog->tempstring_at = prog->strings_count;
if (!qc_program_strings_resize(prog, prog->strings_count + 16*1024))
goto error;
/* spawn the world entity */
if (!qc_program_entitypool_add(prog, true)) {
loaderror("failed to allocate world entity\n");
goto error;
}
for (i = 0; i < prog->entityfields; ++i) {
if (!qc_program_entitydata_add(prog, 0)) {
loaderror("failed to allocate world data\n");
goto error;
}
}
prog->entities = 1;
return prog;
error:
if (prog->filename) mem_d(prog->filename);
if (prog->code) mem_d(prog->code);
if (prog->defs) mem_d(prog->defs);
if (prog->fields) mem_d(prog->fields);
if (prog->functions) mem_d(prog->functions);
if (prog->strings) mem_d(prog->strings);
if (prog->globals) mem_d(prog->globals);
if (prog->entitydata) mem_d(prog->entitydata);
if (prog->entitypool) mem_d(prog->entitypool);
mem_d(prog);
return NULL;
}
void prog_delete(qc_program *prog)
{
if (prog->filename) mem_d(prog->filename);
MEM_VECTOR_CLEAR(prog, code);
MEM_VECTOR_CLEAR(prog, defs);
MEM_VECTOR_CLEAR(prog, fields);
MEM_VECTOR_CLEAR(prog, functions);
MEM_VECTOR_CLEAR(prog, strings);
MEM_VECTOR_CLEAR(prog, globals);
MEM_VECTOR_CLEAR(prog, entitydata);
MEM_VECTOR_CLEAR(prog, entitypool);
MEM_VECTOR_CLEAR(prog, localstack);
MEM_VECTOR_CLEAR(prog, stack);
MEM_VECTOR_CLEAR(prog, profile);
if (prog->builtins_alloc) {
MEM_VECTOR_CLEAR(prog, builtins);
}
/* otherwise the builtins were statically allocated */
mem_d(prog);
}
/***********************************************************************
* VM code
*/
char* prog_getstring(qc_program *prog, qcint str)
{
if (str < 0 || str >= prog->strings_count)
return "<<<invalid string>>>";
return prog->strings + str;
}
prog_section_def* prog_entfield(qc_program *prog, qcint off)
{
size_t i;
for (i = 0; i < prog->fields_count; ++i) {
if (prog->fields[i].offset == off)
return (prog->fields + i);
}
return NULL;
}
prog_section_def* prog_getdef(qc_program *prog, qcint off)
{
size_t i;
for (i = 0; i < prog->defs_count; ++i) {
if (prog->defs[i].offset == off)
return (prog->defs + i);
}
return NULL;
}
qcany* prog_getedict(qc_program *prog, qcint e)
{
if (e >= prog->entitypool_count) {
prog->vmerror++;
printf("Accessing out of bounds edict %i\n", (int)e);
e = 0;
}
return (qcany*)(prog->entitydata + (prog->entityfields * e));
}
qcint prog_spawn_entity(qc_program *prog)
{
char *data;
size_t i;
qcint e;
for (e = 0; e < (qcint)prog->entitypool_count; ++e) {
if (!prog->entitypool[e]) {
data = (char*)(prog->entitydata + (prog->entityfields * e));
memset(data, 0, prog->entityfields * sizeof(qcint));
return e;
}
}
if (!qc_program_entitypool_add(prog, true)) {
prog->vmerror++;
printf("Failed to allocate entity\n");
return 0;
}
prog->entities++;
for (i = 0; i < prog->entityfields; ++i) {
if (!qc_program_entitydata_add(prog, 0)) {
printf("Failed to allocate entity\n");
return 0;
}
}
data = (char*)(prog->entitydata + (prog->entityfields * e));
memset(data, 0, prog->entityfields * sizeof(qcint));
return e;
}
void prog_free_entity(qc_program *prog, qcint e)
{
if (!e) {
prog->vmerror++;
printf("Trying to free world entity\n");
return;
}
if (e >= prog->entitypool_count) {
prog->vmerror++;
printf("Trying to free out of bounds entity\n");
return;
}
if (!prog->entitypool[e]) {
prog->vmerror++;
printf("Double free on entity\n");
return;
}
prog->entitypool[e] = false;
}
qcint prog_tempstring(qc_program *prog, const char *_str)
{
/* we don't access it, but the macro-generated functions don't use
* const
*/
char *str = (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_count)
at = prog->tempstring_start;
/* when it doesn't fit, reallocate */
if (at + len >= prog->strings_count)
{
prog->strings_count = at;
if (!qc_program_strings_append(prog, str, len+1)) {
prog->vmerror = VMERR_TEMPSTRING_ALLOC;
return 0;
}
return at;
}
/* when it fits, just copy */
memcpy(prog->strings + at, str, len+1);
prog->tempstring_at += len+1;
return at;
}
static int print_escaped_string(const char *str, size_t maxlen)
{
int 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 *prog, unsigned int glob, int vtype)
{
static char spaces[28+1] = " ";
prog_section_def *def;
qcany *value;
int len;
if (!glob) {
len = printf("<null>,");
goto done;
}
def = prog_getdef(prog, glob);
value = (qcany*)(&prog->globals[glob]);
if (def) {
const char *name = prog_getstring(prog, def->name);
if (name[0] == '#')
len = printf("$");
else
len = printf("%s ", name);
vtype = def->type;
}
else
len = printf("[@%u] ", glob);
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:
len += print_escaped_string(prog_getstring(prog, value->string), sizeof(spaces)-len-5);
len += printf(",");
/* len += printf("\"%s\",", prog_getstring(prog, value->string)); */
break;
case TYPE_FLOAT:
default:
len += printf("%g,", value->_float);
break;
}
done:
if (len < sizeof(spaces)-1) {
spaces[sizeof(spaces)-1-len] = 0;
printf(spaces);
spaces[sizeof(spaces)-1-len] = ' ';
}
}
static void prog_print_statement(qc_program *prog, prog_section_statement *st)
{
if (st->opcode >= (sizeof(asm_instr)/sizeof(asm_instr[0]))) {
printf("<illegal instruction %d>\n", st->opcode);
return;
}
printf(" <> %-12s", asm_instr[st->opcode].m);
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)
{
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");
}
fflush(stdout);
}
static qcint prog_enterfunction(qc_program *prog, prog_section_function *func)
{
qc_exec_stack st;
size_t p, parampos;
/* back up locals */
st.localsp = prog->localstack_count;
st.stmt = prog->statement;
st.function = func;
#ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS
if (prog->stack_count)
{
prog_section_function *cur;
cur = prog->stack[prog->stack_count-1].function;
if (cur)
{
qcint *globals = prog->globals + cur->firstlocal;
if (!qc_program_localstack_append(prog, globals, cur->locals))
{
printf("out of memory\n");
exit(1);
}
}
}
#else
{
qcint *globals = prog->globals + func->firstlocal;
if (!qc_program_localstack_append(prog, globals, func->locals))
{
printf("out of memory\n");
exit(1);
}
}
#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;
}
}
if (!qc_program_stack_add(prog, st)) {
printf("out of memory\n");
exit(1);
}
return func->entry;
}
static qcint prog_leavefunction(qc_program *prog)
{
prog_section_function *prev = NULL;
size_t oldsp;
qc_exec_stack st = prog->stack[prog->stack_count-1];
#ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS
if (prog->stack_count > 1) {
prev = prog->stack[prog->stack_count-2].function;
oldsp = prog->stack[prog->stack_count-2].localsp;
}
#else
prev = prog->stack[prog->stack_count-1].function;
oldsp = prog->stack[prog->stack_count-1].localsp;
#endif
if (prev) {
qcint *globals = prog->globals + prev->firstlocal;
memcpy(globals, prog->localstack + oldsp, prev->locals);
if (!qc_program_localstack_resize(prog, oldsp)) {
printf("out of memory\n");
exit(1);
}
}
if (!qc_program_stack_remove(prog, prog->stack_count-1)) {
printf("out of memory\n");
exit(1);
}
return st.stmt - 1; /* offset the ++st */
}
bool prog_exec(qc_program *prog, prog_section_function *func, size_t flags, long maxjumps)
{
long jumpcount = 0;
size_t oldxflags = prog->xflags;
prog_section_statement *st;
prog->vmerror = 0;
prog->xflags = flags;
st = prog->code + prog_enterfunction(prog, func);
--st;
switch (flags)
{
default:
case 0:
{
#define QCVM_PROFILE 0
#define QCVM_TRACE 0
# include "execloop.h"
break;
}
case (VMXF_TRACE):
{
#define QCVM_PROFILE 0
#define QCVM_TRACE 1
# include "execloop.h"
break;
}
case (VMXF_PROFILE):
{
#define QCVM_PROFILE 1
#define QCVM_TRACE 0
# include "execloop.h"
break;
}
case (VMXF_TRACE|VMXF_PROFILE):
{
#define QCVM_PROFILE 1
#define QCVM_TRACE 1
# include "execloop.h"
break;
}
};
cleanup:
prog->xflags = oldxflags;
prog->localstack_count = 0;
prog->stack_count = 0;
if (prog->vmerror)
return false;
return true;
}
/***********************************************************************
* main for when building the standalone executor
*/
#if defined(QCVM_EXECUTOR)
#include <math.h>
const char *type_name[TYPE_COUNT] = {
"void",
"string",
"float",
"vector",
"entity",
"field",
"function",
"pointer",
#if 0
"integer",
#endif
"variant"
};
bool opts_debug = false;
bool opts_memchk = false;
typedef struct {
int vtype;
const char *value;
} qcvm_parameter;
VECTOR_MAKE(qcvm_parameter, main_params);
#define CheckArgs(num) do { \
if (prog->argc != (num)) { \
prog->vmerror++; \
printf("ERROR: invalid number of arguments for %s: %i, expected %i\n", \
__FUNCTION__, prog->argc, (num)); \
return -1; \
} \
} while (0)
#define GetGlobal(idx) ((qcany*)(prog->globals + (idx)))
#define GetArg(num) GetGlobal(OFS_PARM0 + 3*(num))
#define Return(any) *(GetGlobal(OFS_RETURN)) = (any)
static int qc_print(qc_program *prog)
{
size_t i;
const char *laststr = NULL;
for (i = 0; i < prog->argc; ++i) {
qcany *str = (qcany*)(prog->globals + OFS_PARM0 + 3*i);
printf("%s", (laststr = prog_getstring(prog, str->string)));
}
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 *prog)
{
printf("*** VM raised an error:\n");
qc_print(prog);
prog->vmerror++;
return -1;
}
static int qc_ftos(qc_program *prog)
{
char buffer[512];
qcany *num;
qcany str;
CheckArgs(1);
num = GetArg(0);
snprintf(buffer, sizeof(buffer), "%g", num->_float);
str.string = prog_tempstring(prog, buffer);
Return(str);
return 0;
}
static int qc_vtos(qc_program *prog)
{
char buffer[512];
qcany *num;
qcany str;
CheckArgs(1);
num = GetArg(0);
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 *prog)
{
char buffer[512];
qcany *num;
qcany str;
CheckArgs(1);
num = GetArg(0);
snprintf(buffer, sizeof(buffer), "%i", num->_int);
str.string = prog_tempstring(prog, buffer);
Return(str);
return 0;
}
static int qc_spawn(qc_program *prog)
{
qcany ent;
CheckArgs(0);
ent.edict = prog_spawn_entity(prog);
Return(ent);
return (ent.edict ? 0 : -1);
}
static int qc_kill(qc_program *prog)
{
qcany *ent;
CheckArgs(1);
ent = GetArg(0);
prog_free_entity(prog, ent->edict);
return 0;
}
static int qc_vlen(qc_program *prog)
{
qcany *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 prog_builtin 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 */
};
static size_t qc_builtins_count = sizeof(qc_builtins) / sizeof(qc_builtins[0]);
static const char *arg0 = NULL;
void usage()
{
printf("usage: [-debug] %s file\n", arg0);
exit(1);
}
static void prog_main_setparams(qc_program *prog)
{
size_t i;
qcany *arg;
for (i = 0; i < main_params_elements; ++i) {
arg = GetGlobal(OFS_PARM0 + 3*i);
arg->vector[0] = 0;
arg->vector[1] = 0;
arg->vector[2] = 0;
switch (main_params_data[i].vtype) {
case TYPE_VECTOR:
#ifdef WIN32
(void)sscanf_s(main_params_data[i].value, " %f %f %f ",
&arg->vector[0],
&arg->vector[1],
&arg->vector[2]);
#else
(void)sscanf(main_params_data[i].value, " %f %f %f ",
&arg->vector[0],
&arg->vector[1],
&arg->vector[2]);
#endif
break;
case TYPE_FLOAT:
arg->_float = atof(main_params_data[i].value);
break;
case TYPE_STRING:
arg->string = prog_tempstring(prog, main_params_data[i].value);
break;
default:
printf("error: unhandled parameter type: %i\n", main_params_data[i].vtype);
break;
}
}
}
int main(int argc, char **argv)
{
size_t i;
qcint fnmain = -1;
qc_program *prog;
size_t xflags = VMXF_DEFAULT;
bool opts_printfields = false;
bool opts_printdefs = false;
bool opts_info = false;
arg0 = argv[0];
if (argc < 2)
usage();
while (argc > 2) {
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;
}
else if (!strcmp(argv[1], "-printdefs")) {
--argc;
++argv;
opts_printdefs = true;
}
else if (!strcmp(argv[1], "-printfields")) {
--argc;
++argv;
opts_printfields = 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;
--argc;
++argv;
if (argc < 3)
usage();
p.value = argv[1];
if (main_params_add(p) < 0) {
if (main_params_data)
mem_d(main_params_data);
printf("cannot add parameter\n");
exit(1);
}
--argc;
++argv;
}
else
usage();
}
prog = prog_load(argv[1]);
if (!prog) {
printf("failed to load program '%s'\n", argv[1]);
exit(1);
}
prog->builtins = qc_builtins;
prog->builtins_count = qc_builtins_count;
prog->builtins_alloc = 0;
if (opts_info) {
printf("Program's system-checksum = 0x%04x\n", (int)prog->crc16);
printf("Entity field space: %i\n", (int)prog->entityfields);
}
for (i = 1; i < prog->functions_count; ++i) {
const char *name = prog_getstring(prog, prog->functions[i].name);
/* printf("Found function: %s\n", name); */
if (!strcmp(name, "main"))
fnmain = (qcint)i;
}
if (opts_info) {
prog_delete(prog);
return 0;
}
if (opts_printdefs) {
for (i = 0; i < prog->defs_count; ++i) {
printf("Global: %8s %-16s at %u\n",
type_name[prog->defs[i].type & DEF_TYPEMASK],
prog_getstring(prog, prog->defs[i].name),
(unsigned int)prog->defs[i].offset);
}
}
else if (opts_printfields) {
for (i = 0; i < prog->fields_count; ++i) {
printf("Field: %8s %-16s at %u\n",
type_name[prog->fields[i].type],
prog_getstring(prog, prog->fields[i].name),
(unsigned int)prog->fields[i].offset);
}
}
else
{
if (fnmain > 0)
{
prog_main_setparams(prog);
prog_exec(prog, &prog->functions[fnmain], xflags, VM_JUMPS_DEFAULT);
}
else
printf("No main function found\n");
}
prog_delete(prog);
return 0;
}
#endif

1624
exec.cpp

File diff suppressed because it is too large Load diff

349
execloop.h Normal file
View file

@ -0,0 +1,349 @@
#if 0
/* Expected variables */
qc_program *prog;
#endif
#define OPA ( (qcany*) (prog->globals + st->o1.u1) )
#define OPB ( (qcany*) (prog->globals + st->o2.u1) )
#define OPC ( (qcany*) (prog->globals + st->o3.u1) )
#define GLOBAL(x) ( (qcany*) (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 (1) {
prog_section_function *newf;
qcany *ed;
qcany *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 (!prog->stack_count)
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:
OPC->vector[0] = OPA->_float * OPB->vector[0];
OPC->vector[1] = OPA->_float * OPB->vector[1];
OPC->vector[2] = OPA->_float * OPB->vector[2];
break;
case INSTR_MUL_VF:
OPC->vector[0] = OPB->_float * OPA->vector[0];
OPC->vector[1] = OPB->_float * OPA->vector[1];
OPC->vector[2] = OPB->_float * 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*)( ((qcint*)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 > 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);
OPC->ivector[0] = ((qcany*)( ((qcint*)ed) + OPB->_int ))->ivector[0];
OPC->ivector[1] = ((qcany*)( ((qcint*)ed) + OPB->_int ))->ivector[1];
OPC->ivector[2] = ((qcany*)( ((qcint*)ed) + OPB->_int ))->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*)ed) - prog->entitydata;
OPC->_int += 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 >= prog->entitydata_count) {
qcvmerror(prog, "`%s` attempted to write to an out of bounds edict (%i)", prog->filename, OPB->_int);
goto cleanup;
}
if (OPB->_int < 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*)(prog->entitydata + OPB->_int);
ptr->_int = OPA->_int;
break;
case INSTR_STOREP_V:
if (OPB->_int < 0 || OPB->_int + 2 >= prog->entitydata_count) {
qcvmerror(prog, "`%s` attempted to write to an out of bounds edict (%i)", prog->filename, OPB->_int);
goto cleanup;
}
if (OPB->_int < 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*)(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 >= (unsigned int)prog->functions_count)
{
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 */
int builtinnumber = -newf->entry;
if (builtinnumber < 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:
qcvmerror(prog, "`%s` tried to execute a STATE operation", prog->filename);
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

8
flags.def Normal file
View file

@ -0,0 +1,8 @@
#ifndef GMQCC_DEFINE_FLAG
#define GMQCC_DEFINE_FLAG(x)
#endif
GMQCC_DEFINE_FLAG(OVERLAP_LOCALS)
GMQCC_DEFINE_FLAG(DARKPLACES_STRING_TABLE_BUG)
GMQCC_DEFINE_FLAG(OMIT_NULL_BYTES)
GMQCC_DEFINE_FLAG(ADJUST_VECTOR_FIELDS)

1679
fold.cpp

File diff suppressed because it is too large Load diff

121
fold.h
View file

@ -1,121 +0,0 @@
#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();
// Bitmask describing which branches of a conditional to take after folding.
// Zero indicates all the branches can be removed.
// ON_TRUE means ON_FALSE can be removed.
// ON_FALSE means ON_TRUE can be removed.
// ON_TRUE | ON_FALSE means nothing can be removed.
enum {
ON_TRUE = 1 << 0,
ON_FALSE = 1 << 1,
};
bool generate(ir_builder *ir);
ast_expression *op(const oper_info *info, ast_expression **opexprs);
ast_expression *intrinsic(const char *intrinsic, size_t n_args, ast_expression **args);
static uint32_t cond_ternary(ast_value *condval, ast_ternary *branch);
static uint32_t cond_ifthen(ast_value *condval, 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_pow(ast_value *lhs, ast_value *rhs);
ast_expression *intrinsic_mod(ast_value *lhs, ast_value *rhs);
ast_expression *intrinsic_fabs(ast_value *a);
ast_expression* intrinsic_nan(void);
ast_expression* intrinsic_epsilon(void);
ast_expression* intrinsic_inf(void);
static qcfloat_t immvalue_float(ir_value *value);
static vec3_t immvalue_vector(ir_value *value);
static uint32_t cond(ast_value *condval, ast_ifthen *branch);
private:
friend struct intrin;
std::vector<ast_value*> m_imm_float;
std::vector<ast_value*> m_imm_vector;
std::vector<ast_value*> m_imm_string;
hash_table_t *m_imm_string_untranslate; /* map<string, ast_value*> */
hash_table_t *m_imm_string_dotranslate; /* map<string, ast_value*> */
parser_t *m_parser;
bool m_initialized;
};
#endif

1947
ftepp.cpp

File diff suppressed because it is too large Load diff

1356
gmqcc.h

File diff suppressed because it is too large Load diff

View file

@ -1,745 +0,0 @@
#This configuration file is similar to a regular .ini file. Comments start
#with hashtags or semicolons, sections are written in square brackets and
#in each section there can be arbitrary many key-value pairs.
#There are 3 sections currently: flags, warnings, optimizations.
#They contain a list of boolean values of the form VARNAME = true or
#VARNAME = false. The variable names are the same as for the corre
#sponding -W, -f or -O flag written with only capital letters and dashes
#replaced by underscores.
[flags]
#Add some additional characters to the string table in order to
#compensate for a wrong boundcheck in some specific version of the
#darkplaces engine.
DARKPLACES_STRING_TABLE_BUG = true
#When assigning to field pointers of type .vector the common be
#haviour in compilers like fteqcc is to only assign the x-compo
#nent of the pointer. This means that you can use the vector as
#such, but you cannot use its y and z components directly. This
#flag fixes this behaviour. Before using it make sure your code
#does not depend on the buggy behaviour.
ADJUST_VECTOR_FIELDS = true
#Enable a partially fteqcc-compatible preprocessor. It supports
#all the features used in the Xonotic codebase. If you need more,
#write a ticket.
FTEPP = true
#Enable some predefined macros. This only works in combination
#with '-fftepp' and is currently not included by '-std=fteqcc'.
#The following macros will be added:
#
# __LINE__
# __FILE__
# __COUNTER__
# __COUNTER_LAST__
# __RANDOM__
# __RANDOM_LAST__
# __DATE__
# __TIME__
# __FUNC__
#
#Note that __FUNC__ is not actually a preprocessor macro, but is
#recognized by the parser even with the preprocessor disabled.
#
#Note that fteqcc also defines __NULL__ which becomes the first
#global. Assigning it to a vector does not yield the same result
#as in gmqcc where __NULL__ is defined to nil (See -funtyped-nil
#), which will cause the vector to be zero in all components. With
#fteqcc only the first component will be 0, while the other two
#will become the first to of the global return value. This behav
#ior is odd and relying on it should be discouraged, and thus is
#not supported by gmqcc.
FTEPP_PREDEFS = false
#Enable math constant definitions. This only works in combination
#with '-fftepp' and is currently not included by '-std=fteqcc'.
#The following macros will be added:
#
# M_E
# M_LOG2E
# M_LOG10E
# M_LN2
# M_LN10
# M_PI
# M_PI_2
# M_PI_4
# M_1_PI
# M_2_PI
# M_2_SQRTPI
# M_SQRT2
# M_SQRT1_2
# M_TAU
FTEPP_MATHDEFS = false
#Enable indirect macro expansion. This only works in combination
#with '-fftepp' and is currently not included by '-std=fteqcc'.
#Enabling this behavior will allow the preprocessor to operate more
#like the standard C preprocessor in that it will allow arguments
#of macros which are macro-expanded to be substituted into the
#definition of the macro. As an example:
#
# #define STR1(x) #x
# #define STR2(x) STR1(x)
# #define THE_ANSWER 42
# #define THE_ANSWER_STR STR2(THE_ANSWER) /* "42" */
#
#With this enabled, an expansion of THE_ANSWER_STR will yield
#the string "42". With this disabled an expansion of THE_ANSWER_STR
#will yield "THE_ANSWER"
FTEPP_INDIRECT_EXPANSION = false
#Allow switch cases to use non constant variables.
RELAXED_SWITCH = true
#Perform early out in logical AND and OR expressions. The final
#result will be either a 0 or a 1, see the next flag for more pos
#sibilities.
SHORT_LOGIC = true
#In many languages, logical expressions perform early out in a
#special way: If the left operand of an AND yeilds true, or the
#one of an OR yields false, the complete expression evaluates to
#the right side. Thus true && 5 evaluates to 5 rather than 1.
PERL_LOGIC = false
#Enable the underscore intrinsic: Using _("A string constant")
#will cause the string immediate to get a name with a "dotrans
#late_" prefix. The darkplaces engine recognizes these and trans
#lates them in a way similar to how gettext works.
TRANSLATABLE_STRINGS = true
#Don't implicitly convert initialized variables to constants. With
#this flag, the const keyword is required to make a constant.
INITIALIZED_NONCONSTANTS = false
#If this flag is not set, (and it is set by default in the qcc and
#fteqcc standards), assigning function pointers of mismatching
#signatures will result in an error rather than a warning.
ASSIGN_FUNCTION_TYPES = true
#Produce a linenumber file along with the output .dat file.
LNO = false
#Use C's operator precedence for ternary expressions. Unless your
#code depends on fteqcc-compatible behaviour, you'll want to use
#this option.
CORRECT_TERNARY = true
#Normally vectors generate 4 defs, once for the vector, and once
#for its components with _x, _y, _z suffixes. This option prevents
#components from being listed.
SINGLE_VECTOR_DEFS = true
#Most QC compilers translate if(a_vector) directly as an IF on
#the vector, which means only the x-component is checked. This
#option causes vectors to be cast to actual booleans via a NOT_V
#and, if necessary, a NOT_F chained to it.
#
# if (a_vector) // becomes
# if not(!a_vector)
# // likewise
# a = a_vector && a_float // becomes
# a = !!a_vector && a_float
CORRECT_LOGIC = true
#An empty string is considered to be true everywhere. The NOT_S
#instruction usually considers an empty string to be false, this
#option effectively causes the unary not in strings to use NOT_F
#instead.
TRUE_EMPTY_STRINGS = false
#An empty string is considered to be false everywhere. This means
#loops and if statements which depend on a string will perform a
#NOT_S instruction on the string before using it.
FALSE_EMPTY_STRINGS = true
#Enable utf8 characters. This allows utf-8 encoded character con
#stants, and escape sequence codepoints in the valid utf-8 range.
#Effectively enabling escape sequences like '\{x2211}'.
UTF8 = true
#When a warning is treated as an error, and this option is set
#(which it is by default), it is like any other error and will
#cause compilation to stop. When disabling this flag by using
#-fno-bail-on-werror, compilation will continue until the end, but
#no output is generated. Instead the first such error message's
#context is shown.
BAIL_ON_WERROR = false
#Allow loops to be labeled, and allow 'break' and 'continue' to
#take an optional label to decide which loop to actually jump out
#of or continue.
#
# for :outer (i = 0; i < n; ++i) {
# while (inner) {
# ...;
# if (something)
# continue outer;
# }
# }
LOOP_LABELS = true
#Adds a global named 'nil' which is of no type and can be assigned
#to anything. No typechecking will be performed on assignments.
#Assigning to it is forbidden, using it in any other kind of
#expression is also not allowed.
#
#Note that this is different from fteqcc's __NULL__: In fteqcc,
#__NULL__ maps to the integer written as '0i'. It's can be
#assigned to function pointers and integers, but it'll error about
#invalid instructions when assigning it to floats without enabling
#the FTE instruction set. There's also a bug which allows it to be
#assigned to vectors, for which the source will be the global at
#offset 0, meaning the vector's y and z components will contain
#the OFS_RETURN x and y components.#
#
#In that gmqcc the nil global is an actual global filled with
#zeroes, and can be assigned to anything including fields, vectors
#or function pointers, and they end up becoming zeroed.
UNTYPED_NIL = true
#Various effects, usually to weaken some conditions.
# with -funtyped-nil
# Allow local variables named nil. (This will not
# allow declaring a global of that name.)
PERMISSIVE = false
#Allow variadic parameters to be accessed by QC code. This can be
#achieved via the '...' function, which takes a parameter index
#and a typename.
#
#Example:
#
# void vafunc(string...count) {
# float i;
# for (i = 0; i < count; ++i)
# print(...(i, string), "\n");
# }
VARIADIC_ARGS = true
#Most Quake VMs, including the one from FTEQW or up till recently
#Darkplaces, do not cope well with vector instructions with over
#lapping input and output. This option will avoid producing such
#code.
LEGACY_VECTOR_MATHS = false
#Usually builtin-numbers are just immediate constants. With this
#flag expressions can be used, as long as they are compile-time
#constant.
#
#Example:
#
# void printA() = #1; // the usual way
# void printB() = #2-1; // with a constant expression
EXPRESSIONS_FOR_BUILTINS = true
#Enabiling this option will allow assigning values or expressions
#to the return keyword as if it were a local variable of the same
#type as the function's signature's return type.
#
#Example:
#
# float bar() { return 1024; }
# float fun() {
# return = bar();
# return; // returns value of bar (this can be omitted)
# }
RETURN_ASSIGNMENTS = true
#When passing on varargs to a different functions, this turns some
#static error cases into warnings. Like when the caller's varargs
#are restricted to a different type than the callee's parameter.
#Or a list of unrestricted varargs is passed into restricted
#varargs.
UNSAFE_VARARGS = false
#Always use STORE_F, LOAD_F, STOREP_F when accessing scalar variables.
#This is somewhat incorrect assembly instruction use, but in all engines
#they do exactly the same. This makes disassembly output harder to read,
#breaks decompilers, but causes the output file to be better compressible.
TYPELESS_STORES = false
#In commutative instructions, always put the lower-numbered operand first.
#This shaves off 1 byte of entropy from all these instructions, reducing
#compressed size of the output file.
SORT_OPERANDS = false
#Emulate OP_STATE operations in code rather than using the instruction.
#The desired fps can be set via -state-fps=NUM, defaults to 10.
EMULATE_STATE = false
#Turn on arithmetic exception tests in the compiler. In constant expressions
#which trigger exceptions like division by zero, overflow, underflow, etc,
#the following flag will produce diagnostics for what triggered that
#exception.
ARITHMETIC_EXCEPTIONS = false
#Split vector-literals which are only used dirctly as function parameters
#into 3 floats stored separately to reduce the number of globals at the
#expense of additional instructions.
SPLIT_VECTOR_PARAMETERS = false
#Force all expressions to be "eraseable" which permits the compiler
#to remove unused functions, variables and statements. This is
#equivlant to putting [[eraseable]] on all definitions. This is
#dangerous as it breaks auto cvars, definitions for functions the
#engine may be looking for and translatable strings. Instead, you
#can mark a definition with [[noerase]] to prevent this from happening.
DEFAULT_ERASEABLE = false
[warnings]
#Generate a warning about variables which are declared but never
#used. This can be avoided by adding the noref keyword in front
#of the variable declaration. Additionally a complete section of
#unreferenced variables can be opened using #pragma noref 1 and
#closed via #pragma noref 0.
UNUSED_VARIABLE = false
#Generate a warning about vector variables which are declared but
#components of it are never used.
UNUSED_COMPONENT = false
#Generate a warning if it is possible that a variable can be used
#without prior initialization. Note that this warning is not nec
#essarily reliable if the initialization happens only under cer
#tain conditions. The other way is not possible: that the warning
#is not generated when uninitialized use is possible.
USED_UNINITIALIZED = false
#Generate an error when an unrecognized control sequence in a
#string is used. Meaning: when there's a character after a back
#slash in a string which has no known meaning.
UNKNOWN_CONTROL_SEQUENCE = false
#Warn when using special extensions which are not part of the
#selected standard.
EXTENSIONS = false
#Generally QC compilers ignore redeclaration of fields. Here you
#can optionally enable a warning.
FIELD_REDECLARED = false
#Functions which aren't of type void will warn if it possible to
#reach the end without returning an actual value.
MISSING_RETURN_VALUES = false
#Warn about a function call with an invalid number of parameters.
INVALID_PARAMETER_COUNT = false
#Warn when a locally declared variable shadows variable.
LOCAL_SHADOWS = false
#Warn when the initialization of a local variable turns the vari
#able into a constant. This is default behaviour unless
#-finitialized-nonconstants is used.
LOCAL_CONSTANTS = false
#There are only 2 known global variables of type void:
#end_sys_globals and end_sys_fields. Any other void-variable
#will warn.
VOID_VARIABLES = false
#A global function which is not declared with the var keyword is
#expected to have an implementing body, or be a builtin. If nei
#ther is the case, it implicitly becomes a function pointer, and a
#warning is generated.
IMPLICIT_FUNCTION_POINTER = false
#Currently there's no way for an in QC implemented function to
#access variadic parameters. If a function with variadic parame
#ters has an implementing body, a warning will be generated.
VARIADIC_FUNCTION = false
#Generate warnings about $frame commands, for instance about
#duplicate frame definitions.
FRAME_MACROS = false
#Warn about statements which have no effect. Any expression which
#does not call a function or assigns a variable.
EFFECTLESS_STATEMENT = false
#The end_sys_fields variable is supposed to be a global variable
#of type void. It is also recognized as a field but this will
#generate a warning.
END_SYS_FIELDS = false
#Warn when assigning to a function pointer with an unmatching sig
#nature. This usually happens in cases like assigning the null
#function to an entity's .think function pointer.
ASSIGN_FUNCTION_TYPES = false
#Show warnings created using the preprocessor's '#warning' directive
CPP = true
#Warn if there's a preprocessor #if spanning across several files.
MULTIFILE_IF = true
#Warn about multiple declarations of globals. This seems pretty
#common in QC code so you probably do not want this unless you
#want to clean up your code.
DOUBLE_DECLARATION = false
#The combination of const and var is not illegal, however differ
#ent compilers may handle them differently. We were told, the
#intention is to create a function-pointer which is not assigna
#ble. This is exactly how we interpret it. However for this
#interpretation the var keyword is considered superfluous (and
#philosophically wrong), so it is possible to generate a warning
#about this.
CONST_VAR = true
#Warn about multibyte character constants, they do not work right
#now.
MULTIBYTE_CHARACTER = false
#Warn if a ternary expression which contains a comma operator is
#used without enclosing parenthesis, since this is most likely not
#what you actually want. We recommend the -fcorrect-ternary
#option.
TERNARY_PRECEDENCE = false
#Warn when encountering an unrecognized #pragma line.
UNKNOWN_PRAGMAS = true
#Warn about unreachable code. That is: code after a return state
#ment, or code after a call to a function marked as 'noreturn'.
UNREACHABLE_CODE = true
#Enable some warnings added in order to help debugging in the com
#piler. You won't need this.
DEBUG = false
#Warn on an unknown attribute. The warning will inlclude only the
#first token inside the enclosing attribute-brackets. This may
#change when the actual attribute syntax is better defined.
UNKNOWN_ATTRIBUTE = true
#Warn when using reserved names such as nil.
RESERVED_NAMES = true
#Warn about global constants (using the const keyword) with no
#assigned value.
UNINITIALIZED_CONSTANT = true
#Warn about global variables with no initializing value. This is
#off by default, and is added mostly to help find null-values
#which are supposed to be replaced by the untyped 'nil' constant.
UNINITIALIZED_GLOBAL = true
#Warn when a variables is redeclared with a different qualifier.
#For example when redeclaring a variable as 'var' which was previ
#ously marked 'const'.
DIFFERENT_QUALIFIERS = true
#Similar to the above but for attributes like [[noreturn]].
DIFFERENT_ATTRIBUTES = true
#Warn when a function is marked with the attribute "[[depre
#cated]]". This flag enables a warning on calls to functions
#marked as such.
DEPRECATED = true
#Warn about possible mistakes caused by missing or wrong parenthe
#sis, like an assignment in an 'if' condition when there's no
#additional set of parens around the assignment.
PARENTHESIS = true
#When passing variadic parameters via ...(N) it can happen that
#incompatible types are passed to functions. This enables several
#warnings when static typechecking cannot guarantee consistent
#behavior.
UNSAFE_TYPES = true
#When compiling original id1 QC there is a definition for `break`
#which conflicts with the 'break' keyword in GMQCC. Enabling this
#print a warning when the definition occurs. The definition is
#ignored for both cases.
BREAKDEF = true
#When compiling original QuakeWorld QC there are instances where
#code overwrites constants. This is considered an error, however
#for QuakeWorld to compile it needs to be treated as a warning
#instead, as such this warning only works when -std=qcc.
CONST_OVERWRITE = true
#Warn about the use of preprocessor directives inside macros.
DIRECTIVE_INMACRO = true
#When using a function that is not explicitly defined, the compiler
#will search its intrinsics table for something that matches that
#function name by appending "__builtin_" to it. This behaviour may
#be unexpected, so enabling this will produce a diagnostic when
#such a function is resolved to a builtin.
BUILTINS = true
#When comparing an inexact value such as `1.0/3.0' the result is
#pathologically wrong. Enabling this will trigger a compiler warning
#on such expressions.
INEXACT_COMPARES = true
[optimizations]
#Some general peephole optimizations. For instance the code `a = b
#+ c` typically generates 2 instructions, an ADD and a STORE. This
#optimization removes the STORE and lets the ADD write directly
#into A.
PEEPHOLE = true
#Tail recursive function calls will be turned into loops to avoid
#the overhead of the CALL and RETURN instructions.
TAIL_RECURSION = true
#Make all functions which use neither local arrays nor have locals
#which are seen as possibly uninitialized use the same local sec
#tion. This should be pretty safe compared to other compilers
#which do not check for uninitialized values properly. The problem
#is that there's QC code out there which really doesn't initialize
#some values. This is fine as long as this kind of optimization
#isn't used, but also, only as long as the functions cannot be
#called in a recursive manner. Since it's hard to know whether or
#not an array is actually fully initialized, especially when ini
#tializing it via a loop, we assume functions with arrays to be
#too dangerous for this optimization.
OVERLAP_LOCALS = true
#This promotes locally declared variables to "temps". Meaning when
#a temporary result of an operation has to be stored somewhere, a
#local variable which is not 'alive' at that point can be used to
#keep the result. This can reduce the size of the global section.
#This will not have declared variables overlap, even if it was
#possible.
LOCAL_TEMPS = true
#Causes temporary values which do not need to be backed up on a
#CALL to not be stored in the function's locals-area. With this, a
#CALL to a function may need to back up fewer values and thus exe
#cute faster.
GLOBAL_TEMPS = true
#Don't generate defs for immediate values or even declared con
#stants. Meaning variables which are implicitly constant or qual
#ified as such using the 'const' keyword.
STRIP_CONSTANT_NAMES = true
#Aggressively reuse strings in the string section. When a string
#should be added which is the trailing substring of an already
#existing string, the existing string's tail will be returned
#instead of the new string being added.
#
#For example the following code will only generate 1 string:
#
# print("Hello you!\n");
# print("you!\n"); // trailing substring of "Hello you!\n"
#
#There's however one limitation. Strings are still processed in
#order, so if the above print statements were reversed, this opti
#mization would not happen.
OVERLAP_STRINGS = true
#By default, all parameters of a CALL are copied into the parame
#ter-globals right before the CALL instructions. This is the easi
#est and safest way to translate calls, but also adds a lot of
#unnecessary copying and unnecessary temporary values. This opti
#mization makes operations which are used as a parameter evaluate
#directly into the parameter-global if that is possible, which is
#when there's no other CALL instruction in between.
CALL_STORES = true
#Usually an empty RETURN instruction is added to the end of a void
#typed function. However, additionally after every function a DONE
#instruction is added for several reasons. (For example the qcvm's
#disassemble switch uses it to know when the function ends.). This
#optimization replaces that last RETURN with DONE rather than
#adding the DONE additionally.
VOID_RETURN = true
#Because traditional QC code doesn't allow you to access individ
#ual vector components of a computed vector without storing it in
#a local first, sometimes people multiply it by a constant like
#'0 1 0' to get, in this case, the y component of a vector. This
#optimization will turn such a multiplication into a direct compo
#nent access. If the factor is anything other than 1, a float-mul
#tiplication will be added, which is still faster than a vector
#multiplication.
VECTOR_COMPONENTS = true
#For constant expressions that result in dead code (such as a
#branch whos condition can be evaluated at compile-time), this
#will eliminate the branch and else body (if present) to produce
#more optimal code.
CONST_FOLD_DCE = true
#For constant expressions we can fold them to immediate values.
#this option cannot be disabled or enabled, the compiler forces
#it to stay enabled by ignoring the value entierly. There are
#plans to enable some level of constant fold disabling, but right
#now the language can't function without it. This is merley here
#as an exercise to the reader.
CONST_FOLD = true

131
gmqcc.vcxproj Executable file
View file

@ -0,0 +1,131 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{85C266A8-7938-4AE6-AB64-428DC32B1ACD}</ProjectGuid>
<RootNamespace>gmqcc</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<OutDir>.\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<IntDir>.\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<TargetName>gmqcc</TargetName>
<GenerateManifest>false</GenerateManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<OutDir>.</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<IntDir>.</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<TargetName>gmqcc</TargetName>
<GenerateManifest>false</GenerateManifest>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>.\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<ExceptionHandling>false</ExceptionHandling>
<BufferSecurityCheck>false</BufferSecurityCheck>
<FloatingPointModel>Fast</FloatingPointModel>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<PrecompiledHeaderOutputFile>
</PrecompiledHeaderOutputFile>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<ManifestFile>
</ManifestFile>
<ProgramDatabaseFile>$(TargetName).pdb</ProgramDatabaseFile>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<AdditionalIncludeDirectories>.\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<ExceptionHandling>false</ExceptionHandling>
<BufferSecurityCheck>false</BufferSecurityCheck>
<FloatingPointModel>Fast</FloatingPointModel>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<PrecompiledHeaderOutputFile>
</PrecompiledHeaderOutputFile>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<ManifestFile>
</ManifestFile>
<ProgramDatabaseFile>$(TargetName).pdb</ProgramDatabaseFile>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="asm.c" />
<ClCompile Include="ast.c" />
<ClCompile Include="code.c" />
<ClCompile Include="error.c" />
<ClCompile Include="exec.c" />
<ClCompile Include="ir.c" />
<ClCompile Include="lexer.c" />
<ClCompile Include="main.c" />
<ClCompile Include="parser.c" />
<ClCompile Include="util.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="ast.h" />
<ClInclude Include="execloop.h" />
<ClInclude Include="gmqcc.h" />
<ClInclude Include="ir.h" />
<ClInclude Include="lexer.h" />
</ItemGroup>
<ItemGroup>
<None Include="flags.def" />
<None Include="warns.def" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

2048
intrin.cpp

File diff suppressed because it is too large Load diff

View file

@ -1,74 +0,0 @@
#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<intrin_func_t> m_intrinsics;
std::vector<ast_expression*> m_generated;
};
#endif

3212
ir.c Normal file

File diff suppressed because it is too large Load diff

4094
ir.cpp

File diff suppressed because it is too large Load diff

516
ir.h
View file

@ -1,334 +1,342 @@
/*
* Copyright (C) 2012
* 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"
/*
* Type large enough to hold all the possible IR flags. This should be
* changed if the static assertion at the end of this file fails.
*/
typedef uint8_t ir_flag_t;
/* ir_value */
struct ir_value;
struct ir_instr;
struct ir_block;
struct ir_function;
struct ir_builder;
struct ir_life_entry_t {
typedef struct
{
/* both inclusive */
size_t start;
size_t end;
};
} ir_life_entry_t;
enum {
IR_FLAG_HAS_ARRAYS = 1 << 0,
IR_FLAG_HAS_UNINITIALIZED = 1 << 1,
IR_FLAG_HAS_GOTO = 1 << 2,
IR_FLAG_INCLUDE_DEF = 1 << 3,
IR_FLAG_ERASABLE = 1 << 4,
IR_FLAG_BLOCK_COVERAGE = 1 << 5,
IR_FLAG_NOREF = 1 << 6,
IR_FLAG_SPLIT_VECTOR = 1 << 7,
struct ir_function_s;
typedef struct ir_value_s {
char *name;
int vtype;
int store;
lex_ctx context;
/* even the IR knows the subtype of a field */
int fieldtype;
/* and the output type of a function */
int outtype;
IR_FLAG_LAST,
IR_FLAG_MASK_NO_OVERLAP = (IR_FLAG_HAS_ARRAYS | IR_FLAG_HAS_UNINITIALIZED),
IR_FLAG_MASK_NO_LOCAL_TEMPS = (IR_FLAG_HAS_ARRAYS | IR_FLAG_HAS_UNINITIALIZED)
};
MEM_VECTOR_MAKE(struct ir_instr_s*, reads);
MEM_VECTOR_MAKE(struct ir_instr_s*, writes);
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;
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;
std::vector<ir_instr *> m_reads;
std::vector<ir_instr *> m_writes;
// constant values
bool m_hasvalue;
/* constantvalues */
bool isconst;
union {
qcfloat_t vfloat;
int vint;
vec3_t vvec;
int32_t ivec[3];
char *vstring;
ir_value *vpointer;
ir_function *vfunc;
} m_constval;
float vfloat;
int vint;
vector vvec;
char *vstring;
struct ir_value_s *vpointer;
struct ir_function_s *vfunc;
} 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
} m_code;
/* filled by the local-allocator */
int32_t local;
/* added for members */
int32_t addroffset;
} code;
// for accessing vectors
ir_value *m_members[3];
ir_value *m_memberof;
/* for acessing vectors */
struct ir_value_s *members[3];
struct ir_value_s *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;
/* For the temp allocator */
MEM_VECTOR_MAKE(ir_life_entry_t, life);
} ir_value;
std::vector<ir_life_entry_t> m_life; // For the temp allocator
int32_t ir_value_code_addr(const ir_value*);
size_t size() const;
/* ir_value can be a variable, or created by an operation */
ir_value* ir_value_var(const char *name, int st, int vtype);
/* if a result of an operation: the function should store
* it to remember to delete it / garbage collect it
*/
ir_value* ir_value_out(struct ir_function_s *owner, const char *name, int st, int vtype);
void ir_value_delete(ir_value*);
bool ir_value_set_name(ir_value*, const char *name);
ir_value* ir_value_vector_member(ir_value*, unsigned int member);
void dump(int (*oprintf)(const char*, ...)) const;
};
MEM_VECTOR_PROTO_ALL(ir_value, struct ir_instr_s*, reads);
MEM_VECTOR_PROTO_ALL(ir_value, struct ir_instr_s*, writes);
bool GMQCC_WARN ir_value_set_float(ir_value*, float f);
bool GMQCC_WARN ir_value_set_func(ir_value*, int f);
#if 0
bool GMQCC_WARN ir_value_set_int(ir_value*, int i);
#endif
bool GMQCC_WARN ir_value_set_string(ir_value*, const char *s);
bool GMQCC_WARN ir_value_set_vector(ir_value*, vector v);
bool GMQCC_WARN ir_value_set_field(ir_value*, ir_value *fld);
/*bool ir_value_set_pointer_v(ir_value*, ir_value* p); */
/*bool ir_value_set_pointer_i(ir_value*, int i); */
MEM_VECTOR_PROTO(ir_value, ir_life_entry_t, life);
/* merge an instruction into the life-range */
/* returns false if the lifepoint was already known */
bool ir_value_life_merge(ir_value*, size_t);
bool ir_value_life_merge_into(ir_value*, const ir_value*);
/* check if a value lives at a specific point */
bool ir_value_lives(ir_value*, size_t);
/* check if the life-range of 2 values overlaps */
bool ir_values_overlap(const ir_value*, const ir_value*);
void ir_value_dump(ir_value*, int (*oprintf)(const char*,...));
void ir_value_dump_life(const ir_value *self, int (*oprintf)(const char*,...));
/* A vector of IR values */
typedef struct {
MEM_VECTOR_MAKE(ir_value*, v);
} ir_value_vector;
MEM_VECTOR_PROTO(ir_value_vector, ir_value*, v);
/* PHI data */
struct ir_phi_entry_t {
ir_value *value;
ir_block *from;
};
typedef struct ir_phi_entry_s
{
ir_value *value;
struct ir_block_s *from;
} ir_phi_entry_t;
/* instruction */
struct ir_instr {
ir_instr(lex_ctx_t, ir_block *owner, int opcode);
~ir_instr();
typedef struct ir_instr_s
{
int opcode;
lex_ctx context;
ir_value* (_ops[3]);
struct ir_block_s* (bops[2]);
int m_opcode;
lex_ctx_t m_context;
ir_value *(_m_ops[3]) = { nullptr, nullptr, nullptr };
ir_block *(m_bops[2]) = { nullptr, nullptr };
std::vector<ir_phi_entry_t> m_phi;
std::vector<ir_value *> m_params;
// For the temp-allocation
size_t m_eid = 0;
// For IFs
bool m_likely = true;
ir_block *m_owner;
};
/* block */
struct ir_block {
ir_block(ir_function *owner, const std::string& name);
~ir_block();
ir_function *m_owner;
std::string m_label;
lex_ctx_t m_context;
bool m_final = false; /* once a jump is added we're done */
std::vector<ir_instr *> m_instr;
std::vector<ir_block *> m_entries;
std::vector<ir_block *> m_exits;
std::vector<ir_value *> m_living;
MEM_VECTOR_MAKE(ir_phi_entry_t, phi);
MEM_VECTOR_MAKE(ir_value*, params);
/* For the temp-allocation */
size_t m_entry_id = 0;
size_t m_eid = 0;
bool m_is_return = false;
size_t eid;
bool m_generated = false;
size_t m_code_start = 0;
};
struct ir_block_s *owner;
} ir_instr;
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, 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);
ir_instr* ir_instr_new(struct ir_block_s *owner, int opcode);
void ir_instr_delete(ir_instr*);
MEM_VECTOR_PROTO(ir_value, ir_phi_entry_t, phi);
bool GMQCC_WARN ir_instr_op(ir_instr*, int op, ir_value *value, bool writing);
MEM_VECTOR_PROTO(ir_value, ir_value*, params);
void ir_instr_dump(ir_instr* in, char *ind, int (*oprintf)(const char*,...));
/* block */
typedef struct ir_block_s
{
char *label;
lex_ctx context;
bool final; /* once a jump is added we're done */
MEM_VECTOR_MAKE(ir_instr*, instr);
MEM_VECTOR_MAKE(struct ir_block_s*, entries);
MEM_VECTOR_MAKE(struct ir_block_s*, exits);
MEM_VECTOR_MAKE(ir_value*, living);
/* For the temp-allocation */
size_t eid;
bool is_return;
size_t run_id;
struct ir_function_s *owner;
bool generated;
size_t code_start;
} ir_block;
ir_block* ir_block_new(struct ir_function_s *owner, const char *label);
void ir_block_delete(ir_block*);
bool ir_block_set_label(ir_block*, const char *label);
MEM_VECTOR_PROTO(ir_block, ir_instr*, instr);
MEM_VECTOR_PROTO_ALL(ir_block, ir_block*, exits);
MEM_VECTOR_PROTO_ALL(ir_block, ir_block*, entries);
ir_value* ir_block_create_binop(ir_block*, const char *label, int op,
ir_value *left, ir_value *right);
ir_value* ir_block_create_unary(ir_block*, const char *label, int op,
ir_value *operand);
bool GMQCC_WARN ir_block_create_store_op(ir_block*, int op, ir_value *target, ir_value *what);
bool GMQCC_WARN ir_block_create_store(ir_block*, ir_value *target, ir_value *what);
bool GMQCC_WARN ir_block_create_storep(ir_block*, ir_value *target, ir_value *what);
/* field must be of TYPE_FIELD */
ir_value* ir_block_create_load_from_ent(ir_block*, const char *label, ir_value *ent, ir_value *field, int outype);
ir_value* ir_block_create_fieldaddress(ir_block*, const char *label, ir_value *entity, ir_value *field);
/* This is to create an instruction of the form
* <outtype>%label := opcode a, b
*/
ir_instr* ir_block_create_phi(ir_block*, lex_ctx_t, const char *label, qc_type vtype);
ir_value* ir_block_create_general_instr(ir_block *self, const char *label,
int op, ir_value *a, ir_value *b, int outype);
ir_value* ir_block_create_add(ir_block*, const char *label, ir_value *l, ir_value *r);
ir_value* ir_block_create_sub(ir_block*, const char *label, ir_value *l, ir_value *r);
ir_value* ir_block_create_mul(ir_block*, const char *label, ir_value *l, ir_value *r);
ir_value* ir_block_create_div(ir_block*, const char *label, ir_value *l, ir_value *r);
ir_instr* ir_block_create_phi(ir_block*, const char *label, int 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);
bool GMQCC_WARN ir_phi_add(ir_instr*, ir_block *b, ir_value *v);
ir_instr* ir_block_create_call(ir_block*, const char *label, ir_value *func);
ir_value* ir_call_value(ir_instr*);
void ir_call_param(ir_instr*, ir_value*);
bool GMQCC_WARN ir_call_param(ir_instr*, ir_value*);
bool GMQCC_WARN ir_block_create_return(ir_block*, lex_ctx_t, ir_value *opt_value);
bool GMQCC_WARN ir_block_create_return(ir_block*, ir_value *opt_value);
bool GMQCC_WARN ir_block_create_if(ir_block*, lex_ctx_t, ir_value *cond,
bool GMQCC_WARN ir_block_create_if(ir_block*, ir_value *cond,
ir_block *ontrue, ir_block *onfalse);
/*
* A 'goto' is an actual 'goto' coded in QC, whereas
/* A 'goto' is an actual 'goto' coded in QC, whereas
* a 'jump' is a virtual construct which simply names the
* next block to go to.
* A goto usually becomes an OP_GOTO in the resulting code,
* whereas a 'jump' usually doesn't add any actual instruction.
*/
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);
bool GMQCC_WARN ir_block_create_jump(ir_block*, ir_block *to);
bool GMQCC_WARN ir_block_create_goto(ir_block*, ir_block *to);
MEM_VECTOR_PROTO_ALL(ir_block, ir_value*, living);
void ir_block_dump(ir_block*, char *ind, int (*oprintf)(const char*,...));
/* function */
struct ir_function {
ir_function(ir_builder *owner, qc_type returntype);
~ir_function();
ir_builder *m_owner;
typedef struct ir_function_s
{
char *name;
int outtype;
MEM_VECTOR_MAKE(int, params);
MEM_VECTOR_MAKE(ir_block*, blocks);
std::string m_name;
qc_type m_outtype;
std::vector<int> m_params;
ir_flag_t m_flags = 0;
int m_builtin = 0;
int builtin;
std::vector<std::unique_ptr<ir_block>> m_blocks;
ir_value *value;
/*
* values generated from operations
/* values generated from operations
* which might get optimized away, so anything
* in there needs to be deleted in the dtor.
*/
std::vector<std::unique_ptr<ir_value>> m_values;
std::vector<std::unique_ptr<ir_value>> m_locals; /* locally defined variables */
ir_value *m_value = nullptr;
MEM_VECTOR_MAKE(ir_value*, values);
size_t m_allocated_locals = 0;
size_t m_globaltemps = 0;
/* locally defined variables */
MEM_VECTOR_MAKE(ir_value*, locals);
ir_block* m_first = nullptr;
ir_block* m_last = nullptr;
size_t allocated_locals;
lex_ctx_t m_context;
ir_block* first;
ir_block* last;
/*
* for prototypes - first we generate all the
lex_ctx context;
/* for prototypes - first we generate all the
* globals, and we remember teh function-defs
* so we can later fill in the entry pos
*
* remember the ID:
*/
qcint_t m_code_function_def = -1;
qcint code_function_def;
/* for temp allocation */
size_t m_run_id = 0;
size_t run_id;
/* vararg support: */
size_t m_max_varargs = 0;
};
struct ir_builder_s *owner;
} ir_function;
ir_function* ir_function_new(struct ir_builder_s *owner, int returntype);
void ir_function_delete(ir_function*);
bool GMQCC_WARN ir_function_collect_value(ir_function*, ir_value *value);
bool ir_function_set_name(ir_function*, const char *name);
MEM_VECTOR_PROTO(ir_function, int, params);
MEM_VECTOR_PROTO(ir_function, ir_block*, blocks);
ir_value* ir_function_get_local(ir_function *self, const char *name);
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);
/*
bool ir_function_naive_phi(ir_function*);
bool ir_function_enumerate(ir_function*);
bool ir_function_calculate_liferanges(ir_function*);
*/
ir_block* ir_function_create_block(ir_function*, const char *label);
void ir_function_dump(ir_function*, char *ind, int (*oprintf)(const char*,...));
/* builder */
#define IR_HT_SIZE 1024
#define IR_MAX_VINSTR_TEMPS 2
typedef struct ir_builder_s
{
char *name;
MEM_VECTOR_MAKE(ir_function*, functions);
MEM_VECTOR_MAKE(ir_value*, globals);
MEM_VECTOR_MAKE(ir_value*, fields);
struct ir_builder {
ir_builder(const std::string& modulename);
~ir_builder();
MEM_VECTOR_MAKE(const char*, filenames);
MEM_VECTOR_MAKE(qcint, filestrings);
/* we cache the #IMMEDIATE string here */
qcint str_immediate;
} 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_builder* ir_builder_new(const char *modulename);
void ir_builder_delete(ir_builder*);
ir_value *generateExtparamProto();
void generateExtparam();
bool ir_builder_set_name(ir_builder *self, const char *name);
ir_value *literalFloat(float value, bool add_to_list);
MEM_VECTOR_PROTO(ir_builder, ir_function*, functions);
MEM_VECTOR_PROTO(ir_builder, ir_value*, globals);
MEM_VECTOR_PROTO(ir_builder, ir_value*, fields);
MEM_VECTOR_PROTO(ir_builder, const char*, filenames);
MEM_VECTOR_PROTO(ir_builder, qcint, filestrings);
std::string m_name;
std::vector<std::unique_ptr<ir_function>> m_functions;
std::vector<std::unique_ptr<ir_value>> m_globals;
std::vector<std::unique_ptr<ir_value>> m_fields;
// for reusing them in vector-splits, TODO: sort this or use a radix-tree
std::vector<ir_value*> m_const_floats;
ir_function* ir_builder_get_function(ir_builder*, const char *fun);
ir_function* ir_builder_create_function(ir_builder*, const char *name, int outtype);
ht m_htfunctions;
ht m_htglobals;
ht m_htfields;
ir_value* ir_builder_get_global(ir_builder*, const char *fun);
ir_value* ir_builder_create_global(ir_builder*, const char *name, int vtype);
ir_value* ir_builder_get_field(ir_builder*, const char *fun);
ir_value* ir_builder_create_field(ir_builder*, const char *name, int vtype);
// extparams' ir_values reference the ones from extparam_protos
std::vector<std::unique_ptr<ir_value>> m_extparam_protos;
std::vector<ir_value*> m_extparams;
bool ir_builder_generate(ir_builder *self, const char *filename);
// 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;
void ir_builder_dump(ir_builder*, int (*oprintf)(const char*, ...));
std::vector<const char*> m_filenames;
std::vector<qcint_t> 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 *m_vinstr_temp[IR_MAX_VINSTR_TEMPS];
/* code generator */
std::unique_ptr<code_t> 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*);
};
/*
* This code assumes 32 bit floats while generating binary
* Blub: don't use extern here, it's annoying and shows up in nm
* for some reason :P
*/
typedef int static_assert_is_32bit_float [(sizeof(int32_t) == 4) ? 1 : -1];
typedef int static_assert_is_32bit_integer[(sizeof(qcfloat_t) == 4) ? 1 : -1];
/*
* If the condition creates a situation where this becomes -1 size it means there are
* more IR_FLAGs than the type ir_flag_t is capable of holding. So either eliminate
* the IR flag count or change the ir_flag_t typedef to a type large enough to accomodate
* all the flags.
*/
typedef int static_assert_is_ir_flag_safe [((IR_FLAG_LAST) <= (ir_flag_t)(-1)) ? 1 : -1];
/* This code assumes 32 bit floats while generating binary */
extern int check_int_and_float_size
[ (sizeof(int32_t) == sizeof(qcfloat)) ? 1 : -1 ];
#endif

1070
lexer.c Normal file

File diff suppressed because it is too large Load diff

1439
lexer.cpp

File diff suppressed because it is too large Load diff

356
lexer.h
View file

@ -1,19 +1,38 @@
#ifndef GMQCC_LEXER_HDR
#define GMQCC_LEXER_HDR
#include "gmqcc.h"
#ifndef GMQCC_LEXER_HDR_
#define GMQCC_LEXER_HDR_
struct token {
int ttype;
char *value;
union {
vec3_t v;
int i;
qcfloat_t f;
qc_type t; /* type */
} constval;
lex_ctx_t ctx;
typedef struct token_s token;
#include "ast.h"
struct token_s {
int ttype;
MEM_VECTOR_MAKE(char, value);
union {
vector v;
int i;
double f;
int t; /* type */
} constval;
#if 0
struct token_s *next;
struct token_s *prev;
#endif
lex_ctx 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
*
*/
@ -32,27 +51,13 @@ enum {
TOKEN_DOTS, /* 3 dots, ... */
TOKEN_ATTRIBUTE_OPEN, /* [[ */
TOKEN_ATTRIBUTE_CLOSE, /* ]] */
TOKEN_VA_ARGS, /* for the ftepp only */
TOKEN_VA_ARGS_ARRAY, /* for the ftepp only */
TOKEN_VA_COUNT, /* to get the count of vaargs */
TOKEN_STRINGCONST, /* not the typename but an actual "string" */
TOKEN_CHARCONST,
TOKEN_VECTORCONST,
TOKEN_INTCONST,
TOKEN_FLOATCONST,
TOKEN_WHITE,
TOKEN_EOL,
/* if we add additional tokens before this, the exposed API
* should not be broken anyway, but EOF/ERROR/... should
* still be at the bottom
*/
TOKEN_EOF = 1024,
TOKEN_EOF,
/* We use '< TOKEN_ERROR', so TOKEN_FATAL must come after it and any
* other error related tokens as well
@ -61,45 +66,59 @@ enum {
TOKEN_FATAL /* internal error, eg out of memory */
};
struct frame_macro {
char *name;
int value;
static const char *_tokennames[] = {
"TOKEN_START",
"TOKEN_IDENT",
"TOKEN_TYPENAME",
"TOKEN_OPERATOR",
"TOKEN_KEYWORD",
"TOKEN_DOTS",
"TOKEN_STRINGCONST",
"TOKEN_CHARCONST",
"TOKEN_VECTORCONST",
"TOKEN_INTCONST",
"TOKEN_FLOATCONST",
"TOKEN_EOF",
"TOKEN_ERROR",
"TOKEN_FATAL",
};
typedef int
_all_tokennames_added_[
((TOKEN_FATAL - TOKEN_START + 1) ==
(sizeof(_tokennames)/sizeof(_tokennames[0])))
? 1 : -1];
struct lex_file {
FILE *file;
const char *open_string;
size_t open_string_length;
size_t open_string_pos;
typedef struct {
char *name;
int value;
} frame_macro;
char *name;
size_t line;
size_t sline; /* line at the start of a token */
size_t column;
typedef struct {
FILE *file;
char *name;
size_t line;
size_t sline; /* line at the start of a token */
int peek[256];
size_t peekpos;
char peek[256];
size_t peekpos;
bool eof;
bool eof;
token tok; /* not a pointer anymore */
token tok; /* not a pointer anymore */
struct {
unsigned noops:1;
unsigned nodigraphs:1; /* used when lexing string constants */
unsigned preprocessing:1; /* whitespace and EOLs become actual tokens */
unsigned mergelines:1; /* backslash at the end of a line escapes the newline */
} flags; /* sizeof == 1 */
struct {
bool noops;
bool nodigraphs; /* used when lexing string constants */
} flags;
int framevalue;
frame_macro *frames;
char *modelname;
MEM_VECTOR_MAKE(frame_macro, frames);
char *modelname;
} lex_file;
size_t push_line;
};
MEM_VECTOR_PROTO(lex_file, char, token);
lex_file* lex_open (const char *file);
lex_file* lex_open_string(const char *str, size_t len, const char *name);
void lex_close(lex_file *lex);
int lex_do (lex_file *lex);
void lex_cleanup(void);
@ -116,188 +135,125 @@ enum {
#define OP_SUFFIX 1
#define OP_PREFIX 2
struct oper_info {
typedef struct {
const char *op;
unsigned int operands;
unsigned int id;
unsigned int assoc;
signed int prec;
unsigned int prec;
unsigned int flags;
bool folds;
};
} oper_info;
/*
* Explicit uint8_t casts since the left operand of shift operator cannot
* be negative, even though it won't happen, this supresses the future
* possibility.
*/
#define opid1(a) ((uint8_t)a)
#define opid2(a,b) (((uint8_t)a<<8) |(uint8_t)b)
#define opid3(a,b,c) (((uint8_t)a<<16)|((uint8_t)b<<8)|(uint8_t)c)
#define opid1(a) (a)
#define opid2(a,b) ((a<<8)|b)
#define opid3(a,b,c) ((a<<16)|(b<<8)|c)
static const oper_info c_operators[] = {
{ "(", 0, opid1('('), ASSOC_LEFT, 99, OP_PREFIX, false}, /* paren expression - non function call */
{ "_length", 1, opid3('l','e','n'), ASSOC_RIGHT, 98, OP_PREFIX, true},
{ "(", 0, opid1('('), ASSOC_LEFT, 99, OP_PREFIX}, /* paren expression - non function call */
{ "++", 1, opid3('S','+','+'), ASSOC_LEFT, 17, OP_SUFFIX, false},
{ "--", 1, opid3('S','-','-'), ASSOC_LEFT, 17, OP_SUFFIX, false},
{ ".", 2, opid1('.'), ASSOC_LEFT, 17, 0, false},
{ "(", 0, opid1('('), ASSOC_LEFT, 17, 0, false}, /* function call */
{ "[", 2, opid1('['), ASSOC_LEFT, 17, 0, false}, /* array subscript */
{ "++", 1, opid3('S','+','+'), ASSOC_LEFT, 16, OP_SUFFIX},
{ "--", 1, opid3('S','-','-'), ASSOC_LEFT, 16, OP_SUFFIX},
{ "++", 1, opid3('+','+','P'), ASSOC_RIGHT, 16, OP_PREFIX, false},
{ "--", 1, opid3('-','-','P'), ASSOC_RIGHT, 16, OP_PREFIX, false},
{ ".", 2, opid1('.'), ASSOC_LEFT, 15, 0 },
{ "(", 0, opid1('('), ASSOC_LEFT, 15, 0 }, /* function call */
{ "**", 2, opid2('*','*'), ASSOC_RIGHT, 14, 0, true},
{ "!", 1, opid2('!','P'), ASSOC_RIGHT, 14, OP_PREFIX, true},
{ "~", 1, opid2('~','P'), ASSOC_RIGHT, 14, OP_PREFIX, true},
{ "+", 1, opid2('+','P'), ASSOC_RIGHT, 14, OP_PREFIX, false},
{ "-", 1, opid2('-','P'), ASSOC_RIGHT, 14, OP_PREFIX, true},
/* { "&", 1, opid2('&','P'), ASSOC_RIGHT, 14, OP_PREFIX, false}, */
{ "!", 1, opid2('!', 'P'), ASSOC_RIGHT, 14, OP_PREFIX },
{ "~", 1, opid2('~', 'P'), ASSOC_RIGHT, 14, OP_PREFIX },
{ "+", 1, opid2('+','P'), ASSOC_RIGHT, 14, OP_PREFIX },
{ "-", 1, opid2('-','P'), ASSOC_RIGHT, 14, OP_PREFIX },
{ "++", 1, opid3('+','+','P'), ASSOC_RIGHT, 14, OP_PREFIX },
{ "--", 1, opid3('-','-','P'), ASSOC_RIGHT, 14, OP_PREFIX },
/* { "&", 1, opid2('&','P'), ASSOC_RIGHT, 14, OP_PREFIX }, */
{ "*", 2, opid1('*'), ASSOC_LEFT, 13, 0, true},
{ "/", 2, opid1('/'), ASSOC_LEFT, 13, 0, true},
{ "%", 2, opid1('%'), ASSOC_LEFT, 13, 0, true},
{ "><", 2, opid2('>','<'), ASSOC_LEFT, 13, 0, true},
{ "*", 2, opid1('*'), ASSOC_LEFT, 13, 0 },
{ "/", 2, opid1('/'), ASSOC_LEFT, 13, 0 },
{ "%", 2, opid1('%'), ASSOC_LEFT, 13, 0 },
{ "+", 2, opid1('+'), ASSOC_LEFT, 12, 0, true},
{ "-", 2, opid1('-'), ASSOC_LEFT, 12, 0, true},
{ "+", 2, opid1('+'), ASSOC_LEFT, 12, 0 },
{ "-", 2, opid1('-'), ASSOC_LEFT, 12, 0 },
{ "<<", 2, opid2('<','<'), ASSOC_LEFT, 11, 0, true},
{ ">>", 2, opid2('>','>'), ASSOC_LEFT, 11, 0, true},
{ "<<", 2, opid2('<','<'), ASSOC_LEFT, 11, 0 },
{ ">>", 2, opid2('>','>'), ASSOC_LEFT, 11, 0 },
{ "<", 2, opid1('<'), ASSOC_LEFT, 10, 0, false},
{ ">", 2, opid1('>'), ASSOC_LEFT, 10, 0, false},
{ "<=>", 2, opid3('<','=','>'), ASSOC_LEFT, 10, 0, true},
{ "<=", 2, opid2('<','='), ASSOC_LEFT, 10, 0, false},
{ ">=", 2, opid2('>','='), ASSOC_LEFT, 10, 0, false},
{ "<", 2, opid1('<'), ASSOC_LEFT, 10, 0 },
{ ">", 2, opid1('>'), ASSOC_LEFT, 10, 0 },
{ "<=", 2, opid2('<','='), ASSOC_LEFT, 10, 0 },
{ ">=", 2, opid2('>','='), ASSOC_LEFT, 10, 0 },
{ "==", 2, opid2('=','='), ASSOC_LEFT, 9, 0, true},
{ "!=", 2, opid2('!','='), ASSOC_LEFT, 9, 0, true},
{ "==", 2, opid2('=','='), ASSOC_LEFT, 9, 0 },
{ "!=", 2, opid2('!','='), ASSOC_LEFT, 9, 0 },
{ "&", 2, opid1('&'), ASSOC_LEFT, 8, 0, true},
{ "&", 2, opid1('&'), ASSOC_LEFT, 8, 0 },
{ "^", 2, opid1('^'), ASSOC_LEFT, 7, 0, true},
{ "^", 2, opid1('^'), ASSOC_LEFT, 7, 0 },
{ "|", 2, opid1('|'), ASSOC_LEFT, 6, 0, true},
{ "|", 2, opid1('|'), ASSOC_LEFT, 6, 0 },
{ "&&", 2, opid2('&','&'), ASSOC_LEFT, 5, 0, true},
{ "&&", 2, opid2('&','&'), ASSOC_LEFT, 5, 0 },
{ "||", 2, opid2('|','|'), ASSOC_LEFT, 4, 0, true},
{ "||", 2, opid2('|','|'), ASSOC_LEFT, 4, 0 },
{ "?", 3, opid2('?',':'), ASSOC_RIGHT, 3, 0, true},
{ "?", 3, opid2('?',':'), ASSOC_RIGHT, 3, 0 },
{ "=", 2, opid1('='), ASSOC_RIGHT, 2, 0, false},
{ "+=", 2, opid2('+','='), ASSOC_RIGHT, 2, 0, false},
{ "-=", 2, opid2('-','='), ASSOC_RIGHT, 2, 0, false},
{ "*=", 2, opid2('*','='), ASSOC_RIGHT, 2, 0, false},
{ "/=", 2, opid2('/','='), ASSOC_RIGHT, 2, 0, false},
{ "%=", 2, opid2('%','='), ASSOC_RIGHT, 2, 0, false},
{ ">>=", 2, opid3('>','>','='), ASSOC_RIGHT, 2, 0, false},
{ "<<=", 2, opid3('<','<','='), ASSOC_RIGHT, 2, 0, false},
{ "&=", 2, opid2('&','='), ASSOC_RIGHT, 2, 0, false},
{ "^=", 2, opid2('^','='), ASSOC_RIGHT, 2, 0, false},
{ "|=", 2, opid2('|','='), ASSOC_RIGHT, 2, 0, false},
{ "=", 2, opid1('='), ASSOC_RIGHT, 2, 0 },
{ "+=", 2, opid2('+','='), ASSOC_RIGHT, 2, 0 },
{ "-=", 2, opid2('-','='), ASSOC_RIGHT, 2, 0 },
{ "*=", 2, opid2('*','='), ASSOC_RIGHT, 2, 0 },
{ "/=", 2, opid2('/','='), ASSOC_RIGHT, 2, 0 },
{ "%=", 2, opid2('%','='), ASSOC_RIGHT, 2, 0 },
{ ">>=", 2, opid3('>','>','='), ASSOC_RIGHT, 2, 0 },
{ "<<=", 2, opid3('<','<','='), ASSOC_RIGHT, 2, 0 },
{ "&=", 2, opid2('&','='), ASSOC_RIGHT, 2, 0 },
{ "^=", 2, opid2('^','='), ASSOC_RIGHT, 2, 0 },
{ "|=", 2, opid2('|','='), ASSOC_RIGHT, 2, 0 },
{ ":", 0, opid2(':','?'), ASSOC_RIGHT, 1, 0, false},
{ ",", 2, opid1(','), ASSOC_LEFT, 0, 0, false}
};
static const oper_info fte_operators[] = {
{ "(", 0, opid1('('), ASSOC_LEFT, 99, OP_PREFIX, false}, /* paren expression - non function call */
{ "++", 1, opid3('S','+','+'), ASSOC_LEFT, 15, OP_SUFFIX, false},
{ "--", 1, opid3('S','-','-'), ASSOC_LEFT, 15, OP_SUFFIX, false},
{ ".", 2, opid1('.'), ASSOC_LEFT, 15, 0, false},
{ "(", 0, opid1('('), ASSOC_LEFT, 15, 0, false}, /* function call */
{ "[", 2, opid1('['), ASSOC_LEFT, 15, 0, false}, /* array subscript */
{ "!", 1, opid2('!','P'), ASSOC_RIGHT, 14, OP_PREFIX, true},
{ "+", 1, opid2('+','P'), ASSOC_RIGHT, 14, OP_PREFIX, false},
{ "-", 1, opid2('-','P'), ASSOC_RIGHT, 14, OP_PREFIX, true},
{ "++", 1, opid3('+','+','P'), ASSOC_RIGHT, 14, OP_PREFIX, false},
{ "--", 1, opid3('-','-','P'), ASSOC_RIGHT, 14, OP_PREFIX, false},
{ "*", 2, opid1('*'), ASSOC_LEFT, 13, 0, true},
{ "/", 2, opid1('/'), ASSOC_LEFT, 13, 0, true},
{ "&", 2, opid1('&'), ASSOC_LEFT, 13, 0, true},
{ "|", 2, opid1('|'), ASSOC_LEFT, 13, 0, true},
{ "+", 2, opid1('+'), ASSOC_LEFT, 12, 0, true},
{ "-", 2, opid1('-'), ASSOC_LEFT, 12, 0, true},
{ "<<", 2, opid2('<','<'), ASSOC_LEFT, 11, 0, true},
{ ">>", 2, opid2('>','>'), ASSOC_LEFT, 11, 0, true},
{ "<", 2, opid1('<'), ASSOC_LEFT, 10, 0, false},
{ ">", 2, opid1('>'), ASSOC_LEFT, 10, 0, false},
{ "<=", 2, opid2('<','='), ASSOC_LEFT, 10, 0, false},
{ ">=", 2, opid2('>','='), ASSOC_LEFT, 10, 0, false},
{ "==", 2, opid2('=','='), ASSOC_LEFT, 10, 0, true},
{ "!=", 2, opid2('!','='), ASSOC_LEFT, 10, 0, true},
{ "?", 3, opid2('?',':'), ASSOC_RIGHT, 9, 0, true},
{ "=", 2, opid1('='), ASSOC_RIGHT, 8, 0, false},
{ "+=", 2, opid2('+','='), ASSOC_RIGHT, 8, 0, false},
{ "-=", 2, opid2('-','='), ASSOC_RIGHT, 8, 0, false},
{ "*=", 2, opid2('*','='), ASSOC_RIGHT, 8, 0, false},
{ "/=", 2, opid2('/','='), ASSOC_RIGHT, 8, 0, false},
{ "%=", 2, opid2('%','='), ASSOC_RIGHT, 8, 0, false},
{ "&=", 2, opid2('&','='), ASSOC_RIGHT, 8, 0, false},
{ "|=", 2, opid2('|','='), ASSOC_RIGHT, 8, 0, false},
{ "&~=", 2, opid3('&','~','='), ASSOC_RIGHT, 8, 0, false},
{ "&&", 2, opid2('&','&'), ASSOC_LEFT, 5, 0, true},
{ "||", 2, opid2('|','|'), ASSOC_LEFT, 5, 0, true},
/* Leave precedence 3 for : with -fcorrect-ternary */
{ ",", 2, opid1(','), ASSOC_LEFT, 2, 0, false},
{ ":", 0, opid2(':','?'), ASSOC_RIGHT, 1, 0, false}
{ ",", 2, opid1(','), ASSOC_LEFT, 1, 0 }
};
static const size_t c_operator_count = (sizeof(c_operators) / sizeof(c_operators[0]));
static const oper_info qcc_operators[] = {
{ "(", 0, opid1('('), ASSOC_LEFT, 99, OP_PREFIX, false}, /* paren expression - non function call */
{ "(", 0, opid1('('), ASSOC_LEFT, 99, OP_PREFIX}, /* paren expression - non function call */
{ ".", 2, opid1('.'), ASSOC_LEFT, 15, 0, false},
{ "(", 0, opid1('('), ASSOC_LEFT, 15, 0, false}, /* function call */
{ "[", 2, opid1('['), ASSOC_LEFT, 15, 0, false}, /* array subscript */
{ ".", 2, opid1('.'), ASSOC_LEFT, 15, 0 },
{ "(", 0, opid1('('), ASSOC_LEFT, 15, 0 }, /* function call */
{ "!", 1, opid2('!','P'), ASSOC_RIGHT, 14, OP_PREFIX, true},
{ "+", 1, opid2('+','P'), ASSOC_RIGHT, 14, OP_PREFIX, false},
{ "-", 1, opid2('-','P'), ASSOC_RIGHT, 14, OP_PREFIX, true},
{ "!", 1, opid2('!', 'P'), ASSOC_RIGHT, 14, OP_PREFIX },
{ "+", 1, opid2('+','P'), ASSOC_RIGHT, 14, OP_PREFIX },
{ "-", 1, opid2('-','P'), ASSOC_RIGHT, 14, OP_PREFIX },
{ "*", 2, opid1('*'), ASSOC_LEFT, 13, 0, true},
{ "/", 2, opid1('/'), ASSOC_LEFT, 13, 0, true},
{ "&", 2, opid1('&'), ASSOC_LEFT, 13, 0, true},
{ "|", 2, opid1('|'), ASSOC_LEFT, 13, 0, true},
{ "*", 2, opid1('*'), ASSOC_LEFT, 13, 0 },
{ "/", 2, opid1('/'), ASSOC_LEFT, 13, 0 },
{ "&", 2, opid1('&'), ASSOC_LEFT, 13, 0 },
{ "|", 2, opid1('|'), ASSOC_LEFT, 13, 0 },
{ "+", 2, opid1('+'), ASSOC_LEFT, 12, 0, true},
{ "-", 2, opid1('-'), ASSOC_LEFT, 12, 0, true},
{ "+", 2, opid1('+'), ASSOC_LEFT, 12, 0 },
{ "-", 2, opid1('-'), ASSOC_LEFT, 12, 0 },
{ "<", 2, opid1('<'), ASSOC_LEFT, 10, 0, false},
{ ">", 2, opid1('>'), ASSOC_LEFT, 10, 0, false},
{ "<=", 2, opid2('<','='), ASSOC_LEFT, 10, 0, false},
{ ">=", 2, opid2('>','='), ASSOC_LEFT, 10, 0, false},
{ "==", 2, opid2('=','='), ASSOC_LEFT, 10, 0, true},
{ "!=", 2, opid2('!','='), ASSOC_LEFT, 10, 0, true},
{ "<", 2, opid1('<'), ASSOC_LEFT, 10, 0 },
{ ">", 2, opid1('>'), ASSOC_LEFT, 10, 0 },
{ "<=", 2, opid2('<','='), ASSOC_LEFT, 10, 0 },
{ ">=", 2, opid2('>','='), ASSOC_LEFT, 10, 0 },
{ "==", 2, opid2('=','='), ASSOC_LEFT, 10, 0 },
{ "!=", 2, opid2('!','='), ASSOC_LEFT, 10, 0 },
{ "=", 2, opid1('='), ASSOC_RIGHT, 8, 0, false},
{ "+=", 2, opid2('+','='), ASSOC_RIGHT, 8, 0, false},
{ "-=", 2, opid2('-','='), ASSOC_RIGHT, 8, 0, false},
{ "*=", 2, opid2('*','='), ASSOC_RIGHT, 8, 0, false},
{ "/=", 2, opid2('/','='), ASSOC_RIGHT, 8, 0, false},
{ "%=", 2, opid2('%','='), ASSOC_RIGHT, 8, 0, false},
{ "&=", 2, opid2('&','='), ASSOC_RIGHT, 8, 0, false},
{ "|=", 2, opid2('|','='), ASSOC_RIGHT, 8, 0, false},
{ "=", 2, opid1('='), ASSOC_RIGHT, 8, 0 },
{ "+=", 2, opid2('+','='), ASSOC_RIGHT, 8, 0 },
{ "-=", 2, opid2('-','='), ASSOC_RIGHT, 8, 0 },
{ "*=", 2, opid2('*','='), ASSOC_RIGHT, 8, 0 },
{ "/=", 2, opid2('/','='), ASSOC_RIGHT, 8, 0 },
{ "%=", 2, opid2('%','='), ASSOC_RIGHT, 8, 0 },
{ "&=", 2, opid2('&','='), ASSOC_RIGHT, 8, 0 },
{ "|=", 2, opid2('|','='), ASSOC_RIGHT, 8, 0 },
{ "&&", 2, opid2('&','&'), ASSOC_LEFT, 5, 0, true},
{ "||", 2, opid2('|','|'), ASSOC_LEFT, 5, 0, true},
{ "&&", 2, opid2('&','&'), ASSOC_LEFT, 5, 0 },
{ "||", 2, opid2('|','|'), ASSOC_LEFT, 5, 0 },
{ ",", 2, opid1(','), ASSOC_LEFT, 2, 0, false},
{ ",", 2, opid1(','), ASSOC_LEFT, 1, 0 }
};
static const size_t qcc_operator_count = (sizeof(qcc_operators) / sizeof(qcc_operators[0]));
extern const oper_info *operators;
extern size_t operator_count;
void lexerror(lex_file*, const char *fmt, ...);
#endif

524
main.c Normal file
View file

@ -0,0 +1,524 @@
/*
* Copyright (C) 2012
* 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 "lexer.h"
uint32_t opts_flags[1 + (COUNT_FLAGS / 32)];
uint32_t opts_warn [1 + (COUNT_WARNINGS / 32)];
uint32_t opts_O = 1;
const char *opts_output = "progs.dat";
int opts_standard = COMPILER_GMQCC;
bool opts_debug = false;
bool opts_memchk = false;
bool opts_dump = false;
bool opts_werror = false;
bool opts_forcecrc = false;
uint16_t opts_forced_crc;
static bool opts_output_wasset = false;
/* set by the standard */
const oper_info *operators = NULL;
size_t operator_count = 0;
typedef struct { char *filename; int type; } argitem;
VECTOR_MAKE(argitem, items);
#define TYPE_QC 0
#define TYPE_ASM 1
#define TYPE_SRC 2
static const char *app_name;
static int usage() {
printf("usage: %s [options] [files...]", app_name);
printf("options:\n"
" -h, --help show this help message\n"
" -debug turns on compiler debug messages\n"
" -memchk turns on compiler memory leak check\n");
printf(" -o, --output=file output file, defaults to progs.dat\n"
" -a filename add an asm file to be assembled\n"
" -s filename add a progs.src file to be used\n");
printf(" -f<flag> enable a flag\n"
" -fno-<flag> disable a flag\n"
" -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");
printf(" -W<warning> enable a warning\n"
" -Wno-<warning> disable a warning\n"
" -Wall enable all warnings\n"
" -Werror treat warnings as errors\n");
printf(" -force-crc=num force a specific checksum into the header\n");
printf("\n");
printf("flags:\n"
" -fadjust-vector-fields\n"
" when assigning a vector field, its _y and _z fields also get assigned\n"
);
return -1;
}
static bool options_setflag_all(const char *name, bool on, uint32_t *flags, const opts_flag_def *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 0
if (on)
flags[lb.idx] |= (1<<(lb.bit));
else
flags[lb.idx] &= ~(1<<(lb.bit));
#else
if (on)
flags[0] |= (1<<lb);
else
flags[0] &= ~(1<<(lb));
#endif
return true;
}
}
return false;
}
static bool options_setflag(const char *name, bool on) {
return options_setflag_all(name, on, opts_flags, opts_flag_list, COUNT_FLAGS);
}
static bool options_setwarn(const char *name, bool on) {
return options_setflag_all(name, on, opts_warn, opts_warn_list, COUNT_WARNINGS);
}
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];
while (!argend && argc > 1) {
char *argarg;
argitem item;
++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_standard = COMPILER_GMQCC;
else if (!strcmp(argarg, "qcc"))
opts_standard = COMPILER_QCC;
else if (!strcmp(argarg, "fte") || !strcmp(argarg, "fteqcc"))
opts_standard = COMPILER_FTEQCC;
else if (!strcmp(argarg, "qccx"))
opts_standard = COMPILER_QCCX;
else {
printf("Unknown standard: %s\n", argarg);
return false;
}
continue;
}
if (options_long_gcc("force-crc", &argc, &argv, &argarg)) {
opts_forcecrc = true;
opts_forced_crc = strtol(argarg, NULL, 0);
continue;
}
if (!strcmp(argv[0]+1, "debug")) {
opts_debug = true;
continue;
}
if (!strcmp(argv[0]+1, "dump")) {
opts_dump = true;
continue;
}
if (!strcmp(argv[0]+1, "memchk")) {
opts_memchk = true;
continue;
}
switch (argv[0][1]) {
/* -h, show usage but exit with 0 */
case 'h':
usage();
exit(0);
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")) {
printf("Possible flags:\n");
for (itr = 0; itr < COUNT_FLAGS; ++itr) {
util_strtononcmd(opts_flag_list[itr].name, buffer, sizeof(buffer));
printf(" -f%s\n", buffer);
}
exit(0);
}
else if (!strncmp(argv[0]+2, "NO_", 3)) {
if (!options_setflag(argv[0]+5, false)) {
printf("unknown flag: %s\n", argv[0]+2);
return false;
}
}
else if (!options_setflag(argv[0]+2, true)) {
printf("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")) {
printf("Possible warnings:\n");
for (itr = 0; itr < COUNT_WARNINGS; ++itr) {
util_strtononcmd(opts_warn_list[itr].name, buffer, sizeof(buffer));
printf(" -W%s\n", buffer);
}
exit(0);
}
else if (!strcmp(argv[0]+2, "NO_ERROR")) {
opts_werror = false;
break;
}
else if (!strcmp(argv[0]+2, "ERROR")) {
opts_werror = true;
break;
}
else if (!strcmp(argv[0]+2, "NONE")) {
for (itr = 0; itr < sizeof(opts_warn)/sizeof(opts_warn[0]); ++itr)
opts_warn[itr] = 0;
break;
}
else if (!strcmp(argv[0]+2, "ALL")) {
for (itr = 0; itr < sizeof(opts_warn)/sizeof(opts_warn[0]); ++itr)
opts_warn[itr] = 0xFFFFFFFFL;
break;
}
if (!strncmp(argv[0]+2, "NO_", 3)) {
if (!options_setwarn(argv[0]+5, false)) {
printf("unknown warning: %s\n", argv[0]+2);
return false;
}
}
else if (!options_setwarn(argv[0]+2, true)) {
printf("unknown warning: %s\n", argv[0]+2);
return false;
}
break;
case 'O':
if (!options_witharg(&argc, &argv, &argarg)) {
printf("option -O requires a numerical argument\n");
return false;
}
opts_O = atoi(argarg);
break;
case 'o':
if (!options_witharg(&argc, &argv, &argarg)) {
printf("option -o requires an argument: the output file name\n");
return false;
}
opts_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)) {
printf("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;
items_add(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 {
/* All long options with arguments */
if (options_long_witharg("output", &argc, &argv, &argarg)) {
opts_output = argarg;
opts_output_wasset = true;
} else {
printf("Unknown parameter: %s\n", argv[0]);
return false;
}
}
break;
default:
printf("Unknown parameter: %s\n", argv[0]);
return false;
}
}
else
{
/* it's a QC filename */
argitem item;
item.filename = argv[0];
item.type = TYPE_QC;
items_add(item);
}
}
return true;
}
static void options_set(uint32_t *flags, size_t idx, bool on)
{
longbit lb = LONGBIT(idx);
#if 0
if (on)
flags[lb.idx] |= (1<<(lb.bit));
else
flags[lb.idx] &= ~(1<<(lb.bit));
#else
if (on)
flags[0] |= (1<<(lb));
else
flags[0] &= ~(1<<(lb));
#endif
}
/* 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; isspace(*start); ++start) {}
/* end at the first non-blank */
for (end = start; *end && !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 opts_output_free = false;
app_name = argv[0];
/* default options / warn flags */
options_set(opts_warn, WARN_UNKNOWN_CONTROL_SEQUENCE, true);
options_set(opts_warn, WARN_EXTENSIONS, true);
options_set(opts_warn, WARN_FIELD_REDECLARED, true);
options_set(opts_warn, WARN_TOO_FEW_PARAMETERS, true);
options_set(opts_warn, WARN_MISSING_RETURN_VALUES, true);
options_set(opts_warn, WARN_USED_UNINITIALIZED, true);
options_set(opts_warn, WARN_LOCAL_CONSTANTS, true);
options_set(opts_warn, WARN_VOID_VARIABLES, true);
options_set(opts_warn, WARN_IMPLICIT_FUNCTION_POINTER, true);
options_set(opts_warn, WARN_VARIADIC_FUNCTION, true);
options_set(opts_warn, WARN_FRAME_MACROS, true);
options_set(opts_warn, WARN_UNUSED_VARIABLE, true);
options_set(opts_warn, WARN_EFFECTLESS_STATEMENT, true);
options_set(opts_warn, WARN_END_SYS_FIELDS, true);
options_set(opts_warn, WARN_ASSIGN_FUNCTION_TYPES, true);
if (!options_parse(argc, argv)) {
return usage();
}
/* the standard decides which set of operators to use */
if (opts_standard == COMPILER_GMQCC) {
operators = c_operators;
operator_count = c_operator_count;
} else {
operators = qcc_operators;
operator_count = qcc_operator_count;
}
if (opts_dump) {
for (itr = 0; itr < COUNT_FLAGS; ++itr) {
printf("Flag %s = %i\n", opts_flag_list[itr].name, OPTS_FLAG(itr));
}
for (itr = 0; itr < COUNT_WARNINGS; ++itr) {
printf("Warning %s = %i\n", opts_warn_list[itr].name, OPTS_WARN(itr));
}
printf("output = %s\n", opts_output);
printf("optimization level = %i\n", (int)opts_O);
printf("standard = %i\n", opts_standard);
}
if (!parser_init()) {
printf("failed to initialize parser\n");
retval = 1;
goto cleanup;
}
util_debug("COM", "starting ...\n");
if (items_elements) {
printf("Mode: manual\n");
printf("There are %lu items to compile:\n", (unsigned long)items_elements);
for (itr = 0; itr < items_elements; ++itr) {
printf(" item: %s (%s)\n",
items_data[itr].filename,
( (items_data[itr].type == TYPE_QC ? "qc" :
(items_data[itr].type == TYPE_ASM ? "asm" :
(items_data[itr].type == TYPE_SRC ? "progs.src" :
("unknown"))))));
if (!parser_compile(items_data[itr].filename)) {
retval = 1;
goto cleanup;
}
}
if (!parser_finish(opts_output)) {
retval = 1;
goto cleanup;
}
} else {
FILE *src;
char *line;
size_t linelen = 0;
printf("Mode: progs.src\n");
src = util_fopen("progs.src", "rb");
if (!src) {
printf("failed to open `progs.src` for reading\n");
retval = 1;
goto cleanup;
}
line = NULL;
if (!progs_nextline(&line, &linelen, src) || !line[0]) {
printf("illformatted progs.src file: expected output filename in first line\n");
retval = 1;
goto srcdone;
}
if (!opts_output_wasset) {
opts_output = util_strdup(line);
opts_output_free = true;
}
while (progs_nextline(&line, &linelen, src)) {
if (!line[0] || (line[0] == '/' && line[1] == '/'))
continue;
printf(" src: %s\n", line);
if (!parser_compile(line)) {
retval = 1;
goto srcdone;
}
}
parser_finish(opts_output);
srcdone:
fclose(src);
mem_d(line);
}
/* stuff */
cleanup:
util_debug("COM", "cleaning ...\n");
mem_d(items_data);
parser_cleanup();
if (opts_output_free)
mem_d((char*)opts_output);
lex_cleanup();
util_meminfo();
return retval;
}

772
main.cpp
View file

@ -1,772 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include "gmqcc.h"
#include "lexer.h"
#include "parser.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<flag> enable a flag\n"
" -fno-<flag> disable a flag\n"
" -fhelp list possible flags\n");
con_out(" -W<warning> enable a warning\n"
" -Wno-<warning> disable a warning\n"
" -Wall enable all warnings\n");
con_out(" -Werror treat warnings as errors\n"
" -Werror-<warning> treat a warning as error\n"
" -Wno-error-<warning> opposite of the above\n");
con_out(" -Whelp list possible warnings\n");
con_out(" -O<number> optimization level\n"
" -O<name> enable specific optimization\n"
" -Ono-<name> 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 *has_progs_src) {
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;
*has_progs_src = true;
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);
if (item.type == TYPE_SRC) {
*has_progs_src = true;
}
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 has_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, &has_progs_src)) {
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) && !has_progs_src) {
FILE *fp = fopen(OPTS_OPTION_STR(OPTION_PROGSRC), "rb");
if (fp) {
has_progs_src = true;
fclose(fp);
}
}
if (has_progs_src) {
FILE *src;
char *line = nullptr;
size_t linelen = 0;
bool has_first_line = false;
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 (has_first_line) {
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);
}
has_first_line = 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", (has_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 (items[itr].type == TYPE_SRC) {
continue;
}
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 (has_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))
delete 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;
}

View file

@ -1,51 +0,0 @@
#!/bin/sh
prog=$0
die() {
echo "$@"
exit 1
}
want() {
test -e "$1" && return
echo "$prog: missing $1"
echo "$prog: run this script from the top of a gmqcc source tree"
exit 1
}
for i in opts.def \
doc/gmqcc.1 \
gmqcc.ini.example
do want "$i"; done
# y/_ABCDEFGHIJKLMNOPQRSTUVWXYZ/-abcdefghijklmnopqrstuvwxyz/;
check_opt() {
opt_def_name=$1
arg_char=$2
for i in $(sed -ne \
'/^#ifdef GMQCC_TYPE_'${opt_def_name}'$/,/^#endif/{
/GMQCC_DEFINE_FLAG/{
s/^.*GMQCC_DEFINE_FLAG(\([^,)]*\)[),].*$/\1/;p;
}
}' opts.def)
do
opt=$(echo "$i" | tr -- '_A-Z' '-a-z')
grep -qF -- ".It Fl "${arg_char}" Ns Cm $opt" \
doc/gmqcc.1 || echo "doc/gmqcc.1: missing: -${arg_char}$opt"
grep -q -- "[^a-zA-Z_]$i[^a-zA-Z_]" \
gmqcc.ini.example || echo "gmqcc.ini.example: missing: $i"
done
}
check_opt FLAGS f
check_opt WARNS W
check_opt OPTIMIZATIONS O
# TODO: linux version
if [ "$(uname -s)" != "Linux" ]; then
for i in doc/*.1;
do
mandoc -Tlint -Wall "$i";
done
fi

424
opts.cpp
View file

@ -1,424 +0,0 @@
#include <string.h>
#include <stdlib.h>
#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] |= (1u<<(lb.bit));
else
flags[lb.idx] &= ~(1u<<(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);
}

126
opts.def
View file

@ -1,126 +0,0 @@
#ifndef GMQCC_DEFINE_FLAG
# error "bad opts.def usage"
#endif
/* codegen flags */
#ifdef GMQCC_TYPE_FLAGS
GMQCC_DEFINE_FLAG(DARKPLACES_STRING_TABLE_BUG)
GMQCC_DEFINE_FLAG(ADJUST_VECTOR_FIELDS)
GMQCC_DEFINE_FLAG(FTEPP)
GMQCC_DEFINE_FLAG(FTEPP_PREDEFS)
GMQCC_DEFINE_FLAG(FTEPP_MATHDEFS)
GMQCC_DEFINE_FLAG(FTEPP_INDIRECT_EXPANSION)
GMQCC_DEFINE_FLAG(RELAXED_SWITCH)
GMQCC_DEFINE_FLAG(SHORT_LOGIC)
GMQCC_DEFINE_FLAG(PERL_LOGIC)
GMQCC_DEFINE_FLAG(TRANSLATABLE_STRINGS)
GMQCC_DEFINE_FLAG(INITIALIZED_NONCONSTANTS)
GMQCC_DEFINE_FLAG(ASSIGN_FUNCTION_TYPES)
GMQCC_DEFINE_FLAG(LNO)
GMQCC_DEFINE_FLAG(CORRECT_TERNARY)
GMQCC_DEFINE_FLAG(SINGLE_VECTOR_DEFS)
GMQCC_DEFINE_FLAG(CORRECT_LOGIC)
GMQCC_DEFINE_FLAG(TRUE_EMPTY_STRINGS)
GMQCC_DEFINE_FLAG(FALSE_EMPTY_STRINGS)
GMQCC_DEFINE_FLAG(UTF8)
GMQCC_DEFINE_FLAG(BAIL_ON_WERROR)
GMQCC_DEFINE_FLAG(LOOP_LABELS)
GMQCC_DEFINE_FLAG(UNTYPED_NIL)
GMQCC_DEFINE_FLAG(PERMISSIVE)
GMQCC_DEFINE_FLAG(VARIADIC_ARGS)
GMQCC_DEFINE_FLAG(LEGACY_VECTOR_MATHS)
GMQCC_DEFINE_FLAG(EXPRESSIONS_FOR_BUILTINS)
GMQCC_DEFINE_FLAG(RETURN_ASSIGNMENTS)
GMQCC_DEFINE_FLAG(UNSAFE_VARARGS)
GMQCC_DEFINE_FLAG(TYPELESS_STORES)
GMQCC_DEFINE_FLAG(SORT_OPERANDS)
GMQCC_DEFINE_FLAG(EMULATE_STATE)
GMQCC_DEFINE_FLAG(ARITHMETIC_EXCEPTIONS)
GMQCC_DEFINE_FLAG(SPLIT_VECTOR_PARAMETERS)
GMQCC_DEFINE_FLAG(DEFAULT_ERASEABLE)
#endif
/* warning flags */
#ifdef GMQCC_TYPE_WARNS
GMQCC_DEFINE_FLAG(UNINITIALIZED_GLOBAL)
GMQCC_DEFINE_FLAG(DEBUG)
GMQCC_DEFINE_FLAG(UNUSED_VARIABLE)
GMQCC_DEFINE_FLAG(UNUSED_COMPONENT)
GMQCC_DEFINE_FLAG(USED_UNINITIALIZED)
GMQCC_DEFINE_FLAG(UNKNOWN_CONTROL_SEQUENCE)
GMQCC_DEFINE_FLAG(EXTENSIONS)
GMQCC_DEFINE_FLAG(FIELD_REDECLARED)
GMQCC_DEFINE_FLAG(MISSING_RETURN_VALUES)
GMQCC_DEFINE_FLAG(INVALID_PARAMETER_COUNT)
GMQCC_DEFINE_FLAG(LOCAL_SHADOWS)
GMQCC_DEFINE_FLAG(LOCAL_CONSTANTS)
GMQCC_DEFINE_FLAG(VOID_VARIABLES)
GMQCC_DEFINE_FLAG(IMPLICIT_FUNCTION_POINTER)
GMQCC_DEFINE_FLAG(VARIADIC_FUNCTION)
GMQCC_DEFINE_FLAG(FRAME_MACROS)
GMQCC_DEFINE_FLAG(EFFECTLESS_STATEMENT)
GMQCC_DEFINE_FLAG(END_SYS_FIELDS)
GMQCC_DEFINE_FLAG(ASSIGN_FUNCTION_TYPES)
GMQCC_DEFINE_FLAG(CPP)
GMQCC_DEFINE_FLAG(MULTIFILE_IF)
GMQCC_DEFINE_FLAG(DOUBLE_DECLARATION)
GMQCC_DEFINE_FLAG(CONST_VAR)
GMQCC_DEFINE_FLAG(MULTIBYTE_CHARACTER)
GMQCC_DEFINE_FLAG(TERNARY_PRECEDENCE)
GMQCC_DEFINE_FLAG(UNKNOWN_PRAGMAS)
GMQCC_DEFINE_FLAG(UNREACHABLE_CODE)
GMQCC_DEFINE_FLAG(UNKNOWN_ATTRIBUTE)
GMQCC_DEFINE_FLAG(RESERVED_NAMES)
GMQCC_DEFINE_FLAG(UNINITIALIZED_CONSTANT)
GMQCC_DEFINE_FLAG(DIFFERENT_QUALIFIERS)
GMQCC_DEFINE_FLAG(DIFFERENT_ATTRIBUTES)
GMQCC_DEFINE_FLAG(DEPRECATED)
GMQCC_DEFINE_FLAG(PARENTHESIS)
GMQCC_DEFINE_FLAG(UNSAFE_TYPES)
GMQCC_DEFINE_FLAG(BREAKDEF)
GMQCC_DEFINE_FLAG(CONST_OVERWRITE)
GMQCC_DEFINE_FLAG(DIRECTIVE_INMACRO)
GMQCC_DEFINE_FLAG(BUILTINS)
GMQCC_DEFINE_FLAG(INEXACT_COMPARES)
#endif
#ifdef GMQCC_TYPE_OPTIMIZATIONS
GMQCC_DEFINE_FLAG(PEEPHOLE, 1)
GMQCC_DEFINE_FLAG(TAIL_RECURSION, 1)
GMQCC_DEFINE_FLAG(OVERLAP_LOCALS, 3)
GMQCC_DEFINE_FLAG(LOCAL_TEMPS, 3)
GMQCC_DEFINE_FLAG(GLOBAL_TEMPS, 3)
GMQCC_DEFINE_FLAG(STRIP_CONSTANT_NAMES, 1)
GMQCC_DEFINE_FLAG(OVERLAP_STRINGS, 2)
GMQCC_DEFINE_FLAG(CALL_STORES, 3)
GMQCC_DEFINE_FLAG(VOID_RETURN, 1)
GMQCC_DEFINE_FLAG(VECTOR_COMPONENTS, 1)
GMQCC_DEFINE_FLAG(CONST_FOLD_DCE, 2)
GMQCC_DEFINE_FLAG(CONST_FOLD, 0) /* cannot be turned off */
#endif
#ifdef GMQCC_TYPE_OPTIONS
GMQCC_DEFINE_FLAG(O)
GMQCC_DEFINE_FLAG(OUTPUT)
GMQCC_DEFINE_FLAG(QUIET)
GMQCC_DEFINE_FLAG(G)
GMQCC_DEFINE_FLAG(STANDARD)
GMQCC_DEFINE_FLAG(DEBUG)
GMQCC_DEFINE_FLAG(DUMPFIN)
GMQCC_DEFINE_FLAG(DUMP)
GMQCC_DEFINE_FLAG(FORCECRC)
GMQCC_DEFINE_FLAG(FORCED_CRC)
GMQCC_DEFINE_FLAG(PP_ONLY)
GMQCC_DEFINE_FLAG(MAX_ARRAY_SIZE)
GMQCC_DEFINE_FLAG(ADD_INFO)
GMQCC_DEFINE_FLAG(PROGSRC)
GMQCC_DEFINE_FLAG(COVERAGE)
GMQCC_DEFINE_FLAG(STATE_FPS)
#endif
/* some cleanup so we don't have to */
#undef GMQCC_TYPE_FLAGS
#undef GMQCC_TYPE_WARNS
#undef GMQCC_TYPE_OPTIONS
#undef GMQCC_TYPE_OPTIMIZATIONS
#undef GMQCC_DEFINE_FLAG

3130
parser.c Normal file

File diff suppressed because it is too large Load diff

6416
parser.cpp

File diff suppressed because it is too large Load diff

View file

@ -1,84 +0,0 @@
#ifndef GMQCC_PARSER_HDR
#define GMQCC_PARSER_HDR
#include "gmqcc.h"
#include "lexer.h"
#include "ast.h"
#include "intrin.h"
#include "fold.h"
struct parser_t;
#define parser_ctx(p) ((p)->lex->tok.ctx)
struct parser_t {
parser_t();
~parser_t();
void remove_ast();
lex_file *lex;
int tok;
bool ast_cleaned;
std::vector<ast_expression *> globals;
std::vector<ast_expression *> fields;
std::vector<ast_function *> functions;
size_t translated;
/* must be deleted first, they reference immediates and values */
std::vector<ast_value *> accessors;
ast_value *nil;
ast_value *reserved_version;
size_t crc_globals;
size_t crc_fields;
ast_function *function;
ht aliases;
/* All the labels the function defined...
* Should they be in ast_function instead?
*/
std::vector<ast_label*> labels;
std::vector<ast_goto*> gotos;
std::vector<const char *> breaks;
std::vector<const char *> continues;
/* A list of hashtables for each scope */
std::vector<ht> variables;
ht htfields;
ht htglobals;
std::vector<ht> typedefs;
/* not to be used directly, we use the hash table */
std::vector<ast_expression*> _locals;
std::vector<size_t> _blocklocals;
std::vector<std::unique_ptr<ast_value>> _typedefs;
std::vector<size_t> _blocktypedefs;
std::vector<lex_ctx_t> _block_ctx;
/* we store the '=' operator info */
const oper_info *assign_op;
/* magic values */
ast_value *const_vec[3];
/* pragma flags */
bool noref;
/* collected information */
size_t max_param_count;
fold m_fold;
intrin m_intrin;
};
/* parser.c */
char *parser_strdup (const char *str);
ast_expression *parser_find_global(parser_t *parser, const char *name);
#endif

35
propsal.txt Normal file
View file

@ -0,0 +1,35 @@
This is a propsal to extend the progs.dat file format without breaking
backwards compatability. Currently the progs file format header has a
description simaler to this:
struct {
uint32_t version;
uint32_t crc16;
....
uint32_t entfield;
}
The obvious notable issue here is version and crc16 are larger than they
essentially need to be, if we made version and crc16 both uint16_t we can
give ourselfs 32 bytes (2x16) to store additional data that can be used
to make smaller progs.dat files.
I propose a new structual layout like this:
struct {
uint16_t version;
uint16_t flags; /* contains a skip field */
uint16_t crc16;
uint16_t skip; /* skiped globals */
....
uint32_t entfield;
}
about 45% of globals are zero, if we could order them at the top of the
globals array we can essentially use the skip field to specify how much
zero globals the engine would have to populate (instead of being stored
in the actual file itself) flags can specify if the progs.dat file skiped
globals on the write of the progs.dat file.
Of course only one bit in the flags would have to be set to specify if the
file contains a skip field. Which lends itself to the fact that flags could
later be extended for other things.

250
stat.cpp
View file

@ -1,250 +0,0 @@
#include <string.h>
#include <stdlib.h>
#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 = nullptr;
size_t m = 0;
void *p = nullptr;
if (*a) {
d = vec_meta(*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);
}

1305
test.cpp

File diff suppressed because it is too large Load diff

163
test/ast-macros.h Normal file
View file

@ -0,0 +1,163 @@
#ifndef TEST_AST_MACROS_HDR
#define TEST_AST_MACROS_HDR
#define TESTVARS() \
ast_block *curblock; \
lex_ctx ctx
#define TESTINIT() \
ctx.file = NULL; \
ctx.line = 1;
#define DEFVAR(name) \
ast_value *name
#define VAR(type, name) \
name = ast_value_new(ctx, #name, type)
#define VARnamed(type, name, varname) \
name = ast_value_new(ctx, #varname, type)
#define MKGLOBAL(name) \
assert(globals_add(name) >= 0)
#define FIELD(type, name) \
name = ast_value_new(ctx, #name, TYPE_FIELD); \
do { \
ast_value *field_##name = ast_value_new(ctx, #name, type); \
name->expression.next = (ast_expression*)field_##name; \
MKFIELD(name); \
} while (0)
#define MKFIELD(name) \
assert(fields_add(name) >= 0)
#define MKCONSTFLOAT(name, value) \
do { \
name->isconst = true; \
name->constval.vfloat = value; \
MKGLOBAL(name); \
} while(0)
#define MKCONSTSTRING(name, value) \
do { \
name->isconst = true; \
name->constval.vstring = util_strdup(value); \
MKGLOBAL(name); \
} while(0)
#define MKCONSTVECTOR(name, valx, valy, valz) \
do { \
name->isconst = true; \
name->constval.vvec.x = (valx); \
name->constval.vvec.y = (valy); \
name->constval.vvec.z = (valz); \
MKGLOBAL(name); \
} while(0)
#define STATE(a) \
do { \
ast_expression *exp = (ast_expression*)(a); \
assert(ast_block_exprs_add(curblock, exp)); \
} while(0)
#define ASSIGN(op, a, b) \
(ast_expression*)ast_store_new(ctx, INSTR_##op, (ast_expression*)(a), (ast_expression*)(b))
#define BIN(op, a, b) \
(ast_expression*)ast_binary_new(ctx, INSTR_##op, (ast_expression*)(a), (ast_expression*)(b))
#define ENTFIELD(a, b) \
(ast_expression*)ast_entfield_new(ctx, (ast_expression*)(a), (ast_expression*)(b))
#define VECMEM(vec, mem) \
(ast_expression*)ast_member_new(ctx, (ast_expression*)(vec), (mem))
#define CALL(what) \
do { \
ast_call *call = ast_call_new(ctx, (ast_expression*)what); \
#define CALLPARAM(x) \
assert(ast_call_params_add(call, (ast_expression*)x));
#define ENDCALL() \
STATE(call); \
} while(0)
#define ENDCALLWITH(as, where) \
{ \
ast_expression *as = (ast_expression*)call; \
where; \
} \
} while(0)
#define WHILE(cond) \
do { \
ast_expression *wh_cond = (ast_expression*)(cond); \
ast_block *wh_body = ast_block_new(ctx); \
ast_block *oldcur = curblock; \
ast_loop *loop; \
curblock = wh_body;
#define ENDWHILE() \
curblock = oldcur; \
loop = ast_loop_new(ctx, NULL, (ast_expression*)wh_cond, \
NULL, NULL, (ast_expression*)wh_body); \
assert(loop); \
STATE(loop); \
} while(0)
#define BUILTIN(name, outtype, number) \
do { \
ast_function *func_##name; \
ast_value *thisfuncval; \
ast_function *thisfunc; \
DEFVAR(return_##name); \
VARnamed(TYPE_FUNCTION, name, name); \
VARnamed(outtype, return_##name, "#returntype"); \
name->expression.next = (ast_expression*)return_##name; \
MKGLOBAL(name); \
func_##name = ast_function_new(ctx, #name, name); \
thisfunc = func_##name; \
(void)thisfunc; \
thisfuncval = name; \
(void)thisfuncval; \
assert(functions_add(func_##name) >= 0); \
func_##name->builtin = number;
#define ENDBUILTIN() } while(0)
#define PARAM(ptype, name) \
do { \
DEFVAR(parm); \
VARnamed(ptype, parm, name); \
assert(ast_value_params_add(thisfuncval, parm)); \
} while(0)
#define FUNCTION(name, outtype) \
do { \
ast_function *thisfunc; \
ast_function *func_##name; \
ast_block *my_funcblock; \
DEFVAR(var_##name); \
DEFVAR(return_##name); \
VARnamed(TYPE_FUNCTION, var_##name, name); \
VARnamed(outtype, return_##name, "#returntype"); \
var_##name->expression.next = (ast_expression*)return_##name; \
MKGLOBAL(var_##name); \
func_##name = ast_function_new(ctx, #name, var_##name); \
thisfunc = func_##name; \
(void)thisfunc; \
assert(functions_add(func_##name) >= 0); \
my_funcblock = ast_block_new(ctx); \
assert(my_funcblock); \
assert(ast_function_blocks_add(func_##name, my_funcblock)); \
curblock = my_funcblock;
#define MKLOCAL(var) \
assert(ast_block_locals_add(curblock, var))
#define ENDFUNCTION(name) \
} while(0)
#endif

201
test/ast-test.c Normal file
View file

@ -0,0 +1,201 @@
#include "gmqcc.h"
#include "ast.h"
/* NOTE: it's a test - I'll abort() on epic-failure */
#ifdef assert
# undef assert
#endif
/* (note: 'do {} while(0)' forces the need for a semicolon after assert() */
#define assert(x) do { if ( !(x) ) { printf("Assertion failed: %s\n", #x); abort(); } } while(0)
VECTOR_MAKE(ast_value*, globals);
VECTOR_MAKE(ast_value*, fields);
VECTOR_MAKE(ast_function*, functions);
uint32_t opts_flags[1 + (COUNT_FLAGS / 32)];
uint32_t opts_warn [1 + (COUNT_WARNINGS / 32)];
uint32_t opts_O = 1;
const char *opts_output = "progs.dat";
int opts_standard = COMPILER_GMQCC;
bool opts_debug = false;
bool opts_memchk = false;
bool opts_werror = false;
#include "ast-macros.h"
int main()
{
size_t i;
ir_builder *ir;
TESTVARS();
DEFVAR(vi);
DEFVAR(vx);
DEFVAR(f0);
DEFVAR(f1);
DEFVAR(f5);
DEFVAR(cv3x4x5);
DEFVAR(cv1x1x1);
DEFVAR(sHello);
DEFVAR(sNL);
DEFVAR(print);
DEFVAR(ftos);
DEFVAR(spawn);
DEFVAR(mema);
DEFVAR(memb);
DEFVAR(memv);
DEFVAR(pawn);
/* opts_debug = true; */
BUILTIN(print, TYPE_VOID, -1);
PARAM(TYPE_STRING, text);
ENDBUILTIN();
BUILTIN(ftos, TYPE_STRING, -2);
PARAM(TYPE_FLOAT, value);
ENDBUILTIN();
BUILTIN(spawn, TYPE_ENTITY, -3);
ENDBUILTIN();
TESTINIT();
VAR(TYPE_FLOAT, f0);
VAR(TYPE_FLOAT, f1);
VAR(TYPE_FLOAT, f5);
VAR(TYPE_STRING, sHello);
VAR(TYPE_STRING, sNL);
VAR(TYPE_VECTOR, cv3x4x5);
VAR(TYPE_VECTOR, cv1x1x1);
FIELD(TYPE_FLOAT, mema);
FIELD(TYPE_FLOAT, memb);
FIELD(TYPE_VECTOR, memv);
MKCONSTFLOAT(f0, 0.0);
MKCONSTFLOAT(f1, 1.0);
MKCONSTFLOAT(f5, 5.0);
MKCONSTSTRING(sHello, "Hello, World\n");
MKCONSTSTRING(sNL, "\n");
MKCONSTVECTOR(cv3x4x5, 3, 4, 5);
MKCONSTVECTOR(cv1x1x1, 1, 1, 1);
FUNCTION(foo, TYPE_VOID);
ENDFUNCTION(foo);
#define PRINTNL() do { CALL(print) CALLPARAM(sNL) ENDCALL(); } while(0)
FUNCTION(main, TYPE_VOID);
VAR(TYPE_FLOAT, vi);
VAR(TYPE_FLOAT, vx);
VAR(TYPE_ENTITY, pawn);
MKLOCAL(vi);
MKLOCAL(vx);
MKLOCAL(pawn);
STATE(ASSIGN(STORE_F, vi, f0));
WHILE(BIN(LT, vi, f5));
STATE(ASSIGN(STORE_F, vx, BIN(MUL_F, vi, f5)));
STATE(ASSIGN(STORE_F, vi, BIN(ADD_F, vi, f1)));
ENDWHILE();
CALL(print)
CALLPARAM(sHello)
ENDCALL();
CALL(spawn)
ENDCALLWITH(newent, STATE(ASSIGN(STORE_ENT, pawn, newent)));
STATE(ASSIGN(STOREP_F, ENTFIELD(pawn, mema), f5));
STATE(ASSIGN(STOREP_F, ENTFIELD(pawn, memb), f1));
STATE(ASSIGN(STOREP_V, ENTFIELD(pawn, memv), cv3x4x5));
CALL(ftos)
CALLPARAM(ENTFIELD(pawn, mema))
ENDCALLWITH(output,
CALL(print)
CALLPARAM(output)
CALLPARAM(sNL)
ENDCALL();
);
CALL(ftos)
CALLPARAM(ENTFIELD(pawn, memb))
ENDCALLWITH(output,
CALL(print)
CALLPARAM(output)
CALLPARAM(sNL)
ENDCALL();
);
CALL(ftos)
CALLPARAM(ENTFIELD(pawn, VECMEM(memv, 2)))
ENDCALLWITH(output,
CALL(print)
CALLPARAM(output)
CALLPARAM(sNL)
ENDCALL();
);
ENDFUNCTION(main);
ir = ir_builder_new("ast_test");
assert(ir);
/* gen fields */
for (i = 0; i < fields_elements; ++i) {
if (!ast_global_codegen(fields_data[i], ir)) {
assert(!"failed to generate field");
}
}
/* gen globals */
for (i = 0; i < globals_elements; ++i) {
if (!ast_global_codegen(globals_data[i], ir)) {
assert(!"failed to generate global");
}
}
/* gen functions */
for (i = 0; i < functions_elements; ++i) {
if (!ast_function_codegen(functions_data[i], ir)) {
assert(!"failed to generate function");
}
if (!ir_function_finalize(functions_data[i]->ir_func))
assert(!"finalize on function failed...");
}
/* dump */
ir_builder_dump(ir, printf);
/* Now create a file */
if (!ir_builder_generate(ir, "test_ast.dat"))
printf("*** failed to generate code\n");
/* ir cleanup */
ir_builder_delete(ir);
/* cleanup */
/* Functions must be deleted FIRST since their expressions
* reference global variables.
*/
for (i = 0; i < functions_elements; ++i) {
ast_function_delete(functions_data[i]);
}
if (functions_data)
mem_d(functions_data);
/* We must delete not only globals, but also the functions'
* ast_values (their type and name), that's why we added them to the globals vector.
*/
for (i = 0; i < globals_elements; ++i) {
ast_value_delete(globals_data[i]);
}
if (globals_data)
mem_d(globals_data);
return 0;
}

150
test/ir-test.c Normal file
View file

@ -0,0 +1,150 @@
#include "gmqcc.h"
#include "ir.h"
#ifdef assert
# undef assert
#endif
/* (note: 'do {} while(0)' forces the need for a semicolon after assert() */
#define assert(x) do { if ( !(x) ) { printf("Assertion failed: %s\n", #x); abort(); } } while(0)
int main()
{
int lf;
ir_builder *b = ir_builder_new("test");
ir_value *va = ir_builder_create_global(b, "a", TYPE_FLOAT);
ir_value *v3 = ir_builder_create_global(b, "const_f_3", TYPE_FLOAT);
ir_value *vb = ir_builder_create_global(b, "b", TYPE_FLOAT);
ir_value *vc = ir_builder_create_global(b, "c", TYPE_FLOAT);
ir_value *vd = ir_builder_create_global(b, "d", TYPE_FLOAT);
ir_value *life1 = ir_builder_create_global(b, "life1", TYPE_FLOAT);
ir_value *life2 = ir_builder_create_global(b, "life2", TYPE_FLOAT);
ir_value *life3 = ir_builder_create_global(b, "life3", TYPE_FLOAT);
ir_function *fmain = NULL;
ir_value *la = NULL;
ir_block *bmain = NULL;
ir_block *blt = NULL;
ir_block *bge = NULL;
ir_block *bend = NULL;
ir_value *sum = NULL;
ir_value *prd = NULL;
ir_value *less = NULL;
ir_value *x1 = NULL;
ir_value *vig = NULL;
ir_value *x2 = NULL;
ir_instr *retphi = NULL;
ir_value *retval = NULL;
assert(ir_value_set_float(v3, 3.0f) );
assert(ir_value_set_float(vb, 4.0f) );
assert(ir_value_set_float(vc, 10.0f) );
assert(ir_value_set_float(vd, 20.0f) );
fmain = ir_builder_create_function(b, "main");
assert(fmain);
la = ir_function_create_local(fmain, "loc1", TYPE_FLOAT);
assert(la);
assert( bmain = ir_function_create_block(fmain, "top") );
assert( blt = ir_function_create_block(fmain, "less") );
assert( bge = ir_function_create_block(fmain, "greaterequal") );
assert( bend = ir_function_create_block(fmain, "end") );
assert(ir_block_create_store_op(bmain, INSTR_STORE_F, va, v3));
assert( sum = ir_block_create_add(bmain, "%sum", va, vb) );
assert( prd = ir_block_create_mul(bmain, "%mul", sum, vc) );
assert( less = ir_block_create_binop(bmain, "%less", INSTR_LT, prd, vd) );
assert(ir_block_create_if(bmain, less, blt, bge));
x1 = ir_block_create_binop(blt, "%x1", INSTR_ADD_F, sum, v3);
assert(x1);
assert(ir_block_create_goto(blt, bend));
vig = ir_block_create_binop(bge, "%ignore", INSTR_ADD_F, va, vb);
assert(vig);
assert(ir_block_create_store_op(bge, INSTR_STORE_F, la, vig));
x2 = ir_block_create_binop(bge, "%x2", INSTR_ADD_F, sum, v3);
assert(x2);
assert(ir_block_create_goto(bge, bend));
retphi = ir_block_create_phi(bend, "%retval", TYPE_FLOAT);
assert(retphi);
assert(ir_phi_add(retphi, blt, x1));
assert(ir_phi_add(retphi, bge, x2));
retval = ir_phi_value(retphi);
assert(retval);
assert(ir_block_create_return(bend, retval));
/*
printf("%i should be 1\n", ir_value_life_merge(va, 31));
printf("%i should be 1\n", ir_value_life_merge(va, 33));
printf("%i should be 0\n", ir_value_life_merge(va, 33));
printf("%i should be 1\n", ir_value_life_merge(va, 1));
printf("%i should be 1\n", ir_value_life_merge(va, 2));
printf("%i should be 1\n", ir_value_life_merge(va, 20));
printf("%i should be 1\n", ir_value_life_merge(va, 21));
printf("%i should be 1\n", ir_value_life_merge(va, 8));
printf("%i should be 1\n", ir_value_life_merge(va, 9));
printf("%i should be 1\n", ir_value_life_merge(va, 3));
printf("%i should be 0\n", ir_value_life_merge(va, 9));
printf("%i should be 1\n", ir_value_life_merge(va, 17));
printf("%i should be 1\n", ir_value_life_merge(va, 18));
printf("%i should be 1\n", ir_value_life_merge(va, 19));
printf("%i should be 0\n", ir_value_life_merge(va, 19));
ir_value_dump_life(va, printf);
printf("%i should be 1\n", ir_value_life_merge(va, 10));
printf("%i should be 1\n", ir_value_life_merge(va, 9));
printf("%i should be 0\n", ir_value_life_merge(va, 10));
printf("%i should be 0\n", ir_value_life_merge(va, 10));
ir_value_dump_life(va, printf);
*/
ir_builder_dump(b, printf);
assert(ir_function_finalize(fmain));
ir_builder_dump(b, printf);
ir_value_dump_life(sum, printf);
ir_value_dump_life(prd, printf);
ir_value_dump_life(less, printf);
ir_value_dump_life(x1, printf);
ir_value_dump_life(x2, printf);
ir_value_dump_life(retval, printf);
ir_value_dump_life(vig, printf);
ir_value_dump_life(la, printf);
ir_value_life_merge_into(retval, vig);
ir_value_dump_life(retval, printf);
ir_value_life_merge(x1, 12);
ir_value_life_merge(x1, 13);
ir_value_life_merge(x1, 14);
ir_value_life_merge_into(retval, x1);
ir_value_dump_life(retval, printf);
ir_value_life_merge(x1, 20);
ir_value_life_merge(x1, 21);
ir_value_life_merge(x1, 22);
ir_value_life_merge_into(retval, x1);
ir_value_dump_life(retval, printf);
ir_value_life_merge(x2, 1);
ir_value_life_merge(x2, 2);
ir_value_life_merge_into(retval, x2);
ir_value_dump_life(retval, printf);
for (lf = 4; lf <= 15; ++lf)
ir_value_life_merge(life1, lf);
ir_value_life_merge_into(retval, life1);
ir_value_dump_life(retval, printf);
for (lf = 17; lf <= 18; ++lf)
ir_value_life_merge(life2, lf);
ir_value_life_merge_into(retval, life2);
ir_value_dump_life(retval, printf);
for (lf = 2; lf <= 29; ++lf)
ir_value_life_merge(life3, lf);
ir_value_life_merge_into(retval, life3);
ir_value_dump_life(retval, printf);
ir_builder_delete(b);
return 0;
}

View file

@ -1,18 +0,0 @@
#define ACCUMULATE_FUNCTION(FUNC) \
[[accumulate]] void FUNC ()
ACCUMULATE_FUNCTION(foo) {
print("hello ");
}
ACCUMULATE_FUNCTION(foo) {
print("accumulation ");
}
ACCUMULATE_FUNCTION(foo) {
print("world\n");
}
void main() {
foo();
}

View file

@ -1,5 +0,0 @@
I: accumulate.qc
D: test function accumulation
T: -execute
C: -std=gmqcc -fftepp
M: hello accumulation world

View file

@ -1,34 +0,0 @@
float alias_1 = 3.14;
void alias_2() {
print("alias_2\n");
}
[[alias("alias_2")]] void alias_2_aliased();
[[alias("alias_1")]] float alias_1_aliased;
// alias to an alias?
vector alias_3;
[[alias("alias_3")]] vector alias_3_aliased;
// expected output
// alias_2
// 3.14
void main() {
alias_2_aliased();
alias_3_aliased= '1 2 3';
print(
ftos(
alias_1_aliased
),
"\n"
);
print(
"x ", ftos(alias_3_aliased_x), "\n",
"y ", ftos(alias_3_aliased_y), "\n",
"z ", ftos(alias_3_aliased_z), "\n"
);
}

View file

@ -1,9 +0,0 @@
I: aliases.qc
D: test aliases
T: -execute
C: -std=gmqcc
M: alias_2
M: 3.14
M: x 1
M: y 2
M: z 3

View file

@ -1,13 +0,0 @@
const float huge = 340282346638528859811704183484516925440.000000; // FLT_MAX
#ifdef DIVBYZERO
const float a = 1.0 / 0.0;
#endif
#ifdef OVERFLOW
const float a = huge * huge;
#endif
#ifdef UNDERFLOW
const float a = 1 / huge;
#endif

View file

@ -1,4 +0,0 @@
I: arithexcept.qc
D: arithmetic exceptions (divide by zero)
T: -fail
C: -std=fteqcc -farithmetic-exceptions -DDIVBYZERO

View file

@ -1,4 +0,0 @@
I: arithexcept.qc
D: arithmetic exceptions (overflow)
T: -fail
C: -std=fteqcc -farithmetic-exceptions -DOVERFLOW

View file

@ -1,4 +0,0 @@
I: arithexcept.qc
D: arithmetic exceptions (underflow)
T: -fail
C: -std=fteqcc -farithmetic-exceptions -DUNDERFLOW

View file

@ -1,52 +0,0 @@
float glob[7];
.float above;
.float flds[6];
.float below;
void main() {
float loc[6];
loc[0] = 1000;
loc[1] = 1100;
loc[2] = 1200;
loc[3] = 1300;
loc[4] = 1400;
loc[5] = 1500;
float i;
for (i = 0; i < 6; i += 1)
loc[i] += 1;
for (i = 0; i < 5; i += 1)
print(ftos(loc[i]), " ");
print(ftos(loc[i]), "\n");
glob[0] = 1000;
glob[1] = 1100;
glob[2] = 1200;
glob[3] = 1300;
glob[4] = 1400;
glob[5] = 1500;
glob[6] = 1600;
for (i = 0; i < 7; i += 1)
glob[i] += 1;
for (i = 0; i < 6; i += 1)
print(ftos(glob[i]), " ");
print(ftos(glob[i]), "\n");
entity e = spawn();
e.above = 7777;
e.below = 9999;
e.flds[0] = 1000;
e.flds[1] = 1100;
e.flds[2] = 1200;
e.flds[3] = 1300;
e.flds[4] = 1400;
e.flds[5] = 1500;
for (i = 0; i < 6; i += 1)
e.flds[i] += 1;
for (i = 0; i < 5; i += 1)
print(ftos(e.flds[i]), " ");
print(ftos(e.flds[i]), "\n");
}

View file

@ -1,7 +0,0 @@
I: arrays2.qc
D: initialized arrays
T: -execute
C: -std=fteqcc
M: 10 20 30 40 50 60 70
M: 100 200 300 400 500 600 0
M: Hello World

View file

@ -1,18 +0,0 @@
float glob1[7] = { 10, 20, 30, 40, 50, 60, 70 };
float glob2[7] = { 100, 200, 300, 400, 500, 600 };
string globs[] = { "Hello ", "World" };
void main() {
float i;
print(ftos(glob1[0]));
for (i = 1; i != 7; ++i)
print(" ", ftos(glob1[i]));
print("\n");
print(ftos(glob2[0]));
for (i = 1; i != 7; ++i)
print(" ", ftos(glob2[i]));
print("\n");
print(globs[0], globs[1], "\n");
}

View file

@ -1,7 +0,0 @@
I: arrays.qc
D: array accessors and functionality
T: -execute
C: -std=fteqcc
M: 1001 1101 1201 1301 1401 1501
M: 1001 1101 1201 1301 1401 1501 1601
M: 1001 1101 1201 1301 1401 1501

View file

@ -1,37 +0,0 @@
void main() {
float a; a = 1;
float b; b = 1;
float c; c = 1;
float d; d = 1;
vector e; e = '1 1 1';
vector f; f = '1 1 1';
#ifdef __STD_FTEQCC__
a &~= 1; // 0
#else
a &= ~1; // 0
#endif
#ifdef __STD_GMQCC__
b &= ~1; // 0
c &= ~d; // 0
#else
b &~= 1; // 0
c &~= 1; // 0
#endif
#ifdef __STD_FTEQCC__
f &~= e; // '0 0 0'
#else
f &= ~e; // '0 0 0'
#endif
#ifdef __STD_GMQCC__
e &= ~e; // '0 0 0'
#else
e &~= e; // '0 0 0'
#endif
print("a: ", ftos(a), "\nb: ",
ftos(b), "\nc: ",
ftos(c), "\n");
print("e: ", vtos(e), "\n");
print("f: ", vtos(f), "\n");
}

View file

@ -1,11 +0,0 @@
# used to test the builtins
I: bitnot.qc
D: test bitwise not operators (fteqcc operators)
T: -execute
C: -std=fteqcc
E: $null
M: a: 0
M: b: 0
M: c: 0
M: e: '0 0 0'
M: f: '0 0 0'

View file

@ -1,11 +0,0 @@
# used to test the builtins
I: bitnot.qc
D: test bitwise not operators (gmqcc operators)
T: -execute
C: -std=gmqcc -fftepp
E: $null
M: a: 0
M: b: 0
M: c: 0
M: e: '0 0 0'
M: f: '0 0 0'

View file

@ -1,23 +0,0 @@
void test(float brkat, float contat) {
float i;
for (i = 0; i < 10; i += 1) {
if (i == contat) {
print("ct ");
continue;
}
print(ftos(i), " ");
if (i == brkat) {
print("brk ");
break;
}
}
print("end\n");
}
void main() {
test(-1, -1);
test( 3, -1);
test(-1, 3);
test( 5, 2);
}

View file

@ -1,8 +0,0 @@
I: break.qc
D: test break and continue
T: -execute
C: -std=fteqcc
M: 0 1 2 3 4 5 6 7 8 9 end
M: 0 1 2 3 brk end
M: 0 1 2 ct 4 5 6 7 8 9 end
M: 0 1 ct 3 4 5 brk end

View file

@ -1,3 +0,0 @@
void() main = {
print("hello world");
}

View file

@ -1,7 +0,0 @@
# used to test the builtins
I: builtin.qc
D: test builtin functions
T: -execute
C: -std=gmqcc
E: $null
M: hello world

View file

@ -1,6 +0,0 @@
I: calls.qc
D: test calls
T: -execute
C: -std=gmqcc
E: -float 100 -float 200 -float 300
M: 4600

View file

@ -1,12 +0,0 @@
I: correct-logic.qc
D: vector logic flags
T: -execute
C: -std=fteqcc -fshort-logic
M: ! & | i N
M: 0, 0 -> 1 0 0 0 1
M: 0, x -> 1 0 1 0 1
M: x, 0 -> 0 0 1 1 0
M: x, x -> 0 1 1 1 0
M: 0, y -> 1 0 0 0 1
M: y, 0 -> 0 0 0 0 1
M: y, y -> 0 0 0 0 1

View file

@ -1,12 +0,0 @@
I: correct-logic.qc
D: vector logic flags
T: -execute
C: -std=fteqcc
M: ! & | i N
M: 0, 0 -> 1 0 0 0 1
M: 0, x -> 1 0 1 0 1
M: x, 0 -> 0 0 1 1 0
M: x, x -> 0 1 1 1 0
M: 0, y -> 1 0 0 0 1
M: y, 0 -> 0 0 0 0 1
M: y, y -> 0 0 0 0 1

View file

@ -1,12 +0,0 @@
I: correct-logic.qc
D: vector logic flags
T: -execute
C: -std=fteqcc -fcorrect-logic -fshort-logic
M: ! & | i N
M: 0, 0 -> 1 0 0 0 1
M: 0, x -> 1 0 1 0 1
M: x, 0 -> 0 0 1 1 0
M: x, x -> 0 1 1 1 0
M: 0, y -> 1 0 1 0 1
M: y, 0 -> 0 0 1 1 0
M: y, y -> 0 1 1 1 0

View file

@ -1,12 +0,0 @@
I: correct-logic.qc
D: vector logic flags
T: -execute
C: -std=fteqcc -fcorrect-logic
M: ! & | i N
M: 0, 0 -> 1 0 0 0 1
M: 0, x -> 1 0 1 0 1
M: x, 0 -> 0 0 1 1 0
M: x, x -> 0 1 1 1 0
M: 0, y -> 1 0 1 0 1
M: y, 0 -> 0 0 1 1 0
M: y, y -> 0 1 1 1 0

View file

@ -1,24 +0,0 @@
float test_s_not (vector s) { return !s; }
float test_s_and (vector s, vector t) { return s && t; }
float test_s_or (vector s, vector t) { return s || t; }
float test_s_if (vector s) { if (s) return 1; return 0; }
float test_s_ifnot(vector s) { if not (s) return 1; return 0; }
void test(vector s, vector t) {
print(ftos(!!test_s_not (s)), " ");
print(ftos(!!test_s_and (s, t)), " ");
print(ftos(!!test_s_or (s, t)), " ");
print(ftos(!!test_s_if (s)), " ");
print(ftos(!!test_s_ifnot(s)), "\n");
}
void main() {
print(" ! & | i N\n");
print("0, 0 -> "); test('0 0 0', '0 0 0');
print("0, x -> "); test('0 0 0', '1 0 0');
print("x, 0 -> "); test('1 0 0', '0 0 0');
print("x, x -> "); test('1 0 0', '1 0 0');
print("0, y -> "); test('0 0 0', '0 1 0');
print("y, 0 -> "); test('0 1 0', '0 0 0');
print("y, y -> "); test('0 1 0', '0 1 0');
}

View file

@ -1,14 +0,0 @@
I: correct-vs-short.qc
D: correct-logic vs short-logic without perl-logic
T: -execute
C: -std=fteqcc
M: X & | B
M: 0 0 0, 0 0 0 :: 0 0 0
M: 0 0 0, 5 0 0 :: 0 2 1
M: 5 0 0, 0 0 0 :: 0 2 1
M: 5 0 0, 5 0 0 :: 2 2 2
M: Y & | B
M: 0 0 0, 0 0 0 :: 0 0 0
M: 0 0 0, 0 5 0 :: 0 0 0
M: 0 5 0, 0 0 0 :: 0 0 0
M: 0 5 0, 0 5 0 :: 0 0 0

View file

@ -1,14 +0,0 @@
I: correct-vs-short.qc
D: correct-logic vs short-logic without perl-logic
T: -execute
C: -std=fteqcc -fcorrect-logic
M: X & | B
M: 0 0 0, 0 0 0 :: 0 0 0
M: 0 0 0, 5 0 0 :: 0 2 1
M: 5 0 0, 0 0 0 :: 0 2 1
M: 5 0 0, 5 0 0 :: 2 2 2
M: Y & | B
M: 0 0 0, 0 0 0 :: 0 0 0
M: 0 0 0, 0 5 0 :: 0 2 1
M: 0 5 0, 0 0 0 :: 0 2 1
M: 0 5 0, 0 5 0 :: 2 2 2

View file

@ -1,14 +0,0 @@
I: correct-vs-short.qc
D: correct-logic vs short-logic without perl-logic
T: -execute
C: -std=fteqcc -fcorrect-logic -fshort-logic
M: X & | B
M: 0 0 0, 0 0 0 :: 0 0 0
M: 0 0 0, 5 0 0 :: 0 2 1
M: 5 0 0, 0 0 0 :: 0 2 1
M: 5 0 0, 5 0 0 :: 2 2 2
M: Y & | B
M: 0 0 0, 0 0 0 :: 0 0 0
M: 0 0 0, 0 5 0 :: 0 2 1
M: 0 5 0, 0 0 0 :: 0 2 1
M: 0 5 0, 0 5 0 :: 2 2 2

View file

@ -1,18 +0,0 @@
void test(vector a, vector b) {
print(ftos((a && b) + (a && b)), " ");
print(ftos((a || b) + (a || b)), " ");
print(ftos((a && b) + (a || b)), "\n");
}
void main() {
print("X & | B\n");
print("0 0 0, 0 0 0 :: "); test('0 0 0', '0 0 0');
print("0 0 0, 5 0 0 :: "); test('0 0 0', '5 0 0');
print("5 0 0, 0 0 0 :: "); test('5 0 0', '0 0 0');
print("5 0 0, 5 0 0 :: "); test('5 0 0', '5 0 0');
print("Y & | B\n");
print("0 0 0, 0 0 0 :: "); test('0 0 0', '0 0 0');
print("0 0 0, 0 5 0 :: "); test('0 0 0', '0 5 0');
print("0 5 0, 0 0 0 :: "); test('0 5 0', '0 0 0');
print("0 5 0, 0 5 0 :: "); test('0 5 0', '0 5 0');
}

View file

@ -1,21 +0,0 @@
// builtins for the standalone qcvm included with gmqcc
// in exec.c These should be updated to reflect the new
// builtins. I no event shall you even consider adding
// these individually per test.
void (string str, ...) print = #1;
string (float val) ftos = #2;
entity () spawn = #3;
void (entity ent) kill = #4;
string (vector vec) vtos = #5;
void (string str) error = #6;
float (vector vec) vlen = #7;
string (entity ent) etos = #8;
float (string str) stof = #9;
string (...) strcat = #10;
float (string str1, string str2) strcmp = #11;
vector (vector vec) normalize = #12;
float (float val) sqrt = #13;
float (float val) floor = #14;
float (float val1, float val2) pow = #15;
vector (string str) stov = #16;

View file

@ -1,27 +0,0 @@
entity self;
.float f;
..float fp;
...float fpp;
void try(entity e, ...float pp) {
print("and: ", ftos( e.(e.(e.pp)) ), "\n");
}
typedef float Float;
void try2(entity e, ...Float pp) {
print("and: ", ftos( e.(e.(e.pp)) ), "\n");
}
// whereas the varargs are tested in vararg tests
void main() {
self = spawn();
self.f = 123;
self.fp = f;
self.fpp = fp;
print(ftos( self.(self.fp) ), "\n");
print(ftos( self.(self.(self.fpp)) ), "\n");
try(self, fpp);
try2(self, fpp);
}

View file

@ -1,8 +0,0 @@
I: dots.qc
D: TOKEN_DOTS disambiguation
T: -execute
C: -std=fteqcc
M: 123
M: 123
M: and: 123
M: and: 123

View file

@ -1,68 +0,0 @@
enum {
// this behaviour is confusing, but I like that
// we support it.
__ = (__ - 1),
A = (__ + 1),
B,
C
};
enum {
D = C + B,
E = C + C,
F = C + D,
};
enum {
G = (B + F), H = (C + F),
I = (D + F), J = (B + I)
};
enum {
K = A + B - C + D - E + F *
G - H + I - J + A - B -
J + A,
L,
M,
N
};
enum : flag {
F1, /* = 1 << 1 */
F2, /* = 1 << 2 */
F3 /* = 1 << 3 */
};
/* reversed enumeration */
enum : reverse {
R1, // 3
R2, // 2
R3, // 1
R4 // 0
};
void main() {
print(ftos(A), "\n");
print(ftos(B), "\n");
print(ftos(C), "\n");
print(ftos(D), "\n");
print(ftos(E), "\n");
print(ftos(F), "\n");
print(ftos(G), "\n");
print(ftos(H), "\n");
print(ftos(I), "\n");
print(ftos(J), "\n");
print(ftos(K), "\n");
print(ftos(L), "\n");
print(ftos(M), "\n");
print(ftos(N), "\n");
print(ftos(F1), "\n");
print(ftos(F2), "\n");
print(ftos(F3), "\n");
print(ftos(R1), "\n");
print(ftos(R2), "\n");
print(ftos(R3), "\n");
print(ftos(R4), "\n");
};

View file

@ -1,25 +0,0 @@
I: enum.qc
D: enumerations
T: -execute
C: -std=fteqcc
M: 0
M: 1
M: 2
M: 3
M: 4
M: 5
M: 6
M: 7
M: 8
M: 9
M: 10
M: 11
M: 12
M: 13
M: 2
M: 4
M: 8
M: 3
M: 2
M: 1
M: 0

Some files were not shown because too many files have changed in this diff Show more