Compare commits

...

24 Commits

Author SHA1 Message Date
Ayke van Laethem ebde8b5875 targets/gba: implement interrupt handler 2020-01-06 14:45:32 +01:00
Ayke van Laethem 9d50587d63 targets/gba: make linker script cleaner
Make it clearer where the stack is located.
2020-01-06 11:48:09 +01:00
Ayke van Laethem 5e6f1725ac WIP interrupts via ptrtoint handler 2020-01-05 08:05:38 +01:00
Ayke van Laethem 9cdc2fa768 WIP: interrupt API 2020-01-04 23:40:45 +01:00
Ayke van Laethem 084386262a compiler: add support for debugging globals
This makes most globals visible from GDB, using `info variables`.
2020-01-04 23:40:44 +01:00
Ayke van Laethem 674ddef219 compiler: add globaldce pass to start of optimization pipeline
This reduces code size in a few cases when tested against the drivers
smoketests (although there was one minor increase) without significantly
increasing compile time. In fact, in my testing compile time appears to
be going down a little bit (around 1%, within the noise).
2020-01-04 23:40:44 +01:00
Ayke van Laethem 0933577e60 compiler: improve "function redeclared" error
This error can normally only happen with //go:linkname and such, but
it's nice to get an informative error message in that case.
2020-01-04 10:24:14 +01:00
Ayke van Laethem 69c1d802e1 loader: improve error messages for failed imports
Add location information (whenever possible) to failed imports. This
helps in debugging where an incorrect import came from.

For example, show the following error message:

    /home/ayke/src/github.com/tinygo-org/tinygo/src/machine/machine.go:5:8: cannot find package "foobar" in any of:
        /usr/local/go/src/foobar (from $GOROOT)
        /home/ayke/src/foobar (from $GOPATH)

Instead of the following:

    error: cannot find package "foobar" in any of:
        /usr/local/go/src/foobar (from $GOROOT)
        /home/ayke/src/foobar (from $GOPATH)
2020-01-04 00:01:07 +01:00
Ayke van Laethem b424056721 cgo: fix a bug in number tokenization 2020-01-03 23:44:58 +01:00
Ayke van Laethem d735df6e16 cgo: add support for symbols 2020-01-03 23:44:58 +01:00
Ayke van Laethem d37bbadb54 machine/arduino-nano33: fix UART1 and UART2
UART2 was configured with the wrong SERCOM for the used pins (PB22 and
PB23). However, after changing the SERCOM from 3 to 5 that led to a
conflict with UART1 (used for the on-board WiFi). But the used pins are
also usable from SERCOM 3, so in the end I switched SERCOM5 and SERCOM3
around.

With this change, I was able to get examples/echo working.
2020-01-03 23:25:37 +01:00
Ayke van Laethem ab7dc45288 wasm: implement memcpy and memset
This was reported in issue #805.
2019-12-30 20:51:46 +01:00
Jaden Weiss eee1b995f6 revise defer to use heap allocations when running a variable number of times 2019-12-30 18:12:40 +01:00
Ayke van Laethem a4fa41b49d compiler: don't crash when encountering types.Invalid
This commit fixes a crash when trying to compile the following (invalid)
code:

    package main

    import "unsafe"

    func main() {
    }

    type Foo struct {
       x DoesNotExist
    }

    const foo = unsafe.Sizeof(Foo{})

This commit fixes this situation. The result is a regular error message,
indicating that DoesNotExist is not defined.
2019-12-30 13:40:37 +01:00
Ayke van Laethem a5a90a57b9 main: remove getting a serial port in gdb subcommand
Remove this code for two reasons:

 1. It is not needed.
 2. It breaks `tinygo gdb` for debugging QEMU targets (such as
    cortex-m-qemu).
2019-12-29 19:41:24 +01:00
Dmitri Goutnik 71a380ce8c Add initial FreeBSD support 2019-12-29 10:48:28 +01:00
Ayke van Laethem 3b2a4b64c5 main: kill tests if they run too long
Sometimes, tests suddenly hang somewhere (in particular in emulators
where crashes often lead to hangs). Setting a limit has two advantages:

  1. Quickly killing test processes that are frozen (as opposed to
     waiting for the default 10min go test timeout).
  2. The output becomes visible, hopefully giving a clue what went
     wrong.
2019-12-28 21:10:13 +01:00
Ayke van Laethem 184827e4d8 riscv: support sleeping in QEMU
QEMU doesn't support the RTC peripheral yet so work around it for now.

This makes the following command work:

    tinygo run -target=hifive1-qemu ./testdata/coroutines.go
2019-12-26 09:57:58 +01:00
Ayke van Laethem 14474e7099 compiler: fix assertion on empty interface
This fixes issue #453.
2019-12-26 09:20:22 +01:00
Ron Evans 3656ac2fc9 main: increment version to 0.12-dev
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-12-24 19:48:38 +01:00
Ayke van Laethem 08f01ba3ff riscv: improve startup assembly
Now that we've switched to LLVM 9, we don't need a workaround anymore
for the 'la' pseudo-instruction.
2019-12-24 19:00:22 +01:00
Ayke van Laethem 699312f477 ci: remove symlink creation
These symlinks are now unnecessary because Clang (and previously lld)
have been integrated into TinyGo.
2019-12-24 08:59:33 +01:00
Ayke van Laethem 9644edcd5a builder: update Clang header location
We have long since moved towards a different location for these headers
in the git checkout, so update where getClangHeaderPath looks for these
headers.

