@@ -81,8 +81,18 @@ if (typeof(URLSearchParams) !== "undefined") {
81
81
globalThis . prefetchResources = getBoolParam ( urlParameters , "prefetchResources" ) ;
82
82
}
83
83
84
+ if ( ! isInBrowser && globalThis . prefetchResources ) {
85
+ // Use the wasm compiled zlib as a polyfill when decompression stream is
86
+ // not available in JS shells.
87
+ load ( "./wasm/zlib/shell.js" ) ;
88
+
89
+ // Load a polyfill for TextEncoder/TextDecoder in shells. Used when
90
+ // decompressing a prefetched resource and converting it to text.
91
+ load ( "./polyfills/fast-text-encoding/1.0.3/text.js" ) ;
92
+ }
93
+
84
94
if ( ! globalThis . prefetchResources )
85
- console . warn ( "Disabling resource prefetching!" ) ;
95
+ console . warn ( "Disabling resource prefetching! All compressed files must have been decompressed using `node utils/compress.mjs -d` " ) ;
86
96
87
97
// Used for the promise representing the current benchmark run.
88
98
this . currentResolve = null ;
@@ -189,6 +199,21 @@ function uiFriendlyDuration(time) {
189
199
return `${ time . toFixed ( 3 ) } ms` ;
190
200
}
191
201
202
+ // Files can be zlib compressed to reduce the size of the JetStream source code.
203
+ // We don't use http compression because we support running from the shell and
204
+ // don't want to require a complicated server setup.
205
+ //
206
+ // zlib was chosen because we already have it in tree for the wasm-zlib test.
207
+ function isCompressed ( name ) {
208
+ return name . endsWith ( ".z" ) ;
209
+ }
210
+
211
+ function uncompressedName ( name ) {
212
+ if ( name . endsWith ( ".z" ) )
213
+ return name . slice ( 0 , - 2 ) ;
214
+ return name ;
215
+ }
216
+
192
217
// TODO: Cleanup / remove / merge. This is only used for caching loads in the
193
218
// non-browser setting. In the browser we use exclusively `loadCache`,
194
219
// `loadBlob`, `doLoadBlob`, `prefetchResourcesForBrowser` etc., see below.
@@ -201,14 +226,28 @@ class ShellFileLoader {
201
226
// share common code.
202
227
load ( url ) {
203
228
console . assert ( ! isInBrowser ) ;
229
+
230
+ let compressed = isCompressed ( url ) ;
231
+ if ( compressed && ! globalThis . prefetchResources ) {
232
+ url = uncompressedName ( url ) ;
233
+ }
234
+
235
+ // If we aren't supposed to prefetch this then return code snippet that will load the url on-demand.
204
236
if ( ! globalThis . prefetchResources )
205
237
return `load("${ url } ");`
206
238
207
239
if ( this . requests . has ( url ) ) {
208
240
return this . requests . get ( url ) ;
209
241
}
210
242
211
- const contents = readFile ( url ) ;
243
+ let contents ;
244
+ if ( compressed ) {
245
+ let bytes = new Int8Array ( read ( url , "binary" ) ) ;
246
+ bytes = zlib . decompress ( bytes ) ;
247
+ contents = new TextDecoder ( ) . decode ( bytes ) ;
248
+ } else {
249
+ contents = readFile ( url ) ;
250
+ }
212
251
this . requests . set ( url , contents ) ;
213
252
return contents ;
214
253
}
@@ -260,10 +299,14 @@ class Driver {
260
299
performance . mark ( "update-ui" ) ;
261
300
benchmark . updateUIAfterRun ( ) ;
262
301
263
- if ( isInBrowser && globalThis . prefetchResources ) {
302
+ if ( isInBrowser ) {
264
303
const cache = JetStream . blobDataCache ;
265
304
for ( const file of benchmark . files ) {
266
305
const blobData = cache [ file ] ;
306
+ // If we didn't prefetch this resource, then no need to free it
307
+ if ( ! blobData . blob ) {
308
+ continue
309
+ }
267
310
blobData . refCount -- ;
268
311
if ( ! blobData . refCount )
269
312
cache [ file ] = undefined ;
@@ -412,6 +455,9 @@ class Driver {
412
455
413
456
async prefetchResources ( ) {
414
457
if ( ! isInBrowser ) {
458
+ if ( globalThis . prefetchResources ) {
459
+ await zlib . initialize ( ) ;
460
+ }
415
461
for ( const benchmark of this . benchmarks )
416
462
benchmark . prefetchResourcesForShell ( ) ;
417
463
return ;
@@ -626,6 +672,11 @@ class Scripts {
626
672
}
627
673
628
674
class ShellScripts extends Scripts {
675
+ constructor ( ) {
676
+ super ( ) ;
677
+ this . prefetchedResources = [ ] ;
678
+ }
679
+
629
680
run ( ) {
630
681
let globalObject ;
631
682
let realm ;
@@ -652,13 +703,33 @@ class ShellScripts extends Scripts {
652
703
currentReject
653
704
} ;
654
705
706
+ // Pass the prefetched resources to the benchmark global.
707
+ if ( globalThis . prefetchResources ) {
708
+ // Pass the 'TextDecoder' polyfill into the benchmark global. Don't
709
+ // use 'TextDecoder' as that will get picked up in the kotlin test
710
+ // without full support.
711
+ globalObject . ShellTextDecoder = TextDecoder ;
712
+ // Store shellPrefetchedResources on ShellPrefetchedResources so that
713
+ // getBinary and getString can find them.
714
+ globalObject . ShellPrefetchedResources = { } ;
715
+ for ( const [ name , value ] of this . prefetchedResources ) {
716
+ globalObject . ShellPrefetchedResources [ name ] = value ;
717
+ }
718
+ } else {
719
+ console . assert ( this . prefetchedResources . length === 0 , "Unexpected prefetched resources" ) ;
720
+ }
721
+
655
722
globalObject . performance ??= performance ;
656
723
for ( const script of this . scripts )
657
724
globalObject . loadString ( script ) ;
658
725
659
726
return isD8 ? realm : globalObject ;
660
727
}
661
728
729
+ addPrefetchedResources ( prefetchedResources ) {
730
+ this . prefetchedResources . push ( ...prefetchedResources ) ;
731
+ }
732
+
662
733
add ( text ) {
663
734
this . scripts . push ( text ) ;
664
735
}
@@ -691,7 +762,6 @@ class BrowserScripts extends Scripts {
691
762
return magicFrame ;
692
763
}
693
764
694
-
695
765
add ( text ) {
696
766
this . scripts . push ( `<script>${ text } </script>` ) ;
697
767
}
@@ -711,6 +781,7 @@ class Benchmark {
711
781
this . allowUtf16 = ! ! plan . allowUtf16 ;
712
782
this . scripts = null ;
713
783
this . preloads = null ;
784
+ this . shellPrefetchedResources = null ;
714
785
this . results = [ ] ;
715
786
this . _state = BenchmarkState . READY ;
716
787
}
@@ -824,6 +895,9 @@ class Benchmark {
824
895
if ( ! ! this . plan . exposeBrowserTest )
825
896
scripts . addBrowserTest ( ) ;
826
897
898
+ if ( this . shellPrefetchedResources ) {
899
+ scripts . addPrefetchedResources ( this . shellPrefetchedResources ) ;
900
+ }
827
901
if ( this . plan . preload ) {
828
902
let preloadCode = "" ;
829
903
for ( let [ variableName , blobURLOrPath ] of this . preloads )
@@ -842,7 +916,7 @@ class Benchmark {
842
916
} else {
843
917
const cache = JetStream . blobDataCache ;
844
918
for ( const file of this . plan . files ) {
845
- scripts . addWithURL ( globalThis . prefetchResources ? cache [ file ] . blobURL : file ) ;
919
+ scripts . addWithURL ( cache [ file ] . blobURL ) ;
846
920
}
847
921
}
848
922
@@ -893,10 +967,19 @@ class Benchmark {
893
967
894
968
async doLoadBlob ( resource ) {
895
969
const blobData = JetStream . blobDataCache [ resource ] ;
970
+
971
+ const compressed = isCompressed ( resource ) ;
972
+ if ( compressed && ! globalThis . prefetchResources ) {
973
+ resource = uncompressedName ( resource ) ;
974
+ }
975
+
976
+ // If we aren't supposed to prefetch this then set the blobURL to just
977
+ // be the resource URL.
896
978
if ( ! globalThis . prefetchResources ) {
897
979
blobData . blobURL = resource ;
898
980
return blobData ;
899
981
}
982
+
900
983
let response ;
901
984
let tries = 3 ;
902
985
while ( tries -- ) {
@@ -912,7 +995,15 @@ class Benchmark {
912
995
continue ;
913
996
throw new Error ( "Fetch failed" ) ;
914
997
}
915
- const blob = await response . blob ( ) ;
998
+
999
+ // If we need to decompress this, then run it through a decompression
1000
+ // stream.
1001
+ if ( compressed ) {
1002
+ const stream = response . body . pipeThrough ( new DecompressionStream ( 'deflate' ) )
1003
+ response = new Response ( stream ) ;
1004
+ }
1005
+
1006
+ let blob = await response . blob ( ) ;
916
1007
blobData . blob = blob ;
917
1008
blobData . blobURL = URL . createObjectURL ( blob ) ;
918
1009
return blobData ;
@@ -1048,7 +1139,28 @@ class Benchmark {
1048
1139
this . scripts = this . plan . files . map ( file => shellFileLoader . load ( file ) ) ;
1049
1140
1050
1141
console . assert ( this . preloads === null , "This initialization should be called only once." ) ;
1051
- this . preloads = Object . entries ( this . plan . preload ?? { } ) ;
1142
+ this . preloads = [ ] ;
1143
+ this . shellPrefetchedResources = [ ] ;
1144
+ if ( this . plan . preload ) {
1145
+ for ( let name of Object . getOwnPropertyNames ( this . plan . preload ) ) {
1146
+ let file = this . plan . preload [ name ] ;
1147
+
1148
+ const compressed = isCompressed ( file ) ;
1149
+ if ( compressed && ! globalThis . prefetchResources ) {
1150
+ file = uncompressedName ( file ) ;
1151
+ }
1152
+
1153
+ if ( globalThis . prefetchResources ) {
1154
+ let bytes = new Int8Array ( read ( file , "binary" ) ) ;
1155
+ if ( compressed ) {
1156
+ bytes = zlib . decompress ( bytes ) ;
1157
+ }
1158
+ this . shellPrefetchedResources . push ( [ file , bytes ] ) ;
1159
+ }
1160
+
1161
+ this . preloads . push ( [ name , file ] ) ;
1162
+ }
1163
+ }
1052
1164
}
1053
1165
1054
1166
scoreIdentifiers ( ) {
@@ -1288,15 +1400,23 @@ class AsyncBenchmark extends DefaultBenchmark {
1288
1400
} else {
1289
1401
str += `
1290
1402
JetStream.getBinary = async function(path) {
1403
+ if (ShellPrefetchedResources) {
1404
+ return ShellPrefetchedResources[path];
1405
+ }
1291
1406
return new Int8Array(read(path, "binary"));
1292
1407
};
1293
1408
1294
1409
JetStream.getString = async function(path) {
1410
+ if (ShellPrefetchedResources) {
1411
+ return new ShellTextDecoder().decode(ShellPrefetchedResources[path]);
1412
+ }
1295
1413
return read(path);
1296
1414
};
1297
1415
1298
1416
JetStream.dynamicImport = async function(path) {
1299
1417
try {
1418
+ // TODO: this skips the prefetched resources, but I'm
1419
+ // not sure of a way around that.
1300
1420
return await import(path);
1301
1421
} catch (e) {
1302
1422
// In shells, relative imports require different paths, so try with and
@@ -1513,7 +1633,11 @@ class WasmLegacyBenchmark extends Benchmark {
1513
1633
` ;
1514
1634
} else {
1515
1635
str += `
1516
- Module[key] = new Int8Array(read(path, "binary"));
1636
+ if (ShellPrefetchedResources) {
1637
+ Module[key] = ShellPrefetchedResources[path];
1638
+ } else {
1639
+ Module[key] = new Int8Array(read(path, "binary"));
1640
+ }
1517
1641
if (andThen == doRun) {
1518
1642
globalObject.read = (...args) => {
1519
1643
console.log("should not be inside read: ", ...args);
0 commit comments