From 128006aacca169283f83016886b9f575c7a849f4 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilev Date: Sat, 18 Nov 2023 23:23:47 +0800 Subject: [PATCH] refactor --- src/eval.js | 6 +-- src/runtime/multiversion.js | 97 +++++++++++++++++++++++++++++++++ src/runtime/runtime.js | 105 ++---------------------------------- 3 files changed, 105 insertions(+), 103 deletions(-) create mode 100644 src/runtime/multiversion.js diff --git a/src/eval.js b/src/eval.js index 39bf3f0..2173971 100644 --- a/src/eval.js +++ b/src/eval.js @@ -18,7 +18,7 @@ import {has_toplevel_await} from './find_definitions.js' // import runtime as external because it has non-functional code // external -import {run, do_eval_expand_calltree_node, Multiversion} from './runtime/runtime.js' +import {run, do_eval_expand_calltree_node, LetMultiversion} from './runtime/runtime.js' // TODO: fix error messages. For example, "__fn is not a function" @@ -766,7 +766,7 @@ const do_eval_frame_expr = (node, eval_cxt, frame_cxt) => { ? null : Object.fromEntries( Object.entries(closure) - .filter(([k,value]) => value instanceof Multiversion) + .filter(([k,value]) => value instanceof LetMultiversion) .map(([k,value]) => [symbol_for_closed_let_var(k), value]) ) @@ -1353,7 +1353,7 @@ export const eval_frame = (calltree_node, modules) => { // TODO default values for destructuring can be function calls const closure = map_object(calltree_node.fn.__closure, (_key, value) => { - return value instanceof Multiversion + return value instanceof LetMultiversion ? value.get_version(calltree_node.id) : value }) diff --git a/src/runtime/multiversion.js b/src/runtime/multiversion.js new file mode 100644 index 0000000..9d40073 --- /dev/null +++ b/src/runtime/multiversion.js @@ -0,0 +1,97 @@ +// https://stackoverflow.com/a/29018745 +function binarySearch(arr, el, compare_fn) { + let m = 0; + let n = arr.length - 1; + while (m <= n) { + let k = (n + m) >> 1; + let cmp = compare_fn(el, arr[k]); + if (cmp > 0) { + m = k + 1; + } else if(cmp < 0) { + n = k - 1; + } else { + return k; + } + } + return ~m; +} + +export class Multiversion { + constructor(cxt, initial) { + this.cxt = cxt + this.is_expanding_calltree_node = cxt.is_expanding_calltree_node + this.latest = initial + this.versions = [{call_id: cxt.call_counter, value: initial}] + } + + get() { + const call_id = this.cxt.call_counter + + if(!this.cxt.is_expanding_calltree_node) { + return this.latest + } else { + if(this.is_expanding_calltree_node) { + // var was created during current expansion, use its latest value + return this.latest + } else { + if(this.latest_copy != null) { + // value was set during expand_calltree_node, use this value + return this.latest + } + // TODO on first read, set latest and latest_copy? + return this.get_version(call_id) + } + } + } + + get_version(call_id) { + const idx = binarySearch(this.versions, call_id, (id, el) => id - el.call_id) + if(idx == 0) { + // This branch is unreachable. get_version will be never called for a + // call_id where let variable was declared. + throw new Error('illegal state') + } else if(idx > 0) { + return this.versions[idx - 1].value + } else if(idx == -1) { + throw new Error('illegal state') + } else { + return this.versions[-idx - 2].value + } + } + + set(value) { + const call_id = this.cxt.call_counter + if(this.cxt.is_expanding_calltree_node) { + if(this.is_expanding_calltree_node) { + this.latest = value + this.set_version(call_id, value) + this.cxt.touched_multiversions.add(this) + } else { + if(this.latest_copy == null) { + this.latest_copy = {value: this.latest} + } + this.cxt.touched_multiversions.add(this) + this.latest = value + } + } else { + this.latest = value + this.set_version(call_id, value) + } + } + + last_version_number() { + return this.versions.at(-1).call_id + } + + set_version(call_id, value) { + const last_version = this.versions.at(-1) + if(last_version.call_id > call_id) { + throw new Error('illegal state') + } + if(last_version.call_id == call_id) { + last_version.value = value + return + } + this.versions.push({call_id, value}) + } +} diff --git a/src/runtime/runtime.js b/src/runtime/runtime.js index 3f1a22a..c9e20ea 100644 --- a/src/runtime/runtime.js +++ b/src/runtime/runtime.js @@ -1,4 +1,8 @@ import {set_current_context} from './record_io.js' +import {Multiversion} from './multiversion.js' + +// Create separate class to check value instanceof LetMultiversion +export class LetMultiversion extends Multiversion {} /* Converts generator-returning function to promise-returning function. Allows to @@ -92,7 +96,7 @@ const do_run = function*(module_fns, cxt, io_trace){ __trace_call, __do_await, __save_ct_node_for_path, - Multiversion, + LetMultiversion, ) if(result instanceof cxt.window.Promise) { yield cxt.window.Promise.race([replay_aborted_promise, result]) @@ -480,102 +484,3 @@ const __save_ct_node_for_path = (cxt, __calltree_node_by_loc, index, __call_id) set_record_call(cxt) } } - -// https://stackoverflow.com/a/29018745 -function binarySearch(arr, el, compare_fn) { - let m = 0; - let n = arr.length - 1; - while (m <= n) { - let k = (n + m) >> 1; - let cmp = compare_fn(el, arr[k]); - if (cmp > 0) { - m = k + 1; - } else if(cmp < 0) { - n = k - 1; - } else { - return k; - } - } - return ~m; -} - -// 'let' variable recording the history of its values -export class Multiversion { - constructor(cxt, initial) { - this.cxt = cxt - this.is_expanding_calltree_node = cxt.is_expanding_calltree_node - this.latest = initial - this.versions = [{call_id: cxt.call_counter, value: initial}] - } - - get() { - const call_id = this.cxt.call_counter - - if(!this.cxt.is_expanding_calltree_node) { - return this.latest - } else { - if(this.is_expanding_calltree_node) { - // var was created during current expansion, use its latest value - return this.latest - } else { - if(this.latest_copy != null) { - // value was set during expand_calltree_node, use this value - return this.latest - } - // TODO on first read, set latest and latest_copy? - return this.get_version(call_id) - } - } - } - - get_version(call_id) { - const idx = binarySearch(this.versions, call_id, (id, el) => id - el.call_id) - if(idx == 0) { - // This branch is unreachable. get_version will be never called for a - // call_id where let variable was declared. - throw new Error('illegal state') - } else if(idx > 0) { - return this.versions[idx - 1].value - } else if(idx == -1) { - throw new Error('illegal state') - } else { - return this.versions[-idx - 2].value - } - } - - set(value) { - const call_id = this.cxt.call_counter - if(this.cxt.is_expanding_calltree_node) { - if(this.is_expanding_calltree_node) { - this.latest = value - this.set_version(call_id, value) - this.cxt.touched_multiversions.add(this) - } else { - if(this.latest_copy == null) { - this.latest_copy = {value: this.latest} - } - this.cxt.touched_multiversions.add(this) - this.latest = value - } - } else { - this.latest = value - this.set_version(call_id, value) - } - } - - last_version_number() { - return this.versions.at(-1).call_id - } - - set_version(call_id, value) { - const last_version = this.versions.at(-1) - if(last_version.call_id > call_id) { - throw new Error('illegal state') - } - if(last_version.call_id == call_id) { - last_version.value = value - return - } - this.versions.push({call_id, value}) - } -}