From 617519534065c013069897b798d200222ed26457 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Sun, 1 Aug 2021 20:11:18 -0500 Subject: [PATCH 1/6] chore(xsnap): add xsnap-native submodule - xsnap -> xsnap-worker to distinguish cli app from long-running worker - get xsnap heap exhaustion crash fix - never mind meters beyond compute, allocate - get mac makefile update fixes #2469 refs #3139 --- .gitmodules | 3 +++ packages/xsnap/moddable | 2 +- packages/xsnap/xsnap-native | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) create mode 160000 packages/xsnap/xsnap-native diff --git a/.gitmodules b/.gitmodules index 0c8bd3cb2b1..7f2d0ffb594 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "packages/xsnap/moddable"] path = packages/xsnap/moddable url = https://github.com/agoric-labs/moddable.git +[submodule "packages/xsnap/xsnap-native"] + path = packages/xsnap/xsnap-native + url = https://github.com/agoric-labs/xsnap-pub diff --git a/packages/xsnap/moddable b/packages/xsnap/moddable index 60247366c31..91b0b8bfdc4 160000 --- a/packages/xsnap/moddable +++ b/packages/xsnap/moddable @@ -1 +1 @@ -Subproject commit 60247366c31c97e3d0907000cdf143fce798bdd6 +Subproject commit 91b0b8bfdc4ce119d6c91df1d253f6d9be095b2b diff --git a/packages/xsnap/xsnap-native b/packages/xsnap/xsnap-native new file mode 160000 index 00000000000..65d8e66073b --- /dev/null +++ b/packages/xsnap/xsnap-native @@ -0,0 +1 @@ +Subproject commit 65d8e66073b9a183a363c90d17ea4090274871f2 From 7e7d584aafc0120c2a02368b2331f9feac43e556 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Thu, 5 Aug 2021 11:55:39 -0500 Subject: [PATCH 2/6] chore(xsnap): get xsnap-worker bin from xsnap-native submodule - build.js: - build from xsnap-native submodule - option to print submodule urls, hashes in env format - factor out ambient authority - use Promise.all() as appropriate - update docker builds - prune files obsoleted by xsnap-native submodule --- .github/workflows/docker.yml | 8 +- packages/deployment/Makefile | 10 +- packages/xsnap/doc/README.md | 9 + packages/xsnap/doc/XS Metering.md | 144 --- packages/xsnap/doc/XS Snapshots.md | 135 -- packages/xsnap/makefiles/lin/makefile | 9 - packages/xsnap/makefiles/lin/xsnap.mk | 128 -- packages/xsnap/makefiles/mac/makefile | 9 - packages/xsnap/makefiles/mac/xsnap.mk | 137 --- packages/xsnap/makefiles/win/build.bat | 2 - packages/xsnap/makefiles/win/xsnap.mak | 142 --- packages/xsnap/package.json | 2 +- packages/xsnap/src/build.js | 276 ++++- packages/xsnap/src/xsnap.c | 1562 ------------------------ packages/xsnap/src/xsnap.h | 99 -- packages/xsnap/src/xsnap.js | 4 +- 16 files changed, 244 insertions(+), 2432 deletions(-) create mode 100644 packages/xsnap/doc/README.md delete mode 100644 packages/xsnap/doc/XS Metering.md delete mode 100644 packages/xsnap/doc/XS Snapshots.md delete mode 100644 packages/xsnap/makefiles/lin/makefile delete mode 100644 packages/xsnap/makefiles/lin/xsnap.mk delete mode 100644 packages/xsnap/makefiles/mac/makefile delete mode 100644 packages/xsnap/makefiles/mac/xsnap.mk delete mode 100755 packages/xsnap/makefiles/win/build.bat delete mode 100644 packages/xsnap/makefiles/win/xsnap.mak delete mode 100644 packages/xsnap/src/xsnap.c delete mode 100644 packages/xsnap/src/xsnap.h diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index e5d4bf4fb88..b05bd30c3f7 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -14,17 +14,15 @@ jobs: run: echo "GIT_REVISION=$(git rev-parse HEAD)" >> $GITHUB_ENV - name: Save SDK_VERSION run: echo "SDK_VERSION=$(jq -r .version package.json)" >> $GITHUB_ENV - - name: Save MODDABLE_COMMIT_HASH, MODDABLE_URL + - name: Save commit hash, url of submodules to environment run: | - git submodule status packages/xsnap/moddable | \ - sed -ne 's/^.\([^ ]*\).*/MODDABLE_COMMIT_HASH=\1/p' >> $GITHUB_ENV - sed -ne 's!^.*url = \(https://.*moddable\.git\).*!MODDABLE_URL=\1!p' .gitmodules >> $GITHUB_ENV + node packages/xsnap/src/build.js --show-env >> $GITHUB_ENV - name: Build SDK image uses: elgohr/Publish-Docker-Github-Action@master with: name: agoric/agoric-sdk dockerfile: packages/deployment/Dockerfile.sdk - buildargs: MODDABLE_COMMIT_HASH,MODDABLE_URL,GIT_REVISION + buildargs: MODDABLE_COMMIT_HASH,MODDABLE_URL,XSNAP_NATIVE_COMMIT_HASH,XSNAP_NATIVE_URL,GIT_REVISION username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} snapshot: true diff --git a/packages/deployment/Makefile b/packages/deployment/Makefile index a06cd11939f..b274465c907 100644 --- a/packages/deployment/Makefile +++ b/packages/deployment/Makefile @@ -4,9 +4,6 @@ SS := ../cosmic-swingset/ VERSION := $(shell node -e 'console.log(require("../../package.json").version)' 2>/dev/null) TAG := $(if $(VERSION),$(VERSION),latest) -MODDABLE_COMMIT_HASH := $(shell git submodule status ../xsnap/moddable | \ - sed -ne 's/^.\([^ ]*\).*/\1/p') -MODDABLE_URL := $(shell sed -ne 's!^.* = \(https://.*moddable\.git\).*!\1!p' ../../.gitmodules) GIT_REVISION := $(shell hash=$$(git rev-parse --short HEAD); \ dirty=`git diff --quiet || echo -dirty`; \ echo "$$hash$$dirty") @@ -18,6 +15,9 @@ else DONT_PUSH_LATEST := $(if $(findstring -,$(TAG)),:,) endif +submodule.env: ../xsnap/moddable/src/build.js + node ../xsnap/moddable/src/build.js --show-env >$@ + docker-show-fat: date > show-fat-bust-cache.stamp docker build --file=Dockerfile.show-fat ../.. @@ -28,6 +28,8 @@ docker-build: docker-build-sdk docker-build-solo \ docker-build-sdk: docker build --build-arg=MODDABLE_COMMIT_HASH=$(MODDABLE_COMMIT_HASH) \ --build-arg=MODDABLE_URL=$(MODDABLE_URL) \ + --build-arg=XSNAP_NATIVE_COMMIT_HASH=$(XSNAP_NATIVE_COMMIT_HASH) \ + --build-arg=XSNAP_NATIVE_URL=$(XSNAP_NATIVE_URL) \ --build-arg=GIT_REVISION=$(GIT_REVISION) \ -t $(REPOSITORY_SDK):$(TAG) --file=Dockerfile.sdk ../.. docker tag $(REPOSITORY_SDK):$(TAG) $(REPOSITORY_SDK):latest @@ -73,3 +75,5 @@ docker-push-solo: docker-push-deployment: $(DONT_PUSH_LATEST) docker push agoric/deployment:latest docker push agoric/deployment:$(TAG) + +-include submodule.env diff --git a/packages/xsnap/doc/README.md b/packages/xsnap/doc/README.md new file mode 100644 index 00000000000..70ce9a48ec5 --- /dev/null +++ b/packages/xsnap/doc/README.md @@ -0,0 +1,9 @@ +See also [xsnap documentation][1] including: + + - [XS Metering][2] + - [XS Snapshots][3] + + +[1]: https://github.com/agoric-labs/xsnap-pub/blob/endo-submodule/xsnap/documentation/ +[2]: https://github.com/agoric-labs/xsnap-pub/blob/endo-submodule/xsnap/documentation/XS%20Metering.md +[3]: https://github.com/agoric-labs/xsnap-pub/blob/endo-submodule/xsnap/documentation/XS%20Snapshots.md diff --git a/packages/xsnap/doc/XS Metering.md b/packages/xsnap/doc/XS Metering.md deleted file mode 100644 index 28962936f81..00000000000 --- a/packages/xsnap/doc/XS Metering.md +++ /dev/null @@ -1,144 +0,0 @@ -# XS Metering -Revised: November 10, 2020 - -Warning: These notes are preliminary. Omissions and errors are likely. If you encounter problems, please ask for assistance. - -## Introduction - -The objective is to allow runtime to constraint how much computation a machine can do. - -The technique is to count how many byte codes are executed and to ask the runtime if the limit has been reached. - -Asking the runtime at every byte code would be prohibitively slow. So XS only asks the runtime if the limit has been reached: - -- when branching backwards, -- when calling a function, -- when returning from a function, -- when catching an exception, -- when iterating a generator, -- when resuming an async function. - -To be faster, the runtime can also set a metering *interval*, a number of byte codes to wait for before asking again. - -When the runtime tells XS that the limit has been reached, XS aborts with a "*too much computation*" exit. Like for other exits ("*not enough memory*", "*unhandled exception*", etc), the runtime can then decide what to do: - -- throwing an exception, -- exiting the machine, -- exiting the process. - -> Both exiting the machine and exiting the process cannot be caught by the executed JavaScript code. - -## Programming Interface - -To begin metering use the `xsBeginMetering` macro: - - xsBeginMetering(xsMachine* machine, - xsBooleanValue (*ask)(xsMachine* the, xsUnsignedValue index), - xsUnsignedValue interval) - -- `machine`: the machine to meter. -- `ask`: the C function that XS will call to ask if the limit has been reached. -- `interval`: the metering interval. - -The macro uses `setjmp` and must be balanced with the `xsEndMetering` macro: - - xsEndMetering(xsMachine* machine) - -- `machine`: the metered machine. - -The `ask` callback gets the metered machine and the current index. It returns `1` to tell XS to continue, `0` to tell XS to abort. - -## Built-ins - -To fine tune the metering, runtimes can patch built-ins functions. - - xsPatchHostFunction(xsSlot function, xsCallback patch) - -- `function`: the function to patch. -- `patch`: the callback that replaces the original callback of the function. - -Patches must conclude by running their original callback with - - xsMeterHostFunction(xsUnsignedValue count) - -- `count`: the number to add to the metering index. - -### Example - -Here is a patch for `Array.prototype` functions that adds the length of the array to the metering index. - - void fx_Array_prototype_meter(xsMachine* the) - { - xsIntegerValue length = xsToInteger(xsGet(xsThis, xsID("length"))); - xsMeterHostFunction(length); - } - -The same patch can be installed into several `Array.prototype` functions, for instance here `Array.prototype.reverse` and `Array.prototype.sort` - - xsBeginHost(machine); - xsVars(2); - xsVar(0) = xsGet(xsGlobal, xsID("Array")); - xsVar(0) = xsGet(xsVar(0), xsID("prototype")); - xsVar(1) = xsGet(xsVar(0), xsID("reverse")); - xsPatchHostFunction(xsVar(1), fx_Array_prototype_meter); - xsVar(1) = xsGet(xsVar(0), xsID("sort")); - xsPatchHostFunction(xsVar(1), fx_Array_prototype_meter); - xsEndHost(machine); - -## Usage - -Here is the typical runtime sequence: - - static xsBooleanValue ask(xsMachine* machine, xsUnsignedValue index) - { - if (index > 10000) { - fprintf(stderr, "too much computation\n"); - return 0; - } - return 1; - } - - int main(int argc, char* argv[]) - { - //... - xsMachine* machine xsCreateMachine(creation, "main", NULL); - xsBeginMetering(machine, ask, 1000); - { - xsBeginHost(machine); - { - // execute scripts or modules - } - xsEndHost(machine); - } - xsEndMetering(machine); - xsDeleteMachine(machine); - //... - } - -The fxAbort function has to be supplied by all runtimes based on XS. Here the `xsTooMuchComputationExit` case exits the machine. - - void fxAbort(xsMachine* the, int exit) - { - if (exit == xsTooMuchComputationExit) { - fxExitToHost(the); - } - //... - } - -### In JavaScript - -The runtime must provide a C function for the `ask` callback. However the `ask` callback can use the XS in C programming interface to call a JavaScript function. Like a system callback, the `ask` callback has to use `xsBeginHost` and `xsEndHost`. - - static xsBooleanValue ask(xsMachine* machine, xsUnsignedValue index) - { - xsBooleanValue result; - xsBeginHost(machine); - { - result = xsToBoolean(xsCall1(xsGlobal, xsID_ask, xsNumber(index)); - } - xsEndHost(machine); - return result; - } - -The metering is suspended during the `ask` callback. - diff --git a/packages/xsnap/doc/XS Snapshots.md b/packages/xsnap/doc/XS Snapshots.md deleted file mode 100644 index 1e21e71aacb..00000000000 --- a/packages/xsnap/doc/XS Snapshots.md +++ /dev/null @@ -1,135 +0,0 @@ -# XS Snapshots -Revised: September 24, 2020 - -Warning: These notes are preliminary. Omissions and errors are likely. If you encounter problems, please ask for assistance. - -## Format - -XS snapshots are atom based. Inside the `XS_M` container, there are nine atoms: - -- `VERS`: The version of XS and the architecture. -- `SIGN`: The runtime signature. -- `CREA`: The parameters to create the machine: block initial and incremental size, heap initial and incremental size, stack size, etc. -- `BLOC`: The chunks in the blocks. -- `HEAP`: The slots in the heaps. -- `STAC`: The slots in the stack. -- `KEYS`: The keys table. -- `NAME`: The names table. -- `SYMB`: The symbols table. - -XS snapshots are bound to: - -- the major and minor version numbers of XS, -- the architecture: 32-bit vs 64-bit, big endian vs little endian, -- the runtime signature. - -## Programming Interface - -The `xsSnapshot` structure is used to read and write snapshots. - - typedef struct { - char* signature; - int signatureLength; - xsCallback* callbacks; - int callbacksLength; - int (*read)(void* stream, void* ptr, size_t size); - int (*write)(void* stream, void* ptr, size_t size); - void* stream; - int error; - void* data[3]; - } xsSnapshot; - - -- `signature`: bytes to identify the runtime. -- `signatureLength`: the number of bytes in `signature`. -- `callbacks`: array of XS callbacks implemented by the runtime. -- `callbacksLength`: the number of XS callbacks in `callbacks`. -- `read`: the function that `xsReadSnapshot` will call to read the snapshot. -- `write`: the function that `xsWriteSnapshot` will call to write the snapshot. -- `stream`: the parameter passed to the `read` and `write` functions. -- `error`: the error that occurred when reading or writing the snapshot. -- `data`: pointers used internally by XS, must be set to `NULL`. - -The `signature` and `callbacks` fields are related. Runtimes must change the `signature` if the `callbacks` become incompatible. - -If the `stream` is a binary `FILE*`, the `read` and `write` functions are trivial: - - int read(void* stream, void* ptr, size_t size) - { - return (fread(ptr, size, 1, stream) == 1) ? 0 : errno; - } - - int write(void* stream, void* ptr, size_t size) - { - return (fwrite(ptr, size, 1, stream) == 1) ? 0 : errno; - } - -### Writing Snapshot - -Here is the typical runtime sequence: - -- create a new machine, -- run modules or scripts, -- wait for all jobs to complete, -- write the snapshot. - -To write the snapshot, fill the `xsSnapshot` structure and call `xsWriteSnapshot`. - - extern int xsWriteSnapshot(xsMachine* the, xsSnapshot* snapshot); - -- `xsWriteSnapshot` must be called outside of XS callbacks and outside of `xsBeginHost` `xsEndHost` blocks. -- There must be no host instances. - -`xsWriteSnapshot` returns `1` if successful, otherwise `xsWriteSnapshot` returns `0` and sets the `error` field. - -### Reading Snapshot - -Once you have a snapshot, instead of creating a new machine with `xsCreateMachine`, you can create a new machine with `xsReadSnapshot`. The new machine will be in the same state as the machine that was saved by `xsWriteSnapshot`. - -To read the snapshot, fill the `xsSnapshot` structure and call `xsReadSnapshot`. - - xsMachine* xsReadSnapshot(xsSnapshot* snapshot, xsStringValue name, void* context); - -- `snapshot`: The snapshot structure. -- `name`: The name of the machine to be displayed in **xsbug**. -- `context`: The initial context of the machine, or `NULL` - -`xsReadSnapshot` returns a machine if successful, otherwise `xsReadSnapshot` returns `NULL` and sets the `error` field. - -## Implementation Details - -To be able to write a snapshot, everything must be in chunk blocks, slot heaps and slot stack. There cannot be pointers to host data. - -That was mostly the case, except for a few optimizations that create JS strings pointing to C data. Such optimizations are now skipped if `mxSnapshot` is defined. We should investigate if such optimizations are still necessary. - -Interestingly enough snapshots are completely orthogonal to the shared machine prepared by the XS linker to flash micro-controllers. The strategy there is to have as many pointers to host data as possible... - -### Callbacks - -The only pointers that cannot be avoided are XS callbacks, i.e. pointers to host code. - -The major and minor version numbers of XS change when byte codes evolve and when built-ins get new features. New features are always implemented with new callbacks. - -Snapshots are obviously bound to major and minor version numbers of XS. For the sake of snapshots, XS maintains an array of callbacks. It is then enough to project XS callbacks into array indexes. - -Similarly, thanks to a signature and an array of callbacks, runtimes can add new callbacks that will be projected into array indexes. - -### Strictly Deterministic - -If two machines with the same allocations perform the same operations with the same results in the same order, their snapshots will be the same. - -The XS garbage collector is always complete and introduces no variations. - -But asynchronous features can of course alter the order. Then the snapshots will not be the same, even if they are functionally equivalent. - -### Tests - -A lot of tests remain to be done to verify how various built-ins survive the snapshot process. - - - - - - - - diff --git a/packages/xsnap/makefiles/lin/makefile b/packages/xsnap/makefiles/lin/makefile deleted file mode 100644 index 16e2636a29e..00000000000 --- a/packages/xsnap/makefiles/lin/makefile +++ /dev/null @@ -1,9 +0,0 @@ -.NOTPARALLEL: - -all: debug release - -debug: - make -f xsnap.mk - -release: - make GOAL=release -f xsnap.mk diff --git a/packages/xsnap/makefiles/lin/xsnap.mk b/packages/xsnap/makefiles/lin/xsnap.mk deleted file mode 100644 index 5168b853883..00000000000 --- a/packages/xsnap/makefiles/lin/xsnap.mk +++ /dev/null @@ -1,128 +0,0 @@ -% : %.c -%.o : %.c - -GOAL ?= debug -NAME = xsnap -ifneq ($(VERBOSE),1) -MAKEFLAGS += --silent -endif - -MODDABLE = $(CURDIR)/../../moddable -XS_DIR = $(MODDABLE)/xs -BUILD_DIR = $(CURDIR)/../../build - -BIN_DIR = $(BUILD_DIR)/bin/lin/$(GOAL) -INC_DIR = $(XS_DIR)/includes -PLT_DIR = $(XS_DIR)/platforms -SRC_DIR = $(XS_DIR)/sources -TLS_DIR = ../../src -TMP_DIR = $(BUILD_DIR)/tmp/lin/$(GOAL)/$(NAME) - -MACOS_ARCH ?= -arch i386 -MACOS_VERSION_MIN ?= -mmacosx-version-min=10.7 - -C_OPTIONS = \ - -fno-common \ - -DINCLUDE_XSPLATFORM \ - -DXSPLATFORM=\"xsnap.h\" \ - -DXSNAP_VERSION=\"$(XSNAP_VERSION)\" \ - -DmxParse=1 \ - -DmxRun=1 \ - -DmxSloppy=1 \ - -DmxSnapshot=1 \ - -DmxMetering=1 \ - -DmxRegExpUnicodePropertyEscapes=1 \ - -I$(INC_DIR) \ - -I$(PLT_DIR) \ - -I$(SRC_DIR) \ - -I$(TLS_DIR) \ - -I$(TMP_DIR) -C_OPTIONS += \ - -Wno-misleading-indentation \ - -Wno-implicit-fallthrough -ifeq ($(GOAL),debug) - C_OPTIONS += -DmxDebug=1 -g -O0 -Wall -Wextra -Wno-missing-field-initializers -Wno-unused-parameter -else - C_OPTIONS += -DmxBoundsCheck=1 -O3 -endif - -LIBRARIES = -ldl -lm -lpthread - -LINK_OPTIONS = -rdynamic - -OBJECTS = \ - $(TMP_DIR)/xsAll.o \ - $(TMP_DIR)/xsAPI.o \ - $(TMP_DIR)/xsArguments.o \ - $(TMP_DIR)/xsArray.o \ - $(TMP_DIR)/xsAtomics.o \ - $(TMP_DIR)/xsBigInt.o \ - $(TMP_DIR)/xsBoolean.o \ - $(TMP_DIR)/xsCode.o \ - $(TMP_DIR)/xsCommon.o \ - $(TMP_DIR)/xsDataView.o \ - $(TMP_DIR)/xsDate.o \ - $(TMP_DIR)/xsDebug.o \ - $(TMP_DIR)/xsDefaults.o \ - $(TMP_DIR)/xsError.o \ - $(TMP_DIR)/xsFunction.o \ - $(TMP_DIR)/xsGenerator.o \ - $(TMP_DIR)/xsGlobal.o \ - $(TMP_DIR)/xsJSON.o \ - $(TMP_DIR)/xsLexical.o \ - $(TMP_DIR)/xsMapSet.o \ - $(TMP_DIR)/xsMarshall.o \ - $(TMP_DIR)/xsMath.o \ - $(TMP_DIR)/xsMemory.o \ - $(TMP_DIR)/xsModule.o \ - $(TMP_DIR)/xsNumber.o \ - $(TMP_DIR)/xsObject.o \ - $(TMP_DIR)/xsPlatforms.o \ - $(TMP_DIR)/xsProfile.o \ - $(TMP_DIR)/xsPromise.o \ - $(TMP_DIR)/xsProperty.o \ - $(TMP_DIR)/xsProxy.o \ - $(TMP_DIR)/xsRegExp.o \ - $(TMP_DIR)/xsRun.o \ - $(TMP_DIR)/xsScope.o \ - $(TMP_DIR)/xsScript.o \ - $(TMP_DIR)/xsSnapshot.o \ - $(TMP_DIR)/xsSourceMap.o \ - $(TMP_DIR)/xsString.o \ - $(TMP_DIR)/xsSymbol.o \ - $(TMP_DIR)/xsSyntaxical.o \ - $(TMP_DIR)/xsTree.o \ - $(TMP_DIR)/xsType.o \ - $(TMP_DIR)/xsdtoa.o \ - $(TMP_DIR)/xsre.o \ - $(TMP_DIR)/$(NAME).o - -VPATH += $(SRC_DIR) $(TLS_DIR) - -build: $(TMP_DIR) $(BIN_DIR) $(BIN_DIR)/$(NAME) - -$(TMP_DIR): - mkdir -p $(TMP_DIR) - -$(BIN_DIR): - mkdir -p $(BIN_DIR) - -$(BIN_DIR)/$(NAME): $(OBJECTS) - @echo "#" $(NAME) $(GOAL) ": cc" $(@F) - $(CC) $(LINK_OPTIONS) $(OBJECTS) $(LIBRARIES) -o $@ - -$(OBJECTS): $(TLS_DIR)/xsnap.h -$(OBJECTS): $(PLT_DIR)/xsPlatform.h -$(OBJECTS): $(SRC_DIR)/xsCommon.h -$(OBJECTS): $(SRC_DIR)/xsAll.h -$(OBJECTS): $(SRC_DIR)/xsScript.h -$(OBJECTS): $(SRC_DIR)/xsSnapshot.h -$(TMP_DIR)/%.o: %.c - @echo "#" $(NAME) $(GOAL) ": cc" $( { - child.on('close', () => { - resolve(); - }); - child.on('error', err => { - reject(new Error(`${command} error ${err}`)); - }); - child.on('exit', code => { - if (code !== 0) { - reject(new Error(`${command} exited with code ${code}`)); - } +// @ts-check +import * as childProcessTop from 'child_process'; +import fsTop from 'fs'; +import osTop from 'os'; + +const { freeze } = Object; + +/** @param { string } path */ +const asset = path => new URL(path, import.meta.url).pathname; + +const ModdableSDK = { + MODDABLE: asset('../moddable'), + /** @type { Record} */ + platforms: { + Linux: { path: 'lin' }, + Darwin: { path: 'mac' }, + Windows_NT: { path: 'win', make: 'nmake' }, + }, + buildGoals: ['release', 'debug'], +}; + +/** + * Adapt spawn to Promises style. + * + * @param {string} command + * @param {{ + * spawn: typeof import('child_process').spawn, + * }} io + */ +function makeCLI(command, { spawn }) { + /** @param { import('child_process').ChildProcess } child */ + const wait = child => + new Promise((resolve, reject) => { + child.on('close', () => { + resolve(undefined); + }); + child.on('error', err => { + reject(new Error(`${command} error ${err}`)); + }); + child.on('exit', code => { + if (code !== 0) { + reject(new Error(`${command} exited with code ${code}`)); + } + }); }); + + return freeze({ + /** + * @param {string[]} args + * @param {{ cwd?: string }=} opts + */ + run: (args, opts) => { + const { cwd = '.' } = opts || {}; + const child = spawn(command, args, { + cwd, + stdio: ['inherit', 'inherit', 'inherit'], + }); + return wait(child); + }, + /** + * @param {string[]} args + * @param {{ cwd?: string }=} opts + */ + pipe: (args, opts) => { + const { cwd = '.' } = opts || {}; + const child = spawn(command, args, { + cwd, + stdio: ['inherit', 'pipe', 'inherit'], + }); + let output = ''; + child.stdout.setEncoding('utf8'); + child.stdout.on('data', data => { + output += data.toString(); + }); + return wait(child).then(() => output); + }, }); } -(async () => { - // Allow overriding of the checked-out version of the Moddable submodule. - const moddableCommitHash = process.env.MODDABLE_COMMIT_HASH; - const moddableUrl = - process.env.MODDABLE_URL || 'https://github.com/agoric-labs/moddable.git'; - - if (moddableCommitHash) { - // Do the moral equivalent of submodule update when explicitly overriding. - if (!existsSync('moddable')) { - await exec('git', '.', [ - 'clone', - // NOTE: We need to depend on cloning from agoric-labs. - moddableUrl, - 'moddable', +/** + * @param {string} path + * @param {string} repoUrl + * @param {{ git: ReturnType }} io + */ +const makeSubmodule = (path, repoUrl, { git }) => { + /** @param { string } text */ + const parseStatus = text => + text + .split('\n') + .map(line => line.split(' ', 4)) + .map(([_indent, hash, statusPath, describe]) => ({ + hash, + path: statusPath, + describe, + })); + + return freeze({ + path, + clone: async () => git.run(['clone', repoUrl, path]), + /** @param { string } commitHash */ + checkout: async commitHash => + git.run(['checkout', commitHash], { cwd: path }), + init: async () => git.run(['submodule', 'update', '--init', '--checkout']), + status: async () => + git.pipe(['submodule', 'status', path]).then(parseStatus), + /** @param { string } leaf */ + config: async leaf => { + // git rev-parse --show-toplevel + const top = await git + .pipe(['rev-parse', '--show-toplevel']) + .then(l => l.trimEnd()); + // assume full paths + const name = path.slice(top.length + 1); + // git config -f ../../.gitmodules --get submodule."$name".url + const value = await git + .pipe([ + 'config', + '-f', + `${top}/.gitmodules`, + '--get', + `submodule.${name}.${leaf}`, + ]) + .then(l => l.trimEnd()); + return value; + }, + }); +}; + +/** + * @param { string[] } args + * @param {{ + * env: Record, + * stdout: typeof process.stdout, + * spawn: typeof import('child_process').spawn, + * fs: { + * existsSync: typeof import('fs').existsSync, + * readFile: typeof import('fs').promises.readFile, + * }, + * os: { + * type: typeof import('os').type, + * } + * }} io + */ +async function main(args, { env, stdout, spawn, fs, os }) { + const git = makeCLI('git', { spawn }); + + const submodules = [ + { + url: env.MODDABLE_URL || 'https://github.com/agoric-labs/moddable.git', + path: ModdableSDK.MODDABLE, + commitHash: env.MODDABLE_COMMIT_HASH, + envPrefix: 'MODDABLE_', + }, + { + url: + env.XSNAP_NATIVE_URL || 'https://github.com/agoric-labs/xsnap-pub.git', + path: asset('../xsnap-native'), + commitHash: env.XSNAP_NATIVE_COMMIT_HASH, + envPrefix: 'XSNAP_NATIVE_', + }, + ]; + + if (args.includes('--show-env')) { + for (const { path, envPrefix } of submodules) { + const submodule = makeSubmodule(path, '?', { git }); + // eslint-disable-next-line no-await-in-loop + const [[{ hash }], url] = await Promise.all([ + submodule.status(), + submodule.config('url'), ]); + stdout.write(`${envPrefix}URL=${url}\n`); + stdout.write(`${envPrefix}COMMIT_HASH=${hash}\n`); + } + return; + } + + for (const { url, path, commitHash } of submodules) { + const submodule = makeSubmodule(path, url, { git }); + + // Allow overriding of the checked-out version of the submodule. + if (commitHash) { + // Do the moral equivalent of submodule update when explicitly overriding. + if (!fs.existsSync(submodule.path)) { + // eslint-disable-next-line no-await-in-loop + await submodule.clone(); + } + submodule.checkout(commitHash); + } else { + // eslint-disable-next-line no-await-in-loop + await submodule.init(); } - await exec('git', 'moddable', ['checkout', moddableCommitHash]); - } else { - await exec('git', '.', ['submodule', 'update', '--init', '--checkout']); } - const pjson = readFileSync( - new URL('../package.json', import.meta.url).pathname, - 'utf-8', - ); + const pjson = await fs.readFile(asset('../package.json'), 'utf-8'); const pkg = JSON.parse(pjson); - const XSNAP_VERSION = `XSNAP_VERSION=${pkg.version}`; - - // Run command depending on the OS - if (os.type() === 'Linux') { - await exec('make', 'makefiles/lin', [XSNAP_VERSION]); - await exec('make', 'makefiles/lin', ['GOAL=debug', XSNAP_VERSION]); - } else if (os.type() === 'Darwin') { - await exec('make', 'makefiles/mac', [XSNAP_VERSION]); - await exec('make', 'makefiles/mac', ['GOAL=debug', XSNAP_VERSION]); - } else if (os.type() === 'Windows_NT') { - await exec('nmake', 'makefiles/win', [XSNAP_VERSION]); - await exec('make', 'makefiles/win', ['GOAL=debug', XSNAP_VERSION]); - } else { + + const platform = ModdableSDK.platforms[os.type()]; + if (!platform) { throw new Error(`Unsupported OS found: ${os.type()}`); } -})().catch(e => { + + const make = makeCLI(platform.make || 'make', { spawn }); + for (const goal of ModdableSDK.buildGoals) { + // eslint-disable-next-line no-await-in-loop + await make.run( + [ + `MODDABLE=${ModdableSDK.MODDABLE}`, + `GOAL=${goal}`, + `XSNAP_VERSION=${pkg.version}`, + '-f', + 'xsnap-worker.mk', + ], + { + cwd: `xsnap-native/xsnap/makefiles/${platform.path}`, + }, + ); + } +} + +main(process.argv.slice(2), { + env: { ...process.env }, + stdout: process.stdout, + spawn: childProcessTop.spawn, + fs: { + readFile: fsTop.promises.readFile, + existsSync: fsTop.existsSync, + }, + os: { + type: osTop.type, + }, +}).catch(e => { console.error(e); process.exit(1); }); diff --git a/packages/xsnap/src/xsnap.c b/packages/xsnap/src/xsnap.c deleted file mode 100644 index d4be87effe5..00000000000 --- a/packages/xsnap/src/xsnap.c +++ /dev/null @@ -1,1562 +0,0 @@ -#include "xsAll.h" -#include "xsScript.h" -#include "xsSnapshot.h" -#include "xs.h" - -#define SNAPSHOT_SIGNATURE "xsnap 1" -#ifndef XSNAP_VERSION -# error "You must define XSNAP_VERSION in the right Makefile" -#endif - -extern txScript* fxLoadScript(txMachine* the, txString path, txUnsigned flags); - -typedef struct sxAliasIDLink txAliasIDLink; -typedef struct sxAliasIDList txAliasIDList; -typedef struct sxJob txJob; -typedef void (*txJobCallback)(txJob*); - -struct sxAliasIDLink { - txAliasIDLink* previous; - txAliasIDLink* next; - txInteger id; - txInteger flag; -}; - -struct sxAliasIDList { - txAliasIDLink* first; - txAliasIDLink* last; - txFlag* aliases; - txInteger errorCount; -}; - -struct sxJob { - txJob* next; - txMachine* the; - txNumber when; - txJobCallback callback; - txSlot self; - txSlot function; - txSlot argument; - txNumber interval; -}; - -static void fxBuildAgent(xsMachine* the); -static txInteger fxCheckAliases(txMachine* the); -static void fxCheckAliasesError(txMachine* the, txAliasIDList* list, txFlag flag); -static void fxCheckEnvironmentAliases(txMachine* the, txSlot* environment, txAliasIDList* list); -static void fxCheckInstanceAliases(txMachine* the, txSlot* instance, txAliasIDList* list); -static void fxFreezeBuiltIns(txMachine* the); -static void fxPrintUsage(); - -static void fx_issueCommand(xsMachine *the); - -extern void fx_clearTimer(txMachine* the); -static void fx_destroyTimer(void* data); -// static void fx_evalScript(xsMachine* the); -static void fx_gc(xsMachine* the); -// static void fx_isPromiseJobQueueEmpty(xsMachine* the); -static void fx_markTimer(txMachine* the, void* it, txMarkRoot markRoot); -static void fx_print(xsMachine* the); -static void fx_performance_now(xsMachine* the); -static void fx_setImmediate(txMachine* the); -// static void fx_setInterval(txMachine* the); -// static void fx_setTimeout(txMachine* the); -static void fx_setTimer(txMachine* the, txNumber interval, txBoolean repeat); -static void fx_setTimerCallback(txJob* job); -static void fx_currentMeterLimit(xsMachine* the); -static void fx_resetMeter(xsMachine* the); - -static void fxFulfillModuleFile(txMachine* the); -static void fxRejectModuleFile(txMachine* the); -static void fxRunModuleFile(txMachine* the, txString path); -static void fxRunProgramFile(txMachine* the, txString path, txUnsigned flags); -static void fxRunLoop(txMachine* the); - -static int fxReadNetString(FILE *inStream, char** dest, size_t* len); -static char* fxReadNetStringError(int code); -static int fxWriteOkay(FILE* outStream, xsUnsignedValue meterIndex, txMachine *the, char* buf, size_t len); -static int fxWriteNetString(FILE* outStream, char* prefix, char* buf, size_t len); -static char* fxWriteNetStringError(int code); - -// 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 7 -txCallback gxSnapshotCallbacks[mxSnapshotCallbackCount] = { - fx_issueCommand, // 0 - fx_print, // 1 - fx_setImmediate, // 2 - fx_gc, // 3 - fx_performance_now, // 4 - fx_currentMeterLimit, // 5 - fx_resetMeter, // 6 - // fx_evalScript, - // fx_isPromiseJobQueueEmpty, - // fx_setInterval, - // fx_setTimeout, - // fx_clearTimer, -}; - -enum { - XSL_MODULE_FLAG, - XSL_EXPORT_FLAG, - XSL_ENVIRONMENT_FLAG, - XSL_PROPERTY_FLAG, - XSL_ITEM_FLAG, - XSL_GETTER_FLAG, - XSL_SETTER_FLAG, - XSL_PROXY_HANDLER_FLAG, - XSL_PROXY_TARGET_FLAG, - XSL_GLOBAL_FLAG, -}; - -#define mxPushLink(name,ID,FLAG) \ - txAliasIDLink name = { C_NULL, C_NULL, ID, FLAG }; \ - name.previous = list->last; \ - if (list->last) \ - list->last->next = &name; \ - else \ - list->first = &name; \ - list->last = &name - -#define mxPopLink(name) \ - if (name.previous) \ - name.previous->next = C_NULL; \ - else \ - list->first = C_NULL; \ - list->last = name.previous - -static int fxSnapshotRead(void* stream, void* address, size_t size) -{ - return (fread(address, size, 1, stream) == 1) ? 0 : errno; -} - -static int fxSnapshotWrite(void* stream, void* address, size_t size) -{ - return (fwrite(address, size, 1, stream) == 1) ? 0 : errno; -} - -#if mxMetering -#define xsBeginMetering(_THE, _CALLBACK, _STEP) \ - do { \ - xsJump __HOST_JUMP__; \ - __HOST_JUMP__.nextJump = (_THE)->firstJump; \ - __HOST_JUMP__.stack = (_THE)->stack; \ - __HOST_JUMP__.scope = (_THE)->scope; \ - __HOST_JUMP__.frame = (_THE)->frame; \ - __HOST_JUMP__.environment = NULL; \ - __HOST_JUMP__.code = (_THE)->code; \ - __HOST_JUMP__.flag = 0; \ - (_THE)->firstJump = &__HOST_JUMP__; \ - if (setjmp(__HOST_JUMP__.buffer) == 0) { \ - fxBeginMetering(_THE, _CALLBACK, _STEP) - -#define xsEndMetering(_THE) \ - fxEndMetering(_THE); \ - } \ - (_THE)->stack = __HOST_JUMP__.stack, \ - (_THE)->scope = __HOST_JUMP__.scope, \ - (_THE)->frame = __HOST_JUMP__.frame, \ - (_THE)->code = __HOST_JUMP__.code, \ - (_THE)->firstJump = __HOST_JUMP__.nextJump; \ - break; \ - } while(1) - -#define xsPatchHostFunction(_FUNCTION,_PATCH) \ - (xsOverflow(-1), \ - fxPush(_FUNCTION), \ - fxPatchHostFunction(the, _PATCH), \ - fxPop()) -#define xsMeterHostFunction(_COUNT) \ - fxMeterHostFunction(the, _COUNT) -#define xsBeginCrank(_THE, _LIMIT) \ - ((_THE)->meterIndex = 0, \ - gxCurrentMeter = _LIMIT) -#define xsEndCrank(_THE) \ - (gxCurrentMeter = 0, \ - (_THE)->meterIndex) -#else - #define xsBeginMetering(_THE, _CALLBACK, _STEP) - #define xsEndMetering(_THE) - #define xsPatchHostFunction(_FUNCTION,_PATCH) - #define xsMeterHostFunction(_COUNT) (void)(_COUNT) - #define xsBeginCrank(_THE, _LIMIT) - #define xsEndCrank(_THE) 0 -#endif - -static xsUnsignedValue gxCrankMeteringLimit = 0; -static xsUnsignedValue gxCurrentMeter = 0; -xsBooleanValue fxMeteringCallback(xsMachine* the, xsUnsignedValue index) -{ - if (gxCurrentMeter > 0 && index > gxCurrentMeter) { - // Just throw right out of the main loop and exit. - return 0; - } - // fprintf(stderr, "metering up to %d\n", index); - return 1; -} -static xsBooleanValue gxMeteringPrint = 0; - -static FILE *fromParent; -static FILE *toParent; - -typedef enum { - E_UNKNOWN_ERROR = -1, - E_SUCCESS = 0, - E_BAD_USAGE, - E_IO_ERROR, - // 10 + XS_NOT_ENOUGH_MEMORY_EXIT - E_NOT_ENOUGH_MEMORY = 11, - E_STACK_OVERFLOW = 12, - E_UNHANDLED_EXCEPTION = 15, - E_NO_MORE_KEYS = 16, - E_TOO_MUCH_COMPUTATION = 17, -} ExitCode; - -int main(int argc, char* argv[]) -{ - int argi; - int argr = 0; - int error = 0; - int interval = 0; - int freeze = 0; - int parserBufferSize = 8192 * 1024; - - txSnapshot snapshot = { - SNAPSHOT_SIGNATURE, - sizeof(SNAPSHOT_SIGNATURE) - 1, - gxSnapshotCallbacks, - mxSnapshotCallbackCount, - fxSnapshotRead, - fxSnapshotWrite, - NULL, - 0, - NULL, - NULL, - NULL, - }; - - xsMachine* machine; - char *path; - char* dot; - - for (argi = 1; argi < argc; argi++) { - if (argv[argi][0] != '-') - continue; - if (!strcmp(argv[argi], "-f")) { - freeze = 1; - } - else if (!strcmp(argv[argi], "-h")) { - fxPrintUsage(); - return 0; - } else if (!strcmp(argv[argi], "-i")) { - argi++; - if (argi < argc) - interval = atoi(argv[argi]); - else { - fxPrintUsage(); - return E_BAD_USAGE; - } - } - else if (!strcmp(argv[argi], "-l")) { -#if mxMetering - argi++; - if (argi < argc) - gxCrankMeteringLimit = atoi(argv[argi]); - else { - fxPrintUsage(); - return E_BAD_USAGE; - } -#else - fprintf(stderr, "%s flag not implemented; mxMetering is not enabled\n", argv[argi]); - return E_BAD_USAGE; -#endif - } - else if (!strcmp(argv[argi], "-p")) - gxMeteringPrint = 1; - else if (!strcmp(argv[argi], "-r")) { - argi++; - if (argi < argc) - argr = argi; - else { - fxPrintUsage(); - return E_BAD_USAGE; - } - } - else if (!strcmp(argv[argi], "-s")) { - argi++; - if (argi < argc) - parserBufferSize = 1024 * atoi(argv[argi]); - else { - fxPrintUsage(); - return E_BAD_USAGE; - } - } - else if (!strcmp(argv[argi], "-v")) { - printf("xsnap %s (XS %d.%d.%d)\n", XSNAP_VERSION, XS_MAJOR_VERSION, XS_MINOR_VERSION, XS_PATCH_VERSION); - return E_SUCCESS; - } else { - fxPrintUsage(); - return E_BAD_USAGE; - } - } - xsCreation _creation = { - 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; - - if (gxCrankMeteringLimit) { - if (interval == 0) - interval = 1; - } - fxInitializeSharedCluster(); - if (argr) { - snapshot.stream = fopen(argv[argr], "rb"); - if (snapshot.stream) { - machine = fxReadSnapshot(&snapshot, "xsnap", NULL); - fclose(snapshot.stream); - } - else - snapshot.error = errno; - if (snapshot.error) { - fprintf(stderr, "cannot read snapshot %s: %s\n", argv[argr], strerror(snapshot.error)); - return E_IO_ERROR; - } - } - else { - machine = xsCreateMachine(creation, "xsnap", NULL); - fxBuildAgent(machine); - } - if (freeze) { - fxFreezeBuiltIns(machine); - fxShareMachine(machine); - fxCheckAliases(machine); - machine = xsCloneMachine(creation, machine, "xsnap", NULL); - } - if (!(fromParent = fdopen(3, "rb"))) { - fprintf(stderr, "fdopen(3) from parent failed\n"); - c_exit(E_IO_ERROR); - } - if (!(toParent = fdopen(4, "wb"))) { - fprintf(stderr, "fdopen(4) to parent failed\n"); - c_exit(E_IO_ERROR); - } - xsBeginMetering(machine, fxMeteringCallback, interval); - { - char done = 0; - while (!done) { - // By default, use the infinite meter. - gxCurrentMeter = 0; - - xsUnsignedValue meterIndex = 0; - char* nsbuf; - size_t nslen; - int readError = fxReadNetString(fromParent, &nsbuf, &nslen); - int writeError = 0; - - if (readError != 0) { - if (feof(fromParent)) { - break; - } else { - fprintf(stderr, "%s\n", fxReadNetStringError(readError)); - c_exit(E_IO_ERROR); - } - } - char command = *nsbuf; - // fprintf(stderr, "command: len %d %c arg: %s\n", nslen, command, nsbuf + 1); - switch(command) { - case 'R': // isReady - fxWriteNetString(toParent, ".", "", 0); - break; - case '?': - case 'e': - xsBeginCrank(machine, gxCrankMeteringLimit); - error = 0; - xsBeginHost(machine); - { - xsVars(3); - xsTry { - if (command == '?') { - xsVar(0) = xsArrayBuffer(nsbuf + 1, nslen - 1); - xsVar(1) = xsCall1(xsGlobal, xsID("handleCommand"), xsVar(0)); - } else { - xsVar(0) = xsStringBuffer(nsbuf + 1, nslen - 1); - xsVar(1) = xsCall1(xsGlobal, xsID("eval"), xsVar(0)); - } - } - xsCatch { - if (xsTypeOf(xsException) != xsUndefinedType) { - // fprintf(stderr, "%c: %s\n", command, xsToString(xsException)); - error = E_UNHANDLED_EXCEPTION; - xsVar(1) = xsException; - xsException = xsUndefined; - } - } - } - fxRunLoop(machine); - 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); - } else { - char* response = NULL; - txInteger responseLength = 0; - // fprintf(stderr, "report: %d %s\n", xsTypeOf(report), xsToString(report)); - xsTry { - if (xsTypeOf(xsVar(1)) == xsReferenceType && xsHas(xsVar(1), xsID("result"))) { - xsVar(2) = xsGet(xsVar(1), xsID("result")); - } else { - xsVar(2) = xsVar(1); - } - // fprintf(stderr, "result: %d %s\n", xsTypeOf(result), xsToString(result)); - if (xsIsInstanceOf(xsVar(2), xsArrayBufferPrototype)) { - responseLength = xsGetArrayBufferLength(xsVar(2)); - response = xsToArrayBuffer(xsVar(2)); - } - } - xsCatch { - if (xsTypeOf(xsException) != xsUndefinedType) { - fprintf(stderr, "%c computing response %d %d: %s: %s\n", command, - xsTypeOf(xsVar(1)), xsTypeOf(xsVar(2)), - xsToString(xsVar(2)), - xsToString(xsException)); - xsException = xsUndefined; - } - } - // fprintf(stderr, "response of %d bytes\n", responseLength); - writeError = fxWriteOkay(toParent, meterIndex, the, response, responseLength); - } - } - xsEndHost(machine); - if (writeError != 0) { - fprintf(stderr, "%s\n", fxWriteNetStringError(writeError)); - c_exit(E_IO_ERROR); - } - break; - case 's': - case 'm': - xsBeginCrank(machine, gxCrankMeteringLimit); - path = nsbuf + 1; - xsBeginHost(machine); - { - xsVars(1); - xsTry { - // ISSUE: realpath necessary? realpath(x, x) doesn't seem to work. - dot = strrchr(path, '.'); - if (command == 'm') - fxRunModuleFile(the, path); - else - fxRunProgramFile(the, path, mxProgramFlag | mxDebugFlag); - } - xsCatch { - if (xsTypeOf(xsException) != xsUndefinedType) { - fprintf(stderr, "%s\n", xsToString(xsException)); - error = E_UNHANDLED_EXCEPTION; - xsException = xsUndefined; - } - } - } - xsEndHost(machine); - fxRunLoop(machine); - meterIndex = xsEndCrank(machine); - if (error == 0) { - int writeError = fxWriteOkay(toParent, meterIndex, machine, "", 0); - if (writeError != 0) { - fprintf(stderr, "%s\n", fxWriteNetStringError(writeError)); - c_exit(E_IO_ERROR); - } - } else { - // TODO: dynamically build error message including Exception message. - int writeError = fxWriteNetString(toParent, "!", "", 0); - if (writeError != 0) { - fprintf(stderr, "%s\n", fxWriteNetStringError(writeError)); - c_exit(E_IO_ERROR); - } - } - break; - - case 'w': - path = nsbuf + 1; - snapshot.stream = fopen(path, "wb"); - if (snapshot.stream) { - fxWriteSnapshot(machine, &snapshot); - fclose(snapshot.stream); - } - else - snapshot.error = errno; - if (snapshot.error) { - fprintf(stderr, "cannot write snapshot %s: %s\n", - path, strerror(snapshot.error)); - c_exit(E_IO_ERROR); - } - if (snapshot.error == 0) { - int writeError = fxWriteOkay(toParent, meterIndex, machine, "", 0); - if (writeError != 0) { - fprintf(stderr, "%s\n", fxWriteNetStringError(writeError)); - c_exit(E_IO_ERROR); - } - } else { - // TODO: dynamically build error message including Exception message. - int writeError = fxWriteNetString(toParent, "!", "", 0); - if (writeError != 0) { - fprintf(stderr, "%s\n", fxWriteNetStringError(writeError)); - c_exit(E_IO_ERROR); - } - } - break; - case -1: - default: - done = 1; - break; - } - free(nsbuf); - } - xsBeginHost(machine); - { - if (xsTypeOf(xsException) != xsUndefinedType) { - fprintf(stderr, "%s\n", xsToString(xsException)); - error = E_UNHANDLED_EXCEPTION; - } - } - xsEndHost(machine); - } - xsEndMetering(machine); - xsDeleteMachine(machine); - fxTerminateSharedCluster(); - if (error != E_SUCCESS) { - c_exit(error); - } - return E_SUCCESS; -} - -void fxBuildAgent(xsMachine* the) -{ - txSlot* slot; - mxPush(mxGlobal); - slot = fxLastProperty(the, fxToInstance(the, the->stack)); - slot = fxNextHostFunctionProperty(the, slot, fx_issueCommand, 1, xsID("issueCommand"), XS_DONT_ENUM_FLAG); - // slot = fxNextHostFunctionProperty(the, slot, fx_clearTimer, 1, xsID("clearImmediate"), XS_DONT_ENUM_FLAG); - // slot = fxNextHostFunctionProperty(the, slot, fx_clearTimer, 1, xsID("clearInterval"), XS_DONT_ENUM_FLAG); - // slot = fxNextHostFunctionProperty(the, slot, fx_clearTimer, 1, xsID("clearTimeout"), XS_DONT_ENUM_FLAG); - // slot = fxNextHostFunctionProperty(the, slot, fx_evalScript, 1, xsID("evalScript"), XS_DONT_ENUM_FLAG); - slot = fxNextHostFunctionProperty(the, slot, fx_gc, 1, xsID("gc"), XS_DONT_ENUM_FLAG); - // slot = fxNextHostFunctionProperty(the, slot, fx_isPromiseJobQueueEmpty, 1, xsID("isPromiseJobQueueEmpty"), XS_DONT_ENUM_FLAG); - slot = fxNextHostFunctionProperty(the, slot, fx_print, 1, xsID("print"), XS_DONT_ENUM_FLAG); - slot = fxNextHostFunctionProperty(the, slot, fx_setImmediate, 1, xsID("setImmediate"), XS_DONT_ENUM_FLAG); - // slot = fxNextHostFunctionProperty(the, slot, fx_setInterval, 1, xsID("setInterval"), XS_DONT_ENUM_FLAG); - // slot = fxNextHostFunctionProperty(the, slot, fx_setTimeout, 1, xsID("setTimeout"), XS_DONT_ENUM_FLAG); - - mxPush(mxObjectPrototype); - txSlot* performance = fxLastProperty(the, fxNewObjectInstance(the)); - fxNextHostFunctionProperty(the, performance, fx_performance_now, 1, xsID("now"), XS_DONT_ENUM_FLAG); - slot = fxNextSlotProperty(the, slot, the->stack, xsID("performance"), XS_DONT_ENUM_FLAG); - mxPop(); - - slot = fxNextHostFunctionProperty(the, slot, fx_currentMeterLimit, 1, xsID("currentMeterLimit"), XS_DONT_ENUM_FLAG); - slot = fxNextHostFunctionProperty(the, slot, fx_resetMeter, 1, xsID("resetMeter"), XS_DONT_ENUM_FLAG); - // mxPush(mxObjectPrototype); - // fxNextHostFunctionProperty(the, fxLastProperty(the, fxNewObjectInstance(the)), fx_print, 1, xsID("log"), XS_DONT_ENUM_FLAG); - // slot = fxNextSlotProperty(the, slot, the->stack, xsID("console"), XS_DONT_ENUM_FLAG); - // mxPop(); - - mxPop(); -} - -txInteger fxCheckAliases(txMachine* the) -{ - txAliasIDList _list = { C_NULL, C_NULL }, *list = &_list; - txSlot* module = mxProgram.value.reference->next; //@@ - list->aliases = c_calloc(the->aliasCount, sizeof(txFlag)); - while (module) { - txSlot* export = mxModuleExports(module)->value.reference->next; - if (export) { - mxPushLink(moduleLink, module->ID, XSL_MODULE_FLAG); - while (export) { - txSlot* closure = export->value.export.closure; - if (closure) { - mxPushLink(exportLink, export->ID, XSL_EXPORT_FLAG); - if (closure->ID != XS_NO_ID) { - if (list->aliases[closure->ID] == 0) { - list->aliases[closure->ID] = 1; - fxCheckAliasesError(the, list, 0); - } - } - if (closure->kind == XS_REFERENCE_KIND) { - fxCheckInstanceAliases(the, closure->value.reference, list); - } - mxPopLink(exportLink); - } - export = export->next; - } - mxPopLink(moduleLink); - } - module = module->next; - } - { - txSlot* global = mxGlobal.value.reference->next->next; - while (global) { - if ((global->ID != mxID(_global)) && (global->ID != mxID(_globalThis))) { - mxPushLink(globalLink, global->ID, XSL_GLOBAL_FLAG); - if (global->kind == XS_REFERENCE_KIND) { - fxCheckInstanceAliases(the, global->value.reference, list); - } - mxPopLink(globalLink); - } - global = global->next; - } - } - { - fxCheckEnvironmentAliases(the, mxException.value.reference, list); - } - return list->errorCount; -} - -void fxCheckAliasesError(txMachine* the, txAliasIDList* list, txFlag flag) -{ - txAliasIDLink* link = list->first; - if (flag > 1) - fprintf(stderr, "### error"); - else - fprintf(stderr, "### warning"); - while (link) { - switch (link->flag) { - case XSL_PROPERTY_FLAG: fprintf(stderr, "."); break; - case XSL_ITEM_FLAG: fprintf(stderr, "["); break; - case XSL_GETTER_FLAG: fprintf(stderr, ".get "); break; - case XSL_SETTER_FLAG: fprintf(stderr, ".set "); break; - case XSL_ENVIRONMENT_FLAG: fprintf(stderr, "() "); break; - case XSL_PROXY_HANDLER_FLAG: fprintf(stderr, ".(handler)"); break; - case XSL_PROXY_TARGET_FLAG: fprintf(stderr, ".(target)"); break; - default: fprintf(stderr, ": "); break; - } - if (link->id < 0) { - if (link->id != XS_NO_ID) { - char* string = fxGetKeyName(the, link->id); - if (string) { - if (link->flag == XSL_MODULE_FLAG) { - char* dot = c_strrchr(string, '.'); - if (dot) { - *dot = 0; - fprintf(stderr, "\"%s\"", string); - *dot = '.'; - } - else - fprintf(stderr, "%s", string); - } - else if (link->flag == XSL_GLOBAL_FLAG) { - fprintf(stderr, "globalThis."); - fprintf(stderr, "%s", string); - } - else - fprintf(stderr, "%s", string); - } - else - fprintf(stderr, "%d", link->id); - } - } - else - fprintf(stderr, "%d", link->id); - if (link->flag == XSL_ITEM_FLAG) - fprintf(stderr, "]"); - link = link->next; - } - if (flag == 3) { - fprintf(stderr, ": generator\n"); - list->errorCount++; - } - else if (flag == 2) { - fprintf(stderr, ": regexp\n"); - list->errorCount++; - } - else if (flag) - fprintf(stderr, ": not frozen\n"); - else - fprintf(stderr, ": no const\n"); -} - -void fxCheckEnvironmentAliases(txMachine* the, txSlot* environment, txAliasIDList* list) -{ - txSlot* closure = environment->next; - if (environment->flag & XS_LEVEL_FLAG) - return; - environment->flag |= XS_LEVEL_FLAG; - if (environment->value.instance.prototype) - fxCheckEnvironmentAliases(the, environment->value.instance.prototype, list); - while (closure) { - if (closure->kind == XS_CLOSURE_KIND) { - txSlot* slot = closure->value.closure; - mxPushLink(closureLink, closure->ID, XSL_ENVIRONMENT_FLAG); - if (slot->ID != XS_NO_ID) { - if (list->aliases[slot->ID] == 0) { - list->aliases[slot->ID] = 1; - fxCheckAliasesError(the, list, 0); - } - } - if (slot->kind == XS_REFERENCE_KIND) { - fxCheckInstanceAliases(the, slot->value.reference, list); - } - mxPopLink(closureLink); - } - closure = closure->next; - } - //environment->flag &= ~XS_LEVEL_FLAG; -} - -void fxCheckInstanceAliases(txMachine* the, txSlot* instance, txAliasIDList* list) -{ - txSlot* property = instance->next; - if (instance->flag & XS_LEVEL_FLAG) - return; - instance->flag |= XS_LEVEL_FLAG; - if (instance->value.instance.prototype) { - mxPushLink(propertyLink, mxID(___proto__), XSL_PROPERTY_FLAG); - fxCheckInstanceAliases(the, instance->value.instance.prototype, list); - mxPopLink(propertyLink); - } - if (instance->ID != XS_NO_ID) { - if (list->aliases[instance->ID] == 0) { - list->aliases[instance->ID] = 1; - fxCheckAliasesError(the, list, 1); - } - } - while (property) { - if (property->kind == XS_ACCESSOR_KIND) { - if (property->value.accessor.getter) { - mxPushLink(propertyLink, property->ID, XSL_GETTER_FLAG); - fxCheckInstanceAliases(the, property->value.accessor.getter, list); - mxPopLink(propertyLink); - } - if (property->value.accessor.setter) { - mxPushLink(propertyLink, property->ID, XSL_SETTER_FLAG); - fxCheckInstanceAliases(the, property->value.accessor.setter, list); - mxPopLink(propertyLink); - } - } - else if (property->kind == XS_ARRAY_KIND) { - txSlot* item = property->value.array.address; - txInteger length = (txInteger)fxGetIndexSize(the, property); - while (length > 0) { - if (item->kind == XS_REFERENCE_KIND) { - mxPushLink(propertyLink, (txInteger)(item->next), XSL_ITEM_FLAG); - fxCheckInstanceAliases(the, item->value.reference, list); - mxPopLink(propertyLink); - } - item++; - length--; - } - } - else if ((property->kind == XS_CODE_KIND) || (property->kind == XS_CODE_X_KIND)) { - if (property->value.code.closures) - fxCheckEnvironmentAliases(the, property->value.code.closures, list); - } - else if (property->kind == XS_PROXY_KIND) { - if (property->value.proxy.handler) { - mxPushLink(propertyLink, XS_NO_ID, XSL_PROXY_HANDLER_FLAG); - fxCheckInstanceAliases(the, property->value.proxy.handler, list); - mxPopLink(propertyLink); - } - if (property->value.proxy.target) { - mxPushLink(propertyLink, XS_NO_ID, XSL_PROXY_TARGET_FLAG); - fxCheckInstanceAliases(the, property->value.proxy.target, list); - mxPopLink(propertyLink); - } - } - else if (property->kind == XS_REFERENCE_KIND) { - mxPushLink(propertyLink, property->ID, XSL_PROPERTY_FLAG); - fxCheckInstanceAliases(the, property->value.reference, list); - mxPopLink(propertyLink); - } - property = property->next; - } -// instance->flag &= ~XS_LEVEL_FLAG; -} - -void fxFreezeBuiltIns(txMachine* the) -{ -#define mxFreezeBuiltInCall \ - mxPush(mxObjectConstructor); \ - mxPushSlot(freeze); \ - mxCall() -#define mxFreezeBuiltInRun \ - mxPushBoolean(1); \ - mxRunCount(2); \ - mxPop() - - txSlot* freeze; - txInteger id; - - mxTemporary(freeze); - mxPush(mxObjectConstructor); - fxGetID(the, mxID(_freeze)); - mxPullSlot(freeze); - - for (id = XS_SYMBOL_ID_COUNT; id < _Infinity; id++) { - mxFreezeBuiltInCall; mxPush(the->stackPrototypes[-1 - id]); mxFreezeBuiltInRun; - } - for (id = _Compartment; id < XS_INTRINSICS_COUNT; id++) { - mxFreezeBuiltInCall; mxPush(the->stackPrototypes[-1 - id]); mxFreezeBuiltInRun; - } - mxFreezeBuiltInCall; mxPush(mxGlobal); fxGetID(the, xsID("gc")); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxGlobal); fxGetID(the, xsID("evalScript")); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxGlobal); fxGetID(the, xsID("print")); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxGlobal); fxGetID(the, xsID("clearInterval")); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxGlobal); fxGetID(the, xsID("clearTimeout")); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxGlobal); fxGetID(the, xsID("setInterval")); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxGlobal); fxGetID(the, xsID("setTimeout")); mxFreezeBuiltInRun; - - mxFreezeBuiltInCall; mxPush(mxArgumentsSloppyPrototype); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxArgumentsStrictPrototype); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxArrayIteratorPrototype); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxAsyncFromSyncIteratorPrototype); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxAsyncFunctionPrototype); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxAsyncGeneratorFunctionPrototype); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxAsyncGeneratorPrototype); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxAsyncIteratorPrototype); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxGeneratorFunctionPrototype); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxGeneratorPrototype); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxHostPrototype); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxIteratorPrototype); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxMapIteratorPrototype); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxModulePrototype); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxRegExpStringIteratorPrototype); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxSetIteratorPrototype); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxStringIteratorPrototype); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxTransferPrototype); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxTypedArrayPrototype); mxFreezeBuiltInRun; - - mxFreezeBuiltInCall; mxPush(mxAssignObjectFunction); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxCopyObjectFunction); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxEnumeratorFunction); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxInitializeRegExpFunction); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxOnRejectedPromiseFunction); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxOnResolvedPromiseFunction); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPush(mxOnThenableFunction); mxFreezeBuiltInRun; - - mxFreezeBuiltInCall; mxPushReference(mxArrayLengthAccessor.value.accessor.getter); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPushReference(mxArrayLengthAccessor.value.accessor.setter); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPushReference(mxStringAccessor.value.accessor.getter); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPushReference(mxStringAccessor.value.accessor.setter); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPushReference(mxProxyAccessor.value.accessor.getter); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPushReference(mxProxyAccessor.value.accessor.setter); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPushReference(mxTypedArrayAccessor.value.accessor.getter); mxFreezeBuiltInRun; - mxFreezeBuiltInCall; mxPushReference(mxTypedArrayAccessor.value.accessor.setter); mxFreezeBuiltInRun; - - mxFreezeBuiltInCall; mxPush(mxArrayPrototype); fxGetID(the, mxID(_Symbol_unscopables)); mxFreezeBuiltInRun; - - mxFreezeBuiltInCall; mxPush(mxProgram); mxFreezeBuiltInRun; //@@ - mxFreezeBuiltInCall; mxPush(mxHosts); mxFreezeBuiltInRun; //@@ - - mxPop(); -} - -void fxPrintUsage() -{ - printf("xsnap [-h] [-f] [-i ] [-l ] [-s ] [-m] [-r ] [-s] [-v]\n"); - printf("\t-f: freeze the XS machine\n"); - printf("\t-h: print this help message\n"); - printf("\t-i : metering interval (default to 1)\n"); - printf("\t-l : metering limit (default to none)\n"); - printf("\t-s : parser buffer size, in kB (default to 8192)\n"); - printf("\t-r : read snapshot to create the XS machine\n"); - printf("\t-v: print XS version\n"); -} - -// void fx_evalScript(xsMachine* the) -// { -// txSlot* realm = mxProgram.value.reference->next->value.module.realm; -// txStringStream aStream; -// aStream.slot = mxArgv(0); -// aStream.offset = 0; -// aStream.size = c_strlen(fxToString(the, mxArgv(0))); -// fxRunScript(the, fxParseScript(the, &aStream, fxStringGetter, mxProgramFlag | mxDebugFlag), mxRealmGlobal(realm), C_NULL, mxRealmClosures(realm)->value.reference, C_NULL, mxProgram.value.reference); -// mxPullSlot(mxResult); -// } - -void fx_gc(xsMachine* the) -{ - xsCollectGarbage(); -} - -// void fx_isPromiseJobQueueEmpty(txMachine* the) -// { -// xsResult = (the->promiseJobs) ? xsFalse : xsTrue; -// } - -void fx_print(xsMachine* the) -{ - xsIntegerValue c = xsToInteger(xsArgc), i; -#if mxMetering - if (gxMeteringPrint) - fprintf(stdout, "[%u] ", the->meterIndex); -#endif - for (i = 0; i < c; i++) { - if (i) - fprintf(stdout, " "); - fprintf(stdout, "%s", xsToString(xsArg(i))); - } - fprintf(stdout, "\n"); - fflush(stdout); -} - -static void fx_currentMeterLimit(xsMachine* the) -{ -#if mxMetering - xsResult = xsInteger(gxCurrentMeter); -#endif -} - -static void fx_resetMeter(xsMachine* the) -{ -#if mxMetering - xsIntegerValue argc = xsToInteger(xsArgc); - if (argc < 2) { - xsTypeError("expected newMeterLimit, newMeterIndex"); - } - xsResult = xsInteger(the->meterIndex); - gxCurrentMeter = xsToInteger(xsArg(0)); - the->meterIndex = xsToInteger(xsArg(1)); -#endif -} - -// void fx_setInterval(txMachine* the) -// { -// fx_setTimer(the, fxToNumber(the, mxArgv(1)), 1); -// } -// -// void fx_setTimeout(txMachine* the) -// { -// fx_setTimer(the, fxToNumber(the, mxArgv(1)), 0); -// } -void fx_setImmediate(txMachine* the) -{ - fx_setTimer(the, 0, 0); -} - - -void fx_clearTimer(txMachine* the) -{ - txJob* job = xsGetHostData(xsArg(0)); - if (job) { - xsForget(job->self); - xsSetHostData(xsArg(0), NULL); - job->the = NULL; - } -} - -static txHostHooks gxTimerHooks = { - fx_destroyTimer, - fx_markTimer -}; - - -void fx_destroyTimer(void* data) -{ -} - -void fx_markTimer(txMachine* the, void* it, txMarkRoot markRoot) -{ - txJob* job = it; - if (job) { - (*markRoot)(the, &job->function); - (*markRoot)(the, &job->argument); - } -} - -void fx_setTimer(txMachine* the, txNumber interval, txBoolean repeat) -{ - c_timeval tv; - txJob* job; - txJob** address = (txJob**)&(the->timerJobs); - while ((job = *address)) - address = &(job->next); - job = *address = malloc(sizeof(txJob)); - c_memset(job, 0, sizeof(txJob)); - job->the = the; - job->callback = fx_setTimerCallback; - c_gettimeofday(&tv, NULL); - if (repeat) - job->interval = interval; - job->when = ((txNumber)(tv.tv_sec) * 1000.0) + ((txNumber)(tv.tv_usec) / 1000.0) + interval; - job->self = xsNewHostObject(NULL); - job->function = xsArg(0); - if (xsToInteger(xsArgc) > 2) - job->argument = xsArg(2); - else - job->argument = xsUndefined; - xsSetHostData(job->self, job); - xsSetHostHooks(job->self, &gxTimerHooks); - xsRemember(job->self); - xsResult = xsAccess(job->self); -} - -void fx_setTimerCallback(txJob* job) -{ - xsMachine* the = job->the; - xsBeginHost(the); - { - mxTry(the) { - xsCallFunction1(job->function, xsUndefined, job->argument); - } - mxCatch(the) { - fprintf(stderr, "exception in setTimerCallback: %s\n", xsToString(xsException)); - } - } - xsEndHost(the); -} - -/* PLATFORM */ - -/** - * fxAbort is the catch-all for "something happened which might make - * you want to abort." The status argument tells you what - * happened. For example, when the metering on opcodes expires, the - * status is XS_TOO_MUCH_COMPUTATION_EXIT. There's no danger, from an - * XS perspective, in ignoring that and simply returning from - * fxAbort (or using fxExitToHost). - * - * But we MUST c_exit() on XS_NOT_ENOUGH_MEMORY_EXIT: it may leave the - * engine corrupted. - */ -void fxAbort(txMachine* the, int status) -{ - switch (status) { - case XS_STACK_OVERFLOW_EXIT: - xsLog("stack overflow\n"); -#ifdef mxDebug - fxDebugger(the, (char *)__FILE__, __LINE__); -#endif - c_exit(E_STACK_OVERFLOW); - break; - case XS_NOT_ENOUGH_MEMORY_EXIT: - xsLog("memory full\n"); -#ifdef mxDebug - fxDebugger(the, (char *)__FILE__, __LINE__); -#endif - c_exit(E_NOT_ENOUGH_MEMORY); - break; - case XS_NO_MORE_KEYS_EXIT: - xsLog("not enough keys\n"); -#ifdef mxDebug - fxDebugger(the, (char *)__FILE__, __LINE__); -#endif - c_exit(E_NO_MORE_KEYS); - break; - case XS_TOO_MUCH_COMPUTATION_EXIT: - xsLog("too much computation\n"); -#ifdef mxDebug - fxDebugger(the, (char *)__FILE__, __LINE__); -#endif - c_exit(E_TOO_MUCH_COMPUTATION); - break; - case XS_UNHANDLED_EXCEPTION_EXIT: - case XS_UNHANDLED_REJECTION_EXIT: - xsLog("%s\n", xsToString(xsException)); - xsException = xsUndefined; - break; - default: - xsLog("fxAbort(%d) - %s\n", status, xsToString(xsException)); - c_exit(E_UNKNOWN_ERROR); - break; - } -} - -void fxCreateMachinePlatform(txMachine* the) -{ -#ifdef mxDebug - the->connection = mxNoSocket; -#endif - the->promiseJobs = 0; - the->timerJobs = NULL; - - // Original 10x strategy: - // SLOGFILE=out.slog agoric start local-chain - // jq -s '.|.[]|.dr[2].allocate' < out.slog|grep -v null|sort -u | sort -nr - // int MB = 1024 * 1024; - // int measured_max = 30 * MB; - // the->allocationLimit = 10 * measured_max; - - size_t GB = 1024 * 1024 * 1024; - the->allocationLimit = 2 * GB; -} - -void fxDeleteMachinePlatform(txMachine* the) -{ -} - -void fxQueuePromiseJobs(txMachine* the) -{ - the->promiseJobs = 1; -} - -void fxRunLoop(txMachine* the) -{ - c_timeval tv; - txNumber when; - txJob* job; - txJob** address; - for (;;) { - while (the->promiseJobs) { - while (the->promiseJobs) { - the->promiseJobs = 0; - fxRunPromiseJobs(the); - } - // give finalizers a chance to run after the promise queue is empty - fxEndJob(the); - // if that added to the promise queue, start again - } - // at this point the promise queue is empty - c_gettimeofday(&tv, NULL); - when = ((txNumber)(tv.tv_sec) * 1000.0) + ((txNumber)(tv.tv_usec) / 1000.0); - address = (txJob**)&(the->timerJobs); - if (!*address) - break; - while ((job = *address)) { - if (job->the) { - if (job->when <= when) { - (*job->callback)(job); - if (job->the) { - if (job->interval) { - job->when += job->interval; - } - else { - xsBeginHost(job->the); - xsResult = xsAccess(job->self); - xsForget(job->self); - xsSetHostData(xsResult, NULL); - xsEndHost(job->the); - job->the = NULL; - } - } - break; // to run promise jobs queued by the timer in the same "tick" - } - address = &(job->next); - } - else { - *address = job->next; - c_free(job); - } - } - } -} - -void fxFulfillModuleFile(txMachine* the) -{ - xsException = xsUndefined; -} - -void fxRejectModuleFile(txMachine* the) -{ - xsException = xsArg(0); -} - -void fxRunModuleFile(txMachine* the, txString path) -{ - txSlot* realm = mxProgram.value.reference->next->value.module.realm; - mxPushStringC(path); - fxRunImport(the, realm, XS_NO_ID); - mxDub(); - fxGetID(the, mxID(_then)); - mxCall(); - fxNewHostFunction(the, fxFulfillModuleFile, 1, XS_NO_ID); - fxNewHostFunction(the, fxRejectModuleFile, 1, XS_NO_ID); - mxRunCount(2); - mxPop(); -} - -void fxRunProgramFile(txMachine* the, txString path, txUnsigned flags) -{ - txSlot* realm = mxProgram.value.reference->next->value.module.realm; - txScript* script = fxLoadScript(the, path, flags); - if (!script) { - xsUnknownError("cannot load script; check filename"); - } - mxModuleInstanceInternal(mxProgram.value.reference)->value.module.id = fxID(the, path); - fxRunScript(the, script, mxRealmGlobal(realm), C_NULL, mxRealmClosures(realm)->value.reference, C_NULL, mxProgram.value.reference); - mxPullSlot(mxResult); -} - -/* DEBUG */ - -#ifdef mxDebug - -void fxConnect(txMachine* the) -{ - char name[256]; - char* colon; - int port; - struct sockaddr_in address; -#if mxWindows - if (GetEnvironmentVariable("XSBUG_HOST", name, sizeof(name))) { -#else - colon = getenv("XSBUG_HOST"); - if ((colon) && (c_strlen(colon) + 1 < sizeof(name))) { - c_strcpy(name, colon); -#endif - colon = strchr(name, ':'); - if (colon == NULL) - port = 5002; - else { - *colon = 0; - colon++; - port = strtol(colon, NULL, 10); - } - } - else { - strcpy(name, "localhost"); - port = 5002; - } - memset(&address, 0, sizeof(address)); - address.sin_family = AF_INET; - address.sin_addr.s_addr = inet_addr(name); - if (address.sin_addr.s_addr == INADDR_NONE) { - struct hostent *host = gethostbyname(name); - if (!host) - return; - memcpy(&(address.sin_addr), host->h_addr, host->h_length); - } - address.sin_port = htons(port); -#if mxWindows -{ - WSADATA wsaData; - unsigned long flag; - if (WSAStartup(0x202, &wsaData) == SOCKET_ERROR) - return; - the->connection = socket(AF_INET, SOCK_STREAM, 0); - if (the->connection == INVALID_SOCKET) - return; - flag = 1; - ioctlsocket(the->connection, FIONBIO, &flag); - if (connect(the->connection, (struct sockaddr*)&address, sizeof(address)) == SOCKET_ERROR) { - if (WSAEWOULDBLOCK == WSAGetLastError()) { - fd_set fds; - struct timeval timeout = { 2, 0 }; // 2 seconds, 0 micro-seconds - FD_ZERO(&fds); - FD_SET(the->connection, &fds); - if (select(0, NULL, &fds, NULL, &timeout) == 0) - goto bail; - if (!FD_ISSET(the->connection, &fds)) - goto bail; - } - else - goto bail; - } - flag = 0; - ioctlsocket(the->connection, FIONBIO, &flag); -} -#else -{ - int flag; - the->connection = socket(AF_INET, SOCK_STREAM, 0); - if (the->connection <= 0) - goto bail; - c_signal(SIGPIPE, SIG_IGN); -#if mxMacOSX - { - int set = 1; - setsockopt(the->connection, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int)); - } -#endif - flag = fcntl(the->connection, F_GETFL, 0); - fcntl(the->connection, F_SETFL, flag | O_NONBLOCK); - if (connect(the->connection, (struct sockaddr*)&address, sizeof(address)) < 0) { - if (errno == EINPROGRESS) { - fd_set fds; - struct timeval timeout = { 2, 0 }; // 2 seconds, 0 micro-seconds - int error = 0; - unsigned int length = sizeof(error); - FD_ZERO(&fds); - FD_SET(the->connection, &fds); - if (select(the->connection + 1, NULL, &fds, NULL, &timeout) == 0) - goto bail; - if (!FD_ISSET(the->connection, &fds)) - goto bail; - if (getsockopt(the->connection, SOL_SOCKET, SO_ERROR, &error, &length) < 0) - goto bail; - if (error) - goto bail; - } - else - goto bail; - } - fcntl(the->connection, F_SETFL, flag); - c_signal(SIGPIPE, SIG_DFL); -} -#endif - return; -bail: - fxDisconnect(the); -} - -void fxDisconnect(txMachine* the) -{ -#if mxWindows - if (the->connection != INVALID_SOCKET) { - closesocket(the->connection); - the->connection = INVALID_SOCKET; - } - WSACleanup(); -#else - if (the->connection >= 0) { - close(the->connection); - the->connection = -1; - } -#endif -} - -txBoolean fxIsConnected(txMachine* the) -{ - return (the->connection != mxNoSocket) ? 1 : 0; -} - -txBoolean fxIsReadable(txMachine* the) -{ - return 0; -} - -void fxReceive(txMachine* the) -{ - int count; - if (the->connection != mxNoSocket) { -#if mxWindows - count = recv(the->connection, the->debugBuffer, sizeof(the->debugBuffer) - 1, 0); - if (count < 0) - fxDisconnect(the); - else - the->debugOffset = count; -#else - again: - count = read(the->connection, the->debugBuffer, sizeof(the->debugBuffer) - 1); - if (count < 0) { - if (errno == EINTR) - goto again; - else - fxDisconnect(the); - } - else - the->debugOffset = count; -#endif - } - the->debugBuffer[the->debugOffset] = 0; -} - -void fxSend(txMachine* the, txBoolean more) -{ - if (the->connection != mxNoSocket) { -#if mxWindows - if (send(the->connection, the->echoBuffer, the->echoOffset, 0) <= 0) - fxDisconnect(the); -#else - again: - if (write(the->connection, the->echoBuffer, the->echoOffset) <= 0) { - if (errno == EINTR) - goto again; - else - fxDisconnect(the); - } -#endif - } -} - -#endif /* mxDebug */ - -static int fxReadNetString(FILE *inStream, char** dest, size_t* len) -{ - int code = 0; - char* buf = NULL; - - if (fscanf(inStream, "%9lu", len) < 1) { - /* >999999999 bytes is bad */ - code = 1; - } else if (fgetc(inStream) != ':') { - code = 2; - } else { - buf = malloc(*len + 1); /* malloc(0) is not portable */ - if (!buf) { - code = 3; - } else if (fread(buf, 1, *len, inStream) < *len) { - code = 4; - } else if (fgetc(inStream) != ',') { - code = 5; - } else { - *(buf + *len) = 0; - } - if (code == 0) { - *dest = buf; - } else { - *dest = 0; - free(buf); - } - } - return code; -} - -static char* fxReadNetStringError(int code) -{ - switch (code) { - case 0: return "OK"; - case 1: return "Cannot read netstring, reading length prefix, fscanf"; - case 2: return "Cannot read netstring, invalid delimiter or end of file, fgetc"; - case 3: return "Cannot read netstring, cannot allocate message buffer, malloc"; - case 4: return "Cannot read netstring, cannot read message body, fread"; - case 5: return "Cannot read netstring, cannot read trailer, fgetc"; - default: return "Cannot read netstring"; - } -} - -static int fxWriteOkay(FILE* outStream, xsUnsignedValue meterIndex, txMachine *the, char* buf, size_t length) -{ - char fmt[] = ("." // OK - "{" - "\"compute\":%u," - "\"allocate\":%u," - "\"allocateChunksCalls\":%u," - "\"allocateSlotsCalls\":%u," - "\"garbageCollectionCount\":%u," - "\"mapSetAddCount\":%u," - "\"mapSetRemoveCount\":%u," - "\"maxBucketSize\":%u}" - "\1" // separate meter info from result - ); - char numeral64[] = "12345678901234567890"; // big enough for 64bit numeral - char prefix[8 + sizeof fmt + 8 * sizeof numeral64]; - // TODO: fxCollect counter - // Prepend the meter usage to the reply. - snprintf(prefix, sizeof(prefix) - 1, fmt, - meterIndex, the->allocatedSpace, - the->allocateChunksCallCount, the->allocateSlotsCallCount, - the->garbageCollectionCount, - the->mapSetAddCount, the->mapSetRemoveCount, - the->maxBucketSize); - return fxWriteNetString(outStream, prefix, buf, length); -} - -static int fxWriteNetString(FILE* outStream, char* prefix, char* buf, size_t length) -{ - if (fprintf(outStream, "%lu:%s", length + strlen(prefix), prefix) < 1) { - return 1; - } else if (fwrite(buf, 1, length, outStream) < length) { - return 2; - } else if (fputc(',', outStream) == EOF) { - return 3; - } else if (fflush(outStream) < 0) { - return 4; - } - - return 0; -} - -static char* fxWriteNetStringError(int code) -{ - switch (code) { - case 0: return "OK"; - case 1: return "Cannot write netstring, error writing length prefix"; - case 2: return "Cannot write netstring, error writing message body"; - case 3: return "Cannot write netstring, error writing terminator"; - case 4: return "Cannot write netstring, error flushing stream, fflush"; - default: return "Cannot write netstring"; - } -} - -static void fx_issueCommand(xsMachine *the) -{ - int argc = xsToInteger(xsArgc); - if (argc < 1) { - mxTypeError("expected ArrayBuffer"); - } - - size_t length; - char* buf = NULL; - length = xsGetArrayBufferLength(xsArg(0)); - - buf = malloc(length); - if (!buf) { - fxAbort(the, XS_NOT_ENOUGH_MEMORY_EXIT); - } - - xsGetArrayBufferData(xsArg(0), 0, buf, length); - int writeError = fxWriteNetString(toParent, "?", buf, length); - - free(buf); - - if (writeError != 0) { - xsUnknownError(fxWriteNetStringError(writeError)); - } - - // read netstring - size_t len; - int readError = fxReadNetString(fromParent, &buf, &len); - if (readError != 0) { - xsUnknownError(fxReadNetStringError(readError)); - } - - xsResult = xsArrayBuffer(buf, len); - free(buf); -} - - -void adjustSpaceMeter(txMachine* the, txSize theSize) -{ - txSize previous = the->allocatedSpace; - the->allocatedSpace += theSize; - if (the->allocatedSpace > the->allocationLimit || - // overflow? - the->allocatedSpace < previous) { - fxAbort(the, XS_NOT_ENOUGH_MEMORY_EXIT); - } -} - -void* fxAllocateChunks(txMachine* the, txSize theSize) -{ - // fprintf(stderr, "fxAllocateChunks(%lu)\n", theSize); - adjustSpaceMeter(the, theSize); - the->allocateChunksCallCount += 1; - return c_malloc(theSize); -} - -void fxFreeChunks(txMachine* the, void* theChunks) -{ - // "XS doesn't currently free the allocations until the VM is - // terminated, so a simple space meter only needs to track the - // allocations." -- PH 2021-04-26 - c_free(theChunks); -} - -txSlot* fxAllocateSlots(txMachine* the, txSize theCount) -{ - // fprintf(stderr, "fxAllocateSlots(%u) * %d = %ld\n", theCount, sizeof(txSlot), theCount * sizeof(txSlot)); - adjustSpaceMeter(the, theCount * sizeof(txSlot)); - the->allocateSlotsCallCount += 1; - return (txSlot*)c_malloc(theCount * sizeof(txSlot)); -} - -void fxFreeSlots(txMachine* the, void* theSlots) -{ - c_free(theSlots); -} - -void fx_performance_now(txMachine *the) -{ - c_timeval tv; - c_gettimeofday(&tv, NULL); - mxResult->kind = XS_NUMBER_KIND; - mxResult->value.number = (double)(tv.tv_sec * 1000.0) + ((double)(tv.tv_usec) / 1000.0); -} - - -// Local Variables: -// tab-width: 4 -// c-basic-offset: 4 -// indent-tabs-mode: t -// End: -// vim: noet ts=4 sw=4 diff --git a/packages/xsnap/src/xsnap.h b/packages/xsnap/src/xsnap.h deleted file mode 100644 index be95b6dc6cd..00000000000 --- a/packages/xsnap/src/xsnap.h +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef __XSNAP__ -#define __XSNAP__ - -#if defined(_MSC_VER) - #if defined(_M_IX86) || defined(_M_X64) - #undef mxLittleEndian - #define mxLittleEndian 1 - #undef mxWindows - #define mxWindows 1 - #define mxExport extern - #define mxImport extern - #define XS_FUNCTION_NORETURN - #else - #error unknown Microsoft compiler - #endif -#elif defined(__GNUC__) - #if defined(__i386__) || defined(i386) || defined(intel) || defined(arm) || defined(__arm__) || defined(__k8__) || defined(__x86_64__) || defined(__aarch64__) - #undef mxLittleEndian - #define mxLittleEndian 1 - #if defined(__linux__) || defined(linux) - #undef mxLinux - #define mxLinux 1 - #else - #undef mxMacOSX - #define mxMacOSX 1 - #endif - #define mxExport extern - #define mxImport extern - #define XS_FUNCTION_NORETURN __attribute__((noreturn)) - #else - #error unknown GNU compiler - #endif -#else - #error unknown compiler -#endif - -#if mxWindows - #define _USE_MATH_DEFINES - #define WIN32_LEAN_AND_MEAN - #define _WINSOCK_DEPRECATED_NO_WARNINGS -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if mxWindows - #include - typedef SOCKET txSocket; - #define mxNoSocket INVALID_SOCKET -#else - #include - #include - #include - #include - #include - #include - typedef int txSocket; - #define mxNoSocket -1 - #define mxUseGCCAtomics 1 - #define mxUsePOSIXThreads 1 -#endif -#define mxMachinePlatform \ - txSocket connection; \ - size_t allocationLimit; \ - size_t allocatedSpace; \ - txUnsigned allocateChunksCallCount; \ - txUnsigned allocateSlotsCallCount; \ - txUnsigned garbageCollectionCount; \ - txUnsigned mapSetAddCount; \ - txUnsigned mapSetRemoveCount; \ - txUnsigned maxBucketSize; \ - int promiseJobs; \ - void* timerJobs; \ - void* waiterCondition; \ - void* waiterData; \ - txMachine* waiterLink; - -#define mxUseDefaultBuildKeys 1 -#define mxUseDefaultChunkAllocation 0 -#define mxUseDefaultSlotAllocation 0 -#define mxUseDefaultFindModule 1 -#define mxUseDefaultLoadModule 1 -#define mxUseDefaultParseScript 1 -#define mxUseDefaultSharedChunks 1 - -#endif /* __XSNAP__ */ - -// Local Variables: -// tab-width: 4 -// c-basic-offset: 4 -// indent-tabs-mode: t -// End: -// vim: noet ts=4 sw=4 diff --git a/packages/xsnap/src/xsnap.js b/packages/xsnap/src/xsnap.js index 89927847f09..5d60a98f797 100644 --- a/packages/xsnap/src/xsnap.js +++ b/packages/xsnap/src/xsnap.js @@ -80,7 +80,9 @@ export function xsnap(options) { } let bin = new URL( - `../build/bin/${platform}/${debug ? 'debug' : 'release'}/xsnap`, + `../xsnap-native/xsnap/build/bin/${platform}/${ + debug ? 'debug' : 'release' + }/xsnap-worker`, import.meta.url, ).pathname; From 8148c13c5f4810c5fe92e05ced57ebf56302404d Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Aug 2021 13:31:15 -0500 Subject: [PATCH 3/6] fix(xsnap)!: don't rely on diagnostic meters plus a drive-by spelling fix --- packages/xsnap/api.js | 2 +- packages/xsnap/test/test-xs-perf.js | 14 +++----------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/xsnap/api.js b/packages/xsnap/api.js index e049891ea39..829ede0fbd5 100644 --- a/packages/xsnap/api.js +++ b/packages/xsnap/api.js @@ -3,7 +3,7 @@ /** The version identifier for our meter type. * TODO Bump this whenever there's a change to metering semantics. */ -export const METER_TYPE = 'xs-meter-9'; +export const METER_TYPE = 'xs-meter-10'; export const ExitCode = { E_UNKNOWN_ERROR: -1, diff --git a/packages/xsnap/test/test-xs-perf.js b/packages/xsnap/test/test-xs-perf.js index 0d5928c47ce..a039edd4c92 100644 --- a/packages/xsnap/test/test-xs-perf.js +++ b/packages/xsnap/test/test-xs-perf.js @@ -55,18 +55,10 @@ test('meter details', async t => { { compute: 'number', allocate: 'number', - allocateChunksCalls: 'number', - allocateSlotsCalls: 'number', - garbageCollectionCount: 'number', - mapSetAddCount: 'number', - mapSetRemoveCount: 'number', - maxBucketSize: 'number', }, - 'auxiliary (non-consensus) meters are available', + 'evaluate returns meter details', ); - // @ts-ignore extra meters not declared on RunResult (TODO: #3139) - t.true(meters.mapSetAddCount > 20000); - t.is(meterType, 'xs-meter-9'); + t.is(meterType, 'xs-meter-10'); }); test('isReady does not compute / allocate', async t => { @@ -221,7 +213,7 @@ function dataStructurePerformance(logn) { } // This test fails intermittently due to some amount of noise that we cannot -// completely elliminate. +// completely eliminate. // Rather than have a very low-probability failing test, we skip this, but // retain the benchmark for future verification in the unlikely event that the // performance character of XS collections regresses. From 8c4a16bc203722d594f09bf7c5acd09c4209ba1c Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Fri, 13 Aug 2021 13:27:55 -0500 Subject: [PATCH 4/6] feat(xsnap): make name available on xsnap object --- packages/xsnap/src/replay.js | 1 + packages/xsnap/src/xsnap.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/xsnap/src/replay.js b/packages/xsnap/src/replay.js index a13af068f12..4e40bc1ec13 100644 --- a/packages/xsnap/src/replay.js +++ b/packages/xsnap/src/replay.js @@ -130,6 +130,7 @@ export function recordXSnap(options, folderPath, { writeFileSync }) { const it = xsnap({ ...options, handleCommand }); return freeze({ + name: it.name, isReady: async () => { nextFile('isReady'); return it.isReady(); diff --git a/packages/xsnap/src/xsnap.js b/packages/xsnap/src/xsnap.js index 5d60a98f797..98520d9ae8b 100644 --- a/packages/xsnap/src/xsnap.js +++ b/packages/xsnap/src/xsnap.js @@ -315,6 +315,7 @@ export function xsnap(options) { } return freeze({ + name, issueCommand, issueStringCommand, isReady, From fc9332fc52f626b884e4998e780dbfbf87cb854d Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Fri, 13 Aug 2021 13:59:53 -0500 Subject: [PATCH 5/6] feat(xsnap): record upstream commands as well as replies --- packages/xsnap/src/replay.js | 6 +++++- packages/xsnap/test/test-replay.js | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/xsnap/src/replay.js b/packages/xsnap/src/replay.js index 4e40bc1ec13..358374a4c19 100644 --- a/packages/xsnap/src/replay.js +++ b/packages/xsnap/src/replay.js @@ -103,6 +103,7 @@ export function recordXSnap(options, folderPath, { writeFileSync }) { /** @param { Uint8Array} msg */ async function handleCommand(msg) { + nextFile('command').put(msg); const result = await handle(msg); nextFile('reply').put(result); return result; @@ -213,7 +214,7 @@ export async function replayXSnap( const [_match, digits, kind] = parts; const seq = parseInt(digits, 10); console.log(folder, seq, kind); - if (running && kind !== 'reply') { + if (running && !['command', 'reply'].includes(kind)) { // eslint-disable-next-line no-await-in-loop await running; running = undefined; @@ -230,6 +231,9 @@ export async function replayXSnap( case 'issueCommand': running = it.issueCommand(file.getData()); break; + case 'command': + // ignore; we already know how to reply + break; case 'reply': replies.put(file.getData()); break; diff --git a/packages/xsnap/test/test-replay.js b/packages/xsnap/test/test-replay.js index e6dea4dc63e..2fabd2df697 100644 --- a/packages/xsnap/test/test-replay.js +++ b/packages/xsnap/test/test-replay.js @@ -21,7 +21,8 @@ const transcript1 = [ '/xsnap-tests/00001-evaluate.dat', 'issueCommand(ArrayBuffer.fromString("Hello, World!"));', ], - ['/xsnap-tests/00002-reply.dat', ''], + ['/xsnap-tests/00002-command.dat', '{"compute":54'], + ['/xsnap-tests/00003-reply.dat', ''], ]; test('record: evaluate and issueCommand', async t => { @@ -72,6 +73,7 @@ test('replay', async t => { t.deepEqual(done, [ ['/xs-test/', 1, 'evaluate'], - ['/xs-test/', 2, 'reply'], + ['/xs-test/', 2, 'command'], + ['/xs-test/', 3, 'reply'], ]); }); From 66f9cde593be84412a8d78b1fa7446edceb59309 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Fri, 13 Aug 2021 13:28:24 -0500 Subject: [PATCH 6/6] test(xsnap): GC of cloned snapshot does not diverge --- packages/xsnap/test/test-xsnap.js | 84 ++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/packages/xsnap/test/test-xsnap.js b/packages/xsnap/test/test-xsnap.js index 0f65c6b4a28..05d1c6290dc 100644 --- a/packages/xsnap/test/test-xsnap.js +++ b/packages/xsnap/test/test-xsnap.js @@ -1,14 +1,16 @@ -/* global setTimeout, __filename */ +/* global setTimeout, WeakRef, setImmediate, process */ // @ts-check // eslint-disable-next-line import/no-extraneous-dependencies import test from 'ava'; import * as proc from 'child_process'; import * as os from 'os'; +import fs, { unlinkSync } from 'fs'; // eslint-disable-next-line import/no-extraneous-dependencies import tmp from 'tmp'; import { xsnap } from '../src/xsnap.js'; +import { recordXSnap } from '../src/replay.js'; import { ExitCode, ErrorCode } from '../api.js'; import { options, decode, encode, loader } from './message-tools.js'; @@ -333,3 +335,83 @@ test('normal close of pathological script', async t => { await vat.terminate(); await hang; }); + +async function runToGC() { + const trashCan = [{}]; + const wr = new WeakRef(trashCan[0]); + trashCan[0] = undefined; + + let qty; + for (qty = 0; wr.deref(); qty += 1) { + // eslint-disable-next-line no-await-in-loop + await new Promise(setImmediate); + trashCan[1] = Array(10_000).map(() => ({})); + } + return qty; +} + +function pickXSnap(env = process.env) { + let doXSnap = xsnap; + const { XSNAP_TEST_RECORD } = env; + if (XSNAP_TEST_RECORD) { + console.log('SwingSet xs-worker tracing:', { XSNAP_TEST_RECORD }); + let serial = 0; + doXSnap = opts => { + const workerTrace = `${XSNAP_TEST_RECORD}/${serial}/`; + serial += 1; + fs.mkdirSync(workerTrace, { recursive: true }); + return recordXSnap(opts, workerTrace, { + writeFileSync: fs.writeFileSync, + }); + }; + } + return doXSnap; +} + +test('GC after snapshot vs restore', async t => { + const xsnapr = pickXSnap(); + + const opts = { ...options(io), name: 'original', meteringLimit: 0 }; + const worker = xsnapr(opts); + t.teardown(worker.terminate); + + await worker.evaluate(` + globalThis.send = it => issueCommand(ArrayBuffer.fromString(JSON.stringify(it))); + globalThis.runToGC = (${runToGC}); + runToGC(); + // bloat the heap + send(Array.from(Array(2_000_000).keys()).length) + `); + + const nextGC = async (w, o) => { + await w.evaluate(`runToGC().then(send)`); + const workToGC = JSON.parse(o.messages.pop()); + t.log({ name: w.name, workToGC }); + return workToGC; + }; + + const beforeClone = await nextGC(worker, opts); + + const snapshot = './bloated.xss'; + await worker.snapshot(snapshot); + t.teardown(() => unlinkSync(snapshot)); + + const optClone = { ...options(io), name: 'clone', snapshot }; + const clone = xsnapr(optClone); + t.log('cloned', { snapshot }); + t.teardown(clone.terminate); + + let workerGC = beforeClone; + let cloneGC = workerGC; + let iters = 0; + + while (workerGC === cloneGC && iters < 3) { + // eslint-disable-next-line no-await-in-loop + workerGC = await nextGC(worker, opts); + // eslint-disable-next-line no-await-in-loop + cloneGC = await nextGC(clone, optClone); + iters += 1; + } + t.log({ beforeClone, workerGC, cloneGC, iters }); + t.is(workerGC, cloneGC); +});