mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-03 10:37:46 +00:00
7939c060ce
These types are often known to the compiler already. Moving them into
the compiler is an important step for two related reasons:
* It makes the compiler better testable. Together with
https://github.com/tinygo-org/tinygo/pull/1008 it will make it
possible to test the compiler without having to load the runtime
package.
* It makes it easier to compile packages independently as the type
information of the runtime package doesn't need to be present.
I had to use hack to get this to work well: internal/task.Task is now an
opaque struct. This is necessary because there is a dependency from
*runtime.channel -> *runtime.channelBlockedList -> *internal/task.Task.
I don't want to include the definition of the internal/task.Task struct
in the compiler directly as that would make changing the internal/task
package a lot harder and the compiler doesn't need to know the layout of
that struct anyway.
139 lines
3.5 KiB
Go
139 lines
3.5 KiB
Go
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)
|
|
c.runtimePkg = types.NewPackage("runtime", "runtime")
|
|
c.taskPkg = types.NewPackage("internal/task", "task")
|
|
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)
|
|
}
|
|
}
|