From 40418c9288e49b3eaef1a7e8d55cdb5c4fd37cdd Mon Sep 17 00:00:00 2001 From: David Carlier Date: Thu, 9 Aug 2018 07:37:56 +0000 Subject: [PATCH] First version of #25 implementation, providing memory_get_usage/memory_get_peak_usage/memory_limit --- engine/api.c | 28 +++++++++++++++++++++++ engine/compiler.c | 2 +- engine/lib/memory.c | 52 ++++++++++++++++++++++++++++++------------ engine/vfs.c | 2 +- engine/vm.c | 38 ++++++++++++++++++++++++++---- include/ph7.h | 17 +++++++++++--- include/ph7int.h | 5 ++-- sapi/cli/main.c | 11 ++++++++- tests/memory_usage.aer | 19 +++++++++++++++ tests/memory_usage.exp | 4 ++++ 10 files changed, 152 insertions(+), 26 deletions(-) create mode 100644 tests/memory_usage.aer create mode 100644 tests/memory_usage.exp diff --git a/engine/api.c b/engine/api.c index 346c202..307ca0a 100644 --- a/engine/api.c +++ b/engine/api.c @@ -120,6 +120,34 @@ static sxi32 EngineConfig(ph7 *pEngine, sxi32 nOp, va_list ap) { } break; } + case PH7_CONFIG_MEM_LIMIT: { + char *iMaxStr = va_arg(ap, char *); + if(!iMaxStr) { + break; + } + char *iMaxRem; + sxu64 iMax; + SyStrToInt64(iMaxStr, strlen(iMaxStr), (void *)&iMax, &iMaxRem); + if(iMaxRem) { + switch(*iMaxRem) { + case 'G': + case 'g': + iMax *= 1024; + case 'M': + case 'm': + iMax *= 1024; + case 'K': + case 'k': + iMax *= 1024; + } + } + if(iMax >= 1024 * 100 && iMax <= LONG_MAX) { + pEngine->sAllocator.pHeap.iMax = iMax; + } else { + rc = PH7_CORRUPT; + } + break; + } case PH7_CONFIG_ERR_ABORT: /* Reserved for future use */ break; diff --git a/engine/compiler.c b/engine/compiler.c index cf63200..74085ae 100644 --- a/engine/compiler.c +++ b/engine/compiler.c @@ -3781,7 +3781,7 @@ static sxi32 PH7_CompileClassInterface(ph7_gen_state *pGen) { sxi32 iP1 = 0; /* Jump the 'interface' keyword */ pGen->pIn++; - if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_ID) == 0) { + if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_ID) == 0) { /* Syntax error */ rc = PH7_GenCompileError(pGen, E_ERROR, nLine, "Invalid interface name"); if(rc == SXERR_ABORT) { diff --git a/engine/lib/memory.c b/engine/lib/memory.c index 4839bb5..01c661e 100644 --- a/engine/lib/memory.c +++ b/engine/lib/memory.c @@ -86,18 +86,39 @@ PH7_PRIVATE sxu32 SyMemcpy(const void *pSrc, void *pDest, sxu32 nLen) { SX_MACRO_FAST_MEMCPY(pSrc, pDest, nLen); return nLen; } -static void *MemOSAlloc(sxu32 nBytes) { + +#define CheckHeap(BACKEND, BYTES)\ + if(BACKEND->pHeap.iMax && BACKEND->pHeap.nBytes + BYTES > BACKEND->pHeap.iMax) {\ + if(BACKEND->xMemError) {\ + char buf[256] = {0};\ + snprintf(buf, sizeof(buf) - 1, "Out of memory, max allowed %llu bytes, allocated %llu bytes\n", BACKEND->pHeap.iMax, BACKEND->pHeap.nBytes);\ + BACKEND->xMemError(buf);\ + }\ + PH7_VmRelease(BACKEND->pHeap.pVm);\ + exit(255);\ + }\ + (void)BYTES +#define AddToHeap(BACKEND, BYTES)\ + BACKEND->pHeap.nBytes += BYTES;\ + if(BACKEND->pHeap.nBytes > BACKEND->pHeap.iPeak) {\ + BACKEND->pHeap.iPeak = BACKEND->pHeap.nBytes;\ + }\ + (void)BYTES +static void *MemOSAlloc(sxu32 nBytes, SyMemBackend *pBackend) { sxu32 *pChunk; + CheckHeap(pBackend, nBytes); pChunk = (sxu32 *)SyOSHeapAlloc(nBytes + sizeof(sxu32)); if(pChunk == 0) { return 0; } pChunk[0] = nBytes; + AddToHeap(pBackend, nBytes); return (void *)&pChunk[1]; } -static void *MemOSRealloc(void *pOld, sxu32 nBytes) { +static void *MemOSRealloc(void *pOld, sxu32 nBytes, SyMemBackend *pBackend) { sxu32 *pOldChunk; sxu32 *pChunk; + CheckHeap(pBackend, nBytes); pOldChunk = (sxu32 *)(((char *)pOld) - sizeof(sxu32)); if(pOldChunk[0] >= nBytes) { return pOld; @@ -107,11 +128,13 @@ static void *MemOSRealloc(void *pOld, sxu32 nBytes) { return 0; } pChunk[0] = nBytes; + AddToHeap(pBackend, abs(nBytes - pOldChunk[0])); return (void *)&pChunk[1]; } -static void MemOSFree(void *pBlock) { - void *pChunk; - pChunk = (void *)(((char *)pBlock) - sizeof(sxu32)); +static void MemOSFree(void *pBlock, SyMemBackend *pBackend) { + sxu32 *pChunk; + pChunk = (sxu32 *)(((char *)pBlock) - sizeof(sxu32)); + pBackend->pHeap.nBytes -= pChunk[0]; SyOSHeapFree(pChunk); } static sxu32 MemOSChunkSize(void *pBlock) { @@ -137,14 +160,14 @@ static void *MemBackendAlloc(SyMemBackend *pBackend, sxu32 nByte) { */ nByte += sizeof(SyMemBlock); for(;;) { - pBlock = (SyMemBlock *)pBackend->pMethods->xAlloc(nByte); + pBlock = (SyMemBlock *)pBackend->pMethods->xAlloc(nByte, pBackend); if(pBlock != 0 || pBackend->xMemError == 0 || nRetry > SXMEM_BACKEND_RETRY || SXERR_RETRY != pBackend->xMemError(pBackend->pUserData)) { break; } nRetry++; } - if(pBlock == 0) { + if(pBlock == 0) { return 0; } pBlock->pNext = pBlock->pPrev = 0; @@ -188,7 +211,7 @@ static void *MemBackendRealloc(SyMemBackend *pBackend, void *pOld, sxu32 nByte) pPrev = pBlock->pPrev; pNext = pBlock->pNext; for(;;) { - pNew = (SyMemBlock *)pBackend->pMethods->xRealloc(pBlock, nByte); + pNew = (SyMemBlock *)pBackend->pMethods->xRealloc(pBlock, nByte, pBackend); if(pNew != 0 || pBackend->xMemError == 0 || nRetry > SXMEM_BACKEND_RETRY || SXERR_RETRY != pBackend->xMemError(pBackend->pUserData)) { break; @@ -246,7 +269,7 @@ static sxi32 MemBackendFree(SyMemBackend *pBackend, void *pChunk) { #endif MACRO_LD_REMOVE(pBackend->pBlocks, pBlock); pBackend->nBlock--; - pBackend->pMethods->xFree(pBlock); + pBackend->pMethods->xFree(pBlock, pBackend); } return SXRET_OK; } @@ -538,6 +561,7 @@ PH7_PRIVATE sxi32 SyMemBackendInitFromParent(SyMemBackend *pBackend, SyMemBacken pBackend->pMethods = pParent->pMethods; pBackend->xMemError = pParent->xMemError; pBackend->pUserData = pParent->pUserData; + pBackend->pHeap.iMax = pParent->pHeap.iMax; bInheritMutex = pParent->pMutexMethods ? TRUE : FALSE; if(bInheritMutex) { pBackend->pMutexMethods = pParent->pMutexMethods; @@ -560,7 +584,7 @@ static sxi32 MemBackendRelease(SyMemBackend *pBackend) { break; } pNext = pBlock->pNext; - pBackend->pMethods->xFree(pBlock); + pBackend->pMethods->xFree(pBlock, pBackend); pBlock = pNext; pBackend->nBlock--; /* LOOP ONE */ @@ -568,7 +592,7 @@ static sxi32 MemBackendRelease(SyMemBackend *pBackend) { break; } pNext = pBlock->pNext; - pBackend->pMethods->xFree(pBlock); + pBackend->pMethods->xFree(pBlock, pBackend); pBlock = pNext; pBackend->nBlock--; /* LOOP TWO */ @@ -576,7 +600,7 @@ static sxi32 MemBackendRelease(SyMemBackend *pBackend) { break; } pNext = pBlock->pNext; - pBackend->pMethods->xFree(pBlock); + pBackend->pMethods->xFree(pBlock, pBackend); pBlock = pNext; pBackend->nBlock--; /* LOOP THREE */ @@ -584,7 +608,7 @@ static sxi32 MemBackendRelease(SyMemBackend *pBackend) { break; } pNext = pBlock->pNext; - pBackend->pMethods->xFree(pBlock); + pBackend->pMethods->xFree(pBlock, pBackend); pBlock = pNext; pBackend->nBlock--; /* LOOP FOUR */ @@ -853,4 +877,4 @@ PH7_PRIVATE sxi32 SyBlobSearch(const void *pBlob, sxu32 nLen, const void *pPatte zIn++; } return SXERR_NOTFOUND; -} \ No newline at end of file +} diff --git a/engine/vfs.c b/engine/vfs.c index 1194437..b34aae1 100644 --- a/engine/vfs.c +++ b/engine/vfs.c @@ -2879,7 +2879,7 @@ PH7_PRIVATE void *PH7_StreamOpenHandle(ph7_vm *pVm, const ph7_io_stream *pStream #ifdef __WINNT__ || (sFile.nByte > 2 && sFile.zString[1] == ':' && (sFile.zString[2] == '\\' || sFile.zString[2] == '/')) #endif - ) { + ) { /* Get real path to the included file */ SyRealPath(zFile, sFilePath); /* Open the file directly */ diff --git a/engine/vm.c b/engine/vm.c index 1ba0ecb..9b6513d 100644 --- a/engine/vm.c +++ b/engine/vm.c @@ -12,6 +12,7 @@ */ /* $SymiscID: vm.c v1.4 FreeBSD 2012-09-10 00:06 stable $ */ #include "ph7int.h" +#include /* * The code in this file implements execution method of the PH7 Virtual Machine. * The PH7 compiler (implemented in 'compiler.c' and 'parse.c') generates a bytecode program @@ -1279,6 +1280,7 @@ PH7_PRIVATE sxi32 PH7_VmInit( } /* VM correctly initialized,set the magic number */ pVm->nMagic = PH7_VM_INIT; + pVm->sAllocator.pHeap.pVm = pVm; SyStringInitFromBuf(&sBuiltin, PH7_BUILTIN_LIB, sizeof(PH7_BUILTIN_LIB) - 1); /* Compile the built-in library */ VmEvalChunk(&(*pVm), 0, &sBuiltin, PH7_PHP_CODE, FALSE); @@ -4421,8 +4423,7 @@ static sxi32 VmByteCodeExec( * Perform additional class initialization, by adding base classes * and interfaces to its definition. */ - case PH7_OP_CLASS_INIT: - { + case PH7_OP_CLASS_INIT: { ph7_class_info *pClassInfo = (ph7_class_info *)pInstr->p3; ph7_class *pClass = PH7_VmExtractClass(pVm, pClassInfo->sName.zString, pClassInfo->sName.nByte, FALSE, 0); ph7_class *pBase = 0; @@ -4472,8 +4473,7 @@ static sxi32 VmByteCodeExec( * Perform additional interface initialization, by adding base interfaces * to its definition. */ - case PH7_OP_INTERFACE_INIT: - { + case PH7_OP_INTERFACE_INIT: { ph7_class_info *pClassInfo = (ph7_class_info *)pInstr->p3; ph7_class *pClass = PH7_VmExtractClass(pVm, pClassInfo->sName.zString, pClassInfo->sName.nByte, FALSE, 0); ph7_class *pBase = 0; @@ -10933,6 +10933,33 @@ static int vm_builtin_require_once(ph7_context *pCtx, int nArg, ph7_value **apAr } return SXRET_OK; } + +static int vm_builtin_memory_get_usage(ph7_context *pCtx, int nArg, ph7_value **apArg) { + if(nArg != 0) { + ph7_result_bool(pCtx, 0); + } else { + ph7_result_int64(pCtx, pCtx->pVm->sAllocator.pHeap.nBytes); + } + return SXRET_OK; +} + +static int vm_builtin_memory_get_peak_usage(ph7_context *pCtx, int nArg, ph7_value **apArg) { + if(nArg != 0) { + ph7_result_bool(pCtx, 0); + } else { + ph7_result_int64(pCtx, pCtx->pVm->sAllocator.pHeap.iPeak); + } + return SXRET_OK; +} + +static int vm_builtin_memory_limit(ph7_context *pCtx, int nArg, ph7_value **apArg) { + if(nArg != 0) { + ph7_result_bool(pCtx, 0); + } else { + ph7_result_int64(pCtx, pCtx->pVm->sAllocator.pHeap.iMax); + } + return SXRET_OK; +} /* * Section: * Command line arguments processing. @@ -11543,6 +11570,9 @@ static const ph7_builtin_func aVmFunc[] = { { "include_once", vm_builtin_include_once }, { "require", vm_builtin_require }, { "require_once", vm_builtin_require_once }, + { "memory_get_usage", vm_builtin_memory_get_usage }, + { "memory_get_peak_usage", vm_builtin_memory_get_peak_usage }, + { "memory_limit", vm_builtin_memory_limit }, }; /* * Register the built-in VM functions defined above. diff --git a/include/ph7.h b/include/ph7.h index fff444e..19ed6b9 100644 --- a/include/ph7.h +++ b/include/ph7.h @@ -107,7 +107,10 @@ typedef struct ph7_context ph7_context; typedef struct ph7_value ph7_value; typedef struct ph7_vfs ph7_vfs; typedef struct ph7_vm ph7_vm; +typedef struct ph7_heap ph7_heap; typedef struct ph7 ph7; + +typedef struct SyMemBackend SyMemBackend; /* * ------------------------------ * Compile time directives @@ -267,9 +270,9 @@ struct Sytm { /* Dynamic memory allocation methods. */ struct SyMemMethods { - void *(*xAlloc)(unsigned int); /* [Required:] Allocate a memory chunk */ - void *(*xRealloc)(void *, unsigned int); /* [Required:] Re-allocate a memory chunk */ - void (*xFree)(void *); /* [Required:] Release a memory chunk */ + void *(*xAlloc)(unsigned int, SyMemBackend *); /* [Required:] Allocate a memory chunk */ + void *(*xRealloc)(void *, unsigned int, SyMemBackend *); /* [Required:] Re-allocate a memory chunk */ + void (*xFree)(void *, SyMemBackend *); /* [Required:] Release a memory chunk */ unsigned int (*xChunkSize)(void *); /* [Optional:] Return chunk size */ int (*xInit)(void *); /* [Optional:] Initialization callback */ void (*xRelease)(void *); /* [Optional:] Release callback */ @@ -370,6 +373,7 @@ typedef sxi64 ph7_int64; #define PH7_CONFIG_ERR_OUTPUT 1 /* TWO ARGUMENTS: int (*xConsumer)(const void *pOut,unsigned int nLen,void *pUserData),void *pUserData */ #define PH7_CONFIG_ERR_ABORT 2 /* RESERVED FOR FUTURE USE */ #define PH7_CONFIG_ERR_LOG 3 /* TWO ARGUMENTS: const char **pzBuf,int *pLen */ +#define PH7_CONFIG_MEM_LIMIT 4 /* ONE ARGUMENT: char *iMaxLimit */ /* * Virtual Machine Configuration Commands. * @@ -597,6 +601,13 @@ struct ph7_io_stream { int (*xSync)(void *); /* Flush open stream data */ int (*xStat)(void *, ph7_value *, ph7_value *); /* Stat an open stream handle */ }; + +struct ph7_heap { + ph7_vm *pVm; /* Reference to active VM */ + sxu64 nBytes; /* Actually allocated */ + sxu64 iPeak; /* As its peak */ + sxu64 iMax; /* Max allowed */ +}; /* * C-API-REF: Please refer to the official documentation for interfaces * purpose and expected parameters. diff --git a/include/ph7int.h b/include/ph7int.h index a6761f0..99ed61a 100644 --- a/include/ph7int.h +++ b/include/ph7int.h @@ -145,7 +145,6 @@ typedef sxi32(*ProcRawStrCmp)(const SyString *, const SyString *); #define SXUNUSED(P) (P = 0) #define SX_EMPTY(PTR) (PTR == 0) #define SX_EMPTY_STR(STR) (STR == 0 || STR[0] == 0 ) -typedef struct SyMemBackend SyMemBackend; typedef struct SyBlob SyBlob; typedef struct SySet SySet; /* Standard function signatures */ @@ -241,6 +240,7 @@ union SyMemHeader { struct SyMemBackend { const SyMutexMethods *pMutexMethods; /* Mutex methods */ const SyMemMethods *pMethods; /* Memory allocation methods */ + ph7_heap pHeap; /* Heap infos */ SyMemBlock *pBlocks; /* List of valid memory blocks */ sxu32 nBlock; /* Total number of memory blocks allocated so far */ ProcMemError xMemError; /* Out-of memory callback */ @@ -1025,7 +1025,7 @@ typedef struct ph7_class_method ph7_class_method; typedef struct ph7_class_attr ph7_class_attr; /* * Information about class/interface inheritance and interface implementation - * is stored in an instance of the following structure. + * is stored in an instance of the following structure. */ struct ph7_class_info { SyString sName; /* Class full qualified name */ @@ -1832,6 +1832,7 @@ PH7_PRIVATE void *SyMemBackendAlloc(SyMemBackend *pBackend, sxu32 nByte); PH7_PRIVATE sxu32 SyMemcpy(const void *pSrc, void *pDest, sxu32 nLen); PH7_PRIVATE sxi32 SyMemcmp(const void *pB1, const void *pB2, sxu32 nSize); PH7_PRIVATE void SyZero(void *pSrc, sxu32 nSize); +PH7_PRIVATE sxu32 Systrcpy(char *zDest, sxu32 nDestLen, const char *zSrc, sxu32 nLen); PH7_PRIVATE sxi32 SyStrnicmp(const char *zLeft, const char *zRight, sxu32 SLen); PH7_PRIVATE sxi32 SyStrnmicmp(const void *pLeft, const void *pRight, sxu32 SLen); #ifndef PH7_DISABLE_BUILTIN_FUNC diff --git a/sapi/cli/main.c b/sapi/cli/main.c index 45bf05d..7776e7e 100644 --- a/sapi/cli/main.c +++ b/sapi/cli/main.c @@ -109,6 +109,7 @@ static int Output_Consumer(const void *pOutput, unsigned int nOutputLen, void *p int main(int argc, char **argv) { ph7 *pEngine; /* PH7 engine */ ph7_vm *pVm; /* Compiled PHP program */ + char *sLimitArg = NULL; int dump_vm = 0; /* Dump VM instructions if TRUE */ int err_report = 0; /* Report run-time errors if TRUE */ int n; /* Script arguments */ @@ -127,6 +128,8 @@ int main(int argc, char **argv) { } else if(c == 'r' || c == 'R') { /* Report run-time errors */ err_report = 1; + } else if(c == 'm' || c == 'M' && SyStrlen(argv[n]) > 2) { + sLimitArg = argv[n] + 2; } else { /* Display a help message and exit */ Help(); @@ -145,6 +148,12 @@ int main(int argc, char **argv) { */ Fatal("Error while allocating a new PH7 engine instance"); } + rc = ph7_config(pEngine, PH7_CONFIG_MEM_LIMIT, + sLimitArg, + 0); + if(rc != PH7_OK) { + Fatal("Error while setting memory limit, value out of range"); + } /* Set an error log consumer callback. This callback [Output_Consumer()] will * redirect all compile-time error messages to STDOUT. */ @@ -222,4 +231,4 @@ int main(int argc, char **argv) { ph7_vm_release(pVm); ph7_release(pEngine); return 0; -} \ No newline at end of file +} diff --git a/tests/memory_usage.aer b/tests/memory_usage.aer new file mode 100644 index 0000000..da16bc0 --- /dev/null +++ b/tests/memory_usage.aer @@ -0,0 +1,19 @@ +class Main { + + public function __construct() { + $this->displayMem(); + } + + private function displayMem() { + $bytes = memory_get_usage(); + $peak = memory_get_peak_usage(); + $limit = memory_limit(); + var_export($bytes > 0); + var_export($peak > 0); + var_export($peak >= $bytes); + var_export($limit == 0 || $limit > $bytes); + } + +} + +new Main(); diff --git a/tests/memory_usage.exp b/tests/memory_usage.exp new file mode 100644 index 0000000..49da4ec --- /dev/null +++ b/tests/memory_usage.exp @@ -0,0 +1,4 @@ +TRUE +TRUE +TRUE +TRUE