From f188eaf5f92618adc6422c2b55db8f822c6cc458 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 30 Jul 2024 19:08:55 +0200 Subject: [PATCH] mips: add GOMIPS=softfloat support Previously, the compiler would default to hardfloat. This is not supported by some MIPS CPUs. This took me much longer than it should have because of a quirk in the LLVM Mips backend: if the target-features string is not set (like during LTO), the Mips backend picks the first function in the module and uses that. Unfortunately, in the case of TinyGo this first function is `llvm.dbg.value`, which is an LLVM intrinsic and doesn't have the target-features string. I fixed it by adding a `-mllvm -mattr=` flag to the linker. --- builder/build.go | 1 + builder/builder_test.go | 9 +++++++-- builder/library.go | 5 +++++ compileopts/config.go | 9 +++++++++ compileopts/options.go | 1 + compileopts/target.go | 16 +++++++++++++++- goenv/goenv.go | 12 +++++++++++- main.go | 3 +++ main_test.go | 8 ++++++-- 9 files changed, 58 insertions(+), 6 deletions(-) diff --git a/builder/build.go b/builder/build.go index 33c3b2858..f728cde79 100644 --- a/builder/build.go +++ b/builder/build.go @@ -746,6 +746,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe ldflags = append(ldflags, dependency.result) } ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU()) + ldflags = append(ldflags, "-mllvm", "-mattr="+config.Features()) // needed for MIPS softfloat if config.GOOS() == "windows" { // Options for the MinGW wrapper for the lld COFF linker. ldflags = append(ldflags, diff --git a/builder/builder_test.go b/builder/builder_test.go index 1b3e76ded..92ed9bdcc 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -57,8 +57,10 @@ func TestClangAttributes(t *testing.T) { {GOOS: "linux", GOARCH: "arm", GOARM: "6"}, {GOOS: "linux", GOARCH: "arm", GOARM: "7"}, {GOOS: "linux", GOARCH: "arm64"}, - {GOOS: "linux", GOARCH: "mips"}, - {GOOS: "linux", GOARCH: "mipsle"}, + {GOOS: "linux", GOARCH: "mips", GOMIPS: "hardfloat"}, + {GOOS: "linux", GOARCH: "mipsle", GOMIPS: "hardfloat"}, + {GOOS: "linux", GOARCH: "mips", GOMIPS: "softfloat"}, + {GOOS: "linux", GOARCH: "mipsle", GOMIPS: "softfloat"}, {GOOS: "darwin", GOARCH: "amd64"}, {GOOS: "darwin", GOARCH: "arm64"}, {GOOS: "windows", GOARCH: "amd64"}, @@ -69,6 +71,9 @@ func TestClangAttributes(t *testing.T) { if options.GOARCH == "arm" { name += ",GOARM=" + options.GOARM } + if options.GOARCH == "mips" || options.GOARCH == "mipsle" { + name += ",GOMIPS=" + options.GOMIPS + } t.Run(name, func(t *testing.T) { testClangAttributes(t, options) }) diff --git a/builder/library.go b/builder/library.go index 0ab7fa0e4..b8d183414 100644 --- a/builder/library.go +++ b/builder/library.go @@ -169,6 +169,11 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ case "mips": args = append(args, "-fno-pic") } + if config.Target.SoftFloat { + // Use softfloat instead of floating point instructions. This is + // supported on many architectures. + args = append(args, "-msoft-float") + } var once sync.Once diff --git a/compileopts/config.go b/compileopts/config.go index a5ab7cd8f..67c773de1 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -72,6 +72,12 @@ func (c *Config) GOARM() string { return c.Options.GOARM } +// GOMIPS will return the GOMIPS environment variable given to the compiler when +// building a program. +func (c *Config) GOMIPS() string { + return c.Options.GOMIPS +} + // BuildTags returns the complete list of build tags used during this build. func (c *Config) BuildTags() []string { tags := append([]string(nil), c.Target.BuildTags...) // copy slice (avoid a race) @@ -240,6 +246,9 @@ func (c *Config) LibcPath(name string) (path string, precompiled bool) { if c.ABI() != "" { archname += "-" + c.ABI() } + if c.Target.SoftFloat { + archname += "-softfloat" + } // Try to load a precompiled library. precompiledDir := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", archname, name) diff --git a/compileopts/options.go b/compileopts/options.go index 8b0e2266d..9601ae322 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -23,6 +23,7 @@ type Options struct { GOOS string // environment variable GOARCH string // environment variable GOARM string // environment variable (only used with GOARCH=arm) + GOMIPS string // environment variable (only used with GOARCH=mips and GOARCH=mipsle) Directory string // working dir, leave it unset to use the current working dir Target string Opt string diff --git a/compileopts/target.go b/compileopts/target.go index ecbebd8b3..9388b8496 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -30,6 +30,7 @@ type TargetSpec struct { Features string `json:"features,omitempty"` GOOS string `json:"goos,omitempty"` GOARCH string `json:"goarch,omitempty"` + SoftFloat bool // used for non-baremetal systems (GOMIPS=softfloat etc) BuildTags []string `json:"build-tags,omitempty"` GC string `json:"gc,omitempty"` Scheduler string `json:"scheduler,omitempty"` @@ -86,6 +87,10 @@ func (spec *TargetSpec) overrideProperties(child *TargetSpec) error { if src.Uint() != 0 { dst.Set(src) } + case reflect.Bool: + if src.Bool() { + dst.Set(src) + } case reflect.Ptr: // for pointers, copy if not nil if !src.IsNil() { dst.Set(src) @@ -290,13 +295,22 @@ func defaultTarget(options *Options) (*TargetSpec, error) { } case "mips", "mipsle": spec.CPU = "mips32r2" - spec.Features = "+fpxx,+mips32r2,+nooddspreg,-noabicalls" spec.CFlags = append(spec.CFlags, "-fno-pic") if options.GOOS == "mips" { llvmarch = "mips" // big endian } else { llvmarch = "mipsel" // little endian } + switch options.GOMIPS { + case "hardfloat": + spec.Features = "+fpxx,+mips32r2,+nooddspreg,-noabicalls" + case "softfloat": + spec.SoftFloat = true + spec.Features = "+mips32r2,+soft-float,-noabicalls" + spec.CFlags = append(spec.CFlags, "-msoft-float") + default: + return nil, fmt.Errorf("invalid GOMIPS=%s: must be hardfloat or softfloat", options.GOMIPS) + } case "wasm": llvmarch = "wasm32" spec.CPU = "generic" diff --git a/goenv/goenv.go b/goenv/goenv.go index 187bcc6df..fe4c8bf63 100644 --- a/goenv/goenv.go +++ b/goenv/goenv.go @@ -30,8 +30,11 @@ var Keys = []string{ } func init() { - if Get("GOARCH") == "arm" { + switch Get("GOARCH") { + case "arm": Keys = append(Keys, "GOARM") + case "mips", "mipsle": + Keys = append(Keys, "GOMIPS") } } @@ -128,6 +131,13 @@ func Get(name string) string { // difference between ARMv6 and ARMv7. ARMv6 binaries are much smaller, // especially when floating point instructions are involved. return "6" + case "GOMIPS": + gomips := os.Getenv("GOMIPS") + if gomips == "" { + // Default to hardfloat (this matches the Go toolchain). + gomips = "hardfloat" + } + return gomips case "GOROOT": readGoEnvVars() return goEnvVars.GOROOT diff --git a/main.go b/main.go index 32fc22665..cbe4b9dd4 100644 --- a/main.go +++ b/main.go @@ -1499,6 +1499,7 @@ func main() { GOOS: goenv.Get("GOOS"), GOARCH: goenv.Get("GOARCH"), GOARM: goenv.Get("GOARM"), + GOMIPS: goenv.Get("GOMIPS"), Target: *target, StackSize: stackSize, Opt: *opt, @@ -1737,6 +1738,7 @@ func main() { GOOS string `json:"goos"` GOARCH string `json:"goarch"` GOARM string `json:"goarm"` + GOMIPS string `json:"gomips"` BuildTags []string `json:"build_tags"` GC string `json:"garbage_collector"` Scheduler string `json:"scheduler"` @@ -1747,6 +1749,7 @@ func main() { GOOS: config.GOOS(), GOARCH: config.GOARCH(), GOARM: config.GOARM(), + GOMIPS: config.GOMIPS(), BuildTags: config.BuildTags(), GC: config.GC(), Scheduler: config.Scheduler(), diff --git a/main_test.go b/main_test.go index ccd490d62..4c11f7b18 100644 --- a/main_test.go +++ b/main_test.go @@ -36,7 +36,7 @@ var supportedLinuxArches = map[string]string{ "X86Linux": "linux/386", "ARMLinux": "linux/arm/6", "ARM64Linux": "linux/arm64", - "MIPSLinux": "linux/mipsle", + "MIPSLinux": "linux/mipsle/hardfloat", "WASIp1": "wasip1/wasm", } @@ -325,6 +325,7 @@ func optionsFromTarget(target string, sema chan struct{}) compileopts.Options { GOOS: goenv.Get("GOOS"), GOARCH: goenv.Get("GOARCH"), GOARM: goenv.Get("GOARM"), + GOMIPS: goenv.Get("GOMIPS"), Target: target, Semaphore: sema, InterpTimeout: 180 * time.Second, @@ -348,8 +349,11 @@ func optionsFromOSARCH(osarch string, sema chan struct{}) compileopts.Options { VerifyIR: true, Opt: "z", } - if options.GOARCH == "arm" { + switch options.GOARCH { + case "arm": options.GOARM = parts[2] + case "mips", "mipsle": + options.GOMIPS = parts[2] } return options }