warpo 3.2.2 → 3.4.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.
@@ -89,7 +89,15 @@
89
89
  "items": {
90
90
  "type": "string",
91
91
  "description": "Name of a feature to disable.",
92
- "enum": ["mutable-globals", "sign-extension", "nontrapping-f2i", "bulk-memory", "multi-value", "tail-call"]
92
+ "enum": [
93
+ "mutable-globals",
94
+ "sign-extension",
95
+ "nontrapping-f2i",
96
+ "bulk-memory",
97
+ "multi-value",
98
+ "tail-call",
99
+ "return-to-br"
100
+ ]
93
101
  }
94
102
  },
95
103
  "enable": {
@@ -98,7 +106,15 @@
98
106
  "items": {
99
107
  "type": "string",
100
108
  "description": "Name of a feature to enable.",
101
- "enum": ["mutable-globals", "sign-extension", "nontrapping-f2i", "bulk-memory", "multi-value", "tail-call"]
109
+ "enum": [
110
+ "mutable-globals",
111
+ "sign-extension",
112
+ "nontrapping-f2i",
113
+ "bulk-memory",
114
+ "multi-value",
115
+ "tail-call",
116
+ "return-to-br"
117
+ ]
102
118
  }
103
119
  },
104
120
  "debug": {
@@ -7,6 +7,7 @@ import { REVERSE, FILL } from "./util/bytes";
7
7
  import { joinBooleanArray, joinIntegerArray, joinFloatArray, joinStringArray, joinReferenceArray } from "./util/string";
8
8
  import { idof, isArray as builtin_isArray } from "./builtins";
9
9
  import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH, E_EMPTYARRAY, E_HOLEYARRAY } from "./util/error";
10
+ import { ReadonlyArray } from "./readonlyarray";
10
11
 
11
12
  // @ts-ignore: decorator
12
13
  @inline @lazy const MIN_SIZE: usize = 8;
@@ -46,7 +47,7 @@ function ensureCapacity(array: usize, newSize: usize, alignLog2: u32, canGrow: b
46
47
  }
47
48
  }
48
49
 
