From 00b7bced7f5d5c869e0064245e08dc730ff4b3c9 Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Mon, 24 Jan 2022 12:50:15 +0900 Subject: [PATCH] [gamecode] Rework PR_RESET_PARAMS to use PR_SetupParams PR_SetupParams is new and sets up the parameter pointers so older code that expects only up to 8 parameter will work with both v6p and Ruamoko progs without having to check what progs are running. PR_SetupParams is useful even when Ruamoko progs are expected as it reserves the required space (respecting alignment) on the stack and returns a pointer to the top (bottom? confusing) of the stack. PR_PushFrame and PR_PopFrame need to be used around PR_SetupParams, regardless of using temp strings, to avoid a stack leak (need to do an audit). --- include/QF/progs.h | 42 +++++++++++++++++++++++++++++++++++------ libs/gamecode/pr_exec.c | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/include/QF/progs.h b/include/QF/progs.h index 066a479af..78e9913c9 100644 --- a/include/QF/progs.h +++ b/include/QF/progs.h @@ -82,15 +82,13 @@ void PR_RunError (progs_t *pr, const char *error, ...) __attribute__((format(PRI \warning Failure to use this macro before assigning to the P_* macros can cause corruption of the VM data due to "register" based calling. Can be safely ignored for parameterless functions, or forwarding parameters - though a builtin. + though a builtin. However, it is ok (and encouraged) to call + PR_SetupParams instead, as this macro calls PR_SetupParams with + PR_MAX_PARAMS and 1 for the alignment. \hideinitializer */ -#define PR_RESET_PARAMS(pr) \ - do { \ - (pr)->pr_params[0] = (pr)->pr_real_params[0]; \ - (pr)->pr_params[1] = (pr)->pr_real_params[1]; \ - } while (0) +#define PR_RESET_PARAMS(pr) PR_SetupParams (pr, PR_MAX_PARAMS, 1) /** \name Detouring Function Calls @@ -155,6 +153,38 @@ void PR_RestoreParams (progs_t *pr, pr_stashed_params_t *params); */ void PR_PushFrame (progs_t *pr); +/** Reserve space on the data stack and set up the param pointers. + + For v6p progs, this only sets up the param pointers as v6p progs do not + have a data stack. + + For Ruamoko progs, space for at least \a num_params (each being 4 words) + is created on the stack, with a minimum alignment of min_alignment words, + or 4, whichever is larger. + + \param pr pointer to ::progs_t VM struct + \param num_params Number of parameter slots needed for the function call. + Each slot is 4 words. dvec4 and lvec4 parameters require + 8 words and must be 8-word aligned. dvec3 and lvec3 + also require 8 words due to the minimum 4 word alignment, + but have no alignment requirements themselves. Be sure to + take this into account in size calculations. + \param min_alignment Minimum number of words to which the stack will be + aligned. Must be a power of two. Note that when passing + dvec4 or lvec4 parameters, they have a hardware-enforced + requirement of 8 word alignment. This means that for + something like (int, lvec4), there will be an unused + parameter slot between the int and the lvec4. + Ignored for v6p progs. + \return Pointer to the base of the created parameter area. For + v6p progs, this is just .param_0, but for Ruamoko progs + this will be the current top of the the data stack after + adjustment for the parameter space. + \note Attempting to pass more than PR_MAX_PARAMS parameters to v6p progs + is a hard error. +*/ +pr_type_t *PR_SetupParams (progs_t *pr, int num_params, int min_alignment); + /** Pop an execution frame from the VM stack. Restores execution state. Also frees any temporary strings allocated in this frame (via PR_FreeTempStrings()). diff --git a/libs/gamecode/pr_exec.c b/libs/gamecode/pr_exec.c index 19507665c..a78b01f9a 100644 --- a/libs/gamecode/pr_exec.c +++ b/libs/gamecode/pr_exec.c @@ -458,6 +458,9 @@ PR_CallFunction (progs_t *pr, pr_func_t fnum, pr_type_t *return_ptr) static void check_stack_pointer (progs_t *pr, pr_ptr_t stack, int size) { + if (stack & 3) { + PR_RunError (pr, "Progs stack not aligned"); + } if (stack < pr->stack_bottom) { PR_RunError (pr, "Progs stack overflow"); } @@ -466,6 +469,36 @@ check_stack_pointer (progs_t *pr, pr_ptr_t stack, int size) } } +VISIBLE pr_type_t * +PR_SetupParams (progs_t *pr, int num_params, int min_alignment) +{ + if (pr->progs->version < PROG_VERSION) { + if (num_params > PR_MAX_PARAMS) { + PR_Error (pr, "attempt to settup more than %d params", + PR_MAX_PARAMS); + } + pr->pr_params[0] = pr->pr_real_params[0]; + pr->pr_params[1] = pr->pr_real_params[1]; + return pr->pr_real_params[0]; + } + int offset = num_params * 4; + if (min_alignment < 4) { + min_alignment = 4; + } + pr_ptr_t mask = ~(min_alignment - 1); + pr_ptr_t stack = (*pr->globals.stack - offset) & mask; + if (pr_boundscheck->int_val) { + check_stack_pointer (pr, stack, 0); + } + *pr->globals.stack = stack; + pr->pr_params[0] = pr->pr_globals + stack; + num_params = min (num_params, PR_MAX_PARAMS); + for (int i = 1; i < num_params; i++) { + pr->pr_params[i] = pr->pr_params[0] + i * 4; + } + return pr->pr_params[0]; +} + static inline void pr_memset (pr_type_t *dst, int val, pr_uint_t count) {