JavaScriptKit icon indicating copy to clipboard operation
JavaScriptKit copied to clipboard

Occasional JSClosure has been already released by Swift side

Open vkhougaz-vifive opened this issue 3 years ago • 3 comments

During development of an animated webassembly feature a function that takes and returns a large typed array started failing occasionally with an error similar to:

The JSClosure has been already released by Swift side. The closure is created at ...

This occurs approximately 50% of frames

I've tried to create a minimal reproduction here, details in the README:

https://github.com/vkhougaz-vifive/swiftwasm-bug

import Foundation

import JavaScriptKit

func reverseArray(bytes: [Float32]) -> [Float32] {
    return [Float32](bytes.reversed())
}

let jsClosure = JSClosure { (input: [JSValue]) in
    let bytes: [Float32] = try! JSValueDecoder().decode(from: input[0])

    return reverseArray(bytes: bytes).jsValue
}

@_cdecl("main")
func main(_ i: Int32, _ j: Int32) -> Int32 {
    JSObject.global.reverseFloat32Array = .object(jsClosure)

    return 0
}
window.onload = async () => {
  const output = document.getElementById("output")!;

  function* generator() {
    for (let step = 0; step < 10000; step++) {
      yield Math.random();
    }
  }

  await loadWasm(bugWasm);

  function animate() {
    try {
      const bytes = Float32Array.from(generator());

      const reversed = reverseFloat32Array(bytes);

      output.innerHTML = reversed.join("\n");
    } catch (e) {
      console.error(e);
    }
    requestAnimationFrame(animate);
  }
  animate();
};

The hacky patch included there is... concerning, pointing towards either a dramatic misunderstanding of how swift works or a crazy low level memory issue.

/// Returns true if the host function has been already released, otherwise false.
@_cdecl("_call_host_function_impl")
func _call_host_function_impl(
    _ hostFuncRef: JavaScriptHostFuncRef,
    _ argv: UnsafePointer<RawJSValue>, _ argc: Int32,
    _ callbackFuncRef: JavaScriptObjectRef
) -> Bool {
    // TODO: This is some sort of horrible hack due to some sort of horrible wasm thing
    // Otherwise the sharedClone SOMETIMES fails
    let sharedClone = Dictionary(uniqueKeysWithValues: zip(JSClosure.sharedClosures.keys, JSClosure.sharedClosures.values))

    guard let (_, hostFunc) = sharedClone[hostFuncRef] else {
        return true
    }
    let arguments = UnsafeBufferPointer(start: argv, count: Int(argc)).map(\.jsValue)
    let result = hostFunc(arguments)
    let callbackFuncRef = JSFunction(id: callbackFuncRef)
    _ = callbackFuncRef(result)
    return false
}

vkhougaz-vifive avatar Jul 16 '22 00:07 vkhougaz-vifive

Thank you for detail report and repro. It looks like our runtime has something wrong. Let me check it

kateinoigakukun avatar Jul 17 '22 10:07 kateinoigakukun

I'm still running into this issue indexing a dict using a string passed in via JSValue

Is it possible that strings in wasm allocated (heap?) memory cannot be used to index dictionaries, but strings on the stack can? hmn.

vkhougaz-vifive avatar Jul 27 '22 00:07 vkhougaz-vifive

OK, the root cause is that you are using command line model instead of reactor model. JavaScriptKit only supports reactor model. I've sent you a patch https://github.com/vkhougaz-vifive/swiftwasm-bug/pull/1

We will add more friendly diagnostics to avoid this situation. See also carton code: https://github.com/swiftwasm/carton/blob/b8cac7dc8e9c96e95406bfc5312768a2e659bd81/Sources/SwiftToolchain/Toolchain.swift#L368-L373

kateinoigakukun avatar Jul 31 '22 13:07 kateinoigakukun

Okay, I don't know why I was having so many issues but I fixed the compilation command and it's working well. Thanks!

swift build --triple wasm32-unknown-wasi -c $MODE -Xswiftc -Xclang-linker -Xswiftc -mexec-model=reactor -Xlinker --export=main

vkhougaz-vifive avatar Aug 18 '22 17:08 vkhougaz-vifive