mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-27 07:08:42 +00:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 008dd7dfe6 | |||
| 9f5066aa6f | |||
| 3b24fedf92 | |||
| 8cd2a462b9 | |||
| 25b045d0a7 | |||
| 78acbdf0d9 | |||
| 617e2791ef | |||
| a0908ff55b | |||
| 2f1f8fb075 | |||
| 6f61b83ad5 | |||
| 8dfefb46d1 | |||
| 959442dc82 | |||
| c1aa152a63 | |||
| dd3d8a363a | |||
| bb509ec91d | |||
| 944f022060 | |||
| cd517a30af | |||
| 52d8655eec | |||
| f5786941e5 | |||
| 1f73941c43 | |||
| abeab51d00 | |||
| 9ef75f17bf | |||
| c3992bd77b | |||
| f79e66ac2e | |||
| 4eac212695 |
+10
-10
@@ -294,16 +294,16 @@ commands:
|
||||
variant: "macos"
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-macos-v2-{{ checksum "go.mod" }}
|
||||
- go-cache-macos-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-macos-v3-{{ checksum "go.mod" }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-source-11-macos-v2
|
||||
- llvm-source-11-macos-v3
|
||||
- run:
|
||||
name: "Fetch LLVM source"
|
||||
command: make llvm-source
|
||||
- save_cache:
|
||||
key: llvm-source-11-macos-v2
|
||||
key: llvm-source-11-macos-v3
|
||||
paths:
|
||||
- llvm-project/clang/lib/Headers
|
||||
- llvm-project/clang/include
|
||||
@@ -311,7 +311,7 @@ commands:
|
||||
- llvm-project/llvm/include
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-build-11-macos-v3
|
||||
- llvm-build-11-macos-v4
|
||||
- run:
|
||||
name: "Build LLVM"
|
||||
command: |
|
||||
@@ -327,17 +327,17 @@ commands:
|
||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-11-macos-v3
|
||||
key: llvm-build-11-macos-v4
|
||||
paths:
|
||||
llvm-build
|
||||
- restore_cache:
|
||||
keys:
|
||||
- wasi-libc-sysroot-macos-v3
|
||||
- wasi-libc-sysroot-macos-v4
|
||||
- run:
|
||||
name: "Build wasi-libc"
|
||||
command: make wasi-libc
|
||||
- save_cache:
|
||||
key: wasi-libc-sysroot-macos-v3
|
||||
key: wasi-libc-sysroot-macos-v4
|
||||
paths:
|
||||
- lib/wasi-libc/sysroot
|
||||
- run:
|
||||
@@ -359,7 +359,7 @@ commands:
|
||||
tinygo version
|
||||
- run: make smoketest AVR=0
|
||||
- save_cache:
|
||||
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
key: go-cache-macos-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- /go/pkg/mod
|
||||
@@ -401,7 +401,7 @@ jobs:
|
||||
- build-linux
|
||||
build-macos:
|
||||
macos:
|
||||
xcode: "10.1.0"
|
||||
xcode: "11.1.0" # macOS 10.14
|
||||
steps:
|
||||
- build-macos
|
||||
|
||||
|
||||
@@ -182,25 +182,28 @@ tinygo:
|
||||
test: wasi-libc
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
|
||||
|
||||
TEST_PACKAGES = \
|
||||
container/heap \
|
||||
container/list \
|
||||
container/ring \
|
||||
crypto/des \
|
||||
encoding \
|
||||
encoding/ascii85 \
|
||||
encoding/base32 \
|
||||
encoding/hex \
|
||||
hash/adler32 \
|
||||
hash/fnv \
|
||||
hash/crc64 \
|
||||
math \
|
||||
math/cmplx \
|
||||
text/scanner \
|
||||
unicode/utf8 \
|
||||
|
||||
# Test known-working standard library packages.
|
||||
# TODO: do this in one command, parallelize, and only show failing tests (no
|
||||
# implied -v flag).
|
||||
# TODO: parallelize, and only show failing tests (no implied -v flag).
|
||||
.PHONY: tinygo-test
|
||||
tinygo-test:
|
||||
$(TINYGO) test container/heap
|
||||
$(TINYGO) test container/list
|
||||
$(TINYGO) test container/ring
|
||||
$(TINYGO) test crypto/des
|
||||
$(TINYGO) test encoding/ascii85
|
||||
$(TINYGO) test encoding/base32
|
||||
$(TINYGO) test encoding/hex
|
||||
$(TINYGO) test hash/adler32
|
||||
$(TINYGO) test hash/fnv
|
||||
$(TINYGO) test hash/crc64
|
||||
$(TINYGO) test math
|
||||
$(TINYGO) test math/cmplx
|
||||
$(TINYGO) test text/scanner
|
||||
$(TINYGO) test unicode/utf8
|
||||
$(TINYGO) test $(TEST_PACKAGES)
|
||||
|
||||
.PHONY: smoketest
|
||||
smoketest:
|
||||
|
||||
+10
-9
@@ -39,6 +39,11 @@ type BuildResult struct {
|
||||
// The directory of the main package. This is useful for testing as the test
|
||||
// binary must be run in the directory of the tested package.
|
||||
MainDir string
|
||||
|
||||
// ImportPath is the import path of the main package. This is useful for
|
||||
// correctly printing test results: the import path isn't always the same as
|
||||
// the path listed on the command line.
|
||||
ImportPath string
|
||||
}
|
||||
|
||||
// packageAction is the struct that is serialized to JSON and hashed, to work as
|
||||
@@ -94,6 +99,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||
DefaultStackSize: config.Target.DefaultStackSize,
|
||||
NeedsStackObjects: config.NeedsStackObjects(),
|
||||
Debug: config.Debug(),
|
||||
LLVMFeatures: config.LLVMFeatures(),
|
||||
}
|
||||
|
||||
// Load the target machine, which is the LLVM object that contains all
|
||||
@@ -300,13 +306,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rename may fail if another process is trying to write to
|
||||
// the same file. However, in this case, the failure is
|
||||
// acceptable because the result of the other process can be
|
||||
// used.
|
||||
os.Rename(f.Name(), bitcodePath)
|
||||
return nil
|
||||
return os.Rename(f.Name(), bitcodePath)
|
||||
},
|
||||
}
|
||||
jobs = append(jobs, job)
|
||||
@@ -643,8 +643,9 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||
return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat)
|
||||
}
|
||||
return action(BuildResult{
|
||||
Binary: tmppath,
|
||||
MainDir: lprogram.MainPkg().Dir,
|
||||
Binary: tmppath,
|
||||
MainDir: lprogram.MainPkg().Dir,
|
||||
ImportPath: lprogram.MainPkg().ImportPath,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,18 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if options.OpenOCDCommands != nil {
|
||||
// Override the OpenOCDCommands from the target spec if specified on
|
||||
// the command-line
|
||||
spec.OpenOCDCommands = options.OpenOCDCommands
|
||||
}
|
||||
|
||||
goroot := goenv.Get("GOROOT")
|
||||
if goroot == "" {
|
||||
return nil, errors.New("cannot locate $GOROOT, please set it manually")
|
||||
}
|
||||
|
||||
major, minor, err := goenv.GetGorootVersion(goroot)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
|
||||
@@ -28,7 +36,9 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
|
||||
if major != 1 || minor < 13 || minor > 16 {
|
||||
return nil, fmt.Errorf("requires go version 1.13 through 1.16, got go%d.%d", major, minor)
|
||||
}
|
||||
|
||||
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
|
||||
|
||||
return &compileopts.Config{
|
||||
Options: options,
|
||||
Target: spec,
|
||||
|
||||
@@ -80,12 +80,7 @@ func (c *Config) GC() string {
|
||||
if c.Target.GC != "" {
|
||||
return c.Target.GC
|
||||
}
|
||||
for _, tag := range c.Target.BuildTags {
|
||||
if tag == "baremetal" || tag == "wasm" {
|
||||
return "conservative"
|
||||
}
|
||||
}
|
||||
return "extalloc"
|
||||
return "conservative"
|
||||
}
|
||||
|
||||
// NeedsStackObjects returns true if the compiler should insert stack objects
|
||||
@@ -342,6 +337,10 @@ func (c *Config) WasmAbi() string {
|
||||
return c.Target.WasmAbi
|
||||
}
|
||||
|
||||
func (c *Config) LLVMFeatures() string {
|
||||
return c.Options.LLVMFeatures
|
||||
}
|
||||
|
||||
type TestConfig struct {
|
||||
CompileTestBinary bool
|
||||
// TODO: Filter the test functions to run, include verbose flag, etc
|
||||
|
||||
+20
-18
@@ -17,24 +17,26 @@ var (
|
||||
// Options contains extra options to give to the compiler. These options are
|
||||
// usually passed from the command line.
|
||||
type Options struct {
|
||||
Target string
|
||||
Opt string
|
||||
GC string
|
||||
PanicStrategy string
|
||||
Scheduler string
|
||||
PrintIR bool
|
||||
DumpSSA bool
|
||||
VerifyIR bool
|
||||
PrintCommands bool
|
||||
Debug bool
|
||||
PrintSizes string
|
||||
PrintAllocs *regexp.Regexp // regexp string
|
||||
PrintStacks bool
|
||||
Tags string
|
||||
WasmAbi string
|
||||
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
|
||||
TestConfig TestConfig
|
||||
Programmer string
|
||||
Target string
|
||||
Opt string
|
||||
GC string
|
||||
PanicStrategy string
|
||||
Scheduler string
|
||||
PrintIR bool
|
||||
DumpSSA bool
|
||||
VerifyIR bool
|
||||
PrintCommands bool
|
||||
Debug bool
|
||||
PrintSizes string
|
||||
PrintAllocs *regexp.Regexp // regexp string
|
||||
PrintStacks bool
|
||||
Tags string
|
||||
WasmAbi string
|
||||
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
|
||||
TestConfig TestConfig
|
||||
Programmer string
|
||||
OpenOCDCommands []string
|
||||
LLVMFeatures string
|
||||
}
|
||||
|
||||
// Verify performs a validation on the given options, raising an error if options are not valid.
|
||||
|
||||
+12
-8
@@ -239,22 +239,26 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
||||
// No target spec available. Use the default one, useful on most systems
|
||||
// with a regular OS.
|
||||
spec := TargetSpec{
|
||||
Triple: triple,
|
||||
GOOS: goos,
|
||||
GOARCH: goarch,
|
||||
BuildTags: []string{goos, goarch},
|
||||
Linker: "cc",
|
||||
CFlags: []string{"--target=" + triple},
|
||||
GDB: []string{"gdb"},
|
||||
PortReset: "false",
|
||||
Triple: triple,
|
||||
GOOS: goos,
|
||||
GOARCH: goarch,
|
||||
BuildTags: []string{goos, goarch},
|
||||
Scheduler: "tasks",
|
||||
Linker: "cc",
|
||||
DefaultStackSize: 1024 * 64, // 64kB
|
||||
CFlags: []string{"--target=" + triple},
|
||||
GDB: []string{"gdb"},
|
||||
PortReset: "false",
|
||||
}
|
||||
if goos == "darwin" {
|
||||
spec.CFlags = append(spec.CFlags, "-isysroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk")
|
||||
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
|
||||
} else {
|
||||
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
|
||||
}
|
||||
if goarch != "wasm" {
|
||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+".S")
|
||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+".S")
|
||||
}
|
||||
if goarch != runtime.GOARCH {
|
||||
// Some educated guesses as to how to invoke helper programs.
|
||||
|
||||
+56
-2
@@ -59,6 +59,7 @@ type Config struct {
|
||||
DefaultStackSize uint64
|
||||
NeedsStackObjects bool
|
||||
Debug bool // Whether to emit debug information in the LLVM module.
|
||||
LLVMFeatures string
|
||||
}
|
||||
|
||||
// compilerContext contains function-independent data that should still be
|
||||
@@ -185,7 +186,12 @@ func NewTargetMachine(config *Config) (llvm.TargetMachine, error) {
|
||||
if err != nil {
|
||||
return llvm.TargetMachine{}, err
|
||||
}
|
||||
features := strings.Join(config.Features, ",")
|
||||
|
||||
feat := config.Features
|
||||
if len(config.LLVMFeatures) > 0 {
|
||||
feat = append(feat, config.LLVMFeatures)
|
||||
}
|
||||
features := strings.Join(feat, ",")
|
||||
|
||||
var codeModel llvm.CodeModel
|
||||
var relocationModel llvm.RelocMode
|
||||
@@ -964,11 +970,59 @@ func (b *builder) createFunction() {
|
||||
}
|
||||
}
|
||||
|
||||
// posser is an interface that's implemented by both ssa.Value and
|
||||
// ssa.Instruction. It is implemented by everything that has a Pos() method,
|
||||
// which is all that getPos() needs.
|
||||
type posser interface {
|
||||
Pos() token.Pos
|
||||
}
|
||||
|
||||
// getPos returns position information for a ssa.Value or ssa.Instruction.
|
||||
//
|
||||
// Not all instructions have position information, especially when they're
|
||||
// implicit (such as implicit casts or implicit returns at the end of a
|
||||
// function). In these cases, it makes sense to try a bit harder to guess what
|
||||
// the position really should be.
|
||||
func getPos(val posser) token.Pos {
|
||||
pos := val.Pos()
|
||||
if pos != token.NoPos {
|
||||
// Easy: position is known.
|
||||
return pos
|
||||
}
|
||||
|
||||
// No position information is known.
|
||||
switch val := val.(type) {
|
||||
case *ssa.MakeInterface:
|
||||
return getPos(val.X)
|
||||
case *ssa.Return:
|
||||
syntax := val.Parent().Syntax()
|
||||
if syntax != nil {
|
||||
// non-synthetic
|
||||
return syntax.End()
|
||||
}
|
||||
return token.NoPos
|
||||
case *ssa.FieldAddr:
|
||||
return getPos(val.X)
|
||||
case *ssa.IndexAddr:
|
||||
return getPos(val.X)
|
||||
case *ssa.Slice:
|
||||
return getPos(val.X)
|
||||
case *ssa.Store:
|
||||
return getPos(val.Addr)
|
||||
case *ssa.Extract:
|
||||
return getPos(val.Tuple)
|
||||
default:
|
||||
// This is reachable, for example with *ssa.Const, *ssa.If, and
|
||||
// *ssa.Jump. They might be implemented in some way in the future.
|
||||
return token.NoPos
|
||||
}
|
||||
}
|
||||
|
||||
// createInstruction builds the LLVM IR equivalent instructions for the
|
||||
// particular Go SSA instruction.
|
||||
func (b *builder) createInstruction(instr ssa.Instruction) {
|
||||
if b.Debug {
|
||||
pos := b.program.Fset.Position(instr.Pos())
|
||||
pos := b.program.Fset.Position(getPos(instr))
|
||||
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestCompiler(t *testing.T) {
|
||||
t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version)
|
||||
}
|
||||
|
||||
target, err := compileopts.LoadTarget("i686--linux")
|
||||
target, err := compileopts.LoadTarget("wasm")
|
||||
if err != nil {
|
||||
t.Fatal("failed to load target:", err)
|
||||
}
|
||||
|
||||
@@ -34,6 +34,9 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p
|
||||
} else {
|
||||
// The stack size is fixed at compile time. By emitting it here as a
|
||||
// constant, it can be optimized.
|
||||
if b.DefaultStackSize == 0 {
|
||||
b.addError(pos, "default stack size for goroutines is not set")
|
||||
}
|
||||
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
|
||||
}
|
||||
case "coroutines":
|
||||
|
||||
Vendored
+2
-2
@@ -1,7 +1,7 @@
|
||||
; ModuleID = 'basic.go'
|
||||
source_filename = "basic.go"
|
||||
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
|
||||
target triple = "i686--linux"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -1,7 +1,7 @@
|
||||
; ModuleID = 'float.go'
|
||||
source_filename = "float.go"
|
||||
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
|
||||
target triple = "i686--linux"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -1,7 +1,7 @@
|
||||
; ModuleID = 'func.go'
|
||||
source_filename = "func.go"
|
||||
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
|
||||
target triple = "i686--linux"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
%runtime.funcValueWithSignature = type { i32, i8* }
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -1,7 +1,7 @@
|
||||
; ModuleID = 'interface.go'
|
||||
source_filename = "interface.go"
|
||||
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
|
||||
target triple = "i686--linux"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID* }
|
||||
%runtime.interfaceMethodInfo = type { i8*, i32 }
|
||||
|
||||
Vendored
+2
-2
@@ -1,7 +1,7 @@
|
||||
; ModuleID = 'pointer.go'
|
||||
source_filename = "pointer.go"
|
||||
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
|
||||
target triple = "i686--linux"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -1,7 +1,7 @@
|
||||
; ModuleID = 'slice.go'
|
||||
source_filename = "slice.go"
|
||||
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
|
||||
target triple = "i686--linux"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -1,7 +1,7 @@
|
||||
; ModuleID = 'string.go'
|
||||
source_filename = "string.go"
|
||||
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
|
||||
target triple = "i686--linux"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
%runtime._string = type { i8*, i32 }
|
||||
|
||||
|
||||
+22
-6
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -930,16 +931,31 @@ func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, me
|
||||
result = r.builder.CreateBitCast(operands[0], inst.llvmInst.Type(), inst.name)
|
||||
case llvm.ExtractValue:
|
||||
indices := inst.llvmInst.Indices()
|
||||
if len(indices) != 1 {
|
||||
panic("expected exactly one index")
|
||||
// Note: the Go LLVM API doesn't support multiple indices, so simulate
|
||||
// this operation with some extra extractvalue instructions. Hopefully
|
||||
// this is optimized to a single instruction.
|
||||
agg := operands[0]
|
||||
for i := 0; i < len(indices)-1; i++ {
|
||||
agg = r.builder.CreateExtractValue(agg, int(indices[i]), inst.name+".agg")
|
||||
}
|
||||
result = r.builder.CreateExtractValue(operands[0], int(indices[0]), inst.name)
|
||||
result = r.builder.CreateExtractValue(agg, int(indices[len(indices)-1]), inst.name)
|
||||
case llvm.InsertValue:
|
||||
indices := inst.llvmInst.Indices()
|
||||
if len(indices) != 1 {
|
||||
panic("expected exactly one index")
|
||||
// Similar to extractvalue, we're working around a limitation in the Go
|
||||
// LLVM API here by splitting the insertvalue into multiple instructions
|
||||
// if there is more than one operand.
|
||||
agg := operands[0]
|
||||
aggregates := []llvm.Value{agg}
|
||||
for i := 0; i < len(indices)-1; i++ {
|
||||
agg = r.builder.CreateExtractValue(agg, int(indices[i]), inst.name+".agg"+strconv.Itoa(i))
|
||||
aggregates = append(aggregates, agg)
|
||||
}
|
||||
result = r.builder.CreateInsertValue(operands[0], operands[1], int(indices[0]), inst.name)
|
||||
result = operands[1]
|
||||
for i := len(indices) - 1; i >= 0; i-- {
|
||||
agg := aggregates[i]
|
||||
result = r.builder.CreateInsertValue(agg, result, int(indices[i]), inst.name+".insertvalue"+strconv.Itoa(i))
|
||||
}
|
||||
|
||||
case llvm.Add:
|
||||
result = r.builder.CreateAdd(operands[0], operands[1], inst.name)
|
||||
case llvm.Sub:
|
||||
|
||||
Vendored
+10
@@ -8,6 +8,7 @@ target triple = "x86_64--linux"
|
||||
@main.exportedValue = global [1 x i16*] [i16* @main.exposedValue1]
|
||||
@main.exposedValue1 = global i16 0
|
||||
@main.exposedValue2 = global i16 0
|
||||
@main.insertedValue = global {i8, i32, {float, {i64, i16}}} zeroinitializer
|
||||
|
||||
declare void @runtime.printint64(i64) unnamed_addr
|
||||
|
||||
@@ -71,6 +72,13 @@ entry:
|
||||
call void @runtime.printint64(i64 %switch1)
|
||||
call void @runtime.printint64(i64 %switch2)
|
||||
|
||||
; Test extractvalue/insertvalue with multiple operands.
|
||||
%agg = call {i8, i32, {float, {i64, i16}}} @nestedStruct()
|
||||
%elt = extractvalue {i8, i32, {float, {i64, i16}}} %agg, 2, 1, 0
|
||||
call void @runtime.printint64(i64 %elt)
|
||||
%agg2 = insertvalue {i8, i32, {float, {i64, i16}}} %agg, i64 5, 2, 1, 0
|
||||
store {i8, i32, {float, {i64, i16}}} %agg2, {i8, i32, {float, {i64, i16}}}* @main.insertedValue
|
||||
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -112,3 +120,5 @@ two:
|
||||
otherwise:
|
||||
ret i64 -1
|
||||
}
|
||||
|
||||
declare {i8, i32, {float, {i64, i16}}} @nestedStruct()
|
||||
|
||||
Vendored
+14
@@ -7,6 +7,7 @@ target triple = "x86_64--linux"
|
||||
@main.exportedValue = global [1 x i16*] [i16* @main.exposedValue1]
|
||||
@main.exposedValue1 = global i16 0
|
||||
@main.exposedValue2 = local_unnamed_addr global i16 0
|
||||
@main.insertedValue = local_unnamed_addr global { i8, i32, { float, { i64, i16 } } } zeroinitializer
|
||||
|
||||
declare void @runtime.printint64(i64) unnamed_addr
|
||||
|
||||
@@ -27,6 +28,17 @@ entry:
|
||||
store i16 7, i16* @main.exposedValue2
|
||||
call void @runtime.printint64(i64 6)
|
||||
call void @runtime.printint64(i64 -1)
|
||||
%agg = call { i8, i32, { float, { i64, i16 } } } @nestedStruct()
|
||||
%elt.agg = extractvalue { i8, i32, { float, { i64, i16 } } } %agg, 2
|
||||
%elt.agg1 = extractvalue { float, { i64, i16 } } %elt.agg, 1
|
||||
%elt = extractvalue { i64, i16 } %elt.agg1, 0
|
||||
call void @runtime.printint64(i64 %elt)
|
||||
%agg2.agg0 = extractvalue { i8, i32, { float, { i64, i16 } } } %agg, 2
|
||||
%agg2.agg1 = extractvalue { float, { i64, i16 } } %agg2.agg0, 1
|
||||
%agg2.insertvalue2 = insertvalue { i64, i16 } %agg2.agg1, i64 5, 0
|
||||
%agg2.insertvalue1 = insertvalue { float, { i64, i16 } } %agg2.agg0, { i64, i16 } %agg2.insertvalue2, 1
|
||||
%agg2.insertvalue0 = insertvalue { i8, i32, { float, { i64, i16 } } } %agg, { float, { i64, i16 } } %agg2.insertvalue1, 2
|
||||
store { i8, i32, { float, { i64, i16 } } } %agg2.insertvalue0, { i8, i32, { float, { i64, i16 } } }* @main.insertedValue
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -67,3 +79,5 @@ two: ; preds = %entry
|
||||
otherwise: ; preds = %entry
|
||||
ret i64 -1
|
||||
}
|
||||
|
||||
declare { i8, i32, { float, { i64, i16 } } } @nestedStruct() local_unnamed_addr
|
||||
|
||||
@@ -23,3 +23,13 @@ type Error struct {
|
||||
func (e Error) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// Error returned when loading a *Program for a test binary but no test files
|
||||
// are present.
|
||||
type NoTestFilesError struct {
|
||||
ImportPath string
|
||||
}
|
||||
|
||||
func (e NoTestFilesError) Error() string {
|
||||
return "no test files"
|
||||
}
|
||||
|
||||
@@ -210,6 +210,12 @@ func Load(config *compileopts.Config, inputPkgs []string, clangHeaders string, t
|
||||
p.Packages[pkg.ImportPath] = pkg
|
||||
}
|
||||
|
||||
if config.TestConfig.CompileTestBinary && !strings.HasSuffix(p.sorted[len(p.sorted)-1].ImportPath, ".test") {
|
||||
// Trying to compile a test binary but there are no test files in this
|
||||
// package.
|
||||
return p, NoTestFilesError{p.sorted[len(p.sorted)-1].ImportPath}
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -136,15 +136,17 @@ func Build(pkgName, outpath string, options *compileopts.Options) error {
|
||||
})
|
||||
}
|
||||
|
||||
// Test runs the tests in the given package.
|
||||
func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, outpath string) error {
|
||||
// Test runs the tests in the given package. Returns whether the test passed and
|
||||
// possibly an error if the test failed to run.
|
||||
func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, outpath string) (bool, error) {
|
||||
options.TestConfig.CompileTestBinary = true
|
||||
config, err := builder.NewConfig(options)
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
|
||||
return builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error {
|
||||
var passed bool
|
||||
err = builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error {
|
||||
if testCompileOnly || outpath != "" {
|
||||
// Write test binary to the specified file name.
|
||||
if outpath == "" {
|
||||
@@ -158,48 +160,78 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, ou
|
||||
// Do not run the test.
|
||||
return nil
|
||||
}
|
||||
if len(config.Target.Emulator) == 0 {
|
||||
// Run directly.
|
||||
cmd := executeCommand(config.Options, result.Binary)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = result.MainDir
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
// Propagate the exit code
|
||||
if err, ok := err.(*exec.ExitError); ok {
|
||||
os.Exit(err.ExitCode())
|
||||
}
|
||||
return &commandError{"failed to run compiled binary", result.Binary, err}
|
||||
}
|
||||
return nil
|
||||
|
||||
// Run the test.
|
||||
start := time.Now()
|
||||
var err error
|
||||
passed, err = runPackageTest(config, result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
duration := time.Since(start)
|
||||
|
||||
// Print the result.
|
||||
importPath := strings.TrimSuffix(result.ImportPath, ".test")
|
||||
if passed {
|
||||
fmt.Printf("ok \t%s\t%.3fs\n", importPath, duration.Seconds())
|
||||
} else {
|
||||
// Run in an emulator.
|
||||
args := append(config.Target.Emulator[1:], result.Binary)
|
||||
cmd := executeCommand(config.Options, config.Target.Emulator[0], args...)
|
||||
buf := &bytes.Buffer{}
|
||||
w := io.MultiWriter(os.Stdout, buf)
|
||||
cmd.Stdout = w
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
if err, ok := err.(*exec.ExitError); !ok || !err.Exited() {
|
||||
// Workaround for QEMU which always exits with an error.
|
||||
return &commandError{"failed to run emulator with", result.Binary, err}
|
||||
}
|
||||
fmt.Printf("FAIL\t%s\t%.3fs\n", importPath, duration.Seconds())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err, ok := err.(loader.NoTestFilesError); ok {
|
||||
fmt.Printf("? \t%s\t[no test files]\n", err.ImportPath)
|
||||
// Pretend the test passed - it at least didn't fail.
|
||||
return true, nil
|
||||
}
|
||||
return passed, err
|
||||
}
|
||||
|
||||
// runPackageTest runs a test binary that was previously built. The return
|
||||
// values are whether the test passed and any errors encountered while trying to
|
||||
// run the binary.
|
||||
func runPackageTest(config *compileopts.Config, result builder.BuildResult) (bool, error) {
|
||||
if len(config.Target.Emulator) == 0 {
|
||||
// Run directly.
|
||||
cmd := executeCommand(config.Options, result.Binary)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = result.MainDir
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
if _, ok := err.(*exec.ExitError); ok {
|
||||
// Binary exited with a non-zero exit code, which means the test
|
||||
// failed.
|
||||
return false, nil
|
||||
}
|
||||
testOutput := string(buf.Bytes())
|
||||
if testOutput == "PASS\n" || strings.HasSuffix(testOutput, "\nPASS\n") {
|
||||
// Test passed.
|
||||
return nil
|
||||
} else {
|
||||
// Test failed, either by ending with the word "FAIL" or with a
|
||||
// panic of some sort.
|
||||
os.Exit(1)
|
||||
return nil // unreachable
|
||||
return false, &commandError{"failed to run compiled binary", result.Binary, err}
|
||||
}
|
||||
return true, nil
|
||||
} else {
|
||||
// Run in an emulator.
|
||||
args := append(config.Target.Emulator[1:], result.Binary)
|
||||
cmd := executeCommand(config.Options, config.Target.Emulator[0], args...)
|
||||
buf := &bytes.Buffer{}
|
||||
w := io.MultiWriter(os.Stdout, buf)
|
||||
cmd.Stdout = w
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
if err, ok := err.(*exec.ExitError); !ok || !err.Exited() {
|
||||
// Workaround for QEMU which always exits with an error.
|
||||
return false, &commandError{"failed to run emulator with", result.Binary, err}
|
||||
}
|
||||
}
|
||||
})
|
||||
testOutput := string(buf.Bytes())
|
||||
if testOutput == "PASS\n" || strings.HasSuffix(testOutput, "\nPASS\n") {
|
||||
// Test passed.
|
||||
return true, nil
|
||||
} else {
|
||||
// Test failed, either by ending with the word "FAIL" or with a
|
||||
// panic of some sort.
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Flash builds and flashes the built binary to the given serial port.
|
||||
@@ -905,11 +937,13 @@ func main() {
|
||||
printAllocsString := flag.String("print-allocs", "", "regular expression of functions for which heap allocations should be printed")
|
||||
printCommands := flag.Bool("x", false, "Print commands")
|
||||
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
|
||||
ocdCommandsString := flag.String("ocd-commands", "", "OpenOCD commands, overriding target spec (can specify multiple separated by commas)")
|
||||
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
|
||||
port := flag.String("port", "", "flash port (can specify multiple candidates separated by commas)")
|
||||
programmer := flag.String("programmer", "", "which hardware programmer to use")
|
||||
ldflags := flag.String("ldflags", "", "Go link tool compatible ldflags")
|
||||
wasmAbi := flag.String("wasm-abi", "", "WebAssembly ABI conventions: js (no i64 params) or generic")
|
||||
llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable")
|
||||
|
||||
var flagJSON, flagDeps *bool
|
||||
if command == "help" || command == "list" {
|
||||
@@ -943,6 +977,7 @@ func main() {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var printAllocs *regexp.Regexp
|
||||
if *printAllocsString != "" {
|
||||
printAllocs, err = regexp.Compile(*printAllocsString)
|
||||
@@ -951,24 +986,32 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
var ocdCommands []string
|
||||
if *ocdCommandsString != "" {
|
||||
ocdCommands = strings.Split(*ocdCommandsString, ",")
|
||||
}
|
||||
|
||||
options := &compileopts.Options{
|
||||
Target: *target,
|
||||
Opt: *opt,
|
||||
GC: *gc,
|
||||
PanicStrategy: *panicStrategy,
|
||||
Scheduler: *scheduler,
|
||||
PrintIR: *printIR,
|
||||
DumpSSA: *dumpSSA,
|
||||
VerifyIR: *verifyIR,
|
||||
Debug: !*nodebug,
|
||||
PrintSizes: *printSize,
|
||||
PrintStacks: *printStacks,
|
||||
PrintAllocs: printAllocs,
|
||||
PrintCommands: *printCommands,
|
||||
Tags: *tags,
|
||||
GlobalValues: globalVarValues,
|
||||
WasmAbi: *wasmAbi,
|
||||
Programmer: *programmer,
|
||||
Target: *target,
|
||||
Opt: *opt,
|
||||
GC: *gc,
|
||||
PanicStrategy: *panicStrategy,
|
||||
Scheduler: *scheduler,
|
||||
PrintIR: *printIR,
|
||||
DumpSSA: *dumpSSA,
|
||||
VerifyIR: *verifyIR,
|
||||
Debug: !*nodebug,
|
||||
PrintSizes: *printSize,
|
||||
PrintStacks: *printStacks,
|
||||
PrintAllocs: printAllocs,
|
||||
PrintCommands: *printCommands,
|
||||
Tags: *tags,
|
||||
GlobalValues: globalVarValues,
|
||||
WasmAbi: *wasmAbi,
|
||||
Programmer: *programmer,
|
||||
OpenOCDCommands: ocdCommands,
|
||||
LLVMFeatures: *llvmFeatures,
|
||||
}
|
||||
|
||||
os.Setenv("CC", "clang -target="+*target)
|
||||
@@ -1061,16 +1104,26 @@ func main() {
|
||||
err := Run(pkgName, options)
|
||||
handleCompilerError(err)
|
||||
case "test":
|
||||
pkgName := "."
|
||||
if flag.NArg() == 1 {
|
||||
pkgName = filepath.ToSlash(flag.Arg(0))
|
||||
} else if flag.NArg() > 1 {
|
||||
fmt.Fprintln(os.Stderr, "test only accepts a single positional argument: package name, but multiple were specified")
|
||||
usage()
|
||||
var pkgNames []string
|
||||
for i := 0; i < flag.NArg(); i++ {
|
||||
pkgNames = append(pkgNames, filepath.ToSlash(flag.Arg(i)))
|
||||
}
|
||||
if len(pkgNames) == 0 {
|
||||
pkgNames = []string{"."}
|
||||
}
|
||||
allTestsPassed := true
|
||||
for _, pkgName := range pkgNames {
|
||||
// TODO: parallelize building the test binaries
|
||||
passed, err := Test(pkgName, options, *testCompileOnlyFlag, outpath)
|
||||
handleCompilerError(err)
|
||||
if !passed {
|
||||
allTestsPassed = false
|
||||
}
|
||||
}
|
||||
if !allTestsPassed {
|
||||
fmt.Println("FAIL")
|
||||
os.Exit(1)
|
||||
}
|
||||
err := Test(pkgName, options, *testCompileOnlyFlag, outpath)
|
||||
handleCompilerError(err)
|
||||
case "targets":
|
||||
dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets")
|
||||
entries, err := ioutil.ReadDir(dir)
|
||||
|
||||
+15
-6
@@ -26,12 +26,21 @@ type SCB_Type struct {
|
||||
SHPR2 volatile.Register32 // 0xD1C: System Handler Priority Register 2
|
||||
SHPR3 volatile.Register32 // 0xD20: System Handler Priority Register 3
|
||||
// the following are only applicable for Cortex-M3/M33/M4/M7
|
||||
SHCSR volatile.Register32 // 0xD24: System Handler Control and State Register
|
||||
CFSR volatile.Register32 // 0xD28: Configurable Fault Status Register
|
||||
HFSR volatile.Register32 // 0xD2C: HardFault Status Register
|
||||
DFSR volatile.Register32 // 0xD30: Debug Fault Status Register
|
||||
MMFAR volatile.Register32 // 0xD34: MemManage Fault Address Register
|
||||
BFAR volatile.Register32 // 0xD38: BusFault Address Register
|
||||
SHCSR volatile.Register32 // 0xD24: System Handler Control and State Register
|
||||
CFSR volatile.Register32 // 0xD28: Configurable Fault Status Register
|
||||
HFSR volatile.Register32 // 0xD2C: HardFault Status Register
|
||||
DFSR volatile.Register32 // 0xD30: Debug Fault Status Register
|
||||
MMFAR volatile.Register32 // 0xD34: MemManage Fault Address Register
|
||||
BFAR volatile.Register32 // 0xD38: BusFault Address Register
|
||||
AFSR volatile.Register32 // 0xD3C: Auxiliary Fault Status Register
|
||||
PFR [2]volatile.Register32 // 0xD40: Processor Feature Register
|
||||
DFR volatile.Register32 // 0xD48: Debug Feature Register
|
||||
ADR volatile.Register32 // 0xD4C: Auxiliary Feature Register
|
||||
MMFR [4]volatile.Register32 // 0xD50: Memory Model Feature Register
|
||||
ISAR [5]volatile.Register32 // 0xD60: Instruction Set Attributes Register
|
||||
_ [5]uint32 // reserved
|
||||
CPACR volatile.Register32 // 0xD88: Coprocessor Access Control Register
|
||||
|
||||
}
|
||||
|
||||
var SCB = (*SCB_Type)(unsafe.Pointer(uintptr(SCB_BASE)))
|
||||
|
||||
@@ -14,7 +14,7 @@ func main() {
|
||||
led := machine.LED
|
||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
sensor := machine.ADC{machine.ADC2}
|
||||
sensor := machine.ADC{Pin: machine.ADC2}
|
||||
sensor.Configure(machine.ADCConfig{})
|
||||
|
||||
for {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
.section .text.tinygo_startTask
|
||||
.global tinygo_startTask
|
||||
.type tinygo_startTask, %function
|
||||
tinygo_startTask:
|
||||
.cfi_startproc
|
||||
// Small assembly stub for starting a goroutine. This is already run on the
|
||||
// new stack, with the callee-saved registers already loaded.
|
||||
// Most importantly, EBX contain the pc of the to-be-started function and
|
||||
// ESI contain the only argument it is given. Multiple arguments are packed
|
||||
// into one by storing them in a new allocation.
|
||||
|
||||
// Indicate to the unwinder that there is nothing to unwind, this is the
|
||||
// root frame. It avoids bogus extra frames in GDB.
|
||||
.cfi_undefined eip
|
||||
|
||||
// Set the first argument of the goroutine start wrapper, which contains all
|
||||
// the arguments.
|
||||
pushl %esi
|
||||
|
||||
// Branch to the "goroutine start" function.
|
||||
calll *%ebx
|
||||
|
||||
// Rebalance the stack (to undo the above push).
|
||||
addl $4, %esp
|
||||
|
||||
// After return, exit this goroutine. This is a tail call.
|
||||
jmp tinygo_pause
|
||||
.cfi_endproc
|
||||
|
||||
.global tinygo_swapTask
|
||||
.type tinygo_swapTask, %function
|
||||
tinygo_swapTask:
|
||||
// This function gets the following parameters:
|
||||
movl 4(%esp), %eax // newStack uintptr
|
||||
movl 8(%esp), %ecx // oldStack *uintptr
|
||||
// More information on the calling convention:
|
||||
// https://wiki.osdev.org/System_V_ABI#i386
|
||||
|
||||
// Save all callee-saved registers:
|
||||
pushl %ebp
|
||||
pushl %edi
|
||||
pushl %esi
|
||||
pushl %ebx
|
||||
|
||||
// Save the current stack pointer in oldStack.
|
||||
movl %esp, (%ecx)
|
||||
|
||||
// Switch to the new stack pointer.
|
||||
movl %eax, %esp
|
||||
|
||||
// Load saved register from the new stack.
|
||||
popl %ebx
|
||||
popl %esi
|
||||
popl %edi
|
||||
popl %ebp
|
||||
|
||||
// Return into the new task, as if tinygo_swapTask was a regular call.
|
||||
ret
|
||||
@@ -0,0 +1,59 @@
|
||||
// +build scheduler.tasks,386
|
||||
|
||||
package task
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var systemStack uintptr
|
||||
|
||||
// calleeSavedRegs is the list of registers that must be saved and restored when
|
||||
// switching between tasks. Also see task_stack_386.S that relies on the exact
|
||||
// layout of this struct.
|
||||
type calleeSavedRegs struct {
|
||||
ebx uintptr
|
||||
esi uintptr
|
||||
edi uintptr
|
||||
ebp uintptr
|
||||
|
||||
pc uintptr
|
||||
}
|
||||
|
||||
// archInit runs architecture-specific setup for the goroutine startup.
|
||||
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
|
||||
// Store the initial sp for the startTask function (implemented in assembly).
|
||||
s.sp = uintptr(unsafe.Pointer(r))
|
||||
|
||||
// Initialize the registers.
|
||||
// These will be popped off of the stack on the first resume of the goroutine.
|
||||
|
||||
// Start the function at tinygo_startTask (defined in
|
||||
// src/internal/task/task_stack_386.S). This assembly code calls a function
|
||||
// (passed in EBX) with a single argument (passed in ESI). After the
|
||||
// function returns, it calls Pause().
|
||||
r.pc = uintptr(unsafe.Pointer(&startTask))
|
||||
|
||||
// Pass the function to call in EBX.
|
||||
// This function is a compiler-generated wrapper which loads arguments out
|
||||
// of a struct pointer. See createGoroutineStartWrapper (defined in
|
||||
// compiler/goroutine.go) for more information.
|
||||
r.ebx = fn
|
||||
|
||||
// Pass the pointer to the arguments struct in ESI.
|
||||
r.esi = uintptr(args)
|
||||
}
|
||||
|
||||
func (s *state) resume() {
|
||||
swapTask(s.sp, &systemStack)
|
||||
}
|
||||
|
||||
func (s *state) pause() {
|
||||
newStack := systemStack
|
||||
systemStack = 0
|
||||
swapTask(newStack, &s.sp)
|
||||
}
|
||||
|
||||
// SystemStack returns the system stack pointer when called from a task stack.
|
||||
// When called from the system stack, it returns 0.
|
||||
func SystemStack() uintptr {
|
||||
return systemStack
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#ifdef __MACH__ // Darwin
|
||||
.global _tinygo_startTask
|
||||
_tinygo_startTask:
|
||||
#else // Linux etc
|
||||
.section .text.tinygo_startTask
|
||||
.global tinygo_startTask
|
||||
tinygo_startTask:
|
||||
#endif
|
||||
.cfi_startproc
|
||||
// Small assembly stub for starting a goroutine. This is already run on the
|
||||
// new stack, with the callee-saved registers already loaded.
|
||||
// Most importantly, r12 contain the pc of the to-be-started function and
|
||||
// r13 contain the only argument it is given. Multiple arguments are packed
|
||||
// into one by storing them in a new allocation.
|
||||
|
||||
// Indicate to the unwinder that there is nothing to unwind, this is the
|
||||
// root frame. It avoids bogus extra frames in GDB like here:
|
||||
// #10 0x00000000004277b6 in <goroutine wrapper> () at [...]
|
||||
// #11 0x00000000004278f3 in tinygo_startTask () at [...]
|
||||
// #12 0x0000000000002030 in ?? ()
|
||||
// #13 0x0000000000000071 in ?? ()
|
||||
.cfi_undefined rip
|
||||
|
||||
// Set the first argument of the goroutine start wrapper, which contains all
|
||||
// the arguments.
|
||||
movq %r13, %rdi
|
||||
|
||||
// Branch to the "goroutine start" function.
|
||||
callq *%r12
|
||||
|
||||
// After return, exit this goroutine. This is a tail call.
|
||||
#ifdef __MACH__
|
||||
jmp _tinygo_pause
|
||||
#else
|
||||
jmp tinygo_pause
|
||||
#endif
|
||||
.cfi_endproc
|
||||
|
||||
#ifdef __MACH__ // Darwin
|
||||
.global _tinygo_swapTask
|
||||
_tinygo_swapTask:
|
||||
#else // Linux etc
|
||||
.global tinygo_swapTask
|
||||
.section .text.tinygo_swapTask
|
||||
tinygo_swapTask:
|
||||
#endif
|
||||
// This function gets the following parameters:
|
||||
// %rdi = newStack uintptr
|
||||
// %rsi = oldStack *uintptr
|
||||
|
||||
// Save all callee-saved registers:
|
||||
pushq %r15
|
||||
pushq %r14
|
||||
pushq %r13
|
||||
pushq %r12
|
||||
pushq %rbp
|
||||
pushq %rbx
|
||||
|
||||
// Save the current stack pointer in oldStack.
|
||||
movq %rsp, (%rsi)
|
||||
|
||||
// Switch to the new stack pointer.
|
||||
movq %rdi, %rsp
|
||||
|
||||
// Load saved register from the new stack.
|
||||
popq %rbx
|
||||
popq %rbp
|
||||
popq %r12
|
||||
popq %r13
|
||||
popq %r14
|
||||
popq %r15
|
||||
|
||||
// Return into the new task, as if tinygo_swapTask was a regular call.
|
||||
ret
|
||||
@@ -0,0 +1,61 @@
|
||||
// +build scheduler.tasks,amd64
|
||||
|
||||
package task
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var systemStack uintptr
|
||||
|
||||
// calleeSavedRegs is the list of registers that must be saved and restored when
|
||||
// switching between tasks. Also see task_stack_amd64.S that relies on the exact
|
||||
// layout of this struct.
|
||||
type calleeSavedRegs struct {
|
||||
rbx uintptr
|
||||
rbp uintptr
|
||||
r12 uintptr
|
||||
r13 uintptr
|
||||
r14 uintptr
|
||||
r15 uintptr
|
||||
|
||||
pc uintptr
|
||||
}
|
||||
|
||||
// archInit runs architecture-specific setup for the goroutine startup.
|
||||
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
|
||||
// Store the initial sp for the startTask function (implemented in assembly).
|
||||
s.sp = uintptr(unsafe.Pointer(r))
|
||||
|
||||
// Initialize the registers.
|
||||
// These will be popped off of the stack on the first resume of the goroutine.
|
||||
|
||||
// Start the function at tinygo_startTask (defined in
|
||||
// src/internal/task/task_stack_amd64.S). This assembly code calls a
|
||||
// function (passed in r12) with a single argument (passed in r13). After
|
||||
// the function returns, it calls Pause().
|
||||
r.pc = uintptr(unsafe.Pointer(&startTask))
|
||||
|
||||
// Pass the function to call in r12.
|
||||
// This function is a compiler-generated wrapper which loads arguments out
|
||||
// of a struct pointer. See createGoroutineStartWrapper (defined in
|
||||
// compiler/goroutine.go) for more information.
|
||||
r.r12 = fn
|
||||
|
||||
// Pass the pointer to the arguments struct in r13.
|
||||
r.r13 = uintptr(args)
|
||||
}
|
||||
|
||||
func (s *state) resume() {
|
||||
swapTask(s.sp, &systemStack)
|
||||
}
|
||||
|
||||
func (s *state) pause() {
|
||||
newStack := systemStack
|
||||
systemStack = 0
|
||||
swapTask(newStack, &s.sp)
|
||||
}
|
||||
|
||||
// SystemStack returns the system stack pointer when called from a task stack.
|
||||
// When called from the system stack, it returns 0.
|
||||
func SystemStack() uintptr {
|
||||
return systemStack
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Only generate .debug_frame, don't generate .eh_frame.
|
||||
.cfi_sections .debug_frame
|
||||
|
||||
.section .text.tinygo_startTask
|
||||
.global tinygo_startTask
|
||||
.type tinygo_startTask, %function
|
||||
tinygo_startTask:
|
||||
.cfi_startproc
|
||||
// Small assembly stub for starting a goroutine. This is already run on the
|
||||
// new stack, with the callee-saved registers already loaded.
|
||||
// Most importantly, r4 contains the pc of the to-be-started function and r5
|
||||
// contains the only argument it is given. Multiple arguments are packed
|
||||
// into one by storing them in a new allocation.
|
||||
|
||||
// Indicate to the unwinder that there is nothing to unwind, this is the
|
||||
// root frame. It avoids the following (bogus) error message in GDB:
|
||||
// Backtrace stopped: previous frame identical to this frame (corrupt stack?)
|
||||
.cfi_undefined lr
|
||||
|
||||
// Set the first argument of the goroutine start wrapper, which contains all
|
||||
// the arguments.
|
||||
mov r0, r5
|
||||
|
||||
// Branch to the "goroutine start" function. By using blx instead of bx,
|
||||
// we'll return here instead of tail calling.
|
||||
blx r4
|
||||
|
||||
// After return, exit this goroutine. This is a tail call.
|
||||
bl tinygo_pause
|
||||
.cfi_endproc
|
||||
.size tinygo_startTask, .-tinygo_startTask
|
||||
|
||||
.global tinygo_swapTask
|
||||
.type tinygo_swapTask, %function
|
||||
tinygo_swapTask:
|
||||
// This function gets the following parameters:
|
||||
// r0 = newStack uintptr
|
||||
// r1 = oldStack *uintptr
|
||||
|
||||
// Save all callee-saved registers:
|
||||
push {r4-r11, lr}
|
||||
|
||||
// Save the current stack pointer in oldStack.
|
||||
str sp, [r1]
|
||||
|
||||
// Switch to the new stack pointer.
|
||||
mov sp, r0
|
||||
|
||||
// Load state from new task and branch to the previous position in the
|
||||
// program.
|
||||
pop {r4-r11, pc}
|
||||
@@ -0,0 +1,61 @@
|
||||
// +build scheduler.tasks,arm,!cortexm,!avr,!xtensa
|
||||
|
||||
package task
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var systemStack uintptr
|
||||
|
||||
// calleeSavedRegs is the list of registers that must be saved and restored when
|
||||
// switching between tasks. Also see task_stack_arm.S that relies on the exact
|
||||
// layout of this struct.
|
||||
type calleeSavedRegs struct {
|
||||
r4 uintptr
|
||||
r5 uintptr
|
||||
r6 uintptr
|
||||
r7 uintptr
|
||||
r8 uintptr
|
||||
r9 uintptr
|
||||
r10 uintptr
|
||||
r11 uintptr
|
||||
|
||||
pc uintptr
|
||||
}
|
||||
|
||||
// archInit runs architecture-specific setup for the goroutine startup.
|
||||
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
|
||||
// Store the initial sp for the startTask function (implemented in assembly).
|
||||
s.sp = uintptr(unsafe.Pointer(r))
|
||||
|
||||
// Initialize the registers.
|
||||
// These will be popped off of the stack on the first resume of the goroutine.
|
||||
|
||||
// Start the function at tinygo_startTask (defined in src/internal/task/task_stack_arm.S).
|
||||
// This assembly code calls a function (passed in r4) with a single argument
|
||||
// (passed in r5). After the function returns, it calls Pause().
|
||||
r.pc = uintptr(unsafe.Pointer(&startTask))
|
||||
|
||||
// Pass the function to call in r4.
|
||||
// This function is a compiler-generated wrapper which loads arguments out of a struct pointer.
|
||||
// See createGoroutineStartWrapper (defined in compiler/goroutine.go) for more information.
|
||||
r.r4 = fn
|
||||
|
||||
// Pass the pointer to the arguments struct in r5.
|
||||
r.r5 = uintptr(args)
|
||||
}
|
||||
|
||||
func (s *state) resume() {
|
||||
swapTask(s.sp, &systemStack)
|
||||
}
|
||||
|
||||
func (s *state) pause() {
|
||||
newStack := systemStack
|
||||
systemStack = 0
|
||||
swapTask(newStack, &s.sp)
|
||||
}
|
||||
|
||||
// SystemStack returns the system stack pointer when called from a task stack.
|
||||
// When called from the system stack, it returns 0.
|
||||
func SystemStack() uintptr {
|
||||
return systemStack
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
.section .text.tinygo_startTask
|
||||
.global tinygo_startTask
|
||||
.type tinygo_startTask, %function
|
||||
tinygo_startTask:
|
||||
.cfi_startproc
|
||||
// Small assembly stub for starting a goroutine. This is already run on the
|
||||
// new stack, with the callee-saved registers already loaded.
|
||||
// Most importantly, x19 contains the pc of the to-be-started function and
|
||||
// x20 contains the only argument it is given. Multiple arguments are packed
|
||||
// into one by storing them in a new allocation.
|
||||
|
||||
// Indicate to the unwinder that there is nothing to unwind, this is the
|
||||
// root frame. It avoids the following (bogus) error message in GDB:
|
||||
// Backtrace stopped: previous frame identical to this frame (corrupt stack?)
|
||||
.cfi_undefined lr
|
||||
|
||||
// Set the first argument of the goroutine start wrapper, which contains all
|
||||
// the arguments.
|
||||
mov x0, x20
|
||||
|
||||
// Branch to the "goroutine start" function. By using blx instead of bx,
|
||||
// we'll return here instead of tail calling.
|
||||
blr x19
|
||||
|
||||
// After return, exit this goroutine. This is a tail call.
|
||||
b tinygo_pause
|
||||
.cfi_endproc
|
||||
.size tinygo_startTask, .-tinygo_startTask
|
||||
|
||||
.global tinygo_swapTask
|
||||
.type tinygo_swapTask, %function
|
||||
tinygo_swapTask:
|
||||
// This function gets the following parameters:
|
||||
// x0 = newStack uintptr
|
||||
// x1 = oldStack *uintptr
|
||||
|
||||
// Save all callee-saved registers:
|
||||
stp x19, x20, [sp, #-96]!
|
||||
stp x21, x22, [sp, #16]
|
||||
stp x23, x24, [sp, #32]
|
||||
stp x25, x26, [sp, #48]
|
||||
stp x27, x28, [sp, #64]
|
||||
stp x29, x30, [sp, #80]
|
||||
|
||||
// Save the current stack pointer in oldStack.
|
||||
mov x8, sp
|
||||
str x8, [x1]
|
||||
|
||||
// Switch to the new stack pointer.
|
||||
mov sp, x0
|
||||
|
||||
// Restore stack state and return.
|
||||
ldp x29, x30, [sp, #80]
|
||||
ldp x27, x28, [sp, #64]
|
||||
ldp x25, x26, [sp, #48]
|
||||
ldp x23, x24, [sp, #32]
|
||||
ldp x21, x22, [sp, #16]
|
||||
ldp x19, x20, [sp], #96
|
||||
ret
|
||||
@@ -0,0 +1,64 @@
|
||||
// +build scheduler.tasks,arm64
|
||||
|
||||
package task
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var systemStack uintptr
|
||||
|
||||
// calleeSavedRegs is the list of registers that must be saved and restored when
|
||||
// switching between tasks. Also see task_stack_arm64.S that relies on the exact
|
||||
// layout of this struct.
|
||||
type calleeSavedRegs struct {
|
||||
x19 uintptr
|
||||
x20 uintptr
|
||||
x21 uintptr
|
||||
x22 uintptr
|
||||
x23 uintptr
|
||||
x24 uintptr
|
||||
x25 uintptr
|
||||
x26 uintptr
|
||||
x27 uintptr
|
||||
x28 uintptr
|
||||
x29 uintptr
|
||||
|
||||
pc uintptr // aka x30 aka LR
|
||||
}
|
||||
|
||||
// archInit runs architecture-specific setup for the goroutine startup.
|
||||
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
|
||||
// Store the initial sp for the startTask function (implemented in assembly).
|
||||
s.sp = uintptr(unsafe.Pointer(r))
|
||||
|
||||
// Initialize the registers.
|
||||
// These will be popped off of the stack on the first resume of the goroutine.
|
||||
|
||||
// Start the function at tinygo_startTask (defined in src/internal/task/task_stack_arm64.S).
|
||||
// This assembly code calls a function (passed in x19) with a single argument
|
||||
// (passed in x20). After the function returns, it calls Pause().
|
||||
r.pc = uintptr(unsafe.Pointer(&startTask))
|
||||
|
||||
// Pass the function to call in x19.
|
||||
// This function is a compiler-generated wrapper which loads arguments out of a struct pointer.
|
||||
// See createGoroutineStartWrapper (defined in compiler/goroutine.go) for more information.
|
||||
r.x19 = fn
|
||||
|
||||
// Pass the pointer to the arguments struct in x20.
|
||||
r.x20 = uintptr(args)
|
||||
}
|
||||
|
||||
func (s *state) resume() {
|
||||
swapTask(s.sp, &systemStack)
|
||||
}
|
||||
|
||||
func (s *state) pause() {
|
||||
newStack := systemStack
|
||||
systemStack = 0
|
||||
swapTask(newStack, &s.sp)
|
||||
}
|
||||
|
||||
// SystemStack returns the system stack pointer when called from a task stack.
|
||||
// When called from the system stack, it returns 0.
|
||||
func SystemStack() uintptr {
|
||||
return systemStack
|
||||
}
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
package task
|
||||
|
||||
// Note that this is almost the same as task_stack_arm.go, but it uses the MSP
|
||||
// register to store the system stack pointer instead of a global variable. The
|
||||
// big advantage of this is that interrupts always execute with MSP (and not
|
||||
// PSP, which is used for goroutines) so that goroutines do not need extra stack
|
||||
// space for interrupts.
|
||||
|
||||
import (
|
||||
"device/arm"
|
||||
"unsafe"
|
||||
|
||||
@@ -9,4 +9,11 @@ type ADCConfig struct {
|
||||
Reference uint32 // analog reference voltage (AREF) in millivolts
|
||||
Resolution uint32 // number of bits for a single conversion (e.g., 8, 10, 12)
|
||||
Samples uint32 // number of samples for a single conversion (e.g., 4, 8, 16, 32)
|
||||
Bus uint8 // bus Number of ADC
|
||||
}
|
||||
|
||||
const (
|
||||
AdcBusAuto = 0
|
||||
AdcBus0 = 1
|
||||
AdcBus1 = 2
|
||||
)
|
||||
|
||||
@@ -94,8 +94,8 @@ const (
|
||||
// I2C on the QT Py M0.
|
||||
var (
|
||||
I2C0 = &I2C{
|
||||
Bus: sam.SERCOM2_I2CM,
|
||||
SERCOM: 2,
|
||||
Bus: sam.SERCOM1_I2CM,
|
||||
SERCOM: 1,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -10,6 +10,11 @@ var (
|
||||
ErrNoPinChangeChannel = errors.New("machine: no channel available for pin interrupt")
|
||||
)
|
||||
|
||||
// PinMode sets the direction and pull mode of the pin. For example, PinOutput
|
||||
// sets the pin as an output and PinInputPullup sets the pin as an input with a
|
||||
// pull-up.
|
||||
type PinMode uint8
|
||||
|
||||
type PinConfig struct {
|
||||
Mode PinMode
|
||||
}
|
||||
@@ -39,4 +44,5 @@ func (p Pin) Low() {
|
||||
|
||||
type ADC struct {
|
||||
Pin Pin
|
||||
Bus uint8
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device"
|
||||
"device/arm"
|
||||
"device/sam"
|
||||
"errors"
|
||||
@@ -17,8 +16,6 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
const (
|
||||
PinAnalog PinMode = 1
|
||||
PinSERCOM PinMode = 2
|
||||
@@ -433,7 +430,18 @@ func (a ADC) Get() uint16 {
|
||||
sam.ADC.CTRLA.ClearBits(sam.ADC_CTRLA_ENABLE)
|
||||
waitADCSync()
|
||||
|
||||
return uint16(val) << 4 // scales from 12 to 16-bit result
|
||||
// scales to 16-bit result
|
||||
switch (sam.ADC.CTRLB.Get() & sam.ADC_CTRLB_RESSEL_Msk) >> sam.ADC_CTRLB_RESSEL_Pos {
|
||||
case sam.ADC_CTRLB_RESSEL_8BIT:
|
||||
val = val << 8
|
||||
case sam.ADC_CTRLB_RESSEL_10BIT:
|
||||
val = val << 6
|
||||
case sam.ADC_CTRLB_RESSEL_16BIT:
|
||||
val = val << 4
|
||||
case sam.ADC_CTRLB_RESSEL_12BIT:
|
||||
val = val << 4
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func (a ADC) getADCChannel() uint8 {
|
||||
@@ -1290,43 +1298,21 @@ var (
|
||||
// spi.Tx(nil, rx)
|
||||
//
|
||||
func (spi SPI) Tx(w, r []byte) error {
|
||||
if spi.Bus.BAUD.Get() == 0x00 {
|
||||
// When the SPI Freq is 24MHz, special processing is performed to improve the speed.
|
||||
switch {
|
||||
case w == nil:
|
||||
// read only, so write zero and read a result.
|
||||
spi.rx(r)
|
||||
case r == nil:
|
||||
// write only
|
||||
spi.tx(w)
|
||||
|
||||
switch {
|
||||
case w == nil:
|
||||
// read only, so write zero and read a result.
|
||||
spi.rx(r)
|
||||
case r == nil:
|
||||
// write only
|
||||
spi.tx24mhz(w)
|
||||
|
||||
default:
|
||||
// write/read
|
||||
if len(w) != len(r) {
|
||||
return ErrTxInvalidSliceSize
|
||||
}
|
||||
|
||||
spi.txrx24mhz(w, r)
|
||||
default:
|
||||
// write/read
|
||||
if len(w) != len(r) {
|
||||
return ErrTxInvalidSliceSize
|
||||
}
|
||||
|
||||
} else {
|
||||
switch {
|
||||
case w == nil:
|
||||
// read only, so write zero and read a result.
|
||||
spi.rx(r)
|
||||
case r == nil:
|
||||
// write only
|
||||
spi.tx(w)
|
||||
|
||||
default:
|
||||
// write/read
|
||||
if len(w) != len(r) {
|
||||
return ErrTxInvalidSliceSize
|
||||
}
|
||||
|
||||
spi.txrx(w, r)
|
||||
}
|
||||
spi.txrx(w, r)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -1379,48 +1365,6 @@ func (spi SPI) txrx(tx, rx []byte) {
|
||||
rx[len(rx)-1] = byte(spi.Bus.DATA.Get())
|
||||
}
|
||||
|
||||
// tx24mhz is a special tx/rx function for CPU Clock 48 Mhz and SPI Freq 24 Mhz
|
||||
func (spi SPI) tx24mhz(tx []byte) {
|
||||
spi.Bus.DATA.Set(uint32(tx[0]))
|
||||
device.Asm("nop")
|
||||
device.Asm("nop")
|
||||
device.Asm("nop")
|
||||
device.Asm("nop")
|
||||
device.Asm("nop")
|
||||
device.Asm("nop")
|
||||
|
||||
for i := 1; i < len(tx); i++ {
|
||||
spi.Bus.DATA.Set(uint32(tx[i]))
|
||||
device.Asm("nop")
|
||||
device.Asm("nop")
|
||||
spi.Bus.DATA.Get()
|
||||
}
|
||||
for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) {
|
||||
}
|
||||
spi.Bus.DATA.Get()
|
||||
}
|
||||
|
||||
// txrx24mhz is a special tx/rx function for CPU Clock 48 Mhz and SPI Freq 24 Mhz
|
||||
func (spi SPI) txrx24mhz(tx, rx []byte) {
|
||||
spi.Bus.DATA.Set(uint32(tx[0]))
|
||||
device.Asm("nop")
|
||||
device.Asm("nop")
|
||||
device.Asm("nop")
|
||||
device.Asm("nop")
|
||||
device.Asm("nop")
|
||||
device.Asm("nop")
|
||||
|
||||
for i := 1; i < len(rx); i++ {
|
||||
spi.Bus.DATA.Set(uint32(tx[i]))
|
||||
device.Asm("nop")
|
||||
device.Asm("nop")
|
||||
rx[i-1] = byte(spi.Bus.DATA.Get())
|
||||
}
|
||||
for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) {
|
||||
}
|
||||
rx[len(rx)-1] = byte(spi.Bus.DATA.Get())
|
||||
}
|
||||
|
||||
// TCC is one timer/counter peripheral, which consists of a counter and multiple
|
||||
// output channels (that can be connected to actual pins). You can set the
|
||||
// frequency using SetPeriod, but only for all the channels in this TCC
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build sam,atsamd51
|
||||
// +build sam,atsamd51 sam,atsame5x
|
||||
|
||||
// Peripheral abstraction layer for the atsamd51.
|
||||
//
|
||||
@@ -20,8 +20,6 @@ func CPUFrequency() uint32 {
|
||||
return 120000000
|
||||
}
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
const (
|
||||
PinAnalog PinMode = 1
|
||||
PinSERCOM PinMode = 2
|
||||
@@ -44,6 +42,9 @@ const (
|
||||
PinTCCF PinMode = PinTimerAlt
|
||||
PinTCCG PinMode = PinTCCPDEC
|
||||
PinInputPulldown PinMode = 18
|
||||
PinCAN PinMode = 19
|
||||
PinCAN0 PinMode = PinSDHC
|
||||
PinCAN1 PinMode = PinCom
|
||||
)
|
||||
|
||||
type PinChange uint8
|
||||
@@ -627,6 +628,18 @@ func (p Pin) Configure(config PinConfig) {
|
||||
}
|
||||
// enable port config
|
||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | sam.PORT_GROUP_PINCFG_DRVSTR)
|
||||
case PinSDHC:
|
||||
if p&1 > 0 {
|
||||
// odd pin, so save the even pins
|
||||
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk
|
||||
p.setPMux(val | (uint8(PinSDHC) << sam.PORT_GROUP_PMUX_PMUXO_Pos))
|
||||
} else {
|
||||
// even pin, so save the odd pins
|
||||
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk
|
||||
p.setPMux(val | (uint8(PinSDHC) << sam.PORT_GROUP_PMUX_PMUXE_Pos))
|
||||
}
|
||||
// enable port config
|
||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -800,6 +813,18 @@ func (a ADC) Configure(config ADCConfig) {
|
||||
}
|
||||
|
||||
a.Pin.Configure(PinConfig{Mode: PinAnalog})
|
||||
|
||||
switch config.Bus {
|
||||
case AdcBusAuto:
|
||||
bus := a.getADCBus()
|
||||
if bus == sam.ADC0 {
|
||||
a.Bus = 0
|
||||
} else {
|
||||
a.Bus = 1
|
||||
}
|
||||
default:
|
||||
a.Bus = config.Bus
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the current value of a ADC pin, in the range 0..0xffff.
|
||||
@@ -848,10 +873,27 @@ func (a ADC) Get() uint16 {
|
||||
for bus.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_ENABLE) {
|
||||
}
|
||||
|
||||
return uint16(val) << 4 // scales from 12 to 16-bit result
|
||||
// scales to 16-bit result
|
||||
switch (bus.CTRLB.Get() & sam.ADC_CTRLB_RESSEL_Msk) >> sam.ADC_CTRLB_RESSEL_Pos {
|
||||
case sam.ADC_CTRLB_RESSEL_8BIT:
|
||||
val = val << 8
|
||||
case sam.ADC_CTRLB_RESSEL_10BIT:
|
||||
val = val << 6
|
||||
case sam.ADC_CTRLB_RESSEL_16BIT:
|
||||
val = val << 4
|
||||
case sam.ADC_CTRLB_RESSEL_12BIT:
|
||||
val = val << 4
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func (a ADC) getADCBus() *sam.ADC_Type {
|
||||
if a.Bus == AdcBus0 {
|
||||
return sam.ADC0
|
||||
} else if a.Bus == AdcBus1 {
|
||||
return sam.ADC1
|
||||
}
|
||||
|
||||
if (a.Pin >= PB04 && a.Pin <= PB07) || (a.Pin >= PC00) {
|
||||
return sam.ADC1
|
||||
}
|
||||
@@ -863,9 +905,17 @@ func (a ADC) getADCChannel() uint8 {
|
||||
case PA02:
|
||||
return 0
|
||||
case PB08:
|
||||
return 2
|
||||
if a.Bus == AdcBus1 {
|
||||
return 0
|
||||
} else {
|
||||
return 2
|
||||
}
|
||||
case PB09:
|
||||
return 3
|
||||
if a.Bus == AdcBus1 {
|
||||
return 1
|
||||
} else {
|
||||
return 3
|
||||
}
|
||||
case PA04:
|
||||
return 4
|
||||
case PA05:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,6 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
const (
|
||||
PinInput PinMode = iota
|
||||
PinInputPullup
|
||||
|
||||
@@ -21,8 +21,6 @@ var (
|
||||
ErrInvalidSPIBus = errors.New("machine: invalid SPI bus")
|
||||
)
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
const (
|
||||
PinOutput PinMode = iota
|
||||
PinInput
|
||||
|
||||
@@ -11,8 +11,6 @@ func CPUFrequency() uint32 {
|
||||
return 80000000 // 80MHz
|
||||
}
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
const (
|
||||
PinOutput PinMode = iota
|
||||
PinInput
|
||||
|
||||
@@ -12,8 +12,6 @@ func CPUFrequency() uint32 {
|
||||
return 16000000
|
||||
}
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
const (
|
||||
PinInput PinMode = iota
|
||||
PinOutput
|
||||
|
||||
@@ -30,8 +30,6 @@ const (
|
||||
// Make it easier to directly write to I/O RAM.
|
||||
var ioram = (*[0x400]volatile.Register8)(unsafe.Pointer(uintptr(0x04000000)))
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
// Set has not been implemented.
|
||||
func (p Pin) Set(value bool) {
|
||||
// do nothing
|
||||
|
||||
@@ -10,8 +10,6 @@ var (
|
||||
UART0 = UART{0}
|
||||
)
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
const (
|
||||
PinInput PinMode = iota
|
||||
PinOutput
|
||||
|
||||
@@ -14,7 +14,6 @@ func CPUFrequency() uint32 {
|
||||
return 390000000
|
||||
}
|
||||
|
||||
type PinMode uint8
|
||||
type fpioaPullMode uint8
|
||||
type PinChange uint8
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@ func CPUFrequency() uint32 {
|
||||
return 600000000
|
||||
}
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
const (
|
||||
// GPIO
|
||||
PinInput PinMode = iota
|
||||
|
||||
@@ -13,8 +13,6 @@ var (
|
||||
ErrTxInvalidSliceSize = errors.New("SPI write and read slices must be same size")
|
||||
)
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
const (
|
||||
PinInput PinMode = (nrf.GPIO_PIN_CNF_DIR_Input << nrf.GPIO_PIN_CNF_DIR_Pos) | (nrf.GPIO_PIN_CNF_INPUT_Connect << nrf.GPIO_PIN_CNF_INPUT_Pos)
|
||||
PinInputPullup PinMode = PinInput | (nrf.GPIO_PIN_CNF_PULL_Pullup << nrf.GPIO_PIN_CNF_PULL_Pos)
|
||||
|
||||
@@ -37,8 +37,6 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
const (
|
||||
PinInput PinMode = iota
|
||||
PinInputPullUp
|
||||
|
||||
@@ -17,8 +17,6 @@ const (
|
||||
portJ
|
||||
)
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
// Peripheral operations sequence:
|
||||
// 1. Enable the clock to the alternate function.
|
||||
// 2. Enable clock to corresponding GPIO
|
||||
|
||||
@@ -204,15 +204,16 @@ func (spi SPI) configurePins(config SPIConfig) {
|
||||
// There are 2 I2C interfaces on the STM32F103xx.
|
||||
// Since the first interface is named I2C1, both I2C0 and I2C1 refer to I2C1.
|
||||
// TODO: implement I2C2.
|
||||
var (
|
||||
I2C1 = (*I2C)(unsafe.Pointer(stm32.I2C1))
|
||||
I2C0 = I2C1
|
||||
)
|
||||
|
||||
type I2C struct {
|
||||
Bus *stm32.I2C_Type
|
||||
}
|
||||
|
||||
var (
|
||||
I2C1 = &I2C{Bus: stm32.I2C1}
|
||||
I2C0 = I2C1
|
||||
)
|
||||
|
||||
func (i2c *I2C) configurePins(config I2CConfig) {
|
||||
if config.SDA == PB9 {
|
||||
// use alternate I2C1 pins PB8/PB9 via AFIO mapping
|
||||
|
||||
@@ -3,3 +3,11 @@
|
||||
package runtime
|
||||
|
||||
const GOOS = "darwin"
|
||||
|
||||
const (
|
||||
// See https://github.com/golang/go/blob/master/src/syscall/zerrors_darwin_amd64.go
|
||||
flag_PROT_READ = 0x1
|
||||
flag_PROT_WRITE = 0x2
|
||||
flag_MAP_PRIVATE = 0x2
|
||||
flag_MAP_ANONYMOUS = 0x1000 // MAP_ANON
|
||||
)
|
||||
|
||||
@@ -3,3 +3,11 @@
|
||||
package runtime
|
||||
|
||||
const GOOS = "linux"
|
||||
|
||||
const (
|
||||
// See https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/mman-common.h
|
||||
flag_PROT_READ = 0x1
|
||||
flag_PROT_WRITE = 0x2
|
||||
flag_MAP_PRIVATE = 0x2
|
||||
flag_MAP_ANONYMOUS = 0x20
|
||||
)
|
||||
|
||||
@@ -71,8 +71,6 @@ func ticks() timeUnit {
|
||||
return 0
|
||||
}
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
func sleepTicks(d timeUnit) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@@ -217,11 +217,19 @@ func initRTC() {
|
||||
waitForSync()
|
||||
|
||||
rtcInterrupt := 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)
|
||||
flags := sam.RTC_MODE0.INTFLAG.Get()
|
||||
if flags&sam.RTC_MODE0_INTENSET_CMP0 != 0 {
|
||||
// The timer (for a sleep) has expired.
|
||||
timerWakeup.Set(1)
|
||||
}
|
||||
if flags&sam.RTC_MODE0_INTENSET_OVF != 0 {
|
||||
// The 32-bit RTC timer has overflowed.
|
||||
rtcOverflows.Set(rtcOverflows.Get() + 1)
|
||||
}
|
||||
// Mark this interrupt has handled for CMP0 and OVF.
|
||||
sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0 | sam.RTC_MODE0_INTENSET_OVF)
|
||||
})
|
||||
sam.RTC_MODE0.INTENSET.Set(sam.RTC_MODE0_INTENSET_OVF)
|
||||
rtcInterrupt.SetPriority(0xc0)
|
||||
rtcInterrupt.Enable()
|
||||
}
|
||||
@@ -231,15 +239,10 @@ func waitForSync() {
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
timestamp timeUnit // ticks since boottime
|
||||
timerLastCounter uint64
|
||||
)
|
||||
var rtcOverflows volatile.Register32 // number of times the RTC wrapped around
|
||||
|
||||
var timerWakeup volatile.Register8
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds.
|
||||
func ticksToNanoseconds(ticks timeUnit) int64 {
|
||||
// The following calculation is actually the following, but with both sides
|
||||
@@ -259,7 +262,6 @@ func nanosecondsToTicks(ns int64) timeUnit {
|
||||
// sleepTicks should sleep for d number of microseconds.
|
||||
func sleepTicks(d timeUnit) {
|
||||
for d != 0 {
|
||||
ticks() // update timestamp
|
||||
ticks := uint32(d)
|
||||
if !timerSleep(ticks) {
|
||||
// Bail out early to handle a non-time interrupt.
|
||||
@@ -269,17 +271,37 @@ func sleepTicks(d timeUnit) {
|
||||
}
|
||||
}
|
||||
|
||||
// ticks returns number of microseconds since start.
|
||||
// ticks returns the elapsed time since reset.
|
||||
func ticks() timeUnit {
|
||||
// For some ways of capturing the time atomically, see this thread:
|
||||
// https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617
|
||||
// Here, instead of re-reading the counter register if an overflow has been
|
||||
// detected, we simply try again because that results in smaller code.
|
||||
for {
|
||||
mask := interrupt.Disable()
|
||||
counter := readRTC()
|
||||
overflows := rtcOverflows.Get()
|
||||
hasOverflow := sam.RTC_MODE0.INTFLAG.Get()&sam.RTC_MODE0_INTENSET_OVF != 0
|
||||
interrupt.Restore(mask)
|
||||
|
||||
if hasOverflow {
|
||||
// There was an overflow while trying to capture the timer.
|
||||
// Try again.
|
||||
continue
|
||||
}
|
||||
|
||||
// This is a 32-bit timer, so the number of timer overflows forms the
|
||||
// upper 32 bits of this timer.
|
||||
return timeUnit(overflows)<<32 + timeUnit(counter)
|
||||
}
|
||||
}
|
||||
|
||||
func readRTC() uint32 {
|
||||
// request read of count
|
||||
sam.RTC_MODE0.READREQ.Set(sam.RTC_MODE0_READREQ_RREQ)
|
||||
waitForSync()
|
||||
|
||||
rtcCounter := uint64(sam.RTC_MODE0.COUNT.Get()) // each counter tick == 30.5us
|
||||
offset := (rtcCounter - timerLastCounter) // change since last measurement
|
||||
timerLastCounter = rtcCounter
|
||||
timestamp += timeUnit(offset)
|
||||
return timestamp
|
||||
return sam.RTC_MODE0.COUNT.Get()
|
||||
}
|
||||
|
||||
// ticks are in microseconds
|
||||
@@ -305,7 +327,7 @@ func timerSleep(ticks uint32) bool {
|
||||
waitForSync()
|
||||
|
||||
// enable IRQ for CMP0 compare
|
||||
sam.RTC_MODE0.INTENSET.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
|
||||
sam.RTC_MODE0.INTENSET.Set(sam.RTC_MODE0_INTENSET_CMP0)
|
||||
|
||||
wait:
|
||||
waitForEvents()
|
||||
@@ -315,7 +337,7 @@ wait:
|
||||
if hasScheduler {
|
||||
// The interurpt may have awoken a goroutine, so bail out early.
|
||||
// Disable IRQ for CMP0 compare.
|
||||
sam.RTC_MODE0.INTENCLR.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
|
||||
sam.RTC_MODE0.INTENCLR.Set(sam.RTC_MODE0_INTENSET_CMP0)
|
||||
return false
|
||||
} else {
|
||||
// This is running without a scheduler.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build sam,atsamd51
|
||||
// +build sam,atsamd51 sam,atsame5x
|
||||
|
||||
package runtime
|
||||
|
||||
@@ -16,6 +16,7 @@ func postinit() {}
|
||||
|
||||
//export Reset_Handler
|
||||
func main() {
|
||||
arm.SCB.CPACR.Set(0) // disable FPU if it is enabled
|
||||
preinit()
|
||||
run()
|
||||
abort()
|
||||
@@ -205,11 +206,19 @@ func initRTC() {
|
||||
}
|
||||
|
||||
irq := interrupt.New(sam.IRQ_RTC, func(interrupt.Interrupt) {
|
||||
// disable IRQ for CMP0 compare
|
||||
sam.RTC_MODE0.INTFLAG.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
|
||||
|
||||
timerWakeup.Set(1)
|
||||
flags := sam.RTC_MODE0.INTFLAG.Get()
|
||||
if flags&sam.RTC_MODE0_INTENSET_CMP0 != 0 {
|
||||
// The timer (for a sleep) has expired.
|
||||
timerWakeup.Set(1)
|
||||
}
|
||||
if flags&sam.RTC_MODE0_INTENSET_OVF != 0 {
|
||||
// The 32-bit RTC timer has overflowed.
|
||||
rtcOverflows.Set(rtcOverflows.Get() + 1)
|
||||
}
|
||||
// Mark this interrupt has handled for CMP0 and OVF.
|
||||
sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0 | sam.RTC_MODE0_INTENSET_OVF)
|
||||
})
|
||||
sam.RTC_MODE0.INTENSET.Set(sam.RTC_MODE0_INTENSET_OVF)
|
||||
irq.SetPriority(0xc0)
|
||||
irq.Enable()
|
||||
}
|
||||
@@ -219,15 +228,10 @@ func waitForSync() {
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
timestamp timeUnit // ticks since boottime
|
||||
timerLastCounter uint64
|
||||
)
|
||||
var rtcOverflows volatile.Register32 // number of times the RTC wrapped around
|
||||
|
||||
var timerWakeup volatile.Register8
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds.
|
||||
func ticksToNanoseconds(ticks timeUnit) int64 {
|
||||
// The following calculation is actually the following, but with both sides
|
||||
@@ -247,7 +251,6 @@ func nanosecondsToTicks(ns int64) timeUnit {
|
||||
// sleepTicks should sleep for d number of microseconds.
|
||||
func sleepTicks(d timeUnit) {
|
||||
for d != 0 {
|
||||
ticks() // update timestamp
|
||||
ticks := uint32(d)
|
||||
if !timerSleep(ticks) {
|
||||
return
|
||||
@@ -256,15 +259,34 @@ func sleepTicks(d timeUnit) {
|
||||
}
|
||||
}
|
||||
|
||||
// ticks returns number of microseconds since start.
|
||||
// ticks returns the elapsed time since reset.
|
||||
func ticks() timeUnit {
|
||||
waitForSync()
|
||||
// For some ways of capturing the time atomically, see this thread:
|
||||
// https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617
|
||||
// Here, instead of re-reading the counter register if an overflow has been
|
||||
// detected, we simply try again because that results in smaller code.
|
||||
for {
|
||||
mask := interrupt.Disable()
|
||||
counter := readRTC()
|
||||
overflows := rtcOverflows.Get()
|
||||
hasOverflow := sam.RTC_MODE0.INTFLAG.Get()&sam.RTC_MODE0_INTENSET_OVF != 0
|
||||
interrupt.Restore(mask)
|
||||
|
||||
rtcCounter := uint64(sam.RTC_MODE0.COUNT.Get())
|
||||
offset := (rtcCounter - timerLastCounter) // change since last measurement
|
||||
timerLastCounter = rtcCounter
|
||||
timestamp += timeUnit(offset)
|
||||
return timestamp
|
||||
if hasOverflow {
|
||||
// There was an overflow while trying to capture the timer.
|
||||
// Try again.
|
||||
continue
|
||||
}
|
||||
|
||||
// This is a 32-bit timer, so the number of timer overflows forms the
|
||||
// upper 32 bits of this timer.
|
||||
return timeUnit(overflows)<<32 + timeUnit(counter)
|
||||
}
|
||||
}
|
||||
|
||||
func readRTC() uint32 {
|
||||
waitForSync()
|
||||
return sam.RTC_MODE0.COUNT.Get()
|
||||
}
|
||||
|
||||
// ticks are in microseconds
|
||||
@@ -289,7 +311,7 @@ func timerSleep(ticks uint32) bool {
|
||||
sam.RTC_MODE0.COMP[0].Set(uint32(cnt) + ticks)
|
||||
|
||||
// enable IRQ for CMP0 compare
|
||||
sam.RTC_MODE0.INTENSET.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
|
||||
sam.RTC_MODE0.INTENSET.Set(sam.RTC_MODE0_INTENSET_CMP0)
|
||||
|
||||
wait:
|
||||
waitForEvents()
|
||||
@@ -299,7 +321,7 @@ wait:
|
||||
if hasScheduler {
|
||||
// The interurpt may have awoken a goroutine, so bail out early.
|
||||
// Disable IRQ for CMP0 compare.
|
||||
sam.RTC_MODE0.INTENCLR.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
|
||||
sam.RTC_MODE0.INTENCLR.Set(sam.RTC_MODE0_INTENSET_CMP0)
|
||||
return false
|
||||
} else {
|
||||
// This is running without a scheduler.
|
||||
|
||||
@@ -1,338 +0,0 @@
|
||||
// +build sam,atsame5x
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"device/arm"
|
||||
"device/sam"
|
||||
"machine"
|
||||
"runtime/interrupt"
|
||||
"runtime/volatile"
|
||||
)
|
||||
|
||||
type timeUnit int64
|
||||
|
||||
func postinit() {}
|
||||
|
||||
//export Reset_Handler
|
||||
func main() {
|
||||
preinit()
|
||||
run()
|
||||
abort()
|
||||
}
|
||||
|
||||
func init() {
|
||||
initClocks()
|
||||
initRTC()
|
||||
initSERCOMClocks()
|
||||
initUSBClock()
|
||||
initADCClock()
|
||||
|
||||
// connect to USB CDC interface
|
||||
machine.UART0.Configure(machine.UARTConfig{})
|
||||
}
|
||||
|
||||
func putchar(c byte) {
|
||||
machine.UART0.WriteByte(c)
|
||||
}
|
||||
|
||||
func initClocks() {
|
||||
// set flash wait state
|
||||
sam.NVMCTRL.CTRLA.SetBits(0 << sam.NVMCTRL_CTRLA_RWS_Pos)
|
||||
|
||||
// software reset
|
||||
sam.GCLK.CTRLA.SetBits(sam.GCLK_CTRLA_SWRST)
|
||||
for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_SWRST) {
|
||||
}
|
||||
|
||||
// Set OSCULP32K as source of Generic Clock Generator 3
|
||||
// GCLK->GENCTRL[GENERIC_CLOCK_GENERATOR_XOSC32K].reg = GCLK_GENCTRL_SRC(GCLK_GENCTRL_SRC_OSCULP32K) | GCLK_GENCTRL_GENEN; //generic clock gen 3
|
||||
sam.GCLK.GENCTRL[3].Set((sam.GCLK_GENCTRL_SRC_OSCULP32K << sam.GCLK_GENCTRL_SRC_Pos) |
|
||||
sam.GCLK_GENCTRL_GENEN)
|
||||
for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK3) {
|
||||
}
|
||||
|
||||
// Set OSCULP32K as source of Generic Clock Generator 0
|
||||
sam.GCLK.GENCTRL[0].Set((sam.GCLK_GENCTRL_SRC_OSCULP32K << sam.GCLK_GENCTRL_SRC_Pos) |
|
||||
sam.GCLK_GENCTRL_GENEN)
|
||||
for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK0) {
|
||||
}
|
||||
|
||||
// Enable DFLL48M clock
|
||||
sam.OSCCTRL.DFLLCTRLA.Set(0)
|
||||
sam.OSCCTRL.DFLLMUL.Set((0x1 << sam.OSCCTRL_DFLLMUL_CSTEP_Pos) |
|
||||
(0x1 << sam.OSCCTRL_DFLLMUL_FSTEP_Pos) |
|
||||
(0x0 << sam.OSCCTRL_DFLLMUL_MUL_Pos))
|
||||
for sam.OSCCTRL.DFLLSYNC.HasBits(sam.OSCCTRL_DFLLSYNC_DFLLMUL) {
|
||||
}
|
||||
|
||||
sam.OSCCTRL.DFLLCTRLB.Set(0)
|
||||
for sam.OSCCTRL.DFLLSYNC.HasBits(sam.OSCCTRL_DFLLSYNC_DFLLCTRLB) {
|
||||
}
|
||||
|
||||
sam.OSCCTRL.DFLLCTRLA.SetBits(sam.OSCCTRL_DFLLCTRLA_ENABLE)
|
||||
for sam.OSCCTRL.DFLLSYNC.HasBits(sam.OSCCTRL_DFLLSYNC_ENABLE) {
|
||||
}
|
||||
|
||||
sam.OSCCTRL.DFLLVAL.Set(sam.OSCCTRL.DFLLVAL.Get())
|
||||
for sam.OSCCTRL.DFLLSYNC.HasBits(sam.OSCCTRL_DFLLSYNC_DFLLVAL) {
|
||||
}
|
||||
|
||||
sam.OSCCTRL.DFLLCTRLB.Set(sam.OSCCTRL_DFLLCTRLB_WAITLOCK |
|
||||
sam.OSCCTRL_DFLLCTRLB_CCDIS |
|
||||
sam.OSCCTRL_DFLLCTRLB_USBCRM)
|
||||
for !sam.OSCCTRL.STATUS.HasBits(sam.OSCCTRL_STATUS_DFLLRDY) {
|
||||
}
|
||||
|
||||
// set GCLK7 to run at 2MHz, using DFLL48M as clock source
|
||||
// GCLK7 = 48MHz / 24 = 2MHz
|
||||
sam.GCLK.GENCTRL[7].Set((sam.GCLK_GENCTRL_SRC_DFLL << sam.GCLK_GENCTRL_SRC_Pos) |
|
||||
(24 << sam.GCLK_GENCTRL_DIV_Pos) |
|
||||
sam.GCLK_GENCTRL_GENEN)
|
||||
for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK7) {
|
||||
}
|
||||
|
||||
// Set up the PLLs
|
||||
|
||||
// Set PLL0 to run at 120MHz, using GCLK7 as clock source
|
||||
sam.GCLK.PCHCTRL[1].Set(sam.GCLK_PCHCTRL_CHEN |
|
||||
(sam.GCLK_PCHCTRL_GEN_GCLK7 << sam.GCLK_PCHCTRL_GEN_Pos))
|
||||
|
||||
// multiplier = 59 + 1 + (0/32) = 60
|
||||
// PLL0 = 2MHz * 60 = 120MHz
|
||||
sam.OSCCTRL.DPLL[0].DPLLRATIO.Set((0x0 << sam.OSCCTRL_DPLL_DPLLRATIO_LDRFRAC_Pos) |
|
||||
(59 << sam.OSCCTRL_DPLL_DPLLRATIO_LDR_Pos))
|
||||
for sam.OSCCTRL.DPLL[0].DPLLSYNCBUSY.HasBits(sam.OSCCTRL_DPLL_DPLLSYNCBUSY_DPLLRATIO) {
|
||||
}
|
||||
|
||||
// MUST USE LBYPASS DUE TO BUG IN REV A OF SAMD51, via Adafruit lib.
|
||||
sam.OSCCTRL.DPLL[0].DPLLCTRLB.Set((sam.OSCCTRL_DPLL_DPLLCTRLB_REFCLK_GCLK << sam.OSCCTRL_DPLL_DPLLCTRLB_REFCLK_Pos) |
|
||||
sam.OSCCTRL_DPLL_DPLLCTRLB_LBYPASS)
|
||||
|
||||
sam.OSCCTRL.DPLL[0].DPLLCTRLA.Set(sam.OSCCTRL_DPLL_DPLLCTRLA_ENABLE)
|
||||
for !sam.OSCCTRL.DPLL[0].DPLLSTATUS.HasBits(sam.OSCCTRL_DPLL_DPLLSTATUS_CLKRDY) ||
|
||||
!sam.OSCCTRL.DPLL[0].DPLLSTATUS.HasBits(sam.OSCCTRL_DPLL_DPLLSTATUS_LOCK) {
|
||||
}
|
||||
|
||||
// Set PLL1 to run at 100MHz, using GCLK7 as clock source
|
||||
sam.GCLK.PCHCTRL[2].Set(sam.GCLK_PCHCTRL_CHEN |
|
||||
(sam.GCLK_PCHCTRL_GEN_GCLK7 << sam.GCLK_PCHCTRL_GEN_Pos))
|
||||
|
||||
// multiplier = 49 + 1 + (0/32) = 50
|
||||
// PLL1 = 2MHz * 50 = 100MHz
|
||||
sam.OSCCTRL.DPLL[1].DPLLRATIO.Set((0x0 << sam.OSCCTRL_DPLL_DPLLRATIO_LDRFRAC_Pos) |
|
||||
(49 << sam.OSCCTRL_DPLL_DPLLRATIO_LDR_Pos))
|
||||
for sam.OSCCTRL.DPLL[1].DPLLSYNCBUSY.HasBits(sam.OSCCTRL_DPLL_DPLLSYNCBUSY_DPLLRATIO) {
|
||||
}
|
||||
|
||||
// // MUST USE LBYPASS DUE TO BUG IN REV A OF SAMD51
|
||||
sam.OSCCTRL.DPLL[1].DPLLCTRLB.Set((sam.OSCCTRL_DPLL_DPLLCTRLB_REFCLK_GCLK << sam.OSCCTRL_DPLL_DPLLCTRLB_REFCLK_Pos) |
|
||||
sam.OSCCTRL_DPLL_DPLLCTRLB_LBYPASS)
|
||||
|
||||
sam.OSCCTRL.DPLL[1].DPLLCTRLA.Set(sam.OSCCTRL_DPLL_DPLLCTRLA_ENABLE)
|
||||
// for !sam.OSCCTRL.DPLLSTATUS1.HasBits(sam.OSCCTRL_DPLLSTATUS_CLKRDY) ||
|
||||
// !sam.OSCCTRL.DPLLSTATUS1.HasBits(sam.OSCCTRL_DPLLSTATUS_LOCK) {
|
||||
// }
|
||||
|
||||
// Set up the peripheral clocks
|
||||
// Set 48MHZ CLOCK FOR USB
|
||||
sam.GCLK.GENCTRL[1].Set((sam.GCLK_GENCTRL_SRC_DFLL << sam.GCLK_GENCTRL_SRC_Pos) |
|
||||
sam.GCLK_GENCTRL_IDC |
|
||||
sam.GCLK_GENCTRL_GENEN)
|
||||
for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK1) {
|
||||
}
|
||||
|
||||
// // Set 100MHZ CLOCK FOR OTHER PERIPHERALS
|
||||
// sam.GCLK.GENCTRL2.Set((sam.GCLK_GENCTRL_SRC_DPLL1 << sam.GCLK_GENCTRL_SRC_Pos) |
|
||||
// sam.GCLK_GENCTRL_IDC |
|
||||
// sam.GCLK_GENCTRL_GENEN)
|
||||
// for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL2) {
|
||||
// }
|
||||
|
||||
// // Set 12MHZ CLOCK FOR DAC
|
||||
sam.GCLK.GENCTRL[4].Set((sam.GCLK_GENCTRL_SRC_DFLL << sam.GCLK_GENCTRL_SRC_Pos) |
|
||||
sam.GCLK_GENCTRL_IDC |
|
||||
(4 << sam.GCLK_GENCTRL_DIVSEL_Pos) |
|
||||
sam.GCLK_GENCTRL_GENEN)
|
||||
for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK4) {
|
||||
}
|
||||
|
||||
// // Set up main clock
|
||||
sam.GCLK.GENCTRL[0].Set((sam.GCLK_GENCTRL_SRC_DPLL0 << sam.GCLK_GENCTRL_SRC_Pos) |
|
||||
sam.GCLK_GENCTRL_IDC |
|
||||
sam.GCLK_GENCTRL_GENEN)
|
||||
for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK0) {
|
||||
}
|
||||
|
||||
sam.MCLK.CPUDIV.Set(sam.MCLK_CPUDIV_DIV_DIV1)
|
||||
|
||||
// Use the LDO regulator by default
|
||||
sam.SUPC.VREG.ClearBits(sam.SUPC_VREG_SEL)
|
||||
|
||||
// Start up the "Debug Watchpoint and Trace" unit, so that we can use
|
||||
// it's 32bit cycle counter for timing.
|
||||
//CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
|
||||
//DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
|
||||
}
|
||||
|
||||
func initRTC() {
|
||||
// turn on digital interface clock
|
||||
sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_RTC_)
|
||||
|
||||
// disable RTC
|
||||
sam.RTC_MODE0.CTRLA.ClearBits(sam.RTC_MODE0_CTRLA_ENABLE)
|
||||
//sam.RTC_MODE0.CTRLA.Set(0)
|
||||
for sam.RTC_MODE0.SYNCBUSY.HasBits(sam.RTC_MODE0_SYNCBUSY_ENABLE) {
|
||||
}
|
||||
|
||||
// reset RTC
|
||||
sam.RTC_MODE0.CTRLA.SetBits(sam.RTC_MODE0_CTRLA_SWRST)
|
||||
for sam.RTC_MODE0.SYNCBUSY.HasBits(sam.RTC_MODE0_SYNCBUSY_SWRST) {
|
||||
}
|
||||
|
||||
// set to use ulp 32k oscillator
|
||||
sam.OSC32KCTRL.OSCULP32K.SetBits(sam.OSC32KCTRL_OSCULP32K_EN32K)
|
||||
sam.OSC32KCTRL.RTCCTRL.Set(sam.OSC32KCTRL_RTCCTRL_RTCSEL_ULP32K)
|
||||
|
||||
// set Mode0 to 32-bit counter (mode 0) with prescaler 1 and GCLK2 is 32KHz/1
|
||||
sam.RTC_MODE0.CTRLA.Set((sam.RTC_MODE0_CTRLA_MODE_COUNT32 << sam.RTC_MODE0_CTRLA_MODE_Pos) |
|
||||
(sam.RTC_MODE0_CTRLA_PRESCALER_DIV1 << sam.RTC_MODE0_CTRLA_PRESCALER_Pos) |
|
||||
(sam.RTC_MODE0_CTRLA_COUNTSYNC))
|
||||
|
||||
// re-enable RTC
|
||||
sam.RTC_MODE0.CTRLA.SetBits(sam.RTC_MODE0_CTRLA_ENABLE)
|
||||
for sam.RTC_MODE0.SYNCBUSY.HasBits(sam.RTC_MODE0_SYNCBUSY_ENABLE) {
|
||||
}
|
||||
|
||||
irq := interrupt.New(sam.IRQ_RTC, func(interrupt.Interrupt) {
|
||||
// disable IRQ for CMP0 compare
|
||||
sam.RTC_MODE0.INTFLAG.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
|
||||
|
||||
timerWakeup.Set(1)
|
||||
})
|
||||
irq.SetPriority(0xc0)
|
||||
irq.Enable()
|
||||
}
|
||||
|
||||
func waitForSync() {
|
||||
for sam.RTC_MODE0.SYNCBUSY.HasBits(sam.RTC_MODE0_SYNCBUSY_COUNT) {
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
timestamp timeUnit // ticks since boottime
|
||||
timerLastCounter uint64
|
||||
)
|
||||
|
||||
var timerWakeup volatile.Register8
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds.
|
||||
func ticksToNanoseconds(ticks timeUnit) int64 {
|
||||
// The following calculation is actually the following, but with both sides
|
||||
// reduced to reduce the risk of overflow:
|
||||
// ticks * 1e9 / 32768
|
||||
return int64(ticks) * 1953125 / 64
|
||||
}
|
||||
|
||||
// nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz).
|
||||
func nanosecondsToTicks(ns int64) timeUnit {
|
||||
// The following calculation is actually the following, but with both sides
|
||||
// reduced to reduce the risk of overflow:
|
||||
// ns * 32768 / 1e9
|
||||
return timeUnit(ns * 64 / 1953125)
|
||||
}
|
||||
|
||||
// sleepTicks should sleep for d number of microseconds.
|
||||
func sleepTicks(d timeUnit) {
|
||||
for d != 0 {
|
||||
ticks() // update timestamp
|
||||
ticks := uint32(d)
|
||||
if !timerSleep(ticks) {
|
||||
return
|
||||
}
|
||||
d -= timeUnit(ticks)
|
||||
}
|
||||
}
|
||||
|
||||
// ticks returns number of microseconds since start.
|
||||
func ticks() timeUnit {
|
||||
waitForSync()
|
||||
|
||||
rtcCounter := uint64(sam.RTC_MODE0.COUNT.Get())
|
||||
offset := (rtcCounter - timerLastCounter) // change since last measurement
|
||||
timerLastCounter = rtcCounter
|
||||
timestamp += timeUnit(offset)
|
||||
return timestamp
|
||||
}
|
||||
|
||||
// ticks are in microseconds
|
||||
// Returns true if the timer completed.
|
||||
// Returns false if another interrupt occured which requires an early return to scheduler.
|
||||
func timerSleep(ticks uint32) bool {
|
||||
timerWakeup.Set(0)
|
||||
if ticks < 8 {
|
||||
// due to delay waiting for the register value to sync, the minimum sleep value
|
||||
// for the SAMD51 is 260us.
|
||||
// For related info for SAMD21, see:
|
||||
// https://community.atmel.com/comment/2507091#comment-2507091
|
||||
ticks = 8
|
||||
}
|
||||
|
||||
// request read of count
|
||||
waitForSync()
|
||||
|
||||
// set compare value
|
||||
cnt := sam.RTC_MODE0.COUNT.Get()
|
||||
|
||||
sam.RTC_MODE0.COMP[0].Set(uint32(cnt) + ticks)
|
||||
|
||||
// enable IRQ for CMP0 compare
|
||||
sam.RTC_MODE0.INTENSET.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
|
||||
|
||||
wait:
|
||||
waitForEvents()
|
||||
if timerWakeup.Get() != 0 {
|
||||
return true
|
||||
}
|
||||
if hasScheduler {
|
||||
// The interurpt may have awoken a goroutine, so bail out early.
|
||||
// Disable IRQ for CMP0 compare.
|
||||
sam.RTC_MODE0.INTENCLR.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
|
||||
return false
|
||||
} else {
|
||||
// This is running without a scheduler.
|
||||
// The application expects this to sleep the whole time.
|
||||
goto wait
|
||||
}
|
||||
}
|
||||
|
||||
func initUSBClock() {
|
||||
// Turn on clock(s) for USB
|
||||
//MCLK->APBBMASK.reg |= MCLK_APBBMASK_USB;
|
||||
//MCLK->AHBMASK.reg |= MCLK_AHBMASK_USB;
|
||||
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_USB_)
|
||||
sam.MCLK.AHBMASK.SetBits(sam.MCLK_AHBMASK_USB_)
|
||||
|
||||
// Put Generic Clock Generator 1 as source for USB
|
||||
//GCLK->PCHCTRL[USB_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK1_Val | (1 << GCLK_PCHCTRL_CHEN_Pos);
|
||||
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_USB].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
|
||||
sam.GCLK_PCHCTRL_CHEN)
|
||||
}
|
||||
|
||||
func initADCClock() {
|
||||
// Turn on clocks for ADC0/ADC1.
|
||||
sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_ADC0_)
|
||||
sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_ADC1_)
|
||||
|
||||
// Put Generic Clock Generator 1 as source for ADC0 and ADC1.
|
||||
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_ADC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
|
||||
sam.GCLK_PCHCTRL_CHEN)
|
||||
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_ADC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
|
||||
sam.GCLK_PCHCTRL_CHEN)
|
||||
}
|
||||
|
||||
func waitForEvents() {
|
||||
arm.Asm("wfe")
|
||||
}
|
||||
@@ -58,8 +58,6 @@ func init() {
|
||||
initUART()
|
||||
}
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
const tickNanos = 1024 * 16384 // roughly 16ms in nanoseconds
|
||||
|
||||
func ticksToNanoseconds(ticks timeUnit) int64 {
|
||||
|
||||
@@ -27,8 +27,6 @@ func main() {
|
||||
abort()
|
||||
}
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
func ticksToNanoseconds(ticks timeUnit) int64 {
|
||||
return int64(ticks)
|
||||
}
|
||||
|
||||
@@ -97,8 +97,6 @@ func ticks() timeUnit {
|
||||
return timeUnit(uint64(esp.TIMG0.T0LO.Get()) | uint64(esp.TIMG0.T0HI.Get())<<32)
|
||||
}
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
func nanosecondsToTicks(ns int64) timeUnit {
|
||||
// Calculate the number of ticks from the number of nanoseconds. At a 80MHz
|
||||
// APB clock, that's 25 nanoseconds per tick with a timer prescaler of 2:
|
||||
|
||||
@@ -89,8 +89,6 @@ func ticks() timeUnit {
|
||||
return currentTime
|
||||
}
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
const tickNanos = 3200 // time.Second / (80MHz / 256)
|
||||
|
||||
func ticksToNanoseconds(ticks timeUnit) int64 {
|
||||
|
||||
@@ -100,8 +100,6 @@ func putchar(c byte) {
|
||||
machine.UART0.WriteByte(c)
|
||||
}
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
var timerWakeup volatile.Register8
|
||||
|
||||
func ticks() timeUnit {
|
||||
|
||||
@@ -112,8 +112,6 @@ func putchar(c byte) {
|
||||
machine.UART0.WriteByte(c)
|
||||
}
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
var timerWakeup volatile.Register8
|
||||
|
||||
func ticks() timeUnit {
|
||||
|
||||
@@ -10,8 +10,6 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
//go:extern _svectors
|
||||
var _svectors [0]byte
|
||||
|
||||
@@ -102,11 +100,6 @@ func initSystem() {
|
||||
|
||||
func initPeripherals() {
|
||||
|
||||
// enable FPU - set CP10, CP11 full access
|
||||
nxp.SystemControl.CPACR.SetBits(
|
||||
((nxp.SCB_CPACR_CP10_CP10_3 << nxp.SCB_CPACR_CP10_Pos) & nxp.SCB_CPACR_CP10_Msk) |
|
||||
((nxp.SCB_CPACR_CP11_CP11_3 << nxp.SCB_CPACR_CP11_Pos) & nxp.SCB_CPACR_CP11_Msk))
|
||||
|
||||
enableTimerClocks() // activate GPT/PIT clock gates
|
||||
initSysTick() // enable SysTick
|
||||
initRTC() // enable real-time clock
|
||||
|
||||
@@ -6,8 +6,6 @@ import "unsafe"
|
||||
|
||||
type timeUnit int64
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
const (
|
||||
// Handles
|
||||
infoTypeTotalMemorySize = 6 // Total amount of memory available for process.
|
||||
|
||||
+38
-19
@@ -3,6 +3,7 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"device/arm"
|
||||
"device/nrf"
|
||||
"machine"
|
||||
"runtime/interrupt"
|
||||
@@ -18,6 +19,9 @@ func postinit() {}
|
||||
|
||||
//export Reset_Handler
|
||||
func main() {
|
||||
if nrf.FPUPresent {
|
||||
arm.SCB.CPACR.Set(0) // disable FPU if it is enabled
|
||||
}
|
||||
systemInit()
|
||||
preinit()
|
||||
run()
|
||||
@@ -43,10 +47,18 @@ func initLFCLK() {
|
||||
func initRTC() {
|
||||
nrf.RTC1.TASKS_START.Set(1)
|
||||
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)
|
||||
if nrf.RTC1.EVENTS_COMPARE[0].Get() != 0 {
|
||||
nrf.RTC1.EVENTS_COMPARE[0].Set(0)
|
||||
nrf.RTC1.INTENCLR.Set(nrf.RTC_INTENSET_COMPARE0)
|
||||
nrf.RTC1.EVENTS_COMPARE[0].Set(0)
|
||||
rtc_wakeup.Set(1)
|
||||
}
|
||||
if nrf.RTC1.EVENTS_OVRFLW.Get() != 0 {
|
||||
nrf.RTC1.EVENTS_OVRFLW.Set(0)
|
||||
rtcOverflows.Set(rtcOverflows.Get() + 1)
|
||||
}
|
||||
})
|
||||
nrf.RTC1.INTENSET.Set(nrf.RTC_INTENSET_OVRFLW)
|
||||
intr.SetPriority(0xc0) // low priority
|
||||
intr.Enable()
|
||||
}
|
||||
@@ -55,21 +67,15 @@ func putchar(c byte) {
|
||||
machine.UART0.WriteByte(c)
|
||||
}
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
func sleepTicks(d timeUnit) {
|
||||
for d != 0 {
|
||||
ticks() // update timestamp
|
||||
ticks := uint32(d) & 0x7fffff // 23 bits (to be on the safe side)
|
||||
rtc_sleep(ticks)
|
||||
d -= timeUnit(ticks)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
timestamp timeUnit // nanoseconds since boottime
|
||||
rtcLastCounter uint32 // 24 bits ticks
|
||||
)
|
||||
var rtcOverflows volatile.Register32 // number of times the RTC wrapped around
|
||||
|
||||
// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds.
|
||||
func ticksToNanoseconds(ticks timeUnit) int64 {
|
||||
@@ -88,16 +94,29 @@ func nanosecondsToTicks(ns int64) timeUnit {
|
||||
}
|
||||
|
||||
// Monotonically increasing numer of ticks since start.
|
||||
//
|
||||
// Note: very long pauses between measurements (more than 8 minutes) may
|
||||
// overflow the counter, leading to incorrect results. This might be fixed by
|
||||
// handling the overflow event.
|
||||
func ticks() timeUnit {
|
||||
rtcCounter := uint32(nrf.RTC1.COUNTER.Get())
|
||||
offset := (rtcCounter - rtcLastCounter) & 0xffffff // change since last measurement
|
||||
rtcLastCounter = rtcCounter
|
||||
timestamp += timeUnit(offset)
|
||||
return timestamp
|
||||
// For some ways of capturing the time atomically, see this thread:
|
||||
// https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617
|
||||
// Here, instead of re-reading the counter register if an overflow has been
|
||||
// detected, we simply try again because that results in (slightly) smaller
|
||||
// code and is perhaps easier to prove correct.
|
||||
for {
|
||||
mask := interrupt.Disable()
|
||||
counter := uint32(nrf.RTC1.COUNTER.Get())
|
||||
overflows := rtcOverflows.Get()
|
||||
hasOverflow := nrf.RTC1.EVENTS_OVRFLW.Get() != 0
|
||||
interrupt.Restore(mask)
|
||||
|
||||
if hasOverflow {
|
||||
// There was an overflow. Try again.
|
||||
continue
|
||||
}
|
||||
|
||||
// The counter is 24 bits in size, so the number of overflows form the
|
||||
// upper 32 bits (together 56 bits, which covers 71493 years at
|
||||
// 32768kHz: I'd argue good enough for most purposes).
|
||||
return timeUnit(overflows)<<24 + timeUnit(counter)
|
||||
}
|
||||
}
|
||||
|
||||
var rtc_wakeup volatile.Register8
|
||||
|
||||
@@ -24,10 +24,10 @@ func waitForEvents() {
|
||||
if enabled != 0 {
|
||||
// Now pick the appropriate SVCall number. Hopefully they won't change
|
||||
// in the future with a different SoftDevice version.
|
||||
if nrf.DEVICE == "nrf51" {
|
||||
if nrf.Device == "nrf51" {
|
||||
// sd_app_evt_wait: SOC_SVC_BASE_NOT_AVAILABLE + 29
|
||||
arm.SVCall0(0x2B + 29)
|
||||
} else if nrf.DEVICE == "nrf52" || nrf.DEVICE == "nrf52840" || nrf.DEVICE == "nrf52833" {
|
||||
} else if nrf.Device == "nrf52" || nrf.Device == "nrf52840" || nrf.Device == "nrf52833" {
|
||||
// sd_app_evt_wait: SOC_SVC_BASE_NOT_AVAILABLE + 21
|
||||
arm.SVCall0(0x2C + 21)
|
||||
} else {
|
||||
|
||||
@@ -75,7 +75,6 @@ func initSystem() {
|
||||
nxp.SIM.SCGC3.Set(nxp.SIM_SCGC3_ADC1 | nxp.SIM_SCGC3_FTM2 | nxp.SIM_SCGC3_FTM3)
|
||||
nxp.SIM.SCGC5.Set(0x00043F82) // clocks active to all GPIO
|
||||
nxp.SIM.SCGC6.Set(nxp.SIM_SCGC6_RTC | nxp.SIM_SCGC6_FTM0 | nxp.SIM_SCGC6_FTM1 | nxp.SIM_SCGC6_ADC0 | nxp.SIM_SCGC6_FTF)
|
||||
nxp.SystemControl.CPACR.Set(0x00F00000)
|
||||
nxp.LMEM.PCCCR.Set(0x85000003)
|
||||
|
||||
// release I/O pins hold, if we woke up from VLLS mode
|
||||
@@ -233,9 +232,6 @@ func putchar(c byte) {
|
||||
machine.PutcharUART(&machine.UART0, c)
|
||||
}
|
||||
|
||||
// ???
|
||||
const asyncScheduler = false
|
||||
|
||||
func abort() {
|
||||
println("!!! ABORT !!!")
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@ const (
|
||||
|
||||
type arrtype = uint32
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
func init() {
|
||||
initCLK()
|
||||
|
||||
|
||||
@@ -80,8 +80,6 @@ const (
|
||||
|
||||
type arrtype = uint32
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
func init() {
|
||||
initOSC() // configure oscillators
|
||||
initCLK()
|
||||
|
||||
@@ -42,8 +42,6 @@ const (
|
||||
|
||||
type arrtype = uint32
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
func init() {
|
||||
initCLK()
|
||||
|
||||
|
||||
@@ -41,8 +41,6 @@ const (
|
||||
|
||||
type arrtype = uint32
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
func init() {
|
||||
initCLK()
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@ const (
|
||||
|
||||
type arrtype = uint16
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
func putchar(c byte) {
|
||||
machine.UART0.WriteByte(c)
|
||||
}
|
||||
|
||||
@@ -66,8 +66,6 @@ const (
|
||||
|
||||
type arrtype = uint32
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
func init() {
|
||||
initCLK()
|
||||
|
||||
|
||||
@@ -42,8 +42,6 @@ const (
|
||||
|
||||
type arrtype = uint32
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
func init() {
|
||||
initCLK()
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@ func main() {
|
||||
abort()
|
||||
}
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
func ticksToNanoseconds(ticks timeUnit) int64 {
|
||||
return int64(ticks)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ func usleep(usec uint) int
|
||||
//export malloc
|
||||
func malloc(size uintptr) unsafe.Pointer
|
||||
|
||||
//export mmap
|
||||
func mmap(addr unsafe.Pointer, length, prot, flags, fd int, offset int) unsafe.Pointer
|
||||
|
||||
//export abort
|
||||
func abort()
|
||||
|
||||
@@ -55,18 +58,16 @@ func main(argc int32, argv *unsafe.Pointer) int {
|
||||
cap uintptr
|
||||
})(unsafe.Pointer(&args))
|
||||
argsSlice.ptr = malloc(uintptr(argc) * (unsafe.Sizeof(uintptr(0))) * 3)
|
||||
argsSlice.len = 0
|
||||
argsSlice.len = uintptr(argc)
|
||||
argsSlice.cap = uintptr(argc)
|
||||
|
||||
// Initialize command line parameters.
|
||||
for *argv != nil {
|
||||
for i := 0; i < int(argc); i++ {
|
||||
// Convert the C string to a Go string.
|
||||
length := strlen(*argv)
|
||||
argString := _string{
|
||||
length: length,
|
||||
ptr: (*byte)(*argv),
|
||||
}
|
||||
args = append(args, *(*string)(unsafe.Pointer(&argString)))
|
||||
arg := (*_string)(unsafe.Pointer(&args[i]))
|
||||
arg.length = length
|
||||
arg.ptr = (*byte)(*argv)
|
||||
// This is the Go equivalent of "argc++" in C.
|
||||
argv = (*unsafe.Pointer)(unsafe.Pointer(uintptr(unsafe.Pointer(argv)) + unsafe.Sizeof(argv)))
|
||||
}
|
||||
@@ -122,8 +123,6 @@ func putchar(c byte) {
|
||||
_putchar(int(c))
|
||||
}
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
func ticksToNanoseconds(ticks timeUnit) int64 {
|
||||
// The OS API works in nanoseconds so no conversion necessary.
|
||||
return int64(ticks)
|
||||
|
||||
@@ -5,20 +5,33 @@
|
||||
|
||||
package runtime
|
||||
|
||||
const heapSize = 1 * 1024 * 1024 // 1MB to start
|
||||
var heapSize uintptr = 128 * 1024 // small amount to start
|
||||
const heapMaxSize = 1 * 1024 * 1024 * 1024 // 1GB for the entire heap
|
||||
|
||||
var heapStart, heapEnd uintptr
|
||||
|
||||
func preinit() {
|
||||
heapStart = uintptr(malloc(heapSize))
|
||||
// Allocate a large chunk of virtual memory. Because it is virtual, it won't
|
||||
// really be allocated in RAM. Memory will only be allocated when it is
|
||||
// first touched.
|
||||
addr := mmap(nil, heapMaxSize, flag_PROT_READ|flag_PROT_WRITE, flag_MAP_PRIVATE|flag_MAP_ANONYMOUS, -1, 0)
|
||||
heapStart = uintptr(addr)
|
||||
heapEnd = heapStart + heapSize
|
||||
}
|
||||
|
||||
// growHeap tries to grow the heap size. It returns true if it succeeds, false
|
||||
// otherwise.
|
||||
func growHeap() bool {
|
||||
// At the moment, this is not possible. However it shouldn't be too
|
||||
// difficult (at least on Linux) to allocate a large amount of virtual
|
||||
// memory at startup that is then slowly used.
|
||||
return false
|
||||
if heapSize == heapMaxSize {
|
||||
// Already at the max. If we run out of memory, we should consider
|
||||
// increasing heapMaxSize on 64-bit systems.
|
||||
return false
|
||||
}
|
||||
// Grow the heap size used by the program.
|
||||
heapSize = (heapSize * 4 / 3) &^ 4095 // grow by around 33%
|
||||
if heapSize > heapMaxSize {
|
||||
heapSize = heapMaxSize
|
||||
}
|
||||
setHeapEnd(heapStart + heapSize)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -40,8 +40,6 @@ func go_scheduler() {
|
||||
scheduler()
|
||||
}
|
||||
|
||||
const asyncScheduler = true
|
||||
|
||||
func ticksToNanoseconds(ticks timeUnit) int64 {
|
||||
// The JavaScript API works in float64 milliseconds, so convert to
|
||||
// nanoseconds first before converting to a timeUnit (which is a float64),
|
||||
|
||||
@@ -30,6 +30,9 @@ func init() {
|
||||
// these args (argv).
|
||||
var argc, argv_buf_size uint32
|
||||
args_sizes_get(&argc, &argv_buf_size)
|
||||
if argc == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Obtain the command line arguments
|
||||
argsSlice := make([]unsafe.Pointer, argc)
|
||||
@@ -56,10 +59,7 @@ func nanosecondsToTicks(ns int64) timeUnit {
|
||||
return timeUnit(ns)
|
||||
}
|
||||
|
||||
const (
|
||||
asyncScheduler = false
|
||||
timePrecisionNanoseconds = 1000 // TODO: how can we determine the appropriate `precision`?
|
||||
)
|
||||
const timePrecisionNanoseconds = 1000 // TODO: how can we determine the appropriate `precision`?
|
||||
|
||||
var (
|
||||
sleepTicksSubscription = __wasi_subscription_t{
|
||||
|
||||
@@ -20,6 +20,10 @@ import (
|
||||
|
||||
const schedulerDebug = false
|
||||
|
||||
// On JavaScript, we can't do a blocking sleep. Instead we have to return and
|
||||
// queue a new scheduler invocation using setTimeout.
|
||||
const asyncScheduler = GOOS == "js"
|
||||
|
||||
var schedulerDone bool
|
||||
|
||||
// Queues used by the scheduler.
|
||||
@@ -138,6 +142,7 @@ func scheduler() {
|
||||
if t == nil {
|
||||
if sleepQueue == nil {
|
||||
if asyncScheduler {
|
||||
// JavaScript is treated specially, see below.
|
||||
return
|
||||
}
|
||||
waitForEvents()
|
||||
@@ -154,7 +159,8 @@ func scheduler() {
|
||||
if asyncScheduler {
|
||||
// The sleepTicks function above only sets a timeout at which
|
||||
// point the scheduler will be called again. It does not really
|
||||
// sleep.
|
||||
// sleep. So instead of sleeping, we return and expect to be
|
||||
// called again.
|
||||
break
|
||||
}
|
||||
continue
|
||||
|
||||
@@ -201,6 +201,10 @@ type M struct {
|
||||
|
||||
// Run the test suite.
|
||||
func (m *M) Run() int {
|
||||
if len(m.Tests) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "testing: warning: no tests to run")
|
||||
}
|
||||
|
||||
failures := 0
|
||||
for _, test := range m.Tests {
|
||||
t := &T{
|
||||
@@ -226,7 +230,6 @@ func (m *M) Run() int {
|
||||
}
|
||||
|
||||
if failures > 0 {
|
||||
fmt.Printf("exit status %d\n", failures)
|
||||
fmt.Println("FAIL")
|
||||
} else {
|
||||
fmt.Println("PASS")
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"inherits": ["arduino-nano"],
|
||||
"flash-command": "avrdude -c arduino -p atmega328p -b 115200 -P {port} -U flash:w:{hex}:i"
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"llvm-target": "aarch64",
|
||||
"build-tags": ["nintendoswitch", "arm64"],
|
||||
"scheduler": "tasks",
|
||||
"goos": "linux",
|
||||
"goarch": "arm64",
|
||||
"linker": "ld.lld",
|
||||
@@ -9,6 +10,7 @@
|
||||
"gc": "conservative",
|
||||
"relocation-model": "pic",
|
||||
"cpu": "cortex-a57",
|
||||
"default-stack-size": 2048,
|
||||
"cflags": [
|
||||
"-target", "aarch64-unknown-none",
|
||||
"-mcpu=cortex-a57",
|
||||
@@ -26,6 +28,7 @@
|
||||
"linkerscript": "targets/nintendoswitch.ld",
|
||||
"extra-files": [
|
||||
"targets/nintendoswitch.s",
|
||||
"src/internal/task/task_stack_arm64.S",
|
||||
"src/runtime/gc_arm64.S",
|
||||
"src/runtime/runtime_nintendoswitch.s"
|
||||
]
|
||||
|
||||
@@ -19,10 +19,15 @@ var validName = regexp.MustCompile("^[a-zA-Z0-9_]+$")
|
||||
var enumBitSpecifier = regexp.MustCompile("^#[x01]+$")
|
||||
|
||||
type SVDFile struct {
|
||||
XMLName xml.Name `xml:"device"`
|
||||
Name string `xml:"name"`
|
||||
Description string `xml:"description"`
|
||||
LicenseText string `xml:"licenseText"`
|
||||
XMLName xml.Name `xml:"device"`
|
||||
Name string `xml:"name"`
|
||||
Description string `xml:"description"`
|
||||
LicenseText string `xml:"licenseText"`
|
||||
CPU *struct {
|
||||
Name string `xml:"name"`
|
||||
FPUPresent bool `xml:"fpuPresent"`
|
||||
NVICPrioBits int `xml:"nvicPrioBits"`
|
||||
} `xml:"cpu"`
|
||||
Peripherals []SVDPeripheral `xml:"peripherals>peripheral"`
|
||||
}
|
||||
|
||||
@@ -95,6 +100,11 @@ type Metadata struct {
|
||||
NameLower string
|
||||
Description string
|
||||
LicenseBlock string
|
||||
|
||||
HasCPUInfo bool // set if the following fields are populated
|
||||
CPUName string
|
||||
FPUPresent bool
|
||||
NVICPrioBits int
|
||||
}
|
||||
|
||||
type Interrupt struct {
|
||||
@@ -418,15 +428,22 @@ func readSVD(path, sourceURL string) (*Device, error) {
|
||||
licenseBlock = regexp.MustCompile(`\s+\n`).ReplaceAllString(licenseBlock, "\n")
|
||||
}
|
||||
|
||||
metadata := &Metadata{
|
||||
File: filepath.Base(path),
|
||||
DescriptorSource: sourceURL,
|
||||
Name: device.Name,
|
||||
NameLower: strings.ToLower(device.Name),
|
||||
Description: strings.TrimSpace(device.Description),
|
||||
LicenseBlock: licenseBlock,
|
||||
}
|
||||
if device.CPU != nil {
|
||||
metadata.HasCPUInfo = true
|
||||
metadata.CPUName = device.CPU.Name
|
||||
metadata.FPUPresent = device.CPU.FPUPresent
|
||||
metadata.NVICPrioBits = device.CPU.NVICPrioBits
|
||||
}
|
||||
return &Device{
|
||||
Metadata: &Metadata{
|
||||
File: filepath.Base(path),
|
||||
DescriptorSource: sourceURL,
|
||||
Name: device.Name,
|
||||
NameLower: strings.ToLower(device.Name),
|
||||
Description: strings.TrimSpace(device.Description),
|
||||
LicenseBlock: licenseBlock,
|
||||
},
|
||||
Metadata: metadata,
|
||||
Interrupts: interruptList,
|
||||
Peripherals: peripheralsList,
|
||||
}, nil
|
||||
@@ -833,7 +850,12 @@ import (
|
||||
|
||||
// Some information about this device.
|
||||
const (
|
||||
DEVICE = "{{.device.Metadata.Name}}"
|
||||
Device = "{{.device.Metadata.Name}}"
|
||||
{{- if .device.Metadata.HasCPUInfo }}
|
||||
CPU = "{{.device.Metadata.CPUName}}"
|
||||
FPUPresent = {{.device.Metadata.FPUPresent}}
|
||||
NVICPrioBits = {{.device.Metadata.NVICPrioBits}}
|
||||
{{- end }}
|
||||
)
|
||||
|
||||
// Interrupt numbers.
|
||||
|
||||
@@ -2,7 +2,6 @@ package transform_test
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -11,9 +10,6 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"github.com/tinygo-org/tinygo/compiler"
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
"github.com/tinygo-org/tinygo/transform"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
@@ -39,52 +35,7 @@ func (out allocsTestOutput) String() string {
|
||||
func TestAllocs2(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
target, err := compileopts.LoadTarget("i686--linux")
|
||||
if err != nil {
|
||||
t.Fatal("failed to load target:", err)
|
||||
}
|
||||
config := &compileopts.Config{
|
||||
Options: &compileopts.Options{},
|
||||
Target: target,
|
||||
}
|
||||
compilerConfig := &compiler.Config{
|
||||
Triple: config.Triple(),
|
||||
GOOS: config.GOOS(),
|
||||
GOARCH: config.GOARCH(),
|
||||
CodeModel: config.CodeModel(),
|
||||
RelocationModel: config.RelocationModel(),
|
||||
Scheduler: config.Scheduler(),
|
||||
FuncImplementation: config.FuncImplementation(),
|
||||
AutomaticStackSize: config.AutomaticStackSize(),
|
||||
Debug: true,
|
||||
}
|
||||
machine, err := compiler.NewTargetMachine(compilerConfig)
|
||||
if err != nil {
|
||||
t.Fatal("failed to create target machine:", err)
|
||||
}
|
||||
|
||||
// Load entire program AST into memory.
|
||||
lprogram, err := loader.Load(config, []string{"./testdata/allocs2.go"}, config.ClangHeaders, types.Config{
|
||||
Sizes: compiler.Sizes(machine),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("failed to create target machine:", err)
|
||||
}
|
||||
err = lprogram.Parse()
|
||||
if err != nil {
|
||||
t.Fatal("could not parse", err)
|
||||
}
|
||||
|
||||
// Compile AST to IR.
|
||||
program := lprogram.LoadSSA()
|
||||
pkg := lprogram.MainPkg()
|
||||
mod, errs := compiler.CompilePackage("allocs2.go", pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
|
||||
if errs != nil {
|
||||
for _, err := range errs {
|
||||
t.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
mod := compileGoFileForTesting(t, "./testdata/allocs2.go")
|
||||
|
||||
// Run functionattrs pass, which is necessary for escape analysis.
|
||||
pm := llvm.NewPassManager()
|
||||
|
||||
+74
-190
@@ -74,12 +74,10 @@ type methodInfo struct {
|
||||
// typeInfo describes a single concrete Go type, which can be a basic or a named
|
||||
// type. If it is a named type, it may have methods.
|
||||
type typeInfo struct {
|
||||
name string
|
||||
typecode llvm.Value
|
||||
methodSet llvm.Value
|
||||
num uint64 // the type number after lowering
|
||||
countTypeAsserts int // how often a type assert happens on this method
|
||||
methods []*methodInfo
|
||||
name string
|
||||
typecode llvm.Value
|
||||
methodSet llvm.Value
|
||||
methods []*methodInfo
|
||||
}
|
||||
|
||||
// getMethod looks up the method on this type with the given signature and
|
||||
@@ -94,27 +92,13 @@ func (t *typeInfo) getMethod(signature *signatureInfo) *methodInfo {
|
||||
panic("could not find method")
|
||||
}
|
||||
|
||||
// typeInfoSlice implements sort.Slice, sorting the most commonly used types
|
||||
// first.
|
||||
type typeInfoSlice []*typeInfo
|
||||
|
||||
func (t typeInfoSlice) Len() int { return len(t) }
|
||||
func (t typeInfoSlice) Less(i, j int) bool {
|
||||
// Try to sort the most commonly used types first.
|
||||
if t[i].countTypeAsserts != t[j].countTypeAsserts {
|
||||
return t[i].countTypeAsserts < t[j].countTypeAsserts
|
||||
}
|
||||
return t[i].name < t[j].name
|
||||
}
|
||||
func (t typeInfoSlice) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||
|
||||
// interfaceInfo keeps information about a Go interface type, including all
|
||||
// methods it has.
|
||||
type interfaceInfo struct {
|
||||
name string // name with $interface suffix
|
||||
methodSet llvm.Value // global which this interfaceInfo describes
|
||||
signatures []*signatureInfo // method set
|
||||
types typeInfoSlice // types this interface implements
|
||||
types []*typeInfo // types this interface implements
|
||||
assertFunc llvm.Value // runtime.interfaceImplements replacement
|
||||
methodFuncs map[*signatureInfo]llvm.Value // runtime.interfaceMethod replacements for each signature
|
||||
}
|
||||
@@ -163,7 +147,6 @@ func LowerInterfaces(mod llvm.Module, sizeLevel int) error {
|
||||
// run runs the pass itself.
|
||||
func (p *lowerInterfacesPass) run() error {
|
||||
// Collect all type codes.
|
||||
var typecodeIDs []llvm.Value
|
||||
for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||
if strings.HasPrefix(global.Name(), "reflect/types.type:") {
|
||||
// Retrieve Go type information based on an opaque global variable.
|
||||
@@ -171,7 +154,6 @@ func (p *lowerInterfacesPass) run() error {
|
||||
// discarded afterwards.
|
||||
name := strings.TrimPrefix(global.Name(), "reflect/types.type:")
|
||||
if _, ok := p.types[name]; !ok {
|
||||
typecodeIDs = append(typecodeIDs, global)
|
||||
t := &typeInfo{
|
||||
name: name,
|
||||
typecode: global,
|
||||
@@ -187,18 +169,6 @@ func (p *lowerInterfacesPass) run() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Count per type how often it is type asserted on (e.g. in a switch
|
||||
// statement).
|
||||
typeAssert := p.mod.NamedFunction("runtime.typeAssert")
|
||||
typeAssertUses := getUses(typeAssert)
|
||||
for _, use := range typeAssertUses {
|
||||
typecode := use.Operand(1)
|
||||
name := strings.TrimPrefix(typecode.Name(), "reflect/types.typeid:")
|
||||
if t, ok := p.types[name]; ok {
|
||||
t.countTypeAsserts++
|
||||
}
|
||||
}
|
||||
|
||||
// Find all interface method calls.
|
||||
interfaceMethod := p.mod.NamedFunction("runtime.interfaceMethod")
|
||||
interfaceMethodUses := getUses(interfaceMethod)
|
||||
@@ -274,10 +244,11 @@ func (p *lowerInterfacesPass) run() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Sort all types added to the interfaces, to check for more common types
|
||||
// first.
|
||||
// Sort all types added to the interfaces.
|
||||
for _, itf := range p.interfaces {
|
||||
sort.Sort(itf.types)
|
||||
sort.Slice(itf.types, func(i, j int) bool {
|
||||
return itf.types[i].name > itf.types[j].name
|
||||
})
|
||||
}
|
||||
|
||||
// Replace all interface methods with their uses, if possible.
|
||||
@@ -339,43 +310,10 @@ func (p *lowerInterfacesPass) run() error {
|
||||
use.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
// Make a slice of types sorted by frequency of use.
|
||||
typeSlice := make(typeInfoSlice, 0, len(p.types))
|
||||
for _, t := range p.types {
|
||||
typeSlice = append(typeSlice, t)
|
||||
}
|
||||
sort.Sort(sort.Reverse(typeSlice))
|
||||
|
||||
// Assign a type code for each type.
|
||||
assignTypeCodes(p.mod, typeSlice)
|
||||
|
||||
// Replace each use of a ptrtoint runtime.typecodeID with the constant type
|
||||
// code.
|
||||
for _, global := range typecodeIDs {
|
||||
for _, use := range getUses(global) {
|
||||
if use.IsAConstantExpr().IsNil() {
|
||||
continue
|
||||
}
|
||||
t := p.types[strings.TrimPrefix(global.Name(), "reflect/types.type:")]
|
||||
typecode := llvm.ConstInt(p.uintptrType, t.num, false)
|
||||
switch use.Opcode() {
|
||||
case llvm.PtrToInt:
|
||||
// Already of the correct type.
|
||||
case llvm.BitCast:
|
||||
// Could happen when stored in an interface (which is of type
|
||||
// i8*).
|
||||
typecode = llvm.ConstIntToPtr(typecode, use.Type())
|
||||
default:
|
||||
panic("unexpected constant expression")
|
||||
}
|
||||
use.ReplaceAllUsesWith(typecode)
|
||||
}
|
||||
}
|
||||
|
||||
// Replace each type assert with an actual type comparison or (if the type
|
||||
// assert is impossible) the constant false.
|
||||
llvmFalse := llvm.ConstInt(p.ctx.Int1Type(), 0, false)
|
||||
for _, use := range typeAssertUses {
|
||||
for _, use := range getUses(p.mod.NamedFunction("runtime.typeAssert")) {
|
||||
actualType := use.Operand(0)
|
||||
name := strings.TrimPrefix(use.Operand(1).Name(), "reflect/types.typeid:")
|
||||
if t, ok := p.types[name]; ok {
|
||||
@@ -395,54 +333,13 @@ func (p *lowerInterfacesPass) run() error {
|
||||
use.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
// Fill in each helper function for type asserts on interfaces
|
||||
// (interface-to-interface matches).
|
||||
for _, itf := range p.interfaces {
|
||||
if !itf.assertFunc.IsNil() {
|
||||
p.createInterfaceImplementsFunc(itf)
|
||||
}
|
||||
for signature := range itf.methodFuncs {
|
||||
p.createInterfaceMethodFunc(itf, signature)
|
||||
}
|
||||
}
|
||||
|
||||
// Replace all ptrtoint typecode placeholders with their final type code
|
||||
// numbers.
|
||||
for _, typ := range p.types {
|
||||
for _, use := range getUses(typ.typecode) {
|
||||
if !use.IsAConstantExpr().IsNil() && use.Opcode() == llvm.PtrToInt {
|
||||
use.ReplaceAllUsesWith(llvm.ConstInt(p.uintptrType, typ.num, false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove most objects created for interface and reflect lowering.
|
||||
// Unnecessary, but cleans up the IR for inspection and testing.
|
||||
for _, typ := range p.types {
|
||||
// Only some typecodes have an initializer.
|
||||
initializer := typ.typecode.Initializer()
|
||||
if !initializer.IsNil() {
|
||||
references := llvm.ConstExtractValue(initializer, []uint32{0})
|
||||
typ.typecode.SetInitializer(llvm.ConstNull(initializer.Type()))
|
||||
if strings.HasPrefix(typ.name, "reflect/types.type:struct:") {
|
||||
// Structs have a 'references' field that is not a typecode but
|
||||
// a pointer to a runtime.structField array and therefore a
|
||||
// bitcast. This global should be erased separately, otherwise
|
||||
// typecode objects cannot be erased.
|
||||
structFields := references.Operand(0)
|
||||
structFields.EraseFromParentAsGlobal()
|
||||
}
|
||||
}
|
||||
|
||||
if !typ.methodSet.IsNil() {
|
||||
typ.methodSet.EraseFromParentAsGlobal()
|
||||
typ.methodSet = llvm.Value{}
|
||||
}
|
||||
}
|
||||
for _, itf := range p.interfaces {
|
||||
// Remove method sets of interfaces.
|
||||
itf.methodSet.EraseFromParentAsGlobal()
|
||||
itf.methodSet = llvm.Value{}
|
||||
// Remove all method sets, which are now unnecessary and inhibit later
|
||||
// optimizations if they are left in place.
|
||||
for _, t := range p.types {
|
||||
initializer := t.typecode.Initializer()
|
||||
methodSet := llvm.ConstExtractValue(initializer, []uint32{2})
|
||||
initializer = llvm.ConstInsertValue(initializer, llvm.ConstNull(methodSet.Type()), []uint32{2})
|
||||
t.typecode.SetInitializer(initializer)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -559,6 +456,10 @@ func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInf
|
||||
// getInterfaceImplementsFunc returns a function that checks whether a given
|
||||
// interface type implements a given interface, by checking all possible types
|
||||
// that implement this interface.
|
||||
//
|
||||
// The type match is implemented using an if/else chain over all possible types.
|
||||
// This if/else chain is easily converted to a big switch over all possible
|
||||
// types by the LLVM simplifycfg pass.
|
||||
func (p *lowerInterfacesPass) getInterfaceImplementsFunc(itf *interfaceInfo) llvm.Value {
|
||||
if !itf.assertFunc.IsNil() {
|
||||
return itf.assertFunc
|
||||
@@ -568,60 +469,49 @@ func (p *lowerInterfacesPass) getInterfaceImplementsFunc(itf *interfaceInfo) llv
|
||||
// TODO: debug info
|
||||
fnName := itf.id() + "$typeassert"
|
||||
fnType := llvm.FunctionType(p.ctx.Int1Type(), []llvm.Type{p.uintptrType}, false)
|
||||
itf.assertFunc = llvm.AddFunction(p.mod, fnName, fnType)
|
||||
itf.assertFunc.Param(0).SetName("actualType")
|
||||
|
||||
// Type asserts will be made for each type, so increment the counter for
|
||||
// those.
|
||||
for _, typ := range itf.types {
|
||||
typ.countTypeAsserts++
|
||||
}
|
||||
|
||||
return itf.assertFunc
|
||||
}
|
||||
|
||||
// createInterfaceImplementsFunc finishes the work of
|
||||
// getInterfaceImplementsFunc, because it needs to run after types have a type
|
||||
// code assigned.
|
||||
//
|
||||
// The type match is implemented using a big type switch over all possible
|
||||
// types.
|
||||
func (p *lowerInterfacesPass) createInterfaceImplementsFunc(itf *interfaceInfo) {
|
||||
fn := itf.assertFunc
|
||||
fn := llvm.AddFunction(p.mod, fnName, fnType)
|
||||
itf.assertFunc = fn
|
||||
fn.Param(0).SetName("actualType")
|
||||
fn.SetLinkage(llvm.InternalLinkage)
|
||||
fn.SetUnnamedAddr(true)
|
||||
if p.sizeLevel >= 2 {
|
||||
fn.AddFunctionAttr(p.ctx.CreateEnumAttribute(llvm.AttributeKindID("optsize"), 0))
|
||||
}
|
||||
|
||||
// TODO: debug info
|
||||
|
||||
// Create all used basic blocks.
|
||||
// Start the if/else chain at the entry block.
|
||||
entry := p.ctx.AddBasicBlock(fn, "entry")
|
||||
thenBlock := p.ctx.AddBasicBlock(fn, "then")
|
||||
elseBlock := p.ctx.AddBasicBlock(fn, "else")
|
||||
|
||||
// Add all possible types as cases.
|
||||
p.builder.SetInsertPointAtEnd(entry)
|
||||
|
||||
// Iterate over all possible types. Each iteration creates a new branch
|
||||
// either to the 'then' block (success) or the .next block, for the next
|
||||
// check.
|
||||
actualType := fn.Param(0)
|
||||
sw := p.builder.CreateSwitch(actualType, elseBlock, len(itf.types))
|
||||
for _, typ := range itf.types {
|
||||
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), thenBlock)
|
||||
nextBlock := p.ctx.AddBasicBlock(fn, typ.name+".next")
|
||||
cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, llvm.ConstPtrToInt(typ.typecode, p.uintptrType), typ.name+".icmp")
|
||||
p.builder.CreateCondBr(cmp, thenBlock, nextBlock)
|
||||
p.builder.SetInsertPointAtEnd(nextBlock)
|
||||
}
|
||||
|
||||
// The builder is now inserting at the last *.next block. Once we reach
|
||||
// this point, all types have been checked so the type assert will have
|
||||
// failed.
|
||||
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 0, false))
|
||||
|
||||
// Fill 'then' block (type assert was successful).
|
||||
p.builder.SetInsertPointAtEnd(thenBlock)
|
||||
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 1, false))
|
||||
|
||||
// Fill 'else' block (type asserted failed).
|
||||
p.builder.SetInsertPointAtEnd(elseBlock)
|
||||
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 0, false))
|
||||
return itf.assertFunc
|
||||
}
|
||||
|
||||
// getInterfaceMethodFunc returns a thunk for calling a method on an interface.
|
||||
// It only declares the function, createInterfaceMethodFunc actually defines the
|
||||
// function.
|
||||
func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signature *signatureInfo, returnType llvm.Type, params []llvm.Type) llvm.Value {
|
||||
//
|
||||
// Matching the actual type is implemented using an if/else chain over all
|
||||
// possible types. This is later converted to a switch statement by the LLVM
|
||||
// simplifycfg pass.
|
||||
func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signature *signatureInfo, returnType llvm.Type, paramTypes []llvm.Type) llvm.Value {
|
||||
if fn, ok := itf.methodFuncs[signature]; ok {
|
||||
// This function has already been created.
|
||||
return fn
|
||||
@@ -634,22 +524,11 @@ func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signatu
|
||||
// Construct the function name, which is of the form:
|
||||
// (main.Stringer).String
|
||||
fnName := "(" + itf.id() + ")." + signature.methodName()
|
||||
fnType := llvm.FunctionType(returnType, append(params, llvm.PointerType(p.ctx.Int8Type(), 0)), false)
|
||||
fnType := llvm.FunctionType(returnType, append(paramTypes, llvm.PointerType(p.ctx.Int8Type(), 0)), false)
|
||||
fn := llvm.AddFunction(p.mod, fnName, fnType)
|
||||
llvm.PrevParam(fn.LastParam()).SetName("actualType")
|
||||
fn.LastParam().SetName("parentHandle")
|
||||
itf.methodFuncs[signature] = fn
|
||||
return fn
|
||||
}
|
||||
|
||||
// createInterfaceMethodFunc finishes the work of getInterfaceMethodFunc,
|
||||
// because it needs to run after type codes have been assigned to concrete
|
||||
// types.
|
||||
//
|
||||
// Matching the actual type is implemented using a big type switch over all
|
||||
// possible types.
|
||||
func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, signature *signatureInfo) {
|
||||
fn := itf.methodFuncs[signature]
|
||||
fn.SetLinkage(llvm.InternalLinkage)
|
||||
fn.SetUnnamedAddr(true)
|
||||
if p.sizeLevel >= 2 {
|
||||
@@ -658,29 +537,6 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
|
||||
|
||||
// TODO: debug info
|
||||
|
||||
// Create entry block.
|
||||
entry := p.ctx.AddBasicBlock(fn, "entry")
|
||||
|
||||
// Create default block and call runtime.nilPanic.
|
||||
// The only other possible value remaining is nil for nil interfaces. We
|
||||
// could panic with a different message here such as "nil interface" but
|
||||
// that would increase code size and "nil panic" is close enough. Most
|
||||
// importantly, it avoids undefined behavior when accidentally calling a
|
||||
// method on a nil interface.
|
||||
defaultBlock := p.ctx.AddBasicBlock(fn, "default")
|
||||
p.builder.SetInsertPointAtEnd(defaultBlock)
|
||||
nilPanic := p.mod.NamedFunction("runtime.nilPanic")
|
||||
p.builder.CreateCall(nilPanic, []llvm.Value{
|
||||
llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
|
||||
llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
|
||||
}, "")
|
||||
p.builder.CreateUnreachable()
|
||||
|
||||
// Create type switch in entry block.
|
||||
p.builder.SetInsertPointAtEnd(entry)
|
||||
actualType := llvm.PrevParam(fn.LastParam())
|
||||
sw := p.builder.CreateSwitch(actualType, defaultBlock, len(itf.types))
|
||||
|
||||
// Collect the params that will be passed to the functions to call.
|
||||
// These params exclude the receiver (which may actually consist of multiple
|
||||
// parts).
|
||||
@@ -689,10 +545,18 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
|
||||
params[i] = fn.Param(i + 1)
|
||||
}
|
||||
|
||||
// Start chain in the entry block.
|
||||
entry := p.ctx.AddBasicBlock(fn, "entry")
|
||||
p.builder.SetInsertPointAtEnd(entry)
|
||||
|
||||
// Define all possible functions that can be called.
|
||||
actualType := llvm.PrevParam(fn.LastParam())
|
||||
for _, typ := range itf.types {
|
||||
// Create type check (if/else).
|
||||
bb := p.ctx.AddBasicBlock(fn, typ.name)
|
||||
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), bb)
|
||||
next := p.ctx.AddBasicBlock(fn, typ.name+".next")
|
||||
cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, llvm.ConstPtrToInt(typ.typecode, p.uintptrType), typ.name+".icmp")
|
||||
p.builder.CreateCondBr(cmp, bb, next)
|
||||
|
||||
// The function we will redirect to when the interface has this type.
|
||||
function := typ.getMethod(signature).function
|
||||
@@ -725,5 +589,25 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
|
||||
} else {
|
||||
p.builder.CreateRet(retval)
|
||||
}
|
||||
|
||||
// Start next comparison in the 'next' block (which is jumped to when
|
||||
// the type doesn't match).
|
||||
p.builder.SetInsertPointAtEnd(next)
|
||||
}
|
||||
|
||||
// The builder now points to the last *.then block, after all types have
|
||||
// been checked. Call runtime.nilPanic here.
|
||||
// The only other possible value remaining is nil for nil interfaces. We
|
||||
// could panic with a different message here such as "nil interface" but
|
||||
// that would increase code size and "nil panic" is close enough. Most
|
||||
// importantly, it avoids undefined behavior when accidentally calling a
|
||||
// method on a nil interface.
|
||||
nilPanic := p.mod.NamedFunction("runtime.nilPanic")
|
||||
p.builder.CreateCall(nilPanic, []llvm.Value{
|
||||
llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
|
||||
llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
|
||||
}, "")
|
||||
p.builder.CreateUnreachable()
|
||||
|
||||
return fn
|
||||
}
|
||||
|
||||
@@ -14,5 +14,10 @@ func TestInterfaceLowering(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pm := llvm.NewPassManager()
|
||||
defer pm.Dispose()
|
||||
pm.AddGlobalDCEPass()
|
||||
pm.Run(mod)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
|
||||
goPasses.AddFunctionAttrsPass()
|
||||
goPasses.Run(mod)
|
||||
|
||||
// Run Go-specific optimization passes.
|
||||
// Run TinyGo-specific optimization passes.
|
||||
OptimizeMaps(mod)
|
||||
OptimizeStringToBytes(mod)
|
||||
OptimizeReflectImplements(mod)
|
||||
@@ -88,6 +88,7 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
|
||||
goPasses.Run(mod)
|
||||
|
||||
// Run TinyGo-specific interprocedural optimizations.
|
||||
LowerReflect(mod)
|
||||
OptimizeAllocs(mod, config.Options.PrintAllocs, func(pos token.Position, msg string) {
|
||||
fmt.Fprintln(os.Stderr, pos.String()+": "+msg)
|
||||
})
|
||||
@@ -100,6 +101,7 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
LowerReflect(mod)
|
||||
if config.FuncImplementation() == "switch" {
|
||||
LowerFuncValues(mod)
|
||||
}
|
||||
|
||||
+71
-4
@@ -31,6 +31,7 @@ import (
|
||||
"encoding/binary"
|
||||
"go/ast"
|
||||
"math/big"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
@@ -122,14 +123,45 @@ type typeCodeAssignmentState struct {
|
||||
needsNamedNonBasicTypesSidetable bool
|
||||
}
|
||||
|
||||
// assignTypeCodes is used to assign a type code to each type in the program
|
||||
// LowerReflect is used to assign a type code to each type in the program
|
||||
// that is ever stored in an interface. It tries to use the smallest possible
|
||||
// numbers to make the code that works with interfaces as small as possible.
|
||||
func assignTypeCodes(mod llvm.Module, typeSlice typeInfoSlice) {
|
||||
func LowerReflect(mod llvm.Module) {
|
||||
// if reflect were not used, we could skip generating the sidetable
|
||||
// this does not help in practice, and is difficult to do correctly
|
||||
|
||||
// Obtain slice of all types in the program.
|
||||
type typeInfo struct {
|
||||
typecode llvm.Value
|
||||
name string
|
||||
numUses int
|
||||
}
|
||||
var types []*typeInfo
|
||||
for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||
if strings.HasPrefix(global.Name(), "reflect/types.type:") {
|
||||
types = append(types, &typeInfo{
|
||||
typecode: global,
|
||||
name: global.Name(),
|
||||
numUses: len(getUses(global)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the slice in a way that often used types are assigned a type code
|
||||
// first.
|
||||
sort.Slice(types, func(i, j int) bool {
|
||||
if types[i].numUses != types[j].numUses {
|
||||
return types[i].numUses < types[j].numUses
|
||||
}
|
||||
// It would make more sense to compare the name in the other direction,
|
||||
// but for some reason that increases binary size. Could be a fluke, but
|
||||
// could also have some good reason (and possibly hint at a small
|
||||
// optimization).
|
||||
return types[i].name > types[j].name
|
||||
})
|
||||
|
||||
// Assign typecodes the way the reflect package expects.
|
||||
uintptrType := mod.Context().IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
|
||||
state := typeCodeAssignmentState{
|
||||
fallbackIndex: 1,
|
||||
uintptrLen: llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8,
|
||||
@@ -143,7 +175,7 @@ func assignTypeCodes(mod llvm.Module, typeSlice typeInfoSlice) {
|
||||
needsStructNamesSidetable: len(getUses(mod.NamedGlobal("reflect.structNamesSidetable"))) != 0,
|
||||
needsArrayTypesSidetable: len(getUses(mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0,
|
||||
}
|
||||
for _, t := range typeSlice {
|
||||
for _, t := range types {
|
||||
num := state.getTypeCodeNum(t.typecode)
|
||||
if num.BitLen() > state.uintptrLen || !num.IsUint64() {
|
||||
// TODO: support this in some way, using a side table for example.
|
||||
@@ -152,7 +184,25 @@ func assignTypeCodes(mod llvm.Module, typeSlice typeInfoSlice) {
|
||||
// AVR).
|
||||
panic("compiler: could not store type code number inside interface type code")
|
||||
}
|
||||
t.num = num.Uint64()
|
||||
|
||||
// Replace each use of the type code global with the constant type code.
|
||||
for _, use := range getUses(t.typecode) {
|
||||
if use.IsAConstantExpr().IsNil() {
|
||||
continue
|
||||
}
|
||||
typecode := llvm.ConstInt(uintptrType, num.Uint64(), false)
|
||||
switch use.Opcode() {
|
||||
case llvm.PtrToInt:
|
||||
// Already of the correct type.
|
||||
case llvm.BitCast:
|
||||
// Could happen when stored in an interface (which is of type
|
||||
// i8*).
|
||||
typecode = llvm.ConstIntToPtr(typecode, use.Type())
|
||||
default:
|
||||
panic("unexpected constant expression")
|
||||
}
|
||||
use.ReplaceAllUsesWith(typecode)
|
||||
}
|
||||
}
|
||||
|
||||
// Only create this sidetable when it is necessary.
|
||||
@@ -180,6 +230,23 @@ func assignTypeCodes(mod llvm.Module, typeSlice typeInfoSlice) {
|
||||
global.SetUnnamedAddr(true)
|
||||
global.SetGlobalConstant(true)
|
||||
}
|
||||
|
||||
// Remove most objects created for interface and reflect lowering.
|
||||
// They would normally be removed anyway in later passes, but not always.
|
||||
// It also cleans up the IR for testing.
|
||||
for _, typ := range types {
|
||||
initializer := typ.typecode.Initializer()
|
||||
references := llvm.ConstExtractValue(initializer, []uint32{0})
|
||||
typ.typecode.SetInitializer(llvm.ConstNull(initializer.Type()))
|
||||
if strings.HasPrefix(typ.name, "reflect/types.type:struct:") {
|
||||
// Structs have a 'references' field that is not a typecode but
|
||||
// a pointer to a runtime.structField array and therefore a
|
||||
// bitcast. This global should be erased separately, otherwise
|
||||
// typecode objects cannot be erased.
|
||||
structFields := references.Operand(0)
|
||||
structFields.EraseFromParentAsGlobal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getTypeCodeNum returns the typecode for a given type as expected by the
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package transform_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/tinygo-org/tinygo/transform"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type reflectAssert struct {
|
||||
call llvm.Value
|
||||
name string
|
||||
expectedNumber uint64
|
||||
}
|
||||
|
||||
// Test reflect lowering. This code looks at IR like this:
|
||||
//
|
||||
// call void @main.assertType(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:int" to i32), i8* inttoptr (i32 3 to i8*), i32 4, i8* undef, i8* undef)
|
||||
//
|
||||
// and verifies that the ptrtoint constant (the first parameter of
|
||||
// @main.assertType) is replaced with the correct type code. The expected
|
||||
// output is this:
|
||||
//
|
||||
// call void @main.assertType(i32 4, i8* inttoptr (i32 3 to i8*), i32 4, i8* undef, i8* undef)
|
||||
//
|
||||
// The first and third parameter are compared and must match, the second
|
||||
// parameter is ignored.
|
||||
func TestReflect(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mod := compileGoFileForTesting(t, "./testdata/reflect.go")
|
||||
|
||||
// Run the instcombine pass, to clean up the IR a bit (especially
|
||||
// insertvalue/extractvalue instructions).
|
||||
pm := llvm.NewPassManager()
|
||||
defer pm.Dispose()
|
||||
pm.AddInstructionCombiningPass()
|
||||
pm.Run(mod)
|
||||
|
||||
// Get a list of all the asserts in the source code.
|
||||
assertType := mod.NamedFunction("main.assertType")
|
||||
var asserts []reflectAssert
|
||||
for user := assertType.FirstUse(); !user.IsNil(); user = user.NextUse() {
|
||||
use := user.User()
|
||||
if use.IsACallInst().IsNil() {
|
||||
t.Fatal("expected call use of main.assertType")
|
||||
}
|
||||
global := use.Operand(0).Operand(0)
|
||||
expectedNumber := use.Operand(2).ZExtValue()
|
||||
asserts = append(asserts, reflectAssert{
|
||||
call: use,
|
||||
name: global.Name(),
|
||||
expectedNumber: expectedNumber,
|
||||
})
|
||||
}
|
||||
|
||||
// Sanity check to show that the test is actually testing anything.
|
||||
if len(asserts) < 3 {
|
||||
t.Errorf("expected at least 3 test cases, got %d", len(asserts))
|
||||
}
|
||||
|
||||
// Now lower the type codes.
|
||||
transform.LowerReflect(mod)
|
||||
|
||||
// Check whether the values are as expected.
|
||||
for _, assert := range asserts {
|
||||
actualNumberValue := assert.call.Operand(0)
|
||||
if actualNumberValue.IsAConstantInt().IsNil() {
|
||||
t.Errorf("expected to see a constant for %s, got something else", assert.name)
|
||||
continue
|
||||
}
|
||||
actualNumber := actualNumberValue.ZExtValue()
|
||||
if actualNumber != assert.expectedNumber {
|
||||
t.Errorf("%s: expected number 0b%b, got 0b%b", assert.name, assert.expectedNumber, actualNumber)
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+20
-1
@@ -24,11 +24,24 @@ func main() {
|
||||
readByteSlice(s4)
|
||||
|
||||
s5 := make([]int, 4) // OUT: object allocated on the heap: escapes at line 27
|
||||
s5 = append(s5, 5)
|
||||
_ = append(s5, 5)
|
||||
|
||||
s6 := make([]int, 3)
|
||||
s7 := []int{1, 2, 3}
|
||||
copySlice(s6, s7)
|
||||
|
||||
c1 := getComplex128() // OUT: object allocated on the heap: escapes at line 34
|
||||
useInterface(c1)
|
||||
|
||||
n3 := 5 // OUT: object allocated on the heap: escapes at line 39
|
||||
func() int {
|
||||
return n3
|
||||
}()
|
||||
|
||||
callVariadic(3, 5, 8) // OUT: object allocated on the heap: escapes at line 41
|
||||
|
||||
s8 := []int{3, 5, 8} // OUT: object allocated on the heap: escapes at line 44
|
||||
callVariadic(s8...)
|
||||
}
|
||||
|
||||
func derefInt(x *int) int {
|
||||
@@ -56,3 +69,9 @@ func getUnknownNumber() int
|
||||
func copySlice(out, in []int) {
|
||||
copy(out, in)
|
||||
}
|
||||
|
||||
func getComplex128() complex128
|
||||
|
||||
func useInterface(interface{})
|
||||
|
||||
func callVariadic(...int)
|
||||
|
||||
Vendored
+2
-2
@@ -4,10 +4,10 @@ target triple = "armv7m-none-eabi"
|
||||
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* }
|
||||
%runtime.interfaceMethodInfo = type { i8*, i32 }
|
||||
|
||||
@"reflect/types.type:basic:uint8" = external constant %runtime.typecodeID
|
||||
@"reflect/types.type:basic:uint8" = private constant %runtime.typecodeID zeroinitializer
|
||||
@"reflect/types.typeid:basic:uint8" = external constant i8
|
||||
@"reflect/types.typeid:basic:int16" = external constant i8
|
||||
@"reflect/types.type:basic:int" = external constant %runtime.typecodeID
|
||||
@"reflect/types.type:basic:int" = private constant %runtime.typecodeID zeroinitializer
|
||||
@"func NeverImplementedMethod()" = external constant i8
|
||||
@"Unmatched$interface" = private constant [1 x i8*] [i8* @"func NeverImplementedMethod()"]
|
||||
@"func Double() int" = external constant i8
|
||||
|
||||
Vendored
+17
-33
@@ -4,19 +4,9 @@ target triple = "armv7m-none-eabi"
|
||||
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* }
|
||||
%runtime.interfaceMethodInfo = type { i8*, i32 }
|
||||
|
||||
@"reflect/types.type:basic:uint8" = external constant %runtime.typecodeID
|
||||
@"reflect/types.typeid:basic:uint8" = external constant i8
|
||||
@"reflect/types.typeid:basic:int16" = external constant i8
|
||||
@"reflect/types.type:basic:int" = external constant %runtime.typecodeID
|
||||
@"func NeverImplementedMethod()" = external constant i8
|
||||
@"func Double() int" = external constant i8
|
||||
@"reflect/types.type:named:Number" = private constant %runtime.typecodeID zeroinitializer
|
||||
|
||||
declare i1 @runtime.interfaceImplements(i32, i8**)
|
||||
|
||||
declare i1 @runtime.typeAssert(i32, i8*)
|
||||
|
||||
declare i32 @runtime.interfaceMethod(i32, i8**, i8*)
|
||||
@"reflect/types.type:basic:uint8" = private constant %runtime.typecodeID zeroinitializer
|
||||
@"reflect/types.type:basic:int" = private constant %runtime.typecodeID zeroinitializer
|
||||
@"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null }
|
||||
|
||||
declare void @runtime.printuint8(i8)
|
||||
|
||||
@@ -31,9 +21,9 @@ declare void @runtime.printnl()
|
||||
declare void @runtime.nilPanic(i8*, i8*)
|
||||
|
||||
define void @printInterfaces() {
|
||||
call void @printInterface(i32 4, i8* inttoptr (i32 5 to i8*))
|
||||
call void @printInterface(i32 16, i8* inttoptr (i8 120 to i8*))
|
||||
call void @printInterface(i32 68, i8* inttoptr (i32 3 to i8*))
|
||||
call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:int" to i32), i8* inttoptr (i32 5 to i8*))
|
||||
call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:uint8" to i32), i8* inttoptr (i8 120 to i8*))
|
||||
call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32), i8* inttoptr (i32 3 to i8*))
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -57,7 +47,7 @@ typeswitch.Doubler: ; preds = %typeswitch.notUnmat
|
||||
ret void
|
||||
|
||||
typeswitch.notDoubler: ; preds = %typeswitch.notUnmatched
|
||||
%typeassert.ok2 = icmp eq i32 16, %typecode
|
||||
%typeassert.ok2 = icmp eq i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:uint8" to i32), %typecode
|
||||
br i1 %typeassert.ok2, label %typeswitch.byte, label %typeswitch.notByte
|
||||
|
||||
typeswitch.byte: ; preds = %typeswitch.notDoubler
|
||||
@@ -92,40 +82,34 @@ define i32 @"(Number).Double$invoke"(i8* %receiverPtr, i8* %parentHandle) {
|
||||
|
||||
define internal i32 @"(Doubler).Double"(i8* %0, i8* %1, i32 %actualType, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
switch i32 %actualType, label %default [
|
||||
i32 68, label %"named:Number"
|
||||
]
|
||||
|
||||
default: ; preds = %entry
|
||||
call void @runtime.nilPanic(i8* undef, i8* undef)
|
||||
unreachable
|
||||
%"named:Number.icmp" = icmp eq i32 %actualType, ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32)
|
||||
br i1 %"named:Number.icmp", label %"named:Number", label %"named:Number.next"
|
||||
|
||||
"named:Number": ; preds = %entry
|
||||
%2 = call i32 @"(Number).Double$invoke"(i8* %0, i8* %1)
|
||||
ret i32 %2
|
||||
|
||||
"named:Number.next": ; preds = %entry
|
||||
call void @runtime.nilPanic(i8* undef, i8* undef)
|
||||
unreachable
|
||||
}
|
||||
|
||||
define internal i1 @"Doubler$typeassert"(i32 %actualType) unnamed_addr {
|
||||
entry:
|
||||
switch i32 %actualType, label %else [
|
||||
i32 68, label %then
|
||||
]
|
||||
%"named:Number.icmp" = icmp eq i32 %actualType, ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32)
|
||||
br i1 %"named:Number.icmp", label %then, label %"named:Number.next"
|
||||
|
||||
then: ; preds = %entry
|
||||
ret i1 true
|
||||
|
||||
else: ; preds = %entry
|
||||
"named:Number.next": ; preds = %entry
|
||||
ret i1 false
|
||||
}
|
||||
|
||||
define internal i1 @"Unmatched$typeassert"(i32 %actualType) unnamed_addr {
|
||||
entry:
|
||||
switch i32 %actualType, label %else [
|
||||
]
|
||||
ret i1 false
|
||||
|
||||
then: ; No predecessors!
|
||||
ret i1 true
|
||||
|
||||
else: ; preds = %entry
|
||||
ret i1 false
|
||||
}
|
||||
|
||||
Vendored
+56
@@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
// This file tests the type codes assigned by the reflect lowering pass.
|
||||
// This test is not complete, most importantly, sidetables are not currently
|
||||
// being tested.
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// See the top of src/reflect/type.go
|
||||
prefixChan = 0b0001
|
||||
prefixInterface = 0b0011
|
||||
prefixPtr = 0b0101
|
||||
prefixSlice = 0b0111
|
||||
prefixArray = 0b1001
|
||||
prefixFunc = 0b1011
|
||||
prefixMap = 0b1101
|
||||
prefixStruct = 0b1111
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Check for some basic types.
|
||||
assertType(3, uintptr(reflect.Int)<<1)
|
||||
assertType(uint8(3), uintptr(reflect.Uint8)<<1)
|
||||
assertType(byte(3), uintptr(reflect.Uint8)<<1)
|
||||
assertType(int64(3), uintptr(reflect.Int64)<<1)
|
||||
assertType("", uintptr(reflect.String)<<1)
|
||||
assertType(3.5, uintptr(reflect.Float64)<<1)
|
||||
assertType(unsafe.Pointer(nil), uintptr(reflect.UnsafePointer)<<1)
|
||||
|
||||
// Check for named types: they are given names in order.
|
||||
// They are sorted in reverse, for no good reason.
|
||||
const intNum = uintptr(reflect.Int) << 1
|
||||
assertType(namedInt1(0), (3<<6)|intNum)
|
||||
assertType(namedInt2(0), (2<<6)|intNum)
|
||||
assertType(namedInt3(0), (1<<6)|intNum)
|
||||
|
||||
// Check for some "prefix-style" types.
|
||||
assertType(make(chan int), (intNum<<5)|prefixChan)
|
||||
assertType(new(int), (intNum<<5)|prefixPtr)
|
||||
assertType([]int{}, (intNum<<5)|prefixSlice)
|
||||
}
|
||||
|
||||
type (
|
||||
namedInt1 int
|
||||
namedInt2 int
|
||||
namedInt3 int
|
||||
)
|
||||
|
||||
// Pseudo call that is being checked by the code in reflect_test.go.
|
||||
// After reflect lowering, the type code as part of the interface should match
|
||||
// the asserted type code.
|
||||
func assertType(itf interface{}, assertedTypeCode uintptr)
|
||||
@@ -4,13 +4,19 @@ package transform_test
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"github.com/tinygo-org/tinygo/compiler"
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
@@ -128,3 +134,86 @@ func filterIrrelevantIRLines(lines []string) []string {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// compileGoFileForTesting compiles the given Go file to run tests against.
|
||||
// Only the given Go file is compiled (no dependencies) and no optimizations are
|
||||
// run.
|
||||
// If there are any errors, they are reported via the *testing.T instance.
|
||||
func compileGoFileForTesting(t *testing.T, filename string) llvm.Module {
|
||||
target, err := compileopts.LoadTarget("i686--linux")
|
||||
if err != nil {
|
||||
t.Fatal("failed to load target:", err)
|
||||
}
|
||||
config := &compileopts.Config{
|
||||
Options: &compileopts.Options{},
|
||||
Target: target,
|
||||
}
|
||||
compilerConfig := &compiler.Config{
|
||||
Triple: config.Triple(),
|
||||
GOOS: config.GOOS(),
|
||||
GOARCH: config.GOARCH(),
|
||||
CodeModel: config.CodeModel(),
|
||||
RelocationModel: config.RelocationModel(),
|
||||
Scheduler: config.Scheduler(),
|
||||
FuncImplementation: config.FuncImplementation(),
|
||||
AutomaticStackSize: config.AutomaticStackSize(),
|
||||
Debug: true,
|
||||
}
|
||||
machine, err := compiler.NewTargetMachine(compilerConfig)
|
||||
if err != nil {
|
||||
t.Fatal("failed to create target machine:", err)
|
||||
}
|
||||
|
||||
// Load entire program AST into memory.
|
||||
lprogram, err := loader.Load(config, []string{filename}, config.ClangHeaders, types.Config{
|
||||
Sizes: compiler.Sizes(machine),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("failed to create target machine:", err)
|
||||
}
|
||||
err = lprogram.Parse()
|
||||
if err != nil {
|
||||
t.Fatal("could not parse", err)
|
||||
}
|
||||
|
||||
// Compile AST to IR.
|
||||
program := lprogram.LoadSSA()
|
||||
pkg := lprogram.MainPkg()
|
||||
mod, errs := compiler.CompilePackage(filename, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
|
||||
if errs != nil {
|
||||
for _, err := range errs {
|
||||
t.Error(err)
|
||||
}
|
||||
t.FailNow()
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
// 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{}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user