tinygo-org / tinygo

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

Don't require main function in wasm builds #2703

Open leighmcculloch opened 2 years ago

leighmcculloch commented 2 years ago

I tried building with tinygo a simple non-main package with no dependencies with the intention of using it with wasm standalone in wasm3 or wasmer and it errored with:

package invoke

func invoke(i int32) int32 {
    return 3
}
$ tinygo build -o invoke.wasm -target wasm && ll
panic: expected main package to have name 'main'

goroutine 1 [running]:
github.com/tinygo-org/tinygo/loader.(*Package).Check(0xc002884000)
        /Users/runner/work/tinygo/tinygo/loader/loader.go:373 +0x318
github.com/tinygo-org/tinygo/loader.(*Program).Parse(0xc002742180)
        /Users/runner/work/tinygo/tinygo/loader/loader.go:299 +0xb9
github.com/tinygo-org/tinygo/builder.Build({0x80f913f, 0x1}, {0x20edde0fb, 0x60}, 0xc0002e5f50, 0xc0001177e0)
        /Users/runner/work/tinygo/tinygo/builder/build.go:177 +0xfa9
main.Build({0x80f913f, 0x1}, {0x20edde0fb, 0xb}, 0x1)
        /Users/runner/work/tinygo/tinygo/main.go:151 +0x8f
main.main()
        /Users/runner/work/tinygo/tinygo/main.go:1337 +0x3aed
hunjixin commented 2 years ago

same issue

dkegel-fastly commented 2 years ago

Related issue: https://github.com/tinygo-org/tinygo/issues/2735

codefromthecrypt commented 2 years ago

One of the more curious causes of support is that when you compile with the wasi target and don't have a main function, it defaults to assuming you want to import your own from the "env" module as "main.main".

I'm not sure the rationale of this assumption, or if it was an oversight. In any case, I think the right behavior is to remove it as an instantiation bomb and instead backfill a no-op main. A flag could be raised if anyone is actually importing "main.main" to allow it, or use the normal //export thing to do that.

wdyt?

dkegel-fastly commented 2 years ago

The title of this issue might be better if it described the use case. What calls the non-main module? How is it linked together?

codefromthecrypt commented 2 years ago

currently, for lack of a standalone target, people use wasi to export functions in WebAssembly. The host uses those functions directly, passing parameters, as opposed to indirectly via a _start.

codefromthecrypt commented 2 years ago

For context, in zig, this is freestanding and rust, cdylib. The main request is to stop support cases where people have to remember to add main and have confusing errors if not.

codefromthecrypt commented 2 years ago

I should also mention that even when some export uses uses WASI (ex. indirectly via fmt.Println), I believe it is better to have a default to backfill main vs have everyone remember to add something like..

// main is required for TinyGo to compile to Wasm.
func main() {}
leighmcculloch commented 2 years ago

What calls the non-main module? How is it linked together?

A wasm runtime that doesn't call the start function, but calls other functions in the complied wasm.

My use case is to be able to build wasm programs to be deployed on @stellar, and so I'm more interested in the freestanding target, i.e. no WASI.

As @codefromthecrypt pointed out Rust and Zig both have good support for this use case.

Other use cases might be applications that use wasm as a plugin system that don't plan to call start.

However, given that this works in all these cases by providing an empty main function maybe this isn't important to address. It's mostly a first time bad experience and then once you know how it works you can move on.

hunjixin commented 2 years ago

the trouble with this is when the init code should be executed. If the initAll code is called in all the export functions. this may be a problem when linking modules in runtime(like wasmtime), and could lead to repeated calls to initAll code. go does not support dynamic linking usage.

hunjixin commented 2 years ago

i am not sure how wasmtime hand call to another module export function, if each module have diferent exec environment, could add a flag to check if have exected init code before. but if all module running in one environment, maybe have difficulty. need more dig for wasmtime.

hunjixin commented 2 years ago

https://github.com/tinygo-org/tinygo/issues/2769

codefromthecrypt commented 2 years ago

per @jzabinski-dolios on https://github.com/tinygo-org/tinygo/issues/2495#issuecomment-1018576022, we should include this feedback in any eventual outcome here.

Should the lack of a func main() in the source file result in a compiler error?

Yes. It did in the past, but apparently this got removed at some point. This would be fixed by removing --allow-undefined.

