Skip to content

Instantly share code, notes, and snippets.

@dckc
Last active February 26, 2020 06:55
Show Gist options
  • Save dckc/52e43c8238e6202a0b90e157f0d74ae8 to your computer and use it in GitHub Desktop.
Save dckc/52e43c8238e6202a0b90e157f0d74ae8 to your computer and use it in GitHub Desktop.
xs snapshots: arraybuffer mystery

context: Agoric/agoric-sdk#511

example run:

mkdir -p build
mcconfig -d -o ./build -m -p x-cli-lin
make[1]: Entering directory '/home/connolly/projects/moddable/examples/js/snapshots'
# xsc Resource.xsb
# xsc instrumentation.xsb
# xsc main.xsb
# xsc mc/config.xsb
# xsid modInstrumentation.c.xsi
# xsc snapshot.xsb
# xsc vat1.xsb
# xsid Resource.c.xsi
# xsid modInstrumentation.h.xsi
# xsid snapshot.c.xsi
# mcrez resources
# xsl modules
# xsl modules
Total resource size: 0 bytes
# cc mc.resources.c
# cc mc.xs.c
# cc modInstrumentation.c.o
# cc Resource.c.o
# cc snapshot.c.o
In file included from /home/connolly/projects/moddable/examples/js/snapshots/snapshot.c:7:0:
/home/connolly/projects/moddable/examples/js/snapshots/snapshot.c: In function ‘alreadySeen’:
/home/connolly/projects/moddable/xs/includes/xs.h:157:26: warning: operation on ‘the->stack’ may be undefined [-Wsequence-point]
 #define fxPush(_SLOT) (*(--the->stack) = (_SLOT))
                         ~^~~~~~~~~~~~~
/home/connolly/projects/moddable/xs/includes/xs.h:842:2: note: in expansion of macro ‘fxPush’
  fxPush(_SLOT), \
  ^~~~~~
/home/connolly/projects/moddable/examples/js/snapshots/snapshot.c:99:9: note: in expansion of macro ‘xsTest’
     if (xsTest(xsGet(xsVar(3), xsID("done"))))
         ^~~~~~
# copy lin_xs_cli.c
In file included from /home/connolly/projects/moddable/examples/js/snapshots/build/tmp/lin/debug/snapshots/lin_xs_cli.c:3:0:
/home/connolly/projects/moddable/xs/platforms/xsPlatform.h:45:0: warning: "mxLinux" redefined
 #define mxLinux 0
 
In file included from /home/connolly/projects/moddable/examples/js/snapshots/build/tmp/lin/debug/snapshots/lin_xs_cli.c:1:0:
/home/connolly/projects/moddable/xs/platforms/lin_xs.h:42:0: note: this is the location of the previous definition
 #define mxLinux 1
 
