Compare commits

...

16 Commits

Author SHA1 Message Date
Ayke van Laethem 4e07fc569c main: add -json flag support to go env
This is needed for VS Code support.
2020-05-08 14:50:37 +02:00
Ayke van Laethem 25e13d887f loader: load packages using Go modules
This commit replaces the existing ad-hoc package loader with a package
loader that uses the x/tools/go/packages package to find all
to-be-loaded packages.
2020-05-08 14:50:26 +02:00
Ayke van Laethem 51daa2a044 main: fix test subcommand
It was broken for quite some time without anybody noticing...
2020-05-08 14:50:26 +02:00
Ayke van Laethem f70dc70247 loader: merge roots from both Go and TinyGo in a cached directory
This commit changes the way that packages are looked up. Instead of
working around the loader package by modifying the GOROOT variable for
specific packages, create a new GOROOT using symlinks. This GOROOT is
cached for the specified configuration (Go version, underlying GOROOT
path, TinyGo path, whether to override the syscall package).

This will also enable go module support in the future.
2020-05-05 00:06:53 +02:00
Ayke van Laethem f24bb1ceda builder: move Go version code to goenv package
This is necessary to avoid a circular dependency in the loader (which
soon will need to read the Go version) and because it seems like a
better place anyway.
2020-05-04 21:58:27 +02:00
Ayke van Laethem 23e88bfb15 arm: allow nesting in DisableInterrupts and EnableInterrupts
This finally fixes a TODO left in the code.
2020-04-29 18:25:16 +02:00
Ayke van Laethem 6389e45d99 all: replace ReadRegister with AsmFull inline assembly
This makes AsmFull more powerful (by supporting return values) and
avoids a compiler builtin.
2020-04-29 18:25:16 +02:00
Ayke van Laethem 9342e73ae1 builder: fix picolibc include path
Previously we used --sysroot to set the sysroot explicitly.
Unfortunately, this flag is not used directly by Clang to set the
include path (<sysroot>/include) but is instead interpreted by the
toolchain code. This means that even when the toolchain is explicitly
set (using the --sysroot parameter), it may still decide to use a
different include path such as <sysroot>/usr/include (such as on
baremetal aarch64).

This commit uses the Clang-internal -internal-isystem flag which sets
the include directory directly (as a system include path). This should
be more robust.

The reason the --sysroot parameter has so far worked is that all
existing targets happened to add <sysroot>/include as an include path.

The relevant Clang code is here:
https://github.com/llvm/llvm-project/blob/release/9.x/clang/lib/Driver/Driver.cpp#L4693-L4739
So far, RISC-V is handled by RISCVToolchain, Cortex-M targets by
BareMetal (which seems to be specific to ARM unlike what the name says)
and aarch64 fell back to Generic_ELF.
2020-04-29 15:41:08 +02:00
Ayke van Laethem fc4857e98c runtime: avoid recursion in printuint64 function
This function is called from runtime.printitf, which is called from
runtime._panic, and is therefore the leaf function of many call paths.
This makes analyzing stack usage very difficult.

Also forwarding printuint32 to printuint64 as it reduces code size in
the few examples I've tested. Printing numbers is not often done so it
doesn't matter if it's a bit slow (the serial connection is probably
slower anyway).
2020-04-26 17:19:07 +02:00
Yannis Huber f66492a338 Fix return address in scheduler 2020-04-26 16:58:02 +02:00
Jaden Weiss 445fd37bef main: update version for beginning of v0.14 development cycle 2020-04-26 16:57:24 +02:00
Ayke van Laethem a9ba6ebad9 main: version 0.13.1
This release fixes a few bugs introduced in the previous 0.13.0 release.
2020-04-21 17:02:43 +02:00
Ayke van Laethem 565ff99c31 gba: always use ARM mode instead of Thumb mode
This results in bigger code size, but it works around a bug in the
linker.

The issue starts with the problem that libraries (picolibc, compiler-rt)
were compiled as ARM and the rest as Thumb. This causes some blx
instructions to be inserted by the linker to call into these libraries.

Ideally we should fix the libraries to use Thumb mode instead, but that
requires some more extensive changes (including fixes to compiler-rt)
and it's just way easier to use ARM mode everywhere.
2020-04-21 15:40:52 +02:00
Jaden Weiss ceeba528e7 runtime: copy stack scan assembly for GBA
The GC stack scanning code was implemented in the Cortex-M assembly, which meant that it was not available on the GBA which is pre-cortex.
This change adds a copy of the relevant code into a new asembly file which is used on the GBA.
2020-04-21 10:28:42 +02:00
Ayke van Laethem 16c2d84c49 compiler: add parameter names to IR
This makes viewing the IR easier because parameters have readable names.

