forked from emscripten-core/emscripten
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlibrary_ccall.js
146 lines (140 loc) · 4.68 KB
/
library_ccall.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/**
* @license
* Copyright 2022 The Emscripten Authors
* SPDX-License-Identifier: MIT
*/
mergeInto(LibraryManager.library, {
// Returns the C function with a specified identifier (for C++, you need to do manual name mangling)
$getCFunc: function(ident) {
var func = Module['_' + ident]; // closure exported function
#if ASSERTIONS
assert(func, 'Cannot call unknown function ' + ident + ', make sure it is exported');
#endif
return func;
},
// C calling interface.
$ccall__deps: ['$getCFunc'],
$ccall__docs: `
/**
* @param {string|null=} returnType
* @param {Array=} argTypes
* @param {Arguments|Array=} args
* @param {Object=} opts
*/`,
$ccall: function(ident, returnType, argTypes, args, opts) {
// For fast lookup of conversion functions
var toC = {
#if MEMORY64
'pointer': (p) => {{{ to64('p') }}},
#endif
'string': (str) => {
var ret = 0;
if (str !== null && str !== undefined && str !== 0) { // null string
// at most 4 bytes per UTF-8 code point, +1 for the trailing '\0'
var len = (str.length << 2) + 1;
ret = stackAlloc(len);
stringToUTF8(str, ret, len);
}
return {{{ to64('ret') }}};
},
'array': (arr) => {
var ret = stackAlloc(arr.length);
writeArrayToMemory(arr, ret);
return {{{ to64('ret') }}};
}
};
function convertReturnValue(ret) {
if (returnType === 'string') {
{{{ from64('ret') }}}
return UTF8ToString(ret);
}
#if MEMORY64
if (returnType === 'pointer') return Number(ret);
#endif
if (returnType === 'boolean') return Boolean(ret);
return ret;
}
var func = getCFunc(ident);
var cArgs = [];
var stack = 0;
#if ASSERTIONS
assert(returnType !== 'array', 'Return type should not be "array".');
#endif
if (args) {
for (var i = 0; i < args.length; i++) {
var converter = toC[argTypes[i]];
if (converter) {
if (stack === 0) stack = stackSave();
cArgs[i] = converter(args[i]);
} else {
cArgs[i] = args[i];
}
}
}
#if ASYNCIFY
// Data for a previous async operation that was in flight before us.
var previousAsync = Asyncify.currData;
#endif
var ret = func.apply(null, cArgs);
function onDone(ret) {
#if ASYNCIFY
runtimeKeepalivePop();
#endif
if (stack !== 0) stackRestore(stack);
return convertReturnValue(ret);
}
#if ASYNCIFY
// Keep the runtime alive through all calls. Note that this call might not be
// async, but for simplicity we push and pop in all calls.
runtimeKeepalivePush();
var asyncMode = opts && opts.async;
if (Asyncify.currData != previousAsync) {
#if ASSERTIONS
// A change in async operation happened. If there was already an async
// operation in flight before us, that is an error: we should not start
// another async operation while one is active, and we should not stop one
// either. The only valid combination is to have no change in the async
// data (so we either had one in flight and left it alone, or we didn't have
// one), or to have nothing in flight and to start one.
assert(!(previousAsync && Asyncify.currData), 'We cannot start an async operation when one is already flight');
assert(!(previousAsync && !Asyncify.currData), 'We cannot stop an async operation in flight');
#endif
// This is a new async operation. The wasm is paused and has unwound its stack.
// We need to return a Promise that resolves the return value
// once the stack is rewound and execution finishes.
#if ASSERTIONS
assert(asyncMode, 'The call to ' + ident + ' is running asynchronously. If this was intended, add the async option to the ccall/cwrap call.');
#endif
return Asyncify.whenDone().then(onDone);
}
#endif
ret = onDone(ret);
#if ASYNCIFY
// If this is an async ccall, ensure we return a promise
if (asyncMode) return Promise.resolve(ret);
#endif
return ret;
},
$cwrap__docs: `
/**
* @param {string=} returnType
* @param {Array=} argTypes
* @param {Object=} opts
*/`,
$cwrap__deps: ['$getCFunc', '$ccall'],
$cwrap: function(ident, returnType, argTypes, opts) {
#if !ASSERTIONS
argTypes = argTypes || [];
// When the function takes numbers and returns a number, we can just return
// the original function
var numericArgs = argTypes.every((type) => type === 'number' || type === 'boolean');
var numericRet = returnType !== 'string';
if (numericRet && numericArgs && !opts) {
return getCFunc(ident);
}
#endif
return function() {
return ccall(ident, returnType, argTypes, arguments, opts);
}
},
});