diff options
-rwxr-xr-x | emcc | 8 | ||||
-rwxr-xr-x | emscripten.py | 36 | ||||
-rw-r--r-- | src/jsifier.js | 1 | ||||
-rw-r--r-- | src/library.js | 3 | ||||
-rw-r--r-- | src/library_gl.js | 11 | ||||
-rw-r--r-- | src/modules.js | 41 | ||||
-rw-r--r-- | src/settings.js | 4 | ||||
-rwxr-xr-x | tests/runner.py | 50 | ||||
-rw-r--r-- | tools/shared.py | 1 |
9 files changed, 121 insertions, 34 deletions
@@ -163,7 +163,10 @@ Options that are modified or new in %s include: EMCC_OPTIMIZE_NORMALLY=1 (not recommended unless you know what you are doing!) -O2 As -O1, plus the relooper (loop recreation), - plus LLVM -O2 optimizations + LLVM -O2 optimizations, and + + -s ALIASING_FUNCTION_POINTERS=1 + -O3 As -O2, plus dangerous optimizations that may break the generated code! This adds @@ -1032,6 +1035,9 @@ try: shared.Settings.CORRECT_OVERFLOWS = 1 assert not shared.Settings.PGO, 'cannot run PGO in ASM_JS mode' + if shared.Settings.ASSERTIONS and shared.Settings.ALIASING_FUNCTION_POINTERS: + logging.warning('ALIASING_FUNCTION_POINTERS is on, function pointer comparisons may be invalid across types') + if shared.Settings.CORRECT_SIGNS >= 2 or shared.Settings.CORRECT_OVERFLOWS >= 2 or shared.Settings.CORRECT_ROUNDINGS >= 2: keep_llvm_debug = True # must keep debug info to do line-by-line operations diff --git a/emscripten.py b/emscripten.py index 35edc515..629bbe5f 100755 --- a/emscripten.py +++ b/emscripten.py @@ -288,6 +288,8 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, exported_implemented_functions.add(key) for key, value in curr_forwarded_json['Functions']['unimplementedFunctions'].iteritems(): forwarded_json['Functions']['unimplementedFunctions'][key] = value + for key, value in curr_forwarded_json['Functions']['neededTables'].iteritems(): + forwarded_json['Functions']['neededTables'][key] = value if settings.get('ASM_JS'): parts = pre.split('// ASM_LIBRARY FUNCTIONS\n') @@ -302,18 +304,24 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, # calculations on merged forwarded data forwarded_json['Functions']['indexedFunctions'] = {} - i = 2 + i = 2 # universal counter if settings['ASM_JS']: i += 2*settings['RESERVED_FUNCTION_POINTERS'] + table_counters = {} # table-specific counters + alias = settings['ASM_JS'] and settings['ALIASING_FUNCTION_POINTERS'] + sig = None for indexed in indexed_functions: - #print >> sys.stderr, 'function indexing', indexed, i - forwarded_json['Functions']['indexedFunctions'][indexed] = i # make sure not to modify this python object later - we use it in indexize - i += 2 - forwarded_json['Functions']['nextIndex'] = i - function_table_size = forwarded_json['Functions']['nextIndex'] - i = 1 - while i < function_table_size: - i *= 2 - function_table_size = i + if alias: + sig = forwarded_json['Functions']['implementedFunctions'].get(indexed) or forwarded_json['Functions']['unimplementedFunctions'].get(indexed) + assert sig, indexed + if sig not in table_counters: + table_counters[sig] = 2 + 2*settings['RESERVED_FUNCTION_POINTERS'] + curr = table_counters[sig] + table_counters[sig] += 2 + else: + curr = i + i += 2 + #print >> sys.stderr, 'function indexing', indexed, curr, sig + forwarded_json['Functions']['indexedFunctions'][indexed] = curr # make sure not to modify this python object later - we use it in indexize def split_32(x): x = int(x) @@ -370,14 +378,6 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, pre_tables = last_forwarded_json['Functions']['tables']['pre'] del last_forwarded_json['Functions']['tables']['pre'] - # Find function table calls without function tables generated for them - for funcs_js_item in funcs_js: - for use in set(re.findall(r'{{{ FTM_[\w\d_$]+ }}}', funcs_js_item)): - sig = use[8:len(use)-4] - if sig not in last_forwarded_json['Functions']['tables']: - if DEBUG: print >> sys.stderr, 'add empty function table', sig - last_forwarded_json['Functions']['tables'][sig] = 'var FUNCTION_TABLE_' + sig + ' = [' + ','.join(['0']*function_table_size) + '];\n' - def make_table(sig, raw): i = Counter.i Counter.i += 1 diff --git a/src/jsifier.js b/src/jsifier.js index 8270b443..3f52337f 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -1463,6 +1463,7 @@ function JSify(data, functionsOnly, givenFunctions) { if (!byPointerForced && !funcData.setjmpTable) { // normal asm function pointer call callIdent = '(' + callIdent + ')&{{{ FTM_' + sig + ' }}}'; // the function table mask is set in emscripten.py + Functions.neededTables[sig] = 1; } else { // This is a call through an invoke_*, either a forced one, or a setjmp-required one // note: no need to update argsTypes at this point diff --git a/src/library.js b/src/library.js index 84071b68..d19fd531 100644 --- a/src/library.js +++ b/src/library.js @@ -3482,7 +3482,8 @@ LibraryManager.library = { } else if (oldObj.isRoot || oldObj.path == FS.currentPath) { ___setErrNo(ERRNO_CODES.EBUSY); return -1; - } else if (newObj.path && newObj.path.indexOf(oldObj.path) == 0) { + } else if (newObj.parentPath && + newObj.parentPath.indexOf(oldObj.path) == 0) { ___setErrNo(ERRNO_CODES.EINVAL); return -1; } else if (newObj.exists && newObj.object.isFolder) { diff --git a/src/library_gl.js b/src/library_gl.js index 0f6fb670..2e59f0d0 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -2880,7 +2880,7 @@ var LibraryGL = { // Vertex array object (VAO) support. TODO: when the WebGL extension is popular, use that and remove this code and GL.vaos glGenVertexArrays__deps: ['$GLEMulation'], - glGenVertexArrays__sig: ['vii'], + glGenVertexArrays__sig: 'vii', glGenVertexArrays: function(n, vaos) { for (var i = 0; i < n; i++) { var id = GL.getNewId(GLEmulation.vaos); @@ -2895,7 +2895,7 @@ var LibraryGL = { {{{ makeSetValue('vaos', 'i*4', 'id', 'i32') }}}; } }, - glDeleteVertexArrays__sig: ['vii'], + glDeleteVertexArrays__sig: 'vii', glDeleteVertexArrays: function(n, vaos) { for (var i = 0; i < n; i++) { var id = {{{ makeGetValue('vaos', 'i*4', 'i32') }}}; @@ -2903,7 +2903,7 @@ var LibraryGL = { if (GLEmulation.currentVao && GLEmulation.currentVao.id == id) GLEmulation.currentVao = null; } }, - glBindVertexArray__sig: ['vi'], + glBindVertexArray__sig: 'vi', glBindVertexArray: function(vao) { // undo vao-related things, wipe the slate clean, both for vao of 0 or an actual vao GLEmulation.currentVao = null; // make sure the commands we run here are not recorded @@ -3331,7 +3331,10 @@ for (var item in LibraryGL) { } LibraryGL.$GLEmulation__deps = LibraryGL.$GLEmulation__deps.concat(glFuncs); LibraryGL.$GLEmulation__deps.push(function() { - for (var func in Functions.getIndex.tentative) Functions.getIndex(func); + for (var func in Functions.getIndex.tentative) { + Functions.getIndex(func); + Functions.unimplementedFunctions[func] = LibraryGL[func.substr(1) + '__sig']; + } }); if (FORCE_GL_EMULATION) { diff --git a/src/modules.js b/src/modules.js index 2775bfbd..e4093078 100644 --- a/src/modules.js +++ b/src/modules.js @@ -229,6 +229,8 @@ var Types = { preciseI64MathUsed: (PRECISE_I64_MATH == 2) }; +var firstTableIndex = (ASM_JS ? 2*RESERVED_FUNCTION_POINTERS : 0) + 2; + var Functions = { // All functions that will be implemented in this file. Maps id to signature implementedFunctions: {}, @@ -236,7 +238,9 @@ var Functions = { unimplementedFunctions: {}, // library etc. functions that we need to index, maps id to signature indexedFunctions: {}, - nextIndex: (ASM_JS ? 2*RESERVED_FUNCTION_POINTERS : 0) + 2, // Start at a non-0 (even, see below) value + nextIndex: firstTableIndex, // Start at a non-0 (even, see below) value + neededTables: set('v', 'vi', 'ii', 'iii'), // signatures that appeared (initialized with library stuff + // we always use), and we will need a function table for blockAddresses: {}, // maps functions to a map of block labels to label ids @@ -261,6 +265,7 @@ var Functions = { if (!doNotCreate) this.indexedFunctions[ident] = 0; // tell python we need this indexized return "'{{ FI_" + toNiceIdent(ident) + " }}'"; // something python will replace later } else { + if (!singlePhase) return 'NO_INDEX'; // Should not index functions in post var ret = this.indexedFunctions[ident]; if (!ret) { if (doNotCreate) return '0'; @@ -278,25 +283,25 @@ var Functions = { // Generate code for function indexing generateIndexing: function() { - var total = this.nextIndex; - if (ASM_JS) total = ceilPowerOfTwo(total); // must be power of 2 for mask - function emptyTable(sig) { - return zeros(total); - } var tables = { pre: '' }; if (ASM_JS) { - ['v', 'vi', 'ii', 'iii'].forEach(function(sig) { // add some default signatures that are used in the library - tables[sig] = emptyTable(sig); // TODO: make them compact + keys(Functions.neededTables).forEach(function(sig) { // add some default signatures that are used in the library + tables[sig] = zeros(firstTableIndex); }); } for (var ident in this.indexedFunctions) { var sig = ASM_JS ? Functions.implementedFunctions[ident] || Functions.unimplementedFunctions[ident] || LibraryManager.library[ident.substr(1) + '__sig'] : 'x'; assert(sig, ident); - if (!tables[sig]) tables[sig] = emptyTable(sig); // TODO: make them compact - tables[sig][this.indexedFunctions[ident]] = ident; + if (!tables[sig]) tables[sig] = zeros(firstTableIndex); + var index = this.indexedFunctions[ident]; + for (var i = tables[sig].length; i < index; i++) { + tables[sig][i] = 0; // keep flat + } + tables[sig][index] = ident; } var generated = false; var wrapped = {}; + var maxTable = 0; for (var t in tables) { if (t == 'pre') continue; generated = true; @@ -351,6 +356,21 @@ var Functions = { j += 10; } } + maxTable = Math.max(maxTable, table.length); + } + if (ASM_JS) maxTable = ceilPowerOfTwo(maxTable); + for (var t in tables) { + if (t == 'pre') continue; + var table = tables[t]; + if (ASM_JS) { + // asm function table mask must be power of two + // if nonaliasing, then standardize function table size, to avoid aliasing pointers through the &M mask (in a small table using a big index) + var fullSize = ALIASING_FUNCTION_POINTERS ? ceilPowerOfTwo(table.length) : maxTable; + for (var i = table.length; i < fullSize; i++) { + table[i] = 0; + } + } + // finalize table var indices = table.toString().replace('"', ''); if (BUILD_AS_SHARED_LIB) { // Shared libraries reuse the parent's function table. @@ -428,6 +448,7 @@ var PassManager = { indexedFunctions: Functions.indexedFunctions, implementedFunctions: ASM_JS ? Functions.implementedFunctions : [], unimplementedFunctions: Functions.unimplementedFunctions, + neededTables: Functions.neededTables } })); } else if (phase == 'post') { diff --git a/src/settings.js b/src/settings.js index 8766277b..d3abb06e 100644 --- a/src/settings.js +++ b/src/settings.js @@ -142,6 +142,10 @@ var SAFE_DYNCALLS = 0; // Show stack traces on missing function pointer/virtual var RESERVED_FUNCTION_POINTERS = 0; // In asm.js mode, we cannot simply add function pointers to // function tables, so we reserve some slots for them. +var ALIASING_FUNCTION_POINTERS = 0; // Whether to allow function pointers to alias if they have + // a different type. This can greatly decrease table sizes + // in asm.js, but can break code that compares function + // pointers across different types. var ASM_HEAP_LOG = 0; // Simple heap logging, like SAFE_HEAP_LOG but cheaper, and in asm.js diff --git a/tests/runner.py b/tests/runner.py index b2b1ff43..87eab8c6 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -3404,6 +3404,35 @@ Exiting setjmp function, level: 0, prev_jmp: -1 ''' self.do_run(src, 'z:1*', force_c=True) + def test_rename(self): + src = ''' + #include <stdio.h> + #include <sys/stat.h> + #include <sys/types.h> + #include <assert.h> + + int main() { + int err; + FILE* fid; + + err = mkdir("/foo", 0777); + err = mkdir("/bar", 0777); + fid = fopen("/foo/bar", "w+"); + fclose(fid); + + err = rename("/foo/bar", "/foo/bar2"); + printf("%d\\n", err); + + err = rename("/foo", "/foo/foo"); + printf("%d\\n", err); + + err = rename("/foo", "/bar/foo"); + printf("%d\\n", err); + return 0; + } + ''' + self.do_run(src, '0\n-1\n0\n', force_c=True) + def test_alloca_stack(self): if self.emcc_args is None: return # too slow in other modes @@ -7727,6 +7756,23 @@ void*:16 self.do_run(path_from_root('tests', 'cubescript'), '*\nTemp is 33\n9\n5\nhello, everyone\n*', main_file='command.cpp') + assert 'asm2g' in test_modes + if self.run_name == 'asm2g': + results = {} + original = open('src.cpp.o.js').read() + results[Settings.ALIASING_FUNCTION_POINTERS] = len(original) + Settings.ALIASING_FUNCTION_POINTERS = 1 - Settings.ALIASING_FUNCTION_POINTERS + self.do_run(path_from_root('tests', 'cubescript'), '*\nTemp is 33\n9\n5\nhello, everyone\n*', main_file='command.cpp') + final = open('src.cpp.o.js').read() + results[Settings.ALIASING_FUNCTION_POINTERS] = len(final) + open('original.js', 'w').write(original) + print results + assert results[1] < 0.99*results[0] + assert ' & 3]()' in original, 'small function table exists' + assert ' & 3]()' not in final, 'small function table does not exist' + assert ' & 255]()' not in original, 'big function table does not exist' + assert ' & 255]()' in final, 'big function table exists' + def test_gcc_unmangler(self): Settings.NAMED_GLOBALS = 1 # test coverage for this @@ -8513,6 +8559,10 @@ def process(filename): self.do_run(src, '''Finished up all reserved function pointers. Use a higher value for RESERVED_FUNCTION_POINTERS.''') generated = open('src.cpp.o.js').read() assert 'jsCall' not in generated + Settings.RESERVED_FUNCTION_POINTERS = 1 + + Settings.ALIASING_FUNCTION_POINTERS = 1 - Settings.ALIASING_FUNCTION_POINTERS # flip the test + self.do_run(src, '''Hello 7 from JS!''') def test_scriptaclass(self): if self.emcc_args is None: return self.skip('requires emcc') diff --git a/tools/shared.py b/tools/shared.py index cc056074..228f1253 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -676,6 +676,7 @@ class Settings: Settings.EMIT_GENERATED_FUNCTIONS = 1 if opt_level >= 2: Settings.RELOOP = 1 + Settings.ALIASING_FUNCTION_POINTERS = 1 if opt_level >= 3: # Aside from these, -O3 also runs closure compiler and llvm lto Settings.FORCE_ALIGNED_MEMORY = 1 |