diff options
48 files changed, 6553 insertions, 394 deletions
@@ -107,3 +107,4 @@ a license to everyone to use it as detailed in LICENSE.) * Michael Tirado <icetooth333@gmail.com> * Ben Noordhuis <info@bnoordhuis.nl> * Bob Roberts <bobroberts177@gmail.com> +* John Vilk <jvilk@cs.umass.edu> diff --git a/cmake/Platform/Emscripten.cmake b/cmake/Platform/Emscripten.cmake index 7c8e83fa..e1e54ccf 100644 --- a/cmake/Platform/Emscripten.cmake +++ b/cmake/Platform/Emscripten.cmake @@ -146,6 +146,10 @@ set(link_js_counter 1) # Internal function: Do not call from user CMakeLists.txt files. Use one of em_link_js_library()/em_link_pre_js()/em_link_post_js() instead. function(em_add_tracked_link_flag target flagname) get_target_property(props ${target} LINK_FLAGS) + if(NOT props) + set(props "") + endif() + # User can input list of JS files either as a single list, or as variable arguments to this function, so iterate over varargs, and treat each # item in varargs as a list itself, to support both syntax forms. foreach(jsFileList ${ARGN}) @@ -1133,10 +1133,11 @@ try: heap = 4096 while heap < shared.Settings.TOTAL_MEMORY: - if heap <= 16*1024*1024: - heap *= 2 - else: - heap += 16*1024*1024 + heap *= 2 + #if heap <= 16*1024*1024: + # heap *= 2 + #else: + # heap += 16*1024*1024 if heap != shared.Settings.TOTAL_MEMORY: logging.warning('increasing TOTAL_MEMORY to %d to be more reasonable for asm.js' % heap) shared.Settings.TOTAL_MEMORY = heap @@ -1429,6 +1430,12 @@ try: 'wctob.c', 'wctomb.c', ]], + ['regex', [ + 'regcomp.c', + 'regerror.c', + 'regexec.c', + 'tre-mem.c', + ]], ['stdio', [ 'fwprintf.c', 'swprintf.c', @@ -1631,18 +1638,26 @@ try: else: # At minimum remove dead functions etc., this potentially saves a lot in the size of the generated code (and the time to compile it) link_opts += shared.Building.get_safe_internalize() + ['-globaldce'] - shared.Building.llvm_opt(in_temp(target_basename + '.bc'), link_opts) - if DEBUG: save_intermediate('linktime', 'bc') + if not save_bc: + # let llvm opt directly emit ll, to skip writing and reading all the bitcode + link_opts += ['-S'] + shared.Building.llvm_opt(final, link_opts, final + '.link.ll') + final = final + '.link.ll' + if DEBUG: save_intermediate('linktime', 'll') + else: + shared.Building.llvm_opt(final, link_opts) + if DEBUG: save_intermediate('linktime', 'bc') if save_bc: shutil.copyfile(final, save_bc) # Prepare .ll for Emscripten if not LEAVE_INPUTS_RAW: - final = shared.Building.llvm_dis(final, final + '.ll') + if save_bc: + final = shared.Building.llvm_dis(final, final + '.ll') else: assert len(input_files) == 1 - if DEBUG: save_intermediate('ll', 'll') + if DEBUG and save_bc: save_intermediate('ll', 'll') if AUTODEBUG: logging.debug('autodebug') @@ -10,21 +10,24 @@ import sys from tools import shared from tools.asm_module import AsmModule -try: - me, main, side, out = sys.argv[:4] -except: - print >> sys.stderr, 'usage: emlink.py [main module] [side module] [output name]' - sys.exit(1) +def run(): + try: + me, main, side, out = sys.argv[:4] + except: + print >> sys.stderr, 'usage: emlink.py [main module] [side module] [output name]' + sys.exit(1) -print 'Main module:', main -print 'Side module:', side -print 'Output:', out + print 'Main module:', main + print 'Side module:', side + print 'Output:', out -shared.try_delete(out) + shared.try_delete(out) -main = AsmModule(main) -side = AsmModule(side) + main = AsmModule(main) + side = AsmModule(side) -side.relocate_into(main) -main.write(out) + side.relocate_into(main) + main.write(out) +if __name__ == '__main__': + run() diff --git a/emscripten.py b/emscripten.py index 5576baba..75e6711a 100755 --- a/emscripten.py +++ b/emscripten.py @@ -11,6 +11,7 @@ headers, for the libc implementation in JS). import os, sys, json, optparse, subprocess, re, time, multiprocessing, string, logging +from tools import shared from tools import jsrun, cache as cache_module, tempfiles from tools.response_file import read_response_file @@ -25,7 +26,6 @@ def get_configuration(): if hasattr(get_configuration, 'configuration'): return get_configuration.configuration - from tools import shared configuration = shared.Configuration(environ=os.environ) get_configuration.configuration = configuration return configuration @@ -117,7 +117,7 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, last = func_start func_start = ll.find('\ndefine ', func_start) if func_start > last: - pre.append(ll[last:min(func_start+1, meta_start)] + '\n') + pre.append(ll[last:min(func_start+1, meta_start) if meta_start > 0 else func_start+1] + '\n') if func_start < 0: pre.append(ll[last:meta_start] + '\n') break @@ -425,8 +425,8 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, Counter.i += 1 bad = 'b' + str(i) params = ','.join(['p%d' % p for p in range(len(sig)-1)]) - coercions = ';'.join(['p%d = %sp%d%s' % (p, '+' if sig[p+1] != 'i' else '', p, '' if sig[p+1] != 'i' else '|0') for p in range(len(sig)-1)]) + ';' - ret = '' if sig[0] == 'v' else ('return %s0' % ('+' if sig[0] != 'i' else '')) + coercions = ';'.join(['p%d = %s' % (p, shared.JS.make_coercion('p%d' % p, sig[p+1], settings)) for p in range(len(sig)-1)]) + ';' + ret = '' if sig[0] == 'v' else ('return %s' % shared.JS.make_initializer(sig[0], settings)) start = raw.index('[') end = raw.rindex(']') body = raw[start+1:end].split(',') @@ -451,7 +451,7 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, math_envs = ['Math.min'] # TODO: move min to maths asm_setup += '\n'.join(['var %s = %s;' % (f.replace('.', '_'), f) for f in math_envs]) - if settings['TO_FLOAT32']: maths += ['Math.toFloat32'] + if settings['PRECISE_F32']: maths += ['Math.fround'] basic_funcs = ['abort', 'assert', 'asmPrintInt', 'asmPrintFloat'] + [m.replace('.', '_') for m in math_envs] if settings['RESERVED_FUNCTION_POINTERS'] > 0: basic_funcs.append('jsCall') @@ -476,18 +476,14 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, asm_runtime_funcs = ['stackAlloc', 'stackSave', 'stackRestore', 'setThrew'] + ['setTempRet%d' % i for i in range(10)] # function tables - def asm_coerce(value, sig): - if sig == 'v': return value - return ('+' if sig != 'i' else '') + value + ('|0' if sig == 'i' else '') - function_tables = ['dynCall_' + table for table in last_forwarded_json['Functions']['tables']] function_tables_impls = [] for sig in last_forwarded_json['Functions']['tables'].iterkeys(): args = ','.join(['a' + str(i) for i in range(1, len(sig))]) - arg_coercions = ' '.join(['a' + str(i) + '=' + asm_coerce('a' + str(i), sig[i]) + ';' for i in range(1, len(sig))]) - coerced_args = ','.join([asm_coerce('a' + str(i), sig[i]) for i in range(1, len(sig))]) - ret = ('return ' if sig[0] != 'v' else '') + asm_coerce('FUNCTION_TABLE_%s[index&{{{ FTM_%s }}}](%s)' % (sig, sig, coerced_args), sig[0]) + arg_coercions = ' '.join(['a' + str(i) + '=' + shared.JS.make_coercion('a' + str(i), sig[i], settings) + ';' for i in range(1, len(sig))]) + coerced_args = ','.join([shared.JS.make_coercion('a' + str(i), sig[i], settings) for i in range(1, len(sig))]) + ret = ('return ' if sig[0] != 'v' else '') + shared.JS.make_coercion('FUNCTION_TABLE_%s[index&{{{ FTM_%s }}}](%s)' % (sig, sig, coerced_args), sig[0], settings) function_tables_impls.append(''' function dynCall_%s(index%s%s) { index = index|0; @@ -497,7 +493,7 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, ''' % (sig, ',' if len(sig) > 1 else '', args, arg_coercions, ret)) for i in range(settings['RESERVED_FUNCTION_POINTERS']): - jsret = ('return ' if sig[0] != 'v' else '') + asm_coerce('jsCall(%d%s%s)' % (i, ',' if coerced_args else '', coerced_args), sig[0]) + jsret = ('return ' if sig[0] != 'v' else '') + shared.JS.make_coercion('jsCall(%d%s%s)' % (i, ',' if coerced_args else '', coerced_args), sig[0], settings) function_tables_impls.append(''' function jsCall_%s_%s(%s) { %s @@ -505,7 +501,6 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, } ''' % (sig, i, args, arg_coercions, jsret)) - from tools import shared shared.Settings.copy(settings) asm_setup += '\n' + shared.JS.make_invoke(sig) + '\n' basic_funcs.append('invoke_%s' % sig) @@ -585,7 +580,7 @@ var asm = (function(global, env, buffer) { var undef = 0; var tempInt = 0, tempBigInt = 0, tempBigIntP = 0, tempBigIntS = 0, tempBigIntR = 0.0, tempBigIntI = 0, tempBigIntD = 0, tempValue = 0, tempDouble = 0.0; ''' + ''.join([''' - var tempRet%d = 0;''' % i for i in range(10)]) + '\n' + asm_global_funcs + ''' + var tempRet%d = 0;''' % i for i in range(10)]) + '\n' + asm_global_funcs] + [' var tempFloat = %s;\n' % ('Math_fround(0)' if settings.get('PRECISE_F32') else '0.0')] + [''' // EMSCRIPTEN_START_FUNCS function stackAlloc(size) { size = size|0; @@ -727,14 +722,12 @@ def main(args, compiler_engine, cache, jcache, relooper, temp_files, DEBUG, DEBU relooper = cache.get_path('relooper.js') settings.setdefault('RELOOPER', relooper) if not os.path.exists(relooper): - from tools import shared shared.Building.ensure_relooper(relooper) settings.setdefault('STRUCT_INFO', cache.get_path('struct_info.compiled.json')) struct_info = settings.get('STRUCT_INFO') if not os.path.exists(struct_info): - from tools import shared shared.Building.ensure_struct_info(struct_info) emscript(args.infile, settings, args.outfile, libraries, compiler_engine=compiler_engine, @@ -833,7 +826,6 @@ WARNING: You should normally never use this! Use emcc instead. temp_files = tempfiles.TempFiles(temp_dir) if keywords.compiler is None: - from tools import shared keywords.compiler = shared.COMPILER_ENGINE if keywords.verbose is None: diff --git a/src/compiler.js b/src/compiler.js index e9197a5d..aa3c7b92 100644 --- a/src/compiler.js +++ b/src/compiler.js @@ -311,12 +311,16 @@ function compile(raw) { B = new Benchmarker(); -if (ll_file) { - if (ll_file.indexOf(String.fromCharCode(10)) == -1) { - compile(read(ll_file)); - } else { - compile(ll_file); // we are given raw .ll +try { + if (ll_file) { + if (ll_file.indexOf(String.fromCharCode(10)) == -1) { + compile(read(ll_file)); + } else { + compile(ll_file); // we are given raw .ll + } } +} catch(err) { + printErr('aborting from js compiler due to exception: ' + err); } //var M = keys(tokenCacheMisses).map(function(m) { return [m, misses[m]] }).sort(function(a, b) { return a[1] - b[1] }); diff --git a/src/intertyper.js b/src/intertyper.js index fceeb38d..fa53c652 100644 --- a/src/intertyper.js +++ b/src/intertyper.js @@ -680,6 +680,9 @@ function intertyper(lines, sidePass, baseLineNums) { args.push(ident); }); } + item.ident = expandLLVMString(item.ident).replace(/(#[^\n]*)/g, function(m) { + return '/* ' + m.substr(1) + ' */'; // fix asm comments to js comments + }); if (item.assignTo) item.ident = 'return ' + item.ident; item.ident = '(function(' + params + ') { ' + item.ident + ' })(' + args + ');'; return { ret: item, item: item }; diff --git a/src/jsifier.js b/src/jsifier.js index 0da48a8c..acfb6365 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -490,10 +490,19 @@ function JSify(data, functionsOnly, givenFunctions) { } else { // If this is not linkable, anything not in the library is definitely missing var cancel = false; + if (item.ident in DEAD_FUNCTIONS) { + if (LibraryManager.library[shortident + '__asm']) { + warn('cannot kill asm library function ' + item.ident); + } else { + LibraryManager.library[shortident] = new Function("Module['printErr']('dead function: " + shortident + "'); abort(-1);"); + delete LibraryManager.library[shortident + '__inline']; + delete LibraryManager.library[shortident + '__deps']; + } + } if (!LINKABLE && !LibraryManager.library.hasOwnProperty(shortident) && !LibraryManager.library.hasOwnProperty(shortident + '__inline')) { if (ERROR_ON_UNDEFINED_SYMBOLS) error('unresolved symbol: ' + shortident); - if (VERBOSE || WARN_ON_UNDEFINED_SYMBOLS) printErr('warning: unresolved symbol: ' + shortident); - if (ASM_JS || item.ident in DEAD_FUNCTIONS) { + else if (VERBOSE || WARN_ON_UNDEFINED_SYMBOLS) warn('unresolved symbol: ' + shortident); + if (ASM_JS) { // emit a stub that will fail during runtime. this allows asm validation to succeed. LibraryManager.library[shortident] = new Function("Module['printErr']('missing function: " + shortident + "'); abort(-1);"); } else { @@ -756,14 +765,7 @@ function JSify(data, functionsOnly, givenFunctions) { if (func.setjmpTable && !ASM_JS) { ret += ' } catch(e) { if (!e.longjmp || !(e.id in mySetjmpIds)) throw(e); setjmpTable[setjmpLabels[e.id]](e.value) }'; } - if (ASM_JS && func.returnType !== 'void') { - // Add a return - if (func.returnType in Runtime.FLOAT_TYPES) { - ret += ' return +0;\n'; - } else { - ret += ' return 0;\n'; - } - } + if (ASM_JS && func.returnType !== 'void') ret += ' return ' + asmInitializer(func.returnType) + ';\n'; // Add a return } else { ret += (SHOW_LABELS ? indent + '/* ' + block.entries[0] + ' */' : '') + '\n' + getLabelLines(block.labels[0]); } @@ -833,11 +835,7 @@ function JSify(data, functionsOnly, givenFunctions) { var lastReturn = func.JS.lastIndexOf('return '); if ((lastCurly < 0 && lastReturn < 0) || // no control flow, no return (lastCurly >= 0 && lastReturn < lastCurly)) { // control flow, no return past last join - if (func.returnType in Runtime.FLOAT_TYPES) { - func.JS += ' return +0;\n'; - } else { - func.JS += ' return 0;\n'; - } + func.JS += ' return ' + asmInitializer(func.returnType) + ';\n'; } } func.JS += '}\n'; @@ -1337,7 +1335,7 @@ function JSify(data, functionsOnly, givenFunctions) { if (isNumber(item.ident)) { // Direct read from a memory address; this may be an intentional segfault, if not, it is a bug in the source if (ASM_JS) { - return asmCoercion('abort(' + item.ident + ')', item.type); + return asmFFICoercion('abort(' + item.ident + ')', item.type); } else { item.assignTo = null; return 'throw "fault on read from ' + item.ident + '";'; @@ -1514,8 +1512,10 @@ function JSify(data, functionsOnly, givenFunctions) { args = args.map(function(arg, i) { return indexizeFunctions(arg, argsTypes[i]) }); if (ASM_JS) { - if (shortident in Functions.libraryFunctions || simpleIdent in Functions.libraryFunctions || byPointerForced || invoke || extCall || funcData.setjmpTable) { - args = args.map(function(arg, i) { return asmCoercion(arg, argsTypes[i]) }); + var ffiCall = (shortident in Functions.libraryFunctions || simpleIdent in Functions.libraryFunctions || byPointerForced || invoke || extCall || funcData.setjmpTable) && + !(simpleIdent in JS_MATH_BUILTINS); + if (ffiCall) { + args = args.map(function(arg, i) { return asmCoercion(arg, ensureValidFFIType(argsTypes[i])) }); } else { args = args.map(function(arg, i) { return asmEnsureFloat(arg, argsTypes[i]) }); } @@ -1592,7 +1592,7 @@ function JSify(data, functionsOnly, givenFunctions) { returnType = getReturnType(type); if (callIdent in Functions.implementedFunctions) { // LLVM sometimes bitcasts for no reason. We must call using the exact same type as the actual function is generated as - var trueType = Functions.getSignatureReturnType(Functions.implementedFunctions[callIdent]); + var trueType = Functions.getSignatureType(Functions.implementedFunctions[callIdent][0]); if (trueType !== returnType && !isIdenticallyImplemented(trueType, returnType)) { if (VERBOSE) warnOnce('Fixing function call based on return type from signature, on ' + [callIdent, returnType, trueType]); returnType = trueType; @@ -1628,7 +1628,11 @@ function JSify(data, functionsOnly, givenFunctions) { var ret = callIdent + '(' + args.join(',') + ')'; if (ASM_JS) { // TODO: do only when needed (library functions and Math.*?) XXX && simpleIdent in Functions.libraryFunctions) { - ret = asmCoercion(ret, returnType); + if (ffiCall) { + ret = asmFFICoercion(ret, returnType); + } else { + ret = asmCoercion(ret, returnType); + } if (simpleIdent == 'abort' && funcData.returnType != 'void') { ret += '; return ' + asmCoercion('0', funcData.returnType); // special case: abort() can happen without return, breaking the return type of asm functions. ensure a return } diff --git a/src/library.js b/src/library.js index 31f531e9..48acf6ac 100644 --- a/src/library.js +++ b/src/library.js @@ -847,10 +847,7 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.ERANGE); return 0; } else { - for (var i = 0; i < cwd.length; i++) { - {{{ makeSetValue('buf', 'i', 'cwd.charCodeAt(i)', 'i8') }}} - } - {{{ makeSetValue('buf', 'i', '0', 'i8') }}} + writeAsciiToMemory(cwd, buf); return buf; } }, @@ -1193,7 +1190,6 @@ LibraryManager.library = { _exit: function(status) { // void _exit(int status); // http://pubs.opengroup.org/onlinepubs/000095399/functions/exit.html - Module.print('exit(' + status + ') called'); Module['exit'](status); }, fork__deps: ['__setErrNo', '$ERRNO_CODES'], @@ -1293,10 +1289,7 @@ LibraryManager.library = { if (namesize < ret.length + 1) { return ___setErrNo(ERRNO_CODES.ERANGE); } else { - for (var i = 0; i < ret.length; i++) { - {{{ makeSetValue('name', 'i', 'ret.charCodeAt(i)', 'i8') }}} - } - {{{ makeSetValue('name', 'i', '0', 'i8') }}} + writeAsciiToMemory(ret, name); return 0; } }, @@ -2699,10 +2692,7 @@ LibraryManager.library = { var result = dir + '/' + name; if (!_tmpnam.buffer) _tmpnam.buffer = _malloc(256); if (!s) s = _tmpnam.buffer; - for (var i = 0; i < result.length; i++) { - {{{ makeSetValue('s', 'i', 'result.charCodeAt(i)', 'i8') }}}; - } - {{{ makeSetValue('s', 'i', '0', 'i8') }}}; + writeAsciiToMemory(result, s); return s; }, tempnam__deps: ['tmpnam'], @@ -3343,10 +3333,7 @@ LibraryManager.library = { var ptrSize = {{{ Runtime.getNativeTypeSize('i8*') }}}; for (var i = 0; i < strings.length; i++) { var line = strings[i]; - for (var j = 0; j < line.length; j++) { - {{{ makeSetValue('poolPtr', 'j', 'line.charCodeAt(j)', 'i8') }}}; - } - {{{ makeSetValue('poolPtr', 'j', '0', 'i8') }}}; + writeAsciiToMemory(line, poolPtr); {{{ makeSetValue('envPtr', 'i * ptrSize', 'poolPtr', 'i8*') }}}; poolPtr += line.length + 1; } @@ -3976,10 +3963,7 @@ LibraryManager.library = { return ___setErrNo(ERRNO_CODES.ERANGE); } else { var msg = ERRNO_MESSAGES[errnum]; - for (var i = 0; i < msg.length; i++) { - {{{ makeSetValue('strerrbuf', 'i', 'msg.charCodeAt(i)', 'i8') }}} - } - {{{ makeSetValue('strerrbuf', 'i', 0, 'i8') }}} + writeAsciiToMemory(msg, strerrbuf); return 0; } } else { @@ -5067,10 +5051,7 @@ LibraryManager.library = { var layout = {{{ JSON.stringify(C_STRUCTS.utsname) }}}; function copyString(element, value) { var offset = layout[element]; - for (var i = 0; i < value.length; i++) { - {{{ makeSetValue('name', 'offset + i', 'value.charCodeAt(i)', 'i8') }}} - } - {{{ makeSetValue('name', 'offset + i', '0', 'i8') }}} + writeAsciiToMemory(value, name + offset); } if (name === 0) { return -1; @@ -6131,8 +6112,10 @@ LibraryManager.library = { // int nanosleep(const struct timespec *rqtp, struct timespec *rmtp); var seconds = {{{ makeGetValue('rqtp', C_STRUCTS.timespec.tv_sec, 'i32') }}}; var nanoseconds = {{{ makeGetValue('rqtp', C_STRUCTS.timespec.tv_nsec, 'i32') }}}; - {{{ makeSetValue('rmtp', C_STRUCTS.timespec.tv_sec, '0', 'i32') }}} - {{{ makeSetValue('rmtp', C_STRUCTS.timespec.tv_nsec, '0', 'i32') }}} + if (rmtp !== 0) { + {{{ makeSetValue('rmtp', C_STRUCTS.timespec.tv_sec, '0', 'i32') }}} + {{{ makeSetValue('rmtp', C_STRUCTS.timespec.tv_nsec, '0', 'i32') }}} + } return _usleep((seconds * 1e6) + (nanoseconds / 1000)); }, // TODO: Implement these for real. @@ -6572,10 +6555,7 @@ LibraryManager.library = { var me = _nl_langinfo; if (!me.ret) me.ret = _malloc(32); - for (var i = 0; i < result.length; i++) { - {{{ makeSetValue('me.ret', 'i', 'result.charCodeAt(i)', 'i8') }}} - } - {{{ makeSetValue('me.ret', 'i', '0', 'i8') }}} + writeAsciiToMemory(result, me.ret); return me.ret; }, @@ -7433,6 +7413,9 @@ LibraryManager.library = { getaddrinfo__deps: ['$Sockets', '$DNS', '_inet_pton4_raw', '_inet_ntop4_raw', '_inet_pton6_raw', '_inet_ntop6_raw', '_write_sockaddr', 'htonl'], getaddrinfo: function(node, service, hint, out) { + // Note getaddrinfo currently only returns a single addrinfo with ai_next defaulting to NULL. When NULL + // hints are specified or ai_family set to AF_UNSPEC or ai_socktype or ai_protocol set to 0 then we + // really should provide a linked list of suitable addrinfo values. var addrs = []; var canon = null; var addr = 0; @@ -7487,6 +7470,15 @@ LibraryManager.library = { type = proto === {{{ cDefine('IPPROTO_UDP') }}} ? {{{ cDefine('SOCK_DGRAM') }}} : {{{ cDefine('SOCK_STREAM') }}}; } + // If type or proto are set to zero in hints we should really be returning multiple addrinfo values, but for + // now default to a TCP STREAM socket so we can at least return a sensible addrinfo given NULL hints. + if (proto === 0) { + proto = {{{ cDefine('IPPROTO_TCP') }}}; + } + if (type === 0) { + type = {{{ cDefine('SOCK_STREAM') }}}; + } + if (!node && !service) { return {{{ cDefine('EAI_NONAME') }}}; } @@ -7494,14 +7486,14 @@ LibraryManager.library = { {{{ cDefine('AI_NUMERICSERV') }}}|{{{ cDefine('AI_V4MAPPED') }}}|{{{ cDefine('AI_ALL') }}}|{{{ cDefine('AI_ADDRCONFIG') }}})) { return {{{ cDefine('EAI_BADFLAGS') }}}; } - if (({{{ makeGetValue('hint', C_STRUCTS.addrinfo.ai_flags, 'i32') }}} & {{{ cDefine('AI_CANONNAME') }}}) && !node) { + if (hint !== 0 && ({{{ makeGetValue('hint', C_STRUCTS.addrinfo.ai_flags, 'i32') }}} & {{{ cDefine('AI_CANONNAME') }}}) && !node) { return {{{ cDefine('EAI_BADFLAGS') }}}; } if (flags & {{{ cDefine('AI_ADDRCONFIG') }}}) { // TODO return {{{ cDefine('EAI_NONAME') }}}; } - if (type !== {{{ cDefine('SOCK_STREAM') }}} && type !== {{{ cDefine('SOCK_DGRAM') }}}) { + if (type !== 0 && type !== {{{ cDefine('SOCK_STREAM') }}} && type !== {{{ cDefine('SOCK_DGRAM') }}}) { return {{{ cDefine('EAI_SOCKTYPE') }}}; } if (family !== {{{ cDefine('AF_UNSPEC') }}} && family !== {{{ cDefine('AF_INET') }}} && family !== {{{ cDefine('AF_INET6') }}}) { @@ -7631,12 +7623,43 @@ LibraryManager.library = { return 0; }, + // Can't use a literal for $GAI_ERRNO_MESSAGES as was done for $ERRNO_MESSAGES as the keys (e.g. EAI_BADFLAGS) + // are actually negative numbers and you can't have expressions as keys in JavaScript literals. + $GAI_ERRNO_MESSAGES: {}, + gai_strerror__deps: ['$GAI_ERRNO_MESSAGES'], gai_strerror: function(val) { - if (!_gai_strerror.error) { - _gai_strerror.error = allocate(intArrayFromString("unknown error"), 'i8', ALLOC_NORMAL); + var buflen = 256; + + // On first call to gai_strerror we initialise the buffer and populate the error messages. + if (!_gai_strerror.buffer) { + _gai_strerror.buffer = _malloc(buflen); + + GAI_ERRNO_MESSAGES['0'] = 'Success'; + GAI_ERRNO_MESSAGES['' + {{{ cDefine('EAI_BADFLAGS') }}}] = 'Invalid value for \'ai_flags\' field'; + GAI_ERRNO_MESSAGES['' + {{{ cDefine('EAI_NONAME') }}}] = 'NAME or SERVICE is unknown'; + GAI_ERRNO_MESSAGES['' + {{{ cDefine('EAI_AGAIN') }}}] = 'Temporary failure in name resolution'; + GAI_ERRNO_MESSAGES['' + {{{ cDefine('EAI_FAIL') }}}] = 'Non-recoverable failure in name res'; + GAI_ERRNO_MESSAGES['' + {{{ cDefine('EAI_FAMILY') }}}] = '\'ai_family\' not supported'; + GAI_ERRNO_MESSAGES['' + {{{ cDefine('EAI_SOCKTYPE') }}}] = '\'ai_socktype\' not supported'; + GAI_ERRNO_MESSAGES['' + {{{ cDefine('EAI_SERVICE') }}}] = 'SERVICE not supported for \'ai_socktype\''; + GAI_ERRNO_MESSAGES['' + {{{ cDefine('EAI_MEMORY') }}}] = 'Memory allocation failure'; + GAI_ERRNO_MESSAGES['' + {{{ cDefine('EAI_SYSTEM') }}}] = 'System error returned in \'errno\''; + GAI_ERRNO_MESSAGES['' + {{{ cDefine('EAI_OVERFLOW') }}}] = 'Argument buffer overflow'; + } + + var msg = 'Unknown error'; + + if (val in GAI_ERRNO_MESSAGES) { + if (GAI_ERRNO_MESSAGES[val].length > buflen - 1) { + msg = 'Message too long'; // EMSGSIZE message. This should never occur given the GAI_ERRNO_MESSAGES above. + } else { + msg = GAI_ERRNO_MESSAGES[val]; + } } - return _gai_strerror.error; + + writeAsciiToMemory(msg, _gai_strerror.buffer); + return _gai_strerror.buffer; }, // ========================================================================== diff --git a/src/library_egl.js b/src/library_egl.js index cc702fec..73d5e544 100644 --- a/src/library_egl.js +++ b/src/library_egl.js @@ -9,12 +9,16 @@ var LibraryEGL = { $EGL: { // This variable tracks the success status of the most recently invoked EGL function call. - eglErrorCode: 0x3000 /* EGL_SUCCESS */, + errorCode: 0x3000 /* EGL_SUCCESS */, + defaultDisplayInitialized: false, + currentContext: 0 /* EGL_NO_CONTEXT */, + currentReadSurface: 0 /* EGL_NO_SURFACE */, + currentDrawSurface: 0 /* EGL_NO_SURFACE */, stringCache: {}, setErrorCode: function(code) { - EGL.eglErrorCode = code; + EGL.errorCode = code; }, chooseConfig: function(display, attribList, config, config_size, numConfigs) { @@ -65,6 +69,7 @@ var LibraryEGL = { if (minorVersion) { {{{ makeSetValue('minorVersion', '0', '4', 'i32') }}}; // Advertise EGL Minor version: '4' } + EGL.defaultDisplayInitialized = true; EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); return 1; } @@ -80,18 +85,10 @@ var LibraryEGL = { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); return 0; } - // TODO: Tear down EGL here. Currently a no-op since we don't need to actually do anything here for the browser. - EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); - return 1; - }, - -// EGLAPI EGLBoolean EGLAPIENTRY eglTerminate(EGLDisplay dpy); - eglTerminate: function(display) { - if (display != 62000 /* Magic ID for Emscripten 'default display' */) { - EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); - return 0; - } - // TODO: Tear down EGL here. Currently a no-op since we don't need to actually do anything here for the browser. + EGL.currentContext = 0; + EGL.currentReadSurface = 0; + EGL.currentDrawSurface = 0; + EGL.defaultDisplayInitialized = false; EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); return 1; }, @@ -248,6 +245,12 @@ var LibraryEGL = { EGL.setErrorCode(0x300D /* EGL_BAD_SURFACE */); return 1; } + if (EGL.currentReadSurface == surface) { + EGL.currentReadSurface = 0; + } + if (EGL.currentDrawSurface == surface) { + EGL.currentDrawSurface = 0; + } EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); return 1; /* Magic ID for Emscripten 'default surface' */ }, @@ -265,6 +268,7 @@ var LibraryEGL = { EGL.windowID = _glutCreateWindow(); if (EGL.windowID != 0) { EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + // Note: This function only creates a context, but it shall not make it active. return 62004; // Magic ID for Emscripten EGLContext } else { EGL.setErrorCode(0x3009 /* EGL_BAD_MATCH */); // By the EGL 1.4 spec, an implementation that does not support GLES2 (WebGL in this case), this error code is set. @@ -280,10 +284,17 @@ var LibraryEGL = { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); return 0; } + if (context != 62004 /* Magic ID for Emscripten EGLContext */) { + EGL.setErrorCode(0x3006 /* EGL_BAD_CONTEXT */); + return 0; + } _glutDestroyWindow(EGL.windowID); EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); - return 62004; // Magic ID for Emscripten EGLContext + if (EGL.currentContext == context) { + EGL.currentContext = 0; + } + return 1 /* EGL_TRUE */; }, // EGLAPI EGLBoolean EGLAPIENTRY eglDestroyContext(EGLDisplay dpy, EGLContext ctx); @@ -407,7 +418,7 @@ var LibraryEGL = { // EGLAPI EGLint EGLAPIENTRY eglGetError(void); eglGetError: function() { - return EGL.eglErrorCode; + return EGL.errorCode; }, // EGLAPI const char * EGLAPIENTRY eglQueryString(EGLDisplay dpy, EGLint name); @@ -477,21 +488,46 @@ var LibraryEGL = { eglMakeCurrent: function(display, draw, read, context) { if (display != 62000 /* Magic ID for Emscripten 'default display' */) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); - return 0; + return 0 /* EGL_FALSE */; } //\todo An EGL_NOT_INITIALIZED error is generated if EGL is not initialized for dpy. - if (context != 62004 /* Magic ID for Emscripten EGLContext */) { + if (context != 0 && context != 62004 /* Magic ID for Emscripten EGLContext */) { EGL.setErrorCode(0x3006 /* EGL_BAD_CONTEXT */); return 0; } - if (read != 62006 || draw != 62006 /* Magic ID for Emscripten 'default surface' */) { + if ((read != 0 && read != 62006) || (draw != 0 && draw != 62006 /* Magic ID for Emscripten 'default surface' */)) { EGL.setErrorCode(0x300D /* EGL_BAD_SURFACE */); return 0; } + EGL.currentContext = context; + EGL.currentDrawSurface = draw; + EGL.currentReadSurface = read; EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); - return 1; + return 1 /* EGL_TRUE */; + }, + + // EGLAPI EGLContext EGLAPIENTRY eglGetCurrentContext(void); + eglGetCurrentContext: function() { + return EGL.currentContext; }, + // EGLAPI EGLSurface EGLAPIENTRY eglGetCurrentSurface(EGLint readdraw); + eglGetCurrentSurface: function(readdraw) { + if (readdraw == 0x305A /* EGL_READ */) { + return EGL.currentReadSurface; + } else if (readdraw == 0x3059 /* EGL_DRAW */) { + return EGL.currentDrawSurface; + } else { + EGL.setErrorCode(0x300C /* EGL_BAD_PARAMETER */); + return 0 /* EGL_NO_SURFACE */; + } + }, + + // EGLAPI EGLDisplay EGLAPIENTRY eglGetCurrentDisplay(void); + eglGetCurrentDisplay: function() { + return EGL.currentContext ? 62000 /* Magic ID for Emscripten 'default display' */ : 0; + }, + // EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffers(EGLDisplay dpy, EGLSurface surface); eglSwapBuffers: function() { EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); diff --git a/src/library_gl.js b/src/library_gl.js index ecb72f0f..afd36197 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -41,7 +41,11 @@ var LibraryGL = { 8 // GL_DOUBLE ], - uniformTable: {}, // name => uniform ID. the uID must be identical until relinking, cannot create a new uID each call to glGetUniformLocation + programInfos: {}, // Stores additional information needed for each shader program. Each entry is of form: + /* { uniforms: {}, // Maps ints back to the opaque WebGLUniformLocation objects. + maxUniformLength: int, // Cached in order to implement glGetProgramiv(GL_ACTIVE_UNIFORM_MAX_LENGTH) + maxAttributeLength: int // Cached in order to implement glGetProgramiv(GL_ACTIVE_ATTRIBUTE_MAX_LENGTH) + } */ stringCache: {}, @@ -463,15 +467,23 @@ var LibraryGL = { GL.validateGLObjectID(GL.programs, program, 'populateUniformTable', 'program'); #endif var p = GL.programs[program]; - GL.uniformTable[program] = {}; - var ptable = GL.uniformTable[program]; - // A program's uniformTable maps the string name of an uniform to an integer location of that uniform. + GL.programInfos[program] = { + uniforms: {}, + maxUniformLength: 0, // This is eagerly computed below, since we already enumerate all uniforms anyway. + maxAttributeLength: -1 // This is lazily computed and cached, computed when/if first asked, "-1" meaning not computed yet. + }; + + var ptable = GL.programInfos[program]; + var utable = ptable.uniforms; + // A program's uniform table maps the string name of an uniform to an integer location of that uniform. // The global GL.uniforms map maps integer locations to WebGLUniformLocations. var numUniforms = Module.ctx.getProgramParameter(p, Module.ctx.ACTIVE_UNIFORMS); for (var i = 0; i < numUniforms; ++i) { var u = Module.ctx.getActiveUniform(p, i); var name = u.name; + ptable.maxUniformLength = Math.max(ptable.maxUniformLength, name.length+1); + // Strip off any trailing array specifier we might have got, e.g. "[0]". if (name.indexOf(']', name.length-1) !== -1) { var ls = name.lastIndexOf('['); @@ -479,11 +491,11 @@ var LibraryGL = { } // Optimize memory usage slightly: If we have an array of uniforms, e.g. 'vec3 colors[3];', then - // only store the string 'colors' in ptable, and 'colors[0]', 'colors[1]' and 'colors[2]' will be parsed as 'colors'+i. + // only store the string 'colors' in utable, and 'colors[0]', 'colors[1]' and 'colors[2]' will be parsed as 'colors'+i. // Note that for the GL.uniforms table, we still need to fetch the all WebGLUniformLocations for all the indices. var loc = Module.ctx.getUniformLocation(p, name); var id = GL.getNewId(GL.uniforms); - ptable[name] = [u.size, id]; + utable[name] = [u.size, id]; GL.uniforms[id] = loc; for (var j = 1; j < u.size; ++j) { @@ -530,7 +542,11 @@ var LibraryGL = { ret = allocate(intArrayFromString('OpenGL ES GLSL 1.00 (WebGL)'), 'i8', ALLOC_NORMAL); break; default: - throw 'Failure: Invalid glGetString value: ' + name_; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetString: Unknown parameter ' + name_ + '!'); +#endif + return 0; } GL.stringCache[name_] = ret; return ret; @@ -542,6 +558,7 @@ var LibraryGL = { case 0x8DFA: // GL_SHADER_COMPILER {{{ makeSetValue('p', '0', '1', 'i32') }}}; return; + case 0x8DF8: // GL_SHADER_BINARY_FORMATS case 0x8DF9: // GL_NUM_SHADER_BINARY_FORMATS {{{ makeSetValue('p', '0', '0', 'i32') }}}; return; @@ -561,7 +578,11 @@ var LibraryGL = { {{{ makeSetValue('p', '0', 'result ? 1 : 0', 'i8') }}}; break; case "string": - throw 'Native code calling glGetIntegerv(' + name_ + ') on a name which returns a string!'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetIntegerv: Native code calling glGetIntegerv(' + name_ + ') on a name which returns a string!'); +#endif + return; case "object": if (result === null) { {{{ makeSetValue('p', '0', '0', 'i32') }}}; @@ -583,18 +604,45 @@ var LibraryGL = { } else if (result instanceof WebGLTexture) { {{{ makeSetValue('p', '0', 'result.name | 0', 'i32') }}}; } else { - throw 'Unknown object returned from WebGL getParameter'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetIntegerv: Unknown object returned from WebGL getParameter(' + name_ + ')!'); +#endif + return; } break; - case "undefined": - throw 'Native code calling glGetIntegerv(' + name_ + ') and it returns undefined'; default: - throw 'Why did we hit the default case?'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetIntegerv: Native code calling glGetIntegerv(' + name_ + ') and it returns ' + result + ' of type ' + typeof(result) + '!'); +#endif + return; } }, glGetFloatv__sig: 'vii', glGetFloatv: function(name_, p) { + switch(name_) { + case 0x8DFA: // GL_SHADER_COMPILER + {{{ makeSetValue('p', '0', '1', 'float') }}}; + return; + case 0x8DF8: // GL_SHADER_BINARY_FORMATS + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetFloatv(GL_SHADER_BINARY_FORMATS): Invalid parameter type!'); +#endif + return; + case 0x8DF9: // GL_NUM_SHADER_BINARY_FORMATS + {{{ makeSetValue('p', '0', '0', 'float') }}}; + return; + case 0x86A2: // GL_NUM_COMPRESSED_TEXTURE_FORMATS + // WebGL doesn't have GL_NUM_COMPRESSED_TEXTURE_FORMATS (it's obsolete since GL_COMPRESSED_TEXTURE_FORMATS returns a JS array that can be queried for length), + // so implement it ourselves to allow C++ GLES2 code get the length. + var formats = Module.ctx.getParameter(0x86A3 /*GL_COMPRESSED_TEXTURE_FORMATS*/); + {{{ makeSetValue('p', '0', 'formats.length', 'float') }}}; + return; + } + var result = Module.ctx.getParameter(name_); switch (typeof(result)) { case "number": @@ -607,7 +655,11 @@ var LibraryGL = { {{{ makeSetValue('p', '0', '0', 'float') }}}; case "object": if (result === null) { - throw 'Native code calling glGetFloatv(' + name_ + ') and it returns null'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetFloatv: Native code calling glGetFloatv(' + name_ + ') and it returns null!'); +#endif + return; } else if (result instanceof Float32Array || result instanceof Uint32Array || result instanceof Int32Array || @@ -626,18 +678,45 @@ var LibraryGL = { } else if (result instanceof WebGLTexture) { {{{ makeSetValue('p', '0', 'result.name | 0', 'float') }}}; } else { - throw 'Unknown object returned from WebGL getParameter'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetFloatv: Native code calling glGetFloatv(' + name_ + ') and it returns ' + result + ' of type ' + typeof(result) + '!'); +#endif + return; } break; - case "undefined": - throw 'Native code calling glGetFloatv(' + name_ + ') and it returns undefined'; default: - throw 'Why did we hit the default case?'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetFloatv: Native code calling glGetFloatv(' + name_ + ') and it returns ' + result + ' of type ' + typeof(result) + '!'); +#endif + return; } }, glGetBooleanv__sig: 'vii', glGetBooleanv: function(name_, p) { + switch(name_) { + case 0x8DFA: // GL_SHADER_COMPILER + {{{ makeSetValue('p', '0', '1', 'i8') }}}; + return; + case 0x8DF8: // GL_SHADER_BINARY_FORMATS + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetBooleanv(GL_SHADER_BINARY_FORMATS): Invalid parameter type!'); +#endif + return; + case 0x8DF9: // GL_NUM_SHADER_BINARY_FORMATS + {{{ makeSetValue('p', '0', '0', 'i8') }}}; + return; + case 0x86A2: // GL_NUM_COMPRESSED_TEXTURE_FORMATS + // WebGL doesn't have GL_NUM_COMPRESSED_TEXTURE_FORMATS (it's obsolete since GL_COMPRESSED_TEXTURE_FORMATS returns a JS array that can be queried for length), + // so implement it ourselves to allow C++ GLES2 code get the length. + var hasCompressedFormats = Module.ctx.getParameter(0x86A3 /*GL_COMPRESSED_TEXTURE_FORMATS*/).length > 0 ? 1 : 0; + {{{ makeSetValue('p', '0', 'hasCompressedFormats', 'i8') }}}; + return; + } + var result = Module.ctx.getParameter(name_); switch (typeof(result)) { case "number": @@ -647,7 +726,11 @@ var LibraryGL = { {{{ makeSetValue('p', '0', 'result != 0', 'i8') }}}; break; case "string": - throw 'Native code calling glGetBooleanv(' + name_ + ') on a name which returns a string!'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetBooleanv: Native code calling glGetBooleanv(' + name_ + ') on a name which returns a string!'); +#endif + return; case "object": if (result === null) { {{{ makeSetValue('p', '0', '0', 'i8') }}}; @@ -665,13 +748,19 @@ var LibraryGL = { result instanceof WebGLTexture) { {{{ makeSetValue('p', '0', '1', 'i8') }}}; // non-zero ID is always 1! } else { - throw 'Unknown object returned from WebGL getParameter'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetBooleanv: Unknown object returned from WebGL getParameter(' + name_ + ')!'); +#endif + return; } break; - case "undefined": - throw 'Unknown object returned from WebGL getParameter'; default: - throw 'Why did we hit the default case?'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetBooleanv: Native code calling glGetBooleanv(' + name_ + ') and it returns ' + result + ' of type ' + typeof(result) + '!'); +#endif + return; } }, @@ -759,7 +848,12 @@ var LibraryGL = { case 0x1908 /* GL_RGBA */: sizePerPixel = 4; break; - default: throw 'unsupported glReadPixels format'; + default: + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glReadPixels: Unsupported format ' + format + '!'); +#endif + return; } var totalSize = width*height*sizePerPixel; Module.ctx.readPixels(x, y, width, height, format, type, HEAPU8.subarray(pixels, pixels + totalSize)); @@ -963,11 +1057,12 @@ var LibraryGL = { name = name.slice(0, ls); } - var ptable = GL.uniformTable[program]; + var ptable = GL.programInfos[program]; if (!ptable) { return -1; } - var uniformInfo = ptable[name]; // returns pair [ dimension_of_uniform_array, uniform_location ] + var utable = ptable.uniforms; + var uniformInfo = utable[name]; // returns pair [ dimension_of_uniform_array, uniform_location ] if (uniformInfo && arrayOffset < uniformInfo[0]) { // Check if user asked for an out-of-bounds element, i.e. for 'vec4 colors[3];' user could ask for 'colors[10]' which should return -1. return uniformInfo[1]+arrayOffset; } else { @@ -1455,6 +1550,47 @@ var LibraryGL = { #endif if (pname == 0x8B84) { // GL_INFO_LOG_LENGTH {{{ makeSetValue('p', '0', 'Module.ctx.getProgramInfoLog(GL.programs[program]).length + 1', 'i32') }}}; + } else if (pname == 0x8B87 /* GL_ACTIVE_UNIFORM_MAX_LENGTH */) { + var ptable = GL.programInfos[program]; + if (ptable) { + {{{ makeSetValue('p', '0', 'ptable.maxUniformLength', 'i32') }}}; + return; + } else if (program < GL.counter) { +#if GL_ASSERTIONS + Module.printErr("A GL object " + program + " that is not a program object was passed to glGetProgramiv!"); +#endif + GL.recordError(0x0502 /* GL_INVALID_OPERATION */); + } else { +#if GL_ASSERTIONS + Module.printErr("A GL object " + program + " that did not come from GL was passed to glGetProgramiv!"); +#endif + GL.recordError(0x0501 /* GL_INVALID_VALUE */); + } + } else if (pname == 0x8B8A /* GL_ACTIVE_ATTRIBUTE_MAX_LENGTH */) { + var ptable = GL.programInfos[program]; + if (ptable) { + if (ptable.maxAttributeLength == -1) { + var program = GL.programs[program]; + var numAttribs = Module.ctx.getProgramParameter(program, Module.ctx.ACTIVE_ATTRIBUTES); + ptable.maxAttributeLength = 0; // Spec says if there are no active attribs, 0 must be returned. + for(var i = 0; i < numAttribs; ++i) { + var activeAttrib = Module.ctx.getActiveAttrib(program, i); + ptable.maxAttributeLength = Math.max(ptable.maxAttributeLength, activeAttrib.name.length+1); + } + } + {{{ makeSetValue('p', '0', 'ptable.maxAttributeLength', 'i32') }}}; + return; + } else if (program < GL.counter) { +#if GL_ASSERTIONS + Module.printErr("A GL object " + program + " that is not a program object was passed to glGetProgramiv!"); +#endif + GL.recordError(0x0502 /* GL_INVALID_OPERATION */); + } else { +#if GL_ASSERTIONS + Module.printErr("A GL object " + program + " that did not come from GL was passed to glGetProgramiv!"); +#endif + GL.recordError(0x0501 /* GL_INVALID_VALUE */); + } } else { {{{ makeSetValue('p', '0', 'Module.ctx.getProgramParameter(GL.programs[program], pname)', 'i32') }}}; } @@ -1482,7 +1618,7 @@ var LibraryGL = { Module.ctx.deleteProgram(program); program.name = 0; GL.programs[program] = null; - GL.uniformTable[program] = null; + GL.programInfos[program] = null; }, glAttachShader__sig: 'vii', @@ -1518,7 +1654,7 @@ var LibraryGL = { GL.validateGLObjectID(GL.programs, program, 'glLinkProgram', 'program'); #endif Module.ctx.linkProgram(GL.programs[program]); - GL.uniformTable[program] = {}; // uniforms no longer keep the same names after linking + GL.programInfos[program] = null; // uniforms no longer keep the same names after linking GL.populateUniformTable(program); }, @@ -2191,7 +2327,12 @@ var LibraryGL = { attribute = GLImmediate.clientAttributes[GLImmediate.COLOR]; break; case 0x8092: // GL_TEXTURE_COORD_ARRAY_POINTER attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0 + GLImmediate.clientActiveTexture]; break; - default: throw 'TODO: glGetPointerv for ' + name; + default: + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetPointerv: Unsupported name ' + name + '!'); +#endif + return; } {{{ makeSetValue('p', '0', 'attribute ? attribute.pointer : 0', 'i32') }}}; }, diff --git a/src/library_sdl.js b/src/library_sdl.js index e2ad82d6..f780e15a 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -75,6 +75,7 @@ var LibrarySDL = { textInput: false, startTime: null, + initFlags: 0, // The flags passed to SDL_Init buttonState: 0, modState: 0, DOMButtons: [0, 0, 0], @@ -639,6 +640,21 @@ var LibrarySDL = { {{{ makeSetValue('ptr', C_STRUCTS.SDL_ResizeEvent.h, 'event.h', 'i32') }}}; break; } + case 'joystick_button_up': case 'joystick_button_down': { + var state = event.type === 'joystick_button_up' ? 0 : 1; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.which, 'event.index', 'i8') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.button, 'event.button', 'i8') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.state, 'state', 'i8') }}}; + break; + } + case 'joystick_axis_motion': { + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.which, 'event.index', 'i8') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.axis, 'event.axis', 'i8') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.value, 'SDL.joystickAxisValueConversion(event.value)', 'i32') }}}; + break; + } default: throw 'Unhandled SDL event: ' + event.type; } }, @@ -695,7 +711,109 @@ var LibrarySDL = { for (var i = 0; i < num; i++) { console.log(' diagonal ' + i + ':' + [data[i*surfData.width*4 + i*4 + 0], data[i*surfData.width*4 + i*4 + 1], data[i*surfData.width*4 + i*4 + 2], data[i*surfData.width*4 + i*4 + 3]]); } - } + }, + + // Joystick helper methods and state + + joystickEventState: 0, + lastJoystickState: {}, // Map from SDL_Joystick* to their last known state. Required to determine if a change has occurred. + // Maps Joystick names to pointers. Allows us to avoid reallocating memory for + // joystick names each time this function is called. + joystickNamePool: {}, + recordJoystickState: function(joystick, state) { + // Standardize button state. + var buttons = new Array(state.buttons.length); + for (var i = 0; i < state.buttons.length; i++) { + buttons[i] = SDL.getJoystickButtonState(state.buttons[i]); + } + + SDL.lastJoystickState[joystick] = { + buttons: buttons, + axes: state.axes.slice(0), + timestamp: state.timestamp, + index: state.index, + id: state.id + }; + }, + // Retrieves the button state of the given gamepad button. + // Abstracts away implementation differences. + // Returns 'true' if pressed, 'false' otherwise. + getJoystickButtonState: function(button) { + if (typeof button === 'object') { + // Current gamepad API editor's draft (Firefox Nightly) + // https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#idl-def-GamepadButton + return button.pressed; + } else { + // Current gamepad API working draft (Firefox / Chrome Stable) + // http://www.w3.org/TR/2012/WD-gamepad-20120529/#gamepad-interface + return button > 0; + } + }, + // Queries for and inserts controller events into the SDL queue. + queryJoysticks: function() { + for (var joystick in SDL.lastJoystickState) { + var state = SDL.getGamepad(joystick - 1); + var prevState = SDL.lastJoystickState[joystick]; + // Check only if the timestamp has differed. + // NOTE: Timestamp is not available in Firefox. + if (typeof state.timestamp !== 'number' || state.timestamp !== prevState.timestamp) { + var i; + for (i = 0; i < state.buttons.length; i++) { + var buttonState = SDL.getJoystickButtonState(state.buttons[i]); + // NOTE: The previous state already has a boolean representation of + // its button, so no need to standardize its button state here. + if (buttonState !== prevState.buttons[i]) { + // Insert button-press event. + SDL.events.push({ + type: buttonState ? 'joystick_button_down' : 'joystick_button_up', + joystick: joystick, + index: joystick - 1, + button: i + }); + } + } + for (i = 0; i < state.axes.length; i++) { + if (state.axes[i] !== prevState.axes[i]) { + // Insert axes-change event. + SDL.events.push({ + type: 'joystick_axis_motion', + joystick: joystick, + index: joystick - 1, + axis: i, + value: state.axes[i] + }); + } + } + + SDL.recordJoystickState(joystick, state); + } + } + }, + // Converts the double-based browser axis value [-1, 1] into SDL's 16-bit + // value [-32768, 32767] + joystickAxisValueConversion: function(value) { + // Ensures that 0 is 0, 1 is 32767, and -1 is 32768. + return Math.ceil(((value+1) * 32767.5) - 32768); + }, + + getGamepads: function() { + var fcn = navigator.getGamepads || navigator.webkitGamepads || navigator.mozGamepads || navigator.gamepads || navigator.webkitGetGamepads; + if (fcn !== undefined) { + // The function must be applied on the navigator object. + return fcn.apply(navigator); + } else { + return []; + } + }, + + // Helper function: Returns the gamepad if available, or null if not. + getGamepad: function(deviceIndex) { + var gamepads = SDL.getGamepads(); + if (gamepads.length > deviceIndex && deviceIndex >= 0) { + return gamepads[deviceIndex]; + } + return null; + }, }, SDL_Linked_Version: function() { @@ -708,8 +826,10 @@ var LibrarySDL = { return SDL.version; }, - SDL_Init: function(what) { + SDL_Init: function(initFlags) { SDL.startTime = Date.now(); + SDL.initFlags = initFlags; + // capture all key events. we just keep down and up, but also capture press to prevent default actions if (!Module['doNotCaptureKeyboard']) { document.addEventListener("keydown", SDL.receiveEvent); @@ -718,6 +838,15 @@ var LibrarySDL = { window.addEventListener("blur", SDL.receiveEvent); document.addEventListener("visibilitychange", SDL.receiveEvent); } + + if (initFlags & 0x200) { + // SDL_INIT_JOYSTICK + // Firefox will not give us Joystick data unless we register this NOP + // callback. + // https://bugzilla.mozilla.org/show_bug.cgi?id=936104 + addEventListener("gamepadconnected", function() {}); + } + window.addEventListener("unload", SDL.receiveEvent); SDL.keyboardState = _malloc(0x10000); // Our SDL needs 512, but 64K is safe for older SDLs _memset(SDL.keyboardState, 0, 0x10000); @@ -730,6 +859,12 @@ var LibrarySDL = { SDL.DOMEventToSDLEvent['mousemove'] = 0x400 /* SDL_MOUSEMOTION */; SDL.DOMEventToSDLEvent['unload'] = 0x100 /* SDL_QUIT */; SDL.DOMEventToSDLEvent['resize'] = 0x7001 /* SDL_VIDEORESIZE/SDL_EVENT_COMPAT2 */; + // These are not technically DOM events; the HTML gamepad API is poll-based. + // However, we define them here, as the rest of the SDL code assumes that + // all SDL events originate as DOM events. + SDL.DOMEventToSDLEvent['joystick_axis_motion'] = 0x600 /* SDL_JOYAXISMOTION */; + SDL.DOMEventToSDLEvent['joystick_button_down'] = 0x603 /* SDL_JOYBUTTONDOWN */; + SDL.DOMEventToSDLEvent['joystick_button_up'] = 0x604 /* SDL_JOYBUTTONUP */; return 0; // success }, @@ -1189,6 +1324,11 @@ var LibrarySDL = { }, SDL_PollEvent: function(ptr) { + if (SDL.initFlags & 0x200 && SDL.joystickEventState) { + // If SDL_INIT_JOYSTICK was supplied AND the joystick system is configured + // to automatically query for events, query for joystick events. + SDL.queryJoysticks(); + } if (SDL.events.length === 0) return 0; if (ptr) { SDL.makeCEvent(SDL.events.shift(), ptr); @@ -2390,37 +2530,103 @@ var LibrarySDL = { // Joysticks - SDL_NumJoysticks: function() { return 0; }, + SDL_NumJoysticks: function() { + var count = 0; + var gamepads = SDL.getGamepads(); + // The length is not the number of gamepads; check which ones are defined. + for (var i = 0; i < gamepads.length; i++) { + if (gamepads[i] !== undefined) count++; + } + return count; + }, - SDL_JoystickName: function(deviceIndex) { return 0; }, + SDL_JoystickName: function(deviceIndex) { + var gamepad = SDL.getGamepad(deviceIndex); + if (gamepad) { + var name = gamepad.id; + if (SDL.joystickNamePool.hasOwnProperty(name)) { + return SDL.joystickNamePool[name]; + } + return SDL.joystickNamePool[name] = allocate(intArrayFromString(name), 'i8', ALLOC_NORMAL); + } + return 0; + }, - SDL_JoystickOpen: function(deviceIndex) { return 0; }, + SDL_JoystickOpen: function(deviceIndex) { + var gamepad = SDL.getGamepad(deviceIndex); + if (gamepad) { + // Use this as a unique 'pointer' for this joystick. + var joystick = deviceIndex+1; + SDL.recordJoystickState(joystick, gamepad); + return joystick; + } + return 0; + }, - SDL_JoystickOpened: function(deviceIndex) { return 0; }, + SDL_JoystickOpened: function(deviceIndex) { + return SDL.lastJoystickState.hasOwnProperty(deviceIndex+1) ? 1 : 0; + }, - SDL_JoystickIndex: function(joystick) { return 0; }, + SDL_JoystickIndex: function(joystick) { + // joystick pointers are simply the deviceIndex+1. + return joystick - 1; + }, - SDL_JoystickNumAxes: function(joystick) { return 0; }, + SDL_JoystickNumAxes: function(joystick) { + var gamepad = SDL.getGamepad(joystick - 1); + if (gamepad) { + return gamepad.axes.length; + } + return 0; + }, SDL_JoystickNumBalls: function(joystick) { return 0; }, SDL_JoystickNumHats: function(joystick) { return 0; }, - SDL_JoystickNumButtons: function(joystick) { return 0; }, + SDL_JoystickNumButtons: function(joystick) { + var gamepad = SDL.getGamepad(joystick - 1); + if (gamepad) { + return gamepad.buttons.length; + } + return 0; + }, - SDL_JoystickUpdate: function() {}, + SDL_JoystickUpdate: function() { + SDL.queryJoysticks(); + }, - SDL_JoystickEventState: function(state) { return 0; }, + SDL_JoystickEventState: function(state) { + if (state < 0) { + // SDL_QUERY: Return current state. + return SDL.joystickEventState; + } + return SDL.joystickEventState = state; + }, - SDL_JoystickGetAxis: function(joystick, axis) { return 0; }, + SDL_JoystickGetAxis: function(joystick, axis) { + var gamepad = SDL.getGamepad(joystick - 1); + if (gamepad && gamepad.axes.length > axis) { + return SDL.joystickAxisValueConversion(gamepad.axes[axis]); + } + return 0; + }, SDL_JoystickGetHat: function(joystick, hat) { return 0; }, SDL_JoystickGetBall: function(joystick, ball, dxptr, dyptr) { return -1; }, - SDL_JoystickGetButton: function(joystick, button) { return 0; }, + SDL_JoystickGetButton: function(joystick, button) { + var gamepad = SDL.getGamepad(joystick - 1); + if (gamepad && gamepad.buttons.length > button) { + return SDL.getJoystickButtonState(gamepad.buttons[button]) ? 1 : 0; + } + return 0; + }, - SDL_JoystickClose: function(joystick) {}, + SDL_JoystickClose: function(joystick) { + delete SDL.lastJoystickState[joystick]; + }, // Misc diff --git a/src/modules.js b/src/modules.js index 854575e0..673e0662 100644 --- a/src/modules.js +++ b/src/modules.js @@ -18,7 +18,7 @@ var LLVM = { PHI_REACHERS: set('branch', 'switch', 'invoke', 'indirectbr'), EXTENDS: set('sext', 'zext'), COMPS: set('icmp', 'fcmp'), - CONVERSIONS: set('inttoptr', 'ptrtoint', 'uitofp', 'sitofp', 'fptosi', 'fptoui'), + CONVERSIONS: set('inttoptr', 'ptrtoint', 'uitofp', 'sitofp', 'fptosi', 'fptoui', 'fpext', 'fptrunc'), INTRINSICS_32: set('_llvm_memcpy_p0i8_p0i8_i64', '_llvm_memmove_p0i8_p0i8_i64', '_llvm_memset_p0i8_i64'), // intrinsics that need args converted to i32 in USE_TYPED_ARRAYS == 2 }; LLVM.GLOBAL_MODIFIERS = set(keys(LLVM.LINKAGES).concat(['constant', 'global', 'hidden'])); @@ -253,13 +253,32 @@ var Functions = { aliases: {}, // in shared modules (MAIN_MODULE or SHARED_MODULE), a list of aliases for functions that have them + getSignatureLetter: function(type) { + switch(type) { + case 'float': return 'f'; + case 'double': return 'd'; + case 'void': return 'v'; + default: return 'i'; + } + }, + + getSignatureType: function(letter) { + switch(letter) { + case 'v': return 'void'; + case 'i': return 'i32'; + case 'f': return 'float'; + case 'd': return 'double'; + default: throw 'what is this sig? ' + sig; + } + }, + getSignature: function(returnType, argTypes, hasVarArgs) { - var sig = returnType == 'void' ? 'v' : (isIntImplemented(returnType) ? 'i' : 'f'); + var sig = Functions.getSignatureLetter(returnType); for (var i = 0; i < argTypes.length; i++) { var type = argTypes[i]; if (!type) break; // varargs if (type in Runtime.FLOAT_TYPES) { - sig += 'f'; + sig += Functions.getSignatureLetter(type); } else { var chunks = getNumIntChunks(type); for (var j = 0; j < chunks; j++) sig += 'i'; @@ -269,15 +288,6 @@ var Functions = { return sig; }, - getSignatureReturnType: function(sig) { - switch(sig[0]) { - case 'v': return 'void'; - case 'i': return 'i32'; - case 'f': return 'double'; - default: throw 'what is this sig? ' + sig; - } - }, - // Mark a function as needing indexing. Python will coordinate them all getIndex: function(ident, sig) { var ret; @@ -331,7 +341,7 @@ var Functions = { // Resolve multi-level aliases all the way down while (1) { var varData = Variables.globals[table[i]]; - if (!(varData && varData.resolvedAlias && varData.resolvedAlias.indexOf('FUNCTION_TABLE_OFFSET') < 0)) break; + if (!(varData && varData.resolvedAlias && !/(FUNCTION_TABLE_OFFSET|F_BASE_)/.test(varData.resolvedAlias))) break; table[i] = table[+varData.resolvedAlias || eval(varData.resolvedAlias)]; // might need to eval to turn (6) into 6 } // Resolve library aliases @@ -350,17 +360,15 @@ var Functions = { if (!wrapped[curr]) { var args = '', arg_coercions = '', call = short + '(', retPre = '', retPost = ''; if (t[0] != 'v') { - if (t[0] == 'i') { - retPre = 'return '; - retPost = '|0'; - } else { - retPre = 'return +'; - } + var temp = asmFFICoercion('X', Functions.getSignatureType(t[0])).split('X'); + retPre = 'return ' + temp[0]; + retPost = temp[1]; } for (var j = 1; j < t.length; j++) { args += (j > 1 ? ',' : '') + 'a' + j; - arg_coercions += 'a' + j + '=' + asmCoercion('a' + j, t[j] != 'i' ? 'float' : 'i32') + ';'; - call += (j > 1 ? ',' : '') + asmCoercion('a' + j, t[j] != 'i' ? 'float' : 'i32'); + var type = Functions.getSignatureType(t[j]); + arg_coercions += 'a' + j + '=' + asmCoercion('a' + j, type) + ';'; + call += (j > 1 ? ',' : '') + asmCoercion('a' + j, type === 'float' ? 'double' : type); // ffi arguments must be doubles if they are floats } call += ')'; if (short == '_setjmp') printErr('WARNING: setjmp used via a function pointer. If this is for libc setjmp (not something of your own with the same name), it will break things'); diff --git a/src/parseTools.js b/src/parseTools.js index c4f28184..08cf9b60 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -629,6 +629,8 @@ function cleanSegment(segment) { var MATHOPS = set(['add', 'sub', 'sdiv', 'udiv', 'mul', 'icmp', 'zext', 'urem', 'srem', 'fadd', 'fsub', 'fmul', 'fdiv', 'fcmp', 'frem', 'uitofp', 'sitofp', 'fpext', 'fptrunc', 'fptoui', 'fptosi', 'trunc', 'sext', 'select', 'shl', 'shr', 'ashl', 'ashr', 'lshr', 'lshl', 'xor', 'or', 'and', 'ptrtoint', 'inttoptr']); +var JS_MATH_BUILTINS = set(['Math_sin', 'Math_cos', 'Math_tan', 'Math_asin', 'Math_acos', 'Math_atan', 'Math_ceil', 'Math_floor', 'Math_exp', 'Math_log', 'Math_sqrt']); + var PARSABLE_LLVM_FUNCTIONS = set('getelementptr', 'bitcast'); mergeInto(PARSABLE_LLVM_FUNCTIONS, MATHOPS); @@ -788,8 +790,8 @@ function splitI64(value, floatConversion) { var high = makeInlineCalculation( asmCoercion('Math_abs(VALUE)', 'double') + ' >= ' + asmEnsureFloat('1', 'double') + ' ? ' + '(VALUE > ' + asmEnsureFloat('0', 'double') + ' ? ' + - asmCoercion('Math_min(' + asmCoercion('Math_floor((VALUE)/' + asmEnsureFloat(4294967296, 'float') + ')', 'double') + ', ' + asmEnsureFloat(4294967295, 'float') + ')', 'i32') + '>>>0' + - ' : ' + asmFloatToInt(asmCoercion('Math_ceil((VALUE - +((' + asmFloatToInt('VALUE') + ')>>>0))/' + asmEnsureFloat(4294967296, 'float') + ')', 'double')) + '>>>0' + + 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, @@ -981,6 +983,12 @@ function parseLLVMString(str) { return ret; } +function expandLLVMString(str) { + return str.replace(/\\../g, function(m) { + return String.fromCharCode(parseInt(m.substr(1), '16')); + }); +} + function getLabelIds(labels) { return labels.map(function(label) { return label.ident }); } @@ -1161,32 +1169,37 @@ function makeVarDef(js) { return js; } +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.indexOf('.') >= 0 || /[IN]/.test(value)) && (!RUNNING_JS_OPTS || Math.abs(value) >= 1)) return value; + if (RUNNING_JS_OPTS) return '(+' + value + ')'; // JS optimizer will run, we must do +x, and it will be corrected later + var e = value.indexOf('e'); + if (e < 0) return value + '.0'; + return value.substr(0, e) + '.0' + value.substr(e); +} + function asmEnsureFloat(value, type) { // ensures that a float type has either 5.5 (clearly a float) or +5 (float due to asm coercion) if (!ASM_JS) return value; - // coerce if missing a '.', or if smaller than 1, so could be 1e-5 which has no . - if (type in Runtime.FLOAT_TYPES && isNumber(value) && (value.toString().indexOf('.') < 0 || Math.abs(value) < 1)) { - if (RUNNING_JS_OPTS) { - return '(+' + value + ')'; // JS optimizer will run, we must do +x, and it will be corrected later - } else { - // ensure a . - value = value.toString(); - if (value.indexOf('.') >= 0 || /[IN]/.test(value)) return value; // if already dotted, or Infinity or NaN, nothing to do here - var e = value.indexOf('e'); - if (e < 0) return value + '.0'; - return value.substr(0, e) + '.0' + value.substr(e); - } + if (!isNumber(value)) return value; + if (PRECISE_F32 && 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 (type in Runtime.FLOAT_TYPES) { + return ensureDot(value); } else { return value; } } -function asmInitializer(type, impl) { +function asmInitializer(type) { if (type in Runtime.FLOAT_TYPES) { - if (RUNNING_JS_OPTS) { - return '+0'; - } else { - return '.0'; - } + if (PRECISE_F32 && type === 'float') return 'Math_fround(0)'; + return RUNNING_JS_OPTS ? '+0' : '.0'; } else { return '0'; } @@ -1207,7 +1220,11 @@ function asmCoercion(value, type, signedness) { value = '(' + value + ')|0'; } } - return '(+(' + value + '))'; + if (PRECISE_F32 && type === 'float') { + return 'Math_fround(' + value + ')'; + } else { + return '(+(' + value + '))'; + } } } else { return '((' + value + ')|0)'; @@ -2041,7 +2058,7 @@ function makeSignOp(value, type, op, force, ignore) { if (isPointerType(type)) type = 'i32'; // Pointers are treated as 32-bit ints if (!value) return value; var bits, full; - if (type in Runtime.INT_TYPES) { + if (type[0] === 'i') { bits = parseInt(type.substr(1)); full = op + 'Sign(' + value + ', ' + bits + ', ' + Math.floor(ignore || correctSpecificSign()) + ')'; // Always sign/unsign constants at compile time, regardless of CHECK/CORRECT @@ -2050,7 +2067,7 @@ function makeSignOp(value, type, op, force, ignore) { } } if ((ignore || !correctSigns()) && !CHECK_SIGNS && !force) return value; - if (type in Runtime.INT_TYPES) { + if (type[0] === 'i') { // this is an integer, but not a number (or we would have already handled it) // shortcuts if (!CHECK_SIGNS || ignore) { @@ -2123,14 +2140,14 @@ function makeRounding(value, bits, signed, floatConversion) { } } -function makeIsNaN(value) { - if (ASM_JS) return makeInlineCalculation('((VALUE) != (VALUE))', value, 'tempDouble'); +function makeIsNaN(value, type) { + if (ASM_JS) return makeInlineCalculation('((VALUE) != (VALUE))', value, type === 'float' ? 'tempFloat' : 'tempDouble'); return 'isNaN(' + value + ')'; } function makeFloat(value, type) { - if (TO_FLOAT32 && type == 'float') { - return 'Math_toFloat32(' + value + ')'; + if (PRECISE_F32 && type == 'float') { + return 'Math_fround(' + value + ')'; } return value; } @@ -2247,8 +2264,8 @@ function processMathop(item) { case 'lshr': { throw 'shifts should have been legalized!'; } - case 'uitofp': case 'sitofp': return RuntimeGenerator.makeBigInt(low1, high1, op[0] == 'u'); - case 'fptoui': case 'fptosi': return finish(splitI64(idents[0], true)); + case 'uitofp': case 'sitofp': return makeFloat(RuntimeGenerator.makeBigInt(low1, high1, op[0] == 'u'), item.type); + case 'fptoui': case 'fptosi': return finish(splitI64(asmCoercion(idents[0], 'double'), true)); // coerce to double before conversion to i64 case 'icmp': { switch (variant) { case 'uge': return '((' + high1 + '>>>0) >= (' + high2 + '>>>0)) & ((((' + high1 + '>>>0) > (' + high2 + '>>>0)) | ' + @@ -2432,12 +2449,17 @@ function processMathop(item) { case 'fdiv': return makeFloat(getFastValue(idents[0], '/', idents[1], item.type), item.type); case 'fmul': return makeFloat(getFastValue(idents[0], '*', idents[1], item.type), item.type); case 'frem': return makeFloat(getFastValue(idents[0], '%', idents[1], item.type), item.type); - case 'uitofp': case 'sitofp': return asmCoercion(idents[0], 'double', op[0]); + case 'uitofp': case 'sitofp': return asmCoercion(idents[0], item.type, op[0]); case 'fptoui': case 'fptosi': return makeRounding(idents[0], bitsLeft, op === 'fptosi', true); // TODO: We sometimes generate false instead of 0, etc., in the *cmps. It seemed slightly faster before, but worth rechecking // Note that with typed arrays, these become 0 when written. So that is a potential difference with non-typed array runs. case 'icmp': { + // unsigned coercions can be (X&Y), which is not a valid asm coercion for comparisons + if (ASM_JS && variant[0] === 'u') { + if (idents[0].indexOf('>>>') < 0) idents[0] = '((' + idents[0] + ')>>>0)'; + if (idents[1].indexOf('>>>') < 0) idents[1] = '((' + idents[1] + ')>>>0)'; + } switch (variant) { case 'uge': case 'sge': return idents[0] + '>=' + idents[1]; case 'ule': case 'sle': return idents[0] + '<=' + idents[1]; @@ -2464,8 +2486,8 @@ function processMathop(item) { case 'ult': case 'olt': return idents[0] + '<' + idents[1]; case 'une': case 'one': return idents[0] + '!=' + idents[1]; case 'ueq': case 'oeq': return idents[0] + '==' + idents[1]; - case 'ord': return '!' + makeIsNaN(idents[0]) + '&!' + makeIsNaN(idents[1]); - case 'uno': return makeIsNaN(idents[0]) + '|' + makeIsNaN(idents[1]); + case 'ord': return '!' + makeIsNaN(idents[0], paramTypes[0]) + '&!' + makeIsNaN(idents[1], paramTypes[0]); + case 'uno': return makeIsNaN(idents[0], paramTypes[0]) + '|' + makeIsNaN(idents[1], paramTypes[0]); case 'true': return '1'; default: throw 'Unknown fcmp variant: ' + variant; } @@ -2479,8 +2501,15 @@ function processMathop(item) { } // otherwise, fall through } - case 'fpext': case 'sext': return idents[0]; - case 'fptrunc': return idents[0]; + case 'sext': return idents[0]; + case 'fpext': { + if (PRECISE_F32) return '+(' + idents[0] + ')'; + return idents[0]; + } + case 'fptrunc': { + if (PRECISE_F32) return 'Math_fround(' + idents[0] + ')'; + return idents[0]; + } case 'select': return '(' + idents[0] + '?' + asmEnsureFloat(idents[1], item.type) + ':' + asmEnsureFloat(idents[2], item.type) + ')'; case 'ptrtoint': case 'inttoptr': { var ret = ''; @@ -2671,3 +2700,14 @@ function ensureVector(ident, base) { return ident == 0 ? base + '32x4.zero()' : ident; } +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 (PRECISE_F32 && type === 'float') value = asmCoercion(value, 'float'); + return value; +} + diff --git a/src/preamble.js b/src/preamble.js index c88e4052..27016c14 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -646,6 +646,10 @@ function demangle(func) { if (func[0] !== '_') return func; if (func[1] !== '_') return func; // C function if (func[2] !== 'Z') return func; + switch (func[3]) { + case 'n': return 'operator new()'; + case 'd': return 'operator delete()'; + } var i = 3; // params, etc. var basicTypes = { @@ -678,7 +682,7 @@ function demangle(func) { var subs = []; function parseNested() { i++; - if (func[i] === 'K') i++; + if (func[i] === 'K') i++; // ignore const var parts = []; while (func[i] !== 'E') { if (func[i] === 'S') { // substitution @@ -689,6 +693,11 @@ function demangle(func) { i = next+1; continue; } + if (func[i] === 'C') { // constructor + parts.push(parts[parts.length-1]); + i += 2; + continue; + } var size = parseInt(func.substr(i)); var pre = size.toString().length; if (!size || !pre) { i--; break; } // counter i++ below us @@ -700,6 +709,7 @@ function demangle(func) { i++; // skip E return parts; } + var first = true; function parse(rawList, limit, allowVoid) { // main parser limit = limit || Infinity; var ret = '', list = []; @@ -707,21 +717,22 @@ function demangle(func) { return '(' + list.join(', ') + ')'; } var name; - if (func[i] !== 'N') { + if (func[i] === 'N') { + // namespaced N-E + name = parseNested().join('::'); + limit--; + if (limit === 0) return rawList ? [name] : name; + } else { // not namespaced - if (func[i] === 'K') i++; + if (func[i] === 'K' || (first && func[i] === 'L')) i++; // ignore const and first 'L' var size = parseInt(func.substr(i)); if (size) { var pre = size.toString().length; name = func.substr(i + pre, size); i += pre + size; } - } else { - // namespaced N-E - name = parseNested().join('::'); - limit--; - if (limit === 0) return rawList ? [name] : name; } + first = false; if (func[i] === 'I') { i++; var iList = parse(true); @@ -1074,11 +1085,16 @@ Math['imul'] = function imul(a, b) { #endif Math.imul = Math['imul']; -#if TO_FLOAT32 -if (!Math['toFloat32']) Math['toFloat32'] = function toFloat32(x) { - return x; -}; -Math.toFloat32 = Math['toFloat32']; +#if PRECISE_F32 +#if PRECISE_F32 == 1 +if (!Math['fround']) { + var froundBuffer = new Float32Array(1); + Math['fround'] = function(x) { froundBuffer[0] = x; return froundBuffer[0] }; +} +#else // 2 +if (!Math['fround']) Math['fround'] = function(x) { return x }; +#endif +Math.fround = Math['fround']; #endif var Math_abs = Math.abs; @@ -1096,7 +1112,7 @@ var Math_ceil = Math.ceil; var Math_floor = Math.floor; var Math_pow = Math.pow; var Math_imul = Math.imul; -var Math_toFloat32 = Math.toFloat32; +var Math_fround = Math.fround; var Math_min = Math.min; // A counter of dependencies for calling run(). If we need to diff --git a/src/relooper/Relooper.cpp b/src/relooper/Relooper.cpp index d79dca5a..0b4284bc 100644 --- a/src/relooper/Relooper.cpp +++ b/src/relooper/Relooper.cpp @@ -541,6 +541,16 @@ void Relooper::Calculate(Block *Entry) { for (BlockSet::iterator iter = Curr->BranchesIn.begin(); iter != Curr->BranchesIn.end(); iter++) { Queue.insert(*iter); } +#if 0 + // Add elements it leads to, if they are dead ends. There is no reason not to hoist dead ends + // into loops, as it can avoid multiple entries after the loop + for (BlockBranchMap::iterator iter = Curr->BranchesOut.begin(); iter != Curr->BranchesOut.end(); iter++) { + Block *Target = iter->first; + if (Target->BranchesIn.size() <= 1 && Target->BranchesOut.size() == 0) { + Queue.insert(Target); + } + } +#endif } } assert(InnerBlocks.size() > 0); diff --git a/src/runtime.js b/src/runtime.js index 3f89dc84..786ae021 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -82,8 +82,8 @@ var RuntimeGenerator = { // Rounding is inevitable if the number is large. This is a particular problem for small negative numbers // (-1 will be rounded!), so handle negatives separately and carefully makeBigInt: function(low, high, unsigned) { - var unsignedRet = '(' + asmCoercion(makeSignOp(low, 'i32', 'un', 1, 1), 'float') + '+(' + asmCoercion(makeSignOp(high, 'i32', 'un', 1, 1), 'float') + '*' + asmEnsureFloat(4294967296, 'float') + '))'; - var signedRet = '(' + asmCoercion(makeSignOp(low, 'i32', 'un', 1, 1), 'float') + '+(' + asmCoercion(makeSignOp(high, 'i32', 're', 1, 1), 'float') + '*' + asmEnsureFloat(4294967296, 'float') + '))'; + var unsignedRet = '(' + asmCoercion(makeSignOp(low, 'i32', 'un', 1, 1), 'double') + '+(' + asmCoercion(makeSignOp(high, 'i32', 'un', 1, 1), 'double') + '*' + asmEnsureFloat(4294967296, 'double') + '))'; + var signedRet = '(' + asmCoercion(makeSignOp(low, 'i32', 'un', 1, 1), 'double') + '+(' + asmCoercion(makeSignOp(high, 'i32', 're', 1, 1), 'double') + '*' + asmEnsureFloat(4294967296, 'double') + '))'; if (typeof unsigned === 'string') return '(' + unsigned + ' ? ' + unsignedRet + ' : ' + signedRet + ')'; return unsigned ? unsignedRet : signedRet; } diff --git a/src/settings.js b/src/settings.js index 42e31b3a..bc665973 100644 --- a/src/settings.js +++ b/src/settings.js @@ -115,7 +115,13 @@ var PRECISE_I64_MATH = 1; // If enabled, i64 addition etc. is emulated - which i var PRECISE_I32_MUL = 1; // If enabled, i32 multiplication is done with full precision, which means it is // correct even if the value exceeds the JS double-integer limit of ~52 bits (otherwise, // rounding will occur above that range). -var TO_FLOAT32 = 0; // Use Math.toFloat32 +var PRECISE_F32 = 0; // 0: Use JS numbers for floating-point values. These are 64-bit and do not model C++ + // floats exactly, which are 32-bit. + // 1: Model C++ floats precisely, using Math.fround, polyfilling when necessary. This + // can be slow if the polyfill is used on heavy float32 computation. + // 2: Model C++ floats precisely using Math.fround if available in the JS engine, otherwise + // use an empty polyfill. This will have less of a speed penalty than using the full + // polyfill in cases where engine support is not present. var CLOSURE_ANNOTATIONS = 0; // If set, the generated code will be annotated for the closure // compiler. This potentially lets closure optimize the code better. @@ -384,13 +390,16 @@ var FAKE_X86_FP80 = 1; // Replaces x86_fp80 with double. This loses precision. I var GC_SUPPORT = 1; // Enables GC, see gc.h (this does not add overhead, so it is on by default) -var WARN_ON_UNDEFINED_SYMBOLS = 0; // If set to 1, we will warn on any undefined symbols that - // are not resolved by the library_*.js files. We by default - // do not warn because (1) it is normal in large projects to +var WARN_ON_UNDEFINED_SYMBOLS = 1; // If set to 1, we will warn on any undefined symbols that + // are not resolved by the library_*.js files. Note that + // it is common in large projects to // not implement everything, when you know what is not // going to actually be called (and don't want to mess with - // the existing buildsystem), and (2) functions might be - // implemented later on, say in --pre-js + // the existing buildsystem), and functions might be + // implemented later on, say in --pre-js, so you may + // want to build with -s WARN_ON_UNDEFINED_SYMBOLS=0 to + // disable the warnings if they annoy you. + // See also ERROR_ON_UNDEFINED_SYMBOLS var ERROR_ON_UNDEFINED_SYMBOLS = 0; // If set to 1, we will give a compile-time error on any // undefined symbols (see WARN_ON_UNDEFINED_SYMBOLS). @@ -422,6 +431,8 @@ var DEAD_FUNCTIONS = []; // Functions on this list are not converted to JS, and // reducing code size. // If a dead function is actually called, you will get a runtime // error. + // This can affect both functions in compiled code, and system + // library functions (e.g., you can use this to kill printf). // TODO: options to lazily load such functions var EXPLICIT_ZEXT = 0; // If 1, generate an explicit conversion of zext i1 to i32, using ?: diff --git a/src/struct_info.json b/src/struct_info.json index 5b4726e8..b91d077e 100644 --- a/src/struct_info.json +++ b/src/struct_info.json @@ -290,7 +290,11 @@ "AI_CANONNAME", "AI_PASSIVE", "NI_NAMEREQD", - "EAI_NONAME", + "EAI_NONAME", + "EAI_AGAIN", + "EAI_FAIL", + "EAI_MEMORY", + "EAI_SYSTEM", "EAI_SOCKTYPE", "EAI_BADFLAGS" ], @@ -961,6 +965,21 @@ "x", "y" ], + "SDL_JoyAxisEvent": [ + "type", + "which", + "axis", + "padding1", + "padding2", + "value" + ], + "SDL_JoyButtonEvent": [ + "type", + "which", + "button", + "state", + "padding1" + ], "SDL_ResizeEvent": [ "type", "w", diff --git a/src/utility.js b/src/utility.js index ac821a89..cd27b209 100644 --- a/src/utility.js +++ b/src/utility.js @@ -68,7 +68,7 @@ function warn(a, msg) { a = false; } if (!a) { - printErr('Warning: ' + msg); + printErr('warning: ' + msg); } } @@ -81,7 +81,7 @@ function warnOnce(a, msg) { if (!warnOnce.msgs) warnOnce.msgs = {}; if (msg in warnOnce.msgs) return; warnOnce.msgs[msg] = true; - printErr('Warning: ' + msg); + printErr('warning: ' + msg); } } @@ -89,7 +89,7 @@ var abortExecution = false; function error(msg) { abortExecution = true; - printErr('Error: ' + msg); + printErr('error: ' + msg); } function dedup(items, ident) { @@ -222,7 +222,8 @@ function mergeInto(obj, other) { } function isNumber(x) { - return x == parseFloat(x) || (typeof x == 'string' && x.match(/^-?\d+$/)); + // XXX this does not handle 0xabc123 etc. We should likely also do x == parseInt(x) (which handles that), and remove hack |// handle 0x... as well| + return x == parseFloat(x) || (typeof x == 'string' && x.match(/^-?\d+$/)) || x === 'NaN'; } function isArray(x) { diff --git a/system/include/emscripten/emscripten.h b/system/include/emscripten/emscripten.h index d30620ec..ac880981 100644 --- a/system/include/emscripten/emscripten.h +++ b/system/include/emscripten/emscripten.h @@ -26,8 +26,11 @@ extern "C" { * This also works with asm.js, as it outlines the code (it * does a function call to reach it). * - * Note: double-quotes (") are not supported, but you can use - * single-quotes (') in js anyhow. + * Notes: double-quotes (") are not supported, but you can use + * single-quotes (') in js anyhow. + * + * you can't access C variables with EM_ASM, use gcc + * inline asm for that, asm("code" : .. etc.) */ #define EM_ASM(...) emscripten_asm_const(#__VA_ARGS__) @@ -203,7 +206,7 @@ void emscripten_get_canvas_size(int *width, int *height, int *isFullscreen); * absolute time, and is only meaningful in comparison to * other calls to this function. The unit is ms. */ -float emscripten_get_now(); +double emscripten_get_now(); /* * Simple random number generation in [0, 1), maps to Math.random(). diff --git a/system/lib/libc/musl/src/regex/regcomp.c b/system/lib/libc/musl/src/regex/regcomp.c new file mode 100644 index 00000000..5cedfd52 --- /dev/null +++ b/system/lib/libc/musl/src/regex/regcomp.c @@ -0,0 +1,3352 @@ +/* + regcomp.c - TRE POSIX compatible regex compilation functions. + + Copyright (c) 2001-2009 Ville Laurikari <vl@iki.fi> + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <regex.h> +#include <limits.h> +#include <stdint.h> + +#include "tre.h" + +#include <assert.h> + +/*********************************************************************** + from tre-compile.h +***********************************************************************/ + +typedef struct { + int position; + int code_min; + int code_max; + int *tags; + int assertions; + tre_ctype_t class; + tre_ctype_t *neg_classes; + int backref; +} tre_pos_and_tags_t; + + +/*********************************************************************** + from tre-ast.c and tre-ast.h +***********************************************************************/ + +/* The different AST node types. */ +typedef enum { + LITERAL, + CATENATION, + ITERATION, + UNION +} tre_ast_type_t; + +/* Special subtypes of TRE_LITERAL. */ +#define EMPTY -1 /* Empty leaf (denotes empty string). */ +#define ASSERTION -2 /* Assertion leaf. */ +#define TAG -3 /* Tag leaf. */ +#define BACKREF -4 /* Back reference leaf. */ + +#define IS_SPECIAL(x) ((x)->code_min < 0) +#define IS_EMPTY(x) ((x)->code_min == EMPTY) +#define IS_ASSERTION(x) ((x)->code_min == ASSERTION) +#define IS_TAG(x) ((x)->code_min == TAG) +#define IS_BACKREF(x) ((x)->code_min == BACKREF) + + +/* A generic AST node. All AST nodes consist of this node on the top + level with `obj' pointing to the actual content. */ +typedef struct { + tre_ast_type_t type; /* Type of the node. */ + void *obj; /* Pointer to actual node. */ + int nullable; + int submatch_id; + int num_submatches; + int num_tags; + tre_pos_and_tags_t *firstpos; + tre_pos_and_tags_t *lastpos; +} tre_ast_node_t; + + +/* A "literal" node. These are created for assertions, back references, + tags, matching parameter settings, and all expressions that match one + character. */ +typedef struct { + long code_min; + long code_max; + int position; + tre_ctype_t class; + tre_ctype_t *neg_classes; +} tre_literal_t; + +/* A "catenation" node. These are created when two regexps are concatenated. + If there are more than one subexpressions in sequence, the `left' part + holds all but the last, and `right' part holds the last subexpression + (catenation is left associative). */ +typedef struct { + tre_ast_node_t *left; + tre_ast_node_t *right; +} tre_catenation_t; + +/* An "iteration" node. These are created for the "*", "+", "?", and "{m,n}" + operators. */ +typedef struct { + /* Subexpression to match. */ + tre_ast_node_t *arg; + /* Minimum number of consecutive matches. */ + int min; + /* Maximum number of consecutive matches. */ + int max; + /* If 0, match as many characters as possible, if 1 match as few as + possible. Note that this does not always mean the same thing as + matching as many/few repetitions as possible. */ + unsigned int minimal:1; +} tre_iteration_t; + +/* An "union" node. These are created for the "|" operator. */ +typedef struct { + tre_ast_node_t *left; + tre_ast_node_t *right; +} tre_union_t; + +static tre_ast_node_t * +tre_ast_new_node(tre_mem_t mem, tre_ast_type_t type, size_t size); + +static tre_ast_node_t * +tre_ast_new_literal(tre_mem_t mem, int code_min, int code_max, int position); + +static tre_ast_node_t * +tre_ast_new_iter(tre_mem_t mem, tre_ast_node_t *arg, int min, int max, + int minimal); + +static tre_ast_node_t * +tre_ast_new_union(tre_mem_t mem, tre_ast_node_t *left, tre_ast_node_t *right); + +static tre_ast_node_t * +tre_ast_new_catenation(tre_mem_t mem, tre_ast_node_t *left, + tre_ast_node_t *right); + + +static tre_ast_node_t * +tre_ast_new_node(tre_mem_t mem, tre_ast_type_t type, size_t size) +{ + tre_ast_node_t *node; + + node = tre_mem_calloc(mem, sizeof(*node)); + if (!node) + return NULL; + node->obj = tre_mem_calloc(mem, size); + if (!node->obj) + return NULL; + node->type = type; + node->nullable = -1; + node->submatch_id = -1; + + return node; +} + +static tre_ast_node_t * +tre_ast_new_literal(tre_mem_t mem, int code_min, int code_max, int position) +{ + tre_ast_node_t *node; + tre_literal_t *lit; + + node = tre_ast_new_node(mem, LITERAL, sizeof(tre_literal_t)); + if (!node) + return NULL; + lit = node->obj; + lit->code_min = code_min; + lit->code_max = code_max; + lit->position = position; + + return node; +} + +static tre_ast_node_t * +tre_ast_new_iter(tre_mem_t mem, tre_ast_node_t *arg, int min, int max, + int minimal) +{ + tre_ast_node_t *node; + tre_iteration_t *iter; + + node = tre_ast_new_node(mem, ITERATION, sizeof(tre_iteration_t)); + if (!node) + return NULL; + iter = node->obj; + iter->arg = arg; + iter->min = min; + iter->max = max; + iter->minimal = minimal; + node->num_submatches = arg->num_submatches; + + return node; +} + +static tre_ast_node_t * +tre_ast_new_union(tre_mem_t mem, tre_ast_node_t *left, tre_ast_node_t *right) +{ + tre_ast_node_t *node; + + node = tre_ast_new_node(mem, UNION, sizeof(tre_union_t)); + if (node == NULL) + return NULL; + ((tre_union_t *)node->obj)->left = left; + ((tre_union_t *)node->obj)->right = right; + node->num_submatches = left->num_submatches + right->num_submatches; + + return node; +} + +static tre_ast_node_t * +tre_ast_new_catenation(tre_mem_t mem, tre_ast_node_t *left, + tre_ast_node_t *right) +{ + tre_ast_node_t *node; + + node = tre_ast_new_node(mem, CATENATION, sizeof(tre_catenation_t)); + if (node == NULL) + return NULL; + ((tre_catenation_t *)node->obj)->left = left; + ((tre_catenation_t *)node->obj)->right = right; + node->num_submatches = left->num_submatches + right->num_submatches; + + return node; +} + + +/*********************************************************************** + from tre-stack.c and tre-stack.h +***********************************************************************/ + +typedef struct tre_stack_rec tre_stack_t; + +/* Creates a new stack object. `size' is initial size in bytes, `max_size' + is maximum size, and `increment' specifies how much more space will be + allocated with realloc() if all space gets used up. Returns the stack + object or NULL if out of memory. */ +static tre_stack_t * +tre_stack_new(int size, int max_size, int increment); + +/* Frees the stack object. */ +static void +tre_stack_destroy(tre_stack_t *s); + +/* Returns the current number of objects in the stack. */ +static int +tre_stack_num_objects(tre_stack_t *s); + +/* Each tre_stack_push_*(tre_stack_t *s, <type> value) function pushes + `value' on top of stack `s'. Returns REG_ESPACE if out of memory. + This tries to realloc() more space before failing if maximum size + has not yet been reached. Returns REG_OK if successful. */ +#define declare_pushf(typetag, type) \ + static reg_errcode_t tre_stack_push_ ## typetag(tre_stack_t *s, type value) + +declare_pushf(voidptr, void *); +declare_pushf(int, int); + +/* Each tre_stack_pop_*(tre_stack_t *s) function pops the topmost + element off of stack `s' and returns it. The stack must not be + empty. */ +#define declare_popf(typetag, type) \ + static type tre_stack_pop_ ## typetag(tre_stack_t *s) + +declare_popf(voidptr, void *); +declare_popf(int, int); + +/* Just to save some typing. */ +#define STACK_PUSH(s, typetag, value) \ + do \ + { \ + status = tre_stack_push_ ## typetag(s, value); \ + } \ + while (/*CONSTCOND*/0) + +#define STACK_PUSHX(s, typetag, value) \ + { \ + status = tre_stack_push_ ## typetag(s, value); \ + if (status != REG_OK) \ + break; \ + } + +#define STACK_PUSHR(s, typetag, value) \ + { \ + reg_errcode_t _status; \ + _status = tre_stack_push_ ## typetag(s, value); \ + if (_status != REG_OK) \ + return _status; \ + } + +union tre_stack_item { + void *voidptr_value; + int int_value; +}; + +struct tre_stack_rec { + int size; + int max_size; + int increment; + int ptr; + union tre_stack_item *stack; +}; + + +static tre_stack_t * +tre_stack_new(int size, int max_size, int increment) +{ + tre_stack_t *s; + + s = xmalloc(sizeof(*s)); + if (s != NULL) + { + s->stack = xmalloc(sizeof(*s->stack) * size); + if (s->stack == NULL) + { + xfree(s); + return NULL; + } + s->size = size; + s->max_size = max_size; + s->increment = increment; + s->ptr = 0; + } + return s; +} + +static void +tre_stack_destroy(tre_stack_t *s) +{ + xfree(s->stack); + xfree(s); +} + +static int +tre_stack_num_objects(tre_stack_t *s) +{ + return s->ptr; +} + +static reg_errcode_t +tre_stack_push(tre_stack_t *s, union tre_stack_item value) +{ + if (s->ptr < s->size) + { + s->stack[s->ptr] = value; + s->ptr++; + } + else + { + if (s->size >= s->max_size) + { + return REG_ESPACE; + } + else + { + union tre_stack_item *new_buffer; + int new_size; + new_size = s->size + s->increment; + if (new_size > s->max_size) + new_size = s->max_size; + new_buffer = xrealloc(s->stack, sizeof(*new_buffer) * new_size); + if (new_buffer == NULL) + { + return REG_ESPACE; + } + assert(new_size > s->size); + s->size = new_size; + s->stack = new_buffer; + tre_stack_push(s, value); + } + } + return REG_OK; +} + +#define define_pushf(typetag, type) \ + declare_pushf(typetag, type) { \ + union tre_stack_item item; \ + item.typetag ## _value = value; \ + return tre_stack_push(s, item); \ +} + +define_pushf(int, int) +define_pushf(voidptr, void *) + +#define define_popf(typetag, type) \ + declare_popf(typetag, type) { \ + return s->stack[--s->ptr].typetag ## _value; \ + } + +define_popf(int, int) +define_popf(voidptr, void *) + + +/*********************************************************************** + from tre-parse.c and tre-parse.h +***********************************************************************/ + +/* Parse context. */ +typedef struct { + /* Memory allocator. The AST is allocated using this. */ + tre_mem_t mem; + /* Stack used for keeping track of regexp syntax. */ + tre_stack_t *stack; + /* The parse result. */ + tre_ast_node_t *result; + /* The regexp to parse and its length. */ + const char *re; + /* The first character of the entire regexp. */ + const char *re_start; + /* Current submatch ID. */ + int submatch_id; + /* Current position (number of literal). */ + int position; + /* The highest back reference or -1 if none seen so far. */ + int max_backref; + /* This flag is set if the regexp uses approximate matching. */ + int have_approx; + /* Compilation flags. */ + int cflags; + /* If this flag is set the top-level submatch is not captured. */ + int nofirstsub; +} tre_parse_ctx_t; + +/* Parses a wide character regexp pattern into a syntax tree. This parser + handles both syntaxes (BRE and ERE), including the TRE extensions. */ +static reg_errcode_t +tre_parse(tre_parse_ctx_t *ctx); + + +/* + This parser is just a simple recursive descent parser for POSIX.2 + regexps. The parser supports both the obsolete default syntax and + the "extended" syntax, and some nonstandard extensions. +*/ + +/* Characters with special meanings in regexp syntax. */ +#define CHAR_PIPE '|' +#define CHAR_LPAREN '(' +#define CHAR_RPAREN ')' +#define CHAR_LBRACE '{' +#define CHAR_RBRACE '}' +#define CHAR_LBRACKET '[' +#define CHAR_RBRACKET ']' +#define CHAR_MINUS '-' +#define CHAR_STAR '*' +#define CHAR_QUESTIONMARK '?' +#define CHAR_PLUS '+' +#define CHAR_PERIOD '.' +#define CHAR_COLON ':' +#define CHAR_EQUAL '=' +#define CHAR_COMMA ',' +#define CHAR_CARET '^' +#define CHAR_DOLLAR '$' +#define CHAR_BACKSLASH '\\' +#define CHAR_HASH '#' +#define CHAR_TILDE '~' + + +/* Some macros for expanding \w, \s, etc. */ +static const struct tre_macro_struct { + const char c; + const char *expansion; +} tre_macros[] = + { {'t', "\t"}, {'n', "\n"}, {'r', "\r"}, + {'f', "\f"}, {'a', "\a"}, {'e', "\033"}, + {'w', "[[:alnum:]_]"}, {'W', "[^[:alnum:]_]"}, {'s', "[[:space:]]"}, + {'S', "[^[:space:]]"}, {'d', "[[:digit:]]"}, {'D', "[^[:digit:]]"}, + { 0, NULL } + }; + + +/* Expands a macro delimited by `regex' and `regex_end' to `buf', which + must have at least `len' items. Sets buf[0] to zero if the there + is no match in `tre_macros'. */ +static const char * +tre_expand_macro(const char *regex) +{ + int i; + + if (!*regex) + return 0; + + for (i = 0; tre_macros[i].expansion && tre_macros[i].c != *regex; i++); + return tre_macros[i].expansion; +} + +static reg_errcode_t +tre_new_item(tre_mem_t mem, int min, int max, int *i, int *max_i, + tre_ast_node_t ***items) +{ + reg_errcode_t status; + tre_ast_node_t **array = *items; + /* Allocate more space if necessary. */ + if (*i >= *max_i) + { + tre_ast_node_t **new_items; + /* If the array is already 1024 items large, give up -- there's + probably an error in the regexp (e.g. not a '\0' terminated + string and missing ']') */ + if (*max_i > 1024) + return REG_ESPACE; + *max_i *= 2; + new_items = xrealloc(array, sizeof(*items) * *max_i); + if (new_items == NULL) + return REG_ESPACE; + *items = array = new_items; + } + array[*i] = tre_ast_new_literal(mem, min, max, -1); + status = array[*i] == NULL ? REG_ESPACE : REG_OK; + (*i)++; + return status; +} + + +static int +tre_compare_items(const void *a, const void *b) +{ + const tre_ast_node_t *node_a = *(tre_ast_node_t * const *)a; + const tre_ast_node_t *node_b = *(tre_ast_node_t * const *)b; + tre_literal_t *l_a = node_a->obj, *l_b = node_b->obj; + int a_min = l_a->code_min, b_min = l_b->code_min; + + if (a_min < b_min) + return -1; + else if (a_min > b_min) + return 1; + else + return 0; +} + +/* Maximum number of character classes that can occur in a negated bracket + expression. */ +#define MAX_NEG_CLASSES 64 + +/* Maximum length of character class names. */ +#define MAX_CLASS_NAME + +static reg_errcode_t +tre_parse_bracket_items(tre_parse_ctx_t *ctx, int negate, + tre_ctype_t neg_classes[], int *num_neg_classes, + tre_ast_node_t ***items, int *num_items, + int *items_size) +{ + const char *re = ctx->re; + reg_errcode_t status = REG_OK; + tre_ctype_t class = (tre_ctype_t)0; + int i = *num_items; + int max_i = *items_size; + int skip; + + /* Build an array of the items in the bracket expression. */ + while (status == REG_OK) + { + skip = 0; + if (!*re) + { + status = REG_EBRACK; + } + else if (*re == CHAR_RBRACKET && re > ctx->re) + { + re++; + break; + } + else + { + tre_cint_t min = 0, max = 0; + wchar_t wc; + int clen = mbtowc(&wc, re, -1); + + if (clen<0) clen=1, wc=WEOF; + + class = (tre_ctype_t)0; + if (*(re + clen) == CHAR_MINUS && *(re + clen + 1) != CHAR_RBRACKET) + { + min = wc; + re += clen+1; + clen = mbtowc(&wc, re, -1); + if (clen<0) clen=1, wc=WEOF; + max = wc; + re += clen; + /* XXX - Should use collation order instead of encoding values + in character ranges. */ + if (min > max) + status = REG_ERANGE; + } + else if (*re == CHAR_LBRACKET && *(re + 1) == CHAR_PERIOD) + status = REG_ECOLLATE; + else if (*re == CHAR_LBRACKET && *(re + 1) == CHAR_EQUAL) + status = REG_ECOLLATE; + else if (*re == CHAR_LBRACKET && *(re + 1) == CHAR_COLON) + { + char tmp_str[64]; + const char *endptr = re + 2; + int len; + while (*endptr && *endptr != CHAR_COLON) + endptr++; + if (*endptr) + { + len = MIN(endptr - re - 2, 63); + strncpy(tmp_str, re + 2, len); + tmp_str[len] = '\0'; + class = tre_ctype(tmp_str); + if (!class) + status = REG_ECTYPE; + re = endptr + 2; + } + else + status = REG_ECTYPE; + min = 0; + max = TRE_CHAR_MAX; + } + else + { + if (*re == CHAR_MINUS && *(re + 1) != CHAR_RBRACKET + && ctx->re != re) + /* Two ranges are not allowed to share and endpoint. */ + status = REG_ERANGE; + min = max = wc; + re += clen; + } + + if (status != REG_OK) + break; + + if (class && negate) + if (*num_neg_classes >= MAX_NEG_CLASSES) + status = REG_ESPACE; + else + neg_classes[(*num_neg_classes)++] = class; + else if (!skip) + { + status = tre_new_item(ctx->mem, min, max, &i, &max_i, items); + if (status != REG_OK) + break; + ((tre_literal_t*)((*items)[i-1])->obj)->class = class; + } + + /* Add opposite-case counterpoints if REG_ICASE is present. + This is broken if there are more than two "same" characters. */ + if (ctx->cflags & REG_ICASE && !class && status == REG_OK && !skip) + { + tre_cint_t cmin, ccurr; + + while (min <= max) + { + if (tre_islower(min)) + { + cmin = ccurr = tre_toupper(min++); + while (tre_islower(min) && tre_toupper(min) == ccurr + 1 + && min <= max) + ccurr = tre_toupper(min++); + status = tre_new_item(ctx->mem, cmin, ccurr, + &i, &max_i, items); + } + else if (tre_isupper(min)) + { + cmin = ccurr = tre_tolower(min++); + while (tre_isupper(min) && tre_tolower(min) == ccurr + 1 + && min <= max) + ccurr = tre_tolower(min++); + status = tre_new_item(ctx->mem, cmin, ccurr, + &i, &max_i, items); + } + else min++; + if (status != REG_OK) + break; + } + if (status != REG_OK) + break; + } + } + } + *num_items = i; + *items_size = max_i; + ctx->re = re; + return status; +} + +static reg_errcode_t +tre_parse_bracket(tre_parse_ctx_t *ctx, tre_ast_node_t **result) +{ + tre_ast_node_t *node = NULL; + int negate = 0; + reg_errcode_t status = REG_OK; + tre_ast_node_t **items, *u, *n; + int i = 0, j, max_i = 32, curr_max, curr_min; + tre_ctype_t neg_classes[MAX_NEG_CLASSES]; + int num_neg_classes = 0; + + /* Start off with an array of `max_i' elements. */ + items = xmalloc(sizeof(*items) * max_i); + if (items == NULL) + return REG_ESPACE; + + if (*ctx->re == CHAR_CARET) + { + negate = 1; + ctx->re++; + } + + status = tre_parse_bracket_items(ctx, negate, neg_classes, &num_neg_classes, + &items, &i, &max_i); + + if (status != REG_OK) + goto parse_bracket_done; + + /* Sort the array if we need to negate it. */ + if (negate) + qsort(items, (unsigned)i, sizeof(*items), tre_compare_items); + + curr_max = curr_min = 0; + /* Build a union of the items in the array, negated if necessary. */ + for (j = 0; j < i && status == REG_OK; j++) + { + int min, max; + tre_literal_t *l = items[j]->obj; + min = l->code_min; + max = l->code_max; + + if (negate) + { + if (min < curr_max) + { + /* Overlap. */ + curr_max = MAX(max + 1, curr_max); + l = NULL; + } + else + { + /* No overlap. */ + curr_max = min - 1; + if (curr_max >= curr_min) + { + l->code_min = curr_min; + l->code_max = curr_max; + } + else + { + l = NULL; + } + curr_min = curr_max = max + 1; + } + } + + if (l != NULL) + { + int k; + l->position = ctx->position; + if (num_neg_classes > 0) + { + l->neg_classes = tre_mem_alloc(ctx->mem, + (sizeof(l->neg_classes) + * (num_neg_classes + 1))); + if (l->neg_classes == NULL) + { + status = REG_ESPACE; + break; + } + for (k = 0; k < num_neg_classes; k++) + l->neg_classes[k] = neg_classes[k]; + l->neg_classes[k] = (tre_ctype_t)0; + } + else + l->neg_classes = NULL; + if (node == NULL) + node = items[j]; + else + { + u = tre_ast_new_union(ctx->mem, node, items[j]); + if (u == NULL) + status = REG_ESPACE; + node = u; + } + } + } + + if (status != REG_OK) + goto parse_bracket_done; + + if (negate) + { + int k; + n = tre_ast_new_literal(ctx->mem, curr_min, TRE_CHAR_MAX, ctx->position); + if (n == NULL) + status = REG_ESPACE; + else + { + tre_literal_t *l = n->obj; + if (num_neg_classes > 0) + { + l->neg_classes = tre_mem_alloc(ctx->mem, + (sizeof(l->neg_classes) + * (num_neg_classes + 1))); + if (l->neg_classes == NULL) + { + status = REG_ESPACE; + goto parse_bracket_done; + } + for (k = 0; k < num_neg_classes; k++) + l->neg_classes[k] = neg_classes[k]; + l->neg_classes[k] = (tre_ctype_t)0; + } + else + l->neg_classes = NULL; + if (node == NULL) + node = n; + else + { + u = tre_ast_new_union(ctx->mem, node, n); + if (u == NULL) + status = REG_ESPACE; + node = u; + } + } + } + + if (status != REG_OK) + goto parse_bracket_done; + +#ifdef TRE_DEBUG + tre_ast_print(node); +#endif /* TRE_DEBUG */ + + parse_bracket_done: + xfree(items); + ctx->position++; + *result = node; + return status; +} + + +/* Parses a positive decimal integer. Returns -1 if the string does not + contain a valid number. */ +static int +tre_parse_int(const char **regex) +{ + int num = -1; + const char *r = *regex; + while (*r-'0'<10U) + { + if (num < 0) + num = 0; + num = num * 10 + *r - '0'; + r++; + } + *regex = r; + return num; +} + + +static reg_errcode_t +tre_parse_bound(tre_parse_ctx_t *ctx, tre_ast_node_t **result) +{ + int min, max; + const char *r = ctx->re; + int minimal = 0; + + /* Parse number (minimum repetition count). */ + min = -1; + if (*r >= '0' && *r <= '9') { + min = tre_parse_int(&r); + } + + /* Parse comma and second number (maximum repetition count). */ + max = min; + if (*r == CHAR_COMMA) + { + r++; + max = tre_parse_int(&r); + } + + /* Check that the repeat counts are sane. */ + if ((max >= 0 && min > max) || max > RE_DUP_MAX) + return REG_BADBR; + + /* Missing }. */ + if (!*r) + return REG_EBRACE; + + /* Empty contents of {}. */ + if (r == ctx->re) + return REG_BADBR; + + /* Parse the ending '}' or '\}'.*/ + if (ctx->cflags & REG_EXTENDED) + { + if (*r != CHAR_RBRACE) + return REG_BADBR; + r++; + } + else + { + if (*r != CHAR_BACKSLASH || *(r + 1) != CHAR_RBRACE) + return REG_BADBR; + r += 2; + } + + /* Create the AST node(s). */ + if (min == 0 && max == 0) + { + *result = tre_ast_new_literal(ctx->mem, EMPTY, -1, -1); + if (*result == NULL) + return REG_ESPACE; + } + else + { + if (min < 0 && max < 0) + /* Only approximate parameters set, no repetitions. */ + min = max = 1; + + *result = tre_ast_new_iter(ctx->mem, *result, min, max, minimal); + if (!*result) + return REG_ESPACE; + } + + ctx->re = r; + return REG_OK; +} + +typedef enum { + PARSE_RE = 0, + PARSE_ATOM, + PARSE_MARK_FOR_SUBMATCH, + PARSE_BRANCH, + PARSE_PIECE, + PARSE_CATENATION, + PARSE_POST_CATENATION, + PARSE_UNION, + PARSE_POST_UNION, + PARSE_POSTFIX, + PARSE_RESTORE_CFLAGS +} tre_parse_re_stack_symbol_t; + + +static reg_errcode_t +tre_parse(tre_parse_ctx_t *ctx) +{ + tre_ast_node_t *result = NULL; + tre_parse_re_stack_symbol_t symbol; + reg_errcode_t status = REG_OK; + tre_stack_t *stack = ctx->stack; + int bottom = tre_stack_num_objects(stack); + int depth = 0; + wchar_t wc; + int clen; + + if (!ctx->nofirstsub) + { + STACK_PUSH(stack, int, ctx->submatch_id); + STACK_PUSH(stack, int, PARSE_MARK_FOR_SUBMATCH); + ctx->submatch_id++; + } + STACK_PUSH(stack, int, PARSE_RE); + ctx->re_start = ctx->re; + + + /* The following is basically just a recursive descent parser. I use + an explicit stack instead of recursive functions mostly because of + two reasons: compatibility with systems which have an overflowable + call stack, and efficiency (both in lines of code and speed). */ + while (tre_stack_num_objects(stack) > bottom && status == REG_OK) + { + if (status != REG_OK) + break; + symbol = tre_stack_pop_int(stack); + switch (symbol) + { + case PARSE_RE: + /* Parse a full regexp. A regexp is one or more branches, + separated by the union operator `|'. */ + if (ctx->cflags & REG_EXTENDED) + STACK_PUSHX(stack, int, PARSE_UNION); + STACK_PUSHX(stack, int, PARSE_BRANCH); + break; + + case PARSE_BRANCH: + /* Parse a branch. A branch is one or more pieces, concatenated. + A piece is an atom possibly followed by a postfix operator. */ + STACK_PUSHX(stack, int, PARSE_CATENATION); + STACK_PUSHX(stack, int, PARSE_PIECE); + break; + + case PARSE_PIECE: + /* Parse a piece. A piece is an atom possibly followed by one + or more postfix operators. */ + STACK_PUSHX(stack, int, PARSE_POSTFIX); + STACK_PUSHX(stack, int, PARSE_ATOM); + break; + + case PARSE_CATENATION: + /* If the expression has not ended, parse another piece. */ + { + tre_char_t c; + if (!*ctx->re) + break; + c = *ctx->re; + if (ctx->cflags & REG_EXTENDED && c == CHAR_PIPE) + break; + if ((ctx->cflags & REG_EXTENDED + && c == CHAR_RPAREN && depth > 0) + || (!(ctx->cflags & REG_EXTENDED) + && (c == CHAR_BACKSLASH + && *(ctx->re + 1) == CHAR_RPAREN))) + { + if (!(ctx->cflags & REG_EXTENDED) && depth == 0) + status = REG_EPAREN; + depth--; + if (!(ctx->cflags & REG_EXTENDED)) + ctx->re += 2; + break; + } + + { + /* Default case, left associative concatenation. */ + STACK_PUSHX(stack, int, PARSE_CATENATION); + STACK_PUSHX(stack, voidptr, result); + STACK_PUSHX(stack, int, PARSE_POST_CATENATION); + STACK_PUSHX(stack, int, PARSE_PIECE); + } + break; + } + + case PARSE_POST_CATENATION: + { + tre_ast_node_t *tree = tre_stack_pop_voidptr(stack); + tre_ast_node_t *tmp_node; + tmp_node = tre_ast_new_catenation(ctx->mem, tree, result); + if (!tmp_node) + return REG_ESPACE; + result = tmp_node; + break; + } + + case PARSE_UNION: + switch (*ctx->re) + { + case CHAR_PIPE: + STACK_PUSHX(stack, int, PARSE_UNION); + STACK_PUSHX(stack, voidptr, result); + STACK_PUSHX(stack, int, PARSE_POST_UNION); + STACK_PUSHX(stack, int, PARSE_BRANCH); + ctx->re++; + break; + + case CHAR_RPAREN: + ctx->re++; + break; + + default: + break; + } + break; + + case PARSE_POST_UNION: + { + tre_ast_node_t *tmp_node; + tre_ast_node_t *tree = tre_stack_pop_voidptr(stack); + tmp_node = tre_ast_new_union(ctx->mem, tree, result); + if (!tmp_node) + return REG_ESPACE; + result = tmp_node; + break; + } + + case PARSE_POSTFIX: + /* Parse postfix operators. */ + switch (*ctx->re) + { + case CHAR_PLUS: + case CHAR_QUESTIONMARK: + if (!(ctx->cflags & REG_EXTENDED)) + break; + /*FALLTHROUGH*/ + case CHAR_STAR: + { + tre_ast_node_t *tmp_node; + int minimal = 0; + int rep_min = 0; + int rep_max = -1; + + if (*ctx->re == CHAR_PLUS) + rep_min = 1; + if (*ctx->re == CHAR_QUESTIONMARK) + rep_max = 1; + + ctx->re++; + tmp_node = tre_ast_new_iter(ctx->mem, result, rep_min, rep_max, + minimal); + if (tmp_node == NULL) + return REG_ESPACE; + result = tmp_node; + STACK_PUSHX(stack, int, PARSE_POSTFIX); + } + break; + + case CHAR_BACKSLASH: + /* "\{" is special without REG_EXTENDED */ + if (!(ctx->cflags & REG_EXTENDED) + && *(ctx->re + 1) == CHAR_LBRACE) + { + ctx->re++; + goto parse_brace; + } + else + break; + + case CHAR_LBRACE: + /* "{" is literal without REG_EXTENDED */ + if (!(ctx->cflags & REG_EXTENDED)) + break; + + parse_brace: + ctx->re++; + + status = tre_parse_bound(ctx, &result); + if (status != REG_OK) + return status; + STACK_PUSHX(stack, int, PARSE_POSTFIX); + break; + } + break; + + case PARSE_ATOM: + /* Parse an atom. An atom is a regular expression enclosed in `()', + an empty set of `()', a bracket expression, `.', `^', `$', + a `\' followed by a character, or a single character. */ + + switch (*ctx->re) + { + case CHAR_LPAREN: /* parenthesized subexpression */ + + if (ctx->cflags & REG_EXTENDED) + { + lparen: + depth++; + { + ctx->re++; + /* First parse a whole RE, then mark the resulting tree + for submatching. */ + STACK_PUSHX(stack, int, ctx->submatch_id); + STACK_PUSHX(stack, int, PARSE_MARK_FOR_SUBMATCH); + STACK_PUSHX(stack, int, PARSE_RE); + ctx->submatch_id++; + } + } + else + goto parse_literal; + break; + + case CHAR_LBRACKET: /* bracket expression */ + ctx->re++; + status = tre_parse_bracket(ctx, &result); + if (status != REG_OK) + return status; + break; + + case CHAR_BACKSLASH: + /* If this is "\(" or "\)" chew off the backslash and + try again. */ + if (!(ctx->cflags & REG_EXTENDED) && *(ctx->re + 1) == CHAR_LPAREN) + { + ctx->re++; + goto lparen; + } + if (!(ctx->cflags & REG_EXTENDED) && *(ctx->re + 1) == CHAR_RPAREN) + { + goto empty_atom; + } + + /* If a macro is used, parse the expanded macro recursively. */ + { + const char *buf = tre_expand_macro(ctx->re + 1); + if (buf) + { + tre_parse_ctx_t subctx; + memcpy(&subctx, ctx, sizeof(subctx)); + subctx.re = buf; + subctx.nofirstsub = 1; + status = tre_parse(&subctx); + if (status != REG_OK) + return status; + ctx->re += 2; + ctx->position = subctx.position; + result = subctx.result; + break; + } + } + + if (!ctx->re[1]) + /* Trailing backslash. */ + return REG_EESCAPE; + + ctx->re++; + switch (*ctx->re) + { + case 'b': + result = tre_ast_new_literal(ctx->mem, ASSERTION, + ASSERT_AT_WB, -1); + ctx->re++; + break; + case 'B': + result = tre_ast_new_literal(ctx->mem, ASSERTION, + ASSERT_AT_WB_NEG, -1); + ctx->re++; + break; + case '<': + result = tre_ast_new_literal(ctx->mem, ASSERTION, + ASSERT_AT_BOW, -1); + ctx->re++; + break; + case '>': + result = tre_ast_new_literal(ctx->mem, ASSERTION, + ASSERT_AT_EOW, -1); + ctx->re++; + break; + case 'x': + ctx->re++; + if (ctx->re[0] != CHAR_LBRACE) + { + /* 8 bit hex char. */ + char tmp[3] = {0, 0, 0}; + long val; + + if (tre_isxdigit(ctx->re[0])) + { + tmp[0] = (char)ctx->re[0]; + ctx->re++; + } + if (tre_isxdigit(ctx->re[0])) + { + tmp[1] = (char)ctx->re[0]; + ctx->re++; + } + val = strtol(tmp, NULL, 16); + result = tre_ast_new_literal(ctx->mem, (int)val, + (int)val, ctx->position); + ctx->position++; + break; + } + else if (*ctx->re) + { + /* Wide char. */ + char tmp[32]; + long val; + int i = 0; + ctx->re++; + while (*ctx->re && i < sizeof tmp) + { + if (ctx->re[0] == CHAR_RBRACE) + break; + if (tre_isxdigit(ctx->re[0])) + { + tmp[i] = (char)ctx->re[0]; + i++; + ctx->re++; + continue; + } + return REG_EBRACE; + } + ctx->re++; + tmp[i] = 0; + val = strtol(tmp, NULL, 16); + result = tre_ast_new_literal(ctx->mem, (int)val, (int)val, + ctx->position); + ctx->position++; + break; + } + /*FALLTHROUGH*/ + + default: + if (tre_isdigit(*ctx->re)) + { + /* Back reference. */ + int val = *ctx->re - '0'; + result = tre_ast_new_literal(ctx->mem, BACKREF, val, + ctx->position); + if (result == NULL) + return REG_ESPACE; + ctx->position++; + ctx->max_backref = MAX(val, ctx->max_backref); + ctx->re++; + } + else + { + /* Escaped character. */ + result = tre_ast_new_literal(ctx->mem, *ctx->re, *ctx->re, + ctx->position); + ctx->position++; + ctx->re++; + } + break; + } + if (result == NULL) + return REG_ESPACE; + break; + + case CHAR_PERIOD: /* the any-symbol */ + if (ctx->cflags & REG_NEWLINE) + { + tre_ast_node_t *tmp1; + tre_ast_node_t *tmp2; + tmp1 = tre_ast_new_literal(ctx->mem, 0, '\n' - 1, + ctx->position); + if (!tmp1) + return REG_ESPACE; + tmp2 = tre_ast_new_literal(ctx->mem, '\n' + 1, TRE_CHAR_MAX, + ctx->position + 1); + if (!tmp2) + return REG_ESPACE; + result = tre_ast_new_union(ctx->mem, tmp1, tmp2); + if (!result) + return REG_ESPACE; + ctx->position += 2; + } + else + { + result = tre_ast_new_literal(ctx->mem, 0, TRE_CHAR_MAX, + ctx->position); + if (!result) + return REG_ESPACE; + ctx->position++; + } + ctx->re++; + break; + + case CHAR_CARET: /* beginning of line assertion */ + /* '^' has a special meaning everywhere in EREs, and at + beginning of BRE. */ + if (ctx->cflags & REG_EXTENDED + || ctx->re == ctx->re_start) + { + if (!(ctx->cflags & REG_EXTENDED)) + STACK_PUSHX(stack, int, PARSE_CATENATION); + result = tre_ast_new_literal(ctx->mem, ASSERTION, + ASSERT_AT_BOL, -1); + if (result == NULL) + return REG_ESPACE; + ctx->re++; + } + else + goto parse_literal; + break; + + case CHAR_DOLLAR: /* end of line assertion. */ + /* '$' is special everywhere in EREs, and in the end of the + string in BREs. */ + if (ctx->cflags & REG_EXTENDED + || !*(ctx->re + 1)) + { + result = tre_ast_new_literal(ctx->mem, ASSERTION, + ASSERT_AT_EOL, -1); + if (result == NULL) + return REG_ESPACE; + ctx->re++; + } + else + goto parse_literal; + break; + + case CHAR_RPAREN: + if (!depth) + goto parse_literal; + case CHAR_STAR: + case CHAR_PIPE: + case CHAR_LBRACE: + case CHAR_PLUS: + case CHAR_QUESTIONMARK: + if (!(ctx->cflags & REG_EXTENDED)) + goto parse_literal; + + case 0: + empty_atom: + result = tre_ast_new_literal(ctx->mem, EMPTY, -1, -1); + if (!result) + return REG_ESPACE; + break; + + default: + parse_literal: + + clen = mbtowc(&wc, ctx->re, -1); + if (clen<0) clen=1, wc=WEOF; + + /* Note that we can't use an tre_isalpha() test here, since there + may be characters which are alphabetic but neither upper or + lower case. */ + if (ctx->cflags & REG_ICASE + && (tre_isupper(wc) || tre_islower(wc))) + { + tre_ast_node_t *tmp1; + tre_ast_node_t *tmp2; + + /* XXX - Can there be more than one opposite-case + counterpoints for some character in some locale? Or + more than two characters which all should be regarded + the same character if case is ignored? If yes, there + does not seem to be a portable way to detect it. I guess + that at least for multi-character collating elements there + could be several opposite-case counterpoints, but they + cannot be supported portably anyway. */ + tmp1 = tre_ast_new_literal(ctx->mem, tre_toupper(wc), + tre_toupper(wc), + ctx->position); + if (!tmp1) + return REG_ESPACE; + tmp2 = tre_ast_new_literal(ctx->mem, tre_tolower(wc), + tre_tolower(wc), + ctx->position); + if (!tmp2) + return REG_ESPACE; + result = tre_ast_new_union(ctx->mem, tmp1, tmp2); + if (!result) + return REG_ESPACE; + } + else + { + result = tre_ast_new_literal(ctx->mem, wc, wc, + ctx->position); + if (!result) + return REG_ESPACE; + } + ctx->position++; + ctx->re += clen; + break; + } + break; + + case PARSE_MARK_FOR_SUBMATCH: + { + int submatch_id = tre_stack_pop_int(stack); + + if (result->submatch_id >= 0) + { + tre_ast_node_t *n, *tmp_node; + n = tre_ast_new_literal(ctx->mem, EMPTY, -1, -1); + if (n == NULL) + return REG_ESPACE; + tmp_node = tre_ast_new_catenation(ctx->mem, n, result); + if (tmp_node == NULL) + return REG_ESPACE; + tmp_node->num_submatches = result->num_submatches; + result = tmp_node; + } + result->submatch_id = submatch_id; + result->num_submatches++; + break; + } + + case PARSE_RESTORE_CFLAGS: + ctx->cflags = tre_stack_pop_int(stack); + break; + + default: + assert(0); + break; + } + } + + /* Check for missing closing parentheses. */ + if (depth > 0) + return REG_EPAREN; + + if (status == REG_OK) + ctx->result = result; + + return status; +} + + + +/*********************************************************************** + from tre-compile.c +***********************************************************************/ + + +/* + TODO: + - Fix tre_ast_to_tnfa() to recurse using a stack instead of recursive + function calls. +*/ + +/* + Algorithms to setup tags so that submatch addressing can be done. +*/ + + +/* Inserts a catenation node to the root of the tree given in `node'. + As the left child a new tag with number `tag_id' to `node' is added, + and the right child is the old root. */ +static reg_errcode_t +tre_add_tag_left(tre_mem_t mem, tre_ast_node_t *node, int tag_id) +{ + tre_catenation_t *c; + + c = tre_mem_alloc(mem, sizeof(*c)); + if (c == NULL) + return REG_ESPACE; + c->left = tre_ast_new_literal(mem, TAG, tag_id, -1); + if (c->left == NULL) + return REG_ESPACE; + c->right = tre_mem_alloc(mem, sizeof(tre_ast_node_t)); + if (c->right == NULL) + return REG_ESPACE; + + c->right->obj = node->obj; + c->right->type = node->type; + c->right->nullable = -1; + c->right->submatch_id = -1; + c->right->firstpos = NULL; + c->right->lastpos = NULL; + c->right->num_tags = 0; + node->obj = c; + node->type = CATENATION; + return REG_OK; +} + +/* Inserts a catenation node to the root of the tree given in `node'. + As the right child a new tag with number `tag_id' to `node' is added, + and the left child is the old root. */ +static reg_errcode_t +tre_add_tag_right(tre_mem_t mem, tre_ast_node_t *node, int tag_id) +{ + tre_catenation_t *c; + + c = tre_mem_alloc(mem, sizeof(*c)); + if (c == NULL) + return REG_ESPACE; + c->right = tre_ast_new_literal(mem, TAG, tag_id, -1); + if (c->right == NULL) + return REG_ESPACE; + c->left = tre_mem_alloc(mem, sizeof(tre_ast_node_t)); + if (c->left == NULL) + return REG_ESPACE; + + c->left->obj = node->obj; + c->left->type = node->type; + c->left->nullable = -1; + c->left->submatch_id = -1; + c->left->firstpos = NULL; + c->left->lastpos = NULL; + c->left->num_tags = 0; + node->obj = c; + node->type = CATENATION; + return REG_OK; +} + +typedef enum { + ADDTAGS_RECURSE, + ADDTAGS_AFTER_ITERATION, + ADDTAGS_AFTER_UNION_LEFT, + ADDTAGS_AFTER_UNION_RIGHT, + ADDTAGS_AFTER_CAT_LEFT, + ADDTAGS_AFTER_CAT_RIGHT, + ADDTAGS_SET_SUBMATCH_END +} tre_addtags_symbol_t; + + +typedef struct { + int tag; + int next_tag; +} tre_tag_states_t; + + +/* Go through `regset' and set submatch data for submatches that are + using this tag. */ +static void +tre_purge_regset(int *regset, tre_tnfa_t *tnfa, int tag) +{ + int i; + + for (i = 0; regset[i] >= 0; i++) + { + int id = regset[i] / 2; + int start = !(regset[i] % 2); + if (start) + tnfa->submatch_data[id].so_tag = tag; + else + tnfa->submatch_data[id].eo_tag = tag; + } + regset[0] = -1; +} + + +/* Adds tags to appropriate locations in the parse tree in `tree', so that + subexpressions marked for submatch addressing can be traced. */ +static reg_errcode_t +tre_add_tags(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *tree, + tre_tnfa_t *tnfa) +{ + reg_errcode_t status = REG_OK; + tre_addtags_symbol_t symbol; + tre_ast_node_t *node = tree; /* Tree node we are currently looking at. */ + int bottom = tre_stack_num_objects(stack); + /* True for first pass (counting number of needed tags) */ + int first_pass = (mem == NULL || tnfa == NULL); + int *regset, *orig_regset; + int num_tags = 0; /* Total number of tags. */ + int num_minimals = 0; /* Number of special minimal tags. */ + int tag = 0; /* The tag that is to be added next. */ + int next_tag = 1; /* Next tag to use after this one. */ + int *parents; /* Stack of submatches the current submatch is + contained in. */ + int minimal_tag = -1; /* Tag that marks the beginning of a minimal match. */ + tre_tag_states_t *saved_states; + + tre_tag_direction_t direction = TRE_TAG_MINIMIZE; + if (!first_pass) + { + tnfa->end_tag = 0; + tnfa->minimal_tags[0] = -1; + } + + regset = xmalloc(sizeof(*regset) * ((tnfa->num_submatches + 1) * 2)); + if (regset == NULL) + return REG_ESPACE; + regset[0] = -1; + orig_regset = regset; + + parents = xmalloc(sizeof(*parents) * (tnfa->num_submatches + 1)); + if (parents == NULL) + { + xfree(regset); + return REG_ESPACE; + } + parents[0] = -1; + + saved_states = xmalloc(sizeof(*saved_states) * (tnfa->num_submatches + 1)); + if (saved_states == NULL) + { + xfree(regset); + xfree(parents); + return REG_ESPACE; + } + else + { + unsigned int i; + for (i = 0; i <= tnfa->num_submatches; i++) + saved_states[i].tag = -1; + } + + STACK_PUSH(stack, voidptr, node); + STACK_PUSH(stack, int, ADDTAGS_RECURSE); + + while (tre_stack_num_objects(stack) > bottom) + { + if (status != REG_OK) + break; + + symbol = (tre_addtags_symbol_t)tre_stack_pop_int(stack); + switch (symbol) + { + + case ADDTAGS_SET_SUBMATCH_END: + { + int id = tre_stack_pop_int(stack); + int i; + + /* Add end of this submatch to regset. */ + for (i = 0; regset[i] >= 0; i++); + regset[i] = id * 2 + 1; + regset[i + 1] = -1; + + /* Pop this submatch from the parents stack. */ + for (i = 0; parents[i] >= 0; i++); + parents[i - 1] = -1; + break; + } + + case ADDTAGS_RECURSE: + node = tre_stack_pop_voidptr(stack); + + if (node->submatch_id >= 0) + { + int id = node->submatch_id; + int i; + + + /* Add start of this submatch to regset. */ + for (i = 0; regset[i] >= 0; i++); + regset[i] = id * 2; + regset[i + 1] = -1; + + if (!first_pass) + { + for (i = 0; parents[i] >= 0; i++); + tnfa->submatch_data[id].parents = NULL; + if (i > 0) + { + int *p = xmalloc(sizeof(*p) * (i + 1)); + if (p == NULL) + { + status = REG_ESPACE; + break; + } + assert(tnfa->submatch_data[id].parents == NULL); + tnfa->submatch_data[id].parents = p; + for (i = 0; parents[i] >= 0; i++) + p[i] = parents[i]; + p[i] = -1; + } + } + + /* Add end of this submatch to regset after processing this + node. */ + STACK_PUSHX(stack, int, node->submatch_id); + STACK_PUSHX(stack, int, ADDTAGS_SET_SUBMATCH_END); + } + + switch (node->type) + { + case LITERAL: + { + tre_literal_t *lit = node->obj; + + if (!IS_SPECIAL(lit) || IS_BACKREF(lit)) + { + int i; + if (regset[0] >= 0) + { + /* Regset is not empty, so add a tag before the + literal or backref. */ + if (!first_pass) + { + status = tre_add_tag_left(mem, node, tag); + tnfa->tag_directions[tag] = direction; + if (minimal_tag >= 0) + { + for (i = 0; tnfa->minimal_tags[i] >= 0; i++); + tnfa->minimal_tags[i] = tag; + tnfa->minimal_tags[i + 1] = minimal_tag; + tnfa->minimal_tags[i + 2] = -1; + minimal_tag = -1; + num_minimals++; + } + tre_purge_regset(regset, tnfa, tag); + } + else + { + node->num_tags = 1; + } + + regset[0] = -1; + tag = next_tag; + num_tags++; + next_tag++; + } + } + else + { + assert(!IS_TAG(lit)); + } + break; + } + case CATENATION: + { + tre_catenation_t *cat = node->obj; + tre_ast_node_t *left = cat->left; + tre_ast_node_t *right = cat->right; + int reserved_tag = -1; + + + /* After processing right child. */ + STACK_PUSHX(stack, voidptr, node); + STACK_PUSHX(stack, int, ADDTAGS_AFTER_CAT_RIGHT); + + /* Process right child. */ + STACK_PUSHX(stack, voidptr, right); + STACK_PUSHX(stack, int, ADDTAGS_RECURSE); + + /* After processing left child. */ + STACK_PUSHX(stack, int, next_tag + left->num_tags); + if (left->num_tags > 0 && right->num_tags > 0) + { + /* Reserve the next tag to the right child. */ + reserved_tag = next_tag; + next_tag++; + } + STACK_PUSHX(stack, int, reserved_tag); + STACK_PUSHX(stack, int, ADDTAGS_AFTER_CAT_LEFT); + + /* Process left child. */ + STACK_PUSHX(stack, voidptr, left); + STACK_PUSHX(stack, int, ADDTAGS_RECURSE); + + } + break; + case ITERATION: + { + tre_iteration_t *iter = node->obj; + + if (first_pass) + { + STACK_PUSHX(stack, int, regset[0] >= 0 || iter->minimal); + } + else + { + STACK_PUSHX(stack, int, tag); + STACK_PUSHX(stack, int, iter->minimal); + } + STACK_PUSHX(stack, voidptr, node); + STACK_PUSHX(stack, int, ADDTAGS_AFTER_ITERATION); + + STACK_PUSHX(stack, voidptr, iter->arg); + STACK_PUSHX(stack, int, ADDTAGS_RECURSE); + + /* Regset is not empty, so add a tag here. */ + if (regset[0] >= 0 || iter->minimal) + { + if (!first_pass) + { + int i; + status = tre_add_tag_left(mem, node, tag); + if (iter->minimal) + tnfa->tag_directions[tag] = TRE_TAG_MAXIMIZE; + else + tnfa->tag_directions[tag] = direction; + if (minimal_tag >= 0) + { + for (i = 0; tnfa->minimal_tags[i] >= 0; i++); + tnfa->minimal_tags[i] = tag; + tnfa->minimal_tags[i + 1] = minimal_tag; + tnfa->minimal_tags[i + 2] = -1; + minimal_tag = -1; + num_minimals++; + } + tre_purge_regset(regset, tnfa, tag); + } + + regset[0] = -1; + tag = next_tag; + num_tags++; + next_tag++; + } + direction = TRE_TAG_MINIMIZE; + } + break; + case UNION: + { + tre_union_t *uni = node->obj; + tre_ast_node_t *left = uni->left; + tre_ast_node_t *right = uni->right; + int left_tag; + int right_tag; + + if (regset[0] >= 0) + { + left_tag = next_tag; + right_tag = next_tag + 1; + } + else + { + left_tag = tag; + right_tag = next_tag; + } + + /* After processing right child. */ + STACK_PUSHX(stack, int, right_tag); + STACK_PUSHX(stack, int, left_tag); + STACK_PUSHX(stack, voidptr, regset); + STACK_PUSHX(stack, int, regset[0] >= 0); + STACK_PUSHX(stack, voidptr, node); + STACK_PUSHX(stack, voidptr, right); + STACK_PUSHX(stack, voidptr, left); + STACK_PUSHX(stack, int, ADDTAGS_AFTER_UNION_RIGHT); + + /* Process right child. */ + STACK_PUSHX(stack, voidptr, right); + STACK_PUSHX(stack, int, ADDTAGS_RECURSE); + + /* After processing left child. */ + STACK_PUSHX(stack, int, ADDTAGS_AFTER_UNION_LEFT); + + /* Process left child. */ + STACK_PUSHX(stack, voidptr, left); + STACK_PUSHX(stack, int, ADDTAGS_RECURSE); + + /* Regset is not empty, so add a tag here. */ + if (regset[0] >= 0) + { + if (!first_pass) + { + int i; + status = tre_add_tag_left(mem, node, tag); + tnfa->tag_directions[tag] = direction; + if (minimal_tag >= 0) + { + for (i = 0; tnfa->minimal_tags[i] >= 0; i++); + tnfa->minimal_tags[i] = tag; + tnfa->minimal_tags[i + 1] = minimal_tag; + tnfa->minimal_tags[i + 2] = -1; + minimal_tag = -1; + num_minimals++; + } + tre_purge_regset(regset, tnfa, tag); + } + + regset[0] = -1; + tag = next_tag; + num_tags++; + next_tag++; + } + + if (node->num_submatches > 0) + { + /* The next two tags are reserved for markers. */ + next_tag++; + tag = next_tag; + next_tag++; + } + + break; + } + } + + if (node->submatch_id >= 0) + { + int i; + /* Push this submatch on the parents stack. */ + for (i = 0; parents[i] >= 0; i++); + parents[i] = node->submatch_id; + parents[i + 1] = -1; + } + + break; /* end case: ADDTAGS_RECURSE */ + + case ADDTAGS_AFTER_ITERATION: + { + int minimal = 0; + int enter_tag; + node = tre_stack_pop_voidptr(stack); + if (first_pass) + { + node->num_tags = ((tre_iteration_t *)node->obj)->arg->num_tags + + tre_stack_pop_int(stack); + minimal_tag = -1; + } + else + { + minimal = tre_stack_pop_int(stack); + enter_tag = tre_stack_pop_int(stack); + if (minimal) + minimal_tag = enter_tag; + } + + if (!first_pass) + { + if (minimal) + direction = TRE_TAG_MINIMIZE; + else + direction = TRE_TAG_MAXIMIZE; + } + break; + } + + case ADDTAGS_AFTER_CAT_LEFT: + { + int new_tag = tre_stack_pop_int(stack); + next_tag = tre_stack_pop_int(stack); + if (new_tag >= 0) + { + tag = new_tag; + } + break; + } + + case ADDTAGS_AFTER_CAT_RIGHT: + node = tre_stack_pop_voidptr(stack); + if (first_pass) + node->num_tags = ((tre_catenation_t *)node->obj)->left->num_tags + + ((tre_catenation_t *)node->obj)->right->num_tags; + break; + + case ADDTAGS_AFTER_UNION_LEFT: + /* Lift the bottom of the `regset' array so that when processing + the right operand the items currently in the array are + invisible. The original bottom was saved at ADDTAGS_UNION and + will be restored at ADDTAGS_AFTER_UNION_RIGHT below. */ + while (*regset >= 0) + regset++; + break; + + case ADDTAGS_AFTER_UNION_RIGHT: + { + int added_tags, tag_left, tag_right; + tre_ast_node_t *left = tre_stack_pop_voidptr(stack); + tre_ast_node_t *right = tre_stack_pop_voidptr(stack); + node = tre_stack_pop_voidptr(stack); + added_tags = tre_stack_pop_int(stack); + if (first_pass) + { + node->num_tags = ((tre_union_t *)node->obj)->left->num_tags + + ((tre_union_t *)node->obj)->right->num_tags + added_tags + + ((node->num_submatches > 0) ? 2 : 0); + } + regset = tre_stack_pop_voidptr(stack); + tag_left = tre_stack_pop_int(stack); + tag_right = tre_stack_pop_int(stack); + + /* Add tags after both children, the left child gets a smaller + tag than the right child. This guarantees that we prefer + the left child over the right child. */ + /* XXX - This is not always necessary (if the children have + tags which must be seen for every match of that child). */ + /* XXX - Check if this is the only place where tre_add_tag_right + is used. If so, use tre_add_tag_left (putting the tag before + the child as opposed after the child) and throw away + tre_add_tag_right. */ + if (node->num_submatches > 0) + { + if (!first_pass) + { + status = tre_add_tag_right(mem, left, tag_left); + tnfa->tag_directions[tag_left] = TRE_TAG_MAXIMIZE; + status = tre_add_tag_right(mem, right, tag_right); + tnfa->tag_directions[tag_right] = TRE_TAG_MAXIMIZE; + } + num_tags += 2; + } + direction = TRE_TAG_MAXIMIZE; + break; + } + + default: + assert(0); + break; + + } /* end switch(symbol) */ + } /* end while(tre_stack_num_objects(stack) > bottom) */ + + if (!first_pass) + tre_purge_regset(regset, tnfa, tag); + + if (!first_pass && minimal_tag >= 0) + { + int i; + for (i = 0; tnfa->minimal_tags[i] >= 0; i++); + tnfa->minimal_tags[i] = tag; + tnfa->minimal_tags[i + 1] = minimal_tag; + tnfa->minimal_tags[i + 2] = -1; + minimal_tag = -1; + num_minimals++; + } + + assert(tree->num_tags == num_tags); + tnfa->end_tag = num_tags; + tnfa->num_tags = num_tags; + tnfa->num_minimals = num_minimals; + xfree(orig_regset); + xfree(parents); + xfree(saved_states); + return status; +} + + + +/* + AST to TNFA compilation routines. +*/ + +typedef enum { + COPY_RECURSE, + COPY_SET_RESULT_PTR +} tre_copyast_symbol_t; + +/* Flags for tre_copy_ast(). */ +#define COPY_REMOVE_TAGS 1 +#define COPY_MAXIMIZE_FIRST_TAG 2 + +static reg_errcode_t +tre_copy_ast(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *ast, + int flags, int *pos_add, tre_tag_direction_t *tag_directions, + tre_ast_node_t **copy, int *max_pos) +{ + reg_errcode_t status = REG_OK; + int bottom = tre_stack_num_objects(stack); + int num_copied = 0; + int first_tag = 1; + tre_ast_node_t **result = copy; + tre_copyast_symbol_t symbol; + + STACK_PUSH(stack, voidptr, ast); + STACK_PUSH(stack, int, COPY_RECURSE); + + while (status == REG_OK && tre_stack_num_objects(stack) > bottom) + { + tre_ast_node_t *node; + if (status != REG_OK) + break; + + symbol = (tre_copyast_symbol_t)tre_stack_pop_int(stack); + switch (symbol) + { + case COPY_SET_RESULT_PTR: + result = tre_stack_pop_voidptr(stack); + break; + case COPY_RECURSE: + node = tre_stack_pop_voidptr(stack); + switch (node->type) + { + case LITERAL: + { + tre_literal_t *lit = node->obj; + int pos = lit->position; + int min = lit->code_min; + int max = lit->code_max; + if (!IS_SPECIAL(lit) || IS_BACKREF(lit)) + { + /* XXX - e.g. [ab] has only one position but two + nodes, so we are creating holes in the state space + here. Not fatal, just wastes memory. */ + pos += *pos_add; + num_copied++; + } + else if (IS_TAG(lit) && (flags & COPY_REMOVE_TAGS)) + { + /* Change this tag to empty. */ + min = EMPTY; + max = pos = -1; + } + else if (IS_TAG(lit) && (flags & COPY_MAXIMIZE_FIRST_TAG) + && first_tag) + { + /* Maximize the first tag. */ + tag_directions[max] = TRE_TAG_MAXIMIZE; + first_tag = 0; + } + *result = tre_ast_new_literal(mem, min, max, pos); + if (*result == NULL) + status = REG_ESPACE; + + if (pos > *max_pos) + *max_pos = pos; + break; + } + case UNION: + { + tre_union_t *uni = node->obj; + tre_union_t *tmp; + *result = tre_ast_new_union(mem, uni->left, uni->right); + if (*result == NULL) + { + status = REG_ESPACE; + break; + } + tmp = (*result)->obj; + result = &tmp->left; + STACK_PUSHX(stack, voidptr, uni->right); + STACK_PUSHX(stack, int, COPY_RECURSE); + STACK_PUSHX(stack, voidptr, &tmp->right); + STACK_PUSHX(stack, int, COPY_SET_RESULT_PTR); + STACK_PUSHX(stack, voidptr, uni->left); + STACK_PUSHX(stack, int, COPY_RECURSE); + break; + } + case CATENATION: + { + tre_catenation_t *cat = node->obj; + tre_catenation_t *tmp; + *result = tre_ast_new_catenation(mem, cat->left, cat->right); + if (*result == NULL) + { + status = REG_ESPACE; + break; + } + tmp = (*result)->obj; + tmp->left = NULL; + tmp->right = NULL; + result = &tmp->left; + + STACK_PUSHX(stack, voidptr, cat->right); + STACK_PUSHX(stack, int, COPY_RECURSE); + STACK_PUSHX(stack, voidptr, &tmp->right); + STACK_PUSHX(stack, int, COPY_SET_RESULT_PTR); + STACK_PUSHX(stack, voidptr, cat->left); + STACK_PUSHX(stack, int, COPY_RECURSE); + break; + } + case ITERATION: + { + tre_iteration_t *iter = node->obj; + STACK_PUSHX(stack, voidptr, iter->arg); + STACK_PUSHX(stack, int, COPY_RECURSE); + *result = tre_ast_new_iter(mem, iter->arg, iter->min, + iter->max, iter->minimal); + if (*result == NULL) + { + status = REG_ESPACE; + break; + } + iter = (*result)->obj; + result = &iter->arg; + break; + } + default: + assert(0); + break; + } + break; + } + } + *pos_add += num_copied; + return status; +} + +typedef enum { + EXPAND_RECURSE, + EXPAND_AFTER_ITER +} tre_expand_ast_symbol_t; + +/* Expands each iteration node that has a finite nonzero minimum or maximum + iteration count to a catenated sequence of copies of the node. */ +static reg_errcode_t +tre_expand_ast(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *ast, + int *position, tre_tag_direction_t *tag_directions) +{ + reg_errcode_t status = REG_OK; + int bottom = tre_stack_num_objects(stack); + int pos_add = 0; + int pos_add_total = 0; + int max_pos = 0; + int iter_depth = 0; + + STACK_PUSHR(stack, voidptr, ast); + STACK_PUSHR(stack, int, EXPAND_RECURSE); + while (status == REG_OK && tre_stack_num_objects(stack) > bottom) + { + tre_ast_node_t *node; + tre_expand_ast_symbol_t symbol; + + if (status != REG_OK) + break; + + symbol = (tre_expand_ast_symbol_t)tre_stack_pop_int(stack); + node = tre_stack_pop_voidptr(stack); + switch (symbol) + { + case EXPAND_RECURSE: + switch (node->type) + { + case LITERAL: + { + tre_literal_t *lit= node->obj; + if (!IS_SPECIAL(lit) || IS_BACKREF(lit)) + { + lit->position += pos_add; + if (lit->position > max_pos) + max_pos = lit->position; + } + break; + } + case UNION: + { + tre_union_t *uni = node->obj; + STACK_PUSHX(stack, voidptr, uni->right); + STACK_PUSHX(stack, int, EXPAND_RECURSE); + STACK_PUSHX(stack, voidptr, uni->left); + STACK_PUSHX(stack, int, EXPAND_RECURSE); + break; + } + case CATENATION: + { + tre_catenation_t *cat = node->obj; + STACK_PUSHX(stack, voidptr, cat->right); + STACK_PUSHX(stack, int, EXPAND_RECURSE); + STACK_PUSHX(stack, voidptr, cat->left); + STACK_PUSHX(stack, int, EXPAND_RECURSE); + break; + } + case ITERATION: + { + tre_iteration_t *iter = node->obj; + STACK_PUSHX(stack, int, pos_add); + STACK_PUSHX(stack, voidptr, node); + STACK_PUSHX(stack, int, EXPAND_AFTER_ITER); + STACK_PUSHX(stack, voidptr, iter->arg); + STACK_PUSHX(stack, int, EXPAND_RECURSE); + /* If we are going to expand this node at EXPAND_AFTER_ITER + then don't increase the `pos' fields of the nodes now, it + will get done when expanding. */ + if (iter->min > 1 || iter->max > 1) + pos_add = 0; + iter_depth++; + break; + } + default: + assert(0); + break; + } + break; + case EXPAND_AFTER_ITER: + { + tre_iteration_t *iter = node->obj; + int pos_add_last; + pos_add = tre_stack_pop_int(stack); + pos_add_last = pos_add; + if (iter->min > 1 || iter->max > 1) + { + tre_ast_node_t *seq1 = NULL, *seq2 = NULL; + int j; + int pos_add_save = pos_add; + + /* Create a catenated sequence of copies of the node. */ + for (j = 0; j < iter->min; j++) + { + tre_ast_node_t *copy; + /* Remove tags from all but the last copy. */ + int flags = ((j + 1 < iter->min) + ? COPY_REMOVE_TAGS + : COPY_MAXIMIZE_FIRST_TAG); + pos_add_save = pos_add; + status = tre_copy_ast(mem, stack, iter->arg, flags, + &pos_add, tag_directions, ©, + &max_pos); + if (status != REG_OK) + return status; + if (seq1 != NULL) + seq1 = tre_ast_new_catenation(mem, seq1, copy); + else + seq1 = copy; + if (seq1 == NULL) + return REG_ESPACE; + } + + if (iter->max == -1) + { + /* No upper limit. */ + pos_add_save = pos_add; + status = tre_copy_ast(mem, stack, iter->arg, 0, + &pos_add, NULL, &seq2, &max_pos); + if (status != REG_OK) + return status; + seq2 = tre_ast_new_iter(mem, seq2, 0, -1, 0); + if (seq2 == NULL) + return REG_ESPACE; + } + else + { + for (j = iter->min; j < iter->max; j++) + { + tre_ast_node_t *tmp, *copy; + pos_add_save = pos_add; + status = tre_copy_ast(mem, stack, iter->arg, 0, + &pos_add, NULL, ©, &max_pos); + if (status != REG_OK) + return status; + if (seq2 != NULL) + seq2 = tre_ast_new_catenation(mem, copy, seq2); + else + seq2 = copy; + if (seq2 == NULL) + return REG_ESPACE; + tmp = tre_ast_new_literal(mem, EMPTY, -1, -1); + if (tmp == NULL) + return REG_ESPACE; + seq2 = tre_ast_new_union(mem, tmp, seq2); + if (seq2 == NULL) + return REG_ESPACE; + } + } + + pos_add = pos_add_save; + if (seq1 == NULL) + seq1 = seq2; + else if (seq2 != NULL) + seq1 = tre_ast_new_catenation(mem, seq1, seq2); + if (seq1 == NULL) + return REG_ESPACE; + node->obj = seq1->obj; + node->type = seq1->type; + } + + iter_depth--; + pos_add_total += pos_add - pos_add_last; + if (iter_depth == 0) + pos_add = pos_add_total; + + break; + } + default: + assert(0); + break; + } + } + + *position += pos_add_total; + + /* `max_pos' should never be larger than `*position' if the above + code works, but just an extra safeguard let's make sure + `*position' is set large enough so enough memory will be + allocated for the transition table. */ + if (max_pos > *position) + *position = max_pos; + + return status; +} + +static tre_pos_and_tags_t * +tre_set_empty(tre_mem_t mem) +{ + tre_pos_and_tags_t *new_set; + + new_set = tre_mem_calloc(mem, sizeof(*new_set)); + if (new_set == NULL) + return NULL; + + new_set[0].position = -1; + new_set[0].code_min = -1; + new_set[0].code_max = -1; + + return new_set; +} + +static tre_pos_and_tags_t * +tre_set_one(tre_mem_t mem, int position, int code_min, int code_max, + tre_ctype_t class, tre_ctype_t *neg_classes, int backref) +{ + tre_pos_and_tags_t *new_set; + + new_set = tre_mem_calloc(mem, sizeof(*new_set) * 2); + if (new_set == NULL) + return NULL; + + new_set[0].position = position; + new_set[0].code_min = code_min; + new_set[0].code_max = code_max; + new_set[0].class = class; + new_set[0].neg_classes = neg_classes; + new_set[0].backref = backref; + new_set[1].position = -1; + new_set[1].code_min = -1; + new_set[1].code_max = -1; + + return new_set; +} + +static tre_pos_and_tags_t * +tre_set_union(tre_mem_t mem, tre_pos_and_tags_t *set1, tre_pos_and_tags_t *set2, + int *tags, int assertions) +{ + int s1, s2, i, j; + tre_pos_and_tags_t *new_set; + int *new_tags; + int num_tags; + + for (num_tags = 0; tags != NULL && tags[num_tags] >= 0; num_tags++); + for (s1 = 0; set1[s1].position >= 0; s1++); + for (s2 = 0; set2[s2].position >= 0; s2++); + new_set = tre_mem_calloc(mem, sizeof(*new_set) * (s1 + s2 + 1)); + if (!new_set ) + return NULL; + + for (s1 = 0; set1[s1].position >= 0; s1++) + { + new_set[s1].position = set1[s1].position; + new_set[s1].code_min = set1[s1].code_min; + new_set[s1].code_max = set1[s1].code_max; + new_set[s1].assertions = set1[s1].assertions | assertions; + new_set[s1].class = set1[s1].class; + new_set[s1].neg_classes = set1[s1].neg_classes; + new_set[s1].backref = set1[s1].backref; + if (set1[s1].tags == NULL && tags == NULL) + new_set[s1].tags = NULL; + else + { + for (i = 0; set1[s1].tags != NULL && set1[s1].tags[i] >= 0; i++); + new_tags = tre_mem_alloc(mem, (sizeof(*new_tags) + * (i + num_tags + 1))); + if (new_tags == NULL) + return NULL; + for (j = 0; j < i; j++) + new_tags[j] = set1[s1].tags[j]; + for (i = 0; i < num_tags; i++) + new_tags[j + i] = tags[i]; + new_tags[j + i] = -1; + new_set[s1].tags = new_tags; + } + } + + for (s2 = 0; set2[s2].position >= 0; s2++) + { + new_set[s1 + s2].position = set2[s2].position; + new_set[s1 + s2].code_min = set2[s2].code_min; + new_set[s1 + s2].code_max = set2[s2].code_max; + /* XXX - why not | assertions here as well? */ + new_set[s1 + s2].assertions = set2[s2].assertions; + new_set[s1 + s2].class = set2[s2].class; + new_set[s1 + s2].neg_classes = set2[s2].neg_classes; + new_set[s1 + s2].backref = set2[s2].backref; + if (set2[s2].tags == NULL) + new_set[s1 + s2].tags = NULL; + else + { + for (i = 0; set2[s2].tags[i] >= 0; i++); + new_tags = tre_mem_alloc(mem, sizeof(*new_tags) * (i + 1)); + if (new_tags == NULL) + return NULL; + for (j = 0; j < i; j++) + new_tags[j] = set2[s2].tags[j]; + new_tags[j] = -1; + new_set[s1 + s2].tags = new_tags; + } + } + new_set[s1 + s2].position = -1; + return new_set; +} + +/* Finds the empty path through `node' which is the one that should be + taken according to POSIX.2 rules, and adds the tags on that path to + `tags'. `tags' may be NULL. If `num_tags_seen' is not NULL, it is + set to the number of tags seen on the path. */ +static reg_errcode_t +tre_match_empty(tre_stack_t *stack, tre_ast_node_t *node, int *tags, + int *assertions, int *num_tags_seen) +{ + tre_literal_t *lit; + tre_union_t *uni; + tre_catenation_t *cat; + tre_iteration_t *iter; + int i; + int bottom = tre_stack_num_objects(stack); + reg_errcode_t status = REG_OK; + if (num_tags_seen) + *num_tags_seen = 0; + + status = tre_stack_push_voidptr(stack, node); + + /* Walk through the tree recursively. */ + while (status == REG_OK && tre_stack_num_objects(stack) > bottom) + { + node = tre_stack_pop_voidptr(stack); + + switch (node->type) + { + case LITERAL: + lit = (tre_literal_t *)node->obj; + switch (lit->code_min) + { + case TAG: + if (lit->code_max >= 0) + { + if (tags != NULL) + { + /* Add the tag to `tags'. */ + for (i = 0; tags[i] >= 0; i++) + if (tags[i] == lit->code_max) + break; + if (tags[i] < 0) + { + tags[i] = lit->code_max; + tags[i + 1] = -1; + } + } + if (num_tags_seen) + (*num_tags_seen)++; + } + break; + case ASSERTION: + assert(lit->code_max >= 1 + || lit->code_max <= ASSERT_LAST); + if (assertions != NULL) + *assertions |= lit->code_max; + break; + case EMPTY: + break; + default: + assert(0); + break; + } + break; + + case UNION: + /* Subexpressions starting earlier take priority over ones + starting later, so we prefer the left subexpression over the + right subexpression. */ + uni = (tre_union_t *)node->obj; + if (uni->left->nullable) + STACK_PUSHX(stack, voidptr, uni->left) + else if (uni->right->nullable) + STACK_PUSHX(stack, voidptr, uni->right) + else + assert(0); + break; + + case CATENATION: + /* The path must go through both children. */ + cat = (tre_catenation_t *)node->obj; + assert(cat->left->nullable); + assert(cat->right->nullable); + STACK_PUSHX(stack, voidptr, cat->left); + STACK_PUSHX(stack, voidptr, cat->right); + break; + + case ITERATION: + /* A match with an empty string is preferred over no match at + all, so we go through the argument if possible. */ + iter = (tre_iteration_t *)node->obj; + if (iter->arg->nullable) + STACK_PUSHX(stack, voidptr, iter->arg); + break; + + default: + assert(0); + break; + } + } + + return status; +} + + +typedef enum { + NFL_RECURSE, + NFL_POST_UNION, + NFL_POST_CATENATION, + NFL_POST_ITERATION +} tre_nfl_stack_symbol_t; + + +/* Computes and fills in the fields `nullable', `firstpos', and `lastpos' for + the nodes of the AST `tree'. */ +static reg_errcode_t +tre_compute_nfl(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *tree) +{ + int bottom = tre_stack_num_objects(stack); + + STACK_PUSHR(stack, voidptr, tree); + STACK_PUSHR(stack, int, NFL_RECURSE); + + while (tre_stack_num_objects(stack) > bottom) + { + tre_nfl_stack_symbol_t symbol; + tre_ast_node_t *node; + + symbol = (tre_nfl_stack_symbol_t)tre_stack_pop_int(stack); + node = tre_stack_pop_voidptr(stack); + switch (symbol) + { + case NFL_RECURSE: + switch (node->type) + { + case LITERAL: + { + tre_literal_t *lit = (tre_literal_t *)node->obj; + if (IS_BACKREF(lit)) + { + /* Back references: nullable = false, firstpos = {i}, + lastpos = {i}. */ + node->nullable = 0; + node->firstpos = tre_set_one(mem, lit->position, 0, + TRE_CHAR_MAX, 0, NULL, -1); + if (!node->firstpos) + return REG_ESPACE; + node->lastpos = tre_set_one(mem, lit->position, 0, + TRE_CHAR_MAX, 0, NULL, + (int)lit->code_max); + if (!node->lastpos) + return REG_ESPACE; + } + else if (lit->code_min < 0) + { + /* Tags, empty strings, params, and zero width assertions: + nullable = true, firstpos = {}, and lastpos = {}. */ + node->nullable = 1; + node->firstpos = tre_set_empty(mem); + if (!node->firstpos) + return REG_ESPACE; + node->lastpos = tre_set_empty(mem); + if (!node->lastpos) + return REG_ESPACE; + } + else + { + /* Literal at position i: nullable = false, firstpos = {i}, + lastpos = {i}. */ + node->nullable = 0; + node->firstpos = + tre_set_one(mem, lit->position, (int)lit->code_min, + (int)lit->code_max, 0, NULL, -1); + if (!node->firstpos) + return REG_ESPACE; + node->lastpos = tre_set_one(mem, lit->position, + (int)lit->code_min, + (int)lit->code_max, + lit->class, lit->neg_classes, + -1); + if (!node->lastpos) + return REG_ESPACE; + } + break; + } + + case UNION: + /* Compute the attributes for the two subtrees, and after that + for this node. */ + STACK_PUSHR(stack, voidptr, node); + STACK_PUSHR(stack, int, NFL_POST_UNION); + STACK_PUSHR(stack, voidptr, ((tre_union_t *)node->obj)->right); + STACK_PUSHR(stack, int, NFL_RECURSE); + STACK_PUSHR(stack, voidptr, ((tre_union_t *)node->obj)->left); + STACK_PUSHR(stack, int, NFL_RECURSE); + break; + + case CATENATION: + /* Compute the attributes for the two subtrees, and after that + for this node. */ + STACK_PUSHR(stack, voidptr, node); + STACK_PUSHR(stack, int, NFL_POST_CATENATION); + STACK_PUSHR(stack, voidptr, ((tre_catenation_t *)node->obj)->right); + STACK_PUSHR(stack, int, NFL_RECURSE); + STACK_PUSHR(stack, voidptr, ((tre_catenation_t *)node->obj)->left); + STACK_PUSHR(stack, int, NFL_RECURSE); + break; + + case ITERATION: + /* Compute the attributes for the subtree, and after that for + this node. */ + STACK_PUSHR(stack, voidptr, node); + STACK_PUSHR(stack, int, NFL_POST_ITERATION); + STACK_PUSHR(stack, voidptr, ((tre_iteration_t *)node->obj)->arg); + STACK_PUSHR(stack, int, NFL_RECURSE); + break; + } + break; /* end case: NFL_RECURSE */ + + case NFL_POST_UNION: + { + tre_union_t *uni = (tre_union_t *)node->obj; + node->nullable = uni->left->nullable || uni->right->nullable; + node->firstpos = tre_set_union(mem, uni->left->firstpos, + uni->right->firstpos, NULL, 0); + if (!node->firstpos) + return REG_ESPACE; + node->lastpos = tre_set_union(mem, uni->left->lastpos, + uni->right->lastpos, NULL, 0); + if (!node->lastpos) + return REG_ESPACE; + break; + } + + case NFL_POST_ITERATION: + { + tre_iteration_t *iter = (tre_iteration_t *)node->obj; + + if (iter->min == 0 || iter->arg->nullable) + node->nullable = 1; + else + node->nullable = 0; + node->firstpos = iter->arg->firstpos; + node->lastpos = iter->arg->lastpos; + break; + } + + case NFL_POST_CATENATION: + { + int num_tags, *tags, assertions; + reg_errcode_t status; + tre_catenation_t *cat = node->obj; + node->nullable = cat->left->nullable && cat->right->nullable; + + /* Compute firstpos. */ + if (cat->left->nullable) + { + /* The left side matches the empty string. Make a first pass + with tre_match_empty() to get the number of tags and + parameters. */ + status = tre_match_empty(stack, cat->left, + NULL, NULL, &num_tags); + if (status != REG_OK) + return status; + /* Allocate arrays for the tags and parameters. */ + tags = xmalloc(sizeof(*tags) * (num_tags + 1)); + if (!tags) + return REG_ESPACE; + tags[0] = -1; + assertions = 0; + /* Second pass with tre_mach_empty() to get the list of + tags and parameters. */ + status = tre_match_empty(stack, cat->left, tags, + &assertions, NULL); + if (status != REG_OK) + { + xfree(tags); + return status; + } + node->firstpos = + tre_set_union(mem, cat->right->firstpos, cat->left->firstpos, + tags, assertions); + xfree(tags); + if (!node->firstpos) + return REG_ESPACE; + } + else + { + node->firstpos = cat->left->firstpos; + } + + /* Compute lastpos. */ + if (cat->right->nullable) + { + /* The right side matches the empty string. Make a first pass + with tre_match_empty() to get the number of tags and + parameters. */ + status = tre_match_empty(stack, cat->right, + NULL, NULL, &num_tags); + if (status != REG_OK) + return status; + /* Allocate arrays for the tags and parameters. */ + tags = xmalloc(sizeof(int) * (num_tags + 1)); + if (!tags) + return REG_ESPACE; + tags[0] = -1; + assertions = 0; + /* Second pass with tre_mach_empty() to get the list of + tags and parameters. */ + status = tre_match_empty(stack, cat->right, tags, + &assertions, NULL); + if (status != REG_OK) + { + xfree(tags); + return status; + } + node->lastpos = + tre_set_union(mem, cat->left->lastpos, cat->right->lastpos, + tags, assertions); + xfree(tags); + if (!node->lastpos) + return REG_ESPACE; + } + else + { + node->lastpos = cat->right->lastpos; + } + break; + } + + default: + assert(0); + break; + } + } + + return REG_OK; +} + + +/* Adds a transition from each position in `p1' to each position in `p2'. */ +static reg_errcode_t +tre_make_trans(tre_pos_and_tags_t *p1, tre_pos_and_tags_t *p2, + tre_tnfa_transition_t *transitions, + int *counts, int *offs) +{ + tre_pos_and_tags_t *orig_p2 = p2; + tre_tnfa_transition_t *trans; + int i, j, k, l, dup, prev_p2_pos; + + if (transitions != NULL) + while (p1->position >= 0) + { + p2 = orig_p2; + prev_p2_pos = -1; + while (p2->position >= 0) + { + /* Optimization: if this position was already handled, skip it. */ + if (p2->position == prev_p2_pos) + { + p2++; + continue; + } + prev_p2_pos = p2->position; + /* Set `trans' to point to the next unused transition from + position `p1->position'. */ + trans = transitions + offs[p1->position]; + while (trans->state != NULL) + { +#if 0 + /* If we find a previous transition from `p1->position' to + `p2->position', it is overwritten. This can happen only + if there are nested loops in the regexp, like in "((a)*)*". + In POSIX.2 repetition using the outer loop is always + preferred over using the inner loop. Therefore the + transition for the inner loop is useless and can be thrown + away. */ + /* XXX - The same position is used for all nodes in a bracket + expression, so this optimization cannot be used (it will + break bracket expressions) unless I figure out a way to + detect it here. */ + if (trans->state_id == p2->position) + { + break; + } +#endif + trans++; + } + + if (trans->state == NULL) + (trans + 1)->state = NULL; + /* Use the character ranges, assertions, etc. from `p1' for + the transition from `p1' to `p2'. */ + trans->code_min = p1->code_min; + trans->code_max = p1->code_max; + trans->state = transitions + offs[p2->position]; + trans->state_id = p2->position; + trans->assertions = p1->assertions | p2->assertions + | (p1->class ? ASSERT_CHAR_CLASS : 0) + | (p1->neg_classes != NULL ? ASSERT_CHAR_CLASS_NEG : 0); + if (p1->backref >= 0) + { + assert((trans->assertions & ASSERT_CHAR_CLASS) == 0); + assert(p2->backref < 0); + trans->u.backref = p1->backref; + trans->assertions |= ASSERT_BACKREF; + } + else + trans->u.class = p1->class; + if (p1->neg_classes != NULL) + { + for (i = 0; p1->neg_classes[i] != (tre_ctype_t)0; i++); + trans->neg_classes = + xmalloc(sizeof(*trans->neg_classes) * (i + 1)); + if (trans->neg_classes == NULL) + return REG_ESPACE; + for (i = 0; p1->neg_classes[i] != (tre_ctype_t)0; i++) + trans->neg_classes[i] = p1->neg_classes[i]; + trans->neg_classes[i] = (tre_ctype_t)0; + } + else + trans->neg_classes = NULL; + + /* Find out how many tags this transition has. */ + i = 0; + if (p1->tags != NULL) + while(p1->tags[i] >= 0) + i++; + j = 0; + if (p2->tags != NULL) + while(p2->tags[j] >= 0) + j++; + + /* If we are overwriting a transition, free the old tag array. */ + if (trans->tags != NULL) + xfree(trans->tags); + trans->tags = NULL; + + /* If there were any tags, allocate an array and fill it. */ + if (i + j > 0) + { + trans->tags = xmalloc(sizeof(*trans->tags) * (i + j + 1)); + if (!trans->tags) + return REG_ESPACE; + i = 0; + if (p1->tags != NULL) + while(p1->tags[i] >= 0) + { + trans->tags[i] = p1->tags[i]; + i++; + } + l = i; + j = 0; + if (p2->tags != NULL) + while (p2->tags[j] >= 0) + { + /* Don't add duplicates. */ + dup = 0; + for (k = 0; k < i; k++) + if (trans->tags[k] == p2->tags[j]) + { + dup = 1; + break; + } + if (!dup) + trans->tags[l++] = p2->tags[j]; + j++; + } + trans->tags[l] = -1; + } + + p2++; + } + p1++; + } + else + /* Compute a maximum limit for the number of transitions leaving + from each state. */ + while (p1->position >= 0) + { + p2 = orig_p2; + while (p2->position >= 0) + { + counts[p1->position]++; + p2++; + } + p1++; + } + return REG_OK; +} + +/* Converts the syntax tree to a TNFA. All the transitions in the TNFA are + labelled with one character range (there are no transitions on empty + strings). The TNFA takes O(n^2) space in the worst case, `n' is size of + the regexp. */ +static reg_errcode_t +tre_ast_to_tnfa(tre_ast_node_t *node, tre_tnfa_transition_t *transitions, + int *counts, int *offs) +{ + tre_union_t *uni; + tre_catenation_t *cat; + tre_iteration_t *iter; + reg_errcode_t errcode = REG_OK; + + /* XXX - recurse using a stack!. */ + switch (node->type) + { + case LITERAL: + break; + case UNION: + uni = (tre_union_t *)node->obj; + errcode = tre_ast_to_tnfa(uni->left, transitions, counts, offs); + if (errcode != REG_OK) + return errcode; + errcode = tre_ast_to_tnfa(uni->right, transitions, counts, offs); + break; + + case CATENATION: + cat = (tre_catenation_t *)node->obj; + /* Add a transition from each position in cat->left->lastpos + to each position in cat->right->firstpos. */ + errcode = tre_make_trans(cat->left->lastpos, cat->right->firstpos, + transitions, counts, offs); + if (errcode != REG_OK) + return errcode; + errcode = tre_ast_to_tnfa(cat->left, transitions, counts, offs); + if (errcode != REG_OK) + return errcode; + errcode = tre_ast_to_tnfa(cat->right, transitions, counts, offs); + break; + + case ITERATION: + iter = (tre_iteration_t *)node->obj; + assert(iter->max == -1 || iter->max == 1); + + if (iter->max == -1) + { + assert(iter->min == 0 || iter->min == 1); + /* Add a transition from each last position in the iterated + expression to each first position. */ + errcode = tre_make_trans(iter->arg->lastpos, iter->arg->firstpos, + transitions, counts, offs); + if (errcode != REG_OK) + return errcode; + } + errcode = tre_ast_to_tnfa(iter->arg, transitions, counts, offs); + break; + } + return errcode; +} + + +#define ERROR_EXIT(err) \ + do \ + { \ + errcode = err; \ + if (/*CONSTCOND*/1) \ + goto error_exit; \ + } \ + while (/*CONSTCOND*/0) + + +int +regcomp(regex_t *restrict preg, const char *restrict regex, int cflags) +{ + tre_stack_t *stack; + tre_ast_node_t *tree, *tmp_ast_l, *tmp_ast_r; + tre_pos_and_tags_t *p; + int *counts = NULL, *offs = NULL; + int i, add = 0; + tre_tnfa_transition_t *transitions, *initial; + tre_tnfa_t *tnfa = NULL; + tre_submatch_data_t *submatch_data; + tre_tag_direction_t *tag_directions = NULL; + reg_errcode_t errcode; + tre_mem_t mem; + + /* Parse context. */ + tre_parse_ctx_t parse_ctx; + + /* Allocate a stack used throughout the compilation process for various + purposes. */ + stack = tre_stack_new(512, 10240, 128); + if (!stack) + return REG_ESPACE; + /* Allocate a fast memory allocator. */ + mem = tre_mem_new(); + if (!mem) + { + tre_stack_destroy(stack); + return REG_ESPACE; + } + + /* Parse the regexp. */ + memset(&parse_ctx, 0, sizeof(parse_ctx)); + parse_ctx.mem = mem; + parse_ctx.stack = stack; + parse_ctx.re = regex; + parse_ctx.cflags = cflags; + parse_ctx.max_backref = -1; + errcode = tre_parse(&parse_ctx); + if (errcode != REG_OK) + ERROR_EXIT(errcode); + preg->re_nsub = parse_ctx.submatch_id - 1; + tree = parse_ctx.result; + + /* Back references and approximate matching cannot currently be used + in the same regexp. */ + if (parse_ctx.max_backref >= 0 && parse_ctx.have_approx) + ERROR_EXIT(REG_BADPAT); + +#ifdef TRE_DEBUG + tre_ast_print(tree); +#endif /* TRE_DEBUG */ + + /* Referring to nonexistent subexpressions is illegal. */ + if (parse_ctx.max_backref > (int)preg->re_nsub) + ERROR_EXIT(REG_ESUBREG); + + /* Allocate the TNFA struct. */ + tnfa = xcalloc(1, sizeof(tre_tnfa_t)); + if (tnfa == NULL) + ERROR_EXIT(REG_ESPACE); + tnfa->have_backrefs = parse_ctx.max_backref >= 0; + tnfa->have_approx = parse_ctx.have_approx; + tnfa->num_submatches = parse_ctx.submatch_id; + + /* Set up tags for submatch addressing. If REG_NOSUB is set and the + regexp does not have back references, this can be skipped. */ + if (tnfa->have_backrefs || !(cflags & REG_NOSUB)) + { + + /* Figure out how many tags we will need. */ + errcode = tre_add_tags(NULL, stack, tree, tnfa); + if (errcode != REG_OK) + ERROR_EXIT(errcode); + + if (tnfa->num_tags > 0) + { + tag_directions = xmalloc(sizeof(*tag_directions) + * (tnfa->num_tags + 1)); + if (tag_directions == NULL) + ERROR_EXIT(REG_ESPACE); + tnfa->tag_directions = tag_directions; + memset(tag_directions, -1, + sizeof(*tag_directions) * (tnfa->num_tags + 1)); + } + tnfa->minimal_tags = xcalloc((unsigned)tnfa->num_tags * 2 + 1, + sizeof(tnfa->minimal_tags)); + if (tnfa->minimal_tags == NULL) + ERROR_EXIT(REG_ESPACE); + + submatch_data = xcalloc((unsigned)parse_ctx.submatch_id, + sizeof(*submatch_data)); + if (submatch_data == NULL) + ERROR_EXIT(REG_ESPACE); + tnfa->submatch_data = submatch_data; + + errcode = tre_add_tags(mem, stack, tree, tnfa); + if (errcode != REG_OK) + ERROR_EXIT(errcode); + + } + + /* Expand iteration nodes. */ + errcode = tre_expand_ast(mem, stack, tree, &parse_ctx.position, + tag_directions); + if (errcode != REG_OK) + ERROR_EXIT(errcode); + + /* Add a dummy node for the final state. + XXX - For certain patterns this dummy node can be optimized away, + for example "a*" or "ab*". Figure out a simple way to detect + this possibility. */ + tmp_ast_l = tree; + tmp_ast_r = tre_ast_new_literal(mem, 0, 0, parse_ctx.position++); + if (tmp_ast_r == NULL) + ERROR_EXIT(REG_ESPACE); + + tree = tre_ast_new_catenation(mem, tmp_ast_l, tmp_ast_r); + if (tree == NULL) + ERROR_EXIT(REG_ESPACE); + + errcode = tre_compute_nfl(mem, stack, tree); + if (errcode != REG_OK) + ERROR_EXIT(errcode); + + counts = xmalloc(sizeof(int) * parse_ctx.position); + if (counts == NULL) + ERROR_EXIT(REG_ESPACE); + + offs = xmalloc(sizeof(int) * parse_ctx.position); + if (offs == NULL) + ERROR_EXIT(REG_ESPACE); + + for (i = 0; i < parse_ctx.position; i++) + counts[i] = 0; + tre_ast_to_tnfa(tree, NULL, counts, NULL); + + add = 0; + for (i = 0; i < parse_ctx.position; i++) + { + offs[i] = add; + add += counts[i] + 1; + counts[i] = 0; + } + transitions = xcalloc((unsigned)add + 1, sizeof(*transitions)); + if (transitions == NULL) + ERROR_EXIT(REG_ESPACE); + tnfa->transitions = transitions; + tnfa->num_transitions = add; + + errcode = tre_ast_to_tnfa(tree, transitions, counts, offs); + if (errcode != REG_OK) + ERROR_EXIT(errcode); + + tnfa->firstpos_chars = NULL; + + p = tree->firstpos; + i = 0; + while (p->position >= 0) + { + i++; + p++; + } + + initial = xcalloc((unsigned)i + 1, sizeof(tre_tnfa_transition_t)); + if (initial == NULL) + ERROR_EXIT(REG_ESPACE); + tnfa->initial = initial; + + i = 0; + for (p = tree->firstpos; p->position >= 0; p++) + { + initial[i].state = transitions + offs[p->position]; + initial[i].state_id = p->position; + initial[i].tags = NULL; + /* Copy the arrays p->tags, and p->params, they are allocated + from a tre_mem object. */ + if (p->tags) + { + int j; + for (j = 0; p->tags[j] >= 0; j++); + initial[i].tags = xmalloc(sizeof(*p->tags) * (j + 1)); + if (!initial[i].tags) + ERROR_EXIT(REG_ESPACE); + memcpy(initial[i].tags, p->tags, sizeof(*p->tags) * (j + 1)); + } + initial[i].assertions = p->assertions; + i++; + } + initial[i].state = NULL; + + tnfa->num_transitions = add; + tnfa->final = transitions + offs[tree->lastpos[0].position]; + tnfa->num_states = parse_ctx.position; + tnfa->cflags = cflags; + + tre_mem_destroy(mem); + tre_stack_destroy(stack); + xfree(counts); + xfree(offs); + + preg->TRE_REGEX_T_FIELD = (void *)tnfa; + return REG_OK; + + error_exit: + /* Free everything that was allocated and return the error code. */ + tre_mem_destroy(mem); + if (stack != NULL) + tre_stack_destroy(stack); + if (counts != NULL) + xfree(counts); + if (offs != NULL) + xfree(offs); + preg->TRE_REGEX_T_FIELD = (void *)tnfa; + regfree(preg); + return errcode; +} + + + + +void +regfree(regex_t *preg) +{ + tre_tnfa_t *tnfa; + unsigned int i; + tre_tnfa_transition_t *trans; + + tnfa = (void *)preg->TRE_REGEX_T_FIELD; + if (!tnfa) + return; + + for (i = 0; i < tnfa->num_transitions; i++) + if (tnfa->transitions[i].state) + { + if (tnfa->transitions[i].tags) + xfree(tnfa->transitions[i].tags); + if (tnfa->transitions[i].neg_classes) + xfree(tnfa->transitions[i].neg_classes); + } + if (tnfa->transitions) + xfree(tnfa->transitions); + + if (tnfa->initial) + { + for (trans = tnfa->initial; trans->state; trans++) + { + if (trans->tags) + xfree(trans->tags); + } + xfree(tnfa->initial); + } + + if (tnfa->submatch_data) + { + for (i = 0; i < tnfa->num_submatches; i++) + if (tnfa->submatch_data[i].parents) + xfree(tnfa->submatch_data[i].parents); + xfree(tnfa->submatch_data); + } + + if (tnfa->tag_directions) + xfree(tnfa->tag_directions); + if (tnfa->firstpos_chars) + xfree(tnfa->firstpos_chars); + if (tnfa->minimal_tags) + xfree(tnfa->minimal_tags); + xfree(tnfa); +} diff --git a/system/lib/libc/musl/src/regex/regerror.c b/system/lib/libc/musl/src/regex/regerror.c new file mode 100644 index 00000000..df4afa4f --- /dev/null +++ b/system/lib/libc/musl/src/regex/regerror.c @@ -0,0 +1,35 @@ +#include <string.h> +#include <regex.h> +#include <stdio.h> + +/* Error message strings for error codes listed in `regex.h'. This list + needs to be in sync with the codes listed there, naturally. */ + +/* Converted to single string by Rich Felker to remove the need for + * data relocations at runtime, 27 Feb 2006. */ + +static const char messages[] = { + "No error\0" + "No match\0" + "Invalid regexp\0" + "Unknown collating element\0" + "Unknown character class name\0" + "Trailing backslash\0" + "Invalid back reference\0" + "Missing ']'\0" + "Missing ')'\0" + "Missing '}'\0" + "Invalid contents of {}\0" + "Invalid character range\0" + "Out of memory\0" + "Repetition not preceded by valid expression\0" + "\0Unknown error" +}; + +size_t regerror(int e, const regex_t *restrict preg, char *restrict buf, size_t size) +{ + const char *s; + for (s=messages; e && *s; e--, s+=strlen(s)+1); + if (!*s) s++; + return 1+snprintf(buf, size, "%s", s); +} diff --git a/system/lib/libc/musl/src/regex/regexec.c b/system/lib/libc/musl/src/regex/regexec.c new file mode 100644 index 00000000..855cef57 --- /dev/null +++ b/system/lib/libc/musl/src/regex/regexec.c @@ -0,0 +1,1011 @@ +/* + regexec.c - TRE POSIX compatible matching functions (and more). + + Copyright (c) 2001-2009 Ville Laurikari <vl@iki.fi> + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include <stdlib.h> +#include <string.h> +#include <wchar.h> +#include <wctype.h> +#include <limits.h> + +#include <regex.h> + +#include "tre.h" + +#include <assert.h> + +static void +tre_fill_pmatch(size_t nmatch, regmatch_t pmatch[], int cflags, + const tre_tnfa_t *tnfa, int *tags, int match_eo); + +/*********************************************************************** + from tre-match-utils.h +***********************************************************************/ + +#define GET_NEXT_WCHAR() do { \ + prev_c = next_c; pos += pos_add_next; \ + if ((pos_add_next = mbtowc(&next_c, str_byte, MB_LEN_MAX)) <= 0) { \ + if (pos_add_next < 0) return REG_NOMATCH; \ + else pos_add_next++; \ + } \ + str_byte += pos_add_next; \ + } while (0) + +#define IS_WORD_CHAR(c) ((c) == L'_' || tre_isalnum(c)) + +#define CHECK_ASSERTIONS(assertions) \ + (((assertions & ASSERT_AT_BOL) \ + && (pos > 0 || reg_notbol) \ + && (prev_c != L'\n' || !reg_newline)) \ + || ((assertions & ASSERT_AT_EOL) \ + && (next_c != L'\0' || reg_noteol) \ + && (next_c != L'\n' || !reg_newline)) \ + || ((assertions & ASSERT_AT_BOW) \ + && (IS_WORD_CHAR(prev_c) || !IS_WORD_CHAR(next_c))) \ + || ((assertions & ASSERT_AT_EOW) \ + && (!IS_WORD_CHAR(prev_c) || IS_WORD_CHAR(next_c))) \ + || ((assertions & ASSERT_AT_WB) \ + && (pos != 0 && next_c != L'\0' \ + && IS_WORD_CHAR(prev_c) == IS_WORD_CHAR(next_c))) \ + || ((assertions & ASSERT_AT_WB_NEG) \ + && (pos == 0 || next_c == L'\0' \ + || IS_WORD_CHAR(prev_c) != IS_WORD_CHAR(next_c)))) + +#define CHECK_CHAR_CLASSES(trans_i, tnfa, eflags) \ + (((trans_i->assertions & ASSERT_CHAR_CLASS) \ + && !(tnfa->cflags & REG_ICASE) \ + && !tre_isctype((tre_cint_t)prev_c, trans_i->u.class)) \ + || ((trans_i->assertions & ASSERT_CHAR_CLASS) \ + && (tnfa->cflags & REG_ICASE) \ + && !tre_isctype(tre_tolower((tre_cint_t)prev_c),trans_i->u.class) \ + && !tre_isctype(tre_toupper((tre_cint_t)prev_c),trans_i->u.class)) \ + || ((trans_i->assertions & ASSERT_CHAR_CLASS_NEG) \ + && tre_neg_char_classes_match(trans_i->neg_classes,(tre_cint_t)prev_c,\ + tnfa->cflags & REG_ICASE))) + + + + +/* Returns 1 if `t1' wins `t2', 0 otherwise. */ +static int +tre_tag_order(int num_tags, tre_tag_direction_t *tag_directions, + int *t1, int *t2) +{ + int i; + for (i = 0; i < num_tags; i++) + { + if (tag_directions[i] == TRE_TAG_MINIMIZE) + { + if (t1[i] < t2[i]) + return 1; + if (t1[i] > t2[i]) + return 0; + } + else + { + if (t1[i] > t2[i]) + return 1; + if (t1[i] < t2[i]) + return 0; + } + } + /* assert(0);*/ + return 0; +} + +static int +tre_neg_char_classes_match(tre_ctype_t *classes, tre_cint_t wc, int icase) +{ + while (*classes != (tre_ctype_t)0) + if ((!icase && tre_isctype(wc, *classes)) + || (icase && (tre_isctype(tre_toupper(wc), *classes) + || tre_isctype(tre_tolower(wc), *classes)))) + return 1; /* Match. */ + else + classes++; + return 0; /* No match. */ +} + + +/*********************************************************************** + from tre-match-parallel.c +***********************************************************************/ + +/* + This algorithm searches for matches basically by reading characters + in the searched string one by one, starting at the beginning. All + matching paths in the TNFA are traversed in parallel. When two or + more paths reach the same state, exactly one is chosen according to + tag ordering rules; if returning submatches is not required it does + not matter which path is chosen. + + The worst case time required for finding the leftmost and longest + match, or determining that there is no match, is always linearly + dependent on the length of the text being searched. + + This algorithm cannot handle TNFAs with back referencing nodes. + See `tre-match-backtrack.c'. +*/ + +typedef struct { + tre_tnfa_transition_t *state; + int *tags; +} tre_tnfa_reach_t; + +typedef struct { + int pos; + int **tags; +} tre_reach_pos_t; + + +static reg_errcode_t +tre_tnfa_run_parallel(const tre_tnfa_t *tnfa, const void *string, + int *match_tags, int eflags, + int *match_end_ofs) +{ + /* State variables required by GET_NEXT_WCHAR. */ + tre_char_t prev_c = 0, next_c = 0; + const char *str_byte = string; + int pos = -1; + int pos_add_next = 1; +#ifdef TRE_MBSTATE + mbstate_t mbstate; +#endif /* TRE_MBSTATE */ + int reg_notbol = eflags & REG_NOTBOL; + int reg_noteol = eflags & REG_NOTEOL; + int reg_newline = tnfa->cflags & REG_NEWLINE; + + char *buf; + tre_tnfa_transition_t *trans_i; + tre_tnfa_reach_t *reach, *reach_next, *reach_i, *reach_next_i; + tre_reach_pos_t *reach_pos; + int *tag_i; + int num_tags, i; + + int match_eo = -1; /* end offset of match (-1 if no match found yet) */ + int new_match = 0; + int *tmp_tags = NULL; + int *tmp_iptr; + +#ifdef TRE_MBSTATE + memset(&mbstate, '\0', sizeof(mbstate)); +#endif /* TRE_MBSTATE */ + + if (!match_tags) + num_tags = 0; + else + num_tags = tnfa->num_tags; + + /* Allocate memory for temporary data required for matching. This needs to + be done for every matching operation to be thread safe. This allocates + everything in a single large block from the stack frame using alloca() + or with malloc() if alloca is unavailable. */ + { + int tbytes, rbytes, pbytes, xbytes, total_bytes; + char *tmp_buf; + /* Compute the length of the block we need. */ + tbytes = sizeof(*tmp_tags) * num_tags; + rbytes = sizeof(*reach_next) * (tnfa->num_states + 1); + pbytes = sizeof(*reach_pos) * tnfa->num_states; + xbytes = sizeof(int) * num_tags; + total_bytes = + (sizeof(long) - 1) * 4 /* for alignment paddings */ + + (rbytes + xbytes * tnfa->num_states) * 2 + tbytes + pbytes; + + /* Allocate the memory. */ + buf = xmalloc((unsigned)total_bytes); + if (buf == NULL) + return REG_ESPACE; + memset(buf, 0, (size_t)total_bytes); + + /* Get the various pointers within tmp_buf (properly aligned). */ + tmp_tags = (void *)buf; + tmp_buf = buf + tbytes; + tmp_buf += ALIGN(tmp_buf, long); + reach_next = (void *)tmp_buf; + tmp_buf += rbytes; + tmp_buf += ALIGN(tmp_buf, long); + reach = (void *)tmp_buf; + tmp_buf += rbytes; + tmp_buf += ALIGN(tmp_buf, long); + reach_pos = (void *)tmp_buf; + tmp_buf += pbytes; + tmp_buf += ALIGN(tmp_buf, long); + for (i = 0; i < tnfa->num_states; i++) + { + reach[i].tags = (void *)tmp_buf; + tmp_buf += xbytes; + reach_next[i].tags = (void *)tmp_buf; + tmp_buf += xbytes; + } + } + + for (i = 0; i < tnfa->num_states; i++) + reach_pos[i].pos = -1; + + GET_NEXT_WCHAR(); + pos = 0; + + reach_next_i = reach_next; + while (1) + { + /* If no match found yet, add the initial states to `reach_next'. */ + if (match_eo < 0) + { + trans_i = tnfa->initial; + while (trans_i->state != NULL) + { + if (reach_pos[trans_i->state_id].pos < pos) + { + if (trans_i->assertions + && CHECK_ASSERTIONS(trans_i->assertions)) + { + trans_i++; + continue; + } + + reach_next_i->state = trans_i->state; + for (i = 0; i < num_tags; i++) + reach_next_i->tags[i] = -1; + tag_i = trans_i->tags; + if (tag_i) + while (*tag_i >= 0) + { + if (*tag_i < num_tags) + reach_next_i->tags[*tag_i] = pos; + tag_i++; + } + if (reach_next_i->state == tnfa->final) + { + match_eo = pos; + new_match = 1; + for (i = 0; i < num_tags; i++) + match_tags[i] = reach_next_i->tags[i]; + } + reach_pos[trans_i->state_id].pos = pos; + reach_pos[trans_i->state_id].tags = &reach_next_i->tags; + reach_next_i++; + } + trans_i++; + } + reach_next_i->state = NULL; + } + else + { + if (num_tags == 0 || reach_next_i == reach_next) + /* We have found a match. */ + break; + } + + /* Check for end of string. */ + if (!next_c) break; + + GET_NEXT_WCHAR(); + + /* Swap `reach' and `reach_next'. */ + reach_i = reach; + reach = reach_next; + reach_next = reach_i; + + /* For each state in `reach', weed out states that don't fulfill the + minimal matching conditions. */ + if (tnfa->num_minimals && new_match) + { + new_match = 0; + reach_next_i = reach_next; + for (reach_i = reach; reach_i->state; reach_i++) + { + int skip = 0; + for (i = 0; tnfa->minimal_tags[i] >= 0; i += 2) + { + int end = tnfa->minimal_tags[i]; + int start = tnfa->minimal_tags[i + 1]; + if (end >= num_tags) + { + skip = 1; + break; + } + else if (reach_i->tags[start] == match_tags[start] + && reach_i->tags[end] < match_tags[end]) + { + skip = 1; + break; + } + } + if (!skip) + { + reach_next_i->state = reach_i->state; + tmp_iptr = reach_next_i->tags; + reach_next_i->tags = reach_i->tags; + reach_i->tags = tmp_iptr; + reach_next_i++; + } + } + reach_next_i->state = NULL; + + /* Swap `reach' and `reach_next'. */ + reach_i = reach; + reach = reach_next; + reach_next = reach_i; + } + + /* For each state in `reach' see if there is a transition leaving with + the current input symbol to a state not yet in `reach_next', and + add the destination states to `reach_next'. */ + reach_next_i = reach_next; + for (reach_i = reach; reach_i->state; reach_i++) + { + for (trans_i = reach_i->state; trans_i->state; trans_i++) + { + /* Does this transition match the input symbol? */ + if (trans_i->code_min <= (tre_cint_t)prev_c && + trans_i->code_max >= (tre_cint_t)prev_c) + { + if (trans_i->assertions + && (CHECK_ASSERTIONS(trans_i->assertions) + || CHECK_CHAR_CLASSES(trans_i, tnfa, eflags))) + { + continue; + } + + /* Compute the tags after this transition. */ + for (i = 0; i < num_tags; i++) + tmp_tags[i] = reach_i->tags[i]; + tag_i = trans_i->tags; + if (tag_i != NULL) + while (*tag_i >= 0) + { + if (*tag_i < num_tags) + tmp_tags[*tag_i] = pos; + tag_i++; + } + + if (reach_pos[trans_i->state_id].pos < pos) + { + /* Found an unvisited node. */ + reach_next_i->state = trans_i->state; + tmp_iptr = reach_next_i->tags; + reach_next_i->tags = tmp_tags; + tmp_tags = tmp_iptr; + reach_pos[trans_i->state_id].pos = pos; + reach_pos[trans_i->state_id].tags = &reach_next_i->tags; + + if (reach_next_i->state == tnfa->final + && (match_eo == -1 + || (num_tags > 0 + && reach_next_i->tags[0] <= match_tags[0]))) + { + match_eo = pos; + new_match = 1; + for (i = 0; i < num_tags; i++) + match_tags[i] = reach_next_i->tags[i]; + } + reach_next_i++; + + } + else + { + assert(reach_pos[trans_i->state_id].pos == pos); + /* Another path has also reached this state. We choose + the winner by examining the tag values for both + paths. */ + if (tre_tag_order(num_tags, tnfa->tag_directions, + tmp_tags, + *reach_pos[trans_i->state_id].tags)) + { + /* The new path wins. */ + tmp_iptr = *reach_pos[trans_i->state_id].tags; + *reach_pos[trans_i->state_id].tags = tmp_tags; + if (trans_i->state == tnfa->final) + { + match_eo = pos; + new_match = 1; + for (i = 0; i < num_tags; i++) + match_tags[i] = tmp_tags[i]; + } + tmp_tags = tmp_iptr; + } + } + } + } + } + reach_next_i->state = NULL; + } + + if (buf) + xfree(buf); + + *match_end_ofs = match_eo; + return match_eo >= 0 ? REG_OK : REG_NOMATCH; +} + + + +/*********************************************************************** + from tre-match-backtrack.c +***********************************************************************/ + +/* + This matcher is for regexps that use back referencing. Regexp matching + with back referencing is an NP-complete problem on the number of back + references. The easiest way to match them is to use a backtracking + routine which basically goes through all possible paths in the TNFA + and chooses the one which results in the best (leftmost and longest) + match. This can be spectacularly expensive and may run out of stack + space, but there really is no better known generic algorithm. Quoting + Henry Spencer from comp.compilers: + <URL: http://compilers.iecc.com/comparch/article/93-03-102> + + POSIX.2 REs require longest match, which is really exciting to + implement since the obsolete ("basic") variant also includes + \<digit>. I haven't found a better way of tackling this than doing + a preliminary match using a DFA (or simulation) on a modified RE + that just replicates subREs for \<digit>, and then doing a + backtracking match to determine whether the subRE matches were + right. This can be rather slow, but I console myself with the + thought that people who use \<digit> deserve very slow execution. + (Pun unintentional but very appropriate.) + +*/ + +typedef struct { + int pos; + const char *str_byte; + tre_tnfa_transition_t *state; + int state_id; + int next_c; + int *tags; +#ifdef TRE_MBSTATE + mbstate_t mbstate; +#endif /* TRE_MBSTATE */ +} tre_backtrack_item_t; + +typedef struct tre_backtrack_struct { + tre_backtrack_item_t item; + struct tre_backtrack_struct *prev; + struct tre_backtrack_struct *next; +} *tre_backtrack_t; + +#ifdef TRE_MBSTATE +#define BT_STACK_MBSTATE_IN stack->item.mbstate = (mbstate) +#define BT_STACK_MBSTATE_OUT (mbstate) = stack->item.mbstate +#else /* !TRE_MBSTATE */ +#define BT_STACK_MBSTATE_IN +#define BT_STACK_MBSTATE_OUT +#endif /* !TRE_MBSTATE */ + +#define tre_bt_mem_new tre_mem_new +#define tre_bt_mem_alloc tre_mem_alloc +#define tre_bt_mem_destroy tre_mem_destroy + + +#define BT_STACK_PUSH(_pos, _str_byte, _str_wide, _state, _state_id, _next_c, _tags, _mbstate) \ + do \ + { \ + int i; \ + if (!stack->next) \ + { \ + tre_backtrack_t s; \ + s = tre_bt_mem_alloc(mem, sizeof(*s)); \ + if (!s) \ + { \ + tre_bt_mem_destroy(mem); \ + if (tags) \ + xfree(tags); \ + if (pmatch) \ + xfree(pmatch); \ + if (states_seen) \ + xfree(states_seen); \ + return REG_ESPACE; \ + } \ + s->prev = stack; \ + s->next = NULL; \ + s->item.tags = tre_bt_mem_alloc(mem, \ + sizeof(*tags) * tnfa->num_tags); \ + if (!s->item.tags) \ + { \ + tre_bt_mem_destroy(mem); \ + if (tags) \ + xfree(tags); \ + if (pmatch) \ + xfree(pmatch); \ + if (states_seen) \ + xfree(states_seen); \ + return REG_ESPACE; \ + } \ + stack->next = s; \ + stack = s; \ + } \ + else \ + stack = stack->next; \ + stack->item.pos = (_pos); \ + stack->item.str_byte = (_str_byte); \ + stack->item.state = (_state); \ + stack->item.state_id = (_state_id); \ + stack->item.next_c = (_next_c); \ + for (i = 0; i < tnfa->num_tags; i++) \ + stack->item.tags[i] = (_tags)[i]; \ + BT_STACK_MBSTATE_IN; \ + } \ + while (0) + +#define BT_STACK_POP() \ + do \ + { \ + int i; \ + assert(stack->prev); \ + pos = stack->item.pos; \ + str_byte = stack->item.str_byte; \ + state = stack->item.state; \ + next_c = stack->item.next_c; \ + for (i = 0; i < tnfa->num_tags; i++) \ + tags[i] = stack->item.tags[i]; \ + BT_STACK_MBSTATE_OUT; \ + stack = stack->prev; \ + } \ + while (0) + +#undef MIN +#define MIN(a, b) ((a) <= (b) ? (a) : (b)) + +static reg_errcode_t +tre_tnfa_run_backtrack(const tre_tnfa_t *tnfa, const void *string, + int *match_tags, int eflags, int *match_end_ofs) +{ + /* State variables required by GET_NEXT_WCHAR. */ + tre_char_t prev_c = 0, next_c = 0; + const char *str_byte = string; + int pos = 0; + int pos_add_next = 1; +#ifdef TRE_MBSTATE + mbstate_t mbstate; +#endif /* TRE_MBSTATE */ + int reg_notbol = eflags & REG_NOTBOL; + int reg_noteol = eflags & REG_NOTEOL; + int reg_newline = tnfa->cflags & REG_NEWLINE; + + /* These are used to remember the necessary values of the above + variables to return to the position where the current search + started from. */ + int next_c_start; + const char *str_byte_start; + int pos_start = -1; +#ifdef TRE_MBSTATE + mbstate_t mbstate_start; +#endif /* TRE_MBSTATE */ + + /* End offset of best match so far, or -1 if no match found yet. */ + int match_eo = -1; + /* Tag arrays. */ + int *next_tags, *tags = NULL; + /* Current TNFA state. */ + tre_tnfa_transition_t *state; + int *states_seen = NULL; + + /* Memory allocator to for allocating the backtracking stack. */ + tre_mem_t mem = tre_bt_mem_new(); + + /* The backtracking stack. */ + tre_backtrack_t stack; + + tre_tnfa_transition_t *trans_i; + regmatch_t *pmatch = NULL; + int ret; + +#ifdef TRE_MBSTATE + memset(&mbstate, '\0', sizeof(mbstate)); +#endif /* TRE_MBSTATE */ + + if (!mem) + return REG_ESPACE; + stack = tre_bt_mem_alloc(mem, sizeof(*stack)); + if (!stack) + { + ret = REG_ESPACE; + goto error_exit; + } + stack->prev = NULL; + stack->next = NULL; + + if (tnfa->num_tags) + { + tags = xmalloc(sizeof(*tags) * tnfa->num_tags); + if (!tags) + { + ret = REG_ESPACE; + goto error_exit; + } + } + if (tnfa->num_submatches) + { + pmatch = xmalloc(sizeof(*pmatch) * tnfa->num_submatches); + if (!pmatch) + { + ret = REG_ESPACE; + goto error_exit; + } + } + if (tnfa->num_states) + { + states_seen = xmalloc(sizeof(*states_seen) * tnfa->num_states); + if (!states_seen) + { + ret = REG_ESPACE; + goto error_exit; + } + } + + retry: + { + int i; + for (i = 0; i < tnfa->num_tags; i++) + { + tags[i] = -1; + if (match_tags) + match_tags[i] = -1; + } + for (i = 0; i < tnfa->num_states; i++) + states_seen[i] = 0; + } + + state = NULL; + pos = pos_start; + GET_NEXT_WCHAR(); + pos_start = pos; + next_c_start = next_c; + str_byte_start = str_byte; +#ifdef TRE_MBSTATE + mbstate_start = mbstate; +#endif /* TRE_MBSTATE */ + + /* Handle initial states. */ + next_tags = NULL; + for (trans_i = tnfa->initial; trans_i->state; trans_i++) + { + if (trans_i->assertions && CHECK_ASSERTIONS(trans_i->assertions)) + { + continue; + } + if (state == NULL) + { + /* Start from this state. */ + state = trans_i->state; + next_tags = trans_i->tags; + } + else + { + /* Backtrack to this state. */ + BT_STACK_PUSH(pos, str_byte, 0, trans_i->state, + trans_i->state_id, next_c, tags, mbstate); + { + int *tmp = trans_i->tags; + if (tmp) + while (*tmp >= 0) + stack->item.tags[*tmp++] = pos; + } + } + } + + if (next_tags) + for (; *next_tags >= 0; next_tags++) + tags[*next_tags] = pos; + + + if (state == NULL) + goto backtrack; + + while (1) + { + tre_tnfa_transition_t *next_state; + int empty_br_match; + + if (state == tnfa->final) + { + if (match_eo < pos + || (match_eo == pos + && match_tags + && tre_tag_order(tnfa->num_tags, tnfa->tag_directions, + tags, match_tags))) + { + int i; + /* This match wins the previous match. */ + match_eo = pos; + if (match_tags) + for (i = 0; i < tnfa->num_tags; i++) + match_tags[i] = tags[i]; + } + /* Our TNFAs never have transitions leaving from the final state, + so we jump right to backtracking. */ + goto backtrack; + } + + /* Go to the next character in the input string. */ + empty_br_match = 0; + trans_i = state; + if (trans_i->state && trans_i->assertions & ASSERT_BACKREF) + { + /* This is a back reference state. All transitions leaving from + this state have the same back reference "assertion". Instead + of reading the next character, we match the back reference. */ + int so, eo, bt = trans_i->u.backref; + int bt_len; + int result; + + /* Get the substring we need to match against. Remember to + turn off REG_NOSUB temporarily. */ + tre_fill_pmatch(bt + 1, pmatch, tnfa->cflags & ~REG_NOSUB, + tnfa, tags, pos); + so = pmatch[bt].rm_so; + eo = pmatch[bt].rm_eo; + bt_len = eo - so; + + result = strncmp((const char*)string + so, str_byte - 1, + (size_t)bt_len); + + if (result == 0) + { + /* Back reference matched. Check for infinite loop. */ + if (bt_len == 0) + empty_br_match = 1; + if (empty_br_match && states_seen[trans_i->state_id]) + { + goto backtrack; + } + + states_seen[trans_i->state_id] = empty_br_match; + + /* Advance in input string and resync `prev_c', `next_c' + and pos. */ + str_byte += bt_len - 1; + pos += bt_len - 1; + GET_NEXT_WCHAR(); + } + else + { + goto backtrack; + } + } + else + { + /* Check for end of string. */ + if (next_c == L'\0') + goto backtrack; + + /* Read the next character. */ + GET_NEXT_WCHAR(); + } + + next_state = NULL; + for (trans_i = state; trans_i->state; trans_i++) + { + if (trans_i->code_min <= (tre_cint_t)prev_c + && trans_i->code_max >= (tre_cint_t)prev_c) + { + if (trans_i->assertions + && (CHECK_ASSERTIONS(trans_i->assertions) + || CHECK_CHAR_CLASSES(trans_i, tnfa, eflags))) + { + continue; + } + + if (next_state == NULL) + { + /* First matching transition. */ + next_state = trans_i->state; + next_tags = trans_i->tags; + } + else + { + /* Second matching transition. We may need to backtrack here + to take this transition instead of the first one, so we + push this transition in the backtracking stack so we can + jump back here if needed. */ + BT_STACK_PUSH(pos, str_byte, 0, trans_i->state, + trans_i->state_id, next_c, tags, mbstate); + { + int *tmp; + for (tmp = trans_i->tags; tmp && *tmp >= 0; tmp++) + stack->item.tags[*tmp] = pos; + } +#if 0 /* XXX - it's important not to look at all transitions here to keep + the stack small! */ + break; +#endif + } + } + } + + if (next_state != NULL) + { + /* Matching transitions were found. Take the first one. */ + state = next_state; + + /* Update the tag values. */ + if (next_tags) + while (*next_tags >= 0) + tags[*next_tags++] = pos; + } + else + { + backtrack: + /* A matching transition was not found. Try to backtrack. */ + if (stack->prev) + { + if (stack->item.state->assertions & ASSERT_BACKREF) + { + states_seen[stack->item.state_id] = 0; + } + + BT_STACK_POP(); + } + else if (match_eo < 0) + { + /* Try starting from a later position in the input string. */ + /* Check for end of string. */ + if (next_c == L'\0') + { + break; + } + next_c = next_c_start; +#ifdef TRE_MBSTATE + mbstate = mbstate_start; +#endif /* TRE_MBSTATE */ + str_byte = str_byte_start; + goto retry; + } + else + { + break; + } + } + } + + ret = match_eo >= 0 ? REG_OK : REG_NOMATCH; + *match_end_ofs = match_eo; + + error_exit: + tre_bt_mem_destroy(mem); +#ifndef TRE_USE_ALLOCA + if (tags) + xfree(tags); + if (pmatch) + xfree(pmatch); + if (states_seen) + xfree(states_seen); +#endif /* !TRE_USE_ALLOCA */ + + return ret; +} + +/*********************************************************************** + from regexec.c +***********************************************************************/ + +/* Fills the POSIX.2 regmatch_t array according to the TNFA tag and match + endpoint values. */ +static void +tre_fill_pmatch(size_t nmatch, regmatch_t pmatch[], int cflags, + const tre_tnfa_t *tnfa, int *tags, int match_eo) +{ + tre_submatch_data_t *submatch_data; + unsigned int i, j; + int *parents; + + i = 0; + if (match_eo >= 0 && !(cflags & REG_NOSUB)) + { + /* Construct submatch offsets from the tags. */ + submatch_data = tnfa->submatch_data; + while (i < tnfa->num_submatches && i < nmatch) + { + if (submatch_data[i].so_tag == tnfa->end_tag) + pmatch[i].rm_so = match_eo; + else + pmatch[i].rm_so = tags[submatch_data[i].so_tag]; + + if (submatch_data[i].eo_tag == tnfa->end_tag) + pmatch[i].rm_eo = match_eo; + else + pmatch[i].rm_eo = tags[submatch_data[i].eo_tag]; + + /* If either of the endpoints were not used, this submatch + was not part of the match. */ + if (pmatch[i].rm_so == -1 || pmatch[i].rm_eo == -1) + pmatch[i].rm_so = pmatch[i].rm_eo = -1; + + i++; + } + /* Reset all submatches that are not within all of their parent + submatches. */ + i = 0; + while (i < tnfa->num_submatches && i < nmatch) + { + if (pmatch[i].rm_eo == -1) + assert(pmatch[i].rm_so == -1); + assert(pmatch[i].rm_so <= pmatch[i].rm_eo); + + parents = submatch_data[i].parents; + if (parents != NULL) + for (j = 0; parents[j] >= 0; j++) + { + if (pmatch[i].rm_so < pmatch[parents[j]].rm_so + || pmatch[i].rm_eo > pmatch[parents[j]].rm_eo) + pmatch[i].rm_so = pmatch[i].rm_eo = -1; + } + i++; + } + } + + while (i < nmatch) + { + pmatch[i].rm_so = -1; + pmatch[i].rm_eo = -1; + i++; + } +} + + +/* + Wrapper functions for POSIX compatible regexp matching. +*/ + +int +regexec(const regex_t *restrict preg, const char *restrict string, + size_t nmatch, regmatch_t pmatch[restrict], int eflags) +{ + tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD; + reg_errcode_t status; + int *tags = NULL, eo; + if (tnfa->num_tags > 0 && nmatch > 0) + { + tags = xmalloc(sizeof(*tags) * tnfa->num_tags); + if (tags == NULL) + return REG_ESPACE; + } + + /* Dispatch to the appropriate matcher. */ + if (tnfa->have_backrefs) + { + /* The regex has back references, use the backtracking matcher. */ + status = tre_tnfa_run_backtrack(tnfa, string, tags, eflags, &eo); + } + else + { + /* Exact matching, no back references, use the parallel matcher. */ + status = tre_tnfa_run_parallel(tnfa, string, tags, eflags, &eo); + } + + if (status == REG_OK) + /* A match was found, so fill the submatch registers. */ + tre_fill_pmatch(nmatch, pmatch, tnfa->cflags, tnfa, tags, eo); + if (tags) + xfree(tags); + return status; +} diff --git a/system/lib/libc/musl/src/regex/tre-mem.c b/system/lib/libc/musl/src/regex/tre-mem.c new file mode 100644 index 00000000..86f809d4 --- /dev/null +++ b/system/lib/libc/musl/src/regex/tre-mem.c @@ -0,0 +1,158 @@ +/* + tre-mem.c - TRE memory allocator + + Copyright (c) 2001-2009 Ville Laurikari <vl@iki.fi> + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +/* + This memory allocator is for allocating small memory blocks efficiently + in terms of memory overhead and execution speed. The allocated blocks + cannot be freed individually, only all at once. There can be multiple + allocators, though. +*/ + +#include <stdlib.h> +#include <string.h> + +#include "tre.h" + +/* + This memory allocator is for allocating small memory blocks efficiently + in terms of memory overhead and execution speed. The allocated blocks + cannot be freed individually, only all at once. There can be multiple + allocators, though. +*/ + +/* Returns a new memory allocator or NULL if out of memory. */ +tre_mem_t +tre_mem_new_impl(int provided, void *provided_block) +{ + tre_mem_t mem; + if (provided) + { + mem = provided_block; + memset(mem, 0, sizeof(*mem)); + } + else + mem = xcalloc(1, sizeof(*mem)); + if (mem == NULL) + return NULL; + return mem; +} + + +/* Frees the memory allocator and all memory allocated with it. */ +void +tre_mem_destroy(tre_mem_t mem) +{ + tre_list_t *tmp, *l = mem->blocks; + + while (l != NULL) + { + xfree(l->data); + tmp = l->next; + xfree(l); + l = tmp; + } + xfree(mem); +} + + +/* Allocates a block of `size' bytes from `mem'. Returns a pointer to the + allocated block or NULL if an underlying malloc() failed. */ +void * +tre_mem_alloc_impl(tre_mem_t mem, int provided, void *provided_block, + int zero, size_t size) +{ + void *ptr; + + if (mem->failed) + { + return NULL; + } + + if (mem->n < size) + { + /* We need more memory than is available in the current block. + Allocate a new block. */ + tre_list_t *l; + if (provided) + { + if (provided_block == NULL) + { + mem->failed = 1; + return NULL; + } + mem->ptr = provided_block; + mem->n = TRE_MEM_BLOCK_SIZE; + } + else + { + int block_size; + if (size * 8 > TRE_MEM_BLOCK_SIZE) + block_size = size * 8; + else + block_size = TRE_MEM_BLOCK_SIZE; + l = xmalloc(sizeof(*l)); + if (l == NULL) + { + mem->failed = 1; + return NULL; + } + l->data = xmalloc(block_size); + if (l->data == NULL) + { + xfree(l); + mem->failed = 1; + return NULL; + } + l->next = NULL; + if (mem->current != NULL) + mem->current->next = l; + if (mem->blocks == NULL) + mem->blocks = l; + mem->current = l; + mem->ptr = l->data; + mem->n = block_size; + } + } + + /* Make sure the next pointer will be aligned. */ + size += ALIGN(mem->ptr + size, long); + + /* Allocate from current block. */ + ptr = mem->ptr; + mem->ptr += size; + mem->n -= size; + + /* Set to zero if needed. */ + if (zero) + memset(ptr, 0, size); + + return ptr; +} diff --git a/system/lib/libc/musl/src/regex/tre.h b/system/lib/libc/musl/src/regex/tre.h new file mode 100644 index 00000000..67cb9a84 --- /dev/null +++ b/system/lib/libc/musl/src/regex/tre.h @@ -0,0 +1,231 @@ +/* + tre-internal.h - TRE internal definitions + + Copyright (c) 2001-2009 Ville Laurikari <vl@iki.fi> + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include <regex.h> +#include <wchar.h> +#include <wctype.h> + +#undef TRE_MBSTATE + +#define NDEBUG + +#define TRE_REGEX_T_FIELD __opaque +typedef int reg_errcode_t; + +typedef wchar_t tre_char_t; + +#define DPRINT(msg) do { } while(0) + +#define elementsof(x) ( sizeof(x) / sizeof(x[0]) ) + +#define tre_mbrtowc(pwc, s, n, ps) (mbtowc((pwc), (s), (n))) + +/* Wide characters. */ +typedef wint_t tre_cint_t; +#define TRE_CHAR_MAX 0x10ffff + +#define tre_isalnum iswalnum +#define tre_isalpha iswalpha +#define tre_isblank iswblank +#define tre_iscntrl iswcntrl +#define tre_isdigit iswdigit +#define tre_isgraph iswgraph +#define tre_islower iswlower +#define tre_isprint iswprint +#define tre_ispunct iswpunct +#define tre_isspace iswspace +#define tre_isupper iswupper +#define tre_isxdigit iswxdigit + +#define tre_tolower towlower +#define tre_toupper towupper +#define tre_strlen wcslen + +/* Use system provided iswctype() and wctype(). */ +typedef wctype_t tre_ctype_t; +#define tre_isctype iswctype +#define tre_ctype wctype + +/* Returns number of bytes to add to (char *)ptr to make it + properly aligned for the type. */ +#define ALIGN(ptr, type) \ + ((((long)ptr) % sizeof(type)) \ + ? (sizeof(type) - (((long)ptr) % sizeof(type))) \ + : 0) + +#undef MAX +#undef MIN +#define MAX(a, b) (((a) >= (b)) ? (a) : (b)) +#define MIN(a, b) (((a) <= (b)) ? (a) : (b)) + +/* TNFA transition type. A TNFA state is an array of transitions, + the terminator is a transition with NULL `state'. */ +typedef struct tnfa_transition tre_tnfa_transition_t; + +struct tnfa_transition { + /* Range of accepted characters. */ + tre_cint_t code_min; + tre_cint_t code_max; + /* Pointer to the destination state. */ + tre_tnfa_transition_t *state; + /* ID number of the destination state. */ + int state_id; + /* -1 terminated array of tags (or NULL). */ + int *tags; + /* Assertion bitmap. */ + int assertions; + /* Assertion parameters. */ + union { + /* Character class assertion. */ + tre_ctype_t class; + /* Back reference assertion. */ + int backref; + } u; + /* Negative character class assertions. */ + tre_ctype_t *neg_classes; +}; + + +/* Assertions. */ +#define ASSERT_AT_BOL 1 /* Beginning of line. */ +#define ASSERT_AT_EOL 2 /* End of line. */ +#define ASSERT_CHAR_CLASS 4 /* Character class in `class'. */ +#define ASSERT_CHAR_CLASS_NEG 8 /* Character classes in `neg_classes'. */ +#define ASSERT_AT_BOW 16 /* Beginning of word. */ +#define ASSERT_AT_EOW 32 /* End of word. */ +#define ASSERT_AT_WB 64 /* Word boundary. */ +#define ASSERT_AT_WB_NEG 128 /* Not a word boundary. */ +#define ASSERT_BACKREF 256 /* A back reference in `backref'. */ +#define ASSERT_LAST 256 + +/* Tag directions. */ +typedef enum { + TRE_TAG_MINIMIZE = 0, + TRE_TAG_MAXIMIZE = 1 +} tre_tag_direction_t; + +/* Instructions to compute submatch register values from tag values + after a successful match. */ +struct tre_submatch_data { + /* Tag that gives the value for rm_so (submatch start offset). */ + int so_tag; + /* Tag that gives the value for rm_eo (submatch end offset). */ + int eo_tag; + /* List of submatches this submatch is contained in. */ + int *parents; +}; + +typedef struct tre_submatch_data tre_submatch_data_t; + + +/* TNFA definition. */ +typedef struct tnfa tre_tnfa_t; + +struct tnfa { + tre_tnfa_transition_t *transitions; + unsigned int num_transitions; + tre_tnfa_transition_t *initial; + tre_tnfa_transition_t *final; + tre_submatch_data_t *submatch_data; + char *firstpos_chars; + int first_char; + unsigned int num_submatches; + tre_tag_direction_t *tag_directions; + int *minimal_tags; + int num_tags; + int num_minimals; + int end_tag; + int num_states; + int cflags; + int have_backrefs; + int have_approx; +}; + +/* from tre-mem.h: */ + +#define TRE_MEM_BLOCK_SIZE 1024 + +typedef struct tre_list { + void *data; + struct tre_list *next; +} tre_list_t; + +typedef struct tre_mem_struct { + tre_list_t *blocks; + tre_list_t *current; + char *ptr; + size_t n; + int failed; + void **provided; +} *tre_mem_t; + +#define tre_mem_new_impl __tre_mem_new_impl +#define tre_mem_alloc_impl __tre_mem_alloc_impl +#define tre_mem_destroy __tre_mem_destroy + +tre_mem_t tre_mem_new_impl(int provided, void *provided_block); +void *tre_mem_alloc_impl(tre_mem_t mem, int provided, void *provided_block, + int zero, size_t size); + +/* Returns a new memory allocator or NULL if out of memory. */ +#define tre_mem_new() tre_mem_new_impl(0, NULL) + +/* Allocates a block of `size' bytes from `mem'. Returns a pointer to the + allocated block or NULL if an underlying malloc() failed. */ +#define tre_mem_alloc(mem, size) tre_mem_alloc_impl(mem, 0, NULL, 0, size) + +/* Allocates a block of `size' bytes from `mem'. Returns a pointer to the + allocated block or NULL if an underlying malloc() failed. The memory + is set to zero. */ +#define tre_mem_calloc(mem, size) tre_mem_alloc_impl(mem, 0, NULL, 1, size) + +#ifdef TRE_USE_ALLOCA +/* alloca() versions. Like above, but memory is allocated with alloca() + instead of malloc(). */ + +#define tre_mem_newa() \ + tre_mem_new_impl(1, alloca(sizeof(struct tre_mem_struct))) + +#define tre_mem_alloca(mem, size) \ + ((mem)->n >= (size) \ + ? tre_mem_alloc_impl((mem), 1, NULL, 0, (size)) \ + : tre_mem_alloc_impl((mem), 1, alloca(TRE_MEM_BLOCK_SIZE), 0, (size))) +#endif /* TRE_USE_ALLOCA */ + + +/* Frees the memory allocator and all memory allocated with it. */ +void tre_mem_destroy(tre_mem_t mem); + +#define xmalloc malloc +#define xcalloc calloc +#define xfree free +#define xrealloc realloc + diff --git a/system/lib/libcextra.symbols b/system/lib/libcextra.symbols index 522137c6..d169ead6 100644 --- a/system/lib/libcextra.symbols +++ b/system/lib/libcextra.symbols @@ -2,6 +2,9 @@ T __strxfrm_l W __towlower_l W __towupper_l + T __tre_mem_alloc_impl + T __tre_mem_destroy + T __tre_mem_new_impl T __wcscoll_l T __wcsxfrm_l W __wctype_l @@ -47,6 +50,10 @@ T mbsrtowcs T mbstowcs T mbtowc + T regcomp + T regerror + T regexec + T regfree T strfmon T strfmon_l T strxfrm diff --git a/tests/cases/storebigfloat.ll b/tests/cases/storebigfloat.ll new file mode 100644 index 00000000..c9995835 --- /dev/null +++ b/tests/cases/storebigfloat.ll @@ -0,0 +1,17 @@ + +@.str = private unnamed_addr constant [15 x i8] c"hello, world!\0A\00", align 1 ; [#uses=1 type=[15 x i8]*] + +; [#uses=0] +define i32 @main() { +entry: + %retval = alloca i32, align 4 ; [#uses=1 type=i32*] + %f = alloca float, align 4 + store float 1.000000e+10, float* %f, align 4 + store i32 0, i32* %retval + %call = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([15 x i8]* @.str, i32 0, i32 0)) ; [#uses=0 type=i32] + ret i32 1 +} + +; [#uses=1] +declare i32 @printf(i8*, ...) + diff --git a/tests/emscripten_get_now.cpp b/tests/emscripten_get_now.cpp index 17aa7d32..5ededb23 100644 --- a/tests/emscripten_get_now.cpp +++ b/tests/emscripten_get_now.cpp @@ -14,10 +14,10 @@ int main() { // b) Values returned by emscripten_get_now() are strictly nondecreasing. // c) emscripten_get_now() is able to return sub-millisecond precision timer values. bool detected_good_timer_precision = false; - float smallest_delta = 0.f; + double smallest_delta = 0.f; for(int x = 0; x < 1000; ++x) { // Have several attempts to find a good small delta, i.e. give time to JS engine to warm up the code and so on. - float t = emscripten_get_now(); - float t2 = emscripten_get_now(); + double t = emscripten_get_now(); + double t2 = emscripten_get_now(); for(int i = 0; i < 100 && t == t2; ++i) { t2 = emscripten_get_now(); } diff --git a/tests/gles2_conformance.cpp b/tests/gles2_conformance.cpp new file mode 100644 index 00000000..80539f7f --- /dev/null +++ b/tests/gles2_conformance.cpp @@ -0,0 +1,76 @@ +#include "SDL/SDL.h" + +#include <GLES2/gl2.h> + +#include <stdio.h> +#include <string.h> + +int result = 1; // Success +#define assert(x) do { if (!(x)) {result = 0; printf("Assertion failure: %s in %s:%d!\n", #x, __FILE__, __LINE__); } } while(0) + +int main(int argc, char *argv[]) +{ + SDL_Surface *screen; + + // Slightly different SDL initialization + if ( SDL_Init(SDL_INIT_VIDEO) != 0 ) { + printf("Unable to initialize SDL: %s\n", SDL_GetError()); + return 1; + } + + screen = SDL_SetVideoMode( 640, 480, 16, SDL_OPENGL ); // *changed* + if ( !screen ) { + printf("Unable to set video mode: %s\n", SDL_GetError()); + return 1; + } + + // Test that code containing functions related to GLES2 binary shader API will successfully compile ad run + // (will be nonfunctional no-ops since WebGL doesn't have binary shaders) + GLuint vs = glCreateShader(GL_VERTEX_SHADER); + glShaderBinary(1, &vs, 0, 0, 0); + assert(glGetError() != GL_NO_ERROR); + + GLboolean b = GL_TRUE; + GLint i = -1; + GLfloat f = -1.f; + glGetBooleanv(GL_NUM_SHADER_BINARY_FORMATS, &b); + assert(glGetError() == GL_NO_ERROR); + assert(b == GL_FALSE); + glGetIntegerv(GL_NUM_SHADER_BINARY_FORMATS, &i); + assert(glGetError() == GL_NO_ERROR); + assert(i == 0); + glGetFloatv(GL_NUM_SHADER_BINARY_FORMATS, &f); + assert(glGetError() == GL_NO_ERROR); + assert(f == 0.f); + + // Currently testing that glGetIntegerv(GL_SHADER_BINARY_FORMATS) should be a no-op. + // The spec is somewhat vague here, equally as good could be to return GL_INVALID_ENUM here. + i = 123; + glGetIntegerv(GL_SHADER_BINARY_FORMATS, &i); + assert(glGetError() == GL_NO_ERROR); + assert(i == 0); + + // Spec does not say what to report on the following, but since GL_SHADER_BINARY_FORMATS is supposed + // to return a a pointer to an array representing a list, the pointer can't be converted to bool or float, + // so report a GL_INVALID_ENUM. + glGetBooleanv(GL_SHADER_BINARY_FORMATS, &b); + assert(glGetError() == GL_INVALID_ENUM); + + glGetFloatv(GL_SHADER_BINARY_FORMATS, &f); + assert(glGetError() == GL_INVALID_ENUM); + + // Test that we can query for shader compiler support. + glGetIntegerv(GL_SHADER_COMPILER, &i); + assert(glGetError() == GL_NO_ERROR); + assert(i != 0); + glGetBooleanv(GL_SHADER_COMPILER, &b); + assert(glGetError() == GL_NO_ERROR); + assert(b == GL_TRUE); + glGetFloatv(GL_SHADER_COMPILER, &f); + assert(glGetError() == GL_NO_ERROR); + assert(f == 1.f); + +#ifdef REPORT_RESULT + REPORT_RESULT(); +#endif +} diff --git a/tests/gles2_uniform_arrays.cpp b/tests/gles2_uniform_arrays.cpp index 84e394dc..7293f9a9 100644 --- a/tests/gles2_uniform_arrays.cpp +++ b/tests/gles2_uniform_arrays.cpp @@ -35,6 +35,15 @@ void RunTest(int testVariant) glBindAttribLocation(program, 0, "pos"); glLinkProgram(program); + // Also test that GL_ACTIVE_ATTRIBUTE_MAX_LENGTH and GL_ACTIVE_UNIFORM_MAX_LENGTH work. See https://github.com/kripken/emscripten/issues/1796. + GLint param; + glGetProgramiv(program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, ¶m); + printf("active attrib max length: %d\n", param); + assert(param == 4); // "pos"+null terminator + glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, ¶m); + printf("active uniform max length: %d\n", param); + assert(param == 10); // "colors[0]"+null terminator + int color_loc = glGetUniformLocation(program, "color"); assert(color_loc != -1); diff --git a/tests/lua/Makefile b/tests/lua/Makefile index bd9515fd..9f0a2edd 100644 --- a/tests/lua/Makefile +++ b/tests/lua/Makefile @@ -51,8 +51,9 @@ R= $V.1 # Targets start here. all: $(PLAT) +# XXX Emscripten Added quotes to $(MAKE) to properly call make when the path contains spaces $(PLATS) clean: - cd src && $(MAKE) $@ + cd src && "$(MAKE)" $@ test: dummy src/lua -v diff --git a/tests/lua/src/Makefile b/tests/lua/src/Makefile index 401e7367..a9cf0911 100644 --- a/tests/lua/src/Makefile +++ b/tests/lua/src/Makefile @@ -59,8 +59,9 @@ o: $(ALL_O) a: $(ALL_A) +# XXX EMSCRIPTEN: add AR_ARGS $(LUA_A): $(BASE_O) - $(AR) $(AR_ARGS) $@ $(BASE_O) # XXX EMSCRIPTEN: add AR_ARGS + $(AR) $(AR_ARGS) $@ $(BASE_O) $(RANLIB) $@ $(LUA_T): $(LUA_O) $(LUA_A) diff --git a/tests/runner.py b/tests/runner.py index 867f7113..7f513635 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -36,7 +36,7 @@ except: # Core test runner class, shared between normal tests and benchmarks checked_sanity = False -test_modes = ['default', 'o1', 'o2', 'asm1', 'asm2', 'asm2g', 'asm2x86', 's_0_0', 's_0_1'] +test_modes = ['default', 'o1', 'o2', 'asm1', 'asm2', 'asm2f', 'asm2g', 'asm2x86', 's_0_0', 's_0_1'] test_index = 0 class RunnerCore(unittest.TestCase): @@ -673,6 +673,24 @@ class BrowserCore(RunnerCore): ################################################################################################### +# Both test_core and test_other access the Bullet library, share the access here to avoid duplication. +def get_bullet_library(runner_core, use_cmake): + if use_cmake: + configure_commands = ['cmake', '.'] + configure_args = ['-DBUILD_DEMOS=OFF', '-DBUILD_EXTRAS=OFF'] + # Depending on whether 'configure' or 'cmake' is used to build, Bullet places output files in different directory structures. + generated_libs = [os.path.join('src', 'BulletDynamics', 'libBulletDynamics.a'), + os.path.join('src', 'BulletCollision', 'libBulletCollision.a'), + os.path.join('src', 'LinearMath', 'libLinearMath.a')] + else: + configure_commands = ['sh', './configure'] + configure_args = ['--disable-demos','--disable-dependency-tracking'] + generated_libs = [os.path.join('src', '.libs', 'libBulletDynamics.a'), + os.path.join('src', '.libs', 'libBulletCollision.a'), + os.path.join('src', '.libs', 'libLinearMath.a')] + + return runner_core.get_library('bullet', generated_libs, configure=configure_commands, configure_args=configure_args, cache_name_extra=configure_commands[0]) + if __name__ == '__main__': # Sanity checks total_engines = len(JS_ENGINES) diff --git a/tests/sdl_joystick.c b/tests/sdl_joystick.c new file mode 100644 index 00000000..50802c31 --- /dev/null +++ b/tests/sdl_joystick.c @@ -0,0 +1,124 @@ +#include <stdio.h> +#include <SDL/SDL.h> +#include <SDL/SDL_ttf.h> +#include <assert.h> +#include <string.h> +#include <emscripten.h> + +int result = 1; + +void assertJoystickEvent(int expectedGamepad, int expectedType, int expectedIndex, int expectedValue) { + SDL_Event event; + while(1) { + // Loop ends either when assertion fails (we run out of events), or we find + // the event we're looking for. + assert(SDL_PollEvent(&event) == 1); + if (event.type != expectedType) { + continue; + } + switch(event.type) { + case SDL_JOYAXISMOTION: { + assert(event.jaxis.which == expectedGamepad); + assert(event.jaxis.axis == expectedIndex); + assert(event.jaxis.value == expectedValue); + break; + } + case SDL_JOYBUTTONUP: case SDL_JOYBUTTONDOWN: { + assert(event.jbutton.which == expectedGamepad); + assert(event.jbutton.button == expectedIndex); + assert(event.jbutton.state == expectedValue); + break; + } + } + // Break out of while loop. + break; + } +} + +void assertNoJoystickEvent() { + SDL_Event event; + while(SDL_PollEvent(&event)) { + switch(event.type) { + case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: case SDL_JOYAXISMOTION: { + // Fail. + assert(0); + } + } + } +} + +void main_2(void* arg); + +int main() { + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK); + SDL_Surface *screen = SDL_SetVideoMode(600, 450, 32, SDL_HWSURFACE); + emscripten_async_call(main_2, NULL, 3000); // avoid startup delays and intermittent errors + return 0; +} + +void main_2(void* arg) { + // TODO: At the moment, we only support joystick support through polling. + emscripten_run_script("window.addNewGamepad('Pad Thai', 4, 16)"); + emscripten_run_script("window.addNewGamepad('Pad Kee Mao', 0, 4)"); + // Check that the joysticks exist properly. + assert(SDL_NumJoysticks() == 2); + assert(!SDL_JoystickOpened(0)); + assert(!SDL_JoystickOpened(1)); + SDL_Joystick* pad1 = SDL_JoystickOpen(0); + assert(SDL_JoystickOpened(0)); + assert(SDL_JoystickIndex(pad1) == 0); + assert(strncmp(SDL_JoystickName(0), "Pad Thai", 9) == 0); + assert(strncmp(SDL_JoystickName(1), "Pad Kee Mao", 12) == 0); + assert(SDL_JoystickNumAxes(pad1) == 4); + assert(SDL_JoystickNumButtons(pad1) == 16); + + // Button events. + emscripten_run_script("window.simulateGamepadButtonDown(0, 1)"); + // We didn't tell SDL to automatically update this joystick's state. + assertNoJoystickEvent(); + SDL_JoystickUpdate(); + assertJoystickEvent(0, SDL_JOYBUTTONDOWN, 1, SDL_PRESSED); + assert(SDL_JoystickGetButton(pad1, 1) == 1); + // Enable automatic updates. + SDL_JoystickEventState(SDL_ENABLE); + assert(SDL_JoystickEventState(SDL_QUERY) == SDL_ENABLE); + emscripten_run_script("window.simulateGamepadButtonUp(0, 1)"); + assertJoystickEvent(0, SDL_JOYBUTTONUP, 1, SDL_RELEASED); + assert(SDL_JoystickGetButton(pad1, 1) == 0); + // No button change: Should not result in a new event. + emscripten_run_script("window.simulateGamepadButtonUp(0, 1)"); + assertNoJoystickEvent(); + // Joystick 1 is not opened; should not result in a new event. + emscripten_run_script("window.simulateGamepadButtonDown(1, 1)"); + assertNoJoystickEvent(); + + // Joystick wiggling + emscripten_run_script("window.simulateAxisMotion(0, 0, 1)"); + assertJoystickEvent(0, SDL_JOYAXISMOTION, 0, 32767); + assert(SDL_JoystickGetAxis(pad1, 0) == 32767); + emscripten_run_script("window.simulateAxisMotion(0, 0, 0)"); + assertJoystickEvent(0, SDL_JOYAXISMOTION, 0, 0); + assert(SDL_JoystickGetAxis(pad1, 0) == 0); + emscripten_run_script("window.simulateAxisMotion(0, 1, -1)"); + assertJoystickEvent(0, SDL_JOYAXISMOTION, 1, -32768); + assert(SDL_JoystickGetAxis(pad1, 1) == -32768); + emscripten_run_script("window.simulateAxisMotion(0, 1, -1)"); + // No joystick change: Should not result in a new event. + assertNoJoystickEvent(); + // Joystick 1 is not opened; should not result in a new event. + emscripten_run_script("window.simulateAxisMotion(1, 1, -1)"); + assertNoJoystickEvent(); + + SDL_JoystickClose(pad1); + assert(!SDL_JoystickOpened(0)); + + // Joystick 0 is closed; we should not process any new gamepad events from it. + emscripten_run_script("window.simulateGamepadButtonDown(0, 1)"); + assertNoJoystickEvent(); + + // End test. + result = 2; + printf("Test passed!\n"); + REPORT_RESULT(); +} + diff --git a/tests/sockets/test_getaddrinfo.c b/tests/sockets/test_getaddrinfo.c index 717a9ae7..1f912c69 100644 --- a/tests/sockets/test_getaddrinfo.c +++ b/tests/sockets/test_getaddrinfo.c @@ -174,6 +174,7 @@ int main() { assert(servinfo->ai_family == AF_INET); assert(servinfo->ai_socktype == SOCK_STREAM); assert(sa4->sin_port == ntohs(89)); + freeaddrinfo(servinfo); // test non-numeric host with AF_INET6 memset(&hints, 0, sizeof(hints)); @@ -189,6 +190,65 @@ int main() { *((uint32_t*)&(sa6->sin6_addr)+2) != 0 || *((uint32_t*)&(sa6->sin6_addr)+3) != 0); assert(sa6->sin6_port == ntohs(90)); + freeaddrinfo(servinfo); + + // test with NULL hints + // Specifying hints as NULL is equivalent to setting ai_socktype and ai_protocol to 0; + // ai_family to AF_UNSPEC; and ai_flags to (AI_V4MAPPED | AI_ADDRCONFIG) + // N.B. with NULL hints getaddrinfo should really be passing back multiple addrinfo structures in a + // linked list with next values given in ai_next. The current implementation doesn't do that yet but the + // following tests have assert(servinfo->ai_next == NULL) so that they will fail when multiple values do + // eventually get implemented, so we know to improve the tests then to cope with multiple values. + + // test numeric host + err = getaddrinfo("1.2.3.4", "85", NULL, &servinfo); + assert(!err); + sa4 = ((struct sockaddr_in*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET); + assert(servinfo->ai_socktype == SOCK_STREAM); + assert(servinfo->ai_protocol == IPPROTO_TCP); + assert(sa4->sin_port == ntohs(85)); + assert(servinfo->ai_next == NULL); + freeaddrinfo(servinfo); + + // test non-numeric host + err = getaddrinfo("www.mozilla.org", "89", NULL, &servinfo); + assert(!err); + sa4 = ((struct sockaddr_in*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET); + assert(servinfo->ai_socktype == SOCK_STREAM); + assert(servinfo->ai_protocol == IPPROTO_TCP); + assert(sa4->sin_port == ntohs(89)); + assert(servinfo->ai_next == NULL); + freeaddrinfo(servinfo); + + // test loopback resolution + err = getaddrinfo(NULL, "80", NULL, &servinfo); + assert(!err); + sa4 = ((struct sockaddr_in*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET); + assert(servinfo->ai_socktype == SOCK_STREAM); + assert(servinfo->ai_protocol == IPPROTO_TCP); + assert(sa4->sin_port == ntohs(80)); + assert(servinfo->ai_next == NULL); + freeaddrinfo(servinfo); + + // test gai_strerror + assert(strncmp(gai_strerror(0), "Success", 256) == 0); + assert(strncmp(gai_strerror(EAI_BADFLAGS), "Invalid value for 'ai_flags' field", 256) == 0); + assert(strncmp(gai_strerror(EAI_NONAME), "NAME or SERVICE is unknown", 256) == 0); + assert(strncmp(gai_strerror(EAI_AGAIN), "Temporary failure in name resolution", 256) == 0); + assert(strncmp(gai_strerror(EAI_FAIL), "Non-recoverable failure in name res", 256) == 0); + assert(strncmp(gai_strerror(EAI_FAMILY), "'ai_family' not supported", 256) == 0); + assert(strncmp(gai_strerror(EAI_SOCKTYPE), "'ai_socktype' not supported", 256) == 0); + assert(strncmp(gai_strerror(EAI_SERVICE), "SERVICE not supported for 'ai_socktype'", 256) == 0); + assert(strncmp(gai_strerror(EAI_MEMORY), "Memory allocation failure", 256) == 0); + assert(strncmp(gai_strerror(EAI_SYSTEM), "System error returned in 'errno'", 256) == 0); + assert(strncmp(gai_strerror(EAI_OVERFLOW), "Argument buffer overflow", 256) == 0); + assert(strncmp(gai_strerror(-5), "Unknown error", 256) == 0); + assert(strncmp(gai_strerror(-9), "Unknown error", 256) == 0); + assert(strncmp(gai_strerror(-13), "Unknown error", 256) == 0); + assert(strncmp(gai_strerror(-100), "Unknown error", 256) == 0); puts("success"); diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py index e9cfee52..63e0041f 100644 --- a/tests/test_benchmark.py +++ b/tests/test_benchmark.py @@ -13,11 +13,6 @@ from tools.shared import * DEFAULT_ARG = '4' TEST_REPS = 2 -TOTAL_TESTS = 8 - -tests_done = 0 -total_times = map(lambda x: 0., range(TOTAL_TESTS)) -total_native_times = map(lambda x: 0., range(TOTAL_TESTS)) class benchmark(RunnerCore): save_dir = True @@ -119,15 +114,15 @@ process(sys.argv[1]) try_delete(final_filename) output = Popen([PYTHON, EMCC, filename, #'-O3', '-O2', '-s', 'DOUBLE_MODE=0', '-s', 'PRECISE_I64_MATH=0', - '--llvm-lto', '3', '--memory-init-file', '0', '--js-transform', 'python hardcode.py', + '--memory-init-file', '0', '--js-transform', 'python hardcode.py', '-s', 'TOTAL_MEMORY=128*1024*1024', '--closure', '1', + #'-s', 'PRECISE_F32=1', #'-g', '-o', final_filename] + shared_args + emcc_args, stdout=PIPE, stderr=self.stderr_redirect).communicate() assert os.path.exists(final_filename), 'Failed to compile file: ' + output[0] # Run JS - global total_times, tests_done times = [] for i in range(reps): start = time.time() @@ -141,7 +136,6 @@ process(sys.argv[1]) else: curr = output_parser(js_output) times.append(curr) - total_times[tests_done] += curr if i == 0: # Sanity check on output self.assertContained(expected_output, js_output) @@ -152,7 +146,6 @@ process(sys.argv[1]) else: shutil.copyfile(native_exec, filename + '.native') shutil.copymode(native_exec, filename + '.native') - global total_native_times native_times = [] for i in range(reps): start = time.time() @@ -165,15 +158,9 @@ process(sys.argv[1]) else: curr = output_parser(native_output) native_times.append(curr) - total_native_times[tests_done] += curr self.print_stats(times, native_times, reps=reps) - #tests_done += 1 - #if tests_done == TOTAL_TESTS: - # print 'Total stats:', - # self.print_stats(total_times, total_native_times, last=True) - def test_primes(self): src = r''' #include<stdio.h> @@ -428,10 +415,15 @@ process(sys.argv[1]) src = open(path_from_root('tests', 'life.c'), 'r').read() self.do_benchmark('life', src, '''--------------------------------''', shared_args=['-std=c99'], force_c=True) - def test_linpack(self): + def test_linpack_double(self): def output_parser(output): return 100.0/float(re.search('Unrolled Double Precision +([\d\.]+) Mflops', output).group(1)) - self.do_benchmark('linpack', open(path_from_root('tests', 'linpack.c')).read(), '''Unrolled Double Precision''', force_c=True, output_parser=output_parser) + self.do_benchmark('linpack_double', open(path_from_root('tests', 'linpack.c')).read(), '''Unrolled Double Precision''', force_c=True, output_parser=output_parser) + + def test_linpack_float(self): # TODO: investigate if this might benefit from -ffast-math in LLVM 3.3+ which has fast math stuff in LLVM IR + def output_parser(output): + return 100.0/float(re.search('Unrolled Single Precision +([\d\.]+) Mflops', output).group(1)) + self.do_benchmark('linpack_float', open(path_from_root('tests', 'linpack.c')).read(), '''Unrolled Single Precision''', force_c=True, output_parser=output_parser, shared_args=['-DSP']) def test_zzz_java_nbody(self): # tests xmlvm compiled java, including bitcasts of doubles, i64 math, etc. args = [path_from_root('tests', 'nbody-java', x) for x in os.listdir(path_from_root('tests', 'nbody-java')) if x.endswith('.c')] + \ @@ -504,4 +496,4 @@ process(sys.argv[1]) native_args = native_lib + ['-I' + path_from_root('tests', 'bullet', 'src'), '-I' + path_from_root('tests', 'bullet', 'Demos', 'Benchmarks')] - self.do_benchmark('bullet', src, '\nok.\n', emcc_args=emcc_args, native_args=native_args)
\ No newline at end of file + self.do_benchmark('bullet', src, '\nok.\n', emcc_args=emcc_args, native_args=native_args) diff --git a/tests/test_browser.py b/tests/test_browser.py index d52f109f..65bccb38 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -874,6 +874,82 @@ keydown(100);keyup(100); // trigger the end def test_glut_wheelevents(self): self.btest('glut_wheelevents.c', '1') + def test_sdl_joystick_1(self): + # Generates events corresponding to the Working Draft of the HTML5 Gamepad API. + # http://www.w3.org/TR/2012/WD-gamepad-20120529/#gamepad-interface + open(os.path.join(self.get_dir(), 'pre.js'), 'w').write(''' + var gamepads = []; + // Spoof this function. + navigator['getGamepads'] = function() { + return gamepads; + }; + window['addNewGamepad'] = function(id, numAxes, numButtons) { + var index = gamepads.length; + gamepads.push({ + axes: new Array(numAxes), + buttons: new Array(numButtons), + id: id, + index: index + }); + var i; + for (i = 0; i < numAxes; i++) gamepads[index].axes[i] = 0; + for (i = 0; i < numButtons; i++) gamepads[index].buttons[i] = 0; + }; + window['simulateGamepadButtonDown'] = function (index, button) { + gamepads[index].buttons[button] = 1; + }; + window['simulateGamepadButtonUp'] = function (index, button) { + gamepads[index].buttons[button] = 0; + }; + window['simulateAxisMotion'] = function (index, axis, value) { + gamepads[index].axes[axis] = value; + }; + ''') + open(os.path.join(self.get_dir(), 'sdl_joystick.c'), 'w').write(self.with_report_result(open(path_from_root('tests', 'sdl_joystick.c')).read())) + + Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'sdl_joystick.c'), '-O2', '--minify', '0', '-o', 'page.html', '--pre-js', 'pre.js']).communicate() + self.run_browser('page.html', '', '/report_result?2') + + def test_sdl_joystick_2(self): + # Generates events corresponding to the Editor's Draft of the HTML5 Gamepad API. + # https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#idl-def-Gamepad + open(os.path.join(self.get_dir(), 'pre.js'), 'w').write(''' + var gamepads = []; + // Spoof this function. + navigator['getGamepads'] = function() { + return gamepads; + }; + window['addNewGamepad'] = function(id, numAxes, numButtons) { + var index = gamepads.length; + gamepads.push({ + axes: new Array(numAxes), + buttons: new Array(numButtons), + id: id, + index: index + }); + var i; + for (i = 0; i < numAxes; i++) gamepads[index].axes[i] = 0; + // Buttons are objects + for (i = 0; i < numButtons; i++) gamepads[index].buttons[i] = { pressed: false, value: 0 }; + }; + // FF mutates the original objects. + window['simulateGamepadButtonDown'] = function (index, button) { + gamepads[index].buttons[button].pressed = true; + gamepads[index].buttons[button].value = 1; + }; + window['simulateGamepadButtonUp'] = function (index, button) { + gamepads[index].buttons[button].pressed = false; + gamepads[index].buttons[button].value = 0; + }; + window['simulateAxisMotion'] = function (index, axis, value) { + gamepads[index].axes[axis] = value; + }; + ''') + open(os.path.join(self.get_dir(), 'sdl_joystick.c'), 'w').write(self.with_report_result(open(path_from_root('tests', 'sdl_joystick.c')).read())) + + Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'sdl_joystick.c'), '-O2', '--minify', '0', '-o', 'page.html', '--pre-js', 'pre.js']).communicate() + self.run_browser('page.html', '', '/report_result?2') + def test_webgl_context_attributes(self): # Javascript code to check the attributes support we want to test in the WebGL implementation # (request the attribute, create a context and check its value afterwards in the context attributes). @@ -1071,6 +1147,12 @@ keydown(100);keyup(100); // trigger the end Popen([PYTHON, EMCC, '-O2', os.path.join(self.get_dir(), 'glfw.c'), '-o', 'page.html', '-s', 'LEGACY_GL_EMULATION=1']).communicate() self.run_browser('page.html', '', '/report_result?1') + def test_egl(self): + open(os.path.join(self.get_dir(), 'test_egl.c'), 'w').write(self.with_report_result(open(path_from_root('tests', 'test_egl.c')).read())) + + Popen([PYTHON, EMCC, '-O2', os.path.join(self.get_dir(), 'test_egl.c'), '-o', 'page.html']).communicate() + self.run_browser('page.html', '', '/report_result?1') + def test_egl_width_height(self): open(os.path.join(self.get_dir(), 'test_egl_width_height.c'), 'w').write(self.with_report_result(open(path_from_root('tests', 'test_egl_width_height.c')).read())) @@ -1367,6 +1449,9 @@ keydown(100);keyup(100); // trigger the end # def test_gles2_uniform_arrays(self): # self.btest('gles2_uniform_arrays.cpp', args=['-s', 'GL_ASSERTIONS=1'], expected=['1']) + def test_gles2_conformance(self): + self.btest('gles2_conformance.cpp', args=['-s', 'GL_ASSERTIONS=1'], expected=['1']) + def test_matrix_identity(self): self.btest('gl_matrix_identity.c', expected=['-1882984448', '460451840'], args=['-s', 'LEGACY_GL_EMULATION=1']) diff --git a/tests/test_core.py b/tests/test_core.py index b2147d49..481130c5 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -3,7 +3,7 @@ import glob, hashlib, os, re, shutil, subprocess, sys import tools.shared from tools.shared import * -from runner import RunnerCore, path_from_root, checked_sanity, test_modes +from runner import RunnerCore, path_from_root, checked_sanity, test_modes, get_bullet_library class T(RunnerCore): # Short name, to make it more fun to use manually on the commandline def is_le32(self): @@ -882,6 +882,32 @@ nada ''' self.do_run(src, 'OK!\n'); + def test_float32_precise(self): + Settings.PRECISE_F32 = 1 + + src = r''' + #include <stdio.h> + + int main(int argc, char **argv) { + float x = 1.23456789123456789; + float y = 5.20456089123406709; + while (argc > 10 || argc % 19 == 15) { + // confuse optimizer + x /= y; + y = 2*y - 1; + argc--; + } + x = x - y; + y = 3*y - x/2; + x = x*y; + y += 0.000000000123123123123; + x -= y/7.654; + printf("\n%.20f, %.20f\n", x, y); + return 0; + } + ''' + self.do_run(src, '\n-72.16590881347656250000, 17.59867858886718750000\n') + def test_negative_zero(self): src = r''' #include <stdio.h> @@ -1490,7 +1516,7 @@ f6: nan #include <stdio.h> #include <stdlib.h> #include <cmath> - int main() + int main(int argc, char **argv) { printf("*%.2f,%.2f,%d", M_PI, -M_PI, (1/0.0) > 1e300); // could end up as infinity, or just a very very big number printf(",%d", isfinite(NAN) != 0); @@ -1512,11 +1538,15 @@ f6: nan sincosf(0.0, &fsine, &fcosine); printf(",%1.1f", fsine); printf(",%1.1f", fcosine); + fsine = sinf(1.1 + argc - 1); + fcosine = cosf(1.1 + argc - 1); + printf(",%1.1f", fsine); + printf(",%1.1f", fcosine); printf("*\\n"); return 0; } ''' - self.do_run(src, '*3.14,-3.14,1,0,0,0,1,0,1,1,0,2,3,0.0,1.0,0.0,1.0*') + self.do_run(src, '*3.14,-3.14,1,0,0,0,1,0,1,1,0,2,3,0.0,1.0,0.0,1.0,0.9,0.5*') def test_erf(self): src = ''' @@ -2175,6 +2205,49 @@ returned |umber one top notchfi FI FO FUM WHEN WHERE WHY HOW WHO|''', ['wowie', ''' self.do_run(src, 'wcslen: 5') + def test_regex(self): + # This is from http://pic.dhe.ibm.com/infocenter/iseries/v7r1m0/index.jsp?topic=%2Frtref%2Fregexec.htm + if self.emcc_args is None: return self.skip('needs emcc for libcextra') + src = r''' + #include <regex.h> + #include <stdio.h> + #include <stdlib.h> + + int main(void) + { + regex_t preg; + const char *string = "a very simple simple simple string"; + const char *pattern = "\\(sim[a-z]le\\) \\1"; + int rc; + size_t nmatch = 2; + regmatch_t pmatch[2]; + + if (0 != (rc = regcomp(&preg, pattern, 0))) { + printf("regcomp() failed, returning nonzero (%d)\n", rc); + exit(EXIT_FAILURE); + } + + if (0 != (rc = regexec(&preg, string, nmatch, pmatch, 0))) { + printf("Failed to match '%s' with '%s',returning %d.\n", + string, pattern, rc); + } + else { + printf("With the whole expression, " + "a matched substring \"%.*s\" is found at position %d to %d.\n", + pmatch[0].rm_eo - pmatch[0].rm_so, &string[pmatch[0].rm_so], + pmatch[0].rm_so, pmatch[0].rm_eo - 1); + printf("With the sub-expression, " + "a matched substring \"%.*s\" is found at position %d to %d.\n", + pmatch[1].rm_eo - pmatch[1].rm_so, &string[pmatch[1].rm_so], + pmatch[1].rm_so, pmatch[1].rm_eo - 1); + } + regfree(&preg); + return 0; + } + ''' + self.do_run(src, 'With the whole expression, a matched substring "simple simple" is found at position 7 to 19.\n' + 'With the sub-expression, a matched substring "simple" is found at position 7 to 12.') + def test_longjmp(self): src = r''' #include <stdio.h> @@ -2878,7 +2951,7 @@ Exiting setjmp function, level: 0, prev_jmp: -1 ''') self.emcc_args += ['--pre-js', 'pre.js'] - self.do_run(src, '''reported\nexit(1) called\nExit Status: 1\npostRun\nok.\n''') + self.do_run(src, '''reported\nExit Status: 1\npostRun\nok.\n''') def test_class(self): src = ''' @@ -3821,6 +3894,10 @@ def process(filename): double get() { double ret = 0; __asm __volatile__("Math.abs(-12/3.3)":"=r"(ret)); // write to a variable + asm("#comment1"); + asm volatile("#comment2"); + asm volatile("#comment3\n" + "#comment4\n"); return ret; } @@ -3839,6 +3916,9 @@ def process(filename): ''' self.do_run(src, 'Inline JS is very cool\n3.64\n') # TODO 1\n2\n3\n1\n2\n3\n') + if self.emcc_args == []: # opts will eliminate the comments + out = open('src.cpp.o.js').read() + for i in range(1, 5): assert ('comment%d' % i) in out def test_inlinejs2(self): if not self.is_le32(): return self.skip('le32 needed for inline js') @@ -8328,9 +8408,14 @@ extern "C" { if self.emcc_args is None: return self.skip('requires emcc') results = [ (1,'''GG*ctt**tgagc*'''), (20,'''GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTT*cttBtatcatatgctaKggNcataaaSatgtaaaDcDRtBggDtctttataattcBgtcg**tacgtgtagcctagtgtttgtgttgcgttatagtctatttgtggacacagtatggtcaaa**tgacgtcttttgatctgacggcgttaacaaagatactctg*'''), (50,'''GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGA*TCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACAT*cttBtatcatatgctaKggNcataaaSatgtaaaDcDRtBggDtctttataattcBgtcg**tactDtDagcctatttSVHtHttKtgtHMaSattgWaHKHttttagacatWatgtRgaaa**NtactMcSMtYtcMgRtacttctWBacgaa**agatactctgggcaacacacatacttctctcatgttgtttcttcggacctttcataacct**ttcctggcacatggttagctgcacatcacaggattgtaagggtctagtggttcagtgagc**ggaatatcattcgtcggtggtgttaatctatctcggtgtagcttataaatgcatccgtaa**gaatattatgtttatttgtcggtacgttcatggtagtggtgtcgccgatttagacgtaaa**ggcatgtatg*''') ] - for i, j in results: - src = open(path_from_root('tests', 'fasta.cpp'), 'r').read() - self.do_run(src, j, [str(i)], lambda x, err: x.replace('\n', '*'), no_build=i>1) + for precision in [0, 1, 2]: + Settings.PRECISE_F32 = precision + for t in ['float', 'double']: + print precision, t + src = open(path_from_root('tests', 'fasta.cpp'), 'r').read().replace('double', t) + for i, j in results: + self.do_run(src, j, [str(i)], lambda x, err: x.replace('\n', '*'), no_build=i>1) + shutil.copyfile('src.cpp.o.js', '%d_%s.js' % (precision, t)) def test_whets(self): if not Settings.ASM_JS: return self.skip('mainly a test for asm validation here') @@ -8860,6 +8945,7 @@ def process(filename): def test_sqlite(self): # gcc -O3 -I/home/alon/Dev/emscripten/tests/sqlite -ldl src.c if self.emcc_args is None: return self.skip('Very slow without ta2, and we would also need to include dlmalloc manually without emcc') + if not self.is_le32(): return self.skip('fails on x86 due to a legalization issue on llvm 3.3') if Settings.QUANTUM_SIZE == 1: return self.skip('TODO FIXME') self.banned_js_engines = [NODE_JS] # OOM in older node @@ -8915,35 +9001,23 @@ def process(filename): Settings.SAFE_HEAP_LINES = ['btVoronoiSimplexSolver.h:40', 'btVoronoiSimplexSolver.h:41', 'btVoronoiSimplexSolver.h:42', 'btVoronoiSimplexSolver.h:43'] - configure_commands = [['sh', './configure'], ['cmake', '.']] - configure_args = [['--disable-demos','--disable-dependency-tracking'], ['-DBUILD_DEMOS=OFF', '-DBUILD_EXTRAS=OFF']] - for c in range(0,2): - configure = configure_commands[c] + for use_cmake in [False, True]: # If false, use a configure script to configure Bullet build. + print 'cmake', use_cmake # Windows cannot run configure sh scripts. - if WINDOWS and configure[0] == 'sh': + if WINDOWS and not use_cmake: continue - # Depending on whether 'configure' or 'cmake' is used to build, Bullet places output files in different directory structures. - if configure[0] == 'sh': - generated_libs = [os.path.join('src', '.libs', 'libBulletDynamics.a'), - os.path.join('src', '.libs', 'libBulletCollision.a'), - os.path.join('src', '.libs', 'libLinearMath.a')] - else: - generated_libs = [os.path.join('src', 'BulletDynamics', 'libBulletDynamics.a'), - os.path.join('src', 'BulletCollision', 'libBulletCollision.a'), - os.path.join('src', 'LinearMath', 'libLinearMath.a')] - def test(): self.do_run(open(path_from_root('tests', 'bullet', 'Demos', 'HelloWorld', 'HelloWorld.cpp'), 'r').read(), [open(path_from_root('tests', 'bullet', 'output.txt'), 'r').read(), # different roundings open(path_from_root('tests', 'bullet', 'output2.txt'), 'r').read(), open(path_from_root('tests', 'bullet', 'output3.txt'), 'r').read()], - libraries=self.get_library('bullet', generated_libs, configure=configure, configure_args=configure_args[c], cache_name_extra=configure[0]), + libraries=get_bullet_library(self, use_cmake), includes=[path_from_root('tests', 'bullet', 'src')]) test() assert 'asm2g' in test_modes - if self.run_name == 'asm2g' and configure[0] == 'sh': + if self.run_name == 'asm2g' and not use_cmake: # Test forced alignment print >> sys.stderr, 'testing FORCE_ALIGNED_MEMORY' old = open('src.cpp.o.js').read() @@ -9463,7 +9537,7 @@ def process(filename): Settings.DEAD_FUNCTIONS = [] # Run the same code with argc that uses the dead function, see abort - test(('missing function: unused'), args=['a', 'b'], no_build=True) + test(('dead function: unused'), args=['a', 'b'], no_build=True) # Normal stuff run_all('normal', r''' @@ -10475,7 +10549,7 @@ def process(filename): Module.callMain(); ''') self.emcc_args += ['-s', 'INVOKE_RUN=0', '--post-js', 'post.js'] - self.do_run(src, 'hello, world!\nexit(118) called\ncleanup\nI see exit status: 118') + self.do_run(src, 'hello, world!\ncleanup\nI see exit status: 118') def test_gc(self): if self.emcc_args == None: return self.skip('needs ta2') @@ -10705,6 +10779,7 @@ o2 = make_run("o2", compiler=CLANG, emcc_args=["-O2", "-s", "ASM_JS=0", "-s", "J # asm.js asm1 = make_run("asm1", compiler=CLANG, emcc_args=["-O1"]) asm2 = make_run("asm2", compiler=CLANG, emcc_args=["-O2"]) +asm2f = make_run("asm2f", compiler=CLANG, emcc_args=["-O2", "-s", "PRECISE_F32=1"]) asm2g = make_run("asm2g", compiler=CLANG, emcc_args=["-O2", "-g", "-s", "ASSERTIONS=1", "--memory-init-file", "1", "-s", "CHECK_HEAP_ALIGN=1"]) asm2x86 = make_run("asm2x86", compiler=CLANG, emcc_args=["-O2", "-g", "-s", "CHECK_HEAP_ALIGN=1"], env={"EMCC_LLVM_TARGET": "i386-pc-linux-gnu"}) diff --git a/tests/test_egl.c b/tests/test_egl.c new file mode 100644 index 00000000..5864a797 --- /dev/null +++ b/tests/test_egl.c @@ -0,0 +1,73 @@ +#include <stdio.h> +#include <EGL/egl.h> + +int result = 1; // Success +#define assert(x) do { if (!(x)) {result = 0; printf("Assertion failure: %s in %s:%d!\n", #x, __FILE__, __LINE__); } } while(0) + +int main(int argc, char *argv[]) +{ + EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + assert(display != EGL_NO_DISPLAY); + assert(eglGetError() == EGL_SUCCESS); + + EGLint major = 0, minor = 0; + EGLBoolean ret = eglInitialize(display, &major, &minor); + assert(eglGetError() == EGL_SUCCESS); + assert(ret == EGL_TRUE); + assert(major * 10000 + minor >= 10004); + + EGLint numConfigs; + ret = eglGetConfigs(display, NULL, 0, &numConfigs); + assert(eglGetError() == EGL_SUCCESS); + assert(ret == EGL_TRUE); + + EGLint attribs[] = { + EGL_RED_SIZE, 5, + EGL_GREEN_SIZE, 6, + EGL_BLUE_SIZE, 5, + EGL_NONE + }; + EGLConfig config; + ret = eglChooseConfig(display, attribs, &config, 1, &numConfigs); + assert(eglGetError() == EGL_SUCCESS); + assert(ret == EGL_TRUE); + + EGLNativeWindowType dummyWindow; + EGLSurface surface = eglCreateWindowSurface(display, config, dummyWindow, NULL); + assert(eglGetError() == EGL_SUCCESS); + assert(surface != 0); + + EGLint contextAttribs[] = + { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + EGLContext context = eglCreateContext(display, config, NULL, contextAttribs); + assert(eglGetError() == EGL_SUCCESS); + assert(context != 0); + + assert(eglGetCurrentContext() == 0); // Creating a context does not yet activate it. + assert(eglGetError() == EGL_SUCCESS); + + ret = eglMakeCurrent(display, surface, surface, context); + assert(eglGetError() == EGL_SUCCESS); + assert(ret == EGL_TRUE); + assert(eglGetCurrentContext() == context); + assert(eglGetCurrentSurface(EGL_READ) == surface); + assert(eglGetCurrentSurface(EGL_DRAW) == surface); + + ret = eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + assert(eglGetError() == EGL_SUCCESS); + assert(ret == EGL_TRUE); + assert(eglGetCurrentContext() == EGL_NO_CONTEXT); + assert(eglGetCurrentSurface(EGL_READ) == EGL_NO_SURFACE); + assert(eglGetCurrentSurface(EGL_DRAW) == EGL_NO_SURFACE); + + ret = eglTerminate(display); + assert(eglGetError() == EGL_SUCCESS); + assert(ret == EGL_TRUE); + +#ifdef REPORT_RESULT + REPORT_RESULT(); +#endif +} diff --git a/tests/test_other.py b/tests/test_other.py index 584f6714..0dd0bd12 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -1,9 +1,15 @@ import multiprocessing, os, re, shutil, subprocess, sys import tools.shared from tools.shared import * -from runner import RunnerCore, path_from_root +from runner import RunnerCore, path_from_root, get_bullet_library class other(RunnerCore): + def get_zlib_library(self): + if WINDOWS: + return self.get_library('zlib', os.path.join('libz.a'), configure=['emconfigure.bat'], configure_args=['cmake', '.', '-DBUILD_SHARED_LIBS=OFF'], make=['mingw32-make'], make_args=[]) + else: + return self.get_library('zlib', os.path.join('libz.a'), make_args=['libz.a']) + def test_emcc(self): for compiler in [EMCC, EMXX]: shortcompiler = os.path.basename(compiler) @@ -169,7 +175,7 @@ Options that are modified or new in %s include: if keep_debug: assert ('(label)' in generated or '(label | 0)' in generated) == (opt_level <= 0), 'relooping should be in opt >= 1' assert ('assert(STACKTOP < STACK_MAX' in generated) == (opt_level == 0), 'assertions should be in opt == 0' - assert 'var $i;' in generated or 'var $i_0' in generated or 'var $storemerge3;' in generated or 'var $storemerge4;' in generated or '$i_04' in generated or '$i_05' in generated or 'var $original = 0' in generated, 'micro opts should always be on' + assert '$i' in generated or '$storemerge' in generated or '$original' in generated, 'micro opts should always be on' if opt_level >= 2 and '-g' in params: assert re.search('HEAP8\[\$?\w+ ?\+ ?\(+\$?\w+ ?', generated) or re.search('HEAP8\[HEAP32\[', generated), 'eliminator should create compound expressions, and fewer one-time vars' # also in -O1, but easier to test in -O2 assert ('_puts(' in generated) == (opt_level >= 1), 'with opt >= 1, llvm opts are run and they should optimize printf to puts' @@ -740,21 +746,21 @@ f.close() # zlib compression library. tests function pointers in initializers and many other things test('zlib', '', open(path_from_root('tests', 'zlib', 'example.c'), 'r').read(), - self.get_library('zlib', os.path.join('libz.a'), make_args=['libz.a']), + self.get_zlib_library(), open(path_from_root('tests', 'zlib', 'ref.txt'), 'r').read(), args=['-I' + path_from_root('tests', 'zlib')], suffix='c') + use_cmake = WINDOWS + bullet_library = get_bullet_library(self, use_cmake) + # bullet physics engine. tests all the things test('bullet', '', open(path_from_root('tests', 'bullet', 'Demos', 'HelloWorld', 'HelloWorld.cpp'), 'r').read(), - self.get_library('bullet', [os.path.join('src', '.libs', 'libBulletDynamics.a'), - os.path.join('src', '.libs', 'libBulletCollision.a'), - os.path.join('src', '.libs', 'libLinearMath.a')]), + bullet_library, [open(path_from_root('tests', 'bullet', 'output.txt'), 'r').read(), # different roundings open(path_from_root('tests', 'bullet', 'output2.txt'), 'r').read(), open(path_from_root('tests', 'bullet', 'output3.txt'), 'r').read()], args=['-I' + path_from_root('tests', 'bullet', 'src')]) - def test_outline(self): def test(name, src, libs, expected, expected_ranges, args=[], suffix='cpp'): print name @@ -820,7 +826,7 @@ f.close() ]: Building.COMPILER_TEST_OPTS = test_opts test('zlib', path_from_root('tests', 'zlib', 'example.c'), - self.get_library('zlib', os.path.join('libz.a'), make_args=['libz.a']), + self.get_zlib_library(), open(path_from_root('tests', 'zlib', 'ref.txt'), 'r').read(), expected_ranges, args=['-I' + path_from_root('tests', 'zlib')], suffix='c') @@ -1440,10 +1446,12 @@ f.close() extern "C" { void something(); + void elsey(); } int main() { something(); + elsey(); return 0; } ''') @@ -1451,26 +1459,24 @@ f.close() def clear(): try_delete('a.out.js') for args in [[], ['-O2']]: - clear() - print 'warn', args - output = Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'main.cpp'), '-s', 'WARN_ON_UNDEFINED_SYMBOLS=1'] + args, stderr=PIPE).communicate() - self.assertContained('unresolved symbol: something', output[1]) - - clear() - output = Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'main.cpp')] + args, stderr=PIPE).communicate() - self.assertNotContained('unresolved symbol: something\n', output[1]) - - for args in [[], ['-O2']]: - clear() - print 'error', args - output = Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'main.cpp'), '-s', 'ERROR_ON_UNDEFINED_SYMBOLS=1'] + args, stderr=PIPE).communicate() - self.assertContained('unresolved symbol: something', output[1]) - assert not os.path.exists('a.out.js') - - clear() - output = Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'main.cpp')] + args, stderr=PIPE).communicate() - self.assertNotContained('unresolved symbol: something\n', output[1]) - assert os.path.exists('a.out.js') + for action in ['WARN', 'ERROR', None]: + for value in ([0, 1] if action else [0]): + clear() + print 'warn', args, action, value + extra = ['-s', action + '_ON_UNDEFINED_SYMBOLS=%d' % value] if action else [] + output = Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'main.cpp')] + extra + args, stderr=PIPE).communicate() + if action == None or (action == 'WARN' and value): + self.assertContained('unresolved symbol: something', output[1]) + self.assertContained('unresolved symbol: elsey', output[1]) + assert os.path.exists('a.out.js') + elif action == 'ERROR' and value: + self.assertContained('unresolved symbol: something', output[1]) + self.assertContained('unresolved symbol: elsey', output[1]) + self.assertNotContained('warning', output[1]) + assert not os.path.exists('a.out.js') + elif action == 'WARN' and not value: + self.assertNotContained('unresolved symbol', output[1]) + assert os.path.exists('a.out.js') def test_toobig(self): # very large [N x i8], we should not oom in the compiler @@ -1909,7 +1915,12 @@ done. out, err = Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-E'], stdout=PIPE).communicate() assert not os.path.exists('a.out.js') - assert '''tests/hello_world.c"''' in out + # Test explicitly that the output contains a line typically written by the preprocessor. + # Clang outputs on Windows lines like "#line 1", on Unix '# 1 '. + # TODO: This is one more of those platform-specific discrepancies, investigate more if this ever becomes an issue, + # ideally we would have emcc output identical data on all platforms. + assert '''#line 1 ''' in out or '''# 1 ''' in out + assert '''hello_world.c"''' in out assert '''printf("hello, world!''' in out def test_demangle(self): @@ -1927,6 +1938,7 @@ done. EM_ASM(Module.print(demangle('_main'))); EM_ASM(Module.print(demangle('__Z2f2v'))); EM_ASM(Module.print(demangle('__Z12abcdabcdabcdi'))); + EM_ASM(Module.print(demangle('__ZL12abcdabcdabcdi'))); EM_ASM(Module.print(demangle('__Z4testcsifdPvPiPc'))); EM_ASM(Module.print(demangle('__ZN4test5moarrEcslfdPvPiPc'))); EM_ASM(Module.print(demangle('__ZN4Waka1f12a234123412345pointEv'))); @@ -1937,6 +1949,7 @@ done. EM_ASM(Module.print(demangle('__Z9parsewordRPKciRi'))); EM_ASM(Module.print(demangle('__Z5multiwahtjmxyz'))); EM_ASM(Module.print(demangle('__Z1aA32_iPA5_c'))); + EM_ASM(Module.print(demangle('__ZN21FWakaGLXFleeflsMarfooC2EjjjPKvbjj'))); one(17); return 0; } @@ -1944,9 +1957,11 @@ done. Popen([PYTHON, EMCC, 'src.cpp', '-s', 'LINKABLE=1']).communicate() output = run_js('a.out.js') - self.assertContained('''main + self.assertContained('''operator new() +_main f2() abcdabcdabcd(int) +abcdabcdabcd(int) test(char, short, int, float, double, void*, int*, char*) test::moarr(char, short, long, float, double, void*, int*, char*) Waka::f::a23412341234::point() @@ -1957,6 +1972,7 @@ __cxxabiv1::__si_class_type_info::search_below_dst(__cxxabiv1::__dynamic_cast_in parseword(char*&, int, int&) multi(wchar_t, signed char, unsigned char, unsigned short, unsigned int, unsigned long, long long, unsigned long long, ...) a(int [32], char [5]*) +FWakaGLXFleeflsMarfoo::FWakaGLXFleeflsMarfoo(unsigned int, unsigned int, unsigned int, void*, bool, unsigned int, unsigned int) ''', output) # test for multiple functions in one stack trace assert 'one(int)' in output @@ -2011,7 +2027,7 @@ a(int [32], char [5]*) try_delete(path_from_root('tests', 'Module-exports', 'test.js.map')) def test_fs_stream_proto(self): - open('src.cpp', 'w').write(r''' + open('src.cpp', 'wb').write(r''' #include <stdio.h> #include <fcntl.h> #include <unistd.h> diff --git a/tests/zlib/CMakeLists.txt b/tests/zlib/CMakeLists.txt new file mode 100644 index 00000000..01a19fb5 --- /dev/null +++ b/tests/zlib/CMakeLists.txt @@ -0,0 +1,190 @@ +cmake_minimum_required(VERSION 2.4.4) +set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON) + +project(zlib C) + +if(NOT DEFINED BUILD_SHARED_LIBS) + option(BUILD_SHARED_LIBS "Build a shared library form of zlib" ON) +endif() + +include(CheckTypeSize) +include(CheckFunctionExists) +include(CheckIncludeFile) +include(CheckCSourceCompiles) +enable_testing() + +check_include_file(sys/types.h HAVE_SYS_TYPES_H) +check_include_file(stdint.h HAVE_STDINT_H) +check_include_file(stddef.h HAVE_STDDEF_H) + +# +# Check to see if we have large file support +# +set(CMAKE_REQUIRED_DEFINITIONS -D_LARGEFILE64_SOURCE=1) +# We add these other definitions here because CheckTypeSize.cmake +# in CMake 2.4.x does not automatically do so and we want +# compatibility with CMake 2.4.x. +if(HAVE_SYS_TYPES_H) + list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_SYS_TYPES_H) +endif() +if(HAVE_STDINT_H) + list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_STDINT_H) +endif() +if(HAVE_STDDEF_H) + list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_STDDEF_H) +endif() +check_type_size(off64_t OFF64_T) +if(HAVE_OFF64_T) + add_definitions(-D_LARGEFILE64_SOURCE=1) +endif() +set(CMAKE_REQUIRED_DEFINITIONS) # clear variable + +# +# Check for fseeko +# +check_function_exists(fseeko HAVE_FSEEKO) +if(NOT HAVE_FSEEKO) + add_definitions(-DNO_FSEEKO) +endif() + +# +# Check for unistd.h +# +check_include_file(unistd.h Z_HAVE_UNISTD_H) + +if(MSVC) + set(CMAKE_DEBUG_POSTFIX "d") + add_definitions(-D_CRT_SECURE_NO_DEPRECATE) + add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE) +endif() + +if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) + # If we're doing an out of source build and the user has a zconf.h + # in their source tree... + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h) + message(FATAL_ERROR + "You must remove ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h " + "from the source tree. This file is included with zlib " + "but CMake generates this file for you automatically " + "in the build directory.") + endif() +endif() + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/zconf.h.cmakein + ${CMAKE_CURRENT_BINARY_DIR}/zconf.h @ONLY) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + + +#============================================================================ +# zlib +#============================================================================ + +set(ZLIB_PUBLIC_HDRS + ${CMAKE_CURRENT_BINARY_DIR}/zconf.h + zlib.h +) +set(ZLIB_PRIVATE_HDRS + crc32.h + deflate.h + gzguts.h + inffast.h + inffixed.h + inflate.h + inftrees.h + trees.h + zutil.h +) +set(ZLIB_SRCS + adler32.c + compress.c + crc32.c + deflate.c + gzclose.c + gzlib.c + gzread.c + gzwrite.c + inflate.c + infback.c + inftrees.c + inffast.c + trees.c + uncompr.c + zutil.c +# win32/zlib1.rc XXX Emscripten remove the Windows resource file from build, not needed and not included in source tree. +) + +# parse the full version number from zlib.h and include in ZLIB_FULL_VERSION +file(READ ${CMAKE_CURRENT_SOURCE_DIR}/zlib.h _zlib_h_contents) +string(REGEX REPLACE ".*#define[ \t]+ZLIB_VERSION[ \t]+\"([0-9A-Za-z.]+)\".*" + "\\1" ZLIB_FULL_VERSION ${_zlib_h_contents}) + +if(MINGW) + # This gets us DLL resource information when compiling on MinGW. + add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj + COMMAND windres.exe + -D GCC_WINDRES + -I ${CMAKE_CURRENT_SOURCE_DIR} + -I ${CMAKE_CURRENT_BINARY_DIR} + -o ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj + -i ${CMAKE_CURRENT_SOURCE_DIR}/win32/zlib1.rc) + set(ZLIB_SRCS ${ZLIB_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj) +endif(MINGW) + +add_library(zlib ${ZLIB_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS}) +set_target_properties(zlib PROPERTIES DEFINE_SYMBOL ZLIB_DLL) + +set_target_properties(zlib PROPERTIES SOVERSION 1) + +if(NOT CYGWIN) + # This property causes shared libraries on Linux to have the full version + # encoded into their final filename. We disable this on Cygwin because + # it causes cygz-${ZLIB_FULL_VERSION}.dll to be created when cygz.dll + # seems to be the default. + # + # This has no effect with MSVC, on that platform the version info for + # the DLL comes from the resource file win32/zlib1.rc + set_target_properties(zlib PROPERTIES VERSION ${ZLIB_FULL_VERSION}) +endif() + +if(UNIX) + # On unix-like platforms the library is almost always called libz + set_target_properties(zlib PROPERTIES OUTPUT_NAME z) +elseif(BUILD_SHARED_LIBS AND WIN32) + # Creates zlib1.dll when building shared library version + set_target_properties(zlib PROPERTIES SUFFIX "1.dll") +endif() + +if(NOT SKIP_INSTALL_LIBRARIES AND NOT SKIP_INSTALL_ALL ) + install(TARGETS zlib + RUNTIME DESTINATION bin + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib ) +endif() +if(NOT SKIP_INSTALL_HEADERS AND NOT SKIP_INSTALL_ALL ) + install(FILES ${ZLIB_PUBLIC_HDRS} DESTINATION include) +endif() +if(NOT SKIP_INSTALL_FILES AND NOT SKIP_INSTALL_ALL ) + install(FILES zlib.3 DESTINATION share/man/man3) +endif() + +#============================================================================ +# Example binaries +#============================================================================ + +add_executable(example example.c) +target_link_libraries(example zlib) +add_test(example example) + +add_executable(minigzip minigzip.c) +target_link_libraries(minigzip zlib) + +if(HAVE_OFF64_T) + add_executable(example64 example.c) + target_link_libraries(example64 zlib) + set_target_properties(example64 PROPERTIES COMPILE_FLAGS "-D_FILE_OFFSET_BITS=64") + add_test(example64 example64) + + add_executable(minigzip64 minigzip.c) + target_link_libraries(minigzip64 zlib) + set_target_properties(minigzip64 PROPERTIES COMPILE_FLAGS "-D_FILE_OFFSET_BITS=64") +endif() diff --git a/tests/zlib/zconf.h b/tests/zlib/zconf.h.cmakein index b2343874..a2f71b1f 100644 --- a/tests/zlib/zconf.h +++ b/tests/zlib/zconf.h.cmakein @@ -7,6 +7,8 @@ #ifndef ZCONF_H #define ZCONF_H +#cmakedefine Z_PREFIX +#cmakedefine Z_HAVE_UNISTD_H /* * If you *really* need a unique prefix for all types and library functions, @@ -356,7 +358,7 @@ typedef uLong FAR uLongf; typedef Byte *voidp; #endif -#if 1 /* was set to #if 1 by ./configure */ +#ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */ # define Z_HAVE_UNISTD_H #endif diff --git a/tools/eliminator/eliminator-test-output.js b/tools/eliminator/eliminator-test-output.js index 1a6506ed..0171e99b 100644 --- a/tools/eliminator/eliminator-test-output.js +++ b/tools/eliminator/eliminator-test-output.js @@ -6119,4 +6119,7 @@ function intoCond() { HEAP32[$115 >> 2] = $NumWords; } } +function math(a, b, c, d) { + print(Math_imul(d) + (Math_fround(c) + (a + Math_abs(b)))); +} diff --git a/tools/eliminator/eliminator-test.js b/tools/eliminator/eliminator-test.js index ffad69ea..ef17b388 100644 --- a/tools/eliminator/eliminator-test.js +++ b/tools/eliminator/eliminator-test.js @@ -8852,5 +8852,13 @@ function intoCond() { HEAP32[$504 >> 2] = $503; } } -// EMSCRIPTEN_GENERATED_FUNCTIONS: ["a", "b", "c", "f", "g", "h", "py", "r", "t", "f2", "f3", "llvm3_1", "_inflate", "_malloc", "_mallocNoU", "asm", "phi", "intoCond"] +function math(a, b, c, d) { + var x, y, z, w; + x = a; + y = Math_abs(b); + z = Math_fround(c); + w = Math_imul(d); + print(x + y + z + w); +} +// EMSCRIPTEN_GENERATED_FUNCTIONS: ["a", "b", "c", "f", "g", "h", "py", "r", "t", "f2", "f3", "llvm3_1", "_inflate", "_malloc", "_mallocNoU", "asm", "phi", "intoCond", "math"] diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 36244298..57ce0071 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -618,6 +618,7 @@ function simplifyExpressions(ast) { if (asm) { if (hasTempDoublePtr) { + var asmData = normalizeAsm(ast); traverse(ast, function(node, type) { if (type === 'assign') { if (node[1] === true && node[2][0] === 'sub' && node[2][1][0] === 'name' && node[2][1][1] === 'HEAP32') { @@ -642,7 +643,7 @@ function simplifyExpressions(ast) { node[2][0] !== 'seq') { // avoid (x, y, z) which can be used for tempDoublePtr on doubles for alignment fixes if (node[1][2][1][1] === 'HEAP32') { node[1][3][1][1] = 'HEAPF32'; - return ['unary-prefix', '+', node[1][3]]; + return makeAsmCoercion(node[1][3], detectAsmCoercion(node[2])); } else { node[1][3][1][1] = 'HEAP32'; return ['binary', '|', node[1][3], ['num', 0]]; @@ -686,7 +687,6 @@ function simplifyExpressions(ast) { } } }); - var asmData = normalizeAsm(ast); for (var v in bitcastVars) { var info = bitcastVars[v]; // good variables define only one type, use only one type, have definitions and uses, and define as a different type than they use @@ -1142,13 +1142,28 @@ function simplifyNotComps(ast) { simplifyNotCompsPass = false; } -var NO_SIDE_EFFECTS = set('num', 'name'); +function callHasSideEffects(node) { // checks if the call itself (not the args) has side effects (or is not statically known) + return !(node[1][0] === 'name' && /^Math_/.test(node[1][1])); +} function hasSideEffects(node) { // this is 99% incomplete! - if (node[0] in NO_SIDE_EFFECTS) return false; - if (node[0] === 'unary-prefix') return hasSideEffects(node[2]); - if (node[0] === 'binary') return hasSideEffects(node[2]) || hasSideEffects(node[3]); - return true; + switch (node[0]) { + case 'num': case 'name': case 'string': return false; + case 'unary-prefix': return hasSideEffects(node[2]); + case 'binary': return hasSideEffects(node[2]) || hasSideEffects(node[3]); + case 'sub': return hasSideEffects(node[1]) || hasSideEffects(node[2]); + case 'call': { + if (callHasSideEffects(node)) return true; + // This is a statically known call, with no side effects. only args can side effect us + var args = node[2]; + var num = args.length; + for (var i = 0; i < num; i++) { + if (hasSideEffects(args[i])) return true; + } + return false; + } + default: return true; + } } // Clear out empty ifs and blocks, and redundant blocks/stats and so forth @@ -1515,21 +1530,33 @@ function unVarify(vars, ret) { // transform var x=1, y=2 etc. into (x=1, y=2), i // annotations, plus explicit metadata) and denormalize (vice versa) var ASM_INT = 0; var ASM_DOUBLE = 1; +var ASM_FLOAT = 2; function detectAsmCoercion(node, asmInfo) { // for params, +x vs x|0, for vars, 0.0 vs 0 if (node[0] === 'num' && node[1].toString().indexOf('.') >= 0) return ASM_DOUBLE; if (node[0] === 'unary-prefix') return ASM_DOUBLE; + if (node[0] === 'call' && node[1][0] === 'name' && node[1][1] === 'Math_fround') return ASM_FLOAT; if (asmInfo && node[0] == 'name') return getAsmType(node[1], asmInfo); return ASM_INT; } function makeAsmCoercion(node, type) { - return type === ASM_INT ? ['binary', '|', node, ['num', 0]] : ['unary-prefix', '+', node]; + switch (type) { + case ASM_INT: return ['binary', '|', node, ['num', 0]]; + case ASM_DOUBLE: return ['unary-prefix', '+', node]; + case ASM_FLOAT: return ['call', ['name', 'Math_fround'], [node]]; + default: throw 'wha? ' + JSON.stringify([node, type]) + new Error().stack; + } } function makeAsmVarDef(v, type) { - return [v, type === ASM_INT ? ['num', 0] : ['unary-prefix', '+', ['num', 0]]]; + switch (type) { + case ASM_INT: return [v, ['num', 0]]; + case ASM_DOUBLE: return [v, ['unary-prefix', '+', ['num', 0]]]; + case ASM_FLOAT: return [v, ['call', ['name', 'Math_fround'], [['num', 0]]]]; + default: throw 'wha?'; + } } function getAsmType(name, asmInfo) { @@ -1568,7 +1595,8 @@ function normalizeAsm(func) { var name = v[0]; var value = v[1]; if (!(name in data.vars)) { - assert(value[0] === 'num' || (value[0] === 'unary-prefix' && value[2][0] === 'num')); // must be valid coercion no-op + assert(value[0] === 'num' || (value[0] === 'unary-prefix' && value[2][0] === 'num') // must be valid coercion no-op + || (value[0] === 'call' && value[1][0] === 'name' && value[1][1] === 'Math_fround')); data.vars[name] = detectAsmCoercion(value); v.length = 1; // make an un-assigning var } else { @@ -1917,7 +1945,7 @@ function registerize(ast) { // we just use a fresh register to make sure we avoid this, but it could be // optimized to check for safe registers (free, and not used in this loop level). var varRegs = {}; // maps variables to the register they will use all their life - var freeRegsClasses = asm ? [[], []] : []; // two classes for asm, one otherwise + var freeRegsClasses = asm ? [[], [], []] : []; // two classes for asm, one otherwise XXX - hardcoded length var nextReg = 1; var fullNames = {}; var loopRegs = {}; // for each loop nesting level, the list of bound variables @@ -2031,6 +2059,33 @@ function registerize(ast) { } } denormalizeAsm(fun, finalAsmData); + if (extraInfo && extraInfo.globals) { + // minify in asm var definitions, that denormalizeAsm just generated + function minify(value) { + if (value && value[0] === 'call' && value[1][0] === 'name') { + var name = value[1][1]; + var minified = extraInfo.globals[name]; + if (minified) { + value[1][1] = minified; + } + } + } + var stats = fun[3]; + for (var i = 0; i < stats.length; i++) { + var line = stats[i]; + if (i >= fun[2].length && line[0] !== 'var') break; // when we pass the arg and var coercions, break + if (line[0] === 'stat') { + assert(line[1][0] === 'assign'); + minify(line[1][3]); + } else { + assert(line[0] === 'var'); + var pairs = line[1]; + for (var j = 0; j < pairs.length; j++) { + minify(pairs[j][1]); + } + } + } + } } }); } @@ -2068,7 +2123,6 @@ function registerize(ast) { // can happen in ALLOW_MEMORY_GROWTH mode var ELIMINATION_SAFE_NODES = set('var', 'assign', 'call', 'if', 'toplevel', 'do', 'return', 'label', 'switch'); // do is checked carefully, however -var NODES_WITHOUT_ELIMINATION_SIDE_EFFECTS = set('name', 'num', 'string', 'binary', 'sub', 'unary-prefix'); var IGNORABLE_ELIMINATOR_SCAN_NODES = set('num', 'toplevel', 'string', 'break', 'continue', 'dot'); // dot can only be STRING_TABLE.* var ABORTING_ELIMINATOR_SCAN_NODES = set('new', 'object', 'function', 'defun', 'for', 'while', 'array', 'throw'); // we could handle some of these, TODO, but nontrivial (e.g. for while, the condition is hit multiple times after the body) @@ -2160,7 +2214,7 @@ function eliminate(ast, memSafe) { if (definitions[name] === 1 && uses[name] === 1) { potentials[name] = 1; } else if (uses[name] === 0 && (!definitions[name] || definitions[name] <= 1)) { // no uses, no def or 1 def (cannot operate on phis, and the llvm optimizer will remove unneeded phis anyhow) (no definition means it is a function parameter, or a local with just |var x;| but no defining assignment) - var hasSideEffects = false; + var sideEffects = false; var value = values[name]; if (value) { // TODO: merge with other side effect code @@ -2170,15 +2224,10 @@ function eliminate(ast, memSafe) { if (!(value[0] === 'seq' && value[1][0] === 'assign' && value[1][2][0] === 'sub' && value[1][2][2][0] === 'binary' && value[1][2][2][1] === '>>' && value[1][2][2][2][0] === 'name' && value[1][2][2][2][1] === 'tempDoublePtr')) { // If not that, then traverse and scan normally. - traverse(value, function(node, type) { - if (!(type in NODES_WITHOUT_ELIMINATION_SIDE_EFFECTS)) { - hasSideEffects = true; // cannot remove this unused variable, constructing it has side effects - return true; - } - }); + sideEffects = hasSideEffects(value); } } - if (!hasSideEffects) { + if (!sideEffects) { varsToRemove[name] = !definitions[name] ? 2 : 1; // remove it normally sideEffectFree[name] = true; // Each time we remove a variable with 0 uses, if its value has no @@ -2437,14 +2486,16 @@ function eliminate(ast, memSafe) { for (var i = 0; i < args.length; i++) { traverseInOrder(args[i]); } - // these two invalidations will also invalidate calls - if (!globalsInvalidated) { - invalidateGlobals(); - globalsInvalidated = true; - } - if (!memoryInvalidated) { - invalidateMemory(); - memoryInvalidated = true; + if (callHasSideEffects(node)) { + // these two invalidations will also invalidate calls + if (!globalsInvalidated) { + invalidateGlobals(); + globalsInvalidated = true; + } + if (!memoryInvalidated) { + invalidateMemory(); + memoryInvalidated = true; + } } } else if (type === 'if') { if (allowTracking) { @@ -3173,7 +3224,7 @@ function outline(ast) { var writes = {}; var namings = {}; - var hasReturn = false, hasReturnInt = false, hasReturnDouble = false, hasBreak = false, hasContinue = false; + var hasReturn = false, hasReturnType = {}, hasBreak = false, hasContinue = false; var breaks = {}; // set of labels we break or continue var continues = {}; // to (name -> id, just like labels) var breakCapturers = 0; @@ -3193,10 +3244,8 @@ function outline(ast) { } else if (type == 'return') { if (!node[1]) { hasReturn = true; - } else if (detectAsmCoercion(node[1]) == ASM_INT) { - hasReturnInt = true; } else { - hasReturnDouble = true; + hasReturnType[detectAsmCoercion(node[1])] = true; } } else if (type == 'break') { var label = node[1] || 0; @@ -3226,7 +3275,6 @@ function outline(ast) { continueCapturers--; } }); - assert(hasReturn + hasReturnInt + hasReturnDouble <= 1); var reads = {}; for (var v in namings) { @@ -3234,7 +3282,7 @@ function outline(ast) { if (actualReads > 0) reads[v] = actualReads; } - return { writes: writes, reads: reads, hasReturn: hasReturn, hasReturnInt: hasReturnInt, hasReturnDouble: hasReturnDouble, hasBreak: hasBreak, hasContinue: hasContinue, breaks: breaks, continues: continues, labels: labels }; + return { writes: writes, reads: reads, hasReturn: hasReturn, hasReturnType: hasReturnType, hasBreak: hasBreak, hasContinue: hasContinue, breaks: breaks, continues: continues, labels: labels }; } function makeAssign(dst, src) { @@ -3255,7 +3303,10 @@ function outline(ast) { return ['switch', value, cases]; } - var CONTROL_BREAK = 1, CONTROL_BREAK_LABEL = 2, CONTROL_CONTINUE = 3, CONTROL_CONTINUE_LABEL = 4, CONTROL_RETURN_VOID = 5, CONTROL_RETURN_INT = 6, CONTROL_RETURN_DOUBLE = 7; + var CONTROL_BREAK = 1, CONTROL_BREAK_LABEL = 2, CONTROL_CONTINUE = 3, CONTROL_CONTINUE_LABEL = 4, CONTROL_RETURN_VOID = 5, CONTROL_RETURN_INT = 6, CONTROL_RETURN_DOUBLE = 7, CONTROL_RETURN_FLOAT = 8; + function controlFromAsmType(asmType) { + return CONTROL_RETURN_INT + (asmType | 0); // assumes ASM_INT starts at 0, and order of these two is identical! + } var sizeToOutline = null; // customized per function and as we make progress function calculateThreshold(func, asmData) { @@ -3314,7 +3365,7 @@ function outline(ast) { }); // Generate new function - if (codeInfo.hasReturn || codeInfo.hasReturnInt || codeInfo.hasReturnDouble || codeInfo.hasBreak || codeInfo.hasContinue) { + if (codeInfo.hasReturn || codeInfo.hasReturnType[ASM_INT] || codeInfo.hasReturnType[ASM_DOUBLE] || codeInfo.hasReturnType[ASM_FLOAT] || codeInfo.hasBreak || codeInfo.hasContinue) { // we need to capture all control flow using a top-level labeled one-time loop in the outlined function var breakCapturers = 0; var continueCapturers = 0; @@ -3339,7 +3390,7 @@ function outline(ast) { ret.push(['stat', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos(outlineIndex)), ['num', CONTROL_RETURN_VOID])]); } else { var type = detectAsmCoercion(node[1], asmData); - ret.push(['stat', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos(outlineIndex)), ['num', type == ASM_INT ? CONTROL_RETURN_INT : CONTROL_RETURN_DOUBLE])]); + ret.push(['stat', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos(outlineIndex)), ['num', controlFromAsmType(type)])]); ret.push(['stat', makeAssign(makeStackAccess(type, asmData.controlDataStackPos(outlineIndex)), node[1])]); } ret.push(['stat', ['break', 'OL']]); @@ -3402,16 +3453,10 @@ function outline(ast) { [['stat', ['return']]] )); } - if (codeInfo.hasReturnInt) { + for (var returnType in codeInfo.hasReturnType) { reps.push(makeIf( - makeComparison(makeAsmCoercion(['name', 'tempValue'], ASM_INT), '==', ['num', CONTROL_RETURN_INT]), - [['stat', ['return', makeAsmCoercion(['name', 'tempInt'], ASM_INT)]]] - )); - } - if (codeInfo.hasReturnDouble) { - reps.push(makeIf( - makeComparison(makeAsmCoercion(['name', 'tempValue'], ASM_INT), '==', ['num', CONTROL_RETURN_DOUBLE]), - [['stat', ['return', makeAsmCoercion(['name', 'tempDouble'], ASM_DOUBLE)]]] + makeComparison(makeAsmCoercion(['name', 'tempValue'], ASM_INT), '==', ['num', controlFromAsmType(returnType)]), + [['stat', ['return', makeAsmCoercion(['name', 'tempInt'], returnType | 0)]]] )); } if (codeInfo.hasBreak) { @@ -3490,8 +3535,9 @@ function outline(ast) { var last = getStatements(func)[getStatements(func).length-1]; if (last[0] === 'stat') last = last[1]; if (last[0] !== 'return') { - if (allCodeInfo.hasReturnInt || allCodeInfo.hasReturnDouble) { - getStatements(func).push(['stat', ['return', makeAsmCoercion(['num', 0], allCodeInfo.hasReturnInt ? ASM_INT : ASM_DOUBLE)]]); + for (var returnType in codeInfo.hasReturnType) { + getStatements(func).push(['stat', ['return', makeAsmCoercion(['num', 0], returnType | 0)]]); + break; } } outliningParents[newIdent] = func[1]; diff --git a/tools/response_file.py b/tools/response_file.py index f19cf8af..7f916752 100644 --- a/tools/response_file.py +++ b/tools/response_file.py @@ -1,4 +1,5 @@ import tempfile, os, sys, shlex +import shared # Routes the given cmdline param list in args into a new response file and returns the filename to it. # The returned filename has a suffix '.rsp'. @@ -9,6 +10,11 @@ def create_response_file(args, directory): args = map(lambda p: p.replace('\\', '\\\\').replace('"', '\\"'), args) response_fd.write('"' + '" "'.join(args) + '"') response_fd.close() + + # Register the created .rsp file to be automatically cleaned up once this process finishes, so that + # caller does not have to remember to do it. + shared.configuration.get_temp_files().note(response_filename) + return response_filename # Reads a response file, and returns the list of cmdline params found in the file. diff --git a/tools/shared.py b/tools/shared.py index d38aef4c..5b165b8b 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -3,7 +3,7 @@ from subprocess import Popen, PIPE, STDOUT from tempfile import mkstemp from distutils.spawn import find_executable import jsrun, cache, tempfiles -from response_file import create_response_file +import response_file import logging, platform def listify(x): @@ -41,8 +41,8 @@ class WindowsPopen: # emscripten.py supports reading args from a response file instead of cmdline. # Use .rsp to avoid cmdline length limitations on Windows. if len(args) >= 2 and args[1].endswith("emscripten.py"): - self.response_filename = create_response_file(args[2:], TEMP_DIR) - args = args[0:2] + ['@' + self.response_filename] + response_filename = response_file.create_response_file(args[2:], TEMP_DIR) + args = args[0:2] + ['@' + response_filename] try: # Call the process with fixed streams. @@ -78,13 +78,6 @@ class WindowsPopen: def kill(self): return self.process.kill() - def __del__(self): - try: - # Clean up the temporary response file that was used to spawn this process, so that we don't leave temp files around. - tempfiles.try_delete(self.response_filename) - except: - pass # Mute all exceptions in dtor, particularly if we didn't use a response file, self.response_filename doesn't exist. - __rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) def path_from_root(*pathelems): return os.path.join(__rootpath__, *pathelems) @@ -314,7 +307,7 @@ def find_temp_directory(): # we re-check sanity when the settings are changed) # We also re-check sanity and clear the cache when the version changes -EMSCRIPTEN_VERSION = '1.7.2' +EMSCRIPTEN_VERSION = '1.7.6' def generate_sanity(): return EMSCRIPTEN_VERSION + '|' + get_llvm_target() + '|' + LLVM_ROOT @@ -1117,14 +1110,16 @@ class Building: # @param opt Either an integer, in which case it is the optimization level (-O1, -O2, etc.), or a list of raw # optimization passes passed to llvm opt @staticmethod - def llvm_opt(filename, opts): + def llvm_opt(filename, opts, out=None): if type(opts) is int: opts = Building.pick_llvm_opts(opts) #opts += ['-debug-pass=Arguments'] logging.debug('emcc: LLVM opts: ' + str(opts)) - output = Popen([LLVM_OPT, filename] + opts + ['-o', filename + '.opt.bc'], stdout=PIPE).communicate()[0] - assert os.path.exists(filename + '.opt.bc'), 'Failed to run llvm optimizations: ' + output - shutil.move(filename + '.opt.bc', filename) + target = out or (filename + '.opt.bc') + output = Popen([LLVM_OPT, filename] + opts + ['-o', target], stdout=PIPE).communicate()[0] + assert os.path.exists(target), 'Failed to run llvm optimizations: ' + output + if not out: + shutil.move(filename + '.opt.bc', filename) @staticmethod def llvm_opts(filename): # deprecated version, only for test runner. TODO: remove @@ -1214,7 +1209,13 @@ class Building: # Run Emscripten Settings.RELOOPER = Cache.get_path('relooper.js') settings = Settings.serialize() - compiler_output = jsrun.timeout_run(Popen([PYTHON, EMSCRIPTEN, filename + ('.o.ll' if append_ext else ''), '-o', filename + '.o.js'] + settings + extra_args, stdout=PIPE), None, 'Compiling') + args = settings + extra_args + if WINDOWS: + args = ['@' + response_file.create_response_file(args, TEMP_DIR)] + cmdline = [PYTHON, EMSCRIPTEN, filename + ('.o.ll' if append_ext else ''), '-o', filename + '.o.js'] + args + if jsrun.TRACK_PROCESS_SPAWNS: + logging.info('Executing emscripten.py compiler with cmdline "' + ' '.join(cmdline) + '"') + compiler_output = jsrun.timeout_run(Popen(cmdline, stdout=PIPE), None, 'Compiling') #print compiler_output # Detect compilation crashes and errors @@ -1511,6 +1512,28 @@ class JS: return ident.replace('%', '$').replace('@', '_') @staticmethod + def make_initializer(sig, settings=None): + settings = settings or Settings + if sig == 'i': + return '0' + elif sig == 'f' and settings.get('PRECISE_F32'): + return 'Math_fround(0)' + else: + return '+0' + + @staticmethod + def make_coercion(value, sig, settings=None): + settings = settings or Settings + if sig == 'i': + return value + '|0' + elif sig == 'f' and settings.get('PRECISE_F32'): + return 'Math_fround(' + value + ')' + elif sig == 'd' or sig == 'f': + return '+' + value + else: + return value + + @staticmethod def make_extcall(sig, named=True): args = ','.join(['a' + str(i) for i in range(1, len(sig))]) args = 'index' + (',' if args else '') + args |