mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-29 16:59:02 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c78cf05cd |
@@ -40,13 +40,13 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.27.0'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Restore LLVM source cache
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-22-${{ matrix.os }}-v1
|
||||
key: llvm-source-20-${{ matrix.os }}-v1
|
||||
path: |
|
||||
llvm-project/clang/lib/Headers
|
||||
llvm-project/clang/include
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-build
|
||||
with:
|
||||
key: llvm-build-22-${{ matrix.os }}-v1
|
||||
key: llvm-build-20-${{ matrix.os }}-v2
|
||||
path: llvm-build
|
||||
- name: Build LLVM
|
||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
path: build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz
|
||||
archive: false
|
||||
- name: Smoke tests
|
||||
run: make smoketest-quick TINYGO=$(PWD)/build/tinygo
|
||||
run: make smoketest TINYGO=$(PWD)/build/tinygo
|
||||
test-macos-homebrew:
|
||||
name: homebrew-install
|
||||
runs-on: macos-latest
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.27.0'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Build TinyGo (LLVM ${{ matrix.version }})
|
||||
run: go install -tags=llvm${{ matrix.version }}
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
env:
|
||||
# Oldest versions currently supported by TinyGo
|
||||
LLVM: "15"
|
||||
Go: "1.25" # when updating this, also update minorMin in builder/config.go
|
||||
Go: "1.24" # when updating this, also update minorMin in builder/config.go
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-22-linux-compat
|
||||
key: llvm-source-20-linux-compat
|
||||
path: llvm-project/compiler-rt
|
||||
- name: Download LLVM source
|
||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
||||
@@ -69,4 +69,4 @@ jobs:
|
||||
- run: tinygo version
|
||||
- run: make gen-device -j4
|
||||
- run: go test -tags=llvm${{ env.LLVM }} -short -skip=TestErrors
|
||||
- run: make smoketest-quick XTENSA=0
|
||||
- run: make smoketest XTENSA=0
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Print the CHANGELOG.md entry for one version, to use as release notes.
|
||||
# The file uses a setext heading: the bare version, then a line of dashes.
|
||||
|
||||
set -e
|
||||
|
||||
version="$1"
|
||||
if [ -z "$version" ]; then
|
||||
echo "usage: $0 <version>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
notes=$(awk -v version="$version" '
|
||||
found {
|
||||
if ($0 ~ /^---+$/ && previous != "") { previous = ""; exit }
|
||||
if (previous != "") print previous
|
||||
previous = $0
|
||||
next
|
||||
}
|
||||
$0 ~ /^---+$/ && previous == version {
|
||||
found = 1
|
||||
previous = ""
|
||||
next
|
||||
}
|
||||
{ previous = $0 }
|
||||
END { if (found && previous != "") print previous }
|
||||
' CHANGELOG.md)
|
||||
|
||||
if [ -z "$notes" ]; then
|
||||
echo "no CHANGELOG.md entry for version $version" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$notes"
|
||||
+16
-64
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.27.0'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Run go mod tidy
|
||||
run: go mod tidy
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
# statically linked binary.
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: golang:1.27-alpine
|
||||
image: golang:1.26-alpine
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-22-linux-alpine-v1
|
||||
key: llvm-source-20-linux-alpine-v2
|
||||
path: |
|
||||
llvm-project/clang/lib/Headers
|
||||
llvm-project/clang/include
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-build
|
||||
with:
|
||||
key: llvm-build-22-linux-alpine-v1
|
||||
key: llvm-build-20-linux-alpine-v2
|
||||
path: llvm-build
|
||||
- name: Build LLVM
|
||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
uses: actions/cache@v5
|
||||
id: cache-binaryen
|
||||
with:
|
||||
key: binaryen-linux-alpine-v3
|
||||
key: binaryen-linux-alpine-v2
|
||||
path: build/wasm-opt
|
||||
- name: Build Binaryen
|
||||
if: steps.cache-binaryen.outputs.cache-hit != 'true'
|
||||
@@ -160,7 +160,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.27.0'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Install wasmtime
|
||||
uses: bytecodealliance/actions/wasmtime/setup@v1
|
||||
@@ -180,43 +180,7 @@ jobs:
|
||||
- run: make tinygo-test-wasip1-fast
|
||||
- run: make tinygo-test-wasip2-fast
|
||||
- run: make tinygo-test-wasm
|
||||
smoketest-linux:
|
||||
# Build a binary for every supported board, using the binaries built in the
|
||||
# build-linux job. This is the only job that runs the full smoke test. The
|
||||
# result does not depend on the host OS, so the other jobs build a
|
||||
# representative board for each architecture with smoketest-quick.
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-linux
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Balanced with the measured build time of each group, so that the
|
||||
# shards finish at about the same time.
|
||||
group:
|
||||
- smoketest-rp2xxx smoketest-selftest smoketest-wasm-sim smoketest-py32
|
||||
- smoketest-esp smoketest-riscv smoketest-flags
|
||||
- smoketest-examples smoketest-nxp smoketest-pwm-usb smoketest-avr
|
||||
- smoketest-samd smoketest-stm32 smoketest-nrf smoketest-wasm
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.27.0'
|
||||
cache: true
|
||||
- name: Download release artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz
|
||||
- name: Extract release tarball
|
||||
run: |
|
||||
mkdir -p ~/lib
|
||||
tar -C ~/lib -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz
|
||||
ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo
|
||||
- run: make testchdir ${{ matrix.group }}
|
||||
- run: make smoketest
|
||||
assert-test-linux:
|
||||
# Run all tests that can run on Linux, with LLVM assertions enabled to catch
|
||||
# potential bugs.
|
||||
@@ -232,10 +196,6 @@ jobs:
|
||||
cat /proc/cpuinfo
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends \
|
||||
libgcrypt20 \
|
||||
libpixman-1-0 \
|
||||
libsdl2-2.0-0 \
|
||||
libslirp0 \
|
||||
qemu-system-arm \
|
||||
qemu-system-riscv32 \
|
||||
qemu-user \
|
||||
@@ -244,31 +204,23 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.27.0'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version: '18'
|
||||
- name: Install wasmtime
|
||||
uses: bytecodealliance/actions/wasmtime/setup@v1
|
||||
with:
|
||||
version: "29.0.1"
|
||||
- name: Setup `wasm-tools`
|
||||
uses: bytecodealliance/actions/wasm-tools/setup@v1
|
||||
- name: Install Espressif QEMU
|
||||
run: |
|
||||
release=esp-develop-9.2.2-20260417
|
||||
archive=qemu-xtensa-softmmu-${release//-/_}-x86_64-linux-gnu.tar.xz
|
||||
curl -fL --retry 3 -o "$RUNNER_TEMP/$archive" \
|
||||
"https://github.com/espressif/qemu/releases/download/$release/$archive"
|
||||
tar -xJf "$RUNNER_TEMP/$archive" -C "$RUNNER_TEMP"
|
||||
echo "$RUNNER_TEMP/qemu/bin" >> "$GITHUB_PATH"
|
||||
- name: Restore LLVM source cache
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-22-linux-asserts-v1
|
||||
key: llvm-source-20-linux-asserts-v1
|
||||
path: |
|
||||
llvm-project/clang/lib/Headers
|
||||
llvm-project/clang/include
|
||||
@@ -293,7 +245,7 @@ jobs:
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-build
|
||||
with:
|
||||
key: llvm-build-22-linux-asserts-v1
|
||||
key: llvm-build-20-linux-asserts-v1
|
||||
path: llvm-build
|
||||
- name: Build LLVM
|
||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
||||
@@ -329,7 +281,7 @@ jobs:
|
||||
echo "$(pwd)/build" >> $GITHUB_PATH
|
||||
- name: Test stdlib packages
|
||||
run: make tinygo-test
|
||||
- run: make smoketest-quick
|
||||
- run: make smoketest
|
||||
- run: make wasmtest
|
||||
- run: make tinygo-test-baremetal
|
||||
- name: Check Go code formatting
|
||||
@@ -371,13 +323,13 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.27.0'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Restore LLVM source cache
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-22-linux-v1
|
||||
key: llvm-source-20-linux-v1
|
||||
path: |
|
||||
llvm-project/clang/lib/Headers
|
||||
llvm-project/clang/include
|
||||
@@ -402,7 +354,7 @@ jobs:
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-build
|
||||
with:
|
||||
key: llvm-build-22-linux-${{ matrix.goarch }}-v1
|
||||
key: llvm-build-20-linux-${{ matrix.goarch }}-v1
|
||||
path: llvm-build
|
||||
- name: Build LLVM
|
||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
||||
@@ -426,7 +378,7 @@ jobs:
|
||||
uses: actions/cache@v5
|
||||
id: cache-binaryen
|
||||
with:
|
||||
key: binaryen-linux-${{ matrix.goarch }}-v5
|
||||
key: binaryen-linux-${{ matrix.goarch }}-v4
|
||||
path: build/wasm-opt
|
||||
- name: Build Binaryen
|
||||
if: steps.cache-binaryen.outputs.cache-hit != 'true'
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-22-linux-nix-v1
|
||||
key: llvm-source-20-linux-nix-v1
|
||||
path: |
|
||||
llvm-project/compiler-rt
|
||||
- name: Download LLVM source
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
# Publish a GitHub release from the artifacts that CI already built.
|
||||
#
|
||||
# The Linux, macOS and Windows workflows build every file that a release needs
|
||||
# when the release branch is pushed. This workflow collects the artifacts of
|
||||
# those runs for the tagged commit, so what ships is what was tested.
|
||||
#
|
||||
# The release is a draft, so the notes can be reviewed before publication.
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag to release, for example v0.42.0'
|
||||
required: true
|
||||
|
||||
concurrency:
|
||||
group: release-${{ inputs.tag || github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
TAG: ${{ inputs.tag || github.ref_name }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.tag || github.ref_name }}
|
||||
fetch-depth: 0
|
||||
- name: Read the version
|
||||
id: version
|
||||
# The release file names come from goenv/version.go, not from the tag,
|
||||
# so the two must agree.
|
||||
run: |
|
||||
version=$(./.github/workflows/tinygo-extract-version.sh | cut -d= -f2-)
|
||||
case "$version" in
|
||||
*-dev)
|
||||
echo "::error::goenv/version.go has development version $version"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
if [ "v$version" != "$TAG" ]; then
|
||||
echo "::error::tag $TAG does not match version $version in goenv/version.go"
|
||||
exit 1
|
||||
fi
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
- name: Wait for the CI runs of this commit
|
||||
id: runs
|
||||
# A pull request run has the merge commit as its head, so look at push
|
||||
# runs only.
|
||||
run: |
|
||||
sha=$(git rev-parse HEAD)
|
||||
for workflow in linux.yml build-macos.yml windows.yml; do
|
||||
id=
|
||||
for _ in $(seq 20); do
|
||||
id=$(gh run list --workflow "$workflow" --commit "$sha" --event push --limit 1 --json databaseId --jq '.[0].databaseId')
|
||||
if [ -n "$id" ]; then
|
||||
break
|
||||
fi
|
||||
echo "waiting for $workflow to start on $sha"
|
||||
sleep 30
|
||||
done
|
||||
if [ -z "$id" ]; then
|
||||
echo "::error::no $workflow run for commit $sha"
|
||||
exit 1
|
||||
fi
|
||||
echo "$workflow: run $id"
|
||||
gh run watch "$id" --exit-status > /dev/null || true
|
||||
conclusion=$(gh run view "$id" --json conclusion --jq .conclusion)
|
||||
if [ "$conclusion" != "success" ]; then
|
||||
echo "::error::$workflow run $id concluded with $conclusion"
|
||||
exit 1
|
||||
fi
|
||||
case "$workflow" in
|
||||
linux.yml) echo "linux=$id" >> "$GITHUB_OUTPUT" ;;
|
||||
build-macos.yml) echo "macos=$id" >> "$GITHUB_OUTPUT" ;;
|
||||
windows.yml) echo "windows=$id" >> "$GITHUB_OUTPUT" ;;
|
||||
esac
|
||||
done
|
||||
# The build jobs upload with `archive: false`, so the artifact is the
|
||||
# release file itself. skip-decompress keeps it that way. The Windows
|
||||
# release is a .zip, which a download would otherwise unpack.
|
||||
- name: Download the Linux artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
run-id: ${{ steps.runs.outputs.linux }}
|
||||
github-token: ${{ github.token }}
|
||||
path: artifacts
|
||||
skip-decompress: true
|
||||
- name: Download the macOS artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
run-id: ${{ steps.runs.outputs.macos }}
|
||||
github-token: ${{ github.token }}
|
||||
path: artifacts
|
||||
skip-decompress: true
|
||||
- name: Download the Windows artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
run-id: ${{ steps.runs.outputs.windows }}
|
||||
github-token: ${{ github.token }}
|
||||
path: artifacts
|
||||
skip-decompress: true
|
||||
- name: Collect the release files
|
||||
# A build that stopped uploading must not give a half complete release,
|
||||
# so list the files that are expected. The search is by file name, not
|
||||
# by artifact name, because the artifact names are not uniform.
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
version=$VERSION
|
||||
mkdir -p dist
|
||||
missing=0
|
||||
for file in \
|
||||
"tinygo$version.linux-amd64.tar.gz" "tinygo_${version}_amd64.deb" \
|
||||
"tinygo$version.linux-arm.tar.gz" "tinygo_${version}_armhf.deb" \
|
||||
"tinygo$version.linux-arm64.tar.gz" "tinygo_${version}_arm64.deb" \
|
||||
"tinygo$version.darwin-amd64.tar.gz" \
|
||||
"tinygo$version.darwin-arm64.tar.gz" \
|
||||
"tinygo$version.windows-amd64.zip"; do
|
||||
found=$(find artifacts -type f -name "$file" | head -1)
|
||||
if [ -z "$found" ]; then
|
||||
echo "::error::missing release file $file"
|
||||
missing=1
|
||||
else
|
||||
mv "$found" "dist/$file"
|
||||
fi
|
||||
done
|
||||
ls -l dist
|
||||
exit $missing
|
||||
- name: Extract the release notes
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: ./.github/workflows/extract-changelog.sh "$VERSION" > notes.md
|
||||
- name: Create the draft release
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
version=$VERSION
|
||||
set --
|
||||
case "$version" in
|
||||
*-*) set -- --prerelease ;;
|
||||
esac
|
||||
gh release create "$TAG" --draft --verify-tag "$@" \
|
||||
--title "$version" \
|
||||
--notes-file notes.md \
|
||||
dist/*
|
||||
@@ -19,10 +19,6 @@ jobs:
|
||||
- name: Add GOBIN to $PATH
|
||||
run: |
|
||||
echo "$HOME/go/bin" >> $GITHUB_PATH
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '~1.25'
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
@@ -34,7 +30,7 @@ jobs:
|
||||
uses: actions/cache@v5
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-22-sizediff-v1
|
||||
key: llvm-source-20-sizediff-v1
|
||||
path: |
|
||||
llvm-project/compiler-rt
|
||||
- name: Download LLVM source
|
||||
|
||||
@@ -34,13 +34,13 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.27.0'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Restore cached LLVM source
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-22-windows-v3
|
||||
key: llvm-source-20-windows-v3
|
||||
path: |
|
||||
llvm-project/clang/lib/Headers
|
||||
llvm-project/clang/include
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-build
|
||||
with:
|
||||
key: llvm-build-22-windows-v1
|
||||
key: llvm-build-20-windows-v4
|
||||
path: llvm-build
|
||||
- name: Build LLVM
|
||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.27.0'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Download TinyGo build
|
||||
uses: actions/download-artifact@v8
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
# This build is already unzipped.
|
||||
- name: Smoke tests
|
||||
shell: bash
|
||||
run: make smoketest-quick TINYGO=$(PWD)/build/tinygo/bin/tinygo
|
||||
run: make smoketest TINYGO=$(PWD)/build/tinygo/bin/tinygo
|
||||
|
||||
stdlib-test-windows:
|
||||
runs-on: windows-2022
|
||||
@@ -150,7 +150,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.27.0'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Download TinyGo build
|
||||
uses: actions/download-artifact@v8
|
||||
@@ -176,7 +176,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.27.0'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Download TinyGo build
|
||||
uses: actions/download-artifact@v8
|
||||
|
||||
@@ -24,8 +24,6 @@ src/device/renesas/*.go
|
||||
src/device/renesas/*.s
|
||||
src/device/rp/*.go
|
||||
src/device/rp/*.s
|
||||
src/device/py32/*.go
|
||||
src/device/py32/*.s
|
||||
./vendor
|
||||
llvm-build
|
||||
llvm-project
|
||||
|
||||
@@ -41,6 +41,3 @@
|
||||
[submodule "lib/bdwgc"]
|
||||
path = lib/bdwgc
|
||||
url = https://github.com/ivmai/bdwgc.git
|
||||
[submodule "lib/py32-svd"]
|
||||
path = lib/py32-svd
|
||||
url = https://github.com/tinygo-org/py32-svd.git
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
## Communication style
|
||||
Use ASD-STE-100 Simplified Technical English when you speak to the operator.
|
||||
|
||||
## Comments
|
||||
Use ASD-STE100 Simplified Technical English for content outside of the code like comments, PR description, PR title, github comments, and issues.
|
||||
|
||||
Generally omit extra redundant comments. If a comment is absolutely needed, always keep it brief, no more than 2 lines. Avoid extra use of colons, semicolons, and dashes. When comments are required due to some specific requirements, include a reference such as to a datasheet or other definitive source to explain why this is the case, including the chapter/section number, page number, and/or URL.
|
||||
|
||||
## No coding tool attributions
|
||||
Never add "created by XXX" or any other attributions from coding tools to any PRs, issues, comments, or anywhere else.
|
||||
+2
-37
@@ -30,10 +30,7 @@ on a different system like Mac.
|
||||
|
||||
## Using GNU Make
|
||||
|
||||
The static build of TinyGo is driven by GNUmakefile, which includes the topic
|
||||
files in the `make/` directory (`config.mk`, `llvm.mk`, `gen-device.mk`,
|
||||
`build.mk`, `test.mk`, `smoketest.mk`, `release.mk`, and `tools.mk`).
|
||||
It provides a help target for quick reference:
|
||||
The static build of TinyGo is driven by GNUmakefile, which provides a help target for quick reference:
|
||||
|
||||
% make help
|
||||
clean Remove build directory
|
||||
@@ -67,7 +64,7 @@ while producing binaries that are about as fast.
|
||||
export CC=clang
|
||||
export CXX=clang++
|
||||
|
||||
`make/config.mk` holds a default configuration that is good for most users. It
|
||||
The Makefile includes a default configuration that is good for most users. It
|
||||
builds a release version of LLVM (optimized, no asserts) and includes all
|
||||
targets supported by TinyGo:
|
||||
|
||||
@@ -119,35 +116,3 @@ the following command (for example in ~/lib):
|
||||
TinyGo will get extracted to a `tinygo` directory. You can then call it with:
|
||||
|
||||
./tinygo/bin/tinygo
|
||||
|
||||
## Publish a release
|
||||
|
||||
The `Release` workflow (`.github/workflows/release.yml`) publishes releases. It
|
||||
does not build anything. The Linux, macOS and Windows workflows already build
|
||||
every file that a release needs when the `release` branch is pushed, so the
|
||||
release workflow collects the artifacts of those runs for the tagged commit.
|
||||
What ships is what was tested.
|
||||
|
||||
1. On the `dev` branch, set `const version` in `goenv/version.go` to the new
|
||||
version (without a `v` prefix), and add the entry to `CHANGELOG.md`.
|
||||
2. Merge `dev` into the `release` branch.
|
||||
3. Tag that commit and push the tag:
|
||||
|
||||
git tag v0.42.0
|
||||
git push origin v0.42.0
|
||||
|
||||
The tag must be `v` plus the version in `goenv/version.go`, because the
|
||||
release file names come from that constant.
|
||||
4. The workflow waits for the Linux, macOS and Windows runs of the tagged
|
||||
commit, collects their nine files, and creates a **draft** release. The
|
||||
release notes come from the `CHANGELOG.md` entry for that version.
|
||||
5. Review the draft release and publish it.
|
||||
6. On the `dev` branch, set `goenv/version.go` to the next `-dev` version.
|
||||
|
||||
To release again after a failure, delete the draft release and start the
|
||||
workflow from the Actions tab with the tag as its input.
|
||||
|
||||
GitHub keeps a SHA-256 digest of every published file. The digest is not shown
|
||||
on the release page, but it can be printed with:
|
||||
|
||||
gh release view v0.42.0 --json assets --jq '.assets[] | "\(.digest) \(.name)"'
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
# tinygo-llvm stage obtains the llvm source for TinyGo
|
||||
FROM golang:1.27 AS tinygo-llvm
|
||||
FROM golang:1.26 AS tinygo-llvm
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y apt-utils make cmake clang-17 ninja-build && \
|
||||
@@ -10,7 +10,6 @@ RUN apt-get update && \
|
||||
/tmp/*
|
||||
|
||||
COPY ./GNUmakefile /tinygo/GNUmakefile
|
||||
COPY ./make /tinygo/make
|
||||
|
||||
RUN cd /tinygo/ && \
|
||||
make llvm-source
|
||||
@@ -34,7 +33,7 @@ RUN cd /tinygo/ && \
|
||||
|
||||
# tinygo-compiler copies the compiler build over to a base Go container (without
|
||||
# all the build tools etc).
|
||||
FROM golang:1.27 AS tinygo-compiler
|
||||
FROM golang:1.26 AS tinygo-compiler
|
||||
|
||||
# Copy tinygo build.
|
||||
COPY --from=tinygo-compiler-build /tinygo/build/release/tinygo /tinygo
|
||||
|
||||
+1237
-10
File diff suppressed because it is too large
Load Diff
+12
-19
@@ -14,7 +14,6 @@ import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
"hash/crc32"
|
||||
"maps"
|
||||
"math/bits"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -24,7 +23,6 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gofrs/flock"
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
@@ -139,7 +137,9 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
if _, ok := globalValues[pkgPath]; !ok {
|
||||
globalValues[pkgPath] = map[string]string{}
|
||||
}
|
||||
maps.Copy(globalValues[pkgPath], vals)
|
||||
for k, v := range vals {
|
||||
globalValues[pkgPath][k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Check for a libc dependency.
|
||||
@@ -270,7 +270,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
// Create the *ssa.Program. This does not yet build the entire SSA of the
|
||||
// program so it's pretty fast and doesn't need to be parallelized.
|
||||
program := lprogram.LoadSSA()
|
||||
buildProgram := sync.OnceFunc(program.Build)
|
||||
|
||||
// Add jobs to compile each package.
|
||||
// Packages that have a cache hit will not be compiled again.
|
||||
@@ -279,6 +278,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
|
||||
var embedFileObjects []*compileJob
|
||||
for _, pkg := range lprogram.Sorted() {
|
||||
pkg := pkg // necessary to avoid a race condition
|
||||
|
||||
var undefinedGlobals []string
|
||||
for name := range globalValues[pkg.Pkg.Path()] {
|
||||
@@ -400,13 +400,8 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
return nil
|
||||
}
|
||||
|
||||
// SSA package builds may run concurrently, but the resulting
|
||||
// functions cannot be inspected until all builds have finished:
|
||||
// generic instances and wrappers can be shared across packages.
|
||||
// Build the whole program once before compiling any package.
|
||||
buildProgram()
|
||||
|
||||
// Compile AST to IR.
|
||||
// Compile AST to IR. The compiler.CompilePackage function will
|
||||
// build the SSA as needed.
|
||||
mod, errs := compiler.CompilePackage(pkg.ImportPath, pkg, program.Package(pkg.Pkg), machine, compilerConfig, config.DumpSSA())
|
||||
defer mod.Context().Dispose()
|
||||
defer mod.Dispose()
|
||||
@@ -780,6 +775,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
// TODO: do this as part of building the package to be able to link the
|
||||
// bitcode files together.
|
||||
for _, pkg := range lprogram.Sorted() {
|
||||
pkg := pkg
|
||||
for _, filename := range pkg.CFiles {
|
||||
abspath := filepath.Join(pkg.OriginalDir(), filename)
|
||||
job := &compileJob{
|
||||
@@ -846,25 +842,22 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
}
|
||||
ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU())
|
||||
ldflags = append(ldflags, "-mllvm", "-mattr="+config.Features()) // needed for MIPS softfloat
|
||||
switch config.LinkerFlavor() {
|
||||
case "coff":
|
||||
// Options for driving ld.lld in PE/COFF mode.
|
||||
if config.GOOS() == "windows" {
|
||||
// Options for the MinGW wrapper for the lld COFF linker.
|
||||
ldflags = append(ldflags,
|
||||
"-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel),
|
||||
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
|
||||
case "darwin":
|
||||
} else if config.GOOS() == "darwin" {
|
||||
// Options for the ld64-compatible lld linker.
|
||||
ldflags = append(ldflags,
|
||||
"--lto-O"+strconv.Itoa(speedLevel),
|
||||
"-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
|
||||
case "gnu":
|
||||
} else {
|
||||
// Options for the ELF linker.
|
||||
ldflags = append(ldflags,
|
||||
"--lto-O"+strconv.Itoa(speedLevel),
|
||||
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"),
|
||||
)
|
||||
default:
|
||||
return fmt.Errorf("unknown linker flavor: %s", config.LinkerFlavor())
|
||||
}
|
||||
if config.CodeModel() != "default" {
|
||||
ldflags = append(ldflags,
|
||||
@@ -921,7 +914,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
}
|
||||
|
||||
// Run wasm-opt for wasm binaries
|
||||
if arch, _, _ := strings.Cut(config.Triple(), "-"); arch == "wasm32" {
|
||||
if arch := strings.Split(config.Triple(), "-")[0]; arch == "wasm32" {
|
||||
optLevel, _, _ := config.OptLevel()
|
||||
opt := "-" + optLevel
|
||||
|
||||
|
||||
+4
-61
@@ -5,7 +5,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
@@ -37,7 +36,6 @@ func TestClangAttributes(t *testing.T) {
|
||||
"nintendoswitch",
|
||||
"riscv-qemu",
|
||||
"tkey",
|
||||
"uefi-amd64",
|
||||
"wasip1",
|
||||
"wasip2",
|
||||
"wasm",
|
||||
@@ -49,6 +47,7 @@ func TestClangAttributes(t *testing.T) {
|
||||
targetNames = append(targetNames, "esp32", "esp8266")
|
||||
}
|
||||
for _, targetName := range targetNames {
|
||||
targetName := targetName
|
||||
t.Run(targetName, func(t *testing.T) {
|
||||
testClangAttributes(t, &compileopts.Options{Target: targetName})
|
||||
})
|
||||
@@ -130,10 +129,8 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
|
||||
defer mod.Dispose()
|
||||
|
||||
// Check whether the LLVM target matches.
|
||||
// Use ClangTriple since LLVM 22 normalizes wasm32-unknown-wasi to wasip1.
|
||||
expectedTriple := compileopts.ClangTriple(config.Triple())
|
||||
if mod.Target() != expectedTriple {
|
||||
t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", expectedTriple, mod.Target())
|
||||
if mod.Target() != config.Triple() {
|
||||
t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", config.Triple(), mod.Target())
|
||||
}
|
||||
|
||||
// Check the "target-cpu" and "target-features" string attribute of the add
|
||||
@@ -157,65 +154,11 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
|
||||
// The reason is that Debian has patched Clang in a way that
|
||||
// modifies the LLVM features string, changing lots of FPU/float
|
||||
// related flags. We want to test vanilla Clang, not Debian Clang.
|
||||
//
|
||||
// Rather than requiring an exact match (which breaks across LLVM
|
||||
// versions as new features are added), check that every feature
|
||||
// TinyGo specifies is consistent with what Clang produces:
|
||||
// - A "+feature" in TinyGo must appear in Clang's output.
|
||||
// - A "-feature" in TinyGo must not be "+feature" in Clang's output.
|
||||
checkFeatureFlags(t, config.Features(), features)
|
||||
t.Errorf("target has LLVM features\n\t%#v\nbut Clang makes it\n\t%#v", config.Features(), features)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkFeatureFlags verifies that all features specified by TinyGo's target
|
||||
// configuration are consistent with Clang's output. This allows Clang to add
|
||||
// new features across LLVM versions without breaking the test.
|
||||
func checkFeatureFlags(t *testing.T, targetFeatures, clangFeatures string) {
|
||||
t.Helper()
|
||||
|
||||
// Build a set of Clang's features for fast lookup.
|
||||
clangSet := make(map[string]bool) // feature name -> enabled
|
||||
for _, f := range strings.Split(clangFeatures, ",") {
|
||||
f = strings.TrimSpace(f)
|
||||
if len(f) < 2 {
|
||||
continue
|
||||
}
|
||||
enabled := f[0] == '+'
|
||||
name := f[1:]
|
||||
clangSet[name] = enabled
|
||||
}
|
||||
|
||||
// Check each feature that TinyGo specifies.
|
||||
var missing, conflicts []string
|
||||
for _, f := range strings.Split(targetFeatures, ",") {
|
||||
f = strings.TrimSpace(f)
|
||||
if len(f) < 2 {
|
||||
continue
|
||||
}
|
||||
wantEnabled := f[0] == '+'
|
||||
name := f[1:]
|
||||
|
||||
clangEnabled, inClang := clangSet[name]
|
||||
if wantEnabled && (!inClang || !clangEnabled) {
|
||||
// TinyGo requires +feature but Clang doesn't enable it.
|
||||
missing = append(missing, f)
|
||||
} else if !wantEnabled && inClang && clangEnabled {
|
||||
// TinyGo requires -feature but Clang enables it.
|
||||
conflicts = append(conflicts, fmt.Sprintf("target has %q but Clang has %q", f, "+"+name))
|
||||
}
|
||||
}
|
||||
|
||||
if len(missing) > 0 {
|
||||
t.Errorf("target specifies features not present in Clang output: %s\n\ttarget features: %s\n\tclang features: %s",
|
||||
strings.Join(missing, ", "), targetFeatures, clangFeatures)
|
||||
}
|
||||
if len(conflicts) > 0 {
|
||||
t.Errorf("target disables features that Clang enables: %s",
|
||||
strings.Join(conflicts, "; "))
|
||||
}
|
||||
}
|
||||
|
||||
// This TestMain is necessary because TinyGo may also be invoked to run certain
|
||||
// LLVM tools in a separate process. Not capturing these invocations would lead
|
||||
// to recursive tests.
|
||||
|
||||
+3
-29
@@ -204,18 +204,7 @@ var avrBuiltins = []string{
|
||||
|
||||
// Builtins needed specifically for windows/386.
|
||||
var windowsI386Builtins = []string{
|
||||
"i386/chkstk.S", // __chkstk_ms
|
||||
"i386/chkstk2.S", // _alloca (__alloca)
|
||||
}
|
||||
|
||||
// Builtins needed specifically for windows/amd64.
|
||||
var windowsAMD64Builtins = []string{
|
||||
"x86_64/chkstk.S",
|
||||
}
|
||||
|
||||
// Builtins needed specifically for windows/arm64.
|
||||
var windowsARM64Builtins = []string{
|
||||
"aarch64/chkstk.S",
|
||||
"i386/chkstk.S", // also _alloca
|
||||
}
|
||||
|
||||
// libCompilerRT is a library with symbols required by programs compiled with
|
||||
@@ -244,28 +233,13 @@ var libCompilerRT = Library{
|
||||
builtins = append(builtins, aeabiBuiltins...)
|
||||
case "avr":
|
||||
builtins = append(builtins, avrBuiltins...)
|
||||
case "x86_64":
|
||||
builtins = append(builtins, genericBuiltins128...)
|
||||
if isWindowsTriple(target) {
|
||||
builtins = append(builtins, windowsAMD64Builtins...)
|
||||
}
|
||||
case "aarch64":
|
||||
builtins = append(builtins, genericBuiltins128...)
|
||||
if isWindowsTriple(target) {
|
||||
builtins = append(builtins, windowsARM64Builtins...)
|
||||
}
|
||||
case "riscv64":
|
||||
case "x86_64", "aarch64", "riscv64": // any 64-bit arch
|
||||
builtins = append(builtins, genericBuiltins128...)
|
||||
case "i386":
|
||||
if isWindowsTriple(target) {
|
||||
if strings.Split(target, "-")[2] == "windows" {
|
||||
builtins = append(builtins, windowsI386Builtins...)
|
||||
}
|
||||
}
|
||||
return builtins, nil
|
||||
},
|
||||
}
|
||||
|
||||
func isWindowsTriple(target string) bool {
|
||||
parts := strings.Split(target, "-")
|
||||
return len(parts) > 2 && parts[2] == "windows"
|
||||
}
|
||||
|
||||
+1
-1
@@ -281,7 +281,7 @@ func parseDepFile(s string) ([]string, error) {
|
||||
s = strings.ReplaceAll(s, "\\\n", " ")
|
||||
|
||||
// Only use the first line, which is expected to begin with "deps:".
|
||||
line, _, _ := strings.Cut(s, "\n")
|
||||
line := strings.SplitN(s, "\n", 2)[0]
|
||||
if !strings.HasPrefix(line, "deps:") {
|
||||
return nil, errors.New("readDepFile: expected 'deps:' prefix")
|
||||
}
|
||||
|
||||
+17
-17
@@ -23,7 +23,7 @@
|
||||
#include "clang/Basic/Diagnostic.h"
|
||||
#include "clang/Basic/DiagnosticOptions.h"
|
||||
#include "clang/Driver/DriverDiagnostic.h"
|
||||
#include "clang/Options/Options.h"
|
||||
#include "clang/Driver/Options.h"
|
||||
#include "clang/Frontend/FrontendDiagnostic.h"
|
||||
#include "clang/Frontend/TextDiagnosticPrinter.h"
|
||||
#include "clang/Frontend/Utils.h"
|
||||
@@ -35,7 +35,6 @@
|
||||
#include "llvm/MC/MCAsmInfo.h"
|
||||
#include "llvm/MC/MCCodeEmitter.h"
|
||||
#include "llvm/MC/MCContext.h"
|
||||
#include "llvm/MC/MCInstPrinter.h"
|
||||
#include "llvm/MC/MCInstrInfo.h"
|
||||
#include "llvm/MC/MCObjectFileInfo.h"
|
||||
#include "llvm/MC/MCObjectWriter.h"
|
||||
@@ -68,7 +67,8 @@
|
||||
#include <optional>
|
||||
#include <system_error>
|
||||
using namespace clang;
|
||||
using namespace clang::options;
|
||||
using namespace clang::driver;
|
||||
using namespace clang::driver::options;
|
||||
using namespace llvm;
|
||||
using namespace llvm::opt;
|
||||
|
||||
@@ -109,7 +109,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
||||
// Construct the invocation.
|
||||
|
||||
// Target Options
|
||||
Opts.Triple = llvm::Triple(llvm::Triple::normalize(Args.getLastArgValue(OPT_triple)));
|
||||
Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple));
|
||||
if (Arg *A = Args.getLastArg(options::OPT_darwin_target_variant_triple))
|
||||
Opts.DarwinTargetVariantTriple = llvm::Triple(A->getValue());
|
||||
if (Arg *A = Args.getLastArg(OPT_darwin_target_variant_sdk_version_EQ)) {
|
||||
@@ -125,8 +125,8 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
||||
Opts.Features = Args.getAllArgValues(OPT_target_feature);
|
||||
|
||||
// Use the default target triple if unspecified.
|
||||
if (Opts.Triple.getTriple().empty())
|
||||
Opts.Triple = llvm::Triple(llvm::sys::getDefaultTargetTriple());
|
||||
if (Opts.Triple.empty())
|
||||
Opts.Triple = llvm::sys::getDefaultTargetTriple();
|
||||
|
||||
// Language Options
|
||||
Opts.IncludePaths = Args.getAllArgValues(OPT_I);
|
||||
@@ -267,7 +267,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
|
||||
std::string Error;
|
||||
const Target *TheTarget = TargetRegistry::lookupTarget(Opts.Triple, Error);
|
||||
if (!TheTarget)
|
||||
return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple.str();
|
||||
return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
|
||||
|
||||
ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
|
||||
MemoryBuffer::getFileOrSTDIN(Opts.InputFile, /*IsText=*/true);
|
||||
@@ -327,7 +327,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
|
||||
TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS));
|
||||
assert(STI && "Unable to create subtarget info!");
|
||||
|
||||
MCContext Ctx(Opts.Triple, MAI.get(), MRI.get(), STI.get(), &SrcMgr,
|
||||
MCContext Ctx(Triple(Opts.Triple), MAI.get(), MRI.get(), STI.get(), &SrcMgr,
|
||||
&MCOptions);
|
||||
|
||||
bool PIC = false;
|
||||
@@ -390,8 +390,8 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
|
||||
|
||||
// FIXME: There is a bit of code duplication with addPassesToEmitFile.
|
||||
if (Opts.OutputType == AssemblerInvocation::FT_Asm) {
|
||||
std::unique_ptr<MCInstPrinter> IP(TheTarget->createMCInstPrinter(
|
||||
Opts.Triple, Opts.OutputAsmVariant, *MAI, *MCII, *MRI));
|
||||
MCInstPrinter *IP = TheTarget->createMCInstPrinter(
|
||||
llvm::Triple(Opts.Triple), Opts.OutputAsmVariant, *MAI, *MCII, *MRI);
|
||||
|
||||
std::unique_ptr<MCCodeEmitter> CE;
|
||||
if (Opts.ShowEncoding)
|
||||
@@ -400,7 +400,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
|
||||
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
|
||||
|
||||
auto FOut = std::make_unique<formatted_raw_ostream>(*Out);
|
||||
Str.reset(TheTarget->createAsmStreamer(Ctx, std::move(FOut), std::move(IP),
|
||||
Str.reset(TheTarget->createAsmStreamer(Ctx, std::move(FOut), IP,
|
||||
std::move(CE), std::move(MAB)));
|
||||
} else if (Opts.OutputType == AssemblerInvocation::FT_Null) {
|
||||
Str.reset(createNullStreamer(Ctx));
|
||||
@@ -422,7 +422,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
|
||||
DwoOS ? MAB->createDwoObjectWriter(*Out, *DwoOS)
|
||||
: MAB->createObjectWriter(*Out);
|
||||
|
||||
Triple T = Opts.Triple;
|
||||
Triple T(Opts.Triple);
|
||||
Str.reset(TheTarget->createMCObjectStreamer(
|
||||
T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI));
|
||||
Str.get()->initSections(Opts.NoExecStack, *STI);
|
||||
@@ -453,7 +453,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
|
||||
std::unique_ptr<MCTargetAsmParser> TAP(
|
||||
TheTarget->createMCAsmParser(*STI, *Parser, *MCII, MCOptions));
|
||||
if (!TAP)
|
||||
Failed = Diags.Report(diag::err_target_unknown_triple) << Opts.Triple.str();
|
||||
Failed = Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
|
||||
|
||||
// Set values for symbols, if any.
|
||||
for (auto &S : Opts.SymbolDefs) {
|
||||
@@ -506,12 +506,12 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
|
||||
InitializeAllAsmParsers();
|
||||
|
||||
// Construct our diagnostic client.
|
||||
DiagnosticOptions DiagOpts;
|
||||
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
|
||||
TextDiagnosticPrinter *DiagClient
|
||||
= new TextDiagnosticPrinter(errs(), DiagOpts);
|
||||
= new TextDiagnosticPrinter(errs(), &*DiagOpts);
|
||||
DiagClient->setPrefix("clang -cc1as");
|
||||
IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
|
||||
DiagnosticsEngine Diags(DiagID, DiagOpts, DiagClient);
|
||||
DiagnosticsEngine Diags(DiagID, &*DiagOpts, DiagClient);
|
||||
|
||||
// Set an error handler, so that any LLVM backend diagnostics go through our
|
||||
// error handler.
|
||||
@@ -528,7 +528,7 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
|
||||
llvm::outs(), "clang -cc1as [options] file...",
|
||||
"Clang Integrated Assembler", /*ShowHidden=*/false,
|
||||
/*ShowAllAliases=*/false,
|
||||
llvm::opt::Visibility(clang::options::CC1AsOption));
|
||||
llvm::opt::Visibility(driver::options::CC1AsOption));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ struct AssemblerInvocation {
|
||||
/// @{
|
||||
|
||||
/// The name of the target triple to assemble for.
|
||||
llvm::Triple Triple;
|
||||
std::string Triple;
|
||||
|
||||
/// If given, the name of the target CPU to determine which instructions
|
||||
/// are legal.
|
||||
@@ -142,7 +142,7 @@ struct AssemblerInvocation {
|
||||
|
||||
public:
|
||||
AssemblerInvocation() {
|
||||
Triple = llvm::Triple();
|
||||
Triple = "";
|
||||
NoInitialTextSection = 0;
|
||||
InputFile = "-";
|
||||
OutputPath = "-";
|
||||
|
||||
+4
-4
@@ -27,9 +27,9 @@ bool tinygo_clang_driver(int argc, char **argv) {
|
||||
std::vector<const char*> args(argv, argv + argc);
|
||||
|
||||
// The compiler invocation needs a DiagnosticsEngine so it can report problems
|
||||
clang::DiagnosticOptions DiagOpts;
|
||||
clang::TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), DiagOpts);
|
||||
clang::DiagnosticsEngine Diags(llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>(new clang::DiagnosticIDs()), DiagOpts, &DiagnosticPrinter, false);
|
||||
llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> DiagOpts = new clang::DiagnosticOptions();
|
||||
clang::TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts);
|
||||
clang::DiagnosticsEngine Diags(llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>(new clang::DiagnosticIDs()), &*DiagOpts, &DiagnosticPrinter, false);
|
||||
|
||||
// Create the clang driver
|
||||
clang::driver::Driver TheDriver(args[0], llvm::sys::getDefaultTargetTriple(), Diags);
|
||||
@@ -60,7 +60,7 @@ bool tinygo_clang_driver(int argc, char **argv) {
|
||||
}
|
||||
|
||||
// Create the actual diagnostics engine.
|
||||
Clang->createDiagnostics();
|
||||
Clang->createDiagnostics(*llvm::vfs::getRealFileSystem());
|
||||
if (!Clang->hasDiagnostics()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ import (
|
||||
var commands = map[string][]string{}
|
||||
|
||||
func init() {
|
||||
llvmMajor, _, _ := strings.Cut(llvm.Version, ".")
|
||||
llvmMajor := strings.Split(llvm.Version, ".")[0]
|
||||
commands["clang"] = []string{"clang-" + llvmMajor}
|
||||
commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
|
||||
commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"}
|
||||
|
||||
+2
-2
@@ -25,8 +25,8 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
|
||||
}
|
||||
|
||||
// Version range supported by TinyGo.
|
||||
const minorMin = 25 // when updating the min version, also update .github/workflows/compat.yml
|
||||
const minorMax = 27
|
||||
const minorMin = 24 // when updating the min version, also update .github/workflows/compat.yml
|
||||
const minorMax = 26
|
||||
|
||||
// Check that we support this Go toolchain version.
|
||||
gorootMajor, gorootMinor, err := goenv.GetGorootVersion()
|
||||
|
||||
@@ -15,7 +15,7 @@ func makeDarwinLibSystemJob(config *compileopts.Config, tmpdir string) *compileJ
|
||||
return &compileJob{
|
||||
description: "compile Darwin libSystem.dylib",
|
||||
run: func(job *compileJob) (err error) {
|
||||
arch, _, _ := strings.Cut(config.Triple(), "-")
|
||||
arch := strings.Split(config.Triple(), "-")[0]
|
||||
job.result = filepath.Join(tmpdir, "libSystem.dylib")
|
||||
objpath := filepath.Join(tmpdir, "libSystem.o")
|
||||
inpath := filepath.Join(goenv.Get("TINYGOROOT"), "lib/macos-minimal-sdk/src", arch, "libSystem.s")
|
||||
|
||||
+9
-135
@@ -65,6 +65,15 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
|
||||
// Sort the segments by address. This is what esptool does too.
|
||||
sort.SliceStable(segments, func(i, j int) bool { return segments[i].addr < segments[j].addr })
|
||||
|
||||
// Calculate checksum over the segment data. This is used in the image
|
||||
// footer.
|
||||
checksum := uint8(0xef)
|
||||
for _, segment := range segments {
|
||||
for _, b := range segment.data {
|
||||
checksum ^= b
|
||||
}
|
||||
}
|
||||
|
||||
// Write first to an in-memory buffer, primarily so that we can easily
|
||||
// calculate a hash over the entire image.
|
||||
// An added benefit is that we don't need to check for errors all the time.
|
||||
@@ -79,83 +88,6 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
|
||||
chip = format[:len(format)-len("-img")]
|
||||
}
|
||||
|
||||
// For ESP32 (original): separate RAM segments (loadable by ROM bootloader)
|
||||
// from flash-mapped segments (DROM/IROM, require MMU setup by startup code).
|
||||
// The ROM bootloader on ESP32 does NOT handle flash-mapped segments —
|
||||
// it tries to memcpy to the virtual address, which crashes.
|
||||
var flashSegments []*espImageSegment
|
||||
if chip == "esp32" {
|
||||
var ramSegments []*espImageSegment
|
||||
for _, seg := range segments {
|
||||
if (seg.addr >= 0x3F400000 && seg.addr < 0x3F800000) || // DROM
|
||||
(seg.addr >= 0x400D0000 && seg.addr < 0x40400000) { // IROM
|
||||
flashSegments = append(flashSegments, seg)
|
||||
} else {
|
||||
ramSegments = append(ramSegments, seg)
|
||||
}
|
||||
}
|
||||
segments = ramSegments
|
||||
}
|
||||
|
||||
// ESP32 flash XIP: compute where the DROM segment will be placed in flash
|
||||
// (page-aligned, right after the RAM segments) and patch the
|
||||
// _drom_flash_addr variable so the startup code can program the cache MMU.
|
||||
// This must happen before the checksum/hash are computed so the patched
|
||||
// value is covered by both.
|
||||
const esp32FlashBase = 0x1000 // esptool flashes the image at 0x1000
|
||||
// The ESP32 flash cache MMU supports configurable page sizes down to 256 B. 64 KiB is the reset/default size.
|
||||
// If the startup code ever changes the MMU page size, this constant must change too.
|
||||
const esp32PageSize = 0x10000 // 64KB MMU pages
|
||||
var esp32DromFlashAddr uint32
|
||||
if chip == "esp32" && len(flashSegments) > 0 {
|
||||
// Compute the size of the RAM portion of the image (everything the ROM
|
||||
// bootloader loads, up to and including the appended SHA256 hash).
|
||||
ramImageSize := 0
|
||||
ramImageSize += 24 // image header (8) + trailer fields (16)
|
||||
for _, seg := range segments {
|
||||
ramImageSize += 8 + len(seg.data) // segment header + data (4-aligned)
|
||||
}
|
||||
ramImageSize += 16 - ramImageSize%16 // footer padding + checksum byte
|
||||
ramImageSize += 32 // appended SHA256 hash
|
||||
|
||||
// DROM flash address must be 64KB page-aligned.
|
||||
esp32DromFlashAddr = uint32(esp32FlashBase+ramImageSize+esp32PageSize-1) &^ (esp32PageSize - 1)
|
||||
|
||||
// Patch _drom_flash_addr in whichever RAM segment contains it.
|
||||
syms, _ := inf.Symbols()
|
||||
var dromSymAddr uint64
|
||||
for _, s := range syms {
|
||||
if s.Name == "_drom_flash_addr" {
|
||||
dromSymAddr = s.Value
|
||||
break
|
||||
}
|
||||
}
|
||||
if dromSymAddr == 0 {
|
||||
return fmt.Errorf("ESP32: _drom_flash_addr symbol not found")
|
||||
}
|
||||
patched := false
|
||||
for _, seg := range segments {
|
||||
if dromSymAddr >= uint64(seg.addr) && dromSymAddr+4 <= uint64(seg.addr)+uint64(len(seg.data)) {
|
||||
off := int(dromSymAddr - uint64(seg.addr))
|
||||
binary.LittleEndian.PutUint32(seg.data[off:], esp32DromFlashAddr)
|
||||
patched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !patched {
|
||||
return fmt.Errorf("ESP32: _drom_flash_addr (0x%x) not in any RAM segment", dromSymAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate checksum over the segment data. This is used in the image
|
||||
// footer.
|
||||
checksum := uint8(0xef)
|
||||
for _, segment := range segments {
|
||||
for _, b := range segment.data {
|
||||
checksum ^= b
|
||||
}
|
||||
}
|
||||
|
||||
if makeImage {
|
||||
// The bootloader starts at 0x1000, or 4096.
|
||||
// TinyGo doesn't use a separate bootloader and runs the entire
|
||||
@@ -259,64 +191,6 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
|
||||
outf.Write(hash[:])
|
||||
}
|
||||
|
||||
// For ESP32: append flash-mapped segments (DROM/IROM) at page-aligned flash
|
||||
// offsets after the RAM portion. The startup code maps them via the flash
|
||||
// cache MMU (DROM at esp32DromFlashAddr, patched into _drom_flash_addr).
|
||||
if len(flashSegments) > 0 {
|
||||
const flashBase = esp32FlashBase
|
||||
const pageSize = esp32PageSize
|
||||
dromFlashAddr := esp32DromFlashAddr
|
||||
|
||||
// Separate DROM and IROM segments.
|
||||
var dromSegs, iromSegs []*espImageSegment
|
||||
for _, seg := range flashSegments {
|
||||
if seg.addr >= 0x3F400000 && seg.addr < 0x3F800000 {
|
||||
dromSegs = append(dromSegs, seg)
|
||||
} else {
|
||||
iromSegs = append(iromSegs, seg)
|
||||
}
|
||||
}
|
||||
|
||||
// Write DROM segments at the computed page-aligned flash offset.
|
||||
dromSize := 0
|
||||
if len(dromSegs) > 0 {
|
||||
targetImageOffset := int(dromFlashAddr - flashBase)
|
||||
if makeImage {
|
||||
targetImageOffset = int(dromFlashAddr)
|
||||
}
|
||||
if outf.Len() > targetImageOffset {
|
||||
return fmt.Errorf("ESP32: RAM segments too large (%d bytes), overlap DROM at flash 0x%x", outf.Len(), dromFlashAddr)
|
||||
}
|
||||
outf.Write(make([]byte, targetImageOffset-outf.Len()))
|
||||
for _, seg := range dromSegs {
|
||||
outf.Write(seg.data)
|
||||
dromSize += len(seg.data)
|
||||
}
|
||||
}
|
||||
|
||||
// Write IROM segments immediately after DROM, at the next page boundary.
|
||||
// IROM flash addr = dromFlashAddr + ceil(dromSize/pageSize)*pageSize
|
||||
// (must match the computation in the startup assembly).
|
||||
if len(iromSegs) > 0 {
|
||||
dromPages := (dromSize + pageSize - 1) / pageSize
|
||||
if dromPages == 0 {
|
||||
dromPages = 1
|
||||
}
|
||||
iromFlashAddr := dromFlashAddr + uint32(dromPages)*pageSize
|
||||
targetImageOffset := int(iromFlashAddr - flashBase)
|
||||
if makeImage {
|
||||
targetImageOffset = int(iromFlashAddr)
|
||||
}
|
||||
if outf.Len() > targetImageOffset {
|
||||
return fmt.Errorf("ESP32: DROM too large, overlaps IROM at flash 0x%x", iromFlashAddr)
|
||||
}
|
||||
outf.Write(make([]byte, targetImageOffset-outf.Len()))
|
||||
for _, seg := range iromSegs {
|
||||
outf.Write(seg.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// QEMU (or more precisely, qemu-system-xtensa from Espressif) expects the
|
||||
// image to be a certain size.
|
||||
if makeImage {
|
||||
|
||||
+2
-2
@@ -195,11 +195,11 @@ type intHeap struct {
|
||||
sort.IntSlice
|
||||
}
|
||||
|
||||
func (h *intHeap) Push(x any) {
|
||||
func (h *intHeap) Push(x interface{}) {
|
||||
h.IntSlice = append(h.IntSlice, x.(int))
|
||||
}
|
||||
|
||||
func (h *intHeap) Pop() any {
|
||||
func (h *intHeap) Pop() interface{} {
|
||||
x := h.IntSlice[len(h.IntSlice)-1]
|
||||
h.IntSlice = h.IntSlice[:len(h.IntSlice)-1]
|
||||
return x
|
||||
|
||||
+2
-1
@@ -133,7 +133,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
|
||||
// Note: -fdebug-prefix-map is necessary to make the output archive
|
||||
// reproducible. Otherwise the temporary directory is stored in the archive
|
||||
// itself, which varies each run.
|
||||
args := append(l.cflags(target, headerPath), "-c", "-Oz", "-gdwarf-4", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+compileopts.ClangTriple(target), "-fdebug-prefix-map="+dir+"="+remapDir)
|
||||
args := append(l.cflags(target, headerPath), "-c", "-Oz", "-gdwarf-4", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
|
||||
resourceDir := goenv.ClangResourceDir(false)
|
||||
if resourceDir != "" {
|
||||
args = append(args, "-resource-dir="+resourceDir)
|
||||
@@ -232,6 +232,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
|
||||
}
|
||||
for _, path := range paths {
|
||||
// Strip leading "../" parts off the path.
|
||||
path := path
|
||||
cleanpath := path
|
||||
for strings.HasPrefix(cleanpath, "../") {
|
||||
cleanpath = cleanpath[3:]
|
||||
|
||||
+2
-2
@@ -28,8 +28,8 @@ func buildMuslAllTypes(arch, muslDir, outputBitsDir string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lines := strings.SplitSeq(string(data), "\n")
|
||||
for line := range lines {
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "TYPEDEF ") {
|
||||
matches := regexp.MustCompile(`TYPEDEF (.*) ([^ ]*);`).FindStringSubmatch(line)
|
||||
value := matches[1]
|
||||
|
||||
+34
-120
@@ -1,13 +1,8 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -16,19 +11,21 @@ import (
|
||||
|
||||
var sema = make(chan struct{}, runtime.NumCPU())
|
||||
|
||||
var flagUpdate = flag.Bool("update", false, "update builder package tests")
|
||||
|
||||
type sizeTest struct {
|
||||
target string
|
||||
path string
|
||||
target string
|
||||
path string
|
||||
codeSize uint64
|
||||
rodataSize uint64
|
||||
dataSize uint64
|
||||
bssSize uint64
|
||||
}
|
||||
|
||||
// Test whether code and data size is as expected for the given targets.
|
||||
// This tests both the logic of loadProgramSize and checks that code size
|
||||
// doesn't change unintentionally.
|
||||
//
|
||||
// If you find that code or data size is reduced, then great! You can update the
|
||||
// golden file by passing -update to the test.
|
||||
// If you find that code or data size is reduced, then great! You can reduce the
|
||||
// number in this test.
|
||||
// If you find that the code or data size is increased, take a look as to why
|
||||
// this is. It could be due to an update (LLVM version, Go version, etc) which
|
||||
// is fine, but it could also mean that a recent change introduced this size
|
||||
@@ -45,110 +42,35 @@ func TestBinarySize(t *testing.T) {
|
||||
// This is a small number of very diverse targets that we want to test.
|
||||
tests := []sizeTest{
|
||||
// microcontrollers
|
||||
{"hifive1b", "examples/echo"},
|
||||
{"microbit", "examples/serial"},
|
||||
{"wioterminal", "examples/pininterrupt"},
|
||||
{"hifive1b", "examples/echo", 3699, 297, 0, 2252},
|
||||
{"microbit", "examples/serial", 2736, 356, 8, 2248},
|
||||
{"wioterminal", "examples/pininterrupt", 7960, 1652, 132, 7480},
|
||||
|
||||
// TODO: also check wasm. Right now this is difficult, because
|
||||
// wasm binaries are run through wasm-opt and therefore the
|
||||
// output varies by binaryen version.
|
||||
}
|
||||
sizes := measureBinarySizes(t, tests)
|
||||
output := formatSizeTable(tests, sizes)
|
||||
checkGolden(t, "testdata/binary-size.txt", output)
|
||||
}
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.target+"/"+tc.path, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
func checkGolden(t *testing.T, path, actual string) {
|
||||
t.Helper()
|
||||
if *flagUpdate {
|
||||
if err := os.WriteFile(path, []byte(actual), 0o666); err != nil {
|
||||
t.Fatal("failed to write updated golden file:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
expected, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal("failed to read golden file:", err)
|
||||
}
|
||||
if actual != string(expected) {
|
||||
t.Errorf("%s does not match expected output (re-run with -update to regenerate):\nexpected:\n%sactual:\n%s", path, expected, actual)
|
||||
}
|
||||
}
|
||||
// Build the binary.
|
||||
result := buildBinary(t, tc.target, tc.path)
|
||||
|
||||
func measureBinarySizes(t *testing.T, tests []sizeTest) []*programSize {
|
||||
t.Helper()
|
||||
type result struct {
|
||||
index int
|
||||
size *programSize
|
||||
err error
|
||||
// Check whether the size of the binary matches the expected size.
|
||||
sizes, err := loadProgramSize(result.Executable, nil)
|
||||
if err != nil {
|
||||
t.Fatal("could not read program size:", err)
|
||||
}
|
||||
if sizes.Code != tc.codeSize || sizes.ROData != tc.rodataSize || sizes.Data != tc.dataSize || sizes.BSS != tc.bssSize {
|
||||
t.Errorf("Unexpected code size when compiling: -target=%s %s", tc.target, tc.path)
|
||||
t.Errorf(" code rodata data bss")
|
||||
t.Errorf("expected: %6d %6d %6d %6d", tc.codeSize, tc.rodataSize, tc.dataSize, tc.bssSize)
|
||||
t.Errorf("actual: %6d %6d %6d %6d", sizes.Code, sizes.ROData, sizes.Data, sizes.BSS)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
results := make(chan result, len(tests))
|
||||
for i, tc := range tests {
|
||||
tmpdir := t.TempDir()
|
||||
go func() {
|
||||
size, err := measureBinarySize(tc, tmpdir)
|
||||
results <- result{i, size, err}
|
||||
}()
|
||||
}
|
||||
|
||||
sizes := make([]*programSize, len(tests))
|
||||
failed := false
|
||||
for range tests {
|
||||
result := <-results
|
||||
if result.err != nil {
|
||||
tc := tests[result.index]
|
||||
t.Errorf("%s/%s: %v", tc.target, tc.path, result.err)
|
||||
failed = true
|
||||
}
|
||||
sizes[result.index] = result.size
|
||||
}
|
||||
if failed {
|
||||
t.FailNow()
|
||||
}
|
||||
return sizes
|
||||
}
|
||||
|
||||
func measureBinarySize(tc sizeTest, tmpdir string) (*programSize, error) {
|
||||
result, err := buildBinaryInDir(tc.target, tc.path, tmpdir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size, err := loadProgramSize(result.Executable, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read program size: %w", err)
|
||||
}
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func formatSizeTable(tests []sizeTest, sizes []*programSize) string {
|
||||
targetWidth := len("target")
|
||||
packageWidth := len("package")
|
||||
codeWidth := len("code")
|
||||
rodataWidth := len("rodata")
|
||||
dataWidth := len("data")
|
||||
bssWidth := len("bss")
|
||||
for i, tc := range tests {
|
||||
targetWidth = max(targetWidth, len(tc.target))
|
||||
packageWidth = max(packageWidth, len(tc.path))
|
||||
codeWidth = max(codeWidth, len(strconv.FormatUint(sizes[i].Code, 10)))
|
||||
rodataWidth = max(rodataWidth, len(strconv.FormatUint(sizes[i].ROData, 10)))
|
||||
dataWidth = max(dataWidth, len(strconv.FormatUint(sizes[i].Data, 10)))
|
||||
bssWidth = max(bssWidth, len(strconv.FormatUint(sizes[i].BSS, 10)))
|
||||
}
|
||||
|
||||
var output strings.Builder
|
||||
fmt.Fprintf(&output, "%-*s %-*s %*s %*s %*s %*s\n",
|
||||
targetWidth, "target", packageWidth, "package",
|
||||
codeWidth, "code", rodataWidth, "rodata", dataWidth, "data", bssWidth, "bss")
|
||||
for i, tc := range tests {
|
||||
size := sizes[i]
|
||||
fmt.Fprintf(&output, "%-*s %-*s %*d %*d %*d %*d\n",
|
||||
targetWidth, tc.target, packageWidth, tc.path,
|
||||
codeWidth, size.Code, rodataWidth, size.ROData,
|
||||
dataWidth, size.Data, bssWidth, size.BSS)
|
||||
}
|
||||
return output.String()
|
||||
}
|
||||
|
||||
// Check that the -size=full flag attributes binary size to the correct package
|
||||
@@ -163,6 +85,7 @@ func TestSizeFull(t *testing.T) {
|
||||
pkgMatch := regexp.MustCompile(`^[a-z/]+$`) // example: "internal/task"
|
||||
|
||||
for _, target := range tests {
|
||||
target := target
|
||||
t.Run(target, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -193,15 +116,6 @@ func TestSizeFull(t *testing.T) {
|
||||
}
|
||||
|
||||
func buildBinary(t *testing.T, targetString, pkgName string) BuildResult {
|
||||
t.Helper()
|
||||
result, err := buildBinaryInDir(targetString, pkgName, t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func buildBinaryInDir(targetString, pkgName, tmpdir string) (BuildResult, error) {
|
||||
options := compileopts.Options{
|
||||
Target: targetString,
|
||||
Opt: "z",
|
||||
@@ -212,15 +126,15 @@ func buildBinaryInDir(targetString, pkgName, tmpdir string) (BuildResult, error)
|
||||
}
|
||||
target, err := compileopts.LoadTarget(&options)
|
||||
if err != nil {
|
||||
return BuildResult{}, fmt.Errorf("could not load target: %w", err)
|
||||
t.Fatal("could not load target:", err)
|
||||
}
|
||||
config := &compileopts.Config{
|
||||
Options: &options,
|
||||
Target: target,
|
||||
}
|
||||
result, err := Build(pkgName, "", tmpdir, config)
|
||||
result, err := Build(pkgName, "", t.TempDir(), config)
|
||||
if err != nil {
|
||||
return BuildResult{}, fmt.Errorf("could not build: %w", err)
|
||||
t.Fatal("could not build:", err)
|
||||
}
|
||||
return result, nil
|
||||
return result
|
||||
}
|
||||
|
||||
Vendored
-4
@@ -1,4 +0,0 @@
|
||||
target package code rodata data bss
|
||||
hifive1b examples/echo 4321 323 0 2268
|
||||
microbit examples/serial 2842 382 8 2264
|
||||
wioterminal examples/pininterrupt 8039 1665 132 7496
|
||||
+1
-1
@@ -116,7 +116,7 @@ func parseLLDErrors(text string) error {
|
||||
// This can happen in some cases like with CGo and //go:linkname tricker.
|
||||
if matches := regexp.MustCompile(`^ld.lld(-[0-9]+)?: error: undefined symbol: (.*)\n`).FindStringSubmatch(message); matches != nil {
|
||||
symbolName := matches[2]
|
||||
for line := range strings.SplitSeq(message, "\n") {
|
||||
for _, line := range strings.Split(message, "\n") {
|
||||
matches := regexp.MustCompile(`referenced by .* \(((.*):([0-9]+))\)`).FindStringSubmatch(line)
|
||||
if matches != nil {
|
||||
parsedError = true
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ func convertBinToUF2(input []byte, targetAddr uint32, uf2FamilyID string) ([]byt
|
||||
}
|
||||
bl.SetNumBlocks(len(blocks))
|
||||
|
||||
for i := range blocks {
|
||||
for i := 0; i < len(blocks); i++ {
|
||||
bl.SetBlockNo(i)
|
||||
bl.SetData(blocks[i])
|
||||
|
||||
|
||||
+8
-10
@@ -40,7 +40,7 @@ type cgoPackage struct {
|
||||
tokenFiles map[string]*token.File
|
||||
definedGlobally map[string]ast.Node
|
||||
noescapingFuncs map[string]*noescapingFunc // #cgo noescape lines
|
||||
anonDecls map[any]string
|
||||
anonDecls map[interface{}]string
|
||||
cflags []string // CFlags from #cgo lines
|
||||
ldflags []string // LDFlags from #cgo lines
|
||||
visitedFiles map[string][]byte
|
||||
@@ -259,7 +259,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
|
||||
tokenFiles: map[string]*token.File{},
|
||||
definedGlobally: map[string]ast.Node{},
|
||||
noescapingFuncs: map[string]*noescapingFunc{},
|
||||
anonDecls: map[any]string{},
|
||||
anonDecls: map[interface{}]string{},
|
||||
visitedFiles: map[string][]byte{},
|
||||
}
|
||||
|
||||
@@ -302,7 +302,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
|
||||
// Find `import "C"` C fragments in the file.
|
||||
p.cgoHeaders = make([]string, len(files)) // combined CGo header fragment for each file
|
||||
for i, f := range files {
|
||||
var cgoHeader strings.Builder
|
||||
var cgoHeader string
|
||||
for i := 0; i < len(f.Decls); i++ {
|
||||
decl := f.Decls[i]
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
@@ -337,8 +337,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
|
||||
// Iterate through all parts of the CGo header. Note that every //
|
||||
// line is a new comment.
|
||||
position := fset.Position(genDecl.Doc.Pos())
|
||||
var fragment strings.Builder
|
||||
fragment.WriteString(fmt.Sprintf("# %d %#v\n", position.Line, position.Filename))
|
||||
fragment := fmt.Sprintf("# %d %#v\n", position.Line, position.Filename)
|
||||
for _, comment := range genDecl.Doc.List {
|
||||
// Find all #cgo lines, extract and use their contents, and
|
||||
// replace the lines with spaces (to preserve locations).
|
||||
@@ -355,13 +354,12 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
|
||||
} else { // comment
|
||||
c = " " + c[2:len(c)-2]
|
||||
}
|
||||
fragment.WriteString(c)
|
||||
fragment.WriteByte('\n')
|
||||
fragment += c + "\n"
|
||||
}
|
||||
cgoHeader.WriteString(fragment.String())
|
||||
cgoHeader += fragment
|
||||
}
|
||||
|
||||
p.cgoHeaders[i] = cgoHeader.String()
|
||||
p.cgoHeaders[i] = cgoHeader
|
||||
}
|
||||
|
||||
// Define CFlags that will be used while parsing the package.
|
||||
@@ -1219,7 +1217,7 @@ func getPos(node ast.Node) token.Pos {
|
||||
// getUnnamedDeclName creates a name (with the given prefix) for the given C
|
||||
// declaration. This is used for structs, unions, and enums that are often
|
||||
// defined without a name and used in a typedef.
|
||||
func (p *cgoPackage) getUnnamedDeclName(prefix string, itf any) string {
|
||||
func (p *cgoPackage) getUnnamedDeclName(prefix string, itf interface{}) string {
|
||||
if name, ok := p.anonDecls[itf]; ok {
|
||||
return name
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ func TestCGo(t *testing.T) {
|
||||
"flags",
|
||||
"const",
|
||||
} {
|
||||
name := name // avoid a race condition
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Read the AST in memory.
|
||||
path := filepath.Join("testdata", name+".go")
|
||||
|
||||
+7
-13
@@ -160,7 +160,7 @@ func (f *cgoFile) readNames(fragment string, cflags []string, filename string, c
|
||||
pos := f.getClangLocationPosition(location, unit)
|
||||
f.addError(pos, severity+": "+spelling)
|
||||
}
|
||||
for i := range numDiagnostics {
|
||||
for i := 0; i < numDiagnostics; i++ {
|
||||
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
|
||||
addDiagnostic(diagnostic)
|
||||
|
||||
@@ -278,7 +278,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
Text: strings.Join(doc, "\n"),
|
||||
})
|
||||
}
|
||||
for i := range numArgs {
|
||||
for i := 0; i < numArgs; i++ {
|
||||
arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i))
|
||||
argName := getString(C.tinygo_clang_getCursorSpelling(arg))
|
||||
argType := C.clang_getArgType(cursorType, C.uint(i))
|
||||
@@ -330,8 +330,10 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
NamePos: pos,
|
||||
Name: typeName,
|
||||
},
|
||||
Assign: pos,
|
||||
Type: f.makeASTType(underlyingType, pos),
|
||||
Type: f.makeASTType(underlyingType, pos),
|
||||
}
|
||||
if underlyingType.kind != C.CXType_Enum {
|
||||
typeSpec.Assign = pos
|
||||
}
|
||||
return typeSpec, nil
|
||||
case C.CXCursor_VarDecl:
|
||||
@@ -532,7 +534,7 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
|
||||
|
||||
// Get the precise location in the source code. Used for uniquely identifying
|
||||
// source locations.
|
||||
func (f *cgoFile) getUniqueLocationID(pos token.Pos, cursor C.GoCXCursor) any {
|
||||
func (f *cgoFile) getUniqueLocationID(pos token.Pos, cursor C.GoCXCursor) interface{} {
|
||||
clangLocation := C.tinygo_clang_getCursorLocation(cursor)
|
||||
var file C.CXFile
|
||||
var line C.unsigned
|
||||
@@ -804,14 +806,6 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
||||
f.addError(pos, fmt.Sprintf("unknown elaborated type (libclang type kind %s)", typeKindSpelling))
|
||||
typeName = "<unknown>"
|
||||
}
|
||||
case C.CXType_Unexposed:
|
||||
// LLVM 22+ may report certain builtin type aliases (e.g. __size_t)
|
||||
// as Unexposed. Resolve via the canonical type.
|
||||
canonical := C.clang_getCanonicalType(typ)
|
||||
if canonical.kind != C.CXType_Unexposed && canonical.kind != C.CXType_Invalid {
|
||||
return f.makeASTType(canonical, pos)
|
||||
}
|
||||
// If still unexposed, fall through to the error below.
|
||||
case C.CXType_Record:
|
||||
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !byollvm && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19 && !llvm21 && !llvm22
|
||||
//go:build !byollvm && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19
|
||||
|
||||
package cgo
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
//go:build !byollvm && llvm21
|
||||
|
||||
package cgo
|
||||
|
||||
/*
|
||||
#cgo linux CFLAGS: -I/usr/include/llvm-21 -I/usr/include/llvm-c-21 -I/usr/lib/llvm-21/include -I/usr/lib64/llvm21/include
|
||||
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@21/include
|
||||
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@21/include
|
||||
#cgo freebsd CFLAGS: -I/usr/local/llvm21/include
|
||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-21/lib -lclang
|
||||
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@21/lib -lclang
|
||||
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@21/lib -lclang
|
||||
#cgo freebsd LDFLAGS: -L/usr/local/llvm21/lib -lclang
|
||||
*/
|
||||
import "C"
|
||||
@@ -1,15 +0,0 @@
|
||||
//go:build !byollvm && llvm22
|
||||
|
||||
package cgo
|
||||
|
||||
/*
|
||||
#cgo linux CFLAGS: -I/usr/include/llvm-22 -I/usr/include/llvm-c-22 -I/usr/lib/llvm-22/include -I/usr/lib64/llvm22/include
|
||||
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@22/include
|
||||
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@22/include
|
||||
#cgo freebsd CFLAGS: -I/usr/local/llvm22/include
|
||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-22/lib -lclang
|
||||
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@22/lib -lclang
|
||||
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@22/lib -lclang
|
||||
#cgo freebsd LDFLAGS: -L/usr/local/llvm22/lib -lclang
|
||||
*/
|
||||
import "C"
|
||||
+4
-4
@@ -12,17 +12,17 @@ import "C"
|
||||
// C. It is useful if an API uses function pointers and you cannot pass a Go
|
||||
// pointer but only a C pointer.
|
||||
type refMap struct {
|
||||
refs map[unsafe.Pointer]any
|
||||
refs map[unsafe.Pointer]interface{}
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// Put stores a value in the map. It can later be retrieved using Get. It must
|
||||
// be removed using Remove to avoid memory leaks.
|
||||
func (m *refMap) Put(v any) unsafe.Pointer {
|
||||
func (m *refMap) Put(v interface{}) unsafe.Pointer {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
if m.refs == nil {
|
||||
m.refs = make(map[unsafe.Pointer]any, 1)
|
||||
m.refs = make(map[unsafe.Pointer]interface{}, 1)
|
||||
}
|
||||
ref := C.malloc(1)
|
||||
m.refs[ref] = v
|
||||
@@ -31,7 +31,7 @@ func (m *refMap) Put(v any) unsafe.Pointer {
|
||||
|
||||
// Get returns a stored value previously inserted with Put. Use the same
|
||||
// reference as you got from Put.
|
||||
func (m *refMap) Get(ref unsafe.Pointer) any {
|
||||
func (m *refMap) Get(ref unsafe.Pointer) interface{} {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
return m.refs[ref]
|
||||
|
||||
+14
-27
@@ -8,7 +8,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -61,15 +60,13 @@ func (c *Config) BuildMode() string {
|
||||
// RISC-V processor, that could be "+a,+c,+m". For many targets, an empty list
|
||||
// will be returned.
|
||||
func (c *Config) Features() string {
|
||||
var features string
|
||||
if c.Target.Features == "" {
|
||||
features = c.Options.LLVMFeatures
|
||||
} else if c.Options.LLVMFeatures == "" {
|
||||
features = c.Target.Features
|
||||
} else {
|
||||
features = c.Target.Features + "," + c.Options.LLVMFeatures
|
||||
return c.Options.LLVMFeatures
|
||||
}
|
||||
return patchFeatures(features)
|
||||
if c.Options.LLVMFeatures == "" {
|
||||
return c.Target.Features
|
||||
}
|
||||
return c.Target.Features + "," + c.Options.LLVMFeatures
|
||||
}
|
||||
|
||||
// ABI returns the -mabi= flag for this target (like -mabi=lp64). A zero-length
|
||||
@@ -143,7 +140,13 @@ func (c *Config) GC() string {
|
||||
func (c *Config) NeedsStackObjects() bool {
|
||||
switch c.GC() {
|
||||
case "conservative", "custom", "precise", "boehm":
|
||||
return slices.Contains(c.BuildTags(), "tinygo.wasm")
|
||||
for _, tag := range c.BuildTags() {
|
||||
if tag == "tinygo.wasm" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -242,7 +245,7 @@ func (c *Config) RP2040BootPatch() bool {
|
||||
// Return a canonicalized architecture name, so we don't have to deal with arm*
|
||||
// vs thumb* vs arm64.
|
||||
func CanonicalArchName(triple string) string {
|
||||
arch, _, _ := strings.Cut(triple, "-")
|
||||
arch := strings.Split(triple, "-")[0]
|
||||
if arch == "arm64" {
|
||||
return "aarch64"
|
||||
}
|
||||
@@ -349,7 +352,7 @@ func (c *Config) CFlags(libclang bool) []string {
|
||||
// Use the same optimization level as TinyGo.
|
||||
cflags = append(cflags, "-O"+c.Options.Opt)
|
||||
// Set the LLVM target triple.
|
||||
cflags = append(cflags, "--target="+ClangTriple(c.Triple()))
|
||||
cflags = append(cflags, "--target="+c.Triple())
|
||||
// Set the -mcpu (or similar) flag.
|
||||
if c.Target.CPU != "" {
|
||||
if c.GOARCH() == "amd64" || c.GOARCH() == "386" {
|
||||
@@ -463,22 +466,6 @@ func (c *Config) LDFlags() []string {
|
||||
return ldflags
|
||||
}
|
||||
|
||||
// LinkerFlavor returns how the configured linker should be driven.
|
||||
// Usually this is derived from GOOS, but targets may override it explicitly.
|
||||
func (c *Config) LinkerFlavor() string {
|
||||
if c.Target.LinkerFlavor != "" {
|
||||
return c.Target.LinkerFlavor
|
||||
}
|
||||
switch c.GOOS() {
|
||||
case "windows":
|
||||
return "coff"
|
||||
case "darwin":
|
||||
return "darwin"
|
||||
default:
|
||||
return "gnu"
|
||||
}
|
||||
}
|
||||
|
||||
// ExtraFiles returns the list of extra files to be built and linked with the
|
||||
// executable. This can include extra C and assembly files.
|
||||
func (c *Config) ExtraFiles() []string {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
//go:build !llvm22 && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19
|
||||
|
||||
package compileopts
|
||||
|
||||
// patchFeatures applies LLVM-version-specific feature name mappings.
|
||||
// For LLVM 20/21, features in the target JSON files are already correct.
|
||||
func patchFeatures(features string) string {
|
||||
return features
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
//go:build llvm22
|
||||
|
||||
package compileopts
|
||||
|
||||
import "strings"
|
||||
|
||||
// patchFeatures applies LLVM-version-specific feature name mappings.
|
||||
// LLVM 22 renamed several Xtensa target features.
|
||||
func patchFeatures(features string) string {
|
||||
// Xtensa feature renames in LLVM 22:
|
||||
// atomctl → (removed, no direct replacement)
|
||||
// memctl → (removed, no direct replacement)
|
||||
// esp32s3 → esp32s3ops
|
||||
// timerint → timers3 (for esp32/esp32s3) or timers1 (for esp8266)
|
||||
// Since we can't distinguish which timer variant at this level,
|
||||
// just remove the obsolete features. The CPU definition already
|
||||
// implies the correct features in LLVM 22.
|
||||
replacer := strings.NewReplacer(
|
||||
"+atomctl,", "",
|
||||
"+memctl,", "",
|
||||
"+esp32s3,", "+esp32s3ops,",
|
||||
"+timerint,", "",
|
||||
",+timerint", "",
|
||||
)
|
||||
return replacer.Replace(features)
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
//go:build llvm14 || llvm15 || llvm16 || llvm17 || llvm18 || llvm19
|
||||
|
||||
package compileopts
|
||||
|
||||
import "strings"
|
||||
|
||||
// patchFeatures applies LLVM-version-specific feature name mappings.
|
||||
// LLVM 19 and earlier do not have +bulk-memory-opt or
|
||||
// +call-indirect-overlong for WebAssembly (added in LLVM 20).
|
||||
func patchFeatures(features string) string {
|
||||
features = strings.ReplaceAll(features, ",+bulk-memory-opt", "")
|
||||
features = strings.ReplaceAll(features, "+bulk-memory-opt,", "")
|
||||
features = strings.ReplaceAll(features, ",+call-indirect-overlong", "")
|
||||
features = strings.ReplaceAll(features, "+call-indirect-overlong,", "")
|
||||
return features
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package compileopts
|
||||
|
||||
import (
|
||||
"go/build/constraint"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestFinalizerRunnerSchedulerCoverage verifies that each scheduler selects one runner file.
|
||||
// It uses validSchedulerOptions so new schedulers are included.
|
||||
func TestFinalizerRunnerSchedulerCoverage(t *testing.T) {
|
||||
files := []string{
|
||||
"gc_finalizer_sched.go",
|
||||
"gc_finalizer_sched_none.go",
|
||||
"gc_finalizer_sched_other.go",
|
||||
}
|
||||
exprs := make([]constraint.Expr, len(files))
|
||||
for i, name := range files {
|
||||
exprs[i] = readBuildConstraint(t, filepath.Join("..", "src", "runtime", name))
|
||||
}
|
||||
|
||||
for _, sched := range validSchedulerOptions {
|
||||
// The finalizer table exists under block GCs.
|
||||
// gc.conservative satisfies the GC condition in every constraint.
|
||||
tags := map[string]bool{
|
||||
"gc.conservative": true,
|
||||
"scheduler." + sched: true,
|
||||
}
|
||||
var matched []string
|
||||
for i, expr := range exprs {
|
||||
if expr.Eval(func(tag string) bool { return tags[tag] }) {
|
||||
matched = append(matched, files[i])
|
||||
}
|
||||
}
|
||||
if len(matched) != 1 {
|
||||
t.Errorf("scheduler.%s: spawnFinalizerRunner defined in %d files %v, want exactly 1",
|
||||
sched, len(matched), matched)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readBuildConstraint(t *testing.T, path string) constraint.Expr {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if constraint.IsGoBuild(line) {
|
||||
expr, err := constraint.Parse(line)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", path, err)
|
||||
}
|
||||
return expr
|
||||
}
|
||||
if line != "" && !strings.HasPrefix(line, "//") {
|
||||
break // reached code before any //go:build line
|
||||
}
|
||||
}
|
||||
t.Fatalf("%s: no //go:build line found", path)
|
||||
return nil
|
||||
}
|
||||
+16
-8
@@ -3,7 +3,6 @@ package compileopts
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -68,7 +67,7 @@ type Options struct {
|
||||
// Verify performs a validation on the given options, raising an error if options are not valid.
|
||||
func (o *Options) Verify() error {
|
||||
if o.BuildMode != "" {
|
||||
valid := slices.Contains(validBuildModeOptions, o.BuildMode)
|
||||
valid := isInArray(validBuildModeOptions, o.BuildMode)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid buildmode option '%s': valid values are %s`,
|
||||
o.BuildMode,
|
||||
@@ -76,7 +75,7 @@ func (o *Options) Verify() error {
|
||||
}
|
||||
}
|
||||
if o.GC != "" {
|
||||
valid := slices.Contains(validGCOptions, o.GC)
|
||||
valid := isInArray(validGCOptions, o.GC)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid gc option '%s': valid values are %s`,
|
||||
o.GC,
|
||||
@@ -85,7 +84,7 @@ func (o *Options) Verify() error {
|
||||
}
|
||||
|
||||
if o.Scheduler != "" {
|
||||
valid := slices.Contains(validSchedulerOptions, o.Scheduler)
|
||||
valid := isInArray(validSchedulerOptions, o.Scheduler)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid scheduler option '%s': valid values are %s`,
|
||||
o.Scheduler,
|
||||
@@ -94,7 +93,7 @@ func (o *Options) Verify() error {
|
||||
}
|
||||
|
||||
if o.Serial != "" {
|
||||
valid := slices.Contains(validSerialOptions, o.Serial)
|
||||
valid := isInArray(validSerialOptions, o.Serial)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid serial option '%s': valid values are %s`,
|
||||
o.Serial,
|
||||
@@ -103,7 +102,7 @@ func (o *Options) Verify() error {
|
||||
}
|
||||
|
||||
if o.PrintSizes != "" {
|
||||
valid := slices.Contains(validPrintSizeOptions, o.PrintSizes)
|
||||
valid := isInArray(validPrintSizeOptions, o.PrintSizes)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid size option '%s': valid values are %s`,
|
||||
o.PrintSizes,
|
||||
@@ -112,7 +111,7 @@ func (o *Options) Verify() error {
|
||||
}
|
||||
|
||||
if o.PanicStrategy != "" {
|
||||
valid := slices.Contains(validPanicStrategyOptions, o.PanicStrategy)
|
||||
valid := isInArray(validPanicStrategyOptions, o.PanicStrategy)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid panic option '%s': valid values are %s`,
|
||||
o.PanicStrategy,
|
||||
@@ -121,10 +120,19 @@ func (o *Options) Verify() error {
|
||||
}
|
||||
|
||||
if o.Opt != "" {
|
||||
if !slices.Contains(validOptOptions, o.Opt) {
|
||||
if !isInArray(validOptOptions, o.Opt) {
|
||||
return fmt.Errorf("invalid -opt=%s: valid values are %s", o.Opt, strings.Join(validOptOptions, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isInArray(arr []string, item string) bool {
|
||||
for _, i := range arr {
|
||||
if i == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -38,8 +38,7 @@ type TargetSpec struct {
|
||||
Scheduler string `json:"scheduler,omitempty"`
|
||||
Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none)
|
||||
Linker string `json:"linker,omitempty"`
|
||||
LinkerFlavor string `json:"linker-flavor,omitempty"` // how to drive the configured linker (for example: gnu, coff, darwin)
|
||||
RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt)
|
||||
RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt)
|
||||
Libc string `json:"libc,omitempty"`
|
||||
AutoStackSize *bool `json:"automatic-stack-size,omitempty"` // Determine stack size automatically at compile time.
|
||||
DefaultStackSize uint64 `json:"default-stack-size,omitempty"` // Default stack size if the size couldn't be determined at compile time.
|
||||
@@ -475,12 +474,10 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
|
||||
"-m", "i386pep",
|
||||
"--image-base", "0x400000",
|
||||
)
|
||||
spec.RTLib = "compiler-rt"
|
||||
case "arm64":
|
||||
spec.LDFlags = append(spec.LDFlags,
|
||||
"-m", "arm64pe",
|
||||
)
|
||||
spec.RTLib = "compiler-rt"
|
||||
}
|
||||
spec.LDFlags = append(spec.LDFlags,
|
||||
"-Bdynamic",
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package compileopts
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
@@ -56,38 +54,6 @@ func TestLoadTarget_InheritableOnlyTargetStillLoadable(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPY32ConcreteTargets(t *testing.T) {
|
||||
file, err := os.Open("../tools/gen-py32-targets/devices.csv")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
records, err := csv.NewReader(file).ReadAll()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, record := range records[1:] {
|
||||
part, core := record[0], record[2]
|
||||
t.Run(part, func(t *testing.T) {
|
||||
spec, err := LoadTarget(&Options{Target: part})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if spec.LinkerScript == "" {
|
||||
t.Error("concrete target has no linker script")
|
||||
}
|
||||
wantCPU := "cortex-m0plus"
|
||||
if core == "m4" {
|
||||
wantCPU = "cortex-m4"
|
||||
}
|
||||
if spec.CPU != wantCPU {
|
||||
t.Errorf("CPU is %q, want %q", spec.CPU, wantCPU)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverrideProperties(t *testing.T) {
|
||||
baseAutoStackSize := true
|
||||
base := &TargetSpec{
|
||||
@@ -146,52 +112,3 @@ func TestOverrideProperties(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestConfigLinkerFlavor(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
target *TargetSpec
|
||||
goos string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "default gnu",
|
||||
target: &TargetSpec{},
|
||||
goos: "linux",
|
||||
want: "gnu",
|
||||
},
|
||||
{
|
||||
name: "default coff",
|
||||
target: &TargetSpec{},
|
||||
goos: "windows",
|
||||
want: "coff",
|
||||
},
|
||||
{
|
||||
name: "default darwin",
|
||||
target: &TargetSpec{},
|
||||
goos: "darwin",
|
||||
want: "darwin",
|
||||
},
|
||||
{
|
||||
name: "target override",
|
||||
target: &TargetSpec{
|
||||
LinkerFlavor: "coff",
|
||||
},
|
||||
goos: "linux",
|
||||
want: "coff",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tc.target.GOOS = tc.goos
|
||||
config := &Config{
|
||||
Options: &Options{},
|
||||
Target: tc.target,
|
||||
}
|
||||
if got := config.LinkerFlavor(); got != tc.want {
|
||||
t.Fatalf("LinkerFlavor() = %q, want %q", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
//go:build llvm22
|
||||
|
||||
package compileopts
|
||||
|
||||
import "strings"
|
||||
|
||||
// ClangTriple returns the target triple to pass to Clang's --target flag.
|
||||
// LLVM 22 deprecated the "wasm32-unknown-wasi" triple in favor of
|
||||
// "wasm32-unknown-wasip1", so we substitute it here to avoid warnings.
|
||||
func ClangTriple(triple string) string {
|
||||
return strings.Replace(triple, "wasm32-unknown-wasi", "wasm32-unknown-wasip1", 1)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
//go:build !llvm22
|
||||
|
||||
package compileopts
|
||||
|
||||
// ClangTriple returns the target triple to pass to Clang's --target flag.
|
||||
// For pre-LLVM 22, the triple is used as-is.
|
||||
func ClangTriple(triple string) string {
|
||||
return triple
|
||||
}
|
||||
@@ -35,9 +35,6 @@ const (
|
||||
|
||||
// Whether this is a readonly parameter (for example, a string pointer).
|
||||
paramIsReadonly
|
||||
|
||||
// Whether this parameter is passed through backing storage.
|
||||
paramIsIndirect
|
||||
)
|
||||
|
||||
// createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or
|
||||
@@ -105,18 +102,6 @@ func (b *builder) createInvoke(fnType llvm.Type, fn llvm.Value, args []llvm.Valu
|
||||
// Expand an argument type to a list that can be used in a function call
|
||||
// parameter list.
|
||||
func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
|
||||
if c.isIndirectAggregate(t) {
|
||||
return []paramInfo{{
|
||||
llvmType: c.dataPtrType,
|
||||
name: name,
|
||||
elemSize: c.targetData.TypeAllocSize(t),
|
||||
flags: paramIsGoParam | paramIsReadonly | paramIsIndirect,
|
||||
}}
|
||||
}
|
||||
return c.expandDirectFormalParamType(t, name, goType)
|
||||
}
|
||||
|
||||
func (c *compilerContext) expandDirectFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fieldInfos := c.flattenAggregateType(t, name, goType)
|
||||
@@ -130,38 +115,6 @@ func (c *compilerContext) expandDirectFormalParamType(t llvm.Type, name string,
|
||||
return []paramInfo{c.getParamInfo(t, name, goType)}
|
||||
}
|
||||
|
||||
func (c *compilerContext) storedParamType(t llvm.Type, exported bool) llvm.Type {
|
||||
if c.isIndirectParam(t, exported) {
|
||||
return c.dataPtrType
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (c *compilerContext) isIndirectParam(t llvm.Type, exported bool) bool {
|
||||
return !exported && c.isIndirectAggregate(t)
|
||||
}
|
||||
|
||||
func (b *builder) appendStoredValueTypes(valueTypes []llvm.Type, values []ssa.Value, exported bool) []llvm.Type {
|
||||
for _, value := range values {
|
||||
valueTypes = append(valueTypes, b.storedParamType(b.getLLVMType(value.Type()), exported))
|
||||
}
|
||||
return valueTypes
|
||||
}
|
||||
|
||||
func (b *builder) appendStoredParamTypes(valueTypes []llvm.Type, params []*types.Var, exported bool) []llvm.Type {
|
||||
for _, param := range params {
|
||||
valueTypes = append(valueTypes, b.storedParamType(b.getLLVMType(param.Type()), exported))
|
||||
}
|
||||
return valueTypes
|
||||
}
|
||||
|
||||
func (b *builder) prependIndirectResult(sig *types.Signature, exported bool, params []llvm.Value, name string) []llvm.Value {
|
||||
if resultType, indirect := b.hasIndirectResult(sig); !exported && indirect {
|
||||
return append([]llvm.Value{b.createIndirectStorage(resultType, name)}, params...)
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
// expandFormalParamOffsets returns a list of offsets from the start of an
|
||||
// object of type t after it would have been split up by expandFormalParam. This
|
||||
// is useful for debug information, where it is necessary to know the offset
|
||||
|
||||
+38
-22
@@ -30,15 +30,17 @@ func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
|
||||
// actual channel send operation during goroutine lowering.
|
||||
func (b *builder) createChanSend(instr *ssa.Send) {
|
||||
ch := b.getValue(instr.Chan, getPos(instr))
|
||||
chanValue := b.getValue(instr.X, getPos(instr))
|
||||
|
||||
// store value-to-send
|
||||
valueType := b.getLLVMType(instr.X.Type())
|
||||
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
|
||||
var storage valueStorage
|
||||
var valueAlloca, valueAllocaSize llvm.Value
|
||||
if isZeroSize {
|
||||
storage.ptr = llvm.ConstNull(b.dataPtrType)
|
||||
valueAlloca = llvm.ConstNull(b.dataPtrType)
|
||||
} else {
|
||||
storage = b.getValueStorage(instr.X, "chan.value")
|
||||
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
|
||||
b.CreateStore(chanValue, valueAlloca)
|
||||
}
|
||||
|
||||
// Allocate buffer for the channel operation.
|
||||
@@ -46,13 +48,15 @@ func (b *builder) createChanSend(instr *ssa.Send) {
|
||||
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
|
||||
|
||||
// Do the send.
|
||||
b.createRuntimeInvoke("chanSend", []llvm.Value{ch, storage.ptr, channelOpAlloca}, "")
|
||||
b.createRuntimeInvoke("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
|
||||
|
||||
// End the lifetime of the allocas.
|
||||
// This also works around a bug in CoroSplit, at least in LLVM 8:
|
||||
// https://bugs.llvm.org/show_bug.cgi?id=41742
|
||||
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
|
||||
b.endValueStorage(storage)
|
||||
if !isZeroSize {
|
||||
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
|
||||
}
|
||||
}
|
||||
|
||||
// createChanRecv emits a pseudo chan receive operation. It is lowered to the
|
||||
@@ -62,17 +66,37 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
|
||||
ch := b.getValue(unop.X, getPos(unop))
|
||||
|
||||
// Allocate memory to receive into.
|
||||
result := b.createRuntimeValueResult(valueType, unop.CommaOk, true, "chan")
|
||||
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
|
||||
var valueAlloca, valueAllocaSize llvm.Value
|
||||
if isZeroSize {
|
||||
valueAlloca = llvm.ConstNull(b.dataPtrType)
|
||||
} else {
|
||||
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
|
||||
}
|
||||
|
||||
// Allocate buffer for the channel operation.
|
||||
channelOp := b.getLLVMRuntimeType("channelOp")
|
||||
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
|
||||
|
||||
// Do the receive.
|
||||
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, result.valuePtr, channelOpAlloca}, "")
|
||||
received := result.finish(b, commaOk, "chan.received")
|
||||
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
|
||||
var received llvm.Value
|
||||
if isZeroSize {
|
||||
received = llvm.ConstNull(valueType)
|
||||
} else {
|
||||
received = b.CreateLoad(valueType, valueAlloca, "chan.received")
|
||||
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
|
||||
}
|
||||
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
|
||||
return received
|
||||
|
||||
if unop.CommaOk {
|
||||
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
|
||||
tuple = b.CreateInsertValue(tuple, received, 0, "")
|
||||
tuple = b.CreateInsertValue(tuple, commaOk, 1, "")
|
||||
return tuple
|
||||
} else {
|
||||
return received
|
||||
}
|
||||
}
|
||||
|
||||
// createChanClose closes the given channel.
|
||||
@@ -146,7 +170,9 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
|
||||
case types.SendOnly:
|
||||
// Store this value in an alloca and put a pointer to this alloca
|
||||
// in the send state.
|
||||
alloca := b.getSelectSendStorage(state.Send)
|
||||
sendValue := b.getValue(state.Send, state.Pos)
|
||||
alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
|
||||
b.CreateStore(sendValue, alloca)
|
||||
selectState = b.CreateInsertValue(selectState, alloca, 1, "")
|
||||
default:
|
||||
panic("unreachable")
|
||||
@@ -254,17 +280,7 @@ func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
|
||||
// receive can proceed at a time) so we'll get that alloca, bitcast
|
||||
// it to the correct type, and dereference it.
|
||||
recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
|
||||
return b.loadFromStorage(recvbuf, expr.Type(), "select.received")
|
||||
typ := b.getLLVMType(expr.Type())
|
||||
return b.CreateLoad(typ, recvbuf, "")
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) getSelectSendStorage(value ssa.Value) llvm.Value {
|
||||
typ := b.getLLVMType(value.Type())
|
||||
if b.isIndirectAggregate(typ) {
|
||||
return b.getValuePointer(value)
|
||||
}
|
||||
llvmValue := b.getValue(value, getPos(value))
|
||||
ptr := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, "select.send.value")
|
||||
b.CreateStore(llvmValue, ptr)
|
||||
return ptr
|
||||
}
|
||||
|
||||
+76
-330
@@ -90,8 +90,7 @@ type compilerContext struct {
|
||||
astComments map[string]*ast.CommentGroup
|
||||
embedGlobals map[string][]*loader.EmbedFile
|
||||
pkg *types.Package
|
||||
loaderPkg *loader.Package // current package being compiled (for AST access)
|
||||
packageDir string // directory for this package
|
||||
packageDir string // directory for this package
|
||||
runtimePkg *types.Package
|
||||
localTypeNames typeutil.Map // *types.Named (synthetic local from generic instantiation) -> string
|
||||
}
|
||||
@@ -155,8 +154,6 @@ type builder struct {
|
||||
llvmFn llvm.Value
|
||||
info functionInfo
|
||||
locals map[ssa.Value]llvm.Value // local variables
|
||||
indirectValues map[ssa.Value]llvm.Value
|
||||
indirectReturn llvm.Value
|
||||
blockInfo []blockInfo
|
||||
currentBlock *ssa.BasicBlock
|
||||
currentBlockInfo *blockInfo
|
||||
@@ -171,7 +168,7 @@ type builder struct {
|
||||
dilocals map[*types.Var]llvm.Metadata
|
||||
initInlinedAt llvm.Metadata // fake inlinedAt position
|
||||
initPseudoFuncs map[string]llvm.Metadata // fake "inlined" functions for proper init debug locations
|
||||
allDeferFuncs []any
|
||||
allDeferFuncs []interface{}
|
||||
deferFuncs map[*ssa.Function]int
|
||||
deferInvokeFuncs map[string]int
|
||||
deferClosureFuncs map[*ssa.Function]int
|
||||
@@ -195,7 +192,6 @@ func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *bu
|
||||
llvmFn: fn,
|
||||
info: c.getFunctionInfo(f),
|
||||
locals: make(map[ssa.Value]llvm.Value),
|
||||
indirectValues: make(map[ssa.Value]llvm.Value),
|
||||
dilocals: make(map[*types.Var]llvm.Metadata),
|
||||
}
|
||||
}
|
||||
@@ -296,19 +292,18 @@ func Sizes(machine llvm.TargetMachine) types.Sizes {
|
||||
}
|
||||
|
||||
// CompilePackage compiles a single package to a LLVM module.
|
||||
//
|
||||
// The SSA package must already be built. When packages are compiled
|
||||
// concurrently, the entire SSA program must be built before compilation starts.
|
||||
func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package, machine llvm.TargetMachine, config *Config, dumpSSA bool) (llvm.Module, []error) {
|
||||
c := newCompilerContext(moduleName, machine, config, dumpSSA)
|
||||
defer c.dispose()
|
||||
c.packageDir = pkg.OriginalDir()
|
||||
c.embedGlobals = pkg.EmbedGlobals
|
||||
c.pkg = pkg.Pkg
|
||||
c.loaderPkg = pkg
|
||||
c.runtimePkg = ssaPkg.Prog.ImportedPackage("runtime").Pkg
|
||||
c.program = ssaPkg.Prog
|
||||
|
||||
// Convert AST to SSA.
|
||||
ssaPkg.Build()
|
||||
|
||||
// Assign names to function-local named types before compiling the
|
||||
// package, so that types declared in different functions (or in
|
||||
// different instantiations of a generic function) do not collide.
|
||||
@@ -425,19 +420,19 @@ func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type {
|
||||
return c.ctx.Int8Type()
|
||||
case types.Int16, types.Uint16:
|
||||
return c.ctx.Int16Type()
|
||||
case types.Int32, types.Uint32, types.UntypedRune:
|
||||
case types.Int32, types.Uint32:
|
||||
return c.ctx.Int32Type()
|
||||
case types.Int, types.Uint, types.UntypedInt:
|
||||
case types.Int, types.Uint:
|
||||
return c.intType
|
||||
case types.Int64, types.Uint64:
|
||||
return c.ctx.Int64Type()
|
||||
case types.Float32:
|
||||
return c.ctx.FloatType()
|
||||
case types.Float64, types.UntypedFloat:
|
||||
case types.Float64:
|
||||
return c.ctx.DoubleType()
|
||||
case types.Complex64:
|
||||
return c.ctx.StructType([]llvm.Type{c.ctx.FloatType(), c.ctx.FloatType()}, false)
|
||||
case types.Complex128, types.UntypedComplex:
|
||||
case types.Complex128:
|
||||
return c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false)
|
||||
case types.String, types.UntypedString:
|
||||
return c.getLLVMRuntimeType("_string")
|
||||
@@ -1285,28 +1280,10 @@ func (b *builder) createFunctionStart(intrinsic bool) {
|
||||
|
||||
// Load function parameters
|
||||
llvmParamIndex := 0
|
||||
if _, indirectResult := b.hasIndirectResult(b.fn.Signature); indirectResult && !b.info.exported {
|
||||
b.indirectReturn = b.llvmFn.Param(llvmParamIndex)
|
||||
b.indirectReturn.SetName("return")
|
||||
llvmParamIndex++
|
||||
}
|
||||
for _, param := range b.fn.Params {
|
||||
llvmType := b.getLLVMType(param.Type())
|
||||
if b.isIndirectParam(llvmType, b.info.exported) {
|
||||
llvmParam := b.llvmFn.Param(llvmParamIndex)
|
||||
llvmParam.SetName(param.Name())
|
||||
b.indirectValues[param] = llvmParam
|
||||
llvmParamIndex++
|
||||
continue
|
||||
}
|
||||
var paramInfos []paramInfo
|
||||
if b.info.exported {
|
||||
paramInfos = b.expandDirectFormalParamType(llvmType, param.Name(), param.Type())
|
||||
} else {
|
||||
paramInfos = b.expandFormalParamType(llvmType, param.Name(), param.Type())
|
||||
}
|
||||
fields := make([]llvm.Value, 0, 1)
|
||||
for _, info := range paramInfos {
|
||||
for _, info := range b.expandFormalParamType(llvmType, param.Name(), param.Type()) {
|
||||
param := b.llvmFn.Param(llvmParamIndex)
|
||||
param.SetName(info.name)
|
||||
fields = append(fields, param)
|
||||
@@ -1377,6 +1354,15 @@ func (b *builder) createFunctionStart(intrinsic bool) {
|
||||
// diagnostic.
|
||||
func (b *builder) createFunction() {
|
||||
b.createFunctionStart(false)
|
||||
// createFunctionStart returns early (leaving blockInfo unset) when it hits
|
||||
// an error such as a redeclared symbol. Don't plow into the block loop and
|
||||
// panic on an empty blockInfo — the error was already recorded.
|
||||
if b.blockInfo == nil {
|
||||
// TINYGO-DEBUG: surface the redeclaration collision.
|
||||
fmt.Printf("TINYGO-DEBUG skipping fn (redeclared/errored): %s pkg=%s\n",
|
||||
b.fn.RelString(nil), b.fn.Pkg.Pkg.Path())
|
||||
return
|
||||
}
|
||||
|
||||
// Fill blocks with instructions.
|
||||
for _, block := range b.fn.DomPreorder() {
|
||||
@@ -1444,7 +1430,7 @@ func (b *builder) createFunction() {
|
||||
for _, phi := range b.phis {
|
||||
block := phi.ssa.Block()
|
||||
for i, edge := range phi.ssa.Edges {
|
||||
llvmVal := b.getCallArgument(edge, false)
|
||||
llvmVal := b.getValue(edge, getPos(phi.ssa))
|
||||
llvmBlock := b.blockInfo[block.Preds[i].Index].exit
|
||||
phi.llvm.AddIncoming([]llvm.Value{llvmVal}, []llvm.BasicBlock{llvmBlock})
|
||||
}
|
||||
@@ -1453,9 +1439,6 @@ func (b *builder) createFunction() {
|
||||
if b.NeedsStackObjects {
|
||||
// Track phi nodes.
|
||||
for _, phi := range b.phis {
|
||||
if b.isOversizedAggregate(phi.ssa.Type()) {
|
||||
continue
|
||||
}
|
||||
insertPoint := llvm.NextInstruction(phi.llvm)
|
||||
for !insertPoint.IsAPHINode().IsNil() {
|
||||
insertPoint = llvm.NextInstruction(insertPoint)
|
||||
@@ -1547,7 +1530,10 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
|
||||
b.diagnostics = append(b.diagnostics, err)
|
||||
b.locals[instr] = llvm.Undef(b.getLLVMType(instr.Type()))
|
||||
} else {
|
||||
b.setValue(instr, value)
|
||||
b.locals[instr] = value
|
||||
if len(*instr.Referrers()) != 0 && b.NeedsStackObjects {
|
||||
b.trackExpr(instr, value)
|
||||
}
|
||||
}
|
||||
case *ssa.DebugRef:
|
||||
// ignore
|
||||
@@ -1567,8 +1553,10 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
|
||||
b.CreateBr(blockJump)
|
||||
case *ssa.MapUpdate:
|
||||
m := b.getValue(instr.Map, getPos(instr))
|
||||
key := b.getValue(instr.Key, getPos(instr))
|
||||
value := b.getValue(instr.Value, getPos(instr))
|
||||
mapType := instr.Map.Type().Underlying().(*types.Map)
|
||||
b.createMapUpdate(mapType.Key(), m, instr.Key, instr.Value, instr.Pos())
|
||||
b.createMapUpdate(mapType.Key(), m, key, value, instr.Pos())
|
||||
case *ssa.Panic:
|
||||
value := b.getValue(instr.X, getPos(instr))
|
||||
b.createRuntimeInvoke("_panic", []llvm.Value{value}, "")
|
||||
@@ -1577,7 +1565,19 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
|
||||
if b.hasDeferFrame() {
|
||||
b.createRuntimeCall("destroyDeferFrame", []llvm.Value{b.deferFrame}, "")
|
||||
}
|
||||
b.createReturn(instr.Results, getPos(instr))
|
||||
if len(instr.Results) == 0 {
|
||||
b.CreateRetVoid()
|
||||
} else if len(instr.Results) == 1 {
|
||||
b.CreateRet(b.getValue(instr.Results[0], getPos(instr)))
|
||||
} else {
|
||||
// Multiple return values. Put them all in a struct.
|
||||
retVal := llvm.ConstNull(b.llvmFn.GlobalValueType().ReturnType())
|
||||
for i, result := range instr.Results {
|
||||
val := b.getValue(result, getPos(instr))
|
||||
retVal = b.CreateInsertValue(retVal, val, i, "")
|
||||
}
|
||||
b.CreateRet(retVal)
|
||||
}
|
||||
case *ssa.RunDefers:
|
||||
// Note where we're going to put the rundefers block
|
||||
run := b.insertBasicBlock("rundefers.block")
|
||||
@@ -1591,246 +1591,18 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
|
||||
b.createChanSend(instr)
|
||||
case *ssa.Store:
|
||||
llvmAddr := b.getValue(instr.Addr, getPos(instr))
|
||||
llvmVal := b.getValue(instr.Val, getPos(instr))
|
||||
b.createNilCheck(instr.Addr, llvmAddr, "store")
|
||||
llvmType := b.getLLVMType(instr.Val.Type())
|
||||
if b.targetData.TypeAllocSize(llvmType) == 0 {
|
||||
if b.targetData.TypeAllocSize(llvmVal.Type()) == 0 {
|
||||
// nothing to store
|
||||
return
|
||||
}
|
||||
b.storeValue(llvmAddr, instr.Val)
|
||||
b.CreateStore(llvmVal, llvmAddr)
|
||||
default:
|
||||
b.addError(instr.Pos(), "unknown instruction: "+instr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) setValue(value ssa.Value, llvmValue llvm.Value) {
|
||||
if b.isAggregateValue(value.Type()) && !llvmValue.IsNil() && llvmValue.Type().TypeKind() == llvm.PointerTypeKind {
|
||||
b.indirectValues[value] = llvmValue
|
||||
return
|
||||
}
|
||||
b.locals[value] = llvmValue
|
||||
if len(*value.Referrers()) != 0 && b.NeedsStackObjects {
|
||||
b.trackExpr(value, llvmValue)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) createReturn(results []ssa.Value, pos token.Pos) {
|
||||
if len(results) == 0 {
|
||||
b.CreateRetVoid()
|
||||
} else if !b.indirectReturn.IsNil() {
|
||||
if len(results) == 1 {
|
||||
b.storeValue(b.indirectReturn, results[0])
|
||||
} else {
|
||||
returnType := b.getLLVMResultType(b.fn.Signature)
|
||||
for i, result := range results {
|
||||
fieldPtr := b.CreateStructGEP(returnType, b.indirectReturn, i, "")
|
||||
b.storeValue(fieldPtr, result)
|
||||
}
|
||||
}
|
||||
b.CreateRetVoid()
|
||||
} else if len(results) == 1 {
|
||||
b.CreateRet(b.getValue(results[0], pos))
|
||||
} else {
|
||||
result := llvm.ConstNull(b.llvmFn.GlobalValueType().ReturnType())
|
||||
for i, value := range results {
|
||||
result = b.CreateInsertValue(result, b.getValue(value, pos), i, "")
|
||||
}
|
||||
b.CreateRet(result)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) isOversizedAggregate(typ types.Type) bool {
|
||||
if !b.isAggregateValue(typ) {
|
||||
return false
|
||||
}
|
||||
return b.isIndirectAggregate(b.getLLVMType(typ))
|
||||
}
|
||||
|
||||
func (b *builder) isAggregateValue(typ types.Type) bool {
|
||||
if tuple, ok := typ.(*types.Tuple); ok {
|
||||
for i := 0; i < tuple.Len(); i++ {
|
||||
if !isLLVMValueType(tuple.At(i).Type()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch typ.Underlying().(type) {
|
||||
case *types.Array, *types.Struct:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
if !isLLVMValueType(typ) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *builder) getValuePointer(value ssa.Value) llvm.Value {
|
||||
if ptr, ok := b.indirectValues[value]; ok {
|
||||
return ptr
|
||||
}
|
||||
llvmType := b.getLLVMType(value.Type())
|
||||
ptr := b.createIndirectStorage(llvmType, value.Name())
|
||||
b.storeValue(ptr, value)
|
||||
return ptr
|
||||
}
|
||||
|
||||
func (b *builder) getCallArgument(value ssa.Value, exported bool) llvm.Value {
|
||||
paramType := b.getLLVMType(value.Type())
|
||||
if b.isIndirectParam(paramType, exported) {
|
||||
return b.getValuePointer(value)
|
||||
}
|
||||
return b.getValue(value, getPos(value))
|
||||
}
|
||||
|
||||
func (b *builder) createIndirectStorage(typ llvm.Type, name string) llvm.Value {
|
||||
// Use runtime.alloc here so storage that escapes remains valid. The
|
||||
// allocation optimizer moves bounded non-escaping storage to the stack.
|
||||
size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(typ), false)
|
||||
layout := b.createObjectLayout(typ, b.fn.Pos())
|
||||
ptr := b.createAlloc(size, layout, b.targetData.ABITypeAlignment(typ), name)
|
||||
if b.NeedsStackObjects {
|
||||
b.trackPointer(ptr)
|
||||
}
|
||||
return ptr
|
||||
}
|
||||
|
||||
func (b *builder) copyIndirectAggregate(dst, src llvm.Value, typ llvm.Type) {
|
||||
size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(typ), false)
|
||||
b.createMemCopy("memcpy", dst, src, size)
|
||||
}
|
||||
|
||||
func (b *builder) storeValue(dst llvm.Value, value ssa.Value) {
|
||||
typ := b.getLLVMType(value.Type())
|
||||
if src, ok := b.indirectValues[value]; ok {
|
||||
b.copyIndirectAggregate(dst, src, typ)
|
||||
} else {
|
||||
b.CreateStore(b.getValue(value, getPos(value)), dst)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) copyToIndirectStorage(src llvm.Value, typ llvm.Type, name string) llvm.Value {
|
||||
dst := b.createIndirectStorage(typ, name)
|
||||
b.copyIndirectAggregate(dst, src, typ)
|
||||
return dst
|
||||
}
|
||||
|
||||
func (b *builder) loadFromStorage(ptr llvm.Value, typ types.Type, name string) llvm.Value {
|
||||
llvmType := b.getLLVMType(typ)
|
||||
if b.isIndirectAggregate(llvmType) {
|
||||
return b.copyToIndirectStorage(ptr, llvmType, name)
|
||||
}
|
||||
return b.CreateLoad(llvmType, ptr, name)
|
||||
}
|
||||
|
||||
func (b *builder) getValueField(value ssa.Value, index int, resultType types.Type, name string) (llvm.Value, bool) {
|
||||
if !b.isAggregateValue(value.Type()) {
|
||||
return llvm.Value{}, false
|
||||
}
|
||||
valueType := b.getLLVMType(value.Type())
|
||||
if _, indirect := b.indirectValues[value]; !indirect && !b.isIndirectAggregate(valueType) {
|
||||
return llvm.Value{}, false
|
||||
}
|
||||
fieldPtr := b.CreateStructGEP(valueType, b.getValuePointer(value), index, "")
|
||||
return b.loadFromStorage(fieldPtr, resultType, name), true
|
||||
}
|
||||
|
||||
func (b *builder) zeroIndirectStorage(ptr llvm.Value, typ llvm.Type) {
|
||||
memset := b.getMemsetFunc()
|
||||
b.createCall(memset.GlobalValueType(), memset, []llvm.Value{
|
||||
ptr,
|
||||
llvm.ConstInt(b.ctx.Int8Type(), 0, false),
|
||||
llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(typ), false),
|
||||
llvm.ConstInt(b.ctx.Int1Type(), 0, false),
|
||||
}, "")
|
||||
}
|
||||
|
||||
type valueStorage struct {
|
||||
ptr, size llvm.Value
|
||||
temporary bool
|
||||
}
|
||||
|
||||
func (b *builder) getValueStorage(value ssa.Value, name string) valueStorage {
|
||||
typ := b.getLLVMType(value.Type())
|
||||
if b.isIndirectAggregate(typ) {
|
||||
return valueStorage{ptr: b.getValuePointer(value)}
|
||||
}
|
||||
ptr, size := b.createTemporaryAlloca(typ, name)
|
||||
b.storeValue(ptr, value)
|
||||
return valueStorage{ptr: ptr, size: size, temporary: true}
|
||||
}
|
||||
|
||||
func (b *builder) endValueStorage(storage valueStorage) {
|
||||
if storage.temporary {
|
||||
b.emitLifetimeEnd(storage.ptr, storage.size)
|
||||
}
|
||||
}
|
||||
|
||||
type runtimeValueResult struct {
|
||||
valueType llvm.Type
|
||||
resultType llvm.Type
|
||||
result llvm.Value
|
||||
valuePtr llvm.Value
|
||||
valueSize llvm.Value
|
||||
temporary bool
|
||||
zero bool
|
||||
commaOk bool
|
||||
}
|
||||
|
||||
func (b *builder) createRuntimeValueResult(valueType llvm.Type, commaOk, zeroAsNull bool, name string) runtimeValueResult {
|
||||
result := runtimeValueResult{
|
||||
valueType: valueType,
|
||||
resultType: valueType,
|
||||
valueSize: llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(valueType), false),
|
||||
commaOk: commaOk,
|
||||
}
|
||||
if commaOk {
|
||||
result.resultType = b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false)
|
||||
}
|
||||
if b.isIndirectAggregate(result.resultType) {
|
||||
result.result = b.createIndirectStorage(result.resultType, name+".result")
|
||||
result.valuePtr = result.result
|
||||
if commaOk {
|
||||
result.valuePtr = b.CreateStructGEP(result.resultType, result.result, 0, "")
|
||||
}
|
||||
return result
|
||||
}
|
||||
if zeroAsNull && b.targetData.TypeAllocSize(valueType) == 0 {
|
||||
result.valuePtr = llvm.ConstNull(b.dataPtrType)
|
||||
result.zero = true
|
||||
return result
|
||||
}
|
||||
result.valuePtr, result.valueSize = b.createTemporaryAlloca(valueType, name+".value")
|
||||
result.temporary = true
|
||||
return result
|
||||
}
|
||||
|
||||
func (r runtimeValueResult) finish(b *builder, commaOk llvm.Value, name string) llvm.Value {
|
||||
if !r.result.IsNil() {
|
||||
if r.commaOk {
|
||||
b.CreateStore(commaOk, b.CreateStructGEP(r.resultType, r.result, 1, ""))
|
||||
}
|
||||
return r.result
|
||||
}
|
||||
|
||||
var value llvm.Value
|
||||
if r.zero {
|
||||
value = llvm.ConstNull(r.valueType)
|
||||
} else {
|
||||
value = b.CreateLoad(r.valueType, r.valuePtr, name)
|
||||
}
|
||||
if r.temporary {
|
||||
b.emitLifetimeEnd(r.valuePtr, r.valueSize)
|
||||
}
|
||||
if !r.commaOk {
|
||||
return value
|
||||
}
|
||||
result := llvm.Undef(r.resultType)
|
||||
result = b.CreateInsertValue(result, value, 0, "")
|
||||
return b.CreateInsertValue(result, commaOk, 1, "")
|
||||
}
|
||||
|
||||
// createBuiltin lowers a builtin Go function (append, close, delete, etc.) to
|
||||
// LLVM IR. It uses runtime calls for some builtins.
|
||||
func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, callName string, pos token.Pos) (llvm.Value, error) {
|
||||
@@ -2216,7 +1988,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
|
||||
if fn := instr.StaticCallee(); fn != nil {
|
||||
// Direct function call, either to a named or anonymous (directly
|
||||
// applied) function call. If it is anonymous, it may be a closure.
|
||||
name := b.getFunctionInfo(fn).linkName
|
||||
name := fn.RelString(nil)
|
||||
switch {
|
||||
case name == "device.Asm" || name == "device/arm.Asm" || name == "device/arm64.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
|
||||
return b.createInlineAsm(instr.Args)
|
||||
@@ -2265,10 +2037,14 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
|
||||
}
|
||||
}
|
||||
|
||||
var params []llvm.Value
|
||||
for _, param := range instr.Args {
|
||||
params = append(params, b.getValue(param, getPos(instr)))
|
||||
}
|
||||
|
||||
// Try to call the function directly for trivially static calls.
|
||||
var callee, context llvm.Value
|
||||
var calleeType llvm.Type
|
||||
var invokeTypecode, invokeReceiver llvm.Value
|
||||
exported := false
|
||||
if fn := instr.StaticCallee(); fn != nil {
|
||||
calleeType, callee = b.getFunction(fn)
|
||||
@@ -2298,18 +2074,19 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
|
||||
exported = info.exported
|
||||
} else if call, ok := instr.Value.(*ssa.Builtin); ok {
|
||||
// Builtin function (append, close, delete, etc.).)
|
||||
var params []llvm.Value
|
||||
var argTypes []types.Type
|
||||
for _, arg := range instr.Args {
|
||||
argTypes = append(argTypes, arg.Type())
|
||||
params = append(params, b.getValue(arg, getPos(instr)))
|
||||
}
|
||||
return b.createBuiltin(argTypes, params, call.Name(), instr.Pos())
|
||||
} else if instr.IsInvoke() {
|
||||
// Interface method call (aka invoke call).
|
||||
itf := b.getValue(instr.Value, getPos(instr)) // interface value (runtime._interface)
|
||||
invokeTypecode = b.CreateExtractValue(itf, 0, "invoke.func.typecode")
|
||||
invokeReceiver = b.CreateExtractValue(itf, 1, "invoke.func.value")
|
||||
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
|
||||
value := b.CreateExtractValue(itf, 1, "invoke.func.value") // receiver
|
||||
// Prefix the params with receiver value and suffix with typecode.
|
||||
params = append([]llvm.Value{value}, params...)
|
||||
params = append(params, typecode)
|
||||
callee = b.getInvokeFunction(instr)
|
||||
calleeType = callee.GlobalValueType()
|
||||
context = llvm.Undef(b.dataPtrType)
|
||||
@@ -2323,23 +2100,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
|
||||
b.createNilCheck(instr.Value, callee, "fpcall")
|
||||
}
|
||||
|
||||
var params []llvm.Value
|
||||
for _, param := range instr.Args {
|
||||
params = append(params, b.getCallArgument(param, exported))
|
||||
}
|
||||
if instr.IsInvoke() {
|
||||
params = append([]llvm.Value{invokeReceiver}, params...)
|
||||
params = append(params, invokeTypecode)
|
||||
}
|
||||
|
||||
if !exported {
|
||||
if resultType, indirectResult := b.hasIndirectResult(instr.Signature()); indirectResult {
|
||||
result := b.createIndirectStorage(resultType, "call.result")
|
||||
params = append([]llvm.Value{result}, params...)
|
||||
params = append(params, context)
|
||||
b.createInvoke(calleeType, callee, params, "")
|
||||
return result, nil
|
||||
}
|
||||
// This function takes a context parameter.
|
||||
// Add it to the end of the parameter list.
|
||||
params = append(params, context)
|
||||
@@ -2378,9 +2139,6 @@ func (b *builder) getValue(expr ssa.Value, pos token.Pos) llvm.Value {
|
||||
return value
|
||||
default:
|
||||
// other (local) SSA value
|
||||
if value, ok := b.indirectValues[expr]; ok {
|
||||
return b.CreateLoad(b.getLLVMType(expr.Type()), value, "")
|
||||
}
|
||||
if value, ok := b.locals[expr]; ok {
|
||||
return value
|
||||
} else {
|
||||
@@ -2405,10 +2163,13 @@ func (c *compilerContext) maxSliceSize(elementType llvm.Type) uint64 {
|
||||
if elementSize == 0 {
|
||||
elementSize = 1
|
||||
}
|
||||
maxSize := min(
|
||||
// len(slice) is an int. Make sure the length remains small enough to fit in
|
||||
// an int.
|
||||
maxPointerValue/elementSize, maxIntegerValue)
|
||||
maxSize := maxPointerValue / elementSize
|
||||
|
||||
// len(slice) is an int. Make sure the length remains small enough to fit in
|
||||
// an int.
|
||||
if maxSize > maxIntegerValue {
|
||||
maxSize = maxIntegerValue
|
||||
}
|
||||
|
||||
return maxSize
|
||||
}
|
||||
@@ -2463,15 +2224,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
|
||||
// This instruction changes the type, but the underlying value remains
|
||||
// the same. This is often a no-op, but sometimes we have to change the
|
||||
// LLVM type as well.
|
||||
llvmType := b.getLLVMType(expr.Type())
|
||||
if b.isIndirectAggregate(llvmType) {
|
||||
sourceType := b.getLLVMType(expr.X.Type())
|
||||
if !b.isIndirectAggregate(sourceType) || b.targetData.TypeAllocSize(sourceType) != b.targetData.TypeAllocSize(llvmType) {
|
||||
return llvm.Value{}, errors.New("todo: indirect aggregate ChangeType with different layout")
|
||||
}
|
||||
return b.getValuePointer(expr.X), nil
|
||||
}
|
||||
x := b.getValue(expr.X, getPos(expr))
|
||||
llvmType := b.getLLVMType(expr.Type())
|
||||
if x.Type() == llvmType {
|
||||
// Different Go type but same LLVM type (for example, named int).
|
||||
// This is the common case.
|
||||
@@ -2501,15 +2255,9 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
|
||||
if _, ok := expr.Tuple.(*ssa.Select); ok {
|
||||
return b.getChanSelectResult(expr), nil
|
||||
}
|
||||
if value, ok := b.getValueField(expr.Tuple, expr.Index, expr.Type(), expr.Name()); ok {
|
||||
return value, nil
|
||||
}
|
||||
value := b.getValue(expr.Tuple, getPos(expr))
|
||||
return b.CreateExtractValue(value, expr.Index, ""), nil
|
||||
case *ssa.Field:
|
||||
if value, ok := b.getValueField(expr.X, expr.Field, expr.Type(), expr.Name()); ok {
|
||||
return value, nil
|
||||
}
|
||||
value := b.getValue(expr.X, getPos(expr))
|
||||
result := b.CreateExtractValue(value, expr.Field, "")
|
||||
return result, nil
|
||||
@@ -2532,11 +2280,11 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
|
||||
case *ssa.Global:
|
||||
panic("global is not an expression")
|
||||
case *ssa.Index:
|
||||
collection := b.getValue(expr.X, getPos(expr))
|
||||
index := b.getValue(expr.Index, getPos(expr))
|
||||
|
||||
switch xType := expr.X.Type().Underlying().(type) {
|
||||
case *types.Basic: // extract byte from string
|
||||
collection := b.getValue(expr.X, getPos(expr))
|
||||
// Value type must be a string, which is a basic type.
|
||||
if xType.Info()&types.IsString == 0 {
|
||||
panic("lookup on non-string?")
|
||||
@@ -2570,12 +2318,13 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
|
||||
|
||||
// Can't load directly from array (as index is non-constant), so
|
||||
// have to do it using an alloca+gep+load.
|
||||
arrayType := b.getLLVMType(expr.X.Type())
|
||||
storage := b.getValueStorage(expr.X, "index.alloca")
|
||||
arrayType := collection.Type()
|
||||
alloca, allocaSize := b.createTemporaryAlloca(arrayType, "index.alloca")
|
||||
b.CreateStore(collection, alloca)
|
||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
||||
ptr := b.CreateInBoundsGEP(arrayType, storage.ptr, []llvm.Value{zero, index}, "index.gep")
|
||||
result := b.loadFromStorage(ptr, expr.Type(), "index.load")
|
||||
b.endValueStorage(storage)
|
||||
ptr := b.CreateInBoundsGEP(arrayType, alloca, []llvm.Value{zero, index}, "index.gep")
|
||||
result := b.CreateLoad(arrayType.ElementType(), ptr, "index.load")
|
||||
b.emitLifetimeEnd(alloca, allocaSize)
|
||||
return result, nil
|
||||
default:
|
||||
panic("unknown *ssa.Index type")
|
||||
@@ -2634,21 +2383,17 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
|
||||
}
|
||||
case *ssa.Lookup: // map lookup
|
||||
value := b.getValue(expr.X, getPos(expr))
|
||||
index := b.getValue(expr.Index, getPos(expr))
|
||||
valueType := expr.Type()
|
||||
if expr.CommaOk {
|
||||
valueType = valueType.(*types.Tuple).At(0).Type()
|
||||
}
|
||||
return b.createMapLookup(expr.X.Type().Underlying().(*types.Map).Key(), valueType, value, expr.Index, expr.CommaOk, expr.Pos())
|
||||
return b.createMapLookup(expr.X.Type().Underlying().(*types.Map).Key(), valueType, value, index, expr.CommaOk, expr.Pos())
|
||||
case *ssa.MakeChan:
|
||||
return b.createMakeChan(expr), nil
|
||||
case *ssa.MakeClosure:
|
||||
return b.parseMakeClosure(expr)
|
||||
case *ssa.MakeInterface:
|
||||
if b.isOversizedAggregate(expr.X.Type()) {
|
||||
typ := b.getLLVMType(expr.X.Type())
|
||||
ptr := b.copyToIndirectStorage(b.getValuePointer(expr.X), typ, "interface.value")
|
||||
return b.createMakeInterfaceFromPointer(ptr, expr.X.Type()), nil
|
||||
}
|
||||
val := b.getValue(expr.X, getPos(expr))
|
||||
return b.createMakeInterface(val, expr.X.Type(), expr.Pos()), nil
|
||||
case *ssa.MakeMap:
|
||||
@@ -2682,7 +2427,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
|
||||
}
|
||||
sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap")
|
||||
layoutValue := b.createObjectLayout(llvmElemType, expr.Pos())
|
||||
slicePtr := b.createAlloc(sliceSize, layoutValue, elemAlign, "makeslice.buf")
|
||||
slicePtr := b.createAlloc(sliceSize, layoutValue, 0, "makeslice.buf")
|
||||
slicePtr.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elemAlign)))
|
||||
|
||||
// Extend or truncate if necessary. This is safe as we've already done
|
||||
// the bounds check.
|
||||
@@ -2715,8 +2461,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
|
||||
return b.createMapIteratorNext(rangeVal, llvmRangeVal, it), nil
|
||||
}
|
||||
case *ssa.Phi:
|
||||
phiType := b.storedParamType(b.getLLVMType(expr.Type()), false)
|
||||
phi := b.CreatePHI(phiType, "")
|
||||
phi := b.CreatePHI(b.getLLVMType(expr.Type()), "")
|
||||
b.phis = append(b.phis, phiNode{expr, phi})
|
||||
return phi, nil
|
||||
case *ssa.Range:
|
||||
@@ -3719,7 +3464,8 @@ func (b *builder) createUnOp(unop *ssa.UnOp) (llvm.Value, error) {
|
||||
return fn, nil
|
||||
} else {
|
||||
b.createNilCheck(unop.X, x, "deref")
|
||||
return b.loadFromStorage(x, unop.Type(), ""), nil
|
||||
load := b.CreateLoad(valueType, x, "")
|
||||
return load, nil
|
||||
}
|
||||
case token.XOR: // ^x, toggle all bits in integer
|
||||
return b.CreateXor(x, llvm.ConstInt(x.Type(), ^uint64(0), false), ""), nil
|
||||
|
||||
+20
-177
@@ -4,7 +4,6 @@ import (
|
||||
"flag"
|
||||
"go/types"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -52,7 +51,6 @@ func TestCompiler(t *testing.T) {
|
||||
{"gc.go", "", ""},
|
||||
{"zeromap.go", "", ""},
|
||||
{"generics.go", "", ""},
|
||||
{"large.go", "", ""},
|
||||
}
|
||||
if goMinor >= 20 {
|
||||
tests = append(tests, testCase{"go1.20.go", "", ""})
|
||||
@@ -60,9 +58,6 @@ func TestCompiler(t *testing.T) {
|
||||
if goMinor >= 21 {
|
||||
tests = append(tests, testCase{"go1.21.go", "", ""})
|
||||
}
|
||||
if goMinor >= 27 {
|
||||
tests = append(tests, testCase{"go1.27.go", "", ""})
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
name := tc.file
|
||||
@@ -115,7 +110,7 @@ func TestCompiler(t *testing.T) {
|
||||
|
||||
// Update test if needed. Do not check the result.
|
||||
if *flagUpdate {
|
||||
err := os.WriteFile(outPath, []byte(normalizeIR(mod.String())), 0666)
|
||||
err := os.WriteFile(outPath, []byte(mod.String()), 0666)
|
||||
if err != nil {
|
||||
t.Error("failed to write updated output file:", err)
|
||||
}
|
||||
@@ -127,137 +122,30 @@ func TestCompiler(t *testing.T) {
|
||||
t.Fatal("failed to read golden file:", err)
|
||||
}
|
||||
|
||||
if diff := diffIR(string(expected), mod.String()); diff != "" {
|
||||
t.Errorf("output does not match expected output (re-run with -update to regenerate):\n%s", diff)
|
||||
if !fuzzyEqualIR(mod.String(), string(expected)) {
|
||||
t.Errorf("output does not match expected output:\n%s", mod.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptimizedLargeAggregateABI(t *testing.T) {
|
||||
options := &compileopts.Options{Target: "wasm"}
|
||||
mod, errs := testCompilePackage(t, options, "large-optimized.go")
|
||||
if len(errs) != 0 {
|
||||
for _, err := range errs {
|
||||
t.Error(err)
|
||||
}
|
||||
return
|
||||
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
|
||||
// equal. That means, only relevant lines are compared (excluding comments
|
||||
// etc.).
|
||||
func fuzzyEqualIR(s1, s2 string) bool {
|
||||
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
|
||||
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
|
||||
if len(lines1) != len(lines2) {
|
||||
return false
|
||||
}
|
||||
defer mod.Dispose()
|
||||
|
||||
passOptions := llvm.NewPassBuilderOptions()
|
||||
defer passOptions.Dispose()
|
||||
if err := mod.RunPasses("default<O2>", llvm.TargetMachine{}, passOptions); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resultFn := mod.NamedFunction("main.makeLargeOptimizedValue")
|
||||
if resultFn.IsNil() {
|
||||
t.Fatal("missing function main.makeLargeOptimizedValue")
|
||||
}
|
||||
if resultType := resultFn.GlobalValueType().ReturnType(); resultType.TypeKind() != llvm.VoidTypeKind {
|
||||
t.Errorf("large aggregate result was promoted to %s", resultType)
|
||||
}
|
||||
|
||||
for _, name := range []string{
|
||||
"main.makeLargeOptimizedValue",
|
||||
"main.readLargeOptimizedValue",
|
||||
"main.readMixedLargeOptimizedValue",
|
||||
} {
|
||||
fn := mod.NamedFunction(name)
|
||||
if fn.IsNil() {
|
||||
t.Fatalf("missing function %s", name)
|
||||
}
|
||||
if paramType := fn.GlobalValueType().ParamTypes()[0]; paramType.TypeKind() != llvm.PointerTypeKind {
|
||||
t.Errorf("%s aggregate parameter was promoted to %s", name, paramType)
|
||||
for i, line1 := range lines1 {
|
||||
line2 := lines2[i]
|
||||
if line1 != line2 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// normalizeIR canonicalizes LLVM-version-specific IR spellings for comparison
|
||||
// and when regenerating golden files.
|
||||
func normalizeIR(s string) string {
|
||||
// Golden files are written using the pre-LLVM21 'nocapture' spelling,
|
||||
// which LLVM printed before any co-occurring attribute such as
|
||||
// 'readonly' (e.g. "ptr nocapture readonly"). LLVM 21+ prints the
|
||||
// equivalent 'captures(none)' instead, and after such attributes (e.g.
|
||||
// "ptr readonly captures(none)"). Normalize both name and position back
|
||||
// to the old spelling.
|
||||
s = normalizeCapturesAttr(s)
|
||||
|
||||
// LLVM 21+ also added an explicit 'nocreateundeforpoison' attribute to
|
||||
// certain intrinsic declarations (e.g. llvm.umin) that were implicitly
|
||||
// assumed not to create undef/poison before. It's unrelated to the
|
||||
// behavior under test, so ignore it for comparison.
|
||||
s = strings.ReplaceAll(s, "nocreateundeforpoison ", "")
|
||||
|
||||
// LLVM 22 dropped the (redundant) i64 size argument from
|
||||
// llvm.lifetime.start/end. Normalize away that argument so golden files
|
||||
// written against the two-argument form still match.
|
||||
s = lifetimeSizeArgRe.ReplaceAllString(s, "$1")
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// diffIR compares two LLVM IR strings, ignoring irrelevant lines (comments,
|
||||
// empty lines, etc.) and normalizing LLVM-version-specific spellings via
|
||||
// normalizeIR. It returns "" when they are equal. Otherwise it returns a
|
||||
// compact diff of only the region that differs: the common prefix and suffix
|
||||
// are trimmed, then the differing expected lines (prefixed "-") are shown
|
||||
// followed by the differing actual lines (prefixed "+").
|
||||
func diffIR(expected, actual string) string {
|
||||
exp := filterIrrelevantIRLines(strings.Split(normalizeIR(expected), "\n"))
|
||||
act := filterIrrelevantIRLines(strings.Split(normalizeIR(actual), "\n"))
|
||||
|
||||
// Trim the common prefix.
|
||||
start := 0
|
||||
for start < len(exp) && start < len(act) && exp[start] == act[start] {
|
||||
start++
|
||||
}
|
||||
// Trim the common suffix.
|
||||
e, a := len(exp), len(act)
|
||||
for e > start && a > start && exp[e-1] == act[a-1] {
|
||||
e--
|
||||
a--
|
||||
}
|
||||
if start == e && start == a {
|
||||
return "" // equal
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString("first difference at relevant line ")
|
||||
b.WriteString(strconv.Itoa(start + 1))
|
||||
b.WriteString(":\n")
|
||||
for _, line := range exp[start:e] {
|
||||
b.WriteString("- ")
|
||||
b.WriteString(line)
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
for _, line := range act[start:a] {
|
||||
b.WriteString("+ ")
|
||||
b.WriteString(line)
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// capturesNoneAttrRe matches a co-occurring attribute directly followed by
|
||||
// 'captures(none)', which is how LLVM 21+ orders these two attributes when
|
||||
// printing IR (the pre-LLVM21 'nocapture' attribute printed the other way
|
||||
// around).
|
||||
var capturesNoneAttrRe = regexp.MustCompile(`\b(readonly|readnone|writeonly|nonnull)\s+captures\(none\)`)
|
||||
|
||||
// lifetimeSizeArgRe matches the i64 size argument of an
|
||||
// llvm.lifetime.start/end call or declaration, which LLVM 22 removed.
|
||||
var lifetimeSizeArgRe = regexp.MustCompile(`(@llvm\.lifetime\.(?:start|end)\.p0\()i64(?: immarg| \d+), `)
|
||||
|
||||
// normalizeCapturesAttr rewrites LLVM 21+'s 'captures(none)' attribute back
|
||||
// to the pre-LLVM21 'nocapture' spelling and position, so golden IR files
|
||||
// written against LLVM <21 keep matching.
|
||||
func normalizeCapturesAttr(s string) string {
|
||||
s = capturesNoneAttrRe.ReplaceAllString(s, "nocapture $1")
|
||||
s = strings.ReplaceAll(s, "captures(none)", "nocapture")
|
||||
return s
|
||||
return true
|
||||
}
|
||||
|
||||
// filterIrrelevantIRLines removes lines from the input slice of strings that
|
||||
@@ -300,9 +188,9 @@ func TestCompilerErrors(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
errorsFileString := strings.ReplaceAll(string(errorsFile), "\r\n", "\n")
|
||||
for line := range strings.SplitSeq(errorsFileString, "\n") {
|
||||
if after, ok := strings.CutPrefix(line, "// ERROR: "); ok {
|
||||
expectedErrors = append(expectedErrors, after)
|
||||
for _, line := range strings.Split(errorsFileString, "\n") {
|
||||
if strings.HasPrefix(line, "// ERROR: ") {
|
||||
expectedErrors = append(expectedErrors, strings.TrimPrefix(line, "// ERROR: "))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,49 +214,6 @@ func TestCompilerErrors(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateValueCount(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := llvm.NewContext()
|
||||
defer ctx.Dispose()
|
||||
|
||||
byteType := ctx.Int8Type()
|
||||
tests := []struct {
|
||||
name string
|
||||
typ llvm.Type
|
||||
count uint64
|
||||
exceeded bool
|
||||
}{
|
||||
{"empty", llvm.ArrayType(byteType, 0), 0, false},
|
||||
{"limit", llvm.ArrayType(byteType, 1024), 1024, false},
|
||||
{"over limit", llvm.ArrayType(byteType, 1025), 0, true},
|
||||
{"combined limit", ctx.StructType([]llvm.Type{
|
||||
llvm.ArrayType(byteType, 512),
|
||||
llvm.ArrayType(byteType, 512),
|
||||
}, false), 1024, false},
|
||||
{"combined over limit", ctx.StructType([]llvm.Type{
|
||||
llvm.ArrayType(byteType, 1000),
|
||||
llvm.ArrayType(byteType, 1000),
|
||||
}, false), 0, true},
|
||||
{"comma-ok over limit", ctx.StructType([]llvm.Type{
|
||||
llvm.ArrayType(byteType, 1024),
|
||||
ctx.Int1Type(),
|
||||
}, false), 0, true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
count, exceeded := aggregateValueCount(test.typ, 0)
|
||||
if exceeded != test.exceeded {
|
||||
t.Errorf("expected exceeded=%t, got %t", test.exceeded, exceeded)
|
||||
}
|
||||
if !exceeded && count != test.count {
|
||||
t.Errorf("expected count=%d, got %d", test.count, count)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Build a package given a number of compiler options and a file.
|
||||
func testCompilePackage(t *testing.T, options *compileopts.Options, file string) (llvm.Module, []error) {
|
||||
target, err := compileopts.LoadTarget(options)
|
||||
@@ -413,7 +258,5 @@ func testCompilePackage(t *testing.T, options *compileopts.Options, file string)
|
||||
// Compile AST to IR.
|
||||
program := lprogram.LoadSSA()
|
||||
pkg := lprogram.MainPkg()
|
||||
ssaPkg := program.Package(pkg.Pkg)
|
||||
ssaPkg.Build()
|
||||
return CompilePackage(file, pkg, ssaPkg, machine, compilerConfig, false)
|
||||
return CompilePackage(file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
|
||||
}
|
||||
|
||||
+90
-97
@@ -32,7 +32,7 @@ func (b *builder) supportsRecover() bool {
|
||||
// proposal of WebAssembly:
|
||||
// https://github.com/WebAssembly/exception-handling
|
||||
return false
|
||||
case "xtensa":
|
||||
case "riscv64", "xtensa":
|
||||
// TODO: add support for these architectures
|
||||
return false
|
||||
default:
|
||||
@@ -116,8 +116,8 @@ func (b *builder) createLandingPad() {
|
||||
func (b *builder) createCheckpoint(ptr llvm.Value) llvm.Value {
|
||||
// Construct inline assembly equivalents of setjmp.
|
||||
// The assembly works as follows:
|
||||
// * Registers are either clobbered or, on 386, saved for longjmp to
|
||||
// restore if the ABI requires them to survive calls.
|
||||
// * All registers (both callee-saved and caller saved) are clobbered
|
||||
// after the inline assembly returns.
|
||||
// * The assembly stores the address just past the end of the assembly
|
||||
// into the jump buffer.
|
||||
// * The return value (eax, rax, r0, etc) is set to zero in the inline
|
||||
@@ -130,12 +130,8 @@ func (b *builder) createCheckpoint(ptr llvm.Value) llvm.Value {
|
||||
asmString = `
|
||||
xorl %eax, %eax
|
||||
movl $$1f, 4(%ebx)
|
||||
movl %ebx, 8(%ebx)
|
||||
movl %esi, 12(%ebx)
|
||||
movl %edi, 16(%ebx)
|
||||
movl %ebp, 20(%ebx)
|
||||
1:`
|
||||
constraints = "={eax},{ebx},~{ecx},~{edx},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
|
||||
constraints = "={eax},{ebx},~{ebx},~{ecx},~{edx},~{esi},~{edi},~{ebp},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
|
||||
// This doesn't include the floating point stack because TinyGo uses
|
||||
// newer floating point instructions.
|
||||
case "x86_64":
|
||||
@@ -217,20 +213,12 @@ sw $$ra, 4($$5)
|
||||
// So only add them when using hardfloat.
|
||||
constraints += ",~{$f0},~{$f1},~{$f2},~{$f3},~{$f4},~{$f5},~{$f6},~{$f7},~{$f8},~{$f9},~{$f10},~{$f11},~{$f12},~{$f13},~{$f14},~{$f15},~{$f16},~{$f17},~{$f18},~{$f19},~{$f20},~{$f21},~{$f22},~{$f23},~{$f24},~{$f25},~{$f26},~{$f27},~{$f28},~{$f29},~{$f30},~{$f31}"
|
||||
}
|
||||
case "riscv32", "riscv64":
|
||||
if b.archFamily() == "riscv32" {
|
||||
asmString = `
|
||||
case "riscv32":
|
||||
asmString = `
|
||||
la a2, 1f
|
||||
sw a2, 4(a1)
|
||||
li a0, 0
|
||||
1:`
|
||||
} else {
|
||||
asmString = `
|
||||
la a2, 1f
|
||||
sd a2, 8(a1)
|
||||
li a0, 0
|
||||
1:`
|
||||
}
|
||||
constraints = "={a0},{a1},~{a1},~{a2},~{a3},~{a4},~{a5},~{a6},~{a7},~{s0},~{s1},~{s2},~{s3},~{s4},~{s5},~{s6},~{s7},~{s8},~{s9},~{s10},~{s11},~{t0},~{t1},~{t2},~{t3},~{t4},~{t5},~{t6},~{ra},~{f0},~{f1},~{f2},~{f3},~{f4},~{f5},~{f6},~{f7},~{f8},~{f9},~{f10},~{f11},~{f12},~{f13},~{f14},~{f15},~{f16},~{f17},~{f18},~{f19},~{f20},~{f21},~{f22},~{f23},~{f24},~{f25},~{f26},~{f27},~{f28},~{f29},~{f30},~{f31},~{memory}"
|
||||
default:
|
||||
// This case should have been handled by b.supportsRecover().
|
||||
@@ -367,44 +355,6 @@ type tarjanNode struct {
|
||||
cyclic bool
|
||||
}
|
||||
|
||||
type llvmValueList struct {
|
||||
values []llvm.Value
|
||||
types []llvm.Type
|
||||
}
|
||||
|
||||
func newLLVMValueList(values ...llvm.Value) llvmValueList {
|
||||
var list llvmValueList
|
||||
list.append(values...)
|
||||
return list
|
||||
}
|
||||
|
||||
func (l *llvmValueList) append(values ...llvm.Value) {
|
||||
for _, value := range values {
|
||||
l.values = append(l.values, value)
|
||||
l.types = append(l.types, value.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func (l *llvmValueList) appendSSAValues(values []ssa.Value, lower func(ssa.Value) llvm.Value) {
|
||||
for _, value := range values {
|
||||
l.append(lower(value))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) loadDeferredCallParams(structType llvm.Type, ptr llvm.Value) []llvm.Value {
|
||||
fieldTypes := structType.StructElementTypes()
|
||||
values := make([]llvm.Value, 0, len(fieldTypes)-2)
|
||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
||||
for i := 2; i < len(fieldTypes); i++ {
|
||||
fieldPtr := b.CreateInBoundsGEP(structType, ptr, []llvm.Value{
|
||||
zero,
|
||||
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
|
||||
}, "gep")
|
||||
values = append(values, b.CreateLoad(fieldTypes[i], fieldPtr, "param"))
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// createDefer emits a single defer instruction, to be run when this function
|
||||
// returns.
|
||||
func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
@@ -412,28 +362,31 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
// make a linked list.
|
||||
next := b.CreateLoad(b.dataPtrType, b.deferPtr, "defer.next")
|
||||
|
||||
var values llvmValueList
|
||||
lowerArgument := func(value ssa.Value) llvm.Value {
|
||||
return b.getCallArgument(value, false)
|
||||
}
|
||||
var values []llvm.Value
|
||||
valueTypes := []llvm.Type{b.uintptrType, next.Type()}
|
||||
if instr.Call.IsInvoke() {
|
||||
// Method call on an interface.
|
||||
|
||||
// Get callback type number.
|
||||
key := b.getInvokeFunctionName(&instr.Call)
|
||||
if _, ok := b.deferInvokeFuncs[key]; !ok {
|
||||
b.deferInvokeFuncs[key] = len(b.allDeferFuncs)
|
||||
methodName := instr.Call.Method.FullName()
|
||||
if _, ok := b.deferInvokeFuncs[methodName]; !ok {
|
||||
b.deferInvokeFuncs[methodName] = len(b.allDeferFuncs)
|
||||
b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call)
|
||||
}
|
||||
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferInvokeFuncs[key]), false)
|
||||
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferInvokeFuncs[methodName]), false)
|
||||
|
||||
// Collect all values to be put in the struct (starting with
|
||||
// runtime._defer fields, followed by the call parameters).
|
||||
itf := b.getValue(instr.Call.Value, getPos(instr)) // interface
|
||||
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
|
||||
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
||||
values = newLLVMValueList(callback, next, typecode, receiverValue)
|
||||
values.appendSSAValues(instr.Call.Args, lowerArgument)
|
||||
values = []llvm.Value{callback, next, typecode, receiverValue}
|
||||
valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType)
|
||||
for _, arg := range instr.Call.Args {
|
||||
val := b.getValue(arg, getPos(instr))
|
||||
values = append(values, val)
|
||||
valueTypes = append(valueTypes, val.Type())
|
||||
}
|
||||
|
||||
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
|
||||
// Regular function call.
|
||||
@@ -445,11 +398,12 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
|
||||
// Collect all values to be put in the struct (starting with
|
||||
// runtime._defer fields).
|
||||
values = newLLVMValueList(callback, next)
|
||||
exported := b.getFunctionInfo(callee).exported
|
||||
values.appendSSAValues(instr.Call.Args, func(value ssa.Value) llvm.Value {
|
||||
return b.getCallArgument(value, exported)
|
||||
})
|
||||
values = []llvm.Value{callback, next}
|
||||
for _, param := range instr.Call.Args {
|
||||
llvmParam := b.getValue(param, getPos(instr))
|
||||
values = append(values, llvmParam)
|
||||
valueTypes = append(valueTypes, llvmParam.Type())
|
||||
}
|
||||
|
||||
} else if makeClosure, ok := instr.Call.Value.(*ssa.MakeClosure); ok {
|
||||
// Immediately applied function literal with free variables.
|
||||
@@ -472,9 +426,14 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
// Collect all values to be put in the struct (starting with
|
||||
// runtime._defer fields, followed by all parameters including the
|
||||
// context pointer).
|
||||
values = newLLVMValueList(callback, next)
|
||||
values.appendSSAValues(instr.Call.Args, lowerArgument)
|
||||
values.append(context)
|
||||
values = []llvm.Value{callback, next}
|
||||
for _, param := range instr.Call.Args {
|
||||
llvmParam := b.getValue(param, getPos(instr))
|
||||
values = append(values, llvmParam)
|
||||
valueTypes = append(valueTypes, llvmParam.Type())
|
||||
}
|
||||
values = append(values, context)
|
||||
valueTypes = append(valueTypes, context.Type())
|
||||
|
||||
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
|
||||
var argTypes []types.Type
|
||||
@@ -497,8 +456,11 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
|
||||
// Collect all values to be put in the struct (starting with
|
||||
// runtime._defer fields).
|
||||
values = newLLVMValueList(callback, next)
|
||||
values.append(argValues...)
|
||||
values = []llvm.Value{callback, next}
|
||||
for _, param := range argValues {
|
||||
values = append(values, param)
|
||||
valueTypes = append(valueTypes, param.Type())
|
||||
}
|
||||
|
||||
} else {
|
||||
funcValue := b.getValue(instr.Call.Value, getPos(instr))
|
||||
@@ -513,15 +475,20 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
// Collect all values to be put in the struct (starting with
|
||||
// runtime._defer fields, followed by all parameters including the
|
||||
// context pointer).
|
||||
values = newLLVMValueList(callback, next, funcValue)
|
||||
values.appendSSAValues(instr.Call.Args, lowerArgument)
|
||||
values = []llvm.Value{callback, next, funcValue}
|
||||
valueTypes = append(valueTypes, funcValue.Type())
|
||||
for _, param := range instr.Call.Args {
|
||||
llvmParam := b.getValue(param, getPos(instr))
|
||||
values = append(values, llvmParam)
|
||||
valueTypes = append(valueTypes, llvmParam.Type())
|
||||
}
|
||||
}
|
||||
|
||||
// Make a struct out of the collected values to put in the deferred call
|
||||
// struct.
|
||||
deferredCallType := b.ctx.StructType(values.types, false)
|
||||
deferredCallType := b.ctx.StructType(valueTypes, false)
|
||||
deferredCall := llvm.ConstNull(deferredCallType)
|
||||
for i, value := range values.values {
|
||||
for i, value := range values {
|
||||
deferredCall = b.CreateInsertValue(deferredCall, value, i, "")
|
||||
}
|
||||
|
||||
@@ -537,9 +504,8 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
// This may be hit a variable number of times, so use a heap allocation.
|
||||
size := b.targetData.TypeAllocSize(deferredCallType)
|
||||
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
|
||||
layoutValue := b.createObjectLayout(deferredCallType, instr.Pos())
|
||||
align := b.targetData.ABITypeAlignment(deferredCallType)
|
||||
alloca = b.createAlloc(sizeValue, layoutValue, align, "defer.alloc.call")
|
||||
nilPtr := llvm.ConstNull(b.dataPtrType)
|
||||
alloca = b.createAlloc(sizeValue, nilPtr, 0, "defer.alloc.call")
|
||||
}
|
||||
if b.NeedsStackObjects {
|
||||
b.trackPointer(alloca)
|
||||
@@ -623,11 +589,19 @@ func (b *builder) createRunDefers() {
|
||||
valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType)
|
||||
}
|
||||
|
||||
valueTypes = b.appendStoredValueTypes(valueTypes, callback.Args, false)
|
||||
for _, arg := range callback.Args {
|
||||
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
|
||||
}
|
||||
|
||||
// Extract the params from the struct (including receiver).
|
||||
forwardParams := []llvm.Value{}
|
||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
||||
deferredCallType := b.ctx.StructType(valueTypes, false)
|
||||
forwardParams := b.loadDeferredCallParams(deferredCallType, deferData)
|
||||
for i := 2; i < len(valueTypes); i++ {
|
||||
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
|
||||
forwardParam := b.CreateLoad(valueTypes[i], gep, "param")
|
||||
forwardParams = append(forwardParams, forwardParam)
|
||||
}
|
||||
|
||||
var fnPtr llvm.Value
|
||||
var fnType llvm.Type
|
||||
@@ -656,7 +630,6 @@ func (b *builder) createRunDefers() {
|
||||
// with a strict calling convention.
|
||||
forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType))
|
||||
}
|
||||
forwardParams = b.prependIndirectResult(callback.Signature(), false, forwardParams, "defer.result")
|
||||
|
||||
b.createCall(fnType, fnPtr, forwardParams, "")
|
||||
|
||||
@@ -665,21 +638,27 @@ func (b *builder) createRunDefers() {
|
||||
|
||||
// Get the real defer struct type and cast to it.
|
||||
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
|
||||
exported := b.getFunctionInfo(callback).exported
|
||||
valueTypes = b.appendStoredParamTypes(valueTypes, getParams(callback.Signature), exported)
|
||||
for _, param := range getParams(callback.Signature) {
|
||||
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
|
||||
}
|
||||
deferredCallType := b.ctx.StructType(valueTypes, false)
|
||||
|
||||
// Extract the params from the struct.
|
||||
forwardParams := b.loadDeferredCallParams(deferredCallType, deferData)
|
||||
forwardParams := []llvm.Value{}
|
||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
||||
for i := range getParams(callback.Signature) {
|
||||
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
||||
forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param")
|
||||
forwardParams = append(forwardParams, forwardParam)
|
||||
}
|
||||
|
||||
// Plain TinyGo functions add some extra parameters to implement async functionality and function receivers.
|
||||
// These parameters should not be supplied when calling into an external C/ASM function.
|
||||
if !exported {
|
||||
if !b.getFunctionInfo(callback).exported {
|
||||
// Add the context parameter. We know it is ignored by the receiving
|
||||
// function, but we have to pass one anyway.
|
||||
forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType))
|
||||
}
|
||||
forwardParams = b.prependIndirectResult(callback.Signature, exported, forwardParams, "defer.result")
|
||||
|
||||
// Call real function.
|
||||
fnType, fn := b.getFunction(callback)
|
||||
@@ -689,16 +668,24 @@ func (b *builder) createRunDefers() {
|
||||
// Get the real defer struct type and cast to it.
|
||||
fn := callback.Fn.(*ssa.Function)
|
||||
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
|
||||
valueTypes = b.appendStoredParamTypes(valueTypes, getParams(fn.Signature), false)
|
||||
params := fn.Signature.Params()
|
||||
for i := 0; i < params.Len(); i++ {
|
||||
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
|
||||
}
|
||||
valueTypes = append(valueTypes, b.dataPtrType) // closure
|
||||
deferredCallType := b.ctx.StructType(valueTypes, false)
|
||||
|
||||
// Extract the params from the struct.
|
||||
forwardParams := b.loadDeferredCallParams(deferredCallType, deferData)
|
||||
forwardParams := []llvm.Value{}
|
||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
||||
for i := 2; i < len(valueTypes); i++ {
|
||||
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
|
||||
forwardParam := b.CreateLoad(valueTypes[i], gep, "param")
|
||||
forwardParams = append(forwardParams, forwardParam)
|
||||
}
|
||||
|
||||
// Call deferred function.
|
||||
fnType, llvmFn := b.getFunction(fn)
|
||||
forwardParams = b.prependIndirectResult(fn.Signature, false, forwardParams, "defer.result")
|
||||
b.createCall(fnType, llvmFn, forwardParams, "")
|
||||
case *ssa.Builtin:
|
||||
db := b.deferBuiltinFuncs[callback]
|
||||
@@ -708,14 +695,20 @@ func (b *builder) createRunDefers() {
|
||||
|
||||
//Get signature from call results
|
||||
params := callback.Type().Underlying().(*types.Signature).Params()
|
||||
for v := range params.Variables() {
|
||||
valueTypes = append(valueTypes, b.getLLVMType(v.Type()))
|
||||
for i := 0; i < params.Len(); i++ {
|
||||
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
|
||||
}
|
||||
|
||||
deferredCallType := b.ctx.StructType(valueTypes, false)
|
||||
|
||||
// Extract the params from the struct.
|
||||
argValues := b.loadDeferredCallParams(deferredCallType, deferData)
|
||||
var argValues []llvm.Value
|
||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
||||
for i := 0; i < params.Len(); i++ {
|
||||
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
||||
forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param")
|
||||
argValues = append(argValues, forwardParam)
|
||||
}
|
||||
|
||||
_, err := b.createBuiltin(db.argTypes, argValues, db.callName, db.pos)
|
||||
if err != nil {
|
||||
|
||||
+22
-97
@@ -10,91 +10,6 @@ import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// LLVM recursively expands each struct field and array element in parameters
|
||||
// and results into separate values. It gets very slow with too many values, so
|
||||
// pass larger aggregates indirectly before LLVM expands them.
|
||||
const maxDirectAggregateValues = 1024
|
||||
|
||||
func (c *compilerContext) getLLVMResultType(sig *types.Signature) llvm.Type {
|
||||
switch sig.Results().Len() {
|
||||
case 0:
|
||||
return c.ctx.VoidType()
|
||||
case 1:
|
||||
return c.getLLVMType(sig.Results().At(0).Type())
|
||||
default:
|
||||
results := make([]llvm.Type, sig.Results().Len())
|
||||
for i := range results {
|
||||
results[i] = c.getLLVMType(sig.Results().At(i).Type())
|
||||
}
|
||||
return c.ctx.StructType(results, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compilerContext) hasIndirectResult(sig *types.Signature) (llvm.Type, bool) {
|
||||
resultType := c.getLLVMResultType(sig)
|
||||
return resultType, c.isIndirectAggregate(resultType)
|
||||
}
|
||||
|
||||
func (c *compilerContext) isIndirectAggregate(typ llvm.Type) bool {
|
||||
switch typ.TypeKind() {
|
||||
case llvm.ArrayTypeKind, llvm.StructTypeKind:
|
||||
_, exceeded := aggregateValueCount(typ, 0)
|
||||
return exceeded
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func aggregateValueCount(typ llvm.Type, count uint64) (uint64, bool) {
|
||||
switch typ.TypeKind() {
|
||||
case llvm.ArrayTypeKind:
|
||||
length := uint64(typ.ArrayLength())
|
||||
if length == 0 {
|
||||
return count, false
|
||||
}
|
||||
elementCount, exceeded := aggregateValueCount(typ.ElementType(), 0)
|
||||
if exceeded {
|
||||
return count, true
|
||||
}
|
||||
if elementCount != 0 && length > (maxDirectAggregateValues-count)/elementCount {
|
||||
return count, true
|
||||
}
|
||||
return count + length*elementCount, false
|
||||
case llvm.StructTypeKind:
|
||||
for _, field := range typ.StructElementTypes() {
|
||||
var exceeded bool
|
||||
count, exceeded = aggregateValueCount(field, count)
|
||||
if exceeded {
|
||||
return count, true
|
||||
}
|
||||
}
|
||||
return count, false
|
||||
default:
|
||||
count++
|
||||
return count, count > maxDirectAggregateValues
|
||||
}
|
||||
}
|
||||
|
||||
func isLLVMValueType(typ types.Type) bool {
|
||||
switch typ := typ.Underlying().(type) {
|
||||
case *types.Basic:
|
||||
return typ.Kind() != types.Invalid
|
||||
case *types.Array:
|
||||
return isLLVMValueType(typ.Elem())
|
||||
case *types.Struct:
|
||||
for field := range typ.Fields() {
|
||||
if !isLLVMValueType(field.Type()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Signature, *types.Slice:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// createFuncValue creates a function value from a raw function pointer with no
|
||||
// context.
|
||||
func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
|
||||
@@ -133,18 +48,28 @@ func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
|
||||
|
||||
// getLLVMFunctionType returns a LLVM function type for a given signature.
|
||||
func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type {
|
||||
returnType, indirectResult := c.hasIndirectResult(typ)
|
||||
// Get the return type.
|
||||
var returnType llvm.Type
|
||||
switch typ.Results().Len() {
|
||||
case 0:
|
||||
// No return values.
|
||||
returnType = c.ctx.VoidType()
|
||||
case 1:
|
||||
// Just one return value.
|
||||
returnType = c.getLLVMType(typ.Results().At(0).Type())
|
||||
default:
|
||||
// Multiple return values. Put them together in a struct.
|
||||
// This appears to be the common way to handle multiple return values in
|
||||
// LLVM.
|
||||
members := make([]llvm.Type, typ.Results().Len())
|
||||
for i := 0; i < typ.Results().Len(); i++ {
|
||||
members[i] = c.getLLVMType(typ.Results().At(i).Type())
|
||||
}
|
||||
returnType = c.ctx.StructType(members, false)
|
||||
}
|
||||
|
||||
// Get the parameter types.
|
||||
var paramTypes []llvm.Type
|
||||
if indirectResult {
|
||||
// LLVM expands aggregate returns into scalar leaves before deciding
|
||||
// whether to pass them indirectly, so a large IR return can exhaust
|
||||
// memory. Returning void avoids that expansion and cannot be demoted
|
||||
// again. Keep the result pointer first so the context remains last.
|
||||
paramTypes = append(paramTypes, c.dataPtrType)
|
||||
returnType = c.ctx.VoidType()
|
||||
}
|
||||
if typ.Recv() != nil {
|
||||
recv := c.getLLVMType(typ.Recv().Type())
|
||||
if recv.StructName() == "runtime._interface" {
|
||||
@@ -156,8 +81,8 @@ func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type {
|
||||
paramTypes = append(paramTypes, info.llvmType)
|
||||
}
|
||||
}
|
||||
for v := range typ.Params().Variables() {
|
||||
subType := c.getLLVMType(v.Type())
|
||||
for i := 0; i < typ.Params().Len(); i++ {
|
||||
subType := c.getLLVMType(typ.Params().At(i).Type())
|
||||
for _, info := range c.expandFormalParamType(subType, "", nil) {
|
||||
paramTypes = append(paramTypes, info.llvmType)
|
||||
}
|
||||
@@ -187,7 +112,7 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
|
||||
|
||||
// Store the bound variables in a single object, allocating it on the heap
|
||||
// if necessary.
|
||||
context := b.emitPointerPack(boundVars, expr.Pos())
|
||||
context := b.emitPointerPack(boundVars)
|
||||
|
||||
// Create the closure.
|
||||
_, fn := b.getFunction(f)
|
||||
|
||||
+11
-7
@@ -5,7 +5,6 @@ package compiler
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"slices"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
@@ -29,10 +28,10 @@ func (b *builder) createAlloc(sizeValue, layoutValue llvm.Value, align int, comm
|
||||
|
||||
// Make the runtime call.
|
||||
call := b.createRuntimeCall(allocFunc, []llvm.Value{sizeValue, layoutValue}, comment)
|
||||
if align == 0 || align&(align-1) != 0 {
|
||||
panic("invalid alignment")
|
||||
if align != 0 {
|
||||
// TODO: make sure all callsites set the correct alignment.
|
||||
call.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
|
||||
}
|
||||
call.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
|
||||
|
||||
return call
|
||||
}
|
||||
@@ -89,7 +88,7 @@ func (b *builder) trackValue(value llvm.Value) {
|
||||
return
|
||||
}
|
||||
numElements := typ.StructElementTypesCount()
|
||||
for i := range numElements {
|
||||
for i := 0; i < numElements; i++ {
|
||||
subValue := b.CreateExtractValue(value, i, "")
|
||||
b.trackValue(subValue)
|
||||
}
|
||||
@@ -98,7 +97,7 @@ func (b *builder) trackValue(value llvm.Value) {
|
||||
return
|
||||
}
|
||||
numElements := typ.ArrayLength()
|
||||
for i := range numElements {
|
||||
for i := 0; i < numElements; i++ {
|
||||
subValue := b.CreateExtractValue(value, i, "")
|
||||
b.trackValue(subValue)
|
||||
}
|
||||
@@ -119,7 +118,12 @@ func typeHasPointers(t llvm.Type) bool {
|
||||
case llvm.PointerTypeKind:
|
||||
return true
|
||||
case llvm.StructTypeKind:
|
||||
return slices.ContainsFunc(t.StructElementTypes(), typeHasPointers)
|
||||
for _, subType := range t.StructElementTypes() {
|
||||
if typeHasPointers(subType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case llvm.ArrayTypeKind:
|
||||
if t.ArrayLength() == 0 {
|
||||
return false
|
||||
|
||||
+21
-34
@@ -47,16 +47,20 @@ func (b *builder) createGo(instr *ssa.Go) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get all function parameters to pass to the goroutine.
|
||||
var params []llvm.Value
|
||||
for _, param := range instr.Call.Args {
|
||||
params = append(params, b.expandFormalParam(b.getValue(param, getPos(instr)))...)
|
||||
}
|
||||
|
||||
var prefix string
|
||||
var funcPtr llvm.Value
|
||||
var funcType llvm.Type
|
||||
var context llvm.Value
|
||||
hasContext := false
|
||||
exported := false
|
||||
if callee := instr.Call.StaticCallee(); callee != nil {
|
||||
// Static callee is known. This makes it easier to start a new
|
||||
// goroutine.
|
||||
var context llvm.Value
|
||||
switch value := instr.Call.Value.(type) {
|
||||
case *ssa.Function:
|
||||
// Goroutine call is regular function call. No context is necessary.
|
||||
@@ -69,10 +73,10 @@ func (b *builder) createGo(instr *ssa.Go) {
|
||||
panic("StaticCallee returned an unexpected value")
|
||||
}
|
||||
if !context.IsNil() {
|
||||
params = append(params, context) // context parameter
|
||||
hasContext = true
|
||||
}
|
||||
funcType, funcPtr = b.getFunction(callee)
|
||||
exported = b.getFunctionInfo(callee).exported
|
||||
} else if instr.Call.IsInvoke() {
|
||||
// This is a method call on an interface value.
|
||||
itf := b.getValue(instr.Call.Value, getPos(instr))
|
||||
@@ -80,32 +84,23 @@ func (b *builder) createGo(instr *ssa.Go) {
|
||||
itfValue := b.CreateExtractValue(itf, 1, "")
|
||||
funcPtr = b.getInvokeFunction(&instr.Call)
|
||||
funcType = funcPtr.GlobalValueType()
|
||||
params = append(params, itfValue)
|
||||
context = itfTypeCode
|
||||
params = append([]llvm.Value{itfValue}, params...) // start with receiver
|
||||
params = append(params, itfTypeCode) // end with typecode
|
||||
} else {
|
||||
// This is a function pointer.
|
||||
// At the moment, two extra params are passed to the newly started
|
||||
// goroutine:
|
||||
// * The function context, for closures.
|
||||
// * The function pointer (for tasks).
|
||||
var context llvm.Value
|
||||
funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value, getPos(instr)))
|
||||
funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature))
|
||||
params = append(params, context, funcPtr)
|
||||
hasContext = true
|
||||
prefix = b.getFunctionInfo(b.fn).linkName
|
||||
prefix = b.fn.RelString(nil)
|
||||
}
|
||||
|
||||
for _, param := range instr.Call.Args {
|
||||
params = append(params, b.getGoroutineCallArgument(param, exported)...)
|
||||
}
|
||||
if !context.IsNil() {
|
||||
params = append(params, context)
|
||||
}
|
||||
if hasContext && instr.Call.StaticCallee() == nil {
|
||||
params = append(params, funcPtr)
|
||||
}
|
||||
params = b.prependIndirectResult(instr.Call.Signature(), exported, params, "go.result")
|
||||
|
||||
paramBundle := b.emitPointerPack(params, instr.Pos())
|
||||
paramBundle := b.emitPointerPack(params)
|
||||
var stackSize llvm.Value
|
||||
callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, false, instr.Pos())
|
||||
if b.AutomaticStackSize {
|
||||
@@ -127,15 +122,6 @@ func (b *builder) createGo(instr *ssa.Go) {
|
||||
b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.dataPtrType)}, "")
|
||||
}
|
||||
|
||||
func (b *builder) getGoroutineCallArgument(value ssa.Value, exported bool) []llvm.Value {
|
||||
typ := b.getLLVMType(value.Type())
|
||||
arg := b.getCallArgument(value, exported)
|
||||
if b.isIndirectParam(typ, exported) {
|
||||
return []llvm.Value{b.copyToIndirectStorage(arg, typ, "go.param")}
|
||||
}
|
||||
return b.expandFormalParam(arg)
|
||||
}
|
||||
|
||||
// Create an exported wrapper function for functions with the //go:wasmexport
|
||||
// pragma. This wrapper function is quite complex when the scheduler is enabled:
|
||||
// it needs to start a new goroutine each time the exported function is called.
|
||||
@@ -153,7 +139,7 @@ func (b *builder) createWasmExport() {
|
||||
// Declare the exported function.
|
||||
paramTypes := b.llvmFnType.ParamTypes()
|
||||
exportedFnType := llvm.FunctionType(b.llvmFnType.ReturnType(), paramTypes[:len(paramTypes)-1], false)
|
||||
exportedFn := llvm.AddFunction(b.mod, b.getFunctionInfo(b.fn).linkName+suffix, exportedFnType)
|
||||
exportedFn := llvm.AddFunction(b.mod, b.fn.RelString(nil)+suffix, exportedFnType)
|
||||
b.addStandardAttributes(exportedFn)
|
||||
llvmutil.AppendToGlobal(b.mod, "llvm.used", exportedFn)
|
||||
exportedFn.AddFunctionAttr(b.ctx.CreateStringAttribute("wasm-export-name", b.info.wasmExport))
|
||||
@@ -309,10 +295,10 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
|
||||
}
|
||||
defer b.Dispose()
|
||||
|
||||
var exitGoroutine llvm.Value
|
||||
var exitGoroutineType llvm.Type
|
||||
var deadlock llvm.Value
|
||||
var deadlockType llvm.Type
|
||||
if c.Scheduler == "asyncify" {
|
||||
exitGoroutineType, exitGoroutine = c.getFunction(c.program.ImportedPackage("runtime").Members["exitGoroutine"].(*ssa.Function))
|
||||
deadlockType, deadlock = c.getFunction(c.program.ImportedPackage("runtime").Members["deadlock"].(*ssa.Function))
|
||||
}
|
||||
|
||||
if !fn.IsAFunction().IsNil() {
|
||||
@@ -377,7 +363,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
|
||||
b.CreateCall(fnType, fn, params, "")
|
||||
|
||||
if c.Scheduler == "asyncify" {
|
||||
b.CreateCall(exitGoroutineType, exitGoroutine, []llvm.Value{
|
||||
b.CreateCall(deadlockType, deadlock, []llvm.Value{
|
||||
llvm.Undef(c.dataPtrType),
|
||||
}, "")
|
||||
}
|
||||
@@ -428,7 +414,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
|
||||
// Extract parameters from the state object, and call the function
|
||||
// that's being wrapped.
|
||||
var callParams []llvm.Value
|
||||
for i := range numParams {
|
||||
for i := 0; i < numParams; i++ {
|
||||
gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
|
||||
@@ -528,13 +514,14 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
|
||||
b.CreateCall(fnType, fnPtr, params, "")
|
||||
|
||||
if c.Scheduler == "asyncify" {
|
||||
b.CreateCall(exitGoroutineType, exitGoroutine, []llvm.Value{
|
||||
b.CreateCall(deadlockType, deadlock, []llvm.Value{
|
||||
llvm.Undef(c.dataPtrType),
|
||||
}, "")
|
||||
}
|
||||
}
|
||||
|
||||
if c.Scheduler == "asyncify" {
|
||||
// The goroutine was terminated via deadlock.
|
||||
b.CreateUnreachable()
|
||||
} else {
|
||||
// Finish the function. Every basic block must end in a terminator, and
|
||||
|
||||
+10
-12
@@ -146,14 +146,13 @@ func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error
|
||||
llvmArgs := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
asm := "svc #" + strconv.FormatUint(num, 10)
|
||||
var constraints strings.Builder
|
||||
constraints.WriteString("={r0}")
|
||||
constraints := "={r0}"
|
||||
for i, arg := range args[1:] {
|
||||
arg = arg.(*ssa.MakeInterface).X
|
||||
if i == 0 {
|
||||
constraints.WriteString(",0")
|
||||
constraints += ",0"
|
||||
} else {
|
||||
constraints.WriteString(",{r" + strconv.Itoa(i) + "}")
|
||||
constraints += ",{r" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
llvmValue := b.getValue(arg, pos)
|
||||
llvmArgs = append(llvmArgs, llvmValue)
|
||||
@@ -162,9 +161,9 @@ func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error
|
||||
// Implement the ARM calling convention by marking r1-r3 as
|
||||
// clobbered. r0 is used as an output register so doesn't have to be
|
||||
// marked as clobbered.
|
||||
constraints.WriteString(",~{r1},~{r2},~{r3}")
|
||||
constraints += ",~{r1},~{r2},~{r3}"
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints.String(), true, false, 0, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false)
|
||||
return b.CreateCall(fnType, target, llvmArgs, ""), nil
|
||||
}
|
||||
|
||||
@@ -185,14 +184,13 @@ func (b *builder) emitSV64Call(args []ssa.Value, pos token.Pos) (llvm.Value, err
|
||||
llvmArgs := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
asm := "svc #" + strconv.FormatUint(num, 10)
|
||||
var constraints strings.Builder
|
||||
constraints.WriteString("={x0}")
|
||||
constraints := "={x0}"
|
||||
for i, arg := range args[1:] {
|
||||
arg = arg.(*ssa.MakeInterface).X
|
||||
if i == 0 {
|
||||
constraints.WriteString(",0")
|
||||
constraints += ",0"
|
||||
} else {
|
||||
constraints.WriteString(",{x" + strconv.Itoa(i) + "}")
|
||||
constraints += ",{x" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
llvmValue := b.getValue(arg, pos)
|
||||
llvmArgs = append(llvmArgs, llvmValue)
|
||||
@@ -201,9 +199,9 @@ func (b *builder) emitSV64Call(args []ssa.Value, pos token.Pos) (llvm.Value, err
|
||||
// Implement the ARM64 calling convention by marking x1-x7 as
|
||||
// clobbered. x0 is used as an output register so doesn't have to be
|
||||
// marked as clobbered.
|
||||
constraints.WriteString(",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}")
|
||||
constraints += ",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}"
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints.String(), true, false, 0, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false)
|
||||
return b.CreateCall(fnType, target, llvmArgs, ""), nil
|
||||
}
|
||||
|
||||
|
||||
+157
-134
@@ -84,11 +84,7 @@ const (
|
||||
//
|
||||
// An interface value is a {typecode, value} tuple named runtime._interface.
|
||||
func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
|
||||
itfValue := b.emitPointerPack([]llvm.Value{val}, pos)
|
||||
return b.createMakeInterfaceFromPointer(itfValue, typ)
|
||||
}
|
||||
|
||||
func (b *builder) createMakeInterfaceFromPointer(itfValue llvm.Value, typ types.Type) llvm.Value {
|
||||
itfValue := b.emitPointerPack([]llvm.Value{val})
|
||||
itfType := b.getTypeCode(typ)
|
||||
itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
|
||||
itf = b.CreateInsertValue(itf, itfType, 0, "")
|
||||
@@ -102,16 +98,9 @@ func (b *builder) createMakeInterfaceFromPointer(itfValue llvm.Value, typ types.
|
||||
// doesn't match the underlying type of the interface.
|
||||
func (b *builder) extractValueFromInterface(itf llvm.Value, llvmType llvm.Type) llvm.Value {
|
||||
valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr")
|
||||
if b.isIndirectAggregate(llvmType) {
|
||||
return valuePtr
|
||||
}
|
||||
return b.emitPointerUnpack(valuePtr, []llvm.Type{llvmType})[0]
|
||||
}
|
||||
|
||||
func (b *builder) extractValuePointerFromInterface(itf llvm.Value) llvm.Value {
|
||||
return b.CreateExtractValue(itf, 1, "typeassert.value.ptr")
|
||||
}
|
||||
|
||||
func (c *compilerContext) pkgPathPtr(pkgpath string) llvm.Value {
|
||||
pkgpathName := "reflect/types.type.pkgpath.empty"
|
||||
if pkgpath != "" {
|
||||
@@ -136,16 +125,6 @@ func (c *compilerContext) pkgPathPtr(pkgpath string) llvm.Value {
|
||||
return pkgPathPtr
|
||||
}
|
||||
|
||||
// isGenericMethod returns true for a method that has its own type parameters
|
||||
// (independent of any type parameters on its receiver), e.g. the N method in
|
||||
// "func (r *Rand) N[Int intType](n Int) Int { ... }". Like the reflect
|
||||
// package, such methods are excluded from runtime method sets: they aren't
|
||||
// instantiated, so their signature can't be represented in a type code, and
|
||||
// they can never satisfy an interface method anyway.
|
||||
func isGenericMethod(fn *types.Func) bool {
|
||||
return fn.Signature().TypeParams().Len() > 0
|
||||
}
|
||||
|
||||
// getTypeCode returns a reference to a type code.
|
||||
// A type code is a pointer to a constant global that describes the type.
|
||||
// This function returns a pointer to the 'kind' field (which might not be the
|
||||
@@ -155,26 +134,22 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
typ = types.Unalias(typ)
|
||||
|
||||
ms := c.program.MethodSets.MethodSet(typ)
|
||||
hasMethodSet := ms.Len() != 0
|
||||
_, isInterface := typ.Underlying().(*types.Interface)
|
||||
if isInterface {
|
||||
hasMethodSet = false
|
||||
}
|
||||
|
||||
// As defined in https://pkg.go.dev/reflect#Type:
|
||||
// NumMethod returns the number of methods accessible using Method.
|
||||
// For a non-interface type, it returns the number of exported methods.
|
||||
// For an interface type, it returns the number of exported and unexported methods.
|
||||
var numMethods int
|
||||
var hasMethodSet bool
|
||||
for method := range ms.Methods() {
|
||||
if isGenericMethod(method.Obj().(*types.Func)) {
|
||||
continue
|
||||
}
|
||||
hasMethodSet = true
|
||||
if isInterface || method.Obj().Exported() {
|
||||
for i := 0; i < ms.Len(); i++ {
|
||||
if isInterface || ms.At(i).Obj().Exported() {
|
||||
numMethods++
|
||||
}
|
||||
}
|
||||
if isInterface {
|
||||
hasMethodSet = false
|
||||
}
|
||||
|
||||
// Short-circuit all the global pointer logic here for pointers to pointers.
|
||||
if typ, ok := typ.(*types.Pointer); ok {
|
||||
@@ -218,12 +193,8 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
}
|
||||
// Compute the method set value for types that support methods.
|
||||
var methods []*types.Func
|
||||
for method := range ms.Methods() {
|
||||
fn := method.Obj().(*types.Func)
|
||||
if isGenericMethod(fn) {
|
||||
continue
|
||||
}
|
||||
methods = append(methods, fn)
|
||||
for i := 0; i < ms.Len(); i++ {
|
||||
methods = append(methods, ms.At(i).Obj().(*types.Func))
|
||||
}
|
||||
methodSetType := types.NewStruct([]*types.Var{
|
||||
types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]),
|
||||
@@ -519,7 +490,10 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
c.getTypeMethodSet(typ),
|
||||
}, typeFields...)
|
||||
}
|
||||
alignment := max(c.targetData.TypeAllocSize(c.dataPtrType), 4)
|
||||
alignment := c.targetData.TypeAllocSize(c.dataPtrType)
|
||||
if alignment < 4 {
|
||||
alignment = 4
|
||||
}
|
||||
globalValue := c.ctx.ConstStruct(typeFields, false)
|
||||
global.SetInitializer(globalValue)
|
||||
if isLocal {
|
||||
@@ -628,21 +602,8 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bo
|
||||
case *types.Named:
|
||||
tn := t.Obj()
|
||||
if tn.Pkg() == nil || tn.Parent() == tn.Pkg().Scope() {
|
||||
name := tn.Name()
|
||||
if tn.Pkg() != nil {
|
||||
name = tn.Pkg().Path() + "." + name
|
||||
}
|
||||
isLocal := false
|
||||
if targs := t.TypeArgs(); targs.Len() != 0 {
|
||||
parts := make([]string, targs.Len())
|
||||
for i := range parts {
|
||||
var local bool
|
||||
parts[i], local = c.getTypeCodeName(targs.At(i))
|
||||
isLocal = isLocal || local
|
||||
}
|
||||
name += "[" + strings.Join(parts, ",") + "]"
|
||||
}
|
||||
return "named:" + name, isLocal
|
||||
// Package-scope or builtin: the printed name is unique.
|
||||
return "named:" + t.String(), false
|
||||
}
|
||||
if tn.Parent() != nil {
|
||||
// Ordinary function-local type. Use the un-//line-adjusted
|
||||
@@ -749,8 +710,8 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bo
|
||||
// Synthetic TypeNames are produced by generic instantiation: two
|
||||
// instantiations of the same generic function (e.g. F[int] and
|
||||
// F[string]) produce TypeNames with the same printed name and the
|
||||
// same source position, so each is named with the enclosing instance's
|
||||
// canonical function name as prefix. The function name encodes the type
|
||||
// same source position, so each is named with the enclosing
|
||||
// instance's RelString as prefix. RelString encodes the type
|
||||
// arguments, matching Go's runtime behavior, where F[int].Inner and
|
||||
// F[string].Inner are distinct types even when Inner does not mention
|
||||
// the type parameter.
|
||||
@@ -759,9 +720,9 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bo
|
||||
// of F[int] is compiled in every package that calls F[int]); its
|
||||
// reflect/types.type:* global has LinkOnceODRLinkage and is merged by
|
||||
// name at link time. The chosen name therefore depends only on
|
||||
// intrinsic SSA properties (the canonical function name and raw token.Pos),
|
||||
// so any package compiling the same instance produces the same
|
||||
// identifier.
|
||||
// intrinsic SSA properties (RelString and the raw token.Pos used as a
|
||||
// sort key), so any package compiling the same instance produces the
|
||||
// same identifier.
|
||||
//
|
||||
// Ordinary function-local TypeNames (TypeName.Parent() != nil) are
|
||||
// not handled here: they are nameable only inside their declaring
|
||||
@@ -822,12 +783,12 @@ func (c *compilerContext) scanLocalTypes(ssaPkg *ssa.Package) {
|
||||
walk(m, false)
|
||||
case *ssa.Type:
|
||||
mset := c.program.MethodSets.MethodSet(m.Type())
|
||||
for method := range mset.Methods() {
|
||||
walk(c.program.MethodValue(method), false)
|
||||
for i := 0; i < mset.Len(); i++ {
|
||||
walk(c.program.MethodValue(mset.At(i)), false)
|
||||
}
|
||||
pmset := c.program.MethodSets.MethodSet(types.NewPointer(m.Type()))
|
||||
for method := range pmset.Methods() {
|
||||
walk(c.program.MethodValue(method), false)
|
||||
for i := 0; i < pmset.Len(); i++ {
|
||||
walk(c.program.MethodValue(pmset.At(i)), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -849,8 +810,9 @@ func (c *compilerContext) scanLocalTypes(ssaPkg *ssa.Package) {
|
||||
|
||||
// registerSyntheticLocalTypes walks every type reachable from fn's
|
||||
// body and records each synthetic *types.Named (TypeName.Parent() ==
|
||||
// nil) in c.localTypeNames. Each is named with the canonical function
|
||||
// name plus a per-function counter assigned in source order.
|
||||
// nil) in c.localTypeNames. Each is named with fn.RelString as the
|
||||
// owning function plus a per-function counter assigned in source
|
||||
// order.
|
||||
//
|
||||
// First-writer-wins: a *types.Named already present in
|
||||
// c.localTypeNames is left alone, so a synthetic type reachable from
|
||||
@@ -884,8 +846,8 @@ func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
|
||||
}
|
||||
}
|
||||
targs := t.TypeArgs()
|
||||
for t := range targs.Types() {
|
||||
visit(t)
|
||||
for i := 0; i < targs.Len(); i++ {
|
||||
visit(targs.At(i))
|
||||
}
|
||||
visit(t.Underlying())
|
||||
case *types.Pointer:
|
||||
@@ -900,23 +862,23 @@ func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
|
||||
visit(t.Key())
|
||||
visit(t.Elem())
|
||||
case *types.Struct:
|
||||
for field := range t.Fields() {
|
||||
visit(field.Type())
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
visit(t.Field(i).Type())
|
||||
}
|
||||
case *types.Signature:
|
||||
if p := t.Params(); p != nil {
|
||||
for v := range p.Variables() {
|
||||
visit(v.Type())
|
||||
for i := 0; i < p.Len(); i++ {
|
||||
visit(p.At(i).Type())
|
||||
}
|
||||
}
|
||||
if r := t.Results(); r != nil {
|
||||
for v := range r.Variables() {
|
||||
visit(v.Type())
|
||||
for i := 0; i < r.Len(); i++ {
|
||||
visit(r.At(i).Type())
|
||||
}
|
||||
}
|
||||
case *types.Tuple:
|
||||
for v := range t.Variables() {
|
||||
visit(v.Type())
|
||||
for i := 0; i < t.Len(); i++ {
|
||||
visit(t.At(i).Type())
|
||||
}
|
||||
case *types.Interface:
|
||||
// A synthetic local type can be reachable only through a
|
||||
@@ -925,8 +887,8 @@ func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
|
||||
// the interface's identifier, and the seen map breaks
|
||||
// cycles formed by methods that mention the interface
|
||||
// itself.
|
||||
for method := range t.Methods() {
|
||||
visit(method.Type())
|
||||
for i := 0; i < t.NumMethods(); i++ {
|
||||
visit(t.Method(i).Type())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -961,7 +923,7 @@ func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
|
||||
sort.Slice(found, func(i, j int) bool {
|
||||
return found[i].Obj().Pos() < found[j].Obj().Pos()
|
||||
})
|
||||
enclosing := c.canonicalFunctionName(fn)
|
||||
enclosing := fn.RelString(nil)
|
||||
for i, named := range found {
|
||||
c.localTypeNames.Set(named, fmt.Sprintf("%s.%s$%d", enclosing, named.Obj().Name(), i))
|
||||
}
|
||||
@@ -970,18 +932,15 @@ func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
|
||||
// getTypeMethodSet returns a reference (GEP) to a global method set. This
|
||||
// method set should be unreferenced after the interface lowering pass.
|
||||
func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
typeName, _ := c.getTypeCodeName(typ)
|
||||
globalName := typeName + "$methodset"
|
||||
globalName := typ.String() + "$methodset"
|
||||
global := c.mod.NamedGlobal(globalName)
|
||||
if global.IsNil() {
|
||||
ms := c.program.MethodSets.MethodSet(typ)
|
||||
|
||||
// Create method set.
|
||||
var signatures, wrappers []llvm.Value
|
||||
for method := range ms.Methods() {
|
||||
if isGenericMethod(method.Obj().(*types.Func)) {
|
||||
continue
|
||||
}
|
||||
for i := 0; i < ms.Len(); i++ {
|
||||
method := ms.At(i)
|
||||
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
|
||||
signatures = append(signatures, signatureGlobal)
|
||||
fn := c.program.MethodValue(method)
|
||||
@@ -996,7 +955,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
|
||||
// Construct global value.
|
||||
globalValue := c.ctx.ConstStruct([]llvm.Value{
|
||||
llvm.ConstInt(c.uintptrType, uint64(len(signatures)), false),
|
||||
llvm.ConstInt(c.uintptrType, uint64(ms.Len()), false),
|
||||
llvm.ConstArray(c.dataPtrType, signatures),
|
||||
c.ctx.ConstStruct(wrappers, false),
|
||||
}, false)
|
||||
@@ -1012,15 +971,14 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
// getMethodSignatureName returns a unique name (that can be used as the name of
|
||||
// a global) for the given method.
|
||||
func (c *compilerContext) getMethodSignatureName(method *types.Func) string {
|
||||
name := method.Name()
|
||||
var prefix string
|
||||
signature := methodSignature(method)
|
||||
var globalName string
|
||||
if token.IsExported(method.Name()) {
|
||||
prefix = "reflect/methods."
|
||||
globalName = "reflect/methods." + signature
|
||||
} else {
|
||||
prefix = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods."
|
||||
globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature
|
||||
}
|
||||
signature, _ := c.getTypeCodeName(method.Type())
|
||||
return prefix + name + ":" + signature
|
||||
return globalName
|
||||
}
|
||||
|
||||
// getMethodSignature returns a global variable which is a reference to an
|
||||
@@ -1099,21 +1057,6 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
||||
if expr.CommaOk {
|
||||
nextBlock := b.insertBasicBlock("typeassert.next")
|
||||
b.currentBlockInfo.exit = nextBlock
|
||||
if b.isIndirectAggregate(assertedType) {
|
||||
resultType := b.getLLVMType(expr.Type())
|
||||
result := b.createIndirectStorage(resultType, "typeassert.result")
|
||||
b.zeroIndirectStorage(result, resultType)
|
||||
b.CreateCondBr(commaOk, okBlock, nextBlock)
|
||||
|
||||
b.SetInsertPointAtEnd(okBlock)
|
||||
valuePtr := b.extractValuePointerFromInterface(itf)
|
||||
b.copyIndirectAggregate(b.CreateStructGEP(resultType, result, 0, ""), valuePtr, assertedType)
|
||||
b.CreateBr(nextBlock)
|
||||
|
||||
b.SetInsertPointAtEnd(nextBlock)
|
||||
b.CreateStore(commaOk, b.CreateStructGEP(resultType, result, 1, ""))
|
||||
return result
|
||||
}
|
||||
b.CreateCondBr(commaOk, okBlock, nextBlock)
|
||||
|
||||
// Retrieve the value from the interface if the type assert was
|
||||
@@ -1240,33 +1183,26 @@ func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
|
||||
// thunk is declared, not defined: it will be defined by the interface lowering
|
||||
// pass.
|
||||
func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
|
||||
fnName := c.getInvokeFunctionName(instr)
|
||||
s, _ := c.getTypeCodeName(instr.Value.Type().Underlying())
|
||||
fnName := s + "." + instr.Method.Name() + "$invoke"
|
||||
llvmFn := c.mod.NamedFunction(fnName)
|
||||
if llvmFn.IsNil() {
|
||||
sig := instr.Method.Type().(*types.Signature)
|
||||
var paramTuple []*types.Var
|
||||
for v := range sig.Params().Variables() {
|
||||
paramTuple = append(paramTuple, v)
|
||||
for i := 0; i < sig.Params().Len(); i++ {
|
||||
paramTuple = append(paramTuple, sig.Params().At(i))
|
||||
}
|
||||
paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.UnsafePointer]))
|
||||
llvmFnType := c.getLLVMFunctionType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false))
|
||||
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
|
||||
c.addStandardDeclaredAttributes(llvmFn)
|
||||
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method)))
|
||||
if _, indirect := c.hasIndirectResult(sig); indirect {
|
||||
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-indirect-result", "true"))
|
||||
}
|
||||
methods := c.getMethodsString(instr.Value.Type().Underlying().(*types.Interface))
|
||||
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods))
|
||||
}
|
||||
return llvmFn
|
||||
}
|
||||
|
||||
func (c *compilerContext) getInvokeFunctionName(instr *ssa.CallCommon) string {
|
||||
s, _ := c.getTypeCodeName(instr.Value.Type().Underlying())
|
||||
return s + "." + instr.Method.Name() + "$invoke"
|
||||
}
|
||||
|
||||
// createInterfaceTypeAssert creates a call to a declared-but-not-defined
|
||||
// $typeassert function for the given interface. This function will be defined
|
||||
// by the interface lowering pass as a type-ID comparison chain, avoiding the
|
||||
@@ -1301,7 +1237,6 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
|
||||
// Get the expanded receiver type.
|
||||
receiverType := c.getLLVMType(fn.Signature.Recv().Type())
|
||||
var expandedReceiverType []llvm.Type
|
||||
receiverIndirect := c.isIndirectAggregate(receiverType)
|
||||
for _, info := range c.expandFormalParamType(receiverType, "", nil) {
|
||||
expandedReceiverType = append(expandedReceiverType, info.llvmType)
|
||||
}
|
||||
@@ -1316,13 +1251,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
|
||||
}
|
||||
|
||||
// create wrapper function
|
||||
resultOffset := 0
|
||||
if _, indirect := c.hasIndirectResult(fn.Signature); indirect {
|
||||
resultOffset = 1
|
||||
}
|
||||
paramTypes := append([]llvm.Type{}, llvmFnType.ParamTypes()[:resultOffset]...)
|
||||
paramTypes = append(paramTypes, c.dataPtrType)
|
||||
paramTypes = append(paramTypes, llvmFnType.ParamTypes()[resultOffset+len(expandedReceiverType):]...)
|
||||
paramTypes := append([]llvm.Type{c.dataPtrType}, llvmFnType.ParamTypes()[len(expandedReceiverType):]...)
|
||||
wrapFnType := llvm.FunctionType(llvmFnType.ReturnType(), paramTypes, false)
|
||||
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
|
||||
c.addStandardAttributes(wrapper)
|
||||
@@ -1348,15 +1277,8 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
|
||||
block := b.ctx.AddBasicBlock(wrapper, "entry")
|
||||
b.SetInsertPointAtEnd(block)
|
||||
|
||||
params := append([]llvm.Value{}, wrapper.Params()[:resultOffset]...)
|
||||
receiverParam := wrapper.Param(resultOffset)
|
||||
if receiverIndirect {
|
||||
params = append(params, receiverParam)
|
||||
} else {
|
||||
receiverValue := b.emitPointerUnpack(receiverParam, []llvm.Type{receiverType})[0]
|
||||
params = append(params, b.expandFormalParam(receiverValue)...)
|
||||
}
|
||||
params = append(params, wrapper.Params()[resultOffset+1:]...)
|
||||
receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
|
||||
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
|
||||
if llvmFnType.ReturnType().TypeKind() == llvm.VoidTypeKind {
|
||||
b.CreateCall(llvmFnType, llvmFn, params, "")
|
||||
b.CreateRetVoid()
|
||||
@@ -1367,3 +1289,104 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
|
||||
|
||||
return wrapper
|
||||
}
|
||||
|
||||
// methodSignature creates a readable version of a method signature (including
|
||||
// the function name, excluding the receiver name). This string is used
|
||||
// internally to match interfaces and to call the correct method on an
|
||||
// interface. Examples:
|
||||
//
|
||||
// String() string
|
||||
// Read([]byte) (int, error)
|
||||
func methodSignature(method *types.Func) string {
|
||||
return method.Name() + signature(method.Type().(*types.Signature))
|
||||
}
|
||||
|
||||
// Make a readable version of a function (pointer) signature.
|
||||
// Examples:
|
||||
//
|
||||
// () string
|
||||
// (string, int) (int, error)
|
||||
func signature(sig *types.Signature) string {
|
||||
s := ""
|
||||
if sig.Params().Len() == 0 {
|
||||
s += "()"
|
||||
} else {
|
||||
s += "("
|
||||
for i := 0; i < sig.Params().Len(); i++ {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
}
|
||||
s += typestring(sig.Params().At(i).Type())
|
||||
}
|
||||
s += ")"
|
||||
}
|
||||
if sig.Results().Len() == 0 {
|
||||
// keep as-is
|
||||
} else if sig.Results().Len() == 1 {
|
||||
s += " " + typestring(sig.Results().At(0).Type())
|
||||
} else {
|
||||
s += " ("
|
||||
for i := 0; i < sig.Results().Len(); i++ {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
}
|
||||
s += typestring(sig.Results().At(i).Type())
|
||||
}
|
||||
s += ")"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// typestring returns a stable (human-readable) type string for the given type
|
||||
// that can be used for interface equality checks. It is almost (but not
|
||||
// exactly) the same as calling t.String(). The main difference is some
|
||||
// normalization around `byte` vs `uint8` for example.
|
||||
func typestring(t types.Type) string {
|
||||
// See: https://github.com/golang/go/blob/master/src/go/types/typestring.go
|
||||
switch t := types.Unalias(t).(type) {
|
||||
case *types.Array:
|
||||
return "[" + strconv.FormatInt(t.Len(), 10) + "]" + typestring(t.Elem())
|
||||
case *types.Basic:
|
||||
return basicTypeNames[t.Kind()]
|
||||
case *types.Chan:
|
||||
switch t.Dir() {
|
||||
case types.SendRecv:
|
||||
return "chan (" + typestring(t.Elem()) + ")"
|
||||
case types.SendOnly:
|
||||
return "chan<- (" + typestring(t.Elem()) + ")"
|
||||
case types.RecvOnly:
|
||||
return "<-chan (" + typestring(t.Elem()) + ")"
|
||||
default:
|
||||
panic("unknown channel direction")
|
||||
}
|
||||
case *types.Interface:
|
||||
methods := make([]string, t.NumMethods())
|
||||
for i := range methods {
|
||||
method := t.Method(i)
|
||||
methods[i] = method.Name() + signature(method.Type().(*types.Signature))
|
||||
}
|
||||
return "interface{" + strings.Join(methods, ";") + "}"
|
||||
case *types.Map:
|
||||
return "map[" + typestring(t.Key()) + "]" + typestring(t.Elem())
|
||||
case *types.Named:
|
||||
return t.String()
|
||||
case *types.Pointer:
|
||||
return "*" + typestring(t.Elem())
|
||||
case *types.Signature:
|
||||
return "func" + signature(t)
|
||||
case *types.Slice:
|
||||
return "[]" + typestring(t.Elem())
|
||||
case *types.Struct:
|
||||
fields := make([]string, t.NumFields())
|
||||
for i := range fields {
|
||||
field := t.Field(i)
|
||||
fields[i] = field.Name() + " " + typestring(field.Type())
|
||||
if tag := t.Tag(i); tag != "" {
|
||||
fields[i] += " " + strconv.Quote(tag)
|
||||
}
|
||||
}
|
||||
return "struct{" + strings.Join(fields, ";") + "}"
|
||||
default:
|
||||
panic("unknown type: " + t.String())
|
||||
}
|
||||
}
|
||||
|
||||
+5
-6
@@ -54,7 +54,7 @@ func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) {
|
||||
// pointer value directly. It returns the pointer with the packed data.
|
||||
// If the values are all constants, they are be stored in a constant global and
|
||||
// deduplicated.
|
||||
func (b *builder) emitPointerPack(values []llvm.Value, pos token.Pos) llvm.Value {
|
||||
func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
|
||||
valueTypes := make([]llvm.Type, len(values))
|
||||
for i, value := range values {
|
||||
valueTypes[i] = value.Type()
|
||||
@@ -128,9 +128,8 @@ func (b *builder) emitPointerPack(values []llvm.Value, pos token.Pos) llvm.Value
|
||||
|
||||
// Packed data is bigger than a pointer, so allocate it on the heap.
|
||||
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
|
||||
layoutValue := b.createObjectLayout(packedType, pos)
|
||||
align := b.targetData.ABITypeAlignment(packedType)
|
||||
packedAlloc := b.createAlloc(sizeValue, layoutValue, align, "")
|
||||
packedAlloc := b.createAlloc(sizeValue, llvm.ConstNull(b.dataPtrType), align, "")
|
||||
if b.NeedsStackObjects {
|
||||
b.trackPointer(packedAlloc)
|
||||
}
|
||||
@@ -207,7 +206,7 @@ func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType l
|
||||
globalType := llvm.ArrayType(elementType, len(buf))
|
||||
global := llvm.AddGlobal(c.mod, globalType, name)
|
||||
value := llvm.Undef(globalType)
|
||||
for i := range buf {
|
||||
for i := 0; i < len(buf); i++ {
|
||||
ch := uint64(buf[i])
|
||||
value = c.builder.CreateInsertValue(value, llvm.ConstInt(elementType, ch, false), i, "")
|
||||
}
|
||||
@@ -391,7 +390,7 @@ func (c *compilerContext) buildPointerBitmap(
|
||||
return
|
||||
}
|
||||
elementSize /= ptrAlign
|
||||
for i := range len {
|
||||
for i := 0; i < len; i++ {
|
||||
c.buildPointerBitmap(
|
||||
dst,
|
||||
ptrAlign,
|
||||
@@ -418,7 +417,7 @@ func (c *compilerContext) archFamily() string {
|
||||
// features string is not one for an ARM architecture.
|
||||
func (c *compilerContext) isThumb() bool {
|
||||
var isThumb, isNotThumb bool
|
||||
for feature := range strings.SplitSeq(c.Features, ",") {
|
||||
for _, feature := range strings.Split(c.Features, ",") {
|
||||
if feature == "+thumb-mode" {
|
||||
isThumb = true
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, n
|
||||
alloca = CreateEntryBlockAlloca(builder, t, name)
|
||||
size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
|
||||
fnType, fn := getLifetimeStartFunc(mod)
|
||||
builder.CreateCall(fnType, fn, lifetimeCallArgs(size, alloca), "")
|
||||
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -58,14 +58,14 @@ func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type,
|
||||
builder.SetInsertPointBefore(inst)
|
||||
size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
|
||||
fnType, fn := getLifetimeStartFunc(mod)
|
||||
builder.CreateCall(fnType, fn, lifetimeCallArgs(size, alloca), "")
|
||||
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
|
||||
if next := llvm.NextInstruction(inst); !next.IsNil() {
|
||||
builder.SetInsertPointBefore(next)
|
||||
} else {
|
||||
builder.SetInsertPointAtEnd(inst.InstructionParent())
|
||||
}
|
||||
fnType, fn = getLifetimeEndFunc(mod)
|
||||
builder.CreateCall(fnType, fn, lifetimeCallArgs(size, alloca), "")
|
||||
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
|
||||
return alloca
|
||||
}
|
||||
|
||||
@@ -74,27 +74,7 @@ func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type,
|
||||
// createTemporaryAlloca.
|
||||
func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value) {
|
||||
fnType, fn := getLifetimeEndFunc(mod)
|
||||
builder.CreateCall(fnType, fn, lifetimeCallArgs(size, ptr), "")
|
||||
}
|
||||
|
||||
// lifetimeCallArgs returns the arguments to pass to a call of the
|
||||
// llvm.lifetime.start/end intrinsics. LLVM 22 dropped the (redundant,
|
||||
// already required to match the alloca size) i64 size argument, so the
|
||||
// intrinsic now only takes the pointer.
|
||||
func lifetimeCallArgs(size, ptr llvm.Value) []llvm.Value {
|
||||
if Version() >= 22 {
|
||||
return []llvm.Value{ptr}
|
||||
}
|
||||
return []llvm.Value{size, ptr}
|
||||
}
|
||||
|
||||
// lifetimeFuncType returns the function type of the llvm.lifetime.start/end
|
||||
// intrinsics, which lost their i64 size parameter in LLVM 22.
|
||||
func lifetimeFuncType(ctx llvm.Context, ptrType llvm.Type) llvm.Type {
|
||||
if Version() >= 22 {
|
||||
return llvm.FunctionType(ctx.VoidType(), []llvm.Type{ptrType}, false)
|
||||
}
|
||||
return llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
|
||||
builder.CreateCall(fnType, fn, []llvm.Value{size, ptr}, "")
|
||||
}
|
||||
|
||||
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
|
||||
@@ -104,7 +84,7 @@ func getLifetimeStartFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
|
||||
fn := mod.NamedFunction(fnName)
|
||||
ctx := mod.Context()
|
||||
ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||
fnType := lifetimeFuncType(ctx, ptrType)
|
||||
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
|
||||
if fn.IsNil() {
|
||||
fn = llvm.AddFunction(mod, fnName, fnType)
|
||||
}
|
||||
@@ -118,7 +98,7 @@ func getLifetimeEndFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
|
||||
fn := mod.NamedFunction(fnName)
|
||||
ctx := mod.Context()
|
||||
ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||
fnType := lifetimeFuncType(ctx, ptrType)
|
||||
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
|
||||
if fn.IsNil() {
|
||||
fn = llvm.AddFunction(mod, fnName, fnType)
|
||||
}
|
||||
@@ -238,36 +218,6 @@ func Version() int {
|
||||
return major
|
||||
}
|
||||
|
||||
// NoCaptureAttrName returns the name of the LLVM attribute kind that marks a
|
||||
// pointer parameter as guaranteed not to escape by capture.
|
||||
//
|
||||
// LLVM 21 removed the old boolean 'nocapture' enum attribute in favor of the
|
||||
// more expressive 'captures' int attribute, where 'captures(none)' (encoded
|
||||
// as the value 0) is the equivalent of the old 'nocapture'. LLVM 20 supports
|
||||
// both, but its own optimizer still emits 'nocapture', so the cutoff for
|
||||
// reading/writing the new name is LLVM 21.
|
||||
func NoCaptureAttrName() string {
|
||||
if Version() >= 21 {
|
||||
return "captures"
|
||||
}
|
||||
return "nocapture"
|
||||
}
|
||||
|
||||
// IsNoCapture reports whether attr (looked up using the kind returned by
|
||||
// NoCaptureAttrName) indicates that the pointer it is attached to does not
|
||||
// escape by capture. It returns false for a nil attribute.
|
||||
func IsNoCapture(attr llvm.Attribute) bool {
|
||||
if attr.IsNil() {
|
||||
return false
|
||||
}
|
||||
if Version() >= 21 {
|
||||
// captures(none) is encoded as the value 0; any other value permits
|
||||
// some form of capture.
|
||||
return attr.GetEnumValue() == 0
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ByteOrder returns the byte order for the given target triple. Most targets are little
|
||||
// endian, but for example MIPS can be big-endian.
|
||||
func ByteOrder(target string) binary.ByteOrder {
|
||||
|
||||
+34
-23
@@ -7,7 +7,6 @@ import (
|
||||
"go/token"
|
||||
"go/types"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"strings"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
@@ -72,20 +71,19 @@ func (b *builder) getRuntimeFunctionValue(name string, sig *types.Signature) llv
|
||||
|
||||
// createMapLookup returns the value in a map. It calls a runtime function
|
||||
// depending on the map key type to load the map value and its comma-ok value.
|
||||
func (b *builder) createMapLookup(keyType, valueType types.Type, m llvm.Value, key ssa.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
|
||||
func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
|
||||
llvmValueType := b.getLLVMType(valueType)
|
||||
|
||||
// Allocate the memory for the resulting type. Do not zero this memory: it
|
||||
// will be zeroed by the hashmap get implementation if the key is not
|
||||
// present in the map.
|
||||
result := b.createRuntimeValueResult(llvmValueType, commaOk, false, "hashmap")
|
||||
mapValueAlloca := result.valuePtr
|
||||
mapValueAlloca, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value")
|
||||
|
||||
// We need the map size (with type uintptr) to pass to the hashmap*Get
|
||||
// functions. This is necessary because those *Get functions are valid on
|
||||
// nil maps, and they'll need to zero the value pointer by that number of
|
||||
// bytes.
|
||||
mapValueSize := result.valueSize
|
||||
mapValueSize := mapValueAllocaSize
|
||||
if mapValueSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
|
||||
mapValueSize = llvm.ConstTrunc(mapValueSize, b.uintptrType)
|
||||
}
|
||||
@@ -95,46 +93,60 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m llvm.Value, k
|
||||
keyType = keyType.Underlying()
|
||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
// key is a string
|
||||
params := []llvm.Value{m, b.getValue(key, getPos(key)), mapValueAlloca, mapValueSize}
|
||||
params := []llvm.Value{m, key, mapValueAlloca, mapValueSize}
|
||||
commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "")
|
||||
} else {
|
||||
// Key stored at actual type: either binary-comparable or with
|
||||
// compiler-generated hash/equal.
|
||||
mapKey := b.getValueStorage(key, "hashmap.key")
|
||||
params := []llvm.Value{m, mapKey.ptr, mapValueAlloca, mapValueSize}
|
||||
mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||
b.CreateStore(key, mapKeyAlloca)
|
||||
params := []llvm.Value{m, mapKeyAlloca, mapValueAlloca, mapValueSize}
|
||||
fnName := "hashmapBinaryGet"
|
||||
if !hashmapIsBinaryKey(keyType) {
|
||||
fnName = "hashmapGenericGet"
|
||||
}
|
||||
commaOkValue = b.createRuntimeCall(fnName, params, "")
|
||||
b.endValueStorage(mapKey)
|
||||
b.emitLifetimeEnd(mapKeyAlloca, mapKeySize)
|
||||
}
|
||||
|
||||
// The value is set to the zero value if the key doesn't exist.
|
||||
return result.finish(b, commaOkValue, ""), nil
|
||||
// Load the resulting value from the hashmap. The value is set to the zero
|
||||
// value if the key doesn't exist in the hashmap.
|
||||
mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "")
|
||||
b.emitLifetimeEnd(mapValueAlloca, mapValueAllocaSize)
|
||||
|
||||
if commaOk {
|
||||
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false))
|
||||
tuple = b.CreateInsertValue(tuple, mapValue, 0, "")
|
||||
tuple = b.CreateInsertValue(tuple, commaOkValue, 1, "")
|
||||
return tuple, nil
|
||||
} else {
|
||||
return mapValue, nil
|
||||
}
|
||||
}
|
||||
|
||||
// createMapUpdate updates a map key to a given value, by creating an
|
||||
// appropriate runtime call.
|
||||
func (b *builder) createMapUpdate(keyType types.Type, m llvm.Value, key, value ssa.Value, pos token.Pos) {
|
||||
storedValue := b.getValueStorage(value, "hashmap.value")
|
||||
func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
|
||||
valueAlloca, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value")
|
||||
b.CreateStore(value, valueAlloca)
|
||||
keyType = keyType.Underlying()
|
||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
// key is a string
|
||||
params := []llvm.Value{m, b.getValue(key, getPos(key)), storedValue.ptr}
|
||||
params := []llvm.Value{m, key, valueAlloca}
|
||||
b.createRuntimeInvoke("hashmapStringSet", params, "")
|
||||
} else {
|
||||
// Key stored at actual type.
|
||||
keyStorage := b.getValueStorage(key, "hashmap.key")
|
||||
keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||
b.CreateStore(key, keyAlloca)
|
||||
fnName := "hashmapBinarySet"
|
||||
if !hashmapIsBinaryKey(keyType) {
|
||||
fnName = "hashmapGenericSet"
|
||||
}
|
||||
params := []llvm.Value{m, keyStorage.ptr, storedValue.ptr}
|
||||
params := []llvm.Value{m, keyAlloca, valueAlloca}
|
||||
b.createRuntimeInvoke(fnName, params, "")
|
||||
b.endValueStorage(keyStorage)
|
||||
b.emitLifetimeEnd(keyAlloca, keySize)
|
||||
}
|
||||
b.endValueStorage(storedValue)
|
||||
b.emitLifetimeEnd(valueAlloca, valueSize)
|
||||
}
|
||||
|
||||
// createMapDelete deletes a key from a map by calling the appropriate runtime
|
||||
@@ -281,15 +293,14 @@ func hashmapCanonicalTypeName(t types.Type) string {
|
||||
}
|
||||
return t.String()
|
||||
case *types.Struct:
|
||||
var s strings.Builder
|
||||
s.WriteString("struct{")
|
||||
s := "struct{"
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
if i > 0 {
|
||||
s.WriteString("; ")
|
||||
s += "; "
|
||||
}
|
||||
s.WriteString(hashmapCanonicalTypeName(t.Field(i).Type()))
|
||||
s += hashmapCanonicalTypeName(t.Field(i).Type())
|
||||
}
|
||||
return s.String() + "}"
|
||||
return s + "}"
|
||||
case *types.Array:
|
||||
return fmt.Sprintf("[%d]%s", t.Len(), hashmapCanonicalTypeName(t.Elem()))
|
||||
}
|
||||
|
||||
+2
-1
@@ -29,7 +29,8 @@ func (s *stdSizes) Alignof(T types.Type) int64 {
|
||||
// is the largest of the values unsafe.Alignof(x.f) for each
|
||||
// field f of x, but at least 1."
|
||||
max := int64(1)
|
||||
for f := range t.Fields() {
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
f := t.Field(i)
|
||||
if a := s.Alignof(f.Type()); a > max {
|
||||
max = a
|
||||
}
|
||||
|
||||
+56
-140
@@ -8,7 +8,6 @@ import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -79,27 +78,24 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
|
||||
return llvmFn.GlobalValueType(), llvmFn
|
||||
}
|
||||
|
||||
retType, indirectResult := c.hasIndirectResult(fn.Signature)
|
||||
if info.exported {
|
||||
indirectResult = false
|
||||
var retType llvm.Type
|
||||
if fn.Signature.Results() == nil {
|
||||
retType = c.ctx.VoidType()
|
||||
} else if fn.Signature.Results().Len() == 1 {
|
||||
retType = c.getLLVMType(fn.Signature.Results().At(0).Type())
|
||||
} else {
|
||||
results := make([]llvm.Type, 0, fn.Signature.Results().Len())
|
||||
for i := 0; i < fn.Signature.Results().Len(); i++ {
|
||||
results = append(results, c.getLLVMType(fn.Signature.Results().At(i).Type()))
|
||||
}
|
||||
retType = c.ctx.StructType(results, false)
|
||||
}
|
||||
|
||||
var paramInfos []paramInfo
|
||||
if indirectResult {
|
||||
paramInfos = append(paramInfos, paramInfo{
|
||||
llvmType: c.dataPtrType,
|
||||
name: "return",
|
||||
elemSize: c.targetData.TypeAllocSize(retType),
|
||||
})
|
||||
retType = c.ctx.VoidType()
|
||||
}
|
||||
for _, param := range getParams(fn.Signature) {
|
||||
paramType := c.getLLVMType(param.Type())
|
||||
if info.exported {
|
||||
paramInfos = append(paramInfos, c.expandDirectFormalParamType(paramType, param.Name(), param.Type())...)
|
||||
} else {
|
||||
paramInfos = append(paramInfos, c.expandFormalParamType(paramType, param.Name(), param.Type())...)
|
||||
}
|
||||
paramFragmentInfos := c.expandFormalParamType(paramType, param.Name(), param.Type())
|
||||
paramInfos = append(paramInfos, paramFragmentInfos...)
|
||||
}
|
||||
|
||||
// Add an extra parameter as the function context. This context is used in
|
||||
@@ -109,20 +105,12 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
|
||||
}
|
||||
|
||||
var paramTypes []llvm.Type
|
||||
hasIndirectABI := indirectResult
|
||||
for _, info := range paramInfos {
|
||||
paramTypes = append(paramTypes, info.llvmType)
|
||||
hasIndirectABI = hasIndirectABI || info.flags¶mIsIndirect != 0
|
||||
}
|
||||
|
||||
fnType := llvm.FunctionType(retType, paramTypes, info.variadic)
|
||||
llvmFn = llvm.AddFunction(c.mod, info.linkName, fnType)
|
||||
if hasIndirectABI {
|
||||
// Argument promotion only rewrites functions whose uses are all direct
|
||||
// calls. Keep an address use so LLVM cannot reconstruct the large
|
||||
// aggregate signature that this ABI exists to avoid.
|
||||
llvmutil.AppendToGlobal(c.mod, "llvm.used", llvmFn)
|
||||
}
|
||||
if strings.HasPrefix(c.Triple, "wasm") {
|
||||
// C functions without prototypes like this:
|
||||
// void foo();
|
||||
@@ -152,7 +140,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
|
||||
// not.
|
||||
// (It may be safe to add the nocapture parameter to the context
|
||||
// parameter, but I'd like to stay on the safe side here).
|
||||
nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0)
|
||||
nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)
|
||||
llvmFn.AddAttributeAtIndex(i+1, nocapture)
|
||||
}
|
||||
if paramInfo.flags¶mIsReadonly != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind {
|
||||
@@ -171,9 +159,9 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
|
||||
// Mark it as noreturn so LLVM can optimize away code.
|
||||
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0))
|
||||
case "internal/abi.NoEscape":
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
case "machine.keepAliveNoEscape", "machine.unsafeNoEscape":
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
case "runtime.alloc", "runtime.alloc_noheap", "runtime.alloc_zero":
|
||||
// Tell the optimizer that runtime.alloc is an allocator, meaning that it
|
||||
// returns values that are never null and never alias to an existing value.
|
||||
@@ -195,44 +183,44 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
|
||||
case "runtime.sliceAppend":
|
||||
// Appending a slice will only read the to-be-appended slice, it won't
|
||||
// be modified.
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
case "runtime.stringFromBytes":
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
case "runtime.stringFromRunes":
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
case "runtime.hashmapSet":
|
||||
// The key (param 2) and value (param 3) pointers are only read via
|
||||
// memcpy/hash/equal and are never captured. The indirect calls
|
||||
// through m.keyHash and m.keyEqual function pointers prevent LLVM's
|
||||
// functionattrs pass from inferring this automatically.
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
case "runtime.hashmapGet":
|
||||
// The key (param 2) is read-only and never captured.
|
||||
// The value (param 3) is written to (receives the result) but never captured.
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
case "runtime.hashmapDelete":
|
||||
// The key (param 2) is read-only and never captured.
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
case "runtime.hashmapGenericSet":
|
||||
// Same as hashmapBinarySet: key (param 2) and value (param 3) are
|
||||
// not captured.
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
case "runtime.hashmapGenericGet":
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
case "runtime.hashmapGenericDelete":
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
case "runtime.trackPointer":
|
||||
// This function is necessary for tracking pointers on the stack in a
|
||||
// portable way (see gc_stack_portable.go). Indicate to the optimizer
|
||||
// that the only thing we'll do is read the pointer.
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
case "__mulsi3", "__divmodsi4", "__udivmodsi4":
|
||||
if strings.Split(c.Triple, "-")[0] == "avr" {
|
||||
@@ -267,7 +255,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
|
||||
|
||||
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-name", info.wasmName))
|
||||
}
|
||||
nocaptureKind := llvm.AttributeKindID(llvmutil.NoCaptureAttrName())
|
||||
nocaptureKind := llvm.AttributeKindID("nocapture")
|
||||
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
|
||||
for i, typ := range paramTypes {
|
||||
if typ.TypeKind() == llvm.PointerTypeKind {
|
||||
@@ -331,7 +319,7 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
|
||||
}
|
||||
info := functionInfo{
|
||||
// Pick the default linkName.
|
||||
linkName: c.canonicalFunctionName(f),
|
||||
linkName: f.RelString(nil),
|
||||
}
|
||||
|
||||
// Check for a few runtime functions that are treated specially.
|
||||
@@ -358,18 +346,6 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
|
||||
return info
|
||||
}
|
||||
|
||||
func (c *compilerContext) canonicalFunctionName(f *ssa.Function) string {
|
||||
typeArgs := f.TypeArgs()
|
||||
if len(typeArgs) == 0 {
|
||||
return f.RelString(nil)
|
||||
}
|
||||
parts := make([]string, len(typeArgs))
|
||||
for i, ta := range typeArgs {
|
||||
parts[i], _ = c.getTypeCodeName(ta)
|
||||
}
|
||||
return f.Origin().RelString(nil) + "[" + strings.Join(parts, ",") + "]"
|
||||
}
|
||||
|
||||
// parsePragmas is used by getFunctionInfo to parse function pragmas such as
|
||||
// //export or //go:noinline.
|
||||
func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
|
||||
@@ -396,51 +372,6 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
|
||||
}
|
||||
}
|
||||
|
||||
// Also scan file-level //go:linkname directives. These appear as
|
||||
// free-standing comments in *ast.File.Comments (not attached to any
|
||||
// declaration), and are used by modern golang.org/x/sys/unix and others.
|
||||
// Function-attached directives (above) take precedence — we only add
|
||||
// file-level ones if no doc-comment linkname was found for this function.
|
||||
//
|
||||
// TODO: the hasUnsafeImport gate enforced downstream (see the
|
||||
// //go:linkname case below) is package-level. gc enforces it per
|
||||
// file, on the file containing the directive. For file-level
|
||||
// linknames this is more important than for function-attached ones,
|
||||
// because the directive can live in a file separate from the
|
||||
// function. A stricter implementation would check whether the file
|
||||
// returned by fileForFunc imports "unsafe", not whether any file in
|
||||
// the package does.
|
||||
hasFunctionLinkname := false
|
||||
for _, comment := range pragmas {
|
||||
if strings.HasPrefix(comment.Text, "//go:linkname ") {
|
||||
parts := strings.Fields(comment.Text)
|
||||
if len(parts) == 3 && parts[1] == f.Name() {
|
||||
hasFunctionLinkname = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasFunctionLinkname {
|
||||
if file := c.fileForFunc(f); file != nil {
|
||||
for _, group := range file.Comments {
|
||||
// Skip the function's own doc comment — already handled above.
|
||||
if decl, ok := syntax.(*ast.FuncDecl); ok && group == decl.Doc {
|
||||
continue
|
||||
}
|
||||
for _, comment := range group.List {
|
||||
if !strings.HasPrefix(comment.Text, "//go:linkname ") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(comment.Text)
|
||||
if len(parts) != 3 || parts[1] != f.Name() {
|
||||
continue
|
||||
}
|
||||
pragmas = append(pragmas, comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse each pragma.
|
||||
for _, comment := range pragmas {
|
||||
parts := strings.Fields(comment.Text)
|
||||
@@ -458,7 +389,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
|
||||
info.wasmName = info.linkName
|
||||
info.exported = true
|
||||
case "//go:interrupt":
|
||||
if slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) {
|
||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.interrupt = true
|
||||
}
|
||||
case "//go:wasm-module":
|
||||
@@ -512,7 +443,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
|
||||
info.inline = inlineHint
|
||||
case "//go:noinline":
|
||||
info.inline = inlineNone
|
||||
case "//go:linkname", "//go:linknamestd":
|
||||
case "//go:linkname":
|
||||
if len(parts) != 3 || parts[1] != f.Name() {
|
||||
continue
|
||||
}
|
||||
@@ -520,14 +451,14 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
|
||||
// This is a slightly looser requirement than what gc uses: gc
|
||||
// requires the file to import "unsafe", not the package as a
|
||||
// whole.
|
||||
if slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) {
|
||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.linkName = parts[2]
|
||||
}
|
||||
case "//go:section":
|
||||
// Only enable go:section when the package imports "unsafe".
|
||||
// go:section also implies go:noinline since inlining could
|
||||
// move the code to a different section than that requested.
|
||||
if len(parts) == 2 && slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) {
|
||||
if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.section = parts[1]
|
||||
info.inline = inlineNone
|
||||
}
|
||||
@@ -536,7 +467,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
|
||||
// runtime functions.
|
||||
// This is somewhat dangerous and thus only imported in packages
|
||||
// that import unsafe.
|
||||
if slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) {
|
||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.nobounds = true
|
||||
}
|
||||
case "//go:noescape":
|
||||
@@ -636,8 +567,8 @@ func (c *compilerContext) isValidWasmType(typ types.Type, site wasmSite) bool {
|
||||
hasHostLayout = false // package structs added in go1.23
|
||||
}
|
||||
}
|
||||
for field := range typ.Fields() {
|
||||
ftyp := field.Type()
|
||||
for i := 0; i < typ.NumFields(); i++ {
|
||||
ftyp := typ.Field(i).Type()
|
||||
if types.Unalias(ftyp).String() == "structs.HostLayout" {
|
||||
hasHostLayout = true
|
||||
continue
|
||||
@@ -669,8 +600,8 @@ func getParams(sig *types.Signature) []*types.Var {
|
||||
if sig.Recv() != nil {
|
||||
params = append(params, sig.Recv())
|
||||
}
|
||||
for v := range sig.Params().Variables() {
|
||||
params = append(params, v)
|
||||
for i := 0; i < sig.Params().Len(); i++ {
|
||||
params = append(params, sig.Params().At(i))
|
||||
}
|
||||
return params
|
||||
}
|
||||
@@ -735,34 +666,6 @@ type globalInfo struct {
|
||||
section string // go:section
|
||||
}
|
||||
|
||||
// fileForFunc returns the *ast.File that contains the declaration of f, or
|
||||
// nil if it cannot be determined. File-level pragmas are only consulted for
|
||||
// functions in the package currently being compiled — functions imported from
|
||||
// other packages have their file-level pragmas processed when those packages
|
||||
// are compiled.
|
||||
func (c *compilerContext) fileForFunc(f *ssa.Function) *ast.File {
|
||||
if c.loaderPkg == nil || f.Pkg == nil || f.Pkg.Pkg != c.loaderPkg.Pkg {
|
||||
return nil
|
||||
}
|
||||
syntax := f.Syntax()
|
||||
if f.Origin() != nil {
|
||||
syntax = f.Origin().Syntax()
|
||||
}
|
||||
if syntax == nil {
|
||||
return nil
|
||||
}
|
||||
pos := syntax.Pos()
|
||||
if !pos.IsValid() {
|
||||
return nil
|
||||
}
|
||||
for _, file := range c.loaderPkg.Files {
|
||||
if file.FileStart <= pos && pos < file.FileEnd {
|
||||
return file
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadASTComments loads comments on globals from the AST, for use later in the
|
||||
// program. In particular, they are required for //go:extern pragmas on globals.
|
||||
func (c *compilerContext) loadASTComments(pkg *loader.Package) {
|
||||
@@ -801,7 +704,10 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
|
||||
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
|
||||
|
||||
// Set alignment from the //go:align comment.
|
||||
alignment := max(info.align, c.targetData.ABITypeAlignment(llvmType))
|
||||
alignment := c.targetData.ABITypeAlignment(llvmType)
|
||||
if info.align > alignment {
|
||||
alignment = info.align
|
||||
}
|
||||
if alignment <= 0 || alignment&(alignment-1) != 0 {
|
||||
// Check for power-of-two (or 0).
|
||||
// See: https://stackoverflow.com/a/108360
|
||||
@@ -875,7 +781,7 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup, c *compilerContext,
|
||||
// This is a slightly looser requirement than what gc uses: gc
|
||||
// requires the file to import "unsafe", not the package as a
|
||||
// whole.
|
||||
if slices.Contains(g.Pkg.Pkg.Imports(), types.Unsafe) {
|
||||
if hasUnsafeImport(g.Pkg.Pkg) {
|
||||
info.linkName = parts[2]
|
||||
}
|
||||
}
|
||||
@@ -891,3 +797,13 @@ func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
// Return true if this package imports "unsafe", false otherwise.
|
||||
func hasUnsafeImport(pkg *types.Package) bool {
|
||||
for _, imp := range pkg.Imports() {
|
||||
if imp == types.Unsafe {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
+27
-32
@@ -26,25 +26,24 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
argTypes := []llvm.Type{b.uintptrType}
|
||||
// Constraints will look something like:
|
||||
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
|
||||
var constraints strings.Builder
|
||||
constraints.WriteString("={rax},0")
|
||||
constraints := "={rax},0"
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints.WriteString("," + [...]string{
|
||||
constraints += "," + [...]string{
|
||||
"{rdi}",
|
||||
"{rsi}",
|
||||
"{rdx}",
|
||||
"{r10}",
|
||||
"{r8}",
|
||||
"{r9}",
|
||||
}[i])
|
||||
}[i]
|
||||
llvmValue := b.getValue(arg, getPos(call))
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
// rcx and r11 are clobbered by the syscall, so make sure they are not used
|
||||
constraints.WriteString(",~{rcx},~{r11}")
|
||||
constraints += ",~{rcx},~{r11}"
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "syscall", constraints.String(), true, false, llvm.InlineAsmDialectIntel, false)
|
||||
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel, false)
|
||||
return b.CreateCall(fnType, target, args, ""), nil
|
||||
|
||||
case b.GOARCH == "386" && b.GOOS == "linux":
|
||||
@@ -56,23 +55,22 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
argTypes := []llvm.Type{b.uintptrType}
|
||||
// Constraints will look something like:
|
||||
// "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}"
|
||||
var constraints strings.Builder
|
||||
constraints.WriteString("={eax},0")
|
||||
constraints := "={eax},0"
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints.WriteString("," + [...]string{
|
||||
constraints += "," + [...]string{
|
||||
"{ebx}",
|
||||
"{ecx}",
|
||||
"{edx}",
|
||||
"{esi}",
|
||||
"{edi}",
|
||||
"{ebp}",
|
||||
}[i])
|
||||
}[i]
|
||||
llvmValue := b.getValue(arg, getPos(call))
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "int 0x80", constraints.String(), true, false, llvm.InlineAsmDialectIntel, false)
|
||||
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel, false)
|
||||
return b.CreateCall(fnType, target, args, ""), nil
|
||||
|
||||
case b.GOARCH == "arm" && b.GOOS == "linux":
|
||||
@@ -90,10 +88,9 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
argTypes := []llvm.Type{}
|
||||
// Constraints will look something like:
|
||||
// ={r0},0,{r1},{r2},{r7},~{r3}
|
||||
var constraints strings.Builder
|
||||
constraints.WriteString("={r0}")
|
||||
constraints := "={r0}"
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints.WriteString("," + [...]string{
|
||||
constraints += "," + [...]string{
|
||||
"0", // tie to output
|
||||
"{r1}",
|
||||
"{r2}",
|
||||
@@ -101,20 +98,20 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
"{r4}",
|
||||
"{r5}",
|
||||
"{r6}",
|
||||
}[i])
|
||||
}[i]
|
||||
llvmValue := b.getValue(arg, getPos(call))
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
args = append(args, num)
|
||||
argTypes = append(argTypes, b.uintptrType)
|
||||
constraints.WriteString(",{r7}") // syscall number
|
||||
constraints += ",{r7}" // syscall number
|
||||
for i := len(call.Args) - 1; i < 4; i++ {
|
||||
// r0-r3 get clobbered after the syscall returns
|
||||
constraints.WriteString(",~{r" + strconv.Itoa(i) + "}")
|
||||
constraints += ",~{r" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints.String(), true, false, 0, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
|
||||
return b.CreateCall(fnType, target, args, ""), nil
|
||||
|
||||
case b.GOARCH == "arm64" && b.GOOS == "linux":
|
||||
@@ -123,32 +120,31 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
argTypes := []llvm.Type{}
|
||||
// Constraints will look something like:
|
||||
// ={x0},0,{x1},{x2},{x8},~{x3},~{x4},~{x5},~{x6},~{x7},~{x16},~{x17}
|
||||
var constraints strings.Builder
|
||||
constraints.WriteString("={x0}")
|
||||
constraints := "={x0}"
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints.WriteString("," + [...]string{
|
||||
constraints += "," + [...]string{
|
||||
"0", // tie to output
|
||||
"{x1}",
|
||||
"{x2}",
|
||||
"{x3}",
|
||||
"{x4}",
|
||||
"{x5}",
|
||||
}[i])
|
||||
}[i]
|
||||
llvmValue := b.getValue(arg, getPos(call))
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
args = append(args, num)
|
||||
argTypes = append(argTypes, b.uintptrType)
|
||||
constraints.WriteString(",{x8}") // syscall number
|
||||
constraints += ",{x8}" // syscall number
|
||||
for i := len(call.Args) - 1; i < 8; i++ {
|
||||
// x0-x7 may get clobbered during the syscall following the aarch64
|
||||
// calling convention.
|
||||
constraints.WriteString(",~{x" + strconv.Itoa(i) + "}")
|
||||
constraints += ",~{x" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
constraints.WriteString(",~{x16},~{x17}") // scratch registers
|
||||
constraints += ",~{x16},~{x17}" // scratch registers
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints.String(), true, false, 0, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
|
||||
return b.CreateCall(fnType, target, args, ""), nil
|
||||
|
||||
case (b.GOARCH == "mips" || b.GOARCH == "mipsle") && b.GOOS == "linux":
|
||||
@@ -167,8 +163,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
// faster and smaller code.
|
||||
args := []llvm.Value{num}
|
||||
argTypes := []llvm.Type{b.uintptrType}
|
||||
var constraints strings.Builder
|
||||
constraints.WriteString("={$2},={$7},0")
|
||||
constraints := "={$2},={$7},0"
|
||||
syscallParams := call.Args[1:]
|
||||
if len(syscallParams) > 7 {
|
||||
// There is one syscall that uses 7 parameters: sync_file_range.
|
||||
@@ -177,7 +172,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
syscallParams = syscallParams[:7]
|
||||
}
|
||||
for i, arg := range syscallParams {
|
||||
constraints.WriteString("," + [...]string{
|
||||
constraints += "," + [...]string{
|
||||
"{$4}", // arg1
|
||||
"{$5}", // arg2
|
||||
"{$6}", // arg3
|
||||
@@ -185,7 +180,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
"r", // arg5 on the stack
|
||||
"r", // arg6 on the stack
|
||||
"r", // arg7 on the stack
|
||||
}[i])
|
||||
}[i]
|
||||
llvmValue := b.getValue(arg, getPos(call))
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
@@ -226,10 +221,10 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
"addu $$sp, $$sp, 32\n" +
|
||||
".set at\n"
|
||||
}
|
||||
constraints.WriteString(",~{$3},~{$4},~{$5},~{$6},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$24},~{$25},~{hi},~{lo},~{memory}")
|
||||
constraints += ",~{$3},~{$4},~{$5},~{$6},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$24},~{$25},~{hi},~{lo},~{memory}"
|
||||
returnType := b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false)
|
||||
fnType := llvm.FunctionType(returnType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints.String(), true, true, 0, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints, true, true, 0, false)
|
||||
call := b.CreateCall(fnType, target, args, "")
|
||||
resultCode := b.CreateExtractValue(call, 0, "") // r2
|
||||
errorFlag := b.CreateExtractValue(call, 1, "") // r7
|
||||
|
||||
Vendored
-5
@@ -66,11 +66,6 @@ func complexSub(x, y complex64) complex64 {
|
||||
return x - y
|
||||
}
|
||||
|
||||
func shiftNested(x uint64) uint64 {
|
||||
k := 3
|
||||
return x >> (1 << k) // https://github.com/tinygo-org/tinygo/issues/5496
|
||||
}
|
||||
|
||||
func complexMul(x, y complex64) complex64 {
|
||||
return x * y
|
||||
}
|
||||
|
||||
Vendored
-7
@@ -176,13 +176,6 @@ entry:
|
||||
ret { float, float } %3
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i64 @main.shiftNested(i64 %x, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%0 = lshr i64 %x, 8
|
||||
ret i64 %0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
|
||||
Vendored
+16
-16
@@ -19,33 +19,33 @@ define hidden void @main.chanIntSend(ptr dereferenceable_or_null(36) %ch, ptr %c
|
||||
entry:
|
||||
%chan.op = alloca %runtime.channelOp, align 8
|
||||
%chan.value = alloca i32, align 4
|
||||
call void @llvm.lifetime.start.p0(ptr nonnull %chan.value)
|
||||
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value)
|
||||
store i32 3, ptr %chan.value, align 4
|
||||
call void @llvm.lifetime.start.p0(ptr nonnull %chan.op)
|
||||
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op)
|
||||
call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #3
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %chan.op)
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %chan.value)
|
||||
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op)
|
||||
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
|
||||
declare void @llvm.lifetime.start.p0(ptr nocapture) #2
|
||||
declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #2
|
||||
|
||||
declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
|
||||
declare void @llvm.lifetime.end.p0(ptr nocapture) #2
|
||||
declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #2
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%chan.op = alloca %runtime.channelOp, align 8
|
||||
%chan.value = alloca i32, align 4
|
||||
call void @llvm.lifetime.start.p0(ptr nonnull %chan.value)
|
||||
call void @llvm.lifetime.start.p0(ptr nonnull %chan.op)
|
||||
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value)
|
||||
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op)
|
||||
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #3
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %chan.value)
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %chan.op)
|
||||
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value)
|
||||
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op)
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -55,9 +55,9 @@ declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferen
|
||||
define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%chan.op = alloca %runtime.channelOp, align 8
|
||||
call void @llvm.lifetime.start.p0(ptr nonnull %chan.op)
|
||||
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op)
|
||||
call void @runtime.chanSend(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #3
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %chan.op)
|
||||
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op)
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -65,9 +65,9 @@ entry:
|
||||
define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%chan.op = alloca %runtime.channelOp, align 8
|
||||
call void @llvm.lifetime.start.p0(ptr nonnull %chan.op)
|
||||
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op)
|
||||
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #3
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %chan.op)
|
||||
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op)
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ entry:
|
||||
%select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
|
||||
%select.send.value = alloca i32, align 4
|
||||
store i32 1, ptr %select.send.value, align 4
|
||||
call void @llvm.lifetime.start.p0(ptr nonnull %select.states.alloca)
|
||||
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %select.states.alloca)
|
||||
store ptr %ch1, ptr %select.states.alloca, align 4
|
||||
%select.states.alloca.repack1 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 4
|
||||
store ptr %select.send.value, ptr %select.states.alloca.repack1, align 4
|
||||
@@ -86,7 +86,7 @@ entry:
|
||||
%.repack3 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 12
|
||||
store ptr null, ptr %.repack3, align 4
|
||||
%select.result = call { i32, i1 } @runtime.chanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr null, i32 0, i32 0, ptr undef) #3
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %select.states.alloca)
|
||||
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %select.states.alloca)
|
||||
%1 = extractvalue { i32, i1 } %select.result, 0
|
||||
%2 = icmp eq i32 %1, 0
|
||||
br i1 %2, label %select.done, label %select.next
|
||||
|
||||
+2
-2
@@ -278,7 +278,7 @@ entry:
|
||||
|
||||
for.body: ; preds = %for.body, %entry
|
||||
%defer.next = load ptr, ptr %deferPtr, align 4
|
||||
%defer.alloc.call = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 135 to ptr), ptr undef) #4
|
||||
%defer.alloc.call = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #4
|
||||
store i32 0, ptr %defer.alloc.call, align 4
|
||||
%defer.alloc.call.repack1 = getelementptr inbounds nuw i8, ptr %defer.alloc.call, i32 4
|
||||
store ptr %defer.next, ptr %defer.alloc.call.repack1, align 4
|
||||
@@ -330,7 +330,7 @@ for.loop: ; preds = %for.body, %entry
|
||||
|
||||
for.body: ; preds = %for.loop
|
||||
%defer.next = load ptr, ptr %deferPtr, align 4
|
||||
%defer.alloc.call = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 135 to ptr), ptr undef) #4
|
||||
%defer.alloc.call = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #4
|
||||
store i32 0, ptr %defer.alloc.call, align 4
|
||||
%defer.alloc.call.repack13 = getelementptr inbounds nuw i8, ptr %defer.alloc.call, i32 4
|
||||
store ptr %defer.next, ptr %defer.alloc.call.repack13, align 4
|
||||
|
||||
Vendored
+1
-1
@@ -131,7 +131,7 @@ entry:
|
||||
define hidden %runtime._interface @main.makeInterface(double %v.r, double %v.i, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%0 = call align 8 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
%0 = call align 8 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #3
|
||||
store double %v.r, ptr %0, align 8
|
||||
%.repack1 = getelementptr inbounds nuw i8, ptr %0, i32 8
|
||||
|
||||
Vendored
-37
@@ -19,49 +19,12 @@ func Add[T Coord](a, b Point[T]) Point[T] {
|
||||
}
|
||||
}
|
||||
|
||||
func aliasSize[F float32 | float64]() uintptr {
|
||||
return unsafe.Sizeof(F(0))
|
||||
}
|
||||
|
||||
func aliasSize32() uintptr {
|
||||
type F = float32
|
||||
return aliasSize[F]()
|
||||
}
|
||||
|
||||
func aliasSize64() uintptr {
|
||||
type F = float64
|
||||
return aliasSize[F]()
|
||||
}
|
||||
|
||||
type aliasMethodResult[F float32 | float64] struct {
|
||||
Value F
|
||||
}
|
||||
|
||||
type aliasMethodValue[F float32 | float64] struct{}
|
||||
|
||||
func (aliasMethodValue[F]) Get() aliasMethodResult[F] {
|
||||
return aliasMethodResult[F]{}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var af, bf Point[float32]
|
||||
Add(af, bf)
|
||||
|
||||
var ai, bi Point[int]
|
||||
Add(ai, bi)
|
||||
|
||||
checkSize(aliasSize32())
|
||||
checkSize(aliasSize64())
|
||||
}
|
||||
|
||||
func checkSize(uintptr)
|
||||
|
||||
func checkBool(bool)
|
||||
|
||||
func aliasMethod32(x any) {
|
||||
type F = float32
|
||||
_, ok := x.(interface {
|
||||
Get() aliasMethodResult[F]
|
||||
})
|
||||
checkBool(ok)
|
||||
}
|
||||
|
||||
Vendored
+21
-70
@@ -14,62 +14,32 @@ entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i32 @main.aliasSize32(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%0 = call i32 @"main.aliasSize[basic:float32]"(ptr undef)
|
||||
ret i32 %0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define linkonce_odr hidden i32 @"main.aliasSize[basic:float32]"(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
ret i32 4
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i32 @main.aliasSize64(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%0 = call i32 @"main.aliasSize[basic:float64]"(ptr undef)
|
||||
ret i32 %0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define linkonce_odr hidden i32 @"main.aliasSize[basic:float64]"(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
ret i32 8
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.main(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%0 = call %"main.Point[float32]" @"main.Add[basic:float32]"(float 0.000000e+00, float 0.000000e+00, float 0.000000e+00, float 0.000000e+00, ptr undef)
|
||||
%1 = call %"main.Point[int]" @"main.Add[basic:int]"(i32 0, i32 0, i32 0, i32 0, ptr undef)
|
||||
%2 = call i32 @main.aliasSize32(ptr undef)
|
||||
call void @main.checkSize(i32 %2, ptr undef) #4
|
||||
%3 = call i32 @main.aliasSize64(ptr undef)
|
||||
call void @main.checkSize(i32 %3, ptr undef) #4
|
||||
%0 = call %"main.Point[float32]" @"main.Add[float32]"(float 0.000000e+00, float 0.000000e+00, float 0.000000e+00, float 0.000000e+00, ptr undef)
|
||||
%1 = call %"main.Point[int]" @"main.Add[int]"(i32 0, i32 0, i32 0, i32 0, ptr undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define linkonce_odr hidden %"main.Point[float32]" @"main.Add[basic:float32]"(float %a.X, float %a.Y, float %b.X, float %b.Y, ptr %context) unnamed_addr #1 {
|
||||
define linkonce_odr hidden %"main.Point[float32]" @"main.Add[float32]"(float %a.X, float %a.Y, float %b.X, float %b.Y, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4
|
||||
call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #4
|
||||
%a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #3
|
||||
store float %a.X, ptr %a, align 4
|
||||
%a.repack5 = getelementptr inbounds nuw i8, ptr %a, i32 4
|
||||
store float %a.Y, ptr %a.repack5, align 4
|
||||
%b = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4
|
||||
call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #4
|
||||
%b = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #3
|
||||
store float %b.X, ptr %b, align 4
|
||||
%b.repack7 = getelementptr inbounds nuw i8, ptr %b, i32 4
|
||||
store float %b.Y, ptr %b.repack7, align 4
|
||||
call void @main.checkSize(i32 4, ptr undef) #4
|
||||
call void @main.checkSize(i32 8, ptr undef) #4
|
||||
%complit = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4
|
||||
call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #4
|
||||
call void @main.checkSize(i32 4, ptr undef) #3
|
||||
call void @main.checkSize(i32 8, ptr undef) #3
|
||||
%complit = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #3
|
||||
br i1 false, label %deref.throw, label %deref.next
|
||||
|
||||
deref.next: ; preds = %entry
|
||||
@@ -116,23 +86,23 @@ declare void @main.checkSize(i32, ptr) #0
|
||||
declare void @runtime.nilPanic(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define linkonce_odr hidden %"main.Point[int]" @"main.Add[basic:int]"(i32 %a.X, i32 %a.Y, i32 %b.X, i32 %b.Y, ptr %context) unnamed_addr #1 {
|
||||
define linkonce_odr hidden %"main.Point[int]" @"main.Add[int]"(i32 %a.X, i32 %a.Y, i32 %b.X, i32 %b.Y, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4
|
||||
call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #4
|
||||
%a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #3
|
||||
store i32 %a.X, ptr %a, align 4
|
||||
%a.repack5 = getelementptr inbounds nuw i8, ptr %a, i32 4
|
||||
store i32 %a.Y, ptr %a.repack5, align 4
|
||||
%b = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4
|
||||
call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #4
|
||||
%b = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #3
|
||||
store i32 %b.X, ptr %b, align 4
|
||||
%b.repack7 = getelementptr inbounds nuw i8, ptr %b, i32 4
|
||||
store i32 %b.Y, ptr %b.repack7, align 4
|
||||
call void @main.checkSize(i32 4, ptr undef) #4
|
||||
call void @main.checkSize(i32 8, ptr undef) #4
|
||||
%complit = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4
|
||||
call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #4
|
||||
call void @main.checkSize(i32 4, ptr undef) #3
|
||||
call void @main.checkSize(i32 8, ptr undef) #3
|
||||
%complit = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #3
|
||||
br i1 false, label %deref.throw, label %deref.next
|
||||
|
||||
deref.next: ; preds = %entry
|
||||
@@ -171,26 +141,7 @@ deref.throw: ; preds = %store.next, %deref.
|
||||
unreachable
|
||||
}
|
||||
|
||||
declare void @main.checkBool(i1, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.aliasMethod32(ptr %x.typecode, ptr %x.value, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%0 = call i1 @"interface:{Get:func:{}{named:main.aliasMethodResult[basic:float32]}}.$typeassert"(ptr %x.typecode) #4
|
||||
br i1 %0, label %typeassert.ok, label %typeassert.next
|
||||
|
||||
typeassert.next: ; preds = %typeassert.ok, %entry
|
||||
call void @main.checkBool(i1 %0, ptr undef) #4
|
||||
ret void
|
||||
|
||||
typeassert.ok: ; preds = %entry
|
||||
br label %typeassert.next
|
||||
}
|
||||
|
||||
declare i1 @"interface:{Get:func:{}{named:main.aliasMethodResult[basic:float32]}}.$typeassert"(ptr) #3
|
||||
|
||||
attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||
attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||
attributes #2 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||
attributes #3 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.Get:func:{}{named:main.aliasMethodResult[basic:float32]}" }
|
||||
attributes #4 = { nounwind }
|
||||
attributes #3 = { nounwind }
|
||||
|
||||
Vendored
-24
@@ -1,24 +0,0 @@
|
||||
package main
|
||||
|
||||
// genericMethod has both a regular method and a "generic method": a method
|
||||
// with its own type parameter, independent of any type parameter on the
|
||||
// receiver. This is a Go 1.27 feature (see math/rand/v2.Rand.N for a
|
||||
// real-world example). Boxing such a value into an interface must not try to
|
||||
// encode the generic method's type-parameterized signature into a type code.
|
||||
type genericMethod struct{}
|
||||
|
||||
func (t genericMethod) Regular(n int) int { return n }
|
||||
|
||||
func (t genericMethod) GenericParam[X int | int64](n X) X { return n }
|
||||
|
||||
// onlyGenericMethod's only method is generic, so its runtime type must have
|
||||
// an empty method set (hasMethodSet must become false, not just numMethods).
|
||||
type onlyGenericMethod struct{}
|
||||
|
||||
func (t onlyGenericMethod) GenericParam[X int | int64](n X) X { return n }
|
||||
|
||||
func useGenericMethods() (any, any) {
|
||||
var g genericMethod
|
||||
var o onlyGenericMethod
|
||||
return any(g), any(o)
|
||||
}
|
||||
Vendored
-67
@@ -1,67 +0,0 @@
|
||||
; ModuleID = 'go1.27.go'
|
||||
source_filename = "go1.27.go"
|
||||
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20"
|
||||
target triple = "wasm32-unknown-wasi"
|
||||
|
||||
%runtime.structField = type { ptr, ptr }
|
||||
%runtime._interface = type { ptr, ptr }
|
||||
|
||||
@"reflect/types.signature:Regular:func:{basic:int}{basic:int}" = linkonce_odr constant i8 0, align 1
|
||||
@"reflect/types.type:named:main.genericMethod" = linkonce_odr constant { ptr, i8, i16, ptr, ptr, ptr, { i32, [1 x ptr] }, [19 x i8] } { ptr @"named:main.genericMethod$methodset", i8 122, i16 -32767, ptr getelementptr ({ ptr, i8, i16, ptr, { i32, [1 x ptr] } }, ptr @"reflect/types.type:pointer:named:main.genericMethod", i32 0, i32 1), ptr @"reflect/types.type:struct:{}", ptr @"reflect/types.type.pkgpath:main", { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:Regular:func:{basic:int}{basic:int}"] }, [19 x i8] c"main.genericMethod\00" }, align 4
|
||||
@"reflect/types.type.pkgpath:main" = linkonce_odr unnamed_addr constant [5 x i8] c"main\00", align 1
|
||||
@"reflect/types.type:pointer:named:main.genericMethod" = linkonce_odr constant { ptr, i8, i16, ptr, { i32, [1 x ptr] } } { ptr @"pointer:named:main.genericMethod$methodset", i8 -43, i16 -32767, ptr getelementptr ({ ptr, i8, i16, ptr, ptr, ptr, { i32, [1 x ptr] }, [19 x i8] }, ptr @"reflect/types.type:named:main.genericMethod", i32 0, i32 1), { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:Regular:func:{basic:int}{basic:int}"] } }, align 4
|
||||
@"reflect/methods.Regular:func:{basic:int}{basic:int}" = linkonce_odr constant i8 0, align 1
|
||||
@"main$string" = internal unnamed_addr constant [18 x i8] c"main.genericMethod", align 1
|
||||
@"main$string.1" = internal unnamed_addr constant [7 x i8] c"Regular", align 1
|
||||
@"pointer:named:main.genericMethod$methodset" = linkonce_odr unnamed_addr constant { i32, [1 x ptr], { ptr } } { i32 1, [1 x ptr] [ptr @"reflect/methods.Regular:func:{basic:int}{basic:int}"], { ptr } { ptr @"(*main.genericMethod).Regular" } }
|
||||
@"reflect/types.type:struct:{}" = linkonce_odr constant { i8, i16, ptr, ptr, i32, i16, [0 x %runtime.structField] } { i8 90, i16 0, ptr @"reflect/types.type:pointer:struct:{}", ptr @"reflect/types.type.pkgpath.empty", i32 0, i16 0, [0 x %runtime.structField] zeroinitializer }, align 4
|
||||
@"reflect/types.type.pkgpath.empty" = linkonce_odr unnamed_addr constant [1 x i8] zeroinitializer, align 1
|
||||
@"reflect/types.type:pointer:struct:{}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:struct:{}" }, align 4
|
||||
@"named:main.genericMethod$methodset" = linkonce_odr unnamed_addr constant { i32, [1 x ptr], { ptr } } { i32 1, [1 x ptr] [ptr @"reflect/methods.Regular:func:{basic:int}{basic:int}"], { ptr } { ptr @"(main.genericMethod).Regular$invoke" } }
|
||||
@"reflect/types.type:named:main.onlyGenericMethod" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, [23 x i8] } { i8 122, i16 0, ptr @"reflect/types.type:pointer:named:main.onlyGenericMethod", ptr @"reflect/types.type:struct:{}", ptr @"reflect/types.type.pkgpath:main", [23 x i8] c"main.onlyGenericMethod\00" }, align 4
|
||||
@"reflect/types.type:pointer:named:main.onlyGenericMethod" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:named:main.onlyGenericMethod" }, align 4
|
||||
|
||||
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: nounwind
|
||||
define hidden i32 @"(main.genericMethod).Regular"(i32 %n, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
ret i32 %n
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden { %runtime._interface, %runtime._interface } @main.useGenericMethods(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
call void @runtime.trackPointer(ptr nonnull getelementptr inbounds nuw (i8, ptr @"reflect/types.type:named:main.genericMethod", i32 4), ptr nonnull %stackalloc, ptr undef) #2
|
||||
call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #2
|
||||
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:named:main.onlyGenericMethod", ptr nonnull %stackalloc, ptr undef) #2
|
||||
call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #2
|
||||
ret { %runtime._interface, %runtime._interface } { %runtime._interface { ptr getelementptr ({ ptr, i8, i16, ptr, ptr, ptr, { i32, [1 x ptr] }, [19 x i8] }, ptr @"reflect/types.type:named:main.genericMethod", i32 0, i32 1), ptr null }, %runtime._interface { ptr @"reflect/types.type:named:main.onlyGenericMethod", ptr null } }
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define linkonce_odr hidden i32 @"(*main.genericMethod).Regular"(ptr %t, i32 %n, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
call void @runtime.trackPointer(ptr %t, ptr nonnull %stackalloc, ptr undef) #2
|
||||
%0 = call i32 @"(main.genericMethod).Regular"(i32 %n, ptr undef)
|
||||
ret i32 %0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define linkonce_odr i32 @"(main.genericMethod).Regular$invoke"(ptr %0, i32 %1, ptr %2) unnamed_addr #1 {
|
||||
entry:
|
||||
%ret = call i32 @"(main.genericMethod).Regular"(i32 %1, ptr %2)
|
||||
ret i32 %ret
|
||||
}
|
||||
|
||||
attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||
attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||
attributes #2 = { nounwind }
|
||||
+4
-4
@@ -60,7 +60,7 @@ define hidden void @main.closureFunctionGoroutine(ptr %context) unnamed_addr #0
|
||||
entry:
|
||||
%n = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #11
|
||||
store i32 3, ptr %n, align 4
|
||||
%0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 133 to ptr), ptr undef) #11
|
||||
%0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #11
|
||||
store i32 5, ptr %0, align 4
|
||||
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4
|
||||
store ptr %n, ptr %1, align 4
|
||||
@@ -102,7 +102,7 @@ declare void @runtime.printunlock(ptr) #1
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 391 to ptr), ptr undef) #11
|
||||
%0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #11
|
||||
store i32 5, ptr %0, align 4
|
||||
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4
|
||||
store ptr %fn.context, ptr %1, align 4
|
||||
@@ -157,7 +157,7 @@ declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #1
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr nonnull inttoptr (i32 713 to ptr), ptr undef) #11
|
||||
%0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #11
|
||||
store ptr %itf.value, ptr %0, align 4
|
||||
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4
|
||||
store ptr @"main$string", ptr %1, align 4
|
||||
@@ -195,6 +195,6 @@ attributes #5 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb
|
||||
attributes #6 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper" }
|
||||
attributes #7 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
|
||||
attributes #8 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
|
||||
attributes #9 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-invoke"="reflect/methods.Print:func:{basic:string}{}" "tinygo-methods"="reflect/methods.Print:func:{basic:string}{}" }
|
||||
attributes #9 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" }
|
||||
attributes #10 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" }
|
||||
attributes #11 = { nounwind }
|
||||
|
||||
+10
-10
@@ -22,14 +22,14 @@ entry:
|
||||
|
||||
declare void @main.regularFunction(i32, ptr) #0
|
||||
|
||||
declare void @runtime.exitGoroutine(ptr) #0
|
||||
declare void @runtime.deadlock(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define linkonce_odr void @"main.regularFunction$gowrapper"(ptr %0) unnamed_addr #2 {
|
||||
entry:
|
||||
%unpack.int = ptrtoint ptr %0 to i32
|
||||
call void @main.regularFunction(i32 %unpack.int, ptr undef) #11
|
||||
call void @runtime.exitGoroutine(ptr undef) #11
|
||||
call void @runtime.deadlock(ptr undef) #11
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ define linkonce_odr void @"main.inlineFunctionGoroutine$1$gowrapper"(ptr %0) unn
|
||||
entry:
|
||||
%unpack.int = ptrtoint ptr %0 to i32
|
||||
call void @"main.inlineFunctionGoroutine$1"(i32 %unpack.int, ptr undef)
|
||||
call void @runtime.exitGoroutine(ptr undef) #11
|
||||
call void @runtime.deadlock(ptr undef) #11
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ entry:
|
||||
store i32 3, ptr %n, align 4
|
||||
call void @runtime.trackPointer(ptr nonnull %n, ptr nonnull %stackalloc, ptr undef) #11
|
||||
call void @runtime.trackPointer(ptr nonnull @"main.closureFunctionGoroutine$1", ptr nonnull %stackalloc, ptr undef) #11
|
||||
%0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 133 to ptr), ptr undef) #11
|
||||
%0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #11
|
||||
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #11
|
||||
store i32 5, ptr %0, align 4
|
||||
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4
|
||||
@@ -96,7 +96,7 @@ entry:
|
||||
%2 = getelementptr inbounds nuw i8, ptr %0, i32 4
|
||||
%3 = load ptr, ptr %2, align 4
|
||||
call void @"main.closureFunctionGoroutine$1"(i32 %1, ptr %3)
|
||||
call void @runtime.exitGoroutine(ptr undef) #11
|
||||
call void @runtime.deadlock(ptr undef) #11
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ declare void @runtime.printunlock(ptr) #0
|
||||
define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 391 to ptr), ptr undef) #11
|
||||
%0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #11
|
||||
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #11
|
||||
store i32 5, ptr %0, align 4
|
||||
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4
|
||||
@@ -130,7 +130,7 @@ entry:
|
||||
%4 = getelementptr inbounds nuw i8, ptr %0, i32 8
|
||||
%5 = load ptr, ptr %4, align 4
|
||||
call void %5(i32 %1, ptr %3) #11
|
||||
call void @runtime.exitGoroutine(ptr undef) #11
|
||||
call void @runtime.deadlock(ptr undef) #11
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #0
|
||||
define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr nonnull inttoptr (i32 713 to ptr), ptr undef) #11
|
||||
%0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #11
|
||||
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #11
|
||||
store ptr %itf.value, ptr %0, align 4
|
||||
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4
|
||||
@@ -193,7 +193,7 @@ entry:
|
||||
%6 = getelementptr inbounds nuw i8, ptr %0, i32 12
|
||||
%7 = load ptr, ptr %6, align 4
|
||||
call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #11
|
||||
call void @runtime.exitGoroutine(ptr undef) #11
|
||||
call void @runtime.deadlock(ptr undef) #11
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -206,6 +206,6 @@ attributes #5 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+cal
|
||||
attributes #6 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper" }
|
||||
attributes #7 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
|
||||
attributes #8 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
|
||||
attributes #9 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Print:func:{basic:string}{}" "tinygo-methods"="reflect/methods.Print:func:{basic:string}{}" }
|
||||
attributes #9 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" }
|
||||
attributes #10 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" }
|
||||
attributes #11 = { nounwind }
|
||||
|
||||
Vendored
+4
-4
@@ -131,8 +131,8 @@ declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"
|
||||
|
||||
attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||
attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||
attributes #2 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.Error:func:{}{basic:string}" }
|
||||
attributes #3 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.String:func:{}{basic:string}" }
|
||||
attributes #4 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="main.$methods.foo:func:{basic:int}{basic:uint8}" "tinygo-methods"="reflect/methods.String:func:{}{basic:string}; main.$methods.foo:func:{basic:int}{basic:uint8}" }
|
||||
attributes #5 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Error:func:{}{basic:string}" "tinygo-methods"="reflect/methods.Error:func:{}{basic:string}" }
|
||||
attributes #2 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.Error() string" }
|
||||
attributes #3 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.String() string" }
|
||||
attributes #4 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="main.$methods.foo(int) uint8" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) uint8" }
|
||||
attributes #5 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Error() string" "tinygo-methods"="reflect/methods.Error() string" }
|
||||
attributes #6 = { nounwind }
|
||||
|
||||
Vendored
-20
@@ -1,20 +0,0 @@
|
||||
package main
|
||||
|
||||
type largeOptimizedValue [1025]byte
|
||||
|
||||
type mixedLargeOptimizedValue struct {
|
||||
value [1025]byte
|
||||
any any
|
||||
}
|
||||
|
||||
func makeLargeOptimizedValue() largeOptimizedValue {
|
||||
return largeOptimizedValue{}
|
||||
}
|
||||
|
||||
func readLargeOptimizedValue(value largeOptimizedValue) byte {
|
||||
return value[len(value)-1]
|
||||
}
|
||||
|
||||
func readMixedLargeOptimizedValue(value mixedLargeOptimizedValue) byte {
|
||||
return value.value[len(value.value)-1]
|
||||
}
|
||||
Vendored
-120
@@ -1,120 +0,0 @@
|
||||
package main
|
||||
|
||||
type largeValue [1025]byte
|
||||
|
||||
type largeStruct struct {
|
||||
value [1025]byte
|
||||
ptr *byte
|
||||
}
|
||||
|
||||
type largeInterface interface {
|
||||
makeLargeValue() largeValue
|
||||
readLargeValue(largeValue) byte
|
||||
}
|
||||
|
||||
type largeReceiver largeValue
|
||||
|
||||
func makeLargeValue(value byte) largeValue {
|
||||
var result largeValue
|
||||
result[len(result)-1] = value
|
||||
return result
|
||||
}
|
||||
|
||||
func makeZeroLargeValue() largeValue {
|
||||
return largeValue{}
|
||||
}
|
||||
|
||||
func passZeroLargeValue() byte {
|
||||
return readLargeValue(largeValue{})
|
||||
}
|
||||
|
||||
func readLargeValue(value largeValue) byte {
|
||||
return value[len(value)-1]
|
||||
}
|
||||
|
||||
func useLargeValue() byte {
|
||||
return readLargeValue(makeLargeValue(42))
|
||||
}
|
||||
|
||||
func useLargeFunctionValue(fn func(largeValue) byte) byte {
|
||||
return fn(makeLargeValue(42))
|
||||
}
|
||||
|
||||
func (receiver largeReceiver) makeLargeValue() largeValue {
|
||||
return largeValue(receiver)
|
||||
}
|
||||
|
||||
func (receiver largeReceiver) readLargeValue(value largeValue) byte {
|
||||
return value[len(value)-1]
|
||||
}
|
||||
|
||||
func useLargeInterface(value largeInterface) byte {
|
||||
return value.readLargeValue(value.makeLargeValue())
|
||||
}
|
||||
|
||||
func deferLargeValue(value largeValue) {
|
||||
defer readLargeValue(value)
|
||||
}
|
||||
|
||||
func goLargeValue(value largeValue) {
|
||||
go readLargeValue(value)
|
||||
}
|
||||
|
||||
func makeLargeResults(value byte) (largeValue, byte) {
|
||||
return makeLargeValue(value), value
|
||||
}
|
||||
|
||||
func makeTwoLargeResults(value byte) (largeValue, largeValue) {
|
||||
return makeLargeValue(value), makeLargeValue(value + 1)
|
||||
}
|
||||
|
||||
func makeMixedLargeResults(value byte) (largeValue, byte, largeValue) {
|
||||
return makeLargeValue(value), value + 1, makeLargeValue(value + 2)
|
||||
}
|
||||
|
||||
func chooseLargeValue(flag bool) largeValue {
|
||||
value := makeLargeValue(1)
|
||||
if flag {
|
||||
value = makeLargeValue(42)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func makePointerLargeValue(value *byte) largeStruct {
|
||||
return largeStruct{ptr: value}
|
||||
}
|
||||
|
||||
func assertLargeValue(value any) byte {
|
||||
large, ok := value.(largeValue)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return large[len(large)-1]
|
||||
}
|
||||
|
||||
func useLargeMap(key, value largeValue) byte {
|
||||
values := map[largeValue]largeValue{key: value}
|
||||
result, ok := values[key]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return result[len(result)-1]
|
||||
}
|
||||
|
||||
func useLargeChannel(ch chan largeValue, value largeValue) byte {
|
||||
ch <- value
|
||||
result, ok := <-ch
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return result[len(result)-1]
|
||||
}
|
||||
|
||||
func selectLargeChannel(ch chan largeValue, value largeValue) byte {
|
||||
select {
|
||||
case ch <- value:
|
||||
return 0
|
||||
case result := <-ch:
|
||||
return result[len(result)-1]
|
||||
}
|
||||
}
|
||||
Vendored
-498
@@ -1,498 +0,0 @@
|
||||
; ModuleID = 'large.go'
|
||||
source_filename = "large.go"
|
||||
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20"
|
||||
target triple = "wasm32-unknown-wasi"
|
||||
|
||||
%runtime._string = type { ptr, i32 }
|
||||
%runtime.channelOp = type { ptr, ptr, i32, ptr }
|
||||
%runtime.chanSelectState = type { ptr, ptr }
|
||||
|
||||
@"runtime/gc.layout:258-000000000000000000000000000000000000000000000000000000000000000002" = linkonce_odr unnamed_addr constant { i32, [33 x i8] } { i32 258, [33 x i8] c"\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\02" }
|
||||
@"reflect/types.typeid:named:main.largeValue" = external constant i8
|
||||
@llvm.used = appending global [15 x ptr] [ptr @"(main.largeReceiver).makeLargeValue", ptr @"(main.largeReceiver).readLargeValue", ptr @main.makeLargeValue, ptr @main.makeZeroLargeValue, ptr @main.readLargeValue, ptr @main.deferLargeValue, ptr @main.goLargeValue, ptr @main.makeLargeResults, ptr @main.makeTwoLargeResults, ptr @main.makeMixedLargeResults, ptr @main.chooseLargeValue, ptr @main.makePointerLargeValue, ptr @main.useLargeMap, ptr @main.useLargeChannel, ptr @main.selectLargeChannel]
|
||||
@"main$string" = internal unnamed_addr constant [31 x i8] c"blocking select matched no case", align 1
|
||||
@"main$pack" = internal unnamed_addr constant { %runtime._string } { %runtime._string { ptr @"main$string", i32 31 } }
|
||||
@"reflect/types.type:basic:string" = linkonce_odr constant { i8, ptr } { i8 81, ptr @"reflect/types.type:pointer:basic:string" }, align 4
|
||||
@"reflect/types.type:pointer:basic:string" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:string" }, align 4
|
||||
|
||||
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: nounwind
|
||||
define hidden void @"(main.largeReceiver).makeLargeValue"(ptr dereferenceable_or_null(1025) %return, ptr readonly dereferenceable_or_null(1025) %receiver, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %receiver, i32 1025, i1 false)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
|
||||
declare void @llvm.memcpy.p0.p0.i32(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i32, i1 immarg) #2
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i8 @"(main.largeReceiver).readLargeValue"(ptr readonly dereferenceable_or_null(1025) %receiver, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%value1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %value1, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %value1, ptr noundef nonnull align 1 dereferenceable(1025) %value, i32 1025, i1 false)
|
||||
%0 = getelementptr inbounds nuw i8, ptr %value1, i32 1024
|
||||
%1 = load i8, ptr %0, align 1
|
||||
ret i8 %1
|
||||
}
|
||||
|
||||
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
|
||||
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #3
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.makeLargeValue(ptr dereferenceable_or_null(1025) %return, i8 %value, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #9
|
||||
%0 = getelementptr inbounds nuw i8, ptr %result, i32 1024
|
||||
store i8 %value, ptr %0, align 1
|
||||
%1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %1, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %1, ptr noundef nonnull align 1 dereferenceable(1025) %result, i32 1025, i1 false)
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %1, i32 1025, i1 false)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.makeZeroLargeValue(ptr dereferenceable_or_null(1025) %return, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
store [1025 x i8] zeroinitializer, ptr %return, align 1
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i8 @main.passZeroLargeValue(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%"main.largeValue{}:main.largeValue" = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %"main.largeValue{}:main.largeValue", ptr nonnull %stackalloc, ptr undef) #9
|
||||
store [1025 x i8] zeroinitializer, ptr %"main.largeValue{}:main.largeValue", align 1
|
||||
%0 = call i8 @main.readLargeValue(ptr nonnull %"main.largeValue{}:main.largeValue", ptr undef)
|
||||
ret i8 %0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i8 @main.readLargeValue(ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%value1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %value1, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %value1, ptr noundef nonnull align 1 dereferenceable(1025) %value, i32 1025, i1 false)
|
||||
%0 = getelementptr inbounds nuw i8, ptr %value1, i32 1024
|
||||
%1 = load i8, ptr %0, align 1
|
||||
ret i8 %1
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i8 @main.useLargeValue(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @main.makeLargeValue(ptr nonnull %call.result, i8 42, ptr undef)
|
||||
%0 = call i8 @main.readLargeValue(ptr nonnull %call.result, ptr undef)
|
||||
ret i8 %0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i8 @main.useLargeFunctionValue(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @main.makeLargeValue(ptr nonnull %call.result, i8 42, ptr undef)
|
||||
%0 = icmp eq ptr %fn.funcptr, null
|
||||
br i1 %0, label %fpcall.throw, label %fpcall.next
|
||||
|
||||
fpcall.next: ; preds = %entry
|
||||
%1 = call i8 %fn.funcptr(ptr nonnull %call.result, ptr %fn.context) #9
|
||||
ret i8 %1
|
||||
|
||||
fpcall.throw: ; preds = %entry
|
||||
call void @runtime.nilPanic(ptr undef) #9
|
||||
unreachable
|
||||
}
|
||||
|
||||
declare void @runtime.nilPanic(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i8 @main.useLargeInterface(ptr %value.typecode, ptr %value.value, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.makeLargeValue$invoke"(ptr nonnull %call.result, ptr %value.value, ptr %value.typecode, ptr undef) #9
|
||||
%0 = call i8 @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.readLargeValue$invoke"(ptr %value.value, ptr nonnull %call.result, ptr %value.typecode, ptr undef) #9
|
||||
ret i8 %0
|
||||
}
|
||||
|
||||
declare void @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.makeLargeValue$invoke"(ptr, ptr, ptr, ptr) #4
|
||||
|
||||
declare i8 @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.readLargeValue$invoke"(ptr, ptr, ptr, ptr) #5
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.deferLargeValue(ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%defer.alloca = alloca { i32, ptr, ptr }, align 8
|
||||
%deferPtr = alloca ptr, align 4
|
||||
store ptr null, ptr %deferPtr, align 4
|
||||
%stackalloc = alloca i8, align 1
|
||||
call void @runtime.trackPointer(ptr nonnull %defer.alloca, ptr nonnull %stackalloc, ptr undef) #9
|
||||
store i32 0, ptr %defer.alloca, align 4
|
||||
%defer.alloca.repack1 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4
|
||||
store ptr null, ptr %defer.alloca.repack1, align 4
|
||||
%defer.alloca.repack3 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 8
|
||||
store ptr %value, ptr %defer.alloca.repack3, align 4
|
||||
store ptr %defer.alloca, ptr %deferPtr, align 4
|
||||
br label %rundefers.block
|
||||
|
||||
rundefers.after: ; preds = %rundefers.end
|
||||
ret void
|
||||
|
||||
rundefers.block: ; preds = %entry
|
||||
br label %rundefers.loophead
|
||||
|
||||
rundefers.loophead: ; preds = %rundefers.callback0, %rundefers.block
|
||||
%0 = load ptr, ptr %deferPtr, align 4
|
||||
%stackIsNil = icmp eq ptr %0, null
|
||||
br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop
|
||||
|
||||
rundefers.loop: ; preds = %rundefers.loophead
|
||||
%stack.next.gep = getelementptr inbounds nuw i8, ptr %0, i32 4
|
||||
%stack.next = load ptr, ptr %stack.next.gep, align 4
|
||||
store ptr %stack.next, ptr %deferPtr, align 4
|
||||
%callback = load i32, ptr %0, align 4
|
||||
switch i32 %callback, label %rundefers.default [
|
||||
i32 0, label %rundefers.callback0
|
||||
]
|
||||
|
||||
rundefers.callback0: ; preds = %rundefers.loop
|
||||
%gep = getelementptr inbounds nuw i8, ptr %0, i32 8
|
||||
%param = load ptr, ptr %gep, align 4
|
||||
%1 = call i8 @main.readLargeValue(ptr %param, ptr undef)
|
||||
br label %rundefers.loophead
|
||||
|
||||
rundefers.default: ; preds = %rundefers.loop
|
||||
unreachable
|
||||
|
||||
rundefers.end: ; preds = %rundefers.loophead
|
||||
br label %rundefers.after
|
||||
|
||||
recover: ; No predecessors!
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.goLargeValue(ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%go.param = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %go.param, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %go.param, ptr noundef nonnull align 1 dereferenceable(1025) %value, i32 1025, i1 false)
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.readLargeValue$gowrapper" to i32), ptr nonnull %go.param, i32 65536, ptr undef) #9
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @runtime.exitGoroutine(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define linkonce_odr void @"main.readLargeValue$gowrapper"(ptr %0) unnamed_addr #6 {
|
||||
entry:
|
||||
%1 = call i8 @main.readLargeValue(ptr %0, ptr undef)
|
||||
call void @runtime.exitGoroutine(ptr undef) #9
|
||||
unreachable
|
||||
}
|
||||
|
||||
declare void @"internal/task.start"(i32, ptr, i32, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.makeLargeResults(ptr dereferenceable_or_null(1026) %return, i8 %value, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @main.makeLargeValue(ptr nonnull %call.result, i8 %value, ptr undef)
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %call.result, i32 1025, i1 false)
|
||||
%0 = getelementptr inbounds nuw i8, ptr %return, i32 1025
|
||||
store i8 %value, ptr %0, align 1
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.makeTwoLargeResults(ptr dereferenceable_or_null(2050) %return, i8 %value, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @main.makeLargeValue(ptr nonnull %call.result, i8 %value, ptr undef)
|
||||
%0 = add i8 %value, 1
|
||||
%call.result1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %call.result1, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @main.makeLargeValue(ptr nonnull %call.result1, i8 %0, ptr undef)
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %call.result, i32 1025, i1 false)
|
||||
%1 = getelementptr inbounds nuw i8, ptr %return, i32 1025
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %1, ptr noundef nonnull align 1 dereferenceable(1025) %call.result1, i32 1025, i1 false)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.makeMixedLargeResults(ptr dereferenceable_or_null(2051) %return, i8 %value, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @main.makeLargeValue(ptr nonnull %call.result, i8 %value, ptr undef)
|
||||
%0 = add i8 %value, 1
|
||||
%1 = add i8 %value, 2
|
||||
%call.result1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %call.result1, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @main.makeLargeValue(ptr nonnull %call.result1, i8 %1, ptr undef)
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %call.result, i32 1025, i1 false)
|
||||
%2 = getelementptr inbounds nuw i8, ptr %return, i32 1025
|
||||
store i8 %0, ptr %2, align 1
|
||||
%3 = getelementptr inbounds nuw i8, ptr %return, i32 1026
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %3, ptr noundef nonnull align 1 dereferenceable(1025) %call.result1, i32 1025, i1 false)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.chooseLargeValue(ptr dereferenceable_or_null(1025) %return, i1 %flag, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @main.makeLargeValue(ptr nonnull %call.result, i8 1, ptr undef)
|
||||
br i1 %flag, label %if.then, label %if.done
|
||||
|
||||
if.then: ; preds = %entry
|
||||
%call.result1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %call.result1, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @main.makeLargeValue(ptr nonnull %call.result1, i8 42, ptr undef)
|
||||
br label %if.done
|
||||
|
||||
if.done: ; preds = %if.then, %entry
|
||||
%0 = phi ptr [ %call.result, %entry ], [ %call.result1, %if.then ]
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %0, i32 1025, i1 false)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.makePointerLargeValue(ptr dereferenceable_or_null(1032) %return, ptr dereferenceable_or_null(1) %value, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%complit = call align 4 dereferenceable(1032) ptr @runtime.alloc(i32 1032, ptr nonnull @"runtime/gc.layout:258-000000000000000000000000000000000000000000000000000000000000000002", ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #9
|
||||
br i1 false, label %store.throw, label %store.next
|
||||
|
||||
store.next: ; preds = %entry
|
||||
%0 = getelementptr inbounds nuw i8, ptr %complit, i32 1028
|
||||
store ptr %value, ptr %0, align 4
|
||||
%1 = call align 4 dereferenceable(1032) ptr @runtime.alloc(i32 1032, ptr nonnull @"runtime/gc.layout:258-000000000000000000000000000000000000000000000000000000000000000002", ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %1, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 4 dereferenceable(1032) %1, ptr noundef nonnull align 4 dereferenceable(1032) %complit, i32 1032, i1 false)
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1032) %return, ptr noundef nonnull align 4 dereferenceable(1032) %1, i32 1032, i1 false)
|
||||
ret void
|
||||
|
||||
store.throw: ; preds = %entry
|
||||
unreachable
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i8 @main.assertLargeValue(ptr %value.typecode, ptr %value.value, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%large = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %large, ptr nonnull %stackalloc, ptr undef) #9
|
||||
%typecode = call i1 @runtime.typeAssert(ptr %value.typecode, ptr nonnull @"reflect/types.typeid:named:main.largeValue", ptr undef) #9
|
||||
%typeassert.result = call align 1 dereferenceable(1026) ptr @runtime.alloc(i32 1026, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %typeassert.result, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @llvm.memset.p0.i32(ptr noundef nonnull align 1 dereferenceable(1026) %typeassert.result, i8 0, i32 1026, i1 false)
|
||||
br i1 %typecode, label %typeassert.ok, label %typeassert.next
|
||||
|
||||
typeassert.next: ; preds = %typeassert.ok, %entry
|
||||
%0 = getelementptr inbounds nuw i8, ptr %typeassert.result, i32 1025
|
||||
store i1 %typecode, ptr %0, align 1
|
||||
%t2 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %t2, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %t2, ptr noundef nonnull align 1 dereferenceable(1025) %typeassert.result, i32 1025, i1 false)
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %large, ptr noundef nonnull align 1 dereferenceable(1025) %t2, i32 1025, i1 false)
|
||||
br i1 %typecode, label %if.done, label %if.then
|
||||
|
||||
typeassert.ok: ; preds = %entry
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %typeassert.result, ptr noundef nonnull align 1 dereferenceable(1025) %value.value, i32 1025, i1 false)
|
||||
br label %typeassert.next
|
||||
|
||||
if.done: ; preds = %typeassert.next
|
||||
%1 = getelementptr inbounds nuw i8, ptr %large, i32 1024
|
||||
%2 = load i8, ptr %1, align 1
|
||||
ret i8 %2
|
||||
|
||||
if.then: ; preds = %typeassert.next
|
||||
ret i8 0
|
||||
}
|
||||
|
||||
declare i1 @runtime.typeAssert(ptr, ptr dereferenceable_or_null(1), ptr) #0
|
||||
|
||||
; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write)
|
||||
declare void @llvm.memset.p0.i32(ptr nocapture writeonly, i8, i32, i1 immarg) #7
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i8 @main.useLargeMap(ptr readonly dereferenceable_or_null(1025) %key, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%0 = call ptr @runtime.hashmapMakeGeneric(i32 1025, i32 1025, i32 1, ptr null, ptr nonnull @runtime.hash32, ptr null, ptr nonnull @runtime.memequal, ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr %0, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @runtime.hashmapBinarySet(ptr %0, ptr %key, ptr %value, ptr undef) #9
|
||||
%result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #9
|
||||
%hashmap.result = call align 1 dereferenceable(1026) ptr @runtime.alloc(i32 1026, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %hashmap.result, ptr nonnull %stackalloc, ptr undef) #9
|
||||
%1 = call i1 @runtime.hashmapBinaryGet(ptr %0, ptr %key, ptr nonnull %hashmap.result, i32 1025, ptr undef) #9
|
||||
%2 = getelementptr inbounds nuw i8, ptr %hashmap.result, i32 1025
|
||||
store i1 %1, ptr %2, align 1
|
||||
%t3 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %t3, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %t3, ptr noundef nonnull align 1 dereferenceable(1025) %hashmap.result, i32 1025, i1 false)
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %result, ptr noundef nonnull align 1 dereferenceable(1025) %t3, i32 1025, i1 false)
|
||||
%3 = getelementptr inbounds nuw i8, ptr %hashmap.result, i32 1025
|
||||
%t4 = load i1, ptr %3, align 1
|
||||
br i1 %t4, label %if.done, label %if.then
|
||||
|
||||
if.done: ; preds = %entry
|
||||
%4 = getelementptr inbounds nuw i8, ptr %result, i32 1024
|
||||
%5 = load i8, ptr %4, align 1
|
||||
ret i8 %5
|
||||
|
||||
if.then: ; preds = %entry
|
||||
ret i8 0
|
||||
}
|
||||
|
||||
declare i32 @runtime.hash32(ptr, i32, i32, ptr) #0
|
||||
|
||||
declare i1 @runtime.memequal(ptr, ptr, i32, ptr) #0
|
||||
|
||||
declare ptr @runtime.hashmapMakeGeneric(i32, i32, i32, ptr, ptr, ptr, ptr, ptr) #0
|
||||
|
||||
declare void @runtime.hashmapBinarySet(ptr dereferenceable_or_null(48), ptr, ptr, ptr) #0
|
||||
|
||||
declare i1 @runtime.hashmapBinaryGet(ptr dereferenceable_or_null(48), ptr, ptr, i32, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i8 @main.useLargeChannel(ptr dereferenceable_or_null(36) %ch, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%chan.op1 = alloca %runtime.channelOp, align 8
|
||||
%chan.op = alloca %runtime.channelOp, align 8
|
||||
%stackalloc = alloca i8, align 1
|
||||
call void @llvm.lifetime.start.p0(ptr nonnull %chan.op)
|
||||
call void @runtime.chanSend(ptr %ch, ptr %value, ptr nonnull %chan.op, ptr undef) #9
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %chan.op)
|
||||
%result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #9
|
||||
%chan.result = call align 1 dereferenceable(1026) ptr @runtime.alloc(i32 1026, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %chan.result, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @llvm.lifetime.start.p0(ptr nonnull %chan.op1)
|
||||
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.result, ptr nonnull %chan.op1, ptr undef) #9
|
||||
%1 = getelementptr inbounds nuw i8, ptr %chan.result, i32 1025
|
||||
store i1 %0, ptr %1, align 1
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %chan.op1)
|
||||
%t2 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %t2, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %t2, ptr noundef nonnull align 1 dereferenceable(1025) %chan.result, i32 1025, i1 false)
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %result, ptr noundef nonnull align 1 dereferenceable(1025) %t2, i32 1025, i1 false)
|
||||
%2 = getelementptr inbounds nuw i8, ptr %chan.result, i32 1025
|
||||
%t3 = load i1, ptr %2, align 1
|
||||
br i1 %t3, label %if.done, label %if.then
|
||||
|
||||
if.done: ; preds = %entry
|
||||
%3 = getelementptr inbounds nuw i8, ptr %result, i32 1024
|
||||
%4 = load i8, ptr %3, align 1
|
||||
ret i8 %4
|
||||
|
||||
if.then: ; preds = %entry
|
||||
ret i8 0
|
||||
}
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
|
||||
declare void @llvm.lifetime.start.p0(ptr nocapture) #8
|
||||
|
||||
declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
|
||||
declare void @llvm.lifetime.end.p0(ptr nocapture) #8
|
||||
|
||||
declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i8 @main.selectLargeChannel(ptr dereferenceable_or_null(36) %ch, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%select.block.alloca = alloca [2 x %runtime.channelOp], align 8
|
||||
%select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
|
||||
%select.recvbuf.alloca = alloca [1025 x i8], align 1
|
||||
%stackalloc = alloca i8, align 1
|
||||
call void @llvm.lifetime.start.p0(ptr nonnull %select.recvbuf.alloca)
|
||||
call void @llvm.lifetime.start.p0(ptr nonnull %select.states.alloca)
|
||||
store ptr %ch, ptr %select.states.alloca, align 4
|
||||
%select.states.alloca.repack3 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 4
|
||||
store ptr %value, ptr %select.states.alloca.repack3, align 4
|
||||
%0 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 8
|
||||
store ptr %ch, ptr %0, align 4
|
||||
%.repack5 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 12
|
||||
store ptr null, ptr %.repack5, align 4
|
||||
call void @llvm.lifetime.start.p0(ptr nonnull %select.block.alloca)
|
||||
%select.result = call { i32, i1 } @runtime.chanSelect(ptr nonnull %select.recvbuf.alloca, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr nonnull %select.block.alloca, i32 2, i32 2, ptr undef) #9
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %select.block.alloca)
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %select.states.alloca)
|
||||
call void @runtime.trackPointer(ptr nonnull %select.recvbuf.alloca, ptr nonnull %stackalloc, ptr undef) #9
|
||||
%1 = extractvalue { i32, i1 } %select.result, 0
|
||||
%2 = icmp eq i32 %1, 0
|
||||
br i1 %2, label %select.body, label %select.next
|
||||
|
||||
select.body: ; preds = %entry
|
||||
ret i8 0
|
||||
|
||||
select.next: ; preds = %entry
|
||||
%3 = icmp eq i32 %1, 1
|
||||
br i1 %3, label %select.body1, label %select.next2
|
||||
|
||||
select.body1: ; preds = %select.next
|
||||
%result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #9
|
||||
%select.received = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %select.received, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %select.received, ptr noundef nonnull align 1 dereferenceable(1025) %select.recvbuf.alloca, i32 1025, i1 false)
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %result, ptr noundef nonnull align 1 dereferenceable(1025) %select.received, i32 1025, i1 false)
|
||||
%4 = getelementptr inbounds nuw i8, ptr %result, i32 1024
|
||||
%5 = load i8, ptr %4, align 1
|
||||
ret i8 %5
|
||||
|
||||
select.next2: ; preds = %select.next
|
||||
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:string", ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull @"main$pack", ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @runtime._panic(ptr nonnull @"reflect/types.type:basic:string", ptr nonnull @"main$pack", ptr undef) #9
|
||||
unreachable
|
||||
}
|
||||
|
||||
declare { i32, i1 } @runtime.chanSelect(ptr, ptr, i32, i32, ptr, i32, i32, ptr) #0
|
||||
|
||||
declare void @runtime._panic(ptr, ptr, ptr) #0
|
||||
|
||||
attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||
attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||
attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
|
||||
attributes #3 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||
attributes #4 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-indirect-result"="true" "tinygo-invoke"="main.$methods.makeLargeValue:func:{}{named:main.largeValue}" "tinygo-methods"="main.$methods.makeLargeValue:func:{}{named:main.largeValue}; main.$methods.readLargeValue:func:{named:main.largeValue}{basic:uint8}" }
|
||||
attributes #5 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="main.$methods.readLargeValue:func:{named:main.largeValue}{basic:uint8}" "tinygo-methods"="main.$methods.makeLargeValue:func:{}{named:main.largeValue}; main.$methods.readLargeValue:func:{named:main.largeValue}{basic:uint8}" }
|
||||
attributes #6 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.readLargeValue" }
|
||||
attributes #7 = { nocallback nofree nounwind willreturn memory(argmem: write) }
|
||||
attributes #8 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
|
||||
attributes #9 = { nounwind }
|
||||
Vendored
-34
@@ -35,12 +35,6 @@ func withLinkageName1() {
|
||||
//go:linkname withLinkageName2 somepkg.someFunction2
|
||||
func withLinkageName2()
|
||||
|
||||
// Import a function from a different package using go:linknamestd (the standard
|
||||
// library variant of go:linkname introduced in Go 1.27).
|
||||
//
|
||||
//go:linknamestd withLinkageNameStd somepkg.someFunctionStd
|
||||
func withLinkageNameStd()
|
||||
|
||||
// Function has an 'inline hint', similar to the inline keyword in C.
|
||||
//
|
||||
//go:inline
|
||||
@@ -126,31 +120,3 @@ func stillEscapes(a *int, b []int, c chan int, d *[0]byte) {
|
||||
func doesHeapAlloc() *int {
|
||||
return new(int)
|
||||
}
|
||||
|
||||
// Define a function in a different package using a file-level go:linkname.
|
||||
// (Same as withLinkageName1, but with the //go:linkname directive detached
|
||||
// from the function declaration — see https://github.com/tinygo-org/tinygo/issues/4395)
|
||||
func withFileLevelLinkageName1() {
|
||||
}
|
||||
|
||||
// Import a function from a different package using a file-level go:linkname.
|
||||
// (Same as withLinkageName2, but with the //go:linkname directive detached
|
||||
// from the function declaration.)
|
||||
func withFileLevelLinkageName2()
|
||||
|
||||
//go:linkname withFileLevelLinkageName1 somepkg.someFileLevelFunction1
|
||||
//go:linkname withFileLevelLinkageName2 somepkg.someFileLevelFunction2
|
||||
|
||||
// File-level linkname directives can also appear between two function
|
||||
// declarations, in which case Go's AST attaches them as the doc comment
|
||||
// of the following function — even when the directive's localname refers
|
||||
// to a different function. Exercise that case: the directive below names
|
||||
// withAdjacentLinkageName, but Go will attach it to
|
||||
// sentinelAfterAdjacentLinkname's Doc. The file-level scan must find it
|
||||
// by walking comment groups regardless of which decl they're attached to.
|
||||
func withAdjacentLinkageName() {
|
||||
}
|
||||
|
||||
//go:linkname withAdjacentLinkageName somepkg.someAdjacentFunction
|
||||
func sentinelAfterAdjacentLinkname() {
|
||||
}
|
||||
|
||||
Vendored
+2
-24
@@ -33,8 +33,6 @@ entry:
|
||||
|
||||
declare void @somepkg.someFunction2(ptr) #0
|
||||
|
||||
declare void @somepkg.someFunctionStd(ptr) #0
|
||||
|
||||
; Function Attrs: inlinehint nounwind
|
||||
define hidden void @main.inlineFunc(ptr %context) unnamed_addr #3 {
|
||||
entry:
|
||||
@@ -50,12 +48,12 @@ entry:
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.useGeneric(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
call void @"main.noinlineGenericFunc[basic:int8]"(ptr undef)
|
||||
call void @"main.noinlineGenericFunc[int8]"(ptr undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: noinline nounwind
|
||||
define linkonce_odr hidden void @"main.noinlineGenericFunc[basic:int8]"(ptr %context) unnamed_addr #4 {
|
||||
define linkonce_odr hidden void @"main.noinlineGenericFunc[int8]"(ptr %context) unnamed_addr #4 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
@@ -104,26 +102,6 @@ entry:
|
||||
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
|
||||
declare noalias nonnull ptr @runtime.alloc_noheap(i32, ptr, ptr) #9
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @somepkg.someFileLevelFunction1(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @somepkg.someFileLevelFunction2(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @somepkg.someAdjacentFunction(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.sentinelAfterAdjacentLinkname(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||
attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||
attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-export-name"="extern_func" }
|
||||
|
||||
Vendored
+18
-18
@@ -21,23 +21,23 @@ entry:
|
||||
%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
|
||||
call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.value)
|
||||
call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.key)
|
||||
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 4
|
||||
%3 = call i1 @runtime.hashmapGenericGet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, i32 4, ptr undef) #4
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.key)
|
||||
call void @llvm.lifetime.end.p0(i64 12, ptr nonnull %hashmap.key)
|
||||
%4 = load i32, ptr %hashmap.value, align 4
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.value)
|
||||
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value)
|
||||
ret i32 %4
|
||||
}
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
|
||||
declare void @llvm.lifetime.start.p0(ptr nocapture) #3
|
||||
declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #3
|
||||
|
||||
declare i1 @runtime.hashmapGenericGet(ptr dereferenceable_or_null(48), ptr nocapture, ptr nocapture, i32, ptr) #0
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
|
||||
declare void @llvm.lifetime.end.p0(ptr nocapture) #3
|
||||
declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #3
|
||||
|
||||
; Function Attrs: noinline nounwind
|
||||
define hidden void @main.testZeroSet(ptr dereferenceable_or_null(48) %m, i1 %s.b1, i32 %s.i, i1 %s.b2, ptr %context) unnamed_addr #2 {
|
||||
@@ -47,13 +47,13 @@ entry:
|
||||
%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
|
||||
call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.value)
|
||||
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(ptr nonnull %hashmap.key)
|
||||
call void @llvm.lifetime.start.p0(i64 12, ptr nonnull %hashmap.key)
|
||||
store %main.hasPadding %2, ptr %hashmap.key, align 4
|
||||
call void @runtime.hashmapGenericSet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #4
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.key)
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.value)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -64,17 +64,17 @@ define hidden i32 @main.testZeroArrayGet(ptr dereferenceable_or_null(48) %m, [2
|
||||
entry:
|
||||
%hashmap.key = alloca [2 x %main.hasPadding], align 8
|
||||
%hashmap.value = alloca i32, align 4
|
||||
call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.value)
|
||||
call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.key)
|
||||
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.elt = extractvalue [2 x %main.hasPadding] %s, 0
|
||||
store %main.hasPadding %s.elt, ptr %hashmap.key, align 4
|
||||
%hashmap.key.repack1 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 12
|
||||
%s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1
|
||||
store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4
|
||||
%0 = call i1 @runtime.hashmapGenericGet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, i32 4, ptr undef) #4
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.key)
|
||||
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %hashmap.key)
|
||||
%1 = load i32, ptr %hashmap.value, align 4
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.value)
|
||||
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value)
|
||||
ret i32 %1
|
||||
}
|
||||
|
||||
@@ -83,17 +83,17 @@ define hidden void @main.testZeroArraySet(ptr dereferenceable_or_null(48) %m, [2
|
||||
entry:
|
||||
%hashmap.key = alloca [2 x %main.hasPadding], align 8
|
||||
%hashmap.value = alloca i32, align 4
|
||||
call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.value)
|
||||
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(ptr nonnull %hashmap.key)
|
||||
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key)
|
||||
%s.elt = extractvalue [2 x %main.hasPadding] %s, 0
|
||||
store %main.hasPadding %s.elt, ptr %hashmap.key, align 4
|
||||
%hashmap.key.repack1 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 12
|
||||
%s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1
|
||||
store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4
|
||||
call void @runtime.hashmapGenericSet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #4
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.key)
|
||||
call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.value)
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ func TestCorpus(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, repo := range repos {
|
||||
repo := repo
|
||||
name := repo.Repo
|
||||
if repo.Tags != "" {
|
||||
name += "(" + strings.ReplaceAll(repo.Tags, " ", "-") + ")"
|
||||
@@ -131,6 +132,7 @@ func TestCorpus(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, dir := range repo.Subdirs {
|
||||
dir := dir
|
||||
t.Run(dir.Pkg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -116,7 +116,10 @@ func Diff(oldName string, old []byte, newName string, new []byte) []byte {
|
||||
|
||||
// End chunk with common lines for context.
|
||||
if len(ctext) > 0 {
|
||||
n := min(end.x-start.x, C)
|
||||
n := end.x - start.x
|
||||
if n > C {
|
||||
n = C
|
||||
}
|
||||
for _, s := range x[start.x : start.x+n] {
|
||||
ctext = append(ctext, " "+s)
|
||||
count.x++
|
||||
@@ -231,7 +234,7 @@ func tgs(x, y []string) []pair {
|
||||
for i := range T {
|
||||
T[i] = n + 1
|
||||
}
|
||||
for i := range n {
|
||||
for i := 0; i < n; i++ {
|
||||
k := sort.Search(n, func(k int) bool {
|
||||
return T[k] >= J[i]
|
||||
})
|
||||
|
||||
+1
-1
@@ -136,7 +136,7 @@ func readErrorMessages(t *testing.T, file string) string {
|
||||
}
|
||||
|
||||
var errors []string
|
||||
for line := range strings.SplitSeq(string(data), "\n") {
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
if strings.HasPrefix(line, "// ERROR: ") {
|
||||
errors = append(errors, strings.TrimRight(line[len("// ERROR: "):], "\r\n"))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/tinygo-org/tinygo
|
||||
|
||||
go 1.25.0
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139
|
||||
@@ -15,21 +15,22 @@ require (
|
||||
github.com/mgechev/revive v1.3.9
|
||||
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3
|
||||
github.com/tetratelabs/wazero v1.9.0
|
||||
go.bug.st/serial v1.8.0
|
||||
go.bug.st/serial v1.6.4
|
||||
go.bytecodealliance.org v0.6.2
|
||||
go.bytecodealliance.org/cm v0.2.2
|
||||
golang.org/x/net v0.56.0
|
||||
golang.org/x/sys v0.47.0
|
||||
golang.org/x/tools v0.47.0
|
||||
golang.org/x/net v0.50.0
|
||||
golang.org/x/sys v0.41.0
|
||||
golang.org/x/tools v0.42.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
tinygo.org/x/espflasher v0.8.1
|
||||
tinygo.org/x/go-llvm v0.0.0-20260721072906-185673ef46a5
|
||||
tinygo.org/x/espflasher v0.6.1
|
||||
tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||
github.com/chavacava/garif v0.1.0 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/creack/goselect v0.1.2 // indirect
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
|
||||
github.com/fatih/color v1.17.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
@@ -47,6 +48,6 @@ require (
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||
github.com/urfave/cli/v3 v3.0.0-beta1 // indirect
|
||||
golang.org/x/mod v0.37.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
)
|
||||
|
||||
@@ -8,6 +8,8 @@ github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc
|
||||
github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww=
|
||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
|
||||
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -87,30 +89,30 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg=
|
||||
github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
|
||||
go.bug.st/serial v1.8.0 h1:ZtnmN8aYXtPlTghwSvDWPHKBHL9TM6oFDa+KpSn4SQE=
|
||||
go.bug.st/serial v1.8.0/go.mod h1:d0MmS16Qt9b1m06yoYRNUXhRRTJV5Qg2S5EKqQtnayQ=
|
||||
go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A=
|
||||
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
|
||||
go.bytecodealliance.org v0.6.2 h1:Jy4u5DVmSkXgsnwojBhJ+AD/YsJsR3VzVnxF0xRCqTQ=
|
||||
go.bytecodealliance.org v0.6.2/go.mod h1:gqjTJm0y9NSksG4py/lSjIQ/SNuIlOQ+hCIEPQwtJgA=
|
||||
go.bytecodealliance.org/cm v0.2.2 h1:M9iHS6qs884mbQbIjtLX1OifgyPG9DuMs2iwz8G4WQA=
|
||||
go.bytecodealliance.org/cm v0.2.2/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI=
|
||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
|
||||
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
@@ -118,7 +120,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
tinygo.org/x/espflasher v0.8.1 h1:Vp+xA16af9NKVOOSJCcrJHqPSLDCpbfCd/TG3x449ks=
|
||||
tinygo.org/x/espflasher v0.8.1/go.mod h1:YLkCtOCz6gdrbueTi6uZbGPSYduTYt62JdNGmiGxgc0=
|
||||
tinygo.org/x/go-llvm v0.0.0-20260721072906-185673ef46a5 h1:0PKRhM1INWAi7PdIng1BHMPTDaRcOmv1UymdTgZ76Jo=
|
||||
tinygo.org/x/go-llvm v0.0.0-20260721072906-185673ef46a5/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0=
|
||||
tinygo.org/x/espflasher v0.6.1 h1:9jfyAP9jGjxF63FQUY2Bml9TFb5fCZYxVgbgR2IjUGs=
|
||||
tinygo.org/x/espflasher v0.6.1/go.mod h1:tr5u08HoE67WD5zxJesCiiVF/R1b6Akz3yXwh5zah8U=
|
||||
tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6 h1:QSnqFgNV2Ij0T4hM2qKv53fcDAFElxClPjVUZXzYkWU=
|
||||
tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0=
|
||||
|
||||
@@ -151,8 +151,6 @@ func Get(name string) string {
|
||||
panic("could not find cache dir: " + err.Error())
|
||||
}
|
||||
return filepath.Join(dir, "tinygo")
|
||||
case "CGO_CFLAGS":
|
||||
return os.Getenv("CGO_CFLAGS")
|
||||
case "CGO_ENABLED":
|
||||
// Always enable CGo. It is required by a number of targets, including
|
||||
// macOS and the rp2040.
|
||||
|
||||
+6
-18
@@ -140,30 +140,18 @@ func (r *runner) compileFunction(llvmFn llvm.Value) *function {
|
||||
panic("unknown number of operands")
|
||||
}
|
||||
case llvm.Switch:
|
||||
// Compile to an array of (value, label) pairs, of which the
|
||||
// A switch is an array of (value, label) pairs, of which the
|
||||
// first one indicates the to-switch value and the default
|
||||
// label.
|
||||
//
|
||||
// Successor 0 is always the default destination; successors
|
||||
// 1..N-1 are the individual cases. This must be read via
|
||||
// GetSwitchCaseValue/Successor rather than raw operands,
|
||||
// because LLVM 22 stopped exposing switch case values as
|
||||
// regular instruction operands (only the condition and
|
||||
// destination-block operands remain).
|
||||
inst.operands = append(inst.operands,
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
literalValue{uint32(blockIndices[llvmInst.Successor(0).AsValue()])},
|
||||
)
|
||||
for i := 1; i < llvmInst.SuccessorsCount(); i++ {
|
||||
inst.operands = append(inst.operands,
|
||||
r.getValue(llvmInst.GetSwitchCaseValue(i)),
|
||||
literalValue{uint32(blockIndices[llvmInst.Successor(i).AsValue()])},
|
||||
)
|
||||
numOperands := llvmInst.OperandsCount()
|
||||
for i := 0; i < numOperands; i += 2 {
|
||||
inst.operands = append(inst.operands, r.getValue(llvmInst.Operand(i)))
|
||||
inst.operands = append(inst.operands, literalValue{uint32(blockIndices[llvmInst.Operand(i+1)])})
|
||||
}
|
||||
case llvm.PHI:
|
||||
inst.name = llvmInst.Name()
|
||||
incomingCount := inst.llvmInst.IncomingCount()
|
||||
for i := range incomingCount {
|
||||
for i := 0; i < incomingCount; i++ {
|
||||
incomingBB := inst.llvmInst.IncomingBlock(i)
|
||||
incomingValue := inst.llvmInst.IncomingValue(i)
|
||||
inst.operands = append(inst.operands,
|
||||
|
||||
+1
-27
@@ -2,7 +2,6 @@ package interp
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -22,8 +21,8 @@ func TestInterp(t *testing.T) {
|
||||
"store",
|
||||
"alloc",
|
||||
"slicedata",
|
||||
"aggregate",
|
||||
} {
|
||||
name := name // make local to this closure
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
runTest(t, "testdata/"+name)
|
||||
@@ -94,16 +93,6 @@ func runTest(t *testing.T, pathPrefix string) {
|
||||
// equal. That means, only relevant lines are compared (excluding comments
|
||||
// etc.).
|
||||
func fuzzyEqualIR(s1, s2 string) bool {
|
||||
// Golden files are written using the pre-LLVM21 'nocapture' spelling,
|
||||
// which LLVM printed before any co-occurring attribute such as
|
||||
// 'readonly' (e.g. "ptr nocapture readonly"). LLVM 21+ prints the
|
||||
// equivalent 'captures(none)' instead, and after such attributes (e.g.
|
||||
// "ptr readonly captures(none)"). Normalize both name and position back
|
||||
// to the old spelling to keep a single golden file working across LLVM
|
||||
// versions.
|
||||
s1 = normalizeCapturesAttr(s1)
|
||||
s2 = normalizeCapturesAttr(s2)
|
||||
|
||||
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
|
||||
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
|
||||
if len(lines1) != len(lines2) {
|
||||
@@ -119,21 +108,6 @@ func fuzzyEqualIR(s1, s2 string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// capturesNoneAttrRe matches a co-occurring attribute directly followed by
|
||||
// 'captures(none)', which is how LLVM 21+ orders these two attributes when
|
||||
// printing IR (the pre-LLVM21 'nocapture' attribute printed the other way
|
||||
// around).
|
||||
var capturesNoneAttrRe = regexp.MustCompile(`\b(readonly|readnone|writeonly|nonnull)\s+captures\(none\)`)
|
||||
|
||||
// normalizeCapturesAttr rewrites LLVM 21+'s 'captures(none)' attribute back
|
||||
// to the pre-LLVM21 'nocapture' spelling and position, so golden IR files
|
||||
// written against LLVM <21 keep matching.
|
||||
func normalizeCapturesAttr(s string) string {
|
||||
s = capturesNoneAttrRe.ReplaceAllString(s, "nocapture $1")
|
||||
s = strings.ReplaceAll(s, "captures(none)", "nocapture")
|
||||
return s
|
||||
}
|
||||
|
||||
// filterIrrelevantIRLines removes lines from the input slice of strings that
|
||||
// are not relevant in comparing IR. For example, empty lines and comments are
|
||||
// stripped out.
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -471,7 +470,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
// should be returned.
|
||||
numMethods := int(r.builder.CreateExtractValue(methodSet, 0, "").ZExtValue())
|
||||
var method llvm.Value
|
||||
for i := range numMethods {
|
||||
for i := 0; i < numMethods; i++ {
|
||||
methodSignatureAgg := r.builder.CreateExtractValue(methodSet, 1, "")
|
||||
methodSignature := r.builder.CreateExtractValue(methodSignatureAgg, i, "")
|
||||
if methodSignature == signature {
|
||||
@@ -908,7 +907,7 @@ func (r *runner) interpretICmp(lhs, rhs value, predicate llvm.IntPredicate) bool
|
||||
func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, mem *memoryView, indent string) *Error {
|
||||
numOperands := inst.llvmInst.OperandsCount()
|
||||
operands := make([]llvm.Value, numOperands)
|
||||
for i := range numOperands {
|
||||
for i := 0; i < numOperands; i++ {
|
||||
operand := inst.llvmInst.Operand(i)
|
||||
if !operand.IsAInstruction().IsNil() || !operand.IsAArgument().IsNil() {
|
||||
var err error
|
||||
@@ -928,9 +927,11 @@ func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, me
|
||||
llvmFn := operands[len(operands)-1]
|
||||
args := operands[:len(operands)-1]
|
||||
for _, op := range operands {
|
||||
err := mem.markExternalStore(op)
|
||||
if err != nil {
|
||||
return r.errorAt(inst, err)
|
||||
if op.Type().TypeKind() == llvm.PointerTypeKind {
|
||||
err := mem.markExternalStore(op)
|
||||
if err != nil {
|
||||
return r.errorAt(inst, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
result = r.builder.CreateCall(inst.llvmInst.CalledFunctionType(), llvmFn, args, inst.name)
|
||||
@@ -984,9 +985,9 @@ func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, me
|
||||
mem.instructions = append(mem.instructions, agg)
|
||||
}
|
||||
result = operands[1]
|
||||
for i, indice := range slices.Backward(indices) {
|
||||
for i := len(indices) - 1; i >= 0; i-- {
|
||||
agg := aggregates[i]
|
||||
result = r.builder.CreateInsertValue(agg, result, int(indice), inst.name+".insertvalue"+strconv.Itoa(i))
|
||||
result = r.builder.CreateInsertValue(agg, result, int(indices[i]), inst.name+".insertvalue"+strconv.Itoa(i))
|
||||
if i != 0 { // don't add last result to mem.instructions as it will be done at the end already
|
||||
mem.instructions = append(mem.instructions, result)
|
||||
}
|
||||
|
||||
+18
-18
@@ -18,10 +18,8 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"math"
|
||||
"math/big"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -82,7 +80,9 @@ func (mv *memoryView) extend(sub memoryView) {
|
||||
if mv.objects == nil && len(sub.objects) != 0 {
|
||||
mv.objects = make(map[uint32]object)
|
||||
}
|
||||
maps.Copy(mv.objects, sub.objects)
|
||||
for key, value := range sub.objects {
|
||||
mv.objects[key] = value
|
||||
}
|
||||
mv.instructions = append(mv.instructions, sub.instructions...)
|
||||
}
|
||||
|
||||
@@ -90,8 +90,8 @@ func (mv *memoryView) extend(sub memoryView) {
|
||||
// created in this memoryView. Do not reuse this memoryView.
|
||||
func (mv *memoryView) revert() {
|
||||
// Erase instructions in reverse order.
|
||||
for _, llvmInst := range slices.Backward(mv.instructions) {
|
||||
|
||||
for i := len(mv.instructions) - 1; i >= 0; i-- {
|
||||
llvmInst := mv.instructions[i]
|
||||
if llvmInst.IsAInstruction().IsNil() {
|
||||
// The IR builder will try to create constant versions of
|
||||
// instructions whenever possible. If it does this, it's not an
|
||||
@@ -172,7 +172,7 @@ func (mv *memoryView) markExternal(llvmValue llvm.Value, mark uint8) error {
|
||||
continue
|
||||
}
|
||||
numOperands := inst.OperandsCount()
|
||||
for i := range numOperands {
|
||||
for i := 0; i < numOperands; i++ {
|
||||
// Using mark '2' (which means read/write access)
|
||||
// because this might be a store instruction.
|
||||
err := mv.markExternal(inst.Operand(i), 2)
|
||||
@@ -215,7 +215,7 @@ func (mv *memoryView) markExternal(llvmValue llvm.Value, mark uint8) error {
|
||||
// need any marking.
|
||||
case llvm.StructTypeKind:
|
||||
numElements := llvmType.StructElementTypesCount()
|
||||
for i := range numElements {
|
||||
for i := 0; i < numElements; i++ {
|
||||
element := mv.r.builder.CreateExtractValue(llvmValue, i, "")
|
||||
err := mv.markExternal(element, mark)
|
||||
if err != nil {
|
||||
@@ -224,7 +224,7 @@ func (mv *memoryView) markExternal(llvmValue llvm.Value, mark uint8) error {
|
||||
}
|
||||
case llvm.ArrayTypeKind:
|
||||
numElements := llvmType.ArrayLength()
|
||||
for i := range numElements {
|
||||
for i := 0; i < numElements; i++ {
|
||||
element := mv.r.builder.CreateExtractValue(llvmValue, i, "")
|
||||
err := mv.markExternal(element, mark)
|
||||
if err != nil {
|
||||
@@ -366,7 +366,7 @@ func (mv *memoryView) store(v value, p pointerValue) bool {
|
||||
buffer := obj.buffer.asRawValue(mv.r)
|
||||
obj.buffer = buffer
|
||||
v := v.asRawValue(mv.r)
|
||||
for i := range valueLen {
|
||||
for i := uint32(0); i < valueLen; i++ {
|
||||
buffer.buf[p.offset()+i] = v.buf[i]
|
||||
}
|
||||
}
|
||||
@@ -392,7 +392,7 @@ type value interface {
|
||||
// literalValue contains simple integer values that don't need to be stored in a
|
||||
// buffer.
|
||||
type literalValue struct {
|
||||
value any
|
||||
value interface{}
|
||||
}
|
||||
|
||||
// Make a literalValue given the number of bits.
|
||||
@@ -931,10 +931,10 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
}
|
||||
// Always use ConstNamedStruct to preserve the exact type identity.
|
||||
// ConstStruct creates a literal struct type which may differ from an
|
||||
// anonymous identified struct even when structurally identical.
|
||||
return llvm.ConstNamedStruct(llvmType, fields), nil
|
||||
if llvmType.StructName() != "" {
|
||||
return llvm.ConstNamedStruct(llvmType, fields), nil
|
||||
}
|
||||
return llvmType.Context().ConstStruct(fields, false), nil
|
||||
case llvm.ArrayTypeKind:
|
||||
numElements := llvmType.ArrayLength()
|
||||
childType := llvmType.ElementType()
|
||||
@@ -1015,7 +1015,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for i := range ptrSize {
|
||||
for i := uint32(0); i < ptrSize; i++ {
|
||||
v.buf[i] = ptr.pointer
|
||||
}
|
||||
} else if !llvmValue.IsAConstantExpr().IsNil() {
|
||||
@@ -1056,7 +1056,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
|
||||
panic(err)
|
||||
}
|
||||
ptrValue.pointer += totalOffset
|
||||
for i := range ptrSize {
|
||||
for i := uint32(0); i < ptrSize; i++ {
|
||||
v.buf[i] = ptrValue.pointer
|
||||
}
|
||||
case llvm.ICmp:
|
||||
@@ -1113,7 +1113,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
|
||||
}
|
||||
case llvm.StructTypeKind:
|
||||
numElements := llvmType.StructElementTypesCount()
|
||||
for i := range numElements {
|
||||
for i := 0; i < numElements; i++ {
|
||||
offset := r.targetData.ElementOffset(llvmType, i)
|
||||
field := rawValue{
|
||||
buf: v.buf[offset:],
|
||||
@@ -1124,7 +1124,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
|
||||
numElements := llvmType.ArrayLength()
|
||||
childType := llvmType.ElementType()
|
||||
childTypeSize := r.targetData.TypeAllocSize(childType)
|
||||
for i := range numElements {
|
||||
for i := 0; i < numElements; i++ {
|
||||
offset := i * int(childTypeSize)
|
||||
field := rawValue{
|
||||
buf: v.buf[offset:],
|
||||
|
||||
Vendored
-27
@@ -1,27 +0,0 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
declare void @externalAggregate({ ptr })
|
||||
|
||||
@main.value = global i32 1
|
||||
@main.result = global i32 0
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @main.init(ptr undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @main.init(ptr %context) unnamed_addr {
|
||||
entry:
|
||||
; The pointer is hidden inside an aggregate argument.
|
||||
%arg = insertvalue { ptr } undef, ptr @main.value, 0
|
||||
|
||||
; This call runs at runtime and may modify @main.value.
|
||||
call void @externalAggregate({ ptr } %arg)
|
||||
|
||||
; Therefore this load must also remain at runtime.
|
||||
%value = load i32, ptr @main.value
|
||||
store i32 %value, ptr @main.result
|
||||
ret void
|
||||
}
|
||||
Vendored
-15
@@ -1,15 +0,0 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@main.value = global i32 1
|
||||
@main.result = local_unnamed_addr global i32 0
|
||||
|
||||
declare void @externalAggregate({ ptr }) local_unnamed_addr
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @externalAggregate({ ptr } { ptr @main.value })
|
||||
%value = load i32, ptr @main.value, align 4
|
||||
store i32 %value, ptr @main.result, align 4
|
||||
ret void
|
||||
}
|
||||
+1
-1
Submodule lib/macos-minimal-sdk updated: 5f57dee4ae...e7c72156ea
-1
Submodule lib/py32-svd deleted from 6aa7396c1d
@@ -22,7 +22,6 @@ import (
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/google/shlex"
|
||||
"github.com/tinygo-org/tinygo/cgo"
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
@@ -507,11 +506,6 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||
var initialCFlags []string
|
||||
initialCFlags = append(initialCFlags, p.program.config.CFlags(true)...)
|
||||
initialCFlags = append(initialCFlags, "-I"+p.Dir)
|
||||
cgoCFlags, err := shlex.Split(goenv.Get("CGO_CFLAGS"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to split CGO_CFLAGS: %w", err)
|
||||
}
|
||||
initialCFlags = append(initialCFlags, cgoCFlags...)
|
||||
generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags, p.program.config.GOOS())
|
||||
p.CFlags = append(initialCFlags, cflags...)
|
||||
p.CGoHeaders = headerCode
|
||||
|
||||
@@ -177,15 +177,8 @@ func Build(pkgName, outpath string, config *compileopts.Config) error {
|
||||
// Pick a default output path based on the main directory.
|
||||
outpath = filepath.Base(result.MainDir) + config.DefaultBinaryExtension()
|
||||
}
|
||||
} else if fi, statErr := os.Stat(outpath); statErr == nil && fi.IsDir() {
|
||||
var name string
|
||||
if strings.HasSuffix(pkgName, ".go") {
|
||||
name = filepath.Base(pkgName[:len(pkgName)-3]) + config.DefaultBinaryExtension()
|
||||
} else {
|
||||
name = filepath.Base(result.MainDir) + config.DefaultBinaryExtension()
|
||||
}
|
||||
outpath = filepath.Join(outpath, name)
|
||||
}
|
||||
|
||||
if err := os.Rename(result.Binary, outpath); err != nil {
|
||||
// Moving failed. Do a file copy.
|
||||
inf, err := os.Open(result.Binary)
|
||||
@@ -793,15 +786,15 @@ func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Option
|
||||
}
|
||||
defer func() {
|
||||
daemon.Process.Signal(os.Interrupt)
|
||||
var stopped atomic.Uint32
|
||||
var stopped uint32
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
if stopped.Load() == 0 {
|
||||
if atomic.LoadUint32(&stopped) == 0 {
|
||||
daemon.Process.Kill()
|
||||
}
|
||||
}()
|
||||
daemon.Wait()
|
||||
stopped.Store(1)
|
||||
atomic.StoreUint32(&stopped, 1)
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -1053,7 +1046,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c
|
||||
|
||||
func touchSerialPortAt1200bps(port string) (err error) {
|
||||
retryCount := 3
|
||||
for range retryCount {
|
||||
for i := 0; i < retryCount; i++ {
|
||||
// Open port
|
||||
p, e := serial.Open(port, &serial.Mode{BaudRate: 1200})
|
||||
if e != nil {
|
||||
@@ -1129,19 +1122,13 @@ const (
|
||||
jtagReset = "jtag"
|
||||
)
|
||||
|
||||
var progressFunc = func(current, total int) {
|
||||
pct := float64(current) / float64(total) * 100
|
||||
bar := int(pct / 2)
|
||||
fmt.Printf("\r[%-50s] %6.1f%%", strings.Repeat("#", bar)+strings.Repeat(".", 50-bar), pct)
|
||||
if current >= total {
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
func flashBinUsingEsp32(port, resetMode, tmppath string, options *compileopts.Options) error {
|
||||
opts := espflasher.DefaultOptions()
|
||||
opts.Compress = true
|
||||
opts.Logger = &espflasher.StdoutLogger{W: os.Stdout}
|
||||
if options.BaudRate != 0 {
|
||||
opts.FlashBaudRate = options.BaudRate
|
||||
}
|
||||
|
||||
if resetMode == jtagReset {
|
||||
opts.ResetMode = espflasher.ResetUSBJTAG
|
||||
@@ -1165,12 +1152,21 @@ func flashBinUsingEsp32(port, resetMode, tmppath string, options *compileopts.Op
|
||||
return err
|
||||
}
|
||||
|
||||
if err := flasher.EraseFlash(progressFunc); err != nil {
|
||||
if err := flasher.EraseFlash(); err != nil {
|
||||
return fmt.Errorf("erase failed: %v", err)
|
||||
}
|
||||
|
||||
progress := func(current, total int) {
|
||||
pct := float64(current) / float64(total) * 100
|
||||
bar := int(pct / 2)
|
||||
fmt.Printf("\r[%-50s] %6.1f%%", strings.Repeat("#", bar)+strings.Repeat(".", 50-bar), pct)
|
||||
if current >= total {
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
// Flash with progress reporting
|
||||
err = flasher.FlashImage(data, offset, progressFunc)
|
||||
err = flasher.FlashImage(data, offset, progress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1248,7 +1244,7 @@ func findFATMounts(options *compileopts.Options) ([]mountPoint, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not list mount points: %w", err)
|
||||
}
|
||||
for line := range strings.SplitSeq(string(tab), "\n") {
|
||||
for _, line := range strings.Split(string(tab), "\n") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) <= 2 {
|
||||
continue
|
||||
@@ -1277,7 +1273,7 @@ func findFATMounts(options *compileopts.Options) ([]mountPoint, error) {
|
||||
}
|
||||
|
||||
// Extract data to convert to a []mountPoint slice.
|
||||
for line := range strings.SplitSeq(out.String(), "\n") {
|
||||
for _, line := range strings.Split(out.String(), "\n") {
|
||||
words := strings.Fields(line)
|
||||
if len(words) < 3 {
|
||||
continue
|
||||
@@ -1689,18 +1685,18 @@ func (m globalValuesFlag) String() string {
|
||||
}
|
||||
|
||||
func (m globalValuesFlag) Set(value string) error {
|
||||
before, after, ok := strings.Cut(value, "=")
|
||||
if !ok {
|
||||
equalsIndex := strings.IndexByte(value, '=')
|
||||
if equalsIndex < 0 {
|
||||
return errors.New("expected format pkgpath.Var=value")
|
||||
}
|
||||
pathAndName := before
|
||||
pathAndName := value[:equalsIndex]
|
||||
pointIndex := strings.LastIndexByte(pathAndName, '.')
|
||||
if pointIndex < 0 {
|
||||
return errors.New("expected format pkgpath.Var=value")
|
||||
}
|
||||
path := pathAndName[:pointIndex]
|
||||
name := pathAndName[pointIndex+1:]
|
||||
stringValue := after
|
||||
stringValue := value[equalsIndex+1:]
|
||||
if m[path] == nil {
|
||||
m[path] = make(map[string]string)
|
||||
}
|
||||
@@ -2058,6 +2054,7 @@ func main() {
|
||||
// This uses an additional semaphore to reduce the memory usage.
|
||||
testSema := make(chan struct{}, cap(options.Semaphore))
|
||||
for i, pkgName := range explicitPkgNames {
|
||||
pkgName := pkgName
|
||||
buf := &bufs[i]
|
||||
testSema <- struct{}{}
|
||||
wg.Add(1)
|
||||
|
||||
+14
-242
@@ -59,11 +59,6 @@ func TestBuild(t *testing.T) {
|
||||
"cgo/",
|
||||
"channel.go",
|
||||
"embed/",
|
||||
"finalizer.go",
|
||||
"finalizerbits.go",
|
||||
"finalizeridle.go",
|
||||
"finalizerinvariants.go",
|
||||
"finalizerlarge.go",
|
||||
"float.go",
|
||||
"gc.go",
|
||||
"generics.go",
|
||||
@@ -120,25 +115,7 @@ func TestBuild(t *testing.T) {
|
||||
|
||||
t.Run("Host", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
hostOptions := optionsFromTarget("", sema)
|
||||
runPlatTests(hostOptions, tests, t)
|
||||
|
||||
// scheduler.threads needs threadID, which exists only on Linux and Darwin.
|
||||
// scheduler.none does not link on Windows.
|
||||
switch runtime.GOOS {
|
||||
case "darwin", "linux":
|
||||
for _, scheduler := range []string{"threads", "none"} {
|
||||
scheduler := scheduler
|
||||
t.Run("finalizerinvariants.go-gc-conservative-scheduler-"+scheduler, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
options := compileopts.Options(hostOptions)
|
||||
options.GC = "conservative"
|
||||
options.Scheduler = scheduler
|
||||
options.Tags = append(append([]string(nil), hostOptions.Tags...), "runtime_asserts")
|
||||
runTest("finalizerinvariants.go", options, t, nil, nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
runPlatTests(optionsFromTarget("", sema), tests, t)
|
||||
})
|
||||
|
||||
// Test a few build options.
|
||||
@@ -268,36 +245,6 @@ func TestBuild(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestTimerStopResetRace checks that stopping or resetting a timer while its
|
||||
// callback is running behaves correctly. It reaches into the runtime timer
|
||||
// hooks via //go:linkname and relies on the threads scheduler, which is only
|
||||
// used on these hosts.
|
||||
func TestTimerStopResetRace(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "darwin", "linux":
|
||||
default:
|
||||
t.Skipf("host GOOS %s does not use the threads scheduler", runtime.GOOS)
|
||||
}
|
||||
runTest("timer_stop_reset_race.go", optionsFromTarget("", sema), t, nil, nil)
|
||||
}
|
||||
|
||||
func TestESP32QEMU(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
options := optionsFromTarget("esp32-qemu", sema)
|
||||
emuCheck(t, options)
|
||||
machines, err := exec.Command("qemu-system-xtensa", "-machine", "help").Output()
|
||||
if err != nil {
|
||||
t.Fatal("failed to list qemu-system-xtensa machines:", err)
|
||||
}
|
||||
if !regexp.MustCompile(`(?m)^esp32\s`).Match(machines) {
|
||||
t.Skip("qemu-system-xtensa does not support the ESP32 machine")
|
||||
}
|
||||
runTest("print.go", options, t, nil, nil)
|
||||
}
|
||||
|
||||
func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
|
||||
emuCheck(t, options)
|
||||
|
||||
@@ -311,17 +258,8 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
|
||||
isWASI := strings.HasPrefix(options.Target, "wasi")
|
||||
isWebAssembly := isWASI || strings.HasPrefix(options.Target, "wasm") || (options.Target == "" && strings.HasPrefix(options.GOARCH, "wasm"))
|
||||
isBaremetal := options.Target == "simavr" || options.Target == "cortex-m-qemu" || options.Target == "riscv-qemu"
|
||||
_, goMinor, err := goenv.GetGorootVersion()
|
||||
if err != nil {
|
||||
t.Fatal("could not get version:", goMinor)
|
||||
}
|
||||
|
||||
for _, name := range tests {
|
||||
if name == "goroutines.go" && (spec.Scheduler == "threads" || spec.Scheduler == "cores") {
|
||||
// This test intentionally checks concurrent scheduling by comparing
|
||||
// output order, so only run it with non-threaded schedulers.
|
||||
continue
|
||||
}
|
||||
if options.GOOS == "linux" && (options.GOARCH == "arm" || options.GOARCH == "386") {
|
||||
switch name {
|
||||
case "timers.go":
|
||||
@@ -343,11 +281,6 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if options.Target == "cortex-m-qemu" && goMinor >= 27 && name == "json.go" {
|
||||
// Go 1.27 jsonv2 exceeds the LM3S6965's 256KiB flash. json.go
|
||||
// is still covered by larger targets such as riscv-qemu.
|
||||
continue
|
||||
}
|
||||
if options.Target == "simavr" {
|
||||
// Not all tests are currently supported on AVR.
|
||||
// Skip the ones that aren't.
|
||||
@@ -401,38 +334,11 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if options.Target != "wasm" {
|
||||
switch name {
|
||||
case "finalizer.go", "finalizerbits.go", "finalizeridle.go", "finalizerlarge.go":
|
||||
// These tests require deterministic finalization on target wasm.
|
||||
// finalizerinvariants.go covers other block GC targets.
|
||||
continue
|
||||
}
|
||||
}
|
||||
if options.Target == "" && options.GC == "" {
|
||||
switch name {
|
||||
case "finalizerinvariants.go":
|
||||
// Skip the default host GC because it does not implement finalizers.
|
||||
// Explicit conservative GC variants cover this test.
|
||||
continue
|
||||
}
|
||||
}
|
||||
if options.Target == "simavr" {
|
||||
switch name {
|
||||
case "finalizerinvariants.go":
|
||||
// Skip because runtime.GC does not return. See the gc.go exclusion above.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
name := name // redefine to avoid race condition
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testOptions := compileopts.Options(options)
|
||||
if name == "finalizerinvariants.go" || name == "finalizerlarge.go" {
|
||||
testOptions.Tags = append(append([]string(nil), options.Tags...), "runtime_asserts")
|
||||
}
|
||||
runTest(name, testOptions, t, nil, nil)
|
||||
runTest(name, options, t, nil, nil)
|
||||
})
|
||||
}
|
||||
if !strings.HasPrefix(spec.Emulator, "simavr ") {
|
||||
@@ -477,7 +383,7 @@ func emuCheck(t *testing.T, options compileopts.Options) {
|
||||
t.Fatal("failed to load target spec:", err)
|
||||
}
|
||||
if spec.Emulator != "" {
|
||||
emulatorCommand, _, _ := strings.Cut(spec.Emulator, " ")
|
||||
emulatorCommand := strings.SplitN(spec.Emulator, " ", 2)[0]
|
||||
_, err := exec.LookPath(emulatorCommand)
|
||||
if err != nil {
|
||||
if errors.Is(err, exec.ErrNotFound) {
|
||||
@@ -552,12 +458,6 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
|
||||
options.Directory = path
|
||||
pkgName = "."
|
||||
}
|
||||
isWebAssembly := strings.HasPrefix(options.Target, "wasi") ||
|
||||
strings.HasPrefix(options.Target, "wasm") ||
|
||||
strings.HasPrefix(options.GOARCH, "wasm")
|
||||
if name == "testing.go" && isWebAssembly {
|
||||
expectedOutputPath = TESTDATA + "/testing-wasm.txt"
|
||||
}
|
||||
|
||||
config, err := builder.NewConfig(&options)
|
||||
if err != nil {
|
||||
@@ -567,18 +467,12 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
|
||||
// Build the test binary.
|
||||
stdout := &bytes.Buffer{}
|
||||
_, err = buildAndRun(pkgName, config, stdout, cmdArgs, environmentVars, 2*time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error {
|
||||
if config.EmulatorName() == "simavr" {
|
||||
// simavr before v1.8 wrote firmware output to stderr and loader logs
|
||||
// to stdout, but PR #490 swapped these streams:
|
||||
// https://github.com/buserror/simavr/pull/490
|
||||
cmd.Stdout = stdout
|
||||
}
|
||||
return cmd.Run()
|
||||
})
|
||||
if err != nil {
|
||||
w := &bytes.Buffer{}
|
||||
diagnostics.CreateDiagnostics(err).WriteTo(w, "")
|
||||
for line := range strings.SplitSeq(strings.TrimRight(w.String(), "\n"), "\n") {
|
||||
for _, line := range strings.Split(strings.TrimRight(w.String(), "\n"), "\n") {
|
||||
t.Log(line)
|
||||
}
|
||||
if stdout.Len() != 0 {
|
||||
@@ -590,10 +484,11 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
|
||||
|
||||
actual := stdout.Bytes()
|
||||
if config.EmulatorName() == "simavr" {
|
||||
actual = cleanSimAVRTestOutput(actual)
|
||||
}
|
||||
if config.EmulatorName() == "qemu-system-xtensa" {
|
||||
actual = cleanESP32QEMUOutput(actual)
|
||||
// Strip simavr log formatting.
|
||||
actual = bytes.Replace(actual, []byte{0x1b, '[', '3', '2', 'm'}, nil, -1)
|
||||
actual = bytes.Replace(actual, []byte{0x1b, '[', '0', 'm'}, nil, -1)
|
||||
actual = bytes.Replace(actual, []byte{'.', '.', '\n'}, []byte{'\n'}, -1)
|
||||
actual = bytes.Replace(actual, []byte{'\n', '.', '\n'}, []byte{'\n', '\n'}, -1)
|
||||
}
|
||||
if name == "testing.go" {
|
||||
// Strip actual time.
|
||||
@@ -620,40 +515,6 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
|
||||
}
|
||||
}
|
||||
|
||||
func cleanESP32QEMUOutput(output []byte) []byte {
|
||||
entryLine := bytes.Index(output, []byte("\nentry "))
|
||||
if entryLine < 0 {
|
||||
return output
|
||||
}
|
||||
entryLineEnd := bytes.IndexByte(output[entryLine+1:], '\n')
|
||||
if entryLineEnd < 0 {
|
||||
return output
|
||||
}
|
||||
return output[entryLine+1+entryLineEnd+1:]
|
||||
}
|
||||
|
||||
func cleanSimAVRTestOutput(output []byte) []byte {
|
||||
output = bytes.ReplaceAll(output, []byte{0x1b, '[', '3', '2', 'm'}, nil)
|
||||
output = bytes.ReplaceAll(output, []byte{0x1b, '[', '0', 'm'}, nil)
|
||||
output = bytes.ReplaceAll(output, []byte{'.', '.', '\n'}, []byte{'\n'})
|
||||
output = bytes.ReplaceAll(output, []byte{'\n', '.', '\n'}, []byte{'\n', '\n'})
|
||||
|
||||
var cleaned []byte
|
||||
for _, line := range bytes.SplitAfter(output, []byte{'\n'}) {
|
||||
trimmedLine := bytes.TrimRight(line, "\r\n")
|
||||
if simavrLoadTextLogPattern.Match(trimmedLine) || simavrLoadBytesLogPattern.Match(trimmedLine) {
|
||||
continue
|
||||
}
|
||||
cleaned = append(cleaned, line...)
|
||||
}
|
||||
return cleaned
|
||||
}
|
||||
|
||||
var (
|
||||
simavrLoadTextLogPattern = regexp.MustCompile(`^Loaded [0-9]+ \.[A-Za-z0-9_]+( at address 0x[0-9a-fA-F]+)?$`)
|
||||
simavrLoadBytesLogPattern = regexp.MustCompile(`^Loaded [0-9]+ bytes of [A-Za-z]+ data at (0x)?[0-9a-fA-F]+$`)
|
||||
)
|
||||
|
||||
// Test WebAssembly files for certain properties.
|
||||
func TestWebAssembly(t *testing.T) {
|
||||
t.Parallel()
|
||||
@@ -669,6 +530,7 @@ func TestWebAssembly(t *testing.T) {
|
||||
{name: "panic-default", target: "wasip1", imports: []string{"wasi_snapshot_preview1.fd_write", "wasi_snapshot_preview1.random_get"}},
|
||||
{name: "panic-trap", target: "wasm-unknown", panicStrategy: "trap", imports: []string{}},
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmpdir := t.TempDir()
|
||||
@@ -790,6 +652,7 @@ func TestWasmExport(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -959,6 +822,7 @@ func TestWasmExportJS(t *testing.T) {
|
||||
{name: "c-shared", buildMode: "c-shared"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Build the wasm binary.
|
||||
@@ -988,30 +852,6 @@ func TestWasmExportJS(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWasmExportFinalizersJS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tmpdir := t.TempDir()
|
||||
options := optionsFromTarget("wasm", sema)
|
||||
options.BuildMode = "c-shared"
|
||||
buildConfig, err := builder.NewConfig(&options)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result, err := builder.Build("testdata/wasmexport-finalizer.go", ".wasm", tmpdir, buildConfig)
|
||||
if err != nil {
|
||||
t.Fatal("failed to build binary:", err)
|
||||
}
|
||||
|
||||
output := &bytes.Buffer{}
|
||||
cmd := exec.Command("node", "testdata/wasmexport-finalizer.js", result.Binary)
|
||||
cmd.Stdout = output
|
||||
cmd.Stderr = output
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("failed to run node: %v\n%s", err, output)
|
||||
}
|
||||
}
|
||||
|
||||
// Test whether Go.run() (in wasm_exec.js) normally returns and returns the
|
||||
// right exit code.
|
||||
func TestWasmExit(t *testing.T) {
|
||||
@@ -1030,6 +870,7 @@ func TestWasmExit(t *testing.T) {
|
||||
{name: "exit-1-sleep", output: "slept\nexit code: 1\n"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
options := optionsFromTarget("wasm", sema)
|
||||
@@ -1072,76 +913,6 @@ func checkOutputData(t *testing.T, expectedOutput, actual []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoexitCrash(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
options := optionsFromTarget("", sema)
|
||||
config, err := builder.NewConfig(&options)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
want string
|
||||
}{
|
||||
{"main", "fatal error: all goroutines are asleep - deadlock!"},
|
||||
{"deadlock", "fatal error: all goroutines are asleep - deadlock!"},
|
||||
{"exit", "fatal error: all goroutines are asleep - deadlock!"},
|
||||
{"main-other", "fatal error: all goroutines are asleep - deadlock!"},
|
||||
{"in-panic", "fatal error: all goroutines are asleep - deadlock!"},
|
||||
{"panic", "panic: panic after Goexit"},
|
||||
{"recovered-panic", "fatal error: all goroutines are asleep - deadlock!"},
|
||||
{"recover-before-panic", "fatal error: all goroutines are asleep - deadlock!"},
|
||||
{"recover-before-panic-loop", "fatal error: all goroutines are asleep - deadlock!"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
output := &bytes.Buffer{}
|
||||
_, err = buildAndRun("testdata/goexit.go", config, output, []string{tc.name}, nil, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error {
|
||||
cmd.Stdout = nil
|
||||
cmd.Stderr = nil
|
||||
data, err := cmd.CombinedOutput()
|
||||
output.Write(data)
|
||||
return err
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("program unexpectedly exited successfully")
|
||||
}
|
||||
if !strings.Contains(output.String(), tc.want) {
|
||||
t.Fatalf("output does not contain %q:\n%s", tc.want, output.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuntimeFatal(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
options := optionsFromTarget("", sema)
|
||||
config, err := builder.NewConfig(&options)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
output := &bytes.Buffer{}
|
||||
_, err = buildAndRun("testdata/runtimefatal.go", config, output, nil, nil, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error {
|
||||
cmd.Stdout = nil
|
||||
cmd.Stderr = nil
|
||||
data, err := cmd.CombinedOutput()
|
||||
output.Write(data)
|
||||
return err
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("program unexpectedly exited successfully")
|
||||
}
|
||||
if want := "fatal error: sync: unlock of unlocked Mutex"; !strings.Contains(output.String(), want) {
|
||||
t.Fatalf("output does not contain %q:\n%s", want, output.String())
|
||||
}
|
||||
if strings.Contains(output.String(), "recovered:") {
|
||||
t.Fatalf("fatal runtime error was recovered:\n%s", output.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -1174,6 +945,7 @@ func TestTest(t *testing.T) {
|
||||
)
|
||||
}
|
||||
for _, targ := range targs {
|
||||
targ := targ
|
||||
t.Run(targ.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user