tinygo icon indicating copy to clipboard operation
tinygo copied to clipboard

Compiled function with arguments has wrong signature

Open alexec opened this issue 2 years ago • 2 comments

This does not make any sense to me:

package main

func main() {}

//export HelloWorld
func HelloWorld() {
}

//export HelloArg
func HelloArg(arg string) {
}

//export HelloReturn
func HelloReturn() string {
	return ""
}
tinygo version 0.24.0 darwin/amd64 (using go version go1.18.4 and LLVM version 14.0.0)
cd wasm && tinygo build -o ../module.wasm -wasm-abi=generic -target=wasi .
wasmer inspect module.wasm
Type: wasm
Size: 36.8 KB
Imports:
  Functions:
    "wasi_snapshot_preview1"."fd_write": [I32, I32, I32, I32] -> [I32]
  Memories:
  Tables:
  Globals:
Exports:
  Functions:
    "malloc": [I32] -> [I32]
    "free": [I32] -> []
    "calloc": [I32, I32] -> [I32]
    "realloc": [I32, I32] -> [I32]
    "_start": [] -> []
    "HelloWorld": [] -> []
    "HelloArg": [I32, I32] -> []
    "HelloReturn": [I32] -> []
    "asyncify_start_unwind": [I32] -> []
    "asyncify_stop_unwind": [] -> []
    "asyncify_start_rewind": [I32] -> []
    "asyncify_stop_rewind": [] -> []
    "asyncify_get_state": [] -> [I32]
  Memories:
    "memory": not shared (2 pages..)
  Tables:
  Globals:

Can someone please explain why HelloArg and HelloReturn has these weird parameters?

alexec avatar Jul 21 '22 19:07 alexec

In Go, a string is actually passed around as a pointer to the string data and a length. For wasm, this is a pair of (int32, int32).

For returns values, wasm functions can only return a single value. So for functions that need to return a string the destination for the multiple return values is passed as a function argument as a pointer to the location to write the multiple values.

Does this make sense?

dgryski avatar Jul 21 '22 20:07 dgryski

Ah. Yet this is not going to work for my use case.

Let me explain what I want to do. I want to allow other teams to implement an interface, and implement in any language, Golang orJavascript. This will then be executed by importing the module into a Go app. The interface I want is this:

func SayHello(person string) string

Can tinygo do this? Or do I need to look for other solutions?

alexec avatar Jul 22 '22 17:07 alexec

my 2p:

I think string and []byte could be special cased somehow, though when (if) strings are supported natively in wasm, special rules like this will create ambiguity.

I might instead keep your signature the same except implement it differently based on arch.

Ex. func SayHello(person string) string can be implemented using WebAssembly types like so:

func SayHello(person string) string {
	ptr, size := stringToPtr(person)
	ptrSize := _SayHello(ptr, size)
	ptr = uint32(ptrSize >> 32)
	size = uint32(ptrSize)
	return ptrToString(ptr, size)
}

// _SayHello implements SayHello using types present in WebAssembly
// core specification 1.0
//
// Note: In TinyGo "//export" on a func is actually an import!
//
//go:wasm-module env
//export SayHello
func _SayHello(ptr uint32, size uint32) (ptrSize uint64)


// ptrToString returns a string from WebAssembly compatible numeric types
// representing its pointer and length.
func ptrToString(ptr uint32, size uint32) string {
	// Get a slice view of the underlying bytes in the stream. We use SliceHeader, not StringHeader
	// as it allows us to fix the capacity to what was allocated.
	return *(*string)(unsafe.Pointer(&reflect.SliceHeader{
		Data: uintptr(ptr),
		Len:  uintptr(size), // Tinygo requires these as uintptrs even if they are int fields.
		Cap:  uintptr(size), // ^^ See https://github.com/tinygo-org/tinygo/issues/1284
	}))
}

// stringToPtr returns a pointer and size pair for the given string in a way
// compatible with WebAssembly numeric types.
func stringToPtr(s string) (uint32, uint32) {
	buf := []byte(s)
	ptr := &buf[0]
	unsafePtr := uintptr(unsafe.Pointer(ptr))
	return uint32(unsafePtr), uint32(len(buf))
}

As you'll notice this code looks generatable even if not done in tinygo compiler.

You can also look at https://github.com/inkeliz/karmem

codefromthecrypt avatar Aug 16 '22 01:08 codefromthecrypt

ps above was basically cobbled from https://github.com/tetratelabs/wazero/tree/main/examples/allocation/tinygo but I didn't make the source files arch specific (ex to have SayHello defined in a different source file if target=wasi)

codefromthecrypt avatar Aug 16 '22 01:08 codefromthecrypt

Wow. The answer appears to be “WASM does not support strings”.

I’m actually pretty 🤯because it’s such a common language feature I’d just assumed it’s be supported.

It’s like buying a car only to find out it does not have windscreen wipers. Yes, you can drive it, but only if you are happy to drive only in the sunshine.

alexec avatar Aug 16 '22 03:08 alexec

@alexec yeah that actually is a better way to frame it, basically it doesn't currently and even 2.0 (draft) doesn't support strings. You aren't alone in surprise, basically what some end up realizing is that wasm is mostly focused on core numeric types still though there is hope in the future.

There are two specifications some compilers may support ahead of passing the requisite w3c phases, as mentioned in the proposals repo: component-model and stringref. What that means is some compilers may opt-in experimental support ahead of actually ending up in the spec.

Meanwhile, what's typical is either a compiler or a code generator translates strings to offset/length pairs, noting that even multiple results aren't supported until WebAssembly 2.0. That said, most runtimes support features needed for multiple results.

codefromthecrypt avatar Aug 16 '22 03:08 codefromthecrypt

This is such a shame. I had strong expectation about using WASM as an fast (i.e millions of evaluations a second) and portable (i.e. many different language runtime) embedded scripting language and have two strong use cases for it.

But lack of string support makes it unusable.

🤷

alexec avatar Aug 16 '22 13:08 alexec

@alexec I suspect it is ok to close this, as tinygo has limited influence on changing the WebAssembly spec ;)

codefromthecrypt avatar Sep 07 '22 06:09 codefromthecrypt

Yeah, we can't do a lot about it. I hope that the stringref proposal gets some traction, that would allow us to use strings in exported functions. Until then, they are represented as a (pointer, length) pair like in many other languages.

aykevl avatar Sep 08 '22 15:09 aykevl

In Go, a string is actually passed around as a pointer to the string data and a length. For wasm, this is a pair of (int32, int32).

For returns values, wasm functions can only return a single value. So for functions that need to return a string the destination for the multiple return values is passed as a function argument as a pointer to the location to write the multiple values.

Does this make sense?

Wasm functions can return multiple values now.

HarikrishnanBalagopal avatar Jul 19 '23 12:07 HarikrishnanBalagopal

Wasm functions can return multiple values now.

Yep, but that isn't a part of a REC spec. Right now, 2.0 which supports that is still a draft. It doesn't mean TinyGo couldn't enable features scheduled for 2.0, but it is a distinct choice to do that. I'm not sure even rust by default will do multiple returns either fwiw.

codefromthecrypt avatar Jul 20 '23 00:07 codefromthecrypt