diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:25:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:25:22 +0000 |
commit | f6ad4dcef54c5ce997a4bad5a6d86de229015700 (patch) | |
tree | 7cfa4e31ace5c2bd95c72b154d15af494b2bcbef /src/cmd/compile/internal/ssagen/abi.go | |
parent | Initial commit. (diff) | |
download | golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.tar.xz golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.zip |
Adding upstream version 1.22.1.upstream/1.22.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/compile/internal/ssagen/abi.go')
-rw-r--r-- | src/cmd/compile/internal/ssagen/abi.go | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/ssagen/abi.go b/src/cmd/compile/internal/ssagen/abi.go new file mode 100644 index 0000000..56af9ce --- /dev/null +++ b/src/cmd/compile/internal/ssagen/abi.go @@ -0,0 +1,440 @@ +// Copyright 2009 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 ssagen + +import ( + "fmt" + "internal/buildcfg" + "log" + "os" + "strings" + + "cmd/compile/internal/abi" + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/objw" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/obj" + "cmd/internal/obj/wasm" +) + +// SymABIs records information provided by the assembler about symbol +// definition ABIs and reference ABIs. +type SymABIs struct { + defs map[string]obj.ABI + refs map[string]obj.ABISet +} + +func NewSymABIs() *SymABIs { + return &SymABIs{ + defs: make(map[string]obj.ABI), + refs: make(map[string]obj.ABISet), + } +} + +// canonicalize returns the canonical name used for a linker symbol in +// s's maps. Symbols in this package may be written either as "".X or +// with the package's import path already in the symbol. This rewrites +// both to use the full path, which matches compiler-generated linker +// symbol names. +func (s *SymABIs) canonicalize(linksym string) string { + if strings.HasPrefix(linksym, `"".`) { + panic("non-canonical symbol name: " + linksym) + } + return linksym +} + +// ReadSymABIs reads a symabis file that specifies definitions and +// references of text symbols by ABI. +// +// The symabis format is a set of lines, where each line is a sequence +// of whitespace-separated fields. The first field is a verb and is +// either "def" for defining a symbol ABI or "ref" for referencing a +// symbol using an ABI. For both "def" and "ref", the second field is +// the symbol name and the third field is the ABI name, as one of the +// named cmd/internal/obj.ABI constants. +func (s *SymABIs) ReadSymABIs(file string) { + data, err := os.ReadFile(file) + if err != nil { + log.Fatalf("-symabis: %v", err) + } + + for lineNum, line := range strings.Split(string(data), "\n") { + lineNum++ // 1-based + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + parts := strings.Fields(line) + switch parts[0] { + case "def", "ref": + // Parse line. + if len(parts) != 3 { + log.Fatalf(`%s:%d: invalid symabi: syntax is "%s sym abi"`, file, lineNum, parts[0]) + } + sym, abistr := parts[1], parts[2] + abi, valid := obj.ParseABI(abistr) + if !valid { + log.Fatalf(`%s:%d: invalid symabi: unknown abi "%s"`, file, lineNum, abistr) + } + + sym = s.canonicalize(sym) + + // Record for later. + if parts[0] == "def" { + s.defs[sym] = abi + } else { + s.refs[sym] |= obj.ABISetOf(abi) + } + default: + log.Fatalf(`%s:%d: invalid symabi type "%s"`, file, lineNum, parts[0]) + } + } +} + +// GenABIWrappers applies ABI information to Funcs and generates ABI +// wrapper functions where necessary. +func (s *SymABIs) GenABIWrappers() { + // For cgo exported symbols, we tell the linker to export the + // definition ABI to C. That also means that we don't want to + // create ABI wrappers even if there's a linkname. + // + // TODO(austin): Maybe we want to create the ABI wrappers, but + // ensure the linker exports the right ABI definition under + // the unmangled name? + cgoExports := make(map[string][]*[]string) + for i, prag := range typecheck.Target.CgoPragmas { + switch prag[0] { + case "cgo_export_static", "cgo_export_dynamic": + symName := s.canonicalize(prag[1]) + pprag := &typecheck.Target.CgoPragmas[i] + cgoExports[symName] = append(cgoExports[symName], pprag) + } + } + + // Apply ABI defs and refs to Funcs and generate wrappers. + // + // This may generate new decls for the wrappers, but we + // specifically *don't* want to visit those, lest we create + // wrappers for wrappers. + for _, fn := range typecheck.Target.Funcs { + nam := fn.Nname + if ir.IsBlank(nam) { + continue + } + sym := nam.Sym() + + symName := sym.Linkname + if symName == "" { + symName = sym.Pkg.Prefix + "." + sym.Name + } + symName = s.canonicalize(symName) + + // Apply definitions. + defABI, hasDefABI := s.defs[symName] + if hasDefABI { + if len(fn.Body) != 0 { + base.ErrorfAt(fn.Pos(), 0, "%v defined in both Go and assembly", fn) + } + fn.ABI = defABI + } + + if fn.Pragma&ir.CgoUnsafeArgs != 0 { + // CgoUnsafeArgs indicates the function (or its callee) uses + // offsets to dispatch arguments, which currently using ABI0 + // frame layout. Pin it to ABI0. + fn.ABI = obj.ABI0 + } + + // If cgo-exported, add the definition ABI to the cgo + // pragmas. + cgoExport := cgoExports[symName] + for _, pprag := range cgoExport { + // The export pragmas have the form: + // + // cgo_export_* <local> [<remote>] + // + // If <remote> is omitted, it's the same as + // <local>. + // + // Expand to + // + // cgo_export_* <local> <remote> <ABI> + if len(*pprag) == 2 { + *pprag = append(*pprag, (*pprag)[1]) + } + // Add the ABI argument. + *pprag = append(*pprag, fn.ABI.String()) + } + + // Apply references. + if abis, ok := s.refs[symName]; ok { + fn.ABIRefs |= abis + } + // Assume all functions are referenced at least as + // ABIInternal, since they may be referenced from + // other packages. + fn.ABIRefs.Set(obj.ABIInternal, true) + + // If a symbol is defined in this package (either in + // Go or assembly) and given a linkname, it may be + // referenced from another package, so make it + // callable via any ABI. It's important that we know + // it's defined in this package since other packages + // may "pull" symbols using linkname and we don't want + // to create duplicate ABI wrappers. + // + // However, if it's given a linkname for exporting to + // C, then we don't make ABI wrappers because the cgo + // tool wants the original definition. + hasBody := len(fn.Body) != 0 + if sym.Linkname != "" && (hasBody || hasDefABI) && len(cgoExport) == 0 { + fn.ABIRefs |= obj.ABISetCallable + } + + // Double check that cgo-exported symbols don't get + // any wrappers. + if len(cgoExport) > 0 && fn.ABIRefs&^obj.ABISetOf(fn.ABI) != 0 { + base.Fatalf("cgo exported function %v cannot have ABI wrappers", fn) + } + + if !buildcfg.Experiment.RegabiWrappers { + continue + } + + forEachWrapperABI(fn, makeABIWrapper) + } +} + +func forEachWrapperABI(fn *ir.Func, cb func(fn *ir.Func, wrapperABI obj.ABI)) { + need := fn.ABIRefs &^ obj.ABISetOf(fn.ABI) + if need == 0 { + return + } + + for wrapperABI := obj.ABI(0); wrapperABI < obj.ABICount; wrapperABI++ { + if !need.Get(wrapperABI) { + continue + } + cb(fn, wrapperABI) + } +} + +// makeABIWrapper creates a new function that will be called with +// wrapperABI and calls "f" using f.ABI. +func makeABIWrapper(f *ir.Func, wrapperABI obj.ABI) { + if base.Debug.ABIWrap != 0 { + fmt.Fprintf(os.Stderr, "=-= %v to %v wrapper for %v\n", wrapperABI, f.ABI, f) + } + + // Q: is this needed? + savepos := base.Pos + savedcurfn := ir.CurFunc + + pos := base.AutogeneratedPos + base.Pos = pos + + // At the moment we don't support wrapping a method, we'd need machinery + // below to handle the receiver. Panic if we see this scenario. + ft := f.Nname.Type() + if ft.NumRecvs() != 0 { + base.ErrorfAt(f.Pos(), 0, "makeABIWrapper support for wrapping methods not implemented") + return + } + + // Reuse f's types.Sym to create a new ODCLFUNC/function. + // TODO(mdempsky): Means we can't set sym.Def in Declfunc, ugh. + fn := ir.NewFunc(pos, pos, f.Sym(), types.NewSignature(nil, + typecheck.NewFuncParams(ft.Params()), + typecheck.NewFuncParams(ft.Results()))) + fn.ABI = wrapperABI + typecheck.DeclFunc(fn) + + fn.SetABIWrapper(true) + fn.SetDupok(true) + + // ABI0-to-ABIInternal wrappers will be mainly loading params from + // stack into registers (and/or storing stack locations back to + // registers after the wrapped call); in most cases they won't + // need to allocate stack space, so it should be OK to mark them + // as NOSPLIT in these cases. In addition, my assumption is that + // functions written in assembly are NOSPLIT in most (but not all) + // cases. In the case of an ABIInternal target that has too many + // parameters to fit into registers, the wrapper would need to + // allocate stack space, but this seems like an unlikely scenario. + // Hence: mark these wrappers NOSPLIT. + // + // ABIInternal-to-ABI0 wrappers on the other hand will be taking + // things in registers and pushing them onto the stack prior to + // the ABI0 call, meaning that they will always need to allocate + // stack space. If the compiler marks them as NOSPLIT this seems + // as though it could lead to situations where the linker's + // nosplit-overflow analysis would trigger a link failure. On the + // other hand if they not tagged NOSPLIT then this could cause + // problems when building the runtime (since there may be calls to + // asm routine in cases where it's not safe to grow the stack). In + // most cases the wrapper would be (in effect) inlined, but are + // there (perhaps) indirect calls from the runtime that could run + // into trouble here. + // FIXME: at the moment all.bash does not pass when I leave out + // NOSPLIT for these wrappers, so all are currently tagged with NOSPLIT. + fn.Pragma |= ir.Nosplit + + // Generate call. Use tail call if no params and no returns, + // but a regular call otherwise. + // + // Note: ideally we would be using a tail call in cases where + // there are params but no returns for ABI0->ABIInternal wrappers, + // provided that all params fit into registers (e.g. we don't have + // to allocate any stack space). Doing this will require some + // extra work in typecheck/walk/ssa, might want to add a new node + // OTAILCALL or something to this effect. + tailcall := fn.Type().NumResults() == 0 && fn.Type().NumParams() == 0 && fn.Type().NumRecvs() == 0 + if base.Ctxt.Arch.Name == "ppc64le" && base.Ctxt.Flag_dynlink { + // cannot tailcall on PPC64 with dynamic linking, as we need + // to restore R2 after call. + tailcall = false + } + if base.Ctxt.Arch.Name == "amd64" && wrapperABI == obj.ABIInternal { + // cannot tailcall from ABIInternal to ABI0 on AMD64, as we need + // to special registers (X15) when returning to ABIInternal. + tailcall = false + } + + var tail ir.Node + call := ir.NewCallExpr(base.Pos, ir.OCALL, f.Nname, nil) + call.Args = ir.ParamNames(fn.Type()) + call.IsDDD = fn.Type().IsVariadic() + tail = call + if tailcall { + tail = ir.NewTailCallStmt(base.Pos, call) + } else if fn.Type().NumResults() > 0 { + n := ir.NewReturnStmt(base.Pos, nil) + n.Results = []ir.Node{call} + tail = n + } + fn.Body.Append(tail) + + typecheck.FinishFuncBody() + + ir.CurFunc = fn + typecheck.Stmts(fn.Body) + + // Restore previous context. + base.Pos = savepos + ir.CurFunc = savedcurfn +} + +// CreateWasmImportWrapper creates a wrapper for imported WASM functions to +// adapt them to the Go calling convention. The body for this function is +// generated in cmd/internal/obj/wasm/wasmobj.go +func CreateWasmImportWrapper(fn *ir.Func) bool { + if fn.WasmImport == nil { + return false + } + if buildcfg.GOARCH != "wasm" { + base.FatalfAt(fn.Pos(), "CreateWasmImportWrapper call not supported on %s: func was %v", buildcfg.GOARCH, fn) + } + + ir.InitLSym(fn, true) + + setupWasmABI(fn) + + pp := objw.NewProgs(fn, 0) + defer pp.Free() + pp.Text.To.Type = obj.TYPE_TEXTSIZE + pp.Text.To.Val = int32(types.RoundUp(fn.Type().ArgWidth(), int64(types.RegSize))) + // Wrapper functions never need their own stack frame + pp.Text.To.Offset = 0 + pp.Flush() + + return true +} + +func paramsToWasmFields(f *ir.Func, result *abi.ABIParamResultInfo, abiParams []abi.ABIParamAssignment) []obj.WasmField { + wfs := make([]obj.WasmField, len(abiParams)) + for i, p := range abiParams { + t := p.Type + switch t.Kind() { + case types.TINT32, types.TUINT32: + wfs[i].Type = obj.WasmI32 + case types.TINT64, types.TUINT64: + wfs[i].Type = obj.WasmI64 + case types.TFLOAT32: + wfs[i].Type = obj.WasmF32 + case types.TFLOAT64: + wfs[i].Type = obj.WasmF64 + case types.TUNSAFEPTR: + wfs[i].Type = obj.WasmPtr + default: + base.ErrorfAt(f.Pos(), 0, "go:wasmimport %s %s: unsupported parameter type %s", f.WasmImport.Module, f.WasmImport.Name, t.String()) + } + wfs[i].Offset = p.FrameOffset(result) + } + return wfs +} + +func resultsToWasmFields(f *ir.Func, result *abi.ABIParamResultInfo, abiParams []abi.ABIParamAssignment) []obj.WasmField { + if len(abiParams) > 1 { + base.ErrorfAt(f.Pos(), 0, "go:wasmimport %s %s: too many return values", f.WasmImport.Module, f.WasmImport.Name) + return nil + } + wfs := make([]obj.WasmField, len(abiParams)) + for i, p := range abiParams { + t := p.Type + switch t.Kind() { + case types.TINT32, types.TUINT32: + wfs[i].Type = obj.WasmI32 + case types.TINT64, types.TUINT64: + wfs[i].Type = obj.WasmI64 + case types.TFLOAT32: + wfs[i].Type = obj.WasmF32 + case types.TFLOAT64: + wfs[i].Type = obj.WasmF64 + default: + base.ErrorfAt(f.Pos(), 0, "go:wasmimport %s %s: unsupported result type %s", f.WasmImport.Module, f.WasmImport.Name, t.String()) + } + wfs[i].Offset = p.FrameOffset(result) + } + return wfs +} + +// setupTextLSym initializes the LSym for a with-body text symbol. +func setupWasmABI(f *ir.Func) { + wi := obj.WasmImport{ + Module: f.WasmImport.Module, + Name: f.WasmImport.Name, + } + if wi.Module == wasm.GojsModule { + // Functions that are imported from the "gojs" module use a special + // ABI that just accepts the stack pointer. + // Example: + // + // //go:wasmimport gojs add + // func importedAdd(a, b uint) uint + // + // will roughly become + // + // (import "gojs" "add" (func (param i32))) + wi.Params = []obj.WasmField{{Type: obj.WasmI32}} + } else { + // All other imported functions use the normal WASM ABI. + // Example: + // + // //go:wasmimport a_module add + // func importedAdd(a, b uint) uint + // + // will roughly become + // + // (import "a_module" "add" (func (param i32 i32) (result i32))) + abiConfig := AbiForBodylessFuncStackMap(f) + abiInfo := abiConfig.ABIAnalyzeFuncType(f.Type()) + wi.Params = paramsToWasmFields(f, abiInfo, abiInfo.InParams()) + wi.Results = resultsToWasmFields(f, abiInfo, abiInfo.OutParams()) + } + f.LSym.Func().WasmImport = &wi +} |