Skip to content

Commit

Permalink
Switch from heap/stack to just a heap
Browse files Browse the repository at this point in the history
This commit switches strategies for storing `JsValue` from a heap/stack
to just one heap. This mirrors the new strategy for `JsValue` storage
in rustwasm#1002 and should make multiplexing those strategies at
`wasm-bindgen`-time much easier.

Instead of having one array which acts as a stack for borrowed values
and one array for a heap of borrowed values, only one JS array is used
for storage of JS values now. This makes `getObject` far simpler by
simply being an array access, but it means that cloning an object now
reserves a new slot instead of reference counting it. If the old
reference counting behavior is needed it's thought that `Rc<JsValue>`
can be used in Rust.

The new "heap" has an initial stack pointer which grows downwards, and a
heap which grows upwards. The heap is a singly-linked-list which is
allocated/deallocated from. The stack grows downwards to zero and
presumably starts generating errors once it underflows. An initial stack
size of 32 is chosen as that should encompass all use cases today, but
we can eventually probably add configuration for this!

Note that the heap is initialized to all `null` for the stack and then
the initial JS values (`undefined`, `null`, `true`, `false`) are pushed
onto the heap in reserved locations.
  • Loading branch information
alexcrichton committed Nov 30, 2018
1 parent fbad34a commit 29591ef
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 207 deletions.
3 changes: 2 additions & 1 deletion crates/cli-support/src/js/js2rust.rs
Expand Up @@ -392,7 +392,8 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
if arg.is_ref_anyref() {
self.js_arguments.push((name.clone(), "any".to_string()));
self.cx.expose_borrowed_objects();
self.finally("stack.pop();");
self.cx.expose_global_stack_pointer();
self.finally("stack_pointer += 1;");
self.rust_arguments
.push(format!("addBorrowedObject({})", name));
return Ok(self);
Expand Down
266 changes: 71 additions & 195 deletions crates/cli-support/src/js/mod.rs
Expand Up @@ -114,7 +114,9 @@ enum Import<'a> {
},
}

const INITIAL_SLAB_VALUES: &[&str] = &["undefined", "null", "true", "false"];
const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"];
// Must be kept in sync with `src/lib.rs` of the `wasm-bindgen` crate
const INITIAL_HEAP_OFFSET: usize = 32;

