Skip to content

Commit 9bec8c7

Browse files
committed
getLength
1 parent cdc73d0 commit 9bec8c7

File tree

9 files changed

+139
-9
lines changed

9 files changed

+139
-9
lines changed

c/interface.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,11 @@ MaybeAsync(JSValue *) QTS_GetProp(JSContext *ctx, JSValueConst *this_val, JSValu
548548
return jsvalue_to_heap(prop_val);
549549
}
550550

551+
MaybeAsync(JSValue *) QTS_GetPropNumber(JSContext *ctx, JSValueConst *this_val, int prop_name) {
552+
JSValue prop_val = JS_GetPropertyUint32(ctx, *this_val, (uint32_t)prop_name);
553+
return jsvalue_to_heap(prop_val);
554+
}
555+
551556
MaybeAsync(void) QTS_SetProp(JSContext *ctx, JSValueConst *this_val, JSValueConst *prop_name, JSValueConst *prop_value) {
552557
JSAtom prop_atom = JS_ValueToAtom(ctx, *prop_name);
553558
JSValue extra_prop_value = JS_DupValue(ctx, *prop_value);
@@ -819,6 +824,24 @@ OwnedHeapChar *QTS_Typeof(JSContext *ctx, JSValueConst *value) {
819824
return out;
820825
}
821826

827+
int QTS_GetLength(JSContext *ctx, uint32_t *out_len, JSValueConst *value) {
828+
JSValue len_val;
829+
int result;
830+
831+
if (!JS_IsObject(*value)) {
832+
return -1;
833+
}
834+
835+
len_val = JS_GetProperty(ctx, *value, JS_ATOM_length);
836+
if (JS_IsException(len_val)) {
837+
return -1;
838+
}
839+
840+
result = JS_ToUint32(ctx, out_len, len_val);
841+
JS_FreeValue(ctx, len_val);
842+
return result;
843+
}
844+
822845
JSValue *QTS_GetGlobalObject(JSContext *ctx) {
823846
return jsvalue_to_heap(JS_GetGlobalObject(ctx));
824847
}

packages/quickjs-emscripten-core/src/context.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {
1010
JSValuePointer,
1111
JSValuePointerPointer,
1212
EitherFFI,
13+
UInt32Pointer,
1314
} from "@jitl/quickjs-ffi-types"
1415
import { debugLog } from "./debug"
1516
import type { JSPromiseState } from "./deferred-promise"
@@ -19,6 +20,7 @@ import type { shouldInterruptAfterDeadline } from "./interrupt-helpers"
1920
import { QuickJSPromisePending, QuickJSUnwrapError } from "./errors"
2021
import type { Disposable } from "./lifetime"
2122
import { Lifetime, Scope, StaticLifetime, UsingDisposable, WeakLifetime } from "./lifetime"
23+
import type { HeapTypedArray } from "./memory"
2224
import { ModuleMemory } from "./memory"
2325
import type { ContextCallbacks, QuickJSModuleCallbacks } from "./module"
2426
import type {
@@ -174,6 +176,8 @@ export class QuickJSContext
174176
protected _global: QuickJSHandle | undefined = undefined
175177
/** @private */
176178
protected _BigInt: QuickJSHandle | undefined = undefined
179+
/** @private */
180+
protected uint32Out: HeapTypedArray<Uint32Array, UInt32Pointer>
177181

178182
/**
179183
* Use {@link QuickJSRuntime#newContext} or {@link QuickJSWASMModule#newContext}
@@ -203,6 +207,9 @@ export class QuickJSContext
203207
this.getString = this.getString.bind(this)
204208
this.getNumber = this.getNumber.bind(this)
205209
this.resolvePromise = this.resolvePromise.bind(this)
210+
this.uint32Out = this.memory.manage(
211+
this.memory.newTypedArray<Uint32Array, UInt32Pointer>(Uint32Array, 1),
212+
)
206213
}
207214

208215
// @implement Disposable ----------------------------------------------------
@@ -748,14 +755,32 @@ export class QuickJSContext
748755
*/
749756
getProp(handle: QuickJSHandle, key: QuickJSPropertyKey): QuickJSHandle {
750757
this.runtime.assertOwned(handle)
751-
const ptr = this.borrowPropertyKey(key).consume((quickJSKey) =>
752-
this.ffi.QTS_GetProp(this.ctx.value, handle.value, quickJSKey.value),
753-
)
758+
let ptr: JSValuePointer
759+
if (typeof key === "number" && key >= 0) {
760+
// Index access fast path
761+
ptr = this.ffi.QTS_GetPropNumber(this.ctx.value, handle.value, key)
762+
} else {
763+
ptr = this.borrowPropertyKey(key).consume((quickJSKey) =>
764+
this.ffi.QTS_GetProp(this.ctx.value, handle.value, quickJSKey.value),
765+
)
766+
}
754767
const result = this.memory.heapValueHandle(ptr)
755768

756769
return result
757770
}
758771

772+
/**
773+
* `handle.length`.
774+
*/
775+
getLength(handle: QuickJSHandle): number | undefined {
776+
this.runtime.assertOwned(handle)
777+
const status = this.ffi.QTS_GetLength(this.ctx.value, this.uint32Out.value.ptr, handle.value)
778+
if (status < 0) {
779+
return undefined
780+
}
781+
return this.uint32Out.value.typedArray[0]
782+
}
783+
759784
/**
760785
* `handle[key] = value`.
761786
* Set a property on a JSValue.

packages/quickjs-emscripten-core/src/memory.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ type HeapUint8Array = {
1717
numBytes: number
1818
}
1919

20+
// Add more types as needed.
21+
type TypedArray = Int32Array | Uint32Array
22+
23+
interface TypedArrayConstructor<T> {
24+
new (length: number): T
25+
new (array: ArrayLike<number> | ArrayBufferLike): T
26+
new (buffer: ArrayBufferLike, byteOffset?: number, length?: number): T
27+
BYTES_PER_ELEMENT: number
28+
}
29+
30+
export type HeapTypedArray<JS extends TypedArray, C extends number> = Lifetime<{
31+
typedArray: JS
32+
ptr: C
33+
}>
34+
2035
/**
2136
* @private
2237
*/
@@ -32,17 +47,25 @@ export class ModuleMemory {
3247
return new Lifetime(ptr, undefined, (ptr) => this.module._free(ptr))
3348
}
3449

35-
newMutablePointerArray<T extends JSContextPointerPointer | JSValuePointerPointer>(
50+
newTypedArray<JS extends TypedArray, C extends number>(
51+
kind: TypedArrayConstructor<JS>,
3652
length: number,
37-
): Lifetime<{ typedArray: Int32Array; ptr: T }> {
38-
const zeros = new Int32Array(new Array(length).fill(0))
53+
): HeapTypedArray<JS, C> {
54+
const zeros = new kind(new Array(length).fill(0))
3955
const numBytes = zeros.length * zeros.BYTES_PER_ELEMENT
40-
const ptr = this.module._malloc(numBytes) as T
41-
const typedArray = new Int32Array(this.module.HEAPU8.buffer, ptr, length)
56+
const ptr = this.module._malloc(numBytes) as C
57+
const typedArray = new kind(this.module.HEAPU8.buffer, ptr, length)
4258
typedArray.set(zeros)
4359
return new Lifetime({ typedArray, ptr }, undefined, (value) => this.module._free(value.ptr))
4460
}
4561

62+
// TODO: shouldn't this be Uint32 instead of Int32?
63+
newMutablePointerArray<T extends number>(
64+
length: number,
65+
): Lifetime<{ typedArray: Int32Array; ptr: T }> {
66+
return this.newTypedArray(Int32Array, length)
67+
}
68+
4669
newHeapCharPointer(string: string): Lifetime<{ ptr: OwnedHeapCharPointer; strlen: number }> {
4770
const strlen = this.module.lengthBytesUTF8(string)
4871
const dataBytes = strlen + 1

packages/quickjs-emscripten/src/quickjs.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,14 @@ function contextTests(getContext: GetTestContext, isDebug = false) {
359359
array.dispose()
360360
vals.forEach((val) => val.dispose())
361361
})
362+
363+
it("can access .length as a number", () => {
364+
const array = vm.newArray()
365+
assert.strictEqual(vm.getLength(array), 0)
366+
vm.newNumber(100).consume((n) => vm.setProp(array, 5, n))
367+
assert.strictEqual(vm.getLength(array), 6)
368+
array.dispose()
369+
})
362370
})
363371

364372
describe(".unwrapResult", () => {

packages/quickjs-emscripten/tsconfig.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
"outDir": ".output",
66
"rootDir": "src",
77
"paths": {
8-
"#variants": ["./src/variants.ts", "./src/variants.js"]
8+
"#variants": ["./src/variants.ts", "./src/variants.js"],
9+
"quickjs-emscripten-core": ["../quickjs-emscripten-core/src"],
10+
"quickjs-emscripten-core/*": ["../quickjs-emscripten-core/src/*"]
911
}
1012
}
1113
}

packages/quickjs-ffi-types/src/ffi-async.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
OwnedHeapCharPointer,
1717
JSBorrowedCharPointer,
1818
JSVoidPointer,
19+
UInt32Pointer,
1920
EvalFlags,
2021
IntrinsicsFlags,
2122
EvalDetectModule,
@@ -119,6 +120,16 @@ export interface QuickJSAsyncFFI {
119120
this_val: JSValuePointer | JSValueConstPointer,
120121
prop_name: JSValuePointer | JSValueConstPointer,
121122
) => JSValuePointer | Promise<JSValuePointer>
123+
QTS_GetPropNumber: (
124+
ctx: JSContextPointer,
125+
this_val: JSValuePointer | JSValueConstPointer,
126+
prop_name: number,
127+
) => JSValuePointer
128+
QTS_GetPropNumber_MaybeAsync: (
129+
ctx: JSContextPointer,
130+
this_val: JSValuePointer | JSValueConstPointer,
131+
prop_name: number,
132+
) => JSValuePointer | Promise<JSValuePointer>
122133
QTS_SetProp: (
123134
ctx: JSContextPointer,
124135
this_val: JSValuePointer | JSValueConstPointer,
@@ -189,6 +200,11 @@ export interface QuickJSAsyncFFI {
189200
ctx: JSContextPointer,
190201
value: JSValuePointer | JSValueConstPointer,
191202
) => OwnedHeapCharPointer
203+
QTS_GetLength: (
204+
ctx: JSContextPointer,
205+
out_len: uint32_tPointer,
206+
value: JSValuePointer | JSValueConstPointer,
207+
) => number
192208
QTS_GetGlobalObject: (ctx: JSContextPointer) => JSValuePointer
193209
QTS_NewPromiseCapability: (
194210
ctx: JSContextPointer,

packages/quickjs-ffi-types/src/ffi-types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ export type JSBorrowedCharPointer = Pointer<"js const char">
9393
*/
9494
export type JSVoidPointer = Pointer<any>
9595

96+
export type UInt32Pointer = Pointer<"uint32_t">
97+
9698
/**
9799
* @private
98100
*/

packages/quickjs-ffi-types/src/ffi.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
OwnedHeapCharPointer,
1717
JSBorrowedCharPointer,
1818
JSVoidPointer,
19+
UInt32Pointer,
1920
EvalFlags,
2021
IntrinsicsFlags,
2122
EvalDetectModule,
@@ -104,6 +105,11 @@ export interface QuickJSFFI {
104105
this_val: JSValuePointer | JSValueConstPointer,
105106
prop_name: JSValuePointer | JSValueConstPointer,
106107
) => JSValuePointer
108+
QTS_GetPropNumber: (
109+
ctx: JSContextPointer,
110+
this_val: JSValuePointer | JSValueConstPointer,
111+
prop_name: number,
112+
) => JSValuePointer
107113
QTS_SetProp: (
108114
ctx: JSContextPointer,
109115
this_val: JSValuePointer | JSValueConstPointer,
@@ -149,6 +155,11 @@ export interface QuickJSFFI {
149155
ctx: JSContextPointer,
150156
value: JSValuePointer | JSValueConstPointer,
151157
) => OwnedHeapCharPointer
158+
QTS_GetLength: (
159+
ctx: JSContextPointer,
160+
out_len: uint32_tPointer,
161+
value: JSValuePointer | JSValueConstPointer,
162+
) => number
152163
QTS_GetGlobalObject: (ctx: JSContextPointer) => JSValuePointer
153164
QTS_NewPromiseCapability: (
154165
ctx: JSContextPointer,

packages/variant-quickjs-ng-wasmfile-debug-asyncify/src/ffi.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
OwnedHeapCharPointer,
1717
JSBorrowedCharPointer,
1818
JSVoidPointer,
19+
UInt32Pointer,
1920
EvalFlags,
2021
IntrinsicsFlags,
2122
EvalDetectModule,
@@ -245,6 +246,25 @@ export class QuickJSAsyncFFI {
245246
{ async: true },
246247
)
247248

249+
QTS_GetPropNumber: (
250+
ctx: JSContextPointer,
251+
this_val: JSValuePointer | JSValueConstPointer,
252+
prop_name: number,
253+
) => JSValuePointer = assertSync(
254+
this.module.cwrap("QTS_GetPropNumber", "number", ["number", "number", "number"]),
255+
)
256+
257+
QTS_GetPropNumber_MaybeAsync: (
258+
ctx: JSContextPointer,
259+
this_val: JSValuePointer | JSValueConstPointer,
260+
prop_name: number,
261+
) => JSValuePointer | Promise<JSValuePointer> = this.module.cwrap(
262+
"QTS_GetPropNumber",
263+
"number",
264+
["number", "number", "number"],
265+
{ async: true },
266+
)
267+
248268
QTS_SetProp: (
249269
ctx: JSContextPointer,
250270
this_val: JSValuePointer | JSValueConstPointer,

0 commit comments

Comments
 (0)