diff --git a/.circleci/config.yml b/.circleci/config.yml index c22bc8be..0822ddce 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -65,7 +65,7 @@ commands: - go-cache-v2-{{ checksum "go.mod" }} - llvm-source-linux - run: go install . - - run: go test -v ./transform . + - run: go test -v ./interp ./transform . - run: make gen-device -j4 - run: make smoketest RISCV=0 - save_cache: diff --git a/Makefile b/Makefile index f55c60e0..156966a7 100644 --- a/Makefile +++ b/Makefile @@ -100,7 +100,7 @@ build/tinygo: CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -o build/tinygo -tags byollvm . test: - CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./transform . + CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./interp ./transform . tinygo-test: cd tests/tinygotest && tinygo test diff --git a/interp/interp_test.go b/interp/interp_test.go new file mode 100644 index 00000000..b9f37e1b --- /dev/null +++ b/interp/interp_test.go @@ -0,0 +1,98 @@ +package interp + +import ( + "io/ioutil" + "os" + "strings" + "testing" + + "tinygo.org/x/go-llvm" +) + +func TestInterp(t *testing.T) { + for _, name := range []string{ + "basic", + } { + name := name // make tc local to this closure + t.Run(name, func(t *testing.T) { + t.Parallel() + runTest(t, "testdata/"+name) + }) + } +} + +func runTest(t *testing.T, pathPrefix string) { + // Read the input IR. + ctx := llvm.NewContext() + buf, err := llvm.NewMemoryBufferFromFile(pathPrefix + ".ll") + os.Stat(pathPrefix + ".ll") // make sure this file is tracked by `go test` caching + if err != nil { + t.Fatalf("could not read file %s: %v", pathPrefix+".ll", err) + } + mod, err := ctx.ParseIR(buf) + if err != nil { + t.Fatalf("could not load module:\n%v", err) + } + + // Perform the transform. + err = Run(mod, false) + if err != nil { + t.Fatal(err) + } + + // Run some cleanup passes to get easy-to-read outputs. + pm := llvm.NewPassManager() + defer pm.Dispose() + pm.AddGlobalOptimizerPass() + pm.AddDeadStoreEliminationPass() + pm.Run(mod) + + // Read the expected output IR. + out, err := ioutil.ReadFile(pathPrefix + ".out.ll") + if err != nil { + t.Fatalf("could not read output file %s: %v", pathPrefix+".out.ll", err) + } + + // See whether the transform output matches with the expected output IR. + expected := string(out) + actual := mod.String() + if !fuzzyEqualIR(expected, actual) { + t.Logf("output does not match expected output:\n%s", actual) + t.Fail() + } +} + +// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly +// equal. That means, only relevant lines are compared (excluding comments +// etc.). +func fuzzyEqualIR(s1, s2 string) bool { + lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n")) + lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n")) + if len(lines1) != len(lines2) { + return false + } + for i, line := range lines1 { + if line != lines2[i] { + return false + } + } + + return true +} + +// filterIrrelevantIRLines removes lines from the input slice of strings that +// are not relevant in comparing IR. For example, empty lines and comments are +// stripped out. +func filterIrrelevantIRLines(lines []string) []string { + var out []string + for _, line := range lines { + if line == "" || line[0] == ';' { + continue + } + if strings.HasPrefix(line, "source_filename = ") { + continue + } + out = append(out, line) + } + return out +} diff --git a/interp/testdata/basic.ll b/interp/testdata/basic.ll new file mode 100644 index 00000000..8b482c71 --- /dev/null +++ b/interp/testdata/basic.ll @@ -0,0 +1,42 @@ +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64--linux" + +@main.v1 = internal global i64 0 + +declare void @runtime.printint64(i64) unnamed_addr + +declare void @runtime.printnl() unnamed_addr + +define void @runtime.initAll() unnamed_addr { +entry: + call void @runtime.init() + call void @main.init() + ret void +} + +define void @main() unnamed_addr { +entry: + %0 = load i64, i64* @main.v1 + call void @runtime.printint64(i64 %0) + call void @runtime.printnl() + ret void +} + +define internal void @runtime.init() unnamed_addr { +entry: + ret void +} + +define internal void @main.init() unnamed_addr { +entry: + store i64 3, i64* @main.v1 + call void @"main.init#1"() + ret void +} + +define internal void @"main.init#1"() unnamed_addr { +entry: + call void @runtime.printint64(i64 5) + call void @runtime.printnl() + ret void +} diff --git a/interp/testdata/basic.out.ll b/interp/testdata/basic.out.ll new file mode 100644 index 00000000..457a6c2b --- /dev/null +++ b/interp/testdata/basic.out.ll @@ -0,0 +1,20 @@ +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64--linux" + +declare void @runtime.printint64(i64) unnamed_addr + +declare void @runtime.printnl() unnamed_addr + +define void @runtime.initAll() unnamed_addr { +entry: + call void @runtime.printint64(i64 5) + call void @runtime.printnl() + ret void +} + +define void @main() unnamed_addr { +entry: + call void @runtime.printint64(i64 3) + call void @runtime.printnl() + ret void +}