# cc snapshots
make[1]: Leaving directory '/home/connolly/projects/moddable/examples/js/snapshots'
./build/bin/lin/debug/snapshots
exits[0] type:10 kind:10 reference:0x55f5c26f0e60
>>root
>>root>slot: 0x7ffc5ff52530 kind:10 offset:5
=== pushSelf 0x7ffc5ff52530 offset 6
==== alreadySeen(0x7ffc5ff52530) seen=4 1,2,3,Hello World,Hello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello World?
===== no not seen 0x7ffc5ff52530 exitQty=5
==== pushSelf(0x7ffc5ff52530) delta=-1 (#4) *seen=6 offset:6 exitQty:5
found Array! slot=0x7ffc5ff52530 reference=0x55f5c3b76ec0 prototype=0x55f5c26f45e0
>>root>slot: 0x7ffc5ff52530 kind:10 offset:5>reference
>>root>slot: 0x7ffc5ff52530 kind:10 offset:5>reference>slot: 0x55f5c3b76ec0 kind:13 offset:21
=== pushSelf 0x55f5c3b76ec0 offset 22
==== alreadySeen(0x55f5c3b76ec0) seen=6 [?]?
===== no not seen 0x55f5c3b76ec0 exitQty=5
==== pushSelf(0x55f5c3b76ec0) delta=-16 (#4) *seen=22 offset:22 exitQty:5
>>root>slot: 0x7ffc5ff52530 kind:10 offset:5>reference>slot: 0x55f5c3b76ec0 kind:13 offset:21>prototype
>>root>slot: 0x7ffc5ff52530 kind:10 offset:5>reference>slot: 0x55f5c3b76ec0 kind:13 offset:21>prototype>slot: 0x55f5c26f45e0 kind:13 offset:37
=== pushSelf 0x55f5c26f45e0 offset 38
==== alreadySeen(0x55f5c26f45e0) seen=22 [?]?
===== yes 0x55f5c26f45e0 is exit 1

...

snapshot value: {
  "self": 140721918387504,
  "next": null,
  "kind": 10,
  "flag": 0,
  "id": 0,
  "value": {
    "reference": {
      "kind": 13,
      "self": 94514038927040,
      "flag": 1,
      "id": -1,
      "idname": null,
      "value": {
        "garbage": null,
        "prototype": {
          "exit": 1
        }
      },
      "next": {
        "kind": 16,
        "self": 94514038927072,
        "flag": 6,
        "id": 3,
        "idname": null,
        "value": [
          {
            "kind": 3,
            "flag": 0,
            "id": -1,
            "idname": null,
            "value": 1,
 main() returned immediate value (not a promise). exiting
           "next": null
          },
          {
            "kind": 3,
            "flag": 0,
            "id": -1,
            "idname": null,
            "value": 2,
            "next": null
          },
          {
            "kind": 3,
            "flag": 0,
            "id": -1,
            "idname": null,
            "value": 3,
            "next": null
          },
          {
            "kind": 6,
            "flag": 0,
            "id": -1,
            "idname": null,
            "value": "Hello World",
            "next": null
          },
          {
            "kind": 5,
            "flag": 0,
            "id": -1,
            "idname": null,
            "value": "Hello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello World",
            "next": null
          }
        ],
        "next": null
      }
    }
  }
}
/* global Compartment, trace, snapshot */
trace(`in main module\n`);
import { Snapshot } from 'snapshot';
function traceError(thunk) {
try {
return thunk();
} catch (err) {
trace(`Error: ${err.message}\n`);
throw err;
}
}
export default function main() {
const root = [
1, 2, 3,
true, false,
null, undefined,
'Hello World',
'Hello World'.repeat(20),
];
trace(`root type: ${ typeof root }\n`); // JSON.stringify could run into circular structures.
trace(`root type: ${JSON.stringify(root.map(e => typeof e))}\n`);
const exits = [Object.prototype, Array.prototype, String.prototype, true, 1];
trace(`[] prototype in exits? ${exits.indexOf(root.__proto__)}\n`);
const s1 = new Snapshot();
const rawbuf = s1.dump(root, exits);
trace(`snapshot: 0x${s1.tohex(rawbuf, 128)}\n`);
const info = traceError(() => s1.restore(rawbuf, exits.length));
const { self, next, kind, flag, id, value } = info;
trace(`snapshot value: ${JSON.stringify({ self, next, kind, flag, id, value }, null, 2)}\n`);
}
SRCS=main.js vat1.js snapshot.js snapshot.c
BIN=./build/bin/lin/debug/snapshots
$(BIN): ./build $(SRCS)
mcconfig -d -o ./build -m -p x-cli-lin
run: $(BIN)
$(BIN)
./build:
mkdir -p build
{
"include": [
"$(MODDABLE)/examples/manifest_base.json"
],
"modules": {
"*": [
"./main",
"./vat1",
"./snapshot",
],
},
"creation": {
"keys": {
"available": 4096
},
"stack": 8192,
},
"preload": [
],
}
/**
*/
#include <assert.h>
#include "xsAll.h"
#include "xs.h"
#define KEEP_NEXT 0
#define SKIP_NEXT 1
#pragma GCC diagnostic ignored "-Wunused-parameter"
void Snapshot_prototype_constructor(xsMachine* the)
{
}
void Snapshot_prototype_destructor(xsMachine* the)
{
}
#pragma GCC diagnostic warning "-Wunused-parameter"
static char debug_buf[1024] = { 0 };
static void debug_push(char *format, ...) {
char *start = debug_buf + strlen(debug_buf);
va_list spread;
va_start(spread, format);
vsprintf(start, format, spread);
fprintf(stderr, ">%s\n", debug_buf);
}
static void debug_pop() {
char* last_dot = strrchr(debug_buf, '>');
fprintf(stderr, "<%s\n", debug_buf);
if (last_dot) {
*last_dot = 0;
}
}
/**
* REQUIRES: xsVar(0) is an ArrayBuffer
* ENSURES: buf.length is at least size
*/
static void ensureSpace(xsMachine* the, xsIntegerValue size)
{
txInteger capacity = xsGetArrayBufferLength(xsVar(0));
if (capacity < size) {
xsIntegerValue nextQuantum = (size + 0x100) - (size % 0x100);
// fprintf(stderr, "ensureSpace(%d) grow capacity %d -> %d\n", size, capacity, nextQuantum);
xsSetArrayBufferLength(xsVar(0), nextQuantum);
} else {
// fprintf(stderr, "ensureSpace(%d) capacity %d ok\n", size, capacity);
}
}
/**
* REQUIRES: xsVar(0) is an ArrayBuffer
* ENSURES: buf.slice(offset, qty) == qty bytes at src
*/
static xsIntegerValue append(xsMachine* the, xsIntegerValue offset, void* src, xsIntegerValue qty)
{
// fprintf(stderr, "append(%d += %d)\n", offset, qty);
ensureSpace(the, offset + qty);
xsSetArrayBufferData(xsVar(0), offset, src, qty);
return offset + qty;
}
/** track "seen" slots
* REQUIRES: xsVar(0) is an ArrayBuffer
* xsArg(1) is an Array (exits)
* seen >= xsArg(1).length
* xsVar(1) is available for storing toString() output
* xsVar(2) thru 5 are available (for iterating over exits)
avoid infinite recursion for cyclic structures
Each time we serialize a compound slot, we save either
- the offset where it was already serialized; or
- the delta to the previous compound slot and
the address of this slot
*/
static xsIntegerValue alreadySeen(xsMachine* the, txSlot* target, xsIntegerValue seen) {
char *display = "[?]";
if (target->kind == XS_REFERENCE_KIND && xsHas(*target, xsID("toString"))) {
xsVar(1) = xsCall0(*target, xsID("toString"));
display = xsToString(xsVar(1));
}
fprintf(stderr, "==== alreadySeen(%p) seen=%d %s?\n", target, seen, display);
xsIntegerValue ix = 0;
xsVar(2) = xsEnumerate(xsArg(1));
for (;;) {
xsVar(3) = xsCall0(xsVar(2), xsID("next"));
// ISSUE: warning: operation on ‘the->stack’ may be undefined [-Wsequence-point]
if (xsTest(xsGet(xsVar(3), xsID("done"))))
break;
xsVar(4) = xsGet(xsVar(3), xsID("value"));
xsVar(5) = xsGetAt(xsArg(1), xsVar(4));
// fprintf(stderr, "exits[%d] kind %d ref %p == target %p?\n",
// ix, xsVar(5).kind, fxGetInstance(the, &xsVar(5)), target);
if (fxIsSameSlot(the, &xsVar(5), target)
|| fxGetInstance(the, &xsVar(5)) == target) {
fprintf(stderr, "===== yes %p is exit %d\n", target, ix);
return ix;
}
ix += 1;
}
if (fxIsSameValue(the, target, &mxArrayPrototype, 0)) {
fprintf(stderr, "!! target:%p == Array.prototype\n", target);
}
if (target->kind == XS_INSTANCE_KIND
&& target == mxArrayPrototype.value.reference) {
fprintf(stderr, "Array.prototype already seen! prototype=%p reference=%p\n",
&mxArrayPrototype, mxArrayPrototype.value.reference);
xsSlot e1 = xsGetAt(xsArg(1), xsInteger(0));
fprintf(stderr, "exits[0] kind:%d reference:%p\n", e1.kind, e1.value.reference);
}
xsIntegerValue exitQty = xsToInteger(xsGet(xsArg(1), xsID("length")));
while (seen >= exitQty) {
xsIntegerValue delta;
txSlot* candidate;
xsGetArrayBufferData(xsVar(0), seen + sizeof(delta), &candidate, sizeof(candidate));
if (candidate == target) {
fprintf(stderr, "===== yes seen %p at %d\n", target, seen);
return seen;
}
xsGetArrayBufferData(xsVar(0), seen, &delta, sizeof(delta));
// fprintf(stderr, "=== seen loop: seen:%d exitQty:%d delta:%d\n", seen, exitQty, delta);
seen += delta;
}
fprintf(stderr, "===== no not seen %p exitQty=%d\n", target, exitQty);
return -1;
}
/**
* REQUIRES: xsVar(0) is an ArrayBuffer
*/
static xsIntegerValue pushSelf(xsMachine* the, txSlot* self, xsIntegerValue offset, xsIntegerValue *seen) {
// xsArg(1) == exits
xsIntegerValue exitQty = xsToInteger(xsGet(xsArg(1), xsID("length")));
xsIntegerValue delta = (*seen >= exitQty ? *seen : exitQty) - offset;
fprintf(stderr, "=== pushSelf %p offset %d\n", self, offset);
xsIntegerValue found;
if ((found = alreadySeen(the, self, *seen)) >= 0) {
fprintf(stderr, "==== pushSelf %p; alreadySeen at %d\n", self, found);
offset = append(the, offset, &found, sizeof(found));
return -offset;
}
*seen = offset;
fprintf(stderr, "==== pushSelf(%p) delta=%d (#%ld) *seen=%d offset:%d exitQty:%d\n", self, delta, sizeof(delta), *seen, offset, exitQty);
offset = append(the, offset, &delta, sizeof(delta));
offset = append(the, offset, &self, sizeof(self));
return offset;
}
/**
* REQUIRES: xsVar(0) is an ArrayBuffer
*
* NOTE: conflates null ID name with empty ID name
*/
static xsIntegerValue dumpID(xsMachine* the, xsIntegerValue offset, txID id)
{
offset = append(the, offset, &id, sizeof(id));
txU4 len = 0;
char* value = fxGetKeyName(the, id);
if (value) {
len = strlen(value);
}
offset = append(the, offset, &len, sizeof(len));
if (value) {
offset = append(the, offset, value, len);
}
fprintf(stderr, "==== dumpID=%d %s\n", id, value);
return offset;
}
/**
* REQUIRES: xsVar(0) is an ArrayBuffer
*/
static xsIntegerValue dumpSimpleValue(xsMachine* the, xsIntegerValue offset, txSlot* slot)
{
fprintf(stderr, "== dumpSimpleValue %p kind = %d\n", slot, slot->kind);
switch(slot->kind) {
case XS_UNINITIALIZED_KIND:
case XS_UNDEFINED_KIND:
case XS_NULL_KIND:
// ISSUE: .value = { .number = 0 }?
break;
case XS_BOOLEAN_KIND: {
debug_push(">=%s", slot->value.boolean ? "true" : "false");
offset = append(the, offset, &(slot->value.boolean), sizeof(slot->value.boolean));
debug_pop();
} break;
case XS_INTEGER_KIND: {
debug_push(">=%d", slot->value.integer);
offset = append(the, offset, &(slot->value.integer), sizeof(slot->value.integer));
debug_pop();
} break;
case XS_NUMBER_KIND: {
// ISSUE: assume IEEE double format?
debug_push(">=%f", slot->value.number);
offset = append(the, offset, &(slot->value.number), sizeof(slot->value.number));
debug_pop();
} break;
case XS_STRING_KIND:
case XS_STRING_X_KIND: {
txU4 len = strlen(slot->value.string);
debug_push(">= #%d '%.4s'", len, slot->value.string);
offset = append(the, offset, &len, sizeof(len));
offset = append(the, offset, slot->value.string, len);
debug_pop();
} break;
case XS_SYMBOL_KIND: {
debug_push("> sym:%d", slot->value.symbol);
offset = append(the, offset, &(slot->value.symbol), sizeof(slot->value.symbol));
} break;
/*@@@@
case XS_BIGINT_KIND:
case XS_BIGINT_X_KIND: {
fprintf(file, ".kind = XS_BIGINT_X_KIND}, ");
fprintf(file, ".value = { .bigint = { ");
fprintf(file, ".data = (txU4*)&gxBigIntData[%d], ", linker->bigintSize);
fprintf(file, ".size = %d, ", slot->value.bigint.size);
fprintf(file, ".sign = %d, ", slot->value.bigint.sign);
fprintf(file, " } } ");
c_memcpy(linker->bigintData + linker->bigintSize, slot->value.bigint.data, slot->value.bigint.size * sizeof(txU4));
linker->bigintSize += slot->value.bigint.size;
} break;
*/
default:
fprintf(stderr, "== slot kind not implemented: %d!\n", slot->kind);
assert(0); //
}
return offset;
}
/**
* REQUIRES: xsVar(0) is an ArrayBuffer
*/
// a la fxPrintSlot
// https://github.com/Moddable-OpenSource/moddable/blob/public/xs/tools/xslSlot.c#L946
// ISSUE: addresses are non-deterministic; exposes runtime details. Snapshot object is hence powerful.
// IDEA: consider protobuf? capnproto?
static xsIntegerValue dumpSlot(xsMachine* the, xsIntegerValue offset, txSlot* slot,
xsIntegerValue* seen, txBoolean skip_next)
{
if (!slot) {
txKind null_slot_kind = XS_UNINITIALIZED_KIND - 1;
fprintf(stderr, "= dumpSlot %p\n", slot);
offset = append(the, offset, &null_slot_kind, sizeof(txKind));
return offset;
}
debug_push(">slot: %p kind:%d offset:%d", slot, slot->kind, offset);
offset = append(the, offset, &(slot->kind), sizeof(slot->kind));
if (slot->kind < XS_REFERENCE_KIND) {
offset = append(the, offset, &(slot->flag), sizeof(slot->flag));
offset = dumpID(the, offset, slot->ID);
offset = dumpSimpleValue(the, offset, slot);
if (!skip_next) {
debug_push(">next");
offset = dumpSlot(the, offset, slot->next, seen, KEEP_NEXT);
debug_pop();
}
debug_pop();
return offset;
}
if ((offset = pushSelf(the, slot, offset, seen)) < 0) {
debug_pop();
return -offset;
}
offset = append(the, offset, &(slot->flag), sizeof(slot->flag));
offset = dumpID(the, offset, slot->ID);
switch(slot->kind) {
case XS_REFERENCE_KIND: {
if (slot->value.reference
&& slot->value.reference == mxArrayPrototype.value.reference) {
fprintf(stderr, "found Array.prototype! slot=%p reference=%p\n",
slot, slot->value.reference);
}
if (slot->value.reference
&& slot->value.reference->value.instance.prototype == mxArrayPrototype.value.reference) {
fprintf(stderr, "found Array! slot=%p reference=%p prototype=%p\n",
slot, slot->value.reference, slot->value.reference->value.instance.prototype);
}
debug_push(">reference");
offset = dumpSlot(the, offset, slot->value.reference, seen, KEEP_NEXT);
debug_pop();
} break;
/*
case XS_CLOSURE_KIND: {
fprintf(file, ".kind = XS_CLOSURE_KIND}, ");
fprintf(file, ".value = { .closure = ");
fxPrintAddress(the, file, slot->value.closure);
fprintf(file, " } ");
} break;
*/
case XS_INSTANCE_KIND: {
// ISSUE: fprintf(file, ".value = { .instance = { NULL, ");
debug_push(">prototype");
offset = dumpSlot(the, offset, slot->value.instance.prototype, seen, KEEP_NEXT);
debug_pop();
} break;
case XS_ARRAY_KIND: {
txSlot *item = slot->value.array.address;
txInteger size = (txInteger)fxGetIndexSize(the, slot);
offset = append(the, offset, &size, sizeof(size));
while (size) {
// ISSUE: XS_MARK_FLAG??
debug_push(">item[%d]", size);
offset = dumpSlot(the, offset, item, seen, SKIP_NEXT);
debug_pop();
item++;
size--;
}
skip_next = 1;
} break;
/*
case XS_ARRAY_BUFFER_KIND: {
fprintf(file, ".kind = XS_ARRAY_BUFFER_KIND}, ");
fprintf(file, ".value = { .arrayBuffer = { (txByte*)");
fxWriteCData(file, slot->value.arrayBuffer.address, slot->value.arrayBuffer.length);
fprintf(file, ", %d } } ", (int)slot->value.arrayBuffer.length);
} break;
*/
case XS_CALLBACK_X_KIND:
case XS_CALLBACK_KIND: {
offset = append(the, offset, slot->value.callback.address, sizeof(slot->value.callback.address));
} break;
case XS_CODE_KIND: {
// .code =
{
txChunk* chunk = (txChunk*)(slot->value.code.address - sizeof(txChunk));
offset = append(the, offset, slot->value.code.address, chunk->size - sizeof(txChunk));
}
offset = dumpSlot(the, offset, slot->value.code.closures, seen, KEEP_NEXT);
} break;
case XS_CODE_X_KIND: {
offset = append(the, offset, &slot->value.code.address, sizeof(slot->value.code.address));
offset = dumpSlot(the, offset, slot->value.code.closures, seen, KEEP_NEXT);
} break;
/*
case XS_DATE_KIND: {
fprintf(file, ".kind = XS_DATE_KIND}, ");
fprintf(file, ".value = { .number = ");
fxPrintNumber(the, file, slot->value.number);
fprintf(file, " } ");
} break;
case XS_DATA_VIEW_KIND: {
fprintf(file, ".kind = XS_DATA_VIEW_KIND}, ");
fprintf(file, ".value = { .dataView = { %d, %d } }", slot->value.dataView.offset, slot->value.dataView.size);
} break;
case XS_FINALIZATION_CELL_KIND: {
fprintf(file, ".kind = XS_FINALIZATION_CELL_KIND}, ");
fprintf(file, ".value = { .finalizationCell = { ");
fxPrintAddress(the, file, slot->value.finalizationCell.target);
fprintf(file, ", ");
fxPrintAddress(the, file, slot->value.finalizationCell.token);
fprintf(file, " } }");
} break;
case XS_FINALIZATION_GROUP_KIND: {
fprintf(file, ".kind = XS_FINALIZATION_GROUP_KIND}, ");
fprintf(file, ".value = { .finalizationGroup = { ");
fxPrintAddress(the, file, slot->value.finalizationGroup.callback);
fprintf(file, ", %d } }", slot->value.finalizationGroup.flags);
} break;
case XS_GLOBAL_KIND: {
fprintf(file, ".kind = XS_GLOBAL_KIND}, ");
fprintf(file, ".value = { .table = { NULL, 0 } }");
} break;
case XS_HOST_KIND: {
fprintf(file, ".kind = XS_HOST_KIND}, ");
fprintf(file, ".value = { .host = { NULL, { .destructor = %s } } }", fxGetCallbackName(the, (txCallback)slot->value.host.variant.destructor ));
} break;
case XS_MAP_KIND: {
fprintf(file, ".kind = XS_MAP_KIND}, ");
fprintf(file, ".value = { .table = { (txSlot**)&gxSlotData[%d], %d } }", linker->slotSize, slot->value.table.length);
c_memcpy(linker->slotData + linker->slotSize, slot->value.table.address, slot->value.table.length * sizeof(txSlot*));
linker->slotSize += slot->value.table.length;
} break;
case XS_MODULE_KIND: {
fprintf(file, ".kind = XS_MODULE_KIND}, ");
fprintf(file, ".value = { .module = { ");
fxPrintAddress(the, file, slot->value.module.realm);
fprintf(file, ", %d } }", slot->value.module.id);
} break;
case XS_PROMISE_KIND: {
fprintf(file, ".kind = XS_PROMISE_KIND}, ");
fprintf(file, ".value = { .integer = %d } ", slot->value.integer);
} break;
case XS_PROXY_KIND: {
fprintf(file, ".kind = XS_PROXY_KIND}, ");
fprintf(file, ".value = { .instance = { ");
fxPrintAddress(the, file, slot->value.proxy.handler);
fprintf(file, ", ");
fxPrintAddress(the, file, slot->value.proxy.target);
fprintf(file, " } } ");
} break;
case XS_REGEXP_KIND: {
fprintf(file, ".kind = XS_REGEXP_KIND}, ");
fprintf(file, ".value = { .regexp = { (txInteger*)NULL, (txInteger*)NULL } } ");
} break;
case XS_SET_KIND: {
fprintf(file, ".kind = XS_SET_KIND}, ");
fprintf(file, ".value = { .table = { (txSlot**)&gxSlotData[%d], %d } }", linker->slotSize, slot->value.table.length);
c_memcpy(linker->slotData + linker->slotSize, slot->value.table.address, slot->value.table.length * sizeof(txSlot*));
linker->slotSize += slot->value.table.length;
} break;
case XS_TYPED_ARRAY_KIND: {
fprintf(file, ".kind = XS_TYPED_ARRAY_KIND}, ");
fprintf(file, ".value = { .typedArray = { (txTypeDispatch*)(&gxTypeDispatches[%d]), (txTypeAtomics*)(&gxTypeAtomics[%d]) } }", fxGetTypeDispatchIndex(slot->value.typedArray.dispatch), fxGetTypeAtomicsIndex(slot->value.typedArray.atomics));
} break;
case XS_WEAK_MAP_KIND: {
fprintf(file, ".kind = XS_WEAK_MAP_KIND}, ");
fprintf(file, ".value = { .table = { (txSlot**)&gxSlotData[%d], %d } }", linker->slotSize, slot->value.table.length);
c_memcpy(linker->slotData + linker->slotSize, slot->value.table.address, (slot->value.table.length + 1) * sizeof(txSlot*));
linker->slotSize += slot->value.table.length + 1;
} break;
case XS_WEAK_REF_KIND: {
fprintf(file, ".kind = XS_WEAK_REF_KIND}, ");
fprintf(file, ".value = { .weakRef = { ");
fxPrintAddress(the, file, slot->value.weakRef.target);
fprintf(file, ", ");
fxPrintAddress(the, file, slot->value.weakRef.link);
fprintf(file, " } }");
} break;
case XS_WEAK_SET_KIND: {
fprintf(file, ".kind = XS_WEAK_SET_KIND}, ");
fprintf(file, ".value = { .table = { (txSlot**)&gxSlotData[%d], %d } }", linker->slotSize, slot->value.table.length);
c_memcpy(linker->slotData + linker->slotSize, slot->value.table.address, (slot->value.table.length + 1) * sizeof(txSlot*));
linker->slotSize += slot->value.table.length + 1;
} break;
*/
case XS_ACCESSOR_KIND: {
debug_push(">getter");
offset = dumpSlot(the, offset, slot->value.accessor.getter, seen, KEEP_NEXT);
debug_pop();
debug_push(">setter");
offset = dumpSlot(the, offset, slot->value.accessor.setter, seen, KEEP_NEXT);
debug_pop();
} break;
/*
case XS_AT_KIND: {
fprintf(file, ".kind = XS_AT_KIND}, ");
fprintf(file, ".value = { .at = { 0x%x, %d } }", slot->value.at.index, slot->value.at.id);
} break;
case XS_ENTRY_KIND: {
fprintf(file, ".kind = XS_ENTRY_KIND}, ");
fprintf(file, ".value = { .entry = { ");
fxPrintAddress(the, file, slot->value.entry.slot);
fprintf(file, ", 0x%x } }", slot->value.entry.sum);
} break;
case XS_ERROR_KIND: {
fprintf(file, ".kind = XS_ERROR_KIND}, ");
fprintf(file, ".value = { .number = 0 } ");
} break;
*/
case XS_HOME_KIND: {
debug_push(">home-object");
offset = dumpSlot(the, offset, slot->value.home.object, seen, KEEP_NEXT);
debug_pop();
debug_push(">home-module");
offset = dumpSlot(the, offset, slot->value.home.module, seen, KEEP_NEXT);
debug_pop();
} break;
/*
case XS_EXPORT_KIND: {
fprintf(file, ".kind = XS_EXPORT_KIND}, ");
fprintf(file, ".value = { .export = { ");
fxPrintAddress(the, file, slot->value.export.closure);
fprintf(file, ", ");
fxPrintAddress(the, file, slot->value.export.module);
fprintf(file, " } }");
} break;
case XS_KEY_KIND:
case XS_KEY_X_KIND: {
fprintf(file, ".kind = XS_KEY_X_KIND}, ");
fprintf(file, ".value = { .key = { ");
fxWriteCString(file, slot->value.key.string);
fprintf(file, ", 0x%x } }", slot->value.key.sum);
} break;
case XS_LIST_KIND: {
fprintf(file, ".kind = XS_LIST_KIND}, ");
fprintf(file, ".value = { .list = { ");
fxPrintAddress(the, file, slot->value.list.first);
fprintf(file, ", ");
fxPrintAddress(the, file, slot->value.list.last);
fprintf(file, " } }");
} break;
case XS_PRIVATE_KIND: {
fprintf(file, ".kind = XS_PRIVATE_KIND}, ");
fprintf(file, ".value = { .private = { ");
fxPrintAddress(the, file, slot->value.private.check);
fprintf(file, ", ");
fxPrintAddress(the, file, slot->value.private.first);
fprintf(file, " } }");
} break;
case XS_STACK_KIND: {
fprintf(file, ".kind = XS_STACK_KIND}, ");
} break;
#ifdef mxHostFunctionPrimitive
case XS_HOST_FUNCTION_KIND: {
fprintf(file, ".kind = XS_HOST_FUNCTION_KIND}, ");
fprintf(file, ".value = { .hostFunction = { %s, NULL } }", fxGetBuilderName(the, slot->value.hostFunction.builder));
} break;
#endif
*/
default:
fprintf(stderr, "== slot kind not implemented: %d!\n", slot->kind);
assert(0); //
}
if (!skip_next) {
debug_push(">next");
offset = dumpSlot(the, offset, slot->next, seen, KEEP_NEXT);
debug_pop();
}
debug_pop();
return offset;
}
void Snapshot_prototype_dump(xsMachine* the)
{
if (xsToInteger(xsArgc) != 2) {
mxTypeError("expected 2 arguments");
}
xsSlot root = xsArg(0);
// xsArg(1) = exits
if (!xsIsInstanceOf(xsArg(1), xsArrayPrototype)) {
mxTypeError("expected array");
}
xsSlot exit0 = xsGetAt(xsArg(1), xsInteger(0));
fprintf(stderr, "exits[0] type:%d kind:%d reference:%p\n", xsTypeOf(exit0), exit0.kind, exit0.value.reference);
xsIntegerValue exitQty = xsToInteger(xsGet(xsArg(1), xsID("length")));
xsIntegerValue seen = exitQty - 1; // IDEA: use xsVars() for seen too
xsVars(6);
// xsVar(0) is snapshot serialization
xsVar(0) = xsArrayBuffer(NULL, exitQty);
// xsVar(1) is for seen.toString() temp space
// xsVar(2) thru 5 are for enumerating exits
debug_push(">root");
xsIntegerValue size = dumpSlot(the, exitQty, (txSlot*)&root, // ISSUE: xsSlot -> txSlot???
&seen, KEEP_NEXT);
debug_pop();
fprintf(stderr, "dump: xsSetArrayBufferLength(size=%d)\n", size);
xsSetArrayBufferLength(xsVar(0), size);
xsResult = xsVar(0);
}
/* global trace */
function tohex(buffer, limit) {
if (typeof limit === 'number') {
buffer = buffer.slice(0, limit);
}
return Array.prototype.map.call(buffer, x => ('00' + x.toString(16)).slice(-2)).join('');
}
export class Snapshot @ "Snapshot_prototype_destructor" {
constructor() @ "Snapshot_prototype_constructor";
dump(root, exits) @ "Snapshot_prototype_dump";
tohex(rawbuf, limit) {
return tohex(new Uint8Array(rawbuf), limit);
}
restore(rawbuf, exitQty) {
const alldata = new Uint8Array(rawbuf);
let data = alldata.slice(exitQty);
function go(qty, label) {
const used = data.slice(0, qty);
data = data.slice(qty);
trace(`go(${qty}, ${label}) ${tohex(used)} => ${tohex(data, 16)}\n`);
}
// IDEA/TODO: use DataView.getUint8 etc.
const u8 = (label) => {
const x = data[0];
go(1, label);
return x;
};
const i8 = (label) => {
const u = u8(label);
return u >= 0x80 ? (-1 - ((~u) & 0x7f)) : u;
};
const u16 = (label) => {
const x = data[0] + data[1] * 0x100;
go(2, label);
return x;
};
const i16 = (label) => {
const u = u16(label);
return u >= 0x8000 ? (-1 - ((~u) & 0x7fff)) : u;
};
const u32 = (label) => {
const x = data[0] + 0x100 * (data[1] + 0x100 * (data[2] + 0x100 * data[3]));
go(4, label);
return x;
};
const i32 = (label) => {
const u = u32(label);
// trace(`i32: ${u >= 0x80000000} ? ${(~u - 1)} : ${u}\n`);
return u >= 0x80000000 ? (-1 - ((~u) & 0x7fffffff)) : u;
};
const chars = (label) => {
const len = u32('len');
const s = [...data.slice(0, len)].map(b => String.fromCharCode(b)).join('');
go(len, label);
return s;
};
const u64 = data => data[0] + 0x100 * (data[1] + 0x100 *
(data[2] + 0x100 * (data[3] + 0x100 *
(data[4] + 0x100 * (data[5] + 0x100 *
(data[6] + 0x100 * data[7]))))));
const u64go = (label) => {
const x = u64(data);
go(8, label);
return x;
};
function flagid() {
const flag = u8('flag');
const id = i16('id');
const idname = chars('id name') || null;
trace(`flagid ${flag} ${id} ${idname}\n`);
return { flag, id, idname };
}
function slot(skip_next = false) {
const kind = i8('kind');
trace(`slot kind: ${kind} skip_next:${skip_next}\n`);
if (kind == -2) {
return null; // NULL
}
let value;
if (kind < 10) { // XS_REFERENCE_KIND; i.e. simple
const { flag, id, idname } = flagid();
switch (kind) {
case 0: // XS_UNDEFINED_KIND
value = undefined;
break;
case 1: // XS_NULL_KIND
value = null;
break;
case 2: // XS_BOOLEAN_KIND
value = !!u32('bool');
break;
case 3: // XS_INTEGER_KIND
value = i32('int');
break;
case 5: // XS_STRING_KIND
case 6: // XS_STRING_X_KIND
value = chars('string');
break;
default:
// TODO: Symbol, BigInt, ...
throw new RangeError(kind);
}
const next = skip_next ? null : slot();
return { kind, flag, id, idname, value, next };
}
const delta = i32('delta'); // txIntegerValue is 32bits
let self;
if (delta > 0) {
self = u64(alldata.slice(delta, delta + 8));
trace(`seen slot: ${delta}, ${self.toString(16).toLowerCase()}\n`);
return delta <= exitQty ? { exit: delta } : { self, delta };
} else {
self = u64go('self');
trace(`fresh slot: ${delta}, ${self.toString(16).toLowerCase()}\n`);
}
const { flag, id, idname } = flagid();
switch (kind) {
case 10: // XS_REFERENCE_KIND
const reference = slot();
value = { reference };
break;
case 13: // XS_INSTANCE_KIND
const prototype = slot();
value = { garbage: null, prototype };
break;
case 16: // XS_ARRAY_KIND
let size = u32('array size');
value = [];
while (size > 0) {
trace(`array items to do: ${size}\n`);
value.push(slot(true));
size -= 1;
}
skip_next = true;
break;
case 20: // XS_CODE_X_KIND
const address = u64go('code.address');
const closures = slot();
value = { address, closures };
break;
case 37: // XS_ACCESSOR_KIND
const getter = slot();
const setter = slot();
value = { getter, setter };
break;
case 41: // XS_HOME_KIND
const object = slot();
const module = slot();
value = { object, module };
break;
case 48: // XS_CALLBACK_X_KIND
const cb_addr = u64go('callback.address');
value = { address: cb_addr };
break;
default:
// TODO: lots
throw new RangeError(kind);
}
const next = skip_next ? null : slot();
trace(`compound: ${JSON.stringify({ kind, self, flag, id, idname, value, next }, null, 2)}\n`);
return { kind, self, flag, id, idname, value, next };
}
return slot();
}
}
/* global Compartment, trace */
trace(`in vat1 module\n`);
@dckc
Copy link
Author

dckc commented Feb 26, 2020

Now that this is starting to work, I'm migrating to agoric-labs/moddable#2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment