@@ -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 ;
@@ -361,6 +404,9 @@ class Driver {
361
404
362
405
async prefetchResources ( ) {
363
406
if ( ! isInBrowser ) {
407
+ if ( JetStreamParams . prefetchResources ) {
408
+ await zlib . initialize ( ) ;
409
+ }
364
410
for ( const benchmark of this . benchmarks )
365
411
benchmark . prefetchResourcesForShell ( ) ;
366
412
return ;
@@ -576,6 +622,11 @@ class Scripts {
576
622
}
577
623
578
624
class ShellScripts extends Scripts {
625
+ constructor ( ) {
626
+ super ( ) ;
627
+ this . prefetchedResources = [ ] ;
628
+ }
629
+
579
630
run ( ) {
580
631
let globalObject ;
581
632
let realm ;
@@ -602,13 +653,33 @@ class ShellScripts extends Scripts {
602
653
currentReject
603
654
} ;
604
655
656
+ // Pass the prefetched resources to the benchmark global.
657
+ if ( JetStreamParams . prefetchResources ) {
658
+ // Pass the 'TextDecoder' polyfill into the benchmark global. Don't
659
+ // use 'TextDecoder' as that will get picked up in the kotlin test
660
+ // without full support.
661
+ globalObject . ShellTextDecoder = TextDecoder ;
662
+ // Store shellPrefetchedResources on ShellPrefetchedResources so that
663
+ // getBinary and getString can find them.
664
+ globalObject . ShellPrefetchedResources = { } ;
665
+ for ( const [ name , value ] of this . prefetchedResources ) {
666
+ globalObject . ShellPrefetchedResources [ name ] = value ;
667
+ }
668
+ } else {
669
+ console . assert ( this . prefetchedResources . length === 0 , "Unexpected prefetched resources" ) ;
670
+ }
671
+
605
672
globalObject . performance ??= performance ;
606
673
for ( const script of this . scripts )
607
674
globalObject . loadString ( script ) ;
608
675
609
676
return isD8 ? realm : globalObject ;
610
677
}
611
678
679
+ addPrefetchedResources ( prefetchedResources ) {
680
+ this . prefetchedResources . push ( ...prefetchedResources ) ;
681
+ }
682
+
612
683
add ( text ) {
613
684
this . scripts . push ( text ) ;
614
685
}
@@ -641,7 +712,6 @@ class BrowserScripts extends Scripts {
641
712
return magicFrame ;
642
713
}
643
714
644
-
645
715
add ( text ) {
646
716
this . scripts . push ( `<script>${ text } </script>` ) ;
647
717
}
@@ -661,6 +731,7 @@ class Benchmark {
661
731
this . allowUtf16 = ! ! plan . allowUtf16 ;
662
732
this . scripts = null ;
663
733
this . preloads = null ;
734
+ this . shellPrefetchedResources = null ;
664
735
this . results = [ ] ;
665
736
this . _state = BenchmarkState . READY ;
666
737
}
@@ -774,6 +845,9 @@ class Benchmark {
774
845
if ( ! ! this . plan . exposeBrowserTest )
775
846
scripts . addBrowserTest ( ) ;
776
847
848
+ if ( this . shellPrefetchedResources ) {
849
+ scripts . addPrefetchedResources ( this . shellPrefetchedResources ) ;
850
+ }
777
851
if ( this . plan . preload ) {
778
852
let preloadCode = "" ;
779
853
for ( let [ variableName , blobURLOrPath ] of this . preloads )
@@ -792,7 +866,7 @@ class Benchmark {
792
866
} else {
793
867
const cache = JetStream . blobDataCache ;
794
868
for ( const file of this . plan . files ) {
795
- scripts . addWithURL ( JetStreamParams . prefetchResources ? cache [ file ] . blobURL : file ) ;
869
+ scripts . addWithURL ( cache [ file ] . blobURL ) ;
796
870
}
797
871
}
798
872
@@ -843,10 +917,19 @@ class Benchmark {
843
917
844
918
async doLoadBlob ( resource ) {
845
919
const blobData = JetStream . blobDataCache [ resource ] ;
920
+
921
+ const compressed = isCompressed ( resource ) ;
922
+ if ( compressed && ! JetStreamParams . prefetchResources ) {
923
+ resource = uncompressedName ( resource ) ;
924
+ }
925
+
926
+ // If we aren't supposed to prefetch this then set the blobURL to just
927
+ // be the resource URL.
846
928
if ( ! JetStreamParams . prefetchResources ) {
847
929
blobData . blobURL = resource ;
848
930
return blobData ;
849
931
}
932
+
850
933
let response ;
851
934
let tries = 3 ;
852
935
while ( tries -- ) {
@@ -862,7 +945,15 @@ class Benchmark {
862
945
continue ;
863
946
throw new Error ( "Fetch failed" ) ;
864
947
}
865
- const blob = await response . blob ( ) ;
948
+
949
+ // If we need to decompress this, then run it through a decompression
950
+ // stream.
951
+ if ( compressed ) {
952
+ const stream = response . body . pipeThrough ( new DecompressionStream ( 'deflate' ) )
953
+ response = new Response ( stream ) ;
954
+ }
955
+
956
+ let blob = await response . blob ( ) ;
866
957
blobData . blob = blob ;
867
958
blobData . blobURL = URL . createObjectURL ( blob ) ;
868
959
return blobData ;
@@ -998,7 +1089,28 @@ class Benchmark {
998
1089
this . scripts = this . plan . files . map ( file => shellFileLoader . load ( file ) ) ;
999
1090
1000
1091
console . assert ( this . preloads === null , "This initialization should be called only once." ) ;
1001
- this . preloads = Object . entries ( this . plan . preload ?? { } ) ;
1092
+ this . preloads = [ ] ;
1093
+ this . shellPrefetchedResources = [ ] ;
1094
+ if ( this . plan . preload ) {
1095
+ for ( let name of Object . getOwnPropertyNames ( this . plan . preload ) ) {
1096
+ let file = this . plan . preload [ name ] ;
1097
+
1098
+ const compressed = isCompressed ( file ) ;
1099
+ if ( compressed && ! JetStreamParams . prefetchResources ) {
1100
+ file = uncompressedName ( file ) ;
1101
+ }
1102
+
1103
+ if ( JetStreamParams . prefetchResources ) {
1104
+ let bytes = new Int8Array ( read ( file , "binary" ) ) ;
1105
+ if ( compressed ) {
1106
+ bytes = zlib . decompress ( bytes ) ;
1107
+ }
1108
+ this . shellPrefetchedResources . push ( [ file , bytes ] ) ;
1109
+ }
1110
+
1111
+ this . preloads . push ( [ name , file ] ) ;
1112
+ }
1113
+ }
1002
1114
}
1003
1115
1004
1116
scoreIdentifiers ( ) {
@@ -1242,15 +1354,23 @@ class AsyncBenchmark extends DefaultBenchmark {
1242
1354
} else {
1243
1355
str += `
1244
1356
JetStream.getBinary = async function(path) {
1357
+ if (ShellPrefetchedResources) {
1358
+ return ShellPrefetchedResources[path];
1359
+ }
1245
1360
return new Int8Array(read(path, "binary"));
1246
1361
};
1247
1362
1248
1363
JetStream.getString = async function(path) {
1364
+ if (ShellPrefetchedResources) {
1365
+ return new ShellTextDecoder().decode(ShellPrefetchedResources[path]);
1366
+ }
1249
1367
return read(path);
1250
1368
};
1251
1369
1252
1370
JetStream.dynamicImport = async function(path) {
1253
1371
try {
1372
+ // TODO: this skips the prefetched resources, but I'm
1373
+ // not sure of a way around that.
1254
1374
return await import(path);
1255
1375
} catch (e) {
1256
1376
// In shells, relative imports require different paths, so try with and
@@ -1467,7 +1587,11 @@ class WasmLegacyBenchmark extends Benchmark {
1467
1587
` ;
1468
1588
} else {
1469
1589
str += `
1470
- Module[key] = new Int8Array(read(path, "binary"));
1590
+ if (ShellPrefetchedResources) {
1591
+ Module[key] = ShellPrefetchedResources[path];
1592
+ } else {
1593
+ Module[key] = new Int8Array(read(path, "binary"));
1594
+ }
1471
1595
if (andThen == doRun) {
1472
1596
globalObject.read = (...args) => {
1473
1597
console.log("should not be inside read: ", ...args);
0 commit comments