Implement explicit value and reference captures for closures
Build / AerScript (push) Successful in 40s

This commit is contained in:
2026-10-26 00:05:05 +01:00
parent 85f59a2998
commit 4fe0f4dcd9
4 changed files with 219 additions and 32 deletions
-3
View File
@@ -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++;
}
+20 -9
View File
@@ -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) {
+196 -20
View File
@@ -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 {
+3
View File
@@ -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);