[qfcc] Implement parameter qualifiers in Ruamoko

Now parameters can be declared `const`, `@in`, `@out`, `@inout`. `@in`
is redundant as it's the default, but I guess it's nice for
self-documenting code. `const` marks the parameter as read-only in the
function, `@out` and `@inout` allow the parameter to pass the value back
out (by copy), but `@out` does not initialize the parameter before
calling and returning without setting an `@out` parameter is an error
(but unfortunately, currently detected only when optimizing).

Unfortunately, it seems to have broken (only!) v6 progs when optimizing
as the second parameter gets optimized out.
This commit is contained in:
Bill Currie 2024-09-03 17:59:56 +09:00
parent 4dd461d1e6
commit b58e7791fd
16 changed files with 269 additions and 30 deletions

View file

@ -104,6 +104,7 @@ typedef struct def_s {
bool external:1; ///< externally declared def
bool local:1; ///< function local def
bool param:1; ///< function param def
bool out_param:1; ///< function out param def
bool argument:1; ///< function argument def
bool system:1; ///< system def
bool nosave:1; ///< don't set DEF_SAVEGLOBAL

View file

@ -265,6 +265,11 @@ typedef struct {
const type_t *ret_type; ///< void for non-call
} ex_branch_t;
typedef struct {
const expr_t *in; ///< source expression for arg
const expr_t *out; ///< destination expression for arg
} ex_inout_t;
typedef struct {
const expr_t *ret_val;
int at_return; ///< return void_return call through void
@ -348,6 +353,7 @@ typedef struct expr_s {
ex_address_t address; ///< alias expr params
ex_assign_t assign; ///< assignment expr params
ex_branch_t branch; ///< branch expr params
ex_inout_t inout; ///< inout arg params
ex_return_t retrn; ///< return expr params
ex_adjstk_t adjstk; ///< stack adjust param
ex_with_t with; ///< with expr param

View file

@ -58,6 +58,7 @@ EX_EXPR(alias) ///< view expression as different type (::ex_alias_t)
EX_EXPR(address) ///< address of an lvalue expression (::ex_address_t)
EX_EXPR(assign) ///< assignment of src expr to dst expr (::ex_assing_t)
EX_EXPR(branch) ///< branch expression (::ex_branch_t)
EX_EXPR(inout) ///< inout arg expression (::ex_inout_t)
EX_EXPR(return) ///< return expression (::ex_return_t)
EX_EXPR(adjstk) ///< stack adjust expression (::ex_adjstk_t)
EX_EXPR(with) ///< with expression (::ex_with_t)

View file

@ -163,6 +163,13 @@ typedef struct metafunc_s {
extern function_t *current_func;
typedef enum param_qual_e {
pq_const,
pq_in,
pq_out,
pq_inout,
} param_qual_t;
/** Representation of a function parameter.
\note The first two fields match the first two fields of keywordarg_t
in method.h
@ -173,6 +180,7 @@ typedef struct param_s {
const type_t *type;
const expr_t *type_expr;
const char *name;
param_qual_t qual;
} param_t;
typedef struct expr_s expr_t;

View file

@ -49,6 +49,7 @@ typedef enum storage_class_e {
sc_in,
sc_out,
sc_inout,
sc_uniform,
sc_buffer,
sc_shared,

View file

@ -35,10 +35,12 @@
#include "specifier.h"
typedef enum param_qual_e param_qual_t;
typedef struct ty_func_s {
const struct type_s *ret_type;
int num_params;
const struct type_s **param_types;
param_qual_t *param_quals;
union {
struct {
unsigned no_va_list:1;///< don't inject va_list for ... function

View file

@ -85,6 +85,7 @@ type_t type_SEL = {
{{&type_selector}},
};
const type_t *IMP_params[] = { &type_id, &type_SEL };
param_qual_t IMP_quals[] = { pq_in, pq_in };
type_t type_IMP = {
.type = ev_func,
.name = "IMP",
@ -92,7 +93,13 @@ type_t type_IMP = {
.width = 1,
.columns = 1,
.meta = ty_basic,
{{&type_id, -3, IMP_params, .no_va_list = 1}},
.func = {
.ret_type = &type_id,
.num_params = -3,
.param_types = IMP_params,
.param_quals = IMP_quals,
.no_va_list = 1,
},
};
type_t type_super = {
.type = ev_invalid,
@ -106,6 +113,7 @@ type_t type_SuperPtr = {
{{&type_super}},
};
const type_t *supermsg_params[] = { &type_SuperPtr, &type_SEL };
param_qual_t supermsg_quals[] = { pq_in, pq_in };
type_t type_supermsg = {
.type = ev_func,
.name = ".supermsg",
@ -113,7 +121,12 @@ type_t type_supermsg = {
.width = 1,
.columns = 1,
.meta = ty_basic,
{{&type_id, -3, supermsg_params}},
.func = {//FIXME is this right?
.ret_type = &type_id,
.num_params = -3,
.param_types = supermsg_params,
.param_quals = supermsg_quals,
},
};
type_t type_method = {
.type = ev_invalid,
@ -146,13 +159,19 @@ type_t type_moduleptr = {
const type_t *obj_exec_class_params[] = {
&type_moduleptr,
};
param_qual_t obj_exec_class_quals[] = { pq_in };
type_t type_exec_class = {
.type = ev_func,
.alignment = 1,
.width = 1,
.columns = 1,
.meta = ty_basic,
{{&type_void, 1, obj_exec_class_params}},
.func = {
.ret_type = &type_void,
.num_params = 1,
.param_types = obj_exec_class_params,
.param_quals = obj_exec_class_quals,
},
};
// the cast of 1 in the init is to ensure pointers to incomplete types
// are never misidentified as id. It will be set to the correct value

View file

@ -95,6 +95,7 @@ set_storage_bits (def_t *def, storage_class_t storage)
case sc_local:
def->local = true;
break;
case sc_inout:
case sc_param:
def->local = true;
def->param = true;

View file

@ -123,6 +123,12 @@ get_type (const expr_t *e)
const type_t *type = 0;
e = convert_name (e);
switch (e->type) {
case ex_inout:
if (!e->inout.out) {
internal_error (e, "inout with no out");
}
type = get_type (e->inout.out);
break;
case ex_branch:
type = e->branch.ret_type;
break;
@ -1730,6 +1736,9 @@ has_function_call (const expr_t *e)
return 0;
}
return has_function_call (e->branch.test);
case ex_inout:
// in is just a cast of out, if it's not null
return has_function_call (e->inout.out);
case ex_return:
return has_function_call (e->retrn.ret_val);
case ex_horizontal:
@ -1887,6 +1896,7 @@ unary_expr (int op, const expr_t *e)
return edag_add_expr (n);
}
case ex_branch:
case ex_inout:
return error (e, "invalid type for unary -");
case ex_expr:
case ex_bool:
@ -2015,6 +2025,7 @@ unary_expr (int op, const expr_t *e)
case ex_multivec:
return algebra_dual (e);
case ex_branch:
case ex_inout:
case ex_nil:
return error (e, "invalid type for unary !");
case ex_count:
@ -2087,6 +2098,7 @@ unary_expr (int op, const expr_t *e)
return error (e, "invalid type for unary ~");
goto bitnot_expr;
case ex_branch:
case ex_inout:
return error (e, "invalid type for unary ~");
case ex_expr:
case ex_bool:
@ -2161,7 +2173,7 @@ build_function_call (const expr_t *fexpr, const type_t *ftype, const expr_t *par
expr_t *call;
const expr_t *err = 0;
int arg_count = params ? list_count (&params->list) :0;
int arg_count = params ? list_count (&params->list) : 0;
const expr_t *arguments[arg_count + 1];
if (params) {
list_scatter_rev (&params->list, arguments);
@ -2231,17 +2243,42 @@ build_function_call (const expr_t *fexpr, const type_t *ftype, const expr_t *par
" value", i + 1);
}
if (i < param_count) {
auto param_type = ftype->func.param_types[i];
if (e->type == ex_nil)
e = convert_nil (e, t = ftype->func.param_types[i]);
e = convert_nil (e, t = param_type);
if (e->type == ex_bool)
e = convert_from_bool (e, ftype->func.param_types[i]);
if (e->type == ex_error)
return e;
if (!type_assignable (ftype->func.param_types[i], t)) {
err = param_mismatch (e, i + 1, fexpr->symbol->name,
ftype->func.param_types[i], t);
e = convert_from_bool (e, param_type);
if (e->type == ex_error) {
err = e;
continue;
}
t = ftype->func.param_types[i];
auto param_qual = ftype->func.param_quals[i];
if (param_qual == pq_out || param_qual == pq_inout) {
//FIXME should be able to use something like *foo() as
//an out or inout arg
if (!is_lvalue (e) || has_function_call (e)) {
error (e, "lvalue required for %s parameter",
param_qual & pq_in ? "inout" : "out");
}
if (param_qual == pq_inout
&& !type_assignable (param_type, t)) {
err = param_mismatch (e, i + 1, fexpr->symbol->name,
param_type, t);
}
// check assignment FROM parameter is ok, but only if
// there wasn't an earlier error so param mismatch doesn't
// get double-reported for inout params.
if (!err && !type_assignable (t, param_type)) {
err = param_mismatch (e, i + 1, fexpr->symbol->name,
t, param_type);
}
} else {
if (!type_assignable (param_type, t)) {
err = param_mismatch (e, i + 1, fexpr->symbol->name,
param_type, t);
}
}
t = param_type;
} else {
if (e->type == ex_nil)
e = convert_nil (e, t = type_nil);
@ -2285,6 +2322,7 @@ build_function_call (const expr_t *fexpr, const type_t *ftype, const expr_t *par
expr_t *args = new_list_expr (0);
// args is built in reverse order so it matches params
for (int i = 0; i < arg_count; i++) {
auto param_qual = i < param_count ? ftype->func.param_quals[i] : pq_in;
if (emit_args && i == param_count) {
expr_prepend_expr (args, new_args_expr ());
emit_args = false;
@ -2308,7 +2346,20 @@ build_function_call (const expr_t *fexpr, const type_t *ftype, const expr_t *par
arg_exprs[arg_expr_count][1] = tmp;
arg_expr_count++;
} else {
e = cast_expr (arg_types[i], e);
if (param_qual != pq_out) {
// out parameters do not need to be sent so no need to cast
}
if (param_qual & pq_out) {
auto inout = new_expr ();
inout->type = ex_inout;
if (param_qual == pq_inout) {
inout->inout.in = cast_expr (arg_types[i], e);
}
inout->inout.out = e;
e = inout;
} else {
e = cast_expr (arg_types[i], e);
}
expr_prepend_expr (args, e);
}
}

View file

@ -92,7 +92,7 @@ is_lvalue (const expr_t *expr)
case sy_name:
break;
case sy_var:
return 1;
return !expr->symbol->def->readonly;
case sy_const:
break;
case sy_type:
@ -130,6 +130,7 @@ is_lvalue (const expr_t *expr)
}
break;
case ex_branch:
case ex_inout:
case ex_memset:
case ex_compound:
case ex_state:

View file

@ -72,6 +72,7 @@ edag_add_expr (const expr_t *expr)
case ex_compound:
case ex_memset:
case ex_branch:
case ex_inout:
case ex_return:
case ex_adjstk:
case ex_with:

View file

@ -1413,7 +1413,9 @@ flow_uninit_scan_statements (flownode_t *node, set_t *defs, set_t *uninit)
} else {
def_t *def = flowvar_get_def (var);
if (def) {
if (options.warnings.uninited_variable) {
if (def->out_param) {
error (st->expr, "%s not initialized", def->name);
} else if (options.warnings.uninited_variable) {
warning (st->expr, "%s may be used uninitialized",
def->name);
}

View file

@ -361,6 +361,7 @@ new_param (const char *selector, const type_t *type, const char *name)
.selector = selector,
.type = find_type (type),
.name = name,
.qual = pq_in,
};
return param;
}
@ -374,6 +375,7 @@ new_generic_param (const expr_t *type_expr, const char *name)
*param = (param_t) {
.type_expr = type_expr,
.name = name,
.qual = pq_in,
};
return param;
}
@ -469,7 +471,8 @@ parse_params (const type_t *return_type, param_t *parms)
}
}
if (count) {
new->func.param_types = malloc (count * sizeof (type_t));
new->func.param_types = malloc (count * sizeof (type_t *));
new->func.param_quals = malloc (count * sizeof (param_qual_t));
}
for (p = parms; p; p = p->next) {
if (!p->selector && !p->type && !p->name) {
@ -483,6 +486,7 @@ parse_params (const type_t *return_type, param_t *parms)
}
auto ptype = unalias_type (p->type);
new->func.param_types[new->func.num_params] = ptype;
new->func.param_quals[new->func.num_params] = p->qual;
new->func.num_params++;
}
}
@ -989,6 +993,14 @@ build_v6p_scope (symbol_t *fsym)
}
param = new_symbol_type (p->name, p->type);
initialize_def (param, 0, parameters->space, sc_param, locals);
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;
}
i++;
}
@ -1051,6 +1063,14 @@ build_rua_scope (symbol_t *fsym)
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;
}
}

View file

@ -107,6 +107,7 @@ glsl_declare_block (specifier_t spec, symbol_t *block_sym,
case sc_param:
case sc_local:
case sc_argument:
case sc_inout:
break;
}
if (!block_tab) {

View file

@ -520,15 +520,45 @@ make_ellipsis (void)
static param_t *
make_param (specifier_t spec)
{
//FIXME should not be sc_global
if (spec.storage == sc_global) {
spec.storage = sc_param;
}
param_t *param;
if (spec.type_expr) {
auto param = new_generic_param (spec.type_expr, spec.sym->name);
return param;
param = new_generic_param (spec.type_expr, spec.sym->name);
} else {
spec = default_type (spec, spec.sym);
spec.type = find_type (append_type (spec.sym->type, spec.type));
auto param = new_param (0, spec.type, spec.sym->name);
return param;
param = new_param (0, spec.type, spec.sym->name);
}
if (spec.is_const) {
if (spec.storage == sc_out) {
error (0, "cannot use const with @out");
} else if (spec.storage == sc_inout) {
error (0, "cannot use const with @inout");
} else {
if (spec.storage != sc_in && spec.storage != sc_param) {
internal_error (0, "unexpected parameter storage: %d",
spec.storage);
}
}
param->qual = pq_const;
} else {
if (spec.storage == sc_out) {
param->qual = pq_out;
} else if (spec.storage == sc_inout) {
param->qual = pq_inout;
} else {
if (spec.storage != sc_in && spec.storage != sc_param) {
internal_error (0, "unexpected parameter storage: %d",
spec.storage);
}
param->qual = pq_in;
}
}
return param;
}
static param_t *
@ -2776,6 +2806,7 @@ static keyword_t at_keywords[] = {
{"sizeof", QC_SIZEOF },
{"not", QC_NOT },
{"auto", QC_TYPE_SPEC, .spec = { .type = &type_auto } },
{"const", QC_TYPE_QUAL, .spec = { .is_const = true } },
};
// These keywords require the QuakeForge VM to be of any use. ie, they cannot
@ -2793,6 +2824,9 @@ static keyword_t qf_keywords[] = {
{"@va_list", QC_TYPE_SPEC, .spec = { .type = &type_va_list } },
{"@param", QC_TYPE_SPEC, .spec = { .type = &type_param } },
{"@return", QC_AT_RETURN, },
{"@in", QC_TYPE_QUAL, .spec = { .storage = sc_in } },
{"@out", QC_TYPE_QUAL, .spec = { .storage = sc_out } },
{"@inout", QC_TYPE_QUAL, .spec = { .storage = sc_inout } },
{"@hadamard", QC_HADAMARD, },
{"@cross", QC_CROSS, },

View file

@ -1099,6 +1099,12 @@ expr_call_v6p (sblock_t *sblock, const expr_t *call, operand_t **op)
continue;
}
ind--;
if (a->type == ex_inout) {
a = a->inout.in;
if (!a) {
continue;
}
}
param = new_param_expr (get_type (a), ind);
if (count && options.code.progsversion != PROG_ID_VERSION && ind < 2) {
pref = "r";
@ -1148,8 +1154,26 @@ expr_call_v6p (sblock_t *sblock, const expr_t *call, operand_t **op)
*op = return_operand (call->branch.ret_type, call);
}
sblock_add_statement (sblock, s);
sblock->next = new_sblock ();
return sblock->next;
ind = count;
for (auto li = args->list.head; li; li = li->next) {
auto a = li->expr;
if (a->type != ex_args) {
ind--;
}
if (a->type != ex_inout) {
continue;
}
a = a->inout.out;
param = new_param_expr (get_type (a), ind);
operand_t *p = nullptr;
sblock = statement_subexpr (sblock, param, &p);
operand_t *arg = nullptr;
sblock = statement_subexpr (sblock, a, &arg);
s = assign_statement (arg, p, a);
sblock_add_statement (sblock, s);
}
return sblock;
}
static sblock_t *
@ -1167,6 +1191,7 @@ expr_call (sblock_t *sblock, const expr_t *call, operand_t **op)
const expr_t *args_params = 0; // first arg in ...
operand_t *use = 0;
operand_t *kill = 0;
operand_t *define = nullptr;
int num_params = 0;
defspace_reset (arg_space);
@ -1174,6 +1199,7 @@ expr_call (sblock_t *sblock, const expr_t *call, operand_t **op)
int num_args = list_count (&call->branch.args->list);
const expr_t *args[num_args + 1];
def_t *out_args[num_args + 1] = {};
list_scatter_rev (&call->branch.args->list, args);
int arg_num = 0;
for (int i = 0; i < num_args; i++) {
@ -1191,6 +1217,9 @@ expr_call (sblock_t *sblock, const expr_t *call, operand_t **op)
alignment);
def->type = arg_type;
def->reg = current_func->temp_reg;
if (a->type == ex_inout) {
out_args[i] = def;
}
const expr_t *def_expr = new_def_expr (def);
if (a->type == ex_args) {
args_va_list = def_expr;
@ -1201,20 +1230,35 @@ expr_call (sblock_t *sblock, const expr_t *call, operand_t **op)
if (args_va_list) {
num_params++;
}
const expr_t *assign = assign_expr (def_expr, a);
sblock = statement_single (sblock, assign);
auto src = a;
if (a->type == ex_inout) {
src = a->inout.in;
}
if (src) {
const expr_t *assign = assign_expr (def_expr, src);
sblock = statement_single (sblock, assign);
}
}
// The call both uses and kills the arguments: use is obvious, but kill
// is because the callee has direct access to them and might modify
// them
// need two ops for the one def because there's two lists
operand_t *u = def_operand (def, arg_type, call);
operand_t *k = def_operand (def, arg_type, call);
u->next = use;
use = u;
k->next = kill;
kill = k;
if (a->type != ex_inout || a->inout.in) {
auto u = def_operand (def, arg_type, call);
u->next = use;
use = u;
}
if (a->type != ex_inout) {
auto k = def_operand (def, arg_type, call);
k->next = kill;
kill = k;
} else {
// inout and out params define the argument
auto d = def_operand (def, arg_type, call);
d->next = define;
define = d;
}
}
if (args_va_list) {
const expr_t *assign;
@ -1249,7 +1293,25 @@ expr_call (sblock_t *sblock, const expr_t *call, operand_t **op)
}
s->use = use;
s->kill = kill;
s->def = define;
sblock_add_statement (sblock, s);
for (int i = 0; i < num_args; i++) {
auto a = args[i];
auto out = out_args[i];
if (a->type != ex_inout) {
continue;
}
auto dst_expr = a->inout.out;
if (!dst_expr) {
internal_error (dst_expr, "inout with no out");
}
if (!out) {
internal_error (a, "no arg def for %d", i);
}
const expr_t *out_expr = new_def_expr (out);
const expr_t *assign = assign_expr (dst_expr, out_expr);
sblock = statement_single (sblock, assign);
}
return sblock;
}
@ -1457,10 +1519,37 @@ statement_return (sblock_t *sblock, const expr_t *e)
{
const char *opcode;
statement_t *s;
operand_t *use = nullptr;
scoped_src_loc (e);
debug (e, "RETURN");
opcode = "return";
if (current_func) {
bool v6p = options.code.progsversion < PROG_VERSION;
auto parameters = v6p ? current_func->locals : current_func->parameters;
int ind = 0;
for (auto p = parameters->symbols; p; p = p->next, ind++) {
if (p->sy_type != sy_var || !p->def->out_param) {
continue;
}
auto type = p->def->type;
if (v6p) {
auto param = new_param_expr (type, ind);
operand_t *p_op = nullptr;
sblock = statement_subexpr (sblock, param, &p_op);
auto a = new_def_expr (p->def);
operand_t *out = nullptr;
sblock = statement_subexpr (sblock, a, &out);
s = assign_statement (p_op, out, e);
sblock_add_statement (sblock, s);
} else {
auto u = def_operand (p->def, type, e);
u->next = use;
use = u;
}
}
}
if (!e->retrn.ret_val) {
if (options.code.progsversion == PROG_ID_VERSION) {
auto n = new_expr ();
@ -1470,6 +1559,7 @@ statement_return (sblock_t *sblock, const expr_t *e)
}
}
s = new_statement (st_func, opcode, e);
s->use = use;
if (options.code.progsversion < PROG_VERSION) {
if (e->retrn.ret_val) {
const expr_t *ret_val = e->retrn.ret_val;