File size: 5,018 Bytes
00df61d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
147
148
149
150
151
152
153
154
155
156
157
/**
 * @license
 * Copyright 2022 The Emscripten Authors
 * SPDX-License-Identifier: MIT
 */

addToLibrary({
  // Returns the C function with a specified identifier (for C++, you need to do manual name mangling)
#if MODULARIZE == 'instance' && !INCLUDE_FULL_LIBRARY
  $getCFunc__deps: [() => error('ccall is not yet compatible with MODULARIZE=instance')],
#endif
  $getCFunc__internal: true,
  $getCFunc: (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', '$writeArrayToMemory', '$stringToUTF8OnStack', '$stackSave', '$stackRestore', '$stackAlloc'],
  $ccall__docs: `
  /**
   * @param {string|null=} returnType
   * @param {Array=} argTypes
   * @param {Array=} args
   * @param {Object=} opts
   */`,
  $ccall: (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
          ret = stringToUTF8OnStack(str);
        }
        return {{{ to64('ret') }}};
      },
      'array': (arr) => {
        var ret = stackAlloc(arr.length);
        writeArrayToMemory(arr, ret);
        return {{{ to64('ret') }}};
      }
    };

    function convertReturnValue(ret) {
      if (returnType === 'string') {
        return UTF8ToString({{{ from64Expr('ret') }}});
      }
#if MEMORY64
      if (returnType === 'pointer') return Number(ret);
#elif CAN_ADDRESS_2GB
      if (returnType === 'pointer') return ret >>> 0;
#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 == 1
    // Data for a previous async operation that was in flight before us.
    var previousAsync = Asyncify.currData;
#endif
    var ret = func(...cArgs);
    function onDone(ret) {
#if ASYNCIFY == 1
      runtimeKeepalivePop();
#endif
      if (stack !== 0) stackRestore(stack);
      return convertReturnValue(ret);
    }
#if ASYNCIFY
  var asyncMode = opts?.async;
#endif

#if ASYNCIFY == 1
    // 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();
    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 in 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

#if ASYNCIFY == 2
    if (asyncMode) return ret.then(onDone);
#endif

    ret = onDone(ret);
#if ASYNCIFY == 1
    // 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: [ '$ccall',
#if !ASSERTIONS
    '$getCFunc',
#endif
  ],
  $cwrap: (ident, returnType, argTypes, opts) => {
#if !ASSERTIONS
    // When the function takes numbers and returns a number, we can just return
    // the original function
    var numericArgs = !argTypes || argTypes.every((type) => type === 'number' || type === 'boolean');
    var numericRet = returnType !== 'string';
    if (numericRet && numericArgs && !opts) {
      return getCFunc(ident);
    }
#endif
    return (...args) => ccall(ident, returnType, argTypes, args, opts);
  },
});