Skip to content

Commit 01cbf9c

Browse files
darvldsgammon
andauthored
feat: native streams (#1365)
* feat(graalvm): completable js promises Signed-off-by: Dario Valdespino <[email protected]> * feat(graalvm): implement web streams api Signed-off-by: Sam Gammon <[email protected]> * feat(graalvm): webstreams standard implementation Signed-off-by: Dario Valdespino <[email protected]> * feat(graalvm): async stream iteration Signed-off-by: Dario Valdespino <[email protected]> * feat(graalvm): intrinsics installation Signed-off-by: Dario Valdespino <[email protected]> * test(graalvm): webstream tests and polyglot proxies Signed-off-by: Dario Valdespino <[email protected]> * fix(graalvm): correct name for queuing strategies Signed-off-by: Dario Valdespino <[email protected]> * feat(graalvm): disable webstream polyfills Signed-off-by: Dario Valdespino <[email protected]> * chore(graalvm): update api pins Signed-off-by: Dario Valdespino <[email protected]> --------- Signed-off-by: Dario Valdespino <[email protected]> Signed-off-by: Sam Gammon <[email protected]> Co-authored-by: Sam Gammon <[email protected]>
1 parent a30547a commit 01cbf9c

File tree

54 files changed

+6122
-303
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+6122
-303
lines changed

packages/engine/src/main/kotlin/elide/runtime/plugins/AbstractLanguagePlugin.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024 Elide Technologies, Inc.
2+
* Copyright (c) 2024-2025 Elide Technologies, Inc.
33
*
44
* Licensed under the MIT license (the "License"); you may not use this file except in compliance
55
* with the License. You may obtain a copy of the License at
@@ -373,7 +373,7 @@ public fun defaultPolyglotContext(): Context = defaultContext
373373
context.evaluate(
374374
this,
375375
script.bufferedReader().use { it.readText() },
376-
name = source.path.split("/").last(),
376+
name = source.path.substringAfterLast('/'),
377377
internals = true,
378378
cached = true,
379379
)
Binary file not shown.

packages/graalvm-js/src/main/resources/META-INF/elide/embedded/runtime/js/polyfills.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/graalvm/api/graalvm.api

Lines changed: 445 additions & 15 deletions
Large diffs are not rendered by default.
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/*
2+
* Copyright (c) 2024-2025 Elide Technologies, Inc.
3+
*
4+
* Licensed under the MIT license (the "License"); you may not use this file except in compliance
5+
* with the License. You may obtain a copy of the License at
6+
*
7+
* https://opensource.org/license/mit/
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11+
* License for the specific language governing permissions and limitations under the License.
12+
*/
13+
package elide.runtime.gvm.internals.intrinsics.js
14+
15+
import org.graalvm.polyglot.Context
16+
import org.graalvm.polyglot.Value
17+
import java.nio.ByteBuffer
18+
import elide.runtime.core.DelicateElideApi
19+
import elide.runtime.gvm.internals.intrinsics.js.ArrayBufferViews.getBackingBuffer
20+
import elide.runtime.gvm.internals.intrinsics.js.ArrayBufferViews.getLength
21+
import elide.runtime.gvm.internals.intrinsics.js.ArrayBufferViews.getOffset
22+
import elide.runtime.gvm.internals.intrinsics.js.ArrayBufferViews.getViewType
23+
import elide.runtime.gvm.internals.intrinsics.js.ArrayBufferViews.newView
24+
import elide.runtime.intrinsics.js.err.TypeError
25+
import elide.runtime.plugins.js.JavaScript
26+
27+
/**
28+
* Alias for a guest [Value] representing a JavaScript `ArrayBuffer`. Values using this alias _must_ be checked before
29+
* being returned from a function or property, to ensure they have [buffer elements][Value.hasBufferElements].
30+
*
31+
* @see ArrayBufferViews.getBackingBuffer
32+
*/
33+
internal typealias ArrayBufferValue = Value
34+
35+
/**
36+
* Represents a type of array buffer view (TypedArray or DataView), identified by the name of its corresponding
37+
* metaobject in a guest context.
38+
*/
39+
internal enum class ArrayBufferViewType {
40+
Int8Array,
41+
Uint8Array,
42+
Uint8ClampedArray,
43+
Int16Array,
44+
Uint16Array,
45+
Int32Array,
46+
Uint32Array,
47+
Float16Array,
48+
Float32Array,
49+
Float64Array,
50+
Int64Array,
51+
Uint64Array,
52+
DataView,
53+
}
54+
55+
/**
56+
* A collection of interoperability helpers for JavaScript array buffer views (i.e. typed arrays and data views).
57+
*
58+
* Use [newView] as a simple one-in-all constructor for buffer views, and [getViewType], [getBackingBuffer],
59+
* [getOffset], and [getLength] to inspect values for known properties.
60+
*
61+
* Note that most of the functions require an active (entered) context in the calling thread, in order to locate the
62+
* necessary metaobjects that act as constructors and contain static methods.
63+
*/
64+
@OptIn(DelicateElideApi::class)
65+
internal object ArrayBufferViews {
66+
private const val ARRAY_ELEMENT_SIZE_MEMBER = "BYTES_PER_ELEMENT"
67+
private const val BYTE_LENGTH_MEMBER = "byteLength"
68+
private const val BYTE_OFFSET_MEMBER = "byteOffset"
69+
private const val BACKING_BUFFER_MEMBER = "buffer"
70+
71+
/**
72+
* Retrieve the metaobject (constructor) for a given array view [type] from the JavaScript bindings of a [context],
73+
* throwing an exception if it cannot be found. The returned value is guaranteed to be a metaobject.
74+
*/
75+
private fun getViewConstructor(type: ArrayBufferViewType, context: Context): Value {
76+
val constructor = context.getBindings(JavaScript.languageId).getMember(type.name)
77+
?: throw TypeError.create("Unknown array buffer view type '$type'")
78+
79+
check(constructor.isMetaObject) { "Expected view constructor to be a metaobject" }
80+
return constructor
81+
}
82+
83+
/**
84+
* Construct a new array buffer view of the given [type] and [length], wrapping [buffer] starting at [offset]. A
85+
* guest metaobject resolved from the specified [context] will be used as constructor for the instance.
86+
*
87+
* The NIO [buffer] will be converted to an [ArrayBufferValue] before being wrapped.
88+
*/
89+
internal fun newView(
90+
type: ArrayBufferViewType,
91+
buffer: ByteBuffer,
92+
offset: Long = 0,
93+
length: Long = buffer.limit().toLong(),
94+
context: Context = Context.getCurrent()
95+
): Value {
96+
return newView(type, Value.asValue(buffer), offset, length, context)
97+
}
98+
99+
/**
100+
* Construct a new array buffer view of the given [type] and [length], wrapping [buffer] starting at [offset]. A
101+
* guest metaobject resolved from the specified [context] will be used as constructor for the instance.
102+
*/
103+
internal fun newView(
104+
type: ArrayBufferViewType,
105+
buffer: ArrayBufferValue,
106+
offset: Long,
107+
length: Long,
108+
context: Context = Context.getCurrent()
109+
): Value {
110+
val constructor = getViewConstructor(type, context)
111+
return constructor.newInstance(buffer, offset, length)
112+
}
113+
114+
/**
115+
* Given an array buffer [view], return the size in bytes of the view's elements (e.g. `8` for an `Int8Array`).
116+
*
117+
* A default value of `1` will be returned if the [view] has no static member indicating the element size, to support
118+
* the use of `DataView` alongside `TypedArray` instances.
119+
*/
120+
internal fun getElementSize(view: Value): Int {
121+
return runCatching { view.getMember(ARRAY_ELEMENT_SIZE_MEMBER).asInt() }.getOrDefault(1)
122+
}
123+
124+
/**
125+
* Given an arbitrary array buffer [view], resolve its [type][ArrayBufferViewType] using the name of its metaobject,
126+
* or throw an exception if it is not found.
127+
*/
128+
internal fun getViewType(view: Value): ArrayBufferViewType {
129+
return getViewTypeOrNull(view) ?: throw TypeError.create("Value $view is not a valid buffer view")
130+
}
131+
132+
/**
133+
* Given an arbitrary array buffer [view], resolve its [type][ArrayBufferViewType] using the name of its metaobject,
134+
* or return `null` if it is not known.
135+
*/
136+
internal fun getViewTypeOrNull(view: Value): ArrayBufferViewType? {
137+
val metaName = view.metaObject.metaSimpleName
138+
return runCatching { ArrayBufferViewType.valueOf(metaName) }.getOrNull()
139+
}
140+
141+
/**
142+
* Given an array buffer [view], return its backing array buffer. The returned value is guaranteed to be a
143+
* [buffer][Value.hasBufferElements] for guest interop purposes.
144+
*
145+
* If the [view] value has no backing buffer, or the buffer is invalid, an exception will be thrown.
146+
*/
147+
internal fun getBackingBuffer(view: Value): ArrayBufferValue {
148+
if (!view.hasMember(BACKING_BUFFER_MEMBER)) throw TypeError.create("Value $view is not a buffer view")
149+
val value = view.getMember(BACKING_BUFFER_MEMBER)
150+
151+
if (!value.hasBufferElements()) throw TypeError.create("Invalid backing buffer: $value has no buffer elements")
152+
return value
153+
}
154+
155+
/**
156+
* Given an array buffer [view], return its `byteOffset` value, or throw an exception if it cannot be resolved or is
157+
* not valid.
158+
*/
159+
internal fun getOffset(view: Value): Long {
160+
if (!view.hasMember(BYTE_OFFSET_MEMBER)) throw TypeError.create("Value $view is not a buffer view")
161+
val value = view.getMember(BYTE_OFFSET_MEMBER)
162+
163+
if (!value.isNumber || !value.fitsInLong()) throw TypeError.create("Invalid byte offset value: $value")
164+
return value.asLong()
165+
}
166+
167+
/**
168+
* Given an array buffer [view], return its `byteLength` value, or throw an exception if it cannot be resolved or is
169+
* not valid.
170+
*/
171+
internal fun getLength(view: Value): Long {
172+
if (!view.hasMember(BYTE_LENGTH_MEMBER)) throw TypeError.create("Value $view is not a buffer view")
173+
val value = view.getMember(BYTE_LENGTH_MEMBER)
174+
175+
if (!value.isNumber || !value.fitsInLong()) throw TypeError.create("Invalid byte length value: $value")
176+
return value.asLong()
177+
}
178+
}

0 commit comments

Comments
 (0)