Compare commits

...

82 Commits

Author SHA1 Message Date
Ayke van Laethem cc313798f3 main: version 0.8.0 2019-09-18 16:51:27 +02:00
Ayke van Laethem 86acfc7ef4 ci: add caching to macOS build
This should speed up testing.
2019-09-18 16:34:01 +02:00
Ron Evans a5fb785334 compiler: ensure that any passed in target, if it does not point to a .json file, is a full LLVM triple
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-18 16:11:09 +02:00
Ron Evans 86b888fdcb docs: update README with current list of supported boards
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-18 15:50:52 +02:00
Ron Evans ef1bfe359c runtime/samd51: set minimum sleep to 260us due to minimum delay in register synchronization
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-18 15:27:34 +02:00
Ron Evans 0a5601c3fc runtime/samd21: set minimum sleep to 214us due to minimum delay in register synchronization
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-18 15:26:16 +02:00
Ron Evans f5fd49bb74 machine/samd21: use PinMode for SPI SERCOM peripheral to allow for more configuration options on boards like Arduino Nano33-IoT that have many predefined pin mappings
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-17 21:28:05 +02:00
Ayke van Laethem d2856bd6bd nrf: improve SPI write-only speed, by making use of double buffering
The SPI peripheral in the nrf chips support double buffering, which
makes it possible to keep sending continuously. This change introduces
double buffering on the nrf chips, which should improve SPI performance.

Tested on the pca10040 (nrf52832).
2019-09-17 17:51:13 +02:00
Elliott Sales de Andrade 869d2c4524 Add a way to bake-in final TINYGOROOT.
This is similar to setting FINAL_GOROOT when building standard Go.
2019-09-17 17:28:17 +02:00
cn 9d35c1197f machine/stm32f103xx/nucleo-f103rb: add support for NUCLEO-F103RB STM32F1-based board
Compared to the already supported stm32f103xx "bluepill" board this:

- features 128 KiB flash memory size ("RB" suffix) instead of 64 KiB, see `targets/stm32f103rb.ld`
- has onboard ST-LINK/V2-1 programmer and debugger requiring different OpenOCD configuration file
- uses USART2 connected to ST-LINK/V2-1 debugger as virtual COM port over USB for `putchar()`
- has a user-accessible button besides the reset button
2019-09-17 13:26:26 +02:00
cn 688dd81400 machine/stm32f103xx: allow board specific UART usage
Motivation: The bluepill uses USART1 as UART0 but other boards like the
STM32 Nucleo boards (and disco as well) use USART2 for USB COM port.

To avoid duplication of code the same pattern as in `machine_atsamd21.go`
is applied where only UART-specific code is moved to `board_*.go`.
2019-09-17 10:28:03 +02:00
Jaden Weiss abca3132a9 fix bugs found by LLVM assertions 2019-09-16 18:31:33 +02:00
Ayke van Laethem e0ebc75df2 runtime: implement memcpy
A call to memcpy is sometimes created by the compiler, for example when
compiling with -opt=s or opt=2.
2019-09-16 15:34:16 +02:00
Ayke van Laethem b4859240e1 main: stop running the LLVM verifier for trivial changes
The above changes might indeed introduce inconsistencies in the IR, but
the code is small and doesn't change often so it's unnecessary to always
check for errors. It will be tested again later anyway.

The compile time impact was somewhere around 6%, so that's a nice
improvement.
2019-09-16 15:11:59 +02:00
Konstantin Itskov 4eb34b36f8 Add syscall/js.valueInvoke support 2019-09-16 13:36:37 +02:00
Konstantin Itskov 61750be9aa Add syscall/js.valueSetIndex support 2019-09-16 13:32:31 +02:00
cn efd9cf72ba machine/stm32f103xx: add machine.Pin.Get method for reading GPIO values
Writing pin values was already possible but reading was missing.
2019-09-16 13:24:21 +02:00
cn 500ee8067d machine/stm32f103xx: fix SPI frequency selection
The SPI frequency is broken since https://github.com/tinygo-org/tinygo/commit/b8c326d710a88877413e550b06100d9b79373dcd
added I2C interface and changed the unrelated `PCLK2 = HCLK/4` initialization
to `PCLK2 = HCLK/1` (but I2C uses PCLK1 anyways).

This commit changes all baud rate prescalers to be /4 compared to before.

Note: it is not possible to find an equivalent for 125 KHz SPI speed,
it will be too fast (`f = 72 MHz / 256`)
2019-09-16 11:08:59 +02:00
Ayke van Laethem 8cd2c7502e all: move OptimizeMaps to transforms and add tests 2019-09-15 21:26:27 +02:00
Ayke van Laethem d905476231 all: refactor heap-to-stack transform into the transform package
Also add unit tests.

This is the first of several transformation (optimization/lowering)
passes that I'd like to move to the new transform package. This
separates the compiler from the optimizer.

Also, it finally adds unit tests for the compiler, not just end-to-end
compilation tests. This should improve robustness and should make it
easier to change these transformation passes in the future.
While the heap-to-stack transform is relatively simple, other passes are
much more complex. Adding unit tests not only helps robustness over
time, but also doubles as documentation as to what these transformation
passes do exactly.
2019-09-15 21:26:27 +02:00
Ayke van Laethem 10ed3decb0 compiler: rename getZeroValue to llvm.ConstNull
It does the same thing but should be more complete, and it probably is
faster as well (just one CGo call instead of several).
2019-09-15 19:09:10 +02:00
Ayke van Laethem 8d959b7c63 all: replace dep with Go modules
We've moved to go modules, and keeping both working is burdensome.
In fact, I think dep already wasn't working.
2019-09-15 18:04:38 +02:00
Ron Evans 02c4020228 machine/atsamd51: add support for ATSAMD51 processor using Adafruit ItsyBitsy-M4 board
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-14 17:56:14 +02:00
Ron Evans bc41cc688f main: check the major/minor installed version of Go before tinygo compile, to ensure that it is a supported version.
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-14 17:50:16 +02:00
Ron Evans 7d481c179f machine/atsamd21: uses different required magic values for BOSSA and UF2 bootloaders
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-14 16:18:30 +02:00
Ron Evans 69aaea44a0 machine/atsamd21: Add support for bootloader reset/programming and correct error in receiving endpoint 0 data for CDC Set Line Coding changes, implementing system reset on switch to 1200 baud connection speed with DTR false.
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-14 16:18:30 +02:00
Ron Evans 6c1abfe047 device/arm: add support for System Control Block (SCB) registers and SystemReset() function
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-14 16:18:30 +02:00
Ron Evans 656fb4e372 tools/generator: correctly handle padding when it is 3 bytes long
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-13 00:40:25 +02:00
Ron Evans f4485f414f uf2: extract target address from ELF to accomodate the differences with M0 vs M4 processors
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-13 00:37:54 +02:00
Phil Kedy 55144ad608 WASM: Support for setting an imported function's module name (#455)
* wasm: add support for setting a function's Wasm import module name by using the //go:wasm-module comment.
2019-09-12 08:35:43 +02:00
Ron Evans 1dbfc976e8 docker: use Go 1.12 base Docker image for compatibility
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-09 09:37:43 +02:00
Ron Evans e26f0b35e3 machine/atsamd21: correct order of params for USB CDC descriptor
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-06 14:24:32 +02:00
Ron Evans 4ee26fd54b machine/atsamd21: correct issue with invalid first reading coming from ADC
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-06 14:22:24 +02:00
Jaden Weiss a718b10502 fix sleep queue corruption bug 2019-09-06 14:19:05 +02:00
BCG 8a5fa51f60 Updated SPI pins so that they are configurable, enabling multiple SPI sercoms for ATSAMD21 2019-08-28 11:19:52 +02:00
Elliott Sales de Andrade 7c5f943564 Fix a Python SyntaxWarning.
Using 'is' for literals is incorrect, and only should be done for
singletons.
2019-08-27 14:17:17 +02:00
Ayke van Laethem 7938740bf3 tools: make tools python2 compatible
This allows the tools to be used for Python 2 and Python 3.
2019-08-26 16:39:28 +02:00
Ayke van Laethem 319d21e662 runtime: don't mark the object right before a non-existing object
False positives (pointers that point to nowhere but happen to point into
the heap) would result in the block just before that pointer to be
marked. This is clearly not intended, so ignore such a pointer.
2019-08-25 13:12:27 +02:00
Ayke van Laethem 3bf2487dc5 runtime: add some more asserts to the GC 2019-08-25 13:12:27 +02:00
Ayke van Laethem 6917faabf5 runtime: fix GC to take goroutines into account
This fix is needed because with the new task-based scheduler, the
current stack pointer may not be on the system stack.
2019-08-25 13:12:27 +02:00
Ayke van Laethem db4de46d88 runtime: add dummy getCurrentStackPointer functions
This is useful for the next commit, to get it to compile on all systems.
2019-08-25 13:12:27 +02:00
Ayke van Laethem 9b1dd8e03d arm7tdmi: clean up architecture definition 2019-08-25 13:12:27 +02:00
Ayke van Laethem e356bad4d1 reflect: implement t.Comparable()
This is necessary to support the context package, which is a dependency
of a lot of packages.
2019-08-20 10:20:09 +02:00
Ayke van Laethem c19c738f52 reflect: implement support for array types 2019-08-19 11:08:26 +02:00
Ayke van Laethem bbc3046687 compiler: add support for 'go' on func values
This commit allows starting a new goroutine directly from a func value,
not just when the static callee is known.

This is necessary to support the whole time package, not just the
commonly used subset that was compiled with the SimpleDCE pass enabled.
2019-08-17 11:51:43 +02:00
Ayke van Laethem e4fc3bb66a compiler,runtime: fix new task-based scheduler
A bug was introduced in the previous commit that led to miscompilations
in the time.Sleep function when the scheduler was disabled, because
time.Sleep (implemented in the runtime) tried to switch to the scheduler
stack.

This commit restores the binary size of most examples to what it was
before, but still reduces static RAM consumption (.bss) slightly. This
gives me some confidence that it does indeed fix the introduced bug.
2019-08-17 08:42:23 +02:00
Ayke van Laethem 542135c357 compiler,runtime: implement stack-based scheduler
This scheduler is intended to live along the (stackless) coroutine based
scheduler which is needed for WebAssembly and unsupported platforms. The
stack based scheduler is somewhat simpler in implementation as it does
not require full program transform passes and supports things like
function pointers and interface methods out of the box with no changes.

Code size is reduced in most cases, even in the case where no scheduler
scheduler is used at all. I'm not exactly sure why but these changes
likely allowed some further optimizations somewhere. Even RAM is
slightly reduced, perhaps some global was elminated in the process as
well.
2019-08-15 17:31:54 +02:00
Brad Erickson 61f711ef26 Add common test logging methods such as Errorf/Fatalf/Printf
Implements nearly all of the test logging methods for both T and B
structs. Majority of the code has been copied from:
golang.org/src/testing/testing.go
then updated to match the existing testing.go structure.

Code structure/function/method order mimics upstream.

Both FailNow() and SkipNow() cannot be completely implemented,
because they require an early exit from the goroutine. Instead,
they call Error() to report the limitation.

This incomplete implementation allows more detailed test logging and
increases compatiblity with upstream.
2019-08-13 20:59:09 +02:00
Ayke van Laethem fd3309afa8 compiler,runtime: implement []rune to string conversion
This is used by a few packages in the standard library, at least
compress/gzip and regexp/syntax.
2019-08-11 15:45:35 +02:00
Ayke van Laethem fea56d4164 compiler: add support for full slice expression for slicing arrays
This was an oversight in the main commit for full slice expressions:
https://github.com/tinygo-org/tinygo/pull/472
This syntax is used by the regexp package, for example.
2019-08-11 15:37:05 +02:00
Ayke van Laethem ea8e4079bc reflect: add support for linked lists
Linked lists are usually implemented as follows:

    type linkedList struct {
        next *linkedList
        data int // whatever
    }

This caused a stack overflow while writing out the reflect run-time type
information. This has now been fixed by splitting the allocation of a
named type number from setting the underlying type in the sidetable.
2019-08-11 15:00:43 +02:00
Ayke van Laethem 0818a125c0 reflect: fix IsNil and Pointer for addressable pointer types 2019-08-11 15:00:43 +02:00
Brad Erickson 7c758b0dfc testing: Add Benchmark B struct stub
This struct allows test files containing basic benchmarks to compile
and run, but will not run the benchmarks themselves.

For #491
2019-08-11 14:51:02 +02:00
Daniel Esteban 4ea59742d7 CONTRIBUTORS: add name 2019-08-10 22:00:01 +02:00
Ayke van Laethem b7cb10519c compiler: fix some invalid IR
Compiling LLVM with assertions enabled revealed some bugs in TinyGo that
weren't caught by normal verifying of the LLVM module.
2019-08-09 21:27:23 +02:00
Ayke van Laethem 562ad740da compiler: make runtime.makeGoroutine AVR compatible
Previously it would use a bitcast, which cannot directly be used on AVR
because functions live in a different address space on AVR. To fix this,
use a ptrtoint/inttoptr pair.