Also add an extra check to make sure a path has been detected.
2019-12-24 08:59:33 +01:00
Ayke van Laethem 46325910c5 ci: increase Azure Pipelines timeout to 4 hours
3 hours is too close to the edge. Extend to 4 hours to make sure a build
including LLVM build will finish before the deadline.
2019-12-24 08:49:04 +01:00
71 changed files with 1030 additions and 320 deletions
-16
View File
@@ -120,12 +120,6 @@ commands:
key: llvm-build-9-linux-v0-assert key: llvm-build-9-linux-v0-assert
paths: paths:
llvm-build llvm-build
- run:
name: "Create LLVM symlinks"
command: |
ln -s $PWD/llvm-build/bin/clang-9 /go/bin/clang-9
ln -s $PWD/llvm-build/bin/ld.lld /go/bin/ld.lld-9
ln -s $PWD/llvm-build/bin/wasm-ld /go/bin/wasm-ld-9
- run: make ASSERT=1 - run: make ASSERT=1
- run: - run:
name: "Test TinyGo" name: "Test TinyGo"
@@ -184,12 +178,6 @@ commands:
key: llvm-build-9-linux-v0 key: llvm-build-9-linux-v0
paths: paths:
llvm-build llvm-build
- run:
name: "Create LLVM symlinks"
command: |
ln -s $PWD/llvm-build/bin/clang-9 /go/bin/clang-9
ln -s $PWD/llvm-build/bin/ld.lld /go/bin/ld.lld-9
ln -s $PWD/llvm-build/bin/wasm-ld /go/bin/wasm-ld-9
- run: - run:
name: "Test TinyGo" name: "Test TinyGo"
command: make test command: make test
@@ -256,10 +244,6 @@ commands:
key: llvm-build-9-macos-v0 key: llvm-build-9-macos-v0
paths: paths:
llvm-build llvm-build
- run:
name: "Create LLVM symlinks"
command: |
ln -s $PWD/llvm-build/bin/clang-9 /usr/local/bin/clang-9
- run: - run:
name: "Test TinyGo" name: "Test TinyGo"
command: make test command: make test
+5
View File
@@ -62,6 +62,11 @@ $(LIBCLANG_PATH): $(LIBCLANG_FILES)
else ifeq ($(shell uname -s),Darwin) else ifeq ($(shell uname -s),Darwin)
MD5SUM = md5 MD5SUM = md5
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
else ifeq ($(shell uname -s),FreeBSD)
MD5SUM = md5
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group
else else
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
START_GROUP = -Wl,--start-group START_GROUP = -Wl,--start-group
+1 -1
View File
@@ -6,7 +6,7 @@ trigger:
jobs: jobs:
- job: Build - job: Build
timeoutInMinutes: 180 timeoutInMinutes: 240 # 4h
pool: pool:
vmImage: 'VS2017-Win2016' vmImage: 'VS2017-Win2016'
steps: steps:
+6
View File
@@ -34,6 +34,12 @@ func init() {
commands["ld.lld"] = append(commands["ld.lld"], "lld", "C:\\Program Files\\LLVM\\bin\\lld.exe") commands["ld.lld"] = append(commands["ld.lld"], "lld", "C:\\Program Files\\LLVM\\bin\\lld.exe")
commands["wasm-ld"] = append(commands["wasm-ld"], "C:\\Program Files\\LLVM\\bin\\wasm-ld.exe") commands["wasm-ld"] = append(commands["wasm-ld"], "C:\\Program Files\\LLVM\\bin\\wasm-ld.exe")
} }
// Add the path to the llvm90 installed from ports
if runtime.GOOS == "freebsd" {
commands["clang"] = append(commands["clang"], "/usr/local/llvm90/bin/clang-9")
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/llvm90/bin/ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/llvm90/bin/wasm-ld")
}
} }
func execCommand(cmdNames []string, args ...string) error { func execCommand(cmdNames []string, args ...string) error {
+5 -1
View File
@@ -26,7 +26,11 @@ func runCCompiler(command string, flags ...string) error {
switch command { switch command {
case "clang": case "clang":
// Compile this with the internal Clang compiler. // Compile this with the internal Clang compiler.
flags = append(flags, "-I"+getClangHeaderPath(goenv.Get("TINYGOROOT"))) headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
if headerPath == "" {
return errors.New("could not locate Clang headers")
}
flags = append(flags, "-I"+headerPath)
flags = append([]string{"tinygo:" + command}, flags...) flags = append([]string{"tinygo:" + command}, flags...)
var cflag *C.char var cflag *C.char
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag))) buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
+1 -1
View File
@@ -71,7 +71,7 @@ func GorootVersionString(goroot string) (string, error) {
// various ways. // various ways.
func getClangHeaderPath(TINYGOROOT string) string { func getClangHeaderPath(TINYGOROOT string) string {
// Check whether we're running from the source directory. // Check whether we're running from the source directory.
path := filepath.Join(TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers") path := filepath.Join(TINYGOROOT, "llvm-project", "clang", "lib", "Headers")
if _, err := os.Stat(path); !os.IsNotExist(err) { if _, err := os.Stat(path); !os.IsNotExist(err) {
return path return path
} }
+1 -1
View File
@@ -22,7 +22,7 @@ var flagUpdate = flag.Bool("update", false, "Update images based on test output.
func TestCGo(t *testing.T) { func TestCGo(t *testing.T) {
var cflags = []string{"--target=armv6m-none-eabi"} var cflags = []string{"--target=armv6m-none-eabi"}
for _, name := range []string{"basic", "errors", "types", "flags"} { for _, name := range []string{"basic", "errors", "types", "flags", "const"} {
name := name // avoid a race condition name := name // avoid a race condition
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
// Read the AST in memory. // Read the AST in memory.
+25 -1
View File
@@ -52,6 +52,13 @@ func parseConstExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
} }
t.Next() t.Next()
return expr, nil return expr, nil
case token.IDENT:
expr := &ast.Ident{
NamePos: t.pos,
Name: "C." + t.value,
}
t.Next()
return expr, nil
case token.EOF: case token.EOF:
return nil, &scanner.Error{ return nil, &scanner.Error{
Pos: t.fset.Position(t.pos), Pos: t.fset.Position(t.pos),
@@ -133,8 +140,10 @@ func (t *tokenizer) Next() {
if c == '.' { if c == '.' {
hasDot = true hasDot = true
} }
if (c >= '0' && c <= '9') || c == '.' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') { if c >= '0' && c <= '9' || c == '.' || c == '_' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' {
tokenLen = i + 1 tokenLen = i + 1
} else {
break
} }
} }
t.value = t.buf[:tokenLen] t.value = t.buf[:tokenLen]
@@ -150,6 +159,21 @@ func (t *tokenizer) Next() {
t.value = strings.TrimRight(t.value, "uUlL") t.value = strings.TrimRight(t.value, "uUlL")
} }
return return
case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_':
// Identifier. Find all remaining tokens that are part of this
// identifier.
tokenLen := len(t.buf)
for i, c := range t.buf {
if c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_' {
tokenLen = i + 1
} else {
break
}
}
t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
t.token = token.IDENT
return
case c == '"': case c == '"':
// String constant. Find the first '"' character that is not // String constant. Find the first '"' character that is not
// preceded by a backslash. // preceded by a backslash.
+3 -2
View File
@@ -21,8 +21,8 @@ func TestParseConst(t *testing.T) {
{`5)`, `error: 1:2: unexpected token )`}, {`5)`, `error: 1:2: unexpected token )`},
{" \t)", `error: 1:4: unexpected token )`}, {" \t)", `error: 1:4: unexpected token )`},
{`5.8f`, `5.8`}, {`5.8f`, `5.8`},
{`foo`, `error: 1:1: unexpected token ILLEGAL`}, // identifiers unimplemented {`foo`, `C.foo`},
{``, `error: 1:1: empty constant`}, // empty constants not allowed in Go {``, `error: 1:1: empty constant`}, // empty constants not allowed in Go
{`"foo"`, `"foo"`}, {`"foo"`, `"foo"`},
{`"a\\n"`, `"a\\n"`}, {`"a\\n"`, `"a\\n"`},
{`"a\n"`, `"a\n"`}, {`"a\n"`, `"a\n"`},
@@ -30,6 +30,7 @@ func TestParseConst(t *testing.T) {
{`'a'`, `'a'`}, {`'a'`, `'a'`},
{`0b10`, `0b10`}, {`0b10`, `0b10`},
{`0x1234_5678`, `0x1234_5678`}, {`0x1234_5678`, `0x1234_5678`},
{`5 5`, `error: 1:3: unexpected token INT`}, // test for a bugfix
} { } {
fset := token.NewFileSet() fset := token.NewFileSet()
startPos := fset.AddFile("", -1, 1000).Pos(0) startPos := fset.AddFile("", -1, 1000).Pos(0)
+2
View File
@@ -5,7 +5,9 @@ package cgo
/* /*
#cgo linux CFLAGS: -I/usr/lib/llvm-9/include #cgo linux CFLAGS: -I/usr/lib/llvm-9/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@9/include #cgo darwin CFLAGS: -I/usr/local/opt/llvm@9/include
#cgo freebsd CFLAGS: -I/usr/local/llvm90/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-9/lib -lclang #cgo linux LDFLAGS: -L/usr/lib/llvm-9/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@9/lib -lclang -lffi #cgo darwin LDFLAGS: -L/usr/local/opt/llvm@9/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm90/lib -lclang
*/ */
import "C" import "C"
+12
View File
@@ -0,0 +1,12 @@
package main
/*
#define foo 3
#define bar foo
*/
import "C"
const (
Foo = C.foo
Bar = C.bar
)
+29
View File
@@ -0,0 +1,29 @@
package main
import "unsafe"
var _ unsafe.Pointer
const C.bar = C.foo
const C.foo = 3
type C.int16_t = int16
type C.int32_t = int32
type C.int64_t = int64
type C.int8_t = int8
type C.uint16_t = uint16
type C.uint32_t = uint32
type C.uint64_t = uint64
type C.uint8_t = uint8
type C.uintptr_t = uintptr
type C.char uint8
type C.int int32
type C.long int32
type C.longlong int64
type C.schar int8
type C.short int16
type C.uchar uint8
type C.uint uint32
type C.ulong uint32
type C.ulonglong uint64
type C.ushort uint16
+30 -16
View File
@@ -217,7 +217,7 @@ func (c *Compiler) Compile(mainPath string) []error {
path = path[len(tinygoPath+"/src/"):] path = path[len(tinygoPath+"/src/"):]
} }
switch path { switch path {
case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync", "testing", "internal/reflectlite": case "machine", "os", "reflect", "runtime", "runtime/interrupt", "runtime/volatile", "sync", "testing", "internal/reflectlite":
return path return path
default: default:
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") { if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
@@ -251,13 +251,17 @@ func (c *Compiler) Compile(mainPath string) []error {
return []error{err} return []error{err}
} }
} else { } else {
_, err = lprogram.Import(mainPath, wd) _, err = lprogram.Import(mainPath, wd, token.Position{
Filename: "build command-line-arguments",
})
if err != nil { if err != nil {
return []error{err} return []error{err}
} }
} }
_, err = lprogram.Import("runtime", "") _, err = lprogram.Import("runtime", "", token.Position{
Filename: "build default import",
})
if err != nil { if err != nil {
return []error{err} return []error{err}
} }
@@ -767,14 +771,6 @@ func (c *Compiler) attachDebugInfo(f *ir.Function) llvm.Metadata {
} }
func (c *Compiler) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix, filename string, line int) llvm.Metadata { func (c *Compiler) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix, filename string, line int) llvm.Metadata {
if _, ok := c.difiles[filename]; !ok {
dir, file := filepath.Split(filename)
if dir != "" {
dir = dir[:len(dir)-1]
}
c.difiles[filename] = c.dibuilder.CreateFile(file, dir)
}
// Debug info for this function. // Debug info for this function.
diparams := make([]llvm.Metadata, 0, len(f.Params)) diparams := make([]llvm.Metadata, 0, len(f.Params))
for _, param := range f.Params { for _, param := range f.Params {
@@ -788,7 +784,7 @@ func (c *Compiler) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix,
difunc := c.dibuilder.CreateFunction(c.difiles[filename], llvm.DIFunction{ difunc := c.dibuilder.CreateFunction(c.difiles[filename], llvm.DIFunction{
Name: f.RelString(nil) + suffix, Name: f.RelString(nil) + suffix,
LinkageName: f.LinkName() + suffix, LinkageName: f.LinkName() + suffix,
File: c.difiles[filename], File: c.getDIFile(filename),
Line: line, Line: line,
Type: diFuncType, Type: diFuncType,
LocalToUnit: true, LocalToUnit: true,
@@ -801,21 +797,37 @@ func (c *Compiler) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix,
return difunc return difunc
} }
// getDIFile returns a DIFile metadata node for the given filename. It tries to
// use one that was already created, otherwise it falls back to creating a new
// one.
func (c *Compiler) getDIFile(filename string) llvm.Metadata {
if _, ok := c.difiles[filename]; !ok {
dir, file := filepath.Split(filename)
if dir != "" {
dir = dir[:len(dir)-1]
}
c.difiles[filename] = c.dibuilder.CreateFile(file, dir)
}
return c.difiles[filename]
}
func (c *Compiler) parseFunc(frame *Frame) { func (c *Compiler) parseFunc(frame *Frame) {
if c.DumpSSA() { if c.DumpSSA() {
fmt.Printf("\nfunc %s:\n", frame.fn.Function) fmt.Printf("\nfunc %s:\n", frame.fn.Function)
} }
if !frame.fn.LLVMFn.IsDeclaration() { if !frame.fn.LLVMFn.IsDeclaration() {
c.addError(frame.fn.Pos(), "function is already defined:"+frame.fn.LLVMFn.Name()) errValue := frame.fn.LLVMFn.Name() + " redeclared in this program"
fnPos := getPosition(frame.fn.LLVMFn)
if fnPos.IsValid() {
errValue += "\n\tprevious declaration at " + fnPos.String()
}
c.addError(frame.fn.Pos(), errValue)
return return
} }
if !frame.fn.IsExported() { if !frame.fn.IsExported() {
frame.fn.LLVMFn.SetLinkage(llvm.InternalLinkage) frame.fn.LLVMFn.SetLinkage(llvm.InternalLinkage)
frame.fn.LLVMFn.SetUnnamedAddr(true) frame.fn.LLVMFn.SetUnnamedAddr(true)
} }
if frame.fn.IsInterrupt() && strings.HasPrefix(c.Triple(), "avr") {
frame.fn.LLVMFn.SetFunctionCallConv(85) // CallingConv::AVR_SIGNAL
}
// Some functions have a pragma controlling the inlining level. // Some functions have a pragma controlling the inlining level.
switch frame.fn.Inline() { switch frame.fn.Inline() {
@@ -1297,6 +1309,8 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
return c.emitVolatileLoad(frame, instr) return c.emitVolatileLoad(frame, instr)
case strings.HasPrefix(name, "runtime/volatile.Store"): case strings.HasPrefix(name, "runtime/volatile.Store"):
return c.emitVolatileStore(frame, instr) return c.emitVolatileStore(frame, instr)
case name == "runtime/interrupt.New" && len(fn.Blocks) == 0:
return c.emitInterruptGlobal(frame, instr)
} }
targetFunc := c.ir.GetFunction(fn) targetFunc := c.ir.GetFunction(fn)
+48 -3
View File
@@ -14,6 +14,7 @@ package compiler
// frames. // frames.
import ( import (
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/ir" "github.com/tinygo-org/tinygo/ir"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
@@ -34,6 +35,40 @@ func (c *Compiler) deferInitFunc(frame *Frame) {
c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr) c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr)
} }
// isInLoop checks if there is a path from a basic block to itself.
func isInLoop(start *ssa.BasicBlock) bool {
// Use a breadth-first search to scan backwards through the block graph.
queue := []*ssa.BasicBlock{start}
checked := map[*ssa.BasicBlock]struct{}{}
for len(queue) > 0 {
// pop a block off of the queue
block := queue[len(queue)-1]
queue = queue[:len(queue)-1]
// Search through predecessors.
// Searching backwards means that this is pretty fast when the block is close to the start of the function.
// Defers are often placed near the start of the function.
for _, pred := range block.Preds {
if pred == start {
// cycle found
return true
}
if _, ok := checked[pred]; ok {
// block already checked
continue
}
// add to queue and checked map
queue = append(queue, pred)
checked[pred] = struct{}{}
}
}
return false
}
// emitDefer emits a single defer instruction, to be run when this function // emitDefer emits a single defer instruction, to be run when this function
// returns. // returns.
func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) { func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
@@ -127,12 +162,22 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "") deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
} }
// Put this struct in an alloca. // Put this struct in an allocation.
alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca") var alloca llvm.Value
c.builder.CreateStore(deferFrame, alloca) if !isInLoop(instr.Block()) {
// This can safely use a stack allocation.
alloca = llvmutil.CreateEntryBlockAlloca(c.builder, deferFrameType, "defer.alloca")
} else {
// This may be hit a variable number of times, so use a heap allocation.
size := c.targetData.TypeAllocSize(deferFrameType)
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
allocCall := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
alloca = c.builder.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
}
if c.NeedsStackObjects() { if c.NeedsStackObjects() {
c.trackPointer(alloca) c.trackPointer(alloca)
} }
c.builder.CreateStore(deferFrame, alloca)
// Push it on top of the linked list by replacing deferPtr. // Push it on top of the linked list by replacing deferPtr.
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast") allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
+25 -14
View File
@@ -31,20 +31,31 @@ func errorAt(inst llvm.Value, msg string) scanner.Error {
} }
} }
// getPosition returns the position information for the given instruction, as // getPosition returns the position information for the given value, as far as
// far as it is available. // it is available.
func getPosition(inst llvm.Value) token.Position { func getPosition(val llvm.Value) token.Position {
if inst.IsAInstruction().IsNil() { if !val.IsAInstruction().IsNil() {
loc := val.InstructionDebugLoc()
if loc.IsNil() {
return token.Position{}
}
file := loc.LocationScope().ScopeFile()
return token.Position{
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
Line: int(loc.LocationLine()),
Column: int(loc.LocationColumn()),
}
} else if !val.IsAFunction().IsNil() {
loc := val.Subprogram()
if loc.IsNil() {
return token.Position{}
}
file := loc.ScopeFile()
return token.Position{
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
Line: int(loc.SubprogramLine()),
}
} else {
return token.Position{} return token.Position{}
} }
loc := inst.InstructionDebugLoc()
if loc.IsNil() {
return token.Position{}
}
file := loc.LocationScope().ScopeFile()
return token.Position{
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
Line: int(loc.LocationLine()),
Column: int(loc.LocationColumn()),
}
} }
+3 -3
View File
@@ -279,14 +279,14 @@ func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
return llvm.ConstGEP(global, []llvm.Value{zero, zero}) return llvm.ConstGEP(global, []llvm.Value{zero, zero})
} }
// Every method is a *i16 reference indicating the signature of this method. // Every method is a *i8 reference indicating the signature of this method.
methods := make([]llvm.Value, typ.Underlying().(*types.Interface).NumMethods()) methods := make([]llvm.Value, typ.Underlying().(*types.Interface).NumMethods())
for i := range methods { for i := range methods {
method := typ.Underlying().(*types.Interface).Method(i) method := typ.Underlying().(*types.Interface).Method(i)
methods[i] = c.getMethodSignature(method) methods[i] = c.getMethodSignature(method)
} }
value := llvm.ConstArray(methods[0].Type(), methods) value := llvm.ConstArray(c.i8ptrType, methods)
global = llvm.AddGlobal(c.mod, value.Type(), typ.String()+"$interface") global = llvm.AddGlobal(c.mod, value.Type(), typ.String()+"$interface")
global.SetInitializer(value) global.SetInitializer(value)
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
@@ -295,7 +295,7 @@ func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
} }
// getMethodSignature returns a global variable which is a reference to an // getMethodSignature returns a global variable which is a reference to an
// external *i16 indicating the indicating the signature of this method. It is // external *i8 indicating the indicating the signature of this method. It is
// used during the interface lowering pass. // used during the interface lowering pass.
func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value { func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
signature := ir.MethodSignature(method) signature := ir.MethodSignature(method)
+91
View File
@@ -0,0 +1,91 @@
package compiler
import (
"strconv"
"strings"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// emitInterruptGlobal creates a new runtime/interrupt.Interrupt struct that
// will be lowered to a real interrupt during interrupt lowering.
//
// This two-stage approach allows unused interrupts to be optimized away if
// necessary.
func (c *Compiler) emitInterruptGlobal(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
// Get the interrupt number, which must be a compile-time constant.
id, ok := instr.Args[0].(*ssa.Const)
if !ok {
return llvm.Value{}, c.makeError(instr.Pos(), "interrupt ID is not a constant")
}
// Get the func value, which also must be a compile time constant.
// Note that bound functions are allowed if the function has a pointer
// receiver and is a global. This is rather strict but still allows for
// idiomatic Go code.
funcValue := c.getValue(frame, instr.Args[1])
if funcValue.IsAConstant().IsNil() {
// Try to determine the cause of the non-constantness for a nice error
// message.
switch instr.Args[1].(type) {
case *ssa.MakeClosure:
// This may also be a bound method.
return llvm.Value{}, c.makeError(instr.Pos(), "closures are not supported in interrupt.New")
}
// Fall back to a generic error.
return llvm.Value{}, c.makeError(instr.Pos(), "interrupt function must be constant")
}
// Create a new global of type runtime/interrupt.handle. Globals of this
// type are lowered in the interrupt lowering pass.
globalType := c.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalLLVMType := c.getLLVMType(globalType)
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
if global := c.mod.NamedGlobal(globalName); !global.IsNil() {
return llvm.Value{}, c.makeError(instr.Pos(), "interrupt redeclared in this program")
}
global := llvm.AddGlobal(c.mod, globalLLVMType, globalName)
global.SetLinkage(llvm.PrivateLinkage)
global.SetGlobalConstant(true)
initializer := llvm.ConstNull(globalLLVMType)
initializer = llvm.ConstInsertValue(initializer, funcValue, []uint32{0})
initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(c.intType, uint64(id.Int64()), true), []uint32{1, 0})
global.SetInitializer(initializer)
// Add debug info to the interrupt global.
if c.Debug() {
pos := c.ir.Program.Fset.Position(instr.Pos())
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
LinkageName: globalName,
File: c.getDIFile(pos.Filename),
Line: pos.Line,
Type: c.getDIType(globalType),
Expr: c.dibuilder.CreateExpression(nil),
LocalToUnit: false,
})
global.AddMetadata(0, diglobal)
}
// Create the runtime/interrupt.Interrupt type. It is a struct with a single
// member of type int.
num := llvm.ConstPtrToInt(global, c.intType)
interrupt := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime/interrupt.Interrupt"), []llvm.Value{num})
// Add dummy "use" call for AVR, because interrupts may be used even though
// they are never referenced again. This is unlike Cortex-M or the RISC-V
// PLIC where each interrupt must be enabled using the interrupt number, and
// thus keeps the Interrupt object alive.
// This call is removed during interrupt lowering.
if strings.HasPrefix(c.Triple(), "avr") {
useFn := c.mod.NamedFunction("runtime/interrupt.use")
if useFn.IsNil() {
useFnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false)
useFn = llvm.AddFunction(c.mod, "runtime/interrupt.use", useFnType)
}
c.builder.CreateCall(useFn, []llvm.Value{interrupt}, "")
}
return interrupt, nil
}
+11
View File
@@ -45,6 +45,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []er
// Run some preparatory passes for the Go optimizer. // Run some preparatory passes for the Go optimizer.
goPasses := llvm.NewPassManager() goPasses := llvm.NewPassManager()
defer goPasses.Dispose() defer goPasses.Dispose()
goPasses.AddGlobalDCEPass()
goPasses.AddGlobalOptimizerPass() goPasses.AddGlobalOptimizerPass()
goPasses.AddConstantPropagationPass() goPasses.AddConstantPropagationPass()
goPasses.AddAggressiveDCEPass() goPasses.AddAggressiveDCEPass()
@@ -56,6 +57,12 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []er
transform.OptimizeStringToBytes(c.mod) transform.OptimizeStringToBytes(c.mod)
transform.OptimizeAllocs(c.mod) transform.OptimizeAllocs(c.mod)
transform.LowerInterfaces(c.mod) transform.LowerInterfaces(c.mod)
errs := transform.LowerInterruptRegistrations(c.mod)
if len(errs) > 0 {
return errs
}
if c.funcImplementation() == funcValueSwitch { if c.funcImplementation() == funcValueSwitch {
transform.LowerFuncValues(c.mod) transform.LowerFuncValues(c.mod)
} }
@@ -99,6 +106,10 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []er
if err != nil { if err != nil {
return []error{err} return []error{err}
} }
errs := transform.LowerInterruptRegistrations(c.mod)
if len(errs) > 0 {
return errs
}
} }
if c.VerifyIR() { if c.VerifyIR() {
if errs := c.checkModule(); errs != nil { if errs := c.checkModule(); errs != nil {
+3
View File
@@ -110,6 +110,9 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
if k == types.UnsafePointer { if k == types.UnsafePointer {
return s.PtrSize return s.PtrSize
} }
if k == types.Invalid {
return 0 // only relevant when there is a type error somewhere
}
panic("unknown basic type: " + t.String()) panic("unknown basic type: " + t.String())
case *types.Array: case *types.Array:
n := t.Len() n := t.Len()
+33 -3
View File
@@ -60,14 +60,44 @@ func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value {
info := c.getGlobalInfo(g) info := c.getGlobalInfo(g)
llvmGlobal := c.mod.NamedGlobal(info.linkName) llvmGlobal := c.mod.NamedGlobal(info.linkName)
if llvmGlobal.IsNil() { if llvmGlobal.IsNil() {
llvmType := c.getLLVMType(g.Type().(*types.Pointer).Elem()) typ := g.Type().(*types.Pointer).Elem()
llvmType := c.getLLVMType(typ)
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName) llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
if !info.extern { if !info.extern {
llvmGlobal.SetInitializer(llvm.ConstNull(llvmType)) llvmGlobal.SetInitializer(llvm.ConstNull(llvmType))
llvmGlobal.SetLinkage(llvm.InternalLinkage) llvmGlobal.SetLinkage(llvm.InternalLinkage)
} }
if info.align > c.targetData.ABITypeAlignment(llvmType) {
llvmGlobal.SetAlignment(info.align) // Set alignment from the //go:align comment.
var alignInBits uint32
if info.align < 0 || info.align&(info.align-1) != 0 {
// Check for power-of-two (or 0).
// See: https://stackoverflow.com/a/108360
c.addError(g.Pos(), "global variable alignment must be a positive power of two")
} else {
// Set the alignment only when it is a power of two.
alignInBits = uint32(info.align) ^ uint32(info.align-1)
if info.align > c.targetData.ABITypeAlignment(llvmType) {
llvmGlobal.SetAlignment(info.align)
}
}
if c.Debug() {
// 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())
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
Name: g.RelString(nil),
LinkageName: info.linkName,
File: c.getDIFile(pos.Filename),
Line: pos.Line,
Type: c.getDIType(typ),
LocalToUnit: false,
Expr: c.dibuilder.CreateExpression(nil),
AlignInBits: alignInBits,
})
llvmGlobal.AddMetadata(0, diglobal)
} }
} }
return llvmGlobal return llvmGlobal
+1 -1
View File
@@ -152,7 +152,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS()+"/"+c.GOARCH()) return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS()+"/"+c.GOARCH())
} }
switch c.GOOS() { switch c.GOOS() {
case "linux": case "linux", "freebsd":
// Return values: r0, r1 uintptr, err Errno // Return values: r0, r1 uintptr, err Errno
// Pseudocode: // Pseudocode:
// var err uintptr // var err uintptr
+1 -1
View File
@@ -10,5 +10,5 @@ require (
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45 go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257 tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566
) )
+4
View File
@@ -30,3 +30,7 @@ tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8 h1:9Bfvso+tTVQg16UzOA614
tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257 h1:o8VDylrMN7gWemBMu8rEyuogKPhcLTdx5KrUAp9macc= tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257 h1:o8VDylrMN7gWemBMu8rEyuogKPhcLTdx5KrUAp9macc=
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae h1:s8J5EyxCkHxXB08UI3gk9W9IS/ekizRvSX+PfZxnAB0=
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566 h1:a4y30bTf7U0zDA75v2PTL+XQ2OzJetj19gK8XwQpUNY=
tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
+7 -28
View File
@@ -27,14 +27,13 @@ type Program struct {
// Function or method. // Function or method.
type Function struct { type Function struct {
*ssa.Function *ssa.Function
LLVMFn llvm.Value LLVMFn llvm.Value
module string // go:wasm-module module string // go:wasm-module
linkName string // go:linkname, go:export, go:interrupt linkName string // go:linkname, go:export
exported bool // go:export exported bool // go:export
nobounds bool // go:nobounds nobounds bool // go:nobounds
flag bool // used by dead code elimination flag bool // used by dead code elimination
interrupt bool // go:interrupt inline InlineType // go:inline
inline InlineType // go:inline
} }
// Interface type that is at some point used in a type assert (to check whether // Interface type that is at some point used in a type assert (to check whether
@@ -243,18 +242,6 @@ func (f *Function) parsePragmas() {
f.inline = InlineHint f.inline = InlineHint
case "//go:noinline": case "//go:noinline":
f.inline = InlineNone f.inline = InlineNone
case "//go:interrupt":
if len(parts) != 2 {
continue
}
name := parts[1]
if strings.HasSuffix(name, "_vect") {
// AVR vector naming
name = "__vector_" + name[:len(name)-5]
}
f.linkName = name
f.exported = true
f.interrupt = true
case "//go:linkname": case "//go:linkname":
if len(parts) != 3 || parts[1] != f.Name() { if len(parts) != 3 || parts[1] != f.Name() {
continue continue
@@ -288,14 +275,6 @@ func (f *Function) IsExported() bool {
return f.exported || f.CName() != "" return f.exported || f.CName() != ""
} }
// Return true for functions annotated with //go:interrupt. The function name is
// already customized in LinkName() to hook up in the interrupt vector.
//
// On some platforms (like AVR), interrupts need a special compiler flag.
func (f *Function) IsInterrupt() bool {
return f.interrupt
}
// Return the inline directive of this function. // Return the inline directive of this function.
func (f *Function) Inline() InlineType { func (f *Function) Inline() InlineType {
return f.inline return f.inline
+14 -3
View File
@@ -6,6 +6,7 @@ import (
"go/ast" "go/ast"
"go/build" "go/build"
"go/parser" "go/parser"
"go/scanner"
"go/token" "go/token"
"go/types" "go/types"
"os" "os"
@@ -46,7 +47,7 @@ type Package struct {
// Import loads the given package relative to srcDir (for the vendor directory). // Import loads the given package relative to srcDir (for the vendor directory).
// It only loads the current package without recursion. // It only loads the current package without recursion.
func (p *Program) Import(path, srcDir string) (*Package, error) { func (p *Program) Import(path, srcDir string, pos token.Position) (*Package, error) {
if p.Packages == nil { if p.Packages == nil {
p.Packages = make(map[string]*Package) p.Packages = make(map[string]*Package)
} }
@@ -59,7 +60,10 @@ func (p *Program) Import(path, srcDir string) (*Package, error) {
} }
buildPkg, err := ctx.Import(path, srcDir, build.ImportComment) buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
if err != nil { if err != nil {
return nil, err return nil, scanner.Error{
Pos: pos,
Msg: err.Error(), // TODO: define a new error type that will wrap the inner error
}
} }
if existingPkg, ok := p.Packages[buildPkg.ImportPath]; ok { if existingPkg, ok := p.Packages[buildPkg.ImportPath]; ok {
// Already imported, or at least started the import. // Already imported, or at least started the import.
@@ -467,7 +471,14 @@ func (p *Package) importRecursively(includeTests bool) error {
if _, ok := p.Imports[to]; ok { if _, ok := p.Imports[to]; ok {
continue continue
} }
importedPkg, err := p.Program.Import(to, p.Package.Dir) // 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 != nil {
if err, ok := err.(*ImportCycleError); ok { if err, ok := err.(*ImportCycleError); ok {
err.Packages = append([]string{p.ImportPath}, err.Packages...) err.Packages = append([]string{p.ImportPath}, err.Packages...)
+6 -12
View File
@@ -274,15 +274,7 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
// //
// Note: this command is expected to execute just before exiting, as it // Note: this command is expected to execute just before exiting, as it
// modifies global state. // modifies global state.
func FlashGDB(pkgName, port string, ocdOutput bool, options *compileopts.Options) error { func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) error {
if port == "" {
var err error
port, err = getDefaultPort()
if err != nil {
return err
}
}
config, err := builder.NewConfig(options) config, err := builder.NewConfig(options)
if err != nil { if err != nil {
return err return err
@@ -452,7 +444,7 @@ func flashUF2UsingMSD(volume, tmppath string) error {
// find standard UF2 info path // find standard UF2 info path
var infoPath string var infoPath string
switch runtime.GOOS { switch runtime.GOOS {
case "linux": case "linux", "freebsd":
infoPath = "/media/*/" + volume + "/INFO_UF2.TXT" infoPath = "/media/*/" + volume + "/INFO_UF2.TXT"
case "darwin": case "darwin":
infoPath = "/Volumes/" + volume + "/INFO_UF2.TXT" infoPath = "/Volumes/" + volume + "/INFO_UF2.TXT"
@@ -479,7 +471,7 @@ func flashHexUsingMSD(volume, tmppath string) error {
// find expected volume path // find expected volume path
var destPath string var destPath string
switch runtime.GOOS { switch runtime.GOOS {
case "linux": case "linux", "freebsd":
destPath = "/media/*/" + volume destPath = "/media/*/" + volume
case "darwin": case "darwin":
destPath = "/Volumes/" + volume destPath = "/Volumes/" + volume
@@ -558,6 +550,8 @@ func getDefaultPort() (port string, err error) {
portPath = "/dev/cu.usb*" portPath = "/dev/cu.usb*"
case "linux": case "linux":
portPath = "/dev/ttyACM*" portPath = "/dev/ttyACM*"
case "freebsd":
portPath = "/dev/cuaU*"
case "windows": case "windows":
cmd := exec.Command("wmic", cmd := exec.Command("wmic",
"PATH", "Win32_SerialPort", "WHERE", "Caption LIKE 'USB Serial%'", "GET", "DeviceID") "PATH", "Win32_SerialPort", "WHERE", "Caption LIKE 'USB Serial%'", "GET", "DeviceID")
@@ -767,7 +761,7 @@ func main() {
usage() usage()
os.Exit(1) os.Exit(1)
} }
err := FlashGDB(flag.Arg(0), *port, *ocdOutput, options) err := FlashGDB(flag.Arg(0), *ocdOutput, options)
handleCompilerError(err) handleCompilerError(err)
} }
case "run": case "run":
+28 -3
View File
@@ -14,6 +14,7 @@ import (
"sort" "sort"
"sync" "sync"
"testing" "testing"
"time"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/loader" "github.com/tinygo-org/tinygo/loader"
@@ -157,7 +158,9 @@ func runTest(path, target string, t *testing.T) {
} }
// Run the test. // Run the test.
runComplete := make(chan struct{})
var cmd *exec.Cmd var cmd *exec.Cmd
ranTooLong := false
if target == "" { if target == "" {
cmd = exec.Command(binary) cmd = exec.Command(binary)
} else { } else {
@@ -173,13 +176,35 @@ func runTest(path, target string, t *testing.T) {
} }
stdout := &bytes.Buffer{} stdout := &bytes.Buffer{}
cmd.Stdout = stdout cmd.Stdout = stdout
if target != "" { cmd.Stderr = os.Stderr
cmd.Stderr = os.Stderr err = cmd.Start()
if err != nil {
t.Fatal("failed to start:", err)
} }
err = cmd.Run() go func() {
// Terminate the process if it runs too long.
timer := time.NewTimer(1 * time.Second)
select {
case <-runComplete:
timer.Stop()
case <-timer.C:
ranTooLong = true
if runtime.GOOS == "windows" {
cmd.Process.Signal(os.Kill) // Windows doesn't support SIGINT.
} else {
cmd.Process.Signal(os.Interrupt)
}
}
}()
err = cmd.Wait()
if _, ok := err.(*exec.ExitError); ok && target != "" { if _, ok := err.(*exec.ExitError); ok && target != "" {
err = nil // workaround for QEMU err = nil // workaround for QEMU
} }
close(runComplete)
if ranTooLong {
stdout.WriteString("--- test ran too long, terminating...\n")
}
// putchar() prints CRLF, convert it to LF. // putchar() prints CRLF, convert it to LF.
actual := bytes.Replace(stdout.Bytes(), []byte{'\r', '\n'}, []byte{'\n'}, -1) actual := bytes.Replace(stdout.Bytes(), []byte{'\r', '\n'}, []byte{'\n'}, -1)
+6 -7
View File
@@ -3,11 +3,10 @@
.type _start,@function .type _start,@function
_start: _start:
// Workaround for missing support of the la pseudo-instruction in Clang 8: // Load the stack pointer.
// https://reviews.llvm.org/D55325 la sp, _stack_top
lui sp, %hi(_stack_top) // Load the globals pointer. The program will load pointers relative to this
addi sp, sp, %lo(_stack_top) // register, so it must be set to the right value on startup.
// see https://gnu-mcu-eclipse.github.io/arch/riscv/programmer/#the-gp-global-pointer-register // See: https://gnu-mcu-eclipse.github.io/arch/riscv/programmer/#the-gp-global-pointer-register
lui gp, %hi(__global_pointer$) la gp, __global_pointer$
addi gp, gp, %lo(__global_pointer$)
call main call main
+17 -20
View File
@@ -2,36 +2,33 @@
package machine package machine
import "device/sam" import (
"device/sam"
"runtime/interrupt"
)
// UART1 on the Arduino Nano 33 connects to the onboard NINA-W102 WiFi chip. // UART1 on the Arduino Nano 33 connects to the onboard NINA-W102 WiFi chip.
var ( var (
UART1 = UART{ UART1 = UART{
Buffer: NewRingBuffer(),
Bus: sam.SERCOM5_USART,
SERCOM: 5,
}
)
//go:export SERCOM5_IRQHandler
func handleUART1() {
defaultUART1Handler()
}
// UART2 on the Arduino Nano 33 connects to the normal TX/RX pins.
var (
UART2 = UART{
Buffer: NewRingBuffer(), Buffer: NewRingBuffer(),
Bus: sam.SERCOM3_USART, Bus: sam.SERCOM3_USART,
SERCOM: 3, SERCOM: 3,
} }
) )
//go:export SERCOM3_IRQHandler // UART2 on the Arduino Nano 33 connects to the normal TX/RX pins.
func handleUART2() { var (
// should reset IRQ UART2 = UART{
UART2.Receive(byte((UART2.Bus.DATA.Get() & 0xFF))) Buffer: NewRingBuffer(),
UART2.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC) Bus: sam.SERCOM5_USART,
SERCOM: 5,
}
)
func init() {
// Work around circular definitions.
UART1.interrupt = interrupt.New(sam.IRQ_SERCOM3, UART1.handleInterrupt)
UART2.interrupt = interrupt.New(sam.IRQ_SERCOM5, UART2.handleInterrupt)
} }
// I2C on the Arduino Nano 33. // I2C on the Arduino Nano 33.
+6 -5
View File
@@ -2,7 +2,10 @@
package machine package machine
import "device/stm32" import (
"device/stm32"
"runtime/interrupt"
)
// https://wiki.stm32duino.com/index.php?title=File:Bluepillpinout.gif // https://wiki.stm32duino.com/index.php?title=File:Bluepillpinout.gif
const ( const (
@@ -61,14 +64,12 @@ var (
UART0 = UART{ UART0 = UART{
Buffer: NewRingBuffer(), Buffer: NewRingBuffer(),
Bus: stm32.USART1, Bus: stm32.USART1,
IRQVal: stm32.IRQ_USART1,
} }
UART1 = &UART0 UART1 = &UART0
) )
//go:export USART1_IRQHandler func init() {
func handleUART1() { UART0.interrupt = interrupt.New(stm32.IRQ_USART1, UART0.handleInterrupt)
UART1.Receive(byte((UART1.Bus.DR.Get() & 0xFF)))
} }
// SPI pins // SPI pins
@@ -2,7 +2,10 @@
package machine package machine
import "device/sam" import (
"device/sam"
"runtime/interrupt"
)
// UART1 on the Circuit Playground Express. // UART1 on the Circuit Playground Express.
var ( var (
@@ -13,9 +16,8 @@ var (
} }
) )
//go:export SERCOM1_IRQHandler func init() {
func handleUART1() { UART1.interrupt = interrupt.New(sam.IRQ_SERCOM4, UART1.handleInterrupt)
defaultUART1Handler()
} }
// I2C on the Circuit Playground Express. // I2C on the Circuit Playground Express.
+6 -4
View File
@@ -2,7 +2,10 @@
package machine package machine
import "device/sam" import (
"device/sam"
"runtime/interrupt"
)
// used to reset into bootloader // used to reset into bootloader
const RESET_MAGIC_VALUE = 0xf01669ef const RESET_MAGIC_VALUE = 0xf01669ef
@@ -60,9 +63,8 @@ var (
} }
) )
//go:export SERCOM1_IRQHandler func init() {
func handleUART1() { UART1.interrupt = interrupt.New(sam.IRQ_SERCOM1, UART1.handleInterrupt)
defaultUART1Handler()
} }
// I2C pins // I2C pins
+3 -3
View File
@@ -4,6 +4,7 @@ package machine
import ( import (
"device/sam" "device/sam"
"runtime/interrupt"
) )
// used to reset into bootloader // used to reset into bootloader
@@ -62,9 +63,8 @@ var (
} }
) )
//go:export SERCOM1_IRQHandler func init() {
func handleUART1() { UART1.interrupt = interrupt.New(sam.IRQ_SERCOM1, UART1.handleInterrupt)
defaultUART1Handler()
} }
// I2C pins // I2C pins
+6 -5
View File
@@ -2,7 +2,10 @@
package machine package machine
import "device/stm32" import (
"device/stm32"
"runtime/interrupt"
)
const ( const (
PA0 = portA + 0 PA0 = portA + 0
@@ -100,14 +103,12 @@ var (
UART0 = UART{ UART0 = UART{
Buffer: NewRingBuffer(), Buffer: NewRingBuffer(),
Bus: stm32.USART2, Bus: stm32.USART2,
IRQVal: stm32.IRQ_USART2,
} }
UART2 = &UART0 UART2 = &UART0
) )
//go:export USART2_IRQHandler func init() {
func handleUART2() { UART0.interrupt = interrupt.New(stm32.IRQ_USART2, UART0.handleInterrupt)
UART2.Receive(byte((UART2.Bus.DR.Get() & 0xFF)))
} }
// SPI pins // SPI pins
+6 -4
View File
@@ -2,7 +2,10 @@
package machine package machine
import "device/sam" import (
"device/sam"
"runtime/interrupt"
)
// used to reset into bootloader // used to reset into bootloader
const RESET_MAGIC_VALUE = 0xf01669ef const RESET_MAGIC_VALUE = 0xf01669ef
@@ -51,9 +54,8 @@ var (
} }
) )
//go:export SERCOM1_IRQHandler func init() {
func handleUART1() { UART1.interrupt = interrupt.New(sam.IRQ_SERCOM0, UART1.handleInterrupt)
defaultUART1Handler()
} }
// SPI pins // SPI pins
+13 -12
View File
@@ -4,6 +4,7 @@ package machine
import ( import (
"device/avr" "device/avr"
"runtime/interrupt"
"runtime/volatile" "runtime/volatile"
) )
@@ -232,6 +233,18 @@ func (uart UART) Configure(config UARTConfig) {
config.BaudRate = 9600 config.BaudRate = 9600
} }
// Register the UART interrupt.
interrupt.New(avr.IRQ_USART_RX, func(intr interrupt.Interrupt) {
// Read register to clear it.
data := avr.UDR0.Get()
// Ensure no error.
if !avr.UCSR0A.HasBits(avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0) {
// Put data from UDR register into buffer.
UART0.Receive(byte(data))
}
})
// Set baud rate based on prescale formula from // Set baud rate based on prescale formula from
// https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html // https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html
// ((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1) // ((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1)
@@ -254,15 +267,3 @@ func (uart UART) WriteByte(c byte) error {
avr.UDR0.Set(c) // send char avr.UDR0.Set(c) // send char
return nil return nil
} }
//go:interrupt USART_RX_vect
func handleUSART_RX() {
// Read register to clear it.
data := avr.UDR0.Get()
// Ensure no error.
if !avr.UCSR0A.HasBits(avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0) {
// Put data from UDR register into buffer.
UART0.Receive(byte(data))
}
}
+14 -14
View File
@@ -11,6 +11,7 @@ import (
"device/arm" "device/arm"
"device/sam" "device/sam"
"errors" "errors"
"runtime/interrupt"
"unsafe" "unsafe"
) )
@@ -292,9 +293,10 @@ func waitADCSync() {
// UART on the SAMD21. // UART on the SAMD21.
type UART struct { type UART struct {
Buffer *RingBuffer Buffer *RingBuffer
Bus *sam.SERCOM_USART_Type Bus *sam.SERCOM_USART_Type
SERCOM uint8 SERCOM uint8
interrupt interrupt.Interrupt
} }
var ( var (
@@ -401,10 +403,7 @@ func (uart UART) Configure(config UARTConfig) error {
uart.Bus.INTENSET.Set(sam.SERCOM_USART_INTENSET_RXC) uart.Bus.INTENSET.Set(sam.SERCOM_USART_INTENSET_RXC)
// Enable RX IRQ. // Enable RX IRQ.
// IRQ lines are in the same order as SERCOM instance numbers on SAMD21 uart.interrupt.Enable()
// chips, so the IRQ number can be trivially determined from the SERCOM
// number.
arm.EnableIRQ(sam.IRQ_SERCOM0 + uint32(uart.SERCOM))
return nil return nil
} }
@@ -432,11 +431,12 @@ func (uart UART) WriteByte(c byte) error {
return nil return nil
} }
// defaultUART1Handler handles the UART1 IRQ. // handleInterrupt should be called from the appropriate interrupt handler for
func defaultUART1Handler() { // this UART instance.
func (uart *UART) handleInterrupt(interrupt.Interrupt) {
// should reset IRQ // should reset IRQ
UART1.Receive(byte((UART1.Bus.DATA.Get() & 0xFF))) uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF)))
UART1.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC) uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC)
} }
// I2C on the SAMD21. // I2C on the SAMD21.
@@ -1368,7 +1368,8 @@ func (usbcdc USBCDC) Configure(config UARTConfig) {
sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_ENABLE) sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_ENABLE)
// enable IRQ // enable IRQ
arm.EnableIRQ(sam.IRQ_USB) intr := interrupt.New(sam.IRQ_USB, handleUSB)
intr.Enable()
} }
func handlePadCalibration() { func handlePadCalibration() {
@@ -1414,8 +1415,7 @@ func handlePadCalibration() {
sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos) sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos)
} }
//go:export USB_IRQHandler func handleUSB(intr interrupt.Interrupt) {
func handleUSB() {
// reset all interrupt flags // reset all interrupt flags
flags := sam.USB_DEVICE.INTFLAG.Get() flags := sam.USB_DEVICE.INTFLAG.Get()
sam.USB_DEVICE.INTFLAG.Set(flags) sam.USB_DEVICE.INTFLAG.Set(flags)
+7 -26
View File
@@ -11,6 +11,7 @@ import (
"device/arm" "device/arm"
"device/sam" "device/sam"
"errors" "errors"
"runtime/interrupt"
"unsafe" "unsafe"
) )
@@ -1402,11 +1403,11 @@ func (usbcdc USBCDC) Configure(config UARTConfig) {
// enable USB // enable USB
sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_ENABLE) sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_ENABLE)
// enable IRQ // enable IRQ at highest priority
arm.EnableIRQ(sam.IRQ_USB_OTHER) interrupt.New(sam.IRQ_USB_OTHER, handleUSBIRQ).Enable()
arm.EnableIRQ(sam.IRQ_USB_SOF_HSOF) interrupt.New(sam.IRQ_USB_SOF_HSOF, handleUSBIRQ).Enable()
arm.EnableIRQ(sam.IRQ_USB_TRCPT0) interrupt.New(sam.IRQ_USB_TRCPT0, handleUSBIRQ).Enable()
arm.EnableIRQ(sam.IRQ_USB_TRCPT1) interrupt.New(sam.IRQ_USB_TRCPT1, handleUSBIRQ).Enable()
} }
func handlePadCalibration() { func handlePadCalibration() {
@@ -1452,27 +1453,7 @@ func handlePadCalibration() {
sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos) sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos)
} }
//go:export USB_OTHER_IRQHandler func handleUSBIRQ(interrupt.Interrupt) {
func handleUSBOther() {
handleUSBIRQ()
}
//go:export USB_SOF_HSOF_IRQHandler
func handleUSBSOFHSOF() {
handleUSBIRQ()
}
//go:export USB_TRCPT0_IRQHandler
func handleUSBTRCPT0() {
handleUSBIRQ()
}
//go:export USB_TRCPT1_IRQHandler
func handleUSBTRCPT1() {
handleUSBIRQ()
}
func handleUSBIRQ() {
// reset all interrupt flags // reset all interrupt flags
flags := sam.USB_DEVICE.INTFLAG.Get() flags := sam.USB_DEVICE.INTFLAG.Get()
sam.USB_DEVICE.INTFLAG.Set(flags) sam.USB_DEVICE.INTFLAG.Set(flags)
+6 -3
View File
@@ -3,9 +3,9 @@
package machine package machine
import ( import (
"device/arm"
"device/nrf" "device/nrf"
"errors" "errors"
"runtime/interrupt"
) )
var ( var (
@@ -88,8 +88,11 @@ func (uart UART) Configure(config UARTConfig) {
nrf.UART0.INTENSET.Set(nrf.UART_INTENSET_RXDRDY_Msk) nrf.UART0.INTENSET.Set(nrf.UART_INTENSET_RXDRDY_Msk)
// Enable RX IRQ. // Enable RX IRQ.
arm.SetPriority(nrf.IRQ_UART0, 0xc0) // low priority intr := interrupt.New(nrf.IRQ_UART0, func(intr interrupt.Interrupt) {
arm.EnableIRQ(nrf.IRQ_UART0) UART0.handleInterrupt()
})
intr.SetPriority(0xc0) // low priority
intr.Enable()
} }
// SetBaudRate sets the communication speed for the UART. // SetBaudRate sets the communication speed for the UART.
-5
View File
@@ -20,11 +20,6 @@ func (uart UART) setPins(tx, rx Pin) {
nrf.UART0.PSELRXD.Set(uint32(rx)) nrf.UART0.PSELRXD.Set(uint32(rx))
} }
//go:export UART0_IRQHandler
func handleUART0() {
UART0.handleInterrupt()
}
func (i2c I2C) setPins(scl, sda Pin) { func (i2c I2C) setPins(scl, sda Pin) {
i2c.Bus.PSELSCL.Set(uint32(scl)) i2c.Bus.PSELSCL.Set(uint32(scl))
i2c.Bus.PSELSDA.Set(uint32(sda)) i2c.Bus.PSELSDA.Set(uint32(sda))
-5
View File
@@ -21,11 +21,6 @@ func (uart UART) setPins(tx, rx Pin) {
nrf.UART0.PSELRXD.Set(uint32(rx)) nrf.UART0.PSELRXD.Set(uint32(rx))
} }
//go:export UARTE0_UART0_IRQHandler
func handleUART0() {
UART0.handleInterrupt()
}
func (i2c I2C) setPins(scl, sda Pin) { func (i2c I2C) setPins(scl, sda Pin) {
i2c.Bus.PSELSCL.Set(uint32(scl)) i2c.Bus.PSELSCL.Set(uint32(scl))
i2c.Bus.PSELSDA.Set(uint32(sda)) i2c.Bus.PSELSDA.Set(uint32(sda))
-5
View File
@@ -25,11 +25,6 @@ func (uart UART) setPins(tx, rx Pin) {
nrf.UART0.PSEL.RXD.Set(uint32(rx)) nrf.UART0.PSEL.RXD.Set(uint32(rx))
} }
//go:export UARTE0_UART0_IRQHandler
func handleUART0() {
UART0.handleInterrupt()
}
func (i2c I2C) setPins(scl, sda Pin) { func (i2c I2C) setPins(scl, sda Pin) {
i2c.Bus.PSEL.SCL.Set(uint32(scl)) i2c.Bus.PSEL.SCL.Set(uint32(scl))
i2c.Bus.PSEL.SDA.Set(uint32(sda)) i2c.Bus.PSEL.SDA.Set(uint32(sda))
+12 -6
View File
@@ -5,9 +5,9 @@ package machine
// Peripheral abstraction layer for the stm32. // Peripheral abstraction layer for the stm32.
import ( import (
"device/arm"
"device/stm32" "device/stm32"
"errors" "errors"
"runtime/interrupt"
) )
func CPUFrequency() uint32 { func CPUFrequency() uint32 {
@@ -111,9 +111,9 @@ func (p Pin) Get() bool {
// UART // UART
type UART struct { type UART struct {
Buffer *RingBuffer Buffer *RingBuffer
Bus *stm32.USART_Type Bus *stm32.USART_Type
IRQVal uint32 interrupt interrupt.Interrupt
} }
// Configure the UART. // Configure the UART.
@@ -155,8 +155,8 @@ func (uart UART) Configure(config UARTConfig) {
uart.Bus.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE) uart.Bus.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE)
// Enable RX IRQ // Enable RX IRQ
arm.SetPriority(uart.IRQVal, 0xc0) uart.interrupt.SetPriority(0xc0)
arm.EnableIRQ(uart.IRQVal) uart.interrupt.Enable()
} }
// SetBaudRate sets the communication speed for the UART. // SetBaudRate sets the communication speed for the UART.
@@ -182,6 +182,12 @@ func (uart UART) WriteByte(c byte) error {
return nil return nil
} }
// handleInterrupt should be called from the appropriate interrupt handler for
// this UART instance.
func (uart *UART) handleInterrupt(interrupt.Interrupt) {
uart.Receive(byte((uart.Bus.DR.Get() & 0xFF)))
}
// SPI on the STM32. // SPI on the STM32.
type SPI struct { type SPI struct {
Bus *stm32.SPI_Type Bus *stm32.SPI_Type
+6 -8
View File
@@ -5,8 +5,8 @@ package machine
// Peripheral abstraction layer for the stm32. // Peripheral abstraction layer for the stm32.
import ( import (
"device/arm"
"device/stm32" "device/stm32"
"runtime/interrupt"
) )
func CPUFrequency() uint32 { func CPUFrequency() uint32 {
@@ -203,8 +203,11 @@ func (uart UART) Configure(config UARTConfig) {
stm32.USART2.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE) stm32.USART2.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE)
// Enable RX IRQ. // Enable RX IRQ.
arm.SetPriority(stm32.IRQ_USART2, 0xc0) intr := interrupt.New(stm32.IRQ_USART2, func(interrupt.Interrupt) {
arm.EnableIRQ(stm32.IRQ_USART2) UART1.Receive(byte((stm32.USART2.DR.Get() & 0xFF)))
})
intr.SetPriority(0xc0)
intr.Enable()
} }
// WriteByte writes a byte of data to the UART. // WriteByte writes a byte of data to the UART.
@@ -215,8 +218,3 @@ func (uart UART) WriteByte(c byte) error {
} }
return nil return nil
} }
//go:export USART2_IRQHandler
func handleUSART2() {
UART1.Receive(byte((stm32.USART2.DR.Get() & 0xFF)))
}
+1 -1
View File
@@ -1,4 +1,4 @@
// +build darwin linux,!baremetal // +build darwin linux,!baremetal freebsd,!baremetal
package os package os
+14
View File
@@ -0,0 +1,14 @@
// Package interrupt provides access to hardware interrupts. It provides a way
// to define interrupts and to enable/disable them.
package interrupt
// Interrupt provides direct access to hardware interrupts. You can configure
// this interrupt through this interface.
//
// Do not use the zero value of an Interrupt object. Instead, call New to obtain
// an interrupt handle.
type Interrupt struct {
// Make this number unexported so it cannot be set directly. This provides
// some encapsulation.
num int
}
@@ -0,0 +1,23 @@
// +build cortexm
package interrupt
import (
"device/arm"
)
// Enable enables this interrupt. Right after calling this function, the
// interrupt may be invoked if it was already pending.
func (irq Interrupt) Enable() {
arm.EnableIRQ(uint32(irq.num))
}
// SetPriority sets the interrupt priority for this interrupt. A lower number
// means a higher priority. Additionally, most hardware doesn't implement all
// priority bits (only the uppoer bits).
//
// Examples: 0xff (lowest priority), 0xc0 (low priority), 0x00 (highest possible
// priority).
func (irq Interrupt) SetPriority(priority uint8) {
arm.SetPriority(uint32(irq.num), uint32(priority))
}
@@ -0,0 +1,59 @@
// +build gameboyadvance
package interrupt
import (
"runtime/volatile"
"unsafe"
)
var handlers = [14]func(Interrupt){}
const (
IRQ_VBLANK = 0
IRQ_HBLANK = 1
IRQ_VCOUNT = 2
IRQ_TIMER0 = 3
IRQ_TIMER1 = 4
IRQ_TIMER2 = 5
IRQ_TIMER3 = 6
IRQ_COM = 7
IRQ_DMA0 = 8
IRQ_DMA1 = 9
IRQ_DMA2 = 10
IRQ_DMA3 = 11
IRQ_KEYPAD = 12
IRQ_GAMEPAK = 13
)
var (
regInterruptEnable = (*volatile.Register16)(unsafe.Pointer(uintptr(0x4000200)))
regInterruptRequestFlags = (*volatile.Register16)(unsafe.Pointer(uintptr(0x4000202)))
regInterruptMasterEnable = (*volatile.Register16)(unsafe.Pointer(uintptr(0x4000208)))
)
// New creates a new Interrupt object. Do not call it multiple times. If you do,
// make sure the interrupt is disabled while you do so. The last call will set
// the active interrupt handler.
func New(id int, handler func(Interrupt)) Interrupt {
handlers[id] = handler
return Interrupt{id}
}
// Enable enables this interrupt. Right after calling this function, the
// interrupt may be invoked if it was already pending.
func (irq Interrupt) Enable() {
regInterruptEnable.SetBits(1 << irq.num)
}
//export handleInterrupt
func handleInterrupt() {
flags := regInterruptRequestFlags.Get()
for i := range handlers {
if flags & (1 << i) != 0 {
irq := Interrupt{i}
regInterruptRequestFlags.Set(1 << i) // acknowledge interrupt
handlers[i](irq)
}
}
}
@@ -0,0 +1,19 @@
// +build avr riscv,baremetal cortexm
package interrupt
// New is a compiler intrinsic that creates a new Interrupt object. You may call
// it only once, and must pass constant parameters to it. That means that the
// interrupt ID must be a Go constant and that the handler must be a simple
// function: closures are not supported.
func New(id int, handler func(Interrupt)) Interrupt
// Register is used to declare an interrupt. You should not normally call this
// function: it is only for telling the compiler about the mapping between an
// interrupt number and the interrupt handler name.
func Register(id int, handlerName string) int
type handle struct {
handler func(Interrupt)
Interrupt
}
+5
View File
@@ -0,0 +1,5 @@
// +build freebsd
package runtime
const GOOS = "freebsd"
+1
View File
@@ -4,6 +4,7 @@ package runtime
import ( import (
"unsafe" "unsafe"
_ "runtime/interrupt" // make sure the interrupt handler is defined
) )
type timeUnit int64 type timeUnit int64
+9 -10
View File
@@ -6,6 +6,7 @@ import (
"device/arm" "device/arm"
"device/sam" "device/sam"
"machine" "machine"
"runtime/interrupt"
"runtime/volatile" "runtime/volatile"
"unsafe" "unsafe"
) )
@@ -214,8 +215,14 @@ func initRTC() {
sam.RTC_MODE0.CTRL.SetBits(sam.RTC_MODE0_CTRL_ENABLE) sam.RTC_MODE0.CTRL.SetBits(sam.RTC_MODE0_CTRL_ENABLE)
waitForSync() waitForSync()
arm.SetPriority(sam.IRQ_RTC, 0xc0) intr := interrupt.New(sam.IRQ_RTC, func(intr interrupt.Interrupt) {
arm.EnableIRQ(sam.IRQ_RTC) // disable IRQ for CMP0 compare
sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0)
timerWakeup.Set(1)
})
intr.SetPriority(0xc0)
intr.Enable()
} }
func waitForSync() { func waitForSync() {
@@ -286,14 +293,6 @@ func timerSleep(ticks uint32) {
} }
} }
//go:export RTC_IRQHandler
func handleRTC() {
// disable IRQ for CMP0 compare
sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0)
timerWakeup.Set(1)
}
func initUSBClock() { func initUSBClock() {
// Turn on clock for USB // Turn on clock for USB
sam.PM.APBBMASK.SetBits(sam.PM_APBBMASK_USB_) sam.PM.APBBMASK.SetBits(sam.PM_APBBMASK_USB_)
-23
View File
@@ -81,27 +81,4 @@ func putchar(c byte) {
machine.UART0.WriteByte(c) machine.UART0.WriteByte(c)
} }
func ticks() timeUnit {
// Combining the low bits and the high bits yields a time span of over 270
// years without counter rollover.
highBits := sifive.RTC.RTCHI.Get()
for {
lowBits := sifive.RTC.RTCLO.Get()
newHighBits := sifive.RTC.RTCHI.Get()
if newHighBits == highBits {
// High bits stayed the same.
return timeUnit(lowBits) | (timeUnit(highBits) << 32)
}
// Retry, because there was a rollover in the low bits (happening every
// 1.5 days).
highBits = newHighBits
}
}
const asyncScheduler = false const asyncScheduler = false
func sleepTicks(d timeUnit) {
target := ticks() + d
for ticks() < target {
}
}
+25
View File
@@ -4,6 +4,7 @@ package runtime
import ( import (
"device/riscv" "device/riscv"
"device/sifive"
) )
func abort() { func abort() {
@@ -12,3 +13,27 @@ func abort() {
riscv.Asm("wfi") riscv.Asm("wfi")
} }
} }
func ticks() timeUnit {
// Combining the low bits and the high bits yields a time span of over 270
// years without counter rollover.
highBits := sifive.RTC.RTCHI.Get()
for {
lowBits := sifive.RTC.RTCLO.Get()
newHighBits := sifive.RTC.RTCHI.Get()
if newHighBits == highBits {
// High bits stayed the same.
println("bits:", highBits, lowBits)
return timeUnit(lowBits) | (timeUnit(highBits) << 32)
}
// Retry, because there was a rollover in the low bits (happening every
// 1.5 days).
highBits = newHighBits
}
}
func sleepTicks(d timeUnit) {
target := ticks() + d
for ticks() < target {
}
}
+12
View File
@@ -10,7 +10,19 @@ import (
// Special memory-mapped device to exit tests, created by SiFive. // Special memory-mapped device to exit tests, created by SiFive.
var testExit = (*volatile.Register32)(unsafe.Pointer(uintptr(0x100000))) var testExit = (*volatile.Register32)(unsafe.Pointer(uintptr(0x100000)))
var timestamp timeUnit
func abort() { func abort() {
// Signal a successful exit. // Signal a successful exit.
testExit.Set(0x5555) testExit.Set(0x5555)
} }
func ticks() timeUnit {
return timestamp
}
func sleepTicks(d timeUnit) {
// Note: QEMU doesn't seem to support the RTC peripheral at the time of
// writing so just simulate sleeping here.
timestamp += d
}
+8 -9
View File
@@ -6,6 +6,7 @@ import (
"device/arm" "device/arm"
"device/nrf" "device/nrf"
"machine" "machine"
"runtime/interrupt"
"runtime/volatile" "runtime/volatile"
) )
@@ -43,8 +44,13 @@ func initLFCLK() {
func initRTC() { func initRTC() {
nrf.RTC1.TASKS_START.Set(1) nrf.RTC1.TASKS_START.Set(1)
arm.SetPriority(nrf.IRQ_RTC1, 0xc0) // low priority intr := interrupt.New(nrf.IRQ_RTC1, func(intr interrupt.Interrupt) {
arm.EnableIRQ(nrf.IRQ_RTC1) nrf.RTC1.INTENCLR.Set(nrf.RTC_INTENSET_COMPARE0)
nrf.RTC1.EVENTS_COMPARE[0].Set(0)
rtc_wakeup.Set(1)
})
intr.SetPriority(0xc0) // low priority
intr.Enable()
} }
func putchar(c byte) { func putchar(c byte) {
@@ -96,10 +102,3 @@ func rtc_sleep(ticks uint32) {
arm.Asm("wfi") arm.Asm("wfi")
} }
} }
//go:export RTC1_IRQHandler
func handleRTC1() {
nrf.RTC1.INTENCLR.Set(nrf.RTC_INTENSET_COMPARE0)
nrf.RTC1.EVENTS_COMPARE[0].Set(0)
rtc_wakeup.Set(1)
}
+5 -4
View File
@@ -6,6 +6,7 @@ import (
"device/arm" "device/arm"
"device/stm32" "device/stm32"
"machine" "machine"
"runtime/interrupt"
"runtime/volatile" "runtime/volatile"
) )
@@ -101,8 +102,9 @@ func initRTC() {
func initTIM() { func initTIM() {
stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN)
arm.SetPriority(stm32.IRQ_TIM3, 0xc3) intr := interrupt.New(stm32.IRQ_TIM3, handleTIM3)
arm.EnableIRQ(stm32.IRQ_TIM3) intr.SetPriority(0xc3)
intr.Enable()
} }
const asyncScheduler = false const asyncScheduler = false
@@ -186,8 +188,7 @@ func timerSleep(ticks uint32) {
} }
} }
//go:export TIM3_IRQHandler func handleTIM3(interrupt.Interrupt) {
func handleTIM3() {
if stm32.TIM3.SR.HasBits(stm32.TIM_SR_UIF) { if stm32.TIM3.SR.HasBits(stm32.TIM_SR_UIF) {
// Disable the timer. // Disable the timer.
stm32.TIM3.CR1.ClearBits(stm32.TIM_CR1_CEN) stm32.TIM3.CR1.ClearBits(stm32.TIM_CR1_CEN)
+9 -8
View File
@@ -6,6 +6,7 @@ import (
"device/arm" "device/arm"
"device/stm32" "device/stm32"
"machine" "machine"
"runtime/interrupt"
"runtime/volatile" "runtime/volatile"
) )
@@ -121,8 +122,9 @@ var timerWakeup volatile.Register8
func initTIM3() { func initTIM3() {
stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN)
arm.SetPriority(stm32.IRQ_TIM3, 0xc3) intr := interrupt.New(stm32.IRQ_TIM3, handleTIM3)
arm.EnableIRQ(stm32.IRQ_TIM3) intr.SetPriority(0xc3)
intr.Enable()
} }
// Enable the TIM7 clock.(tick count) // Enable the TIM7 clock.(tick count)
@@ -139,8 +141,9 @@ func initTIM7() {
// Enable the timer. // Enable the timer.
stm32.TIM7.CR1.SetBits(stm32.TIM_CR1_CEN) stm32.TIM7.CR1.SetBits(stm32.TIM_CR1_CEN)
arm.SetPriority(stm32.IRQ_TIM7, 0xc1) intr := interrupt.New(stm32.IRQ_TIM7, handleTIM7)
arm.EnableIRQ(stm32.IRQ_TIM7) intr.SetPriority(0xc1)
intr.Enable()
} }
const asyncScheduler = false const asyncScheduler = false
@@ -183,8 +186,7 @@ func timerSleep(ticks uint32) {
} }
} }
//go:export TIM3_IRQHandler func handleTIM3(interrupt.Interrupt) {
func handleTIM3() {
if stm32.TIM3.SR.HasBits(stm32.TIM_SR_UIF) { if stm32.TIM3.SR.HasBits(stm32.TIM_SR_UIF) {
// Disable the timer. // Disable the timer.
stm32.TIM3.CR1.ClearBits(stm32.TIM_CR1_CEN) stm32.TIM3.CR1.ClearBits(stm32.TIM_CR1_CEN)
@@ -197,8 +199,7 @@ func handleTIM3() {
} }
} }
//go:export TIM7_IRQHandler func handleTIM7(interrupt.Interrupt) {
func handleTIM7() {
if stm32.TIM7.SR.HasBits(stm32.TIM_SR_UIF) { if stm32.TIM7.SR.HasBits(stm32.TIM_SR_UIF) {
// clear the update flag // clear the update flag
stm32.TIM7.SR.ClearBits(stm32.TIM_SR_UIF) stm32.TIM7.SR.ClearBits(stm32.TIM_SR_UIF)
+1 -1
View File
@@ -1,4 +1,4 @@
// +build darwin linux,!baremetal // +build darwin linux,!baremetal freebsd,!baremetal
package runtime package runtime
+14
View File
@@ -76,3 +76,17 @@ func memset(ptr unsafe.Pointer, c byte, size uintptr) unsafe.Pointer {
} }
return ptr return ptr
} }
// Implement memmove for LLVM and compiler-rt.
//go:export memmove
func libc_memmove(dst, src unsafe.Pointer, size uintptr) unsafe.Pointer {
memmove(dst, src, size)
return dst
}
// Implement memcpy for LLVM and compiler-rt.
//go:export memcpy
func libc_memcpy(dst, src unsafe.Pointer, size uintptr) unsafe.Pointer {
memcpy(dst, src, size)
return dst
}
+12 -9
View File
@@ -1,16 +1,17 @@
OUTPUT_ARCH(arm) OUTPUT_ARCH(arm)
ENTRY(_start) ENTRY(_start)
/* Note: iwram is reduced by 96 bytes because the last part of that RAM
* (starting at 0x03007FA0) is used for interrupt handling.
*/
MEMORY { MEMORY {
ewram : ORIGIN = 0x02000000, LENGTH = 256K /* on-board work RAM (2 wait states) */ ewram : ORIGIN = 0x02000000, LENGTH = 256K /* on-board work RAM (2 wait states) */
iwram : ORIGIN = 0x03000000, LENGTH = 32K /* in-chip work RAM (faster) */ iwram : ORIGIN = 0x03000000, LENGTH = 32K-96 /* in-chip work RAM (faster) */
rom : ORIGIN = 0x08000000, LENGTH = 32M /* flash ROM */ rom : ORIGIN = 0x08000000, LENGTH = 32M /* flash ROM */
} }
__iwram_top = ORIGIN(iwram) + LENGTH(iwram);; __stack_size_irq = 1K;
_stack_size = 3K; __stack_size_usr = 2K;
__sp_irq = _stack_top;
__sp_usr = _stack_top - 1K;
SECTIONS SECTIONS
{ {
@@ -35,8 +36,10 @@ SECTIONS
.stack (NOLOAD) : .stack (NOLOAD) :
{ {
. = ALIGN(4); . = ALIGN(4);
. += _stack_size; __sp_irq = .;
_stack_top = .; . += __stack_size_irq;
__sp_usr = .;
. += __stack_size_usr;
} >iwram } >iwram
/* Start address (in flash) of .data, used by startup code. */ /* Start address (in flash) of .data, used by startup code. */
+15 -3
View File
@@ -17,9 +17,7 @@ _start:
.byte 0x00,0x00 // Checksum (80000BEh) .byte 0x00,0x00 // Checksum (80000BEh)
start_vector: start_vector:
mov r0, #0x4000000 // REG_BASE // Configure stacks
str r0, [r0, #0x208]
mov r0, #0x12 // Switch to IRQ Mode mov r0, #0x12 // Switch to IRQ Mode
msr cpsr, r0 msr cpsr, r0
ldr sp, =__sp_irq // Set IRQ stack ldr sp, =__sp_irq // Set IRQ stack
@@ -27,7 +25,21 @@ start_vector:
msr cpsr, r0 msr cpsr, r0
ldr sp, =__sp_usr // Set user stack ldr sp, =__sp_usr // Set user stack
// Configure interrupt handler
mov r0, #0x4000000 // REG_BASE
ldr r1, =handleInterruptARM
str r1, [r0, #-4] // actually storing to 0x03007FFC due to mirroring
// Enable interrupts
mov r1, #1
str r1, [r0, #0x208] // 0x04000208 Interrupt Master Enable
// Jump to user code (switching to Thumb mode) // Jump to user code (switching to Thumb mode)
ldr r3, =main ldr r3, =main
bx r3 bx r3
// Small interrupt handler that immediately jumps to a function defined in the
// program (in Thumb) for further processing.
handleInterruptARM:
ldr r0, =handleInterrupt
bx r0
+9
View File
@@ -41,6 +41,9 @@ func main() {
// deferred functions // deferred functions
testDefer() testDefer()
// defers in loop
testDeferLoop()
// Take a bound method and use it as a function pointer. // Take a bound method and use it as a function pointer.
// This function pointer needs a context pointer. // This function pointer needs a context pointer.
testBound(thing.String) testBound(thing.String)
@@ -85,6 +88,12 @@ func testDefer() {
println("deferring...") println("deferring...")
} }
func testDeferLoop() {
for j := 0; j < 4; j++ {
defer deferred("loop", j)
}
}
func deferred(msg string, i int) { func deferred(msg string, i int) {
println(msg, i) println(msg, i)
} }
+4
View File
@@ -4,6 +4,10 @@ Thing.Print: foo arg: bar
...run as defer 3 ...run as defer 3
...run closure deferred: 4 ...run closure deferred: 4
...run as defer 1 ...run as defer 1
loop 3
loop 2
loop 1
loop 0
bound method: foo bound method: foo
thing inside closure: foo thing inside closure: foo
inside fp closure: foo 3 inside fp closure: foo 3
+6
View File
@@ -33,6 +33,10 @@ func main() {
// https://github.com/tinygo-org/tinygo/issues/309 // https://github.com/tinygo-org/tinygo/issues/309
itf = linkedList{} itf = linkedList{}
// Test bugfix for assertion on named empty interface:
// https://github.com/tinygo-org/tinygo/issues/453
_, _ = itf.(Empty)
var n int var n int
var f float32 var f float32
var interfaceEqualTests = []struct { var interfaceEqualTests = []struct {
@@ -251,3 +255,5 @@ func (s SleepBlocker) Sleep() {
type StaticBlocker interface { type StaticBlocker interface {
Sleep() Sleep()
} }
type Empty interface{}
+7
View File
@@ -260,6 +260,7 @@ func writeGo(outdir string, device *Device) error {
package {{.pkgName}} package {{.pkgName}}
import ( import (
"runtime/interrupt"
"runtime/volatile" "runtime/volatile"
"unsafe" "unsafe"
) )
@@ -277,6 +278,12 @@ const ({{range .interrupts}}
IRQ_max = {{.interruptMax}} // Highest interrupt number on this device. IRQ_max = {{.interruptMax}} // Highest interrupt number on this device.
) )
// Map interrupt numbers to function names.
// These aren't real calls, they're removed by the compiler.
var ({{range .interrupts}}
_ = interrupt.Register(IRQ_{{.Name}}, "__vector_{{.Name}}"){{end}}
)
// Peripherals. // Peripherals.
var ({{range .peripherals}} var ({{range .peripherals}}
// {{.Caption}} // {{.Caption}}
+14 -5
View File
@@ -82,6 +82,7 @@ type Device struct {
type interrupt struct { type interrupt struct {
Name string Name string
HandlerName string
peripheralIndex int peripheralIndex int
Value int // interrupt number Value int // interrupt number
Description string Description string
@@ -171,12 +172,12 @@ func readSVD(path, sourceURL string) (*Device, error) {
groupName := cleanName(periphEl.GroupName) groupName := cleanName(periphEl.GroupName)
for _, interrupt := range periphEl.Interrupts { for _, interrupt := range periphEl.Interrupts {
addInterrupt(interrupts, interrupt.Name, interrupt.Index, description) addInterrupt(interrupts, interrupt.Name, interrupt.Name, interrupt.Index, description)
// As a convenience, also use the peripheral name as the interrupt // As a convenience, also use the peripheral name as the interrupt
// name. Only do that for the nrf for now, as the stm32 .svd files // name. Only do that for the nrf for now, as the stm32 .svd files
// don't always put interrupts in the correct peripheral... // don't always put interrupts in the correct peripheral...
if len(periphEl.Interrupts) == 1 && strings.HasPrefix(device.Name, "nrf") { if len(periphEl.Interrupts) == 1 && strings.HasPrefix(device.Name, "nrf") {
addInterrupt(interrupts, periphEl.Name, interrupt.Index, description) addInterrupt(interrupts, periphEl.Name, interrupt.Name, interrupt.Index, description)
} }
} }
@@ -388,7 +389,7 @@ func readSVD(path, sourceURL string) (*Device, error) {
}, nil }, nil
} }
func addInterrupt(interrupts map[string]*interrupt, name string, index int, description string) { func addInterrupt(interrupts map[string]*interrupt, name, interruptName string, index int, description string) {
if _, ok := interrupts[name]; ok { if _, ok := interrupts[name]; ok {
if interrupts[name].Value != index { if interrupts[name].Value != index {
// Note: some SVD files like the one for STM32H7x7 contain mistakes. // Note: some SVD files like the one for STM32H7x7 contain mistakes.
@@ -409,6 +410,7 @@ func addInterrupt(interrupts map[string]*interrupt, name string, index int, desc
} else { } else {
interrupts[name] = &interrupt{ interrupts[name] = &interrupt{
Name: name, Name: name,
HandlerName: interruptName + "_IRQHandler",
peripheralIndex: len(interrupts), peripheralIndex: len(interrupts),
Value: index, Value: index,
Description: description, Description: description,
@@ -619,6 +621,7 @@ func writeGo(outdir string, device *Device) error {
package {{.pkgName}} package {{.pkgName}}
import ( import (
"runtime/interrupt"
"runtime/volatile" "runtime/volatile"
"unsafe" "unsafe"
) )
@@ -628,12 +631,18 @@ const (
DEVICE = "{{.metadata.name}}" DEVICE = "{{.metadata.name}}"
) )
// Interrupt numbers // Interrupt numbers.
const ({{range .interrupts}} const ({{range .interrupts}}
IRQ_{{.Name}} = {{.Value}} // {{.Description}}{{end}} IRQ_{{.Name}} = {{.Value}} // {{.Description}}{{end}}
IRQ_max = {{.interruptMax}} // Highest interrupt number on this device. IRQ_max = {{.interruptMax}} // Highest interrupt number on this device.
) )
// Map interrupt numbers to function names.
// These aren't real calls, they're removed by the compiler.
var ({{range .interrupts}}
_ = interrupt.Register(IRQ_{{.Name}}, "{{.HandlerName}}"){{end}}
)
// Peripherals. // Peripherals.
var ( var (
{{range .peripherals}} {{.Name}} = (*{{.GroupName}}_Type)(unsafe.Pointer(uintptr(0x{{printf "%x" .BaseAddress}}))) // {{.Description}} {{range .peripherals}} {{.Name}} = (*{{.GroupName}}_Type)(unsafe.Pointer(uintptr(0x{{printf "%x" .BaseAddress}}))) // {{.Description}}
@@ -879,7 +888,7 @@ Default_Handler:
num++ num++
} }
num++ num++
fmt.Fprintf(w, " .long %s_IRQHandler\n", intr.Name) fmt.Fprintf(w, " .long %s\n", intr.HandlerName)
} }
w.WriteString(` w.WriteString(`
+48
View File
@@ -0,0 +1,48 @@
package transform
import (
"go/scanner"
"go/token"
"path/filepath"
"tinygo.org/x/go-llvm"
)
// errorAt returns an error value at the location of the value.
// The location information may not be complete as it depends on debug
// information in the IR.
func errorAt(val llvm.Value, msg string) scanner.Error {
return scanner.Error{
Pos: getPosition(val),
Msg: msg,
}
}
// getPosition returns the position information for the given value, as far as
// it is available.
func getPosition(val llvm.Value) token.Position {
if !val.IsAInstruction().IsNil() {
loc := val.InstructionDebugLoc()
if loc.IsNil() {
return token.Position{}
}
file := loc.LocationScope().ScopeFile()
return token.Position{
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
Line: int(loc.LocationLine()),
Column: int(loc.LocationColumn()),
}
} else if !val.IsAFunction().IsNil() {
loc := val.Subprogram()
if loc.IsNil() {
return token.Position{}
}
file := loc.ScopeFile()
return token.Position{
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
Line: int(loc.SubprogramLine()),
}
} else {
return token.Position{}
}
}
+184
View File
@@ -0,0 +1,184 @@
package transform
import (
"fmt"
"strings"
"tinygo.org/x/go-llvm"
)
func LowerInterruptRegistrations(mod llvm.Module) []error {
var errs []error
// Discover interrupts. The runtime/interrupt.Register call is a compiler
// intrinsic that maps interrupt numbers to handler names.
handlerNames := map[int64]string{}
for _, call := range getUses(mod.NamedFunction("runtime/interrupt.Register")) {
if call.IsACallInst().IsNil() {
errs = append(errs, errorAt(call, "expected a call to runtime/interrupt.Register?"))
continue
}
num := call.Operand(0)
if num.IsAConstant().IsNil() {
errs = append(errs, errorAt(call, "non-constant interrupt number?"))
continue
}
// extract the interrupt name
nameStrGEP := call.Operand(1)
if nameStrGEP.IsAConstantExpr().IsNil() || nameStrGEP.Opcode() != llvm.GetElementPtr {
errs = append(errs, errorAt(call, "expected a string operand?"))
continue
}
nameStrPtr := nameStrGEP.Operand(0) // note: assuming it's a GEP to the first byte
nameStrLen := call.Operand(2)
if nameStrPtr.IsAGlobalValue().IsNil() || !nameStrPtr.IsGlobalConstant() || nameStrLen.IsAConstant().IsNil() {
errs = append(errs, errorAt(call, "non-constant interrupt name?"))
continue
}
// keep track of this name
name := string(getGlobalBytes(nameStrPtr)[:nameStrLen.SExtValue()])
handlerNames[num.SExtValue()] = name
// remove this pseudo-call
call.ReplaceAllUsesWith(llvm.ConstNull(call.Type()))
call.EraseFromParentAsInstruction()
}
ctx := mod.Context()
nullptr := llvm.ConstNull(llvm.PointerType(ctx.Int8Type(), 0))
builder := ctx.NewBuilder()
defer builder.Dispose()
// Create a function type with the signature of an interrupt handler.
fnType := llvm.FunctionType(ctx.VoidType(), nil, false)
handleType := mod.GetTypeByName("runtime/interrupt.handle")
if handleType.IsNil() {
// Nothing to do here.
return errs
}
handlePtrType := llvm.PointerType(handleType, 0)
var handlers []llvm.Value
for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
if global.Type() != handlePtrType {
continue
}
handlers = append(handlers, global)
}
for _, global := range handlers {
initializer := global.Initializer()
num := llvm.ConstExtractValue(initializer, []uint32{1, 0})
name := handlerNames[num.SExtValue()]
if name == "" {
errs = append(errs, errorAt(global, fmt.Sprintf("cannot find interrupt name for number %d", num.SExtValue())))
continue
}
// Create the func value.
handlerContext := llvm.ConstExtractValue(initializer, []uint32{0, 0})
handlerFuncPtr := llvm.ConstExtractValue(initializer, []uint32{0, 1})
if isFunctionLocal(handlerContext) || isFunctionLocal(handlerFuncPtr) {
errs = append(errs, errorAt(global, "func value must be constant"))
continue
}
if !handlerFuncPtr.IsAConstantExpr().IsNil() && handlerFuncPtr.Opcode() == llvm.PtrToInt {
// This is a ptrtoint: the IR was created for func lowering using a
// switch statement.
global := handlerFuncPtr.Operand(0)
if global.IsAGlobalValue().IsNil() {
errs = append(errs, errorAt(global, "internal error: expected a global for func lowering"))
continue
}
initializer := global.Initializer()
if initializer.Type() != mod.GetTypeByName("runtime.funcValueWithSignature") {
errs = append(errs, errorAt(global, "internal error: func lowering global has unexpected type"))
continue
}
ptrtoint := llvm.ConstExtractValue(initializer, []uint32{0})
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
errs = append(errs, errorAt(global, "internal error: func lowering global has unexpected func ptr type"))
continue
}
handlerFuncPtr = ptrtoint.Operand(0)
}
if handlerFuncPtr.Type().TypeKind() != llvm.PointerTypeKind || handlerFuncPtr.Type().ElementType().TypeKind() != llvm.FunctionTypeKind {
errs = append(errs, errorAt(global, "internal error: unexpected LLVM types in func value"))
continue
}
// Check for an existing handler, and report it as an error if there is
// one.
fn := mod.NamedFunction(name)
if fn.IsNil() {
fn = llvm.AddFunction(mod, name, fnType)
} else if fn.Type().ElementType() != fnType {
// Don't bother with a precise error message (listing the
// previsous location) because this should not normally happen
// anyway.
errs = append(errs, errorAt(global, name+" redeclared with a different signature"))
continue
} else if !fn.IsDeclaration() {
// Interrupt handler was already defined. Check the first
// instruction (which should be a call) whether this handler would
// be identical anyway.
firstInst := fn.FirstBasicBlock().FirstInstruction()
if !firstInst.IsACallInst().IsNil() && firstInst.OperandsCount() == 4 && firstInst.CalledValue() == handlerFuncPtr && firstInst.Operand(0) == num && firstInst.Operand(1) == handlerContext {
// Already defined and apparently identical, so assume this is
// fine.
continue
}
errValue := name + " redeclared in this program"
fnPos := getPosition(fn)
if fnPos.IsValid() {
errValue += "\n\tprevious declaration at " + fnPos.String()
}
errs = append(errs, errorAt(global, errValue))
continue
}
// Create the wrapper function.
fn.SetUnnamedAddr(true)
entryBlock := ctx.AddBasicBlock(fn, "entry")
builder.SetInsertPointAtEnd(entryBlock)
// Set the 'interrupt' flag if needed on this platform.
if strings.HasPrefix(mod.Target(), "avr") {
fn.SetFunctionCallConv(85) // CallingConv::AVR_SIGNAL
}
// Fill the function declaration with the forwarding call.
builder.CreateCall(handlerFuncPtr, []llvm.Value{num, handlerContext, nullptr}, "")
builder.CreateRetVoid()
for _, user := range getUses(global) {
if user.IsAConstantExpr().IsNil() || user.Opcode() != llvm.PtrToInt {
errs = append(errs, errorAt(global, "internal error: expected a ptrtoint"))
continue
}
user.ReplaceAllUsesWith(num)
}
global.EraseFromParentAsGlobal()
}
// Remove now-useless runtime/interrupt.use calls. These are used for some
// platforms like AVR that do not need to enable interrupts to use them, so
// need another way to keep them alive.
// After interrupts have been lowered, this call is useless and would cause
// a linker error so must be removed.
for _, call := range getUses(mod.NamedFunction("runtime/interrupt.use")) {
if call.IsACallInst().IsNil() {
errs = append(errs, errorAt(call, "internal error: expected call to runtime/interrupt.use"))
continue
}
call.EraseFromParentAsInstruction()
}
return errs
}
+16
View File
@@ -89,3 +89,19 @@ func typeHasPointers(t llvm.Type) bool {
return false return false
} }
} }
// isFunctionLocal returns true if (and only if) this value is local to a
// function. That is, it returns true for instructions and parameters, and false
// for constants and globals.
func isFunctionLocal(val llvm.Value) bool {
if !val.IsAConstant().IsNil() {
return false
}
if !val.IsAInstruction().IsNil() {
return true
}
if !val.IsAGlobalValue().IsNil() {
return false
}
panic("unknown value kind")
}
+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.11.0" const version = "0.12.0-dev"