diff options
author | Alon Zakai <alonzakai@gmail.com> | 2014-01-16 11:25:22 -0800 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2014-01-16 11:27:39 -0800 |
commit | a3108271317e8242830113a2c87ceed1ec4ac02d (patch) | |
tree | 08ba8e81eb189666c727374275e3abd01b069aec | |
parent | e14e6ea75cc7afb68e8be6c6994a1602250611ee (diff) |
enable a form of safe heap in asm, using js optimizer pass to ensure full coverage and support for fastcomp
-rwxr-xr-x | emcc | 6 | ||||
-rwxr-xr-x | emscripten.py | 4 | ||||
-rw-r--r-- | src/parseTools.js | 34 | ||||
-rw-r--r-- | src/preamble.js | 33 | ||||
-rw-r--r-- | src/runtime.js | 2 | ||||
-rw-r--r-- | tests/test_core.py | 2 | ||||
-rw-r--r-- | tools/js-optimizer.js | 102 |
7 files changed, 165 insertions, 18 deletions
@@ -1196,7 +1196,6 @@ try: shared.Settings.ASM_JS = 1 assert shared.Settings.ALLOW_MEMORY_GROWTH == 0, 'memory growth not supported in fastcomp yet' assert shared.Settings.UNALIGNED_MEMORY == 0, 'forced unaligned memory not supported in fastcomp' - assert shared.Settings.SAFE_HEAP == 0, 'safe heap not supported in fastcomp yet' assert shared.Settings.CHECK_HEAP_ALIGN == 0, 'check heap align not supported in fastcomp yet' assert shared.Settings.SAFE_DYNCALLS == 0, 'safe dyncalls not supported in fastcomp' assert shared.Settings.RESERVED_FUNCTION_POINTERS == 0, 'reserved function pointers not supported in fastcomp' @@ -1230,6 +1229,9 @@ try: shared.Settings.CORRECT_OVERFLOWS = 1 assert not shared.Settings.PGO, 'cannot run PGO in ASM_JS mode' + if shared.Settings.SAFE_HEAP and not js_opts: + logging.warning('asm.js+SAFE_HEAP requires js opts to be run (-O1 or above by default)') + if shared.Settings.CORRECT_SIGNS >= 2 or shared.Settings.CORRECT_OVERFLOWS >= 2 or shared.Settings.CORRECT_ROUNDINGS >= 2: debug_level = 4 # must keep debug info to do line-by-line operations @@ -1991,6 +1993,8 @@ try: if DEBUG: save_intermediate('closure') if js_opts: + if shared.Settings.ASM_JS and shared.Settings.SAFE_HEAP: js_optimizer_queue += ['safeHeap'] + if shared.Settings.OUTLINING_LIMIT > 0 and shared.Settings.ASM_JS: js_optimizer_queue += ['outline'] js_optimizer_extra_info['sizeToOutline'] = shared.Settings.OUTLINING_LIMIT diff --git a/emscripten.py b/emscripten.py index befad8d5..aeace63d 100755 --- a/emscripten.py +++ b/emscripten.py @@ -455,7 +455,7 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, basic_funcs = ['abort', 'assert', 'asmPrintInt', 'asmPrintFloat'] + [m.replace('.', '_') for m in math_envs] if settings['RESERVED_FUNCTION_POINTERS'] > 0: basic_funcs.append('jsCall') - if settings['SAFE_HEAP']: basic_funcs += ['SAFE_HEAP_LOAD', 'SAFE_HEAP_STORE', 'SAFE_HEAP_CLEAR'] + if settings['SAFE_HEAP']: basic_funcs += ['SAFE_HEAP_LOAD', 'SAFE_HEAP_STORE'] if settings['CHECK_HEAP_ALIGN']: basic_funcs += ['CHECK_ALIGN_2', 'CHECK_ALIGN_4', 'CHECK_ALIGN_8'] if settings['ASSERTIONS']: basic_funcs += ['nullFunc'] @@ -956,7 +956,7 @@ def emscript_fast(infile, settings, outfile, libraries=[], compiler_engine=None, basic_funcs = ['abort', 'assert', 'asmPrintInt', 'asmPrintFloat'] + [m.replace('.', '_') for m in math_envs] if settings['RESERVED_FUNCTION_POINTERS'] > 0: basic_funcs.append('jsCall') - if settings['SAFE_HEAP']: basic_funcs += ['SAFE_HEAP_LOAD', 'SAFE_HEAP_STORE', 'SAFE_HEAP_CLEAR'] + if settings['SAFE_HEAP']: basic_funcs += ['SAFE_HEAP_LOAD', 'SAFE_HEAP_STORE'] if settings['CHECK_HEAP_ALIGN']: basic_funcs += ['CHECK_ALIGN_2', 'CHECK_ALIGN_4', 'CHECK_ALIGN_8'] if settings['ASSERTIONS']: basic_funcs += ['nullFunc'] diff --git a/src/parseTools.js b/src/parseTools.js index be0cbcab..036ccfc1 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1327,18 +1327,22 @@ function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSa var printType = type; if (printType !== 'null' && printType[0] !== '#') printType = '"' + safeQuote(printType) + '"'; if (printType[0] === '#') printType = printType.substr(1); - return asmCoercion('SAFE_HEAP_LOAD(' + asmCoercion(offset, 'i32') + ', ' + (ASM_JS ? 0 : printType) + ', ' + (!!unsigned+0) + ', ' + ((!checkSafeHeap() || ignore)|0) + ')', type); - } else { - var ret = makeGetSlabs(ptr, type, false, unsigned)[0] + '[' + getHeapOffset(offset, type, forceAsm) + ']'; - if (ASM_JS && (phase == 'funcs' || forceAsm)) { - ret = asmCoercion(ret, type); - } - if (ASM_HEAP_LOG) { - ret = makeInlineCalculation('(asmPrint' + (type in Runtime.FLOAT_TYPES ? 'Float' : 'Int') + '(' + (asmPrintCounter++) + ',' + asmCoercion('VALUE', type) + '), VALUE)', ret, - 'temp' + (type in Runtime.FLOAT_TYPES ? 'Double' : 'Int')); + if (ASM_JS) { + if (!ignore) return asmCoercion('SAFE_HEAP_LOAD(' + asmCoercion(offset, 'i32') + ', ' + Runtime.getNativeTypeSize(type) + ', ' + ((type in Runtime.FLOAT_TYPES)|0) + ')', type); + // else fall through + } else { + return asmCoercion('SAFE_HEAP_LOAD(' + offset + ', ' + (ASM_JS ? 0 : printType) + ', ' + (!!unsigned+0) + ', ' + ((!checkSafeHeap() || ignore)|0) + ')', type); } - return ret; } + var ret = makeGetSlabs(ptr, type, false, unsigned)[0] + '[' + getHeapOffset(offset, type, forceAsm) + ']'; + if (ASM_JS && (phase == 'funcs' || forceAsm)) { + ret = asmCoercion(ret, type); + } + if (ASM_HEAP_LOG) { + ret = makeInlineCalculation('(asmPrint' + (type in Runtime.FLOAT_TYPES ? 'Float' : 'Int') + '(' + (asmPrintCounter++) + ',' + asmCoercion('VALUE', type) + '), VALUE)', ret, + 'temp' + (type in Runtime.FLOAT_TYPES ? 'Double' : 'Int')); + } + return ret; } function makeGetValueAsm(ptr, pos, type, unsigned) { @@ -1435,10 +1439,14 @@ function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, var printType = type; if (printType !== 'null' && printType[0] !== '#') printType = '"' + safeQuote(printType) + '"'; if (printType[0] === '#') printType = printType.substr(1); - return 'SAFE_HEAP_STORE(' + asmCoercion(offset, 'i32') + ', ' + asmCoercion(value, type) + ', ' + (ASM_JS ? 0 : printType) + ', ' + ((!checkSafeHeap() || ignore)|0) + ')'; - } else { - return makeGetSlabs(ptr, type, true).map(function(slab) { return slab + '[' + getHeapOffset(offset, type, forceAsm) + ']=' + value }).join(sep); + if (ASM_JS) { + if (!ignore) return asmCoercion('SAFE_HEAP_STORE(' + asmCoercion(offset, 'i32') + ', ' + asmCoercion(value, type) + ', ' + Runtime.getNativeTypeSize(type) + ', ' + ((type in Runtime.FLOAT_TYPES)|0) + ')', type); + // else fall through + } else { + return 'SAFE_HEAP_STORE(' + offset + ', ' + value + ', ' + (ASM_JS ? 0 : printType) + ', ' + ((!checkSafeHeap() || ignore)|0) + ')'; + } } + return makeGetSlabs(ptr, type, true).map(function(slab) { return slab + '[' + getHeapOffset(offset, type, forceAsm) + ']=' + value }).join(sep); } function makeSetValueAsm(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, sep, forcedAlign) { diff --git a/src/preamble.js b/src/preamble.js index ac6ee7b3..d70ef4b1 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -21,6 +21,7 @@ Module.print = Module.printErr = function(){}; #endif #if SAFE_HEAP +#if ASM_JS == 0 //======================================== // Debugging tools - Heap //======================================== @@ -166,6 +167,38 @@ function SAFE_HEAP_FILL_HISTORY(from, to, type) { } //========================================== +#else +// ASM_JS safe heap + +function getSafeHeapType(bytes, isFloat) { + switch (bytes) { + case 1: return 'i8'; + case 2: return 'i16'; + case 4: return isFloat ? 'float' : 'i32'; + case 8: return 'double'; + default: assert(0); + } +} + +function SAFE_HEAP_STORE(dest, value, bytes, isFloat) { +#if SAFE_HEAP_LOG + Module.print('SAFE_HEAP store: ' + [dest, value, bytes, isFloat]); +#endif + assert(dest > 0, 'segmentation fault'); + assert(dest % bytes === 0); + setValue(dest, value, getSafeHeapType(bytes, isFloat), 1); +} + +function SAFE_HEAP_LOAD(dest, bytes, isFloat) { +#if SAFE_HEAP_LOG + Module.print('SAFE_HEAP load: ' + [dest, bytes, isFloat]); +#endif + assert(dest > 0, 'segmentation fault'); + assert(dest % bytes === 0); + return getValue(dest, getSafeHeapType(bytes, isFloat), 1); +} + +#endif #endif #if CHECK_HEAP_ALIGN diff --git a/src/runtime.js b/src/runtime.js index cd3afb4b..1fc9e026 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -49,7 +49,7 @@ var RuntimeGenerator = { stackExit: function(initial, force) { if (initial === 0 && SKIP_STACK_IN_SMALL && !force) return ''; var ret = ''; - if (SAFE_HEAP) { + if (SAFE_HEAP && !ASM_JS) { ret += 'var i = sp; while ((i|0) < (STACKTOP|0)) { SAFE_HEAP_CLEAR(i|0); i = (i+1)|0 }'; } return ret += 'STACKTOP=sp'; diff --git a/tests/test_core.py b/tests/test_core.py index 458e04fb..ce778209 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1486,7 +1486,7 @@ class T(RunnerCore): # Short name, to make it more fun to use manually on the co def test_segfault(self): if self.emcc_args is None: return self.skip('SAFE_HEAP without ta2 means we check types too, which hide segfaults') - if Settings.ASM_JS: return self.skip('asm does not support safe heap') + if os.environ.get('EMCC_FAST_COMPILER') == '1' and '-O2' not in self.emcc_args: return self.skip('todo in non-jsopts-enabled fastcomp') Settings.SAFE_HEAP = 1 diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 9391ff8e..fa59dbec 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -3888,6 +3888,104 @@ function outline(ast) { }); } +function safeHeap(ast) { + function fixPtr(ptr, heap) { + switch (heap) { + case 'HEAP8': case 'HEAPU8': break; + case 'HEAP16': case 'HEAPU16': { + if (ptr[0] === 'binary') { + assert(ptr[1] === '>>' && ptr[3][0] === 'num' && ptr[3][1] === 1); + ptr = ptr[2]; // skip the shift + } else { + ptr = ['binary', '*', ptr, ['num', 2]]; // was unshifted, convert to absolute address + } + break; + } + case 'HEAP32': case 'HEAPU32': { + if (ptr[0] === 'binary') { + assert(ptr[1] === '>>' && ptr[3][0] === 'num' && ptr[3][1] === 2); + ptr = ptr[2]; // skip the shift + } else { + ptr = ['binary', '*', ptr, ['num', 4]]; // was unshifted, convert to absolute address + } + break; + } + case 'HEAPF32': { + if (ptr[0] === 'binary') { + assert(ptr[1] === '>>' && ptr[3][0] === 'num' && ptr[3][1] === 2); + ptr = ptr[2]; // skip the shift + } else { + ptr = ['binary', '*', ptr, ['num', 4]]; // was unshifted, convert to absolute address + } + break; + } + case 'HEAPF64': { + if (ptr[0] === 'binary') { + assert(ptr[1] === '>>' && ptr[3][0] === 'num' && ptr[3][1] === 3); + ptr = ptr[2]; // skip the shift + } else { + ptr = ['binary', '*', ptr, ['num', 8]]; // was unshifted, convert to absolute address + } + break; + } + default: throw 'bad heap ' + heap; + } + ptr = ['binary', '|', ptr, ['num', 0]]; + return ptr; + } + traverseGenerated(ast, function(node, type) { + if (type === 'assign') { + if (node[1] === true && node[2][0] === 'sub') { + var heap = node[2][1][1]; + var ptr = fixPtr(node[2][2], heap); + var value = node[3]; + // SAFE_HEAP_STORE(ptr, value, bytes, isFloat) + switch (heap) { + case 'HEAP8': case 'HEAPU8': { + return ['call', ['name', 'SAFE_HEAP_STORE'], [ptr, makeAsmCoercion(value, ASM_INT), ['num', 1], ['num', '0']]]; + } + case 'HEAP16': case 'HEAPU16': { + return ['call', ['name', 'SAFE_HEAP_STORE'], [ptr, makeAsmCoercion(value, ASM_INT), ['num', 2], ['num', '0']]]; + } + case 'HEAP32': case 'HEAPU32': { + return ['call', ['name', 'SAFE_HEAP_STORE'], [ptr, makeAsmCoercion(value, ASM_INT), ['num', 4], ['num', '0']]]; + } + case 'HEAPF32': { + return ['call', ['name', 'SAFE_HEAP_STORE'], [ptr, makeAsmCoercion(value, ASM_FLOAT), ['num', 4], ['num', '1']]]; + } + case 'HEAPF64': { + return ['call', ['name', 'SAFE_HEAP_STORE'], [ptr, makeAsmCoercion(value, ASM_DOUBLE), ['num', 8], ['num', '1']]]; + } + default: throw 'bad heap ' + heap; + } + } + } else if (type === 'sub') { + var heap = node[1][1]; + if (heap[0] !== 'H') return; + var ptr = fixPtr(node[2], heap); + // SAFE_HEAP_LOAD(ptr, bytes, isFloat) + switch (heap) { + case 'HEAP8': case 'HEAPU8': { + return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 1], ['num', '0']]], ASM_INT); + } + case 'HEAP16': case 'HEAPU16': { + return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 2], ['num', '0']]], ASM_INT); + } + case 'HEAP32': case 'HEAPU32': { + return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 4], ['num', '0']]], ASM_INT); + } + case 'HEAPF32': { + return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 4], ['num', '1']]], ASM_FLOAT); + } + case 'HEAPF64': { + return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 8], ['num', '1']]], ASM_DOUBLE); + } + default: throw 'bad heap ' + heap; + } + } + }); +} + // Last pass utilities // Change +5 to DOT$ZERO(5). We then textually change 5 to 5.0 (uglify's ast cannot differentiate between 5 and 5.0 directly) @@ -3978,6 +4076,7 @@ function asmLastOpts(ast) { var minifyWhitespace = false, printMetadata = true, asm = false, last = false; var passes = { + // passes dumpAst: dumpAst, dumpSrc: dumpSrc, unGlobalize: unGlobalize, @@ -3996,6 +4095,9 @@ var passes = { minifyLocals: minifyLocals, relocate: relocate, outline: outline, + safeHeap: safeHeap, + + // flags minifyWhitespace: function() { minifyWhitespace = true }, noPrintMetadata: function() { printMetadata = false }, asm: function() { asm = true }, |