This allows testdata/coroutines.go to be compiled, but due to what
appears to be an LLVM bug cannot be optimized and codegen'ed:

    tinygo: /home/ayke/src/github.com/tinygo-org/tinygo/llvm-project/llvm/lib/IR/Constants.cpp:1776: static llvm::Constant *llvm::ConstantExpr::getBitCast(llvm::Constant *, llvm::Type *, bool): Assertion `CastInst::castIsValid(Instruction::BitCast, C, DstTy) && "Invalid constantexpr bitcast!"' failed.

This happens as one of the function passes after the TinyGo passes and
after the module has been verified so most likely it is a bug somewhere
in LLVM.
2019-08-09 21:27:23 +02:00
Kyle Lemons (Loon, LLC) 92206558fb gameboy-advance: don't crash copying unaligned strings
Strings are emitted in .rodata sections, which are not yet mentioned in the linker script.

I can't exactly explain why it didn't work before, as these sections should have been included in .bss and thus properly aligned, but it appears to work reliably.
2019-08-09 20:14:17 +02:00
Kyle Lemons (Loon, LLC) fd6b671494 CONTRIBUTORS: Add Loon, LLC for kevlar@loon.com 2019-08-09 20:14:14 +02:00
Ayke van Laethem e2c8654237 reflect: add support for struct types 2019-08-08 15:23:47 +02:00
Ayke van Laethem 5012be337f reflect: implement Type.Align() 2019-08-08 15:23:47 +02:00
Ayke van Laethem 614433cb75 reflect: implement Type.AssignableTo 2019-08-08 15:23:47 +02:00
Ayke van Laethem b8cd8b6f25 reflect: add support for Type.Bits() 2019-08-08 15:23:47 +02:00
Ayke van Laethem f43d01bdc7 compiler: make struct types more unique
There are a lot more fields that are important when comparing structs
with each other. Take them into account when building the unique ID per
struct type.

Example code that differs between the compilers:
https://play.golang.org/p/nDX4tSHOf_T
2019-08-08 15:23:47 +02:00
Ayke van Laethem 9979cf2bbd CONTRIBUTING: add my name 2019-08-08 12:07:49 +02:00
Ayke van Laethem 79b470acee compiler: make goroutine lowering panic message more helpful
This is a workaround for an existing issue, to see which function is to
blame.
2019-08-08 11:59:53 +02:00
Ron Evans 0b7047cc0e governance: add CONTRIBUTORS file to repo
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-08-08 11:59:41 +02:00
Ron Evans 99add1571b docker: Debian stable-slim image now based on buster not stretch, so update the needed package repo used for LLVM.
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-08-07 11:27:44 +02:00
Elliott Sales de Andrade b7f4373681 Fix parsing of beta Go versions. 2019-08-05 17:45:45 +02:00
Ayke van Laethem 95721a8d8c reflect: add support for named types
With this change, it becomes possible to get the element type of named
slices, pointers, and channels.

This is a prerequisite to enable the common named struct types. There's
more to come.
2019-08-05 14:44:30 +02:00
Ayke van Laethem 33dc4b5121 compiler: fix crash with linked lists in interfaces
This commit fixes the following issue:
https://github.com/tinygo-org/tinygo/issues/309
Also, it prepares for some other reflect-related changes that should
make it easier to add support for named types (etc.) in the future.
2019-08-05 14:44:30 +02:00
Ayke van Laethem a04db67ea9 runtime: work around a bug in LLVM for .bss zeroing
See the following bug: https://bugs.llvm.org/show_bug.cgi?id=42881

I think this is a bug in LLVM, but the code in question wasn't the best
code anyway. By fixing this, about 16 bytes of code are saved on ARM
chips (and much more on AVR).
2019-08-05 10:07:46 +02:00
Ayke van Laethem 9846c062b3 targets: add support for GameBoy Advance
Only tested in an emulator (mGBA). Almost nothing is supported, but
drawing to the screen works.
2019-08-05 09:19:49 +02:00
Elliott Sales de Andrade 9878f7ebc4 Add support for linux/386 syscalls. 2019-08-04 18:15:06 +02:00
Ayke van Laethem 4688664b41 compiler: implement full slice expression
This feature was introduced in Go 1.2 and is used by some standard
library packages.
2019-08-04 17:51:16 +02:00
Ayke van Laethem 54169c714f all: use baremetal build tag
This simplifies adding more baremetal targets, like a GameBoy Advance,
or baremetal x86 for unikernels.
2019-08-04 17:12:07 +02:00
Justin Clift f76385850d main: fix outdated panic option text 2019-08-04 14:11:08 +02:00
Doug Daniels cb09531873 Update examples/wasm README.md
Modify instructions for running the wasm example HTTP server to `go run server.go`
2019-08-03 11:27:06 +02:00
Sven Sauleau 51f2a152d5 Update README.md 2019-08-03 11:24:34 +02:00
Ayke van Laethem c3880b3ec5 main: update version to 0.8.0-dev 2019-08-02 17:21:57 +02:00
Ayke van Laethem 7d5542dda7 main: version 0.7.1
This release contains a small fix for some atsamd21-based boards to make
them usable on macOS: it now allows to specify the serial port using the
-port flag.
2019-07-27 10:02:10 -07:00
Ayke van Laethem 8eb6039052 ci: fix Go image on the Debian Stretch images
The build broke because the images got upgraded from stretch to buster.
Specify the stretch images (for now) so that it works again.
We can upgrade to buster for go1.12 at a later time.
2019-07-27 09:51:38 -07:00
Trevor Rosen 64597de344 Device path at flash time for several boards 2019-07-25 10:17:16 -07:00
149 changed files with 7695 additions and 1199 deletions
+22 -10
View File
@@ -61,18 +61,19 @@ commands:
- install-node
- restore_cache:
keys:
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-{{ checksum "Gopkg.lock" }}
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v2-{{ checksum "go.mod" }}
- llvm-source-linux
- run: go install .
- run: go test -v
- run: go test -v ./transform .
- run: make gen-device -j4
- run: make smoketest RISCV=0
- save_cache:
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- run: make fmt-check
build-linux:
steps:
@@ -95,8 +96,8 @@ commands:
- install-node
- restore_cache:
keys:
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-{{ checksum "Gopkg.lock" }}
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v2-{{ checksum "go.mod" }}
- llvm-source-linux
- restore_cache:
keys:
@@ -138,10 +139,11 @@ commands:
- store_artifacts:
path: /tmp/tinygo.linux-amd64.tar.gz
- save_cache:
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- run:
name: "Extract release tarball"
command: |
@@ -166,6 +168,10 @@ commands:
sudo tar -C /usr/local -xzf go1.12.5.darwin-amd64.tar.gz
ln -s /usr/local/go/bin/go /usr/local/bin/go
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
- restore_cache:
keys:
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-macos-v2-{{ checksum "go.mod" }}
- restore_cache:
keys:
- llvm-source-8-macos-v5
@@ -220,22 +226,28 @@ commands:
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
- run: make smoketest AVR=0
- save_cache:
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
jobs:
test-llvm8-go111:
docker:
- image: circleci/golang:1.11
- image: circleci/golang:1.11-stretch
steps:
- test-linux
test-llvm8-go112:
docker:
- image: circleci/golang:1.12
- image: circleci/golang:1.12-stretch
steps:
- test-linux
build-linux:
docker:
- image: circleci/golang:1.12
- image: circleci/golang:1.12-stretch
steps:
- build-linux
build-macos:
+7 -4
View File
@@ -16,7 +16,6 @@ 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
* CMake
@@ -28,15 +27,19 @@ on a different system like Mac.
## Download the source
The first step is to download the TinyGo sources (use `--recursive` if you clone
the git repository). Then, inside the directory, perform these steps:
the git repository). Then, inside the directory, download the LLVM source:
dep ensure -vendor-only # download Go dependencies
make llvm-source # download LLVM
make llvm-source
You can also store LLVM outside of the TinyGo root directory by setting the
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
covered by this guide.
TinyGo uses Go modules, so if you clone TinyGo inside your GOPATH (and are using
Go below 1.13), make sure that Go modules are enabled:
export GO111MODULE=on
## Build LLVM, Clang, LLD
Before starting the build, you may want to set the following environment
+54
View File
@@ -1,3 +1,57 @@
0.8.0
---
* **command line**
- fix parsing of beta Go versions
- check the major/minor installed version of Go before compiling
- validate `-target` flag better to not panic on an invalid target
* **compiler**
- implement full slice expression: `s[:2:4]`
- fix a crash when storing a linked list in an interface
- fix comparing struct types by making type IDs more unique
- fix some bugs in IR generation
- add support for linked lists in reflect data
- implement `[]rune` to string conversion
- implement support for `go` on func values
* **standard library**
- `reflect`: add support for named types
- `reflect`: add support for `t.Bits()`
- `reflect`: add basic support for `t.AssignableTo()`
- `reflect`: implement `t.Align()`
- `reflect`: add support for struct types
- `reflect`: fix bug in `v.IsNil` and `v.Pointer` for addressable values
- `reflect`: implement support for array types
- `reflect`: implement `t.Comparable()`
- `runtime`: implement stack-based scheduler
- `runtime`: fix bug in the sleep queue of the scheduler
- `runtime`: implement `memcpy` for Cortex-M
- `testing`: implement stub `testing.B` struct
- `testing`: add common test logging methods such as Errorf/Fatalf/Printf
* **targets**
- `386`: add support for linux/386 syscalls
- `atsamd21`: make SPI pins configurable so that multiple SPI ports can be
used
- `atsamd21`: correct issue with invalid first reading coming from ADC
- `atsamd21`: add support for reset-to-bootloader using 1200baud over USB-CDC
- `atsamd21`: make pin selection more flexible for peripherals
- `atsamd21`: fix minimum delay in `time.Sleep`
- `atsamd51`: fix minimum delay in `time.Sleep`
- `nrf`: improve SPI write-only speed, by making use of double buffering
- `stm32f103`: fix SPI frequency selection
- `stm32f103`: add machine.Pin.Get method for reading GPIO values
- `stm32f103`: allow board specific UART usage
- `nucleo-f103rb`: add support for NUCLEO-F103RB board
- `itsybitsy-m4`: add support for this board with a SAMD51 family chip
- `cortex-m`: add support for `arm.SystemReset()`
- `gameboy-advance`: add initial support for the GameBoy Advance
- `wasm`: add `//go:wasm-module` magic comment to set the wasm module name
- `wasm`: add syscall/js.valueSetIndex support
- `wasm`: add syscall/js.valueInvoke support
0.7.1
---
* **targets**
- `atsamd21`: add support for the `-port` flag in the flash subcommand
0.7.0
---
* **command line**
+17
View File
@@ -0,0 +1,17 @@
# This is the official list of TinyGo authors for copyright purposes.
#
# This file is not actively maintained.
# To be included, send a change adding the individual or
# company who owns a contribution's copyright.
#
# Names should be added to this file as one of
# Organization's name
# Individual's name <submission email address>
# Individual's name <submission email address> <email2> <emailN>
#
# Please keep the list sorted.
Ayke van Laethem <aykevanlaethem@gmail.com>
Daniel Esteban <conejo@conejo.me>
Loon, LLC.
Ron Evans <ron@hybridgroup.com>
+26 -29
View File
@@ -1,34 +1,31 @@
# TinyGo base stage just installs LLVM 8 and the TinyGo compiler itself.
FROM golang:latest AS tinygo-base
# TinyGo base stage installs Go 1.12, LLVM 8 and the TinyGo compiler itself.
FROM golang:1.12 AS tinygo-base
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-8 main" >> /etc/apt/sources.list && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-8 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y llvm-8-dev libclang-8-dev git
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
COPY . /go/src/github.com/tinygo-org/tinygo
COPY . /tinygo
# remove submodules directories and re-init them to fix any hard-coded paths
# after copying the tinygo directory in the previous step.
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
RUN cd /tinygo/ && \
rm -rf ./lib/* && \
git submodule update --init --recursive --force
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
dep ensure --vendor-only && \
go install /go/src/github.com/tinygo-org/tinygo/
RUN cd /tinygo/ && \
go install /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 /tinygo/src /tinygo/src
COPY --from=tinygo-base /tinygo/targets /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-8 main" >> /etc/apt/sources.list && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-8 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y libllvm8 lld-8
@@ -36,13 +33,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 /tinygo/src /tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
RUN cd /tinygo/ && \
apt-get update && \
apt-get install -y apt-utils python3 make binutils-avr gcc-avr avr-libc && \
make gen-device-avr && \
@@ -54,13 +51,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 /tinygo/src /tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
RUN cd /tinygo/ && \
apt-get update && \
apt-get install -y apt-utils python3 make clang-8 && \
make gen-device-nrf && make gen-device-stm32 && \
@@ -71,11 +68,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 /tinygo/Makefile /tinygo/
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
RUN cd /tinygo/ && \
apt-get update && \
apt-get install -y apt-utils python3 make clang-8 binutils-avr gcc-avr avr-libc && \
make gen-device && \
Generated
-51
View File
@@ -1,51 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
digest = "1:06519a2ec1d59040eaccec40206f9d0b59dc662db2a032f974d6d6b9a2bcb839"
name = "github.com/blakesmith/ar"
packages = ["."]
pruneopts = "UT"
revision = "8bd4349a67f2533b078dbc524689d15dba0f4659"
[[projects]]
branch = "master"
digest = "1:00b45e06c7843541372fc17d982242bd6adfc2fc382b6f2e9ef9ce53d87a50b9"
name = "github.com/marcinbor85/gohex"
packages = ["."]
pruneopts = "UT"
revision = "7a43cd876e46e0f6ddc553f10f91731a78e6e949"
[[projects]]
branch = "master"
digest = "1:ba70784a3deee74c0ca3c87bcac3c2f93d3b2d27d8f237b768c358b45ba47da8"
name = "golang.org/x/tools"
packages = [
"go/ast/astutil",
"go/ssa",
"go/types/typeutil",
]
pruneopts = "UT"
revision = "8dcc6e70cdefe9a82236b6e195e4f4e2108fcb9f"
[[projects]]
branch = "llvm8"
digest = "1:bf5539bdf6b3cc3ec1e45926db05d81180da11ce722fa1edcce3f0b4e1967da5"
name = "tinygo.org/x/go-llvm"
packages = ["."]
pruneopts = "UT"
revision = "7707ae5d1261a8929edea7336c8087ca8b520d8d"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/blakesmith/ar",
"github.com/marcinbor85/gohex",
"golang.org/x/tools/go/ast/astutil",
"golang.org/x/tools/go/ssa",
"tinygo.org/x/go-llvm",
]
solver-name = "gps-cdcl"
solver-version = 1
-11
View File
@@ -1,11 +0,0 @@
[[constraint]]
branch = "llvm8"
name = "tinygo.org/x/go-llvm"
[[constraint]]
branch = "master"
name = "golang.org/x/tools"
[prune]
go-tests = true
unused-packages = true
+4 -1
View File
@@ -86,7 +86,7 @@ build/tinygo:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o build/tinygo -tags byollvm .
test:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm .
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm ./transform .
tinygo-test:
cd tests/tinygotest && tinygo test
@@ -124,6 +124,9 @@ smoketest:
tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
tinygo build -size short -o test.elf -target=gameboy-advance examples/gba-display
tinygo build -size short -o test.elf -target=itsybitsy-m4 examples/blinky1
tinygo build -size short -o test.elf -target=nucleo-f103rb examples/blinky1
ifneq ($(AVR), 0)
tinygo build -size short -o test.elf -target=arduino examples/blinky1
tinygo build -size short -o test.elf -target=digispark examples/blinky1
+5 -2
View File
@@ -2,7 +2,7 @@
[![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (WASM), and command-line tools.
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and command-line tools.
It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language.
@@ -43,16 +43,19 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
The following 15 microcontroller boards are currently supported:
The following 18 microcontroller boards are currently supported:
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
* [BBC micro:bit](https://microbit.org/)
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
* [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
* [Digispark](http://digistump.com/products/1)
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
+15 -3
View File
@@ -56,7 +56,7 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
// normal meaning) and for creating a new slice, where 'capacity' means the
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
// logic is the same in both cases.
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.Value, lowType, highType *types.Basic) {
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
if frame.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
@@ -71,6 +71,9 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V
if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
capacityType = high.Type()
}
if max.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
capacityType = max.Type()
}
if capacityType != capacity.Type() {
capacity = c.builder.CreateZExt(capacity, capacityType, "")
}
@@ -90,6 +93,13 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V
high = c.builder.CreateSExt(high, capacityType, "")
}
}
if max.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if maxType.Info()&types.IsUnsigned != 0 {
max = c.builder.CreateZExt(max, capacityType, "")
} else {
max = c.builder.CreateSExt(max, capacityType, "")
}
}
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
@@ -97,8 +107,10 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V
// Now do the bounds check: low > high || high > capacity
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, capacity, "slice.highcap")
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.outofbounds")
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
outOfBounds3 := c.builder.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
outOfBounds = c.builder.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
// Fail: this is a nil pointer, exit with a panic.
+1 -1
View File
@@ -163,7 +163,7 @@ func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value)
switch t.TypeKind() {
case llvm.StructTypeKind:
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
value := c.getZeroValue(t)
value := llvm.ConstNull(t)
for i, subtyp := range t.StructElementTypes() {
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
fields = remaining
+3 -3
View File
@@ -69,7 +69,7 @@ func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
if unop.CommaOk {
commaOk := c.createRuntimeCall("getTaskPromiseData", []llvm.Value{coroutine}, "chan.commaOk.wide")
commaOk := c.createRuntimeCall("getTaskStateData", []llvm.Value{coroutine}, "chan.commaOk.wide")
commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk")
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
@@ -95,7 +95,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
if expr.Blocking {
// Blocks forever:
// select {}
c.createRuntimeCall("deadlockStub", nil, "")
c.createRuntimeCall("deadlock", nil, "")
return llvm.Undef(llvmType)
} else {
// No-op:
@@ -124,7 +124,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
chanSelectStateType := c.getLLVMRuntimeType("chanSelectState")
for _, state := range expr.States {
ch := c.getValue(frame, state.Chan)
selectState := c.getZeroValue(chanSelectStateType)
selectState := llvm.ConstNull(chanSelectStateType)
selectState = c.builder.CreateInsertValue(selectState, ch, 0, "")
switch state.Dir {
case types.RecvOnly:
+128
View File
@@ -0,0 +1,128 @@
package compiler
// This file implements a set of sanity checks for the IR that is generated.
// It can catch some mistakes that LLVM's verifier cannot.
import (
"errors"
"fmt"
"tinygo.org/x/go-llvm"
)
func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
if t.IsNil() {
panic(t)
}
// prevent infinite recursion for self-referential types
if _, ok := checked[t]; ok {
return
}
checked[t] = struct{}{}
// check for any context mismatches
switch {
case t.Context() == c.ctx:
// this is correct
case t.Context() == llvm.GlobalContext():
// somewhere we accidentally used the global context instead of a real context
panic(fmt.Errorf("type %q uses global context", t.String()))
default:
// we used some other context by accident
panic(fmt.Errorf("type %q uses context %v instead of the main context %v", t.Context(), c.ctx))
}
// if this is a composite type, check the components of the type
switch t.TypeKind() {
case llvm.VoidTypeKind, llvm.LabelTypeKind, llvm.TokenTypeKind, llvm.MetadataTypeKind:
// there should only be one of any of these
if s, ok := specials[t.TypeKind()]; !ok {
specials[t.TypeKind()] = t
} else if s != t {
panic(fmt.Errorf("duplicate special type %q: %v and %v", t.TypeKind().String(), t, s))
}
case llvm.FloatTypeKind, llvm.DoubleTypeKind, llvm.X86_FP80TypeKind, llvm.FP128TypeKind, llvm.PPC_FP128TypeKind:
// floating point numbers are primitives - nothing to recurse
case llvm.IntegerTypeKind:
// integers are primitives - nothing to recurse
case llvm.FunctionTypeKind:
// check arguments and return(s)
for _, v := range t.ParamTypes() {
c.checkType(v, checked, specials)
}
c.checkType(t.ReturnType(), checked, specials)
case llvm.StructTypeKind:
// check all elements
for _, v := range t.StructElementTypes() {
c.checkType(v, checked, specials)
}
case llvm.ArrayTypeKind:
// check element type
c.checkType(t.ElementType(), checked, specials)
case llvm.PointerTypeKind:
// check underlying type
c.checkType(t.ElementType(), checked, specials)
case llvm.VectorTypeKind:
// check element type
c.checkType(t.ElementType(), checked, specials)
}
}
func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
// check type
c.checkType(v.Type(), types, specials)
}
func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
// check value properties
c.checkValue(inst, types, specials)
// check operands
for i := 0; i < inst.OperandsCount(); i++ {
c.checkValue(inst.Operand(i), types, specials)
}
}
func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
// check basic block value and type
c.checkValue(bb.AsValue(), types, specials)
// check instructions
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
c.checkInstruction(inst, types, specials)
}
}
func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
// check function value and type
c.checkValue(fn, types, specials)
// check basic blocks
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
c.checkBasicBlock(bb, types, specials)
}
}
func (c *Compiler) checkModule() {
// check for any context mismatches
switch {
case c.mod.Context() == c.ctx:
// this is correct
case c.mod.Context() == llvm.GlobalContext():
// somewhere we accidentally used the global context instead of a real context
panic(errors.New("module uses global context"))
default:
// we used some other context by accident
panic(fmt.Errorf("module uses context %v instead of the main context %v", c.mod.Context(), c.ctx))
}
types := map[llvm.Type]struct{}{}
specials := map[llvm.TypeKind]llvm.Type{}
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
c.checkFunction(fn, types, specials)
}
for g := c.mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
c.checkValue(g, types, specials)
}
}
+135 -92
View File
@@ -30,6 +30,21 @@ func init() {
// The TinyGo import path.
const tinygoPath = "github.com/tinygo-org/tinygo"
// functionsUsedInTransform is a list of function symbols that may be used
// during TinyGo optimization passes so they have to be marked as external
// linkage until all TinyGo passes have finished.
var functionsUsedInTransforms = []string{
"runtime.alloc",
"runtime.free",
"runtime.sleepTask",
"runtime.sleepCurrentTask",
"runtime.setTaskStatePtr",
"runtime.getTaskStatePtr",
"runtime.activateTask",
"runtime.scheduler",
"runtime.startGoroutine",
}
// Configure the compiler.
type Config struct {
Triple string // LLVM target triple, e.g. x86_64-unknown-linux-gnu (empty string means default)
@@ -38,11 +53,13 @@ type Config struct {
GOOS string //
GOARCH string //
GC string // garbage collection strategy
PanicStrategy string // panic strategy ("abort" or "trap")
Scheduler string // scheduler implementation ("coroutines" or "tasks")
PanicStrategy string // panic strategy ("print" or "trap")
CFlags []string // cflags to pass to cgo
LDFlags []string // ldflags to pass to cgo
ClangHeaders string // Clang built-in header include path
DumpSSA bool // dump Go SSA, for compiler debugging
VerifyIR bool // run extra checks on the IR
Debug bool // add debug symbols for gdb
GOROOT string // GOROOT
TINYGOROOT string // GOROOT for TinyGo
@@ -173,6 +190,17 @@ func (c *Compiler) selectGC() string {
return "conservative"
}
// selectScheduler picks an appropriate scheduler for the target if none was
// given.
func (c *Compiler) selectScheduler() string {
if c.Scheduler != "" {
// A scheduler was specified in the target description.
return c.Scheduler
}
// Fall back to coroutines, which are supported everywhere.
return "coroutines"
}
// Compile the given package path or .go file path. Return an error when this
// fails (in any stage).
func (c *Compiler) Compile(mainPath string) []error {
@@ -189,6 +217,7 @@ func (c *Compiler) Compile(mainPath string) []error {
if err != nil {
return []error{err}
}
buildTags := append([]string{"tinygo", "gc." + c.selectGC(), "scheduler." + c.selectScheduler()}, c.BuildTags...)
lprogram := &loader.Program{
Build: &build.Context{
GOARCH: c.GOARCH,
@@ -198,7 +227,7 @@ func (c *Compiler) Compile(mainPath string) []error {
CgoEnabled: true,
UseAllFiles: false,
Compiler: "gc", // must be one of the recognized compilers
BuildTags: append([]string{"tinygo", "gc." + c.selectGC()}, c.BuildTags...),
BuildTags: buildTags,
},
OverlayBuild: &build.Context{
GOARCH: c.GOARCH,
@@ -208,7 +237,7 @@ func (c *Compiler) Compile(mainPath string) []error {
CgoEnabled: true,
UseAllFiles: false,
Compiler: "gc", // must be one of the recognized compilers
BuildTags: append([]string{"tinygo", "gc." + c.selectGC()}, c.BuildTags...),
BuildTags: buildTags,
},
OverlayPath: func(path string) string {
// Return the (overlay) import path when it should be overlaid, and
@@ -226,7 +255,7 @@ func (c *Compiler) Compile(mainPath string) []error {
return path
} else if path == "syscall" {
for _, tag := range c.BuildTags {
if tag == "avr" || tag == "cortexm" || tag == "darwin" || tag == "riscv" {
if tag == "baremetal" || tag == "darwin" {
return path
}
}
@@ -335,13 +364,15 @@ func (c *Compiler) Compile(mainPath string) []error {
// would be optimized away.
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.sleepTask").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.ExternalLinkage)
// Make sure these functions are kept in tact during TinyGo transformation passes.
for _, name := range functionsUsedInTransforms {
fn := c.mod.NamedFunction(name)
if fn.IsNil() {
continue
}
fn.SetLinkage(llvm.ExternalLinkage)
}
// Load some attributes
getAttr := func(attrName string) llvm.Attribute {
@@ -533,42 +564,6 @@ func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
}
}
// Return a zero LLVM value for any LLVM type. Setting this value as an
// initializer has the same effect as setting 'zeroinitializer' on a value.
// Sadly, I haven't found a way to do it directly with the Go API but this works
// just fine.
func (c *Compiler) getZeroValue(typ llvm.Type) llvm.Value {
switch typ.TypeKind() {
case llvm.ArrayTypeKind:
subTyp := typ.ElementType()
subVal := c.getZeroValue(subTyp)
vals := make([]llvm.Value, typ.ArrayLength())
for i := range vals {
vals[i] = subVal
}
return llvm.ConstArray(subTyp, vals)
case llvm.FloatTypeKind, llvm.DoubleTypeKind:
return llvm.ConstFloat(typ, 0.0)
case llvm.IntegerTypeKind:
return llvm.ConstInt(typ, 0, false)
case llvm.PointerTypeKind:
return llvm.ConstPointerNull(typ)
case llvm.StructTypeKind:
types := typ.StructElementTypes()
vals := make([]llvm.Value, len(types))
for i, subTyp := range types {
vals[i] = c.getZeroValue(subTyp)
}
if typ.StructName() != "" {
return llvm.ConstNamedStruct(typ, vals)
} else {
return c.ctx.ConstStruct(vals, false)
}
default:
panic("unknown LLVM zero inititializer: " + typ.String())
}
}
// Is this a pointer type of some sort? Can be unsafe.Pointer or any *T pointer.
func isPointer(typ types.Type) bool {
if _, ok := typ.(*types.Pointer); ok {
@@ -791,6 +786,11 @@ func (c *Compiler) parseFuncDecl(f *ir.Function) *Frame {
// External/exported functions may not retain pointer values.
// https://golang.org/cmd/cgo/#hdr-Passing_pointers
if f.IsExported() {
// Set the wasm-import-module attribute if the function's module is set.
if f.Module() != "" {
wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", f.Module())
frame.fn.LLVMFn.AddFunctionAttr(wasmImportModuleAttr)
}
nocaptureKind := llvm.AttributeKindID("nocapture")
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
for i, typ := range paramTypes {
@@ -1030,36 +1030,47 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
case *ssa.Defer:
c.emitDefer(frame, instr)
case *ssa.Go:
if instr.Call.IsInvoke() {
c.addError(instr.Pos(), "todo: go on method receiver")
return
}
callee := instr.Call.StaticCallee()
if callee == nil {
c.addError(instr.Pos(), "todo: go on non-direct function (function pointer, etc.)")
return
}
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 {
params = append(params, c.getValue(frame, param))
}
if !calleeFn.IsExported() {
params = append(params, llvm.Undef(c.i8ptrType)) // context parameter
params = append(params, llvm.Undef(c.i8ptrType)) // parent coroutine handle
}
c.createCall(calleeValue, params, "")
// Start a new goroutine.
if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new
// goroutine.
calleeFn := c.ir.GetFunction(callee)
if !calleeFn.IsExported() && c.selectScheduler() != "tasks" {
// For coroutine scheduling, this is only required when calling
// an external function.
// For tasks, because all params are stored in a single object,
// no unnecessary parameters should be stored anyway.
params = append(params, llvm.Undef(c.i8ptrType)) // context parameter
params = append(params, llvm.ConstPointerNull(c.i8ptrType)) // parent coroutine handle
}
c.emitStartGoroutine(calleeFn.LLVMFn, params)
} else if !instr.Call.IsInvoke() {
// This is a function pointer.
// At the moment, two extra params are passed to the newly started
// goroutine:
// * The function context, for closures.
// * The parent handle (for coroutines) or the function pointer
// itself (for tasks).
funcPtr, context := c.decodeFuncValue(c.getValue(frame, instr.Call.Value), instr.Call.Value.Type().(*types.Signature))
params = append(params, context) // context parameter
switch c.selectScheduler() {
case "coroutines":
params = append(params, llvm.ConstPointerNull(c.i8ptrType)) // parent coroutine handle
case "tasks":
params = append(params, funcPtr)
default:
panic("unknown scheduler type")
}
c.emitStartGoroutine(funcPtr, params)
} else {
c.addError(instr.Pos(), "todo: go on interface call")
}
case *ssa.If:
cond := c.getValue(frame, instr.Cond)
block := instr.Block()
@@ -1086,7 +1097,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
c.builder.CreateRet(c.getValue(frame, instr.Results[0]))
} else {
// Multiple return values. Put them all in a struct.
retVal := c.getZeroValue(frame.fn.LLVMFn.Type().ElementType().ReturnType())
retVal := llvm.ConstNull(frame.fn.LLVMFn.Type().ElementType().ReturnType())
for i, result := range instr.Results {
val := c.getValue(frame, result)
retVal = c.builder.CreateInsertValue(retVal, val, i, "")
@@ -1414,7 +1425,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
} else {
buf := c.createEntryBlockAlloca(typ, expr.Comment)
if c.targetData.TypeAllocSize(typ) != 0 {
c.builder.CreateStore(c.getZeroValue(typ), buf) // zero-initialize var
c.builder.CreateStore(llvm.ConstNull(typ), buf) // zero-initialize var
}
return buf, nil
}
@@ -1650,7 +1661,9 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
}
// Bounds checking.
c.emitSliceBoundsCheck(frame, maxSize, sliceLen, sliceCap, expr.Len.Type().(*types.Basic), expr.Cap.Type().(*types.Basic))
lenType := expr.Len.Type().(*types.Basic)
capType := expr.Cap.Type().(*types.Basic)
c.emitSliceBoundsCheck(frame, maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType)
// Allocate the backing array.
sliceCapCast, err := c.parseConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos())
@@ -1719,18 +1732,15 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
panic("unknown type in range: " + typ.String())
}
it, _, _ := c.createTemporaryAlloca(iteratorType, "range.it")
c.builder.CreateStore(c.getZeroValue(iteratorType), it)
c.builder.CreateStore(llvm.ConstNull(iteratorType), it)
return it, nil
case *ssa.Select:
return c.emitSelect(frame, expr), nil
case *ssa.Slice:
if expr.Max != nil {
return llvm.Value{}, c.makeError(expr.Pos(), "todo: full slice expressions (with max): "+expr.Type().String())
}
value := c.getValue(frame, expr.X)
var lowType, highType *types.Basic
var low, high llvm.Value
var lowType, highType, maxType *types.Basic
var low, high, max llvm.Value
if expr.Low != nil {
lowType = expr.Low.Type().Underlying().(*types.Basic)
@@ -1761,6 +1771,20 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
highType = types.Typ[types.Uintptr]
}
if expr.Max != nil {
maxType = expr.Max.Type().Underlying().(*types.Basic)
max = c.getValue(frame, expr.Max)
if max.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() {
if maxType.Info()&types.IsUnsigned != 0 {
max = c.builder.CreateZExt(max, c.uintptrType, "")
} else {
max = c.builder.CreateSExt(max, c.uintptrType, "")
}
}
} else {
maxType = types.Typ[types.Uintptr]
}
switch typ := expr.X.Type().Underlying().(type) {
case *types.Pointer: // pointer to array
// slice an array
@@ -1769,25 +1793,31 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
if high.IsNil() {
high = llvmLen
}
if max.IsNil() {
max = llvmLen
}
indices := []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
low,
}
c.emitSliceBoundsCheck(frame, llvmLen, low, high, lowType, highType)
c.emitSliceBoundsCheck(frame, llvmLen, low, high, max, lowType, highType, maxType)
// Truncate ints bigger than uintptr. This is after the bounds
// check so it's safe.
if c.targetData.TypeAllocSize(low.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
low = c.builder.CreateTrunc(low, c.uintptrType, "")
}
if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
high = c.builder.CreateTrunc(high, c.uintptrType, "")
}
if c.targetData.TypeAllocSize(low.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
low = c.builder.CreateTrunc(low, c.uintptrType, "")
if c.targetData.TypeAllocSize(max.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
max = c.builder.CreateTrunc(max, c.uintptrType, "")
}
sliceLen := c.builder.CreateSub(high, low, "slice.len")
slicePtr := c.builder.CreateInBoundsGEP(value, indices, "slice.ptr")
sliceCap := c.builder.CreateSub(llvmLen, low, "slice.cap")
sliceCap := c.builder.CreateSub(max, low, "slice.cap")
slice := c.ctx.ConstStruct([]llvm.Value{
llvm.Undef(slicePtr.Type()),
@@ -1807,8 +1837,11 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
if high.IsNil() {
high = oldLen
}
if max.IsNil() {
max = oldCap
}
c.emitSliceBoundsCheck(frame, oldCap, low, high, lowType, highType)
c.emitSliceBoundsCheck(frame, oldCap, low, high, max, lowType, highType, maxType)
// Truncate ints bigger than uintptr. This is after the bounds
// check so it's safe.
@@ -1818,10 +1851,13 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
high = c.builder.CreateTrunc(high, c.uintptrType, "")
}
if c.targetData.TypeAllocSize(max.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
max = c.builder.CreateTrunc(max, c.uintptrType, "")
}
newPtr := c.builder.CreateInBoundsGEP(oldPtr, []llvm.Value{low}, "")
newLen := c.builder.CreateSub(high, low, "")
newCap := c.builder.CreateSub(oldCap, low, "")
newCap := c.builder.CreateSub(max, low, "")
slice := c.ctx.ConstStruct([]llvm.Value{
llvm.Undef(newPtr.Type()),
llvm.Undef(c.uintptrType),
@@ -1837,13 +1873,18 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
return llvm.Value{}, c.makeError(expr.Pos(), "unknown slice type: "+typ.String())
}
// slice a string
if expr.Max != nil {
// This might as well be a panic, as the frontend should have
// handled this already.
return llvm.Value{}, c.makeError(expr.Pos(), "slicing a string with a max parameter is not allowed by the spec")
}
oldPtr := c.builder.CreateExtractValue(value, 0, "")
oldLen := c.builder.CreateExtractValue(value, 1, "")
if high.IsNil() {
high = oldLen
}
c.emitSliceBoundsCheck(frame, oldLen, low, high, lowType, highType)
c.emitSliceBoundsCheck(frame, oldLen, low, high, high, lowType, highType, maxType)
// Truncate ints bigger than uintptr. This is after the bounds
// check so it's safe.
@@ -2273,12 +2314,12 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value {
if expr.Value != nil {
panic("expected nil chan constant")
}
return c.getZeroValue(c.getLLVMType(expr.Type()))
return llvm.ConstNull(c.getLLVMType(expr.Type()))
case *types.Signature:
if expr.Value != nil {
panic("expected nil signature constant")
}
return c.getZeroValue(c.getLLVMType(expr.Type()))
return llvm.ConstNull(c.getLLVMType(expr.Type()))
case *types.Interface:
if expr.Value != nil {
panic("expected nil interface constant")
@@ -2313,7 +2354,7 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value {
panic("non-nil map constant")
}
llvmType := c.getLLVMType(typ)
return c.getZeroValue(llvmType)
return llvm.ConstNull(llvmType)
default:
panic("unknown constant: " + expr.String())
}
@@ -2385,6 +2426,8 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p
switch typeFrom.Elem().(*types.Basic).Kind() {
case types.Byte:
return c.createRuntimeCall("stringFromBytes", []llvm.Value{value}, ""), nil
case types.Rune:
return c.createRuntimeCall("stringFromRunes", []llvm.Value{value}, ""), nil
default:
return llvm.Value{}, c.makeError(pos, "todo: convert to string: "+typeFrom.String())
}
@@ -2503,7 +2546,7 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
unop.X.Type().Underlying().(*types.Pointer).Elem()
if c.targetData.TypeAllocSize(x.Type().ElementType()) == 0 {
// zero-length data
return c.getZeroValue(x.Type().ElementType()), nil
return llvm.ConstNull(x.Type().ElementType()), nil
} else if strings.HasSuffix(unop.X.String(), "$funcaddr") {
// CGo function pointer. The cgo part has rewritten CGo function
// pointers as stub global variables of the form:
@@ -2670,7 +2713,7 @@ func (c *Compiler) ExternalInt64AsPtr() error {
// correct calling convention.
fn.SetLinkage(llvm.InternalLinkage)
fn.SetUnnamedAddr(true)
entryBlock := llvm.AddBasicBlock(externalFn, "entry")
entryBlock := c.ctx.AddBasicBlock(externalFn, "entry")
c.builder.SetInsertPointAtEnd(entryBlock)
var callParams []llvm.Value
if fnType.ReturnType() == int64Type {
+6 -6
View File
@@ -122,7 +122,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
// Make a struct out of the collected values to put in the defer frame.
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFrame := c.getZeroValue(deferFrameType)
deferFrame := llvm.ConstNull(deferFrameType)
for i, value := range values {
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
}
@@ -157,10 +157,10 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
// }
// Create loop.
loophead := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
loop := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
unreachable := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
end := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
loophead := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
loop := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
unreachable := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
end := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
c.builder.CreateBr(loophead)
// Create loop head:
@@ -192,7 +192,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
// Create switch case, for example:
// case 0:
// // run first deferred call
block := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback")
block := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback")
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(i), false), block)
c.builder.SetInsertPointAtEnd(block)
switch callback := callback.(type) {
+94 -78
View File
@@ -152,11 +152,11 @@ func (c *Compiler) LowerFuncValues() {
// There are multiple functions used in a func value that
// implement this signature.
// What we'll do is transform the following:
// rawPtr := runtime.getFuncPtr(fn)
// if func.rawPtr == nil {
// rawPtr := runtime.getFuncPtr(func.ptr)
// if rawPtr == nil {
// runtime.nilPanic()
// }
// result := func.rawPtr(...args, func.context)
// result := rawPtr(...args, func.context)
// into this:
// if false {
// runtime.nilPanic()
@@ -175,95 +175,111 @@ func (c *Compiler) LowerFuncValues() {
// Remove some casts, checks, and the old call which we're going
// to replace.
var funcCall llvm.Value
for _, inttoptr := range getUses(getFuncPtrCall) {
if inttoptr.IsAIntToPtrInst().IsNil() {
for _, callIntPtr := range getUses(getFuncPtrCall) {
if !callIntPtr.IsACallInst().IsNil() && callIntPtr.CalledValue().Name() == "runtime.makeGoroutine" {
for _, inttoptr := range getUses(callIntPtr) {
if inttoptr.IsAIntToPtrInst().IsNil() {
panic("expected a inttoptr")
}
for _, use := range getUses(inttoptr) {
c.addFuncLoweringSwitch(funcID, use, c.emitStartGoroutine, functions)
use.EraseFromParentAsInstruction()
}
inttoptr.EraseFromParentAsInstruction()
}
callIntPtr.EraseFromParentAsInstruction()
continue
}
if callIntPtr.IsAIntToPtrInst().IsNil() {
panic("expected inttoptr")
}
for _, ptrUse := range getUses(inttoptr) {
for _, ptrUse := range getUses(callIntPtr) {
if !ptrUse.IsABitCastInst().IsNil() {
for _, bitcastUse := range getUses(ptrUse) {
if bitcastUse.IsACallInst().IsNil() || bitcastUse.CalledValue().Name() != "runtime.isnil" {
if bitcastUse.IsACallInst().IsNil() || bitcastUse.CalledValue().IsAFunction().IsNil() {
panic("expected a call instruction")
}
switch bitcastUse.CalledValue().Name() {
case "runtime.isnil":
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(c.ctx.Int1Type(), 0, false))
bitcastUse.EraseFromParentAsInstruction()
default:
panic("expected a call to runtime.isnil")
}
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(c.ctx.Int1Type(), 0, false))
bitcastUse.EraseFromParentAsInstruction()
}
ptrUse.EraseFromParentAsInstruction()
} else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == inttoptr {
if !funcCall.IsNil() {
panic("multiple calls on a single runtime.getFuncPtr")
}
funcCall = ptrUse
} else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == callIntPtr {
c.addFuncLoweringSwitch(funcID, ptrUse, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
return c.builder.CreateCall(funcPtr, params, "")
}, functions)
} else {
panic("unexpected getFuncPtrCall")
}
ptrUse.EraseFromParentAsInstruction()
}
}
if funcCall.IsNil() {
panic("expected exactly one call use of a runtime.getFuncPtr")
}
// The block that cannot be reached with correct funcValues (to
// help the optimizer).
c.builder.SetInsertPointBefore(funcCall)
defaultBlock := llvm.AddBasicBlock(funcCall.InstructionParent().Parent(), "func.default")
c.builder.SetInsertPointAtEnd(defaultBlock)
c.builder.CreateUnreachable()
// Create the switch.
c.builder.SetInsertPointBefore(funcCall)
sw := c.builder.CreateSwitch(funcID, defaultBlock, len(functions)+1)
// Split right after the switch. We will need to insert a few
// basic blocks in this gap.
nextBlock := c.splitBasicBlock(sw, llvm.NextBasicBlock(sw.InstructionParent()), "func.next")
// The 0 case, which is actually a nil check.
nilBlock := llvm.InsertBasicBlock(nextBlock, "func.nil")
c.builder.SetInsertPointAtEnd(nilBlock)
c.createRuntimeCall("nilPanic", nil, "")
c.builder.CreateUnreachable()
sw.AddCase(llvm.ConstInt(c.uintptrType, 0, false), nilBlock)
// Gather the list of parameters for every call we're going to
// make.
callParams := make([]llvm.Value, funcCall.OperandsCount()-1)
for i := range callParams {
callParams[i] = funcCall.Operand(i)
}
// If the call produces a value, we need to get it using a PHI
// node.
phiBlocks := make([]llvm.BasicBlock, len(functions))
phiValues := make([]llvm.Value, len(functions))
for i, fn := range functions {
// Insert a switch case.
bb := llvm.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
c.builder.SetInsertPointAtEnd(bb)
result := c.builder.CreateCall(fn.funcPtr, callParams, "")
c.builder.CreateBr(nextBlock)
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(fn.id), false), bb)
phiBlocks[i] = bb
phiValues[i] = result
}
// Create the PHI node so that the call result flows into the
// next block (after the split). This is only necessary when the
// call produced a value.
if funcCall.Type().TypeKind() != llvm.VoidTypeKind {
c.builder.SetInsertPointBefore(nextBlock.FirstInstruction())
phi := c.builder.CreatePHI(funcCall.Type(), "")
phi.AddIncoming(phiValues, phiBlocks)
funcCall.ReplaceAllUsesWith(phi)
}
// Finally, remove the old instructions.
funcCall.EraseFromParentAsInstruction()
for _, inttoptr := range getUses(getFuncPtrCall) {
inttoptr.EraseFromParentAsInstruction()
callIntPtr.EraseFromParentAsInstruction()
}
getFuncPtrCall.EraseFromParentAsInstruction()
}
}
}
}
// addFuncLoweringSwitch creates a new switch on a function ID and inserts calls
// to the newly created direct calls. The funcID is the number to switch on,
// call is the call instruction to replace, and createCall is the callback that
// actually creates the new call. By changing createCall to something other than
// c.builder.CreateCall, instead of calling a function it can start a new
// goroutine for example.
func (c *Compiler) addFuncLoweringSwitch(funcID, call llvm.Value, createCall func(funcPtr llvm.Value, params []llvm.Value) llvm.Value, functions funcWithUsesList) {
// The block that cannot be reached with correct funcValues (to help the
// optimizer).
c.builder.SetInsertPointBefore(call)
defaultBlock := c.ctx.AddBasicBlock(call.InstructionParent().Parent(), "func.default")
c.builder.SetInsertPointAtEnd(defaultBlock)
c.builder.CreateUnreachable()
// Create the switch.
c.builder.SetInsertPointBefore(call)
sw := c.builder.CreateSwitch(funcID, defaultBlock, len(functions)+1)
// Split right after the switch. We will need to insert a few basic blocks
// in this gap.
nextBlock := c.splitBasicBlock(sw, llvm.NextBasicBlock(sw.InstructionParent()), "func.next")
// The 0 case, which is actually a nil check.
nilBlock := c.ctx.InsertBasicBlock(nextBlock, "func.nil")
c.builder.SetInsertPointAtEnd(nilBlock)
c.createRuntimeCall("nilPanic", nil, "")
c.builder.CreateUnreachable()
sw.AddCase(llvm.ConstInt(c.uintptrType, 0, false), nilBlock)
// Gather the list of parameters for every call we're going to make.
callParams := make([]llvm.Value, call.OperandsCount()-1)
for i := range callParams {
callParams[i] = call.Operand(i)
}
// If the call produces a value, we need to get it using a PHI
// node.
phiBlocks := make([]llvm.BasicBlock, len(functions))
phiValues := make([]llvm.Value, len(functions))
for i, fn := range functions {
// Insert a switch case.
bb := c.ctx.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
c.builder.SetInsertPointAtEnd(bb)
result := createCall(fn.funcPtr, callParams)
c.builder.CreateBr(nextBlock)
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(fn.id), false), bb)
phiBlocks[i] = bb
phiValues[i] = result
}
// Create the PHI node so that the call result flows into the
// next block (after the split). This is only necessary when the
// call produced a value.
if call.Type().TypeKind() != llvm.VoidTypeKind {
c.builder.SetInsertPointBefore(nextBlock.FirstInstruction())
phi := c.builder.CreatePHI(call.Type(), "")
phi.AddIncoming(phiValues, phiBlocks)
call.ReplaceAllUsesWith(phi)
}
}
+9 -20
View File
@@ -32,10 +32,15 @@ const (
// funcImplementation picks an appropriate func value implementation for the
// target.
func (c *Compiler) funcImplementation() funcValueImplementation {
if c.GOARCH == "wasm" {
// Always pick the switch implementation, as it allows the use of blocking
// inside a function that is used as a func value.
switch c.selectScheduler() {
case "coroutines":
return funcValueSwitch
} else {
case "tasks":
return funcValueDoubleword
default:
panic("unknown scheduler type")
}
}
@@ -48,7 +53,7 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
// Closure is: {context, function pointer}
funcValueScalar = funcPtr
case funcValueSwitch:
sigGlobal := c.getFuncSignature(sig)
sigGlobal := c.getTypeCode(sig)
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
if funcValueWithSignatureGlobal.IsNil() {
@@ -73,22 +78,6 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
return funcValue
}
// getFuncSignature returns a global for identification of a particular function
// signature. It is used in runtime.funcValueWithSignature and in calls to
// getFuncPtr.
func (c *Compiler) getFuncSignature(sig *types.Signature) llvm.Value {
typeCodeName := getTypeCodeName(sig)
sigGlobalName := "reflect/types.type:" + typeCodeName
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
if sigGlobal.IsNil() {
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
sigGlobal.SetInitializer(llvm.Undef(c.ctx.Int8Type()))
sigGlobal.SetGlobalConstant(true)
sigGlobal.SetLinkage(llvm.InternalLinkage)
}
return sigGlobal
}
// extractFuncScalar returns some scalar that can be used in comparisons. It is
// a cheap operation.
func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value {
@@ -110,7 +99,7 @@ func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
case funcValueSwitch:
llvmSig := c.getRawFuncType(sig)
sigGlobal := c.getFuncSignature(sig)
sigGlobal := c.getTypeCode(sig)
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
default:
+6 -6
View File
@@ -18,7 +18,7 @@ func (c *Compiler) needsStackObjects() bool {
return false
}
for _, tag := range c.BuildTags {
if tag == "cortexm" || tag == "tinygo.riscv" {
if tag == "baremetal" {
return false
}
}
@@ -134,7 +134,7 @@ func (c *Compiler) makeGCStackSlots() bool {
}
stackChainStart := c.mod.NamedGlobal("runtime.stackChainStart")
if !stackChainStart.IsNil() {
stackChainStart.SetInitializer(c.getZeroValue(stackChainStart.Type().ElementType()))
stackChainStart.SetInitializer(llvm.ConstNull(stackChainStart.Type().ElementType()))
stackChainStart.SetGlobalConstant(true)
}
}
@@ -198,7 +198,7 @@ func (c *Compiler) makeGCStackSlots() bool {
panic("stack chain start not found!")
}
stackChainStartType := stackChainStart.Type().ElementType()
stackChainStart.SetInitializer(c.getZeroValue(stackChainStartType))
stackChainStart.SetInitializer(llvm.ConstNull(stackChainStartType))
// Iterate until runtime.trackPointer has no uses left.
for use := trackPointer.FirstUse(); !use.IsNil(); use = trackPointer.FirstUse() {
@@ -303,7 +303,7 @@ func (c *Compiler) makeGCStackSlots() bool {
// Create the stack object at the function entry.
c.builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
stackObject := c.builder.CreateAlloca(stackObjectType, "gc.stackobject")
initialStackObject := c.getZeroValue(stackObjectType)
initialStackObject := llvm.ConstNull(stackObjectType)
numSlots := (c.targetData.TypeAllocSize(stackObjectType) - c.targetData.TypeAllocSize(c.i8ptrType)*2) / uint64(c.targetData.ABITypeAlignment(c.uintptrType))
numSlotsValue := llvm.ConstInt(c.uintptrType, numSlots, false)
initialStackObject = llvm.ConstInsertValue(initialStackObject, numSlotsValue, []uint32{1})
@@ -398,10 +398,10 @@ func (c *Compiler) addGlobalsBitmap() bool {
for i, b := range bitmapBytes {
bitmapValues[len(bitmapBytes)-i-1] = llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false)
}
bitmapArray := llvm.ConstArray(llvm.ArrayType(c.ctx.Int8Type(), len(bitmapBytes)), bitmapValues)
bitmapArray := llvm.ConstArray(c.ctx.Int8Type(), bitmapValues)
bitmapNew := llvm.AddGlobal(c.mod, bitmapArray.Type(), "runtime.trackedGlobalsBitmap.tmp")
bitmapOld := c.mod.NamedGlobal("runtime.trackedGlobalsBitmap")
bitmapOld.ReplaceAllUsesWith(bitmapNew)
bitmapOld.ReplaceAllUsesWith(llvm.ConstBitCast(bitmapNew, bitmapOld.Type()))
bitmapNew.SetInitializer(bitmapArray)
bitmapNew.SetName("runtime.trackedGlobalsBitmap")
+109 -37
View File
@@ -1,5 +1,16 @@
package compiler
// This file implements lowering for the goroutine scheduler. There are two
// scheduler implementations, one based on tasks (like RTOSes and the main Go
// runtime) and one based on a coroutine compiler transformation. The task based
// implementation requires very little work from the compiler but is not very
// portable (in particular, it is very hard if not impossible to support on
// WebAssembly). The coroutine based one requires a lot of work by the compiler
// to implement, but can run virtually anywhere with a single scheduler
// implementation.
//
// The below description is for the coroutine based scheduler.
//
// 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
@@ -62,7 +73,7 @@ package compiler
// llvm.suspend(hdl) // suspend point
// println("some other operation")
// var i *int // allocate space on the stack for the return value
// runtime.setTaskPromisePtr(hdl, &i) // store return value alloca in our coroutine promise
// runtime.setTaskStatePtr(hdl, &i) // store return value alloca in our coroutine promise
// bar(hdl) // await, pass a continuation (hdl) to bar
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
// println("done", *i)
@@ -106,10 +117,69 @@ type asyncFunc struct {
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.
// LowerGoroutines performs some IR transformations necessary to support
// goroutines. It does something different based on whether it uses the
// coroutine or the tasks implementation of goroutines, and whether goroutines
// are necessary at all.
func (c *Compiler) LowerGoroutines() error {
switch c.selectScheduler() {
case "coroutines":
return c.lowerCoroutines()
case "tasks":
return c.lowerTasks()
default:
panic("unknown scheduler type")
}
}
// lowerTasks starts the main goroutine and then runs the scheduler.
// This is enough compiler-level transformation for the task-based scheduler.
func (c *Compiler) lowerTasks() error {
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]
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
if len(getUses(c.mod.NamedFunction("runtime.startGoroutine"))) != 0 {
// Program needs a scheduler. Start main.main as a goroutine and start
// the scheduler.
realMainWrapper := c.createGoroutineStartWrapper(realMain)
c.builder.SetInsertPointBefore(mainCall)
zero := llvm.ConstInt(c.uintptrType, 0, false)
c.createRuntimeCall("startGoroutine", []llvm.Value{realMainWrapper, zero}, "")
c.createRuntimeCall("scheduler", nil, "")
sleep := c.mod.NamedFunction("time.Sleep")
if !sleep.IsNil() {
sleep.ReplaceAllUsesWith(c.mod.NamedFunction("runtime.sleepCurrentTask"))
}
} else {
// Program doesn't need a scheduler. Call main.main directly.
c.builder.SetInsertPointBefore(mainCall)
params := []llvm.Value{
llvm.Undef(c.i8ptrType), // unused context parameter
llvm.Undef(c.i8ptrType), // unused coroutine handle
}
c.createCall(realMain, params, "")
// runtime.Goexit isn't needed so let it be optimized away by
// globalopt.
c.mod.NamedFunction("runtime.Goexit").SetLinkage(llvm.InternalLinkage)
}
mainCall.EraseFromParentAsInstruction()
// main.main was set to external linkage during IR construction. Set it to
// internal linkage to enable interprocedural optimizations.
realMain.SetLinkage(llvm.InternalLinkage)
return nil
}
// lowerCoroutines transforms the IR into one where all blocking functions are
// turned into goroutines and blocking calls into await calls. It also makes
// sure that the first coroutine is started and the coroutine scheduler will be
// run.
func (c *Compiler) lowerCoroutines() error {
needsScheduler, err := c.markAsyncFunctions()
if err != nil {
return err
@@ -144,12 +214,6 @@ func (c *Compiler) LowerGoroutines() error {
// 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.sleepTask").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
return nil
}
@@ -173,9 +237,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
if !sleep.IsNil() {
worklist = append(worklist, sleep)
}
deadlockStub := c.mod.NamedFunction("runtime.deadlockStub")
if !deadlockStub.IsNil() {
worklist = append(worklist, deadlockStub)
deadlock := c.mod.NamedFunction("runtime.deadlock")
if !deadlock.IsNil() {
worklist = append(worklist, deadlock)
}
chanSend := c.mod.NamedFunction("runtime.chanSend")
if !chanSend.IsNil() {
@@ -211,17 +275,25 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// 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 use.IsConstant() && use.Opcode() == llvm.PtrToInt {
for _, call := range getUses(use) {
if call.IsACallInst().IsNil() || call.CalledValue().Name() != "runtime.makeGoroutine" {
return false, errors.New("async function " + f.Name() + " incorrectly used in bitcast, expected runtime.makeGoroutine")
return false, errors.New("async function " + f.Name() + " incorrectly used in ptrtoint, 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.IsConstant() && use.Opcode() == llvm.BitCast {
// Not sure why this const bitcast is here but as long as it
// has no uses it can be ignored, I guess?
// I think it was created for the runtime.isnil check but
// somehow wasn't removed when all these checks are removed.
if len(getUses(use)) == 0 {
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
@@ -250,12 +322,12 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// 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")
// Input param must be const ptrtoint of function.
ptrtoint := use.Operand(0)
if !ptrtoint.IsConstant() || ptrtoint.Opcode() != llvm.PtrToInt {
panic("expected const ptrtoint operand of runtime.makeGoroutine")
}
goroutine := bitcast.Operand(0)
goroutine := ptrtoint.Operand(0)
if _, ok := asyncFuncs[goroutine]; ok {
needsScheduler = true
break
@@ -292,7 +364,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// Transform all async functions into coroutines.
for _, f := range asyncList {
if f == sleep || f == deadlockStub || f == chanSend || f == chanRecv {
if f == sleep || f == deadlock || f == chanSend || f == chanRecv {
continue
}
@@ -309,7 +381,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
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 == deadlockStub || callee == chanSend || callee == chanRecv {
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlock || callee == chanSend || callee == chanRecv {
continue
}
asyncCalls = append(asyncCalls, inst)
@@ -357,7 +429,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
retvalAlloca = c.builder.CreateAlloca(inst.Type(), "coro.retvalAlloca")
c.builder.SetInsertPointBefore(inst)
data := c.builder.CreateBitCast(retvalAlloca, c.i8ptrType, "")
c.createRuntimeCall("setTaskPromisePtr", []llvm.Value{frame.taskHandle, data}, "")
c.createRuntimeCall("setTaskStatePtr", []llvm.Value{frame.taskHandle, data}, "")
}
// Suspend.
@@ -395,7 +467,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
var parentHandle llvm.Value
if f.Linkage() == llvm.ExternalLinkage {
// Exported function.
// Note that getTaskPromisePtr will panic if it is called with
// Note that getTaskStatePtr will panic if it is called with
// a nil pointer, so blocking exported functions that try to
// return anything will not work.
parentHandle = llvm.ConstPointerNull(c.i8ptrType)
@@ -403,7 +475,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
parentHandle = f.LastParam()
if parentHandle.IsNil() || parentHandle.Name() != "parentHandle" {
// sanity check
panic("trying to make exported function async")
panic("trying to make exported function async: " + f.Name())
}
}
@@ -415,7 +487,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// Return this value by writing to the pointer stored in the
// parent handle. The parent coroutine has made an alloca that
// we can write to to store our return value.
returnValuePtr := c.createRuntimeCall("getTaskPromisePtr", []llvm.Value{parentHandle}, "coro.parentData")
returnValuePtr := c.createRuntimeCall("getTaskStatePtr", []llvm.Value{parentHandle}, "coro.parentData")
alloca := c.builder.CreateBitCast(returnValuePtr, llvm.PointerType(inst.Operand(0).Type(), 0), "coro.parentAlloca")
c.builder.CreateStore(inst.Operand(0), alloca)
default:
@@ -494,9 +566,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
sleepCall.EraseFromParentAsInstruction()
}
// Transform calls to runtime.deadlockStub into coroutine suspends (without
// Transform calls to runtime.deadlock into coroutine suspends (without
// resume).
for _, deadlockCall := range getUses(deadlockStub) {
for _, deadlockCall := range getUses(deadlock) {
// deadlockCall must be a call instruction.
frame := asyncFuncs[deadlockCall.InstructionParent().Parent()]
@@ -571,14 +643,14 @@ func (c *Compiler) lowerMakeGoroutineCalls() error {
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
for _, goroutine := range getUses(makeGoroutine) {
bitcastIn := goroutine.Operand(0)
origFunc := bitcastIn.Operand(0)
ptrtointIn := goroutine.Operand(0)
origFunc := ptrtointIn.Operand(0)
uses := getUses(goroutine)
if len(uses) != 1 || uses[0].IsABitCastInst().IsNil() {
return errors.New("expected exactly 1 bitcast use of runtime.makeGoroutine")
if len(uses) != 1 || uses[0].IsAIntToPtrInst().IsNil() {
return errors.New("expected exactly 1 inttoptr use of runtime.makeGoroutine")
}
bitcastOut := uses[0]
uses = getUses(bitcastOut)
inttoptrOut := uses[0]
uses = getUses(inttoptrOut)
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
return errors.New("expected exactly 1 call use of runtime.makeGoroutine bitcast")
}
@@ -593,7 +665,7 @@ func (c *Compiler) lowerMakeGoroutineCalls() error {
c.builder.SetInsertPointBefore(realCall)
c.builder.CreateCall(origFunc, params, "")
realCall.EraseFromParentAsInstruction()
bitcastOut.EraseFromParentAsInstruction()
inttoptrOut.EraseFromParentAsInstruction()
goroutine.EraseFromParentAsInstruction()
}
+135
View File
@@ -0,0 +1,135 @@
package compiler
// This file implements the 'go' keyword to start a new goroutine. See
// goroutine-lowering.go for more details.
import "tinygo.org/x/go-llvm"
// emitStartGoroutine starts a new goroutine with the provided function pointer
// and parameters.
//
// Because a go statement doesn't return anything, return undef.
func (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
switch c.selectScheduler() {
case "tasks":
paramBundle := c.emitPointerPack(params)
paramBundle = c.builder.CreatePtrToInt(paramBundle, c.uintptrType, "")
calleeValue := c.createGoroutineStartWrapper(funcPtr)
c.createRuntimeCall("startGoroutine", []llvm.Value{calleeValue, paramBundle}, "")
case "coroutines":
// We roundtrip through runtime.makeGoroutine as a signal (to find these
// calls) and to break any optimizations LLVM will try to do: they are
// invalid if we called this as a regular function to be updated later.
calleeValue := c.builder.CreatePtrToInt(funcPtr, c.uintptrType, "")
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
calleeValue = c.builder.CreateIntToPtr(calleeValue, funcPtr.Type(), "")
c.createCall(calleeValue, params, "")
default:
panic("unreachable")
}
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
}
// createGoroutineStartWrapper creates a wrapper for the task-based
// implementation of goroutines. For example, to call a function like this:
//
// func add(x, y int) int { ... }
//
// It creates a wrapper like this:
//
// func add$gowrapper(ptr *unsafe.Pointer) {
// args := (*struct{
// x, y int
// })(ptr)
// add(args.x, args.y)
// }
//
// This is useful because the task-based goroutine start implementation only
// allows a single (pointer) argument to the newly started goroutine. Also, it
// ignores the return value because newly started goroutines do not have a
// return value.
func (c *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value {
var wrapper llvm.Value
if !fn.IsAFunction().IsNil() {
// See whether this wrapper has already been created. If so, return it.
name := fn.Name()
wrapper = c.mod.NamedFunction(name + "$gowrapper")
if !wrapper.IsNil() {
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
}
// Save the current position in the IR builder.
currentBlock := c.builder.GetInsertBlock()
defer c.builder.SetInsertPointAtEnd(currentBlock)
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
wrapper.SetLinkage(llvm.PrivateLinkage)
wrapper.SetUnnamedAddr(true)
entry := c.ctx.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(entry)
// Create the list of params for the call.
paramTypes := fn.Type().ElementType().ParamTypes()
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes[:len(paramTypes)-2])
params = append(params, llvm.Undef(c.i8ptrType), llvm.ConstPointerNull(c.i8ptrType))
// Create the call.
c.builder.CreateCall(fn, params, "")
} else {
// For a function pointer like this:
//
// var funcPtr func(x, y int) int
//
// A wrapper like the following is created:
//
// func .gowrapper(ptr *unsafe.Pointer) {
// args := (*struct{
// x, y int
// fn func(x, y int) int
// })(ptr)
// args.fn(x, y)
// }
//
// With a bit of luck, identical wrapper functions like these can be
// merged into one.
// Save the current position in the IR builder.
currentBlock := c.builder.GetInsertBlock()
defer c.builder.SetInsertPointAtEnd(currentBlock)
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, ".gowrapper", wrapperType)
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true)
entry := c.ctx.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(entry)
// Get the list of parameters, with the extra parameters at the end.
paramTypes := fn.Type().ElementType().ParamTypes()
paramTypes[len(paramTypes)-1] = fn.Type() // the last element is the function pointer
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes)
// Get the function pointer.
fnPtr := params[len(params)-1]
// Ignore the last param, which isn't used anymore.
// TODO: avoid this extra "parent handle" parameter in most functions.
params[len(params)-1] = llvm.Undef(c.i8ptrType)
// Create the call.
c.builder.CreateCall(fnPtr, params, "")
}
// Finish the function. Every basic block must end in a terminator, and
// because goroutines never return a value we can simply return void.
c.builder.CreateRetVoid()
// Return a ptrtoint of the wrapper, not the function itself.
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
}
+6 -6
View File
@@ -603,9 +603,9 @@ func (p *lowerInterfacesPass) createInterfaceImplementsFunc(itf *interfaceInfo)
// TODO: debug info
// Create all used basic blocks.
entry := llvm.AddBasicBlock(fn, "entry")
thenBlock := llvm.AddBasicBlock(fn, "then")
elseBlock := llvm.AddBasicBlock(fn, "else")
entry := p.ctx.AddBasicBlock(fn, "entry")
thenBlock := p.ctx.AddBasicBlock(fn, "then")
elseBlock := p.ctx.AddBasicBlock(fn, "else")
// Add all possible types as cases.
p.builder.SetInsertPointAtEnd(entry)
@@ -661,11 +661,11 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
// TODO: debug info
// Create entry block.
entry := llvm.AddBasicBlock(fn, "entry")
entry := p.ctx.AddBasicBlock(fn, "entry")
// Create default block and make it unreachable (which it is, because all
// possible types are checked).
defaultBlock := llvm.AddBasicBlock(fn, "default")
defaultBlock := p.ctx.AddBasicBlock(fn, "default")
p.builder.SetInsertPointAtEnd(defaultBlock)
p.builder.CreateUnreachable()
@@ -684,7 +684,7 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
// Define all possible functions that can be called.
for _, typ := range itf.types {
bb := llvm.AddBasicBlock(fn, typ.name)
bb := p.ctx.AddBasicBlock(fn, typ.name)
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), bb)
// The function we will redirect to when the interface has this type.
+97 -17
View File
@@ -45,27 +45,100 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.
// It returns a pointer to an external global which should be replaced with the
// real type in the interface lowering pass.
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
globalName := "type:" + getTypeCodeName(typ)
globalName := "reflect/types.type:" + getTypeCodeName(typ)
global := c.mod.NamedGlobal(globalName)
if global.IsNil() {
// Create a new typecode global.
global = llvm.AddGlobal(c.mod, c.getLLVMRuntimeType("typecodeID"), globalName)
// Some type classes contain more information for underlying types or
// element types. Store it directly in the typecode global to make
// reflect lowering simpler.
var references llvm.Value
var length int64
switch typ := typ.(type) {
case *types.Named:
references = c.getTypeCode(typ.Underlying())
case *types.Chan:
references = c.getTypeCode(typ.Elem())
case *types.Pointer:
references = c.getTypeCode(typ.Elem())
case *types.Slice:
references = c.getTypeCode(typ.Elem())
case *types.Array:
references = c.getTypeCode(typ.Elem())
length = typ.Len()
case *types.Struct:
// Take a pointer to the typecodeID of the first field (if it exists).
structGlobal := c.makeStructTypeFields(typ)
references = llvm.ConstBitCast(structGlobal, global.Type())
}
if !references.IsNil() {
// Set the 'references' field of the runtime.typecodeID struct.
globalValue := llvm.ConstNull(global.Type().ElementType())
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
}
global.SetInitializer(globalValue)
global.SetLinkage(llvm.PrivateLinkage)
}
global.SetGlobalConstant(true)
}
return global
}
// makeStructTypeFields creates a new global that stores all type information
// related to this struct type, and returns the resulting global. This global is
// actually an array of all the fields in the structs.
func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value {
// The global is an array of runtime.structField structs.
runtimeStructField := c.getLLVMRuntimeType("structField")
structGlobalType := llvm.ArrayType(runtimeStructField, typ.NumFields())
structGlobal := llvm.AddGlobal(c.mod, structGlobalType, "reflect/types.structFields")
structGlobalValue := llvm.ConstNull(structGlobalType)
for i := 0; i < typ.NumFields(); i++ {
fieldGlobalValue := llvm.ConstNull(runtimeStructField)
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, c.getTypeCode(typ.Field(i).Type()), []uint32{0})
fieldName := c.makeGlobalArray([]byte(typ.Field(i).Name()), "reflect/types.structFieldName", c.ctx.Int8Type())
fieldName.SetLinkage(llvm.PrivateLinkage)
fieldName.SetUnnamedAddr(true)
fieldName = llvm.ConstGEP(fieldName, []llvm.Value{
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
})
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
if typ.Tag(i) != "" {
fieldTag := c.makeGlobalArray([]byte(typ.Tag(i)), "reflect/types.structFieldTag", c.ctx.Int8Type())
fieldTag.SetLinkage(llvm.PrivateLinkage)
fieldTag.SetUnnamedAddr(true)
fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
})
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldTag, []uint32{2})
}
if typ.Field(i).Embedded() {
fieldEmbedded := llvm.ConstInt(c.ctx.Int1Type(), 1, false)
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldEmbedded, []uint32{3})
}
structGlobalValue = llvm.ConstInsertValue(structGlobalValue, fieldGlobalValue, []uint32{uint32(i)})
}
structGlobal.SetInitializer(structGlobalValue)
structGlobal.SetUnnamedAddr(true)
structGlobal.SetLinkage(llvm.PrivateLinkage)
return structGlobal
}
// getTypeCodeName returns a name for this type that can be used in the
// interface lowering pass to assign type codes as expected by the reflect
// package. See getTypeCodeNum.
func getTypeCodeName(t types.Type) string {
name := ""
if named, ok := t.(*types.Named); ok {
name = "~" + named.String() + ":"
t = t.Underlying()
}
switch t := t.(type) {
case *types.Named:
return "named:" + t.String()
case *types.Array:
return "array:" + name + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
case *types.Basic:
var kind string
switch t.Kind() {
@@ -108,21 +181,21 @@ func getTypeCodeName(t types.Type) string {
default:
panic("unknown basic type: " + t.Name())
}
return "basic:" + name + kind
return "basic:" + kind
case *types.Chan:
return "chan:" + name + getTypeCodeName(t.Elem())
return "chan:" + getTypeCodeName(t.Elem())
case *types.Interface:
methods := make([]string, t.NumMethods())
for i := 0; i < t.NumMethods(); i++ {
methods[i] = getTypeCodeName(t.Method(i).Type())
}
return "interface:" + name + "{" + strings.Join(methods, ",") + "}"
return "interface:" + "{" + strings.Join(methods, ",") + "}"
case *types.Map:
keyType := getTypeCodeName(t.Key())
elemType := getTypeCodeName(t.Elem())
return "map:" + name + "{" + keyType + "," + elemType + "}"
return "map:" + "{" + keyType + "," + elemType + "}"
case *types.Pointer:
return "pointer:" + name + getTypeCodeName(t.Elem())
return "pointer:" + getTypeCodeName(t.Elem())
case *types.Signature:
params := make([]string, t.Params().Len())
for i := 0; i < t.Params().Len(); i++ {
@@ -132,9 +205,9 @@ func getTypeCodeName(t types.Type) string {
for i := 0; i < t.Results().Len(); i++ {
results[i] = getTypeCodeName(t.Results().At(i).Type())
}
return "func:" + name + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
case *types.Slice:
return "slice:" + name + getTypeCodeName(t.Elem())
return "slice:" + getTypeCodeName(t.Elem())
case *types.Struct:
elems := make([]string, t.NumFields())
if t.NumFields() > 2 && t.Field(0).Name() == "C union" {
@@ -142,9 +215,16 @@ func getTypeCodeName(t types.Type) string {
panic("cgo unions are not allowed in interfaces")
}
for i := 0; i < t.NumFields(); i++ {
elems[i] = getTypeCodeName(t.Field(i).Type())
embedded := ""
if t.Field(i).Embedded() {
embedded = "#"
}
elems[i] = embedded + t.Field(i).Name() + ":" + getTypeCodeName(t.Field(i).Type())
if t.Tag(i) != "" {
elems[i] += "`" + t.Tag(i) + "`"
}
}
return "struct:" + name + "{" + strings.Join(elems, ",") + "}"
return "struct:" + "{" + strings.Join(elems, ",") + "}"
default:
panic("unknown type: " + t.String())
}
@@ -300,7 +380,7 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu
// Continue after the if statement.
c.builder.SetInsertPointAtEnd(nextBlock)
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
phi.AddIncoming([]llvm.Value{c.getZeroValue(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
if expr.CommaOk {
tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple
+47
View File
@@ -1,6 +1,8 @@
package compiler
import (
"reflect"
"tinygo.org/x/go-llvm"
)
@@ -152,3 +154,48 @@ func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicB
return newBlock
}
// makeGlobalArray creates a new LLVM global with the given name and integers as
// contents, and returns the global.
// Note that it is left with the default linkage etc., you should set
// linkage/constant/etc properties yourself.
func (c *Compiler) makeGlobalArray(bufItf interface{}, name string, elementType llvm.Type) llvm.Value {
buf := reflect.ValueOf(bufItf)
globalType := llvm.ArrayType(elementType, buf.Len())
global := llvm.AddGlobal(c.mod, globalType, name)
value := llvm.Undef(globalType)
for i := 0; i < buf.Len(); i++ {
ch := buf.Index(i).Uint()
value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)})
}
global.SetInitializer(value)
return global
}
// getGlobalBytes returns the slice contained in the array of the provided
// global. It can recover the bytes originally created using makeGlobalArray, if
// makeGlobalArray was given a byte slice.
func getGlobalBytes(global llvm.Value) []byte {
value := global.Initializer()
buf := make([]byte, value.Type().ArrayLength())
for i := range buf {
buf[i] = byte(llvm.ConstExtractValue(value, []uint32{uint32(i)}).ZExtValue())
}
return buf
}
// replaceGlobalByteWithArray replaces a global integer type in the module with
// an integer array, using a GEP to make the types match. It is a convenience
// function used for creating reflection sidetables, for example.
func (c *Compiler) replaceGlobalIntWithArray(name string, buf interface{}) llvm.Value {
oldGlobal := c.mod.NamedGlobal(name)
global := c.makeGlobalArray(buf, name+".tmp", oldGlobal.Type().ElementType())
gep := llvm.ConstGEP(global, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
})
oldGlobal.ReplaceAllUsesWith(gep)
oldGlobal.EraseFromParentAsGlobal()
global.SetName(name)
return global
}
+18 -140
View File
@@ -3,6 +3,7 @@ package compiler
import (
"errors"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
)
@@ -22,6 +23,11 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
c.replacePanicsWithTrap() // -panic=trap
}
// run a check of all of our code
if c.VerifyIR {
c.checkModule()
}
// Run function passes for each function.
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
defer funcPasses.Dispose()
@@ -43,9 +49,9 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
goPasses.Run(c.mod)
// Run Go-specific optimization passes.
c.OptimizeMaps()
transform.OptimizeMaps(c.mod)
c.OptimizeStringToBytes()
c.OptimizeAllocs()
transform.OptimizeAllocs(c.mod)
c.LowerInterfaces()
c.LowerFuncValues()
@@ -55,7 +61,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
goPasses.Run(c.mod)
// Run TinyGo-specific interprocedural optimizations.
c.OptimizeAllocs()
transform.OptimizeAllocs(c.mod)
c.OptimizeStringToBytes()
// Lower runtime.isnil calls to regular nil comparisons.
@@ -101,6 +107,15 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
}
}
// After TinyGo-specific transforms have finished, undo exporting these functions.
for _, name := range functionsUsedInTransforms {
fn := c.mod.NamedFunction(name)
if fn.IsNil() {
continue
}
fn.SetLinkage(llvm.InternalLinkage)
}
// 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) {
@@ -144,48 +159,6 @@ func (c *Compiler) replacePanicsWithTrap() {
}
}
// Eliminate created but not used maps.
//
// In the future, this should statically allocate created but never modified
// maps. This has not yet been implemented, however.
func (c *Compiler) OptimizeMaps() {
hashmapMake := c.mod.NamedFunction("runtime.hashmapMake")
if hashmapMake.IsNil() {
// nothing to optimize
return
}
hashmapBinarySet := c.mod.NamedFunction("runtime.hashmapBinarySet")
hashmapStringSet := c.mod.NamedFunction("runtime.hashmapStringSet")
for _, makeInst := range getUses(hashmapMake) {
updateInsts := []llvm.Value{}
unknownUses := false // are there any uses other than setting a value?
for _, use := range getUses(makeInst) {
if use := use.IsACallInst(); !use.IsNil() {
switch use.CalledValue() {
case hashmapBinarySet, hashmapStringSet:
updateInsts = append(updateInsts, use)
default:
unknownUses = true
}
} else {
unknownUses = true
}
}
if !unknownUses {
// This map can be entirely removed, as it is only created but never
// used.
for _, inst := range updateInsts {
inst.EraseFromParentAsInstruction()
}
makeInst.EraseFromParentAsInstruction()
}
}
}
// Transform runtime.stringToBytes(...) calls into const []byte slices whenever
// possible. This optimizes the following pattern:
// w.Write([]byte("foo"))
@@ -236,101 +209,6 @@ func (c *Compiler) OptimizeStringToBytes() {
}
}
// Basic escape analysis: translate runtime.alloc calls into alloca
// instructions.
func (c *Compiler) OptimizeAllocs() {
allocator := c.mod.NamedFunction("runtime.alloc")
if allocator.IsNil() {
// nothing to optimize
return
}
heapallocs := getUses(allocator)
for _, heapalloc := range heapallocs {
nilValue := llvm.Value{}
if heapalloc.Operand(0).IsAConstant() == nilValue {
// Do not allocate variable length arrays on the stack.
continue
}
size := heapalloc.Operand(0).ZExtValue()
if size > 256 {
// The maximum value for a stack allocation.
// TODO: tune this, this is just a random value.
continue
}
// In general the pattern is:
// %0 = call i8* @runtime.alloc(i32 %size)
// %1 = bitcast i8* %0 to type*
// (use %1 only)
// But the bitcast might sometimes be dropped when allocating an *i8.
// The 'bitcast' variable below is thus usually a bitcast of the
// heapalloc but not always.
bitcast := heapalloc // instruction that creates the value
if uses := getUses(heapalloc); len(uses) == 1 && uses[0].IsABitCastInst() != nilValue {
// getting only bitcast use
bitcast = uses[0]
}
if !c.doesEscape(bitcast) {
// Insert alloca in the entry block. Do it here so that mem2reg can
// promote it to a SSA value.
fn := bitcast.InstructionParent().Parent()
c.builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
alignment := c.targetData.ABITypeAlignment(c.i8ptrType)
sizeInWords := (size + uint64(alignment) - 1) / uint64(alignment)
allocaType := llvm.ArrayType(c.ctx.IntType(alignment*8), int(sizeInWords))
alloca := c.builder.CreateAlloca(allocaType, "stackalloc.alloca")
zero := c.getZeroValue(alloca.Type().ElementType())
c.builder.CreateStore(zero, alloca)
stackalloc := c.builder.CreateBitCast(alloca, bitcast.Type(), "stackalloc")
bitcast.ReplaceAllUsesWith(stackalloc)
if heapalloc != bitcast {
bitcast.EraseFromParentAsInstruction()
}
heapalloc.EraseFromParentAsInstruction()
}
}
}
// Very basic escape analysis.
func (c *Compiler) doesEscape(value llvm.Value) bool {
uses := getUses(value)
for _, use := range uses {
nilValue := llvm.Value{}
if use.IsAGetElementPtrInst() != nilValue {
if c.doesEscape(use) {
return true
}
} else if use.IsABitCastInst() != nilValue {
// A bitcast escapes if the casted-to value escapes.
if c.doesEscape(use) {
return true
}
} else if use.IsALoadInst() != nilValue {
// Load does not escape.
} else if use.IsAStoreInst() != nilValue {
// Store only escapes when the value is stored to, not when the
// value is stored into another value.
if use.Operand(0) == value {
return true
}
} else if use.IsACallInst() != nilValue {
if !c.hasFlag(use, value, "nocapture") {
return true
}
} else if use.IsAICmpInst() != nilValue {
// Comparing pointers don't let the pointer escape.
// This is often a compiler-inserted nil check.
} else {
// Unknown instruction, might escape.
return true
}
}
// does not escape
return false
}
// Check whether the given value (which is of pointer type) is never stored to.
func (c *Compiler) isReadOnly(value llvm.Value) bool {
uses := getUses(value)
+407 -74
View File
@@ -1,10 +1,43 @@
package compiler
// This file has some compiler support for run-time reflection using the reflect
// package. In particular, it encodes type information in type codes in such a
// way that the reflect package can decode the type from this information.
// Where needed, it also adds some side tables for looking up more information
// about a type, when that information cannot be stored directly in the type
// code.
//
// Go has 26 different type kinds.
//
// Type kinds are subdivided in basic types (see the list of basicTypes below)
// that are mostly numeric literals and non-basic (or "complex") types that are
// more difficult to encode. These non-basic types come in two forms:
// * Prefix types (pointer, slice, interface, channel): these just add
// something to an existing type. For example, a pointer like *int just adds
// the fact that it's a pointer to an existing type (int).
// These are encoded efficiently by adding a prefix to a type code.
// * Types with multiple fields (struct, array, func, map). All of these have
// multiple fields contained within. Most obviously structs can contain many
// types as fields. Also arrays contain not just the element type but also
// the length parameter which can be any arbitrary number and thus may not
// fit in a type code.
// These types are encoded using side tables.
//
// This distinction is also important for how named types are encoded. At the
// moment, named basic type just get a unique number assigned while named
// non-basic types have their underlying type stored in a sidetable.
import (
"encoding/binary"
"go/ast"
"math/big"
"strings"
"tinygo.org/x/go-llvm"
)
// A list of basic types and their numbers. This list should be kept in sync
// with the list of Kind constants of type.go in the reflect package.
var basicTypes = map[string]int64{
"bool": 1,
"int": 2,
@@ -26,6 +59,72 @@ var basicTypes = map[string]int64{
"unsafeptr": 18,
}
// A list of non-basic types. Adding 19 to this number will give the Kind as
// used in src/reflect/types.go, and it must be kept in sync with that list.
var nonBasicTypes = map[string]int64{
"chan": 0,
"interface": 1,
"pointer": 2,
"slice": 3,
"array": 4,
"func": 5,
"map": 6,
"struct": 7,
}
// typeCodeAssignmentState keeps some global state around for type code
// assignments, used to assign one unique type code to each Go type.
type typeCodeAssignmentState struct {
// An integer that's incremented each time it's used to give unique IDs to
// type codes that are not yet fully supported otherwise by the reflect
// package (or are simply unused in the compiled program).
fallbackIndex int
// This is the length of an uintptr. Only used occasionally to know whether
// a given number can be encoded as a varint.
uintptrLen int
// Map of named types to their type code. It is important that named types
// get unique IDs for each type.
namedBasicTypes map[string]int
namedNonBasicTypes map[string]int
// Map of array types to their type code.
arrayTypes map[string]int
arrayTypesSidetable []byte
needsArrayTypesSidetable bool
// Map of struct types to their type code.
structTypes map[string]int
structTypesSidetable []byte
needsStructNamesSidetable bool
// Map of struct names and tags to their name string.
structNames map[string]int
structNamesSidetable []byte
needsStructTypesSidetable bool
// This byte array is stored in reflect.namedNonBasicTypesSidetable and is
// used at runtime to get details about a named non-basic type.
// Entries are varints (see makeVarint below and readVarint in
// reflect/sidetables.go for the encoding): one varint per entry. The
// integers in namedNonBasicTypes are indices into this array. Because these
// are varints, most type codes are really small (just one byte).
//
// Note that this byte buffer is not created when it is not needed
// (reflect.namedNonBasicTypesSidetable has no uses), see
// needsNamedTypesSidetable.
namedNonBasicTypesSidetable []uint64
// This indicates whether namedNonBasicTypesSidetable needs to be created at
// all. If it is false, namedNonBasicTypesSidetable will contain simple
// monotonically increasing numbers.
needsNamedNonBasicTypesSidetable bool
}
// assignTypeCodes is used to assign a type code to each type in the program
// that is ever stored in an interface. It tries to use the smallest possible
// numbers to make the code that works with interfaces as small as possible.
func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
fn := c.mod.NamedFunction("reflect.ValueOf")
if fn.IsNil() {
@@ -38,13 +137,21 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
}
// Assign typecodes the way the reflect package expects.
fallbackIndex := 1
namedTypes := make(map[string]int)
state := typeCodeAssignmentState{
fallbackIndex: 1,
uintptrLen: c.uintptrType.IntTypeWidth(),
namedBasicTypes: make(map[string]int),
namedNonBasicTypes: make(map[string]int),
arrayTypes: make(map[string]int),
structTypes: make(map[string]int),
structNames: make(map[string]int),
needsNamedNonBasicTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.namedNonBasicTypesSidetable"))) != 0,
needsStructTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structTypesSidetable"))) != 0,
needsStructNamesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structNamesSidetable"))) != 0,
needsArrayTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0,
}
for _, t := range typeSlice {
if t.name[:5] != "type:" {
panic("expected type name to start with 'type:'")
}
num := c.getTypeCodeNum(t.name[5:], &fallbackIndex, namedTypes)
num := state.getTypeCodeNum(t.typecode)
if num.BitLen() > c.uintptrType.IntTypeWidth() || !num.IsUint64() {
// TODO: support this in some way, using a side table for example.
// That's less efficient but better than not working at all.
@@ -54,25 +161,41 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
}
t.num = num.Uint64()
}
// Only create this sidetable when it is necessary.
if state.needsNamedNonBasicTypesSidetable {
global := c.replaceGlobalIntWithArray("reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
}
if state.needsArrayTypesSidetable {
global := c.replaceGlobalIntWithArray("reflect.arrayTypesSidetable", state.arrayTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
}
if state.needsStructTypesSidetable {
global := c.replaceGlobalIntWithArray("reflect.structTypesSidetable", state.structTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
}
if state.needsStructNamesSidetable {
global := c.replaceGlobalIntWithArray("reflect.structNamesSidetable", state.structNamesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
}
}
// getTypeCodeNum returns the typecode for a given type as expected by the
// reflect package. Also see getTypeCodeName, which serializes types to a string
// based on a types.Type value for this function.
func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[string]int) *big.Int {
func (state *typeCodeAssignmentState) getTypeCodeNum(typecode llvm.Value) *big.Int {
// Note: see src/reflect/type.go for bit allocations.
// A type can be named or unnamed. Example of both:
// basic:~foo:uint64
// basic:uint64
// Extract the class (basic, slice, pointer, etc.), the name, and the
// contents of this type ID string. Allocate bits based on that, as
// src/runtime/types.go expects.
class := id[:strings.IndexByte(id, ':')]
value := id[len(class)+1:]
class, value := getClassAndValueFromTypeCode(typecode)
name := ""
if value[0] == '~' {
name = value[1:strings.IndexByte(value, ':')]
value = value[len(name)+2:]
if class == "named" {
name = value
typecode = llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
class, value = getClassAndValueFromTypeCode(typecode)
}
if class == "basic" {
// Basic types follow the following bit pattern:
@@ -81,76 +204,286 @@ func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[
// upper bits are used to indicate the named type.
num, ok := basicTypes[value]
if !ok {
panic("invalid basic type: " + id)
panic("invalid basic type: " + value)
}
if name != "" {
// This type is named, set the upper bits to the name ID.
num |= int64(getNamedTypeNum(namedTypes, name)) << 5
num |= int64(state.getBasicNamedTypeNum(name)) << 5
}
return big.NewInt(num << 1)
} else {
// Complex types use the following bit pattern:
// Non-baisc types use the following bit pattern:
// ...nxxx1
// where xxx indicates the complex type (any non-basic type). The upper
// bits contain whatever the type contains. Types that wrap a single
// other type (channel, interface, pointer, slice) just contain the bits
// of the wrapped type. Other types (like struct) have a different
// method of encoding the contents of the type.
var num *big.Int
// where xxx indicates the non-basic type. The upper bits contain
// whatever the type contains. Types that wrap a single other type
// (channel, interface, pointer, slice) just contain the bits of the
// wrapped type. Other types (like struct) need more fields and thus
// cannot be encoded as a simple prefix.
var classNumber int64
switch class {
case "chan":
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
classNumber = 0
case "interface":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 1
case "pointer":
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
classNumber = 2
case "slice":
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
classNumber = 3
case "array":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 4
case "func":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 5
case "map":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 6
case "struct":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 7
default:
panic("unknown type kind: " + id)
}
if name == "" {
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1))
if n, ok := nonBasicTypes[class]; ok {
classNumber = n
} else {
// TODO: store num in a sidetable
num = big.NewInt(int64(getNamedTypeNum(namedTypes, name))<<1 | 1)
num.Lsh(num, 4).Or(num, big.NewInt((classNumber<<1)+1))
panic("unknown type kind: " + class)
}
var num *big.Int
lowBits := (classNumber << 1) + 1 // the 5 low bits of the typecode
if name == "" {
num = state.getNonBasicTypeCode(class, typecode)
} else {
// We must return a named type here. But first check whether it
// has already been defined.
if index, ok := state.namedNonBasicTypes[name]; ok {
num := big.NewInt(int64(index))
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1+(1<<4)))
return num
}
lowBits |= 1 << 4 // set the 'n' bit (see above)
if !state.needsNamedNonBasicTypesSidetable {
// Use simple small integers in this case, to make these numbers
// smaller.
index := len(state.namedNonBasicTypes) + 1
state.namedNonBasicTypes[name] = index
num = big.NewInt(int64(index))
} else {
// We need to store full type information.
// First allocate a number in the named non-basic type
// sidetable.
index := len(state.namedNonBasicTypesSidetable)
state.namedNonBasicTypesSidetable = append(state.namedNonBasicTypesSidetable, 0)
state.namedNonBasicTypes[name] = index
// Get the typecode of the underlying type (which could be the
// element type in the case of pointers, for example).
num = state.getNonBasicTypeCode(class, typecode)
if num.BitLen() > state.uintptrLen || !num.IsUint64() {
panic("cannot store value in sidetable")
}
// Now update the side table with the number we just
// determined. We need this multi-step approach to avoid stack
// overflow due to adding types recursively in the case of
// linked lists (a pointer which points to a struct that
// contains that same pointer).
state.namedNonBasicTypesSidetable[index] = num.Uint64()
num = big.NewInt(int64(index))
}
}
// Concatenate the 'num' and 'lowBits' bitstrings.
num.Lsh(num, 5).Or(num, big.NewInt(lowBits))
return num
}
}
// getNamedTypeNum returns an appropriate (unique) number for the given named
// type. If the name already has a number that number is returned, else a new
// number is returned. The number is always non-zero.
func getNamedTypeNum(namedTypes map[string]int, name string) int {
if num, ok := namedTypes[name]; ok {
return num
} else {
num = len(namedTypes) + 1
namedTypes[name] = num
// getNonBasicTypeCode is used by getTypeCodeNum. It returns the upper bits of
// the type code used there in the type code.
func (state *typeCodeAssignmentState) getNonBasicTypeCode(class string, typecode llvm.Value) *big.Int {
switch class {
case "chan", "pointer", "slice":
// Prefix-style type kinds. The upper bits contain the element type.
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
return state.getTypeCodeNum(sub)
case "array":
// An array is basically a pair of (typecode, length) stored in a
// sidetable.
return big.NewInt(int64(state.getArrayTypeNum(typecode)))
case "struct":
// More complicated type kind. The upper bits contain the index to the
// struct type in the struct types sidetable.
return big.NewInt(int64(state.getStructTypeNum(typecode)))
default:
// Type has not yet been implemented, so fall back by using a unique
// number.
num := big.NewInt(int64(state.fallbackIndex))
state.fallbackIndex++
return num
}
}
// getClassAndValueFromTypeCode takes a typecode (a llvm.Value of type
// runtime.typecodeID), looks at the name, and extracts the typecode class and
// value from it. For example, for a typecode with the following name:
// reflect/types.type:pointer:named:reflect.ValueError
// It extracts:
// class = "pointer"
// value = "named:reflect.ValueError"
func getClassAndValueFromTypeCode(typecode llvm.Value) (class, value string) {
typecodeName := typecode.Name()
const prefix = "reflect/types.type:"
if !strings.HasPrefix(typecodeName, prefix) {
panic("unexpected typecode name: " + typecodeName)
}
id := typecodeName[len(prefix):]
class = id[:strings.IndexByte(id, ':')]
value = id[len(class)+1:]
return
}
// getBasicNamedTypeNum returns an appropriate (unique) number for the given
// named type. If the name already has a number that number is returned, else a
// new number is returned. The number is always non-zero.
func (state *typeCodeAssignmentState) getBasicNamedTypeNum(name string) int {
if num, ok := state.namedBasicTypes[name]; ok {
return num
}
num := len(state.namedBasicTypes) + 1
state.namedBasicTypes[name] = num
return num
}
// getArrayTypeNum returns the array type number, which is an index into the
// reflect.arrayTypesSidetable or a unique number for this type if this table is
// not used.
func (state *typeCodeAssignmentState) getArrayTypeNum(typecode llvm.Value) int {
name := typecode.Name()
if num, ok := state.arrayTypes[name]; ok {
// This array type already has an entry in the sidetable. Don't store
// it twice.
return num
}
if !state.needsArrayTypesSidetable {
// We don't need array sidetables, so we can just assign monotonically
// increasing numbers to each array type.
num := len(state.arrayTypes)
state.arrayTypes[name] = num
return num
}
elemTypeCode := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
elemTypeNum := state.getTypeCodeNum(elemTypeCode)
if elemTypeNum.BitLen() > state.uintptrLen || !elemTypeNum.IsUint64() {
// TODO: make this a regular error
panic("array element type has a type code that is too big")
}
// The array side table is a sequence of {element type, array length}.
arrayLength := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1}).ZExtValue()
buf := makeVarint(elemTypeNum.Uint64())
buf = append(buf, makeVarint(arrayLength)...)
index := len(state.arrayTypesSidetable)
state.arrayTypes[name] = index
state.arrayTypesSidetable = append(state.arrayTypesSidetable, buf...)
return index
}
// getStructTypeNum returns the struct type number, which is an index into
// reflect.structTypesSidetable or an unique number for every struct if this
// sidetable is not needed in the to-be-compiled program.
func (state *typeCodeAssignmentState) getStructTypeNum(typecode llvm.Value) int {
name := typecode.Name()
if num, ok := state.structTypes[name]; ok {
// This struct already has an assigned type code.
return num
}
if !state.needsStructTypesSidetable {
// We don't need struct sidetables, so we can just assign monotonically
// increasing numbers to each struct type.
num := len(state.structTypes)
state.structTypes[name] = num
return num
}
// Get the fields this struct type contains.
// The struct number will be the start index of
structTypeGlobal := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0}).Operand(0).Initializer()
numFields := structTypeGlobal.Type().ArrayLength()
// The first data that is stored in the struct sidetable is the number of
// fields this struct contains. This is usually just a single byte because
// most structs don't contain that many fields, but make it a varint just
// to be sure.
buf := makeVarint(uint64(numFields))
// Iterate over every field in the struct.
// Every field is stored sequentially in the struct sidetable. Fields can
// be retrieved from this list of fields at runtime by iterating over all
// of them until the right field has been found.
// Perhaps adding some index would speed things up, but it would also make
// the sidetable bigger.
for i := 0; i < numFields; i++ {
// Collect some information about this field.
field := llvm.ConstExtractValue(structTypeGlobal, []uint32{uint32(i)})
nameGlobal := llvm.ConstExtractValue(field, []uint32{1})
if nameGlobal == llvm.ConstPointerNull(nameGlobal.Type()) {
panic("compiler: no name for this struct field")
}
fieldNameBytes := getGlobalBytes(nameGlobal.Operand(0))
fieldNameNumber := state.getStructNameNumber(fieldNameBytes)
// See whether this struct field has an associated tag, and if so,
// store that tag in the tags sidetable.
tagGlobal := llvm.ConstExtractValue(field, []uint32{2})
hasTag := false
tagNumber := 0
if tagGlobal != llvm.ConstPointerNull(tagGlobal.Type()) {
hasTag = true
tagBytes := getGlobalBytes(tagGlobal.Operand(0))
tagNumber = state.getStructNameNumber(tagBytes)
}
// The 'embedded' or 'anonymous' flag for this field.
embedded := llvm.ConstExtractValue(field, []uint32{3}).ZExtValue() != 0
// The first byte in the struct types sidetable is a flags byte with
// two bits in it.
flagsByte := byte(0)
if embedded {
flagsByte |= 1
}
if hasTag {
flagsByte |= 2
}
if ast.IsExported(string(fieldNameBytes)) {
flagsByte |= 4
}
buf = append(buf, flagsByte)
// Get the type number and add it to the buffer.
// All fields have a type, so include it directly here.
typeNum := state.getTypeCodeNum(llvm.ConstExtractValue(field, []uint32{0}))
if typeNum.BitLen() > state.uintptrLen || !typeNum.IsUint64() {
// TODO: make this a regular error
panic("struct field has a type code that is too big")
}
buf = append(buf, makeVarint(typeNum.Uint64())...)
// Add the name.
buf = append(buf, makeVarint(uint64(fieldNameNumber))...)
// Add the tag, if there is one.
if hasTag {
buf = append(buf, makeVarint(uint64(tagNumber))...)
}
}
num := len(state.structTypesSidetable)
state.structTypes[name] = num
state.structTypesSidetable = append(state.structTypesSidetable, buf...)
return num
}
// getStructNameNumber stores this string (name or tag) onto the struct names
// sidetable. The format is a varint of the length of the struct, followed by
// the raw bytes of the name. Multiple identical strings are stored under the
// same name for space efficiency.
func (state *typeCodeAssignmentState) getStructNameNumber(nameBytes []byte) int {
name := string(nameBytes)
if n, ok := state.structNames[name]; ok {
// This name was used before, re-use it now (for space efficiency).
return n
}
// This name is not yet in the names sidetable. Add it now.
n := len(state.structNamesSidetable)
state.structNames[name] = n
state.structNamesSidetable = append(state.structNamesSidetable, makeVarint(uint64(len(nameBytes)))...)
state.structNamesSidetable = append(state.structNamesSidetable, nameBytes...)
return n
}
// makeVarint is a small helper function that returns the bytes of the number in
// varint encoding.
func makeVarint(n uint64) []byte {
buf := make([]byte, binary.MaxVarintLen64)
return buf[:binary.PutUvarint(buf, n)]
}
+1 -1
View File
@@ -61,7 +61,7 @@ func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value {
llvmType := c.getLLVMType(g.Type().(*types.Pointer).Elem())
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
if !info.extern {
llvmGlobal.SetInitializer(c.getZeroValue(llvmType))
llvmGlobal.SetInitializer(llvm.ConstNull(llvmType))
llvmGlobal.SetLinkage(llvm.InternalLinkage)
}
}
+28 -2
View File
@@ -58,6 +58,32 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = c.builder.CreateCall(target, args, "")
case c.GOARCH == "386" && c.GOOS == "linux":
// Sources:
// syscall(2) man page
// https://stackoverflow.com/a/2538212
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#int_0x80
args := []llvm.Value{num}
argTypes := []llvm.Type{c.uintptrType}
// Constraints will look something like:
// "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}"
constraints := "={eax},0"
for i, arg := range call.Args[1:] {
constraints += "," + [...]string{
"{ebx}",
"{ecx}",
"{edx}",
"{esi}",
"{edi}",
"{ebp}",
}[i]
llvmValue := c.getValue(frame, arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = c.builder.CreateCall(target, args, "")
case c.GOARCH == "arm" && c.GOOS == "linux":
// Implement the EABI system call convention for Linux.
// Source: syscall(2) man page.
@@ -139,7 +165,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096
hasError := c.builder.CreateAnd(inrange1, inrange2, "")
errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError")
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
@@ -155,7 +181,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
zero := llvm.ConstInt(c.uintptrType, 0, false)
hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError")
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
+1 -1
View File
@@ -101,7 +101,7 @@ func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []l
for i, valueType := range valueTypes {
if c.targetData.TypeAllocSize(valueType) == 0 {
// This value has length zero, so there's nothing to load.
values[i] = c.getZeroValue(valueType)
values[i] = llvm.ConstNull(valueType)
continue
}
indices := []llvm.Value{
+1 -1
View File
@@ -6,5 +6,5 @@ require (
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add
)
+2
View File
@@ -12,3 +12,5 @@ golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef/go.mod h1:9Yl7xja0Znq3iFh3
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261 h1:rJS2Hga39YAnm7DE4qrPm6Dr/67EOojL0XPzvbEeBiw=
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add h1:dFjMH1sLhYADg8UQm7DB56B7e+TfvAmWmEZLhyv3r/w=
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
+4 -4
View File
@@ -86,7 +86,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
case !inst.IsAAllocaInst().IsNil():
allocType := inst.Type().ElementType()
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloca")
alloca.SetInitializer(getZeroValue(allocType))
alloca.SetInitializer(llvm.ConstNull(allocType))
alloca.SetLinkage(llvm.InternalLinkage)
fr.locals[inst] = &LocalValue{
Underlying: alloca,
@@ -253,7 +253,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
allocType = llvm.ArrayType(allocType, elementCount)
}
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloc")
alloc.SetInitializer(getZeroValue(allocType))
alloc.SetInitializer(llvm.ConstNull(allocType))
alloc.SetLinkage(llvm.InternalLinkage)
result := &LocalValue{
Underlying: alloc,
@@ -312,7 +312,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
stringType := fr.Mod.GetTypeByName("runtime._string")
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
retLen := llvm.ConstInt(stringType.StructElementTypes()[1], uint64(len(result)), false)
ret := getZeroValue(stringType)
ret := llvm.ConstNull(stringType)
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0})
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1})
fr.locals[inst] = &LocalValue{fr.Eval, ret}
@@ -335,7 +335,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
sliceType := inst.Type()
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
retLen := llvm.ConstInt(sliceType.StructElementTypes()[1], uint64(len(result)), false)
ret := getZeroValue(sliceType)
ret := llvm.ConstNull(sliceType)
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0}) // ptr
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
-44
View File
@@ -16,50 +16,6 @@ func getUses(value llvm.Value) []llvm.Value {
return uses
}
// Return a zero LLVM value for any LLVM type. Setting this value as an
// initializer has the same effect as setting 'zeroinitializer' on a value.
// Sadly, I haven't found a way to do it directly with the Go API but this works
// just fine.
func getZeroValue(typ llvm.Type) llvm.Value {
switch typ.TypeKind() {
case llvm.ArrayTypeKind:
subTyp := typ.ElementType()
subVal := getZeroValue(subTyp)
vals := make([]llvm.Value, typ.ArrayLength())
for i := range vals {
vals[i] = subVal
}
return llvm.ConstArray(subTyp, vals)
case llvm.FloatTypeKind, llvm.DoubleTypeKind:
return llvm.ConstFloat(typ, 0.0)
case llvm.IntegerTypeKind:
return llvm.ConstInt(typ, 0, false)
case llvm.PointerTypeKind:
return llvm.ConstPointerNull(typ)
case llvm.StructTypeKind:
types := typ.StructElementTypes()
vals := make([]llvm.Value, len(types))
for i, subTyp := range types {
val := getZeroValue(subTyp)
vals[i] = val
}
if typ.StructName() != "" {
return llvm.ConstNamedStruct(typ, vals)
} else {
return typ.Context().ConstStruct(vals, false)
}
case llvm.VectorTypeKind:
zero := getZeroValue(typ.ElementType())
vals := make([]llvm.Value, typ.VectorSize())
for i := range vals {
vals[i] = zero
}
return llvm.ConstVector(vals, false)
default:
panic("interp: unknown LLVM type: " + typ.String())
}
}
// getStringBytes loads the byte slice of a Go string represented as a
// {ptr, len} pair.
func getStringBytes(strPtr Value, strLen llvm.Value) []byte {
+2 -2
View File
@@ -162,7 +162,7 @@ func (v *MapValue) newBucket() llvm.Value {
llvm.ArrayType(v.KeyType, 8), // key type
llvm.ArrayType(v.ValueType, 8), // value type
}, false)
bucketValue := getZeroValue(bucketType)
bucketValue := llvm.ConstNull(bucketType)
bucket := llvm.AddGlobal(v.Eval.Mod, bucketType, v.PkgName+"$mapbucket")
bucket.SetInitializer(bucketValue)
bucket.SetLinkage(llvm.InternalLinkage)
@@ -311,7 +311,7 @@ func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) {
keyType := v.Eval.Mod.GetTypeByName("runtime._string")
v.KeyType = keyType
key := getZeroValue(keyType)
key := llvm.ConstNull(keyType)
key = llvm.ConstInsertValue(key, keyBuf.Value(), []uint32{0})
key = llvm.ConstInsertValue(key, keyLen.Value(), []uint32{1})
+12
View File
@@ -28,6 +28,7 @@ type Program struct {
type Function struct {
*ssa.Function
LLVMFn llvm.Value
module string // go:wasm-module
linkName string // go:linkname, go:export, go:interrupt
exported bool // go:export
nobounds bool // go:nobounds
@@ -229,6 +230,12 @@ func (f *Function) parsePragmas() {
}
f.linkName = parts[1]
f.exported = true
case "//go:wasm-module":
// Alternative comment for setting the import module.
if len(parts) != 2 {
continue
}
f.module = parts[1]
case "//go:inline":
f.inline = InlineHint
case "//go:noinline":
@@ -291,6 +298,11 @@ func (f *Function) Inline() InlineType {
return f.inline
}
// Return the module name if not the default.
func (f *Function) Module() string {
return f.module
}
// Return the link name for this function.
func (f *Function) LinkName() string {
if f.linkName != "" {
+15 -9
View File
@@ -47,8 +47,10 @@ type BuildConfig struct {
opt string
gc string
panicStrategy string
scheduler string
printIR bool
dumpSSA bool
verifyIR bool
debug bool
printSizes string
cFlags []string
@@ -88,8 +90,8 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
if err != nil {
return fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
}
if major != 1 {
return fmt.Errorf("expected major version 1, got go%d.%d", major, minor)
if major != 1 || (minor != 11 && minor != 12) {
return fmt.Errorf("requires go version 1.11 or 1.12, got go%d.%d", major, minor)
}
for i := 1; i <= minor; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i))
@@ -97,6 +99,10 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
if extraTags := strings.Fields(config.tags); len(extraTags) != 0 {
tags = append(tags, extraTags...)
}
scheduler := spec.Scheduler
if config.scheduler != "" {
scheduler = config.scheduler
}
compilerConfig := compiler.Config{
Triple: spec.Triple,
CPU: spec.CPU,
@@ -105,11 +111,13 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
GOARCH: spec.GOARCH,
GC: config.gc,
PanicStrategy: config.panicStrategy,
Scheduler: scheduler,
CFlags: cflags,
LDFlags: ldflags,
ClangHeaders: getClangHeaderPath(root),
Debug: config.debug,
DumpSSA: config.dumpSSA,
VerifyIR: config.verifyIR,
TINYGOROOT: root,
GOROOT: goroot,
GOPATH: getGopath(),
@@ -148,9 +156,6 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
if spec.GOOS != "darwin" {
c.ApplyFunctionSections() // -ffunction-sections
}
if err := c.Verify(); err != nil {
return errors.New("verification error after applying function sections")
}
// Browsers cannot handle external functions that have type i64 because it
// cannot be represented exactly in JavaScript (JS only has doubles). To
@@ -162,9 +167,6 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
if err != nil {
return err
}
if err := c.Verify(); err != nil {
return errors.New("verification error after running the wasm i64 hack")
}
}
// Optimization levels here are roughly the same as Clang, but probably not
@@ -617,9 +619,11 @@ func main() {
outpath := flag.String("o", "", "output filename")
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
panicStrategy := flag.String("panic", "print", "panic strategy (abort, trap)")
panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)")
scheduler := flag.String("scheduler", "", "which scheduler to use (coroutines, tasks)")
printIR := flag.Bool("printir", false, "print LLVM IR")
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
verifyIR := flag.Bool("verifyir", false, "run extra verification steps on LLVM IR")
tags := flag.String("tags", "", "a space-separated list of extra build tags")
target := flag.String("target", "", "LLVM target | .json file with TargetSpec")
printSize := flag.String("size", "", "print sizes (none, short, full)")
@@ -643,8 +647,10 @@ func main() {
opt: *opt,
gc: *gc,
panicStrategy: *panicStrategy,
scheduler: *scheduler,
printIR: *printIR,
dumpSSA: *dumpSSA,
verifyIR: *verifyIR,
debug: !*nodebug,
printSizes: *printSize,
tags: *tags,
+1
View File
@@ -116,6 +116,7 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
opt: "z",
printIR: false,
dumpSSA: false,
verifyIR: true,
debug: false,
printSizes: "",
wasmAbi: "js",
+35
View File
@@ -73,8 +73,32 @@ func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
const (
SCS_BASE = 0xE000E000
NVIC_BASE = SCS_BASE + 0x0100
SCB_BASE = SCS_BASE + 0x0D00
)
const (
SCB_AIRCR_VECTKEY_Pos = 16
SCB_AIRCR_SYSRESETREQ_Pos = 2
SCB_AIRCR_SYSRESETREQ_Msk = 1 << SCB_AIRCR_SYSRESETREQ_Pos
)
// System Control Block (SCB)
//
// SCB_Type provides the definitions for the System Control Block Registers.
type SCB_Type struct {
CPUID volatile.Register32 // CPUID Base Register
ICSR volatile.Register32 // Interrupt Control and State Register
VTOR volatile.Register32 // Vector Table Offset Register
AIRCR volatile.Register32 // Application Interrupt and Reset Control Register
SCR volatile.Register32 // System Control Register
CCR volatile.Register32 // Configuration Control Register
_ volatile.Register32 // RESERVED1;
SHP [2]volatile.Register32 // System Handlers Priority Registers. [0] is RESERVED
SHCSR volatile.Register32 // System Handler Control and State Register
}
var SCB = (*SCB_Type)(unsafe.Pointer(uintptr(SCB_BASE)))
// Nested Vectored Interrupt Controller (NVIC).
//
// Source:
@@ -134,3 +158,14 @@ func DisableInterrupts() uintptr {
func EnableInterrupts(mask uintptr) {
Asm("cpsie if")
}
// SystemReset performs a hard system reset.
func SystemReset() {
// SCB->AIRCR = ((0x5FA << SCB_AIRCR_VECTKEY_Pos) |
// SCB_AIRCR_SYSRESETREQ_Msk);
SCB.AIRCR.Set((0x5FA << SCB_AIRCR_VECTKEY_Pos) | SCB_AIRCR_SYSRESETREQ_Msk)
for {
Asm("wfi")
}
}
+21
View File
@@ -0,0 +1,21 @@
package main
// Draw a red square on the GameBoy Advance screen.
import (
"image/color"
"machine"
)
var display = machine.Display
func main() {
display.Configure()
for x := int16(30); x < 50; x++ {
for y := int16(80); y < 100; y++ {
display.SetPixel(x, y, color.RGBA{255, 0, 0, 255})
}
}
display.Display()
}
+11
View File
@@ -1,3 +1,8 @@
invoke: clean wasm_exec
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./invoke/wasm.go
cp ./invoke/wasm.js ./html/
cp ./invoke/index.html ./html/
export: clean wasm_exec
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./export/wasm.go
cp ./export/wasm.js ./html/
@@ -8,6 +13,12 @@ callback: clean wasm_exec
cp ./callback/wasm.js ./html/
cp ./callback/index.html ./html/
slices: clean wasm_exec
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./slices/wasm.go
cp ./slices/wasm.js ./html/
cp ./slices/index.html ./html/
main: clean wasm_exec
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./main/main.go
cp ./main/index.html ./html/
+4 -2
View File
@@ -3,7 +3,9 @@
The examples here show two different ways of using WebAssembly with TinyGo:
1. Defining and exporting functions via the `//go:export <name>` directive. See
[the export folder](./export) for an example of this.
[the export folder](./export) for an example of this. Additionally, the Wasm
module (which has a default value of `env`) can be specified using
`//go:wasm-module <module>`.
1. Defining and executing a `func main()`. This is similar to how the Go
standard library implementation works. See [the main folder](./main) for an
example of this.
@@ -34,7 +36,7 @@ $ make main
Start the local web server:
```bash
$ go run main.go
$ go run server.go
Serving ./html on http://localhost:8080
```
+19
View File
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Go WebAssembly</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<script src="wasm_exec.js" defer></script>
<script src="wasm.js" defer></script>
</head>
<body>
<h1>WebAssembly</h1>
<p>Edit on either side to mimic values, using WebAssembly:</p>
<input type="text" id="a" value=""/>==<input type="text" id="b" value=""/>
</body>
</html>
+15
View File
@@ -0,0 +1,15 @@
package main
import (
"syscall/js"
)
func runner(this js.Value, args []js.Value) interface{} {
return args[0].Invoke(args[1]).String()
}
func main() {
wait := make(chan struct{}, 0)
js.Global().Set("runner", js.FuncOf(runner))
<-wait
}
+43
View File
@@ -0,0 +1,43 @@
'use strict';
const WASM_URL = 'wasm.wasm';
var wasm;
function updateRight() {
const value = document.getElementById("a").value;
window.runner(function (value) {
document.getElementById("b").value = value;
}, value);
}
function updateLeft() {
const value = document.getElementById("b").value;
window.runner(function (value) {
document.getElementById("a").value = value;
}, value);
}
function init() {
document.querySelector('#a').oninput = updateRight;
document.querySelector('#b').oninput = updateLeft;
const go = new Go();
if ('instantiateStreaming' in WebAssembly) {
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
} else {
fetch(WASM_URL).then(resp =>
resp.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
)
}
}
init();
+19
View File
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Go WebAssembly</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<script src="wasm_exec.js" defer></script>
<script src="wasm.js" defer></script>
</head>
<body>
<h1>WebAssembly</h1>
<p>type values separated by comma, using WebAssembly:</p>
<input type="text" id="a" value=""/>==<div id="b"></div>
</body>
</html>
+23
View File
@@ -0,0 +1,23 @@
package main
import (
"strings"
"syscall/js"
)
func splitter(this js.Value, args []js.Value) interface{} {
values := strings.Split(args[0].String(), ",")
result := make([]interface{}, 0)
for _, each := range values {
result = append(result, each)
}
return js.ValueOf(result)
}
func main() {
wait := make(chan struct{}, 0)
js.Global().Set("splitter", js.FuncOf(splitter))
<-wait
}
+33
View File
@@ -0,0 +1,33 @@
'use strict';
const WASM_URL = 'wasm.wasm';
var wasm;
function update() {
const value = document.getElementById("a").value;
document.getElementById("b").innerHTML = JSON.stringify(window.splitter(value));
}
function init() {
document.querySelector('#a').oninput = update;
const go = new Go();
if ('instantiateStreaming' in WebAssembly) {
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
} else {
fetch(WASM_URL).then(resp =>
resp.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
)
}
}
init();
+14 -5
View File
@@ -8,6 +8,9 @@ package machine
import "device/sam"
// used to reset into bootloader
const RESET_MAGIC_VALUE = 0x07738135
// GPIO Pins
const (
RX0 Pin = PB23 // UART2 RX
@@ -34,7 +37,7 @@ const (
A0 Pin = PA02 // ADC/AIN[0]
A1 Pin = PB02 // ADC/AIN[10]
A2 Pin = PA11 // ADC/AIN[19]
A3 Pin = PA10 // ADC/AIN[18]
A3 Pin = PA10 // ADC/AIN[18],
A4 Pin = PB08 // ADC/AIN[2], SCL: SERCOM2/PAD[1]
A5 Pin = PB09 // ADC/AIN[3], SDA: SERCOM2/PAD[1]
A6 Pin = PA09 // ADC/AIN[17]
@@ -115,14 +118,20 @@ var (
// SPI pins
const (
SPI0_SCK_PIN Pin = PB11 // SCK: SERCOM4/PAD[3]
SPI0_MOSI_PIN Pin = PB10 // MOSI: SERCOM4/PAD[2]
SPI0_MISO_PIN Pin = PA12 // MISO: SERCOM4/PAD[0]
SPI0_SCK_PIN Pin = A2 // SCK: SERCOM0/PAD[3]
SPI0_MOSI_PIN Pin = A3 // MOSI: SERCOM0/PAD[2]
SPI0_MISO_PIN Pin = A6 // MISO: SERCOM0/PAD[1]
)
// SPI on the Arduino Nano 33.
var (
SPI0 = SPI{Bus: sam.SERCOM1_SPI}
SPI0 = SPI{Bus: sam.SERCOM0_SPI,
SCK: SPI0_SCK_PIN,
MOSI: SPI0_MOSI_PIN,
MISO: SPI0_MISO_PIN,
DOpad: spiTXPad2SCK3,
DIpad: sercomRXPad0,
PinMode: PinSERCOM}
)
// I2S pins
+22 -2
View File
@@ -2,6 +2,8 @@
package machine
import "device/stm32"
// https://wiki.stm32duino.com/index.php?title=File:Bluepillpinout.gif
const (
PA0 = portA + 0
@@ -47,10 +49,28 @@ const (
// UART pins
const (
UART_TX_PIN = PA9
UART_RX_PIN = PA10
UART_TX_PIN = PA9
UART_RX_PIN = PA10
UART_ALT_TX_PIN = PB6
UART_ALT_RX_PIN = PB7
)
var (
// USART1 is the first hardware serial port on the STM32.
// Both UART0 and UART1 refer to USART1.
UART0 = UART{
Buffer: NewRingBuffer(),
Bus: stm32.USART1,
IRQVal: stm32.IRQ_USART1,
}
UART1 = &UART0
)
//go:export USART1_IRQHandler
func handleUART1() {
UART1.Receive(byte((UART1.Bus.DR.Get() & 0xFF)))
}
// SPI pins
const (
SPI0_SCK_PIN = PA5
+10 -1
View File
@@ -4,6 +4,9 @@ package machine
import "device/sam"
// used to reset into bootloader
const RESET_MAGIC_VALUE = 0xf01669ef
// GPIO Pins
const (
D0 = PB09
@@ -111,7 +114,13 @@ const (
// SPI on the Circuit Playground Express.
var (
SPI0 = SPI{Bus: sam.SERCOM3_SPI}
SPI0 = SPI{Bus: sam.SERCOM3_SPI,
SCK: SPI0_SCK_PIN,
MOSI: SPI0_MOSI_PIN,
MISO: SPI0_MISO_PIN,
DOpad: spiTXPad2SCK3,
DIpad: sercomRXPad0,
PinMode: PinSERCOMAlt}
)
// I2S pins
+10 -1
View File
@@ -4,6 +4,9 @@ package machine
import "device/sam"
// used to reset into bootloader
const RESET_MAGIC_VALUE = 0xf01669ef
// GPIO Pins
const (
D0 = PA11 // UART0 RX
@@ -85,7 +88,13 @@ const (
// SPI on the Feather M0.
var (
SPI0 = SPI{Bus: sam.SERCOM4_SPI}
SPI0 = SPI{Bus: sam.SERCOM4_SPI,
SCK: SPI0_SCK_PIN,
MOSI: SPI0_MOSI_PIN,
MISO: SPI0_MISO_PIN,
DOpad: spiTXPad2SCK3,
DIpad: sercomRXPad0,
PinMode: PinSERCOMAlt}
)
// I2S pins
+32 -2
View File
@@ -2,7 +2,12 @@
package machine
import "device/sam"
import (
"device/sam"
)
// used to reset into bootloader
const RESET_MAGIC_VALUE = 0xf01669ef
// GPIO Pins
const (
@@ -85,7 +90,32 @@ const (
// SPI on the ItsyBitsy M0.
var (
SPI0 = SPI{Bus: sam.SERCOM4_SPI}
SPI0 = SPI{Bus: sam.SERCOM4_SPI,
SCK: SPI0_SCK_PIN,
MOSI: SPI0_MOSI_PIN,
MISO: SPI0_MISO_PIN,
DOpad: spiTXPad2SCK3,
DIpad: sercomRXPad0,
PinMode: PinSERCOMAlt}
)
// "Internal" SPI pins; SPI flash is attached to these on ItsyBitsy M0
const (
SPI1_CS_PIN = PA27
SPI1_SCK_PIN = PB23
SPI1_MOSI_PIN = PB22
SPI1_MISO_PIN = PB03
)
// "Internal" SPI on Sercom 5
var (
SPI1 = SPI{Bus: sam.SERCOM5_SPI,
SCK: SPI1_SCK_PIN,
MOSI: SPI1_MOSI_PIN,
MISO: SPI1_MISO_PIN,
DOpad: spiTXPad2SCK3,
DIpad: sercomRXPad1,
PinMode: PinSERCOMAlt}
)
// I2S pins
+83
View File
@@ -0,0 +1,83 @@
// +build sam,atsamd51,itsybitsy_m4
package machine
import "device/sam"
// used to reset into bootloader
const RESET_MAGIC_VALUE = 0xf01669ef
// GPIO Pins
const (
D0 = PA16 // UART0 RX/PWM available
D1 = PA17 // UART0 TX/PWM available
D2 = PA07
D3 = PB22
D4 = PA14 // PWM available
D5 = PA15 // PWM available
D6 = PB02 // dotStar clock
D7 = PA18 // PWM available
D8 = PB03 // dotStar data
D9 = PA19 // PWM available
D10 = PA20 // can be used for PWM or UART1 TX
D11 = PA21 // can be used for PWM or UART1 RX
D12 = PA23 // PWM available
D13 = PA22 // PWM available
)
// Analog pins
const (
A0 = PA02 // ADC/AIN[0]
A1 = PB05 // ADC/AIN[2]
A2 = PB08 // ADC/AIN[3]
A3 = PB09 // ADC/AIN[4]
A4 = PA04 // ADC/AIN[5]
A5 = PA06 // ADC/AIN[10]
)
const (
LED = D13
)
// UART0 aka USBCDC pins
const (
USBCDC_DM_PIN = PA24
USBCDC_DP_PIN = PA25
)
// UART1 pins
const (
UART_TX_PIN = D1
UART_RX_PIN = D0
)
// I2C pins
const (
SDA_PIN = PA12 // SDA: SERCOM3/PAD[0]
SCL_PIN = PA13 // SCL: SERCOM3/PAD[1]
)
// I2C on the ItsyBitsy M4.
var (
I2C0 = I2C{Bus: sam.SERCOM2_I2CM,
SDA: SDA_PIN,
SCL: SCL_PIN,
PinMode: PinSERCOM}
)
// SPI pins
const (
SPI0_SCK_PIN = PA01 // SCK: SERCOM1/PAD[1]
SPI0_MOSI_PIN = PA00 // MOSI: SERCOM1/PAD[0]
SPI0_MISO_PIN = PB23 // MISO: SERCOM1/PAD[3]
)
// SPI on the ItsyBitsy M4.
var (
SPI0 = SPI{Bus: sam.SERCOM1_SPI,
SCK: SPI0_SCK_PIN,
MOSI: SPI0_MOSI_PIN,
MISO: SPI0_MISO_PIN,
DOpad: spiTXPad2SCK3,
DIpad: sercomRXPad0}
)
+124
View File
@@ -0,0 +1,124 @@
// +build nucleof103rb
package machine
import "device/stm32"
const (
PA0 = portA + 0
PA1 = portA + 1
PA2 = portA + 2
PA3 = portA + 3
PA4 = portA + 4
PA5 = portA + 5
PA6 = portA + 6
PA7 = portA + 7
PA8 = portA + 8
PA9 = portA + 9
PA10 = portA + 10
PA11 = portA + 11
PA12 = portA + 12
PA13 = portA + 13
PA14 = portA + 14
PA15 = portA + 15
PB0 = portB + 0
PB1 = portB + 1
PB2 = portB + 2
PB3 = portB + 3
PB4 = portB + 4
PB5 = portB + 5
PB6 = portB + 6
PB7 = portB + 7
PB8 = portB + 8
PB9 = portB + 9
PB10 = portB + 10
PB11 = portB + 11
PB12 = portB + 12
PB13 = portB + 13
PB14 = portB + 14
PB15 = portB + 15
PC0 = portC + 0
PC1 = portC + 1
PC2 = portC + 2
PC3 = portC + 3
PC4 = portC + 4
PC5 = portC + 5
PC6 = portC + 6
PC7 = portC + 7
PC8 = portC + 8
PC9 = portC + 9
PC10 = portC + 10
PC11 = portC + 11
PC12 = portC + 12
PC13 = portC + 13
PC14 = portC + 14
PC15 = portC + 15
PD0 = portD + 0
PD1 = portD + 1
PD2 = portD + 2
PD3 = portD + 3
PD4 = portD + 4
PD5 = portD + 5
PD6 = portD + 6
PD7 = portD + 7
PD8 = portD + 8
PD9 = portD + 9
PD10 = portD + 10
PD11 = portD + 11
PD12 = portD + 12
PD13 = portD + 13
PD14 = portD + 14
PD15 = portD + 15
)
const (
LED = LED_BUILTIN
LED_BUILTIN = LED_GREEN
LED_GREEN = PA5
)
const (
BUTTON = BUTTON_USER
BUTTON_USER = PC13
)
// UART pins
const (
UART_TX_PIN = PA2
UART_RX_PIN = PA3
UART_ALT_TX_PIN = PD5
UART_ALT_RX_PIN = PD6
)
var (
// USART2 is the hardware serial port connected to the onboard ST-LINK
// debugger to be exposed as virtual COM port over USB on Nucleo boards.
// Both UART0 and UART1 refer to USART2.
UART0 = UART{
Buffer: NewRingBuffer(),
Bus: stm32.USART2,
IRQVal: stm32.IRQ_USART2,
}
UART2 = &UART0
)
//go:export USART2_IRQHandler
func handleUART2() {
UART2.Receive(byte((UART2.Bus.DR.Get() & 0xFF)))
}
// SPI pins
const (
SPI0_SCK_PIN = PA5
SPI0_MISO_PIN = PA6
SPI0_MOSI_PIN = PA7
)
// I2C pins
const (
SCL_PIN = PB6
SDA_PIN = PB7
)
+1 -1
View File
@@ -1,4 +1,4 @@
// +build bluepill stm32f4disco
// +build bluepill nucleof103rb stm32f4disco
package machine
+10 -1
View File
@@ -4,6 +4,9 @@ package machine
import "device/sam"
// used to reset into bootloader
const RESET_MAGIC_VALUE = 0xf01669ef
// GPIO Pins
const (
D0 = PA08 // PWM available
@@ -62,7 +65,13 @@ const (
// SPI on the Trinket M0.
var (
SPI0 = SPI{Bus: sam.SERCOM0_SPI}
SPI0 = SPI{Bus: sam.SERCOM0_SPI,
SCK: SPI0_SCK_PIN,
MOSI: SPI0_MOSI_PIN,
MISO: SPI0_MISO_PIN,
DOpad: spiTXPad2SCK3,
DIpad: sercomRXPad0,
PinMode: PinSERCOMAlt}
)
// I2C pins
+1 -1
View File
@@ -1,4 +1,4 @@
// +build !stm32f4disco,!hifive1b
// +build avr nrf sam stm32,!stm32f4disco
package machine
+65 -54
View File
@@ -192,6 +192,11 @@ func (a ADC) Get() uint16 {
sam.ADC.SWTRIG.SetBits(sam.ADC_SWTRIG_START)
waitADCSync()
// wait for first conversion to finish to fix same issue as
// https://github.com/arduino/ArduinoCore-samd/issues/446
for !sam.ADC.INTFLAG.HasBits(sam.ADC_INTFLAG_RESRDY) {
}
// Clear the Data Ready flag
sam.ADC.INTFLAG.SetBits(sam.ADC_INTFLAG_RESRDY)
waitADCSync()
@@ -871,7 +876,13 @@ func waitForSync() {
// SPI
type SPI struct {
Bus *sam.SERCOM_SPI_Type
Bus *sam.SERCOM_SPI_Type
SCK Pin
MOSI Pin
MISO Pin
DOpad int
DIpad int
PinMode PinMode
}
// SPIConfig is used to store config info for SPI.
@@ -886,12 +897,14 @@ type SPIConfig struct {
// Configure is intended to setup the SPI interface.
func (spi SPI) Configure(config SPIConfig) {
config.SCK = SPI0_SCK_PIN
config.MOSI = SPI0_MOSI_PIN
config.MISO = SPI0_MISO_PIN
config.SCK = spi.SCK
config.MOSI = spi.MOSI
config.MISO = spi.MISO
doPad := spiTXPad2SCK3
diPad := sercomRXPad0
doPad := spi.DOpad
diPad := spi.DIpad
pinMode := spi.PinMode
// set default frequency
if config.Frequency == 0 {
@@ -904,9 +917,9 @@ func (spi SPI) Configure(config SPIConfig) {
}
// enable pins
config.SCK.Configure(PinConfig{Mode: PinSERCOMAlt})
config.MOSI.Configure(PinConfig{Mode: PinSERCOMAlt})
config.MISO.Configure(PinConfig{Mode: PinSERCOMAlt})
config.SCK.Configure(PinConfig{Mode: pinMode})
config.MOSI.Configure(PinConfig{Mode: pinMode})
config.MISO.Configure(PinConfig{Mode: pinMode})
// reset SERCOM
spi.Bus.CTRLA.SetBits(sam.SERCOM_SPI_CTRLA_SWRST)
@@ -1390,18 +1403,14 @@ func handleUSB() {
for i = 1; i < uint32(len(endPoints)); i++ {
// Check if endpoint has a pending interrupt
epFlags := getEPINTFLAG(i)
if epFlags > 0 {
if (epFlags&sam.USB_DEVICE_EPINTFLAG_TRCPT0) > 0 ||
(epFlags&sam.USB_DEVICE_EPINTFLAG_TRCPT1) > 0 {
switch i {
case usb_CDC_ENDPOINT_OUT:
if (epFlags & sam.USB_DEVICE_EPINTFLAG_TRCPT0) > 0 {
handleEndpoint(i)
}
handleEndpoint(i)
setEPINTFLAG(i, epFlags)
case usb_CDC_ENDPOINT_IN, usb_CDC_ENDPOINT_ACM:
// set bank ready
setEPSTATUSCLR(i, sam.USB_DEVICE_EPSTATUSCLR_BK1RDY)
// ack transfer complete
setEPINTFLAG(i, sam.USB_DEVICE_EPINTFLAG_TRCPT1)
}
}
@@ -1624,17 +1633,19 @@ func cdcSetup(setup usbSetup) bool {
if setup.bRequest == usb_CDC_SET_LINE_CODING || setup.bRequest == usb_CDC_SET_CONTROL_LINE_STATE {
// auto-reset into the bootloader
if usbLineInfo.dwDTERate == 1200 && (usbLineInfo.lineState&0x01) == 0 {
// TODO: system reset
if usbLineInfo.dwDTERate == 1200 && usbLineInfo.lineState&usb_CDC_LINESTATE_DTR == 0 {
ResetProcessor()
} else {
// TODO: cancel any reset
}
sendZlp(0)
}
if setup.bRequest == usb_CDC_SEND_BREAK {
// TODO: something with this value?
// breakValue = ((uint16_t)setup.wValueH << 8) | setup.wValueL;
// return false;
sendZlp(0)
}
return true
}
@@ -1655,53 +1666,41 @@ func sendUSBPacket(ep uint32, data []byte) {
}
func receiveUSBControlPacket() []byte {
// set ready to receive data
// address
usbEndpointDescriptors[0].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[0]))))
// set byte count to zero
usbEndpointDescriptors[0].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
// set ready for next data
setEPSTATUSCLR(0, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY)
// read the data
bytesread := armRecvCtrlOUT(0)
// return the data
data := make([]byte, 0, bytesread)
copy(data, udd_ep_out_cache_buffer[0][:bytesread])
return data
}
func armRecvCtrlOUT(ep uint32) uint32 {
// Set output address to receive data
usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep]))))
// set multi-packet size which is total expected number of bytes to receive.
usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits((8 << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos) |
uint32(epPacketSize(64)<<usb_DEVICE_PCKSIZE_SIZE_Pos))
// clear byte count of bytes received so far.
usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
// clear ready state to start transfer
setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY)
// Wait until OUT transfer is ready.
timeout := 3000
for (getEPSTATUS(ep) & sam.USB_DEVICE_EPSTATUS_BK0RDY) == 0 {
for (getEPSTATUS(0) & sam.USB_DEVICE_EPSTATUS_BK0RDY) == 0 {
timeout--
if timeout == 0 {
return 0
return []byte{}
}
}
// Wait until OUT transfer is completed.
timeout = 3000
for (getEPINTFLAG(ep) & sam.USB_DEVICE_EPINTFLAG_TRCPT0) == 0 {
timeout = 300000
for (getEPINTFLAG(0) & sam.USB_DEVICE_EPINTFLAG_TRCPT0) == 0 {
timeout--
if timeout == 0 {
return 0
return []byte{}
}
}
// return number of bytes received
return (usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.Get() >>
usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask
// get data
bytesread := uint32((usbEndpointDescriptors[0].DeviceDescBank[0].PCKSIZE.Get() >>
usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask)
data := make([]byte, bytesread)
copy(data, udd_ep_out_cache_buffer[0][:])
return data
}
// sendDescriptor creates and sends the various USB descriptor types that
@@ -1797,9 +1796,9 @@ func sendConfiguration(setup usbSetup) {
dif := NewInterfaceDescriptor(usb_CDC_DATA_INTERFACE, 2, usb_CDC_DATA_INTERFACE_CLASS, 0, 0)
in := NewEndpointDescriptor((usb_CDC_ENDPOINT_OUT | usbEndpointOut), usb_ENDPOINT_TYPE_BULK, usbEndpointPacketSize, 0)
out := NewEndpointDescriptor((usb_CDC_ENDPOINT_OUT | usbEndpointOut), usb_ENDPOINT_TYPE_BULK, usbEndpointPacketSize, 0)
out := NewEndpointDescriptor((usb_CDC_ENDPOINT_IN | usbEndpointIn), usb_ENDPOINT_TYPE_BULK, usbEndpointPacketSize, 0)
in := NewEndpointDescriptor((usb_CDC_ENDPOINT_IN | usbEndpointIn), usb_ENDPOINT_TYPE_BULK, usbEndpointPacketSize, 0)
cdc := NewCDCDescriptor(iad,
cif,
@@ -1809,8 +1808,8 @@ func sendConfiguration(setup usbSetup) {
callManagement,
cifin,
dif,
in,
out)
out,
in)
sz := uint16(configDescriptorSize + cdcSize)
config := NewConfigDescriptor(sz, 2)
@@ -2076,3 +2075,15 @@ func setEPINTENSET(ep uint32, val uint8) {
return
}
}
// ResetProcessor should perform a system reset in preperation
// to switch to the bootloader to flash new firmware.
func ResetProcessor() {
arm.DisableInterrupts()
// Perform magic reset into bootloader, as mentioned in
// https://github.com/arduino/ArduinoCore-samd/issues/197
*(*uint32)(unsafe.Pointer(uintptr(0x20007FFC))) = RESET_MAGIC_VALUE
arm.SystemReset()
}
File diff suppressed because it is too large Load Diff
+569
View File
@@ -0,0 +1,569 @@
// +build sam,atsamd51,atsamd51g19
// Peripheral abstraction layer for the atsamd51.
//
// Datasheet:
// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf
//
package machine
import (
"device/sam"
)
// Return the register and mask to enable a given GPIO pin. This can be used to
// implement bit-banged drivers.
func (p Pin) PortMaskSet() (*uint32, uint32) {
if p < 32 {
return &sam.PORT.OUTSET0.Reg, 1 << uint8(p)
} else {
return &sam.PORT.OUTSET1.Reg, 1 << uint8(p-32)
}
}
// Return the register and mask to disable a given port. This can be used to
// implement bit-banged drivers.
func (p Pin) PortMaskClear() (*uint32, uint32) {
if p < 32 {
return &sam.PORT.OUTCLR0.Reg, 1 << uint8(p)
} else {
return &sam.PORT.OUTCLR1.Reg, 1 << uint8(p-32)
}
}
// Set the pin to high or low.
// Warning: only use this on an output pin!
func (p Pin) Set(high bool) {
if p < 32 {
if high {
sam.PORT.OUTSET0.Set(1 << uint8(p))
} else {
sam.PORT.OUTCLR0.Set(1 << uint8(p))
}
} else {
if high {
sam.PORT.OUTSET1.Set(1 << uint8(p-32))
} else {
sam.PORT.OUTCLR1.Set(1 << uint8(p-32))
}
}
}
// Get returns the current value of a GPIO pin.
func (p Pin) Get() bool {
if p < 32 {
return (sam.PORT.IN0.Get()>>uint8(p))&1 > 0
} else {
return (sam.PORT.IN1.Get()>>(uint8(p)-32))&1 > 0
}
}
// Configure this pin with the given configuration.
func (p Pin) Configure(config PinConfig) {
switch config.Mode {
case PinOutput:
if p < 32 {
sam.PORT.DIRSET0.Set(1 << uint8(p))
// output is also set to input enable so pin can read back its own value
p.setPinCfg(sam.PORT_PINCFG0_INEN)
} else {
sam.PORT.DIRSET1.Set(1 << uint8(p-32))
// output is also set to input enable so pin can read back its own value
p.setPinCfg(sam.PORT_PINCFG0_INEN)
}
case PinInput:
if p < 32 {
sam.PORT.DIRCLR0.Set(1 << uint8(p))
p.setPinCfg(sam.PORT_PINCFG0_INEN)
} else {
sam.PORT.DIRCLR1.Set(1<<uint8(p) - 32)
p.setPinCfg(sam.PORT_PINCFG0_INEN)
}
case PinInputPulldown:
if p < 32 {
sam.PORT.DIRCLR0.Set(1 << uint8(p))
sam.PORT.OUTCLR0.Set(1 << uint8(p))
p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN)
} else {
sam.PORT.DIRCLR1.Set(1<<uint8(p) - 32)
sam.PORT.OUTCLR1.Set(1<<uint8(p) - 32)
p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN)
}
case PinInputPullup:
if p < 32 {
sam.PORT.DIRCLR0.Set(1 << uint8(p))
sam.PORT.OUTSET0.Set(1 << uint8(p))
p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN)
} else {
sam.PORT.DIRCLR1.Set(1<<uint8(p) - 32)
sam.PORT.OUTSET1.Set(1<<uint8(p) - 32)
p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN)
}
case PinSERCOM:
if p&1 > 0 {
// odd pin, so save the even pins
val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_PMUX0_PMUXO_Pos))
} else {
// even pin, so save the odd pins
val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_PMUX0_PMUXE_Pos))
}
// enable port config
p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR | sam.PORT_PINCFG0_INEN)
case PinSERCOMAlt:
if p&1 > 0 {
// odd pin, so save the even pins
val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_PMUX0_PMUXO_Pos))
} else {
// even pin, so save the odd pins
val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_PMUX0_PMUXE_Pos))
}
// enable port config
p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR)
case PinCom:
if p&1 > 0 {
// odd pin, so save the even pins
val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
p.setPMux(val | (uint8(PinCom) << sam.PORT_PMUX0_PMUXO_Pos))
} else {
// even pin, so save the odd pins
val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
p.setPMux(val | (uint8(PinCom) << sam.PORT_PMUX0_PMUXE_Pos))
}
// enable port config
p.setPinCfg(sam.PORT_PINCFG0_PMUXEN)
case PinAnalog:
if p&1 > 0 {
// odd pin, so save the even pins
val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
p.setPMux(val | (uint8(PinAnalog) << sam.PORT_PMUX0_PMUXO_Pos))
} else {
// even pin, so save the odd pins
val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
p.setPMux(val | (uint8(PinAnalog) << sam.PORT_PMUX0_PMUXE_Pos))
}
// enable port config
p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR)
}
}
// getPMux returns the value for the correct PMUX register for this pin.
func (p Pin) getPMux() uint8 {
switch uint8(p) >> 1 {
case 0:
return sam.PORT.PMUX0_0.Get()
case 1:
return sam.PORT.PMUX0_1.Get()
case 2:
return sam.PORT.PMUX0_2.Get()
case 3:
return sam.PORT.PMUX0_3.Get()
case 4:
return sam.PORT.PMUX0_4.Get()
case 5:
return sam.PORT.PMUX0_5.Get()
case 6:
return sam.PORT.PMUX0_6.Get()
case 7:
return sam.PORT.PMUX0_7.Get()
case 8:
return sam.PORT.PMUX0_8.Get()
case 9:
return sam.PORT.PMUX0_9.Get()
case 10:
return sam.PORT.PMUX0_10.Get()
case 11:
return sam.PORT.PMUX0_11.Get()
case 12:
return sam.PORT.PMUX0_12.Get()
case 13:
return sam.PORT.PMUX0_13.Get()
case 14:
return sam.PORT.PMUX0_14.Get()
case 15:
return sam.PORT.PMUX0_15.Get()
case 16:
return uint8(sam.PORT.PMUX1_0.Get()>>0) & 0xff
case 17:
return uint8(sam.PORT.PMUX1_0.Get()>>8) & 0xff
case 18:
return uint8(sam.PORT.PMUX1_0.Get()>>16) & 0xff
case 19:
return uint8(sam.PORT.PMUX1_0.Get()>>24) & 0xff
case 20:
return uint8(sam.PORT.PMUX1_4.Get()>>0) & 0xff
case 21:
return uint8(sam.PORT.PMUX1_4.Get()>>8) & 0xff
case 22:
return uint8(sam.PORT.PMUX1_4.Get()>>16) & 0xff
case 23:
return uint8(sam.PORT.PMUX1_4.Get()>>24) & 0xff
case 24:
return uint8(sam.PORT.PMUX1_8.Get()>>0) & 0xff
case 25:
return uint8(sam.PORT.PMUX1_8.Get()>>8) & 0xff
case 26:
return uint8(sam.PORT.PMUX1_8.Get()>>16) & 0xff
case 27:
return uint8(sam.PORT.PMUX1_8.Get()>>24) & 0xff
case 28:
return uint8(sam.PORT.PMUX1_12.Get()>>0) & 0xff
case 29:
return uint8(sam.PORT.PMUX1_12.Get()>>8) & 0xff
case 30:
return uint8(sam.PORT.PMUX1_12.Get()>>16) & 0xff
case 31:
return uint8(sam.PORT.PMUX1_12.Get()>>24) & 0xff
default:
return 0
}
}
// setPMux sets the value for the correct PMUX register for this pin.
func (p Pin) setPMux(val uint8) {
switch uint8(p) >> 1 {
case 0:
sam.PORT.PMUX0_0.Set(val)
case 1:
sam.PORT.PMUX0_1.Set(val)
case 2:
sam.PORT.PMUX0_2.Set(val)
case 3:
sam.PORT.PMUX0_3.Set(val)
case 4:
sam.PORT.PMUX0_4.Set(val)
case 5:
sam.PORT.PMUX0_5.Set(val)
case 6:
sam.PORT.PMUX0_6.Set(val)
case 7:
sam.PORT.PMUX0_7.Set(val)
case 8:
sam.PORT.PMUX0_8.Set(val)
case 9:
sam.PORT.PMUX0_9.Set(val)
case 10:
sam.PORT.PMUX0_10.Set(val)
case 11:
sam.PORT.PMUX0_11.Set(val)
case 12:
sam.PORT.PMUX0_12.Set(val)
case 13:
sam.PORT.PMUX0_13.Set(val)
case 14:
sam.PORT.PMUX0_14.Set(val)
case 15:
sam.PORT.PMUX0_15.Set(val)
case 16:
sam.PORT.PMUX1_0.Set(sam.PORT.PMUX1_0.Get()&^(0xff<<0) | (uint32(val) << 0))
case 17:
sam.PORT.PMUX1_0.Set(sam.PORT.PMUX1_0.Get()&^(0xff<<8) | (uint32(val) << 8))
case 18:
sam.PORT.PMUX1_0.Set(sam.PORT.PMUX1_0.Get()&^(0xff<<16) | (uint32(val) << 16))
case 19:
sam.PORT.PMUX1_0.Set(sam.PORT.PMUX1_0.Get()&^(0xff<<24) | (uint32(val) << 24))
case 20:
sam.PORT.PMUX1_4.Set(sam.PORT.PMUX1_4.Get()&^(0xff<<0) | (uint32(val) << 0))
case 21:
sam.PORT.PMUX1_4.Set(sam.PORT.PMUX1_4.Get()&^(0xff<<8) | (uint32(val) << 8))
case 22:
sam.PORT.PMUX1_4.Set(sam.PORT.PMUX1_4.Get()&^(0xff<<16) | (uint32(val) << 16))
case 23:
sam.PORT.PMUX1_4.Set(sam.PORT.PMUX1_4.Get()&^(0xff<<24) | (uint32(val) << 24))
case 24:
sam.PORT.PMUX1_8.Set(sam.PORT.PMUX1_8.Get()&^(0xff<<0) | (uint32(val) << 0))
case 25:
sam.PORT.PMUX1_8.Set(sam.PORT.PMUX1_8.Get()&^(0xff<<8) | (uint32(val) << 8))
case 26:
sam.PORT.PMUX1_8.Set(sam.PORT.PMUX1_8.Get()&^(0xff<<16) | (uint32(val) << 16))
case 27:
sam.PORT.PMUX1_8.Set(sam.PORT.PMUX1_8.Get()&^(0xff<<24) | (uint32(val) << 24))
case 28:
sam.PORT.PMUX1_12.Set(sam.PORT.PMUX1_12.Get()&^(0xff<<0) | (uint32(val) << 0))
case 29:
sam.PORT.PMUX1_12.Set(sam.PORT.PMUX1_12.Get()&^(0xff<<8) | (uint32(val) << 8))
case 30:
sam.PORT.PMUX1_12.Set(sam.PORT.PMUX1_12.Get()&^(0xff<<16) | (uint32(val) << 16))
case 31:
sam.PORT.PMUX1_12.Set(sam.PORT.PMUX1_12.Get()&^(0xff<<24) | (uint32(val) << 24))
}
}
// getPinCfg returns the value for the correct PINCFG register for this pin.
func (p Pin) getPinCfg() uint8 {
switch p {
case 0:
return sam.PORT.PINCFG0_0.Get()
case 1:
return sam.PORT.PINCFG0_1.Get()
case 2:
return sam.PORT.PINCFG0_2.Get()
case 3:
return sam.PORT.PINCFG0_3.Get()
case 4:
return sam.PORT.PINCFG0_4.Get()
case 5:
return sam.PORT.PINCFG0_5.Get()
case 6:
return sam.PORT.PINCFG0_6.Get()
case 7:
return sam.PORT.PINCFG0_7.Get()
case 8:
return sam.PORT.PINCFG0_8.Get()
case 9:
return sam.PORT.PINCFG0_9.Get()
case 10:
return sam.PORT.PINCFG0_10.Get()
case 11:
return sam.PORT.PINCFG0_11.Get()
case 12:
return sam.PORT.PINCFG0_12.Get()
case 13:
return sam.PORT.PINCFG0_13.Get()
case 14:
return sam.PORT.PINCFG0_14.Get()
case 15:
return sam.PORT.PINCFG0_15.Get()
case 16:
return sam.PORT.PINCFG0_16.Get()
case 17:
return sam.PORT.PINCFG0_17.Get()
case 18:
return sam.PORT.PINCFG0_18.Get()
case 19:
return sam.PORT.PINCFG0_19.Get()
case 20:
return sam.PORT.PINCFG0_20.Get()
case 21:
return sam.PORT.PINCFG0_21.Get()
case 22:
return sam.PORT.PINCFG0_22.Get()
case 23:
return sam.PORT.PINCFG0_23.Get()
case 24:
return sam.PORT.PINCFG0_24.Get()
case 25:
return sam.PORT.PINCFG0_25.Get()
case 26:
return sam.PORT.PINCFG0_26.Get()
case 27:
return sam.PORT.PINCFG0_27.Get()
case 28:
return sam.PORT.PINCFG0_28.Get()
case 29:
return sam.PORT.PINCFG0_29.Get()
case 30:
return sam.PORT.PINCFG0_30.Get()
case 31:
return sam.PORT.PINCFG0_31.Get()
case 32: // PB00
return uint8(sam.PORT.PINCFG1_0.Get()>>0) & 0xff
case 33: // PB01
return uint8(sam.PORT.PINCFG1_0.Get()>>8) & 0xff
case 34: // PB02
return uint8(sam.PORT.PINCFG1_0.Get()>>16) & 0xff
case 35: // PB03
return uint8(sam.PORT.PINCFG1_0.Get()>>24) & 0xff
case 37: // PB04
return uint8(sam.PORT.PINCFG1_4.Get()>>0) & 0xff
case 38: // PB05
return uint8(sam.PORT.PINCFG1_4.Get()>>8) & 0xff
case 39: // PB06
return uint8(sam.PORT.PINCFG1_4.Get()>>16) & 0xff
case 40: // PB07
return uint8(sam.PORT.PINCFG1_4.Get()>>24) & 0xff
case 41: // PB08
return uint8(sam.PORT.PINCFG1_8.Get()>>0) & 0xff
case 42: // PB09
return uint8(sam.PORT.PINCFG1_8.Get()>>8) & 0xff
case 43: // PB10
return uint8(sam.PORT.PINCFG1_8.Get()>>16) & 0xff
case 44: // PB11
return uint8(sam.PORT.PINCFG1_8.Get()>>24) & 0xff
case 45: // PB12
return uint8(sam.PORT.PINCFG1_12.Get()>>0) & 0xff
case 46: // PB13
return uint8(sam.PORT.PINCFG1_12.Get()>>8) & 0xff
case 47: // PB14
return uint8(sam.PORT.PINCFG1_12.Get()>>16) & 0xff
case 48: // PB15
return uint8(sam.PORT.PINCFG1_12.Get()>>24) & 0xff
case 49: // PB16
return uint8(sam.PORT.PINCFG1_16.Get()>>0) & 0xff
case 50: // PB17
return uint8(sam.PORT.PINCFG1_16.Get()>>8) & 0xff
case 51: // PB18
return uint8(sam.PORT.PINCFG1_16.Get()>>16) & 0xff
case 52: // PB19
return uint8(sam.PORT.PINCFG1_16.Get()>>24) & 0xff
case 53: // PB20
return uint8(sam.PORT.PINCFG1_20.Get()>>0) & 0xff
case 54: // PB21
return uint8(sam.PORT.PINCFG1_20.Get()>>8) & 0xff
case 55: // PB22
return uint8(sam.PORT.PINCFG1_20.Get()>>16) & 0xff
case 56: // PB23
return uint8(sam.PORT.PINCFG1_20.Get()>>24) & 0xff
case 57: // PB24
return uint8(sam.PORT.PINCFG1_24.Get()>>0) & 0xff
case 58: // PB25
return uint8(sam.PORT.PINCFG1_24.Get()>>8) & 0xff
case 59: // PB26
return uint8(sam.PORT.PINCFG1_24.Get()>>16) & 0xff
case 60: // PB27
return uint8(sam.PORT.PINCFG1_24.Get()>>24) & 0xff
case 61: // PB28
return uint8(sam.PORT.PINCFG1_28.Get()>>0) & 0xff
case 62: // PB29
return uint8(sam.PORT.PINCFG1_28.Get()>>8) & 0xff
case 63: // PB30
return uint8(sam.PORT.PINCFG1_28.Get()>>16) & 0xff
case 64: // PB31
return uint8(sam.PORT.PINCFG1_28.Get()>>24) & 0xff
default:
return 0
}
}
// setPinCfg sets the value for the correct PINCFG register for this pin.
func (p Pin) setPinCfg(val uint8) {
switch p {
case 0:
sam.PORT.PINCFG0_0.Set(val)
case 1:
sam.PORT.PINCFG0_1.Set(val)
case 2:
sam.PORT.PINCFG0_2.Set(val)
case 3:
sam.PORT.PINCFG0_3.Set(val)
case 4:
sam.PORT.PINCFG0_4.Set(val)
case 5:
sam.PORT.PINCFG0_5.Set(val)
case 6:
sam.PORT.PINCFG0_6.Set(val)
case 7:
sam.PORT.PINCFG0_7.Set(val)
case 8:
sam.PORT.PINCFG0_8.Set(val)
case 9:
sam.PORT.PINCFG0_9.Set(val)
case 10:
sam.PORT.PINCFG0_10.Set(val)
case 11:
sam.PORT.PINCFG0_11.Set(val)
case 12:
sam.PORT.PINCFG0_12.Set(val)
case 13:
sam.PORT.PINCFG0_13.Set(val)
case 14:
sam.PORT.PINCFG0_14.Set(val)
case 15:
sam.PORT.PINCFG0_15.Set(val)
case 16:
sam.PORT.PINCFG0_16.Set(val)
case 17:
sam.PORT.PINCFG0_17.Set(val)
case 18:
sam.PORT.PINCFG0_18.Set(val)
case 19:
sam.PORT.PINCFG0_19.Set(val)
case 20:
sam.PORT.PINCFG0_20.Set(val)
case 21:
sam.PORT.PINCFG0_21.Set(val)
case 22:
sam.PORT.PINCFG0_22.Set(val)
case 23:
sam.PORT.PINCFG0_23.Set(val)
case 24:
sam.PORT.PINCFG0_24.Set(val)
case 25:
sam.PORT.PINCFG0_25.Set(val)
case 26:
sam.PORT.PINCFG0_26.Set(val)
case 27:
sam.PORT.PINCFG0_27.Set(val)
case 28:
sam.PORT.PINCFG0_28.Set(val)
case 29:
sam.PORT.PINCFG0_29.Set(val)
case 30:
sam.PORT.PINCFG0_30.Set(val)
case 31:
sam.PORT.PINCFG0_31.Set(val)
case 32: // PB00
sam.PORT.PINCFG1_0.Set(sam.PORT.PINCFG1_0.Get()&^(0xff<<0) | (uint32(val) << 0))
case 33: // PB01
sam.PORT.PINCFG1_0.Set(sam.PORT.PINCFG1_0.Get()&^(0xff<<8) | (uint32(val) << 8))
case 34: // PB02
sam.PORT.PINCFG1_0.Set(sam.PORT.PINCFG1_0.Get()&^(0xff<<16) | (uint32(val) << 16))
case 35: // PB03
sam.PORT.PINCFG1_0.Set(sam.PORT.PINCFG1_0.Get()&^(0xff<<24) | (uint32(val) << 24))
case 36: // PB04
sam.PORT.PINCFG1_4.Set(sam.PORT.PINCFG1_4.Get()&^(0xff<<0) | (uint32(val) << 0))
case 37: // PB05
sam.PORT.PINCFG1_4.Set(sam.PORT.PINCFG1_4.Get()&^(0xff<<8) | (uint32(val) << 8))
case 38: // PB06
sam.PORT.PINCFG1_4.Set(sam.PORT.PINCFG1_4.Get()&^(0xff<<16) | (uint32(val) << 16))
case 39: // PB07
sam.PORT.PINCFG1_4.Set(sam.PORT.PINCFG1_4.Get()&^(0xff<<24) | (uint32(val) << 24))
case 40: // PB08
sam.PORT.PINCFG1_8.Set(sam.PORT.PINCFG1_8.Get()&^(0xff<<0) | (uint32(val) << 0))
case 41: // PB09
sam.PORT.PINCFG1_8.Set(sam.PORT.PINCFG1_8.Get()&^(0xff<<8) | (uint32(val) << 8))
case 42: // PB10
sam.PORT.PINCFG1_8.Set(sam.PORT.PINCFG1_8.Get()&^(0xff<<16) | (uint32(val) << 16))
case 43: // PB11
sam.PORT.PINCFG1_8.Set(sam.PORT.PINCFG1_8.Get()&^(0xff<<24) | (uint32(val) << 24))
case 44: // PB12
sam.PORT.PINCFG1_12.Set(sam.PORT.PINCFG1_12.Get()&^(0xff<<0) | (uint32(val) << 0))
case 45: // PB13
sam.PORT.PINCFG1_12.Set(sam.PORT.PINCFG1_12.Get()&^(0xff<<8) | (uint32(val) << 8))
case 46: // PB14
sam.PORT.PINCFG1_12.Set(sam.PORT.PINCFG1_12.Get()&^(0xff<<16) | (uint32(val) << 16))
case 47: // PB15
sam.PORT.PINCFG1_12.Set(sam.PORT.PINCFG1_12.Get()&^(0xff<<24) | (uint32(val) << 24))
case 48: // PB16
sam.PORT.PINCFG1_16.Set(sam.PORT.PINCFG1_16.Get()&^(0xff<<0) | (uint32(val) << 0))
case 49: // PB17
sam.PORT.PINCFG1_16.Set(sam.PORT.PINCFG1_16.Get()&^(0xff<<8) | (uint32(val) << 8))
case 50: // PB18
sam.PORT.PINCFG1_16.Set(sam.PORT.PINCFG1_16.Get()&^(0xff<<16) | (uint32(val) << 16))
case 51: // PB19
sam.PORT.PINCFG1_16.Set(sam.PORT.PINCFG1_16.Get()&^(0xff<<24) | (uint32(val) << 24))
case 52: // PB20
sam.PORT.PINCFG1_20.Set(sam.PORT.PINCFG1_20.Get()&^(0xff<<0) | (uint32(val) << 0))
case 53: // PB21
sam.PORT.PINCFG1_20.Set(sam.PORT.PINCFG1_20.Get()&^(0xff<<8) | (uint32(val) << 8))
case 54: // PB22
sam.PORT.PINCFG1_20.Set(sam.PORT.PINCFG1_20.Get()&^(0xff<<16) | (uint32(val) << 16))
case 55: // PB23
sam.PORT.PINCFG1_20.Set(sam.PORT.PINCFG1_20.Get()&^(0xff<<24) | (uint32(val) << 24))
case 56: // PB24
sam.PORT.PINCFG1_24.Set(sam.PORT.PINCFG1_24.Get()&^(0xff<<0) | (uint32(val) << 0))
case 57: // PB25
sam.PORT.PINCFG1_24.Set(sam.PORT.PINCFG1_24.Get()&^(0xff<<8) | (uint32(val) << 8))
case 58: // PB26
sam.PORT.PINCFG1_24.Set(sam.PORT.PINCFG1_24.Get()&^(0xff<<16) | (uint32(val) << 16))
case 59: // PB27
sam.PORT.PINCFG1_24.Set(sam.PORT.PINCFG1_24.Get()&^(0xff<<24) | (uint32(val) << 24))
case 60: // PB28
sam.PORT.PINCFG1_28.Set(sam.PORT.PINCFG1_28.Get()&^(0xff<<0) | (uint32(val) << 0))
case 61: // PB29
sam.PORT.PINCFG1_28.Set(sam.PORT.PINCFG1_28.Get()&^(0xff<<8) | (uint32(val) << 8))
case 62: // PB30
sam.PORT.PINCFG1_28.Set(sam.PORT.PINCFG1_28.Get()&^(0xff<<16) | (uint32(val) << 16))
case 63: // PB31
sam.PORT.PINCFG1_28.Set(sam.PORT.PINCFG1_28.Get()&^(0xff<<24) | (uint32(val) << 24))
}
}
+44
View File
@@ -0,0 +1,44 @@
// +build gameboyadvance
package machine
import (
"image/color"
"runtime/volatile"
"unsafe"
)
// Make it easier to directly write to I/O RAM.
var ioram = (*[0x400]volatile.Register8)(unsafe.Pointer(uintptr(0x04000000)))
type PinMode uint8
// Set has not been implemented.
func (p Pin) Set(value bool) {
// do nothing
}
var Display = FramebufDisplay{(*[160][240]volatile.Register16)(unsafe.Pointer(uintptr(0x06000000)))}
type FramebufDisplay struct {
port *[160][240]volatile.Register16
}
func (d FramebufDisplay) Configure() {
// Write into the I/O registers, setting video display parameters.
ioram[0].Set(0x03) // Use video mode 3 (in BG2, a 16bpp bitmap in VRAM)
ioram[1].Set(0x04) // Enable BG2 (BG0 = 1, BG1 = 2, BG2 = 4, ...)
}
func (d FramebufDisplay) Size() (x, y int16) {
return 240, 160
}
func (d FramebufDisplay) SetPixel(x, y int16, c color.RGBA) {
d.port[y][x].Set(uint16(c.R)&0x1f | uint16(c.G)&0x1f<<5 | uint16(c.B)&0x1f<<10)
}
func (d FramebufDisplay) Display() error {
// Nothing to do here.
return nil
}
+1 -1
View File
@@ -1,4 +1,4 @@
// +build !avr,!nrf,!sam,!sifive,!stm32
// +build !baremetal
package machine
+73
View File
@@ -5,6 +5,12 @@ package machine
import (
"device/arm"
"device/nrf"
"errors"
)
var (
ErrTxSlicesRequired = errors.New("SPI Tx requires a write or read slice, or both")
ErrTxInvalidSliceSize = errors.New("SPI write and read slices must be same size")
)
type PinMode uint8
@@ -322,3 +328,70 @@ func (spi SPI) Transfer(w byte) (byte, error) {
// TODO: handle SPI errors
return byte(r), nil
}
// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read
// interface, there must always be the same number of bytes written as bytes read.
// The Tx method knows about this, and offers a few different ways of calling it.
//
// This form sends the bytes in tx buffer, putting the resulting bytes read into the rx buffer.
// Note that the tx and rx buffers must be the same size:
//
// spi.Tx(tx, rx)
//
// This form sends the tx buffer, ignoring the result. Useful for sending "commands" that return zeros
// until all the bytes in the command packet have been received:
//
// spi.Tx(tx, nil)
//
// This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet":
//
// spi.Tx(nil, rx)
//
func (spi SPI) Tx(w, r []byte) error {
if w == nil && r == nil {
return ErrTxSlicesRequired
}
var err error
switch {
case len(w) == 0:
// read only, so write zero and read a result.
for i := range r {
r[i], err = spi.Transfer(0)
if err != nil {
return err
}
}
case len(r) == 0:
// write only
spi.Bus.TXD.Set(uint32(w[0]))
w = w[1:]
for _, b := range w {
spi.Bus.TXD.Set(uint32(b))
for spi.Bus.EVENTS_READY.Get() == 0 {
}
_ = spi.Bus.RXD.Get()
spi.Bus.EVENTS_READY.Set(0)
}
for spi.Bus.EVENTS_READY.Get() == 0 {
}
_ = spi.Bus.RXD.Get()
spi.Bus.EVENTS_READY.Set(0)
default:
// write/read
if len(w) != len(r) {
return ErrTxInvalidSliceSize
}
for i, b := range w {
r[i], err = spi.Transfer(b)
if err != nil {
return err
}
}
}
return nil
}
+59 -45
View File
@@ -99,18 +99,21 @@ func (p Pin) Set(high bool) {
}
}
// Get returns the current value of a GPIO pin.
func (p Pin) Get() bool {
port := p.getPort()
pin := uint8(p) % 16
val := port.IDR.Get() & (1 << pin)
return (val > 0)
}
// UART
type UART struct {
Buffer *RingBuffer
Bus *stm32.USART_Type
IRQVal uint32
}
var (
// USART1 is the first hardware serial port on the STM32.
// Both UART0 and UART1 refer to USART1.
UART0 = UART{Buffer: NewRingBuffer()}
UART1 = &UART0
)
// Configure the UART.
func (uart UART) Configure(config UARTConfig) {
// Default baud rate to 115200.
@@ -120,53 +123,63 @@ func (uart UART) Configure(config UARTConfig) {
// pins
switch config.TX {
case PB6:
// use alternate TX/RX pins PB6/PB7 via AFIO mapping
case UART_ALT_TX_PIN:
// use alternate TX/RX pins via AFIO mapping
stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_AFIOEN)
stm32.AFIO.MAPR.SetBits(stm32.AFIO_MAPR_USART1_REMAP)
PB6.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull})
PB7.Configure(PinConfig{Mode: PinInputModeFloating})
if uart.Bus == stm32.USART1 {
stm32.AFIO.MAPR.SetBits(stm32.AFIO_MAPR_USART1_REMAP)
} else if uart.Bus == stm32.USART2 {
stm32.AFIO.MAPR.SetBits(stm32.AFIO_MAPR_USART2_REMAP)
}
UART_ALT_TX_PIN.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull})
UART_ALT_RX_PIN.Configure(PinConfig{Mode: PinInputModeFloating})
default:
// use standard TX/RX pins PA9 and PA10
UART_TX_PIN.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull})
UART_RX_PIN.Configure(PinConfig{Mode: PinInputModeFloating})
}
// Enable USART1 clock
stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN)
// Enable USART clock
if uart.Bus == stm32.USART1 {
stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN)
} else if uart.Bus == stm32.USART2 {
stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART2EN)
}
// Set baud rate
uart.SetBaudRate(config.BaudRate)
// Enable USART1 port.
stm32.USART1.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE)
// Enable USART port
uart.Bus.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE)
// Enable RX IRQ.
arm.SetPriority(stm32.IRQ_USART1, 0xc0)
arm.EnableIRQ(stm32.IRQ_USART1)
// Enable RX IRQ
arm.SetPriority(uart.IRQVal, 0xc0)
arm.EnableIRQ(uart.IRQVal)
}
// SetBaudRate sets the communication speed for the UART.
func (uart UART) SetBaudRate(br uint32) {
// first divide by PCLK2 prescaler (div 1) and then desired baudrate
divider := CPU_FREQUENCY / br
stm32.USART1.BRR.Set(divider)
// Note: PCLK2 (from APB2) used for USART1 and PCLK1 for USART2, 3, 4, 5
var divider uint32
if uart.Bus == stm32.USART1 {
// first divide by PCLK2 prescaler (div 1) and then desired baudrate
divider = CPU_FREQUENCY / br
} else {
// first divide by PCLK1 prescaler (div 2) and then desired baudrate
divider = CPU_FREQUENCY / 2 / br
}
uart.Bus.BRR.Set(divider)
}
// WriteByte writes a byte of data to the UART.
func (uart UART) WriteByte(c byte) error {
stm32.USART1.DR.Set(uint32(c))
uart.Bus.DR.Set(uint32(c))
for !stm32.USART1.SR.HasBits(stm32.USART_SR_TXE) {
for !uart.Bus.SR.HasBits(stm32.USART_SR_TXE) {
}
return nil
}
//go:export USART1_IRQHandler
func handleUART1() {
UART1.Receive(byte((stm32.USART1.DR.Get() & 0xFF)))
}
// SPI on the STM32.
type SPI struct {
Bus *stm32.SPI_Type
@@ -202,24 +215,25 @@ func (spi SPI) Configure(config SPIConfig) {
var conf uint32
// set frequency
// set frequency dependent on PCLK2 prescaler (div 1)
switch config.Frequency {
case 125000:
conf |= stm32.SPI_BaudRatePrescaler_128
// Note: impossible to achieve lower frequency with current PCLK2!
conf |= stm32.SPI_BaudRatePrescaler_256
case 250000:
conf |= stm32.SPI_BaudRatePrescaler_64
conf |= stm32.SPI_BaudRatePrescaler_256
case 500000:
conf |= stm32.SPI_BaudRatePrescaler_32
case 1000000:
conf |= stm32.SPI_BaudRatePrescaler_16
case 2000000:
conf |= stm32.SPI_BaudRatePrescaler_8
case 4000000:
conf |= stm32.SPI_BaudRatePrescaler_4
case 8000000:
conf |= stm32.SPI_BaudRatePrescaler_2
default:
conf |= stm32.SPI_BaudRatePrescaler_128
case 1000000:
conf |= stm32.SPI_BaudRatePrescaler_64
case 2000000:
conf |= stm32.SPI_BaudRatePrescaler_32
case 4000000:
conf |= stm32.SPI_BaudRatePrescaler_16
case 8000000:
conf |= stm32.SPI_BaudRatePrescaler_8
default:
conf |= stm32.SPI_BaudRatePrescaler_256
}
// set bit transfer order
@@ -345,11 +359,11 @@ func (i2c I2C) Configure(config I2CConfig) {
// Disable the selected I2C peripheral to configure
i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE)
// pclk1 clock speed is main frequency divided by PCK1 prescaler (div 2)
// pclk1 clock speed is main frequency divided by PCLK1 prescaler (div 2)
pclk1 := uint32(CPU_FREQUENCY / 2)
// set freqency range to pclk1 clock speed in Mhz.
// aka setting the value 36 means to use 36MhZ clock.
// set freqency range to PCLK1 clock speed in MHz
// aka setting the value 36 means to use 36 MHz clock
pclk1Mhz := pclk1 / 1000000
i2c.Bus.CR2.SetBits(pclk1Mhz)
+1 -1
View File
@@ -1,4 +1,4 @@
// +build !stm32f407,!avr,!hifive1b
// +build sam stm32,!stm32f407
package machine
+3 -3
View File
@@ -316,8 +316,8 @@ func NewCDCDescriptor(i IADDescriptor, c InterfaceDescriptor,
callm CMFunctionalDescriptor,
ci EndpointDescriptor,
di InterfaceDescriptor,
inp EndpointDescriptor,
outp EndpointDescriptor) CDCDescriptor {
outp EndpointDescriptor,
inp EndpointDescriptor) CDCDescriptor {
return CDCDescriptor{iad: i,
cif: c,
header: h,
@@ -352,8 +352,8 @@ func (d CDCDescriptor) Bytes() []byte {
buf.Write(d.callManagement.Bytes())
buf.Write(d.cifin.Bytes())
buf.Write(d.dif.Bytes())
buf.Write(d.in.Bytes())
buf.Write(d.out.Bytes())
buf.Write(d.in.Bytes())
return buf.Bytes()
}
+1 -1
View File
@@ -1,4 +1,4 @@
// +build avr cortexm tinygo.riscv wasm
// +build baremetal wasm
package os
+1 -1
View File
@@ -1,4 +1,4 @@
// +build darwin linux,!avr,!cortexm,!tinygo.riscv
// +build darwin linux,!baremetal
package os
+60
View File
@@ -0,0 +1,60 @@
package reflect
import (
"unsafe"
)
// This stores a varint for each named type. Named types are identified by their
// name instead of by their type. The named types stored in this struct are
// non-basic types: pointer, struct, and channel.
//go:extern reflect.namedNonBasicTypesSidetable
var namedNonBasicTypesSidetable uintptr
//go:extern reflect.structTypesSidetable
var structTypesSidetable byte
//go:extern reflect.structNamesSidetable
var structNamesSidetable byte
//go:extern reflect.arrayTypesSidetable
var arrayTypesSidetable byte
// readStringSidetable reads a string from the given table (like
// structNamesSidetable) and returns this string. No heap allocation is
// necessary because it makes the string point directly to the raw bytes of the
// table.
func readStringSidetable(table unsafe.Pointer, index uintptr) string {
nameLen, namePtr := readVarint(unsafe.Pointer(uintptr(table) + index))
return *(*string)(unsafe.Pointer(&StringHeader{
Data: uintptr(namePtr),
Len: nameLen,
}))
}
// readVarint decodes a varint as used in the encoding/binary package.
// It has an input pointer and returns the read varint and the pointer
// incremented to the next field in the data structure, just after the varint.
//
// Details:
// https://github.com/golang/go/blob/e37a1b1c/src/encoding/binary/varint.go#L7-L25
func readVarint(buf unsafe.Pointer) (uintptr, unsafe.Pointer) {
var n uintptr
shift := uintptr(0)
for {
// Read the next byte in the buffer.
c := *(*byte)(buf)
// Decode the bits from this byte and add them to the output number.
n |= uintptr(c&0x7f) << shift
shift += 7
// Increment the buf pointer (pointer arithmetic!).
buf = unsafe.Pointer(uintptr(buf) + 1)
// Check whether this is the last byte of this varint. The upper bit
// (msb) indicates whether any bytes follow.
if c>>7 == 0 {
return n, buf
}
}
}
+269 -12
View File
@@ -142,34 +142,153 @@ func (t Type) Kind() Kind {
}
}
// Elem returns the element type for channel, slice and array types, the
// pointed-to value for pointer types, and the key type for map types.
func (t Type) Elem() Type {
switch t.Kind() {
case Chan, Ptr, Slice:
if (t>>4)%2 != 0 {
panic("unimplemented: (reflect.Type).Elem() for named types")
}
return t >> 5
default: // not implemented: Array, Map
return t.stripPrefix()
case Array:
index := t.stripPrefix()
elem, _ := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&arrayTypesSidetable)) + uintptr(index)))
return Type(elem)
default: // not implemented: Map
panic("unimplemented: (reflect.Type).Elem()")
}
}
// stripPrefix removes the "prefix" (the first 5 bytes of the type code) from
// the type code. If this is a named type, it will resolve the underlying type
// (which is the data for this named type). If it is not, the lower bits are
// simply shifted off.
//
// The behavior is only defined for non-basic types.
func (t Type) stripPrefix() Type {
// Look at the 'n' bit in the type code (see the top of this file) to see
// whether this is a named type.
if (t>>4)%2 != 0 {
// This is a named type. The data is stored in a sidetable.
namedTypeNum := t >> 5
n := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&namedNonBasicTypesSidetable)) + uintptr(namedTypeNum)*unsafe.Sizeof(uintptr(0))))
return Type(n)
}
// Not a named type, so the value is stored directly in the type code.
return t >> 5
}
// Field returns the type of the i'th field of this struct type. It panics if t
// is not a struct type.
func (t Type) Field(i int) StructField {
panic("unimplemented: (reflect.Type).Field()")
if t.Kind() != Struct {
panic(&TypeError{"Field"})
}
structIdentifier := t.stripPrefix()
numField, p := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&structTypesSidetable)) + uintptr(structIdentifier)))
if uint(i) >= uint(numField) {
panic("reflect: field index out of range")
}
// Iterate over every field in the struct and update the StructField each
// time, until the target field has been reached. This is very much not
// efficient, but it is easy to implement.
// Adding a jump table at the start to jump to the field directly would
// make this much faster, but that would also impact code size.
field := StructField{}
offset := uintptr(0)
for fieldNum := 0; fieldNum <= i; fieldNum++ {
// Read some flags of this field, like whether the field is an
// embedded field.
flagsByte := *(*uint8)(p)
p = unsafe.Pointer(uintptr(p) + 1)
// Read the type of this struct field.
var fieldType uintptr
fieldType, p = readVarint(p)
field.Type = Type(fieldType)
// Move Offset forward to align it to this field's alignment.
// Assume alignment is a power of two.
offset = align(offset, uintptr(field.Type.Align()))
field.Offset = offset
offset += field.Type.Size() // starting (unaligned) offset for next field
// Read the field name.
var nameNum uintptr
nameNum, p = readVarint(p)
field.Name = readStringSidetable(unsafe.Pointer(&structNamesSidetable), nameNum)
// The first bit in the flagsByte indicates whether this is an embedded
// field.
field.Anonymous = flagsByte&1 != 0
// The second bit indicates whether there is a tag.
if flagsByte&2 != 0 {
// There is a tag.
var tagNum uintptr
tagNum, p = readVarint(p)
field.Tag = readStringSidetable(unsafe.Pointer(&structNamesSidetable), tagNum)
} else {
// There is no tag.
field.Tag = ""
}
// The third bit indicates whether this field is exported.
if flagsByte&4 != 0 {
// This field is exported.
field.PkgPath = ""
} else {
// This field is unexported.
// TODO: list the real package path here. Storing it should not
// significantly impact binary size as there is only a limited
// number of packages in any program.
field.PkgPath = "<unimplemented>"
}
}
return field
}
// Bits returns the number of bits that this type uses. It is only valid for
// arithmetic types (integers, floats, and complex numbers). For other types, it
// will panic.
func (t Type) Bits() int {
panic("unimplemented: (reflect.Type).Bits()")
kind := t.Kind()
if kind >= Int && kind <= Complex128 {
return int(t.Size()) * 8
}
panic(TypeError{"Bits"})
}
// Len returns the number of elements in this array. It panics of the type kind
// is not Array.
func (t Type) Len() int {
panic("unimplemented: (reflect.Type).Len()")
if t.Kind() != Array {
panic(TypeError{"Len"})
}
// skip past the element type
arrayIdentifier := t.stripPrefix()
_, p := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&arrayTypesSidetable)) + uintptr(arrayIdentifier)))
// Read the array length.
arrayLen, _ := readVarint(p)
return int(arrayLen)
}
// NumField returns the number of fields of a struct type. It panics for other
// type kinds.
func (t Type) NumField() int {
panic("unimplemented: (reflect.Type).NumField()")
if t.Kind() != Struct {
panic(&TypeError{"NumField"})
}
structIdentifier := t.stripPrefix()
n, _ := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&structTypesSidetable)) + uintptr(structIdentifier)))
return int(n)
}
// Size returns the size in bytes of a given type. It is similar to
// unsafe.Sizeof.
func (t Type) Size() uintptr {
switch t.Kind() {
case Bool, Int8, Uint8:
@@ -198,12 +317,150 @@ func (t Type) Size() uintptr {
return unsafe.Sizeof(uintptr(0))
case Slice:
return unsafe.Sizeof(SliceHeader{})
case Interface:
return unsafe.Sizeof(interfaceHeader{})
case Array:
return t.Elem().Size() * uintptr(t.Len())
case Struct:
numField := t.NumField()
if numField == 0 {
return 0
}
lastField := t.Field(numField - 1)
return lastField.Offset + lastField.Type.Size()
default:
panic("unimplemented: size of type")
}
}
type StructField struct {
Name string
Type Type
// Align returns the alignment of this type. It is similar to calling
// unsafe.Alignof.
func (t Type) Align() int {
switch t.Kind() {
case Bool, Int8, Uint8:
return int(unsafe.Alignof(int8(0)))
case Int16, Uint16:
return int(unsafe.Alignof(int16(0)))
case Int32, Uint32:
return int(unsafe.Alignof(int32(0)))
case Int64, Uint64:
return int(unsafe.Alignof(int64(0)))
case Int, Uint:
return int(unsafe.Alignof(int(0)))
case Uintptr:
return int(unsafe.Alignof(uintptr(0)))
case Float32:
return int(unsafe.Alignof(float32(0)))
case Float64:
return int(unsafe.Alignof(float64(0)))
case Complex64:
return int(unsafe.Alignof(complex64(0)))
case Complex128:
return int(unsafe.Alignof(complex128(0)))
case String:
return int(unsafe.Alignof(StringHeader{}))
case UnsafePointer, Chan, Map, Ptr:
return int(unsafe.Alignof(uintptr(0)))
case Slice:
return int(unsafe.Alignof(SliceHeader{}))
case Interface:
return int(unsafe.Alignof(interfaceHeader{}))
case Struct:
numField := t.NumField()
alignment := 1
for i := 0; i < numField; i++ {
fieldAlignment := t.Field(i).Type.Align()
if fieldAlignment > alignment {
alignment = fieldAlignment
}
}
return alignment
default:
panic("unimplemented: alignment of type")
}
}
// FieldAlign returns the alignment if this type is used in a struct field. It
// is currently an alias for Align() but this might change in the future.
func (t Type) FieldAlign() int {
return t.Align()
}
// AssignableTo returns whether a value of type u can be assigned to a variable
// of type t.
func (t Type) AssignableTo(u Type) bool {
if t == u {
return true
}
if t.Kind() == Interface {
panic("reflect: unimplemented: assigning to interface of different type")
}
return false
}
// Comparable returns whether values of this type can be compared to each other.
func (t Type) Comparable() bool {
switch t.Kind() {
case Bool, Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
return true
case Float32, Float64, Complex64, Complex128:
return true
case String:
return true
case UnsafePointer:
return true
case Chan:
return true
case Interface:
return true
case Ptr:
return true
case Slice:
return false
case Array:
return t.Elem().Comparable()
case Func:
return false
case Map:
return false
case Struct:
numField := t.NumField()
for i := 0; i < numField; i++ {
if !t.Field(i).Type.Comparable() {
return false
}
}
return true
default:
panic(TypeError{"Comparable"})
}
}
// A StructField describes a single field in a struct.
type StructField struct {
// Name indicates the field name.
Name string
// PkgPath is the package path where the struct containing this field is
// declared for unexported fields, or the empty string for exported fields.
PkgPath string
Type Type
Tag string
Anonymous bool
Offset uintptr
}
// TypeError is the error that is used in a panic when invoking a method on a
// type that is not applicable to that type.
type TypeError struct {
Method string
}
func (e *TypeError) Error() string {
return "reflect: call of reflect.Type." + e.Method + " on invalid type"
}
func align(offset uintptr, alignment uintptr) uintptr {
return (offset + alignment - 1) &^ (alignment - 1)
}
+195 -53
View File
@@ -4,10 +4,28 @@ import (
"unsafe"
)
type valueFlags uint8
// Flags list some useful flags that contain some extra information not
// contained in an interface{} directly, like whether this value was exported at
// all (it is possible to read unexported fields using reflection, but it is not
// possible to modify them).
const (
valueFlagIndirect valueFlags = 1 << iota
valueFlagExported
)
type Value struct {
typecode Type
value unsafe.Pointer
indirect bool
flags valueFlags
}
// isIndirect returns whether the value pointer in this Value is always a
// pointer to the value. If it is false, it is only a pointer to the value if
// the value is bigger than a pointer.
func (v Value) isIndirect() bool {
return v.flags&valueFlagIndirect != 0
}
func Indirect(v Value) Value {
@@ -22,6 +40,7 @@ func ValueOf(i interface{}) Value {
return Value{
typecode: v.typecode,
value: v.value,
flags: valueFlagExported,
}
}
@@ -30,7 +49,7 @@ func (v Value) Interface() interface{} {
typecode: v.typecode,
value: v.value,
}
if v.indirect && v.Type().Size() <= unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() && v.Type().Size() <= unsafe.Sizeof(uintptr(0)) {
// Value was indirect but must be put back directly in the interface
// value.
var value uintptr
@@ -50,9 +69,14 @@ func (v Value) Kind() Kind {
return v.Type().Kind()
}
// IsNil returns whether the value is the nil value. It panics if the value Kind
// is not a channel, map, pointer, function, slice, or interface.
func (v Value) IsNil() bool {
switch v.Kind() {
case Chan, Map, Ptr:
if v.isIndirect() {
return *(*uintptr)(v.value) == 0
}
return v.value == nil
case Func:
if v.value == nil {
@@ -77,9 +101,14 @@ func (v Value) IsNil() bool {
}
}
// Pointer returns the underlying pointer of the given value for the following
// types: chan, map, pointer, unsafe.Pointer, slice, func.
func (v Value) Pointer() uintptr {
switch v.Kind() {
case Chan, Map, Ptr, UnsafePointer:
if v.isIndirect() {
return *(*uintptr)(v.value)
}
return uintptr(v.value)
case Slice:
slice := (*SliceHeader)(v.value)
@@ -109,13 +138,13 @@ func (v Value) Addr() Value {
}
func (v Value) CanSet() bool {
return v.indirect
return v.flags&(valueFlagExported|valueFlagIndirect) == valueFlagExported|valueFlagIndirect
}
func (v Value) Bool() bool {
switch v.Kind() {
case Bool:
if v.indirect {
if v.isIndirect() {
return *((*bool)(v.value))
} else {
return uintptr(v.value) != 0
@@ -128,31 +157,31 @@ func (v Value) Bool() bool {
func (v Value) Int() int64 {
switch v.Kind() {
case Int:
if v.indirect || unsafe.Sizeof(int(0)) > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() || unsafe.Sizeof(int(0)) > unsafe.Sizeof(uintptr(0)) {
return int64(*(*int)(v.value))
} else {
return int64(int(uintptr(v.value)))
}
case Int8:
if v.indirect {
if v.isIndirect() {
return int64(*(*int8)(v.value))
} else {
return int64(int8(uintptr(v.value)))
}
case Int16:
if v.indirect {
if v.isIndirect() {
return int64(*(*int16)(v.value))
} else {
return int64(int16(uintptr(v.value)))
}
case Int32:
if v.indirect || unsafe.Sizeof(int32(0)) > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() || unsafe.Sizeof(int32(0)) > unsafe.Sizeof(uintptr(0)) {
return int64(*(*int32)(v.value))
} else {
return int64(int32(uintptr(v.value)))
}
case Int64:
if v.indirect || unsafe.Sizeof(int64(0)) > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() || unsafe.Sizeof(int64(0)) > unsafe.Sizeof(uintptr(0)) {
return int64(*(*int64)(v.value))
} else {
return int64(int64(uintptr(v.value)))
@@ -165,37 +194,37 @@ func (v Value) Int() int64 {
func (v Value) Uint() uint64 {
switch v.Kind() {
case Uintptr:
if v.indirect {
if v.isIndirect() {
return uint64(*(*uintptr)(v.value))
} else {
return uint64(uintptr(v.value))
}
case Uint8:
if v.indirect {
if v.isIndirect() {
return uint64(*(*uint8)(v.value))
} else {
return uint64(uintptr(v.value))
}
case Uint16:
if v.indirect {
if v.isIndirect() {
return uint64(*(*uint16)(v.value))
} else {
return uint64(uintptr(v.value))
}
case Uint:
if v.indirect || unsafe.Sizeof(uint(0)) > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() || unsafe.Sizeof(uint(0)) > unsafe.Sizeof(uintptr(0)) {
return uint64(*(*uint)(v.value))
} else {
return uint64(uintptr(v.value))
}
case Uint32:
if v.indirect || unsafe.Sizeof(uint32(0)) > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() || unsafe.Sizeof(uint32(0)) > unsafe.Sizeof(uintptr(0)) {
return uint64(*(*uint32)(v.value))
} else {
return uint64(uintptr(v.value))
}
case Uint64:
if v.indirect || unsafe.Sizeof(uint64(0)) > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() || unsafe.Sizeof(uint64(0)) > unsafe.Sizeof(uintptr(0)) {
return uint64(*(*uint64)(v.value))
} else {
return uint64(uintptr(v.value))
@@ -208,7 +237,7 @@ func (v Value) Uint() uint64 {
func (v Value) Float() float64 {
switch v.Kind() {
case Float32:
if v.indirect || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) {
// The float is stored as an external value on systems with 16-bit
// pointers.
return float64(*(*float32)(v.value))
@@ -218,7 +247,7 @@ func (v Value) Float() float64 {
return float64(*(*float32)(unsafe.Pointer(&v.value)))
}
case Float64:
if v.indirect || unsafe.Sizeof(float64(0)) > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() || unsafe.Sizeof(float64(0)) > unsafe.Sizeof(uintptr(0)) {
// For systems with 16-bit and 32-bit pointers.
return *(*float64)(v.value)
} else {
@@ -234,7 +263,7 @@ func (v Value) Float() float64 {
func (v Value) Complex() complex128 {
switch v.Kind() {
case Complex64:
if v.indirect || unsafe.Sizeof(complex64(0)) > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() || unsafe.Sizeof(complex64(0)) > unsafe.Sizeof(uintptr(0)) {
// The complex number is stored as an external value on systems with
// 16-bit and 32-bit pointers.
return complex128(*(*complex64)(v.value))
@@ -273,6 +302,8 @@ func (v Value) Slice(i, j int) Value {
panic("unimplemented: (reflect.Value).Slice()")
}
// Len returns the length of this value for slices, strings, arrays, channels,
// and maps. For oter types, it panics.
func (v Value) Len() int {
t := v.Type()
switch t.Kind() {
@@ -280,7 +311,9 @@ func (v Value) Len() int {
return int((*SliceHeader)(v.value).Len)
case String:
return int((*StringHeader)(v.value).Len)
default: // Array, Chan, Map
case Array:
return v.Type().Len()
default: // Chan, Map
panic("unimplemented: (reflect.Value).Len()")
}
}
@@ -295,15 +328,17 @@ func (v Value) Cap() int {
}
}
// NumField returns the number of fields of this struct. It panics for other
// value types.
func (v Value) NumField() int {
panic("unimplemented: (reflect.Value).NumField()")
return v.Type().NumField()
}
func (v Value) Elem() Value {
switch v.Kind() {
case Ptr:
ptr := v.value
if v.indirect {
if v.isIndirect() {
ptr = *(*unsafe.Pointer)(ptr)
}
if ptr == nil {
@@ -312,15 +347,71 @@ func (v Value) Elem() Value {
return Value{
typecode: v.Type().Elem(),
value: ptr,
indirect: true,
flags: v.flags | valueFlagIndirect,
}
default: // not implemented: Interface
panic(&ValueError{"Elem"})
}
}
// Field returns the value of the i'th field of this struct.
func (v Value) Field(i int) Value {
panic("unimplemented: (reflect.Value).Field()")
structField := v.Type().Field(i)
flags := v.flags
if structField.PkgPath != "" {
// The fact that PkgPath is present means that this field is not
// exported.
flags &^= valueFlagExported
}
size := v.Type().Size()
fieldSize := structField.Type.Size()
if v.isIndirect() || fieldSize > unsafe.Sizeof(uintptr(0)) {
// v.value was already a pointer to the value and it should stay that
// way.
return Value{
flags: flags,
typecode: structField.Type,
value: unsafe.Pointer(uintptr(v.value) + structField.Offset),
}
}
// The fieldSize is smaller than uintptr, which means that the value will
// have to be stored directly in the interface value.
if fieldSize == 0 {
// The struct field is zero sized.
// This is a rare situation, but because it's undefined behavior
// to shift the size of the value (zeroing the value), handle this
// situation explicitly.
return Value{
flags: flags,
typecode: structField.Type,
value: unsafe.Pointer(uintptr(0)),
}
}
if size > unsafe.Sizeof(uintptr(0)) {
// The value was not stored in the interface before but will be
// afterwards, so load the value (from the correct offset) and return
// it.
ptr := unsafe.Pointer(uintptr(v.value) + structField.Offset)
value := unsafe.Pointer(loadValue(ptr, fieldSize))
return Value{
flags: 0,
typecode: structField.Type,
value: value,
}
}
// The value was already stored directly in the interface and it still
// is. Cut out the part of the value that we need.
value := maskAndShift(uintptr(v.value), structField.Offset, fieldSize)
return Value{
flags: flags,
typecode: structField.Type,
value: unsafe.Pointer(value),
}
}
func (v Value) Index(i int) Value {
@@ -333,7 +424,7 @@ func (v Value) Index(i int) Value {
}
elem := Value{
typecode: v.Type().Elem(),
indirect: true,
flags: v.flags | valueFlagIndirect,
}
addr := uintptr(slice.Data) + elem.Type().Size()*uintptr(i) // pointer to new value
elem.value = unsafe.Pointer(addr)
@@ -351,12 +442,75 @@ func (v Value) Index(i int) Value {
value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Pointer(s.Data + uintptr(i))))),
}
case Array:
panic("unimplemented: (reflect.Value).Index()")
// Extract an element from the array.
elemType := v.Type().Elem()
elemSize := elemType.Size()
size := v.Type().Size()
if size == 0 {
// The element size is 0 and/or the length of the array is 0.
return Value{
typecode: v.Type().Elem(),
flags: v.flags,
}
}
if elemSize > unsafe.Sizeof(uintptr(0)) {
// The resulting value doesn't fit in a pointer so must be
// indirect. Also, because size != 0 this implies that the array
// length must be != 0, and thus that the total size is at least
// elemSize.
addr := uintptr(v.value) + elemSize*uintptr(i) // pointer to new value
return Value{
typecode: v.Type().Elem(),
flags: v.flags,
value: unsafe.Pointer(addr),
}
}
if size > unsafe.Sizeof(uintptr(0)) {
// The element fits in a pointer, but the array does not.
// Load the value from the pointer.
addr := uintptr(v.value) + elemSize*uintptr(i) // pointer to new value
return Value{
typecode: v.Type().Elem(),
flags: v.flags,
value: unsafe.Pointer(loadValue(unsafe.Pointer(addr), elemSize)),
}
}
// The value fits in a pointer, so extract it with some shifting and
// masking.
offset := elemSize * uintptr(i)
value := maskAndShift(uintptr(v.value), offset, elemSize)
return Value{
typecode: v.Type().Elem(),
flags: v.flags,
value: unsafe.Pointer(value),
}
default:
panic(&ValueError{"Index"})
}
}
// loadValue loads a value that may or may not be word-aligned. The number of
// bytes given in size are loaded. The biggest possible size it can load is that
// of an uintptr.
func loadValue(ptr unsafe.Pointer, size uintptr) uintptr {
loadedValue := uintptr(0)
shift := uintptr(0)
for i := uintptr(0); i < size; i++ {
loadedValue |= uintptr(*(*byte)(ptr)) << shift
shift += 8
ptr = unsafe.Pointer(uintptr(ptr) + 1)
}
return loadedValue
}
// maskAndShift cuts out a part of a uintptr. Note that the offset may not be 0.
func maskAndShift(value, offset, size uintptr) uintptr {
mask := ^uintptr(0) >> ((unsafe.Sizeof(uintptr(0)) - size) * 8)
return (uintptr(value) >> (offset * 8)) & mask
}
func (v Value) MapKeys() []Value {
panic("unimplemented: (reflect.Value).MapKeys()")
}
@@ -385,19 +539,13 @@ func (it *MapIter) Next() bool {
}
func (v Value) Set(x Value) {
if !v.indirect {
panic("reflect: value is not addressable")
}
if v.Type() != x.Type() {
if v.Kind() == Interface {
panic("reflect: unimplemented: assigning to interface of different type")
} else {
panic("reflect: cannot assign")
}
v.checkAddressable()
if !v.Type().AssignableTo(x.Type()) {
panic("reflect: cannot set")
}
size := v.Type().Size()
xptr := x.value
if size <= unsafe.Sizeof(uintptr(0)) && !x.indirect {
if size <= unsafe.Sizeof(uintptr(0)) && !x.isIndirect() {
value := x.value
xptr = unsafe.Pointer(&value)
}
@@ -405,9 +553,7 @@ func (v Value) Set(x Value) {
}
func (v Value) SetBool(x bool) {
if !v.indirect {
panic("reflect: value is not addressable")
}
v.checkAddressable()
switch v.Kind() {
case Bool:
*(*bool)(v.value) = x
@@ -417,9 +563,7 @@ func (v Value) SetBool(x bool) {
}
func (v Value) SetInt(x int64) {
if !v.indirect {
panic("reflect: value is not addressable")
}
v.checkAddressable()
switch v.Kind() {
case Int:
*(*int)(v.value) = int(x)
@@ -437,9 +581,7 @@ func (v Value) SetInt(x int64) {
}
func (v Value) SetUint(x uint64) {
if !v.indirect {
panic("reflect: value is not addressable")
}
v.checkAddressable()
switch v.Kind() {
case Uint:
*(*uint)(v.value) = uint(x)
@@ -459,9 +601,7 @@ func (v Value) SetUint(x uint64) {
}
func (v Value) SetFloat(x float64) {
if !v.indirect {
panic("reflect: value is not addressable")
}
v.checkAddressable()
switch v.Kind() {
case Float32:
*(*float32)(v.value) = float32(x)
@@ -473,9 +613,7 @@ func (v Value) SetFloat(x float64) {
}
func (v Value) SetComplex(x complex128) {
if !v.indirect {
panic("reflect: value is not addressable")
}
v.checkAddressable()
switch v.Kind() {
case Complex64:
*(*complex64)(v.value) = complex64(x)
@@ -487,9 +625,7 @@ func (v Value) SetComplex(x complex128) {
}
func (v Value) SetString(x string) {
if !v.indirect {
panic("reflect: value is not addressable")
}
v.checkAddressable()
switch v.Kind() {
case String:
*(*string)(v.value) = x
@@ -498,6 +634,12 @@ func (v Value) SetString(x string) {
}
}
func (v Value) checkAddressable() {
if !v.isIndirect() {
panic("reflect: value is not addressable")
}
}
func MakeSlice(typ Type, len, cap int) Value {
panic("unimplemented: reflect.MakeSlice()")
}
+2
View File
@@ -9,3 +9,5 @@ const TargetBits = 32
func align(ptr uintptr) uintptr {
return (ptr + 3) &^ 3
}
func getCurrentStackPointer() uintptr
+2
View File
@@ -11,3 +11,5 @@ const TargetBits = 64
func align(ptr uintptr) uintptr {
return (ptr + 15) &^ 15
}
func getCurrentStackPointer() uintptr
+7 -1
View File
@@ -1,7 +1,9 @@
// +build arm,!avr,!cortexm,!tinygo.riscv
// +build arm,!baremetal arm,arm7tdmi
package runtime
import "device/arm"
const GOARCH = "arm"
// The bitness of the CPU (e.g. 8, 32, 64).
@@ -11,3 +13,7 @@ const TargetBits = 32
func align(ptr uintptr) uintptr {
return (ptr + 3) &^ 3
}
func getCurrentStackPointer() uintptr {
return arm.ReadRegister("sp")
}
+2
View File
@@ -9,3 +9,5 @@ const TargetBits = 64
func align(ptr uintptr) uintptr {
return (ptr + 7) &^ 7
}
func getCurrentStackPointer() uintptr
+2
View File
@@ -12,3 +12,5 @@ func align(ptr uintptr) uintptr {
// No alignment necessary on the AVR.
return ptr
}
func getCurrentStackPointer() uintptr
+2
View File
@@ -28,3 +28,5 @@ const wasmPageSize = 64 * 1024
func align(ptr uintptr) uintptr {
return (ptr + 3) &^ 3
}
func getCurrentStackPointer() uintptr
+1 -1
View File
@@ -1,4 +1,4 @@
// +build avr cortexm tinygo.riscv
// +build baremetal
package runtime
+48 -37
View File
@@ -30,7 +30,7 @@ import (
type channel struct {
elementSize uint16 // the size of one value in this channel
state chanState
blocked *coroutine
blocked *task
}
type chanState uint8
@@ -50,40 +50,44 @@ type chanSelectState struct {
value unsafe.Pointer
}
func deadlockStub()
// 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.
func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer) {
func chanSend(sender *task, ch *channel, value unsafe.Pointer) {
if ch == nil {
// A nil channel blocks forever. Do not scheduler this goroutine again.
chanYield()
return
}
switch ch.state {
case chanStateEmpty:
sender.promise().ptr = value
scheduleLogChan(" send: chan is empty ", ch, sender)
sender.state().ptr = value
ch.state = chanStateSend
ch.blocked = sender
chanYield()
case chanStateRecv:
scheduleLogChan(" send: chan in recv mode", ch, sender)
receiver := ch.blocked
receiverPromise := receiver.promise()
memcpy(receiverPromise.ptr, value, uintptr(ch.elementSize))
receiverPromise.data = 1 // commaOk = true
ch.blocked = receiverPromise.next
receiverPromise.next = nil
receiverState := receiver.state()
memcpy(receiverState.ptr, value, uintptr(ch.elementSize))
receiverState.data = 1 // commaOk = true
ch.blocked = receiverState.next
receiverState.next = nil
activateTask(receiver)
activateTask(sender)
reactivateParent(sender)
if ch.blocked == nil {
ch.state = chanStateEmpty
}
case chanStateClosed:
runtimePanic("send on closed channel")
case chanStateSend:
sender.promise().ptr = value
sender.promise().next = ch.blocked
scheduleLogChan(" send: chan in send mode", ch, sender)
sender.state().ptr = value
sender.state().next = ch.blocked
ch.blocked = sender
chanYield()
}
}
@@ -91,36 +95,43 @@ func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer) {
// 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.
func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer) {
func chanRecv(receiver *task, ch *channel, value unsafe.Pointer) {
if ch == nil {
// A nil channel blocks forever. Do not scheduler this goroutine again.
chanYield()
return
}
switch ch.state {
case chanStateSend:
scheduleLogChan(" recv: chan in send mode", ch, receiver)
sender := ch.blocked
senderPromise := sender.promise()
memcpy(value, senderPromise.ptr, uintptr(ch.elementSize))
receiver.promise().data = 1 // commaOk = true
ch.blocked = senderPromise.next
senderPromise.next = nil
activateTask(receiver)
senderState := sender.state()
memcpy(value, senderState.ptr, uintptr(ch.elementSize))
receiver.state().data = 1 // commaOk = true
ch.blocked = senderState.next
senderState.next = nil
reactivateParent(receiver)
activateTask(sender)
if ch.blocked == nil {
ch.state = chanStateEmpty
}
case chanStateEmpty:
receiver.promise().ptr = value
scheduleLogChan(" recv: chan is empty ", ch, receiver)
receiver.state().ptr = value
ch.state = chanStateRecv
ch.blocked = receiver
chanYield()
case chanStateClosed:
scheduleLogChan(" recv: chan is closed ", ch, receiver)
memzero(value, uintptr(ch.elementSize))
receiver.promise().data = 0 // commaOk = false
activateTask(receiver)
receiver.state().data = 0 // commaOk = false
reactivateParent(receiver)
case chanStateRecv:
receiver.promise().ptr = value
receiver.promise().next = ch.blocked
scheduleLogChan(" recv: chan in recv mode", ch, receiver)
receiver.state().ptr = value
receiver.state().next = ch.blocked
ch.blocked = receiver
chanYield()
}
}
@@ -143,9 +154,9 @@ func chanClose(ch *channel) {
runtimePanic("close channel during send")
case chanStateRecv:
// The receiver must be re-activated with a zero value.
receiverPromise := ch.blocked.promise()
memzero(receiverPromise.ptr, uintptr(ch.elementSize))
receiverPromise.data = 0 // commaOk = false
receiverState := ch.blocked.state()
memzero(receiverState.ptr, uintptr(ch.elementSize))
receiverState.data = 0 // commaOk = false
activateTask(ch.blocked)
ch.state = chanStateClosed
ch.blocked = nil
@@ -174,10 +185,10 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, blocking bool)
case chanStateSend:
// We can receive immediately.
sender := state.ch.blocked
senderPromise := sender.promise()
memcpy(recvbuf, senderPromise.ptr, uintptr(state.ch.elementSize))
state.ch.blocked = senderPromise.next
senderPromise.next = nil
senderState := sender.state()
memcpy(recvbuf, senderState.ptr, uintptr(state.ch.elementSize))
state.ch.blocked = senderState.next
senderState.next = nil
activateTask(sender)
if state.ch.blocked == nil {
state.ch.state = chanStateEmpty
@@ -193,11 +204,11 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, blocking bool)
switch state.ch.state {
case chanStateRecv:
receiver := state.ch.blocked
receiverPromise := receiver.promise()
memcpy(receiverPromise.ptr, state.value, uintptr(state.ch.elementSize))
receiverPromise.data = 1 // commaOk = true
state.ch.blocked = receiverPromise.next
receiverPromise.next = nil
receiverState := receiver.state()
memcpy(receiverState.ptr, state.value, uintptr(state.ch.elementSize))
receiverState.data = 1 // commaOk = true
state.ch.blocked = receiverState.next
receiverState.next = nil
activateTask(receiver)
if state.ch.blocked == nil {
state.ch.state = chanStateEmpty
+3 -3
View File
@@ -16,13 +16,13 @@ type funcValue struct {
// funcValueWithSignature is used before the func lowering pass.
type funcValueWithSignature struct {
funcPtr uintptr // ptrtoint of the actual function pointer
signature *uint8 // pointer to identify this signature (the value is undef)
funcPtr uintptr // ptrtoint of the actual function pointer
signature *typecodeID // pointer to identify this signature (the value is undef)
}
// getFuncPtr is a dummy function that may be used if the func lowering pass is
// not used. It is generally too slow but may be a useful fallback to debug the
// func lowering pass.
func getFuncPtr(val funcValue, signature *uint8) uintptr {
func getFuncPtr(val funcValue, signature *typecodeID) uintptr {
return (*funcValueWithSignature)(unsafe.Pointer(val.id)).funcPtr
}
+14
View File
@@ -93,6 +93,9 @@ 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 {
if gcAsserts && (addr < poolStart || addr >= heapEnd) {
runtimePanic("gc: trying to get block from invalid address")
}
return gcBlock((addr - poolStart) / bytesPerBlock)
}
@@ -113,6 +116,11 @@ func (b gcBlock) findHead() gcBlock {
for b.state() == blockStateTail {
b--
}
if gcAsserts {
if b.state() != blockStateHead && b.state() != blockStateMark {
runtimePanic("gc: found tail without head")
}
}
return b
}
@@ -319,6 +327,12 @@ func markRoots(start, end uintptr) {
func markRoot(addr, root uintptr) {
if looksLikePointer(root) {
block := blockFromAddr(root)
if block.state() == blockStateFree {
// The to-be-marked object doesn't actually exist.
// This could either be a dangling pointer (oops!) but most likely
// just a false positive.
return
}
head := block.findHead()
if head.state() != blockStateMark {
if gcDebug {
+1 -1
View File
@@ -1,5 +1,5 @@
// +build gc.conservative
// +build cortexm tinygo.riscv
// +build baremetal
package runtime
+1 -1
View File
@@ -1,5 +1,5 @@
// +build gc.conservative
// +build !cortexm,!tinygo.riscv
// +build !baremetal
package runtime
+1 -1
View File
@@ -1,5 +1,5 @@
// +build gc.conservative
// +build !cortexm,!tinygo.riscv
// +build !baremetal
package runtime
+3 -2
View File
@@ -1,5 +1,5 @@
// +build gc.conservative
// +build cortexm tinygo.riscv
// +build baremetal
package runtime
@@ -9,5 +9,6 @@ package runtime
// the linker) and getting the current stack pointer from a register. Also, it
// assumes a descending stack. Thus, it is not very portable.
func markStack() {
markRoots(getCurrentStackPointer(), stackTop)
// Mark system stack.
markRoots(getSystemStackPointer(), stackTop)
}
+23 -1
View File
@@ -43,7 +43,29 @@ type interfaceMethodInfo struct {
funcptr uintptr // bitcast from the actual function pointer
}
type typecodeID struct{}
type typecodeID struct {
// Depending on the type kind of this typecodeID, this pointer is something
// different:
// * basic types: null
// * named type: the underlying type
// * interface: null
// * chan/pointer/slice/array: the element type
// * struct: bitcast of global with structField array
// * func/map: TODO
references *typecodeID
// The array length, for array types.
length uintptr
}
// structField is used by the compiler to pass information to the interface
// lowering pass. It is not used in the final binary.
type structField struct {
typecode *typecodeID // type of this struct field
name *uint8 // pointer to char array
tag *uint8 // pointer to char array, or nil
embedded bool
}
// Pseudo type used before interface lowering. By using a struct instead of a
// function call, this is simpler to reason about during init interpretation
-5
View File
@@ -79,11 +79,6 @@ func memequal(x, y unsafe.Pointer, n uintptr) bool {
return true
}
//go:linkname sleep time.Sleep
func sleep(d int64) {
sleepTicks(timeUnit(d / tickMicros))
}
func nanotime() int64 {
return int64(ticks()) * tickMicros
}
+92
View File
@@ -0,0 +1,92 @@
// +build arm7tdmi
package runtime
import (
"unsafe"
)
type timeUnit int64
const tickMicros = 1
func putchar(c byte) {
// dummy, TODO
}
//go:extern _sbss
var _sbss unsafe.Pointer
//go:extern _ebss
var _ebss unsafe.Pointer
//go:extern _sdata
var _sdata unsafe.Pointer
//go:extern _sidata
var _sidata unsafe.Pointer
//go:extern _edata
var _edata unsafe.Pointer
// Entry point for Go. Initialize all packages and call main.main().
//go:export main
func main() {
// Initialize .data and .bss sections.
preinit()
// Run initializers of all packages.
initAll()
// Compiler-generated call to main.main().
callMain()
}
func preinit() {
// Initialize .bss: zero-initialized global variables.
ptr := unsafe.Pointer(&_sbss)
for ptr != unsafe.Pointer(&_ebss) {
*(*uint32)(ptr) = 0
ptr = unsafe.Pointer(uintptr(ptr) + 4)
}
// Initialize .data: global variables initialized from flash.
src := unsafe.Pointer(&_sidata)
dst := unsafe.Pointer(&_sdata)
for dst != unsafe.Pointer(&_edata) {
*(*uint32)(dst) = *(*uint32)(src)
dst = unsafe.Pointer(uintptr(dst) + 4)
src = unsafe.Pointer(uintptr(src) + 4)
}
}
func ticks() timeUnit {
// TODO
return 0
}
const asyncScheduler = false
func sleepTicks(d timeUnit) {
// TODO
}
func abort() {
// TODO
for {
}
}
// Implement memset for LLVM and compiler-rt.
//go:export memset
func libc_memset(ptr unsafe.Pointer, c byte, size uintptr) {
for i := uintptr(0); i < size; i++ {
*(*byte)(unsafe.Pointer(uintptr(ptr) + i)) = c
}
}
// Implement memmove for LLVM and compiler-rt.
//go:export memmove
func libc_memmove(dst, src unsafe.Pointer, size uintptr) {
memmove(dst, src, size)
}
+6 -3
View File
@@ -261,9 +261,12 @@ func ticks() timeUnit {
// ticks are in microseconds
func timerSleep(ticks uint32) {
timerWakeup.Set(0)
if ticks < 30 {
// have to have at least one clock count
ticks = 30
if ticks < 214 {
// due to around 183us delay waiting for the register value to sync, the minimum sleep value
// for the SAMD21 is 214us.
// For related info, see:
// https://community.atmel.com/comment/2507091#comment-2507091
ticks = 214
}
// request read of count
+302
View File
@@ -0,0 +1,302 @@
// +build sam,atsamd51
package runtime
import (
"device/arm"
"device/sam"
"machine"
)
type timeUnit int64
//go:export Reset_Handler
func main() {
preinit()
initAll()
callMain()
abort()
}
func init() {
initClocks()
initRTC()
initSERCOMClocks()
initUSBClock()
initADCClock()
// connect to USB CDC interface
machine.UART0.Configure(machine.UARTConfig{})
}
func putchar(c byte) {
machine.UART0.WriteByte(c)
}
func initClocks() {
// set flash wait state
sam.NVMCTRL.CTRLA.SetBits(0 << sam.NVMCTRL_CTRLA_RWS_Pos)
// software reset
sam.GCLK.CTRLA.SetBits(sam.GCLK_CTRLA_SWRST)
for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_SWRST) {
}
// Set OSCULP32K as source of Generic Clock Generator 3
// GCLK->GENCTRL[GENERIC_CLOCK_GENERATOR_XOSC32K].reg = GCLK_GENCTRL_SRC(GCLK_GENCTRL_SRC_OSCULP32K) | GCLK_GENCTRL_GENEN; //generic clock gen 3
sam.GCLK.GENCTRL3.Set((sam.GCLK_GENCTRL_SRC_OSCULP32K << sam.GCLK_GENCTRL_SRC_Pos) |
sam.GCLK_GENCTRL_GENEN)
for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL3) {
}
// Set OSCULP32K as source of Generic Clock Generator 0
sam.GCLK.GENCTRL0.Set((sam.GCLK_GENCTRL_SRC_OSCULP32K << sam.GCLK_GENCTRL_SRC_Pos) |
sam.GCLK_GENCTRL_GENEN)
for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL0) {
}
// Enable DFLL48M clock
sam.OSCCTRL.DFLLCTRLA.Set(0)
sam.OSCCTRL.DFLLMUL.Set((0x1 << sam.OSCCTRL_DFLLMUL_CSTEP_Pos) |
(0x1 << sam.OSCCTRL_DFLLMUL_FSTEP_Pos) |
(0x0 << sam.OSCCTRL_DFLLMUL_MUL_Pos))
for sam.OSCCTRL.DFLLSYNC.HasBits(sam.OSCCTRL_DFLLSYNC_DFLLMUL) {
}
sam.OSCCTRL.DFLLCTRLB.Set(0)
for sam.OSCCTRL.DFLLSYNC.HasBits(sam.OSCCTRL_DFLLSYNC_DFLLCTRLB) {
}
sam.OSCCTRL.DFLLCTRLA.SetBits(sam.OSCCTRL_DFLLCTRLA_ENABLE)
for sam.OSCCTRL.DFLLSYNC.HasBits(sam.OSCCTRL_DFLLSYNC_ENABLE) {
}
sam.OSCCTRL.DFLLVAL.Set(sam.OSCCTRL.DFLLVAL.Get())
for sam.OSCCTRL.DFLLSYNC.HasBits(sam.OSCCTRL_DFLLSYNC_DFLLVAL) {
}
sam.OSCCTRL.DFLLCTRLB.Set(sam.OSCCTRL_DFLLCTRLB_WAITLOCK |
sam.OSCCTRL_DFLLCTRLB_CCDIS |
sam.OSCCTRL_DFLLCTRLB_USBCRM)
for !sam.OSCCTRL.STATUS.HasBits(sam.OSCCTRL_STATUS_DFLLRDY) {
}
// set GCLK7 to use DFLL48M as clock source
sam.GCLK.GENCTRL7.Set((sam.GCLK_GENCTRL_SRC_DFLL << sam.GCLK_GENCTRL_SRC_Pos) |
(24 << sam.GCLK_GENCTRL_DIVSEL_Pos) |
sam.GCLK_GENCTRL_GENEN)
for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL7) {
}
// Set up the PLLs
// Set PLL0 at 120MHz
sam.GCLK.PCHCTRL1.Set(sam.GCLK_PCHCTRL_CHEN |
(sam.GCLK_PCHCTRL_GEN_GCLK7 << sam.GCLK_PCHCTRL_GEN_Pos))
sam.OSCCTRL.DPLLRATIO0.Set((0x0 << sam.OSCCTRL_DPLLRATIO_LDRFRAC_Pos) |
(59 << sam.OSCCTRL_DPLLRATIO_LDR_Pos))
for sam.OSCCTRL.DPLLSYNCBUSY0.HasBits(sam.OSCCTRL_DPLLSYNCBUSY_DPLLRATIO) {
}
// MUST USE LBYPASS DUE TO BUG IN REV A OF SAMD51, via Adafruit lib.
sam.OSCCTRL.DPLLCTRLB0.Set((sam.OSCCTRL_DPLLCTRLB_REFCLK_GCLK << sam.OSCCTRL_DPLLCTRLB_REFCLK_Pos) |
sam.OSCCTRL_DPLLCTRLB_LBYPASS)
sam.OSCCTRL.DPLLCTRLA0.Set(sam.OSCCTRL_DPLLCTRLA_ENABLE)
for !sam.OSCCTRL.DPLLSTATUS0.HasBits(sam.OSCCTRL_DPLLSTATUS_CLKRDY) ||
!sam.OSCCTRL.DPLLSTATUS0.HasBits(sam.OSCCTRL_DPLLSTATUS_LOCK) {
}
// // Set PLL1 to 100MHz
sam.GCLK.PCHCTRL2.Set(sam.GCLK_PCHCTRL_CHEN |
(sam.GCLK_PCHCTRL_GEN_GCLK7 << sam.GCLK_PCHCTRL_GEN_Pos))
sam.OSCCTRL.DPLLRATIO1.Set((0x0 << sam.OSCCTRL_DPLLRATIO_LDRFRAC_Pos) |
(49 << sam.OSCCTRL_DPLLRATIO_LDR_Pos)) // this means 100 Mhz?
for sam.OSCCTRL.DPLLSYNCBUSY1.HasBits(sam.OSCCTRL_DPLLSYNCBUSY_DPLLRATIO) {
}
// // MUST USE LBYPASS DUE TO BUG IN REV A OF SAMD51
sam.OSCCTRL.DPLLCTRLB1.Set((sam.OSCCTRL_DPLLCTRLB_REFCLK_GCLK << sam.OSCCTRL_DPLLCTRLB_REFCLK_Pos) |
sam.OSCCTRL_DPLLCTRLB_LBYPASS)
sam.OSCCTRL.DPLLCTRLA1.Set(sam.OSCCTRL_DPLLCTRLA_ENABLE)
// for !sam.OSCCTRL.DPLLSTATUS1.HasBits(sam.OSCCTRL_DPLLSTATUS_CLKRDY) ||
// !sam.OSCCTRL.DPLLSTATUS1.HasBits(sam.OSCCTRL_DPLLSTATUS_LOCK) {
// }
// Set up the peripheral clocks
// Set 48MHZ CLOCK FOR USB
sam.GCLK.GENCTRL1.Set((sam.GCLK_GENCTRL_SRC_DFLL << sam.GCLK_GENCTRL_SRC_Pos) |
sam.GCLK_GENCTRL_IDC |
sam.GCLK_GENCTRL_GENEN)
for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL1) {
}
// // Set 100MHZ CLOCK FOR OTHER PERIPHERALS
// sam.GCLK.GENCTRL2.Set((sam.GCLK_GENCTRL_SRC_DPLL1 << sam.GCLK_GENCTRL_SRC_Pos) |
// sam.GCLK_GENCTRL_IDC |
// sam.GCLK_GENCTRL_GENEN)
// for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL2) {
// }
// // Set 12MHZ CLOCK FOR DAC
sam.GCLK.GENCTRL4.Set((sam.GCLK_GENCTRL_SRC_DFLL << sam.GCLK_GENCTRL_SRC_Pos) |
sam.GCLK_GENCTRL_IDC |
(4 << sam.GCLK_GENCTRL_DIVSEL_Pos) |
sam.GCLK_GENCTRL_GENEN)
for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL4) {
}
// // Set up main clock
sam.GCLK.GENCTRL0.Set((sam.GCLK_GENCTRL_SRC_DPLL0 << sam.GCLK_GENCTRL_SRC_Pos) |
sam.GCLK_GENCTRL_IDC |
sam.GCLK_GENCTRL_GENEN)
for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL0) {
}
sam.MCLK.CPUDIV.Set(sam.MCLK_CPUDIV_DIV_DIV1)
// Use the LDO regulator by default
sam.SUPC.VREG.ClearBits(sam.SUPC_VREG_SEL)
// Start up the "Debug Watchpoint and Trace" unit, so that we can use
// it's 32bit cycle counter for timing.
//CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
//DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}
func initRTC() {
// turn on digital interface clock
sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_RTC_)
// disable RTC
sam.RTC_MODE0.CTRLA.ClearBits(sam.RTC_MODE0_CTRLA_ENABLE)
//sam.RTC_MODE0.CTRLA.Set(0)
for sam.RTC_MODE0.SYNCBUSY.HasBits(sam.RTC_MODE0_SYNCBUSY_ENABLE) {
}
// reset RTC
sam.RTC_MODE0.CTRLA.SetBits(sam.RTC_MODE0_CTRLA_SWRST)
for sam.RTC_MODE0.SYNCBUSY.HasBits(sam.RTC_MODE0_SYNCBUSY_SWRST) {
}
// set to use ulp 32k oscillator
sam.OSC32KCTRL.OSCULP32K.Set(sam.OSC32KCTRL_OSCULP32K_EN32K)
sam.OSC32KCTRL.RTCCTRL.Set(sam.OSC32KCTRL_RTCCTRL_RTCSEL_ULP32K)
// set Mode0 to 32-bit counter (mode 0) with prescaler 1 and GCLK2 is 32KHz/1
sam.RTC_MODE0.CTRLA.Set((sam.RTC_MODE0_CTRLA_MODE_COUNT32 << sam.RTC_MODE0_CTRLA_MODE_Pos) |
(sam.RTC_MODE0_CTRLA_PRESCALER_DIV1 << sam.RTC_MODE0_CTRLA_PRESCALER_Pos) |
(sam.RTC_MODE0_CTRLA_COUNTSYNC))
// re-enable RTC
sam.RTC_MODE0.CTRLA.SetBits(sam.RTC_MODE0_CTRLA_ENABLE)
for sam.RTC_MODE0.SYNCBUSY.HasBits(sam.RTC_MODE0_SYNCBUSY_ENABLE) {
}
arm.SetPriority(sam.IRQ_RTC, 0xc0)
arm.EnableIRQ(sam.IRQ_RTC)
}
func waitForSync() {
for sam.RTC_MODE0.SYNCBUSY.HasBits(sam.RTC_MODE0_SYNCBUSY_COUNT) {
}
}
// 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 {
waitForSync()
rtcCounter := (uint64(sam.RTC_MODE0.COUNT.Get()) * 305) / 10 // 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 < 260 {
// due to delay waiting for the register value to sync, the minimum sleep value
// for the SAMD51 is 260us.
// For related info for SAMD21, see:
// https://community.atmel.com/comment/2507091#comment-2507091
ticks = 260
}
// request read of count
waitForSync()
// set compare value
cnt := sam.RTC_MODE0.COUNT.Get()
sam.RTC_MODE0.COMP0.Set(uint32(cnt) + (ticks * 10 / 305)) // each counter tick == 30.5us
// enable IRQ for CMP0 compare
sam.RTC_MODE0.INTENSET.SetBits(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.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
timerWakeup = true
}
func initUSBClock() {
// Turn on clock(s) for USB
//MCLK->APBBMASK.reg |= MCLK_APBBMASK_USB;
//MCLK->AHBMASK.reg |= MCLK_AHBMASK_USB;
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_USB_)
sam.MCLK.AHBMASK.SetBits(sam.MCLK_AHBMASK_USB_)
// Put Generic Clock Generator 1 as source for USB
//GCLK->PCHCTRL[USB_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK1_Val | (1 << GCLK_PCHCTRL_CHEN_Pos);
sam.GCLK.PCHCTRL10.Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
}
func initADCClock() {
// Turn on clocks for ADC0/ADC1.
sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_ADC0_)
sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_ADC1_)
// Put Generic Clock Generator 1 as source for ADC0 and ADC1.
sam.GCLK.PCHCTRL40.Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
sam.GCLK.PCHCTRL41.Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
}
+43
View File
@@ -0,0 +1,43 @@
// +build sam,atsamd51,atsamd51g19
package runtime
import (
"device/sam"
)
func initSERCOMClocks() {
// Turn on clock to SERCOM0 for UART0
sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_)
sam.GCLK.PCHCTRL7.Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
// sets the "slow" clock shared by all SERCOM
sam.GCLK.PCHCTRL3.Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
// Turn on clock to SERCOM1
sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_)
sam.GCLK.PCHCTRL8.Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
// Turn on clock to SERCOM2
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_)
sam.GCLK.PCHCTRL23.Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
// Turn on clock to SERCOM3
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_)
sam.GCLK.PCHCTRL24.Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
// Turn on clock to SERCOM4
sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_)
sam.GCLK.PCHCTRL34.Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
// Turn on clock to SERCOM5
sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_)
sam.GCLK.PCHCTRL35.Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
}
+4 -4
View File
@@ -47,10 +47,10 @@ func main() {
func preinit() {
// Initialize .bss: zero-initialized global variables.
ptr := uintptr(unsafe.Pointer(&_sbss))
for ptr != uintptr(unsafe.Pointer(&_ebss)) {
*(*uint8)(unsafe.Pointer(ptr)) = 0
ptr += 1
ptr := unsafe.Pointer(&_sbss)
for ptr != unsafe.Pointer(&_ebss) {
*(*uint32)(ptr) = 0
ptr = unsafe.Pointer(uintptr(ptr) + 4)
}
}
+39 -10
View File
@@ -24,22 +24,45 @@ var _edata unsafe.Pointer
func preinit() {
// Initialize .bss: zero-initialized global variables.
ptr := uintptr(unsafe.Pointer(&_sbss))
for ptr != uintptr(unsafe.Pointer(&_ebss)) {
*(*uint32)(unsafe.Pointer(ptr)) = 0
ptr += 4
ptr := unsafe.Pointer(&_sbss)
for ptr != unsafe.Pointer(&_ebss) {
*(*uint32)(ptr) = 0
ptr = unsafe.Pointer(uintptr(ptr) + 4)
}
// Initialize .data: global variables initialized from flash.
src := uintptr(unsafe.Pointer(&_sidata))
dst := uintptr(unsafe.Pointer(&_sdata))
for dst != uintptr(unsafe.Pointer(&_edata)) {
*(*uint32)(unsafe.Pointer(dst)) = *(*uint32)(unsafe.Pointer(src))
dst += 4
src += 4
src := unsafe.Pointer(&_sidata)
dst := unsafe.Pointer(&_sdata)
for dst != unsafe.Pointer(&_edata) {
*(*uint32)(dst) = *(*uint32)(src)
dst = unsafe.Pointer(uintptr(dst) + 4)
src = unsafe.Pointer(uintptr(src) + 4)
}
}
// calleeSavedRegs is the list of registers that must be saved and restored when
// switching between tasks. Also see scheduler_cortexm.S that relies on the
// exact layout of this struct.
type calleeSavedRegs struct {
r4 uintptr
r5 uintptr
r6 uintptr
r7 uintptr
r8 uintptr
r9 uintptr
r10 uintptr
r11 uintptr
}
// prepareStartTask stores fn and args in some callee-saved registers that can
// then be used by the startTask function (implemented in assembly) to set up
// the initial stack pointer and initial argument with the pointer to the object
// with the goroutine start arguments.
func (r *calleeSavedRegs) prepareStartTask(fn, args uintptr) {
r.r4 = fn
r.r5 = args
}
func abort() {
// disable all interrupts
arm.DisableInterrupts()
@@ -109,3 +132,9 @@ func libc_memset(ptr unsafe.Pointer, c byte, size uintptr) {
func libc_memmove(dst, src unsafe.Pointer, size uintptr) {
memmove(dst, src, size)
}
// Implement memcpy for LLVM and compiler-rt.
//go:export memcpy
func libc_memcpy(dst, src unsafe.Pointer, size uintptr) {
memcpy(dst, src, size)
}
+10 -10
View File
@@ -62,19 +62,19 @@ func pric_init() {
func preinit() {
// Initialize .bss: zero-initialized global variables.
ptr := uintptr(unsafe.Pointer(&_sbss))
for ptr != uintptr(unsafe.Pointer(&_ebss)) {
*(*uint32)(unsafe.Pointer(ptr)) = 0
ptr += 4
ptr := unsafe.Pointer(&_sbss)
for ptr != unsafe.Pointer(&_ebss) {
*(*uint32)(ptr) = 0
ptr = unsafe.Pointer(uintptr(ptr) + 4)
}
// Initialize .data: global variables initialized from flash.
src := uintptr(unsafe.Pointer(&_sidata))
dst := uintptr(unsafe.Pointer(&_sdata))
for dst != uintptr(unsafe.Pointer(&_edata)) {
*(*uint32)(unsafe.Pointer(dst)) = *(*uint32)(unsafe.Pointer(src))
dst += 4
src += 4
src := unsafe.Pointer(&_sidata)
dst := unsafe.Pointer(&_sdata)
for dst != unsafe.Pointer(&_edata) {
*(*uint32)(dst) = *(*uint32)(src)
dst = unsafe.Pointer(uintptr(dst) + 4)
src = unsafe.Pointer(uintptr(src) + 4)
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
// +build darwin linux,!avr,!cortexm,!tinygo.riscv
// +build darwin linux,!baremetal
package runtime
+78 -142
View File
@@ -1,63 +1,28 @@
package runtime
// This file implements the Go scheduler using coroutines.
// A goroutine contains a whole stack. A coroutine is just a single function.
// How do we use coroutines for goroutines, then?
// * Every function that contains a blocking call (like sleep) is marked
// blocking, and all it's parents (callers) are marked blocking as well
// transitively until the root (main.main or a go statement).
// * 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.
// 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.
// This file implements the TinyGo scheduler. This scheduler is a very simple
// cooperative round robin scheduler, with a runqueue that contains a linked
// list of goroutines (tasks) that should be run next, in order of when they
// were added to the queue (first-in, first-out). It also contains a sleep queue
// with sleeping goroutines in order of when they should be re-activated.
//
// For more background on coroutines in LLVM:
// https://llvm.org/docs/Coroutines.html
// The scheduler is used both for the coroutine based scheduler and for the task
// based scheduler (see compiler/goroutine-lowering.go for a description). In
// both cases, the 'task' type is used to represent one goroutine. In the case
// of the task based scheduler, it literally is the goroutine itself: a pointer
// to the bottom of the stack where some important fields are kept. In the case
// of the coroutine-based scheduler, it is the coroutine pointer (a *i8 in
// LLVM).
import (
"unsafe"
)
import "unsafe"
const schedulerDebug = false
// A coroutine instance, wrapped here to provide some type safety. The value
// must not be used directly, it is meant to be used as an opaque *i8 in LLVM.
type coroutine uint8
//go:export llvm.coro.resume
func (t *coroutine) resume()
//go:export llvm.coro.destroy
func (t *coroutine) destroy()
//go:export llvm.coro.done
func (t *coroutine) done() bool
//go:export llvm.coro.promise
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))
}
func makeGoroutine(*uint8) *uint8
// Compiler stub to get the current goroutine. Calls to this function are
// removed in the goroutine lowering pass.
func getCoroutine() *coroutine
// State/promise of a task. Internally represented as:
// State of a task. Internally represented as:
//
// {i8* next, i1 commaOk, i32/i64 data}
// {i8* next, i8* ptr, i32/i64 data}
type taskState struct {
next *coroutine
next *task
ptr unsafe.Pointer
data uint
}
@@ -67,35 +32,42 @@ type taskState struct {
// TODO: runqueueFront can be removed by making the run queue a circular linked
// list. The runqueueBack will simply refer to the front in the 'next' pointer.
var (
runqueueFront *coroutine
runqueueBack *coroutine
sleepQueue *coroutine
runqueueFront *task
runqueueBack *task
sleepQueue *task
sleepQueueBaseTime timeUnit
)
// Simple logging, for debugging.
func scheduleLog(msg string) {
if schedulerDebug {
println(msg)
println("---", msg)
}
}
// Simple logging with a task pointer, for debugging.
func scheduleLogTask(msg string, t *coroutine) {
func scheduleLogTask(msg string, t *task) {
if schedulerDebug {
println(msg, t)
println("---", msg, t)
}
}
// Simple logging with a channel and task pointer.
func scheduleLogChan(msg string, ch *channel, t *task) {
if schedulerDebug {
println("---", msg, ch, t)
}
}
// Set the task to sleep for a given time.
//
// This is a compiler intrinsic.
func sleepTask(caller *coroutine, duration int64) {
func sleepTask(caller *task, duration int64) {
if schedulerDebug {
println(" set sleep:", caller, uint(duration/tickMicros))
}
promise := caller.promise()
promise.data = uint(duration / tickMicros) // TODO: longer durations
state := caller.state()
state.data = uint(duration / tickMicros) // TODO: longer durations
addSleepTask(caller)
}
@@ -103,143 +75,105 @@ func sleepTask(caller *coroutine, duration int64) {
//
// This is a compiler intrinsic, and is called from a callee to reactivate the
// caller.
func activateTask(task *coroutine) {
if task == nil {
func activateTask(t *task) {
if t == nil {
return
}
scheduleLogTask(" set runnable:", task)
runqueuePushBack(task)
scheduleLogTask(" set runnable:", t)
runqueuePushBack(t)
}
// getTaskPromisePtr is a helper function to set the current .ptr field of a
// coroutine promise.
func setTaskPromisePtr(task *coroutine, value unsafe.Pointer) {
task.promise().ptr = value
}
// getTaskPromisePtr is a helper function to get the current .ptr field from a
// coroutine promise.
func getTaskPromisePtr(task *coroutine) unsafe.Pointer {
if task == nil {
blockingPanic()
}
return task.promise().ptr
}
// getTaskPromiseData is a helper function to get the current .data field of a
// coroutine promise.
func getTaskPromiseData(task *coroutine) uint {
return task.promise().data
// getTaskStateData is a helper function to get the current .data field of the
// goroutine state.
func getTaskStateData(t *task) uint {
return t.state().data
}
// Add this task to the end of the run queue. May also destroy the task if it's
// done.
func runqueuePushBack(t *coroutine) {
if t.done() {
scheduleLogTask(" destroy task:", t)
t.destroy()
return
}
func runqueuePushBack(t *task) {
if schedulerDebug {
if t.promise().next != nil {
if t.state().next != nil {
panic("runtime: runqueuePushBack: expected next task to be nil")
}
}
if runqueueBack == nil { // empty runqueue
scheduleLogTask(" add to runqueue front:", t)
runqueueBack = t
runqueueFront = t
} else {
scheduleLogTask(" add to runqueue back:", t)
lastTaskPromise := runqueueBack.promise()
lastTaskPromise.next = t
lastTaskState := runqueueBack.state()
lastTaskState.next = t
runqueueBack = t
}
}
// Get a task from the front of the run queue. Returns nil if there is none.
func runqueuePopFront() *coroutine {
func runqueuePopFront() *task {
t := runqueueFront
if t == nil {
return nil
}
if schedulerDebug {
println(" runqueuePopFront:", t)
}
promise := t.promise()
runqueueFront = promise.next
state := t.state()
runqueueFront = state.next
if runqueueFront == nil {
// Runqueue is empty now.
runqueueBack = nil
}
promise.next = nil
state.next = nil
return t
}
// Add this task to the sleep queue, assuming its state is set to sleeping.
func addSleepTask(t *coroutine) {
func addSleepTask(t *task) {
if schedulerDebug {
if t.promise().next != nil {
if t.state().next != nil {
panic("runtime: addSleepTask: expected next task to be nil")
}
}
now := ticks()
if sleepQueue == nil {
scheduleLog(" -> sleep new queue")
// Create new linked list for the sleep queue.
sleepQueue = t
// set new base time
sleepQueueBaseTime = now
return
}
// Make sure promise.data is relative to the queue time base.
promise := t.promise()
// Insert at front of sleep queue.
if promise.data < sleepQueue.promise().data {
scheduleLog(" -> sleep at start")
sleepQueue.promise().data -= promise.data
promise.next = sleepQueue
sleepQueue = t
return
}
// Add to sleep queue (in the middle or at the end).
queueIndex := sleepQueue
for {
promise.data -= queueIndex.promise().data
if queueIndex.promise().next == nil || queueIndex.promise().data > promise.data {
if queueIndex.promise().next == nil {
scheduleLog(" -> sleep at end")
promise.next = nil
} else {
scheduleLog(" -> sleep in middle")
promise.next = queueIndex.promise().next
promise.next.promise().data -= promise.data
}
queueIndex.promise().next = t
// Add to sleep queue.
q := &sleepQueue
for ; *q != nil; q = &((*q).state()).next {
if t.state().data < (*q).state().data {
// this will finish earlier than the next - insert here
break
} else {
// this will finish later - adjust delay
t.state().data -= (*q).state().data
}
queueIndex = queueIndex.promise().next
}
if *q != nil {
// cut delay time between this sleep task and the next
(*q).state().data -= t.state().data
}
t.state().next = *q
*q = t
}
// Run the scheduler until all tasks have finished.
func scheduler() {
// Main scheduler loop.
for {
scheduleLog("\n schedule")
scheduleLog("")
scheduleLog(" schedule")
now := ticks()
// Add tasks that are done sleeping to the end of the runqueue so they
// will be executed soon.
if sleepQueue != nil && now-sleepQueueBaseTime >= timeUnit(sleepQueue.promise().data) {
if sleepQueue != nil && now-sleepQueueBaseTime >= timeUnit(sleepQueue.state().data) {
t := sleepQueue
scheduleLogTask(" awake:", t)
promise := t.promise()
sleepQueueBaseTime += timeUnit(promise.data)
sleepQueue = promise.next
promise.next = nil
state := t.state()
sleepQueueBaseTime += timeUnit(state.data)
sleepQueue = state.next
state.next = nil
runqueuePushBack(t)
}
@@ -253,11 +187,14 @@ func scheduler() {
scheduleLog(" no tasks left!")
return
}
timeLeft := timeUnit(sleepQueue.promise().data) - (now - sleepQueueBaseTime)
timeLeft := timeUnit(sleepQueue.state().data) - (now - sleepQueueBaseTime)
if schedulerDebug {
println(" sleeping...", sleepQueue, uint(timeLeft))
for t := sleepQueue; t != nil; t = t.state().next {
println(" task sleeping:", t, timeUnit(t.state().data))
}
}
sleepTicks(timeUnit(timeLeft))
sleepTicks(timeLeft)
if asyncScheduler {
// The sleepTicks function above only sets a timeout at which
// point the scheduler will be called again. It does not really
@@ -268,7 +205,6 @@ func scheduler() {
}
// Run the given task.
scheduleLog(" <- runqueuePopFront")
scheduleLogTask(" run:", t)
t.resume()
}
+101
View File
@@ -0,0 +1,101 @@
// +build scheduler.coroutines
package runtime
// This file implements the Go scheduler using coroutines.
// A goroutine contains a whole stack. A coroutine is just a single function.
// How do we use coroutines for goroutines, then?
// * Every function that contains a blocking call (like sleep) is marked
// blocking, and all it's parents (callers) are marked blocking as well
// transitively until the root (main.main or a go statement).
// * 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.
// Note that we use the type 'task' to refer to a coroutine, for compatibility
// with the task-based scheduler. A task type here does not represent the whole
// task, but just the topmost coroutine. For most of the scheduler, this
// difference doesn't matter.
//
// For more background on coroutines in LLVM:
// https://llvm.org/docs/Coroutines.html
import "unsafe"
// A coroutine instance, wrapped here to provide some type safety. The value
// must not be used directly, it is meant to be used as an opaque *i8 in LLVM.
type task uint8
//go:export llvm.coro.resume
func (t *task) resume()
//go:export llvm.coro.destroy
func (t *task) destroy()
//go:export llvm.coro.done
func (t *task) done() bool
//go:export llvm.coro.promise
func (t *task) _promise(alignment int32, from bool) unsafe.Pointer
// Get the state belonging to a task.
func (t *task) state() *taskState {
return (*taskState)(t._promise(int32(unsafe.Alignof(taskState{})), false))
}
func makeGoroutine(uintptr) uintptr
// Compiler stub to get the current goroutine. Calls to this function are
// removed in the goroutine lowering pass.
func getCoroutine() *task
// getTaskStatePtr is a helper function to set the current .ptr field of a
// coroutine promise.
func setTaskStatePtr(t *task, value unsafe.Pointer) {
t.state().ptr = value
}
// getTaskStatePtr is a helper function to get the current .ptr field from a
// coroutine promise.
func getTaskStatePtr(t *task) unsafe.Pointer {
if t == nil {
blockingPanic()
}
return t.state().ptr
}
//go:linkname sleep time.Sleep
func sleep(d int64) {
sleepTicks(timeUnit(d / tickMicros))
}
// deadlock is called when a goroutine cannot proceed any more, but is in theory
// not exited (so deferred calls won't run). This can happen for example in code
// like this, that blocks forever:
//
// select{}
//
// The coroutine version is implemented directly in the compiler but it needs
// this definition to work.
func deadlock()
// reactivateParent reactivates the parent goroutine. It is necessary in case of
// the coroutine-based scheduler.
func reactivateParent(t *task) {
activateTask(t)
}
// chanYield exits the current goroutine. Used in the channel implementation, to
// suspend the current goroutine until it is reactivated by a channel operation
// of a different goroutine. It is a no-op in the coroutine implementation.
func chanYield() {
// Nothing to do here, simply returning from the channel operation also exits
// the goroutine temporarily.
}
// getSystemStackPointer returns the current stack pointer of the system stack.
// This is always the current stack pointer.
func getSystemStackPointer() uintptr {
return getCurrentStackPointer()
}
+94
View File
@@ -0,0 +1,94 @@
.section .text.tinygo_startTask
.global tinygo_startTask
.type tinygo_startTask, %function
tinygo_startTask:
// Small assembly stub for starting a goroutine. This is already run on the
// new stack, with the callee-saved registers already loaded.
// Most importantly, r4 contains the pc of the to-be-started function and r5
// contains the only argument it is given. Multiple arguments are packed
// into one by storing them in a new allocation.
// Set the first argument of the goroutine start wrapper, which contains all
// the arguments.
mov r0, r5
// Branch to the "goroutine start" function. By using blx instead of bx,
// we'll return here instead of tail calling.
blx r4
// After return, exit this goroutine. This is a tail call.
bl runtime.Goexit
.section .text.tinygo_swapTask
.global tinygo_swapTask
.type tinygo_swapTask, %function
tinygo_swapTask:
// r0 = oldTask *task
// r1 = newTask *task
// This function stores the current register state to a task struct and
// loads the state of another task to replace the current state. Apart from
// saving and restoring all relevant callee-saved registers, it also ends
// with branching to the last program counter (saved as the lr register, to
// follow the ARM calling convention).
// On pre-Thumb2 CPUs (Cortex-M0 in particular), registers r8-r15 cannot be
// used directly. Only very few operations work on them, such as mov. That's
// why the higher register values are first stored in the temporary register
// r3 when loading/storing them.
// Store state to old task. It saves the lr instead of the pc, because that
// will be the pc after returning back to the old task (in a different
// invocation of swapTask).
str r4, [r0, #0]
str r5, [r0, #4]
str r6, [r0, #8]
str r7, [r0, #12]
#if defined(__thumb2__)
str r8, [r0, #16]
str r9, [r0, #20]
str r10, [r0, #24]
str r11, [r0, #28]
str sp, [r0, #32]
str lr, [r0, #36]
#else
mov r3, r8
str r3, [r0, #16]
mov r3, r9
str r3, [r0, #20]
mov r3, r10
str r3, [r0, #24]
mov r3, r11
str r3, [r0, #28]
mov r3, sp
str r3, [r0, #32]
mov r3, lr
str r3, [r0, #36]
#endif
// Load state from new task and branch to the previous position in the
// program.
ldr r4, [r1, #0]
ldr r5, [r1, #4]
ldr r6, [r1, #8]
ldr r7, [r1, #12]
#if defined(__thumb2__)
ldr r8, [r1, #16]
ldr r9, [r1, #20]
ldr r10, [r1, #24]
ldr r11, [r1, #28]
ldr sp, [r1, #32]
#else
ldr r3, [r1, #16]
mov r8, r3
ldr r3, [r1, #20]
mov r9, r3
ldr r3, [r1, #24]
mov r10, r3
ldr r3, [r1, #28]
mov r11, r3
ldr r3, [r1, #32]
mov sp, r3
#endif
ldr r3, [r1, #36]
bx r3
+143
View File
@@ -0,0 +1,143 @@
// +build scheduler.tasks
package runtime
import "unsafe"
const stackSize = 1024
// Stack canary, to detect a stack overflow. The number is a random number
// generated by random.org. The bit fiddling dance is necessary because
// otherwise Go wouldn't allow the cast to a smaller integer size.
const stackCanary = uintptr(uint64(0x670c1333b83bf575) & uint64(^uintptr(0)))
var (
schedulerState = task{canary: stackCanary}
currentTask *task // currently running goroutine, or nil
)
// This type points to the bottom of the goroutine stack and contains some state
// that must be kept with the task. The last field is a canary, which is
// necessary to make sure that no stack overflow occured when switching tasks.
type task struct {
// The order of fields in this structs must be kept in sync with assembly!
calleeSavedRegs
sp uintptr
pc uintptr
taskState
canary uintptr // used to detect stack overflows
}
// getCoroutine returns the currently executing goroutine. It is used as an
// intrinsic when compiling channel operations, but is not necessary with the
// task-based scheduler.
func getCoroutine() *task {
return currentTask
}
// state is a small helper that returns the task state, and is provided for
// compatibility with the coroutine implementation.
//go:inline
func (t *task) state() *taskState {
return &t.taskState
}
// resume is a small helper that resumes this task until this task switches back
// to the scheduler.
func (t *task) resume() {
currentTask = t
swapTask(&schedulerState, t)
currentTask = nil
}
// swapTask saves the current state to oldTask (which must contain the current
// task state) and switches to newTask. Note that this function usually does
// return, when another task (perhaps newTask) switches back to the current
// task.
//
// As an additional protection, before switching tasks, it checks whether this
// goroutine has overflowed the stack.
func swapTask(oldTask, newTask *task) {
if oldTask.canary != stackCanary {
runtimePanic("goroutine stack overflow")
}
swapTaskLower(oldTask, newTask)
}
//go:linkname swapTaskLower tinygo_swapTask
func swapTaskLower(oldTask, newTask *task)
// Goexit terminates the currently running goroutine. No other goroutines are affected.
//
// Unlike the main Go implementation, no deffered calls will be run.
//export runtime.Goexit
func Goexit() {
// Swap without rescheduling first, effectively exiting the goroutine.
swapTask(currentTask, &schedulerState)
}
// startTask is a small wrapper function that sets up the first (and only)
// argument to the new goroutine and makes sure it is exited when the goroutine
// finishes.
//go:extern tinygo_startTask
var startTask [0]uint8
// startGoroutine starts a new goroutine with the given function pointer and
// argument. It creates a new goroutine stack, prepares it for execution, and
// adds it to the runqueue.
func startGoroutine(fn, args uintptr) {
stack := alloc(stackSize)
t := (*task)(stack)
t.sp = uintptr(stack) + stackSize
t.pc = uintptr(unsafe.Pointer(&startTask))
t.prepareStartTask(fn, args)
t.canary = stackCanary
scheduleLogTask(" start goroutine:", t)
runqueuePushBack(t)
}
//go:linkname sleep time.Sleep
func sleep(d int64) {
sleepTicks(timeUnit(d / tickMicros))
}
// sleepCurrentTask suspends the current goroutine. This is a compiler
// intrinsic. It replaces calls to time.Sleep when a scheduler is in use.
func sleepCurrentTask(d int64) {
sleepTask(currentTask, d)
swapTask(currentTask, &schedulerState)
}
// deadlock is called when a goroutine cannot proceed any more, but is in theory
// not exited (so deferred calls won't run). This can happen for example in code
// like this, that blocks forever:
//
// select{}
func deadlock() {
Goexit()
}
// reactivateParent reactivates the parent goroutine. It is a no-op for the task
// based scheduler.
func reactivateParent(t *task) {
// Nothing to do here, tasks don't stop automatically.
}
// chanYield exits the current goroutine. Used in the channel implementation, to
// suspend the current goroutine until it is reactivated by a channel operation
// of a different goroutine.
func chanYield() {
Goexit()
}
// getSystemStackPointer returns the current stack pointer of the system stack.
// This is not necessarily the same as the current stack pointer.
func getSystemStackPointer() uintptr {
if currentTask == nil {
// Currently on the system stack.
return getCurrentStackPointer()
} else {
// Currently in a goroutine.
return schedulerState.sp
}
}
+24
View File
@@ -89,6 +89,30 @@ func stringToBytes(x _string) (slice struct {
return
}
// Convert a []rune slice to a string.
func stringFromRunes(runeSlice []rune) (s _string) {
// Count the number of characters that will be in the string.
for _, r := range runeSlice {
_, numBytes := encodeUTF8(r)
s.length += numBytes
}
// Allocate memory for the string.
s.ptr = (*byte)(alloc(s.length))
// Encode runes to UTF-8 and store the resulting bytes in the string.
index := uintptr(0)
for _, r := range runeSlice {
array, numBytes := encodeUTF8(r)
for _, c := range array[:numBytes] {
*(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(s.ptr)) + index)) = c
index++
}
}
return
}
// Convert a string to []rune slice.
func stringToRunes(s string) []rune {
var n = 0
+1 -1
View File
@@ -1,4 +1,4 @@
// +build avr cortexm
// +build baremetal
package syscall

Some files were not shown because too many files have changed in this diff Show More