@@ -81,6 +81,10 @@ if (typeof(URLSearchParams) !== "undefined") {
81
81
globalThis . prefetchResources = getBoolParam ( urlParameters , "prefetchResources" ) ;
82
82
}
83
83
84
+ if ( ! isInBrowser ) {
85
+ load ( "wasm-zlib.js" ) ;
86
+ }
87
+
84
88
if ( ! globalThis . prefetchResources )
85
89
console . warn ( "Disabling resource prefetching!" ) ;
86
90
@@ -189,6 +193,47 @@ function uiFriendlyDuration(time) {
189
193
return `${ time . toFixed ( 3 ) } ms` ;
190
194
}
191
195
196
+ // Files can be zlib compressed to reduce the download size. We don't use http
197
+ // compression because we support running from the shell and don't want to
198
+ // require a complicated server setup.
199
+ //
200
+ // zlib was chosen because we already have it in tree for the wasm-zlib test.
201
+ function isCompressed ( name ) {
202
+ return name . endsWith ( ".z" ) ;
203
+ }
204
+
205
+ // Fallback for shell environments without TextDecoder. This only handles valid
206
+ // UTF-8, invalid buffers will lead to unexpected results.
207
+ function decodeUTF8 ( int8Array ) {
208
+ let result = '' ;
209
+ let i = 0 ;
210
+ while ( i < int8Array . length ) {
211
+ let byte1 = int8Array [ i ++ ] ;
212
+ if ( byte1 < 0x80 ) {
213
+ // 1-byte sequence (ASCII)
214
+ result += String . fromCharCode ( byte1 ) ;
215
+ } else if ( ( byte1 & 0xE0 ) === 0xC0 ) {
216
+ // 2-byte sequence
217
+ let byte2 = int8Array [ i ++ ] ;
218
+ result += String . fromCharCode ( ( ( byte1 & 0x1F ) << 6 ) | ( byte2 & 0x3F ) ) ;
219
+ } else if ( ( byte1 & 0xF0 ) === 0xE0 ) {
220
+ // 3-byte sequence
221
+ let byte2 = int8Array [ i ++ ] ;
222
+ let byte3 = int8Array [ i ++ ] ;
223
+ result += String . fromCharCode ( ( ( byte1 & 0x0F ) << 12 ) | ( ( byte2 & 0x3F ) << 6 ) | ( byte3 & 0x3F ) ) ;
224
+ } else if ( ( byte1 & 0xF8 ) === 0xF0 ) {
225
+ // 4-byte sequence (needs surrogate pairs)
226
+ let byte2 = int8Array [ i ++ ] ;
227
+ let byte3 = int8Array [ i ++ ] ;
228
+ let byte4 = int8Array [ i ++ ] ;
229
+ let codePoint = ( ( byte1 & 0x07 ) << 18 ) | ( ( byte2 & 0x3F ) << 12 ) | ( ( byte3 & 0x3F ) << 6 ) | ( byte4 & 0x3F ) ;
230
+ codePoint -= 0x10000 ;
231
+ result += String . fromCharCode ( 0xD800 + ( codePoint >> 10 ) , 0xDC00 + ( codePoint & 0x3FF ) ) ;
232
+ }
233
+ }
234
+ return result ;
235
+ }
236
+
192
237
// TODO: Cleanup / remove / merge. This is only used for caching loads in the
193
238
// non-browser setting. In the browser we use exclusively `loadCache`,
194
239
// `loadBlob`, `doLoadBlob`, `prefetchResourcesForBrowser` etc., see below.
@@ -201,14 +246,25 @@ class ShellFileLoader {
201
246
// share common code.
202
247
load ( url ) {
203
248
console . assert ( ! isInBrowser ) ;
204
- if ( ! globalThis . prefetchResources )
249
+
250
+ // If we aren't supposed to prefetch this and don't need to decompress it,
251
+ // then return code snippet that will load the url on-demand.
252
+ let compressed = isCompressed ( url ) ;
253
+ if ( ! compressed && ! globalThis . prefetchResources )
205
254
return `load("${ url } ");`
206
255
207
256
if ( this . requests . has ( url ) ) {
208
257
return this . requests . get ( url ) ;
209
258
}
210
259
211
- const contents = readFile ( url ) ;
260
+ let contents ;
261
+ if ( isCompressed ( url ) ) {
262
+ let bytes = new Int8Array ( read ( url , "binary" ) ) ;
263
+ bytes = zlib . decompress ( bytes ) ;
264
+ contents = decodeUTF8 ( bytes ) ;
265
+ } else {
266
+ contents = readFile ( url ) ;
267
+ }
212
268
this . requests . set ( url , contents ) ;
213
269
return contents ;
214
270
}
@@ -260,10 +316,14 @@ class Driver {
260
316
performance . mark ( "update-ui" ) ;
261
317
benchmark . updateUIAfterRun ( ) ;
262
318
263
- if ( isInBrowser && globalThis . prefetchResources ) {
319
+ if ( isInBrowser ) {
264
320
const cache = JetStream . blobDataCache ;
265
321
for ( const file of benchmark . files ) {
266
322
const blobData = cache [ file ] ;
323
+ // If we didn't prefetch this resource, then no need to free it
324
+ if ( ! blobData . blob ) {
325
+ continue
326
+ }
267
327
blobData . refCount -- ;
268
328
if ( ! blobData . refCount )
269
329
cache [ file ] = undefined ;
@@ -415,6 +475,7 @@ class Driver {
415
475
416
476
async prefetchResources ( ) {
417
477
if ( ! isInBrowser ) {
478
+ await zlib . initialize ( ) ;
418
479
for ( const benchmark of this . benchmarks )
419
480
benchmark . prefetchResourcesForShell ( ) ;
420
481
return ;
@@ -629,6 +690,11 @@ class Scripts {
629
690
}
630
691
631
692
class ShellScripts extends Scripts {
693
+ constructor ( ) {
694
+ super ( ) ;
695
+ this . blobs = [ ] ;
696
+ }
697
+
632
698
run ( ) {
633
699
let globalObject ;
634
700
let realm ;
@@ -655,13 +721,23 @@ class ShellScripts extends Scripts {
655
721
currentReject
656
722
} ;
657
723
724
+ // Store shellBlobs on JetStreamBlobs so that getBinary can find them.
725
+ globalObject . JetStreamBlobs = { } ;
726
+ for ( const [ name , value ] of this . blobs ) {
727
+ globalObject . JetStreamBlobs [ name ] = value ;
728
+ }
729
+
658
730
globalObject . performance ??= performance ;
659
731
for ( const script of this . scripts )
660
732
globalObject . loadString ( script ) ;
661
733
662
734
return isD8 ? realm : globalObject ;
663
735
}
664
736
737
+ addBlobs ( blobs ) {
738
+ this . blobs . push ( ...blobs ) ;
739
+ }
740
+
665
741
add ( text ) {
666
742
this . scripts . push ( text ) ;
667
743
}
@@ -694,7 +770,6 @@ class BrowserScripts extends Scripts {
694
770
return magicFrame ;
695
771
}
696
772
697
-
698
773
add ( text ) {
699
774
this . scripts . push ( `<script>${ text } </script>` ) ;
700
775
}
@@ -714,6 +789,7 @@ class Benchmark {
714
789
this . allowUtf16 = ! ! plan . allowUtf16 ;
715
790
this . scripts = null ;
716
791
this . preloads = null ;
792
+ this . shellBlobs = null ;
717
793
this . results = [ ] ;
718
794
this . _state = BenchmarkState . READY ;
719
795
}
@@ -820,10 +896,14 @@ class Benchmark {
820
896
if ( ! ! this . plan . exposeBrowserTest )
821
897
scripts . addBrowserTest ( ) ;
822
898
899
+ if ( this . shellBlobs ) {
900
+ scripts . addBlobs ( this . shellBlobs ) ;
901
+ }
823
902
if ( this . plan . preload ) {
824
903
let preloadCode = "" ;
825
- for ( let [ variableName , blobURLOrPath ] of this . preloads )
904
+ for ( let [ variableName , blobURLOrPath ] of this . preloads ) {
826
905
preloadCode += `JetStream.preload.${ variableName } = "${ blobURLOrPath } ";\n` ;
906
+ }
827
907
scripts . add ( preloadCode ) ;
828
908
}
829
909
@@ -838,7 +918,7 @@ class Benchmark {
838
918
} else {
839
919
const cache = JetStream . blobDataCache ;
840
920
for ( const file of this . plan . files ) {
841
- scripts . addWithURL ( globalThis . prefetchResources ? cache [ file ] . blobURL : file ) ;
921
+ scripts . addWithURL ( cache [ file ] . blobURL ) ;
842
922
}
843
923
}
844
924
@@ -889,10 +969,15 @@ class Benchmark {
889
969
890
970
async doLoadBlob ( resource ) {
891
971
const blobData = JetStream . blobDataCache [ resource ] ;
892
- if ( ! globalThis . prefetchResources ) {
972
+
973
+ // If we aren't supposed to prefetch this and don't need to decompress it,
974
+ // then set the blobURL to just be the resource URL.
975
+ const compressed = isCompressed ( resource ) ;
976
+ if ( ! compressed && ! globalThis . prefetchResources ) {
893
977
blobData . blobURL = resource ;
894
978
return blobData ;
895
979
}
980
+
896
981
let response ;
897
982
let tries = 3 ;
898
983
while ( tries -- ) {
@@ -908,7 +993,15 @@ class Benchmark {
908
993
continue ;
909
994
throw new Error ( "Fetch failed" ) ;
910
995
}
911
- const blob = await response . blob ( ) ;
996
+
997
+ // If we need to decompress this, then run it through a decompression
998
+ // stream.
999
+ if ( compressed ) {
1000
+ const stream = response . body . pipeThrough ( new DecompressionStream ( 'deflate' ) )
1001
+ response = new Response ( stream ) ;
1002
+ }
1003
+
1004
+ let blob = await response . blob ( ) ;
912
1005
blobData . blob = blob ;
913
1006
blobData . blobURL = URL . createObjectURL ( blob ) ;
914
1007
return blobData ;
@@ -1044,7 +1137,26 @@ class Benchmark {
1044
1137
this . scripts = this . plan . files . map ( file => shellFileLoader . load ( file ) ) ;
1045
1138
1046
1139
console . assert ( this . preloads === null , "This initialization should be called only once." ) ;
1047
- this . preloads = Object . entries ( this . plan . preload ?? { } ) ;
1140
+ this . preloads = [ ] ;
1141
+ this . shellBlobs = [ ] ;
1142
+ for ( let name of Object . getOwnPropertyNames ( this . plan . preload ) ) {
1143
+ let file = this . plan . preload [ name ] ;
1144
+
1145
+ const compressed = isCompressed ( file ) ;
1146
+ if ( compressed || globalThis . prefetchResources ) {
1147
+ let bytes = new Int8Array ( read ( file , "binary" ) ) ;
1148
+ if ( compressed ) {
1149
+ bytes = zlib . decompress ( bytes ) ;
1150
+ }
1151
+ // Add a `blob` prefix to the file name so that `getBinary`
1152
+ // knows to look for a blob in JetStreamBlobs (setup by
1153
+ // ShellScripts).
1154
+ file = `blob://${ name } ` ;
1155
+ this . shellBlobs . push ( [ file , bytes ] ) ;
1156
+ }
1157
+
1158
+ this . preloads . push ( [ name , file ] ) ;
1159
+ }
1048
1160
}
1049
1161
1050
1162
scoreIdentifiers ( ) {
@@ -1161,7 +1273,7 @@ class GroupedBenchmark extends Benchmark {
1161
1273
await benchmark . prefetchResourcesForBrowser ( ) ;
1162
1274
}
1163
1275
1164
- async retryPrefetchResourcesForBrowser ( ) {
1276
+ async retryjForBrowser ( ) {
1165
1277
for ( const benchmark of this . benchmarks )
1166
1278
await benchmark . retryPrefetchResourcesForBrowser ( ) ;
1167
1279
}
@@ -1296,6 +1408,9 @@ class AsyncBenchmark extends DefaultBenchmark {
1296
1408
} else {
1297
1409
str += `
1298
1410
JetStream.getBinary = async function(path) {
1411
+ if (path.startsWith("blob://")) {
1412
+ return JetStreamBlobs[path];
1413
+ }
1299
1414
return new Int8Array(read(path, "binary"));
1300
1415
};
1301
1416
0 commit comments