impl<'a> Context<'a> {
fn export(&mut self, name: &str, contents: &str, comments: Option<String>) {
Expand Down Expand Up @@ -168,44 +170,20 @@ impl<'a> Context<'a> {
self.write_classes()?;

self.bind("__wbindgen_object_clone_ref", &|me| {
me.expose_add_heap_object();
me.expose_get_object();
let bump_cnt = if me.config.debug {
String::from(
"
if (typeof(val) === 'number') throw new Error('corrupt slab');
val.cnt += 1;
",
)
} else {
String::from("val.cnt += 1;")
};
Ok(format!(
me.expose_add_heap_object();
Ok(String::from(
"
function(idx) {
return addHeapObject(getObject(idx));
}
"
function(idx) {{
// If this object is on the stack promote it to the heap.
if ((idx & 1) === 1) return addHeapObject(getObject(idx));
// Otherwise if the object is on the heap just bump the
// refcount and move on
const val = slab[idx >> 1];
{}
return idx;
}}
",
bump_cnt
))
})?;

self.bind("__wbindgen_object_drop_ref", &|me| {
me.expose_drop_ref();
Ok(String::from(
"
function(i) {
dropRef(i);
}
",
))
Ok(String::from("function(i) { dropObject(i); }"))
})?;

self.bind("__wbindgen_string_new", &|me| {
Expand All @@ -222,13 +200,7 @@ impl<'a> Context<'a> {

self.bind("__wbindgen_number_new", &|me| {
me.expose_add_heap_object();
Ok(String::from(
"
function(i) {
return addHeapObject(i);
}
",
))
Ok(String::from("function(i) { return addHeapObject(i); }"))
})?;

self.bind("__wbindgen_number_get", &|me| {
Expand Down Expand Up @@ -370,7 +342,7 @@ impl<'a> Context<'a> {
"
function(i) {
const obj = getObject(i).original;
dropRef(i);
dropObject(i);
if (obj.cnt-- == 1) {
obj.a = 0;
return 1;
Expand All @@ -383,7 +355,7 @@ impl<'a> Context<'a> {

self.bind("__wbindgen_cb_forget", &|me| {
me.expose_drop_ref();
Ok("dropRef".to_string())
Ok("dropObject".to_string())
})?;

self.bind("__wbindgen_json_parse", &|me| {
Expand Down Expand Up @@ -427,14 +399,7 @@ impl<'a> Context<'a> {
self.bind("__wbindgen_memory", &|me| {
me.expose_add_heap_object();
let mem = me.memory();
Ok(format!(
"
function() {{
return addHeapObject({});
}}
",
mem
))
Ok(format!("function() {{ return addHeapObject({}); }}", mem))
})?;

self.bind("__wbindgen_module", &|me| {
Expand Down Expand Up @@ -916,149 +881,49 @@ impl<'a> Context<'a> {
if !self.exposed_globals.insert("drop_ref") {
return;
}
self.expose_global_slab();
self.expose_global_slab_next();
let validate_owned = if self.config.debug {
String::from(
"
if ((idx & 1) === 1) throw new Error('cannot drop ref of stack objects');
",
)
} else {
String::new()
};
let dec_ref = if self.config.debug {
String::from(
"
if (typeof(obj) === 'number') throw new Error('corrupt slab');
obj.cnt -= 1;
if (obj.cnt > 0) return;
",
)
} else {
String::from(
"
obj.cnt -= 1;
if (obj.cnt > 0) return;
",
)
};
self.expose_global_heap();
self.expose_global_heap_next();
self.global(&format!(
"
function dropRef(idx) {{
{}
idx = idx >> 1;
function dropObject(idx) {{
if (idx < {}) return;
let obj = slab[idx];
{}
// If we hit 0 then free up our space in the slab
slab[idx] = slab_next;
slab_next = idx;
heap[idx] = heap_next;
heap_next = idx;
}}
",
validate_owned,
INITIAL_SLAB_VALUES.len(),
dec_ref
));
}

fn expose_global_stack(&mut self) {
if !self.exposed_globals.insert("stack") {
return;
}
self.global(&format!(
"
const stack = [];
"
INITIAL_HEAP_OFFSET + INITIAL_HEAP_VALUES.len(),
));
if self.config.debug {
self.export(
"assertStackEmpty",
"
function() {
if (stack.length === 0) return;
throw new Error('stack is not currently empty');
}
",
None,
);
}
}

fn expose_global_slab(&mut self) {
if !self.exposed_globals.insert("slab") {
fn expose_global_heap(&mut self) {
if !self.exposed_globals.insert("heap") {
return;
}
let initial_values = INITIAL_SLAB_VALUES
.iter()
.map(|s| format!("{{ obj: {} }}", s))
.collect::<Vec<_>>();
self.global(&format!("const slab = [{}];", initial_values.join(", ")));
if self.config.debug {
self.export(
"assertSlabEmpty",
&format!(
"
function() {{
for (let i = {}; i < slab.length; i++) {{
if (typeof(slab[i]) === 'number') continue;
throw new Error('slab is not currently empty');
}}
}}
",
initial_values.len()
),
None,
);
self.global("const heap = [];");
let init = format!(
"for (let i = 0; i < {}; i++) heap.push(undefined);",
INITIAL_HEAP_OFFSET,
);
self.global(&init);
for value in INITIAL_HEAP_VALUES {
self.global(&format!("heap.push({});", value));
}
}

fn expose_global_slab_next(&mut self) {
if !self.exposed_globals.insert("slab_next") {
fn expose_global_heap_next(&mut self) {
if !self.exposed_globals.insert("heap_next") {
return;
}
self.expose_global_slab();
self.global(
"
let slab_next = slab.length;
",
);
self.expose_global_heap();
self.global("let heap_next = heap.length;");
}

fn expose_get_object(&mut self) {
if !self.exposed_globals.insert("get_object") {
return;
}
self.expose_global_stack();
self.expose_global_slab();

let get_obj = if self.config.debug {
String::from(
"
if (typeof(val) === 'number') throw new Error('corrupt slab');
return val.obj;
",
)
} else {
String::from(
"
return val.obj;
",
)
};
self.global(&format!(
"
function getObject(idx) {{
if ((idx & 1) === 1) {{
return stack[idx >> 1];
}} else {{
const val = slab[idx >> 1];
{}
}}
}}
",
get_obj
));
self.expose_global_heap();
self.global("function getObject(idx) { return heap[idx]; }");
}

fn expose_assert_num(&mut self) {
Expand Down Expand Up @@ -1510,19 +1375,35 @@ impl<'a> Context<'a> {
);
}

fn expose_global_stack_pointer(&mut self) {
if !self.exposed_globals.insert("stack_pointer") {
return;
}
self.global(&format!("let stack_pointer = {};", INITIAL_HEAP_OFFSET));
}

fn expose_borrowed_objects(&mut self) {
if !self.exposed_globals.insert("borrowed_objects") {
return;
}
self.expose_global_stack();
self.global(
self.expose_global_heap();
self.expose_global_stack_pointer();
let validate = if self.config.debug {
"if (stack_pointer == 1) throw new Error('out of js stack');"
} else {
""
};
self.global(&format!(
"
function addBorrowedObject(obj) {
stack.push(obj);
return ((stack.length - 1) << 1) | 1;
}
function addBorrowedObject(obj) {{
{}
stack_pointer -= 1;
heap[stack_pointer] = obj;
return stack_pointer;
}}
",
);
validate,
));
}

fn expose_take_object(&mut self) {
Expand All @@ -1535,7 +1416,7 @@ impl<'a> Context<'a> {
"
function takeObject(idx) {
const ret = getObject(idx);
dropRef(idx);
dropObject(idx);
return ret;
}
",
Expand All @@ -1546,34 +1427,29 @@ impl<'a> Context<'a> {
if !self.exposed_globals.insert("add_heap_object") {
return;
}
self.expose_global_slab();
self.expose_global_slab_next();
let set_slab_next = if self.config.debug {
self.expose_global_heap();
self.expose_global_heap_next();
let set_heap_next = if self.config.debug {
String::from(
"
if (typeof(next) !== 'number') throw new Error('corrupt slab');
slab_next = next;
if (typeof(heap_next) !== 'number') throw new Error('corrupt heap');
",
)
} else {
String::from(
"
slab_next = next;
",
)
String::new()
};
self.global(&format!(
"
function addHeapObject(obj) {{
if (slab_next === slab.length) slab.push(slab.length + 1);
const idx = slab_next;
const next = slab[idx];
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
heap_next = heap[idx];
{}
slab[idx] = {{ obj, cnt: 1 }};
return idx << 1;
heap[idx] = obj;
return idx;
}}
",
set_slab_next
set_heap_next
));
}

Expand Down

0 comments on commit 29591ef

Please sign in to comment.