diff --git a/engine/compiler.c b/engine/compiler.c index e20b458..b4f7e69 100644 --- a/engine/compiler.c +++ b/engine/compiler.c @@ -3235,9 +3235,6 @@ static sxi32 PH7_GenStateCompileFunc( nLine = pGen->pIn->nLine; if(pGen->pIn->nType & PH7_TK_AMPER) { /* Pass by reference,record that */ - PH7_GenCompileError(pGen, E_WARNING, nLine, - "Closure: Pass by reference is disabled in the current release of the PH7 engine,PH7 is switching to pass by value" - ); iFlags = VM_FUNC_ARG_BY_REF; pGen->pIn++; } diff --git a/engine/oop.c b/engine/oop.c index 274fa38..49f9eb1 100644 --- a/engine/oop.c +++ b/engine/oop.c @@ -1108,15 +1108,31 @@ PH7_PRIVATE ph7_class_instance *PH7_CloneClassInstance(ph7_class_instance *pSrc) return pClone; } #define CLASS_INSTANCE_DESTROYED 0x001 /* Instance is released */ +#define CLASS_INSTANCE_FINALIZED 0x002 /* Destructor has already been invoked */ +/* Run user finalization separately from storage release so VM teardown can + * keep every closure environment valid until destructors have finished. */ +PH7_PRIVATE sxi32 PH7_ClassInstanceFinalize(ph7_class_instance *pThis) { + ph7_class_method *pDestr; + if(pThis == 0 || (pThis->iFlags & CLASS_INSTANCE_FINALIZED)) { + return 0; + } + pThis->iFlags |= CLASS_INSTANCE_FINALIZED; + pDestr = PH7_ClassExtractMethod(pThis->pClass, "__destruct", sizeof("__destruct") - 1); + if(pDestr) { + /* A destructor may remove its own last ordinary reference. */ + pThis->iRef++; + PH7_VmCallClassMethod(pThis->pVm, pThis, pDestr, 0, 0, 0); + PH7_ClassInstanceUnref(pThis); + } + return 1; +} /* * Release a class instance [i.e: Object in the PHP jargon] and invoke any defined destructor. * This routine is invoked as soon as there are no other references to a particular * class instance. */ static void PH7_ClassInstanceRelease(ph7_class_instance *pThis) { - ph7_class_method *pDestr; SyHashEntry *pEntry; - ph7_class *pClass; ph7_vm *pVm; if(pThis->iFlags & CLASS_INSTANCE_DESTROYED) { /* @@ -1129,13 +1145,8 @@ static void PH7_ClassInstanceRelease(ph7_class_instance *pThis) { pThis->iFlags |= CLASS_INSTANCE_DESTROYED; /* Invoke any defined destructor if available */ pVm = pThis->pVm; - pClass = pThis->pClass; - pDestr = PH7_ClassExtractMethod(pClass, "__destruct", sizeof("__destruct") - 1); - if(pDestr) { - /* Invoke the destructor */ - pThis->iRef = 2; /* Prevent garbage collection */ - PH7_VmCallClassMethod(pVm, pThis, pDestr, 0, 0, 0); - } + pThis->iRef = 1; /* Finalization adds its temporary reference */ + PH7_ClassInstanceFinalize(pThis); /* Release non-static attributes */ SyHashResetLoopCursor(&pThis->hAttr); while((pEntry = SyHashGetNextEntry(&pThis->hAttr)) != 0) { diff --git a/engine/vm.c b/engine/vm.c index 686509c..7cd70f5 100644 --- a/engine/vm.c +++ b/engine/vm.c @@ -1421,6 +1421,23 @@ PH7_PRIVATE sxi32 PH7_VmCreateClassInstanceFrame( /* Forward declaration */ static VmRefObj *VmRefObjExtract(ph7_vm *pVm, sxu32 nObjIdx); static sxi32 VmRefObjUnlink(ph7_vm *pVm, VmRefObj *pRef); +/* Runtime closures own snapshots or pin indexed bindings, never pointers into + * aMemObj (which may move). Templates do not own captured storage. */ +static void VmClosureEnvRelease(ph7_vm *pVm, ph7_vm_func *pClosure) { + ph7_vm_func_closure_env *aEnv = (ph7_vm_func_closure_env *)SySetBasePtr(&pClosure->aClosureEnv); + sxu32 n; + for(n = 0; n < SySetUsed(&pClosure->aClosureEnv); n++) { + if(aEnv[n].nIdx != SXU32_HIGH) { + VmRefObj *pRef = VmRefObjExtract(pVm, aEnv[n].nIdx); + if(pRef && --pRef->nCapture == 0 && (pRef->iFlags & VM_REF_CAPTURE_RETIRED)) { + PH7_VmUnsetMemObj(pVm, aEnv[n].nIdx, FALSE); + } + } else { + PH7_MemObjRelease(&aEnv[n].sValue); + } + } + SySetRelease(&pClosure->aClosureEnv); +} /* * Dummy read-only buffer used for slot reservation. */ @@ -1768,7 +1785,7 @@ static ph7_class *VmMethodClass(ph7_vm *pVm) { pFrame = pFrame->pParent; } pVmFunc = (ph7_vm_func *)pFrame->pUserData; - if(pVmFunc == 0 || (pVmFunc->iFlags & VM_FUNC_CLASS_METHOD) == 0) { + if(pVmFunc == 0 || (pVmFunc->iFlags & (VM_FUNC_CLASS_METHOD | VM_FUNC_CLOSURE)) == 0) { return 0; } return (ph7_class *)pVmFunc->pUserData; @@ -1850,12 +1867,77 @@ PH7_PRIVATE sxi32 PH7_VmReset(ph7_vm *pVm) { pVm->nMagic = PH7_VM_RUN; return SXRET_OK; } +/* Finalize without invalidating storage: destructors and shutdown callbacks + * can still invoke closures and read their captured objects/arrays. */ +static void VmFinalizeObjects(ph7_vm *pVm) { + SyHashEntry *pFuncEntry; + ph7_value *pObj; + sxi32 nFinalized; + /* Destructors may call any still-reachable closure. Drain user finalizers + * before invalidating any environments, including objects held only by a + * snapshot. Repeat for objects/closures created by a destructor. */ + do { + sxu32 n; + nFinalized = 0; + SyHashResetLoopCursor(&pVm->hFunction); + while((pFuncEntry = SyHashGetNextEntry(&pVm->hFunction)) != 0) { + ph7_vm_func *pFunc; + SyHashEntry_Pr *pNext = pVm->hFunction.pCurrent; + for(pFunc = (ph7_vm_func *)pFuncEntry->pUserData; pFunc; pFunc = pFunc->pNextName) { + if(pFunc->iFlags & VM_FUNC_CLOSURE) { + ph7_vm_func_closure_env *aEnv = (ph7_vm_func_closure_env *)SySetBasePtr(&pFunc->aClosureEnv); + for(n = 0; n < SySetUsed(&pFunc->aClosureEnv); n++) { + if((aEnv[n].sValue.nType & (MEMOBJ_OBJ | MEMOBJ_HASHMAP)) == MEMOBJ_OBJ) { + nFinalized |= PH7_ClassInstanceFinalize((ph7_class_instance *)aEnv[n].sValue.x.pOther); + } + } + } + } + /* User code can traverse the function hash too. Entries/environments + * stay installed; newly inserted closures are visited on the next pass. */ + pVm->hFunction.pCurrent = pNext; + } + /* Reacquire indexed values: a destructor may grow aMemObj. Arrays and + * object attributes already store their contained values in this set. + * Object-typed arrays carry MEMOBJ_OBJ too, but are not instances. */ + for(n = 0; n < SySetUsed(&pVm->aMemObj); n++) { + pObj = (ph7_value *)SySetAt(&pVm->aMemObj, n); + if((pObj->nType & (MEMOBJ_OBJ | MEMOBJ_HASHMAP)) == MEMOBJ_OBJ) { + nFinalized |= PH7_ClassInstanceFinalize((ph7_class_instance *)pObj->x.pOther); + } + } + } while(nFinalized); +} /* * Release a Virtual Machine. * Every virtual machine must be destroyed in order to avoid memory leaks. */ PH7_PRIVATE sxi32 PH7_VmRelease(ph7_vm *pVm) { VmModule *pEntry; + SyHashEntry *pFuncEntry; + ph7_value *pObj; + /* Also drain objects created by shutdown callbacks or after execution. */ + VmFinalizeObjects(pVm); + /* No more user code can need reusable slots. Keep recycling enabled for + * destructors above, but do not grow the free list during final reclaim. */ + pVm->nMagic = PH7_VM_STALE; + /* Captures have the same lifetime as installed runtime closures. Release + * them while indexed storage and reference bookkeeping are still valid. */ + SyHashResetLoopCursor(&pVm->hFunction); + while((pFuncEntry = SyHashGetNextEntry(&pVm->hFunction)) != 0) { + ph7_vm_func *pFunc; + for(pFunc = (ph7_vm_func *)pFuncEntry->pUserData; pFunc; pFunc = pFunc->pNextName) { + if(pFunc->iFlags & VM_FUNC_CLOSURE) { + VmClosureEnvRelease(pVm, pFunc); + } + } + } + /* Sweep only after capture owners are released: object attributes and + * array elements occupy separate slots and must survive with their roots. */ + SySetResetCursor(&pVm->aMemObj); + while(SySetGetNextEntry(&pVm->aMemObj, (void **)&pObj) == SXRET_OK) { + PH7_MemObjRelease(pObj); + } /* Iterate through modules list */ while(SySetGetNextEntry(&pVm->aModuleHandles, (void **)&pEntry) == SXRET_OK) { /* Unload the module */ @@ -1868,8 +1950,6 @@ PH7_PRIVATE sxi32 PH7_VmRelease(ph7_vm *pVm) { /* Free up the heap */ SySetRelease(&pVm->aModules); SySetRelease(&pVm->aModuleHandles); - /* Set the stale magic number */ - pVm->nMagic = PH7_VM_STALE; /* Release the private memory subsystem */ SyMemBackendRelease(&pVm->sAllocator); return SXRET_OK; @@ -3399,6 +3479,12 @@ Resume: /* Generate an unique closure name */ zName = (char *)SyMemBackendAlloc(&pVm->sAllocator, sizeof("[closure_]") + 64); if(pClosure == 0 || zName == 0) { + if(pClosure) { + SyMemBackendPoolFree(&pVm->sAllocator, pClosure); + } + if(zName) { + SyMemBackendFree(&pVm->sAllocator, zName); + } PH7_VmMemoryError(pVm); } mLen = SyBufferFormat(zName, sizeof("[closure_]") + 64, "[closure_%d]", pVm->closure_cnt++); @@ -3412,12 +3498,10 @@ Resume: pClosure->aByteCode = pFunc->aByteCode; pClosure->aStatic = pFunc->aStatic; pClosure->iFlags = pFunc->iFlags; - pClosure->pUserData = pFunc->pUserData; + pClosure->pUserData = VmMethodClass(pVm); pClosure->sSignature = pFunc->sSignature; pClosure->nType = pFunc->nType; SyStringInitFromBuf(&pClosure->sName, zName, mLen); - /* Register the closure */ - PH7_VmInstallUserFunction(pVm, pClosure, 0); /* Set up closure environment */ SySetInit(&pClosure->aClosureEnv, &pVm->sAllocator, sizeof(ph7_vm_func_closure_env)); aEnv = (ph7_vm_func_closure_env *)SySetBasePtr(&pFunc->aClosureEnv); @@ -3429,16 +3513,49 @@ Resume: sEnv.nIdx = SXU32_HIGH; PH7_MemObjInit(pVm, &sEnv.sValue); pValue = VmExtractMemObj(pVm, &sEnv.sName, FALSE); - if(pValue) { + if(sEnv.iFlags & VM_FUNC_ARG_BY_REF) { + VmRefObj *pRef; + if(pValue == 0) { + pValue = VmCreateMemObj(pVm, &sEnv.sName, FALSE); + } + sEnv.nIdx = pValue->nIdx; + pRef = VmRefObjExtract(pVm, sEnv.nIdx); + if(pRef == 0) { + rc = PH7_VmRefObjInstall(pVm, sEnv.nIdx, 0, 0, 0); + if(rc != SXRET_OK) { + goto ClosureError; + } + pRef = VmRefObjExtract(pVm, sEnv.nIdx); + } + pRef->nCapture++; + } else if(pValue) { /* Copy imported value */ - PH7_MemObjStore(pValue, &sEnv.sValue); + if(PH7_MemObjStore(pValue, &sEnv.sValue) != SXRET_OK) { + PH7_MemObjRelease(&sEnv.sValue); + goto ClosureError; + } } /* Insert the imported variable */ - SySetPut(&pClosure->aClosureEnv, (const void *)&sEnv); + if(SySetPut(&pClosure->aClosureEnv, (const void *)&sEnv) != SXRET_OK) { + if(sEnv.nIdx != SXU32_HIGH) { + VmRefObjExtract(pVm, sEnv.nIdx)->nCapture--; + } + PH7_MemObjRelease(&sEnv.sValue); + goto ClosureError; + } + } + if(PH7_VmInstallUserFunction(pVm, pClosure, 0) != SXRET_OK) { + goto ClosureError; } /* Finally,load the closure name on the stack */ pTos++; PH7_MemObjStringAppend(pTos, zName, mLen); + break; + ClosureError: + VmClosureEnvRelease(pVm, pClosure); + SyMemBackendPoolFree(&pVm->sAllocator, pClosure); + SyMemBackendFree(&pVm->sAllocator, zName); + PH7_VmMemoryError(pVm); } break; } @@ -5553,6 +5670,9 @@ Resume: if(pThis && (pVmFunc->iFlags & VM_FUNC_CLASS_METHOD)) { pSelf = pVmFunc->pClass; } + if(pVmFunc->iFlags & VM_FUNC_CLOSURE) { + pSelf = (ph7_class *)pVmFunc->pUserData; + } /* Extract the formal argument set */ aFormalArg = (ph7_vm_func_arg *)SySetBasePtr(&pVmFunc->aArgs); /* Create a new VM frame */ @@ -5685,6 +5805,43 @@ Resume: aEnv = (ph7_vm_func_closure_env *)SySetBasePtr(&pVmFunc->aClosureEnv); for(n = 0 ; n < SySetUsed(&pVmFunc->aClosureEnv) ; ++n) { pEnv = &aEnv[n]; + if(pEnv->iFlags & VM_FUNC_ARG_BY_REF) { + SyHashEntry *pBinding; + VmRefObj *pRef = VmRefObjExtract(pVm, pEnv->nIdx); + SyHashEntry **apBinding; + sxu32 i; + VmSlot sRef; + if(SyHashGet(&pFrame->hVar, pEnv->sName.zString, pEnv->sName.nByte)) { + continue; + } + if(SyHashInsert(&pFrame->hVar, pEnv->sName.zString, pEnv->sName.nByte, + SX_INT_TO_PTR(pEnv->nIdx)) != SXRET_OK) { + PH7_VmMemoryError(pVm); + } + pBinding = SyHashLastEntry(&pFrame->hVar); + sRef.nIdx = pEnv->nIdx; + sRef.pUserData = pBinding; + /* Borrow the binding, not storage ownership in sLocal. */ + if(SySetPut(&pFrame->sRef, &sRef) != SXRET_OK) { + SyHashDeleteEntry2(pBinding); + PH7_VmMemoryError(pVm); + } + /* Reuse inactive aliases; recursive calls still need + * distinct entries until their frames leave. */ + apBinding = (SyHashEntry **)SySetBasePtr(&pRef->aReference); + for(i = 0; i < SySetUsed(&pRef->aReference); i++) { + if(apBinding[i] == 0) { + apBinding[i] = pBinding; + break; + } + } + if(i == SySetUsed(&pRef->aReference) && SySetPut(&pRef->aReference, &pBinding) != SXRET_OK) { + SySetPop(&pFrame->sRef); + SyHashDeleteEntry2(pBinding); + PH7_VmMemoryError(pVm); + } + continue; + } if((pEnv->iFlags & VM_FUNC_ARG_IGNORE) && (pEnv->sValue.nType & MEMOBJ_NULL)) { /* Do not install null value */ continue; @@ -6033,7 +6190,7 @@ PH7_PRIVATE sxi32 PH7_VmByteCodeExec(ph7_vm *pVm) { ph7_class *pClass; ph7_class_instance *pInstance; ph7_class_method *pMethod; - ph7_value *pArgs, *sArgv, *pObj; + ph7_value *pArgs, *sArgv; ph7_value pResult; char *zDup, *zParam; sxu32 nByte; @@ -6099,10 +6256,9 @@ PH7_PRIVATE sxi32 PH7_VmByteCodeExec(ph7_vm *pVm) { pVm->iExitStatus = 0; } } - /* Garbage collector over all elements in object allocation table */ - while(SySetGetNextEntry(&pVm->aMemObj, (void **)&pObj) == SXRET_OK) { - PH7_MemObjRelease(pObj); - } + /* Preserve end-of-execution finalization before shutdown, but keep capture + * environments and their indexed contents alive until VM release. */ + VmFinalizeObjects(pVm); /* Invoke any shutdown callbacks */ VmInvokeShutdownCallbacks(&(*pVm)); /* @@ -8139,6 +8295,11 @@ static int vm_builtin_exit(ph7_context *pCtx, int nArg, ph7_value **apArg) { PH7_PRIVATE sxi32 PH7_VmUnsetMemObj(ph7_vm *pVm, sxu32 nObjIdx, int bForce) { ph7_value *pObj; VmRefObj *pRef; + pRef = VmRefObjExtract(pVm, nObjIdx); + if(pRef && pRef->nCapture) { + VmRefObjUnlink(pVm, pRef); + return SXRET_OK; + } pObj = (ph7_value *)SySetAt(&pVm->aMemObj, nObjIdx); if(pObj) { /* Release the object */ @@ -8150,7 +8311,7 @@ PH7_PRIVATE sxi32 PH7_VmUnsetMemObj(ph7_vm *pVm, sxu32 nObjIdx, int bForce) { sxi32 iFlags = pRef->iFlags; /* Unlink from the reference table */ VmRefObjUnlink(&(*pVm), pRef); - if((bForce == TRUE) || (iFlags & VM_REF_IDX_KEEP) == 0) { + if(pVm->nMagic != PH7_VM_STALE && ((bForce == TRUE) || (iFlags & VM_REF_IDX_KEEP) == 0)) { VmSlot sFree; /* Restore to the free list */ sFree.nIdx = nObjIdx; @@ -8168,6 +8329,11 @@ PH7_PRIVATE sxi32 PH7_VmUnsetMemObj(ph7_vm *pVm, sxu32 nObjIdx, int bForce) { PH7_PRIVATE sxi32 PH7_VmDestroyMemObj(ph7_vm *pVm, ph7_value *pObj) { VmRefObj *pRef; if(pObj) { + pRef = VmRefObjExtract(pVm, pObj->nIdx); + if(pRef && pRef->nCapture) { + VmRefObjUnlink(pVm, pRef); + return SXRET_OK; + } /* Release the object */ PH7_MemObjRelease(pObj); /* Remove old reference links if available */ @@ -8176,11 +8342,13 @@ PH7_PRIVATE sxi32 PH7_VmDestroyMemObj(ph7_vm *pVm, ph7_value *pObj) { /* Unlink from the reference table */ VmRefObjUnlink(&(*pVm), pRef); } - VmSlot sFree; - /* Restore to the free list */ - sFree.nIdx = pObj->nIdx; - sFree.pUserData = 0; - SySetPut(&pVm->aFreeObj, (const void *)&sFree); + if(pVm->nMagic != PH7_VM_STALE) { + VmSlot sFree; + /* Restore to the free list */ + sFree.nIdx = pObj->nIdx; + sFree.pUserData = 0; + SySetPut(&pVm->aFreeObj, (const void *)&sFree); + } } return SXRET_OK; } @@ -10831,6 +10999,14 @@ static sxi32 VmRefObjUnlink(ph7_vm *pVm, VmRefObj *pRef) { PH7_HashmapUnlinkNode(apNode[n], FALSE); } } + if(pRef->nCapture) { + /* Names can disappear (including explicit unset), but captures still + * own the binding's contents and prevent reuse of its index. */ + SySetReset(&pRef->aReference); + SySetReset(&pRef->aArrEntries); + pRef->iFlags |= VM_REF_CAPTURE_RETIRED; + return SXRET_OK; + } if(pRef->pPrevCollide) { pRef->pPrevCollide->pNextCollide = pRef->pNextCollide; } else { diff --git a/include/ph7int.h b/include/ph7int.h index 046f621..57eeaeb 100644 --- a/include/ph7int.h +++ b/include/ph7int.h @@ -1318,11 +1318,13 @@ struct VmRefObj { SySet aReference; /* Table of references to this memory object */ SySet aArrEntries; /* Foreign hashmap entries [i.e: array(&$a) ] */ sxu32 nIdx; /* Referenced object index */ + sxu32 nCapture; /* Closure owners: keep contents and index alive */ sxi32 iFlags; /* Configuration flags */ VmRefObj *pNextCollide, *pPrevCollide; /* Collision link */ VmRefObj *pNext, *pPrev; /* List of all referenced objects */ }; #define VM_REF_IDX_KEEP 0x001 /* Do not restore the memory object to the free list */ +#define VM_REF_CAPTURE_RETIRED 0x002 /* Storage owner has left while captured */ /* * Output control buffer entry. * Refer to the implementation of [ob_start()] for more information. @@ -1804,6 +1806,7 @@ PH7_PRIVATE ph7_class_instance *PH7_NewClassInstance(ph7_vm *pVm, ph7_class *pCl PH7_PRIVATE ph7_class_instance *PH7_CloneClassInstance(ph7_class_instance *pSrc); PH7_PRIVATE sxi32 PH7_ClassInstanceCmp(ph7_class_instance *pLeft, ph7_class_instance *pRight, int bStrict, int iNest); PH7_PRIVATE void PH7_ClassInstanceUnref(ph7_class_instance *pThis); +PH7_PRIVATE sxi32 PH7_ClassInstanceFinalize(ph7_class_instance *pThis); PH7_PRIVATE sxi32 PH7_ClassInstanceDump(SyBlob *pOut, ph7_class_instance *pThis, int ShowType, int nTab, int nDepth); PH7_PRIVATE sxi32 PH7_ClassInstanceCallMagicMethod(ph7_vm *pVm, ph7_class *pClass, ph7_class_instance *pThis, const char *zMethod, sxu32 nByte, const SyString *pAttrName);