Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/tools/js_manipulation.py
4128 views
1
# Copyright 2020 The Emscripten Authors. All rights reserved.
2
# Emscripten is available under two separate licenses, the MIT license and the
3
# University of Illinois/NCSA Open Source License. Both these licenses can be
4
# found in the LICENSE file.
5
6
import re
7
8
from .settings import settings
9
from . import utils, shared
10
11
emscripten_license = '''\
12
/**
13
* @license
14
* Copyright 2010 The Emscripten Authors
15
* SPDX-License-Identifier: MIT
16
*/
17
'''
18
19
# handle the above form, and also what closure can emit which is stuff like
20
# /*
21
#
22
# Copyright 2019 The Emscripten Authors
23
# SPDX-License-Identifier: MIT
24
#
25
# Copyright 2017 The Emscripten Authors
26
# SPDX-License-Identifier: MIT
27
# */
28
emscripten_license_regex = r'\/\*\*?(\s*\*?\s*@license)?(\s*\*?\s*Copyright \d+ The Emscripten Authors\s*\*?\s*SPDX-License-Identifier: MIT)+\s*\*\/\s*'
29
30
31
def add_files_pre_js(pre_js_list, files_pre_js):
32
# the normal thing is to just combine the pre-js content
33
filename = shared.get_temp_files().get('.js').name
34
utils.write_file(filename, files_pre_js)
35
pre_js_list.insert(0, filename)
36
if not settings.ASSERTIONS:
37
return
38
39
# if a user pre-js tramples the file code's changes to Module.preRun
40
# that could be confusing. show a clear error at runtime if assertions are
41
# enabled
42
pre = shared.get_temp_files().get('.js').name
43
post = shared.get_temp_files().get('.js').name
44
utils.write_file(pre, '''
45
// All the pre-js content up to here must remain later on, we need to run
46
// it.
47
if ((typeof ENVIRONMENT_IS_WASM_WORKER != 'undefined' && ENVIRONMENT_IS_WASM_WORKER) || (typeof ENVIRONMENT_IS_PTHREAD != 'undefined' && ENVIRONMENT_IS_PTHREAD) || (typeof ENVIRONMENT_IS_AUDIO_WORKLET != 'undefined' && ENVIRONMENT_IS_AUDIO_WORKLET)) Module['preRun'] = [];
48
var necessaryPreJSTasks = Module['preRun'].slice();
49
''')
50
utils.write_file(post, '''
51
if (!Module['preRun']) throw 'Module.preRun should exist because file support used it; did a pre-js delete it?';
52
necessaryPreJSTasks.forEach((task) => {
53
if (Module['preRun'].indexOf(task) < 0) throw 'All preRun tasks that exist before user pre-js code should remain after; did you replace Module or modify Module.preRun?';
54
});
55
''')
56
57
pre_js_list.insert(1, pre)
58
pre_js_list.append(post)
59
60
61
def handle_license(js_target):
62
# ensure we emit the license if and only if we need to, and exactly once
63
js = utils.read_file(js_target)
64
# first, remove the license as there may be more than once
65
processed_js = re.sub(emscripten_license_regex, '', js)
66
if settings.EMIT_EMSCRIPTEN_LICENSE:
67
processed_js = emscripten_license + processed_js
68
if processed_js != js:
69
utils.write_file(js_target, processed_js)
70
71
72
# Returns the given string with escapes added so that it can safely be placed inside a string in JS code.
73
def escape_for_js_string(s):
74
s = s.replace('\\', '/').replace("'", "\\'").replace('"', '\\"')
75
return s
76
77
78
def legalize_sig(sig):
79
# with BigInt support all sigs are legal since we can use i64s.
80
if settings.WASM_BIGINT:
81
return sig
82
legal = [sig[0]]
83
# a return of i64 is legalized into an i32 (and the high bits are
84
# accessible on the side through getTempRet0).
85
if legal[0] == 'j':
86
legal[0] = 'i'
87
# a parameter of i64 is legalized into i32, i32
88
for s in sig[1:]:
89
if s != 'j':
90
legal.append(s)
91
else:
92
legal.append('i')
93
legal.append('i')
94
return ''.join(legal)
95
96
97
def is_legal_sig(sig):
98
# with BigInt support all sigs are legal since we can use i64s.
99
if settings.WASM_BIGINT:
100
return True
101
return sig == legalize_sig(sig)
102
103
104
def isidentifier(name):
105
# https://stackoverflow.com/questions/43244604/check-that-a-string-is-a-valid-javascript-identifier-name-using-python-3
106
return name.replace('$', '_').isidentifier()
107
108
109
def make_dynCall(sig, args):
110
# wasm2c and asyncify are not yet compatible with direct wasm table calls
111
if settings.MEMORY64:
112
args = list(args)
113
args[0] = f'Number({args[0]})'
114
if settings.DYNCALLS or not is_legal_sig(sig):
115
args = ','.join(args)
116
if not settings.MAIN_MODULE and not settings.SIDE_MODULE:
117
# Optimize dynCall accesses in the case when not building with dynamic
118
# linking enabled.
119
return 'dynCall_%s(%s)' % (sig, args)
120
else:
121
return 'Module["dynCall_%s"](%s)' % (sig, args)
122
else:
123
call_args = ",".join(args[1:])
124
return f'getWasmTableEntry({args[0]})({call_args})'
125
126
127
def make_invoke(sig):
128
legal_sig = legalize_sig(sig) # TODO: do this in extcall, jscall?
129
args = ['index'] + ['a' + str(i) for i in range(1, len(legal_sig))]
130
ret = 'return ' if sig[0] != 'v' else ''
131
# For function that needs to return a genuine i64 (i.e. if legal_sig[0] is 'j')
132
# we need to return an actual BigInt, even in the exceptional case because
133
# wasm won't implicitly convert undefined to 0 in this case.
134
exceptional_ret = '\n return 0n;' if legal_sig[0] == 'j' else ''
135
body = '%s%s;' % (ret, make_dynCall(sig, args))
136
# Create a try-catch guard that rethrows the Emscripten EH exception.
137
if settings.EXCEPTION_STACK_TRACES:
138
# Exceptions thrown from C++ and longjmps will be an instance of
139
# EmscriptenEH.
140
maybe_rethrow = 'if (!(e instanceof EmscriptenEH)) throw e;'
141
else:
142
# Exceptions thrown from C++ will be a pointer (number) and longjmp will
143
# throw the number Infinity. Use the compact and fast "e !== e+0" test to
144
# check if e was not a Number.
145
maybe_rethrow = 'if (e !== e+0) throw e;'
146
147
ret = '''\
148
function invoke_%s(%s) {
149
var sp = stackSave();
150
try {
151
%s
152
} catch(e) {
153
stackRestore(sp);
154
%s
155
_setThrew(1, 0);%s
156
}
157
}''' % (sig, ','.join(args), body, maybe_rethrow, exceptional_ret)
158
159
return ret
160
161
162
def make_wasm64_wrapper(sig):
163
assert 'p' in sig.lower()
164
n_args = len(sig) - 1
165
args = ['a%d' % i for i in range(n_args)]
166
args_converted = args.copy()
167
for i, arg_type in enumerate(sig[1:]):
168
if arg_type == 'p':
169
args_converted[i] = f'BigInt({args_converted[i]})'
170
elif arg_type == 'P':
171
args_converted[i] = f'BigInt({args_converted[i]} ? {args_converted[i]} : 0)'
172
else:
173
assert arg_type == '_'
174
175
args_in = ', '.join(args)
176
args_out = ', '.join(args_converted)
177
result = f'f({args_out})'
178
if sig[0] == 'p':
179
result = f'Number({result})'
180
181
# We can't use an arrow function for the inner wrapper here since there
182
# are certain places we need to avoid strict mode still.
183
# e.g. emscripten_get_callstack (getCallstack) which uses the `arguments`
184
# global.
185
return f' var makeWrapper_{sig} = (f) => ({args_in}) => {result};\n'
186
187
188
def make_unsign_pointer_wrapper(sig):
189
assert sig[0] == 'p'
190
n_args = len(sig) - 1
191
args = ','.join('a%d' % i for i in range(n_args))
192
return f' var makeWrapper_{sig} = (f) => ({args}) => f({args}) >>> 0;\n'
193
194