]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/link, runtime: support library mode on wasip1
authorCherry Mui <cherryyz@google.com>
Thu, 8 Aug 2024 22:45:18 +0000 (18:45 -0400)
committerCherry Mui <cherryyz@google.com>
Tue, 13 Aug 2024 21:27:50 +0000 (21:27 +0000)
This CL adds support of "library", i.e. c-shared, build mode on
wasip1. When -buildmode=c-shared is set, it builds a Wasm module
that is intended to be used as a library, instead of an executable.
It does not have the _start function. Instead, it has an
_initialize function, which initializes the runtime, but not call
the main function.

This is similar to the c-shared build mode on other platforms. One
difference is that unlike cgo callbacks, where Ms are created on-
demand, on Wasm we have only one M, so we just keep the M (and the
G) for callbacks.

For #65199.

Change-Id: Ieb21da96b25c1a9f3989d945cddc964c26f9085b
Reviewed-on: https://go-review.googlesource.com/c/go/+/604316
Reviewed-by: Achille Roussel <achille.roussel@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
14 files changed:
src/cmd/dist/test.go
src/cmd/go/internal/load/pkg.go
src/cmd/internal/obj/wasm/wasmobj.go
src/cmd/link/internal/ld/config.go
src/cmd/link/internal/ld/symtab.go
src/cmd/link/internal/wasm/asm.go
src/internal/platform/supported.go
src/runtime/asm_wasm.s
src/runtime/lock_js.go
src/runtime/proc.go
src/runtime/rt0_js_wasm.s
src/runtime/rt0_wasip1_wasm.s
src/runtime/stubs_nonwasm.go [new file with mode: 0644]
src/runtime/stubs_wasm.go [new file with mode: 0644]

index 0ffcabe4164af6ec05bbe701f5b49d16d6e77a8c..0facfb579cb196ceace19a07279e8d3202a31483 100644 (file)
@@ -1625,7 +1625,8 @@ func buildModeSupported(compiler, buildmode, goos, goarch string) bool {
                        "android/amd64", "android/arm", "android/arm64", "android/386",
                        "freebsd/amd64",
                        "darwin/amd64", "darwin/arm64",
-                       "windows/amd64", "windows/386", "windows/arm64":
+                       "windows/amd64", "windows/386", "windows/arm64",
+                       "wasip1/wasm":
                        return true
                }
                return false
