Skip to content

Commit 2486486

Browse files
authored
Merge pull request #8 from andrewmd5/next
next
2 parents 38b4804 + 1d7757a commit 2486486

75 files changed

Lines changed: 2782 additions & 35 deletions

Some content is hidden

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

bridge/CMakeLists.txt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ set(CMAKE_STRIP "${WASI_SDK_PATH}/bin/llvm-strip")
2727

2828
# Set WASI sysroot and target
2929
set(CMAKE_SYSROOT "${WASI_SDK_PATH}/share/wasi-sysroot")
30-
set(CMAKE_C_COMPILER_TARGET "wasm32-wasi-threads")
31-
set(CMAKE_CXX_COMPILER_TARGET "wasm32-wasi-threads")
30+
set(CMAKE_C_COMPILER_TARGET "wasm32-wasi")
31+
set(CMAKE_CXX_COMPILER_TARGET "wasm32-wasi")
3232

3333
set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY")
3434
set(CMAKE_C_COMPILER_WORKS "1")
@@ -90,7 +90,7 @@ option(DISABLE_NANBOX "Disable nanbox" OFF)
9090
option(ENABLE_CODECACHE "Enable code cache" OFF)
9191
option(CACHE_PROFILE "Enable cache profile" OFF)
9292
option(ENABLE_MEM "Enable memory detection" OFF)
93-
option(ENABLE_ATOMICS "Enable Atomics" ON)
93+
option(ENABLE_ATOMICS "Enable Atomics" OFF)
9494
option(FORCE_GC "Enable force gc" OFF)
9595
option(ENABLE_ASAN "Enable address sanitizer" OFF)
9696
option(ENABLE_BIGNUM "Enable bignum" OFF)
@@ -110,6 +110,7 @@ add_compile_options(
110110
-D_WASI_EMULATED_MMAN
111111
-D_WASI_EMULATED_SIGNAL
112112
-D_WASI_EMULATED_PROCESS_CLOCKS
113+
-DWASI_STACK_SIZE=${WASM_STACK_SIZE}
113114
)
114115

