Skip to content

Commit 81948f0

Browse files
authored
Make ccall/cwrap into library functions (#17433)
These are included as part of LEGACY_RUNTIME, which currently defaults to true, which means this change should not effect any existing users.
1 parent 34761bb commit 81948f0

11 files changed

+163
-142
lines changed

ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ See docs/process.md for more on how version tagging works.
5050
- intArrayFromString
5151
- intArrayToString
5252
- warnOnce
53+
- ccall
54+
- cwrap
5355
However, they all still available by default due to a new setting called
5456
`LEGACY_RUNTIME` which is enabled by default. When `LEGACY_RUNTIME` is
5557
disabled (which it may be in the future) these symbols would only be included

src/library.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3651,5 +3651,7 @@ DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.push(
36513651
'$intArrayFromString',
36523652
'$intArrayToString',
36533653
'$warnOnce',
3654+
'$ccall',
3655+
'$cwrap',
36543656
);
36553657
#endif

src/library_ccall.js

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

src/library_dylink.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
// ==========================================================================
2-
// Dynamic library loading
3-
//
4-
// ==========================================================================
1+
/**
2+
* @license
3+
* Copyright 2020 The Emscripten Authors
4+
* SPDX-License-Identifier: MIT
5+
*
6+
* Dynamic library loading
7+
*/
58

69
var dlopenMissingError = "'To use dlopen, you need enable dynamic linking, see https://github.com/emscripten-core/emscripten/wiki/Linking'"
710

src/modules.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ global.LibraryManager = {
3838
let libraries = [
3939
'library.js',
4040
'library_int53.js',
41+
'library_ccall.js',
4142
'library_addfunction.js',
4243
'library_formatString.js',
4344
'library_getvalue.js',
@@ -345,8 +346,6 @@ function exportRuntime() {
345346
// All possible runtime elements that can be exported
346347
let runtimeElements = [
347348
'run',
348-
'ccall',
349-
'cwrap',
350349
'UTF8ArrayToString',
351350
'UTF8ToString',
352351
'stringToUTF8Array',

src/preamble.js

Lines changed: 0 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -86,137 +86,6 @@ function assert(condition, text) {
8686
}
8787
}
8888

89-
// Returns the C function with a specified identifier (for C++, you need to do manual name mangling)
90-
function getCFunc(ident) {
91-
var func = Module['_' + ident]; // closure exported function
92-
#if ASSERTIONS
93-
assert(func, 'Cannot call unknown function ' + ident + ', make sure it is exported');
94-
#endif
95-
return func;
96-
}
97-
98-
// C calling interface.
99-
/** @param {string|null=} returnType
100-
@param {Array=} argTypes
101-
@param {Arguments|Array=} args
102-
@param {Object=} opts */
103-
function ccall(ident, returnType, argTypes, args, opts) {
104-
// For fast lookup of conversion functions
105-
var toC = {
106-
#if MEMORY64
107-
'pointer': (p) => {{{ to64('p') }}},
108-
#endif
109-
'string': function(str) {
110-
var ret = 0;
111-
if (str !== null && str !== undefined && str !== 0) { // null string
112-
// at most 4 bytes per UTF-8 code point, +1 for the trailing '\0'
113-
var len = (str.length << 2) + 1;
114-
ret = stackAlloc(len);
115-
stringToUTF8(str, ret, len);
116-
}
117-
return {{{ to64('ret') }}};
118-
},
119-
'array': function(arr) {
120-
var ret = stackAlloc(arr.length);
121-
writeArrayToMemory(arr, ret);
122-
return {{{ to64('ret') }}};
123-
}
124-
};
125-
126-
function convertReturnValue(ret) {
127-
if (returnType === 'string') {
128-
{{{ from64('ret') }}}
129-
return UTF8ToString(ret);
130-
}
131-
#if MEMORY64
132-
if (returnType === 'pointer') return Number(ret);
133-
#endif
134-
if (returnType === 'boolean') return Boolean(ret);
135-
return ret;
136-
}
137-
138-
var func = getCFunc(ident);
139-
var cArgs = [];
140-
var stack = 0;
141-
#if ASSERTIONS
142-
assert(returnType !== 'array', 'Return type should not be "array".');
143-
#endif
144-
if (args) {
145-
for (var i = 0; i < args.length; i++) {
146-
var converter = toC[argTypes[i]];
147-
if (converter) {
148-
if (stack === 0) stack = stackSave();
149-
cArgs[i] = converter(args[i]);
150-
} else {
151-
cArgs[i] = args[i];
152-
}
153-
}
154-
}
155-
#if ASYNCIFY
156-
// Data for a previous async operation that was in flight before us.
157-
var previousAsync = Asyncify.currData;
158-
#endif
159-
var ret = func.apply(null, cArgs);
160-
function onDone(ret) {
161-
#if ASYNCIFY
162-
runtimeKeepalivePop();
163-
#endif
164-
if (stack !== 0) stackRestore(stack);
165-
return convertReturnValue(ret);
166-
}
167-
#if ASYNCIFY
168-
// Keep the runtime alive through all calls. Note that this call might not be
169-
// async, but for simplicity we push and pop in all calls.
170-
runtimeKeepalivePush();
171-
var asyncMode = opts && opts.async;
172-
if (Asyncify.currData != previousAsync) {
173-
#if ASSERTIONS
174-
// A change in async operation happened. If there was already an async
175-
// operation in flight before us, that is an error: we should not start
176-
// another async operation while one is active, and we should not stop one
177-
// either. The only valid combination is to have no change in the async
178-
// data (so we either had one in flight and left it alone, or we didn't have
179-
// one), or to have nothing in flight and to start one.
180-
assert(!(previousAsync && Asyncify.currData), 'We cannot start an async operation when one is already flight');
181-
assert(!(previousAsync && !Asyncify.currData), 'We cannot stop an async operation in flight');
182-
#endif
183-
// This is a new async operation. The wasm is paused and has unwound its stack.
184-
// We need to return a Promise that resolves the return value
185-
// once the stack is rewound and execution finishes.
186-
#if ASSERTIONS
187-
assert(asyncMode, 'The call to ' + ident + ' is running asynchronously. If this was intended, add the async option to the ccall/cwrap call.');
188-
#endif
189-
return Asyncify.whenDone().then(onDone);
190-
}
191-
#endif
192-
193-
ret = onDone(ret);
194-
#if ASYNCIFY
195-
// If this is an async ccall, ensure we return a promise
196-
if (asyncMode) return Promise.resolve(ret);
197-
#endif
198-
return ret;
199-
}
200-
201-
/** @param {string=} returnType
202-
@param {Array=} argTypes
203-
@param {Object=} opts */
204-
function cwrap(ident, returnType, argTypes, opts) {
205-
#if !ASSERTIONS
206-
argTypes = argTypes || [];
207-
// When the function takes numbers and returns a number, we can just return
208-
// the original function
209-
var numericArgs = argTypes.every(function(type){ return type === 'number'});
210-
var numericRet = returnType !== 'string';
211-
if (numericRet && numericArgs && !opts) {
212-
return getCFunc(ident);
213-
}
214-
#endif
215-
return function() {
216-
return ccall(ident, returnType, argTypes, arguments, opts);
217-
}
218-
}
219-
22089
#if ASSERTIONS
22190
// We used to include malloc/free by default in the past. Show a helpful error in
22291
// builds with assertions.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
26718
1+
26737
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
21422
1+
21441
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
94964
1+
95175
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
56517
1+
56694

0 commit comments

Comments
 (0)