index 43429a1d9303fcca1fb1579ebb748d68e86ff53e..433e95138805049625e2aa2f87cb161e5292fc7d 100644 (file)
@@ -2577,7 +2577,12 @@ func externalLinkingReason(p *Package) (what string) {
 
        // Some build modes always require external linking.
        switch cfg.BuildBuildmode {
-       case "c-shared", "plugin":
+       case "c-shared":
+               if cfg.BuildContext.GOARCH == "wasm" {
+                       break
+               }
+               fallthrough
+       case "plugin":
                return "-buildmode=" + cfg.BuildBuildmode
        }
 
index 20ed142812d15d8e0b159be06a6c05fcf8cb2436..0189ffe6f560f8d5c7fe808383699470416c7138 100644 (file)
@@ -1025,6 +1025,7 @@ func regAddr(reg int16) obj.Addr {
 var notUsePC_B = map[string]bool{
        "_rt0_wasm_js":            true,
        "_rt0_wasm_wasip1":        true,
+       "_rt0_wasm_wasip1_lib":    true,
        "wasm_export_run":         true,
        "wasm_export_resume":      true,
        "wasm_export_getsp":       true,
@@ -1080,7 +1081,8 @@ func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
        // Function starts with declaration of locals: numbers and types.
        // Some functions use a special calling convention.
        switch s.Name {
-       case "_rt0_wasm_js", "_rt0_wasm_wasip1", "wasm_export_run", "wasm_export_resume", "wasm_export_getsp",
+       case "_rt0_wasm_js", "_rt0_wasm_wasip1", "_rt0_wasm_wasip1_lib",
+               "wasm_export_run", "wasm_export_resume", "wasm_export_getsp",
                "wasm_pc_f_loop", "runtime.wasmDiv", "runtime.wasmTruncS", "runtime.wasmTruncU", "memeqbody":
                varDecls = []*varDecl{}
                useAssemblyRegMap()
index 3a186b47f715aedb67762b1ecc46e58d90f11a27..b2d4ad7cb0e7f6ad1c97e15656c772183fe99f6d 100644 (file)
@@ -145,6 +145,9 @@ func mustLinkExternal(ctxt *Link) (res bool, reason string) {
        case BuildModeCArchive:
                return true, "buildmode=c-archive"
        case BuildModeCShared:
+               if buildcfg.GOARCH == "wasm" {
+                       break
+               }
                return true, "buildmode=c-shared"
        case BuildModePIE:
                if !platform.InternalLinkPIESupported(buildcfg.GOOS, buildcfg.GOARCH) {
index 01f9780d8b44805e89f741a96f553a91f5dc934a..92e856a7660d5e52f56cf0a580a3a2c5f599f75b 100644 (file)
@@ -432,7 +432,7 @@ func textsectionmap(ctxt *Link) (loader.Sym, uint32) {
 func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind {
        ldr := ctxt.loader
 
-       if !ctxt.IsAIX() {
+       if !ctxt.IsAIX() && !ctxt.IsWasm() {
                switch ctxt.BuildMode {
                case BuildModeCArchive, BuildModeCShared:
                        s := ldr.Lookup(*flagEntrySymbol, sym.SymVerABI0)
index 5b36ea0fbcb15ae2bff811a8ebdd17a9f4192cc4..87a67754ccb3a6d2e72afacdffa754b620ee72fd 100644 (file)
@@ -68,6 +68,7 @@ func readWasmImport(ldr *loader.Loader, s loader.Sym) obj.WasmImport {
 var wasmFuncTypes = map[string]*wasmFuncType{
        "_rt0_wasm_js":            {Params: []byte{}},                                         //
        "_rt0_wasm_wasip1":        {Params: []byte{}},                                         //
+       "_rt0_wasm_wasip1_lib":    {Params: []byte{}},                                         //
        "wasm_export__start":      {},                                                         //
        "wasm_export_run":         {Params: []byte{I32, I32}},                                 // argc, argv
        "wasm_export_resume":      {Params: []byte{}},                                         //
@@ -418,9 +419,21 @@ func writeExportSec(ctxt *ld.Link, ldr *loader.Loader, lenHostImports int) {
        switch buildcfg.GOOS {
        case "wasip1":
                writeUleb128(ctxt.Out, uint64(2+len(ldr.WasmExports))) // number of exports
-               s := ldr.Lookup("_rt0_wasm_wasip1", 0)
+               var entry, entryExpName string
+               switch ctxt.BuildMode {
+               case ld.BuildModeExe:
+                       entry = "_rt0_wasm_wasip1"
+                       entryExpName = "_start"
+               case ld.BuildModeCShared:
+                       entry = "_rt0_wasm_wasip1_lib"
+                       entryExpName = "_initialize"
+               }
+               s := ldr.Lookup(entry, 0)
+               if s == 0 {
+                       ld.Errorf(nil, "export symbol %s not defined", entry)
+               }
                idx := uint32(lenHostImports) + uint32(ldr.SymValue(s)>>16) - funcValueOffset
-               writeName(ctxt.Out, "_start")       // the wasi entrypoint
+               writeName(ctxt.Out, entryExpName)   // the wasi entrypoint
                ctxt.Out.WriteByte(0x00)            // func export
                writeUleb128(ctxt.Out, uint64(idx)) // funcidx
                for _, s := range ldr.WasmExports {
@@ -436,6 +449,9 @@ func writeExportSec(ctxt *ld.Link, ldr *loader.Loader, lenHostImports int) {
                writeUleb128(ctxt.Out, 4) // number of exports
                for _, name := range []string{"run", "resume", "getsp"} {
                        s := ldr.Lookup("wasm_export_"+name, 0)
+                       if s == 0 {
+                               ld.Errorf(nil, "export symbol %s not defined", "wasm_export_"+name)
+                       }
                        idx := uint32(lenHostImports) + uint32(ldr.SymValue(s)>>16) - funcValueOffset
                        writeName(ctxt.Out, name)           // inst.exports.run/resume/getsp in wasm_exec.js
                        ctxt.Out.WriteByte(0x00)            // func export
index a774247e6bbc62f031fd68595516142fd7e552ad..193658f878b5819778a3c6bf267658ec868186a6 100644 (file)
@@ -173,7 +173,8 @@ func BuildModeSupported(compiler, buildmode, goos, goarch string) bool {
                        "android/amd64", "android/arm", "android/arm64", "android/386",
                        "freebsd/amd64",
                        "darwin/amd64", "darwin/arm64",
-                       "windows/amd64", "windows/386", "windows/arm64":
+                       "windows/amd64", "windows/386", "windows/arm64",
+                       "wasip1/wasm":
                        return true
                }
                return false
index 419640be2dc8d28f0f432ba3016c0caf33b45a4d..016d2d3825ac49f8780621f21c6118daabdfb7f6 100644 (file)
@@ -608,3 +608,9 @@ outer:
 
 TEXT wasm_export_lib(SB),NOSPLIT,$0
        UNDEF
+
+TEXT runtime·pause(SB), NOSPLIT, $0-8
+       MOVD newsp+0(FP), SP
+       I32Const $1
+       Set PAUSE
+       RETUNWIND
index fcb813df8182b8337e5c69d755d49e85dc978be1..f19e20a4c39806aa92c026d17bfbb661b37c7ad4 100644 (file)
@@ -253,9 +253,6 @@ func clearIdleTimeout() {
        idleTimeout = nil
 }
 
-// pause sets SP to newsp and pauses the execution of Go's WebAssembly code until an event is triggered.
-func pause(newsp uintptr)
-
 // scheduleTimeoutEvent tells the WebAssembly environment to trigger an event after ms milliseconds.
 // It returns a timer id that can be used with clearTimeoutEvent.
 //
index 2cf8a31971f9518e55052b118c4e1a58d1df7dc4..c086c26237da563e33f3b7b13e74632813ee0a04 100644 (file)
@@ -266,6 +266,17 @@ func main() {
        if isarchive || islibrary {
                // A program compiled with -buildmode=c-archive or c-shared
                // has a main, but it is not executed.
+               if GOARCH == "wasm" {
+                       // On Wasm, pause makes it return to the host.
+                       // Unlike cgo callbacks where Ms are created on demand,
+                       // on Wasm we have only one M. So we keep this M (and this
+                       // G) for callbacks.
+                       // Using the caller's SP unwinds this frame and backs to
+                       // goexit. The -16 is: 8 for goexit's (fake) return PC,
+                       // and pause's epilogue pops 8.
+                       pause(getcallersp() - 16) // should not return
+                       panic("unreachable")
+               }
                return
        }
        fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
@@ -5913,7 +5924,9 @@ func checkdead() {
        // For -buildmode=c-shared or -buildmode=c-archive it's OK if
        // there are no running goroutines. The calling program is
        // assumed to be running.
-       if islibrary || isarchive {
+       // One exception is Wasm, which is single-threaded. If we are
+       // in Go and all goroutines are blocked, it deadlocks.
+       if (islibrary || isarchive) && GOARCH != "wasm" {
                return
        }
 
index 34a60474f7a9be86dea02d4b27b56ea2f7540c02..c7a0a2636dce0c206955d108847ba32c76ebd757 100644 (file)
@@ -53,12 +53,6 @@ TEXT wasm_export_getsp(SB),NOSPLIT,$0
        Get SP
        Return
 
-TEXT runtime·pause(SB), NOSPLIT, $0-8
-       MOVD newsp+0(FP), SP
-       I32Const $1
-       Set PAUSE
-       RETUNWIND
-
 TEXT runtime·exit(SB), NOSPLIT, $0-4
        I32Const $0
        Call runtime·wasmExit(SB)
index 6dc239306b6497ea0ae4b71a29434cf34584cbe5..a60566fe06638472a98e4c381bffc163504395f2 100644 (file)
@@ -14,3 +14,7 @@ TEXT _rt0_wasm_wasip1(SB),NOSPLIT,$0
        Call wasm_pc_f_loop(SB)
 
        Return
+
+TEXT _rt0_wasm_wasip1_lib(SB),NOSPLIT,$0
+       Call _rt0_wasm_wasip1(SB)
+       Return
diff --git a/src/runtime/stubs_nonwasm.go b/src/runtime/stubs_nonwasm.go
new file mode 100644 (file)
index 0000000..fa4058b
--- /dev/null
@@ -0,0 +1,10 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !wasm
+
+package runtime
+
+// pause is only used on wasm.
+func pause(newsp uintptr) { panic("unreachable") }
diff --git a/src/runtime/stubs_wasm.go b/src/runtime/stubs_wasm.go
new file mode 100644 (file)
index 0000000..75078b5
--- /dev/null
@@ -0,0 +1,16 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package runtime
+
+// pause sets SP to newsp and pauses the execution of Go's WebAssembly
+// code until an event is triggered, or call back into Go.
+//
+// Note: the epilogue of pause pops 8 bytes from the stack, so when
+// returning to the host, the SP is newsp+8.
+// If we want to set the SP such that when it calls back into Go, the
+// Go function appears to be called from pause's caller's caller, then
+// call pause with newsp = getcallersp()-16 (another 8 is the return
+// PC pushed to the stack).
+func pause(newsp uintptr)