github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/builder/builder_test.go (about) 1 package builder 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "runtime" 8 "testing" 9 10 "github.com/tinygo-org/tinygo/compileopts" 11 "tinygo.org/x/go-llvm" 12 ) 13 14 // Test whether the Clang generated "target-cpu" and "target-features" 15 // attributes match the CPU and Features property in TinyGo target files. 16 func TestClangAttributes(t *testing.T) { 17 var targetNames = []string{ 18 // Please keep this list sorted! 19 "atmega328p", 20 "atmega1280", 21 "atmega1284p", 22 "atmega2560", 23 "attiny85", 24 "cortex-m0", 25 "cortex-m0plus", 26 "cortex-m3", 27 "cortex-m33", 28 "cortex-m4", 29 "cortex-m7", 30 "esp32c3", 31 "fe310", 32 "gameboy-advance", 33 "k210", 34 "nintendoswitch", 35 "riscv-qemu", 36 "wasip1", 37 "wasm", 38 "wasm-unknown", 39 } 40 if hasBuiltinTools { 41 // hasBuiltinTools is set when TinyGo is statically linked with LLVM, 42 // which also implies it was built with Xtensa support. 43 targetNames = append(targetNames, "esp32", "esp8266") 44 } 45 for _, targetName := range targetNames { 46 targetName := targetName 47 t.Run(targetName, func(t *testing.T) { 48 testClangAttributes(t, &compileopts.Options{Target: targetName}) 49 }) 50 } 51 52 for _, options := range []*compileopts.Options{ 53 {GOOS: "linux", GOARCH: "386"}, 54 {GOOS: "linux", GOARCH: "amd64"}, 55 {GOOS: "linux", GOARCH: "arm", GOARM: "5"}, 56 {GOOS: "linux", GOARCH: "arm", GOARM: "6"}, 57 {GOOS: "linux", GOARCH: "arm", GOARM: "7"}, 58 {GOOS: "linux", GOARCH: "arm64"}, 59 {GOOS: "darwin", GOARCH: "amd64"}, 60 {GOOS: "darwin", GOARCH: "arm64"}, 61 {GOOS: "windows", GOARCH: "amd64"}, 62 {GOOS: "windows", GOARCH: "arm64"}, 63 {GOOS: "wasip1", GOARCH: "wasm"}, 64 } { 65 name := "GOOS=" + options.GOOS + ",GOARCH=" + options.GOARCH 66 if options.GOARCH == "arm" { 67 name += ",GOARM=" + options.GOARM 68 } 69 t.Run(name, func(t *testing.T) { 70 testClangAttributes(t, options) 71 }) 72 } 73 } 74 75 func testClangAttributes(t *testing.T, options *compileopts.Options) { 76 testDir := t.TempDir() 77 78 ctx := llvm.NewContext() 79 defer ctx.Dispose() 80 81 target, err := compileopts.LoadTarget(options) 82 if err != nil { 83 t.Fatalf("could not load target: %s", err) 84 } 85 config := compileopts.Config{ 86 Options: options, 87 Target: target, 88 } 89 90 // Create a very simple C input file. 91 srcpath := filepath.Join(testDir, "test.c") 92 err = os.WriteFile(srcpath, []byte("int add(int a, int b) { return a + b; }"), 0o666) 93 if err != nil { 94 t.Fatalf("could not write target file %s: %s", srcpath, err) 95 } 96 97 // Compile this file using Clang. 98 outpath := filepath.Join(testDir, "test.bc") 99 flags := append([]string{"-c", "-emit-llvm", "-o", outpath, srcpath}, config.CFlags(false)...) 100 if config.GOOS() == "darwin" { 101 // Silence some warnings that happen when testing GOOS=darwin on 102 // something other than MacOS. 103 flags = append(flags, "-Wno-missing-sysroot", "-Wno-incompatible-sysroot") 104 } 105 err = runCCompiler(flags...) 106 if err != nil { 107 t.Fatalf("failed to compile %s: %s", srcpath, err) 108 } 109 110 // Read the resulting LLVM bitcode. 111 mod, err := ctx.ParseBitcodeFile(outpath) 112 if err != nil { 113 t.Fatalf("could not parse bitcode file %s: %s", outpath, err) 114 } 115 defer mod.Dispose() 116 117 // Check whether the LLVM target matches. 118 if mod.Target() != config.Triple() { 119 t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", config.Triple(), mod.Target()) 120 } 121 122 // Check the "target-cpu" and "target-features" string attribute of the add 123 // function. 124 add := mod.NamedFunction("add") 125 var cpu, features string 126 cpuAttr := add.GetStringAttributeAtIndex(-1, "target-cpu") 127 featuresAttr := add.GetStringAttributeAtIndex(-1, "target-features") 128 if !cpuAttr.IsNil() { 129 cpu = cpuAttr.GetStringValue() 130 } 131 if !featuresAttr.IsNil() { 132 features = featuresAttr.GetStringValue() 133 } 134 if cpu != config.CPU() { 135 t.Errorf("target has CPU %#v but Clang makes it CPU %#v", config.CPU(), cpu) 136 } 137 if features != config.Features() { 138 if hasBuiltinTools || runtime.GOOS != "linux" { 139 // Skip this step when using an external Clang invocation on Linux. 140 // The reason is that Debian has patched Clang in a way that 141 // modifies the LLVM features string, changing lots of FPU/float 142 // related flags. We want to test vanilla Clang, not Debian Clang. 143 t.Errorf("target has LLVM features\n\t%#v\nbut Clang makes it\n\t%#v", config.Features(), features) 144 } 145 } 146 } 147 148 // This TestMain is necessary because TinyGo may also be invoked to run certain 149 // LLVM tools in a separate process. Not capturing these invocations would lead 150 // to recursive tests. 151 func TestMain(m *testing.M) { 152 if len(os.Args) >= 2 { 153 switch os.Args[1] { 154 case "clang", "ld.lld", "wasm-ld": 155 // Invoke a specific tool. 156 err := RunTool(os.Args[1], os.Args[2:]...) 157 if err != nil { 158 fmt.Fprintln(os.Stderr, err) 159 os.Exit(1) 160 } 161 os.Exit(0) 162 } 163 } 164 165 // Run normal tests. 166 os.Exit(m.Run()) 167 }