[qfcc] Implement bounced return pointer calls

This is achieved by marking a void function with the void_return
attribute and then calling that function in an @return expression.
@return can be used only inside a void function and only with void
functions marked with the void_return attribute. As this is intended for
Objective-QC message forwarding, it is deliberately "difficult" to use
as returning a larger than expected value is unlikely to end well for
the calling function.

However, as a convenience, "@return nil" is allowed (in a void
function). It always returns an integer (which, of course,can be
interpreted as a pointer). This is safe because if the return value is
ignored, it will go into the progs return buffer, and if it is not
ignored, it is the smallest value that can be returned.
This commit is contained in:
Bill Currie 2022-02-05 18:58:42 +09:00
parent 084c2ccb1f
commit 1ce026d168
6 changed files with 60 additions and 4 deletions

View file

@ -236,6 +236,7 @@ typedef struct {
typedef struct {
struct expr_s *ret_val;
int at_return; ///< return void_return call through void
} ex_return_t;
typedef struct {
@ -244,7 +245,7 @@ typedef struct {
} ex_adjstk_t;
typedef struct {
short mode; ///< currently must be 0
short mode;
short reg; ///< base register to load
struct expr_s *with; ///< value to load
} ex_with_t;
@ -791,6 +792,7 @@ expr_t *goto_expr (expr_t *label);
expr_t *jump_table_expr (expr_t *table, expr_t *index);
expr_t *call_expr (expr_t *func, expr_t *args, struct type_s *ret_type);
expr_t *return_expr (struct function_s *f, expr_t *e);
expr_t *at_return_expr (struct function_s *f, expr_t *e);
expr_t *conditional_expr (expr_t *cond, expr_t *e1, expr_t *e2);
expr_t *incop_expr (int op, expr_t *e, int postop);
expr_t *array_expr (expr_t *array, expr_t *index);

View file

@ -42,6 +42,7 @@ typedef struct ty_func_s {
union {
struct {
unsigned no_va_list:1;///< don't inject va_list for ... function
unsigned void_return:1;///< special handling for return value
};
unsigned attribute_bits;
};
@ -106,6 +107,7 @@ typedef struct {
unsigned is_overload:1;
unsigned nosave:1;
unsigned no_va_list:1;
unsigned void_return:1;
};
unsigned spec_bits;
};

View file

@ -2477,6 +2477,30 @@ return_expr (function_t *f, expr_t *e)
return new_return_expr (e);
}
expr_t *
at_return_expr (function_t *f, expr_t *e)
{
const type_t *ret_type = unalias_type (f->type->t.func.type);
if (!is_void(ret_type)) {
return error (e, "use of @return in non-void function");
}
if (is_nil (e)) {
// int or pointer 0 seems reasonable
return new_return_expr (new_int_expr (0));
} else if (!is_function_call (e)) {
return error (e, "@return value not a function");
}
expr_t *call_expr = e->e.block.result->e.branch.target;
const type_t *call_type = get_type (call_expr);
if (!is_func (call_type) && !call_type->t.func.void_return) {
return error (e, "@return function not void_return");
}
expr_t *ret_expr = new_return_expr (e);
ret_expr->e.retrn.at_return = 1;
return ret_expr;
}
expr_t *
conditional_expr (expr_t *cond, expr_t *e1, expr_t *e2)
{

View file

@ -392,6 +392,7 @@ static keyword_t qf_keywords[] = {
{"@args", ARGS, 0 },
{"@va_list", TYPE, &type_va_list },
{"@param", TYPE, &type_param },
{"@return", AT_RETURN, 0 },
{"@cross", CROSS, 0 },
{"@dot", DOT, 0 },

View file

@ -149,7 +149,8 @@ int yylex (void);
%token <symbol> CLASS_NAME NAME
%token <expr> VALUE STRING
%token LOCAL RETURN WHILE DO IF ELSE FOR BREAK CONTINUE ELLIPSIS
%token LOCAL WHILE DO IF ELSE FOR BREAK CONTINUE
%token RETURN AT_RETURN ELLIPSIS
%token NIL GOTO SWITCH CASE DEFAULT ENUM
%token ARGS TYPEDEF EXTERN STATIC SYSTEM OVERLOAD NOT ATTRIBUTE
%token UNSIGNED SIGNED LONG SHORT
@ -251,6 +252,8 @@ parse_attributes (attribute_t *attr_list)
spec.no_va_list = 1;
} else if (!strcmp (attr->name, "nosave")) {
spec.nosave = 1;
} else if (!strcmp (attr->name, "void_return")) {
spec.void_return = 1;
} else {
warning (0, "skipping unknown attribute '%s'", attr->name);
}
@ -425,6 +428,7 @@ static void
set_func_type_attrs (type_t *func, specifier_t spec)
{
func->t.func.no_va_list = spec.no_va_list;
func->t.func.void_return = spec.void_return;
}
%}
@ -1420,6 +1424,7 @@ statement
| local_def { $$ = $1; }
| RETURN opt_expr ';' { $$ = return_expr (current_func, $2); }
| RETURN compound_init ';' { $$ = return_expr (current_func, $2); }
| AT_RETURN expr ';' { $$ = at_return_expr (current_func, $2); }
| BREAK ';'
{
$$ = 0;

View file

@ -1427,11 +1427,18 @@ statement_return (sblock_t *sblock, expr_t *e)
if (e->e.retrn.ret_val) {
expr_t *ret_val = e->e.retrn.ret_val;
type_t *ret_type = get_type (ret_val);
s->opa = return_operand (ret_type, e);
// at_return is used for passing the result of a void_return
// function through void. v6 progs always use .return for the
// return value, so don't need to do anything special: just call
// the function and do a normal void return
if (!e->e.retrn.at_return) {
s->opa = return_operand (ret_type, e);
}
sblock = statement_subexpr (sblock, ret_val, &s->opa);
}
} else {
if (e->e.retrn.ret_val) {
if (!e->e.retrn.at_return && e->e.retrn.ret_val) {
expr_t *ret_val = e->e.retrn.ret_val;
type_t *ret_type = get_type (ret_val);
pr_ushort_t ret_crtl = type_size (ret_type) - 1;
@ -1440,6 +1447,21 @@ statement_return (sblock_t *sblock, expr_t *e)
ret_crtl |= mode << 5;
s->opc = short_operand (ret_crtl, e);
} else {
if (e->e.retrn.at_return) {
expr_t *call = e->e.retrn.ret_val;
if (!call || !is_function_call (call)) {
internal_error (e, "@return with no call");
}
// FIXME hard-coded reg, and assumes 3 is free
#define REG 3
expr_t *with = new_with_expr (11, REG, new_short_expr (0));
def_t *ret_ptr = new_def (0, 0, 0, sc_local);
operand_t *ret_op = def_operand (ret_ptr, &type_void, e);
ret_ptr->reg = REG;
expr_file_line (with, e);
sblock = statement_slist (sblock, with);
sblock = statement_subexpr (sblock, call, &ret_op);
}
s->opa = short_operand (0, e);
s->opb = short_operand (0, e);
s->opc = short_operand (-1, e); // void return