mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-04 19:17:47 +00:00
compiler: add tests
This commit adds a very small test case. More importantly, it adds a framework for other tests to be added in the future.
This commit is contained in:
@@ -85,7 +85,7 @@ commands:
|
||||
key: wasi-libc-sysroot-systemclang-v1
|
||||
paths:
|
||||
- lib/wasi-libc/sysroot
|
||||
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./interp ./transform .
|
||||
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./compiler ./interp ./transform .
|
||||
- run: make gen-device -j4
|
||||
- run: make smoketest
|
||||
- save_cache:
|
||||
|
||||
@@ -111,7 +111,7 @@ endif
|
||||
clean:
|
||||
@rm -rf build
|
||||
|
||||
FMT_PATHS = ./*.go builder cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform
|
||||
FMT_PATHS = ./*.go builder cgo compiler compiler/testdata interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform
|
||||
fmt:
|
||||
@gofmt -l -w $(FMT_PATHS)
|
||||
fmt-check:
|
||||
@@ -174,7 +174,7 @@ tinygo:
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -o build/tinygo$(EXE) -tags byollvm .
|
||||
|
||||
test: wasi-libc
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./cgo ./compileopts ./interp ./transform .
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./cgo ./compileopts ./compiler ./interp ./transform .
|
||||
|
||||
tinygo-test:
|
||||
cd tests/tinygotest && tinygo test
|
||||
|
||||
+17
-9
@@ -98,15 +98,9 @@ func NewTargetMachine(config *compileopts.Config) (llvm.TargetMachine, error) {
|
||||
return machine, nil
|
||||
}
|
||||
|
||||
// Compile the given package path or .go file path. Return an error when this
|
||||
// fails (in any stage). If successful it returns the LLVM module and a list of
|
||||
// extra C files to be compiled. If not, one or more errors will be returned.
|
||||
//
|
||||
// The fact that it returns a list of filenames to compile is a layering
|
||||
// violation. Eventually, this Compile function should only compile a single
|
||||
// package and not the whole program, and loading of the program (including CGo
|
||||
// processing) should be moved outside the compiler package.
|
||||
func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Config) (llvm.Module, []string, []error) {
|
||||
// newCompilerContext builds a new *compilerContext based on the provided
|
||||
// configuration, ready to compile Go SSA to LLVM IR.
|
||||
func newCompilerContext(pkgName string, machine llvm.TargetMachine, config *compileopts.Config) *compilerContext {
|
||||
c := &compilerContext{
|
||||
Config: config,
|
||||
difiles: make(map[string]llvm.Metadata),
|
||||
@@ -140,6 +134,20 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
|
||||
c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace()
|
||||
dummyFunc.EraseFromParentAsFunction()
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Compile the given package path or .go file path. Return an error when this
|
||||
// fails (in any stage). If successful it returns the LLVM module and a list of
|
||||
// extra C files to be compiled. If not, one or more errors will be returned.
|
||||
//
|
||||
// The fact that it returns a list of filenames to compile is a layering
|
||||
// violation. Eventually, this Compile function should only compile a single
|
||||
// package and not the whole program, and loading of the program (including CGo
|
||||
// processing) should be moved outside the compiler package.
|
||||
func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Config) (llvm.Module, []string, []error) {
|
||||
c := newCompilerContext(pkgName, machine, config)
|
||||
|
||||
// Prefix the GOPATH with the system GOROOT, as GOROOT is already set to
|
||||
// the TinyGo root.
|
||||
overlayGopath := goenv.Get("GOPATH")
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"github.com/tinygo-org/tinygo/compiler/ircheck"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
var flagUpdate = flag.Bool("update", false, "update all tests")
|
||||
|
||||
func TestCompiler(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, name := range []string{"basic"} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
runCompilerTest(t, name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func runCompilerTest(t *testing.T, name string) {
|
||||
// Read the AST in memory.
|
||||
path := filepath.Join("testdata", name+".go")
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
t.Fatal("could not parse Go source file:", err)
|
||||
}
|
||||
files := []*ast.File{f}
|
||||
|
||||
// Create Go SSA from the AST.
|
||||
var typecheckErrors []error
|
||||
var typecheckErrorsLock sync.Mutex
|
||||
typesConfig := types.Config{
|
||||
Error: func(err error) {
|
||||
typecheckErrorsLock.Lock()
|
||||
defer typecheckErrorsLock.Unlock()
|
||||
typecheckErrors = append(typecheckErrors, err)
|
||||
},
|
||||
Importer: simpleImporter{},
|
||||
Sizes: types.SizesFor("gccgo", "arm"),
|
||||
}
|
||||
pkg, _, err := ssautil.BuildPackage(&typesConfig, fset, types.NewPackage("main", ""), files, ssa.SanityCheckFunctions|ssa.BareInits|ssa.GlobalDebug)
|
||||
for _, err := range typecheckErrors {
|
||||
t.Error(err)
|
||||
}
|
||||
if err != nil && len(typecheckErrors) == 0 {
|
||||
// Only report errors when no type errors are found (an
|
||||
// unexpected condition).
|
||||
t.Error(err)
|
||||
}
|
||||
if t.Failed() {
|
||||
return
|
||||
}
|
||||
|
||||
// Configure the compiler.
|
||||
config := compileopts.Config{
|
||||
Options: &compileopts.Options{},
|
||||
Target: &compileopts.TargetSpec{
|
||||
Triple: "armv7m-none-eabi",
|
||||
BuildTags: []string{"cortexm", "baremetal", "linux", "arm"},
|
||||
Scheduler: "tasks",
|
||||
},
|
||||
}
|
||||
machine, err := NewTargetMachine(&config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c := newCompilerContext("main", machine, &config)
|
||||
irbuilder := c.ctx.NewBuilder()
|
||||
defer irbuilder.Dispose()
|
||||
|
||||
// Create LLVM IR from the Go SSA.
|
||||
c.createPackage(pkg, irbuilder)
|
||||
|
||||
// Check the IR with the LLVM verifier.
|
||||
if err := llvm.VerifyModule(c.mod, llvm.PrintMessageAction); err != nil {
|
||||
t.Error("verification error after IR construction")
|
||||
}
|
||||
|
||||
// Check the IR with our own verifier (which checks for different things).
|
||||
errs := ircheck.Module(c.mod)
|
||||
for _, err := range errs {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Check whether the IR matches the expected IR.
|
||||
ir := c.mod.String()
|
||||
ir = ir[strings.Index(ir, "\ntarget datalayout = ")+1:]
|
||||
outfile := filepath.Join("testdata", name+".ll")
|
||||
if *flagUpdate {
|
||||
err := ioutil.WriteFile(outfile, []byte(ir), 0666)
|
||||
if err != nil {
|
||||
t.Error("could not read output file:", err)
|
||||
}
|
||||
} else {
|
||||
ir2, err := ioutil.ReadFile(outfile)
|
||||
if err != nil {
|
||||
t.Fatal("could not read input file:", err)
|
||||
}
|
||||
ir2 = bytes.Replace(ir2, []byte("\r\n"), []byte("\n"), -1)
|
||||
if ir != string(ir2) {
|
||||
t.Error("output did not match")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// simpleImporter implements the types.Importer interface, but only allows
|
||||
// importing the unsafe package.
|
||||
type simpleImporter struct {
|
||||
}
|
||||
|
||||
// Import implements the Importer interface. For testing usage only: it only
|
||||
// supports importing the unsafe package.
|
||||
func (i simpleImporter) Import(path string) (*types.Package, error) {
|
||||
switch path {
|
||||
case "unsafe":
|
||||
return types.Unsafe, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("importer not implemented for package %s", path)
|
||||
}
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
package main
|
||||
|
||||
func add(x, y int) int {
|
||||
return x + y
|
||||
}
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||
target triple = "armv7m-none-eabi"
|
||||
|
||||
define internal i32 @main.add(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = add i32 %x, %y
|
||||
ret i32 %0
|
||||
}
|
||||
|
||||
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
Reference in New Issue
Block a user