Compare commits

...

46 Commits

Author SHA1 Message Date
BCG 4f84dcb92f Updated README to mention compiling for Windows 2023-02-28 22:36:53 -05:00
Damian Gryski 1cce1ea423 reflect: uncomment some DeepEqual tests that now pass 2023-02-28 13:10:40 -08:00
Damian Gryski a2bb1d3805 reflect: add MapKeys() 2023-02-28 13:10:40 -08:00
Damian Gryski c4dadbaaab reflect: add MakeMap() 2023-02-28 13:10:40 -08:00
Damian Gryski 828c3169ab reflect: add SetMapIndex() 2023-02-28 13:10:40 -08:00
Damian Gryski f6ee470eda reflect: add MapRange/MapIter 2023-02-28 13:10:40 -08:00
Damian Gryski d0f4702f8b reflect: add MapIndex() 2023-02-28 13:10:40 -08:00
Damian Gryski c0a50e9b47 runtime: add unsafe.pointer reflect wrappers for hashmap calls 2023-02-28 13:10:40 -08:00
Damian Gryski 9541525402 reflect: add Type.Elem() and Type.Key() for Maps 2023-02-28 13:10:40 -08:00
Damian Gryski 60a93e8e2d compiler, reflect: add map key and element type info 2023-02-28 13:10:40 -08:00
Federico G. Schwindt cdf785629a Fail earlier if Go is not available 2023-02-28 08:25:33 +01:00
Ayke van Laethem ea183e9197 compiler: add llvm.ident metadata
This metadata is emitted by Clang and I found it is important for source
level debugging on MacOS. This patch does not get source level debugging
to work yet (for that, it seems like packages need to be built
separately), but it is a step in the right direction.
2023-02-27 23:11:22 +01:00
deadprogram 74160c0e32 runtime/atsamd51: enable CMCC cache for greatly improved performance on SAMD51
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-02-27 20:40:41 +01:00
Ron Evans 6e1b8a54aa machine/stm32, nrf: flash API (#3472)
machine/stm32, nrf: implement machine.Flash

Implements the machine.Flash interface using the same definition as the tinyfs BlockDevice.

This implementation covers the stm32f4, stm32l4, stm32wlx, nrf51, nrf52, and nrf528xx processors.
2023-02-27 13:55:38 +01:00
Ayke van Laethem 8bf94b9231 internal/task: disallow blocking inside an interrupt
Blocking inside an interrupt is always unsafe and will lead to all kinds
of bad behavior (even if it might appear to work sometimes). So disallow
it, just like allocating heap memory inside an interrupt is not allowed.

I suspect this will usually be caused by channel sends, like this:

    ch <- someValue

The easy workaround is to make it a non-blocking send instead:

    select {
    case ch <- someValue:
    default:
    }

This does mean the application might become a bit more complex to be
able to deal with this case, but the alternative (undefined behavior) is
IMHO much worse.
2023-02-26 22:42:24 +01:00
Ayke van Laethem 488174767b builder: remove non-ThinLTO build mode
All targets now support ThinLTO so let's remove the old unused code.
2023-02-26 19:22:10 +01:00
Ayke van Laethem 201592d93b ci: add AVR timers test
Add the timers test because they now work correctly on AVR, probably as
a result of the reflect refactor: https://github.com/tinygo-org/tinygo/pull/2640

I've also updated a few of the other tests to indicate the new status
and why they don't work. It's no longer because of compiler errors, but
because of linker or runtime errors (which is at least some progress).
For example, I found that testdata/reflect.go works if you disable
`testAppendSlice` and increase the stack size.
2023-02-26 17:14:04 +01:00
Damian Gryski 476621736c compiler: zero struct padding during map operations
Fixes #3358
2023-02-25 22:40:08 +01:00
Damian Gryski 7b44fcd865 runtime: properly turn pointer into empty interface when hashing 2023-02-25 06:42:30 -08:00
Bjoern Poetzschke cfe971d723 Rearrange switch case for get pin cfg 2023-02-24 08:53:51 +01:00
John Clark bad7bfc4d5 Pins D4 & D5 are I2C1. Use pins D2 & D3 for I2C0.
Signed-off-by: John Clark <inindev@gmail.com>
2023-02-24 02:20:26 +01:00
deadprogram ad1da7d26f machine/rp2040: correct issue with spi pin validation
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-02-24 01:05:58 +01:00
deadprogram 6df303ff19 machine/rp2040: correct issue with i2c pin validation
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-02-24 01:05:58 +01:00
Patricio Whittingslow 96b70fd619 rp2040: provide better errors for invalid pins on i2c and spi (#3443)
machine/rp2040: provide better errors for invalid pins on i2c and spi
2023-02-23 13:26:14 +01:00
Yurii Soldak 4d0dfbd6fd rp2040: rtc delayed interrupt 2023-02-23 09:23:37 +01:00
deadprogram 1065f06e57 machine/lorae5: add needed definition for UART2
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-02-20 14:15:31 +01:00
Ayke van Laethem ec27d9fb48 ci: don't pass -v to go test
It can be difficult to find what went wrong in a test. Omitting -v
should make it easier to see the failing tests and the output for them
(note that output is still printed for tests that fail).
2023-02-20 00:23:52 +01:00
Ayke van Laethem cce9c6d5a1 arm64: fix register save/restore to include vector registers
Some vector registers must be preserved across calls, but this wasn't
happening on Linux and MacOS. When I added support for windows/arm64, I
saw that it required these vector registers to be preserved and assumed
this was Windows deviating from the standard calling convention. But
actually, Windows was just implementing the standard calling convention
and the bug was on Linux and MacOS.

This commit fixes the bug on Linux and MacOS and at the same time merges
the Go and assembly files as they no longer need to be separate.
2023-02-19 20:48:32 +01:00
Ayke van Laethem f41b6a3b96 runtime: check for heap allocations inside interrupts
This is unsafe and should never be done.
2023-02-19 11:33:24 +01:00
deadprogram e066e67baf machine/rp2040: change calling order for device enumeration fix to do first
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-02-19 08:32:39 +01:00
Ayke van Laethem cacb452aa6 main: support qemu-user debugging
Running binaries in QEMU (when debugging on Linux for example) did not
work correctly as qemu-user expects the `-g` flag to be first on the
command line before the program name. Putting it after will make it a
command line parameter for the emulated program, which is not what we
want.

I don't think this ever worked correctly.
2023-02-19 01:35:10 +01:00
Andy Shinn 9296332151 fix bad qt py pin assignment 2023-02-19 00:39:41 +01:00
deadprogram 1125d42149 build/docker: use build context from pre-saved LLVM container to avoid rebuilding
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-02-18 19:41:11 +01:00
deadprogram 7982d26db0 docker: ignore changes to CI itself to avoid breaking cache on images
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-02-18 19:41:11 +01:00
deadprogram f6758d22d8 build/linux: install wasmtime directly from github release
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-02-18 19:41:11 +01:00
deadprogram 87cfe6a538 build/docker: trigger all builds in tinygo ecosystem repos using GH actions
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-02-18 19:41:11 +01:00
Ayke van Laethem c02cc339c5 runtime: implement KeepAlive using inline assembly 2023-02-18 10:50:26 +01:00
Anton D. Kachalov 361ecf9ea4 Better handling of sub-clusters in SVD (#3335)
gen: Better handling of sub-clusters in SVD
2023-02-18 09:10:23 +01:00
sago35 e21ab04fad machine/usb/hid: add MediaKey support (#3436)
machine/usb/hid: add MediaKey support
2023-02-18 01:27:15 +01:00
Ayke van Laethem 4e8453167f all: refactor reflect package
This is a big commit that changes the way runtime type information is stored in
the binary. Instead of compressing it and storing it in a number of sidetables,
it is stored similar to how the Go compiler toolchain stores it (but still more
compactly).

This has a number of advantages:

  * It is much easier to add new features to reflect support. They can simply
    be added to these structs without requiring massive changes (especially in
    the reflect lowering pass).
  * It removes the reflect lowering pass, which was a large amount of hard to
    understand and debug code.
  * The reflect lowering pass also required merging all LLVM IR into one
    module, which is terrible for performance especially when compiling large
    amounts of code. See issue 2870 for details.
  * It is (probably!) easier to reason about for the compiler.

The downside is that it increases code size a bit, especially when reflect is
involved. I hope to fix some of that in later patches.
2023-02-17 22:54:34 +01:00
Ayke van Laethem ebb410afd9 reflect: make sure null bytes are supported in tags 2023-02-17 22:54:34 +01:00
Ron Evans 012bdfae80 build: always cache LLVM source/build even if the tests fail to avoid extra rebuilds (#3453)
builds/macos, linux, windows: update to explicit restore/save for LLVM source and LLVM builds
2023-02-17 17:52:15 +01:00
Anuraag Agrawal f6df276118 runtime: allow custom-gc SetFinalizer and clarify KeepAlive 2023-02-17 00:51:51 +01:00
Anuraag Agrawal e0a5fc2555 Filter target build-tags if user specified an overriding option (#3357)
compileopts: Filter target build-tags if user specified an overriding option
2023-02-14 23:21:28 +01:00
sago35 cecb80b8bf goenv: update to new v0.28.0 development version 2023-02-14 19:32:02 +01:00
Damian Gryski d899895e32 Makefile: more stdlib tests for CI 2023-02-14 05:17:46 -08:00
104 changed files with 3519 additions and 1972 deletions
+2
View File
@@ -1,3 +1,5 @@
build/
llvm-*/
.github
.circleci
+24 -7
View File
@@ -29,11 +29,11 @@ jobs:
with:
go-version: '1.20'
cache: true
- name: Cache LLVM source
uses: actions/cache@v3
- name: Restore LLVM source cache
uses: actions/cache/restore@v3
id: cache-llvm-source
with:
key: llvm-source-15-macos-v2
key: llvm-source-15-macos-v3
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -43,11 +43,22 @@ jobs:
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Cache LLVM build
uses: actions/cache@v3
- 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-15-macos-v3
key: llvm-build-15-macos-v4
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -61,6 +72,12 @@ jobs:
# build!
make llvm-build
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 wasi-libc sysroot
uses: actions/cache@v3
id: cache-wasi-libc
@@ -72,7 +89,7 @@ jobs:
run: make wasi-libc
- name: Test TinyGo
shell: bash
run: make test GOTESTFLAGS="-v -short"
run: make test GOTESTFLAGS="-short"
- name: Build TinyGo release tarball
run: make release -j3
- name: Test stdlib packages
+26 -15
View File
@@ -53,6 +53,7 @@ jobs:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-contexts: tinygo-llvm-build=docker-image://tinygo/llvm-15
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Trigger Drivers repo build on Github Actions
@@ -69,21 +70,31 @@ jobs:
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/bluetooth/actions/workflows/linux.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger TinyFS repo build on CircleCI
- name: Trigger TinyFS repo build on Github Actions
run: |
curl --location --request POST 'https://circleci.com/api/v2/project/github/tinygo-org/tinyfs/pipeline' \
--header 'Content-Type: application/json' \
-d '{"branch": "dev"}' \
-u "${{ secrets.CIRCLECI_API_TOKEN }}"
- name: Trigger TinyFont repo build on CircleCI
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/tinyfs/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger TinyFont repo build on Github Actions
run: |
curl --location --request POST 'https://circleci.com/api/v2/project/github/tinygo-org/tinyfont/pipeline' \
--header 'Content-Type: application/json' \
-d '{"branch": "dev"}' \
-u "${{ secrets.CIRCLECI_API_TOKEN }}"
- name: Trigger TinyDraw repo build on CircleCI
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/tinyfont/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger TinyDraw repo build on Github Actions
run: |
curl --location --request POST 'https://circleci.com/api/v2/project/github/tinygo-org/tinydraw/pipeline' \
--header 'Content-Type: application/json' \
-d '{"branch": "dev"}' \
-u "${{ secrets.CIRCLECI_API_TOKEN }}"
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/tinydraw/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger TinyTerm repo build on Github Actions
run: |
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/tinyterm/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
+98 -26
View File
@@ -39,11 +39,11 @@ jobs:
path: |
~/.cache/go-build
~/go/pkg/mod
- name: Cache LLVM source
uses: actions/cache@v3
- name: Restore LLVM source cache
uses: actions/cache/restore@v3
id: cache-llvm-source
with:
key: llvm-source-15-linux-alpine-v2
key: llvm-source-15-linux-alpine-v3
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -53,11 +53,22 @@ jobs:
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Cache LLVM build
uses: actions/cache@v3
- 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-15-linux-alpine-v3
key: llvm-build-15-linux-alpine-v4
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -71,6 +82,12 @@ jobs:
make llvm-build
# 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
@@ -122,7 +139,9 @@ jobs:
cache: true
- name: Install wasmtime
run: |
curl https://wasmtime.dev/install.sh -sSf | bash
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
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/*
echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH
- name: Download release artifact
uses: actions/download-artifact@v3
@@ -166,13 +185,15 @@ jobs:
node-version: '14'
- name: Install wasmtime
run: |
curl https://wasmtime.dev/install.sh -sSf | bash
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
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/*
echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH
- name: Cache LLVM source
uses: actions/cache@v3
- name: Restore LLVM source cache
uses: actions/cache/restore@v3
id: cache-llvm-source
with:
key: llvm-source-15-linux-asserts-v2
key: llvm-source-15-linux-asserts-v3
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -182,11 +203,22 @@ jobs:
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Cache LLVM build
uses: actions/cache@v3
- 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-15-linux-asserts-v3
key: llvm-build-15-linux-asserts-v4
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -198,6 +230,12 @@ jobs:
make llvm-build ASSERT=1
# 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
@@ -254,11 +292,11 @@ jobs:
with:
go-version: '1.20'
cache: true
- name: Cache LLVM source
uses: actions/cache@v3
- name: Restore LLVM source cache
uses: actions/cache/restore@v3
id: cache-llvm-source
with:
key: llvm-source-15-linux-v2
key: llvm-source-15-linux-v3
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -268,11 +306,22 @@ jobs:
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Cache LLVM build
uses: actions/cache@v3
- 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-15-linux-arm-v3
key: llvm-build-15-linux-arm-v4
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -286,6 +335,12 @@ jobs:
make llvm-build CROSS=arm-linux-gnueabihf
# 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
@@ -354,11 +409,11 @@ jobs:
with:
go-version: '1.20'
cache: true
- name: Cache LLVM source
uses: actions/cache@v3
- name: Restore LLVM source cache
uses: actions/cache/restore@v3
id: cache-llvm-source
with:
key: llvm-source-15-linux-v2
key: llvm-source-15-linux-v3
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -368,11 +423,22 @@ jobs:
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Cache LLVM build
uses: actions/cache@v3
- 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-15-linux-arm64-v3
key: llvm-build-15-linux-arm64-v4
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -384,6 +450,12 @@ jobs:
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
+48 -7
View File
@@ -15,6 +15,12 @@ jobs:
build-windows:
runs-on: windows-2022
steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.3
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- uses: brechtm/setup-scoop@v2
with:
scoop_update: 'false'
@@ -31,11 +37,11 @@ jobs:
with:
go-version: '1.20'
cache: true
- name: Cache LLVM source
uses: actions/cache@v3
- name: Restore cached LLVM source
uses: actions/cache/restore@v3
id: cache-llvm-source
with:
key: llvm-source-15-windows-v2
key: llvm-source-15-windows-v4
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -45,11 +51,22 @@ jobs:
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Cache LLVM build
uses: actions/cache@v3
- name: Save cached LLVM source
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 cached LLVM build
uses: actions/cache/restore@v3
id: cache-llvm-build
with:
key: llvm-build-15-windows-v3
key: llvm-build-15-windows-v5
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -62,6 +79,12 @@ jobs:
make llvm-build CCACHE=OFF
# Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save cached LLVM build
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 wasi-libc sysroot
uses: actions/cache@v3
id: cache-wasi-libc
@@ -76,7 +99,7 @@ jobs:
scoop install wasmtime
- name: Test TinyGo
shell: bash
run: make test GOTESTFLAGS="-v -short"
run: make test GOTESTFLAGS="-short"
- name: Build TinyGo release tarball
shell: bash
run: make build/release -j4
@@ -100,6 +123,12 @@ jobs:
runs-on: windows-2022
needs: build-windows
steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.3
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- uses: brechtm/setup-scoop@v2
with:
scoop_update: 'false'
@@ -131,6 +160,12 @@ jobs:
runs-on: windows-2022
needs: build-windows
steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.3
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- name: Checkout
uses: actions/checkout@v3
- name: Install Go
@@ -154,6 +189,12 @@ jobs:
runs-on: windows-2022
needs: build-windows
steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.3
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- uses: brechtm/setup-scoop@v2
with:
scoop_update: 'false'
+12 -1
View File
@@ -30,7 +30,7 @@ GO ?= go
export GOROOT = $(shell $(GO) env GOROOT)
# Flags to pass to go test.
GOTESTFLAGS ?= -v
GOTESTFLAGS ?=
# md5sum binary
MD5SUM = md5sum
@@ -296,6 +296,7 @@ TEST_PACKAGES_FAST = \
encoding \
encoding/ascii85 \
encoding/base32 \
encoding/base64 \
encoding/csv \
encoding/hex \
go/scanner \
@@ -331,26 +332,34 @@ TEST_PACKAGES_FAST += crypto/elliptic/internal/fiat
endif
# archive/zip requires os.ReadAt, which is not yet supported on windows
# bytes requires mmap
# compress/flate appears to hang on wasi
# compress/lzw appears to hang on wasi
# crypto/hmac fails on wasi, it exits with a "slice out of range" panic
# debug/plan9obj requires os.ReadAt, which is not yet supported on windows
# image requires recover(), which is not yet supported on wasi
# io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi
# mime/quotedprintable requires syscall.Faccessat
# strconv requires recover() which is not yet supported on wasi
# text/tabwriter requries recover(), which is not yet supported on wasi
# text/template/parse requires recover(), which is not yet supported on wasi
# testing/fstest requires os.ReadDir, which is not yet supported on windows or wasi
# Additional standard library packages that pass tests on individual platforms
TEST_PACKAGES_LINUX := \
archive/zip \
bytes \
compress/flate \
compress/lzw \
crypto/hmac \
debug/dwarf \
debug/plan9obj \
image \
io/ioutil \
mime/quotedprintable \
strconv \
testing/fstest \
text/tabwriter \
text/template/parse
TEST_PACKAGES_DARWIN := $(TEST_PACKAGES_LINUX)
@@ -465,6 +474,8 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/rtcinterrupt
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick
+1 -1
View File
@@ -41,7 +41,7 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
## Supported boards/targets
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
You can compile TinyGo programs for microcontrollers, WebAssembly, Windows, and Linux.
The following 94 microcontroller boards are currently supported:
+36 -56
View File
@@ -169,6 +169,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
SizeLevel: sizeLevel,
TinyGoVersion: goenv.Version,
Scheduler: config.Scheduler(),
AutomaticStackSize: config.AutomaticStackSize(),
@@ -190,6 +191,9 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
lprogram, err := loader.Load(config, pkgName, config.ClangHeaders, types.Config{
Sizes: compiler.Sizes(machine),
})
if err != nil {
return BuildResult{}, err
}
result := BuildResult{
ModuleRoot: lprogram.MainPkg().Module.Dir,
MainDir: lprogram.MainPkg().Dir,
@@ -199,9 +203,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// If there is no module root, just the regular root.
result.ModuleRoot = lprogram.MainPkg().Root
}
if err != nil { // failed to load AST
return result, err
}
err = lprogram.Parse()
if err != nil {
return result, err
@@ -305,7 +306,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
actionID := packageAction{
ImportPath: pkg.ImportPath,
CompilerBuildID: string(compilerBuildID),
TinyGoVersion: goenv.Version,
LLVMVersion: llvm.Version,
Config: compilerConfig,
CFlags: pkg.CFlags,
@@ -594,12 +594,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
defer llvmBuf.Dispose()
return result, os.WriteFile(outpath, llvmBuf.Bytes(), 0666)
case ".bc":
var buf llvm.MemoryBuffer
if config.UseThinLTO() {
buf = llvm.WriteThinLTOBitcodeToMemoryBuffer(mod)
} else {
buf = llvm.WriteBitcodeToMemoryBuffer(mod)
}
buf := llvm.WriteThinLTOBitcodeToMemoryBuffer(mod)
defer buf.Dispose()
return result, os.WriteFile(outpath, buf.Bytes(), 0666)
case ".ll":
@@ -621,16 +616,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
dependencies: []*compileJob{programJob},
result: objfile,
run: func(*compileJob) error {
var llvmBuf llvm.MemoryBuffer
if config.UseThinLTO() {
llvmBuf = llvm.WriteThinLTOBitcodeToMemoryBuffer(mod)
} else {
var err error
llvmBuf, err = machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
}
}
llvmBuf := llvm.WriteThinLTOBitcodeToMemoryBuffer(mod)
defer llvmBuf.Dispose()
return os.WriteFile(objfile, llvmBuf.Bytes(), 0666)
},
@@ -664,7 +650,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
job := &compileJob{
description: "compile extra file " + path,
run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, tmpdir, config.CFlags(), config.UseThinLTO(), config.Options.PrintCommands)
result, err := compileAndCacheCFile(abspath, tmpdir, config.CFlags(), config.Options.PrintCommands)
job.result = result
return err
},
@@ -682,7 +668,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
job := &compileJob{
description: "compile CGo file " + abspath,
run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, tmpdir, pkg.CFlags, config.UseThinLTO(), config.Options.PrintCommands)
result, err := compileAndCacheCFile(abspath, tmpdir, pkg.CFlags, config.Options.PrintCommands)
job.result = result
return err
},
@@ -741,36 +727,34 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
}
ldflags = append(ldflags, dependency.result)
}
if config.UseThinLTO() {
ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU())
if config.GOOS() == "windows" {
// Options for the MinGW wrapper for the lld COFF linker.
ldflags = append(ldflags,
"-Xlink=/opt:lldlto="+strconv.Itoa(optLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
} else if config.GOOS() == "darwin" {
// Options for the ld64-compatible lld linker.
ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(optLevel),
"-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
} else {
// Options for the ELF linker.
ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(optLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"),
)
}
if config.CodeModel() != "default" {
ldflags = append(ldflags,
"-mllvm", "-code-model="+config.CodeModel())
}
if sizeLevel >= 2 {
// Workaround with roughly the same effect as
// https://reviews.llvm.org/D119342.
// Can hopefully be removed in LLVM 15.
ldflags = append(ldflags,
"-mllvm", "--rotation-max-header-size=0")
}
ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU())
if config.GOOS() == "windows" {
// Options for the MinGW wrapper for the lld COFF linker.
ldflags = append(ldflags,
"-Xlink=/opt:lldlto="+strconv.Itoa(optLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
} else if config.GOOS() == "darwin" {
// Options for the ld64-compatible lld linker.
ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(optLevel),
"-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
} else {
// Options for the ELF linker.
ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(optLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"),
)
}
if config.CodeModel() != "default" {
ldflags = append(ldflags,
"-mllvm", "-code-model="+config.CodeModel())
}
if sizeLevel >= 2 {
// Workaround with roughly the same effect as
// https://reviews.llvm.org/D119342.
// Can hopefully be removed in LLVM 15.
ldflags = append(ldflags,
"-mllvm", "--rotation-max-header-size=0")
}
if config.Options.PrintCommands != nil {
config.Options.PrintCommands(config.Target.Linker, ldflags...)
@@ -1069,10 +1053,6 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
}
}
if config.GOOS() != "darwin" && !config.UseThinLTO() {
transform.ApplyFunctionSections(mod) // -ffunction-sections
}
// Insert values from -ldflags="-X ..." into the IR.
err = setGlobalValues(mod, config.Options.GlobalValues)
if err != nil {
+8 -16
View File
@@ -56,7 +56,7 @@ import (
// depfile but without invalidating its name. For this reason, the depfile is
// written on each new compilation (even when it seems unnecessary). However, it
// could in rare cases lead to a stale file fetched from the cache.
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, thinlto bool, printCommands func(string, ...string)) (string, error) {
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands func(string, ...string)) (string, error) {
// Hash input file.
fileHash, err := hashFile(abspath)
if err != nil {
@@ -67,11 +67,6 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, thinlto bool,
unlock := lock(filepath.Join(goenv.Get("GOCACHE"), fileHash+".c.lock"))
defer unlock()
ext := ".o"
if thinlto {
ext = ".bc"
}
// Create cache key for the dependencies file.
buf, err := json.Marshal(struct {
Path string
@@ -104,7 +99,7 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, thinlto bool,
}
// Obtain hashes of all the files listed as a dependency.
outpath, err := makeCFileCachePath(dependencies, depfileNameHash, ext)
outpath, err := makeCFileCachePath(dependencies, depfileNameHash)
if err == nil {
if _, err := os.Stat(outpath); err == nil {
return outpath, nil
@@ -117,7 +112,7 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, thinlto bool,
return "", err
}
objTmpFile, err := os.CreateTemp(goenv.Get("GOCACHE"), "tmp-*"+ext)
objTmpFile, err := os.CreateTemp(goenv.Get("GOCACHE"), "tmp-*.bc")
if err != nil {
return "", err
}
@@ -127,11 +122,8 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, thinlto bool,
return "", err
}
depTmpFile.Close()
flags := append([]string{}, cflags...) // copy cflags
flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name()) // autogenerate dependencies
if thinlto {
flags = append(flags, "-flto=thin")
}
flags := append([]string{}, cflags...) // copy cflags
flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name(), "-flto=thin") // autogenerate dependencies
flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath)
if strings.ToLower(filepath.Ext(abspath)) == ".s" {
// If this is an assembly file (.s or .S, lowercase or uppercase), then
@@ -189,7 +181,7 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, thinlto bool,
}
// Move temporary object file to final location.
outpath, err := makeCFileCachePath(dependencySlice, depfileNameHash, ext)
outpath, err := makeCFileCachePath(dependencySlice, depfileNameHash)
if err != nil {
return "", err
}
@@ -204,7 +196,7 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, thinlto bool,
// Create a cache path (a path in GOCACHE) to store the output of a compiler
// job. This path is based on the dep file name (which is a hash of metadata
// including compiler flags) and the hash of all input files in the paths slice.
func makeCFileCachePath(paths []string, depfileNameHash, ext string) (string, error) {
func makeCFileCachePath(paths []string, depfileNameHash string) (string, error) {
// Hash all input files.
fileHashes := make(map[string]string, len(paths))
for _, path := range paths {
@@ -229,7 +221,7 @@ func makeCFileCachePath(paths []string, depfileNameHash, ext string) (string, er
outFileNameBuf := sha512.Sum512_224(buf)
cacheKey := hex.EncodeToString(outFileNameBuf[:])
outpath := filepath.Join(goenv.Get("GOCACHE"), "obj-"+cacheKey+ext)
outpath := filepath.Join(goenv.Get("GOCACHE"), "obj-"+cacheKey+".bc")
return outpath, nil
}
+3 -8
View File
@@ -118,10 +118,6 @@ var (
// pack: data created when storing a constant in an interface for example
// string: buffer behind strings
packageSymbolRegexp = regexp.MustCompile(`\$(alloc|embedfsfiles|embedfsslice|embedslice|pack|string)(\.[0-9]+)?$`)
// Reflect sidetables. Created by the reflect lowering pass.
// See src/reflect/sidetables.go.
reflectDataRegexp = regexp.MustCompile(`^reflect\.[a-zA-Z]+Sidetable$`)
)
// readProgramSizeFromDWARF reads the source location for each line of code and
@@ -375,7 +371,7 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
if section.Flags&elf.SHF_ALLOC == 0 {
continue
}
if packageSymbolRegexp.MatchString(symbol.Name) || reflectDataRegexp.MatchString(symbol.Name) {
if packageSymbolRegexp.MatchString(symbol.Name) {
addresses = append(addresses, addressLine{
Address: symbol.Value,
Length: symbol.Size,
@@ -836,9 +832,8 @@ func findPackagePath(path string, packagePathMap map[string]string) string {
} else if packageSymbolRegexp.MatchString(path) {
// Parse symbol names like main$alloc or runtime$string.
packagePath = path[:strings.LastIndex(path, "$")]
} else if reflectDataRegexp.MatchString(path) {
// Parse symbol names like reflect.structTypesSidetable.
packagePath = "Go reflect data"
} else if path == "<Go type>" {
packagePath = "Go types"
} else if path == "<Go interface assert>" {
// Interface type assert, generated by the interface lowering pass.
packagePath = "Go interface assert"
+26 -9
View File
@@ -75,7 +75,8 @@ func (c *Config) GOARM() string {
// BuildTags returns the complete list of build tags used during this build.
func (c *Config) BuildTags() []string {
tags := append(c.Target.BuildTags, []string{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...)
targetTags := filterTags(c.Target.BuildTags, c.Options.Tags)
tags := append(targetTags, []string{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...)
for i := 1; i <= c.GoMinorVersion; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i))
}
@@ -190,14 +191,6 @@ func (c *Config) StackSize() uint64 {
return c.Target.DefaultStackSize
}
// UseThinLTO returns whether ThinLTO should be used for the given target.
func (c *Config) UseThinLTO() bool {
// All architectures support ThinLTO now. However, this code is kept for the
// time being in case there are regressions. The non-ThinLTO code support
// should be removed when it is proven to work reliably.
return true
}
// RP2040BootPatch returns whether the RP2040 boot patch should be applied that
// calculates and patches in the checksum for the 2nd stage bootloader.
func (c *Config) RP2040BootPatch() bool {
@@ -550,3 +543,27 @@ type TestConfig struct {
CompileTestBinary bool
// TODO: Filter the test functions to run, include verbose flag, etc
}
// filterTags removes predefined build tags for a target if a conflicting option
// is provided by the user.
func filterTags(targetTags []string, userTags []string) []string {
var filtered []string
for _, t := range targetTags {
switch {
case strings.HasPrefix(t, "runtime_memhash_"):
overridden := false
for _, ut := range userTags {
if strings.HasPrefix(ut, "runtime_memhash_") {
overridden = true
break
}
}
if !overridden {
filtered = append(filtered, t)
}
default:
filtered = append(filtered, t)
}
}
return filtered
}
+132
View File
@@ -0,0 +1,132 @@
package compileopts
import (
"fmt"
"strings"
"testing"
)
func TestBuildTags(t *testing.T) {
tests := []struct {
targetTags []string
userTags []string
result []string
}{
{
targetTags: []string{},
userTags: []string{},
result: []string{
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
},
},
{
targetTags: []string{"bear"},
userTags: []string{},
result: []string{
"bear",
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
},
},
{
targetTags: []string{},
userTags: []string{"cat"},
result: []string{
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
"cat",
},
},
{
targetTags: []string{"bear"},
userTags: []string{"cat"},
result: []string{
"bear",
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
"cat",
},
},
{
targetTags: []string{"bear", "runtime_memhash_leveldb"},
userTags: []string{"cat"},
result: []string{
"bear",
"runtime_memhash_leveldb",
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
"cat",
},
},
{
targetTags: []string{"bear", "runtime_memhash_leveldb"},
userTags: []string{"cat", "runtime_memhash_leveldb"},
result: []string{
"bear",
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
"cat",
"runtime_memhash_leveldb",
},
},
{
targetTags: []string{"bear", "runtime_memhash_leveldb"},
userTags: []string{"cat", "runtime_memhash_sip"},
result: []string{
"bear",
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
"cat",
"runtime_memhash_sip",
},
},
}
for _, tc := range tests {
tt := tc
t.Run(fmt.Sprintf("%s+%s", strings.Join(tt.targetTags, ","), strings.Join(tt.userTags, ",")), func(t *testing.T) {
c := &Config{
Target: &TargetSpec{
BuildTags: tt.targetTags,
},
Options: &Options{
Tags: tt.userTags,
},
}
res := c.BuildTags()
if len(res) != len(tt.result) {
t.Errorf("expected %d tags, got %d", len(tt.result), len(res))
}
for i, tag := range tt.result {
if tag != res[i] {
t.Errorf("tag %d: expected %s, got %s", i, tt.result[i], tag)
}
}
})
}
}
+3 -3
View File
@@ -326,9 +326,9 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
}
if goarch != "wasm" {
suffix := ""
if goos == "windows" {
// Windows uses a different calling convention from other operating
// systems so we need separate assembly files.
if goos == "windows" && goarch == "amd64" {
// Windows uses a different calling convention on amd64 from other
// operating systems so we need separate assembly files.
suffix = "_windows"
}
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_"+goarch+suffix+".S")
+14 -2
View File
@@ -47,6 +47,7 @@ type Config struct {
CodeModel string
RelocationModel string
SizeLevel int
TinyGoVersion string // for llvm.ident
// Various compiler options that determine how code is generated.
Scheduler string
@@ -321,6 +322,14 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
llvm.ConstInt(c.ctx.Int32Type(), 4, false).ConstantAsMetadata(),
}),
)
if c.TinyGoVersion != "" {
// It is necessary to set llvm.ident, otherwise debugging on MacOS
// won't work.
c.mod.AddNamedMetadataOperand("llvm.ident",
c.ctx.MDNode(([]llvm.Metadata{
c.ctx.MDString("TinyGo version " + c.TinyGoVersion),
})))
}
c.dibuilder.Finalize()
c.dibuilder.Destroy()
}
@@ -340,12 +349,15 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
return c.mod, c.diagnostics
}
func (c *compilerContext) getRuntimeType(name string) types.Type {
return c.runtimePkg.Scope().Lookup(name).(*types.TypeName).Type()
}
// getLLVMRuntimeType obtains a named type from the runtime package and returns
// it as a LLVM type, creating it if necessary. It is a shorthand for
// getLLVMType(getRuntimeType(name)).
func (c *compilerContext) getLLVMRuntimeType(name string) llvm.Type {
typ := c.runtimePkg.Scope().Lookup(name).(*types.TypeName).Type()
return c.getLLVMType(typ)
return c.getLLVMType(c.getRuntimeType(name))
}
// getLLVMType returns a LLVM type for a Go type. It doesn't recreate already
+1
View File
@@ -49,6 +49,7 @@ func TestCompiler(t *testing.T) {
{"goroutine.go", "cortex-m-qemu", "tasks"},
{"channel.go", "", ""},
{"gc.go", "", ""},
{"zeromap.go", "", ""},
}
if goMinor >= 20 {
tests = append(tests, testCase{"go1.20.go", "", ""})
+2 -2
View File
@@ -271,7 +271,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = []llvm.Value{callback, next, typecode, receiverValue}
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
valueTypes = append(valueTypes, b.i8ptrType, b.i8ptrType)
for _, arg := range instr.Call.Args {
val := b.getValue(arg)
values = append(values, val)
@@ -476,7 +476,7 @@ func (b *builder) createRunDefers() {
valueTypes = append(valueTypes, b.getFuncType(callback.Signature()))
} else {
//Expect typecode
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
valueTypes = append(valueTypes, b.i8ptrType, b.i8ptrType)
}
for _, arg := range callback.Args {
+292 -162
View File
@@ -6,6 +6,7 @@ package compiler
// interface-lowering.go for more details.
import (
"fmt"
"go/token"
"go/types"
"strconv"
@@ -15,6 +16,49 @@ import (
"tinygo.org/x/go-llvm"
)
// Type kinds for basic types.
// They must match the constants for the Kind type in src/reflect/type.go.
var basicTypes = [...]uint8{
types.Bool: 1,
types.Int: 2,
types.Int8: 3,
types.Int16: 4,
types.Int32: 5,
types.Int64: 6,
types.Uint: 7,
types.Uint8: 8,
types.Uint16: 9,
types.Uint32: 10,
types.Uint64: 11,
types.Uintptr: 12,
types.Float32: 13,
types.Float64: 14,
types.Complex64: 15,
types.Complex128: 16,
types.String: 17,
types.UnsafePointer: 18,
}
// These must also match the constants for the Kind type in src/reflect/type.go.
const (
typeKindChan = 19
typeKindInterface = 20
typeKindPointer = 21
typeKindSlice = 22
typeKindArray = 23
typeKindSignature = 24
typeKindMap = 25
typeKindStruct = 26
)
// Flags stored in the first byte of the struct field byte array. Must be kept
// up to date with src/reflect/type.go.
const (
structFieldFlagAnonymous = 1 << iota
structFieldFlagHasTag
structFieldFlagIsExported
)
// createMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction.
// It tries to put the type in the interface value, but if that's not possible,
// it will do an allocation of the right size and put that in the interface
@@ -23,10 +67,9 @@ import (
// An interface value is a {typecode, value} tuple named runtime._interface.
func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
itfValue := b.emitPointerPack([]llvm.Value{val})
itfTypeCodeGlobal := b.getTypeCode(typ)
itfTypeCode := b.CreatePtrToInt(itfTypeCodeGlobal, b.uintptrType, "")
itfType := b.getTypeCode(typ)
itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
itf = b.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = b.CreateInsertValue(itf, itfType, 0, "")
itf = b.CreateInsertValue(itf, itfValue, 1, "")
return itf
}
@@ -41,118 +84,240 @@ func (b *builder) extractValueFromInterface(itf llvm.Value, llvmType llvm.Type)
}
// getTypeCode returns a reference to a type code.
// It returns a pointer to an external global which should be replaced with the
// real type in the interface lowering pass.
// A type code is a pointer to a constant global that describes the type.
// This function returns a pointer to the 'kind' field (which might not be the
// first field in the struct).
func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
ms := c.program.MethodSets.MethodSet(typ)
hasMethodSet := ms.Len() != 0
if _, ok := typ.Underlying().(*types.Interface); ok {
hasMethodSet = false
}
globalName := "reflect/types.type:" + getTypeCodeName(typ)
global := c.mod.NamedGlobal(globalName)
if global.IsNil() {
// Create a new typecode global.
global = llvm.AddGlobal(c.mod, c.getLLVMRuntimeType("typecodeID"), globalName)
// Some type classes contain more information for underlying types or
// element types. Store it directly in the typecode global to make
// reflect lowering simpler.
var references llvm.Value
var length int64
var methodSet llvm.Value
var ptrTo llvm.Value
var typeAssert llvm.Value
var typeFields []llvm.Value
// Define the type fields. These must match the structs in
// src/reflect/type.go (ptrType, arrayType, etc). See the comment at the
// top of src/reflect/type.go for more information on the layout of these structs.
typeFieldTypes := []*types.Var{
types.NewVar(token.NoPos, nil, "kind", types.Typ[types.Int8]),
}
switch typ := typ.(type) {
case *types.Basic:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
)
case *types.Named:
references = c.getTypeCode(typ.Underlying())
case *types.Chan:
references = c.getTypeCode(typ.Elem())
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "underlying", types.Typ[types.UnsafePointer]),
)
case *types.Chan, *types.Slice:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]),
)
case *types.Pointer:
references = c.getTypeCode(typ.Elem())
case *types.Slice:
references = c.getTypeCode(typ.Elem())
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]),
)
case *types.Array:
references = c.getTypeCode(typ.Elem())
length = typ.Len()
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]),
)
case *types.Map:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "keyType", types.Typ[types.UnsafePointer]),
)
case *types.Struct:
// Take a pointer to the typecodeID of the first field (if it exists).
structGlobal := c.makeStructTypeFields(typ)
references = llvm.ConstBitCast(structGlobal, global.Type())
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "numFields", types.Typ[types.Uint16]),
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "fields", types.NewArray(c.getRuntimeType("structField"), int64(typ.NumFields()))),
)
case *types.Interface:
methodSetGlobal := c.getInterfaceMethodSet(typ)
references = llvm.ConstBitCast(methodSetGlobal, global.Type())
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
)
// TODO: methods
case *types.Signature:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
)
// TODO: signature params and return values
}
if _, ok := typ.Underlying().(*types.Interface); !ok {
methodSet = c.getTypeMethodSet(typ)
} else {
typeAssert = c.getInterfaceImplementsFunc(typ)
typeAssert = llvm.ConstPtrToInt(typeAssert, c.uintptrType)
if hasMethodSet {
// This method set is appended at the start of the struct. It is
// removed in the interface lowering pass.
// TODO: don't remove these and instead do what upstream Go is doing
// instead. See: https://research.swtch.com/interfaces. This can
// likely be optimized in LLVM using
// https://llvm.org/docs/TypeMetadata.html.
typeFieldTypes = append([]*types.Var{
types.NewVar(token.NoPos, nil, "methodSet", types.Typ[types.UnsafePointer]),
}, typeFieldTypes...)
}
if _, ok := typ.Underlying().(*types.Pointer); !ok {
ptrTo = c.getTypeCode(types.NewPointer(typ))
globalType := types.NewStruct(typeFieldTypes, nil)
global = llvm.AddGlobal(c.mod, c.getLLVMType(globalType), globalName)
metabyte := getTypeKind(typ)
switch typ := typ.(type) {
case *types.Basic:
typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))}
case *types.Named:
typeFields = []llvm.Value{
c.getTypeCode(types.NewPointer(typ)), // ptrTo
c.getTypeCode(typ.Underlying()), // underlying
}
metabyte |= 1 << 5 // "named" flag
case *types.Chan:
typeFields = []llvm.Value{
c.getTypeCode(types.NewPointer(typ)), // ptrTo
c.getTypeCode(typ.Elem()), // elementType
}
case *types.Slice:
typeFields = []llvm.Value{
c.getTypeCode(types.NewPointer(typ)), // ptrTo
c.getTypeCode(typ.Elem()), // elementType
}
case *types.Pointer:
typeFields = []llvm.Value{c.getTypeCode(typ.Elem())}
case *types.Array:
typeFields = []llvm.Value{
c.getTypeCode(types.NewPointer(typ)), // ptrTo
c.getTypeCode(typ.Elem()), // elementType
llvm.ConstInt(c.uintptrType, uint64(typ.Len()), false), // length
}
case *types.Map:
typeFields = []llvm.Value{
c.getTypeCode(types.NewPointer(typ)), // ptrTo
c.getTypeCode(typ.Elem()), // elem
c.getTypeCode(typ.Key()), // key
}
case *types.Struct:
typeFields = []llvm.Value{
llvm.ConstInt(c.ctx.Int16Type(), uint64(typ.NumFields()), false), // numFields
c.getTypeCode(types.NewPointer(typ)), // ptrTo
}
structFieldType := c.getLLVMRuntimeType("structField")
var fields []llvm.Value
for i := 0; i < typ.NumFields(); i++ {
field := typ.Field(i)
var flags uint8
if field.Anonymous() {
flags |= structFieldFlagAnonymous
}
if typ.Tag(i) != "" {
flags |= structFieldFlagHasTag
}
if token.IsExported(field.Name()) {
flags |= structFieldFlagIsExported
}
data := string(flags) + field.Name() + "\x00"
if typ.Tag(i) != "" {
if len(typ.Tag(i)) > 0xff {
c.addError(field.Pos(), fmt.Sprintf("struct tag is %d bytes which is too long, max is 255", len(typ.Tag(i))))
}
data += string([]byte{byte(len(typ.Tag(i)))}) + typ.Tag(i)
}
dataInitializer := c.ctx.ConstString(data, false)
dataGlobal := llvm.AddGlobal(c.mod, dataInitializer.Type(), globalName+"."+field.Name())
dataGlobal.SetInitializer(dataInitializer)
dataGlobal.SetAlignment(1)
dataGlobal.SetUnnamedAddr(true)
dataGlobal.SetLinkage(llvm.InternalLinkage)
dataGlobal.SetGlobalConstant(true)
fieldType := c.getTypeCode(field.Type())
fields = append(fields, llvm.ConstNamedStruct(structFieldType, []llvm.Value{
fieldType,
llvm.ConstGEP(dataGlobal.GlobalValueType(), dataGlobal, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}),
}))
}
typeFields = append(typeFields, llvm.ConstArray(structFieldType, fields))
case *types.Interface:
typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))}
// TODO: methods
case *types.Signature:
typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))}
// TODO: params, return values, etc
}
globalValue := llvm.ConstNull(global.GlobalValueType())
if !references.IsNil() {
globalValue = c.builder.CreateInsertValue(globalValue, references, 0, "")
}
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
globalValue = c.builder.CreateInsertValue(globalValue, lengthValue, 1, "")
}
if !methodSet.IsNil() {
globalValue = c.builder.CreateInsertValue(globalValue, methodSet, 2, "")
}
if !ptrTo.IsNil() {
globalValue = c.builder.CreateInsertValue(globalValue, ptrTo, 3, "")
}
if !typeAssert.IsNil() {
globalValue = c.builder.CreateInsertValue(globalValue, typeAssert, 4, "")
// Prepend metadata byte.
typeFields = append([]llvm.Value{
llvm.ConstInt(c.ctx.Int8Type(), uint64(metabyte), false),
}, typeFields...)
if hasMethodSet {
typeFields = append([]llvm.Value{
llvm.ConstBitCast(c.getTypeMethodSet(typ), c.i8ptrType),
}, typeFields...)
}
alignment := c.targetData.TypeAllocSize(c.i8ptrType)
globalValue := c.ctx.ConstStruct(typeFields, false)
global.SetInitializer(globalValue)
global.SetLinkage(llvm.LinkOnceODRLinkage)
global.SetGlobalConstant(true)
}
return global
}
// makeStructTypeFields creates a new global that stores all type information
// related to this struct type, and returns the resulting global. This global is
// actually an array of all the fields in the structs.
func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
// The global is an array of runtime.structField structs.
runtimeStructField := c.getLLVMRuntimeType("structField")
structGlobalType := llvm.ArrayType(runtimeStructField, typ.NumFields())
structGlobal := llvm.AddGlobal(c.mod, structGlobalType, "reflect/types.structFields")
structGlobalValue := llvm.ConstNull(structGlobalType)
for i := 0; i < typ.NumFields(); i++ {
fieldGlobalValue := llvm.ConstNull(runtimeStructField)
fieldGlobalValue = c.builder.CreateInsertValue(fieldGlobalValue, c.getTypeCode(typ.Field(i).Type()), 0, "")
fieldNameType, fieldName := c.makeGlobalArray([]byte(typ.Field(i).Name()), "reflect/types.structFieldName", c.ctx.Int8Type())
fieldName.SetLinkage(llvm.PrivateLinkage)
fieldName.SetUnnamedAddr(true)
fieldName = llvm.ConstGEP(fieldNameType, fieldName, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
})
fieldGlobalValue = c.builder.CreateInsertValue(fieldGlobalValue, fieldName, 1, "")
if typ.Tag(i) != "" {
fieldTagType, fieldTag := c.makeGlobalArray([]byte(typ.Tag(i)), "reflect/types.structFieldTag", c.ctx.Int8Type())
fieldTag.SetLinkage(llvm.PrivateLinkage)
fieldTag.SetUnnamedAddr(true)
fieldTag = llvm.ConstGEP(fieldTagType, fieldTag, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
global.SetAlignment(int(alignment))
if c.Debug {
file := c.getDIFile("<Go type>")
diglobal := c.dibuilder.CreateGlobalVariableExpression(file, llvm.DIGlobalVariableExpression{
Name: "type " + typ.String(),
File: file,
Line: 1,
Type: c.getDIType(globalType),
LocalToUnit: false,
Expr: c.dibuilder.CreateExpression(nil),
AlignInBits: uint32(alignment * 8),
})
fieldGlobalValue = c.builder.CreateInsertValue(fieldGlobalValue, fieldTag, 2, "")
global.AddMetadata(0, diglobal)
}
if typ.Field(i).Embedded() {
fieldEmbedded := llvm.ConstInt(c.ctx.Int1Type(), 1, false)
fieldGlobalValue = c.builder.CreateInsertValue(fieldGlobalValue, fieldEmbedded, 3, "")
}
structGlobalValue = c.builder.CreateInsertValue(structGlobalValue, fieldGlobalValue, i, "")
}
structGlobal.SetInitializer(structGlobalValue)
structGlobal.SetUnnamedAddr(true)
structGlobal.SetLinkage(llvm.PrivateLinkage)
return structGlobal
offset := uint64(0)
if hasMethodSet {
// The pointer to the method set is always the first element of the
// global (if there is a method set). However, the pointer we return
// should point to the 'kind' field not the method set.
offset = 1
}
return llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), offset, false),
})
}
var basicTypes = [...]string{
// getTypeKind returns the type kind for the given type, as defined by
// reflect.Kind.
func getTypeKind(t types.Type) uint8 {
switch t := t.Underlying().(type) {
case *types.Basic:
return basicTypes[t.Kind()]
case *types.Chan:
return typeKindChan
case *types.Interface:
return typeKindInterface
case *types.Pointer:
return typeKindPointer
case *types.Slice:
return typeKindSlice
case *types.Array:
return typeKindArray
case *types.Signature:
return typeKindSignature
case *types.Map:
return typeKindMap
case *types.Struct:
return typeKindStruct
default:
panic("unknown type")
}
}
var basicTypeNames = [...]string{
types.Bool: "bool",
types.Int: "int",
types.Int8: "int8",
@@ -183,7 +348,7 @@ func getTypeCodeName(t types.Type) string {
case *types.Array:
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
case *types.Basic:
return "basic:" + basicTypes[t.Kind()]
return "basic:" + basicTypeNames[t.Kind()]
case *types.Chan:
return "chan:" + getTypeCodeName(t.Elem())
case *types.Interface:
@@ -235,75 +400,40 @@ func getTypeCodeName(t types.Type) string {
// getTypeMethodSet returns a reference (GEP) to a global method set. This
// method set should be unreferenced after the interface lowering pass.
func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
global := c.mod.NamedGlobal(typ.String() + "$methodset")
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
if !global.IsNil() {
// the method set already exists
return llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{zero, zero})
}
globalName := typ.String() + "$methodset"
global := c.mod.NamedGlobal(globalName)
if global.IsNil() {
ms := c.program.MethodSets.MethodSet(typ)
ms := c.program.MethodSets.MethodSet(typ)
if ms.Len() == 0 {
// no methods, so can leave that one out
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
}
methods := make([]llvm.Value, ms.Len())
interfaceMethodInfoType := c.getLLVMRuntimeType("interfaceMethodInfo")
for i := 0; i < ms.Len(); i++ {
method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
fn := c.program.MethodValue(method)
llvmFnType, llvmFn := c.getFunction(fn)
if llvmFn.IsNil() {
// compiler error, so panic
panic("cannot find function: " + c.getFunctionInfo(fn).linkName)
// Create method set.
var signatures, wrappers []llvm.Value
for i := 0; i < ms.Len(); i++ {
method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
signatures = append(signatures, signatureGlobal)
fn := c.program.MethodValue(method)
llvmFnType, llvmFn := c.getFunction(fn)
if llvmFn.IsNil() {
// compiler error, so panic
panic("cannot find function: " + c.getFunctionInfo(fn).linkName)
}
wrapper := c.getInterfaceInvokeWrapper(fn, llvmFnType, llvmFn)
wrappers = append(wrappers, wrapper)
}
wrapper := c.getInterfaceInvokeWrapper(fn, llvmFnType, llvmFn)
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
signatureGlobal,
llvm.ConstPtrToInt(wrapper, c.uintptrType),
})
methods[i] = methodInfo
}
arrayType := llvm.ArrayType(interfaceMethodInfoType, len(methods))
value := llvm.ConstArray(interfaceMethodInfoType, methods)
global = llvm.AddGlobal(c.mod, arrayType, typ.String()+"$methodset")
global.SetInitializer(value)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.LinkOnceODRLinkage)
return llvm.ConstGEP(arrayType, global, []llvm.Value{zero, zero})
}
// getInterfaceMethodSet returns a global variable with the method set of the
// given named interface type. This method set is used by the interface lowering
// pass.
func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
name := typ.String()
if _, ok := typ.(*types.Named); !ok {
// Anonymous interface.
name = "reflect/types.interface:" + name
// Construct global value.
globalValue := c.ctx.ConstStruct([]llvm.Value{
llvm.ConstInt(c.uintptrType, uint64(ms.Len()), false),
llvm.ConstArray(c.i8ptrType, signatures),
c.ctx.ConstStruct(wrappers, false),
}, false)
global = llvm.AddGlobal(c.mod, globalValue.Type(), globalName)
global.SetInitializer(globalValue)
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
global.SetLinkage(llvm.LinkOnceODRLinkage)
}
global := c.mod.NamedGlobal(name + "$interface")
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
if !global.IsNil() {
// method set already exist, return it
return llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{zero, zero})
}
// Every method is a *i8 reference indicating the signature of this method.
methods := make([]llvm.Value, typ.Underlying().(*types.Interface).NumMethods())
for i := range methods {
method := typ.Underlying().(*types.Interface).Method(i)
methods[i] = c.getMethodSignature(method)
}
value := llvm.ConstArray(c.i8ptrType, methods)
global = llvm.AddGlobal(c.mod, value.Type(), name+"$interface")
global.SetInitializer(value)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.LinkOnceODRLinkage)
return llvm.ConstGEP(value.Type(), global, []llvm.Value{zero, zero})
return global
}
// getMethodSignatureName returns a unique name (that can be used as the name of
@@ -443,7 +573,7 @@ func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) ll
fnName := getTypeCodeName(assertedType.Underlying()) + ".$typeassert"
llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.uintptrType}, false)
llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.i8ptrType}, false)
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
c.addStandardDeclaredAttributes(llvmFn)
methods := c.getMethodsString(assertedType.Underlying().(*types.Interface))
@@ -464,7 +594,7 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
for i := 0; i < sig.Params().Len(); i++ {
paramTuple = append(paramTuple, sig.Params().At(i))
}
paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.Uintptr]))
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))
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
c.addStandardDeclaredAttributes(llvmFn)
@@ -601,7 +731,7 @@ func typestring(t types.Type) string {
case *types.Array:
return "[" + strconv.FormatInt(t.Len(), 10) + "]" + typestring(t.Elem())
case *types.Basic:
return basicTypes[t.Kind()]
return basicTypeNames[t.Kind()]
case *types.Chan:
switch t.Dir() {
case types.SendRecv:
+25
View File
@@ -24,6 +24,8 @@ func (b *builder) defineIntrinsicFunction() {
b.createMemoryCopyImpl()
case name == "runtime.memzero":
b.createMemoryZeroImpl()
case name == "runtime.KeepAlive":
b.createKeepAliveImpl()
case strings.HasPrefix(name, "runtime/volatile.Load"):
b.createVolatileLoad()
case strings.HasPrefix(name, "runtime/volatile.Store"):
@@ -87,6 +89,29 @@ func (b *builder) createMemoryZeroImpl() {
b.CreateRetVoid()
}
// createKeepAlive creates the runtime.KeepAlive function. It is implemented
// using inline assembly.
func (b *builder) createKeepAliveImpl() {
b.createFunctionStart(true)
// Get the underlying value of the interface value.
interfaceValue := b.getValue(b.fn.Params[0])
pointerValue := b.CreateExtractValue(interfaceValue, 1, "")
// Create an equivalent of the following C code, which is basically just a
// nop but ensures the pointerValue is kept alive:
//
// __asm__ __volatile__("" : : "r"(pointerValue))
//
// It should be portable to basically everything as the "r" register type
// exists basically everywhere.
asmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType}, false)
asmFn := llvm.InlineAsm(asmType, "", "r", true, false, 0, false)
b.createCall(asmType, asmFn, []llvm.Value{pointerValue}, "")
b.CreateRetVoid()
}
var mathToLLVMMapping = map[string]string{
"math.Ceil": "llvm.ceil.f64",
"math.Exp": "llvm.exp.f64",
+78 -1
View File
@@ -89,6 +89,7 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val
// growth.
mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, mapKeyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), mapKeyAlloca)
// Fetch the value from the hashmap.
params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "")
@@ -133,6 +134,7 @@ func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value,
// key can be compared with runtime.memequal
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca)
params := []llvm.Value{m, keyPtr, valuePtr}
b.createRuntimeCall("hashmapBinarySet", params, "")
b.emitLifetimeEnd(keyPtr, keySize)
@@ -161,6 +163,7 @@ func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos tok
} else if hashmapIsBinaryKey(keyType) {
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca)
params := []llvm.Value{m, keyPtr}
b.createRuntimeCall("hashmapBinaryDelete", params, "")
b.emitLifetimeEnd(keyPtr, keySize)
@@ -240,7 +243,8 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv
}
// Returns true if this key type does not contain strings, interfaces etc., so
// can be compared with runtime.memequal.
// can be compared with runtime.memequal. Note that padding bytes are undef
// and can alter two "equal" structs being equal when compared with memequal.
func hashmapIsBinaryKey(keyType types.Type) bool {
switch keyType := keyType.(type) {
case *types.Basic:
@@ -263,3 +267,76 @@ func hashmapIsBinaryKey(keyType types.Type) bool {
return false
}
}
func (b *builder) zeroUndefBytes(llvmType llvm.Type, ptr llvm.Value) error {
// We know that hashmapIsBinaryKey is true, so we only have to handle those types that can show up there.
// To zero all undefined bytes, we iterate over all the fields in the type. For each element, compute the
// offset of that element. If it's Basic type, there are no internal padding bytes. For compound types, we recurse to ensure
// we handle nested types. Next, we determine if there are any padding bytes before the next
// element and zero those as well.
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
switch llvmType.TypeKind() {
case llvm.IntegerTypeKind:
// no padding bytes
return nil
case llvm.PointerTypeKind:
// mo padding bytes
return nil
case llvm.ArrayTypeKind:
llvmArrayType := llvmType
llvmElemType := llvmType.ElementType()
for i := 0; i < llvmArrayType.ArrayLength(); i++ {
idx := llvm.ConstInt(b.uintptrType, uint64(i), false)
elemPtr := b.CreateInBoundsGEP(llvmArrayType, ptr, []llvm.Value{zero, idx}, "")
// zero any padding bytes in this element
b.zeroUndefBytes(llvmElemType, elemPtr)
}
case llvm.StructTypeKind:
llvmStructType := llvmType
numFields := llvmStructType.StructElementTypesCount()
llvmElementTypes := llvmStructType.StructElementTypes()
for i := 0; i < numFields; i++ {
idx := llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)
elemPtr := b.CreateInBoundsGEP(llvmStructType, ptr, []llvm.Value{zero, idx}, "")
// zero any padding bytes in this field
llvmElemType := llvmElementTypes[i]
b.zeroUndefBytes(llvmElemType, elemPtr)
// zero any padding bytes before the next field, if any
offset := b.targetData.ElementOffset(llvmStructType, i)
storeSize := b.targetData.TypeStoreSize(llvmElemType)
fieldEndOffset := offset + storeSize
var nextOffset uint64
if i < numFields-1 {
nextOffset = b.targetData.ElementOffset(llvmStructType, i+1)
} else {
// Last field? Next offset is the total size of the allcoate struct.
nextOffset = b.targetData.TypeAllocSize(llvmStructType)
}
if fieldEndOffset != nextOffset {
n := llvm.ConstInt(b.uintptrType, nextOffset-fieldEndOffset, false)
llvmStoreSize := llvm.ConstInt(b.uintptrType, storeSize, false)
gepPtr := elemPtr
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}, "")
}
}
}
return nil
}
+1 -1
View File
@@ -4,7 +4,7 @@ target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "thumbv7m-unknown-unknown-eabi"
%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i1, %runtime._interface }
%runtime._interface = type { i32, ptr }
%runtime._interface = type { ptr, ptr }
%runtime._defer = type { i32, ptr }
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
+5 -5
View File
@@ -3,8 +3,7 @@ source_filename = "gc.go"
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-wasi"
%runtime.typecodeID = type { ptr, i32, ptr, ptr, i32 }
%runtime._interface = type { i32, ptr }
%runtime._interface = type { ptr, ptr }
@main.scalar1 = hidden global ptr null, align 4
@main.scalar2 = hidden global ptr null, align 4
@@ -22,8 +21,8 @@ target triple = "wasm32-unknown-wasi"
@main.slice3 = hidden global { ptr, i32, i32 } zeroinitializer, align 8
@"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00 " }
@"runtime/gc.layout:62-0001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00\00" }
@"reflect/types.type:basic:complex128" = linkonce_odr constant %runtime.typecodeID { ptr null, i32 0, ptr null, ptr @"reflect/types.type:pointer:basic:complex128", i32 0 }
@"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant %runtime.typecodeID { ptr @"reflect/types.type:basic:complex128", i32 0, ptr null, ptr null, i32 0 }
@"reflect/types.type:basic:complex128" = linkonce_odr constant { i8, ptr } { i8 16, ptr @"reflect/types.type:pointer:basic:complex128" }, align 4
@"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant { i8, ptr } { i8 21, ptr @"reflect/types.type:basic:complex128" }, align 4
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
@@ -129,7 +128,8 @@ entry:
store double %v.r, ptr %0, align 8
%.repack1 = getelementptr inbounds { double, double }, ptr %0, i32 0, i32 1
store double %v.i, ptr %.repack1, align 8
%1 = insertvalue %runtime._interface { i32 ptrtoint (ptr @"reflect/types.type:basic:complex128" to i32), ptr undef }, ptr %0, 1
%1 = insertvalue %runtime._interface { ptr @"reflect/types.type:basic:complex128", ptr undef }, ptr %0, 1
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:complex128", ptr nonnull %stackalloc, ptr undef) #2
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #2
ret %runtime._interface %1
}
+11 -11
View File
@@ -145,34 +145,34 @@ entry:
declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #0
; Function Attrs: nounwind
define hidden void @main.startInterfaceMethod(i32 %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
entry:
%0 = call ptr @runtime.alloc(i32 16, ptr null, ptr undef) #8
store ptr %itf.value, ptr %0, align 4
%1 = getelementptr inbounds { ptr, %runtime._string, i32 }, ptr %0, i32 0, i32 1
%1 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 1
store ptr @"main$string", ptr %1, align 4
%.repack1 = getelementptr inbounds { ptr, %runtime._string, i32 }, ptr %0, i32 0, i32 1, i32 1
%.repack1 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 1, i32 1
store i32 4, ptr %.repack1, align 4
%2 = getelementptr inbounds { ptr, %runtime._string, i32 }, ptr %0, i32 0, i32 2
store i32 %itf.typecode, ptr %2, align 4
%2 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 2
store ptr %itf.typecode, ptr %2, align 4
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr undef) #8
call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #8
ret void
}
declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i32, i32, ptr) #6
declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i32, ptr, ptr) #6
; Function Attrs: nounwind
define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #7 {
entry:
%1 = load ptr, ptr %0, align 4
%2 = getelementptr inbounds { ptr, ptr, i32, i32 }, ptr %0, i32 0, i32 1
%2 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 1
%3 = load ptr, ptr %2, align 4
%4 = getelementptr inbounds { ptr, ptr, i32, i32 }, ptr %0, i32 0, i32 2
%4 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 2
%5 = load i32, ptr %4, align 4
%6 = getelementptr inbounds { ptr, ptr, i32, i32 }, ptr %0, i32 0, i32 3
%7 = load i32, ptr %6, align 4
call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, i32 %7, ptr undef) #8
%6 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 3
%7 = load ptr, ptr %6, align 4
call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #8
ret void
}
+11 -11
View File
@@ -154,35 +154,35 @@ entry:
declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #0
; Function Attrs: nounwind
define hidden void @main.startInterfaceMethod(i32 %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%0 = call ptr @runtime.alloc(i32 16, ptr null, ptr undef) #8
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #8
store ptr %itf.value, ptr %0, align 4
%1 = getelementptr inbounds { ptr, %runtime._string, i32 }, ptr %0, i32 0, i32 1
%1 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 1
store ptr @"main$string", ptr %1, align 4
%.repack1 = getelementptr inbounds { ptr, %runtime._string, i32 }, ptr %0, i32 0, i32 1, i32 1
%.repack1 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 1, i32 1
store i32 4, ptr %.repack1, align 4
%2 = getelementptr inbounds { ptr, %runtime._string, i32 }, ptr %0, i32 0, i32 2
store i32 %itf.typecode, ptr %2, align 4
%2 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 2
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 16384, ptr undef) #8
ret void
}
declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i32, i32, ptr) #6
declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i32, ptr, ptr) #6
; Function Attrs: nounwind
define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #7 {
entry:
%1 = load ptr, ptr %0, align 4
%2 = getelementptr inbounds { ptr, ptr, i32, i32 }, ptr %0, i32 0, i32 1
%2 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 1
%3 = load ptr, ptr %2, align 4
%4 = getelementptr inbounds { ptr, ptr, i32, i32 }, ptr %0, i32 0, i32 2
%4 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 2
%5 = load i32, ptr %4, align 4
%6 = getelementptr inbounds { ptr, ptr, i32, i32 }, ptr %0, i32 0, i32 3
%7 = load i32, ptr %6, align 4
call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, i32 %7, ptr undef) #8
%6 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 3
%7 = load ptr, ptr %6, align 4
call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #8
call void @runtime.deadlock(ptr undef) #8
unreachable
}
+34 -35
View File
@@ -3,22 +3,17 @@ source_filename = "interface.go"
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-wasi"
%runtime.typecodeID = type { ptr, i32, ptr, ptr, i32 }
%runtime._interface = type { i32, ptr }
%runtime._interface = type { ptr, ptr }
%runtime._string = type { ptr, i32 }
@"reflect/types.type:basic:int" = linkonce_odr constant %runtime.typecodeID { ptr null, i32 0, ptr null, ptr @"reflect/types.type:pointer:basic:int", i32 0 }
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant %runtime.typecodeID { ptr @"reflect/types.type:basic:int", i32 0, ptr null, ptr null, i32 0 }
@"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { ptr @"reflect/types.type:named:error", i32 0, ptr null, ptr null, i32 0 }
@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, ptr null, ptr @"reflect/types.type:pointer:named:error", i32 ptrtoint (ptr @"interface:{Error:func:{}{basic:string}}.$typeassert" to i32) }
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { ptr @"reflect/types.interface:interface{Error() string}$interface", i32 0, ptr null, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}", i32 ptrtoint (ptr @"interface:{Error:func:{}{basic:string}}.$typeassert" to i32) }
@"reflect/methods.Error() string" = linkonce_odr constant i8 0, align 1
@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x ptr] [ptr @"reflect/methods.Error() string"]
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, ptr null, ptr null, i32 0 }
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { ptr @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, ptr null, ptr null, i32 0 }
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { ptr @"reflect/types.interface:interface{String() string}$interface", i32 0, ptr null, ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", i32 ptrtoint (ptr @"interface:{String:func:{}{basic:string}}.$typeassert" to i32) }
@"reflect/methods.String() string" = linkonce_odr constant i8 0, align 1
@"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x ptr] [ptr @"reflect/methods.String() string"]
@"reflect/types.type:basic:int" = linkonce_odr constant { i8, ptr } { i8 2, ptr @"reflect/types.type:pointer:basic:int" }, align 4
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, ptr } { i8 21, ptr @"reflect/types.type:basic:int" }, align 4
@"reflect/types.type:pointer:named:error" = linkonce_odr constant { i8, ptr } { i8 21, ptr @"reflect/types.type:named:error" }, align 4
@"reflect/types.type:named:error" = linkonce_odr constant { i8, ptr, ptr } { i8 52, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 20, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 21, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 21, ptr @"reflect/types.type:interface:{String:func:{}{basic:string}}" }, align 4
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 20, ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" }, align 4
@"reflect/types.typeid:basic:int" = external constant i8
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
@@ -35,42 +30,42 @@ entry:
define hidden %runtime._interface @main.simpleType(ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:int", ptr nonnull %stackalloc, ptr undef) #6
call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #6
ret %runtime._interface { i32 ptrtoint (ptr @"reflect/types.type:basic:int" to i32), ptr null }
ret %runtime._interface { ptr @"reflect/types.type:basic:int", ptr null }
}
; Function Attrs: nounwind
define hidden %runtime._interface @main.pointerType(ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:pointer:basic:int", ptr nonnull %stackalloc, ptr undef) #6
call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #6
ret %runtime._interface { i32 ptrtoint (ptr @"reflect/types.type:pointer:basic:int" to i32), ptr null }
ret %runtime._interface { ptr @"reflect/types.type:pointer:basic:int", ptr null }
}
; Function Attrs: nounwind
define hidden %runtime._interface @main.interfaceType(ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:pointer:named:error", ptr nonnull %stackalloc, ptr undef) #6
call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #6
ret %runtime._interface { i32 ptrtoint (ptr @"reflect/types.type:pointer:named:error" to i32), ptr null }
ret %runtime._interface { ptr @"reflect/types.type:pointer:named:error", ptr null }
}
declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(i32) #2
; Function Attrs: nounwind
define hidden %runtime._interface @main.anonymousInterfaceType(ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", ptr nonnull %stackalloc, ptr undef) #6
call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #6
ret %runtime._interface { i32 ptrtoint (ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" to i32), ptr null }
ret %runtime._interface { ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", ptr null }
}
declare i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(i32) #3
; Function Attrs: nounwind
define hidden i1 @main.isInt(i32 %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
define hidden i1 @main.isInt(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
entry:
%typecode = call i1 @runtime.typeAssert(i32 %itf.typecode, ptr nonnull @"reflect/types.typeid:basic:int", ptr undef) #6
%typecode = call i1 @runtime.typeAssert(ptr %itf.typecode, ptr nonnull @"reflect/types.typeid:basic:int", ptr undef) #6
br i1 %typecode, label %typeassert.ok, label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
@@ -80,12 +75,12 @@ typeassert.ok: ; preds = %entry
br label %typeassert.next
}
declare i1 @runtime.typeAssert(i32, ptr dereferenceable_or_null(1), ptr) #0
declare i1 @runtime.typeAssert(ptr, ptr dereferenceable_or_null(1), ptr) #0
; Function Attrs: nounwind
define hidden i1 @main.isError(i32 %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
define hidden i1 @main.isError(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
entry:
%0 = call i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(i32 %itf.typecode) #6
%0 = call i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr %itf.typecode) #6
br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
@@ -95,10 +90,12 @@ typeassert.ok: ; preds = %entry
br label %typeassert.next
}
declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr) #2
; Function Attrs: nounwind
define hidden i1 @main.isStringer(i32 %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
define hidden i1 @main.isStringer(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
entry:
%0 = call i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(i32 %itf.typecode) #6
%0 = call i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(ptr %itf.typecode) #6
br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
@@ -108,26 +105,28 @@ typeassert.ok: ; preds = %entry
br label %typeassert.next
}
declare i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(ptr) #3
; Function Attrs: nounwind
define hidden i8 @main.callFooMethod(i32 %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
define hidden i8 @main.callFooMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
entry:
%0 = call i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(ptr %itf.value, i32 3, i32 %itf.typecode, ptr undef) #6
%0 = call i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(ptr %itf.value, i32 3, ptr %itf.typecode, ptr undef) #6
ret i8 %0
}
declare i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(ptr, i32, i32, ptr) #4
declare i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(ptr, i32, ptr, ptr) #4
; Function Attrs: nounwind
define hidden %runtime._string @main.callErrorMethod(i32 %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
define hidden %runtime._string @main.callErrorMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%0 = call %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(ptr %itf.value, i32 %itf.typecode, ptr undef) #6
%0 = call %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(ptr %itf.value, ptr %itf.typecode, ptr undef) #6
%1 = extractvalue %runtime._string %0, 0
call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #6
ret %runtime._string %0
}
declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(ptr, i32, ptr) #5
declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(ptr, ptr, ptr) #5
attributes #0 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #1 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
+37
View File
@@ -0,0 +1,37 @@
package main
type hasPadding struct {
b1 bool
i int
b2 bool
}
type nestedPadding struct {
b bool
hasPadding
i int
}
//go:noinline
func testZeroGet(m map[hasPadding]int, s hasPadding) int {
return m[s]
}
//go:noinline
func testZeroSet(m map[hasPadding]int, s hasPadding) {
m[s] = 5
}
//go:noinline
func testZeroArrayGet(m map[[2]hasPadding]int, s [2]hasPadding) int {
return m[s]
}
//go:noinline
func testZeroArraySet(m map[[2]hasPadding]int, s [2]hasPadding) {
m[s] = 5
}
func main() {
}
+170
View File
@@ -0,0 +1,170 @@
; ModuleID = 'zeromap.go'
source_filename = "zeromap.go"
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-wasi"
%main.hasPadding = type { i1, i32, i1 }
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #0
; Function Attrs: nounwind
define hidden void @main.init(ptr %context) unnamed_addr #1 {
entry:
ret void
}
; Function Attrs: noinline nounwind
define hidden i32 @main.testZeroGet(ptr dereferenceable_or_null(40) %m, i1 %s.b1, i32 %s.i, i1 %s.b2, ptr %context) unnamed_addr #2 {
entry:
%hashmap.key = alloca %main.hasPadding, align 8
%hashmap.value = alloca i32, align 4
%s = alloca %main.hasPadding, align 8
%0 = insertvalue %main.hasPadding zeroinitializer, i1 %s.b1, 0
%1 = insertvalue %main.hasPadding %0, i32 %s.i, 1
%2 = insertvalue %main.hasPadding %1, i1 %s.b2, 2
%stackalloc = alloca i8, align 1
store %main.hasPadding zeroinitializer, ptr %s, align 8
call void @runtime.trackPointer(ptr nonnull %s, ptr nonnull %stackalloc, ptr undef) #4
store %main.hasPadding %2, ptr %s, align 8
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value)
call void @llvm.lifetime.start.p0(i64 12, ptr nonnull %hashmap.key)
store %main.hasPadding %2, ptr %hashmap.key, align 8
%3 = getelementptr inbounds i8, ptr %hashmap.key, i32 1
call void @runtime.memzero(ptr nonnull %3, i32 3, ptr undef) #4
%4 = getelementptr inbounds i8, ptr %hashmap.key, i32 9
call void @runtime.memzero(ptr nonnull %4, i32 3, ptr undef) #4
%5 = call i1 @runtime.hashmapBinaryGet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, i32 4, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 12, ptr nonnull %hashmap.key)
%6 = load i32, ptr %hashmap.value, align 4
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value)
ret i32 %6
}
; Function Attrs: argmemonly nocallback nofree nosync nounwind willreturn
declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #3
declare void @runtime.memzero(ptr, i32, ptr) #0
declare i1 @runtime.hashmapBinaryGet(ptr dereferenceable_or_null(40), ptr, ptr, i32, ptr) #0
; Function Attrs: argmemonly nocallback nofree nosync nounwind willreturn
declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #3
; Function Attrs: noinline nounwind
define hidden void @main.testZeroSet(ptr dereferenceable_or_null(40) %m, i1 %s.b1, i32 %s.i, i1 %s.b2, ptr %context) unnamed_addr #2 {
entry:
%hashmap.key = alloca %main.hasPadding, align 8
%hashmap.value = alloca i32, align 4
%s = alloca %main.hasPadding, align 8
%0 = insertvalue %main.hasPadding zeroinitializer, i1 %s.b1, 0
%1 = insertvalue %main.hasPadding %0, i32 %s.i, 1
%2 = insertvalue %main.hasPadding %1, i1 %s.b2, 2
%stackalloc = alloca i8, align 1
store %main.hasPadding zeroinitializer, ptr %s, align 8
call void @runtime.trackPointer(ptr nonnull %s, ptr nonnull %stackalloc, ptr undef) #4
store %main.hasPadding %2, ptr %s, align 8
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value)
store i32 5, ptr %hashmap.value, align 4
call void @llvm.lifetime.start.p0(i64 12, ptr nonnull %hashmap.key)
store %main.hasPadding %2, ptr %hashmap.key, align 8
%3 = getelementptr inbounds i8, ptr %hashmap.key, i32 1
call void @runtime.memzero(ptr nonnull %3, i32 3, ptr undef) #4
%4 = getelementptr inbounds i8, ptr %hashmap.key, i32 9
call void @runtime.memzero(ptr nonnull %4, i32 3, ptr undef) #4
call void @runtime.hashmapBinarySet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 12, ptr nonnull %hashmap.key)
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value)
ret void
}
declare void @runtime.hashmapBinarySet(ptr dereferenceable_or_null(40), ptr, ptr, ptr) #0
; Function Attrs: noinline nounwind
define hidden i32 @main.testZeroArrayGet(ptr dereferenceable_or_null(40) %m, [2 x %main.hasPadding] %s, ptr %context) unnamed_addr #2 {
entry:
%hashmap.key = alloca [2 x %main.hasPadding], align 8
%hashmap.value = alloca i32, align 4
%s1 = alloca [2 x %main.hasPadding], align 8
%stackalloc = alloca i8, align 1
store %main.hasPadding zeroinitializer, ptr %s1, align 8
%s1.repack2 = getelementptr inbounds [2 x %main.hasPadding], ptr %s1, i32 0, i32 1
store %main.hasPadding zeroinitializer, ptr %s1.repack2, align 4
call void @runtime.trackPointer(ptr nonnull %s1, ptr nonnull %stackalloc, ptr undef) #4
%s.elt = extractvalue [2 x %main.hasPadding] %s, 0
store %main.hasPadding %s.elt, ptr %s1, align 8
%s1.repack3 = getelementptr inbounds [2 x %main.hasPadding], ptr %s1, i32 0, i32 1
%s.elt4 = extractvalue [2 x %main.hasPadding] %s, 1
store %main.hasPadding %s.elt4, ptr %s1.repack3, align 4
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value)
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key)
%s.elt7 = extractvalue [2 x %main.hasPadding] %s, 0
store %main.hasPadding %s.elt7, ptr %hashmap.key, align 8
%hashmap.key.repack8 = getelementptr inbounds [2 x %main.hasPadding], ptr %hashmap.key, i32 0, i32 1
%s.elt9 = extractvalue [2 x %main.hasPadding] %s, 1
store %main.hasPadding %s.elt9, ptr %hashmap.key.repack8, align 4
%0 = getelementptr inbounds i8, ptr %hashmap.key, i32 1
call void @runtime.memzero(ptr nonnull %0, i32 3, ptr undef) #4
%1 = getelementptr inbounds i8, ptr %hashmap.key, i32 9
call void @runtime.memzero(ptr nonnull %1, i32 3, ptr undef) #4
%2 = getelementptr inbounds i8, ptr %hashmap.key, i32 13
call void @runtime.memzero(ptr nonnull %2, i32 3, ptr undef) #4
%3 = getelementptr inbounds i8, ptr %hashmap.key, i32 21
call void @runtime.memzero(ptr nonnull %3, i32 3, ptr undef) #4
%4 = call i1 @runtime.hashmapBinaryGet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, i32 4, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %hashmap.key)
%5 = load i32, ptr %hashmap.value, align 4
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value)
ret i32 %5
}
; Function Attrs: noinline nounwind
define hidden void @main.testZeroArraySet(ptr dereferenceable_or_null(40) %m, [2 x %main.hasPadding] %s, ptr %context) unnamed_addr #2 {
entry:
%hashmap.key = alloca [2 x %main.hasPadding], align 8
%hashmap.value = alloca i32, align 4
%s1 = alloca [2 x %main.hasPadding], align 8
%stackalloc = alloca i8, align 1
store %main.hasPadding zeroinitializer, ptr %s1, align 8
%s1.repack2 = getelementptr inbounds [2 x %main.hasPadding], ptr %s1, i32 0, i32 1
store %main.hasPadding zeroinitializer, ptr %s1.repack2, align 4
call void @runtime.trackPointer(ptr nonnull %s1, ptr nonnull %stackalloc, ptr undef) #4
%s.elt = extractvalue [2 x %main.hasPadding] %s, 0
store %main.hasPadding %s.elt, ptr %s1, align 8
%s1.repack3 = getelementptr inbounds [2 x %main.hasPadding], ptr %s1, i32 0, i32 1
%s.elt4 = extractvalue [2 x %main.hasPadding] %s, 1
store %main.hasPadding %s.elt4, ptr %s1.repack3, align 4
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value)
store i32 5, ptr %hashmap.value, align 4
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key)
%s.elt7 = extractvalue [2 x %main.hasPadding] %s, 0
store %main.hasPadding %s.elt7, ptr %hashmap.key, align 8
%hashmap.key.repack8 = getelementptr inbounds [2 x %main.hasPadding], ptr %hashmap.key, i32 0, i32 1
%s.elt9 = extractvalue [2 x %main.hasPadding] %s, 1
store %main.hasPadding %s.elt9, ptr %hashmap.key.repack8, align 4
%0 = getelementptr inbounds i8, ptr %hashmap.key, i32 1
call void @runtime.memzero(ptr nonnull %0, i32 3, ptr undef) #4
%1 = getelementptr inbounds i8, ptr %hashmap.key, i32 9
call void @runtime.memzero(ptr nonnull %1, i32 3, ptr undef) #4
%2 = getelementptr inbounds i8, ptr %hashmap.key, i32 13
call void @runtime.memzero(ptr nonnull %2, i32 3, ptr undef) #4
%3 = getelementptr inbounds i8, ptr %hashmap.key, i32 21
call void @runtime.memzero(ptr nonnull %3, i32 3, ptr undef) #4
call void @runtime.hashmapBinarySet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %hashmap.key)
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value)
ret void
}
; Function Attrs: nounwind
define hidden void @main.main(ptr %context) unnamed_addr #1 {
entry:
ret void
}
attributes #0 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #1 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #2 = { noinline nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #3 = { argmemonly nocallback nofree nosync nounwind willreturn }
attributes #4 = { nounwind }
+1 -1
View File
@@ -12,7 +12,7 @@ import (
// Version of TinyGo.
// Update this value before release of new version of software.
const Version = "0.27.0"
const Version = "0.28.0-dev"
var (
// This variable is set at build time using -ldflags parameters.
+30 -56
View File
@@ -238,7 +238,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// which case this call won't even get to this point but will
// already be emitted in initAll.
continue
case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet" ||
case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet" || callFn.name == "runtime.hashmapInterfaceHash" ||
callFn.name == "os.runtime_args" || callFn.name == "internal/task.start" || callFn.name == "internal/task.Current":
// These functions should be run at runtime. Specifically:
// * Print and panic functions are best emitted directly without
@@ -378,42 +378,6 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():])
dstObj.buffer = dstBuf
mem.put(dst.index(), dstObj)
case callFn.name == "(reflect.rawType).elem":
if r.debug {
fmt.Fprintln(os.Stderr, indent+"call (reflect.rawType).elem:", operands[1:])
}
// Extract the type code global from the first parameter.
typecodeIDPtrToInt, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
typecodeID := typecodeIDPtrToInt.Operand(0)
// Get the type class.
// See also: getClassAndValueFromTypeCode in transform/reflect.go.
typecodeName := typecodeID.Name()
const prefix = "reflect/types.type:"
if !strings.HasPrefix(typecodeName, prefix) {
panic("unexpected typecode name: " + typecodeName)
}
id := typecodeName[len(prefix):]
class := id[:strings.IndexByte(id, ':')]
value := id[len(class)+1:]
if class == "named" {
// Get the underlying type.
class = value[:strings.IndexByte(value, ':')]
value = value[len(class)+1:]
}
// Elem() is only valid for certain type classes.
switch class {
case "chan", "pointer", "slice", "array":
elementType := r.builder.CreateExtractValue(typecodeID.Initializer(), 0, "")
uintptrType := r.mod.Context().IntType(int(mem.r.pointerSize) * 8)
locals[inst.localIndex] = r.getValue(llvm.ConstPtrToInt(elementType, uintptrType))
default:
return nil, mem, r.errorAt(inst, fmt.Errorf("(reflect.Type).Elem() called on %s type", class))
}
case callFn.name == "runtime.typeAssert":
// This function must be implemented manually as it is normally
// implemented by the interface lowering pass.
@@ -424,15 +388,22 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
actualTypePtrToInt, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem)
actualType, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
if !actualTypePtrToInt.IsAConstantInt().IsNil() && actualTypePtrToInt.ZExtValue() == 0 {
if !actualType.IsAConstantInt().IsNil() && actualType.ZExtValue() == 0 {
locals[inst.localIndex] = literalValue{uint8(0)}
break
}
actualType := actualTypePtrToInt.Operand(0)
// Strip pointer casts (bitcast, getelementptr).
for !actualType.IsAConstantExpr().IsNil() {
opcode := actualType.Opcode()
if opcode != llvm.GetElementPtr && opcode != llvm.BitCast {
break
}
actualType = actualType.Operand(0)
}
if strings.TrimPrefix(actualType.Name(), "reflect/types.type:") == strings.TrimPrefix(assertedType.Name(), "reflect/types.typeid:") {
locals[inst.localIndex] = literalValue{uint8(1)}
} else {
@@ -448,11 +419,12 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
methodSetPtr, err := mem.load(typecodePtr.addOffset(r.pointerSize*2), r.pointerSize).asPointer(r)
methodSetPtr, err := mem.load(typecodePtr.addOffset(-int64(r.pointerSize)), r.pointerSize).asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer()
numMethods := int(r.builder.CreateExtractValue(methodSet, 0, "").ZExtValue())
llvmFn := inst.llvmInst.CalledValue()
methodSetAttr := llvmFn.GetStringAttributeAtIndex(-1, "tinygo-methods")
methodSetString := methodSetAttr.GetStringValue()
@@ -460,9 +432,9 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// Make a set of all the methods on the concrete type, for
// easier checking in the next step.
concreteTypeMethods := map[string]struct{}{}
for i := 0; i < methodSet.Type().ArrayLength(); i++ {
methodInfo := r.builder.CreateExtractValue(methodSet, i, "")
name := r.builder.CreateExtractValue(methodInfo, 0, "").Name()
for i := 0; i < numMethods; i++ {
methodInfo := r.builder.CreateExtractValue(methodSet, 1, "")
name := r.builder.CreateExtractValue(methodInfo, i, "").Name()
concreteTypeMethods[name] = struct{}{}
}
@@ -488,15 +460,16 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
fmt.Fprintln(os.Stderr, indent+"invoke method:", operands[1:])
}
// Load the type code of the interface value.
typecodeIDBitCast, err := operands[len(operands)-2].toLLVMValue(inst.llvmInst.Operand(len(operands)-3).Type(), &mem)
// Load the type code and method set of the interface value.
typecodePtr, err := operands[len(operands)-2].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
typecodeID := typecodeIDBitCast.Operand(0).Initializer()
// Load the method set, which is part of the typecodeID object.
methodSet := stripPointerCasts(r.builder.CreateExtractValue(typecodeID, 2, "")).Initializer()
methodSetPtr, err := mem.load(typecodePtr.addOffset(-int64(r.pointerSize)), r.pointerSize).asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer()
// We don't need to load the interface method set.
@@ -508,13 +481,14 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// Iterate through all methods, looking for the one method that
// should be returned.
numMethods := methodSet.Type().ArrayLength()
numMethods := int(r.builder.CreateExtractValue(methodSet, 0, "").ZExtValue())
var method llvm.Value
for i := 0; i < numMethods; i++ {
methodSignatureAgg := r.builder.CreateExtractValue(methodSet, i, "")
methodSignature := r.builder.CreateExtractValue(methodSignatureAgg, 0, "")
methodSignatureAgg := r.builder.CreateExtractValue(methodSet, 1, "")
methodSignature := r.builder.CreateExtractValue(methodSignatureAgg, i, "")
if methodSignature == signature {
method = r.builder.CreateExtractValue(methodSignatureAgg, 1, "").Operand(0)
methodAgg := r.builder.CreateExtractValue(methodSet, 2, "")
method = r.builder.CreateExtractValue(methodAgg, i, "")
}
}
if method.IsNil() {
@@ -685,7 +659,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
}
continue
}
ptr = ptr.addOffset(uint32(offset))
ptr = ptr.addOffset(int64(offset))
locals[inst.localIndex] = ptr
if r.debug {
fmt.Fprintln(os.Stderr, indent+"gep:", operands, "->", ptr)
@@ -784,7 +758,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
case llvm.Add:
// This likely means this is part of a
// unsafe.Pointer(uintptr(ptr) + offset) pattern.
lhsPtr = lhsPtr.addOffset(uint32(rhs.Uint()))
lhsPtr = lhsPtr.addOffset(int64(rhs.Uint()))
locals[inst.localIndex] = lhsPtr
continue
case llvm.Xor:
+2 -2
View File
@@ -501,7 +501,7 @@ func (v pointerValue) offset() uint32 {
// addOffset essentially does a GEP operation (pointer arithmetic): it adds the
// offset to the pointer. It also checks that the offset doesn't overflow the
// maximum offset size (which is 4GB).
func (v pointerValue) addOffset(offset uint32) pointerValue {
func (v pointerValue) addOffset(offset int64) pointerValue {
result := pointerValue{v.pointer + uint64(offset)}
if checks && v.index() != result.index() {
panic("interp: offset out of range")
@@ -815,7 +815,7 @@ func (v rawValue) rawLLVMValue(mem *memoryView) (llvm.Value, error) {
// as a ptrtoint, so that they can be used in certain
// optimizations.
name := elementType.StructName()
if name == "runtime.typecodeID" || name == "runtime.funcValueWithSignature" {
if name == "runtime.funcValueWithSignature" {
uintptrType := ctx.IntType(int(mem.r.pointerSize) * 8)
field = llvm.ConstPtrToInt(field, uintptrType)
}
+7 -8
View File
@@ -1,17 +1,16 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
%runtime.typecodeID = type { %runtime.typecodeID*, i64, %runtime.interfaceMethodInfo* }
%runtime.interfaceMethodInfo = type { i8*, i64 }
@main.v1 = global i1 0
@main.v2 = global i1 0
@"reflect/types.type:named:main.foo" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i64 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:named:main.foo" = private constant { i8, i8*, i8* } { i8 34, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:named:main.foo", i32 0, i32 0), i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:int", i32 0, i32 0) }, align 4
@"reflect/types.type:pointer:named:main.foo" = external constant { i8, i8* }
@"reflect/types.typeid:named:main.foo" = external constant i8
@"reflect/types.type:basic:int" = external constant %runtime.typecodeID
@"reflect/types.type:basic:int" = private constant { i8, i8* } { i8 2, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:basic:int", i32 0, i32 0) }, align 4
@"reflect/types.type:pointer:basic:int" = external constant { i8, i8* }
declare i1 @runtime.typeAssert(i64, i8*, i8*, i8*)
declare i1 @runtime.typeAssert(i8*, i8*, i8*, i8*)
define void @runtime.initAll() unnamed_addr {
entry:
@@ -22,9 +21,9 @@ entry:
define internal void @main.init() unnamed_addr {
entry:
; Test type asserts.
%typecode = call i1 @runtime.typeAssert(i64 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:main.foo" to i64), i8* @"reflect/types.typeid:named:main.foo", i8* undef, i8* null)
%typecode = call i1 @runtime.typeAssert(i8* getelementptr inbounds ({ i8, i8*, i8* }, { i8, i8*, i8* }* @"reflect/types.type:named:main.foo", i32 0, i32 0), i8* @"reflect/types.typeid:named:main.foo", i8* undef, i8* null)
store i1 %typecode, i1* @main.v1
%typecode2 = call i1 @runtime.typeAssert(i64 0, i8* @"reflect/types.typeid:named:main.foo", i8* undef, i8* null)
%typecode2 = call i1 @runtime.typeAssert(i8* null, i8* @"reflect/types.typeid:named:main.foo", i8* undef, i8* null)
store i1 %typecode2, i1* @main.v2
ret void
}
+1 -1
View File
@@ -644,7 +644,7 @@ func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Option
case "qemu-user":
port = ":1234"
// Run in an emulator.
args := append(emulator[1:], "-g", "1234")
args := append([]string{"-g", "1234"}, emulator[1:]...)
daemon = executeCommand(config.Options, emulator[0], args...)
daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr
+6 -9
View File
@@ -180,7 +180,8 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
// Skip the ones that aren't.
switch name {
case "reflect.go":
// Reflect tests do not work due to type code issues.
// Reflect tests do not run correctly, probably because of the
// limited amount of memory.
continue
case "gc.go":
@@ -188,20 +189,16 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
continue
case "json.go", "stdlib.go", "testing.go":
// Breaks interp.
// Too big for AVR. Doesn't fit in flash/RAM.
continue
case "math.go":
// Stuck somewhere, not sure what's happening.
// Needs newer picolibc version (for sqrt).
continue
case "cgo/":
// CGo does not work on AVR.
continue
case "timers.go":
// Doesn't compile:
// panic: compiler: could not store type code number inside interface type code
// CGo function pointers don't work on AVR (needs LLVM 16 and
// some compiler changes).
continue
default:
+57
View File
@@ -0,0 +1,57 @@
package main
import (
"machine"
"time"
)
var (
err error
message = "1234567887654321123456788765432112345678876543211234567887654321"
)
func main() {
time.Sleep(3 * time.Second)
// Print out general information
println("Flash data start: ", machine.FlashDataStart())
println("Flash data end: ", machine.FlashDataEnd())
println("Flash data size, bytes:", machine.Flash.Size())
println("Flash write block size:", machine.Flash.WriteBlockSize())
println("Flash erase block size:", machine.Flash.EraseBlockSize())
println()
flash := machine.OpenFlashBuffer(machine.Flash, machine.FlashDataStart())
original := make([]byte, len(message))
saved := make([]byte, len(message))
// Read flash contents on start (data shall survive power off)
print("Reading data from flash: ")
_, err = flash.Read(original)
checkError(err)
println(string(original))
// Write the message to flash
print("Writing data to flash: ")
flash.Seek(0, 0) // rewind back to beginning
_, err = flash.Write([]byte(message))
checkError(err)
println(string(message))
// Read back flash contents after write (verify data is the same as written)
print("Reading data back from flash: ")
flash.Seek(0, 0) // rewind back to beginning
_, err = flash.Read(saved)
checkError(err)
println(string(saved))
println()
}
func checkError(err error) {
if err != nil {
for {
println(err.Error())
time.Sleep(time.Second)
}
}
}
+35
View File
@@ -0,0 +1,35 @@
//go:build rp2040
package main
// This example demonstrates scheduling a delayed interrupt by real time clock.
//
// An interrupt may execute user callback function or used for its side effects
// like waking up from sleep or dormant states.
//
// The interrupt can be configured to repeat.
//
// There is no separate method to disable interrupt, use 0 delay for that.
//
// Unfortunately, it is not possible to use time.Duration to work with RTC directly,
// that would introduce a circular dependency between "machine" and "time" packages.
import (
"fmt"
"machine"
"time"
)
func main() {
// Schedule and enable recurring interrupt.
// 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.
delay := time.Minute + 12*time.Second
machine.RTC.SetInterrupt(uint32(delay.Seconds()), true, func() { println("Peekaboo!") })
for {
fmt.Printf("%v\r\n", time.Now().Format(time.RFC3339))
time.Sleep(1 * time.Second)
}
}
+7 -1
View File
@@ -2,7 +2,10 @@
package task
import "unsafe"
import (
"runtime/interrupt"
"unsafe"
)
//go:linkname runtimePanic runtime.runtimePanic
func runtimePanic(str string)
@@ -45,6 +48,9 @@ func Pause() {
if *currentTask.state.canaryPtr != stackCanary {
runtimePanic("goroutine stack overflow")
}
if interrupt.In() {
runtimePanic("blocked inside interrupt")
}
currentTask.state.pause()
}
+10 -5
View File
@@ -4,7 +4,6 @@ _tinygo_startTask:
#else
.section .text.tinygo_startTask
.global tinygo_startTask
.type tinygo_startTask, %function
tinygo_startTask:
#endif
.cfi_startproc
@@ -35,7 +34,6 @@ tinygo_startTask:
#endif
.cfi_endproc
#ifndef __MACH__
.size tinygo_startTask, .-tinygo_startTask
#endif
@@ -44,7 +42,6 @@ tinygo_startTask:
_tinygo_swapTask:
#else
.global tinygo_swapTask
.type tinygo_swapTask, %function
tinygo_swapTask:
#endif
// This function gets the following parameters:
@@ -52,12 +49,16 @@ tinygo_swapTask:
// x1 = oldStack *uintptr
// Save all callee-saved registers:
stp x19, x20, [sp, #-96]!
stp x19, x20, [sp, #-160]!
stp x21, x22, [sp, #16]
stp x23, x24, [sp, #32]
stp x25, x26, [sp, #48]
stp x27, x28, [sp, #64]
stp x29, x30, [sp, #80]
stp d8, d9, [sp, #96]
stp d10, d11, [sp, #112]
stp d12, d13, [sp, #128]
stp d14, d15, [sp, #144]
// Save the current stack pointer in oldStack.
mov x8, sp
@@ -67,10 +68,14 @@ tinygo_swapTask:
mov sp, x0
// Restore stack state and return.
ldp d14, d15, [sp, #144]
ldp d12, d13, [sp, #128]
ldp d10, d11, [sp, #112]
ldp d8, d9, [sp, #96]
ldp x29, x30, [sp, #80]
ldp x27, x28, [sp, #64]
ldp x25, x26, [sp, #48]
ldp x23, x24, [sp, #32]
ldp x21, x22, [sp, #16]
ldp x19, x20, [sp], #96
ldp x19, x20, [sp], #160
ret
+10 -2
View File
@@ -1,4 +1,4 @@
//go:build scheduler.tasks && arm64 && !windows
//go:build scheduler.tasks && arm64
package task
@@ -21,8 +21,16 @@ type calleeSavedRegs struct {
x27 uintptr
x28 uintptr
x29 uintptr
pc uintptr // aka x30 aka LR
pc uintptr // aka x30 aka LR
d8 uintptr
d9 uintptr
d10 uintptr
d11 uintptr
d12 uintptr
d13 uintptr
d14 uintptr
d15 uintptr
}
// archInit runs architecture-specific setup for the goroutine startup.
@@ -1,65 +0,0 @@
.section .text.tinygo_startTask,"ax"
.global tinygo_startTask
tinygo_startTask:
.cfi_startproc
// Small assembly stub for starting a goroutine. This is already run on the
// new stack, with the callee-saved registers already loaded.
// Most importantly, x19 contains the pc of the to-be-started function and
// x20 contains the only argument it is given. Multiple arguments are packed
// into one by storing them in a new allocation.
// Indicate to the unwinder that there is nothing to unwind, this is the
// root frame. It avoids the following (bogus) error message in GDB:
// Backtrace stopped: previous frame identical to this frame (corrupt stack?)
.cfi_undefined lr
// Set the first argument of the goroutine start wrapper, which contains all
// the arguments.
mov x0, x20
// Branch to the "goroutine start" function. By using blx instead of bx,
// we'll return here instead of tail calling.
blr x19
// After return, exit this goroutine. This is a tail call.
b tinygo_pause
.cfi_endproc
.global tinygo_swapTask
tinygo_swapTask:
// This function gets the following parameters:
// x0 = newStack uintptr
// x1 = oldStack *uintptr
// Save all callee-saved registers:
stp x19, x20, [sp, #-160]!
stp x21, x22, [sp, #16]
stp x23, x24, [sp, #32]
stp x25, x26, [sp, #48]
stp x27, x28, [sp, #64]
stp x29, x30, [sp, #80]
stp d8, d9, [sp, #96]
stp d10, d11, [sp, #112]
stp d12, d13, [sp, #128]
stp d14, d15, [sp, #144]
// Save the current stack pointer in oldStack.
mov x8, sp
str x8, [x1]
// Switch to the new stack pointer.
mov sp, x0
// Restore stack state and return.
ldp d14, d15, [sp, #144]
ldp d12, d13, [sp, #128]
ldp d10, d11, [sp, #112]
ldp d8, d9, [sp, #96]
ldp x29, x30, [sp, #80]
ldp x27, x28, [sp, #64]
ldp x25, x26, [sp, #48]
ldp x23, x24, [sp, #32]
ldp x21, x22, [sp, #16]
ldp x19, x20, [sp], #160
ret
@@ -1,72 +0,0 @@
//go:build scheduler.tasks && arm64 && windows
package task
import "unsafe"
var systemStack uintptr
// calleeSavedRegs is the list of registers that must be saved and restored
// when switching between tasks. Also see task_stack_arm64_windows.S that
// relies on the exact layout of this struct.
type calleeSavedRegs struct {
x19 uintptr
x20 uintptr
x21 uintptr
x22 uintptr
x23 uintptr
x24 uintptr
x25 uintptr
x26 uintptr
x27 uintptr
x28 uintptr
x29 uintptr
pc uintptr // aka x30 aka LR
d8 uintptr
d9 uintptr
d10 uintptr
d11 uintptr
d12 uintptr
d13 uintptr
d14 uintptr
d15 uintptr
}
// archInit runs architecture-specific setup for the goroutine startup.
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
// Store the initial sp for the startTask function (implemented in assembly).
s.sp = uintptr(unsafe.Pointer(r))
// Initialize the registers.
// These will be popped off of the stack on the first resume of the goroutine.
// Start the function at tinygo_startTask (defined in src/internal/task/task_stack_arm64_windows.S).
// This assembly code calls a function (passed in x19) with a single argument
// (passed in x20). After the function returns, it calls Pause().
r.pc = uintptr(unsafe.Pointer(&startTask))
// Pass the function to call in x19.
// This function is a compiler-generated wrapper which loads arguments out of a struct pointer.
// See createGoroutineStartWrapper (defined in compiler/goroutine.go) for more information.
r.x19 = fn
// Pass the pointer to the arguments struct in x20.
r.x20 = uintptr(args)
}
func (s *state) resume() {
swapTask(s.sp, &systemStack)
}
func (s *state) pause() {
newStack := systemStack
systemStack = 0
swapTask(newStack, &s.sp)
}
// SystemStack returns the system stack pointer when called from a task stack.
// When called from the system stack, it returns 0.
func SystemStack() uintptr {
return systemStack
}
+13
View File
@@ -56,6 +56,18 @@ var (
}
DefaultUART = UART0
// Since we treat UART1 as zero, let's also call it by the real name
UART1 = UART0
// UART2
UART2 = &_UART2
_UART2 = UART{
Buffer: NewRingBuffer(),
Bus: stm32.USART2,
TxAltFuncSelector: AF7_USART1_2,
RxAltFuncSelector: AF7_USART1_2,
}
// I2C Busses
I2C1 = &I2C{
Bus: stm32.I2C1,
@@ -72,4 +84,5 @@ var (
func init() {
// Enable UARTs Interrupts
UART0.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART0.handleInterrupt)
UART2.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART2.handleInterrupt)
}
+1 -1
View File
@@ -29,7 +29,7 @@ const (
// Analog pins
const (
A0 = D1
A0 = D0
A1 = D1
A2 = D2
A3 = D3
+4 -4
View File
@@ -50,11 +50,11 @@ const (
// I2C pins
const (
I2C0_SDA_PIN Pin = D4
I2C0_SCL_PIN Pin = D5
I2C0_SDA_PIN Pin = D2
I2C0_SCL_PIN Pin = D3
I2C1_SDA_PIN Pin = NoPin
I2C1_SCL_PIN Pin = NoPin
I2C1_SDA_PIN Pin = D4
I2C1_SCL_PIN Pin = D5
)
// SPI pins
+144
View File
@@ -0,0 +1,144 @@
//go:build nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l4 || stm32wlx
package machine
import (
"errors"
"io"
"unsafe"
)
//go:extern __flash_data_start
var flashDataStart [0]byte
//go:extern __flash_data_end
var flashDataEnd [0]byte
// Return the start of the writable flash area, aligned on a page boundary. This
// is usually just after the program and static data.
func FlashDataStart() uintptr {
pagesize := uintptr(eraseBlockSize())
return (uintptr(unsafe.Pointer(&flashDataStart)) + pagesize - 1) &^ (pagesize - 1)
}
// Return the end of the writable flash area. Usually this is the address one
// past the end of the on-chip flash.
func FlashDataEnd() uintptr {
return uintptr(unsafe.Pointer(&flashDataEnd))
}
var (
errFlashCannotErasePage = errors.New("cannot erase flash page")
errFlashInvalidWriteLength = errors.New("write flash data must align to correct number of bits")
errFlashNotAllowedWriteData = errors.New("not allowed to write flash data")
errFlashCannotWriteData = errors.New("cannot write flash data")
errFlashCannotReadPastEOF = errors.New("cannot read beyond end of flash data")
errFlashCannotWritePastEOF = errors.New("cannot write beyond end of flash data")
)
// BlockDevice is the raw device that is meant to store flash data.
type BlockDevice interface {
// ReadAt reads the given number of bytes from the block device.
io.ReaderAt
// WriteAt writes the given number of bytes to the block device.
io.WriterAt
// Size returns the number of bytes in this block device.
Size() int64
// WriteBlockSize returns the block size in which data can be written to
// memory. It can be used by a client to optimize writes, non-aligned writes
// should always work correctly.
WriteBlockSize() int64
// EraseBlockSize returns the smallest erasable area on this particular chip
// in bytes. This is used for the block size in EraseBlocks.
// It must be a power of two, and may be as small as 1. A typical size is 4096.
EraseBlockSize() int64
// EraseBlocks erases the given number of blocks. An implementation may
// transparently coalesce ranges of blocks into larger bundles if the chip
// supports this. The start and len parameters are in block numbers, use
// EraseBlockSize to map addresses to blocks.
EraseBlocks(start, len int64) error
}
// FlashBuffer implements the ReadWriteCloser interface using the BlockDevice interface.
type FlashBuffer struct {
b BlockDevice
start uintptr
current uintptr
}
// OpenFlashBuffer opens a FlashBuffer.
func OpenFlashBuffer(b BlockDevice, address uintptr) *FlashBuffer {
return &FlashBuffer{b: b, start: address, current: address}
}
// Read data from a FlashBuffer.
func (fl *FlashBuffer) Read(p []byte) (n int, err error) {
fl.b.ReadAt(p, int64(fl.current))
fl.current += uintptr(len(p))
return len(p), nil
}
// Write data to a FlashBuffer.
func (fl *FlashBuffer) Write(p []byte) (n int, err error) {
// any new pages needed?
// NOTE probably will not work as expected if you try to write over page boundary
// of pages with different sizes.
pagesize := uintptr(fl.b.EraseBlockSize())
currentPageCount := (fl.current - fl.start + pagesize - 1) / pagesize
totalPagesNeeded := (fl.current - fl.start + uintptr(len(p)) + pagesize - 1) / pagesize
if currentPageCount == totalPagesNeeded {
// just write the data
n, err := fl.b.WriteAt(p, int64(fl.current))
if err != nil {
return 0, err
}
fl.current += uintptr(n)
return n, nil
}
// erase enough blocks to hold the data
page := fl.flashPageFromAddress(fl.start + (currentPageCount * pagesize))
fl.b.EraseBlocks(page, int64(totalPagesNeeded-currentPageCount))
// write the data
for i := 0; i < len(p); i += int(pagesize) {
var last int = i + int(pagesize)
if i+int(pagesize) > len(p) {
last = len(p)
}
_, err := fl.b.WriteAt(p[i:last], int64(fl.current))
if err != nil {
return 0, err
}
fl.current += uintptr(last - i)
}
return len(p), nil
}
// Close the FlashBuffer.
func (fl *FlashBuffer) Close() error {
return nil
}
// Seek implements io.Seeker interface, but with limitations.
// You can only seek relative to the start.
// Also, you cannot use seek before write operations, only read.
func (fl *FlashBuffer) Seek(offset int64, whence int) (int64, error) {
fl.current = fl.start + uintptr(offset)
return offset, nil
}
// calculate page number from address
func (fl *FlashBuffer) flashPageFromAddress(address uintptr) int64 {
return int64(address-memoryStart) / fl.b.EraseBlockSize()
}
+28 -28
View File
@@ -406,61 +406,61 @@ func (p Pin) getPinCfg() uint8 {
return uint8(sam.PORT.PINCFG1_0.Get()>>16) & 0xff
case 35: // PB03
return uint8(sam.PORT.PINCFG1_0.Get()>>24) & 0xff
case 37: // PB04
case 36: // PB04
return uint8(sam.PORT.PINCFG1_4.Get()>>0) & 0xff
case 38: // PB05
case 37: // PB05
return uint8(sam.PORT.PINCFG1_4.Get()>>8) & 0xff
case 39: // PB06
case 38: // PB06
return uint8(sam.PORT.PINCFG1_4.Get()>>16) & 0xff
case 40: // PB07
case 39: // PB07
return uint8(sam.PORT.PINCFG1_4.Get()>>24) & 0xff
case 41: // PB08
case 40: // PB08
return uint8(sam.PORT.PINCFG1_8.Get()>>0) & 0xff
case 42: // PB09
case 41: // PB09
return uint8(sam.PORT.PINCFG1_8.Get()>>8) & 0xff
case 43: // PB10
case 42: // PB10
return uint8(sam.PORT.PINCFG1_8.Get()>>16) & 0xff
case 44: // PB11
case 43: // PB11
return uint8(sam.PORT.PINCFG1_8.Get()>>24) & 0xff
case 45: // PB12
case 44: // PB12
return uint8(sam.PORT.PINCFG1_12.Get()>>0) & 0xff
case 46: // PB13
case 45: // PB13
return uint8(sam.PORT.PINCFG1_12.Get()>>8) & 0xff
case 47: // PB14
case 46: // PB14
return uint8(sam.PORT.PINCFG1_12.Get()>>16) & 0xff
case 48: // PB15
case 47: // PB15
return uint8(sam.PORT.PINCFG1_12.Get()>>24) & 0xff
case 49: // PB16
case 48: // PB16
return uint8(sam.PORT.PINCFG1_16.Get()>>0) & 0xff
case 50: // PB17
case 49: // PB17
return uint8(sam.PORT.PINCFG1_16.Get()>>8) & 0xff
case 51: // PB18
case 50: // PB18
return uint8(sam.PORT.PINCFG1_16.Get()>>16) & 0xff
case 52: // PB19
case 51: // PB19
return uint8(sam.PORT.PINCFG1_16.Get()>>24) & 0xff
case 53: // PB20
case 52: // PB20
return uint8(sam.PORT.PINCFG1_20.Get()>>0) & 0xff
case 54: // PB21
case 53: // PB21
return uint8(sam.PORT.PINCFG1_20.Get()>>8) & 0xff
case 55: // PB22
case 54: // PB22
return uint8(sam.PORT.PINCFG1_20.Get()>>16) & 0xff
case 56: // PB23
case 55: // PB23
return uint8(sam.PORT.PINCFG1_20.Get()>>24) & 0xff
case 57: // PB24
case 56: // PB24
return uint8(sam.PORT.PINCFG1_24.Get()>>0) & 0xff
case 58: // PB25
case 57: // PB25
return uint8(sam.PORT.PINCFG1_24.Get()>>8) & 0xff
case 59: // PB26
case 58: // PB26
return uint8(sam.PORT.PINCFG1_24.Get()>>16) & 0xff
case 60: // PB27
case 59: // PB27
return uint8(sam.PORT.PINCFG1_24.Get()>>24) & 0xff
case 61: // PB28
case 60: // PB28
return uint8(sam.PORT.PINCFG1_28.Get()>>0) & 0xff
case 62: // PB29
case 61: // PB29
return uint8(sam.PORT.PINCFG1_28.Get()>>8) & 0xff
case 63: // PB30
case 62: // PB30
return uint8(sam.PORT.PINCFG1_28.Get()>>16) & 0xff
case 64: // PB31
case 63: // PB31
return uint8(sam.PORT.PINCFG1_28.Get()>>24) & 0xff
default:
return 0
+108
View File
@@ -3,7 +3,9 @@
package machine
import (
"bytes"
"device/nrf"
"encoding/binary"
"runtime/interrupt"
"unsafe"
)
@@ -382,3 +384,109 @@ func ReadTemperature() int32 {
nrf.TEMP.EVENTS_DATARDY.Set(0)
return temp
}
const memoryStart = 0x0
// compile-time check for ensuring we fulfill BlockDevice interface
var _ BlockDevice = flashBlockDevice{}
var Flash flashBlockDevice
type flashBlockDevice struct {
}
// ReadAt reads the given number of bytes from the block device.
func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) {
if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() {
return 0, errFlashCannotReadPastEOF
}
data := unsafe.Slice((*byte)(unsafe.Pointer(FlashDataStart()+uintptr(off))), len(p))
copy(p, data)
return len(p), nil
}
// WriteAt writes the given number of bytes to the block device.
// Only double-word (64 bits) length data can be programmed. See rm0461 page 78.
// If the length of p is not long enough it will be padded with 0xFF bytes.
// This method assumes that the destination is already erased.
func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) {
if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() {
return 0, errFlashCannotWritePastEOF
}
address := FlashDataStart() + uintptr(off)
padded := f.pad(p)
waitWhileFlashBusy()
nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Wen)
defer nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Ren)
for j := 0; j < len(padded); j += int(f.WriteBlockSize()) {
// write word
*(*uint32)(unsafe.Pointer(address)) = binary.LittleEndian.Uint32(padded[j : j+int(f.WriteBlockSize())])
address += uintptr(f.WriteBlockSize())
waitWhileFlashBusy()
}
return len(padded), nil
}
// Size returns the number of bytes in this block device.
func (f flashBlockDevice) Size() int64 {
return int64(FlashDataEnd() - FlashDataStart())
}
const writeBlockSize = 4
// WriteBlockSize returns the block size in which data can be written to
// memory. It can be used by a client to optimize writes, non-aligned writes
// should always work correctly.
func (f flashBlockDevice) WriteBlockSize() int64 {
return writeBlockSize
}
// EraseBlockSize returns the smallest erasable area on this particular chip
// in bytes. This is used for the block size in EraseBlocks.
// It must be a power of two, and may be as small as 1. A typical size is 4096.
func (f flashBlockDevice) EraseBlockSize() int64 {
return eraseBlockSize()
}
// EraseBlocks erases the given number of blocks. An implementation may
// transparently coalesce ranges of blocks into larger bundles if the chip
// supports this. The start and len parameters are in block numbers, use
// EraseBlockSize to map addresses to blocks.
func (f flashBlockDevice) EraseBlocks(start, len int64) error {
address := FlashDataStart() + uintptr(start*f.EraseBlockSize())
waitWhileFlashBusy()
nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Een)
defer nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Ren)
for i := start; i < start+len; i++ {
nrf.NVMC.ERASEPAGE.Set(uint32(address))
waitWhileFlashBusy()
address += uintptr(f.EraseBlockSize())
}
return nil
}
// pad data if needed so it is long enough for correct byte alignment on writes.
func (f flashBlockDevice) pad(p []byte) []byte {
paddingNeeded := f.WriteBlockSize() - (int64(len(p)) % f.WriteBlockSize())
if paddingNeeded == 0 {
return p
}
padding := bytes.Repeat([]byte{0xff}, int(paddingNeeded))
return append(p, padding...)
}
func waitWhileFlashBusy() {
for nrf.NVMC.GetREADY() != nrf.NVMC_READY_READY_Ready {
}
}
+6
View File
@@ -6,6 +6,12 @@ import (
"device/nrf"
)
const eraseBlockSizeValue = 1024
func eraseBlockSize() int64 {
return eraseBlockSizeValue
}
// Get peripheral and pin number for this GPIO pin.
func (p Pin) getPortPin() (*nrf.GPIO_Type, uint32) {
return nrf.GPIO, uint32(p)
+6
View File
@@ -63,3 +63,9 @@ var (
PWM1 = &PWM{PWM: nrf.PWM1}
PWM2 = &PWM{PWM: nrf.PWM2}
)
const eraseBlockSizeValue = 4096
func eraseBlockSize() int64 {
return eraseBlockSizeValue
}
+6
View File
@@ -84,3 +84,9 @@ var (
PWM2 = &PWM{PWM: nrf.PWM2}
PWM3 = &PWM{PWM: nrf.PWM3}
)
const eraseBlockSizeValue = 4096
func eraseBlockSize() int64 {
return eraseBlockSizeValue
}
+6
View File
@@ -102,3 +102,9 @@ func (pdm *PDM) Read(buf []int16) (uint32, error) {
return uint32(len(buf)), nil
}
const eraseBlockSizeValue = 4096
func eraseBlockSize() int64 {
return eraseBlockSizeValue
}
+20 -1
View File
@@ -57,6 +57,8 @@ var (
ErrInvalidTgtAddr = errors.New("invalid target i2c address not in 0..0x80 or is reserved")
ErrI2CGeneric = errors.New("i2c error")
ErrRP2040I2CDisable = errors.New("i2c rp2040 peripheral timeout in disable")
errInvalidI2CSDA = errors.New("invalid I2C SDA pin")
errInvalidI2CSCL = errors.New("invalid I2C SCL pin")
)
// Tx performs a write and then a read transfer placing the result in
@@ -90,7 +92,7 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
// SCL: 3, 7, 11, 15, 19, 27
func (i2c *I2C) Configure(config I2CConfig) error {
const defaultBaud uint32 = 100_000 // 100kHz standard mode
if config.SCL == 0 {
if config.SCL == 0 && config.SDA == 0 {
// If config pins are zero valued or clock pin is invalid then we set default values.
switch i2c.Bus {
case rp.I2C0:
@@ -101,6 +103,23 @@ func (i2c *I2C) Configure(config I2CConfig) error {
config.SDA = I2C1_SDA_PIN
}
}
var okSCL, okSDA bool
switch i2c.Bus {
case rp.I2C0:
okSCL = (config.SCL+3)%4 == 0
okSDA = (config.SDA+4)%4 == 0
case rp.I2C1:
okSCL = (config.SCL+1)%4 == 0
okSDA = (config.SDA+2)%4 == 0
}
switch {
case !okSCL:
return errInvalidI2CSCL
case !okSDA:
return errInvalidI2CSDA
}
if config.Frequency == 0 {
config.Frequency = defaultBaud
}
+240
View File
@@ -0,0 +1,240 @@
//go:build rp2040
// Implementation based on code located here:
// https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_rtc/rtc.c
package machine
import (
"device/rp"
"errors"
"runtime/interrupt"
"unsafe"
)
type rtcType rp.RTC_Type
type rtcTime struct {
Year int16
Month int8
Day int8
Dotw int8
Hour int8
Min int8
Sec int8
}
var RTC = (*rtcType)(unsafe.Pointer(rp.RTC))
const (
second = 1
minute = 60 * second
hour = 60 * minute
day = 24 * hour
)
var (
rtcAlarmRepeats bool
rtcCallback func()
rtcEpoch = rtcTime{
Year: 1970, Month: 1, Day: 1, Dotw: 4, Hour: 0, Min: 0, Sec: 0,
}
)
var (
ErrRtcDelayTooSmall = errors.New("RTC interrupt deplay is too small, shall be at least 1 second")
ErrRtcDelayTooLarge = errors.New("RTC interrupt deplay is too large, shall be no more than 1 day")
)
// SetInterrupt configures delayed and optionally recurring interrupt by real time clock.
//
// Delay is specified in whole seconds, allowed range depends on platform.
// Zero delay disables previously configured interrupt, if any.
//
// RP2040 implementation allows delay to be up to 1 day, otherwise a respective error is emitted.
func (rtc *rtcType) SetInterrupt(delay uint32, repeat bool, callback func()) error {
// Verify delay range
if delay > day {
return ErrRtcDelayTooLarge
}
// De-configure delayed interrupt if delay is zero
if delay == 0 {
rtc.disableInterruptMatch()
return nil
}
// Configure delayed interrupt
rtc.setDivider()
rtcAlarmRepeats = repeat
rtcCallback = callback
err := rtc.setTime(rtcEpoch)
if err != nil {
return err
}
rtc.setAlarm(toAlarmTime(delay), callback)
return nil
}
func toAlarmTime(delay uint32) rtcTime {
result := rtcEpoch
remainder := delay + 1 // needed "+1", otherwise alarm fires one second too early
if remainder >= hour {
result.Hour = int8(remainder / hour)
remainder %= hour
}
if remainder >= minute {
result.Min = int8(remainder / minute)
remainder %= minute
}
result.Sec = int8(remainder)
return result
}
func (rtc *rtcType) setDivider() {
// Get clk_rtc freq and make sure it is running
rtcFreq := configuredFreq[clkRTC]
if rtcFreq == 0 {
panic("can not set RTC divider, clock is not running")
}
// Take rtc out of reset now that we know clk_rtc is running
resetBlock(rp.RESETS_RESET_RTC)
unresetBlockWait(rp.RESETS_RESET_RTC)
// Set up the 1 second divider.
// If rtc_freq is 400 then clkdiv_m1 should be 399
rtcFreq -= 1
// Check the freq is not too big to divide
if rtcFreq > rp.RTC_CLKDIV_M1_CLKDIV_M1_Msk {
panic("can not set RTC divider, clock frequency is too big to divide")
}
// Write divide value
rtc.CLKDIV_M1.Set(rtcFreq)
}
// setTime configures RTC with supplied time, initialises and activates it.
func (rtc *rtcType) setTime(t rtcTime) error {
// Disable RTC and wait while it is still running
rtc.CTRL.Set(0)
for rtc.isActive() {
}
rtc.SETUP_0.Set((uint32(t.Year) << rp.RTC_SETUP_0_YEAR_Pos) |
(uint32(t.Month) << rp.RTC_SETUP_0_MONTH_Pos) |
(uint32(t.Day) << rp.RTC_SETUP_0_DAY_Pos))
rtc.SETUP_1.Set((uint32(t.Dotw) << rp.RTC_SETUP_1_DOTW_Pos) |
(uint32(t.Hour) << rp.RTC_SETUP_1_HOUR_Pos) |
(uint32(t.Min) << rp.RTC_SETUP_1_MIN_Pos) |
(uint32(t.Sec) << rp.RTC_SETUP_1_SEC_Pos))
// Load setup values into RTC clock domain
rtc.CTRL.SetBits(rp.RTC_CTRL_LOAD)
// Enable RTC and wait for it to be running
rtc.CTRL.SetBits(rp.RTC_CTRL_RTC_ENABLE)
for !rtc.isActive() {
}
return nil
}
func (rtc *rtcType) isActive() bool {
return rtc.CTRL.HasBits(rp.RTC_CTRL_RTC_ACTIVE)
}
// setAlarm configures alarm in RTC and arms it.
// The callback is executed in the context of an interrupt handler,
// so regular restructions for this sort of code apply: no blocking, no memory allocation, etc.
func (rtc *rtcType) setAlarm(t rtcTime, callback func()) {
rtc.disableInterruptMatch()
// Clear all match enable bits
rtc.IRQ_SETUP_0.ClearBits(rp.RTC_IRQ_SETUP_0_YEAR_ENA | rp.RTC_IRQ_SETUP_0_MONTH_ENA | rp.RTC_IRQ_SETUP_0_DAY_ENA)
rtc.IRQ_SETUP_1.ClearBits(rp.RTC_IRQ_SETUP_1_DOTW_ENA | rp.RTC_IRQ_SETUP_1_HOUR_ENA | rp.RTC_IRQ_SETUP_1_MIN_ENA | rp.RTC_IRQ_SETUP_1_SEC_ENA)
// Only add to setup if it isn't -1 and set the match enable bits for things we care about
if t.Year >= 0 {
rtc.IRQ_SETUP_0.SetBits(uint32(t.Year) << rp.RTC_SETUP_0_YEAR_Pos)
rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_YEAR_ENA)
}
if t.Month >= 0 {
rtc.IRQ_SETUP_0.SetBits(uint32(t.Month) << rp.RTC_SETUP_0_MONTH_Pos)
rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_MONTH_ENA)
}
if t.Day >= 0 {
rtc.IRQ_SETUP_0.SetBits(uint32(t.Day) << rp.RTC_SETUP_0_DAY_Pos)
rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_DAY_ENA)
}
if t.Dotw >= 0 {
rtc.IRQ_SETUP_1.SetBits(uint32(t.Dotw) << rp.RTC_SETUP_1_DOTW_Pos)
rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_DOTW_ENA)
}
if t.Hour >= 0 {
rtc.IRQ_SETUP_1.SetBits(uint32(t.Hour) << rp.RTC_SETUP_1_HOUR_Pos)
rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_HOUR_ENA)
}
if t.Min >= 0 {
rtc.IRQ_SETUP_1.SetBits(uint32(t.Min) << rp.RTC_SETUP_1_MIN_Pos)
rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_MIN_ENA)
}
if t.Sec >= 0 {
rtc.IRQ_SETUP_1.SetBits(uint32(t.Sec) << rp.RTC_SETUP_1_SEC_Pos)
rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_SEC_ENA)
}
// Enable the IRQ at the proc
interrupt.New(rp.IRQ_RTC_IRQ, rtcHandleInterrupt).Enable()
// Enable the IRQ at the peri
rtc.INTE.Set(rp.RTC_INTE_RTC)
rtc.enableInterruptMatch()
}
func (rtc *rtcType) enableInterruptMatch() {
// Set matching and wait for it to be enabled
rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_MATCH_ENA)
for !rtc.IRQ_SETUP_0.HasBits(rp.RTC_IRQ_SETUP_0_MATCH_ACTIVE) {
}
}
func (rtc *rtcType) disableInterruptMatch() {
// Disable matching and wait for it to stop being active
rtc.IRQ_SETUP_0.ClearBits(rp.RTC_IRQ_SETUP_0_MATCH_ENA)
for rtc.IRQ_SETUP_0.HasBits(rp.RTC_IRQ_SETUP_0_MATCH_ACTIVE) {
}
}
func rtcHandleInterrupt(itr interrupt.Interrupt) {
// Always disable the alarm to clear the current IRQ.
// Even if it is a repeatable alarm, we don't want it to keep firing.
// If it matches on a second it can keep firing for that second.
RTC.disableInterruptMatch()
// Call user callback function
if rtcCallback != nil {
rtcCallback()
}
if rtcAlarmRepeats {
// If it is a repeatable alarm, reset time and re-enable the alarm.
RTC.setTime(rtcEpoch)
RTC.enableInterruptMatch()
}
}
+25 -1
View File
@@ -40,6 +40,9 @@ var (
ErrLSBNotSupported = errors.New("SPI LSB unsupported on PL022")
ErrSPITimeout = errors.New("SPI timeout")
ErrSPIBaud = errors.New("SPI baud too low or above 66.5Mhz")
errSPIInvalidSDI = errors.New("invalid SPI SDI pin")
errSPIInvalidSDO = errors.New("invalid SPI SDO pin")
errSPIInvalidSCK = errors.New("invalid SPI SCK pin")
)
type SPI struct {
@@ -162,7 +165,7 @@ func (spi SPI) GetBaudRate() uint32 {
// No pin configuration is needed of SCK, SDO and SDI needed after calling Configure.
func (spi SPI) Configure(config SPIConfig) error {
const defaultBaud uint32 = 115200
if config.SCK == 0 {
if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 {
// set default pins if config zero valued or invalid clock pin supplied.
switch spi.Bus {
case rp.SPI0:
@@ -175,6 +178,27 @@ func (spi SPI) Configure(config SPIConfig) error {
config.SDI = SPI1_SDI_PIN
}
}
var okSDI, okSDO, okSCK bool
switch spi.Bus {
case rp.SPI0:
okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16
okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19
okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18
case rp.SPI1:
okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 28
okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27
okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26
}
switch {
case !okSDI:
return errSPIInvalidSDI
case !okSDO:
return errSPIInvalidSDO
case !okSCK:
return errSPIInvalidSCK
}
if config.DataBits < 4 || config.DataBits > 16 {
config.DataBits = 8
}
+3 -3
View File
@@ -127,10 +127,10 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
// Bus is reset
if (status & rp.USBCTRL_REGS_INTS_BUS_RESET) > 0 {
rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_BUS_RESET)
rp.USBCTRL_REGS.ADDR_ENDP.Set(0)
initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL)
fixRP2040UsbDeviceEnumeration()
rp.USBCTRL_REGS.ADDR_ENDP.Set(0)
initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL)
}
}
+122
View File
@@ -0,0 +1,122 @@
//go:build stm32f4 || stm32l4 || stm32wlx
package machine
import (
"device/stm32"
"bytes"
"unsafe"
)
// compile-time check for ensuring we fulfill BlockDevice interface
var _ BlockDevice = flashBlockDevice{}
var Flash flashBlockDevice
type flashBlockDevice struct {
}
// ReadAt reads the given number of bytes from the block device.
func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) {
if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() {
return 0, errFlashCannotReadPastEOF
}
data := unsafe.Slice((*byte)(unsafe.Pointer(FlashDataStart()+uintptr(off))), len(p))
copy(p, data)
return len(p), nil
}
// WriteAt writes the given number of bytes to the block device.
// Only double-word (64 bits) length data can be programmed. See rm0461 page 78.
// If the length of p is not long enough it will be padded with 0xFF bytes.
// This method assumes that the destination is already erased.
func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) {
if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() {
return 0, errFlashCannotWritePastEOF
}
unlockFlash()
defer lockFlash()
return writeFlashData(FlashDataStart()+uintptr(off), f.pad(p))
}
// Size returns the number of bytes in this block device.
func (f flashBlockDevice) Size() int64 {
return int64(FlashDataEnd() - FlashDataStart())
}
// WriteBlockSize returns the block size in which data can be written to
// memory. It can be used by a client to optimize writes, non-aligned writes
// should always work correctly.
func (f flashBlockDevice) WriteBlockSize() int64 {
return writeBlockSize
}
func eraseBlockSize() int64 {
return eraseBlockSizeValue
}
// EraseBlockSize returns the smallest erasable area on this particular chip
// in bytes. This is used for the block size in EraseBlocks.
// It must be a power of two, and may be as small as 1. A typical size is 4096.
// TODO: correctly handle processors that have differently sized blocks
// in different areas of memory like the STM32F40x and STM32F1x.
func (f flashBlockDevice) EraseBlockSize() int64 {
return eraseBlockSize()
}
// EraseBlocks erases the given number of blocks. An implementation may
// transparently coalesce ranges of blocks into larger bundles if the chip
// supports this. The start and len parameters are in block numbers, use
// EraseBlockSize to map addresses to blocks.
func (f flashBlockDevice) EraseBlocks(start, len int64) error {
unlockFlash()
defer lockFlash()
for i := start; i < start+len; i++ {
if err := eraseBlock(uint32(i)); err != nil {
return err
}
}
return nil
}
// pad data if needed so it is long enough for correct byte alignment on writes.
func (f flashBlockDevice) pad(p []byte) []byte {
paddingNeeded := f.WriteBlockSize() - (int64(len(p)) % f.WriteBlockSize())
if paddingNeeded == 0 {
return p
}
padded := bytes.Repeat([]byte{0xff}, int(paddingNeeded))
return append(p, padded...)
}
const memoryStart = 0x08000000
func unlockFlash() {
// keys as described rm0461 page 76
var fkey1 uint32 = 0x45670123
var fkey2 uint32 = 0xCDEF89AB
// Wait for the flash memory not to be busy
for stm32.FLASH.GetSR_BSY() != 0 {
}
// Check if the controller is unlocked already
if stm32.FLASH.GetCR_LOCK() != 0 {
// Write the first key
stm32.FLASH.SetKEYR(fkey1)
// Write the second key
stm32.FLASH.SetKEYR(fkey2)
}
}
func lockFlash() {
stm32.FLASH.SetCR_LOCK(1)
}
+141
View File
@@ -6,6 +6,8 @@ package machine
import (
"device/stm32"
"encoding/binary"
"errors"
"math/bits"
"runtime/interrupt"
"runtime/volatile"
@@ -791,3 +793,142 @@ func (i2c *I2C) getSpeed(config I2CConfig) uint32 {
}
}
}
//---------- Flash related code
// the block size actually depends on the sector.
// TODO: handle this correctly for sectors > 3
const eraseBlockSizeValue = 16384
// see RM0090 page 75
func sectorNumber(address uintptr) uint32 {
switch {
// 0x0800 0000 - 0x0800 3FFF
case address >= 0x08000000 && address <= 0x08003FFF:
return 0
// 0x0800 4000 - 0x0800 7FFF
case address >= 0x08004000 && address <= 0x08007FFF:
return 1
// 0x0800 8000 - 0x0800 BFFF
case address >= 0x08008000 && address <= 0x0800BFFF:
return 2
// 0x0800 C000 - 0x0800 FFFF
case address >= 0x0800C000 && address <= 0x0800FFFF:
return 3
// 0x0801 0000 - 0x0801 FFFF
case address >= 0x08010000 && address <= 0x0801FFFF:
return 4
// 0x0802 0000 - 0x0803 FFFF
case address >= 0x08020000 && address <= 0x0803FFFF:
return 5
// 0x0804 0000 - 0x0805 FFFF
case address >= 0x08040000 && address <= 0x0805FFFF:
return 6
case address >= 0x08060000 && address <= 0x0807FFFF:
return 7
case address >= 0x08080000 && address <= 0x0809FFFF:
return 8
case address >= 0x080A0000 && address <= 0x080BFFFF:
return 9
case address >= 0x080C0000 && address <= 0x080DFFFF:
return 10
case address >= 0x080E0000 && address <= 0x080FFFFF:
return 11
default:
return 0
}
}
// calculate sector number from address
// var sector uint32 = sectorNumber(address)
// see RM0090 page 85
// eraseBlock at the passed in block number
func eraseBlock(block uint32) error {
waitUntilFlashDone()
// clear any previous errors
stm32.FLASH.SR.SetBits(0xF0)
// set SER bit
stm32.FLASH.SetCR_SER(1)
defer stm32.FLASH.SetCR_SER(0)
// set the block (aka sector) to be erased
stm32.FLASH.SetCR_SNB(block)
defer stm32.FLASH.SetCR_SNB(0)
// start the page erase
stm32.FLASH.SetCR_STRT(1)
waitUntilFlashDone()
if err := checkError(); err != nil {
return err
}
return nil
}
const writeBlockSize = 2
// see RM0090 page 86
// must write data in word-length
func writeFlashData(address uintptr, data []byte) (int, error) {
if len(data)%writeBlockSize != 0 {
return 0, errFlashInvalidWriteLength
}
waitUntilFlashDone()
// clear any previous errors
stm32.FLASH.SR.SetBits(0xF0)
// set parallelism to x32
stm32.FLASH.SetCR_PSIZE(2)
for i := 0; i < len(data); i += writeBlockSize {
// start write operation
stm32.FLASH.SetCR_PG(1)
*(*uint16)(unsafe.Pointer(address)) = binary.BigEndian.Uint16(data[i : i+writeBlockSize])
waitUntilFlashDone()
if err := checkError(); err != nil {
return i, err
}
// end write operation
stm32.FLASH.SetCR_PG(0)
}
return len(data), nil
}
func waitUntilFlashDone() {
for stm32.FLASH.GetSR_BSY() != 0 {
}
}
var (
errFlashPGS = errors.New("errFlashPGS")
errFlashPGP = errors.New("errFlashPGP")
errFlashPGA = errors.New("errFlashPGA")
errFlashWRP = errors.New("errFlashWRP")
)
func checkError() error {
switch {
case stm32.FLASH.GetSR_PGSERR() != 0:
return errFlashPGS
case stm32.FLASH.GetSR_PGPERR() != 0:
return errFlashPGP
case stm32.FLASH.GetSR_PGAERR() != 0:
return errFlashPGA
case stm32.FLASH.GetSR_WRPERR() != 0:
return errFlashWRP
}
return nil
}
+103
View File
@@ -4,6 +4,8 @@ package machine
import (
"device/stm32"
"encoding/binary"
"errors"
"runtime/interrupt"
"runtime/volatile"
"unsafe"
@@ -543,3 +545,104 @@ func initRNG() {
stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_RNGEN)
stm32.RNG.CR.SetBits(stm32.RNG_CR_RNGEN)
}
//---------- Flash related code
const eraseBlockSizeValue = 2048
// see RM0394 page 83
// eraseBlock of the passed in block number
func eraseBlock(block uint32) error {
waitUntilFlashDone()
// clear any previous errors
stm32.FLASH.SR.SetBits(0x3FA)
// page erase operation
stm32.FLASH.SetCR_PER(1)
defer stm32.FLASH.SetCR_PER(0)
// set the page to be erased
stm32.FLASH.SetCR_PNB(block)
// start the page erase
stm32.FLASH.SetCR_START(1)
waitUntilFlashDone()
if err := checkError(); err != nil {
return err
}
return nil
}
const writeBlockSize = 8
// see RM0394 page 84
// It is only possible to program double word (2 x 32-bit data).
func writeFlashData(address uintptr, data []byte) (int, error) {
if len(data)%writeBlockSize != 0 {
return 0, errFlashInvalidWriteLength
}
waitUntilFlashDone()
// clear any previous errors
stm32.FLASH.SR.SetBits(0x3FA)
for j := 0; j < len(data); j += writeBlockSize {
// start page write operation
stm32.FLASH.SetCR_PG(1)
// write first word using double-word low order word
*(*uint32)(unsafe.Pointer(address)) = binary.BigEndian.Uint32(data[j+writeBlockSize/2 : j+writeBlockSize])
address += writeBlockSize / 2
// write second word using double-word high order word
*(*uint32)(unsafe.Pointer(address)) = binary.BigEndian.Uint32(data[j : j+writeBlockSize/2])
waitUntilFlashDone()
if err := checkError(); err != nil {
return j, err
}
// end flash write
stm32.FLASH.SetCR_PG(0)
address += writeBlockSize / 2
}
return len(data), nil
}
func waitUntilFlashDone() {
for stm32.FLASH.GetSR_BSY() != 0 {
}
}
var (
errFlashPGS = errors.New("errFlashPGS")
errFlashSIZE = errors.New("errFlashSIZE")
errFlashPGA = errors.New("errFlashPGA")
errFlashWRP = errors.New("errFlashWRP")
errFlashPROG = errors.New("errFlashPROG")
)
func checkError() error {
switch {
case stm32.FLASH.GetSR_PGSERR() != 0:
return errFlashPGS
case stm32.FLASH.GetSR_SIZERR() != 0:
return errFlashSIZE
case stm32.FLASH.GetSR_PGAERR() != 0:
return errFlashPGA
case stm32.FLASH.GetSR_WRPERR() != 0:
return errFlashWRP
case stm32.FLASH.GetSR_PROGERR() != 0:
return errFlashPROG
}
return nil
}
+114
View File
@@ -6,6 +6,8 @@ package machine
import (
"device/stm32"
"encoding/binary"
"errors"
"math/bits"
"runtime/interrupt"
"runtime/volatile"
@@ -424,3 +426,115 @@ const (
ARR_MAX = 0x10000
PSC_MAX = 0x10000
)
//---------- Flash related code
const eraseBlockSizeValue = 2048
// eraseBlock of the passed in block number
func eraseBlock(block uint32) error {
waitUntilFlashDone()
// check if operation is allowed.
if stm32.FLASH.GetSR_PESD() != 0 {
return errFlashCannotErasePage
}
// clear any previous errors
stm32.FLASH.SR.SetBits(0x3FA)
// page erase operation
stm32.FLASH.SetCR_PER(1)
defer stm32.FLASH.SetCR_PER(0)
// set the address to the page to be written
stm32.FLASH.SetCR_PNB(block)
defer stm32.FLASH.SetCR_PNB(0)
// start the page erase
stm32.FLASH.SetCR_STRT(1)
waitUntilFlashDone()
if err := checkError(); err != nil {
return err
}
return nil
}
const writeBlockSize = 8
func writeFlashData(address uintptr, data []byte) (int, error) {
if len(data)%writeBlockSize != 0 {
return 0, errFlashInvalidWriteLength
}
waitUntilFlashDone()
// check if operation is allowed
if stm32.FLASH.GetSR_PESD() != 0 {
return 0, errFlashNotAllowedWriteData
}
// clear any previous errors
stm32.FLASH.SR.SetBits(0x3FA)
for j := 0; j < len(data); j += writeBlockSize {
// start page write operation
stm32.FLASH.SetCR_PG(1)
// write first word using double-word low order word
*(*uint32)(unsafe.Pointer(address)) = binary.BigEndian.Uint32(data[j+writeBlockSize/2 : j+writeBlockSize])
address += writeBlockSize / 2
// write second word using double-word high order word
*(*uint32)(unsafe.Pointer(address)) = binary.BigEndian.Uint32(data[j : j+writeBlockSize/2])
waitUntilFlashDone()
if err := checkError(); err != nil {
return j, err
}
// end flash write
stm32.FLASH.SetCR_PG(0)
address += writeBlockSize / 2
}
return len(data), nil
}
func waitUntilFlashDone() {
for stm32.FLASH.GetSR_BSY() != 0 {
}
for stm32.FLASH.GetSR_CFGBSY() != 0 {
}
}
var (
errFlashPGS = errors.New("errFlashPGS")
errFlashSIZE = errors.New("errFlashSIZE")
errFlashPGA = errors.New("errFlashPGA")
errFlashWRP = errors.New("errFlashWRP")
errFlashPROG = errors.New("errFlashPROG")
)
func checkError() error {
switch {
case stm32.FLASH.GetSR_PGSERR() != 0:
return errFlashPGS
case stm32.FLASH.GetSR_SIZERR() != 0:
return errFlashSIZE
case stm32.FLASH.GetSR_PGAERR() != 0:
return errFlashPGA
case stm32.FLASH.GetSR_WRPERR() != 0:
return errFlashWRP
case stm32.FLASH.GetSR_PROGERR() != 0:
return errFlashPROG
}
return nil
}
+14 -1
View File
@@ -67,7 +67,7 @@ var DescriptorCDCHID = Descriptor{
0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00,
0x07, 0x05, 0x83, 0x02, 0x40, 0x00, 0x00,
0x09, 0x04, 0x02, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00,
0x09, 0x21, 0x01, 0x01, 0x00, 0x01, 0x22, 0x65, 0x00,
0x09, 0x21, 0x01, 0x01, 0x00, 0x01, 0x22, 0x7E, 0x00,
0x07, 0x05, 0x84, 0x03, 0x40, 0x00, 0x01,
},
HID: map[uint16][]byte{
@@ -80,6 +80,19 @@ var DescriptorCDCHID = Descriptor{
0x03, 0x15, 0x00, 0x25, 0x01, 0x95, 0x03, 0x75, 0x01, 0x81, 0x02, 0x95, 0x01, 0x75, 0x05, 0x81,
0x03, 0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38, 0x15, 0x81, 0x25, 0x7f, 0x75, 0x08, 0x95,
0x03, 0x81, 0x06, 0xc0, 0xc0,
0x05, 0x0C, // Usage Page (Consumer)
0x09, 0x01, // Usage (Consumer Control)
0xA1, 0x01, // Collection (Application)
0x85, 0x03, // Report ID (3)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x1F, // Logical Maximum (8191)
0x19, 0x00, // Usage Minimum (Unassigned)
0x2A, 0xFF, 0x1F, // Usage Maximum (0x1FFF)
0x75, 0x10, // Report Size (16)
0x95, 0x01, // Report Count (1)
0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
},
},
}
+20 -10
View File
@@ -238,16 +238,26 @@ func (kb *keyboard) sendKey(consumer bool, b []byte) bool {
func (kb *keyboard) keyboardSendKeys(consumer bool) bool {
var b [9]byte
b[0] = 0x02
b[1] = kb.mod
b[2] = 0x02
b[3] = kb.key[0]
b[4] = kb.key[1]
b[5] = kb.key[2]
b[6] = kb.key[3]
b[7] = kb.key[4]
b[8] = kb.key[5]
return kb.sendKey(consumer, b[:])
if !consumer {
b[0] = 0x02 // REPORT_ID
b[1] = kb.mod
b[2] = 0x02
b[3] = kb.key[0]
b[4] = kb.key[1]
b[5] = kb.key[2]
b[6] = kb.key[3]
b[7] = kb.key[4]
b[8] = kb.key[5]
return kb.sendKey(consumer, b[:])
} else {
b[0] = 0x03 // REPORT_ID
b[1] = uint8(kb.con[0])
b[2] = uint8((kb.con[0] & 0x0300) >> 8)
return kb.sendKey(consumer, b[:3])
}
}
// Down transmits a key-down event for the given Keycode.
+1
View File
@@ -77,6 +77,7 @@ const (
KeyModifierRightAlt Keycode = 0x40 | 0xE000
KeyModifierRightGUI Keycode = 0x80 | 0xE000
// KeySystemXXX is not supported now
KeySystemPowerDown Keycode = 0x81 | 0xE200
KeySystemSleep Keycode = 0x82 | 0xE200
KeySystemWakeUp Keycode = 0x83 | 0xE200
+12 -12
View File
@@ -71,7 +71,7 @@ var deepEqualTests = []DeepEqualTest{
{&[3]int{1, 2, 3}, &[3]int{1, 2, 3}, true},
{Basic{1, 0.5}, Basic{1, 0.5}, true},
{error(nil), error(nil), true},
//{map[int]string{1: "one", 2: "two"}, map[int]string{2: "two", 1: "one"}, true},
{map[int]string{1: "one", 2: "two"}, map[int]string{2: "two", 1: "one"}, true},
{fn1, fn2, true},
{[]byte{1, 2, 3}, []byte{1, 2, 3}, true},
{[]MyByte{1, 2, 3}, []MyByte{1, 2, 3}, true},
@@ -87,10 +87,10 @@ var deepEqualTests = []DeepEqualTest{
{&[3]int{1, 2, 3}, &[3]int{1, 2, 4}, false},
{Basic{1, 0.5}, Basic{1, 0.6}, false},
{Basic{1, 0}, Basic{2, 0}, false},
//{map[int]string{1: "one", 3: "two"}, map[int]string{2: "two", 1: "one"}, false},
//{map[int]string{1: "one", 2: "txo"}, map[int]string{2: "two", 1: "one"}, false},
//{map[int]string{1: "one"}, map[int]string{2: "two", 1: "one"}, false},
//{map[int]string{2: "two", 1: "one"}, map[int]string{1: "one"}, false},
{map[int]string{1: "one", 3: "two"}, map[int]string{2: "two", 1: "one"}, false},
{map[int]string{1: "one", 2: "txo"}, map[int]string{2: "two", 1: "one"}, false},
{map[int]string{1: "one"}, map[int]string{2: "two", 1: "one"}, false},
{map[int]string{2: "two", 1: "one"}, map[int]string{1: "one"}, false},
{nil, 1, false},
{1, nil, false},
{fn1, fn3, false},
@@ -104,16 +104,16 @@ var deepEqualTests = []DeepEqualTest{
{&[1]float64{math.NaN()}, self{}, true},
{[]float64{math.NaN()}, []float64{math.NaN()}, false},
{[]float64{math.NaN()}, self{}, true},
//{map[float64]float64{math.NaN(): 1}, map[float64]float64{1: 2}, false},
//{map[float64]float64{math.NaN(): 1}, self{}, true},
{map[float64]float64{math.NaN(): 1}, map[float64]float64{1: 2}, false},
{map[float64]float64{math.NaN(): 1}, self{}, true},
// Nil vs empty: not the same.
{[]int{}, []int(nil), false},
{[]int{}, []int{}, true},
{[]int(nil), []int(nil), true},
//{map[int]int{}, map[int]int(nil), false},
//{map[int]int{}, map[int]int{}, true},
//{map[int]int(nil), map[int]int(nil), true},
{map[int]int{}, map[int]int(nil), false},
{map[int]int{}, map[int]int{}, true},
{map[int]int(nil), map[int]int(nil), true},
// Mismatched types
{1, 1.0, false},
@@ -130,8 +130,8 @@ var deepEqualTests = []DeepEqualTest{
// Possible loops.
{&loopy1, &loopy1, true},
{&loopy1, &loopy2, true},
//{&cycleMap1, &cycleMap2, true},
//{&cycleMap1, &cycleMap3, false},
{&cycleMap1, &cycleMap2, true},
{&cycleMap1, &cycleMap3, false},
}
func TestDeepEqual(t *testing.T) {
+1 -1
View File
@@ -15,7 +15,7 @@ import "unsafe"
type visit struct {
a1 unsafe.Pointer
a2 unsafe.Pointer
typ rawType
typ *rawType
}
// Tests for deep equality using reflected types. The map argument tracks
-61
View File
@@ -1,61 +0,0 @@
package reflect
import (
"unsafe"
)
// This stores a varint for each named type. Named types are identified by their
// name instead of by their type. The named types stored in this struct are
// non-basic types: pointer, struct, and channel.
//
//go:extern reflect.namedNonBasicTypesSidetable
var namedNonBasicTypesSidetable uintptr
//go:extern reflect.structTypesSidetable
var structTypesSidetable byte
//go:extern reflect.structNamesSidetable
var structNamesSidetable byte
//go:extern reflect.arrayTypesSidetable
var arrayTypesSidetable byte
// readStringSidetable reads a string from the given table (like
// structNamesSidetable) and returns this string. No heap allocation is
// necessary because it makes the string point directly to the raw bytes of the
// table.
func readStringSidetable(table unsafe.Pointer, index uintptr) string {
nameLen, namePtr := readVarint(unsafe.Pointer(uintptr(table) + index))
return *(*string)(unsafe.Pointer(&stringHeader{
data: namePtr,
len: nameLen,
}))
}
// readVarint decodes a varint as used in the encoding/binary package.
// It has an input pointer and returns the read varint and the pointer
// incremented to the next field in the data structure, just after the varint.
//
// Details:
// https://github.com/golang/go/blob/e37a1b1c/src/encoding/binary/varint.go#L7-L25
func readVarint(buf unsafe.Pointer) (uintptr, unsafe.Pointer) {
var n uintptr
shift := uintptr(0)
for {
// Read the next byte in the buffer.
c := *(*byte)(buf)
// Decode the bits from this byte and add them to the output number.
n |= uintptr(c&0x7f) << shift
shift += 7
// Increment the buf pointer (pointer arithmetic!).
buf = unsafe.Pointer(uintptr(buf) + 1)
// Check whether this is the last byte of this varint. The upper bit
// (msb) indicates whether any bytes follow.
if c>>7 == 0 {
return n, buf
}
}
}
+257 -158
View File
@@ -2,36 +2,74 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Type information of an interface is stored as a pointer to a global in the
// interface type (runtime._interface). This is called a type struct.
// It always starts with a byte that contains both the type kind and a few
// flags. In most cases it also contains a pointer to another type struct
// (ptrTo), that is the pointer type of the current type (for example, type int
// also has a pointer to the type *int). The exception is pointer types, to
// avoid infinite recursion.
//
// The layouts specifically look like this:
// - basic types (Bool..UnsafePointer):
// meta uint8 // actually: kind + flags
// ptrTo *typeStruct
// - named types (see elemType):
// meta uint8
// ptrTo *typeStruct
// underlying *typeStruct // the underlying, non-named type
// - channels and slices (see elemType):
// meta uint8
// ptrTo *typeStruct
// elementType *typeStruct // the type that you get with .Elem()
// - pointer types (see ptrType, this doesn't include chan, map, etc):
// meta uint8
// elementType *typeStruct
// - array types (see arrayType)
// meta uint8
// ptrTo *typeStruct
// elem *typeStruct // element type of the array
// arrayLen uintptr // length of the array (this is part of the type)
// - map types (this is still missing the key and element types)
// meta uint8
// ptrTo *typeStruct
// elem *typeStruct
// key *typeStruct
// - struct types (see structType):
// meta uint8
// numField uint16
// ptrTo *typeStruct
// fields [...]structField // the remaining fields are all of type structField
// - interface types (this is missing the interface methods):
// meta uint8
// ptrTo *typeStruct
// - signature types (this is missing input and output parameters):
// meta uint8
// ptrTo *typeStruct
//
// The type struct is essentially a union of all the above types. Which it is,
// can be determined by looking at the meta byte.
package reflect
import (
"unsafe"
)
// The compiler uses a compact encoding to store type information. Unlike the
// main Go compiler, most of the types are stored directly in the type code.
//
// Type code bit allocation:
// xxxxx0: basic types, where xxxxx is the basic type number (never 0).
// The higher bits indicate the named type, if any.
// nxxx1: complex types, where n indicates whether this is a named type (named
// if set) and xxx contains the type kind number:
// 0 (0001): Chan
// 1 (0011): Interface
// 2 (0101): Pointer
// 3 (0111): Slice
// 4 (1001): Array
// 5 (1011): Func
// 6 (1101): Map
// 7 (1111): Struct
// The higher bits are either the contents of the type depending on the
// type (if n is clear) or indicate the number of the named type (if n
// is set).
// Flags stored in the first byte of the struct field byte array. Must be kept
// up to date with compiler/interface.go.
const (
structFieldFlagAnonymous = 1 << iota
structFieldFlagHasTag
structFieldFlagIsExported
)
type Kind uintptr
type Kind uint8
// Copied from reflect/type.go
// https://golang.org/src/reflect/type.go?s=8302:8316#L217
// These constants must match basicTypes and the typeKind* constants in
// compiler/interface.go
const (
Invalid Kind = iota
Bool
@@ -124,11 +162,6 @@ func (k Kind) String() string {
}
}
// basicType returns a new Type for this kind if Kind is a basic type.
func (k Kind) basicType() rawType {
return rawType(k << 1)
}
// Copied from reflect/type.go
// https://go.dev/src/reflect/type.go?#L348
@@ -346,8 +379,71 @@ type Type interface {
Out(i int) Type
}
// The typecode as used in an interface{}.
type rawType uintptr
// Constants for the 'meta' byte.
const (
kindMask = 31 // mask to apply to the meta byte to get the Kind value
flagNamed = 32 // flag that is set if this is a named type
)
// The base type struct. All type structs start with this.
type rawType struct {
meta uint8 // metadata byte, contains kind and flags (see contants above)
}
// All types that have an element type: named, chan, slice, array, map (but not
// pointer because it doesn't have ptrTo).
type elemType struct {
rawType
ptrTo *rawType
elem *rawType
}
type ptrType struct {
rawType
elem *rawType
}
type arrayType struct {
rawType
ptrTo *rawType
elem *rawType
arrayLen uintptr
}
type mapType struct {
rawType
ptrTo *rawType
elem *rawType
key *rawType
}
// Type for struct types. The numField value is intentionally put before ptrTo
// for better struct packing on 32-bit and 64-bit architectures. On these
// architectures, the ptrTo field still has the same offset as in all the other
// type structs.
// The fields array isn't necessarily 1 structField long, instead it is as long
// as numFields. The array is given a length of 1 to satisfy the Go type
// checker.
type structType struct {
rawType
numField uint16
ptrTo *rawType
fields [1]structField // the remaining fields are all of type structField
}
type structField struct {
fieldType *rawType
data unsafe.Pointer // various bits of information, packed in a byte array
}
// Equivalent to (go/types.Type).Underlying(): if this is a named type return
// the underlying type, else just return the type itself.
func (t *rawType) underlying() *rawType {
if t.meta&flagNamed != 0 {
return (*elemType)(unsafe.Pointer(t)).elem
}
return t
}
func TypeOf(i interface{}) Type {
return ValueOf(i).typecode
@@ -356,70 +452,53 @@ func TypeOf(i interface{}) Type {
func PtrTo(t Type) Type { return PointerTo(t) }
func PointerTo(t Type) Type {
if t.Kind() == Pointer {
switch t.Kind() {
case Pointer:
panic("reflect: cannot make **T type")
case Struct:
return (*structType)(unsafe.Pointer(t.(*rawType))).ptrTo
default:
return (*elemType)(unsafe.Pointer(t.(*rawType))).ptrTo
}
ptrType := t.(rawType)<<5 | 5 // 0b0101 == 5
if ptrType>>5 != t {
panic("reflect: PointerTo type does not fit")
}
return ptrType
}
func (t rawType) String() string {
func (t *rawType) String() string {
return "T"
}
func (t rawType) Kind() Kind {
if t%2 == 0 {
// basic type
return Kind((t >> 1) % 32)
} else {
return Kind(t>>1)%8 + 19
}
func (t *rawType) Kind() Kind {
return Kind(t.meta & kindMask)
}
// Elem returns the element type for channel, slice and array types, the
// pointed-to value for pointer types, and the key type for map types.
func (t rawType) Elem() Type {
func (t *rawType) Elem() Type {
return t.elem()
}
func (t rawType) elem() rawType {
switch t.Kind() {
case Chan, Pointer, Slice:
return t.stripPrefix()
case Array:
index := t.stripPrefix()
elem, _ := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&arrayTypesSidetable)) + uintptr(index)))
return rawType(elem)
default: // not implemented: Map
panic("unimplemented: (reflect.Type).Elem()")
func (t *rawType) elem() *rawType {
underlying := t.underlying()
switch underlying.Kind() {
case Pointer:
return (*ptrType)(unsafe.Pointer(underlying)).elem
case Chan, Slice, Array, Map:
return (*elemType)(unsafe.Pointer(underlying)).elem
default:
panic(&TypeError{"Elem"})
}
}
// stripPrefix removes the "prefix" (the low 5 bits of the type code) from
// the type code. If this is a named type, it will resolve the underlying type
// (which is the data for this named type). If it is not, the lower bits are
// simply shifted off.
//
// The behavior is only defined for non-basic types.
func (t rawType) stripPrefix() rawType {
// Look at the 'n' bit in the type code (see the top of this file) to see
// whether this is a named type.
if (t>>4)%2 != 0 {
// This is a named type. The data is stored in a sidetable.
namedTypeNum := t >> 5
n := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&namedNonBasicTypesSidetable)) + uintptr(namedTypeNum)*unsafe.Sizeof(uintptr(0))))
return rawType(n)
func (t *rawType) key() *rawType {
underlying := t.underlying()
if underlying.Kind() != Map {
panic(&TypeError{"Key"})
}
// Not a named type, so the value is stored directly in the type code.
return t >> 5
return (*mapType)(unsafe.Pointer(underlying)).key
}
// Field returns the type of the i'th field of this struct type. It panics if t
// is not a struct type.
func (t rawType) Field(i int) StructField {
func (t *rawType) Field(i int) StructField {
field := t.rawField(i)
return StructField{
Name: field.Name,
@@ -435,82 +514,87 @@ func (t rawType) Field(i int) StructField {
// Type member to an interface.
//
// For internal use only.
func (t rawType) rawField(i int) rawStructField {
func (t *rawType) rawField(n int) rawStructField {
if t.Kind() != Struct {
panic(&TypeError{"Field"})
}
structIdentifier := t.stripPrefix()
numField, p := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&structTypesSidetable)) + uintptr(structIdentifier)))
if uint(i) >= uint(numField) {
descriptor := (*structType)(unsafe.Pointer(t.underlying()))
if uint(n) >= uint(descriptor.numField) {
panic("reflect: field index out of range")
}
// Iterate over every field in the struct and update the StructField each
// time, until the target field has been reached. This is very much not
// efficient, but it is easy to implement.
// Adding a jump table at the start to jump to the field directly would
// make this much faster, but that would also impact code size.
field := rawStructField{}
offset := uintptr(0)
for fieldNum := 0; fieldNum <= i; fieldNum++ {
// Read some flags of this field, like whether the field is an
// embedded field.
flagsByte := *(*uint8)(p)
p = unsafe.Pointer(uintptr(p) + 1)
// Iterate over all the fields to calculate the offset.
// This offset could have been stored directly in the array (to make the
// lookup faster), but by calculating it on-the-fly a bit of storage can be
// saved.
field := &descriptor.fields[0]
var offset uintptr = 0
for i := 0; i < n; i++ {
offset += field.fieldType.Size()
// Read the type of this struct field.
var fieldTypeVal uintptr
fieldTypeVal, p = readVarint(p)
fieldType := rawType(fieldTypeVal)
field.Type = fieldType
// Increment pointer to the next field.
field = (*structField)(unsafe.Add(unsafe.Pointer(field), unsafe.Sizeof(structField{})))
// Move Offset forward to align it to this field's alignment.
// Assume alignment is a power of two.
offset = align(offset, uintptr(fieldType.Align()))
field.Offset = offset
offset += fieldType.Size() // starting (unaligned) offset for next field
// Read the field name.
var nameNum uintptr
nameNum, p = readVarint(p)
field.Name = readStringSidetable(unsafe.Pointer(&structNamesSidetable), nameNum)
// The first bit in the flagsByte indicates whether this is an embedded
// field.
field.Anonymous = flagsByte&1 != 0
// The second bit indicates whether there is a tag.
if flagsByte&2 != 0 {
// There is a tag.
var tagNum uintptr
tagNum, p = readVarint(p)
field.Tag = StructTag(readStringSidetable(unsafe.Pointer(&structNamesSidetable), tagNum))
} else {
// There is no tag.
field.Tag = ""
}
// The third bit indicates whether this field is exported.
if flagsByte&4 != 0 {
// This field is exported.
field.PkgPath = ""
} else {
// This field is unexported.
// TODO: list the real package path here. Storing it should not
// significantly impact binary size as there is only a limited
// number of packages in any program.
field.PkgPath = "<unimplemented>"
}
// Align the offset for the next field.
offset = align(offset, uintptr(field.fieldType.Align()))
}
return field
data := field.data
// Read some flags of this field, like whether the field is an embedded
// field. See structFieldFlagAnonymous and similar flags.
flagsByte := *(*byte)(data)
data = unsafe.Add(data, 1)
// Read the field name.
nameStart := data
var nameLen uintptr
for *(*byte)(data) != 0 {
nameLen++
data = unsafe.Add(data, 1) // C: data++
}
name := *(*string)(unsafe.Pointer(&stringHeader{
data: nameStart,
len: nameLen,
}))
// Read the field tag, if there is one.
var tag string
if flagsByte&structFieldFlagHasTag != 0 {
data = unsafe.Add(data, 1) // C: data+1
tagLen := uintptr(*(*byte)(data))
data = unsafe.Add(data, 1) // C: data+1
tag = *(*string)(unsafe.Pointer(&stringHeader{
data: data,
len: tagLen,
}))
}
// Set the PkgPath to some (arbitrary) value if the package path is not
// exported.
pkgPath := ""
if flagsByte&structFieldFlagIsExported == 0 {
// This field is unexported.
// TODO: list the real package path here. Storing it should not
// significantly impact binary size as there is only a limited
// number of packages in any program.
pkgPath = "<unimplemented>"
}
return rawStructField{
Name: name,
PkgPath: pkgPath,
Type: field.fieldType,
Tag: StructTag(tag),
Anonymous: flagsByte&structFieldFlagAnonymous != 0,
Offset: offset,
}
}
// Bits returns the number of bits that this type uses. It is only valid for
// arithmetic types (integers, floats, and complex numbers). For other types, it
// will panic.
func (t rawType) Bits() int {
func (t *rawType) Bits() int {
kind := t.Kind()
if kind >= Int && kind <= Complex128 {
return int(t.Size()) * 8
@@ -520,34 +604,26 @@ func (t rawType) Bits() int {
// Len returns the number of elements in this array. It panics of the type kind
// is not Array.
func (t rawType) Len() int {
func (t *rawType) Len() int {
if t.Kind() != Array {
panic(TypeError{"Len"})
}
// skip past the element type
arrayIdentifier := t.stripPrefix()
_, p := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&arrayTypesSidetable)) + uintptr(arrayIdentifier)))
// Read the array length.
arrayLen, _ := readVarint(p)
return int(arrayLen)
return int((*arrayType)(unsafe.Pointer(t.underlying())).arrayLen)
}
// NumField returns the number of fields of a struct type. It panics for other
// type kinds.
func (t rawType) NumField() int {
func (t *rawType) NumField() int {
if t.Kind() != Struct {
panic(&TypeError{"NumField"})
}
structIdentifier := t.stripPrefix()
n, _ := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&structTypesSidetable)) + uintptr(structIdentifier)))
return int(n)
return int((*structType)(unsafe.Pointer(t.underlying())).numField)
}
// Size returns the size in bytes of a given type. It is similar to
// unsafe.Sizeof.
func (t rawType) Size() uintptr {
func (t *rawType) Size() uintptr {
switch t.Kind() {
case Bool, Int8, Uint8:
return 1
@@ -596,7 +672,7 @@ func (t rawType) Size() uintptr {
// Align returns the alignment of this type. It is similar to calling
// unsafe.Alignof.
func (t rawType) Align() int {
func (t *rawType) Align() int {
switch t.Kind() {
case Bool, Int8, Uint8:
return int(unsafe.Alignof(int8(0)))
@@ -648,14 +724,14 @@ func (t rawType) Align() int {
// FieldAlign returns the alignment if this type is used in a struct field. It
// is currently an alias for Align() but this might change in the future.
func (t rawType) FieldAlign() int {
func (t *rawType) FieldAlign() int {
return t.Align()
}
// AssignableTo returns whether a value of type t can be assigned to a variable
// of type u.
func (t rawType) AssignableTo(u Type) bool {
if t == u.(rawType) {
func (t *rawType) AssignableTo(u Type) bool {
if t == u.(*rawType) {
return true
}
if u.Kind() == Interface {
@@ -664,7 +740,7 @@ func (t rawType) AssignableTo(u Type) bool {
return false
}
func (t rawType) Implements(u Type) bool {
func (t *rawType) Implements(u Type) bool {
if u.Kind() != Interface {
panic("reflect: non-interface type passed to Type.Implements")
}
@@ -672,7 +748,7 @@ func (t rawType) Implements(u Type) bool {
}
// Comparable returns whether values of this type can be compared to each other.
func (t rawType) Comparable() bool {
func (t *rawType) Comparable() bool {
switch t.Kind() {
case Bool, Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
return true
@@ -709,36 +785,59 @@ func (t rawType) Comparable() bool {
}
}
// isbinary() returns if the hashmapAlgorithmBinary functions can be used on this type
func (t *rawType) isBinary() bool {
switch t.Kind() {
case Bool, Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
return true
case Float32, Float64, Complex64, Complex128:
return true
case Pointer:
return true
case Array:
return t.elem().isBinary()
case Struct:
numField := t.NumField()
for i := 0; i < numField; i++ {
if !t.rawField(i).Type.isBinary() {
return false
}
}
return true
}
return false
}
func (t rawType) ChanDir() ChanDir {
panic("unimplemented: (reflect.Type).ChanDir()")
}
func (t rawType) ConvertibleTo(u Type) bool {
func (t *rawType) ConvertibleTo(u Type) bool {
panic("unimplemented: (reflect.Type).ConvertibleTo()")
}
func (t rawType) IsVariadic() bool {
func (t *rawType) IsVariadic() bool {
panic("unimplemented: (reflect.Type).IsVariadic()")
}
func (t rawType) NumIn() int {
func (t *rawType) NumIn() int {
panic("unimplemented: (reflect.Type).NumIn()")
}
func (t rawType) NumOut() int {
func (t *rawType) NumOut() int {
panic("unimplemented: (reflect.Type).NumOut()")
}
func (t rawType) NumMethod() int {
func (t *rawType) NumMethod() int {
panic("unimplemented: (reflect.Type).NumMethod()")
}
func (t rawType) Name() string {
func (t *rawType) Name() string {
panic("unimplemented: (reflect.Type).Name()")
}
func (t rawType) Key() Type {
panic("unimplemented: (reflect.Type).Key()")
func (t *rawType) Key() Type {
return t.key()
}
func (t rawType) In(i int) Type {
@@ -792,7 +891,7 @@ func (f StructField) IsExported() bool {
type rawStructField struct {
Name string
PkgPath string
Type rawType
Type *rawType
Tag StructTag
Anonymous bool
Offset uintptr
+214 -17
View File
@@ -17,7 +17,7 @@ const (
)
type Value struct {
typecode rawType
typecode *rawType
value unsafe.Pointer
flags valueFlags
}
@@ -44,15 +44,15 @@ func Indirect(v Value) Value {
}
//go:linkname composeInterface runtime.composeInterface
func composeInterface(rawType, unsafe.Pointer) interface{}
func composeInterface(unsafe.Pointer, unsafe.Pointer) interface{}
//go:linkname decomposeInterface runtime.decomposeInterface
func decomposeInterface(i interface{}) (rawType, unsafe.Pointer)
func decomposeInterface(i interface{}) (unsafe.Pointer, unsafe.Pointer)
func ValueOf(i interface{}) Value {
typecode, value := decomposeInterface(i)
return Value{
typecode: typecode,
typecode: (*rawType)(typecode),
value: value,
flags: valueFlagExported,
}
@@ -85,7 +85,7 @@ func valueInterfaceUnsafe(v Value) interface{} {
}
v.value = unsafe.Pointer(value)
}
return composeInterface(v.typecode, v.value)
return composeInterface(unsafe.Pointer(v.typecode), v.value)
}
func (v Value) Type() Type {
@@ -136,7 +136,7 @@ func (v Value) IsZero() bool {
//
// RawType returns the raw, underlying type code. It is used in the runtime
// package and needs to be exported for the runtime package to access it.
func (v Value) RawType() rawType {
func (v Value) RawType() *rawType {
return v.typecode
}
@@ -205,7 +205,7 @@ func (v Value) pointer() unsafe.Pointer {
}
func (v Value) IsValid() bool {
return v.typecode != 0
return v.typecode != nil
}
func (v Value) CanInterface() bool {
@@ -453,7 +453,7 @@ func (v Value) Elem() Value {
case Interface:
typecode, value := decomposeInterface(*(*interface{})(v.value))
return Value{
typecode: typecode,
typecode: (*rawType)(typecode),
value: value,
flags: v.flags &^ valueFlagIndirect,
}
@@ -523,6 +523,8 @@ func (v Value) Field(i int) Value {
}
}
var uint8Type = TypeOf(uint8(0)).(*rawType)
func (v Value) Index(i int) Value {
switch v.Kind() {
case Slice:
@@ -550,7 +552,7 @@ func (v Value) Index(i int) Value {
panic("reflect: string index out of range")
}
return Value{
typecode: Uint8.basicType(),
typecode: uint8Type,
value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Pointer(uintptr(s.data) + uintptr(i))))),
flags: v.flags & valueFlagExported,
}
@@ -639,30 +641,119 @@ func (v Value) OverflowFloat(x float64) bool {
}
func (v Value) MapKeys() []Value {
panic("unimplemented: (reflect.Value).MapKeys()")
if v.Kind() != Map {
panic(&ValueError{Method: "MapKeys", Kind: v.Kind()})
}
// empty map
if v.Len() == 0 {
return nil
}
keys := make([]Value, 0, v.Len())
it := hashmapNewIterator()
k := New(v.typecode.Key())
e := New(v.typecode.Elem())
for hashmapNext(v.pointer(), it, k.value, e.value) {
keys = append(keys, k.Elem())
k = New(v.typecode.Key())
}
return keys
}
//go:linkname hashmapStringGet runtime.hashmapStringGetUnsafePointer
func hashmapStringGet(m unsafe.Pointer, key string, value unsafe.Pointer, valueSize uintptr) bool
//go:linkname hashmapBinaryGet runtime.hashmapBinaryGetUnsafePointer
func hashmapBinaryGet(m unsafe.Pointer, key, value unsafe.Pointer, valueSize uintptr) bool
func (v Value) MapIndex(key Value) Value {
if v.Kind() != Map {
panic(&ValueError{Method: "MapIndex", Kind: v.Kind()})
}
// compare key type with actual key type of map
if key.typecode != v.typecode.key() {
// type error?
panic("reflect.Value.MapIndex: incompatible types for key")
}
elemType := v.typecode.Elem()
elem := New(elemType)
if key.Kind() == String {
if ok := hashmapStringGet(v.pointer(), *(*string)(key.value), elem.value, elemType.Size()); !ok {
return Value{}
}
return elem.Elem()
} else if key.typecode.isBinary() {
var keyptr unsafe.Pointer
if key.isIndirect() || key.typecode.Size() > unsafe.Sizeof(uintptr(0)) {
keyptr = key.value
} else {
keyptr = unsafe.Pointer(&key.value)
}
//TODO(dgryski): zero out padding bytes in key, if any
if ok := hashmapBinaryGet(v.pointer(), keyptr, elem.value, elemType.Size()); !ok {
return Value{}
}
return elem.Elem()
}
// TODO(dgryski): Add other map types. For now, just string and binary types are supported.
panic("unimplemented: (reflect.Value).MapIndex()")
}
//go:linkname hashmapNewIterator runtime.hashmapNewIterator
func hashmapNewIterator() unsafe.Pointer
//go:linkname hashmapNext runtime.hashmapNextUnsafePointer
func hashmapNext(m unsafe.Pointer, it unsafe.Pointer, key, value unsafe.Pointer) bool
func (v Value) MapRange() *MapIter {
panic("unimplemented: (reflect.Value).MapRange()")
if v.Kind() != Map {
panic(&ValueError{Method: "MapRange", Kind: v.Kind()})
}
return &MapIter{
m: v,
it: hashmapNewIterator(),
key: New(v.typecode.Key()),
val: New(v.typecode.Elem()),
}
}
type MapIter struct {
m Value
it unsafe.Pointer
key Value
val Value
valid bool
}
func (it *MapIter) Key() Value {
panic("unimplemented: (*reflect.MapIter).Key()")
if !it.valid {
panic("reflect.MapIter.Key called on invalid iterator")
}
return it.key.Elem()
}
func (it *MapIter) Value() Value {
panic("unimplemented: (*reflect.MapIter).Value()")
if !it.valid {
panic("reflect.MapIter.Value called on invalid iterator")
}
return it.val.Elem()
}
func (it *MapIter) Next() bool {
panic("unimplemented: (*reflect.MapIter).Next()")
it.valid = hashmapNext(it.m.pointer(), it.it, it.key.value, it.val.value)
return it.valid
}
func (v Value) Set(x Value) {
@@ -803,7 +894,7 @@ func Zero(typ Type) Value {
// new value of the given type.
func New(typ Type) Value {
return Value{
typecode: PtrTo(typ).(rawType),
typecode: PtrTo(typ).(*rawType),
value: alloc(typ.Size(), nil),
flags: valueFlagExported,
}
@@ -901,8 +992,70 @@ func AppendSlice(s, t Value) Value {
}
}
//go:linkname hashmapStringSet runtime.hashmapStringSetUnsafePointer
func hashmapStringSet(m unsafe.Pointer, key string, value unsafe.Pointer)
//go:linkname hashmapBinarySet runtime.hashmapBinarySetUnsafePointer
func hashmapBinarySet(m unsafe.Pointer, key, value unsafe.Pointer)
//go:linkname hashmapStringDelete runtime.hashmapStringDeleteUnsafePointer
func hashmapStringDelete(m unsafe.Pointer, key string)
//go:linkname hashmapBinaryDelete runtime.hashmapBinaryDeleteUnsafePointer
func hashmapBinaryDelete(m unsafe.Pointer, key unsafe.Pointer)
func (v Value) SetMapIndex(key, elem Value) {
panic("unimplemented: (reflect.Value).SetMapIndex()")
if v.Kind() != Map {
panic(&ValueError{Method: "SetMapIndex", Kind: v.Kind()})
}
// compare key type with actual key type of map
if key.typecode != v.typecode.key() {
panic("reflect.Value.SetMapIndex: incompatible types for key")
}
// if elem is the zero Value, it means delete
del := elem == Value{}
if !del && elem.typecode != v.typecode.elem() {
panic("reflect.Value.SetMapIndex: incompatible types for value")
}
if key.Kind() == String {
if del {
hashmapStringDelete(v.pointer(), *(*string)(key.value))
} else {
var elemptr unsafe.Pointer
if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) {
elemptr = elem.value
} else {
elemptr = unsafe.Pointer(&elem.value)
}
hashmapStringSet(v.pointer(), *(*string)(key.value), elemptr)
}
} else if key.typecode.isBinary() {
var keyptr unsafe.Pointer
if key.isIndirect() || key.typecode.Size() > unsafe.Sizeof(uintptr(0)) {
keyptr = key.value
} else {
keyptr = unsafe.Pointer(&key.value)
}
if del {
hashmapBinaryDelete(v.pointer(), keyptr)
} else {
var elemptr unsafe.Pointer
if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) {
elemptr = elem.value
} else {
elemptr = unsafe.Pointer(&elem.value)
}
hashmapBinarySet(v.pointer(), keyptr, elemptr)
}
} else {
panic("unimplemented: (reflect.Value).MapIndex()")
}
}
// FieldByIndex returns the nested field corresponding to index.
@@ -919,9 +1072,53 @@ func (v Value) FieldByName(name string) Value {
panic("unimplemented: (reflect.Value).FieldByName()")
}
//go:linkname hashmapMake runtime.hashmapMakeUnsafePointer
func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) unsafe.Pointer
// MakeMapWithSize creates a new map with the specified type and initial space
// for approximately n elements.
func MakeMapWithSize(typ Type, n int) Value {
// TODO(dgryski): deduplicate these? runtime and reflect both need them.
const (
hashmapAlgorithmBinary uint8 = iota
hashmapAlgorithmString
hashmapAlgorithmInterface
)
if typ.Kind() != Map {
panic(&ValueError{"MakeMap", typ.Kind()})
}
if n < 0 {
panic("reflect.MakeMapWithSize: negative size hint")
}
key := typ.Key().(*rawType)
val := typ.Elem().(*rawType)
var alg uint8
if key.Kind() == String {
alg = hashmapAlgorithmString
} else if key.isBinary() {
alg = hashmapAlgorithmBinary
} else {
panic("reflect.MakeMap: unimplemented key type")
}
m := hashmapMake(key.Size(), val.Size(), uintptr(n), alg)
return Value{
typecode: typ.(*rawType),
value: m,
flags: valueFlagExported,
}
}
// MakeMap creates a new map with the specified type.
func MakeMap(typ Type) Value {
panic("unimplemented: reflect.MakeMap()")
return MakeMapWithSize(typ, 8)
}
func (v Value) Call(in []Value) []Value {
+99
View File
@@ -2,6 +2,7 @@ package reflect_test
import (
. "reflect"
"sort"
"testing"
)
@@ -30,3 +31,101 @@ func TestIndirectPointers(t *testing.T) {
t.Errorf("bad indirect array index via reflect")
}
}
func TestMap(t *testing.T) {
m := make(map[string]int)
mtyp := TypeOf(m)
if got, want := mtyp.Key().Kind().String(), "string"; got != want {
t.Errorf("m.Type().Key().String()=%q, want %q", got, want)
}
if got, want := mtyp.Elem().Kind().String(), "int"; got != want {
t.Errorf("m.Elem().String()=%q, want %q", got, want)
}
m["foo"] = 2
mref := ValueOf(m)
two := mref.MapIndex(ValueOf("foo"))
if got, want := two.Interface().(int), 2; got != want {
t.Errorf("MapIndex(`foo`)=%v, want %v", got, want)
}
m["bar"] = 3
m["baz"] = 4
m["qux"] = 5
it := mref.MapRange()
var gotKeys []string
for it.Next() {
k := it.Key()
v := it.Value()
kstr := k.Interface().(string)
vint := v.Interface().(int)
gotKeys = append(gotKeys, kstr)
if m[kstr] != vint {
t.Errorf("m[%v]=%v, want %v", kstr, vint, m[kstr])
}
}
var wantKeys []string
for k := range m {
wantKeys = append(wantKeys, k)
}
sort.Strings(gotKeys)
sort.Strings(wantKeys)
if !equal(gotKeys, wantKeys) {
t.Errorf("MapRange return unexpected keys: got %v, want %v", gotKeys, wantKeys)
}
refMapKeys := mref.MapKeys()
gotKeys = gotKeys[:0]
for _, v := range refMapKeys {
gotKeys = append(gotKeys, v.Interface().(string))
}
sort.Strings(gotKeys)
if !equal(gotKeys, wantKeys) {
t.Errorf("MapKeys return unexpected keys: got %v, want %v", gotKeys, wantKeys)
}
mref.SetMapIndex(ValueOf("bar"), Value{})
if _, ok := m["bar"]; ok {
t.Errorf("SetMapIndex failed to delete `bar`")
}
mref.SetMapIndex(ValueOf("baz"), ValueOf(6))
if got, want := m["baz"], 6; got != want {
t.Errorf("SetMapIndex(bar, 6) got %v, want %v", got, want)
}
m2ref := MakeMap(mref.Type())
m2ref.SetMapIndex(ValueOf("foo"), ValueOf(2))
m2 := m2ref.Interface().(map[string]int)
if m2["foo"] != 2 {
t.Errorf("MakeMap failed to create map")
}
}
func equal[T comparable](a, b []T) bool {
if len(a) != len(b) {
return false
}
for i, aa := range a {
if b[i] != aa {
return false
}
}
return true
}
+6 -4
View File
@@ -4,7 +4,6 @@ _tinygo_scanCurrentStack:
#else
.section .text.tinygo_scanCurrentStack
.global tinygo_scanCurrentStack
.type tinygo_scanCurrentStack, %function
tinygo_scanCurrentStack:
#endif
// Sources:
@@ -12,12 +11,16 @@ tinygo_scanCurrentStack:
// * https://godbolt.org/z/qrvrEh
// Save callee-saved registers.
stp x29, x30, [sp, #-96]!
stp x29, x30, [sp, #-160]!
stp x28, x27, [sp, #16]
stp x26, x25, [sp, #32]
stp x24, x23, [sp, #48]
stp x22, x21, [sp, #64]
stp x20, x19, [sp, #80]
stp d8, d9, [sp, #96]
stp d10, d11, [sp, #112]
stp d12, d13, [sp, #128]
stp d14, d15, [sp, #144]
// Scan the stack.
mov x0, sp
@@ -28,7 +31,7 @@ tinygo_scanCurrentStack:
#endif
// Restore stack state and return.
ldp x29, x30, [sp], #96
ldp x29, x30, [sp], #160
ret
@@ -38,7 +41,6 @@ _tinygo_longjmp:
#else
.section .text.tinygo_longjmp
.global tinygo_longjmp
.type tinygo_longjmp, %function
tinygo_longjmp:
#endif
// Note: the code we jump to assumes x0 is set to a non-zero value if we
-36
View File
@@ -1,36 +0,0 @@
.section .text.tinygo_scanCurrentStack,"ax"
.global tinygo_scanCurrentStack
tinygo_scanCurrentStack:
// Sources:
// * https://learn.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=msvc-170
// * https://godbolt.org/z/foc1xncvb
// Save callee-saved registers.
stp x29, x30, [sp, #-160]!
stp x28, x27, [sp, #16]
stp x26, x25, [sp, #32]
stp x24, x23, [sp, #48]
stp x22, x21, [sp, #64]
stp x20, x19, [sp, #80]
stp d8, d9, [sp, #96]
stp d10, d11, [sp, #112]
stp d12, d13, [sp, #128]
stp d14, d15, [sp, #144]
// Scan the stack.
mov x0, sp
bl tinygo_scanstack
// Restore stack state and return.
ldp x29, x30, [sp], #160
ret
.section .text.tinygo_longjmp,"ax"
.global tinygo_longjmp
tinygo_longjmp:
// Note: the code we jump to assumes x0 is set to a non-zero value if we
// jump from here (which is conveniently already the case).
ldp x1, x2, [x0] // jumpSP, jumpPC
mov sp, x1
br x2
+8
View File
@@ -277,6 +277,10 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
size += align(unsafe.Sizeof(layout))
}
if interrupt.In() {
runtimePanic("alloc in interrupt")
}
gcTotalAlloc += uint64(size)
gcMallocs++
@@ -687,3 +691,7 @@ func ReadMemStats(m *MemStats) {
m.Frees = gcFrees
m.Sys = uint64(heapEnd - heapStart)
}
func SetFinalizer(obj interface{}, finalizer interface{}) {
// Unimplemented.
}
+4
View File
@@ -20,6 +20,7 @@ package runtime
// - func free(ptr unsafe.Pointer)
// - func markRoots(start, end uintptr)
// - func GC()
// - func SetFinalizer(obj interface{}, finalizer interface{})
// - func ReadMemStats(ms *runtime.MemStats)
//
//
@@ -51,6 +52,9 @@ func markRoots(start, end uintptr)
// GC is called to explicitly run garbage collection.
func GC()
// SetFinalizer registers a finalizer.
func SetFinalizer(obj interface{}, finalizer interface{})
// ReadMemStats populates m with memory statistics.
func ReadMemStats(ms *MemStats)
+4
View File
@@ -85,6 +85,10 @@ func GC() {
// No-op.
}
func SetFinalizer(obj interface{}, finalizer interface{}) {
// No-op.
}
func initHeap() {
// preinit() may have moved heapStart; reset heapptr
heapptr = heapStart
+4
View File
@@ -26,6 +26,10 @@ func GC() {
// Unimplemented.
}
func SetFinalizer(obj interface{}, finalizer interface{}) {
// Unimplemented.
}
func initHeap() {
// Nothing to initialize.
}
+48 -6
View File
@@ -49,6 +49,10 @@ type hashmapIterator struct {
bucketIndex uint8 // current index into bucket
}
func hashmapNewIterator() unsafe.Pointer {
return unsafe.Pointer(new(hashmapIterator))
}
// Get the topmost 8 bits of the hash, without using a special value (like 0).
func hashmapTopHash(hash uint32) uint8 {
tophash := uint8(hash >> 24)
@@ -84,6 +88,10 @@ func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) *hashm
}
}
func hashmapMakeUnsafePointer(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) unsafe.Pointer {
return (unsafe.Pointer)(hashmapMake(keySize, valueSize, sizeHint, alg))
}
func hashmapKeyEqualAlg(alg hashmapAlgorithm) func(x, y unsafe.Pointer, n uintptr) bool {
switch alg {
case hashmapAlgorithmBinary:
@@ -142,10 +150,8 @@ func hashmapLen(m *hashmap) int {
return int(m.count)
}
// wrapper for use in reflect
func hashmapLenUnsafePointer(p unsafe.Pointer) int {
m := (*hashmap)(p)
return hashmapLen(m)
func hashmapLenUnsafePointer(m unsafe.Pointer) int {
return hashmapLen((*hashmap)(m))
}
// Set a specified key to a given value. Grow the map if necessary.
@@ -208,6 +214,10 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
*emptySlotTophash = tophash
}
func hashmapSetUnsafePointer(m unsafe.Pointer, key unsafe.Pointer, value unsafe.Pointer, hash uint32) {
hashmapSet((*hashmap)(m), key, value, hash)
}
// hashmapInsertIntoNewBucket creates a new bucket, inserts the given key and
// value into the bucket, and returns a pointer to this bucket.
func hashmapInsertIntoNewBucket(m *hashmap, key, value unsafe.Pointer, tophash uint8) *hashmapBucket {
@@ -299,6 +309,10 @@ func hashmapGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr, hash u
return false
}
func hashmapGetUnsafePointer(m unsafe.Pointer, key, value unsafe.Pointer, valueSize uintptr, hash uint32) bool {
return hashmapGet((*hashmap)(m), key, value, valueSize, hash)
}
// Delete a given key from the map. No-op when the key does not exist in the
// map.
//
@@ -409,6 +423,10 @@ func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) boo
}
}
func hashmapNextUnsafePointer(m unsafe.Pointer, it unsafe.Pointer, key, value unsafe.Pointer) bool {
return hashmapNext((*hashmap)(m), (*hashmapIterator)(it), key, value)
}
// Hashmap with plain binary data keys (not containing strings etc.).
func hashmapBinarySet(m *hashmap, key, value unsafe.Pointer) {
if m == nil {
@@ -418,6 +436,10 @@ func hashmapBinarySet(m *hashmap, key, value unsafe.Pointer) {
hashmapSet(m, key, value, hash)
}
func hashmapBinarySetUnsafePointer(m unsafe.Pointer, key, value unsafe.Pointer) {
hashmapBinarySet((*hashmap)(m), key, value)
}
func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr) bool {
if m == nil {
memzero(value, uintptr(valueSize))
@@ -427,6 +449,10 @@ func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr)
return hashmapGet(m, key, value, valueSize, hash)
}
func hashmapBinaryGetUnsafePointer(m unsafe.Pointer, key, value unsafe.Pointer, valueSize uintptr) bool {
return hashmapBinaryGet((*hashmap)(m), key, value, valueSize)
}
func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) {
if m == nil {
return
@@ -435,6 +461,10 @@ func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) {
hashmapDelete(m, key, hash)
}
func hashmapBinaryDeleteUnsafePointer(m unsafe.Pointer, key unsafe.Pointer) {
hashmapBinaryDelete((*hashmap)(m), key)
}
// Hashmap with string keys (a common case).
func hashmapStringEqual(x, y unsafe.Pointer, n uintptr) bool {
@@ -459,6 +489,10 @@ func hashmapStringSet(m *hashmap, key string, value unsafe.Pointer) {
hashmapSet(m, unsafe.Pointer(&key), value, hash)
}
func hashmapStringSetUnsafePointer(m unsafe.Pointer, key string, value unsafe.Pointer) {
hashmapStringSet((*hashmap)(m), key, value)
}
func hashmapStringGet(m *hashmap, key string, value unsafe.Pointer, valueSize uintptr) bool {
if m == nil {
memzero(value, uintptr(valueSize))
@@ -468,6 +502,10 @@ func hashmapStringGet(m *hashmap, key string, value unsafe.Pointer, valueSize ui
return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash)
}
func hashmapStringGetUnsafePointer(m unsafe.Pointer, key string, value unsafe.Pointer, valueSize uintptr) bool {
return hashmapStringGet((*hashmap)(m), key, value, valueSize)
}
func hashmapStringDelete(m *hashmap, key string) {
if m == nil {
return
@@ -476,6 +514,10 @@ func hashmapStringDelete(m *hashmap, key string) {
hashmapDelete(m, unsafe.Pointer(&key), hash)
}
func hashmapStringDeleteUnsafePointer(m unsafe.Pointer, key string) {
hashmapStringDelete((*hashmap)(m), key)
}
// Hashmap with interface keys (for everything else).
// This is a method that is intentionally unexported in the reflect package. It
@@ -506,7 +548,7 @@ func hashmapFloat64Hash(ptr unsafe.Pointer, seed uintptr) uint32 {
func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 {
x := reflect.ValueOf(itf)
if x.RawType() == 0 {
if x.RawType() == nil {
return 0 // nil interface
}
@@ -563,7 +605,7 @@ func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 {
}
func hashmapInterfacePtrHash(iptr unsafe.Pointer, size uintptr, seed uintptr) uint32 {
_i := *(*_interface)(iptr)
_i := *(*interface{})(iptr)
return hashmapInterfaceHash(_i, seed)
}
+7 -42
View File
@@ -11,17 +11,17 @@ import (
)
type _interface struct {
typecode uintptr
typecode unsafe.Pointer
value unsafe.Pointer
}
//go:inline
func composeInterface(typecode uintptr, value unsafe.Pointer) _interface {
func composeInterface(typecode, value unsafe.Pointer) _interface {
return _interface{typecode, value}
}
//go:inline
func decomposeInterface(i _interface) (uintptr, unsafe.Pointer) {
func decomposeInterface(i _interface) (unsafe.Pointer, unsafe.Pointer) {
return i.typecode, i.value
}
@@ -34,7 +34,7 @@ func reflectValueEqual(x, y reflect.Value) bool {
// Note: doing a x.Type() == y.Type() comparison would not work here as that
// would introduce an infinite recursion: comparing two reflect.Type values
// is done with this reflectValueEqual runtime call.
if x.RawType() == 0 || y.RawType() == 0 {
if x.RawType() == nil || y.RawType() == nil {
// One of them is nil.
return x.RawType() == y.RawType()
}
@@ -94,48 +94,13 @@ func interfaceTypeAssert(ok bool) {
// lowered to inline IR in the interface lowering pass.
// See compiler/interface-lowering.go for details.
type interfaceMethodInfo struct {
signature *uint8 // external *i8 with a name identifying the Go function signature
funcptr uintptr // bitcast from the actual function pointer
}
type typecodeID struct {
// Depending on the type kind of this typecodeID, this pointer is something
// different:
// * basic types: null
// * named type: the underlying type
// * interface: null
// * chan/pointer/slice/array: the element type
// * struct: bitcast of global with structField array
// * func/map: TODO
references *typecodeID
// The array length, for array types.
length uintptr
methodSet *interfaceMethodInfo // nil or a GEP of an array
// The type that's a pointer to this type, nil if it is already a pointer.
// Keeping the type struct alive here is important so that values from
// reflect.New (which uses reflect.PtrTo) can be used in type asserts etc.
ptrTo *typecodeID
// typeAssert is a ptrtoint of a declared interface assert function.
// It only exists to make the rtcalls pass easier.
typeAssert uintptr
}
// structField is used by the compiler to pass information to the interface
// lowering pass. It is not used in the final binary.
type structField struct {
typecode *typecodeID // type of this struct field
name *uint8 // pointer to char array
tag *uint8 // pointer to char array, or nil
embedded bool
typecode unsafe.Pointer // type of this struct field
data *uint8 // pointer to byte array containing name, tag, and 'embedded' flag
}
// Pseudo function call used during a type assert. It is used during interface
// lowering, to assign the lowest type numbers to the types with the most type
// asserts. Also, it is replaced with const false if this type assert can never
// happen.
func typeAssert(actualType uintptr, assertedType *uint8) bool
func typeAssert(actualType unsafe.Pointer, assertedType *uint8) bool
+9
View File
@@ -34,3 +34,12 @@ func Restore(state State) {
"state": state,
})
}
// In returns whether the system is currently in an interrupt.
//
// Warning: this always returns false on AVR, as there does not appear to be a
// reliable way to determine whether we're currently running inside an interrupt
// handler.
func In() bool {
return false
}
@@ -50,3 +50,13 @@ func Disable() (state State) {
func Restore(state State) {
arm.EnableInterrupts(uintptr(state))
}
// In returns whether the system is currently in an interrupt.
func In() bool {
// The VECTACTIVE field gives the instruction vector that is currently
// active (in handler mode), or 0 if not in an interrupt.
// Documentation:
// https://developer.arm.com/documentation/dui0497/a/cortex-m0-peripherals/system-control-block/interrupt-control-and-state-register
vectactive := uint8(arm.SCB.ICSR.Get())
return vectactive != 0
}
@@ -2,6 +2,8 @@
package interrupt
// This is good documentation of the GBA: https://www.akkit.org/info/gbatek.htm
import (
"runtime/volatile"
"unsafe"
@@ -36,8 +38,11 @@ func (irq Interrupt) Enable() {
regInterruptEnable.SetBits(1 << uint(irq.num))
}
var inInterrupt bool
//export handleInterrupt
func handleInterrupt() {
inInterrupt = true
flags := regInterruptRequestFlags.Get()
for i := 0; i < 14; i++ {
if flags&(1<<uint(i)) != 0 {
@@ -45,6 +50,7 @@ func handleInterrupt() {
callInterruptHandler(i)
}
}
inInterrupt = false
}
// Pseudo function call that is replaced by the compiler with the actual
@@ -115,3 +121,8 @@ func Restore(state State) {
// Restore interrupts to the previous state.
regGlobalInterruptEnable.Set(uint16(state))
}
// In returns whether the system is currently in an interrupt.
func In() bool {
return inInterrupt
}
+6
View File
@@ -23,3 +23,9 @@ func Disable() (state State) {
// calling Disable, this will not re-enable interrupts, allowing for nested
// cricital sections.
func Restore(state State) {}
// In returns whether the system is currently in an interrupt.
func In() bool {
// There are no interrupts, so it can't be in one.
return false
}
@@ -27,3 +27,12 @@ func Disable() (state State) {
func Restore(state State) {
riscv.EnableInterrupts(uintptr(state))
}
// In returns whether the system is currently in an interrupt.
func In() bool {
// There is one exception that has the value 0 (instruction address
// misaligned), but it's not very likely and even if it happens, it's not
// really something that can be recovered from. Therefore I think it's safe
// to ignore it. It's handled specially (in handleException).
return riscv.MCAUSE.Get() != 0
}
@@ -29,3 +29,11 @@ func Restore(state State) {
"state": state,
})
}
// In returns whether the system is currently in an interrupt.
//
// Warning: interrupts have not been implemented for Xtensa yet so this always
// returns false.
func In() bool {
return false
}
+3 -7
View File
@@ -88,13 +88,9 @@ func LockOSThread() {
func UnlockOSThread() {
}
func KeepAlive(x interface{}) {
// Unimplemented. Only required with SetFinalizer().
}
func SetFinalizer(obj interface{}, finalizer interface{}) {
// Unimplemented.
}
// KeepAlive makes sure the value in the interface is alive until at least the
// point of the call.
func KeepAlive(x interface{})
var godebugUpdate func(string, string)
+5
View File
@@ -27,6 +27,7 @@ func init() {
initSERCOMClocks()
initUSBClock()
initADCClock()
enableCache()
cdc.EnableUSBCDC()
machine.USBDev.Configure(machine.UARTConfig{})
@@ -367,6 +368,10 @@ func initADCClock() {
sam.GCLK_PCHCTRL_CHEN)
}
func enableCache() {
sam.CMCC.CTRL.SetBits(sam.CMCC_CTRL_CEN)
}
func waitForEvents() {
arm.Asm("wfe")
}
+10
View File
@@ -25,6 +25,12 @@ func main() {
// Zero the threshold value to allow all priorities of interrupts.
sifive.PLIC.THRESHOLD.Set(0)
// Zero MCAUSE, which is set to the reset reason on reset. It must be zeroed
// to make interrupt.In() work.
// This would also be a good time to save the reset reason, but that hasn't
// been implemented yet.
riscv.MCAUSE.Set(0)
// Set the interrupt address.
// Note that this address must be aligned specially, otherwise the MODE bits
// of MTVEC won't be zero.
@@ -73,6 +79,10 @@ func handleInterrupt() {
// misaligned loads). However, for now we'll just print a fatal error.
handleException(code)
}
// Zero MCAUSE so that it can later be used to see whether we're in an
// interrupt or not.
riscv.MCAUSE.Set(0)
}
// initPeripherals configures periperhals the way the runtime expects them.
+10
View File
@@ -31,6 +31,12 @@ func main() {
kendryte.PLIC.PRIORITY[i].Set(0)
}
// Zero MCAUSE, which is set to the reset reason on reset. It must be zeroed
// to make interrupt.In() work.
// This would also be a good time to save the reset reason, but that hasn't
// been implemented yet.
riscv.MCAUSE.Set(0)
// Set the interrupt address.
// Note that this address must be aligned specially, otherwise the MODE bits
// of MTVEC won't be zero.
@@ -93,6 +99,10 @@ func handleInterrupt() {
// misaligned loads). However, for now we'll just print a fatal error.
handleException(code)
}
// Zero MCAUSE so that it can later be used to see whether we're in an
// interrupt or not.
riscv.MCAUSE.Set(0)
}
// initPeripherals configures periperhals the way the runtime expects them.
+4
View File
@@ -69,3 +69,7 @@ _heap_start = _ebss;
_heap_end = ORIGIN(RAM) + LENGTH(RAM);
_globals_start = _sdata;
_globals_end = _ebss;
/* For the flash API */
__flash_data_start = LOADADDR(.data) + SIZEOF(.data);
__flash_data_end = ORIGIN(FLASH_TEXT) + LENGTH(FLASH_TEXT);
+10
View File
@@ -1,5 +1,7 @@
package main
import "runtime"
var xorshift32State uint32 = 1
func xorshift32(x uint32) uint32 {
@@ -17,6 +19,7 @@ func randuint32() uint32 {
func main() {
testNonPointerHeap()
testKeepAlive()
}
var scalarSlices [4][]byte
@@ -64,3 +67,10 @@ func testNonPointerHeap() {
}
println("ok")
}
func testKeepAlive() {
// There isn't much we can test, but at least we can test that
// runtime.KeepAlive compiles correctly.
var x int
runtime.KeepAlive(&x)
}
+34
View File
@@ -129,6 +129,8 @@ func main() {
floatcmplx()
mapgrow()
interfacerehash()
}
func floatcmplx() {
@@ -274,3 +276,35 @@ func mapgrow() {
}
println("done")
}
type Counter interface {
count() int
}
type counter struct {
i int
}
func (c *counter) count() int {
return c.i
}
func interfacerehash() {
m := make(map[Counter]int)
for i := 0; i < 20; i++ {
c := &counter{i}
m[c] = i
}
var failures int
for k, v := range m {
if got := m[k]; got != v {
println("lookup failure got", got, "want", v)
failures++
}
}
if failures == 0 {
println("no interface lookup failures")
}
}
+1
View File
@@ -80,3 +80,4 @@ tested growing of a map
2
2
done
no interface lookup failures
+4 -3
View File
@@ -3,6 +3,7 @@ package main
import (
"errors"
"reflect"
"strconv"
"unsafe"
)
@@ -17,8 +18,8 @@ type (
Y int16
}
mystruct struct {
n int `foo:"bar"`
some point
n int `foo:"bar"`
some point "some\x00tag"
zero struct{}
buf []byte
Buf []byte
@@ -480,7 +481,7 @@ func showValue(rv reflect.Value, indent string) {
for i := 0; i < rv.NumField(); i++ {
field := rt.Field(i)
println(indent+" field:", i, field.Name)
println(indent+" tag:", field.Tag)
println(indent+" tag:", strconv.Quote(string(field.Tag)))
println(indent+" embedded:", field.Anonymous)
println(indent+" exported:", field.IsExported())
showValue(rv.Field(i), indent+" ")
+15 -15
View File
@@ -233,7 +233,7 @@ reflect type: struct
reflect type: struct
struct: 1
field: 0 error
tag:
tag: ""
embedded: true
exported: false
reflect type: interface caninterface=false
@@ -242,19 +242,19 @@ reflect type: struct
reflect type: struct
struct: 3
field: 0 a
tag:
tag: ""
embedded: false
exported: false
reflect type: uint8 caninterface=false
uint: 42
field: 1 b
tag:
tag: ""
embedded: false
exported: false
reflect type: int16 caninterface=false
int: 321
field: 2 c
tag:
tag: ""
embedded: false
exported: false
reflect type: int8 caninterface=false
@@ -262,37 +262,37 @@ reflect type: struct
reflect type: struct comparable=false
struct: 5
field: 0 n
tag: foo:"bar"
tag: "foo:\"bar\""
embedded: false
exported: false
reflect type: int caninterface=false
int: 5
field: 1 some
tag:
tag: "some\x00tag"
embedded: false
exported: false
reflect type: struct caninterface=false
struct: 2
field: 0 X
tag:
tag: ""
embedded: false
exported: true
reflect type: int16 caninterface=false
int: -5
field: 1 Y
tag:
tag: ""
embedded: false
exported: true
reflect type: int16 caninterface=false
int: 3
field: 2 zero
tag:
tag: ""
embedded: false
exported: false
reflect type: struct caninterface=false
struct: 0
field: 3 buf
tag:
tag: ""
embedded: false
exported: false
reflect type: slice caninterface=false comparable=false
@@ -306,7 +306,7 @@ reflect type: struct comparable=false
reflect type: uint8 addrable=true caninterface=false
uint: 111
field: 4 Buf
tag:
tag: ""
embedded: false
exported: true
reflect type: slice comparable=false
@@ -322,14 +322,14 @@ reflect type: ptr
reflect type: struct settable=true addrable=true
struct: 2
field: 0 next
tag: description:"chain"
tag: "description:\"chain\""
embedded: false
exported: false
reflect type: ptr addrable=true caninterface=false
pointer: false struct
nil: true
field: 1 foo
tag:
tag: ""
embedded: false
exported: false
reflect type: int addrable=true caninterface=false
@@ -337,13 +337,13 @@ reflect type: ptr
reflect type: struct
struct: 2
field: 0 A
tag:
tag: ""
embedded: false
exported: true
reflect type: uintptr
uint: 2
field: 1 B
tag:
tag: ""
embedded: false
exported: true
reflect type: uintptr
+161 -139
View File
@@ -90,9 +90,10 @@ type SVDCluster struct {
}
type Device struct {
Metadata *Metadata
Interrupts []*Interrupt
Peripherals []*Peripheral
Metadata *Metadata
Interrupts []*Interrupt
Peripherals []*Peripheral
PeripheralDict map[string]*Peripheral
}
type Metadata struct {
@@ -191,6 +192,142 @@ func cleanName(text string) string {
return text
}
func processSubCluster(p *Peripheral, cluster *SVDCluster, clusterOffset uint64, clusterName string, peripheralDict map[string]*Peripheral) []*Peripheral {
var peripheralsList []*Peripheral
clusterPrefix := clusterName + "_"
cpRegisters := []*PeripheralField{}
for _, regEl := range cluster.Registers {
cpRegisters = append(cpRegisters, parseRegister(p.GroupName, regEl, p.BaseAddress+clusterOffset, clusterPrefix)...)
}
// handle sub-clusters of registers
for _, subClusterEl := range cluster.Clusters {
subclusterName := strings.ReplaceAll(subClusterEl.Name, "[%s]", "")
subclusterPrefix := subclusterName + "_"
subclusterOffset, err := strconv.ParseUint(subClusterEl.AddressOffset, 0, 32)
if err != nil {
panic(err)
}
subdim := *subClusterEl.Dim
subdimIncrement, err := strconv.ParseInt(subClusterEl.DimIncrement, 0, 32)
if err != nil {
panic(err)
}
if subdim > 1 {
subcpRegisters := []*PeripheralField{}
for _, regEl := range subClusterEl.Registers {
subcpRegisters = append(subcpRegisters, parseRegister(p.GroupName, regEl, p.BaseAddress+clusterOffset+subclusterOffset, subclusterPrefix)...)
}
cpRegisters = append(cpRegisters, &PeripheralField{
Name: subclusterName,
Address: p.BaseAddress + clusterOffset + subclusterOffset,
Description: subClusterEl.Description,
Registers: subcpRegisters,
Array: subdim,
ElementSize: int(subdimIncrement),
ShortName: clusterPrefix + subclusterName,
})
} else {
for _, regEl := range subClusterEl.Registers {
cpRegisters = append(cpRegisters, parseRegister(regEl.Name, regEl, p.BaseAddress+clusterOffset+subclusterOffset, subclusterPrefix)...)
}
}
}
sort.SliceStable(cpRegisters, func(i, j int) bool {
return cpRegisters[i].Address < cpRegisters[j].Address
})
clusterPeripheral := &Peripheral{
Name: p.Name + "_" + clusterName,
GroupName: p.GroupName + "_" + clusterName,
Description: p.Description + " - " + clusterName,
ClusterName: clusterName,
BaseAddress: p.BaseAddress + clusterOffset,
Registers: cpRegisters,
}
peripheralsList = append(peripheralsList, clusterPeripheral)
peripheralDict[clusterPeripheral.Name] = clusterPeripheral
p.Subtypes = append(p.Subtypes, clusterPeripheral)
return peripheralsList
}
func processCluster(p *Peripheral, clusters []*SVDCluster, peripheralDict map[string]*Peripheral) []*Peripheral {
var peripheralsList []*Peripheral
for _, cluster := range clusters {
clusterName := strings.ReplaceAll(cluster.Name, "[%s]", "")
if cluster.DimIndex != nil {
clusterName = strings.ReplaceAll(clusterName, "%s", "")
}
clusterPrefix := clusterName + "_"
clusterOffset, err := strconv.ParseUint(cluster.AddressOffset, 0, 32)
if err != nil {
panic(err)
}
var dim, dimIncrement int
if cluster.Dim == nil {
// Nordic SVD have sub-clusters with another sub-clusters.
if clusterOffset == 0 || len(cluster.Clusters) > 0 {
peripheralsList = append(peripheralsList, processSubCluster(p, cluster, clusterOffset, clusterName, peripheralDict)...)
continue
}
dim = -1
dimIncrement = -1
} else {
dim = *cluster.Dim
if dim == 1 {
dimIncrement = -1
} else {
inc, err := strconv.ParseUint(cluster.DimIncrement, 0, 32)
if err != nil {
panic(err)
}
dimIncrement = int(inc)
}
}
clusterRegisters := []*PeripheralField{}
for _, regEl := range cluster.Registers {
regName := p.GroupName
if regName == "" {
regName = p.Name
}
clusterRegisters = append(clusterRegisters, parseRegister(regName, regEl, p.BaseAddress+clusterOffset, clusterPrefix)...)
}
sort.SliceStable(clusterRegisters, func(i, j int) bool {
return clusterRegisters[i].Address < clusterRegisters[j].Address
})
if dimIncrement == -1 && len(clusterRegisters) > 0 {
lastReg := clusterRegisters[len(clusterRegisters)-1]
lastAddress := lastReg.Address
if lastReg.Array != -1 {
lastAddress = lastReg.Address + uint64(lastReg.Array*lastReg.ElementSize)
}
firstAddress := clusterRegisters[0].Address
dimIncrement = int(lastAddress - firstAddress)
}
if !unicode.IsUpper(rune(clusterName[0])) && !unicode.IsDigit(rune(clusterName[0])) {
clusterName = strings.ToUpper(clusterName)
}
p.Registers = append(p.Registers, &PeripheralField{
Name: clusterName,
Address: p.BaseAddress + clusterOffset,
Description: cluster.Description,
Registers: clusterRegisters,
Array: dim,
ElementSize: dimIncrement,
ShortName: clusterName,
})
}
sort.SliceStable(p.Registers, func(i, j int) bool {
return p.Registers[i].Address < p.Registers[j].Address
})
return peripheralsList
}
// Read ARM SVD files.
func readSVD(path, sourceURL string) (*Device, error) {
// Open the XML file.
@@ -293,133 +430,7 @@ func readSVD(path, sourceURL string) (*Device, error) {
}
p.Registers = append(p.Registers, parseRegister(regName, register, baseAddress, "")...)
}
for _, cluster := range periphEl.Clusters {
clusterName := strings.ReplaceAll(cluster.Name, "[%s]", "")
if cluster.DimIndex != nil {
clusterName = strings.ReplaceAll(clusterName, "%s", "")
}
clusterPrefix := clusterName + "_"
clusterOffset, err := strconv.ParseUint(cluster.AddressOffset, 0, 32)
if err != nil {
panic(err)
}
var dim, dimIncrement int
if cluster.Dim == nil {
if clusterOffset == 0 {
// make this a separate peripheral
cpRegisters := []*PeripheralField{}
for _, regEl := range cluster.Registers {
cpRegisters = append(cpRegisters, parseRegister(groupName, regEl, baseAddress, clusterName+"_")...)
}
// handle sub-clusters of registers
for _, subClusterEl := range cluster.Clusters {
subclusterName := strings.ReplaceAll(subClusterEl.Name, "[%s]", "")
subclusterPrefix := subclusterName + "_"
subclusterOffset, err := strconv.ParseUint(subClusterEl.AddressOffset, 0, 32)
if err != nil {
panic(err)
}
subdim := *subClusterEl.Dim
subdimIncrement, err := strconv.ParseInt(subClusterEl.DimIncrement, 0, 32)
if err != nil {
panic(err)
}
if subdim > 1 {
subcpRegisters := []*PeripheralField{}
subregSize := 0
for _, regEl := range subClusterEl.Registers {
size, err := strconv.ParseInt(*regEl.Size, 0, 32)
if err != nil {
panic(err)
}
subregSize += int(size)
subcpRegisters = append(subcpRegisters, parseRegister(groupName, regEl, baseAddress+subclusterOffset, subclusterPrefix)...)
}
cpRegisters = append(cpRegisters, &PeripheralField{
Name: subclusterName,
Address: baseAddress + subclusterOffset,
Description: subClusterEl.Description,
Registers: subcpRegisters,
Array: subdim,
ElementSize: int(subdimIncrement),
ShortName: clusterPrefix + subclusterName,
})
} else {
for _, regEl := range subClusterEl.Registers {
cpRegisters = append(cpRegisters, parseRegister(regEl.Name, regEl, baseAddress+subclusterOffset, subclusterPrefix)...)
}
}
}
sort.SliceStable(cpRegisters, func(i, j int) bool {
return cpRegisters[i].Address < cpRegisters[j].Address
})
clusterPeripheral := &Peripheral{
Name: periphEl.Name + "_" + clusterName,
GroupName: groupName + "_" + clusterName,
Description: description + " - " + clusterName,
ClusterName: clusterName,
BaseAddress: baseAddress,
Registers: cpRegisters,
}
peripheralsList = append(peripheralsList, clusterPeripheral)
peripheralDict[clusterPeripheral.Name] = clusterPeripheral
p.Subtypes = append(p.Subtypes, clusterPeripheral)
continue
}
dim = -1
dimIncrement = -1
} else {
dim = *cluster.Dim
if dim == 1 {
dimIncrement = -1
} else {
inc, err := strconv.ParseUint(cluster.DimIncrement, 0, 32)
if err != nil {
panic(err)
}
dimIncrement = int(inc)
}
}
clusterRegisters := []*PeripheralField{}
for _, regEl := range cluster.Registers {
regName := groupName
if regName == "" {
regName = periphEl.Name
}
clusterRegisters = append(clusterRegisters, parseRegister(regName, regEl, baseAddress+clusterOffset, clusterPrefix)...)
}
sort.SliceStable(clusterRegisters, func(i, j int) bool {
return clusterRegisters[i].Address < clusterRegisters[j].Address
})
if dimIncrement == -1 && len(clusterRegisters) > 0 {
lastReg := clusterRegisters[len(clusterRegisters)-1]
lastAddress := lastReg.Address
if lastReg.Array != -1 {
lastAddress = lastReg.Address + uint64(lastReg.Array*lastReg.ElementSize)
}
firstAddress := clusterRegisters[0].Address
dimIncrement = int(lastAddress - firstAddress)
}
if !unicode.IsUpper(rune(clusterName[0])) && !unicode.IsDigit(rune(clusterName[0])) {
clusterName = strings.ToUpper(clusterName)
}
p.Registers = append(p.Registers, &PeripheralField{
Name: clusterName,
Address: baseAddress + clusterOffset,
Description: cluster.Description,
Registers: clusterRegisters,
Array: dim,
ElementSize: dimIncrement,
ShortName: clusterName,
})
}
sort.SliceStable(p.Registers, func(i, j int) bool {
return p.Registers[i].Address < p.Registers[j].Address
})
peripheralsList = append(peripheralsList, processCluster(p, periphEl.Clusters, peripheralDict)...)
}
// Make a sorted list of interrupts.
@@ -459,9 +470,10 @@ func readSVD(path, sourceURL string) (*Device, error) {
metadata.NVICPrioBits = device.CPU.NVICPrioBits
}
return &Device{
Metadata: metadata,
Interrupts: interruptList,
Peripherals: peripheralsList,
Metadata: metadata,
Interrupts: interruptList,
Peripherals: peripheralsList,
PeripheralDict: peripheralDict,
}, nil
}
@@ -979,10 +991,11 @@ var (
address := peripheral.BaseAddress
type clusterInfo struct {
name string
address uint64
size uint64
registers []*PeripheralField
name string
description string
address uint64
size uint64
registers []*PeripheralField
}
clusters := []clusterInfo{}
for _, register := range peripheral.Registers {
@@ -1024,7 +1037,7 @@ var (
if register.Registers != nil {
// This is a cluster, not a register. Create the cluster type.
regType = peripheral.GroupName + "_" + register.Name
clusters = append(clusters, clusterInfo{regType, register.Address, uint64(register.ElementSize), register.Registers})
clusters = append(clusters, clusterInfo{regType, register.Description, register.Address, uint64(register.ElementSize), register.Registers})
regType = regType + "_Type"
subaddress := register.Address
for _, subregister := range register.Registers {
@@ -1075,7 +1088,16 @@ var (
continue
}
if _, ok := device.PeripheralDict[cluster.name]; ok {
continue
}
fmt.Fprintln(w)
if cluster.description != "" {
for _, l := range splitLine(cluster.description) {
fmt.Fprintf(w, "// %s\n", l)
}
}
fmt.Fprintf(w, "type %s_Type struct {\n", cluster.name)
address := cluster.address
@@ -1116,7 +1138,7 @@ var (
if register.Registers != nil {
// This is a cluster, not a register. Create the cluster type.
regType = peripheral.GroupName + "_" + register.Name
clusters = append(clusters, clusterInfo{regType, register.Address, uint64(register.ElementSize), register.Registers})
clusters = append(clusters, clusterInfo{regType, register.Description, register.Address, uint64(register.ElementSize), register.Registers})
regType = regType + "_Type"
subaddress := register.Address
-20
View File
@@ -1,20 +0,0 @@
package transform
import "tinygo.org/x/go-llvm"
// This file implements small transformations on globals (functions and global
// variables) for specific ABIs/architectures.
// ApplyFunctionSections puts every function in a separate section. This makes
// it possible for the linker to remove dead code. It is the equivalent of
// passing -ffunction-sections to a C compiler.
func ApplyFunctionSections(mod llvm.Module) {
llvmFn := mod.FirstFunction()
for !llvmFn.IsNil() {
if !llvmFn.IsDeclaration() && llvmFn.Section() == "" {
name := llvmFn.Name()
llvmFn.SetSection(".text." + name)
}
llvmFn = llvm.NextFunction(llvmFn)
}
}
-15
View File
@@ -1,15 +0,0 @@
package transform_test
import (
"testing"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
)
func TestApplyFunctionSections(t *testing.T) {
t.Parallel()
testTransform(t, "testdata/globals-function-sections", func(mod llvm.Module) {
transform.ApplyFunctionSections(mod)
})
}
+84 -34
View File
@@ -13,8 +13,7 @@ package transform
//
// typeAssert:
// Replaced with an icmp instruction so it can be directly used in a type
// switch. This is very easy to optimize for LLVM: it will often translate a
// type switch into a regular switch statement.
// switch.
//
// interface type assert:
// These functions are defined by creating a big type switch over all the
@@ -54,10 +53,11 @@ type methodInfo struct {
// typeInfo describes a single concrete Go type, which can be a basic or a named
// type. If it is a named type, it may have methods.
type typeInfo struct {
name string
typecode llvm.Value
methodSet llvm.Value
methods []*methodInfo
name string
typecode llvm.Value
typecodeGEP llvm.Value
methodSet llvm.Value
methods []*methodInfo
}
// getMethod looks up the method on this type with the given signature and
@@ -91,6 +91,8 @@ type lowerInterfacesPass struct {
difiles map[string]llvm.Metadata
ctx llvm.Context
uintptrType llvm.Type
targetData llvm.TargetData
i8ptrType llvm.Type
types map[string]*typeInfo
signatures map[string]*signatureInfo
interfaces map[string]*interfaceInfo
@@ -101,14 +103,17 @@ type lowerInterfacesPass struct {
// before LLVM can work on them. This is done so that a few cleanup passes can
// run before assigning the final type codes.
func LowerInterfaces(mod llvm.Module, config *compileopts.Config) error {
ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout())
defer targetData.Dispose()
p := &lowerInterfacesPass{
mod: mod,
config: config,
builder: mod.Context().NewBuilder(),
ctx: mod.Context(),
builder: ctx.NewBuilder(),
ctx: ctx,
targetData: targetData,
uintptrType: mod.Context().IntType(targetData.PointerSize() * 8),
i8ptrType: llvm.PointerType(ctx.Int8Type(), 0),
types: make(map[string]*typeInfo),
signatures: make(map[string]*signatureInfo),
interfaces: make(map[string]*interfaceInfo),
@@ -151,11 +156,26 @@ func (p *lowerInterfacesPass) run() error {
}
p.types[name] = t
initializer := global.Initializer()
if initializer.IsNil() {
continue
firstField := p.builder.CreateExtractValue(initializer, 0, "")
if firstField.Type() != p.ctx.Int8Type() {
// This type has a method set at index 0. Change the GEP to
// point to index 1 (the meta byte).
t.typecodeGEP = llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{
llvm.ConstInt(p.ctx.Int32Type(), 0, false),
llvm.ConstInt(p.ctx.Int32Type(), 1, false),
})
methodSet := stripPointerCasts(firstField)
if !strings.HasSuffix(methodSet.Name(), "$methodset") {
panic("expected method set")
}
p.addTypeMethods(t, methodSet)
} else {
// This type has no method set.
t.typecodeGEP = llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{
llvm.ConstInt(p.ctx.Int32Type(), 0, false),
llvm.ConstInt(p.ctx.Int32Type(), 0, false),
})
}
methodSet := p.builder.CreateExtractValue(initializer, 2, "")
p.addTypeMethods(t, methodSet)
}
}
}
@@ -266,10 +286,10 @@ func (p *lowerInterfacesPass) run() error {
actualType := use.Operand(0)
name := strings.TrimPrefix(use.Operand(1).Name(), "reflect/types.typeid:")
if t, ok := p.types[name]; ok {
// The type exists in the program, so lower to a regular integer
// The type exists in the program, so lower to a regular pointer
// comparison.
p.builder.SetInsertPointBefore(use)
commaOk := p.builder.CreateICmp(llvm.IntEQ, llvm.ConstPtrToInt(t.typecode, p.uintptrType), actualType, "typeassert.ok")
commaOk := p.builder.CreateICmp(llvm.IntEQ, t.typecodeGEP, actualType, "typeassert.ok")
use.ReplaceAllUsesWith(commaOk)
} else {
// The type does not exist in the program, so lower to a constant
@@ -283,15 +303,45 @@ func (p *lowerInterfacesPass) run() error {
}
// Remove all method sets, which are now unnecessary and inhibit later
// optimizations if they are left in place. Also remove references to the
// interface type assert functions just to be sure.
zeroUintptr := llvm.ConstNull(p.uintptrType)
// optimizations if they are left in place.
zero := llvm.ConstInt(p.ctx.Int32Type(), 0, false)
for _, t := range p.types {
initializer := t.typecode.Initializer()
methodSet := p.builder.CreateExtractValue(initializer, 2, "")
initializer = p.builder.CreateInsertValue(initializer, llvm.ConstNull(methodSet.Type()), 2, "")
initializer = p.builder.CreateInsertValue(initializer, zeroUintptr, 4, "")
t.typecode.SetInitializer(initializer)
if !t.methodSet.IsNil() {
initializer := t.typecode.Initializer()
var newInitializerFields []llvm.Value
for i := 1; i < initializer.Type().StructElementTypesCount(); i++ {
newInitializerFields = append(newInitializerFields, p.builder.CreateExtractValue(initializer, i, ""))
}
newInitializer := p.ctx.ConstStruct(newInitializerFields, false)
typecodeName := t.typecode.Name()
newGlobal := llvm.AddGlobal(p.mod, newInitializer.Type(), typecodeName+".tmp")
newGlobal.SetInitializer(newInitializer)
newGlobal.SetLinkage(t.typecode.Linkage())
newGlobal.SetGlobalConstant(true)
newGlobal.SetAlignment(t.typecode.Alignment())
for _, use := range getUses(t.typecode) {
if !use.IsAConstantExpr().IsNil() {
opcode := use.Opcode()
if opcode == llvm.GetElementPtr && use.OperandsCount() == 3 {
if use.Operand(1).ZExtValue() == 0 && use.Operand(2).ZExtValue() == 1 {
gep := p.builder.CreateInBoundsGEP(newGlobal.GlobalValueType(), newGlobal, []llvm.Value{zero, zero}, "")
use.ReplaceAllUsesWith(gep)
}
}
}
}
// Fallback.
if hasUses(t.typecode) {
bitcast := llvm.ConstBitCast(newGlobal, p.i8ptrType)
negativeOffset := -int64(p.targetData.TypeAllocSize(p.i8ptrType))
gep := p.builder.CreateInBoundsGEP(p.ctx.Int8Type(), bitcast, []llvm.Value{llvm.ConstInt(p.ctx.Int32Type(), uint64(negativeOffset), true)}, "")
bitcast2 := llvm.ConstBitCast(gep, t.typecode.Type())
t.typecode.ReplaceAllUsesWith(bitcast2)
}
t.typecode.EraseFromParentAsGlobal()
newGlobal.SetName(typecodeName)
t.typecode = newGlobal
}
}
return nil
@@ -301,22 +351,22 @@ func (p *lowerInterfacesPass) run() error {
// retrieves the signatures and the references to the method functions
// themselves for later type<->interface matching.
func (p *lowerInterfacesPass) addTypeMethods(t *typeInfo, methodSet llvm.Value) {
if !t.methodSet.IsNil() || methodSet.IsNull() {
if !t.methodSet.IsNil() {
// no methods or methods already read
return
}
if !methodSet.IsAConstantExpr().IsNil() && methodSet.Opcode() == llvm.GetElementPtr {
methodSet = methodSet.Operand(0) // get global from GEP, for LLVM 14 (non-opaque pointers)
}
// This type has methods, collect all methods of this type.
t.methodSet = methodSet
set := methodSet.Initializer() // get value from global
for i := 0; i < set.Type().ArrayLength(); i++ {
methodData := p.builder.CreateExtractValue(set, i, "")
signatureGlobal := p.builder.CreateExtractValue(methodData, 0, "")
signatures := p.builder.CreateExtractValue(set, 1, "")
wrappers := p.builder.CreateExtractValue(set, 2, "")
numMethods := signatures.Type().ArrayLength()
for i := 0; i < numMethods; i++ {
signatureGlobal := p.builder.CreateExtractValue(signatures, i, "")
function := p.builder.CreateExtractValue(wrappers, i, "")
function = stripPointerCasts(function) // strip bitcasts
signatureName := signatureGlobal.Name()
function := p.builder.CreateExtractValue(methodData, 1, "").Operand(0)
signature := p.getSignature(signatureName)
method := &methodInfo{
function: function,
@@ -401,7 +451,7 @@ func (p *lowerInterfacesPass) defineInterfaceImplementsFunc(fn llvm.Value, itf *
actualType := fn.Param(0)
for _, typ := range itf.types {
nextBlock := p.ctx.AddBasicBlock(fn, typ.name+".next")
cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, llvm.ConstPtrToInt(typ.typecode, p.uintptrType), typ.name+".icmp")
cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, typ.typecodeGEP, typ.name+".icmp")
p.builder.CreateCondBr(cmp, thenBlock, nextBlock)
p.builder.SetInsertPointAtEnd(nextBlock)
}
@@ -440,7 +490,7 @@ func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *inte
params[i] = fn.Param(i + 1)
}
params = append(params,
llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
llvm.Undef(p.i8ptrType),
)
// Start chain in the entry block.
@@ -472,7 +522,7 @@ func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *inte
// Create type check (if/else).
bb := p.ctx.AddBasicBlock(fn, typ.name)
next := p.ctx.AddBasicBlock(fn, typ.name+".next")
cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, llvm.ConstPtrToInt(typ.typecode, p.uintptrType), typ.name+".icmp")
cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, typ.typecodeGEP, typ.name+".icmp")
p.builder.CreateCondBr(cmp, bb, next)
// The function we will redirect to when the interface has this type.
@@ -522,7 +572,7 @@ func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *inte
// method on a nil interface.
nilPanic := p.mod.NamedFunction("runtime.nilPanic")
p.builder.CreateCall(nilPanic.GlobalValueType(), nilPanic, []llvm.Value{
llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
llvm.Undef(p.i8ptrType),
}, "")
p.builder.CreateUnreachable()
}
-2
View File
@@ -116,7 +116,6 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
goPasses.Run(mod)
// Run TinyGo-specific interprocedural optimizations.
LowerReflect(mod)
OptimizeAllocs(mod, config.Options.PrintAllocs, func(pos token.Position, msg string) {
fmt.Fprintln(os.Stderr, pos.String()+": "+msg)
})
@@ -129,7 +128,6 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
if err != nil {
return []error{err}
}
LowerReflect(mod)
errs := LowerInterrupts(mod)
if len(errs) > 0 {
return errs
-567
View File
@@ -1,567 +0,0 @@
package transform
// This file has some compiler support for run-time reflection using the reflect
// package. In particular, it encodes type information in type codes in such a
// way that the reflect package can decode the type from this information.
// Where needed, it also adds some side tables for looking up more information
// about a type, when that information cannot be stored directly in the type
// code.
//
// Go has 26 different type kinds.
//
// Type kinds are subdivided in basic types (see the list of basicTypes below)
// that are mostly numeric literals and non-basic (or "complex") types that are
// more difficult to encode. These non-basic types come in two forms:
// * Prefix types (pointer, slice, interface, channel): these just add
// something to an existing type. For example, a pointer like *int just adds
// the fact that it's a pointer to an existing type (int).
// These are encoded efficiently by adding a prefix to a type code.
// * Types with multiple fields (struct, array, func, map). All of these have
// multiple fields contained within. Most obviously structs can contain many
// types as fields. Also arrays contain not just the element type but also
// the length parameter which can be any arbitrary number and thus may not
// fit in a type code.
// These types are encoded using side tables.
//
// This distinction is also important for how named types are encoded. At the
// moment, named basic type just get a unique number assigned while named
// non-basic types have their underlying type stored in a sidetable.
import (
"encoding/binary"
"go/ast"
"math/big"
"sort"
"strings"
"tinygo.org/x/go-llvm"
)
// A list of basic types and their numbers. This list should be kept in sync
// with the list of Kind constants of type.go in the reflect package.
var basicTypes = map[string]int64{
"bool": 1,
"int": 2,
"int8": 3,
"int16": 4,
"int32": 5,
"int64": 6,
"uint": 7,
"uint8": 8,
"uint16": 9,
"uint32": 10,
"uint64": 11,
"uintptr": 12,
"float32": 13,
"float64": 14,
"complex64": 15,
"complex128": 16,
"string": 17,
"unsafe.Pointer": 18,
}
// A list of non-basic types. Adding 19 to this number will give the Kind as
// used in src/reflect/types.go, and it must be kept in sync with that list.
var nonBasicTypes = map[string]int64{
"chan": 0,
"interface": 1,
"pointer": 2,
"slice": 3,
"array": 4,
"func": 5,
"map": 6,
"struct": 7,
}
// typeCodeAssignmentState keeps some global state around for type code
// assignments, used to assign one unique type code to each Go type.
type typeCodeAssignmentState struct {
// Builder used purely for constant operations (because LLVM 15 removed many
// llvm.Const* functions).
builder llvm.Builder
// An integer that's incremented each time it's used to give unique IDs to
// type codes that are not yet fully supported otherwise by the reflect
// package (or are simply unused in the compiled program).
fallbackIndex int
// This is the length of an uintptr. Only used occasionally to know whether
// a given number can be encoded as a varint.
uintptrLen int
// Map of named types to their type code. It is important that named types
// get unique IDs for each type.
namedBasicTypes map[string]int
namedNonBasicTypes map[string]int
// Map of array types to their type code.
arrayTypes map[string]int
arrayTypesSidetable []byte
needsArrayTypesSidetable bool
// Map of struct types to their type code.
structTypes map[string]int
structTypesSidetable []byte
needsStructNamesSidetable bool
// Map of struct names and tags to their name string.
structNames map[string]int
structNamesSidetable []byte
needsStructTypesSidetable bool
// This byte array is stored in reflect.namedNonBasicTypesSidetable and is
// used at runtime to get details about a named non-basic type.
// Entries are varints (see makeVarint below and readVarint in
// reflect/sidetables.go for the encoding): one varint per entry. The
// integers in namedNonBasicTypes are indices into this array. Because these
// are varints, most type codes are really small (just one byte).
//
// Note that this byte buffer is not created when it is not needed
// (reflect.namedNonBasicTypesSidetable has no uses), see
// needsNamedTypesSidetable.
namedNonBasicTypesSidetable []uint64
// This indicates whether namedNonBasicTypesSidetable needs to be created at
// all. If it is false, namedNonBasicTypesSidetable will contain simple
// monotonically increasing numbers.
needsNamedNonBasicTypesSidetable bool
}
// LowerReflect is used to assign a type code to each type in the program
// that is ever stored in an interface. It tries to use the smallest possible
// numbers to make the code that works with interfaces as small as possible.
func LowerReflect(mod llvm.Module) {
// if reflect were not used, we could skip generating the sidetable
// this does not help in practice, and is difficult to do correctly
// Obtain slice of all types in the program.
type typeInfo struct {
typecode llvm.Value
name string
numUses int
}
var types []*typeInfo
for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
if strings.HasPrefix(global.Name(), "reflect/types.type:") {
types = append(types, &typeInfo{
typecode: global,
name: global.Name(),
numUses: len(getUses(global)),
})
}
}
// Sort the slice in a way that often used types are assigned a type code
// first.
sort.Slice(types, func(i, j int) bool {
if types[i].numUses != types[j].numUses {
return types[i].numUses < types[j].numUses
}
// It would make more sense to compare the name in the other direction,
// but for some reason that increases binary size. Could be a fluke, but
// could also have some good reason (and possibly hint at a small
// optimization).
return types[i].name > types[j].name
})
// Assign typecodes the way the reflect package expects.
targetData := llvm.NewTargetData(mod.DataLayout())
defer targetData.Dispose()
uintptrType := mod.Context().IntType(targetData.PointerSize() * 8)
state := typeCodeAssignmentState{
builder: mod.Context().NewBuilder(),
fallbackIndex: 1,
uintptrLen: targetData.PointerSize() * 8,
namedBasicTypes: make(map[string]int),
namedNonBasicTypes: make(map[string]int),
arrayTypes: make(map[string]int),
structTypes: make(map[string]int),
structNames: make(map[string]int),
needsNamedNonBasicTypesSidetable: len(getUses(mod.NamedGlobal("reflect.namedNonBasicTypesSidetable"))) != 0,
needsStructTypesSidetable: len(getUses(mod.NamedGlobal("reflect.structTypesSidetable"))) != 0,
needsStructNamesSidetable: len(getUses(mod.NamedGlobal("reflect.structNamesSidetable"))) != 0,
needsArrayTypesSidetable: len(getUses(mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0,
}
defer state.builder.Dispose()
for _, t := range types {
num := state.getTypeCodeNum(t.typecode)
if num.BitLen() > state.uintptrLen || !num.IsUint64() {
// TODO: support this in some way, using a side table for example.
// That's less efficient but better than not working at all.
// Particularly important on systems with 16-bit pointers (e.g.
// AVR).
panic("compiler: could not store type code number inside interface type code")
}
// Replace each use of the type code global with the constant type code.
for _, use := range getUses(t.typecode) {
if use.IsAConstantExpr().IsNil() {
continue
}
typecode := llvm.ConstInt(uintptrType, num.Uint64(), false)
switch use.Opcode() {
case llvm.PtrToInt:
// Already of the correct type.
case llvm.BitCast:
// Could happen when stored in an interface (which is of type
// i8*).
typecode = llvm.ConstIntToPtr(typecode, use.Type())
default:
panic("unexpected constant expression")
}
use.ReplaceAllUsesWith(typecode)
}
}
// Only create this sidetable when it is necessary.
if state.needsNamedNonBasicTypesSidetable {
global := replaceGlobalIntWithArray(mod, "reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
global.SetGlobalConstant(true)
}
if state.needsArrayTypesSidetable {
global := replaceGlobalIntWithArray(mod, "reflect.arrayTypesSidetable", state.arrayTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
global.SetGlobalConstant(true)
}
if state.needsStructTypesSidetable {
global := replaceGlobalIntWithArray(mod, "reflect.structTypesSidetable", state.structTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
global.SetGlobalConstant(true)
}
if state.needsStructNamesSidetable {
global := replaceGlobalIntWithArray(mod, "reflect.structNamesSidetable", state.structNamesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
global.SetGlobalConstant(true)
}
// Remove most objects created for interface and reflect lowering.
// They would normally be removed anyway in later passes, but not always.
// It also cleans up the IR for testing.
for _, typ := range types {
initializer := typ.typecode.Initializer()
references := state.builder.CreateExtractValue(initializer, 0, "")
typ.typecode.SetInitializer(llvm.ConstNull(initializer.Type()))
if strings.HasPrefix(typ.name, "reflect/types.type:struct:") {
// Structs have a 'references' field that is not a typecode but
// a pointer to a runtime.structField array and therefore a
// bitcast. This global should be erased separately, otherwise
// typecode objects cannot be erased.
structFields := references
if !structFields.IsAConstantExpr().IsNil() && structFields.Opcode() == llvm.BitCast {
structFields = structFields.Operand(0) // get global from bitcast, for LLVM 14 compatibility (non-opaque pointers)
}
structFields.EraseFromParentAsGlobal()
}
}
}
// getTypeCodeNum returns the typecode for a given type as expected by the
// reflect package. Also see getTypeCodeName, which serializes types to a string
// based on a types.Type value for this function.
func (state *typeCodeAssignmentState) getTypeCodeNum(typecode llvm.Value) *big.Int {
// Note: see src/reflect/type.go for bit allocations.
class, value := getClassAndValueFromTypeCode(typecode)
name := ""
if class == "named" {
name = value
typecode = state.builder.CreateExtractValue(typecode.Initializer(), 0, "")
class, value = getClassAndValueFromTypeCode(typecode)
}
if class == "basic" {
// Basic types follow the following bit pattern:
// ...xxxxx0
// where xxxxx is allocated for the 18 possible basic types and all the
// upper bits are used to indicate the named type.
num, ok := basicTypes[value]
if !ok {
panic("invalid basic type: " + value)
}
if name != "" {
// This type is named, set the upper bits to the name ID.
num |= int64(state.getBasicNamedTypeNum(name)) << 5
}
return big.NewInt(num << 1)
} else {
// Non-baisc types use the following bit pattern:
// ...nxxx1
// where xxx indicates the non-basic type. The upper bits contain
// whatever the type contains. Types that wrap a single other type
// (channel, interface, pointer, slice) just contain the bits of the
// wrapped type. Other types (like struct) need more fields and thus
// cannot be encoded as a simple prefix.
var classNumber int64
if n, ok := nonBasicTypes[class]; ok {
classNumber = n
} else {
panic("unknown type kind: " + class)
}
var num *big.Int
lowBits := (classNumber << 1) + 1 // the 5 low bits of the typecode
if name == "" {
num = state.getNonBasicTypeCode(class, typecode)
} else {
// We must return a named type here. But first check whether it
// has already been defined.
if index, ok := state.namedNonBasicTypes[name]; ok {
num := big.NewInt(int64(index))
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1+(1<<4)))
return num
}
lowBits |= 1 << 4 // set the 'n' bit (see above)
if !state.needsNamedNonBasicTypesSidetable {
// Use simple small integers in this case, to make these numbers
// smaller.
index := len(state.namedNonBasicTypes) + 1
state.namedNonBasicTypes[name] = index
num = big.NewInt(int64(index))
} else {
// We need to store full type information.
// First allocate a number in the named non-basic type
// sidetable.
index := len(state.namedNonBasicTypesSidetable)
state.namedNonBasicTypesSidetable = append(state.namedNonBasicTypesSidetable, 0)
state.namedNonBasicTypes[name] = index
// Get the typecode of the underlying type (which could be the
// element type in the case of pointers, for example).
num = state.getNonBasicTypeCode(class, typecode)
if num.BitLen() > state.uintptrLen || !num.IsUint64() {
panic("cannot store value in sidetable")
}
// Now update the side table with the number we just
// determined. We need this multi-step approach to avoid stack
// overflow due to adding types recursively in the case of
// linked lists (a pointer which points to a struct that
// contains that same pointer).
state.namedNonBasicTypesSidetable[index] = num.Uint64()
num = big.NewInt(int64(index))
}
}
// Concatenate the 'num' and 'lowBits' bitstrings.
num.Lsh(num, 5).Or(num, big.NewInt(lowBits))
return num
}
}
// getNonBasicTypeCode is used by getTypeCodeNum. It returns the upper bits of
// the type code used there in the type code.
func (state *typeCodeAssignmentState) getNonBasicTypeCode(class string, typecode llvm.Value) *big.Int {
switch class {
case "chan", "pointer", "slice":
// Prefix-style type kinds. The upper bits contain the element type.
sub := state.builder.CreateExtractValue(typecode.Initializer(), 0, "")
return state.getTypeCodeNum(sub)
case "array":
// An array is basically a pair of (typecode, length) stored in a
// sidetable.
return big.NewInt(int64(state.getArrayTypeNum(typecode)))
case "struct":
// More complicated type kind. The upper bits contain the index to the
// struct type in the struct types sidetable.
return big.NewInt(int64(state.getStructTypeNum(typecode)))
default:
// Type has not yet been implemented, so fall back by using a unique
// number.
num := big.NewInt(int64(state.fallbackIndex))
state.fallbackIndex++
return num
}
}
// getClassAndValueFromTypeCode takes a typecode (a llvm.Value of type
// runtime.typecodeID), looks at the name, and extracts the typecode class and
// value from it. For example, for a typecode with the following name:
//
// reflect/types.type:pointer:named:reflect.ValueError
//
// It extracts:
//
// class = "pointer"
// value = "named:reflect.ValueError"
func getClassAndValueFromTypeCode(typecode llvm.Value) (class, value string) {
typecodeName := typecode.Name()
const prefix = "reflect/types.type:"
if !strings.HasPrefix(typecodeName, prefix) {
panic("unexpected typecode name: " + typecodeName)
}
id := typecodeName[len(prefix):]
class = id[:strings.IndexByte(id, ':')]
value = id[len(class)+1:]
return
}
// getBasicNamedTypeNum returns an appropriate (unique) number for the given
// named type. If the name already has a number that number is returned, else a
// new number is returned. The number is always non-zero.
func (state *typeCodeAssignmentState) getBasicNamedTypeNum(name string) int {
if num, ok := state.namedBasicTypes[name]; ok {
return num
}
num := len(state.namedBasicTypes) + 1
state.namedBasicTypes[name] = num
return num
}
// getArrayTypeNum returns the array type number, which is an index into the
// reflect.arrayTypesSidetable or a unique number for this type if this table is
// not used.
func (state *typeCodeAssignmentState) getArrayTypeNum(typecode llvm.Value) int {
name := typecode.Name()
if num, ok := state.arrayTypes[name]; ok {
// This array type already has an entry in the sidetable. Don't store
// it twice.
return num
}
if !state.needsArrayTypesSidetable {
// We don't need array sidetables, so we can just assign monotonically
// increasing numbers to each array type.
num := len(state.arrayTypes)
state.arrayTypes[name] = num
return num
}
elemTypeCode := state.builder.CreateExtractValue(typecode.Initializer(), 0, "")
elemTypeNum := state.getTypeCodeNum(elemTypeCode)
if elemTypeNum.BitLen() > state.uintptrLen || !elemTypeNum.IsUint64() {
// TODO: make this a regular error
panic("array element type has a type code that is too big")
}
// The array side table is a sequence of {element type, array length}.
arrayLength := state.builder.CreateExtractValue(typecode.Initializer(), 1, "").ZExtValue()
buf := makeVarint(elemTypeNum.Uint64())
buf = append(buf, makeVarint(arrayLength)...)
index := len(state.arrayTypesSidetable)
state.arrayTypes[name] = index
state.arrayTypesSidetable = append(state.arrayTypesSidetable, buf...)
return index
}
// getStructTypeNum returns the struct type number, which is an index into
// reflect.structTypesSidetable or an unique number for every struct if this
// sidetable is not needed in the to-be-compiled program.
func (state *typeCodeAssignmentState) getStructTypeNum(typecode llvm.Value) int {
name := typecode.Name()
if num, ok := state.structTypes[name]; ok {
// This struct already has an assigned type code.
return num
}
if !state.needsStructTypesSidetable {
// We don't need struct sidetables, so we can just assign monotonically
// increasing numbers to each struct type.
num := len(state.structTypes)
state.structTypes[name] = num
return num
}
// Get the fields this struct type contains.
// The struct number will be the start index of
structTypeGlobal := stripPointerCasts(state.builder.CreateExtractValue(typecode.Initializer(), 0, "")).Initializer()
numFields := structTypeGlobal.Type().ArrayLength()
// The first data that is stored in the struct sidetable is the number of
// fields this struct contains. This is usually just a single byte because
// most structs don't contain that many fields, but make it a varint just
// to be sure.
buf := makeVarint(uint64(numFields))
// Iterate over every field in the struct.
// Every field is stored sequentially in the struct sidetable. Fields can
// be retrieved from this list of fields at runtime by iterating over all
// of them until the right field has been found.
// Perhaps adding some index would speed things up, but it would also make
// the sidetable bigger.
for i := 0; i < numFields; i++ {
// Collect some information about this field.
field := state.builder.CreateExtractValue(structTypeGlobal, i, "")
nameGlobal := state.builder.CreateExtractValue(field, 1, "")
if nameGlobal == llvm.ConstPointerNull(nameGlobal.Type()) {
panic("compiler: no name for this struct field")
}
fieldNameBytes := getGlobalBytes(stripPointerCasts(nameGlobal), state.builder)
fieldNameNumber := state.getStructNameNumber(fieldNameBytes)
// See whether this struct field has an associated tag, and if so,
// store that tag in the tags sidetable.
tagGlobal := state.builder.CreateExtractValue(field, 2, "")
hasTag := false
tagNumber := 0
if tagGlobal != llvm.ConstPointerNull(tagGlobal.Type()) {
hasTag = true
tagBytes := getGlobalBytes(stripPointerCasts(tagGlobal), state.builder)
tagNumber = state.getStructNameNumber(tagBytes)
}
// The 'embedded' or 'anonymous' flag for this field.
embedded := state.builder.CreateExtractValue(field, 3, "").ZExtValue() != 0
// The first byte in the struct types sidetable is a flags byte with
// two bits in it.
flagsByte := byte(0)
if embedded {
flagsByte |= 1
}
if hasTag {
flagsByte |= 2
}
if ast.IsExported(string(fieldNameBytes)) {
flagsByte |= 4
}
buf = append(buf, flagsByte)
// Get the type number and add it to the buffer.
// All fields have a type, so include it directly here.
typeNum := state.getTypeCodeNum(state.builder.CreateExtractValue(field, 0, ""))
if typeNum.BitLen() > state.uintptrLen || !typeNum.IsUint64() {
// TODO: make this a regular error
panic("struct field has a type code that is too big")
}
buf = append(buf, makeVarint(typeNum.Uint64())...)
// Add the name.
buf = append(buf, makeVarint(uint64(fieldNameNumber))...)
// Add the tag, if there is one.
if hasTag {
buf = append(buf, makeVarint(uint64(tagNumber))...)
}
}
num := len(state.structTypesSidetable)
state.structTypes[name] = num
state.structTypesSidetable = append(state.structTypesSidetable, buf...)
return num
}
// getStructNameNumber stores this string (name or tag) onto the struct names
// sidetable. The format is a varint of the length of the struct, followed by
// the raw bytes of the name. Multiple identical strings are stored under the
// same name for space efficiency.
func (state *typeCodeAssignmentState) getStructNameNumber(nameBytes []byte) int {
name := string(nameBytes)
if n, ok := state.structNames[name]; ok {
// This name was used before, re-use it now (for space efficiency).
return n
}
// This name is not yet in the names sidetable. Add it now.
n := len(state.structNamesSidetable)
state.structNames[name] = n
state.structNamesSidetable = append(state.structNamesSidetable, makeVarint(uint64(len(nameBytes)))...)
state.structNamesSidetable = append(state.structNamesSidetable, nameBytes...)
return n
}
// makeVarint is a small helper function that returns the bytes of the number in
// varint encoding.
func makeVarint(n uint64) []byte {
buf := make([]byte, binary.MaxVarintLen64)
return buf[:binary.PutUvarint(buf, n)]
}
-77
View File
@@ -1,77 +0,0 @@
package transform_test
import (
"testing"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
)
type reflectAssert struct {
call llvm.Value
name string
expectedNumber uint64
}
// Test reflect lowering. This code looks at IR like this:
//
// call void @main.assertType(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:int" to i32), i8* inttoptr (i32 3 to i8*), i32 4, i8* undef, i8* undef)
//
// and verifies that the ptrtoint constant (the first parameter of
// @main.assertType) is replaced with the correct type code. The expected
// output is this:
//
// call void @main.assertType(i32 4, i8* inttoptr (i32 3 to i8*), i32 4, i8* undef, i8* undef)
//
// The first and third parameter are compared and must match, the second
// parameter is ignored.
func TestReflect(t *testing.T) {
t.Parallel()
mod := compileGoFileForTesting(t, "./testdata/reflect.go")
// Run the instcombine pass, to clean up the IR a bit (especially
// insertvalue/extractvalue instructions).
pm := llvm.NewPassManager()
defer pm.Dispose()
pm.AddInstructionCombiningPass()
pm.Run(mod)
// Get a list of all the asserts in the source code.
assertType := mod.NamedFunction("main.assertType")
var asserts []reflectAssert
for user := assertType.FirstUse(); !user.IsNil(); user = user.NextUse() {
use := user.User()
if use.IsACallInst().IsNil() {
t.Fatal("expected call use of main.assertType")
}
global := use.Operand(0).Operand(0)
expectedNumber := use.Operand(2).ZExtValue()
asserts = append(asserts, reflectAssert{
call: use,
name: global.Name(),
expectedNumber: expectedNumber,
})
}
// Sanity check to show that the test is actually testing anything.
if len(asserts) < 3 {
t.Errorf("expected at least 3 test cases, got %d", len(asserts))
}
// Now lower the type codes.
transform.LowerReflect(mod)
// Check whether the values are as expected.
for _, assert := range asserts {
actualNumberValue := assert.call.Operand(0)
if actualNumberValue.IsAConstantInt().IsNil() {
t.Errorf("expected to see a constant for %s, got something else", assert.name)
continue
}
actualNumber := actualNumberValue.ZExtValue()
if actualNumber != assert.expectedNumber {
t.Errorf("%s: expected number 0b%b, got 0b%b", assert.name, assert.expectedNumber, actualNumber)
}
}
}
+5 -12
View File
@@ -113,11 +113,6 @@ func OptimizeReflectImplements(mod llvm.Module) {
builder := mod.Context().NewBuilder()
defer builder.Dispose()
// Get a few useful object for use later.
targetData := llvm.NewTargetData(mod.DataLayout())
defer targetData.Dispose()
uintptrType := mod.Context().IntType(targetData.PointerSize() * 8)
// Look up the (reflect.Value).Implements() method.
var implementsFunc llvm.Value
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
@@ -141,14 +136,13 @@ func OptimizeReflectImplements(mod llvm.Module) {
}
interfaceType := stripPointerCasts(call.Operand(2))
if interfaceType.IsAGlobalVariable().IsNil() {
// The asserted interface is not constant, so can't optimize this
// code.
// Interface is unknown at compile time. This can't be optimized.
continue
}
if strings.HasPrefix(interfaceType.Name(), "reflect/types.type:named:") {
// Get the underlying type.
interfaceType = builder.CreateExtractValue(interfaceType.Initializer(), 0, "")
interfaceType = stripPointerCasts(builder.CreateExtractValue(interfaceType.Initializer(), 2, ""))
}
if !strings.HasPrefix(interfaceType.Name(), "reflect/types.type:interface:") {
// This is an error. The Type passed to Implements should be of
@@ -156,16 +150,15 @@ func OptimizeReflectImplements(mod llvm.Module) {
// reported at runtime.
continue
}
if interfaceType.IsAGlobalVariable().IsNil() {
// Interface is unknown at compile time. This can't be optimized.
typeAssertFunction := mod.NamedFunction(strings.TrimPrefix(interfaceType.Name(), "reflect/types.type:") + ".$typeassert")
if typeAssertFunction.IsNil() {
continue
}
typeAssertFunction := builder.CreateExtractValue(interfaceType.Initializer(), 4, "").Operand(0)
// Replace Implements call with the type assert call.
builder.SetInsertPointBefore(call)
implements := builder.CreateCall(typeAssertFunction.GlobalValueType(), typeAssertFunction, []llvm.Value{
builder.CreatePtrToInt(call.Operand(0), uintptrType, ""), // typecode to check
call.Operand(0), // typecode to check
}, "")
call.ReplaceAllUsesWith(implements)
call.EraseFromParentAsInstruction()
-8
View File
@@ -1,8 +0,0 @@
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7em-none-eabi"
declare void @foo()
define void @bar() {
ret void
}
-8
View File
@@ -1,8 +0,0 @@
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7em-none-eabi"
declare void @foo()
define void @bar() section ".text.bar" {
ret void
}

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