Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug in Embedded/ARC/WebAssembly combination? #73768

Closed
turbolent opened this issue May 20, 2024 · 5 comments
Closed

Bug in Embedded/ARC/WebAssembly combination? #73768

turbolent opened this issue May 20, 2024 · 5 comments
Assignees
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. embedded Embedded Swift triage needed This issue needs more specific labels

Comments

@turbolent
Copy link

turbolent commented May 20, 2024

Description

I'm seeing some strange behaviour when using classes/ARC in WebAssembly/Embedded (might be unrelated):
In some cases calls to deinit seem to be missing, and in some cases, deinit seems to be called before a last use of the instance.

Reproduction

Missing deinit

@_extern(wasm, module: "foo", name: "a")
@_extern(c)
func foo_a()

@_extern(wasm, module: "foo", name: "b")
@_extern(c)
func foo_b()

@_extern(wasm, module: "foo", name: "c")
@_extern(c)
func foo_c()

class Foo {

    init() {
        foo_a()
    }

    func foo() {
        foo_b()
    }

    deinit {
        foo_c()
    }
}


@_expose(wasm, "test1")
@_cdecl("test1")
func test1() {
    let foo = Foo()
    foo.foo()
}

gets compiled to (pseudo-code produced by wasm-decompile):

export function test1() {
  foo_a();
  foo_b();
}

Why is the call to foo_c in deinit missing?

Deinit before last use

@_extern(wasm, module: "bar", name: "new")
@_extern(c)
func bar_new() -> UnsafeMutablePointer<Int>

@_extern(wasm, module: "bar", name: "use")
@_extern(c)
func bar_use(_ ptr: UnsafeMutablePointer<Int>)

@_extern(wasm, module: "bar", name: "free")
@_extern(c)
func bar_free(_ ptr: UnsafeMutablePointer<Int>)

class Bar {

    let ptr: UnsafeMutablePointer<Int>

    init() {
        ptr = bar_new()
    }

    func use() {
        bar_use(ptr)
    }

    deinit {
        bar_free(ptr)
    }
}


@_expose(wasm, "test2")
@_cdecl("test2")
func test2() {
    let bar = Bar()
    bar.use()
}

gets compiled to:

export function test2() {
  var a:int = bar_new();
  bar_free(a);
  bar_use(a);
}

Note the order: Why is bar_free called before bar_use?

Is this expected? If so, how so? If not, are these bugs? They might not be related to WebAssembly or Embedded at all, it is just the combination I'm trying out and can easily reproduce locally.

Expected behavior

I had assumed that the results are:

export function test1() {
  foo_a();
  foo_b();
  foo_c();
}

and

export function test2() {
  var a:int = bar_new();
  bar_use(a);
  bar_free(a);
}

Environment

Swift version 6.0-dev (LLVM cef183591317ec7, Swift 66e311074bff491) / swift-DEVELOPMENT-SNAPSHOT-2024-05-15-a-ubuntu22.04, and the following flags in the Package.swift file:

            cSettings: [
                .unsafeFlags(["-fdeclspec"])
            ],
            swiftSettings: [
                .enableExperimentalFeature("Embedded"),
                .interoperabilityMode(.C),
                .unsafeFlags([
                    "-wmo",
                    "-disable-cmo",
                    "-Xfrontend", "-disable-stack-protector"
                ])
            ],
            linkerSettings: [
                .unsafeFlags([
                    "-Xclang-linker", "-nostdlib",
                    "-Xlinker", "--no-entry"
                ])
            ]

Additional information

Asked about this in the forum https://forums.swift.org/t/bug-in-embedded-arc-webassembly-combination/71967.
@kubamracek asked me to open a bug report

@turbolent turbolent added bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. triage needed This issue needs more specific labels labels May 20, 2024
@kubamracek kubamracek added the embedded Embedded Swift label May 21, 2024
@meg-gupta meg-gupta self-assigned this May 21, 2024
@turbolent
Copy link
Author

Re-testing with swift-DEVELOPMENT-SNAPSHOT-2024-06-03-a-ubuntu22.04 it looks like the bugs are unfortunately not fixed, I now get:

export function test1() {
  foo_c();
  foo_a();
  foo_b();
}

It seems odd the deinit occurs first, even before the init.

export function test2() {
  var a:int = bar_new();
  bar_free(a);
  bar_use(a);
}

Still getting a free before use.

@meg-gupta
Copy link
Contributor

I'll take another look. Seems like a different issue.

@meg-gupta
Copy link
Contributor

@turbolent #74152 fixed the remaining issue here.

I verified with the generated IR that the deinit is no longer eliminated and init side-effects are before deinit side-effects.

// test1()
sil @$s5test1AAyyF : $@convention(thin) () -> () {
[global: read,write,copy,destroy,allocate,deinit_barrier]
bb0:
  // function_ref foo_init
  %0 = function_ref @foo_init : $@convention(c) () -> () // user: %1
  %1 = apply %0() : $@convention(c) () -> ()
  // function_ref foo_method
  %2 = function_ref @foo_method : $@convention(c) () -> () // user: %3
  %3 = apply %2() : $@convention(c) () -> ()
  // function_ref foo_deinit
  %4 = function_ref @foo_deinit : $@convention(c) () -> () // user: %5
  %5 = apply %4() : $@convention(c) () -> ()
  %6 = tuple ()                                   // user: %7
  return %6 : $()                                 // id: %7
} // end sil function '$s5test1AAyyF'

@turbolent
Copy link
Author

@meg-gupta Great, thank you! I'll test again with the next trunk toolchain

@turbolent
Copy link
Author

I can confirm that the tests look good now with swift-DEVELOPMENT-SNAPSHOT-2024-06-06-a-ubuntu22.04:

export function test1() {
  foo_a();
  foo_b();
  foo_c();
}

export function test2() {
  var a:int = bar_new();
  bar_use(a);
  bar_free(a);
}

Thank you again for fixing these issues!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. embedded Embedded Swift triage needed This issue needs more specific labels
Projects
None yet
Development

No branches or pull requests

3 participants