tinygo icon indicating copy to clipboard operation
tinygo copied to clipboard

wasm-ld: error: couldn't allocate output register for constraint '{r0}'

Open anshuman-mor opened this issue 5 months ago • 7 comments

Getting this error intermittently when compiling to wasip2

tinygo build -o test-tinygo-wasi2.wasm --target wasip2 --no-debug

wasm-ld: error: couldn't allocate output register for constraint '{r0}'

I am running this on Windows.

tinygo version tinygo version 0.38.0 windows/amd64 (using go version go1.23.2 and LLVM version 19.1.2)

anshuman-mor avatar Jul 17 '25 13:07 anshuman-mor

Hello @anshuman-mor could you please provide some code that reproduces this error? Thank you.

deadprogram avatar Aug 12 '25 17:08 deadprogram

Hello, I have the same issue. I am compiling this library, which has some crypto operations:

https://github.com/gagliardetto/solana-go

I am using the same version as @anshuman-mor

package main

import sgo "github.com/gagliardetto/solana-go"

func main() {
        _, err := sgo.NewRandomPrivateKey()
        if err != nil {
                panic(err)
        }
        time.Sleep(50 * time.Second)
}

I am trying to generate a ed25519 key pair.

joelnpdr avatar Aug 20 '25 14:08 joelnpdr

I see that for wasip2, tinygo pulls in ARM code:

https://github.com/tinygo-org/tinygo/blob/release/targets/wasip2.json#L8

joelnpdr avatar Aug 21 '25 02:08 joelnpdr

Oh, huh. Somehow this program is calling syscalls, which of course won't work on wasip2. I wonder how that happened.

aykevl avatar Sep 24 '25 11:09 aykevl

I traced the calls a little bit:

  • github.com/streamingfast/logging.maybeNewLogger
  • golang.org/x/crypto/ssh/terminal.IsTerminal
  • golang.org/x/term.IsTerminal
  • golang.org/x/term.isTerminal
  • golang.org/x/sys/unix.IoctlGetTermios
  • golang.org/x/sys/unix.ioctl

So yeah this all makes sense, considering we emulate linux/arm for wasip2 (since upstream doesn't support wasip2 in the stdlib). But the compiler should not have emitted these syscalls at least.

aykevl avatar Sep 24 '25 11:09 aykevl

Ok, here you go: https://github.com/tinygo-org/tinygo/pull/5054 Not a fix, but a better error message.

I don't know what the correct fix for this would be. A workaround would be to add the right build tags in a place like this but I doubt that will be accepted upstream. A proper fix would be to add support for wasip2 in Go so that we don't have to pretend to be linux/arm just to get the standard library working.

aykevl avatar Sep 24 '25 11:09 aykevl

I ran into the same issue as well, but a lot weirder. On 0.39.0 the first time I compile the code, it would give the errors described in the original description. However, if I just try it again it works on all subsequent attempts:

$ tinygo build -target wasip2 -no-debug -o main.wasm
wasm-ld: error: couldn't allocate output register for constraint '{r0}'
wasm-ld: error: couldn't allocate output register for constraint '{r0}'
wasm-ld: error: couldn't allocate output register for constraint '{r0}'
wasm-ld: error: couldn't allocate output register for constraint '{r0}'
wasm-ld: error: couldn't allocate output register for constraint '{r0}'
wasm-ld: error: couldn't allocate output register for constraint '{r0}'
wasm-ld: error: couldn't allocate output register for constraint '{r0}'
$ tinygo build -target wasip2 -no-debug -o main.wasm
$ tinygo build -target wasip2 -no-debug -o main.wasm

If I change any line of code, then it doesn't work for the first time again, but if I compile again it works.

On dev, after #5054, it now throws the error added and never compiles (of course now no longer after multiple attempts either). This let me know that the issue was in golang.org/x/sys for me as well.

As a workaround to fix it (on dev, and so it compiles successfully on the first time on 0.39.0), I forked golang.org/x/sys and did a replacement in my go.mod:

replace golang.org/x/sys => github.com/terminal-games/sys v0.0.1

I wrote a codemod script to add panics before every syscall in unix.

func fcntl(fd int, cmd, arg int) (int, error) {
+	panic("syscall not supported in wasm: Syscall(fcntl64Syscall, uintptr(fd), uintptr(cmd), uintptr(arg))")
	valptr, _, errno := Syscall(fcntl64Syscall, uintptr(fd), uintptr(cmd), uintptr(arg))
	var err error
	if errno != 0 {
		err = errno
	}
	return int(valptr), err
}

This way, the rest of the function body is optimized out, and the call to the syscall is never emitted. Also, it happens to reduce the bundle slightly from 897K (on a second-time run on 0.39.0) to 889K (with my fork, also on 0.39.0).

Lots of go projects have a transitive dependency on golang.org/x/sys, but even if the functions are never called, the compiler tries to emit instructions for the syscalls within them since tinygo does linux/arm "emulation" for WASM. Lowering it to a panic instead of just not being compilable allows for greater package compatibility.

It would be nice if instead of stopping the compilation like in #5054, tinygo would actually just insert a similar runtime panic like my fork of golang.org/x/sys does. I think it will increase compatibility while tinygo still does linux/arm "emulation" for WASM. And that way it will also cover the syscall package in the standard library.

Another scarier idea could be to just make syscalls no-ops (return immediately) and see what happens. People will then compile to WASM with the understanding that obviously syscalls won't work. This might make sense because sometimes packages really do define a _other.go version which does a no-op, but tinygo wasip2 tries to compile the linux/arm version and fails. Though this idea obviously has its own problems.

Side note: is there a list somewhere of what standard library packages we gain by "emulating" linux/arm, over just setting GOOS/GOARCH correctly?

mbund avatar Dec 04 '25 04:12 mbund