diff options
author | Alon Zakai <azakai@mozilla.com> | 2011-03-02 19:12:13 -0800 |
---|---|---|
committer | Alon Zakai <azakai@mozilla.com> | 2011-03-02 19:12:13 -0800 |
commit | c3408af2696ab4c0649495b63ec912f55156890b (patch) | |
tree | 5ff7d4a9faf843069fc6f2ec8aa4823197afcc8f | |
parent | 841d890872d0e4ff480d81dad909b31772c0f98d (diff) |
autodebugger tool
-rw-r--r-- | tests/runner.py | 43 | ||||
-rw-r--r-- | tools/autodebugger.py | 49 |
2 files changed, 83 insertions, 9 deletions
diff --git a/tests/runner.py b/tests/runner.py index 35be05a6..27a939bd 100644 --- a/tests/runner.py +++ b/tests/runner.py @@ -27,6 +27,7 @@ EMSCRIPTEN = path_from_root('emscripten.py') DEMANGLER = path_from_root('third_party', 'demangler.py') NAMESPACER = path_from_root('tools', 'namespacer.py') EMMAKEN = path_from_root('tools', 'emmaken.py') +AUTODEBUGGER = path_from_root('tools', 'autodebugger.py') # Global cache for tests (we have multiple TestCase instances; this object lets them share data) @@ -183,6 +184,9 @@ class RunnerCore(unittest.TestCase): assert 'strict warning:' not in ret, 'We should pass all strict mode checks: ' + ret return ret + def run_llvm_interpreter(self, args): + return Popen([LLVM_INTERPRETER] + args, stdout=PIPE, stderr=STDOUT).communicate()[0] + def assertContained(self, value, string): if value not in string: raise Exception("Expected to find '%s' in '%s'" % (value, string)) @@ -200,7 +204,7 @@ if 'benchmark' not in sys.argv: class T(RunnerCore): # Short name, to make it more fun to use manually on the commandline ## Does a complete test - builds, runs, checks output, etc. - def do_test(self, src, expected_output, args=[], output_nicerizer=None, output_processor=None, no_build=False, main_file=None, additional_files=[], js_engines=None, post_build=None, basename='src.cpp', libraries=[], includes=[], force_c=False): + def do_test(self, src, expected_output=None, args=[], output_nicerizer=None, output_processor=None, no_build=False, main_file=None, additional_files=[], js_engines=None, post_build=None, basename='src.cpp', libraries=[], includes=[], force_c=False): #print 'Running test:', inspect.stack()[1][3].replace('test_', ''), '[%s,%s,%s]' % (COMPILER.split(os.sep)[-1], 'llvm-optimizations' if LLVM_OPTS else '', 'reloop&optimize' if RELOOP else '') if force_c or (main_file is not None and main_file[-2:]) == '.c': basename = 'src.c' @@ -215,6 +219,11 @@ if 'benchmark' not in sys.argv: if post_build is not None: post_build(filename + '.o.js') + # If not provided with expected output, then generate it right now, using lli + if expected_output is None: + expected_output = self.run_llvm_interpreter([filename + '.o']) + print '[autogenerated expected output: %20s]' % (expected_output[0:17].replace('\n', '')+'...') + # Run in both JavaScript engines, if optimizing - significant differences there (typed arrays) if js_engines is None: js_engines = [V8_ENGINE, SPIDERMONKEY_ENGINE] @@ -227,7 +236,7 @@ if 'benchmark' not in sys.argv: #shutil.rmtree(dirname) # TODO: leave no trace in memory. But for now nice for debugging - def prep_ll_test(self, filename, ll_file): + def prep_ll_test(self, filename, ll_file, force_recompile=False): if ll_file.endswith(('.bc', '.o')): shutil.copy(ll_file, filename + '.o') self.do_llvm_dis(filename) @@ -235,24 +244,25 @@ if 'benchmark' not in sys.argv: os.remove(filename + '.o.ll') ll_file = filename + '.o.ll.in' - if LLVM_OPTS: + if LLVM_OPTS or force_recompile: shutil.copy(ll_file, filename + '.o.ll.pre') - Popen([LLVM_AS, filename + '.o.ll.pre'] + ['-o=' + filename + '.o'], stdout=PIPE, stderr=STDOUT).communicate()[0] + output = Popen([LLVM_AS, filename + '.o.ll.pre'] + ['-o=' + filename + '.o'], stdout=PIPE, stderr=STDOUT).communicate()[0] + assert 'error:' not in output, 'Error in llvm-as: ' + output self.do_llvm_opts(filename) Popen([LLVM_DIS, filename + '.o'] + LLVM_DIS_OPTS + ['-o=' + filename + '.o.ll'], stdout=PIPE, stderr=STDOUT).communicate()[0] else: shutil.copy(ll_file, filename + '.o.ll') # No building - just process an existing .ll file (or .bc, which we turn into .ll) - def do_ll_test(self, ll_file, output, args=[], js_engines=None, output_nicerizer=None, post_build=None): + def do_ll_test(self, ll_file, expected_output=None, args=[], js_engines=None, output_nicerizer=None, post_build=None, force_recompile=False): if COMPILER != LLVM_GCC: return # We use existing .ll, so which compiler is unimportant filename = os.path.join(self.get_dir(), 'src.cpp') - self.prep_ll_test(filename, ll_file) + self.prep_ll_test(filename, ll_file, force_recompile) self.do_emscripten(filename) self.do_test(None, - output, + expected_output, args, no_build=True, js_engines=js_engines, @@ -1663,8 +1673,7 @@ if 'benchmark' not in sys.argv: # Generate the native code output using lli lli_file = os.path.join(self.get_dir(), 'lli.raw') - stdout = Popen([LLVM_INTERPRETER, os.path.join(self.get_dir(), 'src.c.o'), '-i', original_j2k, '-o', lli_file], - stdout=PIPE, stderr=STDOUT).communicate()[0] + stdout = self.run_llvm_interpreter([os.path.join(self.get_dir(), 'src.c.o'), '-i', original_j2k, '-o', lli_file]) assert 'Successfully generated' in stdout, 'Error in lli run: ' + stdout lli_data = open(lli_file, 'rb').read() @@ -1743,6 +1752,22 @@ if 'benchmark' not in sys.argv: output = 'hello, world!' self.do_ll_test(path_from_root('tests', 'cases', name), output) + def test_autodebug(self): + if COMPILER != LLVM_GCC: return # TODO: Check both + if LLVM_OPTS: return # They mess us up + + # Run a test that should work, generating some code + self.test_structs() + + # Autodebug the code + filename = os.path.join(self.get_dir(), 'src.cpp.o.ll') + output = Popen(['python', AUTODEBUGGER, filename, filename+'.ll'], stdout=PIPE, stderr=STDOUT).communicate()[0] + assert 'Success.' in output + + # Compare to each other, and to expected output + self.do_ll_test(path_from_root('tests', filename+'.ll'), force_recompile=True) + self.do_ll_test(path_from_root('tests', filename+'.ll'), '34 : 10\n42 : 7008\n51 : 7018') # No need to force recompile twice - already autodebugged + ### Integration tests def test_scriptaclass(self): diff --git a/tools/autodebugger.py b/tools/autodebugger.py new file mode 100644 index 00000000..b90501cc --- /dev/null +++ b/tools/autodebugger.py @@ -0,0 +1,49 @@ +''' +Processes an LLVM assembly (.ll) file, adding debugging information. + +You can then run the .ll file in the LLVM interpreter (lli) and +compare that to the output when compiled using emscripten. +''' + +import os, sys, re + +POSTAMBLE = ''' +@.emscripten.autodebug.str = private constant [9 x i8] c"%d : %d\\0A\\00", align 1 ; [#uses=1] + +; [#uses=1] +define void @emscripten_autodebug(i32 %line, i32 %value) { +entry: + %0 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([9 x i8]* @.emscripten.autodebug.str, i32 0, i32 0), i32 %line, i32 %value) ; [#uses=0] + br label %return + +return: ; preds = %entry + ret void +} +''' + +filename, ofilename = sys.argv[1], sys.argv[2] +f = open(filename, 'r') +data = f.read() +f.close() + +if 'declare i32 @printf(' not in data: + POSTAMBLE += ''' +; [#uses=1] +declare i32 @printf(i8*, ...) +''' + +lines = data.split('\n') +for i in range(len(lines)): + #if i == 5: + # lines[i] += '\n + + m = re.match(' store (?P<type>i64|i32|i16|i8) %(?P<var>[\w.]+), .*', lines[i]) + if m and m.group('type') == 'i32': # TODO: Other types + lines[i] += '\n call void @emscripten_autodebug(i32 %d, i32 %%%s)' % (i+1, m.group('var')) + +f = open(ofilename, 'w') +f.write('\n'.join(lines) + '\n' + POSTAMBLE + '\n') +f.close() + +print 'Success.' + |