From 7cb44fb37324d9d369d04a66bb3a6f43e7c11d0e Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 14 Nov 2021 01:39:34 +0100 Subject: [PATCH] all: add support for GOARM This environment variable can be set to 5, 6, or 7 and controls which ARM version (ARMv5, ARMv6, ARMv7) is used when compiling for GOARCH=arm. I have picked the default value ARMv6, which I believe is supported on most common single board computers including all Raspberry Pis. The difference in code size is pretty big. We could even go further and support ARMv4 if anybody is interested. It should be pretty simple to add this if needed. --- builder/builder_test.go | 4 +++- compileopts/config.go | 6 ++++++ compileopts/options.go | 1 + compileopts/target.go | 37 +++++++++++++++++++++++++++++-------- goenv/goenv.go | 20 ++++++++++++++++++++ main.go | 2 ++ main_test.go | 26 +++++++++++++++++++------- 7 files changed, 80 insertions(+), 16 deletions(-) diff --git a/builder/builder_test.go b/builder/builder_test.go index 3d443dc4..6e08b9ac 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -52,7 +52,9 @@ func TestClangAttributes(t *testing.T) { for _, options := range []*compileopts.Options{ {GOOS: "linux", GOARCH: "386"}, {GOOS: "linux", GOARCH: "amd64"}, - {GOOS: "linux", GOARCH: "arm"}, + {GOOS: "linux", GOARCH: "arm", GOARM: "5"}, + {GOOS: "linux", GOARCH: "arm", GOARM: "6"}, + {GOOS: "linux", GOARCH: "arm", GOARM: "7"}, {GOOS: "linux", GOARCH: "arm64"}, {GOOS: "darwin", GOARCH: "amd64"}, {GOOS: "darwin", GOARCH: "arm64"}, diff --git a/compileopts/config.go b/compileopts/config.go index 2d68b60b..99c43c6f 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -60,6 +60,12 @@ func (c *Config) GOARCH() string { return c.Target.GOARCH } +// GOARM will return the GOARM environment variable given to the compiler when +// building a program. +func (c *Config) GOARM() string { + return c.Options.GOARM +} + // BuildTags returns the complete list of build tags used during this build. func (c *Config) BuildTags() []string { tags := append(c.Target.BuildTags, []string{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...) diff --git a/compileopts/options.go b/compileopts/options.go index 065eb97a..9aca50d1 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -21,6 +21,7 @@ var ( type Options struct { GOOS string // environment variable GOARCH string // environment variable + GOARM string // environment variable (only used with GOARCH=arm) Target string Opt string GC string diff --git a/compileopts/target.go b/compileopts/target.go index 0c8ce1d9..5440536d 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -5,6 +5,7 @@ package compileopts import ( "encoding/json" "errors" + "fmt" "io" "os" "os/exec" @@ -165,13 +166,26 @@ func LoadTarget(options *Options) (*TargetSpec, error) { if options.Target == "" { // Configure based on GOOS/GOARCH environment variables (falling back to // runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it. - llvmarch := map[string]string{ - "386": "i386", - "amd64": "x86_64", - "arm64": "aarch64", - "arm": "armv7", - }[options.GOARCH] - if llvmarch == "" { + var llvmarch string + switch options.GOARCH { + case "386": + llvmarch = "i386" + case "amd64": + llvmarch = "x86_64" + case "arm64": + llvmarch = "aarch64" + case "arm": + switch options.GOARM { + case "5": + llvmarch = "armv5" + case "6": + llvmarch = "armv6" + case "7": + llvmarch = "armv7" + default: + return nil, fmt.Errorf("invalid GOARM=%s, must be 5, 6, or 7", options.GOARM) + } + default: llvmarch = options.GOARCH } llvmos := options.GOOS @@ -245,7 +259,14 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { spec.Features = "+cx8,+fxsr,+mmx,+sse,+sse2,+x87" case "arm": spec.CPU = "generic" - spec.Features = "+armv7-a,+dsp,+fp64,+vfp2,+vfp2sp,+vfp3d16,+vfp3d16sp,-thumb-mode" + switch strings.Split(triple, "-")[0] { + case "armv5": + spec.Features = "+armv5t,+strict-align,-thumb-mode" + case "armv6": + spec.Features = "+armv6,+dsp,+fp64,+strict-align,+vfp2,+vfp2sp,-thumb-mode" + case "armv7": + spec.Features = "+armv7-a,+dsp,+fp64,+vfp2,+vfp2sp,+vfp3d16,+vfp3d16sp,-thumb-mode" + } case "arm64": spec.CPU = "generic" spec.Features = "+neon" diff --git a/goenv/goenv.go b/goenv/goenv.go index 0ef18ba5..6fcd913f 100644 --- a/goenv/goenv.go +++ b/goenv/goenv.go @@ -25,6 +25,12 @@ var Keys = []string{ "TINYGOROOT", } +func init() { + if Get("GOARCH") == "arm" { + Keys = append(Keys, "GOARM") + } +} + // TINYGOROOT is the path to the final location for checking tinygo files. If // unset (by a -X ldflag), then sourceDir() will fallback to the original build // directory. @@ -44,6 +50,20 @@ func Get(name string) string { return dir } return runtime.GOARCH + case "GOARM": + if goarm := os.Getenv("GOARM"); goarm != "" { + return goarm + } + if goos := Get("GOOS"); goos == "windows" || goos == "android" { + // Assume Windows and Android are running on modern CPU cores. + // This matches upstream Go. + return "7" + } + // Default to ARMv6 on other devices. + // The difference between ARMv5 and ARMv6 is big, much bigger than the + // difference between ARMv6 and ARMv7. ARMv6 binaries are much smaller, + // especially when floating point instructions are involved. + return "6" case "GOROOT": return getGoroot() case "GOPATH": diff --git a/main.go b/main.go index af3ed554..0f1d98da 100644 --- a/main.go +++ b/main.go @@ -1192,6 +1192,7 @@ func main() { options := &compileopts.Options{ GOOS: goenv.Get("GOOS"), GOARCH: goenv.Get("GOARCH"), + GOARM: goenv.Get("GOARM"), Target: *target, Opt: *opt, GC: *gc, @@ -1401,6 +1402,7 @@ func main() { fmt.Printf("LLVM triple: %s\n", config.Triple()) fmt.Printf("GOOS: %s\n", config.GOOS()) fmt.Printf("GOARCH: %s\n", config.GOARCH()) + fmt.Printf("GOARM: %s\n", config.GOARM()) fmt.Printf("build tags: %s\n", strings.Join(config.BuildTags(), " ")) fmt.Printf("garbage collector: %s\n", config.GC()) fmt.Printf("scheduler: %s\n", config.Scheduler()) diff --git a/main_test.go b/main_test.go index fe534e15..89985c3e 100644 --- a/main_test.go +++ b/main_test.go @@ -95,13 +95,13 @@ func TestCompiler(t *testing.T) { if runtime.GOOS == "linux" { t.Run("X86Linux", func(t *testing.T) { - runPlatTests(optionsFromOSARCH("linux", "386"), tests, t) + runPlatTests(optionsFromOSARCH("linux/386"), tests, t) }) t.Run("ARMLinux", func(t *testing.T) { - runPlatTests(optionsFromOSARCH("linux", "arm"), tests, t) + runPlatTests(optionsFromOSARCH("linux/arm/6"), tests, t) }) t.Run("ARM64Linux", func(t *testing.T) { - runPlatTests(optionsFromOSARCH("linux", "arm64"), tests, t) + runPlatTests(optionsFromOSARCH("linux/arm64"), tests, t) }) t.Run("WebAssembly", func(t *testing.T) { runPlatTests(optionsFromTarget("wasm"), tests, t) @@ -125,6 +125,7 @@ func TestCompiler(t *testing.T) { runTestWithConfig("stdlib.go", t, compileopts.Options{ GOOS: goenv.Get("GOOS"), GOARCH: goenv.Get("GOARCH"), + GOARM: goenv.Get("GOARM"), Opt: "1", }, nil, nil) }) @@ -136,6 +137,7 @@ func TestCompiler(t *testing.T) { runTestWithConfig("print.go", t, compileopts.Options{ GOOS: goenv.Get("GOOS"), GOARCH: goenv.Get("GOARCH"), + GOARM: goenv.Get("GOARM"), Opt: "0", }, nil, nil) }) @@ -145,6 +147,7 @@ func TestCompiler(t *testing.T) { runTestWithConfig("ldflags.go", t, compileopts.Options{ GOOS: goenv.Get("GOOS"), GOARCH: goenv.Get("GOARCH"), + GOARM: goenv.Get("GOARM"), GlobalValues: map[string]map[string]string{ "main": { "someGlobal": "foobar", @@ -200,15 +203,24 @@ func optionsFromTarget(target string) compileopts.Options { // GOOS/GOARCH are only used if target == "" GOOS: goenv.Get("GOOS"), GOARCH: goenv.Get("GOARCH"), + GOARM: goenv.Get("GOARM"), Target: target, } } -func optionsFromOSARCH(goos, goarch string) compileopts.Options { - return compileopts.Options{ - GOOS: goos, - GOARCH: goarch, +// optionsFromOSARCH returns a set of options based on the "osarch" string. This +// string is in the form of "os/arch/subarch", with the subarch only sometimes +// being necessary. Examples are "darwin/amd64" or "linux/arm/7". +func optionsFromOSARCH(osarch string) compileopts.Options { + parts := strings.Split(osarch, "/") + options := compileopts.Options{ + GOOS: parts[0], + GOARCH: parts[1], } + if options.GOARCH == "arm" { + options.GOARM = parts[2] + } + return options } func runTest(name string, options compileopts.Options, t *testing.T, cmdArgs, environmentVars []string) {