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

Problem of the interoperability with Wasm. #9

Closed
Becavalier opened this issue Apr 16, 2021 · 11 comments
Closed

Problem of the interoperability with Wasm. #9

Becavalier opened this issue Apr 16, 2021 · 11 comments

Comments

@Becavalier
Copy link

Becavalier commented Apr 16, 2021

Since JavaScript and WebAssembly can communicate with each other by accessing the same Arraybuffer, and the WebAssembly.Memory object has the ability to export its internal Arraybuffer by calling the buffer method on it. If so, what would be the behavior if we freeze the exported Arraybuffer and mutate the corresponding memory of this Arraybuffer by the Wasm instruction like i32.store?Without messing things up, I think the better ways are:

  1. Proposing a new type like "ReadOnlyArraybuffer", for example. And It should be inherited from Arraybuffer, then moving the read-only property from Arraybuffer to this new type without touching anything of the inherited Arraybuffer. And also, by leveraging the "fixed-view" proposal (which could be extended more on this new type), people cannot get the internal Arraybuffer of this new type directly. (so, it's just something like a fix-view and read-only TypedArray). And here comes the next solution.
  2. Simply adding two new methods on all the TypedArray objects, the first one is called "freeze()" which is almost the same as you just proposed. By calling this method, people cannot mutate any bytes of the underlying Arraybuffer through this view. The second method can be called "hide()" which can make it impossible to get the internal Arraybuffer reference of a specific view. And the result is that we should keep the Arraybuffer as is since it's more of an underlying mechanism.
var memory = new WebAssembly.Memory({ initial:10, maximum:100 });
var arraybuffer = memory.buffer;  // returns an Arraybuffer instance.
arraybuffer.prototype.freeze();  // freeze the buffer.
WebAssembly.instantiateStreaming(fetch('memory.wasm'), { js: { mem: memory } })
.then(obj => {
  // ...
  // call the exported method which would mutate the value of a specific location of memory, 
  // what should be the behavior here?
});

In conclusion, I think ArrayBuffer itself should not take the responsibility of the mutability, this ability should be controlled by the view lying on it. How do you think?

@Jack-Works
Copy link
Member

I think ArrayBuffer itself should not take the responsibility of the mutability, this ability should be controlled by the view lying on it.

The motivation of making the read-only ability on the ArrayBuffer itself instead of on the view is cause I think it allows the engine to optimize based on the immutable binary. If the ArrayBuffer cannot be guaranteed to be frozen, (I guess) the engine cannot do those optimizations. But yes, limit it to the view can make things better.

@ghost
Copy link

ghost commented Apr 16, 2021

I don't see how this is different from issue #6?

It doesn't particularly matter how it's being mutated, or who is mutating it, rather what matters is that it's being mutated at all.

I raised the same idea, that the ArrayBuffer itself should be mutable/immutable here: #8. The API doesn't look great once you consider that there are currently a bunch of other proposals that are trying to add new ArrayBuffer types too.

Oh yeah, imho, any freezing mechanism should throw when used on detached ArrayBuffers, and on Wasm heaps and/or views to these, at least, in strict mode they should.

@Becavalier
Copy link
Author

Becavalier commented Apr 16, 2021

It doesn't particularly matter how it's being mutated, or who is mutating it, rather what matters is that it's being mutated at all.

On the contrary, I think it's important to make sure who or which part is responsible to mutate the underlying Arraybuffer, the responsibility boundary should be clear. To me, the Arraybuffer itself is only a bunch of continuous memory without any other specific attributes or functionalities.

You can regard the relationship between Arraybuffer and ArrayBufferView in analogous to the relationship of pointer/reference and the pointed value in C/C++/Rust. For pointers, we have the concepts like bottom-level constness and top-level constness, so, you can think of the view just like a pointer which points to the underlying data.

And we can maybe even enable the below pattern to make a "frozen" ArrayBufferView just behaves like what we expected: people cannot mutate the underlying data through it (this particular view).

// enable this usage.
let v = Object.freeze(new Float64Array(new ArrayBuffer(64), 32, 2))
v.buffer  // returns null.
v[0] = 1  // Error!

In this case, the internal Arraybuffer would be safe as long as there is no external reference on it, meaning no access and no return value assignment on the buffer attribute of the view before calling the "freeze" method. It's a little bit similar to the && in C++ or let v = &[1,2,3]; in Rust formally.

@ghost
Copy link

ghost commented Apr 16, 2021

@Becavalier For clarification, by "who," I was referring to JS vs Wasm, and by "how," I was referring to JS' property assignment vs Wasm's TxN.store operations.

Also, slightly off-topic, I understand the reference to Rust's &x, but not C++'s &&, considering how this is valid:

void reassign(
	unsigned int&& x
) {
	x = 0u;
}

@ghost
Copy link

ghost commented Apr 16, 2021

@Jack-Works

If the ArrayBuffer cannot be guaranteed to be frozen, (I guess) the engine cannot do those optimizations. But yes, limit it to the view can make things better.

By having frozen array buffers, the view is guaranteed to be frozen too, but not vice verse, therefore this idea seems to support frozen arraybuffers, better than oppose the idea.

@Becavalier
Copy link
Author

Also, slightly off-topic, I understand the reference to Rust's &x, but not C++'s &&, considering how this is valid:

Yes, this is valid, but it just doesn't make any practical sense. The rvalue passed in will be placed on the stack without any accessible external reference. So, the re-assignment to this rvalue doesn't make it live longer.

image

Anyway, the purpose of the analogies of pointers/references is that I want to demonstrate the relationship between Arraybuffer (analogous to rvalue) and ArrayBufferView (analogous to pointers/references). The mutability of the pointers/references will not affect the mutability of the rvalue itself.

@ghost
Copy link

ghost commented Apr 17, 2021

The mutability of the pointers/references will not affect the mutability of the rvalue itself.

Yes, this is an idea being proposed here, but as an extra feature, see #6 (comment); the primary mechanism for creating frozen ArrayBuffers/ArrayBufferViews should copy once to create a frozen buffer/view.

@Jack-Works
Copy link
Member

Engines might still be able to optimize if all mutable reference to an ArrayBuffer is dropped.

function ro() {
    const x = new ArrayBuffer(2048)
    fillData(x)
    return new ReadonlyArrayBuffer(x)
    // x the read-write ref is lost
}

But here is a question, does line 4 new ROArrayBuffer(x) requires a full-clone on the original buffer? That's an unacceptable performance cost. And this way also makes the optimization unstable because you should make sure all read-write references are GCed thus it's an implicit optimization instead of an explicit (.freeze()) one.

@ghost
Copy link

ghost commented Apr 19, 2021

Well, consider what you had said before: #6 (comment), a copy is almost definitely required.

But maybe an engine could attempt to implement copy-on-write semantics under the hood? (Doesn't sound ideal)

The only problems that I have with a .freeze method are that it requires mutating objects to make it immutable, which is somewhat counter-intuitive? Yes, Object.freeze has this same problem, it can be used to mutate arbitrary (e.x.: third party library) objects, that may have been intended to have stated mutable.

The other reason is that freezing a Wasm heap makes no sense, and should definitely fail.

@Jack-Works
Copy link
Member

Well, consider what you had said before: #6 (comment), a copy is almost definitely required.

Yes, when only generating an immutable value from a mutable reference. If you freeze the original value there is no need to copy it.

The other reason is that freezing a Wasm heap makes no sense, and should definitely fail.

The freezing feature is not prepared for WASM. It is used for other security scenarios.

@Jack-Works
Copy link
Member

Jack-Works commented Nov 11, 2021

This issue is fixed in the new API design in #13.

https://github.com/tc39/proposal-limited-arraybuffer/blob/15d807a6c2ff63f45dcbc132c00cc95cbe881830/design.md#host-integration

Host integration guide in the new API design suggests:

  • The host should reject frozen ArrayBuffer if they need to write it.
  • The host should prevent the ArrayBuffer to be frozen when they're holding it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants