|
| 1 | +/** |
| 2 | + * @license |
| 3 | + * Copyright 2020 The Emscripten Authors |
| 4 | + * SPDX-License-Identifier: MIT |
| 5 | + */ |
| 6 | + |
| 7 | +mergeInto(LibraryManager.library, { |
| 8 | + // This gives correct answers for everything less than 2^{14} = 16384 |
| 9 | + // I hope nobody is contemplating functions with 16384 arguments... |
| 10 | + $uleb128Encode: function(n) { |
| 11 | +#if ASSERTIONS |
| 12 | + assert(n < 16384); |
| 13 | +#endif |
| 14 | + if (n < 128) { |
| 15 | + return [n]; |
| 16 | + } |
| 17 | + return [(n % 128) | 128, n >> 7]; |
| 18 | + }, |
| 19 | + |
| 20 | + // Converts a signature like 'vii' into a description of the wasm types, like |
| 21 | + // { parameters: ['i32', 'i32'], results: [] }. |
| 22 | + $sigToWasmTypes: function(sig) { |
| 23 | + var typeNames = { |
| 24 | + 'i': 'i32', |
| 25 | + 'j': 'i64', |
| 26 | + 'f': 'f32', |
| 27 | + 'd': 'f64', |
| 28 | +#if MEMORY64 |
| 29 | + 'p': 'i64', |
| 30 | +#else |
| 31 | + 'p': 'i32', |
| 32 | +#endif |
| 33 | + }; |
| 34 | + var type = { |
| 35 | + parameters: [], |
| 36 | + results: sig[0] == 'v' ? [] : [typeNames[sig[0]]] |
| 37 | + }; |
| 38 | + for (var i = 1; i < sig.length; ++i) { |
| 39 | +#if ASSERTIONS |
| 40 | + assert(sig[i] in typeNames, 'invalid signature char: ' + sig[i]); |
| 41 | +#endif |
| 42 | + type.parameters.push(typeNames[sig[i]]); |
| 43 | + } |
| 44 | + return type; |
| 45 | + }, |
| 46 | + |
| 47 | + // Wraps a JS function as a wasm function with a given signature. |
| 48 | + $convertJsFunctionToWasm__deps: ['$uleb128Encode', '$sigToWasmTypes'], |
| 49 | + $convertJsFunctionToWasm: function(func, sig) { |
| 50 | +#if WASM2JS |
| 51 | + return func; |
| 52 | +#else // WASM2JS |
| 53 | + |
| 54 | + // If the type reflection proposal is available, use the new |
| 55 | + // "WebAssembly.Function" constructor. |
| 56 | + // Otherwise, construct a minimal wasm module importing the JS function and |
| 57 | + // re-exporting it. |
| 58 | + if (typeof WebAssembly.Function == "function") { |
| 59 | + return new WebAssembly.Function(sigToWasmTypes(sig), func); |
| 60 | + } |
| 61 | + |
| 62 | + // The module is static, with the exception of the type section, which is |
| 63 | + // generated based on the signature passed in. |
| 64 | + var typeSection = [ |
| 65 | + 0x01, // count: 1 |
| 66 | + 0x60, // form: func |
| 67 | + ]; |
| 68 | + var sigRet = sig.slice(0, 1); |
| 69 | + var sigParam = sig.slice(1); |
| 70 | + var typeCodes = { |
| 71 | + 'i': 0x7f, // i32 |
| 72 | +#if MEMORY64 |
| 73 | + 'p': 0x7e, // i64 |
| 74 | +#else |
| 75 | + 'p': 0x7f, // i32 |
| 76 | +#endif |
| 77 | + 'j': 0x7e, // i64 |
| 78 | + 'f': 0x7d, // f32 |
| 79 | + 'd': 0x7c, // f64 |
| 80 | + }; |
| 81 | + |
| 82 | + // Parameters, length + signatures |
| 83 | + typeSection = typeSection.concat(uleb128Encode(sigParam.length)); |
| 84 | + for (var i = 0; i < sigParam.length; ++i) { |
| 85 | +#if ASSERTIONS |
| 86 | + assert(sigParam[i] in typeCodes, 'invalid signature char: ' + sigParam[i]); |
| 87 | +#endif |
| 88 | + typeSection.push(typeCodes[sigParam[i]]); |
| 89 | + } |
| 90 | + |
| 91 | + // Return values, length + signatures |
| 92 | + // With no multi-return in MVP, either 0 (void) or 1 (anything else) |
| 93 | + if (sigRet == 'v') { |
| 94 | + typeSection.push(0x00); |
| 95 | + } else { |
| 96 | + typeSection = typeSection.concat([0x01, typeCodes[sigRet]]); |
| 97 | + } |
| 98 | + |
| 99 | + // Write the section code and overall length of the type section into the |
| 100 | + // section header |
| 101 | + typeSection = [0x01 /* Type section code */].concat( |
| 102 | + uleb128Encode(typeSection.length), |
| 103 | + typeSection |
| 104 | + ); |
| 105 | + |
| 106 | + // Rest of the module is static |
| 107 | + var bytes = new Uint8Array([ |
| 108 | + 0x00, 0x61, 0x73, 0x6d, // magic ("\0asm") |
| 109 | + 0x01, 0x00, 0x00, 0x00, // version: 1 |
| 110 | + ].concat(typeSection, [ |
| 111 | + 0x02, 0x07, // import section |
| 112 | + // (import "e" "f" (func 0 (type 0))) |
| 113 | + 0x01, 0x01, 0x65, 0x01, 0x66, 0x00, 0x00, |
| 114 | + 0x07, 0x05, // export section |
| 115 | + // (export "f" (func 0 (type 0))) |
| 116 | + 0x01, 0x01, 0x66, 0x00, 0x00, |
| 117 | + ])); |
| 118 | + |
| 119 | + // We can compile this wasm module synchronously because it is very small. |
| 120 | + // This accepts an import (at "e.f"), that it reroutes to an export (at "f") |
| 121 | + var module = new WebAssembly.Module(bytes); |
| 122 | + var instance = new WebAssembly.Instance(module, { |
| 123 | + 'e': { |
| 124 | + 'f': func |
| 125 | + } |
| 126 | + }); |
| 127 | + var wrappedFunc = instance.exports['f']; |
| 128 | + return wrappedFunc; |
| 129 | +#endif // WASM2JS |
| 130 | + }, |
| 131 | + |
| 132 | + $freeTableIndexes: [], |
| 133 | + |
| 134 | + // Weak map of functions in the table to their indexes, created on first use. |
| 135 | + $functionsInTableMap: undefined, |
| 136 | + |
| 137 | + $getEmptyTableSlot__deps: ['$freeTableIndexes'], |
| 138 | + $getEmptyTableSlot: function() { |
| 139 | + // Reuse a free index if there is one, otherwise grow. |
| 140 | + if (freeTableIndexes.length) { |
| 141 | + return freeTableIndexes.pop(); |
| 142 | + } |
| 143 | + // Grow the table |
| 144 | + try { |
| 145 | + wasmTable.grow(1); |
| 146 | + } catch (err) { |
| 147 | + if (!(err instanceof RangeError)) { |
| 148 | + throw err; |
| 149 | + } |
| 150 | + throw 'Unable to grow wasm table. Set ALLOW_TABLE_GROWTH.'; |
| 151 | + } |
| 152 | + return wasmTable.length - 1; |
| 153 | + }, |
| 154 | + |
| 155 | + $updateTableMap__deps: ['$getWasmTableEntry'], |
| 156 | + $updateTableMap: function(offset, count) { |
| 157 | + for (var i = offset; i < offset + count; i++) { |
| 158 | + var item = getWasmTableEntry(i); |
| 159 | + // Ignore null values. |
| 160 | + if (item) { |
| 161 | + functionsInTableMap.set(item, i); |
| 162 | + } |
| 163 | + } |
| 164 | + }, |
| 165 | + |
| 166 | + /** |
| 167 | + * Add a function to the table. |
| 168 | + * 'sig' parameter is required if the function being added is a JS function. |
| 169 | + */ |
| 170 | + $addFunction__docs: '/** @param {string=} sig */', |
| 171 | + $addFunction__deps: ['$convertJsFunctionToWasm', '$updateTableMap', |
| 172 | + '$functionsInTableMap', '$getEmptyTableSlot', |
| 173 | + '$getWasmTableEntry', '$setWasmTableEntry'], |
| 174 | + $addFunction: function(func, sig) { |
| 175 | + #if ASSERTIONS |
| 176 | + assert(typeof func != 'undefined'); |
| 177 | + #endif // ASSERTIONS |
| 178 | + |
| 179 | + // Check if the function is already in the table, to ensure each function |
| 180 | + // gets a unique index. First, create the map if this is the first use. |
| 181 | + if (!functionsInTableMap) { |
| 182 | + functionsInTableMap = new WeakMap(); |
| 183 | + updateTableMap(0, wasmTable.length); |
| 184 | + } |
| 185 | + if (functionsInTableMap.has(func)) { |
| 186 | + return functionsInTableMap.get(func); |
| 187 | + } |
| 188 | + |
| 189 | + // It's not in the table, add it now. |
| 190 | + |
| 191 | + #if ASSERTIONS >= 2 |
| 192 | + // Make sure functionsInTableMap is actually up to date, that is, that this |
| 193 | + // function is not actually in the wasm Table despite not being tracked in |
| 194 | + // functionsInTableMap. |
| 195 | + for (var i = 0; i < wasmTable.length; i++) { |
| 196 | + assert(getWasmTableEntry(i) != func, 'function in Table but not functionsInTableMap'); |
| 197 | + } |
| 198 | + #endif |
| 199 | + |
| 200 | + var ret = getEmptyTableSlot(); |
| 201 | + |
| 202 | + // Set the new value. |
| 203 | + try { |
| 204 | + // Attempting to call this with JS function will cause of table.set() to fail |
| 205 | + setWasmTableEntry(ret, func); |
| 206 | + } catch (err) { |
| 207 | + if (!(err instanceof TypeError)) { |
| 208 | + throw err; |
| 209 | + } |
| 210 | + #if ASSERTIONS |
| 211 | + assert(typeof sig != 'undefined', 'Missing signature argument to addFunction: ' + func); |
| 212 | + #endif |
| 213 | + var wrapped = convertJsFunctionToWasm(func, sig); |
| 214 | + setWasmTableEntry(ret, wrapped); |
| 215 | + } |
| 216 | + |
| 217 | + functionsInTableMap.set(func, ret); |
| 218 | + |
| 219 | + return ret; |
| 220 | + }, |
| 221 | + |
| 222 | + $removeFunction__deps: ['$functionsInTableMap', '$freeTableIndexes', '$getWasmTableEntry'], |
| 223 | + $removeFunction: function(index) { |
| 224 | + functionsInTableMap.delete(getWasmTableEntry(index)); |
| 225 | + freeTableIndexes.push(index); |
| 226 | + }, |
| 227 | +}); |
| 228 | + |
0 commit comments