@daugasauron/dolly 0.1.4-rc.3 → 0.1.5-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -14
- package/coi-serviceworker.js +28 -0
- package/dist/dolly-build-id.mjs +1 -1
- package/dist/dolly-dso-0.wasm +0 -0
- package/dist/dolly-image-build-id.mjs +1 -1
- package/dist/dolly-process-0.wasm +0 -0
- package/dist/dolly-process-abi.mjs +1 -1
- package/dist/dolly-process-dso.mjs +382 -53
- package/dist/dolly-process-gate-0.wasm +0 -0
- package/dist/dolly-process-worker.mjs +164 -35
- package/dist/dolly.wasm +0 -0
- package/host/dso/abi.mjs +3 -1
- package/host/dso/dso.mjs +1 -2
- package/host/http/abi.mjs +2 -2
- package/host/http/broker.mjs +18 -8
- package/host/http/http.mjs +0 -1
- package/host/http/policy.mjs +8 -4
- package/host/input/abi.mjs +47 -0
- package/host/modules.mjs +4 -3
- package/host/nena/abi.mjs +1 -1
- package/host/nena/nena.mjs +16 -18
- package/host/packages/service.mjs +35 -15
- package/host/packages/worker.mjs +21 -0
- package/host/runtime/abi.mjs +1 -0
- package/host/runtime/boot-files.mjs +8 -2
- package/host/runtime/grower.mjs +11 -8
- package/host/runtime/lease-bridge.mjs +8 -5
- package/host/runtime/module.json +1 -1
- package/host/runtime/runtime.mjs +37 -18
- package/host/snapshot/abi.mjs +7 -9
- package/host/snapshot/snapshot.mjs +14 -12
- package/host/snapshot/transport.mjs +4 -12
- package/host/snapshot/ui.mjs +17 -7
- package/host/stack/stack.mjs +1 -2
- package/host/threads/abi.mjs +1 -1
- package/host/threads/threads.mjs +2 -1
- package/package.json +1 -1
- package/src/browser.mjs +39 -40
- package/src/build-log.mjs +13 -2
- package/src/build-worker.mjs +182 -0
- package/src/custom-image.mjs +59 -0
- package/src/dollyfile-graph.mjs +127 -0
- package/src/dollyfile-view.mjs +335 -0
- package/src/image-artifact.mjs +248 -0
- package/src/image-build.mjs +89 -0
- package/src/image-builder.mjs +63 -0
- package/src/images.mjs +13 -2
- package/src/pack-store.mjs +141 -0
- package/src/packaged-snapshot.mjs +263 -91
- package/src/page-chords.mjs +4 -2
- package/src/process-constants.mjs +4 -2
- package/src/process-supervisor.mjs +209 -122
- package/src/runtime-worker.mjs +14 -12
- package/src/session-store.mjs +5 -7
- package/src/sha256.mjs +65 -0
- package/src/snapshot-records.mjs +135 -23
- package/src/static-asset.mjs +29 -2
- package/src/terminal-text.mjs +8 -5
- package/src/version.mjs +1 -1
- package/src/worker-image.mjs +3 -5
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
// dist/dolly-process-abi.mjs
|
|
2
|
-
var DOLLY_PROCESS_ABI_DIGEST = "
|
|
2
|
+
var DOLLY_PROCESS_ABI_DIGEST = "36d9bdf963bcc094b53c3b7e44f244aeb2e9213d99a729a90fd01b2f9a368c5f";
|
|
3
3
|
|
|
4
4
|
// host/dso/abi.mjs
|
|
5
5
|
var DOLLY_DSO_OPEN = 112;
|
|
6
6
|
var DOLLY_DSO_SYMBOL = 113;
|
|
7
7
|
var DOLLY_DSO_CLOSE = 114;
|
|
8
|
+
var DOLLY_DSO_WAIT = 115;
|
|
8
9
|
var DOLLY_DSO_GLOBAL = 1;
|
|
9
10
|
var DOLLY_DSO_LIMIT = 536870912;
|
|
10
11
|
var DOLLY_DSO_ERROR_CAPACITY = 240;
|
|
12
|
+
var DOLLY_DSO_THREADS_SIZE = 272;
|
|
11
13
|
var DOLLY_FFI_CALL = 120;
|
|
12
14
|
var DOLLY_FFI_CLOSURE_ALLOC = 121;
|
|
13
15
|
var DOLLY_FFI_CLOSURE_FREE = 122;
|
|
@@ -387,9 +389,10 @@ var DOLLY_HOST_MAX_RECORDS = 64;
|
|
|
387
389
|
// src/snapshot-records.mjs
|
|
388
390
|
var decoder2 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
389
391
|
var MAX_SNAPSHOT_BYTES = 2 * 1024 * 1024 * 1024;
|
|
392
|
+
var MAX_SNAPSHOT_PACKS = MAX_SNAPSHOT_BYTES / (1024 * 1024);
|
|
390
393
|
|
|
391
394
|
// src/version.mjs
|
|
392
|
-
var DOLLY_VERSION = "0.1.
|
|
395
|
+
var DOLLY_VERSION = "0.1.5";
|
|
393
396
|
|
|
394
397
|
// src/static-asset.mjs
|
|
395
398
|
var STATIC_PART_BYTES = 20 * 1024 * 1024;
|
|
@@ -588,7 +591,8 @@ function createProcessFfi({
|
|
|
588
591
|
getTable,
|
|
589
592
|
growTable: growTable2,
|
|
590
593
|
setTable: setTable2,
|
|
591
|
-
castEmulationParameters: castEmulationParameters2 = 0
|
|
594
|
+
castEmulationParameters: castEmulationParameters2 = 0,
|
|
595
|
+
change = (operation, values, apply) => apply()
|
|
592
596
|
}) {
|
|
593
597
|
if (!(memory instanceof WebAssembly.Memory) || typeof getInstance !== "function" || typeof getTable !== "function" || typeof growTable2 !== "function" || typeof setTable2 !== "function") {
|
|
594
598
|
throw new TypeError("invalid process FFI configuration");
|
|
@@ -1089,47 +1093,62 @@ function createProcessFfi({
|
|
|
1089
1093
|
return returnByArgument ? void 0 : callbackResult(returnPointer, cif.returnType);
|
|
1090
1094
|
}, parameters, result);
|
|
1091
1095
|
}
|
|
1096
|
+
const closureChanges = {
|
|
1097
|
+
[DOLLY_FFI_CLOSURE_ALLOC](closure) {
|
|
1098
|
+
address(closure, 32, "FFI closure");
|
|
1099
|
+
if (closures.has(closure)) throw new TypeError("FFI closure is allocated");
|
|
1100
|
+
const index = freeTableIndices.length === 0 ? growTable2(1n) : freeTableIndices.pop();
|
|
1101
|
+
closures.set(closure, { index, prepared: false });
|
|
1102
|
+
return index;
|
|
1103
|
+
},
|
|
1104
|
+
[DOLLY_FFI_CLOSURE_FREE](closure) {
|
|
1105
|
+
const record = closures.get(closure);
|
|
1106
|
+
if (!record) throw new TypeError("FFI closure is not allocated");
|
|
1107
|
+
setTable2(record.index, null);
|
|
1108
|
+
freeTableIndices.push(record.index);
|
|
1109
|
+
closures.delete(closure);
|
|
1110
|
+
return record.index;
|
|
1111
|
+
},
|
|
1112
|
+
[DOLLY_FFI_CLOSURE_PREP](closure, cifPointer, callbackIndex, userData, code) {
|
|
1113
|
+
const record = closures.get(closure);
|
|
1114
|
+
if (!record || code !== record.index) throw new TypeError("FFI closure is not allocated");
|
|
1115
|
+
setTable2(record.index, prepareClosure(closure, cifInfo(cifPointer), callbackIndex, userData));
|
|
1116
|
+
record.prepared = true;
|
|
1117
|
+
return record.index;
|
|
1118
|
+
}
|
|
1119
|
+
};
|
|
1120
|
+
function changeClosure(operation, values) {
|
|
1121
|
+
return change(operation, values, () => closureChanges[operation](...values));
|
|
1122
|
+
}
|
|
1092
1123
|
function allocateClosure(request, response) {
|
|
1093
1124
|
if (request.size !== 8 || response.size < 8) return -BigInt(DOLLY_ERRNO.EINVAL);
|
|
1094
1125
|
const closure = view(request.address, 8, "FFI closure allocation request").getBigUint64(0, true);
|
|
1095
|
-
|
|
1096
|
-
if (closures.has(closure)) return -BigInt(DOLLY_ERRNO.EINVAL);
|
|
1097
|
-
const index = freeTableIndices.length === 0 ? growTable2(1n) : freeTableIndices.pop();
|
|
1098
|
-
closures.set(closure, { index, prepared: false });
|
|
1126
|
+
const index = changeClosure(DOLLY_FFI_CLOSURE_ALLOC, [closure]);
|
|
1099
1127
|
view(response.address, 8, "FFI closure allocation response").setBigUint64(0, index, true);
|
|
1100
1128
|
return 8n;
|
|
1101
1129
|
}
|
|
1102
1130
|
function freeClosure(request) {
|
|
1103
1131
|
if (request.size !== 8) return -BigInt(DOLLY_ERRNO.EINVAL);
|
|
1104
|
-
|
|
1105
|
-
const record = closures.get(closure);
|
|
1106
|
-
if (!record) return -BigInt(DOLLY_ERRNO.EINVAL);
|
|
1107
|
-
setTable2(record.index, null);
|
|
1108
|
-
freeTableIndices.push(record.index);
|
|
1109
|
-
closures.delete(closure);
|
|
1132
|
+
changeClosure(DOLLY_FFI_CLOSURE_FREE, [view(request.address, 8, "FFI closure free request").getBigUint64(0, true)]);
|
|
1110
1133
|
return 0n;
|
|
1111
1134
|
}
|
|
1112
1135
|
function configureClosure(request) {
|
|
1113
1136
|
if (request.size !== 40) return -BigInt(DOLLY_ERRNO.EINVAL);
|
|
1114
1137
|
const packet = view(request.address, 40, "FFI closure preparation request");
|
|
1115
|
-
const
|
|
1116
|
-
const cifPointer =
|
|
1117
|
-
const
|
|
1118
|
-
|
|
1119
|
-
const code = packet.getBigUint64(32, true);
|
|
1120
|
-
const record = closures.get(closure);
|
|
1121
|
-
if (!record || code !== record.index) return -BigInt(DOLLY_ERRNO.EINVAL);
|
|
1122
|
-
const cif = cifInfo(cifPointer);
|
|
1123
|
-
const wrapper = prepareClosure(closure, cif, callbackIndex, userData);
|
|
1124
|
-
setTable2(record.index, wrapper);
|
|
1125
|
-
writePointer(closure, 0, record.index);
|
|
1138
|
+
const values = [0, 8, 16, 24, 32].map((offset) => packet.getBigUint64(offset, true));
|
|
1139
|
+
const [closure, cifPointer, callbackIndex, userData] = values;
|
|
1140
|
+
const index = changeClosure(DOLLY_FFI_CLOSURE_PREP, values);
|
|
1141
|
+
writePointer(closure, 0, index);
|
|
1126
1142
|
writePointer(closure, 8, cifPointer);
|
|
1127
1143
|
writePointer(closure, 16, callbackIndex);
|
|
1128
1144
|
writePointer(closure, 24, userData);
|
|
1129
|
-
record.prepared = true;
|
|
1130
1145
|
return BigInt(FFI_OK);
|
|
1131
1146
|
}
|
|
1132
1147
|
return Object.freeze({
|
|
1148
|
+
// Repeats another thread's closure change: the table index it gave.
|
|
1149
|
+
repeat(operation, values) {
|
|
1150
|
+
return closureChanges[operation](...values);
|
|
1151
|
+
},
|
|
1133
1152
|
handles(operation) {
|
|
1134
1153
|
return localOperations.has(operation);
|
|
1135
1154
|
},
|
|
@@ -1210,7 +1229,7 @@ function validateDsoInterface(contract, dso, digest) {
|
|
|
1210
1229
|
throw new TypeError(`shared-object import is outside the process namespace: ${entry.module}.${entry.name}`);
|
|
1211
1230
|
}
|
|
1212
1231
|
}
|
|
1213
|
-
for (const name of ["__wasm_apply_data_relocs", "__wasm_call_ctors"]) {
|
|
1232
|
+
for (const name of ["__wasm_apply_data_relocs", "__wasm_call_ctors", "__wasm_init_tls", "__tls_size", "__tls_align"]) {
|
|
1214
1233
|
if (exports.has(name)) requireDsoType(
|
|
1215
1234
|
exports.get(name).type,
|
|
1216
1235
|
contract.exports.find((entry) => entry.name === name).type,
|
|
@@ -1220,8 +1239,173 @@ function validateDsoInterface(contract, dso, digest) {
|
|
|
1220
1239
|
return { imports, exports };
|
|
1221
1240
|
}
|
|
1222
1241
|
|
|
1242
|
+
// host/dso/threads.mjs
|
|
1243
|
+
var LOCK = 0;
|
|
1244
|
+
var COUNT = 1;
|
|
1245
|
+
var RECORDS = 1;
|
|
1246
|
+
var CAPACITY = 4;
|
|
1247
|
+
var SLOTS = 5;
|
|
1248
|
+
var SLOT_COUNT = DOLLY_DSO_THREADS_SIZE / 4 - SLOTS;
|
|
1249
|
+
var TAKEN = 1 << 31;
|
|
1250
|
+
var WAITING = 1 << 30;
|
|
1251
|
+
var APPLIED = WAITING - 1;
|
|
1252
|
+
var RECORD_SIZE = 48;
|
|
1253
|
+
var FIRST_CAPACITY = 64;
|
|
1254
|
+
var ThreadRecordError = class extends Error {
|
|
1255
|
+
};
|
|
1256
|
+
function createThreadRecords({ memory, address, allocate, apply }) {
|
|
1257
|
+
let words, bigWords;
|
|
1258
|
+
function view() {
|
|
1259
|
+
if (words?.buffer !== memory.buffer) {
|
|
1260
|
+
words = new Int32Array(memory.buffer, Number(address), DOLLY_DSO_THREADS_SIZE / 4);
|
|
1261
|
+
bigWords = new BigInt64Array(memory.buffer, Number(address), 2);
|
|
1262
|
+
}
|
|
1263
|
+
return words;
|
|
1264
|
+
}
|
|
1265
|
+
let slot = -1, applied = 0, waiting = true, applying = false, locks = 0;
|
|
1266
|
+
for (let index = 0; slot < 0 && index < SLOT_COUNT; ++index) {
|
|
1267
|
+
if (Atomics.compareExchange(view(), SLOTS + index, 0, TAKEN | WAITING) === 0) slot = index;
|
|
1268
|
+
}
|
|
1269
|
+
if (slot < 0) throw new RangeError("dso@0 has no room for another thread");
|
|
1270
|
+
function publishState() {
|
|
1271
|
+
Atomics.store(view(), SLOTS + slot, TAKEN | (waiting ? WAITING : 0) | applied);
|
|
1272
|
+
Atomics.notify(view(), SLOTS + slot);
|
|
1273
|
+
}
|
|
1274
|
+
function recordView(array, index) {
|
|
1275
|
+
return new DataView(memory.buffer, Number(array) + index * RECORD_SIZE, RECORD_SIZE);
|
|
1276
|
+
}
|
|
1277
|
+
function readRecord(array, index) {
|
|
1278
|
+
const data = recordView(array, index);
|
|
1279
|
+
return {
|
|
1280
|
+
kind: data.getUint32(0, true),
|
|
1281
|
+
word: data.getUint32(4, true),
|
|
1282
|
+
values: [8, 16, 24, 32, 40].map((offset) => data.getBigUint64(offset, true))
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
function synchronize() {
|
|
1286
|
+
if (applying) return;
|
|
1287
|
+
applying = true;
|
|
1288
|
+
try {
|
|
1289
|
+
for (let count; applied < (count = Atomics.load(view(), COUNT)); ) {
|
|
1290
|
+
const array = Atomics.load(bigWords, RECORDS);
|
|
1291
|
+
const records = Array.from({ length: count - applied }, (_, index) => readRecord(array, applied + index));
|
|
1292
|
+
records.forEach((_, index) => {
|
|
1293
|
+
try {
|
|
1294
|
+
apply(records, index);
|
|
1295
|
+
} catch (error) {
|
|
1296
|
+
throw error instanceof ThreadRecordError ? error : new ThreadRecordError(`dso@0: this thread could not make another's change: ${error.message}`, { cause: error });
|
|
1297
|
+
}
|
|
1298
|
+
++applied;
|
|
1299
|
+
publishState();
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
} finally {
|
|
1303
|
+
applying = false;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
function enterHost() {
|
|
1307
|
+
waiting = true;
|
|
1308
|
+
publishState();
|
|
1309
|
+
}
|
|
1310
|
+
function leaveHost() {
|
|
1311
|
+
waiting = false;
|
|
1312
|
+
publishState();
|
|
1313
|
+
synchronize();
|
|
1314
|
+
}
|
|
1315
|
+
function awaitApplied(target) {
|
|
1316
|
+
enterHost();
|
|
1317
|
+
for (let index = 0; index < SLOT_COUNT; ++index) {
|
|
1318
|
+
if (index === slot) continue;
|
|
1319
|
+
for (; ; ) {
|
|
1320
|
+
const state = Atomics.load(view(), SLOTS + index);
|
|
1321
|
+
if (!(state & TAKEN) || state & WAITING || (state & APPLIED) >= target) break;
|
|
1322
|
+
Atomics.wait(view(), SLOTS + index, state);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
leaveHost();
|
|
1326
|
+
}
|
|
1327
|
+
function reserve() {
|
|
1328
|
+
const count = Atomics.load(view(), COUNT), current = view()[CAPACITY];
|
|
1329
|
+
if (count < current) return;
|
|
1330
|
+
const capacity = current === 0 ? FIRST_CAPACITY : current * 2;
|
|
1331
|
+
const grown = allocate(BigInt(capacity * RECORD_SIZE), 8n);
|
|
1332
|
+
if (!grown) throw new RangeError("process could not reserve dso@0's thread records");
|
|
1333
|
+
if (count !== 0) {
|
|
1334
|
+
new Uint8Array(memory.buffer, Number(grown), count * RECORD_SIZE).set(new Uint8Array(memory.buffer, Number(Atomics.load(bigWords, RECORDS)), count * RECORD_SIZE));
|
|
1335
|
+
}
|
|
1336
|
+
Atomics.store(bigWords, RECORDS, grown);
|
|
1337
|
+
view()[CAPACITY] = capacity;
|
|
1338
|
+
}
|
|
1339
|
+
function publish({ kind, word = 0, values }, acknowledged) {
|
|
1340
|
+
const count = Atomics.load(view(), COUNT);
|
|
1341
|
+
const data = recordView(Atomics.load(bigWords, RECORDS), count);
|
|
1342
|
+
data.setUint32(0, kind, true);
|
|
1343
|
+
data.setUint32(4, word, true);
|
|
1344
|
+
values.forEach((value, index) => data.setBigUint64(8 + index * 8, value, true));
|
|
1345
|
+
applied = count + 1;
|
|
1346
|
+
publishState();
|
|
1347
|
+
Atomics.store(view(), COUNT, applied);
|
|
1348
|
+
if (acknowledged) awaitApplied(applied);
|
|
1349
|
+
}
|
|
1350
|
+
function lock() {
|
|
1351
|
+
if (locks++) return;
|
|
1352
|
+
const owner = slot + 1;
|
|
1353
|
+
if (Atomics.compareExchange(view(), LOCK, 0, owner) === 0) return;
|
|
1354
|
+
enterHost();
|
|
1355
|
+
for (let holder; (holder = Atomics.compareExchange(view(), LOCK, 0, owner)) !== 0; ) {
|
|
1356
|
+
Atomics.wait(view(), LOCK, holder);
|
|
1357
|
+
}
|
|
1358
|
+
leaveHost();
|
|
1359
|
+
}
|
|
1360
|
+
function unlock() {
|
|
1361
|
+
if (--locks) return;
|
|
1362
|
+
Atomics.store(view(), LOCK, 0);
|
|
1363
|
+
Atomics.notify(view(), LOCK, 1);
|
|
1364
|
+
}
|
|
1365
|
+
return {
|
|
1366
|
+
synchronize,
|
|
1367
|
+
// run(publish) changes this Worker's table and publishes the record of
|
|
1368
|
+
// the change; one change at a time in the process.
|
|
1369
|
+
change(run) {
|
|
1370
|
+
lock();
|
|
1371
|
+
try {
|
|
1372
|
+
synchronize();
|
|
1373
|
+
reserve();
|
|
1374
|
+
return run(publish);
|
|
1375
|
+
} finally {
|
|
1376
|
+
unlock();
|
|
1377
|
+
}
|
|
1378
|
+
},
|
|
1379
|
+
// A wait for the host's answer, during which no change waits for this
|
|
1380
|
+
// thread: it applies them before it returns.
|
|
1381
|
+
awaitHost(run) {
|
|
1382
|
+
enterHost();
|
|
1383
|
+
const result = run();
|
|
1384
|
+
leaveHost();
|
|
1385
|
+
return result;
|
|
1386
|
+
},
|
|
1387
|
+
// A futex wait on the 32-bit word at address, or none: Atomics.wait's outcome.
|
|
1388
|
+
wait(wordAddress, value, milliseconds) {
|
|
1389
|
+
if (wordAddress === 0) {
|
|
1390
|
+
leaveHost();
|
|
1391
|
+
return "ok";
|
|
1392
|
+
}
|
|
1393
|
+
enterHost();
|
|
1394
|
+
const outcome = Atomics.wait(new Int32Array(memory.buffer, wordAddress, 1), 0, value, milliseconds);
|
|
1395
|
+
leaveHost();
|
|
1396
|
+
return outcome;
|
|
1397
|
+
},
|
|
1398
|
+
// The thread ends: no change waits for it again.
|
|
1399
|
+
leaving() {
|
|
1400
|
+
Atomics.store(view(), SLOTS + slot, 0);
|
|
1401
|
+
Atomics.notify(view(), SLOTS + slot);
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1223
1406
|
// host/dso/process.mjs
|
|
1224
1407
|
var DSO_RESPONSE_SIZE = 16 + DOLLY_DSO_ERROR_CAPACITY;
|
|
1408
|
+
var LOAD_FAILED = 2147483648;
|
|
1225
1409
|
var encoder = new TextEncoder();
|
|
1226
1410
|
var decoder3 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
1227
1411
|
var configuration;
|
|
@@ -1233,6 +1417,8 @@ var nextDsoHandle = 2n;
|
|
|
1233
1417
|
var loadedDsos = /* @__PURE__ */ new Map();
|
|
1234
1418
|
var globalDsos = [];
|
|
1235
1419
|
var functionIndices = /* @__PURE__ */ new WeakMap();
|
|
1420
|
+
var threadRecords;
|
|
1421
|
+
var ffi;
|
|
1236
1422
|
function dylinkRequirements(module) {
|
|
1237
1423
|
const sections = WebAssembly.Module.customSections(module, "dylink.0");
|
|
1238
1424
|
if (sections.length !== 1) throw new TypeError("shared object needs one dylink.0 section");
|
|
@@ -1264,7 +1450,9 @@ function dylinkRequirements(module) {
|
|
|
1264
1450
|
const moduleName = section2.string();
|
|
1265
1451
|
const symbolName = section2.string();
|
|
1266
1452
|
const flags = section2.unsigned(32);
|
|
1267
|
-
if ((flags & 3n)
|
|
1453
|
+
if ((flags & 3n) !== 1n) continue;
|
|
1454
|
+
weakImports.add(`${moduleName}\0${symbolName}`);
|
|
1455
|
+
if (moduleName === "env") for (const got of ["GOT.func", "GOT.mem"]) weakImports.add(`${got}\0${symbolName}`);
|
|
1268
1456
|
}
|
|
1269
1457
|
}
|
|
1270
1458
|
}
|
|
@@ -1316,16 +1504,34 @@ function symbolAddress({ value }) {
|
|
|
1316
1504
|
}
|
|
1317
1505
|
throw new TypeError("symbol is not a callable or data address");
|
|
1318
1506
|
}
|
|
1507
|
+
function gotAddress(relocation, symbol) {
|
|
1508
|
+
if (relocation.module === "GOT.mem" && processSymbols.get(relocation.name) === symbol) {
|
|
1509
|
+
const name = relocation.name;
|
|
1510
|
+
const wrapper = processSymbols.get(name.startsWith("_Z") ? `_ZTW${name.slice(2)}` : `_ZTW${name.length}${name}`);
|
|
1511
|
+
if (typeof wrapper?.value === "function") return BigInt(wrapper.value());
|
|
1512
|
+
}
|
|
1513
|
+
return symbolAddress(symbol);
|
|
1514
|
+
}
|
|
1319
1515
|
function aligned(value, power) {
|
|
1320
1516
|
const alignment = 1n << power;
|
|
1321
1517
|
return value + alignment - 1n & -alignment;
|
|
1322
1518
|
}
|
|
1323
|
-
function
|
|
1519
|
+
function initializeThreadLocals(exports) {
|
|
1520
|
+
if (exports.__tls_size.value === 0n) return;
|
|
1521
|
+
const block = instance.exports.__dolly_dso_allocate(exports.__tls_size.value, exports.__tls_align.value);
|
|
1522
|
+
if (!block) throw new RangeError("process could not reserve a shared object's thread-locals");
|
|
1523
|
+
exports.__wasm_init_tls(block);
|
|
1524
|
+
}
|
|
1525
|
+
function instantiateDso(bytes, flags, placement = {}) {
|
|
1526
|
+
const first = placement.tableBase === void 0;
|
|
1324
1527
|
bytes = bytes.slice();
|
|
1325
1528
|
const contract = configuration.contract;
|
|
1326
1529
|
const source = parseWasmInterface(bytes);
|
|
1327
1530
|
const parsed = validateDsoInterface(contract, source, DOLLY_PROCESS_ABI_DIGEST);
|
|
1328
1531
|
checkHostAbi(executableHostRequirements(source), configuration.hostAbi);
|
|
1532
|
+
if (threadRecords && !["__wasm_init_tls", "__tls_size", "__tls_align"].every((name) => parsed.exports.has(name))) {
|
|
1533
|
+
throw new TypeError("shared object exports no __wasm_init_tls for other threads: link it again with this cc");
|
|
1534
|
+
}
|
|
1329
1535
|
const module = new WebAssembly.Module(bytes);
|
|
1330
1536
|
const requirements = dylinkRequirements(module);
|
|
1331
1537
|
if (requirements.needed.length !== 0) {
|
|
@@ -1335,6 +1541,7 @@ function instantiateDso(bytes, flags) {
|
|
|
1335
1541
|
}
|
|
1336
1542
|
const currentTable = processTable.length;
|
|
1337
1543
|
const tableBase = aligned(currentTable, requirements.tableAlignment);
|
|
1544
|
+
if (!first && placement.tableBase !== tableBase) throw new Error("dso@0: this thread's table is not the loading thread's");
|
|
1338
1545
|
const infrastructure = new Map(contract.imports.filter((entry) => entry.module === "env").map((entry) => [entry.name, entry.type]));
|
|
1339
1546
|
infrastructure.set("memory", {
|
|
1340
1547
|
...configuration.processInterface.imports.find((entry) => entry.type.kind === "memory").type,
|
|
@@ -1364,14 +1571,16 @@ function instantiateDso(bytes, flags) {
|
|
|
1364
1571
|
bindings.set(key, symbol);
|
|
1365
1572
|
}
|
|
1366
1573
|
const memoryAlignment = 1n << requirements.memoryAlignment;
|
|
1367
|
-
const memoryBase = instance.exports.__dolly_dso_allocate(
|
|
1574
|
+
const memoryBase = first ? instance.exports.__dolly_dso_allocate(
|
|
1368
1575
|
requirements.memorySize,
|
|
1369
1576
|
memoryAlignment
|
|
1370
|
-
);
|
|
1577
|
+
) : placement.memoryBase;
|
|
1371
1578
|
if (typeof memoryBase !== "bigint" || memoryBase === 0n) {
|
|
1372
1579
|
throw new RangeError("process could not reserve shared-object memory");
|
|
1373
1580
|
}
|
|
1581
|
+
if (first) Object.assign(placement, { memoryBase, bytes: placement.keep?.(bytes) });
|
|
1374
1582
|
growTable(tableBase - currentTable + requirements.tableSize);
|
|
1583
|
+
placement.tableBase = tableBase;
|
|
1375
1584
|
const imports = {
|
|
1376
1585
|
env: Object.assign(/* @__PURE__ */ Object.create(null), {
|
|
1377
1586
|
memory: configuration.memory,
|
|
@@ -1426,7 +1635,7 @@ function instantiateDso(bytes, flags) {
|
|
|
1426
1635
|
for (const relocation of relocations) {
|
|
1427
1636
|
if (parsed.exports.has(relocation.name)) continue;
|
|
1428
1637
|
const existing = globalSymbol(relocation.name);
|
|
1429
|
-
if (existing !== void 0) relocation.global.value =
|
|
1638
|
+
if (existing !== void 0) relocation.global.value = gotAddress(relocation, existing);
|
|
1430
1639
|
}
|
|
1431
1640
|
dsoInstance = new WebAssembly.Instance(module, imports);
|
|
1432
1641
|
dsoSymbols = typedSymbols(parsed.exports.values(), dsoInstance.exports, memoryBase);
|
|
@@ -1436,7 +1645,7 @@ function instantiateDso(bytes, flags) {
|
|
|
1436
1645
|
}
|
|
1437
1646
|
for (const relocation of relocations) {
|
|
1438
1647
|
const symbol = dsoSymbols.get(relocation.name) ?? globalSymbol(relocation.name);
|
|
1439
|
-
if (symbol !== void 0) relocation.global.value =
|
|
1648
|
+
if (symbol !== void 0) relocation.global.value = gotAddress(relocation, symbol);
|
|
1440
1649
|
else if (relocation.weak) {
|
|
1441
1650
|
relocation.global.value = 0n;
|
|
1442
1651
|
} else {
|
|
@@ -1444,14 +1653,106 @@ function instantiateDso(bytes, flags) {
|
|
|
1444
1653
|
}
|
|
1445
1654
|
}
|
|
1446
1655
|
const applyDataRelocations = dsoInstance.exports.__wasm_apply_data_relocs;
|
|
1447
|
-
if (
|
|
1656
|
+
if (!first) initializeThreadLocals(dsoInstance.exports);
|
|
1657
|
+
else if (applyDataRelocations !== void 0) applyDataRelocations();
|
|
1448
1658
|
const handle = nextDsoHandle++;
|
|
1449
1659
|
const record = { handle, module, instance: dsoInstance, symbols: dsoSymbols, flags, references: 1 };
|
|
1450
1660
|
loadedDsos.set(handle, record);
|
|
1451
1661
|
if ((flags & DOLLY_DSO_GLOBAL) !== 0) globalDsos.push(record);
|
|
1452
|
-
|
|
1662
|
+
return record;
|
|
1663
|
+
}
|
|
1664
|
+
function construct(dso) {
|
|
1665
|
+
const constructors = dso.instance.exports.__wasm_call_ctors;
|
|
1453
1666
|
if (constructors !== void 0) constructors();
|
|
1454
|
-
return handle;
|
|
1667
|
+
return dso.handle;
|
|
1668
|
+
}
|
|
1669
|
+
function memoryBytes(address, size) {
|
|
1670
|
+
return new Uint8Array(configuration.memory.buffer, Number(address), Number(size));
|
|
1671
|
+
}
|
|
1672
|
+
function keep(bytes) {
|
|
1673
|
+
const copy = instance.exports.__dolly_dso_allocate(BigInt(bytes.length), 1n);
|
|
1674
|
+
if (!copy) throw new RangeError("process could not keep a shared object for its threads");
|
|
1675
|
+
memoryBytes(copy, bytes.length).set(bytes);
|
|
1676
|
+
return copy;
|
|
1677
|
+
}
|
|
1678
|
+
function openDsoForThreads(bytes, flags) {
|
|
1679
|
+
return threadRecords.change((publish) => {
|
|
1680
|
+
const placement = { keep };
|
|
1681
|
+
const record = (word) => ({
|
|
1682
|
+
kind: DOLLY_DSO_OPEN,
|
|
1683
|
+
word,
|
|
1684
|
+
values: [placement.bytes, BigInt(bytes.length), placement.memoryBase, placement.tableBase, 0n]
|
|
1685
|
+
});
|
|
1686
|
+
let dso;
|
|
1687
|
+
try {
|
|
1688
|
+
dso = instantiateDso(bytes, flags, placement);
|
|
1689
|
+
} catch (error) {
|
|
1690
|
+
if (placement.tableBase !== void 0) publish(record(flags | LOAD_FAILED));
|
|
1691
|
+
throw error;
|
|
1692
|
+
}
|
|
1693
|
+
publish(record(flags), true);
|
|
1694
|
+
return construct(dso);
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
function lookupSymbol(handle, name) {
|
|
1698
|
+
if (handle === 0n) return globalSymbol(name);
|
|
1699
|
+
if (handle === 1n) return processSymbols.get(name);
|
|
1700
|
+
return loadedDsos.get(handle)?.symbols.get(name);
|
|
1701
|
+
}
|
|
1702
|
+
function symbolIndex(handle, name, value) {
|
|
1703
|
+
if (!threadRecords || typeof value.value !== "function") return symbolAddress(value);
|
|
1704
|
+
return threadRecords.change((publish) => {
|
|
1705
|
+
const indexed = functionIndices.get(value.value);
|
|
1706
|
+
if (indexed !== void 0) return indexed;
|
|
1707
|
+
const bytes = encoder.encode(name);
|
|
1708
|
+
const copy = keep(bytes);
|
|
1709
|
+
const index = functionIndex(value.value);
|
|
1710
|
+
publish({ kind: DOLLY_DSO_SYMBOL, values: [handle, copy, BigInt(bytes.length), index, 0n] }, true);
|
|
1711
|
+
return index;
|
|
1712
|
+
});
|
|
1713
|
+
}
|
|
1714
|
+
var divergence = () => new ThreadRecordError("dso@0: this thread's table is not the one that changed it");
|
|
1715
|
+
function freedLater(records, index, closure) {
|
|
1716
|
+
for (let later = index + 1; later < records.length; ++later) {
|
|
1717
|
+
if (records[later].kind === DOLLY_FFI_CLOSURE_FREE && records[later].values[0] === closure) return true;
|
|
1718
|
+
}
|
|
1719
|
+
return false;
|
|
1720
|
+
}
|
|
1721
|
+
function applyRecord(records, index) {
|
|
1722
|
+
const { kind, word, values } = records[index];
|
|
1723
|
+
if (kind === DOLLY_DSO_OPEN) {
|
|
1724
|
+
const [bytes, size, memoryBase, tableBase] = values;
|
|
1725
|
+
try {
|
|
1726
|
+
instantiateDso(memoryBytes(bytes, size), word & ~LOAD_FAILED, { memoryBase, tableBase });
|
|
1727
|
+
} catch (error) {
|
|
1728
|
+
if (word & LOAD_FAILED) return;
|
|
1729
|
+
throw error;
|
|
1730
|
+
}
|
|
1731
|
+
if (word & LOAD_FAILED) throw divergence();
|
|
1732
|
+
} else if (kind === DOLLY_DSO_SYMBOL) {
|
|
1733
|
+
const [handle, name, size, index2] = values;
|
|
1734
|
+
const value = lookupSymbol(handle, decoder3.decode(Uint8Array.from(memoryBytes(name, size))));
|
|
1735
|
+
if (typeof value?.value !== "function" || functionIndex(value.value) !== index2) throw divergence();
|
|
1736
|
+
} else if (kind === DOLLY_FFI_CLOSURE_PREP && freedLater(records, index, values[0])) {
|
|
1737
|
+
return;
|
|
1738
|
+
} else if (ffi.handles(kind) && kind !== DOLLY_FFI_CALL) {
|
|
1739
|
+
if (ffi.repeat(kind, values) !== BigInt(word)) throw divergence();
|
|
1740
|
+
} else {
|
|
1741
|
+
throw new TypeError(`dso@0 has no thread record of kind ${kind}`);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
function futexWait(request) {
|
|
1745
|
+
if (request.size !== 24) return -BigInt(DOLLY_ERRNO.EINVAL);
|
|
1746
|
+
const packet = new DataView(configuration.memory.buffer, request.address, 24);
|
|
1747
|
+
const address = packet.getBigUint64(0, true), timeout = packet.getBigInt64(16, true);
|
|
1748
|
+
if (packet.getUint32(12, true) !== 0 || address % 4n !== 0n) return -BigInt(DOLLY_ERRNO.EINVAL);
|
|
1749
|
+
if (address > BigInt(configuration.memory.buffer.byteLength - 4)) return -BigInt(DOLLY_ERRNO.EFAULT);
|
|
1750
|
+
const outcome = threadRecords.wait(
|
|
1751
|
+
Number(address),
|
|
1752
|
+
packet.getUint32(8, true) | 0,
|
|
1753
|
+
timeout < 0n ? Infinity : Number(timeout) / 1e6
|
|
1754
|
+
);
|
|
1755
|
+
return outcome === "ok" ? 0n : -BigInt(outcome === "not-equal" ? DOLLY_ERRNO.EAGAIN : DOLLY_ERRNO.ETIMEDOUT);
|
|
1455
1756
|
}
|
|
1456
1757
|
function writeDsoResponse(response, value = 0n, error = 0, message = "") {
|
|
1457
1758
|
if (response.size < DSO_RESPONSE_SIZE) return -BigInt(DOLLY_ERRNO.ENOBUFS);
|
|
@@ -1497,10 +1798,8 @@ function processDsoCall(operation, request, response) {
|
|
|
1497
1798
|
if ((flags & ~DOLLY_DSO_GLOBAL) !== 0 || reserved !== 0 || size !== BigInt(request.size - 16) || size > BigInt(DOLLY_DSO_LIMIT)) {
|
|
1498
1799
|
throw new TypeError("invalid DSO_OPEN packet");
|
|
1499
1800
|
}
|
|
1500
|
-
const
|
|
1501
|
-
|
|
1502
|
-
flags
|
|
1503
|
-
);
|
|
1801
|
+
const bytes = new Uint8Array(configuration.memory.buffer, request.address + 16, Number(size));
|
|
1802
|
+
const handle = size === 0n ? 1n : threadRecords ? openDsoForThreads(bytes, flags) : construct(instantiateDso(bytes, flags));
|
|
1504
1803
|
return writeDsoResponse(response, handle);
|
|
1505
1804
|
}
|
|
1506
1805
|
if (operation === DOLLY_DSO_SYMBOL) {
|
|
@@ -1513,18 +1812,14 @@ function processDsoCall(operation, request, response) {
|
|
|
1513
1812
|
request.address + 16,
|
|
1514
1813
|
nameSize
|
|
1515
1814
|
)));
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
else if (handle === 1n) value = processSymbols.get(name);
|
|
1519
|
-
else {
|
|
1520
|
-
const dso = loadedDsos.get(handle);
|
|
1521
|
-
if (!dso) return writeDsoResponse(response, 0n, DOLLY_ERRNO.EBADF, "invalid DSO handle");
|
|
1522
|
-
value = dso.symbols.get(name);
|
|
1815
|
+
if (handle > 1n && !loadedDsos.has(handle)) {
|
|
1816
|
+
return writeDsoResponse(response, 0n, DOLLY_ERRNO.EBADF, "invalid DSO handle");
|
|
1523
1817
|
}
|
|
1818
|
+
const value = lookupSymbol(handle, name);
|
|
1524
1819
|
if (value === void 0) {
|
|
1525
1820
|
return writeDsoResponse(response, 0n, DOLLY_ERRNO.ENOENT, `undefined symbol: ${name}`);
|
|
1526
1821
|
}
|
|
1527
|
-
return writeDsoResponse(response,
|
|
1822
|
+
return writeDsoResponse(response, symbolIndex(handle, name, value));
|
|
1528
1823
|
}
|
|
1529
1824
|
if (operation === DOLLY_DSO_CLOSE) {
|
|
1530
1825
|
if (request.size !== 8) throw new TypeError("invalid DSO_CLOSE packet");
|
|
@@ -1537,6 +1832,7 @@ function processDsoCall(operation, request, response) {
|
|
|
1537
1832
|
}
|
|
1538
1833
|
return writeDsoResponse(response, 0n, DOLLY_ERRNO.ENOSYS, "unknown process-local operation");
|
|
1539
1834
|
} catch (error) {
|
|
1835
|
+
if (error instanceof ThreadRecordError) throw error;
|
|
1540
1836
|
return writeDsoResponse(
|
|
1541
1837
|
response,
|
|
1542
1838
|
0n,
|
|
@@ -1550,24 +1846,57 @@ function serve(context) {
|
|
|
1550
1846
|
instance = context.instance;
|
|
1551
1847
|
processTable = instance.exports.__indirect_function_table;
|
|
1552
1848
|
processSymbols = typedSymbols(configuration.processInterface.exports, instance.exports);
|
|
1553
|
-
const
|
|
1849
|
+
const threads = processSymbols.get("__dolly_dso_threads");
|
|
1850
|
+
if (context.threaded && threads && typeof instance.exports.__dolly_dso_allocate === "function") {
|
|
1851
|
+
requireDsoType(
|
|
1852
|
+
threads.type,
|
|
1853
|
+
context.contract.exports.find((entry) => entry.name === "__dolly_dso_threads").type,
|
|
1854
|
+
"__dolly_dso_threads"
|
|
1855
|
+
);
|
|
1856
|
+
const address = threads.value.value;
|
|
1857
|
+
if (address === 0n || address > BigInt(context.memory.buffer.byteLength - DOLLY_DSO_THREADS_SIZE)) {
|
|
1858
|
+
throw new RangeError("__dolly_dso_threads is outside the process memory");
|
|
1859
|
+
}
|
|
1860
|
+
threadRecords = createThreadRecords({
|
|
1861
|
+
memory: context.memory,
|
|
1862
|
+
address,
|
|
1863
|
+
apply: applyRecord,
|
|
1864
|
+
allocate: (size, alignment) => instance.exports.__dolly_dso_allocate(size, alignment)
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
ffi = createProcessFfi({
|
|
1554
1868
|
memory: configuration.memory,
|
|
1555
1869
|
getInstance: () => instance,
|
|
1556
1870
|
getTable: () => processTable,
|
|
1557
1871
|
growTable,
|
|
1558
1872
|
setTable,
|
|
1559
|
-
castEmulationParameters: context.processInterface.castEmulationParameters
|
|
1873
|
+
castEmulationParameters: context.processInterface.castEmulationParameters,
|
|
1874
|
+
change: threadRecords && ((operation, values, apply) => threadRecords.change((publish) => {
|
|
1875
|
+
const index = apply();
|
|
1876
|
+
publish(
|
|
1877
|
+
{ kind: operation, word: Number(index), values: [...values, 0n, 0n, 0n, 0n].slice(0, 5) },
|
|
1878
|
+
operation === DOLLY_FFI_CLOSURE_PREP
|
|
1879
|
+
);
|
|
1880
|
+
return index;
|
|
1881
|
+
}))
|
|
1560
1882
|
});
|
|
1561
|
-
const callsOnly = context.threaded || context.processInterface.castEmulationParameters > 0;
|
|
1883
|
+
const callsOnly = context.threaded && !threadRecords || context.processInterface.castEmulationParameters > 0;
|
|
1562
1884
|
const refusal = (operation, response) => ffi.handles(operation) ? -BigInt(DOLLY_ERRNO.ENOTSUP) : writeDsoResponse(
|
|
1563
1885
|
response,
|
|
1564
1886
|
0n,
|
|
1565
1887
|
DOLLY_ERRNO.ENOTSUP,
|
|
1566
|
-
context.threaded ? "not in a program using threads@0" : "not in a cast-emulated program"
|
|
1888
|
+
context.threaded ? "not in a program using threads@0 without -rdynamic" : "not in a cast-emulated program"
|
|
1567
1889
|
);
|
|
1568
1890
|
return {
|
|
1569
|
-
handles: (operation) => operation >= DOLLY_DSO_OPEN && operation <=
|
|
1570
|
-
call
|
|
1891
|
+
handles: (operation) => operation >= DOLLY_DSO_OPEN && operation <= DOLLY_DSO_WAIT || ffi.handles(operation),
|
|
1892
|
+
call(operation, request, response) {
|
|
1893
|
+
if (operation === DOLLY_DSO_WAIT) return threadRecords ? futexWait(request) : -BigInt(DOLLY_ERRNO.ENOTSUP);
|
|
1894
|
+
if (callsOnly && operation !== DOLLY_FFI_CALL) return refusal(operation, response);
|
|
1895
|
+
threadRecords?.synchronize();
|
|
1896
|
+
return ffi.handles(operation) ? ffi.call(operation, request, response) : processDsoCall(operation, request, response);
|
|
1897
|
+
},
|
|
1898
|
+
awaitHost: threadRecords?.awaitHost,
|
|
1899
|
+
leaving: threadRecords?.leaving
|
|
1571
1900
|
};
|
|
1572
1901
|
}
|
|
1573
1902
|
export {
|
|
Binary file
|