Compare commits

..

2 Commits

Author SHA1 Message Date
Ayke van Laethem d27cfb1585 WIP shadow-stack based mark/sweep collector 2019-01-17 21:01:15 +01:00
Ayke van Laethem e6e561100a WIP refactor GC 2019-01-17 20:52:15 +01:00
83 changed files with 1361 additions and 3816 deletions
+1 -2
View File
@@ -15,7 +15,7 @@ install:
- dep ensure --vendor-only
script:
- go install github.com/tinygo-org/tinygo
- go install github.com/aykevl/tinygo
- go test -v .
- make gen-device
- tinygo build -o blinky1.nrf.elf -target=pca10040 examples/blinky1
@@ -31,4 +31,3 @@ script:
- tinygo build -o blinky2.reel.elf -target=reelboard examples/blinky2
- tinygo build -o blinky1.pca10056.elf -target=pca10056 examples/blinky1
- tinygo build -o blinky2.pca10056.elf -target=pca10056 examples/blinky2
- tinygo build -o blinky1.samd21.elf -target=itsybitsy-m0 examples/blinky1
-125
View File
@@ -1,125 +0,0 @@
# Building TinyGo
TinyGo depends on LLVM and libclang, which are both big C++ libraries. It can
also optionally use a built-in lld to ease cross compiling. There are two ways
these can be linked: dynamically and statically. The default is dynamic linking
because it is fast and works almost out of the box on Debian-based systems with
the right libraries installed.
This guide describes how to statically link TinyGo against LLVM, libclang and
lld so that the binary can be easily moved between systems. It also shows how to
build a release tarball that includes this binary and all necessary extra files.
## Dependencies
LLVM, Clang and LLD are quite light on dependencies, requiring only standard
build tools to be built. Go is of course necessary to build TinyGo itself.
* Go (1.11+)
* [dep](https://golang.github.io/dep/)
* Standard build tools (gcc/clang)
* git or subversion
* CMake
* [Ninja](https://ninja-build.org/) or make (preferably Ninja)
The rest of this guide assumes you're running Linux, but it should be equivalent
on a different system like Mac.
## Download the source
The first step is to get the source code. Place it in some directory, assuming
`$HOME/src` here, but you can pick a different one of course:
git clone -b release_70 https://github.com/llvm-mirror/llvm.git $HOME/src/llvm
git clone -b release_70 https://github.com/llvm-mirror/clang.git $HOME/src/llvm/tools/clang
git clone -b release_70 https://github.com/llvm-mirror/lld.git $HOME/src/llvm/tools/lld
go get -d github.com/tinygo-org/tinygo
cd $HOME/go/src/github.com/tinygo-org/tinygo
dep ensure -vendor-only # download dependencies
Note that Clang and LLD must be placed inside the tools subdirectory of LLVM to
be automatically built with the rest of the system.
## Build LLVM, Clang, LLD
Building LLVM is quite easy compared to some other software packages. However,
the default configuration is _not_ optimized for distribution. It is optimized
for development, meaning that binaries produce accurate error messages at the
cost of huge binaries and slow compiles.
Before configuring, you may want to set the following environment variables to
speed up the build. Most Linux distributions ship with GCC as the default
compiler, but Clang is significantly faster and uses much less memory while
producing binaries that are about as fast.
export CC=clang
export CXX=clang++
Make a build directory. LLVM requires out-of-tree builds:
mkdir $HOME/src/llvm-build
cd $HOME/src/llvm-build
Configure LLVM with CMake:
cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;WebAssembly" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON
You can also choose a different build system than Ninja, but Ninja is fast.
There are various options you can tune here, but the options given above are
preferable for releases. Here is what they do:
* `LLVM_TARGETS_TO_BUILD` and `LLVM_EXPERIMENTAL_TARGETS_TO_BUILD`: the
targets that are natively supported by the LLVM code generators. The targets
listed here are the ones supported by TinyGo. Note that LLVM is a cross
compiler by default, unlike some other compilers.
* `CMAKE_BUILD_TYPE`: the default is Debug, which produces large inefficient
binaries that are easy to debug. We want small and fast binaries.
* `LLVM_ENABLE_ASSERTIONS`: the default is ON, which greatly slows down LLVM
and is only really useful during development. Disable them here.
* `LIBCLANG_BUILD_STATIC`: unlike LLVM, libclang is built as a shared library
by default. We want a static library for easy distribution.
Now build it:
ninja # or make, if you choose make in the previous step
This can take over an hour depending on the speed of your system.
## Build TinyGo
Now that you have a working version of LLVM, build TinyGo using it. You need to
specify the directories to the LLVM build directory and to the Clang and LLD source.
cd $HOME/go/src/github.com/tinygo-org/tinygo
make static LLVM_BUILDDIR=$HOME/src/llvm-build CLANG_SRC=$HOME/src/llvm/tools/clang LLD_SRC=$HOME/src/llvm/tools/lld
## Verify TinyGo
Try running TinyGo:
./build/tinygo help
Also, make sure the `tinygo` binary really is statically linked. Check this
using `ldd` (not to be confused with `lld`):
ldd ./build/tinygo
The result should not contain libclang or libLLVM.
## Make a release tarball
Now that we have a working static build, it's time to make a release tarball.
This is just a slight change from the command to build TinyGo:
cd $HOME/go/src/github.com/tinygo-org/tinygo
make release LLVM_BUILDDIR=$HOME/src/llvm-build CLANG_SRC=$HOME/src/llvm/tools/clang LLD_SRC=$HOME/src/llvm/tools/lld
The release tarball is stored in build/release.tar.gz, and can be extracted with
the following command:
tar -xvf path/to/release.tar.gz
TinyGo will get extracted to a `tinygo` directory. You can then call it with:
./tinygo/bin/tinygo
+21 -21
View File
@@ -8,18 +8,18 @@ RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
COPY . /go/src/github.com/tinygo-org/tinygo
COPY . /go/src/github.com/aykevl/tinygo
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
RUN cd /go/src/github.com/aykevl/tinygo/ && \
dep ensure --vendor-only && \
go install /go/src/github.com/tinygo-org/tinygo/
go install /go/src/github.com/aykevl/tinygo/
# tinygo-wasm stage installs the needed dependencies to compile TinyGo programs for WASM.
FROM tinygo-base AS tinygo-wasm
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/src /go/src/github.com/aykevl/tinygo/src
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/targets /go/src/github.com/aykevl/tinygo/targets
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-7 main" >> /etc/apt/sources.list && \
@@ -30,13 +30,13 @@ RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
FROM tinygo-base AS tinygo-avr
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/src /go/src/github.com/aykevl/tinygo/src
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/targets /go/src/github.com/aykevl/tinygo/targets
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/Makefile /go/src/github.com/aykevl/tinygo/
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/tools /go/src/github.com/aykevl/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/lib /go/src/github.com/aykevl/tinygo/lib
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
RUN cd /go/src/github.com/aykevl/tinygo/ && \
apt-get update && \
apt-get install -y apt-utils python3 make binutils-avr gcc-avr avr-libc && \
make gen-device-avr && \
@@ -48,13 +48,13 @@ RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
FROM tinygo-base AS tinygo-arm
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/src /go/src/github.com/aykevl/tinygo/src
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/targets /go/src/github.com/aykevl/tinygo/targets
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/Makefile /go/src/github.com/aykevl/tinygo/
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/tools /go/src/github.com/aykevl/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/lib /go/src/github.com/aykevl/tinygo/lib
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
RUN cd /go/src/github.com/aykevl/tinygo/ && \
apt-get update && \
apt-get install -y apt-utils python3 make binutils-arm-none-eabi clang-7 && \
make gen-device-nrf && make gen-device-stm32 && \
@@ -65,11 +65,11 @@ RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
FROM tinygo-wasm AS tinygo-all
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/Makefile /go/src/github.com/aykevl/tinygo/
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/tools /go/src/github.com/aykevl/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/lib /go/src/github.com/aykevl/tinygo/lib
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
RUN cd /go/src/github.com/aykevl/tinygo/ && \
apt-get update && \
apt-get install -y apt-utils python3 make binutils-arm-none-eabi clang-7 binutils-avr gcc-avr avr-libc && \
make gen-device && \
Generated
+25 -13
View File
@@ -3,30 +3,42 @@
[[projects]]
branch = "master"
digest = "1:84316faef4ea12d34dde3b3e6dab682715a23b1c2bb8ab82cec9ab619766e214"
name = "golang.org/x/tools"
packages = [
"go/ast/astutil",
"go/ssa",
"go/types/typeutil",
]
digest = "1:f250e2a6d7e4f9ebc5ba37e5e2ec91b46eb1399ee43f2fdaeb20cd4fd1aeee59"
name = "github.com/aykevl/go-llvm"
packages = ["."]
pruneopts = "UT"
revision = "3e7aa9e59977626dc60433e9aeadf1bb63d28295"
revision = "d8539684f173a591ea9474d6262ac47ef2277d64"
[[projects]]
branch = "master"
digest = "1:3611159788efdd4e0cfae18b6ebcccbad25a2815968b0e4323b42647d201031a"
name = "tinygo.org/x/go-llvm"
packages = ["."]
digest = "1:d1102ae84d8c9318db4ce2ad2673eb2bf54569ab2a4a5d57e70d8aef726b681d"
name = "golang.org/x/tools"
packages = [
"go/ast/astutil",
"go/buildutil",
"go/gcexportdata",
"go/internal/cgo",
"go/internal/gcimporter",
"go/loader",
"go/packages",
"go/ssa",
"go/ssa/ssautil",
"go/types/typeutil",
"internal/fastwalk",
"internal/gopathwalk",
"internal/semver",
]
pruneopts = "UT"
revision = "f420620d1a0f54417a5712260153fe861780d030"
revision = "3e7aa9e59977626dc60433e9aeadf1bb63d28295"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/aykevl/go-llvm",
"golang.org/x/tools/go/loader",
"golang.org/x/tools/go/ssa",
"tinygo.org/x/go-llvm",
"golang.org/x/tools/go/ssa/ssautil",
]
solver-name = "gps-cdcl"
solver-version = 1
+1 -1
View File
@@ -1,6 +1,6 @@
[[constraint]]
branch = "master"
name = "tinygo.org/x/go-llvm"
name = "github.com/aykevl/go-llvm"
[[constraint]]
branch = "master"
+9 -46
View File
@@ -1,9 +1,9 @@
# aliases
all: tinygo
tinygo: build/tinygo
all: tgo
tgo: build/tgo
.PHONY: all tinygo static run-test run-blinky run-blinky2 clean fmt gen-device gen-device-nrf gen-device-avr
.PHONY: all tgo run-test run-blinky run-blinky2 clean fmt gen-device gen-device-nrf gen-device-avr
TARGET ?= unix
@@ -40,18 +40,6 @@ $(error Unknown target)
endif
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
CLANG_LIBS = -Wl,--start-group $(abspath $(LLVM_BUILDDIR))/lib/libclang.a -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions -lclangToolingRefactor -Wl,--end-group -lstdc++
LLD_LIBS = -Wl,--start-group -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML -Wl,--end-group
# For static linking.
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
CGO_CXXFLAGS=-std=c++11
CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS))
run-test: build/test
@@ -108,42 +96,17 @@ gen-device-stm32:
go fmt ./src/device/stm32
# Build the Go compiler.
tinygo:
build/tgo: *.go compiler/*.go interp/*.go loader/*.go ir/*.go
@mkdir -p build
go build -o build/tinygo .
static:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o build/tinygo -tags byollvm .
release: static gen-device
@mkdir -p build/release/tinygo/bin
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
@mkdir -p build/release/tinygo/lib/nrfx
@mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
@mkdir -p build/release/tinygo/pkg/armv7em-none-eabi
@cp -p build/tinygo build/release/tinygo/bin
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
@cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib
@cp -rp lib/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt
@cp -rp lib/compiler-rt/README.txt build/release/tinygo/lib/compiler-rt
@cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx
@cp -rp src build/release/tinygo/src
@cp -rp targets build/release/tinygo/targets
./build/tinygo build-builtins -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a
./build/tinygo build-builtins -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a
./build/tinygo build-builtins -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a
tar -czf build/release.tar.gz -C build/release tinygo
go build -o build/tgo -i .
# Binary that can run on the host.
build/%: src/examples/% src/examples/%/*.go build/tinygo src/runtime/*.go
./build/tinygo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
build/%: src/examples/% src/examples/%/*.go build/tgo src/runtime/*.go
./build/tgo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
# ELF file that can run on a microcontroller.
build/%.elf: src/examples/% src/examples/%/*.go build/tinygo src/runtime/*.go
./build/tinygo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
build/%.elf: src/examples/% src/examples/%/*.go build/tgo src/runtime/*.go
./build/tgo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
# Convert executable to Intel hex file (for flashing).
build/%.hex: build/%.elf
+3 -4
View File
@@ -1,6 +1,6 @@
# TinyGo - Go compiler for microcontrollers
[![Build Status](https://travis-ci.com/aykevl/tinygo.svg?branch=master)](https://travis-ci.com/tinygo-org/tinygo)
[![Build Status](https://travis-ci.com/aykevl/tinygo.svg?branch=master)](https://travis-ci.com/aykevl/tinygo)
> We never expected Go to be an embedded language and so it's got serious
> problems [...].
@@ -56,14 +56,13 @@ Currently supported features:
* closures
* bound methods
* complex numbers (except for arithmetic)
* channels (with some limitations)
Not yet supported:
* select
* complex arithmetic
* garbage collection
* recover
* channels
* introspection (if it ever gets implemented)
* ...
@@ -166,7 +165,7 @@ If you want to contribute, here are some suggestions:
requires a few compiler changes, but if the architecture is supported you
can try implementing support for a new chip or board in `src/runtime`. For
details, see [this wiki entry on adding
archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
archs/chips/boards](https://github.com/aykevl/tinygo/wiki/Adding-a-new-board).
* Microcontrollers have lots of peripherals and many don't have an
implementation yet in the `machine` package. Adding support for new
peripherals is very useful.
+22 -38
View File
@@ -80,45 +80,29 @@ func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string,
return "", err
}
cachepath := filepath.Join(dir, name)
err = moveFile(tmppath, cachepath)
err = os.Rename(tmppath, cachepath)
if err != nil {
return "", err
inf, err := os.Open(tmppath)
if err != nil {
return "", err
}
defer inf.Close()
outf, err := os.Create(cachepath + ".tmp")
if err != nil {
return "", err
}
_, err = io.Copy(outf, inf)
if err != nil {
return "", err
}
err = os.Rename(cachepath+".tmp", cachepath)
if err != nil {
return "", err
}
return cachepath, outf.Close()
}
return cachepath, nil
}
// moveFile renames the file from src to dst. If renaming doesn't work (for
// example, the rename crosses a filesystem boundary), the file is copied and
// the old file is removed.
func moveFile(src, dst string) error {
err := os.Rename(src, dst)
if err == nil {
// Success!
return nil
}
// Failed to move, probably a different filesystem.
// Do a copy + remove.
inf, err := os.Open(src)
if err != nil {
return err
}
defer inf.Close()
outpath := dst + ".tmp"
outf, err := os.Create(outpath)
if err != nil {
return err
}
_, err = io.Copy(outf, inf)
if err != nil {
os.Remove(outpath)
return err
}
err = os.Rename(dst+".tmp", dst)
if err != nil {
return err
}
return outf.Close()
}
+8 -50
View File
@@ -157,29 +157,16 @@ var aeabiBuiltins = []string{
func builtinFiles(target string) []string {
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
if strings.HasPrefix(target, "arm") {
if target[:3] == "arm" {
builtins = append(builtins, aeabiBuiltins...)
}
return builtins
}
// builtinsDir returns the directory where the sources for compiler-rt are kept.
func builtinsDir() string {
return filepath.Join(sourceDir(), "lib", "compiler-rt", "lib", "builtins")
}
// Get the builtins archive, possibly generating it as needed.
func loadBuiltins(target string) (path string, err error) {
// Try to load a precompiled compiler-rt library.
precompiledPath := filepath.Join(sourceDir(), "pkg", target, "compiler-rt.a")
if _, err := os.Stat(precompiledPath); err == nil {
// Found a precompiled compiler-rt for this OS/architecture. Return the
// path directly.
return precompiledPath, nil
}
outfile := "librt-" + target + ".a"
builtinsDir := builtinsDir()
builtinsDir := filepath.Join(sourceDir(), "lib", "compiler-rt", "lib", "builtins")
builtins := builtinFiles(target)
srcs := make([]string, len(builtins))
@@ -191,33 +178,9 @@ func loadBuiltins(target string) (path string, err error) {
return path, err
}
var cachepath string
err = compileBuiltins(target, func(path string) error {
path, err := cacheStore(path, outfile, commands["clang"], srcs)
cachepath = path
return err
})
return cachepath, err
}
// compileBuiltins compiles builtins from compiler-rt into a static library.
// When it succeeds, it will call the callback with the resulting path. The path
// will be removed after callback returns. If callback returns an error, this is
// passed through to the return value of this function.
func compileBuiltins(target string, callback func(path string) error) error {
builtinsDir := builtinsDir()
builtins := builtinFiles(target)
srcs := make([]string, len(builtins))
for i, name := range builtins {
srcs[i] = filepath.Join(builtinsDir, name)
}
dirPrefix := "tinygo-builtins"
remapDir := filepath.Join(os.TempDir(), dirPrefix)
dir, err := ioutil.TempDir(os.TempDir(), dirPrefix)
dir, err := ioutil.TempDir("", "tinygo-builtins")
if err != nil {
return err
return "", err
}
defer os.RemoveAll(dir)
@@ -232,16 +195,13 @@ func compileBuiltins(target string, callback func(path string) error) error {
objpath := filepath.Join(dir, objname+".o")
objs = append(objs, objpath)
srcpath := filepath.Join(builtinsDir, name)
// Note: -fdebug-prefix-map is necessary to make the output archive
// reproducible. Otherwise the temporary directory is stored in the
// archive itself, which varies each run.
cmd := exec.Command(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir, "-o", objpath, srcpath)
cmd := exec.Command(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-o", objpath, srcpath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = dir
err = cmd.Run()
if err != nil {
return &commandError{"failed to build", srcpath, err}
return "", err
}
}
@@ -253,10 +213,8 @@ func compileBuiltins(target string, callback func(path string) error) error {
cmd.Dir = dir
err = cmd.Run()
if err != nil {
return &commandError{"failed to make static library", arpath, err}
return "", err
}
// Give the caller the resulting file. The callback must copy the file,
// because after it returns the temporary directory will be removed.
return callback(arpath)
return cacheStore(arpath, outfile, commands["clang"], srcs)
}
+2 -3
View File
@@ -1,8 +1,8 @@
package compiler
import (
"github.com/aykevl/go-llvm"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// For a description of the calling convention in prose, see:
@@ -21,8 +21,7 @@ func (c *Compiler) createRuntimeCall(fnName string, args []llvm.Value, name stri
}
fn := c.ir.GetFunction(member.(*ssa.Function))
if !fn.IsExported() {
args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter
args = append(args, llvm.ConstPointerNull(c.i8ptrType)) // coroutine handle
args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter
}
return c.createCall(fn.LLVMFn, args, name)
}
-97
View File
@@ -1,97 +0,0 @@
package compiler
// This file lowers channel operations (make/send/recv/close) to runtime calls
// or pseudo-operations that are lowered during goroutine lowering.
import (
"go/types"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// emitMakeChan returns a new channel value for the given channel type.
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
valueType, err := c.getLLVMType(expr.Type().(*types.Chan).Elem())
if err != nil {
return llvm.Value{}, err
}
if c.targetData.TypeAllocSize(valueType) > c.targetData.TypeAllocSize(c.intType) {
// Values bigger than int overflow the data part of the coroutine.
// TODO: make the coroutine data part big enough to hold these bigger
// values.
return llvm.Value{}, c.makeError(expr.Pos(), "todo: channel with values bigger than int")
}
chanType := c.mod.GetTypeByName("runtime.channel")
size := c.targetData.TypeAllocSize(chanType)
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
ptr := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "chan.alloc")
ptr = c.builder.CreateBitCast(ptr, llvm.PointerType(chanType, 0), "chan")
return ptr, nil
}
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
// channel send operation during goroutine lowering.
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) error {
valueType, err := c.getLLVMType(instr.Chan.Type().(*types.Chan).Elem())
if err != nil {
return err
}
ch, err := c.parseExpr(frame, instr.Chan)
if err != nil {
return err
}
chanValue, err := c.parseExpr(frame, instr.X)
if err != nil {
return err
}
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false)
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
c.builder.CreateStore(chanValue, valueAlloca)
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
c.createRuntimeCall("chanSendStub", []llvm.Value{llvm.Undef(c.i8ptrType), ch, valueAllocaCast, valueSize}, "")
return nil
}
// emitChanRecv emits a pseudo chan receive operation. It is lowered to the
// actual channel receive operation during goroutine lowering.
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
valueType, err := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
if err != nil {
return llvm.Value{}, err
}
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
ch, err := c.parseExpr(frame, unop.X)
if err != nil {
return llvm.Value{}, err
}
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
valueOk := c.builder.CreateAlloca(c.ctx.Int1Type(), "chan.comma-ok.alloca")
c.createRuntimeCall("chanRecvStub", []llvm.Value{llvm.Undef(c.i8ptrType), ch, valueAllocaCast, valueOk, valueSize}, "")
received := c.builder.CreateLoad(valueAlloca, "chan.received")
if unop.CommaOk {
commaOk := c.builder.CreateLoad(valueOk, "chan.comma-ok")
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
return tuple, nil
} else {
return received, nil
}
}
// emitChanClose closes the given channel.
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) error {
valueType, err := c.getLLVMType(param.Type().(*types.Chan).Elem())
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
if err != nil {
return err
}
ch, err := c.parseExpr(frame, param)
if err != nil {
return err
}
c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
return nil
}
+225 -104
View File
@@ -14,10 +14,10 @@ import (
"strconv"
"strings"
"github.com/tinygo-org/tinygo/ir"
"github.com/tinygo-org/tinygo/loader"
"github.com/aykevl/go-llvm"
"github.com/aykevl/tinygo/ir"
"github.com/aykevl/tinygo/loader"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
func init() {
@@ -59,6 +59,12 @@ type Compiler struct {
intType llvm.Type
i8ptrType llvm.Type // for convenience
uintptrType llvm.Type
coroIdFunc llvm.Value
coroSizeFunc llvm.Value
coroBeginFunc llvm.Value
coroSuspendFunc llvm.Value
coroEndFunc llvm.Value
coroFreeFunc llvm.Value
initFuncs []llvm.Value
interfaceInvokeWrappers []interfaceInvokeWrapper
ir *ir.Program
@@ -71,7 +77,10 @@ type Frame struct {
blockExits map[*ssa.BasicBlock]llvm.BasicBlock // these are the exit blocks
currentBlock *ssa.BasicBlock
phis []Phi
blocking bool
taskHandle llvm.Value
cleanupBlock llvm.BasicBlock
suspendBlock llvm.BasicBlock
deferPtr llvm.Value
difunc llvm.Metadata
allDeferFuncs []interface{}
@@ -124,6 +133,24 @@ func NewCompiler(pkgName string, config Config) (*Compiler, error) {
}
c.i8ptrType = llvm.PointerType(c.ctx.Int8Type(), 0)
coroIdType := llvm.FunctionType(c.ctx.TokenType(), []llvm.Type{c.ctx.Int32Type(), c.i8ptrType, c.i8ptrType, c.i8ptrType}, false)
c.coroIdFunc = llvm.AddFunction(c.mod, "llvm.coro.id", coroIdType)
coroSizeType := llvm.FunctionType(c.ctx.Int32Type(), nil, false)
c.coroSizeFunc = llvm.AddFunction(c.mod, "llvm.coro.size.i32", coroSizeType)
coroBeginType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
c.coroBeginFunc = llvm.AddFunction(c.mod, "llvm.coro.begin", coroBeginType)
coroSuspendType := llvm.FunctionType(c.ctx.Int8Type(), []llvm.Type{c.ctx.TokenType(), c.ctx.Int1Type()}, false)
c.coroSuspendFunc = llvm.AddFunction(c.mod, "llvm.coro.suspend", coroSuspendType)
coroEndType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.i8ptrType, c.ctx.Int1Type()}, false)
c.coroEndFunc = llvm.AddFunction(c.mod, "llvm.coro.end", coroEndType)
coroFreeType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
c.coroFreeFunc = llvm.AddFunction(c.mod, "llvm.coro.free", coroFreeType)
return c, nil
}
@@ -210,8 +237,12 @@ func (c *Compiler) Compile(mainPath string) error {
c.ir = ir.NewProgram(lprogram, mainPath)
// Run a simple dead code elimination pass.
c.ir.SimpleDCE()
// Run some DCE and analysis passes. The results are later used by the
// compiler.
c.ir.SimpleDCE() // remove most dead code
c.ir.AnalyseCallgraph() // set up callgraph
c.ir.AnalyseBlockingRecursive() // make all parents of blocking calls blocking (transitively)
c.ir.AnalyseGoCalls() // check whether we need a scheduler
// Initialize debug information.
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
@@ -356,21 +387,33 @@ func (c *Compiler) Compile(mainPath string) error {
block := c.ctx.AddBasicBlock(initFn.LLVMFn, "entry")
c.builder.SetInsertPointAtEnd(block)
for _, fn := range c.initFuncs {
c.builder.CreateCall(fn, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.Undef(c.i8ptrType)}, "")
c.builder.CreateCall(fn, []llvm.Value{llvm.Undef(c.i8ptrType)}, "")
}
c.builder.CreateRetVoid()
// Conserve for goroutine lowering. Without marking these as external, they
// would be optimized away.
// Add a wrapper for the main.main function, either calling it directly or
// setting up the scheduler with it.
mainWrapper := c.ir.GetFunction(c.ir.Program.ImportedPackage("runtime").Members["mainWrapper"].(*ssa.Function))
mainWrapper.LLVMFn.SetLinkage(llvm.InternalLinkage)
mainWrapper.LLVMFn.SetUnnamedAddr(true)
if c.Debug {
difunc, err := c.attachDebugInfo(mainWrapper)
if err != nil {
return err
}
pos := c.ir.Program.Fset.Position(mainWrapper.Pos())
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
block = c.ctx.AddBasicBlock(mainWrapper.LLVMFn, "entry")
c.builder.SetInsertPointAtEnd(block)
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.chanSend").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.chanRecv").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.ExternalLinkage)
if c.ir.NeedsScheduler() {
coroutine := c.builder.CreateCall(realMain, []llvm.Value{llvm.ConstPointerNull(c.i8ptrType), llvm.Undef(c.i8ptrType)}, "")
c.createRuntimeCall("scheduler", []llvm.Value{coroutine}, "")
} else {
c.builder.CreateCall(realMain, []llvm.Value{llvm.Undef(c.i8ptrType)}, "")
}
c.builder.CreateRetVoid()
// see: https://reviews.llvm.org/D18355
c.mod.AddNamedMetadataOperand("llvm.module.flags",
@@ -492,8 +535,7 @@ func (c *Compiler) getLLVMType(goType types.Type) (llvm.Type, error) {
}
// make a closure type (with a function pointer type inside):
// {context, funcptr}
paramTypes = append(paramTypes, c.i8ptrType) // context
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine
paramTypes = append(paramTypes, c.i8ptrType)
ptr := llvm.PointerType(llvm.FunctionType(returnType, paramTypes, false), 0)
ptr = c.ctx.StructType([]llvm.Type{c.i8ptrType, ptr}, false)
return ptr, nil
@@ -634,10 +676,16 @@ func (c *Compiler) parseFuncDecl(f *ir.Function) (*Frame, error) {
locals: make(map[ssa.Value]llvm.Value),
blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock),
blockExits: make(map[*ssa.BasicBlock]llvm.BasicBlock),
blocking: c.ir.IsBlocking(f),
}
var retType llvm.Type
if f.Signature.Results() == nil {
if frame.blocking {
if f.Signature.Results() != nil {
return nil, c.makeError(f.Function.Pos(), "todo: return values in blocking function")
}
retType = c.i8ptrType
} else if f.Signature.Results() == nil {
retType = c.ctx.VoidType()
} else if f.Signature.Results().Len() == 1 {
var err error
@@ -658,6 +706,9 @@ func (c *Compiler) parseFuncDecl(f *ir.Function) (*Frame, error) {
}
var paramTypes []llvm.Type
if frame.blocking {
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine
}
for _, param := range f.Params {
paramType, err := c.getLLVMType(param.Type())
if err != nil {
@@ -670,8 +721,7 @@ func (c *Compiler) parseFuncDecl(f *ir.Function) (*Frame, error) {
// Add an extra parameter as the function context. This context is used in
// closures and bound methods, but should be optimized away when not used.
if !f.IsExported() {
paramTypes = append(paramTypes, c.i8ptrType) // context
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine
paramTypes = append(paramTypes, c.i8ptrType)
}
fnType := llvm.FunctionType(retType, paramTypes, false)
@@ -1045,6 +1095,10 @@ func (c *Compiler) parseFunc(frame *Frame) error {
frame.blockEntries[block] = llvmBlock
frame.blockExits[block] = llvmBlock
}
if frame.blocking {
frame.cleanupBlock = c.ctx.AddBasicBlock(frame.fn.LLVMFn, "task.cleanup")
frame.suspendBlock = c.ctx.AddBasicBlock(frame.fn.LLVMFn, "task.suspend")
}
entryBlock := frame.blockEntries[frame.fn.Blocks[0]]
c.builder.SetInsertPointAtEnd(entryBlock)
@@ -1083,14 +1137,10 @@ func (c *Compiler) parseFunc(frame *Frame) error {
// Load free variables from the context. This is a closure (or bound
// method).
var context llvm.Value
if !frame.fn.IsExported() {
parentHandle := frame.fn.LLVMFn.LastParam()
parentHandle.SetName("parentHandle")
context = llvm.PrevParam(parentHandle)
context.SetName("context")
}
if len(frame.fn.FreeVars) != 0 {
context := frame.fn.LLVMFn.LastParam()
context.SetName("context")
// Determine the context type. It's a struct containing all variables.
freeVarTypes := make([]llvm.Type, 0, len(frame.fn.FreeVars))
for _, freeVar := range frame.fn.FreeVars {
@@ -1136,6 +1186,39 @@ func (c *Compiler) parseFunc(frame *Frame) error {
c.deferInitFunc(frame)
}
if frame.blocking {
// Coroutine initialization.
taskState := c.builder.CreateAlloca(c.mod.GetTypeByName("runtime.taskState"), "task.state")
stateI8 := c.builder.CreateBitCast(taskState, c.i8ptrType, "task.state.i8")
id := c.builder.CreateCall(c.coroIdFunc, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
stateI8,
llvm.ConstNull(c.i8ptrType),
llvm.ConstNull(c.i8ptrType),
}, "task.token")
size := c.builder.CreateCall(c.coroSizeFunc, nil, "task.size")
if c.targetData.TypeAllocSize(size.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
size = c.builder.CreateTrunc(size, c.uintptrType, "task.size.uintptr")
} else if c.targetData.TypeAllocSize(size.Type()) < c.targetData.TypeAllocSize(c.uintptrType) {
size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr")
}
data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data")
frame.taskHandle = c.builder.CreateCall(c.coroBeginFunc, []llvm.Value{id, data}, "task.handle")
// Coroutine cleanup. Free resources associated with this coroutine.
c.builder.SetInsertPointAtEnd(frame.cleanupBlock)
mem := c.builder.CreateCall(c.coroFreeFunc, []llvm.Value{id, frame.taskHandle}, "task.data.free")
c.createRuntimeCall("free", []llvm.Value{mem}, "")
// re-insert parent coroutine
c.createRuntimeCall("yieldToScheduler", []llvm.Value{frame.fn.LLVMFn.FirstParam()}, "")
c.builder.CreateBr(frame.suspendBlock)
// Coroutine suspend. A call to llvm.coro.suspend() will branch here.
c.builder.SetInsertPointAtEnd(frame.suspendBlock)
c.builder.CreateCall(c.coroEndFunc, []llvm.Value{frame.taskHandle, llvm.ConstInt(c.ctx.Int1Type(), 0, false)}, "unused")
c.builder.CreateRet(frame.taskHandle)
}
// Fill blocks with instructions.
for _, block := range frame.fn.DomPreorder() {
if c.DumpSSA {
@@ -1200,38 +1283,25 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error {
case *ssa.Defer:
return c.emitDefer(frame, instr)
case *ssa.Go:
if instr.Call.IsInvoke() {
if instr.Common().Method != nil {
return c.makeError(instr.Pos(), "todo: go on method receiver")
}
callee := instr.Call.StaticCallee()
if callee == nil {
return c.makeError(instr.Pos(), "todo: go on non-direct function (function pointer, etc.)")
}
calleeFn := c.ir.GetFunction(callee)
// Mark this function as a 'go' invocation and break invalid
// interprocedural optimizations. For example, heap-to-stack
// transformations are not sound as goroutines can outlive their parent.
calleeType := calleeFn.LLVMFn.Type()
calleeValue := c.builder.CreateBitCast(calleeFn.LLVMFn, c.i8ptrType, "")
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
calleeValue = c.builder.CreateBitCast(calleeValue, calleeType, "")
// Get all function parameters to pass to the goroutine.
var params []llvm.Value
for _, param := range instr.Call.Args {
val, err := c.parseExpr(frame, param)
if err != nil {
return err
}
params = append(params, val)
}
if !calleeFn.IsExported() {
params = append(params, llvm.Undef(c.i8ptrType)) // context parameter
params = append(params, llvm.Undef(c.i8ptrType)) // parent coroutine handle
// Execute non-blocking calls (including builtins) directly.
// parentHandle param is ignored.
if !c.ir.IsBlocking(c.ir.GetFunction(instr.Common().Value.(*ssa.Function))) {
_, err := c.parseCall(frame, instr.Common(), llvm.Value{})
return err // probably nil
}
c.createCall(calleeValue, params, "")
// Start this goroutine.
// parentHandle is nil, as the goroutine has no parent frame (it's a new
// stack).
handle, err := c.parseCall(frame, instr.Common(), llvm.Value{})
if err != nil {
return err
}
c.createRuntimeCall("yieldToScheduler", []llvm.Value{handle}, "")
return nil
case *ssa.If:
cond, err := c.parseExpr(frame, instr.Cond)
@@ -1271,36 +1341,48 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error {
c.builder.CreateUnreachable()
return nil
case *ssa.Return:
if len(instr.Results) == 0 {
c.builder.CreateRetVoid()
return nil
} else if len(instr.Results) == 1 {
val, err := c.parseExpr(frame, instr.Results[0])
if err != nil {
return err
if frame.blocking {
if len(instr.Results) != 0 {
return c.makeError(instr.Pos(), "todo: return values from blocking function")
}
c.builder.CreateRet(val)
// Final suspend.
continuePoint := c.builder.CreateCall(c.coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 1, false), // final=true
}, "")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
return nil
} else {
// Multiple return values. Put them all in a struct.
retVal, err := c.getZeroValue(frame.fn.LLVMFn.Type().ElementType().ReturnType())
if err != nil {
return err
}
for i, result := range instr.Results {
val, err := c.parseExpr(frame, result)
if len(instr.Results) == 0 {
c.builder.CreateRetVoid()
return nil
} else if len(instr.Results) == 1 {
val, err := c.parseExpr(frame, instr.Results[0])
if err != nil {
return err
}
retVal = c.builder.CreateInsertValue(retVal, val, i, "")
c.builder.CreateRet(val)
return nil
} else {
// Multiple return values. Put them all in a struct.
retVal, err := c.getZeroValue(frame.fn.LLVMFn.Type().ElementType().ReturnType())
if err != nil {
return err
}
for i, result := range instr.Results {
val, err := c.parseExpr(frame, result)
if err != nil {
return err
}
retVal = c.builder.CreateInsertValue(retVal, val, i, "")
}
c.builder.CreateRet(retVal)
return nil
}
c.builder.CreateRet(retVal)
return nil
}
case *ssa.RunDefers:
return c.emitRunDefers(frame)
case *ssa.Send:
return c.emitChanSend(frame, instr)
case *ssa.Store:
llvmAddr, err := c.parseExpr(frame, instr.Addr)
if err == ir.ErrCGoWrapper {
@@ -1366,17 +1448,11 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string,
return llvm.Value{}, err
}
switch args[0].Type().(type) {
case *types.Chan:
// Channel. Buffered channels haven't been implemented yet so always
// return 0.
return llvm.ConstInt(c.intType, 0, false), nil
case *types.Slice:
return c.builder.CreateExtractValue(value, 2, "cap"), nil
default:
return llvm.Value{}, c.makeError(pos, "todo: cap: unknown type")
}
case "close":
return llvm.Value{}, c.emitChanClose(frame, args[0])
case "complex":
r, err := c.parseExpr(frame, args[0])
if err != nil {
@@ -1444,10 +1520,6 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string,
case *types.Basic, *types.Slice:
// string or slice
llvmLen = c.builder.CreateExtractValue(value, 1, "len")
case *types.Chan:
// Channel. Buffered channels haven't been implemented yet so always
// return 0.
llvmLen = llvm.ConstInt(c.intType, 0, false)
case *types.Map:
llvmLen = c.createRuntimeCall("hashmapLen", []llvm.Value{value}, "len")
default:
@@ -1534,8 +1606,17 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string,
}
}
func (c *Compiler) parseFunctionCall(frame *Frame, args []ssa.Value, llvmFn, context llvm.Value, exported bool) (llvm.Value, error) {
func (c *Compiler) parseFunctionCall(frame *Frame, args []ssa.Value, llvmFn, context llvm.Value, blocking bool, parentHandle llvm.Value) (llvm.Value, error) {
var params []llvm.Value
if blocking {
if parentHandle.IsNil() {
// Started from 'go' statement.
params = append(params, llvm.ConstNull(c.i8ptrType))
} else {
// Blocking function calls another blocking function.
params = append(params, parentHandle)
}
}
for _, param := range args {
val, err := c.parseExpr(frame, param)
if err != nil {
@@ -1544,20 +1625,60 @@ func (c *Compiler) parseFunctionCall(frame *Frame, args []ssa.Value, llvmFn, con
params = append(params, val)
}
if !exported {
if !context.IsNil() {
// This function takes a context parameter.
// Add it to the end of the parameter list.
params = append(params, context)
// Parent coroutine handle.
params = append(params, llvm.Undef(c.i8ptrType))
}
return c.createCall(llvmFn, params, ""), nil
if frame.blocking && llvmFn.Name() == "time.Sleep" {
// Set task state to TASK_STATE_SLEEP and set the duration.
c.createRuntimeCall("sleepTask", []llvm.Value{frame.taskHandle, params[0]}, "")
// Yield to scheduler.
continuePoint := c.builder.CreateCall(c.coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "")
wakeup := c.ctx.InsertBasicBlock(llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
c.builder.SetInsertPointAtEnd(wakeup)
return llvm.Value{}, nil
}
result := c.createCall(llvmFn, params, "")
if blocking && !parentHandle.IsNil() {
// Calling a blocking function as a regular function call.
// This is done by passing the current coroutine as a parameter to the
// new coroutine and dropping the current coroutine from the scheduler
// (with the TASK_STATE_CALL state). When the subroutine is finished, it
// will reactivate the parent (this frame) in it's destroy function.
c.createRuntimeCall("yieldToScheduler", []llvm.Value{result}, "")
// Set task state to TASK_STATE_CALL.
c.createRuntimeCall("waitForAsyncCall", []llvm.Value{frame.taskHandle}, "")
// Yield to the scheduler.
continuePoint := c.builder.CreateCall(c.coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "")
resume := c.ctx.InsertBasicBlock(llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.callComplete")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), resume)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
c.builder.SetInsertPointAtEnd(resume)
}
return result, nil
}
func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon, parentHandle llvm.Value) (llvm.Value, error) {
if instr.IsInvoke() {
// TODO: blocking methods (needs analysis)
fnCast, args, err := c.getInvokeCall(frame, instr)
if err != nil {
return llvm.Value{}, err
@@ -1700,7 +1821,7 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
} else {
context = llvm.Undef(c.i8ptrType)
}
return c.parseFunctionCall(frame, instr.Args, targetFunc.LLVMFn, context, targetFunc.IsExported())
return c.parseFunctionCall(frame, instr.Args, targetFunc.LLVMFn, context, c.ir.IsBlocking(targetFunc), parentHandle)
}
// Builtin or function pointer.
@@ -1712,12 +1833,13 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
if err != nil {
return llvm.Value{}, err
}
// TODO: blocking function pointers (needs analysis)
// 'value' is a closure, not a raw function pointer.
// Extract the function pointer and the context pointer.
// closure: {context, function pointer}
context := c.builder.CreateExtractValue(value, 0, "")
value = c.builder.CreateExtractValue(value, 1, "")
return c.parseFunctionCall(frame, instr.Args, value, context, false)
return c.parseFunctionCall(frame, instr.Args, value, context, false, parentHandle)
}
}
@@ -1832,7 +1954,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
case *ssa.Call:
// Passing the current task here to the subroutine. It is only used when
// the subroutine is blocking.
return c.parseCall(frame, expr.Common())
return c.parseCall(frame, expr.Common(), frame.taskHandle)
case *ssa.ChangeInterface:
// Do not change between interface types: always use the underlying
// (concrete) type in the type number of the interface. Every method
@@ -1995,7 +2117,10 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
// Bounds check.
// LLVM optimizes this away in most cases.
length := c.builder.CreateExtractValue(value, 1, "len")
length, err := c.parseBuiltin(frame, []ssa.Value{expr.X}, "len", expr.Pos())
if err != nil {
return llvm.Value{}, err // shouldn't happen
}
c.emitBoundsCheck(frame, length, index, expr.Index.Type())
// Lookup byte
@@ -2011,12 +2136,12 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
default:
panic("unknown lookup type: " + expr.String())
}
case *ssa.MakeChan:
return c.emitMakeChan(expr)
case *ssa.MakeClosure:
// A closure returns a function pointer with context:
// {context, fp}
return c.parseMakeClosure(frame, expr)
case *ssa.MakeInterface:
val, err := c.parseExpr(frame, expr.X)
if err != nil {
@@ -2952,8 +3077,6 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
}
case token.XOR: // ^x, toggle all bits in integer
return c.builder.CreateXor(x, llvm.ConstInt(x.Type(), ^uint64(0), false), ""), nil
case token.ARROW: // <-x, receive from channel
return c.emitChanRecv(frame, unop)
default:
return llvm.Value{}, c.makeError(unop.Pos(), "todo: unknown unop")
}
@@ -3006,10 +3129,8 @@ func (c *Compiler) ExternalInt64AsPtr() error {
// Only change externally visible functions (exports and imports).
continue
}
if strings.HasPrefix(fn.Name(), "llvm.") || strings.HasPrefix(fn.Name(), "runtime.") {
// Do not try to modify the signature of internal LLVM functions and
// assume that runtime functions are only temporarily exported for
// coroutine lowering.
if strings.HasPrefix(fn.Name(), "llvm.") {
// Do not try to modify the signature of internal LLVM functions.
continue
}
+2 -11
View File
@@ -14,9 +14,9 @@ package compiler
// frames.
import (
"github.com/tinygo-org/tinygo/ir"
"github.com/aykevl/go-llvm"
"github.com/aykevl/tinygo/ir"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// deferInitFunc sets up this function for future deferred calls. It must be
@@ -243,9 +243,6 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
// with a strict calling convention.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
fnPtr, _, err := c.getInvokeCall(frame, callback)
if err != nil {
return err
@@ -280,9 +277,6 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
// function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
// Call real function.
c.createCall(callback.LLVMFn, forwardParams, "")
@@ -311,9 +305,6 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
forwardParams = append(forwardParams, forwardParam)
}
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
// Call deferred function.
c.createCall(fn.LLVMFn, forwardParams, "")
+199
View File
@@ -0,0 +1,199 @@
package compiler
// This file implements a compiler pass to move GC pointers to a "shadow stack"
// that can easily be scanned by a garbage collector, even without platform
// support.
// For more information, see:
// https://llvm.org/docs/GarbageCollection.html#the-shadow-stack-gc
import (
"github.com/aykevl/go-llvm"
)
// AddGCRoots moves pointer values to shadow stack frames when this function (or
// any function it calls) may allocate something. This allows the GC to scan the
// stack in a highly portable way.
func (c *Compiler) AddGCRoots() {
alloc := c.mod.NamedFunction("runtime.alloc")
if alloc.IsNil() {
return
}
// Find all functions that do memory allocation.
worklist := []llvm.Value{alloc}
allocSet := make(map[llvm.Value]struct{})
allocList := make([]llvm.Value, 0, 4)
for len(worklist) != 0 {
// Pick the topmost.
f := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
if _, ok := allocSet[f]; ok {
continue // already added to list
}
// Add to set of allocating functions.
allocSet[f] = struct{}{}
allocList = append(allocList, f)
// Add all callees to the worklist.
for _, use := range getUses(f) {
if use.IsACallInst().IsNil() {
// TODO: function pointers
panic("allocating function " + f.Name() + " used as function pointer")
}
parent := use.InstructionParent().Parent()
for i := 0; i < use.OperandsCount()-1; i++ {
if use.Operand(i) == f {
// TODO: function pointers
panic("allocating function " + f.Name() + " used as function pointer in " + parent.Name())
}
}
worklist = append(worklist, parent)
}
}
i8ptrPtrType := llvm.PointerType(c.i8ptrType, 0)
gcrootType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{i8ptrPtrType, c.i8ptrType}, false)
gcroot := llvm.AddFunction(c.mod, "llvm.gcroot", gcrootType)
// Process every function that needs to save pointers to the shadow stack.
for _, fn := range allocList {
if fn == alloc {
// runtime.alloc itself should not be treated this way, it is a
// special case.
continue
}
// Check all instructions in this function and see whether the value
// needs to be kept on the shadow stack.
var values []llvm.Value // values to be kept in the shadow stack
for bb := fn.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if !typeHasPointer(inst.Type()) {
// This instruction does not result in a pointer value.
continue
}
// Check whether any of the uses may occur after a call to
// runtime.alloca. For example, if there are no call
// instructions between the definition and the use, then the
// pointer does not have to be stored in the shadow stack.
for _, use := range getUses(inst) {
if crossesAllocatingInst(inst, use, allocSet) {
values = append(values, inst)
break
}
}
}
}
if len(values) == 0 {
// The children of this function do allocations, but there is
// nothing to keep in a stack frame for this function.
continue
}
fn.SetGC("shadow-stack")
// Convert all values to be kept in the shadow stack to actually be in
// the shadow stack.
firstInst := fn.EntryBasicBlock().FirstInstruction()
for _, value := range values {
valueUses := getUses(value)
c.builder.SetInsertPointBefore(firstInst)
alloca := c.builder.CreateAlloca(value.Type(), "gcroot.value")
c.builder.SetInsertPointBefore(llvm.NextInstruction(value))
c.builder.CreateStore(value, alloca)
metadata := c.gcTypeMetadata(alloca.Type().ElementType())
allocaCast := alloca
if alloca.Type() != i8ptrPtrType {
allocaCast = c.builder.CreateBitCast(alloca, i8ptrPtrType, "")
}
c.builder.CreateCall(gcroot, []llvm.Value{allocaCast, metadata}, "")
for _, use := range valueUses {
c.builder.SetInsertPointBefore(use)
load := c.builder.CreateLoad(alloca, "")
for i := 0; i < use.OperandsCount(); i++ {
if use.Operand(i) == value {
use.SetOperand(i, load)
}
}
}
}
}
println(c.IR())
}
// typeHasPointer returns true if (and only if) the given type contains a
// pointer value.
func typeHasPointer(typ llvm.Type) bool {
switch typ.TypeKind() {
case llvm.PointerTypeKind:
return true
case llvm.ArrayTypeKind, llvm.VectorTypeKind:
return typeHasPointer(typ.ElementType())
case llvm.StructTypeKind:
return false
for _, subtyp := range typ.StructElementTypes() {
if typeHasPointer(subtyp) {
return true
}
}
return false
default:
return false
}
}
// gcTypeMetadata returns a pointer value to be used in the llvm.gcroot
// intrinsic. It is either a null pointer or a number which is the number of
// words in the stack slot for this value.
func (c *Compiler) gcTypeMetadata(typ llvm.Type) llvm.Value {
if typ.TypeKind() == llvm.PointerTypeKind {
// Simple pointer. This is a common case, so signal this fact by setting
// the pointer to null.
return llvm.ConstPointerNull(c.i8ptrType)
}
if typ.TypeKind() == llvm.StructTypeKind {
// Check for structs that only contain a pointer at the start.
// We can pretend that such structs are a simple pointer, as the GC only
// needs to read the first word.
subTypes := typ.StructElementTypes()
onlyFirstPointer := subTypes[0].TypeKind() == llvm.PointerTypeKind
for _, subType := range subTypes[1:] {
if typeHasPointer(subType) {
onlyFirstPointer = false
}
}
if onlyFirstPointer {
// Types like string and slice.
return llvm.ConstPointerNull(c.i8ptrType)
}
}
allocaSize := c.targetData.TypeAllocSize(typ)
pointerAlignment := uint64(c.targetData.PrefTypeAlignment(c.i8ptrType))
numWords := allocaSize / pointerAlignment
// TODO: only return the number until all pointers are included in this
// struct, not more.
metadata := llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, numWords, false), c.i8ptrType)
return metadata
}
// crossesAllocatingInst returns true if the given value may be used across a
// call to runtime.alloc. This check is very conservative.
func crossesAllocatingInst(from, to llvm.Value, allocSet map[llvm.Value]struct{}) bool {
if from.InstructionParent() != to.InstructionParent() {
// Don't try to check the CFG, conservatively assume there is an alloca
// in between these instructions.
return true
}
for inst := llvm.NextInstruction(from); inst != to; inst = llvm.NextInstruction(inst) {
if inst.IsACallInst().IsNil() {
// Not a call instruction thus not an alloca instruction.
continue
}
if _, ok := allocSet[inst.CalledValue()]; ok {
// This call is to a function that may do an allocation, or is even
// runtime.alloc itself.
// TODO: function pointers
return true
}
}
return false
}
-588
View File
@@ -1,588 +0,0 @@
package compiler
// This file lowers goroutine pseudo-functions into coroutines scheduled by a
// scheduler at runtime. It uses coroutine support in LLVM for this
// transformation: https://llvm.org/docs/Coroutines.html
//
// For example, take the following code:
//
// func main() {
// go foo()
// time.Sleep(2 * time.Second)
// println("some other operation")
// bar()
// println("done")
// }
//
// func foo() {
// for {
// println("foo!")
// time.Sleep(time.Second)
// }
// }
//
// func bar() {
// time.Sleep(time.Second)
// println("blocking operation completed)
// }
//
// It is transformed by the IR generator in compiler.go into the following
// pseudo-Go code:
//
// func main() {
// fn := runtime.makeGoroutine(foo)
// fn()
// time.Sleep(2 * time.Second)
// println("some other operation")
// bar() // imagine an 'await' keyword in front of this call
// println("done")
// }
//
// func foo() {
// for {
// println("foo!")
// time.Sleep(time.Second)
// }
// }
//
// func bar() {
// time.Sleep(time.Second)
// println("blocking operation completed)
// }
//
// The pass in this file transforms this code even further, to the following
// async/await style pseudocode:
//
// func main(parent) {
// hdl := llvm.makeCoroutine()
// foo(nil) // do not pass the parent coroutine: this is an independent goroutine
// runtime.sleepTask(hdl, 2 * time.Second) // ask the scheduler to re-activate this coroutine at the right time
// llvm.suspend(hdl) // suspend point
// println("some other operation")
// bar(hdl) // await, pass a continuation (hdl) to bar
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
// println("done")
// runtime.activateTask(parent) // re-activate the parent (nop, there is no parent)
// }
//
// func foo(parent) {
// hdl := llvm.makeCoroutine()
// for {
// println("foo!")
// runtime.sleepTask(hdl, time.Second) // ask the scheduler to re-activate this coroutine at the right time
// llvm.suspend(hdl) // suspend point
// }
// }
//
// func bar(parent) {
// hdl := llvm.makeCoroutine()
// runtime.sleepTask(hdl, time.Second) // ask the scheduler to re-activate this coroutine at the right time
// llvm.suspend(hdl) // suspend point
// println("blocking operation completed)
// runtime.activateTask(parent) // re-activate the parent coroutine before returning
// }
//
// The real LLVM code is more complicated, but this is the general idea.
//
// The LLVM coroutine passes will then process this file further transforming
// these three functions into coroutines. Most of the actual work is done by the
// scheduler, which runs in the background scheduling all coroutines.
import (
"errors"
"strings"
"tinygo.org/x/go-llvm"
)
type asyncFunc struct {
taskHandle llvm.Value
cleanupBlock llvm.BasicBlock
suspendBlock llvm.BasicBlock
unreachableBlock llvm.BasicBlock
}
// LowerGoroutines is a pass called during optimization that transforms the IR
// into one where all blocking functions are turned into goroutines and blocking
// calls into await calls.
func (c *Compiler) LowerGoroutines() error {
needsScheduler, err := c.markAsyncFunctions()
if err != nil {
return err
}
uses := getUses(c.mod.NamedFunction("runtime.callMain"))
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
panic("expected exactly 1 call of runtime.callMain, check the entry point")
}
mainCall := uses[0]
// Replace call of runtime.callMain() with a real call to main.main(),
// optionally followed by a call to runtime.scheduler().
c.builder.SetInsertPointBefore(mainCall)
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
c.builder.CreateCall(realMain, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.ConstPointerNull(c.i8ptrType)}, "")
if needsScheduler {
c.createRuntimeCall("scheduler", nil, "")
}
mainCall.EraseFromParentAsInstruction()
if !needsScheduler {
go_scheduler := c.mod.NamedFunction("go_scheduler")
if !go_scheduler.IsNil() {
// This is the WebAssembly backend.
// There is no need to export the go_scheduler function, but it is
// still exported. Make sure it is optimized away.
go_scheduler.SetLinkage(llvm.InternalLinkage)
}
}
// main.main was set to external linkage during IR construction. Set it to
// internal linkage to enable interprocedural optimizations.
realMain.SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.chanSend").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.chanRecv").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
return nil
}
// markAsyncFunctions does the bulk of the work of lowering goroutines. It
// determines whether a scheduler is needed, and if it is, it transforms
// blocking operations into goroutines and blocking calls into await calls.
//
// It does the following operations:
// * Find all blocking functions.
// * Determine whether a scheduler is necessary. If not, it skips the
// following operations.
// * Transform call instructions into await calls.
// * Transform return instructions into final suspends.
// * Set up the coroutine frames for async functions.
// * Transform blocking calls into their async equivalents.
func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
var worklist []llvm.Value
sleep := c.mod.NamedFunction("time.Sleep")
if !sleep.IsNil() {
worklist = append(worklist, sleep)
}
chanSendStub := c.mod.NamedFunction("runtime.chanSendStub")
if !chanSendStub.IsNil() {
worklist = append(worklist, chanSendStub)
}
chanRecvStub := c.mod.NamedFunction("runtime.chanRecvStub")
if !chanRecvStub.IsNil() {
worklist = append(worklist, chanRecvStub)
}
if len(worklist) == 0 {
// There are no blocking operations, so no need to transform anything.
return false, c.lowerMakeGoroutineCalls()
}
// Find all async functions.
// Keep reducing this worklist by marking a function as recursively async
// from the worklist and pushing all its parents that are non-async.
// This is somewhat similar to a worklist in a mark-sweep garbage collector:
// the work items are then grey objects.
asyncFuncs := make(map[llvm.Value]*asyncFunc)
asyncList := make([]llvm.Value, 0, 4)
for len(worklist) != 0 {
// Pick the topmost.
f := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
if _, ok := asyncFuncs[f]; ok {
continue // already processed
}
// Add to set of async functions.
asyncFuncs[f] = &asyncFunc{}
asyncList = append(asyncList, f)
// Add all callees to the worklist.
for _, use := range getUses(f) {
if use.IsConstant() && use.Opcode() == llvm.BitCast {
bitcastUses := getUses(use)
for _, call := range bitcastUses {
if call.IsACallInst().IsNil() || call.CalledValue().Name() != "runtime.makeGoroutine" {
return false, errors.New("async function " + f.Name() + " incorrectly used in bitcast, expected runtime.makeGoroutine")
}
}
// This is a go statement. Do not mark the parent as async, as
// starting a goroutine is not a blocking operation.
continue
}
if use.IsACallInst().IsNil() {
// Not a call instruction. Maybe a store to a global? In any
// case, this requires support for async calls across function
// pointers which is not yet supported.
return false, errors.New("async function " + f.Name() + " used as function pointer")
}
parent := use.InstructionParent().Parent()
for i := 0; i < use.OperandsCount()-1; i++ {
if use.Operand(i) == f {
return false, errors.New("async function " + f.Name() + " used as function pointer in " + parent.Name())
}
}
worklist = append(worklist, parent)
}
}
// Check whether a scheduler is needed.
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
if c.GOOS == "js" && strings.HasPrefix(c.Triple, "wasm") {
// JavaScript always needs a scheduler, as in general no blocking
// operations are possible. Blocking operations block the browser UI,
// which is very bad.
needsScheduler = true
} else {
// Only use a scheduler when an async goroutine is started. When the
// goroutine is not async (does not do any blocking operation), no
// scheduler is necessary as it can be called directly.
for _, use := range getUses(makeGoroutine) {
// Input param must be const bitcast of function.
bitcast := use.Operand(0)
if !bitcast.IsConstant() || bitcast.Opcode() != llvm.BitCast {
panic("expected const bitcast operand of runtime.makeGoroutine")
}
goroutine := bitcast.Operand(0)
if _, ok := asyncFuncs[goroutine]; ok {
needsScheduler = true
break
}
}
}
if !needsScheduler {
// No scheduler is needed. Do not transform all functions here.
// However, make sure that all go calls (which are all non-async) are
// transformed into regular calls.
return false, c.lowerMakeGoroutineCalls()
}
// Create a few LLVM intrinsics for coroutine support.
coroIdType := llvm.FunctionType(c.ctx.TokenType(), []llvm.Type{c.ctx.Int32Type(), c.i8ptrType, c.i8ptrType, c.i8ptrType}, false)
coroIdFunc := llvm.AddFunction(c.mod, "llvm.coro.id", coroIdType)
coroSizeType := llvm.FunctionType(c.ctx.Int32Type(), nil, false)
coroSizeFunc := llvm.AddFunction(c.mod, "llvm.coro.size.i32", coroSizeType)
coroBeginType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
coroBeginFunc := llvm.AddFunction(c.mod, "llvm.coro.begin", coroBeginType)
coroPromiseType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.i8ptrType, c.ctx.Int32Type(), c.ctx.Int1Type()}, false)
coroPromiseFunc := llvm.AddFunction(c.mod, "llvm.coro.promise", coroPromiseType)
coroSuspendType := llvm.FunctionType(c.ctx.Int8Type(), []llvm.Type{c.ctx.TokenType(), c.ctx.Int1Type()}, false)
coroSuspendFunc := llvm.AddFunction(c.mod, "llvm.coro.suspend", coroSuspendType)
coroEndType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.i8ptrType, c.ctx.Int1Type()}, false)
coroEndFunc := llvm.AddFunction(c.mod, "llvm.coro.end", coroEndType)
coroFreeType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
coroFreeFunc := llvm.AddFunction(c.mod, "llvm.coro.free", coroFreeType)
// Transform all async functions into coroutines.
for _, f := range asyncList {
if f == sleep || f == chanSendStub || f == chanRecvStub {
continue
}
frame := asyncFuncs[f]
frame.cleanupBlock = c.ctx.AddBasicBlock(f, "task.cleanup")
frame.suspendBlock = c.ctx.AddBasicBlock(f, "task.suspend")
frame.unreachableBlock = c.ctx.AddBasicBlock(f, "task.unreachable")
// Scan for async calls and return instructions that need to have
// suspend points inserted.
var asyncCalls []llvm.Value
var returns []llvm.Value
for bb := f.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if !inst.IsACallInst().IsNil() {
callee := inst.CalledValue()
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == chanSendStub || callee == chanRecvStub {
continue
}
asyncCalls = append(asyncCalls, inst)
} else if !inst.IsAReturnInst().IsNil() {
returns = append(returns, inst)
}
}
}
// Coroutine setup.
c.builder.SetInsertPointBefore(f.EntryBasicBlock().FirstInstruction())
taskState := c.builder.CreateAlloca(c.mod.GetTypeByName("runtime.taskState"), "task.state")
stateI8 := c.builder.CreateBitCast(taskState, c.i8ptrType, "task.state.i8")
id := c.builder.CreateCall(coroIdFunc, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
stateI8,
llvm.ConstNull(c.i8ptrType),
llvm.ConstNull(c.i8ptrType),
}, "task.token")
size := c.builder.CreateCall(coroSizeFunc, nil, "task.size")
if c.targetData.TypeAllocSize(size.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
size = c.builder.CreateTrunc(size, c.uintptrType, "task.size.uintptr")
} else if c.targetData.TypeAllocSize(size.Type()) < c.targetData.TypeAllocSize(c.uintptrType) {
size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr")
}
data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data")
frame.taskHandle = c.builder.CreateCall(coroBeginFunc, []llvm.Value{id, data}, "task.handle")
// Modify async calls so this function suspends right after the child
// returns, because the child is probably not finished yet. Wait until
// the child reactivates the parent.
for _, inst := range asyncCalls {
inst.SetOperand(inst.OperandsCount()-2, frame.taskHandle)
// Split this basic block.
await := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.await")
// Set task state to TASK_STATE_CALL.
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
// Suspend.
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), await)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
}
// Replace return instructions with suspend points that should
// reactivate the parent coroutine.
for _, inst := range returns {
if inst.OperandsCount() == 0 {
// These properties were added by the functionattrs pass.
// Remove them, because now we start using the parameter.
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
for _, kind := range []string{"nocapture", "readnone"} {
kindID := llvm.AttributeKindID(kind)
f.RemoveEnumAttributeAtIndex(f.ParamsCount(), kindID)
}
// Reactivate the parent coroutine. This adds it back to
// the run queue, so it is started again by the
// scheduler when possible (possibly right after the
// following suspend).
c.builder.SetInsertPointBefore(inst)
parentHandle := f.LastParam()
c.createRuntimeCall("activateTask", []llvm.Value{parentHandle}, "")
// Suspend this coroutine.
// It would look like this is unnecessary, but if this
// suspend point is left out, it leads to undefined
// behavior somehow (with the unreachable instruction).
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 1, false),
}, "ret")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
inst.EraseFromParentAsInstruction()
} else {
panic("todo: return value from coroutine")
}
}
// Coroutine cleanup. Free resources associated with this coroutine.
c.builder.SetInsertPointAtEnd(frame.cleanupBlock)
mem := c.builder.CreateCall(coroFreeFunc, []llvm.Value{id, frame.taskHandle}, "task.data.free")
c.createRuntimeCall("free", []llvm.Value{mem}, "")
c.builder.CreateBr(frame.suspendBlock)
// Coroutine suspend. A call to llvm.coro.suspend() will branch here.
c.builder.SetInsertPointAtEnd(frame.suspendBlock)
c.builder.CreateCall(coroEndFunc, []llvm.Value{frame.taskHandle, llvm.ConstInt(c.ctx.Int1Type(), 0, false)}, "unused")
returnType := f.Type().ElementType().ReturnType()
if returnType.TypeKind() == llvm.VoidTypeKind {
c.builder.CreateRetVoid()
} else {
c.builder.CreateRet(llvm.Undef(returnType))
}
// Coroutine exit. All final suspends (return instructions) will branch
// here.
c.builder.SetInsertPointAtEnd(frame.unreachableBlock)
c.builder.CreateUnreachable()
}
// Transform calls to time.Sleep() into coroutine suspend points.
for _, sleepCall := range getUses(sleep) {
// sleepCall must be a call instruction.
frame := asyncFuncs[sleepCall.InstructionParent().Parent()]
duration := sleepCall.Operand(0)
// Set task state to TASK_STATE_SLEEP and set the duration.
c.builder.SetInsertPointBefore(sleepCall)
c.createRuntimeCall("sleepTask", []llvm.Value{frame.taskHandle, duration}, "")
// Yield to scheduler.
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "")
wakeup := c.splitBasicBlock(sleepCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup")
c.builder.SetInsertPointBefore(sleepCall)
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
sleepCall.EraseFromParentAsInstruction()
}
// Transform calls to runtime.chanSendStub into channel send operations.
for _, sendOp := range getUses(chanSendStub) {
// sendOp must be a call instruction.
frame := asyncFuncs[sendOp.InstructionParent().Parent()]
// Send the value over the channel, or block.
sendOp.SetOperand(0, frame.taskHandle)
sendOp.SetOperand(sendOp.OperandsCount()-1, c.mod.NamedFunction("runtime.chanSend"))
// Use taskState.data to store the value to send:
// *(*valueType)(&coroutine.promise().data) = valueToSend
// runtime.chanSend(coroutine, ch)
bitcast := sendOp.Operand(2)
valueAlloca := bitcast.Operand(0)
c.builder.SetInsertPointBefore(valueAlloca)
promiseType := c.mod.GetTypeByName("runtime.taskState")
promiseRaw := c.builder.CreateCall(coroPromiseFunc, []llvm.Value{
frame.taskHandle,
llvm.ConstInt(c.ctx.Int32Type(), uint64(c.targetData.PrefTypeAlignment(promiseType)), false),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "task.promise.raw")
promise := c.builder.CreateBitCast(promiseRaw, llvm.PointerType(promiseType, 0), "task.promise")
dataPtr := c.builder.CreateGEP(promise, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 2, false),
}, "task.promise.data")
sendOp.SetOperand(2, llvm.Undef(c.i8ptrType))
valueAlloca.ReplaceAllUsesWith(c.builder.CreateBitCast(dataPtr, valueAlloca.Type(), ""))
bitcast.EraseFromParentAsInstruction()
valueAlloca.EraseFromParentAsInstruction()
// Yield to scheduler.
c.builder.SetInsertPointBefore(llvm.NextInstruction(sendOp))
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
wakeup := c.splitBasicBlock(sw, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.sent")
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
}
// Transform calls to runtime.chanRecvStub into channel receive operations.
for _, recvOp := range getUses(chanRecvStub) {
// recvOp must be a call instruction.
frame := asyncFuncs[recvOp.InstructionParent().Parent()]
bitcast := recvOp.Operand(2)
commaOk := recvOp.Operand(3)
valueAlloca := bitcast.Operand(0)
// Receive the value over the channel, or block.
recvOp.SetOperand(0, frame.taskHandle)
recvOp.SetOperand(recvOp.OperandsCount()-1, c.mod.NamedFunction("runtime.chanRecv"))
recvOp.SetOperand(2, llvm.Undef(c.i8ptrType))
bitcast.EraseFromParentAsInstruction()
// Yield to scheduler.
c.builder.SetInsertPointBefore(llvm.NextInstruction(recvOp))
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
wakeup := c.splitBasicBlock(sw, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.received")
c.builder.SetInsertPointAtEnd(recvOp.InstructionParent())
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
// The value to receive is stored in taskState.data:
// runtime.chanRecv(coroutine, ch)
// promise := coroutine.promise()
// valueReceived := *(*valueType)(&promise.data)
// ok := promise.commaOk
c.builder.SetInsertPointBefore(wakeup.FirstInstruction())
promiseType := c.mod.GetTypeByName("runtime.taskState")
promiseRaw := c.builder.CreateCall(coroPromiseFunc, []llvm.Value{
frame.taskHandle,
llvm.ConstInt(c.ctx.Int32Type(), uint64(c.targetData.PrefTypeAlignment(promiseType)), false),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "task.promise.raw")
promise := c.builder.CreateBitCast(promiseRaw, llvm.PointerType(promiseType, 0), "task.promise")
dataPtr := c.builder.CreateGEP(promise, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 2, false),
}, "task.promise.data")
valueAlloca.ReplaceAllUsesWith(c.builder.CreateBitCast(dataPtr, valueAlloca.Type(), ""))
valueAlloca.EraseFromParentAsInstruction()
commaOkPtr := c.builder.CreateGEP(promise, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 1, false),
}, "task.promise.comma-ok")
commaOk.ReplaceAllUsesWith(commaOkPtr)
recvOp.SetOperand(3, llvm.Undef(commaOk.Type()))
}
return true, c.lowerMakeGoroutineCalls()
}
// Lower runtime.makeGoroutine calls to regular call instructions. This is done
// after the regular goroutine transformations. The started goroutines are
// either non-blocking (in which case they can be called directly) or blocking,
// in which case they will ask the scheduler themselves to be rescheduled.
func (c *Compiler) lowerMakeGoroutineCalls() error {
// The following Go code:
// go startedGoroutine()
//
// Is translated to the following during IR construction, to preserve the
// fact that this function should be called as a new goroutine.
// %0 = call i8* @runtime.makeGoroutine(i8* bitcast (void (i8*, i8*)* @main.startedGoroutine to i8*), i8* undef, i8* null)
// %1 = bitcast i8* %0 to void (i8*, i8*)*
// call void %1(i8* undef, i8* undef)
//
// This function rewrites it to a direct call:
// call void @main.startedGoroutine(i8* undef, i8* null)
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
for _, goroutine := range getUses(makeGoroutine) {
bitcastIn := goroutine.Operand(0)
origFunc := bitcastIn.Operand(0)
uses := getUses(goroutine)
if len(uses) != 1 || uses[0].IsABitCastInst().IsNil() {
return errors.New("expected exactly 1 bitcast use of runtime.makeGoroutine")
}
bitcastOut := uses[0]
uses = getUses(bitcastOut)
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
return errors.New("expected exactly 1 call use of runtime.makeGoroutine bitcast")
}
realCall := uses[0]
// Create call instruction.
var params []llvm.Value
for i := 0; i < realCall.OperandsCount()-1; i++ {
params = append(params, realCall.Operand(i))
}
params[len(params)-1] = llvm.ConstPointerNull(c.i8ptrType) // parent coroutine handle (must be nil)
c.builder.SetInsertPointBefore(realCall)
c.builder.CreateCall(origFunc, params, "")
realCall.EraseFromParentAsInstruction()
bitcastOut.EraseFromParentAsInstruction()
goroutine.EraseFromParentAsInstruction()
}
return nil
}
+5 -5
View File
@@ -49,7 +49,7 @@ import (
"sort"
"strings"
"tinygo.org/x/go-llvm"
"github.com/aykevl/go-llvm"
)
// signatureInfo is a Go signature of an interface method. It does not represent
@@ -444,7 +444,7 @@ func (p *lowerInterfacesPass) run() {
var commaOk llvm.Value
if t.countMakeInterfaces == 0 {
// impossible type assert: optimize accordingly
commaOk = llvm.ConstInt(p.ctx.Int1Type(), 0, false)
commaOk = llvm.ConstInt(llvm.Int1Type(), 0, false)
} else {
// regular type assert
p.builder.SetInsertPointBefore(use)
@@ -631,9 +631,9 @@ func (p *lowerInterfacesPass) createInterfaceImplementsFunc(itf *interfaceInfo)
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 0, false))
}
// getInterfaceMethodFunc returns a thunk for calling a method on an interface.
// It only declares the function, createInterfaceMethodFunc actually defines the
// function.
// getInterfaceMethodFunc return a function that returns a function pointer 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 {
if fn, ok := itf.methodFuncs[signature]; ok {
// This function has already been created.
+2 -4
View File
@@ -9,9 +9,9 @@ import (
"go/token"
"go/types"
"github.com/tinygo-org/tinygo/ir"
"github.com/aykevl/go-llvm"
"github.com/aykevl/tinygo/ir"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// parseMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction.
@@ -330,8 +330,6 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
// Add the context parameter. An interface call never takes a context but we
// have to supply the parameter anyway.
args = append(args, llvm.Undef(c.i8ptrType))
// Add the parent goroutine handle.
args = append(args, llvm.Undef(c.i8ptrType))
return fnCast, args, nil
}
-95
View File
@@ -1,95 +0,0 @@
package compiler
import (
"tinygo.org/x/go-llvm"
)
// This file contains helper functions for LLVM that are not exposed in the Go
// bindings.
// Return a list of values (actually, instructions) where this value is used as
// an operand.
func getUses(value llvm.Value) []llvm.Value {
if value.IsNil() {
return nil
}
var uses []llvm.Value
use := value.FirstUse()
for !use.IsNil() {
uses = append(uses, use.User())
use = use.NextUse()
}
return uses
}
// splitBasicBlock splits a LLVM basic block into two parts. All instructions
// after afterInst are moved into a new basic block (created right after the
// current one) with the given name.
func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicBlock, name string) llvm.BasicBlock {
oldBlock := afterInst.InstructionParent()
newBlock := c.ctx.InsertBasicBlock(insertAfter, name)
var nextInstructions []llvm.Value // values to move
// Collect to-be-moved instructions.
inst := afterInst
for {
inst = llvm.NextInstruction(inst)
if inst.IsNil() {
break
}
nextInstructions = append(nextInstructions, inst)
}
// Move instructions.
c.builder.SetInsertPointAtEnd(newBlock)
for _, inst := range nextInstructions {
inst.RemoveFromParentAsInstruction()
c.builder.Insert(inst)
}
// Find PHI nodes to update.
var phiNodes []llvm.Value // PHI nodes to update
for bb := insertAfter.Parent().FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if inst.IsAPHINode().IsNil() {
continue
}
needsUpdate := false
incomingCount := inst.IncomingCount()
for i := 0; i < incomingCount; i++ {
if inst.IncomingBlock(i) == oldBlock {
needsUpdate = true
break
}
}
if !needsUpdate {
// PHI node has no incoming edge from the old block.
continue
}
phiNodes = append(phiNodes, inst)
}
}
// Update PHI nodes.
for _, phi := range phiNodes {
c.builder.SetInsertPointBefore(phi)
newPhi := c.builder.CreatePHI(phi.Type(), "")
incomingCount := phi.IncomingCount()
incomingVals := make([]llvm.Value, incomingCount)
incomingBlocks := make([]llvm.BasicBlock, incomingCount)
for i := 0; i < incomingCount; i++ {
value := phi.IncomingValue(i)
block := phi.IncomingBlock(i)
if block == oldBlock {
block = newBlock
}
incomingVals[i] = value
incomingBlocks[i] = block
}
newPhi.AddIncoming(incomingVals, incomingBlocks)
phi.ReplaceAllUsesWith(newPhi)
phi.EraseFromParentAsInstruction()
}
return newBlock
}
+4 -8
View File
@@ -6,7 +6,7 @@ import (
"go/token"
"go/types"
"tinygo.org/x/go-llvm"
"github.com/aykevl/go-llvm"
)
func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
@@ -29,7 +29,7 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
params := []llvm.Value{m, keyPtr, mapValuePtr}
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
} else {
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
return llvm.Value{}, c.makeError(pos, "todo: map lookup key type: "+keyType.String())
}
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
if commaOk {
@@ -61,7 +61,7 @@ func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, p
c.createRuntimeCall("hashmapBinarySet", params, "")
return nil
} else {
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
return c.makeError(pos, "todo: map update key type: "+keyType.String())
}
}
@@ -80,7 +80,7 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke
c.createRuntimeCall("hashmapBinaryDelete", params, "")
return nil
} else {
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
return c.makeError(pos, "todo: map delete key type: "+keyType.String())
}
}
@@ -120,10 +120,6 @@ func hashmapIsBinaryKey(keyType types.Type) bool {
}
}
return true
case *types.Array:
return hashmapIsBinaryKey(keyType.Elem())
case *types.Named:
return hashmapIsBinaryKey(keyType.Underlying())
default:
return false
}
+18 -15
View File
@@ -3,7 +3,7 @@ package compiler
import (
"errors"
"tinygo.org/x/go-llvm"
"github.com/aykevl/go-llvm"
)
// Run the LLVM optimizer over the module.
@@ -53,17 +53,12 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
c.OptimizeAllocs()
c.OptimizeStringToBytes()
err := c.LowerGoroutines()
if err != nil {
return err
if c.selectGC() == "shadowstack" {
c.AddGCRoots()
}
} else {
// Must be run at any optimization level.
c.LowerInterfaces()
err := c.LowerGoroutines()
if err != nil {
return err
}
}
if err := c.Verify(); err != nil {
return errors.New("optimizations caused a verification failure")
@@ -79,13 +74,6 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
}
}
// Run function passes again, because without it, llvm.coro.size.i32()
// doesn't get lowered.
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
funcPasses.RunFunc(fn)
}
funcPasses.FinalizeFunc()
// Run module passes.
modPasses := llvm.NewPassManager()
defer modPasses.Dispose()
@@ -340,3 +328,18 @@ func (c *Compiler) hasFlag(call, param llvm.Value, kind string) bool {
}
return true
}
// Return a list of values (actually, instructions) where this value is used as
// an operand.
func getUses(value llvm.Value) []llvm.Value {
if value.IsNil() {
return nil
}
var uses []llvm.Value
use := value.FirstUse()
for !use.IsNil() {
uses = append(uses, use.User())
use = use.NextUse()
}
return uses
}
+1 -1
View File
@@ -3,7 +3,7 @@ package interp
// This file provides useful types for errors encountered during IR evaluation.
import (
"tinygo.org/x/go-llvm"
"github.com/aykevl/go-llvm"
)
type Unsupported struct {
+2 -7
View File
@@ -7,7 +7,7 @@ import (
"errors"
"strings"
"tinygo.org/x/go-llvm"
"github.com/aykevl/go-llvm"
)
type frame struct {
@@ -244,18 +244,13 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
case callee.Name() == "runtime.hashmapStringSet":
// set a string key in the map
m := fr.getLocal(inst.Operand(0)).(*MapValue)
// "key" is a Go string value, which in the TinyGo calling convention is split up
// into separate pointer and length parameters.
keyBuf := fr.getLocal(inst.Operand(1))
keyLen := fr.getLocal(inst.Operand(2))
valPtr := fr.getLocal(inst.Operand(3))
m.PutString(keyBuf, keyLen, valPtr)
case callee.Name() == "runtime.hashmapBinarySet":
// set a binary (int etc.) key in the map
m := fr.getLocal(inst.Operand(0)).(*MapValue)
keyBuf := fr.getLocal(inst.Operand(1))
valPtr := fr.getLocal(inst.Operand(2))
m.PutBinary(keyBuf, valPtr)
// TODO: unimplemented
case callee.Name() == "runtime.stringConcat":
// adding two strings together
buf1Ptr := fr.getLocal(inst.Operand(0))
+2 -2
View File
@@ -10,7 +10,7 @@ import (
"errors"
"strings"
"tinygo.org/x/go-llvm"
"github.com/aykevl/go-llvm"
)
type Eval struct {
@@ -63,7 +63,7 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
return errors.New("expected all instructions in " + name + " to be *.init() calls")
}
pkgName := initName[:len(initName)-5]
_, err := e.Function(call.CalledValue(), []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, pkgName)
_, err := e.Function(call.CalledValue(), []Value{&LocalValue{e, undefPtr}}, pkgName)
if err == ErrUnreachable {
break
}
+1 -1
View File
@@ -1,7 +1,7 @@
package interp
import (
"tinygo.org/x/go-llvm"
"github.com/aykevl/go-llvm"
)
type sideEffectSeverity int
+1 -4
View File
@@ -1,7 +1,7 @@
package interp
import (
"tinygo.org/x/go-llvm"
"github.com/aykevl/go-llvm"
)
// Return a list of values (actually, instructions) where this value is used as
@@ -63,9 +63,6 @@ func getZeroValue(typ llvm.Type) llvm.Value {
// getStringBytes loads the byte slice of a Go string represented as a
// {ptr, len} pair.
func getStringBytes(strPtr Value, strLen llvm.Value) []byte {
if !strLen.IsConstant() {
panic("getStringBytes with a non-constant length")
}
buf := make([]byte, strLen.ZExtValue())
for i := range buf {
c := strPtr.GetElementPtr([]uint32{uint32(i)}).Load()
+1 -40
View File
@@ -5,7 +5,7 @@ package interp
import (
"strconv"
"tinygo.org/x/go-llvm"
"github.com/aykevl/go-llvm"
)
// A Value is a LLVM value with some extra methods attached for easier
@@ -451,13 +451,6 @@ func (v *MapValue) Value() llvm.Value {
keyBuf[i] = byte(n)
n >>= 8
}
} else if key.Type().TypeKind() == llvm.ArrayTypeKind &&
key.Type().ElementType().TypeKind() == llvm.IntegerTypeKind &&
key.Type().ElementType().IntTypeWidth() == 8 {
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
for i := range keyBuf {
keyBuf[i] = byte(llvm.ConstExtractValue(llvmKey, []uint32{uint32(i)}).ZExtValue())
}
} else {
panic("interp: map key type not implemented: " + key.Type().String())
}
@@ -568,38 +561,6 @@ func (v *MapValue) PutString(keyBuf, keyLen, valPtr Value) {
v.Values = append(v.Values, &LocalValue{v.Eval, value})
}
// PutBinary does a map assign operation.
func (v *MapValue) PutBinary(keyPtr, valPtr Value) {
if !v.Underlying.IsNil() {
panic("map already created")
}
var value llvm.Value
switch valPtr := valPtr.(type) {
case *PointerCastValue:
value = valPtr.Underlying.Load()
if v.ValueType.IsNil() {
v.ValueType = value.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
panic("interp: map store value type has the wrong size")
}
} else {
if value.Type() != v.ValueType {
panic("interp: map store value type is inconsistent")
}
}
default:
panic("interp: todo: handle map value pointer")
}
key := keyPtr.(*PointerCastValue).Underlying.Load()
v.KeyType = key.Type()
// TODO: avoid duplicate keys
v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
v.Values = append(v.Values, &LocalValue{v.Eval, value})
}
// Get FNV-1a hash of this string.
//
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash
+21 -16
View File
@@ -7,9 +7,9 @@ import (
"sort"
"strings"
"github.com/tinygo-org/tinygo/loader"
"github.com/aykevl/go-llvm"
"github.com/aykevl/tinygo/loader"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// This file provides a wrapper around go/ssa values and adds extra
@@ -18,26 +18,31 @@ import (
// View on all functions, types, and globals in a program, with analysis
// results.
type Program struct {
Program *ssa.Program
LoaderProgram *loader.Program
mainPkg *ssa.Package
Functions []*Function
functionMap map[*ssa.Function]*Function
Globals []*Global
globalMap map[*ssa.Global]*Global
comments map[string]*ast.CommentGroup
NamedTypes []*NamedType
Program *ssa.Program
LoaderProgram *loader.Program
mainPkg *ssa.Package
Functions []*Function
functionMap map[*ssa.Function]*Function
Globals []*Global
globalMap map[*ssa.Global]*Global
comments map[string]*ast.CommentGroup
NamedTypes []*NamedType
needsScheduler bool
goCalls []*ssa.Go
}
// Function or method.
type Function struct {
*ssa.Function
LLVMFn llvm.Value
linkName string // go:linkname, go:export, go:interrupt
exported bool // go:export
nobounds bool // go:nobounds
flag bool // used by dead code elimination
interrupt bool // go:interrupt
linkName string // go:linkname, go:export, go:interrupt
exported bool // go:export
nobounds bool // go:nobounds
blocking bool // calculated by AnalyseBlockingRecursive
flag bool // used by dead code elimination
interrupt bool // go:interrupt
parents []*Function // calculated by AnalyseCallgraph
children []*Function // calculated by AnalyseCallgraph
}
// Global variable, possibly constant.
+122
View File
@@ -56,6 +56,110 @@ func signature(sig *types.Signature) string {
return s
}
// Fill in parents of all functions.
//
// All packages need to be added before this pass can run, or it will produce
// incorrect results.
func (p *Program) AnalyseCallgraph() {
for _, f := range p.Functions {
// Clear, if AnalyseCallgraph has been called before.
f.children = nil
f.parents = nil
for _, block := range f.Blocks {
for _, instr := range block.Instrs {
switch instr := instr.(type) {
case *ssa.Call:
if instr.Common().IsInvoke() {
continue
}
switch call := instr.Call.Value.(type) {
case *ssa.Builtin:
// ignore
case *ssa.Function:
if isCGoInternal(call.Name()) {
continue
}
child := p.GetFunction(call)
if child.CName() != "" {
continue // assume non-blocking
}
if child.RelString(nil) == "time.Sleep" {
f.blocking = true
}
f.children = append(f.children, child)
}
}
}
}
}
for _, f := range p.Functions {
for _, child := range f.children {
child.parents = append(child.parents, f)
}
}
}
// Analyse which functions are recursively blocking.
//
// Depends on AnalyseCallgraph.
func (p *Program) AnalyseBlockingRecursive() {
worklist := make([]*Function, 0)
// Fill worklist with directly blocking functions.
for _, f := range p.Functions {
if f.blocking {
worklist = append(worklist, f)
}
}
// Keep reducing this worklist by marking a function as recursively blocking
// from the worklist and pushing all its parents that are non-blocking.
// This is somewhat similar to a worklist in a mark-sweep garbage collector.
// The work items are then grey objects.
for len(worklist) != 0 {
// Pick the topmost.
f := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
for _, parent := range f.parents {
if !parent.blocking {
parent.blocking = true
worklist = append(worklist, parent)
}
}
}
}
// Check whether we need a scheduler. A scheduler is only necessary when there
// are go calls that start blocking functions (if they're not blocking, the go
// function can be turned into a regular function call).
//
// Depends on AnalyseBlockingRecursive.
func (p *Program) AnalyseGoCalls() {
p.goCalls = nil
for _, f := range p.Functions {
for _, block := range f.Blocks {
for _, instr := range block.Instrs {
switch instr := instr.(type) {
case *ssa.Go:
p.goCalls = append(p.goCalls, instr)
}
}
}
}
for _, instr := range p.goCalls {
switch instr := instr.Call.Value.(type) {
case *ssa.Builtin:
case *ssa.Function:
if p.functionMap[instr].blocking {
p.needsScheduler = true
}
default:
panic("unknown go call function type")
}
}
}
// Simple pass that removes dead code. This pass makes later analysis passes
// more useful.
func (p *Program) SimpleDCE() {
@@ -135,3 +239,21 @@ func (p *Program) SimpleDCE() {
}
p.Functions = livefunctions
}
// Whether this function needs a scheduler.
//
// Depends on AnalyseGoCalls.
func (p *Program) NeedsScheduler() bool {
return p.needsScheduler
}
// Whether this function blocks. Builtins are also accepted for convenience.
// They will always be non-blocking.
//
// Depends on AnalyseBlockingRecursive.
func (p *Program) IsBlocking(f *Function) bool {
if !p.needsScheduler {
return false
}
return f.blocking
}
-66
View File
@@ -1,66 +0,0 @@
// +build byollvm
package main
// This file provides a Link() function that uses the bundled lld if possible.
import (
"errors"
"os"
"os/exec"
"unsafe"
)
/*
#include <stdbool.h>
#include <stdlib.h>
bool tinygo_link_elf(int argc, char **argv);
bool tinygo_link_wasm(int argc, char **argv);
*/
import "C"
// Link invokes a linker with the given name and flags.
//
// This version uses the built-in linker when trying to use lld.
func Link(dir, linker string, flags ...string) error {
switch linker {
case "ld.lld", commands["ld.lld"]:
flags = append([]string{"tinygo:" + linker}, flags...)
var cflag *C.char
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
for i, flag := range flags {
cflag := C.CString(flag)
cflags[i] = cflag
defer C.free(unsafe.Pointer(cflag))
}
ok := C.tinygo_link_elf(C.int(len(flags)), (**C.char)(buf))
if !ok {
return errors.New("failed to link using built-in ld.lld")
}
return nil
case "wasm-ld", commands["wasm-ld"]:
flags = append([]string{"tinygo:" + linker}, flags...)
var cflag *C.char
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
defer C.free(buf)
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
for i, flag := range flags {
cflag := C.CString(flag)
cflags[i] = cflag
defer C.free(unsafe.Pointer(cflag))
}
ok := C.tinygo_link_wasm(C.int(len(flags)), (**C.char)(buf))
if !ok {
return errors.New("failed to link using built-in wasm-ld")
}
return nil
default:
// Fall back to external command.
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = dir
return cmd.Run()
}
}
-22
View File
@@ -1,22 +0,0 @@
// +build !byollvm
package main
// This file provides a Link() function that always runs an external command. It
// is provided for when tinygo is built without linking to liblld.
import (
"os"
"os/exec"
)
// Link invokes a linker with the given name and arguments.
//
// This version always runs the linker as an external command.
func Link(dir, linker string, flags ...string) error {
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = dir
return cmd.Run()
}
-19
View File
@@ -1,19 +0,0 @@
// +build byollvm
// This file provides C wrappers for liblld.
#include <lld/Common/Driver.h>
extern "C" {
bool tinygo_link_elf(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
return lld::elf::link(args, false);
}
bool tinygo_link_wasm(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
return lld::wasm::link(args, false);
}
} // external "C"
+5 -17
View File
@@ -1,10 +1,5 @@
package loader
import (
"go/token"
"strings"
)
// Errors contains a list of parser errors or a list of typechecker errors for
// the given package.
type Errors struct {
@@ -20,20 +15,13 @@ func (e Errors) Error() string {
// packages is a list from the root package to the leaf package that imports one
// of the packages in the list.
type ImportCycleError struct {
Packages []string
ImportPositions []token.Position
Packages []string
}
func (e *ImportCycleError) Error() string {
var msg strings.Builder
msg.WriteString("import cycle:\n\t")
msg.WriteString(strings.Join(e.Packages, "\n\t"))
msg.WriteString("\n at ")
for i, pos := range e.ImportPositions {
if i > 0 {
msg.WriteString(", ")
}
msg.WriteString(pos.String())
msg := "import cycle: " + e.Packages[0]
for _, path := range e.Packages[1:] {
msg += " → " + path
}
return msg.String()
return msg
}
+3 -1
View File
@@ -9,6 +9,8 @@ import (
)
/*
#cgo CFLAGS: -I/usr/lib/llvm-7/include
#cgo LDFLAGS: -L/usr/lib/llvm-7/lib -lclang
#include <clang-c/Index.h> // if this fails, install libclang-7-dev
#include <stdlib.h>
@@ -37,7 +39,7 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string) error {
// convert Go slice of strings to C array of strings.
cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0))))
defer C.free(cmdargsC)
cmdargs := (*[1 << 16]*C.char)(cmdargsC)
cmdargs := (*[1<<30 - 1]*C.char)(cmdargsC)
for i, cflag := range cflags {
s := C.CString(cflag)
cmdargs[i] = s
-9
View File
@@ -1,9 +0,0 @@
// +build !byollvm
package loader
/*
#cgo CFLAGS: -I/usr/lib/llvm-7/include
#cgo LDFLAGS: -L/usr/lib/llvm-7/lib -lclang
*/
import "C"
+2 -7
View File
@@ -166,9 +166,7 @@ func (p *Program) Parse() error {
err := pkg.importRecursively()
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
if pkg.ImportPath != err.Packages[0] {
err.Packages = append([]string{pkg.ImportPath}, err.Packages...)
}
err.Packages = append([]string{pkg.ImportPath}, err.Packages...)
}
return err
}
@@ -341,13 +339,10 @@ func (p *Package) importRecursively() error {
return err
}
if importedPkg.Importing {
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]}
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}}
}
err = importedPkg.importRecursively()
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
err.Packages = append([]string{p.ImportPath}, err.Packages...)
}
return err
}
p.Imports[to] = importedPkg
+22 -55
View File
@@ -15,28 +15,14 @@ import (
"strings"
"syscall"
"github.com/tinygo-org/tinygo/compiler"
"github.com/tinygo-org/tinygo/interp"
"github.com/tinygo-org/tinygo/loader"
"github.com/aykevl/tinygo/compiler"
"github.com/aykevl/tinygo/interp"
"github.com/aykevl/tinygo/loader"
)
var commands = map[string]string{
"ar": "ar",
"clang": "clang-7",
"ld.lld": "ld.lld-7",
"wasm-ld": "wasm-ld-7",
}
// commandError is an error type to wrap os/exec.Command errors. This provides
// some more information regarding what went wrong while running a command.
type commandError struct {
Msg string
File string
Err error
}
func (e *commandError) Error() string {
return e.Msg + " " + e.File + ": " + e.Err.Error()
"ar": "ar",
"clang": "clang-7",
}
type BuildConfig struct {
@@ -188,12 +174,13 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
// Load builtins library from the cache, possibly compiling it on the
// fly.
var librt string
var cachePath string
if spec.RTLib == "compiler-rt" {
librt, err = loadBuiltins(spec.Triple)
librt, err := loadBuiltins(spec.Triple)
if err != nil {
return err
}
cachePath, _ = filepath.Split(librt)
}
// Prepare link command.
@@ -201,7 +188,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
tmppath := executable // final file
ldflags := append(spec.LDFlags, "-o", executable, objfile)
if spec.RTLib == "compiler-rt" {
ldflags = append(ldflags, librt)
ldflags = append(ldflags, "-L", cachePath, "-lrt-"+spec.Triple)
}
// Compile extra files.
@@ -213,7 +200,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
cmd.Dir = sourceDir()
err := cmd.Run()
if err != nil {
return &commandError{"failed to build", path, err}
return err
}
ldflags = append(ldflags, outpath)
}
@@ -229,16 +216,20 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
cmd.Dir = sourceDir()
err := cmd.Run()
if err != nil {
return &commandError{"failed to build", path, err}
return err
}
ldflags = append(ldflags, outpath)
}
}
// Link the object files together.
err = Link(sourceDir(), spec.Linker, ldflags...)
cmd := exec.Command(spec.Linker, ldflags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
err = cmd.Run()
if err != nil {
return &commandError{"failed to link", executable, err}
return err
}
if config.printSizes == "short" || config.printSizes == "full" {
@@ -272,7 +263,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return &commandError{"failed to extract " + format + " from", executable, err}
return err
}
}
return action(tmppath)
@@ -334,11 +325,7 @@ func Flash(pkgName, target, port string, config *BuildConfig) error {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
err := cmd.Run()
if err != nil {
return &commandError{"failed to flash", tmppath, err}
}
return nil
return cmd.Run()
})
}
@@ -405,11 +392,7 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return &commandError{"failed to run gdb with", tmppath, err}
}
return nil
return cmd.Run()
})
}
@@ -432,9 +415,8 @@ func Run(pkgName, target string, config *BuildConfig) error {
// Workaround for QEMU which always exits with an error.
return nil
}
return &commandError{"failed to run compiled binary", tmppath, err}
}
return nil
return err
} else {
// Run in an emulator.
args := append(spec.Emulator[1:], tmppath)
@@ -447,9 +429,8 @@ func Run(pkgName, target string, config *BuildConfig) error {
// Workaround for QEMU which always exits with an error.
return nil
}
return &commandError{"failed to run emulator with", tmppath, err}
}
return nil
return err
}
})
}
@@ -551,20 +532,6 @@ func main() {
}
err := Build(flag.Arg(0), *outpath, target, config)
handleCompilerError(err)
case "build-builtins":
// Note: this command is only meant to be used while making a release!
if *outpath == "" {
fmt.Fprintln(os.Stderr, "No output filename supplied (-o).")
usage()
os.Exit(1)
}
if *target == "" {
fmt.Fprintln(os.Stderr, "No target (-target).")
}
err := compileBuiltins(*target, func(path string) error {
return moveFile(path, *outpath)
})
handleCompilerError(err)
case "flash", "gdb":
if *outpath != "" {
fmt.Fprintln(os.Stderr, "Output cannot be specified with the flash command.")
+9 -16
View File
@@ -7,34 +7,27 @@ import (
"time"
)
// change these to test a different UART or pins if available
var (
uart = machine.UART0
tx uint8 = machine.UART_TX_PIN
rx uint8 = machine.UART_RX_PIN
)
func main() {
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
uart.Write([]byte("Echo console enabled. Type something then press enter:\r\n"))
machine.UART0.Configure(machine.UARTConfig{})
machine.UART0.Write([]byte("Echo console enabled. Type something then press enter:\r\n"))
input := make([]byte, 64)
i := 0
for {
if uart.Buffered() > 0 {
data, _ := uart.ReadByte()
if machine.UART0.Buffered() > 0 {
data, _ := machine.UART0.ReadByte()
switch data {
case 13:
// return key
uart.Write([]byte("\r\n"))
uart.Write([]byte("You typed: "))
uart.Write(input[:i])
uart.Write([]byte("\r\n"))
machine.UART0.Write([]byte("\r\n"))
machine.UART0.Write([]byte("You typed: "))
machine.UART0.Write(input[:i])
machine.UART0.Write([]byte("\r\n"))
i = 0
default:
// just echo the character
uart.WriteByte(data)
machine.UART0.WriteByte(data)
input[i] = data
i++
}
-6
View File
@@ -16,9 +16,3 @@ const (
ADC4 = 4 // Used by TWI for SDA
ADC5 = 5 // Used by TWI for SCL
)
// UART pins
const (
UART_TX_PIN = 1
UART_RX_PIN = 0
)
-47
View File
@@ -1,47 +0,0 @@
// +build sam,atsamd21g18a,itsybitsy_m0
package machine
// GPIO Pins
const (
D0 = 11 // UART0 RX
D1 = 10 // UART0 TX
D2 = 14
D3 = 9 // PWM available
D4 = 8 // PWM available
D5 = 15 // PWM available
D6 = 20 // PWM available
D7 = 21 // PWM available
D8 = 6 // PWM available
D9 = 7 // PWM available
D10 = 18 // can be used for PWM or UART1 TX
D11 = 16 // can be used for PWM or UART1 RX
D12 = 19 // PWM available
D13 = 17 // PWM available
)
// Analog pins
const (
A0 = 2 // ADC/AIN[0]
// A1 = 8 // ADC/AIN[2] TODO: requires PORTB
// A2 = 9 // ADC/AIN[3] TODO: requires PORTB
A3 = 4 // ADC/AIN[4]
A4 = 5 // ADC/AIN[5]
//A5 = 2 // ADC/AIN[10] TODO: requires PORTB
)
const (
LED = D13
)
// UART0 pins
const (
UART_TX_PIN = D1
UART_RX_PIN = D0
)
// I2C pins
const (
SDA_PIN = 22 // SDA: SERCOM3/PAD[0]
SCL_PIN = 23 // SCL: SERCOM3/PAD[1]
)
-49
View File
@@ -1,49 +0,0 @@
package machine
const bufferSize = 128
//go:volatile
type volatileByte byte
// RingBuffer is ring buffer implementation inspired by post at
// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php
//
// It has some limitations currently due to how "volatile" variables that are
// members of a struct are not compiled correctly by TinyGo.
// See https://github.com/tinygo-org/tinygo/issues/151 for details.
type RingBuffer struct {
rxbuffer [bufferSize]volatileByte
head volatileByte
tail volatileByte
}
// NewRingBuffer returns a new ring buffer.
func NewRingBuffer() *RingBuffer {
return &RingBuffer{}
}
// Used returns how many bytes in buffer have been used.
func (rb *RingBuffer) Used() uint8 {
return uint8(rb.head - rb.tail)
}
// Put stores a byte in the buffer. If the buffer is already
// full, the method will return false.
func (rb *RingBuffer) Put(val byte) bool {
if rb.Used() != bufferSize {
rb.head++
rb.rxbuffer[rb.head%bufferSize] = volatileByte(val)
return true
}
return false
}
// Get returns a byte from the buffer. If the buffer is empty,
// the method will return a false as the second value.
func (rb *RingBuffer) Get() (byte, bool) {
if rb.Used() != 0 {
rb.tail++
return byte(rb.rxbuffer[rb.tail%bufferSize]), true
}
return 0, false
}
+1 -1
View File
@@ -1,4 +1,4 @@
// +build avr nrf sam stm32f103xx
// +build avr nrf stm32f103xx
package machine
+1 -6
View File
@@ -211,11 +211,6 @@ func (i2c I2C) readByte() byte {
return byte(*avr.TWDR)
}
// UART on the AVR.
type UART struct {
Buffer *RingBuffer
}
// Configure the UART on the AVR. Defaults to 9600 baud on Arduino.
func (uart UART) Configure(config UARTConfig) {
if config.BaudRate == 0 {
@@ -253,6 +248,6 @@ func handleUSART_RX() {
// Ensure no error.
if (*avr.UCSR0A & (avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0)) == 0 {
// Put data from UDR register into buffer.
UART0.Receive(byte(data))
bufferPut(byte(data))
}
}
-908
View File
@@ -1,908 +0,0 @@
// +build sam,atsamd21g18a
// Peripheral abstraction layer for the atsamd21.
//
// Datasheet:
// http://ww1.microchip.com/downloads/en/DeviceDoc/SAMD21-Family-DataSheet-DS40001882D.pdf
//
package machine
import (
"device/arm"
"device/sam"
"errors"
)
const CPU_FREQUENCY = 48000000
type GPIOMode uint8
const (
GPIO_ANALOG = 1
GPIO_SERCOM = 2
GPIO_SERCOM_ALT = 3
GPIO_TIMER = 4
GPIO_TIMER_ALT = 5
GPIO_COM = 6
GPIO_AC_CLK = 7
GPIO_DIGITAL = 8
GPIO_INPUT = 9
GPIO_INPUT_PULLUP = 10
GPIO_OUTPUT = 11
GPIO_PWM = GPIO_TIMER
GPIO_PWM_ALT = GPIO_TIMER_ALT
)
// Configure this pin with the given configuration.
func (p GPIO) Configure(config GPIOConfig) {
switch config.Mode {
case GPIO_OUTPUT:
sam.PORT.DIRSET0 = (1 << p.Pin)
// output is also set to input enable so pin can read back its own value
p.setPinCfg(sam.PORT_PINCFG0_INEN)
case GPIO_INPUT:
sam.PORT.DIRCLR0 = (1 << p.Pin)
p.setPinCfg(sam.PORT_PINCFG0_INEN)
case GPIO_SERCOM:
if p.Pin&1 > 0 {
// odd pin, so save the even pins
val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
p.setPMux(val | (GPIO_SERCOM << sam.PORT_PMUX0_PMUXO_Pos))
} else {
// even pin, so save the odd pins
val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
p.setPMux(val | (GPIO_SERCOM << sam.PORT_PMUX0_PMUXE_Pos))
}
// enable port config
p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR | sam.PORT_PINCFG0_INEN)
}
}
// Get returns the current value of a GPIO pin.
func (p GPIO) Get() bool {
return (sam.PORT.IN0>>p.Pin)&1 > 0
}
// Set the pin to high or low.
// Warning: only use this on an output pin!
func (p GPIO) Set(high bool) {
if high {
sam.PORT.OUTSET0 = (1 << p.Pin)
} else {
sam.PORT.OUTCLR0 = (1 << p.Pin)
}
}
// getPMux returns the value for the correct PMUX register for this pin.
func (p GPIO) getPMux() sam.RegValue8 {
return getPMux(p.Pin)
}
// setPMux sets the value for the correct PMUX register for this pin.
func (p GPIO) setPMux(val sam.RegValue8) {
setPMux(p.Pin, val)
}
// getPinCfg returns the value for the correct PINCFG register for this pin.
func (p GPIO) getPinCfg() sam.RegValue8 {
return getPinCfg(p.Pin)
}
// setPinCfg sets the value for the correct PINCFG register for this pin.
func (p GPIO) setPinCfg(val sam.RegValue8) {
setPinCfg(p.Pin, val)
}
// UART on the SAMD21.
type UART struct {
Buffer *RingBuffer
Bus *sam.SERCOM_USART_Type
}
var (
// The first hardware serial port on the SAMD21. Uses the SERCOM0 interface.
UART0 = UART{Bus: sam.SERCOM0_USART, Buffer: NewRingBuffer()}
// The second hardware serial port on the SAMD21. Uses the SERCOM1 interface.
UART1 = UART{Bus: sam.SERCOM1_USART, Buffer: NewRingBuffer()}
)
const (
sampleRate16X = 16
lsbFirst = 1
sercomRXPad0 = 0
sercomRXPad1 = 1
sercomRXPad2 = 2
sercomRXPad3 = 3
sercomTXPad0 = 0 // Only for UART
sercomTXPad2 = 1 // Only for UART
sercomTXPad023 = 2 // Only for UART with TX on PAD0, RTS on PAD2 and CTS on PAD3
)
// Configure the UART.
func (uart UART) Configure(config UARTConfig) {
// Default baud rate to 115200.
if config.BaudRate == 0 {
config.BaudRate = 115200
}
// determine pins
if config.TX == 0 {
// use default pins
config.TX = UART_TX_PIN
config.RX = UART_RX_PIN
}
// determine pads
var txpad, rxpad int
switch config.TX {
case UART_TX_PIN:
txpad = sercomTXPad2
case D10:
txpad = sercomTXPad2
case D11:
txpad = sercomTXPad0
default:
panic("Invalid TX pin for UART")
}
switch config.RX {
case UART_RX_PIN:
rxpad = sercomRXPad3
case D10:
rxpad = sercomRXPad2
case D11:
rxpad = sercomRXPad0
case D12:
rxpad = sercomRXPad3
case D13:
rxpad = sercomRXPad1
default:
panic("Invalid RX pin for UART")
}
// configure pins
GPIO{config.TX}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
GPIO{config.RX}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
// reset SERCOM0
uart.Bus.CTRLA |= sam.SERCOM_USART_CTRLA_SWRST
for (uart.Bus.CTRLA&sam.SERCOM_USART_CTRLA_SWRST) > 0 ||
(uart.Bus.SYNCBUSY&sam.SERCOM_USART_SYNCBUSY_SWRST) > 0 {
}
// set UART mode/sample rate
// SERCOM_USART_CTRLA_MODE(mode) |
// SERCOM_USART_CTRLA_SAMPR(sampleRate);
uart.Bus.CTRLA = (sam.SERCOM_USART_CTRLA_MODE_USART_INT_CLK << sam.SERCOM_USART_CTRLA_MODE_Pos) |
(1 << sam.SERCOM_USART_CTRLA_SAMPR_Pos) // sample rate of 16x
// Set baud rate
uart.SetBaudRate(config.BaudRate)
// setup UART frame
// SERCOM_USART_CTRLA_FORM( (parityMode == SERCOM_NO_PARITY ? 0 : 1) ) |
// dataOrder << SERCOM_USART_CTRLA_DORD_Pos;
uart.Bus.CTRLA |= (0 << sam.SERCOM_USART_CTRLA_FORM_Pos) | // no parity
(lsbFirst << sam.SERCOM_USART_CTRLA_DORD_Pos) // data order
// set UART stop bits/parity
// SERCOM_USART_CTRLB_CHSIZE(charSize) |
// nbStopBits << SERCOM_USART_CTRLB_SBMODE_Pos |
// (parityMode == SERCOM_NO_PARITY ? 0 : parityMode) << SERCOM_USART_CTRLB_PMODE_Pos; //If no parity use default value
uart.Bus.CTRLB |= (0 << sam.SERCOM_USART_CTRLB_CHSIZE_Pos) | // 8 bits is 0
(0 << sam.SERCOM_USART_CTRLB_SBMODE_Pos) | // 1 stop bit is zero
(0 << sam.SERCOM_USART_CTRLB_PMODE_Pos) // no parity
// set UART pads. This is not same as pins...
// SERCOM_USART_CTRLA_TXPO(txPad) |
// SERCOM_USART_CTRLA_RXPO(rxPad);
uart.Bus.CTRLA |= sam.RegValue((txpad << sam.SERCOM_USART_CTRLA_TXPO_Pos) |
(rxpad << sam.SERCOM_USART_CTRLA_RXPO_Pos))
// Enable Transceiver and Receiver
//sercom->USART.CTRLB.reg |= SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_RXEN ;
uart.Bus.CTRLB |= (sam.SERCOM_USART_CTRLB_TXEN | sam.SERCOM_USART_CTRLB_RXEN)
// Enable USART1 port.
// sercom->USART.CTRLA.bit.ENABLE = 0x1u;
uart.Bus.CTRLA |= sam.SERCOM_USART_CTRLA_ENABLE
for (uart.Bus.SYNCBUSY & sam.SERCOM_USART_SYNCBUSY_ENABLE) > 0 {
}
// setup interrupt on receive
uart.Bus.INTENSET = sam.SERCOM_USART_INTENSET_RXC
// Enable RX IRQ.
if config.TX == UART_TX_PIN {
// UART0
arm.EnableIRQ(sam.IRQ_SERCOM0)
} else {
// UART1
arm.EnableIRQ(sam.IRQ_SERCOM1)
}
}
// SetBaudRate sets the communication speed for the UART.
func (uart UART) SetBaudRate(br uint32) {
// Asynchronous fractional mode (Table 24-2 in datasheet)
// BAUD = fref / (sampleRateValue * fbaud)
// (multiply by 8, to calculate fractional piece)
// uint32_t baudTimes8 = (SystemCoreClock * 8) / (16 * baudrate);
baud := (CPU_FREQUENCY * 8) / (sampleRate16X * br)
// sercom->USART.BAUD.FRAC.FP = (baudTimes8 % 8);
// sercom->USART.BAUD.FRAC.BAUD = (baudTimes8 / 8);
uart.Bus.BAUD = sam.RegValue16(((baud % 8) << sam.SERCOM_USART_BAUD_FRAC_MODE_FP_Pos) |
((baud / 8) << sam.SERCOM_USART_BAUD_FRAC_MODE_BAUD_Pos))
}
// WriteByte writes a byte of data to the UART.
func (uart UART) WriteByte(c byte) error {
// wait until ready to receive
for (uart.Bus.INTFLAG & sam.SERCOM_USART_INTFLAG_DRE) == 0 {
}
uart.Bus.DATA = sam.RegValue16(c)
return nil
}
//go:export SERCOM0_IRQHandler
func handleUART0() {
// should reset IRQ
UART0.Receive(byte((UART0.Bus.DATA & 0xFF)))
UART0.Bus.INTFLAG |= sam.SERCOM_USART_INTFLAG_RXC
}
//go:export SERCOM1_IRQHandler
func handleUART1() {
// should reset IRQ
UART1.Receive(byte((UART1.Bus.DATA & 0xFF)))
UART1.Bus.INTFLAG |= sam.SERCOM_USART_INTFLAG_RXC
}
// I2C on the SAMD21.
type I2C struct {
Bus *sam.SERCOM_I2CM_Type
}
// Since the I2C interfaces on the SAMD21 use the SERCOMx peripherals,
// you can have multiple ones. we currently only implement one.
var (
I2C0 = I2C{Bus: sam.SERCOM3_I2CM}
)
// I2CConfig is used to store config info for I2C.
type I2CConfig struct {
Frequency uint32
SCL uint8
SDA uint8
}
const (
// Default rise time in nanoseconds, based on 4.7K ohm pull up resistors
riseTimeNanoseconds = 125
// wire bus states
wireUnknownState = 0
wireIdleState = 1
wireOwnerState = 2
wireBusyState = 3
// wire commands
wireCmdNoAction = 0
wireCmdRepeatStart = 1
wireCmdRead = 2
wireCmdStop = 3
)
const i2cTimeout = 1000
// Configure is intended to setup the I2C interface.
func (i2c I2C) Configure(config I2CConfig) {
// Default I2C bus speed is 100 kHz.
if config.Frequency == 0 {
config.Frequency = TWI_FREQ_100KHZ
}
// reset SERCOM3
i2c.Bus.CTRLA |= sam.SERCOM_I2CM_CTRLA_SWRST
for (i2c.Bus.CTRLA&sam.SERCOM_I2CM_CTRLA_SWRST) > 0 ||
(i2c.Bus.SYNCBUSY&sam.SERCOM_I2CM_SYNCBUSY_SWRST) > 0 {
}
// Set i2c master mode
//SERCOM_I2CM_CTRLA_MODE( I2C_MASTER_OPERATION )
i2c.Bus.CTRLA = (sam.SERCOM_I2CM_CTRLA_MODE_I2C_MASTER << sam.SERCOM_I2CM_CTRLA_MODE_Pos) // |
i2c.SetBaudRate(config.Frequency)
// Enable I2CM port.
// sercom->USART.CTRLA.bit.ENABLE = 0x1u;
i2c.Bus.CTRLA |= sam.SERCOM_I2CM_CTRLA_ENABLE
for (i2c.Bus.SYNCBUSY & sam.SERCOM_I2CM_SYNCBUSY_ENABLE) > 0 {
}
// set bus idle mode
i2c.Bus.STATUS |= (wireIdleState << sam.SERCOM_I2CM_STATUS_BUSSTATE_Pos)
for (i2c.Bus.SYNCBUSY & sam.SERCOM_I2CM_SYNCBUSY_SYSOP) > 0 {
}
// enable pins
GPIO{SDA_PIN}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
GPIO{SCL_PIN}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
}
// SetBaudRate sets the communication speed for the I2C.
func (i2c I2C) SetBaudRate(br uint32) {
// Synchronous arithmetic baudrate, via Arduino SAMD implementation:
// SystemCoreClock / ( 2 * baudrate) - 5 - (((SystemCoreClock / 1000000) * WIRE_RISE_TIME_NANOSECONDS) / (2 * 1000));
baud := CPU_FREQUENCY/(2*br) - 5 - (((CPU_FREQUENCY / 1000000) * riseTimeNanoseconds) / (2 * 1000))
i2c.Bus.BAUD = sam.RegValue(baud)
}
// Tx does a single I2C transaction at the specified address.
// It clocks out the given address, writes the bytes in w, reads back len(r)
// bytes and stores them in r, and generates a stop condition on the bus.
func (i2c I2C) Tx(addr uint16, w, r []byte) error {
var err error
if len(w) != 0 {
// send start/address for write
i2c.sendAddress(addr, true)
// wait until transmission complete
timeout := i2cTimeout
for (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_MB) == 0 {
timeout--
if timeout == 0 {
return errors.New("I2C timeout on ready to write data")
}
}
// ACK received (0: ACK, 1: NACK)
if (i2c.Bus.STATUS & sam.SERCOM_I2CM_STATUS_RXNACK) > 0 {
return errors.New("I2C write error: expected ACK not NACK")
}
// write data
for _, b := range w {
err = i2c.WriteByte(b)
if err != nil {
return err
}
}
err = i2c.signalStop()
if err != nil {
return err
}
}
if len(r) != 0 {
// send start/address for read
i2c.sendAddress(addr, false)
// wait transmission complete
for (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_SB) == 0 {
// If the slave NACKS the address, the MB bit will be set.
// In that case, send a stop condition and return error.
if (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_MB) > 0 {
i2c.Bus.CTRLB |= (wireCmdStop << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Stop condition
return errors.New("I2C read error: expected ACK not NACK")
}
}
// ACK received (0: ACK, 1: NACK)
if (i2c.Bus.STATUS & sam.SERCOM_I2CM_STATUS_RXNACK) > 0 {
return errors.New("I2C read error: expected ACK not NACK")
}
// read first byte
r[0] = i2c.readByte()
for i := 1; i < len(r); i++ {
// Send an ACK
i2c.Bus.CTRLB &^= sam.SERCOM_I2CM_CTRLB_ACKACT
i2c.signalRead()
// Read data and send the ACK
r[i] = i2c.readByte()
}
// Send NACK to end transmission
i2c.Bus.CTRLB |= sam.SERCOM_I2CM_CTRLB_ACKACT
err = i2c.signalStop()
if err != nil {
return err
}
}
return nil
}
// WriteByte writes a single byte to the I2C bus.
func (i2c I2C) WriteByte(data byte) error {
// Send data byte
i2c.Bus.DATA = sam.RegValue8(data)
// wait until transmission successful
timeout := i2cTimeout
for (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_MB) == 0 {
// check for bus error
if (sam.SERCOM3_I2CM.STATUS & sam.SERCOM_I2CM_STATUS_BUSERR) > 0 {
return errors.New("I2C bus error")
}
timeout--
if timeout == 0 {
return errors.New("I2C timeout on write data")
}
}
if (i2c.Bus.STATUS & sam.SERCOM_I2CM_STATUS_RXNACK) > 0 {
return errors.New("I2C write error: expected ACK not NACK")
}
return nil
}
// sendAddress sends the address and start signal
func (i2c I2C) sendAddress(address uint16, write bool) error {
data := (address << 1)
if !write {
data |= 1 // set read flag
}
// wait until bus ready
timeout := i2cTimeout
for (i2c.Bus.STATUS&(wireIdleState<<sam.SERCOM_I2CM_STATUS_BUSSTATE_Pos)) == 0 &&
(i2c.Bus.STATUS&(wireOwnerState<<sam.SERCOM_I2CM_STATUS_BUSSTATE_Pos)) == 0 {
timeout--
if timeout == 0 {
return errors.New("I2C timeout on bus ready")
}
}
i2c.Bus.ADDR = sam.RegValue(data)
return nil
}
func (i2c I2C) signalStop() error {
i2c.Bus.CTRLB |= (wireCmdStop << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Stop command
timeout := i2cTimeout
for (i2c.Bus.SYNCBUSY & sam.SERCOM_I2CM_SYNCBUSY_SYSOP) > 0 {
timeout--
if timeout == 0 {
return errors.New("I2C timeout on signal stop")
}
}
return nil
}
func (i2c I2C) signalRead() error {
i2c.Bus.CTRLB |= (wireCmdRead << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Read command
timeout := i2cTimeout
for (i2c.Bus.SYNCBUSY & sam.SERCOM_I2CM_SYNCBUSY_SYSOP) > 0 {
timeout--
if timeout == 0 {
return errors.New("I2C timeout on signal read")
}
}
return nil
}
func (i2c I2C) readByte() byte {
for (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_SB) == 0 {
}
return byte(i2c.Bus.DATA)
}
// PWM
const period = 0xFFFF
// InitPWM initializes the PWM interface.
func InitPWM() {
// turn on timer clocks used for PWM
sam.PM.APBCMASK |= sam.PM_APBCMASK_TCC0_ | sam.PM_APBCMASK_TCC1_ | sam.PM_APBCMASK_TCC2_
// Use GCLK0 for TCC0/TCC1
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_TCC0_TCC1 << sam.GCLK_CLKCTRL_ID_Pos) |
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
sam.GCLK_CLKCTRL_CLKEN)
for (sam.GCLK.STATUS & sam.GCLK_STATUS_SYNCBUSY) > 0 {
}
// Use GCLK0 for TCC2/TC3
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_TCC2_TC3 << sam.GCLK_CLKCTRL_ID_Pos) |
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
sam.GCLK_CLKCTRL_CLKEN)
for (sam.GCLK.STATUS & sam.GCLK_STATUS_SYNCBUSY) > 0 {
}
}
// Configure configures a PWM pin for output.
func (pwm PWM) Configure() {
// figure out which TCCX timer for this pin
timer := pwm.getTimer()
// disable timer
timer.CTRLA &^= sam.TCC_CTRLA_ENABLE
// Wait for synchronization
for (timer.SYNCBUSY & sam.TCC_SYNCBUSY_ENABLE) > 0 {
}
// Use "Normal PWM" (single-slope PWM)
timer.WAVE |= sam.TCC_WAVE_WAVEGEN_NPWM
// Wait for synchronization
for (timer.SYNCBUSY & sam.TCC_SYNCBUSY_WAVE) > 0 {
}
// Set the period (the number to count to (TOP) before resetting timer)
//TCC0->PER.reg = period;
timer.PER = period
// Wait for synchronization
for (timer.SYNCBUSY & sam.TCC_SYNCBUSY_PER) > 0 {
}
// Set pin as output
sam.PORT.DIRSET0 = (1 << pwm.Pin)
// Set pin to low
sam.PORT.OUTCLR0 = (1 << pwm.Pin)
// Enable the port multiplexer for pin
pwm.setPinCfg(sam.PORT_PINCFG0_PMUXEN)
// Connect TCCX timer to pin.
// we normally use the F channel aka ALT
pwmConfig := GPIO_PWM_ALT
// in the case of PA6 or PA7 we have to use E channel
if pwm.Pin == 6 || pwm.Pin == 7 {
pwmConfig = GPIO_PWM
}
if pwm.Pin&1 > 0 {
// odd pin, so save the even pins
val := pwm.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
pwm.setPMux(val | sam.RegValue8(pwmConfig<<sam.PORT_PMUX0_PMUXO_Pos))
} else {
// even pin, so save the odd pins
val := pwm.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
pwm.setPMux(val | sam.RegValue8(pwmConfig<<sam.PORT_PMUX0_PMUXE_Pos))
}
}
// Set turns on the duty cycle for a PWM pin using the provided value.
func (pwm PWM) Set(value uint16) {
// figure out which TCCX timer for this pin
timer := pwm.getTimer()
// disable output
timer.CTRLA &^= sam.TCC_CTRLA_ENABLE
// Wait for synchronization
for (timer.SYNCBUSY & sam.TCC_SYNCBUSY_ENABLE) > 0 {
}
// Set PWM signal to output duty cycle
pwm.setChannel(sam.RegValue(value))
// Wait for synchronization on all channels
for (timer.SYNCBUSY & (sam.TCC_SYNCBUSY_CC0 |
sam.TCC_SYNCBUSY_CC1 |
sam.TCC_SYNCBUSY_CC2 |
sam.TCC_SYNCBUSY_CC3)) > 0 {
}
// enable
timer.CTRLA |= sam.TCC_CTRLA_ENABLE
// Wait for synchronization
for (timer.SYNCBUSY & sam.TCC_SYNCBUSY_ENABLE) > 0 {
}
}
// getPMux returns the value for the correct PMUX register for this pin.
func (pwm PWM) getPMux() sam.RegValue8 {
return getPMux(pwm.Pin)
}
// setPMux sets the value for the correct PMUX register for this pin.
func (pwm PWM) setPMux(val sam.RegValue8) {
setPMux(pwm.Pin, val)
}
// getPinCfg returns the value for the correct PINCFG register for this pin.
func (pwm PWM) getPinCfg() sam.RegValue8 {
return getPinCfg(pwm.Pin)
}
// setPinCfg sets the value for the correct PINCFG register for this pin.
func (pwm PWM) setPinCfg(val sam.RegValue8) {
setPinCfg(pwm.Pin, val)
}
// getPMux returns the value for the correct PMUX register for this pin.
func getPMux(p uint8) sam.RegValue8 {
pin := p >> 1
switch pin {
case 0:
return sam.PORT.PMUX0_0
case 1:
return sam.PORT.PMUX0_1
case 2:
return sam.PORT.PMUX0_2
case 3:
return sam.PORT.PMUX0_3
case 4:
return sam.PORT.PMUX0_4
case 5:
return sam.PORT.PMUX0_5
case 6:
return sam.PORT.PMUX0_6
case 7:
return sam.PORT.PMUX0_7
case 8:
return sam.PORT.PMUX0_8
case 9:
return sam.PORT.PMUX0_9
case 10:
return sam.PORT.PMUX0_10
case 11:
return sam.PORT.PMUX0_11
case 12:
return sam.PORT.PMUX0_12
case 13:
return sam.PORT.PMUX0_13
case 14:
return sam.PORT.PMUX0_14
case 15:
return sam.PORT.PMUX0_15
default:
return 0
}
}
// setPMux sets the value for the correct PMUX register for this pin.
func setPMux(p uint8, val sam.RegValue8) {
pin := p >> 1
switch pin {
case 0:
sam.PORT.PMUX0_0 = val
case 1:
sam.PORT.PMUX0_1 = val
case 2:
sam.PORT.PMUX0_2 = val
case 3:
sam.PORT.PMUX0_3 = val
case 4:
sam.PORT.PMUX0_4 = val
case 5:
sam.PORT.PMUX0_5 = val
case 6:
sam.PORT.PMUX0_6 = val
case 7:
sam.PORT.PMUX0_7 = val
case 8:
sam.PORT.PMUX0_8 = val
case 9:
sam.PORT.PMUX0_9 = val
case 10:
sam.PORT.PMUX0_10 = val
case 11:
sam.PORT.PMUX0_11 = val
case 12:
sam.PORT.PMUX0_12 = val
case 13:
sam.PORT.PMUX0_13 = val
case 14:
sam.PORT.PMUX0_14 = val
case 15:
sam.PORT.PMUX0_15 = val
}
}
// getPinCfg returns the value for the correct PINCFG register for this pin.
func getPinCfg(p uint8) sam.RegValue8 {
switch p {
case 0:
return sam.PORT.PINCFG0_0
case 1:
return sam.PORT.PINCFG0_1
case 2:
return sam.PORT.PINCFG0_2
case 3:
return sam.PORT.PINCFG0_3
case 4:
return sam.PORT.PINCFG0_4
case 5:
return sam.PORT.PINCFG0_5
case 6:
return sam.PORT.PINCFG0_6
case 7:
return sam.PORT.PINCFG0_7
case 8:
return sam.PORT.PINCFG0_8
case 9:
return sam.PORT.PINCFG0_9
case 10:
return sam.PORT.PINCFG0_10
case 11:
return sam.PORT.PINCFG0_11
case 12:
return sam.PORT.PINCFG0_12
case 13:
return sam.PORT.PINCFG0_13
case 14:
return sam.PORT.PINCFG0_14
case 15:
return sam.PORT.PINCFG0_15
case 16:
return sam.PORT.PINCFG0_16
case 17:
return sam.PORT.PINCFG0_17
case 18:
return sam.PORT.PINCFG0_18
case 19:
return sam.PORT.PINCFG0_19
case 20:
return sam.PORT.PINCFG0_20
case 21:
return sam.PORT.PINCFG0_21
case 22:
return sam.PORT.PINCFG0_22
case 23:
return sam.PORT.PINCFG0_23
case 24:
return sam.PORT.PINCFG0_24
case 25:
return sam.PORT.PINCFG0_25
case 26:
return sam.PORT.PINCFG0_26
case 27:
return sam.PORT.PINCFG0_27
case 28:
return sam.PORT.PINCFG0_28
case 29:
return sam.PORT.PINCFG0_29
case 30:
return sam.PORT.PINCFG0_30
case 31:
return sam.PORT.PINCFG0_31
default:
return 0
}
}
// setPinCfg sets the value for the correct PINCFG register for this pin.
func setPinCfg(p uint8, val sam.RegValue8) {
switch p {
case 0:
sam.PORT.PINCFG0_0 = val
case 1:
sam.PORT.PINCFG0_1 = val
case 2:
sam.PORT.PINCFG0_2 = val
case 3:
sam.PORT.PINCFG0_3 = val
case 4:
sam.PORT.PINCFG0_4 = val
case 5:
sam.PORT.PINCFG0_5 = val
case 6:
sam.PORT.PINCFG0_6 = val
case 7:
sam.PORT.PINCFG0_7 = val
case 8:
sam.PORT.PINCFG0_8 = val
case 9:
sam.PORT.PINCFG0_9 = val
case 10:
sam.PORT.PINCFG0_10 = val
case 11:
sam.PORT.PINCFG0_11 = val
case 12:
sam.PORT.PINCFG0_12 = val
case 13:
sam.PORT.PINCFG0_13 = val
case 14:
sam.PORT.PINCFG0_14 = val
case 15:
sam.PORT.PINCFG0_15 = val
case 16:
sam.PORT.PINCFG0_16 = val
case 17:
sam.PORT.PINCFG0_17 = val
case 18:
sam.PORT.PINCFG0_18 = val
case 19:
sam.PORT.PINCFG0_19 = val
case 20:
sam.PORT.PINCFG0_20 = val
case 21:
sam.PORT.PINCFG0_21 = val
case 22:
sam.PORT.PINCFG0_22 = val
case 23:
sam.PORT.PINCFG0_23 = val
case 24:
sam.PORT.PINCFG0_24 = val
case 25:
sam.PORT.PINCFG0_25 = val
case 26:
sam.PORT.PINCFG0_26 = val
case 27:
sam.PORT.PINCFG0_27 = val
case 28:
sam.PORT.PINCFG0_28 = val
case 29:
sam.PORT.PINCFG0_29 = val
case 30:
sam.PORT.PINCFG0_30 = val
case 31:
sam.PORT.PINCFG0_31 = val
}
}
// getTimer returns the timer to be used for PWM on this pin
func (pwm PWM) getTimer() *sam.TCC_Type {
switch pwm.Pin {
case 6:
return sam.TCC1
case 7:
return sam.TCC1
case 8:
return sam.TCC1
case 9:
return sam.TCC1
case 14:
return sam.TCC0
case 15:
return sam.TCC0
case 16:
return sam.TCC0
case 17:
return sam.TCC0
case 18:
return sam.TCC0
case 19:
return sam.TCC0
case 20:
return sam.TCC0
case 21:
return sam.TCC0
default:
return nil // not supported on this pin
}
}
// setChannel sets the value for the correct channel for PWM on this pin
func (pwm PWM) setChannel(val sam.RegValue) {
switch pwm.Pin {
case 6:
pwm.getTimer().CC0 = val
case 7:
pwm.getTimer().CC1 = val
case 8:
pwm.getTimer().CC0 = val
case 9:
pwm.getTimer().CC1 = val
case 14:
pwm.getTimer().CC0 = val
case 15:
pwm.getTimer().CC1 = val
case 16:
pwm.getTimer().CC2 = val
case 17:
pwm.getTimer().CC3 = val
case 18:
pwm.getTimer().CC2 = val
case 19:
pwm.getTimer().CC3 = val
case 20:
pwm.getTimer().CC2 = val
case 21:
pwm.getTimer().CC3 = val
default:
return // not supported on this pin
}
}
-6
View File
@@ -25,12 +25,6 @@ func (p GPIO) Get() bool {
return (val > 0)
}
// UART on the AVR is a dummy implementation. UART has not been implemented for ATtiny
// devices.
type UART struct {
Buffer *RingBuffer
}
// Configure is a dummy implementation. UART has not been implemented for ATtiny
// devices.
func (uart UART) Configure(config UARTConfig) {
+1 -1
View File
@@ -92,5 +92,5 @@ var I2C0 = I2C{}
// UART
var (
// UART0 is the hardware serial port on the AVR.
UART0 = UART{Buffer: NewRingBuffer()}
UART0 = &UART{}
)
+1 -1
View File
@@ -1,4 +1,4 @@
// +build !avr,!nrf,!sam,!stm32
// +build !avr,!nrf,!stm32
package machine
+2 -7
View File
@@ -54,15 +54,10 @@ func (p GPIO) Get() bool {
return (port.IN>>pin)&1 != 0
}
// UART on the NRF.
type UART struct {
Buffer *RingBuffer
}
// UART
var (
// UART0 is the hardware serial port on the NRF.
UART0 = UART{Buffer: NewRingBuffer()}
UART0 = &UART{}
)
// Configure the UART.
@@ -113,7 +108,7 @@ func (uart UART) WriteByte(c byte) error {
func (uart UART) handleInterrupt() {
if nrf.UART0.EVENTS_RXDRDY != 0 {
uart.Receive(byte(nrf.UART0.RXD))
bufferPut(byte(nrf.UART0.RXD))
nrf.UART0.EVENTS_RXDRDY = 0x0
}
}
+4 -8
View File
@@ -100,15 +100,11 @@ func (p GPIO) Set(high bool) {
}
// UART
type UART struct {
Buffer *RingBuffer
}
var (
// USART1 is the first hardware serial port on the STM32.
// Both UART0 and UART1 refer to USART1.
UART0 = UART{Buffer: NewRingBuffer()}
UART1 = &UART0
// Both UART0 and UART1 refers to USART1.
UART0 = &UART{}
UART1 = UART0
)
// Configure the UART.
@@ -164,7 +160,7 @@ func (uart UART) WriteByte(c byte) error {
//go:export USART1_IRQHandler
func handleUART1() {
UART1.Receive(byte((stm32.USART1.DR & 0xFF)))
bufferPut(byte((stm32.USART1.DR & 0xFF)))
}
// SPI on the STM32.
+32 -22
View File
@@ -1,4 +1,4 @@
// +build avr nrf sam stm32
// +build avr nrf stm32
package machine
@@ -10,19 +10,8 @@ type UARTConfig struct {
RX uint8
}
// To implement the UART interface for a board, you must declare a concrete type as follows:
//
// type UART struct {
// Buffer *RingBuffer
// }
//
// You can also add additional members to this struct depending on your implementation,
// but the *RingBuffer is required.
// When you are declaring your UARTs for your board, make sure that you also declare the
// RingBuffer using the NewRingBuffer() function when you declare your UART:
//
// UART{Buffer: NewRingBuffer()}
//
type UART struct {
}
// Read from the RX buffer.
func (uart UART) Read(data []byte) (n int, err error) {
@@ -58,20 +47,41 @@ func (uart UART) Write(data []byte) (n int, err error) {
// If there is no data in the buffer, returns an error.
func (uart UART) ReadByte() (byte, error) {
// check if RX buffer is empty
buf, ok := uart.Buffer.Get()
if !ok {
if uart.Buffered() == 0 {
return 0, errors.New("Buffer empty")
}
return buf, nil
return bufferGet(), nil
}
// Buffered returns the number of bytes currently stored in the RX buffer.
func (uart UART) Buffered() int {
return int(uart.Buffer.Used())
return int(bufferUsed())
}
// Receive handles adding data to the UART's data buffer.
// Usually called by the IRQ handler for a machine.
func (uart UART) Receive(data byte) {
uart.Buffer.Put(data)
const bufferSize = 64
// Minimal ring buffer implementation inspired by post at
// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php
//go:volatile
type volatileByte byte
var rxbuffer [bufferSize]volatileByte
var head volatileByte
var tail volatileByte
func bufferUsed() uint8 { return uint8(head - tail) }
func bufferPut(val byte) {
if bufferUsed() != bufferSize {
head++
rxbuffer[head%bufferSize] = volatileByte(val)
}
}
func bufferGet() byte {
if bufferUsed() != 0 {
tail++
return byte(rxbuffer[tail%bufferSize])
}
return 0
}
+1 -146
View File
@@ -2,152 +2,7 @@ package runtime
// This file implements the 'chan' type and send/receive/select operations.
// A channel can be in one of the following states:
// empty:
// No goroutine is waiting on a send or receive operation. The 'blocked'
// member is nil.
// recv:
// A goroutine tries to receive from the channel. This goroutine is stored
// in the 'blocked' member.
// send:
// The reverse of send. A goroutine tries to send to the channel. This
// goroutine is stored in the 'blocked' member.
// closed:
// The channel is closed. Sends will panic, receives will get a zero value
// plus optionally the indication that the channel is zero (with the
// commao-ok value in the coroutine).
//
// A send/recv transmission is completed by copying from the data element of the
// sending coroutine to the data element of the receiving coroutine, and setting
// the 'comma-ok' value to true.
// A receive operation on a closed channel is completed by zeroing the data
// element of the receiving coroutine and setting the 'comma-ok' value to false.
import (
"unsafe"
)
// dummy
type channel struct {
state uint8
blocked *coroutine
}
const (
chanStateEmpty = iota
chanStateRecv
chanStateSend
chanStateClosed
)
func chanSendStub(caller *coroutine, ch *channel, _ unsafe.Pointer, size uintptr)
func chanRecvStub(caller *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size uintptr)
// chanSend sends a single value over the channel. If this operation can
// complete immediately (there is a goroutine waiting for a value), it sends the
// value and re-activates both goroutines. If not, it sets itself as waiting on
// a value.
//
// The unsafe.Pointer value is used during lowering. During IR generation, it
// points to the to-be-received value. During coroutine lowering, this value is
// replaced with a read from the coroutine promise.
func chanSend(sender *coroutine, ch *channel, _ unsafe.Pointer, size uintptr) {
if ch == nil {
// A nil channel blocks forever. Do not scheduler this goroutine again.
return
}
switch ch.state {
case chanStateEmpty:
ch.state = chanStateSend
ch.blocked = sender
case chanStateRecv:
receiver := ch.blocked
receiverPromise := receiver.promise()
senderPromise := sender.promise()
memcpy(unsafe.Pointer(&receiverPromise.data), unsafe.Pointer(&senderPromise.data), size)
receiverPromise.commaOk = true
ch.blocked = receiverPromise.next
receiverPromise.next = nil
activateTask(receiver)
activateTask(sender)
if ch.blocked == nil {
ch.state = chanStateEmpty
}
case chanStateClosed:
runtimePanic("send on closed channel")
case chanStateSend:
sender.promise().next = ch.blocked
ch.blocked = sender
}
}
// chanRecv receives a single value over a channel. If there is an available
// sender, it receives the value immediately and re-activates both coroutines.
// If not, it sets itself as available for receiving. If the channel is closed,
// it immediately activates itself with a zero value as the result.
//
// The two unnamed values exist to help during lowering. The unsafe.Pointer
// points to the value, and the *bool points to the comma-ok value. Both are
// replaced by reads from the coroutine promise.
func chanRecv(receiver *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size uintptr) {
if ch == nil {
// A nil channel blocks forever. Do not scheduler this goroutine again.
return
}
switch ch.state {
case chanStateSend:
sender := ch.blocked
receiverPromise := receiver.promise()
senderPromise := sender.promise()
memcpy(unsafe.Pointer(&receiverPromise.data), unsafe.Pointer(&senderPromise.data), size)
receiverPromise.commaOk = true
ch.blocked = senderPromise.next
senderPromise.next = nil
activateTask(receiver)
activateTask(sender)
if ch.blocked == nil {
ch.state = chanStateEmpty
}
case chanStateEmpty:
ch.state = chanStateRecv
ch.blocked = receiver
case chanStateClosed:
receiverPromise := receiver.promise()
memzero(unsafe.Pointer(&receiverPromise.data), size)
receiverPromise.commaOk = false
activateTask(receiver)
case chanStateRecv:
receiver.promise().next = ch.blocked
ch.blocked = receiver
}
}
// chanClose closes the given channel. If this channel has a receiver or is
// empty, it closes the channel. Else, it panics.
func chanClose(ch *channel, size uintptr) {
if ch == nil {
// Not allowed by the language spec.
runtimePanic("close of nil channel")
}
switch ch.state {
case chanStateClosed:
// Not allowed by the language spec.
runtimePanic("close of closed channel")
case chanStateSend:
// This panic should ideally on the sending side, not in this goroutine.
// But when a goroutine tries to send while the channel is being closed,
// that is clearly invalid: the send should have been completed already
// before the close.
runtimePanic("close channel during send")
case chanStateRecv:
// The receiver must be re-activated with a zero value.
receiverPromise := ch.blocked.promise()
memzero(unsafe.Pointer(&receiverPromise.data), size)
receiverPromise.commaOk = false
activateTask(ch.blocked)
ch.state = chanStateClosed
ch.blocked = nil
case chanStateEmpty:
// Easy case. No available sender or receiver.
ch.state = chanStateClosed
}
}
+366
View File
@@ -0,0 +1,366 @@
package runtime
// This memory manager is a textbook mark/sweep implementation, heavily inspired
// by the MicroPython garbage collector.
//
// The memory manager internally uses blocks of 4 pointers big (see
// bytesPerBlock). Every allocation first rounds up to this size to align every
// block. It will first try to find a chain of blocks that is big enough to
// satisfy the allocation. If it finds one, it marks the first one as the "head"
// and the following ones (if any) as the "tail" (see below). If it cannot find
// any free space, it will perform a garbage collection cycle and try again. If
// it still cannot find any free space, it gives up.
//
// Every block has some metadata, which is stored at the beginning of the heap.
// The four states are "free", "head", "tail", and "mark". During normal
// operation, there are no marked blocks. Every allocated object starts with a
// "head" and is followed by "tail" blocks. The reason for this distinction is
// that this way, the start and end of every object can be found easily.
//
// Metadata is stored in a special area at the beginning of the heap, in the
// area heapStart..poolStart. The actual blocks are stored in
// poolStart..heapEnd.
//
// More information:
// https://github.com/micropython/micropython/wiki/Memory-Manager
// "The Garbage Collection Handbook" by Richard Jones, Antony Hosking, Eliot
// Moss.
import (
"unsafe"
)
// Set gcDebug to true to print debug information.
const (
gcDebug = false // print debug info
gcAsserts = gcDebug // perform sanity checks
)
// Some globals + constants for the entire GC.
const (
wordsPerBlock = 4 // number of pointers in an allocated block
bytesPerBlock = wordsPerBlock * unsafe.Sizeof(heapStart)
stateBits = 2 // how many bits a block state takes (see blockState type)
blocksPerStateByte = 8 / stateBits
)
var (
poolStart uintptr // the first heap pointer
nextAlloc gcBlock // the next block that should be tried by the allocator
endBlock gcBlock // the block just past the end of the available space
)
// zeroSizedAlloc is just a sentinel that gets returned when allocating 0 bytes.
var zeroSizedAlloc uint8
// Provide some abstraction over heap blocks.
// blockState stores the four states in which a block can be. It is two bits in
// size.
type blockState uint8
const (
blockStateFree blockState = 0 // 00
blockStateHead blockState = 1 // 01
blockStateTail blockState = 2 // 10
blockStateMark blockState = 3 // 11
blockStateMask blockState = 3 // 11
)
// String returns a human-readable version of the block state, for debugging.
func (s blockState) String() string {
switch s {
case blockStateFree:
return "free"
case blockStateHead:
return "head"
case blockStateTail:
return "tail"
case blockStateMark:
return "mark"
default:
// must never happen
return "!err"
}
}
// The block number in the pool.
type gcBlock uintptr
// blockFromAddr returns a block given an address somewhere in the heap (which
// might not be heap-aligned).
func blockFromAddr(addr uintptr) gcBlock {
return gcBlock((addr - poolStart) / bytesPerBlock)
}
// Return a pointer to the start of the allocated object.
func (b gcBlock) pointer() unsafe.Pointer {
return unsafe.Pointer(b.address())
}
// Return the address of the start of the allocated object.
func (b gcBlock) address() uintptr {
return poolStart + uintptr(b)*bytesPerBlock
}
// findHead returns the head (first block) of an object, assuming the block
// points to an allocated object. It returns the same block if this block
// already points to the head.
func (b gcBlock) findHead() gcBlock {
for b.state() == blockStateTail {
b--
}
return b
}
// findNext returns the first block just past the end of the tail. This may or
// may not be the head of an object.
func (b gcBlock) findNext() gcBlock {
if b.state() == blockStateHead {
b++
}
for b.state() == blockStateTail {
b++
}
return b
}
// State returns the current block state.
func (b gcBlock) state() blockState {
stateBytePtr := (*uint8)(unsafe.Pointer(heapStart + uintptr(b/blocksPerStateByte)))
return blockState(*stateBytePtr>>((b%blocksPerStateByte)*2)) % 4
}
// setState sets the current block to the given state, which must contain more
// bits than the current state. Allowed transitions: from free to any state and
// from head to mark.
func (b gcBlock) setState(newState blockState) {
stateBytePtr := (*uint8)(unsafe.Pointer(heapStart + uintptr(b/blocksPerStateByte)))
*stateBytePtr |= uint8(newState << ((b % blocksPerStateByte) * 2))
if gcAsserts && b.state() != newState {
runtimePanic("gc: setState() was not successful")
}
}
// markFree sets the block state to free, no matter what state it was in before.
func (b gcBlock) markFree() {
stateBytePtr := (*uint8)(unsafe.Pointer(heapStart + uintptr(b/blocksPerStateByte)))
*stateBytePtr &^= uint8(blockStateMask << ((b % blocksPerStateByte) * 2))
if gcAsserts && b.state() != blockStateFree {
runtimePanic("gc: markFree() was not successful")
}
}
// unmark changes the state of the block from mark to head. It must be marked
// before calling this function.
func (b gcBlock) unmark() {
if gcAsserts && b.state() != blockStateMark {
runtimePanic("gc: unmark() on a block that is not marked")
}
clearMask := blockStateMask ^ blockStateHead // the bits to clear from the state
stateBytePtr := (*uint8)(unsafe.Pointer(heapStart + uintptr(b/blocksPerStateByte)))
*stateBytePtr &^= uint8(clearMask << ((b % blocksPerStateByte) * 2))
if gcAsserts && b.state() != blockStateHead {
runtimePanic("gc: unmark() was not successful")
}
}
// Initialize the memory allocator.
// No memory may be allocated before this is called. That means the runtime and
// any packages the runtime depends upon may not allocate memory during package
// initialization.
func initHeap() {
totalSize := heapEnd - heapStart
// Allocate some memory to keep 2 bits of information about every block.
metadataSize := totalSize / (blocksPerStateByte * bytesPerBlock)
// Align the pool.
poolStart = (heapStart + metadataSize + (bytesPerBlock - 1)) &^ (bytesPerBlock - 1)
poolEnd := heapEnd &^ (bytesPerBlock - 1)
numBlocks := (poolEnd - poolStart) / bytesPerBlock
endBlock = gcBlock(numBlocks)
if gcDebug {
println("heapStart: ", heapStart)
println("heapEnd: ", heapEnd)
println("total size: ", totalSize)
println("metadata size: ", metadataSize)
println("poolStart: ", poolStart)
println("# of blocks: ", numBlocks)
println("# of block states:", metadataSize*blocksPerStateByte)
}
if gcAsserts && metadataSize*blocksPerStateByte < numBlocks {
// sanity check
runtimePanic("gc: metadata array is too small")
}
// Set all block states to 'free'.
memzero(unsafe.Pointer(heapStart), metadataSize)
}
// heapAlloc tries to find some free space on the heap, possibly doing a garbage
// collection cycle if needed. If no space is free, it panics.
func heapAlloc(size uintptr) unsafe.Pointer {
if size == 0 {
return unsafe.Pointer(&zeroSizedAlloc)
}
neededBlocks := (size + (bytesPerBlock - 1)) / bytesPerBlock
// Continue looping until a run of free blocks has been found that fits the
// requested size.
index := nextAlloc
numFreeBlocks := uintptr(0)
heapScanCount := uint8(0)
for {
if index == nextAlloc {
if heapScanCount == 0 {
heapScanCount = 1
} else if heapScanCount == 1 {
// The entire heap has been searched for free memory, but none
// could be found. Run a garbage collection cycle to reclaim
// free memory and try again.
heapScanCount = 2
GC()
} else {
// Even after garbage collection, no free memory could be found.
runtimePanic("out of memory")
}
}
// Wrap around the end of the heap.
if index == endBlock {
index = 0
// Reset numFreeBlocks as allocations cannot wrap.
numFreeBlocks = 0
}
// Is the block we're looking at free?
if index.state() != blockStateFree {
// This block is in use. Try again from this point.
numFreeBlocks = 0
index++
continue
}
numFreeBlocks++
index++
// Are we finished?
if numFreeBlocks == neededBlocks {
// Found a big enough range of free blocks!
nextAlloc = index
thisAlloc := index - gcBlock(neededBlocks)
if gcDebug {
println("found memory:", thisAlloc.pointer(), int(size))
}
// Set the following blocks as being allocated.
thisAlloc.setState(blockStateHead)
for i := thisAlloc + 1; i != nextAlloc; i++ {
i.setState(blockStateTail)
}
// Return a pointer to this allocation.
pointer := thisAlloc.pointer()
memzero(pointer, size)
return pointer
}
}
}
func heapFree(ptr unsafe.Pointer) {
// TODO: free blocks on request, when the compiler knows they're unused.
}
// markRoots reads all pointers from start to end (exclusive) and if they look
// like a heap pointer and are unmarked, marks them and scans that object as
// well (recursively). The start and end parameters must be valid pointers and
// must be aligned.
func markRoots(start, end uintptr) {
if gcDebug {
println("mark from", start, "to", end, int(end-start))
}
for addr := start; addr != end; addr += unsafe.Sizeof(addr) {
root := *(*uintptr)(unsafe.Pointer(addr))
if looksLikePointer(root) {
markRoot(root, addr)
}
}
}
func markRoot(root, parent uintptr) {
block := blockFromAddr(root)
head := block.findHead()
if head.state() != blockStateMark {
if gcDebug {
println("found unmarked pointer", root, "at address", parent)
}
head.setState(blockStateMark)
next := block.findNext()
// TODO: avoid recursion as much as possible
markRoots(head.address(), next.address())
}
}
// Sweep goes through all memory and frees unmarked memory.
func sweep() {
freeCurrentObject := false
for block := gcBlock(0); block < endBlock; block++ {
switch block.state() {
case blockStateHead:
// Unmarked head. Free it, including all tail blocks following it.
block.markFree()
freeCurrentObject = true
case blockStateTail:
if freeCurrentObject {
// This is a tail object following an unmarked head.
// Free it now.
block.markFree()
}
case blockStateMark:
// This is a marked object. The next tail blocks must not be freed,
// but the mark bit must be removed so the next GC cycle will
// collect this object if it is unreferenced then.
block.unmark()
freeCurrentObject = false
}
}
}
// looksLikePointer returns whether this could be a pointer. Currently, it
// simply returns whether it lies anywhere in the heap. Go allows interior
// pointers so we can't check alignment or anything like that.
func looksLikePointer(ptr uintptr) bool {
return ptr >= poolStart && ptr < heapEnd
}
// dumpHeap can be used for debugging purposes. It dumps the state of each heap
// block to standard output.
func dumpHeap() {
println("heap:")
for block := gcBlock(0); block < endBlock; block++ {
switch block.state() {
case blockStateHead:
print("*")
case blockStateTail:
print("-")
case blockStateMark:
print("#")
default: // free
print("·")
}
if block%64 == 63 || block+1 == endBlock {
println()
}
}
}
func KeepAlive(x interface{}) {
// Unimplemented. Only required with SetFinalizer().
}
func SetFinalizer(obj interface{}, finalizer interface{}) {
// Unimplemented.
}
-8
View File
@@ -37,11 +37,3 @@ func free(ptr unsafe.Pointer) {
func GC() {
// No-op.
}
func KeepAlive(x interface{}) {
// Unimplemented. Only required with SetFinalizer().
}
func SetFinalizer(obj interface{}, finalizer interface{}) {
// Unimplemented.
}
+3 -348
View File
@@ -2,277 +2,20 @@
package runtime
// This memory manager is a textbook mark/sweep implementation, heavily inspired
// by the MicroPython garbage collector.
//
// The memory manager internally uses blocks of 4 pointers big (see
// bytesPerBlock). Every allocation first rounds up to this size to align every
// block. It will first try to find a chain of blocks that is big enough to
// satisfy the allocation. If it finds one, it marks the first one as the "head"
// and the following ones (if any) as the "tail" (see below). If it cannot find
// any free space, it will perform a garbage collection cycle and try again. If
// it still cannot find any free space, it gives up.
//
// Every block has some metadata, which is stored at the beginning of the heap.
// The four states are "free", "head", "tail", and "mark". During normal
// operation, there are no marked blocks. Every allocated object starts with a
// "head" and is followed by "tail" blocks. The reason for this distinction is
// that this way, the start and end of every object can be found easily.
//
// Metadata is stored in a special area at the beginning of the heap, in the
// area heapStart..poolStart. The actual blocks are stored in
// poolStart..heapEnd.
//
// More information:
// https://github.com/micropython/micropython/wiki/Memory-Manager
// "The Garbage Collection Handbook" by Richard Jones, Antony Hosking, Eliot
// Moss.
import (
"unsafe"
)
// Set gcDebug to true to print debug information.
const (
gcDebug = false // print debug info
gcAsserts = gcDebug // perform sanity checks
)
// Some globals + constants for the entire GC.
const (
wordsPerBlock = 4 // number of pointers in an allocated block
bytesPerBlock = wordsPerBlock * unsafe.Sizeof(heapStart)
stateBits = 2 // how many bits a block state takes (see blockState type)
blocksPerStateByte = 8 / stateBits
)
var (
poolStart uintptr // the first heap pointer
nextAlloc gcBlock // the next block that should be tried by the allocator
endBlock gcBlock // the block just past the end of the available space
)
// zeroSizedAlloc is just a sentinel that gets returned when allocating 0 bytes.
var zeroSizedAlloc uint8
// Provide some abstraction over heap blocks.
// blockState stores the four states in which a block can be. It is two bits in
// size.
type blockState uint8
const (
blockStateFree blockState = 0 // 00
blockStateHead blockState = 1 // 01
blockStateTail blockState = 2 // 10
blockStateMark blockState = 3 // 11
blockStateMask blockState = 3 // 11
)
// String returns a human-readable version of the block state, for debugging.
func (s blockState) String() string {
switch s {
case blockStateFree:
return "free"
case blockStateHead:
return "head"
case blockStateTail:
return "tail"
case blockStateMark:
return "mark"
default:
// must never happen
return "!err"
}
}
// The block number in the pool.
type gcBlock uintptr
// blockFromAddr returns a block given an address somewhere in the heap (which
// might not be heap-aligned).
func blockFromAddr(addr uintptr) gcBlock {
return gcBlock((addr - poolStart) / bytesPerBlock)
}
// Return a pointer to the start of the allocated object.
func (b gcBlock) pointer() unsafe.Pointer {
return unsafe.Pointer(b.address())
}
// Return the address of the start of the allocated object.
func (b gcBlock) address() uintptr {
return poolStart + uintptr(b)*bytesPerBlock
}
// findHead returns the head (first block) of an object, assuming the block
// points to an allocated object. It returns the same block if this block
// already points to the head.
func (b gcBlock) findHead() gcBlock {
for b.state() == blockStateTail {
b--
}
return b
}
// findNext returns the first block just past the end of the tail. This may or
// may not be the head of an object.
func (b gcBlock) findNext() gcBlock {
if b.state() == blockStateHead {
b++
}
for b.state() == blockStateTail {
b++
}
return b
}
// State returns the current block state.
func (b gcBlock) state() blockState {
stateBytePtr := (*uint8)(unsafe.Pointer(heapStart + uintptr(b/blocksPerStateByte)))
return blockState(*stateBytePtr>>((b%blocksPerStateByte)*2)) % 4
}
// setState sets the current block to the given state, which must contain more
// bits than the current state. Allowed transitions: from free to any state and
// from head to mark.
func (b gcBlock) setState(newState blockState) {
stateBytePtr := (*uint8)(unsafe.Pointer(heapStart + uintptr(b/blocksPerStateByte)))
*stateBytePtr |= uint8(newState << ((b % blocksPerStateByte) * 2))
if gcAsserts && b.state() != newState {
runtimePanic("gc: setState() was not successful")
}
}
// markFree sets the block state to free, no matter what state it was in before.
func (b gcBlock) markFree() {
stateBytePtr := (*uint8)(unsafe.Pointer(heapStart + uintptr(b/blocksPerStateByte)))
*stateBytePtr &^= uint8(blockStateMask << ((b % blocksPerStateByte) * 2))
if gcAsserts && b.state() != blockStateFree {
runtimePanic("gc: markFree() was not successful")
}
}
// unmark changes the state of the block from mark to head. It must be marked
// before calling this function.
func (b gcBlock) unmark() {
if gcAsserts && b.state() != blockStateMark {
runtimePanic("gc: unmark() on a block that is not marked")
}
clearMask := blockStateMask ^ blockStateHead // the bits to clear from the state
stateBytePtr := (*uint8)(unsafe.Pointer(heapStart + uintptr(b/blocksPerStateByte)))
*stateBytePtr &^= uint8(clearMask << ((b % blocksPerStateByte) * 2))
if gcAsserts && b.state() != blockStateHead {
runtimePanic("gc: unmark() was not successful")
}
}
// Initialize the memory allocator.
// No memory may be allocated before this is called. That means the runtime and
// any packages the runtime depends upon may not allocate memory during package
// initialization.
func init() {
totalSize := heapEnd - heapStart
// Allocate some memory to keep 2 bits of information about every block.
metadataSize := totalSize / (blocksPerStateByte * bytesPerBlock)
// Align the pool.
poolStart = (heapStart + metadataSize + (bytesPerBlock - 1)) &^ (bytesPerBlock - 1)
poolEnd := heapEnd &^ (bytesPerBlock - 1)
numBlocks := (poolEnd - poolStart) / bytesPerBlock
endBlock = gcBlock(numBlocks)
if gcDebug {
println("heapStart: ", heapStart)
println("heapEnd: ", heapEnd)
println("total size: ", totalSize)
println("metadata size: ", metadataSize)
println("poolStart: ", poolStart)
println("# of blocks: ", numBlocks)
println("# of block states:", metadataSize*blocksPerStateByte)
}
if gcAsserts && metadataSize*blocksPerStateByte < numBlocks {
// sanity check
runtimePanic("gc: metadata array is too small")
}
// Set all block states to 'free'.
memzero(unsafe.Pointer(heapStart), metadataSize)
initHeap()
}
// alloc tries to find some free space on the heap, possibly doing a garbage
// collection cycle if needed. If no space is free, it panics.
func alloc(size uintptr) unsafe.Pointer {
if size == 0 {
return unsafe.Pointer(&zeroSizedAlloc)
}
neededBlocks := (size + (bytesPerBlock - 1)) / bytesPerBlock
// Continue looping until a run of free blocks has been found that fits the
// requested size.
index := nextAlloc
numFreeBlocks := uintptr(0)
heapScanCount := uint8(0)
for {
if index == nextAlloc {
if heapScanCount == 0 {
heapScanCount = 1
} else if heapScanCount == 1 {
// The entire heap has been searched for free memory, but none
// could be found. Run a garbage collection cycle to reclaim
// free memory and try again.
heapScanCount = 2
GC()
} else {
// Even after garbage collection, no free memory could be found.
runtimePanic("out of memory")
}
}
// Wrap around the end of the heap.
if index == endBlock {
index = 0
// Reset numFreeBlocks as allocations cannot wrap.
numFreeBlocks = 0
}
// Is the block we're looking at free?
if index.state() != blockStateFree {
// This block is in use. Try again from this point.
numFreeBlocks = 0
index++
continue
}
numFreeBlocks++
index++
// Are we finished?
if numFreeBlocks == neededBlocks {
// Found a big enough range of free blocks!
nextAlloc = index
thisAlloc := index - gcBlock(neededBlocks)
if gcDebug {
println("found memory:", thisAlloc.pointer(), int(size))
}
// Set the following blocks as being allocated.
thisAlloc.setState(blockStateHead)
for i := thisAlloc + 1; i != nextAlloc; i++ {
i.setState(blockStateTail)
}
// Return a pointer to this allocation.
pointer := thisAlloc.pointer()
memzero(pointer, size)
return pointer
}
}
return heapAlloc(size)
}
func free(ptr unsafe.Pointer) {
// TODO: free blocks on request, when the compiler knows they're unused.
heapFree(ptr)
}
// GC performs a garbage collection cycle.
@@ -294,91 +37,3 @@ func GC() {
dumpHeap()
}
}
// markRoots reads all pointers from start to end (exclusive) and if they look
// like a heap pointer and are unmarked, marks them and scans that object as
// well (recursively). The start and end parameters must be valid pointers and
// must be aligned.
func markRoots(start, end uintptr) {
if gcDebug {
println("mark from", start, "to", end, int(end-start))
}
for addr := start; addr != end; addr += unsafe.Sizeof(addr) {
root := *(*uintptr)(unsafe.Pointer(addr))
if looksLikePointer(root) {
block := blockFromAddr(root)
head := block.findHead()
if head.state() != blockStateMark {
if gcDebug {
println("found unmarked pointer", root, "at address", addr)
}
head.setState(blockStateMark)
next := block.findNext()
// TODO: avoid recursion as much as possible
markRoots(head.address(), next.address())
}
}
}
}
// Sweep goes through all memory and frees unmarked memory.
func sweep() {
freeCurrentObject := false
for block := gcBlock(0); block < endBlock; block++ {
switch block.state() {
case blockStateHead:
// Unmarked head. Free it, including all tail blocks following it.
block.markFree()
freeCurrentObject = true
case blockStateTail:
if freeCurrentObject {
// This is a tail object following an unmarked head.
// Free it now.
block.markFree()
}
case blockStateMark:
// This is a marked object. The next tail blocks must not be freed,
// but the mark bit must be removed so the next GC cycle will
// collect this object if it is unreferenced then.
block.unmark()
freeCurrentObject = false
}
}
}
// looksLikePointer returns whether this could be a pointer. Currently, it
// simply returns whether it lies anywhere in the heap. Go allows interior
// pointers so we can't check alignment or anything like that.
func looksLikePointer(ptr uintptr) bool {
return ptr >= poolStart && ptr < heapEnd
}
// dumpHeap can be used for debugging purposes. It dumps the state of each heap
// block to standard output.
func dumpHeap() {
println("heap:")
for block := gcBlock(0); block < endBlock; block++ {
switch block.state() {
case blockStateHead:
print("*")
case blockStateTail:
print("-")
case blockStateMark:
print("#")
default: // free
print("·")
}
if block%64 == 63 || block+1 == endBlock {
println()
}
}
}
func KeepAlive(x interface{}) {
// Unimplemented. Only required with SetFinalizer().
}
func SetFinalizer(obj interface{}, finalizer interface{}) {
// Unimplemented.
}
-8
View File
@@ -19,11 +19,3 @@ func free(ptr unsafe.Pointer) {
func GC() {
// Unimplemented.
}
func KeepAlive(x interface{}) {
// Unimplemented. Only required with SetFinalizer().
}
func SetFinalizer(obj interface{}, finalizer interface{}) {
// Unimplemented.
}
+106
View File
@@ -0,0 +1,106 @@
// +build gc.shadowstack
package runtime
import (
"unsafe"
)
//go:extern __data_start
var dataStartSymbol unsafe.Pointer
//go:extern _edata
var dataEndSymbol unsafe.Pointer
//go:extern __bss_start
var bssStartSymbol unsafe.Pointer
//go:extern _ebss
var bssEndSymbol unsafe.Pointer
var (
dataStart = uintptr(unsafe.Pointer(&dataStartSymbol))
dataEnd = uintptr(unsafe.Pointer(&dataEndSymbol))
bssStart = uintptr(unsafe.Pointer(&bssStartSymbol))
bssEnd = uintptr(unsafe.Pointer(&bssEndSymbol))
)
// Constant value with metadata about a given function.
type frameMap struct {
numRoots int32
numMeta int32
meta [0]uintptr // unknown number of 'meta' pointers
}
// Stack-allocated struct with live pointers.
type stackEntry struct {
next *stackEntry
frame *frameMap
roots [0]uintptr // unknown number of root pointers
}
// Note: this symbol must be redefined to llvm_gc_root_chain by the linker.
// For example, you can use this linker option:
// --defsym=tinygo_gc_root_chain=llvm_gc_root_chain
// The reason is that otherwise the shadow stack GC pass in LLVM tries to set an
// initial value to llvm_gc_root_chain of a different type.
//go:extern tinygo_gc_root_chain
var gcRootChain *stackEntry
func init() {
initHeap()
}
func alloc(size uintptr) unsafe.Pointer {
GC()
return heapAlloc(size)
}
func free(ptr unsafe.Pointer) {
heapFree(ptr)
}
// GC performs a garbage collection cycle.
func GC() {
if gcDebug {
println("running collection cycle...")
}
// Mark phase: mark all reachable objects, recursively.
markRoots(globalsStart, globalsEnd)
markStack()
// Sweep phase: free all non-marked objects and unmark marked objects for
// the next collection cycle.
sweep()
// Show how much has been sweeped, for debugging.
if gcDebug {
dumpHeap()
}
}
// Mark all pointers from the stack using the LLVM shadow stack.
//go:nobounds
func markStack() {
chain := gcRootChain
// Traverse the linked-list of stack roots.
for chain != nil {
// Check each root.
for i := int32(0); i < chain.frame.numRoots; i++ {
root := chain.roots[i]
size := uintptr(1)
if i < chain.frame.numMeta {
// This root has no metadata associated with it.
// Therefore, it must be a single pointer.
size = chain.frame.meta[i]
}
for i := uintptr(0); i < size; i++ {
if looksLikePointer(root) {
markRoot(root, 0)
}
}
}
chain = chain.next
}
}
+6 -6
View File
@@ -4,14 +4,14 @@ import (
"unsafe"
)
const Compiler = "tinygo"
const Compiler = "tgo"
// The compiler will fill this with calls to the initialization function of each
// package.
func initAll()
// A function call to this function is replaced withone of the following,
// depending on whether the scheduler is necessary:
// The compiler will insert the call to main.main() here, depending on whether
// the scheduler is necessary.
//
// Without scheduler:
//
@@ -19,9 +19,9 @@ func initAll()
//
// With scheduler:
//
// main.main()
// scheduler()
func callMain()
// coroutine := main.main(nil)
// scheduler(coroutine)
func mainWrapper()
func GOMAXPROCS(n int) int {
// Note: setting GOMAXPROCS is ignored.
-337
View File
@@ -1,337 +0,0 @@
// +build sam,atsamd21g18a
package runtime
import (
"device/arm"
"device/sam"
"machine"
"unsafe"
)
type timeUnit int64
//go:export Reset_Handler
func main() {
preinit()
initAll()
callMain()
abort()
}
func init() {
initClocks()
initRTC()
initUARTClock()
initI2CClock()
// connect to UART
machine.UART0.Configure(machine.UARTConfig{})
}
func putchar(c byte) {
machine.UART0.WriteByte(c)
}
func initClocks() {
// Set 1 Flash Wait State for 48MHz, required for 3.3V operation according to SAMD21 Datasheet
sam.NVMCTRL.CTRLB |= (sam.NVMCTRL_CTRLB_RWS_HALF << sam.NVMCTRL_CTRLB_RWS_Pos)
// Turn on the digital interface clock
sam.PM.APBAMASK |= sam.PM_APBAMASK_GCLK_
// turn off RTC
sam.PM.APBAMASK &^= sam.PM_APBAMASK_RTC_
// Enable OSC32K clock (Internal 32.768Hz oscillator).
// This requires registers that are not included in the SVD file.
// This is from samd21g18a.h and nvmctrl.h:
//
// #define NVMCTRL_OTP4 0x00806020
//
// #define SYSCTRL_FUSES_OSC32K_CAL_ADDR (NVMCTRL_OTP4 + 4)
// #define SYSCTRL_FUSES_OSC32K_CAL_Pos 6 /** (NVMCTRL_OTP4) OSC32K Calibration */
// #define SYSCTRL_FUSES_OSC32K_CAL_Msk (0x7Fu << SYSCTRL_FUSES_OSC32K_CAL_Pos)
// #define SYSCTRL_FUSES_OSC32K_CAL(value) ((SYSCTRL_FUSES_OSC32K_CAL_Msk & ((value) << SYSCTRL_FUSES_OSC32K_CAL_Pos)))
// u32_t fuse = *(u32_t *)FUSES_OSC32K_CAL_ADDR;
// u32_t calib = (fuse & FUSES_OSC32K_CAL_Msk) >> FUSES_OSC32K_CAL_Pos;
fuse := *(*uint32)(unsafe.Pointer(uintptr(0x00806020) + 4))
calib := (fuse & uint32(0x7f<<6)) >> 6
// SYSCTRL_OSC32K_CALIB(calib) |
// SYSCTRL_OSC32K_STARTUP(0x6u) |
// SYSCTRL_OSC32K_EN32K | SYSCTRL_OSC32K_ENABLE;
sam.SYSCTRL.OSC32K = sam.RegValue((calib << sam.SYSCTRL_OSC32K_CALIB_Pos) |
(0x6 << sam.SYSCTRL_OSC32K_STARTUP_Pos) |
sam.SYSCTRL_OSC32K_EN32K |
sam.SYSCTRL_OSC32K_EN1K |
sam.SYSCTRL_OSC32K_ENABLE)
// Wait for oscillator stabilization
for (sam.SYSCTRL.PCLKSR & sam.SYSCTRL_PCLKSR_OSC32KRDY) == 0 {
}
// Software reset the module to ensure it is re-initialized correctly
sam.GCLK.CTRL = sam.GCLK_CTRL_SWRST
// Wait for reset to complete
for (sam.GCLK.CTRL&sam.GCLK_CTRL_SWRST) > 0 && (sam.GCLK.STATUS&sam.GCLK_STATUS_SYNCBUSY) > 0 {
}
// Put OSC32K as source of Generic Clock Generator 1
sam.GCLK.GENDIV = sam.RegValue((1 << sam.GCLK_GENDIV_ID_Pos) |
(0 << sam.GCLK_GENDIV_DIV_Pos))
waitForSync()
// GCLK_GENCTRL_ID(1) | GCLK_GENCTRL_SRC_OSC32K | GCLK_GENCTRL_GENEN;
sam.GCLK.GENCTRL = sam.RegValue((1 << sam.GCLK_GENCTRL_ID_Pos) |
(sam.GCLK_GENCTRL_SRC_OSC32K << sam.GCLK_GENCTRL_SRC_Pos) |
sam.GCLK_GENCTRL_GENEN)
waitForSync()
// Use Generic Clock Generator 1 as source for Generic Clock Multiplexer 0 (DFLL48M reference)
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_DFLL48 << sam.GCLK_CLKCTRL_ID_Pos) |
(sam.GCLK_CLKCTRL_GEN_GCLK1 << sam.GCLK_CLKCTRL_GEN_Pos) |
sam.GCLK_CLKCTRL_CLKEN)
waitForSync()
// Remove the OnDemand mode, Bug http://avr32.icgroup.norway.atmel.com/bugzilla/show_bug.cgi?id=9905
sam.SYSCTRL.DFLLCTRL = sam.SYSCTRL_DFLLCTRL_ENABLE
// Wait for ready
for (sam.SYSCTRL.PCLKSR & sam.SYSCTRL_PCLKSR_DFLLRDY) == 0 {
}
// Handle DFLL calibration based on info learned from Arduino SAMD implementation,
// using value stored in fuse.
// #define SYSCTRL_FUSES_DFLL48M_COARSE_CAL_ADDR (NVMCTRL_OTP4 + 4)
// #define SYSCTRL_FUSES_DFLL48M_COARSE_CAL_Pos 26 /**< \brief (NVMCTRL_OTP4) DFLL48M Coarse Calibration */
// #define SYSCTRL_FUSES_DFLL48M_COARSE_CAL_Msk (0x3Fu << SYSCTRL_FUSES_DFLL48M_COARSE_CAL_Pos)
// #define SYSCTRL_FUSES_DFLL48M_COARSE_CAL(value) ((SYSCTRL_FUSES_DFLL48M_COARSE_CAL_Msk & ((value) << SYSCTRL_FUSES_DFLL48M_COARSE_CAL_Pos)))
coarse := (fuse >> 26) & 0x3F
if coarse == 0x3f {
coarse = 0x1f
}
sam.SYSCTRL.DFLLVAL |= sam.RegValue(coarse << sam.SYSCTRL_DFLLVAL_COARSE_Pos)
sam.SYSCTRL.DFLLVAL |= (0x1ff << sam.SYSCTRL_DFLLVAL_FINE_Pos)
// Write full configuration to DFLL control register
// SYSCTRL_DFLLMUL_CSTEP( 0x1f / 4 ) | // Coarse step is 31, half of the max value
// SYSCTRL_DFLLMUL_FSTEP( 10 ) |
// SYSCTRL_DFLLMUL_MUL( (48000) ) ;
sam.SYSCTRL.DFLLMUL = sam.RegValue(((31 / 4) << sam.SYSCTRL_DFLLMUL_CSTEP_Pos) |
(10 << sam.SYSCTRL_DFLLMUL_FSTEP_Pos) |
(48000 << sam.SYSCTRL_DFLLMUL_MUL_Pos))
// disable DFLL
sam.SYSCTRL.DFLLCTRL = 0
waitForSync()
sam.SYSCTRL.DFLLCTRL |= sam.SYSCTRL_DFLLCTRL_MODE |
sam.SYSCTRL_DFLLCTRL_CCDIS |
sam.SYSCTRL_DFLLCTRL_USBCRM |
sam.SYSCTRL_DFLLCTRL_BPLCKC
// Wait for ready
for (sam.SYSCTRL.PCLKSR & sam.SYSCTRL_PCLKSR_DFLLRDY) == 0 {
}
// Re-enable the DFLL
sam.SYSCTRL.DFLLCTRL |= sam.SYSCTRL_DFLLCTRL_ENABLE
// Wait for ready
for (sam.SYSCTRL.PCLKSR & sam.SYSCTRL_PCLKSR_DFLLRDY) == 0 {
}
// Switch Generic Clock Generator 0 to DFLL48M. CPU will run at 48MHz.
sam.GCLK.GENDIV = sam.RegValue((0 << sam.GCLK_GENDIV_ID_Pos) |
(0 << sam.GCLK_GENDIV_DIV_Pos))
waitForSync()
sam.GCLK.GENCTRL = sam.RegValue((0 << sam.GCLK_GENCTRL_ID_Pos) |
(sam.GCLK_GENCTRL_SRC_DFLL48M << sam.GCLK_GENCTRL_SRC_Pos) |
sam.GCLK_GENCTRL_IDC |
sam.GCLK_GENCTRL_GENEN)
waitForSync()
// Modify PRESCaler value of OSC8M to have 8MHz
sam.SYSCTRL.OSC8M |= (sam.SYSCTRL_OSC8M_PRESC_0 << sam.SYSCTRL_OSC8M_PRESC_Pos)
sam.SYSCTRL.OSC8M &^= (1 << sam.SYSCTRL_OSC8M_ONDEMAND_Pos)
// Wait for oscillator stabilization
for (sam.SYSCTRL.PCLKSR & sam.SYSCTRL_PCLKSR_OSC8MRDY) == 0 {
}
// Use OSC8M as source for Generic Clock Generator 3
sam.GCLK.GENDIV = sam.RegValue((3 << sam.GCLK_GENDIV_ID_Pos))
waitForSync()
sam.GCLK.GENCTRL = sam.RegValue((3 << sam.GCLK_GENCTRL_ID_Pos) |
(sam.GCLK_GENCTRL_SRC_OSC8M << sam.GCLK_GENCTRL_SRC_Pos) |
sam.GCLK_GENCTRL_GENEN)
waitForSync()
// Use OSC32K as source for Generic Clock Generator 2
// OSC32K/1 -> GCLK2 at 32KHz
sam.GCLK.GENDIV = sam.RegValue(2 << sam.GCLK_GENDIV_ID_Pos)
waitForSync()
sam.GCLK.GENCTRL = sam.RegValue((2 << sam.GCLK_GENCTRL_ID_Pos) |
(sam.GCLK_GENCTRL_SRC_OSC32K << sam.GCLK_GENCTRL_SRC_Pos) |
sam.GCLK_GENCTRL_GENEN)
waitForSync()
// Use GCLK2 for RTC
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_RTC << sam.GCLK_CLKCTRL_ID_Pos) |
(sam.GCLK_CLKCTRL_GEN_GCLK2 << sam.GCLK_CLKCTRL_GEN_Pos) |
sam.GCLK_CLKCTRL_CLKEN)
waitForSync()
// Set the CPU, APBA, B, and C dividers
sam.PM.CPUSEL = sam.PM_CPUSEL_CPUDIV_DIV1
sam.PM.APBASEL = sam.PM_APBASEL_APBADIV_DIV1
sam.PM.APBBSEL = sam.PM_APBBSEL_APBBDIV_DIV1
sam.PM.APBCSEL = sam.PM_APBCSEL_APBCDIV_DIV1
// Disable automatic NVM write operations
sam.NVMCTRL.CTRLB |= sam.NVMCTRL_CTRLB_MANW
}
func initRTC() {
// turn on digital interface clock
sam.PM.APBAMASK |= sam.PM_APBAMASK_RTC_
// disable RTC
sam.RTC_MODE0.CTRL = 0
waitForSync()
// reset RTC
sam.RTC_MODE0.CTRL |= sam.RTC_MODE0_CTRL_SWRST
waitForSync()
// set Mode0 to 32-bit counter (mode 0) with prescaler 1 and GCLK2 is 32KHz/1
sam.RTC_MODE0.CTRL = sam.RegValue16((sam.RTC_MODE0_CTRL_MODE_COUNT32 << sam.RTC_MODE0_CTRL_MODE_Pos) |
(sam.RTC_MODE0_CTRL_PRESCALER_DIV1 << sam.RTC_MODE0_CTRL_PRESCALER_Pos) |
sam.RTC_MODE0_CTRL_MATCHCLR)
waitForSync()
sam.RTC_MODE0.COMP0 = 0xffffffff
waitForSync()
// re-enable RTC
sam.RTC_MODE0.CTRL |= sam.RTC_MODE0_CTRL_ENABLE
waitForSync()
arm.SetPriority(sam.IRQ_RTC, 0xc0)
arm.EnableIRQ(sam.IRQ_RTC)
}
func waitForSync() {
for (sam.GCLK.STATUS & sam.GCLK_STATUS_SYNCBUSY) > 0 {
}
}
// treat all ticks params coming from runtime as being in microseconds
const tickMicros = 1000
var (
timestamp timeUnit // ticks since boottime
timerLastCounter uint64
)
//go:volatile
type isrFlag bool
var timerWakeup isrFlag
const asyncScheduler = false
// sleepTicks should sleep for d number of microseconds.
func sleepTicks(d timeUnit) {
for d != 0 {
ticks() // update timestamp
ticks := uint32(d)
timerSleep(ticks)
d -= timeUnit(ticks)
}
}
// ticks returns number of microseconds since start.
func ticks() timeUnit {
// request read of count
sam.RTC_MODE0.READREQ = sam.RTC_MODE0_READREQ_RREQ
waitForSync()
rtcCounter := uint64(sam.RTC_MODE0.COUNT) * 30 // each counter tick == 30.5us
offset := (rtcCounter - timerLastCounter) // change since last measurement
timerLastCounter = rtcCounter
timestamp += timeUnit(offset) // TODO: not precise
return timestamp
}
// ticks are in microseconds
func timerSleep(ticks uint32) {
timerWakeup = false
if ticks < 30 {
// have to have at least one clock count
ticks = 30
}
// request read of count
sam.RTC_MODE0.READREQ = sam.RTC_MODE0_READREQ_RREQ
waitForSync()
// set compare value
cnt := sam.RTC_MODE0.COUNT
sam.RTC_MODE0.COMP0 = sam.RegValue(uint32(cnt) + (ticks / 30)) // each counter tick == 30.5us
waitForSync()
// enable IRQ for CMP0 compare
sam.RTC_MODE0.INTENSET |= sam.RTC_MODE0_INTENSET_CMP0
for !timerWakeup {
arm.Asm("wfi")
}
}
//go:export RTC_IRQHandler
func handleRTC() {
// disable IRQ for CMP0 compare
sam.RTC_MODE0.INTFLAG = sam.RTC_MODE0_INTENSET_CMP0
timerWakeup = true
}
func initUARTClock() {
// Turn on clock to SERCOM0 for UART0
sam.PM.APBCMASK |= sam.PM_APBCMASK_SERCOM0_
// Use GCLK0 for SERCOM0 aka UART0
// GCLK_CLKCTRL_ID( clockId ) | // Generic Clock 0 (SERCOMx)
// GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is source
// GCLK_CLKCTRL_CLKEN ;
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_SERCOM0_CORE << sam.GCLK_CLKCTRL_ID_Pos) |
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
sam.GCLK_CLKCTRL_CLKEN)
waitForSync()
// Turn on clock to SERCOM1 for UART1
sam.PM.APBCMASK |= sam.PM_APBCMASK_SERCOM1_
// Use GCLK0 for SERCOM1 aka UART1
// GCLK_CLKCTRL_ID( clockId ) | // Generic Clock 0 (SERCOMx)
// GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is source
// GCLK_CLKCTRL_CLKEN ;
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_SERCOM1_CORE << sam.GCLK_CLKCTRL_ID_Pos) |
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
sam.GCLK_CLKCTRL_CLKEN)
waitForSync()
}
func initI2CClock() {
// Turn on clock to SERCOM3 for I2C0
sam.PM.APBCMASK |= sam.PM_APBCMASK_SERCOM3_
// Use GCLK0 for SERCOM3 aka I2C0
// GCLK_CLKCTRL_ID( clockId ) | // Generic Clock 0 (SERCOMx)
// GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is source
// GCLK_CLKCTRL_CLKEN ;
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_SERCOM3_CORE << sam.GCLK_CLKCTRL_ID_Pos) |
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
sam.GCLK_CLKCTRL_CLKEN)
waitForSync()
}
+1 -3
View File
@@ -41,7 +41,7 @@ func main() {
preinit()
initAll()
postinit()
callMain()
mainWrapper()
abort()
}
@@ -71,8 +71,6 @@ func putchar(c byte) {
machine.UART0.WriteByte(c)
}
const asyncScheduler = false
// Sleep this number of ticks of 16ms.
//
// TODO: not very accurate. Improve accuracy by calibrating on startup and every
+1 -3
View File
@@ -20,7 +20,7 @@ func main() {
systemInit()
preinit()
initAll()
callMain()
mainWrapper()
abort()
}
@@ -50,8 +50,6 @@ func putchar(c byte) {
machine.UART0.WriteByte(c)
}
const asyncScheduler = false
func sleepTicks(d timeUnit) {
for d != 0 {
ticks() // update timestamp
+1 -3
View File
@@ -20,13 +20,11 @@ var timestamp timeUnit
func main() {
preinit()
initAll()
callMain()
mainWrapper()
arm.SemihostingCall(arm.SemihostingReportException, arm.SemihostingApplicationExit)
abort()
}
const asyncScheduler = false
func sleepTicks(d timeUnit) {
// TODO: actually sleep here for the given time.
timestamp += d
+1 -1
View File
@@ -8,6 +8,6 @@ type timeUnit int64
func main() {
preinit()
initAll()
callMain()
mainWrapper()
abort()
}
-2
View File
@@ -107,8 +107,6 @@ func initTIM() {
arm.EnableIRQ(stm32.IRQ_TIM3)
}
const asyncScheduler = false
// sleepTicks should sleep for specific number of microseconds.
func sleepTicks(d timeUnit) {
for d != 0 {
+2 -4
View File
@@ -46,8 +46,8 @@ func main() int {
// Run initializers of all packages.
initAll()
// Compiler-generated call to main.main().
callMain()
// Compiler-generated wrapper to main.main().
mainWrapper()
// For libc compatibility.
return 0
@@ -57,8 +57,6 @@ func putchar(c byte) {
_putchar(int(c))
}
const asyncScheduler = false
func sleepTicks(d timeUnit) {
usleep(uint(d) / 1000)
}
+10 -26
View File
@@ -2,13 +2,11 @@
package runtime
import (
"unsafe"
)
type timeUnit int64
type timeUnit float64 // time in milliseconds, just like Date.now() in JavaScript
const tickMicros = 1
const tickMicros = 1000000
var timestamp timeUnit
//go:export io_get_stdout
func io_get_stdout() int32
@@ -30,37 +28,23 @@ func _start() {
//go:export cwa_main
func cwa_main() {
initAll() // _start is not called by olin/cwa so has to be called here
callMain()
mainWrapper()
}
func putchar(c byte) {
resource_write(stdout, &c, 1)
}
//go:export go_scheduler
func go_scheduler() {
scheduler()
func sleepTicks(d timeUnit) {
// TODO: actually sleep here for the given time.
timestamp += d
}
const asyncScheduler = true
// This function is called by the scheduler.
// Schedule a call to runtime.scheduler, do not actually sleep.
//go:export runtime.sleepTicks
func sleepTicks(d timeUnit)
//go:export runtime.ticks
func ticks() timeUnit
func ticks() timeUnit {
return timestamp
}
// Abort executes the wasm 'unreachable' instruction.
func abort() {
trap()
}
//go:export memset
func memset(ptr unsafe.Pointer, c byte, size uintptr) unsafe.Pointer {
for i := uintptr(0); i < size; i++ {
*(*byte)(unsafe.Pointer(uintptr(ptr) + i)) = c
}
return ptr
}
+68 -30
View File
@@ -9,13 +9,14 @@ package runtime
// * A blocking function that calls a non-blocking function is called as
// usual.
// * A blocking function that calls a blocking function passes its own
// coroutine handle as a parameter to the subroutine. When the subroutine
// returns, it will re-insert the parent into the scheduler.
// coroutine handle as a parameter to the subroutine and will make sure it's
// own coroutine is removed from the scheduler. When the subroutine returns,
// it will re-insert the parent into the scheduler.
// Note that a goroutine is generally called a 'task' for brevity and because
// that's the more common term among RTOSes. But a goroutine and a task are
// basically the same thing. Although, the code often uses the word 'task' to
// refer to both a coroutine and a goroutine, as most of the scheduler doesn't
// care about the difference.
// refer to both a coroutine and a goroutine, as most of the scheduler isn't
// aware of the difference.
//
// For more background on coroutines in LLVM:
// https://llvm.org/docs/Coroutines.html
@@ -44,20 +45,25 @@ func (t *coroutine) _promise(alignment int32, from bool) unsafe.Pointer
// Get the promise belonging to a task.
func (t *coroutine) promise() *taskState {
return (*taskState)(t._promise(int32(unsafe.Alignof(taskState{})), false))
return (*taskState)(t._promise(4, false))
}
func makeGoroutine(*uint8) *uint8
// State/promise of a task. Internally represented as:
//
// {i8* next, i1 commaOk, i32/i64 data}
// {i8 state, i32 data, i8* next}
type taskState struct {
next *coroutine
commaOk bool // 'comma-ok' flag for channel receive operation
data uint
state uint8
data uint32
next *coroutine
}
// Various states a task can be in.
const (
TASK_STATE_RUNNABLE = iota
TASK_STATE_SLEEP
TASK_STATE_CALL // waiting for a sub-coroutine
)
// Queues used by the scheduler.
//
// TODO: runqueueFront can be removed by making the run queue a circular linked
@@ -83,28 +89,48 @@ func scheduleLogTask(msg string, t *coroutine) {
}
}
// Set the task to sleep for a given time.
// Set the task state to sleep for a given time.
//
// This is a compiler intrinsic.
func sleepTask(caller *coroutine, duration int64) {
if schedulerDebug {
println(" set sleep:", caller, uint(duration/tickMicros))
println(" set state sleep:", caller, uint32(duration/tickMicros))
}
promise := caller.promise()
promise.data = uint(duration / tickMicros) // TODO: longer durations
addSleepTask(caller)
promise.state = TASK_STATE_SLEEP
promise.data = uint32(duration / tickMicros) // TODO: longer durations
}
// Add a non-queued task to the run queue.
// Wait for the result of an async call. This means that the parent goroutine
// will be removed from the runqueue and be rescheduled by the callee.
//
// This is a compiler intrinsic, and is called from a callee to reactivate the
// caller.
func activateTask(task *coroutine) {
if task == nil {
// This is a compiler intrinsic.
func waitForAsyncCall(caller *coroutine) {
scheduleLogTask(" set state call:", caller)
promise := caller.promise()
promise.state = TASK_STATE_CALL
}
// Add a task to the runnable or sleep queue, depending on the state.
//
// This is a compiler intrinsic.
func yieldToScheduler(t *coroutine) {
if t == nil {
return
}
scheduleLogTask(" set runnable:", task)
runqueuePushBack(task)
// See what we should do with this task: try to execute it directly
// again or let it sleep for a bit.
promise := t.promise()
if promise.state == TASK_STATE_CALL {
scheduleLogTask(" set waiting for call:", t)
return // calling an async task, the subroutine will re-active the parent
} else if promise.state == TASK_STATE_SLEEP && promise.data != 0 {
scheduleLogTask(" set sleeping:", t)
addSleepTask(t)
} else {
scheduleLogTask(" set runnable:", t)
runqueuePushBack(t)
}
}
// Add this task to the end of the run queue. May also destroy the task if it's
@@ -119,6 +145,9 @@ func runqueuePushBack(t *coroutine) {
if t.promise().next != nil {
panic("runtime: runqueuePushBack: expected next task to be nil")
}
if t.promise().state != TASK_STATE_RUNNABLE {
panic("runtime: runqueuePushBack: expected task state to be runnable")
}
}
if runqueueBack == nil { // empty runqueue
scheduleLogTask(" add to runqueue front:", t)
@@ -140,6 +169,10 @@ func runqueuePopFront() *coroutine {
}
if schedulerDebug {
println(" runqueuePopFront:", t)
// Sanity checking.
if t.promise().state != TASK_STATE_RUNNABLE {
panic("runtime: runqueuePopFront: task not runnable")
}
}
promise := t.promise()
runqueueFront = promise.next
@@ -157,6 +190,9 @@ func addSleepTask(t *coroutine) {
if t.promise().next != nil {
panic("runtime: addSleepTask: expected next task to be nil")
}
if t.promise().state != TASK_STATE_SLEEP {
panic("runtime: addSleepTask: task not sleeping")
}
}
now := ticks()
if sleepQueue == nil {
@@ -200,7 +236,11 @@ func addSleepTask(t *coroutine) {
}
// Run the scheduler until all tasks have finished.
func scheduler() {
// It takes an initial task (main.main) to bootstrap.
func scheduler(main *coroutine) {
// Initial task.
yieldToScheduler(main)
// Main scheduler loop.
for {
scheduleLog("\n schedule")
@@ -214,6 +254,7 @@ func scheduler() {
promise := t.promise()
sleepQueueBaseTime += timeUnit(promise.data)
sleepQueue = promise.next
promise.state = TASK_STATE_RUNNABLE
promise.next = nil
runqueuePushBack(t)
}
@@ -230,15 +271,9 @@ func scheduler() {
}
timeLeft := timeUnit(sleepQueue.promise().data) - (now - sleepQueueBaseTime)
if schedulerDebug {
println(" sleeping...", sleepQueue, uint(timeLeft))
println(" sleeping...", sleepQueue, uint32(timeLeft))
}
sleepTicks(timeUnit(timeLeft))
if asyncScheduler {
// The sleepTicks function above only sets a timeout at which
// point the scheduler will be called again. It does not really
// sleep.
break
}
continue
}
@@ -246,5 +281,8 @@ func scheduler() {
scheduleLog(" <- runqueuePopFront")
scheduleLogTask(" run:", t)
t.resume()
// Add the just resumed task to the run queue or the sleep queue.
yieldToScheduler(t)
}
}
+4 -14
View File
@@ -14,7 +14,8 @@ type _string struct {
// The iterator state for a range over a string.
type stringIterator struct {
byteindex uintptr
byteindex uintptr
rangeindex uintptr
}
// Return true iff the strings match.
@@ -104,10 +105,10 @@ func stringNext(s string, it *stringIterator) (bool, int, rune) {
if len(s) <= int(it.byteindex) {
return false, 0, 0
}
i := int(it.byteindex)
r, length := decodeUTF8(s, it.byteindex)
it.byteindex += length
return true, i, r
it.rangeindex += 1
return true, int(it.rangeindex), r
}
// Convert a Unicode code point into an array of bytes and its length.
@@ -165,14 +166,3 @@ func decodeUTF8(s string, index uintptr) (rune, uintptr) {
return 0xfffd, 1
}
}
// indexByte returns the index of the first instance of c in s, or -1 if c is not present in s.
//go:linkname indexByte strings.IndexByte
func indexByte(s string, c byte) int {
for i := 0; i < len(s); i++ {
if s[i] == c {
return i
}
}
return -1
}
+5 -44
View File
@@ -3,7 +3,6 @@ package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/user"
@@ -211,7 +210,7 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
BuildTags: []string{goos, goarch},
Compiler: commands["clang"],
Linker: "cc",
LDFlags: []string{"-no-pie"}, // WARNING: clang < 5.0 requires -nopie
LDFlags: []string{"-Wl,--defsym=tinygo_gc_root_chain=llvm_gc_root_chain", "-no-pie"}, // WARNING: clang < 5.0 requires -nopie
Objcopy: "objcopy",
GDB: "gdb",
GDBCmds: []string{"run"},
@@ -236,50 +235,12 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
return &spec, nil
}
// Return the TINYGOROOT, or exit with an error.
// Return the source directory of this package, or "." when it cannot be
// recovered.
func sourceDir() string {
// Use $TINYGOROOT as root, if available.
root := os.Getenv("TINYGOROOT")
if root != "" {
if !isSourceDir(root) {
fmt.Fprintln(os.Stderr, "error: $TINYGOROOT was not set to the correct root")
os.Exit(1)
}
return root
}
// Find root from executable path.
path, err := os.Executable()
if err != nil {
// Very unlikely. Bail out if it happens.
panic("could not get executable path: " + err.Error())
}
root = filepath.Dir(filepath.Dir(path))
if isSourceDir(root) {
return root
}
// Fallback: use the original directory from where it was built
// https://stackoverflow.com/a/32163888/559350
_, path, _, _ = runtime.Caller(0)
root = filepath.Dir(path)
if isSourceDir(root) {
return root
}
fmt.Fprintln(os.Stderr, "error: could not autodetect root directory, set the TINYGOROOT environment variable to override")
os.Exit(1)
panic("unreachable")
}
// isSourceDir returns true if the directory looks like a TinyGo source directory.
func isSourceDir(root string) bool {
_, err := os.Stat(filepath.Join(root, "src/runtime/internal/sys/zversion.go"))
if err != nil {
return false
}
_, err = os.Stat(filepath.Join(root, "src/device/arm/arm.go"))
return err == nil
_, path, _, _ := runtime.Caller(0)
return filepath.Dir(path)
}
func getGopath() string {
-15
View File
@@ -1,15 +0,0 @@
{
"inherits": ["cortex-m"],
"llvm-target": "armv6m-none-eabi",
"build-tags": ["atsamd21g18", "sam"],
"cflags": [
"--target=armv6m-none-eabi",
"-Qunused-arguments"
],
"ldflags": [
"-T", "targets/atsamd21g18.ld"
],
"extra-files": [
"src/device/sam/atsamd21g18a.s"
]
}
-10
View File
@@ -1,10 +0,0 @@
MEMORY
{
FLASH_TEXT (rw) : ORIGIN = 0x00000000+0x2000, LENGTH = 0x00040000-0x2000 /* First 8KB used by bootloader */
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x00008000
}
_stack_size = 2K;
INCLUDE "targets/arm.ld"
-5
View File
@@ -1,5 +0,0 @@
{
"inherits": ["atsamd21g18"],
"build-tags": ["sam", "atsamd21g18a", "itsybitsy_m0"],
"flash": "bossac -d -i -e -w -v -R --offset=0x2000 {hex}"
}
+2 -3
View File
@@ -1,15 +1,14 @@
{
"llvm-target": "wasm32-unknown-unknown-wasm",
"build-tags": ["js", "wasm"],
"goos": "js",
"goarch": "wasm",
"compiler": "clang-7",
"linker": "wasm-ld-7",
"linker": "ld.lld-7",
"cflags": [
"--target=wasm32",
"-Oz"
],
"ldflags": [
"-flavor", "wasm",
"-allow-undefined"
],
"emulator": ["cwa"]
+1 -12
View File
@@ -213,17 +213,6 @@
}
},
// func ticks() float64
"runtime.ticks": () => {
return timeOrigin + performance.now();
},
// func sleepTicks(timeout float64)
"runtime.sleepTicks": (timeout) => {
// Do not sleep, only reactivate scheduler after the given timeout.
setTimeout(this._inst.exports.go_scheduler, timeout);
},
// func stringVal(value string) ref
"syscall/js.stringVal": (ret_ptr, value_ptr, value_len) => {
const s = loadString(value_ptr, value_len);
@@ -333,7 +322,7 @@
true,
false,
global,
this._inst.exports.memory,
this._inst.exports.mem,
this,
];
this._refs = new Map();
-95
View File
@@ -1,95 +0,0 @@
package main
import "time"
func main() {
ch := make(chan int)
println("len, cap of channel:", len(ch), cap(ch))
go sender(ch)
n, ok := <-ch
println("recv from open channel:", n, ok)
for n := range ch {
if n == 6 {
time.Sleep(time.Microsecond)
}
println("received num:", n)
}
n, ok = <-ch
println("recv from closed channel:", n, ok)
// Test multi-sender.
ch = make(chan int)
go fastsender(ch)
go fastsender(ch)
go fastsender(ch)
slowreceiver(ch)
// Test multi-receiver.
ch = make(chan int)
go fastreceiver(ch)
go fastreceiver(ch)
go fastreceiver(ch)
slowsender(ch)
// Test iterator style channel.
ch = make(chan int)
go iterator(ch, 100)
sum := 0
for i := range ch {
sum += i
}
println("sum(100):", sum)
// Allow goroutines to exit.
time.Sleep(time.Microsecond)
}
func sender(ch chan int) {
for i := 1; i <= 8; i++ {
if i == 4 {
time.Sleep(time.Microsecond)
println("slept")
}
ch <- i
}
close(ch)
}
func fastsender(ch chan int) {
ch <- 10
ch <- 11
}
func slowreceiver(ch chan int) {
for i := 0; i < 6; i++ {
n := <-ch
println("got n:", n)
time.Sleep(time.Microsecond)
}
}
func slowsender(ch chan int) {
for n := 0; n < 6; n++ {
time.Sleep(time.Microsecond)
ch <- 12 + n
}
}
func fastreceiver(ch chan int) {
sum := 0
for i := 0; i < 2; i++ {
n := <-ch
sum += n
}
println("sum:", sum)
}
func iterator(ch chan int, top int) {
for i := 0; i < top; i++ {
ch <- i
}
close(ch)
}
-21
View File
@@ -1,21 +0,0 @@
len, cap of channel: 0 0
recv from open channel: 1 true
received num: 2
received num: 3
slept
received num: 4
received num: 5
received num: 6
received num: 7
received num: 8
recv from closed channel: 0 false
got n: 10
got n: 11
got n: 10
got n: 11
got n: 10
got n: 11
sum: 25
sum: 29
sum: 33
sum(100): 4950
-22
View File
@@ -9,18 +9,6 @@ func main() {
println("main 2")
time.Sleep(2 * time.Millisecond)
println("main 3")
// Await a blocking call. This must create a new coroutine.
println("wait:")
wait()
println("end waiting")
// Run a non-blocking call in a goroutine. This should be turned into a
// regular call, so should be equivalent to calling nowait() without 'go'
// prefix.
go nowait()
time.Sleep(time.Millisecond)
println("done with non-blocking goroutine")
}
func sub() {
@@ -28,13 +16,3 @@ func sub() {
time.Sleep(2 * time.Millisecond)
println("sub 2")
}
func wait() {
println(" wait start")
time.Sleep(time.Millisecond)
println(" wait end")
}
func nowait() {
println("non-blocking goroutine")
}
-6
View File
@@ -3,9 +3,3 @@ sub 1
main 2
sub 2
main 3
wait:
wait start
wait end
end waiting
non-blocking goroutine
done with non-blocking goroutine
-16
View File
@@ -16,14 +16,6 @@ var testmap2 = map[string]int{
"twelve": 12,
}
type ArrayKey [4]byte
var testMapArrayKey = map[ArrayKey]int{
ArrayKey([4]byte{1, 2, 3, 4}): 1234,
ArrayKey([4]byte{4, 3, 2, 1}): 4321,
}
var testmapIntInt = map[int]int{1: 1, 2: 4, 3: 9}
func main() {
m := map[string]int{"answer": 42, "foo": 3}
readMap(m, "answer")
@@ -39,14 +31,6 @@ func main() {
var nilmap map[string]int
println(m == nil, m != nil, len(m))
println(nilmap == nil, nilmap != nil, len(nilmap))
println(testmapIntInt[2])
testmapIntInt[2] = 42
println(testmapIntInt[2])
arrKey := ArrayKey([4]byte{4, 3, 2, 1})
println(testMapArrayKey[arrKey])
testMapArrayKey[arrKey] = 5555
println(testMapArrayKey[arrKey])
}
func readMap(m map[string]int, key string) {
-4
View File
@@ -50,7 +50,3 @@ lookup with comma-ok: eight 8 true
lookup with comma-ok: nokey 0 false
false true 2
true false 0
4
42
4321
5555
-11
View File
@@ -1,11 +0,0 @@
package main
func testRangeString() {
for i, c := range "abcü¢€𐍈°x" {
println(i, c)
}
}
func main() {
testRangeString()
}
-9
View File
@@ -1,9 +0,0 @@
0 97
1 98
2 99
3 252
5 162
7 8364
10 66376
14 176
16 120
+1 -1
View File
@@ -92,7 +92,7 @@ func main() {
}
println()
// Verify the fix in https://github.com/tinygo-org/tinygo/pull/119
// Verify the fix in https://github.com/aykevl/tinygo/pull/119
var unnamed [32]byte
var named MySlice
assert(len(unnamed[:]) == 32)
-27
View File
@@ -81,15 +81,6 @@ def readSVD(path, sourceURL):
}
device.peripherals.append(peripheral)
peripheralDict[name] = peripheral
if 'subtypes' in derivedFrom:
for subtype in derivedFrom['subtypes']:
subp = {
'name': name + "_"+subtype['clusterName'],
'groupName': subtype['groupName'],
'description': subtype['description'],
'baseAddress': baseAddress,
}
device.peripherals.append(subp)
continue
peripheral = {
@@ -98,7 +89,6 @@ def readSVD(path, sourceURL):
'description': description,
'baseAddress': baseAddress,
'registers': [],
'subtypes': [],
}
device.peripherals.append(peripheral)
peripheralDict[name] = peripheral
@@ -118,23 +108,6 @@ def readSVD(path, sourceURL):
clusterPrefix = clusterName + '_'
clusterOffset = int(getText(cluster.find('addressOffset')), 0)
if cluster.find('dim') is None:
if clusterOffset is 0:
# make this a separate peripheral
cpRegisters = []
for regEl in cluster.findall('register'):
cpRegisters.extend(parseRegister(groupName, regEl, baseAddress, clusterName+"_"))
cpRegisters.sort(key=lambda r: r['address'])
clusterPeripheral = {
'name': name+ "_" +clusterName,
'groupName': groupName+ "_" +clusterName,
'description': description+ " - " +clusterName,
'clusterName': clusterName,
'baseAddress': baseAddress,
'registers': cpRegisters,
}
device.peripherals.append(clusterPeripheral)
peripheral['subtypes'].append(clusterPeripheral)
continue
dim = None
dimIncrement = None
else: