From 9ac8b0985a2fdd3839886ec5a435d59165899522 Mon Sep 17 00:00:00 2001 From: havoc Date: Thu, 6 Feb 2020 14:43:18 +0000 Subject: [PATCH] Implemented garbage collection of lost references to strzone strings in the PRVM, this is controlled by several prvm_garbagecollection_* cvars, and can notify when this occurs. git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@12520 d7cf8633-e32d-0410-b094-e92efae38249 --- csprogs.c | 2 + menu.c | 3 + progsvm.h | 37 ++++++- prvm_edict.c | 253 +++++++++++++++++++++++++++++++++------------ prvm_exec.c | 6 +- prvm_execprogram.h | 57 +++++++++- sv_phys.c | 3 + 7 files changed, 284 insertions(+), 77 deletions(-) diff --git a/csprogs.c b/csprogs.c index 75a8cf94..3bc3495c 100644 --- a/csprogs.c +++ b/csprogs.c @@ -485,6 +485,8 @@ qboolean CL_VM_UpdateView (double frametime) r_refdef.scene.numlights = 0; // polygonbegin without draw2d arg has to guess prog->polygonbegin_guess2d = false; + // free memory for resources that are no longer referenced + PRVM_GarbageCollection(prog); // pass in width and height as parameters (EXT_CSQC_1) PRVM_G_FLOAT(OFS_PARM0) = vid.width; PRVM_G_FLOAT(OFS_PARM1) = vid.height; diff --git a/menu.c b/menu.c index 77218e4b..60922547 100644 --- a/menu.c +++ b/menu.c @@ -5306,6 +5306,9 @@ static void MP_Draw (void) // TODO: this needs to be exposed to R_SetView (or something similar) ASAP [2/5/2008 Andreas] r_refdef.scene.time = realtime; + // free memory for resources that are no longer referenced + PRVM_GarbageCollection(prog); + // FIXME: this really shouldnt error out lest we have a very broken refdef state...? // or does it kill the server too? PRVM_G_FLOAT(OFS_PARM0) = vid.width; diff --git a/progsvm.h b/progsvm.h index 41c755d6..392d8465 100644 --- a/progsvm.h +++ b/progsvm.h @@ -489,6 +489,31 @@ typedef struct prvm_stringbuffer_s } prvm_stringbuffer_t; +// flags for knownstrings +#define KNOWNSTRINGFLAG_ENGINE 1 +#define KNOWNSTRINGFLAG_GCMARK 2 +#define KNOWNSTRINGFLAG_GCPRUNE 4 // cleared by GCMARK code, string is freed if prune remains after two sweeps + +typedef enum prvm_prog_garbagecollection_state_stage_e +{ + PRVM_GC_START = 0, + PRVM_GC_GLOBALS_MARK, + PRVM_GC_FIELDS_MARK, + PRVM_GC_KNOWNSTRINGS_SWEEP, + PRVM_GC_RESET, +} +prvm_prog_garbagecollection_state_stage_t; + +typedef struct prvm_prog_garbagecollection_state_s +{ + prvm_prog_garbagecollection_state_stage_t stage; + int globals_mark_progress; + int fields_mark_progress; + int fields_mark_progress_entity; + int knownstrings_sweep_progress; +} +prvm_prog_garbagecollection_state_t; + // [INIT] variables flagged with this token can be initialized by 'you' // NOTE: external code has to create and free the mempools but everything else is done by prvm ! typedef struct prvm_prog_s @@ -547,12 +572,15 @@ typedef struct prvm_prog_s // (simple optimization of the free string search) int firstfreeknownstring; const char **knownstrings; - unsigned char *knownstrings_freeable; + unsigned char *knownstrings_flags; const char **knownstrings_origin; const char ***stringshash; memexpandablearray_t stringbuffersarray; + // garbage collection status + prvm_prog_garbagecollection_state_t gc; + // all memory allocations related to this vm_prog (code, edicts, strings) mempool_t *progs_mempool; // [INIT] @@ -621,7 +649,11 @@ typedef struct prvm_prog_s int reserved_edicts; // [INIT] prvm_edict_t *edicts; - prvm_vec_t *edictsfields; + union + { + prvm_vec_t *fp; + prvm_int_t *ip; + } edictsfields; void *edictprivate; // size of the engine private struct @@ -767,6 +799,7 @@ void PRVM_PrintState(prvm_prog_t *prog, int stack_index); void PRVM_Crash(prvm_prog_t *prog); void PRVM_ShortStackTrace(prvm_prog_t *prog, char *buf, size_t bufsize); const char *PRVM_AllocationOrigin(prvm_prog_t *prog); +void PRVM_GarbageCollection(prvm_prog_t *prog); ddef_t *PRVM_ED_FindField(prvm_prog_t *prog, const char *name); ddef_t *PRVM_ED_FindGlobal(prvm_prog_t *prog, const char *name); diff --git a/prvm_edict.c b/prvm_edict.c index 687b9e4b..a2e23b65 100644 --- a/prvm_edict.c +++ b/prvm_edict.c @@ -44,6 +44,10 @@ cvar_t prvm_errordump = {CVAR_CLIENT | CVAR_SERVER, "prvm_errordump", "0", "writ cvar_t prvm_breakpointdump = {CVAR_CLIENT | CVAR_SERVER, "prvm_breakpointdump", "0", "write a savegame on breakpoint to breakpoint-server.dmp"}; cvar_t prvm_reuseedicts_startuptime = {CVAR_CLIENT | CVAR_SERVER, "prvm_reuseedicts_startuptime", "2", "allows immediate re-use of freed entity slots during start of new level (value in seconds)"}; cvar_t prvm_reuseedicts_neverinsameframe = {CVAR_CLIENT | CVAR_SERVER, "prvm_reuseedicts_neverinsameframe", "1", "never allows re-use of freed entity slots during same frame"}; +cvar_t prvm_garbagecollection_enable = {CVAR_CLIENT | CVAR_SERVER, "prvm_garbagecollection_enable", "1", "automatically scan for and free resources that are not referenced by the code being executed in the VM"}; +cvar_t prvm_garbagecollection_notify = {CVAR_CLIENT | CVAR_SERVER, "prvm_garbagecollection_notify", "0", "print out a notification for each resource freed by garbage collection"}; +cvar_t prvm_garbagecollection_scan_limit = {CVAR_CLIENT | CVAR_SERVER, "prvm_garbagecollection_scan_limit", "10000", "scan this many fields or resources per frame to free up unreferenced resources"}; +cvar_t prvm_garbagecollection_strings = {CVAR_CLIENT | CVAR_SERVER, "prvm_garbagecollection_strings", "1", "automatically call strunzone() on strings that are not referenced"}; static double prvm_reuseedicts_always_allow = 0; qboolean prvm_runawaycheck = true; @@ -76,13 +80,13 @@ static void PRVM_MEM_Alloc(prvm_prog_t *prog) // alloc edict fields prog->entityfieldsarea = prog->entityfields * prog->max_edicts; - prog->edictsfields = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, prog->entityfieldsarea * sizeof(prvm_vec_t)); + prog->edictsfields.fp = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, prog->entityfieldsarea * sizeof(prvm_vec_t)); // set edict pointers for(i = 0; i < prog->max_edicts; i++) { prog->edicts[i].priv.required = (prvm_edict_private_t *)((unsigned char *)prog->edictprivate + i * prog->edictprivate_size); - prog->edicts[i].fields.fp = prog->edictsfields + i * prog->entityfields; + prog->edicts[i].fields.fp = prog->edictsfields.fp + i * prog->entityfields; } } @@ -104,14 +108,14 @@ void PRVM_MEM_IncreaseEdicts(prvm_prog_t *prog) prog->max_edicts = min(prog->max_edicts + 256, prog->limit_edicts); prog->entityfieldsarea = prog->entityfields * prog->max_edicts; - prog->edictsfields = (prvm_vec_t*)Mem_Realloc(prog->progs_mempool, (void *)prog->edictsfields, prog->entityfieldsarea * sizeof(prvm_vec_t)); + prog->edictsfields.fp = (prvm_vec_t*)Mem_Realloc(prog->progs_mempool, (void *)prog->edictsfields.fp, prog->entityfieldsarea * sizeof(prvm_vec_t)); prog->edictprivate = (void *)Mem_Realloc(prog->progs_mempool, (void *)prog->edictprivate, prog->max_edicts * prog->edictprivate_size); //set e and v pointers for(i = 0; i < prog->max_edicts; i++) { prog->edicts[i].priv.required = (prvm_edict_private_t *)((unsigned char *)prog->edictprivate + i * prog->edictprivate_size); - prog->edicts[i].fields.fp = prog->edictsfields + i * prog->entityfields; + prog->edicts[i].fields.fp = prog->edictsfields.fp + i * prog->entityfields; } prog->end_increase_edicts(prog); @@ -2021,7 +2025,7 @@ void PRVM_Prog_Load(prvm_prog_t *prog, const char * filename, unsigned char * da prog->numknownstrings = 0; prog->maxknownstrings = 0; prog->knownstrings = NULL; - prog->knownstrings_freeable = NULL; + prog->knownstrings_flags = NULL; Mem_ExpandableArray_NewArray(&prog->stringbuffersarray, prog->progs_mempool, sizeof(prvm_stringbuffer_t), 64); @@ -2967,6 +2971,10 @@ void PRVM_Init (void) Cvar_RegisterVariable (&prvm_breakpointdump); Cvar_RegisterVariable (&prvm_reuseedicts_startuptime); Cvar_RegisterVariable (&prvm_reuseedicts_neverinsameframe); + Cvar_RegisterVariable (&prvm_garbagecollection_enable); + Cvar_RegisterVariable (&prvm_garbagecollection_notify); + Cvar_RegisterVariable (&prvm_garbagecollection_scan_limit); + Cvar_RegisterVariable (&prvm_garbagecollection_strings); // COMMANDLINEOPTION: PRVM: -norunaway disables the runaway loop check (it might be impossible to exit DarkPlaces if used!) prvm_runawaycheck = !COM_CheckParm("-norunaway"); @@ -3031,6 +3039,11 @@ const char *PRVM_GetString(prvm_prog_t *prog, int num) VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i has been freed)\n", num); return ""; } + // refresh the garbage collection on the string - this guards + // against a certain sort of repeated migration to earlier + // points in the scan that could otherwise result in the string + // being freed for being unused + prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] & ~KNOWNSTRINGFLAG_GCPRUNE) | KNOWNSTRINGFLAG_GCMARK; return prog->knownstrings[num]; } else @@ -3051,51 +3064,34 @@ const char *PRVM_ChangeEngineString(prvm_prog_t *prog, int i, const char *s) { const char *old; i = i - PRVM_KNOWNSTRINGBASE; - if(i < 0 || i >= prog->numknownstrings) - prog->error_cmd("PRVM_ChangeEngineString: s is not an engine string"); + if (i < 0 || i >= prog->numknownstrings) + prog->error_cmd("PRVM_ChangeEngineString: string index %i is out of bounds", i); + else if ((prog->knownstrings_flags[i] & KNOWNSTRINGFLAG_ENGINE) == 0) + prog->error_cmd("PRVM_ChangeEngineString: string index %i is not an engine string", i); old = prog->knownstrings[i]; prog->knownstrings[i] = s; return old; } -int PRVM_SetEngineString(prvm_prog_t *prog, const char *s) +static void PRVM_NewKnownString(prvm_prog_t *prog, int i, int flags, const char *s) { - int i; - if (!s) - return 0; - if (s >= prog->strings && s <= prog->strings + prog->stringssize) - prog->error_cmd("PRVM_SetEngineString: s in prog->strings area"); - // if it's in the tempstrings area, use a reserved range - // (otherwise we'd get millions of useless string offsets cluttering the database) - if (s >= (char *)prog->tempstringsbuf.data && s < (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.maxsize) - return prog->stringssize + (s - (char *)prog->tempstringsbuf.data); - // see if it's a known string address - for (i = 0;i < prog->numknownstrings;i++) - if (prog->knownstrings[i] == s) - return PRVM_KNOWNSTRINGBASE + i; - // new unknown engine string - if (developer_insane.integer) - Con_DPrintf("new engine string %p = \"%s\"\n", s, s); - for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++) - if (!prog->knownstrings[i]) - break; if (i >= prog->numknownstrings) { if (i >= prog->maxknownstrings) { const char **oldstrings = prog->knownstrings; - const unsigned char *oldstrings_freeable = prog->knownstrings_freeable; + const unsigned char *oldstrings_flags = prog->knownstrings_flags; const char **oldstrings_origin = prog->knownstrings_origin; prog->maxknownstrings += 128; prog->knownstrings = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *)); - prog->knownstrings_freeable = (unsigned char *)PRVM_Alloc(prog->maxknownstrings * sizeof(unsigned char)); - if(prog->leaktest_active) + prog->knownstrings_flags = (unsigned char *)PRVM_Alloc(prog->maxknownstrings * sizeof(unsigned char)); + if (prog->leaktest_active) prog->knownstrings_origin = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *)); if (prog->numknownstrings) { memcpy((char **)prog->knownstrings, oldstrings, prog->numknownstrings * sizeof(char *)); - memcpy((char **)prog->knownstrings_freeable, oldstrings_freeable, prog->numknownstrings * sizeof(unsigned char)); - if(prog->leaktest_active) + memcpy((char **)prog->knownstrings_flags, oldstrings_flags, prog->numknownstrings * sizeof(unsigned char)); + if (prog->leaktest_active) memcpy((char **)prog->knownstrings_origin, oldstrings_origin, prog->numknownstrings * sizeof(char *)); } } @@ -3103,9 +3099,34 @@ int PRVM_SetEngineString(prvm_prog_t *prog, const char *s) } prog->firstfreeknownstring = i + 1; prog->knownstrings[i] = s; - prog->knownstrings_freeable[i] = false; - if(prog->leaktest_active) + // it's in use right now, spare it until the next gc pass - that said, it is not freeable so this is probably moot + prog->knownstrings_flags[i] = flags; + if (prog->leaktest_active) prog->knownstrings_origin[i] = NULL; +} + +int PRVM_SetEngineString(prvm_prog_t *prog, const char *s) +{ + int i; + if (!s) + return 0; + if (s >= prog->strings && s <= prog->strings + prog->stringssize) + prog->error_cmd("PRVM_SetEngineString: s in prog->strings area"); + // if it's in the tempstrings area, use a reserved range + // (otherwise we'd get millions of useless string offsets cluttering the database) + if (s >= (char *)prog->tempstringsbuf.data && s < (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.maxsize) + return prog->stringssize + (s - (char *)prog->tempstringsbuf.data); + // see if it's a known string address + for (i = 0;i < prog->numknownstrings;i++) + if (prog->knownstrings[i] == s) + return PRVM_KNOWNSTRINGBASE + i; + // new unknown engine string + if (developer_insane.integer) + Con_DPrintf("new engine string %p = \"%s\"\n", s, s); + for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++) + if (!prog->knownstrings[i]) + break; + PRVM_NewKnownString(prog, i, KNOWNSTRINGFLAG_GCMARK | KNOWNSTRINGFLAG_ENGINE, s); return PRVM_KNOWNSTRINGBASE + i; } @@ -3156,6 +3177,7 @@ int PRVM_SetTempString(prvm_prog_t *prog, const char *s) int PRVM_AllocString(prvm_prog_t *prog, size_t bufferlength, char **pointer) { int i; + char *s; if (!bufferlength) { if (pointer) @@ -3165,37 +3187,8 @@ int PRVM_AllocString(prvm_prog_t *prog, size_t bufferlength, char **pointer) for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++) if (!prog->knownstrings[i]) break; - if (i >= prog->numknownstrings) - { - if (i >= prog->maxknownstrings) - { - const char **oldstrings = prog->knownstrings; - const unsigned char *oldstrings_freeable = prog->knownstrings_freeable; - const char **oldstrings_origin = prog->knownstrings_origin; - prog->maxknownstrings += 128; - prog->knownstrings = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *)); - prog->knownstrings_freeable = (unsigned char *)PRVM_Alloc(prog->maxknownstrings * sizeof(unsigned char)); - if(prog->leaktest_active) - prog->knownstrings_origin = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *)); - if (prog->numknownstrings) - { - memcpy((char **)prog->knownstrings, oldstrings, prog->numknownstrings * sizeof(char *)); - memcpy((char **)prog->knownstrings_freeable, oldstrings_freeable, prog->numknownstrings * sizeof(unsigned char)); - if(prog->leaktest_active) - memcpy((char **)prog->knownstrings_origin, oldstrings_origin, prog->numknownstrings * sizeof(char *)); - } - if (oldstrings) - Mem_Free((char **)oldstrings); - if (oldstrings_freeable) - Mem_Free((unsigned char *)oldstrings_freeable); - if (oldstrings_origin) - Mem_Free((char **)oldstrings_origin); - } - prog->numknownstrings++; - } - prog->firstfreeknownstring = i + 1; - prog->knownstrings[i] = (char *)PRVM_Alloc(bufferlength); - prog->knownstrings_freeable[i] = true; + s = PRVM_Alloc(bufferlength); + PRVM_NewKnownString(prog, i, KNOWNSTRINGFLAG_GCMARK, s); if(prog->leaktest_active) prog->knownstrings_origin[i] = PRVM_AllocationOrigin(prog); if (pointer) @@ -3214,14 +3207,14 @@ void PRVM_FreeString(prvm_prog_t *prog, int num) num = num - PRVM_KNOWNSTRINGBASE; if (!prog->knownstrings[num]) prog->error_cmd("PRVM_FreeString: attempt to free a non-existent or already freed string"); - if (!prog->knownstrings_freeable[num]) + if (!prog->knownstrings_flags[num]) prog->error_cmd("PRVM_FreeString: attempt to free a string owned by the engine"); PRVM_Free((char *)prog->knownstrings[num]); if(prog->leaktest_active) if(prog->knownstrings_origin[num]) PRVM_Free((char *)prog->knownstrings_origin[num]); prog->knownstrings[num] = NULL; - prog->knownstrings_freeable[num] = false; + prog->knownstrings_flags[num] = 0; prog->firstfreeknownstring = min(prog->firstfreeknownstring, num); } else @@ -3416,7 +3409,7 @@ void PRVM_LeakTest(prvm_prog_t *prog) for (i = 0; i < prog->numknownstrings; ++i) { if(prog->knownstrings[i]) - if(prog->knownstrings_freeable[i]) + if(prog->knownstrings_flags[i]) if(prog->knownstrings_origin[i]) if(!PRVM_IsStringReferenced(prog, PRVM_KNOWNSTRINGBASE + i)) { @@ -3478,3 +3471,127 @@ void PRVM_LeakTest(prvm_prog_t *prog) if(!leaked) Con_Printf("Congratulations. No leaks found.\n"); } + +void PRVM_GarbageCollection(prvm_prog_t *prog) +{ + int limit = prvm_garbagecollection_scan_limit.integer; + prvm_prog_garbagecollection_state_t *gc = &prog->gc; + if (!prvm_garbagecollection_enable.integer) + return; + // philosophy: + // we like to limit how much scanning we do so it doesn't put a significant + // burden on the cpu, so each of these are not complete scans, we also like + // to have consistent cpu usage so we do a bit of work on each category of + // leaked object every frame + switch (gc->stage) + { + case PRVM_GC_START: + gc->stage++; + break; + case PRVM_GC_GLOBALS_MARK: + for (; gc->globals_mark_progress < prog->numglobaldefs && (limit--) > 0; gc->globals_mark_progress++) + { + ddef_t *d = &prog->globaldefs[gc->globals_mark_progress]; + switch (d->type) + { + case ev_string: + { + prvm_int_t s = prog->globals.ip[d->ofs]; + if (s & PRVM_KNOWNSTRINGBASE) + { + prvm_int_t num = s - PRVM_KNOWNSTRINGBASE; + if (!prog->knownstrings[num]) + { + // invalid + Con_DPrintf("PRVM_GarbageCollection: Found bogus strzone reference in global %i (global name: \"%s\"), erasing reference", d->ofs, PRVM_GetString(prog, d->s_name)); + prog->globals.ip[d->ofs] = 0; + continue; + } + prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] | KNOWNSTRINGFLAG_GCMARK) & ~KNOWNSTRINGFLAG_GCPRUNE; + } + } + break; + default: + break; + } + } + if (gc->globals_mark_progress >= prog->numglobaldefs) + gc->stage++; + break; + case PRVM_GC_FIELDS_MARK: + for (; gc->fields_mark_progress < prog->numfielddefs && limit > 0;) + { + ddef_t *d = &prog->fielddefs[gc->fields_mark_progress]; + switch (d->type) + { + case ev_string: + //for (gc-> entityindex = 0; entityindex < prog->num_edicts; entityindex++) + for (;gc->fields_mark_progress_entity < prog->num_edicts && (limit--) > 0;gc->fields_mark_progress_entity++) + { + int entityindex = gc->fields_mark_progress_entity; + prvm_int_t s = prog->edictsfields.ip[entityindex * prog->entityfields + d->ofs]; + if (s & PRVM_KNOWNSTRINGBASE) + { + prvm_int_t num = s - PRVM_KNOWNSTRINGBASE; + if (!prog->knownstrings[num]) + { + // invalid + Con_DPrintf("PRVM_GarbageCollection: Found bogus strzone reference in edict %i field %i (field name: \"%s\"), erasing reference", entityindex, d->ofs, PRVM_GetString(prog, d->s_name)); + prog->edictsfields.ip[entityindex * prog->entityfields + d->ofs] = 0; + continue; + } + prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] | KNOWNSTRINGFLAG_GCMARK) & ~KNOWNSTRINGFLAG_GCPRUNE; + } + } + if (gc->fields_mark_progress_entity >= prog->num_edicts) + { + gc->fields_mark_progress_entity = 0; + gc->fields_mark_progress++; + } + break; + default: + gc->fields_mark_progress_entity = 0; + gc->fields_mark_progress++; + break; + } + } + if (gc->fields_mark_progress >= prog->numfielddefs) + gc->stage++; + break; + case PRVM_GC_KNOWNSTRINGS_SWEEP: + // free any strzone'd strings that are not marked + if (!prvm_garbagecollection_strings.integer) + { + gc->stage++; + break; + } + for (;gc->knownstrings_sweep_progress < prog->numknownstrings && (limit--) > 0;gc->knownstrings_sweep_progress++) + { + int num = gc->knownstrings_sweep_progress; + if (prog->knownstrings[num] && (prog->knownstrings_flags[num] & (KNOWNSTRINGFLAG_GCMARK | KNOWNSTRINGFLAG_ENGINE)) == 0) + { + if (prog->knownstrings_flags[num] & KNOWNSTRINGFLAG_GCPRUNE) + { + // string has been marked for pruning two passes in a row + if (prvm_garbagecollection_notify.integer) + Con_DPrintf("prvm_garbagecollection_notify: %s: freeing unreferenced string %i: \"%s\"\n", prog->name, num, prog->knownstrings[num]); + Mem_Free((char *)prog->knownstrings[num]); + prog->knownstrings[num] = NULL; + prog->knownstrings_flags[num] = 0; + prog->firstfreeknownstring = min(prog->firstfreeknownstring, num); + } + else + { + // mark it for pruning next pass + prog->knownstrings_flags[num] |= KNOWNSTRINGFLAG_GCPRUNE; + } + } + } + if (gc->knownstrings_sweep_progress >= prog->numknownstrings) + gc->stage++; + break; + case PRVM_GC_RESET: + default: + memset(gc, 0, sizeof(*gc)); + } +} diff --git a/prvm_exec.c b/prvm_exec.c index 762488c6..c43fbef4 100644 --- a/prvm_exec.c +++ b/prvm_exec.c @@ -746,7 +746,7 @@ void MVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessag double tm, starttm; prvm_vec_t tempfloat; // these may become out of date when a builtin is called, and are updated accordingly - prvm_vec_t *cached_edictsfields = prog->edictsfields; + prvm_vec_t *cached_edictsfields = prog->edictsfields.fp; unsigned int cached_entityfields = prog->entityfields; unsigned int cached_entityfields_3 = prog->entityfields - 3; unsigned int cached_entityfieldsarea = prog->entityfieldsarea; @@ -853,7 +853,7 @@ void CLVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessa double tm, starttm; prvm_vec_t tempfloat; // these may become out of date when a builtin is called, and are updated accordingly - prvm_vec_t *cached_edictsfields = prog->edictsfields; + prvm_vec_t *cached_edictsfields = prog->edictsfields.fp; unsigned int cached_entityfields = prog->entityfields; unsigned int cached_entityfields_3 = prog->entityfields - 3; unsigned int cached_entityfieldsarea = prog->entityfieldsarea; @@ -964,7 +964,7 @@ void PRVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessa double tm, starttm; prvm_vec_t tempfloat; // these may become out of date when a builtin is called, and are updated accordingly - prvm_vec_t *cached_edictsfields = prog->edictsfields; + prvm_vec_t *cached_edictsfields = prog->edictsfields.fp; unsigned int cached_entityfields = prog->entityfields; unsigned int cached_entityfields_3 = prog->entityfields - 3; unsigned int cached_entityfieldsarea = prog->entityfieldsarea; diff --git a/prvm_execprogram.h b/prvm_execprogram.h index 02de4e6b..256676e9 100644 --- a/prvm_execprogram.h +++ b/prvm_execprogram.h @@ -281,10 +281,17 @@ HANDLE_OPCODE(OP_STORE_F): HANDLE_OPCODE(OP_STORE_ENT): HANDLE_OPCODE(OP_STORE_FLD): // integers - HANDLE_OPCODE(OP_STORE_S): HANDLE_OPCODE(OP_STORE_FNC): // pointers OPB->_int = OPA->_int; DISPATCH_OPCODE(); + HANDLE_OPCODE(OP_STORE_S): + // refresh the garbage collection on the string - this guards + // against a certain sort of repeated migration to earlier + // points in the scan that could otherwise result in the string + // being freed for being unused + PRVM_GetString(prog, OPA->_int); + OPB->_int = OPA->_int; + DISPATCH_OPCODE(); HANDLE_OPCODE(OP_STORE_V): OPB->ivector[0] = OPA->ivector[0]; OPB->ivector[1] = OPA->ivector[1]; @@ -294,7 +301,6 @@ HANDLE_OPCODE(OP_STOREP_F): HANDLE_OPCODE(OP_STOREP_ENT): HANDLE_OPCODE(OP_STOREP_FLD): // integers - HANDLE_OPCODE(OP_STOREP_S): HANDLE_OPCODE(OP_STOREP_FNC): // pointers if ((prvm_uint_t)OPB->_int - cached_entityfields >= cached_entityfieldsarea_entityfields) { @@ -313,6 +319,29 @@ ptr = (prvm_eval_t *)(cached_edictsfields + OPB->_int); ptr->_int = OPA->_int; DISPATCH_OPCODE(); + HANDLE_OPCODE(OP_STOREP_S): + if ((prvm_uint_t)OPB->_int - cached_entityfields >= cached_entityfieldsarea_entityfields) + { + if ((prvm_uint_t)OPB->_int >= cached_entityfieldsarea) + { + PRE_ERROR(); + prog->error_cmd("%s attempted to write to an out of bounds edict (%i)", prog->name, (int)OPB->_int); + goto cleanup; + } + if ((prvm_uint_t)OPB->_int < cached_entityfields && !cached_allowworldwrites) + { + PRE_ERROR(); + VM_Warning(prog, "assignment to world.%s (field %i) in %s\n", PRVM_GetString(prog, PRVM_ED_FieldAtOfs(prog, OPB->_int)->s_name), (int)OPB->_int, prog->name); + } + } + // refresh the garbage collection on the string - this guards + // against a certain sort of repeated migration to earlier + // points in the scan that could otherwise result in the string + // being freed for being unused + PRVM_GetString(prog, OPA->_int); + ptr = (prvm_eval_t *)(cached_edictsfields + OPB->_int); + ptr->_int = OPA->_int; + DISPATCH_OPCODE(); HANDLE_OPCODE(OP_STOREP_V): if ((prvm_uint_t)OPB->_int - cached_entityfields > (prvm_uint_t)cached_entityfieldsarea_entityfields_3) { @@ -361,7 +390,6 @@ HANDLE_OPCODE(OP_LOAD_F): HANDLE_OPCODE(OP_LOAD_FLD): HANDLE_OPCODE(OP_LOAD_ENT): - HANDLE_OPCODE(OP_LOAD_S): HANDLE_OPCODE(OP_LOAD_FNC): if ((prvm_uint_t)OPA->edict >= cached_max_edicts) { @@ -378,6 +406,27 @@ ed = PRVM_PROG_TO_EDICT(OPA->edict); OPC->_int = ((prvm_eval_t *)(ed->fields.ip + OPB->_int))->_int; DISPATCH_OPCODE(); + HANDLE_OPCODE(OP_LOAD_S): + if ((prvm_uint_t)OPA->edict >= cached_max_edicts) + { + PRE_ERROR(); + prog->error_cmd("%s Progs attempted to read an out of bounds edict number", prog->name); + goto cleanup; + } + if ((prvm_uint_t)OPB->_int >= cached_entityfields) + { + PRE_ERROR(); + prog->error_cmd("%s attempted to read an invalid field in an edict (%i)", prog->name, (int)OPB->_int); + goto cleanup; + } + ed = PRVM_PROG_TO_EDICT(OPA->edict); + OPC->_int = ((prvm_eval_t *)(ed->fields.ip + OPB->_int))->_int; + // refresh the garbage collection on the string - this guards + // against a certain sort of repeated migration to earlier + // points in the scan that could otherwise result in the string + // being freed for being unused + PRVM_GetString(prog, OPC->_int); + DISPATCH_OPCODE(); HANDLE_OPCODE(OP_LOAD_V): if ((prvm_uint_t)OPA->edict >= cached_max_edicts) @@ -501,7 +550,7 @@ starttm = tm; #endif // builtins may cause ED_Alloc() to be called, update cached variables - cached_edictsfields = prog->edictsfields; + cached_edictsfields = prog->edictsfields.fp; cached_entityfields = prog->entityfields; cached_entityfields_3 = prog->entityfields - 3; cached_entityfieldsarea = prog->entityfieldsarea; diff --git a/sv_phys.c b/sv_phys.c index 1c671558..1a774216 100644 --- a/sv_phys.c +++ b/sv_phys.c @@ -3148,6 +3148,9 @@ void SV_Physics (void) int i; prvm_edict_t *ent; + // free memory for resources that are no longer referenced + PRVM_GarbageCollection(prog); + // let the progs know that a new frame has started PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(prog->edicts); PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(prog->edicts); -- 2.39.2