Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/tools/maint/gen_sig_info.py
4150 views
1
#!/usr/bin/env python3
2
# Copyright 2023 The Emscripten Authors. All rights reserved.
3
# Emscripten is available under two separate licenses, the MIT license and the
4
# University of Illinois/NCSA Open Source License. Both these licenses can be
5
# found in the LICENSE file.
6
7
"""This tool extracts native/C signature information for JS library functions
8
9
It generates a file called `src/lib/libsigs.js` which contains `__sig` declarations
10
for the majority of JS library functions.
11
"""
12
13
import argparse
14
import json
15
import os
16
import sys
17
import subprocess
18
import re
19
import glob
20
21
22
__scriptdir__ = os.path.dirname(os.path.abspath(__file__))
23
__rootdir__ = os.path.dirname(os.path.dirname(__scriptdir__))
24
sys.path.insert(0, __rootdir__)
25
26
from tools import shared, utils, webassembly
27
28
c_header = '''/* Auto-generated by %s */
29
30
#define _GNU_SOURCE
31
32
// Public emscripten headers
33
#include <emscripten/emscripten.h>
34
#include <emscripten/heap.h>
35
#include <emscripten/console.h>
36
#include <emscripten/em_math.h>
37
#include <emscripten/html5.h>
38
#include <emscripten/html5_webgpu.h>
39
#include <emscripten/fiber.h>
40
#include <emscripten/websocket.h>
41
#include <emscripten/wasm_worker.h>
42
#include <emscripten/fetch.h>
43
#include <emscripten/webaudio.h>
44
#include <emscripten/threading.h>
45
#include <emscripten/trace.h>
46
#include <emscripten/proxying.h>
47
#include <emscripten/exports.h>
48
#include <wasi/api.h>
49
50
// Internal emscripten headers
51
#include "emscripten_internal.h"
52
#include "threading_internal.h"
53
#include "webgl_internal.h"
54
#include "thread_mailbox.h"
55
56
// Internal musl headers
57
#include "musl/include/assert.h"
58
#include "musl/arch/emscripten/syscall_arch.h"
59
#include "dynlink.h"
60
61
// Public musl/libc headers
62
#include <cxxabi.h>
63
#include <unwind.h>
64
#include <sys/types.h>
65
#include <sys/socket.h>
66
#include <netdb.h>
67
#include <time.h>
68
#include <unistd.h>
69
#include <dlfcn.h>
70
71
// Public library headers
72
#define GL_GLEXT_PROTOTYPES
73
#ifdef GLES
74
#include <GLES/gl.h>
75
#include <GLES/glext.h>
76
#else
77
#include <GL/gl.h>
78
#include <GL/glext.h>
79
#endif
80
#if GLFW3
81
#include <GLFW/glfw3.h>
82
#else
83
#include <GL/glfw.h>
84
#endif
85
#include <EGL/egl.h>
86
#include <GL/glew.h>
87
#include <GL/glut.h>
88
#include <AL/al.h>
89
#include <AL/alc.h>
90
#include <SDL/SDL.h>
91
#include <SDL/SDL_mutex.h>
92
#include <SDL/SDL_image.h>
93
#include <SDL/SDL_mixer.h>
94
#include <SDL/SDL_surface.h>
95
#include <SDL/SDL_ttf.h>
96
#include <SDL/SDL_gfxPrimitives.h>
97
#include <SDL/SDL_rotozoom.h>
98
#include <webgl/webgl1_ext.h>
99
#include <webgl/webgl2_ext.h>
100
#include <X11/Xlib.h>
101
#include <X11/Xutil.h>
102
#include <uuid/uuid.h>
103
#include <webgpu/webgpu.h>
104
''' % os.path.basename(__file__)
105
106
cxx_header = '''/* Auto-generated by %s */
107
108
// Public emscripten headers
109
#include <emscripten/bind.h>
110
#include <emscripten/heap.h>
111
#include <emscripten/em_math.h>
112
#include <emscripten/fiber.h>
113
114
// Internal emscripten headers
115
#include "emscripten_internal.h"
116
#include "wasmfs_internal.h"
117
#include "backends/opfs_backend.h"
118
#include "backends/fetch_backend.h"
119
#include "backends/node_backend.h"
120
#include "backends/js_file_backend.h"
121
#include "proxied_async_js_impl_backend.h"
122
#include "js_impl_backend.h"
123
124
// Public musl/libc headers
125
#include <cxxabi.h>
126
#include <unwind.h>
127
#include <sys/socket.h>
128
#include <unistd.h>
129
#include <netdb.h>
130
#include <time.h>
131
#include <dlfcn.h>
132
133
#include <musl/arch/emscripten/syscall_arch.h>
134
135
using namespace emscripten::internal;
136
using namespace __cxxabiv1;
137
138
''' % os.path.basename(__file__)
139
140
footer = '''\
141
};
142
143
int main(int argc, char* argv[]) {
144
return argc + (intptr_t)symbol_list;
145
}
146
'''
147
148
wasi_symbols = {
149
'proc_exit',
150
'environ_sizes_get',
151
'environ_get',
152
'clock_time_get',
153
'clock_res_get',
154
'fd_write',
155
'fd_pwrite',
156
'fd_read',
157
'fd_pread',
158
'fd_close',
159
'fd_seek',
160
'fd_sync',
161
'fd_fdstat_get',
162
'args_get',
163
'args_sizes_get',
164
'random_get',
165
}
166
167
168
def ignore_symbol(s, cxx):
169
# We need to ignore certain symbols here. Specifically, any symbol that is not
170
# pre-declared in a C/C++ header need to be ignored, otherwise the generated
171
# file will fail to compile.
172
if s.startswith('$'):
173
return True
174
if s in {'SDL_GetKeyState'}:
175
return True
176
# Symbols that start with `emscripten_gl` or `emscripten_alc` are auto-generated
177
# wrappers around GL and OpenGL symbols. Since they inherit their signature they
178
# don't need to be auto-generated.
179
if s.startswith(('emscripten_gl', 'emscripten_alc')):
180
return True
181
if s.startswith('gl') and any(s.endswith(x) for x in ('NV', 'EXT', 'WEBGL', 'ARB', 'ANGLE')):
182
return True
183
if s in {'__stack_base', '__memory_base', '__table_base', '__global_base', '__heap_base',
184
'__stack_pointer', '__stack_high', '__stack_low', '_load_secondary_module',
185
'__asyncify_state', '__asyncify_data',
186
# legacy aliases, not callable from native code.
187
'stackSave', 'stackRestore', 'stackAlloc', 'getTempRet0', 'setTempRet0',
188
}:
189
return True
190
return cxx and s == '__asctime_r' or s.startswith('__cxa_find_matching_catch')
191
192
193
def create_c_file(filename, symbol_list, header):
194
source_lines = [header]
195
source_lines.append('\nvoid* symbol_list[] = {')
196
for s in symbol_list:
197
if s in wasi_symbols:
198
source_lines.append(f' (void*)&__wasi_{s},')
199
else:
200
source_lines.append(f' (void*)&{s},')
201
source_lines.append(footer)
202
utils.write_file(filename, '\n'.join(source_lines) + '\n')
203
204
205
def valuetype_to_chr(t, t64):
206
if t == webassembly.Type.I32 and t64 == webassembly.Type.I64:
207
return 'p'
208
assert t == t64
209
return {
210
webassembly.Type.I32: 'i',
211
webassembly.Type.I64: 'j',
212
webassembly.Type.F32: 'f',
213
webassembly.Type.F64: 'd',
214
}[t]
215
216
217
def functype_to_str(t, t64):
218
assert len(t.returns) == len(t64.returns)
219
assert len(t.params) == len(t64.params)
220
if t.returns:
221
assert len(t.returns) == 1
222
rtn = valuetype_to_chr(t.returns[0], t64.returns[0])
223
else:
224
rtn = 'v'
225
for p, p64 in zip(t.params, t64.params):
226
rtn += valuetype_to_chr(p, p64)
227
return rtn
228
229
230
def write_sig_library(filename, sig_info):
231
lines = [
232
'/* Auto-generated by tools/gen_sig_info.py. DO NOT EDIT. */',
233
'',
234
'sigs = {',
235
]
236
for s, sig in sorted(sig_info.items()):
237
lines.append(f" {s}__sig: '{sig}',")
238
lines += [
239
'}',
240
'',
241
'// We have to merge with `allowMissing` since this file contains signatures',
242
'// for functions that might not exist in all build configurations.',
243
'addToLibrary(sigs, {allowMissing: true});',
244
]
245
utils.write_file(filename, '\n'.join(lines) + '\n')
246
247
248
def update_sigs(sig_info):
249
print("updating __sig attributes ...")
250
251
def update_line(l):
252
if '__sig' not in l:
253
return l
254
stripped = l.strip()
255
for sym, sig in sig_info.items():
256
if stripped.startswith(f'{sym}__sig:'):
257
return re.sub(rf"\b{sym}__sig: '.*'", f"{sym}__sig: '{sig}'", l)
258
return l
259
260
files = glob.glob('src/*.js') + glob.glob('src/**/*.js')
261
for file in files:
262
lines = utils.read_file(file).splitlines()
263
lines = [update_line(l) for l in lines]
264
utils.write_file(file, '\n'.join(lines) + '\n')
265
266
267
def remove_sigs(sig_info):
268
print("removing __sig attributes ...")
269
270
to_remove = [f'{sym}__sig:' for sym in sig_info]
271
272
def strip_line(l):
273
l = l.strip()
274
return any(l.startswith(r) for r in to_remove)
275
276
files = glob.glob('src/*.js') + glob.glob('src/**/*.js')
277
for file in files:
278
if os.path.basename(file) != 'libsigs.js':
279
lines = utils.read_file(file).splitlines()
280
lines = [l for l in lines if not strip_line(l)]
281
utils.write_file(file, '\n'.join(lines) + '\n')
282
283
284
def extract_sigs(symbols, obj_file):
285
sig_info = {}
286
with webassembly.Module(obj_file) as mod:
287
imports = mod.get_imports()
288
types = mod.get_types()
289
import_map = {i.field: i for i in imports}
290
for s in symbols:
291
sig_info[s] = types[import_map[s].type]
292
return sig_info
293
294
295
def extract_sig_info(sig_info, extra_settings=None, extra_cflags=None, cxx=False):
296
print(' .. ' + str(extra_settings) + ' + ' + str(extra_cflags))
297
tempfiles = shared.get_temp_files()
298
settings = {
299
# Enable as many settings as we can here to ensure the maximum number
300
# of JS symbols are included.
301
'STACK_OVERFLOW_CHECK': 1,
302
'USE_SDL': 1,
303
'USE_GLFW': 0,
304
'FETCH': 1,
305
'PTHREADS': 1,
306
'SHARED_MEMORY': 1,
307
'JS_LIBRARIES': [
308
'libwebsocket.js',
309
'libexports.js',
310
'libwebaudio.js',
311
'libfetch.js',
312
'libpthread.js',
313
'libtrace.js',
314
],
315
'SUPPORT_LONGJMP': 'emscripten',
316
}
317
if extra_settings:
318
settings.update(extra_settings)
319
settings['JS_LIBRARIES'] = [os.path.join(utils.path_from_root('src/lib'), s) for s in settings['JS_LIBRARIES']]
320
with tempfiles.get_file('.json') as settings_json:
321
utils.write_file(settings_json, json.dumps(settings))
322
output = shared.run_js_tool(utils.path_from_root('tools/compiler.mjs'),
323
['--symbols-only', settings_json],
324
stdout=subprocess.PIPE)
325
symbols = json.loads(output)['deps'].keys()
326
symbols = [s for s in symbols if not ignore_symbol(s, cxx)]
327
if cxx:
328
ext = '.cpp'
329
compiler = shared.EMXX
330
header = cxx_header
331
else:
332
ext = '.c'
333
compiler = shared.EMCC
334
header = c_header
335
with tempfiles.get_file(ext) as c_file:
336
create_c_file(c_file, symbols, header)
337
338
# We build the `.c` file twice, once with wasm32 and wasm64.
339
# The first build gives is that base signature of each function.
340
# The second build build allows us to determine which args/returns are pointers
341
# or `size_t` types. These get marked as `p` in the `__sig`.
342
obj_file = 'out.o'
343
cmd = [compiler, c_file, '-c', '-pthread',
344
'--tracing',
345
'-Wno-deprecated-declarations',
346
'-I' + utils.path_from_root('system/lib/libc'),
347
'-I' + utils.path_from_root('system/lib/wasmfs'),
348
'-o', obj_file]
349
if not cxx:
350
cmd += ['-I' + utils.path_from_root('system/lib/pthread'),
351
'-I' + utils.path_from_root('system/lib/libc/musl/src/include'),
352
'-I' + utils.path_from_root('system/lib/libc/musl/src/internal'),
353
'-I' + utils.path_from_root('system/lib/gl'),
354
'-I' + utils.path_from_root('system/lib/libcxxabi/include')]
355
if extra_cflags:
356
cmd += extra_cflags
357
shared.check_call(cmd)
358
sig_info32 = extract_sigs(symbols, obj_file)
359
360
# Run the same command again with memory64.
361
shared.check_call(cmd + ['-sMEMORY64', '-Wno-experimental'])
362
sig_info64 = extract_sigs(symbols, obj_file)
363
364
for sym, sig32 in sig_info32.items():
365
assert sym in sig_info64
366
sig64 = sig_info64[sym]
367
sig_string = functype_to_str(sig32, sig64)
368
if sym in sig_info and sig_info[sym] != sig_string:
369
print(sym)
370
print(sig_string)
371
print(sig_info[sym])
372
assert sig_info[sym] == sig_string
373
sig_info[sym] = sig_string
374
375
376
def main(args):
377
parser = argparse.ArgumentParser()
378
parser.add_argument('-o', '--output', default='src/lib/libsigs.js')
379
parser.add_argument('-r', '--remove', action='store_true', help='remove from JS library files any `__sig` entries that are part of the auto-generated file')
380
parser.add_argument('-u', '--update', action='store_true', help='update with JS library files any `__sig` entries that are part of the auto-generated file')
381
args = parser.parse_args()
382
383
print('generating signatures ...')
384
sig_info = {}
385
extract_sig_info(sig_info, {'WASMFS': 1,
386
'JS_LIBRARIES': [],
387
'USE_SDL': 0,
388
'MAX_WEBGL_VERSION': 0,
389
'BUILD_AS_WORKER': 1,
390
'LINK_AS_CXX': 1,
391
'AUTO_JS_LIBRARIES': 0}, cxx=True)
392
extract_sig_info(sig_info, {'AUDIO_WORKLET': 1, 'WASM_WORKERS': 1, 'JS_LIBRARIES': ['libwasm_worker.js', 'libwebaudio.js']})
393
extract_sig_info(sig_info, {'USE_GLFW': 3}, ['-DGLFW3'])
394
extract_sig_info(sig_info, {'JS_LIBRARIES': ['libembind.js', 'libemval.js'],
395
'USE_SDL': 0,
396
'MAX_WEBGL_VERSION': 0,
397
'AUTO_JS_LIBRARIES': 0,
398
'ASYNCIFY_LAZY_LOAD_CODE': 1,
399
'ASYNCIFY': 1}, cxx=True, extra_cflags=['-std=c++20'])
400
extract_sig_info(sig_info, {'LEGACY_GL_EMULATION': 1}, ['-DGLES'])
401
extract_sig_info(sig_info, {'USE_GLFW': 2, 'FULL_ES3': 1, 'MAX_WEBGL_VERSION': 2})
402
extract_sig_info(sig_info, {'STANDALONE_WASM': 1})
403
extract_sig_info(sig_info, {'MAIN_MODULE': 2, 'RELOCATABLE': 1, 'USE_WEBGPU': 1, 'ASYNCIFY': 1})
404
405
write_sig_library(args.output, sig_info)
406
if args.update:
407
update_sigs(sig_info)
408
if args.remove:
409
remove_sigs(sig_info)
410
411
412
if __name__ == '__main__':
413
sys.exit(main(sys.argv[1:]))
414
415