]> git.xonotic.org Git - xonotic/darkplaces.git/commitdiff
Implemented garbage collection of lost references to strzone strings in the PRVM...
authorhavoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Thu, 6 Feb 2020 14:43:18 +0000 (14:43 +0000)
committerhavoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Thu, 6 Feb 2020 14:43:18 +0000 (14:43 +0000)
git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@12520 d7cf8633-e32d-0410-b094-e92efae38249

csprogs.c
menu.c
progsvm.h
prvm_edict.c
prvm_exec.c
prvm_execprogram.h
sv_phys.c

index 75a8cf9448d281e786f102dd582419724fe7cb86..3bc3495c7e0412f743099b175bf7b518c4f88319 100644 (file)
--- 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 77218e4b4b8349bed1b96230c12862734a004f2f..6092254778caf4b391aae2a353af076c5a7820c6 100644 (file)
--- 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;
index 41c755d6358231fe245253950d26dea17509b195..392d8465ff4ca0696f77a1e16ce43e93a4d54916 100644 (file)
--- 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);
index 687b9e4bf7af083b754e638c1535ae09c2a1928e..a2e23b651933f456c0ff7027afd954f2c6205dea 100644 (file)
@@ -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));
+       }
+}
index 762488c6a522c3c281aa8fc6c9e9dad31b55f3ad..c43fbef42791ec12bd23e9933b1a94bbe6afd8c5 100644 (file)
@@ -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;
index 02de4e6b77a4b48bd07104a1e5c8dda1819ace96..256676e9bad58331d972bd2a2eadfc8c0deedbab 100644 (file)
                        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];
                        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)
                                {
                                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)
                                {
                        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)
                                {
                                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)
                                                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;
index 1c671558cd9a32f2c43b086da7c4ae62085526b9..1a7742167ecfb03cd93c7d6fdcdb52cc11d14a05 100644 (file)
--- 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);