This also makes it easier to write compiler tests (still a work in
progress), that work in LLVM 9 and LLVM 10, as LLVM 10 started printing
value names for unnamed parameters.
2020-04-21 08:54:39 +02:00
Ayke van Laethem f00bb63330 runtime: do not put scheduler and GC code in the same section
This allows dead code elimination and avoids linker errors with
-scheduler=leaking.
2020-04-20 21:32:29 +02:00
30 changed files with 739 additions and 544 deletions
+9
View File
@@ -1,3 +1,12 @@
0.13.1
---
* **standard library**
- `runtime`: do not put scheduler and GC code in the same section
- `runtime`: copy stack scan assembly for GBA
* **boards**
- `gameboy-advance`: always use ARM mode instead of Thumb mode
0.13.0 0.13.0
--- ---
* **command line** * **command line**
+1 -1
View File
@@ -21,7 +21,7 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
if goroot == "" { if goroot == "" {
return nil, errors.New("cannot locate $GOROOT, please set it manually") return nil, errors.New("cannot locate $GOROOT, please set it manually")
} }
major, minor, err := getGorootVersion(goroot) major, minor, err := goenv.GetGorootVersion(goroot)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err) return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
} }
-58
View File
@@ -1,71 +1,13 @@
package builder package builder
import ( import (
"errors"
"fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp"
"sort" "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 // getClangHeaderPath returns the path to the built-in Clang headers. It tries
// multiple locations, which should make it find the directory when installed in // multiple locations, which should make it find the directory when installed in
// various ways. // various ways.
+1 -1
View File
@@ -12,7 +12,7 @@ var Picolibc = Library{
name: "picolibc", name: "picolibc",
cflags: func() []string { cflags: func() []string {
picolibcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib/libc") picolibcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib/libc")
return []string{"-Werror", "-Wall", "-std=gnu11", "-D_COMPILING_NEWLIB", "--sysroot=" + picolibcDir, "-I" + picolibcDir + "/tinystdio", "-I" + goenv.Get("TINYGOROOT") + "/lib/picolibc-include"} return []string{"-Werror", "-Wall", "-std=gnu11", "-D_COMPILING_NEWLIB", "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", picolibcDir + "/include", "-I" + picolibcDir + "/tinystdio", "-I" + goenv.Get("TINYGOROOT") + "/lib/picolibc-include"}
}, },
sourceDir: "lib/picolibc/newlib/libc", sourceDir: "lib/picolibc/newlib/libc",
sources: func(target string) []string { sources: func(target string) []string {
+1 -1
View File
@@ -173,7 +173,7 @@ func (c *Config) CFlags() []string {
} }
if c.Target.Libc == "picolibc" { if c.Target.Libc == "picolibc" {
root := goenv.Get("TINYGOROOT") root := goenv.Get("TINYGOROOT")
cflags = append(cflags, "--sysroot="+filepath.Join(root, "lib", "picolibc", "newlib", "libc")) cflags = append(cflags, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include"))
cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include")) cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include"))
} }
return cflags return cflags
+60 -21
View File
@@ -2,6 +2,7 @@ package compiler
import ( import (
"go/types" "go/types"
"strconv"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -13,6 +14,14 @@ import (
// a struct contains more fields, it is passed as a struct without expanding. // a struct contains more fields, it is passed as a struct without expanding.
const maxFieldsPerParam = 3 const maxFieldsPerParam = 3
// paramInfo contains some information collected about a function parameter,
// useful while declaring or defining a function.
type paramInfo struct {
llvmType llvm.Type
name string // name, possibly with suffixes for e.g. struct fields
flags paramFlags
}
// paramFlags identifies parameter attributes for flags. Most importantly, it // paramFlags identifies parameter attributes for flags. Most importantly, it
// determines which parameters are dereferenceable_or_null and which aren't. // determines which parameters are dereferenceable_or_null and which aren't.
type paramFlags uint8 type paramFlags uint8
@@ -48,19 +57,23 @@ func (b *builder) createCall(fn llvm.Value, args []llvm.Value, name string) llvm
// Expand an argument type to a list that can be used in a function call // Expand an argument type to a list that can be used in a function call
// parameter list. // parameter list.
func expandFormalParamType(t llvm.Type, goType types.Type) ([]llvm.Type, []paramFlags) { func expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
switch t.TypeKind() { switch t.TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
fields, fieldFlags := flattenAggregateType(t, goType) fieldInfos := flattenAggregateType(t, name, goType)
if len(fields) <= maxFieldsPerParam { if len(fieldInfos) <= maxFieldsPerParam {
return fields, fieldFlags return fieldInfos
} else { } else {
// failed to lower // failed to lower
return []llvm.Type{t}, []paramFlags{getTypeFlags(goType)}
} }
default: }
// TODO: split small arrays // TODO: split small arrays
return []llvm.Type{t}, []paramFlags{getTypeFlags(goType)} return []paramInfo{
{
llvmType: t,
name: name,
flags: getTypeFlags(goType),
},
} }
} }
@@ -91,10 +104,10 @@ func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 {
func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value { func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
switch v.Type().TypeKind() { switch v.Type().TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
fieldTypes, _ := flattenAggregateType(v.Type(), nil) fieldInfos := flattenAggregateType(v.Type(), "", nil)
if len(fieldTypes) <= maxFieldsPerParam { if len(fieldInfos) <= maxFieldsPerParam {
fields := b.flattenAggregate(v) fields := b.flattenAggregate(v)
if len(fields) != len(fieldTypes) { if len(fields) != len(fieldInfos) {
panic("type and value param lowering don't match") panic("type and value param lowering don't match")
} }
return fields return fields
@@ -110,23 +123,49 @@ func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
// Try to flatten a struct type to a list of types. Returns a 1-element slice // Try to flatten a struct type to a list of types. Returns a 1-element slice
// with the passed in type if this is not possible. // with the passed in type if this is not possible.
func flattenAggregateType(t llvm.Type, goType types.Type) ([]llvm.Type, []paramFlags) { func flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
typeFlags := getTypeFlags(goType) typeFlags := getTypeFlags(goType)
switch t.TypeKind() { switch t.TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
fields := make([]llvm.Type, 0, t.StructElementTypesCount()) paramInfos := make([]paramInfo, 0, t.StructElementTypesCount())
fieldFlags := make([]paramFlags, 0, cap(fields))
for i, subfield := range t.StructElementTypes() { for i, subfield := range t.StructElementTypes() {
subfields, subfieldFlags := flattenAggregateType(subfield, extractSubfield(goType, i)) suffix := strconv.Itoa(i)
for i := range subfieldFlags { if goType != nil {
subfieldFlags[i] |= typeFlags // Try to come up with a good suffix for this struct field,
// depending on which Go type it's based on.
switch goType := goType.Underlying().(type) {
case *types.Interface:
suffix = []string{"typecode", "value"}[i]
case *types.Slice:
suffix = []string{"data", "len", "cap"}[i]
case *types.Struct:
suffix = goType.Field(i).Name()
case *types.Basic:
switch goType.Kind() {
case types.Complex64, types.Complex128:
suffix = []string{"r", "i"}[i]
case types.String:
suffix = []string{"data", "len"}[i]
} }
fields = append(fields, subfields...) case *types.Signature:
fieldFlags = append(fieldFlags, subfieldFlags...) suffix = []string{"context", "funcptr"}[i]
} }
return fields, fieldFlags }
subInfos := flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
for i := range subInfos {
subInfos[i].flags |= typeFlags
}
paramInfos = append(paramInfos, subInfos...)
}
return paramInfos
default: default:
return []llvm.Type{t}, []paramFlags{typeFlags} return []paramInfo{
{
llvmType: t,
name: name,
flags: typeFlags,
},
}
} }
} }
@@ -226,7 +265,7 @@ func (b *builder) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Val
func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) { func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
switch t.TypeKind() { switch t.TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
flattened, _ := flattenAggregateType(t, nil) flattened := flattenAggregateType(t, "", nil)
if len(flattened) <= maxFieldsPerParam { if len(flattened) <= maxFieldsPerParam {
value := llvm.ConstNull(t) value := llvm.ConstNull(t)
for i, subtyp := range t.StructElementTypes() { for i, subtyp := range t.StructElementTypes() {
+32 -82
View File
@@ -137,64 +137,26 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace() c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace()
dummyFunc.EraseFromParentAsFunction() dummyFunc.EraseFromParentAsFunction()
// 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
}
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
return c.mod, nil, []error{err} return c.mod, nil, []error{err}
} }
goroot, err := loader.GetCachedGoroot(c.Config)
if err != nil {
return c.mod, nil, []error{err}
}
lprogram := &loader.Program{ lprogram := &loader.Program{
Build: &build.Context{ Build: &build.Context{
GOARCH: c.GOARCH(), GOARCH: c.GOARCH(),
GOOS: c.GOOS(), GOOS: c.GOOS(),
GOROOT: goenv.Get("GOROOT"), GOROOT: goroot,
GOPATH: goenv.Get("GOPATH"), GOPATH: goenv.Get("GOPATH"),
CgoEnabled: c.CgoEnabled(), CgoEnabled: c.CgoEnabled(),
UseAllFiles: false, UseAllFiles: false,
Compiler: "gc", // must be one of the recognized compilers Compiler: "gc", // must be one of the recognized compilers
BuildTags: c.BuildTags(), BuildTags: c.BuildTags(),
}, },
OverlayBuild: &build.Context{ Tests: c.TestConfig.CompileTestBinary,
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{ TypeChecker: types.Config{
Sizes: &stdSizes{ Sizes: &stdSizes{
IntSize: int64(c.targetData.TypeAllocSize(c.intType)), IntSize: int64(c.targetData.TypeAllocSize(c.intType)),
@@ -208,33 +170,17 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
ClangHeaders: c.ClangHeaders, ClangHeaders: c.ClangHeaders,
} }
if strings.HasSuffix(pkgName, ".go") { err = lprogram.Load(pkgName)
_, 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 { if err != nil {
return c.mod, nil, []error{err} return c.mod, nil, []error{err}
} }
err = lprogram.Parse(c.TestConfig.CompileTestBinary) err = lprogram.Parse()
if err != nil { if err != nil {
return c.mod, nil, []error{err} return c.mod, nil, []error{err}
} }
c.ir = ir.NewProgram(lprogram, pkgName) c.ir = ir.NewProgram(lprogram)
// Run a simple dead code elimination pass. // Run a simple dead code elimination pass.
err = c.ir.SimpleDCE() err = c.ir.SimpleDCE()
@@ -378,8 +324,11 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
// Gather the list of (C) file paths that should be included in the build. // Gather the list of (C) file paths that should be included in the build.
var extraFiles []string var extraFiles []string
for _, pkg := range c.ir.LoaderProgram.Sorted() { for _, pkg := range c.ir.LoaderProgram.Sorted() {
for _, file := range pkg.CFiles { for _, file := range pkg.OtherFiles {
extraFiles = append(extraFiles, filepath.Join(pkg.Package.Dir, file)) switch strings.ToLower(filepath.Ext(file)) {
case ".c":
extraFiles = append(extraFiles, file)
}
} }
} }
@@ -738,21 +687,23 @@ func (c *compilerContext) createFunctionDeclaration(f *ir.Function) {
retType = c.ctx.StructType(results, false) retType = c.ctx.StructType(results, false)
} }
var paramTypes []llvm.Type var paramInfos []paramInfo
var paramTypeVariants []paramFlags
for _, param := range f.Params { for _, param := range f.Params {
paramType := c.getLLVMType(param.Type()) paramType := c.getLLVMType(param.Type())
paramTypeFragments, paramTypeFragmentVariants := expandFormalParamType(paramType, param.Type()) paramFragmentInfos := expandFormalParamType(paramType, param.Name(), param.Type())
paramTypes = append(paramTypes, paramTypeFragments...) paramInfos = append(paramInfos, paramFragmentInfos...)
paramTypeVariants = append(paramTypeVariants, paramTypeFragmentVariants...)
} }
// Add an extra parameter as the function context. This context is used in // 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. // closures and bound methods, but should be optimized away when not used.
if !f.IsExported() { if !f.IsExported() {
paramTypes = append(paramTypes, c.i8ptrType) // context paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", flags: 0})
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "parentHandle", flags: 0})
paramTypeVariants = append(paramTypeVariants, 0, 0) }
var paramTypes []llvm.Type
for _, info := range paramInfos {
paramTypes = append(paramTypes, info.llvmType)
} }
fnType := llvm.FunctionType(retType, paramTypes, false) fnType := llvm.FunctionType(retType, paramTypes, false)
@@ -764,12 +715,12 @@ func (c *compilerContext) createFunctionDeclaration(f *ir.Function) {
} }
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null") dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
for i, typ := range paramTypes { for i, info := range paramInfos {
if paramTypeVariants[i]&paramIsDeferenceableOrNull == 0 { if info.flags&paramIsDeferenceableOrNull == 0 {
continue continue
} }
if typ.TypeKind() == llvm.PointerTypeKind { if info.llvmType.TypeKind() == llvm.PointerTypeKind {
el := typ.ElementType() el := info.llvmType.ElementType()
size := c.targetData.TypeAllocSize(el) size := c.targetData.TypeAllocSize(el)
if size == 0 { if size == 0 {
// dereferenceable_or_null(0) appears to be illegal in LLVM. // dereferenceable_or_null(0) appears to be illegal in LLVM.
@@ -911,9 +862,10 @@ func (b *builder) createFunctionDefinition() {
for _, param := range b.fn.Params { for _, param := range b.fn.Params {
llvmType := b.getLLVMType(param.Type()) llvmType := b.getLLVMType(param.Type())
fields := make([]llvm.Value, 0, 1) fields := make([]llvm.Value, 0, 1)
fieldFragments, _ := expandFormalParamType(llvmType, nil) for _, info := range expandFormalParamType(llvmType, param.Name(), param.Type()) {
for range fieldFragments { param := b.fn.LLVMFn.Param(llvmParamIndex)
fields = append(fields, b.fn.LLVMFn.Param(llvmParamIndex)) param.SetName(info.name)
fields = append(fields, param)
llvmParamIndex++ llvmParamIndex++
} }
b.locals[param] = b.collapseFormalParam(llvmType, fields) b.locals[param] = b.collapseFormalParam(llvmType, fields)
@@ -1361,8 +1313,6 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
return b.createMemoryCopyCall(fn, instr.Args) return b.createMemoryCopyCall(fn, instr.Args)
case name == "runtime.memzero": case name == "runtime.memzero":
return b.createMemoryZeroCall(instr.Args) return b.createMemoryZeroCall(instr.Args)
case name == "device/arm.ReadRegister" || name == "device/riscv.ReadRegister":
return b.createReadRegister(name, instr.Args)
case name == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm": case name == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
return b.createInlineAsm(instr.Args) return b.createInlineAsm(instr.Args)
case name == "device/arm.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull": case name == "device/arm.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull":
+6 -4
View File
@@ -125,13 +125,15 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
// The receiver is not an interface, but a i8* type. // The receiver is not an interface, but a i8* type.
recv = c.i8ptrType recv = c.i8ptrType
} }
recvFragments, _ := expandFormalParamType(recv, nil) for _, info := range expandFormalParamType(recv, "", nil) {
paramTypes = append(paramTypes, recvFragments...) paramTypes = append(paramTypes, info.llvmType)
}
} }
for i := 0; i < typ.Params().Len(); i++ { for i := 0; i < typ.Params().Len(); i++ {
subType := c.getLLVMType(typ.Params().At(i).Type()) subType := c.getLLVMType(typ.Params().At(i).Type())
paramTypeFragments, _ := expandFormalParamType(subType, nil) for _, info := range expandFormalParamType(subType, "", nil) {
paramTypes = append(paramTypes, paramTypeFragments...) paramTypes = append(paramTypes, info.llvmType)
}
} }
// All functions take these parameters at the end. // All functions take these parameters at the end.
paramTypes = append(paramTypes, c.i8ptrType) // context paramTypes = append(paramTypes, c.i8ptrType) // context
+26 -26
View File
@@ -13,27 +13,6 @@ import (
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
// This is a compiler builtin, which reads the given register by name:
//
// func ReadRegister(name string) uintptr
//
// The register name must be a constant, for example "sp".
func (b *builder) createReadRegister(name string, args []ssa.Value) (llvm.Value, error) {
fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{}, false)
regname := constant.StringVal(args[0].(*ssa.Const).Value)
var asm string
switch name {
case "device/arm.ReadRegister":
asm = "mov $0, " + regname
case "device/riscv.ReadRegister":
asm = "mv $0, " + regname
default:
panic("unknown architecture")
}
target := llvm.InlineAsm(fnType, asm, "=r", false, false, 0)
return b.CreateCall(target, nil, ""), nil
}
// This is a compiler builtin, which emits a piece of inline assembly with no // This is a compiler builtin, which emits a piece of inline assembly with no
// operands or return values. It is useful for trivial instructions, like wfi in // operands or return values. It is useful for trivial instructions, like wfi in
// ARM or sleep in AVR. // ARM or sleep in AVR.
@@ -52,7 +31,7 @@ func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) {
// This is a compiler builtin, which allows assembly to be called in a flexible // This is a compiler builtin, which allows assembly to be called in a flexible
// way. // way.
// //
// func AsmFull(asm string, regs map[string]interface{}) // func AsmFull(asm string, regs map[string]interface{}) uintptr
// //
// The asm parameter must be a constant string. The regs parameter must be // The asm parameter must be a constant string. The regs parameter must be
// provided immediately. For example: // provided immediately. For example:
@@ -66,7 +45,7 @@ func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) {
func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) { func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) {
asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value) asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
registers := map[string]llvm.Value{} registers := map[string]llvm.Value{}
registerMap := instr.Args[1].(*ssa.MakeMap) if registerMap, ok := instr.Args[1].(*ssa.MakeMap); ok {
for _, r := range *registerMap.Referrers() { for _, r := range *registerMap.Referrers() {
switch r := r.(type) { switch r := r.(type) {
case *ssa.DebugRef: case *ssa.DebugRef:
@@ -76,7 +55,6 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
return llvm.Value{}, b.makeError(instr.Pos(), "register value map must be created in the same basic block") return llvm.Value{}, b.makeError(instr.Pos(), "register value map must be created in the same basic block")
} }
key := constant.StringVal(r.Key.(*ssa.Const).Value) key := constant.StringVal(r.Key.(*ssa.Const).Value)
//println("value:", r.Value.(*ssa.MakeInterface).X.String())
registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X) registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X)
case *ssa.Call: case *ssa.Call:
if r.Common() == instr { if r.Common() == instr {
@@ -86,12 +64,22 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
return llvm.Value{}, b.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String()) return llvm.Value{}, b.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
} }
} }
}
// TODO: handle dollar signs in asm string // TODO: handle dollar signs in asm string
registerNumbers := map[string]int{} registerNumbers := map[string]int{}
var err error var err error
argTypes := []llvm.Type{} argTypes := []llvm.Type{}
args := []llvm.Value{} args := []llvm.Value{}
constraints := []string{} constraints := []string{}
hasOutput := false
asmString = regexp.MustCompile("\\{\\}").ReplaceAllStringFunc(asmString, func(s string) string {
hasOutput = true
return "$0"
})
if hasOutput {
constraints = append(constraints, "=&r")
registerNumbers[""] = 0
}
asmString = regexp.MustCompile("\\{[a-zA-Z]+\\}").ReplaceAllStringFunc(asmString, func(s string) string { asmString = regexp.MustCompile("\\{[a-zA-Z]+\\}").ReplaceAllStringFunc(asmString, func(s string) string {
// TODO: skip strings like {r4} etc. that look like ARM push/pop // TODO: skip strings like {r4} etc. that look like ARM push/pop
// instructions. // instructions.
@@ -121,9 +109,21 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
if err != nil { if err != nil {
return llvm.Value{}, err return llvm.Value{}, err
} }
fnType := llvm.FunctionType(b.ctx.VoidType(), argTypes, false) var outputType llvm.Type
if hasOutput {
outputType = b.uintptrType
} else {
outputType = b.ctx.VoidType()
}
fnType := llvm.FunctionType(outputType, argTypes, false)
target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0) target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0)
return b.CreateCall(target, args, ""), nil result := b.CreateCall(target, args, "")
if hasOutput {
return result, nil
} else {
// Make sure we return something valid.
return llvm.ConstInt(b.uintptrType, 0, false), nil
}
} }
// This is a compiler builtin which emits an inline SVCall instruction. It can // This is a compiler builtin which emits an inline SVCall instruction. It can
+4 -1
View File
@@ -446,7 +446,10 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
// Get the expanded receiver type. // Get the expanded receiver type.
receiverType := c.getLLVMType(f.Params[0].Type()) receiverType := c.getLLVMType(f.Params[0].Type())
expandedReceiverType, _ := expandFormalParamType(receiverType, nil) var expandedReceiverType []llvm.Type
for _, info := range expandFormalParamType(receiverType, "", nil) {
expandedReceiverType = append(expandedReceiverType, info.llvmType)
}
// Does this method even need any wrapping? // Does this method even need any wrapping?
if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind { if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind {
+64
View File
@@ -0,0 +1,64 @@
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
}
}
+4 -25
View File
@@ -63,23 +63,13 @@ const (
) )
// Create and initialize a new *Program from a *ssa.Program. // Create and initialize a new *Program from a *ssa.Program.
func NewProgram(lprogram *loader.Program, mainPath string) *Program { func NewProgram(lprogram *loader.Program) *Program {
program := lprogram.LoadSSA() program := lprogram.LoadSSA()
program.Build() program.Build()
// Find the main package, which is a bit difficult when running a .go file // Find the main package, which is a bit difficult when running a .go file
// directly. // directly.
mainPkg := program.ImportedPackage(mainPath) mainPkg := program.ImportedPackage(lprogram.MainPkg.PkgPath)
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 { if mainPkg == nil {
panic("could not find main package") panic("could not find main package")
} }
@@ -87,21 +77,10 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
// Make a list of packages in import order. // Make a list of packages in import order.
packageList := []*ssa.Package{} packageList := []*ssa.Package{}
packageSet := map[string]struct{}{} packageSet := map[string]struct{}{}
worklist := []string{"runtime", mainPath} worklist := []string{"runtime", lprogram.MainPkg.PkgPath}
for len(worklist) != 0 { for len(worklist) != 0 {
pkgPath := worklist[0] pkgPath := worklist[0]
var pkg *ssa.Package pkg := program.ImportedPackage(pkgPath)
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 { if _, ok := packageSet[pkgPath]; ok {
// Package already in the final package list. // Package already in the final package list.
worklist = worklist[1:] worklist = worklist[1:]
-27
View File
@@ -1,10 +1,5 @@
package loader package loader
import (
"go/token"
"strings"
)
// Errors contains a list of parser errors or a list of typechecker errors for // Errors contains a list of parser errors or a list of typechecker errors for
// the given package. // the given package.
type Errors struct { type Errors struct {
@@ -15,25 +10,3 @@ type Errors struct {
func (e Errors) Error() string { func (e Errors) Error() string {
return "could not compile: " + e.Errs[0].Error() 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
@@ -0,0 +1,225 @@
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
}
+124 -216
View File
@@ -3,10 +3,10 @@ package loader
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"go/ast" "go/ast"
"go/build" "go/build"
"go/parser" "go/parser"
"go/scanner"
"go/token" "go/token"
"go/types" "go/types"
"os" "os"
@@ -16,15 +16,16 @@ import (
"text/template" "text/template"
"github.com/tinygo-org/tinygo/cgo" "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. // Program holds all packages and some metadata about the program as a whole.
type Program struct { type Program struct {
mainPkg string
Build *build.Context Build *build.Context
OverlayBuild *build.Context Tests bool
OverlayPath func(path string) string
Packages map[string]*Package Packages map[string]*Package
MainPkg *Package
sorted []*Package sorted []*Package
fset *token.FileSet fset *token.FileSet
TypeChecker types.Config TypeChecker types.Config
@@ -37,89 +38,114 @@ type Program struct {
// Package holds a loaded package, its imports, and its parsed files. // Package holds a loaded package, its imports, and its parsed files.
type Package struct { type Package struct {
*Program *Program
*build.Package *packages.Package
Imports map[string]*Package
Importing bool
Files []*ast.File Files []*ast.File
Pkg *types.Package Pkg *types.Package
types.Info types.Info
} }
// Import loads the given package relative to srcDir (for the vendor directory). // Load loads the given package with all dependencies (including the runtime
// It only loads the current package without recursion. // package). Call .Parse() afterwards to parse all Go files (including CGo
func (p *Program) Import(path, srcDir string, pos token.Position) (*Package, error) { // processing, if necessary).
func (p *Program) Load(importPath string) error {
if p.Packages == nil { if p.Packages == nil {
p.Packages = make(map[string]*Package) p.Packages = make(map[string]*Package)
} }
// Load this package. err := p.loadPackage(importPath)
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 { if err != nil {
return nil, scanner.Error{ return err
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 { return nil
// Already imported, or at least started the import.
return existingPkg, 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"
} }
pkgs, err := packages.Load(&packages.Config{
// ImportFile loads and parses the import statements in the given path and Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps,
// creates a pseudo-package out of it. Env: append(os.Environ(), "GOROOT="+p.Build.GOROOT, "GOOS="+p.Build.GOOS, "GOARCH="+p.Build.GOARCH, "CGO_ENABLED="+cgoEnabled),
func (p *Program) ImportFile(path string) (*Package, error) { BuildFlags: []string{"-tags", strings.Join(p.Build.BuildTags, " ")},
if p.Packages == nil { Tests: p.Tests,
p.Packages = make(map[string]*Package) }, 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 { if err != nil {
return nil, err return err
} }
buildPkg := &build.Package{ var pkg *packages.Package
Dir: filepath.Dir(path), if p.Tests {
ImportPath: path, // We need the second package. Quoting from the docs:
GoFiles: []string{filepath.Base(path)}, // > 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))
} }
for _, importSpec := range file.Imports { pkg = pkgs[0]
buildPkg.Imports = append(buildPkg.Imports, importSpec.Path.Value[1:len(importSpec.Path.Value)-1])
} }
p.sorted = nil // invalidate the sorted order of packages var importError *Errors
pkg := p.newPackage(buildPkg) var addPackages func(pkg *packages.Package)
p.Packages[buildPkg.ImportPath] = pkg addPackages = func(pkg *packages.Package) {
if _, ok := p.Packages[pkg.PkgPath]; ok {
if p.mainPkg == "" { return
p.mainPkg = buildPkg.ImportPath }
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
} }
return pkg, nil // 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)
}
addPackages(pkg)
if importError != nil {
return importError
}
return nil
} }
// newPackage instantiates a new *Package object with initialized members. // newPackage instantiates a new *Package object with initialized members.
func (p *Program) newPackage(pkg *build.Package) *Package { func (p *Program) newPackage(pkg *packages.Package) *Package {
return &Package{ return &Package{
Program: p, Program: p,
Package: pkg, Package: pkg,
Imports: make(map[string]*Package, len(pkg.Imports)),
Info: types.Info{ Info: types.Info{
Types: make(map[ast.Expr]types.TypeAndValue), Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object), Defs: make(map[*ast.Ident]types.Object),
@@ -134,87 +160,25 @@ func (p *Program) newPackage(pkg *build.Package) *Package {
// Sorted returns a list of all packages, sorted in a way that no packages come // Sorted returns a list of all packages, sorted in a way that no packages come
// before the packages they depend upon. // before the packages they depend upon.
func (p *Program) Sorted() []*Package { func (p *Program) Sorted() []*Package {
if p.sorted == nil {
p.sort()
}
return p.sorted return p.sorted
} }
func (p *Program) sort() { // Parse parses all packages and typechecks them.
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. // The returned error may be an Errors error, which contains a list of errors.
// //
// Idempotent. // Idempotent.
func (p *Program) Parse(compileTestBinary bool) error { func (p *Program) Parse() 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. // Parse all packages.
for _, pkg := range p.Sorted() { for _, pkg := range p.Sorted() {
err := pkg.Parse(includeTests) err := pkg.Parse()
if err != nil { if err != nil {
return err return err
} }
} }
if compileTestBinary { if p.Tests {
err := p.SwapTestMain() err := p.swapTestMain()
if err != nil { if err != nil {
return err return err
} }
@@ -231,7 +195,7 @@ func (p *Program) Parse(compileTestBinary bool) error {
return nil return nil
} }
func (p *Program) SwapTestMain() error { func (p *Program) swapTestMain() error {
var tests []string var tests []string
isTestFunc := func(f *ast.FuncDecl) bool { isTestFunc := func(f *ast.FuncDecl) bool {
@@ -241,8 +205,7 @@ func (p *Program) SwapTestMain() error {
} }
return false return false
} }
mainPkg := p.Packages[p.mainPkg] for _, f := range p.MainPkg.Files {
for _, f := range mainPkg.Files {
for i, d := range f.Decls { for i, d := range f.Decls {
switch v := d.(type) { switch v := d.(type) {
case *ast.FuncDecl: case *ast.FuncDecl:
@@ -293,7 +256,7 @@ func main () {
if err != nil { if err != nil {
return err return err
} }
path := filepath.Join(p.mainPkg, "$testmain.go") path := filepath.Join(p.MainPkg.Dir, "$testmain.go")
if p.fset == nil { if p.fset == nil {
p.fset = token.NewFileSet() p.fset = token.NewFileSet()
@@ -303,7 +266,7 @@ func main () {
if err != nil { if err != nil {
return err return err
} }
mainPkg.Files = append(mainPkg.Files, newMain) p.MainPkg.Files = append(p.MainPkg.Files, newMain)
return nil return nil
} }
@@ -319,34 +282,41 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
return nil, err return nil, err
} }
defer rd.Close() defer rd.Close()
relpath := path diagnosticPath := path
if filepath.IsAbs(path) { if strings.HasPrefix(path, p.Build.GOROOT+string(filepath.Separator)) {
rp, err := filepath.Rel(p.Dir, path) // If this file is part of the synthetic GOROOT, try to infer the
if err == nil { // original path.
relpath = rp 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
} }
} }
return parser.ParseFile(p.fset, relpath, rd, mode) return parser.ParseFile(p.fset, diagnosticPath, rd, mode)
} }
// Parse parses and typechecks this package. // Parse parses and typechecks this package.
// //
// Idempotent. // Idempotent.
func (p *Package) Parse(includeTests bool) error { func (p *Package) Parse() error {
if len(p.Files) != 0 { if len(p.Files) != 0 {
return nil return nil
} }
// Load the AST. // Load the AST.
// TODO: do this in parallel. // TODO: do this in parallel.
if p.ImportPath == "unsafe" { if p.PkgPath == "unsafe" {
// Special case for the unsafe package. Don't even bother loading // Special case for the unsafe package. Don't even bother loading
// the files. // the files.
p.Pkg = types.Unsafe p.Pkg = types.Unsafe
return nil return nil
} }
files, err := p.parseFiles(includeTests) files, err := p.parseFiles()
if err != nil { if err != nil {
return err return err
} }
@@ -373,7 +343,7 @@ func (p *Package) Check() error {
// Do typechecking of the package. // Do typechecking of the package.
checker.Importer = p checker.Importer = p
typesPkg, err := checker.Check(p.ImportPath, p.fset, p.Files, &p.Info) typesPkg, err := checker.Check(p.PkgPath, p.fset, p.Files, &p.Info)
if err != nil { if err != nil {
if err, ok := err.(Errors); ok { if err, ok := err.(Errors); ok {
return err return err
@@ -385,22 +355,14 @@ func (p *Package) Check() error {
} }
// parseFiles parses the loaded list of files and returns this list. // parseFiles parses the loaded list of files and returns this list.
func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) { func (p *Package) parseFiles() ([]*ast.File, error) {
// TODO: do this concurrently. // TODO: do this concurrently.
var files []*ast.File var files []*ast.File
var fileErrs []error var fileErrs []error
var gofiles []string var cgoFiles []*ast.File
if includeTests { for _, file := range p.GoFiles {
gofiles = make([]string, 0, len(p.GoFiles)+len(p.TestGoFiles)) f, err := p.parseFile(file, parser.ParseComments)
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 { if err != nil {
fileErrs = append(fileErrs, err) fileErrs = append(fileErrs, err)
continue continue
@@ -409,19 +371,15 @@ func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
fileErrs = append(fileErrs, err) fileErrs = append(fileErrs, err)
continue continue
} }
for _, importSpec := range f.Imports {
if importSpec.Path.Value == `"C"` {
cgoFiles = append(cgoFiles, f)
}
}
files = append(files, f) files = append(files, f)
} }
for _, file := range p.CgoFiles { if len(cgoFiles) != 0 {
path := filepath.Join(p.Package.Dir, file) cflags := append(p.CFlags, "-I"+filepath.Dir(p.GoFiles[0]))
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 != "" { if p.ClangHeaders != "" {
cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.ClangHeaders) cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.ClangHeaders)
} }
@@ -445,58 +403,8 @@ func (p *Package) Import(to string) (*types.Package, error) {
return types.Unsafe, nil return types.Unsafe, nil
} }
if _, ok := p.Imports[to]; ok { if _, ok := p.Imports[to]; ok {
return p.Imports[to].Pkg, nil return p.Packages[p.Imports[to].PkgPath].Pkg, nil
} else { } else {
return nil, errors.New("package not imported: " + to) 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
}
+76 -8
View File
@@ -2,6 +2,7 @@ package main
import ( import (
"bytes" "bytes"
"encoding/json"
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
@@ -124,6 +125,7 @@ func Build(pkgName, outpath string, options *compileopts.Options) error {
// Test runs the tests in the given package. // Test runs the tests in the given package.
func Test(pkgName string, options *compileopts.Options) error { func Test(pkgName string, options *compileopts.Options) error {
options.TestConfig.CompileTestBinary = true
config, err := builder.NewConfig(options) config, err := builder.NewConfig(options)
if err != nil { if err != nil {
return err return err
@@ -135,7 +137,6 @@ func Test(pkgName string, options *compileopts.Options) error {
// For details: https://github.com/golang/go/issues/21360 // For details: https://github.com/golang/go/issues/21360
config.Target.BuildTags = append(config.Target.BuildTags, "test") config.Target.BuildTags = append(config.Target.BuildTags, "test")
options.TestConfig.CompileTestBinary = true
return builder.Build(pkgName, ".elf", config, func(tmppath string) error { return builder.Build(pkgName, ".elf", config, func(tmppath string) error {
cmd := exec.Command(tmppath) cmd := exec.Command(tmppath)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
@@ -641,6 +642,36 @@ func getDefaultPort() (port string, err error) {
return d[0], nil 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() { func usage() {
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.") fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
fmt.Fprintln(os.Stderr, "version:", version) fmt.Fprintln(os.Stderr, "version:", version)
@@ -652,6 +683,7 @@ func usage() {
fmt.Fprintln(os.Stderr, " flash: compile and flash to the device") 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, " gdb: run/flash and immediately enter GDB")
fmt.Fprintln(os.Stderr, " env: list environment variables used during build") 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, " clean: empty cache directory ("+goenv.Get("GOCACHE")+")")
fmt.Fprintln(os.Stderr, " help: print this help text") fmt.Fprintln(os.Stderr, " help: print this help text")
fmt.Fprintln(os.Stderr, "\nflags:") fmt.Fprintln(os.Stderr, "\nflags:")
@@ -683,7 +715,7 @@ func printCompilerError(logln func(...interface{}), err error) {
} }
} }
case loader.Errors: case loader.Errors:
logln("#", err.Pkg.ImportPath) logln("#", err.Pkg.PkgPath)
for _, err := range err.Errs { for _, err := range err.Errs {
logln(err) logln(err)
} }
@@ -706,6 +738,13 @@ func handleCompilerError(err error) {
} }
func main() { 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") outpath := flag.String("o", "", "output filename")
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z") opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
gc := flag.String("gc", "", "garbage collector to use (none, leaking, extalloc, conservative)") gc := flag.String("gc", "", "garbage collector to use (none, leaking, extalloc, conservative)")
@@ -726,12 +765,13 @@ func main() {
wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic") 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)") heapSize := flag.String("heap-size", "1M", "default heap size in bytes (only supported by WebAssembly)")
if len(os.Args) < 2 { var flagJSON, flagDeps *bool
fmt.Fprintln(os.Stderr, "No command-line arguments supplied.") if command == "list" || command == "env" {
usage() flagJSON = flag.Bool("json", false, "print data in JSON format")
os.Exit(1) }
if command == "list" {
flagDeps = flag.Bool("deps", false, "")
} }
command := os.Args[1]
// Early command processing, before commands are interpreted by the Go flag // Early command processing, before commands are interpreted by the Go flag
// library. // library.
@@ -895,6 +935,18 @@ func main() {
fmt.Printf("build tags: %s\n", strings.Join(config.BuildTags(), " ")) fmt.Printf("build tags: %s\n", strings.Join(config.BuildTags(), " "))
fmt.Printf("garbage collector: %s\n", config.GC()) fmt.Printf("garbage collector: %s\n", config.GC())
fmt.Printf("scheduler: %s\n", config.Scheduler()) 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": case "clean":
// remove cache directory // remove cache directory
err := os.RemoveAll(goenv.Get("GOCACHE")) err := os.RemoveAll(goenv.Get("GOCACHE"))
@@ -906,11 +958,26 @@ func main() {
usage() usage()
case "version": case "version":
goversion := "<unknown>" goversion := "<unknown>"
if s, err := builder.GorootVersionString(goenv.Get("GOROOT")); err == nil { if s, err := goenv.GorootVersionString(goenv.Get("GOROOT")); err == nil {
goversion = s 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) 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": case "env":
if *flagJSON {
keys := goenv.Keys
if flag.NArg() != 0 {
// Show only one (or a few) environment variables.
keys = flag.Args()
}
// 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 { if flag.NArg() == 0 {
// Show all environment variables. // Show all environment variables.
for _, key := range goenv.Keys { for _, key := range goenv.Keys {
@@ -922,6 +989,7 @@ func main() {
fmt.Println(goenv.Get(flag.Arg(i))) fmt.Println(goenv.Get(flag.Arg(i)))
} }
} }
}
default: default:
fmt.Fprintln(os.Stderr, "Unknown command:", command) fmt.Fprintln(os.Stderr, "Unknown command:", command)
usage() usage()
+1 -1
View File
@@ -73,7 +73,7 @@ func TestCompiler(t *testing.T) {
t.Run("ARM64Linux", func(t *testing.T) { t.Run("ARM64Linux", func(t *testing.T) {
runPlatTests("aarch64--linux-gnu", matches, t) runPlatTests("aarch64--linux-gnu", matches, t)
}) })
goVersion, err := builder.GorootVersionString(goenv.Get("GOROOT")) goVersion, err := goenv.GorootVersionString(goenv.Get("GOROOT"))
if err != nil { if err != nil {
t.Error("could not get Go version:", err) t.Error("could not get Go version:", err)
return return
+13 -15
View File
@@ -52,11 +52,10 @@ func Asm(asm string)
// "value": 1 // "value": 1
// "result": &dest, // "result": &dest,
// }) // })
func AsmFull(asm string, regs map[string]interface{}) //
// You can use {} in the asm string (which expands to a register) to set the
// ReadRegister returns the contents of the specified register. The register // return value.
// must be a processor register, reachable with the "mov" instruction. func AsmFull(asm string, regs map[string]interface{}) uintptr
func ReadRegister(name string) uintptr
// Run the following system call (SVCall) with 0 arguments. // Run the following system call (SVCall) with 0 arguments.
func SVCall0(num uintptr) uintptr func SVCall0(num uintptr) uintptr
@@ -192,22 +191,21 @@ func SetPriority(irq uint32, priority uint32) {
NVIC.IPR[regnum].Set((uint32(NVIC.IPR[regnum].Get()) &^ mask) | priority) NVIC.IPR[regnum].Set((uint32(NVIC.IPR[regnum].Get()) &^ mask) | priority)
} }
// DisableInterrupts disables all interrupts, and returns the old state. // DisableInterrupts disables all interrupts, and returns the old interrupt
// // state.
// TODO: it doesn't actually return the old state, meaning that it cannot be
// nested.
func DisableInterrupts() uintptr { func DisableInterrupts() uintptr {
Asm("cpsid if") return AsmFull(`
return 0 mrs {}, PRIMASK
cpsid if
`, nil)
} }
// EnableInterrupts enables all interrupts again. The value passed in must be // EnableInterrupts enables all interrupts again. The value passed in must be
// the mask returned by DisableInterrupts. // the mask returned by DisableInterrupts.
//
// TODO: it doesn't actually use the old state, meaning that it cannot be
// nested.
func EnableInterrupts(mask uintptr) { func EnableInterrupts(mask uintptr) {
Asm("cpsie if") AsmFull("msr PRIMASK, {mask}", map[string]interface{}{
"mask": mask,
})
} }
// SystemReset performs a hard system reset. // SystemReset performs a hard system reset.
+4 -1
View File
@@ -15,4 +15,7 @@ func Asm(asm string)
// "value": 1 // "value": 1
// "result": &dest, // "result": &dest,
// }) // })
func AsmFull(asm string, regs map[string]interface{}) //
// You can use {} in the asm string (which expands to a register) to set the
// return value.
func AsmFull(asm string, regs map[string]interface{}) uintptr
+14 -3
View File
@@ -5,6 +5,17 @@ package riscv
// optimizer. // optimizer.
func Asm(asm string) func Asm(asm string)
// ReadRegister returns the contents of the specified register. The register // Run the given inline assembly. The code will be marked as having side
// must be a processor register, reachable with the "mov" instruction. // effects, as it would otherwise be optimized away. The inline assembly string
func ReadRegister(name string) uintptr // recognizes template values in the form {name}, like so:
//
// arm.AsmFull(
// "st {value}, {result}",
// map[string]interface{}{
// "value": 1
// "result": &dest,
// })
//
// You can use {} in the asm string (which expands to a register) to set the
// return value.
func AsmFull(asm string, regs map[string]interface{}) uintptr
+1 -1
View File
@@ -15,5 +15,5 @@ func align(ptr uintptr) uintptr {
} }
func getCurrentStackPointer() uintptr { func getCurrentStackPointer() uintptr {
return arm.ReadRegister("sp") return arm.AsmFull("mov {}, sp", nil)
} }
+1 -1
View File
@@ -17,5 +17,5 @@ func align(ptr uintptr) uintptr {
} }
func getCurrentStackPointer() uintptr { func getCurrentStackPointer() uintptr {
return arm.ReadRegister("sp") return arm.AsmFull("mov {}, sp", nil)
} }
+1 -1
View File
@@ -15,5 +15,5 @@ func align(ptr uintptr) uintptr {
} }
func getCurrentStackPointer() uintptr { func getCurrentStackPointer() uintptr {
return riscv.ReadRegister("sp") return riscv.AsmFull("mv {}, sp", nil)
} }
+16 -20
View File
@@ -47,23 +47,8 @@ func printint16(n int16) {
printint32(int32(n)) printint32(int32(n))
} }
//go:nobounds
func printuint32(n uint32) { func printuint32(n uint32) {
digits := [10]byte{} // enough to hold (2^32)-1 printuint64(uint64(n))
// Fill in all 10 digits.
firstdigit := 9 // digit index that isn't zero (by default, the last to handle '0' correctly)
for i := 9; i >= 0; i-- {
digit := byte(n%10 + '0')
digits[i] = digit
if digit != '0' {
firstdigit = i
}
n /= 10
}
// Print digits without the leading zeroes.
for i := firstdigit; i < 10; i++ {
putchar(digits[i])
}
} }
func printint32(n int32) { func printint32(n int32) {
@@ -76,12 +61,23 @@ func printint32(n int32) {
printuint32(uint32(n)) printuint32(uint32(n))
} }
//go:nobounds
func printuint64(n uint64) { func printuint64(n uint64) {
prevdigits := n / 10 digits := [20]byte{} // enough to hold (2^64)-1
if prevdigits != 0 { // Fill in all 10 digits.
printuint64(prevdigits) firstdigit := 19 // digit index that isn't zero (by default, the last to handle '0' correctly)
for i := 19; i >= 0; i-- {
digit := byte(n%10 + '0')
digits[i] = digit
if digit != '0' {
firstdigit = i
}
n /= 10
}
// Print digits without the leading zeroes.
for i := firstdigit; i < 20; i++ {
putchar(digits[i])
} }
putchar(byte((n % 10) + '0'))
} }
func printint64(n int64) { func printint64(n int64) {
+1
View File
@@ -188,6 +188,7 @@ tinygo_switchToScheduler:
// Return into the scheduler, as if tinygo_switchToTask was a regular call. // Return into the scheduler, as if tinygo_switchToTask was a regular call.
ret ret
.section .text.tinygo_scanCurrentStack
.global tinygo_scanCurrentStack .global tinygo_scanCurrentStack
.type tinygo_scanCurrentStack, %function .type tinygo_scanCurrentStack, %function
tinygo_scanCurrentStack: tinygo_scanCurrentStack:
+1
View File
@@ -112,6 +112,7 @@ tinygo_swapTask:
pop {pc} pop {pc}
#endif #endif
.section .text.tinygo_scanCurrentStack
.global tinygo_scanCurrentStack .global tinygo_scanCurrentStack
.type tinygo_scanCurrentStack, %function .type tinygo_scanCurrentStack, %function
tinygo_scanCurrentStack: tinygo_scanCurrentStack:
+19
View File
@@ -0,0 +1,19 @@
.section .text.tinygo_scanCurrentStack
.global tinygo_scanCurrentStack
.type tinygo_scanCurrentStack, %function
tinygo_scanCurrentStack:
// Save callee-saved registers onto the stack.
mov r0, r8
mov r1, r9
mov r2, r10
mov r3, r11
push {r0-r3, lr}
push {r4-r7}
// Scan the stack.
mov r0, sp
bl tinygo_scanstack
// Restore stack state and return.
add sp, #32
pop {pc}
+3
View File
@@ -22,6 +22,9 @@ tinygo_scanCurrentStack:
mv a0, sp mv a0, sp
call tinygo_scanstack call tinygo_scanstack
// Restore return address.
lw ra, 60(sp)
// Restore stack state. // Restore stack state.
addi sp, sp, 64 addi sp, sp, 64
+4 -3
View File
@@ -1,5 +1,5 @@
{ {
"llvm-target": "thumb4-none-eabi", "llvm-target": "arm4-none-eabi",
"cpu": "arm7tdmi", "cpu": "arm7tdmi",
"build-tags": ["gameboyadvance", "arm7tdmi", "baremetal", "linux", "arm"], "build-tags": ["gameboyadvance", "arm7tdmi", "baremetal", "linux", "arm"],
"goos": "linux", "goos": "linux",
@@ -10,7 +10,7 @@
"libc": "picolibc", "libc": "picolibc",
"cflags": [ "cflags": [
"-g", "-g",
"--target=thumb4-none-eabi", "--target=arm4-none-eabi",
"-mcpu=arm7tdmi", "-mcpu=arm7tdmi",
"-Oz", "-Oz",
"-Werror", "-Werror",
@@ -25,7 +25,8 @@
], ],
"linkerscript": "targets/gameboy-advance.ld", "linkerscript": "targets/gameboy-advance.ld",
"extra-files": [ "extra-files": [
"targets/gameboy-advance.s" "targets/gameboy-advance.s",
"src/runtime/scheduler_gba.S"
], ],
"gdb": "gdb-multiarch", "gdb": "gdb-multiarch",
"emulator": ["mgba", "-3"] "emulator": ["mgba", "-3"]
+1 -1
View File
@@ -2,4 +2,4 @@ package main
// version of this package. // version of this package.
// Update this value before release of new version of software. // Update this value before release of new version of software.
const version = "0.13.0" const version = "0.14.0-dev"