-
Notifications
You must be signed in to change notification settings - Fork 9
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
RFC: VM 2.0 #14
Conversation
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>> { |
There was a problem hiding this comment.
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)
?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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}?
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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>; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
📢 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. 📢 |
…e the `impl Trait` syntax explicitly since it's not yet part of the wider Rust vocabulary
…obj.borrow_mut(&lock)` and define `Ref` and `RefMut` types to track the duration of the dynamic borrow
- add `FunctionContext`, `MethodContext` shorthands
Replaces four abstractions of the existing Neon API (
Scope
,Arguments
,FunctionCall
, and thevm
module) with oneVm
trait, representing a (context-sensitive) view of the JavaScript virtual machine.Rendered
WIP Implementation