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