Skip to content

Commit

Permalink
mips: add GOMIPS=softfloat support
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
aykevl authored and deadprogram committed Aug 12, 2024
1 parent 6efc6d2 commit f188eaf
Show file tree
Hide file tree
Showing 9 changed files with 58 additions and 6 deletions.
1 change: 1 addition & 0 deletions builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 7 additions & 2 deletions builder/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand All @@ -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)
})
Expand Down
5 changes: 5 additions & 0 deletions builder/library.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 9 additions & 0 deletions compileopts/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions compileopts/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 15 additions & 1 deletion compileopts/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"
Expand Down
12 changes: 11 additions & 1 deletion goenv/goenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}

Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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"`
Expand All @@ -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(),
Expand Down
8 changes: 6 additions & 2 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}

Expand Down Expand Up @@ -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,
Expand All @@ -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
}
Expand Down

0 comments on commit f188eaf

Please sign in to comment.