Compare commits

...

41 Commits

Author SHA1 Message Date
Ayke van Laethem 65a059a973 samd51: add support for async SPI using DMA 2023-11-05 19:35:00 +01:00
Ayke van Laethem cb95259adf rp2040: add SPI DMA support 2023-11-04 16:18:45 +01:00
Ayke van Laethem 24d718fa05 machine: add dummy async SPI methods
This prepares all devices for actual async SPI support.
2023-11-04 16:14:47 +01:00
Randy Reddig 174d492355 compileopts, targets, main: support Wasmtime v14 (#3972)
compileopts, targets, main: support Wasmtime v14
2023-11-02 19:49:52 +01:00
Christian Ege 5355473dce doc: fix a typo in the rtcinterrupt example (#3981)
docs: fix a typo in the rtcinterrupt example, also provide a link to the interrupt online doc
2023-11-02 18:13:22 +01:00
sago35 a531ed614a main, compileopts: move GetTargetSpecs() to compileopts package 2023-11-02 15:37:43 +01:00
sago35 24ae6fdf29 main: add -info option to tinygo monitor 2023-11-02 15:37:43 +01:00
deadprogram 938ce22307 machine/stm32: implement DeviceID() with unique ID per processor
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-10-27 17:44:53 +02:00
Kenneth Bell 9fb5a5b9a4 nrf,sam,rp2040: add machine.HardwareID function 2023-10-27 13:25:32 +02:00
deadprogram 9fd9d9c05a compileopts: add cflag '-isystem' for resource directory search since needed for Xtensa
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-10-25 08:57:39 +02:00
deadprogram fd50227a3d build: pin wasmtime version used for testing to v13.0.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-10-24 13:10:09 +02:00
Kenneth Bell a90295430c ci: work-around for broken links in github runners 2023-10-23 21:25:41 +02:00
deadprogram fa4ca63ff2 machine/spi: use interface to ensure uniformity for all machine implementations
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-10-17 13:41:32 +02:00
Flavio Castelli 7019c4e8fc Binaryen116 (#3958)
dependencies: update binaryen submodule to version 116

Signed-off-by: Flavio Castelli <fcastelli@suse.com>
Co-authored-by: DarkByteBen <ben@darkbytelabs.com>
2023-10-16 18:34:20 +02:00
deadprogram b79e0e8528 docs: add Nix badge for builds to README
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-10-15 21:02:25 +02:00
Ayke van Laethem 51bed3afae nix: fix md5sum on MacOS
The default on MacOS is `md5`, while Nix only has `md5sum` available.
Therefore, make it possible to override the variable via the environment
so that flake.nix can set the correct binary name.
2023-10-15 17:51:13 +02:00
Ayke van Laethem 4d4ccddad8 nix: support make wasi-libc on MacOS 2023-10-15 17:51:13 +02:00
Ayke van Laethem f55f5315cc builder: generalize build ID fallback to darwin
This is to support NixOS, who have added -no_uuid to the linker.
Upstream bug report: https://github.com/NixOS/nixpkgs/issues/178366
2023-10-15 17:51:13 +02:00
Ayke van Laethem 7468a00ef4 all: fix a small incompatibility with Nix
Hopefully this won't break anybody: while all tests still pass, there
could in theory be systems where not supplying those libraries leads to
linker errors.
2023-10-15 17:51:13 +02:00
ginglis13 8d77278c6b refactor: rm io/ioutil funcs
io/ioutil has been deprecated since Go 1.16
https://pkg.go.dev/io/ioutil

Signed-off-by: ginglis13 <ginglis05@gmail.com>
2023-10-15 12:12:07 +02:00
Ayke van Laethem dde9b5ad3a goenv: re-add support for Clang headers on darwin
When TinyGo is installed using `go install` or `go build`, it uses the
Clang resource directory from the host. This works for Linux, but
doesn't work anymore on macOS with a recent change I made.

This re-adds support for Darwin in that case (with much, much simpler
code than there used to be).
2023-10-15 10:51:06 +02:00
Ayke van Laethem 2d4307647e nix: improve docs and add support for wasi-libc
I forgot a few things in the flake file, but now everything should be
included.
2023-10-14 17:32:56 +02:00
Ron Evans 935a293106 machine/i2c: add interface check and implementation where missing for SetBaudRate() (#3406)
* machine/i2c: add interface check and placeholder implementation where missing for SetBaudRate()
2023-10-14 13:17:24 +02:00
Ayke van Laethem 72b715fa99 all: add Nix flake file
This adds a flake.nix file that makes it possible to quickly create a
development environment.

You can download Nix here, for use on your Linux or macOS system:
https://nixos.org/download.html

After you have installed Nix, you can enter the development environment
as follows:

    nix develop

This drops you into a bash shell, where you can install TinyGo simply
using the following command:

    go install

That's all! Assuming you've set up your $PATH correctly, you can now use
the tinygo command as usual:

    tinygo version

You can also do many other things from this environment. Building and
flashing should work as you're used to: it's not a VM or container so
there are no access restrictions.
2023-10-14 11:35:26 +02:00
Ayke van Laethem d801d0cd53 builder: refactor clang include headers
Set -resource-dir in a central place instead of passing the header path
around everywhere and adding it using the `-I` flag. I believe this is
closer to how Clang is intended to be used.

This change was inspired by my attempt to add a Nix flake file to
TinyGo.
2023-10-14 11:35:26 +02:00
Anuraag Agrawal c2f1965e03 Fix bitstring order in precise GC docs (#3947)
docs: Fix bitstring order in precise GC docs
2023-10-13 08:57:30 +02:00
sago35 75bba42b60 goenv: update to new v0.31.0 development version 2023-10-11 02:24:58 +02:00
Ayke van Laethem 18b50db0dc all: add initial LLVM 17 support
This allows us to test and use LLVM 17, now that it is available in
Homebrew.

Full support for LLVM 17 (including using it by default) will have to
wait until Espressif rebases their Xtensa fork of LLVM.
2023-10-06 09:05:07 +02:00
Ayke van Laethem 499fce9cee avr: don't compile large parts of picolibc (math, stdio)
These parts aren't critical and lead to crashes on small chips without
long jumps (like the attiny85) with LLVM 17. (Older LLVM versions would
emit long jumps regardless, even if the chip didn't support those).

For more information, see: https://github.com/llvm/llvm-project/issues/67042
2023-10-06 09:05:07 +02:00
deadprogram 88b29589d6 targets: increase default stack size to 64k for wasi/wasm targets
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-10-04 22:43:14 +02:00
Ayke van Laethem 8cbfbcae5a build: avoid sharing GlobalValues between build instances
This happens with `tinygo test` for example when testing multiple
packages at the same time. I found this because the compiler crashed in
`make tinygo-test-fast`:

    fatal error: concurrent map writes
    fatal error: concurrent map read and map write

    goroutine 15 [running]:
    github.com/tinygo-org/tinygo/builder.Build({0x40002d0be0, 0xa}, {0x0, 0x0}, {0x4000398048, 0x14}, 0x40003b0000)
            /home/ayke/src/tinygo/tinygo/builder/build.go:131 +0x388
    main.buildAndRun({0x40002d0be0, 0xa}, 0x40003b0000, {0x8bc178, 0x40003a4090}, {0x0, 0x0, 0x0}, {0x0, 0x0, ...}, ...)
            /home/ayke/src/tinygo/tinygo/main.go:856 +0x45c
    main.Test({0x40002d0be0, 0xa}, {0x8bc118, 0x40000b3a08}, {0x0?, 0x0?}, 0x40001e6000, {0x0, 0x0})
            /home/ayke/src/tinygo/tinygo/main.go:270 +0x758
    main.main.func3()
            /home/ayke/src/tinygo/tinygo/main.go:1718 +0xe4
    created by main.main in goroutine 1
            /home/ayke/src/tinygo/tinygo/main.go:1712 +0x34ec

My solution is essentially to copy the map over instead of modifying it
directly.

I've also moved the code up a little, because I think that's a more
sensible place (out of the way of the whole package compile logic).
2023-10-04 16:20:32 +02:00
Ayke van Laethem 5cd8ba2421 all: refactor goenv.Version to add the git sha1 if needed
Previously all (except one!) usage of goenv.Version manually added the
git sha1 hash, leading to duplicate code. I've moved this to do it all
in one place, to avoid this duplication.
2023-10-04 16:20:32 +02:00
Ayke van Laethem 2320b18953 ci: use matrix instead of duplicating the Linux cross job
This reduces a lot of copy-pasted code (that was in fact different in a
small way!)
2023-10-04 14:21:43 +02:00
Ayke van Laethem 3b1913ac57 all: use the new LLVM pass manager
The old LLVM pass manager is deprecated and should not be used anymore.
Moreover, the pass manager builder (which we used to set up a pass
pipeline) is actually removed from LLVM entirely in LLVM 17:
https://reviews.llvm.org/D145387
https://reviews.llvm.org/D145835

The new pass manager does change the binary size in many cases: both
growing and shrinking it. However, on average the binary size remains
more or less the same.

This is needed as a preparation for LLVM 17.
2023-10-04 13:05:58 +02:00
Ayke van Laethem 1da1abe314 all: remove LLVM 14 support
This is a big change: apart from removing LLVM 14 it also removes typed
pointer support (which was only fully supported in LLVM up to version
14). This removes about 200 lines of code, but more importantly removes
a ton of special cases for LLVM 14.
2023-10-01 18:32:15 +02:00
ayan george c9721197d5 bulid: Rename Makefile to GNUmakefile
Prior to this commit, the build instructions were encoded in Makefiles
that contained GNU extensions that are unique to GNU Make and
incompatible with older implementations.

Thie commit renames all Makefiles to GNUmakefile which clearly denotes
that the file contains GNU extensions.

GNU Make actually first looks for a GNUmakefile, then makefile, THEN
Makefile so in addition to simply being more correct and portable, it
saves a few unnecessary failed attempts to open the correct build
file.
2023-10-01 14:55:34 +02:00
ayan george 738747bd05 build: use $(MAKE) to represent make executable
Prior to this commit, our Makefiles assumed the name of the make program
was simply "make".

Since we use GNU make extensions, this isn't always the case --
operating systems like FreeBSD come with their own implementation named
make which can be incompatible with the extensions used in by tinygo.

To run a compatible make on some of these systems, we have explicitly
call GNU make by running gmake.

This commit changes references to the command "make" to "$(MAKE)" which
is a variable that contains the name of the executable invoked to
process the Makefile.

This allow the Makefiles to be uniformly processed by the same make
program.
2023-10-01 14:55:34 +02:00
ayan george 0bb4a8abeb docs: Update BUILDING.md
Indicate that GNU Make is the version of make required to build.
2023-09-29 17:45:59 +02:00
Ayke van Laethem 6ef8fcd537 cgo: add C._Bool type
This fixes https://github.com/tinygo-org/tinygo/issues/3926.

While working on this I've found another bug: if C.bool is referenced
from within Go, it isn't available anymore on the C side. This is an
existing bug that also applies to float and double, but may be less
likely to be triggered there.
This bug is something to be fixed at a later time (it has something to
do with preprocessor defines).
2023-09-24 07:41:26 -07:00
Ayke van Laethem a896f7f218 transform: fix bug in StringToBytes optimization pass
Previously, this pass would convert any read-only use of a
runtime.stringToBytes call to use the original string buffer instead.
This is incorrect: if there are any writes to the resulting buffer, none
of the slice buffer pointers can be converted to use the original
read-only string buffer.

This commit fixes that bug and adds a test to prove the new (correct)
behavior.
2023-09-23 07:48:43 -07:00
Ayke van Laethem 081d2e58c6 interp: print LLVM instruction in traceback
The old traceback would look like this:

    # internal/godebug
    /usr/local/go/src/internal/godebug/godebug.go:101:11: interp: test
    call <2> 0 <3> 0

    traceback:
    /usr/local/go/src/internal/godebug/godebug.go:101:11:
    call <2> 0 <3> 0
    /usr/local/go/src/internal/godebug:
    call <1> 0

With this patch, it looks like this:

    # io/fs
    /usr/local/go/src/io/fs/fs.go:144:45: interp: test
      %0 = load %runtime._interface, ptr @"internal/oserror.ErrInvalid", align 8, !dbg !316

    traceback:
    /usr/local/go/src/io/fs/fs.go:144:45:
      %0 = load %runtime._interface, ptr @"internal/oserror.ErrInvalid", align 8, !dbg !316
    /usr/local/go/src/io/fs/fs.go:137:28:
      %0 = call %runtime._interface @"io/fs.errInvalid"(ptr undef), !dbg !317

For developers (like me) who are familiar with LLVM, this is probably
easier to read. For users, I'm not sure: the instructions have quite a
lot of distracting fluff in them. But at least it contains the function
names that are called (which are not currently present in the old
traceback).
...that said, having the LLVM instructions in a bug report is probably
going to be easier for people who are familar with LLVM.
2023-09-22 15:28:52 +02:00
123 changed files with 1889 additions and 1144 deletions
+18 -9
View File
@@ -5,7 +5,7 @@ commands:
steps: steps:
- run: - run:
name: "Pull submodules" name: "Pull submodules"
command: git submodule update --init command: git submodule update --init --recursive
llvm-source-linux: llvm-source-linux:
steps: steps:
- restore_cache: - restore_cache:
@@ -55,7 +55,7 @@ commands:
- run: - run:
name: "Install apt dependencies" name: "Install apt dependencies"
command: | command: |
echo 'deb https://apt.llvm.org/buster/ llvm-toolchain-buster-<<parameters.llvm>> main' > /etc/apt/sources.list.d/llvm.list echo 'deb https://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-<<parameters.llvm>> main' > /etc/apt/sources.list.d/llvm.list
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
apt-get update apt-get update
apt-get install --no-install-recommends -y \ apt-get install --no-install-recommends -y \
@@ -69,8 +69,8 @@ commands:
- build-binaryen-linux - build-binaryen-linux
- restore_cache: - restore_cache:
keys: keys:
- go-cache-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} - go-cache-v4-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v3-{{ checksum "go.mod" }} - go-cache-v4-{{ checksum "go.mod" }}
- llvm-source-linux - llvm-source-linux
- run: go install -tags=llvm<<parameters.llvm>> . - run: go install -tags=llvm<<parameters.llvm>> .
- restore_cache: - restore_cache:
@@ -92,18 +92,25 @@ commands:
- run: make gen-device -j4 - run: make gen-device -j4
- run: make smoketest XTENSA=0 - run: make smoketest XTENSA=0
- save_cache: - save_cache:
key: go-cache-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} key: go-cache-v4-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths: paths:
- ~/.cache/go-build - ~/.cache/go-build
- /go/pkg/mod - /go/pkg/mod
jobs: jobs:
test-llvm14-go118: test-llvm15-go118:
docker: docker:
- image: golang:1.18-buster - image: golang:1.18-bullseye
steps: steps:
- test-linux: - test-linux:
llvm: "14" llvm: "15"
resource_class: large
test-llvm17-go121:
docker:
- image: golang:1.21-bullseye
steps:
- test-linux:
llvm: "17"
resource_class: large resource_class: large
workflows: workflows:
@@ -111,4 +118,6 @@ workflows:
jobs: jobs:
# This tests our lowest supported versions of Go and LLVM, to make sure at # This tests our lowest supported versions of Go and LLVM, to make sure at
# least the smoke tests still pass. # least the smoke tests still pass.
- test-llvm14-go118 - test-llvm15-go118
# This tests the upcoming LLVM 17 support.
- test-llvm17-go121
+21 -5
View File
@@ -116,11 +116,21 @@ jobs:
test-macos-homebrew: test-macos-homebrew:
name: homebrew-install name: homebrew-install
runs-on: macos-latest runs-on: macos-latest
strategy:
matrix:
version: [16, 17]
steps: steps:
- name: Install LLVM - name: Update Homebrew
shell: bash run: brew update
- name: Fix Python symlinks
run: | run: |
HOMEBREW_NO_AUTO_UPDATE=1 brew install llvm@16 # Github runners have broken symlinks, so relink
# see: https://github.com/actions/setup-python/issues/577
brew unlink python
brew link --overwrite python
- name: Install LLVM
run: |
HOMEBREW_NO_AUTO_UPDATE=1 brew install llvm@${{ matrix.version }}
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Install Go - name: Install Go
@@ -128,7 +138,13 @@ jobs:
with: with:
go-version: '1.21' go-version: '1.21'
cache: true cache: true
- name: Build TinyGo - name: Build TinyGo (LLVM ${{ matrix.version }})
run: go install run: go install -tags=llvm${{ matrix.version }}
- name: Check binary - name: Check binary
run: tinygo version run: tinygo version
- name: Build TinyGo (default LLVM)
if: matrix.version == 16
run: go install
- name: Check binary
if: matrix.version == 16
run: tinygo version
+30 -134
View File
@@ -140,8 +140,8 @@ jobs:
- name: Install wasmtime - name: Install wasmtime
run: | run: |
mkdir -p $HOME/.wasmtime $HOME/.wasmtime/bin mkdir -p $HOME/.wasmtime $HOME/.wasmtime/bin
curl https://github.com/bytecodealliance/wasmtime/releases/download/v5.0.0/wasmtime-v5.0.0-x86_64-linux.tar.xz -o wasmtime-v5.0.0-x86_64-linux.tar.xz -SfL curl https://github.com/bytecodealliance/wasmtime/releases/download/v14.0.4/wasmtime-v14.0.4-x86_64-linux.tar.xz -o wasmtime-v14.0.4-x86_64-linux.tar.xz -SfL
tar -C $HOME/.wasmtime/bin --wildcards -xf wasmtime-v5.0.0-x86_64-linux.tar.xz --strip-components=1 wasmtime-v5.0.0-x86_64-linux/* tar -C $HOME/.wasmtime/bin --wildcards -xf wasmtime-v14.0.4-x86_64-linux.tar.xz --strip-components=1 wasmtime-v14.0.4-x86_64-linux/*
echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH
- name: Download release artifact - name: Download release artifact
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
@@ -187,8 +187,8 @@ jobs:
- name: Install wasmtime - name: Install wasmtime
run: | run: |
mkdir -p $HOME/.wasmtime $HOME/.wasmtime/bin mkdir -p $HOME/.wasmtime $HOME/.wasmtime/bin
curl -L https://github.com/bytecodealliance/wasmtime/releases/download/v5.0.0/wasmtime-v5.0.0-x86_64-linux.tar.xz -o wasmtime-v5.0.0-x86_64-linux.tar.xz -SfL curl -L https://github.com/bytecodealliance/wasmtime/releases/download/v14.0.4/wasmtime-v14.0.4-x86_64-linux.tar.xz -o wasmtime-v14.0.4-x86_64-linux.tar.xz -SfL
tar -C $HOME/.wasmtime/bin --wildcards -xf wasmtime-v5.0.0-x86_64-linux.tar.xz --strip-components=1 wasmtime-v5.0.0-x86_64-linux/* tar -C $HOME/.wasmtime/bin --wildcards -xf wasmtime-v14.0.4-x86_64-linux.tar.xz --strip-components=1 wasmtime-v14.0.4-x86_64-linux/*
echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH
- name: Restore LLVM source cache - name: Restore LLVM source cache
uses: actions/cache/restore@v3 uses: actions/cache/restore@v3
@@ -267,7 +267,7 @@ jobs:
- run: make smoketest - run: make smoketest
- run: make wasmtest - run: make wasmtest
- run: make tinygo-baremetal - run: make tinygo-baremetal
build-linux-arm: build-linux-cross:
# Build ARM Linux binaries, ready for release. # Build ARM Linux binaries, ready for release.
# This intentionally uses an older Linux image, so that we compile against # This intentionally uses an older Linux image, so that we compile against
# an older glibc version and therefore are compatible with a wide range of # an older glibc version and therefore are compatible with a wide range of
@@ -276,6 +276,16 @@ jobs:
# in that process to avoid doing lots of duplicate work and to avoid # in that process to avoid doing lots of duplicate work and to avoid
# complications around precompiled libraries such as compiler-rt shipped as # complications around precompiled libraries such as compiler-rt shipped as
# part of the release tarball. # part of the release tarball.
strategy:
matrix:
goarch: [ arm, arm64 ]
include:
- goarch: arm64
toolchain: aarch64-linux-gnu
libc: arm64
- goarch: arm
toolchain: arm-linux-gnueabihf
libc: armhf
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
needs: build-linux needs: build-linux
steps: steps:
@@ -286,8 +296,8 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get install --no-install-recommends \ sudo apt-get install --no-install-recommends \
qemu-user \ qemu-user \
g++-arm-linux-gnueabihf \ g++-${{ matrix.toolchain }} \
libc6-dev-armhf-cross libc6-dev-${{ matrix.libc }}-cross
- name: Install Go - name: Install Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
@@ -322,7 +332,7 @@ jobs:
uses: actions/cache/restore@v3 uses: actions/cache/restore@v3
id: cache-llvm-build id: cache-llvm-build
with: with:
key: llvm-build-16-linux-arm-v1 key: llvm-build-16-linux-${{ matrix.goarch }}-v1
path: llvm-build path: llvm-build
- name: Build LLVM - name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true' if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -333,7 +343,7 @@ jobs:
# Install build dependencies. # Install build dependencies.
sudo apt-get install --no-install-recommends ninja-build sudo apt-get install --no-install-recommends ninja-build
# build! # build!
make llvm-build CROSS=arm-linux-gnueabihf make llvm-build CROSS=${{ matrix.toolchain }}
# Remove unnecessary object files (to reduce cache size). # Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save LLVM build cache - name: Save LLVM build cache
@@ -346,14 +356,14 @@ jobs:
uses: actions/cache@v3 uses: actions/cache@v3
id: cache-binaryen id: cache-binaryen
with: with:
key: binaryen-linux-arm-v1 key: binaryen-linux-${{ matrix.goarch }}-v3
path: build/wasm-opt path: build/wasm-opt
- name: Build Binaryen - name: Build Binaryen
if: steps.cache-binaryen.outputs.cache-hit != 'true' if: steps.cache-binaryen.outputs.cache-hit != 'true'
run: | run: |
sudo apt-get install --no-install-recommends ninja-build sudo apt-get install --no-install-recommends ninja-build
git submodule update --init lib/binaryen git submodule update --init --recursive lib/binaryen
make CROSS=arm-linux-gnueabihf binaryen make CROSS=${{ matrix.toolchain }} binaryen
- name: Install fpm - name: Install fpm
run: | run: |
sudo gem install --version 4.0.7 public_suffix sudo gem install --version 4.0.7 public_suffix
@@ -361,7 +371,7 @@ jobs:
sudo gem install --no-document fpm sudo gem install --no-document fpm
- name: Build TinyGo binary - name: Build TinyGo binary
run: | run: |
make CROSS=arm-linux-gnueabihf make CROSS=${{ matrix.toolchain }}
- name: Download amd64 release - name: Download amd64 release
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
@@ -374,129 +384,15 @@ jobs:
run: | run: |
cp -p build/tinygo build/release/tinygo/bin cp -p build/tinygo build/release/tinygo/bin
cp -p build/wasm-opt build/release/tinygo/bin cp -p build/wasm-opt build/release/tinygo/bin
- name: Create arm release - name: Create ${{ matrix.goarch }} release
run: | run: |
make release deb RELEASEONLY=1 DEB_ARCH=armhf make release deb RELEASEONLY=1 DEB_ARCH=${{ matrix.libc }}
cp -p build/release.tar.gz /tmp/tinygo.linux-arm.tar.gz cp -p build/release.tar.gz /tmp/tinygo.linux-${{ matrix.goarch }}.tar.gz
cp -p build/release.deb /tmp/tinygo_armhf.deb cp -p build/release.deb /tmp/tinygo_${{ matrix.libc }}.deb
- name: Publish release artifact - name: Publish release artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: linux-arm-double-zipped name: linux-${{ matrix.goarch }}-double-zipped
path: | path: |
/tmp/tinygo.linux-arm.tar.gz /tmp/tinygo.linux-${{ matrix.goarch }}.tar.gz
/tmp/tinygo_armhf.deb /tmp/tinygo_${{ matrix.libc }}.deb
build-linux-arm64:
# Build ARM64 Linux binaries, ready for release.
# It is set to "needs: build-linux" because it modifies the release created
# in that process to avoid doing lots of duplicate work and to avoid
# complications around precompiled libraries such as compiler-rt shipped as
# part of the release tarball.
runs-on: ubuntu-20.04
needs: build-linux
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install apt dependencies
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends \
qemu-user \
g++-aarch64-linux-gnu \
libc6-dev-arm64-cross \
ninja-build
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: '1.21'
cache: true
- name: Restore LLVM source cache
uses: actions/cache/restore@v3
id: cache-llvm-source
with:
key: llvm-source-16-linux-v1
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
llvm-project/compiler-rt
llvm-project/lld/include
llvm-project/llvm/include
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Save LLVM source cache
uses: actions/cache/save@v3
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
llvm-project/compiler-rt
llvm-project/lld/include
llvm-project/llvm/include
- name: Restore LLVM build cache
uses: actions/cache/restore@v3
id: cache-llvm-build
with:
key: llvm-build-16-linux-arm64-v1
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
run: |
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# build!
make llvm-build CROSS=aarch64-linux-gnu
# Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save LLVM build cache
uses: actions/cache/save@v3
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }}
path: llvm-build
- name: Cache Binaryen
uses: actions/cache@v3
id: cache-binaryen
with:
key: binaryen-linux-arm64-v1
path: build/wasm-opt
- name: Build Binaryen
if: steps.cache-binaryen.outputs.cache-hit != 'true'
run: |
git submodule update --init lib/binaryen
make CROSS=aarch64-linux-gnu binaryen
- name: Install fpm
run: |
sudo gem install --version 4.0.7 public_suffix
sudo gem install --version 2.7.6 dotenv
sudo gem install --no-document fpm
- name: Build TinyGo binary
run: |
make CROSS=aarch64-linux-gnu
- name: Download amd64 release
uses: actions/download-artifact@v3
with:
name: linux-amd64-double-zipped
- name: Extract amd64 release
run: |
mkdir -p build/release
tar -xf tinygo.linux-amd64.tar.gz -C build/release tinygo
- name: Modify release
run: |
cp -p build/tinygo build/release/tinygo/bin
cp -p build/wasm-opt build/release/tinygo/bin
- name: Create arm64 release
run: |
make release deb RELEASEONLY=1 DEB_ARCH=arm64
cp -p build/release.tar.gz /tmp/tinygo.linux-arm64.tar.gz
cp -p build/release.deb /tmp/tinygo_arm64.deb
- name: Publish release artifact
uses: actions/upload-artifact@v3
with:
name: linux-arm64-double-zipped
path: |
/tmp/tinygo.linux-arm64.tar.gz
/tmp/tinygo_arm64.deb
+43
View File
@@ -0,0 +1,43 @@
name: Nix
on:
pull_request:
push:
branches:
- dev
- release
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
nix-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Pull musl
run: |
git submodule update --init lib/musl
- name: Restore LLVM source cache
uses: actions/cache/restore@v3
id: cache-llvm-source
with:
key: llvm-source-16-linux-nix-v1
path: |
llvm-project/compiler-rt
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Save LLVM source cache
uses: actions/cache/save@v3
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
path: |
llvm-project/compiler-rt
- uses: cachix/install-nix-action@v22
- name: Test
run: |
nix develop --ignore-environment --keep HOME --command bash -c "go install && ~/go/bin/tinygo version && ~/go/bin/tinygo build -o test ./testdata/cgo"
+2 -2
View File
@@ -96,7 +96,7 @@ jobs:
run: make wasi-libc run: make wasi-libc
- name: Install wasmtime - name: Install wasmtime
run: | run: |
scoop install wasmtime scoop install wasmtime@14.0.4
- name: make gen-device - name: make gen-device
run: make -j3 gen-device run: make -j3 gen-device
- name: Test TinyGo - name: Test TinyGo
@@ -203,7 +203,7 @@ jobs:
- name: Install Dependencies - name: Install Dependencies
shell: bash shell: bash
run: | run: |
scoop install binaryen wasmtime scoop install binaryen && scoop install wasmtime@14.0.4
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Install Go - name: Install Go
+2 -1
View File
@@ -19,6 +19,7 @@ LLVM, Clang and LLD are quite light on dependencies, requiring only standard
build tools to be built. Go is of course necessary to build TinyGo itself. build tools to be built. Go is of course necessary to build TinyGo itself.
* Go (1.18+) * Go (1.18+)
* GNU Make
* Standard build tools (gcc/clang) * Standard build tools (gcc/clang)
* git * git
* CMake * CMake
@@ -84,7 +85,7 @@ Now that we have a working static build, it's time to make a release tarball:
If you did not clone the repository with the `--recursive` option, you will get errors until you initialize the project submodules: If you did not clone the repository with the `--recursive` option, you will get errors until you initialize the project submodules:
git submodule update --init git submodule update --init --recursive
The release tarball is stored in build/release.tar.gz, and can be extracted with The release tarball is stored in build/release.tar.gz, and can be extracted with
the following command (for example in ~/lib): the following command (for example in ~/lib):
+13 -11
View File
@@ -10,7 +10,7 @@ LLD_SRC ?= $(LLVM_PROJECTDIR)/lld
# Try to autodetect LLVM build tools. # Try to autodetect LLVM build tools.
# Versions are listed here in descending priority order. # Versions are listed here in descending priority order.
LLVM_VERSIONS = 16 15 14 13 12 11 LLVM_VERSIONS = 17 16 15
errifempty = $(if $(1),$(1),$(error $(2))) errifempty = $(if $(1),$(1),$(error $(2)))
detect = $(shell which $(call errifempty,$(firstword $(foreach p,$(2),$(shell command -v $(p) 2> /dev/null && echo $(p)))),failed to locate $(1) at any of: $(2))) detect = $(shell which $(call errifempty,$(firstword $(foreach p,$(2),$(shell command -v $(p) 2> /dev/null && echo $(p)))),failed to locate $(1) at any of: $(2)))
toolSearchPathsVersion = $(1)-$(2) toolSearchPathsVersion = $(1)-$(2)
@@ -32,9 +32,6 @@ export GOROOT = $(shell $(GO) env GOROOT)
# Flags to pass to go test. # Flags to pass to go test.
GOTESTFLAGS ?= GOTESTFLAGS ?=
# md5sum binary
MD5SUM = md5sum
# tinygo binary for tests # tinygo binary for tests
TINYGO ?= $(call detect,tinygo,tinygo $(CURDIR)/build/tinygo) TINYGO ?= $(call detect,tinygo,tinygo $(CURDIR)/build/tinygo)
@@ -130,14 +127,14 @@ ifeq ($(OS),Windows_NT)
USE_SYSTEM_BINARYEN ?= 1 USE_SYSTEM_BINARYEN ?= 1
else ifeq ($(shell uname -s),Darwin) else ifeq ($(shell uname -s),Darwin)
MD5SUM = md5 MD5SUM ?= md5
CGO_LDFLAGS += -lxar CGO_LDFLAGS += -lxar
USE_SYSTEM_BINARYEN ?= 1 USE_SYSTEM_BINARYEN ?= 1
else ifeq ($(shell uname -s),FreeBSD) else ifeq ($(shell uname -s),FreeBSD)
MD5SUM = md5 MD5SUM ?= md5
START_GROUP = -Wl,--start-group START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group END_GROUP = -Wl,--end-group
else else
@@ -145,6 +142,9 @@ else
END_GROUP = -Wl,--end-group END_GROUP = -Wl,--end-group
endif endif
# md5sum binary default, can be overridden by an environment variable
MD5SUM ?= md5sum
# Libraries that should be linked in for the statically linked Clang. # Libraries that should be linked in for the statically linked Clang.
CLANG_LIB_NAMES = clangAnalysis clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangExtractAPI clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangSupport clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions CLANG_LIB_NAMES = clangAnalysis clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangExtractAPI clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangSupport clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++ CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++
@@ -190,7 +190,7 @@ gen-device: gen-device-stm32
endif endif
gen-device-avr: gen-device-avr:
@if [ ! -e lib/avr/README.md ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi @if [ ! -e lib/avr/README.md ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init --recursive"; exit 1; fi
$(GO) build -o ./build/gen-device-avr ./tools/gen-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/atmega src/device/avr/
./build/gen-device-avr lib/avr/packs/tiny src/device/avr/ ./build/gen-device-avr lib/avr/packs/tiny src/device/avr/
@@ -264,8 +264,8 @@ endif
.PHONY: wasi-libc .PHONY: wasi-libc
wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a
lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a:
@if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi @if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init --recursive"; exit 1; fi
cd lib/wasi-libc && make -j4 EXTRA_CFLAGS="-O2 -g -DNDEBUG -mnontrapping-fptoint -msign-ext" MALLOC_IMPL=none CC=$(CLANG) AR=$(LLVM_AR) NM=$(LLVM_NM) cd lib/wasi-libc && $(MAKE) -j4 EXTRA_CFLAGS="-O2 -g -DNDEBUG -mnontrapping-fptoint -msign-ext" MALLOC_IMPL=none CC="$(CLANG)" AR=$(LLVM_AR) NM=$(LLVM_NM)
# Check for Node.js used during WASM tests. # Check for Node.js used during WASM tests.
NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-))) NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-)))
@@ -280,7 +280,7 @@ endif
# Build the Go compiler. # Build the Go compiler.
tinygo: tinygo:
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi @if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " $(MAKE) llvm-source"; echo " $(MAKE) $(LLVM_BUILDDIR)"; exit 1; fi
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" -ldflags="-X github.com/tinygo-org/tinygo/goenv.GitSha1=`git rev-parse --short HEAD`" . CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" -ldflags="-X github.com/tinygo-org/tinygo/goenv.GitSha1=`git rev-parse --short HEAD`" .
test: wasi-libc check-nodejs-version test: wasi-libc check-nodejs-version
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=20m -buildmode exe -tags "byollvm osusergo" ./builder ./cgo ./compileopts ./compiler ./interp ./transform . CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=20m -buildmode exe -tags "byollvm osusergo" ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
@@ -506,6 +506,8 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/watchdog $(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/watchdog
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/device-id
@$(MD5SUM) test.hex
# test simulated boards on play.tinygo.org # test simulated boards on play.tinygo.org
ifneq ($(WASM), 0) ifneq ($(WASM), 0)
$(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1 $(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1
@@ -878,7 +880,7 @@ deb:
@mkdir -p build/release-deb/usr/local/lib @mkdir -p build/release-deb/usr/local/lib
cp -ar build/release/tinygo build/release-deb/usr/local/lib/tinygo cp -ar build/release/tinygo build/release-deb/usr/local/lib/tinygo
ln -sf ../lib/tinygo/bin/tinygo build/release-deb/usr/local/bin/tinygo ln -sf ../lib/tinygo/bin/tinygo build/release-deb/usr/local/bin/tinygo
fpm -f -s dir -t deb -n tinygo -a $(DEB_ARCH) -v $(shell grep "const Version = " goenv/version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb fpm -f -s dir -t deb -n tinygo -a $(DEB_ARCH) -v $(shell grep "const version = " goenv/version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb
ifneq ($(RELEASEONLY), 1) ifneq ($(RELEASEONLY), 1)
release: build/release release: build/release
+1 -1
View File
@@ -1,6 +1,6 @@
# TinyGo - Go compiler for small places # TinyGo - Go compiler for small places
[![Linux](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml) [![macOS](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml) [![Windows](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml) [![Docker](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml) [![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev) [![Linux](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml) [![macOS](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml) [![Windows](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml) [![Docker](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml) [![Nix](https://github.com/tinygo-org/tinygo/actions/workflows/nix.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/nix.yml) [![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (wasm/wasi), and command-line tools. TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (wasm/wasi), and command-line tools.
+42 -43
View File
@@ -83,8 +83,7 @@ type packageAction struct {
FileHashes map[string]string // hash of every file that's part of the package FileHashes map[string]string // hash of every file that's part of the package
EmbeddedFiles map[string]string // hash of all the //go:embed files in the package EmbeddedFiles map[string]string // hash of all the //go:embed files in the package
Imports map[string]string // map from imported package to action ID hash Imports map[string]string // map from imported package to action ID hash
OptLevel int // LLVM optimization level (0-3) OptLevel string // LLVM optimization level (O0, O1, O2, Os, Oz)
SizeLevel int // LLVM optimization for size level (0-2)
UndefinedGlobals []string // globals that are left as external globals (no initializer) UndefinedGlobals []string // globals that are left as external globals (no initializer)
} }
@@ -115,6 +114,30 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
cacheDir = tmpdir cacheDir = tmpdir
} }
// Create default global values.
globalValues := map[string]map[string]string{
"runtime": {
"buildVersion": goenv.Version(),
},
"testing": {},
}
if config.TestConfig.CompileTestBinary {
// The testing.testBinary is set to "1" when in a test.
// This is needed for testing.Testing() to work correctly.
globalValues["testing"]["testBinary"] = "1"
}
// Copy over explicitly set global values, like
// -ldflags="-X main.Version="1.0"
for pkgPath, vals := range config.Options.GlobalValues {
if _, ok := globalValues[pkgPath]; !ok {
globalValues[pkgPath] = map[string]string{}
}
for k, v := range vals {
globalValues[pkgPath][k] = v
}
}
// Check for a libc dependency. // Check for a libc dependency.
// As a side effect, this also creates the headers for the given libc, if // As a side effect, this also creates the headers for the given libc, if
// the libc needs them. // the libc needs them.
@@ -158,7 +181,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
return BuildResult{}, fmt.Errorf("unknown libc: %s", config.Target.Libc) return BuildResult{}, fmt.Errorf("unknown libc: %s", config.Target.Libc)
} }
optLevel, sizeLevel, _ := config.OptLevels() optLevel, speedLevel, sizeLevel := config.OptLevel()
compilerConfig := &compiler.Config{ compilerConfig := &compiler.Config{
Triple: config.Triple(), Triple: config.Triple(),
CPU: config.CPU(), CPU: config.CPU(),
@@ -169,7 +192,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
CodeModel: config.CodeModel(), CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(), RelocationModel: config.RelocationModel(),
SizeLevel: sizeLevel, SizeLevel: sizeLevel,
TinyGoVersion: goenv.Version, TinyGoVersion: goenv.Version(),
Scheduler: config.Scheduler(), Scheduler: config.Scheduler(),
AutomaticStackSize: config.AutomaticStackSize(), AutomaticStackSize: config.AutomaticStackSize(),
@@ -188,7 +211,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
defer machine.Dispose() defer machine.Dispose()
// Load entire program AST into memory. // Load entire program AST into memory.
lprogram, err := loader.Load(config, pkgName, config.ClangHeaders, types.Config{ lprogram, err := loader.Load(config, pkgName, types.Config{
Sizes: compiler.Sizes(machine), Sizes: compiler.Sizes(machine),
}) })
if err != nil { if err != nil {
@@ -217,34 +240,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
var packageJobs []*compileJob var packageJobs []*compileJob
packageActionIDJobs := make(map[string]*compileJob) packageActionIDJobs := make(map[string]*compileJob)
if config.Options.GlobalValues == nil {
config.Options.GlobalValues = make(map[string]map[string]string)
}
if config.Options.GlobalValues["runtime"]["buildVersion"] == "" {
version := goenv.Version
if strings.HasSuffix(goenv.Version, "-dev") && goenv.GitSha1 != "" {
version += "-" + goenv.GitSha1
}
if config.Options.GlobalValues["runtime"] == nil {
config.Options.GlobalValues["runtime"] = make(map[string]string)
}
config.Options.GlobalValues["runtime"]["buildVersion"] = version
}
if config.TestConfig.CompileTestBinary {
// The testing.testBinary is set to "1" when in a test.
// This is needed for testing.Testing() to work correctly.
if config.Options.GlobalValues["testing"] == nil {
config.Options.GlobalValues["testing"] = make(map[string]string)
}
config.Options.GlobalValues["testing"]["testBinary"] = "1"
}
var embedFileObjects []*compileJob var embedFileObjects []*compileJob
for _, pkg := range lprogram.Sorted() { for _, pkg := range lprogram.Sorted() {
pkg := pkg // necessary to avoid a race condition pkg := pkg // necessary to avoid a race condition
var undefinedGlobals []string var undefinedGlobals []string
for name := range config.Options.GlobalValues[pkg.Pkg.Path()] { for name := range globalValues[pkg.Pkg.Path()] {
undefinedGlobals = append(undefinedGlobals, name) undefinedGlobals = append(undefinedGlobals, name)
} }
sort.Strings(undefinedGlobals) sort.Strings(undefinedGlobals)
@@ -321,7 +322,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
EmbeddedFiles: make(map[string]string, len(allFiles)), EmbeddedFiles: make(map[string]string, len(allFiles)),
Imports: make(map[string]string, len(pkg.Pkg.Imports())), Imports: make(map[string]string, len(pkg.Pkg.Imports())),
OptLevel: optLevel, OptLevel: optLevel,
SizeLevel: sizeLevel,
UndefinedGlobals: undefinedGlobals, UndefinedGlobals: undefinedGlobals,
} }
for filePath, hash := range pkg.FileHashes { for filePath, hash := range pkg.FileHashes {
@@ -532,13 +532,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
irbuilder := mod.Context().NewBuilder() irbuilder := mod.Context().NewBuilder()
defer irbuilder.Dispose() defer irbuilder.Dispose()
irbuilder.SetInsertPointAtEnd(block) irbuilder.SetInsertPointAtEnd(block)
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
for _, pkg := range lprogram.Sorted() { for _, pkg := range lprogram.Sorted() {
pkgInit := mod.NamedFunction(pkg.Pkg.Path() + ".init") pkgInit := mod.NamedFunction(pkg.Pkg.Path() + ".init")
if pkgInit.IsNil() { if pkgInit.IsNil() {
panic("init not found for " + pkg.Pkg.Path()) panic("init not found for " + pkg.Pkg.Path())
} }
irbuilder.CreateCall(pkgInit.GlobalValueType(), pkgInit, []llvm.Value{llvm.Undef(i8ptrType)}, "") irbuilder.CreateCall(pkgInit.GlobalValueType(), pkgInit, []llvm.Value{llvm.Undef(ptrType)}, "")
} }
irbuilder.CreateRetVoid() irbuilder.CreateRetVoid()
@@ -573,7 +573,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// Run all optimization passes, which are much more effective now // Run all optimization passes, which are much more effective now
// that the optimizer can see the whole program at once. // that the optimizer can see the whole program at once.
err := optimizeProgram(mod, config) err := optimizeProgram(mod, config, globalValues)
if err != nil { if err != nil {
return err return err
} }
@@ -662,7 +662,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
job := &compileJob{ job := &compileJob{
description: "compile extra file " + path, description: "compile extra file " + path,
run: func(job *compileJob) error { run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, tmpdir, config.CFlags(), config.Options.PrintCommands) result, err := compileAndCacheCFile(abspath, tmpdir, config.CFlags(false), config.Options.PrintCommands)
job.result = result job.result = result
return err return err
}, },
@@ -743,17 +743,17 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if config.GOOS() == "windows" { if config.GOOS() == "windows" {
// Options for the MinGW wrapper for the lld COFF linker. // Options for the MinGW wrapper for the lld COFF linker.
ldflags = append(ldflags, ldflags = append(ldflags,
"-Xlink=/opt:lldlto="+strconv.Itoa(optLevel), "-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto")) "--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
} else if config.GOOS() == "darwin" { } else if config.GOOS() == "darwin" {
// Options for the ld64-compatible lld linker. // Options for the ld64-compatible lld linker.
ldflags = append(ldflags, ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(optLevel), "--lto-O"+strconv.Itoa(speedLevel),
"-cache_path_lto", filepath.Join(cacheDir, "thinlto")) "-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
} else { } else {
// Options for the ELF linker. // Options for the ELF linker.
ldflags = append(ldflags, ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(optLevel), "--lto-O"+strconv.Itoa(speedLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"), "--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"),
) )
} }
@@ -764,7 +764,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if sizeLevel >= 2 { if sizeLevel >= 2 {
// Workaround with roughly the same effect as // Workaround with roughly the same effect as
// https://reviews.llvm.org/D119342. // https://reviews.llvm.org/D119342.
// Can hopefully be removed in LLVM 15. // Can hopefully be removed in LLVM 18.
ldflags = append(ldflags, ldflags = append(ldflags,
"-mllvm", "--rotation-max-header-size=0") "-mllvm", "--rotation-max-header-size=0")
} }
@@ -1042,7 +1042,7 @@ func createEmbedObjectFile(data, hexSum, sourceFile, sourceDir, tmpdir string, c
// optimizeProgram runs a series of optimizations and transformations that are // optimizeProgram runs a series of optimizations and transformations that are
// needed to convert a program to its final form. Some transformations are not // needed to convert a program to its final form. Some transformations are not
// optional and must be run as the compiler expects them to run. // optional and must be run as the compiler expects them to run.
func optimizeProgram(mod llvm.Module, config *compileopts.Config) error { func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues map[string]map[string]string) error {
err := interp.Run(mod, config.Options.InterpTimeout, config.DumpSSA()) err := interp.Run(mod, config.Options.InterpTimeout, config.DumpSSA())
if err != nil { if err != nil {
return err return err
@@ -1061,15 +1061,14 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
} }
// Insert values from -ldflags="-X ..." into the IR. // Insert values from -ldflags="-X ..." into the IR.
err = setGlobalValues(mod, config.Options.GlobalValues) err = setGlobalValues(mod, globalValues)
if err != nil { if err != nil {
return err return err
} }
// Optimization levels here are roughly the same as Clang, but probably not // Run most of the whole-program optimizations (including the whole
// exactly. // O0/O1/O2/Os/Oz optimization pipeline).
optLevel, sizeLevel, inlinerThreshold := config.OptLevels() errs := transform.Optimize(mod, config)
errs := transform.Optimize(mod, config, optLevel, sizeLevel, inlinerThreshold)
if len(errs) > 0 { if len(errs) > 0 {
return newMultiError(errs) return newMultiError(errs)
} }
+3 -6
View File
@@ -8,7 +8,6 @@ import (
"testing" "testing"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -74,7 +73,6 @@ func TestClangAttributes(t *testing.T) {
func testClangAttributes(t *testing.T, options *compileopts.Options) { func testClangAttributes(t *testing.T, options *compileopts.Options) {
testDir := t.TempDir() testDir := t.TempDir()
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
ctx := llvm.NewContext() ctx := llvm.NewContext()
defer ctx.Dispose() defer ctx.Dispose()
@@ -84,9 +82,8 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
t.Fatalf("could not load target: %s", err) t.Fatalf("could not load target: %s", err)
} }
config := compileopts.Config{ config := compileopts.Config{
Options: options, Options: options,
Target: target, Target: target,
ClangHeaders: clangHeaderPath,
} }
// Create a very simple C input file. // Create a very simple C input file.
@@ -98,7 +95,7 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
// Compile this file using Clang. // Compile this file using Clang.
outpath := filepath.Join(testDir, "test.bc") outpath := filepath.Join(testDir, "test.bc")
flags := append([]string{"-c", "-emit-llvm", "-o", outpath, srcpath}, config.CFlags()...) flags := append([]string{"-c", "-emit-llvm", "-o", outpath, srcpath}, config.CFlags(false)...)
if config.GOOS() == "darwin" { if config.GOOS() == "darwin" {
// Silence some warnings that happen when testing GOOS=darwin on // Silence some warnings that happen when testing GOOS=darwin on
// something other than MacOS. // something other than MacOS.
+34 -10
View File
@@ -77,6 +77,15 @@ func ReadBuildID() ([]byte, error) {
} }
return raw[4:], nil return raw[4:], nil
} }
// Normally we would have found a build ID by now. But not on Nix,
// unfortunately, because Nix adds -no_uuid for some reason:
// https://github.com/NixOS/nixpkgs/issues/178366
// Fall back to the same implementation that we use for Windows.
id, err := readRawGoBuildID(f, 32*1024)
if len(id) != 0 || err != nil {
return id, err
}
default: default:
// On other platforms (such as Windows) there isn't such a convenient // On other platforms (such as Windows) there isn't such a convenient
// build ID. Luckily, Go does have an equivalent of the build ID, which // build ID. Luckily, Go does have an equivalent of the build ID, which
@@ -88,16 +97,31 @@ func ReadBuildID() ([]byte, error) {
// directly. Luckily the build ID is always at the start of the file. // directly. Luckily the build ID is always at the start of the file.
// For details, see: // For details, see:
// https://github.com/golang/go/blob/master/src/cmd/internal/buildid/buildid.go // https://github.com/golang/go/blob/master/src/cmd/internal/buildid/buildid.go
fileStart := make([]byte, 4096) id, err := readRawGoBuildID(f, 4096)
_, err := io.ReadFull(f, fileStart) if len(id) != 0 || err != nil {
index := bytes.Index(fileStart, []byte("\xff Go build ID: \"")) return id, err
if index < 0 || index > len(fileStart)-103 {
return nil, fmt.Errorf("could not find build id in %s", err)
}
buf := fileStart[index : index+103]
if bytes.HasPrefix(buf, []byte("\xff Go build ID: \"")) && bytes.HasSuffix(buf, []byte("\"\n \xff")) {
return buf[len("\xff Go build ID: \"") : len(buf)-1], nil
} }
} }
return nil, fmt.Errorf("could not find build ID in %s", executable) return nil, fmt.Errorf("could not find build ID in %v", executable)
}
// The Go toolchain stores a build ID in the binary that we can use, as a
// fallback if binary file specific build IDs can't be obtained.
// This function reads that build ID from the binary.
func readRawGoBuildID(f *os.File, prefixSize int) ([]byte, error) {
fileStart := make([]byte, prefixSize)
_, err := io.ReadFull(f, fileStart)
if err != nil {
return nil, fmt.Errorf("could not read build id from %s: %v", f.Name(), err)
}
index := bytes.Index(fileStart, []byte("\xff Go build ID: \""))
if index < 0 || index > len(fileStart)-103 {
return nil, fmt.Errorf("could not find build id in %s", f.Name())
}
buf := fileStart[index : index+103]
if bytes.HasPrefix(buf, []byte("\xff Go build ID: \"")) && bytes.HasSuffix(buf, []byte("\"\n \xff")) {
return buf[len("\xff Go build ID: \"") : len(buf)-1], nil
}
return nil, nil
} }
-3
View File
@@ -33,13 +33,10 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
return nil, fmt.Errorf("requires go version 1.18 through 1.21, got go%d.%d", major, minor) return nil, fmt.Errorf("requires go version 1.18 through 1.21, got go%d.%d", major, minor)
} }
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
return &compileopts.Config{ return &compileopts.Config{
Options: options, Options: options,
Target: spec, Target: spec,
GoMinorVersion: minor, GoMinorVersion: minor,
ClangHeaders: clangHeaderPath,
TestConfig: options.TestConfig, TestConfig: options.TestConfig,
}, nil }, nil
} }
-105
View File
@@ -1,105 +0,0 @@
package builder
import (
"errors"
"io/fs"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"tinygo.org/x/go-llvm"
)
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
// multiple locations, which should make it find the directory when installed in
// various ways.
func getClangHeaderPath(TINYGOROOT string) string {
// Check whether we're running from the source directory.
path := filepath.Join(TINYGOROOT, "llvm-project", "clang", "lib", "Headers")
if _, err := os.Stat(path); !errors.Is(err, fs.ErrNotExist) {
return path
}
// Check whether we're running from the installation directory.
path = filepath.Join(TINYGOROOT, "lib", "clang", "include")
if _, err := os.Stat(path); !errors.Is(err, fs.ErrNotExist) {
return path
}
// It looks like we are built with a system-installed LLVM. Do a last
// attempt: try to use Clang headers relative to the clang binary.
llvmMajor := strings.Split(llvm.Version, ".")[0]
for _, cmdName := range commands["clang"] {
binpath, err := exec.LookPath(cmdName)
if err == nil {
// This should be the command that will also be used by
// execCommand. To avoid inconsistencies, make sure we use the
// headers relative to this command.
binpath, err = filepath.EvalSymlinks(binpath)
if err != nil {
// Unexpected.
return ""
}
// Example executable:
// /usr/lib/llvm-9/bin/clang
// Example include path:
// /usr/lib/llvm-9/lib64/clang/9.0.1/include/
llvmRoot := filepath.Dir(filepath.Dir(binpath))
clangVersionRoot := filepath.Join(llvmRoot, "lib64", "clang")
dirs64, err64 := ioutil.ReadDir(clangVersionRoot)
// Example include path:
// /usr/lib/llvm-9/lib/clang/9.0.1/include/
clangVersionRoot = filepath.Join(llvmRoot, "lib", "clang")
dirs32, err32 := ioutil.ReadDir(clangVersionRoot)
if err64 != nil && err32 != nil {
// Unexpected.
continue
}
dirnames := make([]string, len(dirs64)+len(dirs32))
dirCount := 0
for _, d := range dirs32 {
name := d.Name()
if name == llvmMajor || strings.HasPrefix(name, llvmMajor+".") {
dirnames[dirCount] = filepath.Join(llvmRoot, "lib", "clang", name)
dirCount++
}
}
for _, d := range dirs64 {
name := d.Name()
if name == llvmMajor || strings.HasPrefix(name, llvmMajor+".") {
dirnames[dirCount] = filepath.Join(llvmRoot, "lib64", "clang", name)
dirCount++
}
}
sort.Strings(dirnames)
// Check for the highest version first.
for i := dirCount - 1; i >= 0; i-- {
path := filepath.Join(dirnames[i], "include")
_, err := os.Stat(filepath.Join(path, "stdint.h"))
if err == nil {
return path
}
}
}
}
// On Arch Linux, the clang executable is stored in /usr/bin rather than being symlinked from there.
// Search directly in /usr/lib for clang.
if matches, err := filepath.Glob("/usr/lib/clang/" + llvmMajor + ".*.*"); err == nil {
// Check for the highest version first.
sort.Strings(matches)
for i := len(matches) - 1; i >= 0; i-- {
path := filepath.Join(matches[i], "include")
_, err := os.Stat(filepath.Join(path, "stdint.h"))
if err == nil {
return path
}
}
}
// Could not find it.
return ""
}
+4 -6
View File
@@ -143,6 +143,10 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
// reproducible. Otherwise the temporary directory is stored in the archive // reproducible. Otherwise the temporary directory is stored in the archive
// itself, which varies each run. // itself, which varies each run.
args := append(l.cflags(target, headerPath), "-c", "-Oz", "-gdwarf-4", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir) args := append(l.cflags(target, headerPath), "-c", "-Oz", "-gdwarf-4", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
resourceDir := goenv.ClangResourceDir(false)
if resourceDir != "" {
args = append(args, "-resource-dir="+resourceDir)
}
cpu := config.CPU() cpu := config.CPU()
if cpu != "" { if cpu != "" {
// X86 has deprecated the -mcpu flag, so we need to use -march instead. // X86 has deprecated the -mcpu flag, so we need to use -march instead.
@@ -178,12 +182,6 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
if strings.HasPrefix(target, "riscv64-") { if strings.HasPrefix(target, "riscv64-") {
args = append(args, "-march=rv64gc") args = append(args, "-march=rv64gc")
} }
if strings.HasPrefix(target, "xtensa") {
// Hack to work around an issue in the Xtensa port:
// https://github.com/espressif/llvm-project/issues/52
// Hopefully this will be fixed soon (LLVM 14).
args = append(args, "-D__ELF__")
}
var once sync.Once var once sync.Once
+1 -7
View File
@@ -6,12 +6,10 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strconv"
"strings" "strings"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
"tinygo.org/x/go-llvm"
) )
var Musl = Library{ var Musl = Library{
@@ -93,6 +91,7 @@ var Musl = Library{
"-Wno-string-plus-int", "-Wno-string-plus-int",
"-Wno-ignored-pragmas", "-Wno-ignored-pragmas",
"-Wno-tautological-constant-out-of-range-compare", "-Wno-tautological-constant-out-of-range-compare",
"-Wno-deprecated-non-prototype",
"-Qunused-arguments", "-Qunused-arguments",
// Select include dirs. Don't include standard library includes // Select include dirs. Don't include standard library includes
// (that would introduce host dependencies and other complications), // (that would introduce host dependencies and other complications),
@@ -106,11 +105,6 @@ var Musl = Library{
"-I" + muslDir + "/include", "-I" + muslDir + "/include",
"-fno-stack-protector", "-fno-stack-protector",
} }
llvmMajor, _ := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
if llvmMajor >= 15 {
// This flag was added in Clang 15. It is not present in LLVM 14.
cflags = append(cflags, "-Wno-deprecated-non-prototype")
}
return cflags return cflags
}, },
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl/src") }, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl/src") },
+93 -79
View File
@@ -3,6 +3,7 @@ package builder
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
) )
@@ -41,91 +42,23 @@ var Picolibc = Library{
}, },
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib") }, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib") },
librarySources: func(target string) ([]string, error) { librarySources: func(target string) ([]string, error) {
return picolibcSources, nil sources := append([]string(nil), picolibcSources...)
if !strings.HasPrefix(target, "avr") {
// Small chips without long jumps can't compile many files (printf,
// pow, etc). Therefore exclude those source files for those chips.
// Unfortunately it's difficult to exclude only some chips, so this
// excludes those files on all AVR chips for now.
// More information:
// https://github.com/llvm/llvm-project/issues/67042
sources = append(sources, picolibcSourcesLarge...)
}
return sources, nil
}, },
} }
var picolibcSources = []string{ var picolibcSources = []string{
"../../picolibc-stdio.c", "../../picolibc-stdio.c",
// srcs_tinystdio
"libc/tinystdio/asprintf.c",
"libc/tinystdio/bufio.c",
"libc/tinystdio/clearerr.c",
"libc/tinystdio/ecvt_r.c",
"libc/tinystdio/ecvt.c",
"libc/tinystdio/ecvtf_r.c",
"libc/tinystdio/ecvtf.c",
"libc/tinystdio/fcvt.c",
"libc/tinystdio/fcvt_r.c",
"libc/tinystdio/fcvtf.c",
"libc/tinystdio/fcvtf_r.c",
"libc/tinystdio/gcvt.c",
"libc/tinystdio/gcvtf.c",
"libc/tinystdio/fclose.c",
"libc/tinystdio/fdevopen.c",
"libc/tinystdio/feof.c",
"libc/tinystdio/ferror.c",
"libc/tinystdio/fflush.c",
"libc/tinystdio/fgetc.c",
"libc/tinystdio/fgets.c",
"libc/tinystdio/fileno.c",
"libc/tinystdio/filestrget.c",
"libc/tinystdio/filestrput.c",
"libc/tinystdio/filestrputalloc.c",
"libc/tinystdio/fmemopen.c",
"libc/tinystdio/fprintf.c",
"libc/tinystdio/fputc.c",
"libc/tinystdio/fputs.c",
"libc/tinystdio/fread.c",
//"libc/tinystdio/freopen.c", // crashes with AVR, see: https://github.com/picolibc/picolibc/pull/369
"libc/tinystdio/fscanf.c",
"libc/tinystdio/fseek.c",
"libc/tinystdio/fseeko.c",
"libc/tinystdio/ftell.c",
"libc/tinystdio/ftello.c",
"libc/tinystdio/fwrite.c",
"libc/tinystdio/getchar.c",
"libc/tinystdio/gets.c",
"libc/tinystdio/matchcaseprefix.c",
"libc/tinystdio/mktemp.c",
"libc/tinystdio/perror.c",
"libc/tinystdio/printf.c",
"libc/tinystdio/putchar.c",
"libc/tinystdio/puts.c",
"libc/tinystdio/rewind.c",
"libc/tinystdio/scanf.c",
"libc/tinystdio/setbuf.c",
"libc/tinystdio/setbuffer.c",
"libc/tinystdio/setlinebuf.c",
"libc/tinystdio/setvbuf.c",
"libc/tinystdio/snprintf.c",
"libc/tinystdio/sprintf.c",
"libc/tinystdio/snprintfd.c",
"libc/tinystdio/snprintff.c",
"libc/tinystdio/sprintff.c",
"libc/tinystdio/sprintfd.c",
"libc/tinystdio/sscanf.c",
"libc/tinystdio/strfromf.c",
"libc/tinystdio/strfromd.c",
"libc/tinystdio/strtof.c",
"libc/tinystdio/strtof_l.c",
"libc/tinystdio/strtod.c",
"libc/tinystdio/strtod_l.c",
"libc/tinystdio/ungetc.c",
"libc/tinystdio/vasprintf.c",
"libc/tinystdio/vfiprintf.c",
"libc/tinystdio/vfprintf.c",
"libc/tinystdio/vfprintff.c",
"libc/tinystdio/vfscanf.c",
"libc/tinystdio/vfiscanf.c",
"libc/tinystdio/vfscanff.c",
"libc/tinystdio/vprintf.c",
"libc/tinystdio/vscanf.c",
"libc/tinystdio/vsscanf.c",
"libc/tinystdio/vsnprintf.c",
"libc/tinystdio/vsprintf.c",
"libc/string/bcmp.c", "libc/string/bcmp.c",
"libc/string/bcopy.c", "libc/string/bcopy.c",
"libc/string/bzero.c", "libc/string/bzero.c",
@@ -229,6 +162,87 @@ var picolibcSources = []string{
"libc/string/wmempcpy.c", "libc/string/wmempcpy.c",
"libc/string/wmemset.c", "libc/string/wmemset.c",
"libc/string/xpg_strerror_r.c", "libc/string/xpg_strerror_r.c",
}
// Parts of picolibc that are too large for small AVRs.
var picolibcSourcesLarge = []string{
// srcs_tinystdio
"libc/tinystdio/asprintf.c",
"libc/tinystdio/bufio.c",
"libc/tinystdio/clearerr.c",
"libc/tinystdio/ecvt_r.c",
"libc/tinystdio/ecvt.c",
"libc/tinystdio/ecvtf_r.c",
"libc/tinystdio/ecvtf.c",
"libc/tinystdio/fcvt.c",
"libc/tinystdio/fcvt_r.c",
"libc/tinystdio/fcvtf.c",
"libc/tinystdio/fcvtf_r.c",
"libc/tinystdio/gcvt.c",
"libc/tinystdio/gcvtf.c",
"libc/tinystdio/fclose.c",
"libc/tinystdio/fdevopen.c",
"libc/tinystdio/feof.c",
"libc/tinystdio/ferror.c",
"libc/tinystdio/fflush.c",
"libc/tinystdio/fgetc.c",
"libc/tinystdio/fgets.c",
"libc/tinystdio/fileno.c",
"libc/tinystdio/filestrget.c",
"libc/tinystdio/filestrput.c",
"libc/tinystdio/filestrputalloc.c",
"libc/tinystdio/fmemopen.c",
"libc/tinystdio/fprintf.c",
"libc/tinystdio/fputc.c",
"libc/tinystdio/fputs.c",
"libc/tinystdio/fread.c",
//"libc/tinystdio/freopen.c", // crashes with AVR, see: https://github.com/picolibc/picolibc/pull/369
"libc/tinystdio/fscanf.c",
"libc/tinystdio/fseek.c",
"libc/tinystdio/fseeko.c",
"libc/tinystdio/ftell.c",
"libc/tinystdio/ftello.c",
"libc/tinystdio/fwrite.c",
"libc/tinystdio/getchar.c",
"libc/tinystdio/gets.c",
"libc/tinystdio/matchcaseprefix.c",
"libc/tinystdio/mktemp.c",
"libc/tinystdio/perror.c",
"libc/tinystdio/printf.c",
"libc/tinystdio/putchar.c",
"libc/tinystdio/puts.c",
"libc/tinystdio/rewind.c",
"libc/tinystdio/scanf.c",
"libc/tinystdio/setbuf.c",
"libc/tinystdio/setbuffer.c",
"libc/tinystdio/setlinebuf.c",
"libc/tinystdio/setvbuf.c",
"libc/tinystdio/snprintf.c",
"libc/tinystdio/sprintf.c",
"libc/tinystdio/snprintfd.c",
"libc/tinystdio/snprintff.c",
"libc/tinystdio/sprintff.c",
"libc/tinystdio/sprintfd.c",
"libc/tinystdio/sscanf.c",
"libc/tinystdio/strfromf.c",
"libc/tinystdio/strfromd.c",
"libc/tinystdio/strtof.c",
"libc/tinystdio/strtof_l.c",
"libc/tinystdio/strtod.c",
"libc/tinystdio/strtod_l.c",
"libc/tinystdio/ungetc.c",
"libc/tinystdio/vasprintf.c",
"libc/tinystdio/vfiprintf.c",
"libc/tinystdio/vfprintf.c",
"libc/tinystdio/vfprintff.c",
"libc/tinystdio/vfscanf.c",
"libc/tinystdio/vfiscanf.c",
"libc/tinystdio/vfscanff.c",
"libc/tinystdio/vprintf.c",
"libc/tinystdio/vscanf.c",
"libc/tinystdio/vsscanf.c",
"libc/tinystdio/vsnprintf.c",
"libc/tinystdio/vsprintf.c",
"libm/common/sf_finite.c", "libm/common/sf_finite.c",
"libm/common/sf_copysign.c", "libm/common/sf_copysign.c",
+3 -3
View File
@@ -41,9 +41,9 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test. // This is a small number of very diverse targets that we want to test.
tests := []sizeTest{ tests := []sizeTest{
// microcontrollers // microcontrollers
{"hifive1b", "examples/echo", 4568, 280, 0, 2252}, {"hifive1b", "examples/echo", 4484, 280, 0, 2252},
{"microbit", "examples/serial", 2728, 388, 8, 2256}, {"microbit", "examples/serial", 2724, 388, 8, 2256},
{"wioterminal", "examples/pininterrupt", 5996, 1484, 116, 6816}, {"wioterminal", "examples/pininterrupt", 6000, 1484, 116, 6816},
// TODO: also check wasm. Right now this is difficult, because // TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the // wasm binaries are run through wasm-opt and therefore the
-6
View File
@@ -1,7 +1,6 @@
package builder package builder
import ( import (
"errors"
"os" "os"
"os/exec" "os/exec"
@@ -12,11 +11,6 @@ import (
func runCCompiler(flags ...string) error { func runCCompiler(flags ...string) error {
if hasBuiltinTools { if hasBuiltinTools {
// Compile this with the internal Clang compiler. // Compile this with the internal Clang compiler.
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
if headerPath == "" {
return errors.New("could not locate Clang headers")
}
flags = append(flags, "-I"+headerPath)
cmd := exec.Command(os.Args[0], append([]string{"clang"}, flags...)...) cmd := exec.Command(os.Args[0], append([]string{"clang"}, flags...)...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
+5 -6
View File
@@ -88,6 +88,7 @@ var cgoAliases = map[string]string{
"C.uintptr_t": "uintptr", "C.uintptr_t": "uintptr",
"C.float": "float32", "C.float": "float32",
"C.double": "float64", "C.double": "float64",
"C._Bool": "bool",
} }
// builtinAliases are handled specially because they only exist on the Go side // builtinAliases are handled specially because they only exist on the Go side
@@ -164,7 +165,7 @@ func GoBytes(ptr unsafe.Pointer, length C.int) []byte {
// functions), the CFLAGS and LDFLAGS found in #cgo lines, and a map of file // functions), the CFLAGS and LDFLAGS found in #cgo lines, and a map of file
// hashes of the accessed C header files. If there is one or more error, it // hashes of the accessed C header files. If there is one or more error, it
// returns these in the []error slice but still modifies the AST. // returns these in the []error slice but still modifies the AST.
func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string, clangHeaders string) (*ast.File, []string, []string, []string, map[string][]byte, []error) { func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string) (*ast.File, []string, []string, []string, map[string][]byte, []error) {
p := &cgoPackage{ p := &cgoPackage{
currentDir: dir, currentDir: dir,
importPath: importPath, importPath: importPath,
@@ -291,9 +292,6 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// have better alternatives anyway. // have better alternatives anyway.
cflagsForCGo := append([]string{"-D_FORTIFY_SOURCE=0"}, cflags...) cflagsForCGo := append([]string{"-D_FORTIFY_SOURCE=0"}, cflags...)
cflagsForCGo = append(cflagsForCGo, p.cflags...) cflagsForCGo = append(cflagsForCGo, p.cflags...)
if clangHeaders != "" {
cflagsForCGo = append(cflagsForCGo, "-isystem", clangHeaders)
}
// Retrieve types such as C.int, C.longlong, etc from C. // Retrieve types such as C.int, C.longlong, etc from C.
p.newCGoFile(nil, -1).readNames(builtinAliasTypedefs, cflagsForCGo, "", func(names map[string]clangCursor) { p.newCGoFile(nil, -1).readNames(builtinAliasTypedefs, cflagsForCGo, "", func(names map[string]clangCursor) {
@@ -311,10 +309,11 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// Process CGo imports for each file. // Process CGo imports for each file.
for i, f := range files { for i, f := range files {
cf := p.newCGoFile(f, i) cf := p.newCGoFile(f, i)
// Float and double are aliased, meaning that C.float is the same thing // These types are aliased with the corresponding types in C. For
// as float32 in Go. // example, float in C is always float32 in Go.
cf.names["float"] = clangCursor{} cf.names["float"] = clangCursor{}
cf.names["double"] = clangCursor{} cf.names["double"] = clangCursor{}
cf.names["_Bool"] = clangCursor{}
// Now read all the names (identifies) that C defines in the header // Now read all the names (identifies) that C defines in the header
// snippet. // snippet.
cf.readNames(p.cgoHeaders[i], cflagsForCGo, filepath.Base(fset.File(f.Pos()).Name()), func(names map[string]clangCursor) { cf.readNames(p.cgoHeaders[i], cflagsForCGo, filepath.Base(fset.File(f.Pos()).Name()), func(names map[string]clangCursor) {
+1 -1
View File
@@ -55,7 +55,7 @@ func TestCGo(t *testing.T) {
} }
// Process the AST with CGo. // Process the AST with CGo.
cgoAST, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags, "") cgoAST, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags)
// Check the AST for type errors. // Check the AST for type errors.
var typecheckErrors []error var typecheckErrors []error
-15
View File
@@ -1,15 +0,0 @@
//go:build !byollvm && llvm14
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-14/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@14/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@14/include
#cgo freebsd CFLAGS: -I/usr/local/llvm14/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-14/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@14/lib -lclang -lffi
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@14/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm14/lib -lclang
*/
import "C"
+2 -2
View File
@@ -8,8 +8,8 @@ package cgo
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@15/include #cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@15/include
#cgo freebsd CFLAGS: -I/usr/local/llvm15/include #cgo freebsd CFLAGS: -I/usr/local/llvm15/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-15/lib -lclang #cgo linux LDFLAGS: -L/usr/lib/llvm-15/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@15/lib -lclang -lffi #cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@15/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@15/lib -lclang -lffi #cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@15/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm15/lib -lclang #cgo freebsd LDFLAGS: -L/usr/local/llvm15/lib -lclang
*/ */
import "C" import "C"
+4 -4
View File
@@ -1,8 +1,8 @@
//go:build !byollvm && !llvm14 && !llvm15 //go:build !byollvm && !llvm15 && !llvm17
package cgo package cgo
// As of 2023-05-05, there is a packaging issue on Debian: // As of 2023-05-05, there is a packaging issue with LLVM 16 on Debian:
// https://github.com/llvm/llvm-project/issues/62199 // https://github.com/llvm/llvm-project/issues/62199
// A workaround is to fix this locally, using something like this: // A workaround is to fix this locally, using something like this:
// //
@@ -14,8 +14,8 @@ package cgo
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@16/include #cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@16/include
#cgo freebsd CFLAGS: -I/usr/local/llvm16/include #cgo freebsd CFLAGS: -I/usr/local/llvm16/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-16/lib -lclang #cgo linux LDFLAGS: -L/usr/lib/llvm-16/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@16/lib -lclang -lffi #cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@16/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@16/lib -lclang -lffi #cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@16/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm16/lib -lclang #cgo freebsd LDFLAGS: -L/usr/local/llvm16/lib -lclang
*/ */
import "C" import "C"
+15
View File
@@ -0,0 +1,15 @@
//go:build !byollvm && llvm17
package cgo
/*
#cgo linux CFLAGS: -I/usr/include/llvm-17 -I/usr/include/llvm-c-17 -I/usr/lib/llvm-17/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@17/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@17/include
#cgo freebsd CFLAGS: -I/usr/local/llvm17/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-17/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@17/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@17/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm17/lib -lclang
*/
import "C"
+27 -12
View File
@@ -19,7 +19,6 @@ type Config struct {
Options *Options Options *Options
Target *TargetSpec Target *TargetSpec
GoMinorVersion int GoMinorVersion int
ClangHeaders string // Clang built-in header include path
TestConfig TestConfig TestConfig TestConfig
} }
@@ -145,18 +144,18 @@ func (c *Config) Serial() string {
// OptLevels returns the optimization level (0-2), size level (0-2), and inliner // OptLevels returns the optimization level (0-2), size level (0-2), and inliner
// threshold as used in the LLVM optimization pipeline. // threshold as used in the LLVM optimization pipeline.
func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) { func (c *Config) OptLevel() (level string, speedLevel, sizeLevel int) {
switch c.Options.Opt { switch c.Options.Opt {
case "none", "0": case "none", "0":
return 0, 0, 0 // -O0 return "O0", 0, 0
case "1": case "1":
return 1, 0, 0 // -O1 return "O1", 1, 0
case "2": case "2":
return 2, 0, 225 // -O2 return "O2", 2, 0
case "s": case "s":
return 2, 1, 225 // -Os return "Os", 2, 1
case "z": case "z":
return 2, 2, 5 // -Oz, default return "Oz", 2, 2 // default
default: default:
// This is not shown to the user: valid choices are already checked as // This is not shown to the user: valid choices are already checked as
// part of Options.Verify(). It is here as a sanity check. // part of Options.Verify(). It is here as a sanity check.
@@ -259,24 +258,39 @@ func (c *Config) DefaultBinaryExtension() string {
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo // CFlags returns the flags to pass to the C compiler. This is necessary for CGo
// preprocessing. // preprocessing.
func (c *Config) CFlags() []string { func (c *Config) CFlags(libclang bool) []string {
var cflags []string var cflags []string
for _, flag := range c.Target.CFlags { for _, flag := range c.Target.CFlags {
cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT"))) cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT")))
} }
resourceDir := goenv.ClangResourceDir(libclang)
if resourceDir != "" {
// The resource directory contains the built-in clang headers like
// stdbool.h, stdint.h, float.h, etc.
// It is left empty if we're using an external compiler (that already
// knows these headers).
cflags = append(cflags,
"-resource-dir="+resourceDir,
)
if strings.HasPrefix(c.Triple(), "xtensa") {
// workaround needed in LLVM 16, see: https://github.com/espressif/llvm-project/issues/83
cflags = append(cflags, "-isystem", filepath.Join(resourceDir, "include"))
}
}
switch c.Target.Libc { switch c.Target.Libc {
case "darwin-libSystem": case "darwin-libSystem":
root := goenv.Get("TINYGOROOT") root := goenv.Get("TINYGOROOT")
cflags = append(cflags, cflags = append(cflags,
"--sysroot="+filepath.Join(root, "lib/macos-minimal-sdk/src"), "-nostdlibinc",
"-isystem", filepath.Join(root, "lib/macos-minimal-sdk/src/usr/include"),
) )
case "picolibc": case "picolibc":
root := goenv.Get("TINYGOROOT") root := goenv.Get("TINYGOROOT")
picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc") picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc")
path, _ := c.LibcPath("picolibc") path, _ := c.LibcPath("picolibc")
cflags = append(cflags, cflags = append(cflags,
"--sysroot="+path, "-nostdlibinc",
"-isystem", filepath.Join(path, "include"), // necessary for Xtensa "-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(picolibcDir, "include"), "-isystem", filepath.Join(picolibcDir, "include"),
"-isystem", filepath.Join(picolibcDir, "tinystdio"), "-isystem", filepath.Join(picolibcDir, "tinystdio"),
) )
@@ -297,7 +311,8 @@ func (c *Config) CFlags() []string {
root := goenv.Get("TINYGOROOT") root := goenv.Get("TINYGOROOT")
path, _ := c.LibcPath("mingw-w64") path, _ := c.LibcPath("mingw-w64")
cflags = append(cflags, cflags = append(cflags,
"--sysroot="+path, "-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"),
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"),
"-D_UCRT", "-D_UCRT",
+39 -2
View File
@@ -246,6 +246,43 @@ func LoadTarget(options *Options) (*TargetSpec, error) {
return spec, nil return spec, nil
} }
// GetTargetSpecs retrieves target specifications from the TINYGOROOT targets
// directory. Only valid target JSON files are considered, and the function
// returns a map of target names to their respective TargetSpec.
func GetTargetSpecs() (map[string]*TargetSpec, error) {
dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets")
entries, err := os.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("could not list targets: %w", err)
}
maps := map[string]*TargetSpec{}
for _, entry := range entries {
entryInfo, err := entry.Info()
if err != nil {
return nil, fmt.Errorf("could not get entry info: %w", err)
}
if !entryInfo.Mode().IsRegular() || !strings.HasSuffix(entry.Name(), ".json") {
// Only inspect JSON files.
continue
}
path := filepath.Join(dir, entry.Name())
spec, err := LoadTarget(&Options{Target: path})
if err != nil {
return nil, fmt.Errorf("could not list target: %w", err)
}
if spec.FlashMethod == "" && spec.FlashCommand == "" && spec.Emulator == "" {
// This doesn't look like a regular target file, but rather like
// a parent target (such as targets/cortex-m.json).
continue
}
name := entry.Name()
name = name[:len(name)-5]
maps[name] = spec
}
return maps, nil
}
func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
// No target spec available. Use the default one, useful on most systems // No target spec available. Use the default one, useful on most systems
// with a regular OS. // with a regular OS.
@@ -344,12 +381,12 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
spec.Linker = "wasm-ld" spec.Linker = "wasm-ld"
spec.RTLib = "compiler-rt" spec.RTLib = "compiler-rt"
spec.Libc = "wasi-libc" spec.Libc = "wasi-libc"
spec.DefaultStackSize = 1024 * 32 // 32kB spec.DefaultStackSize = 1024 * 64 // 64kB
spec.LDFlags = append(spec.LDFlags, spec.LDFlags = append(spec.LDFlags,
"--stack-first", "--stack-first",
"--no-demangle", "--no-demangle",
) )
spec.Emulator = "wasmtime --mapdir=/tmp::{tmpDir} {}" spec.Emulator = "wasmtime --dir={tmpDir}::/tmp {}"
spec.ExtraFiles = append(spec.ExtraFiles, spec.ExtraFiles = append(spec.ExtraFiles,
"src/runtime/asm_tinygowasm.S", "src/runtime/asm_tinygowasm.S",
"src/internal/task/task_asyncify_wasm.S", "src/internal/task/task_asyncify_wasm.S",
-28
View File
@@ -35,17 +35,7 @@ func (b *builder) createAtomicOp(name string) llvm.Value {
case "SwapInt32", "SwapInt64", "SwapUint32", "SwapUint64", "SwapUintptr", "SwapPointer": case "SwapInt32", "SwapInt64", "SwapUint32", "SwapUint64", "SwapUintptr", "SwapPointer":
ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) ptr := b.getValue(b.fn.Params[0], getPos(b.fn))
val := b.getValue(b.fn.Params[1], getPos(b.fn)) val := b.getValue(b.fn.Params[1], getPos(b.fn))
isPointer := val.Type().TypeKind() == llvm.PointerTypeKind
if isPointer {
// atomicrmw only supports integers, so cast to an integer.
// TODO: this is fixed in LLVM 15.
val = b.CreatePtrToInt(val, b.uintptrType, "")
ptr = b.CreateBitCast(ptr, llvm.PointerType(val.Type(), 0), "")
}
oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpXchg, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpXchg, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true)
if isPointer {
oldVal = b.CreateIntToPtr(oldVal, b.i8ptrType, "")
}
return oldVal return oldVal
case "CompareAndSwapInt32", "CompareAndSwapInt64", "CompareAndSwapUint32", "CompareAndSwapUint64", "CompareAndSwapUintptr", "CompareAndSwapPointer": case "CompareAndSwapInt32", "CompareAndSwapInt64", "CompareAndSwapUint32", "CompareAndSwapUint64", "CompareAndSwapUintptr", "CompareAndSwapPointer":
ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) ptr := b.getValue(b.fn.Params[0], getPos(b.fn))
@@ -63,24 +53,6 @@ func (b *builder) createAtomicOp(name string) llvm.Value {
case "StoreInt32", "StoreInt64", "StoreUint32", "StoreUint64", "StoreUintptr", "StorePointer": case "StoreInt32", "StoreInt64", "StoreUint32", "StoreUint64", "StoreUintptr", "StorePointer":
ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) ptr := b.getValue(b.fn.Params[0], getPos(b.fn))
val := b.getValue(b.fn.Params[1], getPos(b.fn)) val := b.getValue(b.fn.Params[1], getPos(b.fn))
if strings.HasPrefix(b.Triple, "avr") {
// SelectionDAGBuilder is currently missing the "are unaligned atomics allowed" check for stores.
vType := val.Type()
isPointer := vType.TypeKind() == llvm.PointerTypeKind
if isPointer {
// libcalls only supports integers, so cast to an integer.
vType = b.uintptrType
val = b.CreatePtrToInt(val, vType, "")
ptr = b.CreateBitCast(ptr, llvm.PointerType(vType, 0), "")
}
name := fmt.Sprintf("__atomic_store_%d", vType.IntTypeWidth()/8)
fn := b.mod.NamedFunction(name)
if fn.IsNil() {
fn = llvm.AddFunction(b.mod, name, llvm.FunctionType(vType, []llvm.Type{ptr.Type(), vType, b.uintptrType}, false))
}
b.createCall(fn.GlobalValueType(), fn, []llvm.Value{ptr, val, llvm.ConstInt(b.uintptrType, 5, false)}, "")
return llvm.Value{}
}
store := b.CreateStore(val, ptr) store := b.CreateStore(val, ptr)
store.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent) store.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent)
store.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required store.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required
+1 -1
View File
@@ -45,7 +45,7 @@ func (b *builder) createRuntimeCallCommon(fnName string, args []llvm.Value, name
if llvmFn.IsNil() { if llvmFn.IsNil() {
panic("trying to call non-existent function: " + fn.RelString(nil)) panic("trying to call non-existent function: " + fn.RelString(nil))
} }
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter args = append(args, llvm.Undef(b.dataPtrType)) // unused context parameter
if isInvoke { if isInvoke {
return b.createInvoke(fnType, llvmFn, args, name) return b.createInvoke(fnType, llvmFn, args, name)
} }
+22 -26
View File
@@ -33,28 +33,27 @@ func (b *builder) createChanSend(instr *ssa.Send) {
// store value-to-send // store value-to-send
valueType := b.getLLVMType(instr.X.Type()) valueType := b.getLLVMType(instr.X.Type())
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value var valueAlloca, valueAllocaSize llvm.Value
if isZeroSize { if isZeroSize {
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0)) valueAlloca = llvm.ConstNull(b.dataPtrType)
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else { } else {
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value") valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca) b.CreateStore(chanValue, valueAlloca)
} }
// Allocate blockedlist buffer. // Allocate blockedlist buffer.
channelBlockedList := b.getLLVMRuntimeType("channelBlockedList") channelBlockedList := b.getLLVMRuntimeType("channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList") channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Do the send. // Do the send.
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "") b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "")
// End the lifetime of the allocas. // End the lifetime of the allocas.
// This also works around a bug in CoroSplit, at least in LLVM 8: // This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742 // https://bugs.llvm.org/show_bug.cgi?id=41742
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize) b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize)
if !isZeroSize { if !isZeroSize {
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
} }
} }
@@ -66,28 +65,27 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
// Allocate memory to receive into. // Allocate memory to receive into.
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value var valueAlloca, valueAllocaSize llvm.Value
if isZeroSize { if isZeroSize {
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0)) valueAlloca = llvm.ConstNull(b.dataPtrType)
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else { } else {
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value") valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
} }
// Allocate blockedlist buffer. // Allocate blockedlist buffer.
channelBlockedList := b.getLLVMRuntimeType("channelBlockedList") channelBlockedList := b.getLLVMRuntimeType("channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList") channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Do the receive. // Do the receive.
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "") commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "")
var received llvm.Value var received llvm.Value
if isZeroSize { if isZeroSize {
received = llvm.ConstNull(valueType) received = llvm.ConstNull(valueType)
} else { } else {
received = b.CreateLoad(valueType, valueAlloca, "chan.received") received = b.CreateLoad(valueType, valueAlloca, "chan.received")
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
} }
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize) b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize)
if unop.CommaOk { if unop.CommaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false)) tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
@@ -159,8 +157,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
sendValue := b.getValue(state.Send, state.Pos) sendValue := b.getValue(state.Send, state.Pos)
alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value") alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
b.CreateStore(sendValue, alloca) b.CreateStore(sendValue, alloca)
ptr := b.CreateBitCast(alloca, b.i8ptrType, "") selectState = b.CreateInsertValue(selectState, alloca, 1, "")
selectState = b.CreateInsertValue(selectState, ptr, 1, "")
default: default:
panic("unreachable") panic("unreachable")
} }
@@ -168,10 +165,10 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
} }
// Create a receive buffer, where the received value will be stored. // Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(b.i8ptrType) recvbuf := llvm.Undef(b.dataPtrType)
if recvbufSize != 0 { if recvbufSize != 0 {
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize)) allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca") recvbufAlloca, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
recvbufAlloca.SetAlignment(recvbufAlign) recvbufAlloca.SetAlignment(recvbufAlign)
recvbuf = b.CreateGEP(allocaType, recvbufAlloca, []llvm.Value{ recvbuf = b.CreateGEP(allocaType, recvbufAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
@@ -181,7 +178,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// Create the states slice (allocated on the stack). // Create the states slice (allocated on the stack).
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates)) statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
statesAlloca, statesI8, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca") statesAlloca, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
for i, state := range selectStates { for i, state := range selectStates {
// Set each slice element to the appropriate channel. // Set each slice element to the appropriate channel.
gep := b.CreateGEP(statesAllocaType, statesAlloca, []llvm.Value{ gep := b.CreateGEP(statesAllocaType, statesAlloca, []llvm.Value{
@@ -202,7 +199,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// Stack-allocate operation structures. // Stack-allocate operation structures.
// If these were simply created as a slice, they would heap-allocate. // If these were simply created as a slice, they would heap-allocate.
chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates)) chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
chBlockAlloca, chBlockAllocaPtr, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca") chBlockAlloca, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false) chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
chBlockPtr := b.CreateGEP(chBlockAllocaType, chBlockAlloca, []llvm.Value{ chBlockPtr := b.CreateGEP(chBlockAllocaType, chBlockAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
@@ -216,7 +213,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
}, "select.result") }, "select.result")
// Terminate the lifetime of the operation structures. // Terminate the lifetime of the operation structures.
b.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize) b.emitLifetimeEnd(chBlockAlloca, chBlockSize)
} else { } else {
results = b.createRuntimeCall("tryChanSelect", []llvm.Value{ results = b.createRuntimeCall("tryChanSelect", []llvm.Value{
recvbuf, recvbuf,
@@ -225,7 +222,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
} }
// Terminate the lifetime of the states alloca. // Terminate the lifetime of the states alloca.
b.emitLifetimeEnd(statesI8, statesSize) b.emitLifetimeEnd(statesAlloca, statesSize)
// The result value does not include all the possible received values, // The result value does not include all the possible received values,
// because we can't load them in advance. Instead, the *ssa.Extract // because we can't load them in advance. Instead, the *ssa.Extract
@@ -265,7 +262,6 @@ func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
// it to the correct type, and dereference it. // it to the correct type, and dereference it.
recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)] recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := b.getLLVMType(expr.Type()) typ := b.getLLVMType(expr.Type())
ptr := b.CreateBitCast(recvbuf, llvm.PointerType(typ, 0), "") return b.CreateLoad(typ, recvbuf, "")
return b.CreateLoad(typ, ptr, "")
} }
} }
+27 -57
View File
@@ -75,10 +75,9 @@ type compilerContext struct {
machine llvm.TargetMachine machine llvm.TargetMachine
targetData llvm.TargetData targetData llvm.TargetData
intType llvm.Type intType llvm.Type
i8ptrType llvm.Type // for convenience dataPtrType llvm.Type // pointer in address space 0
rawVoidFuncType llvm.Type // for convenience funcPtrType llvm.Type // pointer in function address space (1 for AVR, 0 elsewhere)
funcPtrAddrSpace int funcPtrAddrSpace int
hasTypedPointers bool // for LLVM 14 backwards compatibility
uintptrType llvm.Type uintptrType llvm.Type
program *ssa.Program program *ssa.Program
diagnostics []error diagnostics []error
@@ -123,13 +122,12 @@ func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *C
} else { } else {
panic("unknown pointer size") panic("unknown pointer size")
} }
c.i8ptrType = llvm.PointerType(c.ctx.Int8Type(), 0) c.dataPtrType = llvm.PointerType(c.ctx.Int8Type(), 0)
dummyFuncType := llvm.FunctionType(c.ctx.VoidType(), nil, false) dummyFuncType := llvm.FunctionType(c.ctx.VoidType(), nil, false)
dummyFunc := llvm.AddFunction(c.mod, "tinygo.dummy", dummyFuncType) dummyFunc := llvm.AddFunction(c.mod, "tinygo.dummy", dummyFuncType)
c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace() c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace()
c.hasTypedPointers = c.i8ptrType != llvm.PointerType(c.ctx.Int16Type(), 0) // with opaque pointers, all pointers are the same type (LLVM 15+) c.funcPtrType = dummyFunc.Type()
c.rawVoidFuncType = dummyFunc.Type()
dummyFunc.EraseFromParentAsFunction() dummyFunc.EraseFromParentAsFunction()
return c return c
@@ -417,16 +415,14 @@ func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type {
case types.Uintptr: case types.Uintptr:
return c.uintptrType return c.uintptrType
case types.UnsafePointer: case types.UnsafePointer:
return c.i8ptrType return c.dataPtrType
default: default:
panic("unknown basic type: " + typ.String()) panic("unknown basic type: " + typ.String())
} }
case *types.Chan: case *types.Chan, *types.Map, *types.Pointer:
return llvm.PointerType(c.getLLVMRuntimeType("channel"), 0) return c.dataPtrType // all pointers are the same
case *types.Interface: case *types.Interface:
return c.getLLVMRuntimeType("_interface") return c.getLLVMRuntimeType("_interface")
case *types.Map:
return llvm.PointerType(c.getLLVMRuntimeType("hashmap"), 0)
case *types.Named: case *types.Named:
if st, ok := typ.Underlying().(*types.Struct); ok { if st, ok := typ.Underlying().(*types.Struct); ok {
// Structs are a special case. While other named types are ignored // Structs are a special case. While other named types are ignored
@@ -441,21 +437,11 @@ func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type {
return llvmType return llvmType
} }
return c.getLLVMType(typ.Underlying()) return c.getLLVMType(typ.Underlying())
case *types.Pointer:
if c.hasTypedPointers {
ptrTo := c.getLLVMType(typ.Elem())
return llvm.PointerType(ptrTo, 0)
}
return c.i8ptrType // all pointers are the same
case *types.Signature: // function value case *types.Signature: // function value
return c.getFuncType(typ) return c.getFuncType(typ)
case *types.Slice: case *types.Slice:
ptrType := c.i8ptrType
if c.hasTypedPointers {
ptrType = llvm.PointerType(c.getLLVMType(typ.Elem()), 0)
}
members := []llvm.Type{ members := []llvm.Type{
ptrType, c.dataPtrType,
c.uintptrType, // len c.uintptrType, // len
c.uintptrType, // cap c.uintptrType, // cap
} }
@@ -545,8 +531,8 @@ func (c *compilerContext) createDIType(typ types.Type) llvm.Metadata {
Elements: []llvm.Metadata{ Elements: []llvm.Metadata{
c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{
Name: "ptr", Name: "ptr",
SizeInBits: c.targetData.TypeAllocSize(c.i8ptrType) * 8, SizeInBits: c.targetData.TypeAllocSize(c.dataPtrType) * 8,
AlignInBits: uint32(c.targetData.ABITypeAlignment(c.i8ptrType)) * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(c.dataPtrType)) * 8,
OffsetInBits: 0, OffsetInBits: 0,
Type: c.getDIType(types.NewPointer(types.Typ[types.Byte])), Type: c.getDIType(types.NewPointer(types.Typ[types.Byte])),
}), }),
@@ -1548,21 +1534,18 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
src := argValues[0] src := argValues[0]
elems := argValues[1] elems := argValues[1]
srcBuf := b.CreateExtractValue(src, 0, "append.srcBuf") srcBuf := b.CreateExtractValue(src, 0, "append.srcBuf")
srcPtr := b.CreateBitCast(srcBuf, b.i8ptrType, "append.srcPtr")
srcLen := b.CreateExtractValue(src, 1, "append.srcLen") srcLen := b.CreateExtractValue(src, 1, "append.srcLen")
srcCap := b.CreateExtractValue(src, 2, "append.srcCap") srcCap := b.CreateExtractValue(src, 2, "append.srcCap")
elemsBuf := b.CreateExtractValue(elems, 0, "append.elemsBuf") elemsBuf := b.CreateExtractValue(elems, 0, "append.elemsBuf")
elemsPtr := b.CreateBitCast(elemsBuf, b.i8ptrType, "append.srcPtr")
elemsLen := b.CreateExtractValue(elems, 1, "append.elemsLen") elemsLen := b.CreateExtractValue(elems, 1, "append.elemsLen")
elemType := b.getLLVMType(argTypes[0].Underlying().(*types.Slice).Elem()) elemType := b.getLLVMType(argTypes[0].Underlying().(*types.Slice).Elem())
elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false) elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false)
result := b.createRuntimeCall("sliceAppend", []llvm.Value{srcPtr, elemsPtr, srcLen, srcCap, elemsLen, elemSize}, "append.new") result := b.createRuntimeCall("sliceAppend", []llvm.Value{srcBuf, elemsBuf, srcLen, srcCap, elemsLen, elemSize}, "append.new")
newPtr := b.CreateExtractValue(result, 0, "append.newPtr") newPtr := b.CreateExtractValue(result, 0, "append.newPtr")
newBuf := b.CreateBitCast(newPtr, srcBuf.Type(), "append.newBuf")
newLen := b.CreateExtractValue(result, 1, "append.newLen") newLen := b.CreateExtractValue(result, 1, "append.newLen")
newCap := b.CreateExtractValue(result, 2, "append.newCap") newCap := b.CreateExtractValue(result, 2, "append.newCap")
newSlice := llvm.Undef(src.Type()) newSlice := llvm.Undef(src.Type())
newSlice = b.CreateInsertValue(newSlice, newBuf, 0, "") newSlice = b.CreateInsertValue(newSlice, newPtr, 0, "")
newSlice = b.CreateInsertValue(newSlice, newLen, 1, "") newSlice = b.CreateInsertValue(newSlice, newLen, 1, "")
newSlice = b.CreateInsertValue(newSlice, newCap, 2, "") newSlice = b.CreateInsertValue(newSlice, newCap, 2, "")
return newSlice, nil return newSlice, nil
@@ -1610,9 +1593,6 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
// The pointer to the data to be cleared. // The pointer to the data to be cleared.
llvmBuf := b.CreateExtractValue(value, 0, "buf") llvmBuf := b.CreateExtractValue(value, 0, "buf")
if llvmBuf.Type() != b.i8ptrType { // compatibility with LLVM 14
llvmBuf = b.CreateBitCast(llvmBuf, b.i8ptrType, "")
}
// The length (in bytes) to be cleared. // The length (in bytes) to be cleared.
llvmLen := b.CreateExtractValue(value, 1, "len") llvmLen := b.CreateExtractValue(value, 1, "len")
@@ -1647,8 +1627,6 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
dstBuf := b.CreateExtractValue(dst, 0, "copy.dstArray") dstBuf := b.CreateExtractValue(dst, 0, "copy.dstArray")
srcBuf := b.CreateExtractValue(src, 0, "copy.srcArray") srcBuf := b.CreateExtractValue(src, 0, "copy.srcArray")
elemType := b.getLLVMType(argTypes[0].Underlying().(*types.Slice).Elem()) elemType := b.getLLVMType(argTypes[0].Underlying().(*types.Slice).Elem())
dstBuf = b.CreateBitCast(dstBuf, b.i8ptrType, "copy.dstPtr")
srcBuf = b.CreateBitCast(srcBuf, b.i8ptrType, "copy.srcPtr")
elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false) elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false)
return b.createRuntimeCall("sliceCopy", []llvm.Value{dstBuf, srcBuf, dstLen, srcLen, elemSize}, "copy.n"), nil return b.createRuntimeCall("sliceCopy", []llvm.Value{dstBuf, srcBuf, dstLen, srcLen, elemSize}, "copy.n"), nil
case "delete": case "delete":
@@ -1888,14 +1866,13 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
switch value := instr.Value.(type) { switch value := instr.Value.(type) {
case *ssa.Function: case *ssa.Function:
// Regular function call. No context is necessary. // Regular function call. No context is necessary.
context = llvm.Undef(b.i8ptrType) context = llvm.Undef(b.dataPtrType)
if info.variadic && len(fn.Params) == 0 { if info.variadic && len(fn.Params) == 0 {
// This matches Clang, see: https://godbolt.org/z/Gqv49xKMq // This matches Clang, see: https://godbolt.org/z/Gqv49xKMq
// Eventually we might be able to eliminate this special case // Eventually we might be able to eliminate this special case
// entirely. For details, see: // entirely. For details, see:
// https://discourse.llvm.org/t/rfc-enabling-wstrict-prototypes-by-default-in-c/60521 // https://discourse.llvm.org/t/rfc-enabling-wstrict-prototypes-by-default-in-c/60521
calleeType = llvm.FunctionType(callee.GlobalValueType().ReturnType(), nil, false) calleeType = llvm.FunctionType(callee.GlobalValueType().ReturnType(), nil, false)
callee = llvm.ConstBitCast(callee, llvm.PointerType(calleeType, b.funcPtrAddrSpace))
} }
case *ssa.MakeClosure: case *ssa.MakeClosure:
// A call on a func value, but the callee is trivial to find. For // A call on a func value, but the callee is trivial to find. For
@@ -1923,13 +1900,14 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
params = append(params, typecode) params = append(params, typecode)
callee = b.getInvokeFunction(instr) callee = b.getInvokeFunction(instr)
calleeType = callee.GlobalValueType() calleeType = callee.GlobalValueType()
context = llvm.Undef(b.i8ptrType) context = llvm.Undef(b.dataPtrType)
} else { } else {
// Function pointer. // Function pointer.
value := b.getValue(instr.Value, getPos(instr)) value := b.getValue(instr.Value, getPos(instr))
// This is a func value, which cannot be called directly. We have to // This is a func value, which cannot be called directly. We have to
// extract the function pointer and context first from the func value. // extract the function pointer and context first from the func value.
calleeType, callee, context = b.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature)) callee, context = b.decodeFuncValue(value)
calleeType = b.getLLVMFunctionType(instr.Value.Type().Underlying().(*types.Signature))
b.createNilCheck(instr.Value, callee, "fpcall") b.createNilCheck(instr.Value, callee, "fpcall")
} }
@@ -1962,7 +1940,7 @@ func (b *builder) getValue(expr ssa.Value, pos token.Pos) llvm.Value {
return llvm.Undef(b.getLLVMType(expr.Type())) return llvm.Undef(b.getLLVMType(expr.Type()))
} }
_, fn := b.getFunction(expr) _, fn := b.getFunction(expr)
return b.createFuncValue(fn, llvm.Undef(b.i8ptrType), expr.Signature) return b.createFuncValue(fn, llvm.Undef(b.dataPtrType), expr.Signature)
case *ssa.Global: case *ssa.Global:
value := b.getGlobal(expr) value := b.getGlobal(expr)
if value.IsNil() { if value.IsNil() {
@@ -2030,7 +2008,6 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
sizeValue := llvm.ConstInt(b.uintptrType, size, false) sizeValue := llvm.ConstInt(b.uintptrType, size, false)
layoutValue := b.createObjectLayout(typ, expr.Pos()) layoutValue := b.createObjectLayout(typ, expr.Pos())
buf := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, layoutValue}, expr.Comment) buf := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, layoutValue}, expr.Comment)
buf = b.CreateBitCast(buf, llvm.PointerType(typ, 0), "")
return buf, nil return buf, nil
} else { } else {
buf := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, expr.Comment) buf := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, expr.Comment)
@@ -2076,10 +2053,6 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
value = b.CreateInsertValue(value, field, i, "changetype.struct") value = b.CreateInsertValue(value, field, i, "changetype.struct")
} }
return value, nil return value, nil
case llvm.PointerTypeKind:
// This can happen with pointers to structs. This case is easy:
// simply bitcast the pointer to the destination type.
return b.CreateBitCast(x, llvmType, "changetype.pointer"), nil
default: default:
return llvm.Value{}, errors.New("todo: unknown ChangeType type: " + expr.X.Type().String()) return llvm.Value{}, errors.New("todo: unknown ChangeType type: " + expr.X.Type().String())
} }
@@ -2156,12 +2129,12 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
// Can't load directly from array (as index is non-constant), so // Can't load directly from array (as index is non-constant), so
// have to do it using an alloca+gep+load. // have to do it using an alloca+gep+load.
arrayType := collection.Type() arrayType := collection.Type()
alloca, allocaPtr, allocaSize := b.createTemporaryAlloca(arrayType, "index.alloca") alloca, allocaSize := b.createTemporaryAlloca(arrayType, "index.alloca")
b.CreateStore(collection, alloca) b.CreateStore(collection, alloca)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
ptr := b.CreateInBoundsGEP(arrayType, alloca, []llvm.Value{zero, index}, "index.gep") ptr := b.CreateInBoundsGEP(arrayType, alloca, []llvm.Value{zero, index}, "index.gep")
result := b.CreateLoad(arrayType.ElementType(), ptr, "index.load") result := b.CreateLoad(arrayType.ElementType(), ptr, "index.load")
b.emitLifetimeEnd(allocaPtr, allocaSize) b.emitLifetimeEnd(alloca, allocaSize)
return result, nil return result, nil
default: default:
panic("unknown *ssa.Index type") panic("unknown *ssa.Index type")
@@ -2264,7 +2237,6 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap") sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap")
layoutValue := b.createObjectLayout(llvmElemType, expr.Pos()) layoutValue := b.createObjectLayout(llvmElemType, expr.Pos())
slicePtr := b.createRuntimeCall("alloc", []llvm.Value{sliceSize, layoutValue}, "makeslice.buf") slicePtr := b.createRuntimeCall("alloc", []llvm.Value{sliceSize, layoutValue}, "makeslice.buf")
slicePtr = b.CreateBitCast(slicePtr, llvm.PointerType(llvmElemType, 0), "makeslice.array")
// Extend or truncate if necessary. This is safe as we've already done // Extend or truncate if necessary. This is safe as we've already done
// the bounds check. // the bounds check.
@@ -2310,7 +2282,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
default: default:
panic("unknown type in range: " + typ.String()) panic("unknown type in range: " + typ.String())
} }
it, _, _ := b.createTemporaryAlloca(iteratorType, "range.it") it, _ := b.createTemporaryAlloca(iteratorType, "range.it")
b.CreateStore(llvm.ConstNull(iteratorType), it) b.CreateStore(llvm.ConstNull(iteratorType), it)
return it, nil return it, nil
case *ssa.Select: case *ssa.Select:
@@ -2478,7 +2450,6 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
arrayLen := expr.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Array).Len() arrayLen := expr.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Array).Len()
b.createSliceToArrayPointerCheck(sliceLen, arrayLen) b.createSliceToArrayPointerCheck(sliceLen, arrayLen)
ptr := b.CreateExtractValue(slice, 0, "") ptr := b.CreateExtractValue(slice, 0, "")
ptr = b.CreateBitCast(ptr, b.getLLVMType(expr.Type()), "")
return ptr, nil return ptr, nil
case *ssa.TypeAssert: case *ssa.TypeAssert:
return b.createTypeAssert(expr), nil return b.createTypeAssert(expr), nil
@@ -2944,16 +2915,16 @@ func (c *compilerContext) createConst(expr *ssa.Const, pos token.Pos) llvm.Value
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
strPtr = llvm.ConstInBoundsGEP(globalType, global, []llvm.Value{zero, zero}) strPtr = llvm.ConstInBoundsGEP(globalType, global, []llvm.Value{zero, zero})
} else { } else {
strPtr = llvm.ConstNull(c.i8ptrType) strPtr = llvm.ConstNull(c.dataPtrType)
} }
strObj := llvm.ConstNamedStruct(c.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen}) strObj := llvm.ConstNamedStruct(c.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen})
return strObj return strObj
} else if typ.Kind() == types.UnsafePointer { } else if typ.Kind() == types.UnsafePointer {
if !expr.IsNil() { if !expr.IsNil() {
value, _ := constant.Uint64Val(constant.ToInt(expr.Value)) value, _ := constant.Uint64Val(constant.ToInt(expr.Value))
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, value, false), c.i8ptrType) return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, value, false), c.dataPtrType)
} }
return llvm.ConstNull(c.i8ptrType) return llvm.ConstNull(c.dataPtrType)
} else if typ.Info()&types.IsUnsigned != 0 { } else if typ.Info()&types.IsUnsigned != 0 {
n, _ := constant.Uint64Val(constant.ToInt(expr.Value)) n, _ := constant.Uint64Val(constant.ToInt(expr.Value))
return llvm.ConstInt(llvmType, n, false) return llvm.ConstInt(llvmType, n, false)
@@ -2997,7 +2968,7 @@ func (c *compilerContext) createConst(expr *ssa.Const, pos token.Pos) llvm.Value
// Create a generic nil interface with no dynamic type (typecode=0). // Create a generic nil interface with no dynamic type (typecode=0).
fields := []llvm.Value{ fields := []llvm.Value{
llvm.ConstInt(c.uintptrType, 0, false), llvm.ConstInt(c.uintptrType, 0, false),
llvm.ConstPointerNull(c.i8ptrType), llvm.ConstPointerNull(c.dataPtrType),
} }
return llvm.ConstNamedStruct(c.getLLVMRuntimeType("_interface"), fields) return llvm.ConstNamedStruct(c.getLLVMRuntimeType("_interface"), fields)
case *types.Pointer: case *types.Pointer:
@@ -3014,8 +2985,7 @@ func (c *compilerContext) createConst(expr *ssa.Const, pos token.Pos) llvm.Value
if expr.Value != nil { if expr.Value != nil {
panic("expected nil slice constant") panic("expected nil slice constant")
} }
elemType := c.getLLVMType(typ.Elem()) llvmPtr := llvm.ConstPointerNull(c.dataPtrType)
llvmPtr := llvm.ConstPointerNull(llvm.PointerType(elemType, 0))
llvmLen := llvm.ConstInt(c.uintptrType, 0, false) llvmLen := llvm.ConstInt(c.uintptrType, 0, false)
slice := c.ctx.ConstStruct([]llvm.Value{ slice := c.ctx.ConstStruct([]llvm.Value{
llvmPtr, // backing array llvmPtr, // backing array
@@ -3056,7 +3026,7 @@ func (b *builder) createConvert(typeFrom, typeTo types.Type, value llvm.Value, p
// Conversion between pointers and unsafe.Pointer. // Conversion between pointers and unsafe.Pointer.
if isPtrFrom && isPtrTo { if isPtrFrom && isPtrTo {
return b.CreateBitCast(value, llvmTypeTo, ""), nil return value, nil
} }
switch typeTo := typeTo.Underlying().(type) { switch typeTo := typeTo.Underlying().(type) {
@@ -3295,7 +3265,7 @@ func (b *builder) createUnOp(unop *ssa.UnOp) (llvm.Value, error) {
if fn.IsNil() { if fn.IsNil() {
return llvm.Value{}, b.makeError(unop.Pos(), "cgo function not found: "+name) return llvm.Value{}, b.makeError(unop.Pos(), "cgo function not found: "+name)
} }
return b.CreateBitCast(fn, b.i8ptrType, ""), nil return fn, nil
} else { } else {
b.createNilCheck(unop.X, x, "deref") b.createNilCheck(unop.X, x, "deref")
load := b.CreateLoad(valueType, x, "") load := b.CreateLoad(valueType, x, "")
+8 -10
View File
@@ -91,14 +91,12 @@ func TestCompiler(t *testing.T) {
} }
// Optimize IR a little. // Optimize IR a little.
funcPasses := llvm.NewFunctionPassManagerForModule(mod) passOptions := llvm.NewPassBuilderOptions()
defer funcPasses.Dispose() defer passOptions.Dispose()
funcPasses.AddInstructionCombiningPass() err = mod.RunPasses("instcombine", llvm.TargetMachine{}, passOptions)
funcPasses.InitializeFunc() if err != nil {
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { t.Error(err)
funcPasses.RunFunc(fn)
} }
funcPasses.FinalizeFunc()
outFilePrefix := tc.file[:len(tc.file)-3] outFilePrefix := tc.file[:len(tc.file)-3]
if tc.target != "" { if tc.target != "" {
@@ -169,9 +167,9 @@ func filterIrrelevantIRLines(lines []string) []string {
if strings.HasPrefix(line, "source_filename = ") { if strings.HasPrefix(line, "source_filename = ") {
continue continue
} }
if llvmVersion < 14 && strings.HasPrefix(line, "target datalayout = ") { if llvmVersion < 15 && strings.HasPrefix(line, "target datalayout = ") {
// The datalayout string may vary betewen LLVM versions. // The datalayout string may vary betewen LLVM versions.
// Right now test outputs are for LLVM 14 and higher. // Right now test outputs are for LLVM 15 and higher.
continue continue
} }
out = append(out, line) out = append(out, line)
@@ -245,7 +243,7 @@ func testCompilePackage(t *testing.T, options *compileopts.Options, file string)
defer machine.Dispose() defer machine.Dispose()
// Load entire program AST into memory. // Load entire program AST into memory.
lprogram, err := loader.Load(config, "./testdata/"+file, config.ClangHeaders, types.Config{ lprogram, err := loader.Load(config, "./testdata/"+file, types.Config{
Sizes: Sizes(machine), Sizes: Sizes(machine),
}) })
if err != nil { if err != nil {
+24 -33
View File
@@ -60,9 +60,8 @@ func (b *builder) deferInitFunc() {
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin) b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
// Create defer list pointer. // Create defer list pointer.
deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0) b.deferPtr = b.CreateAlloca(b.dataPtrType, "deferPtr")
b.deferPtr = b.CreateAlloca(deferType, "deferPtr") b.CreateStore(llvm.ConstPointerNull(b.dataPtrType), b.deferPtr)
b.CreateStore(llvm.ConstPointerNull(deferType), b.deferPtr)
if b.hasDeferFrame() { if b.hasDeferFrame() {
// Set up the defer frame with the current stack pointer. // Set up the defer frame with the current stack pointer.
@@ -249,8 +248,7 @@ func isInLoop(start *ssa.BasicBlock) bool {
func (b *builder) createDefer(instr *ssa.Defer) { func (b *builder) createDefer(instr *ssa.Defer) {
// The pointer to the previous defer struct, which we will replace to // The pointer to the previous defer struct, which we will replace to
// make a linked list. // make a linked list.
deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0) next := b.CreateLoad(b.dataPtrType, b.deferPtr, "defer.next")
next := b.CreateLoad(deferType, b.deferPtr, "defer.next")
var values []llvm.Value var values []llvm.Value
valueTypes := []llvm.Type{b.uintptrType, next.Type()} valueTypes := []llvm.Type{b.uintptrType, next.Type()}
@@ -271,7 +269,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode") typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver") receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = []llvm.Value{callback, next, typecode, receiverValue} values = []llvm.Value{callback, next, typecode, receiverValue}
valueTypes = append(valueTypes, b.i8ptrType, b.i8ptrType) valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType)
for _, arg := range instr.Call.Args { for _, arg := range instr.Call.Args {
val := b.getValue(arg, getPos(instr)) val := b.getValue(arg, getPos(instr))
values = append(values, val) values = append(values, val)
@@ -391,9 +389,8 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// This may be hit a variable number of times, so use a heap allocation. // This may be hit a variable number of times, so use a heap allocation.
size := b.targetData.TypeAllocSize(deferredCallType) size := b.targetData.TypeAllocSize(deferredCallType)
sizeValue := llvm.ConstInt(b.uintptrType, size, false) sizeValue := llvm.ConstInt(b.uintptrType, size, false)
nilPtr := llvm.ConstNull(b.i8ptrType) nilPtr := llvm.ConstNull(b.dataPtrType)
allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call") alloca = b.createRuntimeCall("alloc", []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferredCallType, 0), "defer.alloc")
} }
if b.NeedsStackObjects { if b.NeedsStackObjects {
b.trackPointer(alloca) b.trackPointer(alloca)
@@ -401,14 +398,12 @@ func (b *builder) createDefer(instr *ssa.Defer) {
b.CreateStore(deferredCall, alloca) b.CreateStore(deferredCall, alloca)
// Push it on top of the linked list by replacing deferPtr. // Push it on top of the linked list by replacing deferPtr.
allocaCast := b.CreateBitCast(alloca, next.Type(), "defer.alloca.cast") b.CreateStore(alloca, b.deferPtr)
b.CreateStore(allocaCast, b.deferPtr)
} }
// createRunDefers emits code to run all deferred functions. // createRunDefers emits code to run all deferred functions.
func (b *builder) createRunDefers() { func (b *builder) createRunDefers() {
deferType := b.getLLVMRuntimeType("_defer") deferType := b.getLLVMRuntimeType("_defer")
deferPtrType := llvm.PointerType(deferType, 0)
// Add a loop like the following: // Add a loop like the following:
// for stack != nil { // for stack != nil {
@@ -435,7 +430,7 @@ func (b *builder) createRunDefers() {
// Create loop head: // Create loop head:
// for stack != nil { // for stack != nil {
b.SetInsertPointAtEnd(loophead) b.SetInsertPointAtEnd(loophead)
deferData := b.CreateLoad(deferPtrType, b.deferPtr, "") deferData := b.CreateLoad(b.dataPtrType, b.deferPtr, "")
stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil") stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
b.CreateCondBr(stackIsNil, end, loop) b.CreateCondBr(stackIsNil, end, loop)
@@ -448,7 +443,7 @@ func (b *builder) createRunDefers() {
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field
}, "stack.next.gep") }, "stack.next.gep")
nextStack := b.CreateLoad(deferPtrType, nextStackGEP, "stack.next") nextStack := b.CreateLoad(b.dataPtrType, nextStackGEP, "stack.next")
b.CreateStore(nextStack, b.deferPtr) b.CreateStore(nextStack, b.deferPtr)
gep := b.CreateInBoundsGEP(deferType, deferData, []llvm.Value{ gep := b.CreateInBoundsGEP(deferType, deferData, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
@@ -469,28 +464,26 @@ func (b *builder) createRunDefers() {
// Call on an value or interface value. // Call on an value or interface value.
// Get the real defer struct type and cast to it. // Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)} valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
if !callback.IsInvoke() { if !callback.IsInvoke() {
//Expect funcValue to be passed through the deferred call. //Expect funcValue to be passed through the deferred call.
valueTypes = append(valueTypes, b.getFuncType(callback.Signature())) valueTypes = append(valueTypes, b.getFuncType(callback.Signature()))
} else { } else {
//Expect typecode //Expect typecode
valueTypes = append(valueTypes, b.i8ptrType, b.i8ptrType) valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType)
} }
for _, arg := range callback.Args { for _, arg := range callback.Args {
valueTypes = append(valueTypes, b.getLLVMType(arg.Type())) valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
} }
deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// Extract the params from the struct (including receiver). // Extract the params from the struct (including receiver).
forwardParams := []llvm.Value{} forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
deferredCallType := b.ctx.StructType(valueTypes, false)
for i := 2; i < len(valueTypes); i++ { for i := 2; i < len(valueTypes); i++ {
gep := b.CreateInBoundsGEP(deferredCallType, deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep") gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
forwardParam := b.CreateLoad(valueTypes[i], gep, "param") forwardParam := b.CreateLoad(valueTypes[i], gep, "param")
forwardParams = append(forwardParams, forwardParam) forwardParams = append(forwardParams, forwardParam)
} }
@@ -505,7 +498,8 @@ func (b *builder) createRunDefers() {
//Get function pointer and context //Get function pointer and context
var context llvm.Value var context llvm.Value
fnType, fnPtr, context = b.decodeFuncValue(funcValue, callback.Signature()) fnPtr, context = b.decodeFuncValue(funcValue)
fnType = b.getLLVMFunctionType(callback.Signature())
//Pass context //Pass context
forwardParams = append(forwardParams, context) forwardParams = append(forwardParams, context)
@@ -519,7 +513,7 @@ func (b *builder) createRunDefers() {
// Add the context parameter. An interface call cannot also be a // Add the context parameter. An interface call cannot also be a
// closure but we have to supply the parameter anyway for platforms // closure but we have to supply the parameter anyway for platforms
// with a strict calling convention. // with a strict calling convention.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType))
} }
b.createCall(fnType, fnPtr, forwardParams, "") b.createCall(fnType, fnPtr, forwardParams, "")
@@ -528,18 +522,17 @@ func (b *builder) createRunDefers() {
// Direct call. // Direct call.
// Get the real defer struct type and cast to it. // Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)} valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
for _, param := range getParams(callback.Signature) { for _, param := range getParams(callback.Signature) {
valueTypes = append(valueTypes, b.getLLVMType(param.Type())) valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
} }
deferredCallType := b.ctx.StructType(valueTypes, false) deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// Extract the params from the struct. // Extract the params from the struct.
forwardParams := []llvm.Value{} forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := range getParams(callback.Signature) { for i := range getParams(callback.Signature) {
gep := b.CreateInBoundsGEP(deferredCallType, deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep") gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param") forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param")
forwardParams = append(forwardParams, forwardParam) forwardParams = append(forwardParams, forwardParam)
} }
@@ -549,7 +542,7 @@ func (b *builder) createRunDefers() {
if !b.getFunctionInfo(callback).exported { if !b.getFunctionInfo(callback).exported {
// Add the context parameter. We know it is ignored by the receiving // Add the context parameter. We know it is ignored by the receiving
// function, but we have to pass one anyway. // function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType))
} }
// Call real function. // Call real function.
@@ -559,20 +552,19 @@ func (b *builder) createRunDefers() {
case *ssa.MakeClosure: case *ssa.MakeClosure:
// Get the real defer struct type and cast to it. // Get the real defer struct type and cast to it.
fn := callback.Fn.(*ssa.Function) fn := callback.Fn.(*ssa.Function)
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)} valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
params := fn.Signature.Params() params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ { for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type())) valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
} }
valueTypes = append(valueTypes, b.i8ptrType) // closure valueTypes = append(valueTypes, b.dataPtrType) // closure
deferredCallType := b.ctx.StructType(valueTypes, false) deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// Extract the params from the struct. // Extract the params from the struct.
forwardParams := []llvm.Value{} forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 2; i < len(valueTypes); i++ { for i := 2; i < len(valueTypes); i++ {
gep := b.CreateInBoundsGEP(deferredCallType, deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "") gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
forwardParam := b.CreateLoad(valueTypes[i], gep, "param") forwardParam := b.CreateLoad(valueTypes[i], gep, "param")
forwardParams = append(forwardParams, forwardParam) forwardParams = append(forwardParams, forwardParam)
} }
@@ -584,7 +576,7 @@ func (b *builder) createRunDefers() {
db := b.deferBuiltinFuncs[callback] db := b.deferBuiltinFuncs[callback]
//Get parameter types //Get parameter types
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)} valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
//Get signature from call results //Get signature from call results
params := callback.Type().Underlying().(*types.Signature).Params() params := callback.Type().Underlying().(*types.Signature).Params()
@@ -593,13 +585,12 @@ func (b *builder) createRunDefers() {
} }
deferredCallType := b.ctx.StructType(valueTypes, false) deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// Extract the params from the struct. // Extract the params from the struct.
var argValues []llvm.Value var argValues []llvm.Value
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 0; i < params.Len(); i++ { for i := 0; i < params.Len(); i++ {
gep := b.CreateInBoundsGEP(deferredCallType, deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep") gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param") forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param")
argValues = append(argValues, forwardParam) argValues = append(argValues, forwardParam)
} }
+10 -39
View File
@@ -13,35 +13,14 @@ import (
// createFuncValue creates a function value from a raw function pointer with no // createFuncValue creates a function value from a raw function pointer with no
// context. // context.
func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value { func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
return b.compilerContext.createFuncValue(b.Builder, funcPtr, context, sig)
}
// createFuncValue creates a function value from a raw function pointer with no
// context.
func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
// Closure is: {context, function pointer} // Closure is: {context, function pointer}
funcValueScalar := llvm.ConstBitCast(funcPtr, c.rawVoidFuncType) funcValueType := b.getFuncType(sig)
funcValueType := c.getFuncType(sig)
funcValue := llvm.Undef(funcValueType) funcValue := llvm.Undef(funcValueType)
funcValue = builder.CreateInsertValue(funcValue, context, 0, "") funcValue = b.CreateInsertValue(funcValue, context, 0, "")
funcValue = builder.CreateInsertValue(funcValue, funcValueScalar, 1, "") funcValue = b.CreateInsertValue(funcValue, funcPtr, 1, "")
return funcValue return funcValue
} }
// getFuncSignatureID returns a new external global for a given signature. This
// global reference is not real, it is only used during func lowering to assign
// signature types to functions and will then be removed.
func (c *compilerContext) getFuncSignatureID(sig *types.Signature) llvm.Value {
s, _ := getTypeCodeName(sig)
sigGlobalName := "reflect/types.funcid:" + s
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
if sigGlobal.IsNil() {
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
sigGlobal.SetGlobalConstant(true)
}
return sigGlobal
}
// extractFuncScalar returns some scalar that can be used in comparisons. It is // extractFuncScalar returns some scalar that can be used in comparisons. It is
// a cheap operation. // a cheap operation.
func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value { func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value {
@@ -55,28 +34,20 @@ func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value {
} }
// decodeFuncValue extracts the context and the function pointer from this func // decodeFuncValue extracts the context and the function pointer from this func
// value. This may be an expensive operation. // value.
func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcType llvm.Type, funcPtr, context llvm.Value) { func (b *builder) decodeFuncValue(funcValue llvm.Value) (funcPtr, context llvm.Value) {
context = b.CreateExtractValue(funcValue, 0, "") context = b.CreateExtractValue(funcValue, 0, "")
funcPtr = b.CreateExtractValue(funcValue, 1, "") funcPtr = b.CreateExtractValue(funcValue, 1, "")
if !funcPtr.IsAConstantExpr().IsNil() && funcPtr.Opcode() == llvm.BitCast {
funcPtr = funcPtr.Operand(0) // needed for LLVM 14 (no opaque pointers)
}
if sig != nil {
funcType = b.getRawFuncType(sig)
llvmSig := llvm.PointerType(funcType, b.funcPtrAddrSpace)
funcPtr = b.CreateBitCast(funcPtr, llvmSig, "")
}
return return
} }
// getFuncType returns the type of a func value given a signature. // getFuncType returns the type of a func value given a signature.
func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type { func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
return c.ctx.StructType([]llvm.Type{c.i8ptrType, c.rawVoidFuncType}, false) return c.ctx.StructType([]llvm.Type{c.dataPtrType, c.funcPtrType}, false)
} }
// getRawFuncType returns a LLVM function type for a given signature. // getLLVMFunctionType returns a LLVM function type for a given signature.
func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type { func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type {
// Get the return type. // Get the return type.
var returnType llvm.Type var returnType llvm.Type
switch typ.Results().Len() { switch typ.Results().Len() {
@@ -104,7 +75,7 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
if recv.StructName() == "runtime._interface" { if recv.StructName() == "runtime._interface" {
// This is a call on an interface, not a concrete type. // This is a call on an interface, not a concrete type.
// The receiver is not an interface, but a i8* type. // The receiver is not an interface, but a i8* type.
recv = c.i8ptrType recv = c.dataPtrType
} }
for _, info := range c.expandFormalParamType(recv, "", nil) { for _, info := range c.expandFormalParamType(recv, "", nil) {
paramTypes = append(paramTypes, info.llvmType) paramTypes = append(paramTypes, info.llvmType)
@@ -117,7 +88,7 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
} }
} }
// All functions take these parameters at the end. // All functions take these parameters at the end.
paramTypes = append(paramTypes, c.i8ptrType) // context paramTypes = append(paramTypes, c.dataPtrType) // context
// Make a func type out of the signature. // Make a func type out of the signature.
return llvm.FunctionType(returnType, paramTypes, false) return llvm.FunctionType(returnType, paramTypes, false)
-3
View File
@@ -81,9 +81,6 @@ func (b *builder) trackValue(value llvm.Value) {
// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner // trackPointer creates a call to runtime.trackPointer, bitcasting the poitner
// first if needed. The input value must be of LLVM pointer type. // first if needed. The input value must be of LLVM pointer type.
func (b *builder) trackPointer(value llvm.Value) { func (b *builder) trackPointer(value llvm.Value) {
if value.Type() != b.i8ptrType {
value = b.CreateBitCast(value, b.i8ptrType, "")
}
b.createRuntimeCall("trackPointer", []llvm.Value{value, b.stackChainAlloca}, "") b.createRuntimeCall("trackPointer", []llvm.Value{value, b.stackChainAlloca}, "")
} }
+13 -12
View File
@@ -21,7 +21,7 @@ func (b *builder) createGo(instr *ssa.Go) {
var prefix string var prefix string
var funcPtr llvm.Value var funcPtr llvm.Value
var funcPtrType llvm.Type var funcType llvm.Type
hasContext := false hasContext := false
if callee := instr.Call.StaticCallee(); callee != nil { if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new // Static callee is known. This makes it easier to start a new
@@ -42,7 +42,7 @@ func (b *builder) createGo(instr *ssa.Go) {
params = append(params, context) // context parameter params = append(params, context) // context parameter
hasContext = true hasContext = true
} }
funcPtrType, funcPtr = b.getFunction(callee) funcType, funcPtr = b.getFunction(callee)
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok { } else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
// We cheat. None of the builtins do any long or blocking operation, so // We cheat. None of the builtins do any long or blocking operation, so
// we might as well run these builtins right away without the program // we might as well run these builtins right away without the program
@@ -80,7 +80,7 @@ func (b *builder) createGo(instr *ssa.Go) {
itfTypeCode := b.CreateExtractValue(itf, 0, "") itfTypeCode := b.CreateExtractValue(itf, 0, "")
itfValue := b.CreateExtractValue(itf, 1, "") itfValue := b.CreateExtractValue(itf, 1, "")
funcPtr = b.getInvokeFunction(&instr.Call) funcPtr = b.getInvokeFunction(&instr.Call)
funcPtrType = funcPtr.GlobalValueType() funcType = funcPtr.GlobalValueType()
params = append([]llvm.Value{itfValue}, params...) // start with receiver params = append([]llvm.Value{itfValue}, params...) // start with receiver
params = append(params, itfTypeCode) // end with typecode params = append(params, itfTypeCode) // end with typecode
} else { } else {
@@ -90,7 +90,8 @@ func (b *builder) createGo(instr *ssa.Go) {
// * The function context, for closures. // * The function context, for closures.
// * The function pointer (for tasks). // * The function pointer (for tasks).
var context llvm.Value var context llvm.Value
funcPtrType, funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value, getPos(instr)), instr.Call.Value.Type().Underlying().(*types.Signature)) funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value, getPos(instr)))
funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature))
params = append(params, context, funcPtr) params = append(params, context, funcPtr)
hasContext = true hasContext = true
prefix = b.fn.RelString(nil) prefix = b.fn.RelString(nil)
@@ -98,14 +99,14 @@ func (b *builder) createGo(instr *ssa.Go) {
paramBundle := b.emitPointerPack(params) paramBundle := b.emitPointerPack(params)
var stackSize llvm.Value var stackSize llvm.Value
callee := b.createGoroutineStartWrapper(funcPtrType, funcPtr, prefix, hasContext, instr.Pos()) callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, instr.Pos())
if b.AutomaticStackSize { if b.AutomaticStackSize {
// The stack size is not known until after linking. Call a dummy // The stack size is not known until after linking. Call a dummy
// function that will be replaced with a load from a special ELF // function that will be replaced with a load from a special ELF
// section that contains the stack size (and is modified after // section that contains the stack size (and is modified after
// linking). // linking).
stackSizeFnType, stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function)) stackSizeFnType, stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function))
stackSize = b.createCall(stackSizeFnType, stackSizeFn, []llvm.Value{callee, llvm.Undef(b.i8ptrType)}, "stacksize") stackSize = b.createCall(stackSizeFnType, stackSizeFn, []llvm.Value{callee, llvm.Undef(b.dataPtrType)}, "stacksize")
} else { } else {
// The stack size is fixed at compile time. By emitting it here as a // The stack size is fixed at compile time. By emitting it here as a
// constant, it can be optimized. // constant, it can be optimized.
@@ -115,7 +116,7 @@ func (b *builder) createGo(instr *ssa.Go) {
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false) stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
} }
fnType, start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function)) fnType, start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType)}, "") b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.dataPtrType)}, "")
} }
// createGoroutineStartWrapper creates a wrapper for the task-based // createGoroutineStartWrapper creates a wrapper for the task-based
@@ -165,7 +166,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
} }
// Create the wrapper. // Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false) wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false)
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType) wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
c.addStandardAttributes(wrapper) c.addStandardAttributes(wrapper)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
@@ -203,7 +204,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
} }
params := b.emitPointerUnpack(wrapper.Param(0), paramTypes) params := b.emitPointerUnpack(wrapper.Param(0), paramTypes)
if !hasContext { if !hasContext {
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy context parameter params = append(params, llvm.Undef(c.dataPtrType)) // add dummy context parameter
} }
// Create the call. // Create the call.
@@ -211,7 +212,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
if c.Scheduler == "asyncify" { if c.Scheduler == "asyncify" {
b.CreateCall(deadlockType, deadlock, []llvm.Value{ b.CreateCall(deadlockType, deadlock, []llvm.Value{
llvm.Undef(c.i8ptrType), llvm.Undef(c.dataPtrType),
}, "") }, "")
} }
@@ -234,7 +235,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
// merged into one. // merged into one.
// Create the wrapper. // Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false) wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false)
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType) wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
c.addStandardAttributes(wrapper) c.addStandardAttributes(wrapper)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
@@ -279,7 +280,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
if c.Scheduler == "asyncify" { if c.Scheduler == "asyncify" {
b.CreateCall(deadlockType, deadlock, []llvm.Value{ b.CreateCall(deadlockType, deadlock, []llvm.Value{
llvm.Undef(c.i8ptrType), llvm.Undef(c.dataPtrType),
}, "") }, "")
} }
} }
+2 -2
View File
@@ -99,8 +99,8 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
case llvm.IntegerTypeKind: case llvm.IntegerTypeKind:
constraints = append(constraints, "r") constraints = append(constraints, "r")
case llvm.PointerTypeKind: case llvm.PointerTypeKind:
// Memory references require a type in LLVM 14, probably as a // Memory references require a type starting with LLVM 14,
// preparation for opaque pointers. // probably as a preparation for opaque pointers.
err = b.makeError(instr.Pos(), "support for pointer operands was dropped in TinyGo 0.23") err = b.makeError(instr.Pos(), "support for pointer operands was dropped in TinyGo 0.23")
return s return s
default: default:
+6 -6
View File
@@ -414,10 +414,10 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
}, typeFields...) }, typeFields...)
if hasMethodSet { if hasMethodSet {
typeFields = append([]llvm.Value{ typeFields = append([]llvm.Value{
llvm.ConstBitCast(c.getTypeMethodSet(typ), c.i8ptrType), c.getTypeMethodSet(typ),
}, typeFields...) }, typeFields...)
} }
alignment := c.targetData.TypeAllocSize(c.i8ptrType) alignment := c.targetData.TypeAllocSize(c.dataPtrType)
if alignment < 4 { if alignment < 4 {
alignment = 4 alignment = 4
} }
@@ -628,7 +628,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
// Construct global value. // Construct global value.
globalValue := c.ctx.ConstStruct([]llvm.Value{ globalValue := c.ctx.ConstStruct([]llvm.Value{
llvm.ConstInt(c.uintptrType, uint64(ms.Len()), false), llvm.ConstInt(c.uintptrType, uint64(ms.Len()), false),
llvm.ConstArray(c.i8ptrType, signatures), llvm.ConstArray(c.dataPtrType, signatures),
c.ctx.ConstStruct(wrappers, false), c.ctx.ConstStruct(wrappers, false),
}, false) }, false)
global = llvm.AddGlobal(c.mod, globalValue.Type(), globalName) global = llvm.AddGlobal(c.mod, globalValue.Type(), globalName)
@@ -779,7 +779,7 @@ func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) ll
fnName := s + ".$typeassert" fnName := s + ".$typeassert"
llvmFn := c.mod.NamedFunction(fnName) llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() { if llvmFn.IsNil() {
llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.i8ptrType}, false) llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.dataPtrType}, false)
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType) llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
c.addStandardDeclaredAttributes(llvmFn) c.addStandardDeclaredAttributes(llvmFn)
methods := c.getMethodsString(assertedType.Underlying().(*types.Interface)) methods := c.getMethodsString(assertedType.Underlying().(*types.Interface))
@@ -802,7 +802,7 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
paramTuple = append(paramTuple, sig.Params().At(i)) paramTuple = append(paramTuple, sig.Params().At(i))
} }
paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.UnsafePointer])) paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.UnsafePointer]))
llvmFnType := c.getRawFuncType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false)) llvmFnType := c.getLLVMFunctionType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false))
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType) llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
c.addStandardDeclaredAttributes(llvmFn) c.addStandardDeclaredAttributes(llvmFn)
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method))) llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method)))
@@ -842,7 +842,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
} }
// create wrapper function // create wrapper function
paramTypes := append([]llvm.Type{c.i8ptrType}, llvmFnType.ParamTypes()[len(expandedReceiverType):]...) paramTypes := append([]llvm.Type{c.dataPtrType}, llvmFnType.ParamTypes()[len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(llvmFnType.ReturnType(), paramTypes, false) wrapFnType := llvm.FunctionType(llvmFnType.ReturnType(), paramTypes, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType) wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
c.addStandardAttributes(wrapper) c.addStandardAttributes(wrapper)
+1 -1
View File
@@ -36,7 +36,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// Fall back to a generic error. // Fall back to a generic error.
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant") return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant")
} }
_, funcRawPtr, funcContext := b.decodeFuncValue(funcValue, nil) funcRawPtr, funcContext := b.decodeFuncValue(funcValue)
funcPtr := llvm.ConstPtrToInt(funcRawPtr, b.uintptrType) funcPtr := llvm.ConstPtrToInt(funcRawPtr, b.uintptrType)
// Create a new global of type runtime/interrupt.handle. Globals of this // Create a new global of type runtime/interrupt.handle. Globals of this
+3 -10
View File
@@ -7,7 +7,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -48,12 +47,9 @@ func (b *builder) defineIntrinsicFunction() {
func (b *builder) createMemoryCopyImpl() { func (b *builder) createMemoryCopyImpl() {
b.createFunctionStart(true) b.createFunctionStart(true)
fnName := "llvm." + b.fn.Name() + ".p0.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth()) fnName := "llvm." + b.fn.Name() + ".p0.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
if llvmutil.Major() < 15 { // compatibility with LLVM 14
fnName = "llvm." + b.fn.Name() + ".p0i8.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
}
llvmFn := b.mod.NamedFunction(fnName) llvmFn := b.mod.NamedFunction(fnName)
if llvmFn.IsNil() { if llvmFn.IsNil() {
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.i8ptrType, b.uintptrType, b.ctx.Int1Type()}, false) fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType, b.dataPtrType, b.uintptrType, b.ctx.Int1Type()}, false)
llvmFn = llvm.AddFunction(b.mod, fnName, fnType) llvmFn = llvm.AddFunction(b.mod, fnName, fnType)
} }
var params []llvm.Value var params []llvm.Value
@@ -84,12 +80,9 @@ func (b *builder) createMemoryZeroImpl() {
// Return the llvm.memset.p0.i8 function declaration. // Return the llvm.memset.p0.i8 function declaration.
func (c *compilerContext) getMemsetFunc() llvm.Value { func (c *compilerContext) getMemsetFunc() llvm.Value {
fnName := "llvm.memset.p0.i" + strconv.Itoa(c.uintptrType.IntTypeWidth()) fnName := "llvm.memset.p0.i" + strconv.Itoa(c.uintptrType.IntTypeWidth())
if llvmutil.Major() < 15 { // compatibility with LLVM 14
fnName = "llvm.memset.p0i8.i" + strconv.Itoa(c.uintptrType.IntTypeWidth())
}
llvmFn := c.mod.NamedFunction(fnName) llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() { if llvmFn.IsNil() {
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType, c.ctx.Int8Type(), c.uintptrType, c.ctx.Int1Type()}, false) fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType, c.ctx.Int8Type(), c.uintptrType, c.ctx.Int1Type()}, false)
llvmFn = llvm.AddFunction(c.mod, fnName, fnType) llvmFn = llvm.AddFunction(c.mod, fnName, fnType)
} }
return llvmFn return llvmFn
@@ -111,7 +104,7 @@ func (b *builder) createKeepAliveImpl() {
// //
// It should be portable to basically everything as the "r" register type // It should be portable to basically everything as the "r" register type
// exists basically everywhere. // exists basically everywhere.
asmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType}, false) asmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType}, false)
asmFn := llvm.InlineAsm(asmType, "", "r", true, false, 0, false) asmFn := llvm.InlineAsm(asmType, "", "r", true, false, 0, false)
b.createCall(asmType, asmFn, []llvm.Value{pointerValue}, "") b.createCall(asmType, asmFn, []llvm.Value{pointerValue}, "")
+39 -44
View File
@@ -20,7 +20,7 @@ import (
// //
// This is useful for creating temporary allocas for intrinsics. Don't forget to // This is useful for creating temporary allocas for intrinsics. Don't forget to
// end the lifetime using emitLifetimeEnd after you're done with it. // end the lifetime using emitLifetimeEnd after you're done with it.
func (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) { func (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, size llvm.Value) {
return llvmutil.CreateTemporaryAlloca(b.Builder, b.mod, t, name) return llvmutil.CreateTemporaryAlloca(b.Builder, b.mod, t, name)
} }
@@ -63,47 +63,45 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
// Allocate memory for the packed data. // Allocate memory for the packed data.
size := b.targetData.TypeAllocSize(packedType) size := b.targetData.TypeAllocSize(packedType)
if size == 0 { if size == 0 {
return llvm.ConstPointerNull(b.i8ptrType) return llvm.ConstPointerNull(b.dataPtrType)
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind { } else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
return b.CreateBitCast(values[0], b.i8ptrType, "pack.ptr") return values[0]
} else if size <= b.targetData.TypeAllocSize(b.i8ptrType) { } else if size <= b.targetData.TypeAllocSize(b.dataPtrType) {
// Packed data fits in a pointer, so store it directly inside the // Packed data fits in a pointer, so store it directly inside the
// pointer. // pointer.
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind { if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
// Try to keep this cast in SSA form. // Try to keep this cast in SSA form.
return b.CreateIntToPtr(values[0], b.i8ptrType, "pack.int") return b.CreateIntToPtr(values[0], b.dataPtrType, "pack.int")
} }
// Because packedType is a struct and we have to cast it to a *i8, store // Because packedType is a struct and we have to cast it to a *i8, store
// it in a *i8 alloca first and load the *i8 value from there. This is // it in a *i8 alloca first and load the *i8 value from there. This is
// effectively a bitcast. // effectively a bitcast.
packedAlloc, _, _ := b.createTemporaryAlloca(b.i8ptrType, "") packedAlloc, _ := b.createTemporaryAlloca(b.dataPtrType, "")
if size < b.targetData.TypeAllocSize(b.i8ptrType) { if size < b.targetData.TypeAllocSize(b.dataPtrType) {
// The alloca is bigger than the value that will be stored in it. // The alloca is bigger than the value that will be stored in it.
// To avoid having some bits undefined, zero the alloca first. // To avoid having some bits undefined, zero the alloca first.
// Hopefully this will get optimized away. // Hopefully this will get optimized away.
b.CreateStore(llvm.ConstNull(b.i8ptrType), packedAlloc) b.CreateStore(llvm.ConstNull(b.dataPtrType), packedAlloc)
} }
// Store all values in the alloca. // Store all values in the alloca.
packedAllocCast := b.CreateBitCast(packedAlloc, llvm.PointerType(packedType, 0), "")
for i, value := range values { for i, value := range values {
indices := []llvm.Value{ indices := []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
} }
gep := b.CreateInBoundsGEP(packedType, packedAllocCast, indices, "") gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "")
b.CreateStore(value, gep) b.CreateStore(value, gep)
} }
// Load value (the *i8) from the alloca. // Load value (the *i8) from the alloca.
result := b.CreateLoad(b.i8ptrType, packedAlloc, "") result := b.CreateLoad(b.dataPtrType, packedAlloc, "")
// End the lifetime of the alloca, to help the optimizer. // End the lifetime of the alloca, to help the optimizer.
packedPtr := b.CreateBitCast(packedAlloc, b.i8ptrType, "")
packedSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(packedAlloc.Type()), false) packedSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(packedAlloc.Type()), false)
b.emitLifetimeEnd(packedPtr, packedSize) b.emitLifetimeEnd(packedAlloc, packedSize)
return result return result
} else { } else {
@@ -124,21 +122,20 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
global.SetUnnamedAddr(true) global.SetUnnamedAddr(true)
global.SetLinkage(llvm.InternalLinkage) global.SetLinkage(llvm.InternalLinkage)
return llvm.ConstBitCast(global, b.i8ptrType) return global
} }
// Packed data is bigger than a pointer, so allocate it on the heap. // Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(b.uintptrType, size, false) sizeValue := llvm.ConstInt(b.uintptrType, size, false)
alloc := b.mod.NamedFunction("runtime.alloc") alloc := b.mod.NamedFunction("runtime.alloc")
packedHeapAlloc := b.CreateCall(alloc.GlobalValueType(), alloc, []llvm.Value{ packedAlloc := b.CreateCall(alloc.GlobalValueType(), alloc, []llvm.Value{
sizeValue, sizeValue,
llvm.ConstNull(b.i8ptrType), llvm.ConstNull(b.dataPtrType),
llvm.Undef(b.i8ptrType), // unused context parameter llvm.Undef(b.dataPtrType), // unused context parameter
}, "") }, "")
if b.NeedsStackObjects { if b.NeedsStackObjects {
b.trackPointer(packedHeapAlloc) b.trackPointer(packedAlloc)
} }
packedAlloc := b.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
// Store all values in the heap pointer. // Store all values in the heap pointer.
for i, value := range values { for i, value := range values {
@@ -151,7 +148,7 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
} }
// Return the original heap allocation pointer, which already is an *i8. // Return the original heap allocation pointer, which already is an *i8.
return packedHeapAlloc return packedAlloc
} }
} }
@@ -160,28 +157,27 @@ func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []ll
packedType := b.ctx.StructType(valueTypes, false) packedType := b.ctx.StructType(valueTypes, false)
// Get a correctly-typed pointer to the packed data. // Get a correctly-typed pointer to the packed data.
var packedAlloc, packedRawAlloc llvm.Value var packedAlloc llvm.Value
needsLifetimeEnd := false
size := b.targetData.TypeAllocSize(packedType) size := b.targetData.TypeAllocSize(packedType)
if size == 0 { if size == 0 {
// No data to unpack. // No data to unpack.
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind { } else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
// A single pointer is always stored directly. // A single pointer is always stored directly.
return []llvm.Value{b.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")} return []llvm.Value{ptr}
} else if size <= b.targetData.TypeAllocSize(b.i8ptrType) { } else if size <= b.targetData.TypeAllocSize(b.dataPtrType) {
// Packed data stored directly in pointer. // Packed data stored directly in pointer.
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind { if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
// Keep this cast in SSA form. // Keep this cast in SSA form.
return []llvm.Value{b.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")} return []llvm.Value{b.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
} }
// Fallback: load it using an alloca. // Fallback: load it using an alloca.
packedRawAlloc, _, _ = b.createTemporaryAlloca(llvm.PointerType(b.i8ptrType, 0), "unpack.raw.alloc") packedAlloc, _ = b.createTemporaryAlloca(b.dataPtrType, "unpack.raw.alloc")
packedRawValue := b.CreateBitCast(ptr, llvm.PointerType(b.i8ptrType, 0), "unpack.raw.value") b.CreateStore(ptr, packedAlloc)
b.CreateStore(packedRawValue, packedRawAlloc) needsLifetimeEnd = true
packedAlloc = b.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
} else { } else {
// Packed data stored on the heap. Bitcast the passed-in pointer to the // Packed data stored on the heap.
// correct pointer type. packedAlloc = ptr
packedAlloc = b.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr")
} }
// Load each value from the packed data. // Load each value from the packed data.
values := make([]llvm.Value, len(valueTypes)) values := make([]llvm.Value, len(valueTypes))
@@ -198,10 +194,9 @@ func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []ll
gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "") gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "")
values[i] = b.CreateLoad(valueType, gep, "") values[i] = b.CreateLoad(valueType, gep, "")
} }
if !packedRawAlloc.IsNil() { if needsLifetimeEnd {
allocPtr := b.CreateBitCast(packedRawAlloc, b.i8ptrType, "")
allocSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(b.uintptrType), false) allocSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(b.uintptrType), false)
b.emitLifetimeEnd(allocPtr, allocSize) b.emitLifetimeEnd(packedAlloc, allocSize)
} }
return values return values
} }
@@ -253,12 +248,12 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
// Do a few checks to see whether we need to generate any object layout // Do a few checks to see whether we need to generate any object layout
// information at all. // information at all.
objectSizeBytes := c.targetData.TypeAllocSize(t) objectSizeBytes := c.targetData.TypeAllocSize(t)
pointerSize := c.targetData.TypeAllocSize(c.i8ptrType) pointerSize := c.targetData.TypeAllocSize(c.dataPtrType)
pointerAlignment := c.targetData.PrefTypeAlignment(c.i8ptrType) pointerAlignment := c.targetData.PrefTypeAlignment(c.dataPtrType)
if objectSizeBytes < pointerSize { if objectSizeBytes < pointerSize {
// Too small to contain a pointer. // Too small to contain a pointer.
layout := (uint64(1) << 1) | 1 layout := (uint64(1) << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType) return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType)
} }
bitmap := c.getPointerBitmap(t, pos) bitmap := c.getPointerBitmap(t, pos)
if bitmap.BitLen() == 0 { if bitmap.BitLen() == 0 {
@@ -266,13 +261,13 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
// TODO: this can be done in many other cases, e.g. when allocating an // TODO: this can be done in many other cases, e.g. when allocating an
// array (like [4][]byte, which repeats a slice 4 times). // array (like [4][]byte, which repeats a slice 4 times).
layout := (uint64(1) << 1) | 1 layout := (uint64(1) << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType) return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType)
} }
if objectSizeBytes%uint64(pointerAlignment) != 0 { if objectSizeBytes%uint64(pointerAlignment) != 0 {
// This shouldn't happen except for packed structs, which aren't // This shouldn't happen except for packed structs, which aren't
// currently used. // currently used.
c.addError(pos, "internal error: unexpected object size for object with pointer field") c.addError(pos, "internal error: unexpected object size for object with pointer field")
return llvm.ConstNull(c.i8ptrType) return llvm.ConstNull(c.dataPtrType)
} }
objectSizeWords := objectSizeBytes / uint64(pointerAlignment) objectSizeWords := objectSizeBytes / uint64(pointerAlignment)
@@ -297,7 +292,7 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
// The runtime knows that if the least significant bit of the pointer is // The runtime knows that if the least significant bit of the pointer is
// set, the pointer contains the value itself. // set, the pointer contains the value itself.
layout := bitmap.Uint64()<<(sizeFieldBits+1) | (objectSizeWords << 1) | 1 layout := bitmap.Uint64()<<(sizeFieldBits+1) | (objectSizeWords << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType) return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType)
} }
// Unfortunately, the object layout is too big to fit in a pointer-sized // Unfortunately, the object layout is too big to fit in a pointer-sized
@@ -308,7 +303,7 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", objectSizeWords, (objectSizeWords+15)/16, bitmap) globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", objectSizeWords, (objectSizeWords+15)/16, bitmap)
global := c.mod.NamedGlobal(globalName) global := c.mod.NamedGlobal(globalName)
if !global.IsNil() { if !global.IsNil() {
return llvm.ConstBitCast(global, c.i8ptrType) return global
} }
// Create the global initializer. // Create the global initializer.
@@ -359,13 +354,13 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
global.AddMetadata(0, diglobal) global.AddMetadata(0, diglobal)
} }
return llvm.ConstBitCast(global, c.i8ptrType) return global
} }
// getPointerBitmap scans the given LLVM type for pointers and sets bits in a // getPointerBitmap scans the given LLVM type for pointers and sets bits in a
// bigint at the word offset that contains a pointer. This scan is recursive. // bigint at the word offset that contains a pointer. This scan is recursive.
func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.Int { func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.Int {
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType) alignment := c.targetData.PrefTypeAlignment(c.dataPtrType)
switch typ.TypeKind() { switch typ.TypeKind() {
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind: case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
return big.NewInt(0) return big.NewInt(0)
@@ -378,7 +373,7 @@ func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.In
// of type uintptr, but before the LowerFuncValues pass it actually // of type uintptr, but before the LowerFuncValues pass it actually
// contains a pointer (ptrtoint) to a global. This trips up the // contains a pointer (ptrtoint) to a global. This trips up the
// interp package. Therefore, make the id field a pointer for now. // interp package. Therefore, make the id field a pointer for now.
typ = c.ctx.StructType([]llvm.Type{c.i8ptrType, c.i8ptrType}, false) typ = c.ctx.StructType([]llvm.Type{c.dataPtrType, c.dataPtrType}, false)
} }
for i, subtyp := range typ.StructElementTypes() { for i, subtyp := range typ.StructElementTypes() {
subptrs := c.getPointerBitmap(subtyp, pos) subptrs := c.getPointerBitmap(subtyp, pos)
@@ -458,7 +453,7 @@ func (c *compilerContext) isThumb() bool {
func (b *builder) readStackPointer() llvm.Value { func (b *builder) readStackPointer() llvm.Value {
stacksave := b.mod.NamedFunction("llvm.stacksave") stacksave := b.mod.NamedFunction("llvm.stacksave")
if stacksave.IsNil() { if stacksave.IsNil() {
fnType := llvm.FunctionType(b.i8ptrType, nil, false) fnType := llvm.FunctionType(b.dataPtrType, nil, false)
stacksave = llvm.AddFunction(b.mod, "llvm.stacksave", fnType) stacksave = llvm.AddFunction(b.mod, "llvm.stacksave", fnType)
} }
return b.CreateCall(stacksave.GlobalValueType(), stacksave, nil, "") return b.CreateCall(stacksave.GlobalValueType(), stacksave, nil, "")
+13 -34
View File
@@ -8,22 +8,9 @@
package llvmutil package llvmutil
import ( import (
"strconv"
"strings"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
// Major returns the LLVM major version.
func Major() int {
llvmMajor, err := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
if err != nil {
// sanity check, should be unreachable
panic("could not parse LLVM version: " + err.Error())
}
return llvmMajor
}
// CreateEntryBlockAlloca creates a new alloca in the entry block, even though // CreateEntryBlockAlloca creates a new alloca in the entry block, even though
// the IR builder is located elsewhere. It assumes that the insert point is // the IR builder is located elsewhere. It assumes that the insert point is
// at the end of the current block. // at the end of the current block.
@@ -46,16 +33,14 @@ func CreateEntryBlockAlloca(builder llvm.Builder, t llvm.Type, name string) llvm
// //
// This is useful for creating temporary allocas for intrinsics. Don't forget to // This is useful for creating temporary allocas for intrinsics. Don't forget to
// end the lifetime using emitLifetimeEnd after you're done with it. // end the lifetime using emitLifetimeEnd after you're done with it.
func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, name string) (alloca, bitcast, size llvm.Value) { func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, name string) (alloca, size llvm.Value) {
ctx := t.Context() ctx := t.Context()
targetData := llvm.NewTargetData(mod.DataLayout()) targetData := llvm.NewTargetData(mod.DataLayout())
defer targetData.Dispose() defer targetData.Dispose()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
alloca = CreateEntryBlockAlloca(builder, t, name) alloca = CreateEntryBlockAlloca(builder, t, name)
bitcast = builder.CreateBitCast(alloca, i8ptrType, name+".bitcast")
size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false) size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
fnType, fn := getLifetimeStartFunc(mod) fnType, fn := getLifetimeStartFunc(mod)
builder.CreateCall(fnType, fn, []llvm.Value{size, bitcast}, "") builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
return return
} }
@@ -64,21 +49,19 @@ func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type,
ctx := mod.Context() ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout()) targetData := llvm.NewTargetData(mod.DataLayout())
defer targetData.Dispose() defer targetData.Dispose()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
alloca := CreateEntryBlockAlloca(builder, t, name) alloca := CreateEntryBlockAlloca(builder, t, name)
builder.SetInsertPointBefore(inst) builder.SetInsertPointBefore(inst)
bitcast := builder.CreateBitCast(alloca, i8ptrType, name+".bitcast")
size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false) size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
fnType, fn := getLifetimeStartFunc(mod) fnType, fn := getLifetimeStartFunc(mod)
builder.CreateCall(fnType, fn, []llvm.Value{size, bitcast}, "") builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
if next := llvm.NextInstruction(inst); !next.IsNil() { if next := llvm.NextInstruction(inst); !next.IsNil() {
builder.SetInsertPointBefore(next) builder.SetInsertPointBefore(next)
} else { } else {
builder.SetInsertPointAtEnd(inst.InstructionParent()) builder.SetInsertPointAtEnd(inst.InstructionParent())
} }
fnType, fn = getLifetimeEndFunc(mod) fnType, fn = getLifetimeEndFunc(mod)
builder.CreateCall(fnType, fn, []llvm.Value{size, bitcast}, "") builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
return alloca return alloca
} }
@@ -94,13 +77,10 @@ func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value
// first if it doesn't exist yet. // first if it doesn't exist yet.
func getLifetimeStartFunc(mod llvm.Module) (llvm.Type, llvm.Value) { func getLifetimeStartFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
fnName := "llvm.lifetime.start.p0" fnName := "llvm.lifetime.start.p0"
if Major() < 15 { // compatibility with LLVM 14
fnName = "llvm.lifetime.start.p0i8"
}
fn := mod.NamedFunction(fnName) fn := mod.NamedFunction(fnName)
ctx := mod.Context() ctx := mod.Context()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0) ptrType := llvm.PointerType(ctx.Int8Type(), 0)
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false) fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
if fn.IsNil() { if fn.IsNil() {
fn = llvm.AddFunction(mod, fnName, fnType) fn = llvm.AddFunction(mod, fnName, fnType)
} }
@@ -111,13 +91,10 @@ func getLifetimeStartFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
// first if it doesn't exist yet. // first if it doesn't exist yet.
func getLifetimeEndFunc(mod llvm.Module) (llvm.Type, llvm.Value) { func getLifetimeEndFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
fnName := "llvm.lifetime.end.p0" fnName := "llvm.lifetime.end.p0"
if Major() < 15 {
fnName = "llvm.lifetime.end.p0i8"
}
fn := mod.NamedFunction(fnName) fn := mod.NamedFunction(fnName)
ctx := mod.Context() ctx := mod.Context()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0) ptrType := llvm.PointerType(ctx.Int8Type(), 0)
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false) fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
if fn.IsNil() { if fn.IsNil() {
fn = llvm.AddFunction(mod, fnName, fnType) fn = llvm.AddFunction(mod, fnName, fnType)
} }
@@ -213,13 +190,15 @@ func AppendToGlobal(mod llvm.Module, globalName string, values ...llvm.Value) {
} }
// Add the new values. // Add the new values.
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
for _, value := range values { for _, value := range values {
usedValues = append(usedValues, llvm.ConstPointerCast(value, i8ptrType)) // Note: the bitcast is necessary to cast AVR function pointers to
// address space 0 pointer types.
usedValues = append(usedValues, llvm.ConstPointerCast(value, ptrType))
} }
// Create a new array (with the old and new values). // Create a new array (with the old and new values).
usedInitializer := llvm.ConstArray(i8ptrType, usedValues) usedInitializer := llvm.ConstArray(ptrType, usedValues)
used := llvm.AddGlobal(mod, usedInitializer.Type(), globalName) used := llvm.AddGlobal(mod, usedInitializer.Type(), globalName)
used.SetInitializer(usedInitializer) used.SetInitializer(usedInitializer)
used.SetLinkage(llvm.AppendingLinkage) used.SetLinkage(llvm.AppendingLinkage)
+23 -30
View File
@@ -65,7 +65,7 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val
// Allocate the memory for the resulting type. Do not zero this memory: it // Allocate the memory for the resulting type. Do not zero this memory: it
// will be zeroed by the hashmap get implementation if the key is not // will be zeroed by the hashmap get implementation if the key is not
// present in the map. // present in the map.
mapValueAlloca, mapValuePtr, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value") mapValueAlloca, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value")
// We need the map size (with type uintptr) to pass to the hashmap*Get // We need the map size (with type uintptr) to pass to the hashmap*Get
// functions. This is necessary because those *Get functions are valid on // functions. This is necessary because those *Get functions are valid on
@@ -82,19 +82,19 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val
keyType = keyType.Underlying() keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string // key is a string
params := []llvm.Value{m, key, mapValuePtr, mapValueSize} params := []llvm.Value{m, key, mapValueAlloca, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "") commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "")
} else if hashmapIsBinaryKey(keyType) { } else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal // key can be compared with runtime.memequal
// Store the key in an alloca, in the entry block to avoid dynamic stack // Store the key in an alloca, in the entry block to avoid dynamic stack
// growth. // growth.
mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, mapKeyAlloca) b.CreateStore(key, mapKeyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), mapKeyAlloca) b.zeroUndefBytes(b.getLLVMType(keyType), mapKeyAlloca)
// Fetch the value from the hashmap. // Fetch the value from the hashmap.
params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize} params := []llvm.Value{m, mapKeyAlloca, mapValueAlloca, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "") commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "")
b.emitLifetimeEnd(mapKeyPtr, mapKeySize) b.emitLifetimeEnd(mapKeyAlloca, mapKeySize)
} else { } else {
// Not trivially comparable using memcmp. Make it an interface instead. // Not trivially comparable using memcmp. Make it an interface instead.
itfKey := key itfKey := key
@@ -102,14 +102,14 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val
// Not already an interface, so convert it to an interface now. // Not already an interface, so convert it to an interface now.
itfKey = b.createMakeInterface(key, origKeyType, pos) itfKey = b.createMakeInterface(key, origKeyType, pos)
} }
params := []llvm.Value{m, itfKey, mapValuePtr, mapValueSize} params := []llvm.Value{m, itfKey, mapValueAlloca, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapInterfaceGet", params, "") commaOkValue = b.createRuntimeCall("hashmapInterfaceGet", params, "")
} }
// Load the resulting value from the hashmap. The value is set to the zero // Load the resulting value from the hashmap. The value is set to the zero
// value if the key doesn't exist in the hashmap. // value if the key doesn't exist in the hashmap.
mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "") mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "")
b.emitLifetimeEnd(mapValuePtr, mapValueAllocaSize) b.emitLifetimeEnd(mapValueAlloca, mapValueAllocaSize)
if commaOk { if commaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false)) tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false))
@@ -124,22 +124,22 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val
// createMapUpdate updates a map key to a given value, by creating an // createMapUpdate updates a map key to a given value, by creating an
// appropriate runtime call. // appropriate runtime call.
func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) { func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
valueAlloca, valuePtr, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value") valueAlloca, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value")
b.CreateStore(value, valueAlloca) b.CreateStore(value, valueAlloca)
origKeyType := keyType origKeyType := keyType
keyType = keyType.Underlying() keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string // key is a string
params := []llvm.Value{m, key, valuePtr} params := []llvm.Value{m, key, valueAlloca}
b.createRuntimeCall("hashmapStringSet", params, "") b.createRuntimeCall("hashmapStringSet", params, "")
} else if hashmapIsBinaryKey(keyType) { } else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal // key can be compared with runtime.memequal
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca) b.CreateStore(key, keyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca) b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca)
params := []llvm.Value{m, keyPtr, valuePtr} params := []llvm.Value{m, keyAlloca, valueAlloca}
b.createRuntimeCall("hashmapBinarySet", params, "") b.createRuntimeCall("hashmapBinarySet", params, "")
b.emitLifetimeEnd(keyPtr, keySize) b.emitLifetimeEnd(keyAlloca, keySize)
} else { } else {
// Key is not trivially comparable, so compare it as an interface instead. // Key is not trivially comparable, so compare it as an interface instead.
itfKey := key itfKey := key
@@ -147,10 +147,10 @@ func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value,
// Not already an interface, so convert it to an interface first. // Not already an interface, so convert it to an interface first.
itfKey = b.createMakeInterface(key, origKeyType, pos) itfKey = b.createMakeInterface(key, origKeyType, pos)
} }
params := []llvm.Value{m, itfKey, valuePtr} params := []llvm.Value{m, itfKey, valueAlloca}
b.createRuntimeCall("hashmapInterfaceSet", params, "") b.createRuntimeCall("hashmapInterfaceSet", params, "")
} }
b.emitLifetimeEnd(valuePtr, valueSize) b.emitLifetimeEnd(valueAlloca, valueSize)
} }
// createMapDelete deletes a key from a map by calling the appropriate runtime // createMapDelete deletes a key from a map by calling the appropriate runtime
@@ -164,12 +164,12 @@ func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos tok
b.createRuntimeCall("hashmapStringDelete", params, "") b.createRuntimeCall("hashmapStringDelete", params, "")
return nil return nil
} else if hashmapIsBinaryKey(keyType) { } else if hashmapIsBinaryKey(keyType) {
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca) b.CreateStore(key, keyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca) b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca)
params := []llvm.Value{m, keyPtr} params := []llvm.Value{m, keyAlloca}
b.createRuntimeCall("hashmapBinaryDelete", params, "") b.createRuntimeCall("hashmapBinaryDelete", params, "")
b.emitLifetimeEnd(keyPtr, keySize) b.emitLifetimeEnd(keyAlloca, keySize)
return nil return nil
} else { } else {
// Key is not trivially comparable, so compare it as an interface // Key is not trivially comparable, so compare it as an interface
@@ -225,9 +225,9 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv
} }
// Extract the key and value from the map. // Extract the key and value from the map.
mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(llvmStoredKeyType, "range.key") mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(llvmStoredKeyType, "range.key")
mapValueAlloca, mapValuePtr, mapValueSize := b.createTemporaryAlloca(llvmValueType, "range.value") mapValueAlloca, mapValueSize := b.createTemporaryAlloca(llvmValueType, "range.value")
ok := b.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyPtr, mapValuePtr}, "range.next") ok := b.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyAlloca, mapValueAlloca}, "range.next")
mapKey := b.CreateLoad(llvmStoredKeyType, mapKeyAlloca, "") mapKey := b.CreateLoad(llvmStoredKeyType, mapKeyAlloca, "")
mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "") mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "")
@@ -238,8 +238,8 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv
} }
// End the lifetimes of the allocas, because we're done with them. // End the lifetimes of the allocas, because we're done with them.
b.emitLifetimeEnd(mapKeyPtr, mapKeySize) b.emitLifetimeEnd(mapKeyAlloca, mapKeySize)
b.emitLifetimeEnd(mapValuePtr, mapValueSize) b.emitLifetimeEnd(mapValueAlloca, mapValueSize)
// Construct the *ssa.Next return value: {ok, mapKey, mapValue} // Construct the *ssa.Next return value: {ok, mapKey, mapValue}
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.Int1Type(), llvmKeyType, llvmValueType}, false)) tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.Int1Type(), llvmKeyType, llvmValueType}, false))
@@ -333,14 +333,7 @@ func (b *builder) zeroUndefBytes(llvmType llvm.Type, ptr llvm.Value) error {
if fieldEndOffset != nextOffset { if fieldEndOffset != nextOffset {
n := llvm.ConstInt(b.uintptrType, nextOffset-fieldEndOffset, false) n := llvm.ConstInt(b.uintptrType, nextOffset-fieldEndOffset, false)
llvmStoreSize := llvm.ConstInt(b.uintptrType, storeSize, false) llvmStoreSize := llvm.ConstInt(b.uintptrType, storeSize, false)
gepPtr := elemPtr paddingStart := b.CreateInBoundsGEP(b.ctx.Int8Type(), elemPtr, []llvm.Value{llvmStoreSize}, "")
if gepPtr.Type() != b.i8ptrType {
gepPtr = b.CreateBitCast(gepPtr, b.i8ptrType, "") // LLVM 14
}
paddingStart := b.CreateInBoundsGEP(b.ctx.Int8Type(), gepPtr, []llvm.Value{llvmStoreSize}, "")
if paddingStart.Type() != b.i8ptrType {
paddingStart = b.CreateBitCast(paddingStart, b.i8ptrType, "") // LLVM 14
}
b.createRuntimeCall("memzero", []llvm.Value{paddingStart, n}, "") b.createRuntimeCall("memzero", []llvm.Value{paddingStart, n}, "")
} }
} }
+17 -24
View File
@@ -96,7 +96,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
// Add an extra parameter as the function context. This context is used in // Add an extra parameter as the function context. This context is used in
// closures and bound methods, but should be optimized away when not used. // closures and bound methods, but should be optimized away when not used.
if !info.exported { if !info.exported {
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", elemSize: 0}) paramInfos = append(paramInfos, paramInfo{llvmType: c.dataPtrType, name: "context", elemSize: 0})
} }
var paramTypes []llvm.Type var paramTypes []llvm.Type
@@ -145,20 +145,18 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
for _, attrName := range []string{"noalias", "nonnull"} { for _, attrName := range []string{"noalias", "nonnull"} {
llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0)) llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0))
} }
if llvmutil.Major() >= 15 { // allockind etc are not available in LLVM 14 // Add attributes to signal to LLVM that this is an allocator function.
// Add attributes to signal to LLVM that this is an allocator // This enables a number of optimizations.
// function. This enables a number of optimizations. llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("allockind"), allocKindAlloc|allocKindZeroed))
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("allockind"), allocKindAlloc|allocKindZeroed)) llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("alloc-family", "runtime.alloc"))
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("alloc-family", "runtime.alloc")) // Use a special value to indicate the first parameter:
// Use a special value to indicate the first parameter: // > allocsize has two integer arguments, but because they're both 32 bits, we can
// > allocsize has two integer arguments, but because they're both 32 bits, we can // > pack them into one 64-bit value, at the cost of making said value
// > pack them into one 64-bit value, at the cost of making said value // > nonsensical.
// > nonsensical. // >
// > // > In order to do this, we need to reserve one value of the second (optional)
// > In order to do this, we need to reserve one value of the second (optional) // > allocsize argument to signify "not present."
// > allocsize argument to signify "not present." llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("allocsize"), 0x0000_0000_ffff_ffff))
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("allocsize"), 0x0000_0000_ffff_ffff))
}
case "runtime.sliceAppend": case "runtime.sliceAppend":
// Appending a slice will only read the to-be-appended slice, it won't // Appending a slice will only read the to-be-appended slice, it won't
// be modified. // be modified.
@@ -445,15 +443,10 @@ func (c *compilerContext) addStandardDefinedAttributes(llvmFn llvm.Value) {
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nounwind"), 0)) llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nounwind"), 0))
if strings.Split(c.Triple, "-")[0] == "x86_64" { if strings.Split(c.Triple, "-")[0] == "x86_64" {
// Required by the ABI. // Required by the ABI.
if llvmutil.Major() < 15 { // The uwtable has two possible values: sync (1) or async (2). We use
// Needed for LLVM 14 support. // sync because we currently don't use async unwind tables.
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 0)) // For details, see: https://llvm.org/docs/LangRef.html#function-attributes
} else { llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 1))
// The uwtable has two possible values: sync (1) or async (2). We
// use sync because we currently don't use async unwind tables.
// For details, see: https://llvm.org/docs/LangRef.html#function-attributes
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 1))
}
} }
} }
+1 -1
View File
@@ -183,7 +183,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
} }
llvmType := llvm.FunctionType(b.uintptrType, paramTypes, false) llvmType := llvm.FunctionType(b.uintptrType, paramTypes, false)
fn := b.getValue(call.Args[0], getPos(call)) fn := b.getValue(call.Args[0], getPos(call))
fnPtr := b.CreateIntToPtr(fn, llvm.PointerType(llvmType, 0), "") fnPtr := b.CreateIntToPtr(fn, b.dataPtrType, "")
// Prepare some functions that will be called later. // Prepare some functions that will be called later.
setLastError := b.mod.NamedFunction("SetLastError") setLastError := b.mod.NamedFunction("SetLastError")
+5 -5
View File
@@ -21,7 +21,7 @@ entry:
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.regularFunctionGoroutine(ptr %context) unnamed_addr #2 { define hidden void @main.regularFunctionGoroutine(ptr %context) unnamed_addr #2 {
entry: entry:
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 32768, ptr undef) #9 call void @"internal/task.start"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 65536, ptr undef) #9
ret void ret void
} }
@@ -43,7 +43,7 @@ declare void @"internal/task.start"(i32, ptr, i32, ptr) #1
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.inlineFunctionGoroutine(ptr %context) unnamed_addr #2 { define hidden void @main.inlineFunctionGoroutine(ptr %context) unnamed_addr #2 {
entry: entry:
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 32768, ptr undef) #9 call void @"internal/task.start"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 65536, ptr undef) #9
ret void ret void
} }
@@ -76,7 +76,7 @@ entry:
store i32 5, ptr %0, align 4 store i32 5, ptr %0, align 4
%1 = getelementptr inbounds { i32, ptr }, ptr %0, i32 0, i32 1 %1 = getelementptr inbounds { i32, ptr }, ptr %0, i32 0, i32 1
store ptr %n, ptr %1, align 4 store ptr %n, ptr %1, align 4
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 32768, ptr undef) #9 call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #9
%2 = load i32, ptr %n, align 4 %2 = load i32, ptr %n, align 4
call void @runtime.printint32(i32 %2, ptr undef) #9 call void @runtime.printint32(i32 %2, ptr undef) #9
ret void ret void
@@ -113,7 +113,7 @@ entry:
store ptr %fn.context, ptr %1, align 4 store ptr %fn.context, ptr %1, align 4
%2 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 2 %2 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 2
store ptr %fn.funcptr, ptr %2, align 4 store ptr %fn.funcptr, ptr %2, align 4
call void @"internal/task.start"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr nonnull %0, i32 32768, ptr undef) #9 call void @"internal/task.start"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr nonnull %0, i32 65536, ptr undef) #9
ret void ret void
} }
@@ -167,7 +167,7 @@ entry:
store i32 4, ptr %.repack1, align 4 store i32 4, ptr %.repack1, align 4
%2 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 2 %2 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 2
store ptr %itf.typecode, ptr %2, align 4 store ptr %itf.typecode, ptr %2, align 4
call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 32768, ptr undef) #9 call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #9
ret void ret void
} }
+1 -1
View File
@@ -16,7 +16,7 @@ I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help .PHONY: help
help: help:
@echo "Please use \`make <target>' where <target> is one of" @echo "Please use \`$(MAKE) <target>' where <target> is one of"
@echo " html to make standalone HTML files" @echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories" @echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file" @echo " singlehtml to make a single large HTML file"
Generated
+60
View File
@@ -0,0 +1,60 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1696983906,
"narHash": "sha256-L7GyeErguS7Pg4h8nK0wGlcUTbfUMDu+HMf1UcyP72k=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "bd1cde45c77891214131cbbea5b1203e485a9d51",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-23.05",
"type": "indirect"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+85
View File
@@ -0,0 +1,85 @@
# A Nix flake file, mainly intended for developing TinyGo.
# You can download Nix here, for use on your Linux or macOS system:
# https://nixos.org/download.html
# After you have installed Nix, you can enter the development environment as
# follows:
#
# nix develop
#
# This drops you into a bash shell, where you can install TinyGo simply using
# the following command:
#
# go install
#
# That's all! Assuming you've set up your $PATH correctly, you can now use the
# tinygo command as usual:
#
# tinygo version
#
# But you'll need a bit more to make TinyGo actually able to compile code:
#
# make llvm-source # fetch compiler-rt
# git submodule update --init --recursive # fetch lots of other libraries and SVD files
# make gen-device -j4 # build src/device/*/*.go files
# make wasi-libc # build support for wasi/wasm
#
# With this, you should have an environment that can compile anything - except
# for the Xtensa architecture (ESP8266/ESP32) because support for that lives in
# a separate LLVM fork.
#
# You can also do many other things from this environment. Building and flashing
# should work as you're used to: it's not a VM or container so there are no
# access restrictions and you're running in the same host environment - just
# with a slightly different set of tools available.
{
inputs = {
# Use a recent stable release, but fix the version to make it reproducible.
# This version should be updated from time to time.
nixpkgs.url = "nixpkgs/nixos-23.05";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
with pkgs;
{
devShells.default = mkShell {
buildInputs = [
# These dependencies are required for building tinygo (go install).
go
llvmPackages_16.llvm
llvmPackages_16.libclang
# Additional dependencies needed at runtime, for building and/or
# flashing.
llvmPackages_16.lld
avrdude
binaryen
# Additional dependencies needed for on-chip debugging.
# These tools are rather big (especially GDB) and not frequently
# used, so are commented out. On-chip debugging is still possible if
# these tools are available in the host environment.
#gdb
#openocd
];
shellHook= ''
# Configure CLANG, LLVM_AR, and LLVM_NM for `make wasi-libc`.
# Without setting these explicitly, Homebrew versions might be used
# or the default `ar` and `nm` tools might be used (which don't
# support wasi).
export CLANG="clang-16 -resource-dir ${llvmPackages_16.clang.cc.lib}/lib/clang/16"
export LLVM_AR=llvm-ar
export LLVM_NM=llvm-nm
# Make `make smoketest` work (the default is `md5`, while Nix only
# has `md5sum`).
export MD5SUM=md5sum
# Ugly hack to make the Clang resources directory available.
export GOFLAGS="\"-ldflags=-X github.com/tinygo-org/tinygo/goenv.clangResourceDir=${llvmPackages_16.clang.cc.lib}/lib/clang/16"\"
'';
};
}
);
}
+1 -1
View File
@@ -18,7 +18,7 @@ require (
golang.org/x/sys v0.11.0 golang.org/x/sys v0.11.0
golang.org/x/tools v0.12.0 golang.org/x/tools v0.12.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
tinygo.org/x/go-llvm v0.0.0-20230918183930-9edb6403d0bc tinygo.org/x/go-llvm v0.0.0-20231014233752-75a8a9fe6f74
) )
require ( require (
+2 -2
View File
@@ -65,5 +65,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
tinygo.org/x/go-llvm v0.0.0-20230918183930-9edb6403d0bc h1:IVX1dqCX3c88P7iEMBtz1xCAM4UIqCMgbqHdSefBaWE= tinygo.org/x/go-llvm v0.0.0-20231014233752-75a8a9fe6f74 h1:tW8XhLI9gUZLL+2pG0HYb5dc6bpMj1aqtESpizXPnMY=
tinygo.org/x/go-llvm v0.0.0-20230918183930-9edb6403d0bc/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= tinygo.org/x/go-llvm v0.0.0-20231014233752-75a8a9fe6f74/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0=
+103
View File
@@ -14,6 +14,8 @@ import (
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
"tinygo.org/x/go-llvm"
) )
// Keys is a slice of all available environment variable keys. // Keys is a slice of all available environment variable keys.
@@ -33,11 +35,20 @@ func init() {
} }
} }
// Set to true if we're linking statically against LLVM.
var hasBuiltinTools = false
// TINYGOROOT is the path to the final location for checking tinygo files. If // TINYGOROOT is the path to the final location for checking tinygo files. If
// unset (by a -X ldflag), then sourceDir() will fallback to the original build // unset (by a -X ldflag), then sourceDir() will fallback to the original build
// directory. // directory.
var TINYGOROOT string var TINYGOROOT string
// If a particular Clang resource dir must always be used and TinyGo can't
// figure out the directory using heuristics, this global can be set using a
// linker flag.
// This is needed for Nix.
var clangResourceDir string
// Variables read from a `go env` command invocation. // Variables read from a `go env` command invocation.
var goEnvVars struct { var goEnvVars struct {
GOPATH string GOPATH string
@@ -284,3 +295,95 @@ func isSourceDir(root string) bool {
_, err = os.Stat(filepath.Join(root, "src/device/arm/arm.go")) _, err = os.Stat(filepath.Join(root, "src/device/arm/arm.go"))
return err == nil return err == nil
} }
// ClangResourceDir returns the clang resource dir if available. This is the
// -resource-dir flag. If it isn't available, an empty string is returned and
// -resource-dir should be left unset.
// The libclang flag must be set if the resource dir is read for use by
// libclang.
// In that case, the resource dir is always returned (even when linking
// dynamically against LLVM) because libclang always needs this directory.
func ClangResourceDir(libclang bool) string {
if clangResourceDir != "" {
// The resource dir is forced to a particular value at build time.
// This is needed on Nix for example, where Clang and libclang don't
// know their own resource dir.
// Also see:
// https://discourse.nixos.org/t/why-is-the-clang-resource-dir-split-in-a-separate-package/34114
return clangResourceDir
}
if !hasBuiltinTools && !libclang {
// Using external tools, so the resource dir doesn't need to be
// specified. Clang knows where to find it.
return ""
}
// Check whether we're running from a TinyGo release directory.
// This is the case for release binaries on GitHub.
root := Get("TINYGOROOT")
releaseHeaderDir := filepath.Join(root, "lib", "clang")
if _, err := os.Stat(releaseHeaderDir); !errors.Is(err, fs.ErrNotExist) {
return releaseHeaderDir
}
if hasBuiltinTools {
// We are statically linked to LLVM.
// Check whether we're running from the source directory.
// This typically happens when TinyGo was built using `make` as part of
// development.
llvmMajor := strings.Split(llvm.Version, ".")[0]
buildResourceDir := filepath.Join(root, "llvm-build", "lib", "clang", llvmMajor)
if _, err := os.Stat(buildResourceDir); !errors.Is(err, fs.ErrNotExist) {
return buildResourceDir
}
} else {
// We use external tools, either when installed using `go install` or
// when packaged in a Linux distribution (Linux distros typically prefer
// dynamic linking).
// Try to detect the system clang resources directory.
resourceDir := findSystemClangResources(root)
if resourceDir != "" {
return resourceDir
}
}
// Resource directory not found.
return ""
}
// Find the Clang resource dir on this particular system.
// Return the empty string when they aren't found.
func findSystemClangResources(TINYGOROOT string) string {
llvmMajor := strings.Split(llvm.Version, ".")[0]
switch runtime.GOOS {
case "linux", "android":
// Header files are typically stored in /usr/lib/clang/<version>/include.
// Tested on Fedora 39, Debian 12, and Arch Linux.
path := filepath.Join("/usr/lib/clang", llvmMajor)
_, err := os.Stat(filepath.Join(path, "include", "stdint.h"))
if err == nil {
return path
}
case "darwin":
// This assumes a Homebrew installation, like in builder/commands.go.
var prefix string
switch runtime.GOARCH {
case "amd64":
prefix = "/usr/local/opt/llvm@" + llvmMajor
case "arm64":
prefix = "/opt/homebrew/opt/llvm@" + llvmMajor
default:
return "" // very unlikely for now
}
path := fmt.Sprintf("%s/lib/clang/%s", prefix, llvmMajor)
_, err := os.Stat(path + "/include/stdint.h")
if err == nil {
return path
}
}
// Could not find it.
return ""
}
+7
View File
@@ -0,0 +1,7 @@
//go:build byollvm
package goenv
func init() {
hasBuiltinTools = true
}
+11 -1
View File
@@ -9,7 +9,7 @@ import (
// Version of TinyGo. // Version of TinyGo.
// Update this value before release of new version of software. // Update this value before release of new version of software.
const Version = "0.30.0" const version = "0.31.0-dev"
var ( var (
// This variable is set at build time using -ldflags parameters. // This variable is set at build time using -ldflags parameters.
@@ -17,6 +17,16 @@ var (
GitSha1 string GitSha1 string
) )
// Return TinyGo version, either in the form 0.30.0 or as a development version
// (like 0.30.0-dev-abcd012).
func Version() string {
v := version
if strings.HasSuffix(version, "-dev") && GitSha1 != "" {
v += "-" + GitSha1
}
return v
}
// GetGorootVersion returns the major and minor version for a given GOROOT path. // GetGorootVersion returns the major and minor version for a given GOROOT path.
// If the goroot cannot be determined, (0, 0) is returned. // If the goroot cannot be determined, (0, 0) is returned.
func GetGorootVersion() (major, minor int, err error) { func GetGorootVersion() (major, minor int, err error) {
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash #!/bin/bash
# Docker hub does a recursive clone, then checks the branch out, # Docker hub does a recursive clone, then checks the branch out,
# so when a PR adds a submodule (or updates it), it fails. # so when a PR adds a submodule (or updates it), it fails.
git submodule update --init git submodule update --init --recursive
+2 -2
View File
@@ -60,10 +60,10 @@ func (r *runner) errorAt(inst instruction, err error) *Error {
pos := getPosition(inst.llvmInst) pos := getPosition(inst.llvmInst)
return &Error{ return &Error{
ImportPath: r.pkgName, ImportPath: r.pkgName,
Inst: inst.String(), Inst: inst.llvmInst.String(),
Pos: pos, Pos: pos,
Err: err, Err: err,
Traceback: []ErrorLine{{pos, inst.String()}}, Traceback: []ErrorLine{{pos, inst.llvmInst.String()}},
} }
} }
+5 -6
View File
@@ -21,7 +21,7 @@ type runner struct {
targetData llvm.TargetData targetData llvm.TargetData
builder llvm.Builder builder llvm.Builder
pointerSize uint32 // cached pointer size from the TargetData pointerSize uint32 // cached pointer size from the TargetData
i8ptrType llvm.Type // often used type so created in advance dataPtrType llvm.Type // often used type so created in advance
uintptrType llvm.Type // equivalent to uintptr in Go uintptrType llvm.Type // equivalent to uintptr in Go
maxAlign int // maximum alignment of an object, alignment of runtime.alloc() result maxAlign int // maximum alignment of an object, alignment of runtime.alloc() result
debug bool // log debug messages debug bool // log debug messages
@@ -46,9 +46,9 @@ func newRunner(mod llvm.Module, timeout time.Duration, debug bool) *runner {
timeout: timeout, timeout: timeout,
} }
r.pointerSize = uint32(r.targetData.PointerSize()) r.pointerSize = uint32(r.targetData.PointerSize())
r.i8ptrType = llvm.PointerType(mod.Context().Int8Type(), 0) r.dataPtrType = llvm.PointerType(mod.Context().Int8Type(), 0)
r.uintptrType = mod.Context().IntType(r.targetData.PointerSize() * 8) r.uintptrType = mod.Context().IntType(r.targetData.PointerSize() * 8)
r.maxAlign = r.targetData.PrefTypeAlignment(r.i8ptrType) // assume pointers are maximally aligned (this is not always the case) r.maxAlign = r.targetData.PrefTypeAlignment(r.dataPtrType) // assume pointers are maximally aligned (this is not always the case)
return &r return &r
} }
@@ -126,7 +126,7 @@ func Run(mod llvm.Module, timeout time.Duration, debug bool) error {
mem.revert() mem.revert()
// Create a call to the package initializer (which was // Create a call to the package initializer (which was
// previously deleted). // previously deleted).
i8undef := llvm.Undef(r.i8ptrType) i8undef := llvm.Undef(r.dataPtrType)
r.builder.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{i8undef}, "") r.builder.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{i8undef}, "")
// Make sure that any globals touched by the package // Make sure that any globals touched by the package
// initializer, won't be accessed by later package initializers. // initializer, won't be accessed by later package initializers.
@@ -174,8 +174,7 @@ func Run(mod llvm.Module, timeout time.Duration, debug bool) error {
newGlobal.SetLinkage(obj.llvmGlobal.Linkage()) newGlobal.SetLinkage(obj.llvmGlobal.Linkage())
newGlobal.SetAlignment(obj.llvmGlobal.Alignment()) newGlobal.SetAlignment(obj.llvmGlobal.Alignment())
// TODO: copy debug info, unnamed_addr, ... // TODO: copy debug info, unnamed_addr, ...
bitcast := llvm.ConstBitCast(newGlobal, obj.llvmGlobal.Type()) obj.llvmGlobal.ReplaceAllUsesWith(newGlobal)
obj.llvmGlobal.ReplaceAllUsesWith(bitcast)
name := obj.llvmGlobal.Name() name := obj.llvmGlobal.Name()
obj.llvmGlobal.EraseFromParentAsGlobal() obj.llvmGlobal.EraseFromParentAsGlobal()
newGlobal.SetName(name) newGlobal.SetName(name)
+3 -6
View File
@@ -77,12 +77,9 @@ func runTest(t *testing.T, pathPrefix string) {
} }
// Run some cleanup passes to get easy-to-read outputs. // Run some cleanup passes to get easy-to-read outputs.
pm := llvm.NewPassManager() to := llvm.NewPassBuilderOptions()
defer pm.Dispose() defer to.Dispose()
pm.AddGlobalOptimizerPass() mod.RunPasses("globalopt,dse,adce", llvm.TargetMachine{}, to)
pm.AddDeadStoreEliminationPass()
pm.AddAggressiveDCEPass()
pm.Run(mod)
// Read the expected output IR. // Read the expected output IR.
out, err := os.ReadFile(pathPrefix + ".out.ll") out, err := os.ReadFile(pathPrefix + ".out.ll")
+1 -13
View File
@@ -543,7 +543,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// how this function got called. // how this function got called.
callErr.Traceback = append(callErr.Traceback, ErrorLine{ callErr.Traceback = append(callErr.Traceback, ErrorLine{
Pos: getPosition(inst.llvmInst), Pos: getPosition(inst.llvmInst),
Inst: inst.String(), Inst: inst.llvmInst.String(),
}) })
return nil, mem, callErr return nil, mem, callErr
} }
@@ -1046,15 +1046,3 @@ func intPredicateString(predicate llvm.IntPredicate) string {
return "cmp?" return "cmp?"
} }
} }
// Strip some pointer casts. This is probably unnecessary once support for
// LLVM 14 (non-opaque pointers) is dropped.
func stripPointerCasts(value llvm.Value) llvm.Value {
if !value.IsAConstantExpr().IsNil() {
switch value.Opcode() {
case llvm.GetElementPtr, llvm.BitCast:
return stripPointerCasts(value.Operand(0))
}
}
return value
}
+4 -15
View File
@@ -658,20 +658,11 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
if v.offset() != 0 { if v.offset() != 0 {
// If there is an offset, make sure to use a GEP to index into the // If there is an offset, make sure to use a GEP to index into the
// pointer. // pointer.
// Cast to an i8* first (if needed) for easy indexing.
if llvmValue.Type() != mem.r.i8ptrType {
llvmValue = llvm.ConstBitCast(llvmValue, mem.r.i8ptrType)
}
llvmValue = llvm.ConstInBoundsGEP(mem.r.mod.Context().Int8Type(), llvmValue, []llvm.Value{ llvmValue = llvm.ConstInBoundsGEP(mem.r.mod.Context().Int8Type(), llvmValue, []llvm.Value{
llvm.ConstInt(mem.r.mod.Context().Int32Type(), uint64(v.offset()), false), llvm.ConstInt(mem.r.mod.Context().Int32Type(), uint64(v.offset()), false),
}) })
} }
// If a particular LLVM pointer type is requested, cast to it.
if !llvmType.IsNil() && llvmType != llvmValue.Type() {
llvmValue = llvm.ConstBitCast(llvmValue, llvmType)
}
return llvmValue, nil return llvmValue, nil
} }
@@ -872,7 +863,7 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
if err != nil { if err != nil {
panic(err) panic(err)
} }
if checks && mem.r.targetData.TypeAllocSize(llvmType) != mem.r.targetData.TypeAllocSize(mem.r.i8ptrType) { if checks && mem.r.targetData.TypeAllocSize(llvmType) != mem.r.targetData.TypeAllocSize(mem.r.dataPtrType) {
// Probably trying to serialize a pointer to a byte array, // Probably trying to serialize a pointer to a byte array,
// perhaps as a result of rawLLVMValue() in a previous interp // perhaps as a result of rawLLVMValue() in a previous interp
// run. // run.
@@ -954,8 +945,6 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
// Because go-llvm doesn't have addrspacecast at the moment, // Because go-llvm doesn't have addrspacecast at the moment,
// do it indirectly with a ptrtoint/inttoptr pair. // do it indirectly with a ptrtoint/inttoptr pair.
llvmValue = llvm.ConstIntToPtr(llvm.ConstPtrToInt(llvmValue, mem.r.uintptrType), llvmType) llvmValue = llvm.ConstIntToPtr(llvm.ConstPtrToInt(llvmValue, mem.r.uintptrType), llvmType)
} else {
llvmValue = llvm.ConstBitCast(llvmValue, llvmType)
} }
} }
return llvmValue, nil return llvmValue, nil
@@ -1256,7 +1245,7 @@ func (r *runner) getValue(llvmValue llvm.Value) value {
// For details on this format, see src/runtime/gc_precise.go. // For details on this format, see src/runtime/gc_precise.go.
func (r *runner) readObjectLayout(layoutValue value) (uint64, *big.Int) { func (r *runner) readObjectLayout(layoutValue value) (uint64, *big.Int) {
pointerSize := layoutValue.len(r) pointerSize := layoutValue.len(r)
if checks && uint64(pointerSize) != r.targetData.TypeAllocSize(r.i8ptrType) { if checks && uint64(pointerSize) != r.targetData.TypeAllocSize(r.dataPtrType) {
panic("inconsistent pointer size") panic("inconsistent pointer size")
} }
@@ -1331,12 +1320,12 @@ func (r *runner) getLLVMTypeFromLayout(layoutValue value) llvm.Type {
// Create the LLVM type. // Create the LLVM type.
pointerSize := layoutValue.len(r) pointerSize := layoutValue.len(r)
pointerAlignment := r.targetData.PrefTypeAlignment(r.i8ptrType) pointerAlignment := r.targetData.PrefTypeAlignment(r.dataPtrType)
var fields []llvm.Type var fields []llvm.Type
for i := 0; i < int(objectSizeWords); { for i := 0; i < int(objectSizeWords); {
if bitmap.Bit(i) != 0 { if bitmap.Bit(i) != 0 {
// Pointer field. // Pointer field.
fields = append(fields, r.i8ptrType) fields = append(fields, r.dataPtrType)
i += int(pointerSize / uint32(pointerAlignment)) i += int(pointerSize / uint32(pointerAlignment))
} else { } else {
// Byte/word field. // Byte/word field.
+2 -3
View File
@@ -18,7 +18,6 @@ import (
"errors" "errors"
"io" "io"
"io/fs" "io/fs"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path" "path"
@@ -157,7 +156,7 @@ func listGorootMergeLinks(goroot, tinygoroot string, overrides map[string]bool)
// Add files from TinyGo. // Add files from TinyGo.
tinygoDir := filepath.Join(tinygoSrc, dir) tinygoDir := filepath.Join(tinygoSrc, dir)
tinygoEntries, err := ioutil.ReadDir(tinygoDir) tinygoEntries, err := os.ReadDir(tinygoDir)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -177,7 +176,7 @@ func listGorootMergeLinks(goroot, tinygoroot string, overrides map[string]bool)
// Add all directories from $GOROOT that are not part of the TinyGo // Add all directories from $GOROOT that are not part of the TinyGo
// overrides. // overrides.
goDir := filepath.Join(goSrc, dir) goDir := filepath.Join(goSrc, dir)
goEntries, err := ioutil.ReadDir(goDir) goEntries, err := os.ReadDir(goDir)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+13 -15
View File
@@ -29,11 +29,10 @@ import (
// Program holds all packages and some metadata about the program as a whole. // Program holds all packages and some metadata about the program as a whole.
type Program struct { type Program struct {
config *compileopts.Config config *compileopts.Config
clangHeaders string typeChecker types.Config
typeChecker types.Config goroot string // synthetic GOROOT
goroot string // synthetic GOROOT workingDir string
workingDir string
Packages map[string]*Package Packages map[string]*Package
sorted []*Package sorted []*Package
@@ -103,7 +102,7 @@ type EmbedFile struct {
// Load loads the given package with all dependencies (including the runtime // Load loads the given package with all dependencies (including the runtime
// package). Call .Parse() afterwards to parse all Go files (including CGo // package). Call .Parse() afterwards to parse all Go files (including CGo
// processing, if necessary). // processing, if necessary).
func Load(config *compileopts.Config, inputPkg string, clangHeaders string, typeChecker types.Config) (*Program, error) { func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config) (*Program, error) {
goroot, err := GetCachedGoroot(config) goroot, err := GetCachedGoroot(config)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -118,13 +117,12 @@ func Load(config *compileopts.Config, inputPkg string, clangHeaders string, type
} }
} }
p := &Program{ p := &Program{
config: config, config: config,
clangHeaders: clangHeaders, typeChecker: typeChecker,
typeChecker: typeChecker, goroot: goroot,
goroot: goroot, workingDir: wd,
workingDir: wd, Packages: make(map[string]*Package),
Packages: make(map[string]*Package), fset: token.NewFileSet(),
fset: token.NewFileSet(),
} }
// List the dependencies of this package, in raw JSON format. // List the dependencies of this package, in raw JSON format.
@@ -438,9 +436,9 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
// to call cgo.Process in that case as it will only cause issues. // to call cgo.Process in that case as it will only cause issues.
if len(p.CgoFiles) != 0 && len(files) != 0 { if len(p.CgoFiles) != 0 && len(files) != 0 {
var initialCFlags []string var initialCFlags []string
initialCFlags = append(initialCFlags, p.program.config.CFlags()...) initialCFlags = append(initialCFlags, p.program.config.CFlags(true)...)
initialCFlags = append(initialCFlags, "-I"+p.Dir) initialCFlags = append(initialCFlags, "-I"+p.Dir)
generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags, p.program.clangHeaders) generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags)
p.CFlags = append(initialCFlags, cflags...) p.CFlags = append(initialCFlags, cflags...)
p.CGoHeaders = headerCode p.CGoHeaders = headerCode
for path, hash := range accessedFiles { for path, hash := range accessedFiles {
+27 -41
View File
@@ -11,7 +11,6 @@ import (
"go/scanner" "go/scanner"
"go/types" "go/types"
"io" "io"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
@@ -798,7 +797,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c
needsEnvInVars = true needsEnvInVars = true
} }
} }
var args, env []string var args, emuArgs, env []string
var extraCmdEnv []string var extraCmdEnv []string
if needsEnvInVars { if needsEnvInVars {
runtimeGlobals := make(map[string]string) runtimeGlobals := make(map[string]string)
@@ -821,13 +820,14 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c
} else if config.EmulatorName() == "wasmtime" { } else if config.EmulatorName() == "wasmtime" {
// Wasmtime needs some special flags to pass environment variables // Wasmtime needs some special flags to pass environment variables
// and allow reading from the current directory. // and allow reading from the current directory.
args = append(args, "--dir=.") emuArgs = append(emuArgs, "--dir=.")
for _, v := range environmentVars { for _, v := range environmentVars {
args = append(args, "--env", v) emuArgs = append(emuArgs, "--env", v)
} }
if len(cmdArgs) != 0 { if len(cmdArgs) != 0 {
// mark end of wasmtime arguments and start of program ones: -- // Use of '--' argument no longer necessary as of Wasmtime v14:
args = append(args, "--") // https://github.com/bytecodealliance/wasmtime/pull/6946
// args = append(args, "--")
args = append(args, cmdArgs...) args = append(args, cmdArgs...)
} }
@@ -877,7 +877,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c
return result, err return result, err
} }
name = emulator[0] name = emulator[0]
emuArgs := append([]string(nil), emulator[1:]...) emuArgs = append(emuArgs, emulator[1:]...)
args = append(emuArgs, args...) args = append(emuArgs, args...)
} }
var cmd *exec.Cmd var cmd *exec.Cmd
@@ -1212,15 +1212,10 @@ func getBMPPorts() (gdbPort, uartPort string, err error) {
} }
func usage(command string) { func usage(command string) {
version := goenv.Version
if strings.HasSuffix(version, "-dev") && goenv.GitSha1 != "" {
version += "-" + goenv.GitSha1
}
switch command { switch command {
default: default:
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.") fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
fmt.Fprintln(os.Stderr, "version:", version) fmt.Fprintln(os.Stderr, "version:", goenv.Version())
fmt.Fprintf(os.Stderr, "usage: %s <command> [arguments]\n", os.Args[0]) fmt.Fprintf(os.Stderr, "usage: %s <command> [arguments]\n", os.Args[0])
fmt.Fprintln(os.Stderr, "\ncommands:") fmt.Fprintln(os.Stderr, "\ncommands:")
fmt.Fprintln(os.Stderr, " build: compile packages and dependencies") fmt.Fprintln(os.Stderr, " build: compile packages and dependencies")
@@ -1442,6 +1437,7 @@ func main() {
llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable") llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable")
cpuprofile := flag.String("cpuprofile", "", "cpuprofile output") cpuprofile := flag.String("cpuprofile", "", "cpuprofile output")
monitor := flag.Bool("monitor", false, "enable serial monitor") monitor := flag.Bool("monitor", false, "enable serial monitor")
info := flag.Bool("info", false, "print information")
baudrate := flag.Int("baudrate", 115200, "baudrate of serial monitor") baudrate := flag.Int("baudrate", 115200, "baudrate of serial monitor")
// Internal flags, that are only intended for TinyGo development. // Internal flags, that are only intended for TinyGo development.
@@ -1737,35 +1733,29 @@ func main() {
os.Exit(1) os.Exit(1)
} }
case "monitor": case "monitor":
err := Monitor("", *port, options) if *info {
handleCompilerError(err) serialPortInfo, err := ListSerialPorts()
handleCompilerError(err)
for _, s := range serialPortInfo {
fmt.Printf("%s %4s %4s %s\n", s.Name, s.VID, s.PID, s.Target)
}
} else {
err := Monitor("", *port, options)
handleCompilerError(err)
}
case "targets": case "targets":
dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets") specs, err := compileopts.GetTargetSpecs()
entries, err := ioutil.ReadDir(dir)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, "could not list targets:", err) fmt.Fprintln(os.Stderr, "could not list targets:", err)
os.Exit(1) os.Exit(1)
return return
} }
for _, entry := range entries { names := []string{}
if !entry.Mode().IsRegular() || !strings.HasSuffix(entry.Name(), ".json") { for key := range specs {
// Only inspect JSON files. names = append(names, key)
continue }
} sort.Strings(names)
path := filepath.Join(dir, entry.Name()) for _, name := range names {
spec, err := compileopts.LoadTarget(&compileopts.Options{Target: path})
if err != nil {
fmt.Fprintln(os.Stderr, "could not list target:", err)
os.Exit(1)
return
}
if spec.FlashMethod == "" && spec.FlashCommand == "" && spec.Emulator == "" {
// This doesn't look like a regular target file, but rather like
// a parent target (such as targets/cortex-m.json).
continue
}
name := entry.Name()
name = name[:len(name)-5]
fmt.Println(name) fmt.Println(name)
} }
case "info": case "info":
@@ -1874,11 +1864,7 @@ func main() {
if s, err := goenv.GorootVersionString(); err == nil { if s, err := goenv.GorootVersionString(); err == nil {
goversion = s goversion = s
} }
version := goenv.Version fmt.Printf("tinygo version %s %s/%s (using go version %s and LLVM version %s)\n", goenv.Version(), runtime.GOOS, runtime.GOARCH, goversion, llvm.Version)
if strings.HasSuffix(goenv.Version, "-dev") && goenv.GitSha1 != "" {
version += "-" + goenv.GitSha1
}
fmt.Printf("tinygo version %s %s/%s (using go version %s and LLVM version %s)\n", version, runtime.GOOS, runtime.GOARCH, goversion, llvm.Version)
case "env": case "env":
if flag.NArg() == 0 { if flag.NArg() == 0 {
// Show all environment variables. // Show all environment variables.
+56
View File
@@ -13,12 +13,15 @@ import (
"os/signal" "os/signal"
"regexp" "regexp"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/mattn/go-tty" "github.com/mattn/go-tty"
"github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/builder"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"go.bug.st/serial" "go.bug.st/serial"
"go.bug.st/serial/enumerator"
) )
// Monitor connects to the given port and reads/writes the serial port. // Monitor connects to the given port and reads/writes the serial port.
@@ -128,6 +131,59 @@ func Monitor(executable, port string, options *compileopts.Options) error {
return <-errCh return <-errCh
} }
// SerialPortInfo is a structure that holds information about the port and its
// associated TargetSpec.
type SerialPortInfo struct {
Name string
IsUSB bool
VID string
PID string
Target string
Spec *compileopts.TargetSpec
}
// ListSerialPort returns serial port information and any detected TinyGo
// target.
func ListSerialPorts() ([]SerialPortInfo, error) {
maps, err := compileopts.GetTargetSpecs()
if err != nil {
return nil, err
}
portsList, err := enumerator.GetDetailedPortsList()
if err != nil {
return nil, err
}
serialPortInfo := []SerialPortInfo{}
for _, p := range portsList {
info := SerialPortInfo{
Name: p.Name,
IsUSB: p.IsUSB,
VID: p.VID,
PID: p.PID,
}
vid := strings.ToLower(p.VID)
pid := strings.ToLower(p.PID)
for k, v := range maps {
usbInterfaces := v.SerialPort
for _, s := range usbInterfaces {
parts := strings.Split(s, ":")
if len(parts) != 2 {
continue
}
if vid == strings.ToLower(parts[0]) && pid == strings.ToLower(parts[1]) {
info.Target = k
info.Spec = v
}
}
}
serialPortInfo = append(serialPortInfo, info)
}
return serialPortInfo, nil
}
var addressMatch = regexp.MustCompile(`^panic: runtime error at 0x([0-9a-f]+): `) var addressMatch = regexp.MustCompile(`^panic: runtime error at 0x([0-9a-f]+): `)
// Extract the address from the "panic: runtime error at" message. // Extract the address from the "panic: runtime error at" message.
+21
View File
@@ -0,0 +1,21 @@
package main
import (
"encoding/hex"
"machine"
"time"
)
func main() {
time.Sleep(2 * time.Second)
// For efficiency, it's best to get the device ID once and cache it
// (e.g. on RP2040 XIP flash and interrupts disabled for period of
// retrieving the hardware ID from ROM chip)
id := machine.DeviceID()
for {
println("Device ID:", hex.EncodeToString(id))
time.Sleep(1 * time.Second)
}
}
+3 -1
View File
@@ -24,7 +24,9 @@ func main() {
// Schedule and enable recurring interrupt. // Schedule and enable recurring interrupt.
// The callback function is executed in the context of an interrupt handler, // The callback function is executed in the context of an interrupt handler,
// so regular restructions for this sort of code apply: no blocking, no memory allocation, etc. // so regular restrictions for this sort of code apply: no blocking, no memory allocation, etc.
// Please check the online documentation for the details about interrupts:
// https://tinygo.org/docs/concepts/compiler-internals/interrupts/
delay := time.Minute + 12*time.Second delay := time.Minute + 12*time.Second
machine.RTC.SetInterrupt(uint32(delay.Seconds()), true, func() { println("Peekaboo!") }) machine.RTC.SetInterrupt(uint32(delay.Seconds()), true, func() { println("Peekaboo!") })
+17
View File
@@ -0,0 +1,17 @@
//go:build rp2040 || nrf || sam
package machine
// DeviceID returns an identifier that is unique within
// a particular chipset.
//
// The identity is one burnt into the MCU itself, or the
// flash chip at time of manufacture.
//
// It's possible that two different vendors may allocate
// the same DeviceID, so callers should take this into
// account if needing to generate a globally unique id.
//
// The length of the hardware ID is vendor-specific, but
// 8 bytes (64 bits) and 16 bytes (128 bits) are common.
var _ = (func() []byte)(DeviceID)
+13 -1
View File
@@ -1,4 +1,4 @@
//go:build atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 //go:build atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || mimxrt1062
package machine package machine
@@ -6,6 +6,17 @@ import (
"errors" "errors"
) )
// If you are getting a compile error on this line please check to see you've
// correctly implemented the methods on the I2C type. They must match
// the i2cController interface method signatures type to type perfectly.
// If not implementing the I2C type please remove your target from the build tags
// at the top of this file.
var _ interface { // 2
Configure(config I2CConfig) error
Tx(addr uint16, w, r []byte) error
SetBaudRate(br uint32) error
} = (*I2C)(nil)
// TWI_FREQ is the I2C bus speed. Normally either 100 kHz, or 400 kHz for high-speed bus. // TWI_FREQ is the I2C bus speed. Normally either 100 kHz, or 400 kHz for high-speed bus.
// //
// Deprecated: use 100 * machine.KHz or 400 * machine.KHz instead. // Deprecated: use 100 * machine.KHz or 400 * machine.KHz instead.
@@ -25,6 +36,7 @@ var (
errI2CBusError = errors.New("I2C bus error") errI2CBusError = errors.New("I2C bus error")
errI2COverflow = errors.New("I2C receive buffer overflow") errI2COverflow = errors.New("I2C receive buffer overflow")
errI2COverread = errors.New("I2C transmit buffer overflow") errI2COverread = errors.New("I2C transmit buffer overflow")
errI2CNotImplemented = errors.New("I2C operation not yet implemented")
) )
// I2CTargetEvent reflects events on the I2C bus // I2CTargetEvent reflects events on the I2C bus
+6 -1
View File
@@ -31,6 +31,11 @@ func (i2c *I2C) Configure(config I2CConfig) error {
// Activate internal pullups for twi. // Activate internal pullups for twi.
avr.PORTC.SetBits((avr.DIDR0_ADC4D | avr.DIDR0_ADC5D)) avr.PORTC.SetBits((avr.DIDR0_ADC4D | avr.DIDR0_ADC5D))
return i2c.SetBaudRate(config.Frequency)
}
// SetBaudRate sets the communication speed for I2C.
func (i2c *I2C) SetBaudRate(br uint32) error {
// Initialize twi prescaler and bit rate. // Initialize twi prescaler and bit rate.
avr.TWSR.SetBits((avr.TWSR_TWPS0 | avr.TWSR_TWPS1)) avr.TWSR.SetBits((avr.TWSR_TWPS0 | avr.TWSR_TWPS1))
@@ -38,7 +43,7 @@ func (i2c *I2C) Configure(config I2CConfig) error {
// SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR)) // SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
// NOTE: TWBR should be 10 or higher for controller mode. // NOTE: TWBR should be 10 or higher for controller mode.
// It is 72 for a 16mhz board with 100kHz TWI // It is 72 for a 16mhz board with 100kHz TWI
avr.TWBR.Set(uint8(((CPUFrequency() / config.Frequency) - 16) / 2)) avr.TWBR.Set(uint8(((CPUFrequency() / br) - 16) / 2))
// Enable twi module. // Enable twi module.
avr.TWCR.Set(avr.TWCR_TWEN) avr.TWCR.Set(avr.TWCR_TWEN)
+31
View File
@@ -0,0 +1,31 @@
//go:build sam
package machine
import (
"runtime/volatile"
"unsafe"
)
var deviceID [16]byte
// DeviceID returns an identifier that is unique within
// a particular chipset.
//
// The identity is one burnt into the MCU itself, or the
// flash chip at time of manufacture.
//
// It's possible that two different vendors may allocate
// the same DeviceID, so callers should take this into
// account if needing to generate a globally unique id.
//
// The length of the hardware ID is vendor-specific, but
// 8 bytes (64 bits) and 16 bytes (128 bits) are common.
func DeviceID() []byte {
for i := 0; i < len(deviceID); i++ {
word := (*volatile.Register32)(unsafe.Pointer(deviceIDAddr[i/4])).Get()
deviceID[i] = byte(word >> ((i % 4) * 8))
}
return deviceID[:]
}
+6 -2
View File
@@ -18,6 +18,9 @@ import (
const deviceName = sam.Device const deviceName = sam.Device
// DS40001882F, Section 10.3.3: Serial Number
var deviceIDAddr = []uintptr{0x0080A00C, 0x0080A040, 0x0080A044, 0x0080A048}
const ( const (
PinAnalog PinMode = 1 PinAnalog PinMode = 1
PinSERCOM PinMode = 2 PinSERCOM PinMode = 2
@@ -732,12 +735,13 @@ func (i2c *I2C) Configure(config I2CConfig) error {
return nil return nil
} }
// SetBaudRate sets the communication speed for the I2C. // SetBaudRate sets the communication speed for I2C.
func (i2c *I2C) SetBaudRate(br uint32) { func (i2c *I2C) SetBaudRate(br uint32) error {
// Synchronous arithmetic baudrate, via Arduino SAMD implementation: // Synchronous arithmetic baudrate, via Arduino SAMD implementation:
// SystemCoreClock / ( 2 * baudrate) - 5 - (((SystemCoreClock / 1000000) * WIRE_RISE_TIME_NANOSECONDS) / (2 * 1000)); // SystemCoreClock / ( 2 * baudrate) - 5 - (((SystemCoreClock / 1000000) * WIRE_RISE_TIME_NANOSECONDS) / (2 * 1000));
baud := CPUFrequency()/(2*br) - 5 - (((CPUFrequency() / 1000000) * riseTimeNanoseconds) / (2 * 1000)) baud := CPUFrequency()/(2*br) - 5 - (((CPUFrequency() / 1000000) * riseTimeNanoseconds) / (2 * 1000))
i2c.Bus.BAUD.Set(baud) i2c.Bus.BAUD.Set(baud)
return nil
} }
// Tx does a single I2C transaction at the specified address. // Tx does a single I2C transaction at the specified address.
+151 -2
View File
@@ -18,6 +18,9 @@ import (
const deviceName = sam.Device const deviceName = sam.Device
// DS60001507, Section 9.6: Serial Number
var deviceIDAddr = []uintptr{0x008061FC, 0x00806010, 0x00806014, 0x00806018}
func CPUFrequency() uint32 { func CPUFrequency() uint32 {
return 120000000 return 120000000
} }
@@ -680,6 +683,56 @@ func (p Pin) getPinGrouping() (uint8, uint8) {
return group, pin_in_group return group, pin_in_group
} }
// Static DMA channel allocation.
// If there are a lot of DMA using peripherals, we might need to switch to
// dynamic allocation instead.
const (
dmaChannelSERCOM0 = iota
dmaChannelSERCOM1
dmaChannelSERCOM2
dmaChannelSERCOM3
dmaChannelSERCOM4
dmaChannelSERCOM5
dmaChannelSERCOM6
dmaChannelSERCOM7
dmaNumChannels
)
// DMA descriptor structure. This structure is defined by the hardware, and is
// described by "22.9 Register Summary - SRAM" in the datasheet.
type dmaDescriptor struct {
btctrl uint16
btcnt uint16
srcaddr unsafe.Pointer
dstaddr unsafe.Pointer
descaddr unsafe.Pointer
}
//go:align 16
var dmaDescriptorSection [dmaNumChannels]dmaDescriptor
//go:align 16
var dmaDescriptorWritebackSection [dmaNumChannels]dmaDescriptor
// Enable and configure the DMAC peripheral if it hasn't been enabled already.
func enableDMAC() {
if !sam.DMAC.CTRL.HasBits(sam.DMAC_CTRL_DMAENABLE) {
// Init DMAC.
// First configure the clocks, then configure the DMA descriptors. Those
// descriptors must live in SRAM and must be aligned on a 16-byte
// boundary.
// Some examples:
// http://www.lucadavidian.com/2018/03/08/wifi-controlled-neo-pixels-strips/
// https://svn.larosterna.com/oss/trunk/arduino/zerotimer/zerodma.cpp
sam.MCLK.AHBMASK.SetBits(sam.MCLK_AHBMASK_DMAC_)
sam.DMAC.BASEADDR.Set(uint32(uintptr(unsafe.Pointer(&dmaDescriptorSection))))
sam.DMAC.WRBADDR.Set(uint32(uintptr(unsafe.Pointer(&dmaDescriptorWritebackSection))))
// Enable peripheral with all priorities.
sam.DMAC.CTRL.SetBits(sam.DMAC_CTRL_DMAENABLE | sam.DMAC_CTRL_LVLEN0 | sam.DMAC_CTRL_LVLEN1 | sam.DMAC_CTRL_LVLEN2 | sam.DMAC_CTRL_LVLEN3)
}
}
// InitADC initializes the ADC. // InitADC initializes the ADC.
func InitADC() { func InitADC() {
// ADC Bias Calibration // ADC Bias Calibration
@@ -1228,12 +1281,13 @@ func (i2c *I2C) Configure(config I2CConfig) error {
return nil return nil
} }
// SetBaudRate sets the communication speed for the I2C. // SetBaudRate sets the communication speed for I2C.
func (i2c *I2C) SetBaudRate(br uint32) { func (i2c *I2C) SetBaudRate(br uint32) error {
// Synchronous arithmetic baudrate, via Adafruit SAMD51 implementation: // Synchronous arithmetic baudrate, via Adafruit SAMD51 implementation:
// sercom->I2CM.BAUD.bit.BAUD = SERCOM_FREQ_REF / ( 2 * baudrate) - 1 ; // sercom->I2CM.BAUD.bit.BAUD = SERCOM_FREQ_REF / ( 2 * baudrate) - 1 ;
baud := SERCOM_FREQ_REF/(2*br) - 1 baud := SERCOM_FREQ_REF/(2*br) - 1
i2c.Bus.BAUD.Set(baud) i2c.Bus.BAUD.Set(baud)
return nil
} }
// Tx does a single I2C transaction at the specified address. // Tx does a single I2C transaction at the specified address.
@@ -1648,6 +1702,101 @@ func (spi SPI) txrx(tx, rx []byte) {
rx[len(rx)-1] = byte(spi.Bus.DATA.Get()) rx[len(rx)-1] = byte(spi.Bus.DATA.Get())
} }
// Channel to be used for SPI transfers.
// These channels are currently statically allocated.
func (spi SPI) dmaTxChannel() uint8 {
return dmaChannelSERCOM0 + spi.SERCOM
}
// IsAsync returns whether the SPI supports async operation (usually DMA).
//
// It returns true on the SAM D5x chips.
func (spi SPI) IsAsync() bool {
return true
}
// Start a transfer in the background.
//
// After this, another StartTx() or Wait() must be called. The provided byte
// slices (tx and rx) may only be accessed again after Wait() was called.
func (s SPI) StartTx(tx, rx []byte) error {
// Check whether we support doing this transfer using DMA.
if len(rx) != 0 {
return s.Tx(tx, rx)
}
if len(tx) == 0 {
return nil // nothing to send/receive
}
if len(tx) != int(uint16(len(tx))) {
// The transfer size is a 16-bit field.
// TODO: chain multiple transfers in some way when encountering these
// large buffer sizes. They're not currently implemented because
// transferring 64kB of data is less commonly done.
return s.Tx(tx, rx)
}
// Enable DMAC (if not already enabled).
enableDMAC()
// Wait until a possible previous transfer has been completed.
s.Wait()
// Configure the DMA channel, if it hasn't been configured already.
dstaddr := unsafe.Pointer(&s.Bus.DATA.Reg)
if dmaDescriptorSection[s.dmaTxChannel()].dstaddr != dstaddr {
// Configure channel descriptor.
dmaDescriptorSection[s.dmaTxChannel()] = dmaDescriptor{
btctrl: (1 << 0) | // VALID: Descriptor Valid
(0 << 3) | // BLOCKACT=NOACT: Block Action
(1 << 10) | // SRCINC: Source Address Increment Enable
(0 << 11) | // DSTINC: Destination Address Increment Enable
(1 << 12) | // STEPSEL=SRC: Step Selection
(0 << 13), // STEPSIZE=X1: Address Increment Step Size
dstaddr: dstaddr,
}
// Reset channel.
sam.DMAC.CHANNEL[s.dmaTxChannel()].CHCTRLA.ClearBits(sam.DMAC_CHANNEL_CHCTRLA_ENABLE)
sam.DMAC.CHANNEL[s.dmaTxChannel()].CHCTRLA.SetBits(sam.DMAC_CHANNEL_CHCTRLA_SWRST)
// Configure channel.
sam.DMAC.CHANNEL[s.dmaTxChannel()].CHPRILVL.Set(0)
sam.DMAC.CHANNEL[s.dmaTxChannel()].CHCTRLA.Set((sam.DMAC_CHANNEL_CHCTRLA_TRIGACT_BURST << sam.DMAC_CHANNEL_CHCTRLA_TRIGACT_Pos) |
(s.triggerSource() << sam.DMAC_CHANNEL_CHCTRLA_TRIGSRC_Pos) |
(sam.DMAC_CHANNEL_CHCTRLA_BURSTLEN_SINGLE << sam.DMAC_CHANNEL_CHCTRLA_BURSTLEN_Pos))
}
// For some reason, you have to provide the address just past the end of the
// array instead of the address of the array.
descriptor := &dmaDescriptorSection[s.dmaTxChannel()]
descriptor.srcaddr = unsafe.Pointer(uintptr(unsafe.Pointer(&tx[0])) + uintptr(len(tx)))
descriptor.btcnt = uint16(len(tx)) // beat count
// Start the transfer.
sam.DMAC.CHANNEL[s.dmaTxChannel()].CHCTRLA.SetBits(sam.DMAC_CHANNEL_CHCTRLA_ENABLE)
return nil
}
// Wait until all active transactions (started by StartTx) have finished. The
// buffers provided in StartTx will be available after this method returns.
func (spi SPI) Wait() error {
// Wait until the previous SPI transfer completed.
// This is basically the same thing as in SPI.tx.
// TODO: maybe block (and sleep) until the transfer has completed?
for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_TXC) {
}
// read to clear RXC register
for spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_RXC) {
spi.Bus.DATA.Get()
}
return nil
}
// The QSPI peripheral on ATSAMD51 is only available on the following pins // The QSPI peripheral on ATSAMD51 is only available on the following pins
const ( const (
QSPI_SCK = PB10 QSPI_SCK = PB10
+21
View File
@@ -62,6 +62,27 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) {
} }
} }
// DMA trigger source
func (spi SPI) triggerSource() (tx uint32) {
// See TRIGSRC field of CHCTRLA register for description of these constants.
switch spi.Bus {
case sam.SERCOM0_SPIM:
return 0x05
case sam.SERCOM1_SPIM:
return 0x07
case sam.SERCOM2_SPIM:
return 0x09
case sam.SERCOM3_SPIM:
return 0x0A
case sam.SERCOM4_SPIM:
return 0x0C
case sam.SERCOM5_SPIM:
return 0x0E
default:
return 0 // should be unreachable
}
}
// This chip has three TCC peripherals, which have PWM as one feature. // This chip has three TCC peripherals, which have PWM as one feature.
var ( var (
TCC0 = (*TCC)(sam.TCC0) TCC0 = (*TCC)(sam.TCC0)
+21
View File
@@ -62,6 +62,27 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) {
} }
} }
// DMA trigger source
func (spi SPI) triggerSource() (tx uint32) {
// See TRIGSRC field of CHCTRLA register for description of these constants.
switch spi.Bus {
case sam.SERCOM0_SPIM:
return 0x05
case sam.SERCOM1_SPIM:
return 0x07
case sam.SERCOM2_SPIM:
return 0x09
case sam.SERCOM3_SPIM:
return 0x0B
case sam.SERCOM4_SPIM:
return 0x0D
case sam.SERCOM5_SPIM:
return 0x0F
default:
return 0 // should be unreachable
}
}
// This chip has five TCC peripherals, which have PWM as one feature. // This chip has five TCC peripherals, which have PWM as one feature.
var ( var (
TCC0 = (*TCC)(sam.TCC0) TCC0 = (*TCC)(sam.TCC0)
+21
View File
@@ -62,6 +62,27 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) {
} }
} }
// DMA trigger source
func (spi SPI) triggerSource() (tx uint32) {
// See TRIGSRC field of CHCTRLA register for description of these constants.
switch spi.Bus {
case sam.SERCOM0_SPIM:
return 0x05
case sam.SERCOM1_SPIM:
return 0x07
case sam.SERCOM2_SPIM:
return 0x09
case sam.SERCOM3_SPIM:
return 0x0A
case sam.SERCOM4_SPIM:
return 0x0C
case sam.SERCOM5_SPIM:
return 0x0E
default:
return 0 // should be unreachable
}
}
// This chip has five TCC peripherals, which have PWM as one feature. // This chip has five TCC peripherals, which have PWM as one feature.
var ( var (
TCC0 = (*TCC)(sam.TCC0) TCC0 = (*TCC)(sam.TCC0)
+25
View File
@@ -76,6 +76,31 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) {
} }
} }
// DMA trigger source
func (spi SPI) triggerSource() (tx uint32) {
// See TRIGSRC field of CHCTRLA register for description of these constants.
switch spi.Bus {
case sam.SERCOM0_SPIM:
return 0x05
case sam.SERCOM1_SPIM:
return 0x07
case sam.SERCOM2_SPIM:
return 0x09
case sam.SERCOM3_SPIM:
return 0x0A
case sam.SERCOM4_SPIM:
return 0x0C
case sam.SERCOM5_SPIM:
return 0x0E
case sam.SERCOM6_SPIM:
return 0x10
case sam.SERCOM7_SPIM:
return 0x12
default:
return 0 // should be unreachable
}
}
// This chip has five TCC peripherals, which have PWM as one feature. // This chip has five TCC peripherals, which have PWM as one feature.
var ( var (
TCC0 = (*TCC)(sam.TCC0) TCC0 = (*TCC)(sam.TCC0)
+25
View File
@@ -76,6 +76,31 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) {
} }
} }
// DMA trigger source
func (spi SPI) triggerSource() (tx uint32) {
// See TRIGSRC field of CHCTRLA register for description of these constants.
switch spi.Bus {
case sam.SERCOM0_SPIM:
return 0x05
case sam.SERCOM1_SPIM:
return 0x07
case sam.SERCOM2_SPIM:
return 0x09
case sam.SERCOM3_SPIM:
return 0x0A
case sam.SERCOM4_SPIM:
return 0x0C
case sam.SERCOM5_SPIM:
return 0x0E
case sam.SERCOM6_SPIM:
return 0x10
case sam.SERCOM7_SPIM:
return 0x12
default:
return 0 // should be unreachable
}
}
// This chip has five TCC peripherals, which have PWM as one feature. // This chip has five TCC peripherals, which have PWM as one feature.
var ( var (
TCC0 = (*TCC)(sam.TCC0) TCC0 = (*TCC)(sam.TCC0)
+21
View File
@@ -62,6 +62,27 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) {
} }
} }
// DMA trigger source
func (spi SPI) triggerSource() (tx uint32) {
// See TRIGSRC field of CHCTRLA register for description of these constants.
switch spi.Bus {
case sam.SERCOM0_SPIM:
return 0x05
case sam.SERCOM1_SPIM:
return 0x07
case sam.SERCOM2_SPIM:
return 0x09
case sam.SERCOM3_SPIM:
return 0x0A
case sam.SERCOM4_SPIM:
return 0x0C
case sam.SERCOM5_SPIM:
return 0x0E
default:
return 0 // should be unreachable
}
}
// This chip has five TCC peripherals, which have PWM as one feature. // This chip has five TCC peripherals, which have PWM as one feature.
var ( var (
TCC0 = (*TCC)(sam.TCC0) TCC0 = (*TCC)(sam.TCC0)
+25
View File
@@ -76,6 +76,31 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) {
} }
} }
// DMA trigger source
func (spi SPI) triggerSource() (tx uint32) {
// See TRIGSRC field of CHCTRLA register for description of these constants.
switch spi.Bus {
case sam.SERCOM0_SPIM:
return 0x05
case sam.SERCOM1_SPIM:
return 0x07
case sam.SERCOM2_SPIM:
return 0x09
case sam.SERCOM3_SPIM:
return 0x0A
case sam.SERCOM4_SPIM:
return 0x0C
case sam.SERCOM5_SPIM:
return 0x0E
case sam.SERCOM6_SPIM:
return 0x10
case sam.SERCOM7_SPIM:
return 0x12
default:
return 0 // should be unreachable
}
}
// This chip has five TCC peripherals, which have PWM as one feature. // This chip has five TCC peripherals, which have PWM as one feature.
var ( var (
TCC0 = (*TCC)(sam.TCC0) TCC0 = (*TCC)(sam.TCC0)
+13 -5
View File
@@ -229,9 +229,10 @@ type I2CConfig struct {
SDA Pin SDA Pin
} }
var i2cClockFrequency uint32 = 32000000
// Configure is intended to setup the I2C interface. // Configure is intended to setup the I2C interface.
func (i2c *I2C) Configure(config I2CConfig) error { func (i2c *I2C) Configure(config I2CConfig) error {
var i2cClockFrequency uint32 = 32000000
if config.Frequency == 0 { if config.Frequency == 0 {
config.Frequency = 100 * KHz config.Frequency = 100 * KHz
} }
@@ -241,7 +242,17 @@ func (i2c *I2C) Configure(config I2CConfig) error {
config.SCL = I2C0_SCL_PIN config.SCL = I2C0_SCL_PIN
} }
var prescaler = i2cClockFrequency/(5*config.Frequency) - 1 i2c.SetBaudRate(config.Frequency)
config.SDA.Configure(PinConfig{Mode: PinI2C})
config.SCL.Configure(PinConfig{Mode: PinI2C})
return nil
}
// SetBaudRate sets the communication speed for I2C.
func (i2c *I2C) SetBaudRate(br uint32) error {
var prescaler = i2cClockFrequency/(5*br) - 1
// disable controller before setting the prescale registers // disable controller before setting the prescale registers
i2c.Bus.CTR.ClearBits(sifive.I2C_CTR_EN) i2c.Bus.CTR.ClearBits(sifive.I2C_CTR_EN)
@@ -253,9 +264,6 @@ func (i2c *I2C) Configure(config I2CConfig) error {
// enable controller // enable controller
i2c.Bus.CTR.SetBits(sifive.I2C_CTR_EN) i2c.Bus.CTR.SetBits(sifive.I2C_CTR_EN)
config.SDA.Configure(PinConfig{Mode: PinI2C})
config.SCL.Configure(PinConfig{Mode: PinI2C})
return nil return nil
} }
+11 -1
View File
@@ -57,8 +57,9 @@ type SPIConfig struct {
Mode uint8 Mode uint8
} }
func (spi SPI) Configure(config SPIConfig) { func (spi SPI) Configure(config SPIConfig) error {
spiConfigure(spi.Bus, config.SCK, config.SDO, config.SDI) spiConfigure(spi.Bus, config.SCK, config.SDO, config.SDI)
return nil
} }
// Transfer writes/reads a single byte using the SPI interface. // Transfer writes/reads a single byte using the SPI interface.
@@ -107,6 +108,12 @@ func (i2c *I2C) Configure(config I2CConfig) error {
return nil return nil
} }
// SetBaudRate sets the I2C frequency.
func (i2c *I2C) SetBaudRate(br uint32) error {
i2cSetBaudRate(i2c.Bus, br)
return nil
}
// Tx does a single I2C transaction at the specified address. // Tx does a single I2C transaction at the specified address.
func (i2c *I2C) Tx(addr uint16, w, r []byte) error { func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
i2cTransfer(i2c.Bus, &w[0], len(w), &r[0], len(r)) i2cTransfer(i2c.Bus, &w[0], len(w), &r[0], len(r))
@@ -117,6 +124,9 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
//export __tinygo_i2c_configure //export __tinygo_i2c_configure
func i2cConfigure(bus uint8, scl Pin, sda Pin) func i2cConfigure(bus uint8, scl Pin, sda Pin)
//export __tinygo_i2c_set_baud_rate
func i2cSetBaudRate(bus uint8, br uint32)
//export __tinygo_i2c_transfer //export __tinygo_i2c_transfer
func i2cTransfer(bus uint8, w *byte, wlen int, r *byte, rlen int) int func i2cTransfer(bus uint8, w *byte, wlen int, r *byte, rlen int) int
+13 -6
View File
@@ -563,7 +563,19 @@ func (i2c *I2C) Configure(config I2CConfig) error {
config.SCL.SetFPIOAFunction(FUNC_I2C2_SCLK) config.SCL.SetFPIOAFunction(FUNC_I2C2_SCLK)
} }
div := CPUFrequency() / config.Frequency / 16 i2c.SetBaudRate(config.Frequency)
i2c.Bus.INTR_MASK.Set(0)
i2c.Bus.DMA_CR.Set(0x03)
i2c.Bus.DMA_RDLR.Set(0)
i2c.Bus.DMA_TDLR.Set(0x4)
return nil
}
// SetBaudRate sets the communication speed for I2C.
func (i2c *I2C) SetBaudRate(br uint32) error {
div := CPUFrequency() / br / 16
// Disable controller before setting the prescale register. // Disable controller before setting the prescale register.
i2c.Bus.ENABLE.Set(0) i2c.Bus.ENABLE.Set(0)
@@ -574,11 +586,6 @@ func (i2c *I2C) Configure(config I2CConfig) error {
i2c.Bus.SS_SCL_HCNT.Set(uint32(div)) i2c.Bus.SS_SCL_HCNT.Set(uint32(div))
i2c.Bus.SS_SCL_LCNT.Set(uint32(div)) i2c.Bus.SS_SCL_LCNT.Set(uint32(div))
i2c.Bus.INTR_MASK.Set(0)
i2c.Bus.DMA_CR.Set(0x03)
i2c.Bus.DMA_RDLR.Set(0)
i2c.Bus.DMA_TDLR.Set(0x4)
return nil return nil
} }
+14 -19
View File
@@ -6,19 +6,6 @@ package machine
import ( import (
"device/nxp" "device/nxp"
"errors"
)
var (
errI2CWriteTimeout = errors.New("I2C timeout during write")
errI2CReadTimeout = errors.New("I2C timeout during read")
errI2CBusReadyTimeout = errors.New("I2C timeout on bus ready")
errI2CSignalStartTimeout = errors.New("I2C timeout on signal start")
errI2CSignalReadTimeout = errors.New("I2C timeout on signal read")
errI2CSignalStopTimeout = errors.New("I2C timeout on signal stop")
errI2CAckExpected = errors.New("I2C error: expected ACK not NACK")
errI2CBusError = errors.New("I2C bus error")
errI2CNotConfigured = errors.New("I2C interface is not yet configured")
) )
// I2CConfig is used to store config info for I2C. // I2CConfig is used to store config info for I2C.
@@ -150,7 +137,7 @@ func (i2c *I2C) setPins(c I2CConfig) (sda, scl Pin) {
} }
// Configure is intended to setup an I2C interface for transmit/receive. // Configure is intended to setup an I2C interface for transmit/receive.
func (i2c *I2C) Configure(config I2CConfig) { func (i2c *I2C) Configure(config I2CConfig) error {
// init pins // init pins
sda, scl := i2c.setPins(config) sda, scl := i2c.setPins(config)
@@ -169,6 +156,14 @@ func (i2c *I2C) Configure(config I2CConfig) {
// reset clock and registers, and enable LPI2C module interface // reset clock and registers, and enable LPI2C module interface
i2c.reset(freq) i2c.reset(freq)
return nil
}
// SetBaudRate sets the communication speed for I2C.
func (i2c I2C) SetBaudRate(br uint32) error {
// TODO: implement
return errI2CNotImplemented
} }
func (i2c I2C) Tx(addr uint16, w, r []byte) error { func (i2c I2C) Tx(addr uint16, w, r []byte) error {
@@ -212,13 +207,13 @@ func (i2c I2C) Tx(addr uint16, w, r []byte) error {
return nil return nil
} }
// WriteRegister transmits first the register and then the data to the // WriteRegisterEx transmits first the register and then the data to the
// peripheral device. // peripheral device.
// //
// Many I2C-compatible devices are organized in terms of registers. This method // Many I2C-compatible devices are organized in terms of registers. This method
// is a shortcut to easily write to such registers. Also, it only works for // is a shortcut to easily write to such registers. Also, it only works for
// devices with 7-bit addresses, which is the vast majority. // devices with 7-bit addresses, which is the vast majority.
func (i2c I2C) WriteRegister(address uint8, register uint8, data []byte) error { func (i2c I2C) WriteRegisterEx(address uint8, register uint8, data []byte) error {
option := transferOption{ option := transferOption{
flags: transferDefault, // transfer options bit mask (0 = normal transfer) flags: transferDefault, // transfer options bit mask (0 = normal transfer)
peripheral: uint16(address), // 7-bit peripheral address peripheral: uint16(address), // 7-bit peripheral address
@@ -232,13 +227,13 @@ func (i2c I2C) WriteRegister(address uint8, register uint8, data []byte) error {
return nil return nil
} }
// ReadRegister transmits the register, restarts the connection as a read // ReadRegisterEx transmits the register, restarts the connection as a read
// operation, and reads the response. // operation, and reads the response.
// //
// Many I2C-compatible devices are organized in terms of registers. This method // Many I2C-compatible devices are organized in terms of registers. This method
// is a shortcut to easily read such registers. Also, it only works for devices // is a shortcut to easily read such registers. Also, it only works for devices
// with 7-bit addresses, which is the vast majority. // with 7-bit addresses, which is the vast majority.
func (i2c I2C) ReadRegister(address uint8, register uint8, data []byte) error { func (i2c I2C) ReadRegisterEx(address uint8, register uint8, data []byte) error {
option := transferOption{ option := transferOption{
flags: transferDefault, // transfer options bit mask (0 = normal transfer) flags: transferDefault, // transfer options bit mask (0 = normal transfer)
peripheral: uint16(address), // 7-bit peripheral address peripheral: uint16(address), // 7-bit peripheral address
@@ -560,7 +555,7 @@ func (i2c *I2C) controllerReceive(rxBuffer []byte) resultFlag {
return result return result
} }
// controllerReceive performs a polling transmit transfer on the I2C bus. // controllerTransmit performs a polling transmit transfer on the I2C bus.
func (i2c *I2C) controllerTransmit(txBuffer []byte) resultFlag { func (i2c *I2C) controllerTransmit(txBuffer []byte) resultFlag {
txSize := len(txBuffer) txSize := len(txBuffer)
for txSize > 0 { for txSize > 0 {
+3 -1
View File
@@ -68,7 +68,7 @@ var (
) )
// Configure is intended to setup an SPI interface for transmit/receive. // Configure is intended to setup an SPI interface for transmit/receive.
func (spi *SPI) Configure(config SPIConfig) { func (spi *SPI) Configure(config SPIConfig) error {
const defaultSpiFreq = 4000000 // 4 MHz const defaultSpiFreq = 4000000 // 4 MHz
@@ -132,6 +132,8 @@ func (spi *SPI) Configure(config SPIConfig) {
spi.Bus.CR.Set(nxp.LPSPI_CR_MEN) spi.Bus.CR.Set(nxp.LPSPI_CR_MEN)
spi.configured = true spi.configured = true
return nil
} }
// Transfer writes/reads a single byte using the SPI interface. // Transfer writes/reads a single byte using the SPI interface.
+46 -6
View File
@@ -12,6 +12,34 @@ import (
const deviceName = nrf.Device const deviceName = nrf.Device
var deviceID [8]byte
// DeviceID returns an identifier that is unique within
// a particular chipset.
//
// The identity is one burnt into the MCU itself, or the
// flash chip at time of manufacture.
//
// It's possible that two different vendors may allocate
// the same DeviceID, so callers should take this into
// account if needing to generate a globally unique id.
//
// The length of the hardware ID is vendor-specific, but
// 8 bytes (64 bits) is common.
func DeviceID() []byte {
words := make([]uint32, 2)
words[0] = nrf.FICR.DEVICEID[0].Get()
words[1] = nrf.FICR.DEVICEID[1].Get()
for i := 0; i < 8; i++ {
shift := (i % 4) * 8
w := i / 4
deviceID[i] = byte(words[w] >> shift)
}
return deviceID[:]
}
const ( const (
PinInput PinMode = (nrf.GPIO_PIN_CNF_DIR_Input << nrf.GPIO_PIN_CNF_DIR_Pos) | (nrf.GPIO_PIN_CNF_INPUT_Connect << nrf.GPIO_PIN_CNF_INPUT_Pos) PinInput PinMode = (nrf.GPIO_PIN_CNF_DIR_Input << nrf.GPIO_PIN_CNF_DIR_Pos) | (nrf.GPIO_PIN_CNF_INPUT_Connect << nrf.GPIO_PIN_CNF_INPUT_Pos)
PinInputPullup PinMode = PinInput | (nrf.GPIO_PIN_CNF_PULL_Pullup << nrf.GPIO_PIN_CNF_PULL_Pos) PinInputPullup PinMode = PinInput | (nrf.GPIO_PIN_CNF_PULL_Pullup << nrf.GPIO_PIN_CNF_PULL_Pos)
@@ -246,13 +274,8 @@ func (i2c *I2C) Configure(config I2CConfig) error {
i2c.setPins(config.SCL, config.SDA) i2c.setPins(config.SCL, config.SDA)
i2c.mode = config.Mode i2c.mode = config.Mode
if i2c.mode == I2CModeController { if i2c.mode == I2CModeController {
if config.Frequency >= 400*KHz { i2c.SetBaudRate(config.Frequency)
i2c.Bus.FREQUENCY.Set(nrf.TWI_FREQUENCY_FREQUENCY_K400)
} else {
i2c.Bus.FREQUENCY.Set(nrf.TWI_FREQUENCY_FREQUENCY_K100)
}
i2c.enableAsController() i2c.enableAsController()
} else { } else {
@@ -262,6 +285,23 @@ func (i2c *I2C) Configure(config I2CConfig) error {
return nil return nil
} }
// SetBaudRate sets the I2C frequency. It has the side effect of also
// enabling the I2C hardware if disabled beforehand.
//
//go:inline
func (i2c *I2C) SetBaudRate(br uint32) error {
switch {
case br >= 400*KHz:
i2c.Bus.SetFREQUENCY(nrf.TWI_FREQUENCY_FREQUENCY_K400)
case br >= 250*KHz:
i2c.Bus.SetFREQUENCY(nrf.TWI_FREQUENCY_FREQUENCY_K250)
default:
i2c.Bus.SetFREQUENCY(nrf.TWI_FREQUENCY_FREQUENCY_K100)
}
return nil
}
// signalStop sends a stop signal to the I2C peripheral and waits for confirmation. // signalStop sends a stop signal to the I2C peripheral and waits for confirmation.
func (i2c *I2C) signalStop() error { func (i2c *I2C) signalStop() error {
tries := 0 tries := 0
+3 -1
View File
@@ -49,7 +49,7 @@ type SPIConfig struct {
} }
// Configure is intended to setup the SPI interface. // Configure is intended to setup the SPI interface.
func (spi SPI) Configure(config SPIConfig) { func (spi SPI) Configure(config SPIConfig) error {
// Disable bus to configure it // Disable bus to configure it
spi.Bus.ENABLE.Set(nrf.SPI_ENABLE_ENABLE_Disabled) spi.Bus.ENABLE.Set(nrf.SPI_ENABLE_ENABLE_Disabled)
@@ -117,6 +117,8 @@ func (spi SPI) Configure(config SPIConfig) {
// Re-enable bus now that it is configured. // Re-enable bus now that it is configured.
spi.Bus.ENABLE.Set(nrf.SPI_ENABLE_ENABLE_Enabled) spi.Bus.ENABLE.Set(nrf.SPI_ENABLE_ENABLE_Enabled)
return nil
} }
// Transfer writes/reads a single byte using the SPI interface. // Transfer writes/reads a single byte using the SPI interface.
+3 -1
View File
@@ -197,7 +197,7 @@ type SPIConfig struct {
} }
// Configure is intended to setup the SPI interface. // Configure is intended to setup the SPI interface.
func (spi SPI) Configure(config SPIConfig) { func (spi SPI) Configure(config SPIConfig) error {
// Disable bus to configure it // Disable bus to configure it
spi.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Disabled) spi.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Disabled)
@@ -265,6 +265,8 @@ func (spi SPI) Configure(config SPIConfig) {
// Re-enable bus now that it is configured. // Re-enable bus now that it is configured.
spi.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Enabled) spi.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Enabled)
return nil
} }
// Transfer writes/reads a single byte using the SPI interface. // Transfer writes/reads a single byte using the SPI interface.
+26
View File
@@ -13,6 +13,32 @@ func EnterBootloader() {
enterBootloader() enterBootloader()
} }
// 13 = 1 + FLASH_RUID_DUMMY_BYTES(4) + FLASH_RUID_DATA_BYTES(8)
var deviceIDBuf [13]byte
// DeviceID returns an identifier that is unique within
// a particular chipset.
//
// The identity is one burnt into the MCU itself, or the
// flash chip at time of manufacture.
//
// It's possible that two different vendors may allocate
// the same DeviceID, so callers should take this into
// account if needing to generate a globally unique id.
//
// The length of the hardware ID is vendor-specific, but
// 8 bytes (64 bits) is common.
func DeviceID() []byte {
deviceIDBuf[0] = 0x4b // FLASH_RUID_CMD
err := doFlashCommand(deviceIDBuf[:], deviceIDBuf[:])
if err != nil {
panic(err)
}
return deviceIDBuf[5:13]
}
// compile-time check for ensuring we fulfill BlockDevice interface // compile-time check for ensuring we fulfill BlockDevice interface
var _ BlockDevice = flashBlockDevice{} var _ BlockDevice = flashBlockDevice{}
+76
View File
@@ -93,6 +93,33 @@ static ram_func void flash_enable_xip_via_boot2() {
((void (*)(void))boot2_copyout+1)(); ((void (*)(void))boot2_copyout+1)();
} }
#define IO_QSPI_BASE 0x40018000
#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS 0x00000300
#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_MSB 9
#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB 8
#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW 0x2
#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH 0x3
#define XIP_SSI_BASE 0x18000000
#define ssi_hw ((ssi_hw_t *)XIP_SSI_BASE)
#define SSI_SR_OFFSET 0x00000028
#define SSI_DR0_OFFSET 0x00000060
#define SSI_SR_TFNF_BITS 0x00000002
#define SSI_SR_RFNE_BITS 0x00000008
void ram_func flash_cs_force(bool high) {
uint32_t field_val = high ?
IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH :
IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW;
// &ioqspi_hw->io[1].ctrl
uint32_t *addr = (uint32_t*)(IO_QSPI_BASE + (1 * 8) + 4);
*addr = ((*addr) & !IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS)
| (field_val << IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB);
}
// See https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_flash/flash.c#L86 // See https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_flash/flash.c#L86
void ram_func flash_range_write(uint32_t offset, const uint8_t *data, size_t count) void ram_func flash_range_write(uint32_t offset, const uint8_t *data, size_t count)
{ {
@@ -132,6 +159,42 @@ void ram_func flash_erase_blocks(uint32_t offset, size_t count)
flash_enable_xip_via_boot2(); flash_enable_xip_via_boot2();
} }
void ram_func flash_do_cmd(const uint8_t *txbuf, uint8_t *rxbuf, size_t count) {
flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn) rom_func_lookup(ROM_FUNC_CONNECT_INTERNAL_FLASH);
flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn) rom_func_lookup(ROM_FUNC_FLASH_EXIT_XIP);
flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn) rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE);
flash_init_boot2_copyout();
__compiler_memory_barrier();
flash_connect_internal_func();
flash_exit_xip_func();
flash_cs_force(0);
size_t tx_remaining = count;
size_t rx_remaining = count;
// We may be interrupted -- don't want FIFO to overflow if we're distracted.
const size_t max_in_flight = 16 - 2;
while (tx_remaining || rx_remaining) {
uint32_t flags = *(uint32_t*)(XIP_SSI_BASE + SSI_SR_OFFSET);
bool can_put = !!(flags & SSI_SR_TFNF_BITS);
bool can_get = !!(flags & SSI_SR_RFNE_BITS);
if (can_put && tx_remaining && rx_remaining - tx_remaining < max_in_flight) {
*(uint32_t*)(XIP_SSI_BASE + SSI_DR0_OFFSET) = *txbuf++;
--tx_remaining;
}
if (can_get && rx_remaining) {
*rxbuf++ = (uint8_t)*(uint32_t*)(XIP_SSI_BASE + SSI_DR0_OFFSET);
--rx_remaining;
}
}
flash_cs_force(1);
flash_flush_cache_func();
flash_enable_xip_via_boot2();
}
*/ */
import "C" import "C"
@@ -139,6 +202,19 @@ func enterBootloader() {
C.reset_usb_boot(0, 0) C.reset_usb_boot(0, 0)
} }
func doFlashCommand(tx []byte, rx []byte) error {
if len(tx) != len(rx) {
return errFlashInvalidWriteLength
}
C.flash_do_cmd(
(*C.uint8_t)(unsafe.Pointer(&tx[0])),
(*C.uint8_t)(unsafe.Pointer(&rx[0])),
C.ulong(len(tx)))
return nil
}
// Flash related code // Flash related code
const memoryStart = C.XIP_BASE // memory start for purpose of erase const memoryStart = C.XIP_BASE // memory start for purpose of erase
+42 -1
View File
@@ -289,7 +289,26 @@ func (spi SPI) isBusy() bool {
// tx writes buffer to SPI ignoring Rx. // tx writes buffer to SPI ignoring Rx.
func (spi SPI) tx(tx []byte) error { func (spi SPI) tx(tx []byte) error {
if len(tx) == 0 { err := spi.StartTx(tx, nil)
if err != nil {
return err
}
return spi.Wait()
}
// IsAsync returns whether the SPI supports async operation (usually DMA).
//
// It returns true on the rp2040.
func (spi SPI) IsAsync() bool {
return true
}
// Start a transfer in the background.
//
// After this, another StartTx() or Wait() must be called. The provided byte
// slices (tx and rx) may only be accessed again after Wait() was called.
func (spi SPI) StartTx(tx, rx []byte) error {
if len(tx) == 0 && len(rx) == 0 {
// We don't have to do anything. // We don't have to do anything.
// This avoids a panic in &tx[0] when len(tx) == 0. // This avoids a panic in &tx[0] when len(tx) == 0.
return nil return nil
@@ -306,6 +325,15 @@ func (spi SPI) tx(tx []byte) error {
dreq = 18 // DREQ_SPI1_TX dreq = 18 // DREQ_SPI1_TX
} }
// Wait for the previous transmission to complete.
for ch.CTRL_TRIG.Get()&rp.DMA_CH0_CTRL_TRIG_BUSY != 0 {
}
if len(rx) != 0 {
// Fallback. We don't support receiving data using DMA yet.
return spi.Tx(tx, rx)
}
// Configure the DMA peripheral as follows: // Configure the DMA peripheral as follows:
// - set read address, write address, and number of transfer units (bytes) // - set read address, write address, and number of transfer units (bytes)
// - increment read address (in memory), don't increment write address (SSPDR) // - increment read address (in memory), don't increment write address (SSPDR)
@@ -319,6 +347,19 @@ func (spi SPI) tx(tx []byte) error {
rp.DMA_CH0_CTRL_TRIG_DATA_SIZE_SIZE_BYTE<<rp.DMA_CH0_CTRL_TRIG_DATA_SIZE_Pos | rp.DMA_CH0_CTRL_TRIG_DATA_SIZE_SIZE_BYTE<<rp.DMA_CH0_CTRL_TRIG_DATA_SIZE_Pos |
dreq<<rp.DMA_CH0_CTRL_TRIG_TREQ_SEL_Pos | dreq<<rp.DMA_CH0_CTRL_TRIG_TREQ_SEL_Pos |
rp.DMA_CH0_CTRL_TRIG_EN) rp.DMA_CH0_CTRL_TRIG_EN)
return nil
}
// Wait until all active transactions (started by StartTx) have finished. The
// buffers provided in StartTx will be available after this method returns.
func (spi SPI) Wait() error {
// Pick the DMA channel reserved for this SPI peripheral.
var ch *dmaChannel
if spi.Bus == rp.SPI0 {
ch = &dmaChannels[spi0DMAChannel]
} else { // SPI1
ch = &dmaChannels[spi1DMAChannel]
}
// Wait until the transfer is complete. // Wait until the transfer is complete.
// TODO: do this more efficiently: // TODO: do this more efficiently:
+23 -1
View File
@@ -2,7 +2,12 @@
package machine package machine
import "device/stm32" import (
"device/stm32"
"runtime/volatile"
"unsafe"
)
const deviceName = stm32.Device const deviceName = stm32.Device
@@ -80,3 +85,20 @@ func (p Pin) PortMaskClear() (*uint32, uint32) {
pin := uint8(p) % 16 pin := uint8(p) % 16
return &port.BSRR.Reg, 1 << (pin + 16) return &port.BSRR.Reg, 1 << (pin + 16)
} }
var deviceID [12]byte
// DeviceID returns an identifier that is unique within
// a particular chipset.
//
// The identity is one burnt into the MCU itself.
//
// The length of the device ID for STM32 is 12 bytes (96 bits).
func DeviceID() []byte {
for i := 0; i < len(deviceID); i++ {
word := (*volatile.Register32)(unsafe.Pointer(deviceIDAddr[i/4])).Get()
deviceID[i] = byte(word >> ((i % 4) * 8))
}
return deviceID[:]
}
+6
View File
@@ -157,6 +157,12 @@ func (i2c *I2C) Configure(config I2CConfig) error {
return nil return nil
} }
// SetBaudRate sets the communication speed for I2C.
func (i2c *I2C) SetBaudRate(br uint32) error {
// TODO: implement
return errI2CNotImplemented
}
func (i2c *I2C) Tx(addr uint16, w, r []byte) error { func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
if err := i2c.controllerTransmit(addr, w); nil != err { if err := i2c.controllerTransmit(addr, w); nil != err {
+6
View File
@@ -84,6 +84,12 @@ func (i2c *I2C) Configure(config I2CConfig) error {
return nil return nil
} }
// SetBaudRate sets the communication speed for I2C.
func (i2c *I2C) SetBaudRate(br uint32) error {
// TODO: implement
return errI2CNotImplemented
}
func (i2c *I2C) Tx(addr uint16, w, r []byte) error { func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
if len(w) > 0 { if len(w) > 0 {
if err := i2c.controllerTransmit(addr, w); nil != err { if err := i2c.controllerTransmit(addr, w); nil != err {
+3 -1
View File
@@ -21,7 +21,7 @@ type SPIConfig struct {
} }
// Configure is intended to setup the STM32 SPI1 interface. // Configure is intended to setup the STM32 SPI1 interface.
func (spi SPI) Configure(config SPIConfig) { func (spi SPI) Configure(config SPIConfig) error {
// -- CONFIGURING THE SPI IN MASTER MODE -- // -- CONFIGURING THE SPI IN MASTER MODE --
// //
@@ -93,6 +93,8 @@ func (spi SPI) Configure(config SPIConfig) {
// enable SPI // enable SPI
spi.Bus.CR1.SetBits(stm32.SPI_CR1_SPE) spi.Bus.CR1.SetBits(stm32.SPI_CR1_SPE)
return nil
} }
// Transfer writes/reads a single byte using the SPI interface. // Transfer writes/reads a single byte using the SPI interface.
+2
View File
@@ -15,6 +15,8 @@ func CPUFrequency() uint32 {
return 72000000 return 72000000
} }
var deviceIDAddr = []uintptr{0x1FFFF7E8, 0x1FFFF7EC, 0x1FFFF7F0}
// Internal use: configured speed of the APB1 and APB2 timers, this should be kept // Internal use: configured speed of the APB1 and APB2 timers, this should be kept
// in sync with any changes to runtime package which configures the oscillators // in sync with any changes to runtime package which configures the oscillators
// and clock frequencies // and clock frequencies
+2
View File
@@ -14,6 +14,8 @@ import (
"unsafe" "unsafe"
) )
var deviceIDAddr = []uintptr{0x1FFF7A10, 0x1FFF7A14, 0x1FFF7A18}
const ( const (
PA0 = portA + 0 PA0 = portA + 0
PA1 = portA + 1 PA1 = portA + 1
+2
View File
@@ -11,6 +11,8 @@ import (
"unsafe" "unsafe"
) )
var deviceIDAddr = []uintptr{0x1FF0F420, 0x1FF0F424, 0x1FF0F428}
// Alternative peripheral pin functions // Alternative peripheral pin functions
const ( const (
AF0_SYSTEM = 0 AF0_SYSTEM = 0
+2
View File
@@ -13,6 +13,8 @@ func CPUFrequency() uint32 {
return 32000000 return 32000000
} }
var deviceIDAddr = []uintptr{0x1FF80050, 0x1FF80054, 0x1FF80058}
// Internal use: configured speed of the APB1 and APB2 timers, this should be kept // Internal use: configured speed of the APB1 and APB2 timers, this should be kept
// in sync with any changes to runtime package which configures the oscillators // in sync with any changes to runtime package which configures the oscillators
// and clock frequencies // and clock frequencies
+2
View File
@@ -13,6 +13,8 @@ import (
// Peripheral abstraction layer for the stm32l4 // Peripheral abstraction layer for the stm32l4
var deviceIDAddr = []uintptr{0x1FFF7590, 0x1FFF7594, 0x1FFF7598}
const ( const (
AF0_SYSTEM = 0 AF0_SYSTEM = 0
AF1_TIM1_2_LPTIM1 = 1 AF1_TIM1_2_LPTIM1 = 1

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