Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: VM 2.0 #14

Merged
merged 15 commits into from
Jul 25, 2018
Merged

RFC: VM 2.0 #14

merged 15 commits into from
Jul 25, 2018

Conversation

dherman
Copy link
Contributor

@dherman dherman commented Dec 20, 2017

Replaces four abstractions of the existing Neon API (Scope, Arguments, FunctionCall, and the vm module) with one Vm trait, representing a (context-sensitive) view of the JavaScript virtual machine.

Rendered

WIP Implementation

text/vm-2.0.md Outdated
For example, a simple Neon function that takes a single `ArrayBuffer` object and sets its first byte to 0 would be implemented by calling `vm.lock()` and then `lock.inspect()` with the resulting lock object:

```rust
fn zero_first_byte(mut vm: impl Vm) -> JsResult<Handle<JsNumber>> {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason that this needs to rely on unstable features? Could this just be fn zero_first_byte<T: Vm>(mut vm: T)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I was just using the shorthand because it makes me happy :P There's no inherent reliance on it. I should eliminate that to avoid confusion.

text/vm-2.0.md Outdated

```rust
impl Lock {
fn inspect<T: Inspect>(&self, value: Handle<T>) -> &mut T::Internals;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not look quite right: the Handle is copy, so we can get two aliased &mut T::Internals?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good point. The way the current locking mechanism works is by dynamically tracking what ranges in memory are currently being inspected. We can store that state inside the Lock object itself, and either make inspect an &mut self method or store that state in a cell.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way the current locking mechanism works is by dynamically tracking what ranges in memory are currently being inspected.

Yeah, so I would expect the return type to be RefMut<T::Internals>, like RefCell works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really interesting point. If we're going all the way to consistency with the stdlib, it makes me wonder if that suggests refactoring the API so that Inspect gets a name more like RefValue or something like that, with:

trait RefValue<T> {
    fn borrow(&self, lock: &Lock) -> Ref<T>;
    fn borrow_mut(&mut self, lock: &Lock) -> RefMut<T>;
}

So that you would write examples like zero_first_byte from the RFC like so:

fn zero_first_byte(mut vm: impl Vm) -> JsResult<Handle<JsNumber>> {
    let array_buffer = vm.argument(0).unwrap().check::<JsArrayBuffer>()?;
    let lock = vm.lock();
    let mut contents = array_buffer.borrow_mut(&lock);
    let mut buf = contents.as_u8_slice();
    buf[0] = 0;
    Ok(())
}

It's appealing to use the nomenclature of borrowing, although since I haven't worked with ref cells in practice, I don't know what kind of learnability hurdles they might introduce. Maybe it's no worse than the learnability of the Inspect API though? I really like reusing the intuitions of borrowing for understanding what it means to temporarily inspect the contents of a JS value.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're going all the way to consistency with the stdlib,

My point was not about consistency with stdlib (though that'd be nice!). I think that if you do run-time tracking, you'll have to use either closure-base APIs, or return some sort of lock-object, like RefMut. Returning plain &mut T I think will be unsond.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I don't see how to make it work with the actual Ref/RefMut types of std::cell, since those have no constructors and the only way to get one is to call borrow{_mut}() on a RefCell. I could do something analogous with a custom Neon Ref and RefMut type though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose the soundness issue is needing to track the duration of the borrows? And having a wrapper type allows us to register the borrow on allocation of the Ref{Mut} and unregister it on drop of the Ref{Mut}?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I totally didn't mean that we should use std::cell::RefMut, that's impossible. But we quite probably will have to roll our own neon::RefMut.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks! This makes sense to me, and I really like the way the API feels.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose the soundness issue is needing to track the duration of the borrows? And having a wrapper type allows us to register the borrow on allocation of the Ref{Mut} and unregister it on drop of the Ref{Mut}?

Exactly, std::RefCell is basically a single-threaded std::Mutex, and std::RefMut is std::LockGuard.

Copy link

@tptee tptee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really pumped to see this happen 😄 The API ergonomics are a huge leap forward and I think they'll lower the barrier of entry a good bit.

I never understood nest/chain, so seeing the docs and renames to execute_scope and compute_scope are particularly helpful. Love the shorthands on CallContext as well!

text/vm-2.0.md Outdated
fn argument_opt(&mut self, i: i32) -> Option<Handle<'a, JsValue>>;
fn argument<V: Value>(&mut self, i: i32) -> JsResult<'a, V>;
fn this(&mut self) -> Handle<'a, T>;
fn callee(&mut self) -> Handle<'a, JsFunction>;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we leaving this out of VM 2.0 or removing it later before the 0.2 release?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I'll remove it from the RFC.

@dherman
Copy link
Contributor Author

dherman commented Jul 5, 2018

📢 This RFC is going into final comment period! We will plan to merge it within a week if there aren't significant new issues that arise in the next week. 📢

@dherman dherman added the final comment period last opportunity to discuss the proposed conclusion label Jul 5, 2018
@dherman dherman merged commit e67ff40 into neon-bindings:master Jul 25, 2018
@dherman dherman deleted the vm-two-point-oh branch July 25, 2018 07:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
final comment period last opportunity to discuss the proposed conclusion
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants