This commit is contained in:
Dmitry Vasilev
2023-11-18 23:23:47 +08:00
parent 2ce096dc53
commit 128006aacc
3 changed files with 105 additions and 103 deletions

View File

@@ -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
})

View File

@@ -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})
}
}

View File

@@ -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})
}
}