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) {