| /** |
| * @license |
| * Copyright 2010 The Emscripten Authors |
| * SPDX-License-Identifier: MIT |
| */ |
| |
| // Various tools for parsing LLVM. Utilities of various sorts, that are |
| // specific to Emscripten (and hence not in utility.js). |
| |
| const FOUR_GB = 4 * 1024 * 1024 * 1024; |
| const FLOAT_TYPES = new Set(['float', 'double']); |
| |
| let currentlyParsedFilename = ''; |
| |
| // Does simple 'macro' substitution, using Django-like syntax, |
| // {{{ code }}} will be replaced with |eval(code)|. |
| // NOTE: Be careful with that ret check. If ret is |0|, |ret ? ret.toString() : ''| would result in ''! |
| function processMacros(text) { |
| return text.replace(/{{{([^}]|}(?!}))+}}}/g, (str) => { |
| str = str.substr(3, str.length - 6); |
| try { |
| const ret = eval(str); |
| return ret !== null ? ret.toString() : ''; |
| } catch (ex) { |
| ex.stack = 'In the following macro:\n\n' + str + '\n\n' + ex.stack; |
| throw ex; |
| } |
| }); |
| } |
| |
| // Simple #if/else/endif preprocessing for a file. Checks if the |
| // ident checked is true in our global. |
| // Also handles #include x.js (similar to C #include <file>) |
| // Param filenameHint can be passed as a description to identify the file that is being processed, used |
| // to locate errors for reporting and for html files to stop expansion between <style> and </style>. |
| function preprocess(text, filenameHint) { |
| if (EXPORT_ES6 && USE_ES6_IMPORT_META) { |
| // `eval`, Terser and Closure don't support module syntax; to allow it, |
| // we need to temporarily replace `import.meta` usages with placeholders |
| // during preprocess phase, and back after all the other ops. |
| // See also: `phase_final_emitting` in emcc.py. |
| text = text.replace(/\bimport\.meta\b/g, 'EMSCRIPTEN$IMPORT$META'); |
| } |
| |
| const IGNORE = 0; |
| const SHOW = 1; |
| // This state is entered after we have shown one of the block of an if/elif/else sequence. |
| // Once we enter this state we dont show any blocks or evaluate any |
| // conditions until the sequence ends. |
| const IGNORE_ALL = 2; |
| const showStack = []; |
| const showCurrentLine = () => showStack.every((x) => x == SHOW); |
| |
| currentlyParsedFilename = filenameHint; |
| const fileExt = (filenameHint) ? filenameHint.split('.').pop().toLowerCase() : ''; |
| const isHtml = (fileExt === 'html' || fileExt === 'htm') ? true : false; |
| let inStyle = false; |
| const lines = text.split('\n'); |
| let ret = ''; |
| let emptyLine = false; |
| |
| try { |
| for (let i = 0; i < lines.length; i++) { |
| let line = lines[i]; |
| try { |
| if (line[line.length - 1] === '\r') { |
| line = line.substr(0, line.length - 1); // Windows will have '\r' left over from splitting over '\r\n' |
| } |
| if (isHtml && line.includes('<style') && !inStyle) { |
| inStyle = true; |
| } |
| if (isHtml && line.includes('</style') && inStyle) { |
| inStyle = false; |
| } |
| |
| if (!inStyle) { |
| const trimmed = line.trim(); |
| if (trimmed.startsWith('#')) { |
| const first = trimmed.split(' ', 1)[0]; |
| if (first == '#if' || first == '#ifdef' || first == '#elif') { |
| if (first == '#ifdef') { |
| warn('warning: use of #ifdef in js library. Use #if instead.'); |
| } |
| if (first == '#elif') { |
| const curr = showStack.pop(); |
| if (curr == SHOW || curr == IGNORE_ALL) { |
| // If we showed to previous block we enter the IGNORE_ALL state |
| // and stay there until endif is seen |
| showStack.push(IGNORE_ALL); |
| continue; |
| } |
| } |
| const after = trimmed.substring(trimmed.indexOf(' ')); |
| const truthy = !!eval(after); |
| showStack.push(truthy ? SHOW : IGNORE); |
| } else if (first === '#include') { |
| if (showCurrentLine()) { |
| let filename = line.substr(line.indexOf(' ') + 1); |
| if (filename.startsWith('"')) { |
| filename = filename.substr(1, filename.length - 2); |
| } |
| const included = read(filename); |
| const result = preprocess(included, filename); |
| if (result) { |
| ret += `// include: ${filename}\n`; |
| ret += result; |
| ret += `// end include: ${filename}\n`; |
| } |
| } |
| } else if (first === '#else') { |
| assert(showStack.length > 0); |
| const curr = showStack.pop(); |
| if (curr == IGNORE) { |
| showStack.push(SHOW); |
| } else { |
| showStack.push(IGNORE); |
| } |
| } else if (first === '#endif') { |
| assert(showStack.length > 0); |
| showStack.pop(); |
| } else if (first === '#warning') { |
| if (showCurrentLine()) { |
| printErr(`${filenameHint}:${i + 1}: #warning ${trimmed.substring(trimmed.indexOf(' ')).trim()}`); |
| } |
| } else if (first === '#error') { |
| if (showCurrentLine()) { |
| error(`${filenameHint}:${i + 1}: #error ${trimmed.substring(trimmed.indexOf(' ')).trim()}`); |
| } |
| } else { |
| throw new Error(`Unknown preprocessor directive on line ${i}: ``${line}```); |
| } |
| } else { |
| if (showCurrentLine()) { |
| // Never emit more than one empty line at a time. |
| if (emptyLine && !line) { |
| continue; |
| } |
| ret += line + '\n'; |
| if (!line) { |
| emptyLine = true; |
| } else { |
| emptyLine = false; |
| } |
| } |
| } |
| } else { // !inStyle |
| if (showCurrentLine()) { |
| ret += line + '\n'; |
| } |
| } |
| } catch (e) { |
| printErr('parseTools.js preprocessor error in ' + filenameHint + ':' + (i + 1) + ': \"' + line + '\"!'); |
| throw e; |
| } |
| } |
| assert(showStack.length == 0, `preprocessing error in file ${filenameHint}, \ |
| no matching #endif found (${showStack.length$}' unmatched preprocessing directives on stack)`); |
| return ret; |
| } finally { |
| currentlyParsedFilename = null; |
| } |
| } |
| |
| // Returns true if ident is a niceIdent (see toNiceIdent). Also allow () and spaces. |
| function isNiceIdent(ident, loose) { |
| return /^\(?[$_]+[\w$_\d ]*\)?$/.test(ident); |
| } |
| |
| // Simple variables or numbers, or things already quoted, do not need to be quoted |
| function needsQuoting(ident) { |
| if (/^[-+]?[$_]?[\w$_\d]*$/.test(ident)) return false; // number or variable |
| if (ident[0] === '(' && ident[ident.length - 1] === ')' && ident.indexOf('(', 1) < 0) return false; // already fully quoted |
| return true; |
| } |
| |
| const POINTER_SIZE = MEMORY64 ? 8 : 4; |
| const POINTER_BITS = POINTER_SIZE * 8; |
| const POINTER_TYPE = 'i' + POINTER_BITS; |
| |
| const SIZE_TYPE = POINTER_TYPE; |
| |
| function isPointerType(type) { |
| return type[type.length - 1] == '*'; |
| } |
| |
| function pointerT(x) { |
| return MEMORY64 ? `BigInt(${x})` : x; |
| } |
| |
| function isIntImplemented(type) { |
| return type[0] == 'i' || isPointerType(type); |
| } |
| |
| // Note: works for iX types and structure types, not pointers (even though they are implemented as ints) |
| function getBits(type, allowPointers) { |
| if (allowPointers && isPointerType(type)) return POINTER_SIZE; |
| if (!type) return 0; |
| if (type[0] == 'i') { |
| const left = type.substr(1); |
| if (!isNumber(left)) return 0; |
| return parseInt(left); |
| } |
| return 0; |
| } |
| |
| // Given an expression like (VALUE=VALUE*2,VALUE<10?VALUE:t+1) , this will |
| // replace VALUE with value. If value is not a simple identifier of a variable, |
| // value will be replaced with tempVar. |
| function makeInlineCalculation(expression, value, tempVar) { |
| if (!isNiceIdent(value)) { |
| expression = tempVar + '=' + value + ',' + expression; |
| value = tempVar; |
| } |
| return '(' + expression.replace(/VALUE/g, value) + ')'; |
| } |
| |
| // Makes a proper runtime value for a 64-bit value from low and high i32s. low and high are assumed to be unsigned. |
| function makeI64(low, high) { |
| high = high || '0'; |
| return '[' + makeSignOp(low, 'i32', 'un', 1, 1) + ',' + makeSignOp(high, 'i32', 'un', 1, 1) + ']'; |
| } |
| |
| // XXX Make all i64 parts signed |
| |
| // Splits a number (an integer in a double, possibly > 32 bits) into an i64 value, represented by a low and high i32 pair. |
| // Will suffer from rounding. |
| function splitI64(value, floatConversion) { |
| // general idea: |
| // |
| // $1$0 = ~~$d >>> 0; |
| // $1$1 = Math.abs($d) >= 1 ? ( |
| // $d > 0 ? Math.min(Math.floor(($d)/ 4294967296.0), 4294967295.0) |
| // : Math.ceil(Math.min(-4294967296.0, $d - $1$0)/ 4294967296.0) |
| // ) : 0; |
| // |
| // We need to min on positive values here, since our input might be a double, and large values are rounded, so they can |
| // be slightly higher than expected. And if we get 4294967296, that will turn into a 0 if put into a |
| // HEAP32 or |0'd, etc. |
| // |
| // For negatives, we need to ensure a -1 if the value is overall negative, even if not significant negative component |
| |
| const lowInput = legalizedI64s ? value : 'VALUE'; |
| if (floatConversion) lowInput = asmFloatToInt(lowInput); |
| const low = lowInput + '>>>0'; |
| const high = makeInlineCalculation( |
| asmCoercion('Math.abs(VALUE)', 'double') + ' >= ' + asmEnsureFloat('1', 'double') + ' ? ' + |
| '(VALUE > ' + asmEnsureFloat('0', 'double') + ' ? ' + |
| asmCoercion('Math.min(' + asmCoercion('Math.floor((VALUE)/' + |
| asmEnsureFloat(4294967296, 'double') + ')', 'double') + ', ' + |
| asmEnsureFloat(4294967295, 'double') + ')', 'i32') + '>>>0' + |
| ' : ' + |
| asmFloatToInt(asmCoercion('Math.ceil((VALUE - +((' + asmFloatToInt('VALUE') + ')>>>0))/' + |
| asmEnsureFloat(4294967296, 'double') + ')', 'double')) + '>>>0' + |
| ')' + |
| ' : 0', |
| value, |
| 'tempDouble', |
| ); |
| if (legalizedI64s) { |
| return [low, high]; |
| } |
| return makeI64(low, high); |
| } |
| |
| // Misc |
| |
| function indentify(text, indent) { |
| // Don't try to indentify huge strings - we may run out of memory |
| if (text.length > 1024 * 1024) return text; |
| if (typeof indent == 'number') { |
| const len = indent; |
| indent = ''; |
| for (let i = 0; i < len; i++) { |
| indent += ' '; |
| } |
| } |
| return text.replace(/\n/g, '\n' + indent); |
| } |
| |
| // Correction tools |
| |
| function checkSafeHeap() { |
| return SAFE_HEAP === 1; |
| } |
| |
| function getHeapOffset(offset, type) { |
| if (!WASM_BIGINT && Runtime.getNativeFieldSize(type) > 4 && type == 'i64') { |
| // we emulate 64-bit integer values as 32 in asmjs-unknown-emscripten, but not double |
| type = 'i32'; |
| } |
| |
| const sz = Runtime.getNativeTypeSize(type); |
| const shifts = Math.log(sz) / Math.LN2; |
| return `((${offset})>>${shifts})`; |
| } |
| |
| function ensureDot(value) { |
| value = value.toString(); |
| // if already dotted, or Infinity or NaN, nothing to do here |
| // if smaller than 1 and running js opts, we always need to force a coercion |
| // (0.001 will turn into 1e-3, which has no .) |
| if ((value.includes('.') || /[IN]/.test(value))) return value; |
| const e = value.indexOf('e'); |
| if (e < 0) return value + '.0'; |
| return value.substr(0, e) + '.0' + value.substr(e); |
| } |
| |
| // ensures that a float type has either 5.5 (clearly a float) or +5 (float due to asm coercion) |
| function asmEnsureFloat(value, type) { |
| if (!isNumber(value)) return value; |
| if (type === 'float') { |
| // normally ok to just emit Math.fround(0), but if the constant is large we |
| // may need a .0 (if it can't fit in an int) |
| if (value == 0) return 'Math.fround(0)'; |
| value = ensureDot(value); |
| return 'Math.fround(' + value + ')'; |
| } |
| if (FLOAT_TYPES.has(type)) { |
| return ensureDot(value); |
| } |
| return value; |
| } |
| |
| function asmCoercion(value, type, signedness) { |
| if (type == 'void') { |
| return value; |
| } else if (FLOAT_TYPES.has(type)) { |
| if (isNumber(value)) { |
| return asmEnsureFloat(value, type); |
| } else { |
| if (signedness) { |
| if (signedness == 'u') { |
| value = '(' + value + ')>>>0'; |
| } else { |
| value = '(' + value + ')|0'; |
| } |
| } |
| if (type === 'float') { |
| return 'Math.fround(' + value + ')'; |
| } else { |
| return '(+(' + value + '))'; |
| } |
| } |
| } else { |
| if (signedness == 'u') { |
| return '((' + value + ')>>>0)'; |
| } |
| return '((' + value + ')|0)'; |
| } |
| } |
| |
| function asmFloatToInt(x) { |
| return '(~~(' + x + '))'; |
| } |
| |
| function makeGetTempDouble(i, type, forSet) { // get an aliased part of the tempDouble temporary storage |
| // Cannot use makeGetValue because it uses us |
| // this is a unique case where we *can* use HEAPF64 |
| const heap = getHeapForType(type); |
| const ptr = getFastValue('tempDoublePtr', '+', Runtime.getNativeTypeSize(type) * i); |
| let offset; |
| if (type == 'double') { |
| offset = '(' + ptr + ')>>3'; |
| } else { |
| offset = getHeapOffset(ptr, type); |
| } |
| let ret = heap + '[' + offset + ']'; |
| if (!forSet) ret = asmCoercion(ret, type); |
| return ret; |
| } |
| |
| function makeSetTempDouble(i, type, value) { |
| return makeGetTempDouble(i, type, true) + '=' + asmEnsureFloat(value, type); |
| } |
| |
| // See makeSetValue |
| function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSafe, forceAsm) { |
| assert(!forceAsm, 'forceAsm is no longer supported'); |
| |
| if (type == 'double' && (align < 8)) { |
| const setdouble1 = makeSetTempDouble(0, 'i32', makeGetValue(ptr, pos, 'i32', noNeedFirst, unsigned, ignore, align, noSafe)); |
| const setdouble2 = makeSetTempDouble(1, 'i32', makeGetValue(ptr, getFastValue(pos, '+', Runtime.getNativeTypeSize('i32')), 'i32', noNeedFirst, unsigned, ignore, align, noSafe)); |
| return '(' + setdouble1 + ',' + setdouble2 + ',' + makeGetTempDouble(0, 'double') + ')'; |
| } |
| |
| if (align) { |
| // Alignment is important here. May need to split this up |
| const bytes = Runtime.getNativeTypeSize(type); |
| if (bytes > align) { |
| let ret = '('; |
| if (isIntImplemented(type)) { |
| if (bytes == 4 && align == 2) { |
| // Special case that we can optimize |
| ret += makeGetValue(ptr, pos, 'i16', noNeedFirst, 2, ignore, 2, noSafe) + '|' + |
| '(' + makeGetValue(ptr, getFastValue(pos, '+', 2), 'i16', noNeedFirst, 2, ignore, 2, noSafe) + '<<16)'; |
| } else { // XXX we cannot truly handle > 4... (in x86) |
| ret = ''; |
| for (let i = 0; i < bytes; i++) { |
| ret += '(' + makeGetValue(ptr, getFastValue(pos, '+', i), 'i8', noNeedFirst, 1, ignore, 1, noSafe) + (i > 0 ? '<<' + (8 * i) : '') + ')'; |
| if (i < bytes - 1) ret += '|'; |
| } |
| ret = '(' + makeSignOp(ret, type, unsigned ? 'un' : 're', true); |
| } |
| } else { |
| if (type == 'float') { |
| ret += 'copyTempFloat(' + asmCoercion(getFastValue(ptr, '+', pos), 'i32') + '),' + makeGetTempDouble(0, 'float'); |
| } else { |
| ret += 'copyTempDouble(' + asmCoercion(getFastValue(ptr, '+', pos), 'i32') + '),' + makeGetTempDouble(0, 'double'); |
| } |
| } |
| ret += ')'; |
| return ret; |
| } |
| } |
| |
| const offset = calcFastOffset(ptr, pos, noNeedFirst); |
| if (SAFE_HEAP && !noSafe) { |
| if (!ignore) { |
| return asmCoercion('SAFE_HEAP_LOAD' + (FLOAT_TYPES.has(type) ? '_D' : '') + '(' + asmCoercion(offset, 'i32') + ', ' + Runtime.getNativeTypeSize(type) + ', ' + (!!unsigned + 0) + ')', type, unsigned ? 'u' : undefined); |
| } |
| } |
| |
| const slab = getHeapForType(type, unsigned); |
| let ret = slab + '[' + getHeapOffset(offset, type) + ']'; |
| if (slab.substr(slab.length - 2) == '64') { |
| ret = `Number(${ret})`; |
| } |
| if (forceAsm) { |
| ret = asmCoercion(ret, type); |
| } |
| return ret; |
| } |
| |
| /** |
| * @param {nunber} ptr The pointer. Used to find both the slab and the offset in that slab. If the pointer |
| * is just an integer, then this is almost redundant, but in general the pointer type |
| * may in the future include information about which slab as well. So, for now it is |
| * possible to put |0| here, but if a pointer is available, that is more future-proof. |
| * @param {nunber} pos The position in that slab - the offset. Added to any offset in the pointer itself. |
| * @param {number} value The value to set. |
| * @param {string} type A string defining the type. Used to find the slab (HEAPU8, HEAP16, HEAPU32, etc.). |
| * 'null' means, in the context of SAFE_HEAP, that we should accept all types; |
| * which means we should write to all slabs, ignore type differences if any on reads, etc. |
| * @param {bool} noNeedFirst Whether to ignore the offset in the pointer itself. |
| * @param {bool} ignore: legacy, ignored. |
| * @param {number} align: TODO |
| * @param {bool} noSafe: TODO |
| * @param {string} sep: TODO |
| * @param {bool} forcedAlign: legacy, ignored. |
| * @return {TODO} |
| */ |
| function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, sep = ';', forcedAlign) { |
| assert(!forcedAlign, 'forcedAlign is no longer supported'); |
| |
| if (type == 'double' && (align < 8)) { |
| return '(' + makeSetTempDouble(0, 'double', value) + ',' + |
| makeSetValue(ptr, pos, makeGetTempDouble(0, 'i32'), 'i32', noNeedFirst, ignore, align, noSafe, ',') + ',' + |
| makeSetValue(ptr, getFastValue(pos, '+', Runtime.getNativeTypeSize('i32')), makeGetTempDouble(1, 'i32'), 'i32', noNeedFirst, ignore, align, noSafe, ',') + ')'; |
| } else if (!WASM_BIGINT && type == 'i64') { |
| return '(tempI64 = [' + splitI64(value) + '],' + |
| makeSetValue(ptr, pos, 'tempI64[0]', 'i32', noNeedFirst, ignore, align, noSafe, ',') + ',' + |
| makeSetValue(ptr, getFastValue(pos, '+', Runtime.getNativeTypeSize('i32')), 'tempI64[1]', 'i32', noNeedFirst, ignore, align, noSafe, ',') + ')'; |
| } |
| |
| const bits = getBits(type); |
| const needSplitting = bits > 0 && !isPowerOfTwo(bits); // an unnatural type like i24 |
| if (align || needSplitting) { |
| // Alignment is important here, or we need to split this up for other reasons. |
| const bytes = Runtime.getNativeTypeSize(type); |
| if (bytes > align || needSplitting) { |
| let ret = ''; |
| if (isIntImplemented(type)) { |
| if (bytes == 4 && align == 2) { |
| // Special case that we can optimize |
| ret += 'tempBigInt=' + value + sep; |
| ret += makeSetValue(ptr, pos, 'tempBigInt&0xffff', 'i16', noNeedFirst, ignore, 2, noSafe) + sep; |
| ret += makeSetValue(ptr, getFastValue(pos, '+', 2), 'tempBigInt>>16', 'i16', noNeedFirst, ignore, 2, noSafe); |
| } else { |
| ret += 'tempBigInt=' + value + sep; |
| for (let i = 0; i < bytes; i++) { |
| ret += makeSetValue(ptr, getFastValue(pos, '+', i), 'tempBigInt&0xff', 'i8', noNeedFirst, ignore, 1, noSafe); |
| if (i < bytes - 1) ret += sep + 'tempBigInt = tempBigInt>>8' + sep; |
| } |
| } |
| } else { |
| ret += makeSetValue('tempDoublePtr', 0, value, type, noNeedFirst, ignore, 8, noSafe, null, true) + sep; |
| ret += makeCopyValues(getFastValue(ptr, '+', pos), 'tempDoublePtr', Runtime.getNativeTypeSize(type), type, null, align, sep); |
| } |
| return ret; |
| } |
| } |
| |
| const offset = calcFastOffset(ptr, pos, noNeedFirst); |
| if (SAFE_HEAP && !noSafe) { |
| if (!ignore) { |
| return 'SAFE_HEAP_STORE' + (FLOAT_TYPES.has(type) ? '_D' : '') + '(' + asmCoercion(offset, 'i32') + ', ' + asmCoercion(value, type) + ', ' + Runtime.getNativeTypeSize(type) + ')'; |
| } |
| } |
| |
| const slab = getHeapForType(type); |
| if (slab == 'HEAPU64' || slab == 'HEAP64') { |
| value = `BigInt(${value})`; |
| } |
| return slab + '[' + getHeapOffset(offset, type) + '] = ' + value; |
| } |
| |
| const UNROLL_LOOP_MAX = 8; |
| |
| function makeCopyValues(dest, src, num, type, modifier, align, sep = ';') { |
| function unroll(type, num, jump) { |
| jump = jump || 1; |
| const setValues = range(num).map((i) => makeSetValue(dest, i * jump, makeGetValue(src, i * jump, type), type)); |
| return setValues.join(sep); |
| } |
| // If we don't know how to handle this at compile-time, or handling it is best |
| // done in a large amount of code, call memcpy |
| if (!isNumber(num)) num = stripCorrections(num); |
| if (!isNumber(align)) align = stripCorrections(align); |
| if (!isNumber(num) || (parseInt(num) / align >= UNROLL_LOOP_MAX)) { |
| return '(_memcpy(' + dest + ', ' + src + ', ' + num + ')|0)'; |
| } |
| num = parseInt(num); |
| // remove corrections, since we will be correcting after we add anyhow, |
| dest = stripCorrections(dest); |
| src = stripCorrections(src); |
| // and in the heap assignment expression |
| const ret = []; |
| [4, 2, 1].forEach((possibleAlign) => { |
| if (num == 0) return; |
| if (align >= possibleAlign) { |
| ret.push(unroll('i' + (possibleAlign * 8), Math.floor(num / possibleAlign), possibleAlign)); |
| src = getFastValue(src, '+', Math.floor(num / possibleAlign) * possibleAlign); |
| dest = getFastValue(dest, '+', Math.floor(num / possibleAlign) * possibleAlign); |
| num %= possibleAlign; |
| } |
| }); |
| return ret.join(sep); |
| } |
| |
| function makeHEAPView(which, start, end) { |
| const size = parseInt(which.replace('U', '').replace('F', '')) / 8; |
| const mod = size == 1 ? '' : ('>>' + Math.log2(size)); |
| return `HEAP${which}.subarray((${start})${mod}, (${end})${mod})`; |
| } |
| |
| // When dynamically linking, some things like dynCalls may not exist in one module and |
| // be provided by a linked module, so they must be accessed indirectly using Module |
| function exportedAsmFunc(func) { |
| if (!MAIN_MODULE) { |
| return func; |
| } |
| return `Module['${func}']`; |
| } |
| |
| const TWO_TWENTY = Math.pow(2, 20); |
| |
| // Given two values and an operation, returns the result of that operation. |
| // Tries to do as much as possible at compile time. |
| // Leaves overflows etc. unhandled, *except* for integer multiply, in order to be efficient with Math.imul |
| function getFastValue(a, op, b, type) { |
| a = a === 'true' ? '1' : (a === 'false' ? '0' : a); |
| b = b === 'true' ? '1' : (b === 'false' ? '0' : b); |
| |
| let aNumber = null; |
| let bNumber = null; |
| if (typeof a == 'number') { |
| aNumber = a; |
| a = a.toString(); |
| } else if (isNumber(a)) aNumber = parseFloat(a); |
| if (typeof b == 'number') { |
| bNumber = b; |
| b = b.toString(); |
| } else if (isNumber(b)) bNumber = parseFloat(b); |
| |
| if (aNumber !== null && bNumber !== null) { |
| switch (op) { |
| case '+': return (aNumber + bNumber).toString(); |
| case '-': return (aNumber - bNumber).toString(); |
| case '*': return (aNumber * bNumber).toString(); |
| case '/': { |
| if (type[0] === 'i') { |
| return ((aNumber / bNumber) | 0).toString(); |
| } else { |
| return (aNumber / bNumber).toString(); |
| } |
| } |
| case '%': return (aNumber % bNumber).toString(); |
| case '|': return (aNumber | bNumber).toString(); |
| case '>>>': return (aNumber >>> bNumber).toString(); |
| case '&': return (aNumber & bNumber).toString(); |
| case 'pow': return Math.pow(aNumber, bNumber).toString(); |
| default: assert(false, 'need to implement getFastValue pn ' + op); |
| } |
| } |
| if (op === 'pow') { |
| if (a === '2' && isIntImplemented(type)) { |
| return `(1 << (${b}))`; |
| } |
| return `Math.pow(${a}, ${b})`; |
| } |
| if ((op === '+' || op === '*') && aNumber !== null) { // if one of them is a number, keep it last |
| const c = b; |
| b = a; |
| a = c; |
| const cNumber = bNumber; |
| bNumber = aNumber; |
| aNumber = cNumber; |
| } |
| if (op === '*') { |
| // We can't eliminate where a or b are 0 as that would break things for creating |
| // a negative 0. |
| if ((aNumber === 0 || bNumber === 0) && !FLOAT_TYPES.has(type)) { |
| return '0'; |
| } else if (aNumber === 1) { |
| return b; |
| } else if (bNumber === 1) { |
| return a; |
| } else if (bNumber !== null && type && isIntImplemented(type) && Runtime.getNativeTypeSize(type) <= 32) { |
| const shifts = Math.log(bNumber) / Math.LN2; |
| if (shifts % 1 === 0) { |
| return `(${a}<<${shifts})`; |
| } |
| } |
| if (!FLOAT_TYPES.has(type)) { |
| // if guaranteed small enough to not overflow into a double, do a normal multiply |
| // default is 32-bit multiply for things like getelementptr indexes |
| const bits = getBits(type) || 32; |
| // Note that we can emit simple multiple in non-asm.js mode, but asm.js |
| // will not parse "16-bit" multiple, so must do imul there |
| if ((aNumber !== null && Math.abs(a) < TWO_TWENTY) || (bNumber !== null && Math.abs(b) < TWO_TWENTY)) { |
| // keep a non-eliminatable coercion directly on this |
| return `(((${a})*(${b}))&${(Math.pow(2, bits) - 1) | 0})`; |
| } |
| return `(Math.imul(${a}, ${b})|0)`; |
| } |
| } else if (op === '/') { |
| // careful on floats, since 0*NaN is not 0 |
| if (a === '0' && !FLOAT_TYPES.has(type)) { |
| return '0'; |
| } else if (b === 1) { |
| return a; |
| } // Doing shifts for division is problematic, as getting the rounding right on negatives is tricky |
| } else if (op === '+' || op === '-') { |
| if (b[0] === '-') { |
| op = op === '+' ? '-' : '+'; |
| b = b.substr(1); |
| } |
| if (aNumber === 0) { |
| return op === '+' ? b : `(-${b})`; |
| } else if (bNumber === 0) { |
| return a; |
| } |
| } |
| return `(${a})${op}(${b})`; |
| } |
| |
| function calcFastOffset(ptr, pos, noNeedFirst) { |
| assert(!noNeedFirst); |
| if (typeof ptr == 'bigint') ptr = Number(ptr); |
| if (typeof pos == 'bigint') pos = Number(pos); |
| return getFastValue(ptr, '+', pos, 'i32'); |
| } |
| |
| function getHeapForType(type, unsigned) { |
| assert(type); |
| if (isPointerType(type)) { |
| type = POINTER_TYPE; |
| } |
| switch (type) { |
| case 'i1': |
| case 'i8': |
| return unsigned ? 'HEAPU8' : 'HEAP8'; |
| case 'i16': |
| return unsigned ? 'HEAPU16' : 'HEAP16'; |
| case 'i64': |
| if (WASM_BIGINT) { |
| return unsigned ? 'HEAPU64' : 'HEAP64'; |
| } |
| // fall through |
| case '<4 x i32>': |
| case 'i32': |
| return unsigned ? 'HEAPU32' : 'HEAP32'; |
| case 'double': |
| return 'HEAPF64'; |
| case '<4 x float>': |
| case 'float': |
| return 'HEAPF32'; |
| } |
| assert(false, 'bad heap type: ' + type); |
| } |
| |
| // Takes a pair of return values, stashes one in tempRet0 and returns the other. |
| // Should probably be renamed to `makeReturn64` but keeping this old name in |
| // case external JS library code uses this name. |
| function makeStructuralReturn(values) { |
| assert(values.length == 2); |
| return 'setTempRet0(' + values[1] + '); return ' + asmCoercion(values[0], 'i32'); |
| } |
| |
| function makeThrow(what) { |
| if (ASSERTIONS && DISABLE_EXCEPTION_CATCHING) { |
| what += ' + " - Exception catching is disabled, this exception cannot be caught. Compile with -s NO_DISABLE_EXCEPTION_CATCHING or -s EXCEPTION_CATCHING_ALLOWED=[..] to catch."'; |
| if (MAIN_MODULE) { |
| what += ' + " (note: in dynamic linking, if a side module wants exceptions, the main module must be built with that support)"'; |
| } |
| } |
| return `throw ${what};`; |
| } |
| |
| function makeSignOp(value, type, op, force, ignore) { |
| if (isPointerType(type)) type = POINTER_TYPE; |
| if (!value) return value; |
| let bits; |
| let full; |
| if (type[0] === 'i') { |
| bits = parseInt(type.substr(1)); |
| full = op + 'Sign(' + value + ', ' + bits + ', ' + Math.floor(ignore) + ')'; |
| // Always sign/unsign constants at compile time, regardless of CHECK/CORRECT |
| if (isNumber(value)) { |
| return eval(full).toString(); |
| } |
| } |
| if ((ignore) && !force) return value; |
| if (type[0] === 'i') { |
| // this is an integer, but not a number (or we would have already handled it) |
| // shortcuts |
| if (ignore) { |
| if (value === 'true') { |
| value = '1'; |
| } else if (value === 'false') { |
| value = '0'; |
| } else if (needsQuoting(value)) value = '(' + value + ')'; |
| if (bits === 32) { |
| if (op === 're') { |
| return '(' + value + '|0)'; |
| } else { |
| return '(' + value + '>>>0)'; |
| } |
| } else if (bits < 32) { |
| if (op === 're') { |
| return '((' + value + '<<' + (32 - bits) + ')>>' + (32 - bits) + ')'; |
| } else { |
| return '(' + value + '&' + (Math.pow(2, bits) - 1) + ')'; |
| } |
| } else { // bits > 32 |
| if (op === 're') { |
| return makeInlineCalculation('VALUE >= ' + Math.pow(2, bits - 1) + ' ? VALUE-' + Math.pow(2, bits) + ' : VALUE', value, 'tempBigIntS'); |
| } else { |
| return makeInlineCalculation('VALUE >= 0 ? VALUE : ' + Math.pow(2, bits) + '+VALUE', value, 'tempBigIntS'); |
| } |
| } |
| } |
| return full; |
| } |
| return value; |
| } |
| |
| // We do not legalize globals, but do legalize function lines. This will be true in the latter case |
| // eslint-disable-next-line prefer-const |
| global.legalizedI64s = true; |
| |
| function stripCorrections(param) { |
| let m; |
| while (true) { |
| if (m = /^\((.*)\)$/.exec(param)) { |
| param = m[1]; |
| continue; |
| } |
| if (m = /^\(([$_\w]+)\)&\d+$/.exec(param)) { |
| param = m[1]; |
| continue; |
| } |
| if (m = /^\(([$_\w()]+)\)\|0$/.exec(param)) { |
| param = m[1]; |
| continue; |
| } |
| if (m = /^\(([$_\w()]+)\)\>>>0$/.exec(param)) { |
| param = m[1]; |
| continue; |
| } |
| if (m = /CHECK_OVERFLOW\(([^,)]*),.*/.exec(param)) { |
| param = m[1]; |
| continue; |
| } |
| break; |
| } |
| return param; |
| } |
| |
| function charCode(char) { |
| return char.charCodeAt(0); |
| } |
| |
| function getTypeFromHeap(suffix) { |
| switch (suffix) { |
| case '8': return 'i8'; |
| case '16': return 'i16'; |
| case '32': return 'i32'; |
| case 'F32': return 'float'; |
| case 'F64': return 'double'; |
| } |
| assert(false, 'bad type suffix: ' + suffix); |
| } |
| |
| function ensureValidFFIType(type) { |
| return type === 'float' ? 'double' : type; // ffi does not tolerate float XXX |
| } |
| |
| // FFI return values must arrive as doubles, and we can force them to floats afterwards |
| function asmFFICoercion(value, type) { |
| value = asmCoercion(value, ensureValidFFIType(type)); |
| if (type === 'float') value = asmCoercion(value, 'float'); |
| return value; |
| } |
| |
| function makeDynCall(sig, funcPtr) { |
| assert(!sig.includes('j'), 'Cannot specify 64-bit signatures ("j" in signature string) with makeDynCall!'); |
| |
| const returnExpr = (sig[0] == 'v') ? '' : 'return'; |
| |
| let args = []; |
| for (let i = 1; i < sig.length; ++i) { |
| args.push(`a${i}`); |
| } |
| args = args.join(', '); |
| |
| if (funcPtr === undefined) { |
| printErr(`warning: ${currentlyParsedFilename}: \ |
| Legacy use of {{{ makeDynCall("${sig}") }}}(funcPtr, arg1, arg2, ...). \ |
| Starting from Emscripten 2.0.2 (Aug 31st 2020), syntax for makeDynCall has changed. \ |
| New syntax is {{{ makeDynCall("${sig}", "funcPtr") }}}(arg1, arg2, ...). \ |
| Please update to new syntax.`); |
| |
| if (DYNCALLS) { |
| if (!hasExportedFunction(`dynCall_${sig}`)) { |
| if (ASSERTIONS) { |
| return `(function(${args}) { throw 'Internal Error! Attempted to invoke wasm function pointer with signature "${sig}", but no such functions have gotten exported!'; })`; |
| } else { |
| return `(function(${args}) { /* a dynamic function call to signature ${sig}, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */ })`; |
| } |
| } |
| return `(function(cb, ${args}) { ${returnExpr} getDynCaller("${sig}", cb)(${args}) })`; |
| } else { |
| return `(function(cb, ${args}) { ${returnExpr} getWasmTableEntry(cb)(${args}) })`; |
| } |
| } |
| |
| if (DYNCALLS) { |
| if (!hasExportedFunction(`dynCall_${sig}`)) { |
| if (ASSERTIONS) { |
| return `(function(${args}) { throw 'Internal Error! Attempted to invoke wasm function pointer with signature "${sig}", but no such functions have gotten exported!'; })`; |
| } else { |
| return `(function(${args}) { /* a dynamic function call to signature ${sig}, but there are no exported function pointers with that signature, so this path should never be taken. Build with ASSERTIONS enabled to validate. */ })`; |
| } |
| } |
| |
| const dyncall = exportedAsmFunc(`dynCall_${sig}`); |
| if (sig.length > 1) { |
| return `(function(${args}) { ${returnExpr} ${dyncall}.apply(null, [${funcPtr}, ${args}]); })`; |
| } else { |
| return `(function() { ${returnExpr} ${dyncall}.call(null, ${funcPtr}); })`; |
| } |
| } else { |
| return `getWasmTableEntry(${funcPtr})`; |
| } |
| } |
| |
| function heapAndOffset(heap, ptr) { // given HEAP8, ptr , we return splitChunk, relptr |
| return heap + ',' + ptr; |
| } |
| |
| function makeEval(code) { |
| if (DYNAMIC_EXECUTION == 0) { |
| // Treat eval as error. |
| return "abort('DYNAMIC_EXECUTION=0 was set, cannot eval');"; |
| } |
| let ret = ''; |
| if (DYNAMIC_EXECUTION == 2) { |
| // Warn on evals, but proceed. |
| ret += "err('Warning: DYNAMIC_EXECUTION=2 was set, but calling eval in the following location:');\n"; |
| ret += 'err(stackTrace());\n'; |
| } |
| ret += code; |
| return ret; |
| } |
| |
| global.ATINITS = []; |
| |
| function addAtInit(code) { |
| ATINITS.push(code); |
| } |
| |
| // TODO(sbc): There are no more uses to ATMAINS or addAtMain in emscripten. |
| // We should look into removing these. |
| global.ATMAINS = []; |
| |
| function addAtMain(code) { |
| assert(HAS_MAIN, 'addAtMain called but program has no main function'); |
| ATMAINS.push(code); |
| } |
| |
| global.ATEXITS = []; |
| |
| function addAtExit(code) { |
| if (EXIT_RUNTIME) { |
| ATEXITS.push(code); |
| } |
| } |
| |
| function makeRetainedCompilerSettings() { |
| const ignore = new Set(['STRUCT_INFO']); |
| if (STRICT) { |
| for (const setting of LEGACY_SETTINGS) { |
| ignore.add(setting); |
| } |
| } |
| |
| const ret = {}; |
| for (const x in global) { |
| if (!ignore.has(x) && x[0] !== '_' && x == x.toUpperCase()) { |
| const value = global[x]; |
| if (typeof value == 'number' || typeof value == 'boolean' || typeof value == 'string' || Array.isArray(x)) { |
| ret[x] = value; |
| } |
| } |
| } |
| return ret; |
| } |
| |
| // In wasm, the heap size must be a multiple of 64KiB. |
| const WASM_PAGE_SIZE = 65536; |
| |
| // Receives a function as text, and a function that constructs a modified |
| // function, to which we pass the parsed-out name, arguments, body, and possible |
| // "async" prefix of the input function. Returns the output of that function. |
| function modifyFunction(text, func) { |
| // Match a function with a name. |
| let match = text.match(/^\s*(async\s+)?function\s+([^(]*)?\s*\(([^)]*)\)/); |
| let async_; |
| let names; |
| let args; |
| let rest; |
| if (match) { |
| async_ = match[1] || ''; |
| name = match[2]; |
| args = match[3]; |
| rest = text.substr(match[0].length); |
| } else { |
| // Match a function without a name (we could probably use a single regex |
| // for both, but it would be more complex). |
| match = text.match(/^\s*(async\s+)?function\(([^)]*)\)/); |
| assert(match, 'could not match function ' + text + '.'); |
| name = ''; |
| async_ = match[1] || ''; |
| args = match[2]; |
| rest = text.substr(match[0].length); |
| } |
| const bodyStart = rest.indexOf('{'); |
| assert(bodyStart >= 0); |
| const bodyEnd = rest.lastIndexOf('}'); |
| assert(bodyEnd > 0); |
| return func(name, args, rest.substring(bodyStart + 1, bodyEnd), async_); |
| } |
| |
| function runOnMainThread(text) { |
| if (WASM_WORKERS && USE_PTHREADS) { |
| return 'if (!ENVIRONMENT_IS_WASM_WORKER && !ENVIRONMENT_IS_PTHREAD) { ' + text + ' }'; |
| } else if (WASM_WORKERS) { |
| return 'if (!ENVIRONMENT_IS_WASM_WORKER) { ' + text + ' }'; |
| } else if (USE_PTHREADS) { |
| return 'if (!ENVIRONMENT_IS_PTHREAD) { ' + text + ' }'; |
| } else { |
| return text; |
| } |
| } |
| |
| function expectToReceiveOnModule(name) { |
| return INCOMING_MODULE_JS_API.has(name); |
| } |
| |
| function makeRemovedModuleAPIAssert(moduleName, localName) { |
| if (!ASSERTIONS) return ''; |
| if (!localName) localName = moduleName; |
| return `legacyModuleProp('${moduleName}', '${localName}');`; |
| } |
| |
| function checkReceiving(name) { |
| // ALL_INCOMING_MODULE_JS_API contains all valid incoming module API symbols |
| // so calling makeModuleReceive* with a symbol not in this list is an error |
| assert(ALL_INCOMING_MODULE_JS_API.includes(name)); |
| } |
| |
| // Make code to receive a value on the incoming Module object. |
| function makeModuleReceive(localName, moduleName) { |
| if (!moduleName) moduleName = localName; |
| checkReceiving(moduleName); |
| let ret = ''; |
| if (expectToReceiveOnModule(moduleName)) { |
| // Usually the local we use is the same as the Module property name, |
| // but sometimes they must differ. |
| ret = `\nif (Module['${moduleName}']) ${localName} = Module['${moduleName}'];`; |
| } |
| ret += makeRemovedModuleAPIAssert(moduleName, localName); |
| return ret; |
| } |
| |
| function makeModuleReceiveExpr(name, defaultValue) { |
| checkReceiving(name); |
| if (expectToReceiveOnModule(name)) { |
| return `Module['${name}'] || ${defaultValue}`; |
| } else { |
| return `${defaultValue}`; |
| } |
| } |
| |
| function makeModuleReceiveWithVar(localName, moduleName, defaultValue, noAssert) { |
| if (!moduleName) moduleName = localName; |
| checkReceiving(moduleName); |
| let ret = 'var ' + localName; |
| if (!expectToReceiveOnModule(moduleName)) { |
| if (defaultValue) { |
| ret += ' = ' + defaultValue; |
| } |
| ret += ';'; |
| } else { |
| if (defaultValue) { |
| ret += ` = Module['${moduleName}'] || ${defaultValue};`; |
| } else { |
| ret += ';' + makeModuleReceive(localName, moduleName); |
| return ret; |
| } |
| } |
| if (!noAssert) { |
| ret += makeRemovedModuleAPIAssert(moduleName, localName); |
| } |
| return ret; |
| } |
| |
| function makeRemovedFSAssert(fsName) { |
| assert(ASSERTIONS); |
| const lower = fsName.toLowerCase(); |
| if (JS_LIBRARIES.includes('library_' + lower + '.js')) return ''; |
| return `var ${fsName} = '${fsName} is no longer included by default; build with -l${lower}.js';`; |
| } |
| |
| function makeRemovedRuntimeFunction(name) { |
| assert(ASSERTIONS); |
| if (libraryFunctions.includes(name)) { |
| return ''; |
| } |
| return `function ${name}() { abort('\`${name}\` is now a library function and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line'); }`; |
| } |
| |
| // Given an array of elements [elem1,elem2,elem3], returns a string "['elem1','elem2','elem3']" |
| function buildStringArray(array) { |
| if (array.length > 0) { |
| return "['" + array.join("','") + "']"; |
| } else { |
| return '[]'; |
| } |
| } |
| |
| // Generates access to a JS imports scope variable in pthreads worker.js. In MODULARIZE mode these flow into the imports object for the Module. |
| // In non-MODULARIZE mode, we can directly access the variables in global scope. |
| function makeAsmImportsAccessInPthread(variable) { |
| if (!MINIMAL_RUNTIME) { |
| // Regular runtime uses the name "Module" for both imports and exports. |
| return `Module['${variable}']`; |
| } |
| if (MODULARIZE) { |
| // MINIMAL_RUNTIME uses 'imports' as the name for the imports object in MODULARIZE builds. |
| return `imports['${variable}']`; |
| } |
| // In non-MODULARIZE builds, can access the imports from global scope. |
| return `self.${variable}`; |
| } |
| |
| function _asmjsDemangle(symbol) { |
| if (symbol in WASM_SYSTEM_EXPORTS) { |
| return symbol; |
| } |
| if (symbol.startsWith('dynCall_')) { |
| return symbol; |
| } |
| // Strip leading "_" |
| assert(symbol.startsWith('_')); |
| return symbol.substr(1); |
| } |
| |
| function hasExportedFunction(func) { |
| return WASM_EXPORTS.has(_asmjsDemangle(func)); |
| } |
| |
| // JS API I64 param handling: if we have BigInt support, the ABI is simple, |
| // it is a BigInt. Otherwise, we legalize into pairs of i32s. |
| function defineI64Param(name) { |
| if (WASM_BIGINT) { |
| return `/** @type {!BigInt} */ ${name}_bigint`; |
| } |
| return `${name}_low, ${name}_high`; |
| } |
| |
| function receiveI64ParamAsI32s(name) { |
| if (WASM_BIGINT) { |
| // TODO: use Xn notation when JS parsers support it (as of April 6 2020, |
| // * closure compiler is missing support |
| // https://github.com/google/closure-compiler/issues/3167 |
| // * acorn needs to be upgraded, and to set ecmascript version >= 11 |
| // * terser needs to be upgraded |
| return `var ${name}_low = Number(${name}_bigint & BigInt(0xffffffff)) | 0, ${name}_high = Number(${name}_bigint >> BigInt(32)) | 0;`; |
| } |
| return ''; |
| } |
| |
| // TODO: use this in library_wasi.js and other places. but we need to add an |
| // error-handling hook here. |
| function receiveI64ParamAsDouble(name) { |
| if (WASM_BIGINT) { |
| // Just convert the bigint into a double. |
| return `var ${name} = Number(${name}_bigint);`; |
| } |
| |
| // Combine the i32 params. Use an unsigned operator on low and shift high by |
| // 32 bits. |
| return `var ${name} = ${name}_high * 0x100000000 + (${name}_low >>> 0);`; |
| } |
| |
| function sendI64Argument(low, high) { |
| if (WASM_BIGINT) { |
| return 'BigInt(low) | (BigInt(high) << BigInt(32))'; |
| } |
| return low + ', ' + high; |
| } |
| |
| // Any function called from wasm64 may have bigint args, this function takes |
| // a list of variable names to convert to number. |
| function from64(x) { |
| if (!MEMORY64) { |
| return ''; |
| } |
| if (Array.isArray(x)) { |
| let ret = ''; |
| for (e of x) ret += from64(e); |
| return ret; |
| } else { |
| return `${x} = Number(${x});`; |
| } |
| } |
| |
| function to64(x) { |
| if (!MEMORY64) return x; |
| return `BigInt(${x})`; |
| } |
| |
| // Add assertions to catch common errors when using the Promise object we |
| // create on Module.ready() and return from MODULARIZE Module() invocations. |
| function addReadyPromiseAssertions(promise) { |
| // Warn on someone doing |
| // |
| // var instance = Module(); |
| // ... |
| // instance._main(); |
| const properties = Array.from(EXPORTED_FUNCTIONS.values()); |
| // Also warn on onRuntimeInitialized which might be a common pattern with |
| // older MODULARIZE-using codebases. |
| properties.push('onRuntimeInitialized'); |
| return properties.map((property) => { |
| const warningEnding = `${property} on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js`; |
| return ` |
| if (!Object.getOwnPropertyDescriptor(${promise}, '${property}')) { |
| Object.defineProperty(${promise}, '${property}', { configurable: true, get: function() { abort('You are getting ${warningEnding}') } }); |
| Object.defineProperty(${promise}, '${property}', { configurable: true, set: function() { abort('You are setting ${warningEnding}') } }); |
| } |
| `; |
| }).join('\n'); |
| } |
| |
| function makeMalloc(source, param) { |
| if (hasExportedFunction('_malloc')) { |
| return `_malloc(${param})`; |
| } |
| // It should be impossible to call some functions without malloc being |
| // included, unless we have a deps_info.json bug. To let closure not error |
| // on `_malloc` not being present, they don't call malloc and instead abort |
| // with an error at runtime. |
| // TODO: A more comprehensive deps system could catch this at compile time. |
| if (!ASSERTIONS) { |
| return 'abort();'; |
| } |
| return `abort('malloc was not included, but is needed in ${source}. Adding "_malloc" to EXPORTED_FUNCTIONS should fix that. This may be a bug in the compiler, please file an issue.');`; |
| } |
| |
| // Adds a call to runtimeKeepalivePush, if needed by the current build |
| // configuration. |
| // We skip this completely in MINIMAL_RUNTIME and also in builds that |
| // don't ever need to exit the runtime. |
| function runtimeKeepalivePush() { |
| if (MINIMAL_RUNTIME || (EXIT_RUNTIME == 0 && USE_PTHREADS == 0)) return ''; |
| return 'runtimeKeepalivePush();'; |
| } |
| |
| // Adds a call to runtimeKeepalivePush, if needed by the current build |
| // configuration. |
| // We skip this completely in MINIMAL_RUNTIME and also in builds that |
| // don't ever need to exit the runtime. |
| function runtimeKeepalivePop() { |
| if (MINIMAL_RUNTIME || (EXIT_RUNTIME == 0 && USE_PTHREADS == 0)) return ''; |
| return 'runtimeKeepalivePop();'; |
| } |
| |
| // Some web functions like TextDecoder.decode() may not work with a view of a |
| // SharedArrayBuffer, see https://github.com/whatwg/encoding/issues/172 |
| // To avoid that, this function allows obtaining an unshared copy of an |
| // ArrayBuffer. |
| function getUnsharedTextDecoderView(heap, start, end) { |
| const shared = `${heap}.slice(${start}, ${end})`; |
| const unshared = `${heap}.subarray(${start}, ${end})`; |
| |
| // No need to worry about this in non-shared memory builds |
| if (!SHARED_MEMORY) return unshared; |
| |
| // If asked to get an unshared view to what we know will be a shared view, or if in -Oz, |
| // then unconditionally do a .slice() for smallest code size. |
| if (SHRINK_LEVEL == 2 || heap == 'HEAPU8') return shared; |
| |
| // Otherwise, generate a runtime type check: must do a .slice() if looking at a SAB, |
| // or can use .subarray() otherwise. |
| return `${heap}.buffer instanceof SharedArrayBuffer ? ${shared} : ${unshared}`; |
| } |