IIRC the WebAssembly text format (.wat) is not able to express everything .wasm can, such as binary (custom) sections. I'm not sure about the current state of WebAssembly text and binary formats now though.

I asked here recently about how I could add source locations (including the full call stack) to thrown exceptions in C++ and got this answer suggesting the #line directive (which can be useful in my case) and the use of libunwind to collect the call stack.

I was thinking of researching on the WebAssembly target for avoiding possible name clash and reserved keywords issues and also because I'm not experienced with C++.

I'm wondering if it's feasible to target .wat since writing the binary manually requires more work. Did any language do this in practice? I know Lobster targets the binary format (.wasm), but I use another language for my type checker, C#, and Lobster's writer is written in C++, so it may be problematic to port it.

It looks like C# does have a library for writing .wasm: https://github.com/RyanLamansky/dotnet-webassembly

  • 1
    $\begingroup$ Tangential to the question, but if you target wasm, you might face this issue. $\endgroup$
    – Bubbler
    Commented Jun 29, 2023 at 23:57
  • 1
    $\begingroup$ Are either of the existing answers acceptable, or does a new answer need to be written? $\endgroup$
    – Corbin
    Commented Feb 24 at 17:48

2 Answers 2


I think yes, the compiler should produce .wat format.

My Valkyrie language now uses the .wat format, and a long time ago it directly output .wasm.

Next I will explain the reasons for this change, I hope it will be helpful to you.

As preparatory knowledge, first introduce some core libraries and concepts I use ​

  • wasm-encoder: The widely used wasm linker, which produces .wasm
  • wast-encoder: The new linker I implemented, output is .wat
  • wasmparser: The widely used wasm parser and validator, supporting both text and binary.
  • wasm-opt: Widely used optimizer, but does not support the latest wit components.

A long time ago I used wasm-encoder to directly encode my types, and wasm-opt to optimize the binary, and everything was great.

Until the introduction of wit and gc, various tool chains were unable to parse the gc instructions and wit definitions emitted by wasm-encoder. Only the chrome browser could execute the binary I output, but it was very difficult to debug.

This has been going on for a long time and will continue to be for a long time. My toolchain is paralyzed.

In this context I developed wast-encoder, which handles the linking process and finally outputs .wat. This is essentially a linker, not an encoder.

In the wit component, the wasm modules need to be instantiated in a specific order, and each newly defined type must reference an already defined type. This is beyond the scope of a simple coder.

Another reason is that the instructions issued by the encoder erase the information in the source language. Even if chrome tells me which line wasm reports an error, I don't know what went wrong.

wasm-encoder emits code similar to this (decompiled)

  (import "wasi:io/[email protected]" (instance (;1;) (type 1)))
  (alias export 1 "error" (type (;2;)))
  (type (;3;)
      (export (;0;) "output-stream" (type (sub resource)))
      (alias outer 1 2 (type (;1;)))
      (export (;2;) "error" (type (eq 1)))
      (type (;3;) (own 2))
      (type (;4;) (variant (case "last-operation-failed" 3) (case "closed")))
      (export (;5;) "stream-error" (type (eq 4)))
      (type (;6;) (borrow 0))
      (type (;7;) (list u8))
      (type (;8;) (result (error 5)))
      (type (;9;) (func (param "self" 6) (param "contents" 7) (result 8)))
      (export (;0;) "[method]output-stream.blocking-write-and-flush" (func (type 9)))
  (import "wasi:io/[email protected]" (instance (;2;) (type 3)))
  (alias export 2 "output-stream" (type (;4;)))
  (core instance (;4;)
    (export "get-stdin" (func 2))
  (alias export 5 "get-stdout" (func (;5;)))

wast-encoder emits more understandable code, although this is longer.

(component $Root
    (import "wasi:io/[email protected]" (instance $wasi:io/[email protected]
        ;; class std::io::Error
        (export "error" (type (sub resource)))
    (alias export $wasi:io/[email protected] "error" (type $io-error))

    ;; variant std::io::StreamError
    (type $stream-error (variant
    (case "last-operation-failed" (own $io-error))
        (case "closed")

    (import "wasi:io/[email protected]" (instance $wasi:io/[email protected]
    (export $output-stream "output-stream" (type (sub resource)))

        (alias outer $Root $io-error (type $io-error))
    (export $stream-error0 "stream-error" (type (eq $stream-error)))
    (export $stream-error1 "alias0" (type (eq $stream-error)))

        (export "[method]output-stream.blocking-write-and-flush"
            ;; class std::io::OutputStream
            ;; method blocking-write-and-flush(self) -> Result<(), std::io::Error>
            (func (param "self" (borrow $output-stream)) (param "contents" (list u8)) (result (result (error $stream-error0))))
    (alias export $wasi:io/[email protected] "output-stream" (type $output-stream))
    (alias export $wasi:io/[email protected] "alias0" (type $output-stream0))

    (import "wasi:cli/[email protected]" (instance $wasi:cli/[email protected]
    (alias outer $Root $output-stream (type $output-stream))
        (export "get-stdout"
            ;; class std::io::OutputStream
            ;; static stdout() -> Self
            (func (result (own $output-stream)))

The size issue is not very important. Although wasm-opt does not currently support gc and wit, compact binaries will be obtained in the future.

In contrast, text with source language information and structure is more helpful for debugging.

Finally, focus your scenario in question.

There is currently no specification standard that can emit raw stack information. I know that LLDB can add some custom instructions, and chrome can recognize and jump directly, but this is non-standard, the tool chain support is limited, and the future is uncertain.

I have a proposal for tools to recognize such information (this is legal wasm, even now). ​

(func $always-one
    (@name "function-name-in-source-language")
    (@file "user/language/source.vk")
    (@line 12)
    (@column 4)
    (result i32) 
    (i32.const 0)

In any case, I hope to use the solution within the wasm standard, rather than the solution customized by various tools.


Well, the main compiler for my programming language, the AEC-to-WebAssembly compiler, does output .wat and use wat2wasm. My programming language doesn't have exceptions, though, nor would I know how to implement them.


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .