mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-02 01:57:48 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2de0635140 | |||
| 9b91cbb841 | |||
| fa6bc6445c | |||
| 8bf94b9231 | |||
| 488174767b | |||
| 201592d93b | |||
| 476621736c | |||
| 7b44fcd865 | |||
| cfe971d723 | |||
| bad7bfc4d5 | |||
| ad1da7d26f | |||
| 6df303ff19 | |||
| 96b70fd619 | |||
| 4d0dfbd6fd | |||
| 1065f06e57 | |||
| ec27d9fb48 | |||
| cce9c6d5a1 | |||
| f41b6a3b96 | |||
| e066e67baf | |||
| cacb452aa6 | |||
| 9296332151 | |||
| 1125d42149 | |||
| 7982d26db0 | |||
| f6758d22d8 | |||
| 87cfe6a538 | |||
| c02cc339c5 | |||
| 361ecf9ea4 | |||
| e21ab04fad | |||
| 4e8453167f | |||
| ebb410afd9 | |||
| 012bdfae80 | |||
| f6df276118 | |||
| e0a5fc2555 | |||
| cecb80b8bf | |||
| d899895e32 |
@@ -1,3 +1,5 @@
|
|||||||
build/
|
build/
|
||||||
llvm-*/
|
llvm-*/
|
||||||
|
.github
|
||||||
|
.circleci
|
||||||
|
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: '1.20'
|
go-version: '1.20'
|
||||||
cache: true
|
cache: true
|
||||||
- name: Cache LLVM source
|
- name: Restore LLVM source cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache/restore@v3
|
||||||
id: cache-llvm-source
|
id: cache-llvm-source
|
||||||
with:
|
with:
|
||||||
key: llvm-source-15-macos-v2
|
key: llvm-source-15-macos-v3
|
||||||
path: |
|
path: |
|
||||||
llvm-project/clang/lib/Headers
|
llvm-project/clang/lib/Headers
|
||||||
llvm-project/clang/include
|
llvm-project/clang/include
|
||||||
@@ -43,11 +43,22 @@ jobs:
|
|||||||
- name: Download LLVM source
|
- name: Download LLVM source
|
||||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
||||||
run: make llvm-source
|
run: make llvm-source
|
||||||
- name: Cache LLVM build
|
- name: Save LLVM source cache
|
||||||
uses: actions/cache@v3
|
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
|
id: cache-llvm-build
|
||||||
with:
|
with:
|
||||||
key: llvm-build-15-macos-v3
|
key: llvm-build-15-macos-v4
|
||||||
path: llvm-build
|
path: llvm-build
|
||||||
- name: Build LLVM
|
- name: Build LLVM
|
||||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
||||||
@@ -61,6 +72,12 @@ jobs:
|
|||||||
# build!
|
# build!
|
||||||
make llvm-build
|
make llvm-build
|
||||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
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
|
- name: Cache wasi-libc sysroot
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
id: cache-wasi-libc
|
id: cache-wasi-libc
|
||||||
@@ -72,7 +89,7 @@ jobs:
|
|||||||
run: make wasi-libc
|
run: make wasi-libc
|
||||||
- name: Test TinyGo
|
- name: Test TinyGo
|
||||||
shell: bash
|
shell: bash
|
||||||
run: make test GOTESTFLAGS="-v -short"
|
run: make test GOTESTFLAGS="-short"
|
||||||
- name: Build TinyGo release tarball
|
- name: Build TinyGo release tarball
|
||||||
run: make release -j3
|
run: make release -j3
|
||||||
- name: Test stdlib packages
|
- name: Test stdlib packages
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
build-contexts: tinygo-llvm-build=docker-image://tinygo/llvm-15
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
- name: Trigger Drivers repo build on Github Actions
|
- name: Trigger Drivers repo build on Github Actions
|
||||||
@@ -69,21 +70,31 @@ jobs:
|
|||||||
-H "Accept: application/vnd.github.v3+json" \
|
-H "Accept: application/vnd.github.v3+json" \
|
||||||
https://api.github.com/repos/tinygo-org/bluetooth/actions/workflows/linux.yml/dispatches \
|
https://api.github.com/repos/tinygo-org/bluetooth/actions/workflows/linux.yml/dispatches \
|
||||||
-d '{"ref": "dev"}'
|
-d '{"ref": "dev"}'
|
||||||
- name: Trigger TinyFS repo build on CircleCI
|
- name: Trigger TinyFS repo build on Github Actions
|
||||||
run: |
|
run: |
|
||||||
curl --location --request POST 'https://circleci.com/api/v2/project/github/tinygo-org/tinyfs/pipeline' \
|
curl -X POST \
|
||||||
--header 'Content-Type: application/json' \
|
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
|
||||||
-d '{"branch": "dev"}' \
|
-H "Accept: application/vnd.github.v3+json" \
|
||||||
-u "${{ secrets.CIRCLECI_API_TOKEN }}"
|
https://api.github.com/repos/tinygo-org/tinyfs/actions/workflows/build.yml/dispatches \
|
||||||
- name: Trigger TinyFont repo build on CircleCI
|
-d '{"ref": "dev"}'
|
||||||
|
- name: Trigger TinyFont repo build on Github Actions
|
||||||
run: |
|
run: |
|
||||||
curl --location --request POST 'https://circleci.com/api/v2/project/github/tinygo-org/tinyfont/pipeline' \
|
curl -X POST \
|
||||||
--header 'Content-Type: application/json' \
|
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
|
||||||
-d '{"branch": "dev"}' \
|
-H "Accept: application/vnd.github.v3+json" \
|
||||||
-u "${{ secrets.CIRCLECI_API_TOKEN }}"
|
https://api.github.com/repos/tinygo-org/tinyfont/actions/workflows/build.yml/dispatches \
|
||||||
- name: Trigger TinyDraw repo build on CircleCI
|
-d '{"ref": "dev"}'
|
||||||
|
- name: Trigger TinyDraw repo build on Github Actions
|
||||||
run: |
|
run: |
|
||||||
curl --location --request POST 'https://circleci.com/api/v2/project/github/tinygo-org/tinydraw/pipeline' \
|
curl -X POST \
|
||||||
--header 'Content-Type: application/json' \
|
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
|
||||||
-d '{"branch": "dev"}' \
|
-H "Accept: application/vnd.github.v3+json" \
|
||||||
-u "${{ secrets.CIRCLECI_API_TOKEN }}"
|
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
@@ -39,11 +39,11 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
~/.cache/go-build
|
~/.cache/go-build
|
||||||
~/go/pkg/mod
|
~/go/pkg/mod
|
||||||
- name: Cache LLVM source
|
- name: Restore LLVM source cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache/restore@v3
|
||||||
id: cache-llvm-source
|
id: cache-llvm-source
|
||||||
with:
|
with:
|
||||||
key: llvm-source-15-linux-alpine-v2
|
key: llvm-source-15-linux-alpine-v3
|
||||||
path: |
|
path: |
|
||||||
llvm-project/clang/lib/Headers
|
llvm-project/clang/lib/Headers
|
||||||
llvm-project/clang/include
|
llvm-project/clang/include
|
||||||
@@ -53,11 +53,22 @@ jobs:
|
|||||||
- name: Download LLVM source
|
- name: Download LLVM source
|
||||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
||||||
run: make llvm-source
|
run: make llvm-source
|
||||||
- name: Cache LLVM build
|
- name: Save LLVM source cache
|
||||||
uses: actions/cache@v3
|
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
|
id: cache-llvm-build
|
||||||
with:
|
with:
|
||||||
key: llvm-build-15-linux-alpine-v3
|
key: llvm-build-15-linux-alpine-v4
|
||||||
path: llvm-build
|
path: llvm-build
|
||||||
- name: Build LLVM
|
- name: Build LLVM
|
||||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
||||||
@@ -71,6 +82,12 @@ jobs:
|
|||||||
make llvm-build
|
make llvm-build
|
||||||
# Remove unnecessary object files (to reduce cache size).
|
# Remove unnecessary object files (to reduce cache size).
|
||||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
||||||
|
- name: Save LLVM build cache
|
||||||
|
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
|
- name: Cache Binaryen
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
id: cache-binaryen
|
id: cache-binaryen
|
||||||
@@ -122,7 +139,9 @@ jobs:
|
|||||||
cache: true
|
cache: true
|
||||||
- name: Install wasmtime
|
- name: Install wasmtime
|
||||||
run: |
|
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
|
echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH
|
||||||
- name: Download release artifact
|
- name: Download release artifact
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
@@ -166,13 +185,15 @@ jobs:
|
|||||||
node-version: '14'
|
node-version: '14'
|
||||||
- name: Install wasmtime
|
- name: Install wasmtime
|
||||||
run: |
|
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
|
echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH
|
||||||
- name: Cache LLVM source
|
- name: Restore LLVM source cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache/restore@v3
|
||||||
id: cache-llvm-source
|
id: cache-llvm-source
|
||||||
with:
|
with:
|
||||||
key: llvm-source-15-linux-asserts-v2
|
key: llvm-source-15-linux-asserts-v3
|
||||||
path: |
|
path: |
|
||||||
llvm-project/clang/lib/Headers
|
llvm-project/clang/lib/Headers
|
||||||
llvm-project/clang/include
|
llvm-project/clang/include
|
||||||
@@ -182,11 +203,22 @@ jobs:
|
|||||||
- name: Download LLVM source
|
- name: Download LLVM source
|
||||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
||||||
run: make llvm-source
|
run: make llvm-source
|
||||||
- name: Cache LLVM build
|
- name: Save LLVM source cache
|
||||||
uses: actions/cache@v3
|
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
|
id: cache-llvm-build
|
||||||
with:
|
with:
|
||||||
key: llvm-build-15-linux-asserts-v3
|
key: llvm-build-15-linux-asserts-v4
|
||||||
path: llvm-build
|
path: llvm-build
|
||||||
- name: Build LLVM
|
- name: Build LLVM
|
||||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
||||||
@@ -198,6 +230,12 @@ jobs:
|
|||||||
make llvm-build ASSERT=1
|
make llvm-build ASSERT=1
|
||||||
# Remove unnecessary object files (to reduce cache size).
|
# Remove unnecessary object files (to reduce cache size).
|
||||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
||||||
|
- name: Save LLVM build cache
|
||||||
|
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
|
- name: Cache Binaryen
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
id: cache-binaryen
|
id: cache-binaryen
|
||||||
@@ -254,11 +292,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: '1.20'
|
go-version: '1.20'
|
||||||
cache: true
|
cache: true
|
||||||
- name: Cache LLVM source
|
- name: Restore LLVM source cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache/restore@v3
|
||||||
id: cache-llvm-source
|
id: cache-llvm-source
|
||||||
with:
|
with:
|
||||||
key: llvm-source-15-linux-v2
|
key: llvm-source-15-linux-v3
|
||||||
path: |
|
path: |
|
||||||
llvm-project/clang/lib/Headers
|
llvm-project/clang/lib/Headers
|
||||||
llvm-project/clang/include
|
llvm-project/clang/include
|
||||||
@@ -268,11 +306,22 @@ jobs:
|
|||||||
- name: Download LLVM source
|
- name: Download LLVM source
|
||||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
||||||
run: make llvm-source
|
run: make llvm-source
|
||||||
- name: Cache LLVM build
|
- name: Save LLVM source cache
|
||||||
uses: actions/cache@v3
|
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
|
id: cache-llvm-build
|
||||||
with:
|
with:
|
||||||
key: llvm-build-15-linux-arm-v3
|
key: llvm-build-15-linux-arm-v4
|
||||||
path: llvm-build
|
path: llvm-build
|
||||||
- name: Build LLVM
|
- name: Build LLVM
|
||||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
||||||
@@ -286,6 +335,12 @@ jobs:
|
|||||||
make llvm-build CROSS=arm-linux-gnueabihf
|
make llvm-build CROSS=arm-linux-gnueabihf
|
||||||
# Remove unnecessary object files (to reduce cache size).
|
# Remove unnecessary object files (to reduce cache size).
|
||||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
||||||
|
- name: Save LLVM build cache
|
||||||
|
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
|
- name: Cache Binaryen
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
id: cache-binaryen
|
id: cache-binaryen
|
||||||
@@ -354,11 +409,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: '1.20'
|
go-version: '1.20'
|
||||||
cache: true
|
cache: true
|
||||||
- name: Cache LLVM source
|
- name: Restore LLVM source cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache/restore@v3
|
||||||
id: cache-llvm-source
|
id: cache-llvm-source
|
||||||
with:
|
with:
|
||||||
key: llvm-source-15-linux-v2
|
key: llvm-source-15-linux-v3
|
||||||
path: |
|
path: |
|
||||||
llvm-project/clang/lib/Headers
|
llvm-project/clang/lib/Headers
|
||||||
llvm-project/clang/include
|
llvm-project/clang/include
|
||||||
@@ -368,11 +423,22 @@ jobs:
|
|||||||
- name: Download LLVM source
|
- name: Download LLVM source
|
||||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
||||||
run: make llvm-source
|
run: make llvm-source
|
||||||
- name: Cache LLVM build
|
- name: Save LLVM source cache
|
||||||
uses: actions/cache@v3
|
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
|
id: cache-llvm-build
|
||||||
with:
|
with:
|
||||||
key: llvm-build-15-linux-arm64-v3
|
key: llvm-build-15-linux-arm64-v4
|
||||||
path: llvm-build
|
path: llvm-build
|
||||||
- name: Build LLVM
|
- name: Build LLVM
|
||||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
||||||
@@ -384,6 +450,12 @@ jobs:
|
|||||||
make llvm-build CROSS=aarch64-linux-gnu
|
make llvm-build CROSS=aarch64-linux-gnu
|
||||||
# Remove unnecessary object files (to reduce cache size).
|
# Remove unnecessary object files (to reduce cache size).
|
||||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
||||||
|
- name: Save LLVM build cache
|
||||||
|
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
|
- name: Cache Binaryen
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
id: cache-binaryen
|
id: cache-binaryen
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ jobs:
|
|||||||
build-windows:
|
build-windows:
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
steps:
|
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
|
- uses: brechtm/setup-scoop@v2
|
||||||
with:
|
with:
|
||||||
scoop_update: 'false'
|
scoop_update: 'false'
|
||||||
@@ -31,11 +37,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: '1.20'
|
go-version: '1.20'
|
||||||
cache: true
|
cache: true
|
||||||
- name: Cache LLVM source
|
- name: Restore cached LLVM source
|
||||||
uses: actions/cache@v3
|
uses: actions/cache/restore@v3
|
||||||
id: cache-llvm-source
|
id: cache-llvm-source
|
||||||
with:
|
with:
|
||||||
key: llvm-source-15-windows-v2
|
key: llvm-source-15-windows-v4
|
||||||
path: |
|
path: |
|
||||||
llvm-project/clang/lib/Headers
|
llvm-project/clang/lib/Headers
|
||||||
llvm-project/clang/include
|
llvm-project/clang/include
|
||||||
@@ -45,11 +51,22 @@ jobs:
|
|||||||
- name: Download LLVM source
|
- name: Download LLVM source
|
||||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
||||||
run: make llvm-source
|
run: make llvm-source
|
||||||
- name: Cache LLVM build
|
- name: Save cached LLVM source
|
||||||
uses: actions/cache@v3
|
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
|
id: cache-llvm-build
|
||||||
with:
|
with:
|
||||||
key: llvm-build-15-windows-v3
|
key: llvm-build-15-windows-v5
|
||||||
path: llvm-build
|
path: llvm-build
|
||||||
- name: Build LLVM
|
- name: Build LLVM
|
||||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
||||||
@@ -62,6 +79,12 @@ jobs:
|
|||||||
make llvm-build CCACHE=OFF
|
make llvm-build CCACHE=OFF
|
||||||
# Remove unnecessary object files (to reduce cache size).
|
# Remove unnecessary object files (to reduce cache size).
|
||||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
||||||
|
- name: Save 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
|
- name: Cache wasi-libc sysroot
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
id: cache-wasi-libc
|
id: cache-wasi-libc
|
||||||
@@ -76,7 +99,7 @@ jobs:
|
|||||||
scoop install wasmtime
|
scoop install wasmtime
|
||||||
- name: Test TinyGo
|
- name: Test TinyGo
|
||||||
shell: bash
|
shell: bash
|
||||||
run: make test GOTESTFLAGS="-v -short"
|
run: make test GOTESTFLAGS="-short"
|
||||||
- name: Build TinyGo release tarball
|
- name: Build TinyGo release tarball
|
||||||
shell: bash
|
shell: bash
|
||||||
run: make build/release -j4
|
run: make build/release -j4
|
||||||
@@ -100,6 +123,12 @@ jobs:
|
|||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
needs: build-windows
|
needs: build-windows
|
||||||
steps:
|
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
|
- uses: brechtm/setup-scoop@v2
|
||||||
with:
|
with:
|
||||||
scoop_update: 'false'
|
scoop_update: 'false'
|
||||||
@@ -131,6 +160,12 @@ jobs:
|
|||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
needs: build-windows
|
needs: build-windows
|
||||||
steps:
|
steps:
|
||||||
|
- name: Configure pagefile
|
||||||
|
uses: al-cheb/configure-pagefile-action@v1.3
|
||||||
|
with:
|
||||||
|
minimum-size: 8GB
|
||||||
|
maximum-size: 24GB
|
||||||
|
disk-root: "C:"
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
@@ -154,6 +189,12 @@ jobs:
|
|||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
needs: build-windows
|
needs: build-windows
|
||||||
steps:
|
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
|
- uses: brechtm/setup-scoop@v2
|
||||||
with:
|
with:
|
||||||
scoop_update: 'false'
|
scoop_update: 'false'
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ GO ?= go
|
|||||||
export GOROOT = $(shell $(GO) env GOROOT)
|
export GOROOT = $(shell $(GO) env GOROOT)
|
||||||
|
|
||||||
# Flags to pass to go test.
|
# Flags to pass to go test.
|
||||||
GOTESTFLAGS ?= -v
|
GOTESTFLAGS ?=
|
||||||
|
|
||||||
# md5sum binary
|
# md5sum binary
|
||||||
MD5SUM = md5sum
|
MD5SUM = md5sum
|
||||||
@@ -296,6 +296,7 @@ TEST_PACKAGES_FAST = \
|
|||||||
encoding \
|
encoding \
|
||||||
encoding/ascii85 \
|
encoding/ascii85 \
|
||||||
encoding/base32 \
|
encoding/base32 \
|
||||||
|
encoding/base64 \
|
||||||
encoding/csv \
|
encoding/csv \
|
||||||
encoding/hex \
|
encoding/hex \
|
||||||
go/scanner \
|
go/scanner \
|
||||||
@@ -331,26 +332,34 @@ TEST_PACKAGES_FAST += crypto/elliptic/internal/fiat
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
# archive/zip requires os.ReadAt, which is not yet supported on windows
|
# archive/zip requires os.ReadAt, which is not yet supported on windows
|
||||||
|
# bytes requires mmap
|
||||||
# compress/flate appears to hang on wasi
|
# compress/flate appears to hang on wasi
|
||||||
# compress/lzw 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
|
# 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
|
# 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
|
# 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
|
# 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
|
# 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
|
# testing/fstest requires os.ReadDir, which is not yet supported on windows or wasi
|
||||||
|
|
||||||
# Additional standard library packages that pass tests on individual platforms
|
# Additional standard library packages that pass tests on individual platforms
|
||||||
TEST_PACKAGES_LINUX := \
|
TEST_PACKAGES_LINUX := \
|
||||||
archive/zip \
|
archive/zip \
|
||||||
|
bytes \
|
||||||
compress/flate \
|
compress/flate \
|
||||||
compress/lzw \
|
compress/lzw \
|
||||||
crypto/hmac \
|
crypto/hmac \
|
||||||
debug/dwarf \
|
debug/dwarf \
|
||||||
debug/plan9obj \
|
debug/plan9obj \
|
||||||
|
image \
|
||||||
io/ioutil \
|
io/ioutil \
|
||||||
|
mime/quotedprintable \
|
||||||
strconv \
|
strconv \
|
||||||
testing/fstest \
|
testing/fstest \
|
||||||
|
text/tabwriter \
|
||||||
text/template/parse
|
text/template/parse
|
||||||
|
|
||||||
TEST_PACKAGES_DARWIN := $(TEST_PACKAGES_LINUX)
|
TEST_PACKAGES_DARWIN := $(TEST_PACKAGES_LINUX)
|
||||||
@@ -465,6 +474,8 @@ smoketest:
|
|||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
|
||||||
@$(MD5SUM) test.hex
|
@$(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
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick
|
||||||
|
|||||||
+124
-81
@@ -169,12 +169,14 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
|||||||
CodeModel: config.CodeModel(),
|
CodeModel: config.CodeModel(),
|
||||||
RelocationModel: config.RelocationModel(),
|
RelocationModel: config.RelocationModel(),
|
||||||
SizeLevel: sizeLevel,
|
SizeLevel: sizeLevel,
|
||||||
|
TinyGoVersion: goenv.Version,
|
||||||
|
|
||||||
Scheduler: config.Scheduler(),
|
Scheduler: config.Scheduler(),
|
||||||
AutomaticStackSize: config.AutomaticStackSize(),
|
AutomaticStackSize: config.AutomaticStackSize(),
|
||||||
DefaultStackSize: config.StackSize(),
|
DefaultStackSize: config.StackSize(),
|
||||||
NeedsStackObjects: config.NeedsStackObjects(),
|
NeedsStackObjects: config.NeedsStackObjects(),
|
||||||
Debug: true,
|
Debug: true,
|
||||||
|
LTO: config.LTO() != "legacy",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the target machine, which is the LLVM object that contains all
|
// Load the target machine, which is the LLVM object that contains all
|
||||||
@@ -305,7 +307,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
|||||||
actionID := packageAction{
|
actionID := packageAction{
|
||||||
ImportPath: pkg.ImportPath,
|
ImportPath: pkg.ImportPath,
|
||||||
CompilerBuildID: string(compilerBuildID),
|
CompilerBuildID: string(compilerBuildID),
|
||||||
TinyGoVersion: goenv.Version,
|
|
||||||
LLVMVersion: llvm.Version,
|
LLVMVersion: llvm.Version,
|
||||||
Config: compilerConfig,
|
Config: compilerConfig,
|
||||||
CFlags: pkg.CFlags,
|
CFlags: pkg.CFlags,
|
||||||
@@ -452,18 +453,24 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if runtime.GOOS == "windows" {
|
if compilerConfig.LTO {
|
||||||
// Work around a problem on Windows.
|
buf := llvm.WriteThinLTOBitcodeToMemoryBuffer(mod)
|
||||||
// For some reason, WriteBitcodeToFile causes TinyGo to
|
|
||||||
// exit with the following message:
|
|
||||||
// LLVM ERROR: IO failure on output stream: Bad file descriptor
|
|
||||||
buf := llvm.WriteBitcodeToMemoryBuffer(mod)
|
|
||||||
defer buf.Dispose()
|
defer buf.Dispose()
|
||||||
_, err = f.Write(buf.Bytes())
|
_, err = f.Write(buf.Bytes())
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, write bitcode directly to the file (probably
|
if runtime.GOOS == "windows" {
|
||||||
// faster).
|
// Work around a problem on Windows.
|
||||||
err = llvm.WriteBitcodeToFile(mod, f)
|
// For some reason, WriteBitcodeToFile causes TinyGo to
|
||||||
|
// exit with the following message:
|
||||||
|
// LLVM ERROR: IO failure on output stream: Bad file descriptor
|
||||||
|
buf := llvm.WriteBitcodeToMemoryBuffer(mod)
|
||||||
|
defer buf.Dispose()
|
||||||
|
_, err = f.Write(buf.Bytes())
|
||||||
|
} else {
|
||||||
|
// Otherwise, write bitcode directly to the file (probably
|
||||||
|
// faster).
|
||||||
|
err = llvm.WriteBitcodeToFile(mod, f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// WriteBitcodeToFile doesn't produce a useful error on its
|
// WriteBitcodeToFile doesn't produce a useful error on its
|
||||||
@@ -511,24 +518,11 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
|||||||
|
|
||||||
// Create runtime.initAll function that calls the runtime
|
// Create runtime.initAll function that calls the runtime
|
||||||
// initializer of each package.
|
// initializer of each package.
|
||||||
llvmInitFn := mod.NamedFunction("runtime.initAll")
|
initAllModule := createInitAll(ctx, config, compilerConfig, lprogram.Sorted())
|
||||||
llvmInitFn.SetLinkage(llvm.InternalLinkage)
|
err := llvm.LinkModules(mod, initAllModule)
|
||||||
llvmInitFn.SetUnnamedAddr(true)
|
if err != nil {
|
||||||
transform.AddStandardAttributes(llvmInitFn, config)
|
return fmt.Errorf("failed to link module: %w", err)
|
||||||
llvmInitFn.Param(0).SetName("context")
|
|
||||||
block := mod.Context().AddBasicBlock(llvmInitFn, "entry")
|
|
||||||
irbuilder := mod.Context().NewBuilder()
|
|
||||||
defer irbuilder.Dispose()
|
|
||||||
irbuilder.SetInsertPointAtEnd(block)
|
|
||||||
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
|
|
||||||
for _, pkg := range lprogram.Sorted() {
|
|
||||||
pkgInit := mod.NamedFunction(pkg.Pkg.Path() + ".init")
|
|
||||||
if pkgInit.IsNil() {
|
|
||||||
panic("init not found for " + pkg.Pkg.Path())
|
|
||||||
}
|
|
||||||
irbuilder.CreateCall(pkgInit.GlobalValueType(), pkgInit, []llvm.Value{llvm.Undef(i8ptrType)}, "")
|
|
||||||
}
|
}
|
||||||
irbuilder.CreateRetVoid()
|
|
||||||
|
|
||||||
// After linking, functions should (as far as possible) be set to
|
// After linking, functions should (as far as possible) be set to
|
||||||
// private linkage or internal linkage. The compiler package marks
|
// private linkage or internal linkage. The compiler package marks
|
||||||
@@ -561,7 +555,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
|||||||
|
|
||||||
// Run all optimization passes, which are much more effective now
|
// Run all optimization passes, which are much more effective now
|
||||||
// that the optimizer can see the whole program at once.
|
// that the optimizer can see the whole program at once.
|
||||||
err := optimizeProgram(mod, config)
|
err = optimizeProgram(mod, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -594,12 +588,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
|||||||
defer llvmBuf.Dispose()
|
defer llvmBuf.Dispose()
|
||||||
return result, os.WriteFile(outpath, llvmBuf.Bytes(), 0666)
|
return result, os.WriteFile(outpath, llvmBuf.Bytes(), 0666)
|
||||||
case ".bc":
|
case ".bc":
|
||||||
var buf llvm.MemoryBuffer
|
buf := llvm.WriteThinLTOBitcodeToMemoryBuffer(mod)
|
||||||
if config.UseThinLTO() {
|
|
||||||
buf = llvm.WriteThinLTOBitcodeToMemoryBuffer(mod)
|
|
||||||
} else {
|
|
||||||
buf = llvm.WriteBitcodeToMemoryBuffer(mod)
|
|
||||||
}
|
|
||||||
defer buf.Dispose()
|
defer buf.Dispose()
|
||||||
return result, os.WriteFile(outpath, buf.Bytes(), 0666)
|
return result, os.WriteFile(outpath, buf.Bytes(), 0666)
|
||||||
case ".ll":
|
case ".ll":
|
||||||
@@ -621,23 +610,44 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
|||||||
dependencies: []*compileJob{programJob},
|
dependencies: []*compileJob{programJob},
|
||||||
result: objfile,
|
result: objfile,
|
||||||
run: func(*compileJob) error {
|
run: func(*compileJob) error {
|
||||||
var llvmBuf llvm.MemoryBuffer
|
llvmBuf := llvm.WriteThinLTOBitcodeToMemoryBuffer(mod)
|
||||||
if config.UseThinLTO() {
|
|
||||||
llvmBuf = llvm.WriteThinLTOBitcodeToMemoryBuffer(mod)
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
llvmBuf, err = machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer llvmBuf.Dispose()
|
defer llvmBuf.Dispose()
|
||||||
return os.WriteFile(objfile, llvmBuf.Bytes(), 0666)
|
return os.WriteFile(objfile, llvmBuf.Bytes(), 0666)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add job to create the runtime.initAll function in a new module.
|
||||||
|
initAllJob := &compileJob{
|
||||||
|
description: "create runtime.initAll",
|
||||||
|
result: filepath.Join(tmpdir, "runtime-initAll.bc"),
|
||||||
|
run: func(job *compileJob) (err error) {
|
||||||
|
// Create module with runtime.initAll.
|
||||||
|
ctx := llvm.NewContext()
|
||||||
|
defer ctx.Dispose()
|
||||||
|
initAllMod := createInitAll(ctx, config, compilerConfig, lprogram.Sorted())
|
||||||
|
defer initAllMod.Dispose()
|
||||||
|
|
||||||
|
// Write module to bitcode file.
|
||||||
|
llvmBuf := llvm.WriteThinLTOBitcodeToMemoryBuffer(initAllMod)
|
||||||
|
defer llvmBuf.Dispose()
|
||||||
|
return os.WriteFile(job.result, llvmBuf.Bytes(), 0666)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare link command.
|
// Prepare link command.
|
||||||
linkerDependencies := []*compileJob{outputObjectFileJob}
|
var linkerDependencies []*compileJob
|
||||||
|
switch config.LTO() {
|
||||||
|
case "legacy":
|
||||||
|
// Link all Go bitcode files together into a single large module and
|
||||||
|
// then do a ThinLTO link with the resulting large module + extra C
|
||||||
|
// files (from CGo etc).
|
||||||
|
linkerDependencies = append(linkerDependencies, outputObjectFileJob)
|
||||||
|
case "thin":
|
||||||
|
// Do a real thin link, with each Go package in a separate translation
|
||||||
|
// unit. This is faster than merging them into one big LTO module.
|
||||||
|
linkerDependencies = append(linkerDependencies, packageJobs...)
|
||||||
|
linkerDependencies = append(linkerDependencies, initAllJob)
|
||||||
|
}
|
||||||
result.Executable = filepath.Join(tmpdir, "main")
|
result.Executable = filepath.Join(tmpdir, "main")
|
||||||
if config.GOOS() == "windows" {
|
if config.GOOS() == "windows" {
|
||||||
result.Executable += ".exe"
|
result.Executable += ".exe"
|
||||||
@@ -664,7 +674,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
|||||||
job := &compileJob{
|
job := &compileJob{
|
||||||
description: "compile extra file " + path,
|
description: "compile extra file " + path,
|
||||||
run: func(job *compileJob) error {
|
run: func(job *compileJob) error {
|
||||||
result, err := compileAndCacheCFile(abspath, tmpdir, config.CFlags(), config.UseThinLTO(), config.Options.PrintCommands)
|
result, err := compileAndCacheCFile(abspath, tmpdir, config.CFlags(), config.Options.PrintCommands)
|
||||||
job.result = result
|
job.result = result
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
@@ -682,7 +692,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
|||||||
job := &compileJob{
|
job := &compileJob{
|
||||||
description: "compile CGo file " + abspath,
|
description: "compile CGo file " + abspath,
|
||||||
run: func(job *compileJob) error {
|
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
|
job.result = result
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
@@ -741,36 +751,34 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
|||||||
}
|
}
|
||||||
ldflags = append(ldflags, dependency.result)
|
ldflags = append(ldflags, dependency.result)
|
||||||
}
|
}
|
||||||
if config.UseThinLTO() {
|
ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU())
|
||||||
ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU())
|
if config.GOOS() == "windows" {
|
||||||
if config.GOOS() == "windows" {
|
// Options for the MinGW wrapper for the lld COFF linker.
|
||||||
// Options for the MinGW wrapper for the lld COFF linker.
|
ldflags = append(ldflags,
|
||||||
ldflags = append(ldflags,
|
"-Xlink=/opt:lldlto="+strconv.Itoa(optLevel),
|
||||||
"-Xlink=/opt:lldlto="+strconv.Itoa(optLevel),
|
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
|
||||||
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
|
} else if config.GOOS() == "darwin" {
|
||||||
} else if config.GOOS() == "darwin" {
|
// Options for the ld64-compatible lld linker.
|
||||||
// Options for the ld64-compatible lld linker.
|
ldflags = append(ldflags,
|
||||||
ldflags = append(ldflags,
|
"--lto-O"+strconv.Itoa(optLevel),
|
||||||
"--lto-O"+strconv.Itoa(optLevel),
|
"-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
|
||||||
"-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
|
} else {
|
||||||
} else {
|
// Options for the ELF linker.
|
||||||
// Options for the ELF linker.
|
ldflags = append(ldflags,
|
||||||
ldflags = append(ldflags,
|
"--lto-O"+strconv.Itoa(optLevel),
|
||||||
"--lto-O"+strconv.Itoa(optLevel),
|
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"),
|
||||||
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"),
|
)
|
||||||
)
|
}
|
||||||
}
|
if config.CodeModel() != "default" {
|
||||||
if config.CodeModel() != "default" {
|
ldflags = append(ldflags,
|
||||||
ldflags = append(ldflags,
|
"-mllvm", "-code-model="+config.CodeModel())
|
||||||
"-mllvm", "-code-model="+config.CodeModel())
|
}
|
||||||
}
|
if sizeLevel >= 2 {
|
||||||
if sizeLevel >= 2 {
|
// Workaround with roughly the same effect as
|
||||||
// Workaround with roughly the same effect as
|
// https://reviews.llvm.org/D119342.
|
||||||
// https://reviews.llvm.org/D119342.
|
// Can hopefully be removed in LLVM 15.
|
||||||
// Can hopefully be removed in LLVM 15.
|
ldflags = append(ldflags,
|
||||||
ldflags = append(ldflags,
|
"-mllvm", "--rotation-max-header-size=0")
|
||||||
"-mllvm", "--rotation-max-header-size=0")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if config.Options.PrintCommands != nil {
|
if config.Options.PrintCommands != nil {
|
||||||
config.Options.PrintCommands(config.Target.Linker, ldflags...)
|
config.Options.PrintCommands(config.Target.Linker, ldflags...)
|
||||||
@@ -1048,6 +1056,45 @@ func createEmbedObjectFile(data, hexSum, sourceFile, sourceDir, tmpdir string, c
|
|||||||
return outfile.Name(), outfile.Close()
|
return outfile.Name(), outfile.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a module with the runtime.initAll function that calls all package
|
||||||
|
// initializers in initialization order.
|
||||||
|
func createInitAll(ctx llvm.Context, config *compileopts.Config, compilerConfig *compiler.Config, pkgs []*loader.Package) llvm.Module {
|
||||||
|
// Create LLVM module.
|
||||||
|
mod := ctx.NewModule("runtime-initAll")
|
||||||
|
|
||||||
|
// Add datalayout string.
|
||||||
|
machine, err := compiler.NewTargetMachine(compilerConfig)
|
||||||
|
if err != nil {
|
||||||
|
panic(err) // extremely unlikely to fail here (NewTargetMachine is called many times before)
|
||||||
|
}
|
||||||
|
defer machine.Dispose()
|
||||||
|
targetData := machine.CreateTargetData()
|
||||||
|
defer targetData.Dispose()
|
||||||
|
mod.SetTarget(config.Triple())
|
||||||
|
mod.SetDataLayout(targetData.String())
|
||||||
|
|
||||||
|
// Create empty runtime.initAll function.
|
||||||
|
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
|
||||||
|
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{i8ptrType}, false)
|
||||||
|
fn := llvm.AddFunction(mod, "runtime.initAll", fnType)
|
||||||
|
fn.SetUnnamedAddr(true)
|
||||||
|
transform.AddStandardAttributes(fn, config)
|
||||||
|
|
||||||
|
// Add calls to package initializers.
|
||||||
|
fn.Param(0).SetName("context")
|
||||||
|
block := mod.Context().AddBasicBlock(fn, "entry")
|
||||||
|
irbuilder := mod.Context().NewBuilder()
|
||||||
|
defer irbuilder.Dispose()
|
||||||
|
irbuilder.SetInsertPointAtEnd(block)
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
pkgInit := llvm.AddFunction(mod, pkg.Pkg.Path()+".init", fnType)
|
||||||
|
irbuilder.CreateCall(fnType, pkgInit, []llvm.Value{llvm.Undef(i8ptrType)}, "")
|
||||||
|
}
|
||||||
|
irbuilder.CreateRetVoid()
|
||||||
|
|
||||||
|
return mod
|
||||||
|
}
|
||||||
|
|
||||||
// optimizeProgram runs a series of optimizations and transformations that are
|
// optimizeProgram runs a series of optimizations and transformations that are
|
||||||
// needed to convert a program to its final form. Some transformations are not
|
// needed to convert a program to its final form. Some transformations are not
|
||||||
// optional and must be run as the compiler expects them to run.
|
// optional and must be run as the compiler expects them to run.
|
||||||
@@ -1069,10 +1116,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.
|
// Insert values from -ldflags="-X ..." into the IR.
|
||||||
err = setGlobalValues(mod, config.Options.GlobalValues)
|
err = setGlobalValues(mod, config.Options.GlobalValues)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+8
-16
@@ -56,7 +56,7 @@ import (
|
|||||||
// depfile but without invalidating its name. For this reason, the depfile is
|
// depfile but without invalidating its name. For this reason, the depfile is
|
||||||
// written on each new compilation (even when it seems unnecessary). However, it
|
// 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.
|
// 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.
|
// Hash input file.
|
||||||
fileHash, err := hashFile(abspath)
|
fileHash, err := hashFile(abspath)
|
||||||
if err != nil {
|
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"))
|
unlock := lock(filepath.Join(goenv.Get("GOCACHE"), fileHash+".c.lock"))
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
ext := ".o"
|
|
||||||
if thinlto {
|
|
||||||
ext = ".bc"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create cache key for the dependencies file.
|
// Create cache key for the dependencies file.
|
||||||
buf, err := json.Marshal(struct {
|
buf, err := json.Marshal(struct {
|
||||||
Path string
|
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.
|
// 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 == nil {
|
||||||
if _, err := os.Stat(outpath); err == nil {
|
if _, err := os.Stat(outpath); err == nil {
|
||||||
return outpath, nil
|
return outpath, nil
|
||||||
@@ -117,7 +112,7 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, thinlto bool,
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
objTmpFile, err := os.CreateTemp(goenv.Get("GOCACHE"), "tmp-*"+ext)
|
objTmpFile, err := os.CreateTemp(goenv.Get("GOCACHE"), "tmp-*.bc")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -127,11 +122,8 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, thinlto bool,
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
depTmpFile.Close()
|
depTmpFile.Close()
|
||||||
flags := append([]string{}, cflags...) // copy cflags
|
flags := append([]string{}, cflags...) // copy cflags
|
||||||
flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name()) // autogenerate dependencies
|
flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name(), "-flto=thin") // autogenerate dependencies
|
||||||
if thinlto {
|
|
||||||
flags = append(flags, "-flto=thin")
|
|
||||||
}
|
|
||||||
flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath)
|
flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath)
|
||||||
if strings.ToLower(filepath.Ext(abspath)) == ".s" {
|
if strings.ToLower(filepath.Ext(abspath)) == ".s" {
|
||||||
// If this is an assembly file (.s or .S, lowercase or uppercase), then
|
// 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.
|
// Move temporary object file to final location.
|
||||||
outpath, err := makeCFileCachePath(dependencySlice, depfileNameHash, ext)
|
outpath, err := makeCFileCachePath(dependencySlice, depfileNameHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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
|
// 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
|
// 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.
|
// 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.
|
// Hash all input files.
|
||||||
fileHashes := make(map[string]string, len(paths))
|
fileHashes := make(map[string]string, len(paths))
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
@@ -229,7 +221,7 @@ func makeCFileCachePath(paths []string, depfileNameHash, ext string) (string, er
|
|||||||
outFileNameBuf := sha512.Sum512_224(buf)
|
outFileNameBuf := sha512.Sum512_224(buf)
|
||||||
cacheKey := hex.EncodeToString(outFileNameBuf[:])
|
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
|
return outpath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-8
@@ -118,10 +118,6 @@ var (
|
|||||||
// pack: data created when storing a constant in an interface for example
|
// pack: data created when storing a constant in an interface for example
|
||||||
// string: buffer behind strings
|
// string: buffer behind strings
|
||||||
packageSymbolRegexp = regexp.MustCompile(`\$(alloc|embedfsfiles|embedfsslice|embedslice|pack|string)(\.[0-9]+)?$`)
|
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
|
// 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 {
|
if section.Flags&elf.SHF_ALLOC == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if packageSymbolRegexp.MatchString(symbol.Name) || reflectDataRegexp.MatchString(symbol.Name) {
|
if packageSymbolRegexp.MatchString(symbol.Name) {
|
||||||
addresses = append(addresses, addressLine{
|
addresses = append(addresses, addressLine{
|
||||||
Address: symbol.Value,
|
Address: symbol.Value,
|
||||||
Length: symbol.Size,
|
Length: symbol.Size,
|
||||||
@@ -836,9 +832,8 @@ func findPackagePath(path string, packagePathMap map[string]string) string {
|
|||||||
} else if packageSymbolRegexp.MatchString(path) {
|
} else if packageSymbolRegexp.MatchString(path) {
|
||||||
// Parse symbol names like main$alloc or runtime$string.
|
// Parse symbol names like main$alloc or runtime$string.
|
||||||
packagePath = path[:strings.LastIndex(path, "$")]
|
packagePath = path[:strings.LastIndex(path, "$")]
|
||||||
} else if reflectDataRegexp.MatchString(path) {
|
} else if path == "<Go type>" {
|
||||||
// Parse symbol names like reflect.structTypesSidetable.
|
packagePath = "Go types"
|
||||||
packagePath = "Go reflect data"
|
|
||||||
} else if path == "<Go interface assert>" {
|
} else if path == "<Go interface assert>" {
|
||||||
// Interface type assert, generated by the interface lowering pass.
|
// Interface type assert, generated by the interface lowering pass.
|
||||||
packagePath = "Go interface assert"
|
packagePath = "Go interface assert"
|
||||||
|
|||||||
+34
-9
@@ -75,7 +75,8 @@ func (c *Config) GOARM() string {
|
|||||||
|
|
||||||
// BuildTags returns the complete list of build tags used during this build.
|
// BuildTags returns the complete list of build tags used during this build.
|
||||||
func (c *Config) BuildTags() []string {
|
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++ {
|
for i := 1; i <= c.GoMinorVersion; i++ {
|
||||||
tags = append(tags, fmt.Sprintf("go1.%d", i))
|
tags = append(tags, fmt.Sprintf("go1.%d", i))
|
||||||
}
|
}
|
||||||
@@ -164,6 +165,14 @@ func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LTO returns one of the possible LTO configurations: legacy or thin.
|
||||||
|
func (c *Config) LTO() string {
|
||||||
|
if c.Options.LTO != "" {
|
||||||
|
return c.Options.LTO
|
||||||
|
}
|
||||||
|
return "legacy"
|
||||||
|
}
|
||||||
|
|
||||||
// PanicStrategy returns the panic strategy selected for this target. Valid
|
// PanicStrategy returns the panic strategy selected for this target. Valid
|
||||||
// values are "print" (print the panic value, then exit) or "trap" (issue a trap
|
// values are "print" (print the panic value, then exit) or "trap" (issue a trap
|
||||||
// instruction).
|
// instruction).
|
||||||
@@ -190,14 +199,6 @@ func (c *Config) StackSize() uint64 {
|
|||||||
return c.Target.DefaultStackSize
|
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
|
// RP2040BootPatch returns whether the RP2040 boot patch should be applied that
|
||||||
// calculates and patches in the checksum for the 2nd stage bootloader.
|
// calculates and patches in the checksum for the 2nd stage bootloader.
|
||||||
func (c *Config) RP2040BootPatch() bool {
|
func (c *Config) RP2040BootPatch() bool {
|
||||||
@@ -550,3 +551,27 @@ type TestConfig struct {
|
|||||||
CompileTestBinary bool
|
CompileTestBinary bool
|
||||||
// TODO: Filter the test functions to run, include verbose flag, etc
|
// 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ var (
|
|||||||
validPrintSizeOptions = []string{"none", "short", "full"}
|
validPrintSizeOptions = []string{"none", "short", "full"}
|
||||||
validPanicStrategyOptions = []string{"print", "trap"}
|
validPanicStrategyOptions = []string{"print", "trap"}
|
||||||
validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
|
validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
|
||||||
|
validLTOOptions = []string{"legacy", "thin"}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Options contains extra options to give to the compiler. These options are
|
// Options contains extra options to give to the compiler. These options are
|
||||||
@@ -25,6 +26,7 @@ type Options struct {
|
|||||||
GOARM string // environment variable (only used with GOARCH=arm)
|
GOARM string // environment variable (only used with GOARCH=arm)
|
||||||
Target string
|
Target string
|
||||||
Opt string
|
Opt string
|
||||||
|
LTO string
|
||||||
GC string
|
GC string
|
||||||
PanicStrategy string
|
PanicStrategy string
|
||||||
Scheduler string
|
Scheduler string
|
||||||
@@ -107,6 +109,12 @@ func (o *Options) Verify() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o.LTO != "" {
|
||||||
|
if !isInArray(validLTOOptions, o.LTO) {
|
||||||
|
return fmt.Errorf("invalid -lto=%s: valid values are %s", o.LTO, strings.Join(validLTOOptions, ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -326,9 +326,9 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
|||||||
}
|
}
|
||||||
if goarch != "wasm" {
|
if goarch != "wasm" {
|
||||||
suffix := ""
|
suffix := ""
|
||||||
if goos == "windows" {
|
if goos == "windows" && goarch == "amd64" {
|
||||||
// Windows uses a different calling convention from other operating
|
// Windows uses a different calling convention on amd64 from other
|
||||||
// systems so we need separate assembly files.
|
// operating systems so we need separate assembly files.
|
||||||
suffix = "_windows"
|
suffix = "_windows"
|
||||||
}
|
}
|
||||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_"+goarch+suffix+".S")
|
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_"+goarch+suffix+".S")
|
||||||
|
|||||||
+3
-1
@@ -34,7 +34,9 @@ var stdlibAliases = map[string]string{
|
|||||||
// createAlias implements the function (in the builder) as a call to the alias
|
// createAlias implements the function (in the builder) as a call to the alias
|
||||||
// function.
|
// function.
|
||||||
func (b *builder) createAlias(alias llvm.Value) {
|
func (b *builder) createAlias(alias llvm.Value) {
|
||||||
b.llvmFn.SetVisibility(llvm.HiddenVisibility)
|
if !b.LTO {
|
||||||
|
b.llvmFn.SetVisibility(llvm.HiddenVisibility)
|
||||||
|
}
|
||||||
b.llvmFn.SetUnnamedAddr(true)
|
b.llvmFn.SetUnnamedAddr(true)
|
||||||
|
|
||||||
if b.Debug {
|
if b.Debug {
|
||||||
|
|||||||
+29
-7
@@ -47,6 +47,7 @@ type Config struct {
|
|||||||
CodeModel string
|
CodeModel string
|
||||||
RelocationModel string
|
RelocationModel string
|
||||||
SizeLevel int
|
SizeLevel int
|
||||||
|
TinyGoVersion string // for llvm.ident
|
||||||
|
|
||||||
// Various compiler options that determine how code is generated.
|
// Various compiler options that determine how code is generated.
|
||||||
Scheduler string
|
Scheduler string
|
||||||
@@ -54,6 +55,7 @@ type Config struct {
|
|||||||
DefaultStackSize uint64
|
DefaultStackSize uint64
|
||||||
NeedsStackObjects bool
|
NeedsStackObjects bool
|
||||||
Debug bool // Whether to emit debug information in the LLVM module.
|
Debug bool // Whether to emit debug information in the LLVM module.
|
||||||
|
LTO bool // non-legacy LTO (meaning: package bitcode is merged by the linker)
|
||||||
}
|
}
|
||||||
|
|
||||||
// compilerContext contains function-independent data that should still be
|
// compilerContext contains function-independent data that should still be
|
||||||
@@ -321,6 +323,14 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
|
|||||||
llvm.ConstInt(c.ctx.Int32Type(), 4, false).ConstantAsMetadata(),
|
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.Finalize()
|
||||||
c.dibuilder.Destroy()
|
c.dibuilder.Destroy()
|
||||||
}
|
}
|
||||||
@@ -340,12 +350,15 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
|
|||||||
return c.mod, c.diagnostics
|
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
|
// 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
|
// it as a LLVM type, creating it if necessary. It is a shorthand for
|
||||||
// getLLVMType(getRuntimeType(name)).
|
// getLLVMType(getRuntimeType(name)).
|
||||||
func (c *compilerContext) getLLVMRuntimeType(name string) llvm.Type {
|
func (c *compilerContext) getLLVMRuntimeType(name string) llvm.Type {
|
||||||
typ := c.runtimePkg.Scope().Lookup(name).(*types.TypeName).Type()
|
return c.getLLVMType(c.getRuntimeType(name))
|
||||||
return c.getLLVMType(typ)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getLLVMType returns a LLVM type for a Go type. It doesn't recreate already
|
// getLLVMType returns a LLVM type for a Go type. It doesn't recreate already
|
||||||
@@ -885,7 +898,9 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package
|
|||||||
c.createEmbedGlobal(member, global, files)
|
c.createEmbedGlobal(member, global, files)
|
||||||
} else if !info.extern {
|
} else if !info.extern {
|
||||||
global.SetInitializer(llvm.ConstNull(global.GlobalValueType()))
|
global.SetInitializer(llvm.ConstNull(global.GlobalValueType()))
|
||||||
global.SetVisibility(llvm.HiddenVisibility)
|
if !c.LTO {
|
||||||
|
global.SetVisibility(llvm.HiddenVisibility)
|
||||||
|
}
|
||||||
if info.section != "" {
|
if info.section != "" {
|
||||||
global.SetSection(info.section)
|
global.SetSection(info.section)
|
||||||
}
|
}
|
||||||
@@ -932,7 +947,9 @@ func (c *compilerContext) createEmbedGlobal(member *ssa.Global, global llvm.Valu
|
|||||||
}
|
}
|
||||||
strObj := c.getEmbedFileString(files[0])
|
strObj := c.getEmbedFileString(files[0])
|
||||||
global.SetInitializer(strObj)
|
global.SetInitializer(strObj)
|
||||||
global.SetVisibility(llvm.HiddenVisibility)
|
if !c.LTO {
|
||||||
|
global.SetVisibility(llvm.HiddenVisibility)
|
||||||
|
}
|
||||||
|
|
||||||
case *types.Slice:
|
case *types.Slice:
|
||||||
if typ.Elem().Underlying().(*types.Basic).Kind() != types.Byte {
|
if typ.Elem().Underlying().(*types.Basic).Kind() != types.Byte {
|
||||||
@@ -956,7 +973,9 @@ func (c *compilerContext) createEmbedGlobal(member *ssa.Global, global llvm.Valu
|
|||||||
sliceLen := llvm.ConstInt(c.uintptrType, file.Size, false)
|
sliceLen := llvm.ConstInt(c.uintptrType, file.Size, false)
|
||||||
sliceObj := c.ctx.ConstStruct([]llvm.Value{slicePtr, sliceLen, sliceLen}, false)
|
sliceObj := c.ctx.ConstStruct([]llvm.Value{slicePtr, sliceLen, sliceLen}, false)
|
||||||
global.SetInitializer(sliceObj)
|
global.SetInitializer(sliceObj)
|
||||||
global.SetVisibility(llvm.HiddenVisibility)
|
if !c.LTO {
|
||||||
|
global.SetVisibility(llvm.HiddenVisibility)
|
||||||
|
}
|
||||||
|
|
||||||
case *types.Struct:
|
case *types.Struct:
|
||||||
// Assume this is an embed.FS struct:
|
// Assume this is an embed.FS struct:
|
||||||
@@ -1039,7 +1058,9 @@ func (c *compilerContext) createEmbedGlobal(member *ssa.Global, global llvm.Valu
|
|||||||
globalInitializer := llvm.ConstNull(c.getLLVMType(member.Type().(*types.Pointer).Elem()))
|
globalInitializer := llvm.ConstNull(c.getLLVMType(member.Type().(*types.Pointer).Elem()))
|
||||||
globalInitializer = c.builder.CreateInsertValue(globalInitializer, sliceGlobal, 0, "")
|
globalInitializer = c.builder.CreateInsertValue(globalInitializer, sliceGlobal, 0, "")
|
||||||
global.SetInitializer(globalInitializer)
|
global.SetInitializer(globalInitializer)
|
||||||
global.SetVisibility(llvm.HiddenVisibility)
|
if !c.LTO {
|
||||||
|
global.SetVisibility(llvm.HiddenVisibility)
|
||||||
|
}
|
||||||
global.SetAlignment(c.targetData.ABITypeAlignment(globalInitializer.Type()))
|
global.SetAlignment(c.targetData.ABITypeAlignment(globalInitializer.Type()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1086,7 +1107,8 @@ func (b *builder) createFunctionStart(intrinsic bool) {
|
|||||||
// assertion error in llvm-project/llvm/include/llvm/IR/GlobalValue.h:236
|
// assertion error in llvm-project/llvm/include/llvm/IR/GlobalValue.h:236
|
||||||
// is thrown.
|
// is thrown.
|
||||||
if b.llvmFn.Linkage() != llvm.InternalLinkage &&
|
if b.llvmFn.Linkage() != llvm.InternalLinkage &&
|
||||||
b.llvmFn.Linkage() != llvm.PrivateLinkage {
|
b.llvmFn.Linkage() != llvm.PrivateLinkage &&
|
||||||
|
!b.LTO {
|
||||||
b.llvmFn.SetVisibility(llvm.HiddenVisibility)
|
b.llvmFn.SetVisibility(llvm.HiddenVisibility)
|
||||||
}
|
}
|
||||||
b.llvmFn.SetUnnamedAddr(true)
|
b.llvmFn.SetUnnamedAddr(true)
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ func TestCompiler(t *testing.T) {
|
|||||||
{"goroutine.go", "cortex-m-qemu", "tasks"},
|
{"goroutine.go", "cortex-m-qemu", "tasks"},
|
||||||
{"channel.go", "", ""},
|
{"channel.go", "", ""},
|
||||||
{"gc.go", "", ""},
|
{"gc.go", "", ""},
|
||||||
|
{"zeromap.go", "", ""},
|
||||||
}
|
}
|
||||||
if goMinor >= 20 {
|
if goMinor >= 20 {
|
||||||
tests = append(tests, testCase{"go1.20.go", "", ""})
|
tests = append(tests, testCase{"go1.20.go", "", ""})
|
||||||
|
|||||||
+2
-2
@@ -271,7 +271,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
|||||||
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
|
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
|
||||||
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
||||||
values = []llvm.Value{callback, next, typecode, receiverValue}
|
values = []llvm.Value{callback, next, typecode, receiverValue}
|
||||||
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
|
valueTypes = append(valueTypes, b.i8ptrType, b.i8ptrType)
|
||||||
for _, arg := range instr.Call.Args {
|
for _, arg := range instr.Call.Args {
|
||||||
val := b.getValue(arg)
|
val := b.getValue(arg)
|
||||||
values = append(values, val)
|
values = append(values, val)
|
||||||
@@ -476,7 +476,7 @@ func (b *builder) createRunDefers() {
|
|||||||
valueTypes = append(valueTypes, b.getFuncType(callback.Signature()))
|
valueTypes = append(valueTypes, b.getFuncType(callback.Signature()))
|
||||||
} else {
|
} else {
|
||||||
//Expect typecode
|
//Expect typecode
|
||||||
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
|
valueTypes = append(valueTypes, b.i8ptrType, b.i8ptrType)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, arg := range callback.Args {
|
for _, arg := range callback.Args {
|
||||||
|
|||||||
+303
-172
@@ -6,6 +6,7 @@ package compiler
|
|||||||
// interface-lowering.go for more details.
|
// interface-lowering.go for more details.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -15,6 +16,49 @@ import (
|
|||||||
"tinygo.org/x/go-llvm"
|
"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.
|
// 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 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
|
// 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.
|
// 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 {
|
func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
|
||||||
itfValue := b.emitPointerPack([]llvm.Value{val})
|
itfValue := b.emitPointerPack([]llvm.Value{val})
|
||||||
itfTypeCodeGlobal := b.getTypeCode(typ)
|
itfType := b.getTypeCode(typ)
|
||||||
itfTypeCode := b.CreatePtrToInt(itfTypeCodeGlobal, b.uintptrType, "")
|
|
||||||
itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
|
itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
|
||||||
itf = b.CreateInsertValue(itf, itfTypeCode, 0, "")
|
itf = b.CreateInsertValue(itf, itfType, 0, "")
|
||||||
itf = b.CreateInsertValue(itf, itfValue, 1, "")
|
itf = b.CreateInsertValue(itf, itfValue, 1, "")
|
||||||
return itf
|
return itf
|
||||||
}
|
}
|
||||||
@@ -41,118 +84,236 @@ func (b *builder) extractValueFromInterface(itf llvm.Value, llvmType llvm.Type)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getTypeCode returns a reference to a type code.
|
// getTypeCode returns a reference to a type code.
|
||||||
// It returns a pointer to an external global which should be replaced with the
|
// A type code is a pointer to a constant global that describes the type.
|
||||||
// real type in the interface lowering pass.
|
// 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 {
|
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)
|
globalName := "reflect/types.type:" + getTypeCodeName(typ)
|
||||||
global := c.mod.NamedGlobal(globalName)
|
global := c.mod.NamedGlobal(globalName)
|
||||||
if global.IsNil() {
|
if global.IsNil() {
|
||||||
// Create a new typecode global.
|
var typeFields []llvm.Value
|
||||||
global = llvm.AddGlobal(c.mod, c.getLLVMRuntimeType("typecodeID"), globalName)
|
// Define the type fields. These must match the structs in
|
||||||
// Some type classes contain more information for underlying types or
|
// src/reflect/type.go (ptrType, arrayType, etc). See the comment at the
|
||||||
// element types. Store it directly in the typecode global to make
|
// top of src/reflect/type.go for more information on the layout of these structs.
|
||||||
// reflect lowering simpler.
|
typeFieldTypes := []*types.Var{
|
||||||
var references llvm.Value
|
types.NewVar(token.NoPos, nil, "kind", types.Typ[types.Int8]),
|
||||||
var length int64
|
}
|
||||||
var methodSet llvm.Value
|
|
||||||
var ptrTo llvm.Value
|
|
||||||
var typeAssert llvm.Value
|
|
||||||
switch typ := typ.(type) {
|
switch typ := typ.(type) {
|
||||||
|
case *types.Basic:
|
||||||
|
typeFieldTypes = append(typeFieldTypes,
|
||||||
|
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
|
||||||
|
)
|
||||||
case *types.Named:
|
case *types.Named:
|
||||||
references = c.getTypeCode(typ.Underlying())
|
typeFieldTypes = append(typeFieldTypes,
|
||||||
case *types.Chan:
|
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
|
||||||
references = c.getTypeCode(typ.Elem())
|
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:
|
case *types.Pointer:
|
||||||
references = c.getTypeCode(typ.Elem())
|
typeFieldTypes = append(typeFieldTypes,
|
||||||
case *types.Slice:
|
types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]),
|
||||||
references = c.getTypeCode(typ.Elem())
|
)
|
||||||
case *types.Array:
|
case *types.Array:
|
||||||
references = c.getTypeCode(typ.Elem())
|
typeFieldTypes = append(typeFieldTypes,
|
||||||
length = typ.Len()
|
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]),
|
||||||
|
)
|
||||||
case *types.Struct:
|
case *types.Struct:
|
||||||
// Take a pointer to the typecodeID of the first field (if it exists).
|
typeFieldTypes = append(typeFieldTypes,
|
||||||
structGlobal := c.makeStructTypeFields(typ)
|
types.NewVar(token.NoPos, nil, "numFields", types.Typ[types.Uint16]),
|
||||||
references = llvm.ConstBitCast(structGlobal, global.Type())
|
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:
|
case *types.Interface:
|
||||||
methodSetGlobal := c.getInterfaceMethodSet(typ)
|
typeFieldTypes = append(typeFieldTypes,
|
||||||
references = llvm.ConstBitCast(methodSetGlobal, global.Type())
|
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 {
|
if hasMethodSet {
|
||||||
methodSet = c.getTypeMethodSet(typ)
|
// This method set is appended at the start of the struct. It is
|
||||||
} else {
|
// removed in the interface lowering pass.
|
||||||
typeAssert = c.getInterfaceImplementsFunc(typ)
|
// TODO: don't remove these and instead do what upstream Go is doing
|
||||||
typeAssert = llvm.ConstPtrToInt(typeAssert, c.uintptrType)
|
// 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 {
|
globalType := types.NewStruct(typeFieldTypes, nil)
|
||||||
ptrTo = c.getTypeCode(types.NewPointer(typ))
|
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
|
||||||
|
}
|
||||||
|
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())
|
// Prepend metadata byte.
|
||||||
if !references.IsNil() {
|
typeFields = append([]llvm.Value{
|
||||||
globalValue = c.builder.CreateInsertValue(globalValue, references, 0, "")
|
llvm.ConstInt(c.ctx.Int8Type(), uint64(metabyte), false),
|
||||||
}
|
}, typeFields...)
|
||||||
if length != 0 {
|
if hasMethodSet {
|
||||||
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
|
typeFields = append([]llvm.Value{
|
||||||
globalValue = c.builder.CreateInsertValue(globalValue, lengthValue, 1, "")
|
llvm.ConstBitCast(c.getTypeMethodSet(typ), c.i8ptrType),
|
||||||
}
|
}, typeFields...)
|
||||||
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, "")
|
|
||||||
}
|
}
|
||||||
|
alignment := c.targetData.TypeAllocSize(c.i8ptrType)
|
||||||
|
globalValue := c.ctx.ConstStruct(typeFields, false)
|
||||||
global.SetInitializer(globalValue)
|
global.SetInitializer(globalValue)
|
||||||
global.SetLinkage(llvm.LinkOnceODRLinkage)
|
global.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||||
global.SetGlobalConstant(true)
|
global.SetGlobalConstant(true)
|
||||||
}
|
global.SetAlignment(int(alignment))
|
||||||
return global
|
if c.Debug {
|
||||||
}
|
file := c.getDIFile("<Go type>")
|
||||||
|
diglobal := c.dibuilder.CreateGlobalVariableExpression(file, llvm.DIGlobalVariableExpression{
|
||||||
// makeStructTypeFields creates a new global that stores all type information
|
Name: "type " + typ.String(),
|
||||||
// related to this struct type, and returns the resulting global. This global is
|
File: file,
|
||||||
// actually an array of all the fields in the structs.
|
Line: 1,
|
||||||
func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
|
Type: c.getDIType(globalType),
|
||||||
// The global is an array of runtime.structField structs.
|
LocalToUnit: false,
|
||||||
runtimeStructField := c.getLLVMRuntimeType("structField")
|
Expr: c.dibuilder.CreateExpression(nil),
|
||||||
structGlobalType := llvm.ArrayType(runtimeStructField, typ.NumFields())
|
AlignInBits: uint32(alignment * 8),
|
||||||
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),
|
|
||||||
})
|
})
|
||||||
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)
|
offset := uint64(0)
|
||||||
structGlobal.SetUnnamedAddr(true)
|
if hasMethodSet {
|
||||||
structGlobal.SetLinkage(llvm.PrivateLinkage)
|
// The pointer to the method set is always the first element of the
|
||||||
return structGlobal
|
// 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.Bool: "bool",
|
||||||
types.Int: "int",
|
types.Int: "int",
|
||||||
types.Int8: "int8",
|
types.Int8: "int8",
|
||||||
@@ -183,7 +344,7 @@ func getTypeCodeName(t types.Type) string {
|
|||||||
case *types.Array:
|
case *types.Array:
|
||||||
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
|
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
|
||||||
case *types.Basic:
|
case *types.Basic:
|
||||||
return "basic:" + basicTypes[t.Kind()]
|
return "basic:" + basicTypeNames[t.Kind()]
|
||||||
case *types.Chan:
|
case *types.Chan:
|
||||||
return "chan:" + getTypeCodeName(t.Elem())
|
return "chan:" + getTypeCodeName(t.Elem())
|
||||||
case *types.Interface:
|
case *types.Interface:
|
||||||
@@ -235,75 +396,40 @@ func getTypeCodeName(t types.Type) string {
|
|||||||
// getTypeMethodSet returns a reference (GEP) to a global method set. This
|
// getTypeMethodSet returns a reference (GEP) to a global method set. This
|
||||||
// method set should be unreferenced after the interface lowering pass.
|
// method set should be unreferenced after the interface lowering pass.
|
||||||
func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||||
global := c.mod.NamedGlobal(typ.String() + "$methodset")
|
globalName := typ.String() + "$methodset"
|
||||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
global := c.mod.NamedGlobal(globalName)
|
||||||
if !global.IsNil() {
|
if global.IsNil() {
|
||||||
// the method set already exists
|
ms := c.program.MethodSets.MethodSet(typ)
|
||||||
return llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{zero, zero})
|
|
||||||
}
|
|
||||||
|
|
||||||
ms := c.program.MethodSets.MethodSet(typ)
|
// Create method set.
|
||||||
if ms.Len() == 0 {
|
var signatures, wrappers []llvm.Value
|
||||||
// no methods, so can leave that one out
|
for i := 0; i < ms.Len(); i++ {
|
||||||
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
|
method := ms.At(i)
|
||||||
}
|
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
|
||||||
|
signatures = append(signatures, signatureGlobal)
|
||||||
methods := make([]llvm.Value, ms.Len())
|
fn := c.program.MethodValue(method)
|
||||||
interfaceMethodInfoType := c.getLLVMRuntimeType("interfaceMethodInfo")
|
llvmFnType, llvmFn := c.getFunction(fn)
|
||||||
for i := 0; i < ms.Len(); i++ {
|
if llvmFn.IsNil() {
|
||||||
method := ms.At(i)
|
// compiler error, so panic
|
||||||
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
|
panic("cannot find function: " + c.getFunctionInfo(fn).linkName)
|
||||||
fn := c.program.MethodValue(method)
|
}
|
||||||
llvmFnType, llvmFn := c.getFunction(fn)
|
wrapper := c.getInterfaceInvokeWrapper(fn, llvmFnType, llvmFn)
|
||||||
if llvmFn.IsNil() {
|
wrappers = append(wrappers, wrapper)
|
||||||
// compiler error, so panic
|
|
||||||
panic("cannot find function: " + c.getFunctionInfo(fn).linkName)
|
|
||||||
}
|
}
|
||||||
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
|
// Construct global value.
|
||||||
// given named interface type. This method set is used by the interface lowering
|
globalValue := c.ctx.ConstStruct([]llvm.Value{
|
||||||
// pass.
|
llvm.ConstInt(c.uintptrType, uint64(ms.Len()), false),
|
||||||
func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
|
llvm.ConstArray(c.i8ptrType, signatures),
|
||||||
name := typ.String()
|
c.ctx.ConstStruct(wrappers, false),
|
||||||
if _, ok := typ.(*types.Named); !ok {
|
}, false)
|
||||||
// Anonymous interface.
|
global = llvm.AddGlobal(c.mod, globalValue.Type(), globalName)
|
||||||
name = "reflect/types.interface:" + name
|
global.SetInitializer(globalValue)
|
||||||
|
global.SetGlobalConstant(true)
|
||||||
|
global.SetUnnamedAddr(true)
|
||||||
|
global.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||||
}
|
}
|
||||||
global := c.mod.NamedGlobal(name + "$interface")
|
return global
|
||||||
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})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getMethodSignatureName returns a unique name (that can be used as the name of
|
// getMethodSignatureName returns a unique name (that can be used as the name of
|
||||||
@@ -364,17 +490,22 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
|||||||
commaOk = b.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{actualTypeNum}, "")
|
commaOk = b.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{actualTypeNum}, "")
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType)
|
if b.LTO {
|
||||||
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
|
assertedTypeCodeGlobal := b.getTypeCode(expr.AssertedType)
|
||||||
if assertedTypeCodeGlobal.IsNil() {
|
commaOk = b.CreateICmp(llvm.IntEQ, actualTypeNum, assertedTypeCodeGlobal, "commaok")
|
||||||
// Create a new typecode global.
|
} else {
|
||||||
assertedTypeCodeGlobal = llvm.AddGlobal(b.mod, b.ctx.Int8Type(), globalName)
|
globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType)
|
||||||
assertedTypeCodeGlobal.SetGlobalConstant(true)
|
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
|
||||||
|
if assertedTypeCodeGlobal.IsNil() {
|
||||||
|
// Create a new typecode global.
|
||||||
|
assertedTypeCodeGlobal = llvm.AddGlobal(b.mod, b.ctx.Int8Type(), globalName)
|
||||||
|
assertedTypeCodeGlobal.SetGlobalConstant(true)
|
||||||
|
}
|
||||||
|
// Type assert on concrete type.
|
||||||
|
// Call runtime.typeAssert, which will be lowered to a simple icmp
|
||||||
|
// or const false in the interface lowering pass.
|
||||||
|
commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
|
||||||
}
|
}
|
||||||
// Type assert on concrete type.
|
|
||||||
// Call runtime.typeAssert, which will be lowered to a simple icmp or
|
|
||||||
// const false in the interface lowering pass.
|
|
||||||
commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add 2 new basic blocks (that should get optimized away): one for the
|
// Add 2 new basic blocks (that should get optimized away): one for the
|
||||||
@@ -443,7 +574,7 @@ func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) ll
|
|||||||
fnName := getTypeCodeName(assertedType.Underlying()) + ".$typeassert"
|
fnName := getTypeCodeName(assertedType.Underlying()) + ".$typeassert"
|
||||||
llvmFn := c.mod.NamedFunction(fnName)
|
llvmFn := c.mod.NamedFunction(fnName)
|
||||||
if llvmFn.IsNil() {
|
if llvmFn.IsNil() {
|
||||||
llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.uintptrType}, false)
|
llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.i8ptrType}, false)
|
||||||
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
|
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
|
||||||
c.addStandardDeclaredAttributes(llvmFn)
|
c.addStandardDeclaredAttributes(llvmFn)
|
||||||
methods := c.getMethodsString(assertedType.Underlying().(*types.Interface))
|
methods := c.getMethodsString(assertedType.Underlying().(*types.Interface))
|
||||||
@@ -464,7 +595,7 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
|
|||||||
for i := 0; i < sig.Params().Len(); i++ {
|
for i := 0; i < sig.Params().Len(); i++ {
|
||||||
paramTuple = append(paramTuple, sig.Params().At(i))
|
paramTuple = append(paramTuple, sig.Params().At(i))
|
||||||
}
|
}
|
||||||
paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.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))
|
llvmFnType := c.getRawFuncType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false))
|
||||||
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
|
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
|
||||||
c.addStandardDeclaredAttributes(llvmFn)
|
c.addStandardDeclaredAttributes(llvmFn)
|
||||||
@@ -601,7 +732,7 @@ func typestring(t types.Type) string {
|
|||||||
case *types.Array:
|
case *types.Array:
|
||||||
return "[" + strconv.FormatInt(t.Len(), 10) + "]" + typestring(t.Elem())
|
return "[" + strconv.FormatInt(t.Len(), 10) + "]" + typestring(t.Elem())
|
||||||
case *types.Basic:
|
case *types.Basic:
|
||||||
return basicTypes[t.Kind()]
|
return basicTypeNames[t.Kind()]
|
||||||
case *types.Chan:
|
case *types.Chan:
|
||||||
switch t.Dir() {
|
switch t.Dir() {
|
||||||
case types.SendRecv:
|
case types.SendRecv:
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
|
|||||||
globalLLVMType := b.getLLVMType(globalType)
|
globalLLVMType := b.getLLVMType(globalType)
|
||||||
globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10)
|
globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10)
|
||||||
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
|
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
|
||||||
global.SetVisibility(llvm.HiddenVisibility)
|
if !b.LTO {
|
||||||
|
global.SetVisibility(llvm.HiddenVisibility)
|
||||||
|
}
|
||||||
global.SetGlobalConstant(true)
|
global.SetGlobalConstant(true)
|
||||||
global.SetUnnamedAddr(true)
|
global.SetUnnamedAddr(true)
|
||||||
initializer := llvm.ConstNull(globalLLVMType)
|
initializer := llvm.ConstNull(globalLLVMType)
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ func (b *builder) defineIntrinsicFunction() {
|
|||||||
b.createMemoryCopyImpl()
|
b.createMemoryCopyImpl()
|
||||||
case name == "runtime.memzero":
|
case name == "runtime.memzero":
|
||||||
b.createMemoryZeroImpl()
|
b.createMemoryZeroImpl()
|
||||||
|
case name == "runtime.KeepAlive":
|
||||||
|
b.createKeepAliveImpl()
|
||||||
case strings.HasPrefix(name, "runtime/volatile.Load"):
|
case strings.HasPrefix(name, "runtime/volatile.Load"):
|
||||||
b.createVolatileLoad()
|
b.createVolatileLoad()
|
||||||
case strings.HasPrefix(name, "runtime/volatile.Store"):
|
case strings.HasPrefix(name, "runtime/volatile.Store"):
|
||||||
@@ -87,6 +89,29 @@ func (b *builder) createMemoryZeroImpl() {
|
|||||||
b.CreateRetVoid()
|
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{
|
var mathToLLVMMapping = map[string]string{
|
||||||
"math.Ceil": "llvm.ceil.f64",
|
"math.Ceil": "llvm.ceil.f64",
|
||||||
"math.Exp": "llvm.exp.f64",
|
"math.Exp": "llvm.exp.f64",
|
||||||
|
|||||||
+78
-1
@@ -89,6 +89,7 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val
|
|||||||
// growth.
|
// growth.
|
||||||
mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
|
mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||||
b.CreateStore(key, mapKeyAlloca)
|
b.CreateStore(key, mapKeyAlloca)
|
||||||
|
b.zeroUndefBytes(b.getLLVMType(keyType), mapKeyAlloca)
|
||||||
// Fetch the value from the hashmap.
|
// Fetch the value from the hashmap.
|
||||||
params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize}
|
params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize}
|
||||||
commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "")
|
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
|
// key can be compared with runtime.memequal
|
||||||
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
|
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||||
b.CreateStore(key, keyAlloca)
|
b.CreateStore(key, keyAlloca)
|
||||||
|
b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca)
|
||||||
params := []llvm.Value{m, keyPtr, valuePtr}
|
params := []llvm.Value{m, keyPtr, valuePtr}
|
||||||
b.createRuntimeCall("hashmapBinarySet", params, "")
|
b.createRuntimeCall("hashmapBinarySet", params, "")
|
||||||
b.emitLifetimeEnd(keyPtr, keySize)
|
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) {
|
} else if hashmapIsBinaryKey(keyType) {
|
||||||
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
|
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||||
b.CreateStore(key, keyAlloca)
|
b.CreateStore(key, keyAlloca)
|
||||||
|
b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca)
|
||||||
params := []llvm.Value{m, keyPtr}
|
params := []llvm.Value{m, keyPtr}
|
||||||
b.createRuntimeCall("hashmapBinaryDelete", params, "")
|
b.createRuntimeCall("hashmapBinaryDelete", params, "")
|
||||||
b.emitLifetimeEnd(keyPtr, keySize)
|
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
|
// 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 {
|
func hashmapIsBinaryKey(keyType types.Type) bool {
|
||||||
switch keyType := keyType.(type) {
|
switch keyType := keyType.(type) {
|
||||||
case *types.Basic:
|
case *types.Basic:
|
||||||
@@ -263,3 +267,76 @@ func hashmapIsBinaryKey(keyType types.Type) bool {
|
|||||||
return false
|
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
@@ -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"
|
target triple = "thumbv7m-unknown-unknown-eabi"
|
||||||
|
|
||||||
%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i1, %runtime._interface }
|
%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 }
|
%runtime._defer = type { i32, ptr }
|
||||||
|
|
||||||
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
|
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
|
||||||
|
|||||||
Vendored
+5
-5
@@ -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 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"
|
target triple = "wasm32-unknown-wasi"
|
||||||
|
|
||||||
%runtime.typecodeID = type { ptr, i32, ptr, ptr, i32 }
|
%runtime._interface = type { ptr, ptr }
|
||||||
%runtime._interface = type { i32, ptr }
|
|
||||||
|
|
||||||
@main.scalar1 = hidden global ptr null, align 4
|
@main.scalar1 = hidden global ptr null, align 4
|
||||||
@main.scalar2 = 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
|
@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-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" }
|
@"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: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 %runtime.typecodeID { ptr @"reflect/types.type:basic:complex128", i32 0, ptr null, ptr null, i32 0 }
|
@"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
|
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
|
||||||
|
|
||||||
@@ -129,7 +128,8 @@ entry:
|
|||||||
store double %v.r, ptr %0, align 8
|
store double %v.r, ptr %0, align 8
|
||||||
%.repack1 = getelementptr inbounds { double, double }, ptr %0, i32 0, i32 1
|
%.repack1 = getelementptr inbounds { double, double }, ptr %0, i32 0, i32 1
|
||||||
store double %v.i, ptr %.repack1, align 8
|
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
|
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #2
|
||||||
ret %runtime._interface %1
|
ret %runtime._interface %1
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-11
@@ -145,34 +145,34 @@ entry:
|
|||||||
declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #0
|
declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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:
|
entry:
|
||||||
%0 = call ptr @runtime.alloc(i32 16, ptr null, ptr undef) #8
|
%0 = call ptr @runtime.alloc(i32 16, ptr null, ptr undef) #8
|
||||||
store ptr %itf.value, ptr %0, align 4
|
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
|
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
|
store i32 4, ptr %.repack1, align 4
|
||||||
%2 = getelementptr inbounds { ptr, %runtime._string, i32 }, ptr %0, i32 0, i32 2
|
%2 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 2
|
||||||
store i32 %itf.typecode, ptr %2, align 4
|
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
|
%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
|
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
|
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
|
; Function Attrs: nounwind
|
||||||
define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #7 {
|
define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #7 {
|
||||||
entry:
|
entry:
|
||||||
%1 = load ptr, ptr %0, align 4
|
%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
|
%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
|
%5 = load i32, ptr %4, align 4
|
||||||
%6 = getelementptr inbounds { ptr, ptr, i32, i32 }, ptr %0, i32 0, i32 3
|
%6 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 3
|
||||||
%7 = load i32, ptr %6, align 4
|
%7 = load ptr, ptr %6, align 4
|
||||||
call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, i32 %7, ptr undef) #8
|
call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #8
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-11
@@ -154,35 +154,35 @@ entry:
|
|||||||
declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #0
|
declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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:
|
entry:
|
||||||
%stackalloc = alloca i8, align 1
|
%stackalloc = alloca i8, align 1
|
||||||
%0 = call ptr @runtime.alloc(i32 16, ptr null, ptr undef) #8
|
%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
|
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #8
|
||||||
store ptr %itf.value, ptr %0, align 4
|
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
|
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
|
store i32 4, ptr %.repack1, align 4
|
||||||
%2 = getelementptr inbounds { ptr, %runtime._string, i32 }, ptr %0, i32 0, i32 2
|
%2 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 2
|
||||||
store i32 %itf.typecode, ptr %2, align 4
|
store ptr %itf.typecode, ptr %2, align 4
|
||||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 16384, 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 16384, ptr undef) #8
|
||||||
ret void
|
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
|
; Function Attrs: nounwind
|
||||||
define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #7 {
|
define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #7 {
|
||||||
entry:
|
entry:
|
||||||
%1 = load ptr, ptr %0, align 4
|
%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
|
%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
|
%5 = load i32, ptr %4, align 4
|
||||||
%6 = getelementptr inbounds { ptr, ptr, i32, i32 }, ptr %0, i32 0, i32 3
|
%6 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 3
|
||||||
%7 = load i32, ptr %6, align 4
|
%7 = load ptr, ptr %6, align 4
|
||||||
call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, i32 %7, ptr undef) #8
|
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
|
call void @runtime.deadlock(ptr undef) #8
|
||||||
unreachable
|
unreachable
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+34
-35
@@ -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 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"
|
target triple = "wasm32-unknown-wasi"
|
||||||
|
|
||||||
%runtime.typecodeID = type { ptr, i32, ptr, ptr, i32 }
|
%runtime._interface = type { ptr, ptr }
|
||||||
%runtime._interface = type { i32, ptr }
|
|
||||||
%runtime._string = type { ptr, i32 }
|
%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: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 %runtime.typecodeID { ptr @"reflect/types.type:basic:int", i32 0, ptr null, ptr null, i32 0 }
|
@"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 %runtime.typecodeID { ptr @"reflect/types.type:named:error", i32 0, ptr null, ptr null, i32 0 }
|
@"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 %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: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 %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/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/methods.Error() string" = linkonce_odr constant i8 0, align 1
|
@"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.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x ptr] [ptr @"reflect/methods.Error() string"]
|
@"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: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: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.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.typeid:basic:int" = external constant i8
|
@"reflect/types.typeid:basic:int" = external constant i8
|
||||||
|
|
||||||
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
|
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 {
|
define hidden %runtime._interface @main.simpleType(ptr %context) unnamed_addr #1 {
|
||||||
entry:
|
entry:
|
||||||
%stackalloc = alloca i8, align 1
|
%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
|
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
|
; Function Attrs: nounwind
|
||||||
define hidden %runtime._interface @main.pointerType(ptr %context) unnamed_addr #1 {
|
define hidden %runtime._interface @main.pointerType(ptr %context) unnamed_addr #1 {
|
||||||
entry:
|
entry:
|
||||||
%stackalloc = alloca i8, align 1
|
%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
|
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
|
; Function Attrs: nounwind
|
||||||
define hidden %runtime._interface @main.interfaceType(ptr %context) unnamed_addr #1 {
|
define hidden %runtime._interface @main.interfaceType(ptr %context) unnamed_addr #1 {
|
||||||
entry:
|
entry:
|
||||||
%stackalloc = alloca i8, align 1
|
%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
|
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
|
; Function Attrs: nounwind
|
||||||
define hidden %runtime._interface @main.anonymousInterfaceType(ptr %context) unnamed_addr #1 {
|
define hidden %runtime._interface @main.anonymousInterfaceType(ptr %context) unnamed_addr #1 {
|
||||||
entry:
|
entry:
|
||||||
%stackalloc = alloca i8, align 1
|
%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
|
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
|
; 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:
|
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
|
br i1 %typecode, label %typeassert.ok, label %typeassert.next
|
||||||
|
|
||||||
typeassert.next: ; preds = %typeassert.ok, %entry
|
typeassert.next: ; preds = %typeassert.ok, %entry
|
||||||
@@ -80,12 +75,12 @@ typeassert.ok: ; preds = %entry
|
|||||||
br label %typeassert.next
|
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
|
; 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:
|
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
|
br i1 %0, label %typeassert.ok, label %typeassert.next
|
||||||
|
|
||||||
typeassert.next: ; preds = %typeassert.ok, %entry
|
typeassert.next: ; preds = %typeassert.ok, %entry
|
||||||
@@ -95,10 +90,12 @@ typeassert.ok: ; preds = %entry
|
|||||||
br label %typeassert.next
|
br label %typeassert.next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr) #2
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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:
|
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
|
br i1 %0, label %typeassert.ok, label %typeassert.next
|
||||||
|
|
||||||
typeassert.next: ; preds = %typeassert.ok, %entry
|
typeassert.next: ; preds = %typeassert.ok, %entry
|
||||||
@@ -108,26 +105,28 @@ typeassert.ok: ; preds = %entry
|
|||||||
br label %typeassert.next
|
br label %typeassert.next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(ptr) #3
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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:
|
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
|
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
|
; 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:
|
entry:
|
||||||
%stackalloc = alloca i8, align 1
|
%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
|
%1 = extractvalue %runtime._string %0, 0
|
||||||
call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #6
|
call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #6
|
||||||
ret %runtime._string %0
|
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 #0 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
|
||||||
attributes #1 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
|
attributes #1 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
|
||||||
|
|||||||
Vendored
+37
@@ -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() {
|
||||||
|
|
||||||
|
}
|
||||||
Vendored
+170
@@ -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
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
// Version of TinyGo.
|
// Version of TinyGo.
|
||||||
// Update this value before release of new version of software.
|
// Update this value before release of new version of software.
|
||||||
const Version = "0.27.0"
|
const Version = "0.28.0-dev"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// This variable is set at build time using -ldflags parameters.
|
// This variable is set at build time using -ldflags parameters.
|
||||||
|
|||||||
+30
-56
@@ -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
|
// which case this call won't even get to this point but will
|
||||||
// already be emitted in initAll.
|
// already be emitted in initAll.
|
||||||
continue
|
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":
|
callFn.name == "os.runtime_args" || callFn.name == "internal/task.start" || callFn.name == "internal/task.Current":
|
||||||
// These functions should be run at runtime. Specifically:
|
// These functions should be run at runtime. Specifically:
|
||||||
// * Print and panic functions are best emitted directly without
|
// * 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():])
|
copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():])
|
||||||
dstObj.buffer = dstBuf
|
dstObj.buffer = dstBuf
|
||||||
mem.put(dst.index(), dstObj)
|
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":
|
case callFn.name == "runtime.typeAssert":
|
||||||
// This function must be implemented manually as it is normally
|
// This function must be implemented manually as it is normally
|
||||||
// implemented by the interface lowering pass.
|
// implemented by the interface lowering pass.
|
||||||
@@ -424,15 +388,22 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, mem, r.errorAt(inst, err)
|
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 {
|
if err != nil {
|
||||||
return nil, mem, r.errorAt(inst, err)
|
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)}
|
locals[inst.localIndex] = literalValue{uint8(0)}
|
||||||
break
|
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:") {
|
if strings.TrimPrefix(actualType.Name(), "reflect/types.type:") == strings.TrimPrefix(assertedType.Name(), "reflect/types.typeid:") {
|
||||||
locals[inst.localIndex] = literalValue{uint8(1)}
|
locals[inst.localIndex] = literalValue{uint8(1)}
|
||||||
} else {
|
} else {
|
||||||
@@ -448,11 +419,12 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, mem, r.errorAt(inst, err)
|
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 {
|
if err != nil {
|
||||||
return nil, mem, r.errorAt(inst, err)
|
return nil, mem, r.errorAt(inst, err)
|
||||||
}
|
}
|
||||||
methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer()
|
methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer()
|
||||||
|
numMethods := int(r.builder.CreateExtractValue(methodSet, 0, "").ZExtValue())
|
||||||
llvmFn := inst.llvmInst.CalledValue()
|
llvmFn := inst.llvmInst.CalledValue()
|
||||||
methodSetAttr := llvmFn.GetStringAttributeAtIndex(-1, "tinygo-methods")
|
methodSetAttr := llvmFn.GetStringAttributeAtIndex(-1, "tinygo-methods")
|
||||||
methodSetString := methodSetAttr.GetStringValue()
|
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
|
// Make a set of all the methods on the concrete type, for
|
||||||
// easier checking in the next step.
|
// easier checking in the next step.
|
||||||
concreteTypeMethods := map[string]struct{}{}
|
concreteTypeMethods := map[string]struct{}{}
|
||||||
for i := 0; i < methodSet.Type().ArrayLength(); i++ {
|
for i := 0; i < numMethods; i++ {
|
||||||
methodInfo := r.builder.CreateExtractValue(methodSet, i, "")
|
methodInfo := r.builder.CreateExtractValue(methodSet, 1, "")
|
||||||
name := r.builder.CreateExtractValue(methodInfo, 0, "").Name()
|
name := r.builder.CreateExtractValue(methodInfo, i, "").Name()
|
||||||
concreteTypeMethods[name] = struct{}{}
|
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:])
|
fmt.Fprintln(os.Stderr, indent+"invoke method:", operands[1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the type code of the interface value.
|
// Load the type code and method set of the interface value.
|
||||||
typecodeIDBitCast, err := operands[len(operands)-2].toLLVMValue(inst.llvmInst.Operand(len(operands)-3).Type(), &mem)
|
typecodePtr, err := operands[len(operands)-2].asPointer(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, mem, r.errorAt(inst, err)
|
return nil, mem, r.errorAt(inst, err)
|
||||||
}
|
}
|
||||||
typecodeID := typecodeIDBitCast.Operand(0).Initializer()
|
methodSetPtr, err := mem.load(typecodePtr.addOffset(-int64(r.pointerSize)), r.pointerSize).asPointer(r)
|
||||||
|
if err != nil {
|
||||||
// Load the method set, which is part of the typecodeID object.
|
return nil, mem, r.errorAt(inst, err)
|
||||||
methodSet := stripPointerCasts(r.builder.CreateExtractValue(typecodeID, 2, "")).Initializer()
|
}
|
||||||
|
methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer()
|
||||||
|
|
||||||
// We don't need to load the interface method set.
|
// 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
|
// Iterate through all methods, looking for the one method that
|
||||||
// should be returned.
|
// should be returned.
|
||||||
numMethods := methodSet.Type().ArrayLength()
|
numMethods := int(r.builder.CreateExtractValue(methodSet, 0, "").ZExtValue())
|
||||||
var method llvm.Value
|
var method llvm.Value
|
||||||
for i := 0; i < numMethods; i++ {
|
for i := 0; i < numMethods; i++ {
|
||||||
methodSignatureAgg := r.builder.CreateExtractValue(methodSet, i, "")
|
methodSignatureAgg := r.builder.CreateExtractValue(methodSet, 1, "")
|
||||||
methodSignature := r.builder.CreateExtractValue(methodSignatureAgg, 0, "")
|
methodSignature := r.builder.CreateExtractValue(methodSignatureAgg, i, "")
|
||||||
if methodSignature == signature {
|
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() {
|
if method.IsNil() {
|
||||||
@@ -685,7 +659,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ptr = ptr.addOffset(uint32(offset))
|
ptr = ptr.addOffset(int64(offset))
|
||||||
locals[inst.localIndex] = ptr
|
locals[inst.localIndex] = ptr
|
||||||
if r.debug {
|
if r.debug {
|
||||||
fmt.Fprintln(os.Stderr, indent+"gep:", operands, "->", ptr)
|
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:
|
case llvm.Add:
|
||||||
// This likely means this is part of a
|
// This likely means this is part of a
|
||||||
// unsafe.Pointer(uintptr(ptr) + offset) pattern.
|
// unsafe.Pointer(uintptr(ptr) + offset) pattern.
|
||||||
lhsPtr = lhsPtr.addOffset(uint32(rhs.Uint()))
|
lhsPtr = lhsPtr.addOffset(int64(rhs.Uint()))
|
||||||
locals[inst.localIndex] = lhsPtr
|
locals[inst.localIndex] = lhsPtr
|
||||||
continue
|
continue
|
||||||
case llvm.Xor:
|
case llvm.Xor:
|
||||||
|
|||||||
+2
-2
@@ -501,7 +501,7 @@ func (v pointerValue) offset() uint32 {
|
|||||||
// addOffset essentially does a GEP operation (pointer arithmetic): it adds the
|
// 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
|
// offset to the pointer. It also checks that the offset doesn't overflow the
|
||||||
// maximum offset size (which is 4GB).
|
// 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)}
|
result := pointerValue{v.pointer + uint64(offset)}
|
||||||
if checks && v.index() != result.index() {
|
if checks && v.index() != result.index() {
|
||||||
panic("interp: offset out of range")
|
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
|
// as a ptrtoint, so that they can be used in certain
|
||||||
// optimizations.
|
// optimizations.
|
||||||
name := elementType.StructName()
|
name := elementType.StructName()
|
||||||
if name == "runtime.typecodeID" || name == "runtime.funcValueWithSignature" {
|
if name == "runtime.funcValueWithSignature" {
|
||||||
uintptrType := ctx.IntType(int(mem.r.pointerSize) * 8)
|
uintptrType := ctx.IntType(int(mem.r.pointerSize) * 8)
|
||||||
field = llvm.ConstPtrToInt(field, uintptrType)
|
field = llvm.ConstPtrToInt(field, uintptrType)
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+7
-8
@@ -1,17 +1,16 @@
|
|||||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||||
target triple = "x86_64--linux"
|
target triple = "x86_64--linux"
|
||||||
|
|
||||||
%runtime.typecodeID = type { %runtime.typecodeID*, i64, %runtime.interfaceMethodInfo* }
|
|
||||||
%runtime.interfaceMethodInfo = type { i8*, i64 }
|
|
||||||
|
|
||||||
@main.v1 = global i1 0
|
@main.v1 = global i1 0
|
||||||
@main.v2 = 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.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 {
|
define void @runtime.initAll() unnamed_addr {
|
||||||
entry:
|
entry:
|
||||||
@@ -22,9 +21,9 @@ entry:
|
|||||||
define internal void @main.init() unnamed_addr {
|
define internal void @main.init() unnamed_addr {
|
||||||
entry:
|
entry:
|
||||||
; Test type asserts.
|
; 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
|
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
|
store i1 %typecode2, i1* @main.v2
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -644,7 +644,7 @@ func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Option
|
|||||||
case "qemu-user":
|
case "qemu-user":
|
||||||
port = ":1234"
|
port = ":1234"
|
||||||
// Run in an emulator.
|
// 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 = executeCommand(config.Options, emulator[0], args...)
|
||||||
daemon.Stdout = os.Stdout
|
daemon.Stdout = os.Stdout
|
||||||
daemon.Stderr = os.Stderr
|
daemon.Stderr = os.Stderr
|
||||||
@@ -1357,6 +1357,7 @@ func main() {
|
|||||||
command := os.Args[1]
|
command := os.Args[1]
|
||||||
|
|
||||||
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
|
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
|
||||||
|
lto := flag.String("lto", "legacy", "LTO mode: legacy or thin")
|
||||||
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
|
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
|
||||||
panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)")
|
panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)")
|
||||||
scheduler := flag.String("scheduler", "", "which scheduler to use (none, tasks, asyncify)")
|
scheduler := flag.String("scheduler", "", "which scheduler to use (none, tasks, asyncify)")
|
||||||
@@ -1459,6 +1460,7 @@ func main() {
|
|||||||
Target: *target,
|
Target: *target,
|
||||||
StackSize: stackSize,
|
StackSize: stackSize,
|
||||||
Opt: *opt,
|
Opt: *opt,
|
||||||
|
LTO: *lto,
|
||||||
GC: *gc,
|
GC: *gc,
|
||||||
PanicStrategy: *panicStrategy,
|
PanicStrategy: *panicStrategy,
|
||||||
Scheduler: *scheduler,
|
Scheduler: *scheduler,
|
||||||
|
|||||||
+13
-9
@@ -105,6 +105,13 @@ func TestBuild(t *testing.T) {
|
|||||||
runTestWithConfig("print.go", t, opts, nil, nil)
|
runTestWithConfig("print.go", t, opts, nil, nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("lto=thin", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
opts := optionsFromTarget("", sema)
|
||||||
|
opts.LTO = "thin"
|
||||||
|
runTestWithConfig("init.go", t, opts, nil, nil)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("ldflags", func(t *testing.T) {
|
t.Run("ldflags", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
opts := optionsFromTarget("", sema)
|
opts := optionsFromTarget("", sema)
|
||||||
@@ -180,7 +187,8 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
|
|||||||
// Skip the ones that aren't.
|
// Skip the ones that aren't.
|
||||||
switch name {
|
switch name {
|
||||||
case "reflect.go":
|
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
|
continue
|
||||||
|
|
||||||
case "gc.go":
|
case "gc.go":
|
||||||
@@ -188,20 +196,16 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
case "json.go", "stdlib.go", "testing.go":
|
case "json.go", "stdlib.go", "testing.go":
|
||||||
// Breaks interp.
|
// Too big for AVR. Doesn't fit in flash/RAM.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case "math.go":
|
case "math.go":
|
||||||
// Stuck somewhere, not sure what's happening.
|
// Needs newer picolibc version (for sqrt).
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case "cgo/":
|
case "cgo/":
|
||||||
// CGo does not work on AVR.
|
// CGo function pointers don't work on AVR (needs LLVM 16 and
|
||||||
continue
|
// some compiler changes).
|
||||||
|
|
||||||
case "timers.go":
|
|
||||||
// Doesn't compile:
|
|
||||||
// panic: compiler: could not store type code number inside interface type code
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
package task
|
package task
|
||||||
|
|
||||||
import "unsafe"
|
import (
|
||||||
|
"runtime/interrupt"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
//go:linkname runtimePanic runtime.runtimePanic
|
//go:linkname runtimePanic runtime.runtimePanic
|
||||||
func runtimePanic(str string)
|
func runtimePanic(str string)
|
||||||
@@ -45,6 +48,9 @@ func Pause() {
|
|||||||
if *currentTask.state.canaryPtr != stackCanary {
|
if *currentTask.state.canaryPtr != stackCanary {
|
||||||
runtimePanic("goroutine stack overflow")
|
runtimePanic("goroutine stack overflow")
|
||||||
}
|
}
|
||||||
|
if interrupt.In() {
|
||||||
|
runtimePanic("blocked inside interrupt")
|
||||||
|
}
|
||||||
currentTask.state.pause()
|
currentTask.state.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ _tinygo_startTask:
|
|||||||
#else
|
#else
|
||||||
.section .text.tinygo_startTask
|
.section .text.tinygo_startTask
|
||||||
.global tinygo_startTask
|
.global tinygo_startTask
|
||||||
.type tinygo_startTask, %function
|
|
||||||
tinygo_startTask:
|
tinygo_startTask:
|
||||||
#endif
|
#endif
|
||||||
.cfi_startproc
|
.cfi_startproc
|
||||||
@@ -35,7 +34,6 @@ tinygo_startTask:
|
|||||||
#endif
|
#endif
|
||||||
.cfi_endproc
|
.cfi_endproc
|
||||||
#ifndef __MACH__
|
#ifndef __MACH__
|
||||||
.size tinygo_startTask, .-tinygo_startTask
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@@ -44,7 +42,6 @@ tinygo_startTask:
|
|||||||
_tinygo_swapTask:
|
_tinygo_swapTask:
|
||||||
#else
|
#else
|
||||||
.global tinygo_swapTask
|
.global tinygo_swapTask
|
||||||
.type tinygo_swapTask, %function
|
|
||||||
tinygo_swapTask:
|
tinygo_swapTask:
|
||||||
#endif
|
#endif
|
||||||
// This function gets the following parameters:
|
// This function gets the following parameters:
|
||||||
@@ -52,12 +49,16 @@ tinygo_swapTask:
|
|||||||
// x1 = oldStack *uintptr
|
// x1 = oldStack *uintptr
|
||||||
|
|
||||||
// Save all callee-saved registers:
|
// Save all callee-saved registers:
|
||||||
stp x19, x20, [sp, #-96]!
|
stp x19, x20, [sp, #-160]!
|
||||||
stp x21, x22, [sp, #16]
|
stp x21, x22, [sp, #16]
|
||||||
stp x23, x24, [sp, #32]
|
stp x23, x24, [sp, #32]
|
||||||
stp x25, x26, [sp, #48]
|
stp x25, x26, [sp, #48]
|
||||||
stp x27, x28, [sp, #64]
|
stp x27, x28, [sp, #64]
|
||||||
stp x29, x30, [sp, #80]
|
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.
|
// Save the current stack pointer in oldStack.
|
||||||
mov x8, sp
|
mov x8, sp
|
||||||
@@ -67,10 +68,14 @@ tinygo_swapTask:
|
|||||||
mov sp, x0
|
mov sp, x0
|
||||||
|
|
||||||
// Restore stack state and return.
|
// 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 x29, x30, [sp, #80]
|
||||||
ldp x27, x28, [sp, #64]
|
ldp x27, x28, [sp, #64]
|
||||||
ldp x25, x26, [sp, #48]
|
ldp x25, x26, [sp, #48]
|
||||||
ldp x23, x24, [sp, #32]
|
ldp x23, x24, [sp, #32]
|
||||||
ldp x21, x22, [sp, #16]
|
ldp x21, x22, [sp, #16]
|
||||||
ldp x19, x20, [sp], #96
|
ldp x19, x20, [sp], #160
|
||||||
ret
|
ret
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build scheduler.tasks && arm64 && !windows
|
//go:build scheduler.tasks && arm64
|
||||||
|
|
||||||
package task
|
package task
|
||||||
|
|
||||||
@@ -21,8 +21,16 @@ type calleeSavedRegs struct {
|
|||||||
x27 uintptr
|
x27 uintptr
|
||||||
x28 uintptr
|
x28 uintptr
|
||||||
x29 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.
|
// 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
|
|
||||||
}
|
|
||||||
@@ -56,6 +56,18 @@ var (
|
|||||||
}
|
}
|
||||||
DefaultUART = UART0
|
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
|
// I2C Busses
|
||||||
I2C1 = &I2C{
|
I2C1 = &I2C{
|
||||||
Bus: stm32.I2C1,
|
Bus: stm32.I2C1,
|
||||||
@@ -72,4 +84,5 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
// Enable UARTs Interrupts
|
// Enable UARTs Interrupts
|
||||||
UART0.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART0.handleInterrupt)
|
UART0.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART0.handleInterrupt)
|
||||||
|
UART2.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART2.handleInterrupt)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const (
|
|||||||
|
|
||||||
// Analog pins
|
// Analog pins
|
||||||
const (
|
const (
|
||||||
A0 = D1
|
A0 = D0
|
||||||
A1 = D1
|
A1 = D1
|
||||||
A2 = D2
|
A2 = D2
|
||||||
A3 = D3
|
A3 = D3
|
||||||
|
|||||||
@@ -50,11 +50,11 @@ const (
|
|||||||
|
|
||||||
// I2C pins
|
// I2C pins
|
||||||
const (
|
const (
|
||||||
I2C0_SDA_PIN Pin = D4
|
I2C0_SDA_PIN Pin = D2
|
||||||
I2C0_SCL_PIN Pin = D5
|
I2C0_SCL_PIN Pin = D3
|
||||||
|
|
||||||
I2C1_SDA_PIN Pin = NoPin
|
I2C1_SDA_PIN Pin = D4
|
||||||
I2C1_SCL_PIN Pin = NoPin
|
I2C1_SCL_PIN Pin = D5
|
||||||
)
|
)
|
||||||
|
|
||||||
// SPI pins
|
// SPI pins
|
||||||
|
|||||||
@@ -406,61 +406,61 @@ func (p Pin) getPinCfg() uint8 {
|
|||||||
return uint8(sam.PORT.PINCFG1_0.Get()>>16) & 0xff
|
return uint8(sam.PORT.PINCFG1_0.Get()>>16) & 0xff
|
||||||
case 35: // PB03
|
case 35: // PB03
|
||||||
return uint8(sam.PORT.PINCFG1_0.Get()>>24) & 0xff
|
return uint8(sam.PORT.PINCFG1_0.Get()>>24) & 0xff
|
||||||
case 37: // PB04
|
case 36: // PB04
|
||||||
return uint8(sam.PORT.PINCFG1_4.Get()>>0) & 0xff
|
return uint8(sam.PORT.PINCFG1_4.Get()>>0) & 0xff
|
||||||
case 38: // PB05
|
case 37: // PB05
|
||||||
return uint8(sam.PORT.PINCFG1_4.Get()>>8) & 0xff
|
return uint8(sam.PORT.PINCFG1_4.Get()>>8) & 0xff
|
||||||
case 39: // PB06
|
case 38: // PB06
|
||||||
return uint8(sam.PORT.PINCFG1_4.Get()>>16) & 0xff
|
return uint8(sam.PORT.PINCFG1_4.Get()>>16) & 0xff
|
||||||
case 40: // PB07
|
case 39: // PB07
|
||||||
return uint8(sam.PORT.PINCFG1_4.Get()>>24) & 0xff
|
return uint8(sam.PORT.PINCFG1_4.Get()>>24) & 0xff
|
||||||
case 41: // PB08
|
case 40: // PB08
|
||||||
return uint8(sam.PORT.PINCFG1_8.Get()>>0) & 0xff
|
return uint8(sam.PORT.PINCFG1_8.Get()>>0) & 0xff
|
||||||
case 42: // PB09
|
case 41: // PB09
|
||||||
return uint8(sam.PORT.PINCFG1_8.Get()>>8) & 0xff
|
return uint8(sam.PORT.PINCFG1_8.Get()>>8) & 0xff
|
||||||
case 43: // PB10
|
case 42: // PB10
|
||||||
return uint8(sam.PORT.PINCFG1_8.Get()>>16) & 0xff
|
return uint8(sam.PORT.PINCFG1_8.Get()>>16) & 0xff
|
||||||
case 44: // PB11
|
case 43: // PB11
|
||||||
return uint8(sam.PORT.PINCFG1_8.Get()>>24) & 0xff
|
return uint8(sam.PORT.PINCFG1_8.Get()>>24) & 0xff
|
||||||
case 45: // PB12
|
case 44: // PB12
|
||||||
return uint8(sam.PORT.PINCFG1_12.Get()>>0) & 0xff
|
return uint8(sam.PORT.PINCFG1_12.Get()>>0) & 0xff
|
||||||
case 46: // PB13
|
case 45: // PB13
|
||||||
return uint8(sam.PORT.PINCFG1_12.Get()>>8) & 0xff
|
return uint8(sam.PORT.PINCFG1_12.Get()>>8) & 0xff
|
||||||
case 47: // PB14
|
case 46: // PB14
|
||||||
return uint8(sam.PORT.PINCFG1_12.Get()>>16) & 0xff
|
return uint8(sam.PORT.PINCFG1_12.Get()>>16) & 0xff
|
||||||
case 48: // PB15
|
case 47: // PB15
|
||||||
return uint8(sam.PORT.PINCFG1_12.Get()>>24) & 0xff
|
return uint8(sam.PORT.PINCFG1_12.Get()>>24) & 0xff
|
||||||
case 49: // PB16
|
case 48: // PB16
|
||||||
return uint8(sam.PORT.PINCFG1_16.Get()>>0) & 0xff
|
return uint8(sam.PORT.PINCFG1_16.Get()>>0) & 0xff
|
||||||
case 50: // PB17
|
case 49: // PB17
|
||||||
return uint8(sam.PORT.PINCFG1_16.Get()>>8) & 0xff
|
return uint8(sam.PORT.PINCFG1_16.Get()>>8) & 0xff
|
||||||
case 51: // PB18
|
case 50: // PB18
|
||||||
return uint8(sam.PORT.PINCFG1_16.Get()>>16) & 0xff
|
return uint8(sam.PORT.PINCFG1_16.Get()>>16) & 0xff
|
||||||
case 52: // PB19
|
case 51: // PB19
|
||||||
return uint8(sam.PORT.PINCFG1_16.Get()>>24) & 0xff
|
return uint8(sam.PORT.PINCFG1_16.Get()>>24) & 0xff
|
||||||
case 53: // PB20
|
case 52: // PB20
|
||||||
return uint8(sam.PORT.PINCFG1_20.Get()>>0) & 0xff
|
return uint8(sam.PORT.PINCFG1_20.Get()>>0) & 0xff
|
||||||
case 54: // PB21
|
case 53: // PB21
|
||||||
return uint8(sam.PORT.PINCFG1_20.Get()>>8) & 0xff
|
return uint8(sam.PORT.PINCFG1_20.Get()>>8) & 0xff
|
||||||
case 55: // PB22
|
case 54: // PB22
|
||||||
return uint8(sam.PORT.PINCFG1_20.Get()>>16) & 0xff
|
return uint8(sam.PORT.PINCFG1_20.Get()>>16) & 0xff
|
||||||
case 56: // PB23
|
case 55: // PB23
|
||||||
return uint8(sam.PORT.PINCFG1_20.Get()>>24) & 0xff
|
return uint8(sam.PORT.PINCFG1_20.Get()>>24) & 0xff
|
||||||
case 57: // PB24
|
case 56: // PB24
|
||||||
return uint8(sam.PORT.PINCFG1_24.Get()>>0) & 0xff
|
return uint8(sam.PORT.PINCFG1_24.Get()>>0) & 0xff
|
||||||
case 58: // PB25
|
case 57: // PB25
|
||||||
return uint8(sam.PORT.PINCFG1_24.Get()>>8) & 0xff
|
return uint8(sam.PORT.PINCFG1_24.Get()>>8) & 0xff
|
||||||
case 59: // PB26
|
case 58: // PB26
|
||||||
return uint8(sam.PORT.PINCFG1_24.Get()>>16) & 0xff
|
return uint8(sam.PORT.PINCFG1_24.Get()>>16) & 0xff
|
||||||
case 60: // PB27
|
case 59: // PB27
|
||||||
return uint8(sam.PORT.PINCFG1_24.Get()>>24) & 0xff
|
return uint8(sam.PORT.PINCFG1_24.Get()>>24) & 0xff
|
||||||
case 61: // PB28
|
case 60: // PB28
|
||||||
return uint8(sam.PORT.PINCFG1_28.Get()>>0) & 0xff
|
return uint8(sam.PORT.PINCFG1_28.Get()>>0) & 0xff
|
||||||
case 62: // PB29
|
case 61: // PB29
|
||||||
return uint8(sam.PORT.PINCFG1_28.Get()>>8) & 0xff
|
return uint8(sam.PORT.PINCFG1_28.Get()>>8) & 0xff
|
||||||
case 63: // PB30
|
case 62: // PB30
|
||||||
return uint8(sam.PORT.PINCFG1_28.Get()>>16) & 0xff
|
return uint8(sam.PORT.PINCFG1_28.Get()>>16) & 0xff
|
||||||
case 64: // PB31
|
case 63: // PB31
|
||||||
return uint8(sam.PORT.PINCFG1_28.Get()>>24) & 0xff
|
return uint8(sam.PORT.PINCFG1_28.Get()>>24) & 0xff
|
||||||
default:
|
default:
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ var (
|
|||||||
ErrInvalidTgtAddr = errors.New("invalid target i2c address not in 0..0x80 or is reserved")
|
ErrInvalidTgtAddr = errors.New("invalid target i2c address not in 0..0x80 or is reserved")
|
||||||
ErrI2CGeneric = errors.New("i2c error")
|
ErrI2CGeneric = errors.New("i2c error")
|
||||||
ErrRP2040I2CDisable = errors.New("i2c rp2040 peripheral timeout in disable")
|
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
|
// 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
|
// SCL: 3, 7, 11, 15, 19, 27
|
||||||
func (i2c *I2C) Configure(config I2CConfig) error {
|
func (i2c *I2C) Configure(config I2CConfig) error {
|
||||||
const defaultBaud uint32 = 100_000 // 100kHz standard mode
|
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.
|
// If config pins are zero valued or clock pin is invalid then we set default values.
|
||||||
switch i2c.Bus {
|
switch i2c.Bus {
|
||||||
case rp.I2C0:
|
case rp.I2C0:
|
||||||
@@ -101,6 +103,23 @@ func (i2c *I2C) Configure(config I2CConfig) error {
|
|||||||
config.SDA = I2C1_SDA_PIN
|
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 {
|
if config.Frequency == 0 {
|
||||||
config.Frequency = defaultBaud
|
config.Frequency = defaultBaud
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,6 +40,9 @@ var (
|
|||||||
ErrLSBNotSupported = errors.New("SPI LSB unsupported on PL022")
|
ErrLSBNotSupported = errors.New("SPI LSB unsupported on PL022")
|
||||||
ErrSPITimeout = errors.New("SPI timeout")
|
ErrSPITimeout = errors.New("SPI timeout")
|
||||||
ErrSPIBaud = errors.New("SPI baud too low or above 66.5Mhz")
|
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 {
|
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.
|
// No pin configuration is needed of SCK, SDO and SDI needed after calling Configure.
|
||||||
func (spi SPI) Configure(config SPIConfig) error {
|
func (spi SPI) Configure(config SPIConfig) error {
|
||||||
const defaultBaud uint32 = 115200
|
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.
|
// set default pins if config zero valued or invalid clock pin supplied.
|
||||||
switch spi.Bus {
|
switch spi.Bus {
|
||||||
case rp.SPI0:
|
case rp.SPI0:
|
||||||
@@ -175,6 +178,27 @@ func (spi SPI) Configure(config SPIConfig) error {
|
|||||||
config.SDI = SPI1_SDI_PIN
|
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 {
|
if config.DataBits < 4 || config.DataBits > 16 {
|
||||||
config.DataBits = 8
|
config.DataBits = 8
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,10 +127,10 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
|
|||||||
// Bus is reset
|
// Bus is reset
|
||||||
if (status & rp.USBCTRL_REGS_INTS_BUS_RESET) > 0 {
|
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.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_BUS_RESET)
|
||||||
rp.USBCTRL_REGS.ADDR_ENDP.Set(0)
|
|
||||||
|
|
||||||
initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL)
|
|
||||||
fixRP2040UsbDeviceEnumeration()
|
fixRP2040UsbDeviceEnumeration()
|
||||||
|
|
||||||
|
rp.USBCTRL_REGS.ADDR_ENDP.Set(0)
|
||||||
|
initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ var DescriptorCDCHID = Descriptor{
|
|||||||
0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00,
|
0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00,
|
||||||
0x07, 0x05, 0x83, 0x02, 0x40, 0x00, 0x00,
|
0x07, 0x05, 0x83, 0x02, 0x40, 0x00, 0x00,
|
||||||
0x09, 0x04, 0x02, 0x00, 0x01, 0x03, 0x00, 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,
|
0x07, 0x05, 0x84, 0x03, 0x40, 0x00, 0x01,
|
||||||
},
|
},
|
||||||
HID: map[uint16][]byte{
|
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, 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, 0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38, 0x15, 0x81, 0x25, 0x7f, 0x75, 0x08, 0x95,
|
||||||
0x03, 0x81, 0x06, 0xc0, 0xc0,
|
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
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,16 +238,26 @@ func (kb *keyboard) sendKey(consumer bool, b []byte) bool {
|
|||||||
|
|
||||||
func (kb *keyboard) keyboardSendKeys(consumer bool) bool {
|
func (kb *keyboard) keyboardSendKeys(consumer bool) bool {
|
||||||
var b [9]byte
|
var b [9]byte
|
||||||
b[0] = 0x02
|
|
||||||
b[1] = kb.mod
|
if !consumer {
|
||||||
b[2] = 0x02
|
b[0] = 0x02 // REPORT_ID
|
||||||
b[3] = kb.key[0]
|
b[1] = kb.mod
|
||||||
b[4] = kb.key[1]
|
b[2] = 0x02
|
||||||
b[5] = kb.key[2]
|
b[3] = kb.key[0]
|
||||||
b[6] = kb.key[3]
|
b[4] = kb.key[1]
|
||||||
b[7] = kb.key[4]
|
b[5] = kb.key[2]
|
||||||
b[8] = kb.key[5]
|
b[6] = kb.key[3]
|
||||||
return kb.sendKey(consumer, b[:])
|
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.
|
// Down transmits a key-down event for the given Keycode.
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ const (
|
|||||||
KeyModifierRightAlt Keycode = 0x40 | 0xE000
|
KeyModifierRightAlt Keycode = 0x40 | 0xE000
|
||||||
KeyModifierRightGUI Keycode = 0x80 | 0xE000
|
KeyModifierRightGUI Keycode = 0x80 | 0xE000
|
||||||
|
|
||||||
|
// KeySystemXXX is not supported now
|
||||||
KeySystemPowerDown Keycode = 0x81 | 0xE200
|
KeySystemPowerDown Keycode = 0x81 | 0xE200
|
||||||
KeySystemSleep Keycode = 0x82 | 0xE200
|
KeySystemSleep Keycode = 0x82 | 0xE200
|
||||||
KeySystemWakeUp Keycode = 0x83 | 0xE200
|
KeySystemWakeUp Keycode = 0x83 | 0xE200
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import "unsafe"
|
|||||||
type visit struct {
|
type visit struct {
|
||||||
a1 unsafe.Pointer
|
a1 unsafe.Pointer
|
||||||
a2 unsafe.Pointer
|
a2 unsafe.Pointer
|
||||||
typ rawType
|
typ *rawType
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests for deep equality using reflected types. The map argument tracks
|
// Tests for deep equality using reflected types. The map argument tracks
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+217
-158
@@ -2,36 +2,72 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// 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
|
||||||
|
// - 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
|
package reflect
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The compiler uses a compact encoding to store type information. Unlike the
|
// Flags stored in the first byte of the struct field byte array. Must be kept
|
||||||
// main Go compiler, most of the types are stored directly in the type code.
|
// up to date with compiler/interface.go.
|
||||||
//
|
const (
|
||||||
// Type code bit allocation:
|
structFieldFlagAnonymous = 1 << iota
|
||||||
// xxxxx0: basic types, where xxxxx is the basic type number (never 0).
|
structFieldFlagHasTag
|
||||||
// The higher bits indicate the named type, if any.
|
structFieldFlagIsExported
|
||||||
// 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).
|
|
||||||
|
|
||||||
type Kind uintptr
|
type Kind uint8
|
||||||
|
|
||||||
// Copied from reflect/type.go
|
// Copied from reflect/type.go
|
||||||
// https://golang.org/src/reflect/type.go?s=8302:8316#L217
|
// 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 (
|
const (
|
||||||
Invalid Kind = iota
|
Invalid Kind = iota
|
||||||
Bool
|
Bool
|
||||||
@@ -124,11 +160,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
|
// Copied from reflect/type.go
|
||||||
// https://go.dev/src/reflect/type.go?#L348
|
// https://go.dev/src/reflect/type.go?#L348
|
||||||
|
|
||||||
@@ -346,8 +377,64 @@ type Type interface {
|
|||||||
Out(i int) Type
|
Out(i int) Type
|
||||||
}
|
}
|
||||||
|
|
||||||
// The typecode as used in an interface{}.
|
// Constants for the 'meta' byte.
|
||||||
type rawType uintptr
|
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 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 {
|
func TypeOf(i interface{}) Type {
|
||||||
return ValueOf(i).typecode
|
return ValueOf(i).typecode
|
||||||
@@ -356,70 +443,45 @@ func TypeOf(i interface{}) Type {
|
|||||||
func PtrTo(t Type) Type { return PointerTo(t) }
|
func PtrTo(t Type) Type { return PointerTo(t) }
|
||||||
|
|
||||||
func PointerTo(t Type) Type {
|
func PointerTo(t Type) Type {
|
||||||
if t.Kind() == Pointer {
|
switch t.Kind() {
|
||||||
|
case Pointer:
|
||||||
panic("reflect: cannot make **T type")
|
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"
|
return "T"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t rawType) Kind() Kind {
|
func (t *rawType) Kind() Kind {
|
||||||
if t%2 == 0 {
|
return Kind(t.meta & kindMask)
|
||||||
// basic type
|
|
||||||
return Kind((t >> 1) % 32)
|
|
||||||
} else {
|
|
||||||
return Kind(t>>1)%8 + 19
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Elem returns the element type for channel, slice and array types, the
|
// 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.
|
// 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()
|
return t.elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t rawType) elem() rawType {
|
func (t *rawType) elem() *rawType {
|
||||||
switch t.Kind() {
|
underlying := t.underlying()
|
||||||
case Chan, Pointer, Slice:
|
switch underlying.Kind() {
|
||||||
return t.stripPrefix()
|
case Pointer:
|
||||||
case Array:
|
return (*ptrType)(unsafe.Pointer(underlying)).elem
|
||||||
index := t.stripPrefix()
|
case Chan, Slice, Array:
|
||||||
elem, _ := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&arrayTypesSidetable)) + uintptr(index)))
|
return (*elemType)(unsafe.Pointer(underlying)).elem
|
||||||
return rawType(elem)
|
|
||||||
default: // not implemented: Map
|
default: // not implemented: Map
|
||||||
panic("unimplemented: (reflect.Type).Elem()")
|
panic("unimplemented: (reflect.Type).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)
|
|
||||||
}
|
|
||||||
// Not a named type, so the value is stored directly in the type code.
|
|
||||||
return t >> 5
|
|
||||||
}
|
|
||||||
|
|
||||||
// Field returns the type of the i'th field of this struct type. It panics if t
|
// Field returns the type of the i'th field of this struct type. It panics if t
|
||||||
// is not a struct type.
|
// is not a struct type.
|
||||||
func (t rawType) Field(i int) StructField {
|
func (t *rawType) Field(i int) StructField {
|
||||||
field := t.rawField(i)
|
field := t.rawField(i)
|
||||||
return StructField{
|
return StructField{
|
||||||
Name: field.Name,
|
Name: field.Name,
|
||||||
@@ -435,82 +497,87 @@ func (t rawType) Field(i int) StructField {
|
|||||||
// Type member to an interface.
|
// Type member to an interface.
|
||||||
//
|
//
|
||||||
// For internal use only.
|
// For internal use only.
|
||||||
func (t rawType) rawField(i int) rawStructField {
|
func (t *rawType) rawField(n int) rawStructField {
|
||||||
if t.Kind() != Struct {
|
if t.Kind() != Struct {
|
||||||
panic(&TypeError{"Field"})
|
panic(&TypeError{"Field"})
|
||||||
}
|
}
|
||||||
structIdentifier := t.stripPrefix()
|
descriptor := (*structType)(unsafe.Pointer(t.underlying()))
|
||||||
|
if uint(n) >= uint(descriptor.numField) {
|
||||||
numField, p := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&structTypesSidetable)) + uintptr(structIdentifier)))
|
|
||||||
if uint(i) >= uint(numField) {
|
|
||||||
panic("reflect: field index out of range")
|
panic("reflect: field index out of range")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate over every field in the struct and update the StructField each
|
// Iterate over all the fields to calculate the offset.
|
||||||
// time, until the target field has been reached. This is very much not
|
// This offset could have been stored directly in the array (to make the
|
||||||
// efficient, but it is easy to implement.
|
// lookup faster), but by calculating it on-the-fly a bit of storage can be
|
||||||
// Adding a jump table at the start to jump to the field directly would
|
// saved.
|
||||||
// make this much faster, but that would also impact code size.
|
field := &descriptor.fields[0]
|
||||||
field := rawStructField{}
|
var offset uintptr = 0
|
||||||
offset := uintptr(0)
|
for i := 0; i < n; i++ {
|
||||||
for fieldNum := 0; fieldNum <= i; fieldNum++ {
|
offset += field.fieldType.Size()
|
||||||
// Read some flags of this field, like whether the field is an
|
|
||||||
// embedded field.
|
|
||||||
flagsByte := *(*uint8)(p)
|
|
||||||
p = unsafe.Pointer(uintptr(p) + 1)
|
|
||||||
|
|
||||||
// Read the type of this struct field.
|
// Increment pointer to the next field.
|
||||||
var fieldTypeVal uintptr
|
field = (*structField)(unsafe.Add(unsafe.Pointer(field), unsafe.Sizeof(structField{})))
|
||||||
fieldTypeVal, p = readVarint(p)
|
|
||||||
fieldType := rawType(fieldTypeVal)
|
|
||||||
field.Type = fieldType
|
|
||||||
|
|
||||||
// Move Offset forward to align it to this field's alignment.
|
// Align the offset for the next field.
|
||||||
// Assume alignment is a power of two.
|
offset = align(offset, uintptr(field.fieldType.Align()))
|
||||||
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>"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// 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
|
// arithmetic types (integers, floats, and complex numbers). For other types, it
|
||||||
// will panic.
|
// will panic.
|
||||||
func (t rawType) Bits() int {
|
func (t *rawType) Bits() int {
|
||||||
kind := t.Kind()
|
kind := t.Kind()
|
||||||
if kind >= Int && kind <= Complex128 {
|
if kind >= Int && kind <= Complex128 {
|
||||||
return int(t.Size()) * 8
|
return int(t.Size()) * 8
|
||||||
@@ -520,34 +587,26 @@ func (t rawType) Bits() int {
|
|||||||
|
|
||||||
// Len returns the number of elements in this array. It panics of the type kind
|
// Len returns the number of elements in this array. It panics of the type kind
|
||||||
// is not Array.
|
// is not Array.
|
||||||
func (t rawType) Len() int {
|
func (t *rawType) Len() int {
|
||||||
if t.Kind() != Array {
|
if t.Kind() != Array {
|
||||||
panic(TypeError{"Len"})
|
panic(TypeError{"Len"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip past the element type
|
return int((*arrayType)(unsafe.Pointer(t.underlying())).arrayLen)
|
||||||
arrayIdentifier := t.stripPrefix()
|
|
||||||
_, p := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&arrayTypesSidetable)) + uintptr(arrayIdentifier)))
|
|
||||||
|
|
||||||
// Read the array length.
|
|
||||||
arrayLen, _ := readVarint(p)
|
|
||||||
return int(arrayLen)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NumField returns the number of fields of a struct type. It panics for other
|
// NumField returns the number of fields of a struct type. It panics for other
|
||||||
// type kinds.
|
// type kinds.
|
||||||
func (t rawType) NumField() int {
|
func (t *rawType) NumField() int {
|
||||||
if t.Kind() != Struct {
|
if t.Kind() != Struct {
|
||||||
panic(&TypeError{"NumField"})
|
panic(&TypeError{"NumField"})
|
||||||
}
|
}
|
||||||
structIdentifier := t.stripPrefix()
|
return int((*structType)(unsafe.Pointer(t.underlying())).numField)
|
||||||
n, _ := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&structTypesSidetable)) + uintptr(structIdentifier)))
|
|
||||||
return int(n)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size returns the size in bytes of a given type. It is similar to
|
// Size returns the size in bytes of a given type. It is similar to
|
||||||
// unsafe.Sizeof.
|
// unsafe.Sizeof.
|
||||||
func (t rawType) Size() uintptr {
|
func (t *rawType) Size() uintptr {
|
||||||
switch t.Kind() {
|
switch t.Kind() {
|
||||||
case Bool, Int8, Uint8:
|
case Bool, Int8, Uint8:
|
||||||
return 1
|
return 1
|
||||||
@@ -596,7 +655,7 @@ func (t rawType) Size() uintptr {
|
|||||||
|
|
||||||
// Align returns the alignment of this type. It is similar to calling
|
// Align returns the alignment of this type. It is similar to calling
|
||||||
// unsafe.Alignof.
|
// unsafe.Alignof.
|
||||||
func (t rawType) Align() int {
|
func (t *rawType) Align() int {
|
||||||
switch t.Kind() {
|
switch t.Kind() {
|
||||||
case Bool, Int8, Uint8:
|
case Bool, Int8, Uint8:
|
||||||
return int(unsafe.Alignof(int8(0)))
|
return int(unsafe.Alignof(int8(0)))
|
||||||
@@ -648,14 +707,14 @@ func (t rawType) Align() int {
|
|||||||
|
|
||||||
// FieldAlign returns the alignment if this type is used in a struct field. It
|
// 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.
|
// 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()
|
return t.Align()
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssignableTo returns whether a value of type t can be assigned to a variable
|
// AssignableTo returns whether a value of type t can be assigned to a variable
|
||||||
// of type u.
|
// of type u.
|
||||||
func (t rawType) AssignableTo(u Type) bool {
|
func (t *rawType) AssignableTo(u Type) bool {
|
||||||
if t == u.(rawType) {
|
if t == u.(*rawType) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if u.Kind() == Interface {
|
if u.Kind() == Interface {
|
||||||
@@ -664,7 +723,7 @@ func (t rawType) AssignableTo(u Type) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t rawType) Implements(u Type) bool {
|
func (t *rawType) Implements(u Type) bool {
|
||||||
if u.Kind() != Interface {
|
if u.Kind() != Interface {
|
||||||
panic("reflect: non-interface type passed to Type.Implements")
|
panic("reflect: non-interface type passed to Type.Implements")
|
||||||
}
|
}
|
||||||
@@ -672,7 +731,7 @@ func (t rawType) Implements(u Type) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Comparable returns whether values of this type can be compared to each other.
|
// 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() {
|
switch t.Kind() {
|
||||||
case Bool, Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
|
case Bool, Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
|
||||||
return true
|
return true
|
||||||
@@ -713,31 +772,31 @@ func (t rawType) ChanDir() ChanDir {
|
|||||||
panic("unimplemented: (reflect.Type).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()")
|
panic("unimplemented: (reflect.Type).ConvertibleTo()")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t rawType) IsVariadic() bool {
|
func (t *rawType) IsVariadic() bool {
|
||||||
panic("unimplemented: (reflect.Type).IsVariadic()")
|
panic("unimplemented: (reflect.Type).IsVariadic()")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t rawType) NumIn() int {
|
func (t *rawType) NumIn() int {
|
||||||
panic("unimplemented: (reflect.Type).NumIn()")
|
panic("unimplemented: (reflect.Type).NumIn()")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t rawType) NumOut() int {
|
func (t *rawType) NumOut() int {
|
||||||
panic("unimplemented: (reflect.Type).NumOut()")
|
panic("unimplemented: (reflect.Type).NumOut()")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t rawType) NumMethod() int {
|
func (t *rawType) NumMethod() int {
|
||||||
panic("unimplemented: (reflect.Type).NumMethod()")
|
panic("unimplemented: (reflect.Type).NumMethod()")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t rawType) Name() string {
|
func (t *rawType) Name() string {
|
||||||
panic("unimplemented: (reflect.Type).Name()")
|
panic("unimplemented: (reflect.Type).Name()")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t rawType) Key() Type {
|
func (t *rawType) Key() Type {
|
||||||
panic("unimplemented: (reflect.Type).Key()")
|
panic("unimplemented: (reflect.Type).Key()")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -792,7 +851,7 @@ func (f StructField) IsExported() bool {
|
|||||||
type rawStructField struct {
|
type rawStructField struct {
|
||||||
Name string
|
Name string
|
||||||
PkgPath string
|
PkgPath string
|
||||||
Type rawType
|
Type *rawType
|
||||||
Tag StructTag
|
Tag StructTag
|
||||||
Anonymous bool
|
Anonymous bool
|
||||||
Offset uintptr
|
Offset uintptr
|
||||||
|
|||||||
+12
-10
@@ -17,7 +17,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Value struct {
|
type Value struct {
|
||||||
typecode rawType
|
typecode *rawType
|
||||||
value unsafe.Pointer
|
value unsafe.Pointer
|
||||||
flags valueFlags
|
flags valueFlags
|
||||||
}
|
}
|
||||||
@@ -44,15 +44,15 @@ func Indirect(v Value) Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//go:linkname composeInterface runtime.composeInterface
|
//go:linkname composeInterface runtime.composeInterface
|
||||||
func composeInterface(rawType, unsafe.Pointer) interface{}
|
func composeInterface(unsafe.Pointer, unsafe.Pointer) interface{}
|
||||||
|
|
||||||
//go:linkname decomposeInterface runtime.decomposeInterface
|
//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 {
|
func ValueOf(i interface{}) Value {
|
||||||
typecode, value := decomposeInterface(i)
|
typecode, value := decomposeInterface(i)
|
||||||
return Value{
|
return Value{
|
||||||
typecode: typecode,
|
typecode: (*rawType)(typecode),
|
||||||
value: value,
|
value: value,
|
||||||
flags: valueFlagExported,
|
flags: valueFlagExported,
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ func valueInterfaceUnsafe(v Value) interface{} {
|
|||||||
}
|
}
|
||||||
v.value = unsafe.Pointer(value)
|
v.value = unsafe.Pointer(value)
|
||||||
}
|
}
|
||||||
return composeInterface(v.typecode, v.value)
|
return composeInterface(unsafe.Pointer(v.typecode), v.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v Value) Type() Type {
|
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
|
// 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.
|
// 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
|
return v.typecode
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +205,7 @@ func (v Value) pointer() unsafe.Pointer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v Value) IsValid() bool {
|
func (v Value) IsValid() bool {
|
||||||
return v.typecode != 0
|
return v.typecode != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v Value) CanInterface() bool {
|
func (v Value) CanInterface() bool {
|
||||||
@@ -453,7 +453,7 @@ func (v Value) Elem() Value {
|
|||||||
case Interface:
|
case Interface:
|
||||||
typecode, value := decomposeInterface(*(*interface{})(v.value))
|
typecode, value := decomposeInterface(*(*interface{})(v.value))
|
||||||
return Value{
|
return Value{
|
||||||
typecode: typecode,
|
typecode: (*rawType)(typecode),
|
||||||
value: value,
|
value: value,
|
||||||
flags: v.flags &^ valueFlagIndirect,
|
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 {
|
func (v Value) Index(i int) Value {
|
||||||
switch v.Kind() {
|
switch v.Kind() {
|
||||||
case Slice:
|
case Slice:
|
||||||
@@ -550,7 +552,7 @@ func (v Value) Index(i int) Value {
|
|||||||
panic("reflect: string index out of range")
|
panic("reflect: string index out of range")
|
||||||
}
|
}
|
||||||
return Value{
|
return Value{
|
||||||
typecode: Uint8.basicType(),
|
typecode: uint8Type,
|
||||||
value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Pointer(uintptr(s.data) + uintptr(i))))),
|
value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Pointer(uintptr(s.data) + uintptr(i))))),
|
||||||
flags: v.flags & valueFlagExported,
|
flags: v.flags & valueFlagExported,
|
||||||
}
|
}
|
||||||
@@ -803,7 +805,7 @@ func Zero(typ Type) Value {
|
|||||||
// new value of the given type.
|
// new value of the given type.
|
||||||
func New(typ Type) Value {
|
func New(typ Type) Value {
|
||||||
return Value{
|
return Value{
|
||||||
typecode: PtrTo(typ).(rawType),
|
typecode: PtrTo(typ).(*rawType),
|
||||||
value: alloc(typ.Size(), nil),
|
value: alloc(typ.Size(), nil),
|
||||||
flags: valueFlagExported,
|
flags: valueFlagExported,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ _tinygo_scanCurrentStack:
|
|||||||
#else
|
#else
|
||||||
.section .text.tinygo_scanCurrentStack
|
.section .text.tinygo_scanCurrentStack
|
||||||
.global tinygo_scanCurrentStack
|
.global tinygo_scanCurrentStack
|
||||||
.type tinygo_scanCurrentStack, %function
|
|
||||||
tinygo_scanCurrentStack:
|
tinygo_scanCurrentStack:
|
||||||
#endif
|
#endif
|
||||||
// Sources:
|
// Sources:
|
||||||
@@ -12,12 +11,16 @@ tinygo_scanCurrentStack:
|
|||||||
// * https://godbolt.org/z/qrvrEh
|
// * https://godbolt.org/z/qrvrEh
|
||||||
|
|
||||||
// Save callee-saved registers.
|
// Save callee-saved registers.
|
||||||
stp x29, x30, [sp, #-96]!
|
stp x29, x30, [sp, #-160]!
|
||||||
stp x28, x27, [sp, #16]
|
stp x28, x27, [sp, #16]
|
||||||
stp x26, x25, [sp, #32]
|
stp x26, x25, [sp, #32]
|
||||||
stp x24, x23, [sp, #48]
|
stp x24, x23, [sp, #48]
|
||||||
stp x22, x21, [sp, #64]
|
stp x22, x21, [sp, #64]
|
||||||
stp x20, x19, [sp, #80]
|
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.
|
// Scan the stack.
|
||||||
mov x0, sp
|
mov x0, sp
|
||||||
@@ -28,7 +31,7 @@ tinygo_scanCurrentStack:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Restore stack state and return.
|
// Restore stack state and return.
|
||||||
ldp x29, x30, [sp], #96
|
ldp x29, x30, [sp], #160
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
|
||||||
@@ -38,7 +41,6 @@ _tinygo_longjmp:
|
|||||||
#else
|
#else
|
||||||
.section .text.tinygo_longjmp
|
.section .text.tinygo_longjmp
|
||||||
.global tinygo_longjmp
|
.global tinygo_longjmp
|
||||||
.type tinygo_longjmp, %function
|
|
||||||
tinygo_longjmp:
|
tinygo_longjmp:
|
||||||
#endif
|
#endif
|
||||||
// Note: the code we jump to assumes x0 is set to a non-zero value if we
|
// Note: the code we jump to assumes x0 is set to a non-zero value if we
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -277,6 +277,10 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
|
|||||||
size += align(unsafe.Sizeof(layout))
|
size += align(unsafe.Sizeof(layout))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if interrupt.In() {
|
||||||
|
runtimePanic("alloc in interrupt")
|
||||||
|
}
|
||||||
|
|
||||||
gcTotalAlloc += uint64(size)
|
gcTotalAlloc += uint64(size)
|
||||||
gcMallocs++
|
gcMallocs++
|
||||||
|
|
||||||
@@ -687,3 +691,7 @@ func ReadMemStats(m *MemStats) {
|
|||||||
m.Frees = gcFrees
|
m.Frees = gcFrees
|
||||||
m.Sys = uint64(heapEnd - heapStart)
|
m.Sys = uint64(heapEnd - heapStart)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetFinalizer(obj interface{}, finalizer interface{}) {
|
||||||
|
// Unimplemented.
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package runtime
|
|||||||
// - func free(ptr unsafe.Pointer)
|
// - func free(ptr unsafe.Pointer)
|
||||||
// - func markRoots(start, end uintptr)
|
// - func markRoots(start, end uintptr)
|
||||||
// - func GC()
|
// - func GC()
|
||||||
|
// - func SetFinalizer(obj interface{}, finalizer interface{})
|
||||||
// - func ReadMemStats(ms *runtime.MemStats)
|
// - func ReadMemStats(ms *runtime.MemStats)
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
@@ -51,6 +52,9 @@ func markRoots(start, end uintptr)
|
|||||||
// GC is called to explicitly run garbage collection.
|
// GC is called to explicitly run garbage collection.
|
||||||
func GC()
|
func GC()
|
||||||
|
|
||||||
|
// SetFinalizer registers a finalizer.
|
||||||
|
func SetFinalizer(obj interface{}, finalizer interface{})
|
||||||
|
|
||||||
// ReadMemStats populates m with memory statistics.
|
// ReadMemStats populates m with memory statistics.
|
||||||
func ReadMemStats(ms *MemStats)
|
func ReadMemStats(ms *MemStats)
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ func GC() {
|
|||||||
// No-op.
|
// No-op.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetFinalizer(obj interface{}, finalizer interface{}) {
|
||||||
|
// No-op.
|
||||||
|
}
|
||||||
|
|
||||||
func initHeap() {
|
func initHeap() {
|
||||||
// preinit() may have moved heapStart; reset heapptr
|
// preinit() may have moved heapStart; reset heapptr
|
||||||
heapptr = heapStart
|
heapptr = heapStart
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ func GC() {
|
|||||||
// Unimplemented.
|
// Unimplemented.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetFinalizer(obj interface{}, finalizer interface{}) {
|
||||||
|
// Unimplemented.
|
||||||
|
}
|
||||||
|
|
||||||
func initHeap() {
|
func initHeap() {
|
||||||
// Nothing to initialize.
|
// Nothing to initialize.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -506,7 +506,7 @@ func hashmapFloat64Hash(ptr unsafe.Pointer, seed uintptr) uint32 {
|
|||||||
|
|
||||||
func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 {
|
func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 {
|
||||||
x := reflect.ValueOf(itf)
|
x := reflect.ValueOf(itf)
|
||||||
if x.RawType() == 0 {
|
if x.RawType() == nil {
|
||||||
return 0 // nil interface
|
return 0 // nil interface
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,7 +563,7 @@ func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func hashmapInterfacePtrHash(iptr unsafe.Pointer, size uintptr, seed uintptr) uint32 {
|
func hashmapInterfacePtrHash(iptr unsafe.Pointer, size uintptr, seed uintptr) uint32 {
|
||||||
_i := *(*_interface)(iptr)
|
_i := *(*interface{})(iptr)
|
||||||
return hashmapInterfaceHash(_i, seed)
|
return hashmapInterfaceHash(_i, seed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,17 +11,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type _interface struct {
|
type _interface struct {
|
||||||
typecode uintptr
|
typecode unsafe.Pointer
|
||||||
value unsafe.Pointer
|
value unsafe.Pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:inline
|
//go:inline
|
||||||
func composeInterface(typecode uintptr, value unsafe.Pointer) _interface {
|
func composeInterface(typecode, value unsafe.Pointer) _interface {
|
||||||
return _interface{typecode, value}
|
return _interface{typecode, value}
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:inline
|
//go:inline
|
||||||
func decomposeInterface(i _interface) (uintptr, unsafe.Pointer) {
|
func decomposeInterface(i _interface) (unsafe.Pointer, unsafe.Pointer) {
|
||||||
return i.typecode, i.value
|
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
|
// Note: doing a x.Type() == y.Type() comparison would not work here as that
|
||||||
// would introduce an infinite recursion: comparing two reflect.Type values
|
// would introduce an infinite recursion: comparing two reflect.Type values
|
||||||
// is done with this reflectValueEqual runtime call.
|
// 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.
|
// One of them is nil.
|
||||||
return x.RawType() == y.RawType()
|
return x.RawType() == y.RawType()
|
||||||
}
|
}
|
||||||
@@ -94,48 +94,13 @@ func interfaceTypeAssert(ok bool) {
|
|||||||
// lowered to inline IR in the interface lowering pass.
|
// lowered to inline IR in the interface lowering pass.
|
||||||
// See compiler/interface-lowering.go for details.
|
// 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 {
|
type structField struct {
|
||||||
typecode *typecodeID // type of this struct field
|
typecode unsafe.Pointer // type of this struct field
|
||||||
name *uint8 // pointer to char array
|
data *uint8 // pointer to byte array containing name, tag, and 'embedded' flag
|
||||||
tag *uint8 // pointer to char array, or nil
|
|
||||||
embedded bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pseudo function call used during a type assert. It is used during interface
|
// 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
|
// 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
|
// asserts. Also, it is replaced with const false if this type assert can never
|
||||||
// happen.
|
// happen.
|
||||||
func typeAssert(actualType uintptr, assertedType *uint8) bool
|
func typeAssert(actualType unsafe.Pointer, assertedType *uint8) bool
|
||||||
|
|||||||
@@ -34,3 +34,12 @@ func Restore(state State) {
|
|||||||
"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) {
|
func Restore(state State) {
|
||||||
arm.EnableInterrupts(uintptr(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
|
package interrupt
|
||||||
|
|
||||||
|
// This is good documentation of the GBA: https://www.akkit.org/info/gbatek.htm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime/volatile"
|
"runtime/volatile"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -36,8 +38,11 @@ func (irq Interrupt) Enable() {
|
|||||||
regInterruptEnable.SetBits(1 << uint(irq.num))
|
regInterruptEnable.SetBits(1 << uint(irq.num))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var inInterrupt bool
|
||||||
|
|
||||||
//export handleInterrupt
|
//export handleInterrupt
|
||||||
func handleInterrupt() {
|
func handleInterrupt() {
|
||||||
|
inInterrupt = true
|
||||||
flags := regInterruptRequestFlags.Get()
|
flags := regInterruptRequestFlags.Get()
|
||||||
for i := 0; i < 14; i++ {
|
for i := 0; i < 14; i++ {
|
||||||
if flags&(1<<uint(i)) != 0 {
|
if flags&(1<<uint(i)) != 0 {
|
||||||
@@ -45,6 +50,7 @@ func handleInterrupt() {
|
|||||||
callInterruptHandler(i)
|
callInterruptHandler(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
inInterrupt = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pseudo function call that is replaced by the compiler with the actual
|
// 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.
|
// Restore interrupts to the previous state.
|
||||||
regGlobalInterruptEnable.Set(uint16(state))
|
regGlobalInterruptEnable.Set(uint16(state))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In returns whether the system is currently in an interrupt.
|
||||||
|
func In() bool {
|
||||||
|
return inInterrupt
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,3 +23,9 @@ func Disable() (state State) {
|
|||||||
// calling Disable, this will not re-enable interrupts, allowing for nested
|
// calling Disable, this will not re-enable interrupts, allowing for nested
|
||||||
// cricital sections.
|
// cricital sections.
|
||||||
func Restore(state State) {}
|
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) {
|
func Restore(state State) {
|
||||||
riscv.EnableInterrupts(uintptr(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,
|
"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
|
||||||
|
}
|
||||||
|
|||||||
@@ -88,13 +88,9 @@ func LockOSThread() {
|
|||||||
func UnlockOSThread() {
|
func UnlockOSThread() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func KeepAlive(x interface{}) {
|
// KeepAlive makes sure the value in the interface is alive until at least the
|
||||||
// Unimplemented. Only required with SetFinalizer().
|
// point of the call.
|
||||||
}
|
func KeepAlive(x interface{})
|
||||||
|
|
||||||
func SetFinalizer(obj interface{}, finalizer interface{}) {
|
|
||||||
// Unimplemented.
|
|
||||||
}
|
|
||||||
|
|
||||||
var godebugUpdate func(string, string)
|
var godebugUpdate func(string, string)
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ func main() {
|
|||||||
// Zero the threshold value to allow all priorities of interrupts.
|
// Zero the threshold value to allow all priorities of interrupts.
|
||||||
sifive.PLIC.THRESHOLD.Set(0)
|
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.
|
// Set the interrupt address.
|
||||||
// Note that this address must be aligned specially, otherwise the MODE bits
|
// Note that this address must be aligned specially, otherwise the MODE bits
|
||||||
// of MTVEC won't be zero.
|
// of MTVEC won't be zero.
|
||||||
@@ -73,6 +79,10 @@ func handleInterrupt() {
|
|||||||
// misaligned loads). However, for now we'll just print a fatal error.
|
// misaligned loads). However, for now we'll just print a fatal error.
|
||||||
handleException(code)
|
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.
|
// initPeripherals configures periperhals the way the runtime expects them.
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ func main() {
|
|||||||
kendryte.PLIC.PRIORITY[i].Set(0)
|
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.
|
// Set the interrupt address.
|
||||||
// Note that this address must be aligned specially, otherwise the MODE bits
|
// Note that this address must be aligned specially, otherwise the MODE bits
|
||||||
// of MTVEC won't be zero.
|
// of MTVEC won't be zero.
|
||||||
@@ -93,6 +99,10 @@ func handleInterrupt() {
|
|||||||
// misaligned loads). However, for now we'll just print a fatal error.
|
// misaligned loads). However, for now we'll just print a fatal error.
|
||||||
handleException(code)
|
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.
|
// initPeripherals configures periperhals the way the runtime expects them.
|
||||||
|
|||||||
Vendored
+10
@@ -1,5 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import "runtime"
|
||||||
|
|
||||||
var xorshift32State uint32 = 1
|
var xorshift32State uint32 = 1
|
||||||
|
|
||||||
func xorshift32(x uint32) uint32 {
|
func xorshift32(x uint32) uint32 {
|
||||||
@@ -17,6 +19,7 @@ func randuint32() uint32 {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
testNonPointerHeap()
|
testNonPointerHeap()
|
||||||
|
testKeepAlive()
|
||||||
}
|
}
|
||||||
|
|
||||||
var scalarSlices [4][]byte
|
var scalarSlices [4][]byte
|
||||||
@@ -64,3 +67,10 @@ func testNonPointerHeap() {
|
|||||||
}
|
}
|
||||||
println("ok")
|
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)
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+34
@@ -129,6 +129,8 @@ func main() {
|
|||||||
floatcmplx()
|
floatcmplx()
|
||||||
|
|
||||||
mapgrow()
|
mapgrow()
|
||||||
|
|
||||||
|
interfacerehash()
|
||||||
}
|
}
|
||||||
|
|
||||||
func floatcmplx() {
|
func floatcmplx() {
|
||||||
@@ -274,3 +276,35 @@ func mapgrow() {
|
|||||||
}
|
}
|
||||||
println("done")
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+1
@@ -80,3 +80,4 @@ tested growing of a map
|
|||||||
2
|
2
|
||||||
2
|
2
|
||||||
done
|
done
|
||||||
|
no interface lookup failures
|
||||||
|
|||||||
Vendored
+4
-3
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,8 +18,8 @@ type (
|
|||||||
Y int16
|
Y int16
|
||||||
}
|
}
|
||||||
mystruct struct {
|
mystruct struct {
|
||||||
n int `foo:"bar"`
|
n int `foo:"bar"`
|
||||||
some point
|
some point "some\x00tag"
|
||||||
zero struct{}
|
zero struct{}
|
||||||
buf []byte
|
buf []byte
|
||||||
Buf []byte
|
Buf []byte
|
||||||
@@ -480,7 +481,7 @@ func showValue(rv reflect.Value, indent string) {
|
|||||||
for i := 0; i < rv.NumField(); i++ {
|
for i := 0; i < rv.NumField(); i++ {
|
||||||
field := rt.Field(i)
|
field := rt.Field(i)
|
||||||
println(indent+" field:", i, field.Name)
|
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+" embedded:", field.Anonymous)
|
||||||
println(indent+" exported:", field.IsExported())
|
println(indent+" exported:", field.IsExported())
|
||||||
showValue(rv.Field(i), indent+" ")
|
showValue(rv.Field(i), indent+" ")
|
||||||
|
|||||||
Vendored
+15
-15
@@ -233,7 +233,7 @@ reflect type: struct
|
|||||||
reflect type: struct
|
reflect type: struct
|
||||||
struct: 1
|
struct: 1
|
||||||
field: 0 error
|
field: 0 error
|
||||||
tag:
|
tag: ""
|
||||||
embedded: true
|
embedded: true
|
||||||
exported: false
|
exported: false
|
||||||
reflect type: interface caninterface=false
|
reflect type: interface caninterface=false
|
||||||
@@ -242,19 +242,19 @@ reflect type: struct
|
|||||||
reflect type: struct
|
reflect type: struct
|
||||||
struct: 3
|
struct: 3
|
||||||
field: 0 a
|
field: 0 a
|
||||||
tag:
|
tag: ""
|
||||||
embedded: false
|
embedded: false
|
||||||
exported: false
|
exported: false
|
||||||
reflect type: uint8 caninterface=false
|
reflect type: uint8 caninterface=false
|
||||||
uint: 42
|
uint: 42
|
||||||
field: 1 b
|
field: 1 b
|
||||||
tag:
|
tag: ""
|
||||||
embedded: false
|
embedded: false
|
||||||
exported: false
|
exported: false
|
||||||
reflect type: int16 caninterface=false
|
reflect type: int16 caninterface=false
|
||||||
int: 321
|
int: 321
|
||||||
field: 2 c
|
field: 2 c
|
||||||
tag:
|
tag: ""
|
||||||
embedded: false
|
embedded: false
|
||||||
exported: false
|
exported: false
|
||||||
reflect type: int8 caninterface=false
|
reflect type: int8 caninterface=false
|
||||||
@@ -262,37 +262,37 @@ reflect type: struct
|
|||||||
reflect type: struct comparable=false
|
reflect type: struct comparable=false
|
||||||
struct: 5
|
struct: 5
|
||||||
field: 0 n
|
field: 0 n
|
||||||
tag: foo:"bar"
|
tag: "foo:\"bar\""
|
||||||
embedded: false
|
embedded: false
|
||||||
exported: false
|
exported: false
|
||||||
reflect type: int caninterface=false
|
reflect type: int caninterface=false
|
||||||
int: 5
|
int: 5
|
||||||
field: 1 some
|
field: 1 some
|
||||||
tag:
|
tag: "some\x00tag"
|
||||||
embedded: false
|
embedded: false
|
||||||
exported: false
|
exported: false
|
||||||
reflect type: struct caninterface=false
|
reflect type: struct caninterface=false
|
||||||
struct: 2
|
struct: 2
|
||||||
field: 0 X
|
field: 0 X
|
||||||
tag:
|
tag: ""
|
||||||
embedded: false
|
embedded: false
|
||||||
exported: true
|
exported: true
|
||||||
reflect type: int16 caninterface=false
|
reflect type: int16 caninterface=false
|
||||||
int: -5
|
int: -5
|
||||||
field: 1 Y
|
field: 1 Y
|
||||||
tag:
|
tag: ""
|
||||||
embedded: false
|
embedded: false
|
||||||
exported: true
|
exported: true
|
||||||
reflect type: int16 caninterface=false
|
reflect type: int16 caninterface=false
|
||||||
int: 3
|
int: 3
|
||||||
field: 2 zero
|
field: 2 zero
|
||||||
tag:
|
tag: ""
|
||||||
embedded: false
|
embedded: false
|
||||||
exported: false
|
exported: false
|
||||||
reflect type: struct caninterface=false
|
reflect type: struct caninterface=false
|
||||||
struct: 0
|
struct: 0
|
||||||
field: 3 buf
|
field: 3 buf
|
||||||
tag:
|
tag: ""
|
||||||
embedded: false
|
embedded: false
|
||||||
exported: false
|
exported: false
|
||||||
reflect type: slice caninterface=false comparable=false
|
reflect type: slice caninterface=false comparable=false
|
||||||
@@ -306,7 +306,7 @@ reflect type: struct comparable=false
|
|||||||
reflect type: uint8 addrable=true caninterface=false
|
reflect type: uint8 addrable=true caninterface=false
|
||||||
uint: 111
|
uint: 111
|
||||||
field: 4 Buf
|
field: 4 Buf
|
||||||
tag:
|
tag: ""
|
||||||
embedded: false
|
embedded: false
|
||||||
exported: true
|
exported: true
|
||||||
reflect type: slice comparable=false
|
reflect type: slice comparable=false
|
||||||
@@ -322,14 +322,14 @@ reflect type: ptr
|
|||||||
reflect type: struct settable=true addrable=true
|
reflect type: struct settable=true addrable=true
|
||||||
struct: 2
|
struct: 2
|
||||||
field: 0 next
|
field: 0 next
|
||||||
tag: description:"chain"
|
tag: "description:\"chain\""
|
||||||
embedded: false
|
embedded: false
|
||||||
exported: false
|
exported: false
|
||||||
reflect type: ptr addrable=true caninterface=false
|
reflect type: ptr addrable=true caninterface=false
|
||||||
pointer: false struct
|
pointer: false struct
|
||||||
nil: true
|
nil: true
|
||||||
field: 1 foo
|
field: 1 foo
|
||||||
tag:
|
tag: ""
|
||||||
embedded: false
|
embedded: false
|
||||||
exported: false
|
exported: false
|
||||||
reflect type: int addrable=true caninterface=false
|
reflect type: int addrable=true caninterface=false
|
||||||
@@ -337,13 +337,13 @@ reflect type: ptr
|
|||||||
reflect type: struct
|
reflect type: struct
|
||||||
struct: 2
|
struct: 2
|
||||||
field: 0 A
|
field: 0 A
|
||||||
tag:
|
tag: ""
|
||||||
embedded: false
|
embedded: false
|
||||||
exported: true
|
exported: true
|
||||||
reflect type: uintptr
|
reflect type: uintptr
|
||||||
uint: 2
|
uint: 2
|
||||||
field: 1 B
|
field: 1 B
|
||||||
tag:
|
tag: ""
|
||||||
embedded: false
|
embedded: false
|
||||||
exported: true
|
exported: true
|
||||||
reflect type: uintptr
|
reflect type: uintptr
|
||||||
|
|||||||
@@ -90,9 +90,10 @@ type SVDCluster struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Device struct {
|
type Device struct {
|
||||||
Metadata *Metadata
|
Metadata *Metadata
|
||||||
Interrupts []*Interrupt
|
Interrupts []*Interrupt
|
||||||
Peripherals []*Peripheral
|
Peripherals []*Peripheral
|
||||||
|
PeripheralDict map[string]*Peripheral
|
||||||
}
|
}
|
||||||
|
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
@@ -191,6 +192,142 @@ func cleanName(text string) string {
|
|||||||
return text
|
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.
|
// Read ARM SVD files.
|
||||||
func readSVD(path, sourceURL string) (*Device, error) {
|
func readSVD(path, sourceURL string) (*Device, error) {
|
||||||
// Open the XML file.
|
// Open the XML file.
|
||||||
@@ -293,133 +430,7 @@ func readSVD(path, sourceURL string) (*Device, error) {
|
|||||||
}
|
}
|
||||||
p.Registers = append(p.Registers, parseRegister(regName, register, baseAddress, "")...)
|
p.Registers = append(p.Registers, parseRegister(regName, register, baseAddress, "")...)
|
||||||
}
|
}
|
||||||
for _, cluster := range periphEl.Clusters {
|
peripheralsList = append(peripheralsList, processCluster(p, periphEl.Clusters, peripheralDict)...)
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a sorted list of interrupts.
|
// Make a sorted list of interrupts.
|
||||||
@@ -459,9 +470,10 @@ func readSVD(path, sourceURL string) (*Device, error) {
|
|||||||
metadata.NVICPrioBits = device.CPU.NVICPrioBits
|
metadata.NVICPrioBits = device.CPU.NVICPrioBits
|
||||||
}
|
}
|
||||||
return &Device{
|
return &Device{
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
Interrupts: interruptList,
|
Interrupts: interruptList,
|
||||||
Peripherals: peripheralsList,
|
Peripherals: peripheralsList,
|
||||||
|
PeripheralDict: peripheralDict,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -979,10 +991,11 @@ var (
|
|||||||
|
|
||||||
address := peripheral.BaseAddress
|
address := peripheral.BaseAddress
|
||||||
type clusterInfo struct {
|
type clusterInfo struct {
|
||||||
name string
|
name string
|
||||||
address uint64
|
description string
|
||||||
size uint64
|
address uint64
|
||||||
registers []*PeripheralField
|
size uint64
|
||||||
|
registers []*PeripheralField
|
||||||
}
|
}
|
||||||
clusters := []clusterInfo{}
|
clusters := []clusterInfo{}
|
||||||
for _, register := range peripheral.Registers {
|
for _, register := range peripheral.Registers {
|
||||||
@@ -1024,7 +1037,7 @@ var (
|
|||||||
if register.Registers != nil {
|
if register.Registers != nil {
|
||||||
// This is a cluster, not a register. Create the cluster type.
|
// This is a cluster, not a register. Create the cluster type.
|
||||||
regType = peripheral.GroupName + "_" + register.Name
|
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"
|
regType = regType + "_Type"
|
||||||
subaddress := register.Address
|
subaddress := register.Address
|
||||||
for _, subregister := range register.Registers {
|
for _, subregister := range register.Registers {
|
||||||
@@ -1075,7 +1088,16 @@ var (
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, ok := device.PeripheralDict[cluster.name]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Fprintln(w)
|
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)
|
fmt.Fprintf(w, "type %s_Type struct {\n", cluster.name)
|
||||||
|
|
||||||
address := cluster.address
|
address := cluster.address
|
||||||
@@ -1116,7 +1138,7 @@ var (
|
|||||||
if register.Registers != nil {
|
if register.Registers != nil {
|
||||||
// This is a cluster, not a register. Create the cluster type.
|
// This is a cluster, not a register. Create the cluster type.
|
||||||
regType = peripheral.GroupName + "_" + register.Name
|
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"
|
regType = regType + "_Type"
|
||||||
|
|
||||||
subaddress := register.Address
|
subaddress := register.Address
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -13,8 +13,7 @@ package transform
|
|||||||
//
|
//
|
||||||
// typeAssert:
|
// typeAssert:
|
||||||
// Replaced with an icmp instruction so it can be directly used in a type
|
// 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
|
// switch.
|
||||||
// type switch into a regular switch statement.
|
|
||||||
//
|
//
|
||||||
// interface type assert:
|
// interface type assert:
|
||||||
// These functions are defined by creating a big type switch over all the
|
// 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
|
// 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. If it is a named type, it may have methods.
|
||||||
type typeInfo struct {
|
type typeInfo struct {
|
||||||
name string
|
name string
|
||||||
typecode llvm.Value
|
typecode llvm.Value
|
||||||
methodSet llvm.Value
|
typecodeGEP llvm.Value
|
||||||
methods []*methodInfo
|
methodSet llvm.Value
|
||||||
|
methods []*methodInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// getMethod looks up the method on this type with the given signature and
|
// 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
|
difiles map[string]llvm.Metadata
|
||||||
ctx llvm.Context
|
ctx llvm.Context
|
||||||
uintptrType llvm.Type
|
uintptrType llvm.Type
|
||||||
|
targetData llvm.TargetData
|
||||||
|
i8ptrType llvm.Type
|
||||||
types map[string]*typeInfo
|
types map[string]*typeInfo
|
||||||
signatures map[string]*signatureInfo
|
signatures map[string]*signatureInfo
|
||||||
interfaces map[string]*interfaceInfo
|
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
|
// before LLVM can work on them. This is done so that a few cleanup passes can
|
||||||
// run before assigning the final type codes.
|
// run before assigning the final type codes.
|
||||||
func LowerInterfaces(mod llvm.Module, config *compileopts.Config) error {
|
func LowerInterfaces(mod llvm.Module, config *compileopts.Config) error {
|
||||||
|
ctx := mod.Context()
|
||||||
targetData := llvm.NewTargetData(mod.DataLayout())
|
targetData := llvm.NewTargetData(mod.DataLayout())
|
||||||
defer targetData.Dispose()
|
defer targetData.Dispose()
|
||||||
p := &lowerInterfacesPass{
|
p := &lowerInterfacesPass{
|
||||||
mod: mod,
|
mod: mod,
|
||||||
config: config,
|
config: config,
|
||||||
builder: mod.Context().NewBuilder(),
|
builder: ctx.NewBuilder(),
|
||||||
ctx: mod.Context(),
|
ctx: ctx,
|
||||||
|
targetData: targetData,
|
||||||
uintptrType: mod.Context().IntType(targetData.PointerSize() * 8),
|
uintptrType: mod.Context().IntType(targetData.PointerSize() * 8),
|
||||||
|
i8ptrType: llvm.PointerType(ctx.Int8Type(), 0),
|
||||||
types: make(map[string]*typeInfo),
|
types: make(map[string]*typeInfo),
|
||||||
signatures: make(map[string]*signatureInfo),
|
signatures: make(map[string]*signatureInfo),
|
||||||
interfaces: make(map[string]*interfaceInfo),
|
interfaces: make(map[string]*interfaceInfo),
|
||||||
@@ -151,11 +156,26 @@ func (p *lowerInterfacesPass) run() error {
|
|||||||
}
|
}
|
||||||
p.types[name] = t
|
p.types[name] = t
|
||||||
initializer := global.Initializer()
|
initializer := global.Initializer()
|
||||||
if initializer.IsNil() {
|
firstField := p.builder.CreateExtractValue(initializer, 0, "")
|
||||||
continue
|
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)
|
actualType := use.Operand(0)
|
||||||
name := strings.TrimPrefix(use.Operand(1).Name(), "reflect/types.typeid:")
|
name := strings.TrimPrefix(use.Operand(1).Name(), "reflect/types.typeid:")
|
||||||
if t, ok := p.types[name]; ok {
|
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.
|
// comparison.
|
||||||
p.builder.SetInsertPointBefore(use)
|
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)
|
use.ReplaceAllUsesWith(commaOk)
|
||||||
} else {
|
} else {
|
||||||
// The type does not exist in the program, so lower to a constant
|
// 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
|
// Remove all method sets, which are now unnecessary and inhibit later
|
||||||
// optimizations if they are left in place. Also remove references to the
|
// optimizations if they are left in place.
|
||||||
// interface type assert functions just to be sure.
|
zero := llvm.ConstInt(p.ctx.Int32Type(), 0, false)
|
||||||
zeroUintptr := llvm.ConstNull(p.uintptrType)
|
|
||||||
for _, t := range p.types {
|
for _, t := range p.types {
|
||||||
initializer := t.typecode.Initializer()
|
if !t.methodSet.IsNil() {
|
||||||
methodSet := p.builder.CreateExtractValue(initializer, 2, "")
|
initializer := t.typecode.Initializer()
|
||||||
initializer = p.builder.CreateInsertValue(initializer, llvm.ConstNull(methodSet.Type()), 2, "")
|
var newInitializerFields []llvm.Value
|
||||||
initializer = p.builder.CreateInsertValue(initializer, zeroUintptr, 4, "")
|
for i := 1; i < initializer.Type().StructElementTypesCount(); i++ {
|
||||||
t.typecode.SetInitializer(initializer)
|
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
|
return nil
|
||||||
@@ -301,22 +351,22 @@ func (p *lowerInterfacesPass) run() error {
|
|||||||
// retrieves the signatures and the references to the method functions
|
// retrieves the signatures and the references to the method functions
|
||||||
// themselves for later type<->interface matching.
|
// themselves for later type<->interface matching.
|
||||||
func (p *lowerInterfacesPass) addTypeMethods(t *typeInfo, methodSet llvm.Value) {
|
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
|
// no methods or methods already read
|
||||||
return
|
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.
|
// This type has methods, collect all methods of this type.
|
||||||
t.methodSet = methodSet
|
t.methodSet = methodSet
|
||||||
set := methodSet.Initializer() // get value from global
|
set := methodSet.Initializer() // get value from global
|
||||||
for i := 0; i < set.Type().ArrayLength(); i++ {
|
signatures := p.builder.CreateExtractValue(set, 1, "")
|
||||||
methodData := p.builder.CreateExtractValue(set, i, "")
|
wrappers := p.builder.CreateExtractValue(set, 2, "")
|
||||||
signatureGlobal := p.builder.CreateExtractValue(methodData, 0, "")
|
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()
|
signatureName := signatureGlobal.Name()
|
||||||
function := p.builder.CreateExtractValue(methodData, 1, "").Operand(0)
|
|
||||||
signature := p.getSignature(signatureName)
|
signature := p.getSignature(signatureName)
|
||||||
method := &methodInfo{
|
method := &methodInfo{
|
||||||
function: function,
|
function: function,
|
||||||
@@ -401,7 +451,7 @@ func (p *lowerInterfacesPass) defineInterfaceImplementsFunc(fn llvm.Value, itf *
|
|||||||
actualType := fn.Param(0)
|
actualType := fn.Param(0)
|
||||||
for _, typ := range itf.types {
|
for _, typ := range itf.types {
|
||||||
nextBlock := p.ctx.AddBasicBlock(fn, typ.name+".next")
|
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.CreateCondBr(cmp, thenBlock, nextBlock)
|
||||||
p.builder.SetInsertPointAtEnd(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[i] = fn.Param(i + 1)
|
||||||
}
|
}
|
||||||
params = append(params,
|
params = append(params,
|
||||||
llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
|
llvm.Undef(p.i8ptrType),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Start chain in the entry block.
|
// Start chain in the entry block.
|
||||||
@@ -472,7 +522,7 @@ func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *inte
|
|||||||
// Create type check (if/else).
|
// Create type check (if/else).
|
||||||
bb := p.ctx.AddBasicBlock(fn, typ.name)
|
bb := p.ctx.AddBasicBlock(fn, typ.name)
|
||||||
next := p.ctx.AddBasicBlock(fn, typ.name+".next")
|
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)
|
p.builder.CreateCondBr(cmp, bb, next)
|
||||||
|
|
||||||
// The function we will redirect to when the interface has this type.
|
// 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.
|
// method on a nil interface.
|
||||||
nilPanic := p.mod.NamedFunction("runtime.nilPanic")
|
nilPanic := p.mod.NamedFunction("runtime.nilPanic")
|
||||||
p.builder.CreateCall(nilPanic.GlobalValueType(), nilPanic, []llvm.Value{
|
p.builder.CreateCall(nilPanic.GlobalValueType(), nilPanic, []llvm.Value{
|
||||||
llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
|
llvm.Undef(p.i8ptrType),
|
||||||
}, "")
|
}, "")
|
||||||
p.builder.CreateUnreachable()
|
p.builder.CreateUnreachable()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,6 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
|
|||||||
goPasses.Run(mod)
|
goPasses.Run(mod)
|
||||||
|
|
||||||
// Run TinyGo-specific interprocedural optimizations.
|
// Run TinyGo-specific interprocedural optimizations.
|
||||||
LowerReflect(mod)
|
|
||||||
OptimizeAllocs(mod, config.Options.PrintAllocs, func(pos token.Position, msg string) {
|
OptimizeAllocs(mod, config.Options.PrintAllocs, func(pos token.Position, msg string) {
|
||||||
fmt.Fprintln(os.Stderr, pos.String()+": "+msg)
|
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 {
|
if err != nil {
|
||||||
return []error{err}
|
return []error{err}
|
||||||
}
|
}
|
||||||
LowerReflect(mod)
|
|
||||||
errs := LowerInterrupts(mod)
|
errs := LowerInterrupts(mod)
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return errs
|
return errs
|
||||||
|
|||||||
@@ -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)]
|
|
||||||
}
|
|
||||||
@@ -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
@@ -113,11 +113,6 @@ func OptimizeReflectImplements(mod llvm.Module) {
|
|||||||
builder := mod.Context().NewBuilder()
|
builder := mod.Context().NewBuilder()
|
||||||
defer builder.Dispose()
|
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.
|
// Look up the (reflect.Value).Implements() method.
|
||||||
var implementsFunc llvm.Value
|
var implementsFunc llvm.Value
|
||||||
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||||
@@ -141,14 +136,13 @@ func OptimizeReflectImplements(mod llvm.Module) {
|
|||||||
}
|
}
|
||||||
interfaceType := stripPointerCasts(call.Operand(2))
|
interfaceType := stripPointerCasts(call.Operand(2))
|
||||||
if interfaceType.IsAGlobalVariable().IsNil() {
|
if interfaceType.IsAGlobalVariable().IsNil() {
|
||||||
// The asserted interface is not constant, so can't optimize this
|
// Interface is unknown at compile time. This can't be optimized.
|
||||||
// code.
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(interfaceType.Name(), "reflect/types.type:named:") {
|
if strings.HasPrefix(interfaceType.Name(), "reflect/types.type:named:") {
|
||||||
// Get the underlying type.
|
// 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:") {
|
if !strings.HasPrefix(interfaceType.Name(), "reflect/types.type:interface:") {
|
||||||
// This is an error. The Type passed to Implements should be of
|
// This is an error. The Type passed to Implements should be of
|
||||||
@@ -156,16 +150,15 @@ func OptimizeReflectImplements(mod llvm.Module) {
|
|||||||
// reported at runtime.
|
// reported at runtime.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if interfaceType.IsAGlobalVariable().IsNil() {
|
typeAssertFunction := mod.NamedFunction(strings.TrimPrefix(interfaceType.Name(), "reflect/types.type:") + ".$typeassert")
|
||||||
// Interface is unknown at compile time. This can't be optimized.
|
if typeAssertFunction.IsNil() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
typeAssertFunction := builder.CreateExtractValue(interfaceType.Initializer(), 4, "").Operand(0)
|
|
||||||
|
|
||||||
// Replace Implements call with the type assert call.
|
// Replace Implements call with the type assert call.
|
||||||
builder.SetInsertPointBefore(call)
|
builder.SetInsertPointBefore(call)
|
||||||
implements := builder.CreateCall(typeAssertFunction.GlobalValueType(), typeAssertFunction, []llvm.Value{
|
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.ReplaceAllUsesWith(implements)
|
||||||
call.EraseFromParentAsInstruction()
|
call.EraseFromParentAsInstruction()
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
Vendored
+20
-20
@@ -1,19 +1,19 @@
|
|||||||
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
|
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||||
target triple = "armv7m-none-eabi"
|
target triple = "armv7m-none-eabi"
|
||||||
|
|
||||||
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 }
|
@"reflect/types.type:basic:uint8" = linkonce_odr constant { i8, i8* } { i8 8, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:basic:uint8", i32 0, i32 0) }, align 4
|
||||||
%runtime.interfaceMethodInfo = type { i8*, i32 }
|
@"reflect/types.type:pointer:basic:uint8" = linkonce_odr constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:uint8", i32 0, i32 0) }, align 4
|
||||||
|
|
||||||
@"reflect/types.type:basic:uint8" = private constant %runtime.typecodeID zeroinitializer
|
|
||||||
@"reflect/types.typeid:basic:uint8" = external constant i8
|
@"reflect/types.typeid:basic:uint8" = external constant i8
|
||||||
@"reflect/types.typeid:basic:int16" = external constant i8
|
@"reflect/types.typeid:basic:int16" = external constant i8
|
||||||
@"reflect/types.type:basic:int" = private constant %runtime.typecodeID zeroinitializer
|
@"reflect/types.type:basic:int" = linkonce_odr 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" = linkonce_odr constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:int", i32 0, i32 0) }, align 4
|
||||||
@"reflect/methods.NeverImplementedMethod()" = linkonce_odr constant i8 0
|
@"reflect/methods.NeverImplementedMethod()" = linkonce_odr constant i8 0
|
||||||
@"reflect/methods.Double() int" = linkonce_odr constant i8 0
|
@"reflect/methods.Double() int" = linkonce_odr constant i8 0
|
||||||
@"Number$methodset" = private constant [1 x %runtime.interfaceMethodInfo] [%runtime.interfaceMethodInfo { i8* @"reflect/methods.Double() int", i32 ptrtoint (i32 (i8*, i8*)* @"(Number).Double$invoke" to i32) }]
|
@"Number$methodset" = linkonce_odr unnamed_addr constant { i32, [1 x i8*], { i32 (i8*, i8*)* } } { i32 1, [1 x i8*] [i8* @"reflect/methods.Double() int"], { i32 (i8*, i8*)* } { i32 (i8*, i8*)* @"(Number).Double$invoke" } }
|
||||||
@"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([1 x %runtime.interfaceMethodInfo], [1 x %runtime.interfaceMethodInfo]* @"Number$methodset", i32 0, i32 0), %runtime.typecodeID* null, i32 0 }
|
@"reflect/types.type:named:Number" = linkonce_odr constant { i8*, i8, i8*, i8* } { i8* bitcast ({ i32, [1 x i8*], { i32 (i8*, i8*)* } }* @"Number$methodset" to i8*), i8 34, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:named:Number", 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:Number" = linkonce_odr constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8*, i8, i8*, i8* }, { i8*, i8, i8*, i8* }* @"reflect/types.type:named:Number", i32 0, i32 1) }, align 4
|
||||||
|
|
||||||
declare i1 @runtime.typeAssert(i32, i8*)
|
declare i1 @runtime.typeAssert(i8*, i8*)
|
||||||
declare void @runtime.printuint8(i8)
|
declare void @runtime.printuint8(i8)
|
||||||
declare void @runtime.printint16(i16)
|
declare void @runtime.printint16(i16)
|
||||||
declare void @runtime.printint32(i32)
|
declare void @runtime.printint32(i32)
|
||||||
@@ -22,15 +22,15 @@ declare void @runtime.printnl()
|
|||||||
declare void @runtime.nilPanic(i8*)
|
declare void @runtime.nilPanic(i8*)
|
||||||
|
|
||||||
define void @printInterfaces() {
|
define void @printInterfaces() {
|
||||||
call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:int" to i32), i8* inttoptr (i32 5 to i8*))
|
call void @printInterface(i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:int", i32 0, i32 0), i8* inttoptr (i32 5 to i8*))
|
||||||
call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:uint8" to i32), i8* inttoptr (i8 120 to i8*))
|
call void @printInterface(i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:uint8", i32 0, i32 0), i8* inttoptr (i8 120 to i8*))
|
||||||
call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32), i8* inttoptr (i32 3 to i8*))
|
call void @printInterface(i8* getelementptr inbounds ({ i8*, i8, i8*, i8* }, { i8*, i8, i8*, i8* }* @"reflect/types.type:named:Number", i32 0, i32 1), i8* inttoptr (i32 3 to i8*))
|
||||||
|
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
define void @printInterface(i32 %typecode, i8* %value) {
|
define void @printInterface(i8* %typecode, i8* %value) {
|
||||||
%isUnmatched = call i1 @Unmatched$typeassert(i32 %typecode)
|
%isUnmatched = call i1 @Unmatched$typeassert(i8* %typecode)
|
||||||
br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched
|
br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched
|
||||||
|
|
||||||
typeswitch.Unmatched:
|
typeswitch.Unmatched:
|
||||||
@@ -40,16 +40,16 @@ typeswitch.Unmatched:
|
|||||||
ret void
|
ret void
|
||||||
|
|
||||||
typeswitch.notUnmatched:
|
typeswitch.notUnmatched:
|
||||||
%isDoubler = call i1 @Doubler$typeassert(i32 %typecode)
|
%isDoubler = call i1 @Doubler$typeassert(i8* %typecode)
|
||||||
br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler
|
br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler
|
||||||
|
|
||||||
typeswitch.Doubler:
|
typeswitch.Doubler:
|
||||||
%doubler.result = call i32 @"Doubler.Double$invoke"(i8* %value, i32 %typecode, i8* undef)
|
%doubler.result = call i32 @"Doubler.Double$invoke"(i8* %value, i8* %typecode, i8* undef)
|
||||||
call void @runtime.printint32(i32 %doubler.result)
|
call void @runtime.printint32(i32 %doubler.result)
|
||||||
ret void
|
ret void
|
||||||
|
|
||||||
typeswitch.notDoubler:
|
typeswitch.notDoubler:
|
||||||
%isByte = call i1 @runtime.typeAssert(i32 %typecode, i8* nonnull @"reflect/types.typeid:basic:uint8")
|
%isByte = call i1 @runtime.typeAssert(i8* %typecode, i8* nonnull @"reflect/types.typeid:basic:uint8")
|
||||||
br i1 %isByte, label %typeswitch.byte, label %typeswitch.notByte
|
br i1 %isByte, label %typeswitch.byte, label %typeswitch.notByte
|
||||||
|
|
||||||
typeswitch.byte:
|
typeswitch.byte:
|
||||||
@@ -60,7 +60,7 @@ typeswitch.byte:
|
|||||||
|
|
||||||
typeswitch.notByte:
|
typeswitch.notByte:
|
||||||
; this is a type assert that always fails
|
; this is a type assert that always fails
|
||||||
%isInt16 = call i1 @runtime.typeAssert(i32 %typecode, i8* nonnull @"reflect/types.typeid:basic:int16")
|
%isInt16 = call i1 @runtime.typeAssert(i8* %typecode, i8* nonnull @"reflect/types.typeid:basic:int16")
|
||||||
br i1 %isInt16, label %typeswitch.int16, label %typeswitch.notInt16
|
br i1 %isInt16, label %typeswitch.int16, label %typeswitch.notInt16
|
||||||
|
|
||||||
typeswitch.int16:
|
typeswitch.int16:
|
||||||
@@ -84,11 +84,11 @@ define i32 @"(Number).Double$invoke"(i8* %receiverPtr, i8* %context) {
|
|||||||
ret i32 %ret
|
ret i32 %ret
|
||||||
}
|
}
|
||||||
|
|
||||||
declare i32 @"Doubler.Double$invoke"(i8* %receiver, i32 %typecode, i8* %context) #0
|
declare i32 @"Doubler.Double$invoke"(i8* %receiver, i8* %typecode, i8* %context) #0
|
||||||
|
|
||||||
declare i1 @Doubler$typeassert(i32 %typecode) #1
|
declare i1 @Doubler$typeassert(i8* %typecode) #1
|
||||||
|
|
||||||
declare i1 @Unmatched$typeassert(i32 %typecode) #2
|
declare i1 @Unmatched$typeassert(i8* %typecode) #2
|
||||||
|
|
||||||
attributes #0 = { "tinygo-invoke"="reflect/methods.Double() int" "tinygo-methods"="reflect/methods.Double() int" }
|
attributes #0 = { "tinygo-invoke"="reflect/methods.Double() int" "tinygo-methods"="reflect/methods.Double() int" }
|
||||||
attributes #1 = { "tinygo-methods"="reflect/methods.Double() int" }
|
attributes #1 = { "tinygo-methods"="reflect/methods.Double() int" }
|
||||||
|
|||||||
Vendored
+19
-19
@@ -1,12 +1,12 @@
|
|||||||
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
|
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||||
target triple = "armv7m-none-eabi"
|
target triple = "armv7m-none-eabi"
|
||||||
|
|
||||||
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 }
|
@"reflect/types.type:basic:uint8" = linkonce_odr constant { i8, i8* } { i8 8, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:basic:uint8", i32 0, i32 0) }, align 4
|
||||||
%runtime.interfaceMethodInfo = type { i8*, i32 }
|
@"reflect/types.type:pointer:basic:uint8" = linkonce_odr constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:uint8", i32 0, i32 0) }, align 4
|
||||||
|
@"reflect/types.type:basic:int" = linkonce_odr 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:basic:uint8" = private constant %runtime.typecodeID zeroinitializer
|
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:int", i32 0, i32 0) }, align 4
|
||||||
@"reflect/types.type:basic:int" = private constant %runtime.typecodeID zeroinitializer
|
@"reflect/types.type:pointer:named:Number" = linkonce_odr constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8, i8*, i8* }, { i8, i8*, i8* }* @"reflect/types.type:named:Number", i32 0, i32 0) }, align 4
|
||||||
@"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 }
|
@"reflect/types.type:named:Number" = linkonce_odr constant { i8, i8*, i8* } { i8 34, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:named:Number", i32 0, i32 0), i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:int", i32 0, i32 0) }, align 4
|
||||||
|
|
||||||
declare void @runtime.printuint8(i8)
|
declare void @runtime.printuint8(i8)
|
||||||
|
|
||||||
@@ -21,14 +21,14 @@ declare void @runtime.printnl()
|
|||||||
declare void @runtime.nilPanic(i8*)
|
declare void @runtime.nilPanic(i8*)
|
||||||
|
|
||||||
define void @printInterfaces() {
|
define void @printInterfaces() {
|
||||||
call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:int" to i32), i8* inttoptr (i32 5 to i8*))
|
call void @printInterface(i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:int", i32 0, i32 0), i8* inttoptr (i32 5 to i8*))
|
||||||
call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:uint8" to i32), i8* inttoptr (i8 120 to i8*))
|
call void @printInterface(i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:uint8", i32 0, i32 0), i8* inttoptr (i8 120 to i8*))
|
||||||
call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32), i8* inttoptr (i32 3 to i8*))
|
call void @printInterface(i8* getelementptr inbounds ({ i8, i8*, i8* }, { i8, i8*, i8* }* @"reflect/types.type:named:Number", i32 0, i32 0), i8* inttoptr (i32 3 to i8*))
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
define void @printInterface(i32 %typecode, i8* %value) {
|
define void @printInterface(i8* %typecode, i8* %value) {
|
||||||
%isUnmatched = call i1 @"Unmatched$typeassert"(i32 %typecode)
|
%isUnmatched = call i1 @"Unmatched$typeassert"(i8* %typecode)
|
||||||
br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched
|
br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched
|
||||||
|
|
||||||
typeswitch.Unmatched: ; preds = %0
|
typeswitch.Unmatched: ; preds = %0
|
||||||
@@ -38,16 +38,16 @@ typeswitch.Unmatched: ; preds = %0
|
|||||||
ret void
|
ret void
|
||||||
|
|
||||||
typeswitch.notUnmatched: ; preds = %0
|
typeswitch.notUnmatched: ; preds = %0
|
||||||
%isDoubler = call i1 @"Doubler$typeassert"(i32 %typecode)
|
%isDoubler = call i1 @"Doubler$typeassert"(i8* %typecode)
|
||||||
br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler
|
br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler
|
||||||
|
|
||||||
typeswitch.Doubler: ; preds = %typeswitch.notUnmatched
|
typeswitch.Doubler: ; preds = %typeswitch.notUnmatched
|
||||||
%doubler.result = call i32 @"Doubler.Double$invoke"(i8* %value, i32 %typecode, i8* undef)
|
%doubler.result = call i32 @"Doubler.Double$invoke"(i8* %value, i8* %typecode, i8* undef)
|
||||||
call void @runtime.printint32(i32 %doubler.result)
|
call void @runtime.printint32(i32 %doubler.result)
|
||||||
ret void
|
ret void
|
||||||
|
|
||||||
typeswitch.notDoubler: ; preds = %typeswitch.notUnmatched
|
typeswitch.notDoubler: ; preds = %typeswitch.notUnmatched
|
||||||
%typeassert.ok = icmp eq i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:uint8" to i32), %typecode
|
%typeassert.ok = icmp eq i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:uint8", i32 0, i32 0), %typecode
|
||||||
br i1 %typeassert.ok, label %typeswitch.byte, label %typeswitch.notByte
|
br i1 %typeassert.ok, label %typeswitch.byte, label %typeswitch.notByte
|
||||||
|
|
||||||
typeswitch.byte: ; preds = %typeswitch.notDoubler
|
typeswitch.byte: ; preds = %typeswitch.notDoubler
|
||||||
@@ -80,9 +80,9 @@ define i32 @"(Number).Double$invoke"(i8* %receiverPtr, i8* %context) {
|
|||||||
ret i32 %ret
|
ret i32 %ret
|
||||||
}
|
}
|
||||||
|
|
||||||
define internal i32 @"Doubler.Double$invoke"(i8* %receiver, i32 %actualType, i8* %context) unnamed_addr #0 {
|
define internal i32 @"Doubler.Double$invoke"(i8* %receiver, i8* %actualType, i8* %context) unnamed_addr #0 {
|
||||||
entry:
|
entry:
|
||||||
%"named:Number.icmp" = icmp eq i32 %actualType, ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32)
|
%"named:Number.icmp" = icmp eq i8* %actualType, getelementptr inbounds ({ i8, i8*, i8* }, { i8, i8*, i8* }* @"reflect/types.type:named:Number", i32 0, i32 0)
|
||||||
br i1 %"named:Number.icmp", label %"named:Number", label %"named:Number.next"
|
br i1 %"named:Number.icmp", label %"named:Number", label %"named:Number.next"
|
||||||
|
|
||||||
"named:Number": ; preds = %entry
|
"named:Number": ; preds = %entry
|
||||||
@@ -94,9 +94,9 @@ entry:
|
|||||||
unreachable
|
unreachable
|
||||||
}
|
}
|
||||||
|
|
||||||
define internal i1 @"Doubler$typeassert"(i32 %actualType) unnamed_addr #1 {
|
define internal i1 @"Doubler$typeassert"(i8* %actualType) unnamed_addr #1 {
|
||||||
entry:
|
entry:
|
||||||
%"named:Number.icmp" = icmp eq i32 %actualType, ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32)
|
%"named:Number.icmp" = icmp eq i8* %actualType, getelementptr inbounds ({ i8, i8*, i8* }, { i8, i8*, i8* }* @"reflect/types.type:named:Number", i32 0, i32 0)
|
||||||
br i1 %"named:Number.icmp", label %then, label %"named:Number.next"
|
br i1 %"named:Number.icmp", label %then, label %"named:Number.next"
|
||||||
|
|
||||||
then: ; preds = %entry
|
then: ; preds = %entry
|
||||||
@@ -106,7 +106,7 @@ then: ; preds = %entry
|
|||||||
ret i1 false
|
ret i1 false
|
||||||
}
|
}
|
||||||
|
|
||||||
define internal i1 @"Unmatched$typeassert"(i32 %actualType) unnamed_addr #2 {
|
define internal i1 @"Unmatched$typeassert"(i8* %actualType) unnamed_addr #2 {
|
||||||
entry:
|
entry:
|
||||||
ret i1 false
|
ret i1 false
|
||||||
|
|
||||||
|
|||||||
+13
-18
@@ -1,19 +1,14 @@
|
|||||||
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
|
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
|
||||||
target triple = "i686--linux"
|
target triple = "i686--linux"
|
||||||
|
|
||||||
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 }
|
%runtime._interface = type { i8*, i8* }
|
||||||
%runtime.interfaceMethodInfo = type { i8*, i32 }
|
|
||||||
|
|
||||||
@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 ptrtoint (i1 (i32)* @"error.$typeassert" to i32) }
|
@"reflect/types.type:named:error" = internal constant { i8, i8*, i8* } { i8 52, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:named:error", i32 0, i32 0), i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, i32 0) }, align 4
|
||||||
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 ptrtoint (i1 (i32)* @"error.$typeassert" to i32) }
|
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = internal constant { i8, i8* } { i8 20, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}", i32 0, i32 0) }, align 4
|
||||||
@"reflect/methods.Error() string" = linkonce_odr constant i8 0
|
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = internal constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, i32 0) }, align 4
|
||||||
@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"]
|
@"reflect/types.type:pointer:named:error" = internal constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8, i8*, i8* }, { i8, i8*, i8* }* @"reflect/types.type:named:error", i32 0, i32 0) }, align 4
|
||||||
@"reflect/methods.Align() int" = linkonce_odr constant i8 0
|
@"reflect/types.type:pointer:named:reflect.rawType" = internal constant { i8*, i8, i8* } { i8* null, i8 21, i8* null }, align 4
|
||||||
@"reflect/methods.Implements(reflect.Type) bool" = linkonce_odr constant i8 0
|
@"reflect/methods.Implements(reflect.Type) bool" = internal constant i8 0, align 1
|
||||||
@"reflect.Type$interface" = linkonce_odr constant [2 x i8*] [i8* @"reflect/methods.Align() int", i8* @"reflect/methods.Implements(reflect.Type) bool"]
|
|
||||||
@"reflect/types.type:named:reflect.rawType" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:uintptr", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([20 x %runtime.interfaceMethodInfo], [20 x %runtime.interfaceMethodInfo]* @"reflect.rawType$methodset", i32 0, i32 0), %runtime.typecodeID* null, i32 0 }
|
|
||||||
@"reflect.rawType$methodset" = linkonce_odr constant [20 x %runtime.interfaceMethodInfo] zeroinitializer
|
|
||||||
@"reflect/types.type:basic:uintptr" = linkonce_odr constant %runtime.typecodeID zeroinitializer
|
|
||||||
|
|
||||||
; var errorType = reflect.TypeOf((*error)(nil)).Elem()
|
; var errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||||
; func isError(typ reflect.Type) bool {
|
; func isError(typ reflect.Type) bool {
|
||||||
@@ -22,9 +17,9 @@ target triple = "i686--linux"
|
|||||||
; The type itself is stored in %typ.value, %typ.typecode just refers to the
|
; The type itself is stored in %typ.value, %typ.typecode just refers to the
|
||||||
; type of reflect.Type. This function can be optimized because errorType is
|
; type of reflect.Type. This function can be optimized because errorType is
|
||||||
; known at compile time (after the interp pass has run).
|
; known at compile time (after the interp pass has run).
|
||||||
define i1 @main.isError(i32 %typ.typecode, i8* %typ.value, i8* %context) {
|
define i1 @main.isError(i8* %typ.typecode, i8* %typ.value, i8* %context) {
|
||||||
entry:
|
entry:
|
||||||
%result = call i1 @"reflect.Type.Implements$invoke"(i8* %typ.value, i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:reflect.rawType" to i32), i8* bitcast (%runtime.typecodeID* @"reflect/types.type:named:error" to i8*), i32 %typ.typecode, i8* undef)
|
%result = call i1 @"reflect.Type.Implements$invoke"(i8* %typ.value, i8* getelementptr inbounds ({ i8*, i8, i8* }, { i8*, i8, i8* }* @"reflect/types.type:pointer:named:reflect.rawType", i32 0, i32 1), i8* getelementptr inbounds ({ i8, i8*, i8* }, { i8, i8*, i8* }* @"reflect/types.type:named:error", i32 0, i32 0), i8* %typ.typecode, i8* undef)
|
||||||
ret i1 %result
|
ret i1 %result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,14 +28,14 @@ entry:
|
|||||||
; func isUnknown(typ, itf reflect.Type) bool {
|
; func isUnknown(typ, itf reflect.Type) bool {
|
||||||
; return typ.Implements(itf)
|
; return typ.Implements(itf)
|
||||||
; }
|
; }
|
||||||
define i1 @main.isUnknown(i32 %typ.typecode, i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i8* %context) {
|
define i1 @main.isUnknown(i8* %typ.typecode, i8* %typ.value, i8* %itf.typecode, i8* %itf.value, i8* %context) {
|
||||||
entry:
|
entry:
|
||||||
%result = call i1 @"reflect.Type.Implements$invoke"(i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i32 %typ.typecode, i8* undef)
|
%result = call i1 @"reflect.Type.Implements$invoke"(i8* %typ.value, i8* %itf.typecode, i8* %itf.value, i8* %typ.typecode, i8* undef)
|
||||||
ret i1 %result
|
ret i1 %result
|
||||||
}
|
}
|
||||||
|
|
||||||
declare i1 @"reflect.Type.Implements$invoke"(i8*, i32, i8*, i32, i8*) #0
|
declare i1 @"reflect.Type.Implements$invoke"(i8*, i8*, i8*, i8*, i8*) #0
|
||||||
declare i1 @"error.$typeassert"(i32) #1
|
declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(i8* %0) #1
|
||||||
|
|
||||||
attributes #0 = { "tinygo-invoke"="reflect/methods.Implements(reflect.Type) bool" "tinygo-methods"="reflect/methods.Align() int; reflect/methods.Implements(reflect.Type) bool" }
|
attributes #0 = { "tinygo-invoke"="reflect/methods.Implements(reflect.Type) bool" "tinygo-methods"="reflect/methods.Align() int; reflect/methods.Implements(reflect.Type) bool" }
|
||||||
attributes #1 = { "tinygo-methods"="reflect/methods.Error() string" }
|
attributes #1 = { "tinygo-methods"="reflect/methods.Error() string" }
|
||||||
|
|||||||
+13
-21
@@ -1,36 +1,28 @@
|
|||||||
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
|
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
|
||||||
target triple = "i686--linux"
|
target triple = "i686--linux"
|
||||||
|
|
||||||
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 }
|
@"reflect/types.type:named:error" = internal constant { i8, i8*, i8* } { i8 52, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:named:error", i32 0, i32 0), i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, i32 0) }, align 4
|
||||||
%runtime.interfaceMethodInfo = type { i8*, i32 }
|
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = internal constant { i8, i8* } { i8 20, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}", i32 0, i32 0) }, align 4
|
||||||
|
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = internal constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, i32 0) }, align 4
|
||||||
|
@"reflect/types.type:pointer:named:error" = internal constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8, i8*, i8* }, { i8, i8*, i8* }* @"reflect/types.type:named:error", i32 0, i32 0) }, align 4
|
||||||
|
@"reflect/types.type:pointer:named:reflect.rawType" = internal constant { i8*, i8, i8* } { i8* null, i8 21, i8* null }, align 4
|
||||||
|
@"reflect/methods.Implements(reflect.Type) bool" = internal constant i8 0, align 1
|
||||||
|
|
||||||
@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 ptrtoint (i1 (i32)* @"error.$typeassert" to i32) }
|
define i1 @main.isError(i8* %typ.typecode, i8* %typ.value, i8* %context) {
|
||||||
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 ptrtoint (i1 (i32)* @"error.$typeassert" to i32) }
|
|
||||||
@"reflect/methods.Error() string" = linkonce_odr constant i8 0
|
|
||||||
@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"]
|
|
||||||
@"reflect/methods.Align() int" = linkonce_odr constant i8 0
|
|
||||||
@"reflect/methods.Implements(reflect.Type) bool" = linkonce_odr constant i8 0
|
|
||||||
@"reflect.Type$interface" = linkonce_odr constant [2 x i8*] [i8* @"reflect/methods.Align() int", i8* @"reflect/methods.Implements(reflect.Type) bool"]
|
|
||||||
@"reflect/types.type:named:reflect.rawType" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:uintptr", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([20 x %runtime.interfaceMethodInfo], [20 x %runtime.interfaceMethodInfo]* @"reflect.rawType$methodset", i32 0, i32 0), %runtime.typecodeID* null, i32 0 }
|
|
||||||
@"reflect.rawType$methodset" = linkonce_odr constant [20 x %runtime.interfaceMethodInfo] zeroinitializer
|
|
||||||
@"reflect/types.type:basic:uintptr" = linkonce_odr constant %runtime.typecodeID zeroinitializer
|
|
||||||
|
|
||||||
define i1 @main.isError(i32 %typ.typecode, i8* %typ.value, i8* %context) {
|
|
||||||
entry:
|
entry:
|
||||||
%0 = ptrtoint i8* %typ.value to i32
|
%0 = call i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(i8* %typ.value)
|
||||||
%1 = call i1 @"error.$typeassert"(i32 %0)
|
ret i1 %0
|
||||||
ret i1 %1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
define i1 @main.isUnknown(i32 %typ.typecode, i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i8* %context) {
|
define i1 @main.isUnknown(i8* %typ.typecode, i8* %typ.value, i8* %itf.typecode, i8* %itf.value, i8* %context) {
|
||||||
entry:
|
entry:
|
||||||
%result = call i1 @"reflect.Type.Implements$invoke"(i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i32 %typ.typecode, i8* undef)
|
%result = call i1 @"reflect.Type.Implements$invoke"(i8* %typ.value, i8* %itf.typecode, i8* %itf.value, i8* %typ.typecode, i8* undef)
|
||||||
ret i1 %result
|
ret i1 %result
|
||||||
}
|
}
|
||||||
|
|
||||||
declare i1 @"reflect.Type.Implements$invoke"(i8*, i32, i8*, i32, i8*) #0
|
declare i1 @"reflect.Type.Implements$invoke"(i8*, i8*, i8*, i8*, i8*) #0
|
||||||
|
|
||||||
declare i1 @"error.$typeassert"(i32) #1
|
declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(i8*) #1
|
||||||
|
|
||||||
attributes #0 = { "tinygo-invoke"="reflect/methods.Implements(reflect.Type) bool" "tinygo-methods"="reflect/methods.Align() int; reflect/methods.Implements(reflect.Type) bool" }
|
attributes #0 = { "tinygo-invoke"="reflect/methods.Implements(reflect.Type) bool" "tinygo-methods"="reflect/methods.Align() int; reflect/methods.Implements(reflect.Type) bool" }
|
||||||
attributes #1 = { "tinygo-methods"="reflect/methods.Error() string" }
|
attributes #1 = { "tinygo-methods"="reflect/methods.Error() string" }
|
||||||
|
|||||||
Reference in New Issue
Block a user