115116
# Simplify for WASI
@@ -122,7 +123,7 @@ set(CMAKE_COMMON_FLAGS
122123
"${OPTIMIZATION_FLAGS} -fPIC -ffunction-sections -fdata-sections \
123124
-fno-short-enums -fno-strict-aliasing -Wall -Wextra -Wno-unused-parameter \
124125
-Wno-unused-function -faddrsig -Wno-c99-designator -Wno-unknown-warning-option \
125-
-Wno-sign-compare -Wno-unused-but-set-variable -pthread -matomics -msimd128 -mmultivalue -mmutable-globals -mtail-call -msign-ext -mbulk-memory -mnontrapping-fptoint -mextended-const")
126+
-Wno-sign-compare -Wno-unused-but-set-variable -msimd128 -mmultivalue -mmutable-globals -mtail-call -msign-ext -mbulk-memory -mnontrapping-fptoint -mextended-const")
126127

127128
if(ENABLE_ASAN)
128129
add_definitions(-DHAKO_SANITIZE_LEAK)
@@ -281,7 +282,7 @@ set_target_properties(quickjs PROPERTIES
281282

282283
# Add WASI-specific link options to the QuickJS library
283284
target_link_options(quickjs PRIVATE
284-
"-Wl,--allow-undefined -Wl,wasi-emulated-signal -Wl,wasi-emulated-process-clocks -Wl,--shared-memory")
285+
"-Wl,--allow-undefined -Wl,wasi-emulated-signal -Wl,wasi-emulated-process-clocks")
285286

286287
# Build the hako WASM module
287288
set(hako_source
@@ -311,7 +312,6 @@ target_include_directories(hako_reactor PRIVATE
311312
# WASM-specific link options
312313
target_link_options(hako_reactor PRIVATE
313314
-mexec-model=reactor
314-
-Wl,--import-memory,--export-memory
315315
-Wl,--no-entry
316316
-Wl,--export=malloc
317317
-Wl,--export=free

bridge/hako.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1586,12 +1586,14 @@ HAKO_TypedArrayType WASM_EXPORT(HAKO_GetTypedArrayType)(LEPUSContext *ctx,
15861586
return HAKO_TYPED_UINT32_ARRAY;
15871587
case LEPUS_TYPED_INT32_ARRAY:
15881588
return HAKO_TYPED_INT32_ARRAY;
1589+
case LEPUS_TYPED_FLOAT16_ARRAY:
1590+
return HAKO_TYPED_FLOAT16_ARRAY;
15891591
case LEPUS_TYPED_FLOAT32_ARRAY:
15901592
return HAKO_TYPED_FLOAT32_ARRAY;
15911593
case LEPUS_TYPED_FLOAT64_ARRAY:
15921594
return HAKO_TYPED_FLOAT64_ARRAY;
15931595
default:
1594-
return HAKO_TYPED_UNKNOWN;
1596+
return -1;
15951597
}
15961598
}
15971599

bridge/hako.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,18 @@ extern "C"
4848

4949
typedef enum
5050
{
51-
HAKO_TYPED_UNKNOWN = 0,
5251
HAKO_TYPED_UINT8_ARRAY = 1,
5352
HAKO_TYPED_UINT8C_ARRAY = 2,
5453
HAKO_TYPED_INT8_ARRAY = 3,
5554
HAKO_TYPED_UINT16_ARRAY = 4,
5655
HAKO_TYPED_INT16_ARRAY = 5,
5756
HAKO_TYPED_UINT32_ARRAY = 6,
5857
HAKO_TYPED_INT32_ARRAY = 7,
59-
HAKO_TYPED_FLOAT32_ARRAY = 8,
60-
HAKO_TYPED_FLOAT64_ARRAY = 9
58+
HAKO_TYPED_BIG_INT64_ARRAY = 8,
59+
HAKO_TYPED_BIG_UINT64_ARRAY = 9,
60+
HAKO_TYPED_FLOAT16_ARRAY = 10,
61+
HAKO_TYPED_FLOAT32_ARRAY = 11,
62+
HAKO_TYPED_FLOAT64_ARRAY = 12
6163
} HAKO_TypedArrayType;
6264

6365
typedef enum IsEqualOp

embedders/ts/src/etc/ffi.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/**
2-
* Generated on: 2025-04-14 08:27:22
2+
* Generated on: 2025-06-24 17:01:53
33
* Source file: hako.h
4-
* Git commit: 1ac71aadb54d23306f1bee8b1b982128dfc4cf4f
5-
* Git branch: main
4+
* Git commit: 7a9d6e411817d8012facedac0694be2cee4cb846
5+
* Git branch: next
66
* Git author: andrew <1297077+andrewmd5@users.noreply.github.com>
77
* Git remote: https://github.com/andrewmd5/hako.git
88
*/

embedders/ts/src/etc/types.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -585,17 +585,14 @@ export function evalOptionsToFlags(
585585
/**
586586
* JavaScript Promise states.
587587
*/
588-
export enum PromiseState {
588+
export type PromiseState =
589589
/** Promise has not been resolved or rejected yet */
590-
Pending = 0,
591-
590+
| "pending"
592591
/** Promise has been resolved with a value */
593-
Fulfilled = 1,
594-
592+
| "fulfilled"
595593
/** Promise has been rejected with a reason */
596-
Rejected = 2,
597-
}
598-
594+
| "rejected";
595+
599596
//=============================================================================
600597
// Equality Operations
601598
//=============================================================================
@@ -1031,5 +1028,8 @@ export type TypedArrayType =
10311028
| "Int16Array"
10321029
| "Uint32Array"
10331030
| "Int32Array"
1031+
| "BigUint64Array"
1032+
| "BigInt64Array"
1033+
| "Float16Array"
10341034
| "Float32Array"
10351035
| "Float64Array";

embedders/ts/src/index.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,6 @@ export interface HakoOptions<TOptions, TResponse> {
9191
initial?: number;
9292
/** Maximum memory size in bytes */
9393
maximum?: number;
94-
/** Whether to use shared memory */
95-
shared?: boolean;
9694
/** Bring Your Own Memory - use an existing WebAssembly memory instance */
9795
byom?: WebAssembly.Memory;
9896
};
@@ -129,7 +127,7 @@ export async function createHakoRuntime<TOptions, TResponse>(
129127
const memConfig = options.wasm?.memory || {};
130128
const initialMemory = memConfig.initial || defaultInitialMemory;
131129
const maximumMemory = memConfig.maximum || defaultMaximumMemory;
132-
const sharedMemory = memConfig.shared !== undefined ? memConfig.shared : true;
130+
133131

134132
// Use BYOM (Bring Your Own Memory) or create a new one
135133
let wasmMemory: WebAssembly.Memory;
@@ -142,7 +140,7 @@ export async function createHakoRuntime<TOptions, TResponse>(
142140
wasmMemory = new WebAssembly.Memory({
143141
initial: initialPages,
144142
maximum: maximumPages,
145-
shared: sharedMemory,
143+
shared: false,
146144
});
147145
}
148146

embedders/ts/src/vm/context.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,17 +368,43 @@ export class VMContext implements Disposable {
368368
`Expected a Promise-like value, received ${promiseLikeHandle.type}`
369369
);
370370
}
371+
371372
using vmResolveResult = Scope.withScope((scope) => {
372373
const global = this.getGlobalObject();
373374
const vmPromise = scope.manage(global.getProperty("Promise"));
374375
// biome-ignore lint/style/noNonNullAssertion: <explanation>
375376
const vmPromiseResolve = scope.manage(vmPromise?.getProperty("resolve"))!;
376377
return this.callFunction(vmPromiseResolve, vmPromise, promiseLikeHandle);
377378
});
379+
378380
if (vmResolveResult.error) {
379381
return Promise.resolve(vmResolveResult);
380382
}
381383

384+
const resolvedPromise = vmResolveResult.value;
385+
386+
// Check if the promise is already settled
387+
const state = resolvedPromise.getPromiseState();
388+
389+
if (state === "fulfilled") {
390+
const result = resolvedPromise.getPromiseResult();
391+
if (result) {
392+
return Promise.resolve(this.success(result));
393+
}
394+
// This shouldn't happen for fulfilled promises, but handle gracefully
395+
return Promise.resolve(this.success(this.newValue(undefined)));
396+
}
397+
398+
if (state === "rejected") {
399+
const error = resolvedPromise.getPromiseResult();
400+
if (error) {
401+
return Promise.resolve(this.fail(error));
402+
}
403+
// This shouldn't happen for rejected promises, but handle gracefully
404+
return Promise.resolve(this.fail(this.newValue(undefined)));
405+
}
406+
407+
// Promise is pending, set up async handlers
382408
return new Promise<VMContextResult<VMValue>>((resolve) => {
383409
Scope.withScope((scope) => {
384410
const resolveHandle = scope.manage(
@@ -393,7 +419,7 @@ export class VMContext implements Disposable {
393419
})
394420
);
395421

396-
const promiseHandle = scope.manage(vmResolveResult.value);
422+
const promiseHandle = scope.manage(resolvedPromise);
397423
// biome-ignore lint/style/noNonNullAssertion: <explanation>
398424
const promiseThenHandle = scope.manage(
399425
promiseHandle.getProperty("then")

embedders/ts/src/vm/value.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
type PropertyDescriptor,
77
PropertyEnumFlags,
88
EqualOp,
9-
PromiseState,
9+
type PromiseState,
1010
IsEqualOp,
1111
LEPUS_BOOLToBoolean,
1212
type JSType,
@@ -358,9 +358,15 @@ export class VMValue implements Disposable {
358358
return "Uint32Array";
359359
case 7:
360360
return "Int32Array";
361-
case 8:
362-
return "Float32Array";
361+
case 8:
362+
return "BigInt64Array";
363363
case 9:
364+
return "BigUint64Array";
365+
case 10:
366+
return "Float16Array";
367+
case 11:
368+
return "Float32Array";
369+
case 12:
364370
return "Float64Array";
365371
default:
366372
return "Unknown";
@@ -769,15 +775,25 @@ export class VMValue implements Disposable {
769775
* @throws Error if the value is not a promise
770776
* @throws {PrimJSUseAfterFree} If the value has been disposed
771777
*/
772-
getPromiseState(): PromiseState | null {
778+
getPromiseState(): PromiseState | undefined {
773779
this.assertAlive();
774780
if (!this.isPromise()) {
775781
throw new Error("Value is not a promise");
776782
}
777-
return this.context.container.exports.HAKO_PromiseState(
783+
switch (this.context.container.exports.HAKO_PromiseState(
778784
this.context.pointer,
779785
this.handle
780-
);
786+
)) {
787+
case 0:
788+
return "pending";
789+
case 1:
790+
return "fulfilled";
791+
case 2:
792+
return "rejected";
793+
default:
794+
return undefined;
795+
796+
}
781797
}
782798

783799
/**
@@ -794,7 +810,7 @@ export class VMValue implements Disposable {
794810
}
795811

796812
const state = this.getPromiseState();
797-
if (state !== PromiseState.Fulfilled && state !== PromiseState.Rejected) {
813+
if (state !== "fulfilled" && state !== "rejected") {
798814
return undefined;
799815
}
800816

embedders/ts/tests/context.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,42 @@ fibonacci(100);
601601
});
602602

603603
describe("Promise handling", () => {
604+
605+
it("should resolve already-settled async function promises without deadlocking", async () => {
606+
// This test reproduces the deadlock issue from the GitHub issue
607+
const code = `export const run = async (name) => { return "Hello" + name };`;
608+
609+
using result = context.evalCode(code, { type: "module" });
610+
expect(result.error).toBeUndefined();
611+
612+
using mod = result.unwrap();
613+
using runFunction = mod.getProperty("run");
614+
615+
// Call the async function - this creates an already-settled promise
616+
using arg = context.newValue("Test");
617+
console.log("Calling runFunction with arg:", arg.asString());
618+
using callResult = context.callFunction(runFunction, null, arg);
619+
using promiseHandle = callResult.unwrap();
620+
console.log("Promise handle created:");
621+
622+
// Verify it's a promise and already settled
623+
expect(promiseHandle.isPromise()).toBe(true);
624+
625+
626+
// Before the fix: this would deadlock because the promise is already settled
627+
// After the fix: this should resolve without blocking
628+
const startTime = Date.now();
629+
console.log("Resolving promise handle...");
630+
using resolvedResult = await context.resolvePromise(promiseHandle);
631+
const endTime = Date.now();
632+
633+
// Should resolve quickly (within reasonable time, not hang indefinitely)
634+
expect(endTime - startTime).toBeLessThan(1000); // Should be much faster than 1 second
635+
636+
using resolvedHandle = resolvedResult.unwrap();
637+
expect(resolvedHandle.asString()).toBe("HelloTest");
638+
});
639+
604640
it("should handle promise resolution", async () => {
605641
const fakeFileSystem = new Map([["example.txt", "Example file content"]]);
606642
using readFileHandle = context.newFunction("readFile", (pathHandle) => {

0 commit comments

Comments
 (0)