Compare commits

..

5 Commits

Author SHA1 Message Date
Ayke van Laethem ca7b95039d interp: do not rely on fixed type names
After merging multiple LLVM modules into one, types may have been
renamed. Therefore, the interp package should not rely on types to have
any particular name.
2020-05-03 15:43:45 +02:00
Ayke van Laethem 7939c060ce compiler: include type information about the runtime in the compiler
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.
2020-05-03 15:43:45 +02:00
Ayke van Laethem dd04f34059 compiler: integrate ir package into compiler package
The ir package has long lost its original purpose (which was doing some
analysis and optimization on the Go SSA directly). There is very little
of it left, which is best integrated directly in the compiler package to
avoid unnecessary abstraction.
2020-05-03 15:43:45 +02:00
Ayke van Laethem f58e75c386 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.
2020-05-03 15:43:45 +02:00
Ayke van Laethem b8db79f6a6 compiler: compile all functions/methods, remove SimpleDCE
This is important because once we move to compiling packages
independently, SimpleDCE can't work anymore. Instead we'll have to
compile all parts of a package and cache that for later reuse.
2020-05-03 15:43:44 +02:00
30 changed files with 1988 additions and 1263 deletions
+1 -1
View File
@@ -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:
+2 -2
View File
@@ -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 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
+1 -1
View File
@@ -21,7 +21,7 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
if goroot == "" {
return nil, errors.New("cannot locate $GOROOT, please set it manually")
}
major, minor, err := goenv.GetGorootVersion(goroot)
major, minor, err := getGorootVersion(goroot)
if err != nil {
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
}
+58
View File
@@ -1,13 +1,71 @@
package builder
import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
)
// getGorootVersion returns the major and minor version for a given GOROOT path.
// If the goroot cannot be determined, (0, 0) is returned.
func getGorootVersion(goroot string) (major, minor int, err error) {
s, err := GorootVersionString(goroot)
if err != nil {
return 0, 0, err
}
if s == "" || s[:2] != "go" {
return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
}
parts := strings.Split(s[2:], ".")
if len(parts) < 2 {
return 0, 0, errors.New("could not parse Go version: version has less than two parts")
}
// Ignore the errors, we don't really handle errors here anyway.
var trailing string
n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing)
if n == 2 && err == io.EOF {
// Means there were no trailing characters (i.e., not an alpha/beta)
err = nil
}
if err != nil {
return 0, 0, fmt.Errorf("failed to parse version: %s", err)
}
return
}
// GorootVersionString returns the version string as reported by the Go
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but
// can have some variations (for beta releases, for example).
func GorootVersionString(goroot string) (string, error) {
if data, err := ioutil.ReadFile(filepath.Join(
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
r := regexp.MustCompile("const TheVersion = `(.*)`")
matches := r.FindSubmatch(data)
if len(matches) != 2 {
return "", errors.New("Invalid go version output:\n" + string(data))
}
return string(matches[1]), nil
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
return string(data), nil
} else {
return "", err
}
}
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
// multiple locations, which should make it find the directory when installed in
// various ways.
+6 -6
View File
@@ -16,7 +16,7 @@ import (
// slice. This is required by the Go language spec: an index out of bounds must
// cause a panic.
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) {
if b.fn.IsNoBounds() {
if b.info.nobounds {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
@@ -48,7 +48,7 @@ func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
// logic is the same in both cases.
func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
if b.fn.IsNoBounds() {
if b.info.nobounds {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
@@ -104,7 +104,7 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
// createChanBoundsCheck creates a bounds check before creating a new channel to
// check that the value is not too big for runtime.chanMake.
func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
if b.fn.IsNoBounds() {
if b.info.nobounds {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
@@ -189,7 +189,7 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str
// createNegativeShiftCheck creates an assertion that panics if the given shift value is negative.
// This function assumes that the shift value is signed.
func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
if b.fn.IsNoBounds() {
if b.info.nobounds {
// Function disabled bounds checking - skip shift check.
return
}
@@ -212,8 +212,8 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
}
}
faultBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".throw")
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".next")
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now branch to the out-of-bounds or the regular block.
+4 -6
View File
@@ -34,14 +34,12 @@ const (
// createCall creates a new call to runtime.<fnName> with the given arguments.
func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
fullName := "runtime." + fnName
fn := b.mod.NamedFunction(fullName)
if fn.IsNil() {
panic("trying to call non-existent function: " + fullName)
}
llvmFn := b.getFunctionRaw(b.getRuntimeFuncType(fnName), functionInfo{
linkName: "runtime." + fnName,
})
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle
return b.createCall(fn, args, name)
return b.createCall(llvmFn, args, name)
}
// createCall creates a call to the given function with the arguments possibly
+342 -214
View File
@@ -1,5 +1,7 @@
package compiler
//go:generate go run ./mkruntimetypes.go
import (
"debug/dwarf"
"errors"
@@ -11,13 +13,13 @@ import (
"go/types"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/ir"
"github.com/tinygo-org/tinygo/loader"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
@@ -49,18 +51,23 @@ type compilerContext struct {
targetData llvm.TargetData
intType llvm.Type
i8ptrType llvm.Type // for convenience
runtimeTypes map[string]types.Type
funcPtrAddrSpace int
uintptrType llvm.Type
ir *ir.Program
program *ssa.Program
diagnostics []error
astComments map[string]*ast.CommentGroup
runtimePkg *types.Package // package runtime
taskPkg *types.Package // package internal/task
}
// builder contains all information relevant to build a single function.
type builder struct {
*compilerContext
llvm.Builder
fn *ir.Function
fn *ssa.Function
llvmFn llvm.Value
info functionInfo
locals map[ssa.Value]llvm.Value // local variables
blockEntries map[*ssa.BasicBlock]llvm.BasicBlock // a *ssa.BasicBlock may be split up
blockExits map[*ssa.BasicBlock]llvm.BasicBlock // these are the exit blocks
@@ -71,9 +78,9 @@ type builder struct {
difunc llvm.Metadata
dilocals map[*types.Var]llvm.Metadata
allDeferFuncs []interface{}
deferFuncs map[*ir.Function]int
deferFuncs map[*ssa.Function]int
deferInvokeFuncs map[string]int
deferClosureFuncs map[*ir.Function]int
deferClosureFuncs map[*ssa.Function]int
selectRecvBuf map[*ssa.Select]llvm.Value
}
@@ -95,21 +102,16 @@ 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),
ditypes: make(map[types.Type]llvm.Metadata),
machine: machine,
targetData: machine.CreateTargetData(),
Config: config,
difiles: make(map[string]llvm.Metadata),
ditypes: make(map[types.Type]llvm.Metadata),
machine: machine,
targetData: machine.CreateTargetData(),
runtimeTypes: make(map[string]types.Type),
}
c.ctx = llvm.NewContext()
@@ -137,11 +139,30 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace()
dummyFunc.EraseFromParentAsFunction()
wd, err := os.Getwd()
if err != nil {
return c.mod, nil, []error{err}
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")
if overlayGopath == "" {
overlayGopath = goenv.Get("GOROOT")
} else {
overlayGopath = goenv.Get("GOROOT") + string(filepath.ListSeparator) + overlayGopath
}
goroot, err := loader.GetCachedGoroot(c.Config)
wd, err := os.Getwd()
if err != nil {
return c.mod, nil, []error{err}
}
@@ -149,14 +170,47 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
Build: &build.Context{
GOARCH: c.GOARCH(),
GOOS: c.GOOS(),
GOROOT: goroot,
GOROOT: goenv.Get("GOROOT"),
GOPATH: goenv.Get("GOPATH"),
CgoEnabled: c.CgoEnabled(),
UseAllFiles: false,
Compiler: "gc", // must be one of the recognized compilers
BuildTags: c.BuildTags(),
},
Tests: c.TestConfig.CompileTestBinary,
OverlayBuild: &build.Context{
GOARCH: c.GOARCH(),
GOOS: c.GOOS(),
GOROOT: goenv.Get("TINYGOROOT"),
GOPATH: overlayGopath,
CgoEnabled: c.CgoEnabled(),
UseAllFiles: false,
Compiler: "gc", // must be one of the recognized compilers
BuildTags: c.BuildTags(),
},
OverlayPath: func(path string) string {
// Return the (overlay) import path when it should be overlaid, and
// "" if it should not.
if strings.HasPrefix(path, tinygoPath+"/src/") {
// Avoid issues with packages that are imported twice, one from
// GOPATH and one from TINYGOPATH.
path = path[len(tinygoPath+"/src/"):]
}
switch path {
case "machine", "os", "reflect", "runtime", "runtime/interrupt", "runtime/volatile", "sync", "testing", "internal/reflectlite", "internal/task":
return path
default:
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
return path
} else if path == "syscall" {
for _, tag := range c.BuildTags() {
if tag == "baremetal" || tag == "darwin" {
return path
}
}
}
}
return ""
},
TypeChecker: types.Config{
Sizes: &stdSizes{
IntSize: int64(c.targetData.TypeAllocSize(c.intType)),
@@ -170,23 +224,36 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
ClangHeaders: c.ClangHeaders,
}
err = lprogram.Load(pkgName)
if strings.HasSuffix(pkgName, ".go") {
_, err = lprogram.ImportFile(pkgName)
if err != nil {
return c.mod, nil, []error{err}
}
} else {
_, err = lprogram.Import(pkgName, wd, token.Position{
Filename: "build command-line-arguments",
})
if err != nil {
return c.mod, nil, []error{err}
}
}
_, err = lprogram.Import("runtime", "", token.Position{
Filename: "build default import",
})
if err != nil {
return c.mod, nil, []error{err}
}
err = lprogram.Parse()
err = lprogram.Parse(c.TestConfig.CompileTestBinary)
if err != nil {
return c.mod, nil, []error{err}
}
c.ir = ir.NewProgram(lprogram)
// Run a simple dead code elimination pass.
err = c.ir.SimpleDCE()
if err != nil {
return c.mod, nil, []error{err}
}
c.program = lprogram.LoadSSA()
c.program.Build()
c.runtimePkg = c.program.ImportedPackage("runtime").Pkg
c.taskPkg = c.program.ImportedPackage("internal/task").Pkg
// Initialize debug information.
if c.Debug() {
@@ -201,64 +268,44 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
c.loadASTComments(lprogram)
// Declare runtime types.
// TODO: lazily create runtime types in getLLVMRuntimeType when they are
// needed. Eventually this will be required anyway, when packages are
// compiled independently (and the runtime types are not available).
for _, member := range c.ir.Program.ImportedPackage("runtime").Members {
if member, ok := member.(*ssa.Type); ok {
if typ, ok := member.Type().(*types.Named); ok {
if _, ok := typ.Underlying().(*types.Struct); ok {
c.getLLVMType(typ)
// Predeclare the runtime.alloc function, which is used by the wordpack
// functionality.
c.getFunction(c.program.ImportedPackage("runtime").Members["alloc"].(*ssa.Function))
sortedPackages := sortPackages(c.program, pkgName)
// Find package initializers.
var initFuncs []llvm.Value
for _, pkg := range sortedPackages {
for _, member := range pkg.Members {
switch member := member.(type) {
case *ssa.Function:
if member.Synthetic == "package initializer" {
initFuncs = append(initFuncs, c.getFunction(member))
}
}
}
}
// Declare all functions.
for _, f := range c.ir.Functions {
c.createFunctionDeclaration(f)
}
// Add definitions to declarations.
var initFuncs []llvm.Value
irbuilder := c.ctx.NewBuilder()
defer irbuilder.Dispose()
for _, f := range c.ir.Functions {
if f.Synthetic == "package initializer" {
initFuncs = append(initFuncs, f.LLVMFn)
}
if f.CName() != "" {
continue
}
if f.Blocks == nil {
continue // external function
}
// Create the function definition.
b := builder{
compilerContext: c,
Builder: irbuilder,
fn: f,
locals: make(map[ssa.Value]llvm.Value),
dilocals: make(map[*types.Var]llvm.Metadata),
blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock),
blockExits: make(map[*ssa.BasicBlock]llvm.BasicBlock),
}
b.createFunctionDefinition()
for _, pkg := range sortedPackages {
c.createPackage(pkg, irbuilder)
}
// After all packages are imported, add a synthetic initializer function
// that calls the initializer of each package.
initFn := c.ir.GetFunction(c.ir.Program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function))
initFn.LLVMFn.SetLinkage(llvm.InternalLinkage)
initFn.LLVMFn.SetUnnamedAddr(true)
initFn := c.program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function)
llvmInitFn := c.getFunction(initFn)
llvmInitFn.SetLinkage(llvm.InternalLinkage)
llvmInitFn.SetUnnamedAddr(true)
if c.Debug() {
difunc := c.attachDebugInfo(initFn)
pos := c.ir.Program.Fset.Position(initFn.Pos())
pos := c.program.Fset.Position(initFn.Pos())
irbuilder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
block := c.ctx.AddBasicBlock(initFn.LLVMFn, "entry")
block := c.ctx.AddBasicBlock(llvmInitFn, "entry")
irbuilder.SetInsertPointAtEnd(block)
for _, fn := range initFuncs {
irbuilder.CreateCall(fn, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.Undef(c.i8ptrType)}, "")
@@ -267,7 +314,7 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
// Conserve for goroutine lowering. Without marking these as external, they
// would be optimized away.
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
realMain := c.mod.NamedFunction(pkgName + ".main")
realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering
// Replace callMain placeholder with actual main function.
@@ -323,29 +370,91 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
// Gather the list of (C) file paths that should be included in the build.
var extraFiles []string
for _, pkg := range c.ir.LoaderProgram.Sorted() {
for _, file := range pkg.OtherFiles {
switch strings.ToLower(filepath.Ext(file)) {
case ".c":
extraFiles = append(extraFiles, file)
}
for _, pkg := range lprogram.Sorted() {
for _, file := range pkg.CFiles {
extraFiles = append(extraFiles, filepath.Join(pkg.Package.Dir, file))
}
}
return c.mod, extraFiles, c.diagnostics
}
// sortPackages returns a list of all packages, sorted by import order.
func sortPackages(program *ssa.Program, mainPath string) []*ssa.Package {
// Find the main package, which is a bit difficult when running a .go file
// directly.
mainPkg := program.ImportedPackage(mainPath)
if mainPkg == nil {
for _, pkgInfo := range program.AllPackages() {
if pkgInfo.Pkg.Name() == "main" {
if mainPkg != nil {
panic("more than one main package found")
}
mainPkg = pkgInfo
}
}
}
if mainPkg == nil {
panic("could not find main package")
}
packageList := []*ssa.Package{}
packageSet := map[string]struct{}{}
worklist := []string{"runtime", mainPath}
for len(worklist) != 0 {
pkgPath := worklist[0]
var pkg *ssa.Package
if pkgPath == mainPath {
pkg = mainPkg // necessary for compiling individual .go files
} else {
pkg = program.ImportedPackage(pkgPath)
}
if pkg == nil {
// Non-SSA package (e.g. cgo).
packageSet[pkgPath] = struct{}{}
worklist = worklist[1:]
continue
}
if _, ok := packageSet[pkgPath]; ok {
// Package already in the final package list.
worklist = worklist[1:]
continue
}
unsatisfiedImports := make([]string, 0)
imports := pkg.Pkg.Imports()
for _, pkg := range imports {
if _, ok := packageSet[pkg.Path()]; ok {
continue
}
unsatisfiedImports = append(unsatisfiedImports, pkg.Path())
}
if len(unsatisfiedImports) == 0 {
// All dependencies of this package are satisfied, so add this
// package to the list.
packageList = append(packageList, pkg)
packageSet[pkgPath] = struct{}{}
worklist = worklist[1:]
} else {
// Prepend all dependencies to the worklist and reconsider this
// package (by not removing it from the worklist). At that point, it
// must be possible to add it to packageList.
worklist = append(unsatisfiedImports, worklist...)
}
}
return packageList
}
// getLLVMRuntimeType obtains a named type from the runtime package and returns
// it as a LLVM type, creating it if necessary. It is a shorthand for
// getLLVMType(getRuntimeType(name)).
// it as a LLVM type, creating it if necessary.
func (c *compilerContext) getLLVMRuntimeType(name string) llvm.Type {
fullName := "runtime." + name
typ := c.mod.GetTypeByName(fullName)
if typ.IsNil() {
println(c.mod.String())
panic("could not find runtime type: " + fullName)
llvmType := c.mod.GetTypeByName(fullName)
if llvmType.IsNil() {
llvmType = c.getLLVMType(c.getRuntimeType(name))
}
return typ
return llvmType
}
// getLLVMType creates and returns a LLVM type for a Go type. In the case of
@@ -405,6 +514,10 @@ func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type {
llvmType = c.ctx.StructCreateNamed(llvmName)
underlying := c.getLLVMType(st)
llvmType.StructSetBody(underlying.StructElementTypes(), false)
} else if typ.String() == "internal/task.Task" && llvmType.StructElementTypesCount() == 0 {
// Note: this struct is an opaque struct. Give it a body.
underlying := c.getLLVMType(st)
llvmType.StructSetBody(underlying.StructElementTypes(), false)
}
return llvmType
}
@@ -425,6 +538,23 @@ func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type {
case *types.Struct:
members := make([]llvm.Type, typ.NumFields())
for i := 0; i < typ.NumFields(); i++ {
if ptr, ok := typ.Field(i).Type().(*types.Pointer); ok {
if named, ok := ptr.Elem().(*types.Named); ok && named.String() == "internal/task.Task" {
// Special workaround for internal/task.Task. It is
// referenced from the runtime.channel type, which
// references runtime.channelBlockedList, which references
// internal/task.Task. To avoid having to define
// internal/task.Task as a compiler-internal type, make the
// type opaque.
ptrTo := c.mod.GetTypeByName(named.String())
if ptrTo.IsNil() {
ptrTo = c.ctx.StructCreateNamed(named.String())
}
members[i] = llvm.PointerType(ptrTo, 0)
continue
}
}
members[i] = c.getLLVMType(typ.Field(i).Type())
}
return c.ctx.StructType(members, false)
@@ -528,11 +658,11 @@ func (c *compilerContext) createDIType(typ types.Type) llvm.Metadata {
Encoding: encoding,
})
case *types.Chan:
return c.getDIType(types.NewPointer(c.ir.Program.ImportedPackage("runtime").Members["channel"].(*ssa.Type).Type()))
return c.getDIType(types.NewPointer(c.getRuntimeType("channel")))
case *types.Interface:
return c.getDIType(c.ir.Program.ImportedPackage("runtime").Members["_interface"].(*ssa.Type).Type())
return c.getDIType(c.getRuntimeType("_interface"))
case *types.Map:
return c.getDIType(types.NewPointer(c.ir.Program.ImportedPackage("runtime").Members["hashmap"].(*ssa.Type).Type()))
return c.getDIType(types.NewPointer(c.getRuntimeType("hashmap")))
case *types.Named:
return c.dibuilder.CreateTypedef(llvm.DITypedef{
Type: c.getDIType(typ.Underlying()),
@@ -640,7 +770,7 @@ func (b *builder) getLocalVariable(variable *types.Var) llvm.Metadata {
return dilocal
}
pos := b.ir.Program.Fset.Position(variable.Pos())
pos := b.program.Fset.Position(variable.Pos())
// Check whether this is a function parameter.
for i, param := range b.fn.Params {
@@ -671,95 +801,17 @@ func (b *builder) getLocalVariable(variable *types.Var) llvm.Metadata {
return dilocal
}
// createFunctionDeclaration creates a LLVM function declaration without body.
// It can later be filled with frame.createFunctionDefinition().
func (c *compilerContext) createFunctionDeclaration(f *ir.Function) {
var retType llvm.Type
if f.Signature.Results() == nil {
retType = c.ctx.VoidType()
} else if f.Signature.Results().Len() == 1 {
retType = c.getLLVMType(f.Signature.Results().At(0).Type())
} else {
results := make([]llvm.Type, 0, f.Signature.Results().Len())
for i := 0; i < f.Signature.Results().Len(); i++ {
results = append(results, c.getLLVMType(f.Signature.Results().At(i).Type()))
}
retType = c.ctx.StructType(results, false)
}
var paramInfos []paramInfo
for _, param := range f.Params {
paramType := c.getLLVMType(param.Type())
paramFragmentInfos := expandFormalParamType(paramType, param.Name(), param.Type())
paramInfos = append(paramInfos, paramFragmentInfos...)
}
// Add an extra parameter as the function context. This context is used in
// closures and bound methods, but should be optimized away when not used.
if !f.IsExported() {
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", flags: 0})
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "parentHandle", flags: 0})
}
var paramTypes []llvm.Type
for _, info := range paramInfos {
paramTypes = append(paramTypes, info.llvmType)
}
fnType := llvm.FunctionType(retType, paramTypes, false)
name := f.LinkName()
f.LLVMFn = c.mod.NamedFunction(name)
if f.LLVMFn.IsNil() {
f.LLVMFn = llvm.AddFunction(c.mod, name, fnType)
}
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
for i, info := range paramInfos {
if info.flags&paramIsDeferenceableOrNull == 0 {
continue
}
if info.llvmType.TypeKind() == llvm.PointerTypeKind {
el := info.llvmType.ElementType()
size := c.targetData.TypeAllocSize(el)
if size == 0 {
// dereferenceable_or_null(0) appears to be illegal in LLVM.
continue
}
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, size)
f.LLVMFn.AddAttributeAtIndex(i+1, dereferenceableOrNull)
}
}
// External/exported functions may not retain pointer values.
// https://golang.org/cmd/cgo/#hdr-Passing_pointers
if f.IsExported() {
// Set the wasm-import-module attribute if the function's module is set.
if f.Module() != "" {
wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", f.Module())
f.LLVMFn.AddFunctionAttr(wasmImportModuleAttr)
}
nocaptureKind := llvm.AttributeKindID("nocapture")
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
for i, typ := range paramTypes {
if typ.TypeKind() == llvm.PointerTypeKind {
f.LLVMFn.AddAttributeAtIndex(i+1, nocapture)
}
}
}
}
// attachDebugInfo adds debug info to a function declaration. It returns the
// DISubprogram metadata node.
func (c *compilerContext) attachDebugInfo(f *ir.Function) llvm.Metadata {
pos := c.ir.Program.Fset.Position(f.Syntax().Pos())
return c.attachDebugInfoRaw(f, f.LLVMFn, "", pos.Filename, pos.Line)
func (c *compilerContext) attachDebugInfo(f *ssa.Function) llvm.Metadata {
pos := c.program.Fset.Position(f.Syntax().Pos())
return c.attachDebugInfoRaw(f, c.getFunction(f), "", pos.Filename, pos.Line)
}
// attachDebugInfo adds debug info to a function declaration. It returns the
// DISubprogram metadata node. This method allows some more control over how
// debug info is added to the function.
func (c *compilerContext) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix, filename string, line int) llvm.Metadata {
func (c *compilerContext) attachDebugInfoRaw(f *ssa.Function, llvmFn llvm.Value, suffix, filename string, line int) llvm.Metadata {
// Debug info for this function.
diparams := make([]llvm.Metadata, 0, len(f.Params))
for _, param := range f.Params {
@@ -772,7 +824,7 @@ func (c *compilerContext) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value,
})
difunc := c.dibuilder.CreateFunction(c.getDIFile(filename), llvm.DIFunction{
Name: f.RelString(nil) + suffix,
LinkageName: f.LinkName() + suffix,
LinkageName: c.getFunctionInfo(f).linkName + suffix,
File: c.getDIFile(filename),
Line: line,
Type: diFuncType,
@@ -800,37 +852,99 @@ func (c *compilerContext) getDIFile(filename string) llvm.Metadata {
return c.difiles[filename]
}
// createFunctionDefinition builds the LLVM IR implementation for this function.
// The function must be declared but not yet defined, otherwise this function
// will create a diagnostic.
func (b *builder) createFunctionDefinition() {
if b.DumpSSA() {
fmt.Printf("\nfunc %s:\n", b.fn.Function)
func (c *compilerContext) createPackage(pkg *ssa.Package, irbuilder llvm.Builder) {
memberNames := make([]string, 0)
for name := range pkg.Members {
memberNames = append(memberNames, name)
}
if !b.fn.LLVMFn.IsDeclaration() {
sort.Strings(memberNames)
for _, name := range memberNames {
switch member := pkg.Members[name].(type) {
case *ssa.Function:
llvmFn := c.getFunction(member)
if member.Blocks == nil {
continue // external function
}
c.createFunction(irbuilder, member, llvmFn)
case *ssa.Type:
if types.IsInterface(member.Type()) {
// Interfaces don't have concrete methods.
continue
}
// Named type. We should make sure all methods are created.
// This includes both functions with pointer receivers and those
// without.
methods := getAllMethods(pkg.Prog, member.Type())
methods = append(methods, getAllMethods(pkg.Prog, types.NewPointer(member.Type()))...)
for _, method := range methods {
// Parse this method.
fn := pkg.Prog.MethodValue(method)
if fn.Blocks == nil {
continue // external function
}
if member.Type().String() != member.String() {
// This is a member on a type alias. Do not build such a
// function.
continue
}
c.createFunction(irbuilder, fn, c.getFunction(fn))
}
case *ssa.Global:
// Make sure the global is present and has an initializer.
c.getGlobal(member)
case *ssa.NamedConst:
// TODO: create DWARF entries for these.
default:
panic("unknown member type: " + member.String())
}
}
}
// createFunction builds the LLVM IR implementation for this function. The
// function must not yet be defined, otherwise this function will create a
// diagnostic.
func (c *compilerContext) createFunction(irbuilder llvm.Builder, fn *ssa.Function, llvmFn llvm.Value) {
b := builder{
compilerContext: c,
Builder: irbuilder,
fn: fn,
llvmFn: llvmFn,
info: c.getFunctionInfo(fn),
locals: make(map[ssa.Value]llvm.Value),
dilocals: make(map[*types.Var]llvm.Metadata),
blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock),
blockExits: make(map[*ssa.BasicBlock]llvm.BasicBlock),
}
if b.DumpSSA() {
fmt.Printf("\nfunc %s:\n", b.fn)
}
if !b.llvmFn.IsDeclaration() {
errValue := b.fn.Name() + " redeclared in this program"
fnPos := getPosition(b.fn.LLVMFn)
fnPos := getPosition(b.llvmFn)
if fnPos.IsValid() {
errValue += "\n\tprevious declaration at " + fnPos.String()
}
b.addError(b.fn.Pos(), errValue)
return
}
if !b.fn.IsExported() {
b.fn.LLVMFn.SetLinkage(llvm.InternalLinkage)
b.fn.LLVMFn.SetUnnamedAddr(true)
if !b.info.exported {
b.llvmFn.SetLinkage(llvm.InternalLinkage)
b.llvmFn.SetUnnamedAddr(true)
}
// Some functions have a pragma controlling the inlining level.
switch b.fn.Inline() {
case ir.InlineHint:
switch b.info.inline {
case inlineHint:
// Add LLVM inline hint to functions with //go:inline pragma.
inline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0)
b.fn.LLVMFn.AddFunctionAttr(inline)
case ir.InlineNone:
b.llvmFn.AddFunctionAttr(inline)
case inlineNone:
// Add LLVM attribute to always avoid inlining this function.
noinline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0)
b.fn.LLVMFn.AddFunctionAttr(noinline)
b.llvmFn.AddFunctionAttr(noinline)
}
// Add debug info, if needed.
@@ -839,18 +953,18 @@ func (b *builder) createFunctionDefinition() {
// Package initializers have no debug info. Create some fake debug
// info to at least have *something*.
filename := b.fn.Package().Pkg.Path() + "/<init>"
b.difunc = b.attachDebugInfoRaw(b.fn, b.fn.LLVMFn, "", filename, 0)
b.difunc = b.attachDebugInfoRaw(b.fn, b.llvmFn, "", filename, 0)
} else if b.fn.Syntax() != nil {
// Create debug info file if needed.
b.difunc = b.attachDebugInfo(b.fn)
}
pos := b.ir.Program.Fset.Position(b.fn.Pos())
pos := b.program.Fset.Position(b.fn.Pos())
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
}
// Pre-create all basic blocks in the function.
for _, block := range b.fn.DomPreorder() {
llvmBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, block.Comment)
llvmBlock := b.ctx.AddBasicBlock(b.llvmFn, block.Comment)
b.blockEntries[block] = llvmBlock
b.blockExits[block] = llvmBlock
}
@@ -863,7 +977,7 @@ func (b *builder) createFunctionDefinition() {
llvmType := b.getLLVMType(param.Type())
fields := make([]llvm.Value, 0, 1)
for _, info := range expandFormalParamType(llvmType, param.Name(), param.Type()) {
param := b.fn.LLVMFn.Param(llvmParamIndex)
param := b.llvmFn.Param(llvmParamIndex)
param.SetName(info.name)
fields = append(fields, param)
llvmParamIndex++
@@ -894,8 +1008,8 @@ func (b *builder) createFunctionDefinition() {
// Load free variables from the context. This is a closure (or bound
// method).
var context llvm.Value
if !b.fn.IsExported() {
parentHandle := b.fn.LLVMFn.LastParam()
if !b.info.exported {
parentHandle := b.llvmFn.LastParam()
parentHandle.SetName("parentHandle")
context = llvm.PrevParam(parentHandle)
context.SetName("context")
@@ -946,7 +1060,7 @@ func (b *builder) createFunctionDefinition() {
continue
}
dbgVar := b.getLocalVariable(variable)
pos := b.ir.Program.Fset.Position(instr.Pos())
pos := b.program.Fset.Position(instr.Pos())
b.dibuilder.InsertValueAtEnd(b.getValue(instr.X), dbgVar, b.dibuilder.CreateExpression(nil), llvm.DebugLoc{
Line: uint(pos.Line),
Col: uint(pos.Column),
@@ -989,13 +1103,18 @@ func (b *builder) createFunctionDefinition() {
b.trackValue(phi.llvm)
}
}
// Compile all anonymous functions part of this function.
for _, fn := range b.fn.AnonFuncs {
b.createFunction(b.Builder, fn, b.getFunction(fn))
}
}
// createInstruction builds the LLVM IR equivalent instructions for the
// particular Go SSA instruction.
func (b *builder) createInstruction(instr ssa.Instruction) {
if b.Debug() {
pos := b.ir.Program.Fset.Position(instr.Pos())
pos := b.program.Fset.Position(instr.Pos())
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
}
@@ -1031,7 +1150,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new
// goroutine.
calleeFn := b.ir.GetFunction(callee)
calleeFn := b.getFunction(callee)
var context llvm.Value
switch value := instr.Call.Value.(type) {
case *ssa.Function:
@@ -1046,7 +1165,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
panic("StaticCallee returned an unexpected value")
}
params = append(params, context) // context parameter
b.createGoInstruction(calleeFn.LLVMFn, params, "", callee.Pos())
b.createGoInstruction(calleeFn, params, "", callee.Pos())
} else if !instr.Call.IsInvoke() {
// This is a function pointer.
// At the moment, two extra params are passed to the newly started
@@ -1094,7 +1213,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
b.CreateRet(b.getValue(instr.Results[0]))
} else {
// Multiple return values. Put them all in a struct.
retVal := llvm.ConstNull(b.fn.LLVMFn.Type().ElementType().ReturnType())
retVal := llvm.ConstNull(b.llvmFn.Type().ElementType().ReturnType())
for i, result := range instr.Results {
val := b.getValue(result)
retVal = b.CreateInsertValue(retVal, val, i, "")
@@ -1320,7 +1439,15 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
case strings.HasPrefix(name, "device/arm.SVCall"):
return b.emitSVCall(instr.Args)
case strings.HasPrefix(name, "(device/riscv.CSR)."):
return b.emitCSROperation(instr)
// The device/riscv.CSR operations must happen on constants.
// However, the compiler also creates pointer receivers for this
// operation which do not provide constant parameters. Therefore, do
// not create the regular inline asm for such functions but leave
// the call to an undefined function instead.
recv := b.fn.Signature.Recv()
if recv == nil || recv.Type().String() != "*device/riscv.CSR" {
return b.emitCSROperation(instr)
}
case strings.HasPrefix(name, "syscall.Syscall"):
return b.createSyscall(instr)
case strings.HasPrefix(name, "runtime/volatile.Load"):
@@ -1331,9 +1458,10 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
return b.createInterruptGlobal(instr)
}
targetFunc := b.ir.GetFunction(fn)
if targetFunc.LLVMFn.IsNil() {
return llvm.Value{}, b.makeError(instr.Pos(), "undefined function: "+targetFunc.LinkName())
callee = b.getFunction(fn)
info := b.getFunctionInfo(fn)
if callee.IsNil() {
return llvm.Value{}, b.makeError(instr.Pos(), "undefined function: "+info.linkName)
}
switch value := instr.Value.(type) {
case *ssa.Function:
@@ -1347,8 +1475,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
default:
panic("StaticCallee returned an unexpected value")
}
callee = targetFunc.LLVMFn
exported = targetFunc.IsExported()
exported = info.exported
} else if call, ok := instr.Value.(*ssa.Builtin); ok {
// Builtin function (append, close, delete, etc.).)
return b.createBuiltin(instr.Args, call.Name(), instr.Pos())
@@ -1383,14 +1510,15 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
func (b *builder) getValue(expr ssa.Value) llvm.Value {
switch expr := expr.(type) {
case *ssa.Const:
return b.createConst(b.fn.LinkName(), expr)
return b.createConst(b.info.linkName, expr)
case *ssa.Function:
fn := b.ir.GetFunction(expr)
if fn.IsExported() {
info := b.getFunctionInfo(expr)
if info.exported {
b.addError(expr.Pos(), "cannot use an exported function as value: "+expr.String())
return llvm.Undef(b.getLLVMType(expr.Type()))
}
return b.createFuncValue(fn.LLVMFn, llvm.Undef(b.i8ptrType), fn.Signature)
llvmFn := b.getFunction(expr)
return b.createFuncValue(llvmFn, llvm.Undef(b.i8ptrType), expr.Signature)
case *ssa.Global:
value := b.getGlobal(expr)
if value.IsNil() {
+138
View File
@@ -0,0 +1,138 @@
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)
}
}
+17 -19
View File
@@ -15,7 +15,6 @@ package compiler
import (
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/ir"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
@@ -25,9 +24,9 @@ import (
// calls.
func (b *builder) deferInitFunc() {
// Some setup.
b.deferFuncs = make(map[*ir.Function]int)
b.deferFuncs = make(map[*ssa.Function]int)
b.deferInvokeFuncs = make(map[string]int)
b.deferClosureFuncs = make(map[*ir.Function]int)
b.deferClosureFuncs = make(map[*ssa.Function]int)
// Create defer list pointer.
deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)
@@ -104,13 +103,12 @@ func (b *builder) createDefer(instr *ssa.Defer) {
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
// Regular function call.
fn := b.ir.GetFunction(callee)
if _, ok := b.deferFuncs[fn]; !ok {
b.deferFuncs[fn] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, fn)
if _, ok := b.deferFuncs[callee]; !ok {
b.deferFuncs[callee] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, callee)
}
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[fn]), false)
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[callee]), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields).
@@ -132,7 +130,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
context := b.CreateExtractValue(closure, 0, "")
// Get the callback number.
fn := b.ir.GetFunction(makeClosure.Fn.(*ssa.Function))
fn := makeClosure.Fn.(*ssa.Function)
if _, ok := b.deferClosureFuncs[fn]; !ok {
b.deferClosureFuncs[fn] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, makeClosure)
@@ -203,10 +201,10 @@ func (b *builder) createRunDefers() {
// }
// Create loop.
loophead := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loophead")
loop := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loop")
unreachable := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.default")
end := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.end")
loophead := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loophead")
loop := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loop")
unreachable := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.default")
end := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.end")
b.CreateBr(loophead)
// Create loop head:
@@ -238,7 +236,7 @@ func (b *builder) createRunDefers() {
// Create switch case, for example:
// case 0:
// // run first deferred call
block := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.callback")
block := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.callback")
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
b.SetInsertPointAtEnd(block)
switch callback := callback.(type) {
@@ -279,7 +277,7 @@ func (b *builder) createRunDefers() {
fnPtr := b.getInvokePtr(callback, typecode)
b.createCall(fnPtr, forwardParams, "")
case *ir.Function:
case *ssa.Function:
// Direct call.
// Get the real defer struct type and cast to it.
@@ -301,7 +299,7 @@ func (b *builder) createRunDefers() {
// Plain TinyGo functions add some extra parameters to implement async functionality and function recievers.
// These parameters should not be supplied when calling into an external C/ASM function.
if !callback.IsExported() {
if !b.getFunctionInfo(callback).exported {
// Add the context parameter. We know it is ignored by the receiving
// function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
@@ -311,11 +309,11 @@ func (b *builder) createRunDefers() {
}
// Call real function.
b.createCall(callback.LLVMFn, forwardParams, "")
b.createCall(b.getFunction(callback), forwardParams, "")
case *ssa.MakeClosure:
// Get the real defer struct type and cast to it.
fn := b.ir.GetFunction(callback.Fn.(*ssa.Function))
fn := callback.Fn.(*ssa.Function)
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ {
@@ -338,7 +336,7 @@ func (b *builder) createRunDefers() {
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
// Call deferred function.
b.createCall(fn.LLVMFn, forwardParams, "")
b.createCall(b.getFunction(fn), forwardParams, "")
default:
panic("unknown deferred function type")
+1 -1
View File
@@ -14,7 +14,7 @@ import (
// makeError makes it easy to create an error from a token.Pos with a message.
func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error {
return types.Error{
Fset: c.ir.Program.Fset,
Fset: c.program.Fset,
Pos: pos,
Msg: msg,
}
+12 -2
View File
@@ -5,6 +5,7 @@ package compiler
import (
"go/types"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
"golang.org/x/tools/go/ssa"
@@ -149,7 +150,16 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
if len(expr.Bindings) == 0 {
panic("unexpected: MakeClosure without bound variables")
}
f := b.ir.GetFunction(expr.Fn.(*ssa.Function))
f := expr.Fn.(*ssa.Function)
llvmFn := b.getFunction(f)
if strings.HasSuffix(f.Name(), "$bound") && llvmFn.IsDeclaration() {
// Hack: the ssa package does not expose bound methods so make sure
// they're built here when necessary.
irbuilder := b.ctx.NewBuilder()
defer irbuilder.Dispose()
b.createFunction(irbuilder, f, llvmFn)
}
// Collect all bound variables.
boundVars := make([]llvm.Value, len(expr.Bindings))
@@ -164,5 +174,5 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
context := b.emitPointerPack(boundVars)
// Create the closure.
return b.createFuncValue(f.LLVMFn, context, f.Signature), nil
return b.createFuncValue(llvmFn, context, f.Signature), nil
}
+5 -3
View File
@@ -7,6 +7,7 @@ import (
"go/token"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
@@ -28,7 +29,8 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p
default:
panic("unreachable")
}
b.createCall(b.mod.NamedFunction("internal/task.start"), []llvm.Value{callee, paramBundle, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
b.createCall(start, []llvm.Value{callee, paramBundle, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
}
@@ -73,7 +75,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
builder.SetInsertPointAtEnd(entry)
if c.Debug() {
pos := c.ir.Program.Fset.Position(pos)
pos := c.program.Fset.Position(pos)
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: c.getDIFile(pos.Filename),
Parameters: nil, // do not show parameters in debugger
@@ -129,7 +131,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
builder.SetInsertPointAtEnd(entry)
if c.Debug() {
pos := c.ir.Program.Fset.Position(pos)
pos := c.program.Fset.Position(pos)
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: c.getDIFile(pos.Filename),
Parameters: nil, // do not show parameters in debugger
+87 -21
View File
@@ -11,7 +11,6 @@ import (
"strconv"
"strings"
"github.com/tinygo-org/tinygo/ir"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
@@ -236,7 +235,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
ms := c.ir.Program.MethodSets.MethodSet(typ)
ms := c.program.MethodSets.MethodSet(typ)
if ms.Len() == 0 {
// no methods, so can leave that one out
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
@@ -247,15 +246,23 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
for i := 0; i < ms.Len(); i++ {
method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
f := c.ir.GetFunction(c.ir.Program.MethodValue(method))
if f.LLVMFn.IsNil() {
fn := c.program.MethodValue(method)
llvmFn := c.getFunction(fn)
if llvmFn.IsNil() {
// compiler error, so panic
panic("cannot find function: " + f.LinkName())
panic("cannot find function: " + c.getFunctionInfo(fn).linkName)
}
fn := c.getInterfaceInvokeWrapper(f)
if isAnonymous(typ) && llvmFn.IsDeclaration() {
// Inline types may also have methods when they embed interface
// types with methods. Example: struct{ error }
irbuilder := c.ctx.NewBuilder()
defer irbuilder.Dispose()
c.createFunction(irbuilder, fn, llvmFn)
}
wrapper := c.getInterfaceInvokeWrapper(fn, llvmFn)
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
signatureGlobal,
llvm.ConstPtrToInt(fn, c.uintptrType),
llvm.ConstPtrToInt(wrapper, c.uintptrType),
})
methods[i] = methodInfo
}
@@ -303,7 +310,7 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
// external *i8 indicating the indicating the signature of this method. It is
// used during the interface lowering pass.
func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
signature := ir.MethodSignature(method)
signature := methodSignature(method)
signatureGlobal := c.mod.NamedGlobal("func " + signature)
if signatureGlobal.IsNil() {
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
@@ -357,8 +364,8 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
// value.
prevBlock := b.GetInsertBlock()
okBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.ok")
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.next")
okBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.ok")
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
b.CreateCondBr(commaOk, okBlock, nextBlock)
@@ -436,8 +443,8 @@ func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value
// value, dereferences or unpacks it if necessary, and calls the real method.
// If the method to wrap has a pointer receiver, no wrapping is necessary and
// the function is returned directly.
func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
wrapperName := f.LinkName() + "$invoke"
func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llvm.Value) llvm.Value {
wrapperName := llvmFn.Name() + "$invoke"
wrapper := c.mod.NamedFunction(wrapperName)
if !wrapper.IsNil() {
// Wrapper already created. Return it directly.
@@ -445,7 +452,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
}
// Get the expanded receiver type.
receiverType := c.getLLVMType(f.Params[0].Type())
receiverType := c.getLLVMType(fn.Params[0].Type())
var expandedReceiverType []llvm.Type
for _, info := range expandFormalParamType(receiverType, "", nil) {
expandedReceiverType = append(expandedReceiverType, info.llvmType)
@@ -457,15 +464,15 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
// Casting a function signature to a different signature and calling it
// with a receiver pointer bitcasted to *i8 (as done in calls on an
// interface) is hopefully a safe (defined) operation.
return f.LLVMFn
return llvmFn
}
// create wrapper function
fnType := f.LLVMFn.Type().ElementType()
fnType := llvmFn.Type().ElementType()
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
if f.LLVMFn.LastParam().Name() == "parentHandle" {
if llvmFn.LastParam().Name() == "parentHandle" {
wrapper.LastParam().SetName("parentHandle")
}
@@ -481,8 +488,8 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
// add debug info if needed
if c.Debug() {
pos := c.ir.Program.Fset.Position(f.Pos())
difunc := c.attachDebugInfoRaw(f, wrapper, "$invoke", pos.Filename, pos.Line)
pos := c.program.Fset.Position(fn.Pos())
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
@@ -492,13 +499,72 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if f.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
b.CreateCall(f.LLVMFn, params, "")
if llvmFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
b.CreateCall(llvmFn, params, "")
b.CreateRetVoid()
} else {
ret := b.CreateCall(f.LLVMFn, params, "ret")
ret := b.CreateCall(llvmFn, params, "ret")
b.CreateRet(ret)
}
return wrapper
}
// isAnonymous returns true if (and only if) this is an anonymous type: one that
// is created inline. It can have methods if it embeds a type with methods.
func isAnonymous(typ types.Type) bool {
if t, ok := typ.(*types.Pointer); ok {
typ = t.Elem()
}
if _, ok := typ.(*types.Named); !ok {
return true
}
return false
}
// methodSignature creates a readable version of a method signature (including
// the function name, excluding the receiver name). This string is used
// internally to match interfaces and to call the correct method on an
// interface. Examples:
//
// String() string
// Read([]byte) (int, error)
func methodSignature(method *types.Func) string {
return method.Name() + signature(method.Type().(*types.Signature))
}
// Make a readable version of a function (pointer) signature.
// Examples:
//
// () string
// (string, int) (int, error)
func signature(sig *types.Signature) string {
s := ""
if sig.Params().Len() == 0 {
s += "()"
} else {
s += "("
for i := 0; i < sig.Params().Len(); i++ {
if i > 0 {
s += ", "
}
s += sig.Params().At(i).Type().String()
}
s += ")"
}
if sig.Results().Len() == 0 {
// keep as-is
} else if sig.Results().Len() == 1 {
s += " " + sig.Results().At(0).Type().String()
} else {
s += " ("
for i := 0; i < sig.Results().Len(); i++ {
if i > 0 {
s += ", "
}
s += sig.Results().At(i).Type().String()
}
s += ")"
}
return s
}
+2 -2
View File
@@ -39,7 +39,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// Create a new global of type runtime/interrupt.handle. Globals of this
// type are lowered in the interrupt lowering pass.
globalType := b.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalLLVMType := b.getLLVMType(globalType)
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
if global := b.mod.NamedGlobal(globalName); !global.IsNil() {
@@ -56,7 +56,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// Add debug info to the interrupt global.
if b.Debug() {
pos := b.ir.Program.Fset.Position(instr.Pos())
pos := b.program.Fset.Position(instr.Pos())
diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
LinkageName: globalName,
+344
View File
@@ -0,0 +1,344 @@
// +build none
// This file generates runtimetypes.go from the AST of the TinyGo runtime
// package. This type information is necessary to avoid having to compile the
// runtime to compile any package.
package main
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/token"
"io"
"io/ioutil"
"os"
"strings"
"golang.org/x/tools/go/packages"
)
// The list of runtime types known by the compiler.
var runtimeTypes = []string{
// panic, recover
"_defer",
// strings
"_string", "stringIterator",
// map
"hashmap", "hashmapBucket", "hashmapIterator",
// channel
"channel", "channelBlockedList", "chanSelectState",
// interface
"_interface", "interfaceMethodInfo", "typecodeID", "structField", "typeInInterface",
// func
"funcValue", "funcValueWithSignature",
}
// The list of runtime calls known to the compiler.
var runtimeCalls = []string{
// panic, recover
"_panic", "_recover",
"nilPanic", "lookupPanic", "slicePanic", "chanMakePanic", "negativeShiftPanic",
// string
"stringEqual", "stringLess",
"stringConcat",
"stringFromBytes", "stringToBytes",
"stringFromRunes", "stringToRunes",
"stringFromUnicode",
"stringNext",
// complex
"complex64div", "complex128div",
// slice
"sliceAppend", "sliceCopy",
// memory
"alloc", "trackPointer",
// print builtin
"printbool",
"printint8", "printuint8",
"printint16", "printuint16",
"printint32", "printuint32",
"printint64", "printuint64",
"printfloat32", "printfloat64",
"printcomplex64", "printcomplex128",
"printstring", "printspace", "printnl",
"printptr", "printmap", "printitf",
// hashmap
"hashmapMake", "hashmapLen", "hashmapNext",
"hashmapStringGet", "hashmapStringSet", "hashmapStringDelete",
"hashmapInterfaceGet", "hashmapInterfaceSet", "hashmapInterfaceDelete",
"hashmapBinaryGet", "hashmapBinarySet", "hashmapBinaryDelete",
// channel, concurrency
"tryChanSelect", "chanMake", "chanSend", "chanRecv", "chanClose", "chanSelect",
"deadlock",
// interface, reflect
"interfaceEqual", "interfaceImplements", "interfaceMethod",
"typeAssert", "interfaceTypeAssert",
// func
"getFuncPtr",
}
// makeDefs generates runtimetypes.go and writes it out after formatting it.
func makeDefs() error {
// Load the runtime package.
pkgs, err := packages.Load(&packages.Config{
Mode: packages.NeedSyntax,
BuildFlags: []string{"-tags=gc.extalloc"},
}, "../src/runtime")
if err != nil {
return err
}
if len(pkgs) != 1 {
return fmt.Errorf("expected 1 package, got %d", len(pkgs))
}
runtimePkg := pkgs[0]
if len(runtimePkg.Errors) != 0 {
return runtimePkg.Errors[0]
}
// Start creating the new Go file.
buf := &bytes.Buffer{}
buf.WriteString(`// Autogenerated by mkruntimetypes.go, DO NOT EDIT.
package compiler
// This file contains definitions for runtime types and functions, so that the
// runtime package can be compiled independently of other packages.
import (
"go/token"
"go/types"
"strconv"
)
// getRuntimeType constructs a new runtime type with the given name. The types
// constructed here must match the types in the runtime package.
func (c *compilerContext) getRuntimeType(name string) types.Type {
if c.program != nil {
return c.program.ImportedPackage("runtime").Type(name).Type()
}
if typ, ok := c.runtimeTypes[name]; ok {
// This type was already created.
return typ
}
typeName := types.NewTypeName(token.NoPos, c.runtimePkg, name, nil)
named := types.NewNamed(typeName, nil, nil)
// Make sure recursive types are only defined once.
c.runtimeTypes[name] = named
var fieldTypes []types.Type
switch name {
`)
err = makeTypeDefs(buf, runtimePkg)
if err != nil {
return err
}
buf.WriteString(` default:
panic("could not find runtime type: runtime." + name)
}
// Create the named struct type.
var fields []*types.Var
for i, t := range fieldTypes {
// Field name doesn't matter: this type is only used to create a LLVM
// struct type which don't have field names.
fields = append(fields, types.NewField(token.NoPos, nil, "field"+strconv.Itoa(i), t, false))
}
named.SetUnderlying(types.NewStruct(fields, nil))
return named
}
// getRuntimeFuncType constructs a new runtime function signature with the given
// name. The function signatures constructed here must match the functions in
// the runtime package.
func (c *compilerContext) getRuntimeFuncType(name string) *types.Signature {
var params []*types.Var
addParam := func(name string, typ types.Type) {
params = append(params, types.NewParam(token.NoPos, c.runtimePkg, name, typ))
}
var results []*types.Var
addResult := func(typ types.Type) {
results = append(results, types.NewParam(token.NoPos, c.runtimePkg, "", typ))
}
switch name {
`)
err = makeFuncDefs(buf, runtimePkg)
if err != nil {
return err
}
buf.WriteString(` default:
panic("unknown runtime call: runtime." + name)
}
return types.NewSignature(nil, types.NewTuple(params...), types.NewTuple(results...), false)
}
`)
source, err := format.Source(buf.Bytes())
if err != nil {
// Fallback (useful for investigating errors).
source = buf.Bytes()
}
err2 := ioutil.WriteFile("runtimetypes.go", source, 0666)
if err2 != nil {
return err2 // error from ioutil.WriteFile
}
return err // error from format.Source (if any)
}
// makeTypeDefs generates the switch body of the getRuntimeType function.
func makeTypeDefs(w io.Writer, runtimePkg *packages.Package) error {
typeSpecs := map[string]*ast.TypeSpec{}
for _, file := range runtimePkg.Syntax {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
if decl.Tok != token.TYPE {
continue
}
for _, spec := range decl.Specs {
typeSpec := spec.(*ast.TypeSpec)
typeSpecs[typeSpec.Name.Name] = typeSpec
}
}
}
}
for _, name := range runtimeTypes {
typeSpec := typeSpecs[name]
if typeSpec == nil {
return fmt.Errorf("could not find type: %s", name)
}
fmt.Fprintf(w, "\tcase %#v:\n", typeSpec.Name.Name)
if name == "channelBlockedList" {
fmt.Fprintf(w, "\t\ttaskType := types.NewNamed(types.NewTypeName(token.NoPos, c.taskPkg, \"Task\", nil), nil, nil)\n")
}
fmt.Fprintf(w, "\t\tfieldTypes = []types.Type{\n")
for _, field := range typeSpec.Type.(*ast.StructType).Fields.List {
fieldType := getTypeFromExpr(field.Type, typeSpec.Name.Name)
for _, ident := range field.Names {
fmt.Fprintf(w, "\t\t\t%s, // %s\n", fieldType, ident.Name)
}
}
fmt.Fprintf(w, "\t\t}\n")
}
return nil
}
// makeFuncDefs generates the switch body of the getRuntimeFuncType function.
func makeFuncDefs(w io.Writer, runtimePkg *packages.Package) error {
functions := map[string]*ast.FuncDecl{}
for _, file := range runtimePkg.Syntax {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.FuncDecl:
functions[decl.Name.Name] = decl
default:
}
}
}
for _, name := range runtimeCalls {
decl := functions[name]
if decl == nil {
return fmt.Errorf("could not find function: %s", name)
}
fmt.Fprintf(w, "\tcase %#v:\n", decl.Name.Name)
for _, field := range decl.Type.Params.List {
typeString := getTypeFromExpr(field.Type, "")
for _, name := range field.Names {
fmt.Fprintf(w, "\t\taddParam(%#v, %s)\n", name.Name, typeString)
}
}
if decl.Type.Results != nil {
for _, field := range decl.Type.Results.List {
typeString := getTypeFromExpr(field.Type, "")
for range field.Names {
fmt.Fprintf(w, "\t\taddResult(%s)\n", typeString)
}
if len(field.Names) == 0 {
fmt.Fprintf(w, "\t\taddResult(%s)\n", typeString)
}
}
}
}
return nil
}
// getTypeFromExpr returns a string which is a piece of Go code that constructs
// the type (as given in ast.Expr) using the go/types package.
func getTypeFromExpr(typ ast.Expr, currentTypeName string) string {
switch typ := typ.(type) {
case *ast.Ident:
if typ.Name == currentTypeName {
// Assume a global named "named" which refers to the currently
// created named type.
return "named"
}
switch typ.Name {
case "bool", "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "float32", "float64", "complex64", "complex128", "string", "byte", "rune":
// Built-in types.
return fmt.Sprintf("types.Typ[types.%s]", strings.Title(typ.Name))
case "chanState":
// runtime.chanState is a named type, but that doesn't matter when
// generating LLVM IR.
return fmt.Sprintf("types.Typ[types.Uint8]")
default:
// Assume that we can simply get the type recursively.
return fmt.Sprintf("c.getRuntimeType(%#v)", typ.Name)
}
case *ast.StarExpr:
return fmt.Sprintf("types.NewPointer(%s)", getTypeFromExpr(typ.X, currentTypeName))
case *ast.InterfaceType:
if len(typ.Methods.List) != 0 {
// Unimplemented: interfaces with methods.
return "interface{?}"
}
return "types.NewInterfaceType(nil, nil)"
case *ast.ArrayType:
// Slices and arrays. Assume the array length is a numeric constant and
// not a Go named constant for example.
elementType := getTypeFromExpr(typ.Elt, currentTypeName)
if typ.Len == nil {
return fmt.Sprintf("types.NewSlice(%s)", elementType)
}
length := typ.Len.(*ast.BasicLit).Value
return fmt.Sprintf("types.NewArray(%s, %s)", elementType, length)
case *ast.SelectorExpr:
s := typ.X.(*ast.Ident).Name + "." + typ.Sel.Name
switch s {
case "unsafe.Pointer":
return "types.Typ[types.UnsafePointer]"
case "task.Task":
// Assume there is a variable taskType which refers to the task.Task
// structure.
return "taskType"
default:
return fmt.Sprintf("<unknown %s>", s)
}
case *ast.StructType:
// Inline struct type.
var fields string
for _, field := range typ.Fields.List {
fieldType := getTypeFromExpr(field.Type, currentTypeName)
for _, ident := range field.Names {
fields += fmt.Sprintf("\t\t\ttypes.NewField(token.NoPos, nil, %#v, %s, false),\n", ident.Name, fieldType)
}
}
return fmt.Sprintf("types.NewStruct([]*types.Var{\n%s\t\t}, nil)", fields)
default:
// Dump the raw typ value, for debugging.
return fmt.Sprintf("%#v", typ)
}
}
func main() {
err := makeDefs()
if err != nil {
fmt.Fprintln(os.Stderr, "could not create defs:", err)
os.Exit(1)
}
}
+377
View File
@@ -0,0 +1,377 @@
// Autogenerated by mkruntimetypes.go, DO NOT EDIT.
package compiler
// This file contains definitions for runtime types and functions, so that the
// runtime package can be compiled independently of other packages.
import (
"go/token"
"go/types"
"strconv"
)
// getRuntimeType constructs a new runtime type with the given name. The types
// constructed here must match the types in the runtime package.
func (c *compilerContext) getRuntimeType(name string) types.Type {
if c.program != nil {
return c.program.ImportedPackage("runtime").Type(name).Type()
}
if typ, ok := c.runtimeTypes[name]; ok {
// This type was already created.
return typ
}
typeName := types.NewTypeName(token.NoPos, c.runtimePkg, name, nil)
named := types.NewNamed(typeName, nil, nil)
// Make sure recursive types are only defined once.
c.runtimeTypes[name] = named
var fieldTypes []types.Type
switch name {
case "_defer":
fieldTypes = []types.Type{
types.Typ[types.Uintptr], // callback
types.NewPointer(named), // next
}
case "_string":
fieldTypes = []types.Type{
types.NewPointer(types.Typ[types.Byte]), // ptr
types.Typ[types.Uintptr], // length
}
case "stringIterator":
fieldTypes = []types.Type{
types.Typ[types.Uintptr], // byteindex
}
case "hashmap":
fieldTypes = []types.Type{
types.NewPointer(named), // next
types.Typ[types.UnsafePointer], // buckets
types.Typ[types.Uintptr], // count
types.Typ[types.Uint8], // keySize
types.Typ[types.Uint8], // valueSize
types.Typ[types.Uint8], // bucketBits
}
case "hashmapBucket":
fieldTypes = []types.Type{
types.NewArray(types.Typ[types.Uint8], 8), // tophash
types.NewPointer(named), // next
}
case "hashmapIterator":
fieldTypes = []types.Type{
types.Typ[types.Uintptr], // bucketNumber
types.NewPointer(c.getRuntimeType("hashmapBucket")), // bucket
types.Typ[types.Uint8], // bucketIndex
}
case "channel":
fieldTypes = []types.Type{
types.Typ[types.Uintptr], // elementSize
types.Typ[types.Uintptr], // bufSize
types.Typ[types.Uint8], // state
types.NewPointer(c.getRuntimeType("channelBlockedList")), // blocked
types.Typ[types.Uintptr], // bufHead
types.Typ[types.Uintptr], // bufTail
types.Typ[types.Uintptr], // bufUsed
types.Typ[types.UnsafePointer], // buf
}
case "channelBlockedList":
taskType := types.NewNamed(types.NewTypeName(token.NoPos, c.taskPkg, "Task", nil), nil, nil)
fieldTypes = []types.Type{
types.NewPointer(named), // next
types.NewPointer(taskType), // t
types.NewPointer(c.getRuntimeType("chanSelectState")), // s
types.NewSlice(named), // allSelectOps
}
case "chanSelectState":
fieldTypes = []types.Type{
types.NewPointer(c.getRuntimeType("channel")), // ch
types.Typ[types.UnsafePointer], // value
}
case "_interface":
fieldTypes = []types.Type{
types.Typ[types.Uintptr], // typecode
types.Typ[types.UnsafePointer], // value
}
case "interfaceMethodInfo":
fieldTypes = []types.Type{
types.NewPointer(types.Typ[types.Uint8]), // signature
types.Typ[types.Uintptr], // funcptr
}
case "typecodeID":
fieldTypes = []types.Type{
types.NewPointer(named), // references
types.Typ[types.Uintptr], // length
}
case "structField":
fieldTypes = []types.Type{
types.NewPointer(c.getRuntimeType("typecodeID")), // typecode
types.NewPointer(types.Typ[types.Uint8]), // name
types.NewPointer(types.Typ[types.Uint8]), // tag
types.Typ[types.Bool], // embedded
}
case "typeInInterface":
fieldTypes = []types.Type{
types.NewPointer(c.getRuntimeType("typecodeID")), // typecode
types.NewPointer(c.getRuntimeType("interfaceMethodInfo")), // methodSet
}
case "funcValue":
fieldTypes = []types.Type{
types.Typ[types.UnsafePointer], // context
types.Typ[types.Uintptr], // id
}
case "funcValueWithSignature":
fieldTypes = []types.Type{
types.Typ[types.Uintptr], // funcPtr
types.NewPointer(c.getRuntimeType("typecodeID")), // signature
}
default:
panic("could not find runtime type: runtime." + name)
}
// Create the named struct type.
var fields []*types.Var
for i, t := range fieldTypes {
// Field name doesn't matter: this type is only used to create a LLVM
// struct type which don't have field names.
fields = append(fields, types.NewField(token.NoPos, nil, "field"+strconv.Itoa(i), t, false))
}
named.SetUnderlying(types.NewStruct(fields, nil))
return named
}
// getRuntimeFuncType constructs a new runtime function signature with the given
// name. The function signatures constructed here must match the functions in
// the runtime package.
func (c *compilerContext) getRuntimeFuncType(name string) *types.Signature {
var params []*types.Var
addParam := func(name string, typ types.Type) {
params = append(params, types.NewParam(token.NoPos, c.runtimePkg, name, typ))
}
var results []*types.Var
addResult := func(typ types.Type) {
results = append(results, types.NewParam(token.NoPos, c.runtimePkg, "", typ))
}
switch name {
case "_panic":
addParam("message", types.NewInterfaceType(nil, nil))
case "_recover":
addResult(types.NewInterfaceType(nil, nil))
case "nilPanic":
case "lookupPanic":
case "slicePanic":
case "chanMakePanic":
case "negativeShiftPanic":
case "stringEqual":
addParam("x", types.Typ[types.String])
addParam("y", types.Typ[types.String])
addResult(types.Typ[types.Bool])
case "stringLess":
addParam("x", types.Typ[types.String])
addParam("y", types.Typ[types.String])
addResult(types.Typ[types.Bool])
case "stringConcat":
addParam("x", c.getRuntimeType("_string"))
addParam("y", c.getRuntimeType("_string"))
addResult(c.getRuntimeType("_string"))
case "stringFromBytes":
addParam("x", types.NewStruct([]*types.Var{
types.NewField(token.NoPos, nil, "ptr", types.NewPointer(types.Typ[types.Byte]), false),
types.NewField(token.NoPos, nil, "len", types.Typ[types.Uintptr], false),
types.NewField(token.NoPos, nil, "cap", types.Typ[types.Uintptr], false),
}, nil))
addResult(c.getRuntimeType("_string"))
case "stringToBytes":
addParam("x", c.getRuntimeType("_string"))
addResult(types.NewStruct([]*types.Var{
types.NewField(token.NoPos, nil, "ptr", types.NewPointer(types.Typ[types.Byte]), false),
types.NewField(token.NoPos, nil, "len", types.Typ[types.Uintptr], false),
types.NewField(token.NoPos, nil, "cap", types.Typ[types.Uintptr], false),
}, nil))
case "stringFromRunes":
addParam("runeSlice", types.NewSlice(types.Typ[types.Rune]))
addResult(c.getRuntimeType("_string"))
case "stringToRunes":
addParam("s", types.Typ[types.String])
addResult(types.NewSlice(types.Typ[types.Rune]))
case "stringFromUnicode":
addParam("x", types.Typ[types.Rune])
addResult(c.getRuntimeType("_string"))
case "stringNext":
addParam("s", types.Typ[types.String])
addParam("it", types.NewPointer(c.getRuntimeType("stringIterator")))
addResult(types.Typ[types.Bool])
addResult(types.Typ[types.Int])
addResult(types.Typ[types.Rune])
case "complex64div":
addParam("n", types.Typ[types.Complex64])
addParam("m", types.Typ[types.Complex64])
addResult(types.Typ[types.Complex64])
case "complex128div":
addParam("n", types.Typ[types.Complex128])
addParam("m", types.Typ[types.Complex128])
addResult(types.Typ[types.Complex128])
case "sliceAppend":
addParam("srcBuf", types.Typ[types.UnsafePointer])
addParam("elemsBuf", types.Typ[types.UnsafePointer])
addParam("srcLen", types.Typ[types.Uintptr])
addParam("srcCap", types.Typ[types.Uintptr])
addParam("elemsLen", types.Typ[types.Uintptr])
addParam("elemSize", types.Typ[types.Uintptr])
addResult(types.Typ[types.UnsafePointer])
addResult(types.Typ[types.Uintptr])
addResult(types.Typ[types.Uintptr])
case "sliceCopy":
addParam("dst", types.Typ[types.UnsafePointer])
addParam("src", types.Typ[types.UnsafePointer])
addParam("dstLen", types.Typ[types.Uintptr])
addParam("srcLen", types.Typ[types.Uintptr])
addParam("elemSize", types.Typ[types.Uintptr])
addResult(types.Typ[types.Int])
case "alloc":
addParam("size", types.Typ[types.Uintptr])
addResult(types.Typ[types.UnsafePointer])
case "trackPointer":
addParam("ptr", types.Typ[types.UnsafePointer])
case "printbool":
addParam("b", types.Typ[types.Bool])
case "printint8":
addParam("n", types.Typ[types.Int8])
case "printuint8":
addParam("n", types.Typ[types.Uint8])
case "printint16":
addParam("n", types.Typ[types.Int16])
case "printuint16":
addParam("n", types.Typ[types.Uint16])
case "printint32":
addParam("n", types.Typ[types.Int32])
case "printuint32":
addParam("n", types.Typ[types.Uint32])
case "printint64":
addParam("n", types.Typ[types.Int64])
case "printuint64":
addParam("n", types.Typ[types.Uint64])
case "printfloat32":
addParam("v", types.Typ[types.Float32])
case "printfloat64":
addParam("v", types.Typ[types.Float64])
case "printcomplex64":
addParam("c", types.Typ[types.Complex64])
case "printcomplex128":
addParam("c", types.Typ[types.Complex128])
case "printstring":
addParam("s", types.Typ[types.String])
case "printspace":
case "printnl":
case "printptr":
addParam("ptr", types.Typ[types.Uintptr])
case "printmap":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
case "printitf":
addParam("msg", types.NewInterfaceType(nil, nil))
case "hashmapMake":
addParam("keySize", types.Typ[types.Uint8])
addParam("valueSize", types.Typ[types.Uint8])
addParam("sizeHint", types.Typ[types.Uintptr])
addResult(types.NewPointer(c.getRuntimeType("hashmap")))
case "hashmapLen":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addResult(types.Typ[types.Int])
case "hashmapNext":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("it", types.NewPointer(c.getRuntimeType("hashmapIterator")))
addParam("key", types.Typ[types.UnsafePointer])
addParam("value", types.Typ[types.UnsafePointer])
addResult(types.Typ[types.Bool])
case "hashmapStringGet":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.Typ[types.String])
addParam("value", types.Typ[types.UnsafePointer])
addParam("valueSize", types.Typ[types.Uintptr])
addResult(types.Typ[types.Bool])
case "hashmapStringSet":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.Typ[types.String])
addParam("value", types.Typ[types.UnsafePointer])
case "hashmapStringDelete":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.Typ[types.String])
case "hashmapInterfaceGet":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.NewInterfaceType(nil, nil))
addParam("value", types.Typ[types.UnsafePointer])
addParam("valueSize", types.Typ[types.Uintptr])
addResult(types.Typ[types.Bool])
case "hashmapInterfaceSet":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.NewInterfaceType(nil, nil))
addParam("value", types.Typ[types.UnsafePointer])
case "hashmapInterfaceDelete":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.NewInterfaceType(nil, nil))
case "hashmapBinaryGet":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.Typ[types.UnsafePointer])
addParam("value", types.Typ[types.UnsafePointer])
addParam("valueSize", types.Typ[types.Uintptr])
addResult(types.Typ[types.Bool])
case "hashmapBinarySet":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.Typ[types.UnsafePointer])
addParam("value", types.Typ[types.UnsafePointer])
case "hashmapBinaryDelete":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.Typ[types.UnsafePointer])
case "tryChanSelect":
addParam("recvbuf", types.Typ[types.UnsafePointer])
addParam("states", types.NewSlice(c.getRuntimeType("chanSelectState")))
addResult(types.Typ[types.Uintptr])
addResult(types.Typ[types.Bool])
case "chanMake":
addParam("elementSize", types.Typ[types.Uintptr])
addParam("bufSize", types.Typ[types.Uintptr])
addResult(types.NewPointer(c.getRuntimeType("channel")))
case "chanSend":
addParam("ch", types.NewPointer(c.getRuntimeType("channel")))
addParam("value", types.Typ[types.UnsafePointer])
case "chanRecv":
addParam("ch", types.NewPointer(c.getRuntimeType("channel")))
addParam("value", types.Typ[types.UnsafePointer])
addResult(types.Typ[types.Bool])
case "chanClose":
addParam("ch", types.NewPointer(c.getRuntimeType("channel")))
case "chanSelect":
addParam("recvbuf", types.Typ[types.UnsafePointer])
addParam("states", types.NewSlice(c.getRuntimeType("chanSelectState")))
addParam("ops", types.NewSlice(c.getRuntimeType("channelBlockedList")))
addResult(types.Typ[types.Uintptr])
addResult(types.Typ[types.Bool])
case "deadlock":
case "interfaceEqual":
addParam("x", types.NewInterfaceType(nil, nil))
addParam("y", types.NewInterfaceType(nil, nil))
addResult(types.Typ[types.Bool])
case "interfaceImplements":
addParam("typecode", types.Typ[types.Uintptr])
addParam("interfaceMethodSet", types.NewPointer(types.NewPointer(types.Typ[types.Uint8])))
addResult(types.Typ[types.Bool])
case "interfaceMethod":
addParam("typecode", types.Typ[types.Uintptr])
addParam("interfaceMethodSet", types.NewPointer(types.NewPointer(types.Typ[types.Uint8])))
addParam("signature", types.NewPointer(types.Typ[types.Uint8]))
addResult(types.Typ[types.Uintptr])
case "typeAssert":
addParam("actualType", types.Typ[types.Uintptr])
addParam("assertedType", types.NewPointer(c.getRuntimeType("typecodeID")))
addResult(types.Typ[types.Bool])
case "interfaceTypeAssert":
addParam("ok", types.Typ[types.Bool])
case "getFuncPtr":
addParam("val", c.getRuntimeType("funcValue"))
addParam("signature", types.NewPointer(c.getRuntimeType("typecodeID")))
addResult(types.Typ[types.Uintptr])
default:
panic("unknown runtime call: runtime." + name)
}
return types.NewSignature(nil, types.NewTuple(params...), types.NewTuple(results...), false)
}
+223 -1
View File
@@ -15,6 +15,208 @@ import (
"tinygo.org/x/go-llvm"
)
type inlineType int
// How much to inline.
const (
// Default behavior. The compiler decides for itself whether any given
// function will be inlined. Whether any function is inlined depends on the
// optimization level.
inlineDefault inlineType = iota
// Inline hint, just like the C inline keyword (signalled using
// //go:inline). The compiler will be more likely to inline this function,
// but it is not a guarantee.
inlineHint
// Don't inline, just like the GCC noinline attribute. Signalled using
// //go:noinline.
inlineNone
)
// functionInfo contains some information about a function or method. In
// particular, it contains information obtained from pragmas.
//
// The linkName value contains a valid link name, even though //go:linkname is
// not present.
type functionInfo struct {
linkName string // go:linkname, go:export
module string // go:wasm-module
exported bool // go:export
nobounds bool // go:nobounds
inline inlineType // go:inline
}
// getFunction returns the LLVM function for the given *ssa.Function, creating
// it if needed. It can later be filled with compilerContext.createFunction().
func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
info := c.getFunctionInfo(fn)
return c.getFunctionRaw(fn.Signature, info)
}
func (c *compilerContext) getFunctionRaw(sig *types.Signature, info functionInfo) llvm.Value {
llvmFn := c.mod.NamedFunction(info.linkName)
if !llvmFn.IsNil() {
return llvmFn
}
var retType llvm.Type
if sig.Results() == nil {
retType = c.ctx.VoidType()
} else if sig.Results().Len() == 1 {
retType = c.getLLVMType(sig.Results().At(0).Type())
} else {
results := make([]llvm.Type, 0, sig.Results().Len())
for i := 0; i < sig.Results().Len(); i++ {
results = append(results, c.getLLVMType(sig.Results().At(i).Type()))
}
retType = c.ctx.StructType(results, false)
}
var paramInfos []paramInfo
params := []*types.Var{}
if sig.Recv() != nil {
params = append(params, sig.Recv())
}
for i := 0; i < sig.Params().Len(); i++ {
params = append(params, sig.Params().At(i))
}
for _, param := range params {
paramType := c.getLLVMType(param.Type())
paramFragmentInfos := expandFormalParamType(paramType, param.Name(), param.Type())
paramInfos = append(paramInfos, paramFragmentInfos...)
}
// Add an extra parameter as the function context. This context is used in
// closures and bound methods, but should be optimized away when not used.
if !info.exported {
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", flags: 0})
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "parentHandle", flags: 0})
}
var paramTypes []llvm.Type
for _, info := range paramInfos {
paramTypes = append(paramTypes, info.llvmType)
}
fnType := llvm.FunctionType(retType, paramTypes, false)
llvmFn = llvm.AddFunction(c.mod, info.linkName, fnType)
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
for i, info := range paramInfos {
if info.flags&paramIsDeferenceableOrNull == 0 {
continue
}
if info.llvmType.TypeKind() == llvm.PointerTypeKind {
el := info.llvmType.ElementType()
size := c.targetData.TypeAllocSize(el)
if size == 0 {
// dereferenceable_or_null(0) appears to be illegal in LLVM.
continue
}
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, size)
llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull)
}
}
// External/exported functions may not retain pointer values.
// https://golang.org/cmd/cgo/#hdr-Passing_pointers
if info.exported {
// Set the wasm-import-module attribute if the function's module is set.
if info.module != "" {
wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", info.module)
llvmFn.AddFunctionAttr(wasmImportModuleAttr)
}
nocaptureKind := llvm.AttributeKindID("nocapture")
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
for i, typ := range paramTypes {
if typ.TypeKind() == llvm.PointerTypeKind {
llvmFn.AddAttributeAtIndex(i+1, nocapture)
}
}
}
return llvmFn
}
// getFunctionInfo returns information about a function that is not directly
// present in *ssa.Function, such as the link name and whether it should be
// exported.
func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
info := functionInfo{}
if strings.HasPrefix(f.Name(), "C.") {
// Created by CGo: such a name cannot be created by regular C code.
info.linkName = f.Name()[2:]
info.exported = true
} else {
// Pick the default linkName.
info.linkName = f.RelString(nil)
// Check for //go: pragmas, which may change the link name (among
// others).
info.parsePragmas(f)
}
return info
}
// parsePragmas is used by getFunctionInfo to parse function pragmas such as
// //export or //go:noinline.
func (info *functionInfo) parsePragmas(f *ssa.Function) {
// Parse compiler directives in the preceding comments.
if f.Syntax() == nil {
return
}
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
for _, comment := range decl.Doc.List {
text := comment.Text
if strings.HasPrefix(text, "//export ") {
// Rewrite '//export' to '//go:export' for compatibility with
// gc.
text = "//go:" + text[2:]
}
if !strings.HasPrefix(text, "//go:") {
continue
}
parts := strings.Fields(text)
switch parts[0] {
case "//go:export":
if len(parts) != 2 {
continue
}
info.linkName = parts[1]
info.exported = true
case "//go:wasm-module":
// Alternative comment for setting the import module.
if len(parts) != 2 {
continue
}
info.module = parts[1]
case "//go:inline":
info.inline = inlineHint
case "//go:noinline":
info.inline = inlineNone
case "//go:linkname":
if len(parts) != 3 || parts[1] != f.Name() {
continue
}
// Only enable go:linkname when the package imports "unsafe".
// This is a slightly looser requirement than what gc uses: gc
// requires the file to import "unsafe", not the package as a
// whole.
if hasUnsafeImport(f.Pkg.Pkg) {
info.linkName = parts[2]
}
case "//go:nobounds":
// Skip bounds checking in this function. Useful for some
// runtime functions.
// This is somewhat dangerous and thus only imported in packages
// that import unsafe.
if hasUnsafeImport(f.Pkg.Pkg) {
info.nobounds = true
}
}
}
}
}
// globalInfo contains some information about a specific global. By default,
// linkName is equal to .RelString(nil) on a global and extern is false, but for
// some symbols this is different (due to //go:extern for example).
@@ -86,7 +288,7 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
// Add debug info.
// TODO: this should be done for every global in the program, not just
// the ones that are referenced from some code.
pos := c.ir.Program.Fset.Position(g.Pos())
pos := c.program.Fset.Position(g.Pos())
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
Name: g.RelString(nil),
LinkageName: info.linkName,
@@ -145,3 +347,23 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
}
}
}
// Get all methods of a type.
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
ms := prog.MethodSets.MethodSet(typ)
methods := make([]*types.Selection, ms.Len())
for i := 0; i < ms.Len(); i++ {
methods[i] = ms.At(i)
}
return methods
}
// Return true if this package imports "unsafe", false otherwise.
func hasUnsafeImport(pkg *types.Package) bool {
for _, imp := range pkg.Imports() {
if imp == types.Unsafe {
return true
}
}
return false
}
+13
View File
@@ -0,0 +1,13 @@
package main
func add(x, y int) int {
return x + y
}
func stringEqual(s string) bool {
return s == "s"
}
func closeChan(ch chan int) {
close(ch)
}
+41
View File
@@ -0,0 +1,41 @@
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
%"internal/task.Task" = type opaque
%runtime.chanSelectState = type { %runtime.channel*, i8* }
%runtime._string = type { i8*, i32 }
@"main.stringEqual$string" = internal unnamed_addr constant [1 x i8] c"s"
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.closeChan(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @runtime.chanClose(%runtime.channel* %ch, i8* undef, i8* null)
ret void
}
declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*)
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define internal i1 @main.stringEqual(i8* %s.data, i32 %s.len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = insertvalue %runtime._string zeroinitializer, i8* %s.data, 0
%1 = insertvalue %runtime._string %0, i32 %s.len, 1
%2 = extractvalue %runtime._string %1, 0
%3 = extractvalue %runtime._string %1, 1
%4 = call i1 @runtime.stringEqual(i8* %2, i32 %3, i8* getelementptr inbounds ([1 x i8], [1 x i8]* @"main.stringEqual$string", i32 0, i32 0), i32 1, i8* undef, i8* null)
ret i1 %4
}
declare i1 @runtime.stringEqual(i8*, i32, i8*, i32, i8*, i8*)
-64
View File
@@ -1,64 +0,0 @@
package goenv
import (
"errors"
"fmt"
"io"
"io/ioutil"
"path/filepath"
"regexp"
"strings"
)
// GetGorootVersion returns the major and minor version for a given GOROOT path.
// If the goroot cannot be determined, (0, 0) is returned.
func GetGorootVersion(goroot string) (major, minor int, err error) {
s, err := GorootVersionString(goroot)
if err != nil {
return 0, 0, err
}
if s == "" || s[:2] != "go" {
return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
}
parts := strings.Split(s[2:], ".")
if len(parts) < 2 {
return 0, 0, errors.New("could not parse Go version: version has less than two parts")
}
// Ignore the errors, we don't really handle errors here anyway.
var trailing string
n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing)
if n == 2 && err == io.EOF {
// Means there were no trailing characters (i.e., not an alpha/beta)
err = nil
}
if err != nil {
return 0, 0, fmt.Errorf("failed to parse version: %s", err)
}
return
}
// GorootVersionString returns the version string as reported by the Go
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but
// can have some variations (for beta releases, for example).
func GorootVersionString(goroot string) (string, error) {
if data, err := ioutil.ReadFile(filepath.Join(
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
r := regexp.MustCompile("const TheVersion = `(.*)`")
matches := r.FindSubmatch(data)
if len(matches) != 2 {
return "", errors.New("Invalid go version output:\n" + string(data))
}
return string(matches[1]), nil
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
return string(data), nil
} else {
return "", err
}
}
+2 -1
View File
@@ -283,6 +283,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
PkgName: fr.packagePath,
KeySize: int(keySize),
ValueSize: int(valueSize),
MapType: inst.Type().ElementType(),
}
case callee.Name() == "runtime.hashmapStringSet":
// set a string key in the map
@@ -350,7 +351,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
global.SetLinkage(llvm.InternalLinkage)
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
stringType := fr.Mod.GetTypeByName("runtime._string")
stringType := inst.Type()
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
retLen := llvm.ConstInt(stringType.StructElementTypes()[1], uint64(len(result)), false)
ret := llvm.ConstNull(stringType)
+50 -20
View File
@@ -178,6 +178,8 @@ type MapValue struct {
ValueSize int
KeyType llvm.Type
ValueType llvm.Type
MapType llvm.Type // *%runtime.hashmap
keyVariant string
}
func (v *MapValue) newBucket() llvm.Value {
@@ -221,27 +223,32 @@ func (v *MapValue) Value() llvm.Value {
var keyBuf []byte
llvmKey := key.Value()
llvmValue := v.Values[i].Value()
if key.Type().TypeKind() == llvm.StructTypeKind && key.Type().StructName() == "runtime._string" {
switch v.keyVariant {
case "string":
keyPtr := llvm.ConstExtractValue(llvmKey, []uint32{0})
keyLen := llvm.ConstExtractValue(llvmKey, []uint32{1})
keyPtrVal := v.Eval.getValue(keyPtr)
keyBuf = getStringBytes(keyPtrVal, keyLen)
} else if key.Type().TypeKind() == llvm.IntegerTypeKind {
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
n := key.Value().ZExtValue()
for i := range keyBuf {
keyBuf[i] = byte(n)
n >>= 8
case "binary":
if key.Type().TypeKind() == llvm.IntegerTypeKind {
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
n := key.Value().ZExtValue()
for i := range keyBuf {
keyBuf[i] = byte(n)
n >>= 8
}
} else if key.Type().TypeKind() == llvm.ArrayTypeKind &&
key.Type().ElementType().TypeKind() == llvm.IntegerTypeKind &&
key.Type().ElementType().IntTypeWidth() == 8 {
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
for i := range keyBuf {
keyBuf[i] = byte(llvm.ConstExtractValue(llvmKey, []uint32{uint32(i)}).ZExtValue())
}
} else {
panic("interp: map key type not implemented: " + key.Type().String())
}
} else if key.Type().TypeKind() == llvm.ArrayTypeKind &&
key.Type().ElementType().TypeKind() == llvm.IntegerTypeKind &&
key.Type().ElementType().IntTypeWidth() == 8 {
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
for i := range keyBuf {
keyBuf[i] = byte(llvm.ConstExtractValue(llvmKey, []uint32{uint32(i)}).ZExtValue())
}
} else {
panic("interp: map key type not implemented: " + key.Type().String())
default:
panic("interp: map key variant: " + v.keyVariant)
}
hash := v.hash(keyBuf)
@@ -291,7 +298,7 @@ func (v *MapValue) Value() llvm.Value {
// Type returns type runtime.hashmap, which is the actual hashmap type.
func (v *MapValue) Type() llvm.Type {
return v.Eval.Mod.GetTypeByName("runtime.hashmap")
return v.MapType
}
func (v *MapValue) IsConstant() bool {
@@ -314,12 +321,30 @@ func (v *MapValue) GetElementPtr(indices []uint32) (Value, error) {
return nil, errors.New("interp: GEP on a map")
}
// setKeyVariant sets the key variant as a result of storing to the hashmap
// (string, binary, or interface). The way that TinyGo is structured, the key
// variant is not known until there is a store to the hashmap.
// The key variant has to be known when lowering the hashmap to its final form,
// to correctly calculate the hash of a key (for example, a string key must
// calculate the hash over the string contents).
func (v *MapValue) setKeyVariant(keyVariant string) {
if v.keyVariant == "" {
v.keyVariant = keyVariant
return
}
if v.keyVariant != keyVariant {
// Valid IR will not cause this panic to occur.
panic("MapValue store with inconsistent key type")
}
}
// PutString does a map assign operation, assuming that the map is of type
// map[string]T.
func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) {
if !v.Underlying.IsNil() {
panic("map already created")
}
v.setKeyVariant("string")
if valPtr.Underlying.Opcode() == llvm.BitCast {
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
@@ -336,9 +361,13 @@ func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) {
}
}
keyType := v.Eval.Mod.GetTypeByName("runtime._string")
v.KeyType = keyType
key := llvm.ConstNull(keyType)
if v.KeyType.IsNil() {
v.KeyType = v.Eval.Mod.Context().StructType([]llvm.Type{
keyBuf.Type(),
keyLen.Type(),
}, false)
}
key := llvm.ConstNull(v.KeyType)
key = llvm.ConstInsertValue(key, keyBuf.Value(), []uint32{0})
key = llvm.ConstInsertValue(key, keyLen.Value(), []uint32{1})
@@ -352,6 +381,7 @@ func (v *MapValue) PutBinary(keyPtr, valPtr *LocalValue) {
if !v.Underlying.IsNil() {
panic("map already created")
}
v.setKeyVariant("binary")
if valPtr.Underlying.Opcode() == llvm.BitCast {
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
-309
View File
@@ -1,309 +0,0 @@
package ir
import (
"go/ast"
"go/types"
"sort"
"strings"
"github.com/tinygo-org/tinygo/loader"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// This file provides a wrapper around go/ssa values and adds extra
// functionality to them.
// View on all functions, types, and globals in a program, with analysis
// results.
type Program struct {
Program *ssa.Program
LoaderProgram *loader.Program
mainPkg *ssa.Package
Functions []*Function
functionMap map[*ssa.Function]*Function
}
// Function or method.
type Function struct {
*ssa.Function
LLVMFn llvm.Value
module string // go:wasm-module
linkName string // go:linkname, go:export
exported bool // go:export
nobounds bool // go:nobounds
flag bool // used by dead code elimination
inline InlineType // go:inline
}
// Interface type that is at some point used in a type assert (to check whether
// it implements another interface).
type Interface struct {
Num int
Type *types.Interface
}
type InlineType int
// How much to inline.
const (
// Default behavior. The compiler decides for itself whether any given
// function will be inlined. Whether any function is inlined depends on the
// optimization level.
InlineDefault InlineType = iota
// Inline hint, just like the C inline keyword (signalled using
// //go:inline). The compiler will be more likely to inline this function,
// but it is not a guarantee.
InlineHint
// Don't inline, just like the GCC noinline attribute. Signalled using
// //go:noinline.
InlineNone
)
// Create and initialize a new *Program from a *ssa.Program.
func NewProgram(lprogram *loader.Program) *Program {
program := lprogram.LoadSSA()
program.Build()
// Find the main package, which is a bit difficult when running a .go file
// directly.
mainPkg := program.ImportedPackage(lprogram.MainPkg.PkgPath)
if mainPkg == nil {
panic("could not find main package")
}
// Make a list of packages in import order.
packageList := []*ssa.Package{}
packageSet := map[string]struct{}{}
worklist := []string{"runtime", lprogram.MainPkg.PkgPath}
for len(worklist) != 0 {
pkgPath := worklist[0]
pkg := program.ImportedPackage(pkgPath)
if _, ok := packageSet[pkgPath]; ok {
// Package already in the final package list.
worklist = worklist[1:]
continue
}
unsatisfiedImports := make([]string, 0)
imports := pkg.Pkg.Imports()
for _, pkg := range imports {
if _, ok := packageSet[pkg.Path()]; ok {
continue
}
unsatisfiedImports = append(unsatisfiedImports, pkg.Path())
}
if len(unsatisfiedImports) == 0 {
// All dependencies of this package are satisfied, so add this
// package to the list.
packageList = append(packageList, pkg)
packageSet[pkgPath] = struct{}{}
worklist = worklist[1:]
} else {
// Prepend all dependencies to the worklist and reconsider this
// package (by not removing it from the worklist). At that point, it
// must be possible to add it to packageList.
worklist = append(unsatisfiedImports, worklist...)
}
}
p := &Program{
Program: program,
LoaderProgram: lprogram,
mainPkg: mainPkg,
functionMap: make(map[*ssa.Function]*Function),
}
for _, pkg := range packageList {
p.AddPackage(pkg)
}
return p
}
// Add a package to this Program. All packages need to be added first before any
// analysis is done for correct results.
func (p *Program) AddPackage(pkg *ssa.Package) {
memberNames := make([]string, 0)
for name := range pkg.Members {
memberNames = append(memberNames, name)
}
sort.Strings(memberNames)
for _, name := range memberNames {
member := pkg.Members[name]
switch member := member.(type) {
case *ssa.Function:
p.addFunction(member)
case *ssa.Type:
methods := getAllMethods(pkg.Prog, member.Type())
if !types.IsInterface(member.Type()) {
// named type
for _, method := range methods {
p.addFunction(pkg.Prog.MethodValue(method))
}
}
case *ssa.Global:
// Ignore. Globals are not handled here.
case *ssa.NamedConst:
// Ignore: these are already resolved.
default:
panic("unknown member type: " + member.String())
}
}
}
func (p *Program) addFunction(ssaFn *ssa.Function) {
if _, ok := p.functionMap[ssaFn]; ok {
return
}
f := &Function{Function: ssaFn}
f.parsePragmas()
p.Functions = append(p.Functions, f)
p.functionMap[ssaFn] = f
for _, anon := range ssaFn.AnonFuncs {
p.addFunction(anon)
}
}
// Return true if this package imports "unsafe", false otherwise.
func hasUnsafeImport(pkg *types.Package) bool {
for _, imp := range pkg.Imports() {
if imp == types.Unsafe {
return true
}
}
return false
}
func (p *Program) GetFunction(ssaFn *ssa.Function) *Function {
return p.functionMap[ssaFn]
}
func (p *Program) MainPkg() *ssa.Package {
return p.mainPkg
}
// Parse compiler directives in the preceding comments.
func (f *Function) parsePragmas() {
if f.Syntax() == nil {
return
}
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
for _, comment := range decl.Doc.List {
text := comment.Text
if strings.HasPrefix(text, "//export ") {
// Rewrite '//export' to '//go:export' for compatibility with
// gc.
text = "//go:" + text[2:]
}
if !strings.HasPrefix(text, "//go:") {
continue
}
parts := strings.Fields(text)
switch parts[0] {
case "//go:export":
if len(parts) != 2 {
continue
}
f.linkName = parts[1]
f.exported = true
case "//go:wasm-module":
// Alternative comment for setting the import module.
if len(parts) != 2 {
continue
}
f.module = parts[1]
case "//go:inline":
f.inline = InlineHint
case "//go:noinline":
f.inline = InlineNone
case "//go:linkname":
if len(parts) != 3 || parts[1] != f.Name() {
continue
}
// Only enable go:linkname when the package imports "unsafe".
// This is a slightly looser requirement than what gc uses: gc
// requires the file to import "unsafe", not the package as a
// whole.
if hasUnsafeImport(f.Pkg.Pkg) {
f.linkName = parts[2]
}
case "//go:nobounds":
// Skip bounds checking in this function. Useful for some
// runtime functions.
// This is somewhat dangerous and thus only imported in packages
// that import unsafe.
if hasUnsafeImport(f.Pkg.Pkg) {
f.nobounds = true
}
}
}
}
}
func (f *Function) IsNoBounds() bool {
return f.nobounds
}
// Return true iff this function is externally visible.
func (f *Function) IsExported() bool {
return f.exported || f.CName() != ""
}
// Return the inline directive of this function.
func (f *Function) Inline() InlineType {
return f.inline
}
// Return the module name if not the default.
func (f *Function) Module() string {
return f.module
}
// Return the link name for this function.
func (f *Function) LinkName() string {
if f.linkName != "" {
return f.linkName
}
if f.Signature.Recv() != nil {
// Method on a defined type (which may be a pointer).
return f.RelString(nil)
} else {
// Bare function.
if name := f.CName(); name != "" {
// Name CGo functions directly.
return name
} else {
return f.RelString(nil)
}
}
}
// Return the name of the C function if this is a CGo wrapper. Otherwise, return
// a zero-length string.
func (f *Function) CName() string {
name := f.Name()
if strings.HasPrefix(name, "_Cfunc_") {
// emitted by `go tool cgo`
return name[len("_Cfunc_"):]
}
if strings.HasPrefix(name, "C.") {
// created by ../loader/cgo.go
return name[2:]
}
return ""
}
// Get all methods of a type.
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
ms := prog.MethodSets.MethodSet(typ)
methods := make([]*types.Selection, ms.Len())
for i := 0; i < ms.Len(); i++ {
methods[i] = ms.At(i)
}
return methods
}
-149
View File
@@ -1,149 +0,0 @@
package ir
import (
"errors"
"go/types"
"golang.org/x/tools/go/ssa"
)
// This file implements several optimization passes (analysis + transform) to
// optimize code in SSA form before it is compiled to LLVM IR. It is based on
// the IR defined in ir.go.
// Make a readable version of a method signature (including the function name,
// excluding the receiver name). This string is used internally to match
// interfaces and to call the correct method on an interface. Examples:
//
// String() string
// Read([]byte) (int, error)
func MethodSignature(method *types.Func) string {
return method.Name() + signature(method.Type().(*types.Signature))
}
// Make a readable version of a function (pointer) signature.
// Examples:
//
// () string
// (string, int) (int, error)
func signature(sig *types.Signature) string {
s := ""
if sig.Params().Len() == 0 {
s += "()"
} else {
s += "("
for i := 0; i < sig.Params().Len(); i++ {
if i > 0 {
s += ", "
}
s += sig.Params().At(i).Type().String()
}
s += ")"
}
if sig.Results().Len() == 0 {
// keep as-is
} else if sig.Results().Len() == 1 {
s += " " + sig.Results().At(0).Type().String()
} else {
s += " ("
for i := 0; i < sig.Results().Len(); i++ {
if i > 0 {
s += ", "
}
s += sig.Results().At(i).Type().String()
}
s += ")"
}
return s
}
// Simple pass that removes dead code. This pass makes later analysis passes
// more useful.
func (p *Program) SimpleDCE() error {
// Unmark all functions.
for _, f := range p.Functions {
f.flag = false
}
// Initial set of live functions. Include main.main, *.init and runtime.*
// functions.
main, ok := p.mainPkg.Members["main"].(*ssa.Function)
if !ok {
if p.mainPkg.Members["main"] == nil {
return errors.New("function main is undeclared in the main package")
} else {
return errors.New("cannot declare main - must be func")
}
}
runtimePkg := p.Program.ImportedPackage("runtime")
mathPkg := p.Program.ImportedPackage("math")
taskPkg := p.Program.ImportedPackage("internal/task")
p.GetFunction(main).flag = true
worklist := []*ssa.Function{main}
for _, f := range p.Functions {
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || f.Pkg == taskPkg || (f.Pkg == mathPkg && f.Pkg != nil) {
if f.flag {
continue
}
f.flag = true
worklist = append(worklist, f.Function)
}
}
// Mark all called functions recursively.
for len(worklist) != 0 {
f := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
for _, block := range f.Blocks {
for _, instr := range block.Instrs {
if instr, ok := instr.(*ssa.MakeInterface); ok {
for _, sel := range getAllMethods(p.Program, instr.X.Type()) {
fn := p.Program.MethodValue(sel)
callee := p.GetFunction(fn)
if callee == nil {
// TODO: why is this necessary?
p.addFunction(fn)
callee = p.GetFunction(fn)
}
if !callee.flag {
callee.flag = true
worklist = append(worklist, callee.Function)
}
}
}
for _, operand := range instr.Operands(nil) {
if operand == nil || *operand == nil {
continue
}
switch operand := (*operand).(type) {
case *ssa.Function:
f := p.GetFunction(operand)
if f == nil {
// FIXME HACK: this function should have been
// discovered already. It is not for bound methods.
p.addFunction(operand)
f = p.GetFunction(operand)
}
if !f.flag {
f.flag = true
worklist = append(worklist, operand)
}
}
}
}
}
}
// Remove unmarked functions.
livefunctions := []*Function{}
for _, f := range p.Functions {
if f.flag {
livefunctions = append(livefunctions, f)
} else {
delete(p.functionMap, f.Function)
}
}
p.Functions = livefunctions
return nil
}
+27
View File
@@ -1,5 +1,10 @@
package loader
import (
"go/token"
"strings"
)
// Errors contains a list of parser errors or a list of typechecker errors for
// the given package.
type Errors struct {
@@ -10,3 +15,25 @@ type Errors struct {
func (e Errors) Error() string {
return "could not compile: " + e.Errs[0].Error()
}
// ImportCycleErrors is returned when encountering an import cycle. The list of
// packages is a list from the root package to the leaf package that imports one
// of the packages in the list.
type ImportCycleError struct {
Packages []string
ImportPositions []token.Position
}
func (e *ImportCycleError) Error() string {
var msg strings.Builder
msg.WriteString("import cycle:\n\t")
msg.WriteString(strings.Join(e.Packages, "\n\t"))
msg.WriteString("\n at ")
for i, pos := range e.ImportPositions {
if i > 0 {
msg.WriteString(", ")
}
msg.WriteString(pos.String())
}
return msg.String()
}
-225
View File
@@ -1,225 +0,0 @@
package loader
// This file constructs a new temporary GOROOT directory by merging both the
// standard Go GOROOT and the GOROOT from TinyGo using symlinks.
import (
"crypto/sha512"
"encoding/hex"
"errors"
"io/ioutil"
"math/rand"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"strconv"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
)
// GetCachedGoroot creates a new GOROOT by merging both the standard GOROOT and
// the GOROOT from TinyGo using lots of symbolic links.
func GetCachedGoroot(config *compileopts.Config) (string, error) {
goroot := goenv.Get("GOROOT")
if goroot == "" {
return "", errors.New("could not determine GOROOT")
}
tinygoroot := goenv.Get("TINYGOROOT")
if tinygoroot == "" {
return "", errors.New("could not determine TINYGOROOT")
}
needsSyscallPackage := false
for _, tag := range config.BuildTags() {
if tag == "baremetal" || tag == "darwin" {
needsSyscallPackage = true
}
}
// Determine the location of the cached GOROOT.
version, err := goenv.GorootVersionString(goroot)
if err != nil {
return "", err
}
gorootsHash := sha512.Sum512_256([]byte(goroot + "\x00" + tinygoroot))
gorootsHashHex := hex.EncodeToString(gorootsHash[:])
cachedgoroot := filepath.Join(goenv.Get("GOCACHE"), "goroot-"+version+"-"+gorootsHashHex)
if needsSyscallPackage {
cachedgoroot += "-syscall"
}
if _, err := os.Stat(cachedgoroot); err == nil {
return cachedgoroot, nil
}
tmpgoroot := cachedgoroot + ".tmp" + strconv.Itoa(rand.Int())
err = os.MkdirAll(tmpgoroot, 0777)
if err != nil {
return "", err
}
// Remove the temporary directory if it wasn't moved to the right place
// (for example, when there was an error).
defer os.RemoveAll(tmpgoroot)
for _, name := range []string{"bin", "lib", "pkg"} {
err = symlink(filepath.Join(goroot, name), filepath.Join(tmpgoroot, name))
if err != nil {
return "", err
}
}
err = mergeDirectory(goroot, tinygoroot, tmpgoroot, "", pathsToOverride(needsSyscallPackage))
if err != nil {
return "", err
}
err = os.Rename(tmpgoroot, cachedgoroot)
if err != nil {
if os.IsExist(err) {
// Another invocation of TinyGo also seems to have created a GOROOT.
// Use that one instead. Our new GOROOT will be automatically
// deleted by the defer above.
return cachedgoroot, nil
}
return "", err
}
return cachedgoroot, nil
}
// mergeDirectory merges two roots recursively. The tmpgoroot is the directory
// that will be created by this call by either symlinking the directory from
// goroot or tinygoroot, or by creating the directory and merging the contents.
func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides map[string]bool) error {
if mergeSubdirs, ok := overrides[importPath+"/"]; ok {
if !mergeSubdirs {
// This directory and all subdirectories should come from the TinyGo
// root, so simply make a symlink.
newname := filepath.Join(tmpgoroot, "src", importPath)
oldname := filepath.Join(tinygoroot, "src", importPath)
return symlink(oldname, newname)
}
// Merge subdirectories. Start by making the directory to merge.
err := os.Mkdir(filepath.Join(tmpgoroot, "src", importPath), 0777)
if err != nil {
return err
}
// Symlink all files from TinyGo, and symlink directories from TinyGo
// that need to be overridden.
tinygoEntries, err := ioutil.ReadDir(filepath.Join(tinygoroot, "src", importPath))
if err != nil {
return err
}
for _, e := range tinygoEntries {
if e.IsDir() {
// A directory, so merge this thing.
err := mergeDirectory(goroot, tinygoroot, tmpgoroot, path.Join(importPath, e.Name()), overrides)
if err != nil {
return err
}
} else {
// A file, so symlink this.
newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
oldname := filepath.Join(tinygoroot, "src", importPath, e.Name())
err := symlink(oldname, newname)
if err != nil {
return err
}
}
}
// Symlink all directories from $GOROOT that are not part of the TinyGo
// overrides.
gorootEntries, err := ioutil.ReadDir(filepath.Join(goroot, "src", importPath))
if err != nil {
return err
}
for _, e := range gorootEntries {
if !e.IsDir() {
// Don't merge in files from Go. Otherwise we'd end up with a
// weird syscall package with files from both roots.
continue
}
if _, ok := overrides[path.Join(importPath, e.Name())+"/"]; ok {
// Already included above, so don't bother trying to create this
// symlink.
continue
}
newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
oldname := filepath.Join(goroot, "src", importPath, e.Name())
err := symlink(oldname, newname)
if err != nil {
return err
}
}
}
return nil
}
// The boolean indicates whether to merge the subdirs. True means merge, false
// means use the TinyGo version.
func pathsToOverride(needsSyscallPackage bool) map[string]bool {
paths := map[string]bool{
"/": true,
"device/": false,
"examples/": false,
"internal/": true,
"internal/reflectlite/": false,
"internal/task/": false,
"machine/": false,
"os/": true,
"reflect/": false,
"runtime/": false,
"sync/": true,
"testing/": false,
}
if needsSyscallPackage {
paths["syscall/"] = true // include syscall/js
}
return paths
}
// symlink creates a symlink or something similar. On Unix-like systems, it
// always creates a symlink. On Windows, it tries to create a symlink and if
// that fails, creates a hardlink or directory junction instead.
//
// Note that while Windows 10 does support symlinks and allows them to be
// created using os.Symlink, it requires developer mode to be enabled.
// Therefore provide a fallback for when symlinking is not possible.
// Unfortunately this fallback only works when TinyGo is installed on the same
// filesystem as the TinyGo cache and the Go installation (which is usually the
// C drive).
func symlink(oldname, newname string) error {
symlinkErr := os.Symlink(oldname, newname)
if runtime.GOOS == "windows" && symlinkErr != nil {
// Fallback for when developer mode is disabled.
// Note that we return the symlink error even if something else fails
// later on. This is because symlinks are the easiest to support
// (they're also used on Linux and MacOS) and enabling them is easy:
// just enable developer mode.
st, err := os.Stat(oldname)
if err != nil {
return symlinkErr
}
if st.IsDir() {
// Make a directory junction. There may be a way to do this
// programmatically, but it involves a lot of magic. Use the mklink
// command built into cmd instead (mklink is a builtin, not an
// external command).
err := exec.Command("cmd", "/k", "mklink", "/J", newname, oldname).Run()
if err != nil {
return symlinkErr
}
} else {
// Make a hard link.
err := os.Link(oldname, newname)
if err != nil {
return symlinkErr
}
}
return nil // success
}
return symlinkErr
}
+219 -127
View File
@@ -3,10 +3,10 @@ package loader
import (
"bytes"
"errors"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/scanner"
"go/token"
"go/types"
"os"
@@ -16,16 +16,15 @@ import (
"text/template"
"github.com/tinygo-org/tinygo/cgo"
"github.com/tinygo-org/tinygo/goenv"
"golang.org/x/tools/go/packages"
)
// Program holds all packages and some metadata about the program as a whole.
type Program struct {
mainPkg string
Build *build.Context
Tests bool
OverlayBuild *build.Context
OverlayPath func(path string) string
Packages map[string]*Package
MainPkg *Package
sorted []*Package
fset *token.FileSet
TypeChecker types.Config
@@ -38,114 +37,89 @@ type Program struct {
// Package holds a loaded package, its imports, and its parsed files.
type Package struct {
*Program
*packages.Package
Files []*ast.File
Pkg *types.Package
*build.Package
Imports map[string]*Package
Importing bool
Files []*ast.File
Pkg *types.Package
types.Info
}
// Load loads the given package with all dependencies (including the runtime
// package). Call .Parse() afterwards to parse all Go files (including CGo
// processing, if necessary).
func (p *Program) Load(importPath string) error {
// Import loads the given package relative to srcDir (for the vendor directory).
// It only loads the current package without recursion.
func (p *Program) Import(path, srcDir string, pos token.Position) (*Package, error) {
if p.Packages == nil {
p.Packages = make(map[string]*Package)
}
err := p.loadPackage(importPath)
// Load this package.
ctx := p.Build
if newPath := p.OverlayPath(path); newPath != "" {
ctx = p.OverlayBuild
path = newPath
}
buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
if err != nil {
return err
return nil, scanner.Error{
Pos: pos,
Msg: err.Error(), // TODO: define a new error type that will wrap the inner error
}
}
p.MainPkg = p.sorted[len(p.sorted)-1]
if _, ok := p.Packages["runtime"]; !ok {
// The runtime package wasn't loaded. Although `go list -deps` seems to
// return the full dependency list, there is no way to get those
// packages from the go/packages package. Therefore load the runtime
// manually and add it to the list of to-be-compiled packages
// (duplicates are already filtered).
return p.loadPackage("runtime")
if existingPkg, ok := p.Packages[buildPkg.ImportPath]; ok {
// Already imported, or at least started the import.
return existingPkg, nil
}
return nil
p.sorted = nil // invalidate the sorted order of packages
pkg := p.newPackage(buildPkg)
p.Packages[buildPkg.ImportPath] = pkg
if p.mainPkg == "" {
p.mainPkg = buildPkg.ImportPath
}
return pkg, nil
}
func (p *Program) loadPackage(importPath string) error {
cgoEnabled := "0"
if p.Build.CgoEnabled {
cgoEnabled = "1"
// ImportFile loads and parses the import statements in the given path and
// creates a pseudo-package out of it.
func (p *Program) ImportFile(path string) (*Package, error) {
if p.Packages == nil {
p.Packages = make(map[string]*Package)
}
pkgs, err := packages.Load(&packages.Config{
Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps,
Env: append(os.Environ(), "GOROOT="+p.Build.GOROOT, "GOOS="+p.Build.GOOS, "GOARCH="+p.Build.GOARCH, "CGO_ENABLED="+cgoEnabled),
BuildFlags: []string{"-tags", strings.Join(p.Build.BuildTags, " ")},
Tests: p.Tests,
}, importPath)
if _, ok := p.Packages[path]; ok {
// unlikely
return nil, errors.New("loader: cannot import file that is already imported as package: " + path)
}
file, err := p.parseFile(path, parser.ImportsOnly)
if err != nil {
return err
return nil, err
}
var pkg *packages.Package
if p.Tests {
// We need the second package. Quoting from the docs:
// > For example, when using the go command, loading "fmt" with Tests=true
// > returns four packages, with IDs "fmt" (the standard package),
// > "fmt [fmt.test]" (the package as compiled for the test),
// > "fmt_test" (the test functions from source files in package fmt_test),
// > and "fmt.test" (the test binary).
pkg = pkgs[1]
} else {
if len(pkgs) != 1 {
return fmt.Errorf("expected exactly one package while importing %s, got %d", importPath, len(pkgs))
}
pkg = pkgs[0]
buildPkg := &build.Package{
Dir: filepath.Dir(path),
ImportPath: path,
GoFiles: []string{filepath.Base(path)},
}
var importError *Errors
var addPackages func(pkg *packages.Package)
addPackages = func(pkg *packages.Package) {
if _, ok := p.Packages[pkg.PkgPath]; ok {
return
}
pkg2 := p.newPackage(pkg)
p.Packages[pkg.PkgPath] = pkg2
if len(pkg.Errors) != 0 {
if importError != nil {
// There was another error reported already. Do not report
// errors from multiple packages at once.
return
}
importError = &Errors{
Pkg: pkg2,
}
for _, err := range pkg.Errors {
importError.Errs = append(importError.Errs, err)
}
return
}
for _, importSpec := range file.Imports {
buildPkg.Imports = append(buildPkg.Imports, importSpec.Path.Value[1:len(importSpec.Path.Value)-1])
}
p.sorted = nil // invalidate the sorted order of packages
pkg := p.newPackage(buildPkg)
p.Packages[buildPkg.ImportPath] = pkg
// Get the list of imports (sorted alphabetically).
names := make([]string, 0, len(pkg.Imports))
for name := range pkg.Imports {
names = append(names, name)
}
sort.Strings(names)
// Add all the imports.
for _, name := range names {
addPackages(pkg.Imports[name])
}
p.sorted = append(p.sorted, pkg2)
if p.mainPkg == "" {
p.mainPkg = buildPkg.ImportPath
}
addPackages(pkg)
if importError != nil {
return importError
}
return nil
return pkg, nil
}
// newPackage instantiates a new *Package object with initialized members.
func (p *Program) newPackage(pkg *packages.Package) *Package {
func (p *Program) newPackage(pkg *build.Package) *Package {
return &Package{
Program: p,
Package: pkg,
Imports: make(map[string]*Package, len(pkg.Imports)),
Info: types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
@@ -160,25 +134,87 @@ func (p *Program) newPackage(pkg *packages.Package) *Package {
// Sorted returns a list of all packages, sorted in a way that no packages come
// before the packages they depend upon.
func (p *Program) Sorted() []*Package {
if p.sorted == nil {
p.sort()
}
return p.sorted
}
// Parse parses all packages and typechecks them.
func (p *Program) sort() {
p.sorted = nil
packageList := make([]*Package, 0, len(p.Packages))
packageSet := make(map[string]struct{}, len(p.Packages))
worklist := make([]string, 0, len(p.Packages))
for path := range p.Packages {
worklist = append(worklist, path)
}
sort.Strings(worklist)
for len(worklist) != 0 {
pkgPath := worklist[0]
pkg := p.Packages[pkgPath]
if _, ok := packageSet[pkgPath]; ok {
// Package already in the final package list.
worklist = worklist[1:]
continue
}
unsatisfiedImports := make([]string, 0)
for _, pkg := range pkg.Imports {
if _, ok := packageSet[pkg.ImportPath]; ok {
continue
}
unsatisfiedImports = append(unsatisfiedImports, pkg.ImportPath)
}
sort.Strings(unsatisfiedImports)
if len(unsatisfiedImports) == 0 {
// All dependencies of this package are satisfied, so add this
// package to the list.
packageList = append(packageList, pkg)
packageSet[pkgPath] = struct{}{}
worklist = worklist[1:]
} else {
// Prepend all dependencies to the worklist and reconsider this
// package (by not removing it from the worklist). At that point, it
// must be possible to add it to packageList.
worklist = append(unsatisfiedImports, worklist...)
}
}
p.sorted = packageList
}
// Parse recursively imports all packages, parses them, and typechecks them.
//
// The returned error may be an Errors error, which contains a list of errors.
//
// Idempotent.
func (p *Program) Parse() error {
func (p *Program) Parse(compileTestBinary bool) error {
includeTests := compileTestBinary
// Load all imports
for _, pkg := range p.Sorted() {
err := pkg.importRecursively(includeTests)
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
if pkg.ImportPath != err.Packages[0] {
err.Packages = append([]string{pkg.ImportPath}, err.Packages...)
}
}
return err
}
}
// Parse all packages.
for _, pkg := range p.Sorted() {
err := pkg.Parse()
err := pkg.Parse(includeTests)
if err != nil {
return err
}
}
if p.Tests {
err := p.swapTestMain()
if compileTestBinary {
err := p.SwapTestMain()
if err != nil {
return err
}
@@ -195,7 +231,7 @@ func (p *Program) Parse() error {
return nil
}
func (p *Program) swapTestMain() error {
func (p *Program) SwapTestMain() error {
var tests []string
isTestFunc := func(f *ast.FuncDecl) bool {
@@ -205,7 +241,8 @@ func (p *Program) swapTestMain() error {
}
return false
}
for _, f := range p.MainPkg.Files {
mainPkg := p.Packages[p.mainPkg]
for _, f := range mainPkg.Files {
for i, d := range f.Decls {
switch v := d.(type) {
case *ast.FuncDecl:
@@ -256,7 +293,7 @@ func main () {
if err != nil {
return err
}
path := filepath.Join(p.MainPkg.Dir, "$testmain.go")
path := filepath.Join(p.mainPkg, "$testmain.go")
if p.fset == nil {
p.fset = token.NewFileSet()
@@ -266,7 +303,7 @@ func main () {
if err != nil {
return err
}
p.MainPkg.Files = append(p.MainPkg.Files, newMain)
mainPkg.Files = append(mainPkg.Files, newMain)
return nil
}
@@ -282,41 +319,34 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
return nil, err
}
defer rd.Close()
diagnosticPath := path
if strings.HasPrefix(path, p.Build.GOROOT+string(filepath.Separator)) {
// If this file is part of the synthetic GOROOT, try to infer the
// original path.
relpath := path[len(p.Build.GOROOT)+1:]
tinygoPath := filepath.Join(p.TINYGOROOT, relpath)
if _, err := os.Stat(tinygoPath); err == nil {
diagnosticPath = tinygoPath
}
realgorootPath := filepath.Join(goenv.Get("GOROOT"), relpath)
if _, err := os.Stat(realgorootPath); err == nil {
diagnosticPath = realgorootPath
relpath := path
if filepath.IsAbs(path) {
rp, err := filepath.Rel(p.Dir, path)
if err == nil {
relpath = rp
}
}
return parser.ParseFile(p.fset, diagnosticPath, rd, mode)
return parser.ParseFile(p.fset, relpath, rd, mode)
}
// Parse parses and typechecks this package.
//
// Idempotent.
func (p *Package) Parse() error {
func (p *Package) Parse(includeTests bool) error {
if len(p.Files) != 0 {
return nil
}
// Load the AST.
// TODO: do this in parallel.
if p.PkgPath == "unsafe" {
if p.ImportPath == "unsafe" {
// Special case for the unsafe package. Don't even bother loading
// the files.
p.Pkg = types.Unsafe
return nil
}
files, err := p.parseFiles()
files, err := p.parseFiles(includeTests)
if err != nil {
return err
}
@@ -343,7 +373,7 @@ func (p *Package) Check() error {
// Do typechecking of the package.
checker.Importer = p
typesPkg, err := checker.Check(p.PkgPath, p.fset, p.Files, &p.Info)
typesPkg, err := checker.Check(p.ImportPath, p.fset, p.Files, &p.Info)
if err != nil {
if err, ok := err.(Errors); ok {
return err
@@ -355,14 +385,22 @@ func (p *Package) Check() error {
}
// parseFiles parses the loaded list of files and returns this list.
func (p *Package) parseFiles() ([]*ast.File, error) {
func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
// TODO: do this concurrently.
var files []*ast.File
var fileErrs []error
var cgoFiles []*ast.File
for _, file := range p.GoFiles {
f, err := p.parseFile(file, parser.ParseComments)
var gofiles []string
if includeTests {
gofiles = make([]string, 0, len(p.GoFiles)+len(p.TestGoFiles))
gofiles = append(gofiles, p.GoFiles...)
gofiles = append(gofiles, p.TestGoFiles...)
} else {
gofiles = p.GoFiles
}
for _, file := range gofiles {
f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments)
if err != nil {
fileErrs = append(fileErrs, err)
continue
@@ -371,15 +409,19 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
fileErrs = append(fileErrs, err)
continue
}
for _, importSpec := range f.Imports {
if importSpec.Path.Value == `"C"` {
cgoFiles = append(cgoFiles, f)
}
}
files = append(files, f)
}
if len(cgoFiles) != 0 {
cflags := append(p.CFlags, "-I"+filepath.Dir(p.GoFiles[0]))
for _, file := range p.CgoFiles {
path := filepath.Join(p.Package.Dir, file)
f, err := p.parseFile(path, parser.ParseComments)
if err != nil {
fileErrs = append(fileErrs, err)
continue
}
files = append(files, f)
}
if len(p.CgoFiles) != 0 {
cflags := append(p.CFlags, "-I"+p.Package.Dir)
if p.ClangHeaders != "" {
cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.ClangHeaders)
}
@@ -403,8 +445,58 @@ func (p *Package) Import(to string) (*types.Package, error) {
return types.Unsafe, nil
}
if _, ok := p.Imports[to]; ok {
return p.Packages[p.Imports[to].PkgPath].Pkg, nil
return p.Imports[to].Pkg, nil
} else {
return nil, errors.New("package not imported: " + to)
}
}
// importRecursively calls Program.Import() on all imported packages, and calls
// importRecursively() on the imported packages as well.
//
// Idempotent.
func (p *Package) importRecursively(includeTests bool) error {
p.Importing = true
imports := p.Package.Imports
if includeTests {
imports = append(imports, p.Package.TestImports...)
}
for _, to := range imports {
if to == "C" {
// Do CGo processing in a later stage.
continue
}
if _, ok := p.Imports[to]; ok {
continue
}
// Find error location.
var pos token.Position
if len(p.Package.ImportPos[to]) > 0 {
pos = p.Package.ImportPos[to][0]
} else {
pos = token.Position{Filename: p.Package.ImportPath}
}
importedPkg, err := p.Program.Import(to, p.Package.Dir, pos)
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
err.Packages = append([]string{p.ImportPath}, err.Packages...)
}
return err
}
if importedPkg.Importing {
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]}
}
err = importedPkg.importRecursively(false)
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
err.Packages = append([]string{p.ImportPath}, err.Packages...)
}
return err
}
p.Imports[to] = importedPkg
}
p.Importing = false
return nil
}
+15 -83
View File
@@ -2,7 +2,6 @@ package main
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
@@ -125,7 +124,6 @@ func Build(pkgName, outpath string, options *compileopts.Options) error {
// Test runs the tests in the given package.
func Test(pkgName string, options *compileopts.Options) error {
options.TestConfig.CompileTestBinary = true
config, err := builder.NewConfig(options)
if err != nil {
return err
@@ -137,6 +135,7 @@ func Test(pkgName string, options *compileopts.Options) error {
// For details: https://github.com/golang/go/issues/21360
config.Target.BuildTags = append(config.Target.BuildTags, "test")
options.TestConfig.CompileTestBinary = true
return builder.Build(pkgName, ".elf", config, func(tmppath string) error {
cmd := exec.Command(tmppath)
cmd.Stdout = os.Stdout
@@ -642,36 +641,6 @@ func getDefaultPort() (port string, err error) {
return d[0], nil
}
// runGoList runs the `go list` command but using the configuration used for
// TinyGo.
func runGoList(config *compileopts.Config, flagJSON, flagDeps bool, pkgs []string) error {
goroot, err := loader.GetCachedGoroot(config)
if err != nil {
return err
}
args := []string{"list"}
if flagJSON {
args = append(args, "-json")
}
if flagDeps {
args = append(args, "-deps")
}
if len(config.BuildTags()) != 0 {
args = append(args, "-tags", strings.Join(config.BuildTags(), " "))
}
args = append(args, pkgs...)
cgoEnabled := "0"
if config.CgoEnabled() {
cgoEnabled = "1"
}
cmd := exec.Command("go", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = append(os.Environ(), "GOROOT="+goroot, "GOOS="+config.GOOS(), "GOARCH="+config.GOARCH(), "CGO_ENABLED="+cgoEnabled)
cmd.Run()
return nil
}
func usage() {
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
fmt.Fprintln(os.Stderr, "version:", version)
@@ -683,7 +652,6 @@ func usage() {
fmt.Fprintln(os.Stderr, " flash: compile and flash to the device")
fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB")
fmt.Fprintln(os.Stderr, " env: list environment variables used during build")
fmt.Fprintln(os.Stderr, " list: run go list using the TinyGo root")
fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+goenv.Get("GOCACHE")+")")
fmt.Fprintln(os.Stderr, " help: print this help text")
fmt.Fprintln(os.Stderr, "\nflags:")
@@ -715,7 +683,7 @@ func printCompilerError(logln func(...interface{}), err error) {
}
}
case loader.Errors:
logln("#", err.Pkg.PkgPath)
logln("#", err.Pkg.ImportPath)
for _, err := range err.Errs {
logln(err)
}
@@ -738,13 +706,6 @@ func handleCompilerError(err error) {
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
usage()
os.Exit(1)
}
command := os.Args[1]
outpath := flag.String("o", "", "output filename")
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
gc := flag.String("gc", "", "garbage collector to use (none, leaking, extalloc, conservative)")
@@ -765,13 +726,12 @@ func main() {
wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic")
heapSize := flag.String("heap-size", "1M", "default heap size in bytes (only supported by WebAssembly)")
var flagJSON, flagDeps *bool
if command == "list" || command == "env" {
flagJSON = flag.Bool("json", false, "print data in JSON format")
}
if command == "list" {
flagDeps = flag.Bool("deps", false, "")
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
usage()
os.Exit(1)
}
command := os.Args[1]
// Early command processing, before commands are interpreted by the Go flag
// library.
@@ -935,18 +895,6 @@ func main() {
fmt.Printf("build tags: %s\n", strings.Join(config.BuildTags(), " "))
fmt.Printf("garbage collector: %s\n", config.GC())
fmt.Printf("scheduler: %s\n", config.Scheduler())
case "list":
config, err := builder.NewConfig(options)
if err != nil {
fmt.Fprintln(os.Stderr, err)
usage()
os.Exit(1)
}
err = runGoList(config, *flagJSON, *flagDeps, flag.Args())
if err != nil {
fmt.Fprintln(os.Stderr, "failed to run `go list`:", err)
os.Exit(1)
}
case "clean":
// remove cache directory
err := os.RemoveAll(goenv.Get("GOCACHE"))
@@ -958,36 +906,20 @@ func main() {
usage()
case "version":
goversion := "<unknown>"
if s, err := goenv.GorootVersionString(goenv.Get("GOROOT")); err == nil {
if s, err := builder.GorootVersionString(goenv.Get("GOROOT")); err == nil {
goversion = s
}
fmt.Printf("tinygo version %s %s/%s (using go version %s and LLVM version %s)\n", version, runtime.GOOS, runtime.GOARCH, goversion, llvm.Version)
case "env":
if *flagJSON {
keys := goenv.Keys
if flag.NArg() != 0 {
// Show only one (or a few) environment variables.
keys = flag.Args()
if flag.NArg() == 0 {
// Show all environment variables.
for _, key := range goenv.Keys {
fmt.Printf("%s=%#v\n", key, goenv.Get(key))
}
// Show environment variables in JSON format.
env := make(map[string]string)
for _, key := range keys {
env[key] = goenv.Get(key)
}
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", "\t")
encoder.Encode(env)
} else {
if flag.NArg() == 0 {
// Show all environment variables.
for _, key := range goenv.Keys {
fmt.Printf("%s=%#v\n", key, goenv.Get(key))
}
} else {
// Show only one (or a few) environment variables.
for i := 0; i < flag.NArg(); i++ {
fmt.Println(goenv.Get(flag.Arg(i)))
}
// Show only one (or a few) environment variables.
for i := 0; i < flag.NArg(); i++ {
fmt.Println(goenv.Get(flag.Arg(i)))
}
}
default:
+1 -1
View File
@@ -73,7 +73,7 @@ func TestCompiler(t *testing.T) {
t.Run("ARM64Linux", func(t *testing.T) {
runPlatTests("aarch64--linux-gnu", matches, t)
})
goVersion, err := goenv.GorootVersionString(goenv.Get("GOROOT"))
goVersion, err := builder.GorootVersionString(goenv.Get("GOROOT"))
if err != nil {
t.Error("could not get Go version:", err)
return
-5
View File
@@ -85,14 +85,9 @@ type taskHolder interface {
getReturnPtr() unsafe.Pointer
}
// If there are no direct references to the task methods, they will not be discovered by the compiler, and this will trigger a compiler error.
// Instantiating this interface forces discovery of these methods.
var _ = taskHolder((*Task)(nil))
func fake() {
// Hack to ensure intrinsics are discovered.
Current()
go func() {}()
Pause()
}