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

Allow i64 values to be passed #35

Closed
xtuc opened this issue Feb 20, 2018 · 28 comments
Closed

Allow i64 values to be passed #35

xtuc opened this issue Feb 20, 2018 · 28 comments
Labels
more-types Adding support for more Rust types to cross the boundary

Comments

@xtuc
Copy link
Member

xtuc commented Feb 20, 2018

I mentioned here WebAssembly/design#1172 that we will be able to use the BigInt proposal to represent the i64 values from WebAssembly.

Currently there is not support for BigInt in browsers (nor in Babel). So I believe we can use this kind of tools to allow it.

We use a 64 bit two's-complement (https://github.com/dcodeIO/long.js).

Have you considered to implement it?

Edit:
I just remembered that WebAssembly doesn't allow multiple results for a func, I'm not sure what's the best solution, maybe the linear memory?

When I said "we use" I meant in https://github.com/xtuc/webassemblyjs which is not the same use-case.

@alexcrichton
Copy link
Contributor

Seems plausible to me! I initially hesitated in exposing this because I wasn't sure what types these would map to on the JS side of things, but maybe long.js is standard enough that "pulling in a dependency to do this" isn't so bad?

@xtuc
Copy link
Member Author

xtuc commented Feb 20, 2018

Long.js doesn't everything we need and is fast (uses WebAssembly when possible), it's ok to pull in that dependency IMO.

what types these would map to on the JS side

Well i64 can not be represented as a primitive in JavaScript, you'll need to use an object (like Long.js does).

It will also hide the implementation (long.js or bigint).

@alexcrichton
Copy link
Contributor

Oh how would the implementation be hidden? Wouldn't JS want to pass values in (aka construct them) and also read the results?

@xtuc
Copy link
Member Author

xtuc commented Feb 21, 2018

My point is that we can't represent a i64 value is JS so you couldn't pass it in the constructor, you could either as string or as a two's complement int.

To be more accurate, we need an abstraction to represent the value. For example:

i64 =
| BigInt if supported
| {i32, i32}

Apart with BigInt all built-in operations won't work (+, -, etc), but that's probably worth having the value at all. Note that Long.js already provides some methods to do the operations.

Depending how far you want to go but we have a PR against Babel to implement BigInt, we could re-use that here to "fake" the built-in operations (until BigInt is supported).

Does that make sense to you?

@alexcrichton
Copy link
Contributor

With WebAssembly/design#1186 being created pretty recently, I wonder if it'd be best to hold off on a long.js polyfill and wait for BigInt?

@xtuc
Copy link
Member Author

xtuc commented Feb 26, 2018

That's a good question. BigInt is stage-3 so we can't assume that browser have it available until years (probably).

I think it could act as a good experimentation and make i64 available until BigInt is there, what do you think?

@alexcrichton
Copy link
Contributor

Ah ok yeah, if it's that far off then doing the polyfill of long.js for now seems reasonable to me!

@xtuc
Copy link
Member Author

xtuc commented Feb 27, 2018

I think we could mutualize some work here. I would like to use the same mechanism in multiple projects, what do you think if I publish a @webassemblyjs/i64 (or something like that) package that does this abstraction?

The logic would be

i64 =
| BigInt if supported
| Long.js

@alexcrichton
Copy link
Contributor

Hm I think unfortunately I'm not familiar enough with idiomatic JS really to make a judgement call here. I'd naively expect that a custom package for this wouldn't be idiomatic, but I also don't know the idioms so I'm not sure.

@xtuc
Copy link
Member Author

xtuc commented Feb 27, 2018

I'm not sure to fully understand your concern.

The package is just here to provide an abstraction, with the same API than BigInt. Ideally for user it's transparent.

@RReverser
Copy link
Member

I'd naively expect that a custom package for this wouldn't be idiomatic, but I also don't know the idioms so I'm not sure.

I'd agree with @alexcrichton, automatically pulling an external dependency is a concerning decision since now you need to be either responsible for keeping it up-to-date or forcing user to do it, either way keeping code locked to such dependency.

On the other hand, there are no really existing idioms for 64-bit integers in JS yet because they just weren't part of it. Most common was to either pass them as string or loosely convert to floats (then you preserve precision up to 53 bits), but each way has its own pitfalls.

So my suggestion would be to go the way each new API in JS gets adopted: generate calls to upcoming native BigInt API, and so expect it to either be already in the environment or that users polyfill it themselves by window.BigInt = (whatever compatible implementation they prefer).

@xtuc
Copy link
Member Author

xtuc commented Feb 27, 2018

Most common was to either pass them as string or loosely convert to floats

I would prefer the two's complement because:

  • the arithmetic on the string representation is super slow, they are implementable using a carry and by parsing only a bunch of number at a time (which is generally slow).
  • JavaScript Number or 64 bits floats can't represent numbers > 2^53, as you mentioned.

Some operations are tricky to implement on two i32 but hopefully for us Long.js already done that.

polyfill

I like that idea, we could provide a polyfill (using long.js or whatever), checking first for typeof window.BigInt === "undefined" and the bindings would just return an instance of window.BigInt. It's also future proof.

The package is just here to provide an abstraction, with the same API than BigInt. Ideally for user it's transparent.

I might have misworded that. My idea is that we wouldn't expose/impose anything to the user, the user just gets an object with the BigInt API (backend by BigInt or not).

The package simplifies the creation of that object, just like a factory.

Or the package would contain the polyfill of BigInt (backend by BigInt or not).

Does that make sense to you?

@RReverser
Copy link
Member

I might have misworded that. My idea is that we wouldn't expose/impose anything to the user, the user just gets an object with the BigInt API (backend by BigInt or not).

Oh ok. It did initially sound like you want to include specifically long.js together with library.

So we agree that wasm-bindgen can just use new BigInt and user of the library will be responsible for ensuring that it's polyfilled when needed using whatever implementation they like?

@alexcrichton
Copy link
Contributor

Delegating to proposed BigInt APIs and relying on an external polyfill sounds like a great idea to me!

@xtuc
Copy link
Member Author

xtuc commented Feb 27, 2018

That sounds good but unless we're adding a transpilation phase we won't be able to use the built-in operators (+, -, /...) where BigInt will.

@RReverser
Copy link
Member

@xtuc Why would we need to use these operators in bindings code? We only need to construct BigInt instances, and it's up to the user of bindings how to use them, whether to transpile their code etc.

@xtuc
Copy link
Member Author

xtuc commented Feb 28, 2018

For example:

someBindingToWasm.exports.getMaxI64() - 1n

should work according to the BigInt specification, but the polyfill won't cover that case.

I mentioned transpilation because that could be a possible solution.

@RReverser
Copy link
Member

RReverser commented Feb 28, 2018

But, again, what you posted would be the user code, not the generated one, no? So it's outside of wasm-bindgen's responsibility in any way and it's up to the user to choose how they want to work with BigInts.

@xtuc
Copy link
Member Author

xtuc commented Feb 28, 2018

I agree, but my point is that if you want to add two numbers:

  • BigInt uses the built-in +
  • Long.js uses a add method (or something similar)

As a user you can't use the same code.

@RReverser
Copy link
Member

I understand what you're saying but I don't understand how it's related or how it affects wasm-bindgen :)

@RReverser
Copy link
Member

I mean, as long as we just use new BigInt on JS side, difference in operations affects only the end user, and it's up to them to make sure they have needed polyfills, transpiler plugins etc. for their own code - nothing on wasm-bindgen side or in generated bindings will change based on their decision, they will continue to work nevertheless.

@xtuc
Copy link
Member Author

xtuc commented Mar 1, 2018

I don't understand how it's related or how it affects wasm-bindgen

Ok, I get it now, you're right!

@alexcrichton alexcrichton added the more-types Adding support for more Rust types to cross the boundary label Apr 16, 2018
@jsheard
Copy link
Contributor

jsheard commented May 2, 2018

FYI: Native BigInt is now available in Chrome Beta/Canary, with intent to ship in Chrome 67 stable.

https://developers.google.com/web/updates/2018/05/bigint

Viewing wasm memory as a BigInt64Array/BigUint64Array should make i64/u64 interop pretty easy.

@alexcrichton
Copy link
Contributor

I've sent a PR to take advantage of the new support in Chrome beta at #188

@tsing80
Copy link

tsing80 commented May 17, 2018

is there any polyfill available to work with existing browser?

@alexcrichton
Copy link
Contributor

Unfortunately I'm not currently aware of one myself

@xtuc
Copy link
Member Author

xtuc commented May 17, 2018

Not in Babel. It requires changing somehow the built-in operations.

@xtuc
Copy link
Member Author

xtuc commented Dec 20, 2018

We are working on the conversion between i64 and JS's BigInt; instead of the current polyfilling strategy it could be exposed natively. However, in order to move the proposal forward (WebAssembly/JS-BigInt-integration#15) we would need some toolchain support. I think it would be great to implement something here, behind an experimental flag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
more-types Adding support for more Rust types to cross the boundary
Projects
None yet
Development

No branches or pull requests

5 participants