From ee3a9bd2006ac09c113f89d8f2f4ab8f24180d48 Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Tue, 17 Sep 2024 16:30:45 +0900 Subject: [PATCH] [qfcc] Start work on emitting SPIR-V It's rather non-functional but does so far pass validation via spirv-val. However, it's showing that qfcc's internals need a lot of work. --- config.d/qfcc.m4 | 5 + tools/qfcc/include/def.h | 6 +- tools/qfcc/include/function.h | 1 + tools/qfcc/include/options.h | 1 + tools/qfcc/include/qfcc.h | 1 + tools/qfcc/include/spirv.h | 33 +++ tools/qfcc/include/statements.h | 1 + tools/qfcc/source/Makemodule.am | 1 + tools/qfcc/source/function.c | 1 + tools/qfcc/source/options.c | 3 + tools/qfcc/source/qfcc.c | 18 +- tools/qfcc/source/spirv.c | 404 ++++++++++++++++++++++++++++++++ 12 files changed, 469 insertions(+), 6 deletions(-) create mode 100644 tools/qfcc/include/spirv.h create mode 100644 tools/qfcc/source/spirv.c diff --git a/config.d/qfcc.m4 b/config.d/qfcc.m4 index 55477fe81..383bc27ea 100644 --- a/config.d/qfcc.m4 +++ b/config.d/qfcc.m4 @@ -43,3 +43,8 @@ AC_DEFINE_UNQUOTED(QFCC_INCLUDE_PATH, "${expanded_datarootdir}/qfcc/include", [Define this to where qfcc should look for header files]) AC_DEFINE_UNQUOTED(QFCC_LIB_PATH, "${expanded_datarootdir}/qfcc/lib", [Define this to where qfcc should look for lib files]) + +PKG_CHECK_MODULES([spirv], [SPIRV-Headers], HAVE_SPIRV=yes, HAVE_SPIRV=no) +if test "x$HAVE_SPIRV" = "xyes"; then + AC_DEFINE(HAVE_SPIRV, 1, [Define if you have SPIRV-Headers]) +fi diff --git a/tools/qfcc/include/def.h b/tools/qfcc/include/def.h index 29922d19a..28e023c88 100644 --- a/tools/qfcc/include/def.h +++ b/tools/qfcc/include/def.h @@ -120,8 +120,10 @@ typedef struct def_s { void *free_addr; ///< who freed this } def_t; -#define D_PACKED(t,d) (*(t *) &(d)->space->data[(d)->offset]) -#define D_var(t, d) D_PACKED (pr_##t##_t, d) +#define D_packed(t,d,o) (*(t *) &(d)->space->data[(d)->offset + (o)]) +#define D_PACKED(t,d) D_packed (t, d, 0) +#define D_var_o(t,d,o) D_packed (pr_##t##_t, d, o) +#define D_var(t,d) D_var_o (t, d, 0) #define D_DOUBLE(d) D_var (double, d) #define D_FLOAT(d) D_var (float, d) #define D_INT(d) D_var (int, d) diff --git a/tools/qfcc/include/function.h b/tools/qfcc/include/function.h index b7de65841..1d2324865 100644 --- a/tools/qfcc/include/function.h +++ b/tools/qfcc/include/function.h @@ -89,6 +89,7 @@ typedef enum { */ typedef struct function_s { struct function_s *next; + const char *o_name; int builtin; ///< if non 0, call an internal function int code; ///< first statement int function_num; diff --git a/tools/qfcc/include/options.h b/tools/qfcc/include/options.h index dbf8400ab..547a2012f 100644 --- a/tools/qfcc/include/options.h +++ b/tools/qfcc/include/options.h @@ -43,6 +43,7 @@ typedef struct { bool vector_calls; // use floats instead of vectors for constant function args bool local_merging; // merge function locals into one block unsigned progsversion; // Progs version to generate code for + bool spirv; // Target spir-v instead of Quake bool vector_components; // add *_[xyz] symbols for vectors bool ifstring; // expand if (str) to if (str != "") bool const_initializers; // initialied globals are constant diff --git a/tools/qfcc/include/qfcc.h b/tools/qfcc/include/qfcc.h index ba2230534..be82eca36 100644 --- a/tools/qfcc/include/qfcc.h +++ b/tools/qfcc/include/qfcc.h @@ -75,6 +75,7 @@ typedef struct pr_info_s { struct DARRAY_TYPE (const char *) comp_files; const char *comp_dir; const char *unit_name; + const char *src_name; ///< main source file name struct symtab_s *symtab; struct symtab_s *entity_fields; diff --git a/tools/qfcc/include/spirv.h b/tools/qfcc/include/spirv.h new file mode 100644 index 000000000..bfe7ae6c2 --- /dev/null +++ b/tools/qfcc/include/spirv.h @@ -0,0 +1,33 @@ +/* + spirv.c + + qfcc spir-v file support + + Copyright (C) 2024 Bill Currie + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +#ifndef __spirv_h +#define __spirv_h + +bool spirv_write (struct pr_info_s *pr, const char *filename); + +#endif//__spirv_h diff --git a/tools/qfcc/include/statements.h b/tools/qfcc/include/statements.h index 6bb0bdc58..ca53c6da0 100644 --- a/tools/qfcc/include/statements.h +++ b/tools/qfcc/include/statements.h @@ -133,6 +133,7 @@ typedef struct sblock_s { int offset; ///< offset of first statement of block int reachable; int number; ///< number of this block in flow graph + unsigned id; ///< label id for this block (spir-v) statement_t *statements; statement_t **tail; } sblock_t; diff --git a/tools/qfcc/source/Makemodule.am b/tools/qfcc/source/Makemodule.am index 003777b75..ea84a18d6 100644 --- a/tools/qfcc/source/Makemodule.am +++ b/tools/qfcc/source/Makemodule.am @@ -71,6 +71,7 @@ qfcc_SOURCES = \ tools/qfcc/source/qp-parse.y \ tools/qfcc/source/reloc.c \ tools/qfcc/source/shared.c \ + tools/qfcc/source/spirv.c \ tools/qfcc/source/statements.c \ tools/qfcc/source/strpool.c \ tools/qfcc/source/struct.c \ diff --git a/tools/qfcc/source/function.c b/tools/qfcc/source/function.c index 3215c778e..09ed66dcf 100644 --- a/tools/qfcc/source/function.c +++ b/tools/qfcc/source/function.c @@ -1130,6 +1130,7 @@ new_function (const char *name, const char *nice_name) function_t *f; ALLOC (1024, function_t, functions, f); + f->o_name = save_string (name); f->s_name = ReuseString (name); f->s_file = pr.loc.file; if (!(f->name = nice_name)) diff --git a/tools/qfcc/source/options.c b/tools/qfcc/source/options.c index 9cb489f05..0b106231d 100644 --- a/tools/qfcc/source/options.c +++ b/tools/qfcc/source/options.c @@ -522,6 +522,9 @@ parse_code_option (const char *opt) options.code.progsversion = PROG_V6P_VERSION; } else if (!strcasecmp (tgt, "ruamoko")) { options.code.progsversion = PROG_VERSION; + } else if (!strcasecmp (tgt, "spir-v")) { + options.code.progsversion = PROG_VERSION; + options.code.spirv = true; } else { fprintf (stderr, "unknown target: %s\n", tgt); exit (1); diff --git a/tools/qfcc/source/qfcc.c b/tools/qfcc/source/qfcc.c index 3c92933b1..34a3b0af9 100644 --- a/tools/qfcc/source/qfcc.c +++ b/tools/qfcc/source/qfcc.c @@ -86,6 +86,7 @@ #include "tools/qfcc/include/options.h" #include "tools/qfcc/include/reloc.h" #include "tools/qfcc/include/shared.h" +#include "tools/qfcc/include/spirv.h" #include "tools/qfcc/include/strpool.h" #include "tools/qfcc/include/struct.h" #include "tools/qfcc/include/symtab.h" @@ -389,6 +390,7 @@ compile_to_obj (const char *file, const char *obj, language_t *lang) InitData (); chain_initial_types (); begin_compilation (); + pr.src_name = save_string (file); pr.comp_dir = save_cwd (); add_source_file (file); lang->initialized = false; @@ -411,9 +413,13 @@ compile_to_obj (const char *file, const char *obj, language_t *lang) } err = pr.error_count; if (!err) { - qfo = qfo_from_progs (&pr); - err = qfo_write (qfo, obj); - qfo_delete (qfo); + if (options.code.spirv) { + err = spirv_write (&pr, obj); + } else { + qfo = qfo_from_progs (&pr); + err = qfo_write (qfo, obj); + qfo_delete (qfo); + } } } return err; @@ -554,7 +560,11 @@ separate_compile (void) } else { dstring_copysubstr (output_file, base, ext - base); } - dstring_appendstr (output_file, ".qfo"); + if (options.code.spirv) { + dstring_appendstr (output_file, ".spv"); + } else { + dstring_appendstr (output_file, ".qfo"); + } } // need *file for checking -lfoo auto lang = file_language (*file, extension->str); diff --git a/tools/qfcc/source/spirv.c b/tools/qfcc/source/spirv.c new file mode 100644 index 000000000..e512ffbc1 --- /dev/null +++ b/tools/qfcc/source/spirv.c @@ -0,0 +1,404 @@ +/* + spirv.c + + qfcc spir-v file support + + Copyright (C) 2024 Bill Currie + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include + +#include "QF/quakeio.h" + +#include "tools/qfcc/include/def.h" +#include "tools/qfcc/include/defspace.h" +#include "tools/qfcc/include/function.h" +#include "tools/qfcc/include/qfcc.h" +#include "tools/qfcc/include/spirv.h" +#include "tools/qfcc/include/statements.h" +#include "tools/qfcc/include/strpool.h" +#include "tools/qfcc/include/symtab.h" +#include "tools/qfcc/include/type.h" + +typedef struct spirvctx_s { + defspace_t *space; + defspace_t *linkage; + defspace_t *strings; + defspace_t *names; + defspace_t *types; + defspace_t *code; + struct DARRAY_TYPE (unsigned) type_ids; + unsigned id; +} spirvctx_t; + +static def_t * +spirv_new_insn (int op, int size, defspace_t *space) +{ + auto def = new_def (nullptr, nullptr, space, sc_static); + def->offset = defspace_alloc_loc (space, size); + + D_INT (def) = (op & SpvOpCodeMask) | (size << SpvWordCountShift); + return def; +} + +static unsigned +spirv_id (spirvctx_t *ctx) +{ + return ++ctx->id; +} + +static void +spirv_Capability (SpvCapability capability, defspace_t *space) +{ + auto def = spirv_new_insn (SpvOpCapability, 2, space); + D_var_o(int, def, 1) = capability; +} + +static void +spirv_MemoryModel (SpvAddressingModel addressing, SpvMemoryModel memory, + defspace_t *space) +{ + auto def = spirv_new_insn (SpvOpMemoryModel, 3, space); + D_var_o(int, def, 1) = addressing; + D_var_o(int, def, 2) = memory; +} + +static unsigned +spirv_String (const char *name, spirvctx_t *ctx) +{ + int id = spirv_id (ctx); + int len = strlen (name) + 1; + auto def = spirv_new_insn (SpvOpString, 2 + RUP(len, 4) / 4, ctx->strings); + D_var_o(int, def, 1) = id; + memcpy (&D_var_o(int, def, 2), name, len); + return id; +} + +static void +spirv_Source (unsigned lang, unsigned version, unsigned srcid, + const char *src_str, spirvctx_t *ctx) +{ + auto def = spirv_new_insn (SpvOpSource, 4, ctx->strings); + D_var_o(int, def, 1) = lang; + D_var_o(int, def, 2) = version; + D_var_o(int, def, 3) = srcid; +} + +static void +spirv_Name (unsigned id, const char *name, spirvctx_t *ctx) +{ + int len = strlen (name) + 1; + auto def = spirv_new_insn (SpvOpName, 2 + RUP(len, 4) / 4, ctx->names); + D_var_o(int, def, 1) = id; + memcpy (&D_var_o(int, def, 2), name, len); +} + +static void +spirv_MemberName (unsigned id, unsigned member, const char *name, + spirvctx_t *ctx) +{ + int len = strlen (name) + 1; + auto def = spirv_new_insn (SpvOpMemberName, 3 + RUP(len, 4) / 4, + ctx->names); + D_var_o(int, def, 1) = id; + D_var_o(int, def, 2) = member; + memcpy (&D_var_o(int, def, 3), name, len); +} + +static unsigned type_id (const type_t *type, spirvctx_t *ctx); + +static unsigned +spirv_TypeVoid (spirvctx_t *ctx) +{ + auto def = spirv_new_insn (SpvOpTypeVoid, 2, ctx->types); + D_var_o(int, def, 1) = spirv_id (ctx); + return D_var_o(int, def, 1); +} + +static unsigned +spirv_TypeInt (unsigned bitwidth, bool is_signed, spirvctx_t *ctx) +{ + auto def = spirv_new_insn (SpvOpTypeInt, 4, ctx->types); + D_var_o(int, def, 1) = spirv_id (ctx); + D_var_o(int, def, 2) = bitwidth; + D_var_o(int, def, 3) = is_signed; + return D_var_o(int, def, 1); +} + +static unsigned +spirv_TypeFloat (unsigned bitwidth, spirvctx_t *ctx) +{ + auto def = spirv_new_insn (SpvOpTypeFloat, 3, ctx->types); + D_var_o(int, def, 1) = spirv_id (ctx); + D_var_o(int, def, 2) = bitwidth; + return D_var_o(int, def, 1); +} + +static unsigned +spirv_TypeVector (unsigned base, unsigned width, spirvctx_t *ctx) +{ + auto def = spirv_new_insn (SpvOpTypeVector, 4, ctx->types); + D_var_o(int, def, 1) = spirv_id (ctx); + D_var_o(int, def, 2) = base; + D_var_o(int, def, 3) = width; + return D_var_o(int, def, 1); +} + +static unsigned +spirv_TypeMatrix (unsigned col_type, unsigned columns, spirvctx_t *ctx) +{ + auto def = spirv_new_insn (SpvOpTypeMatrix, 4, ctx->types); + D_var_o(int, def, 1) = spirv_id (ctx); + D_var_o(int, def, 2) = col_type; + D_var_o(int, def, 3) = columns; + return D_var_o(int, def, 1); +} + +static unsigned +spirv_TypeStruct (const type_t *type, spirvctx_t *ctx) +{ + auto symtab = type->symtab; + int num_members = 0; + for (auto s = symtab->symbols; s; s = s->next) { + num_members++; + } + unsigned member_types[num_members]; + num_members = 0; + for (auto s = symtab->symbols; s; s = s->next) { + member_types[num_members++] = type_id (s->type, ctx); + } + + unsigned id = spirv_id (ctx); + auto def = spirv_new_insn (SpvOpTypeStruct, 2 + num_members, ctx->types); + D_var_o(int, def, 1) = id; + memcpy (&D_var_o(int, def, 2), member_types, sizeof (member_types)); + + num_members = 0; + for (auto s = symtab->symbols; s; s = s->next) { + int m = num_members++; + spirv_MemberName (id, m, s->name, ctx); + } + return id; +} + +static unsigned +spirv_TypeFunction (const type_t *type, spirvctx_t *ctx) +{ + int num_params = type->func.num_params; + if (num_params < 0) { + //FIXME no vararg functions? + num_params = ~num_params; + } + unsigned ret_type = type_id (type->func.ret_type, ctx); + unsigned param_types[num_params]; + for (int i = 0; i < num_params; i++) { + param_types[i] = type_id (type->func.param_types[i], ctx); + } + + auto def = spirv_new_insn (SpvOpTypeFunction, 3 + num_params, ctx->types); + D_var_o(int, def, 1) = spirv_id (ctx); + D_var_o(int, def, 2) = ret_type; + for (int i = 0; i < num_params; i++) { + D_var_o(int, def, 3 + i) = param_types[i]; + } + return D_var_o(int, def, 1); +} + +static unsigned +type_id (const type_t *type, spirvctx_t *ctx) +{ + if (type->id < ctx->type_ids.size && ctx->type_ids.a[type->id]) { + return ctx->type_ids.a[type->id]; + } + if (type->id >= ctx->type_ids.size) { + size_t base = ctx->type_ids.size; + DARRAY_RESIZE (&ctx->type_ids, type->id + 1); + while (base < type->id + 1) { + ctx->type_ids.a[base++] = 0; + } + } + unsigned id = 0; + if (is_void (type)) { + id = spirv_TypeVoid (ctx); + } else if (is_int (type)) { + id = spirv_TypeInt (32, true, ctx); + } else if (is_uint (type)) { + id = spirv_TypeInt (32, false, ctx); + } else if (is_float (type) && type->width == 1) { + id = spirv_TypeFloat (32, ctx); + } else if (is_float (type) && type_cols (type) == 1) { + unsigned fid = type_id (&type_float, ctx); + id = spirv_TypeVector (fid, type->width, ctx); + } else if (is_float (type)) { + auto ctype = vector_type (&type_float, type_rows (type)); + unsigned cid = type_id (ctype, ctx); + id = spirv_TypeMatrix (cid, type_cols (type), ctx); + } else if (is_vector (type)) { + // spir-v doesn't allow duplicate non-aggregate types, so emit + // vector as vec3 + auto vtype = vector_type (&type_float, 3); + id = type_id (vtype, ctx); + } else if (is_quaternion (type)) { + // spir-v doesn't allow duplicate non-aggregate types, so emit + // quaternion as vec4 + auto qtype = vector_type (&type_float, 4); + id = type_id (qtype, ctx); + } else if (is_struct (type)) { + id = spirv_TypeStruct (type, ctx); + } else if (is_func (type)) { + id = spirv_TypeFunction (type, ctx); + } + + ctx->type_ids.a[type->id] = id; + return id; +} + +static unsigned +spirv_Label (spirvctx_t *ctx) +{ + auto def = spirv_new_insn (SpvOpLabel, 2, ctx->code); + D_var_o(int, def, 1) = spirv_id (ctx); + return D_var_o(int, def, 1); +} + +static void +spirv_Unreachable (spirvctx_t *ctx) +{ + spirv_new_insn (SpvOpUnreachable, 1, ctx->code); +} + +static void +spirv_FunctionEnd (spirvctx_t *ctx) +{ + spirv_new_insn (SpvOpFunctionEnd, 1, ctx->code); +} + +static unsigned +spirv_FunctionParameter (const char *name, const type_t *type, spirvctx_t *ctx) +{ + unsigned id = spirv_id (ctx); + auto def = spirv_new_insn (SpvOpFunctionParameter, 3, ctx->code); + D_var_o(int, def, 1) = type_id (type, ctx); + D_var_o(int, def, 2) = id; + spirv_Name (id, name, ctx); + return id; +} + +static unsigned +spirv_function (function_t *func, spirvctx_t *ctx) +{ + unsigned func_id = spirv_id (ctx); + auto def = spirv_new_insn (SpvOpFunction, 5, ctx->code); + D_var_o(int, def, 1) = type_id (func->type->func.ret_type, ctx); + D_var_o(int, def, 2) = func_id; + D_var_o(int, def, 3) = 0; + D_var_o(int, def, 4) = type_id (func->type, ctx); + spirv_Name (func_id, GETSTR (func->s_name), ctx); + + for (auto p = func->parameters->symbols; p; p = p->next) { + spirv_FunctionParameter (p->name, p->type, ctx); + } + + if (!func->sblock) { + spirv_Label (ctx); + spirv_Unreachable (ctx); + } + for (auto sblock = func->sblock; sblock; sblock = sblock->next) { + if (!sblock->id) { + sblock->id = spirv_Label (ctx); + } + spirv_Unreachable (ctx); + } + spirv_FunctionEnd (ctx); + return func_id; +} + +static void +spirv_EntryPoint (unsigned func_id, const char *func_name, + SpvExecutionModel model, spirvctx_t *ctx) +{ + int len = strlen (func_name) + 1; + auto def = spirv_new_insn (SpvOpEntryPoint, 3 + RUP(len, 4) / 4, + ctx->linkage); + D_var_o(int, def, 1) = model; + D_var_o(int, def, 2) = func_id; + memcpy (&D_var_o(int, def, 3), func_name, len); +} + +bool +spirv_write (struct pr_info_s *pr, const char *filename) +{ + spirvctx_t ctx = { + .space = defspace_new (ds_backed), + .linkage = defspace_new (ds_backed), + .strings = defspace_new (ds_backed), + .names = defspace_new (ds_backed), + .types = defspace_new (ds_backed), + .code = defspace_new (ds_backed), + .type_ids = DARRAY_STATIC_INIT (64), + .id = 0, + }; + auto header = spirv_new_insn (0, 5, ctx.space); + D_var_o(int, header, 0) = SpvMagicNumber; + D_var_o(int, header, 1) = SpvVersion; + D_var_o(int, header, 2) = 0; // FIXME get a magic number for QFCC + D_var_o(int, header, 3) = 0; // Filled in later + D_var_o(int, header, 4) = 0; // Reserved + + //FIXME none of these should be hard-coded + spirv_Capability (SpvCapabilityShader, ctx.space); + //spirv_Capability (SpvCapabilityLinkage, ctx.space); + spirv_MemoryModel (SpvAddressingModelLogical, SpvMemoryModelGLSL450, + ctx.space); + + auto srcid = spirv_String (pr->src_name, &ctx); + spirv_Source (0, 1, srcid, nullptr, &ctx); + for (auto func = pr->func_head; func; func = func->next) + { + auto func_id = spirv_function (func, &ctx); + if (strcmp ("main", func->o_name) == 0) { + auto model = SpvExecutionModelVertex;//FIXME + spirv_EntryPoint (func_id, func->o_name, model, &ctx); + } + } + + auto main_sym = symtab_lookup (pr->symtab, "main"); + if (main_sym && main_sym->sy_type == sy_func) { + } + + D_var_o(int, header, 3) = ctx.id + 1; + defspace_add_data (ctx.space, ctx.linkage->data, ctx.linkage->size); + defspace_add_data (ctx.space, ctx.strings->data, ctx.strings->size); + defspace_add_data (ctx.space, ctx.names->data, ctx.names->size); + defspace_add_data (ctx.space, ctx.types->data, ctx.types->size); + defspace_add_data (ctx.space, ctx.code->data, ctx.code->size); + + QFile *file = Qopen (filename, "wb"); + Qwrite (file, ctx.space->data, ctx.space->size * sizeof (pr_type_t)); + Qclose (file); + return false; +}