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

Consider adding wasm linear memory access as a target use-case for the "view" feature #2582

Closed
yjbanov opened this issue Oct 20, 2022 · 10 comments
Labels
extension-types feature Proposed language feature that solves one or more problems inline-classes Cf. language/accepted/future-releases/inline-classes/feature-specification.md

Comments

@yjbanov
Copy link

yjbanov commented Oct 20, 2022

While dart2wasm compiles code to WasmGC instructions the wasm ecosystem will always have a good amount of code relying on wasm's linear memory. Even though data may be structured in the linear memory, all access to it is done via plain numbers combined with a set of load and store instructions. Dart2wasm will include intrinsics for accessing linear memory, but without any assistance from the language the code accessing it will be quite unreadable.

The view proposal is a great candidate to clean up such Dart code. A wasm i64 number may represent a "pointer" to an "object" inside the linear memory. Views can be used to guarantee safe access to the object by:

  • Removing integer arithmetic from i64, effectively removing "pointer arithmetic".
  • Providing safe access to object fields via typed getters and setters that hide the minutiae of calculating field offsets, alignment, and size, and interpretation/conversion of the stored values.

It is possible that the current proposal already covers this use-case, but if it does, it's only accidentally. I think it would be prudent to target it on purpose. Even if it's not part of the MVP, the design could accept the constraints early enough to prevent us from painting ourselves into a corner later.

@yjbanov yjbanov added the feature Proposed language feature that solves one or more problems label Oct 20, 2022
@mraleph
Copy link
Member

mraleph commented Oct 20, 2022

I always imagined that dart2wasm would actually implement dart:ffi APIs as a structured way to access linear memory. You get a lot of millage by reusing existing APIs and transforms. Any specific reason you don't want to go that way?

The kind of lowering into views is exactly the same thing we are considering to do to Pointer once views are available.

@leafpetersen leafpetersen added the inline-classes Cf. language/accepted/future-releases/inline-classes/feature-specification.md label Oct 20, 2022
@yjbanov
Copy link
Author

yjbanov commented Oct 24, 2022

@mraleph Possibly? To me "FFI" implies some other, "foreign", language on the other side that I'd be calling from Dart. However, my request includes use-cases where Dart is reading from/writing to linear memory standalone via dart:wasm. It is possible that the same data may be used from another language, but it doesn't have to be linked in with the Dart program in a way FFI requires. For example, it could be a C/C++ program running in a web worker accessing the same linear memory as a SharedArrayBuffer. In fact, using SharedArrayBuffer as linear memory and dart:wasm one could implement shared memory multi-threaded computations with just Dart. No second language involved. The trouble is code written against raw dart:wasm will be quite unreadable, which is something views can help with.

@mraleph
Copy link
Member

mraleph commented Oct 24, 2022

@yjbanov foreign is just contrasting GC managed Dart world and some other outside (foreign) world. The same dichotomy exists in Wasm GC: there is linear memory and there are GC managed objects - these two worlds are disjoint, they don't overlap so FFI is a perfect metaphor. You can take FFI when running natively, create aPointer from integer and start poking into memory - no second language is required. You can cast this pointer to Pointer<SomeStruct> and get a low-level but structured view on this memory, etc. This is exactly the same use case you are describing with when working with linear memory.

@yjbanov
Copy link
Author

yjbanov commented Oct 26, 2022

create aPointer from integer and start poking into memory - no second language is required

Whoa! I did not know this was possible. By "lowering into views" do you mean unwrapping the Pointer class? I see that pointer.runtimeType prints the string Pointer<...> so it seems the VM uses extra space for the type info in addition to the pointer itself. If pointers are unwrapped, it could work. The idea is that a Dart expression that looks like a.b.c.d.e should not allocate any new objects in the Dart heap or in the wasm memory.

@yjbanov
Copy link
Author

yjbanov commented Oct 26, 2022

So yeah, this prints false, but it should print true (without cheating in the implementation of identical 😜):

import 'dart:ffi';
import 'package:ffi/ffi.dart';

class B extends Struct {
  @Float() external double c;
}

class A extends Struct {
  external Pointer<B> b;
}

void main(List<String> arguments) {
  final Pointer<A> a = malloc<A>(1);
  print(identical(a.ref.b, a.ref.b));
}

Also, ideally, there would be a way to remove ref when pointers are implied.

@mraleph
Copy link
Member

