diff --git a/tools/qfcc/include/def.h b/tools/qfcc/include/def.h index 290510fa0..723ba4813 100644 --- a/tools/qfcc/include/def.h +++ b/tools/qfcc/include/def.h @@ -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 diff --git a/tools/qfcc/include/expr.h b/tools/qfcc/include/expr.h index 5623906fe..256755d86 100644 --- a/tools/qfcc/include/expr.h +++ b/tools/qfcc/include/expr.h @@ -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 diff --git a/tools/qfcc/include/expr_names.h b/tools/qfcc/include/expr_names.h index cd57b5611..411940139 100644 --- a/tools/qfcc/include/expr_names.h +++ b/tools/qfcc/include/expr_names.h @@ -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) diff --git a/tools/qfcc/include/function.h b/tools/qfcc/include/function.h index 4e1794d5b..7d2ed844a 100644 --- a/tools/qfcc/include/function.h +++ b/tools/qfcc/include/function.h @@ -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; diff --git a/tools/qfcc/include/specifier.h b/tools/qfcc/include/specifier.h index cedaf8bc0..48b91987e 100644 --- a/tools/qfcc/include/specifier.h +++ b/tools/qfcc/include/specifier.h @@ -49,6 +49,7 @@ typedef enum storage_class_e { sc_in, sc_out, + sc_inout, sc_uniform, sc_buffer, sc_shared, diff --git a/tools/qfcc/include/type.h b/tools/qfcc/include/type.h index ba5ade9f4..b4182b310 100644 --- a/tools/qfcc/include/type.h +++ b/tools/qfcc/include/type.h @@ -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 diff --git a/tools/qfcc/source/class.c b/tools/qfcc/source/class.c index 550da62f9..94c461095 100644 --- a/tools/qfcc/source/class.c +++ b/tools/qfcc/source/class.c @@ -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 diff --git a/tools/qfcc/source/def.c b/tools/qfcc/source/def.c index 03b877378..2e5af49ab 100644 --- a/tools/qfcc/source/def.c +++ b/tools/qfcc/source/def.c @@ -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; diff --git a/tools/qfcc/source/expr.c b/tools/qfcc/source/expr.c index 2342ef2d5..cd98e923e 100644 --- a/tools/qfcc/source/expr.c +++ b/tools/qfcc/source/expr.c @@ -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 (¶ms->list) :0; + int arg_count = params ? list_count (¶ms->list) : 0; const expr_t *arguments[arg_count + 1]; if (params) { list_scatter_rev (¶ms->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); } } diff --git a/tools/qfcc/source/expr_assign.c b/tools/qfcc/source/expr_assign.c index 053be4b5c..b78c71334 100644 --- a/tools/qfcc/source/expr_assign.c +++ b/tools/qfcc/source/expr_assign.c @@ -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: diff --git a/tools/qfcc/source/expr_dag.c b/tools/qfcc/source/expr_dag.c index 2d7b978de..eb89f933c 100644 --- a/tools/qfcc/source/expr_dag.c +++ b/tools/qfcc/source/expr_dag.c @@ -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: diff --git a/tools/qfcc/source/flow.c b/tools/qfcc/source/flow.c index 0ff832a45..1e34d2ceb 100644 --- a/tools/qfcc/source/flow.c +++ b/tools/qfcc/source/flow.c @@ -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); } diff --git a/tools/qfcc/source/function.c b/tools/qfcc/source/function.c index 97e9dc00c..92d5e19bb 100644 --- a/tools/qfcc/source/function.c +++ b/tools/qfcc/source/function.c @@ -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; } } diff --git a/tools/qfcc/source/glsl-block.c b/tools/qfcc/source/glsl-block.c index c173cf845..1953c6bb4 100644 --- a/tools/qfcc/source/glsl-block.c +++ b/tools/qfcc/source/glsl-block.c @@ -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) { diff --git a/tools/qfcc/source/qc-parse.y b/tools/qfcc/source/qc-parse.y index d048be9e9..65701bd20 100644 --- a/tools/qfcc/source/qc-parse.y +++ b/tools/qfcc/source/qc-parse.y @@ -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, }, diff --git a/tools/qfcc/source/statements.c b/tools/qfcc/source/statements.c index 614a06400..752a02ec4 100644 --- a/tools/qfcc/source/statements.c +++ b/tools/qfcc/source/statements.c @@ -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;