From b2853368953f81b242e744964e1b077aba4e09d4 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Thu, 14 Apr 2022 07:09:34 +0530 Subject: [PATCH] fiber stored as register variable and the benchmark script refactored, and now it will generate an html report --- .gitignore | 4 + docs/.gitignore | 3 - src/pk_vm.c | 104 ++++---- tests/benchmarks/benchmarks.py | 337 +++++++++++++++--------- tests/benchmarks/list/list.lua | 4 +- tests/benchmarks/list/list.pk | 3 +- tests/benchmarks/list/list.py | 3 +- tests/benchmarks/list/list.rb | 5 +- tests/benchmarks/list/list.wren | 21 ++ tests/benchmarks/tco/{toc.pk => tco.pk} | 2 +- 10 files changed, 305 insertions(+), 181 deletions(-) delete mode 100644 docs/.gitignore create mode 100644 tests/benchmarks/list/list.wren rename tests/benchmarks/tco/{toc.pk => tco.pk} (95%) diff --git a/.gitignore b/.gitignore index 77c67d7..51987eb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,10 @@ .DS_Store build/ +tests/benchmarks/report.html +docs/build/ +docs/static/wasm/ + # Prerequisites *.d diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 789c8b7..0000000 --- a/docs/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ - -build/ -static/wasm/ diff --git a/src/pk_vm.c b/src/pk_vm.c index d5601ff..bf41a1a 100644 --- a/src/pk_vm.c +++ b/src/pk_vm.c @@ -677,14 +677,14 @@ static void reportError(PKVM* vm) { * RUNTIME * *****************************************************************************/ -static PkResult runFiber(PKVM* vm, Fiber* fiber) { +static PkResult runFiber(PKVM* vm, Fiber* fiber_) { // Set the fiber as the vm's current fiber (another root object) to prevent // it from garbage collection and get the reference from native functions. - vm->fiber = fiber; + vm->fiber = fiber_; - ASSERT(fiber->state == FIBER_NEW || fiber->state == FIBER_YIELDED, OOPS); - fiber->state = FIBER_RUNNING; + ASSERT(fiber_->state == FIBER_NEW || fiber_->state == FIBER_YIELDED, OOPS); + fiber_->state = FIBER_RUNNING; // The instruction pointer. register const uint8_t* ip; @@ -692,21 +692,22 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) { register Var* rbp; //< Stack base pointer register. register CallFrame* frame; //< Current call frame. register Module* module; //< Currently executing module. + register Fiber* fiber = fiber_; #if DEBUG - #define PUSH(value) \ - do { \ - ASSERT(vm->fiber->sp < (vm->fiber->stack + (vm->fiber->stack_size - 1)), \ - OOPS); \ - (*vm->fiber->sp++ = (value)); \ + #define PUSH(value) \ + do { \ + ASSERT(fiber->sp < (fiber->stack + (fiber->stack_size - 1)), \ + OOPS); \ + (*fiber->sp++ = (value)); \ } while (false) #else - #define PUSH(value) (*vm->fiber->sp++ = (value)) + #define PUSH(value) (*fiber->sp++ = (value)) #endif -#define POP() (*(--vm->fiber->sp)) -#define DROP() (--vm->fiber->sp) -#define PEEK(off) (*(vm->fiber->sp + (off))) +#define POP() (*(--fiber->sp)) +#define DROP() (--fiber->sp) +#define PEEK(off) (*(fiber->sp + (off))) #define READ_BYTE() (*ip++) #define READ_SHORT() (ip+=2, (uint16_t)((ip[-2] << 8) | ip[-1])) @@ -714,11 +715,12 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) { // done with the fiber or aborting it for runtime errors. #define FIBER_SWITCH_BACK() \ do { \ - Fiber* caller = vm->fiber->caller; \ + Fiber* caller = fiber->caller; \ ASSERT(caller == NULL || caller->state == FIBER_RUNNING, OOPS); \ - vm->fiber->state = FIBER_DONE; \ - vm->fiber->caller = NULL; \ - vm->fiber = caller; \ + fiber->state = FIBER_DONE; \ + fiber->caller = NULL; \ + fiber = caller; \ + vm->fiber = fiber; \ } while (false) // Check if any runtime error exists and if so returns RESULT_RUNTIME_ERROR. @@ -744,12 +746,12 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) { // Load the last call frame to vm's execution variables to resume/run the // function. -#define LOAD_FRAME() \ - do { \ - frame = &vm->fiber->frames[vm->fiber->frame_count-1]; \ - ip = frame->ip; \ - rbp = frame->rbp; \ - module = frame->closure->fn->owner; \ +#define LOAD_FRAME() \ + do { \ + frame = &fiber->frames[fiber->frame_count-1]; \ + ip = frame->ip; \ + rbp = frame->rbp; \ + module = frame->closure->fn->owner; \ } while (false) // Update the frame's execution variables before pushing another call frame. @@ -807,9 +809,9 @@ L_vm_main_loop: OPCODE(SWAP): { - Var tmp = *(vm->fiber->sp - 1); - *(vm->fiber->sp - 1) = *(vm->fiber->sp - 2); - *(vm->fiber->sp - 2) = tmp; + Var tmp = *(fiber->sp - 1); + *(fiber->sp - 1) = *(fiber->sp - 2); + *(fiber->sp - 2) = tmp; DISPATCH(); } @@ -979,8 +981,7 @@ L_vm_main_loop: if (is_immediate) { // rbp[0] is the return value, rbp + 1 is the first local and so on. - closure->upvalues[i] = captureUpvalue(vm, vm->fiber, - (rbp + 1 + index)); + closure->upvalues[i] = captureUpvalue(vm, fiber, (rbp + 1 + index)); } else { // The upvalue is already captured by the current function, reuse it. closure->upvalues[i] = frame->closure->upvalues[index]; @@ -993,7 +994,7 @@ L_vm_main_loop: OPCODE(CLOSE_UPVALUE): { - closeUpvalues(vm->fiber, vm->fiber->sp - 1); + closeUpvalues(fiber, fiber->sp - 1); DROP(); DISPATCH(); } @@ -1026,7 +1027,7 @@ L_vm_main_loop: // body of the module, so the main function will return what's at the // rbp without modifying it. So at the end of the main function the // stack top would be the module itself. - Var* module_ret = vm->fiber->sp - 1; + Var* module_ret = fiber->sp - 1; UPDATE_FRAME(); //< Update the current frame's ip. pushCallFrame(vm, imported->body, module_ret); @@ -1040,11 +1041,7 @@ L_vm_main_loop: OPCODE(TAIL_CALL): { const uint8_t argc = READ_BYTE(); - - // The call might change the vm->fiber so we need the reference to the - // fiber that actually called the function. - Fiber* call_fiber = vm->fiber; - Var* callable = call_fiber->sp - argc - 1; + Var* callable = fiber->sp - argc - 1; const Closure* closure = NULL; @@ -1076,8 +1073,8 @@ L_vm_main_loop: } // Next call frame starts here. (including return value). - call_fiber->ret = callable; - *(call_fiber->ret) = VAR_NULL; //< Set the return value to null. + fiber->ret = callable; + *(fiber->ret) = VAR_NULL; //< Set the return value to null. if (closure->fn->is_native) { @@ -1095,13 +1092,18 @@ L_vm_main_loop: // would be null if we're not running the function with a fiber. if (vm->fiber == NULL) return PK_RESULT_SUCCESS; - // Load the top frame to vm's execution variables. - if (vm->fiber != call_fiber) LOAD_FRAME(); - // Pop function arguments except for the return value. - // Don't use 'vm->fiber' because calling fiber_new() and yield() - // would change the fiber. - call_fiber->sp = call_fiber->ret + 1; + // Note that calling fiber_new() and yield() would change the + // vm->fiber so we're using fiber. + fiber->sp = fiber->ret + 1; + + // If the fiber has changed, Load the top frame to vm's execution + // variables. + if (vm->fiber != fiber) { + fiber = vm->fiber; + LOAD_FRAME(); + } + CHECK_ERROR(); } else { @@ -1145,8 +1147,8 @@ L_vm_main_loop: // TODO: move this to a function in pk_core.c. OPCODE(ITER): { - Var* value = (vm->fiber->sp - 1); - Var* iterator = (vm->fiber->sp - 2); + Var* value = (fiber->sp - 1); + Var* iterator = (fiber->sp - 2); Var seq = PEEK(-3); uint16_t jump_offset = READ_SHORT(); @@ -1295,32 +1297,32 @@ L_vm_main_loop: { // Close all the locals of the current frame. - closeUpvalues(vm->fiber, rbp + 1); + closeUpvalues(fiber, rbp + 1); // Set the return value. Var ret_value = POP(); // Pop the last frame, and if no more call frames, we're done with the // current fiber. - if (--vm->fiber->frame_count == 0) { + if (--fiber->frame_count == 0) { // TODO: if we're evaluating an expression we need to set it's // value on the stack. - //vm->fiber->sp = vm->fiber->stack; ?? + //fiber->sp = fiber->stack; ?? FIBER_SWITCH_BACK(); - if (vm->fiber == NULL) { + if (fiber == NULL) { return PK_RESULT_SUCCESS; } else { - *vm->fiber->ret = ret_value; + *fiber->ret = ret_value; } } else { *rbp = ret_value; // Pop the params (locals should have popped at this point) and update // stack pointer. - vm->fiber->sp = rbp + 1; // +1: rbp is returned value. + fiber->sp = rbp + 1; // +1: rbp is returned value. } LOAD_FRAME(); diff --git a/tests/benchmarks/benchmarks.py b/tests/benchmarks/benchmarks.py index 161afa3..764b2a0 100644 --- a/tests/benchmarks/benchmarks.py +++ b/tests/benchmarks/benchmarks.py @@ -1,22 +1,17 @@ #!python ## Copyright (c) 2020-2021 Thakee Nathees +## Copyright (c) 2021-2022 Pocketlang Contributors ## Distributed Under The MIT License -import subprocess, re, os, sys, platform -from os.path import join, abspath, dirname, relpath +import re, os, sys +import subprocess, platform from shutil import which +from os.path import join, abspath, dirname, relpath, exists ## The absolute path of this file, when run as a script. ## This file is not intended to be included in other files at the moment. THIS_PATH = abspath(dirname(__file__)) -## Map from systems to the relative pocket binary path. -SYSTEM_TO_BINARY_PATH = { - "Windows": "..\\..\\build\\release\\bin\\pocket.exe", - "Linux" : "../../build/release/pocket", - "Darwin" : "../../build/release/pocket", -} - ## A list of benchmark directories, relative to THIS_PATH BENCHMARKS = ( "factors", @@ -26,140 +21,246 @@ BENCHMARKS = ( "primes", ) -## Map from file extension to it's interpreter, Will be updated. -INTERPRETERS = {} +## Map the files extension with it's interpreter. (executable, extension). +INTERPRETERS = { + 'pocketlang' : ('pocket', '.pk'), + 'python' : ('python3', '.py'), + 'wren' : ('wren', '.wren'), + 'ruby' : ('ruby', '.rb'), + 'lua' : ('lua', '.lua'), + ## Javascript on Node is using V8 that compiles the function before calling it + ## which makes it way faster than every other language in this list, we're + ## only comparing byte-code interpreted VM languages. Node is the odd one out. + #'javascript' : ('node', '.js'), +} + +## Map from systems to the relative pocket binary path. +SYSTEM_TO_POCKET_PATH = { + "current" : { + "Windows": "..\\..\\build\\release\\bin\\pocket.exe", + "Linux" : "../../build/release/pocket", + "Darwin" : "../../build/release/pocket", + }, + + ## This maps the older version of pocket in the system path, to compare + ## pocketlang with it's older version. + "older" : { + "Windows": "..\\..\\build\\release\\bin\\pocket_older.exe", + "Linux" : "../../build/release/pocket_older", + "Darwin" : "../../build/release/pocket_older", + } +} + +## The html template to display the report. +HTML_TEMPLATE = "template.html" def main(): + results = dict() + update_interpreters() - run_all_benchmarsk() -## ---------------------------------------------------------------------------- -## RUN ALL BENCHMARKS -## ---------------------------------------------------------------------------- - -def run_all_benchmarsk(): for benchmark in BENCHMARKS: print_title(benchmark.title()) - dir = join(THIS_PATH, benchmark) - for file in _source_files(os.listdir(dir)): - file = abspath(join(dir, file)) - - ext = get_ext(file) ## File extension. - lang, interp, val = INTERPRETERS[ext] - if not interp: continue - + for lang in INTERPRETERS: + interp, ext = INTERPRETERS[lang] + source = abspath(join(THIS_PATH, benchmark, benchmark + ext)) + if not exists(source): continue print(" %-10s : "%lang, end=''); sys.stdout.flush() - result = _run_command([interp, file]) - time = re.findall(r'elapsed:\s*([0-9\.]+)\s*s', - result.stdout.decode('utf8'), - re.MULTILINE) + result = subprocess.run([interp, source], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + time = get_time(result.stdout.decode('utf8')) + print('%.6fs' % float(time)) - if len(time) != 1: - print() # Skip the line. - error_exit(r'elapsed:\s*([0-9\.]+)\s*s --> no mach found.') - print('%.6fs'%float(time[0])) - pass + if benchmark not in results: + results[benchmark] = [] + results[benchmark].append([lang, time]) -def _run_command(command): - return subprocess.run(command, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - -## Returns a list of valid source files to run benchmarks. -def _source_files(files): - global INTERPRETERS - ret = [] - for file in files: - ext = get_ext(file) - if ext not in INTERPRETERS: continue - ret.append(file) - - ret.sort(key=lambda f : INTERPRETERS[get_ext(f)][2]) - return ret - -## ---------------------------------------------------------------------------- -## UPDATE INTERPRETERS -## ---------------------------------------------------------------------------- + display_results(results) +## Set the pocketlang path for the current system to the compiled output. def update_interpreters(): - pocket = _get_pocket_binary() - python = 'python' if platform.system() == 'Windows' else 'python3' - print_title("CHECKING FOR INTERPRETERS") - global INTERPRETERS - order = 0 - INTERPRETERS['.pk'] = _find_interp('pocketlang', pocket, order); order+=1 - INTERPRETERS['.wren'] = _find_interp('wren', 'wren', order); order+=1 - INTERPRETERS['.py'] = _find_interp('python', python, order); order+=1 - INTERPRETERS['.rb'] = _find_interp('ruby', 'ruby', order); order+=1 - INTERPRETERS['.lua'] = _find_interp('lua', 'lua', order); order+=1 - INTERPRETERS['.js'] = _find_interp('javascript', 'node', order); order+=1 - -## This will return the path of the pocket binary (on different platforms). -## The debug version of it for enabling the assertions. -def _get_pocket_binary(): system = platform.system() - if system not in SYSTEM_TO_BINARY_PATH: - error_exit("Unsupported platform %s" % system) + if system not in SYSTEM_TO_POCKET_PATH['current']: + print("Unsupported platform %s" % system) + sys.exit(1) - pocket = abspath(join(THIS_PATH, SYSTEM_TO_BINARY_PATH[system])) - if not os.path.exists(pocket): - error_exit("Pocket interpreter not found at: '%s'" % pocket) + global INTERPRETERS + pocket = abspath(join(THIS_PATH, SYSTEM_TO_POCKET_PATH['current'][system])) + pocket_older = abspath(join(THIS_PATH, SYSTEM_TO_POCKET_PATH['older'][system])) + if not exists(pocket): + print(f"{colmsg('Error', COL_RED)}: " + + "Pocket interpreter not found at: '%s'" % pocket) + sys.exit(1) + INTERPRETERS['pocketlang'] = (pocket, '.pk') - return pocket + ## Add if older version of pocketlang if exists. + if exists(pocket_older): + INTERPRETERS['pk-older'] = (pocket_older, '.pk') -## Find and return the interpreter from the path. -## as (lang, interp, val) tuple, where the val is the additional. -## data related to the interpreter. -def _find_interp(lang, interpreter, val): - print('%-27s' % (' Searching for %s ' % lang), end='') - sys.stdout.flush() - if which(interpreter): - print_success('-- found') - return (lang, interpreter, val) - print_warning('-- not found (skipped)') - return (lang, None, val) + if 'python' in INTERPRETERS and system == "Windows": + INTERPRETERS['python'] = ('python', '.py') + + missing = [] + for lang in INTERPRETERS: + interpreter = INTERPRETERS[lang][0] + print('%-27s' % (' Searching for %s ' % lang), end='') + if which(interpreter): + print(f"-- {colmsg('found', COL_GREEN)}") + else: + print(f"-- {colmsg('missing', COL_YELLOW)}") + missing.append(lang) + for miss in missing: + INTERPRETERS.pop(miss) -## Return the extension from the file name. -def get_ext(file_name): - period = file_name.rfind('.'); assert period > 0 - return file_name[period:] +## Match 'elapsed: