Skip to content

Conversation

@graalvmbot
Copy link
Collaborator

The existing array coercion rules require JS typed arrays to be coercible to Java arrays while observing changes in both directions and the other way around Java primitive arrays to be coercible to JS typed arrays.
This is only possible in WasmGC if we can have a Java array backed by a JS typed array, this introduces an overhead on all arrays and we don't do here.

Instead, all array coercion rules are removed in both directions. You cannot get a typed JS array backed by a Java array anymore and you can't get a Java primitive array backed by a JS typed array anymore.

This PR thus introduces some breaking changes in the JS interop API (which is experimental so this is fine):

  • Coercion:
    • JS array to java: You get a regular JSObject through conversion. You can't coerce to a Java primitive array anymore.
    • Java array to JS: You get a regular proxy through conversion instead of the underlying typed array. Coercion doesn't do anything
  • JSValue#toString now contains the toString result of calling toString on the underlying JS value instead of first converting it to Java and then calling toString on the Java boxed equivalent.
  • All the JSValue.as*Array methods have been removed since casting to primitive arrays is no longer allowed: asBooleanArray, asByteArray, asShortArray, asCharArray, asIntArray, asFloatArray, asLongArray, asDoubleArray

The removal of the JS to Java coercion is probably the biggest breaking change because there is no good way to make accessing JS arrays from Java seamless anymore: You now have to use JSObject.get to access array elements (or alternatively create your own subtype that implements a get with the right return value (and out of bounds semantics)).
The remove of Java to JS coercion is only problematic if you relied on it being a typed array and/or accessed the underlying buffer, these things will not work anymore. Now you just get a proxy around the Java array. However, that proxy behaves almost like an array: the length property exists on it, indexed accesses (e.g. arrayProxy[0] = 12) work, and it implements the iterable protocol meaning things like the spread operator (...arrayProxy), or for loops just work from JavaScript.

Accessing elements of primitive arrays is subject to the JS->Java (for stores) and Java->JS (for loads) coercion rules:

  • boolean[] instances return JS boolean for loads and only accept JS boolean for stores (otherwise TypeError)
  • byte[], short[], char[], int[], float[], and double[] instances return JS number for loads and only accept JS number and bigint for stores (otherwise TypeError). Additionally, the numbers have to be in range for the target Java type (e.g. from 0 to 65536 for char[]) and must be integer values for the integer valued arrays (otherwise RangeError)
  • long[] behaves the same as the other number arrays but returns a JS bigint for loads.

See ArrayProxyTest for the full set of expected behavior of the new Java array proxies.


This also cleans up how we create proxies a bit, unifying some functionality between JS and WasmGC.

Due to the more powerful array proxies, this didn't break too many tests. Only the ones that relied on coercion to a Java array had to be removed.

The proxy handler always takes the hub instance as its only argument
setPrototypeOf is not recommended because it can mess up runtime
optimizations.
It used the 'in' operator to check if the "$as" method was already
defined, however because the _methods object has the prototype set to
the _methods object of the superclass proxy handler, the "$as" property
already exists (but not as an own property), but it points to a method
that captures the supertype proxy handler causing the wrong class
metadata to be loaded when used

This only caused issues with WasmGC because the JS backend implemented
_getSingleAbstractMethod (which is the method called on the proxy
handler) without using 'this'.
The proxy now simplifies this because it implements the iterable
protocol
This is now a uniform way to invoke toString on a JS value.

This introduces a slight breaking change in that the JSValue.toString
now has slightly different output (mostly no trailing .0 for integer
values) because it now shows the value how the JS engine would show it,
not how it would look like converted to Java
The Conversion class already contains a lot of similar utilities
@oracle-contributor-agreement oracle-contributor-agreement bot added the OCA Verified All contributors have signed the Oracle Contributor Agreement. label Dec 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

OCA Verified All contributors have signed the Oracle Contributor Agreement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants