mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-27 23:28:42 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 375033a6bb | |||
| 2dfc32dde9 | |||
| d7341cf7cc | |||
| 0fca2ae8c8 | |||
| cee97e6d97 | |||
| 26f3b70304 | |||
| bb509ec91d | |||
| 944f022060 | |||
| cd517a30af | |||
| 52d8655eec | |||
| f5786941e5 | |||
| 1f73941c43 | |||
| abeab51d00 | |||
| 9ef75f17bf | |||
| c3992bd77b | |||
| f79e66ac2e | |||
| 4eac212695 |
+14
-14
@@ -119,7 +119,7 @@ commands:
|
||||
- lib/wasi-libc/sysroot
|
||||
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./compiler ./interp ./transform .
|
||||
- run: make gen-device -j4
|
||||
- run: make smoketest XTENSA=0
|
||||
- run: make smoketest XTENSA=0 RUN_SMOKETEST_JOBS=4
|
||||
- run: make tinygo-test
|
||||
- run: make wasmtest
|
||||
- save_cache:
|
||||
@@ -195,7 +195,7 @@ commands:
|
||||
- ~/.cache/go-build
|
||||
- /go/pkg/mod
|
||||
- run: make gen-device -j4
|
||||
- run: make smoketest TINYGO=build/tinygo
|
||||
- run: make smoketest TINYGO=build/tinygo RUN_SMOKETEST_JOBS=4
|
||||
build-linux:
|
||||
steps:
|
||||
- checkout
|
||||
@@ -278,7 +278,7 @@ commands:
|
||||
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
|
||||
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
|
||||
tinygo version
|
||||
- run: make smoketest
|
||||
- run: make smoketest RUN_SMOKETEST_JOBS=4
|
||||
build-macos:
|
||||
steps:
|
||||
- checkout
|
||||
@@ -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:
|
||||
@@ -357,9 +357,9 @@ commands:
|
||||
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
|
||||
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
|
||||
tinygo version
|
||||
- run: make smoketest AVR=0
|
||||
- run: make smoketest AVR=0 RUN_SMOKETEST_JOBS=4
|
||||
- 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
|
||||
|
||||
|
||||
@@ -202,8 +202,11 @@ tinygo-test:
|
||||
$(TINYGO) test text/scanner
|
||||
$(TINYGO) test unicode/utf8
|
||||
|
||||
.PHONY: smoketest
|
||||
.PHONY: smoketest smoketest-commands
|
||||
smoketest:
|
||||
@go run ./src/cmd/run-smoketest make smoketest-commands
|
||||
|
||||
smoketest-commands:
|
||||
$(TINYGO) version
|
||||
# test all examples (except pwm)
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1
|
||||
|
||||
@@ -84,7 +84,7 @@ func copyFile(src, dst string) error {
|
||||
return err
|
||||
}
|
||||
defer inf.Close()
|
||||
outpath := dst + ".tmp"
|
||||
outpath := src + ".tmp"
|
||||
outf, err := os.Create(outpath)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -101,5 +101,10 @@ func copyFile(src, dst string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Rename(dst+".tmp", dst)
|
||||
// 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(outpath, dst)
|
||||
return nil
|
||||
}
|
||||
|
||||
+1
-4
@@ -167,10 +167,7 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = os.Rename(f.Name(), depfileCachePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
os.Rename(f.Name(), depfileCachePath)
|
||||
|
||||
// Move temporary object file to final location.
|
||||
outpath, err := makeCFileCachePath(dependencySlice, depfileNameHash)
|
||||
|
||||
@@ -249,6 +249,7 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
||||
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
|
||||
|
||||
+49
-1
@@ -964,11 +964,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{})
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ require (
|
||||
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
|
||||
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
|
||||
github.com/mattn/go-colorable v0.1.8
|
||||
github.com/sago35/ochan v0.0.0-20200313013834-e0f46da4579f // indirect
|
||||
go.bug.st/serial v1.1.2
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78
|
||||
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
|
||||
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c
|
||||
|
||||
@@ -28,6 +28,8 @@ github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sago35/ochan v0.0.0-20200313013834-e0f46da4579f h1:bHOcIl3AODQMzleLeSGs3xbIvAzhcgLTUwd8IUKqLMI=
|
||||
github.com/sago35/ochan v0.0.0-20200313013834-e0f46da4579f/go.mod h1:55Pg0jnassdnFxlhS8HIU5pdYhEBYftGaknIP4bBUq8=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
go.bug.st/serial v1.0.0 h1:ogEPzrllCsnG00EqKRjeYvPRsO7NJW6DqykzkdD6E/k=
|
||||
@@ -41,6 +43,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
|
||||
|
||||
+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
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/sago35/ochan"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func main() {
|
||||
threads := runtime.NumCPU()
|
||||
if j, ok := os.LookupEnv(`RUN_SMOKETEST_JOBS`); ok {
|
||||
n, err := strconv.ParseInt(j, 0, 0)
|
||||
if err == nil {
|
||||
threads = int(n)
|
||||
}
|
||||
}
|
||||
|
||||
flag.IntVar(&threads, "threads", threads, "threads of make smoketest")
|
||||
flag.Parse()
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Printf("usage: %s make smoketest\n", filepath.Base(os.Args[0]))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
commands, err := getMakeCommands(flag.Args())
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
err = run(commands, threads)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func getMakeCommands(args []string) ([]string, error) {
|
||||
var buf bytes.Buffer
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Args = append(cmd.Args, "-n")
|
||||
cmd.Stdout = &buf
|
||||
cmd.Stderr = &buf
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commands := []string{}
|
||||
scanner := bufio.NewScanner(&buf)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
fields := strings.Fields(line)
|
||||
if filepath.Base(fields[0]) == "tinygo" && (fields[1] == "build" || fields[1] == "version") {
|
||||
commands = append(commands, line)
|
||||
} else if strings.HasPrefix(line, "#") {
|
||||
commands = append(commands, line)
|
||||
}
|
||||
}
|
||||
|
||||
return commands, nil
|
||||
}
|
||||
|
||||
func run(commands []string, threads int) error {
|
||||
ch := make(chan string, 1000)
|
||||
o := ochan.NewOchan(ch, 100)
|
||||
|
||||
go func() {
|
||||
limit := make(chan struct{}, threads)
|
||||
var eg errgroup.Group
|
||||
|
||||
for _, command := range commands {
|
||||
command := command
|
||||
select {
|
||||
case limit <- struct{}{}:
|
||||
och := o.GetCh()
|
||||
eg.Go(func() error {
|
||||
var err error
|
||||
|
||||
if runtime.GOOS == `windows` {
|
||||
command = convertToWindowsPath(command)
|
||||
}
|
||||
|
||||
fields := strings.Fields(command)
|
||||
if filepath.Base(fields[0]) == "tinygo" && fields[1] == "build" {
|
||||
err = buildAndmd5sum(command, och)
|
||||
} else if strings.HasPrefix(command, "#") {
|
||||
och <- fmt.Sprintf("%s\n", command)
|
||||
} else {
|
||||
err = runCommand(command, och)
|
||||
}
|
||||
close(och)
|
||||
<-limit
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if err := eg.Wait(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
o.Wait()
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
for s := range ch {
|
||||
fmt.Print(s)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCommand(command string, ch chan string) error {
|
||||
var buf bytes.Buffer
|
||||
|
||||
fmt.Fprintf(&buf, "%s\n", command)
|
||||
|
||||
args := strings.Fields(command)
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stdout = &buf
|
||||
cmd.Stderr = &buf
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
ch <- buf.String()
|
||||
return err
|
||||
}
|
||||
|
||||
ch <- buf.String()
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildAndmd5sum(command string, ch chan string) error {
|
||||
var buf bytes.Buffer
|
||||
|
||||
fmt.Fprintf(&buf, "%s\n", command)
|
||||
|
||||
args := strings.Fields(command)
|
||||
|
||||
output := ""
|
||||
tmpOutput := ""
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range args {
|
||||
if args[i] == "-o" {
|
||||
output = args[i+1]
|
||||
ext := filepath.Ext(output)
|
||||
tmpOutput = filepath.Join(tmpdir, fmt.Sprintf("%s.%s", filepath.Base(output), ext))
|
||||
args[i+1] = tmpOutput
|
||||
} else if strings.HasPrefix(args[i], "-o=") {
|
||||
output = args[i][3:]
|
||||
ext := filepath.Ext(output)
|
||||
tmpOutput = filepath.Join(tmpdir, fmt.Sprintf("%s.%s", filepath.Base(output), ext))
|
||||
args[i] = fmt.Sprintf("-o=%s", tmpOutput)
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stdout = &buf
|
||||
cmd.Stderr = &buf
|
||||
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
ch <- buf.String()
|
||||
return err
|
||||
}
|
||||
|
||||
md5str, err := calcMD5(tmpOutput)
|
||||
if err != nil {
|
||||
ch <- buf.String()
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(&buf, "%s %s\n", md5str, output)
|
||||
|
||||
ch <- buf.String()
|
||||
return nil
|
||||
}
|
||||
|
||||
func calcMD5(f string) (string, error) {
|
||||
r, err := os.Open(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
h := md5.New()
|
||||
if _, err := io.Copy(h, r); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func convertToWindowsPath(command string) string {
|
||||
if !strings.HasPrefix(command, `/`) {
|
||||
return command
|
||||
}
|
||||
|
||||
command = fmt.Sprintf("%c:%s", command[1], command[2:])
|
||||
return command
|
||||
}
|
||||
+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)))
|
||||
|
||||
@@ -433,7 +433,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 {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build sam,atsamd51
|
||||
// +build sam,atsamd51 sam,atsame5x
|
||||
|
||||
// Peripheral abstraction layer for the atsamd51.
|
||||
//
|
||||
@@ -44,6 +44,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 +630,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -848,7 +863,18 @@ 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 {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -102,11 +102,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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"inherits": ["arduino-nano"],
|
||||
"flash-command": "avrdude -c arduino -p atmega328p -b 115200 -P {port} -U flash:w:{hex}:i"
|
||||
}
|
||||
@@ -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