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