-
Notifications
You must be signed in to change notification settings - Fork 3.4k
/
Copy pathminimal_runtime_shell.py
214 lines (185 loc) · 9.97 KB
/
minimal_runtime_shell.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
import re
import sys
import os
import logging
__scriptdir__ = os.path.dirname(os.path.abspath(__file__))
__rootdir__ = os.path.dirname(__scriptdir__)
sys.path.insert(0, __rootdir__)
from . import shared
from . import line_endings
from . import utils
from .settings import settings
logger = logging.getLogger('minimal_runtime_shell')
def generate_minimal_runtime_load_statement(target_basename):
prefix_statements = [] # Extra code to appear before the loader
then_statements = [] # Statements to appear inside a Promise .then() block after loading has finished
modularize_imports = [] # Import parameters to call the main JS runtime function with
# Depending on whether streaming Wasm compilation is enabled or not, the minimal sized code to download Wasm looks a bit different.
# Expand {{{ DOWNLOAD_WASM }}} block from here (if we added #define support, this could be done in the template directly)
if settings.MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION:
if settings.MIN_SAFARI_VERSION != settings.TARGET_NOT_SUPPORTED or settings.ENVIRONMENT_MAY_BE_NODE or settings.MIN_FIREFOX_VERSION < 58 or settings.MIN_CHROME_VERSION < 61:
# Firefox 52 added Wasm support, but only Firefox 58 added compileStreaming.
# Chrome 57 added Wasm support, but only Chrome 61 added compileStreaming.
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/compileStreaming
# In Safari and Node.js, WebAssembly.compileStreaming() is not supported, in which case fall back to regular download.
download_wasm = "WebAssembly.compileStreaming ? WebAssembly.compileStreaming(fetch('%s')) : binary('%s')" % (target_basename + '.wasm', target_basename + '.wasm')
else:
# WebAssembly.compileStreaming() is unconditionally supported:
download_wasm = "WebAssembly.compileStreaming(fetch('%s'))" % (target_basename + '.wasm')
elif settings.MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION:
# Same compatibility story as above for https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiateStreaming
if settings.MIN_SAFARI_VERSION != settings.TARGET_NOT_SUPPORTED or settings.ENVIRONMENT_MAY_BE_NODE or settings.MIN_FIREFOX_VERSION < 58 or settings.MIN_CHROME_VERSION < 61:
download_wasm = "!WebAssembly.instantiateStreaming && binary('%s')" % (target_basename + '.wasm')
else:
# WebAssembly.instantiateStreaming() is unconditionally supported, so we do not download wasm in the .html file,
# but leave it to the .js file to download
download_wasm = None
else:
download_wasm = "binary('%s')" % (target_basename + '.wasm')
files_to_load = ["script('%s')" % (target_basename + '.js')] # Main JS file always in first entry
# Download separate memory initializer file .mem
if not settings.MEM_INIT_IN_WASM:
if settings.MODULARIZE:
modularize_imports += ['mem: r[%d]' % len(files_to_load)]
else:
then_statements += ["%s.mem = r[%d];" % (settings.EXPORT_NAME, len(files_to_load))]
files_to_load += ["binary('%s')" % (target_basename + '.mem')]
# Download .wasm file
if (settings.WASM == 1 and settings.WASM2JS == 0) or not download_wasm:
if settings.MODULARIZE:
modularize_imports += ['wasm: r[%d]' % len(files_to_load)]
else:
then_statements += ["%s.wasm = r[%d];" % (settings.EXPORT_NAME, len(files_to_load))]
if download_wasm:
files_to_load += [download_wasm]
# Download wasm_worker file
if settings.WASM_WORKERS:
if settings.MODULARIZE:
if settings.WASM_WORKERS == 1: # '$wb': Wasm Worker Blob
modularize_imports += ['$wb: URL.createObjectURL(new Blob([r[%d]], { type: \'application/javascript\' }))' % len(files_to_load)]
modularize_imports += ['js: js']
else:
if settings.WASM_WORKERS == 1:
then_statements += ['%s.$wb = URL.createObjectURL(new Blob([r[%d]], { type: \'application/javascript\' }));' % (settings.EXPORT_NAME, len(files_to_load))]
if download_wasm and settings.WASM_WORKERS == 1:
files_to_load += ["binary('%s')" % (target_basename + '.ww.js')]
if settings.MODULARIZE and settings.PTHREADS:
modularize_imports += ["worker: '{{{ PTHREAD_WORKER_FILE }}}'"]
# Download Wasm2JS code if target browser does not support WebAssembly
if settings.WASM == 2:
if settings.MODULARIZE:
modularize_imports += ['wasm: supportsWasm ? r[%d] : 0' % len(files_to_load)]
else:
then_statements += ["if (supportsWasm) %s.wasm = r[%d];" % (settings.EXPORT_NAME, len(files_to_load))]
files_to_load += ["supportsWasm ? %s : script('%s')" % (download_wasm, target_basename + '.wasm.js')]
# Execute compiled output when building with MODULARIZE
if settings.MODULARIZE:
if settings.WASM_WORKERS:
then_statements += ['''// Detour the JS code to a separate variable to avoid instantiating with 'r' array as "this" directly to avoid strict ECMAScript/Firefox GC problems that cause a leak, see https://bugzilla.mozilla.org/show_bug.cgi?id=1540101
var js = URL.createObjectURL(new Blob([r[0]], { type: \'application/javascript\' }));\n script(js).then(function(c) { c({ %s }); });''' % ',\n '.join(modularize_imports)]
else:
then_statements += ['''// Detour the JS code to a separate variable to avoid instantiating with 'r' array as "this" directly to avoid strict ECMAScript/Firefox GC problems that cause a leak, see https://bugzilla.mozilla.org/show_bug.cgi?id=1540101
var js = r[0];\n js({ %s });''' % ',\n '.join(modularize_imports)]
binary_xhr = ''' function binary(url) { // Downloads a binary file and outputs it in the specified callback
return new Promise((ok, err) => {
var x = new XMLHttpRequest();
x.open('GET', url, true);
x.responseType = 'arraybuffer';
x.onload = () => { ok(x.response); }
x.send(null);
});
}
'''
script_xhr = ''' function script(url) { // Downloads a script file and adds it to DOM
return new Promise((ok, err) => {
var s = document.createElement('script');
s.src = url;
s.onload = () => {
#if MODULARIZE
#if WASM == 2
// In MODULARIZEd WASM==2 builds, we use this same function to download
// both .js and .asm.js that are structured with {{{ EXPORT_NAME }}}
// at the top level, but also use this function to download the Wasm2JS
// file that does not have an {{{ EXPORT_NAME }}} function, hence the
// variable typeof check:
if (typeof {{{ EXPORT_NAME }}} !== 'undefined') {
var c = {{{ EXPORT_NAME }}};
delete {{{ EXPORT_NAME }}};
ok(c);
} else {
ok();
}
#else
var c = {{{ EXPORT_NAME }}};
delete {{{ EXPORT_NAME }}};
ok(c);
#endif
#else
ok();
#endif
};
document.body.appendChild(s);
});
}
'''
# Only one file to download - no need to use Promise.all()
if len(files_to_load) == 1:
if settings.MODULARIZE:
return script_xhr + files_to_load[0] + ".then((js) => {\n js();\n});"
else:
return script_xhr + files_to_load[0] + ";"
if not settings.MODULARIZE or settings.WASM_WORKERS:
# If downloading multiple files like .wasm or .mem, those need to be loaded in
# before we can add the main runtime script to the DOM, so convert the main .js
# script load from direct script() load to a binary() load so we can still
# immediately start the download, but can control when we add the script to the
# DOM.
if settings.PTHREADS or settings.WASM_WORKERS:
script_load = "script(url)"
else:
script_load = "script(url).then(() => { URL.revokeObjectURL(url) });"
if settings.WASM_WORKERS:
save_js = '%s.js = ' % settings.EXPORT_NAME
else:
save_js = ''
files_to_load[0] = "binary('%s')" % (target_basename + '.js')
if not settings.MODULARIZE:
then_statements += ["var url = %sURL.createObjectURL(new Blob([r[0]], { type: 'application/javascript' }));" % save_js,
script_load]
# Add in binary() XHR loader if used:
if any("binary(" in s for s in files_to_load + then_statements):
prefix_statements += [binary_xhr]
if any("script(" in s for s in files_to_load + then_statements):
prefix_statements += [script_xhr]
# Several files to download, go via Promise.all()
load = '\n'.join(prefix_statements)
load += "Promise.all([" + ', '.join(files_to_load) + "])"
if len(then_statements) > 0:
load += '.then((r) => {\n %s\n});' % '\n '.join(then_statements)
return load
def generate_minimal_runtime_html(target, options, js_target, target_basename):
logger.debug('generating HTML for minimal runtime')
shell = utils.read_file(options.shell_path)
if settings.SINGLE_FILE:
# No extra files needed to download in a SINGLE_FILE build.
shell = shell.replace('{{{ DOWNLOAD_JS_AND_WASM_FILES }}}', '')
else:
shell = shell.replace('{{{ DOWNLOAD_JS_AND_WASM_FILES }}}', generate_minimal_runtime_load_statement(target_basename))
temp_files = shared.get_temp_files()
with temp_files.get_file(suffix='.js') as shell_temp:
utils.write_file(shell_temp, shell)
shell = shared.read_and_preprocess(shell_temp)
if re.search(r'{{{\s*SCRIPT\s*}}}', shell):
shared.exit_with_error('--shell-file "' + options.shell_path + '": MINIMAL_RUNTIME uses a different kind of HTML page shell file than the traditional runtime! Please see $EMSCRIPTEN/src/shell_minimal_runtime.html for a template to use as a basis.')
shell = shell.replace('{{{ TARGET_BASENAME }}}', target_basename)
shell = shell.replace('{{{ EXPORT_NAME }}}', settings.EXPORT_NAME)
shell = shell.replace('{{{ PTHREAD_WORKER_FILE }}}', settings.PTHREAD_WORKER_FILE)
# In SINGLE_FILE build, embed the main .js file into the .html output
if settings.SINGLE_FILE:
js_contents = utils.read_file(js_target)
utils.delete_file(js_target)
else:
js_contents = ''
shell = shell.replace('{{{ JS_CONTENTS_IN_SINGLE_FILE_BUILD }}}', js_contents)
shell = line_endings.convert_line_endings(shell, '\n', options.output_eol)
utils.write_file(target, shell)