From 185845445ba62c7821061ae742eb356b215b7d4f Mon Sep 17 00:00:00 2001 From: Peter Hoddie Date: Fri, 20 May 2022 10:06:38 -0700 Subject: [PATCH 01/17] dump and replay updates --- xsnap/sources/xsnap.c | 51 +++++++++++++++++++++++++++------- xsnap/sources/xsnapPlatform.c | 52 +++++++++++++++++++++-------------- 2 files changed, 72 insertions(+), 31 deletions(-) diff --git a/xsnap/sources/xsnap.c b/xsnap/sources/xsnap.c index aac51cd..ce36b00 100644 --- a/xsnap/sources/xsnap.c +++ b/xsnap/sources/xsnap.c @@ -379,11 +379,10 @@ static int gxStep = 0; void xsReplay(xsMachine* machine) { char path[C_PATH_MAX]; - char* names[5] = { "-evaluate.dat", "-issueCommand.dat", "-command.dat", "-reply.dat", "-options.json", }; + char* names[6] = { "-evaluate.dat", "-issueCommand.dat", "-snapshot.dat", "-command.dat", "-reply.dat", "-options.json", }; for (;;) { int which; - xsBeginHost(machine); - for (which = 0; which < 5; which++) { + for (which = 0; which < 6; which++) { sprintf(path, "%05d%s", gxStep, names[which]); { #if mxWindows @@ -394,6 +393,7 @@ void xsReplay(xsMachine* machine) if ((stat(path, &a_stat) == 0) && (S_ISREG(a_stat.st_mode))) #endif { + gxStep++; fprintf(stderr, "### %s\n", path); FILE* file = fopen(path, "rb"); if (file) { @@ -402,6 +402,7 @@ void xsReplay(xsMachine* machine) length = ftell(file); fseek(file, 0, SEEK_SET); if (which == 0) { + xsBeginHost(machine); xsStringValue string; xsResult = xsStringBuffer(NULL, (xsIntegerValue)length); string = xsToString(xsResult); @@ -409,25 +410,55 @@ void xsReplay(xsMachine* machine) string[length] = 0; fclose(file); xsCall1(xsGlobal, xsID("eval"), xsResult); - + fxRunLoop(machine); + xsEndHost(machine); } else if (which == 1) { + xsBeginHost(machine); xsResult = xsArrayBuffer(NULL, (xsIntegerValue)length); length = fread(xsToArrayBuffer(xsResult), 1, length, file); fclose(file); xsCall1(xsGlobal, xsID("handleCommand"), xsResult); - + fxRunLoop(machine); + xsEndHost(machine); } + else if (which == 2) { + char buffer[1024]; + char* slash; + xsSnapshot snapshot = { + SNAPSHOT_SIGNATURE, + sizeof(SNAPSHOT_SIGNATURE) - 1, + gxSnapshotCallbacks, + mxSnapshotCallbackCount, + xsSnapshopRead, + xsSnapshopWrite, + NULL, + 0, + NULL, + NULL, + NULL, + }; + length = fread(buffer, 1, length, file); + buffer[length] = 0; + fclose(file); + slash = c_strrchr(buffer, '/'); + if (slash) slash++; + else slash = buffer; + snapshot.stream = fopen(slash, "wb"); + if (snapshot.stream) { + xsWriteSnapshot(machine, &snapshot); + fclose(snapshot.stream); + } + } + else + fclose(file); } break; } } } - xsEndHost(machine); - if (which == 5) + if (which == 6) break; - gxStep++; - fxRunLoop(machine); } } @@ -454,7 +485,7 @@ void xs_issueCommand(xsMachine* the) FILE* file; size_t length; sprintf(path, "%05d-command.dat", gxStep); - fprintf(stderr, "### %s\n", path); + fprintf(stderr, "### %s %.*s\n", path, xsGetArrayBufferLength(xsArg(0)), (char*)xsToArrayBuffer(xsArg(0))); gxStep++; sprintf(path, "%05d-reply.dat", gxStep); fprintf(stderr, "### %s\n", path); diff --git a/xsnap/sources/xsnapPlatform.c b/xsnap/sources/xsnapPlatform.c index 685d004..1e507b5 100644 --- a/xsnap/sources/xsnapPlatform.c +++ b/xsnap/sources/xsnapPlatform.c @@ -1674,7 +1674,8 @@ void fxDumpSnapshot(txMachine* the, txSnapshot* snapshot) if (chunk->temporary) (*(txDumpChunk)(chunk->temporary))(stderr, address + sizeof(txChunk), chunk->size - sizeof(txChunk)); else - fprintf(stderr, "\n\t\t?"); + fxDumpChunkData(stderr, address + sizeof(txChunk), chunk->size - sizeof(txChunk)); +// fxDumpChunkData(stderr, address + sizeof(txChunk), chunk->size - sizeof(txChunk)); fprintf(stderr, "\n"); address += chunk->size; offset += chunk->size; @@ -1776,7 +1777,10 @@ void fxDumpChunk(txSlot* slot, txByte* block) chunk = (txChunk*)(block + (size_t)(slot->value.bigint.data) - sizeof(txChunk)); chunk->temporary = (txByte*)fxDumpChunkData; } break; - case XS_ARRAY_KIND: { + case XS_ARGUMENTS_SLOPPY_KIND: + case XS_ARGUMENTS_STRICT_KIND: + case XS_ARRAY_KIND: + case XS_STACK_KIND: { if (slot->value.array.address) { chunk = (txChunk*)(block + (size_t)(slot->value.array.address) - sizeof(txChunk)); chunk->temporary = (txByte*)fxDumpChunkArray; @@ -1803,14 +1807,6 @@ void fxDumpChunk(txSlot* slot, txByte* block) chunk = (txChunk*)(block + (size_t)(slot->value.code.address) - sizeof(txChunk)); chunk->temporary = (txByte*)fxDumpChunkData; } break; - case XS_GLOBAL_KIND: { - chunk = (txChunk*)(block + (size_t)(slot->value.table.address) - sizeof(txChunk)); - chunk->temporary = (txByte*)fxDumpChunkTable; - } break; - case XS_MAP_KIND: { - chunk = (txChunk*)(block + (size_t)(slot->value.table.address) - sizeof(txChunk)); - chunk->temporary = (txByte*)fxDumpChunkTable; - } break; case XS_REGEXP_KIND: { if (slot->value.regexp.code) { chunk = (txChunk*)(block + (size_t)(slot->value.regexp.code) - sizeof(txChunk)); @@ -1821,16 +1817,24 @@ void fxDumpChunk(txSlot* slot, txByte* block) chunk->temporary = (txByte*)fxDumpChunkData; } } break; - case XS_SET_KIND: { - chunk = (txChunk*)(block + (size_t)(slot->value.table.address) - sizeof(txChunk)); - chunk->temporary = (txByte*)fxDumpChunkTable; - } break; case XS_KEY_KIND: { if (slot->value.key.string) { chunk = (txChunk*)(block + (size_t)(slot->value.key.string) - sizeof(txChunk)); chunk->temporary = (txByte*)fxDumpChunkString; } } break; + case XS_GLOBAL_KIND: + case XS_MAP_KIND: + case XS_SET_KIND: { + chunk = (txChunk*)(block + (size_t)(slot->value.table.address) - sizeof(txChunk)); + chunk->temporary = (txByte*)fxDumpChunkTable; + } break; + case XS_HOST_KIND: { + if (slot->value.host.data) { + chunk = (txChunk*)(block + (size_t)(slot->value.host.data) - sizeof(txChunk)); + chunk->temporary = (txByte*)fxDumpChunkData; + } + } break; default: break; } @@ -2041,6 +2045,8 @@ void fxDumpSlot(FILE* file, txSlot* slot) fxDumpSlotAddress(file, slot->value.instance.prototype); fprintf(file, " }"); } break; + case XS_ARGUMENTS_SLOPPY_KIND: + case XS_ARGUMENTS_STRICT_KIND: case XS_ARRAY_KIND: { fprintf(file, "array = { .address = "); fxDumpChunkAddress(file, slot->value.array.address); @@ -2088,9 +2094,6 @@ void fxDumpSlot(FILE* file, txSlot* slot) fxDumpChunkAddress(file, slot->value.table.address); fprintf(file, ", .length = %d }", (int)slot->value.table.length); } break; - case XS_HOST_KIND: { - fprintf(file, ".kind = XS_HOST_KIND}, "); - } break; case XS_MAP_KIND: { fprintf(file, "map = { .address = "); fxDumpChunkAddress(file, slot->value.table.address); @@ -2173,9 +2176,9 @@ void fxDumpSlot(FILE* file, txSlot* slot) fprintf(file, ", 0x%x }", slot->value.entry.sum); } break; case XS_ERROR_KIND: { - fprintf(file, "error = "); - fxDumpSlotAddress(file, slot->value.reference); - fprintf(file, " }"); + fprintf(file, "error = { "); + fxDumpSlotAddress(file, slot->value.error.info); + fprintf(file, ", %d }", slot->value.error.which); } break; case XS_EXPORT_KIND: { fprintf(file, "export = { .closure = "); @@ -2211,7 +2214,9 @@ void fxDumpSlot(FILE* file, txSlot* slot) fprintf(file, " }"); } break; case XS_STACK_KIND: { - fprintf(file, "stack"); + fprintf(file, "stack = { .address = "); + fxDumpChunkAddress(file, slot->value.array.address); + fprintf(file, ", .length = %d }", (int)slot->value.array.length); } break; case XS_WEAK_ENTRY_KIND: { fprintf(file, "weakEntry = { .check = "); @@ -2220,6 +2225,11 @@ void fxDumpSlot(FILE* file, txSlot* slot) fxDumpSlotAddress(file, slot->value.weakEntry.value); fprintf(file, " }"); } break; + case XS_HOST_KIND: { + fprintf(file, "host = { .data = "); + fxDumpChunkAddress(file, slot->value.host.data); + fprintf(file, " }"); + } break; default: break; } From f3891e1aa3364197dbb86f539bc020a04f2a7c07 Mon Sep 17 00:00:00 2001 From: Peter Hoddie Date: Mon, 29 Aug 2022 12:26:02 -0700 Subject: [PATCH 02/17] sync --- xsnap/makefiles/lin/xsnap-worker.mk | 2 + xsnap/makefiles/lin/xsnap.mk | 11 +- xsnap/makefiles/mac/xsnap-worker.mk | 2 + xsnap/makefiles/mac/xsnap.mk | 2 + xsnap/sources/xsnap-worker.c | 2 + xsnap/sources/xsnap.c | 72 ++- xsnap/sources/xsnap.h | 2 + xsnap/sources/xsnapPlatform.c | 851 +--------------------------- 8 files changed, 78 insertions(+), 866 deletions(-) diff --git a/xsnap/makefiles/lin/xsnap-worker.mk b/xsnap/makefiles/lin/xsnap-worker.mk index b4d5839..8e656a2 100644 --- a/xsnap/makefiles/lin/xsnap-worker.mk +++ b/xsnap/makefiles/lin/xsnap-worker.mk @@ -28,6 +28,7 @@ C_OPTIONS = \ -DXSPLATFORM=\"xsnapPlatform.h\" \ -DXSNAP_VERSION=\"$(XSNAP_VERSION)\" \ -DXSNAP_TEST_RECORD=0 \ + -DmxLockdown=1 \ -DmxMetering=1 \ -DmxDebug=1 \ -DmxNoConsole=1 \ @@ -82,6 +83,7 @@ OBJECTS = \ $(TMP_DIR)/xsGlobal.o \ $(TMP_DIR)/xsJSON.o \ $(TMP_DIR)/xsLexical.o \ + $(TMP_DIR)/xsLockdown.o \ $(TMP_DIR)/xsMapSet.o \ $(TMP_DIR)/xsMarshall.o \ $(TMP_DIR)/xsMath.o \ diff --git a/xsnap/makefiles/lin/xsnap.mk b/xsnap/makefiles/lin/xsnap.mk index 307d8ac..2abb078 100644 --- a/xsnap/makefiles/lin/xsnap.mk +++ b/xsnap/makefiles/lin/xsnap.mk @@ -22,6 +22,7 @@ C_OPTIONS = \ -fno-common \ -DINCLUDE_XSPLATFORM \ -DXSPLATFORM=\"xsnapPlatform.h\" \ + -DmxLockdown=1 \ -DmxMetering=1 \ -DmxParse=1 \ -DmxRun=1 \ @@ -50,7 +51,7 @@ ifeq ($(XSNAP_RANDOM_INIT),1) LIBRARIES += -lbsd C_OPTIONS += -DmxSnapshotRandomInit endif - + LINK_OPTIONS = -rdynamic OBJECTS = \ @@ -73,6 +74,7 @@ OBJECTS = \ $(TMP_DIR)/xsGlobal.o \ $(TMP_DIR)/xsJSON.o \ $(TMP_DIR)/xsLexical.o \ + $(TMP_DIR)/xsLockdown.o \ $(TMP_DIR)/xsMapSet.o \ $(TMP_DIR)/xsMarshall.o \ $(TMP_DIR)/xsMath.o \ @@ -98,10 +100,17 @@ OBJECTS = \ $(TMP_DIR)/xsType.o \ $(TMP_DIR)/xsdtoa.o \ $(TMP_DIR)/xsre.o \ + $(TMP_DIR)/xsmc.o \ + $(TMP_DIR)/textdecoder.o \ + $(TMP_DIR)/textencoder.o \ + $(TMP_DIR)/modBase64.o \ $(TMP_DIR)/xsnapPlatform.o \ $(TMP_DIR)/xsnap.o VPATH += $(SRC_DIR) $(TLS_DIR) +VPATH += $(MODDABLE)/modules/data/text/decoder +VPATH += $(MODDABLE)/modules/data/text/encoder +VPATH += $(MODDABLE)/modules/data/base64 build: $(TMP_DIR) $(BIN_DIR) $(BIN_DIR)/$(NAME) diff --git a/xsnap/makefiles/mac/xsnap-worker.mk b/xsnap/makefiles/mac/xsnap-worker.mk index 2816cfd..b5a0031 100644 --- a/xsnap/makefiles/mac/xsnap-worker.mk +++ b/xsnap/makefiles/mac/xsnap-worker.mk @@ -33,6 +33,7 @@ C_OPTIONS = \ -DXSPLATFORM=\"xsnapPlatform.h\" \ -DXSNAP_VERSION=\"$(XSNAP_VERSION)\" \ -DXSNAP_TEST_RECORD=0 \ + -DmxLockdown=1 \ -DmxMetering=1 \ -DmxDebug=1 \ -DmxNoConsole=1 \ @@ -91,6 +92,7 @@ OBJECTS = \ $(TMP_DIR)/xsGlobal.o \ $(TMP_DIR)/xsJSON.o \ $(TMP_DIR)/xsLexical.o \ + $(TMP_DIR)/xsLockdown.o \ $(TMP_DIR)/xsMapSet.o \ $(TMP_DIR)/xsMarshall.o \ $(TMP_DIR)/xsMath.o \ diff --git a/xsnap/makefiles/mac/xsnap.mk b/xsnap/makefiles/mac/xsnap.mk index e52774d..43c85c2 100644 --- a/xsnap/makefiles/mac/xsnap.mk +++ b/xsnap/makefiles/mac/xsnap.mk @@ -27,6 +27,7 @@ C_OPTIONS = \ $(MACOS_VERSION_MIN) \ -DINCLUDE_XSPLATFORM \ -DXSPLATFORM=\"xsnapPlatform.h\" \ + -DmxLockdown=1 \ -DmxMetering=1 \ -DmxParse=1 \ -DmxRun=1 \ @@ -82,6 +83,7 @@ OBJECTS = \ $(TMP_DIR)/xsGlobal.o \ $(TMP_DIR)/xsJSON.o \ $(TMP_DIR)/xsLexical.o \ + $(TMP_DIR)/xsLockdown.o \ $(TMP_DIR)/xsMapSet.o \ $(TMP_DIR)/xsMarshall.o \ $(TMP_DIR)/xsMath.o \ diff --git a/xsnap/sources/xsnap-worker.c b/xsnap/sources/xsnap-worker.c index 6df5c23..0ffa88b 100644 --- a/xsnap/sources/xsnap-worker.c +++ b/xsnap/sources/xsnap-worker.c @@ -228,6 +228,8 @@ int main(int argc, char* argv[]) NULL, NULL, NULL, + 0, + NULL }; xsMachine* machine; diff --git a/xsnap/sources/xsnap.c b/xsnap/sources/xsnap.c index ce36b00..f4fa237 100644 --- a/xsnap/sources/xsnap.c +++ b/xsnap/sources/xsnap.c @@ -37,7 +37,7 @@ extern void xs_base64_encode(xsMachine *the); extern void xs_base64_decode(xsMachine *the); extern void modInstallBase64(xsMachine *the); -#define mxSnapshotCallbackCount 21 +#define mxSnapshotCallbackCount 17 xsCallback gxSnapshotCallbacks[mxSnapshotCallbackCount] = { xs_issueCommand, // 0 xs_print, // 1 @@ -52,18 +52,18 @@ xsCallback gxSnapshotCallbacks[mxSnapshotCallbackCount] = { xs_textdecoder_get_encoding, // 9 xs_textdecoder_get_ignoreBOM, // 10 xs_textdecoder_get_fatal, // 11 - +// xs_textencoder, // 12 xs_textencoder_encode, // 13 xs_textencoder_encodeInto, // 14 xs_base64_encode, // 15 xs_base64_decode, // 16 - - fx_harden, // 17 - xs_lockdown, // 18 - fx_petrify, // 19 - fx_mutabilities, // 20 +// +// fx_harden, // 17 +// xs_lockdown, // 18 +// fx_petrify, // 19 +// fx_mutabilities, // 20 }; static int xsSnapshopRead(void* stream, void* address, size_t size) @@ -125,6 +125,8 @@ int main(int argc, char* argv[]) NULL, NULL, NULL, + 0, + NULL, }; xsMachine* machine; char path[C_PATH_MAX]; @@ -242,6 +244,7 @@ int main(int argc, char* argv[]) } } else if (option == 4) { + fprintf(stderr, "%p\n", machine); xsReplay(machine); } else { @@ -278,8 +281,8 @@ int main(int argc, char* argv[]) } } } - xsEndHost(machine); xsRunLoop(machine); + xsEndHost(machine); if (argw) { snapshot.stream = fopen(argv[argw], "wb"); if (snapshot.stream) { @@ -343,15 +346,15 @@ void xsBuildAgent(xsMachine* machine) modInstallTextDecoder(the); modInstallTextEncoder(the); modInstallBase64(the); - - xsResult = xsNewHostFunction(fx_harden, 1); - xsDefine(xsGlobal, xsID("harden"), xsResult, xsDontEnum); - xsResult = xsNewHostFunction(xs_lockdown, 0); - xsDefine(xsGlobal, xsID("lockdown"), xsResult, xsDontEnum); - xsResult = xsNewHostFunction(fx_petrify, 1); - xsDefine(xsGlobal, xsID("petrify"), xsResult, xsDontEnum); - xsResult = xsNewHostFunction(fx_mutabilities, 1); - xsDefine(xsGlobal, xsID("mutabilities"), xsResult, xsDontEnum); +// +// xsResult = xsNewHostFunction(fx_harden, 1); +// xsDefine(xsGlobal, xsID("harden"), xsResult, xsDontEnum); +// xsResult = xsNewHostFunction(xs_lockdown, 0); +// xsDefine(xsGlobal, xsID("lockdown"), xsResult, xsDontEnum); +// xsResult = xsNewHostFunction(fx_petrify, 1); +// xsDefine(xsGlobal, xsID("petrify"), xsResult, xsDontEnum); +// xsResult = xsNewHostFunction(fx_mutabilities, 1); +// xsDefine(xsGlobal, xsID("mutabilities"), xsResult, xsDontEnum); xsEndHost(machine); } @@ -423,6 +426,10 @@ void xsReplay(xsMachine* machine) xsEndHost(machine); } else if (which == 2) { +// xsBeginHost(machine); +// xsCollectGarbage(); +// xsEndHost(machine); +// fclose(file); char buffer[1024]; char* slash; xsSnapshot snapshot = { @@ -437,6 +444,8 @@ void xsReplay(xsMachine* machine) NULL, NULL, NULL, + 0, + NULL }; length = fread(buffer, 1, length, file); buffer[length] = 0; @@ -476,6 +485,7 @@ void xs_currentMeterLimit(xsMachine* the) void xs_gc(xsMachine* the) { + fprintf(stderr, "gc()\n"); xsCollectGarbage(); } @@ -484,9 +494,32 @@ void xs_issueCommand(xsMachine* the) char path[C_PATH_MAX]; FILE* file; size_t length; + void* data; + size_t argLength; + void* argData; + sprintf(path, "%05d-command.dat", gxStep); - fprintf(stderr, "### %s %.*s\n", path, xsGetArrayBufferLength(xsArg(0)), (char*)xsToArrayBuffer(xsArg(0))); gxStep++; + file = fopen(path, "rb"); + if (!file) xsUnknownError("cannot open %s", path); + fseek(file, 0, SEEK_END); + length = ftell(file); + fseek(file, 0, SEEK_SET); + xsResult = xsArrayBuffer(NULL, (xsIntegerValue)length); + data = xsToArrayBuffer(xsResult); + length = fread(data, 1, length, file); + fclose(file); + + argLength = xsGetArrayBufferLength(xsArg(0)); + argData = xsToArrayBuffer(xsArg(0)); + + if ((length != argLength) || c_memcmp(data, argData, length)) { + fprintf(stderr, "### %s %.*s\n", path, (int)argLength, (char*)argData); +// fprintf(stderr, "@@@ %s %.*s\n", path, (int)length, (char*)data); + } + else + fprintf(stderr, "### %s\n", path); + sprintf(path, "%05d-reply.dat", gxStep); fprintf(stderr, "### %s\n", path); gxStep++; @@ -496,7 +529,8 @@ void xs_issueCommand(xsMachine* the) length = ftell(file); fseek(file, 0, SEEK_SET); xsResult = xsArrayBuffer(NULL, (xsIntegerValue)length); - length = fread(xsToArrayBuffer(xsResult), 1, length, file); + data = xsToArrayBuffer(xsResult); + length = fread(data, 1, length, file); fclose(file); } diff --git a/xsnap/sources/xsnap.h b/xsnap/sources/xsnap.h index 82da902..1380a45 100644 --- a/xsnap/sources/xsnap.h +++ b/xsnap/sources/xsnap.h @@ -17,6 +17,8 @@ struct xsSnapshotRecord { void* firstChunk; void* firstProjection; void* firstSlot; + int slotSize; + void* slots; }; #define xsInitializeSharedCluster() \ diff --git a/xsnap/sources/xsnapPlatform.c b/xsnap/sources/xsnapPlatform.c index 1e507b5..3e0d8b2 100644 --- a/xsnap/sources/xsnapPlatform.c +++ b/xsnap/sources/xsnapPlatform.c @@ -306,6 +306,7 @@ void fxRunLoop(txMachine* the) txNumber when; txJob* job; txJob** address; + fxEndJob(the); for (;;) { while (the->promiseJobs) { while (the->promiseJobs) { @@ -363,6 +364,7 @@ void fxRunLoop(txMachine* the) } } } + fxCheckUnhandledRejections(the, 1); } void fxFulfillModuleFile(txMachine* the) @@ -709,847 +711,6 @@ void fxSetCurrentMeter(txMachine* the, txUnsigned value) } #endif -void fxSetHostFunctionProperty(txMachine* the, txSlot* property, txCallback call, txInteger length, txID id) -{ - txSlot* home = the->stack; - txSlot* function = fxNewHostFunction(the, call, length, id); - txSlot* slot = mxFunctionInstanceHome(function); - slot->value.home.object = home->value.reference; - property->kind = the->stack->kind; - property->value = the->stack->value; - mxPop(); -} - -txSize fxGetCurrentHeapCount(txMachine* the) -{ - return the->currentHeapCount; -} - -void fx_lockdown(txMachine* the) -{ -#define mxHardenBuiltInCall \ - mxPush(mxGlobal); \ - mxPushSlot(harden); \ - mxCall() -#define mxHardenBuiltInRun \ - mxRunCount(1); \ - mxPop() - - txSlot* instance; - txSlot* property; - txSlot* item; - txSlot* harden; - txInteger id; - - if (mxProgram.value.reference->flag & XS_DONT_MARSHALL_FLAG) - mxTypeError("lockdown already called"); - mxProgram.value.reference->flag |= XS_DONT_MARSHALL_FLAG; - - property = mxBehaviorGetProperty(the, mxAsyncFunctionPrototype.value.reference, mxID(_constructor), 0, XS_OWN); - property->kind = mxThrowTypeErrorFunction.kind; - property->value = mxThrowTypeErrorFunction.value; - property = mxBehaviorGetProperty(the, mxAsyncGeneratorFunctionPrototype.value.reference, mxID(_constructor), 0, XS_OWN); - property->kind = mxThrowTypeErrorFunction.kind; - property->value = mxThrowTypeErrorFunction.value; - property = mxBehaviorGetProperty(the, mxFunctionPrototype.value.reference, mxID(_constructor), 0, XS_OWN); - property->kind = mxThrowTypeErrorFunction.kind; - property->value = mxThrowTypeErrorFunction.value; - property = mxBehaviorGetProperty(the, mxGeneratorFunctionPrototype.value.reference, mxID(_constructor), 0, XS_OWN); - property->kind = mxThrowTypeErrorFunction.kind; - property->value = mxThrowTypeErrorFunction.value; - property = mxBehaviorGetProperty(the, mxCompartmentPrototype.value.reference, mxID(_constructor), 0, XS_OWN); - property->kind = mxThrowTypeErrorFunction.kind; - property->value = mxThrowTypeErrorFunction.value; - - instance = fxNewArray(the, _Compartment); - property = the->stackPrototypes - 1; - item = instance->next->value.array.address; - for (id = 0; id < XS_SYMBOL_ID_COUNT; id++) { - *((txIndex*)item) = id; - property--; - item++; - } - for (; id < _Compartment; id++) { - *((txIndex*)item) = id; - item->kind = property->kind; - item->value = property->value; - property--; - item++; - } - - fxDuplicateInstance(the, mxDateConstructor.value.reference); - property = mxFunctionInstanceCode(the->stack->value.reference); - property->value.callback.address = mxCallback(fx_Date_secure); - property = mxBehaviorGetProperty(the, the->stack->value.reference, mxID(_now), 0, XS_OWN); - fxSetHostFunctionProperty(the, property, mxCallback(fx_Date_now_secure), 0, mxID(_now)); - property = mxBehaviorGetProperty(the, mxDatePrototype.value.reference, mxID(_constructor), 0, XS_OWN); - property->kind = the->stack->kind; - property->value = the->stack->value; - mxPull(instance->next->value.array.address[_Date]); - - fxDuplicateInstance(the, mxMathObject.value.reference); - property = mxBehaviorGetProperty(the, the->stack->value.reference, mxID(_random), 0, XS_OWN); - fxSetHostFunctionProperty(the, property, mxCallback(fx_Math_random_secure), 0, mxID(_random)); - mxPull(instance->next->value.array.address[_Math]); - - mxPull(mxCompartmentGlobal); - - mxTemporary(harden); - mxPush(mxGlobal); - fxGetID(the, fxID(the, "harden")); - mxPullSlot(harden); - - for (id = XS_SYMBOL_ID_COUNT; id < _Infinity; id++) { - mxHardenBuiltInCall; mxPush(the->stackPrototypes[-1 - id]); mxHardenBuiltInRun; - } - for (id = _Compartment; id < XS_INTRINSICS_COUNT; id++) { - mxHardenBuiltInCall; mxPush(the->stackPrototypes[-1 - id]); mxHardenBuiltInRun; - } - - mxHardenBuiltInCall; mxPush(mxArgumentsSloppyPrototype); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxArgumentsStrictPrototype); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxArrayIteratorPrototype); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxAsyncFromSyncIteratorPrototype); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxAsyncFunctionPrototype); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxAsyncGeneratorFunctionPrototype); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxAsyncGeneratorPrototype); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxAsyncIteratorPrototype); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxGeneratorFunctionPrototype); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxGeneratorPrototype); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxHostPrototype); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxIteratorPrototype); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxMapIteratorPrototype); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxModulePrototype); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxRegExpStringIteratorPrototype); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxSetIteratorPrototype); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxStringIteratorPrototype); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxTransferPrototype); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxTypedArrayPrototype); mxHardenBuiltInRun; - - mxHardenBuiltInCall; mxPush(mxAssignObjectFunction); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxCopyObjectFunction); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxEnumeratorFunction); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxInitializeRegExpFunction); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxOnRejectedPromiseFunction); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxOnResolvedPromiseFunction); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPush(mxOnThenableFunction); mxHardenBuiltInRun; - - mxHardenBuiltInCall; mxPushReference(mxArrayLengthAccessor.value.accessor.getter); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPushReference(mxArrayLengthAccessor.value.accessor.setter); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPushReference(mxStringAccessor.value.accessor.getter); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPushReference(mxStringAccessor.value.accessor.setter); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPushReference(mxProxyAccessor.value.accessor.getter); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPushReference(mxProxyAccessor.value.accessor.setter); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPushReference(mxTypedArrayAccessor.value.accessor.getter); mxHardenBuiltInRun; - mxHardenBuiltInCall; mxPushReference(mxTypedArrayAccessor.value.accessor.setter); mxHardenBuiltInRun; - - mxHardenBuiltInCall; mxPush(mxArrayPrototype); fxGetID(the, mxID(_Symbol_unscopables)); mxHardenBuiltInRun; - - mxHardenBuiltInCall; mxPush(mxCompartmentGlobal); mxHardenBuiltInRun; - - mxFunctionInstanceCode(mxThrowTypeErrorFunction.value.reference)->ID = XS_NO_ID; - mxFunctionInstanceHome(mxThrowTypeErrorFunction.value.reference)->value.home.object = NULL; - - mxPop(); -} - -static void fx_hardenQueue(txMachine* the, txSlot* list, txSlot* instance, txFlag flag) -{ - txSlot* item; - if (instance->flag & flag) - return; - item = fxNewSlot(the); - item->value.reference = instance; - item->kind = XS_REFERENCE_KIND; - list->value.list.last->next = item; - list->value.list.last = item; -} - -static void fx_hardenFreezeAndTraverse(txMachine* the, txSlot* reference, txSlot* freeze, txSlot* list, txFlag flag) -{ - txSlot* instance = reference->value.reference; - txSlot* property; - txBoolean useIndexes = 1; - txSlot* at; -// txSlot* slot; - - if (!mxBehaviorPreventExtensions(the, instance)) - mxTypeError("extensible object"); - - property = instance->next; - if (property && (property->flag & XS_INTERNAL_FLAG) && (property->kind == XS_TYPED_ARRAY_KIND)) { - useIndexes = 0; - } - - at = fxNewInstance(the); - mxBehaviorOwnKeys(the, instance, XS_EACH_NAME_FLAG | XS_EACH_SYMBOL_FLAG, at); - mxPushUndefined(); - property = the->stack; - while ((at = at->next)) { - if ((at->value.at.id != XS_NO_ID) || useIndexes) { - if (mxBehaviorGetOwnProperty(the, instance, at->value.at.id, at->value.at.index, property)) { - txFlag mask = XS_DONT_DELETE_FLAG; - property->flag |= XS_DONT_DELETE_FLAG; - if (property->kind != XS_ACCESSOR_KIND) { - mask |= XS_DONT_SET_FLAG; - property->flag |= XS_DONT_SET_FLAG; - } - property->kind = XS_UNINITIALIZED_KIND; - if (!mxBehaviorDefineOwnProperty(the, instance, at->value.at.id, at->value.at.index, property, mask)) - mxTypeError("cannot configure property"); - } - } - } - mxPop(); - mxPop(); - -// if (flag == XS_DONT_MODIFY_FLAG) { -// property = instance->next; -// while (property) { -// if (property->flag & XS_INTERNAL_FLAG) { -// switch (property->kind) { -// case XS_ARRAY_BUFFER_KIND: -// case XS_DATE_KIND: -// case XS_MAP_KIND: -// case XS_SET_KIND: -// case XS_WEAK_MAP_KIND: -// case XS_WEAK_SET_KIND: -// property->flag |= XS_DONT_SET_FLAG; -// break; -// case XS_PRIVATE_KIND: -// slot = property->value.private.first; -// while (slot) { -// if (slot->kind != XS_ACCESSOR_KIND) -// slot->flag |= XS_DONT_SET_FLAG; -// slot->flag |= XS_DONT_DELETE_FLAG; -// slot = slot->next; -// } -// break; -// } -// } -// property = property->next; -// } -// } - instance->flag |= flag; - - at = fxNewInstance(the); - mxBehaviorOwnKeys(the, instance, XS_EACH_NAME_FLAG | XS_EACH_SYMBOL_FLAG, at); - - mxTemporary(property); - mxBehaviorGetPrototype(the, instance, property); - if (property->kind == XS_REFERENCE_KIND) - fx_hardenQueue(the, list, property->value.reference, flag); - - while ((at = at->next)) { - if (mxBehaviorGetOwnProperty(the, instance, at->value.at.id, at->value.at.index, property)) { - if (property->kind == XS_REFERENCE_KIND) - fx_hardenQueue(the, list, property->value.reference, flag); - else if (property->kind == XS_ACCESSOR_KIND) { - if (property->value.accessor.getter) - fx_hardenQueue(the, list, property->value.accessor.getter, flag); - if (property->value.accessor.setter) - fx_hardenQueue(the, list, property->value.accessor.setter, flag); - } - } - } - -// if (flag == XS_DONT_MODIFY_FLAG) { -// property = instance->next; -// while (property) { -// if (property->flag & XS_INTERNAL_FLAG) { -// if (property->kind == XS_PRIVATE_KIND) { -// txSlot* item = property->value.private.first; -// while (item) { -// if (property->kind == XS_REFERENCE_KIND) -// fx_hardenQueue(the, list, property->value.reference, flag); -// else if (property->kind == XS_ACCESSOR_KIND) { -// if (property->value.accessor.getter) -// fx_hardenQueue(the, list, property->value.accessor.getter, flag); -// if (property->value.accessor.setter) -// fx_hardenQueue(the, list, property->value.accessor.setter, flag); -// } -// item = item->next; -// } -// } -// else if (property->kind == XS_DATA_VIEW_KIND) { -// property = property->next; -// fx_hardenQueue(the, list, property->value.reference, flag); -// } -// } -// property = property->next; -// } -// } - - mxPop(); - mxPop(); -} - -void fx_harden(txMachine* the) -{ - txFlag flag = XS_DONT_MARSHALL_FLAG; - txSlot* freeze; - txSlot* slot; - txSlot* list; - txSlot* item; - - if (!(mxProgram.value.reference->flag & XS_DONT_MARSHALL_FLAG)) - mxTypeError("call lockdown before harden"); - - if (mxArgc == 0) - return; - - *mxResult = *mxArgv(0); - - slot = mxArgv(0); - if (slot->kind != XS_REFERENCE_KIND) - return; -// if (mxArgc > 1) { -// txString string = fxToString(the, mxArgv(1)); -// if (c_strcmp(string, "freeze") == 0) -// flag = XS_DONT_MARSHALL_FLAG; -// else if (c_strcmp(string, "petrify") == 0) -// flag = XS_DONT_MODIFY_FLAG; -// else -// mxTypeError("invalid integrity"); -// } - slot = slot->value.reference; - if (slot->flag & flag) - return; - - mxTemporary(freeze); - mxPush(mxObjectConstructor); - mxGetID(mxID(_freeze)); - mxPullSlot(freeze); - - mxTemporary(list); - list->value.list.first = C_NULL; - list->value.list.last = C_NULL; - list->kind = XS_LIST_KIND; - - item = fxNewSlot(the); - item->value.reference = slot; - item->kind = XS_REFERENCE_KIND; - list->value.list.first = item; - list->value.list.last = item; - - { - mxTry(the) { - while (item) { - fx_hardenFreezeAndTraverse(the, item, freeze, list, flag); - item = item->next; - } - } - mxCatch(the) { - item = list->value.list.first; - while (item) { - item->value.reference->flag &= ~flag; - item = item->next; - } - fxJump(the); - } - } - - mxPop(); - mxPop(); -} - -void fx_petrify(txMachine* the) -{ - txSlot* slot; - txSlot* instance; - txBoolean useIndexes = 1; - txSlot* at; - txSlot* property; - if (mxArgc == 0) - return; - slot = mxArgv(0); - *mxResult = *slot; - if (slot->kind != XS_REFERENCE_KIND) - return; - instance = slot->value.reference; - if (!mxBehaviorPreventExtensions(the, instance)) - mxTypeError("extensible object"); - slot = instance->next; - if (slot && (slot->flag & XS_INTERNAL_FLAG)) { - if ((slot->kind == XS_STRING_KIND) || (slot->kind == XS_STRING_X_KIND) || (slot->kind == XS_TYPED_ARRAY_KIND)) - useIndexes = 0; - } - - at = fxNewInstance(the); - mxBehaviorOwnKeys(the, instance, XS_EACH_NAME_FLAG | XS_EACH_SYMBOL_FLAG, at); - mxPushUndefined(); - property = the->stack; - while ((at = at->next)) { - if ((at->value.at.id != XS_NO_ID) || useIndexes) { - if (mxBehaviorGetOwnProperty(the, instance, at->value.at.id, at->value.at.index, property)) { - txFlag mask = XS_DONT_DELETE_FLAG; - property->flag |= XS_DONT_DELETE_FLAG; - if (property->kind != XS_ACCESSOR_KIND) { - mask |= XS_DONT_SET_FLAG; - property->flag |= XS_DONT_SET_FLAG; - } - property->kind = XS_UNINITIALIZED_KIND; - if (!mxBehaviorDefineOwnProperty(the, instance, at->value.at.id, at->value.at.index, property, mask)) - mxTypeError("cannot configure property"); - } - } - } - mxPop(); - - property = instance->next; - while (property) { - if (property->flag & XS_INTERNAL_FLAG) { - switch (property->kind) { - case XS_ARRAY_BUFFER_KIND: - case XS_DATE_KIND: - case XS_MAP_KIND: - case XS_SET_KIND: - case XS_WEAK_MAP_KIND: - case XS_WEAK_SET_KIND: - property->flag |= XS_DONT_SET_FLAG; - break; - case XS_PRIVATE_KIND: - slot = property->value.private.first; - while (slot) { - if (slot->kind != XS_ACCESSOR_KIND) - slot->flag |= XS_DONT_SET_FLAG; - slot->flag |= XS_DONT_DELETE_FLAG; - slot = slot->next; - } - break; - } - } - property = property->next; - } -} - -static void fxVerifyCode(txMachine* the, txSlot* list, txSlot* path, txByte* codeBuffer, txSize codeSize); -static void fxVerifyError(txMachine* the, txSlot* path, txID id, txIndex index, txString name); -static void fxVerifyErrorString(txMachine* the, txSlot* slot, txID id, txIndex index, txString name); -static void fxVerifyInstance(txMachine* the, txSlot* list, txSlot* path, txSlot* instance); -static void fxVerifyProperty(txMachine* the, txSlot *list, txSlot *path, txSlot* property, txID id, txIndex index, txString name); -static void fxVerifyQueue(txMachine* the, txSlot* list, txSlot* path, txSlot* instance, txID id, txIndex index, txString name); - -void fx_mutabilities(txMachine* the) -{ - txSlot* instance; - txSlot* module; - txSlot* realm; - txSlot* slot; - txSlot* list; - txSlot* item; - - fxVars(the, 2); - - mxPush(mxArrayPrototype); - instance = fxNewArrayInstance(the); - mxPullSlot(mxResult); - - fxNewHostObject(the, NULL); - fxSetHostChunk(the, the->stack, NULL, the->keyIndex); - mxPullSlot(mxVarv(0)); - - module = mxFunctionInstanceHome(mxFunction->value.reference)->value.home.module; - if (!module) module = mxProgram.value.reference; - realm = mxModuleInstanceInternal(module)->value.module.realm; - mxPushSlot(mxRealmGlobal(realm)); - mxPullSlot(mxVarv(1)); - - if (mxArgc == 0) - return; - slot = mxArgv(0); - if (slot->kind != XS_REFERENCE_KIND) - return; - - mxTemporary(list); - list->value.list.first = C_NULL; - list->value.list.last = C_NULL; - list->kind = XS_LIST_KIND; - - item = fxNewSlot(the); - item->value.list.first = C_NULL; - item->value.list.last = slot->value.reference; - item->kind = XS_LIST_KIND; - list->value.list.first = item; - list->value.list.last = item; - - { - mxTry(the) { - while (item) { - fxVerifyInstance(the, list, item->value.list.first, item->value.list.last); - item = item->next; - } - item = list->value.list.first; - while (item) { - item->value.list.last->flag &= ~XS_LEVEL_FLAG; - item = item->next; - } - } - mxCatch(the) { - item = list->value.list.first; - while (item) { - item->value.list.last->flag &= ~XS_LEVEL_FLAG; - item = item->next; - } - fxJump(the); - } - } - - mxPop(); // list - - fxCacheArray(the, instance); - mxPushSlot(mxResult); - mxPushSlot(mxResult); - mxGetID(mxID(_sort)); - mxCall(); - mxRunCount(0); - mxPullSlot(mxResult); -} - -void fxVerifyCode(txMachine* the, txSlot* list, txSlot* path, txByte* codeBuffer, txSize codeSize) -{ - const txS1* bytes = gxCodeSizes; - txByte* p = codeBuffer; - txByte* q = p + codeSize; - txU1 byte; - txS1 offset; - txID id; - txInteger count = 0; - txByte flag = 0; - txByte* flags = fxGetHostChunk(the, mxVarv(0)); - while (p < q) { -// fprintf(stderr, "%s", gxCodeNames[*((txU1*)p)]); - byte = (txU1)c_read8(p); - offset = (txS1)c_read8(bytes + byte); - if (0 < offset) { - p += offset; - } - else if (0 == offset) { - p++; - mxDecodeID(p, id); - if (byte == XS_CODE_PROGRAM_REFERENCE) { - flag = 1; - flags[id] = 1; - } - } - else if (-1 == offset) { - txU1 index; - p++; - index = *((txU1*)p); - p += 1 + index; - } - else if (-2 == offset) { - txU2 index; - p++; - mxDecode2(p, index); - p += index; - } - else if (-4 == offset) { - txS4 index; - p++; - mxDecode4(p, index); - p += index; - } -// fprintf(stderr, "\n"); - if ((XS_CODE_BEGIN_SLOPPY <= byte) && (byte <= XS_CODE_BEGIN_STRICT_FIELD)) { - count++; - } - else if ((XS_CODE_END <= byte) && (byte <= XS_CODE_END_DERIVED)) { - count--; - if (count == 0) - break; - } - } - if (flag) { - txSlot* instance = fxGetInstance(the, mxVarv(1)); - txSlot* name = fxNewSlot(the); - name->value.string = "GlobalEnvironment"; - name->kind = XS_STRING_X_KIND; - name->next = path; - id = 0; - p = flags; - q = flags + the->keyIndex; - while (p < q) { - if (*p) { - txSlot* property = mxBehaviorGetProperty(the, instance, id, 0, XS_OWN); - if (property) - fxVerifyProperty(the, list, name, property, id, 0, NULL); - *p = 0; - } - id++; - p++; - } - } -} - -void fxVerifyError(txMachine* the, txSlot* path, txID id, txIndex index, txString string) -{ - txSlot* array; - txSlot* slot; - txSlot* current; - txSlot* next; - txSlot* previous; - - array = mxResult->value.reference->next; - slot = fxNewSlot(the); - slot->next = array->next; - array->next = slot; - array->value.array.length++; - fxString(the, slot, ""); - - current = path; - next = C_NULL; - previous = C_NULL; - while (current) { - next = current->next; - current->next = previous; - previous = current; - current = next; - } - - path = previous; - current = path; - while (current) { - if (current->kind == XS_STRING_X_KIND) { - fxVerifyErrorString(the, slot, XS_NO_ID, 0, current->value.string); - } - else { - fxVerifyErrorString(the, slot, current->value.at.id, current->value.at.index, C_NULL); - } - current = current->next; - } - fxVerifyErrorString(the, slot, id, index, string); - - current = path; - next = C_NULL; - previous = C_NULL; - while (current) { - next = current->next; - current->next = previous; - previous = current; - current = next; - } -} - -void fxVerifyErrorString(txMachine* the, txSlot* slot, txID id, txIndex index, txString string) -{ - if (string) { - fxConcatStringC(the, slot, "[["); - fxConcatStringC(the, slot, string); - fxConcatStringC(the, slot, "]]"); - } - else if (id != XS_NO_ID) { - txSlot* key = fxGetKey(the, id); - if (key) { - if (key->flag & XS_DONT_ENUM_FLAG) { - c_snprintf(the->nameBuffer, sizeof(the->nameBuffer), "%s", key->value.key.string); - fxConcatStringC(the, slot, "."); - fxConcatStringC(the, slot, the->nameBuffer); - } - else { - if ((key->kind == XS_KEY_KIND) || (key->kind == XS_KEY_X_KIND)) - c_snprintf(the->nameBuffer, sizeof(the->nameBuffer), "%s", key->value.key.string); - else if ((key->kind == XS_STRING_KIND) || (key->kind == XS_STRING_X_KIND)) - c_snprintf(the->nameBuffer, sizeof(the->nameBuffer), "%s", key->value.string); - else - the->nameBuffer[0] = 0; - fxConcatStringC(the, slot, "[Symbol("); - fxConcatStringC(the, slot, the->nameBuffer); - fxConcatStringC(the, slot, ")]"); - } - } - else { - fxConcatStringC(the, slot, "[Symbol()]"); - } - } - else { - fxNumberToString(the->dtoa, index, the->nameBuffer, sizeof(the->nameBuffer), 0, 0); - fxConcatStringC(the, slot, "["); - fxConcatStringC(the, slot, the->nameBuffer); - fxConcatStringC(the, slot, "]"); - } -} - -void fxVerifyInstance(txMachine* the, txSlot* list, txSlot* path, txSlot* instance) -{ - txSlot* property; - txSlot* prototype; - - instance->flag |= XS_LEVEL_FLAG; - - if (instance->next && (instance->next->ID == XS_ENVIRONMENT_BEHAVIOR)) { - property = instance->next->next; - while (property) { - if ((property->kind == XS_CLOSURE_KIND) && (property->ID != XS_NO_ID)) { // skip private fields initializers - txSlot* closure = property->value.closure; - if (!(closure->flag & XS_DONT_SET_FLAG)) { - fxVerifyError(the, path, property->ID, 0, C_NULL); - } - if (closure->kind == XS_REFERENCE_KIND) { - fxVerifyQueue(the, list, path, closure->value.reference, property->ID, 0, C_NULL); - } - } - property = property->next; - } - return; - } - - if (!(instance->flag & XS_DONT_PATCH_FLAG)) { - fxVerifyError(the, path, XS_NO_ID, 0, "Extensible"); - } - - prototype = fxGetPrototype(the, instance); - if (prototype) { - fxVerifyQueue(the, list, path, prototype, mxID(___proto__), 0, C_NULL); - } - - property = instance->next; - while (property) { - if (property->flag & XS_INTERNAL_FLAG) { - switch (property->kind) { - case XS_ARRAY_KIND: - { - txSlot* address = property->value.array.address; - if (address) { - txIndex offset = 0, size = (((txChunk*)(((txByte*)address) - sizeof(txChunk)))->size) / sizeof(txSlot); - while (offset < size) { - address = property->value.array.address + offset; - fxVerifyProperty(the, list, path, address, XS_NO_ID, *((txIndex*)address), C_NULL); - offset++; - } - } - } - break; - case XS_ARRAY_BUFFER_KIND: - if (!(property->flag & XS_DONT_SET_FLAG)) { - if (property->value.arrayBuffer.address != C_NULL) - fxVerifyError(the, path, XS_NO_ID, 0, "ArrayBufferData"); - } - break; - case XS_CODE_KIND: - case XS_CODE_X_KIND: - if (property->value.code.closures) - fxVerifyQueue(the, list, path, property->value.code.closures, XS_NO_ID, 0, "Environment"); - fxVerifyCode(the, list, path, property->value.code.address, ((txChunk*)(((txByte*)(property->value.code.address)) - sizeof(txChunk)))->size); - break; - case XS_DATA_VIEW_KIND: - property = property->next; - fxVerifyQueue(the, list, path, property->value.reference, XS_NO_ID, 0, "ViewedArrayBuffer"); - break; - case XS_DATE_KIND: - if (!(property->flag & XS_DONT_SET_FLAG)) - fxVerifyError(the, path, XS_NO_ID, 0, "DateValue"); - break; - case XS_REGEXP_KIND: - break; - case XS_MAP_KIND: - if (!(property->flag & XS_DONT_SET_FLAG)) - fxVerifyError(the, path, XS_NO_ID, 0, "MapData"); - break; - case XS_MODULE_KIND: - { - txSlot* exports = mxModuleInstanceExports(instance); - if (mxIsReference(exports)) { - txSlot* property = exports->value.reference->next; - while (property) { - if (property->value.export.closure) { - txSlot* closure = property->value.export.closure; - closure->flag |= XS_DONT_DELETE_FLAG; - fxVerifyProperty(the, list, path, closure, property->ID, 0, C_NULL); - closure->flag &= ~XS_DONT_DELETE_FLAG; - } - property = property->next; - } - } - } - break; - case XS_PRIVATE_KIND: - { - txSlot* item = property->value.private.first; - while (item) { - fxVerifyProperty(the, list, path, item, item->ID, 0, C_NULL); - item = item->next; - } - } - break; - case XS_PROXY_KIND: - if (property->value.proxy.handler) { - fxVerifyQueue(the, list, path, property->value.proxy.target, XS_NO_ID, 0, "ProxyHandler"); - } - if (property->value.proxy.target) { - fxVerifyQueue(the, list, path, property->value.proxy.target, XS_NO_ID, 0, "ProxyTarget"); - } - break; - case XS_SET_KIND: - if (!(property->flag & XS_DONT_SET_FLAG)) - fxVerifyError(the, path, XS_NO_ID, 0, "SetData"); - break; - case XS_WEAK_MAP_KIND: - if (!(property->flag & XS_DONT_SET_FLAG)) - fxVerifyError(the, path, XS_NO_ID, 0, "WeakMapData"); - break; - case XS_WEAK_SET_KIND: - if (!(property->flag & XS_DONT_SET_FLAG)) - fxVerifyError(the, path, XS_NO_ID, 0, "WeakSetData"); - break; - } - } - else { - fxVerifyProperty(the, list, path, property, property->ID, 0, C_NULL); - } - property = property->next; - } -} - -void fxVerifyProperty(txMachine* the, txSlot *list, txSlot *path, txSlot* property, txID id, txIndex index, txString string) -{ - txBoolean immutable = 1; - - if (property->kind != XS_ACCESSOR_KIND) - if (!(property->flag & XS_DONT_SET_FLAG)) - immutable = 0; - if (!(property->flag & XS_DONT_DELETE_FLAG)) - immutable = 0; - if (!immutable) { - fxVerifyError(the, path, id, index, string); - } - if (property->kind == XS_REFERENCE_KIND) - fxVerifyQueue(the, list, path, property->value.reference, id, index, string); - else if (property->kind == XS_ACCESSOR_KIND) { - if (property->value.accessor.getter) - fxVerifyQueue(the, list, path, property->value.accessor.getter, id, index, string); - if (property->value.accessor.setter) - fxVerifyQueue(the, list, path, property->value.accessor.setter, id, index, string); - } -} - -void fxVerifyQueue(txMachine* the, txSlot* list, txSlot* path, txSlot* instance, txID id, txIndex index, txString string) -{ - txSlot* item; - txSlot* name; - if (instance->kind != XS_INSTANCE_KIND) - return; - if (instance->flag & XS_LEVEL_FLAG) - return; - item = fxNewSlot(the); - list->value.list.last->next = item; - list->value.list.last = item; - - item->value.list.first = name = fxNewSlot(the); - if (string) { - name->value.string = string; - name->kind = XS_STRING_X_KIND; - } - else { - name->value.at.id = id; - name->value.at.index = index; - name->kind = XS_AT_KIND; - } - name->next = path; - item->value.list.last = instance; - item->kind = XS_LIST_KIND; -} - extern void fxDumpSnapshot(txMachine* the, txSnapshot* snapshot); typedef void (*txDumpChunk)(FILE* file, txByte* data, txSize size); @@ -1575,7 +736,7 @@ void fxDumpSnapshot(txMachine* the, txSnapshot* snapshot) txCreation creation; Atom blockAtom; txByte* block = C_NULL; - txByte* blockLimit; +// txByte* blockLimit; Atom heapAtom; txSlot* heap = C_NULL; txSlot* heapLimit; @@ -1637,7 +798,7 @@ void fxDumpSnapshot(txMachine* the, txSnapshot* snapshot) block = c_malloc(blockAtom.atomSize); mxThrowIf(block == C_NULL); mxThrowIf((*snapshot->read)(snapshot->stream, block, blockAtom.atomSize)); - blockLimit = block + blockAtom.atomSize; +// blockLimit = block + blockAtom.atomSize; mxThrowIf((*snapshot->read)(snapshot->stream, &heapAtom, sizeof(Atom))); heapAtom.atomSize = ntohl(heapAtom.atomSize) - 8; @@ -1899,9 +1060,7 @@ void fxDumpChunkTable(FILE* file, txByte* data, txSize size) void fxDumpID(FILE* file, txID id) { - if (id < 0) - fprintf(file, "ID_? "); - else if (id == 0) + if (id == 0) fprintf(file, " "); else fprintf(file, "ID_%6.6d", id); From 40726ada4f9926502f1c370106f539b918ece321 Mon Sep 17 00:00:00 2001 From: Peter Hoddie Date: Mon, 29 Aug 2022 12:30:17 -0700 Subject: [PATCH 03/17] lockdown callbacks are now part of XS --- xsnap/sources/xsnap-worker.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/xsnap/sources/xsnap-worker.c b/xsnap/sources/xsnap-worker.c index 0ffa88b..6b959f2 100644 --- a/xsnap/sources/xsnap-worker.c +++ b/xsnap/sources/xsnap-worker.c @@ -64,7 +64,7 @@ extern void modInstallBase64(xsMachine *the); // The order of the callbacks materially affects how they are introduced to // code that runs from a snapshot, so must be consistent in the face of // upgrade. -#define mxSnapshotCallbackCount 20 +#define mxSnapshotCallbackCount 17 xsCallback gxSnapshotCallbacks[mxSnapshotCallbackCount] = { xs_issueCommand, // 0 xs_print, // 1 @@ -87,10 +87,6 @@ xsCallback gxSnapshotCallbacks[mxSnapshotCallbackCount] = { xs_base64_encode, // 15 xs_base64_decode, // 16 - fx_lockdown, // 17 - fx_harden, // 18 - fx_petrify, // 19 - // fx_setInterval, // fx_setTimeout, // fx_clearTimer, From de069895c3c2d337db42bec4e428ade766f0df19 Mon Sep 17 00:00:00 2001 From: Peter Hoddie Date: Wed, 14 Sep 2022 12:19:08 -0700 Subject: [PATCH 04/17] sync sync --- xsnap/sources/xsnap-worker.c | 12 +++++------- xsnap/sources/xsnap.c | 26 ++++++++++++++------------ xsnap/sources/xsnapPlatform.c | 5 +++++ 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/xsnap/sources/xsnap-worker.c b/xsnap/sources/xsnap-worker.c index 1e76288..41d702e 100644 --- a/xsnap/sources/xsnap-worker.c +++ b/xsnap/sources/xsnap-worker.c @@ -143,8 +143,7 @@ typedef enum { E_TOO_MUCH_COMPUTATION = 17, } ExitCode; -// 250 syscalls -#define MAX_TIMESTAMPS 502 +#define MAX_TIMESTAMPS 100 static struct timeval timestamps[MAX_TIMESTAMPS]; static unsigned int num_timestamps; static unsigned int timestamps_overrun; @@ -167,10 +166,9 @@ static void recordTimestamp() { // 2^64 is 18446744073709551616 , which is 20 characters long #define DIGITS_FOR_64 20 // [AA.AA,BB.BB,CC.CC]\0 -static char timestampBuffer[1 + MAX_TIMESTAMPS * (DIGITS_FOR_64 + 1 + 6 + 1) + 1]; -// over provisioning by considering all "sec" values as the max printed length. -// While the last timestamps does not have a trailing comma, the payload ends -// with both a closing square bracket and a null byte. +static char timestampBuffer[1 + MAX_TIMESTAMPS * (DIGITS_FOR_64 + 1 + DIGITS_FOR_64 + 1) + 1]; +// careless overprovisioning: the fractional part is only 6 digits, +// not 20, also we never add a trailing comma static char *renderTimestamps() { // return pointer to static string buffer with '[NN.NN,NN.NN]', or NULL @@ -735,7 +733,7 @@ static char* fxReadNetStringError(int code) static int fxWriteOkay(FILE* outStream, xsUnsignedValue meterIndex, xsMachine *the, char* buf, size_t length) { - recordTimestamp(); // before sending delivery-result to parent + recordTimestamp(); // delivery-result sent to parent char *tsbuf = renderTimestamps(); if (!tsbuf) { // rendering overrun error, send empty list diff --git a/xsnap/sources/xsnap.c b/xsnap/sources/xsnap.c index f4fa237..56155f9 100644 --- a/xsnap/sources/xsnap.c +++ b/xsnap/sources/xsnap.c @@ -100,16 +100,17 @@ int main(int argc, char* argv[]) int error = 0; int interval = 0; int option = 0; + int parserBufferSize = 8192 * 1024; xsCreation _creation = { - 16 * 1024 * 1024, /* initialChunkSize */ - 4 * 1024 * 1024, /* incrementalChunkSize */ - 1 * 1024 * 1024, /* initialHeapCount */ - 1 * 1024 * 1024, /* incrementalHeapCount */ - 4096, /* stackCount */ - 256 * 1024, /* keyCount */ - 1993, /* nameModulo */ - 127, /* symbolModulo */ - 256 * 1024, /* parserBufferSize */ + 32 * 1024 * 1024, /* initialChunkSize */ + 4 * 1024 * 1024, /* incrementalChunkSize */ + 256 * 1024, /* initialHeapCount */ + 128 * 1024, /* incrementalHeapCount */ + 4096, /* stackCount */ + 32000, /* keyCount */ + 1993, /* nameModulo */ + 127, /* symbolModulo */ + parserBufferSize, /* parserBufferSize */ 1993, /* parserTableModulo */ }; xsCreation* creation = &_creation; @@ -500,13 +501,13 @@ void xs_issueCommand(xsMachine* the) sprintf(path, "%05d-command.dat", gxStep); gxStep++; + file = fopen(path, "rb"); if (!file) xsUnknownError("cannot open %s", path); fseek(file, 0, SEEK_END); length = ftell(file); fseek(file, 0, SEEK_SET); - xsResult = xsArrayBuffer(NULL, (xsIntegerValue)length); - data = xsToArrayBuffer(xsResult); + data = c_malloc(length); length = fread(data, 1, length, file); fclose(file); @@ -515,10 +516,11 @@ void xs_issueCommand(xsMachine* the) if ((length != argLength) || c_memcmp(data, argData, length)) { fprintf(stderr, "### %s %.*s\n", path, (int)argLength, (char*)argData); -// fprintf(stderr, "@@@ %s %.*s\n", path, (int)length, (char*)data); + fprintf(stderr, "@@@ %s %.*s\n", path, (int)length, (char*)data); } else fprintf(stderr, "### %s\n", path); + c_free(data); sprintf(path, "%05d-reply.dat", gxStep); fprintf(stderr, "### %s\n", path); diff --git a/xsnap/sources/xsnapPlatform.c b/xsnap/sources/xsnapPlatform.c index 3e0d8b2..09c8528 100644 --- a/xsnap/sources/xsnapPlatform.c +++ b/xsnap/sources/xsnapPlatform.c @@ -711,6 +711,11 @@ void fxSetCurrentMeter(txMachine* the, txUnsigned value) } #endif +txSize fxGetCurrentHeapCount(txMachine* the) +{ + return the->currentHeapCount; +} + extern void fxDumpSnapshot(txMachine* the, txSnapshot* snapshot); typedef void (*txDumpChunk)(FILE* file, txByte* data, txSize size); From fdc84979993ffe11bec970e463e4b2e46949db4b Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Sat, 17 Sep 2022 21:47:54 +0000 Subject: [PATCH 05/17] fix: workaround unexpected exit after unhandled rejections --- xsnap/sources/xsnapPlatform.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xsnap/sources/xsnapPlatform.c b/xsnap/sources/xsnapPlatform.c index 09c8528..55c4eb7 100644 --- a/xsnap/sources/xsnapPlatform.c +++ b/xsnap/sources/xsnapPlatform.c @@ -364,7 +364,7 @@ void fxRunLoop(txMachine* the) } } } - fxCheckUnhandledRejections(the, 1); + // fxCheckUnhandledRejections(the, 1); } void fxFulfillModuleFile(txMachine* the) From ac35d1aa03415abd24b66bed9d21ac52e969b825 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Sun, 18 Sep 2022 14:14:22 +0000 Subject: [PATCH 06/17] fix: write delivery result after xs has gone quiescent --- xsnap/sources/xsnap-worker.c | 18 +++++++++++------- xsnap/sources/xsnapPlatform.c | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/xsnap/sources/xsnap-worker.c b/xsnap/sources/xsnap-worker.c index 1e76288..c3750cc 100644 --- a/xsnap/sources/xsnap-worker.c +++ b/xsnap/sources/xsnap-worker.c @@ -372,6 +372,8 @@ int main(int argc, char* argv[]) case '?': case 'e': xsBeginCrank(machine, gxCrankMeteringLimit); + char* response = NULL; + xsIntegerValue responseLength = 0; error = 0; xsBeginHost(machine); { @@ -405,12 +407,9 @@ int main(int argc, char* argv[]) meterIndex = xsEndCrank(machine); { if (error) { - xsStringValue message = xsToString(xsVar(1)); - writeError = fxWriteNetString(toParent, "!", message, strlen(message)); - // fprintf(stderr, "error: %d, writeError: %d %s\n", error, writeError, message); + response = xsToString(xsVar(1)); + responseLength = strlen(response); } else { - char* response = NULL; - xsIntegerValue responseLength = 0; // fprintf(stderr, "report: %d %s\n", xsTypeOf(report), xsToString(report)); xsTry { if (xsTypeOf(xsVar(1)) == xsReferenceType && xsHas(xsVar(1), xsID("result"))) { @@ -433,11 +432,16 @@ int main(int argc, char* argv[]) xsException = xsUndefined; } } - // fprintf(stderr, "response of %d bytes\n", responseLength); - writeError = fxWriteOkay(toParent, meterIndex, the, response, responseLength); } } xsEndHost(machine); + if (error) { + writeError = fxWriteNetString(toParent, "!", response, responseLength); + // fprintf(stderr, "error: %d, writeError: %d %s\n", error, writeError, response); + } else { + // fprintf(stderr, "response of %d bytes\n", responseLength); + writeError = fxWriteOkay(toParent, meterIndex, machine, response, responseLength); + } if (writeError != 0) { fprintf(stderr, "%s\n", fxWriteNetStringError(writeError)); c_exit(E_IO_ERROR); diff --git a/xsnap/sources/xsnapPlatform.c b/xsnap/sources/xsnapPlatform.c index 55c4eb7..09c8528 100644 --- a/xsnap/sources/xsnapPlatform.c +++ b/xsnap/sources/xsnapPlatform.c @@ -364,7 +364,7 @@ void fxRunLoop(txMachine* the) } } } - // fxCheckUnhandledRejections(the, 1); + fxCheckUnhandledRejections(the, 1); } void fxFulfillModuleFile(txMachine* the) From c91d9652277dc23e0d2a4dfd66a8acad265da878 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Sun, 18 Sep 2022 14:52:03 +0000 Subject: [PATCH 07/17] fix: prefix command results --- xsnap/sources/xsnap-worker.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/xsnap/sources/xsnap-worker.c b/xsnap/sources/xsnap-worker.c index c3750cc..cf87455 100644 --- a/xsnap/sources/xsnap-worker.c +++ b/xsnap/sources/xsnap-worker.c @@ -520,6 +520,12 @@ int main(int argc, char* argv[]) } } break; + case '/': + case '.': + case '!': + fprintf(stderr, "Unexpected command %c\n", command); + c_exit(E_IO_ERROR); + break; default: done = 1; break; @@ -818,8 +824,12 @@ static void xs_issueCommand(xsMachine *the) #if XSNAP_TEST_RECORD fxTestRecord(mxTestRecordJSON | mxTestRecordReply, buf, len); #endif - // TODO: can we avoid a copy? - xsResult = xsArrayBuffer(buf, len); + char command = *buf; + if (len == 0 || command != '/') { + xsUnknownError("Received unexpected command reply."); + } + + xsResult = xsArrayBuffer(buf + 1, len - 1); free(buf); } From 705ae284c36852dbebbe01bc14d9517e5ba60391 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 19 Sep 2022 09:52:23 -0700 Subject: [PATCH 08/17] docs: describe upstream issueCommand protocol --- xsnap/documentation/xsnap-worker.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/xsnap/documentation/xsnap-worker.md b/xsnap/documentation/xsnap-worker.md index 06eeec1..abad799 100644 --- a/xsnap/documentation/xsnap-worker.md +++ b/xsnap/documentation/xsnap-worker.md @@ -22,7 +22,11 @@ Once started, the process listens on file descriptor 3, and will write to file d * `?` (command): feed the body (as a JS `String`) to the registered `handleCommand(body)` handler * for both `e` and `?`, execution continues until both the `setImmediate` and the ready-promise-callback queues are empty (the vat is "quiescent") * if evaluation/`handleCommand()` throws an error (and the evaluated code does not catch it), the worker writes `!${toString(err)}` to fd4 - * if successful, the result should be an ArrayBuffer, or an object with a `.result` property that is an ArrayBuffer + * while running, if the application calls `globalThis.issueCommand(query)` (where `query` is an ArrayBuffer), the worker will write a netstring to file descriptor 4, whose payload is a single `?` character followed by contents of `query` + * the application will then do a blocking read on file descriptor 3 until a complete netstring is received + * the payload of this response must start with a single `/` character + * the remainder of the payload will returned (as an ArrayBuffer) to the caller of `issueCommand()` + * if successful, the evaluation/`handleCommand()` result should be an ArrayBuffer, or an object with a `.result` property that is an ArrayBuffer * anything else will yield an empty response string * the worker writes a netstring with the following body to fd4: * `.${meterObj}\1${result}` From f8e959289ac06b2f2746d67877e9193265c83514 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 19 Sep 2022 09:53:03 -0700 Subject: [PATCH 09/17] fix: all unknown commands exit with error, add 'q' to quit Rather than treating all unknown commands as a request for the worker to silently exit (rc=0), we make a specific 'q' command for that purpose. Unknown commands now cause an error exit (rc=2), with a message (to stderr) about what the surprising command was. This includes the characters that are only used in downstream responses to upstream `issueCommand()` calls, which might help diagnose desynchronization errors faster. --- xsnap/documentation/xsnap-worker.md | 7 ++++++- xsnap/sources/xsnap-worker.c | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/xsnap/documentation/xsnap-worker.md b/xsnap/documentation/xsnap-worker.md index abad799..fad11d9 100644 --- a/xsnap/documentation/xsnap-worker.md +++ b/xsnap/documentation/xsnap-worker.md @@ -48,7 +48,8 @@ Once started, the process listens on file descriptor 3, and will write to file d * for both `s` and `m`, an error writes a terse `!` to fd4, and success writes `.${meterObj}\1` (the same success response as for `e`/`?` but with an empty message: just the metering data) * both `s` and `m` are holdovers from `xsnap.c`, and should be considered deprecated in `xsnap-worker.c` * `w`: the body is treated as a filename. A GC collection is triggered, and then the JS engine state snapshot (the entire virtual machine state: heap, stack, symbol table, etc) is written to the given filename. Then execution continues normally. The response is `!` or `.${meterObj}\1` as with `s`/`m` -* all other command characters cause the worker to exit +* `q`: causes the worker to exit gently, with an exit code of `E_SUCCESS` (0) +* all other command characters cause the worker to exit noisily, with a messge to stderr about the unrecognized command, and an exit code of `E_IO_ERROR` (2) If at any point the computation exceeds one of the following limits, the process will exit with a non-zero (and non-negative) exit code: @@ -56,3 +57,7 @@ If at any point the computation exceeds one of the following limits, the process * `E_STACK_OVERFLOW` (12): when the JS stack exceeds the configured limit (hard-coded in `xsnap-worker.c` as `stackCount` to 4096). Also, at least for now, when the native stack exceeds a limit. * `E_NO_MORE_KEYS` (16): when the number of "keys" (unique property names) exceeds the limit (hard-coded in `xsnap-worker.c` as `keyCount` to 32000) * `E_TOO_MUCH_COMPUTATION` (17): when the computation exceeds the `-l` computron limit + +The other possible exit codes are: +* `E_SUCCESS` (0): when a `q` command is received +* `E_IO_ERROR` (2): when an unrecognized command is received diff --git a/xsnap/sources/xsnap-worker.c b/xsnap/sources/xsnap-worker.c index cf87455..ebfbb9c 100644 --- a/xsnap/sources/xsnap-worker.c +++ b/xsnap/sources/xsnap-worker.c @@ -520,14 +520,20 @@ int main(int argc, char* argv[]) } } break; - case '/': - case '.': - case '!': - fprintf(stderr, "Unexpected command %c\n", command); - c_exit(E_IO_ERROR); + case 'q': + done = 1; break; + + // We reserve some prefix characters to avoid/detect/debug confusion, + // all of which are explicitly rejected, just like unknown commands. Do not + // reuse these for new commands. + case '/': // downstream response to upstream issueCommand() + case '.': // upstream good response to downstream execute/eval + case '!': // upstream error response to downstream execute/eval default: - done = 1; + // note: the nsbuf we receive from fxReadNetString is null-terminated + fprintf(stderr, "Unexpected prefix '%c' in command '%s'\n", command, nsbuf); + c_exit(E_IO_ERROR); break; } free(nsbuf); From a4ccbeb439e395f47d9c50d8f7f6c6be621542c7 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 19 Sep 2022 12:27:15 -0700 Subject: [PATCH 10/17] Update xsnap/documentation/xsnap-worker.md Co-authored-by: Mathieu Hofman <86499+mhofman@users.noreply.github.com> --- xsnap/documentation/xsnap-worker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xsnap/documentation/xsnap-worker.md b/xsnap/documentation/xsnap-worker.md index fad11d9..166983e 100644 --- a/xsnap/documentation/xsnap-worker.md +++ b/xsnap/documentation/xsnap-worker.md @@ -25,7 +25,7 @@ Once started, the process listens on file descriptor 3, and will write to file d * while running, if the application calls `globalThis.issueCommand(query)` (where `query` is an ArrayBuffer), the worker will write a netstring to file descriptor 4, whose payload is a single `?` character followed by contents of `query` * the application will then do a blocking read on file descriptor 3 until a complete netstring is received * the payload of this response must start with a single `/` character - * the remainder of the payload will returned (as an ArrayBuffer) to the caller of `issueCommand()` + * the remainder of the payload will be returned (as an ArrayBuffer) to the caller of `issueCommand()` * if successful, the evaluation/`handleCommand()` result should be an ArrayBuffer, or an object with a `.result` property that is an ArrayBuffer * anything else will yield an empty response string * the worker writes a netstring with the following body to fd4: From aa494cee6f6b5cda70f7c685535940dae4321252 Mon Sep 17 00:00:00 2001 From: Peter Hoddie Date: Tue, 6 Dec 2022 13:37:34 -0800 Subject: [PATCH 11/17] sync profiler changes --- xsnap/documentation/Compartments.md | 4 +- xsnap/makefiles/lin/xsnap-worker.mk | 1 + xsnap/makefiles/lin/xsnap.mk | 3 +- xsnap/makefiles/mac/xsnap-worker.mk | 1 + xsnap/makefiles/mac/xsnap.mk | 1 + xsnap/sources/xsnap-worker.c | 61 ++++- xsnap/sources/xsnap.c | 27 ++- xsnap/sources/xsnap.h | 18 ++ xsnap/sources/xsnapPlatform.c | 80 ++++++- xsnap/sources/xsnapPlatform.h | 12 + xsnap/xsbug-node/main.js | 52 +++++ xsnap/xsbug-node/readme.md | 139 ++++++++++++ xsnap/xsbug-node/test.js | 12 + xsnap/xsbug-node/xsbug-machine.js | 331 ++++++++++++++++++++++++++++ xsnap/xsbug-node/xsbug-profile.js | 173 +++++++++++++++ 15 files changed, 893 insertions(+), 22 deletions(-) create mode 100644 xsnap/xsbug-node/main.js create mode 100644 xsnap/xsbug-node/readme.md create mode 100644 xsnap/xsbug-node/test.js create mode 100644 xsnap/xsbug-node/xsbug-machine.js create mode 100644 xsnap/xsbug-node/xsbug-profile.js diff --git a/xsnap/documentation/Compartments.md b/xsnap/documentation/Compartments.md index 4fce279..fde3f6f 100644 --- a/xsnap/documentation/Compartments.md +++ b/xsnap/documentation/Compartments.md @@ -119,9 +119,7 @@ The `moduleMap` parameter is an object that initializes the **module map** of th The `Compartment` constructor copies the `moduleMap` properties using the same behavior as `Object.assign`. -A compartment cannot acces module descriptors or module specifiers provided by -the module map. A compartment can only access module namespaces that will be -loaded and initialized based on such module descriptors or module specifiers. +A compartment cannot access module records or module specifiers provided by the module map. A compartment can only access module namespaces that will be loaded and initialized based on such module records or module specifiers. #### module namespaces diff --git a/xsnap/makefiles/lin/xsnap-worker.mk b/xsnap/makefiles/lin/xsnap-worker.mk index 8e656a2..2e29c0f 100644 --- a/xsnap/makefiles/lin/xsnap-worker.mk +++ b/xsnap/makefiles/lin/xsnap-worker.mk @@ -31,6 +31,7 @@ C_OPTIONS = \ -DmxLockdown=1 \ -DmxMetering=1 \ -DmxDebug=1 \ + -DmxInstrument=1 \ -DmxNoConsole=1 \ -DmxBoundsCheck=1 \ -DmxParse=1 \ diff --git a/xsnap/makefiles/lin/xsnap.mk b/xsnap/makefiles/lin/xsnap.mk index 2abb078..d6860af 100644 --- a/xsnap/makefiles/lin/xsnap.mk +++ b/xsnap/makefiles/lin/xsnap.mk @@ -25,6 +25,7 @@ C_OPTIONS = \ -DmxLockdown=1 \ -DmxMetering=1 \ -DmxParse=1 \ + -DmxProfile=1 \ -DmxRun=1 \ -DmxSloppy=1 \ -DmxSnapshot=1 \ @@ -51,7 +52,7 @@ ifeq ($(XSNAP_RANDOM_INIT),1) LIBRARIES += -lbsd C_OPTIONS += -DmxSnapshotRandomInit endif - + LINK_OPTIONS = -rdynamic OBJECTS = \ diff --git a/xsnap/makefiles/mac/xsnap-worker.mk b/xsnap/makefiles/mac/xsnap-worker.mk index b5a0031..d3ef737 100644 --- a/xsnap/makefiles/mac/xsnap-worker.mk +++ b/xsnap/makefiles/mac/xsnap-worker.mk @@ -36,6 +36,7 @@ C_OPTIONS = \ -DmxLockdown=1 \ -DmxMetering=1 \ -DmxDebug=1 \ + -DmxInstrument=1 \ -DmxNoConsole=1 \ -DmxBoundsCheck=1 \ -DmxParse=1 \ diff --git a/xsnap/makefiles/mac/xsnap.mk b/xsnap/makefiles/mac/xsnap.mk index 43c85c2..5c784e8 100644 --- a/xsnap/makefiles/mac/xsnap.mk +++ b/xsnap/makefiles/mac/xsnap.mk @@ -30,6 +30,7 @@ C_OPTIONS = \ -DmxLockdown=1 \ -DmxMetering=1 \ -DmxParse=1 \ + -DmxProfile=1 \ -DmxRun=1 \ -DmxSloppy=1 \ -DmxSnapshot=1 \ diff --git a/xsnap/sources/xsnap-worker.c b/xsnap/sources/xsnap-worker.c index 41d702e..9d0e127 100644 --- a/xsnap/sources/xsnap-worker.c +++ b/xsnap/sources/xsnap-worker.c @@ -102,6 +102,19 @@ static int fxSnapshotWrite(void* stream, void* address, size_t size) return (fwrite(address, size, 1, stream) == 1) ? 0 : errno; } +#if mxInstrument +#define xsnapInstrumentCount 1 +static xsStringValue xsnapInstrumentNames[xsnapInstrumentCount] = { + "Metering", +}; +static xsStringValue xsnapInstrumentUnits[xsnapInstrumentCount] = { + " times", +}; +static xsIntegerValue xsnapInstrumentValues[xsnapInstrumentCount] = { + 0, +}; +#endif + #if mxMetering #define xsBeginCrank(_THE, _LIMIT) \ (xsSetCurrentMeter(_THE, 0), \ @@ -182,7 +195,7 @@ static char *renderTimestamps() { // final \0". It writes at most size-1 characters, then writes // the trailing \0. wrote = snprintf(p, size, "%lu.%06lu", - timestamps[i].tv_sec, timestamps[i].tv_usec); + timestamps[i].tv_sec, (unsigned long)timestamps[i].tv_usec); p += wrote; size -= wrote; if (size < 2) { // 2 is enough for "]\0", but 1 is not @@ -338,10 +351,28 @@ int main(int argc, char* argv[]) fprintf(stderr, "fdopen(4) to parent failed\n"); c_exit(E_IO_ERROR); } +#if mxInstrument + xsDescribeInstrumentation(machine, xsnapInstrumentCount, xsnapInstrumentNames, xsnapInstrumentUnits); +#endif xsBeginMetering(machine, fxMeteringCallback, interval); { + fd_set rfds; char done = 0; while (!done) { + FD_ZERO(&rfds); + FD_SET(3, &rfds); + FD_SET(5, &rfds); + if (select(6, &rfds, NULL, NULL, NULL) >= 0) { + if (FD_ISSET(5, &rfds)) + xsRunDebugger(machine); + if (!FD_ISSET(3, &rfds)) + continue; + } + else { + fprintf(stderr, "select failed\n"); + error = E_IO_ERROR; + break; + } // By default, use the infinite meter. gxCurrentMeter = 0; @@ -350,7 +381,7 @@ int main(int argc, char* argv[]) size_t nslen; resetTimestamps(); int readError = fxReadNetString(fromParent, &nsbuf, &nslen); - recordTimestamp(); // after delivery received from parent + recordTimestamp(); // delivery received from parent int writeError = 0; if (readError != 0) { @@ -362,7 +393,7 @@ int main(int argc, char* argv[]) } } char command = *nsbuf; - // fprintf(stderr, "command: len %d %c arg: %s\n", nslen, command, nsbuf + 1); +// fprintf(stderr, "command: len %d %c arg: %s\n", nslen, command, nsbuf + 1); switch(command) { case 'R': // isReady fxWriteNetString(toParent, ".", "", 0); @@ -379,7 +410,6 @@ int main(int argc, char* argv[]) #if XSNAP_TEST_RECORD fxTestRecord(mxTestRecordJSON | mxTestRecordParam, nsbuf + 1, nslen - 1); #endif - // TODO: can we avoid a copy? xsVar(0) = xsArrayBuffer(nsbuf + 1, nslen - 1); xsVar(1) = xsCall1(xsGlobal, xsID("handleCommand"), xsVar(0)); } else { @@ -519,6 +549,10 @@ int main(int argc, char* argv[]) break; } free(nsbuf); +#if mxInstrument + xsnapInstrumentValues[0] = (xsIntegerValue)meterIndex; + xsSampleInstrumentation(machine, xsnapInstrumentCount, xsnapInstrumentValues); +#endif } xsBeginHost(machine); { @@ -790,16 +824,24 @@ static void xs_issueCommand(xsMachine *the) xsTypeError("expected ArrayBuffer"); } - size_t length = xsGetArrayBufferLength(xsArg(0)); - char* buf = xsToArrayBuffer(xsArg(0)); - - recordTimestamp(); // before sending command to parent + size_t length; + char* buf = NULL; + length = xsGetArrayBufferLength(xsArg(0)); + + buf = malloc(length); + if (!buf) { + fxAbort(the, xsNotEnoughMemoryExit); + } + xsGetArrayBufferData(xsArg(0), 0, buf, length); int writeError = fxWriteNetString(toParent, "?", buf, length); + free(buf); + if (writeError != 0) { xsUnknownError(fxWriteNetStringError(writeError)); } + recordTimestamp(); // command sent to parent // read netstring size_t len; @@ -807,12 +849,11 @@ static void xs_issueCommand(xsMachine *the) if (readError != 0) { xsUnknownError(fxReadNetStringError(readError)); } - recordTimestamp(); // after command-result received from parent + recordTimestamp(); // command-result received from parent #if XSNAP_TEST_RECORD fxTestRecord(mxTestRecordJSON | mxTestRecordReply, buf, len); #endif - // TODO: can we avoid a copy? xsResult = xsArrayBuffer(buf, len); free(buf); } diff --git a/xsnap/sources/xsnap.c b/xsnap/sources/xsnap.c index 56155f9..9a487f6 100644 --- a/xsnap/sources/xsnap.c +++ b/xsnap/sources/xsnap.c @@ -95,12 +95,14 @@ int main(int argc, char* argv[]) { int argi; int argd = 0; + int argp = 0; int argr = 0; int argw = 0; int error = 0; int interval = 0; int option = 0; int parserBufferSize = 8192 * 1024; + int profiling = 0; xsCreation _creation = { 32 * 1024 * 1024, /* initialChunkSize */ 4 * 1024 * 1024, /* incrementalChunkSize */ @@ -174,7 +176,15 @@ int main(int argc, char* argv[]) } else if (!strcmp(argv[argi], "-m")) option = 2; - else if (!strcmp(argv[argi], "-p")) + else if (!strcmp(argv[argi], "-p")) { + profiling = 1; + argi++; + if ((argi < argc) && (argv[argi][0] != '-')) + argp = argi; + else + argi--; + } + else if (!strcmp(argv[argi], "-q")) gxMeteringPrint = 1; else if (!strcmp(argv[argi], "-r")) { argi++; @@ -229,6 +239,8 @@ int main(int argc, char* argv[]) machine = xsCreateMachine(creation, "xsnap", NULL); xsBuildAgent(machine); } + if (profiling) + fxStartProfiling(machine); xsBeginMetering(machine, xsMeteringCallback, interval); { if (option == 5) { @@ -263,6 +275,8 @@ int main(int argc, char* argv[]) } if (argv[argi][0] == '-') continue; + if (argi == argp) + continue; if (argi == argr) continue; if (argi == argw) @@ -299,6 +313,17 @@ int main(int argc, char* argv[]) } } xsEndMetering(machine); + if (profiling) { + if (argp) { + FILE* stream = fopen(argv[argp], "w"); + if (stream) + fxStopProfiling(machine, stream); + else + fprintf(stderr, "cannot write profile %s: %s\n", argv[argp], strerror(errno)); + } + else + fxStopProfiling(machine, C_NULL); + } if (machine->abortStatus) error = machine->abortStatus; xsDeleteMachine(machine); diff --git a/xsnap/sources/xsnap.h b/xsnap/sources/xsnap.h index 1380a45..32330a3 100644 --- a/xsnap/sources/xsnap.h +++ b/xsnap/sources/xsnap.h @@ -72,6 +72,8 @@ struct xsSnapshotRecord { #define xsWriteSnapshot(_THE, _SNAPSHOT) \ fxWriteSnapshot(_THE, _SNAPSHOT) +#define xsRunDebugger(_THE) \ + fxRunDebugger(_THE) #define xsRunModuleFile(_PATH) \ fxRunModuleFile(the, _PATH) #define xsRunProgramFile(_PATH) \ @@ -84,6 +86,16 @@ struct xsSnapshotRecord { #define xsSetTimer(_INTERVAL, _REPEAT) \ fxSetTimer(the, _INTERVAL, _REPEAT) +#define xsVersion(_BUFFER, _SIZE) \ + fxVersion(_BUFFER, _SIZE) + +#ifdef mxInstrument +#define xsDescribeInstrumentation(_THE,_COUNT,_NAMES,_UNITS) \ + fxDescribeInstrumentation(_THE,_COUNT,_NAMES,_UNITS) +#define xsSampleInstrumentation(_THE,_COUNT,_VALUES) \ + fxSampleInstrumentation(_THE,_COUNT,_VALUES) +#endif + #define xsVersion(_BUFFER, _SIZE) \ fxVersion(_BUFFER, _SIZE) @@ -106,6 +118,7 @@ mxImport void fxSetCurrentMeter(xsMachine* the, xsUnsignedValue value); mxImport xsMachine* fxReadSnapshot(xsSnapshot* snapshot, xsStringValue theName, void* theContext); mxImport int fxWriteSnapshot(xsMachine* the, xsSnapshot* snapshot); +mxImport void fxRunDebugger(xsMachine* the); mxImport void fxRunModuleFile(xsMachine* the, xsStringValue path); mxImport void fxRunProgramFile(xsMachine* the, xsStringValue path); mxImport void fxRunLoop(xsMachine* the); @@ -113,6 +126,11 @@ mxImport void fxRunLoop(xsMachine* the); mxImport void fxClearTimer(xsMachine* the); mxImport void fxSetTimer(xsMachine* the, xsNumberValue interval, xsBooleanValue repeat); +#ifdef mxInstrument +mxImport void fxDescribeInstrumentation(xsMachine* the, xsIntegerValue count, xsStringValue* names, xsStringValue* units); +mxImport void fxSampleInstrumentation(xsMachine* the, xsIntegerValue count, xsIntegerValue* values); +#endif + mxImport void fxVersion(xsStringValue theBuffer, xsUnsignedValue theSize); mxImport void fx_lockdown(xsMachine* the); diff --git a/xsnap/sources/xsnapPlatform.c b/xsnap/sources/xsnapPlatform.c index 09c8528..207b97e 100644 --- a/xsnap/sources/xsnapPlatform.c +++ b/xsnap/sources/xsnapPlatform.c @@ -278,7 +278,10 @@ void fxFreeSlots(txMachine* the, void* theSlots) void fxCreateMachinePlatform(txMachine* the) { #ifdef mxDebug +#ifdef mxInstrument +#else the->connection = mxNoSocket; +#endif #endif // Original 10x strategy: // SLOGFILE=out.slog agoric start local-chain @@ -300,13 +303,21 @@ void fxQueuePromiseJobs(txMachine* the) the->promiseJobs = 1; } +void fxRunDebugger(txMachine* the) +{ +#ifdef mxDebug + fxDebugCommand(the); +#endif +} + void fxRunLoop(txMachine* the) { c_timeval tv; txNumber when; txJob* job; txJob** address; - fxEndJob(the); + // Comment next line to make snapshots consistent again. +// fxEndJob(the); for (;;) { while (the->promiseJobs) { while (the->promiseJobs) { @@ -385,8 +396,8 @@ void fxRunModuleFile(txMachine* the, txString path) mxDub(); fxGetID(the, mxID(_then)); mxCall(); - fxNewHostFunction(the, fxFulfillModuleFile, 1, XS_NO_ID); - fxNewHostFunction(the, fxRejectModuleFile, 1, XS_NO_ID); + fxNewHostFunction(the, fxFulfillModuleFile, 1, XS_NO_ID, XS_NO_ID); + fxNewHostFunction(the, fxRejectModuleFile, 1, XS_NO_ID, XS_NO_ID); mxRunCount(2); mxPop(); } @@ -418,7 +429,8 @@ txID fxFindModule(txMachine* the, txSlot* realm, txID moduleID, txSlot* slot) if (dot) { if (moduleID == XS_NO_ID) return XS_NO_ID; - c_strcpy(path, fxGetKeyName(the, moduleID)); + c_strncpy(path, fxGetKeyName(the, moduleID), C_PATH_MAX - 1); + path[C_PATH_MAX - 1] = 0; slash = c_strrchr(path, mxSeparator); if (!slash) return XS_NO_ID; @@ -457,7 +469,8 @@ void fxLoadModule(txMachine* the, txSlot* module, txID moduleID) #else txUnsigned flags = 0; #endif - c_strcpy(path, fxGetKeyName(the, moduleID)); + c_strncpy(path, fxGetKeyName(the, moduleID), C_PATH_MAX - 1); + path[C_PATH_MAX - 1] = 0; if (c_realpath(path, real)) { script = fxLoadScript(the, real, flags); if (script) @@ -514,6 +527,54 @@ txScript* fxLoadScript(txMachine* the, txString path, txUnsigned flags) #ifdef mxDebug +#ifdef mxInstrument + +void fxConnect(txMachine* the) +{ +} + +void fxDisconnect(txMachine* the) +{ +} + +txBoolean fxIsConnected(txMachine* the) +{ + return 1; +} + +txBoolean fxIsReadable(txMachine* the) +{ + return 0; +} + +void fxReceive(txMachine* the) +{ + ssize_t count; +again: + count = read(5, the->debugBuffer, sizeof(the->debugBuffer) - 1); + if (count < 0) { + if (errno == EINTR) + goto again; + the->debugOffset = 0; + } + else + the->debugOffset = count; + the->debugBuffer[the->debugOffset] = 0; +} + +void fxSend(txMachine* the, txBoolean more) +{ + ssize_t count; +again: + count = write(6, the->echoBuffer, the->echoOffset); + if (count < 0) { + if (errno == EINTR) + goto again; + } +} + +#else + void fxConnect(txMachine* the) { char name[256]; @@ -537,8 +598,8 @@ void fxConnect(txMachine* the) } } else { - // Require XSBUG_HOST to be set for debugging. - return; + strcpy(name, "localhost"); + port = 5002; } memset(&address, 0, sizeof(address)); address.sin_family = AF_INET; @@ -692,6 +753,8 @@ void fxSend(txMachine* the, txBoolean more) } } +#endif /* mxInstrument */ + #endif /* mxDebug */ void fxVersion(txString theBuffer, txSize theSize) @@ -738,6 +801,7 @@ void fxDumpSnapshot(txMachine* the, txSnapshot* snapshot) { Atom atom; txByte byte; + txID profileID; txCreation creation; Atom blockAtom; txByte* block = C_NULL; @@ -785,6 +849,7 @@ void fxDumpSnapshot(txMachine* the, txSnapshot* snapshot) mxThrowIf((*snapshot->read)(snapshot->stream, &atom, sizeof(Atom))); atom.atomSize = ntohl(atom.atomSize) - 8; mxThrowIf((*snapshot->read)(snapshot->stream, &creation, sizeof(txCreation))); + mxThrowIf((*snapshot->read)(snapshot->stream, &profileID, sizeof(txID))); fprintf(stderr, "%4.4s %d\n", (txString)&(atom.atomType), atom.atomSize + 8); fprintf(stderr, "\tinitialChunkSize: %d\n", creation.initialChunkSize); fprintf(stderr, "\tincrementalChunkSize: %d\n", creation.incrementalChunkSize); @@ -797,6 +862,7 @@ void fxDumpSnapshot(txMachine* the, txSnapshot* snapshot) fprintf(stderr, "\tparserBufferSize: %d\n", creation.parserBufferSize); fprintf(stderr, "\tparserTableModulo: %d\n", creation.parserTableModulo); fprintf(stderr, "\tstaticSize: %d\n", creation.staticSize); + fprintf(stderr, "\tprofileID: %d\n", profileID); mxThrowIf((*snapshot->read)(snapshot->stream, &blockAtom, sizeof(Atom))); blockAtom.atomSize = ntohl(blockAtom.atomSize) - 8; diff --git a/xsnap/sources/xsnapPlatform.h b/xsnap/sources/xsnapPlatform.h index dc8c143..d87918f 100644 --- a/xsnap/sources/xsnapPlatform.h +++ b/xsnap/sources/xsnapPlatform.h @@ -69,6 +69,17 @@ #define mxUseGCCAtomics 1 #define mxUsePOSIXThreads 1 #endif +#ifdef mxInstrument +#define mxMachinePlatform \ + int abortStatus; \ + int promiseJobs; \ + void* timerJobs; \ + void* waiterCondition; \ + void* waiterData; \ + void* waiterLink; \ + size_t allocationLimit; \ + size_t allocatedSpace; +#else #define mxMachinePlatform \ txSocket connection; \ int abortStatus; \ @@ -79,6 +90,7 @@ void* waiterLink; \ size_t allocationLimit; \ size_t allocatedSpace; +#endif #define mxUseDefaultBuildKeys 1 #define mxUseDefaultParseScript 1 diff --git a/xsnap/xsbug-node/main.js b/xsnap/xsbug-node/main.js new file mode 100644 index 0000000..c2d160d --- /dev/null +++ b/xsnap/xsbug-node/main.js @@ -0,0 +1,52 @@ +const fs = require("fs"); +const os = require('node:os'); +const { spawn } = require('node:child_process'); +const { Machine } = require('./xsbug-machine.js'); + +const encoder = new TextEncoder(); +const decoder = new TextDecoder(); + +function writeNetString(output, string) { + const message = encoder.encode(string); + const messageLength = message.length; + const prefix = encoder.encode(messageLength.toString() + ':'); + const prefixLength = prefix.length; + const data = new Uint8Array(prefixLength + messageLength + 1); + data.set(prefix, 0); + data.set(message, prefixLength); + data[prefixLength + messageLength] = ','.charCodeAt(0); +// console.log(`>>> ${messageLength}:${string}`); + output.write(data); +} + +const platforms = { + linux: 'lin', + darwin: 'mac', +}; +const xsnapProcess = spawn(`../build/bin/${platforms[os.platform()]}/debug/xsnap-worker`, [], { + stdio: ['ignore', 'inherit', 'inherit', 'pipe', 'pipe', 'pipe', 'pipe'], +}); +xsnapProcess.once('exit', (code, signal) => { + console.log(`exit ${code} ${signal}`); +}); +const xsnapOutput = xsnapProcess.stdio[3]; +const xsnapInput = xsnapProcess.stdio[4]; +const xsbugOutput = xsnapProcess.stdio[5]; +const xsbugInput = xsnapProcess.stdio[6]; + +const machine = new Machine(xsbugInput, xsbugOutput); +machine.on('instruments', (instruments) => { + machine.reportInstruments(instruments); +}); +machine.on('profile', (profile) => { + profile.reportHits(); + fs.writeFileSync("./test.cpuprofile", profile.toString()); +}); +machine.doStartProfiling(); + +xsnapInput.on('data', (data) => { +// console.log(`<<< ${data}`); + machine.doStopProfiling(); +}); +const buffer = fs.readFileSync("./test.js"); +writeNetString(xsnapOutput, `e{${buffer.toString()}}`); diff --git a/xsnap/xsbug-node/readme.md b/xsnap/xsbug-node/readme.md new file mode 100644 index 0000000..d96ab7c --- /dev/null +++ b/xsnap/xsbug-node/readme.md @@ -0,0 +1,139 @@ +# xsbug protocol in node.js + +This snippet shows how to use the xsbug protocol to profile and instrument the xsnap worker in node.js + +To test, build the xsnap worker then execute + + npm install --save saxophone + node main.js + +in this directory. A few seconds later the console displays + + Metering: 178698743 times + Chunk used: 16795680 / 33554432 bytes + Slot used: 8514336 / 12582912 bytes + Stack used: 6880 / 131072 bytes + Garbage collections: 12 times + Keys used: 483 keys + Modules loaded: 0 modules + Parser used: 8418919 bytes + Floating Point: 6139586 operations + 1467 ms 1467 ms (anonymous-540) (test.js:8) + 119 ms 1771 ms test (test.js:1) + 79 ms 79 ms foo (test.js:4) + 73 ms 83 ms Array.prototype.push + 31 ms 31 ms (gc) + 2 ms 1479 ms Array.prototype.sort + +The snippet also writes a `test.cpuprofile` file that can be viewed in **Google Chrome DevTools**, **Visual Studio Code**, and **xsbug**. See [XS Profiler](https://github.com/Moddable-OpenSource/moddable/blob/public/documentation/xs/XS%20Profiler.md) for details. + +## main.js + +The script spawns the xsnap worker process with four pipes: + +- 3: xsnap output +- 4: xsnap input +- 5: xsbug output +- 6: xsbug input + +The xsbug pipes are used for the xsbug protocol thru an xsbug `Machine` object. + + const machine = new Machine(xsbugInput, xsbugOutput); + machine.on('instruments', (instruments) => { + machine.reportInstruments(instruments); + }); + machine.on('profile', (profile) => { + profile.reportHits(); + fs.writeFileSync("./test.cpuprofile", profile.toString()); + }); + machine.doStartProfiling(); + +The xsnap pipes are used to communicate with xsnap as usual. The script reads the `test.js` file, sends an `e{...}` command to xsnap and waits for the result. + + xsnapInput.on('data', (data) => { + machine.doStopProfiling(); + }); + const buffer = fs.readFileSync("./test.js"); + writeNetString(xsnapOutput, `e{${buffer.toString()}}`); + +## xsbug-machine.js + +This module defines the xsbug `Machine` class. + +The xsbug protocol uses XML so the module needs a SAX parser. A few have been tried but [Saxophone](https://github.com/matteodelabre/saxophone) was the only one that worked well. + +The xsbug `Machine` class defines a bunch of commands and internal events. These are all the commands and events that **xsbug** uses. + +Currently there are two external events: `'instruments'` and `'profile'`. + +## xsbug-profile.js + +This module defines the xsbug `Profile` class. + +The xsbug `Machine` object creates an xsbug `Profile` object to accumulate profile records and samples. + +Once the xsbug `Machine` object triggered the `'profile'` event, the xsbug `Profile` object can be transformed into a `JSON` string to be saved into a `.cpuprofile` file. + +The xsbug `Profile` object can also report a summary of the profile hits to the console. + +## ../sources + +`xsnapPlatform.c` has been modified to use pipes instead of sockets for the xsbug protocol. + +### select + +Because there are two read pipes, the xsnap worker cannot block while waiting for one of them to be readable. So `xsnap-worker.c` has been modified to use `select` in its main loop. + + fd_set rfds; + char done = 0; + while (!done) { + FD_ZERO(&rfds); + FD_SET(3, &rfds); + FD_SET(5, &rfds); + if (select(6, &rfds, NULL, NULL, NULL) >= 0) { + if (FD_ISSET(5, &rfds)) + xsRunDebugger(machine); + if (!FD_ISSET(3, &rfds)) + continue; + } + else { + fprintf(stderr, "select failed\n"); + error = E_IO_ERROR; + break; + } + //... + +### metering instrument + +XS allows hosts to add instruments. As an example, `xsnap-worker.c` has been modified to add a metering instrument. + + #if mxInstrument + #define xsnapInstrumentCount 1 + static xsStringValue xsnapInstrumentNames[xsnapInstrumentCount] = { + "Metering", + }; + static xsStringValue xsnapInstrumentUnits[xsnapInstrumentCount] = { + " times", + }; + static xsIntegerValue xsnapInstrumentValues[xsnapInstrumentCount] = { + 0, + }; + #endif + +Instruments needs to be described once, typically after creating the XS machine. + + #if mxInstrument + xsDescribeInstrumentation(machine, xsnapInstrumentCount, xsnapInstrumentNames, xsnapInstrumentUnits); + #endif + +Then instruments can be sampled at will. + + #if mxInstrument + xsnapInstrumentValues[0] = (xsIntegerValue)meterIndex; + xsSampleInstrumentation(machine, xsnapInstrumentCount, xsnapInstrumentValues); + #endif + +Every time instruments are sampled, the xsbug `Machine` object triggers the `'instruments'` event. + + + diff --git a/xsnap/xsbug-node/test.js b/xsnap/xsbug-node/test.js new file mode 100644 index 0000000..80c10de --- /dev/null +++ b/xsnap/xsbug-node/test.js @@ -0,0 +1,12 @@ +function test(n) { + const result = []; + while (n > 0) { + const foo = x => x ** x; + result.push(foo(n)); + n--; + } + result.sort((a,b) => a - b); + return result +} +test(1024*256); +//# sourceURL=test.js diff --git a/xsnap/xsbug-node/xsbug-machine.js b/xsnap/xsbug-node/xsbug-machine.js new file mode 100644 index 0000000..c676eb0 --- /dev/null +++ b/xsnap/xsbug-node/xsbug-machine.js @@ -0,0 +1,331 @@ +const { Profile } = require('./xsbug-profile.js'); +const Saxophone = require('saxophone'); + +class Machine { + constructor(input, output) { + const parser = new Saxophone() + parser.on('tagopen', ({ name, attrs, isSelfClosing }) => { + this.onStartElement(name, Saxophone.parseAttrs(attrs)); + if (isSelfClosing) + this.onEndElement(name); + }); + parser.on('tagclose', ({ name }) => { + this.onEndElement(name); + }); + parser.on('text', ({ contents }) => { + this.onCharacterData(Saxophone.parseEntities(contents)); + }); + parser.on('cdata', ({ contents }) => { + this.onCharacterData(contents); + }); + parser.on('error', err => { + this.onError(err); + }); + input.pipe(parser); + this.encoder = new TextEncoder(); + this.instruments = []; + this.output = output; + this.profile = new Profile(); + this.profiling = false; + } + + onStartElement(name, attributes) { +// console.log(`onStartElement ${name} ${JSON.stringify(attributes)}`); + const parent = this.current; + const current = attributes; + switch (name) { + case 'xsbug': + break; + case 'breakpoint': + case 'instrument': + case 'file': + case 'frame': + parent.children.push(current); + current.parent = parent; + break; + case 'node': + case 'property': + parent.children.push(current); + current.parent = parent; + current.children = []; + break; + + case 'break': + case 'bubble': + case 'eval': + case 'log': + case 'ps': + case 'samples': + current.data = ""; + break; + + case 'breakpoints': + case 'instruments': + case 'files': + case 'frames': + case 'global': + case 'grammar': + case 'local': + current.children = []; + break; + + } + this.current = current; + } + onEndElement(name) { +// console.log(`onEndElement ${name}`); + const current = this.current; + this.current = null; + switch (name) { + case 'xsbug': + break; + case 'breakpoint': + case 'instrument': + case 'file': + case 'frame': + this.current = current.parent; + delete current.parent; + break; + case 'node': + case 'property': + this.current = current.parent; + delete current.parent; + if (current.children.length == 0) + delete current.children; + break; + + case 'break': + this.onLogged(current.path, current.line, current.data); + this.onBroken(current.path, current.line, current.data); + break; + case 'breakpoints': + this.onViewChanged(name, current.children); + break; + case 'bubble': + this.onBubbled(current.name, current.value, current.path, current.line, current.data); + break; + case 'eval': + this.onEval(current.path, current.data); + break; + case 'files': + this.onViewChanged(name, current.children); + break; + case 'frames': + this.onViewChanged(name, current.children); + break; + case 'global': + this.onViewChanged(name, current.children); + break; + case 'grammar': + this.onViewChanged("modules", current.children); // historical + break; + case 'import': + this.onImport(current.path); + break; + case 'instruments': + this.onInstrumentDescriptions(current.children); + break; + case 'local': + if (current.path && current.line) + this.onFileChanged(current.path, current.line); + this.onFrameChanged(current.name, current.value); + this.onViewChanged(name, current.children); + break; + case 'log': + this.onLogged(current.path, current.line, current.data); + break; + case 'login': + this.onTitleChanged(current.name, current.value); + break; + case 'pr': + this.onProfileRecord(current.name, current.value, current.path, current.line); + break; + case 'ps': + this.onProfileSamples(current.data); + break; + case 'pt': + this.onProfileTime(current.name, current.value); + break; + case 'samples': + this.onInstrumentValues(current.data.split(",")); + break; + } + } + onCharacterData(data) { +// console.log(`onCharacterData ${data}`); + if (this.current) + this.current.data += data; + } + onError(error) { + } + + // COMMANDS + doCommand(name) { + this.output.write(this.encoder.encode(`\r\n<${name}/>\r\n`)); + } + doAbort() { + this.doCommand(`abort`); + } + doClearAllBreakpoints() { + this.doCommand(`clear-all-breakpoints`); + } + doClearBreakpoint(path, line) { + this.doCommand(`clear-breakpoint path="${path}" line="${line}"`); + } + doGo() { + this.doCommand(`go`); + } + doImport(path, wait) { + this.doCommand(`import path="${path}" line="${wait ? 1 : 0}"`); + } + doLogout() { + this.doCommand(`logout`); + } + doModule(path, wait, source) { + let string = `\r\n\r\n`; + this.output.write(this.encoder.encode(string)); + } + doScript(path, wait, source) { + let string = `\r\n