mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-28 15:48:40 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ebde8b5875 | |||
| 9d50587d63 | |||
| 5e6f1725ac | |||
| 9cdc2fa768 | |||
| 084386262a | |||
| 674ddef219 | |||
| 0933577e60 | |||
| 69c1d802e1 | |||
| b424056721 | |||
| d735df6e16 | |||
| d37bbadb54 | |||
| ab7dc45288 | |||
| eee1b995f6 | |||
| a4fa41b49d | |||
| a5a90a57b9 | |||
| 71a380ce8c | |||
| 3b2a4b64c5 | |||
| 184827e4d8 | |||
| 14474e7099 | |||
| 3656ac2fc9 | |||
| 08f01ba3ff | |||
| 699312f477 | |||
| 9644edcd5a | |||
| 46325910c5 |
@@ -120,12 +120,6 @@ commands:
|
||||
key: llvm-build-9-linux-v0-assert
|
||||
paths:
|
||||
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:
|
||||
name: "Test TinyGo"
|
||||
@@ -184,12 +178,6 @@ commands:
|
||||
key: llvm-build-9-linux-v0
|
||||
paths:
|
||||
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:
|
||||
name: "Test TinyGo"
|
||||
command: make test
|
||||
@@ -256,10 +244,6 @@ commands:
|
||||
key: llvm-build-9-macos-v0
|
||||
paths:
|
||||
llvm-build
|
||||
- run:
|
||||
name: "Create LLVM symlinks"
|
||||
command: |
|
||||
ln -s $PWD/llvm-build/bin/clang-9 /usr/local/bin/clang-9
|
||||
- run:
|
||||
name: "Test TinyGo"
|
||||
command: make test
|
||||
|
||||
@@ -62,6 +62,11 @@ $(LIBCLANG_PATH): $(LIBCLANG_FILES)
|
||||
else ifeq ($(shell uname -s),Darwin)
|
||||
MD5SUM = md5
|
||||
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
|
||||
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
|
||||
START_GROUP = -Wl,--start-group
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ trigger:
|
||||
|
||||
jobs:
|
||||
- job: Build
|
||||
timeoutInMinutes: 180
|
||||
timeoutInMinutes: 240 # 4h
|
||||
pool:
|
||||
vmImage: 'VS2017-Win2016'
|
||||
steps:
|
||||
|
||||
@@ -34,6 +34,12 @@ func init() {
|
||||
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")
|
||||
}
|
||||
// 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 {
|
||||
|
||||
@@ -26,7 +26,11 @@ func runCCompiler(command string, flags ...string) error {
|
||||
switch command {
|
||||
case "clang":
|
||||
// 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...)
|
||||
var cflag *C.char
|
||||
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ func GorootVersionString(goroot string) (string, error) {
|
||||
// various ways.
|
||||
func getClangHeaderPath(TINYGOROOT string) string {
|
||||
// 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) {
|
||||
return path
|
||||
}
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ var flagUpdate = flag.Bool("update", false, "Update images based on test output.
|
||||
func TestCGo(t *testing.T) {
|
||||
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
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Read the AST in memory.
|
||||
|
||||
+25
-1
@@ -52,6 +52,13 @@ func parseConstExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
|
||||
}
|
||||
t.Next()
|
||||
return expr, nil
|
||||
case token.IDENT:
|
||||
expr := &ast.Ident{
|
||||
NamePos: t.pos,
|
||||
Name: "C." + t.value,
|
||||
}
|
||||
t.Next()
|
||||
return expr, nil
|
||||
case token.EOF:
|
||||
return nil, &scanner.Error{
|
||||
Pos: t.fset.Position(t.pos),
|
||||
@@ -133,8 +140,10 @@ func (t *tokenizer) Next() {
|
||||
if c == '.' {
|
||||
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
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.value = t.buf[:tokenLen]
|
||||
@@ -150,6 +159,21 @@ func (t *tokenizer) Next() {
|
||||
t.value = strings.TrimRight(t.value, "uUlL")
|
||||
}
|
||||
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 == '"':
|
||||
// String constant. Find the first '"' character that is not
|
||||
// preceded by a backslash.
|
||||
|
||||
+3
-2
@@ -21,8 +21,8 @@ func TestParseConst(t *testing.T) {
|
||||
{`5)`, `error: 1:2: unexpected token )`},
|
||||
{" \t)", `error: 1:4: unexpected token )`},
|
||||
{`5.8f`, `5.8`},
|
||||
{`foo`, `error: 1:1: unexpected token ILLEGAL`}, // identifiers unimplemented
|
||||
{``, `error: 1:1: empty constant`}, // empty constants not allowed in Go
|
||||
{`foo`, `C.foo`},
|
||||
{``, `error: 1:1: empty constant`}, // empty constants not allowed in Go
|
||||
{`"foo"`, `"foo"`},
|
||||
{`"a\\n"`, `"a\\n"`},
|
||||
{`"a\n"`, `"a\n"`},
|
||||
@@ -30,6 +30,7 @@ func TestParseConst(t *testing.T) {
|
||||
{`'a'`, `'a'`},
|
||||
{`0b10`, `0b10`},
|
||||
{`0x1234_5678`, `0x1234_5678`},
|
||||
{`5 5`, `error: 1:3: unexpected token INT`}, // test for a bugfix
|
||||
} {
|
||||
fset := token.NewFileSet()
|
||||
startPos := fset.AddFile("", -1, 1000).Pos(0)
|
||||
|
||||
@@ -5,7 +5,9 @@ package cgo
|
||||
/*
|
||||
#cgo linux CFLAGS: -I/usr/lib/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 darwin LDFLAGS: -L/usr/local/opt/llvm@9/lib -lclang -lffi
|
||||
#cgo freebsd LDFLAGS: -L/usr/local/llvm90/lib -lclang
|
||||
*/
|
||||
import "C"
|
||||
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#define foo 3
|
||||
#define bar foo
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const (
|
||||
Foo = C.foo
|
||||
Bar = C.bar
|
||||
)
|
||||
Vendored
+29
@@ -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
@@ -217,7 +217,7 @@ func (c *Compiler) Compile(mainPath string) []error {
|
||||
path = path[len(tinygoPath+"/src/"):]
|
||||
}
|
||||
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
|
||||
default:
|
||||
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
|
||||
@@ -251,13 +251,17 @@ func (c *Compiler) Compile(mainPath string) []error {
|
||||
return []error{err}
|
||||
}
|
||||
} else {
|
||||
_, err = lprogram.Import(mainPath, wd)
|
||||
_, err = lprogram.Import(mainPath, wd, token.Position{
|
||||
Filename: "build command-line-arguments",
|
||||
})
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = lprogram.Import("runtime", "")
|
||||
_, err = lprogram.Import("runtime", "", token.Position{
|
||||
Filename: "build default import",
|
||||
})
|
||||
if err != nil {
|
||||
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 {
|
||||
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.
|
||||
diparams := make([]llvm.Metadata, 0, len(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{
|
||||
Name: f.RelString(nil) + suffix,
|
||||
LinkageName: f.LinkName() + suffix,
|
||||
File: c.difiles[filename],
|
||||
File: c.getDIFile(filename),
|
||||
Line: line,
|
||||
Type: diFuncType,
|
||||
LocalToUnit: true,
|
||||
@@ -801,21 +797,37 @@ func (c *Compiler) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix,
|
||||
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) {
|
||||
if c.DumpSSA() {
|
||||
fmt.Printf("\nfunc %s:\n", frame.fn.Function)
|
||||
}
|
||||
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
|
||||
}
|
||||
if !frame.fn.IsExported() {
|
||||
frame.fn.LLVMFn.SetLinkage(llvm.InternalLinkage)
|
||||
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.
|
||||
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)
|
||||
case strings.HasPrefix(name, "runtime/volatile.Store"):
|
||||
return c.emitVolatileStore(frame, instr)
|
||||
case name == "runtime/interrupt.New" && len(fn.Blocks) == 0:
|
||||
return c.emitInterruptGlobal(frame, instr)
|
||||
}
|
||||
|
||||
targetFunc := c.ir.GetFunction(fn)
|
||||
|
||||
+48
-3
@@ -14,6 +14,7 @@ package compiler
|
||||
// frames.
|
||||
|
||||
import (
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"github.com/tinygo-org/tinygo/ir"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
@@ -34,6 +35,40 @@ func (c *Compiler) deferInitFunc(frame *Frame) {
|
||||
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
|
||||
// returns.
|
||||
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, "")
|
||||
}
|
||||
|
||||
// Put this struct in an alloca.
|
||||
alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca")
|
||||
c.builder.CreateStore(deferFrame, alloca)
|
||||
// Put this struct in an allocation.
|
||||
var alloca llvm.Value
|
||||
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() {
|
||||
c.trackPointer(alloca)
|
||||
}
|
||||
c.builder.CreateStore(deferFrame, alloca)
|
||||
|
||||
// Push it on top of the linked list by replacing deferPtr.
|
||||
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
|
||||
|
||||
+25
-14
@@ -31,20 +31,31 @@ func errorAt(inst llvm.Value, msg string) scanner.Error {
|
||||
}
|
||||
}
|
||||
|
||||
// getPosition returns the position information for the given instruction, as
|
||||
// far as it is available.
|
||||
func getPosition(inst llvm.Value) token.Position {
|
||||
if inst.IsAInstruction().IsNil() {
|
||||
// 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{}
|
||||
}
|
||||
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()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,14 +279,14 @@ func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
|
||||
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())
|
||||
for i := range methods {
|
||||
method := typ.Underlying().(*types.Interface).Method(i)
|
||||
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.SetInitializer(value)
|
||||
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
|
||||
// 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.
|
||||
func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
|
||||
signature := ir.MethodSignature(method)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -45,6 +45,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []er
|
||||
// Run some preparatory passes for the Go optimizer.
|
||||
goPasses := llvm.NewPassManager()
|
||||
defer goPasses.Dispose()
|
||||
goPasses.AddGlobalDCEPass()
|
||||
goPasses.AddGlobalOptimizerPass()
|
||||
goPasses.AddConstantPropagationPass()
|
||||
goPasses.AddAggressiveDCEPass()
|
||||
@@ -56,6 +57,12 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []er
|
||||
transform.OptimizeStringToBytes(c.mod)
|
||||
transform.OptimizeAllocs(c.mod)
|
||||
transform.LowerInterfaces(c.mod)
|
||||
|
||||
errs := transform.LowerInterruptRegistrations(c.mod)
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
if c.funcImplementation() == funcValueSwitch {
|
||||
transform.LowerFuncValues(c.mod)
|
||||
}
|
||||
@@ -99,6 +106,10 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []er
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
errs := transform.LowerInterruptRegistrations(c.mod)
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
}
|
||||
if c.VerifyIR() {
|
||||
if errs := c.checkModule(); errs != nil {
|
||||
|
||||
@@ -110,6 +110,9 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
|
||||
if k == types.UnsafePointer {
|
||||
return s.PtrSize
|
||||
}
|
||||
if k == types.Invalid {
|
||||
return 0 // only relevant when there is a type error somewhere
|
||||
}
|
||||
panic("unknown basic type: " + t.String())
|
||||
case *types.Array:
|
||||
n := t.Len()
|
||||
|
||||
+33
-3
@@ -60,14 +60,44 @@ func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value {
|
||||
info := c.getGlobalInfo(g)
|
||||
llvmGlobal := c.mod.NamedGlobal(info.linkName)
|
||||
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)
|
||||
if !info.extern {
|
||||
llvmGlobal.SetInitializer(llvm.ConstNull(llvmType))
|
||||
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
|
||||
|
||||
+1
-1
@@ -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())
|
||||
}
|
||||
switch c.GOOS() {
|
||||
case "linux":
|
||||
case "linux", "freebsd":
|
||||
// Return values: r0, r1 uintptr, err Errno
|
||||
// Pseudocode:
|
||||
// var err uintptr
|
||||
|
||||
@@ -10,5 +10,5 @@ require (
|
||||
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
|
||||
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
|
||||
)
|
||||
|
||||
@@ -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-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-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=
|
||||
|
||||
@@ -27,14 +27,13 @@ type Program struct {
|
||||
// Function or method.
|
||||
type Function struct {
|
||||
*ssa.Function
|
||||
LLVMFn llvm.Value
|
||||
module string // go:wasm-module
|
||||
linkName string // go:linkname, go:export, go:interrupt
|
||||
exported bool // go:export
|
||||
nobounds bool // go:nobounds
|
||||
flag bool // used by dead code elimination
|
||||
interrupt bool // go:interrupt
|
||||
inline InlineType // go:inline
|
||||
LLVMFn llvm.Value
|
||||
module string // go:wasm-module
|
||||
linkName string // go:linkname, go:export
|
||||
exported bool // go:export
|
||||
nobounds bool // go:nobounds
|
||||
flag bool // used by dead code elimination
|
||||
inline InlineType // go:inline
|
||||
}
|
||||
|
||||
// Interface type that is at some point used in a type assert (to check whether
|
||||
@@ -243,18 +242,6 @@ func (f *Function) parsePragmas() {
|
||||
f.inline = InlineHint
|
||||
case "//go:noinline":
|
||||
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":
|
||||
if len(parts) != 3 || parts[1] != f.Name() {
|
||||
continue
|
||||
@@ -288,14 +275,6 @@ func (f *Function) IsExported() bool {
|
||||
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.
|
||||
func (f *Function) Inline() InlineType {
|
||||
return f.inline
|
||||
|
||||
+14
-3
@@ -6,6 +6,7 @@ import (
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
@@ -46,7 +47,7 @@ type Package struct {
|
||||
|
||||
// Import loads the given package relative to srcDir (for the vendor directory).
|
||||
// It only loads the current package without recursion.
|
||||
func (p *Program) Import(path, srcDir string) (*Package, error) {
|
||||
func (p *Program) Import(path, srcDir string, pos token.Position) (*Package, error) {
|
||||
if p.Packages == nil {
|
||||
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)
|
||||
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 {
|
||||
// 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 {
|
||||
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, ok := err.(*ImportCycleError); ok {
|
||||
err.Packages = append([]string{p.ImportPath}, err.Packages...)
|
||||
|
||||
@@ -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
|
||||
// modifies global state.
|
||||
func FlashGDB(pkgName, port string, ocdOutput bool, options *compileopts.Options) error {
|
||||
if port == "" {
|
||||
var err error
|
||||
port, err = getDefaultPort()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) error {
|
||||
config, err := builder.NewConfig(options)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -452,7 +444,7 @@ func flashUF2UsingMSD(volume, tmppath string) error {
|
||||
// find standard UF2 info path
|
||||
var infoPath string
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
case "linux", "freebsd":
|
||||
infoPath = "/media/*/" + volume + "/INFO_UF2.TXT"
|
||||
case "darwin":
|
||||
infoPath = "/Volumes/" + volume + "/INFO_UF2.TXT"
|
||||
@@ -479,7 +471,7 @@ func flashHexUsingMSD(volume, tmppath string) error {
|
||||
// find expected volume path
|
||||
var destPath string
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
case "linux", "freebsd":
|
||||
destPath = "/media/*/" + volume
|
||||
case "darwin":
|
||||
destPath = "/Volumes/" + volume
|
||||
@@ -558,6 +550,8 @@ func getDefaultPort() (port string, err error) {
|
||||
portPath = "/dev/cu.usb*"
|
||||
case "linux":
|
||||
portPath = "/dev/ttyACM*"
|
||||
case "freebsd":
|
||||
portPath = "/dev/cuaU*"
|
||||
case "windows":
|
||||
cmd := exec.Command("wmic",
|
||||
"PATH", "Win32_SerialPort", "WHERE", "Caption LIKE 'USB Serial%'", "GET", "DeviceID")
|
||||
@@ -767,7 +761,7 @@ func main() {
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
err := FlashGDB(flag.Arg(0), *port, *ocdOutput, options)
|
||||
err := FlashGDB(flag.Arg(0), *ocdOutput, options)
|
||||
handleCompilerError(err)
|
||||
}
|
||||
case "run":
|
||||
|
||||
+28
-3
@@ -14,6 +14,7 @@ import (
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
@@ -157,7 +158,9 @@ func runTest(path, target string, t *testing.T) {
|
||||
}
|
||||
|
||||
// Run the test.
|
||||
runComplete := make(chan struct{})
|
||||
var cmd *exec.Cmd
|
||||
ranTooLong := false
|
||||
if target == "" {
|
||||
cmd = exec.Command(binary)
|
||||
} else {
|
||||
@@ -173,13 +176,35 @@ func runTest(path, target string, t *testing.T) {
|
||||
}
|
||||
stdout := &bytes.Buffer{}
|
||||
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 != "" {
|
||||
err = nil // workaround for QEMU
|
||||
}
|
||||
close(runComplete)
|
||||
|
||||
if ranTooLong {
|
||||
stdout.WriteString("--- test ran too long, terminating...\n")
|
||||
}
|
||||
|
||||
// putchar() prints CRLF, convert it to LF.
|
||||
actual := bytes.Replace(stdout.Bytes(), []byte{'\r', '\n'}, []byte{'\n'}, -1)
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
.type _start,@function
|
||||
|
||||
_start:
|
||||
// Workaround for missing support of the la pseudo-instruction in Clang 8:
|
||||
// https://reviews.llvm.org/D55325
|
||||
lui sp, %hi(_stack_top)
|
||||
addi sp, sp, %lo(_stack_top)
|
||||
// see https://gnu-mcu-eclipse.github.io/arch/riscv/programmer/#the-gp-global-pointer-register
|
||||
lui gp, %hi(__global_pointer$)
|
||||
addi gp, gp, %lo(__global_pointer$)
|
||||
// Load the stack pointer.
|
||||
la sp, _stack_top
|
||||
// Load the globals pointer. The program will load pointers relative to this
|
||||
// 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
|
||||
la gp, __global_pointer$
|
||||
call main
|
||||
|
||||
@@ -2,36 +2,33 @@
|
||||
|
||||
package machine
|
||||
|
||||
import "device/sam"
|
||||
import (
|
||||
"device/sam"
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
// UART1 on the Arduino Nano 33 connects to the onboard NINA-W102 WiFi chip.
|
||||
var (
|
||||
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(),
|
||||
Bus: sam.SERCOM3_USART,
|
||||
SERCOM: 3,
|
||||
}
|
||||
)
|
||||
|
||||
//go:export SERCOM3_IRQHandler
|
||||
func handleUART2() {
|
||||
// should reset IRQ
|
||||
UART2.Receive(byte((UART2.Bus.DATA.Get() & 0xFF)))
|
||||
UART2.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC)
|
||||
// UART2 on the Arduino Nano 33 connects to the normal TX/RX pins.
|
||||
var (
|
||||
UART2 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
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.
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
package machine
|
||||
|
||||
import "device/stm32"
|
||||
import (
|
||||
"device/stm32"
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
// https://wiki.stm32duino.com/index.php?title=File:Bluepillpinout.gif
|
||||
const (
|
||||
@@ -61,14 +64,12 @@ var (
|
||||
UART0 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
Bus: stm32.USART1,
|
||||
IRQVal: stm32.IRQ_USART1,
|
||||
}
|
||||
UART1 = &UART0
|
||||
)
|
||||
|
||||
//go:export USART1_IRQHandler
|
||||
func handleUART1() {
|
||||
UART1.Receive(byte((UART1.Bus.DR.Get() & 0xFF)))
|
||||
func init() {
|
||||
UART0.interrupt = interrupt.New(stm32.IRQ_USART1, UART0.handleInterrupt)
|
||||
}
|
||||
|
||||
// SPI pins
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
package machine
|
||||
|
||||
import "device/sam"
|
||||
import (
|
||||
"device/sam"
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
// UART1 on the Circuit Playground Express.
|
||||
var (
|
||||
@@ -13,9 +16,8 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
//go:export SERCOM1_IRQHandler
|
||||
func handleUART1() {
|
||||
defaultUART1Handler()
|
||||
func init() {
|
||||
UART1.interrupt = interrupt.New(sam.IRQ_SERCOM4, UART1.handleInterrupt)
|
||||
}
|
||||
|
||||
// I2C on the Circuit Playground Express.
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
package machine
|
||||
|
||||
import "device/sam"
|
||||
import (
|
||||
"device/sam"
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
// used to reset into bootloader
|
||||
const RESET_MAGIC_VALUE = 0xf01669ef
|
||||
@@ -60,9 +63,8 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
//go:export SERCOM1_IRQHandler
|
||||
func handleUART1() {
|
||||
defaultUART1Handler()
|
||||
func init() {
|
||||
UART1.interrupt = interrupt.New(sam.IRQ_SERCOM1, UART1.handleInterrupt)
|
||||
}
|
||||
|
||||
// I2C pins
|
||||
|
||||
@@ -4,6 +4,7 @@ package machine
|
||||
|
||||
import (
|
||||
"device/sam"
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
// used to reset into bootloader
|
||||
@@ -62,9 +63,8 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
//go:export SERCOM1_IRQHandler
|
||||
func handleUART1() {
|
||||
defaultUART1Handler()
|
||||
func init() {
|
||||
UART1.interrupt = interrupt.New(sam.IRQ_SERCOM1, UART1.handleInterrupt)
|
||||
}
|
||||
|
||||
// I2C pins
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
package machine
|
||||
|
||||
import "device/stm32"
|
||||
import (
|
||||
"device/stm32"
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
const (
|
||||
PA0 = portA + 0
|
||||
@@ -100,14 +103,12 @@ var (
|
||||
UART0 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
Bus: stm32.USART2,
|
||||
IRQVal: stm32.IRQ_USART2,
|
||||
}
|
||||
UART2 = &UART0
|
||||
)
|
||||
|
||||
//go:export USART2_IRQHandler
|
||||
func handleUART2() {
|
||||
UART2.Receive(byte((UART2.Bus.DR.Get() & 0xFF)))
|
||||
func init() {
|
||||
UART0.interrupt = interrupt.New(stm32.IRQ_USART2, UART0.handleInterrupt)
|
||||
}
|
||||
|
||||
// SPI pins
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
package machine
|
||||
|
||||
import "device/sam"
|
||||
import (
|
||||
"device/sam"
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
// used to reset into bootloader
|
||||
const RESET_MAGIC_VALUE = 0xf01669ef
|
||||
@@ -51,9 +54,8 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
//go:export SERCOM1_IRQHandler
|
||||
func handleUART1() {
|
||||
defaultUART1Handler()
|
||||
func init() {
|
||||
UART1.interrupt = interrupt.New(sam.IRQ_SERCOM0, UART1.handleInterrupt)
|
||||
}
|
||||
|
||||
// SPI pins
|
||||
|
||||
@@ -4,6 +4,7 @@ package machine
|
||||
|
||||
import (
|
||||
"device/avr"
|
||||
"runtime/interrupt"
|
||||
"runtime/volatile"
|
||||
)
|
||||
|
||||
@@ -232,6 +233,18 @@ func (uart UART) Configure(config UARTConfig) {
|
||||
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
|
||||
// https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html
|
||||
// ((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
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"device/arm"
|
||||
"device/sam"
|
||||
"errors"
|
||||
"runtime/interrupt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
@@ -292,9 +293,10 @@ func waitADCSync() {
|
||||
|
||||
// UART on the SAMD21.
|
||||
type UART struct {
|
||||
Buffer *RingBuffer
|
||||
Bus *sam.SERCOM_USART_Type
|
||||
SERCOM uint8
|
||||
Buffer *RingBuffer
|
||||
Bus *sam.SERCOM_USART_Type
|
||||
SERCOM uint8
|
||||
interrupt interrupt.Interrupt
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -401,10 +403,7 @@ func (uart UART) Configure(config UARTConfig) error {
|
||||
uart.Bus.INTENSET.Set(sam.SERCOM_USART_INTENSET_RXC)
|
||||
|
||||
// Enable RX IRQ.
|
||||
// IRQ lines are in the same order as SERCOM instance numbers on SAMD21
|
||||
// chips, so the IRQ number can be trivially determined from the SERCOM
|
||||
// number.
|
||||
arm.EnableIRQ(sam.IRQ_SERCOM0 + uint32(uart.SERCOM))
|
||||
uart.interrupt.Enable()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -432,11 +431,12 @@ func (uart UART) WriteByte(c byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// defaultUART1Handler handles the UART1 IRQ.
|
||||
func defaultUART1Handler() {
|
||||
// handleInterrupt should be called from the appropriate interrupt handler for
|
||||
// this UART instance.
|
||||
func (uart *UART) handleInterrupt(interrupt.Interrupt) {
|
||||
// should reset IRQ
|
||||
UART1.Receive(byte((UART1.Bus.DATA.Get() & 0xFF)))
|
||||
UART1.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC)
|
||||
uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF)))
|
||||
uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC)
|
||||
}
|
||||
|
||||
// I2C on the SAMD21.
|
||||
@@ -1368,7 +1368,8 @@ func (usbcdc USBCDC) Configure(config UARTConfig) {
|
||||
sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_ENABLE)
|
||||
|
||||
// enable IRQ
|
||||
arm.EnableIRQ(sam.IRQ_USB)
|
||||
intr := interrupt.New(sam.IRQ_USB, handleUSB)
|
||||
intr.Enable()
|
||||
}
|
||||
|
||||
func handlePadCalibration() {
|
||||
@@ -1414,8 +1415,7 @@ func handlePadCalibration() {
|
||||
sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos)
|
||||
}
|
||||
|
||||
//go:export USB_IRQHandler
|
||||
func handleUSB() {
|
||||
func handleUSB(intr interrupt.Interrupt) {
|
||||
// reset all interrupt flags
|
||||
flags := sam.USB_DEVICE.INTFLAG.Get()
|
||||
sam.USB_DEVICE.INTFLAG.Set(flags)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"device/arm"
|
||||
"device/sam"
|
||||
"errors"
|
||||
"runtime/interrupt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
@@ -1402,11 +1403,11 @@ func (usbcdc USBCDC) Configure(config UARTConfig) {
|
||||
// enable USB
|
||||
sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_ENABLE)
|
||||
|
||||
// enable IRQ
|
||||
arm.EnableIRQ(sam.IRQ_USB_OTHER)
|
||||
arm.EnableIRQ(sam.IRQ_USB_SOF_HSOF)
|
||||
arm.EnableIRQ(sam.IRQ_USB_TRCPT0)
|
||||
arm.EnableIRQ(sam.IRQ_USB_TRCPT1)
|
||||
// enable IRQ at highest priority
|
||||
interrupt.New(sam.IRQ_USB_OTHER, handleUSBIRQ).Enable()
|
||||
interrupt.New(sam.IRQ_USB_SOF_HSOF, handleUSBIRQ).Enable()
|
||||
interrupt.New(sam.IRQ_USB_TRCPT0, handleUSBIRQ).Enable()
|
||||
interrupt.New(sam.IRQ_USB_TRCPT1, handleUSBIRQ).Enable()
|
||||
}
|
||||
|
||||
func handlePadCalibration() {
|
||||
@@ -1452,27 +1453,7 @@ func handlePadCalibration() {
|
||||
sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos)
|
||||
}
|
||||
|
||||
//go:export USB_OTHER_IRQHandler
|
||||
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() {
|
||||
func handleUSBIRQ(interrupt.Interrupt) {
|
||||
// reset all interrupt flags
|
||||
flags := sam.USB_DEVICE.INTFLAG.Get()
|
||||
sam.USB_DEVICE.INTFLAG.Set(flags)
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/arm"
|
||||
"device/nrf"
|
||||
"errors"
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -88,8 +88,11 @@ func (uart UART) Configure(config UARTConfig) {
|
||||
nrf.UART0.INTENSET.Set(nrf.UART_INTENSET_RXDRDY_Msk)
|
||||
|
||||
// Enable RX IRQ.
|
||||
arm.SetPriority(nrf.IRQ_UART0, 0xc0) // low priority
|
||||
arm.EnableIRQ(nrf.IRQ_UART0)
|
||||
intr := interrupt.New(nrf.IRQ_UART0, func(intr interrupt.Interrupt) {
|
||||
UART0.handleInterrupt()
|
||||
})
|
||||
intr.SetPriority(0xc0) // low priority
|
||||
intr.Enable()
|
||||
}
|
||||
|
||||
// SetBaudRate sets the communication speed for the UART.
|
||||
|
||||
@@ -20,11 +20,6 @@ func (uart UART) setPins(tx, rx Pin) {
|
||||
nrf.UART0.PSELRXD.Set(uint32(rx))
|
||||
}
|
||||
|
||||
//go:export UART0_IRQHandler
|
||||
func handleUART0() {
|
||||
UART0.handleInterrupt()
|
||||
}
|
||||
|
||||
func (i2c I2C) setPins(scl, sda Pin) {
|
||||
i2c.Bus.PSELSCL.Set(uint32(scl))
|
||||
i2c.Bus.PSELSDA.Set(uint32(sda))
|
||||
|
||||
@@ -21,11 +21,6 @@ func (uart UART) setPins(tx, rx Pin) {
|
||||
nrf.UART0.PSELRXD.Set(uint32(rx))
|
||||
}
|
||||
|
||||
//go:export UARTE0_UART0_IRQHandler
|
||||
func handleUART0() {
|
||||
UART0.handleInterrupt()
|
||||
}
|
||||
|
||||
func (i2c I2C) setPins(scl, sda Pin) {
|
||||
i2c.Bus.PSELSCL.Set(uint32(scl))
|
||||
i2c.Bus.PSELSDA.Set(uint32(sda))
|
||||
|
||||
@@ -25,11 +25,6 @@ func (uart UART) setPins(tx, rx Pin) {
|
||||
nrf.UART0.PSEL.RXD.Set(uint32(rx))
|
||||
}
|
||||
|
||||
//go:export UARTE0_UART0_IRQHandler
|
||||
func handleUART0() {
|
||||
UART0.handleInterrupt()
|
||||
}
|
||||
|
||||
func (i2c I2C) setPins(scl, sda Pin) {
|
||||
i2c.Bus.PSEL.SCL.Set(uint32(scl))
|
||||
i2c.Bus.PSEL.SDA.Set(uint32(sda))
|
||||
|
||||
@@ -5,9 +5,9 @@ package machine
|
||||
// Peripheral abstraction layer for the stm32.
|
||||
|
||||
import (
|
||||
"device/arm"
|
||||
"device/stm32"
|
||||
"errors"
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
func CPUFrequency() uint32 {
|
||||
@@ -111,9 +111,9 @@ func (p Pin) Get() bool {
|
||||
|
||||
// UART
|
||||
type UART struct {
|
||||
Buffer *RingBuffer
|
||||
Bus *stm32.USART_Type
|
||||
IRQVal uint32
|
||||
Buffer *RingBuffer
|
||||
Bus *stm32.USART_Type
|
||||
interrupt interrupt.Interrupt
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// Enable RX IRQ
|
||||
arm.SetPriority(uart.IRQVal, 0xc0)
|
||||
arm.EnableIRQ(uart.IRQVal)
|
||||
uart.interrupt.SetPriority(0xc0)
|
||||
uart.interrupt.Enable()
|
||||
}
|
||||
|
||||
// SetBaudRate sets the communication speed for the UART.
|
||||
@@ -182,6 +182,12 @@ func (uart UART) WriteByte(c byte) error {
|
||||
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.
|
||||
type SPI struct {
|
||||
Bus *stm32.SPI_Type
|
||||
|
||||
@@ -5,8 +5,8 @@ package machine
|
||||
// Peripheral abstraction layer for the stm32.
|
||||
|
||||
import (
|
||||
"device/arm"
|
||||
"device/stm32"
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
// Enable RX IRQ.
|
||||
arm.SetPriority(stm32.IRQ_USART2, 0xc0)
|
||||
arm.EnableIRQ(stm32.IRQ_USART2)
|
||||
intr := interrupt.New(stm32.IRQ_USART2, func(interrupt.Interrupt) {
|
||||
UART1.Receive(byte((stm32.USART2.DR.Get() & 0xFF)))
|
||||
})
|
||||
intr.SetPriority(0xc0)
|
||||
intr.Enable()
|
||||
}
|
||||
|
||||
// WriteByte writes a byte of data to the UART.
|
||||
@@ -215,8 +218,3 @@ func (uart UART) WriteByte(c byte) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:export USART2_IRQHandler
|
||||
func handleUSART2() {
|
||||
UART1.Receive(byte((stm32.USART2.DR.Get() & 0xFF)))
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build darwin linux,!baremetal
|
||||
// +build darwin linux,!baremetal freebsd,!baremetal
|
||||
|
||||
package os
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// +build freebsd
|
||||
|
||||
package runtime
|
||||
|
||||
const GOOS = "freebsd"
|
||||
@@ -4,6 +4,7 @@ package runtime
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
_ "runtime/interrupt" // make sure the interrupt handler is defined
|
||||
)
|
||||
|
||||
type timeUnit int64
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"device/arm"
|
||||
"device/sam"
|
||||
"machine"
|
||||
"runtime/interrupt"
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
@@ -214,8 +215,14 @@ func initRTC() {
|
||||
sam.RTC_MODE0.CTRL.SetBits(sam.RTC_MODE0_CTRL_ENABLE)
|
||||
waitForSync()
|
||||
|
||||
arm.SetPriority(sam.IRQ_RTC, 0xc0)
|
||||
arm.EnableIRQ(sam.IRQ_RTC)
|
||||
intr := interrupt.New(sam.IRQ_RTC, func(intr interrupt.Interrupt) {
|
||||
// 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() {
|
||||
@@ -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() {
|
||||
// Turn on clock for USB
|
||||
sam.PM.APBBMASK.SetBits(sam.PM_APBBMASK_USB_)
|
||||
|
||||
@@ -81,27 +81,4 @@ func putchar(c byte) {
|
||||
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
|
||||
|
||||
func sleepTicks(d timeUnit) {
|
||||
target := ticks() + d
|
||||
for ticks() < target {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package runtime
|
||||
|
||||
import (
|
||||
"device/riscv"
|
||||
"device/sifive"
|
||||
)
|
||||
|
||||
func abort() {
|
||||
@@ -12,3 +13,27 @@ func abort() {
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,19 @@ import (
|
||||
// Special memory-mapped device to exit tests, created by SiFive.
|
||||
var testExit = (*volatile.Register32)(unsafe.Pointer(uintptr(0x100000)))
|
||||
|
||||
var timestamp timeUnit
|
||||
|
||||
func abort() {
|
||||
// Signal a successful exit.
|
||||
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
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"device/arm"
|
||||
"device/nrf"
|
||||
"machine"
|
||||
"runtime/interrupt"
|
||||
"runtime/volatile"
|
||||
)
|
||||
|
||||
@@ -43,8 +44,13 @@ func initLFCLK() {
|
||||
|
||||
func initRTC() {
|
||||
nrf.RTC1.TASKS_START.Set(1)
|
||||
arm.SetPriority(nrf.IRQ_RTC1, 0xc0) // low priority
|
||||
arm.EnableIRQ(nrf.IRQ_RTC1)
|
||||
intr := interrupt.New(nrf.IRQ_RTC1, func(intr interrupt.Interrupt) {
|
||||
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) {
|
||||
@@ -96,10 +102,3 @@ func rtc_sleep(ticks uint32) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"device/arm"
|
||||
"device/stm32"
|
||||
"machine"
|
||||
"runtime/interrupt"
|
||||
"runtime/volatile"
|
||||
)
|
||||
|
||||
@@ -101,8 +102,9 @@ func initRTC() {
|
||||
func initTIM() {
|
||||
stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN)
|
||||
|
||||
arm.SetPriority(stm32.IRQ_TIM3, 0xc3)
|
||||
arm.EnableIRQ(stm32.IRQ_TIM3)
|
||||
intr := interrupt.New(stm32.IRQ_TIM3, handleTIM3)
|
||||
intr.SetPriority(0xc3)
|
||||
intr.Enable()
|
||||
}
|
||||
|
||||
const asyncScheduler = false
|
||||
@@ -186,8 +188,7 @@ func timerSleep(ticks uint32) {
|
||||
}
|
||||
}
|
||||
|
||||
//go:export TIM3_IRQHandler
|
||||
func handleTIM3() {
|
||||
func handleTIM3(interrupt.Interrupt) {
|
||||
if stm32.TIM3.SR.HasBits(stm32.TIM_SR_UIF) {
|
||||
// Disable the timer.
|
||||
stm32.TIM3.CR1.ClearBits(stm32.TIM_CR1_CEN)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"device/arm"
|
||||
"device/stm32"
|
||||
"machine"
|
||||
"runtime/interrupt"
|
||||
"runtime/volatile"
|
||||
)
|
||||
|
||||
@@ -121,8 +122,9 @@ var timerWakeup volatile.Register8
|
||||
func initTIM3() {
|
||||
stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN)
|
||||
|
||||
arm.SetPriority(stm32.IRQ_TIM3, 0xc3)
|
||||
arm.EnableIRQ(stm32.IRQ_TIM3)
|
||||
intr := interrupt.New(stm32.IRQ_TIM3, handleTIM3)
|
||||
intr.SetPriority(0xc3)
|
||||
intr.Enable()
|
||||
}
|
||||
|
||||
// Enable the TIM7 clock.(tick count)
|
||||
@@ -139,8 +141,9 @@ func initTIM7() {
|
||||
// Enable the timer.
|
||||
stm32.TIM7.CR1.SetBits(stm32.TIM_CR1_CEN)
|
||||
|
||||
arm.SetPriority(stm32.IRQ_TIM7, 0xc1)
|
||||
arm.EnableIRQ(stm32.IRQ_TIM7)
|
||||
intr := interrupt.New(stm32.IRQ_TIM7, handleTIM7)
|
||||
intr.SetPriority(0xc1)
|
||||
intr.Enable()
|
||||
}
|
||||
|
||||
const asyncScheduler = false
|
||||
@@ -183,8 +186,7 @@ func timerSleep(ticks uint32) {
|
||||
}
|
||||
}
|
||||
|
||||
//go:export TIM3_IRQHandler
|
||||
func handleTIM3() {
|
||||
func handleTIM3(interrupt.Interrupt) {
|
||||
if stm32.TIM3.SR.HasBits(stm32.TIM_SR_UIF) {
|
||||
// Disable the timer.
|
||||
stm32.TIM3.CR1.ClearBits(stm32.TIM_CR1_CEN)
|
||||
@@ -197,8 +199,7 @@ func handleTIM3() {
|
||||
}
|
||||
}
|
||||
|
||||
//go:export TIM7_IRQHandler
|
||||
func handleTIM7() {
|
||||
func handleTIM7(interrupt.Interrupt) {
|
||||
if stm32.TIM7.SR.HasBits(stm32.TIM_SR_UIF) {
|
||||
// clear the update flag
|
||||
stm32.TIM7.SR.ClearBits(stm32.TIM_SR_UIF)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build darwin linux,!baremetal
|
||||
// +build darwin linux,!baremetal freebsd,!baremetal
|
||||
|
||||
package runtime
|
||||
|
||||
|
||||
@@ -76,3 +76,17 @@ func memset(ptr unsafe.Pointer, c byte, size uintptr) unsafe.Pointer {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
OUTPUT_ARCH(arm)
|
||||
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 {
|
||||
ewram : ORIGIN = 0x02000000, LENGTH = 256K /* on-board work RAM (2 wait states) */
|
||||
iwram : ORIGIN = 0x03000000, LENGTH = 32K /* in-chip work RAM (faster) */
|
||||
rom : ORIGIN = 0x08000000, LENGTH = 32M /* flash ROM */
|
||||
ewram : ORIGIN = 0x02000000, LENGTH = 256K /* on-board work RAM (2 wait states) */
|
||||
iwram : ORIGIN = 0x03000000, LENGTH = 32K-96 /* in-chip work RAM (faster) */
|
||||
rom : ORIGIN = 0x08000000, LENGTH = 32M /* flash ROM */
|
||||
}
|
||||
|
||||
__iwram_top = ORIGIN(iwram) + LENGTH(iwram);;
|
||||
_stack_size = 3K;
|
||||
__sp_irq = _stack_top;
|
||||
__sp_usr = _stack_top - 1K;
|
||||
__stack_size_irq = 1K;
|
||||
__stack_size_usr = 2K;
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
@@ -35,8 +36,10 @@ SECTIONS
|
||||
.stack (NOLOAD) :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
. += _stack_size;
|
||||
_stack_top = .;
|
||||
__sp_irq = .;
|
||||
. += __stack_size_irq;
|
||||
__sp_usr = .;
|
||||
. += __stack_size_usr;
|
||||
} >iwram
|
||||
|
||||
/* Start address (in flash) of .data, used by startup code. */
|
||||
|
||||
@@ -17,9 +17,7 @@ _start:
|
||||
.byte 0x00,0x00 // Checksum (80000BEh)
|
||||
|
||||
start_vector:
|
||||
mov r0, #0x4000000 // REG_BASE
|
||||
str r0, [r0, #0x208]
|
||||
|
||||
// Configure stacks
|
||||
mov r0, #0x12 // Switch to IRQ Mode
|
||||
msr cpsr, r0
|
||||
ldr sp, =__sp_irq // Set IRQ stack
|
||||
@@ -27,7 +25,21 @@ start_vector:
|
||||
msr cpsr, r0
|
||||
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)
|
||||
ldr r3, =main
|
||||
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
|
||||
|
||||
Vendored
+9
@@ -41,6 +41,9 @@ func main() {
|
||||
// deferred functions
|
||||
testDefer()
|
||||
|
||||
// defers in loop
|
||||
testDeferLoop()
|
||||
|
||||
// Take a bound method and use it as a function pointer.
|
||||
// This function pointer needs a context pointer.
|
||||
testBound(thing.String)
|
||||
@@ -85,6 +88,12 @@ func testDefer() {
|
||||
println("deferring...")
|
||||
}
|
||||
|
||||
func testDeferLoop() {
|
||||
for j := 0; j < 4; j++ {
|
||||
defer deferred("loop", j)
|
||||
}
|
||||
}
|
||||
|
||||
func deferred(msg string, i int) {
|
||||
println(msg, i)
|
||||
}
|
||||
|
||||
Vendored
+4
@@ -4,6 +4,10 @@ Thing.Print: foo arg: bar
|
||||
...run as defer 3
|
||||
...run closure deferred: 4
|
||||
...run as defer 1
|
||||
loop 3
|
||||
loop 2
|
||||
loop 1
|
||||
loop 0
|
||||
bound method: foo
|
||||
thing inside closure: foo
|
||||
inside fp closure: foo 3
|
||||
|
||||
Vendored
+6
@@ -33,6 +33,10 @@ func main() {
|
||||
// https://github.com/tinygo-org/tinygo/issues/309
|
||||
itf = linkedList{}
|
||||
|
||||
// Test bugfix for assertion on named empty interface:
|
||||
// https://github.com/tinygo-org/tinygo/issues/453
|
||||
_, _ = itf.(Empty)
|
||||
|
||||
var n int
|
||||
var f float32
|
||||
var interfaceEqualTests = []struct {
|
||||
@@ -251,3 +255,5 @@ func (s SleepBlocker) Sleep() {
|
||||
type StaticBlocker interface {
|
||||
Sleep()
|
||||
}
|
||||
|
||||
type Empty interface{}
|
||||
|
||||
@@ -260,6 +260,7 @@ func writeGo(outdir string, device *Device) error {
|
||||
package {{.pkgName}}
|
||||
|
||||
import (
|
||||
"runtime/interrupt"
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
@@ -277,6 +278,12 @@ const ({{range .interrupts}}
|
||||
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.
|
||||
var ({{range .peripherals}}
|
||||
// {{.Caption}}
|
||||
|
||||
@@ -82,6 +82,7 @@ type Device struct {
|
||||
|
||||
type interrupt struct {
|
||||
Name string
|
||||
HandlerName string
|
||||
peripheralIndex int
|
||||
Value int // interrupt number
|
||||
Description string
|
||||
@@ -171,12 +172,12 @@ func readSVD(path, sourceURL string) (*Device, error) {
|
||||
groupName := cleanName(periphEl.GroupName)
|
||||
|
||||
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
|
||||
// name. Only do that for the nrf for now, as the stm32 .svd files
|
||||
// don't always put interrupts in the correct peripheral...
|
||||
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
|
||||
}
|
||||
|
||||
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 interrupts[name].Value != index {
|
||||
// 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 {
|
||||
interrupts[name] = &interrupt{
|
||||
Name: name,
|
||||
HandlerName: interruptName + "_IRQHandler",
|
||||
peripheralIndex: len(interrupts),
|
||||
Value: index,
|
||||
Description: description,
|
||||
@@ -619,6 +621,7 @@ func writeGo(outdir string, device *Device) error {
|
||||
package {{.pkgName}}
|
||||
|
||||
import (
|
||||
"runtime/interrupt"
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
@@ -628,12 +631,18 @@ const (
|
||||
DEVICE = "{{.metadata.name}}"
|
||||
)
|
||||
|
||||
// Interrupt numbers
|
||||
// Interrupt numbers.
|
||||
const ({{range .interrupts}}
|
||||
IRQ_{{.Name}} = {{.Value}} // {{.Description}}{{end}}
|
||||
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.
|
||||
var (
|
||||
{{range .peripherals}} {{.Name}} = (*{{.GroupName}}_Type)(unsafe.Pointer(uintptr(0x{{printf "%x" .BaseAddress}}))) // {{.Description}}
|
||||
@@ -879,7 +888,7 @@ Default_Handler:
|
||||
num++
|
||||
}
|
||||
num++
|
||||
fmt.Fprintf(w, " .long %s_IRQHandler\n", intr.Name)
|
||||
fmt.Fprintf(w, " .long %s\n", intr.HandlerName)
|
||||
}
|
||||
|
||||
w.WriteString(`
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -89,3 +89,19 @@ func typeHasPointers(t llvm.Type) bool {
|
||||
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
@@ -2,4 +2,4 @@ package main
|
||||
|
||||
// version of this package.
|
||||
// Update this value before release of new version of software.
|
||||
const version = "0.11.0"
|
||||
const version = "0.12.0-dev"
|
||||
|
||||
Reference in New Issue
Block a user