quakeforge/tools/qfcc/source/target_rua.c
Bill Currie 92727019b5 [qfcc] Split up vector/quaternion compare by target
The v6 and v6p targets don't have horizontal operations, instead they
have direct vector/quaternion equality to float/int scalar result. Fixes
an ice when building game-source/quake.
2025-01-18 14:06:27 +09:00

384 lines
11 KiB
C

/*
target_rua.c
Ruamoko progs backend.
Copyright (C) 2024 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
#include "QF/va.h"
#include "QF/progs/pr_comp.h"
#include "tools/qfcc/include/codespace.h"
#include "tools/qfcc/include/debug.h"
#include "tools/qfcc/include/defspace.h"
#include "tools/qfcc/include/diagnostic.h"
#include "tools/qfcc/include/emit.h"
#include "tools/qfcc/include/flow.h"
#include "tools/qfcc/include/function.h"
#include "tools/qfcc/include/options.h"
#include "tools/qfcc/include/qfcc.h"
#include "tools/qfcc/include/statements.h"
#include "tools/qfcc/include/strpool.h"
#include "tools/qfcc/include/switch.h"
#include "tools/qfcc/include/symtab.h"
#include "tools/qfcc/include/target.h"
#include "tools/qfcc/include/type.h"
// standardized base register to use for all locals (arguments, local defs,
// params)
#define LOCALS_REG 1
// keep the stack aligned to 8 words (32 bytes) so lvec etc can be used without
// having to do shenanigans with mixed-alignment stack frames
#define STACK_ALIGN 8
static bool
ruamoko_value_too_large (const type_t *val_type)
{
return type_size (val_type) > MAX_DEF_SIZE;
}
static void
create_param (symtab_t *parameters, symbol_t *param)
{
defspace_t *space = parameters->space;
def_t *def = new_def (param->name, 0, space, sc_param);
int size = type_size (param->type);
int alignment = param->type->alignment;
if (alignment < 4) {
alignment = 4;
}
def->offset = defspace_alloc_aligned_highwater (space, size, alignment);
def->type = param->type;
param->def = def;
param->sy_type = sy_def;
param->lvalue = !def->readonly;
symtab_addsymbol (parameters, param);
if (is_vector(param->type) && options.code.vector_components)
init_vector_components (param, 0, parameters);
}
static void
ruamoko_build_scope (symbol_t *fsym)
{
function_t *func = fsym->metafunc->func;
for (param_t *p = fsym->params; p; p = p->next) {
symbol_t *param;
if (!p->selector && !p->type && !p->name) {
// ellipsis marker
param = new_symbol_type (".args", &type_va_list);
} else {
if (!p->type) {
continue; // non-param selector
}
if (is_void (p->type)) {
if (p->name) {
error (0, "invalid parameter type for %s", p->name);
} else if (p != fsym->params || p->next) {
error (0, "void must be the only parameter");
continue;
} else {
continue;
}
}
if (!p->name) {
error (0, "parameter name omitted");
p->name = save_string ("");
}
param = new_symbol_type (p->name, p->type);
}
create_param (func->parameters, param);
if (p->qual == pq_out) {
param->def->param = false;
param->def->out_param = true;
} else if (p->qual == pq_inout) {
param->def->out_param = true;
} else if (p->qual == pq_const) {
param->def->readonly = true;
}
param->def->reg = func->temp_reg;
}
}
static void
ruamoko_build_code (function_t *func, const expr_t *statements)
{
/* Create a function entry block to set up the stack frame and add the
* actual function code to that block. This ensure that the adjstk and
* with statements always come first, regardless of what ideas the
* optimizer gets.
*/
expr_t *e;
expr_t *entry = new_block_expr (0);
entry->loc = func->def->loc;
e = new_adjstk_expr (0, 0);
e->loc = entry->loc;
append_expr (entry, e);
e = new_with_expr (2, LOCALS_REG, new_short_expr (0));
e->loc = entry->loc;
append_expr (entry, e);
append_expr (entry, statements);
statements = entry;
/* Mark all local defs as using the base register used for stack
* references.
*/
func->temp_reg = LOCALS_REG;
for (def_t *def = func->locals->space->defs; def; def = def->next) {
if (def->local || def->param) {
def->reg = LOCALS_REG;
}
}
for (def_t *def = func->parameters->space->defs; def; def = def->next) {
if (def->local || def->param) {
def->reg = LOCALS_REG;
}
}
func->code = pr.code->size;
lineno_base = func->def->loc.line;
func->sblock = make_statements (statements);
if (options.code.optimize) {
flow_data_flow (func);
} else {
statements_count_temps (func->sblock);
}
emit_statements (func->sblock);
defspace_sort_defs (func->parameters->space);
defspace_sort_defs (func->locals->space);
defspace_t *space = defspace_new (ds_virtual);
if (func->arguments) {
func->arguments->size = func->arguments->max_size;
merge_spaces (space, func->arguments, STACK_ALIGN);
func->arguments = 0;
}
merge_spaces (space, func->locals->space, STACK_ALIGN);
func->locals->space = space;
// allocate 0 words to force alignment and get the address
func->params_start = defspace_alloc_aligned_highwater (space, 0,
STACK_ALIGN);
dstatement_t *st = &pr.code->code[func->code];
if (pr.code->size > func->code && st->op == OP_ADJSTK) {
if (func->params_start) {
st->b = -func->params_start;
} else {
// skip over adjstk so a zero adjustment doesn't get executed
func->code += 1;
}
}
merge_spaces (space, func->parameters->space, STACK_ALIGN);
func->parameters->space = space;
// force the alignment again so the full stack slot is counted when
// the final parameter is smaller than STACK_ALIGN words
defspace_alloc_aligned_highwater (space, 0, STACK_ALIGN);
}
static int
copy_elements (expr_t *block, const expr_t *dst, const expr_t *src, int base)
{
int index = 0;
for (auto li = src->vector.list.head; li; li = li->next) {
auto e = li->expr;
if (e->type == ex_vector) {
index += copy_elements (block, dst, e, index + base);
} else {
auto type = get_type (e);
auto dst_ele = new_offset_alias_expr (type, dst, index + base);
append_expr (block, assign_expr (dst_ele, e));
index += type_width (type);
}
}
return index;
}
static const expr_t *
ruamoko_assign_vector (const expr_t *dst, const expr_t *src)
{
expr_t *block = new_block_expr (0);
copy_elements (block, dst, src, 0);
block->block.result = dst;
return block;
}
static switch_block_t *switch_block;
const expr_t *
ruamoko_proc_switch (const expr_t *expr, rua_ctx_t *ctx)
{
scoped_src_loc (expr);
auto test = expr_process (expr->switchblock.test, ctx);
auto sb = switch_block;
switch_block = new_switch_block ();
switch_block->test = test;
auto body = expr_process (expr->switchblock.body, ctx);
auto break_label = expr->switchblock.break_label;
auto swtch = switch_expr (switch_block, break_label, body);
switch_block = sb;
return swtch;
}
const expr_t *
ruamoko_proc_caselabel (const expr_t *expr, rua_ctx_t *ctx)
{
scoped_src_loc (expr);
if (expr->caselabel.end_value) {
internal_error (expr, "case ranges not implemented");
}
auto value = expr_process (expr->caselabel.value, ctx);
if (is_error (value)) {
return value;
}
return case_label_expr (switch_block, value);
}
const expr_t *
ruamoko_field_array (const expr_t *e)
{
ex_list_t list = {};
// convert the left-branching field/array expression chain to a list
while (e->type == ex_field || e->type == ex_array) {
for (; e->type == ex_field; e = e->field.object) {
list_prepend (&list, e);
}
for (; e->type == ex_array; e = e->array.base) {
list_prepend (&list, e);
}
}
// e is now the base object of the field/array expression chain
int num_obj = list_count (&list);
const expr_t *objects[num_obj];
list_scatter (&list, objects);
for (int i = 0; i < num_obj; i++) {
auto obj = objects[i];
auto base_type = get_type (e);
if (obj->type == ex_field) {
if (obj->field.member->type != ex_symbol) {
internal_error (obj->field.member, "not a symbol");
}
auto sym = obj->field.member->symbol;
if (is_pointer (base_type)) {
auto offset = new_short_expr (sym->offset);
e = offset_pointer_expr (e, offset);
e = cast_expr (pointer_type (obj->field.type), e);
e = unary_expr ('.', e);
} else if (e->type == ex_uexpr && e->expr.op == '.') {
auto offset = new_short_expr (sym->offset);
e = offset_pointer_expr (e->expr.e1, offset);
e = cast_expr (pointer_type (obj->field.type), e);
e = unary_expr ('.', e);
} else {
e = new_offset_alias_expr (obj->field.type, e, sym->offset);
}
} else if (obj->type == ex_array) {
scoped_src_loc (obj->array.index);
int base_ind = 0;
if (is_array (base_type)) {
base_ind = base_type->array.base;
}
auto ele_type = obj->array.type;
auto base = new_int_expr (base_ind, false);
auto scale = new_int_expr (type_size (ele_type), false);
auto offset = binary_expr ('*', base, scale);
auto index = binary_expr ('*', obj->array.index, scale);
offset = binary_expr ('-', index, offset);
const expr_t *ptr;
if (is_array (base_type)) {
if (e->type == ex_uexpr && e->expr.op == '.') {
ptr = e->expr.e1;
} else {
auto alias = new_offset_alias_expr (ele_type, e, 0);
ptr = new_address_expr (ele_type, alias, 0);
}
} else if (is_nonscalar (base_type) || is_matrix (base_type)) {
auto alias = new_offset_alias_expr (ele_type, e, 0);
ptr = new_address_expr (ele_type, alias, 0);
} else {
ptr = e;
}
ptr = offset_pointer_expr (ptr, offset);
ptr = cast_expr (pointer_type (obj->field.type), ptr);
e = unary_expr ('.', ptr);
} else {
internal_error (obj, "what the what?!?");
}
}
return e;
}
const expr_t *
ruamoko_proc_address (const expr_t *expr, rua_ctx_t *ctx)
{
scoped_src_loc (expr);
auto e = expr_process (expr->expr.e1, ctx);
if (is_error (e)) {
return e;
}
if (e->type == ex_field || e->type == ex_array) {
e = ruamoko_field_array (e);
}
return address_expr (e, nullptr);
}
static const expr_t *
ruamoko_vector_compare (int op, const expr_t *e1, const expr_t *e2)
{
// both e1 and e2 should have the same types here
auto type = get_type (e1);
if (op != QC_EQ && op != QC_NE) {
return error (e2, "invalid comparison for %s", type->name);
}
int hop = op == QC_EQ ? '&' : '|';
auto e = new_binary_expr (op, e1, e2);
e->expr.type = bool_type (type);
e = new_horizontal_expr (hop, e, &type_int);
return e;
}
target_t ruamoko_target = {
.value_too_large = ruamoko_value_too_large,
.build_scope = ruamoko_build_scope,
.build_code = ruamoko_build_code,
.declare_sym = declare_def,
.initialized_temp = initialized_temp_expr,
.assign_vector = ruamoko_assign_vector,
.proc_switch = ruamoko_proc_switch,
.proc_caselabel = ruamoko_proc_caselabel,
.proc_address = ruamoko_proc_address,
.vector_compare = ruamoko_vector_compare,
};