@daugasauron/dolly 0.1.4 → 0.1.5

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.
Files changed (60) hide show
  1. package/README.md +134 -14
  2. package/coi-serviceworker.js +28 -0
  3. package/dist/dolly-build-id.mjs +1 -1
  4. package/dist/dolly-dso-0.wasm +0 -0
  5. package/dist/dolly-image-build-id.mjs +1 -1
  6. package/dist/dolly-process-0.wasm +0 -0
  7. package/dist/dolly-process-abi.mjs +1 -1
  8. package/dist/dolly-process-dso.mjs +382 -53
  9. package/dist/dolly-process-gate-0.wasm +0 -0
  10. package/dist/dolly-process-worker.mjs +164 -35
  11. package/dist/dolly.wasm +0 -0
  12. package/host/dso/abi.mjs +3 -1
  13. package/host/dso/dso.mjs +1 -2
  14. package/host/http/abi.mjs +2 -2
  15. package/host/http/broker.mjs +18 -8
  16. package/host/http/http.mjs +0 -1
  17. package/host/http/policy.mjs +8 -4
  18. package/host/input/abi.mjs +47 -0
  19. package/host/modules.mjs +4 -3
  20. package/host/nena/abi.mjs +1 -1
  21. package/host/nena/nena.mjs +16 -18
  22. package/host/packages/service.mjs +35 -15
  23. package/host/packages/worker.mjs +21 -0
  24. package/host/runtime/abi.mjs +1 -0
  25. package/host/runtime/boot-files.mjs +8 -2
  26. package/host/runtime/grower.mjs +11 -8
  27. package/host/runtime/lease-bridge.mjs +8 -5
  28. package/host/runtime/module.json +1 -1
  29. package/host/runtime/runtime.mjs +37 -18
  30. package/host/snapshot/abi.mjs +7 -9
  31. package/host/snapshot/snapshot.mjs +14 -12
  32. package/host/snapshot/transport.mjs +4 -12
  33. package/host/snapshot/ui.mjs +17 -7
  34. package/host/stack/stack.mjs +1 -2
  35. package/host/threads/abi.mjs +1 -1
  36. package/host/threads/threads.mjs +2 -1
  37. package/package.json +1 -1
  38. package/src/browser.mjs +34 -37
  39. package/src/build-log.mjs +13 -2
  40. package/src/build-worker.mjs +182 -0
  41. package/src/custom-image.mjs +59 -0
  42. package/src/dollyfile-graph.mjs +127 -0
  43. package/src/dollyfile-view.mjs +335 -0
  44. package/src/image-artifact.mjs +248 -0
  45. package/src/image-build.mjs +89 -0
  46. package/src/image-builder.mjs +63 -0
  47. package/src/images.mjs +13 -2
  48. package/src/pack-store.mjs +141 -0
  49. package/src/packaged-snapshot.mjs +263 -91
  50. package/src/page-chords.mjs +4 -2
  51. package/src/process-constants.mjs +4 -2
  52. package/src/process-supervisor.mjs +209 -122
  53. package/src/runtime-worker.mjs +9 -10
  54. package/src/session-store.mjs +5 -7
  55. package/src/sha256.mjs +65 -0
  56. package/src/snapshot-records.mjs +135 -23
  57. package/src/static-asset.mjs +29 -2
  58. package/src/terminal-text.mjs +8 -5
  59. package/src/version.mjs +1 -1
  60. package/src/worker-image.mjs +3 -5
@@ -1,13 +1,15 @@
1
1
  // dist/dolly-process-abi.mjs
2
- var DOLLY_PROCESS_ABI_DIGEST = "8002d2ac9e15e7d366e3eae77fdb1c61ac7650239792dc0c1ca64b7a1c3f4730";
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.4";
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
- address(closure, 32, "FFI closure");
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
- const closure = view(request.address, 8, "FFI closure free request").getBigUint64(0, true);
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 closure = packet.getBigUint64(0, true);
1116
- const cifPointer = packet.getBigUint64(8, true);
1117
- const callbackIndex = packet.getBigUint64(16, true);
1118
- const userData = packet.getBigUint64(24, true);
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) === 1n) weakImports.add(`${moduleName}\0${symbolName}`);
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 instantiateDso(bytes, flags) {
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 = symbolAddress(existing);
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 = symbolAddress(symbol);
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 (applyDataRelocations !== void 0) applyDataRelocations();
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
- const constructors = dsoInstance.exports.__wasm_call_ctors;
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 handle = size === 0n ? 1n : instantiateDso(
1501
- new Uint8Array(configuration.memory.buffer, request.address + 16, Number(size)),
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
- let value;
1517
- if (handle === 0n) value = globalSymbol(name);
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, symbolAddress(value));
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 ffi = createProcessFfi({
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 <= DOLLY_DSO_CLOSE || ffi.handles(operation),
1570
- call: (operation, request, response) => callsOnly && operation !== DOLLY_FFI_CALL ? refusal(operation, response) : ffi.handles(operation) ? ffi.call(operation, request, response) : processDsoCall(operation, request, response)
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