github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/interp/interp_test.go (about) 1 package interp 2 3 import ( 4 "os" 5 "strconv" 6 "strings" 7 "testing" 8 "time" 9 10 "tinygo.org/x/go-llvm" 11 ) 12 13 func TestInterp(t *testing.T) { 14 llvmVersion, err := strconv.Atoi(strings.Split(llvm.Version, ".")[0]) 15 if err != nil { 16 // Note: this should never happen and if it does, it will always happen 17 // for a particular build because llvm.Version is a constant. 18 panic(err) 19 } 20 for _, name := range []string{ 21 "basic", 22 "phi", 23 "slice-copy", 24 "consteval", 25 "interface", 26 "revert", 27 "alloc", 28 } { 29 name := name // make local to this closure 30 if name == "slice-copy" && llvmVersion < 14 { 31 continue 32 } 33 t.Run(name, func(t *testing.T) { 34 t.Parallel() 35 runTest(t, "testdata/"+name) 36 }) 37 } 38 } 39 40 func runTest(t *testing.T, pathPrefix string) { 41 // Read the input IR. 42 ctx := llvm.NewContext() 43 defer ctx.Dispose() 44 buf, err := llvm.NewMemoryBufferFromFile(pathPrefix + ".ll") 45 os.Stat(pathPrefix + ".ll") // make sure this file is tracked by `go test` caching 46 if err != nil { 47 t.Fatalf("could not read file %s: %v", pathPrefix+".ll", err) 48 } 49 mod, err := ctx.ParseIR(buf) 50 if err != nil { 51 t.Fatalf("could not load module:\n%v", err) 52 } 53 defer mod.Dispose() 54 55 // Perform the transform. 56 err = Run(mod, 10*time.Minute, false) 57 if err != nil { 58 if err, match := err.(*Error); match { 59 println(err.Error()) 60 if len(err.Inst) != 0 { 61 println(err.Inst) 62 } 63 if len(err.Traceback) > 0 { 64 println("\ntraceback:") 65 for _, line := range err.Traceback { 66 println(line.Pos.String() + ":") 67 println(line.Inst) 68 } 69 } 70 } 71 t.Fatal(err) 72 } 73 74 // To be sure, verify that the module is still valid. 75 if llvm.VerifyModule(mod, llvm.PrintMessageAction) != nil { 76 t.FailNow() 77 } 78 79 // Run some cleanup passes to get easy-to-read outputs. 80 to := llvm.NewPassBuilderOptions() 81 defer to.Dispose() 82 mod.RunPasses("globalopt,dse,adce", llvm.TargetMachine{}, to) 83 84 // Read the expected output IR. 85 out, err := os.ReadFile(pathPrefix + ".out.ll") 86 if err != nil { 87 t.Fatalf("could not read output file %s: %v", pathPrefix+".out.ll", err) 88 } 89 90 // See whether the transform output matches with the expected output IR. 91 expected := string(out) 92 actual := mod.String() 93 if !fuzzyEqualIR(expected, actual) { 94 t.Logf("output does not match expected output:\n%s", actual) 95 t.Fail() 96 } 97 } 98 99 // fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly 100 // equal. That means, only relevant lines are compared (excluding comments 101 // etc.). 102 func fuzzyEqualIR(s1, s2 string) bool { 103 lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n")) 104 lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n")) 105 if len(lines1) != len(lines2) { 106 return false 107 } 108 for i, line1 := range lines1 { 109 line2 := lines2[i] 110 if line1 != line2 { 111 return false 112 } 113 } 114 115 return true 116 } 117 118 // filterIrrelevantIRLines removes lines from the input slice of strings that 119 // are not relevant in comparing IR. For example, empty lines and comments are 120 // stripped out. 121 func filterIrrelevantIRLines(lines []string) []string { 122 var out []string 123 for _, line := range lines { 124 line = strings.TrimSpace(line) // drop '\r' on Windows 125 if line == "" || line[0] == ';' { 126 continue 127 } 128 if strings.HasPrefix(line, "source_filename = ") { 129 continue 130 } 131 out = append(out, line) 132 } 133 return out 134 }