diff options
Diffstat (limited to 'src/internal/abi')
-rw-r--r-- | src/internal/abi/abi.go | 126 | ||||
-rw-r--r-- | src/internal/abi/abi_amd64.go | 18 | ||||
-rw-r--r-- | src/internal/abi/abi_arm64.go | 17 | ||||
-rw-r--r-- | src/internal/abi/abi_generic.go | 38 | ||||
-rw-r--r-- | src/internal/abi/abi_ppc64x.go | 19 | ||||
-rw-r--r-- | src/internal/abi/abi_riscv64.go | 17 | ||||
-rw-r--r-- | src/internal/abi/abi_test.go | 79 | ||||
-rw-r--r-- | src/internal/abi/abi_test.s | 27 | ||||
-rw-r--r-- | src/internal/abi/export_test.go | 14 | ||||
-rw-r--r-- | src/internal/abi/testdata/x.go | 22 | ||||
-rw-r--r-- | src/internal/abi/testdata/x.s | 6 |
11 files changed, 383 insertions, 0 deletions
diff --git a/src/internal/abi/abi.go b/src/internal/abi/abi.go new file mode 100644 index 0000000..11acac3 --- /dev/null +++ b/src/internal/abi/abi.go @@ -0,0 +1,126 @@ +// Copyright 2020 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 abi + +import ( + "internal/goarch" + "unsafe" +) + +// RegArgs is a struct that has space for each argument +// and return value register on the current architecture. +// +// Assembly code knows the layout of the first two fields +// of RegArgs. +// +// RegArgs also contains additional space to hold pointers +// when it may not be safe to keep them only in the integer +// register space otherwise. +type RegArgs struct { + // Values in these slots should be precisely the bit-by-bit + // representation of how they would appear in a register. + // + // This means that on big endian arches, integer values should + // be in the top bits of the slot. Floats are usually just + // directly represented, but some architectures treat narrow + // width floating point values specially (e.g. they're promoted + // first, or they need to be NaN-boxed). + Ints [IntArgRegs]uintptr // untyped integer registers + Floats [FloatArgRegs]uint64 // untyped float registers + + // Fields above this point are known to assembly. + + // Ptrs is a space that duplicates Ints but with pointer type, + // used to make pointers passed or returned in registers + // visible to the GC by making the type unsafe.Pointer. + Ptrs [IntArgRegs]unsafe.Pointer + + // ReturnIsPtr is a bitmap that indicates which registers + // contain or will contain pointers on the return path from + // a reflectcall. The i'th bit indicates whether the i'th + // register contains or will contain a valid Go pointer. + ReturnIsPtr IntArgRegBitmap +} + +func (r *RegArgs) Dump() { + print("Ints:") + for _, x := range r.Ints { + print(" ", x) + } + println() + print("Floats:") + for _, x := range r.Floats { + print(" ", x) + } + println() + print("Ptrs:") + for _, x := range r.Ptrs { + print(" ", x) + } + println() +} + +// IntRegArgAddr returns a pointer inside of r.Ints[reg] that is appropriately +// offset for an argument of size argSize. +// +// argSize must be non-zero, fit in a register, and a power-of-two. +// +// This method is a helper for dealing with the endianness of different CPU +// architectures, since sub-word-sized arguments in big endian architectures +// need to be "aligned" to the upper edge of the register to be interpreted +// by the CPU correctly. +func (r *RegArgs) IntRegArgAddr(reg int, argSize uintptr) unsafe.Pointer { + if argSize > goarch.PtrSize || argSize == 0 || argSize&(argSize-1) != 0 { + panic("invalid argSize") + } + offset := uintptr(0) + if goarch.BigEndian { + offset = goarch.PtrSize - argSize + } + return unsafe.Pointer(uintptr(unsafe.Pointer(&r.Ints[reg])) + offset) +} + +// IntArgRegBitmap is a bitmap large enough to hold one bit per +// integer argument/return register. +type IntArgRegBitmap [(IntArgRegs + 7) / 8]uint8 + +// Set sets the i'th bit of the bitmap to 1. +func (b *IntArgRegBitmap) Set(i int) { + b[i/8] |= uint8(1) << (i % 8) +} + +// Get returns whether the i'th bit of the bitmap is set. +// +// nosplit because it's called in extremely sensitive contexts, like +// on the reflectcall return path. +// +//go:nosplit +func (b *IntArgRegBitmap) Get(i int) bool { + return b[i/8]&(uint8(1)<<(i%8)) != 0 +} + +// FuncPC* intrinsics. +// +// CAREFUL: In programs with plugins, FuncPC* can return different values +// for the same function (because there are actually multiple copies of +// the same function in the address space). To be safe, don't use the +// results of this function in any == expression. It is only safe to +// use the result as an address at which to start executing code. + +// FuncPCABI0 returns the entry PC of the function f, which must be a +// direct reference of a function defined as ABI0. Otherwise it is a +// compile-time error. +// +// Implemented as a compile intrinsic. +func FuncPCABI0(f any) uintptr + +// FuncPCABIInternal returns the entry PC of the function f. If f is a +// direct reference of a function, it must be defined as ABIInternal. +// Otherwise it is a compile-time error. If f is not a direct reference +// of a defined function, it assumes that f is a func value. Otherwise +// the behavior is undefined. +// +// Implemented as a compile intrinsic. +func FuncPCABIInternal(f any) uintptr diff --git a/src/internal/abi/abi_amd64.go b/src/internal/abi/abi_amd64.go new file mode 100644 index 0000000..d3c5678 --- /dev/null +++ b/src/internal/abi/abi_amd64.go @@ -0,0 +1,18 @@ +// Copyright 2020 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 abi + +const ( + // See abi_generic.go. + + // RAX, RBX, RCX, RDI, RSI, R8, R9, R10, R11. + IntArgRegs = 9 + + // X0 -> X14. + FloatArgRegs = 15 + + // We use SSE2 registers which support 64-bit float operations. + EffectiveFloatRegSize = 8 +) diff --git a/src/internal/abi/abi_arm64.go b/src/internal/abi/abi_arm64.go new file mode 100644 index 0000000..4dc5143 --- /dev/null +++ b/src/internal/abi/abi_arm64.go @@ -0,0 +1,17 @@ +// Copyright 2021 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 abi + +const ( + // See abi_generic.go. + + // R0 - R15. + IntArgRegs = 16 + + // F0 - F15. + FloatArgRegs = 16 + + EffectiveFloatRegSize = 8 +) diff --git a/src/internal/abi/abi_generic.go b/src/internal/abi/abi_generic.go new file mode 100644 index 0000000..76ef2e2 --- /dev/null +++ b/src/internal/abi/abi_generic.go @@ -0,0 +1,38 @@ +// Copyright 2020 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 !goexperiment.regabiargs && !amd64 && !arm64 && !ppc64 && !ppc64le && !riscv64 + +package abi + +const ( + // ABI-related constants. + // + // In the generic case, these are all zero + // which lets them gracefully degrade to ABI0. + + // IntArgRegs is the number of registers dedicated + // to passing integer argument values. Result registers are identical + // to argument registers, so this number is used for those too. + IntArgRegs = 0 + + // FloatArgRegs is the number of registers dedicated + // to passing floating-point argument values. Result registers are + // identical to argument registers, so this number is used for + // those too. + FloatArgRegs = 0 + + // EffectiveFloatRegSize describes the width of floating point + // registers on the current platform from the ABI's perspective. + // + // Since Go only supports 32-bit and 64-bit floating point primitives, + // this number should be either 0, 4, or 8. 0 indicates no floating + // point registers for the ABI or that floating point values will be + // passed via the softfloat ABI. + // + // For platforms that support larger floating point register widths, + // such as x87's 80-bit "registers" (not that we support x87 currently), + // use 8. + EffectiveFloatRegSize = 0 +) diff --git a/src/internal/abi/abi_ppc64x.go b/src/internal/abi/abi_ppc64x.go new file mode 100644 index 0000000..73416d7 --- /dev/null +++ b/src/internal/abi/abi_ppc64x.go @@ -0,0 +1,19 @@ +// Copyright 2021 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 ppc64 || ppc64le + +package abi + +const ( + // See abi_generic.go. + + // R3 - R10, R14 - R17. + IntArgRegs = 12 + + // F1 - F12. + FloatArgRegs = 12 + + EffectiveFloatRegSize = 8 +) diff --git a/src/internal/abi/abi_riscv64.go b/src/internal/abi/abi_riscv64.go new file mode 100644 index 0000000..2bcd9d6 --- /dev/null +++ b/src/internal/abi/abi_riscv64.go @@ -0,0 +1,17 @@ +// Copyright 2022 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 abi + +const ( + // See abi_generic.go. + + // X8 - X23 + IntArgRegs = 16 + + // F8 - F23. + FloatArgRegs = 16 + + EffectiveFloatRegSize = 8 +) diff --git a/src/internal/abi/abi_test.go b/src/internal/abi/abi_test.go new file mode 100644 index 0000000..f0d8dce --- /dev/null +++ b/src/internal/abi/abi_test.go @@ -0,0 +1,79 @@ +// Copyright 2021 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 abi_test + +import ( + "internal/abi" + "internal/testenv" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +func TestFuncPC(t *testing.T) { + // Test that FuncPC* can get correct function PC. + pcFromAsm := abi.FuncPCTestFnAddr + + // Test FuncPC for locally defined function + pcFromGo := abi.FuncPCTest() + if pcFromGo != pcFromAsm { + t.Errorf("FuncPC returns wrong PC, want %x, got %x", pcFromAsm, pcFromGo) + } + + // Test FuncPC for imported function + pcFromGo = abi.FuncPCABI0(abi.FuncPCTestFn) + if pcFromGo != pcFromAsm { + t.Errorf("FuncPC returns wrong PC, want %x, got %x", pcFromAsm, pcFromGo) + } +} + +func TestFuncPCCompileError(t *testing.T) { + // Test that FuncPC* on a function of a mismatched ABI is rejected. + testenv.MustHaveGoBuild(t) + + // We want to test internal package, which we cannot normally import. + // Run the assembler and compiler manually. + tmpdir := t.TempDir() + asmSrc := filepath.Join("testdata", "x.s") + goSrc := filepath.Join("testdata", "x.go") + symabi := filepath.Join(tmpdir, "symabi") + obj := filepath.Join(tmpdir, "x.o") + + importcfgfile := filepath.Join(tmpdir, "hello.importcfg") + testenv.WriteImportcfg(t, importcfgfile, nil) + + // parse assembly code for symabi. + cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-gensymabis", "-o", symabi, asmSrc) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("go tool asm -gensymabis failed: %v\n%s", err, out) + } + + // compile go code. + cmd = exec.Command(testenv.GoToolPath(t), "tool", "compile", "-importcfg="+importcfgfile, "-p=p", "-symabis", symabi, "-o", obj, goSrc) + out, err = cmd.CombinedOutput() + if err == nil { + t.Fatalf("go tool compile did not fail") + } + + // Expect errors in line 17, 18, 20, no errors on other lines. + want := []string{"x.go:17", "x.go:18", "x.go:20"} + got := strings.Split(string(out), "\n") + if got[len(got)-1] == "" { + got = got[:len(got)-1] // remove last empty line + } + for i, s := range got { + if !strings.Contains(s, want[i]) { + t.Errorf("did not error on line %s", want[i]) + } + } + if len(got) != len(want) { + t.Errorf("unexpected number of errors, want %d, got %d", len(want), len(got)) + } + if t.Failed() { + t.Logf("output:\n%s", string(out)) + } +} diff --git a/src/internal/abi/abi_test.s b/src/internal/abi/abi_test.s new file mode 100644 index 0000000..93ace3e --- /dev/null +++ b/src/internal/abi/abi_test.s @@ -0,0 +1,27 @@ +// Copyright 2021 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. + +#include "textflag.h" + +#ifdef GOARCH_386 +#define PTRSIZE 4 +#endif +#ifdef GOARCH_arm +#define PTRSIZE 4 +#endif +#ifdef GOARCH_mips +#define PTRSIZE 4 +#endif +#ifdef GOARCH_mipsle +#define PTRSIZE 4 +#endif +#ifndef PTRSIZE +#define PTRSIZE 8 +#endif + +TEXT internal∕abi·FuncPCTestFn(SB),NOSPLIT,$0-0 + RET + +GLOBL internal∕abi·FuncPCTestFnAddr(SB), NOPTR, $PTRSIZE +DATA internal∕abi·FuncPCTestFnAddr(SB)/PTRSIZE, $internal∕abi·FuncPCTestFn(SB) diff --git a/src/internal/abi/export_test.go b/src/internal/abi/export_test.go new file mode 100644 index 0000000..2a87e9d --- /dev/null +++ b/src/internal/abi/export_test.go @@ -0,0 +1,14 @@ +// Copyright 2021 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 abi + +func FuncPCTestFn() + +var FuncPCTestFnAddr uintptr // address of FuncPCTestFn, directly retrieved from assembly + +//go:noinline +func FuncPCTest() uintptr { + return FuncPCABI0(FuncPCTestFn) +} diff --git a/src/internal/abi/testdata/x.go b/src/internal/abi/testdata/x.go new file mode 100644 index 0000000..cae103d --- /dev/null +++ b/src/internal/abi/testdata/x.go @@ -0,0 +1,22 @@ +// Copyright 2021 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 x + +import "internal/abi" + +func Fn0() // defined in assembly + +func Fn1() {} + +var FnExpr func() + +func test() { + _ = abi.FuncPCABI0(Fn0) // line 16, no error + _ = abi.FuncPCABIInternal(Fn0) // line 17, error + _ = abi.FuncPCABI0(Fn1) // line 18, error + _ = abi.FuncPCABIInternal(Fn1) // line 19, no error + _ = abi.FuncPCABI0(FnExpr) // line 20, error + _ = abi.FuncPCABIInternal(FnExpr) // line 21, no error +} diff --git a/src/internal/abi/testdata/x.s b/src/internal/abi/testdata/x.s new file mode 100644 index 0000000..63c1385 --- /dev/null +++ b/src/internal/abi/testdata/x.s @@ -0,0 +1,6 @@ +// Copyright 2021 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. + +TEXT ·Fn0(SB), 0, $0-0 + RET |