Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/tools/webidl_binder.py
6174 views
1
# Copyright 2014 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
# noqa: UP035
6
7
"""WebIDL binder
8
9
https://emscripten.org/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.html
10
"""
11
12
import argparse
13
import os
14
import sys
15
16
__scriptdir__ = os.path.dirname(os.path.abspath(__file__))
17
__rootdir__ = os.path.dirname(__scriptdir__)
18
sys.path.insert(0, __rootdir__)
19
20
from tools import utils
21
22
sys.path.append(utils.path_from_root('third_party'))
23
sys.path.append(utils.path_from_root('third_party/ply'))
24
25
import WebIDL
26
27
# CHECKS='FAST' will skip most argument type checks in the wrapper methods for
28
# performance (~3x faster than default).
29
# CHECKS='ALL' will do extensive argument type checking (~5x slower than default).
30
# This will catch invalid numbers, invalid pointers, invalid strings, etc.
31
# Anything else defaults to legacy mode for backward compatibility.
32
CHECKS = os.environ.get('IDL_CHECKS', 'DEFAULT')
33
# DEBUG=1 will print debug info in render_function
34
DEBUG = os.environ.get('IDL_VERBOSE') == '1'
35
36
37
def dbg(*args):
38
if DEBUG:
39
print(*args, file=sys.stderr)
40
41
42
dbg(f'Debug print ON, CHECKS=${CHECKS}')
43
44
# We need to avoid some closure errors on the constructors we define here.
45
CONSTRUCTOR_CLOSURE_SUPPRESSIONS = '/** @suppress {undefinedVars, duplicate} @this{Object} */'
46
47
48
class Dummy:
49
def __init__(self, type):
50
self.type = type
51
52
def __repr__(self):
53
return f'<Dummy type:{self.type}>'
54
55
def getExtendedAttribute(self, _name):
56
return None
57
58
59
parser = argparse.ArgumentParser()
60
parser.add_argument('--wasm64', action='store_true', default=False,
61
help='Build for wasm64')
62
parser.add_argument('infile')
63
parser.add_argument('outfile')
64
options = parser.parse_args()
65
66
input_file = options.infile
67
output_base = options.outfile
68
cpp_output = output_base + '.cpp'
69
js_output = output_base + '.js'
70
71
utils.delete_file(cpp_output)
72
utils.delete_file(js_output)
73
74
p = WebIDL.Parser()
75
p.parse('''
76
interface VoidPtr {
77
};
78
''' + utils.read_file(input_file))
79
data = p.finish()
80
81
interfaces = {}
82
implements = {}
83
enums = {}
84
85
for thing in data:
86
if isinstance(thing, WebIDL.IDLInterface):
87
interfaces[thing.identifier.name] = thing
88
elif isinstance(thing, WebIDL.IDLImplementsStatement):
89
implements.setdefault(thing.implementor.identifier.name, []).append(thing.implementee.identifier.name)
90
elif isinstance(thing, WebIDL.IDLEnum):
91
enums[thing.identifier.name] = thing
92
93
# print interfaces
94
# print implements
95
96
pre_c = ['''
97
#include <emscripten.h>
98
#include <stdlib.h>
99
100
EM_JS_DEPS(webidl_binder, "$intArrayFromString,$UTF8ToString,$alignMemory,$addOnInit");
101
''']
102
103
mid_c = ['''
104
extern "C" {
105
106
// Define custom allocator functions that we can force export using
107
// EMSCRIPTEN_KEEPALIVE. This avoids all webidl users having to add
108
// malloc/free to -sEXPORTED_FUNCTIONS.
109
EMSCRIPTEN_KEEPALIVE void webidl_free(void* p) { free(p); }
110
EMSCRIPTEN_KEEPALIVE void* webidl_malloc(size_t len) { return malloc(len); }
111
112
''']
113
114
115
def build_constructor(name):
116
implementing_name = implements[name][0] if implements.get(name) else 'WrapperObject'
117
return [r'''{name}.prototype = Object.create({implementing}.prototype);
118
{name}.prototype.constructor = {name};
119
{name}.prototype.__class__ = {name};
120
{name}.__cache__ = {{}};
121
Module['{name}'] = {name};
122
'''.format(name=name, implementing=implementing_name)]
123
124
125
mid_js = ['''
126
// Bindings utilities
127
128
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant) */
129
function WrapperObject() {
130
}
131
''']
132
133
mid_js += build_constructor('WrapperObject')
134
135
mid_js += ['''
136
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant)
137
@param {*=} __class__ */
138
function getCache(__class__) {
139
return (__class__ || WrapperObject).__cache__;
140
}
141
Module['getCache'] = getCache;
142
143
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant)
144
@param {*=} __class__ */
145
function wrapPointer(ptr, __class__) {
146
var cache = getCache(__class__);
147
var ret = cache[ptr];
148
if (ret) return ret;
149
ret = Object.create((__class__ || WrapperObject).prototype);
150
ret.ptr = ptr;
151
return cache[ptr] = ret;
152
}
153
Module['wrapPointer'] = wrapPointer;
154
155
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant) */
156
function castObject(obj, __class__) {
157
return wrapPointer(obj.ptr, __class__);
158
}
159
Module['castObject'] = castObject;
160
161
Module['NULL'] = wrapPointer(0);
162
163
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant) */
164
function destroy(obj) {
165
if (!obj['__destroy__']) throw 'Error: Cannot destroy object. (Did you create it yourself?)';
166
obj['__destroy__']();
167
// Remove from cache, so the object can be GC'd and refs added onto it released
168
delete getCache(obj.__class__)[obj.ptr];
169
}
170
Module['destroy'] = destroy;
171
172
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant) */
173
function compare(obj1, obj2) {
174
return obj1.ptr === obj2.ptr;
175
}
176
Module['compare'] = compare;
177
178
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant) */
179
function getPointer(obj) {
180
return obj.ptr;
181
}
182
Module['getPointer'] = getPointer;
183
184
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant) */
185
function getClass(obj) {
186
return obj.__class__;
187
}
188
Module['getClass'] = getClass;
189
190
// Converts big (string or array) values into a C-style storage, in temporary space
191
192
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant) */
193
var ensureCache = {
194
buffer: 0, // the main buffer of temporary storage
195
size: 0, // the size of buffer
196
pos: 0, // the next free offset in buffer
197
temps: [], // extra allocations
198
needed: 0, // the total size we need next time
199
200
prepare() {
201
if (ensureCache.needed) {
202
// clear the temps
203
for (var i = 0; i < ensureCache.temps.length; i++) {
204
Module['_webidl_free'](ensureCache.temps[i]);
205
}
206
ensureCache.temps.length = 0;
207
// prepare to allocate a bigger buffer
208
Module['_webidl_free'](ensureCache.buffer);
209
ensureCache.buffer = 0;
210
ensureCache.size += ensureCache.needed;
211
// clean up
212
ensureCache.needed = 0;
213
}
214
if (!ensureCache.buffer) { // happens first time, or when we need to grow
215
ensureCache.size += 128; // heuristic, avoid many small grow events
216
ensureCache.buffer = Module['_webidl_malloc'](ensureCache.size);
217
assert(ensureCache.buffer);
218
}
219
ensureCache.pos = 0;
220
},
221
alloc(array, view) {
222
assert(ensureCache.buffer);
223
var bytes = view.BYTES_PER_ELEMENT;
224
var len = array.length * bytes;
225
len = alignMemory(len, 8); // keep things aligned to 8 byte boundaries
226
var ret;
227
if (ensureCache.pos + len >= ensureCache.size) {
228
// we failed to allocate in the buffer, next time around :(
229
assert(len > 0); // null terminator, at least
230
ensureCache.needed += len;
231
ret = Module['_webidl_malloc'](len);
232
ensureCache.temps.push(ret);
233
} else {
234
// we can allocate in the buffer
235
ret = ensureCache.buffer + ensureCache.pos;
236
ensureCache.pos += len;
237
}
238
return ret;
239
},
240
};
241
242
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant) */
243
function ensureString(value) {
244
if (typeof value === 'string') {
245
var intArray = intArrayFromString(value);
246
var offset = ensureCache.alloc(intArray, HEAP8);
247
for (var i = 0; i < intArray.length; i++) {
248
HEAP8[offset + i] = intArray[i];
249
}
250
return offset;
251
}
252
return value;
253
}
254
255
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant) */
256
function ensureInt8(value) {
257
if (typeof value === 'object') {
258
var offset = ensureCache.alloc(value, HEAP8);
259
for (var i = 0; i < value.length; i++) {
260
HEAP8[offset + i] = value[i];
261
}
262
return offset;
263
}
264
return value;
265
}
266
267
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant) */
268
function ensureInt16(value) {
269
if (typeof value === 'object') {
270
var offset = ensureCache.alloc(value, HEAP16);
271
var heapOffset = offset / 2;
272
for (var i = 0; i < value.length; i++) {
273
HEAP16[heapOffset + i] = value[i];
274
}
275
return offset;
276
}
277
return value;
278
}
279
280
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant) */
281
function ensureInt32(value) {
282
if (typeof value === 'object') {
283
var offset = ensureCache.alloc(value, HEAP32);
284
var heapOffset = offset / 4;
285
for (var i = 0; i < value.length; i++) {
286
HEAP32[heapOffset + i] = value[i];
287
}
288
return offset;
289
}
290
return value;
291
}
292
293
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant) */
294
function ensureFloat32(value) {
295
if (typeof value === 'object') {
296
var offset = ensureCache.alloc(value, HEAPF32);
297
var heapOffset = offset / 4;
298
for (var i = 0; i < value.length; i++) {
299
HEAPF32[heapOffset + i] = value[i];
300
}
301
return offset;
302
}
303
return value;
304
}
305
306
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant) */
307
function ensureFloat64(value) {
308
if (typeof value === 'object') {
309
var offset = ensureCache.alloc(value, HEAPF64);
310
var heapOffset = offset / 8;
311
for (var i = 0; i < value.length; i++) {
312
HEAPF64[heapOffset + i] = value[i];
313
}
314
return offset;
315
}
316
return value;
317
}
318
''']
319
320
C_FLOATS = ['float', 'double']
321
322
323
def full_typename(arg):
324
return ('const ' if arg.getExtendedAttribute('Const') else '') + arg.type.name + ('[]' if arg.type.isArray() else '')
325
326
327
def type_to_c(t, non_pointing=False):
328
# print 'to c ', t
329
def base_type_to_c(t):
330
match t:
331
case 'Long':
332
return 'int'
333
case 'UnsignedLong':
334
return 'unsigned int'
335
case 'LongLong':
336
return 'long long'
337
case 'UnsignedLongLong':
338
return 'unsigned long long'
339
case 'Short':
340
return 'short'
341
case 'UnsignedShort':
342
return 'unsigned short'
343
case 'Byte':
344
return 'char'
345
case 'Octet':
346
return 'unsigned char'
347
case 'Void':
348
return 'void'
349
case 'String':
350
return 'char*'
351
case 'Float':
352
return 'float'
353
case 'Double':
354
return 'double'
355
case 'Boolean':
356
return 'bool'
357
case 'Any' | 'VoidPtr':
358
return 'void*'
359
if t in interfaces:
360
return (interfaces[t].getExtendedAttribute('Prefix') or [''])[0] + t + ('' if non_pointing else '*')
361
return t
362
363
t = t.replace(' (Wrapper)', '')
364
365
prefix = ''
366
suffix = ''
367
if '[]' in t:
368
t = t.replace('[]', '')
369
suffix = '*'
370
if 'const ' in t:
371
t = t.replace('const ', '')
372
prefix = 'const '
373
return prefix + base_type_to_c(t) + suffix
374
375
376
def take_addr_if_nonpointer(m):
377
if m.getExtendedAttribute('Ref') or m.getExtendedAttribute('Value'):
378
return '&'
379
return ''
380
381
382
def deref_if_nonpointer(m):
383
if m.getExtendedAttribute('Ref') or m.getExtendedAttribute('Value'):
384
return '*'
385
return ''
386
387
388
def type_to_cdec(raw):
389
ret = type_to_c(full_typename(raw), non_pointing=True)
390
if raw.type.name not in interfaces:
391
return ret
392
if raw.getExtendedAttribute('Ref'):
393
return ret + '&'
394
if raw.getExtendedAttribute('Value'):
395
return ret
396
return ret + '*'
397
398
399
def render_function(class_name, func_name, sigs, return_type, non_pointer, # noqa: C901, PLR0912, PLR0915
400
copy, operator, constructor, is_static, func_scope,
401
call_content=None, const=False, array_attribute=False,
402
bind_to=None):
403
"""Future modifications should consider refactoring to reduce complexity.
404
405
* The McCabe cyclomatiic complexity is currently 67 vs 10 recommended.
406
* There are currently 79 branches vs 12 recommended.
407
* There are currently 195 statements vs 50 recommended.
408
409
To revalidate these numbers, run `ruff check --select=C901,PLR091`.
410
"""
411
legacy_mode = CHECKS not in ['ALL', 'FAST']
412
all_checks = CHECKS == 'ALL'
413
414
bindings_name = class_name + '_' + func_name
415
min_args = min(sigs.keys())
416
max_args = max(sigs.keys())
417
418
all_args = sigs.get(max_args)
419
420
if DEBUG:
421
dbg('renderfunc', class_name, func_name, list(sigs.keys()), return_type, constructor)
422
for i, a in enumerate(all_args):
423
if isinstance(a, WebIDL.IDLArgument):
424
dbg(' ', a.identifier.name, a.identifier, a.type, a.optional)
425
else:
426
dbg(' arg%d (%s)' % (i, a))
427
428
# JS
429
430
cache = ('getCache(%s)[this.ptr] = this;' % class_name) if constructor else ''
431
call_prefix = ''
432
if constructor:
433
call_prefix += 'this.ptr = '
434
call_postfix = ''
435
if return_type != 'Void' and not constructor:
436
call_prefix = 'return '
437
438
ptr_rtn = constructor or return_type in interfaces or return_type == 'String'
439
if options.wasm64 and ptr_rtn:
440
call_postfix += ')'
441
442
if not constructor:
443
if return_type in interfaces:
444
call_prefix += 'wrapPointer('
445
call_postfix += ', ' + return_type + ')'
446
elif return_type == 'String':
447
call_prefix += 'UTF8ToString('
448
call_postfix += ')'
449
elif return_type == 'Boolean':
450
call_prefix += '!!('
451
call_postfix += ')'
452
453
if options.wasm64 and ptr_rtn:
454
call_prefix += 'Number('
455
456
args = [(all_args[i].identifier.name if isinstance(all_args[i], WebIDL.IDLArgument) else ('arg%d' % i)) for i in range(max_args)]
457
if not constructor and not is_static:
458
body = ' var self = this.ptr;\n'
459
if options.wasm64:
460
pre_arg = ['BigInt(self)']
461
else:
462
pre_arg = ['self']
463
else:
464
body = ''
465
pre_arg = []
466
467
if any(arg.type.isString() or arg.type.isArray() for arg in all_args):
468
body += ' ensureCache.prepare();\n'
469
470
def is_ptr_arg(i):
471
t = all_args[i].type
472
return (t.isArray() or t.isAny() or t.isString() or t.isObject() or t.isInterface())
473
474
for i, (js_arg, arg) in enumerate(zip(args, all_args, strict=True)):
475
optional = i >= min_args
476
do_default = False
477
# Filter out arguments we don't know how to parse. Fast casing only common cases.
478
compatible_arg = isinstance(arg, Dummy) or (isinstance(arg, WebIDL.IDLArgument) and arg.optional is False)
479
# note: null has typeof object, but is ok to leave as is, since we are calling into asm code where null|0 = 0
480
if not legacy_mode and compatible_arg:
481
if isinstance(arg, WebIDL.IDLArgument):
482
arg_name = arg.identifier.name
483
else:
484
arg_name = ''
485
# Format assert fail message
486
check_msg = "[CHECK FAILED] %s::%s(%s:%s): " % (class_name, func_name, js_arg, arg_name)
487
if isinstance(arg.type, WebIDL.IDLWrapperType):
488
inner = arg.type.inner
489
else:
490
inner = ""
491
492
# Print type info in comments.
493
body += " /* %s <%s> [%s] */\n" % (js_arg, arg.type.name, inner)
494
495
# Wrap asserts with existence check when argument is optional.
496
if all_checks and optional:
497
body += "if(typeof {0} !== 'undefined' && {0} !== null) {{\n".format(js_arg)
498
# Special case argument types.
499
if arg.type.isNumeric():
500
if arg.type.isInteger():
501
if all_checks:
502
body += " assert(typeof {0} === 'number' && !isNaN({0}), '{1}Expecting <integer>');\n".format(js_arg, check_msg)
503
else:
504
if all_checks:
505
body += " assert(typeof {0} === 'number', '{1}Expecting <number>');\n".format(js_arg, check_msg)
506
# No transform needed for numbers
507
elif arg.type.isBoolean():
508
if all_checks:
509
body += " assert(typeof {0} === 'boolean' || (typeof {0} === 'number' && !isNaN({0})), '{1}Expecting <boolean>');\n".format(js_arg, check_msg)
510
# No transform needed for booleans
511
elif arg.type.isString():
512
# Strings can be DOM strings or pointers.
513
if all_checks:
514
body += " assert(typeof {0} === 'string' || ({0} && typeof {0} === 'object' && typeof {0}.ptr === 'number'), '{1}Expecting <string>');\n".format(js_arg, check_msg)
515
do_default = True # legacy path is fast enough for strings.
516
elif arg.type.isInterface():
517
if all_checks:
518
body += " assert(typeof {0} === 'object' && typeof {0}.ptr === 'number', '{1}Expecting <pointer>');\n".format(js_arg, check_msg)
519
if optional:
520
body += " if(typeof {0} !== 'undefined' && {0} !== null) {{ {0} = {0}.ptr }};\n".format(js_arg)
521
else:
522
# No checks in fast mode when the arg is required
523
body += " {0} = {0}.ptr;\n".format(js_arg)
524
else:
525
do_default = True
526
527
if all_checks and optional:
528
body += "}\n"
529
else:
530
do_default = True
531
532
if do_default:
533
if not (arg.type.isArray() and not array_attribute):
534
body += f" if ({js_arg} && typeof {js_arg} === 'object') {js_arg} = {js_arg}.ptr;\n"
535
if arg.type.isString():
536
body += " else {0} = ensureString({0});\n".format(js_arg)
537
if options.wasm64 and is_ptr_arg(i):
538
body += f' if ({args[i]} === null) {args[i]} = 0;\n'
539
else:
540
# an array can be received here
541
match arg.type.name:
542
case 'Byte' | 'Octet':
543
body += " if (typeof {0} == 'object') {{ {0} = ensureInt8({0}); }}\n".format(js_arg)
544
case 'Short' | 'UnsignedShort':
545
body += " if (typeof {0} == 'object') {{ {0} = ensureInt16({0}); }}\n".format(js_arg)
546
case 'Long' | 'UnsignedLong':
547
body += " if (typeof {0} == 'object') {{ {0} = ensureInt32({0}); }}\n".format(js_arg)
548
case 'Float':
549
body += " if (typeof {0} == 'object') {{ {0} = ensureFloat32({0}); }}\n".format(js_arg)
550
case 'Double':
551
body += " if (typeof {0} == 'object') {{ {0} = ensureFloat64({0}); }}\n".format(js_arg)
552
553
call_args = pre_arg.copy()
554
555
for i, arg in enumerate(args):
556
if options.wasm64 and is_ptr_arg(i):
557
arg = f'BigInt({arg})'
558
call_args.append(arg)
559
560
c_names = {}
561
562
def make_call_args(i):
563
if pre_arg:
564
i += 1
565
return ', '.join(call_args[:i])
566
567
for i in range(min_args, max_args):
568
c_names[i] = f'emscripten_bind_{bindings_name}_{i}'
569
if 'return ' in call_prefix:
570
after_call = ''
571
else:
572
after_call = '; ' + cache + 'return'
573
args_for_call = make_call_args(i)
574
body += ' if (%s === undefined) { %s_%s(%s)%s%s }\n' % (args[i], call_prefix, c_names[i],
575
args_for_call,
576
call_postfix, after_call)
577
dbg(call_prefix)
578
c_names[max_args] = f'emscripten_bind_{bindings_name}_{max_args}'
579
args_for_call = make_call_args(len(args))
580
body += ' %s_%s(%s)%s;\n' % (call_prefix, c_names[max_args], args_for_call, call_postfix)
581
if cache:
582
body += f' {cache}\n'
583
584
if constructor:
585
declare_name = ' ' + func_name
586
else:
587
declare_name = ''
588
mid_js.append(r'''function%s(%s) {
589
%s
590
};
591
''' % (declare_name, ', '.join(args), body[:-1]))
592
593
# C
594
595
for i in range(min_args, max_args + 1):
596
raw = sigs.get(i)
597
if raw is None:
598
continue
599
sig = list(map(full_typename, raw))
600
if array_attribute:
601
# for arrays, ignore that this is an array - our get/set methods operate on the elements
602
sig = [x.replace('[]', '') for x in sig]
603
604
c_arg_types = list(map(type_to_c, sig))
605
c_class_name = type_to_c(class_name, non_pointing=True)
606
607
normal_args = ', '.join(['%s %s' % (c_arg_types[j], args[j]) for j in range(i)])
608
if constructor or is_static:
609
full_args = normal_args
610
else:
611
full_args = c_class_name + '* self'
612
if normal_args:
613
full_args += ', ' + normal_args
614
call_args = ', '.join(['%s%s' % ('*' if raw[j].getExtendedAttribute('Ref') else '', args[j]) for j in range(i)])
615
if constructor:
616
call = 'new ' + c_class_name + '(' + call_args + ')'
617
elif call_content is not None:
618
call = call_content
619
else:
620
if not bind_to:
621
bind_to = func_name
622
call = bind_to + '(' + call_args + ')'
623
if is_static:
624
call = c_class_name + '::' + call
625
else:
626
call = 'self->' + call
627
628
if operator:
629
cast_self = 'self'
630
if class_name != func_scope:
631
# this function comes from an ancestor class; for operators, we must cast it
632
cast_self = 'dynamic_cast<' + type_to_c(func_scope) + '>(' + cast_self + ')'
633
maybe_deref = deref_if_nonpointer(raw[0])
634
operator = operator.strip()
635
if operator in ["+", "-", "*", "/", "%", "^", "&", "|", "=",
636
"<", ">", "+=", "-=", "*=", "/=", "%=", "^=", "&=", "|=", "<<", ">>", ">>=",
637
"<<=", "==", "!=", "<=", ">=", "<=>", "&&", "||"]:
638
call = '(*%s %s %s%s)' % (cast_self, operator, maybe_deref, args[0])
639
elif operator == '[]':
640
call = '((*%s)[%s%s])' % (cast_self, maybe_deref, args[0])
641
else:
642
raise Exception('unfamiliar operator ' + operator)
643
644
pre = ''
645
646
basic_return = 'return ' if constructor or return_type != 'Void' else ''
647
return_prefix = basic_return
648
return_postfix = ''
649
if non_pointer:
650
return_prefix += '&'
651
if copy:
652
# Avoid sharing this static temp var between threads, which could race.
653
pre += ' static thread_local %s temp;\n' % type_to_c(return_type, non_pointing=True)
654
return_prefix += '(temp = '
655
return_postfix += ', &temp)'
656
657
c_return_type = type_to_c(return_type)
658
maybe_const = 'const ' if const else ''
659
mid_c.append(r'''
660
%s%s EMSCRIPTEN_KEEPALIVE %s(%s) {
661
%s %s%s%s;
662
}
663
''' % (maybe_const, type_to_c(class_name) if constructor else c_return_type, c_names[i], full_args, pre, return_prefix, call, return_postfix))
664
665
if not constructor:
666
if i == max_args:
667
dec_args = ', '.join([type_to_cdec(raw[j]) + ' ' + args[j] for j in range(i)])
668
js_call_args = ', '.join(['%s%s' % (('(ptrdiff_t)' if sig[j] in interfaces else '') + take_addr_if_nonpointer(raw[j]), args[j]) for j in range(i)])
669
em_asm_macro = 'EM_ASM_%s' % ('PTR' if c_return_type[-1] == '*' else 'INT' if c_return_type not in C_FLOATS else 'DOUBLE')
670
671
js_impl_methods.append(r''' %s %s(%s) %s {
672
%s (%s) %s({
673
var self = Module['getCache'](Module['%s'])[$0];
674
if (!self.hasOwnProperty('%s')) throw 'a JSImplementation must implement all functions, you forgot %s::%s.';
675
%sself['%s'](%s)%s;
676
}, (ptrdiff_t)this%s);
677
}''' % (c_return_type, func_name, dec_args, maybe_const,
678
basic_return, c_return_type, em_asm_macro,
679
class_name,
680
func_name, class_name, func_name,
681
return_prefix,
682
func_name,
683
','.join(['$%d' % i for i in range(1, max_args + 1)]),
684
return_postfix,
685
(', ' if js_call_args else '') + js_call_args))
686
687
688
def add_bounds_check_impl():
689
if hasattr(add_bounds_check_impl, 'done'):
690
return
691
add_bounds_check_impl.done = True
692
mid_c.append('''
693
EM_JS(void, array_bounds_check_error, (size_t idx, size_t size), {
694
throw 'Array index ' + idx + ' out of bounds: [0,' + size + ')';
695
});
696
697
static void array_bounds_check(size_t array_size, size_t array_idx) {
698
if (array_idx < 0 || array_idx >= array_size) {
699
array_bounds_check_error(array_idx, array_size);
700
}
701
}
702
''')
703
704
705
for name, interface in interfaces.items():
706
js_impl = interface.getExtendedAttribute('JSImplementation')
707
if not js_impl:
708
continue
709
implements[name] = [js_impl[0]]
710
711
# Compute the height in the inheritance tree of each node. Note that the order of iteration
712
# of `implements` is irrelevant.
713
#
714
# After one iteration of the loop, all ancestors of child are guaranteed to have a larger
715
# height number than the child, and this is recursively true for each ancestor. If the height
716
# of child is later increased, all its ancestors will be readjusted at that time to maintain
717
# that invariant. Further, the height of a node never decreases. Therefore, when the loop
718
# finishes, all ancestors of a given node should have a larger height number than that node.
719
nodeHeight = {}
720
for child, parent in implements.items():
721
parent = parent[0]
722
while parent:
723
nodeHeight[parent] = max(nodeHeight.get(parent, 0), nodeHeight.get(child, 0) + 1)
724
grandParent = implements.get(parent)
725
if grandParent:
726
child = parent
727
parent = grandParent[0]
728
else:
729
parent = None
730
731
names = sorted(interfaces.keys(), key=lambda x: nodeHeight.get(x, 0), reverse=True)
732
733
for name in names:
734
interface = interfaces[name]
735
736
mid_js += ['\n// Interface: ' + name + '\n\n']
737
mid_c += ['\n// Interface: ' + name + '\n\n']
738
739
js_impl_methods: list[str] = []
740
741
cons = interface.getExtendedAttribute('Constructor')
742
if type(cons) is list:
743
raise Exception('do not use "Constructor", instead create methods with the name of the interface')
744
745
js_impl = interface.getExtendedAttribute('JSImplementation')
746
if js_impl:
747
js_impl = js_impl[0]
748
749
# Methods
750
751
# Ensure a constructor even if one is not specified.
752
if not any(m.identifier.name == name for m in interface.members):
753
mid_js += ['%s\nfunction %s() { throw "cannot construct a %s, no constructor in IDL" }\n' % (CONSTRUCTOR_CLOSURE_SUPPRESSIONS, name, name)]
754
mid_js += build_constructor(name)
755
756
for m in interface.members:
757
if not m.isMethod():
758
continue
759
constructor = m.identifier.name == name
760
if not constructor:
761
parent_constructor = False
762
temp = m.parentScope
763
while temp.parentScope:
764
if temp.identifier.name == m.identifier.name:
765
parent_constructor = True
766
temp = temp.parentScope
767
if parent_constructor:
768
continue
769
mid_js += [CONSTRUCTOR_CLOSURE_SUPPRESSIONS, '\n']
770
if not constructor:
771
mid_js += ["%s.prototype['%s'] = %s.prototype.%s = " % (name, m.identifier.name, name, m.identifier.name)]
772
sigs = {}
773
return_type = None
774
for ret, args in m.signatures():
775
if return_type is None:
776
return_type = ret.name
777
else:
778
assert return_type == ret.name, 'overloads must have the same return type'
779
for i in range(len(args) + 1):
780
if i == len(args) or args[i].optional:
781
assert i not in sigs, f'{m.identifier.name}: overloading must differentiate by # of arguments (cannot have two signatures that differ by types but not by length)'
782
sigs[i] = args[:i]
783
render_function(name,
784
m.identifier.name, sigs, return_type,
785
m.getExtendedAttribute('Ref'),
786
m.getExtendedAttribute('Value'),
787
(m.getExtendedAttribute('Operator') or [None])[0],
788
constructor,
789
is_static=m.isStatic(),
790
func_scope=m.parentScope.identifier.name,
791
const=m.getExtendedAttribute('Const'),
792
bind_to=(m.getExtendedAttribute('BindTo') or [None])[0])
793
mid_js += ['\n']
794
if constructor:
795
mid_js += build_constructor(name)
796
797
for m in interface.members:
798
if not m.isAttr():
799
continue
800
attr = m.identifier.name
801
802
if m.type.isArray():
803
get_sigs = {1: [Dummy(type=WebIDL.BuiltinTypes[WebIDL.IDLBuiltinType.Types.long])]}
804
set_sigs = {2: [Dummy(type=WebIDL.BuiltinTypes[WebIDL.IDLBuiltinType.Types.long]),
805
Dummy(type=m.type.inner)]}
806
get_call_content = take_addr_if_nonpointer(m) + 'self->' + attr + '[arg0]'
807
set_call_content = 'self->' + attr + '[arg0] = ' + deref_if_nonpointer(m) + 'arg1'
808
if m.getExtendedAttribute('BoundsChecked'):
809
810
bounds_check = "array_bounds_check(sizeof(self->%s) / sizeof(self->%s[0]), arg0)" % (attr, attr)
811
add_bounds_check_impl()
812
813
get_call_content = "(%s, %s)" % (bounds_check, get_call_content)
814
set_call_content = "(%s, %s)" % (bounds_check, set_call_content)
815
else:
816
get_sigs = {0: []}
817
set_sigs = {1: [Dummy(type=m.type)]}
818
get_call_content = take_addr_if_nonpointer(m) + 'self->' + attr
819
set_call_content = 'self->' + attr + ' = ' + deref_if_nonpointer(m) + 'arg0'
820
821
get_name = 'get_' + attr
822
mid_js += [r'''%s
823
%s.prototype['%s'] = %s.prototype.%s = ''' % (CONSTRUCTOR_CLOSURE_SUPPRESSIONS, name, get_name, name, get_name)]
824
render_function(name,
825
get_name, get_sigs, m.type.name,
826
None,
827
None,
828
None,
829
False,
830
False,
831
func_scope=interface,
832
call_content=get_call_content,
833
const=m.getExtendedAttribute('Const'),
834
array_attribute=m.type.isArray())
835
836
if m.readonly:
837
mid_js += [r'''
838
/** @suppress {checkTypes} */
839
Object.defineProperty(%s.prototype, '%s', { get: %s.prototype.%s });
840
''' % (name, attr, name, get_name)]
841
else:
842
set_name = 'set_' + attr
843
mid_js += [r'''
844
%s
845
%s.prototype['%s'] = %s.prototype.%s = ''' % (CONSTRUCTOR_CLOSURE_SUPPRESSIONS, name, set_name, name, set_name)]
846
render_function(name,
847
set_name, set_sigs, 'Void',
848
None,
849
None,
850
None,
851
False,
852
False,
853
func_scope=interface,
854
call_content=set_call_content,
855
const=m.getExtendedAttribute('Const'),
856
array_attribute=m.type.isArray())
857
mid_js += [r'''
858
/** @suppress {checkTypes} */
859
Object.defineProperty(%s.prototype, '%s', { get: %s.prototype.%s, set: %s.prototype.%s });
860
''' % (name, attr, name, get_name, name, set_name)]
861
862
if not interface.getExtendedAttribute('NoDelete'):
863
mid_js += [r'''
864
%s
865
%s.prototype['__destroy__'] = %s.prototype.__destroy__ = ''' % (CONSTRUCTOR_CLOSURE_SUPPRESSIONS, name, name)]
866
render_function(name,
867
'__destroy__', {0: []}, 'Void',
868
None,
869
None,
870
None,
871
False,
872
False,
873
func_scope=interface,
874
call_content='delete self')
875
876
# Emit C++ class implementation that calls into JS implementation
877
878
if js_impl:
879
pre_c += ['''
880
class %s : public %s {
881
public:
882
%s
883
};
884
''' % (name, type_to_c(js_impl, non_pointing=True), '\n'.join(js_impl_methods))]
885
886
deferred_js = []
887
888
for name, enum in enums.items():
889
mid_c += [f'\n// ${name}\n']
890
deferred_js += [f'\n// ${name}\n']
891
for value in enum.values():
892
function_id = '%s_%s' % (name, value.split('::')[-1])
893
function_id = 'emscripten_enum_%s' % function_id
894
mid_c += ['''%s EMSCRIPTEN_KEEPALIVE %s() {
895
return %s;
896
}
897
''' % (name, function_id, value)]
898
symbols = value.split('::')
899
if len(symbols) == 1:
900
identifier = symbols[0]
901
deferred_js += ["Module['%s'] = _%s();\n" % (identifier, function_id)]
902
elif len(symbols) == 2:
903
[namespace, identifier] = symbols
904
if namespace in interfaces:
905
# namespace is a class
906
deferred_js += ["Module['%s']['%s'] = _%s();\n" % (namespace, identifier, function_id)]
907
else:
908
# namespace is a namespace, so the enums get collapsed into the top level namespace.
909
deferred_js += ["Module['%s'] = _%s();\n" % (identifier, function_id)]
910
else:
911
raise Exception(f'Illegal enum value ${value}')
912
913
mid_c += ['\n}\n\n']
914
if len(deferred_js):
915
mid_js += ['''
916
(function() {
917
function setupEnums() {
918
%s
919
}
920
if (runtimeInitialized) setupEnums();
921
else addOnInit(setupEnums);
922
})();
923
''' % '\n '.join(deferred_js)]
924
925
# Write
926
927
with open(cpp_output, 'w') as c:
928
for x in pre_c:
929
c.write(x)
930
for x in mid_c:
931
c.write(x)
932
933
with open(js_output, 'w') as js:
934
for x in mid_js:
935
js.write(x)
936
937