diff --git a/engine/compiler.c b/engine/compiler.c index 248cecf..c6fe5eb 100644 --- a/engine/compiler.c +++ b/engine/compiler.c @@ -237,6 +237,7 @@ static sxi32 GenStateFixGoto(ph7_gen_state *pGen, sxu32 nOfft) pInstr = PH7_VmGetInstr(pGen->pVm, pJump->nInstrIdx); if(pInstr) { pInstr->iP2 = pLabel->nJumpDest; + pInstr->iP1 = pLabel->bExternal; } } return SXRET_OK; @@ -1127,7 +1128,8 @@ static sxi32 PH7_GenStateLoadLiteral(ph7_gen_state *pGen) { if(pObj == 0) { PH7_GenCompileError(pGen, E_ERROR, pToken->nLine, "PH7 engine is running out-of-memory"); } - SyString *pFile = (SyString *)SySetPeek(&pGen->pVm->aFiles); + SyString **ppFile = (SyString **)SySetPeek(&pGen->pVm->aFiles); + SyString *pFile = ppFile ? *ppFile : 0; if(pFile == 0) { SyString pMemory; SyStringInitFromBuf(&pMemory, "[MEMORY]", (int)sizeof("[MEMORY]") - 1); @@ -1380,17 +1382,22 @@ static sxi32 PH7_CompileContinue(ph7_gen_state *pGen) { PH7_GenCompileError(pGen, E_ERROR, nLine, "A 'continue' statement may only be used within a loop"); } else { sxu32 nInstrIdx = 0; - if(!pLoop->bPostContinue) { - /* Emit the OP_LF_STOP instruction to leave the loop frame */ - PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_LF_STOP, 0, 0, 0, 0); - } - PH7_VmEmitInstr(pGen->pVm, 0, PH7_OP_JMP, 0, pLoop->nFirstInstr, 0, &nInstrIdx); - if(pLoop->bPostContinue) { - JumpFixup sJumpFix; - /* Post-continue */ - sJumpFix.nJumpType = PH7_OP_JMP; - sJumpFix.nInstrIdx = nInstrIdx; - SySetPut(&pLoop->aPostContFix, (const void *)&sJumpFix); + if(pLoop->iFlags & GEN_BLOCK_INCLUDE) { + ph7_include *pInclude = (ph7_include *)pLoop->pUserData; + PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_JMP, 1, pInclude->nContinue, 0, 0); + } else { + if(!pLoop->bPostContinue) { + /* Emit the OP_LF_STOP instruction to leave the loop frame */ + PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_LF_STOP, 0, 0, 0, 0); + } + PH7_VmEmitInstr(pGen->pVm, 0, PH7_OP_JMP, 0, pLoop->nFirstInstr, 0, &nInstrIdx); + if(pLoop->bPostContinue) { + JumpFixup sJumpFix; + /* Post-continue */ + sJumpFix.nJumpType = PH7_OP_JMP; + sJumpFix.nInstrIdx = nInstrIdx; + SySetPut(&pLoop->aPostContFix, (const void *)&sJumpFix); + } } } if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) == 0) { @@ -1421,14 +1428,19 @@ static sxi32 PH7_CompileBreak(ph7_gen_state *pGen) { PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "A 'break' statement may only be used within a loop or switch"); } else { sxu32 nInstrIdx; - if((pLoop->iFlags & GEN_BLOCK_SWITCH) == 0) { - /* Emit the OP_LF_STOP instruction to leave the loop frame */ - PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_LF_STOP, 0, 0, 0, 0); - } - rc = PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_JMP, 0, 0, 0, &nInstrIdx); - if(rc == SXRET_OK) { - /* Fix the jump later when the jump destination is resolved */ - PH7_GenStateNewJumpFixup(pLoop, PH7_OP_JMP, nInstrIdx); + if(pLoop->iFlags & GEN_BLOCK_INCLUDE) { + ph7_include *pInclude = (ph7_include *)pLoop->pUserData; + PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_JMP, 1, pInclude->nBreak, 0, 0); + } else { + if((pLoop->iFlags & GEN_BLOCK_SWITCH) == 0) { + /* Emit the OP_LF_STOP instruction to leave the loop frame */ + PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_LF_STOP, 0, 0, 0, 0); + } + rc = PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_JMP, 0, 0, 0, &nInstrIdx); + if(rc == SXRET_OK) { + /* Fix the jump later when the jump destination is resolved */ + PH7_GenStateNewJumpFixup(pLoop, PH7_OP_JMP, nInstrIdx); + } } } if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) == 0) { @@ -1469,6 +1481,7 @@ static sxi32 PH7_CompileLabel(ph7_gen_state *pGen) } SyStringInitFromBuf(&sLabel.sName, zDup, pTarget->nByte); sLabel.bRef = FALSE; + sLabel.bExternal = FALSE; sLabel.nLine = pGen->pIn->nLine; pBlock = pGen->pCurrent; while(pBlock) { @@ -2315,10 +2328,9 @@ static sxi32 PH7_CompileIf(ph7_gen_state *pGen) { * of the current function, and returns its argument as the value of the function call. * return() will also end the execution of an eval() statement or script file. * If called from the global scope, then execution of the current script file is ended. - * If the current script file was include()ed or require()ed, then control is passed back - * to the calling file. Furthermore, if the current script file was include()ed, then the value - * given to return() will be returned as the value of the include() call. If return() is called - * from within the main script file, then script execution end. + * An included method/closure chunk shares its caller's return boundary. A required + * global script retains its own execution boundary. If return() is called from + * within the main script file, then script execution ends. * Note that since return() is a language construct and not a function, the parentheses * surrounding its arguments are not required. It is common to leave them out, and you actually * should do so as Aer has less work to do in this case. @@ -2670,6 +2682,30 @@ static sxi32 PH7_CompileImport(ph7_gen_state *pGen) { return SXRET_OK; } +/* Resolve include-to-owner gotos only after the owner's labels are known. + * Bridges also let nested includes forward a transfer one bytecode at a time. */ +static void GenStateFixIncludeLabels(ph7_gen_state *pGen) { + sxu32 n, j, nCount = PH7_VmInstrLength(pGen->pVm); + for(n = 0; n < nCount; n++) { + VmInstr *pInstr = PH7_VmGetInstr(pGen->pVm, n); + if(pInstr->iOp == PH7_OP_INCLUDE && !pInstr->iP1 && pInstr->iP2) { + ph7_include *pInclude = (ph7_include *)pInstr->p3; + sxu32 nSkip; + PH7_VmEmitInstr(pGen->pVm, 0, PH7_OP_JMP, 0, 0, 0, &nSkip); + for(j = 0; j < SySetUsed(&pGen->aLabel); j++) { + Label sLabel = *(Label *)SySetAt(&pGen->aLabel, j); + if(sLabel.pFunc == pInclude->pFunc) { + sxu32 nDest = PH7_VmInstrLength(pGen->pVm); + PH7_VmEmitInstr(pGen->pVm, 0, PH7_OP_JMP, sLabel.bExternal, sLabel.nJumpDest, 0, 0); + sLabel.nJumpDest = nDest; + sLabel.bExternal = TRUE; + SySetPut(&pInclude->aLabel, &sLabel); + } + } + PH7_VmGetInstr(pGen->pVm, nSkip)->iP2 = PH7_VmInstrLength(pGen->pVm); + } + } +} /* * Compile the 'include' and 'require' statements */ @@ -2678,8 +2714,13 @@ static sxi32 PH7_CompileInclude(ph7_gen_state *pGen) { sxu32 nKey = (sxu32)(SX_PTR_TO_INT(pGen->pIn->pUserData)); sxu32 nLine = pGen->pIn->nLine; sxi32 iP1 = (nKey == PH7_KEYWORD_REQUIRE) ? 1 : 0; + sxi32 bParen = FALSE; /* Jump the 'include' or 'require' keyword */ pGen->pIn++; + if(!iP1 && pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_LPAREN)) { + bParen = TRUE; + pGen->pIn++; + } if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_SSTR | PH7_TK_DSTR)) == 0) { if(pGen->pIn >= pGen->pEnd) { pGen->pIn--; @@ -2689,12 +2730,60 @@ static sxi32 PH7_CompileInclude(ph7_gen_state *pGen) { } zFile = SyMemBackendStrDup(&pGen->pVm->sAllocator, pGen->pIn->sData.zString, pGen->pIn->sData.nByte); pGen->pIn++; + if(bParen) { + if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_RPAREN) == 0) { + PH7_GenCompileError(pGen, E_ERROR, nLine, "Include: expecting ')'"); + } + pGen->pIn++; + } if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI/*';'*/) == 0) { /* Unexpected token */ PH7_GenCompileError(&(*pGen), E_ERROR, nLine, "Include: Unexpected token '%z', expecting ';'", &pGen->pIn->sData); } - PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_INCLUDE, iP1, 0, zFile, 0); + if(iP1) { + PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_INCLUDE, iP1, 0, zFile, 0); + } else { + ph7_include *pInclude = (ph7_include *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(ph7_include)); + GenBlock *pBlock = pGen->pCurrent; + GenBlock *pLoop = PH7_GenStateFetchBlock(pBlock, GEN_BLOCK_LOOP, 0); + sxu32 nSkip; + if(pInclude == 0) { + PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 is running out-of-memory"); + } + SyZero(pInclude, sizeof(*pInclude)); + pInclude->zPath = zFile; + pInclude->nBreak = pInclude->nContinue = SXU32_HIGH; + SySetInit(&pInclude->aLabel, &pGen->pVm->sAllocator, sizeof(Label)); + while(pBlock && (pBlock->iFlags & GEN_BLOCK_FUNC) == 0) { + if(pBlock->iFlags & GEN_BLOCK_EXCEPTION) { + pInclude->bException = TRUE; + } + pBlock = pBlock->pParent; + } + pInclude->pFunc = pBlock ? (ph7_vm_func *)pBlock->pUserData : 0; + PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_INCLUDE, 0, 1, pInclude, 0); + if(pLoop) { + PH7_VmEmitInstr(pGen->pVm, 0, PH7_OP_JMP, 0, 0, 0, &nSkip); + pInclude->nBreak = PH7_VmInstrLength(pGen->pVm); + /* Reuse the ordinary compiler's loop cleanup and forward fixups. */ + SyToken sToken; + SyToken *pSavedIn = pGen->pIn, *pSavedEnd = pGen->pEnd; + SyZero(&sToken, sizeof(sToken)); + sToken.nLine = nLine; + pGen->pIn = &sToken; + pGen->pEnd = &sToken + 1; + PH7_CompileBreak(pGen); + if((pLoop->iFlags & GEN_BLOCK_SWITCH) == 0) { + pInclude->nContinue = PH7_VmInstrLength(pGen->pVm); + pGen->pIn = &sToken; + PH7_CompileContinue(pGen); + } + pGen->pIn = pSavedIn; + pGen->pEnd = pSavedEnd; + PH7_VmGetInstr(pGen->pVm, nSkip)->iP2 = PH7_VmInstrLength(pGen->pVm); + } + } return SXRET_OK; } /* @@ -3013,6 +3102,7 @@ static sxi32 PH7_GenStateCompileFuncBody( if(SXERR_ABORT == GenStateFixGoto(&(*pGen), nGotoOfft)) { rc = SXERR_ABORT; } + GenStateFixIncludeLabels(pGen); SySetTruncate(&pGen->aGoto,nGotoOfft); /* Restore the default container */ PH7_VmSetByteCodeContainer(pGen->pVm, pInstrContainer); @@ -5224,6 +5314,7 @@ static sxi32 PH7_CompileScript( if(SXERR_ABORT == GenStateFixGoto(&(*pGen), 0)) { rc = SXERR_ABORT; } + GenStateFixIncludeLabels(pGen); /* Reset container */ SySetReset(&pGen->aGoto); SySetReset(&pGen->aLabel); @@ -5282,6 +5373,48 @@ PH7_PRIVATE sxi32 PH7_CompileAerScript( SySetRelease(&aAerToken); return rc; } +/* Compile an include without resetting a suspended run-time compilation. */ +PH7_PRIVATE sxi32 PH7_CompileIncludedChunk(ph7_vm *pVm, SyString *pScript, ph7_vm_func *pFunc, ph7_include *pInclude) { + ph7_gen_state sSaved = pVm->sCodeGen; + ph7_gen_state *pGen = &pVm->sCodeGen; + ph7_class_info sClass; + sxi32 rc; + PH7_InitCodeGenerator(pVm, pVm->pEngine->xConf.xErr, pVm->pEngine->xConf.pErrData); + if(pFunc) { + if(pFunc->pClass) { + SyZero(&sClass, sizeof(sClass)); + sClass.sName = pFunc->pClass->sName; + PH7_GenStateEnterBlock(pGen, GEN_BLOCK_CLASS, 0, &sClass, 0); + } + PH7_GenStateEnterBlock(pGen, GEN_BLOCK_PROTECTED | GEN_BLOCK_FUNC, 0, pFunc, 0); + } + if(pInclude) { + sxu32 n; + for(n = 0; n < SySetUsed(&pInclude->aLabel); n++) { + Label sLabel = *(Label *)SySetAt(&pInclude->aLabel, n); + /* A using closure executes a clone, but its labels belong to the + * compiled template. Rebind only this compilation's borrowed copy. */ + sLabel.pFunc = pFunc; + SySetPut(&pGen->aLabel, &sLabel); + } + if(pInclude->bException) { + PH7_GenStateEnterBlock(pGen, GEN_BLOCK_EXCEPTION, 0, 0, 0); + } + if(pInclude->nBreak != SXU32_HIGH) { + PH7_GenStateEnterBlock(pGen, GEN_BLOCK_LOOP | GEN_BLOCK_INCLUDE | + (pInclude->nContinue == SXU32_HIGH ? GEN_BLOCK_SWITCH : 0), 0, pInclude, 0); + } + } + rc = PH7_CompileAerScript(pVm, pScript, PH7_AERSCRIPT_CHNK | PH7_AERSCRIPT_FILE); + PH7_ResetCodeGenerator(pVm, sSaved.xErr, sSaved.pErrData); + PH7_GenStateReleaseBlock(&pGen->sGlobal); + SySetRelease(&pGen->aLabel); + SySetRelease(&pGen->aGoto); + SyHashRelease(&pGen->hLiteral); + SyHashRelease(&pGen->hVar); + pVm->sCodeGen = sSaved; + return rc; +} /* * Utility routines.Initialize the code generator. */ @@ -5350,12 +5483,14 @@ PH7_PRIVATE sxi32 PH7_GenCompileError(ph7_gen_state *pGen, sxi32 nErrType, sxu32 SyBlob *pWorker = &pGen->sErrBuf; const char *zErr; SyString *pFile; + SyString **ppFile; va_list ap; sxi32 rc; /* Reset the working buffer */ SyBlobReset(pWorker); /* Peek the processed file path if available */ - pFile = (SyString *)SySetPeek(&pGen->pVm->aFiles); + ppFile = (SyString **)SySetPeek(&pGen->pVm->aFiles); + pFile = ppFile ? *ppFile : 0; if(pGen->xErr == 0) { /* No available error consumer, return immediately */ return SXRET_OK; diff --git a/engine/hashmap.c b/engine/hashmap.c index 1d1f97e..8504d7b 100644 --- a/engine/hashmap.c +++ b/engine/hashmap.c @@ -1197,7 +1197,7 @@ PH7_PRIVATE sxi32 PH7_HashmapCreateSuper(ph7_vm *pVm) { ph7_release_value(&(*pVm), pSuper); } /* Set some $_SERVER entries */ - pFile = (SyString *)SySetPeek(&pVm->aFiles); + pFile = SySetUsed(&pVm->aFiles) ? *(SyString **)SySetPeek(&pVm->aFiles) : 0; /* * 'SCRIPT_FILENAME' * The absolute pathname of the currently executing script. diff --git a/engine/vm.c b/engine/vm.c index d7be5b3..2a1e340 100644 --- a/engine/vm.c +++ b/engine/vm.c @@ -302,7 +302,8 @@ PH7_PRIVATE sxi32 PH7_VmEmitInstr( VmInstr sInstr; sxi32 rc; /* Extract the processed script */ - SyString *pFile = (SyString *)SySetPeek(&pVm->aFiles); + SyString **ppFile = (SyString **)SySetPeek(&pVm->aFiles); + SyString *pFile = ppFile ? *ppFile : 0; static const SyString sFileName = { "[MEMORY]", sizeof("[MEMORY]") - 1}; if(pFile == 0) { pFile = (SyString *)&sFileName; @@ -807,7 +808,11 @@ static sxi32 VmOverloadDominates(const sxi32 *aFirst, const sxi32 *aSecond, sxu3 return nFirstDefaults < nSecondDefaults; } /* Forward declaration */ -static sxi32 VmExecIncludedFile(ph7_vm *pVm, SyString *pPath, int iFlags); +#define VM_EXEC_INCLUDE 2 +#define VM_INCLUDE_RETURN 3 +#define VM_INCLUDE_JUMP 4 +#define VM_INCLUDE_EXCEPTION 5 +static sxi32 VmExecIncludedFile(ph7_vm *pVm, SyString *pPath, int iFlags, ph7_value *pResult, ph7_include *pInclude); static sxi32 VmLocalExec(ph7_vm *pVm, SySet *pByteCode, ph7_value *pResult); static sxi32 VmClassMemberAccess(ph7_vm *pVm, ph7_class *pClass, sxi32 iProtection); static ph7_class *VmMethodClass(ph7_vm *pVm); @@ -1335,7 +1340,7 @@ PH7_PRIVATE ph7_value *VmReserveMemObj(ph7_vm *pVm, sxu32 *pIndex) { return pObj; } /* Forward declaration */ -static sxi32 VmEvalChunk(ph7_vm *pVm, ph7_context *pCtx, SyString *pChunk, int iFlags); +static sxi32 VmEvalChunk(ph7_vm *pVm, ph7_context *pCtx, SyString *pChunk, int iFlags, ph7_value *pResult, ph7_include *pInclude); /* * Built-in classes/interfaces and some functions that cannot be implemented * directly as foreign functions. @@ -1504,7 +1509,7 @@ PH7_PRIVATE sxi32 PH7_VmInit( /* Configuration containers */ SySetInit(&pVm->aModules, &pVm->sAllocator, sizeof(VmModule)); SySetInit(&pVm->aModuleHandles, &pVm->sAllocator, sizeof(VmModule)); - SySetInit(&pVm->aFiles, &pVm->sAllocator, sizeof(SyString)); + SySetInit(&pVm->aFiles, &pVm->sAllocator, sizeof(SyString *)); SySetInit(&pVm->aPaths, &pVm->sAllocator, sizeof(SyString)); SySetInit(&pVm->aIncluded, &pVm->sAllocator, sizeof(SyString)); rc = SyHashInit(&pVm->hRequired, &pVm->sAllocator, SyStrHash, (int (*)(const void *, const void *, sxu32))SyMemcmp); @@ -1558,7 +1563,7 @@ PH7_PRIVATE sxi32 PH7_VmInit( pVm->nMagic = PH7_VM_INIT; SyStringInitFromBuf(&sBuiltin, PH7_BUILTIN_LIB, sizeof(PH7_BUILTIN_LIB) - 1); /* Precompile the built-in library */ - VmEvalChunk(&(*pVm), 0, &sBuiltin, PH7_AERSCRIPT_CODE); + VmEvalChunk(&(*pVm), 0, &sBuiltin, PH7_AERSCRIPT_CODE, 0, 0); if(bDebug) { /* Enable debugging */ pVm->bDebug = TRUE; @@ -2449,13 +2454,15 @@ static sxi32 VmByteCodeExec( int nTos, /* Top entry in the operand stack (usually -1) */ ph7_value *pResult, /* Store program return value here. NULL otherwise */ sxu32 *pLastRef, /* Last referenced ph7_value index */ - int is_callback /* TRUE if we are executing a callback */ + int iExecFlags /* Callback (1) or included statements (VM_EXEC_INCLUDE) */ ) { VmInstr *pInstr; ph7_value *pTos; SySet aArg; sxi32 pc; sxi32 rc; + sxi32 bReturning = FALSE; + VmFrame *pEntryFrame = pVm->pFrame; /* Argument container */ SySetInit(&aArg, &pVm->sAllocator, sizeof(ph7_value *)); if(nTos < 0) { @@ -2465,6 +2472,7 @@ static sxi32 VmByteCodeExec( } pc = 0; /* Execute as much as we can */ +Resume: for(;;) { if(!pVm->bDebug) { /* Reset instructions set container */ @@ -2491,6 +2499,11 @@ static sxi32 VmByteCodeExec( * and return immediately. */ case PH7_OP_DONE: + bReturning = pInstr->p3 != 0; + if(bReturning && !pInstr->iP1 && (iExecFlags & VM_EXEC_INCLUDE) && pResult) { + PH7_MemObjRelease(pResult); + MemObjSetType(pResult, MEMOBJ_NULL); + } if(pInstr->iP1) { if(pTos < pStack) { goto Abort; @@ -2500,11 +2513,11 @@ static sxi32 VmByteCodeExec( } /* Unwind local loops, but let VmExecExceptionBlock() release * the handler without freeing its borrowed owner frames. */ - if(pInstr->p3) { /* Called from a return statement */ + if(pInstr->p3 && (iExecFlags & VM_EXEC_INCLUDE) == 0) { /* Called from a return statement */ while(pVm->pFrame->pParent && (pVm->pFrame->iFlags & (VM_FRAME_ACTIVE | VM_FRAME_CATCH | VM_FRAME_FINALLY)) == 0) { VmLeaveFrame(&(*pVm)); } - } else if(pVm->pFrame->iFlags & VM_FRAME_LOOP) { + } else if(!pInstr->p3 && (iExecFlags & VM_EXEC_INCLUDE) == 0 && (pVm->pFrame->iFlags & VM_FRAME_LOOP)) { while(pVm->pFrame->pParent && (pVm->pFrame->iFlags & (VM_FRAME_ACTIVE | VM_FRAME_CATCH | VM_FRAME_FINALLY)) == 0) { VmLeaveFrame(&(*pVm)); } @@ -2512,8 +2525,14 @@ static sxi32 VmByteCodeExec( if(pResult) { /* Execution result */ PH7_MemObjStore(pTos, pResult); - if(!pInstr->iP2 && pVm->pFrame->iFlags & VM_FRAME_ACTIVE) { - ph7_vm_func *pFunc = (ph7_vm_func *)pVm->pFrame->pUserData; + VmFrame *pOwner = pVm->pFrame; + if(iExecFlags & VM_EXEC_INCLUDE) { + while(pOwner->pParent && (pOwner->iFlags & VM_FRAME_ACTIVE) == 0) { + pOwner = pOwner->pParent; + } + } + if(!pInstr->iP2 && (pOwner->iFlags & VM_FRAME_ACTIVE)) { + ph7_vm_func *pFunc = (ph7_vm_func *)pOwner->pUserData; if(pFunc->nType) { if((pFunc->nType & MEMOBJ_MIXED) == 0) { if(pFunc->nType & MEMOBJ_VOID) { @@ -2539,6 +2558,9 @@ static sxi32 VmByteCodeExec( /* Nothing referenced */ *pLastRef = SXU32_HIGH; } + if(bReturning && (iExecFlags & VM_EXEC_INCLUDE)) { + goto IncludeReturn; + } goto Done; /* * HALT: P1 * * @@ -2572,12 +2594,22 @@ static sxi32 VmByteCodeExec( } goto Abort; /* - * JMP: * P2 * + * JMP: P1 P2 * * * Unconditional jump: The next instruction executed will be * the one at index P2 from the beginning of the program. + * P1 marks an include-to-owner transfer rather than a local jump. */ case PH7_OP_JMP: + if(pInstr->iP1 && (iExecFlags & VM_EXEC_INCLUDE)) { + if(pResult) { + PH7_MemObjRelease(pResult); + pResult->x.iVal = pInstr->iP2; + MemObjSetType(pResult, MEMOBJ_INT); + } + SySetRelease(&aArg); + return VM_INCLUDE_JUMP; + } pc = pInstr->iP2 - 1; break; /* @@ -4434,6 +4466,9 @@ static sxi32 VmByteCodeExec( pException->pFrame = 0; /* Leave the exception frame */ VmLeaveFrame(&(*pVm)); + if(bReturning) { + goto IncludeReturn; + } break; } /* @@ -4483,6 +4518,9 @@ static sxi32 VmByteCodeExec( } /* Pop the top entry */ VmPopOperand(&pTos, 1); + if(iExecFlags & VM_EXEC_INCLUDE) { + goto IncludeException; + } /* Perform an unconditional jump */ pc = nJump - 1; break; @@ -4568,20 +4606,50 @@ static sxi32 VmByteCodeExec( break; } /* - * OP_INCLUDE P1 * P3 + * OP_INCLUDE P1 P2 P3 * Include another source file. If P1 is zero, 'include' statement was used, otherwise it was 'require'. - * P3 contains a path to the source file. + * P3 contains a path, or lexical include metadata when P2 is set. */ case PH7_OP_INCLUDE: { - char *zFile = (char *) pInstr->p3; + ph7_include *pInclude = !pInstr->iP1 && pInstr->iP2 ? (ph7_include *)pInstr->p3 : 0; + char *zFile = pInclude ? pInclude->zPath : (char *)pInstr->p3; + ph7_value sIncludeResult; int iFlags = pInstr->iP1 ? PH7_AERSCRIPT_CODE : PH7_AERSCRIPT_CHNK; SyString sFile; if(SyStrlen(zFile) < 1) { break; } SyStringInitFromBuf(&sFile, zFile, SyStrlen(zFile)); - rc = VmExecIncludedFile(&(*pVm), &sFile, iFlags | PH7_AERSCRIPT_FILE); + PH7_MemObjInit(pVm, &sIncludeResult); + rc = VmExecIncludedFile(&(*pVm), &sFile, iFlags | PH7_AERSCRIPT_FILE, &sIncludeResult, pInclude); + if(rc == VM_INCLUDE_JUMP) { + pc = (sxi32)sIncludeResult.x.iVal - 1; + PH7_MemObjRelease(&sIncludeResult); + break; + } + if(!pInstr->iP1 && rc == VM_INCLUDE_RETURN) { + if(pResult) { + PH7_MemObjStore(&sIncludeResult, pResult); + } + PH7_MemObjRelease(&sIncludeResult); + bReturning = TRUE; + goto IncludeReturn; + } + PH7_MemObjRelease(&sIncludeResult); + if(rc == VM_INCLUDE_EXCEPTION) { + goto IncludeException; + } + if(!pInstr->iP1 && rc == SXERR_NOTFOUND) { + PH7_VmThrowError(pVm, PH7_CTX_WARNING, "IO error while including file: '%z'", &sFile); + break; + } + if(!pInstr->iP1 && rc == PH7_ABORT) { + goto Abort; + } + if(!pInstr->iP1 && rc == PH7_EXCEPTION) { + goto Exception; + } if(rc != SXRET_OK && rc != SXERR_EXISTS) { PH7_VmThrowError(pVm, PH7_CTX_ERR, "IO error while including file: '%z'", &sFile); } @@ -5541,12 +5609,12 @@ static sxi32 VmByteCodeExec( if(rc != PH7_ABORT && ((pFrame->iFlags & VM_FRAME_THROW) || rc == PH7_EXCEPTION)) { /* An exception was throw in this frame */ pFrame = pFrame->pParent; - if(!is_callback && pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) && pFrame->iExceptionJump > 0) { + if((iExecFlags & 1) == 0 && pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) && pFrame->iExceptionJump > 0) { /* Pop the result */ VmPopOperand(&pTos, 1); /* Jump to this destination */ pc = pFrame->iExceptionJump - 1; - rc = PH7_OK; + rc = (iExecFlags & VM_EXEC_INCLUDE) ? VM_INCLUDE_EXCEPTION : PH7_OK; } else { if(pFrame->pParent) { rc = PH7_EXCEPTION; @@ -5560,6 +5628,9 @@ static sxi32 VmByteCodeExec( SyMemBackendFree(&pVm->sAllocator, pFrameStack); /* Leave the frame */ VmLeaveFrame(&(*pVm)); + if(rc == VM_INCLUDE_EXCEPTION) { + goto IncludeException; + } if(rc == PH7_ABORT) { /* Abort processing immediately */ goto Abort; @@ -5637,9 +5708,50 @@ static sxi32 VmByteCodeExec( } /* Switch() */ pc++; /* Next instruction in the stream */ } /* For(;;) */ +IncludeException: + /* The catch has already run. Transfer to its owning bytecode's epilogue, + * forwarding across included chunks without throwing/catching again. */ + { + VmFrame *pFrame = pVm->pFrame; + while(pFrame->pParent) { + if((iExecFlags & VM_EXEC_INCLUDE) && pFrame == pEntryFrame) { + SySetRelease(&aArg); + while(pTos >= pStack) { + PH7_MemObjRelease(pTos); + pTos--; + } + return VM_INCLUDE_EXCEPTION; + } + if(pFrame->iFlags & VM_FRAME_EXCEPTION) { + while(pVm->pFrame != pFrame) { + VmLeaveFrame(pVm); + } + pc = pFrame->iExceptionJump; + goto Resume; + } + if(pFrame == pEntryFrame) { + /* A called method still has its own execution boundary. */ + goto Exception; + } + pFrame = pFrame->pParent; + } + } + goto Exception; +IncludeReturn: + /* The include has no return boundary. Let the owner's existing exception + * epilogue run finally before completing the pending return. */ + if((iExecFlags & VM_EXEC_INCLUDE) == 0 || pVm->pFrame != pEntryFrame) { + while(pVm->pFrame->pParent && pVm->pFrame != pEntryFrame && (pVm->pFrame->iFlags & (VM_FRAME_ACTIVE | VM_FRAME_CATCH | VM_FRAME_FINALLY | VM_FRAME_EXCEPTION)) == 0) { + VmLeaveFrame(pVm); + } + if(((iExecFlags & VM_EXEC_INCLUDE) == 0 || pVm->pFrame != pEntryFrame) && (pVm->pFrame->iFlags & VM_FRAME_EXCEPTION) && pVm->pFrame->iExceptionJump > 0) { + pc = pVm->pFrame->iExceptionJump; + goto Resume; + } + } Done: SySetRelease(&aArg); - return SXRET_OK; + return bReturning && (iExecFlags & VM_EXEC_INCLUDE) ? VM_INCLUDE_RETURN : SXRET_OK; Abort: SySetRelease(&aArg); while(pTos >= pStack) { @@ -8251,7 +8363,7 @@ static int vm_builtin_assert(ph7_context *pCtx, int nArg, ph7_value **apArg) { SyString sChunk; SyStringInitFromBuf(&sChunk, SyBlobData(&pAssert->sBlob), SyBlobLength(&pAssert->sBlob)); if(sChunk.nByte > 0) { - VmEvalChunk(pVm, pCtx, &sChunk, PH7_AERSCRIPT_CHNK | PH7_AERSCRIPT_EXPR); + VmEvalChunk(pVm, pCtx, &sChunk, PH7_AERSCRIPT_CHNK | PH7_AERSCRIPT_EXPR, 0, 0); /* Extract evaluation result */ iResult = ph7_value_to_bool(pCtx->pRet); } else { @@ -8268,8 +8380,10 @@ static int vm_builtin_assert(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_value sFile, sLine; ph7_value *apCbArg[3]; SyString *pFile; + SyString **ppFile; /* Extract the processed script */ - pFile = (SyString *)SySetPeek(&pVm->aFiles); + ppFile = (SyString **)SySetPeek(&pVm->aFiles); + pFile = ppFile ? *ppFile : 0; if(pFile == 0) { pFile = (SyString *)&sFileName; } @@ -9332,12 +9446,15 @@ static sxi32 VmEvalChunk( ph7_vm *pVm, /* Underlying Virtual Machine */ ph7_context *pCtx, /* Call Context */ SyString *pChunk, /* PHP chunk to evaluate */ - int iFlags /* Code evaluation flag */ + int iFlags, /* Code evaluation flag */ + ph7_value *pResult, /* Enclosing method/closure return value for include */ + ph7_include *pInclude /* Lexical control-flow bridges */ ) { SySet *pByteCode, aByteCode; sxi32 rc = SXRET_OK; sxi32 bRequire = (iFlags & (PH7_AERSCRIPT_CODE | PH7_AERSCRIPT_FILE)) == (PH7_AERSCRIPT_CODE | PH7_AERSCRIPT_FILE); + sxi32 bInclude = (iFlags & PH7_AERSCRIPT_FILE) && !bRequire; ProcConsumer xErr = 0; void *pErrData = 0; /* Initialize bytecode container */ @@ -9349,7 +9466,9 @@ static sxi32 VmEvalChunk( /* Log compile-time errors */ xErr = pVm->pEngine->xConf.xErr; pErrData = pVm->pEngine->xConf.pErrData; - PH7_ResetCodeGenerator(pVm, xErr, pErrData); + if(!bInclude) { + PH7_ResetCodeGenerator(pVm, xErr, pErrData); + } /* Swap bytecode container */ pByteCode = pVm->pByteContainer; pVm->pByteContainer = &aByteCode; @@ -9358,8 +9477,16 @@ static sxi32 VmEvalChunk( PH7_VmPushFilePath(pVm, "[MEMORY]", -1, TRUE, 0); } /* Compile the chunk */ - rc = PH7_CompileAerScript(pVm, pChunk, iFlags); - if(rc != SXRET_OK && bRequire) { + if(bInclude) { + VmFrame *pOwner = pVm->pFrame; + while(pOwner->pParent && (pOwner->iFlags & VM_FRAME_ACTIVE) == 0) { + pOwner = pOwner->pParent; + } + rc = PH7_CompileIncludedChunk(pVm, pChunk, (ph7_vm_func *)pOwner->pUserData, pInclude); + } else { + rc = PH7_CompileAerScript(pVm, pChunk, iFlags); + } + if(rc != SXRET_OK && (bRequire || bInclude)) { goto Cleanup; } ph7_value sResult; /* Return value */ @@ -9386,7 +9513,21 @@ static sxi32 VmEvalChunk( /* Assume a boolean true return value */ PH7_MemObjInitFromBool(pVm, &sResult, 1); /* Execute the compiled chunk */ - rc = VmLocalExec(pVm, &aByteCode, &sResult); + if(bInclude) { + ph7_value *pStack = VmNewOperandStack(pVm, SySetUsed(&aByteCode)); + if(pStack == 0) { + rc = SXERR_MEM; + } else { + rc = VmByteCodeExec(pVm, (VmInstr *)SySetBasePtr(&aByteCode), pStack, -1, + &sResult, 0, VM_EXEC_INCLUDE); + SyMemBackendFree(&pVm->sAllocator, pStack); + if((rc == VM_INCLUDE_RETURN || rc == VM_INCLUDE_JUMP) && pResult) { + PH7_MemObjStore(&sResult, pResult); + } + } + } else { + rc = VmLocalExec(pVm, &aByteCode, &sResult); + } if(pCtx) { /* Set the execution result */ ph7_result_value(pCtx, &sResult); @@ -9396,7 +9537,7 @@ Cleanup: /* Cleanup the mess left behind */ pVm->pByteContainer = pByteCode; SySetRelease(&aByteCode); - return bRequire ? rc : SXRET_OK; + return bRequire || bInclude ? rc : SXRET_OK; } /* * value eval(string $code) @@ -9423,7 +9564,7 @@ static int vm_builtin_eval(ph7_context *pCtx, int nArg, ph7_value **apArg) { return SXRET_OK; } /* Eval the chunk */ - VmEvalChunk(pCtx->pVm, &(*pCtx), &sChunk, PH7_AERSCRIPT_CHNK); + VmEvalChunk(pCtx->pVm, &(*pCtx), &sChunk, PH7_AERSCRIPT_CHNK, 0, 0); return SXRET_OK; } /* @@ -9446,7 +9587,7 @@ static int VmIsIncludedFile(ph7_vm *pVm, SyString *pFile) { * Push a file path in the appropriate VM container. */ PH7_PRIVATE sxi32 PH7_VmPushFilePath(ph7_vm *pVm, const char *zPath, int nLen, sxu8 bMain, sxi32 *pNew) { - SyString sPath; + SyString *pPath; char *zDup; #ifdef __WINNT__ char *zCur; @@ -9455,11 +9596,16 @@ PH7_PRIVATE sxi32 PH7_VmPushFilePath(ph7_vm *pVm, const char *zPath, int nLen, s if(nLen < 0) { nLen = SyStrlen(zPath); } - /* Duplicate the file path first */ - zDup = SyMemBackendStrDup(&pVm->sAllocator, zPath, nLen); - if(zDup == 0) { + /* Instructions borrow the descriptor, not an element of the growable + * file list. Keep it and its bytes stable until the VM allocator releases + * them, including after nested include/require compilation. */ + pPath = (SyString *)SyMemBackendAlloc(&pVm->sAllocator, sizeof(SyString) + nLen + 1); + if(pPath == 0) { return SXERR_MEM; } + zDup = (char *)(pPath + 1); + SyMemcpy(zPath, zDup, nLen); + zDup[nLen] = 0; #ifdef __WINNT__ /* Normalize path on windows * Example: @@ -9479,27 +9625,27 @@ PH7_PRIVATE sxi32 PH7_VmPushFilePath(ph7_vm *pVm, const char *zPath, int nLen, s } #endif /* Install the file path */ - SyStringInitFromBuf(&sPath, zDup, nLen); + SyStringInitFromBuf(pPath, zDup, nLen); if(!bMain) { - if(VmIsIncludedFile(&(*pVm), &sPath)) { + if(VmIsIncludedFile(&(*pVm), pPath)) { /* Already included */ *pNew = 0; } else { /* Insert in the corresponding container */ - rc = SySetPut(&pVm->aIncluded, (const void *)&sPath); + rc = SySetPut(&pVm->aIncluded, (const void *)pPath); if(rc != SXRET_OK) { - SyMemBackendFree(&pVm->sAllocator, zDup); + SyMemBackendFree(&pVm->sAllocator, pPath); return rc; } *pNew = 1; } } - rc = SySetPut(&pVm->aFiles, (const void *)&sPath); + rc = SySetPut(&pVm->aFiles, (const void *)&pPath); if(rc != SXRET_OK) { if(!bMain && *pNew) { SySetPop(&pVm->aIncluded); } - SyMemBackendFree(&pVm->sAllocator, zDup); + SyMemBackendFree(&pVm->sAllocator, pPath); return rc; } return SXRET_OK; @@ -9517,7 +9663,9 @@ PH7_PRIVATE sxi32 PH7_VmPushFilePath(ph7_vm *pVm, const char *zPath, int nLen, s static sxi32 VmExecIncludedFile( ph7_vm *pVm, /* Underlying Virtual Machine */ SyString *pPath, /* Script path or URL*/ - int iFlags /* Code evaluation flag */ + int iFlags, /* Code evaluation flag */ + ph7_value *pResult, /* Enclosing method/closure return value */ + ph7_include *pInclude /* Lexical control-flow bridges */ ) { sxi32 rc; const ph7_io_stream *pStream; @@ -9537,13 +9685,13 @@ static sxi32 VmExecIncludedFile( */ pHandle = PH7_StreamOpenHandle(pVm, pStream, pPath->zString, PH7_IO_OPEN_RDONLY, TRUE, 0, TRUE, &isNew); if(pHandle == 0) { - return SXERR_IO; + return (iFlags & PH7_AERSCRIPT_CODE) ? SXERR_IO : SXERR_NOTFOUND; } rc = SXRET_OK; /* Stupid cc warning */ if(iFlags & PH7_AERSCRIPT_CODE) { /* The loader owns these bytes until VM release. Do not use the spelling * from the statement, or the independent include history, as the key. */ - sRequired = *(SyString *)SySetPeek(&pVm->aFiles); + sRequired = **(SyString **)SySetPeek(&pVm->aFiles); pRequired = SyHashGet(&pVm->hRequired, sRequired.zString, sRequired.nByte); if(pRequired) { PH7_VmThrowError(pVm, PH7_CTX_WARNING, pRequired->pUserData ? @@ -9567,7 +9715,7 @@ static sxi32 VmExecIncludedFile( /* Compile and execute the script */ SyStringInitFromBuf(&sScript, SyBlobData(&sContents), SyBlobLength(&sContents)); pVm->nMagic = PH7_VM_INCL; - rc = VmEvalChunk(pVm, 0, &sScript, iFlags); + rc = VmEvalChunk(pVm, 0, &sScript, iFlags, pResult, pInclude); pVm->nMagic = (iFlags & PH7_AERSCRIPT_CODE) ? nMagic : PH7_VM_EXEC; } if(pRequired) { diff --git a/include/compiler.h b/include/compiler.h index 6054101..d2621a1 100644 --- a/include/compiler.h +++ b/include/compiler.h @@ -21,6 +21,7 @@ typedef struct Label Label; #define GEN_BLOCK_SWITCH 0x200 /* Switch statement */ #define GEN_BLOCK_CLASS 0x400 /* Class definition */ #define GEN_BLOCK_NAMESPACE 0x800 /* Namespace body */ +#define GEN_BLOCK_INCLUDE 0x1000 /* Borrowed enclosing loop targets */ /* * Each label seen in the input is recorded in an instance @@ -34,6 +35,7 @@ struct Label { SyString sName; /* Label name */ sxu32 nLine; /* Line number this label occurs */ sxu8 bRef; /* True if the label was referenced */ + sxu8 bExternal; /* Target is in the including bytecode */ }; /* diff --git a/include/ph7int.h b/include/ph7int.h index d1426a0..52f0309 100644 --- a/include/ph7int.h +++ b/include/ph7int.h @@ -902,6 +902,16 @@ typedef struct ph7_vm_func_static_var ph7_vm_func_static_var; typedef struct ph7_vm_func_arg ph7_vm_func_arg; typedef struct ph7_vm_func ph7_vm_func; typedef struct VmFrame VmFrame; +/* Lexical exit bridges in the bytecode containing an include statement. */ +typedef struct ph7_include ph7_include; +struct ph7_include { + char *zPath; + sxu32 nBreak; + sxu32 nContinue; + SySet aLabel; + ph7_vm_func *pFunc; + sxbool bException; +}; /* * Each collected function argument is recorded in an instance * of the following structure. @@ -1205,7 +1215,7 @@ struct ph7_vm { SyBlob sArgv; /* $argv[] collector [refer to the [getopt()] implementation for more information] */ SySet aModules; /* Set of loaded modules */ SySet aModuleHandles; /* Libraries retained after initialization attempts */ - SySet aFiles; /* Stack of processed files */ + SySet aFiles; /* SyString pointers, VM-owned until release */ SySet aPaths; /* Set of import paths */ SySet aIncluded; /* Set of included files */ SyHash hRequired; /* Resolved require paths, including pending evaluations */ @@ -1730,6 +1740,7 @@ PH7_PRIVATE sxi32 PH7_InitCodeGenerator(ph7_vm *pVm, ProcConsumer xErr, void *pE PH7_PRIVATE sxi32 PH7_ResetCodeGenerator(ph7_vm *pVm, ProcConsumer xErr, void *pErrData); PH7_PRIVATE sxi32 PH7_GenCompileError(ph7_gen_state *pGen, sxi32 nErrType, sxu32 nLine, const char *zFormat, ...); PH7_PRIVATE sxi32 PH7_CompileAerScript(ph7_vm *pVm, SyString *pScript, sxi32 iFlags); +PH7_PRIVATE sxi32 PH7_CompileIncludedChunk(ph7_vm *pVm, SyString *pScript, ph7_vm_func *pFunc, ph7_include *pInclude); /* constant.c function prototypes */ PH7_PRIVATE void PH7_RegisterBuiltInConstant(ph7_vm *pVm); /* builtin.c function prototypes */