quakeforge/tools/qfcc/source/spirv_grammar.c
Bill Currie 99b1859aac [qfcc] Parse the spirv grammar json files
I really don't like the way they're included (I'm really looking forward
to #embed, but gotta wait for gcc 15), and I'm a tad grumpy that the
documentation for them
(https://registry.khronos.org/SPIR-V/specs/unified1/MachineReadableGrammar.html)
is wrong (missing fields), but I think I like the result.

The grammars (core and glsl.std.450) are parsed into structs that should
be fairly easy to interpret: the instructions, kinds, and enumerant
values are sorted by name for search with bsearch. Having the data
parsed in means source code can refer to the items by name rather than
magic numbers, which will be very nice for intrinsics and image types
(and probably a few other things).
2025-01-07 03:26:12 +09:00

435 lines
12 KiB
C

/*
spirv_grammar.c
SPIR-V grammar json embedding
Copyright (C) 2024 Bill Currie
Author: Bill Currie <bill@taniwha.org>
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
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <stdlib.h>
#include "QF/heapsort.h"
#include "QF/plist.h"
#include "QF/sys.h"
#include "QF/va.h"
#include "tools/qfcc/include/qfcc.h"
#include "tools/qfcc/include/diagnostic.h"
#include "tools/qfcc/include/expr.h"
#include "tools/qfcc/include/options.h"
#include "tools/qfcc/include/shared.h"
#include "tools/qfcc/include/strpool.h"
#include "tools/qfcc/include/struct.h"
#include "tools/qfcc/include/symtab.h"
#include "tools/qfcc/include/spirv.h"
#include "tools/qfcc/include/spirv_grammar.h"
#include "tools/qfcc/include/type.h"
#include "tools/qfcc/include/value.h"
typedef struct spirv_json_s {
struct spirv_json_s *parent;
const char *name;
const char *json;
spirv_grammar_t *grammar;
} spirv_json_t;
static spirv_json_t builtin_json[] = {
{ .name = "core",
.json =
#include "tools/qfcc/source/spirv.core.grammar.jinc"
},
{ .parent = &builtin_json[0],
.name = "GLSL.std.450",
.json =
#include "tools/qfcc/source/extinst.glsl.std.450.grammar.jinc"
},
{}
};
static void *
spvg_alloc (void *data, size_t size)
{
return malloc (size);
}
typedef struct parse_string_s {
size_t value_offset;
} parse_string_t;
typedef struct parse_array_s {
pltype_t type;
size_t stride;
plparser_t parser;
void *data;
size_t value_offset;
size_t size_offset;
__compar_fn_t cmp;
} parse_array_t;
static int
parse_char (const plfield_t *field, const plitem_t *item,
void *data, plitem_t *messages, void *context)
{
auto value = (char *) data;
const char *str = PL_String (item);
*value = *str;
return 1;
}
static int
parse_hex (const plfield_t *field, const plitem_t *item,
void *data, plitem_t *messages, void *context)
{
auto value = (uint32_t *) data;
const char *str = PL_String (item);
char *end = 0;
*value = strtol (str, &end, 0);
if (*end) {
PL_Message (messages, item, "error parsing %s: %s", field->name, str);
}
return !*end;
}
static int
parse_uint32_t (const plfield_t *field, const plitem_t *item,
void *data, plitem_t *messages, void *context)
{
auto value = (uint32_t *) data;
*value = PL_Number (item);
return 1;
}
static int
parse_value (const plfield_t *field, const plitem_t *item,
void *data, plitem_t *messages, void *context)
{
if (PL_Type (item) == QFString) {
return parse_hex (field, item, data, messages, context);
} else {
return parse_uint32_t (field, item, data, messages, context);
}
}
static parse_string_t parse_string_array = { 0 };
static int
parse_string (const plfield_t *field, const plitem_t *item,
void *data, plitem_t *messages, void *context)
{
auto string = (parse_string_t *) field->data;
auto value = (const char **) ((byte *)data + string->value_offset);
const char *str = PL_String (item);
*value = save_string (str);
return 1;
}
static int
parse_array (const plfield_t *field, const plitem_t *item,
void *data, plitem_t *messages, void *context)
{
auto array = (parse_array_t *) field->data;
auto value = (void **) ((byte *)data + array->value_offset);
auto size = (uint32_t *) ((byte *)data + array->size_offset);
plelement_t element = {
array->type,
array->stride,
spvg_alloc,
array->parser,
array->data,
};
plfield_t f = { 0, 0, 0, 0, &element };
typedef struct arr_s DARRAY_TYPE(byte) arr_t;
arr_t *arr;
if (!PL_ParseArray (&f, item, &arr, messages, context)) {
return 0;
}
*value = arr->a;
if ((void *) size >= data) {
*size = arr->size;
}
if (array->cmp) {
heapsort (*value, *size, array->stride, array->cmp);
}
return 1;
}
static int
parse_struct (const plfield_t *field, const plitem_t *dict,
void *data, plitem_t *messages, void *context)
{
return PL_ParseStruct (field->data, dict, data, messages, context);
}
static int
parse_ignore (const plfield_t *field, const plitem_t *item,
void *data, plitem_t *messages, void *context)
{
return 1;
}
static int
spirv_enumerant_cmp (const void *_a, const void *_b)
{
auto a = (const spirv_enumerant_t *) _a;
auto b = (const spirv_enumerant_t *) _b;
return strcmp (a->enumerant, b->enumerant);
}
static int
spirv_instruction_cmp (const void *_a, const void *_b)
{
auto a = (const spirv_instruction_t *) _a;
auto b = (const spirv_instruction_t *) _b;
return strcmp (a->opname, b->opname);
}
static int
spirv_kind_cmp (const void *_a, const void *_b)
{
auto a = (const spirv_kind_t *) _a;
auto b = (const spirv_kind_t *) _b;
return strcmp (a->kind, b->kind);
}
static plfield_t spirv_operand_fields[] = {
{"kind", offsetof (spirv_operand_t, kind), QFString, parse_string, &parse_string_array},
{"quantifier", offsetof (spirv_operand_t, quantifier), QFString, parse_char, nullptr},
{"name", offsetof (spirv_operand_t, name), QFString, parse_string, &parse_string_array},
{ }
};
static parse_array_t parse_ecapability_data = {
.type = QFString,
.stride = sizeof (const char *),
.parser = parse_string,
.data = &parse_string_array,
.value_offset = offsetof (spirv_enumerant_t, capabilities),
.size_offset = offsetof (spirv_enumerant_t, num_capabilities),
};
static parse_array_t parse_eextension_data = {
.type = QFString,
.stride = sizeof (const char *),
.parser = parse_string,
.data = &parse_string_array,
.value_offset = offsetof (spirv_enumerant_t, extensions),
.size_offset = offsetof (spirv_enumerant_t, num_extensions),
};
static parse_array_t parse_parameter_data = {
.type = QFDictionary,
.stride = sizeof (spirv_operand_t),
.parser = parse_struct,
.data = &spirv_operand_fields,
.value_offset = offsetof (spirv_enumerant_t, parameters),
.size_offset = offsetof (spirv_enumerant_t, num_parameters),
};
static plfield_t spirv_enumerant_fields[] = {
{"enumerant", offsetof (spirv_enumerant_t, enumerant), QFString, parse_string, &parse_string_array},
{"value", offsetof (spirv_enumerant_t, value), QFMultiType | (1 << QFString) | (1 << QFNumber), parse_value, nullptr},
{"capabilities", 0, QFArray, parse_array, &parse_ecapability_data},
{"extensions", 0, QFArray, parse_array, &parse_eextension_data},
{"parameters", 0, QFArray, parse_array, &parse_parameter_data},
{"version", offsetof (spirv_enumerant_t, version), QFString, parse_string, &parse_string_array},
{"lastVersion", offsetof (spirv_enumerant_t, lastVersion), QFString, parse_string, &parse_string_array},
{ }
};
static parse_array_t parse_enumerant_data = {
.type = QFDictionary,
.stride = sizeof (spirv_enumerant_t),
.parser = parse_struct,
.data = &spirv_enumerant_fields,
.value_offset = offsetof (spirv_kind_t, enumerants),
.size_offset = offsetof (spirv_kind_t, num),
.cmp = spirv_enumerant_cmp,
};
static parse_array_t parse_base_data = {
.type = QFString,
.stride = sizeof (const char *),
.parser = parse_string,
.data = &parse_string_array,
.value_offset = offsetof (spirv_kind_t, bases),
.size_offset = offsetof (spirv_kind_t, num),
};
static plfield_t spirv_kind_fields[] = {
{"category", offsetof (spirv_kind_t, category), QFString, parse_string, &parse_string_array},
{"kind", offsetof (spirv_kind_t, kind), QFString, parse_string, &parse_string_array},
{"doc", offsetof (spirv_kind_t, doc), QFString, parse_string, &parse_string_array},
{"enumerants", 0, QFArray, parse_array, &parse_enumerant_data},
{"bases", 0, QFArray, parse_array, &parse_base_data},
};
static parse_array_t parse_operand_data = {
.type = QFDictionary,
.stride = sizeof (spirv_operand_t),
.parser = parse_struct,
.data = &spirv_operand_fields,
.value_offset = offsetof (spirv_instruction_t, operands),
.size_offset = offsetof (spirv_instruction_t, num_operands),
};
static parse_array_t parse_capability_data = {
.type = QFString,
.stride = sizeof (const char *),
.parser = parse_string,
.data = &parse_string_array,
.value_offset = offsetof (spirv_instruction_t, capabilities),
.size_offset = offsetof (spirv_instruction_t, num_capabilities),
};
static parse_array_t parse_extension_data = {
.type = QFString,
.stride = sizeof (const char *),
.parser = parse_string,
.data = &parse_string_array,
.value_offset = offsetof (spirv_instruction_t, extensions),
.size_offset = offsetof (spirv_instruction_t, num_extensions),
};
static plfield_t spirv_instruction_fields[] = {
{"opname", offsetof (spirv_instruction_t, opname), QFString, parse_string, &parse_string_array},
{"opcode", offsetof (spirv_instruction_t, opcode), QFNumber, parse_uint32_t, nullptr},
{"operands", 0, QFArray, parse_array, &parse_operand_data},
{"capabilities", 0, QFArray, parse_array, &parse_capability_data},
{"extensions", 0, QFArray, parse_array, &parse_extension_data},
{"version", offsetof (spirv_instruction_t, version), QFString, parse_string, &parse_string_array},
{"lastVersion", offsetof (spirv_instruction_t, lastVersion), QFString, parse_string, &parse_string_array},
{"class", 0, QFString, parse_ignore, nullptr},
{ }
};
static parse_array_t parse_copyright_data = {
.type = QFString,
.stride = sizeof (const char *),
.parser = parse_string,
.data = &parse_string_array,
.value_offset = offsetof (spirv_grammar_t, copyright),
.size_offset = offsetof (spirv_grammar_t, num_copyright),
};
static parse_array_t parse_instruction_data = {
.type = QFDictionary,
.stride = sizeof (spirv_instruction_t),
.parser = parse_struct,
.data = spirv_instruction_fields,
.value_offset = offsetof (spirv_grammar_t, instructions),
.size_offset = offsetof (spirv_grammar_t, num_instructions),
.cmp = spirv_instruction_cmp,
};
static parse_array_t parse_operand_kind_data = {
.type = QFDictionary,
.stride = sizeof (spirv_kind_t),
.parser = parse_struct,
.data = spirv_kind_fields,
.value_offset = offsetof (spirv_grammar_t, operand_kinds),
.size_offset = offsetof (spirv_grammar_t, num_operand_kinds),
.cmp = spirv_kind_cmp,
};
static bool built;
static plfield_t spirv_grammar_fields[] = {
{"copyright", 0, QFArray, parse_array, &parse_copyright_data},
{"magic_number", offsetof (spirv_grammar_t, magic_number), QFString, parse_hex, nullptr},
{"major_version", offsetof (spirv_grammar_t, major_version), QFNumber, parse_uint32_t, nullptr},
{"minor_version", offsetof (spirv_grammar_t, minor_version), QFNumber, parse_uint32_t, nullptr},
{"version", offsetof (spirv_grammar_t, version), QFNumber, parse_uint32_t, nullptr},
{"revision", offsetof (spirv_grammar_t, revision), QFNumber, parse_uint32_t, nullptr},
{"instructions", 0, QFArray, parse_array, &parse_instruction_data},
{"operand_kinds", 0, QFArray, parse_array, &parse_operand_kind_data},
{"instruction_printing_class", 0, QFArray, parse_ignore, nullptr},
{ }
};
static bool
parse_grammar (plitem_t *plitem, spirv_grammar_t **grammar)
{
spirv_grammar_t g = {};
auto messages = PL_NewArray ();
bool ret = PL_ParseStruct (spirv_grammar_fields, plitem, &g, messages,
nullptr);
if (!ret) {
for (int i = 0; i < PL_A_NumObjects (messages); i++) {
fprintf (stderr, "%s\n",
PL_String (PL_ObjectAtIndex (messages, i)));
}
} else {
*grammar = malloc (sizeof (**grammar));
**grammar = g;
}
PL_Release (messages);
return ret;
}
static void
build_grammars (void)
{
for (int i = 0; builtin_json[i].name; i++) {
auto plitem = PL_ParseJSON (builtin_json[i].json, nullptr);
if (!plitem) {
internal_error (0, "could not parse JSON for %s",
builtin_json[i].name);
}
if (!parse_grammar (plitem, &builtin_json[i].grammar)) {
internal_error (0, "could not parse grammar spec for %s",
builtin_json[i].name);
}
}
for (int i = 0; builtin_json[i].name; i++) {
auto parent = builtin_json[i].parent;
if (parent) {
builtin_json[i].grammar->parent = parent->grammar;
}
}
}
const plitem_t *
spirv_operand_kind (const char *set, const char *kind)
{
if (!built) {
build_grammars ();
built = true;
}
return nullptr;
}