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  }