Plus I think we should have an outcome shortly as this is one topic that causes quite a lot of support load.

aykevl commented 2 years ago

One of the more curious causes of support is that when you compile with the wasi target and don't have a main function, it defaults to assuming you want to import your own from the "env" module as "main.main".

I'm not sure the rationale of this assumption, or if it was an oversight.

It was an oversight. The change in #3133 will make this a linker error instead of a runtime error, which is already a lot better. I suggest simply creating an empty main function.

codefromthecrypt commented 2 years ago

so this issue was requesting to build a non-main package (ex one not named main), and then there was a second topic which is that if you name it main, you had no feedback you missed adding a boilerplate main function.

Now, the latter is solved "good enough" imho:

$ ~/oss/tinygo/build/tinygo build -target=wasi -o main.wasm main.go
tinygo:wasm-ld: error: /var/folders/vd/1cf8zdb1721f4z5rjggy8bp40000gn/T/tinygo2841776377/main.o: undefined symbol: main.main
failed to run tool: wasm-ld
error: failed to link /var/folders/vd/1cf8zdb1721f4z5rjggy8bp40000gn/T/tinygo2841776377/main: exit status 1

At least it doesn't silently pass anymore, and I think it is reasonable to expect someone to figure out main function was missing. from the symbol: main.main part.

What I'm hearing is that for the first part, to allow building non-main packages (ex ones that don't want to have main in them, like library packages), that this is a won't fix for the wasi target, even if another target such as freestanding might. Am I understanding right @aykevl? If so, I'd recommend closing this and we can refer back to it and close any new issues as dupes when they arise.

Otherwise, this is a "not yet" and I expect that means help wanted, right?

aykevl commented 2 years ago

Non-main packages are not something the Go language supports:

Program execution

A complete program is created by linking a single, unimported package called the main package with all the packages it imports, transitively. The main package must have package name main and declare a function main that takes no arguments and returns no value.

func main() { … }

Program execution begins by initializing the main package and then invoking the function main. When that function invocation returns, the program exits. It does not wait for other (non-main) goroutines to complete.

So, while we could support it, note that it is non-standard. It would make a lot more sense to me to simply require users to use a main package.

leighmcculloch commented 1 year ago

Non-main packages are not something the Go language supports:

In that case keeping the requirement on it being a main package makes sense, and instead lifting the requirement on there being a main function to call.

This would be the same as for how Go plugins behave:

A plugin is a Go main package with exported functions and variables that has been built with:

go build -buildmode=plugin

When a plugin is first opened, the init functions of all packages not already part of the program are called. The main function is not run.

Ref: https://pkg.go.dev/plugin

leighmcculloch commented 1 year ago

I've updated the issue title to reflect the above.

fgsch commented 1 year ago

Maybe this is something for the docs?

dkegel-fastly commented 1 year ago

Once implemented in tinygo, perhaps.

I will be interested to see if this is how the https://github.com/WebAssembly/component-model camel gets its nose into the tinygo tent, or vice versa.

otaxhu commented 2 months ago

Same issue here, it has been a lot time since the last comment, there are news about this?

otaxhu commented 2 months ago

I have this use case where I need to export go functions with the //export directive in a non-main package

package non_main

//export add
func add(a, b int) {
    return a + b
}
otaxhu commented 2 months ago

tinygo version command output:

tinygo version 0.32.0 linux/amd64 (using go version go1.22.4 and LLVM version 18.1.6)

Output after building previous package:

$ tinygo build -target wasm -o a.wasm
panic: expected main package to have name 'main'
otaxhu commented 2 months ago

As @leighmcculloch cites, tinygo could support -buildmode flag with a value like package or other better descriptive name, allowing building a package and not running a main function, just exporting symbols

hunjixin commented 2 months ago

my solution is use a empty main function and export function in main package, after build process the wasm file. insert a call instruction to invoke __start function before each export function, this operation is for invoke initAll function. this export function can work correctly.

otaxhu commented 2 months ago

I would like to contribute to solve this issue, it doesn't seem like there is an active branch related to this. So I will proceed to create a development fork.

ydnar commented 2 months ago

my solution is use a empty main function and export function in main package, after build process the wasm file. insert a call instruction to invoke __start function before each export function, this operation is for invoke initAll function. this export function can work correctly.

For reactor programs that export functions, the runtime should be initialized in _initialize.