mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-01 17:47:46 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 923c2e7ada | |||
| 18e446561d | |||
| 447537aebe | |||
| 74e32acf33 | |||
| 072f8c354e | |||
| 0587934a44 | |||
| c2481b10f4 | |||
| d41f01f003 | |||
| 5a70c88483 | |||
| 5510dec846 | |||
| dffb9fbfa7 | |||
| 525ded3d90 | |||
| ec2658ca79 | |||
| 2004555fe2 | |||
| 9aeb8d9e06 | |||
| 34ee3883d6 | |||
| 768c652468 | |||
| 2778377ac9 | |||
| cf32607306 | |||
| ad022ef23d | |||
| c97b7221bd | |||
| 8d32a7c3a3 | |||
| 49eb414530 | |||
| 39d21e21f1 | |||
| fa8a93b4e7 | |||
| 8f9419a35d | |||
| 7bdd4a1186 | |||
| 0105f815c6 | |||
| d441f0152f | |||
| 06647aab24 | |||
| 24259cbb5f | |||
| 2f932a9eee | |||
| 93a06d1157 | |||
| 374349cfa5 | |||
| 024a0827ea | |||
| fadddc54a3 |
+2
-10
@@ -18,7 +18,6 @@ commands:
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
|
||||
sudo apt-get update
|
||||
sudo apt-get install \
|
||||
python3 \
|
||||
llvm<<parameters.llvm>>-dev \
|
||||
clang<<parameters.llvm>> \
|
||||
libclang<<parameters.llvm>>-dev \
|
||||
@@ -67,7 +66,7 @@ commands:
|
||||
- run: go install .
|
||||
- run: go test -v ./cgo ./compileopts ./interp ./transform .
|
||||
- run: make gen-device -j4
|
||||
- run: make smoketest RISCV=0
|
||||
- run: make smoketest
|
||||
- save_cache:
|
||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
@@ -83,7 +82,6 @@ commands:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
sudo apt-get install \
|
||||
python3 \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
binutils-arm-none-eabi \
|
||||
libc6-dev-armel-cross \
|
||||
@@ -139,7 +137,7 @@ commands:
|
||||
- ~/.cache/tinygo
|
||||
- /go/pkg/mod
|
||||
- run: make gen-device -j4
|
||||
- run: make smoketest TINYGO=build/tinygo RISCV=0
|
||||
- run: make smoketest TINYGO=build/tinygo
|
||||
build-linux:
|
||||
steps:
|
||||
- checkout
|
||||
@@ -148,7 +146,6 @@ commands:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
sudo apt-get install \
|
||||
python3 \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
binutils-arm-none-eabi \
|
||||
libc6-dev-armel-cross \
|
||||
@@ -216,11 +213,6 @@ commands:
|
||||
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
|
||||
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
|
||||
tinygo version
|
||||
- run:
|
||||
name: "Download SiFive GNU toolchain"
|
||||
command: |
|
||||
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-linux-ubuntu14.tar.gz
|
||||
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-linux-ubuntu14.tar.gz
|
||||
- run: make smoketest
|
||||
build-macos:
|
||||
steps:
|
||||
|
||||
@@ -1,3 +1,29 @@
|
||||
0.11.0
|
||||
---
|
||||
* **command line**
|
||||
- add support for QEMU in `gdb` subcommand
|
||||
- use builtin Clang when building statically, dropping the clang-9 dependency
|
||||
- search for default serial port on both macOS and Linux
|
||||
- windows: support `tinygo flash` directly by using win32 wmi
|
||||
* **compiler**
|
||||
- add location information to the IR checker
|
||||
- make reflection sidetables constant globals
|
||||
- improve error locations in goroutine lowering
|
||||
- interp: improve support for maps with string keys
|
||||
- interp: add runtime fallback for mapassign operations
|
||||
* **standard library**
|
||||
- `machine`: add support for `SPI.Tx()` on play.tinygo.org
|
||||
- `machine`: rename `CPU_FREQUENCY` to `CPUFrequency()`
|
||||
* **targets**
|
||||
- `adafruit-pybadge`: add Adafruit Pybadge
|
||||
- `arduino-nano33`: allow simulation on play.tinygo.org
|
||||
- `arduino-nano33`: fix default SPI pin numbers to be D13/D11/D12
|
||||
- `circuitplay-express`: allow simulation on play.tinygo.org
|
||||
- `hifive1-qemu`: add target for testing RISC-V bare metal in QEMU
|
||||
- `riscv`: fix heap corruption due to changes in LLVM 9
|
||||
- `riscv`: add support for compiler-rt
|
||||
- `qemu`: rename to `cortex-m-qemu`
|
||||
|
||||
0.10.0
|
||||
---
|
||||
* **command line**
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all project spaces, and it also applies when
|
||||
an individual is representing the project or its community in public spaces.
|
||||
Examples of representing a project or community include using an official
|
||||
project e-mail address, posting via an official social media account, or acting
|
||||
as an appointed representative at an online or offline event. Representation of
|
||||
a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at [conduct@tinygo.org](mailto:conduct@tinygo.org). All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
+5
-12
@@ -41,9 +41,8 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils python3 make binutils-avr gcc-avr avr-libc && \
|
||||
apt-get install -y apt-utils make binutils-avr gcc-avr avr-libc && \
|
||||
make gen-device-avr && \
|
||||
apt-get remove -y python3 && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean
|
||||
|
||||
@@ -59,11 +58,8 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils python3 make clang-9 && \
|
||||
make gen-device-nrf && make gen-device-stm32 && \
|
||||
apt-get remove -y python3 && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean
|
||||
apt-get install -y apt-utils make clang-9 && \
|
||||
make gen-device-nrf && make gen-device-stm32
|
||||
|
||||
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
|
||||
FROM tinygo-wasm AS tinygo-all
|
||||
@@ -74,10 +70,7 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils python3 make clang-9 binutils-avr gcc-avr avr-libc && \
|
||||
make gen-device && \
|
||||
apt-get remove -y python3 && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean
|
||||
apt-get install -y apt-utils make clang-9 binutils-avr gcc-avr avr-libc && \
|
||||
make gen-device
|
||||
|
||||
CMD ["tinygo"]
|
||||
|
||||
@@ -3,6 +3,9 @@ Copyright (c) 2018-2019 TinyGo Authors. All rights reserved.
|
||||
TinyGo includes portions of the Go standard library.
|
||||
Copyright (c) 2009-2019 The Go Authors. All rights reserved.
|
||||
|
||||
TinyGo includes portions of LLVM, which is under the Apache License v2.0 with
|
||||
LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
@@ -14,9 +14,6 @@ export GOROOT = $(shell $(GO) env GOROOT)
|
||||
# md5sum binary
|
||||
MD5SUM = md5sum
|
||||
|
||||
# Python binary
|
||||
PYTHON ?= python
|
||||
|
||||
# tinygo binary for tests
|
||||
TINYGO ?= tinygo
|
||||
|
||||
@@ -34,7 +31,7 @@ endif
|
||||
|
||||
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
|
||||
|
||||
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
|
||||
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
EXE = .exe
|
||||
@@ -78,7 +75,7 @@ LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -
|
||||
|
||||
# For static linking.
|
||||
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","")
|
||||
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
|
||||
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(LLVM_BUILDDIR))/tools/clang/include -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
|
||||
CGO_CXXFLAGS=-std=c++11
|
||||
CGO_LDFLAGS+=$(LIBCLANG_PATH) -std=c++11 -L$(abspath $(LLVM_BUILDDIR)/lib) $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
|
||||
endif
|
||||
@@ -97,24 +94,28 @@ fmt-check:
|
||||
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32
|
||||
|
||||
gen-device-avr:
|
||||
$(PYTHON) ./tools/gen-device-avr.py lib/avr/packs/atmega src/device/avr/
|
||||
$(PYTHON) ./tools/gen-device-avr.py lib/avr/packs/tiny src/device/avr/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/avr
|
||||
$(GO) build -o ./build/gen-device-avr ./tools/gen-device-avr/
|
||||
./build/gen-device-avr lib/avr/packs/atmega src/device/avr/
|
||||
./build/gen-device-avr lib/avr/packs/tiny src/device/avr/
|
||||
@GO111MODULE=off $(GO) fmt ./src/device/avr
|
||||
|
||||
gen-device-nrf:
|
||||
$(PYTHON) ./tools/gen-device-svd.py lib/nrfx/mdk/ src/device/nrf/ --source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk
|
||||
build/gen-device-svd: ./tools/gen-device-svd/*.go
|
||||
$(GO) build -o $@ ./tools/gen-device-svd/
|
||||
|
||||
gen-device-nrf: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk lib/nrfx/mdk/ src/device/nrf/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/nrf
|
||||
|
||||
gen-device-sam:
|
||||
$(PYTHON) ./tools/gen-device-svd.py lib/cmsis-svd/data/Atmel/ src/device/sam/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel
|
||||
gen-device-sam: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel lib/cmsis-svd/data/Atmel/ src/device/sam/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/sam
|
||||
|
||||
gen-device-sifive:
|
||||
$(PYTHON) ./tools/gen-device-svd.py lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community
|
||||
gen-device-sifive: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/sifive
|
||||
|
||||
gen-device-stm32:
|
||||
$(PYTHON) ./tools/gen-device-svd.py lib/cmsis-svd/data/STMicro/ src/device/stm32/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro
|
||||
gen-device-stm32: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro lib/cmsis-svd/data/STMicro/ src/device/stm32/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/stm32
|
||||
|
||||
|
||||
@@ -187,6 +188,8 @@ smoketest:
|
||||
@$(MD5SUM) test.wasm
|
||||
$(TINYGO) build -o test.wasm -tags=pca10056 examples/blinky2
|
||||
@$(MD5SUM) test.wasm
|
||||
$(TINYGO) build -o test.wasm -tags=circuitplay_express examples/blinky1
|
||||
@$(MD5SUM) test.wasm
|
||||
# test all targets/boards
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040-s132v6 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
@@ -226,6 +229,8 @@ smoketest:
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pybadge examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=metro-m4-airlift examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-f103rb examples/blinky1
|
||||
@@ -242,10 +247,8 @@ ifneq ($(AVR), 0)
|
||||
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
endif
|
||||
ifneq ($(RISCV), 0)
|
||||
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
endif
|
||||
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export
|
||||
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ The following 22 microcontroller boards are currently supported:
|
||||
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
||||
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
|
||||
* [Adafruit Metro M4 Express Airlift](https://www.adafruit.com/product/4000)
|
||||
* [Adafruit PyBadge](https://www.adafruit.com/product/4200)
|
||||
* [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)
|
||||
@@ -62,6 +63,7 @@ The following 22 microcontroller boards are currently supported:
|
||||
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
|
||||
* [Nordic Semiconductor PCA10056](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
|
||||
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
|
||||
* [PineTime DevKit](https://www.pine64.org/pinetime/)
|
||||
* [SiFIve HiFive1](https://www.sifive.com/boards/hifive1)
|
||||
* [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
|
||||
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
|
||||
@@ -125,3 +127,5 @@ The original reasoning was: if [Python](https://micropython.org/) can run on mic
|
||||
## License
|
||||
|
||||
This project is licensed under the BSD 3-clause license, just like the [Go project](https://golang.org/LICENSE) itself.
|
||||
|
||||
Some code has been copied from the LLVM project and is therefore licensed under [a variant of the Apache 2.0 license](http://releases.llvm.org/9.0.0/LICENSE.TXT). This has been clearly indicated in the header of these files.
|
||||
|
||||
+1
-1
@@ -71,4 +71,4 @@ jobs:
|
||||
script: |
|
||||
export PATH="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
unset GOROOT
|
||||
make smoketest TINYGO=build/tinygo AVR=0 RISCV=0
|
||||
make smoketest TINYGO=build/tinygo AVR=0
|
||||
|
||||
+12
-22
@@ -33,10 +33,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
|
||||
// Compile Go code to IR.
|
||||
errs := c.Compile(pkgName)
|
||||
if len(errs) != 0 {
|
||||
if len(errs) == 1 {
|
||||
return errs[0]
|
||||
}
|
||||
return &MultiError{errs}
|
||||
return newMultiError(errs)
|
||||
}
|
||||
if config.Options.PrintIR {
|
||||
fmt.Println("; Generated LLVM IR:")
|
||||
@@ -72,22 +69,23 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
|
||||
|
||||
// Optimization levels here are roughly the same as Clang, but probably not
|
||||
// exactly.
|
||||
errs = nil
|
||||
switch config.Options.Opt {
|
||||
case "none:", "0":
|
||||
err = c.Optimize(0, 0, 0) // -O0
|
||||
errs = c.Optimize(0, 0, 0) // -O0
|
||||
case "1":
|
||||
err = c.Optimize(1, 0, 0) // -O1
|
||||
errs = c.Optimize(1, 0, 0) // -O1
|
||||
case "2":
|
||||
err = c.Optimize(2, 0, 225) // -O2
|
||||
errs = c.Optimize(2, 0, 225) // -O2
|
||||
case "s":
|
||||
err = c.Optimize(2, 1, 225) // -Os
|
||||
errs = c.Optimize(2, 1, 225) // -Os
|
||||
case "z":
|
||||
err = c.Optimize(2, 2, 5) // -Oz, default
|
||||
errs = c.Optimize(2, 2, 5) // -Oz, default
|
||||
default:
|
||||
err = errors.New("unknown optimization level: -opt=" + config.Options.Opt)
|
||||
errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
if len(errs) > 0 {
|
||||
return newMultiError(errs)
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification failure after LLVM optimization passes")
|
||||
@@ -154,11 +152,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
|
||||
for i, path := range config.ExtraFiles() {
|
||||
abspath := filepath.Join(root, path)
|
||||
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
|
||||
cmdNames := []string{config.Target.Compiler}
|
||||
if names, ok := commands[config.Target.Compiler]; ok {
|
||||
cmdNames = names
|
||||
}
|
||||
err := execCommand(cmdNames, append(config.CFlags(), "-c", "-o", outpath, abspath)...)
|
||||
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, abspath)...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", path, err}
|
||||
}
|
||||
@@ -170,11 +164,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
|
||||
for _, file := range pkg.CFiles {
|
||||
path := filepath.Join(pkg.Package.Dir, file)
|
||||
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o")
|
||||
cmdNames := []string{config.Target.Compiler}
|
||||
if names, ok := commands[config.Target.Compiler]; ok {
|
||||
cmdNames = names
|
||||
}
|
||||
err := execCommand(cmdNames, append(config.CFlags(), "-c", "-o", outpath, path)...)
|
||||
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, path)...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", path, err}
|
||||
}
|
||||
|
||||
+5
-1
@@ -236,7 +236,11 @@ func CompileBuiltins(target string, callback func(path string) error) error {
|
||||
// Note: -fdebug-prefix-map is necessary to make the output archive
|
||||
// reproducible. Otherwise the temporary directory is stored in the
|
||||
// archive itself, which varies each run.
|
||||
err := execCommand(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir, "-o", objpath, srcpath)
|
||||
args := []string{"-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target=" + target, "-fdebug-prefix-map=" + dir + "=" + remapDir}
|
||||
if strings.HasPrefix(target, "riscv32-") {
|
||||
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
|
||||
}
|
||||
err := runCCompiler("clang", append(args, "-o", objpath, srcpath)...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", srcpath, err}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,504 @@
|
||||
// +build byollvm
|
||||
|
||||
//===-- cc1as.cpp - Clang Assembler --------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This is the entry point to the clang -cc1as functionality, which implements
|
||||
// the direct interface to the LLVM MC based assembler.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "clang/Basic/Diagnostic.h"
|
||||
#include "clang/Basic/DiagnosticOptions.h"
|
||||
#include "clang/Driver/DriverDiagnostic.h"
|
||||
#include "clang/Driver/Options.h"
|
||||
#include "clang/Frontend/FrontendDiagnostic.h"
|
||||
#include "clang/Frontend/TextDiagnosticPrinter.h"
|
||||
#include "clang/Frontend/Utils.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/StringSwitch.h"
|
||||
#include "llvm/ADT/Triple.h"
|
||||
#include "llvm/IR/DataLayout.h"
|
||||
#include "llvm/MC/MCAsmBackend.h"
|
||||
#include "llvm/MC/MCAsmInfo.h"
|
||||
#include "llvm/MC/MCCodeEmitter.h"
|
||||
#include "llvm/MC/MCContext.h"
|
||||
#include "llvm/MC/MCInstrInfo.h"
|
||||
#include "llvm/MC/MCObjectFileInfo.h"
|
||||
#include "llvm/MC/MCObjectWriter.h"
|
||||
#include "llvm/MC/MCParser/MCAsmParser.h"
|
||||
#include "llvm/MC/MCParser/MCTargetAsmParser.h"
|
||||
#include "llvm/MC/MCRegisterInfo.h"
|
||||
#include "llvm/MC/MCSectionMachO.h"
|
||||
#include "llvm/MC/MCStreamer.h"
|
||||
#include "llvm/MC/MCSubtargetInfo.h"
|
||||
#include "llvm/MC/MCTargetOptions.h"
|
||||
#include "llvm/Option/Arg.h"
|
||||
#include "llvm/Option/ArgList.h"
|
||||
#include "llvm/Option/OptTable.h"
|
||||
#include "llvm/Support/CommandLine.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
#include "llvm/Support/FormattedStream.h"
|
||||
#include "llvm/Support/Host.h"
|
||||
#include "llvm/Support/MemoryBuffer.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
#include "llvm/Support/Signals.h"
|
||||
#include "llvm/Support/SourceMgr.h"
|
||||
#include "llvm/Support/TargetRegistry.h"
|
||||
#include "llvm/Support/TargetSelect.h"
|
||||
#include "llvm/Support/Timer.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include <memory>
|
||||
#include <system_error>
|
||||
using namespace clang;
|
||||
using namespace clang::driver;
|
||||
using namespace clang::driver::options;
|
||||
using namespace llvm;
|
||||
using namespace llvm::opt;
|
||||
|
||||
#include "cc1as.h"
|
||||
|
||||
bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
||||
ArrayRef<const char *> Argv,
|
||||
DiagnosticsEngine &Diags) {
|
||||
bool Success = true;
|
||||
|
||||
// Parse the arguments.
|
||||
std::unique_ptr<OptTable> OptTbl(createDriverOptTable());
|
||||
|
||||
const unsigned IncludedFlagsBitmask = options::CC1AsOption;
|
||||
unsigned MissingArgIndex, MissingArgCount;
|
||||
InputArgList Args = OptTbl->ParseArgs(Argv, MissingArgIndex, MissingArgCount,
|
||||
IncludedFlagsBitmask);
|
||||
|
||||
// Check for missing argument error.
|
||||
if (MissingArgCount) {
|
||||
Diags.Report(diag::err_drv_missing_argument)
|
||||
<< Args.getArgString(MissingArgIndex) << MissingArgCount;
|
||||
Success = false;
|
||||
}
|
||||
|
||||
// Issue errors on unknown arguments.
|
||||
for (const Arg *A : Args.filtered(OPT_UNKNOWN)) {
|
||||
auto ArgString = A->getAsString(Args);
|
||||
std::string Nearest;
|
||||
if (OptTbl->findNearest(ArgString, Nearest, IncludedFlagsBitmask) > 1)
|
||||
Diags.Report(diag::err_drv_unknown_argument) << ArgString;
|
||||
else
|
||||
Diags.Report(diag::err_drv_unknown_argument_with_suggestion)
|
||||
<< ArgString << Nearest;
|
||||
Success = false;
|
||||
}
|
||||
|
||||
// Construct the invocation.
|
||||
|
||||
// Target Options
|
||||
Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple));
|
||||
Opts.CPU = Args.getLastArgValue(OPT_target_cpu);
|
||||
Opts.Features = Args.getAllArgValues(OPT_target_feature);
|
||||
|
||||
// Use the default target triple if unspecified.
|
||||
if (Opts.Triple.empty())
|
||||
Opts.Triple = llvm::sys::getDefaultTargetTriple();
|
||||
|
||||
// Language Options
|
||||
Opts.IncludePaths = Args.getAllArgValues(OPT_I);
|
||||
Opts.NoInitialTextSection = Args.hasArg(OPT_n);
|
||||
Opts.SaveTemporaryLabels = Args.hasArg(OPT_msave_temp_labels);
|
||||
// Any DebugInfoKind implies GenDwarfForAssembly.
|
||||
Opts.GenDwarfForAssembly = Args.hasArg(OPT_debug_info_kind_EQ);
|
||||
|
||||
if (const Arg *A = Args.getLastArg(OPT_compress_debug_sections,
|
||||
OPT_compress_debug_sections_EQ)) {
|
||||
if (A->getOption().getID() == OPT_compress_debug_sections) {
|
||||
// TODO: be more clever about the compression type auto-detection
|
||||
Opts.CompressDebugSections = llvm::DebugCompressionType::GNU;
|
||||
} else {
|
||||
Opts.CompressDebugSections =
|
||||
llvm::StringSwitch<llvm::DebugCompressionType>(A->getValue())
|
||||
.Case("none", llvm::DebugCompressionType::None)
|
||||
.Case("zlib", llvm::DebugCompressionType::Z)
|
||||
.Case("zlib-gnu", llvm::DebugCompressionType::GNU)
|
||||
.Default(llvm::DebugCompressionType::None);
|
||||
}
|
||||
}
|
||||
|
||||
Opts.RelaxELFRelocations = Args.hasArg(OPT_mrelax_relocations);
|
||||
Opts.DwarfVersion = getLastArgIntValue(Args, OPT_dwarf_version_EQ, 2, Diags);
|
||||
Opts.DwarfDebugFlags = Args.getLastArgValue(OPT_dwarf_debug_flags);
|
||||
Opts.DwarfDebugProducer = Args.getLastArgValue(OPT_dwarf_debug_producer);
|
||||
Opts.DebugCompilationDir = Args.getLastArgValue(OPT_fdebug_compilation_dir);
|
||||
Opts.MainFileName = Args.getLastArgValue(OPT_main_file_name);
|
||||
|
||||
for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ))
|
||||
Opts.DebugPrefixMap.insert(StringRef(Arg).split('='));
|
||||
|
||||
// Frontend Options
|
||||
if (Args.hasArg(OPT_INPUT)) {
|
||||
bool First = true;
|
||||
for (const Arg *A : Args.filtered(OPT_INPUT)) {
|
||||
if (First) {
|
||||
Opts.InputFile = A->getValue();
|
||||
First = false;
|
||||
} else {
|
||||
Diags.Report(diag::err_drv_unknown_argument) << A->getAsString(Args);
|
||||
Success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Opts.LLVMArgs = Args.getAllArgValues(OPT_mllvm);
|
||||
Opts.OutputPath = Args.getLastArgValue(OPT_o);
|
||||
Opts.SplitDwarfOutput = Args.getLastArgValue(OPT_split_dwarf_output);
|
||||
if (Arg *A = Args.getLastArg(OPT_filetype)) {
|
||||
StringRef Name = A->getValue();
|
||||
unsigned OutputType = StringSwitch<unsigned>(Name)
|
||||
.Case("asm", FT_Asm)
|
||||
.Case("null", FT_Null)
|
||||
.Case("obj", FT_Obj)
|
||||
.Default(~0U);
|
||||
if (OutputType == ~0U) {
|
||||
Diags.Report(diag::err_drv_invalid_value) << A->getAsString(Args) << Name;
|
||||
Success = false;
|
||||
} else
|
||||
Opts.OutputType = FileType(OutputType);
|
||||
}
|
||||
Opts.ShowHelp = Args.hasArg(OPT_help);
|
||||
Opts.ShowVersion = Args.hasArg(OPT_version);
|
||||
|
||||
// Transliterate Options
|
||||
Opts.OutputAsmVariant =
|
||||
getLastArgIntValue(Args, OPT_output_asm_variant, 0, Diags);
|
||||
Opts.ShowEncoding = Args.hasArg(OPT_show_encoding);
|
||||
Opts.ShowInst = Args.hasArg(OPT_show_inst);
|
||||
|
||||
// Assemble Options
|
||||
Opts.RelaxAll = Args.hasArg(OPT_mrelax_all);
|
||||
Opts.NoExecStack = Args.hasArg(OPT_mno_exec_stack);
|
||||
Opts.FatalWarnings = Args.hasArg(OPT_massembler_fatal_warnings);
|
||||
Opts.RelocationModel = Args.getLastArgValue(OPT_mrelocation_model, "pic");
|
||||
Opts.TargetABI = Args.getLastArgValue(OPT_target_abi);
|
||||
Opts.IncrementalLinkerCompatible =
|
||||
Args.hasArg(OPT_mincremental_linker_compatible);
|
||||
Opts.SymbolDefs = Args.getAllArgValues(OPT_defsym);
|
||||
|
||||
// EmbedBitcode Option. If -fembed-bitcode is enabled, set the flag.
|
||||
// EmbedBitcode behaves the same for all embed options for assembly files.
|
||||
if (auto *A = Args.getLastArg(OPT_fembed_bitcode_EQ)) {
|
||||
Opts.EmbedBitcode = llvm::StringSwitch<unsigned>(A->getValue())
|
||||
.Case("all", 1)
|
||||
.Case("bitcode", 1)
|
||||
.Case("marker", 1)
|
||||
.Default(0);
|
||||
}
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
static std::unique_ptr<raw_fd_ostream>
|
||||
getOutputStream(StringRef Path, DiagnosticsEngine &Diags, bool Binary) {
|
||||
// Make sure that the Out file gets unlinked from the disk if we get a
|
||||
// SIGINT.
|
||||
if (Path != "-")
|
||||
sys::RemoveFileOnSignal(Path);
|
||||
|
||||
std::error_code EC;
|
||||
auto Out = llvm::make_unique<raw_fd_ostream>(
|
||||
Path, EC, (Binary ? sys::fs::F_None : sys::fs::F_Text));
|
||||
if (EC) {
|
||||
Diags.Report(diag::err_fe_unable_to_open_output) << Path << EC.message();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return Out;
|
||||
}
|
||||
|
||||
bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
// Get the target specific parser.
|
||||
std::string Error;
|
||||
const Target *TheTarget = TargetRegistry::lookupTarget(Opts.Triple, Error);
|
||||
if (!TheTarget)
|
||||
return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
|
||||
|
||||
ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
|
||||
MemoryBuffer::getFileOrSTDIN(Opts.InputFile);
|
||||
|
||||
if (std::error_code EC = Buffer.getError()) {
|
||||
Error = EC.message();
|
||||
return Diags.Report(diag::err_fe_error_reading) << Opts.InputFile;
|
||||
}
|
||||
|
||||
SourceMgr SrcMgr;
|
||||
|
||||
// Tell SrcMgr about this buffer, which is what the parser will pick up.
|
||||
unsigned BufferIndex = SrcMgr.AddNewSourceBuffer(std::move(*Buffer), SMLoc());
|
||||
|
||||
// Record the location of the include directories so that the lexer can find
|
||||
// it later.
|
||||
SrcMgr.setIncludeDirs(Opts.IncludePaths);
|
||||
|
||||
std::unique_ptr<MCRegisterInfo> MRI(TheTarget->createMCRegInfo(Opts.Triple));
|
||||
assert(MRI && "Unable to create target register info!");
|
||||
|
||||
std::unique_ptr<MCAsmInfo> MAI(TheTarget->createMCAsmInfo(*MRI, Opts.Triple));
|
||||
assert(MAI && "Unable to create target asm info!");
|
||||
|
||||
// Ensure MCAsmInfo initialization occurs before any use, otherwise sections
|
||||
// may be created with a combination of default and explicit settings.
|
||||
MAI->setCompressDebugSections(Opts.CompressDebugSections);
|
||||
|
||||
MAI->setRelaxELFRelocations(Opts.RelaxELFRelocations);
|
||||
|
||||
bool IsBinary = Opts.OutputType == AssemblerInvocation::FT_Obj;
|
||||
if (Opts.OutputPath.empty())
|
||||
Opts.OutputPath = "-";
|
||||
std::unique_ptr<raw_fd_ostream> FDOS =
|
||||
getOutputStream(Opts.OutputPath, Diags, IsBinary);
|
||||
if (!FDOS)
|
||||
return true;
|
||||
std::unique_ptr<raw_fd_ostream> DwoOS;
|
||||
if (!Opts.SplitDwarfOutput.empty())
|
||||
DwoOS = getOutputStream(Opts.SplitDwarfOutput, Diags, IsBinary);
|
||||
|
||||
// FIXME: This is not pretty. MCContext has a ptr to MCObjectFileInfo and
|
||||
// MCObjectFileInfo needs a MCContext reference in order to initialize itself.
|
||||
std::unique_ptr<MCObjectFileInfo> MOFI(new MCObjectFileInfo());
|
||||
|
||||
MCContext Ctx(MAI.get(), MRI.get(), MOFI.get(), &SrcMgr);
|
||||
|
||||
bool PIC = false;
|
||||
if (Opts.RelocationModel == "static") {
|
||||
PIC = false;
|
||||
} else if (Opts.RelocationModel == "pic") {
|
||||
PIC = true;
|
||||
} else {
|
||||
assert(Opts.RelocationModel == "dynamic-no-pic" &&
|
||||
"Invalid PIC model!");
|
||||
PIC = false;
|
||||
}
|
||||
|
||||
MOFI->InitMCObjectFileInfo(Triple(Opts.Triple), PIC, Ctx);
|
||||
if (Opts.SaveTemporaryLabels)
|
||||
Ctx.setAllowTemporaryLabels(false);
|
||||
if (Opts.GenDwarfForAssembly)
|
||||
Ctx.setGenDwarfForAssembly(true);
|
||||
if (!Opts.DwarfDebugFlags.empty())
|
||||
Ctx.setDwarfDebugFlags(StringRef(Opts.DwarfDebugFlags));
|
||||
if (!Opts.DwarfDebugProducer.empty())
|
||||
Ctx.setDwarfDebugProducer(StringRef(Opts.DwarfDebugProducer));
|
||||
if (!Opts.DebugCompilationDir.empty())
|
||||
Ctx.setCompilationDir(Opts.DebugCompilationDir);
|
||||
else {
|
||||
// If no compilation dir is set, try to use the current directory.
|
||||
SmallString<128> CWD;
|
||||
if (!sys::fs::current_path(CWD))
|
||||
Ctx.setCompilationDir(CWD);
|
||||
}
|
||||
if (!Opts.DebugPrefixMap.empty())
|
||||
for (const auto &KV : Opts.DebugPrefixMap)
|
||||
Ctx.addDebugPrefixMapEntry(KV.first, KV.second);
|
||||
if (!Opts.MainFileName.empty())
|
||||
Ctx.setMainFileName(StringRef(Opts.MainFileName));
|
||||
Ctx.setDwarfVersion(Opts.DwarfVersion);
|
||||
if (Opts.GenDwarfForAssembly)
|
||||
Ctx.setGenDwarfRootFile(Opts.InputFile,
|
||||
SrcMgr.getMemoryBuffer(BufferIndex)->getBuffer());
|
||||
|
||||
// Build up the feature string from the target feature list.
|
||||
std::string FS;
|
||||
if (!Opts.Features.empty()) {
|
||||
FS = Opts.Features[0];
|
||||
for (unsigned i = 1, e = Opts.Features.size(); i != e; ++i)
|
||||
FS += "," + Opts.Features[i];
|
||||
}
|
||||
|
||||
std::unique_ptr<MCStreamer> Str;
|
||||
|
||||
std::unique_ptr<MCInstrInfo> MCII(TheTarget->createMCInstrInfo());
|
||||
std::unique_ptr<MCSubtargetInfo> STI(
|
||||
TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS));
|
||||
|
||||
raw_pwrite_stream *Out = FDOS.get();
|
||||
std::unique_ptr<buffer_ostream> BOS;
|
||||
|
||||
MCTargetOptions MCOptions;
|
||||
MCOptions.ABIName = Opts.TargetABI;
|
||||
|
||||
// FIXME: There is a bit of code duplication with addPassesToEmitFile.
|
||||
if (Opts.OutputType == AssemblerInvocation::FT_Asm) {
|
||||
MCInstPrinter *IP = TheTarget->createMCInstPrinter(
|
||||
llvm::Triple(Opts.Triple), Opts.OutputAsmVariant, *MAI, *MCII, *MRI);
|
||||
|
||||
std::unique_ptr<MCCodeEmitter> CE;
|
||||
if (Opts.ShowEncoding)
|
||||
CE.reset(TheTarget->createMCCodeEmitter(*MCII, *MRI, Ctx));
|
||||
std::unique_ptr<MCAsmBackend> MAB(
|
||||
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
|
||||
|
||||
auto FOut = llvm::make_unique<formatted_raw_ostream>(*Out);
|
||||
Str.reset(TheTarget->createAsmStreamer(
|
||||
Ctx, std::move(FOut), /*asmverbose*/ true,
|
||||
/*useDwarfDirectory*/ true, IP, std::move(CE), std::move(MAB),
|
||||
Opts.ShowInst));
|
||||
} else if (Opts.OutputType == AssemblerInvocation::FT_Null) {
|
||||
Str.reset(createNullStreamer(Ctx));
|
||||
} else {
|
||||
assert(Opts.OutputType == AssemblerInvocation::FT_Obj &&
|
||||
"Invalid file type!");
|
||||
if (!FDOS->supportsSeeking()) {
|
||||
BOS = make_unique<buffer_ostream>(*FDOS);
|
||||
Out = BOS.get();
|
||||
}
|
||||
|
||||
std::unique_ptr<MCCodeEmitter> CE(
|
||||
TheTarget->createMCCodeEmitter(*MCII, *MRI, Ctx));
|
||||
std::unique_ptr<MCAsmBackend> MAB(
|
||||
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
|
||||
std::unique_ptr<MCObjectWriter> OW =
|
||||
DwoOS ? MAB->createDwoObjectWriter(*Out, *DwoOS)
|
||||
: MAB->createObjectWriter(*Out);
|
||||
|
||||
Triple T(Opts.Triple);
|
||||
Str.reset(TheTarget->createMCObjectStreamer(
|
||||
T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI,
|
||||
Opts.RelaxAll, Opts.IncrementalLinkerCompatible,
|
||||
/*DWARFMustBeAtTheEnd*/ true));
|
||||
Str.get()->InitSections(Opts.NoExecStack);
|
||||
}
|
||||
|
||||
// When -fembed-bitcode is passed to clang_as, a 1-byte marker
|
||||
// is emitted in __LLVM,__asm section if the object file is MachO format.
|
||||
if (Opts.EmbedBitcode && Ctx.getObjectFileInfo()->getObjectFileType() ==
|
||||
MCObjectFileInfo::IsMachO) {
|
||||
MCSection *AsmLabel = Ctx.getMachOSection(
|
||||
"__LLVM", "__asm", MachO::S_REGULAR, 4, SectionKind::getReadOnly());
|
||||
Str.get()->SwitchSection(AsmLabel);
|
||||
Str.get()->EmitZeros(1);
|
||||
}
|
||||
|
||||
// Assembly to object compilation should leverage assembly info.
|
||||
Str->setUseAssemblerInfoForParsing(true);
|
||||
|
||||
bool Failed = false;
|
||||
|
||||
std::unique_ptr<MCAsmParser> Parser(
|
||||
createMCAsmParser(SrcMgr, Ctx, *Str.get(), *MAI));
|
||||
|
||||
// FIXME: init MCTargetOptions from sanitizer flags here.
|
||||
std::unique_ptr<MCTargetAsmParser> TAP(
|
||||
TheTarget->createMCAsmParser(*STI, *Parser, *MCII, MCOptions));
|
||||
if (!TAP)
|
||||
Failed = Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
|
||||
|
||||
// Set values for symbols, if any.
|
||||
for (auto &S : Opts.SymbolDefs) {
|
||||
auto Pair = StringRef(S).split('=');
|
||||
auto Sym = Pair.first;
|
||||
auto Val = Pair.second;
|
||||
int64_t Value;
|
||||
// We have already error checked this in the driver.
|
||||
Val.getAsInteger(0, Value);
|
||||
Ctx.setSymbolValue(Parser->getStreamer(), Sym, Value);
|
||||
}
|
||||
|
||||
if (!Failed) {
|
||||
Parser->setTargetParser(*TAP.get());
|
||||
Failed = Parser->Run(Opts.NoInitialTextSection);
|
||||
}
|
||||
|
||||
// Close Streamer first.
|
||||
// It might have a reference to the output stream.
|
||||
Str.reset();
|
||||
// Close the output stream early.
|
||||
BOS.reset();
|
||||
FDOS.reset();
|
||||
|
||||
// Delete output file if there were errors.
|
||||
if (Failed) {
|
||||
if (Opts.OutputPath != "-")
|
||||
sys::fs::remove(Opts.OutputPath);
|
||||
if (!Opts.SplitDwarfOutput.empty() && Opts.SplitDwarfOutput != "-")
|
||||
sys::fs::remove(Opts.SplitDwarfOutput);
|
||||
}
|
||||
|
||||
return Failed;
|
||||
}
|
||||
|
||||
static void LLVMErrorHandler(void *UserData, const std::string &Message,
|
||||
bool GenCrashDiag) {
|
||||
DiagnosticsEngine &Diags = *static_cast<DiagnosticsEngine*>(UserData);
|
||||
|
||||
Diags.Report(diag::err_fe_error_backend) << Message;
|
||||
|
||||
// We cannot recover from llvm errors.
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
|
||||
// Initialize targets and assembly printers/parsers.
|
||||
InitializeAllTargetInfos();
|
||||
InitializeAllTargetMCs();
|
||||
InitializeAllAsmParsers();
|
||||
|
||||
// Construct our diagnostic client.
|
||||
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
|
||||
TextDiagnosticPrinter *DiagClient
|
||||
= new TextDiagnosticPrinter(errs(), &*DiagOpts);
|
||||
DiagClient->setPrefix("clang -cc1as");
|
||||
IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
|
||||
DiagnosticsEngine Diags(DiagID, &*DiagOpts, DiagClient);
|
||||
|
||||
// Set an error handler, so that any LLVM backend diagnostics go through our
|
||||
// error handler.
|
||||
ScopedFatalErrorHandler FatalErrorHandler
|
||||
(LLVMErrorHandler, static_cast<void*>(&Diags));
|
||||
|
||||
// Parse the arguments.
|
||||
AssemblerInvocation Asm;
|
||||
if (!AssemblerInvocation::CreateFromArgs(Asm, Argv, Diags))
|
||||
return 1;
|
||||
|
||||
if (Asm.ShowHelp) {
|
||||
std::unique_ptr<OptTable> Opts(driver::createDriverOptTable());
|
||||
Opts->PrintHelp(llvm::outs(), "clang -cc1as [options] file...",
|
||||
"Clang Integrated Assembler",
|
||||
/*Include=*/driver::options::CC1AsOption, /*Exclude=*/0,
|
||||
/*ShowAllAliases=*/false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Honor -version.
|
||||
//
|
||||
// FIXME: Use a better -version message?
|
||||
if (Asm.ShowVersion) {
|
||||
llvm::cl::PrintVersionMessage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Honor -mllvm.
|
||||
//
|
||||
// FIXME: Remove this, one day.
|
||||
if (!Asm.LLVMArgs.empty()) {
|
||||
unsigned NumArgs = Asm.LLVMArgs.size();
|
||||
auto Args = llvm::make_unique<const char*[]>(NumArgs + 2);
|
||||
Args[0] = "clang (LLVM option parsing)";
|
||||
for (unsigned i = 0; i != NumArgs; ++i)
|
||||
Args[i + 1] = Asm.LLVMArgs[i].c_str();
|
||||
Args[NumArgs + 1] = nullptr;
|
||||
llvm::cl::ParseCommandLineOptions(NumArgs + 1, Args.get());
|
||||
}
|
||||
|
||||
// Execute the invocation, unless there were parsing errors.
|
||||
bool Failed = Diags.hasErrorOccurred() || ExecuteAssembler(Asm, Diags);
|
||||
|
||||
// If any timers were active but haven't been destroyed yet, print their
|
||||
// results now.
|
||||
TimerGroup::printAll(errs());
|
||||
|
||||
return !!Failed;
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
//===-- cc1as.h - Clang Assembler ----------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This is the entry point to the clang -cc1as functionality, which implements
|
||||
// the direct interface to the LLVM MC based assembler.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
|
||||
/// Helper class for representing a single invocation of the assembler.
|
||||
struct AssemblerInvocation {
|
||||
/// @name Target Options
|
||||
/// @{
|
||||
|
||||
/// The name of the target triple to assemble for.
|
||||
std::string Triple;
|
||||
|
||||
/// If given, the name of the target CPU to determine which instructions
|
||||
/// are legal.
|
||||
std::string CPU;
|
||||
|
||||
/// The list of target specific features to enable or disable -- this should
|
||||
/// be a list of strings starting with '+' or '-'.
|
||||
std::vector<std::string> Features;
|
||||
|
||||
/// The list of symbol definitions.
|
||||
std::vector<std::string> SymbolDefs;
|
||||
|
||||
/// @}
|
||||
/// @name Language Options
|
||||
/// @{
|
||||
|
||||
std::vector<std::string> IncludePaths;
|
||||
unsigned NoInitialTextSection : 1;
|
||||
unsigned SaveTemporaryLabels : 1;
|
||||
unsigned GenDwarfForAssembly : 1;
|
||||
unsigned RelaxELFRelocations : 1;
|
||||
unsigned DwarfVersion;
|
||||
std::string DwarfDebugFlags;
|
||||
std::string DwarfDebugProducer;
|
||||
std::string DebugCompilationDir;
|
||||
std::map<const std::string, const std::string> DebugPrefixMap;
|
||||
llvm::DebugCompressionType CompressDebugSections =
|
||||
llvm::DebugCompressionType::None;
|
||||
std::string MainFileName;
|
||||
std::string SplitDwarfOutput;
|
||||
|
||||
/// @}
|
||||
/// @name Frontend Options
|
||||
/// @{
|
||||
|
||||
std::string InputFile;
|
||||
std::vector<std::string> LLVMArgs;
|
||||
std::string OutputPath;
|
||||
enum FileType {
|
||||
FT_Asm, ///< Assembly (.s) output, transliterate mode.
|
||||
FT_Null, ///< No output, for timing purposes.
|
||||
FT_Obj ///< Object file output.
|
||||
};
|
||||
FileType OutputType;
|
||||
unsigned ShowHelp : 1;
|
||||
unsigned ShowVersion : 1;
|
||||
|
||||
/// @}
|
||||
/// @name Transliterate Options
|
||||
/// @{
|
||||
|
||||
unsigned OutputAsmVariant;
|
||||
unsigned ShowEncoding : 1;
|
||||
unsigned ShowInst : 1;
|
||||
|
||||
/// @}
|
||||
/// @name Assembler Options
|
||||
/// @{
|
||||
|
||||
unsigned RelaxAll : 1;
|
||||
unsigned NoExecStack : 1;
|
||||
unsigned FatalWarnings : 1;
|
||||
unsigned IncrementalLinkerCompatible : 1;
|
||||
unsigned EmbedBitcode : 1;
|
||||
|
||||
/// The name of the relocation model to use.
|
||||
std::string RelocationModel;
|
||||
|
||||
/// The ABI targeted by the backend. Specified using -target-abi. Empty
|
||||
/// otherwise.
|
||||
std::string TargetABI;
|
||||
|
||||
/// @}
|
||||
|
||||
public:
|
||||
AssemblerInvocation() {
|
||||
Triple = "";
|
||||
NoInitialTextSection = 0;
|
||||
InputFile = "-";
|
||||
OutputPath = "-";
|
||||
OutputType = FT_Asm;
|
||||
OutputAsmVariant = 0;
|
||||
ShowInst = 0;
|
||||
ShowEncoding = 0;
|
||||
RelaxAll = 0;
|
||||
NoExecStack = 0;
|
||||
FatalWarnings = 0;
|
||||
IncrementalLinkerCompatible = 0;
|
||||
DwarfVersion = 0;
|
||||
EmbedBitcode = 0;
|
||||
}
|
||||
|
||||
static bool CreateFromArgs(AssemblerInvocation &Res,
|
||||
ArrayRef<const char *> Argv,
|
||||
DiagnosticsEngine &Diags);
|
||||
};
|
||||
|
||||
bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags);
|
||||
@@ -0,0 +1,97 @@
|
||||
// +build byollvm
|
||||
|
||||
#include <clang/Basic/DiagnosticOptions.h>
|
||||
#include <clang/CodeGen/CodeGenAction.h>
|
||||
#include <clang/Driver/Compilation.h>
|
||||
#include <clang/Driver/Driver.h>
|
||||
#include <clang/Frontend/CompilerInstance.h>
|
||||
#include <clang/Frontend/CompilerInvocation.h>
|
||||
#include <clang/Frontend/FrontendDiagnostic.h>
|
||||
#include <clang/Frontend/TextDiagnosticPrinter.h>
|
||||
#include <clang/FrontendTool/Utils.h>
|
||||
#include <llvm/ADT/IntrusiveRefCntPtr.h>
|
||||
#include <llvm/Option/Option.h>
|
||||
|
||||
using namespace llvm;
|
||||
using namespace clang;
|
||||
|
||||
#include "cc1as.h"
|
||||
|
||||
// This file provides C wrappers for the builtin tools cc1 and cc1as
|
||||
// provided by Clang, and calls them as the driver would call them.
|
||||
|
||||
extern "C" {
|
||||
|
||||
bool tinygo_clang_driver(int argc, char **argv) {
|
||||
std::vector<const char*> args(argv, argv + argc);
|
||||
|
||||
// The compiler invocation needs a DiagnosticsEngine so it can report problems
|
||||
llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> DiagOpts = new clang::DiagnosticOptions();
|
||||
clang::TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts);
|
||||
clang::DiagnosticsEngine Diags(llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>(new clang::DiagnosticIDs()), &*DiagOpts, &DiagnosticPrinter, false);
|
||||
|
||||
// Create the clang driver
|
||||
clang::driver::Driver TheDriver(args[0], llvm::sys::getDefaultTargetTriple(), Diags);
|
||||
|
||||
// Create the set of actions to perform
|
||||
std::unique_ptr<clang::driver::Compilation> C(TheDriver.BuildCompilation(args));
|
||||
if (!C) {
|
||||
return false;
|
||||
}
|
||||
const clang::driver::JobList &Jobs = C->getJobs();
|
||||
|
||||
// There may be more than one job, for example for .S files
|
||||
// (preprocessor + assembler).
|
||||
for (auto Cmd : Jobs) {
|
||||
// Select the tool: cc1 or cc1as.
|
||||
const llvm::opt::ArgStringList &CCArgs = Cmd.getArguments();
|
||||
|
||||
if (strcmp(*CCArgs.data(), "-cc1") == 0) {
|
||||
// This is the C frontend.
|
||||
// Initialize a compiler invocation object from the clang (-cc1) arguments.
|
||||
std::unique_ptr<clang::CompilerInstance> Clang(new clang::CompilerInstance());
|
||||
bool success = clang::CompilerInvocation::CreateFromArgs(
|
||||
Clang->getInvocation(),
|
||||
const_cast<const char **>(CCArgs.data()),
|
||||
const_cast<const char **>(CCArgs.data()) + CCArgs.size(),
|
||||
Diags);
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the actual diagnostics engine.
|
||||
Clang->createDiagnostics();
|
||||
if (!Clang->hasDiagnostics()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Execute the frontend actions.
|
||||
success = ExecuteCompilerInvocation(Clang.get());
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else if (strcmp(*CCArgs.data(), "-cc1as") == 0) {
|
||||
// This is the assembler frontend. Parse the arguments.
|
||||
AssemblerInvocation Asm;
|
||||
if (!AssemblerInvocation::CreateFromArgs(Asm, llvm::ArrayRef<const char*>(CCArgs).slice(1), Diags))
|
||||
return false;
|
||||
|
||||
// Execute the invocation, unless there were parsing errors.
|
||||
bool failed = Diags.hasErrorOccurred() || ExecuteAssembler(Asm, Diags);
|
||||
if (failed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Unknown tool, print the tool and exit.
|
||||
fprintf(stderr, "unknown tool: %s\n", *CCArgs.data());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Commands executed successfully.
|
||||
return true;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,56 @@
|
||||
// +build byollvm
|
||||
|
||||
package builder
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CXXFLAGS: -fno-rtti
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
bool tinygo_clang_driver(int argc, char **argv);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// runCCompiler invokes a C compiler with the given arguments.
|
||||
//
|
||||
// This version invokes the built-in Clang when trying to run the Clang compiler.
|
||||
func runCCompiler(command string, flags ...string) error {
|
||||
switch command {
|
||||
case "clang":
|
||||
// Compile this with the internal Clang compiler.
|
||||
flags = append(flags, "-I"+getClangHeaderPath(goenv.Get("TINYGOROOT")))
|
||||
flags = append([]string{"tinygo:" + command}, flags...)
|
||||
var cflag *C.char
|
||||
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
|
||||
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
|
||||
for i, flag := range flags {
|
||||
cflag := C.CString(flag)
|
||||
cflags[i] = cflag
|
||||
defer C.free(unsafe.Pointer(cflag))
|
||||
}
|
||||
ok := C.tinygo_clang_driver(C.int(len(flags)), (**C.char)(buf))
|
||||
if !ok {
|
||||
return errors.New("failed to compile using built-in clang")
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
// Running some other compiler. Maybe it has been defined in the
|
||||
// commands map (unlikely).
|
||||
if cmdNames, ok := commands[command]; ok {
|
||||
return execCommand(cmdNames, flags...)
|
||||
}
|
||||
// Alternatively, run the compiler directly.
|
||||
cmd := exec.Command(command, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// +build !byollvm
|
||||
|
||||
package builder
|
||||
|
||||
// This file provides a way to a C compiler as an external command. See also:
|
||||
// clang-external.go
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// runCCompiler invokes a C compiler with the given arguments.
|
||||
//
|
||||
// This version always runs the compiler as an external command.
|
||||
func runCCompiler(command string, flags ...string) error {
|
||||
if cmdNames, ok := commands[command]; ok {
|
||||
return execCommand(cmdNames, flags...)
|
||||
}
|
||||
cmd := exec.Command(command, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
@@ -12,6 +12,20 @@ func (e *MultiError) Error() string {
|
||||
return e.Errs[0].Error()
|
||||
}
|
||||
|
||||
// newMultiError returns a *MultiError if there is more than one error, or
|
||||
// returns that error directly when there is only one. Passing an empty slice
|
||||
// will lead to a panic.
|
||||
func newMultiError(errs []error) error {
|
||||
switch len(errs) {
|
||||
case 0:
|
||||
panic("attempted to create empty MultiError")
|
||||
case 1:
|
||||
return errs[0]
|
||||
default:
|
||||
return &MultiError{errs}
|
||||
}
|
||||
}
|
||||
|
||||
// commandError is an error type to wrap os/exec.Command errors. This provides
|
||||
// some more information regarding what went wrong while running a command.
|
||||
type commandError struct {
|
||||
|
||||
@@ -25,8 +25,6 @@ func TestCGo(t *testing.T) {
|
||||
for _, name := range []string{"basic", "errors", "types", "flags"} {
|
||||
name := name // avoid a race condition
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Read the AST in memory.
|
||||
path := filepath.Join("testdata", name+".go")
|
||||
fset := token.NewFileSet()
|
||||
|
||||
+19
-20
@@ -98,76 +98,75 @@ func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specia
|
||||
func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
||||
// check value properties
|
||||
if err := c.checkValue(inst, types, specials); err != nil {
|
||||
return fmt.Errorf("failed to validate value of instruction %q: %s", inst.Name(), err.Error())
|
||||
return errorAt(inst, err.Error())
|
||||
}
|
||||
|
||||
// check operands
|
||||
for i := 0; i < inst.OperandsCount(); i++ {
|
||||
if err := c.checkValue(inst.Operand(i), types, specials); err != nil {
|
||||
return fmt.Errorf("failed to validate argument %d of instruction %q: %s", i, inst.Name(), err.Error())
|
||||
return errorAt(inst, fmt.Sprintf("failed to validate operand %d of instruction %q: %s", i, inst.Name(), err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
||||
func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
|
||||
// check basic block value and type
|
||||
var errs []error
|
||||
if err := c.checkValue(bb.AsValue(), types, specials); err != nil {
|
||||
return fmt.Errorf("failed to validate value of basic block %s: %s", bb.AsValue().Name(), err.Error())
|
||||
errs = append(errs, errorAt(bb.Parent(), fmt.Sprintf("failed to validate value of basic block %s: %v", bb.AsValue().Name(), err)))
|
||||
}
|
||||
|
||||
// check instructions
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if err := c.checkInstruction(inst, types, specials); err != nil {
|
||||
return fmt.Errorf("failed to validate basic block %q: %s", bb.AsValue().Name(), err.Error())
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return errs
|
||||
}
|
||||
|
||||
func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
||||
func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
|
||||
// check function value and type
|
||||
var errs []error
|
||||
if err := c.checkValue(fn, types, specials); err != nil {
|
||||
return fmt.Errorf("failed to validate value of function %s: %s", fn.Name(), err.Error())
|
||||
errs = append(errs, fmt.Errorf("failed to validate value of function %s: %s", fn.Name(), err.Error()))
|
||||
}
|
||||
|
||||
// check basic blocks
|
||||
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
if err := c.checkBasicBlock(bb, types, specials); err != nil {
|
||||
return fmt.Errorf("failed to validate basic block of function %s: %s", fn.Name(), err.Error())
|
||||
}
|
||||
errs = append(errs, c.checkBasicBlock(bb, types, specials)...)
|
||||
}
|
||||
|
||||
return nil
|
||||
return errs
|
||||
}
|
||||
|
||||
func (c *Compiler) checkModule() error {
|
||||
func (c *Compiler) checkModule() []error {
|
||||
// check for any context mismatches
|
||||
var errs []error
|
||||
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
|
||||
return errors.New("module uses global context")
|
||||
errs = append(errs, errors.New("module uses global context"))
|
||||
default:
|
||||
// we used some other context by accident
|
||||
return fmt.Errorf("module uses context %v instead of the main context %v", c.mod.Context(), c.ctx)
|
||||
errs = append(errs, 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) {
|
||||
if err := c.checkFunction(fn, types, specials); err != nil {
|
||||
return err
|
||||
}
|
||||
errs = append(errs, c.checkFunction(fn, types, specials)...)
|
||||
}
|
||||
for g := c.mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
|
||||
if err := c.checkValue(g, types, specials); err != nil {
|
||||
return fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error())
|
||||
errs = append(errs, fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return errs
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"path/filepath"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
|
||||
@@ -16,3 +20,31 @@ func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
|
||||
func (c *Compiler) addError(pos token.Pos, msg string) {
|
||||
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
|
||||
}
|
||||
|
||||
// errorAt returns an error value at the location of the instruction.
|
||||
// The location information may not be complete as it depends on debug
|
||||
// information in the IR.
|
||||
func errorAt(inst llvm.Value, msg string) scanner.Error {
|
||||
return scanner.Error{
|
||||
Pos: getPosition(inst),
|
||||
Msg: msg,
|
||||
}
|
||||
}
|
||||
|
||||
// getPosition returns the position information for the given instruction, as
|
||||
// far as it is available.
|
||||
func getPosition(inst llvm.Value) token.Position {
|
||||
if inst.IsAInstruction().IsNil() {
|
||||
return token.Position{}
|
||||
}
|
||||
loc := inst.InstructionDebugLoc()
|
||||
if loc.IsNil() {
|
||||
return token.Position{}
|
||||
}
|
||||
file := loc.LocationScope().ScopeFile()
|
||||
return token.Position{
|
||||
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
|
||||
Line: int(loc.LocationLine()),
|
||||
Column: int(loc.LocationColumn()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,6 @@ package compiler
|
||||
// scheduler, which runs in the background scheduling all coroutines.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -283,7 +282,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
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 ptrtoint, expected runtime.makeGoroutine")
|
||||
return false, errorAt(call, "async function incorrectly used in ptrtoint, expected runtime.makeGoroutine")
|
||||
}
|
||||
}
|
||||
// This is a go statement. Do not mark the parent as async, as
|
||||
@@ -303,12 +302,19 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
// Not a call instruction. Maybe a store to a global? In any
|
||||
// case, this requires support for async calls across function
|
||||
// pointers which is not yet supported.
|
||||
return false, errors.New("async function " + f.Name() + " used as function pointer")
|
||||
at := use
|
||||
if use.IsAInstruction().IsNil() {
|
||||
// The use might not be an instruction (for example, in the
|
||||
// case of a const bitcast). Fall back to reporting the
|
||||
// location of the function instead.
|
||||
at = f
|
||||
}
|
||||
return false, errorAt(at, "async function "+f.Name()+" used as function pointer")
|
||||
}
|
||||
parent := use.InstructionParent().Parent()
|
||||
for i := 0; i < use.OperandsCount()-1; i++ {
|
||||
if use.Operand(i) == f {
|
||||
return false, errors.New("async function " + f.Name() + " used as function pointer in " + parent.Name())
|
||||
return false, errorAt(use, "async function "+f.Name()+" used as function pointer")
|
||||
}
|
||||
}
|
||||
worklist = append(worklist, parent)
|
||||
@@ -805,7 +811,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
llvm.ConstNull(c.ctx.TokenType()),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||
}, "")
|
||||
wakeup := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup")
|
||||
wakeup := llvmutil.SplitBasicBlock(c.builder, inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup")
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
||||
@@ -906,12 +912,12 @@ func (c *Compiler) lowerMakeGoroutineCalls(sched bool) error {
|
||||
origFunc := ptrtointIn.Operand(0)
|
||||
uses := getUses(goroutine)
|
||||
if len(uses) != 1 || uses[0].IsAIntToPtrInst().IsNil() {
|
||||
return errors.New("expected exactly 1 inttoptr use of runtime.makeGoroutine")
|
||||
return errorAt(makeGoroutine, "expected exactly 1 inttoptr use of runtime.makeGoroutine")
|
||||
}
|
||||
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")
|
||||
return errorAt(inttoptrOut, "expected exactly 1 call use of runtime.makeGoroutine bitcast")
|
||||
}
|
||||
realCall := uses[0]
|
||||
|
||||
|
||||
@@ -52,78 +52,6 @@ func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []l
|
||||
return llvmutil.EmitPointerUnpack(c.builder, c.mod, ptr, valueTypes)
|
||||
}
|
||||
|
||||
// splitBasicBlock splits a LLVM basic block into two parts. All instructions
|
||||
// after afterInst are moved into a new basic block (created right after the
|
||||
// current one) with the given name.
|
||||
func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicBlock, name string) llvm.BasicBlock {
|
||||
oldBlock := afterInst.InstructionParent()
|
||||
newBlock := c.ctx.InsertBasicBlock(insertAfter, name)
|
||||
var nextInstructions []llvm.Value // values to move
|
||||
|
||||
// Collect to-be-moved instructions.
|
||||
inst := afterInst
|
||||
for {
|
||||
inst = llvm.NextInstruction(inst)
|
||||
if inst.IsNil() {
|
||||
break
|
||||
}
|
||||
nextInstructions = append(nextInstructions, inst)
|
||||
}
|
||||
|
||||
// Move instructions.
|
||||
c.builder.SetInsertPointAtEnd(newBlock)
|
||||
for _, inst := range nextInstructions {
|
||||
inst.RemoveFromParentAsInstruction()
|
||||
c.builder.Insert(inst)
|
||||
}
|
||||
|
||||
// Find PHI nodes to update.
|
||||
var phiNodes []llvm.Value // PHI nodes to update
|
||||
for bb := insertAfter.Parent().FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if inst.IsAPHINode().IsNil() {
|
||||
continue
|
||||
}
|
||||
needsUpdate := false
|
||||
incomingCount := inst.IncomingCount()
|
||||
for i := 0; i < incomingCount; i++ {
|
||||
if inst.IncomingBlock(i) == oldBlock {
|
||||
needsUpdate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !needsUpdate {
|
||||
// PHI node has no incoming edge from the old block.
|
||||
continue
|
||||
}
|
||||
phiNodes = append(phiNodes, inst)
|
||||
}
|
||||
}
|
||||
|
||||
// Update PHI nodes.
|
||||
for _, phi := range phiNodes {
|
||||
c.builder.SetInsertPointBefore(phi)
|
||||
newPhi := c.builder.CreatePHI(phi.Type(), "")
|
||||
incomingCount := phi.IncomingCount()
|
||||
incomingVals := make([]llvm.Value, incomingCount)
|
||||
incomingBlocks := make([]llvm.BasicBlock, incomingCount)
|
||||
for i := 0; i < incomingCount; i++ {
|
||||
value := phi.IncomingValue(i)
|
||||
block := phi.IncomingBlock(i)
|
||||
if block == oldBlock {
|
||||
block = newBlock
|
||||
}
|
||||
incomingVals[i] = value
|
||||
incomingBlocks[i] = block
|
||||
}
|
||||
newPhi.AddIncoming(incomingVals, incomingBlocks)
|
||||
phi.ReplaceAllUsesWith(newPhi)
|
||||
phi.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
return newBlock
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@@ -94,3 +94,75 @@ func getLifetimeEndFunc(mod llvm.Module) llvm.Value {
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// SplitBasicBlock splits a LLVM basic block into two parts. All instructions
|
||||
// after afterInst are moved into a new basic block (created right after the
|
||||
// current one) with the given name.
|
||||
func SplitBasicBlock(builder llvm.Builder, afterInst llvm.Value, insertAfter llvm.BasicBlock, name string) llvm.BasicBlock {
|
||||
oldBlock := afterInst.InstructionParent()
|
||||
newBlock := afterInst.Type().Context().InsertBasicBlock(insertAfter, name)
|
||||
var nextInstructions []llvm.Value // values to move
|
||||
|
||||
// Collect to-be-moved instructions.
|
||||
inst := afterInst
|
||||
for {
|
||||
inst = llvm.NextInstruction(inst)
|
||||
if inst.IsNil() {
|
||||
break
|
||||
}
|
||||
nextInstructions = append(nextInstructions, inst)
|
||||
}
|
||||
|
||||
// Move instructions.
|
||||
builder.SetInsertPointAtEnd(newBlock)
|
||||
for _, inst := range nextInstructions {
|
||||
inst.RemoveFromParentAsInstruction()
|
||||
builder.Insert(inst)
|
||||
}
|
||||
|
||||
// Find PHI nodes to update.
|
||||
var phiNodes []llvm.Value // PHI nodes to update
|
||||
for bb := insertAfter.Parent().FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if inst.IsAPHINode().IsNil() {
|
||||
continue
|
||||
}
|
||||
needsUpdate := false
|
||||
incomingCount := inst.IncomingCount()
|
||||
for i := 0; i < incomingCount; i++ {
|
||||
if inst.IncomingBlock(i) == oldBlock {
|
||||
needsUpdate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !needsUpdate {
|
||||
// PHI node has no incoming edge from the old block.
|
||||
continue
|
||||
}
|
||||
phiNodes = append(phiNodes, inst)
|
||||
}
|
||||
}
|
||||
|
||||
// Update PHI nodes.
|
||||
for _, phi := range phiNodes {
|
||||
builder.SetInsertPointBefore(phi)
|
||||
newPhi := builder.CreatePHI(phi.Type(), "")
|
||||
incomingCount := phi.IncomingCount()
|
||||
incomingVals := make([]llvm.Value, incomingCount)
|
||||
incomingBlocks := make([]llvm.BasicBlock, incomingCount)
|
||||
for i := 0; i < incomingCount; i++ {
|
||||
value := phi.IncomingValue(i)
|
||||
block := phi.IncomingBlock(i)
|
||||
if block == oldBlock {
|
||||
block = newBlock
|
||||
}
|
||||
incomingVals[i] = value
|
||||
incomingBlocks[i] = block
|
||||
}
|
||||
newPhi.AddIncoming(incomingVals, incomingBlocks)
|
||||
phi.ReplaceAllUsesWith(newPhi)
|
||||
phi.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
return newBlock
|
||||
}
|
||||
|
||||
+16
-12
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
// Run the LLVM optimizer over the module.
|
||||
// The inliner can be disabled (if necessary) by passing 0 to the inlinerThreshold.
|
||||
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) error {
|
||||
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []error {
|
||||
builder := llvm.NewPassManagerBuilder()
|
||||
defer builder.Dispose()
|
||||
builder.SetOptLevel(optLevel)
|
||||
@@ -25,9 +25,9 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
|
||||
// run a check of all of our code
|
||||
if c.VerifyIR() {
|
||||
err := c.checkModule()
|
||||
if err != nil {
|
||||
return err
|
||||
errs := c.checkModule()
|
||||
if errs != nil {
|
||||
return errs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
transform.OptimizeStringToBytes(c.mod)
|
||||
transform.OptimizeAllocs(c.mod)
|
||||
transform.LowerInterfaces(c.mod)
|
||||
c.LowerFuncValues()
|
||||
if c.funcImplementation() == funcValueSwitch {
|
||||
transform.LowerFuncValues(c.mod)
|
||||
}
|
||||
|
||||
// After interfaces are lowered, there are many more opportunities for
|
||||
// interprocedural optimizations. To get them to work, function
|
||||
@@ -85,24 +87,26 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
|
||||
err := c.LowerGoroutines()
|
||||
if err != nil {
|
||||
return err
|
||||
return []error{err}
|
||||
}
|
||||
} else {
|
||||
// Must be run at any optimization level.
|
||||
transform.LowerInterfaces(c.mod)
|
||||
c.LowerFuncValues()
|
||||
if c.funcImplementation() == funcValueSwitch {
|
||||
transform.LowerFuncValues(c.mod)
|
||||
}
|
||||
err := c.LowerGoroutines()
|
||||
if err != nil {
|
||||
return err
|
||||
return []error{err}
|
||||
}
|
||||
}
|
||||
if c.VerifyIR() {
|
||||
if err := c.checkModule(); err != nil {
|
||||
return err
|
||||
if errs := c.checkModule(); errs != nil {
|
||||
return errs
|
||||
}
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("optimizations caused a verification failure")
|
||||
return []error{errors.New("optimizations caused a verification failure")}
|
||||
}
|
||||
|
||||
if sizeLevel >= 2 {
|
||||
@@ -141,7 +145,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
hasGCPass = transform.MakeGCStackSlots(c.mod) || hasGCPass
|
||||
if hasGCPass {
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("GC pass caused a verification failure")
|
||||
return []error{errors.New("GC pass caused a verification failure")}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+37
-10
@@ -278,24 +278,49 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
}
|
||||
case callee.Name() == "runtime.hashmapStringSet":
|
||||
// set a string key in the map
|
||||
m, ok := fr.getLocal(inst.Operand(0)).(*MapValue)
|
||||
if !ok {
|
||||
return nil, nil, fr.errorAt(inst, "could not update map with string key")
|
||||
}
|
||||
// "key" is a Go string value, which in the TinyGo calling convention is split up
|
||||
// into separate pointer and length parameters.
|
||||
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
|
||||
keyLen := fr.getLocal(inst.Operand(2)).(*LocalValue)
|
||||
valPtr := fr.getLocal(inst.Operand(3)).(*LocalValue)
|
||||
m, ok := fr.getLocal(inst.Operand(0)).(*MapValue)
|
||||
if !ok || !keyBuf.IsConstant() || !keyLen.IsConstant() || !valPtr.IsConstant() {
|
||||
// The mapassign operation could not be done at compile
|
||||
// time. Do it at runtime instead.
|
||||
m := fr.getLocal(inst.Operand(0)).Value()
|
||||
fr.markDirty(m)
|
||||
llvmParams := []llvm.Value{
|
||||
m, // *runtime.hashmap
|
||||
fr.getLocal(inst.Operand(1)).Value(), // key.ptr
|
||||
fr.getLocal(inst.Operand(2)).Value(), // key.len
|
||||
fr.getLocal(inst.Operand(3)).Value(), // value (unsafe.Pointer)
|
||||
fr.getLocal(inst.Operand(4)).Value(), // context
|
||||
fr.getLocal(inst.Operand(5)).Value(), // parentHandle
|
||||
}
|
||||
fr.builder.CreateCall(callee, llvmParams, "")
|
||||
continue
|
||||
}
|
||||
// "key" is a Go string value, which in the TinyGo calling convention is split up
|
||||
// into separate pointer and length parameters.
|
||||
m.PutString(keyBuf, keyLen, valPtr)
|
||||
case callee.Name() == "runtime.hashmapBinarySet":
|
||||
// set a binary (int etc.) key in the map
|
||||
m, ok := fr.getLocal(inst.Operand(0)).(*MapValue)
|
||||
if !ok {
|
||||
return nil, nil, fr.errorAt(inst, "could not update map")
|
||||
}
|
||||
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
|
||||
valPtr := fr.getLocal(inst.Operand(2)).(*LocalValue)
|
||||
m, ok := fr.getLocal(inst.Operand(0)).(*MapValue)
|
||||
if !ok || !keyBuf.IsConstant() || !valPtr.IsConstant() {
|
||||
// The mapassign operation could not be done at compile
|
||||
// time. Do it at runtime instead.
|
||||
m := fr.getLocal(inst.Operand(0)).Value()
|
||||
fr.markDirty(m)
|
||||
llvmParams := []llvm.Value{
|
||||
m, // *runtime.hashmap
|
||||
fr.getLocal(inst.Operand(1)).Value(), // key
|
||||
fr.getLocal(inst.Operand(2)).Value(), // value
|
||||
fr.getLocal(inst.Operand(3)).Value(), // context
|
||||
fr.getLocal(inst.Operand(4)).Value(), // parentHandle
|
||||
}
|
||||
fr.builder.CreateCall(callee, llvmParams, "")
|
||||
continue
|
||||
}
|
||||
m.PutBinary(keyBuf, valPtr)
|
||||
case callee.Name() == "runtime.stringConcat":
|
||||
// adding two strings together
|
||||
@@ -433,6 +458,8 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
|
||||
case callee.Name() == "llvm.dbg.value":
|
||||
// do nothing
|
||||
case strings.HasPrefix(callee.Name(), "llvm.lifetime."):
|
||||
// do nothing
|
||||
case callee.Name() == "runtime.trackPointer":
|
||||
// do nothing
|
||||
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
|
||||
|
||||
@@ -14,6 +14,7 @@ func TestInterp(t *testing.T) {
|
||||
"basic",
|
||||
"slice-copy",
|
||||
"consteval",
|
||||
"map",
|
||||
} {
|
||||
name := name // make tc local to this closure
|
||||
t.Run(name, func(t *testing.T) {
|
||||
|
||||
+13
-8
@@ -1,6 +1,8 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
@@ -39,22 +41,25 @@ type sideEffectResult struct {
|
||||
// returns whether this function has side effects and if it does, which globals
|
||||
// it mentions anywhere in this function or any called functions.
|
||||
func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, error) {
|
||||
switch fn.Name() {
|
||||
case "runtime.alloc":
|
||||
name := fn.Name()
|
||||
switch {
|
||||
case name == "runtime.alloc":
|
||||
// Cannot be scanned but can be interpreted.
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case "runtime.nanotime":
|
||||
case name == "runtime.nanotime":
|
||||
// Fixed value at compile time.
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case "runtime._panic":
|
||||
case name == "runtime._panic":
|
||||
return &sideEffectResult{severity: sideEffectLimited}, nil
|
||||
case "runtime.interfaceImplements":
|
||||
case name == "runtime.interfaceImplements":
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case "runtime.sliceCopy":
|
||||
case name == "runtime.sliceCopy":
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case "runtime.trackPointer":
|
||||
case name == "runtime.trackPointer":
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case "llvm.dbg.value":
|
||||
case name == "llvm.dbg.value":
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case strings.HasPrefix(name, "llvm.lifetime."):
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
}
|
||||
if fn.IsDeclaration() {
|
||||
|
||||
Vendored
+76
@@ -0,0 +1,76 @@
|
||||
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||
target triple = "armv6m-none-eabi"
|
||||
|
||||
%runtime._string = type { i8*, i32 }
|
||||
%runtime.hashmap = type { %runtime.hashmap*, i8*, i32, i8, i8, i8 }
|
||||
|
||||
@main.m = global %runtime.hashmap* null
|
||||
@main.binaryMap = global %runtime.hashmap* null
|
||||
@main.stringMap = global %runtime.hashmap* null
|
||||
@main.init.string = internal unnamed_addr constant [7 x i8] c"CONNECT"
|
||||
|
||||
declare %runtime.hashmap* @runtime.hashmapMake(i8, i8, i32, i8* %context, i8* %parentHandle)
|
||||
declare void @runtime.hashmapBinarySet(%runtime.hashmap*, i8*, i8*, i8* %context, i8* %parentHandle)
|
||||
declare void @runtime.hashmapStringSet(%runtime.hashmap*, i8*, i32, i8*, i8* %context, i8* %parentHandle)
|
||||
declare void @llvm.lifetime.end.p0i8(i64, i8*)
|
||||
declare void @llvm.lifetime.start.p0i8(i64, i8*)
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @main.init(i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
; Test that hashmap optimizations generally work (even with lifetimes).
|
||||
%hashmap.key = alloca i8
|
||||
%hashmap.value = alloca %runtime._string
|
||||
%0 = call %runtime.hashmap* @runtime.hashmapMake(i8 1, i8 8, i32 1, i8* undef, i8* null)
|
||||
%hashmap.value.bitcast = bitcast %runtime._string* %hashmap.value to i8*
|
||||
call void @llvm.lifetime.start.p0i8(i64 8, i8* %hashmap.value.bitcast)
|
||||
store %runtime._string { i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7 }, %runtime._string* %hashmap.value
|
||||
call void @llvm.lifetime.start.p0i8(i64 1, i8* %hashmap.key)
|
||||
store i8 1, i8* %hashmap.key
|
||||
call void @runtime.hashmapBinarySet(%runtime.hashmap* %0, i8* %hashmap.key, i8* %hashmap.value.bitcast, i8* undef, i8* null)
|
||||
call void @llvm.lifetime.end.p0i8(i64 1, i8* %hashmap.key)
|
||||
call void @llvm.lifetime.end.p0i8(i64 8, i8* %hashmap.value.bitcast)
|
||||
store %runtime.hashmap* %0, %runtime.hashmap** @main.m
|
||||
|
||||
; Other tests, that can be done in a separate function.
|
||||
call void @main.testNonConstantBinarySet()
|
||||
call void @main.testNonConstantStringSet()
|
||||
ret void
|
||||
}
|
||||
|
||||
; Test that a map loaded from a global can still be used for mapassign
|
||||
; operations (with binary keys).
|
||||
define internal void @main.testNonConstantBinarySet() {
|
||||
%hashmap.key = alloca i8
|
||||
%hashmap.value = alloca i8
|
||||
; Create hashmap from global. This breaks the normal hashmapBinarySet
|
||||
; optimization, to test the fallback.
|
||||
%map.new = call %runtime.hashmap* @runtime.hashmapMake(i8 1, i8 1, i32 1, i8* undef, i8* null)
|
||||
store %runtime.hashmap* %map.new, %runtime.hashmap** @main.binaryMap
|
||||
%map = load %runtime.hashmap*, %runtime.hashmap** @main.binaryMap
|
||||
; Do the binary set to the newly loaded map.
|
||||
store i8 1, i8* %hashmap.key
|
||||
store i8 2, i8* %hashmap.value
|
||||
call void @runtime.hashmapBinarySet(%runtime.hashmap* %map, i8* %hashmap.key, i8* %hashmap.value, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Test that a map loaded from a global can still be used for mapassign
|
||||
; operations (with string keys).
|
||||
define internal void @main.testNonConstantStringSet() {
|
||||
%hashmap.value = alloca i8
|
||||
; Create hashmap from global. This breaks the normal hashmapStringSet
|
||||
; optimization, to test the fallback.
|
||||
%map.new = call %runtime.hashmap* @runtime.hashmapMake(i8 8, i8 1, i32 1, i8* undef, i8* null)
|
||||
store %runtime.hashmap* %map.new, %runtime.hashmap** @main.stringMap
|
||||
%map = load %runtime.hashmap*, %runtime.hashmap** @main.stringMap
|
||||
; Do the string set to the newly loaded map.
|
||||
store i8 2, i8* %hashmap.value
|
||||
call void @runtime.hashmapStringSet(%runtime.hashmap* %map, i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7, i8* %hashmap.value, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||
target triple = "armv6m-none-eabi"
|
||||
|
||||
%runtime.hashmap = type { %runtime.hashmap*, i8*, i32, i8, i8, i8 }
|
||||
%runtime._string = type { i8*, i32 }
|
||||
|
||||
@main.m = local_unnamed_addr global %runtime.hashmap* @"main$map"
|
||||
@main.binaryMap = local_unnamed_addr global %runtime.hashmap* @"main$map.4"
|
||||
@main.stringMap = local_unnamed_addr global %runtime.hashmap* @"main$map.6"
|
||||
@main.init.string = internal unnamed_addr constant [7 x i8] c"CONNECT"
|
||||
@"main$mapbucket" = internal unnamed_addr global { [8 x i8], i8*, [8 x i8], [8 x %runtime._string] } { [8 x i8] c"\04\00\00\00\00\00\00\00", i8* null, [8 x i8] c"\01\00\00\00\00\00\00\00", [8 x %runtime._string] [%runtime._string { i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7 }, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer] }
|
||||
@"main$map" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* getelementptr inbounds ({ [8 x i8], i8*, [8 x i8], [8 x %runtime._string] }, { [8 x i8], i8*, [8 x i8], [8 x %runtime._string] }* @"main$mapbucket", i32 0, i32 0, i32 0), i32 1, i8 1, i8 8, i8 0 }
|
||||
@"main$alloca.2" = internal global i8 1
|
||||
@"main$alloca.3" = internal global i8 2
|
||||
@"main$map.4" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* null, i32 0, i8 1, i8 1, i8 0 }
|
||||
@"main$alloca.5" = internal global i8 2
|
||||
@"main$map.6" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* null, i32 0, i8 8, i8 1, i8 0 }
|
||||
|
||||
declare void @runtime.hashmapBinarySet(%runtime.hashmap*, i8*, i8*, i8*, i8*) local_unnamed_addr
|
||||
|
||||
declare void @runtime.hashmapStringSet(%runtime.hashmap*, i8*, i32, i8*, i8*, i8*) local_unnamed_addr
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.hashmapBinarySet(%runtime.hashmap* @"main$map.4", i8* @"main$alloca.2", i8* @"main$alloca.3", i8* undef, i8* null)
|
||||
call void @runtime.hashmapStringSet(%runtime.hashmap* @"main$map.6", i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7, i8* @"main$alloca.5", i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
+6
-4
@@ -367,10 +367,12 @@ func (v *MapValue) PutBinary(keyPtr, valPtr *LocalValue) {
|
||||
}
|
||||
}
|
||||
|
||||
if keyPtr.Underlying.Opcode() == llvm.BitCast {
|
||||
keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)}
|
||||
} else if keyPtr.Underlying.Opcode() == llvm.GetElementPtr {
|
||||
keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)}
|
||||
if !keyPtr.Underlying.IsAConstantExpr().IsNil() {
|
||||
if keyPtr.Underlying.Opcode() == llvm.BitCast {
|
||||
keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)}
|
||||
} else if keyPtr.Underlying.Opcode() == llvm.GetElementPtr {
|
||||
keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)}
|
||||
}
|
||||
}
|
||||
key := keyPtr.Load()
|
||||
if v.KeyType.IsNil() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
@@ -144,6 +145,14 @@ func Test(pkgName string, options *compileopts.Options) error {
|
||||
|
||||
// Flash builds and flashes the built binary to the given serial port.
|
||||
func Flash(pkgName, port string, options *compileopts.Options) error {
|
||||
if port == "" {
|
||||
var err error
|
||||
port, err = getDefaultPort()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
config, err := builder.NewConfig(options)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -201,7 +210,18 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
|
||||
flashCmd = strings.Replace(flashCmd, "{port}", port, -1)
|
||||
|
||||
// Execute the command.
|
||||
cmd := exec.Command("/bin/sh", "-c", flashCmd)
|
||||
var cmd *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
command := strings.Split(flashCmd, " ")
|
||||
if len(command) < 2 {
|
||||
return errors.New("invalid flash command")
|
||||
}
|
||||
cmd = exec.Command(command[0], command[1:]...)
|
||||
default:
|
||||
cmd = exec.Command("/bin/sh", "-c", flashCmd)
|
||||
}
|
||||
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = goenv.Get("TINYGOROOT")
|
||||
@@ -255,6 +275,14 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
|
||||
// Note: this command is expected to execute just before exiting, as it
|
||||
// modifies global state.
|
||||
func FlashGDB(pkgName, port string, ocdOutput bool, options *compileopts.Options) error {
|
||||
if port == "" {
|
||||
var err error
|
||||
port, err = getDefaultPort()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
config, err := builder.NewConfig(options)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -268,12 +296,13 @@ func FlashGDB(pkgName, port string, ocdOutput bool, options *compileopts.Options
|
||||
gdbInterface, openocdInterface := config.Programmer()
|
||||
switch gdbInterface {
|
||||
case "msd", "command", "":
|
||||
if gdbInterface == "" {
|
||||
gdbInterface = "command"
|
||||
}
|
||||
if openocdInterface != "" && config.Target.OpenOCDTarget != "" {
|
||||
gdbInterface = "openocd"
|
||||
}
|
||||
if len(config.Target.Emulator) != 0 {
|
||||
// Assume QEMU as an emulator.
|
||||
gdbInterface = "qemu"
|
||||
}
|
||||
}
|
||||
|
||||
// Run the GDB server, if necessary.
|
||||
@@ -303,6 +332,26 @@ func FlashGDB(pkgName, port string, ocdOutput bool, options *compileopts.Options
|
||||
// Make sure the daemon doesn't receive Ctrl-C that is intended for
|
||||
// GDB (to break the currently executing program).
|
||||
setCommandAsDaemon(daemon)
|
||||
// Start now, and kill it on exit.
|
||||
daemon.Start()
|
||||
defer func() {
|
||||
daemon.Process.Signal(os.Interrupt)
|
||||
// Maybe we should send a .Kill() after x seconds?
|
||||
daemon.Wait()
|
||||
}()
|
||||
case "qemu":
|
||||
gdbCommands = append(gdbCommands, "target remote :1234")
|
||||
|
||||
// Run in an emulator.
|
||||
args := append(config.Target.Emulator[1:], tmppath, "-s", "-S")
|
||||
daemon := exec.Command(config.Target.Emulator[0], args...)
|
||||
daemon.Stdout = os.Stdout
|
||||
daemon.Stderr = os.Stderr
|
||||
|
||||
// Make sure the daemon doesn't receive Ctrl-C that is intended for
|
||||
// GDB (to break the currently executing program).
|
||||
setCommandAsDaemon(daemon)
|
||||
|
||||
// Start now, and kill it on exit.
|
||||
daemon.Start()
|
||||
defer func() {
|
||||
@@ -401,9 +450,18 @@ func touchSerialPortAt1200bps(port string) error {
|
||||
|
||||
func flashUF2UsingMSD(volume, tmppath string) error {
|
||||
// find standard UF2 info path
|
||||
infoPath := "/media/*/" + volume + "/INFO_UF2.TXT"
|
||||
if runtime.GOOS == "darwin" {
|
||||
var infoPath string
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
infoPath = "/media/*/" + volume + "/INFO_UF2.TXT"
|
||||
case "darwin":
|
||||
infoPath = "/Volumes/" + volume + "/INFO_UF2.TXT"
|
||||
case "windows":
|
||||
path, err := windowsFindUSBDrive(volume)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
infoPath = path + "/INFO_UF2.TXT"
|
||||
}
|
||||
|
||||
d, err := filepath.Glob(infoPath)
|
||||
@@ -419,9 +477,18 @@ func flashUF2UsingMSD(volume, tmppath string) error {
|
||||
|
||||
func flashHexUsingMSD(volume, tmppath string) error {
|
||||
// find expected volume path
|
||||
destPath := "/media/*/" + volume
|
||||
if runtime.GOOS == "darwin" {
|
||||
var destPath string
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
destPath = "/media/*/" + volume
|
||||
case "darwin":
|
||||
destPath = "/Volumes/" + volume
|
||||
case "windows":
|
||||
path, err := windowsFindUSBDrive(volume)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
destPath = path + "/"
|
||||
}
|
||||
|
||||
d, err := filepath.Glob(destPath)
|
||||
@@ -435,6 +502,29 @@ func flashHexUsingMSD(volume, tmppath string) error {
|
||||
return moveFile(tmppath, d[0]+"/flash.hex")
|
||||
}
|
||||
|
||||
func windowsFindUSBDrive(volume string) (string, error) {
|
||||
cmd := exec.Command("wmic",
|
||||
"PATH", "Win32_LogicalDisk", "WHERE", "VolumeName = '"+volume+"'",
|
||||
"get", "DeviceID,VolumeName,FileSystem,DriveType")
|
||||
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(out.String(), "\n") {
|
||||
words := strings.Fields(line)
|
||||
if len(words) >= 3 {
|
||||
if words[1] == "2" && words[2] == "FAT" {
|
||||
return words[0], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", errors.New("unable to locate a USB device to be flashed")
|
||||
}
|
||||
|
||||
// parseSize converts a human-readable size (with k/m/g suffix) into a plain
|
||||
// number.
|
||||
func parseSize(s string) (int64, error) {
|
||||
@@ -459,6 +549,54 @@ func parseSize(s string) (int64, error) {
|
||||
return n, err
|
||||
}
|
||||
|
||||
// getDefaultPort returns the default serial port depending on the operating system.
|
||||
// Currently only supports macOS and Linux.
|
||||
func getDefaultPort() (port string, err error) {
|
||||
var portPath string
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
portPath = "/dev/cu.usb*"
|
||||
case "linux":
|
||||
portPath = "/dev/ttyACM*"
|
||||
case "windows":
|
||||
cmd := exec.Command("wmic",
|
||||
"PATH", "Win32_SerialPort", "WHERE", "Caption LIKE 'USB Serial%'", "GET", "DeviceID")
|
||||
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if out.String() == "No Instance(s) Available." {
|
||||
return "", errors.New("unable to locate a USB device to be flashed")
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(out.String(), "\n") {
|
||||
words := strings.Fields(line)
|
||||
if len(words) == 1 {
|
||||
if strings.Contains(words[0], "COM") {
|
||||
return words[0], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", errors.New("unable to locate a USB device to be flashed")
|
||||
default:
|
||||
return "", errors.New("unable to search for a default USB device to be flashed on this OS")
|
||||
}
|
||||
|
||||
d, err := filepath.Glob(portPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if d == nil {
|
||||
return "", errors.New("unable to locate a USB device to be flashed")
|
||||
}
|
||||
|
||||
return d[0], nil
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
|
||||
fmt.Fprintln(os.Stderr, "version:", version)
|
||||
@@ -526,7 +664,7 @@ func main() {
|
||||
printSize := flag.String("size", "", "print sizes (none, short, full)")
|
||||
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
|
||||
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
|
||||
port := flag.String("port", "/dev/ttyACM0", "flash port")
|
||||
port := flag.String("port", "", "flash port")
|
||||
programmer := flag.String("programmer", "", "which hardware programmer to use")
|
||||
cFlags := flag.String("cflags", "", "additional cflags for compiler")
|
||||
ldFlags := flag.String("ldflags", "", "additional ldflags for linker")
|
||||
|
||||
+21
-8
@@ -12,6 +12,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
@@ -50,7 +51,7 @@ func TestCompiler(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("EmulatedCortexM3", func(t *testing.T) {
|
||||
runPlatTests("qemu", matches, t)
|
||||
runPlatTests("cortex-m-qemu", matches, t)
|
||||
})
|
||||
|
||||
if runtime.GOOS == "linux" {
|
||||
@@ -67,6 +68,8 @@ func TestCompiler(t *testing.T) {
|
||||
}
|
||||
|
||||
func runPlatTests(target string, matches []string, t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, path := range matches {
|
||||
switch {
|
||||
case target == "wasm":
|
||||
@@ -76,7 +79,7 @@ func runPlatTests(target string, matches []string, t *testing.T) {
|
||||
}
|
||||
case target == "":
|
||||
// run all tests on host
|
||||
case target == "qemu":
|
||||
case target == "cortex-m-qemu":
|
||||
// all tests are supported
|
||||
default:
|
||||
// cross-compilation of cgo is not yet supported
|
||||
@@ -86,22 +89,32 @@ func runPlatTests(target string, matches []string, t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run(filepath.Base(path), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
runTest(path, target, t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Due to some problems with LLD, we cannot run links in parallel, or in parallel with compiles.
|
||||
// Therefore, we put a lock around builds and run everything else in parallel.
|
||||
var buildLock sync.Mutex
|
||||
|
||||
// runBuild is a thread-safe wrapper around Build.
|
||||
func runBuild(src, out string, opts *compileopts.Options) error {
|
||||
buildLock.Lock()
|
||||
defer buildLock.Unlock()
|
||||
|
||||
return Build(src, out, opts)
|
||||
}
|
||||
|
||||
func runTest(path, target string, t *testing.T) {
|
||||
// Get the expected output for this test.
|
||||
txtpath := path[:len(path)-3] + ".txt"
|
||||
if path[len(path)-1] == os.PathSeparator {
|
||||
txtpath = path + "out.txt"
|
||||
}
|
||||
f, err := os.Open(txtpath)
|
||||
if err != nil {
|
||||
t.Fatal("could not open expected output file:", err)
|
||||
}
|
||||
expected, err := ioutil.ReadAll(f)
|
||||
expected, err := ioutil.ReadFile(txtpath)
|
||||
if err != nil {
|
||||
t.Fatal("could not read expected output file:", err)
|
||||
}
|
||||
@@ -130,7 +143,7 @@ func runTest(path, target string, t *testing.T) {
|
||||
WasmAbi: "js",
|
||||
}
|
||||
binary := filepath.Join(tmpdir, "test")
|
||||
err = Build("./"+path, binary, config)
|
||||
err = runBuild("./"+path, binary, config)
|
||||
if err != nil {
|
||||
if errLoader, ok := err.(loader.Errors); ok {
|
||||
for _, err := range errLoader.Errs {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Hand created file. DO NOT DELETE.
|
||||
// STM32F103XX bitfield definitions that are not auto-generated by gen-device-svd.py
|
||||
// STM32F103XX bitfield definitions that are not auto-generated by gen-device-svd.go
|
||||
|
||||
// +build stm32,stm32f103xx
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ func main() {
|
||||
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
// timer fires 10 times per second
|
||||
arm.SetupSystemTimer(machine.CPU_FREQUENCY / 10)
|
||||
arm.SetupSystemTimer(machine.CPUFrequency() / 10)
|
||||
|
||||
for {
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
package machine
|
||||
|
||||
const CPU_FREQUENCY = 16000000
|
||||
// Return the current CPU frequency in hertz.
|
||||
func CPUFrequency() uint32 {
|
||||
return 16000000
|
||||
}
|
||||
|
||||
// LED on the Arduino
|
||||
const LED Pin = 13
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build sam,atsamd21,arduino_nano33
|
||||
// +build arduino_nano33
|
||||
|
||||
// This contains the pin mappings for the Arduino Nano33 IoT board.
|
||||
//
|
||||
@@ -6,8 +6,6 @@
|
||||
//
|
||||
package machine
|
||||
|
||||
import "device/sam"
|
||||
|
||||
// used to reset into bootloader
|
||||
const RESET_MAGIC_VALUE = 0x07738135
|
||||
|
||||
@@ -54,69 +52,23 @@ const (
|
||||
USBCDC_DP_PIN Pin = PA25
|
||||
)
|
||||
|
||||
// UART1 on the Arduino Nano 33 connects to the onboard NINA-W102 WiFi chip.
|
||||
var (
|
||||
UART1 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
Bus: sam.SERCOM5_USART,
|
||||
SERCOM: 5,
|
||||
}
|
||||
)
|
||||
|
||||
// UART1 pins
|
||||
const (
|
||||
UART_TX_PIN Pin = PA22
|
||||
UART_RX_PIN Pin = PA23
|
||||
)
|
||||
|
||||
//go:export SERCOM5_IRQHandler
|
||||
func handleUART1() {
|
||||
defaultUART1Handler()
|
||||
}
|
||||
|
||||
// UART2 on the Arduino Nano 33 connects to the normal TX/RX pins.
|
||||
var (
|
||||
UART2 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
Bus: sam.SERCOM3_USART,
|
||||
SERCOM: 3,
|
||||
}
|
||||
)
|
||||
|
||||
//go:export SERCOM3_IRQHandler
|
||||
func handleUART2() {
|
||||
// should reset IRQ
|
||||
UART2.Receive(byte((UART2.Bus.DATA.Get() & 0xFF)))
|
||||
UART2.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC)
|
||||
}
|
||||
|
||||
// I2C pins
|
||||
const (
|
||||
SDA_PIN Pin = A4 // SDA: SERCOM4/PAD[1]
|
||||
SCL_PIN Pin = A5 // SCL: SERCOM4/PAD[1]
|
||||
)
|
||||
|
||||
// I2C on the Arduino Nano 33.
|
||||
var (
|
||||
I2C0 = I2C{
|
||||
Bus: sam.SERCOM4_I2CM,
|
||||
SERCOM: 4,
|
||||
}
|
||||
)
|
||||
|
||||
// SPI pins
|
||||
const (
|
||||
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.SERCOM0_SPI,
|
||||
SERCOM: 0,
|
||||
}
|
||||
SPI0_SCK_PIN Pin = D13 // SCK: SERCOM1/PAD[1]
|
||||
SPI0_MOSI_PIN Pin = D11 // MOSI: SERCOM1/PAD[0]
|
||||
SPI0_MISO_PIN Pin = D12 // MISO: SERCOM1/PAD[3]
|
||||
)
|
||||
|
||||
// NINA-W102 Pins
|
||||
@@ -132,23 +84,9 @@ const (
|
||||
NINA_RX Pin = PA23
|
||||
)
|
||||
|
||||
// SPI1 is connected to the NINA-W102 chip on the Arduino Nano 33.
|
||||
var (
|
||||
SPI1 = SPI{
|
||||
Bus: sam.SERCOM2_SPI,
|
||||
SERCOM: 2,
|
||||
}
|
||||
NINA_SPI = SPI1
|
||||
)
|
||||
|
||||
// I2S pins
|
||||
const (
|
||||
I2S_SCK_PIN Pin = PA10
|
||||
I2S_SD_PIN Pin = PA08
|
||||
I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino Nano 33.
|
||||
)
|
||||
|
||||
// I2S on the Arduino Nano 33.
|
||||
var (
|
||||
I2S0 = I2S{Bus: sam.I2S}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// +build sam,atsamd21,arduino_nano33
|
||||
|
||||
package machine
|
||||
|
||||
import "device/sam"
|
||||
|
||||
// UART1 on the Arduino Nano 33 connects to the onboard NINA-W102 WiFi chip.
|
||||
var (
|
||||
UART1 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
Bus: sam.SERCOM5_USART,
|
||||
SERCOM: 5,
|
||||
}
|
||||
)
|
||||
|
||||
//go:export SERCOM5_IRQHandler
|
||||
func handleUART1() {
|
||||
defaultUART1Handler()
|
||||
}
|
||||
|
||||
// UART2 on the Arduino Nano 33 connects to the normal TX/RX pins.
|
||||
var (
|
||||
UART2 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
Bus: sam.SERCOM3_USART,
|
||||
SERCOM: 3,
|
||||
}
|
||||
)
|
||||
|
||||
//go:export SERCOM3_IRQHandler
|
||||
func handleUART2() {
|
||||
// should reset IRQ
|
||||
UART2.Receive(byte((UART2.Bus.DATA.Get() & 0xFF)))
|
||||
UART2.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC)
|
||||
}
|
||||
|
||||
// I2C on the Arduino Nano 33.
|
||||
var (
|
||||
I2C0 = I2C{
|
||||
Bus: sam.SERCOM4_I2CM,
|
||||
SERCOM: 4,
|
||||
}
|
||||
)
|
||||
|
||||
// SPI on the Arduino Nano 33.
|
||||
var (
|
||||
SPI0 = SPI{
|
||||
Bus: sam.SERCOM1_SPI,
|
||||
SERCOM: 1,
|
||||
}
|
||||
)
|
||||
|
||||
// SPI1 is connected to the NINA-W102 chip on the Arduino Nano 33.
|
||||
var (
|
||||
SPI1 = SPI{
|
||||
Bus: sam.SERCOM2_SPI,
|
||||
SERCOM: 2,
|
||||
}
|
||||
NINA_SPI = SPI1
|
||||
)
|
||||
|
||||
// I2S on the Arduino Nano 33.
|
||||
var (
|
||||
I2S0 = I2S{Bus: sam.I2S}
|
||||
)
|
||||
@@ -0,0 +1,76 @@
|
||||
// +build sam,atsamd21 arduino_nano33 circuitplay_express
|
||||
|
||||
package machine
|
||||
|
||||
// Return the current CPU frequency in hertz.
|
||||
func CPUFrequency() uint32 {
|
||||
return 48000000
|
||||
}
|
||||
|
||||
// Hardware pins
|
||||
const (
|
||||
PA00 Pin = 0
|
||||
PA01 Pin = 1
|
||||
PA02 Pin = 2
|
||||
PA03 Pin = 3
|
||||
PA04 Pin = 4
|
||||
PA05 Pin = 5
|
||||
PA06 Pin = 6
|
||||
PA07 Pin = 7
|
||||
PA08 Pin = 8
|
||||
PA09 Pin = 9
|
||||
PA10 Pin = 10
|
||||
PA11 Pin = 11
|
||||
PA12 Pin = 12
|
||||
PA13 Pin = 13
|
||||
PA14 Pin = 14
|
||||
PA15 Pin = 15
|
||||
PA16 Pin = 16
|
||||
PA17 Pin = 17
|
||||
PA18 Pin = 18
|
||||
PA19 Pin = 19
|
||||
PA20 Pin = 20
|
||||
PA21 Pin = 21
|
||||
PA22 Pin = 22
|
||||
PA23 Pin = 23
|
||||
PA24 Pin = 24
|
||||
PA25 Pin = 25
|
||||
PA26 Pin = 26
|
||||
PA27 Pin = 27
|
||||
PA28 Pin = 28
|
||||
PA29 Pin = 29
|
||||
PA30 Pin = 30
|
||||
PA31 Pin = 31
|
||||
PB00 Pin = 32
|
||||
PB01 Pin = 33
|
||||
PB02 Pin = 34
|
||||
PB03 Pin = 35
|
||||
PB04 Pin = 36
|
||||
PB05 Pin = 37
|
||||
PB06 Pin = 38
|
||||
PB07 Pin = 39
|
||||
PB08 Pin = 40
|
||||
PB09 Pin = 41
|
||||
PB10 Pin = 42
|
||||
PB11 Pin = 43
|
||||
PB12 Pin = 44
|
||||
PB13 Pin = 45
|
||||
PB14 Pin = 46
|
||||
PB15 Pin = 47
|
||||
PB16 Pin = 48
|
||||
PB17 Pin = 49
|
||||
PB18 Pin = 50
|
||||
PB19 Pin = 51
|
||||
PB20 Pin = 52
|
||||
PB21 Pin = 53
|
||||
PB22 Pin = 54
|
||||
PB23 Pin = 55
|
||||
PB24 Pin = 56
|
||||
PB25 Pin = 57
|
||||
PB26 Pin = 58
|
||||
PB27 Pin = 59
|
||||
PB28 Pin = 60
|
||||
PB29 Pin = 61
|
||||
PB30 Pin = 62
|
||||
PB31 Pin = 63
|
||||
)
|
||||
@@ -1,9 +1,7 @@
|
||||
// +build sam,atsamd21,circuitplay_express
|
||||
// +build circuitplay_express
|
||||
|
||||
package machine
|
||||
|
||||
import "device/sam"
|
||||
|
||||
// used to reset into bootloader
|
||||
const RESET_MAGIC_VALUE = 0xf01669ef
|
||||
|
||||
@@ -68,20 +66,6 @@ const (
|
||||
UART_RX_PIN = PB09 // PORTB
|
||||
)
|
||||
|
||||
// UART1 on the Circuit Playground Express.
|
||||
var (
|
||||
UART1 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
Bus: sam.SERCOM4_USART,
|
||||
SERCOM: 4,
|
||||
}
|
||||
)
|
||||
|
||||
//go:export SERCOM1_IRQHandler
|
||||
func handleUART1() {
|
||||
defaultUART1Handler()
|
||||
}
|
||||
|
||||
// I2C pins
|
||||
const (
|
||||
SDA_PIN = PB02 // I2C0 external
|
||||
@@ -91,20 +75,6 @@ const (
|
||||
SCL1_PIN = PA01 // I2C1 internal
|
||||
)
|
||||
|
||||
// I2C on the Circuit Playground Express.
|
||||
var (
|
||||
// external device
|
||||
I2C0 = I2C{
|
||||
Bus: sam.SERCOM5_I2CM,
|
||||
SERCOM: 5,
|
||||
}
|
||||
// internal device
|
||||
I2C1 = I2C{
|
||||
Bus: sam.SERCOM1_I2CM,
|
||||
SERCOM: 1,
|
||||
}
|
||||
)
|
||||
|
||||
// SPI pins (internal flash)
|
||||
const (
|
||||
SPI0_SCK_PIN = PA21 // SCK: SERCOM3/PAD[3]
|
||||
@@ -112,22 +82,9 @@ const (
|
||||
SPI0_MISO_PIN = PA16 // MISO: SERCOM3/PAD[0]
|
||||
)
|
||||
|
||||
// SPI on the Circuit Playground Express.
|
||||
var (
|
||||
SPI0 = SPI{
|
||||
Bus: sam.SERCOM3_SPI,
|
||||
SERCOM: 3,
|
||||
}
|
||||
)
|
||||
|
||||
// I2S pins
|
||||
const (
|
||||
I2S_SCK_PIN = PA10
|
||||
I2S_SD_PIN = PA08
|
||||
I2S_WS_PIN = NoPin // no WS, instead uses SCK to sync
|
||||
)
|
||||
|
||||
// I2S on the Circuit Playground Express.
|
||||
var (
|
||||
I2S0 = I2S{Bus: sam.I2S}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// +build sam,atsamd21,circuitplay_express
|
||||
|
||||
package machine
|
||||
|
||||
import "device/sam"
|
||||
|
||||
// UART1 on the Circuit Playground Express.
|
||||
var (
|
||||
UART1 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
Bus: sam.SERCOM4_USART,
|
||||
SERCOM: 4,
|
||||
}
|
||||
)
|
||||
|
||||
//go:export SERCOM1_IRQHandler
|
||||
func handleUART1() {
|
||||
defaultUART1Handler()
|
||||
}
|
||||
|
||||
// I2C on the Circuit Playground Express.
|
||||
var (
|
||||
// external device
|
||||
I2C0 = I2C{
|
||||
Bus: sam.SERCOM5_I2CM,
|
||||
SERCOM: 5,
|
||||
}
|
||||
// internal device
|
||||
I2C1 = I2C{
|
||||
Bus: sam.SERCOM1_I2CM,
|
||||
SERCOM: 1,
|
||||
}
|
||||
)
|
||||
|
||||
// SPI on the Circuit Playground Express.
|
||||
var (
|
||||
SPI0 = SPI{
|
||||
Bus: sam.SERCOM3_SPI,
|
||||
SERCOM: 3,
|
||||
}
|
||||
)
|
||||
|
||||
// I2S on the Circuit Playground Express.
|
||||
var (
|
||||
I2S0 = I2S{Bus: sam.I2S}
|
||||
)
|
||||
@@ -0,0 +1,150 @@
|
||||
// +build sam,atsamd51,pybadge
|
||||
|
||||
package machine
|
||||
|
||||
import "device/sam"
|
||||
|
||||
// used to reset into bootloader
|
||||
const RESET_MAGIC_VALUE = 0xf01669ef
|
||||
|
||||
// GPIO Pins
|
||||
const (
|
||||
D0 = PB17 // UART0 RX/PWM available
|
||||
D1 = PB16 // UART0 TX/PWM available
|
||||
D2 = PB03
|
||||
D3 = PB02
|
||||
D4 = PA14 // PWM available
|
||||
D5 = PA16 // PWM available
|
||||
D6 = PA18 // PWM available
|
||||
D7 = PB14
|
||||
D8 = PA15 // built-in neopixel
|
||||
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 = PA22 // PWM available
|
||||
D13 = PA23 // PWM available
|
||||
)
|
||||
|
||||
// Analog pins
|
||||
const (
|
||||
A0 = PA02 // ADC/AIN[0]
|
||||
A1 = PA05 // ADC/AIN[2]
|
||||
A2 = PB08 // ADC/AIN[3]
|
||||
A3 = PB09 // ADC/AIN[4]
|
||||
A4 = PA04 // ADC/AIN[5]
|
||||
A5 = PA06 // ADC/AIN[6]
|
||||
A6 = PB01 // ADC/AIN[12]/VMEAS
|
||||
A7 = PB04 // ADC/AIN[6]/LIGHT
|
||||
A8 = D2 // ADC/AIN[14]
|
||||
A9 = D3 // ADC/AIN[15]
|
||||
)
|
||||
|
||||
const (
|
||||
LED = D13
|
||||
NEOPIXELS = D8
|
||||
|
||||
LIGHTSENSOR = A7
|
||||
|
||||
BUTTON_LATCH = PB00
|
||||
BUTTON_OUT = PB30
|
||||
BUTTON_CLK = PB31
|
||||
|
||||
TFT_DC = PB05
|
||||
TFT_CS = PB07
|
||||
TFT_RST = PA00
|
||||
TFT_LITE = PA01
|
||||
|
||||
SPEAKER_ENABLE = PA27
|
||||
|
||||
QSPI_SCK = PB10
|
||||
QSPI_CS = PB11
|
||||
QSPI_DATA_1 = PA08
|
||||
QSPI_DATA_2 = PA09
|
||||
QSPI_DATA_3 = PA10
|
||||
QSPI_DATA_4 = PA11
|
||||
)
|
||||
|
||||
const (
|
||||
BUTTON_LEFT_MASK = 1
|
||||
BUTTON_UP_MASK = 2
|
||||
BUTTON_DOWN_MASK = 4
|
||||
BUTTON_RIGHT_MASK = 8
|
||||
BUTTON_SELECT_MASK = 16
|
||||
BUTTON_START_MASK = 32
|
||||
BUTTON_A_MASK = 64
|
||||
BUTTON_B_MASK = 128
|
||||
)
|
||||
|
||||
// UART0 aka USBCDC pins
|
||||
const (
|
||||
USBCDC_DM_PIN = PA24
|
||||
USBCDC_DP_PIN = PA25
|
||||
)
|
||||
|
||||
// UART1 pins
|
||||
const (
|
||||
UART_TX_PIN = D1
|
||||
UART_RX_PIN = D0
|
||||
)
|
||||
|
||||
// UART1 var is on SERCOM3, defined in atsamd51.go
|
||||
|
||||
// UART2 pins
|
||||
const (
|
||||
UART2_TX_PIN = A4
|
||||
UART2_RX_PIN = A5
|
||||
)
|
||||
|
||||
// UART2 var is on SERCOM0, defined in atsamd51.go
|
||||
|
||||
// I2C pins
|
||||
const (
|
||||
SDA_PIN = PA12 // SDA: SERCOM2/PAD[0]
|
||||
SCL_PIN = PA13 // SCL: SERCOM2/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 = PA17 // SCK: SERCOM1/PAD[1]
|
||||
SPI0_MOSI_PIN = PB23 // MOSI: SERCOM1/PAD[3]
|
||||
SPI0_MISO_PIN = PB22 // MISO: SERCOM1/PAD[2]
|
||||
)
|
||||
|
||||
// SPI on the PyBadge.
|
||||
var (
|
||||
SPI0 = SPI{Bus: sam.SERCOM1_SPIM,
|
||||
SCK: SPI0_SCK_PIN,
|
||||
MOSI: SPI0_MOSI_PIN,
|
||||
MISO: SPI0_MISO_PIN,
|
||||
DOpad: spiTXPad3SCK1,
|
||||
DIpad: sercomRXPad2,
|
||||
MISOPinMode: PinSERCOM,
|
||||
MOSIPinMode: PinSERCOM,
|
||||
SCKPinMode: PinSERCOM,
|
||||
}
|
||||
)
|
||||
|
||||
// TFT SPI pins
|
||||
const (
|
||||
SPI1_SCK_PIN = PB13 // SCK: SERCOM4/PAD[1]
|
||||
SPI1_MOSI_PIN = PB15 // MOSI: SERCOM4/PAD[3]
|
||||
)
|
||||
|
||||
// TFT SPI on the PyBadge.
|
||||
var (
|
||||
SPI1 = SPI{Bus: sam.SERCOM4_SPIM,
|
||||
SCK: SPI1_SCK_PIN,
|
||||
MOSI: SPI1_MOSI_PIN,
|
||||
DOpad: spiTXPad3SCK1,
|
||||
MOSIPinMode: PinSERCOM,
|
||||
SCKPinMode: PinSERCOM,
|
||||
}
|
||||
)
|
||||
@@ -140,7 +140,7 @@ func (i2c I2C) Configure(config I2CConfig) {
|
||||
// SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
|
||||
// NOTE: TWBR should be 10 or higher for master mode.
|
||||
// It is 72 for a 16mhz board with 100kHz TWI
|
||||
avr.TWBR.Set(uint8(((CPU_FREQUENCY / config.Frequency) - 16) / 2))
|
||||
avr.TWBR.Set(uint8(((CPUFrequency() / config.Frequency) - 16) / 2))
|
||||
|
||||
// Enable twi module.
|
||||
avr.TWCR.Set(avr.TWCR_TWEN)
|
||||
@@ -235,7 +235,7 @@ func (uart UART) Configure(config UARTConfig) {
|
||||
// Set baud rate based on prescale formula from
|
||||
// https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html
|
||||
// ((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1)
|
||||
ps := ((CPU_FREQUENCY+config.BaudRate*8)/(config.BaudRate*16) - 1)
|
||||
ps := ((CPUFrequency()+config.BaudRate*8)/(config.BaudRate*16) - 1)
|
||||
avr.UBRR0H.Set(uint8(ps >> 8))
|
||||
avr.UBRR0L.Set(uint8(ps & 0xff))
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const CPU_FREQUENCY = 48000000
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
const (
|
||||
@@ -35,74 +33,6 @@ const (
|
||||
PinInputPulldown PinMode = 12
|
||||
)
|
||||
|
||||
// Hardware pins
|
||||
const (
|
||||
PA00 Pin = 0
|
||||
PA01 Pin = 1
|
||||
PA02 Pin = 2
|
||||
PA03 Pin = 3
|
||||
PA04 Pin = 4
|
||||
PA05 Pin = 5
|
||||
PA06 Pin = 6
|
||||
PA07 Pin = 7
|
||||
PA08 Pin = 8
|
||||
PA09 Pin = 9
|
||||
PA10 Pin = 10
|
||||
PA11 Pin = 11
|
||||
PA12 Pin = 12
|
||||
PA13 Pin = 13
|
||||
PA14 Pin = 14
|
||||
PA15 Pin = 15
|
||||
PA16 Pin = 16
|
||||
PA17 Pin = 17
|
||||
PA18 Pin = 18
|
||||
PA19 Pin = 19
|
||||
PA20 Pin = 20
|
||||
PA21 Pin = 21
|
||||
PA22 Pin = 22
|
||||
PA23 Pin = 23
|
||||
PA24 Pin = 24
|
||||
PA25 Pin = 25
|
||||
PA26 Pin = 26
|
||||
PA27 Pin = 27
|
||||
PA28 Pin = 28
|
||||
PA29 Pin = 29
|
||||
PA30 Pin = 30
|
||||
PA31 Pin = 31
|
||||
PB00 Pin = 32
|
||||
PB01 Pin = 33
|
||||
PB02 Pin = 34
|
||||
PB03 Pin = 35
|
||||
PB04 Pin = 36
|
||||
PB05 Pin = 37
|
||||
PB06 Pin = 38
|
||||
PB07 Pin = 39
|
||||
PB08 Pin = 40
|
||||
PB09 Pin = 41
|
||||
PB10 Pin = 42
|
||||
PB11 Pin = 43
|
||||
PB12 Pin = 44
|
||||
PB13 Pin = 45
|
||||
PB14 Pin = 46
|
||||
PB15 Pin = 47
|
||||
PB16 Pin = 48
|
||||
PB17 Pin = 49
|
||||
PB18 Pin = 50
|
||||
PB19 Pin = 51
|
||||
PB20 Pin = 52
|
||||
PB21 Pin = 53
|
||||
PB22 Pin = 54
|
||||
PB23 Pin = 55
|
||||
PB24 Pin = 56
|
||||
PB25 Pin = 57
|
||||
PB26 Pin = 58
|
||||
PB27 Pin = 59
|
||||
PB28 Pin = 60
|
||||
PB29 Pin = 61
|
||||
PB30 Pin = 62
|
||||
PB31 Pin = 63
|
||||
)
|
||||
|
||||
const (
|
||||
pinPadMapSERCOM0Pad0 byte = (0x10 << 1) | 0x00
|
||||
pinPadMapSERCOM1Pad0 byte = (0x20 << 1) | 0x00
|
||||
@@ -485,7 +415,7 @@ func (uart UART) SetBaudRate(br uint32) {
|
||||
// BAUD = fref / (sampleRateValue * fbaud)
|
||||
// (multiply by 8, to calculate fractional piece)
|
||||
// uint32_t baudTimes8 = (SystemCoreClock * 8) / (16 * baudrate);
|
||||
baud := (CPU_FREQUENCY * 8) / (sampleRate16X * br)
|
||||
baud := (CPUFrequency() * 8) / (sampleRate16X * br)
|
||||
|
||||
// sercom->USART.BAUD.FRAC.FP = (baudTimes8 % 8);
|
||||
// sercom->USART.BAUD.FRAC.BAUD = (baudTimes8 / 8);
|
||||
@@ -601,7 +531,7 @@ func (i2c I2C) Configure(config I2CConfig) error {
|
||||
func (i2c I2C) SetBaudRate(br uint32) {
|
||||
// Synchronous arithmetic baudrate, via Arduino SAMD implementation:
|
||||
// SystemCoreClock / ( 2 * baudrate) - 5 - (((SystemCoreClock / 1000000) * WIRE_RISE_TIME_NANOSECONDS) / (2 * 1000));
|
||||
baud := CPU_FREQUENCY/(2*br) - 5 - (((CPU_FREQUENCY / 1000000) * riseTimeNanoseconds) / (2 * 1000))
|
||||
baud := CPUFrequency()/(2*br) - 5 - (((CPUFrequency() / 1000000) * riseTimeNanoseconds) / (2 * 1000))
|
||||
i2c.Bus.BAUD.Set(baud)
|
||||
}
|
||||
|
||||
@@ -793,7 +723,7 @@ func (i2s I2S) Configure(config I2SConfig) {
|
||||
sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_I2S_)
|
||||
|
||||
// setting clock rate for sample.
|
||||
division_factor := CPU_FREQUENCY / (config.AudioFrequency * uint32(config.DataFormat))
|
||||
division_factor := CPUFrequency() / (config.AudioFrequency * uint32(config.DataFormat))
|
||||
|
||||
// Switch Generic Clock Generator 3 to DFLL48M.
|
||||
sam.GCLK.GENDIV.Set((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENDIV_ID_Pos) |
|
||||
@@ -1113,7 +1043,7 @@ func (spi SPI) Configure(config SPIConfig) error {
|
||||
}
|
||||
|
||||
// Set synch speed for SPI
|
||||
baudRate := (CPU_FREQUENCY / (2 * config.Frequency)) - 1
|
||||
baudRate := (CPUFrequency() / (2 * config.Frequency)) - 1
|
||||
spi.Bus.BAUD.Set(uint8(baudRate))
|
||||
|
||||
// Enable SPI port.
|
||||
|
||||
@@ -14,7 +14,9 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const CPU_FREQUENCY = 120000000
|
||||
func CPUFrequency() uint32 {
|
||||
return 120000000
|
||||
}
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
"device/sifive"
|
||||
)
|
||||
|
||||
const CPU_FREQUENCY = 16000000
|
||||
func CPUFrequency() uint32 {
|
||||
return 16000000
|
||||
}
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
@@ -108,7 +110,7 @@ func (spi SPI) Configure(config SPIConfig) error {
|
||||
}
|
||||
|
||||
// div = (SPI_CFG(dev)->f_sys / (2 * frequency)) - 1;
|
||||
div := CPU_FREQUENCY/(2*config.Frequency) - 1
|
||||
div := CPUFrequency()/(2*config.Frequency) - 1
|
||||
spi.Bus.DIV.Set(div)
|
||||
|
||||
// set mode
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
"device/nrf"
|
||||
)
|
||||
|
||||
const CPU_FREQUENCY = 16000000
|
||||
func CPUFrequency() uint32 {
|
||||
return 16000000
|
||||
}
|
||||
|
||||
// Get peripheral and pin number for this GPIO pin.
|
||||
func (p Pin) getPortPin() (*nrf.GPIO_Type, uint32) {
|
||||
|
||||
@@ -7,7 +7,9 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const CPU_FREQUENCY = 64000000
|
||||
func CPUFrequency() uint32 {
|
||||
return 64000000
|
||||
}
|
||||
|
||||
// Get peripheral and pin number for this GPIO pin.
|
||||
func (p Pin) getPortPin() (*nrf.GPIO_Type, uint32) {
|
||||
|
||||
@@ -7,7 +7,9 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const CPU_FREQUENCY = 64000000
|
||||
func CPUFrequency() uint32 {
|
||||
return 64000000
|
||||
}
|
||||
|
||||
// Get peripheral and pin number for this GPIO pin.
|
||||
func (p Pin) getPortPin() (*nrf.GPIO_Type, uint32) {
|
||||
|
||||
@@ -10,7 +10,9 @@ import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
const CPU_FREQUENCY = 72000000
|
||||
func CPUFrequency() uint32 {
|
||||
return 72000000
|
||||
}
|
||||
|
||||
const (
|
||||
PinInput PinMode = 0 // Input mode
|
||||
@@ -163,10 +165,10 @@ func (uart UART) SetBaudRate(br uint32) {
|
||||
var divider uint32
|
||||
if uart.Bus == stm32.USART1 {
|
||||
// first divide by PCLK2 prescaler (div 1) and then desired baudrate
|
||||
divider = CPU_FREQUENCY / br
|
||||
divider = CPUFrequency() / br
|
||||
} else {
|
||||
// first divide by PCLK1 prescaler (div 2) and then desired baudrate
|
||||
divider = CPU_FREQUENCY / 2 / br
|
||||
divider = CPUFrequency() / 2 / br
|
||||
}
|
||||
uart.Bus.BRR.Set(divider)
|
||||
}
|
||||
@@ -360,7 +362,7 @@ func (i2c I2C) Configure(config I2CConfig) {
|
||||
i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE)
|
||||
|
||||
// pclk1 clock speed is main frequency divided by PCLK1 prescaler (div 2)
|
||||
pclk1 := uint32(CPU_FREQUENCY / 2)
|
||||
pclk1 := CPUFrequency() / 2
|
||||
|
||||
// set freqency range to PCLK1 clock speed in MHz
|
||||
// aka setting the value 36 means to use 36 MHz clock
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
"device/stm32"
|
||||
)
|
||||
|
||||
const CPU_FREQUENCY = 168000000
|
||||
func CPUFrequency() uint32 {
|
||||
return 168000000
|
||||
}
|
||||
|
||||
const (
|
||||
// Mode Flag
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build sam stm32,!stm32f407 fe310
|
||||
// +build !baremetal sam stm32,!stm32f407 fe310
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build qemu
|
||||
// +build cortexm,qemu
|
||||
|
||||
package runtime
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"machine"
|
||||
"unsafe"
|
||||
|
||||
"device/riscv"
|
||||
"device/sifive"
|
||||
)
|
||||
|
||||
@@ -106,10 +105,3 @@ func sleepTicks(d timeUnit) {
|
||||
for ticks() < target {
|
||||
}
|
||||
}
|
||||
|
||||
func abort() {
|
||||
// lock up forever
|
||||
for {
|
||||
riscv.Asm("wfi")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// +build fe310,!qemu
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"device/riscv"
|
||||
)
|
||||
|
||||
func abort() {
|
||||
// lock up forever
|
||||
for {
|
||||
riscv.Asm("wfi")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// +build fe310,qemu
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Special memory-mapped device to exit tests, created by SiFive.
|
||||
var testExit = (*volatile.Register32)(unsafe.Pointer(uintptr(0x100000)))
|
||||
|
||||
func abort() {
|
||||
// Signal a successful exit.
|
||||
testExit.Set(0x5555)
|
||||
}
|
||||
@@ -163,7 +163,7 @@ func timerSleep(ticks uint32) {
|
||||
// The current scaling only supports a range of 200 usec to 6553 msec.
|
||||
|
||||
// prescale counter down from 72mhz to 10khz aka 0.1 ms frequency.
|
||||
stm32.TIM3.PSC.Set(machine.CPU_FREQUENCY/10000 - 1) // 7199
|
||||
stm32.TIM3.PSC.Set(machine.CPUFrequency()/10000 - 1) // 7199
|
||||
|
||||
// Set duty aka duration.
|
||||
// STM32 dividers use n-1, i.e. n counts from 0 to n-1.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
],
|
||||
"linkerscript": "targets/lm3s6965.ld",
|
||||
"extra-files": [
|
||||
"targets/qemu.s"
|
||||
"targets/cortex-m-qemu.s"
|
||||
],
|
||||
"emulator": ["qemu-system-arm", "-machine", "lm3s6965evb", "-semihosting", "-nographic", "-kernel"]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Generic Cortex-M interrupt vector.
|
||||
// This vector is used by the QEMU target.
|
||||
// This vector is used by the Cortex-M QEMU target.
|
||||
|
||||
.syntax unified
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"inherits": ["fe310"],
|
||||
"build-tags": ["hifive1b", "qemu"],
|
||||
"linkerscript": "targets/hifive1-qemu.ld",
|
||||
"emulator": ["qemu-system-riscv32", "-machine", "sifive_e", "-nographic", "-kernel"]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
/* memory map:
|
||||
* https://github.com/sifive/freedom-e-sdk/blob/v201908-branch/bsp/sifive-hifive1/metal.default.lds
|
||||
*/
|
||||
MEMORY
|
||||
{
|
||||
FLASH_TEXT (rw) : ORIGIN = 0x20400000, LENGTH = 0x1fc00000
|
||||
RAM (xrw) : ORIGIN = 0x80000000, LENGTH = 0x4000
|
||||
}
|
||||
|
||||
_stack_size = 2K;
|
||||
|
||||
INCLUDE "targets/riscv.ld"
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"inherits": ["atsamd51j19a"],
|
||||
"build-tags": ["sam", "atsamd51j19a", "pybadge"],
|
||||
"flash-1200-bps-reset": "true",
|
||||
"flash-method": "msd",
|
||||
"msd-volume-name": "PYBADGEBOOT",
|
||||
"msd-firmware-name": "arcade.uf2"
|
||||
}
|
||||
+6
-3
@@ -4,9 +4,11 @@
|
||||
"goarch": "arm",
|
||||
"build-tags": ["tinygo.riscv", "baremetal", "linux", "arm"],
|
||||
"gc": "conservative",
|
||||
"compiler": "riscv64-unknown-elf-gcc",
|
||||
"linker": "riscv64-unknown-elf-ld",
|
||||
"compiler": "clang",
|
||||
"linker": "ld.lld",
|
||||
"rtlib": "compiler-rt",
|
||||
"cflags": [
|
||||
"--target=riscv32--none",
|
||||
"-march=rv32imac",
|
||||
"-mabi=ilp32",
|
||||
"-Os",
|
||||
@@ -21,5 +23,6 @@
|
||||
],
|
||||
"extra-files": [
|
||||
"src/device/riscv/start.S"
|
||||
]
|
||||
],
|
||||
"gdb": "riscv64-unknown-elf-gdb"
|
||||
}
|
||||
|
||||
+9
-4
@@ -30,8 +30,8 @@ SECTIONS
|
||||
/* see https://gnu-mcu-eclipse.github.io/arch/riscv/programmer/#the-gp-global-pointer-register */
|
||||
PROVIDE( __global_pointer$ = . + (4K / 2) );
|
||||
_sdata = .; /* used by startup code */
|
||||
*(.data)
|
||||
*(.data*)
|
||||
*(.sdata)
|
||||
*(.data .data.*)
|
||||
. = ALIGN(4);
|
||||
_edata = .; /* used by startup code */
|
||||
} >RAM AT>FLASH_TEXT
|
||||
@@ -41,12 +41,17 @@ SECTIONS
|
||||
{
|
||||
. = ALIGN(4);
|
||||
_sbss = .; /* used by startup code */
|
||||
*(.bss)
|
||||
*(.bss*)
|
||||
*(.sbss)
|
||||
*(.bss .bss.*)
|
||||
*(COMMON)
|
||||
. = ALIGN(4);
|
||||
_ebss = .; /* used by startup code */
|
||||
} >RAM
|
||||
|
||||
/DISCARD/ :
|
||||
{
|
||||
*(.eh_frame) /* causes 'no memory region specified' error in lld */
|
||||
}
|
||||
}
|
||||
|
||||
/* For the memory allocator. */
|
||||
|
||||
@@ -1,291 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
import os
|
||||
from xml.dom import minidom
|
||||
from glob import glob
|
||||
from collections import OrderedDict
|
||||
import re
|
||||
|
||||
class Device:
|
||||
# dummy
|
||||
pass
|
||||
|
||||
def getText(element):
|
||||
strings = []
|
||||
for node in element.childNodes:
|
||||
if node.nodeType == node.TEXT_NODE:
|
||||
strings.append(node.data)
|
||||
return ''.join(strings)
|
||||
|
||||
def formatText(text):
|
||||
text = re.sub('[ \t\n]+', ' ', text) # Collapse whitespace (like in HTML)
|
||||
text = text.replace('\\n ', '\n')
|
||||
text = text.strip()
|
||||
return text
|
||||
|
||||
def readATDF(path):
|
||||
# Read Atmel device descriptor files.
|
||||
# See: http://packs.download.atmel.com
|
||||
|
||||
device = Device()
|
||||
|
||||
xml = minidom.parse(path)
|
||||
device = xml.getElementsByTagName('device')[0]
|
||||
deviceName = device.getAttribute('name')
|
||||
arch = device.getAttribute('architecture')
|
||||
family = device.getAttribute('family')
|
||||
|
||||
memorySizes = {}
|
||||
for el in device.getElementsByTagName('address-space'):
|
||||
addressSpace = {
|
||||
'size': int(el.getAttribute('size'), 0),
|
||||
'segments': {},
|
||||
}
|
||||
memorySizes[el.getAttribute('name')] = addressSpace
|
||||
for segmentEl in el.getElementsByTagName('memory-segment'):
|
||||
addressSpace['segments'][segmentEl.getAttribute('name')] = int(segmentEl.getAttribute('size'), 0)
|
||||
|
||||
device.interrupts = []
|
||||
for el in device.getElementsByTagName('interrupts')[0].getElementsByTagName('interrupt'):
|
||||
device.interrupts.append({
|
||||
'index': int(el.getAttribute('index')),
|
||||
'name': el.getAttribute('name'),
|
||||
'description': el.getAttribute('caption'),
|
||||
})
|
||||
|
||||
allRegisters = {}
|
||||
commonRegisters = {}
|
||||
|
||||
device.peripherals = []
|
||||
for el in xml.getElementsByTagName('modules')[0].getElementsByTagName('module'):
|
||||
peripheral = {
|
||||
'name': el.getAttribute('name'),
|
||||
'description': el.getAttribute('caption'),
|
||||
'registers': [],
|
||||
}
|
||||
device.peripherals.append(peripheral)
|
||||
for regElGroup in el.getElementsByTagName('register-group'):
|
||||
for regEl in regElGroup.getElementsByTagName('register'):
|
||||
size = int(regEl.getAttribute('size'))
|
||||
regName = regEl.getAttribute('name')
|
||||
regOffset = int(regEl.getAttribute('offset'), 0)
|
||||
reg = {
|
||||
'description': regEl.getAttribute('caption'),
|
||||
'bitfields': [],
|
||||
'array': None,
|
||||
}
|
||||
if size == 1:
|
||||
reg['variants'] = [{
|
||||
'name': regName,
|
||||
'address': regOffset,
|
||||
}]
|
||||
elif size == 2:
|
||||
reg['variants'] = [{
|
||||
'name': regName + 'L',
|
||||
'address': regOffset,
|
||||
}, {
|
||||
'name': regName + 'H',
|
||||
'address': regOffset + 1,
|
||||
}]
|
||||
else:
|
||||
# TODO
|
||||
continue
|
||||
|
||||
for bitfieldEl in regEl.getElementsByTagName('bitfield'):
|
||||
mask = bitfieldEl.getAttribute('mask')
|
||||
if len(mask) == 2:
|
||||
# Two devices (ATtiny102 and ATtiny104) appear to have
|
||||
# an error in the bitfields, leaving out the '0x'
|
||||
# prefix.
|
||||
mask = '0x' + mask
|
||||
reg['bitfields'].append({
|
||||
'name': regName + '_' + bitfieldEl.getAttribute('name'),
|
||||
'description': bitfieldEl.getAttribute('caption'),
|
||||
'value': int(mask, 0),
|
||||
})
|
||||
|
||||
if regName in allRegisters:
|
||||
firstReg = allRegisters[regName]
|
||||
if firstReg['register'] in firstReg['peripheral']['registers']:
|
||||
firstReg['peripheral']['registers'].remove(firstReg['register'])
|
||||
if firstReg['address'] != regOffset:
|
||||
continue # TODO
|
||||
commonRegisters = allRegisters[regName]['register']
|
||||
continue
|
||||
else:
|
||||
allRegisters[regName] = {'address': regOffset, 'register': reg, 'peripheral': peripheral}
|
||||
|
||||
peripheral['registers'].append(reg)
|
||||
|
||||
ramSize = 0 # for devices with no RAM
|
||||
for ramSegmentName in ['IRAM', 'INTERNAL_SRAM', 'SRAM']:
|
||||
if ramSegmentName in memorySizes['data']['segments']:
|
||||
ramSize = memorySizes['data']['segments'][ramSegmentName]
|
||||
|
||||
device.metadata = {
|
||||
'file': os.path.basename(path),
|
||||
'descriptorSource': 'http://packs.download.atmel.com/',
|
||||
'name': deviceName,
|
||||
'nameLower': deviceName.lower(),
|
||||
'description': 'Device information for the {}.'.format(deviceName),
|
||||
'arch': arch,
|
||||
'family': family,
|
||||
'flashSize': memorySizes['prog']['size'],
|
||||
'ramSize': ramSize,
|
||||
'numInterrupts': len(device.interrupts),
|
||||
}
|
||||
|
||||
return device
|
||||
|
||||
def writeGo(outdir, device):
|
||||
# The Go module for this device.
|
||||
out = open(outdir + '/' + device.metadata['nameLower'] + '.go', 'w')
|
||||
pkgName = os.path.basename(outdir.rstrip('/'))
|
||||
out.write('''\
|
||||
// Automatically generated file. DO NOT EDIT.
|
||||
// Generated by gen-device-avr.py from {file}, see {descriptorSource}
|
||||
|
||||
// +build {pkgName},{nameLower}
|
||||
|
||||
// {description}
|
||||
package {pkgName}
|
||||
|
||||
import (
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Some information about this device.
|
||||
const (
|
||||
DEVICE = "{name}"
|
||||
ARCH = "{arch}"
|
||||
FAMILY = "{family}"
|
||||
)
|
||||
'''.format(pkgName=pkgName, **device.metadata))
|
||||
|
||||
out.write('\n// Interrupts\nconst (\n')
|
||||
for intr in device.interrupts:
|
||||
out.write('\tIRQ_{name} = {index} // {description}\n'.format(**intr))
|
||||
intrMax = max(map(lambda intr: intr['index'], device.interrupts))
|
||||
out.write('\tIRQ_max = {} // Highest interrupt number on this device.\n'.format(intrMax))
|
||||
out.write(')\n')
|
||||
|
||||
out.write('\n// Peripherals.\nvar (')
|
||||
first = True
|
||||
for peripheral in device.peripherals:
|
||||
out.write('\n\t// {description}\n'.format(**peripheral))
|
||||
for register in peripheral['registers']:
|
||||
for variant in register['variants']:
|
||||
out.write('\t{name} = (*volatile.Register8)(unsafe.Pointer(uintptr(0x{address:x})))\n'.format(**variant))
|
||||
out.write(')\n')
|
||||
|
||||
for peripheral in device.peripherals:
|
||||
if not sum(map(lambda r: len(r['bitfields']), peripheral['registers'])): continue
|
||||
out.write('\n// Bitfields for {name}: {description}\nconst('.format(**peripheral))
|
||||
for register in peripheral['registers']:
|
||||
if not register['bitfields']: continue
|
||||
for variant in register['variants']:
|
||||
out.write('\n\t// {name}'.format(**variant))
|
||||
if register['description']:
|
||||
out.write(': {description}'.format(**register))
|
||||
out.write('\n')
|
||||
for bitfield in register['bitfields']:
|
||||
name = bitfield['name']
|
||||
value = bitfield['value']
|
||||
if '{:08b}'.format(value).count('1') == 1:
|
||||
out.write('\t{name} = 0x{value:x}'.format(**bitfield))
|
||||
if bitfield['description']:
|
||||
out.write(' // {description}'.format(**bitfield))
|
||||
out.write('\n')
|
||||
else:
|
||||
n = 0
|
||||
for i in range(8):
|
||||
if (value >> i) & 1 == 0: continue
|
||||
out.write('\t{}{} = 0x{:x}'.format(name, n, 1 << i))
|
||||
if bitfield['description']:
|
||||
out.write(' // {description}'.format(**bitfield))
|
||||
n += 1
|
||||
out.write('\n')
|
||||
out.write(')\n')
|
||||
|
||||
def writeAsm(outdir, device):
|
||||
# The interrupt vector, which is hard to write directly in Go.
|
||||
out = open(outdir + '/' + device.metadata['nameLower'] + '.s', 'w')
|
||||
out.write('''\
|
||||
; Automatically generated file. DO NOT EDIT.
|
||||
; Generated by gen-device-avr.py from {file}, see {descriptorSource}
|
||||
|
||||
; This is the default handler for interrupts, if triggered but not defined.
|
||||
; Sleep inside so that an accidentally triggered interrupt won't drain the
|
||||
; battery of a battery-powered device.
|
||||
.section .text.__vector_default
|
||||
.global __vector_default
|
||||
__vector_default:
|
||||
sleep
|
||||
rjmp __vector_default
|
||||
|
||||
; Avoid the need for repeated .weak and .set instructions.
|
||||
.macro IRQ handler
|
||||
.weak \\handler
|
||||
.set \\handler, __vector_default
|
||||
.endm
|
||||
|
||||
; The interrupt vector of this device. Must be placed at address 0 by the linker.
|
||||
.section .vectors
|
||||
.global __vectors
|
||||
'''.format(**device.metadata))
|
||||
num = 0
|
||||
for intr in device.interrupts:
|
||||
jmp = 'jmp'
|
||||
if device.metadata['flashSize'] <= 8 * 1024:
|
||||
# When a device has 8kB or less flash, rjmp (2 bytes) must be used
|
||||
# instead of jmp (4 bytes).
|
||||
# https://www.avrfreaks.net/forum/rjmp-versus-jmp
|
||||
jmp = 'rjmp'
|
||||
if intr['index'] < num:
|
||||
# Some devices have duplicate interrupts, probably for historical
|
||||
# reasons.
|
||||
continue
|
||||
while intr['index'] > num:
|
||||
out.write(' {jmp} __vector_default\n'.format(jmp=jmp))
|
||||
num += 1
|
||||
num += 1
|
||||
out.write(' {jmp} __vector_{name}\n'.format(jmp=jmp, **intr))
|
||||
|
||||
out.write('''
|
||||
; Define default implementations for interrupts, redirecting to
|
||||
; __vector_default when not implemented.
|
||||
''')
|
||||
for intr in device.interrupts:
|
||||
out.write(' IRQ __vector_{name}\n'.format(**intr))
|
||||
|
||||
def writeLD(outdir, device):
|
||||
# Variables for the linker script.
|
||||
out = open(outdir + '/' + device.metadata['nameLower'] + '.ld', 'w')
|
||||
out.write('''\
|
||||
/* Automatically generated file. DO NOT EDIT. */
|
||||
/* Generated by gen-device-avr.py from {file}, see {descriptorSource} */
|
||||
|
||||
__flash_size = 0x{flashSize:x};
|
||||
__ram_size = 0x{ramSize:x};
|
||||
__num_isrs = {numInterrupts};
|
||||
'''.format(**device.metadata))
|
||||
out.close()
|
||||
|
||||
|
||||
def generate(indir, outdir):
|
||||
for filepath in sorted(glob(indir + '/*.atdf')):
|
||||
print(filepath)
|
||||
device = readATDF(filepath)
|
||||
writeGo(outdir, device)
|
||||
writeAsm(outdir, device)
|
||||
writeLD(outdir, device)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
indir = sys.argv[1] # directory with register descriptor files (*.atdf)
|
||||
outdir = sys.argv[2] # output directory
|
||||
generate(indir, outdir)
|
||||
Executable
+502
@@ -0,0 +1,502 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"math/bits"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type AVRToolsDeviceFile struct {
|
||||
XMLName xml.Name `xml:"avr-tools-device-file"`
|
||||
Devices []struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Architecture string `xml:"architecture,attr"`
|
||||
Family string `xml:"family,attr"`
|
||||
AddressSpaces []struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Size string `xml:"size,attr"`
|
||||
MemorySegments []struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Size string `xml:"size,attr"`
|
||||
} `xml:"memory-segment"`
|
||||
} `xml:"address-spaces>address-space"`
|
||||
Interrupts []Interrupt `xml:"interrupts>interrupt"`
|
||||
} `xml:"devices>device"`
|
||||
Modules []struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Caption string `xml:"caption,attr"`
|
||||
RegisterGroup struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Caption string `xml:"caption,attr"`
|
||||
Registers []struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Caption string `xml:"caption,attr"`
|
||||
Offset string `xml:"offset,attr"`
|
||||
Size int `xml:"size,attr"`
|
||||
Bitfields []struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Caption string `xml:"caption,attr"`
|
||||
Mask string `xml:"mask,attr"`
|
||||
} `xml:"bitfield"`
|
||||
} `xml:"register"`
|
||||
} `xml:"register-group"`
|
||||
} `xml:"modules>module"`
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
metadata map[string]interface{}
|
||||
interrupts []Interrupt
|
||||
peripherals []*Peripheral
|
||||
}
|
||||
|
||||
type AddressSpace struct {
|
||||
Size string
|
||||
Segments map[string]int
|
||||
}
|
||||
|
||||
type Interrupt struct {
|
||||
Index int `xml:"index,attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
Caption string `xml:"caption,attr"`
|
||||
}
|
||||
|
||||
type Peripheral struct {
|
||||
Name string
|
||||
Caption string
|
||||
Registers []*Register
|
||||
}
|
||||
|
||||
type Register struct {
|
||||
Caption string
|
||||
Variants []RegisterVariant
|
||||
Bitfields []Bitfield
|
||||
peripheral *Peripheral
|
||||
}
|
||||
|
||||
type RegisterVariant struct {
|
||||
Name string
|
||||
Address int64
|
||||
}
|
||||
|
||||
type Bitfield struct {
|
||||
Name string
|
||||
Caption string
|
||||
Mask uint
|
||||
}
|
||||
|
||||
func readATDF(path string) (*Device, error) {
|
||||
// Read Atmel device descriptor files.
|
||||
// See: http://packs.download.atmel.com
|
||||
|
||||
// Open the XML file.
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
decoder := xml.NewDecoder(f)
|
||||
xml := &AVRToolsDeviceFile{}
|
||||
err = decoder.Decode(xml)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
device := xml.Devices[0]
|
||||
|
||||
memorySizes := make(map[string]*AddressSpace, len(device.AddressSpaces))
|
||||
for _, el := range device.AddressSpaces {
|
||||
memorySizes[el.Name] = &AddressSpace{
|
||||
Size: el.Size,
|
||||
Segments: make(map[string]int),
|
||||
}
|
||||
for _, segmentEl := range el.MemorySegments {
|
||||
size, err := strconv.ParseInt(segmentEl.Size, 0, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
memorySizes[el.Name].Segments[segmentEl.Name] = int(size)
|
||||
}
|
||||
}
|
||||
|
||||
allRegisters := map[string]*Register{}
|
||||
|
||||
var peripherals []*Peripheral
|
||||
for _, el := range xml.Modules {
|
||||
peripheral := &Peripheral{
|
||||
Name: el.Name,
|
||||
Caption: el.Caption,
|
||||
}
|
||||
peripherals = append(peripherals, peripheral)
|
||||
|
||||
regElGroup := el.RegisterGroup
|
||||
for _, regEl := range regElGroup.Registers {
|
||||
regOffset, err := strconv.ParseInt(regEl.Offset, 0, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse offset %#v of register %s: %v", regEl.Offset, regEl.Name, err)
|
||||
}
|
||||
reg := &Register{
|
||||
Caption: regEl.Caption,
|
||||
peripheral: peripheral,
|
||||
}
|
||||
switch regEl.Size {
|
||||
case 1:
|
||||
reg.Variants = []RegisterVariant{
|
||||
{
|
||||
Name: regEl.Name,
|
||||
Address: regOffset,
|
||||
},
|
||||
}
|
||||
case 2:
|
||||
reg.Variants = []RegisterVariant{
|
||||
{
|
||||
Name: regEl.Name + "L",
|
||||
Address: regOffset,
|
||||
},
|
||||
{
|
||||
Name: regEl.Name + "H",
|
||||
Address: regOffset + 1,
|
||||
},
|
||||
}
|
||||
default:
|
||||
// TODO
|
||||
continue
|
||||
}
|
||||
|
||||
for _, bitfieldEl := range regEl.Bitfields {
|
||||
mask := bitfieldEl.Mask
|
||||
if len(mask) == 2 {
|
||||
// Two devices (ATtiny102 and ATtiny104) appear to have an
|
||||
// error in the bitfields, leaving out the '0x' prefix.
|
||||
mask = "0x" + mask
|
||||
}
|
||||
maskInt, err := strconv.ParseUint(mask, 0, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse mask %#v of bitfield %s: %v", mask, bitfieldEl.Name, err)
|
||||
}
|
||||
reg.Bitfields = append(reg.Bitfields, Bitfield{
|
||||
Name: regEl.Name + "_" + bitfieldEl.Name,
|
||||
Caption: bitfieldEl.Caption,
|
||||
Mask: uint(maskInt),
|
||||
})
|
||||
}
|
||||
|
||||
if _, ok := allRegisters[regEl.Name]; ok {
|
||||
firstReg := allRegisters[regEl.Name]
|
||||
for i := 0; i < len(firstReg.peripheral.Registers); i++ {
|
||||
if firstReg.peripheral.Registers[i] == firstReg {
|
||||
firstReg.peripheral.Registers = append(firstReg.peripheral.Registers[:i], firstReg.peripheral.Registers[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
allRegisters[regEl.Name] = reg
|
||||
}
|
||||
|
||||
peripheral.Registers = append(peripheral.Registers, reg)
|
||||
}
|
||||
}
|
||||
|
||||
ramSize := 0 // for devices with no RAM
|
||||
for _, ramSegmentName := range []string{"IRAM", "INTERNAL_SRAM", "SRAM"} {
|
||||
if segment, ok := memorySizes["data"].Segments[ramSegmentName]; ok {
|
||||
ramSize = segment
|
||||
}
|
||||
}
|
||||
|
||||
flashSize, err := strconv.ParseInt(memorySizes["prog"].Size, 0, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Device{
|
||||
metadata: map[string]interface{}{
|
||||
"file": filepath.Base(path),
|
||||
"descriptorSource": "http://packs.download.atmel.com/",
|
||||
"name": device.Name,
|
||||
"nameLower": strings.ToLower(device.Name),
|
||||
"description": fmt.Sprintf("Device information for the %s.", device.Name),
|
||||
"arch": device.Architecture,
|
||||
"family": device.Family,
|
||||
"flashSize": int(flashSize),
|
||||
"ramSize": ramSize,
|
||||
"numInterrupts": len(device.Interrupts),
|
||||
},
|
||||
interrupts: device.Interrupts,
|
||||
peripherals: peripherals,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func writeGo(outdir string, device *Device) error {
|
||||
// The Go module for this device.
|
||||
outf, err := os.Create(outdir + "/" + device.metadata["nameLower"].(string) + ".go")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outf.Close()
|
||||
w := bufio.NewWriter(outf)
|
||||
|
||||
maxInterruptNum := 0
|
||||
for _, intr := range device.interrupts {
|
||||
if intr.Index > maxInterruptNum {
|
||||
maxInterruptNum = intr.Index
|
||||
}
|
||||
}
|
||||
|
||||
t := template.Must(template.New("go").Parse(`// Automatically generated file. DO NOT EDIT.
|
||||
// Generated by gen-device-avr.go from {{.metadata.file}}, see {{.metadata.descriptorSource}}
|
||||
|
||||
// +build {{.pkgName}},{{.metadata.nameLower}}
|
||||
|
||||
// {{.metadata.description}}
|
||||
package {{.pkgName}}
|
||||
|
||||
import (
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Some information about this device.
|
||||
const (
|
||||
DEVICE = "{{.metadata.name}}"
|
||||
ARCH = "{{.metadata.arch}}"
|
||||
FAMILY = "{{.metadata.family}}"
|
||||
)
|
||||
|
||||
// Interrupts
|
||||
const ({{range .interrupts}}
|
||||
IRQ_{{.Name}} = {{.Index}} // {{.Caption}}{{end}}
|
||||
IRQ_max = {{.interruptMax}} // Highest interrupt number on this device.
|
||||
)
|
||||
|
||||
// Peripherals.
|
||||
var ({{range .peripherals}}
|
||||
// {{.Caption}}
|
||||
{{range .Registers}}{{range .Variants}} {{.Name}} = (*volatile.Register8)(unsafe.Pointer(uintptr(0x{{printf "%x" .Address}})))
|
||||
{{end}}{{end}}{{end}})
|
||||
`))
|
||||
err = t.Execute(w, map[string]interface{}{
|
||||
"metadata": device.metadata,
|
||||
"pkgName": filepath.Base(strings.TrimRight(outdir, "/")),
|
||||
"interrupts": device.interrupts,
|
||||
"interruptMax": maxInterruptNum,
|
||||
"peripherals": device.peripherals,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write bitfields.
|
||||
for _, peripheral := range device.peripherals {
|
||||
// Only write bitfields when there are any.
|
||||
numFields := 0
|
||||
for _, r := range peripheral.Registers {
|
||||
numFields += len(r.Bitfields)
|
||||
}
|
||||
if numFields == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "\n// Bitfields for %s: %s\nconst(", peripheral.Name, peripheral.Caption)
|
||||
for _, register := range peripheral.Registers {
|
||||
if len(register.Bitfields) == 0 {
|
||||
continue
|
||||
}
|
||||
for _, variant := range register.Variants {
|
||||
fmt.Fprintf(w, "\n\t// %s", variant.Name)
|
||||
if register.Caption != "" {
|
||||
fmt.Fprintf(w, ": %s", register.Caption)
|
||||
}
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
for _, bitfield := range register.Bitfields {
|
||||
if bits.OnesCount(bitfield.Mask) == 1 {
|
||||
fmt.Fprintf(w, "\t%s = 0x%x", bitfield.Name, bitfield.Mask)
|
||||
if len(bitfield.Caption) != 0 {
|
||||
fmt.Fprintf(w, " // %s", bitfield.Caption)
|
||||
}
|
||||
fmt.Fprintf(w, "\n")
|
||||
} else {
|
||||
n := 0
|
||||
for i := uint(0); i < 8; i++ {
|
||||
if (bitfield.Mask>>i)&1 == 0 {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(w, "\t%s%d = 0x%x", bitfield.Name, n, 1<<i)
|
||||
if len(bitfield.Caption) != 0 {
|
||||
fmt.Fprintf(w, " // %s", bitfield.Caption)
|
||||
}
|
||||
n++
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, ")\n")
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func writeAsm(outdir string, device *Device) error {
|
||||
// The interrupt vector, which is hard to write directly in Go.
|
||||
out, err := os.Create(outdir + "/" + device.metadata["nameLower"].(string) + ".s")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
t := template.Must(template.New("asm").Parse(
|
||||
`; Automatically generated file. DO NOT EDIT.
|
||||
; Generated by gen-device-avr.go from {{.file}}, see {{.descriptorSource}}
|
||||
|
||||
; This is the default handler for interrupts, if triggered but not defined.
|
||||
; Sleep inside so that an accidentally triggered interrupt won't drain the
|
||||
; battery of a battery-powered device.
|
||||
.section .text.__vector_default
|
||||
.global __vector_default
|
||||
__vector_default:
|
||||
sleep
|
||||
rjmp __vector_default
|
||||
|
||||
; Avoid the need for repeated .weak and .set instructions.
|
||||
.macro IRQ handler
|
||||
.weak \handler
|
||||
.set \handler, __vector_default
|
||||
.endm
|
||||
|
||||
; The interrupt vector of this device. Must be placed at address 0 by the linker.
|
||||
.section .vectors
|
||||
.global __vectors
|
||||
`))
|
||||
err = t.Execute(out, device.metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
num := 0
|
||||
for _, intr := range device.interrupts {
|
||||
jmp := "jmp"
|
||||
if device.metadata["flashSize"].(int) <= 8*1024 {
|
||||
// When a device has 8kB or less flash, rjmp (2 bytes) must be used
|
||||
// instead of jmp (4 bytes).
|
||||
// https://www.avrfreaks.net/forum/rjmp-versus-jmp
|
||||
jmp = "rjmp"
|
||||
}
|
||||
if intr.Index < num {
|
||||
// Some devices have duplicate interrupts, probably for historical
|
||||
// reasons.
|
||||
continue
|
||||
}
|
||||
for intr.Index > num {
|
||||
fmt.Fprintf(out, " %s __vector_default\n", jmp)
|
||||
num++
|
||||
}
|
||||
num++
|
||||
fmt.Fprintf(out, " %s __vector_%s\n", jmp, intr.Name)
|
||||
}
|
||||
|
||||
fmt.Fprint(out, `
|
||||
; Define default implementations for interrupts, redirecting to
|
||||
; __vector_default when not implemented.
|
||||
`)
|
||||
for _, intr := range device.interrupts {
|
||||
fmt.Fprintf(out, " IRQ __vector_%s\n", intr.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeLD(outdir string, device *Device) error {
|
||||
// Variables for the linker script.
|
||||
out, err := os.Create(outdir + "/" + device.metadata["nameLower"].(string) + ".ld")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
t := template.Must(template.New("ld").Parse(`/* Automatically generated file. DO NOT EDIT. */
|
||||
/* Generated by gen-device-avr.go from {{.file}}, see {{.descriptorSource}} */
|
||||
|
||||
__flash_size = 0x{{printf "%x" .flashSize}};
|
||||
__ram_size = 0x{{printf "%x" .ramSize}};
|
||||
__num_isrs = {{.numInterrupts}};
|
||||
`))
|
||||
return t.Execute(out, device.metadata)
|
||||
}
|
||||
|
||||
func processFile(filepath, outdir string) error {
|
||||
device, err := readATDF(filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = writeGo(outdir, device)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = writeAsm(outdir, device)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeLD(outdir, device)
|
||||
}
|
||||
|
||||
func generate(indir, outdir string) error {
|
||||
// Read list of ATDF files to process.
|
||||
matches, err := filepath.Glob(indir + "/*.atdf")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Start worker goroutines.
|
||||
var wg sync.WaitGroup
|
||||
workChan := make(chan string)
|
||||
errChan := make(chan error, 1)
|
||||
for i := 0; i < runtime.NumCPU(); i++ {
|
||||
go func() {
|
||||
for filepath := range workChan {
|
||||
err := processFile(filepath, outdir)
|
||||
wg.Done()
|
||||
if err != nil {
|
||||
// Store error to errChan if no error was stored before.
|
||||
select {
|
||||
case errChan <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Submit all jobs to the goroutines.
|
||||
wg.Add(len(matches))
|
||||
for _, filepath := range matches {
|
||||
fmt.Println(filepath)
|
||||
workChan <- filepath
|
||||
}
|
||||
close(workChan)
|
||||
|
||||
// Wait until all workers have finished.
|
||||
wg.Wait()
|
||||
|
||||
// Check for an error.
|
||||
select {
|
||||
case err := <-errChan:
|
||||
return err
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
indir := os.Args[1] // directory with register descriptor files (*.atdf)
|
||||
outdir := os.Args[2] // output directory
|
||||
err := generate(indir, outdir)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -1,648 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
import os
|
||||
from xml.etree import ElementTree
|
||||
from glob import glob
|
||||
from collections import OrderedDict
|
||||
import re
|
||||
import argparse
|
||||
|
||||
validName = re.compile('^[a-zA-Z0-9_]+$')
|
||||
|
||||
|
||||
class Device:
|
||||
# dummy
|
||||
pass
|
||||
|
||||
def getText(element):
|
||||
if element is None:
|
||||
return "None"
|
||||
return ''.join(element.itertext())
|
||||
|
||||
def formatText(text):
|
||||
text = re.sub('[ \t\n]+', ' ', text) # Collapse whitespace (like in HTML)
|
||||
text = text.replace('\\n ', '\n')
|
||||
text = text.strip()
|
||||
return text
|
||||
|
||||
# Replace characters that are not allowed in a symbol name with a '_'. This is
|
||||
# useful to be able to process SVD files with errors.
|
||||
def cleanName(text):
|
||||
if not validName.match(text):
|
||||
return ''.join(list(map(lambda c: c if validName.match(c) else '_', text)))
|
||||
return text
|
||||
|
||||
def readSVD(path, sourceURL):
|
||||
# Read ARM SVD files.
|
||||
device = Device()
|
||||
xml = ElementTree.parse(path)
|
||||
root = xml.getroot()
|
||||
deviceName = getText(root.find('name'))
|
||||
deviceDescription = getText(root.find('description')).strip()
|
||||
licenseTexts = root.findall('licenseText')
|
||||
if len(licenseTexts) == 0:
|
||||
licenseText = None
|
||||
elif len(licenseTexts) == 1:
|
||||
licenseText = formatText(getText(licenseTexts[0]))
|
||||
else:
|
||||
raise ValueError('multiple <licenseText> elements')
|
||||
|
||||
device.peripherals = []
|
||||
peripheralDict = {}
|
||||
groups = {}
|
||||
|
||||
interrupts = OrderedDict()
|
||||
|
||||
for periphEl in root.findall('./peripherals/peripheral'):
|
||||
name = getText(periphEl.find('name'))
|
||||
descriptionTags = periphEl.findall('description')
|
||||
description = ''
|
||||
if descriptionTags:
|
||||
description = formatText(getText(descriptionTags[0]))
|
||||
baseAddress = int(getText(periphEl.find('baseAddress')), 0)
|
||||
groupNameTags = periphEl.findall('groupName')
|
||||
groupName = None
|
||||
if groupNameTags:
|
||||
# Some group names (for example the STM32H7A3x) have an invalid
|
||||
# group name. Replace invalid characters with '_'.
|
||||
groupName = cleanName(getText(groupNameTags[0]))
|
||||
|
||||
interruptEls = periphEl.findall('interrupt')
|
||||
for interrupt in interruptEls:
|
||||
intrName = getText(interrupt.find('name'))
|
||||
intrIndex = int(getText(interrupt.find('value')))
|
||||
addInterrupt(interrupts, intrName, intrIndex, description)
|
||||
# As a convenience, also use the peripheral name as the interrupt
|
||||
# name. Only do that for the nrf for now, as the stm32 .svd files
|
||||
# don't always put interrupts in the correct peripheral...
|
||||
if len(interruptEls) == 1 and deviceName.startswith('nrf'):
|
||||
addInterrupt(interrupts, name, intrIndex, description)
|
||||
|
||||
if periphEl.get('derivedFrom') or groupName in groups:
|
||||
if periphEl.get('derivedFrom'):
|
||||
derivedFromName = periphEl.get('derivedFrom')
|
||||
derivedFrom = peripheralDict[derivedFromName]
|
||||
else:
|
||||
derivedFrom = groups[groupName]
|
||||
peripheral = {
|
||||
'name': name,
|
||||
'groupName': derivedFrom['groupName'],
|
||||
'description': description or derivedFrom['description'],
|
||||
'baseAddress': baseAddress,
|
||||
}
|
||||
device.peripherals.append(peripheral)
|
||||
peripheralDict[name] = peripheral
|
||||
if 'subtypes' in derivedFrom:
|
||||
for subtype in derivedFrom['subtypes']:
|
||||
subp = {
|
||||
'name': name + "_"+subtype['clusterName'],
|
||||
'groupName': subtype['groupName'],
|
||||
'description': subtype['description'],
|
||||
'baseAddress': baseAddress,
|
||||
}
|
||||
device.peripherals.append(subp)
|
||||
continue
|
||||
|
||||
peripheral = {
|
||||
'name': name,
|
||||
'groupName': groupName or name,
|
||||
'description': description,
|
||||
'baseAddress': baseAddress,
|
||||
'registers': [],
|
||||
'subtypes': [],
|
||||
}
|
||||
device.peripherals.append(peripheral)
|
||||
peripheralDict[name] = peripheral
|
||||
|
||||
if groupName and groupName not in groups:
|
||||
groups[groupName] = peripheral
|
||||
|
||||
regsEls = periphEl.findall('registers')
|
||||
if regsEls:
|
||||
if len(regsEls) != 1:
|
||||
raise ValueError('expected just one <registers> in a <peripheral>')
|
||||
for register in regsEls[0].findall('register'):
|
||||
peripheral['registers'].extend(parseRegister(groupName or name, register, baseAddress))
|
||||
for cluster in regsEls[0].findall('cluster'):
|
||||
clusterName = getText(cluster.find('name')).replace('[%s]', '')
|
||||
if cluster.find('dimIndex') is not None:
|
||||
clusterName = clusterName.replace('%s', '')
|
||||
clusterDescription = getText(cluster.find('description'))
|
||||
clusterPrefix = clusterName + '_'
|
||||
clusterOffset = int(getText(cluster.find('addressOffset')), 0)
|
||||
if cluster.find('dim') is None:
|
||||
if clusterOffset == 0:
|
||||
# make this a separate peripheral
|
||||
cpRegisters = []
|
||||
for regEl in cluster.findall('register'):
|
||||
cpRegisters.extend(parseRegister(groupName, regEl, baseAddress, clusterName+"_"))
|
||||
# handle sub-clusters of registers
|
||||
for subClusterEl in cluster.findall('cluster'):
|
||||
subclusterName = getText(subClusterEl.find('name')).replace('[%s]', '')
|
||||
subclusterDescription = getText(subClusterEl.find('description'))
|
||||
subclusterPrefix = subclusterName + '_'
|
||||
subclusterOffset = int(getText(subClusterEl.find('addressOffset')), 0)
|
||||
subdim = int(getText(subClusterEl.find('dim')))
|
||||
subdimIncrement = int(getText(subClusterEl.find('dimIncrement')), 16)
|
||||
|
||||
if subdim > 1:
|
||||
subcpRegisters = []
|
||||
subregSize = 0
|
||||
for regEl in subClusterEl.findall('register'):
|
||||
subregSize += int(getText(regEl.find('size')))
|
||||
subcpRegisters.extend(parseRegister(groupName, regEl, baseAddress + subclusterOffset, subclusterPrefix))
|
||||
cpRegisters.append({
|
||||
'name': subclusterName,
|
||||
'address': baseAddress + subclusterOffset,
|
||||
'description': subclusterDescription,
|
||||
'registers': subcpRegisters,
|
||||
'array': subdim,
|
||||
'elementsize': subdimIncrement,
|
||||
})
|
||||
else:
|
||||
for regEl in subClusterEl.findall('register'):
|
||||
cpRegisters.extend(parseRegister(getText(regEl.find('name')), regEl, baseAddress + subclusterOffset, subclusterPrefix))
|
||||
|
||||
cpRegisters.sort(key=lambda r: r['address'])
|
||||
clusterPeripheral = {
|
||||
'name': name+ "_" +clusterName,
|
||||
'groupName': groupName+ "_" +clusterName,
|
||||
'description': description+ " - " +clusterName,
|
||||
'clusterName': clusterName,
|
||||
'baseAddress': baseAddress,
|
||||
'registers': cpRegisters,
|
||||
}
|
||||
device.peripherals.append(clusterPeripheral)
|
||||
peripheral['subtypes'].append(clusterPeripheral)
|
||||
continue
|
||||
dim = None
|
||||
dimIncrement = None
|
||||
else:
|
||||
dim = int(getText(cluster.find('dim')))
|
||||
if dim == 1:
|
||||
dimIncrement = None
|
||||
else:
|
||||
dimIncrement = int(getText(cluster.find('dimIncrement')), 0)
|
||||
clusterRegisters = []
|
||||
for regEl in cluster.findall('register'):
|
||||
clusterRegisters.extend(parseRegister(groupName or name, regEl, baseAddress + clusterOffset, clusterPrefix))
|
||||
clusterRegisters.sort(key=lambda r: r['address'])
|
||||
if dimIncrement is None:
|
||||
lastReg = clusterRegisters[-1]
|
||||
lastAddress = lastReg['address']
|
||||
if lastReg['array'] is not None:
|
||||
lastAddress = lastReg['address'] + lastReg['array'] * lastReg['elementsize']
|
||||
firstAddress = clusterRegisters[0]['address']
|
||||
dimIncrement = lastAddress - firstAddress
|
||||
peripheral['registers'].append({
|
||||
'name': clusterName,
|
||||
'address': baseAddress + clusterOffset,
|
||||
'description': clusterDescription,
|
||||
'registers': clusterRegisters,
|
||||
'array': dim,
|
||||
'elementsize': dimIncrement,
|
||||
})
|
||||
peripheral['registers'].sort(key=lambda r: r['address'])
|
||||
|
||||
device.interrupts = sorted(interrupts.values(), key=lambda v: v['index'])
|
||||
licenseBlock = ''
|
||||
if licenseText is not None:
|
||||
licenseBlock = '// ' + licenseText.replace('\n', '\n// ')
|
||||
licenseBlock = '\n'.join(map(str.rstrip, licenseBlock.split('\n'))) # strip trailing whitespace
|
||||
device.metadata = {
|
||||
'file': os.path.basename(path),
|
||||
'descriptorSource': sourceURL,
|
||||
'name': deviceName,
|
||||
'nameLower': deviceName.lower(),
|
||||
'description': deviceDescription,
|
||||
'licenseBlock': licenseBlock,
|
||||
}
|
||||
|
||||
return device
|
||||
|
||||
def addInterrupt(interrupts, intrName, intrIndex, description):
|
||||
if intrName in interrupts:
|
||||
if interrupts[intrName]['index'] != intrIndex:
|
||||
# Note: some SVD files like the one for STM32H7x7 contain mistakes.
|
||||
# Instead of throwing an error, simply log it.
|
||||
print ('interrupt with the same name has different indexes: %s (%d vs %d)'
|
||||
% (intrName, interrupts[intrName]['index'], intrIndex))
|
||||
if description not in interrupts[intrName]['description'].split(' // '):
|
||||
interrupts[intrName]['description'] += ' // ' + description
|
||||
else:
|
||||
interrupts[intrName] = {
|
||||
'name': intrName,
|
||||
'index': intrIndex,
|
||||
'description': description,
|
||||
}
|
||||
|
||||
def parseBitfields(groupName, regName, fieldsEls, bitfieldPrefix=''):
|
||||
fields = []
|
||||
if fieldsEls:
|
||||
for fieldEl in fieldsEls[0].findall('field'):
|
||||
# Some bitfields (like the STM32H7x7) contain invalid bitfield
|
||||
# names like 'CNT[31]'. Replace invalid characters with '_' when
|
||||
# needed.
|
||||
fieldName = cleanName(getText(fieldEl.find('name')))
|
||||
if not fieldName[0].isupper() and not fieldName[0].isdigit():
|
||||
fieldName = fieldName.upper()
|
||||
if len(fieldEl.findall('lsb')) == 1 and len(fieldEl.findall('msb')) == 1:
|
||||
# try to use lsb/msb tags
|
||||
lsb = int(getText(fieldEl.findall('lsb')[0]))
|
||||
msb = int(getText(fieldEl.findall('msb')[0]))
|
||||
elif len(fieldEl.findall('bitOffset')) > 0 and len(fieldEl.findall('bitWidth')) > 0:
|
||||
# try to use bitOffset/bitWidth tags
|
||||
lsb = int(getText(fieldEl.find('bitOffset')))
|
||||
msb = int(getText(fieldEl.find('bitWidth'))) + lsb - 1
|
||||
elif len(fieldEl.findall('bitRange')) > 0:
|
||||
# try use bitRange
|
||||
bitRangeTags = fieldEl.findall('bitRange')
|
||||
lsb = int(getText(bitRangeTags[0]).split(":")[1][:-1])
|
||||
msb = int(getText(bitRangeTags[0]).split(":")[0][1:])
|
||||
else:
|
||||
# this is an error. what to do?
|
||||
print("unable to find lsb/msb in field:", fieldName)
|
||||
|
||||
fields.append({
|
||||
'name': '{}_{}{}_{}_Pos'.format(groupName, bitfieldPrefix, regName, fieldName),
|
||||
'description': 'Position of %s field.' % fieldName,
|
||||
'value': lsb,
|
||||
})
|
||||
fields.append({
|
||||
'name': '{}_{}{}_{}_Msk'.format(groupName, bitfieldPrefix, regName, fieldName),
|
||||
'description': 'Bit mask of %s field.' % fieldName,
|
||||
'value': (0xffffffff >> (31 - (msb - lsb))) << lsb,
|
||||
})
|
||||
if lsb == msb: # single bit
|
||||
fields.append({
|
||||
'name': '{}_{}{}_{}'.format(groupName, bitfieldPrefix, regName, fieldName),
|
||||
'description': 'Bit %s.' % fieldName,
|
||||
'value': 1 << lsb,
|
||||
})
|
||||
for enumEl in fieldEl.findall('enumeratedValues/enumeratedValue'):
|
||||
enumName = getText(enumEl.find('name'))
|
||||
if not enumName[0].isupper() and not enumName[0].isdigit():
|
||||
enumName = enumName.upper()
|
||||
enumDescription = getText(enumEl.find('description')).replace('\n', ' ')
|
||||
enumValue = int(getText(enumEl.find('value')), 0)
|
||||
fields.append({
|
||||
'name': '{}_{}{}_{}_{}'.format(groupName, bitfieldPrefix, regName, fieldName, enumName),
|
||||
'description': enumDescription,
|
||||
'value': enumValue,
|
||||
})
|
||||
return fields
|
||||
|
||||
class Register:
|
||||
def __init__(self, element, baseAddress):
|
||||
self.element = element
|
||||
self.baseAddress = baseAddress
|
||||
|
||||
def name(self):
|
||||
return getText(self.element.find('name')).replace('[%s]', '')
|
||||
|
||||
def description(self):
|
||||
return getText(self.element.find('description')).replace('\n', ' ')
|
||||
|
||||
def address(self):
|
||||
offsetEls = self.element.findall('offset')
|
||||
if not offsetEls:
|
||||
offsetEls = self.element.findall('addressOffset')
|
||||
return self.baseAddress + int(getText(offsetEls[0]), 0)
|
||||
|
||||
def dim(self):
|
||||
dimEls = self.element.findall('dim')
|
||||
if len(dimEls) == 0:
|
||||
return None
|
||||
elif len(dimEls) == 1:
|
||||
return int(getText(dimEls[0]), 0)
|
||||
else:
|
||||
raise ValueError('expected at most one <dim> element in %s register' % self.name())
|
||||
|
||||
def size(self):
|
||||
size = 4
|
||||
elSizes = self.element.findall('size')
|
||||
if elSizes:
|
||||
size = int(getText(elSizes[0]), 0) // 8
|
||||
return size
|
||||
|
||||
|
||||
def parseRegister(groupName, regEl, baseAddress, bitfieldPrefix=''):
|
||||
reg = Register(regEl, baseAddress)
|
||||
|
||||
fieldsEls = regEl.findall('fields')
|
||||
|
||||
if reg.dim() is not None:
|
||||
dimIncrement = int(getText(regEl.find('dimIncrement')), 0)
|
||||
if "%s" in reg.name():
|
||||
# a "spaced array" of registers, special processing required
|
||||
# we need to generate a separate register for each "element"
|
||||
results = []
|
||||
for i in range(reg.dim()):
|
||||
regAddress = reg.address() + (i * dimIncrement)
|
||||
results.append({
|
||||
'name': reg.name().replace('%s', str(i)),
|
||||
'address': regAddress,
|
||||
'description': reg.description(),
|
||||
'bitfields': [],
|
||||
'array': None,
|
||||
'elementsize': reg.size(),
|
||||
})
|
||||
# set first result bitfield
|
||||
shortName = reg.name().replace('_%s', '').replace('%s', '').upper()
|
||||
results[0]['bitfields'] = parseBitfields(groupName, shortName, fieldsEls, bitfieldPrefix)
|
||||
return results
|
||||
regName = reg.name()
|
||||
if not regName[0].isupper() and not regName[0].isdigit():
|
||||
regName = regName.upper()
|
||||
return [{
|
||||
'name': regName,
|
||||
'address': reg.address(),
|
||||
'description': reg.description(),
|
||||
'bitfields': parseBitfields(groupName, regName, fieldsEls, bitfieldPrefix),
|
||||
'array': reg.dim(),
|
||||
'elementsize': reg.size(),
|
||||
}]
|
||||
|
||||
def writeGo(outdir, device):
|
||||
# The Go module for this device.
|
||||
out = open(outdir + '/' + device.metadata['nameLower'] + '.go', 'w')
|
||||
pkgName = os.path.basename(outdir.rstrip('/'))
|
||||
out.write('''\
|
||||
// Automatically generated file. DO NOT EDIT.
|
||||
// Generated by gen-device-svd.py from {file}, see {descriptorSource}
|
||||
|
||||
// +build {pkgName},{nameLower}
|
||||
|
||||
// {description}
|
||||
//
|
||||
{licenseBlock}
|
||||
package {pkgName}
|
||||
|
||||
import (
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Some information about this device.
|
||||
const (
|
||||
DEVICE = "{name}"
|
||||
)
|
||||
'''.format(pkgName=pkgName, **device.metadata))
|
||||
|
||||
out.write('\n// Interrupt numbers\nconst (\n')
|
||||
for intr in device.interrupts:
|
||||
out.write('\tIRQ_{name} = {index} // {description}\n'.format(**intr))
|
||||
intrMax = max(map(lambda intr: intr['index'], device.interrupts))
|
||||
out.write('\tIRQ_max = {} // Highest interrupt number on this device.\n'.format(intrMax))
|
||||
out.write(')\n')
|
||||
|
||||
# Define actual peripheral pointers.
|
||||
out.write('\n// Peripherals.\nvar (\n')
|
||||
for peripheral in device.peripherals:
|
||||
out.write('\t{name} = (*{groupName}_Type)(unsafe.Pointer(uintptr(0x{baseAddress:x}))) // {description}\n'.format(**peripheral))
|
||||
out.write(')\n')
|
||||
|
||||
# Define peripheral struct types.
|
||||
for peripheral in device.peripherals:
|
||||
if 'registers' not in peripheral:
|
||||
# This peripheral was derived from another peripheral. No new type
|
||||
# needs to be defined for it.
|
||||
continue
|
||||
out.write('\n// {description}\ntype {groupName}_Type struct {{\n'.format(**peripheral))
|
||||
address = peripheral['baseAddress']
|
||||
padNumber = 0
|
||||
for register in peripheral['registers']:
|
||||
if address > register['address'] and 'registers' not in register :
|
||||
# In Nordic SVD files, these registers are deprecated or
|
||||
# duplicates, so can be ignored.
|
||||
#print('skip: %s.%s %s - %s %s' % (peripheral['name'], register['name'], address, register['address'], register['elementsize']))
|
||||
continue
|
||||
eSize = register['elementsize']
|
||||
if eSize == 4:
|
||||
regType = 'volatile.Register32'
|
||||
elif eSize == 2:
|
||||
regType = 'volatile.Register16'
|
||||
elif eSize == 1:
|
||||
regType = 'volatile.Register8'
|
||||
else:
|
||||
eSize = 4
|
||||
regType = 'volatile.Register32'
|
||||
|
||||
# insert padding, if needed
|
||||
if address < register['address']:
|
||||
bytesNeeded = register['address'] - address
|
||||
if bytesNeeded == 1:
|
||||
out.write('\t_padding{padNumber} {regType}\n'.format(padNumber=padNumber, regType='volatile.Register8'))
|
||||
elif bytesNeeded == 2:
|
||||
out.write('\t_padding{padNumber} {regType}\n'.format(padNumber=padNumber, regType='volatile.Register16'))
|
||||
elif bytesNeeded == 3:
|
||||
out.write('\t_padding{padNumber} [3]{regType}\n'.format(padNumber=padNumber, regType='volatile.Register8'))
|
||||
else:
|
||||
numSkip = (register['address'] - address) // eSize
|
||||
if numSkip == 1:
|
||||
out.write('\t_padding{padNumber} {regType}\n'.format(padNumber=padNumber, regType=regType))
|
||||
else:
|
||||
out.write('\t_padding{padNumber} [{num}]{regType}\n'.format(padNumber=padNumber, num=numSkip, regType=regType))
|
||||
padNumber += 1
|
||||
address = register['address']
|
||||
|
||||
lastCluster = False
|
||||
if 'registers' in register:
|
||||
# This is a cluster, not a register. Create the cluster type.
|
||||
regType = 'struct {\n'
|
||||
subaddress = register['address']
|
||||
for subregister in register['registers']:
|
||||
if subregister['elementsize'] == 4:
|
||||
subregType = 'volatile.Register32'
|
||||
elif subregister['elementsize'] == 2:
|
||||
subregType = 'volatile.Register16'
|
||||
elif subregister['elementsize'] == 1:
|
||||
subregType = 'volatile.Register8'
|
||||
|
||||
if subregister['array']:
|
||||
subregType = '[{}]{}'.format(subregister['array'], subregType)
|
||||
if subaddress != subregister['address']:
|
||||
bytesNeeded = subregister['address'] - subaddress
|
||||
if bytesNeeded == 1:
|
||||
regType += '\t\t_padding{padNumber} {subregType}\n'.format(padNumber=padNumber, subregType='volatile.Register8')
|
||||
elif bytesNeeded == 2:
|
||||
regType += '\t\t_padding{padNumber} {subregType}\n'.format(padNumber=padNumber, subregType='volatile.Register16')
|
||||
else:
|
||||
numSkip = (subregister['address'] - subaddress)
|
||||
if numSkip < 1:
|
||||
continue
|
||||
elif numSkip == 1:
|
||||
regType += '\t\t_padding{padNumber} {subregType}\n'.format(padNumber=padNumber, subregType='volatile.Register8')
|
||||
else:
|
||||
regType += '\t\t_padding{padNumber} [{num}]{subregType}\n'.format(padNumber=padNumber, num=numSkip, subregType='volatile.Register8')
|
||||
padNumber += 1
|
||||
subaddress += bytesNeeded
|
||||
if subregister['array'] is not None:
|
||||
subregSize = subregister['array'] * subregister['elementsize']
|
||||
else:
|
||||
subregSize = subregister['elementsize']
|
||||
subaddress += subregSize
|
||||
regType += '\t\t{name} {subregType}\n'.format(name=subregister['name'], subregType=subregType)
|
||||
if register['array'] is not None:
|
||||
if subaddress != register['address'] + register['elementsize']:
|
||||
numSkip = ((register['address'] + register['elementsize']) - subaddress) // subregSize
|
||||
if numSkip <= 1:
|
||||
regType += '\t\t_padding{padNumber} {subregType}\n'.format(padNumber=padNumber, subregType=subregType)
|
||||
else:
|
||||
regType += '\t\t_padding{padNumber} [{num}]{subregType}\n'.format(padNumber=padNumber, num=numSkip, subregType=subregType)
|
||||
else:
|
||||
lastCluster = True
|
||||
regType += '\t}'
|
||||
address = subaddress
|
||||
if register['array'] is not None:
|
||||
regType = '[{}]{}'.format(register['array'], regType)
|
||||
out.write('\t{name} {regType}\n'.format(name=register['name'], regType=regType))
|
||||
|
||||
# next address
|
||||
if lastCluster is True:
|
||||
lastCluster = False
|
||||
elif register['array'] is not None:
|
||||
address = register['address'] + register['elementsize'] * register['array']
|
||||
else:
|
||||
address = register['address'] + register['elementsize']
|
||||
out.write('}\n')
|
||||
|
||||
# Define bitfields.
|
||||
for peripheral in device.peripherals:
|
||||
if 'registers' not in peripheral:
|
||||
# This peripheral was derived from another peripheral. Bitfields are
|
||||
# already defined.
|
||||
continue
|
||||
out.write('\n// Bitfields for {name}: {description}\nconst('.format(**peripheral))
|
||||
for register in peripheral['registers']:
|
||||
if register.get('bitfields'):
|
||||
writeGoRegisterBitfields(out, register, register['name'])
|
||||
for subregister in register.get('registers', []):
|
||||
writeGoRegisterBitfields(out, subregister, register['name'] + '.' + subregister['name'])
|
||||
out.write(')\n')
|
||||
|
||||
def writeGoRegisterBitfields(out, register, name):
|
||||
out.write('\n\t// {}'.format(name))
|
||||
if register['description']:
|
||||
out.write(': {description}'.format(**register))
|
||||
out.write('\n')
|
||||
for bitfield in register['bitfields']:
|
||||
out.write('\t{name} = 0x{value:x}'.format(**bitfield))
|
||||
if bitfield['description']:
|
||||
out.write(' // {description}'.format(**bitfield))
|
||||
out.write('\n')
|
||||
|
||||
|
||||
def writeAsm(outdir, device):
|
||||
# The interrupt vector, which is hard to write directly in Go.
|
||||
out = open(outdir + '/' + device.metadata['nameLower'] + '.s', 'w')
|
||||
out.write('''\
|
||||
// Automatically generated file. DO NOT EDIT.
|
||||
// Generated by gen-device-svd.py from {file}, see {descriptorSource}
|
||||
|
||||
// {description}
|
||||
//
|
||||
{licenseBlock}
|
||||
|
||||
.syntax unified
|
||||
|
||||
// This is the default handler for interrupts, if triggered but not defined.
|
||||
.section .text.Default_Handler
|
||||
.global Default_Handler
|
||||
.type Default_Handler, %function
|
||||
Default_Handler:
|
||||
wfe
|
||||
b Default_Handler
|
||||
|
||||
// Avoid the need for repeated .weak and .set instructions.
|
||||
.macro IRQ handler
|
||||
.weak \\handler
|
||||
.set \\handler, Default_Handler
|
||||
.endm
|
||||
|
||||
// Must set the "a" flag on the section:
|
||||
// https://svnweb.freebsd.org/base/stable/11/sys/arm/arm/locore-v4.S?r1=321049&r2=321048&pathrev=321049
|
||||
// https://sourceware.org/binutils/docs/as/Section.html#ELF-Version
|
||||
.section .isr_vector, "a", %progbits
|
||||
.global __isr_vector
|
||||
// Interrupt vector as defined by Cortex-M, starting with the stack top.
|
||||
// On reset, SP is initialized with *0x0 and PC is loaded with *0x4, loading
|
||||
// _stack_top and Reset_Handler.
|
||||
.long _stack_top
|
||||
.long Reset_Handler
|
||||
.long NMI_Handler
|
||||
.long HardFault_Handler
|
||||
.long MemoryManagement_Handler
|
||||
.long BusFault_Handler
|
||||
.long UsageFault_Handler
|
||||
.long 0
|
||||
.long 0
|
||||
.long 0
|
||||
.long 0
|
||||
.long SVC_Handler
|
||||
.long DebugMon_Handler
|
||||
.long 0
|
||||
.long PendSV_Handler
|
||||
.long SysTick_Handler
|
||||
|
||||
// Extra interrupts for peripherals defined by the hardware vendor.
|
||||
'''.format(**device.metadata))
|
||||
num = 0
|
||||
for intr in device.interrupts:
|
||||
if intr['index'] == num - 1:
|
||||
continue
|
||||
if intr['index'] < num:
|
||||
raise ValueError('interrupt numbers are not sorted')
|
||||
while intr['index'] > num:
|
||||
out.write(' .long 0\n')
|
||||
num += 1
|
||||
num += 1
|
||||
out.write(' .long {name}_IRQHandler\n'.format(**intr))
|
||||
|
||||
out.write('''
|
||||
// Define default implementations for interrupts, redirecting to
|
||||
// Default_Handler when not implemented.
|
||||
IRQ NMI_Handler
|
||||
IRQ HardFault_Handler
|
||||
IRQ MemoryManagement_Handler
|
||||
IRQ BusFault_Handler
|
||||
IRQ UsageFault_Handler
|
||||
IRQ SVC_Handler
|
||||
IRQ DebugMon_Handler
|
||||
IRQ PendSV_Handler
|
||||
IRQ SysTick_Handler
|
||||
''')
|
||||
for intr in device.interrupts:
|
||||
out.write(' IRQ {name}_IRQHandler\n'.format(**intr))
|
||||
|
||||
def generate(indir, outdir, sourceURL):
|
||||
if not os.path.isdir(indir):
|
||||
print('cannot find input directory:', indir, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not os.path.isdir(outdir):
|
||||
os.mkdir(outdir)
|
||||
infiles = glob(indir + '/*.svd')
|
||||
if not infiles:
|
||||
print('no .svd files found:', indir, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
for filepath in sorted(infiles):
|
||||
print(filepath)
|
||||
device = readSVD(filepath, sourceURL)
|
||||
writeGo(outdir, device)
|
||||
writeAsm(outdir, device)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='Generate Go register descriptors and interrupt vectors from .svd files')
|
||||
parser.add_argument('indir', metavar='indir', type=str,
|
||||
help='input directory containing .svd files')
|
||||
parser.add_argument('outdir', metavar='outdir', type=str,
|
||||
help='output directory')
|
||||
parser.add_argument('--source', metavar='source', type=str,
|
||||
help='output directory',
|
||||
default='<unknown>')
|
||||
args = parser.parse_args()
|
||||
generate(args.indir, args.outdir, args.source)
|
||||
Executable
+950
@@ -0,0 +1,950 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/xml"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var validName = regexp.MustCompile("^[a-zA-Z0-9_]+$")
|
||||
|
||||
type SVDFile struct {
|
||||
XMLName xml.Name `xml:"device"`
|
||||
Name string `xml:"name"`
|
||||
Description string `xml:"description"`
|
||||
LicenseText string `xml:"licenseText"`
|
||||
Peripherals []struct {
|
||||
Name string `xml:"name"`
|
||||
Description string `xml:"description"`
|
||||
BaseAddress string `xml:"baseAddress"`
|
||||
GroupName string `xml:"groupName"`
|
||||
DerivedFrom string `xml:"derivedFrom,attr"`
|
||||
Interrupts []struct {
|
||||
Name string `xml:"name"`
|
||||
Index int `xml:"value"`
|
||||
} `xml:"interrupt"`
|
||||
Registers []*SVDRegister `xml:"registers>register"`
|
||||
Clusters []*SVDCluster `xml:"registers>cluster"`
|
||||
} `xml:"peripherals>peripheral"`
|
||||
}
|
||||
|
||||
type SVDRegister struct {
|
||||
Name string `xml:"name"`
|
||||
Description string `xml:"description"`
|
||||
Dim *string `xml:"dim"`
|
||||
DimIncrement string `xml:"dimIncrement"`
|
||||
Size *string `xml:"size"`
|
||||
Fields []*SVDField `xml:"fields>field"`
|
||||
Offset *string `xml:"offset"`
|
||||
AddressOffset *string `xml:"addressOffset"`
|
||||
}
|
||||
|
||||
type SVDField struct {
|
||||
Name string `xml:"name"`
|
||||
Description string `xml:"description"`
|
||||
Lsb *uint32 `xml:"lsb"`
|
||||
Msb *uint32 `xml:"msb"`
|
||||
BitOffset *uint32 `xml:"bitOffset"`
|
||||
BitWidth *uint32 `xml:"bitWidth"`
|
||||
BitRange *string `xml:"bitRange"`
|
||||
EnumeratedValues []struct {
|
||||
Name string `xml:"name"`
|
||||
Description string `xml:"description"`
|
||||
Value string `xml:"value"`
|
||||
} `xml:"enumeratedValues>enumeratedValue"`
|
||||
}
|
||||
|
||||
type SVDCluster struct {
|
||||
Dim *int `xml:"dim"`
|
||||
DimIncrement string `xml:"dimIncrement"`
|
||||
DimIndex *string `xml:"dimIndex"`
|
||||
Name string `xml:"name"`
|
||||
Description string `xml:"description"`
|
||||
Registers []*SVDRegister `xml:"register"`
|
||||
Clusters []*SVDCluster `xml:"cluster"`
|
||||
AddressOffset string `xml:"addressOffset"`
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
metadata map[string]string
|
||||
interrupts []*interrupt
|
||||
peripherals []*peripheral
|
||||
}
|
||||
|
||||
type interrupt struct {
|
||||
Name string
|
||||
peripheralIndex int
|
||||
Value int // interrupt number
|
||||
Description string
|
||||
}
|
||||
|
||||
type peripheral struct {
|
||||
Name string
|
||||
GroupName string
|
||||
BaseAddress uint64
|
||||
Description string
|
||||
ClusterName string
|
||||
registers []*PeripheralField
|
||||
subtypes []*peripheral
|
||||
}
|
||||
|
||||
// A PeripheralField is a single field in a peripheral type. It may be a full
|
||||
// peripheral or a cluster within a peripheral.
|
||||
type PeripheralField struct {
|
||||
name string
|
||||
address uint64
|
||||
description string
|
||||
registers []*PeripheralField // contains fields if this is a cluster
|
||||
array int
|
||||
elementSize int
|
||||
bitfields []Bitfield
|
||||
}
|
||||
|
||||
type Bitfield struct {
|
||||
name string
|
||||
description string
|
||||
value uint32
|
||||
}
|
||||
|
||||
func formatText(text string) string {
|
||||
text = regexp.MustCompile(`[ \t\n]+`).ReplaceAllString(text, " ") // Collapse whitespace (like in HTML)
|
||||
text = strings.Replace(text, "\\n ", "\n", -1)
|
||||
text = strings.TrimSpace(text)
|
||||
return text
|
||||
}
|
||||
|
||||
// Replace characters that are not allowed in a symbol name with a '_'. This is
|
||||
// useful to be able to process SVD files with errors.
|
||||
func cleanName(text string) string {
|
||||
if !validName.MatchString(text) {
|
||||
result := make([]rune, 0, len(text))
|
||||
for _, c := range text {
|
||||
if validName.MatchString(string(c)) {
|
||||
result = append(result, c)
|
||||
} else {
|
||||
result = append(result, '_')
|
||||
}
|
||||
}
|
||||
text = string(result)
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
// Read ARM SVD files.
|
||||
func readSVD(path, sourceURL string) (*Device, error) {
|
||||
// Open the XML file.
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
decoder := xml.NewDecoder(f)
|
||||
device := &SVDFile{}
|
||||
err = decoder.Decode(device)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peripheralDict := map[string]*peripheral{}
|
||||
groups := map[string]*peripheral{}
|
||||
|
||||
interrupts := make(map[string]*interrupt)
|
||||
var peripheralsList []*peripheral
|
||||
|
||||
for _, periphEl := range device.Peripherals {
|
||||
description := formatText(periphEl.Description)
|
||||
baseAddress, err := strconv.ParseUint(periphEl.BaseAddress, 0, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid base address: %w", err)
|
||||
}
|
||||
// Some group names (for example the STM32H7A3x) have an invalid
|
||||
// group name. Replace invalid characters with "_".
|
||||
groupName := cleanName(periphEl.GroupName)
|
||||
|
||||
for _, interrupt := range periphEl.Interrupts {
|
||||
addInterrupt(interrupts, interrupt.Name, interrupt.Index, description)
|
||||
// As a convenience, also use the peripheral name as the interrupt
|
||||
// name. Only do that for the nrf for now, as the stm32 .svd files
|
||||
// don't always put interrupts in the correct peripheral...
|
||||
if len(periphEl.Interrupts) == 1 && strings.HasPrefix(device.Name, "nrf") {
|
||||
addInterrupt(interrupts, periphEl.Name, interrupt.Index, description)
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := groups[groupName]; ok || periphEl.DerivedFrom != "" {
|
||||
var derivedFrom *peripheral
|
||||
if periphEl.DerivedFrom != "" {
|
||||
derivedFrom = peripheralDict[periphEl.DerivedFrom]
|
||||
} else {
|
||||
derivedFrom = groups[groupName]
|
||||
}
|
||||
p := &peripheral{
|
||||
Name: periphEl.Name,
|
||||
GroupName: derivedFrom.GroupName,
|
||||
Description: description,
|
||||
BaseAddress: baseAddress,
|
||||
}
|
||||
if p.Description == "" {
|
||||
p.Description = derivedFrom.Description
|
||||
}
|
||||
peripheralsList = append(peripheralsList, p)
|
||||
peripheralDict[p.Name] = p
|
||||
for _, subtype := range derivedFrom.subtypes {
|
||||
peripheralsList = append(peripheralsList, &peripheral{
|
||||
Name: periphEl.Name + "_" + subtype.ClusterName,
|
||||
GroupName: subtype.GroupName,
|
||||
Description: subtype.Description,
|
||||
BaseAddress: baseAddress,
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
p := &peripheral{
|
||||
Name: periphEl.Name,
|
||||
GroupName: groupName,
|
||||
Description: description,
|
||||
BaseAddress: baseAddress,
|
||||
registers: []*PeripheralField{},
|
||||
}
|
||||
if p.GroupName == "" {
|
||||
p.GroupName = periphEl.Name
|
||||
}
|
||||
peripheralsList = append(peripheralsList, p)
|
||||
peripheralDict[periphEl.Name] = p
|
||||
|
||||
if _, ok := groups[groupName]; !ok && groupName != "" {
|
||||
groups[groupName] = p
|
||||
}
|
||||
|
||||
for _, register := range periphEl.Registers {
|
||||
regName := groupName // preferably use the group name
|
||||
if regName == "" {
|
||||
regName = periphEl.Name // fall back to peripheral name
|
||||
}
|
||||
p.registers = append(p.registers, parseRegister(regName, register, baseAddress, "")...)
|
||||
}
|
||||
for _, cluster := range periphEl.Clusters {
|
||||
clusterName := strings.Replace(cluster.Name, "[%s]", "", -1)
|
||||
if cluster.DimIndex != nil {
|
||||
clusterName = strings.Replace(clusterName, "%s", "", -1)
|
||||
}
|
||||
clusterPrefix := clusterName + "_"
|
||||
clusterOffset, err := strconv.ParseUint(cluster.AddressOffset, 0, 32)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var dim, dimIncrement int
|
||||
if cluster.Dim == nil {
|
||||
if clusterOffset == 0 {
|
||||
// make this a separate peripheral
|
||||
cpRegisters := []*PeripheralField{}
|
||||
for _, regEl := range cluster.Registers {
|
||||
cpRegisters = append(cpRegisters, parseRegister(groupName, regEl, baseAddress, clusterName+"_")...)
|
||||
}
|
||||
// handle sub-clusters of registers
|
||||
for _, subClusterEl := range cluster.Clusters {
|
||||
subclusterName := strings.Replace(subClusterEl.Name, "[%s]", "", -1)
|
||||
subclusterPrefix := subclusterName + "_"
|
||||
subclusterOffset, err := strconv.ParseUint(subClusterEl.AddressOffset, 0, 32)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
subdim := *subClusterEl.Dim
|
||||
subdimIncrement, err := strconv.ParseInt(subClusterEl.DimIncrement, 0, 32)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if subdim > 1 {
|
||||
subcpRegisters := []*PeripheralField{}
|
||||
subregSize := 0
|
||||
for _, regEl := range subClusterEl.Registers {
|
||||
size, err := strconv.ParseInt(*regEl.Size, 0, 32)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
subregSize += int(size)
|
||||
subcpRegisters = append(subcpRegisters, parseRegister(groupName, regEl, baseAddress+subclusterOffset, subclusterPrefix)...)
|
||||
}
|
||||
cpRegisters = append(cpRegisters, &PeripheralField{
|
||||
name: subclusterName,
|
||||
address: baseAddress + subclusterOffset,
|
||||
description: subClusterEl.Description,
|
||||
registers: subcpRegisters,
|
||||
array: subdim,
|
||||
elementSize: int(subdimIncrement),
|
||||
})
|
||||
} else {
|
||||
for _, regEl := range subClusterEl.Registers {
|
||||
cpRegisters = append(cpRegisters, parseRegister(regEl.Name, regEl, baseAddress+subclusterOffset, subclusterPrefix)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.SliceStable(cpRegisters, func(i, j int) bool {
|
||||
return cpRegisters[i].address < cpRegisters[j].address
|
||||
})
|
||||
clusterPeripheral := &peripheral{
|
||||
Name: periphEl.Name + "_" + clusterName,
|
||||
GroupName: groupName + "_" + clusterName,
|
||||
Description: description + " - " + clusterName,
|
||||
ClusterName: clusterName,
|
||||
BaseAddress: baseAddress,
|
||||
registers: cpRegisters,
|
||||
}
|
||||
peripheralsList = append(peripheralsList, clusterPeripheral)
|
||||
peripheralDict[clusterPeripheral.Name] = clusterPeripheral
|
||||
p.subtypes = append(p.subtypes, clusterPeripheral)
|
||||
continue
|
||||
}
|
||||
dim = -1
|
||||
dimIncrement = -1
|
||||
} else {
|
||||
dim = *cluster.Dim
|
||||
if dim == 1 {
|
||||
dimIncrement = -1
|
||||
} else {
|
||||
inc, err := strconv.ParseUint(cluster.DimIncrement, 0, 32)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dimIncrement = int(inc)
|
||||
}
|
||||
}
|
||||
clusterRegisters := []*PeripheralField{}
|
||||
for _, regEl := range cluster.Registers {
|
||||
regName := groupName
|
||||
if regName == "" {
|
||||
regName = periphEl.Name
|
||||
}
|
||||
clusterRegisters = append(clusterRegisters, parseRegister(regName, regEl, baseAddress+clusterOffset, clusterPrefix)...)
|
||||
}
|
||||
sort.SliceStable(clusterRegisters, func(i, j int) bool {
|
||||
return clusterRegisters[i].address < clusterRegisters[j].address
|
||||
})
|
||||
if dimIncrement == -1 {
|
||||
lastReg := clusterRegisters[len(clusterRegisters)-1]
|
||||
lastAddress := lastReg.address
|
||||
if lastReg.array != -1 {
|
||||
lastAddress = lastReg.address + uint64(lastReg.array*lastReg.elementSize)
|
||||
}
|
||||
firstAddress := clusterRegisters[0].address
|
||||
dimIncrement = int(lastAddress - firstAddress)
|
||||
}
|
||||
p.registers = append(p.registers, &PeripheralField{
|
||||
name: clusterName,
|
||||
address: baseAddress + clusterOffset,
|
||||
description: cluster.Description,
|
||||
registers: clusterRegisters,
|
||||
array: dim,
|
||||
elementSize: dimIncrement,
|
||||
})
|
||||
}
|
||||
sort.SliceStable(p.registers, func(i, j int) bool {
|
||||
return p.registers[i].address < p.registers[j].address
|
||||
})
|
||||
}
|
||||
|
||||
// Make a sorted list of interrupts.
|
||||
interruptList := make([]*interrupt, 0, len(interrupts))
|
||||
for _, intr := range interrupts {
|
||||
interruptList = append(interruptList, intr)
|
||||
}
|
||||
sort.SliceStable(interruptList, func(i, j int) bool {
|
||||
if interruptList[i].Value != interruptList[j].Value {
|
||||
return interruptList[i].Value < interruptList[j].Value
|
||||
}
|
||||
return interruptList[i].peripheralIndex < interruptList[j].peripheralIndex
|
||||
})
|
||||
|
||||
// Properly format the license block, with comments.
|
||||
licenseBlock := ""
|
||||
if text := formatText(device.LicenseText); text != "" {
|
||||
licenseBlock = "// " + strings.Replace(text, "\n", "\n// ", -1)
|
||||
licenseBlock = regexp.MustCompile(`\s+\n`).ReplaceAllString(licenseBlock, "\n")
|
||||
}
|
||||
|
||||
return &Device{
|
||||
metadata: map[string]string{
|
||||
"file": filepath.Base(path),
|
||||
"descriptorSource": sourceURL,
|
||||
"name": device.Name,
|
||||
"nameLower": strings.ToLower(device.Name),
|
||||
"description": strings.TrimSpace(device.Description),
|
||||
"licenseBlock": licenseBlock,
|
||||
},
|
||||
interrupts: interruptList,
|
||||
peripherals: peripheralsList,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func addInterrupt(interrupts map[string]*interrupt, name string, index int, description string) {
|
||||
if _, ok := interrupts[name]; ok {
|
||||
if interrupts[name].Value != index {
|
||||
// Note: some SVD files like the one for STM32H7x7 contain mistakes.
|
||||
// Instead of throwing an error, simply log it.
|
||||
fmt.Fprintf(os.Stderr, "interrupt with the same name has different indexes: %s (%d vs %d)",
|
||||
name, interrupts[name].Value, index)
|
||||
}
|
||||
parts := strings.Split(interrupts[name].Description, " // ")
|
||||
hasDescription := false
|
||||
for _, part := range parts {
|
||||
if part == description {
|
||||
hasDescription = true
|
||||
}
|
||||
}
|
||||
if !hasDescription {
|
||||
interrupts[name].Description += " // " + description
|
||||
}
|
||||
} else {
|
||||
interrupts[name] = &interrupt{
|
||||
Name: name,
|
||||
peripheralIndex: len(interrupts),
|
||||
Value: index,
|
||||
Description: description,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPrefix string) []Bitfield {
|
||||
var fields []Bitfield
|
||||
for _, fieldEl := range fieldEls {
|
||||
// Some bitfields (like the STM32H7x7) contain invalid bitfield
|
||||
// names like "CNT[31]". Replace invalid characters with "_" when
|
||||
// needed.
|
||||
fieldName := cleanName(fieldEl.Name)
|
||||
if !unicode.IsUpper(rune(fieldName[0])) && !unicode.IsDigit(rune(fieldName[0])) {
|
||||
fieldName = strings.ToUpper(fieldName)
|
||||
}
|
||||
|
||||
// Find the lsb/msb that is encoded in various ways.
|
||||
// Standards are great, that's why there are so many to choose from!
|
||||
var lsb, msb uint32
|
||||
if fieldEl.Lsb != nil && fieldEl.Msb != nil {
|
||||
// try to use lsb/msb tags
|
||||
lsb = *fieldEl.Lsb
|
||||
msb = *fieldEl.Msb
|
||||
} else if fieldEl.BitOffset != nil && fieldEl.BitWidth != nil {
|
||||
// try to use bitOffset/bitWidth tags
|
||||
lsb = *fieldEl.BitOffset
|
||||
msb = *fieldEl.BitWidth + lsb - 1
|
||||
} else if fieldEl.BitRange != nil {
|
||||
// try use bitRange
|
||||
// example string: "[20:16]"
|
||||
parts := strings.Split(strings.Trim(*fieldEl.BitRange, "[]"), ":")
|
||||
l, err := strconv.ParseUint(parts[1], 0, 32)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
lsb = uint32(l)
|
||||
m, err := strconv.ParseUint(parts[0], 0, 32)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
msb = uint32(m)
|
||||
} else {
|
||||
// this is an error. what to do?
|
||||
fmt.Fprintln(os.Stderr, "unable to find lsb/msb in field:", fieldName)
|
||||
continue
|
||||
}
|
||||
|
||||
fields = append(fields, Bitfield{
|
||||
name: fmt.Sprintf("%s_%s%s_%s_Pos", groupName, bitfieldPrefix, regName, fieldName),
|
||||
description: fmt.Sprintf("Position of %s field.", fieldName),
|
||||
value: lsb,
|
||||
})
|
||||
fields = append(fields, Bitfield{
|
||||
name: fmt.Sprintf("%s_%s%s_%s_Msk", groupName, bitfieldPrefix, regName, fieldName),
|
||||
description: fmt.Sprintf("Bit mask of %s field.", fieldName),
|
||||
value: (0xffffffff >> (31 - (msb - lsb))) << lsb,
|
||||
})
|
||||
if lsb == msb { // single bit
|
||||
fields = append(fields, Bitfield{
|
||||
name: fmt.Sprintf("%s_%s%s_%s", groupName, bitfieldPrefix, regName, fieldName),
|
||||
description: fmt.Sprintf("Bit %s.", fieldName),
|
||||
value: 1 << lsb,
|
||||
})
|
||||
}
|
||||
for _, enumEl := range fieldEl.EnumeratedValues {
|
||||
enumName := enumEl.Name
|
||||
if !unicode.IsUpper(rune(enumName[0])) && !unicode.IsDigit(rune(enumName[0])) {
|
||||
enumName = strings.ToUpper(enumName)
|
||||
}
|
||||
enumDescription := strings.Replace(enumEl.Description, "\n", " ", -1)
|
||||
enumValue, err := strconv.ParseUint(enumEl.Value, 0, 32)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fields = append(fields, Bitfield{
|
||||
name: fmt.Sprintf("%s_%s%s_%s_%s", groupName, bitfieldPrefix, regName, fieldName, enumName),
|
||||
description: enumDescription,
|
||||
value: uint32(enumValue),
|
||||
})
|
||||
}
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
type Register struct {
|
||||
element *SVDRegister
|
||||
baseAddress uint64
|
||||
}
|
||||
|
||||
func NewRegister(element *SVDRegister, baseAddress uint64) *Register {
|
||||
return &Register{
|
||||
element: element,
|
||||
baseAddress: baseAddress,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Register) name() string {
|
||||
return strings.Replace(r.element.Name, "[%s]", "", -1)
|
||||
}
|
||||
|
||||
func (r *Register) description() string {
|
||||
return strings.Replace(r.element.Description, "\n", " ", -1)
|
||||
}
|
||||
|
||||
func (r *Register) address() uint64 {
|
||||
offsetString := r.element.Offset
|
||||
if offsetString == nil {
|
||||
offsetString = r.element.AddressOffset
|
||||
}
|
||||
addr, err := strconv.ParseUint(*offsetString, 0, 32)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return r.baseAddress + addr
|
||||
}
|
||||
|
||||
func (r *Register) dim() int {
|
||||
if r.element.Dim == nil {
|
||||
return -1 // no dim elements
|
||||
}
|
||||
dim, err := strconv.ParseInt(*r.element.Dim, 0, 32)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return int(dim)
|
||||
}
|
||||
|
||||
func (r *Register) size() int {
|
||||
if r.element.Size != nil {
|
||||
size, err := strconv.ParseInt(*r.element.Size, 0, 32)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return int(size) / 8
|
||||
}
|
||||
return 4
|
||||
}
|
||||
|
||||
func parseRegister(groupName string, regEl *SVDRegister, baseAddress uint64, bitfieldPrefix string) []*PeripheralField {
|
||||
reg := NewRegister(regEl, baseAddress)
|
||||
|
||||
if reg.dim() != -1 {
|
||||
dimIncrement, err := strconv.ParseUint(regEl.DimIncrement, 0, 32)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if strings.Contains(reg.name(), "%s") {
|
||||
// a "spaced array" of registers, special processing required
|
||||
// we need to generate a separate register for each "element"
|
||||
var results []*PeripheralField
|
||||
for i := uint64(0); i < uint64(reg.dim()); i++ {
|
||||
regAddress := reg.address() + (i * dimIncrement)
|
||||
results = append(results, &PeripheralField{
|
||||
name: strings.Replace(reg.name(), "%s", strconv.FormatUint(i, 10), -1),
|
||||
address: regAddress,
|
||||
description: reg.description(),
|
||||
array: -1,
|
||||
elementSize: reg.size(),
|
||||
})
|
||||
}
|
||||
// set first result bitfield
|
||||
shortName := strings.ToUpper(strings.Replace(strings.Replace(reg.name(), "_%s", "", -1), "%s", "", -1))
|
||||
results[0].bitfields = parseBitfields(groupName, shortName, regEl.Fields, bitfieldPrefix)
|
||||
return results
|
||||
}
|
||||
}
|
||||
regName := reg.name()
|
||||
if !unicode.IsUpper(rune(regName[0])) && !unicode.IsDigit(rune(regName[0])) {
|
||||
regName = strings.ToUpper(regName)
|
||||
}
|
||||
|
||||
return []*PeripheralField{&PeripheralField{
|
||||
name: regName,
|
||||
address: reg.address(),
|
||||
description: reg.description(),
|
||||
bitfields: parseBitfields(groupName, regName, regEl.Fields, bitfieldPrefix),
|
||||
array: reg.dim(),
|
||||
elementSize: reg.size(),
|
||||
}}
|
||||
}
|
||||
|
||||
// The Go module for this device.
|
||||
func writeGo(outdir string, device *Device) error {
|
||||
outf, err := os.Create(filepath.Join(outdir, device.metadata["nameLower"]+".go"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outf.Close()
|
||||
w := bufio.NewWriter(outf)
|
||||
|
||||
maxInterruptValue := 0
|
||||
for _, intr := range device.interrupts {
|
||||
if intr.Value > maxInterruptValue {
|
||||
maxInterruptValue = intr.Value
|
||||
}
|
||||
}
|
||||
|
||||
t := template.Must(template.New("go").Parse(`// Automatically generated file. DO NOT EDIT.
|
||||
// Generated by gen-device-svd.go from {{.metadata.file}}, see {{.metadata.descriptorSource}}
|
||||
|
||||
// +build {{.pkgName}},{{.metadata.nameLower}}
|
||||
|
||||
// {{.metadata.description}}
|
||||
//
|
||||
{{.metadata.licenseBlock}}
|
||||
package {{.pkgName}}
|
||||
|
||||
import (
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Some information about this device.
|
||||
const (
|
||||
DEVICE = "{{.metadata.name}}"
|
||||
)
|
||||
|
||||
// Interrupt numbers
|
||||
const ({{range .interrupts}}
|
||||
IRQ_{{.Name}} = {{.Value}} // {{.Description}}{{end}}
|
||||
IRQ_max = {{.interruptMax}} // Highest interrupt number on this device.
|
||||
)
|
||||
|
||||
// Peripherals.
|
||||
var (
|
||||
{{range .peripherals}} {{.Name}} = (*{{.GroupName}}_Type)(unsafe.Pointer(uintptr(0x{{printf "%x" .BaseAddress}}))) // {{.Description}}
|
||||
{{end}})
|
||||
`))
|
||||
err = t.Execute(w, map[string]interface{}{
|
||||
"metadata": device.metadata,
|
||||
"interrupts": device.interrupts,
|
||||
"peripherals": device.peripherals,
|
||||
"pkgName": filepath.Base(strings.TrimRight(outdir, "/")),
|
||||
"interruptMax": maxInterruptValue,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Define peripheral struct types.
|
||||
for _, peripheral := range device.peripherals {
|
||||
if peripheral.registers == nil {
|
||||
// This peripheral was derived from another peripheral. No new type
|
||||
// needs to be defined for it.
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(w, "\n// %s\ntype %s_Type struct {\n", peripheral.Description, peripheral.GroupName)
|
||||
address := peripheral.BaseAddress
|
||||
for _, register := range peripheral.registers {
|
||||
if register.registers == nil && address > register.address {
|
||||
// In Nordic SVD files, these registers are deprecated or
|
||||
// duplicates, so can be ignored.
|
||||
//fmt.Fprintf(os.Stderr, "skip: %s.%s 0x%x - 0x%x %d\n", peripheral.Name, register.name, address, register.address, register.elementSize)
|
||||
continue
|
||||
}
|
||||
|
||||
var regType string
|
||||
switch register.elementSize {
|
||||
case 4:
|
||||
regType = "volatile.Register32"
|
||||
case 2:
|
||||
regType = "volatile.Register16"
|
||||
case 1:
|
||||
regType = "volatile.Register8"
|
||||
default:
|
||||
regType = "volatile.Register32"
|
||||
}
|
||||
|
||||
// insert padding, if needed
|
||||
if address < register.address {
|
||||
bytesNeeded := register.address - address
|
||||
if bytesNeeded == 1 {
|
||||
w.WriteString("\t_ byte\n")
|
||||
} else {
|
||||
fmt.Fprintf(w, "\t_ [%d]byte\n", bytesNeeded)
|
||||
}
|
||||
address = register.address
|
||||
}
|
||||
|
||||
lastCluster := false
|
||||
if register.registers != nil {
|
||||
// This is a cluster, not a register. Create the cluster type.
|
||||
regType = "struct {\n"
|
||||
subaddress := register.address
|
||||
for _, subregister := range register.registers {
|
||||
var subregType string
|
||||
switch subregister.elementSize {
|
||||
case 4:
|
||||
subregType = "volatile.Register32"
|
||||
case 2:
|
||||
subregType = "volatile.Register16"
|
||||
case 1:
|
||||
subregType = "volatile.Register8"
|
||||
}
|
||||
if subregType == "" {
|
||||
panic("unknown element size")
|
||||
}
|
||||
|
||||
if subregister.array != -1 {
|
||||
subregType = fmt.Sprintf("[%d]%s", subregister.array, subregType)
|
||||
}
|
||||
if subaddress != subregister.address {
|
||||
bytesNeeded := subregister.address - subaddress
|
||||
if bytesNeeded == 1 {
|
||||
regType += "\t\t_ byte\n"
|
||||
} else {
|
||||
regType += fmt.Sprintf("\t\t_ [%d]byte\n", bytesNeeded)
|
||||
}
|
||||
subaddress += bytesNeeded
|
||||
}
|
||||
var subregSize uint64
|
||||
if subregister.array != -1 {
|
||||
subregSize = uint64(subregister.array * subregister.elementSize)
|
||||
} else {
|
||||
subregSize = uint64(subregister.elementSize)
|
||||
}
|
||||
subaddress += subregSize
|
||||
regType += fmt.Sprintf("\t\t%s %s\n", subregister.name, subregType)
|
||||
}
|
||||
if register.array != -1 {
|
||||
if subaddress != register.address+uint64(register.elementSize) {
|
||||
bytesNeeded := (register.address + uint64(register.elementSize)) - subaddress
|
||||
if bytesNeeded == 1 {
|
||||
regType += "\t_ byte\n"
|
||||
} else {
|
||||
regType += fmt.Sprintf("\t_ [%d]byte\n", bytesNeeded)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lastCluster = true
|
||||
}
|
||||
regType += "\t}"
|
||||
address = subaddress
|
||||
}
|
||||
|
||||
if register.array != -1 {
|
||||
regType = fmt.Sprintf("[%d]%s", register.array, regType)
|
||||
}
|
||||
fmt.Fprintf(w, "\t%s %s\n", register.name, regType)
|
||||
|
||||
// next address
|
||||
if lastCluster {
|
||||
lastCluster = false
|
||||
} else if register.array != -1 {
|
||||
address = register.address + uint64(register.elementSize*register.array)
|
||||
} else {
|
||||
address = register.address + uint64(register.elementSize)
|
||||
}
|
||||
}
|
||||
w.WriteString("}\n")
|
||||
}
|
||||
|
||||
// Define bitfields.
|
||||
for _, peripheral := range device.peripherals {
|
||||
if peripheral.registers == nil {
|
||||
// This peripheral was derived from another peripheral. Bitfields are
|
||||
// already defined.
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(w, "\n// Bitfields for %s: %s\nconst(", peripheral.Name, peripheral.Description)
|
||||
for _, register := range peripheral.registers {
|
||||
if len(register.bitfields) != 0 {
|
||||
writeGoRegisterBitfields(w, register, register.name)
|
||||
}
|
||||
if register.registers == nil {
|
||||
continue
|
||||
}
|
||||
for _, subregister := range register.registers {
|
||||
writeGoRegisterBitfields(w, subregister, register.name+"."+subregister.name)
|
||||
}
|
||||
}
|
||||
w.WriteString(")\n")
|
||||
}
|
||||
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func writeGoRegisterBitfields(w *bufio.Writer, register *PeripheralField, name string) {
|
||||
w.WriteString("\n\t// " + name)
|
||||
if register.description != "" {
|
||||
w.WriteString(": " + register.description)
|
||||
}
|
||||
w.WriteByte('\n')
|
||||
for _, bitfield := range register.bitfields {
|
||||
fmt.Fprintf(w, "\t%s = 0x%x", bitfield.name, bitfield.value)
|
||||
if bitfield.description != "" {
|
||||
w.WriteString(" // " + bitfield.description)
|
||||
}
|
||||
w.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
// The interrupt vector, which is hard to write directly in Go.
|
||||
func writeAsm(outdir string, device *Device) error {
|
||||
outf, err := os.Create(filepath.Join(outdir, device.metadata["nameLower"]+".s"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outf.Close()
|
||||
w := bufio.NewWriter(outf)
|
||||
|
||||
t := template.Must(template.New("go").Parse(`// Automatically generated file. DO NOT EDIT.
|
||||
// Generated by gen-device-svd.go from {{.file}}, see {{.descriptorSource}}
|
||||
|
||||
// {{.description}}
|
||||
//
|
||||
{{.licenseBlock}}
|
||||
|
||||
.syntax unified
|
||||
|
||||
// This is the default handler for interrupts, if triggered but not defined.
|
||||
.section .text.Default_Handler
|
||||
.global Default_Handler
|
||||
.type Default_Handler, %function
|
||||
Default_Handler:
|
||||
wfe
|
||||
b Default_Handler
|
||||
|
||||
// Avoid the need for repeated .weak and .set instructions.
|
||||
.macro IRQ handler
|
||||
.weak \handler
|
||||
.set \handler, Default_Handler
|
||||
.endm
|
||||
|
||||
// Must set the "a" flag on the section:
|
||||
// https://svnweb.freebsd.org/base/stable/11/sys/arm/arm/locore-v4.S?r1=321049&r2=321048&pathrev=321049
|
||||
// https://sourceware.org/binutils/docs/as/Section.html#ELF-Version
|
||||
.section .isr_vector, "a", %progbits
|
||||
.global __isr_vector
|
||||
// Interrupt vector as defined by Cortex-M, starting with the stack top.
|
||||
// On reset, SP is initialized with *0x0 and PC is loaded with *0x4, loading
|
||||
// _stack_top and Reset_Handler.
|
||||
.long _stack_top
|
||||
.long Reset_Handler
|
||||
.long NMI_Handler
|
||||
.long HardFault_Handler
|
||||
.long MemoryManagement_Handler
|
||||
.long BusFault_Handler
|
||||
.long UsageFault_Handler
|
||||
.long 0
|
||||
.long 0
|
||||
.long 0
|
||||
.long 0
|
||||
.long SVC_Handler
|
||||
.long DebugMon_Handler
|
||||
.long 0
|
||||
.long PendSV_Handler
|
||||
.long SysTick_Handler
|
||||
|
||||
// Extra interrupts for peripherals defined by the hardware vendor.
|
||||
`))
|
||||
err = t.Execute(w, device.metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
num := 0
|
||||
for _, intr := range device.interrupts {
|
||||
if intr.Value == num-1 {
|
||||
continue
|
||||
}
|
||||
if intr.Value < num {
|
||||
panic("interrupt numbers are not sorted")
|
||||
}
|
||||
for intr.Value > num {
|
||||
w.WriteString(" .long 0\n")
|
||||
num++
|
||||
}
|
||||
num++
|
||||
fmt.Fprintf(w, " .long %s_IRQHandler\n", intr.Name)
|
||||
}
|
||||
|
||||
w.WriteString(`
|
||||
// Define default implementations for interrupts, redirecting to
|
||||
// Default_Handler when not implemented.
|
||||
IRQ NMI_Handler
|
||||
IRQ HardFault_Handler
|
||||
IRQ MemoryManagement_Handler
|
||||
IRQ BusFault_Handler
|
||||
IRQ UsageFault_Handler
|
||||
IRQ SVC_Handler
|
||||
IRQ DebugMon_Handler
|
||||
IRQ PendSV_Handler
|
||||
IRQ SysTick_Handler
|
||||
`)
|
||||
for _, intr := range device.interrupts {
|
||||
fmt.Fprintf(w, " IRQ %s_IRQHandler\n", intr.Name)
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func generate(indir, outdir, sourceURL string) error {
|
||||
if _, err := os.Stat(indir); os.IsNotExist(err) {
|
||||
fmt.Fprintln(os.Stderr, "cannot find input directory:", indir)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.MkdirAll(outdir, 0777)
|
||||
|
||||
infiles, err := filepath.Glob(filepath.Join(indir, "*.svd"))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "could not read .svd files:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
sort.Strings(infiles)
|
||||
for _, infile := range infiles {
|
||||
fmt.Println(infile)
|
||||
device, err := readSVD(infile, sourceURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read: %w", err)
|
||||
}
|
||||
err = writeGo(outdir, device)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write Go file: %w", err)
|
||||
}
|
||||
err = writeAsm(outdir, device)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write assembly file: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
sourceURL := flag.String("source", "<unknown>", "source SVD file")
|
||||
flag.Parse()
|
||||
if flag.NArg() != 2 {
|
||||
fmt.Fprintln(os.Stderr, "provide exactly two arguments: input directory (with .svd files) and output directory for generated files")
|
||||
flag.PrintDefaults()
|
||||
return
|
||||
}
|
||||
indir := flag.Arg(0)
|
||||
outdir := flag.Arg(1)
|
||||
err := generate(indir, outdir, *sourceURL)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package compiler
|
||||
package transform
|
||||
|
||||
// This file lowers func values into their final form. This is necessary for
|
||||
// funcValueSwitch, which needs full program analysis.
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
@@ -43,17 +44,17 @@ func (l funcWithUsesList) Swap(i, j int) {
|
||||
l[i], l[j] = l[j], l[i]
|
||||
}
|
||||
|
||||
// LowerFuncValue lowers the runtime.funcValueWithSignature type and
|
||||
// LowerFuncValues lowers the runtime.funcValueWithSignature type and
|
||||
// runtime.getFuncPtr function to their final form.
|
||||
func (c *Compiler) LowerFuncValues() {
|
||||
if c.funcImplementation() != funcValueSwitch {
|
||||
return
|
||||
}
|
||||
func LowerFuncValues(mod llvm.Module) {
|
||||
ctx := mod.Context()
|
||||
builder := ctx.NewBuilder()
|
||||
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
|
||||
|
||||
// Find all func values used in the program with their signatures.
|
||||
funcValueWithSignaturePtr := llvm.PointerType(c.getLLVMRuntimeType("funcValueWithSignature"), 0)
|
||||
funcValueWithSignaturePtr := llvm.PointerType(mod.GetTypeByName("runtime.funcValueWithSignature"), 0)
|
||||
signatures := map[string]*funcSignatureInfo{}
|
||||
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||
for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||
if global.Type() != funcValueWithSignaturePtr {
|
||||
continue
|
||||
}
|
||||
@@ -107,7 +108,7 @@ func (c *Compiler) LowerFuncValues() {
|
||||
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
|
||||
panic("expected const ptrtoint")
|
||||
}
|
||||
use.ReplaceAllUsesWith(llvm.ConstInt(c.uintptrType, uint64(fn.id), false))
|
||||
use.ReplaceAllUsesWith(llvm.ConstInt(uintptrType, uint64(fn.id), false))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,11 +140,11 @@ func (c *Compiler) LowerFuncValues() {
|
||||
// There is exactly one function with this signature that is
|
||||
// used in a func value. The func value itself can be either nil
|
||||
// or this one function.
|
||||
c.builder.SetInsertPointBefore(getFuncPtrCall)
|
||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
||||
isnil := c.builder.CreateICmp(llvm.IntEQ, funcID, zero, "")
|
||||
builder.SetInsertPointBefore(getFuncPtrCall)
|
||||
zero := llvm.ConstInt(uintptrType, 0, false)
|
||||
isnil := builder.CreateICmp(llvm.IntEQ, funcID, zero, "")
|
||||
funcPtrNil := llvm.ConstPointerNull(functions[0].funcPtr.Type())
|
||||
funcPtr := c.builder.CreateSelect(isnil, funcPtrNil, functions[0].funcPtr, "")
|
||||
funcPtr := builder.CreateSelect(isnil, funcPtrNil, functions[0].funcPtr, "")
|
||||
for _, inttoptr := range getUses(getFuncPtrCall) {
|
||||
if inttoptr.IsAIntToPtrInst().IsNil() {
|
||||
panic("expected inttoptr")
|
||||
@@ -181,15 +182,23 @@ func (c *Compiler) LowerFuncValues() {
|
||||
// to replace.
|
||||
for _, callIntPtr := range getUses(getFuncPtrCall) {
|
||||
if !callIntPtr.IsACallInst().IsNil() && callIntPtr.CalledValue().Name() == "runtime.makeGoroutine" {
|
||||
// Special case for runtime.makeGoroutine.
|
||||
for _, inttoptr := range getUses(callIntPtr) {
|
||||
if inttoptr.IsAIntToPtrInst().IsNil() {
|
||||
panic("expected a inttoptr")
|
||||
}
|
||||
for _, use := range getUses(inttoptr) {
|
||||
c.addFuncLoweringSwitch(funcID, use, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
||||
addFuncLoweringSwitch(mod, builder, funcID, use, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
||||
// The function lowering switch code passes in a parent handle value.
|
||||
// Strip the parent handle off here because it is irrelevant to goroutine starts.
|
||||
return c.emitStartGoroutine(funcPtr, params[:len(params)-1])
|
||||
// Set the parent handle to null here because it is irrelevant to goroutine starts.
|
||||
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||
params[len(params)-1] = llvm.ConstPointerNull(i8ptrType)
|
||||
calleeValue := builder.CreatePtrToInt(funcPtr, uintptrType, "")
|
||||
makeGoroutine := mod.NamedFunction("runtime.makeGoroutine")
|
||||
calleeValue = builder.CreateCall(makeGoroutine, []llvm.Value{calleeValue, llvm.Undef(i8ptrType), llvm.ConstNull(i8ptrType)}, "")
|
||||
calleeValue = builder.CreateIntToPtr(calleeValue, funcPtr.Type(), "")
|
||||
builder.CreateCall(calleeValue, params, "")
|
||||
return llvm.Value{} // void so no return value
|
||||
}, functions)
|
||||
use.EraseFromParentAsInstruction()
|
||||
}
|
||||
@@ -209,15 +218,15 @@ func (c *Compiler) LowerFuncValues() {
|
||||
}
|
||||
switch bitcastUse.CalledValue().Name() {
|
||||
case "runtime.isnil":
|
||||
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(c.ctx.Int1Type(), 0, false))
|
||||
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(ctx.Int1Type(), 0, false))
|
||||
bitcastUse.EraseFromParentAsInstruction()
|
||||
default:
|
||||
panic("expected a call to runtime.isnil")
|
||||
}
|
||||
}
|
||||
} 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, "")
|
||||
addFuncLoweringSwitch(mod, builder, funcID, ptrUse, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
||||
return builder.CreateCall(funcPtr, params, "")
|
||||
}, functions)
|
||||
} else {
|
||||
panic("unexpected getFuncPtrCall")
|
||||
@@ -236,30 +245,35 @@ func (c *Compiler) LowerFuncValues() {
|
||||
// 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
|
||||
// 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) {
|
||||
func addFuncLoweringSwitch(mod llvm.Module, builder llvm.Builder, funcID, call llvm.Value, createCall func(funcPtr llvm.Value, params []llvm.Value) llvm.Value, functions funcWithUsesList) {
|
||||
ctx := mod.Context()
|
||||
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
|
||||
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||
|
||||
// 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()
|
||||
builder.SetInsertPointBefore(call)
|
||||
defaultBlock := ctx.AddBasicBlock(call.InstructionParent().Parent(), "func.default")
|
||||
builder.SetInsertPointAtEnd(defaultBlock)
|
||||
builder.CreateUnreachable()
|
||||
|
||||
// Create the switch.
|
||||
c.builder.SetInsertPointBefore(call)
|
||||
sw := c.builder.CreateSwitch(funcID, defaultBlock, len(functions)+1)
|
||||
builder.SetInsertPointBefore(call)
|
||||
sw := 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")
|
||||
nextBlock := llvmutil.SplitBasicBlock(builder, 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)
|
||||
nilBlock := ctx.InsertBasicBlock(nextBlock, "func.nil")
|
||||
builder.SetInsertPointAtEnd(nilBlock)
|
||||
nilPanic := mod.NamedFunction("runtime.nilPanic")
|
||||
builder.CreateCall(nilPanic, []llvm.Value{llvm.Undef(i8ptrType), llvm.ConstNull(i8ptrType)}, "")
|
||||
builder.CreateUnreachable()
|
||||
sw.AddCase(llvm.ConstInt(uintptrType, 0, false), nilBlock)
|
||||
|
||||
// Gather the list of parameters for every call we're going to make.
|
||||
callParams := make([]llvm.Value, call.OperandsCount()-1)
|
||||
@@ -273,11 +287,11 @@ func (c *Compiler) addFuncLoweringSwitch(funcID, call llvm.Value, createCall fun
|
||||
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)
|
||||
bb := ctx.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
|
||||
builder.SetInsertPointAtEnd(bb)
|
||||
result := createCall(fn.funcPtr, callParams)
|
||||
c.builder.CreateBr(nextBlock)
|
||||
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(fn.id), false), bb)
|
||||
builder.CreateBr(nextBlock)
|
||||
sw.AddCase(llvm.ConstInt(uintptrType, uint64(fn.id), false), bb)
|
||||
phiBlocks[i] = bb
|
||||
phiValues[i] = result
|
||||
}
|
||||
@@ -285,8 +299,8 @@ func (c *Compiler) addFuncLoweringSwitch(funcID, call llvm.Value, createCall fun
|
||||
// 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(), "")
|
||||
builder.SetInsertPointBefore(nextBlock.FirstInstruction())
|
||||
phi := builder.CreatePHI(call.Type(), "")
|
||||
phi.AddIncoming(phiValues, phiBlocks)
|
||||
call.ReplaceAllUsesWith(phi)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package transform
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFuncLowering(t *testing.T) {
|
||||
t.Parallel()
|
||||
testTransform(t, "testdata/func-lowering", LowerFuncValues)
|
||||
}
|
||||
@@ -160,21 +160,25 @@ func assignTypeCodes(mod llvm.Module, typeSlice typeInfoSlice) {
|
||||
global := replaceGlobalIntWithArray(mod, "reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetUnnamedAddr(true)
|
||||
global.SetGlobalConstant(true)
|
||||
}
|
||||
if state.needsArrayTypesSidetable {
|
||||
global := replaceGlobalIntWithArray(mod, "reflect.arrayTypesSidetable", state.arrayTypesSidetable)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetUnnamedAddr(true)
|
||||
global.SetGlobalConstant(true)
|
||||
}
|
||||
if state.needsStructTypesSidetable {
|
||||
global := replaceGlobalIntWithArray(mod, "reflect.structTypesSidetable", state.structTypesSidetable)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetUnnamedAddr(true)
|
||||
global.SetGlobalConstant(true)
|
||||
}
|
||||
if state.needsStructNamesSidetable {
|
||||
global := replaceGlobalIntWithArray(mod, "reflect.structNamesSidetable", state.structNamesSidetable)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetUnnamedAddr(true)
|
||||
global.SetGlobalConstant(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+83
@@ -0,0 +1,83 @@
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32-unknown-unknown-wasm"
|
||||
|
||||
%runtime.typecodeID = type { %runtime.typecodeID*, i32 }
|
||||
%runtime.funcValueWithSignature = type { i32, %runtime.typecodeID* }
|
||||
|
||||
@"reflect/types.type:func:{basic:int8}{}" = external constant %runtime.typecodeID
|
||||
@"reflect/types.type:func:{basic:uint8}{}" = external constant %runtime.typecodeID
|
||||
@"reflect/types.type:func:{basic:int}{}" = external constant %runtime.typecodeID
|
||||
@"funcInt8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @funcInt8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:int8}{}" }
|
||||
@"func1Uint8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @func1Uint8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:uint8}{}" }
|
||||
@"func2Uint8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @func2Uint8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:uint8}{}" }
|
||||
@"main$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main$1" to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:int}{}" }
|
||||
@"main$2$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main$2" to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:int}{}" }
|
||||
|
||||
declare i32 @runtime.getFuncPtr(i8*, i32, %runtime.typecodeID*, i8*, i8*)
|
||||
|
||||
declare i32 @runtime.makeGoroutine(i32, i8*, i8*)
|
||||
|
||||
declare void @runtime.nilPanic(i8*, i8*)
|
||||
|
||||
declare i1 @runtime.isnil(i8*, i8*, i8*)
|
||||
|
||||
declare void @"main$1"(i32, i8*, i8*)
|
||||
|
||||
declare void @"main$2"(i32, i8*, i8*)
|
||||
|
||||
declare void @funcInt8(i8, i8*, i8*)
|
||||
|
||||
declare void @func1Uint8(i8, i8*, i8*)
|
||||
|
||||
declare void @func2Uint8(i8, i8*, i8*)
|
||||
|
||||
; Call a function of which only one function with this signature is used as a
|
||||
; function value. This means that lowering it to IR is trivial: simply check
|
||||
; whether the func value is nil, and if not, call that one function directly.
|
||||
define void @runFunc1(i8*, i32, i8, i8* %context, i8* %parentHandle) {
|
||||
entry:
|
||||
%3 = call i32 @runtime.getFuncPtr(i8* %0, i32 %1, %runtime.typecodeID* @"reflect/types.type:func:{basic:int8}{}", i8* undef, i8* null)
|
||||
%4 = inttoptr i32 %3 to void (i8, i8*, i8*)*
|
||||
%5 = bitcast void (i8, i8*, i8*)* %4 to i8*
|
||||
%6 = call i1 @runtime.isnil(i8* %5, i8* undef, i8* null)
|
||||
br i1 %6, label %fpcall.nil, label %fpcall.next
|
||||
|
||||
fpcall.nil:
|
||||
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
fpcall.next:
|
||||
call void %4(i8 %2, i8* %0, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
; There are two functions with this signature used in a func value. That means
|
||||
; that we'll have to check at runtime which of the two it is (or whether the
|
||||
; func value is nil). This call will thus be lowered to a switch statement.
|
||||
define void @runFunc2(i8*, i32, i8, i8* %context, i8* %parentHandle) {
|
||||
entry:
|
||||
%3 = call i32 @runtime.getFuncPtr(i8* %0, i32 %1, %runtime.typecodeID* @"reflect/types.type:func:{basic:uint8}{}", i8* undef, i8* null)
|
||||
%4 = inttoptr i32 %3 to void (i8, i8*, i8*)*
|
||||
%5 = bitcast void (i8, i8*, i8*)* %4 to i8*
|
||||
%6 = call i1 @runtime.isnil(i8* %5, i8* undef, i8* null)
|
||||
br i1 %6, label %fpcall.nil, label %fpcall.next
|
||||
|
||||
fpcall.nil:
|
||||
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
fpcall.next:
|
||||
call void %4(i8 %2, i8* %0, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Special case for runtime.makeGoroutine.
|
||||
define void @sleepFuncValue(i8*, i32, i8* nocapture readnone %context, i8* nocapture readnone %parentHandle) {
|
||||
entry:
|
||||
%2 = call i32 @runtime.getFuncPtr(i8* %0, i32 %1, %runtime.typecodeID* @"reflect/types.type:func:{basic:int}{}", i8* undef, i8* null)
|
||||
%3 = call i32 @runtime.makeGoroutine(i32 %2, i8* undef, i8* null)
|
||||
%4 = inttoptr i32 %3 to void (i32, i8*, i8*)*
|
||||
call void %4(i32 8, i8* %0, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32-unknown-unknown-wasm"
|
||||
|
||||
%runtime.typecodeID = type { %runtime.typecodeID*, i32 }
|
||||
%runtime.funcValueWithSignature = type { i32, %runtime.typecodeID* }
|
||||
|
||||
@"reflect/types.type:func:{basic:int8}{}" = external constant %runtime.typecodeID
|
||||
@"reflect/types.type:func:{basic:uint8}{}" = external constant %runtime.typecodeID
|
||||
@"reflect/types.type:func:{basic:int}{}" = external constant %runtime.typecodeID
|
||||
@"funcInt8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @funcInt8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:int8}{}" }
|
||||
@"func1Uint8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @func1Uint8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:uint8}{}" }
|
||||
@"func2Uint8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @func2Uint8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:uint8}{}" }
|
||||
@"main$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main$1" to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:int}{}" }
|
||||
@"main$2$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main$2" to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:int}{}" }
|
||||
|
||||
declare i32 @runtime.getFuncPtr(i8*, i32, %runtime.typecodeID*, i8*, i8*)
|
||||
|
||||
declare i32 @runtime.makeGoroutine(i32, i8*, i8*)
|
||||
|
||||
declare void @runtime.nilPanic(i8*, i8*)
|
||||
|
||||
declare i1 @runtime.isnil(i8*, i8*, i8*)
|
||||
|
||||
declare void @"main$1"(i32, i8*, i8*)
|
||||
|
||||
declare void @"main$2"(i32, i8*, i8*)
|
||||
|
||||
declare void @funcInt8(i8, i8*, i8*)
|
||||
|
||||
declare void @func1Uint8(i8, i8*, i8*)
|
||||
|
||||
declare void @func2Uint8(i8, i8*, i8*)
|
||||
|
||||
; Call a function of which only one function with this signature is used as a
|
||||
; function value. This means that lowering it to IR is trivial: simply check
|
||||
; whether the func value is nil, and if not, call that one function directly.
|
||||
define void @runFunc1(i8*, i32, i8, i8* %context, i8* %parentHandle) {
|
||||
entry:
|
||||
%3 = icmp eq i32 %1, 0
|
||||
%4 = select i1 %3, void (i8, i8*, i8*)* null, void (i8, i8*, i8*)* @funcInt8
|
||||
%5 = bitcast void (i8, i8*, i8*)* %4 to i8*
|
||||
%6 = call i1 @runtime.isnil(i8* %5, i8* undef, i8* null)
|
||||
br i1 %6, label %fpcall.nil, label %fpcall.next
|
||||
|
||||
fpcall.nil:
|
||||
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
fpcall.next:
|
||||
call void %4(i8 %2, i8* %0, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
; There are two functions with this signature used in a func value. That means
|
||||
; that we'll have to check at runtime which of the two it is (or whether the
|
||||
; func value is nil). This call will thus be lowered to a switch statement.
|
||||
define void @runFunc2(i8*, i32, i8, i8* %context, i8* %parentHandle) {
|
||||
entry:
|
||||
br i1 false, label %fpcall.nil, label %fpcall.next
|
||||
|
||||
fpcall.nil:
|
||||
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
fpcall.next:
|
||||
switch i32 %1, label %func.default [
|
||||
i32 0, label %func.nil
|
||||
i32 1, label %func.call1
|
||||
i32 2, label %func.call2
|
||||
]
|
||||
|
||||
func.nil:
|
||||
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
func.call1:
|
||||
call void @func1Uint8(i8 %2, i8* %0, i8* undef)
|
||||
br label %func.next
|
||||
|
||||
func.call2:
|
||||
call void @func2Uint8(i8 %2, i8* %0, i8* undef)
|
||||
br label %func.next
|
||||
|
||||
func.next:
|
||||
ret void
|
||||
|
||||
func.default:
|
||||
unreachable
|
||||
}
|
||||
|
||||
; Special case for runtime.makeGoroutine.
|
||||
define void @sleepFuncValue(i8*, i32, i8* nocapture readnone %context, i8* nocapture readnone %parentHandle) {
|
||||
entry:
|
||||
switch i32 %1, label %func.default [
|
||||
i32 0, label %func.nil
|
||||
i32 1, label %func.call1
|
||||
i32 2, label %func.call2
|
||||
]
|
||||
|
||||
func.nil:
|
||||
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
func.call1:
|
||||
%2 = call i32 @runtime.makeGoroutine(i32 ptrtoint (void (i32, i8*, i8*)* @"main$1" to i32), i8* undef, i8* null)
|
||||
%3 = inttoptr i32 %2 to void (i32, i8*, i8*)*
|
||||
call void %3(i32 8, i8* %0, i8* null)
|
||||
br label %func.next
|
||||
|
||||
func.call2:
|
||||
%4 = call i32 @runtime.makeGoroutine(i32 ptrtoint (void (i32, i8*, i8*)* @"main$2" to i32), i8* undef, i8* null)
|
||||
%5 = inttoptr i32 %4 to void (i32, i8*, i8*)*
|
||||
call void %5(i32 8, i8* %0, i8* null)
|
||||
br label %func.next
|
||||
|
||||
func.next:
|
||||
ret void
|
||||
|
||||
func.default:
|
||||
unreachable
|
||||
}
|
||||
+1
-1
@@ -2,4 +2,4 @@ package main
|
||||
|
||||
// version of this package.
|
||||
// Update this value before release of new version of software.
|
||||
const version = "0.10.0"
|
||||
const version = "0.11.0"
|
||||
|
||||
Reference in New Issue
Block a user