@btc-vision/btc-runtime 1.10.2 → 1.10.4

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.
@@ -1,60 +1,149 @@
1
- import { Revert } from '../types/Revert';
2
- import { Map } from './Map';
3
1
  import { Address } from '../types/Address';
2
+ import { Revert } from '../types/Revert';
3
+ import { IMap } from './Map';
4
4
 
5
5
  @final
6
- export class AddressMap<V> extends Map<Address, V> {
6
+ export class AddressMap<V> implements IMap<Address, V> {
7
+ protected _keys: Address[] = [];
8
+ protected _values: V[] = [];
9
+
10
+ // CACHE: Stores the index of the last successful lookup to make repeated access O(1)
11
+ private _lastIndex: i32 = -1;
12
+
13
+ @inline
14
+ public get size(): i32 {
15
+ return this._keys.length;
16
+ }
17
+
18
+ @inline
19
+ public keys(): Address[] {
20
+ return this._keys;
21
+ }
22
+
23
+ @inline
24
+ public values(): V[] {
25
+ return this._values;
26
+ }
27
+
7
28
  public set(key: Address, value: V): this {
8
- const index: i32 = this.indexOf(key);
29
+ let index = -1;
30
+
31
+ // Check Cache (Fastest)
32
+ if (this._lastIndex !== -1) {
33
+ const cachedKey = unchecked(this._keys[this._lastIndex]);
34
+ // Check length first, then full content equality
35
+ if (cachedKey.length === key.length) {
36
+ if (memory.compare(cachedKey.dataStart, key.dataStart, key.length) === 0) {
37
+ index = this._lastIndex;
38
+ }
39
+ }
40
+ }
41
+
42
+ // Full Scan if cache missed
43
+ if (index === -1) {
44
+ index = this.indexOf(key);
45
+ }
46
+
9
47
  if (index === -1) {
10
48
  this._keys.push(key);
11
49
  this._values.push(value);
50
+ this._lastIndex = this._keys.length - 1;
12
51
  } else {
13
- this._values[index] = value;
52
+ unchecked((this._values[index] = value));
53
+ // Cache is already pointing to this index (from indexOf or the check above)
54
+ this._lastIndex = index;
14
55
  }
15
56
 
16
57
  return this;
17
58
  }
18
59
 
19
- public indexOf(address: Address): i32 {
20
- for (let i: i32 = 0; i < this._keys.length; i++) {
21
- const key = this._keys[i];
60
+ /**
61
+ * HYPER-OPTIMIZED SEARCH
62
+ * Scans for Data Equality (not object equality).
63
+ * Uses a "Prefix Filter" to skip expensive memory comparisons.
64
+ */
65
+ public indexOf(pointerHash: Address): i32 {
66
+ const len = this._keys.length;
67
+ if (len === 0) return -1;
68
+
69
+ const ptrLen = pointerHash.length;
70
+ const ptrData = pointerHash.dataStart;
22
71
 
23
- if (address.equals(key)) {
24
- return i;
72
+ // OPTIMIZATION: Prefix Filter
73
+ // If keys are long enough (hashes/addresses), we compare the first 8 bytes as a simple integer.
74
+ // This is 1 CPU cycle vs ~30+ cycles for a function call loop.
75
+ if (ptrLen >= 8) {
76
+ // Read the first 8 bytes of the SEARCH NEEDLE
77
+ const searchHeader = load<u64>(ptrData);
78
+
79
+ // Loop Backwards (finding most recently added items first is usually better for contracts)
80
+ for (let i = len - 1; i >= 0; i--) {
81
+ const key = unchecked(this._keys[i]);
82
+
83
+ // Cheap Length Check
84
+ if (key.length !== ptrLen) continue;
85
+
86
+ // Cheap Integer Check (The Prefix Filter)
87
+ // This reads the CONTENT of the key, not the object pointer.
88
+ // If the first 8 bytes of data don't match, we skip the expensive check.
89
+ if (load<u64>(key.dataStart) !== searchHeader) continue;
90
+
91
+ // Expensive Full Check
92
+ // Only runs if length AND first 8 bytes match.
93
+ if (memory.compare(key.dataStart, ptrData, ptrLen) === 0) {
94
+ this._lastIndex = i;
95
+ return i;
96
+ }
97
+ }
98
+ } else {
99
+ // Fallback for small keys (< 8 bytes)
100
+ for (let i = len - 1; i >= 0; i--) {
101
+ const key = unchecked(this._keys[i]);
102
+ if (key.length !== ptrLen) continue;
103
+
104
+ if (memory.compare(key.dataStart, ptrData, ptrLen) === 0) {
105
+ this._lastIndex = i;
106
+ return i;
107
+ }
25
108
  }
26
109
  }
27
110
 
28
111
  return -1;
29
112
  }
30
113
 
114
+ @inline
31
115
  public has(key: Address): bool {
32
- for (let i: i32 = 0; i < this._keys.length; i++) {
33
- if (key.equals(this._keys[i])) {
34
- return true;
35
- }
36
- }
37
-
38
- return false;
116
+ return this.indexOf(key) !== -1;
39
117
  }
40
118
 
41
119
  public get(key: Address): V {
42
- const index: i32 = this.indexOf(key);
120
+ const index = this.indexOf(key);
43
121
  if (index === -1) {
44
- throw new Revert('Key not found in map (AddressMap)');
122
+ throw new Revert('Key not found in map');
45
123
  }
46
- return this._values[index];
124
+ return unchecked(this._values[index]);
47
125
  }
48
126
 
49
127
  public delete(key: Address): bool {
50
- const index: i32 = this.indexOf(key);
51
- if (index === -1) {
52
- return false;
53
- }
128
+ const index = this.indexOf(key);
129
+ if (index === -1) return false;
54
130
 
55
131
  this._keys.splice(index, 1);
56
132
  this._values.splice(index, 1);
57
133
 
134
+ this._lastIndex = -1;
135
+
58
136
  return true;
59
137
  }
138
+
139
+ @inline
140
+ public clear(): void {
141
+ this._keys = [];
142
+ this._values = [];
143
+ this._lastIndex = -1;
144
+ }
145
+
146
+ public toString(): string {
147
+ return `Map(size=${this._keys.length})`;
148
+ }
60
149
  }
@@ -2,19 +2,13 @@ import { Revert } from '../types/Revert';
2
2
 
3
3
  export interface IMap<K, V> {
4
4
  readonly size: i32;
5
-
6
5
  has(key: K): bool;
7
-
8
6
  set(key: K, value: V): this;
9
-
10
7
  get(key: K): V;
11
-
12
8
  delete(key: K): bool;
13
-
14
9
  clear(): void;
15
-
16
- keys(): K[]; // preliminary
17
- values(): V[]; // preliminary
10
+ keys(): K[];
11
+ values(): V[];
18
12
  toString(): string;
19
13
  }
20
14
 
@@ -22,33 +16,69 @@ export class Map<K, V> implements IMap<K, V> {
22
16
  protected _keys: K[] = [];
23
17
  protected _values: V[] = [];
24
18
 
19
+ // OPTIMIZATION: Cache the last found index.
20
+ // This makes has() -> get() sequences O(1) for the second call.
21
+ protected _lastIndex: i32 = -1;
22
+
23
+ @inline
25
24
  public get size(): i32 {
26
25
  return this._keys.length;
27
26
  }
28
27
 
28
+ @inline
29
29
  public keys(): K[] {
30
30
  return this._keys;
31
31
  }
32
32
 
33
+ @inline
33
34
  public values(): V[] {
34
35
  return this._values;
35
36
  }
36
37
 
37
38
  public set(key: K, value: V): this {
38
- const index: i32 = this.indexOf(key);
39
+ // Fast Cache Check
40
+ if (this._lastIndex != -1) {
41
+ if (unchecked(this._keys[this._lastIndex]) == key) {
42
+ unchecked((this._values[this._lastIndex] = value));
43
+ return this;
44
+ }
45
+ }
46
+
47
+ // Full Scan
48
+ const index = this.indexOf(key);
49
+
39
50
  if (index == -1) {
40
51
  this._keys.push(key);
41
52
  this._values.push(value);
53
+ // Update cache to new item
54
+ this._lastIndex = this._keys.length - 1;
42
55
  } else {
43
- this._values[index] = value;
56
+ unchecked((this._values[index] = value));
57
+ // Update cache to found item (indexOf updates it too, but explicit here for safety)
58
+ this._lastIndex = index;
44
59
  }
45
60
 
46
61
  return this;
47
62
  }
48
63
 
64
+ /**
65
+ * Optimized Linear Scan.
66
+ * Iterates backwards (LIFO assumption: recent items are hotter).
67
+ */
49
68
  public indexOf(key: K): i32 {
50
- for (let i: i32 = 0; i < this._keys.length; i++) {
51
- if (this._keys[i] == key) {
69
+ const len = this._keys.length;
70
+
71
+ // Optimization: Check cache first
72
+ if (this._lastIndex != -1 && this._lastIndex < len) {
73
+ if (unchecked(this._keys[this._lastIndex]) == key) {
74
+ return this._lastIndex;
75
+ }
76
+ }
77
+
78
+ // Reverse loop is often slightly faster for finding recent items
79
+ for (let i = len - 1; i >= 0; i--) {
80
+ if (unchecked(this._keys[i] == key)) {
81
+ this._lastIndex = i; // Update cache
52
82
  return i;
53
83
  }
54
84
  }
@@ -57,40 +87,66 @@ export class Map<K, V> implements IMap<K, V> {
57
87
  }
58
88
 
59
89
  public get(key: K): V {
60
- const index: i32 = this.indexOf(key);
90
+ const index = this.indexOf(key);
61
91
  if (index == -1) {
62
92
  throw new Revert('Key not found in map (Map)');
63
93
  }
64
- return this._values[index];
94
+ return unchecked(this._values[index]);
65
95
  }
66
96
 
97
+ @inline
67
98
  public has(key: K): bool {
68
99
  return this.indexOf(key) != -1;
69
100
  }
70
101
 
102
+ /**
103
+ * Optimized Delete (Swap and Pop).
104
+ * O(1) complexity instead of O(N) splice.
105
+ */
71
106
  public delete(key: K): bool {
72
- const index: i32 = this.indexOf(key);
107
+ const index = this.indexOf(key);
73
108
  if (index == -1) {
74
109
  return false;
75
110
  }
76
111
 
77
- this._keys.splice(index, 1);
78
- this._values.splice(index, 1);
112
+ const lastIndex = this._keys.length - 1;
113
+
114
+ // If the element to delete is not the last one, swap it with the last one
115
+ if (index != lastIndex) {
116
+ unchecked((this._keys[index] = this._keys[lastIndex]));
117
+ unchecked((this._values[index] = this._values[lastIndex]));
118
+
119
+ // Fix cache if we moved the cached item
120
+ if (this._lastIndex == lastIndex) {
121
+ this._lastIndex = index;
122
+ } else if (this._lastIndex == index) {
123
+ this._lastIndex = -1;
124
+ }
125
+ } else {
126
+ // We are deleting the tail
127
+ if (this._lastIndex == lastIndex) this._lastIndex = -1;
128
+ }
129
+
130
+ this._keys.pop();
131
+ this._values.pop();
132
+
79
133
  return true;
80
134
  }
81
135
 
136
+ @inline
82
137
  public clear(): void {
83
138
  this._keys = [];
84
139
  this._values = [];
140
+ this._lastIndex = -1;
85
141
  }
86
142
 
87
143
  public toString(): string {
88
- const str: string = ``;
89
-
90
- for (let i: i32 = 0; i < this._keys.length; i++) {
91
- str.concat(`[${this._keys[i]}] => [${this._values[i]}]`);
144
+ // Warning: String concatenation in loops is O(N^2). Use sparingly.
145
+ let str = '';
146
+ const len = this._keys.length;
147
+ for (let i = 0; i < len; i++) {
148
+ str += `[${unchecked(this._keys[i])}] => [${unchecked(this._values[i])}]`;
92
149
  }
93
-
94
150
  return str;
95
151
  }
96
152
  }
@@ -1,14 +1,13 @@
1
1
  import { Revert } from '../types/Revert';
2
2
  import { IMap } from './Map';
3
3
 
4
+ /**
5
+ * Optimized equality check using WASM intrinsic memory comparison.
6
+ */
7
+ @inline
4
8
  export function eqUint(data: Uint8Array, data2: Uint8Array): bool {
5
9
  if (data.length !== data2.length) return false;
6
-
7
- for (let i = 0; i < data.length; i++) {
8
- if (data[i] !== data2[i]) return false;
9
- }
10
-
11
- return true;
10
+ return memory.compare(data.dataStart, data2.dataStart, data.length) === 0;
12
11
  }
13
12
 
14
13
  @final
@@ -16,78 +15,167 @@ export class MapUint8Array implements IMap<Uint8Array, Uint8Array> {
16
15
  protected _keys: Uint8Array[] = [];
17
16
  protected _values: Uint8Array[] = [];
18
17
 
18
+ // CACHE: Stores the index of the last successful lookup to make repeated access O(1)
19
+ private _lastIndex: i32 = -1;
20
+
21
+ @inline
19
22
  public get size(): i32 {
20
23
  return this._keys.length;
21
24
  }
22
25
 
26
+ @inline
23
27
  public keys(): Uint8Array[] {
24
28
  return this._keys;
25
29
  }
26
30
 
31
+ @inline
27
32
  public values(): Uint8Array[] {
28
33
  return this._values;
29
34
  }
30
35
 
31
36
  public set(key: Uint8Array, value: Uint8Array): this {
32
- const index: i32 = this.indexOf(key);
37
+ let index = -1;
38
+
39
+ // Check Cache (Fastest)
40
+ if (this._lastIndex !== -1) {
41
+ const cachedKey = unchecked(this._keys[this._lastIndex]);
42
+ // Check length first, then full content equality
43
+ if (cachedKey.length === key.length) {
44
+ if (memory.compare(cachedKey.dataStart, key.dataStart, key.length) === 0) {
45
+ index = this._lastIndex;
46
+ }
47
+ }
48
+ }
49
+
50
+ // Full Scan if cache missed
51
+ if (index === -1) {
52
+ index = this.indexOf(key);
53
+ }
54
+
33
55
  if (index === -1) {
34
56
  this._keys.push(key);
35
57
  this._values.push(value);
58
+ this._lastIndex = this._keys.length - 1;
36
59
  } else {
37
- this._values[index] = value;
60
+ unchecked((this._values[index] = value));
61
+ // Cache is already pointing to this index (from indexOf or the check above)
62
+ this._lastIndex = index;
38
63
  }
39
64
 
40
65
  return this;
41
66
  }
42
67
 
68
+ /**
69
+ * HYPER-OPTIMIZED SEARCH
70
+ * Scans for Data Equality (not object equality).
71
+ * Uses a "Prefix Filter" to skip expensive memory comparisons.
72
+ */
43
73
  public indexOf(pointerHash: Uint8Array): i32 {
44
- for (let i: i32 = 0; i < this._keys.length; i++) {
45
- const key = this._keys[i];
46
-
47
- if (eqUint(key, pointerHash)) {
48
- return i;
74
+ const len = this._keys.length;
75
+ if (len === 0) return -1;
76
+
77
+ const ptrLen = pointerHash.length;
78
+ const ptrData = pointerHash.dataStart;
79
+
80
+ // OPTIMIZATION: Prefix Filter
81
+ // If keys are long enough (hashes/addresses), we compare the first 8 bytes as a simple integer.
82
+ // This is 1 CPU cycle vs ~30+ cycles for a function call loop.
83
+ if (ptrLen >= 8) {
84
+ // Read the first 8 bytes of the SEARCH NEEDLE
85
+ const searchHeader = load<u64>(ptrData);
86
+
87
+ // Loop Backwards (finding most recently added items first is usually better for contracts)
88
+ for (let i = len - 1; i >= 0; i--) {
89
+ const key = unchecked(this._keys[i]);
90
+
91
+ // Cheap Length Check
92
+ if (key.length !== ptrLen) continue;
93
+
94
+ // Cheap Integer Check (The Prefix Filter)
95
+ // This reads the CONTENT of the key, not the object pointer.
96
+ // If the first 8 bytes of data don't match, we skip the expensive check.
97
+ if (load<u64>(key.dataStart) !== searchHeader) continue;
98
+
99
+ // Expensive Full Check
100
+ // Only runs if length AND first 8 bytes match.
101
+ if (memory.compare(key.dataStart, ptrData, ptrLen) === 0) {
102
+ this._lastIndex = i;
103
+ return i;
104
+ }
105
+ }
106
+ } else {
107
+ // Fallback for small keys (< 8 bytes)
108
+ for (let i = len - 1; i >= 0; i--) {
109
+ const key = unchecked(this._keys[i]);
110
+ if (key.length !== ptrLen) continue;
111
+
112
+ if (memory.compare(key.dataStart, ptrData, ptrLen) === 0) {
113
+ this._lastIndex = i;
114
+ return i;
115
+ }
49
116
  }
50
117
  }
51
118
 
52
119
  return -1;
53
120
  }
54
121
 
122
+ @inline
55
123
  public has(key: Uint8Array): bool {
56
124
  return this.indexOf(key) !== -1;
57
125
  }
58
126
 
59
127
  public get(key: Uint8Array): Uint8Array {
60
- const index: i32 = this.indexOf(key);
128
+ const index = this.indexOf(key);
61
129
  if (index === -1) {
62
130
  throw new Revert('Key not found in map (uint8array)');
63
131
  }
64
- return this._values[index];
132
+ return unchecked(this._values[index]);
65
133
  }
66
134
 
67
135
  public delete(key: Uint8Array): bool {
68
- const index: i32 = this.indexOf(key);
69
- if (index === -1) {
70
- return false;
136
+ const index = this.indexOf(key);
137
+ if (index === -1) return false;
138
+
139
+ const lastIndex = this._keys.length - 1;
140
+
141
+ // Swap and Pop (O(1) delete)
142
+ if (index !== lastIndex) {
143
+ // Move last element to the deleted position
144
+ unchecked((this._keys[index] = this._keys[lastIndex]));
145
+ unchecked((this._values[index] = this._values[lastIndex]));
146
+
147
+ // Update cache based on what was affected
148
+ if (this._lastIndex === lastIndex) {
149
+ // Cache pointed to last element, which moved to 'index'
150
+ this._lastIndex = index;
151
+ } else if (this._lastIndex === index) {
152
+ // Cache pointed to deleted element, invalidate it
153
+ this._lastIndex = -1;
154
+ }
155
+ // Note: If cache points to any other index, it remains valid
156
+ // because swap-and-pop only modifies positions 'index' and 'lastIndex'
157
+ } else {
158
+ // Deleting the last element (no swap needed)
159
+ if (this._lastIndex === lastIndex) {
160
+ this._lastIndex = -1;
161
+ }
162
+ // If cache points to any earlier index, it remains valid
71
163
  }
72
164
 
73
- this._keys.splice(index, 1);
74
- this._values.splice(index, 1);
165
+ this._keys.pop();
166
+ this._values.pop();
75
167
 
76
168
  return true;
77
169
  }
78
170
 
171
+ @inline
79
172
  public clear(): void {
80
173
  this._keys = [];
81
174
  this._values = [];
175
+ this._lastIndex = -1;
82
176
  }
83
177
 
84
178
  public toString(): string {
85
- const str: string = ``;
86
-
87
- for (let i: i32 = 0; i < this._keys.length; i++) {
88
- str.concat(`[${this._keys[i].toString()}] => [${this._values[i].toString()}]`);
89
- }
90
-
91
- return str;
179
+ return `Map(size=${this._keys.length})`;
92
180
  }
93
181
  }
@@ -15,7 +15,7 @@ export class StoredAddress {
15
15
  this.addressPointer = encodePointer(pointer, EMPTY_POINTER, true, 'StoredAddress');
16
16
  }
17
17
 
18
- private _value: Address = Address.dead();
18
+ private _value: Address = Address.zero();
19
19
 
20
20
  public get value(): Address {
21
21
  this.ensureValue();
@@ -34,7 +34,7 @@ export class StoredAddress {
34
34
  }
35
35
 
36
36
  public isDead(): bool {
37
- return eqUint(Address.dead(), this.value);
37
+ return eqUint(Address.zero(), this.value);
38
38
  }
39
39
 
40
40
  private ensureValue(): void {