@@ -31,7 +31,17 @@ const defaultIterationCount = 120;
31
31
const defaultWorstCaseCount = 4 ;
32
32
33
33
if ( ! JetStreamParams . prefetchResources )
34
- console . warn ( "Disabling resource prefetching!" ) ;
34
+ console . warn ( "Disabling resource prefetching! All compressed files must have been decompressed using `node utils/compress.mjs -d`" ) ;
35
+
36
+ if ( ! isInBrowser && JetStreamParams . prefetchResources ) {
37
+ // Use the wasm compiled zlib as a polyfill when decompression stream is
38
+ // not available in JS shells.
39
+ load ( "./wasm/zlib/shell.js" ) ;
40
+
41
+ // Load a polyfill for TextEncoder/TextDecoder in shells. Used when
42
+ // decompressing a prefetched resource and converting it to text.
43
+ load ( "./polyfills/fast-text-encoding/1.0.3/text.js" ) ;
44
+ }
35
45
36
46
// Used for the promise representing the current benchmark run.
37
47
this . currentResolve = null ;
@@ -138,6 +148,21 @@ function uiFriendlyDuration(time) {
138
148
return `${ time . toFixed ( 3 ) } ms` ;
139
149
}
140
150
151
+ // Files can be zlib compressed to reduce the size of the JetStream source code.
152
+ // We don't use http compression because we support running from the shell and
153
+ // don't want to require a complicated server setup.
154
+ //
155
+ // zlib was chosen because we already have it in tree for the wasm-zlib test.
156
+ function isCompressed ( name ) {
157
+ return name . endsWith ( ".z" ) ;
158
+ }
159
+
160
+ function uncompressedName ( name ) {
161
+ if ( name . endsWith ( ".z" ) )
162
+ return name . slice ( 0 , - 2 ) ;
163
+ return name ;
164
+ }
165
+
141
166
// TODO: Cleanup / remove / merge. This is only used for caching loads in the
142
167
// non-browser setting. In the browser we use exclusively `loadCache`,
143
168
// `loadBlob`, `doLoadBlob`, `prefetchResourcesForBrowser` etc., see below.
@@ -150,14 +175,28 @@ class ShellFileLoader {
150
175
// share common code.
151
176
load ( url ) {
152
177
console . assert ( ! isInBrowser ) ;
178
+
179
+ let compressed = isCompressed ( url ) ;
180
+ if ( compressed && ! JetStreamParams . prefetchResources ) {
181
+ url = uncompressedName ( url ) ;
182
+ }
183
+
184
+ // If we aren't supposed to prefetch this then return code snippet that will load the url on-demand.
153
185
if ( ! JetStreamParams . prefetchResources )
154
186
return `load("${ url } ");`
155
187
156
188
if ( this . requests . has ( url ) ) {
157
189
return this . requests . get ( url ) ;
158
190
}
159
191
160
- const contents = readFile ( url ) ;
192
+ let contents ;
193
+ if ( compressed ) {
194
+ let bytes = new Int8Array ( read ( url , "binary" ) ) ;
195
+ bytes = zlib . decompress ( bytes ) ;
196
+ contents = new TextDecoder ( ) . decode ( bytes ) ;
197
+ } else {
198
+ contents = readFile ( url ) ;
199
+ }
161
200
this . requests . set ( url , contents ) ;
162
201
return contents ;
163
202
}
@@ -209,10 +248,14 @@ class Driver {
209
248
performance . mark ( "update-ui" ) ;
210
249
benchmark . updateUIAfterRun ( ) ;
211
250
212
- if ( isInBrowser && JetStreamParams . prefetchResources ) {
251
+ if ( isInBrowser ) {
213
252
const cache = JetStream . blobDataCache ;
214
253
for ( const file of benchmark . files ) {
215
254
const blobData = cache [ file ] ;
255
+ // If we didn't prefetch this resource, then no need to free it
256
+ if ( ! blobData . blob ) {
257
+ continue
258
+ }
216
259
blobData . refCount -- ;
217
260
if ( ! blobData . refCount )
218
261
cache [ file ] = undefined ;
@@ -338,6 +381,9 @@ class Driver {
338
381
339
382
async prefetchResources ( ) {
340
383
if ( ! isInBrowser ) {
384
+ if ( JetStreamParams . prefetchResources ) {
385
+ await zlib . initialize ( ) ;
386
+ }
341
387
for ( const benchmark of this . benchmarks )
342
388
benchmark . prefetchResourcesForShell ( ) ;
343
389
return ;
@@ -553,6 +599,11 @@ class Scripts {
553
599
}
554
600
555
601
class ShellScripts extends Scripts {
602
+ constructor ( ) {
603
+ super ( ) ;
604
+ this . prefetchedResources = [ ] ;
605
+ }
606
+
556
607
run ( ) {
557
608
let globalObject ;
558
609
let realm ;
@@ -579,13 +630,33 @@ class ShellScripts extends Scripts {
579
630
currentReject
580
631
} ;
581
632
633
+ // Pass the prefetched resources to the benchmark global.
634
+ if ( JetStreamParams . prefetchResources ) {
635
+ // Pass the 'TextDecoder' polyfill into the benchmark global. Don't
636
+ // use 'TextDecoder' as that will get picked up in the kotlin test
637
+ // without full support.
638
+ globalObject . ShellTextDecoder = TextDecoder ;
639
+ // Store shellPrefetchedResources on ShellPrefetchedResources so that
640
+ // getBinary and getString can find them.
641
+ globalObject . ShellPrefetchedResources = { } ;
642
+ for ( const [ name , value ] of this . prefetchedResources ) {
643
+ globalObject . ShellPrefetchedResources [ name ] = value ;
644
+ }
645
+ } else {
646
+ console . assert ( this . prefetchedResources . length === 0 , "Unexpected prefetched resources" ) ;
647
+ }
648
+
582
649
globalObject . performance ??= performance ;
583
650
for ( const script of this . scripts )
584
651
globalObject . loadString ( script ) ;
585
652
586
653
return isD8 ? realm : globalObject ;
587
654
}
588
655
656
+ addPrefetchedResources ( prefetchedResources ) {
657
+ this . prefetchedResources . push ( ...prefetchedResources ) ;
658
+ }
659
+
589
660
add ( text ) {
590
661
this . scripts . push ( text ) ;
591
662
}
@@ -618,7 +689,6 @@ class BrowserScripts extends Scripts {
618
689
return magicFrame ;
619
690
}
620
691
621
-
622
692
add ( text ) {
623
693
this . scripts . push ( `<script>${ text } </script>` ) ;
624
694
}
@@ -638,6 +708,7 @@ class Benchmark {
638
708
this . allowUtf16 = ! ! plan . allowUtf16 ;
639
709
this . scripts = null ;
640
710
this . preloads = null ;
711
+ this . shellPrefetchedResources = null ;
641
712
this . results = [ ] ;
642
713
this . _state = BenchmarkState . READY ;
643
714
}
@@ -771,6 +842,9 @@ class Benchmark {
771
842
if ( ! ! this . plan . exposeBrowserTest )
772
843
scripts . addBrowserTest ( ) ;
773
844
845
+ if ( this . shellPrefetchedResources ) {
846
+ scripts . addPrefetchedResources ( this . shellPrefetchedResources ) ;
847
+ }
774
848
if ( this . plan . preload ) {
775
849
let preloadCode = "" ;
776
850
for ( let [ variableName , blobURLOrPath ] of this . preloads )
@@ -789,7 +863,7 @@ class Benchmark {
789
863
} else {
790
864
const cache = JetStream . blobDataCache ;
791
865
for ( const file of this . plan . files ) {
792
- scripts . addWithURL ( JetStreamParams . prefetchResources ? cache [ file ] . blobURL : file ) ;
866
+ scripts . addWithURL ( cache [ file ] . blobURL ) ;
793
867
}
794
868
}
795
869
@@ -840,10 +914,19 @@ class Benchmark {
840
914
841
915
async doLoadBlob ( resource ) {
842
916
const blobData = JetStream . blobDataCache [ resource ] ;
917
+
918
+ const compressed = isCompressed ( resource ) ;
919
+ if ( compressed && ! JetStreamParams . prefetchResources ) {
920
+ resource = uncompressedName ( resource ) ;
921
+ }
922
+
923
+ // If we aren't supposed to prefetch this then set the blobURL to just
924
+ // be the resource URL.
843
925
if ( ! JetStreamParams . prefetchResources ) {
844
926
blobData . blobURL = resource ;
845
927
return blobData ;
846
928
}
929
+
847
930
let response ;
848
931
let tries = 3 ;
849
932
while ( tries -- ) {
@@ -859,7 +942,15 @@ class Benchmark {
859
942
continue ;
860
943
throw new Error ( "Fetch failed" ) ;
861
944
}
862
- const blob = await response . blob ( ) ;
945
+
946
+ // If we need to decompress this, then run it through a decompression
947
+ // stream.
948
+ if ( compressed ) {
949
+ const stream = response . body . pipeThrough ( new DecompressionStream ( 'deflate' ) )
950
+ response = new Response ( stream ) ;
951
+ }
952
+
953
+ let blob = await response . blob ( ) ;
863
954
blobData . blob = blob ;
864
955
blobData . blobURL = URL . createObjectURL ( blob ) ;
865
956
return blobData ;
@@ -995,7 +1086,28 @@ class Benchmark {
995
1086
this . scripts = this . plan . files . map ( file => shellFileLoader . load ( file ) ) ;
996
1087
997
1088
console . assert ( this . preloads === null , "This initialization should be called only once." ) ;
998
- this . preloads = Object . entries ( this . plan . preload ?? { } ) ;
1089
+ this . preloads = [ ] ;
1090
+ this . shellPrefetchedResources = [ ] ;
1091
+ if ( this . plan . preload ) {
1092
+ for ( let name of Object . getOwnPropertyNames ( this . plan . preload ) ) {
1093
+ let file = this . plan . preload [ name ] ;
1094
+
1095
+ const compressed = isCompressed ( file ) ;
1096
+ if ( compressed && ! JetStreamParams . prefetchResources ) {
1097
+ file = uncompressedName ( file ) ;
1098
+ }
1099
+
1100
+ if ( JetStreamParams . prefetchResources ) {
1101
+ let bytes = new Int8Array ( read ( file , "binary" ) ) ;
1102
+ if ( compressed ) {
1103
+ bytes = zlib . decompress ( bytes ) ;
1104
+ }
1105
+ this . shellPrefetchedResources . push ( [ file , bytes ] ) ;
1106
+ }
1107
+
1108
+ this . preloads . push ( [ name , file ] ) ;
1109
+ }
1110
+ }
999
1111
}
1000
1112
1001
1113
scoreIdentifiers ( ) {
@@ -1268,15 +1380,23 @@ class AsyncBenchmark extends DefaultBenchmark {
1268
1380
} else {
1269
1381
str += `
1270
1382
JetStream.getBinary = async function(path) {
1383
+ if (ShellPrefetchedResources) {
1384
+ return ShellPrefetchedResources[path];
1385
+ }
1271
1386
return new Int8Array(read(path, "binary"));
1272
1387
};
1273
1388
1274
1389
JetStream.getString = async function(path) {
1390
+ if (ShellPrefetchedResources) {
1391
+ return new ShellTextDecoder().decode(ShellPrefetchedResources[path]);
1392
+ }
1275
1393
return read(path);
1276
1394
};
1277
1395
1278
1396
JetStream.dynamicImport = async function(path) {
1279
1397
try {
1398
+ // TODO: this skips the prefetched resources, but I'm
1399
+ // not sure of a way around that.
1280
1400
return await import(path);
1281
1401
} catch (e) {
1282
1402
// In shells, relative imports require different paths, so try with and
@@ -1493,7 +1613,11 @@ class WasmLegacyBenchmark extends Benchmark {
1493
1613
` ;
1494
1614
} else {
1495
1615
str += `
1496
- Module[key] = new Int8Array(read(path, "binary"));
1616
+ if (ShellPrefetchedResources) {
1617
+ Module[key] = ShellPrefetchedResources[path];
1618
+ } else {
1619
+ Module[key] = new Int8Array(read(path, "binary"));
1620
+ }
1497
1621
if (andThen == doRun) {
1498
1622
globalObject.read = (...args) => {
1499
1623
console.log("should not be inside read: ", ...args);
0 commit comments