eigen-db 4.1.0 → 4.3.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/src/lib/simd.wat CHANGED
@@ -4,70 +4,38 @@
4
4
 
5
5
  ;; normalize(ptr: i32, dimensions: i32)
6
6
  ;; Normalizes a vector in-place to unit length using SIMD.
7
- ;; Optimized with 4x loop unrolling (16 floats/iteration) and multiple accumulators.
8
7
  (func (export "normalize") (param $ptr i32) (param $dim i32)
9
8
  (local $i i32)
10
- (local $acc0 v128)
11
- (local $acc1 v128)
12
- (local $acc2 v128)
13
- (local $acc3 v128)
9
+ (local $acc v128)
14
10
  (local $sum f32)
15
11
  (local $mag f32)
16
12
  (local $inv_mag f32)
17
13
  (local $inv_vec v128)
18
- (local $unroll_end i32)
19
14
  (local $simd_end i32)
20
15
  (local $offset i32)
21
16
 
22
- ;; Phase 1: Sum of squares with 4x unroll and 4 independent accumulators
23
- (local.set $acc0 (v128.const f32x4 0 0 0 0))
24
- (local.set $acc1 (v128.const f32x4 0 0 0 0))
25
- (local.set $acc2 (v128.const f32x4 0 0 0 0))
26
- (local.set $acc3 (v128.const f32x4 0 0 0 0))
27
- (local.set $unroll_end (i32.and (local.get $dim) (i32.const -16)))
17
+ ;; Phase 1: Sum of squares
18
+ (local.set $acc (v128.const f32x4 0 0 0 0))
28
19
  (local.set $simd_end (i32.and (local.get $dim) (i32.const -4)))
29
20
  (local.set $i (i32.const 0))
30
21
 
31
- (block $break_sum_u
32
- (loop $loop_sum_u
33
- (br_if $break_sum_u (i32.ge_u (local.get $i) (local.get $unroll_end)))
22
+ ;; SIMD loop: 4 floats per iteration
23
+ (block $break_sum
24
+ (loop $loop_sum
25
+ (br_if $break_sum (i32.ge_u (local.get $i) (local.get $simd_end)))
34
26
  (local.set $offset (i32.add (local.get $ptr) (i32.shl (local.get $i) (i32.const 2))))
35
-
36
- (local.set $acc0 (f32x4.add (local.get $acc0)
37
- (f32x4.mul (v128.load (local.get $offset)) (v128.load (local.get $offset)))))
38
- (local.set $acc1 (f32x4.add (local.get $acc1)
39
- (f32x4.mul (v128.load offset=16 (local.get $offset)) (v128.load offset=16 (local.get $offset)))))
40
- (local.set $acc2 (f32x4.add (local.get $acc2)
41
- (f32x4.mul (v128.load offset=32 (local.get $offset)) (v128.load offset=32 (local.get $offset)))))
42
- (local.set $acc3 (f32x4.add (local.get $acc3)
43
- (f32x4.mul (v128.load offset=48 (local.get $offset)) (v128.load offset=48 (local.get $offset)))))
44
-
45
- (local.set $i (i32.add (local.get $i) (i32.const 16)))
46
- (br $loop_sum_u)
47
- )
48
- )
49
-
50
- ;; Merge 4 accumulators
51
- (local.set $acc0 (f32x4.add (f32x4.add (local.get $acc0) (local.get $acc1))
52
- (f32x4.add (local.get $acc2) (local.get $acc3))))
53
-
54
- ;; Remaining 4-wide chunks
55
- (block $break_sum4
56
- (loop $loop_sum4
57
- (br_if $break_sum4 (i32.ge_u (local.get $i) (local.get $simd_end)))
58
- (local.set $offset (i32.add (local.get $ptr) (i32.shl (local.get $i) (i32.const 2))))
59
- (local.set $acc0 (f32x4.add (local.get $acc0)
27
+ (local.set $acc (f32x4.add (local.get $acc)
60
28
  (f32x4.mul (v128.load (local.get $offset)) (v128.load (local.get $offset)))))
61
29
  (local.set $i (i32.add (local.get $i) (i32.const 4)))
62
- (br $loop_sum4)
30
+ (br $loop_sum)
63
31
  )
64
32
  )
65
33
 
66
34
  ;; Horizontal sum
67
35
  (local.set $sum
68
36
  (f32.add
69
- (f32.add (f32x4.extract_lane 0 (local.get $acc0)) (f32x4.extract_lane 1 (local.get $acc0)))
70
- (f32.add (f32x4.extract_lane 2 (local.get $acc0)) (f32x4.extract_lane 3 (local.get $acc0)))))
37
+ (f32.add (f32x4.extract_lane 0 (local.get $acc)) (f32x4.extract_lane 1 (local.get $acc)))
38
+ (f32.add (f32x4.extract_lane 2 (local.get $acc)) (f32x4.extract_lane 3 (local.get $acc)))))
71
39
 
72
40
  ;; Scalar remainder
73
41
  (block $break_rem_sum
@@ -86,39 +54,20 @@
86
54
  (if (f32.eq (local.get $mag) (f32.const 0))
87
55
  (then (return)))
88
56
 
89
- ;; Phase 2: Scale by inverse magnitude (4x unrolled)
57
+ ;; Phase 2: Scale by inverse magnitude
90
58
  (local.set $inv_mag (f32.div (f32.const 1) (local.get $mag)))
91
59
  (local.set $inv_vec (f32x4.splat (local.get $inv_mag)))
92
60
  (local.set $i (i32.const 0))
93
61
 
94
- (block $break_norm_u
95
- (loop $loop_norm_u
96
- (br_if $break_norm_u (i32.ge_u (local.get $i) (local.get $unroll_end)))
97
- (local.set $offset (i32.add (local.get $ptr) (i32.shl (local.get $i) (i32.const 2))))
98
-
99
- (v128.store (local.get $offset)
100
- (f32x4.mul (v128.load (local.get $offset)) (local.get $inv_vec)))
101
- (v128.store offset=16 (local.get $offset)
102
- (f32x4.mul (v128.load offset=16 (local.get $offset)) (local.get $inv_vec)))
103
- (v128.store offset=32 (local.get $offset)
104
- (f32x4.mul (v128.load offset=32 (local.get $offset)) (local.get $inv_vec)))
105
- (v128.store offset=48 (local.get $offset)
106
- (f32x4.mul (v128.load offset=48 (local.get $offset)) (local.get $inv_vec)))
107
-
108
- (local.set $i (i32.add (local.get $i) (i32.const 16)))
109
- (br $loop_norm_u)
110
- )
111
- )
112
-
113
- ;; Remaining 4-wide chunks
114
- (block $break_norm4
115
- (loop $loop_norm4
116
- (br_if $break_norm4 (i32.ge_u (local.get $i) (local.get $simd_end)))
62
+ ;; SIMD loop
63
+ (block $break_norm
64
+ (loop $loop_norm
65
+ (br_if $break_norm (i32.ge_u (local.get $i) (local.get $simd_end)))
117
66
  (local.set $offset (i32.add (local.get $ptr) (i32.shl (local.get $i) (i32.const 2))))
118
67
  (v128.store (local.get $offset)
119
68
  (f32x4.mul (v128.load (local.get $offset)) (local.get $inv_vec)))
120
69
  (local.set $i (i32.add (local.get $i) (i32.const 4)))
121
- (br $loop_norm4)
70
+ (br $loop_norm)
122
71
  )
123
72
  )
124
73
 
@@ -137,226 +86,71 @@
137
86
 
138
87
  ;; search_all(query_ptr, db_ptr, scores_ptr, db_size, dimensions)
139
88
  ;; Computes dot products of query against every vector in the database.
140
- ;; Optimized with:
141
- ;; - 2-vector outer loop unrolling (halves query memory reads)
142
- ;; - 4x inner loop unrolling (16 floats/iteration, 4 accumulators per vector)
143
89
  (func (export "search_all") (param $query_ptr i32) (param $db_ptr i32) (param $scores_ptr i32) (param $db_size i32) (param $dim i32)
144
90
  (local $i i32)
145
91
  (local $j i32)
146
- (local $accA0 v128)
147
- (local $accA1 v128)
148
- (local $accA2 v128)
149
- (local $accA3 v128)
150
- (local $accB0 v128)
151
- (local $accB1 v128)
152
- (local $accB2 v128)
153
- (local $accB3 v128)
154
- (local $q0 v128)
155
- (local $q1 v128)
156
- (local $q2 v128)
157
- (local $q3 v128)
158
- (local $dotA f32)
159
- (local $dotB f32)
160
- (local $vec_ptrA i32)
161
- (local $vec_ptrB i32)
162
- (local $unroll_end i32)
92
+ (local $acc v128)
93
+ (local $dot f32)
94
+ (local $vec_ptr i32)
163
95
  (local $simd_end i32)
164
96
  (local $q_offset i32)
165
- (local $vA_offset i32)
166
- (local $vB_offset i32)
97
+ (local $v_offset i32)
167
98
  (local $bytes_per_vec i32)
168
- (local $pair_end i32)
169
99
 
170
- (local.set $unroll_end (i32.and (local.get $dim) (i32.const -16)))
171
100
  (local.set $simd_end (i32.and (local.get $dim) (i32.const -4)))
172
101
  (local.set $bytes_per_vec (i32.shl (local.get $dim) (i32.const 2)))
173
- (local.set $pair_end (i32.and (local.get $db_size) (i32.const -2)))
174
102
  (local.set $i (i32.const 0))
175
103
 
176
- ;; Main loop: process 2 database vectors per iteration
104
+ ;; Outer loop: one database vector per iteration
177
105
  (block $break_outer
178
106
  (loop $loop_outer
179
- (br_if $break_outer (i32.ge_u (local.get $i) (local.get $pair_end)))
107
+ (br_if $break_outer (i32.ge_u (local.get $i) (local.get $db_size)))
180
108
 
181
- (local.set $vec_ptrA
109
+ (local.set $vec_ptr
182
110
  (i32.add (local.get $db_ptr) (i32.mul (local.get $i) (local.get $bytes_per_vec))))
183
- (local.set $vec_ptrB
184
- (i32.add (local.get $vec_ptrA) (local.get $bytes_per_vec)))
185
-
186
- (local.set $accA0 (v128.const f32x4 0 0 0 0))
187
- (local.set $accA1 (v128.const f32x4 0 0 0 0))
188
- (local.set $accA2 (v128.const f32x4 0 0 0 0))
189
- (local.set $accA3 (v128.const f32x4 0 0 0 0))
190
- (local.set $accB0 (v128.const f32x4 0 0 0 0))
191
- (local.set $accB1 (v128.const f32x4 0 0 0 0))
192
- (local.set $accB2 (v128.const f32x4 0 0 0 0))
193
- (local.set $accB3 (v128.const f32x4 0 0 0 0))
111
+ (local.set $acc (v128.const f32x4 0 0 0 0))
194
112
  (local.set $j (i32.const 0))
195
113
 
196
- ;; Inner loop: load query once, dot product against both vectors
114
+ ;; SIMD inner loop: dot product, 4 floats per iteration
197
115
  (block $break_inner
198
116
  (loop $loop_inner
199
- (br_if $break_inner (i32.ge_u (local.get $j) (local.get $unroll_end)))
200
- (local.set $q_offset (i32.add (local.get $query_ptr) (i32.shl (local.get $j) (i32.const 2))))
201
- (local.set $vA_offset (i32.add (local.get $vec_ptrA) (i32.shl (local.get $j) (i32.const 2))))
202
- (local.set $vB_offset (i32.add (local.get $vec_ptrB) (i32.shl (local.get $j) (i32.const 2))))
203
-
204
- ;; Load query chunk (shared between both vectors)
205
- (local.set $q0 (v128.load (local.get $q_offset)))
206
- (local.set $q1 (v128.load offset=16 (local.get $q_offset)))
207
- (local.set $q2 (v128.load offset=32 (local.get $q_offset)))
208
- (local.set $q3 (v128.load offset=48 (local.get $q_offset)))
209
-
210
- ;; Vector A
211
- (local.set $accA0 (f32x4.add (local.get $accA0)
212
- (f32x4.mul (local.get $q0) (v128.load (local.get $vA_offset)))))
213
- (local.set $accA1 (f32x4.add (local.get $accA1)
214
- (f32x4.mul (local.get $q1) (v128.load offset=16 (local.get $vA_offset)))))
215
- (local.set $accA2 (f32x4.add (local.get $accA2)
216
- (f32x4.mul (local.get $q2) (v128.load offset=32 (local.get $vA_offset)))))
217
- (local.set $accA3 (f32x4.add (local.get $accA3)
218
- (f32x4.mul (local.get $q3) (v128.load offset=48 (local.get $vA_offset)))))
219
-
220
- ;; Vector B (reuses query loads)
221
- (local.set $accB0 (f32x4.add (local.get $accB0)
222
- (f32x4.mul (local.get $q0) (v128.load (local.get $vB_offset)))))
223
- (local.set $accB1 (f32x4.add (local.get $accB1)
224
- (f32x4.mul (local.get $q1) (v128.load offset=16 (local.get $vB_offset)))))
225
- (local.set $accB2 (f32x4.add (local.get $accB2)
226
- (f32x4.mul (local.get $q2) (v128.load offset=32 (local.get $vB_offset)))))
227
- (local.set $accB3 (f32x4.add (local.get $accB3)
228
- (f32x4.mul (local.get $q3) (v128.load offset=48 (local.get $vB_offset)))))
229
-
230
- (local.set $j (i32.add (local.get $j) (i32.const 16)))
231
- (br $loop_inner)
232
- )
233
- )
234
-
235
- ;; Merge accumulators
236
- (local.set $accA0 (f32x4.add (f32x4.add (local.get $accA0) (local.get $accA1))
237
- (f32x4.add (local.get $accA2) (local.get $accA3))))
238
- (local.set $accB0 (f32x4.add (f32x4.add (local.get $accB0) (local.get $accB1))
239
- (f32x4.add (local.get $accB2) (local.get $accB3))))
240
-
241
- ;; 4-wide cleanup (both vectors)
242
- (block $break_inner4
243
- (loop $loop_inner4
244
- (br_if $break_inner4 (i32.ge_u (local.get $j) (local.get $simd_end)))
117
+ (br_if $break_inner (i32.ge_u (local.get $j) (local.get $simd_end)))
245
118
  (local.set $q_offset (i32.add (local.get $query_ptr) (i32.shl (local.get $j) (i32.const 2))))
246
- (local.set $vA_offset (i32.add (local.get $vec_ptrA) (i32.shl (local.get $j) (i32.const 2))))
247
- (local.set $vB_offset (i32.add (local.get $vec_ptrB) (i32.shl (local.get $j) (i32.const 2))))
248
- (local.set $q0 (v128.load (local.get $q_offset)))
249
- (local.set $accA0 (f32x4.add (local.get $accA0) (f32x4.mul (local.get $q0) (v128.load (local.get $vA_offset)))))
250
- (local.set $accB0 (f32x4.add (local.get $accB0) (f32x4.mul (local.get $q0) (v128.load (local.get $vB_offset)))))
119
+ (local.set $v_offset (i32.add (local.get $vec_ptr) (i32.shl (local.get $j) (i32.const 2))))
120
+ (local.set $acc (f32x4.add (local.get $acc)
121
+ (f32x4.mul (v128.load (local.get $q_offset)) (v128.load (local.get $v_offset)))))
251
122
  (local.set $j (i32.add (local.get $j) (i32.const 4)))
252
- (br $loop_inner4)
123
+ (br $loop_inner)
253
124
  )
254
125
  )
255
126
 
256
- ;; Horizontal sums
257
- (local.set $dotA
127
+ ;; Horizontal sum
128
+ (local.set $dot
258
129
  (f32.add
259
- (f32.add (f32x4.extract_lane 0 (local.get $accA0)) (f32x4.extract_lane 1 (local.get $accA0)))
260
- (f32.add (f32x4.extract_lane 2 (local.get $accA0)) (f32x4.extract_lane 3 (local.get $accA0)))))
261
- (local.set $dotB
262
- (f32.add
263
- (f32.add (f32x4.extract_lane 0 (local.get $accB0)) (f32x4.extract_lane 1 (local.get $accB0)))
264
- (f32.add (f32x4.extract_lane 2 (local.get $accB0)) (f32x4.extract_lane 3 (local.get $accB0)))))
130
+ (f32.add (f32x4.extract_lane 0 (local.get $acc)) (f32x4.extract_lane 1 (local.get $acc)))
131
+ (f32.add (f32x4.extract_lane 2 (local.get $acc)) (f32x4.extract_lane 3 (local.get $acc)))))
265
132
 
266
- ;; Scalar remainder (both vectors)
133
+ ;; Scalar remainder
267
134
  (block $break_rem
268
135
  (loop $loop_rem
269
136
  (br_if $break_rem (i32.ge_u (local.get $j) (local.get $dim)))
270
137
  (local.set $q_offset (i32.add (local.get $query_ptr) (i32.shl (local.get $j) (i32.const 2))))
271
- (local.set $vA_offset (i32.add (local.get $vec_ptrA) (i32.shl (local.get $j) (i32.const 2))))
272
- (local.set $vB_offset (i32.add (local.get $vec_ptrB) (i32.shl (local.get $j) (i32.const 2))))
273
- (local.set $dotA (f32.add (local.get $dotA)
274
- (f32.mul (f32.load (local.get $q_offset)) (f32.load (local.get $vA_offset)))))
275
- (local.set $dotB (f32.add (local.get $dotB)
276
- (f32.mul (f32.load (local.get $q_offset)) (f32.load (local.get $vB_offset)))))
138
+ (local.set $v_offset (i32.add (local.get $vec_ptr) (i32.shl (local.get $j) (i32.const 2))))
139
+ (local.set $dot (f32.add (local.get $dot)
140
+ (f32.mul (f32.load (local.get $q_offset)) (f32.load (local.get $v_offset)))))
277
141
  (local.set $j (i32.add (local.get $j) (i32.const 1)))
278
142
  (br $loop_rem)
279
143
  )
280
144
  )
281
145
 
282
- ;; Store scores for vectors i and i+1
146
+ ;; Store score
283
147
  (f32.store
284
148
  (i32.add (local.get $scores_ptr) (i32.shl (local.get $i) (i32.const 2)))
285
- (local.get $dotA))
286
- (f32.store
287
- (i32.add (local.get $scores_ptr) (i32.shl (i32.add (local.get $i) (i32.const 1)) (i32.const 2)))
288
- (local.get $dotB))
149
+ (local.get $dot))
289
150
 
290
- (local.set $i (i32.add (local.get $i) (i32.const 2)))
151
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
291
152
  (br $loop_outer)
292
153
  )
293
154
  )
294
-
295
- ;; Handle last vector if db_size is odd
296
- (if (i32.lt_u (local.get $i) (local.get $db_size))
297
- (then
298
- (local.set $vec_ptrA
299
- (i32.add (local.get $db_ptr) (i32.mul (local.get $i) (local.get $bytes_per_vec))))
300
- (local.set $accA0 (v128.const f32x4 0 0 0 0))
301
- (local.set $accA1 (v128.const f32x4 0 0 0 0))
302
- (local.set $accA2 (v128.const f32x4 0 0 0 0))
303
- (local.set $accA3 (v128.const f32x4 0 0 0 0))
304
- (local.set $j (i32.const 0))
305
-
306
- (block $break_last
307
- (loop $loop_last
308
- (br_if $break_last (i32.ge_u (local.get $j) (local.get $unroll_end)))
309
- (local.set $q_offset (i32.add (local.get $query_ptr) (i32.shl (local.get $j) (i32.const 2))))
310
- (local.set $vA_offset (i32.add (local.get $vec_ptrA) (i32.shl (local.get $j) (i32.const 2))))
311
- (local.set $accA0 (f32x4.add (local.get $accA0)
312
- (f32x4.mul (v128.load (local.get $q_offset)) (v128.load (local.get $vA_offset)))))
313
- (local.set $accA1 (f32x4.add (local.get $accA1)
314
- (f32x4.mul (v128.load offset=16 (local.get $q_offset)) (v128.load offset=16 (local.get $vA_offset)))))
315
- (local.set $accA2 (f32x4.add (local.get $accA2)
316
- (f32x4.mul (v128.load offset=32 (local.get $q_offset)) (v128.load offset=32 (local.get $vA_offset)))))
317
- (local.set $accA3 (f32x4.add (local.get $accA3)
318
- (f32x4.mul (v128.load offset=48 (local.get $q_offset)) (v128.load offset=48 (local.get $vA_offset)))))
319
- (local.set $j (i32.add (local.get $j) (i32.const 16)))
320
- (br $loop_last)
321
- )
322
- )
323
-
324
- (local.set $accA0 (f32x4.add (f32x4.add (local.get $accA0) (local.get $accA1))
325
- (f32x4.add (local.get $accA2) (local.get $accA3))))
326
-
327
- (block $break_last4
328
- (loop $loop_last4
329
- (br_if $break_last4 (i32.ge_u (local.get $j) (local.get $simd_end)))
330
- (local.set $q_offset (i32.add (local.get $query_ptr) (i32.shl (local.get $j) (i32.const 2))))
331
- (local.set $vA_offset (i32.add (local.get $vec_ptrA) (i32.shl (local.get $j) (i32.const 2))))
332
- (local.set $accA0 (f32x4.add (local.get $accA0)
333
- (f32x4.mul (v128.load (local.get $q_offset)) (v128.load (local.get $vA_offset)))))
334
- (local.set $j (i32.add (local.get $j) (i32.const 4)))
335
- (br $loop_last4)
336
- )
337
- )
338
-
339
- (local.set $dotA
340
- (f32.add
341
- (f32.add (f32x4.extract_lane 0 (local.get $accA0)) (f32x4.extract_lane 1 (local.get $accA0)))
342
- (f32.add (f32x4.extract_lane 2 (local.get $accA0)) (f32x4.extract_lane 3 (local.get $accA0)))))
343
-
344
- (block $break_last_rem
345
- (loop $loop_last_rem
346
- (br_if $break_last_rem (i32.ge_u (local.get $j) (local.get $dim)))
347
- (local.set $q_offset (i32.add (local.get $query_ptr) (i32.shl (local.get $j) (i32.const 2))))
348
- (local.set $vA_offset (i32.add (local.get $vec_ptrA) (i32.shl (local.get $j) (i32.const 2))))
349
- (local.set $dotA (f32.add (local.get $dotA)
350
- (f32.mul (f32.load (local.get $q_offset)) (f32.load (local.get $vA_offset)))))
351
- (local.set $j (i32.add (local.get $j) (i32.const 1)))
352
- (br $loop_last_rem)
353
- )
354
- )
355
-
356
- (f32.store
357
- (i32.add (local.get $scores_ptr) (i32.shl (local.get $i) (i32.const 2)))
358
- (local.get $dotA))
359
- )
360
- )
361
155
  )
362
156
  )
package/src/lib/types.ts CHANGED
@@ -2,20 +2,18 @@
2
2
  * Options for opening a VectorDB instance.
3
3
  */
4
4
  export interface OpenOptions {
5
- /** Name of the OPFS directory. Defaults to "default". */
6
- name?: string;
7
5
  /** Vector dimensions (e.g., 1536 for OpenAI text-embedding-3-small) */
8
6
  dimensions: number;
9
7
  /** Whether to normalize vectors on set/query (default: true). */
10
8
  normalize?: boolean;
9
+ /** Storage provider for persistence. Defaults to InMemoryStorageProvider. Use OPFSStorageProvider for browser persistence. */
10
+ storage?: import("./storage").StorageProvider;
11
11
  }
12
12
 
13
13
  /**
14
14
  * Extended options including internal overrides for testing/advanced use.
15
15
  */
16
16
  export interface OpenOptionsInternal extends OpenOptions {
17
- /** Override the storage provider (default: OPFS). Useful for testing. */
18
- storage?: import("./storage").StorageProvider;
19
17
  /** Pre-compiled WASM binary. If not provided, uses the embedded SIMD binary. Set to null to force JS-only mode. */
20
18
  wasmBinary?: Uint8Array | null;
21
19
  }
@@ -41,8 +39,8 @@ export interface SetOptions {
41
39
  export interface QueryOptions {
42
40
  /** Maximum number of results to return. Defaults to Infinity (all results). */
43
41
  topK?: number;
44
- /** Maximum distance threshold (inclusive). Results with distance > maxDistance are excluded. */
45
- maxDistance?: number;
42
+ /** Minimum similarity threshold (inclusive). Results with similarity < minSimilarity are excluded. */
43
+ minSimilarity?: number;
46
44
  /** Override normalization for this call. */
47
45
  normalize?: boolean;
48
46
  /** When true, returns an Iterable<ResultItem> instead of ResultItem[]. */