mraleph commented Oct 26, 2022

By "lowering into views" do you mean unwrapping the Pointer class? I see that pointer.runtimeType prints the string Pointer<...> so it seems the VM uses extra space for the type info in addition to the pointer itself.

Right now we represent pointers as boxed objects, but we are going to change that. Most of the APIs on pointers are actually extension methods already.

Structs are a complicated case because they are not always pointers, because you can pass/return a struct by value in C and we need to be able to model that.

The idea is that a Dart expression that looks like a.b.c.d.e should not allocate any new objects in the Dart heap or in the wasm memory.

That's somewhat unrelated to whether a itself is boxed or not. You can elide intermediate object allocation in various ways through optimisations on different levels. We see that in practice it rarely matters that we currently box our pointers. (Though as I have said we are going to change that as well).

The point I am trying to make here is different though: I think what you are interested in here and what FFI provides is the same abstraction ultimately - so it is good to converge on an existing abstraction and make it portable, rather than building yet another custom abstraction.

@yjbanov
Copy link
Author

yjbanov commented Nov 8, 2022

We see that in practice it rarely matters

Practice is a funny thing. Before V8 came out with a 10x speed-up of JavaScript, one could say that JS speed didn't matter in practice because how much performance do you need to implement a blinking tag? :)

FFI provides is the same abstraction ultimately

Will FFI provide access to all the wasm types that are defined in dart:wasm? If not, will it be possible to combine FFI types with wasm types? For example, I see that Int8 is documented as "Int8 is not constructible in the Dart code and serves purely as marker in type signatures". So there's no way to have an int8 on the stack or in a field.

@mraleph
Copy link
Member

mraleph commented Nov 8, 2022

Practice is a funny thing. Before V8 came out with a 10x speed-up of JavaScript, one could say that JS speed didn't matter in practice because how much performance do you need to implement a blinking tag? :)

I think we are going off the rails here a bit - I am not going to dispute that being able to unbox pointers globally is a good property. It has always been part of the vision - we want pointers in Dart have the same overhead as pointers in C.

I just remarked that I have written and profiled a bunch of performance sensitive FFI code and pointer boxing was only a concern in a few corner cases, that's all.

So there's no way to have an int8 on the stack or in a field.

You can't have int8 in the Dart field or Dart variable because there is no int8 in Dart. You can have int8 in a field in an FFI struct just fine or as a return value / parameter in a function signature:

class B extends Struct {
  @Int8() external int field;
}

@Native<Int8 Function(Int8)>
external int foo(int v);

This is how you get an int8 field. It will still have to be projected into int when in crosses the boundary to Dart.

If you want int8 as a Dart value then you need zoo of integer types feature... Something we also asked @leafpetersen and @lrhn to consider (having this types would simplify how we look at dart:ffi a lot) - but this is orthogonal to views.

@yjbanov
Copy link
Author

yjbanov commented Nov 11, 2022

I think we are going off the rails here a bit - I am not going to dispute that being able to unbox pointers globally is a good property.

I apologize if I sound pushy. I've been burned by performance issues from unnecessary wrappers enough times that I must have developed an allergy from wrappers of some sort. This is just me sneezing 🤧

More practically, with dart2wasm + Flutter Web we're going to be adopting FFI as the method to interop with C++ for the exact same API that led to 4x slowdown due to wrappers in dart2js case. So I'm being cautious.

You can't have int8 in the Dart field or Dart variable because there is no int8 in Dart.

https://github.com/dart-lang/sdk/blob/3ab116198c8879e1be72720851eb9739fc5ae26a/sdk/lib/wasm/wasm_types.dart#L10

I'm assuming this means dart2wasm provides intrinsics for these types, which I think would be a good thing. Nor would it be a first time in Dart's history. JS-interop provides intrinsics to work with JavaScript objects directly, with no wrapping/conversion, and is used with great success. I imagine SIMD types work similarly.

Anyway, standardizing on FFI would be great from developer productivity standpoint, and you have my full support in making it as fast as possible. "the same overhead as pointers in C" is the right mindset to have!

@leafpetersen
Copy link
Member

I think this is covered by the current feature under development, closing this out (let me know if there's something additional we should be tracking here)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
extension-types feature Proposed language feature that solves one or more problems inline-classes Cf. language/accepted/future-releases/inline-classes/feature-specification.md
Projects
None yet
Development

No branches or pull requests

4 participants