tinygo-org / tinygo

Go compiler for small places. Microcontrollers, WebAssembly (WASM/WASI), and command-line tools. Based on LLVM.
https://tinygo.org
Other
14.81k stars 872 forks source link

`-panic=trap` still prints panic messages #4161

Closed jharveyb closed 4 months ago

jharveyb commented 5 months ago

Related to #3068, #2491.

tl;dr there should be a panic build option to short-circuit runtime.runtimePanicAt to just unreachable vs. printing a string.

I was trying to produce a WASM progra, that could accept & return a string from/to the host, similar to this wazero example.

I compiled with tinygo v0.30.0, tinygo build -o main.wasm -no-debug -panic=trap -scheduler=none -gc=leaking -target=wasm main.go.

Inspecting the output with wasmer, I found wasi_snapshot_preview1 fd_write being imported. My program does not print anything, and only imports unsafe.

Converting the WASM to WAT, I found that the runtime will unconditionally try to print during a runtimePanic. This feels pretty unavoidable, as funcs in the unsafe package like Slice or String can panic, but are also useful for passing memory between the guest and host.

Relevant WAT snippet:

(func $runtime.runtimePanicAt (type 4) (param i32 i32)
    i32.const 65536
    i32.const 22
    call $runtime.printstring
    local.get 0
    local.get 1
    call $runtime.printstring
    i32.const 10
    call $runtime.putchar
    unreachable)
  (func $runtime.printstring (type 4) (param i32 i32)
    local.get 1
    i32.const 0
    local.get 1
    i32.const 0
    i32.gt_s
    select
    local.set 1
    loop  ;; label = @1
      local.get 1
      if  ;; label = @2
        local.get 0
        i32.load8_u
        call $runtime.putchar
        local.get 1
        i32.const 1
        i32.sub
        local.set 1
        local.get 0
        i32.const 1
        i32.add
        local.set 0
        br 1 (;@1;)
      end
    end)
  (func $runtime.putchar (type 1) (param i32)
    (local i32 i32)
    i32.const 65716
    i32.load
    local.tee 1
    i32.const 119
    i32.le_u
    if  ;; label = @1
      i32.const 65716
      local.get 1
      i32.const 1
      i32.add
      local.tee 2
      i32.store
      local.get 1
      i32.const 65720
      i32.add
      local.get 0
      i32.store8
      local.get 0
      i32.const 255
      i32.and
      i32.const 10
      i32.ne
      i32.const 0
      local.get 1
      i32.const 119
      i32.ne
      select
      i32.eqz
      if  ;; label = @2
        i32.const 65668
        local.get 2
        i32.store
        i32.const 1
        i32.const 65664
        i32.const 1
        i32.const 65856
        call $runtime.fd_write
        drop
        i32.const 65716
        i32.const 0
        i32.store
      end
      return
    end
    i32.const 65581
    i32.const 18
    call $runtime.runtimePanicAt
    unreachable)

Call graph:

flowchart TD
  A[runtime.slicePanic] & B[runtime.nilPanic] --> C[runtime.runtimePanicAt]
  C --> D[runtime.printstring] & E[runtime.putchar]
  D --> E
  E --> F[runtime.fd_write]

Rust has the build option panic_immediate_abort to omit any string formatting on panic, for similar reasons of small code size & minimal dependencies. Adding something similar to TinyGo would allow for WASM programs that include the runtime without also requiring a WASI-compliant host.

aykevl commented 4 months ago

The bug here is that TinyGo still calls runtime.putchar even with -panic=trap. We don't need a new option, we just need to make that option work correctly.

aykevl commented 4 months ago

Here is a fix: https://github.com/tinygo-org/tinygo/pull/4195 Can you test it for your use case?

jharveyb commented 4 months ago

Here is a fix: #4195 Can you test it for your use case?

This worked btw, thanks for the fix!

I switched to a custom target like that specified in #4174 to drop all the runtime GC functions.