49
- export class Array<T> implements Iterable<T> {
50
+ export class Array<T> implements ReadonlyArray<T> {
50
51
  [key: number]: T;
51
52
 
52
53
  // Mimicking ArrayBufferView isn't strictly necessary here but is done to allow glue code
@@ -57,6 +57,41 @@ function ENTRY_SIZE<K, V>(): usize {
57
57
  return size;
58
58
  }
59
59
 
60
+ /**
61
+ * Forward-only history of entries layouts: the map keeps the latest version,
62
+ * while each iterator keeps the version matching its cursor. Old versions retain
63
+ * removed slot indices, not obsolete entries buffers or references to their contents.
64
+ */
65
+ class MapIteratorVersion {
66
+ private nextVersion: MapIteratorVersion | null = null;
67
+ // For versions with a successor, null marks clear(); otherwise indices are sorted in the old layout.
68
+ private removedIndices: StaticArray<i32> | null = null;
69
+
70
+ transition(removedIndices: StaticArray<i32> | null): MapIteratorVersion {
71
+ this.removedIndices = removedIndices;
72
+ return (this.nextVersion = new MapIteratorVersion());
73
+ }
74
+
75
+ translateIndex(index: i32): i32 {
76
+ const removedIndices = this.removedIndices;
77
+ if (!removedIndices) return 0;
78
+ // New cursor = old cursor - number of removed slots strictly before it.
79
+ // A removed slot at index is unvisited and must not move the boundary backward.
80
+ let lower = 0;
81
+ let upper = removedIndices.length;
82
+ while (lower < upper) {
83
+ const middle = lower + ((upper - lower) >> 1);
84
+ if (unchecked(removedIndices[middle]) < index) lower = middle + 1;
85
+ else upper = middle;
86
+ }
87
+ return index - lower;
88
+ }
89
+
90
+ getNext(): MapIteratorVersion {
91
+ return assert(this.nextVersion);
92
+ }
93
+ }
94
+
60
95
  // @ts-ignore: decorator
61
96
  @lazy
62
97
  const GET_START = Symbol();
@@ -65,11 +100,33 @@ const GET_START = Symbol();
65
100
  @lazy
66
101
  const GET_ENTRIES_OFFSET = Symbol();
67
102
 
103
+ // @ts-ignore: decorator
104
+ @lazy
105
+ const GET_ITERATOR_VERSION = Symbol();
106
+
68
107
  class MapIterator<K, V> implements Iterator<[K, V]> {
108
+ // Next physical slot in this.version's layout, not the number of values returned.
69
109
  private i: i32 = 0;
70
- constructor(private map: Map<K, V>) {}
110
+ private map: Map<K, V> | null;
111
+ private version: MapIteratorVersion | null;
112
+
113
+ constructor(map: Map<K, V>) {
114
+ this.map = map;
115
+ this.version = map[GET_ITERATOR_VERSION]();
116
+ }
117
+
71
118
  next(): IteratorResult<[K, V]> {
72
119
  const map = this.map;
120
+ if (!map) return IteratorResult.done<[K, V]>();
121
+ // Apply every missed compaction or clear before accessing the current entries buffer.
122
+ const currentVersion = map[GET_ITERATOR_VERSION]();
123
+ let version = assert(this.version);
124
+ while (version != currentVersion) {
125
+ this.i = version.translateIndex(this.i);
126
+ version = version.getNext();
127
+ }
128
+ this.version = version;
129
+
73
130
  const start = map[GET_START]();
74
131
  const size = map[GET_ENTRIES_OFFSET]();
75
132
  for (let i = this.i; i < size; ++i) {
@@ -79,6 +136,10 @@ class MapIterator<K, V> implements Iterator<[K, V]> {
79
136
  return IteratorResult.fromValue<[K, V]>([entry.key, entry.value]);
80
137
  }
81
138
  }
139
+ // Once done, later insertions must not revive this iterator.
140
+ // Release the map and any layout history still retained by this iterator.
141
+ this.map = null;
142
+ this.version = null;
82
143
  return IteratorResult.done<[K, V]>();
83
144
  }
84
145
  }
@@ -93,6 +154,8 @@ export class Map<K, V> implements Iterable<[K, V]> {
93
154
  private entriesCapacity: i32 = INITIAL_CAPACITY;
94
155
  private entriesOffset: i32 = 0;
95
156
  private entriesCount: i32 = 0;
157
+ // Lazy state: maps that never create iterators need no layout-change history.
158
+ private iteratorVersion: MapIteratorVersion | null = null;
96
159
 
97
160
  constructor() {
98
161
  /* nop */
@@ -107,6 +170,12 @@ export class Map<K, V> implements Iterable<[K, V]> {
107
170
  [GET_ENTRIES_OFFSET](): i32 {
108
171
  return this.entriesOffset;
109
172
  }
173
+ @inline
174
+ [GET_ITERATOR_VERSION](): MapIteratorVersion {
175
+ let iteratorVersion = this.iteratorVersion;
176
+ if (!iteratorVersion) this.iteratorVersion = iteratorVersion = new MapIteratorVersion();
177
+ return iteratorVersion;
178
+ }
110
179
 
111
180
  get size(): i32 {
112
181
  return this.entriesCount;
@@ -116,6 +185,9 @@ export class Map<K, V> implements Iterable<[K, V]> {
116
185
  this.buckets = new ArrayBuffer(INITIAL_CAPACITY * <i32>BUCKET_SIZE);
117
186
  this.bucketsMask = INITIAL_CAPACITY - 1;
118
187
  this.entries = new ArrayBuffer(INITIAL_CAPACITY * <i32>ENTRY_SIZE<K, V>());
188
+ // Live iterators must restart at entries inserted after clear(), rather than keep their old indices.
189
+ let iteratorVersion = this.iteratorVersion;
190
+ if (iteratorVersion) this.iteratorVersion = iteratorVersion.transition(null);
119
191
  this.entriesCapacity = INITIAL_CAPACITY;
120
192
  this.entriesOffset = 0;
121
193
  this.entriesCount = 0;
@@ -206,9 +278,17 @@ export class Map<K, V> implements Iterable<[K, V]> {
206
278
  let newEntries = new ArrayBuffer(newEntriesCapacity * <i32>ENTRY_SIZE<K, V>());
207
279
 
208
280
  // copy old entries to new entries
209
- let oldPtr = changetype<usize>(this.entries);
281
+ let oldStart = changetype<usize>(this.entries);
282
+ let oldPtr = oldStart;
210
283
  let oldEnd = oldPtr + <usize>this.entriesOffset * ENTRY_SIZE<K, V>();
211
284
  let newPtr = changetype<usize>(newEntries);
285
+ let iteratorVersion = this.iteratorVersion;
286
+ // Record deleted slots only if iterator state exists; growth without holes leaves indices unchanged.
287
+ let removedIndices: StaticArray<i32> | null =
288
+ iteratorVersion && this.entriesOffset != this.entriesCount
289
+ ? new StaticArray<i32>(this.entriesOffset - this.entriesCount)
290
+ : null;
291
+ let removedCount = 0;
212
292
  while (oldPtr != oldEnd) {
213
293
  let oldEntry = changetype<MapEntry<K, V>>(oldPtr);
214
294
  if (!(oldEntry.taggedNext & EMPTY)) {
@@ -221,6 +301,9 @@ export class Map<K, V> implements Iterable<[K, V]> {
221
301
  newEntry.taggedNext = load<usize>(newBucketPtrBase);
222
302
  store<usize>(newBucketPtrBase, newPtr);
223
303
  newPtr += ENTRY_SIZE<K, V>();
304
+ } else if (removedIndices) {
305
+ // Old slots are scanned in order, keeping the indices sorted for the lower-bound search.
306
+ unchecked((removedIndices[removedCount++] = <i32>((oldPtr - oldStart) / ENTRY_SIZE<K, V>())));
224
307
  }
225
308
  oldPtr += ENTRY_SIZE<K, V>();
226
309
  }
@@ -228,6 +311,7 @@ export class Map<K, V> implements Iterable<[K, V]> {
228
311
  this.buckets = newBuckets;
229
312
  this.bucketsMask = newBucketsMask;
230
313
  this.entries = newEntries;
314
+ if (iteratorVersion && removedIndices) this.iteratorVersion = iteratorVersion.transition(removedIndices);
231
315
  this.entriesCapacity = newEntriesCapacity;
232
316
  this.entriesOffset = this.entriesCount;
233
317
  }
@@ -276,6 +360,9 @@ export class Map<K, V> implements Iterable<[K, V]> {
276
360
 
277
361
  @unsafe private __visit(cookie: u32): void {
278
362
  __visit(changetype<usize>(this.buckets), cookie);
363
+ // Map uses a custom visitor, so its managed iterator-version field must be traced explicitly.
364
+ let iteratorVersion = this.iteratorVersion;
365
+ if (iteratorVersion) __visit(changetype<usize>(iteratorVersion), cookie);
279
366
  let entries = changetype<usize>(this.entries);
280
367
  if (isManaged<K>() || isManaged<V>()) {
281
368
  let cur = entries;
@@ -0,0 +1,24 @@
1
+ // Copyright (C) 2026 wasm-ecosystem
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { Array } from "./array";
5
+
6
+ export interface ReadonlyArray<T> extends Iterable<T> {
7
+ [key: i32]: T;
8
+ readonly length: i32;
9
+ at(index: i32): T;
10
+ findIndex(fn: (value: T, index: i32, array: ReadonlyArray<T>) => bool): i32;
11
+ findLastIndex(fn: (value: T, index: i32, array: ReadonlyArray<T>) => bool): i32;
12
+ includes(value: T, fromIndex: i32): bool;
13
+ indexOf(value: T, fromIndex: i32): i32;
14
+ lastIndexOf(value: T, fromIndex: i32): i32;
15
+ forEach(fn: (value: T, index: i32, array: ReadonlyArray<T>) => void): void;
16
+ filter(fn: (value: T, index: i32, array: ReadonlyArray<T>) => bool): Array<T>;
17
+ every(fn: (value: T, index: i32, array: ReadonlyArray<T>) => bool): bool;
18
+ some(fn: (value: T, index: i32, array: ReadonlyArray<T>) => bool): bool;
19
+ concat(other: Array<T>): Array<T>;
20
+ slice(start: i32, end: i32): Array<T>;
21
+ join(separator: string): string;
22
+ flat(): T;
23
+ toString(): string;
24
+ }
@@ -207,10 +207,10 @@ class StringIterator implements Iterator<string> {
207
207
 
208
208
  indexOf(search: String, start: i32 = 0): i32 {
209
209
  let searchLen = <isize>search.length;
210
- if (!searchLen) return 0;
211
210
  let len = <isize>this.length;
212
- if (!len) return -1;
213
211
  let searchStart = min(max(<isize>start, 0), len);
212
+ if (!searchLen) return <i32>searchStart;
213
+ if (!len) return -1;
214
214
  for (len -= searchLen; searchStart <= len; ++searchStart) {
215
215
  // @ts-ignore: string <-> String
216
216
  if (!compareImpl(this, searchStart, search, 0, searchLen)) return <i32>searchStart;
@@ -456,8 +456,9 @@ class StringIterator implements Iterator<string> {
456
456
  while (~(next = <isize>this.indexOf(search, <i32>prev))) {
457
457
  if (!out) out = changetype<String>(__new(thisLen << 1, idof<String>()));
458
458
  let chunk = next - prev;
459
- if (offset + chunk + replaceLen > outSize) {
460
- outSize <<= 1;
459
+ let requiredSize = offset + chunk + replaceLen;
460
+ if (requiredSize > outSize) {
461
+ outSize = max(outSize << 1, requiredSize);
461
462
  out = changetype<String>(__renew(changetype<usize>(out), outSize << 1));
462
463
  }
463
464
  memory.copy(changetype<usize>(out) + (offset << 1), changetype<usize>(this) + (prev << 1), chunk << 1);
@@ -468,8 +469,9 @@ class StringIterator implements Iterator<string> {
468
469
  }
469
470
  if (out) {
470
471
  let rest = thisLen - prev;
471
- if (offset + rest > outSize) {
472
- outSize <<= 1;
472
+ let requiredSize = offset + rest;
473
+ if (requiredSize > outSize) {
474
+ outSize = max(outSize << 1, requiredSize);
473
475
  out = changetype<String>(__renew(changetype<usize>(out), outSize << 1));
474
476
  }
475
477
  if (rest) {
@@ -886,7 +888,3 @@ export namespace String {
886
888
  }
887
889
  }
888
890
  }
889
-
890
- export class TemplateStringsArray extends Array<string> {
891
- readonly raw: string[];
892
- }
@@ -0,0 +1,113 @@
1
+ // Copyright (C) 2026 wasm-ecosystem
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { Array } from "./array";
5
+ import { ReadonlyArray } from "./readonlyarray";
6
+
7
+ export class TemplateStringsArray implements ReadonlyArray<string> {
8
+ [key: i32]: string;
9
+
10
+ private values: Array<string>;
11
+ readonly raw: ReadonlyArray<string>;
12
+
13
+ constructor(values: Array<string>, raw: ReadonlyArray<string>) {
14
+ this.values = values;
15
+ this.raw = raw;
16
+ }
17
+
18
+
19
+ @operator("[]") private __get(index: i32): string {
20
+ return this.values[index];
21
+ }
22
+
23
+
24
+ @unsafe @operator("{}") private __uget(index: i32): string {
25
+ return unchecked(this.values[index]);
26
+ }
27
+
28
+ get length(): i32 {
29
+ return this.values.length;
30
+ }
31
+
32
+ at(index: i32): string {
33
+ return this.values.at(index);
34
+ }
35
+
36
+ findIndex(fn: (value: string, index: i32, array: ReadonlyArray<string>) => bool): i32 {
37
+ return this.values.findIndex(fn);
38
+ }
39
+
40
+ findLastIndex(fn: (value: string, index: i32, array: ReadonlyArray<string>) => bool): i32 {
41
+ return this.values.findLastIndex(fn);
42
+ }
43
+
44
+ includes(value: string, fromIndex: i32 = 0): bool {
45
+ return this.values.includes(value, fromIndex);
46
+ }
47
+
48
+ indexOf(value: string, fromIndex: i32 = 0): i32 {
49
+ return this.values.indexOf(value, fromIndex);
50
+ }
51
+
52
+ lastIndexOf(value: string, fromIndex: i32 = i32.MAX_VALUE): i32 {
53
+ return this.values.lastIndexOf(value, fromIndex);
54
+ }
55
+
56
+ forEach(fn: (value: string, index: i32, array: ReadonlyArray<string>) => void): void {
57
+ this.values.forEach(fn);
58
+ }
59
+
60
+ map<U>(fn: (value: string, index: i32, array: ReadonlyArray<string>) => U): Array<U> {
61
+ return this.values.map<U>(fn);
62
+ }
63
+
64
+ filter(fn: (value: string, index: i32, array: ReadonlyArray<string>) => bool): Array<string> {
65
+ return this.values.filter(fn);
66
+ }
67
+
68
+ reduce<U>(
69
+ fn: (previousValue: U, currentValue: string, currentIndex: i32, array: ReadonlyArray<string>) => U,
70
+ initialValue: U
71
+ ): U {
72
+ return this.values.reduce<U>(fn, initialValue);
73
+ }
74
+
75
+ reduceRight<U>(
76
+ fn: (previousValue: U, currentValue: string, currentIndex: i32, array: ReadonlyArray<string>) => U,
77
+ initialValue: U
78
+ ): U {
79
+ return this.values.reduceRight<U>(fn, initialValue);
80
+ }
81
+
82
+ every(fn: (value: string, index: i32, array: ReadonlyArray<string>) => bool): bool {
83
+ return this.values.every(fn);
84
+ }
85
+
86
+ some(fn: (value: string, index: i32, array: ReadonlyArray<string>) => bool): bool {
87
+ return this.values.some(fn);
88
+ }
89
+
90
+ concat(other: Array<string>): Array<string> {
91
+ return this.values.concat(other);
92
+ }
93
+
94
+ slice(start: i32 = 0, end: i32 = i32.MAX_VALUE): Array<string> {
95
+ return this.values.slice(start, end);
96
+ }
97
+
98
+ join(separator: string = ","): string {
99
+ return this.values.join(separator);
100
+ }
101
+
102
+ flat(): string {
103
+ return this.values.flat();
104
+ }
105
+
106
+ toString(): string {
107
+ return this.values.toString();
108
+ }
109
+
110
+ [Symbol.iterator](): Iterator<string> {
111
+ return this.values[Symbol.iterator]();
112
+ }
113
+ }
@@ -1679,7 +1679,7 @@ function COPY_WITHIN<TArray extends ArrayBufferView, T extends number>(
1679
1679
  let to = target < 0 ? max(len + target, 0) : min(target, len);
1680
1680
  let from = start < 0 ? max(len + start, 0) : min(start, len);
1681
1681
  let last = end < 0 ? max(len + end, 0) : min(end, len);
1682
- let count = min(last - from, len - to);
1682
+ let count = max(min(last - from, len - to), 0);
1683
1683
 
1684
1684
  memory.copy(
1685
1685
  ptr + (<usize>to << alignof<T>()),
@@ -1908,7 +1908,7 @@ function WRAP<TArray extends ArrayBufferView, T>(
1908
1908
  byteOffset: i32 = 0,
1909
1909
  len: i32 = -1
1910
1910
  ): TArray {
1911
- let byteLength: i32;
1911
+ let byteLength: usize;
1912
1912
  let bufferByteLength = buffer.byteLength;
1913
1913
  const mask: u32 = sizeof<T>() - 1;
1914
1914
  if (i32(<u32>byteOffset > <u32>bufferByteLength) | (byteOffset & mask)) {
@@ -1924,8 +1924,8 @@ function WRAP<TArray extends ArrayBufferView, T>(
1924
1924
  throw new RangeError(E_INVALIDLENGTH);
1925
1925
  }
1926
1926
  } else {
1927
- byteLength = len << alignof<T>();
1928
- if (byteOffset + byteLength > bufferByteLength) {
1927
+ byteLength = <usize>len << alignof<T>();
1928
+ if (<usize>byteOffset + byteLength > <usize>bufferByteLength) {
1929
1929
  throw new RangeError(E_INVALIDLENGTH);
1930
1930
  }
1931
1931
  }
@@ -1952,7 +1952,7 @@ function SET<
1952
1952
  ERROR(E_NOTIMPLEMENTED);
1953
1953
  }
1954
1954
  let sourceLen = source.length;
1955
- if (offset < 0 || sourceLen + offset > target.length) {
1955
+ if (offset < 0 || <usize>offset + <usize>sourceLen > <usize>target.length) {
1956
1956
  // offset is out of bounds
1957
1957
  throw new RangeError(E_INDEXOUTOFRANGE);
1958
1958
  }
@@ -2,6 +2,7 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  import * as api from "./__warpo_create";
5
+ import { memory_expose } from "warpo/memory";
5
6
 
6
7
  export class ModuleResolve {
7
8
  packageName!: string;
@@ -13,5 +14,6 @@ export class ModuleResolve {
13
14
 
14
15
  export type Fn = (task: ModuleResolve) => void;
15
16
  export function onModuleResolve(fn: Fn): void {
17
+ memory_expose<ModuleResolve>();
16
18
  api.onModuleResolve(fn.index, idof<ModuleResolve>());
17
19
  }
@@ -7,6 +7,7 @@ interface WarpoLaunchRequestArguments extends DebugProtocol.LaunchRequestArgumen
7
7
  entryFunctionName?: string;
8
8
  cwd?: string;
9
9
  warpoPath?: string;
10
+ testNamePattern?: string;
10
11
  debugSessionLogging?: boolean;
11
12
  debugSessionLogFile?: string;
12
13
  args?: number[];
@@ -255,6 +255,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
255
255
  wasmFilePath,
256
256
  cwd,
257
257
  warpoPath: args.warpoPath,
258
+ testNamePattern: args.testNamePattern,
258
259
  }
259
260
  : {
260
261
  wasmFilePath,