Compare commits

..

1 Commits

Author SHA1 Message Date
Ayke van Laethem 3ecefd6a0a cgo: refactor Go types a little bit
Code like this is not allowed by the upstream Go CGo implementation, but
was allowed by TinyGo:

    var _ int8 = C.int8_t(5)

The reason it shouldn't be allowed is a little bit complicated. While
it is true that C.int8_t is always the same underlying data type as Go
int8 (signed 8-bit integer), the C type is actually a typedef of one of
the base C types (usually unsigned char or signed char) which in turn do
_not_ map cleanly to Go types: the 'char' type is ambiguous (it may be
either signed or unsigned depending on the ABI) and types like 'int'
vary in size by ABI as well.

To make code more portable, I think it's better to match the upstream
implementation.
2023-09-22 17:51:15 +02:00
59 changed files with 773 additions and 499 deletions
+4 -4
View File
@@ -89,7 +89,7 @@ commands:
# formatting of generated files. # formatting of generated files.
name: Check Go code formatting name: Check Go code formatting
command: make fmt-check command: make fmt-check
- run: make gen-device - run: make gen-device -j4
- run: make smoketest XTENSA=0 - run: make smoketest XTENSA=0
- save_cache: - save_cache:
key: go-cache-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} key: go-cache-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
@@ -98,12 +98,12 @@ commands:
- /go/pkg/mod - /go/pkg/mod
jobs: jobs:
test-llvm15-go118: test-llvm14-go118:
docker: docker:
- image: golang:1.18-buster - image: golang:1.18-buster
steps: steps:
- test-linux: - test-linux:
llvm: "15" llvm: "14"
resource_class: large resource_class: large
workflows: workflows:
@@ -111,4 +111,4 @@ workflows:
jobs: jobs:
# This tests our lowest supported versions of Go and LLVM, to make sure at # This tests our lowest supported versions of Go and LLVM, to make sure at
# least the smoke tests still pass. # least the smoke tests still pass.
- test-llvm15-go118 - test-llvm14-go118
+1 -1
View File
@@ -88,7 +88,7 @@ jobs:
if: steps.cache-wasi-libc.outputs.cache-hit != 'true' if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
run: make wasi-libc run: make wasi-libc
- name: make gen-device - name: make gen-device
run: make gen-device run: make -j3 gen-device
- name: Test TinyGo - name: Test TinyGo
shell: bash shell: bash
run: make test GOTESTFLAGS="-short" run: make test GOTESTFLAGS="-short"
+130 -26
View File
@@ -255,7 +255,7 @@ jobs:
- name: Build wasi-libc - name: Build wasi-libc
if: steps.cache-wasi-libc.outputs.cache-hit != 'true' if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
run: make wasi-libc run: make wasi-libc
- run: make gen-device - run: make gen-device -j4
- name: Test TinyGo - name: Test TinyGo
run: make ASSERT=1 test run: make ASSERT=1 test
- name: Build TinyGo - name: Build TinyGo
@@ -267,7 +267,7 @@ jobs:
- run: make smoketest - run: make smoketest
- run: make wasmtest - run: make wasmtest
- run: make tinygo-baremetal - run: make tinygo-baremetal
build-linux-cross: build-linux-arm:
# Build ARM Linux binaries, ready for release. # Build ARM Linux binaries, ready for release.
# This intentionally uses an older Linux image, so that we compile against # This intentionally uses an older Linux image, so that we compile against
# an older glibc version and therefore are compatible with a wide range of # an older glibc version and therefore are compatible with a wide range of
@@ -276,16 +276,6 @@ jobs:
# in that process to avoid doing lots of duplicate work and to avoid # in that process to avoid doing lots of duplicate work and to avoid
# complications around precompiled libraries such as compiler-rt shipped as # complications around precompiled libraries such as compiler-rt shipped as
# part of the release tarball. # part of the release tarball.
strategy:
matrix:
goarch: [ arm, arm64 ]
include:
- goarch: arm64
toolchain: aarch64-linux-gnu
libc: arm64
- goarch: arm
toolchain: arm-linux-gnueabihf
libc: armhf
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
needs: build-linux needs: build-linux
steps: steps:
@@ -296,8 +286,8 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get install --no-install-recommends \ sudo apt-get install --no-install-recommends \
qemu-user \ qemu-user \
g++-${{ matrix.toolchain }} \ g++-arm-linux-gnueabihf \
libc6-dev-${{ matrix.libc }}-cross libc6-dev-armhf-cross
- name: Install Go - name: Install Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
@@ -332,7 +322,7 @@ jobs:
uses: actions/cache/restore@v3 uses: actions/cache/restore@v3
id: cache-llvm-build id: cache-llvm-build
with: with:
key: llvm-build-16-linux-${{ matrix.goarch }}-v1 key: llvm-build-16-linux-arm-v1
path: llvm-build path: llvm-build
- name: Build LLVM - name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true' if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -343,7 +333,7 @@ jobs:
# Install build dependencies. # Install build dependencies.
sudo apt-get install --no-install-recommends ninja-build sudo apt-get install --no-install-recommends ninja-build
# build! # build!
make llvm-build CROSS=${{ matrix.toolchain }} make llvm-build CROSS=arm-linux-gnueabihf
# Remove unnecessary object files (to reduce cache size). # Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save LLVM build cache - name: Save LLVM build cache
@@ -356,14 +346,14 @@ jobs:
uses: actions/cache@v3 uses: actions/cache@v3
id: cache-binaryen id: cache-binaryen
with: with:
key: binaryen-linux-${{ matrix.goarch }}-v1 key: binaryen-linux-arm-v1
path: build/wasm-opt path: build/wasm-opt
- name: Build Binaryen - name: Build Binaryen
if: steps.cache-binaryen.outputs.cache-hit != 'true' if: steps.cache-binaryen.outputs.cache-hit != 'true'
run: | run: |
sudo apt-get install --no-install-recommends ninja-build sudo apt-get install --no-install-recommends ninja-build
git submodule update --init lib/binaryen git submodule update --init lib/binaryen
make CROSS=${{ matrix.toolchain }} binaryen make CROSS=arm-linux-gnueabihf binaryen
- name: Install fpm - name: Install fpm
run: | run: |
sudo gem install --version 4.0.7 public_suffix sudo gem install --version 4.0.7 public_suffix
@@ -371,7 +361,7 @@ jobs:
sudo gem install --no-document fpm sudo gem install --no-document fpm
- name: Build TinyGo binary - name: Build TinyGo binary
run: | run: |
make CROSS=${{ matrix.toolchain }} make CROSS=arm-linux-gnueabihf
- name: Download amd64 release - name: Download amd64 release
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
@@ -384,15 +374,129 @@ jobs:
run: | run: |
cp -p build/tinygo build/release/tinygo/bin cp -p build/tinygo build/release/tinygo/bin
cp -p build/wasm-opt build/release/tinygo/bin cp -p build/wasm-opt build/release/tinygo/bin
- name: Create ${{ matrix.goarch }} release - name: Create arm release
run: | run: |
make release deb RELEASEONLY=1 DEB_ARCH=${{ matrix.libc }} make release deb RELEASEONLY=1 DEB_ARCH=armhf
cp -p build/release.tar.gz /tmp/tinygo.linux-${{ matrix.goarch }}.tar.gz cp -p build/release.tar.gz /tmp/tinygo.linux-arm.tar.gz
cp -p build/release.deb /tmp/tinygo_${{ matrix.libc }}.deb cp -p build/release.deb /tmp/tinygo_armhf.deb
- name: Publish release artifact - name: Publish release artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: linux-${{ matrix.goarch }}-double-zipped name: linux-arm-double-zipped
path: | path: |
/tmp/tinygo.linux-${{ matrix.goarch }}.tar.gz /tmp/tinygo.linux-arm.tar.gz
/tmp/tinygo_${{ matrix.libc }}.deb /tmp/tinygo_armhf.deb
build-linux-arm64:
# Build ARM64 Linux binaries, ready for release.
# It is set to "needs: build-linux" because it modifies the release created
# in that process to avoid doing lots of duplicate work and to avoid
# complications around precompiled libraries such as compiler-rt shipped as
# part of the release tarball.
runs-on: ubuntu-20.04
needs: build-linux
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install apt dependencies
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends \
qemu-user \
g++-aarch64-linux-gnu \
libc6-dev-arm64-cross \
ninja-build
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: '1.21'
cache: true
- name: Restore LLVM source cache
uses: actions/cache/restore@v3
id: cache-llvm-source
with:
key: llvm-source-16-linux-v1
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
llvm-project/compiler-rt
llvm-project/lld/include
llvm-project/llvm/include
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Save LLVM source cache
uses: actions/cache/save@v3
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
llvm-project/compiler-rt
llvm-project/lld/include
llvm-project/llvm/include
- name: Restore LLVM build cache
uses: actions/cache/restore@v3
id: cache-llvm-build
with:
key: llvm-build-16-linux-arm64-v1
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
run: |
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# build!
make llvm-build CROSS=aarch64-linux-gnu
# Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save LLVM build cache
uses: actions/cache/save@v3
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }}
path: llvm-build
- name: Cache Binaryen
uses: actions/cache@v3
id: cache-binaryen
with:
key: binaryen-linux-arm64-v1
path: build/wasm-opt
- name: Build Binaryen
if: steps.cache-binaryen.outputs.cache-hit != 'true'
run: |
git submodule update --init lib/binaryen
make CROSS=aarch64-linux-gnu binaryen
- name: Install fpm
run: |
sudo gem install --version 4.0.7 public_suffix
sudo gem install --version 2.7.6 dotenv
sudo gem install --no-document fpm
- name: Build TinyGo binary
run: |
make CROSS=aarch64-linux-gnu
- name: Download amd64 release
uses: actions/download-artifact@v3
with:
name: linux-amd64-double-zipped
- name: Extract amd64 release
run: |
mkdir -p build/release
tar -xf tinygo.linux-amd64.tar.gz -C build/release tinygo
- name: Modify release
run: |
cp -p build/tinygo build/release/tinygo/bin
cp -p build/wasm-opt build/release/tinygo/bin
- name: Create arm64 release
run: |
make release deb RELEASEONLY=1 DEB_ARCH=arm64
cp -p build/release.tar.gz /tmp/tinygo.linux-arm64.tar.gz
cp -p build/release.deb /tmp/tinygo_arm64.deb
- name: Publish release artifact
uses: actions/upload-artifact@v3
with:
name: linux-arm64-double-zipped
path: |
/tmp/tinygo.linux-arm64.tar.gz
/tmp/tinygo_arm64.deb
+1 -1
View File
@@ -49,7 +49,7 @@ jobs:
path: | path: |
~/.cache/go-build ~/.cache/go-build
~/go/pkg/mod ~/go/pkg/mod
- run: make gen-device - run: make gen-device -j4
- name: Download drivers repo - name: Download drivers repo
run: git clone https://github.com/tinygo-org/drivers.git run: git clone https://github.com/tinygo-org/drivers.git
- name: Save HEAD - name: Save HEAD
+1 -1
View File
@@ -98,7 +98,7 @@ jobs:
run: | run: |
scoop install wasmtime scoop install wasmtime
- name: make gen-device - name: make gen-device
run: make gen-device run: make -j3 gen-device
- name: Test TinyGo - name: Test TinyGo
shell: bash shell: bash
run: make test GOTESTFLAGS="-short" run: make test GOTESTFLAGS="-short"
-1
View File
@@ -19,7 +19,6 @@ LLVM, Clang and LLD are quite light on dependencies, requiring only standard
build tools to be built. Go is of course necessary to build TinyGo itself. build tools to be built. Go is of course necessary to build TinyGo itself.
* Go (1.18+) * Go (1.18+)
* GNU Make
* Standard build tools (gcc/clang) * Standard build tools (gcc/clang)
* git * git
* CMake * CMake
+3 -3
View File
@@ -10,7 +10,7 @@ LLD_SRC ?= $(LLVM_PROJECTDIR)/lld
# Try to autodetect LLVM build tools. # Try to autodetect LLVM build tools.
# Versions are listed here in descending priority order. # Versions are listed here in descending priority order.
LLVM_VERSIONS = 16 15 LLVM_VERSIONS = 16 15 14 13 12 11
errifempty = $(if $(1),$(1),$(error $(2))) errifempty = $(if $(1),$(1),$(error $(2)))
detect = $(shell which $(call errifempty,$(firstword $(foreach p,$(2),$(shell command -v $(p) 2> /dev/null && echo $(p)))),failed to locate $(1) at any of: $(2))) detect = $(shell which $(call errifempty,$(firstword $(foreach p,$(2),$(shell command -v $(p) 2> /dev/null && echo $(p)))),failed to locate $(1) at any of: $(2)))
toolSearchPathsVersion = $(1)-$(2) toolSearchPathsVersion = $(1)-$(2)
@@ -265,7 +265,7 @@ endif
wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a
lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a:
@if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi @if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
cd lib/wasi-libc && $(MAKE) -j4 EXTRA_CFLAGS="-O2 -g -DNDEBUG -mnontrapping-fptoint -msign-ext" MALLOC_IMPL=none CC=$(CLANG) AR=$(LLVM_AR) NM=$(LLVM_NM) cd lib/wasi-libc && make -j4 EXTRA_CFLAGS="-O2 -g -DNDEBUG -mnontrapping-fptoint -msign-ext" MALLOC_IMPL=none CC=$(CLANG) AR=$(LLVM_AR) NM=$(LLVM_NM)
# Check for Node.js used during WASM tests. # Check for Node.js used during WASM tests.
NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-))) NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-)))
@@ -280,7 +280,7 @@ endif
# Build the Go compiler. # Build the Go compiler.
tinygo: tinygo:
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " $(MAKE) llvm-source"; echo " $(MAKE) $(LLVM_BUILDDIR)"; exit 1; fi @if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" -ldflags="-X github.com/tinygo-org/tinygo/goenv.GitSha1=`git rev-parse --short HEAD`" . CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" -ldflags="-X github.com/tinygo-org/tinygo/goenv.GitSha1=`git rev-parse --short HEAD`" .
test: wasi-libc check-nodejs-version test: wasi-libc check-nodejs-version
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=20m -buildmode exe -tags "byollvm osusergo" ./builder ./cgo ./compileopts ./compiler ./interp ./transform . CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=20m -buildmode exe -tags "byollvm osusergo" ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
+14 -11
View File
@@ -83,7 +83,8 @@ type packageAction struct {
FileHashes map[string]string // hash of every file that's part of the package FileHashes map[string]string // hash of every file that's part of the package
EmbeddedFiles map[string]string // hash of all the //go:embed files in the package EmbeddedFiles map[string]string // hash of all the //go:embed files in the package
Imports map[string]string // map from imported package to action ID hash Imports map[string]string // map from imported package to action ID hash
OptLevel string // LLVM optimization level (O0, O1, O2, Os, Oz) OptLevel int // LLVM optimization level (0-3)
SizeLevel int // LLVM optimization for size level (0-2)
UndefinedGlobals []string // globals that are left as external globals (no initializer) UndefinedGlobals []string // globals that are left as external globals (no initializer)
} }
@@ -157,7 +158,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
return BuildResult{}, fmt.Errorf("unknown libc: %s", config.Target.Libc) return BuildResult{}, fmt.Errorf("unknown libc: %s", config.Target.Libc)
} }
optLevel, speedLevel, sizeLevel := config.OptLevel() optLevel, sizeLevel, _ := config.OptLevels()
compilerConfig := &compiler.Config{ compilerConfig := &compiler.Config{
Triple: config.Triple(), Triple: config.Triple(),
CPU: config.CPU(), CPU: config.CPU(),
@@ -320,6 +321,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
EmbeddedFiles: make(map[string]string, len(allFiles)), EmbeddedFiles: make(map[string]string, len(allFiles)),
Imports: make(map[string]string, len(pkg.Pkg.Imports())), Imports: make(map[string]string, len(pkg.Pkg.Imports())),
OptLevel: optLevel, OptLevel: optLevel,
SizeLevel: sizeLevel,
UndefinedGlobals: undefinedGlobals, UndefinedGlobals: undefinedGlobals,
} }
for filePath, hash := range pkg.FileHashes { for filePath, hash := range pkg.FileHashes {
@@ -530,13 +532,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
irbuilder := mod.Context().NewBuilder() irbuilder := mod.Context().NewBuilder()
defer irbuilder.Dispose() defer irbuilder.Dispose()
irbuilder.SetInsertPointAtEnd(block) irbuilder.SetInsertPointAtEnd(block)
ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
for _, pkg := range lprogram.Sorted() { for _, pkg := range lprogram.Sorted() {
pkgInit := mod.NamedFunction(pkg.Pkg.Path() + ".init") pkgInit := mod.NamedFunction(pkg.Pkg.Path() + ".init")
if pkgInit.IsNil() { if pkgInit.IsNil() {
panic("init not found for " + pkg.Pkg.Path()) panic("init not found for " + pkg.Pkg.Path())
} }
irbuilder.CreateCall(pkgInit.GlobalValueType(), pkgInit, []llvm.Value{llvm.Undef(ptrType)}, "") irbuilder.CreateCall(pkgInit.GlobalValueType(), pkgInit, []llvm.Value{llvm.Undef(i8ptrType)}, "")
} }
irbuilder.CreateRetVoid() irbuilder.CreateRetVoid()
@@ -741,17 +743,17 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if config.GOOS() == "windows" { if config.GOOS() == "windows" {
// Options for the MinGW wrapper for the lld COFF linker. // Options for the MinGW wrapper for the lld COFF linker.
ldflags = append(ldflags, ldflags = append(ldflags,
"-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel), "-Xlink=/opt:lldlto="+strconv.Itoa(optLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto")) "--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
} else if config.GOOS() == "darwin" { } else if config.GOOS() == "darwin" {
// Options for the ld64-compatible lld linker. // Options for the ld64-compatible lld linker.
ldflags = append(ldflags, ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(speedLevel), "--lto-O"+strconv.Itoa(optLevel),
"-cache_path_lto", filepath.Join(cacheDir, "thinlto")) "-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
} else { } else {
// Options for the ELF linker. // Options for the ELF linker.
ldflags = append(ldflags, ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(speedLevel), "--lto-O"+strconv.Itoa(optLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"), "--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"),
) )
} }
@@ -762,7 +764,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if sizeLevel >= 2 { if sizeLevel >= 2 {
// Workaround with roughly the same effect as // Workaround with roughly the same effect as
// https://reviews.llvm.org/D119342. // https://reviews.llvm.org/D119342.
// Can hopefully be removed in LLVM 18. // Can hopefully be removed in LLVM 15.
ldflags = append(ldflags, ldflags = append(ldflags,
"-mllvm", "--rotation-max-header-size=0") "-mllvm", "--rotation-max-header-size=0")
} }
@@ -1064,9 +1066,10 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
return err return err
} }
// Run most of the whole-program optimizations (including the whole // Optimization levels here are roughly the same as Clang, but probably not
// O0/O1/O2/Os/Oz optimization pipeline). // exactly.
errs := transform.Optimize(mod, config) optLevel, sizeLevel, inlinerThreshold := config.OptLevels()
errs := transform.Optimize(mod, config, optLevel, sizeLevel, inlinerThreshold)
if len(errs) > 0 { if len(errs) > 0 {
return newMultiError(errs) return newMultiError(errs)
} }
+6
View File
@@ -178,6 +178,12 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
if strings.HasPrefix(target, "riscv64-") { if strings.HasPrefix(target, "riscv64-") {
args = append(args, "-march=rv64gc") args = append(args, "-march=rv64gc")
} }
if strings.HasPrefix(target, "xtensa") {
// Hack to work around an issue in the Xtensa port:
// https://github.com/espressif/llvm-project/issues/52
// Hopefully this will be fixed soon (LLVM 14).
args = append(args, "-D__ELF__")
}
var once sync.Once var once sync.Once
+7 -1
View File
@@ -6,10 +6,12 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strconv"
"strings" "strings"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
"tinygo.org/x/go-llvm"
) )
var Musl = Library{ var Musl = Library{
@@ -91,7 +93,6 @@ var Musl = Library{
"-Wno-string-plus-int", "-Wno-string-plus-int",
"-Wno-ignored-pragmas", "-Wno-ignored-pragmas",
"-Wno-tautological-constant-out-of-range-compare", "-Wno-tautological-constant-out-of-range-compare",
"-Wno-deprecated-non-prototype",
"-Qunused-arguments", "-Qunused-arguments",
// Select include dirs. Don't include standard library includes // Select include dirs. Don't include standard library includes
// (that would introduce host dependencies and other complications), // (that would introduce host dependencies and other complications),
@@ -105,6 +106,11 @@ var Musl = Library{
"-I" + muslDir + "/include", "-I" + muslDir + "/include",
"-fno-stack-protector", "-fno-stack-protector",
} }
llvmMajor, _ := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
if llvmMajor >= 15 {
// This flag was added in Clang 15. It is not present in LLVM 14.
cflags = append(cflags, "-Wno-deprecated-non-prototype")
}
return cflags return cflags
}, },
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl/src") }, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl/src") },
+3 -3
View File
@@ -41,9 +41,9 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test. // This is a small number of very diverse targets that we want to test.
tests := []sizeTest{ tests := []sizeTest{
// microcontrollers // microcontrollers
{"hifive1b", "examples/echo", 4484, 280, 0, 2252}, {"hifive1b", "examples/echo", 4568, 280, 0, 2252},
{"microbit", "examples/serial", 2724, 388, 8, 2256}, {"microbit", "examples/serial", 2728, 388, 8, 2256},
{"wioterminal", "examples/pininterrupt", 6000, 1484, 116, 6816}, {"wioterminal", "examples/pininterrupt", 5996, 1484, 116, 6816},
// TODO: also check wasm. Right now this is difficult, because // TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the // wasm binaries are run through wasm-opt and therefore the
+7 -19
View File
@@ -75,20 +75,10 @@ type bitfieldInfo struct {
} }
// cgoAliases list type aliases between Go and C, for types that are equivalent // cgoAliases list type aliases between Go and C, for types that are equivalent
// in both languages. See addTypeAliases. // in both languages.
var cgoAliases = map[string]string{ var cgoAliases = map[string]string{
"C.int8_t": "int8", "float": "float32",
"C.int16_t": "int16", "double": "float64",
"C.int32_t": "int32",
"C.int64_t": "int64",
"C.uint8_t": "uint8",
"C.uint16_t": "uint16",
"C.uint32_t": "uint32",
"C.uint64_t": "uint64",
"C.uintptr_t": "uintptr",
"C.float": "float32",
"C.double": "float64",
"C._Bool": "bool",
} }
// builtinAliases are handled specially because they only exist on the Go side // builtinAliases are handled specially because they only exist on the Go side
@@ -314,9 +304,9 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
cf := p.newCGoFile(f, i) cf := p.newCGoFile(f, i)
// These types are aliased with the corresponding types in C. For // These types are aliased with the corresponding types in C. For
// example, float in C is always float32 in Go. // example, float in C is always float32 in Go.
cf.names["float"] = clangCursor{} for name := range cgoAliases {
cf.names["double"] = clangCursor{} cf.names[name] = clangCursor{}
cf.names["_Bool"] = clangCursor{} }
// Now read all the names (identifies) that C defines in the header // Now read all the names (identifies) that C defines in the header
// snippet. // snippet.
cf.readNames(p.cgoHeaders[i], cflagsForCGo, filepath.Base(fset.File(f.Pos()).Name()), func(names map[string]clangCursor) { cf.readNames(p.cgoHeaders[i], cflagsForCGo, filepath.Base(fset.File(f.Pos()).Name()), func(names map[string]clangCursor) {
@@ -1131,9 +1121,7 @@ func (p *cgoPackage) getUnnamedDeclName(prefix string, itf interface{}) string {
// getASTDeclName will declare the given C AST node (if not already defined) and // getASTDeclName will declare the given C AST node (if not already defined) and
// will return its name, in the form of C.foo. // will return its name, in the form of C.foo.
func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) string { func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) string {
// Some types are defined in stdint.h and map directly to a particular Go if alias := cgoAliases[name]; alias != "" {
// type.
if alias := cgoAliases["C."+name]; alias != "" {
return alias return alias
} }
node := f.getASTDeclNode(name, found, iscall) node := f.getASTDeclNode(name, found, iscall)
+15
View File
@@ -0,0 +1,15 @@
//go:build !byollvm && llvm14
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-14/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@14/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@14/include
#cgo freebsd CFLAGS: -I/usr/local/llvm14/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-14/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@14/lib -lclang -lffi
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@14/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm14/lib -lclang
*/
import "C"
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !byollvm && !llvm15 //go:build !byollvm && !llvm14 && !llvm15
package cgo package cgo
+8
View File
@@ -22,8 +22,12 @@ import "C"
// #warning another warning // #warning another warning
import "C" import "C"
// #include <stdint.h>
import "C"
// Make sure that errors for the following lines won't change with future // Make sure that errors for the following lines won't change with future
// additions to the CGo preamble. // additions to the CGo preamble.
//
//line errors.go:100 //line errors.go:100
var ( var (
// constant too large // constant too large
@@ -38,4 +42,8 @@ var (
_ byte = C.SOME_CONST_3 _ byte = C.SOME_CONST_3
_ = C.SOME_CONST_4 _ = C.SOME_CONST_4
// This must result in a type error. Previously, TinyGo would allow this
// code (which is not allowed by upstream Go).
_ int8 = C.int8_t(5)
) )
+3
View File
@@ -11,6 +11,7 @@
// testdata/errors.go:108: undefined: C.SOME_CONST_1 // testdata/errors.go:108: undefined: C.SOME_CONST_1
// testdata/errors.go:110: cannot use C.SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows) // testdata/errors.go:110: cannot use C.SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows)
// testdata/errors.go:112: undefined: C.SOME_CONST_4 // testdata/errors.go:112: undefined: C.SOME_CONST_4
// testdata/errors.go:116: cannot use C.int8_t(5) (constant 5 of type C.schar) as int8 value in variable declaration
package main package main
@@ -58,3 +59,5 @@ type C.struct_point_t struct {
type C.point_t = C.struct_point_t type C.point_t = C.struct_point_t
const C.SOME_CONST_3 = 1234 const C.SOME_CONST_3 = 1234
type C.int8_t = C.schar
+6 -6
View File
@@ -145,18 +145,18 @@ func (c *Config) Serial() string {
// OptLevels returns the optimization level (0-2), size level (0-2), and inliner // OptLevels returns the optimization level (0-2), size level (0-2), and inliner
// threshold as used in the LLVM optimization pipeline. // threshold as used in the LLVM optimization pipeline.
func (c *Config) OptLevel() (level string, speedLevel, sizeLevel int) { func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
switch c.Options.Opt { switch c.Options.Opt {
case "none", "0": case "none", "0":
return "O0", 0, 0 return 0, 0, 0 // -O0
case "1": case "1":
return "O1", 1, 0 return 1, 0, 0 // -O1
case "2": case "2":
return "O2", 2, 0 return 2, 0, 225 // -O2
case "s": case "s":
return "Os", 2, 1 return 2, 1, 225 // -Os
case "z": case "z":
return "Oz", 2, 2 // default return 2, 2, 5 // -Oz, default
default: default:
// This is not shown to the user: valid choices are already checked as // This is not shown to the user: valid choices are already checked as
// part of Options.Verify(). It is here as a sanity check. // part of Options.Verify(). It is here as a sanity check.
+28
View File
@@ -35,7 +35,17 @@ func (b *builder) createAtomicOp(name string) llvm.Value {
case "SwapInt32", "SwapInt64", "SwapUint32", "SwapUint64", "SwapUintptr", "SwapPointer": case "SwapInt32", "SwapInt64", "SwapUint32", "SwapUint64", "SwapUintptr", "SwapPointer":
ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) ptr := b.getValue(b.fn.Params[0], getPos(b.fn))
val := b.getValue(b.fn.Params[1], getPos(b.fn)) val := b.getValue(b.fn.Params[1], getPos(b.fn))
isPointer := val.Type().TypeKind() == llvm.PointerTypeKind
if isPointer {
// atomicrmw only supports integers, so cast to an integer.
// TODO: this is fixed in LLVM 15.
val = b.CreatePtrToInt(val, b.uintptrType, "")
ptr = b.CreateBitCast(ptr, llvm.PointerType(val.Type(), 0), "")
}
oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpXchg, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpXchg, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true)
if isPointer {
oldVal = b.CreateIntToPtr(oldVal, b.i8ptrType, "")
}
return oldVal return oldVal
case "CompareAndSwapInt32", "CompareAndSwapInt64", "CompareAndSwapUint32", "CompareAndSwapUint64", "CompareAndSwapUintptr", "CompareAndSwapPointer": case "CompareAndSwapInt32", "CompareAndSwapInt64", "CompareAndSwapUint32", "CompareAndSwapUint64", "CompareAndSwapUintptr", "CompareAndSwapPointer":
ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) ptr := b.getValue(b.fn.Params[0], getPos(b.fn))
@@ -53,6 +63,24 @@ func (b *builder) createAtomicOp(name string) llvm.Value {
case "StoreInt32", "StoreInt64", "StoreUint32", "StoreUint64", "StoreUintptr", "StorePointer": case "StoreInt32", "StoreInt64", "StoreUint32", "StoreUint64", "StoreUintptr", "StorePointer":
ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) ptr := b.getValue(b.fn.Params[0], getPos(b.fn))
val := b.getValue(b.fn.Params[1], getPos(b.fn)) val := b.getValue(b.fn.Params[1], getPos(b.fn))
if strings.HasPrefix(b.Triple, "avr") {
// SelectionDAGBuilder is currently missing the "are unaligned atomics allowed" check for stores.
vType := val.Type()
isPointer := vType.TypeKind() == llvm.PointerTypeKind
if isPointer {
// libcalls only supports integers, so cast to an integer.
vType = b.uintptrType
val = b.CreatePtrToInt(val, vType, "")
ptr = b.CreateBitCast(ptr, llvm.PointerType(vType, 0), "")
}
name := fmt.Sprintf("__atomic_store_%d", vType.IntTypeWidth()/8)
fn := b.mod.NamedFunction(name)
if fn.IsNil() {
fn = llvm.AddFunction(b.mod, name, llvm.FunctionType(vType, []llvm.Type{ptr.Type(), vType, b.uintptrType}, false))
}
b.createCall(fn.GlobalValueType(), fn, []llvm.Value{ptr, val, llvm.ConstInt(b.uintptrType, 5, false)}, "")
return llvm.Value{}
}
store := b.CreateStore(val, ptr) store := b.CreateStore(val, ptr)
store.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent) store.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent)
store.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required store.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required
+1 -1
View File
@@ -45,7 +45,7 @@ func (b *builder) createRuntimeCallCommon(fnName string, args []llvm.Value, name
if llvmFn.IsNil() { if llvmFn.IsNil() {
panic("trying to call non-existent function: " + fn.RelString(nil)) panic("trying to call non-existent function: " + fn.RelString(nil))
} }
args = append(args, llvm.Undef(b.dataPtrType)) // unused context parameter args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
if isInvoke { if isInvoke {
return b.createInvoke(fnType, llvmFn, args, name) return b.createInvoke(fnType, llvmFn, args, name)
} }
+26 -22
View File
@@ -33,27 +33,28 @@ func (b *builder) createChanSend(instr *ssa.Send) {
// store value-to-send // store value-to-send
valueType := b.getLLVMType(instr.X.Type()) valueType := b.getLLVMType(instr.X.Type())
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaSize llvm.Value var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
if isZeroSize { if isZeroSize {
valueAlloca = llvm.ConstNull(b.dataPtrType) valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else { } else {
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value") valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca) b.CreateStore(chanValue, valueAlloca)
} }
// Allocate blockedlist buffer. // Allocate blockedlist buffer.
channelBlockedList := b.getLLVMRuntimeType("channelBlockedList") channelBlockedList := b.getLLVMRuntimeType("channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList") channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Do the send. // Do the send.
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "") b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
// End the lifetime of the allocas. // End the lifetime of the allocas.
// This also works around a bug in CoroSplit, at least in LLVM 8: // This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742 // https://bugs.llvm.org/show_bug.cgi?id=41742
b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize) b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
if !isZeroSize { if !isZeroSize {
b.emitLifetimeEnd(valueAlloca, valueAllocaSize) b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
} }
} }
@@ -65,27 +66,28 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
// Allocate memory to receive into. // Allocate memory to receive into.
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaSize llvm.Value var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
if isZeroSize { if isZeroSize {
valueAlloca = llvm.ConstNull(b.dataPtrType) valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else { } else {
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value") valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
} }
// Allocate blockedlist buffer. // Allocate blockedlist buffer.
channelBlockedList := b.getLLVMRuntimeType("channelBlockedList") channelBlockedList := b.getLLVMRuntimeType("channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList") channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Do the receive. // Do the receive.
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "") commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
var received llvm.Value var received llvm.Value
if isZeroSize { if isZeroSize {
received = llvm.ConstNull(valueType) received = llvm.ConstNull(valueType)
} else { } else {
received = b.CreateLoad(valueType, valueAlloca, "chan.received") received = b.CreateLoad(valueType, valueAlloca, "chan.received")
b.emitLifetimeEnd(valueAlloca, valueAllocaSize) b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
} }
b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize) b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
if unop.CommaOk { if unop.CommaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false)) tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
@@ -157,7 +159,8 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
sendValue := b.getValue(state.Send, state.Pos) sendValue := b.getValue(state.Send, state.Pos)
alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value") alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
b.CreateStore(sendValue, alloca) b.CreateStore(sendValue, alloca)
selectState = b.CreateInsertValue(selectState, alloca, 1, "") ptr := b.CreateBitCast(alloca, b.i8ptrType, "")
selectState = b.CreateInsertValue(selectState, ptr, 1, "")
default: default:
panic("unreachable") panic("unreachable")
} }
@@ -165,10 +168,10 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
} }
// Create a receive buffer, where the received value will be stored. // Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(b.dataPtrType) recvbuf := llvm.Undef(b.i8ptrType)
if recvbufSize != 0 { if recvbufSize != 0 {
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize)) allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca") recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
recvbufAlloca.SetAlignment(recvbufAlign) recvbufAlloca.SetAlignment(recvbufAlign)
recvbuf = b.CreateGEP(allocaType, recvbufAlloca, []llvm.Value{ recvbuf = b.CreateGEP(allocaType, recvbufAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
@@ -178,7 +181,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// Create the states slice (allocated on the stack). // Create the states slice (allocated on the stack).
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates)) statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
statesAlloca, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca") statesAlloca, statesI8, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
for i, state := range selectStates { for i, state := range selectStates {
// Set each slice element to the appropriate channel. // Set each slice element to the appropriate channel.
gep := b.CreateGEP(statesAllocaType, statesAlloca, []llvm.Value{ gep := b.CreateGEP(statesAllocaType, statesAlloca, []llvm.Value{
@@ -199,7 +202,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// Stack-allocate operation structures. // Stack-allocate operation structures.
// If these were simply created as a slice, they would heap-allocate. // If these were simply created as a slice, they would heap-allocate.
chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates)) chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
chBlockAlloca, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca") chBlockAlloca, chBlockAllocaPtr, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false) chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
chBlockPtr := b.CreateGEP(chBlockAllocaType, chBlockAlloca, []llvm.Value{ chBlockPtr := b.CreateGEP(chBlockAllocaType, chBlockAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
@@ -213,7 +216,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
}, "select.result") }, "select.result")
// Terminate the lifetime of the operation structures. // Terminate the lifetime of the operation structures.
b.emitLifetimeEnd(chBlockAlloca, chBlockSize) b.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize)
} else { } else {
results = b.createRuntimeCall("tryChanSelect", []llvm.Value{ results = b.createRuntimeCall("tryChanSelect", []llvm.Value{
recvbuf, recvbuf,
@@ -222,7 +225,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
} }
// Terminate the lifetime of the states alloca. // Terminate the lifetime of the states alloca.
b.emitLifetimeEnd(statesAlloca, statesSize) b.emitLifetimeEnd(statesI8, statesSize)
// The result value does not include all the possible received values, // The result value does not include all the possible received values,
// because we can't load them in advance. Instead, the *ssa.Extract // because we can't load them in advance. Instead, the *ssa.Extract
@@ -262,6 +265,7 @@ func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
// it to the correct type, and dereference it. // it to the correct type, and dereference it.
recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)] recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := b.getLLVMType(expr.Type()) typ := b.getLLVMType(expr.Type())
return b.CreateLoad(typ, recvbuf, "") ptr := b.CreateBitCast(recvbuf, llvm.PointerType(typ, 0), "")
return b.CreateLoad(typ, ptr, "")
} }
} }
+57 -27
View File
@@ -75,9 +75,10 @@ type compilerContext struct {
machine llvm.TargetMachine machine llvm.TargetMachine
targetData llvm.TargetData targetData llvm.TargetData
intType llvm.Type intType llvm.Type
dataPtrType llvm.Type // pointer in address space 0 i8ptrType llvm.Type // for convenience
funcPtrType llvm.Type // pointer in function address space (1 for AVR, 0 elsewhere) rawVoidFuncType llvm.Type // for convenience
funcPtrAddrSpace int funcPtrAddrSpace int
hasTypedPointers bool // for LLVM 14 backwards compatibility
uintptrType llvm.Type uintptrType llvm.Type
program *ssa.Program program *ssa.Program
diagnostics []error diagnostics []error
@@ -122,12 +123,13 @@ func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *C
} else { } else {
panic("unknown pointer size") panic("unknown pointer size")
} }
c.dataPtrType = llvm.PointerType(c.ctx.Int8Type(), 0) c.i8ptrType = llvm.PointerType(c.ctx.Int8Type(), 0)
dummyFuncType := llvm.FunctionType(c.ctx.VoidType(), nil, false) dummyFuncType := llvm.FunctionType(c.ctx.VoidType(), nil, false)
dummyFunc := llvm.AddFunction(c.mod, "tinygo.dummy", dummyFuncType) dummyFunc := llvm.AddFunction(c.mod, "tinygo.dummy", dummyFuncType)
c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace() c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace()
c.funcPtrType = dummyFunc.Type() c.hasTypedPointers = c.i8ptrType != llvm.PointerType(c.ctx.Int16Type(), 0) // with opaque pointers, all pointers are the same type (LLVM 15+)
c.rawVoidFuncType = dummyFunc.Type()
dummyFunc.EraseFromParentAsFunction() dummyFunc.EraseFromParentAsFunction()
return c return c
@@ -415,14 +417,16 @@ func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type {
case types.Uintptr: case types.Uintptr:
return c.uintptrType return c.uintptrType
case types.UnsafePointer: case types.UnsafePointer:
return c.dataPtrType return c.i8ptrType
default: default:
panic("unknown basic type: " + typ.String()) panic("unknown basic type: " + typ.String())
} }
case *types.Chan, *types.Map, *types.Pointer: case *types.Chan:
return c.dataPtrType // all pointers are the same return llvm.PointerType(c.getLLVMRuntimeType("channel"), 0)
case *types.Interface: case *types.Interface:
return c.getLLVMRuntimeType("_interface") return c.getLLVMRuntimeType("_interface")
case *types.Map:
return llvm.PointerType(c.getLLVMRuntimeType("hashmap"), 0)
case *types.Named: case *types.Named:
if st, ok := typ.Underlying().(*types.Struct); ok { if st, ok := typ.Underlying().(*types.Struct); ok {
// Structs are a special case. While other named types are ignored // Structs are a special case. While other named types are ignored
@@ -437,11 +441,21 @@ func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type {
return llvmType return llvmType
} }
return c.getLLVMType(typ.Underlying()) return c.getLLVMType(typ.Underlying())
case *types.Pointer:
if c.hasTypedPointers {
ptrTo := c.getLLVMType(typ.Elem())
return llvm.PointerType(ptrTo, 0)
}
return c.i8ptrType // all pointers are the same
case *types.Signature: // function value case *types.Signature: // function value
return c.getFuncType(typ) return c.getFuncType(typ)
case *types.Slice: case *types.Slice:
ptrType := c.i8ptrType
if c.hasTypedPointers {
ptrType = llvm.PointerType(c.getLLVMType(typ.Elem()), 0)
}
members := []llvm.Type{ members := []llvm.Type{
c.dataPtrType, ptrType,
c.uintptrType, // len c.uintptrType, // len
c.uintptrType, // cap c.uintptrType, // cap
} }
@@ -531,8 +545,8 @@ func (c *compilerContext) createDIType(typ types.Type) llvm.Metadata {
Elements: []llvm.Metadata{ Elements: []llvm.Metadata{
c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{
Name: "ptr", Name: "ptr",
SizeInBits: c.targetData.TypeAllocSize(c.dataPtrType) * 8, SizeInBits: c.targetData.TypeAllocSize(c.i8ptrType) * 8,
AlignInBits: uint32(c.targetData.ABITypeAlignment(c.dataPtrType)) * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(c.i8ptrType)) * 8,
OffsetInBits: 0, OffsetInBits: 0,
Type: c.getDIType(types.NewPointer(types.Typ[types.Byte])), Type: c.getDIType(types.NewPointer(types.Typ[types.Byte])),
}), }),
@@ -1534,18 +1548,21 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
src := argValues[0] src := argValues[0]
elems := argValues[1] elems := argValues[1]
srcBuf := b.CreateExtractValue(src, 0, "append.srcBuf") srcBuf := b.CreateExtractValue(src, 0, "append.srcBuf")
srcPtr := b.CreateBitCast(srcBuf, b.i8ptrType, "append.srcPtr")
srcLen := b.CreateExtractValue(src, 1, "append.srcLen") srcLen := b.CreateExtractValue(src, 1, "append.srcLen")
srcCap := b.CreateExtractValue(src, 2, "append.srcCap") srcCap := b.CreateExtractValue(src, 2, "append.srcCap")
elemsBuf := b.CreateExtractValue(elems, 0, "append.elemsBuf") elemsBuf := b.CreateExtractValue(elems, 0, "append.elemsBuf")
elemsPtr := b.CreateBitCast(elemsBuf, b.i8ptrType, "append.srcPtr")
elemsLen := b.CreateExtractValue(elems, 1, "append.elemsLen") elemsLen := b.CreateExtractValue(elems, 1, "append.elemsLen")
elemType := b.getLLVMType(argTypes[0].Underlying().(*types.Slice).Elem()) elemType := b.getLLVMType(argTypes[0].Underlying().(*types.Slice).Elem())
elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false) elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false)
result := b.createRuntimeCall("sliceAppend", []llvm.Value{srcBuf, elemsBuf, srcLen, srcCap, elemsLen, elemSize}, "append.new") result := b.createRuntimeCall("sliceAppend", []llvm.Value{srcPtr, elemsPtr, srcLen, srcCap, elemsLen, elemSize}, "append.new")
newPtr := b.CreateExtractValue(result, 0, "append.newPtr") newPtr := b.CreateExtractValue(result, 0, "append.newPtr")
newBuf := b.CreateBitCast(newPtr, srcBuf.Type(), "append.newBuf")
newLen := b.CreateExtractValue(result, 1, "append.newLen") newLen := b.CreateExtractValue(result, 1, "append.newLen")
newCap := b.CreateExtractValue(result, 2, "append.newCap") newCap := b.CreateExtractValue(result, 2, "append.newCap")
newSlice := llvm.Undef(src.Type()) newSlice := llvm.Undef(src.Type())
newSlice = b.CreateInsertValue(newSlice, newPtr, 0, "") newSlice = b.CreateInsertValue(newSlice, newBuf, 0, "")
newSlice = b.CreateInsertValue(newSlice, newLen, 1, "") newSlice = b.CreateInsertValue(newSlice, newLen, 1, "")
newSlice = b.CreateInsertValue(newSlice, newCap, 2, "") newSlice = b.CreateInsertValue(newSlice, newCap, 2, "")
return newSlice, nil return newSlice, nil
@@ -1593,6 +1610,9 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
// The pointer to the data to be cleared. // The pointer to the data to be cleared.
llvmBuf := b.CreateExtractValue(value, 0, "buf") llvmBuf := b.CreateExtractValue(value, 0, "buf")
if llvmBuf.Type() != b.i8ptrType { // compatibility with LLVM 14
llvmBuf = b.CreateBitCast(llvmBuf, b.i8ptrType, "")
}
// The length (in bytes) to be cleared. // The length (in bytes) to be cleared.
llvmLen := b.CreateExtractValue(value, 1, "len") llvmLen := b.CreateExtractValue(value, 1, "len")
@@ -1627,6 +1647,8 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
dstBuf := b.CreateExtractValue(dst, 0, "copy.dstArray") dstBuf := b.CreateExtractValue(dst, 0, "copy.dstArray")
srcBuf := b.CreateExtractValue(src, 0, "copy.srcArray") srcBuf := b.CreateExtractValue(src, 0, "copy.srcArray")
elemType := b.getLLVMType(argTypes[0].Underlying().(*types.Slice).Elem()) elemType := b.getLLVMType(argTypes[0].Underlying().(*types.Slice).Elem())
dstBuf = b.CreateBitCast(dstBuf, b.i8ptrType, "copy.dstPtr")
srcBuf = b.CreateBitCast(srcBuf, b.i8ptrType, "copy.srcPtr")
elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false) elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false)
return b.createRuntimeCall("sliceCopy", []llvm.Value{dstBuf, srcBuf, dstLen, srcLen, elemSize}, "copy.n"), nil return b.createRuntimeCall("sliceCopy", []llvm.Value{dstBuf, srcBuf, dstLen, srcLen, elemSize}, "copy.n"), nil
case "delete": case "delete":
@@ -1866,13 +1888,14 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
switch value := instr.Value.(type) { switch value := instr.Value.(type) {
case *ssa.Function: case *ssa.Function:
// Regular function call. No context is necessary. // Regular function call. No context is necessary.
context = llvm.Undef(b.dataPtrType) context = llvm.Undef(b.i8ptrType)
if info.variadic && len(fn.Params) == 0 { if info.variadic && len(fn.Params) == 0 {
// This matches Clang, see: https://godbolt.org/z/Gqv49xKMq // This matches Clang, see: https://godbolt.org/z/Gqv49xKMq
// Eventually we might be able to eliminate this special case // Eventually we might be able to eliminate this special case
// entirely. For details, see: // entirely. For details, see:
// https://discourse.llvm.org/t/rfc-enabling-wstrict-prototypes-by-default-in-c/60521 // https://discourse.llvm.org/t/rfc-enabling-wstrict-prototypes-by-default-in-c/60521
calleeType = llvm.FunctionType(callee.GlobalValueType().ReturnType(), nil, false) calleeType = llvm.FunctionType(callee.GlobalValueType().ReturnType(), nil, false)
callee = llvm.ConstBitCast(callee, llvm.PointerType(calleeType, b.funcPtrAddrSpace))
} }
case *ssa.MakeClosure: case *ssa.MakeClosure:
// A call on a func value, but the callee is trivial to find. For // A call on a func value, but the callee is trivial to find. For
@@ -1900,14 +1923,13 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
params = append(params, typecode) params = append(params, typecode)
callee = b.getInvokeFunction(instr) callee = b.getInvokeFunction(instr)
calleeType = callee.GlobalValueType() calleeType = callee.GlobalValueType()
context = llvm.Undef(b.dataPtrType) context = llvm.Undef(b.i8ptrType)
} else { } else {
// Function pointer. // Function pointer.
value := b.getValue(instr.Value, getPos(instr)) value := b.getValue(instr.Value, getPos(instr))
// This is a func value, which cannot be called directly. We have to // This is a func value, which cannot be called directly. We have to
// extract the function pointer and context first from the func value. // extract the function pointer and context first from the func value.
callee, context = b.decodeFuncValue(value) calleeType, callee, context = b.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature))
calleeType = b.getLLVMFunctionType(instr.Value.Type().Underlying().(*types.Signature))
b.createNilCheck(instr.Value, callee, "fpcall") b.createNilCheck(instr.Value, callee, "fpcall")
} }
@@ -1940,7 +1962,7 @@ func (b *builder) getValue(expr ssa.Value, pos token.Pos) llvm.Value {
return llvm.Undef(b.getLLVMType(expr.Type())) return llvm.Undef(b.getLLVMType(expr.Type()))
} }
_, fn := b.getFunction(expr) _, fn := b.getFunction(expr)
return b.createFuncValue(fn, llvm.Undef(b.dataPtrType), expr.Signature) return b.createFuncValue(fn, llvm.Undef(b.i8ptrType), expr.Signature)
case *ssa.Global: case *ssa.Global:
value := b.getGlobal(expr) value := b.getGlobal(expr)
if value.IsNil() { if value.IsNil() {
@@ -2008,6 +2030,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
sizeValue := llvm.ConstInt(b.uintptrType, size, false) sizeValue := llvm.ConstInt(b.uintptrType, size, false)
layoutValue := b.createObjectLayout(typ, expr.Pos()) layoutValue := b.createObjectLayout(typ, expr.Pos())
buf := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, layoutValue}, expr.Comment) buf := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, layoutValue}, expr.Comment)
buf = b.CreateBitCast(buf, llvm.PointerType(typ, 0), "")
return buf, nil return buf, nil
} else { } else {
buf := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, expr.Comment) buf := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, expr.Comment)
@@ -2053,6 +2076,10 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
value = b.CreateInsertValue(value, field, i, "changetype.struct") value = b.CreateInsertValue(value, field, i, "changetype.struct")
} }
return value, nil return value, nil
case llvm.PointerTypeKind:
// This can happen with pointers to structs. This case is easy:
// simply bitcast the pointer to the destination type.
return b.CreateBitCast(x, llvmType, "changetype.pointer"), nil
default: default:
return llvm.Value{}, errors.New("todo: unknown ChangeType type: " + expr.X.Type().String()) return llvm.Value{}, errors.New("todo: unknown ChangeType type: " + expr.X.Type().String())
} }
@@ -2129,12 +2156,12 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
// Can't load directly from array (as index is non-constant), so // Can't load directly from array (as index is non-constant), so
// have to do it using an alloca+gep+load. // have to do it using an alloca+gep+load.
arrayType := collection.Type() arrayType := collection.Type()
alloca, allocaSize := b.createTemporaryAlloca(arrayType, "index.alloca") alloca, allocaPtr, allocaSize := b.createTemporaryAlloca(arrayType, "index.alloca")
b.CreateStore(collection, alloca) b.CreateStore(collection, alloca)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
ptr := b.CreateInBoundsGEP(arrayType, alloca, []llvm.Value{zero, index}, "index.gep") ptr := b.CreateInBoundsGEP(arrayType, alloca, []llvm.Value{zero, index}, "index.gep")
result := b.CreateLoad(arrayType.ElementType(), ptr, "index.load") result := b.CreateLoad(arrayType.ElementType(), ptr, "index.load")
b.emitLifetimeEnd(alloca, allocaSize) b.emitLifetimeEnd(allocaPtr, allocaSize)
return result, nil return result, nil
default: default:
panic("unknown *ssa.Index type") panic("unknown *ssa.Index type")
@@ -2237,6 +2264,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap") sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap")
layoutValue := b.createObjectLayout(llvmElemType, expr.Pos()) layoutValue := b.createObjectLayout(llvmElemType, expr.Pos())
slicePtr := b.createRuntimeCall("alloc", []llvm.Value{sliceSize, layoutValue}, "makeslice.buf") slicePtr := b.createRuntimeCall("alloc", []llvm.Value{sliceSize, layoutValue}, "makeslice.buf")
slicePtr = b.CreateBitCast(slicePtr, llvm.PointerType(llvmElemType, 0), "makeslice.array")
// Extend or truncate if necessary. This is safe as we've already done // Extend or truncate if necessary. This is safe as we've already done
// the bounds check. // the bounds check.
@@ -2282,7 +2310,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
default: default:
panic("unknown type in range: " + typ.String()) panic("unknown type in range: " + typ.String())
} }
it, _ := b.createTemporaryAlloca(iteratorType, "range.it") it, _, _ := b.createTemporaryAlloca(iteratorType, "range.it")
b.CreateStore(llvm.ConstNull(iteratorType), it) b.CreateStore(llvm.ConstNull(iteratorType), it)
return it, nil return it, nil
case *ssa.Select: case *ssa.Select:
@@ -2450,6 +2478,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
arrayLen := expr.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Array).Len() arrayLen := expr.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Array).Len()
b.createSliceToArrayPointerCheck(sliceLen, arrayLen) b.createSliceToArrayPointerCheck(sliceLen, arrayLen)
ptr := b.CreateExtractValue(slice, 0, "") ptr := b.CreateExtractValue(slice, 0, "")
ptr = b.CreateBitCast(ptr, b.getLLVMType(expr.Type()), "")
return ptr, nil return ptr, nil
case *ssa.TypeAssert: case *ssa.TypeAssert:
return b.createTypeAssert(expr), nil return b.createTypeAssert(expr), nil
@@ -2915,16 +2944,16 @@ func (c *compilerContext) createConst(expr *ssa.Const, pos token.Pos) llvm.Value
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
strPtr = llvm.ConstInBoundsGEP(globalType, global, []llvm.Value{zero, zero}) strPtr = llvm.ConstInBoundsGEP(globalType, global, []llvm.Value{zero, zero})
} else { } else {
strPtr = llvm.ConstNull(c.dataPtrType) strPtr = llvm.ConstNull(c.i8ptrType)
} }
strObj := llvm.ConstNamedStruct(c.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen}) strObj := llvm.ConstNamedStruct(c.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen})
return strObj return strObj
} else if typ.Kind() == types.UnsafePointer { } else if typ.Kind() == types.UnsafePointer {
if !expr.IsNil() { if !expr.IsNil() {
value, _ := constant.Uint64Val(constant.ToInt(expr.Value)) value, _ := constant.Uint64Val(constant.ToInt(expr.Value))
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, value, false), c.dataPtrType) return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, value, false), c.i8ptrType)
} }
return llvm.ConstNull(c.dataPtrType) return llvm.ConstNull(c.i8ptrType)
} else if typ.Info()&types.IsUnsigned != 0 { } else if typ.Info()&types.IsUnsigned != 0 {
n, _ := constant.Uint64Val(constant.ToInt(expr.Value)) n, _ := constant.Uint64Val(constant.ToInt(expr.Value))
return llvm.ConstInt(llvmType, n, false) return llvm.ConstInt(llvmType, n, false)
@@ -2968,7 +2997,7 @@ func (c *compilerContext) createConst(expr *ssa.Const, pos token.Pos) llvm.Value
// Create a generic nil interface with no dynamic type (typecode=0). // Create a generic nil interface with no dynamic type (typecode=0).
fields := []llvm.Value{ fields := []llvm.Value{
llvm.ConstInt(c.uintptrType, 0, false), llvm.ConstInt(c.uintptrType, 0, false),
llvm.ConstPointerNull(c.dataPtrType), llvm.ConstPointerNull(c.i8ptrType),
} }
return llvm.ConstNamedStruct(c.getLLVMRuntimeType("_interface"), fields) return llvm.ConstNamedStruct(c.getLLVMRuntimeType("_interface"), fields)
case *types.Pointer: case *types.Pointer:
@@ -2985,7 +3014,8 @@ func (c *compilerContext) createConst(expr *ssa.Const, pos token.Pos) llvm.Value
if expr.Value != nil { if expr.Value != nil {
panic("expected nil slice constant") panic("expected nil slice constant")
} }
llvmPtr := llvm.ConstPointerNull(c.dataPtrType) elemType := c.getLLVMType(typ.Elem())
llvmPtr := llvm.ConstPointerNull(llvm.PointerType(elemType, 0))
llvmLen := llvm.ConstInt(c.uintptrType, 0, false) llvmLen := llvm.ConstInt(c.uintptrType, 0, false)
slice := c.ctx.ConstStruct([]llvm.Value{ slice := c.ctx.ConstStruct([]llvm.Value{
llvmPtr, // backing array llvmPtr, // backing array
@@ -3026,7 +3056,7 @@ func (b *builder) createConvert(typeFrom, typeTo types.Type, value llvm.Value, p
// Conversion between pointers and unsafe.Pointer. // Conversion between pointers and unsafe.Pointer.
if isPtrFrom && isPtrTo { if isPtrFrom && isPtrTo {
return value, nil return b.CreateBitCast(value, llvmTypeTo, ""), nil
} }
switch typeTo := typeTo.Underlying().(type) { switch typeTo := typeTo.Underlying().(type) {
@@ -3265,7 +3295,7 @@ func (b *builder) createUnOp(unop *ssa.UnOp) (llvm.Value, error) {
if fn.IsNil() { if fn.IsNil() {
return llvm.Value{}, b.makeError(unop.Pos(), "cgo function not found: "+name) return llvm.Value{}, b.makeError(unop.Pos(), "cgo function not found: "+name)
} }
return fn, nil return b.CreateBitCast(fn, b.i8ptrType, ""), nil
} else { } else {
b.createNilCheck(unop.X, x, "deref") b.createNilCheck(unop.X, x, "deref")
load := b.CreateLoad(valueType, x, "") load := b.CreateLoad(valueType, x, "")
+9 -7
View File
@@ -91,12 +91,14 @@ func TestCompiler(t *testing.T) {
} }
// Optimize IR a little. // Optimize IR a little.
passOptions := llvm.NewPassBuilderOptions() funcPasses := llvm.NewFunctionPassManagerForModule(mod)
defer passOptions.Dispose() defer funcPasses.Dispose()
err = mod.RunPasses("instcombine", llvm.TargetMachine{}, passOptions) funcPasses.AddInstructionCombiningPass()
if err != nil { funcPasses.InitializeFunc()
t.Error(err) for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
funcPasses.RunFunc(fn)
} }
funcPasses.FinalizeFunc()
outFilePrefix := tc.file[:len(tc.file)-3] outFilePrefix := tc.file[:len(tc.file)-3]
if tc.target != "" { if tc.target != "" {
@@ -167,9 +169,9 @@ func filterIrrelevantIRLines(lines []string) []string {
if strings.HasPrefix(line, "source_filename = ") { if strings.HasPrefix(line, "source_filename = ") {
continue continue
} }
if llvmVersion < 15 && strings.HasPrefix(line, "target datalayout = ") { if llvmVersion < 14 && strings.HasPrefix(line, "target datalayout = ") {
// The datalayout string may vary betewen LLVM versions. // The datalayout string may vary betewen LLVM versions.
// Right now test outputs are for LLVM 15 and higher. // Right now test outputs are for LLVM 14 and higher.
continue continue
} }
out = append(out, line) out = append(out, line)
+33 -24
View File
@@ -60,8 +60,9 @@ func (b *builder) deferInitFunc() {
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin) b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
// Create defer list pointer. // Create defer list pointer.
b.deferPtr = b.CreateAlloca(b.dataPtrType, "deferPtr") deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)
b.CreateStore(llvm.ConstPointerNull(b.dataPtrType), b.deferPtr) b.deferPtr = b.CreateAlloca(deferType, "deferPtr")
b.CreateStore(llvm.ConstPointerNull(deferType), b.deferPtr)
if b.hasDeferFrame() { if b.hasDeferFrame() {
// Set up the defer frame with the current stack pointer. // Set up the defer frame with the current stack pointer.
@@ -248,7 +249,8 @@ func isInLoop(start *ssa.BasicBlock) bool {
func (b *builder) createDefer(instr *ssa.Defer) { func (b *builder) createDefer(instr *ssa.Defer) {
// The pointer to the previous defer struct, which we will replace to // The pointer to the previous defer struct, which we will replace to
// make a linked list. // make a linked list.
next := b.CreateLoad(b.dataPtrType, b.deferPtr, "defer.next") deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)
next := b.CreateLoad(deferType, b.deferPtr, "defer.next")
var values []llvm.Value var values []llvm.Value
valueTypes := []llvm.Type{b.uintptrType, next.Type()} valueTypes := []llvm.Type{b.uintptrType, next.Type()}
@@ -269,7 +271,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode") typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver") receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = []llvm.Value{callback, next, typecode, receiverValue} values = []llvm.Value{callback, next, typecode, receiverValue}
valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType) valueTypes = append(valueTypes, b.i8ptrType, b.i8ptrType)
for _, arg := range instr.Call.Args { for _, arg := range instr.Call.Args {
val := b.getValue(arg, getPos(instr)) val := b.getValue(arg, getPos(instr))
values = append(values, val) values = append(values, val)
@@ -389,8 +391,9 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// This may be hit a variable number of times, so use a heap allocation. // This may be hit a variable number of times, so use a heap allocation.
size := b.targetData.TypeAllocSize(deferredCallType) size := b.targetData.TypeAllocSize(deferredCallType)
sizeValue := llvm.ConstInt(b.uintptrType, size, false) sizeValue := llvm.ConstInt(b.uintptrType, size, false)
nilPtr := llvm.ConstNull(b.dataPtrType) nilPtr := llvm.ConstNull(b.i8ptrType)
alloca = b.createRuntimeCall("alloc", []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call") allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferredCallType, 0), "defer.alloc")
} }
if b.NeedsStackObjects { if b.NeedsStackObjects {
b.trackPointer(alloca) b.trackPointer(alloca)
@@ -398,12 +401,14 @@ func (b *builder) createDefer(instr *ssa.Defer) {
b.CreateStore(deferredCall, alloca) b.CreateStore(deferredCall, alloca)
// Push it on top of the linked list by replacing deferPtr. // Push it on top of the linked list by replacing deferPtr.
b.CreateStore(alloca, b.deferPtr) allocaCast := b.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
b.CreateStore(allocaCast, b.deferPtr)
} }
// createRunDefers emits code to run all deferred functions. // createRunDefers emits code to run all deferred functions.
func (b *builder) createRunDefers() { func (b *builder) createRunDefers() {
deferType := b.getLLVMRuntimeType("_defer") deferType := b.getLLVMRuntimeType("_defer")
deferPtrType := llvm.PointerType(deferType, 0)
// Add a loop like the following: // Add a loop like the following:
// for stack != nil { // for stack != nil {
@@ -430,7 +435,7 @@ func (b *builder) createRunDefers() {
// Create loop head: // Create loop head:
// for stack != nil { // for stack != nil {
b.SetInsertPointAtEnd(loophead) b.SetInsertPointAtEnd(loophead)
deferData := b.CreateLoad(b.dataPtrType, b.deferPtr, "") deferData := b.CreateLoad(deferPtrType, b.deferPtr, "")
stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil") stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
b.CreateCondBr(stackIsNil, end, loop) b.CreateCondBr(stackIsNil, end, loop)
@@ -443,7 +448,7 @@ func (b *builder) createRunDefers() {
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field
}, "stack.next.gep") }, "stack.next.gep")
nextStack := b.CreateLoad(b.dataPtrType, nextStackGEP, "stack.next") nextStack := b.CreateLoad(deferPtrType, nextStackGEP, "stack.next")
b.CreateStore(nextStack, b.deferPtr) b.CreateStore(nextStack, b.deferPtr)
gep := b.CreateInBoundsGEP(deferType, deferData, []llvm.Value{ gep := b.CreateInBoundsGEP(deferType, deferData, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
@@ -464,26 +469,28 @@ func (b *builder) createRunDefers() {
// Call on an value or interface value. // Call on an value or interface value.
// Get the real defer struct type and cast to it. // Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
if !callback.IsInvoke() { if !callback.IsInvoke() {
//Expect funcValue to be passed through the deferred call. //Expect funcValue to be passed through the deferred call.
valueTypes = append(valueTypes, b.getFuncType(callback.Signature())) valueTypes = append(valueTypes, b.getFuncType(callback.Signature()))
} else { } else {
//Expect typecode //Expect typecode
valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType) valueTypes = append(valueTypes, b.i8ptrType, b.i8ptrType)
} }
for _, arg := range callback.Args { for _, arg := range callback.Args {
valueTypes = append(valueTypes, b.getLLVMType(arg.Type())) valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
} }
deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// Extract the params from the struct (including receiver). // Extract the params from the struct (including receiver).
forwardParams := []llvm.Value{} forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
deferredCallType := b.ctx.StructType(valueTypes, false)
for i := 2; i < len(valueTypes); i++ { for i := 2; i < len(valueTypes); i++ {
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep") gep := b.CreateInBoundsGEP(deferredCallType, deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
forwardParam := b.CreateLoad(valueTypes[i], gep, "param") forwardParam := b.CreateLoad(valueTypes[i], gep, "param")
forwardParams = append(forwardParams, forwardParam) forwardParams = append(forwardParams, forwardParam)
} }
@@ -498,8 +505,7 @@ func (b *builder) createRunDefers() {
//Get function pointer and context //Get function pointer and context
var context llvm.Value var context llvm.Value
fnPtr, context = b.decodeFuncValue(funcValue) fnType, fnPtr, context = b.decodeFuncValue(funcValue, callback.Signature())
fnType = b.getLLVMFunctionType(callback.Signature())
//Pass context //Pass context
forwardParams = append(forwardParams, context) forwardParams = append(forwardParams, context)
@@ -513,7 +519,7 @@ func (b *builder) createRunDefers() {
// Add the context parameter. An interface call cannot also be a // Add the context parameter. An interface call cannot also be a
// closure but we have to supply the parameter anyway for platforms // closure but we have to supply the parameter anyway for platforms
// with a strict calling convention. // with a strict calling convention.
forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType)) forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
} }
b.createCall(fnType, fnPtr, forwardParams, "") b.createCall(fnType, fnPtr, forwardParams, "")
@@ -522,17 +528,18 @@ func (b *builder) createRunDefers() {
// Direct call. // Direct call.
// Get the real defer struct type and cast to it. // Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
for _, param := range getParams(callback.Signature) { for _, param := range getParams(callback.Signature) {
valueTypes = append(valueTypes, b.getLLVMType(param.Type())) valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
} }
deferredCallType := b.ctx.StructType(valueTypes, false) deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// Extract the params from the struct. // Extract the params from the struct.
forwardParams := []llvm.Value{} forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := range getParams(callback.Signature) { for i := range getParams(callback.Signature) {
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep") gep := b.CreateInBoundsGEP(deferredCallType, deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param") forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param")
forwardParams = append(forwardParams, forwardParam) forwardParams = append(forwardParams, forwardParam)
} }
@@ -542,7 +549,7 @@ func (b *builder) createRunDefers() {
if !b.getFunctionInfo(callback).exported { if !b.getFunctionInfo(callback).exported {
// Add the context parameter. We know it is ignored by the receiving // Add the context parameter. We know it is ignored by the receiving
// function, but we have to pass one anyway. // function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType)) forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
} }
// Call real function. // Call real function.
@@ -552,19 +559,20 @@ func (b *builder) createRunDefers() {
case *ssa.MakeClosure: case *ssa.MakeClosure:
// Get the real defer struct type and cast to it. // Get the real defer struct type and cast to it.
fn := callback.Fn.(*ssa.Function) fn := callback.Fn.(*ssa.Function)
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
params := fn.Signature.Params() params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ { for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type())) valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
} }
valueTypes = append(valueTypes, b.dataPtrType) // closure valueTypes = append(valueTypes, b.i8ptrType) // closure
deferredCallType := b.ctx.StructType(valueTypes, false) deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// Extract the params from the struct. // Extract the params from the struct.
forwardParams := []llvm.Value{} forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 2; i < len(valueTypes); i++ { for i := 2; i < len(valueTypes); i++ {
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "") gep := b.CreateInBoundsGEP(deferredCallType, deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
forwardParam := b.CreateLoad(valueTypes[i], gep, "param") forwardParam := b.CreateLoad(valueTypes[i], gep, "param")
forwardParams = append(forwardParams, forwardParam) forwardParams = append(forwardParams, forwardParam)
} }
@@ -576,7 +584,7 @@ func (b *builder) createRunDefers() {
db := b.deferBuiltinFuncs[callback] db := b.deferBuiltinFuncs[callback]
//Get parameter types //Get parameter types
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
//Get signature from call results //Get signature from call results
params := callback.Type().Underlying().(*types.Signature).Params() params := callback.Type().Underlying().(*types.Signature).Params()
@@ -585,12 +593,13 @@ func (b *builder) createRunDefers() {
} }
deferredCallType := b.ctx.StructType(valueTypes, false) deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// Extract the params from the struct. // Extract the params from the struct.
var argValues []llvm.Value var argValues []llvm.Value
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 0; i < params.Len(); i++ { for i := 0; i < params.Len(); i++ {
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep") gep := b.CreateInBoundsGEP(deferredCallType, deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param") forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param")
argValues = append(argValues, forwardParam) argValues = append(argValues, forwardParam)
} }
+39 -10
View File
@@ -13,14 +13,35 @@ import (
// createFuncValue creates a function value from a raw function pointer with no // createFuncValue creates a function value from a raw function pointer with no
// context. // context.
func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value { func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
return b.compilerContext.createFuncValue(b.Builder, funcPtr, context, sig)
}
// createFuncValue creates a function value from a raw function pointer with no
// context.
func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
// Closure is: {context, function pointer} // Closure is: {context, function pointer}
funcValueType := b.getFuncType(sig) funcValueScalar := llvm.ConstBitCast(funcPtr, c.rawVoidFuncType)
funcValueType := c.getFuncType(sig)
funcValue := llvm.Undef(funcValueType) funcValue := llvm.Undef(funcValueType)
funcValue = b.CreateInsertValue(funcValue, context, 0, "") funcValue = builder.CreateInsertValue(funcValue, context, 0, "")
funcValue = b.CreateInsertValue(funcValue, funcPtr, 1, "") funcValue = builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
return funcValue return funcValue
} }
// getFuncSignatureID returns a new external global for a given signature. This
// global reference is not real, it is only used during func lowering to assign
// signature types to functions and will then be removed.
func (c *compilerContext) getFuncSignatureID(sig *types.Signature) llvm.Value {
s, _ := getTypeCodeName(sig)
sigGlobalName := "reflect/types.funcid:" + s
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
if sigGlobal.IsNil() {
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
sigGlobal.SetGlobalConstant(true)
}
return sigGlobal
}
// extractFuncScalar returns some scalar that can be used in comparisons. It is // extractFuncScalar returns some scalar that can be used in comparisons. It is
// a cheap operation. // a cheap operation.
func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value { func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value {
@@ -34,20 +55,28 @@ func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value {
} }
// decodeFuncValue extracts the context and the function pointer from this func // decodeFuncValue extracts the context and the function pointer from this func
// value. // value. This may be an expensive operation.
func (b *builder) decodeFuncValue(funcValue llvm.Value) (funcPtr, context llvm.Value) { func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcType llvm.Type, funcPtr, context llvm.Value) {
context = b.CreateExtractValue(funcValue, 0, "") context = b.CreateExtractValue(funcValue, 0, "")
funcPtr = b.CreateExtractValue(funcValue, 1, "") funcPtr = b.CreateExtractValue(funcValue, 1, "")
if !funcPtr.IsAConstantExpr().IsNil() && funcPtr.Opcode() == llvm.BitCast {
funcPtr = funcPtr.Operand(0) // needed for LLVM 14 (no opaque pointers)
}
if sig != nil {
funcType = b.getRawFuncType(sig)
llvmSig := llvm.PointerType(funcType, b.funcPtrAddrSpace)
funcPtr = b.CreateBitCast(funcPtr, llvmSig, "")
}
return return
} }
// getFuncType returns the type of a func value given a signature. // getFuncType returns the type of a func value given a signature.
func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type { func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
return c.ctx.StructType([]llvm.Type{c.dataPtrType, c.funcPtrType}, false) return c.ctx.StructType([]llvm.Type{c.i8ptrType, c.rawVoidFuncType}, false)
} }
// getLLVMFunctionType returns a LLVM function type for a given signature. // getRawFuncType returns a LLVM function type for a given signature.
func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type { func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
// Get the return type. // Get the return type.
var returnType llvm.Type var returnType llvm.Type
switch typ.Results().Len() { switch typ.Results().Len() {
@@ -75,7 +104,7 @@ func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type {
if recv.StructName() == "runtime._interface" { if recv.StructName() == "runtime._interface" {
// This is a call on an interface, not a concrete type. // This is a call on an interface, not a concrete type.
// The receiver is not an interface, but a i8* type. // The receiver is not an interface, but a i8* type.
recv = c.dataPtrType recv = c.i8ptrType
} }
for _, info := range c.expandFormalParamType(recv, "", nil) { for _, info := range c.expandFormalParamType(recv, "", nil) {
paramTypes = append(paramTypes, info.llvmType) paramTypes = append(paramTypes, info.llvmType)
@@ -88,7 +117,7 @@ func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type {
} }
} }
// All functions take these parameters at the end. // All functions take these parameters at the end.
paramTypes = append(paramTypes, c.dataPtrType) // context paramTypes = append(paramTypes, c.i8ptrType) // context
// Make a func type out of the signature. // Make a func type out of the signature.
return llvm.FunctionType(returnType, paramTypes, false) return llvm.FunctionType(returnType, paramTypes, false)
+3
View File
@@ -81,6 +81,9 @@ func (b *builder) trackValue(value llvm.Value) {
// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner // trackPointer creates a call to runtime.trackPointer, bitcasting the poitner
// first if needed. The input value must be of LLVM pointer type. // first if needed. The input value must be of LLVM pointer type.
func (b *builder) trackPointer(value llvm.Value) { func (b *builder) trackPointer(value llvm.Value) {
if value.Type() != b.i8ptrType {
value = b.CreateBitCast(value, b.i8ptrType, "")
}
b.createRuntimeCall("trackPointer", []llvm.Value{value, b.stackChainAlloca}, "") b.createRuntimeCall("trackPointer", []llvm.Value{value, b.stackChainAlloca}, "")
} }
+12 -13
View File
@@ -21,7 +21,7 @@ func (b *builder) createGo(instr *ssa.Go) {
var prefix string var prefix string
var funcPtr llvm.Value var funcPtr llvm.Value
var funcType llvm.Type var funcPtrType llvm.Type
hasContext := false hasContext := false
if callee := instr.Call.StaticCallee(); callee != nil { if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new // Static callee is known. This makes it easier to start a new
@@ -42,7 +42,7 @@ func (b *builder) createGo(instr *ssa.Go) {
params = append(params, context) // context parameter params = append(params, context) // context parameter
hasContext = true hasContext = true
} }
funcType, funcPtr = b.getFunction(callee) funcPtrType, funcPtr = b.getFunction(callee)
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok { } else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
// We cheat. None of the builtins do any long or blocking operation, so // We cheat. None of the builtins do any long or blocking operation, so
// we might as well run these builtins right away without the program // we might as well run these builtins right away without the program
@@ -80,7 +80,7 @@ func (b *builder) createGo(instr *ssa.Go) {
itfTypeCode := b.CreateExtractValue(itf, 0, "") itfTypeCode := b.CreateExtractValue(itf, 0, "")
itfValue := b.CreateExtractValue(itf, 1, "") itfValue := b.CreateExtractValue(itf, 1, "")
funcPtr = b.getInvokeFunction(&instr.Call) funcPtr = b.getInvokeFunction(&instr.Call)
funcType = funcPtr.GlobalValueType() funcPtrType = funcPtr.GlobalValueType()
params = append([]llvm.Value{itfValue}, params...) // start with receiver params = append([]llvm.Value{itfValue}, params...) // start with receiver
params = append(params, itfTypeCode) // end with typecode params = append(params, itfTypeCode) // end with typecode
} else { } else {
@@ -90,8 +90,7 @@ func (b *builder) createGo(instr *ssa.Go) {
// * The function context, for closures. // * The function context, for closures.
// * The function pointer (for tasks). // * The function pointer (for tasks).
var context llvm.Value var context llvm.Value
funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value, getPos(instr))) funcPtrType, funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value, getPos(instr)), instr.Call.Value.Type().Underlying().(*types.Signature))
funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature))
params = append(params, context, funcPtr) params = append(params, context, funcPtr)
hasContext = true hasContext = true
prefix = b.fn.RelString(nil) prefix = b.fn.RelString(nil)
@@ -99,14 +98,14 @@ func (b *builder) createGo(instr *ssa.Go) {
paramBundle := b.emitPointerPack(params) paramBundle := b.emitPointerPack(params)
var stackSize llvm.Value var stackSize llvm.Value
callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, instr.Pos()) callee := b.createGoroutineStartWrapper(funcPtrType, funcPtr, prefix, hasContext, instr.Pos())
if b.AutomaticStackSize { if b.AutomaticStackSize {
// The stack size is not known until after linking. Call a dummy // The stack size is not known until after linking. Call a dummy
// function that will be replaced with a load from a special ELF // function that will be replaced with a load from a special ELF
// section that contains the stack size (and is modified after // section that contains the stack size (and is modified after
// linking). // linking).
stackSizeFnType, stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function)) stackSizeFnType, stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function))
stackSize = b.createCall(stackSizeFnType, stackSizeFn, []llvm.Value{callee, llvm.Undef(b.dataPtrType)}, "stacksize") stackSize = b.createCall(stackSizeFnType, stackSizeFn, []llvm.Value{callee, llvm.Undef(b.i8ptrType)}, "stacksize")
} else { } else {
// The stack size is fixed at compile time. By emitting it here as a // The stack size is fixed at compile time. By emitting it here as a
// constant, it can be optimized. // constant, it can be optimized.
@@ -116,7 +115,7 @@ func (b *builder) createGo(instr *ssa.Go) {
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false) stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
} }
fnType, start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function)) fnType, start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.dataPtrType)}, "") b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType)}, "")
} }
// createGoroutineStartWrapper creates a wrapper for the task-based // createGoroutineStartWrapper creates a wrapper for the task-based
@@ -166,7 +165,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
} }
// Create the wrapper. // Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false) wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType) wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
c.addStandardAttributes(wrapper) c.addStandardAttributes(wrapper)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
@@ -204,7 +203,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
} }
params := b.emitPointerUnpack(wrapper.Param(0), paramTypes) params := b.emitPointerUnpack(wrapper.Param(0), paramTypes)
if !hasContext { if !hasContext {
params = append(params, llvm.Undef(c.dataPtrType)) // add dummy context parameter params = append(params, llvm.Undef(c.i8ptrType)) // add dummy context parameter
} }
// Create the call. // Create the call.
@@ -212,7 +211,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
if c.Scheduler == "asyncify" { if c.Scheduler == "asyncify" {
b.CreateCall(deadlockType, deadlock, []llvm.Value{ b.CreateCall(deadlockType, deadlock, []llvm.Value{
llvm.Undef(c.dataPtrType), llvm.Undef(c.i8ptrType),
}, "") }, "")
} }
@@ -235,7 +234,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
// merged into one. // merged into one.
// Create the wrapper. // Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false) wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType) wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
c.addStandardAttributes(wrapper) c.addStandardAttributes(wrapper)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
@@ -280,7 +279,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
if c.Scheduler == "asyncify" { if c.Scheduler == "asyncify" {
b.CreateCall(deadlockType, deadlock, []llvm.Value{ b.CreateCall(deadlockType, deadlock, []llvm.Value{
llvm.Undef(c.dataPtrType), llvm.Undef(c.i8ptrType),
}, "") }, "")
} }
} }
+2 -2
View File
@@ -99,8 +99,8 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
case llvm.IntegerTypeKind: case llvm.IntegerTypeKind:
constraints = append(constraints, "r") constraints = append(constraints, "r")
case llvm.PointerTypeKind: case llvm.PointerTypeKind:
// Memory references require a type starting with LLVM 14, // Memory references require a type in LLVM 14, probably as a
// probably as a preparation for opaque pointers. // preparation for opaque pointers.
err = b.makeError(instr.Pos(), "support for pointer operands was dropped in TinyGo 0.23") err = b.makeError(instr.Pos(), "support for pointer operands was dropped in TinyGo 0.23")
return s return s
default: default:
+6 -6
View File
@@ -414,10 +414,10 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
}, typeFields...) }, typeFields...)
if hasMethodSet { if hasMethodSet {
typeFields = append([]llvm.Value{ typeFields = append([]llvm.Value{
c.getTypeMethodSet(typ), llvm.ConstBitCast(c.getTypeMethodSet(typ), c.i8ptrType),
}, typeFields...) }, typeFields...)
} }
alignment := c.targetData.TypeAllocSize(c.dataPtrType) alignment := c.targetData.TypeAllocSize(c.i8ptrType)
if alignment < 4 { if alignment < 4 {
alignment = 4 alignment = 4
} }
@@ -628,7 +628,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
// Construct global value. // Construct global value.
globalValue := c.ctx.ConstStruct([]llvm.Value{ globalValue := c.ctx.ConstStruct([]llvm.Value{
llvm.ConstInt(c.uintptrType, uint64(ms.Len()), false), llvm.ConstInt(c.uintptrType, uint64(ms.Len()), false),
llvm.ConstArray(c.dataPtrType, signatures), llvm.ConstArray(c.i8ptrType, signatures),
c.ctx.ConstStruct(wrappers, false), c.ctx.ConstStruct(wrappers, false),
}, false) }, false)
global = llvm.AddGlobal(c.mod, globalValue.Type(), globalName) global = llvm.AddGlobal(c.mod, globalValue.Type(), globalName)
@@ -779,7 +779,7 @@ func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) ll
fnName := s + ".$typeassert" fnName := s + ".$typeassert"
llvmFn := c.mod.NamedFunction(fnName) llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() { if llvmFn.IsNil() {
llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.dataPtrType}, false) llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.i8ptrType}, false)
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType) llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
c.addStandardDeclaredAttributes(llvmFn) c.addStandardDeclaredAttributes(llvmFn)
methods := c.getMethodsString(assertedType.Underlying().(*types.Interface)) methods := c.getMethodsString(assertedType.Underlying().(*types.Interface))
@@ -802,7 +802,7 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
paramTuple = append(paramTuple, sig.Params().At(i)) paramTuple = append(paramTuple, sig.Params().At(i))
} }
paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.UnsafePointer])) paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.UnsafePointer]))
llvmFnType := c.getLLVMFunctionType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false)) llvmFnType := c.getRawFuncType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false))
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType) llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
c.addStandardDeclaredAttributes(llvmFn) c.addStandardDeclaredAttributes(llvmFn)
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method))) llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method)))
@@ -842,7 +842,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
} }
// create wrapper function // create wrapper function
paramTypes := append([]llvm.Type{c.dataPtrType}, llvmFnType.ParamTypes()[len(expandedReceiverType):]...) paramTypes := append([]llvm.Type{c.i8ptrType}, llvmFnType.ParamTypes()[len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(llvmFnType.ReturnType(), paramTypes, false) wrapFnType := llvm.FunctionType(llvmFnType.ReturnType(), paramTypes, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType) wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
c.addStandardAttributes(wrapper) c.addStandardAttributes(wrapper)
+1 -1
View File
@@ -36,7 +36,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// Fall back to a generic error. // Fall back to a generic error.
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant") return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant")
} }
funcRawPtr, funcContext := b.decodeFuncValue(funcValue) _, funcRawPtr, funcContext := b.decodeFuncValue(funcValue, nil)
funcPtr := llvm.ConstPtrToInt(funcRawPtr, b.uintptrType) funcPtr := llvm.ConstPtrToInt(funcRawPtr, b.uintptrType)
// Create a new global of type runtime/interrupt.handle. Globals of this // Create a new global of type runtime/interrupt.handle. Globals of this
+10 -3
View File
@@ -7,6 +7,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -47,9 +48,12 @@ func (b *builder) defineIntrinsicFunction() {
func (b *builder) createMemoryCopyImpl() { func (b *builder) createMemoryCopyImpl() {
b.createFunctionStart(true) b.createFunctionStart(true)
fnName := "llvm." + b.fn.Name() + ".p0.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth()) fnName := "llvm." + b.fn.Name() + ".p0.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
if llvmutil.Major() < 15 { // compatibility with LLVM 14
fnName = "llvm." + b.fn.Name() + ".p0i8.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
}
llvmFn := b.mod.NamedFunction(fnName) llvmFn := b.mod.NamedFunction(fnName)
if llvmFn.IsNil() { if llvmFn.IsNil() {
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType, b.dataPtrType, b.uintptrType, b.ctx.Int1Type()}, false) fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.i8ptrType, b.uintptrType, b.ctx.Int1Type()}, false)
llvmFn = llvm.AddFunction(b.mod, fnName, fnType) llvmFn = llvm.AddFunction(b.mod, fnName, fnType)
} }
var params []llvm.Value var params []llvm.Value
@@ -80,9 +84,12 @@ func (b *builder) createMemoryZeroImpl() {
// Return the llvm.memset.p0.i8 function declaration. // Return the llvm.memset.p0.i8 function declaration.
func (c *compilerContext) getMemsetFunc() llvm.Value { func (c *compilerContext) getMemsetFunc() llvm.Value {
fnName := "llvm.memset.p0.i" + strconv.Itoa(c.uintptrType.IntTypeWidth()) fnName := "llvm.memset.p0.i" + strconv.Itoa(c.uintptrType.IntTypeWidth())
if llvmutil.Major() < 15 { // compatibility with LLVM 14
fnName = "llvm.memset.p0i8.i" + strconv.Itoa(c.uintptrType.IntTypeWidth())
}
llvmFn := c.mod.NamedFunction(fnName) llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() { if llvmFn.IsNil() {
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType, c.ctx.Int8Type(), c.uintptrType, c.ctx.Int1Type()}, false) fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType, c.ctx.Int8Type(), c.uintptrType, c.ctx.Int1Type()}, false)
llvmFn = llvm.AddFunction(c.mod, fnName, fnType) llvmFn = llvm.AddFunction(c.mod, fnName, fnType)
} }
return llvmFn return llvmFn
@@ -104,7 +111,7 @@ func (b *builder) createKeepAliveImpl() {
// //
// It should be portable to basically everything as the "r" register type // It should be portable to basically everything as the "r" register type
// exists basically everywhere. // exists basically everywhere.
asmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType}, false) asmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType}, false)
asmFn := llvm.InlineAsm(asmType, "", "r", true, false, 0, false) asmFn := llvm.InlineAsm(asmType, "", "r", true, false, 0, false)
b.createCall(asmType, asmFn, []llvm.Value{pointerValue}, "") b.createCall(asmType, asmFn, []llvm.Value{pointerValue}, "")
+44 -39
View File
@@ -20,7 +20,7 @@ import (
// //
// This is useful for creating temporary allocas for intrinsics. Don't forget to // This is useful for creating temporary allocas for intrinsics. Don't forget to
// end the lifetime using emitLifetimeEnd after you're done with it. // end the lifetime using emitLifetimeEnd after you're done with it.
func (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, size llvm.Value) { func (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
return llvmutil.CreateTemporaryAlloca(b.Builder, b.mod, t, name) return llvmutil.CreateTemporaryAlloca(b.Builder, b.mod, t, name)
} }
@@ -63,45 +63,47 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
// Allocate memory for the packed data. // Allocate memory for the packed data.
size := b.targetData.TypeAllocSize(packedType) size := b.targetData.TypeAllocSize(packedType)
if size == 0 { if size == 0 {
return llvm.ConstPointerNull(b.dataPtrType) return llvm.ConstPointerNull(b.i8ptrType)
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind { } else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
return values[0] return b.CreateBitCast(values[0], b.i8ptrType, "pack.ptr")
} else if size <= b.targetData.TypeAllocSize(b.dataPtrType) { } else if size <= b.targetData.TypeAllocSize(b.i8ptrType) {
// Packed data fits in a pointer, so store it directly inside the // Packed data fits in a pointer, so store it directly inside the
// pointer. // pointer.
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind { if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
// Try to keep this cast in SSA form. // Try to keep this cast in SSA form.
return b.CreateIntToPtr(values[0], b.dataPtrType, "pack.int") return b.CreateIntToPtr(values[0], b.i8ptrType, "pack.int")
} }
// Because packedType is a struct and we have to cast it to a *i8, store // Because packedType is a struct and we have to cast it to a *i8, store
// it in a *i8 alloca first and load the *i8 value from there. This is // it in a *i8 alloca first and load the *i8 value from there. This is
// effectively a bitcast. // effectively a bitcast.
packedAlloc, _ := b.createTemporaryAlloca(b.dataPtrType, "") packedAlloc, _, _ := b.createTemporaryAlloca(b.i8ptrType, "")
if size < b.targetData.TypeAllocSize(b.dataPtrType) { if size < b.targetData.TypeAllocSize(b.i8ptrType) {
// The alloca is bigger than the value that will be stored in it. // The alloca is bigger than the value that will be stored in it.
// To avoid having some bits undefined, zero the alloca first. // To avoid having some bits undefined, zero the alloca first.
// Hopefully this will get optimized away. // Hopefully this will get optimized away.
b.CreateStore(llvm.ConstNull(b.dataPtrType), packedAlloc) b.CreateStore(llvm.ConstNull(b.i8ptrType), packedAlloc)
} }
// Store all values in the alloca. // Store all values in the alloca.
packedAllocCast := b.CreateBitCast(packedAlloc, llvm.PointerType(packedType, 0), "")
for i, value := range values { for i, value := range values {
indices := []llvm.Value{ indices := []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
} }
gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "") gep := b.CreateInBoundsGEP(packedType, packedAllocCast, indices, "")
b.CreateStore(value, gep) b.CreateStore(value, gep)
} }
// Load value (the *i8) from the alloca. // Load value (the *i8) from the alloca.
result := b.CreateLoad(b.dataPtrType, packedAlloc, "") result := b.CreateLoad(b.i8ptrType, packedAlloc, "")
// End the lifetime of the alloca, to help the optimizer. // End the lifetime of the alloca, to help the optimizer.
packedPtr := b.CreateBitCast(packedAlloc, b.i8ptrType, "")
packedSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(packedAlloc.Type()), false) packedSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(packedAlloc.Type()), false)
b.emitLifetimeEnd(packedAlloc, packedSize) b.emitLifetimeEnd(packedPtr, packedSize)
return result return result
} else { } else {
@@ -122,20 +124,21 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
global.SetUnnamedAddr(true) global.SetUnnamedAddr(true)
global.SetLinkage(llvm.InternalLinkage) global.SetLinkage(llvm.InternalLinkage)
return global return llvm.ConstBitCast(global, b.i8ptrType)
} }
// Packed data is bigger than a pointer, so allocate it on the heap. // Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(b.uintptrType, size, false) sizeValue := llvm.ConstInt(b.uintptrType, size, false)
alloc := b.mod.NamedFunction("runtime.alloc") alloc := b.mod.NamedFunction("runtime.alloc")
packedAlloc := b.CreateCall(alloc.GlobalValueType(), alloc, []llvm.Value{ packedHeapAlloc := b.CreateCall(alloc.GlobalValueType(), alloc, []llvm.Value{
sizeValue, sizeValue,
llvm.ConstNull(b.dataPtrType), llvm.ConstNull(b.i8ptrType),
llvm.Undef(b.dataPtrType), // unused context parameter llvm.Undef(b.i8ptrType), // unused context parameter
}, "") }, "")
if b.NeedsStackObjects { if b.NeedsStackObjects {
b.trackPointer(packedAlloc) b.trackPointer(packedHeapAlloc)
} }
packedAlloc := b.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
// Store all values in the heap pointer. // Store all values in the heap pointer.
for i, value := range values { for i, value := range values {
@@ -148,7 +151,7 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
} }
// Return the original heap allocation pointer, which already is an *i8. // Return the original heap allocation pointer, which already is an *i8.
return packedAlloc return packedHeapAlloc
} }
} }
@@ -157,27 +160,28 @@ func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []ll
packedType := b.ctx.StructType(valueTypes, false) packedType := b.ctx.StructType(valueTypes, false)
// Get a correctly-typed pointer to the packed data. // Get a correctly-typed pointer to the packed data.
var packedAlloc llvm.Value var packedAlloc, packedRawAlloc llvm.Value
needsLifetimeEnd := false
size := b.targetData.TypeAllocSize(packedType) size := b.targetData.TypeAllocSize(packedType)
if size == 0 { if size == 0 {
// No data to unpack. // No data to unpack.
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind { } else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
// A single pointer is always stored directly. // A single pointer is always stored directly.
return []llvm.Value{ptr} return []llvm.Value{b.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
} else if size <= b.targetData.TypeAllocSize(b.dataPtrType) { } else if size <= b.targetData.TypeAllocSize(b.i8ptrType) {
// Packed data stored directly in pointer. // Packed data stored directly in pointer.
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind { if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
// Keep this cast in SSA form. // Keep this cast in SSA form.
return []llvm.Value{b.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")} return []llvm.Value{b.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
} }
// Fallback: load it using an alloca. // Fallback: load it using an alloca.
packedAlloc, _ = b.createTemporaryAlloca(b.dataPtrType, "unpack.raw.alloc") packedRawAlloc, _, _ = b.createTemporaryAlloca(llvm.PointerType(b.i8ptrType, 0), "unpack.raw.alloc")
b.CreateStore(ptr, packedAlloc) packedRawValue := b.CreateBitCast(ptr, llvm.PointerType(b.i8ptrType, 0), "unpack.raw.value")
needsLifetimeEnd = true b.CreateStore(packedRawValue, packedRawAlloc)
packedAlloc = b.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
} else { } else {
// Packed data stored on the heap. // Packed data stored on the heap. Bitcast the passed-in pointer to the
packedAlloc = ptr // correct pointer type.
packedAlloc = b.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr")
} }
// Load each value from the packed data. // Load each value from the packed data.
values := make([]llvm.Value, len(valueTypes)) values := make([]llvm.Value, len(valueTypes))
@@ -194,9 +198,10 @@ func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []ll
gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "") gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "")
values[i] = b.CreateLoad(valueType, gep, "") values[i] = b.CreateLoad(valueType, gep, "")
} }
if needsLifetimeEnd { if !packedRawAlloc.IsNil() {
allocPtr := b.CreateBitCast(packedRawAlloc, b.i8ptrType, "")
allocSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(b.uintptrType), false) allocSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(b.uintptrType), false)
b.emitLifetimeEnd(packedAlloc, allocSize) b.emitLifetimeEnd(allocPtr, allocSize)
} }
return values return values
} }
@@ -248,12 +253,12 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
// Do a few checks to see whether we need to generate any object layout // Do a few checks to see whether we need to generate any object layout
// information at all. // information at all.
objectSizeBytes := c.targetData.TypeAllocSize(t) objectSizeBytes := c.targetData.TypeAllocSize(t)
pointerSize := c.targetData.TypeAllocSize(c.dataPtrType) pointerSize := c.targetData.TypeAllocSize(c.i8ptrType)
pointerAlignment := c.targetData.PrefTypeAlignment(c.dataPtrType) pointerAlignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
if objectSizeBytes < pointerSize { if objectSizeBytes < pointerSize {
// Too small to contain a pointer. // Too small to contain a pointer.
layout := (uint64(1) << 1) | 1 layout := (uint64(1) << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType)
} }
bitmap := c.getPointerBitmap(t, pos) bitmap := c.getPointerBitmap(t, pos)
if bitmap.BitLen() == 0 { if bitmap.BitLen() == 0 {
@@ -261,13 +266,13 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
// TODO: this can be done in many other cases, e.g. when allocating an // TODO: this can be done in many other cases, e.g. when allocating an
// array (like [4][]byte, which repeats a slice 4 times). // array (like [4][]byte, which repeats a slice 4 times).
layout := (uint64(1) << 1) | 1 layout := (uint64(1) << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType)
} }
if objectSizeBytes%uint64(pointerAlignment) != 0 { if objectSizeBytes%uint64(pointerAlignment) != 0 {
// This shouldn't happen except for packed structs, which aren't // This shouldn't happen except for packed structs, which aren't
// currently used. // currently used.
c.addError(pos, "internal error: unexpected object size for object with pointer field") c.addError(pos, "internal error: unexpected object size for object with pointer field")
return llvm.ConstNull(c.dataPtrType) return llvm.ConstNull(c.i8ptrType)
} }
objectSizeWords := objectSizeBytes / uint64(pointerAlignment) objectSizeWords := objectSizeBytes / uint64(pointerAlignment)
@@ -292,7 +297,7 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
// The runtime knows that if the least significant bit of the pointer is // The runtime knows that if the least significant bit of the pointer is
// set, the pointer contains the value itself. // set, the pointer contains the value itself.
layout := bitmap.Uint64()<<(sizeFieldBits+1) | (objectSizeWords << 1) | 1 layout := bitmap.Uint64()<<(sizeFieldBits+1) | (objectSizeWords << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType)
} }
// Unfortunately, the object layout is too big to fit in a pointer-sized // Unfortunately, the object layout is too big to fit in a pointer-sized
@@ -303,7 +308,7 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", objectSizeWords, (objectSizeWords+15)/16, bitmap) globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", objectSizeWords, (objectSizeWords+15)/16, bitmap)
global := c.mod.NamedGlobal(globalName) global := c.mod.NamedGlobal(globalName)
if !global.IsNil() { if !global.IsNil() {
return global return llvm.ConstBitCast(global, c.i8ptrType)
} }
// Create the global initializer. // Create the global initializer.
@@ -354,13 +359,13 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
global.AddMetadata(0, diglobal) global.AddMetadata(0, diglobal)
} }
return global return llvm.ConstBitCast(global, c.i8ptrType)
} }
// getPointerBitmap scans the given LLVM type for pointers and sets bits in a // getPointerBitmap scans the given LLVM type for pointers and sets bits in a
// bigint at the word offset that contains a pointer. This scan is recursive. // bigint at the word offset that contains a pointer. This scan is recursive.
func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.Int { func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.Int {
alignment := c.targetData.PrefTypeAlignment(c.dataPtrType) alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
switch typ.TypeKind() { switch typ.TypeKind() {
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind: case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
return big.NewInt(0) return big.NewInt(0)
@@ -373,7 +378,7 @@ func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.In
// of type uintptr, but before the LowerFuncValues pass it actually // of type uintptr, but before the LowerFuncValues pass it actually
// contains a pointer (ptrtoint) to a global. This trips up the // contains a pointer (ptrtoint) to a global. This trips up the
// interp package. Therefore, make the id field a pointer for now. // interp package. Therefore, make the id field a pointer for now.
typ = c.ctx.StructType([]llvm.Type{c.dataPtrType, c.dataPtrType}, false) typ = c.ctx.StructType([]llvm.Type{c.i8ptrType, c.i8ptrType}, false)
} }
for i, subtyp := range typ.StructElementTypes() { for i, subtyp := range typ.StructElementTypes() {
subptrs := c.getPointerBitmap(subtyp, pos) subptrs := c.getPointerBitmap(subtyp, pos)
@@ -453,7 +458,7 @@ func (c *compilerContext) isThumb() bool {
func (b *builder) readStackPointer() llvm.Value { func (b *builder) readStackPointer() llvm.Value {
stacksave := b.mod.NamedFunction("llvm.stacksave") stacksave := b.mod.NamedFunction("llvm.stacksave")
if stacksave.IsNil() { if stacksave.IsNil() {
fnType := llvm.FunctionType(b.dataPtrType, nil, false) fnType := llvm.FunctionType(b.i8ptrType, nil, false)
stacksave = llvm.AddFunction(b.mod, "llvm.stacksave", fnType) stacksave = llvm.AddFunction(b.mod, "llvm.stacksave", fnType)
} }
return b.CreateCall(stacksave.GlobalValueType(), stacksave, nil, "") return b.CreateCall(stacksave.GlobalValueType(), stacksave, nil, "")
+34 -13
View File
@@ -8,9 +8,22 @@
package llvmutil package llvmutil
import ( import (
"strconv"
"strings"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
// Major returns the LLVM major version.
func Major() int {
llvmMajor, err := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
if err != nil {
// sanity check, should be unreachable
panic("could not parse LLVM version: " + err.Error())
}
return llvmMajor
}
// CreateEntryBlockAlloca creates a new alloca in the entry block, even though // CreateEntryBlockAlloca creates a new alloca in the entry block, even though
// the IR builder is located elsewhere. It assumes that the insert point is // the IR builder is located elsewhere. It assumes that the insert point is
// at the end of the current block. // at the end of the current block.
@@ -33,14 +46,16 @@ func CreateEntryBlockAlloca(builder llvm.Builder, t llvm.Type, name string) llvm
// //
// This is useful for creating temporary allocas for intrinsics. Don't forget to // This is useful for creating temporary allocas for intrinsics. Don't forget to
// end the lifetime using emitLifetimeEnd after you're done with it. // end the lifetime using emitLifetimeEnd after you're done with it.
func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, name string) (alloca, size llvm.Value) { func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
ctx := t.Context() ctx := t.Context()
targetData := llvm.NewTargetData(mod.DataLayout()) targetData := llvm.NewTargetData(mod.DataLayout())
defer targetData.Dispose() defer targetData.Dispose()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
alloca = CreateEntryBlockAlloca(builder, t, name) alloca = CreateEntryBlockAlloca(builder, t, name)
bitcast = builder.CreateBitCast(alloca, i8ptrType, name+".bitcast")
size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false) size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
fnType, fn := getLifetimeStartFunc(mod) fnType, fn := getLifetimeStartFunc(mod)
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "") builder.CreateCall(fnType, fn, []llvm.Value{size, bitcast}, "")
return return
} }
@@ -49,19 +64,21 @@ func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type,
ctx := mod.Context() ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout()) targetData := llvm.NewTargetData(mod.DataLayout())
defer targetData.Dispose() defer targetData.Dispose()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
alloca := CreateEntryBlockAlloca(builder, t, name) alloca := CreateEntryBlockAlloca(builder, t, name)
builder.SetInsertPointBefore(inst) builder.SetInsertPointBefore(inst)
bitcast := builder.CreateBitCast(alloca, i8ptrType, name+".bitcast")
size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false) size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
fnType, fn := getLifetimeStartFunc(mod) fnType, fn := getLifetimeStartFunc(mod)
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "") builder.CreateCall(fnType, fn, []llvm.Value{size, bitcast}, "")
if next := llvm.NextInstruction(inst); !next.IsNil() { if next := llvm.NextInstruction(inst); !next.IsNil() {
builder.SetInsertPointBefore(next) builder.SetInsertPointBefore(next)
} else { } else {
builder.SetInsertPointAtEnd(inst.InstructionParent()) builder.SetInsertPointAtEnd(inst.InstructionParent())
} }
fnType, fn = getLifetimeEndFunc(mod) fnType, fn = getLifetimeEndFunc(mod)
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "") builder.CreateCall(fnType, fn, []llvm.Value{size, bitcast}, "")
return alloca return alloca
} }
@@ -77,10 +94,13 @@ func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value
// first if it doesn't exist yet. // first if it doesn't exist yet.
func getLifetimeStartFunc(mod llvm.Module) (llvm.Type, llvm.Value) { func getLifetimeStartFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
fnName := "llvm.lifetime.start.p0" fnName := "llvm.lifetime.start.p0"
if Major() < 15 { // compatibility with LLVM 14
fnName = "llvm.lifetime.start.p0i8"
}
fn := mod.NamedFunction(fnName) fn := mod.NamedFunction(fnName)
ctx := mod.Context() ctx := mod.Context()
ptrType := llvm.PointerType(ctx.Int8Type(), 0) i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false) fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false)
if fn.IsNil() { if fn.IsNil() {
fn = llvm.AddFunction(mod, fnName, fnType) fn = llvm.AddFunction(mod, fnName, fnType)
} }
@@ -91,10 +111,13 @@ func getLifetimeStartFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
// first if it doesn't exist yet. // first if it doesn't exist yet.
func getLifetimeEndFunc(mod llvm.Module) (llvm.Type, llvm.Value) { func getLifetimeEndFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
fnName := "llvm.lifetime.end.p0" fnName := "llvm.lifetime.end.p0"
if Major() < 15 {
fnName = "llvm.lifetime.end.p0i8"
}
fn := mod.NamedFunction(fnName) fn := mod.NamedFunction(fnName)
ctx := mod.Context() ctx := mod.Context()
ptrType := llvm.PointerType(ctx.Int8Type(), 0) i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false) fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false)
if fn.IsNil() { if fn.IsNil() {
fn = llvm.AddFunction(mod, fnName, fnType) fn = llvm.AddFunction(mod, fnName, fnType)
} }
@@ -190,15 +213,13 @@ func AppendToGlobal(mod llvm.Module, globalName string, values ...llvm.Value) {
} }
// Add the new values. // Add the new values.
ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
for _, value := range values { for _, value := range values {
// Note: the bitcast is necessary to cast AVR function pointers to usedValues = append(usedValues, llvm.ConstPointerCast(value, i8ptrType))
// address space 0 pointer types.
usedValues = append(usedValues, llvm.ConstPointerCast(value, ptrType))
} }
// Create a new array (with the old and new values). // Create a new array (with the old and new values).
usedInitializer := llvm.ConstArray(ptrType, usedValues) usedInitializer := llvm.ConstArray(i8ptrType, usedValues)
used := llvm.AddGlobal(mod, usedInitializer.Type(), globalName) used := llvm.AddGlobal(mod, usedInitializer.Type(), globalName)
used.SetInitializer(usedInitializer) used.SetInitializer(usedInitializer)
used.SetLinkage(llvm.AppendingLinkage) used.SetLinkage(llvm.AppendingLinkage)
+30 -23
View File
@@ -65,7 +65,7 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val
// Allocate the memory for the resulting type. Do not zero this memory: it // Allocate the memory for the resulting type. Do not zero this memory: it
// will be zeroed by the hashmap get implementation if the key is not // will be zeroed by the hashmap get implementation if the key is not
// present in the map. // present in the map.
mapValueAlloca, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value") mapValueAlloca, mapValuePtr, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value")
// We need the map size (with type uintptr) to pass to the hashmap*Get // We need the map size (with type uintptr) to pass to the hashmap*Get
// functions. This is necessary because those *Get functions are valid on // functions. This is necessary because those *Get functions are valid on
@@ -82,19 +82,19 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val
keyType = keyType.Underlying() keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string // key is a string
params := []llvm.Value{m, key, mapValueAlloca, mapValueSize} params := []llvm.Value{m, key, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "") commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "")
} else if hashmapIsBinaryKey(keyType) { } else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal // key can be compared with runtime.memequal
// Store the key in an alloca, in the entry block to avoid dynamic stack // Store the key in an alloca, in the entry block to avoid dynamic stack
// growth. // growth.
mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, mapKeyAlloca) b.CreateStore(key, mapKeyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), mapKeyAlloca) b.zeroUndefBytes(b.getLLVMType(keyType), mapKeyAlloca)
// Fetch the value from the hashmap. // Fetch the value from the hashmap.
params := []llvm.Value{m, mapKeyAlloca, mapValueAlloca, mapValueSize} params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "") commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "")
b.emitLifetimeEnd(mapKeyAlloca, mapKeySize) b.emitLifetimeEnd(mapKeyPtr, mapKeySize)
} else { } else {
// Not trivially comparable using memcmp. Make it an interface instead. // Not trivially comparable using memcmp. Make it an interface instead.
itfKey := key itfKey := key
@@ -102,14 +102,14 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val
// Not already an interface, so convert it to an interface now. // Not already an interface, so convert it to an interface now.
itfKey = b.createMakeInterface(key, origKeyType, pos) itfKey = b.createMakeInterface(key, origKeyType, pos)
} }
params := []llvm.Value{m, itfKey, mapValueAlloca, mapValueSize} params := []llvm.Value{m, itfKey, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapInterfaceGet", params, "") commaOkValue = b.createRuntimeCall("hashmapInterfaceGet", params, "")
} }
// Load the resulting value from the hashmap. The value is set to the zero // Load the resulting value from the hashmap. The value is set to the zero
// value if the key doesn't exist in the hashmap. // value if the key doesn't exist in the hashmap.
mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "") mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "")
b.emitLifetimeEnd(mapValueAlloca, mapValueAllocaSize) b.emitLifetimeEnd(mapValuePtr, mapValueAllocaSize)
if commaOk { if commaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false)) tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false))
@@ -124,22 +124,22 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val
// createMapUpdate updates a map key to a given value, by creating an // createMapUpdate updates a map key to a given value, by creating an
// appropriate runtime call. // appropriate runtime call.
func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) { func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
valueAlloca, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value") valueAlloca, valuePtr, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value")
b.CreateStore(value, valueAlloca) b.CreateStore(value, valueAlloca)
origKeyType := keyType origKeyType := keyType
keyType = keyType.Underlying() keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string // key is a string
params := []llvm.Value{m, key, valueAlloca} params := []llvm.Value{m, key, valuePtr}
b.createRuntimeCall("hashmapStringSet", params, "") b.createRuntimeCall("hashmapStringSet", params, "")
} else if hashmapIsBinaryKey(keyType) { } else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal // key can be compared with runtime.memequal
keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca) b.CreateStore(key, keyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca) b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca)
params := []llvm.Value{m, keyAlloca, valueAlloca} params := []llvm.Value{m, keyPtr, valuePtr}
b.createRuntimeCall("hashmapBinarySet", params, "") b.createRuntimeCall("hashmapBinarySet", params, "")
b.emitLifetimeEnd(keyAlloca, keySize) b.emitLifetimeEnd(keyPtr, keySize)
} else { } else {
// Key is not trivially comparable, so compare it as an interface instead. // Key is not trivially comparable, so compare it as an interface instead.
itfKey := key itfKey := key
@@ -147,10 +147,10 @@ func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value,
// Not already an interface, so convert it to an interface first. // Not already an interface, so convert it to an interface first.
itfKey = b.createMakeInterface(key, origKeyType, pos) itfKey = b.createMakeInterface(key, origKeyType, pos)
} }
params := []llvm.Value{m, itfKey, valueAlloca} params := []llvm.Value{m, itfKey, valuePtr}
b.createRuntimeCall("hashmapInterfaceSet", params, "") b.createRuntimeCall("hashmapInterfaceSet", params, "")
} }
b.emitLifetimeEnd(valueAlloca, valueSize) b.emitLifetimeEnd(valuePtr, valueSize)
} }
// createMapDelete deletes a key from a map by calling the appropriate runtime // createMapDelete deletes a key from a map by calling the appropriate runtime
@@ -164,12 +164,12 @@ func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos tok
b.createRuntimeCall("hashmapStringDelete", params, "") b.createRuntimeCall("hashmapStringDelete", params, "")
return nil return nil
} else if hashmapIsBinaryKey(keyType) { } else if hashmapIsBinaryKey(keyType) {
keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca) b.CreateStore(key, keyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca) b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca)
params := []llvm.Value{m, keyAlloca} params := []llvm.Value{m, keyPtr}
b.createRuntimeCall("hashmapBinaryDelete", params, "") b.createRuntimeCall("hashmapBinaryDelete", params, "")
b.emitLifetimeEnd(keyAlloca, keySize) b.emitLifetimeEnd(keyPtr, keySize)
return nil return nil
} else { } else {
// Key is not trivially comparable, so compare it as an interface // Key is not trivially comparable, so compare it as an interface
@@ -225,9 +225,9 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv
} }
// Extract the key and value from the map. // Extract the key and value from the map.
mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(llvmStoredKeyType, "range.key") mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(llvmStoredKeyType, "range.key")
mapValueAlloca, mapValueSize := b.createTemporaryAlloca(llvmValueType, "range.value") mapValueAlloca, mapValuePtr, mapValueSize := b.createTemporaryAlloca(llvmValueType, "range.value")
ok := b.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyAlloca, mapValueAlloca}, "range.next") ok := b.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyPtr, mapValuePtr}, "range.next")
mapKey := b.CreateLoad(llvmStoredKeyType, mapKeyAlloca, "") mapKey := b.CreateLoad(llvmStoredKeyType, mapKeyAlloca, "")
mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "") mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "")
@@ -238,8 +238,8 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv
} }
// End the lifetimes of the allocas, because we're done with them. // End the lifetimes of the allocas, because we're done with them.
b.emitLifetimeEnd(mapKeyAlloca, mapKeySize) b.emitLifetimeEnd(mapKeyPtr, mapKeySize)
b.emitLifetimeEnd(mapValueAlloca, mapValueSize) b.emitLifetimeEnd(mapValuePtr, mapValueSize)
// Construct the *ssa.Next return value: {ok, mapKey, mapValue} // Construct the *ssa.Next return value: {ok, mapKey, mapValue}
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.Int1Type(), llvmKeyType, llvmValueType}, false)) tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.Int1Type(), llvmKeyType, llvmValueType}, false))
@@ -333,7 +333,14 @@ func (b *builder) zeroUndefBytes(llvmType llvm.Type, ptr llvm.Value) error {
if fieldEndOffset != nextOffset { if fieldEndOffset != nextOffset {
n := llvm.ConstInt(b.uintptrType, nextOffset-fieldEndOffset, false) n := llvm.ConstInt(b.uintptrType, nextOffset-fieldEndOffset, false)
llvmStoreSize := llvm.ConstInt(b.uintptrType, storeSize, false) llvmStoreSize := llvm.ConstInt(b.uintptrType, storeSize, false)
paddingStart := b.CreateInBoundsGEP(b.ctx.Int8Type(), elemPtr, []llvm.Value{llvmStoreSize}, "") gepPtr := elemPtr
if gepPtr.Type() != b.i8ptrType {
gepPtr = b.CreateBitCast(gepPtr, b.i8ptrType, "") // LLVM 14
}
paddingStart := b.CreateInBoundsGEP(b.ctx.Int8Type(), gepPtr, []llvm.Value{llvmStoreSize}, "")
if paddingStart.Type() != b.i8ptrType {
paddingStart = b.CreateBitCast(paddingStart, b.i8ptrType, "") // LLVM 14
}
b.createRuntimeCall("memzero", []llvm.Value{paddingStart, n}, "") b.createRuntimeCall("memzero", []llvm.Value{paddingStart, n}, "")
} }
} }
+24 -17
View File
@@ -96,7 +96,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
// Add an extra parameter as the function context. This context is used in // Add an extra parameter as the function context. This context is used in
// closures and bound methods, but should be optimized away when not used. // closures and bound methods, but should be optimized away when not used.
if !info.exported { if !info.exported {
paramInfos = append(paramInfos, paramInfo{llvmType: c.dataPtrType, name: "context", elemSize: 0}) paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", elemSize: 0})
} }
var paramTypes []llvm.Type var paramTypes []llvm.Type
@@ -145,18 +145,20 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
for _, attrName := range []string{"noalias", "nonnull"} { for _, attrName := range []string{"noalias", "nonnull"} {
llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0)) llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0))
} }
// Add attributes to signal to LLVM that this is an allocator function. if llvmutil.Major() >= 15 { // allockind etc are not available in LLVM 14
// This enables a number of optimizations. // Add attributes to signal to LLVM that this is an allocator
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("allockind"), allocKindAlloc|allocKindZeroed)) // function. This enables a number of optimizations.
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("alloc-family", "runtime.alloc")) llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("allockind"), allocKindAlloc|allocKindZeroed))
// Use a special value to indicate the first parameter: llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("alloc-family", "runtime.alloc"))
// > allocsize has two integer arguments, but because they're both 32 bits, we can // Use a special value to indicate the first parameter:
// > pack them into one 64-bit value, at the cost of making said value // > allocsize has two integer arguments, but because they're both 32 bits, we can
// > nonsensical. // > pack them into one 64-bit value, at the cost of making said value
// > // > nonsensical.
// > In order to do this, we need to reserve one value of the second (optional) // >
// > allocsize argument to signify "not present." // > In order to do this, we need to reserve one value of the second (optional)
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("allocsize"), 0x0000_0000_ffff_ffff)) // > allocsize argument to signify "not present."
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("allocsize"), 0x0000_0000_ffff_ffff))
}
case "runtime.sliceAppend": case "runtime.sliceAppend":
// Appending a slice will only read the to-be-appended slice, it won't // Appending a slice will only read the to-be-appended slice, it won't
// be modified. // be modified.
@@ -443,10 +445,15 @@ func (c *compilerContext) addStandardDefinedAttributes(llvmFn llvm.Value) {
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nounwind"), 0)) llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nounwind"), 0))
if strings.Split(c.Triple, "-")[0] == "x86_64" { if strings.Split(c.Triple, "-")[0] == "x86_64" {
// Required by the ABI. // Required by the ABI.
// The uwtable has two possible values: sync (1) or async (2). We use if llvmutil.Major() < 15 {
// sync because we currently don't use async unwind tables. // Needed for LLVM 14 support.
// For details, see: https://llvm.org/docs/LangRef.html#function-attributes llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 0))
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 1)) } else {
// The uwtable has two possible values: sync (1) or async (2). We
// use sync because we currently don't use async unwind tables.
// For details, see: https://llvm.org/docs/LangRef.html#function-attributes
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 1))
}
} }
} }
+1 -1
View File
@@ -183,7 +183,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
} }
llvmType := llvm.FunctionType(b.uintptrType, paramTypes, false) llvmType := llvm.FunctionType(b.uintptrType, paramTypes, false)
fn := b.getValue(call.Args[0], getPos(call)) fn := b.getValue(call.Args[0], getPos(call))
fnPtr := b.CreateIntToPtr(fn, b.dataPtrType, "") fnPtr := b.CreateIntToPtr(fn, llvm.PointerType(llvmType, 0), "")
// Prepare some functions that will be called later. // Prepare some functions that will be called later.
setLastError := b.mod.NamedFunction("SetLastError") setLastError := b.mod.NamedFunction("SetLastError")
+1 -1
View File
@@ -16,7 +16,7 @@ I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help .PHONY: help
help: help:
@echo "Please use \`$(MAKE) <target>' where <target> is one of" @echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files" @echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories" @echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file" @echo " singlehtml to make a single large HTML file"
+1 -1
View File
@@ -18,7 +18,7 @@ require (
golang.org/x/sys v0.11.0 golang.org/x/sys v0.11.0
golang.org/x/tools v0.12.0 golang.org/x/tools v0.12.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
tinygo.org/x/go-llvm v0.0.0-20230920233244-32ed56c6be9c tinygo.org/x/go-llvm v0.0.0-20230918183930-9edb6403d0bc
) )
require ( require (
+2 -2
View File
@@ -65,5 +65,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
tinygo.org/x/go-llvm v0.0.0-20230920233244-32ed56c6be9c h1:rS8mAFqf0CfxPCbtfsI3bWL4jkb0TBYA1wx7tY1nu28= tinygo.org/x/go-llvm v0.0.0-20230918183930-9edb6403d0bc h1:IVX1dqCX3c88P7iEMBtz1xCAM4UIqCMgbqHdSefBaWE=
tinygo.org/x/go-llvm v0.0.0-20230920233244-32ed56c6be9c/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= tinygo.org/x/go-llvm v0.0.0-20230918183930-9edb6403d0bc/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0=
+2 -2
View File
@@ -60,10 +60,10 @@ func (r *runner) errorAt(inst instruction, err error) *Error {
pos := getPosition(inst.llvmInst) pos := getPosition(inst.llvmInst)
return &Error{ return &Error{
ImportPath: r.pkgName, ImportPath: r.pkgName,
Inst: inst.llvmInst.String(), Inst: inst.String(),
Pos: pos, Pos: pos,
Err: err, Err: err,
Traceback: []ErrorLine{{pos, inst.llvmInst.String()}}, Traceback: []ErrorLine{{pos, inst.String()}},
} }
} }
+6 -5
View File
@@ -21,7 +21,7 @@ type runner struct {
targetData llvm.TargetData targetData llvm.TargetData
builder llvm.Builder builder llvm.Builder
pointerSize uint32 // cached pointer size from the TargetData pointerSize uint32 // cached pointer size from the TargetData
dataPtrType llvm.Type // often used type so created in advance i8ptrType llvm.Type // often used type so created in advance
uintptrType llvm.Type // equivalent to uintptr in Go uintptrType llvm.Type // equivalent to uintptr in Go
maxAlign int // maximum alignment of an object, alignment of runtime.alloc() result maxAlign int // maximum alignment of an object, alignment of runtime.alloc() result
debug bool // log debug messages debug bool // log debug messages
@@ -46,9 +46,9 @@ func newRunner(mod llvm.Module, timeout time.Duration, debug bool) *runner {
timeout: timeout, timeout: timeout,
} }
r.pointerSize = uint32(r.targetData.PointerSize()) r.pointerSize = uint32(r.targetData.PointerSize())
r.dataPtrType = llvm.PointerType(mod.Context().Int8Type(), 0) r.i8ptrType = llvm.PointerType(mod.Context().Int8Type(), 0)
r.uintptrType = mod.Context().IntType(r.targetData.PointerSize() * 8) r.uintptrType = mod.Context().IntType(r.targetData.PointerSize() * 8)
r.maxAlign = r.targetData.PrefTypeAlignment(r.dataPtrType) // assume pointers are maximally aligned (this is not always the case) r.maxAlign = r.targetData.PrefTypeAlignment(r.i8ptrType) // assume pointers are maximally aligned (this is not always the case)
return &r return &r
} }
@@ -126,7 +126,7 @@ func Run(mod llvm.Module, timeout time.Duration, debug bool) error {
mem.revert() mem.revert()
// Create a call to the package initializer (which was // Create a call to the package initializer (which was
// previously deleted). // previously deleted).
i8undef := llvm.Undef(r.dataPtrType) i8undef := llvm.Undef(r.i8ptrType)
r.builder.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{i8undef}, "") r.builder.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{i8undef}, "")
// Make sure that any globals touched by the package // Make sure that any globals touched by the package
// initializer, won't be accessed by later package initializers. // initializer, won't be accessed by later package initializers.
@@ -174,7 +174,8 @@ func Run(mod llvm.Module, timeout time.Duration, debug bool) error {
newGlobal.SetLinkage(obj.llvmGlobal.Linkage()) newGlobal.SetLinkage(obj.llvmGlobal.Linkage())
newGlobal.SetAlignment(obj.llvmGlobal.Alignment()) newGlobal.SetAlignment(obj.llvmGlobal.Alignment())
// TODO: copy debug info, unnamed_addr, ... // TODO: copy debug info, unnamed_addr, ...
obj.llvmGlobal.ReplaceAllUsesWith(newGlobal) bitcast := llvm.ConstBitCast(newGlobal, obj.llvmGlobal.Type())
obj.llvmGlobal.ReplaceAllUsesWith(bitcast)
name := obj.llvmGlobal.Name() name := obj.llvmGlobal.Name()
obj.llvmGlobal.EraseFromParentAsGlobal() obj.llvmGlobal.EraseFromParentAsGlobal()
newGlobal.SetName(name) newGlobal.SetName(name)
+6 -3
View File
@@ -77,9 +77,12 @@ func runTest(t *testing.T, pathPrefix string) {
} }
// Run some cleanup passes to get easy-to-read outputs. // Run some cleanup passes to get easy-to-read outputs.
to := llvm.NewPassBuilderOptions() pm := llvm.NewPassManager()
defer to.Dispose() defer pm.Dispose()
mod.RunPasses("globalopt,dse,adce", llvm.TargetMachine{}, to) pm.AddGlobalOptimizerPass()
pm.AddDeadStoreEliminationPass()
pm.AddAggressiveDCEPass()
pm.Run(mod)
// Read the expected output IR. // Read the expected output IR.
out, err := os.ReadFile(pathPrefix + ".out.ll") out, err := os.ReadFile(pathPrefix + ".out.ll")
+13 -1
View File
@@ -543,7 +543,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// how this function got called. // how this function got called.
callErr.Traceback = append(callErr.Traceback, ErrorLine{ callErr.Traceback = append(callErr.Traceback, ErrorLine{
Pos: getPosition(inst.llvmInst), Pos: getPosition(inst.llvmInst),
Inst: inst.llvmInst.String(), Inst: inst.String(),
}) })
return nil, mem, callErr return nil, mem, callErr
} }
@@ -1046,3 +1046,15 @@ func intPredicateString(predicate llvm.IntPredicate) string {
return "cmp?" return "cmp?"
} }
} }
// Strip some pointer casts. This is probably unnecessary once support for
// LLVM 14 (non-opaque pointers) is dropped.
func stripPointerCasts(value llvm.Value) llvm.Value {
if !value.IsAConstantExpr().IsNil() {
switch value.Opcode() {
case llvm.GetElementPtr, llvm.BitCast:
return stripPointerCasts(value.Operand(0))
}
}
return value
}
+15 -4
View File
@@ -658,11 +658,20 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
if v.offset() != 0 { if v.offset() != 0 {
// If there is an offset, make sure to use a GEP to index into the // If there is an offset, make sure to use a GEP to index into the
// pointer. // pointer.
// Cast to an i8* first (if needed) for easy indexing.
if llvmValue.Type() != mem.r.i8ptrType {
llvmValue = llvm.ConstBitCast(llvmValue, mem.r.i8ptrType)
}
llvmValue = llvm.ConstInBoundsGEP(mem.r.mod.Context().Int8Type(), llvmValue, []llvm.Value{ llvmValue = llvm.ConstInBoundsGEP(mem.r.mod.Context().Int8Type(), llvmValue, []llvm.Value{
llvm.ConstInt(mem.r.mod.Context().Int32Type(), uint64(v.offset()), false), llvm.ConstInt(mem.r.mod.Context().Int32Type(), uint64(v.offset()), false),
}) })
} }
// If a particular LLVM pointer type is requested, cast to it.
if !llvmType.IsNil() && llvmType != llvmValue.Type() {
llvmValue = llvm.ConstBitCast(llvmValue, llvmType)
}
return llvmValue, nil return llvmValue, nil
} }
@@ -863,7 +872,7 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
if err != nil { if err != nil {
panic(err) panic(err)
} }
if checks && mem.r.targetData.TypeAllocSize(llvmType) != mem.r.targetData.TypeAllocSize(mem.r.dataPtrType) { if checks && mem.r.targetData.TypeAllocSize(llvmType) != mem.r.targetData.TypeAllocSize(mem.r.i8ptrType) {
// Probably trying to serialize a pointer to a byte array, // Probably trying to serialize a pointer to a byte array,
// perhaps as a result of rawLLVMValue() in a previous interp // perhaps as a result of rawLLVMValue() in a previous interp
// run. // run.
@@ -945,6 +954,8 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
// Because go-llvm doesn't have addrspacecast at the moment, // Because go-llvm doesn't have addrspacecast at the moment,
// do it indirectly with a ptrtoint/inttoptr pair. // do it indirectly with a ptrtoint/inttoptr pair.
llvmValue = llvm.ConstIntToPtr(llvm.ConstPtrToInt(llvmValue, mem.r.uintptrType), llvmType) llvmValue = llvm.ConstIntToPtr(llvm.ConstPtrToInt(llvmValue, mem.r.uintptrType), llvmType)
} else {
llvmValue = llvm.ConstBitCast(llvmValue, llvmType)
} }
} }
return llvmValue, nil return llvmValue, nil
@@ -1245,7 +1256,7 @@ func (r *runner) getValue(llvmValue llvm.Value) value {
// For details on this format, see src/runtime/gc_precise.go. // For details on this format, see src/runtime/gc_precise.go.
func (r *runner) readObjectLayout(layoutValue value) (uint64, *big.Int) { func (r *runner) readObjectLayout(layoutValue value) (uint64, *big.Int) {
pointerSize := layoutValue.len(r) pointerSize := layoutValue.len(r)
if checks && uint64(pointerSize) != r.targetData.TypeAllocSize(r.dataPtrType) { if checks && uint64(pointerSize) != r.targetData.TypeAllocSize(r.i8ptrType) {
panic("inconsistent pointer size") panic("inconsistent pointer size")
} }
@@ -1320,12 +1331,12 @@ func (r *runner) getLLVMTypeFromLayout(layoutValue value) llvm.Type {
// Create the LLVM type. // Create the LLVM type.
pointerSize := layoutValue.len(r) pointerSize := layoutValue.len(r)
pointerAlignment := r.targetData.PrefTypeAlignment(r.dataPtrType) pointerAlignment := r.targetData.PrefTypeAlignment(r.i8ptrType)
var fields []llvm.Type var fields []llvm.Type
for i := 0; i < int(objectSizeWords); { for i := 0; i < int(objectSizeWords); {
if bitmap.Bit(i) != 0 { if bitmap.Bit(i) != 0 {
// Pointer field. // Pointer field.
fields = append(fields, r.dataPtrType) fields = append(fields, r.i8ptrType)
i += int(pointerSize / uint32(pointerAlignment)) i += int(pointerSize / uint32(pointerAlignment))
} else { } else {
// Byte/word field. // Byte/word field.
+1
View File
@@ -1,3 +1,4 @@
; TODO: remove these in LLVM 15
#define __tmp_reg__ r16 #define __tmp_reg__ r16
#define __zero_reg__ r17 #define __zero_reg__ r17
-8
View File
@@ -9,7 +9,6 @@ int mul(int, int);
#include <string.h> #include <string.h>
#cgo CFLAGS: -DSOME_CONSTANT=17 #cgo CFLAGS: -DSOME_CONSTANT=17
#define someDefine -5 + 2 * 7 #define someDefine -5 + 2 * 7
bool someBool;
*/ */
import "C" import "C"
@@ -53,13 +52,6 @@ func main() {
println("static headerfunc:", C.headerfunc_static(5)) println("static headerfunc:", C.headerfunc_static(5))
headerfunc_2() headerfunc_2()
// equivalent types
var goInt8 int8 = 5
var _ C.int8_t = goInt8
var _ bool = C.someBool
var _ C._Bool = C.someBool
// more globals // more globals
println("bool:", C.globalBool, C.globalBool2 == true) println("bool:", C.globalBool, C.globalBool2 == true)
println("float:", C.globalFloat) println("float:", C.globalFloat)
+21 -65
View File
@@ -10,11 +10,9 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"sync"
"text/template" "text/template"
"unicode" "unicode"
) )
@@ -1443,31 +1441,6 @@ __isr_vector:
return w.Flush() return w.Flush()
} }
// Process a single SVD file, synchronously.
func processFile(infile, outdir, sourceURL, interruptSystem string) error {
device, err := readSVD(infile, sourceURL)
if err != nil {
return fmt.Errorf("failed to read: %w", err)
}
err = writeGo(outdir, device, interruptSystem)
if err != nil {
return fmt.Errorf("failed to write Go file: %w", err)
}
switch interruptSystem {
case "software":
// Nothing to do.
case "hardware":
err = writeAsm(outdir, device)
if err != nil {
return fmt.Errorf("failed to write assembly file: %w", err)
}
default:
return fmt.Errorf("unknown interrupt system: %s", interruptSystem)
}
return nil
}
// Process an entire directory, in parallel.
func generate(indir, outdir, sourceURL, interruptSystem string) error { func generate(indir, outdir, sourceURL, interruptSystem string) error {
if _, err := os.Stat(indir); errors.Is(err, fs.ErrNotExist) { if _, err := os.Stat(indir); errors.Is(err, fs.ErrNotExist) {
fmt.Fprintln(os.Stderr, "cannot find input directory:", indir) fmt.Fprintln(os.Stderr, "cannot find input directory:", indir)
@@ -1475,52 +1448,35 @@ func generate(indir, outdir, sourceURL, interruptSystem string) error {
} }
os.MkdirAll(outdir, 0777) os.MkdirAll(outdir, 0777)
// Read list of SVD files to process.
infiles, err := filepath.Glob(filepath.Join(indir, "*.svd")) infiles, err := filepath.Glob(filepath.Join(indir, "*.svd"))
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, "could not read .svd files:", err) fmt.Fprintln(os.Stderr, "could not read .svd files:", err)
os.Exit(1) os.Exit(1)
} }
sort.Strings(infiles) sort.Strings(infiles)
for _, infile := range infiles {
// Start worker goroutines. fmt.Println(infile)
var wg sync.WaitGroup device, err := readSVD(infile, sourceURL)
workChan := make(chan string) if err != nil {
errChan := make(chan error, 1) return fmt.Errorf("failed to read: %w", err)
for i := 0; i < runtime.NumCPU(); i++ { }
go func() { err = writeGo(outdir, device, interruptSystem)
for infile := range workChan { if err != nil {
err := processFile(infile, outdir, sourceURL, interruptSystem) return fmt.Errorf("failed to write Go file: %w", err)
if err != nil { }
// Store error to errChan if no error was stored before. switch interruptSystem {
select { case "software":
case errChan <- err: // Nothing to do.
default: case "hardware":
} err = writeAsm(outdir, device)
} if err != nil {
wg.Done() return fmt.Errorf("failed to write assembly file: %w", err)
} }
}() default:
} return fmt.Errorf("unknown interrupt system: %s", interruptSystem)
}
// Submit all jobs to the goroutines.
wg.Add(len(infiles))
for _, filepath := range infiles {
fmt.Println(filepath)
workChan <- filepath
}
close(workChan)
// Wait until all workers have finished.
wg.Wait()
// Check for an error.
select {
case err := <-errChan:
return err
default:
return nil
} }
return nil
} }
func main() { func main() {
+5 -4
View File
@@ -37,7 +37,7 @@ func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, logger func(tok
targetData := llvm.NewTargetData(mod.DataLayout()) targetData := llvm.NewTargetData(mod.DataLayout())
defer targetData.Dispose() defer targetData.Dispose()
ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
builder := mod.Context().NewBuilder() builder := mod.Context().NewBuilder()
defer builder.Dispose() defer builder.Dispose()
@@ -110,7 +110,7 @@ func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, logger func(tok
} else { } else {
alignment = 8 alignment = 8
} }
if pointerAlignment := targetData.ABITypeAlignment(ptrType); pointerAlignment < alignment { if pointerAlignment := targetData.ABITypeAlignment(i8ptrType); pointerAlignment < alignment {
// Use min(alignment, alignof(void*)) as the alignment. // Use min(alignment, alignof(void*)) as the alignment.
alignment = pointerAlignment alignment = pointerAlignment
} }
@@ -120,7 +120,7 @@ func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, logger func(tok
fn := bitcast.InstructionParent().Parent() fn := bitcast.InstructionParent().Parent()
builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction()) builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
allocaType := llvm.ArrayType(mod.Context().Int8Type(), int(size)) allocaType := llvm.ArrayType(mod.Context().Int8Type(), int(size))
alloca := builder.CreateAlloca(allocaType, "stackalloc") alloca := builder.CreateAlloca(allocaType, "stackalloc.alloca")
alloca.SetAlignment(alignment) alloca.SetAlignment(alignment)
// Zero the allocation inside the block where the value was originally allocated. // Zero the allocation inside the block where the value was originally allocated.
@@ -130,7 +130,8 @@ func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, logger func(tok
store.SetAlignment(alignment) store.SetAlignment(alignment)
// Replace heap alloc bitcast with stack alloc bitcast. // Replace heap alloc bitcast with stack alloc bitcast.
bitcast.ReplaceAllUsesWith(alloca) stackalloc := builder.CreateBitCast(alloca, bitcast.Type(), "stackalloc")
bitcast.ReplaceAllUsesWith(stackalloc)
if heapalloc != bitcast { if heapalloc != bitcast {
bitcast.EraseFromParentAsInstruction() bitcast.EraseFromParentAsInstruction()
} }
+5 -6
View File
@@ -38,12 +38,11 @@ func TestAllocs2(t *testing.T) {
mod := compileGoFileForTesting(t, "./testdata/allocs2.go") mod := compileGoFileForTesting(t, "./testdata/allocs2.go")
// Run functionattrs pass, which is necessary for escape analysis. // Run functionattrs pass, which is necessary for escape analysis.
po := llvm.NewPassBuilderOptions() pm := llvm.NewPassManager()
defer po.Dispose() defer pm.Dispose()
err := mod.RunPasses("function(instcombine),function-attrs", llvm.TargetMachine{}, po) pm.AddInstructionCombiningPass()
if err != nil { pm.AddFunctionAttrsPass()
t.Error("failed to run passes:", err) pm.Run(mod)
}
// Run heap to stack transform. // Run heap to stack transform.
var testOutputs []allocsTestOutput var testOutputs []allocsTestOutput
+2 -1
View File
@@ -225,7 +225,8 @@ func MakeGCStackSlots(mod llvm.Module) bool {
llvm.ConstInt(ctx.Int32Type(), 0, false), llvm.ConstInt(ctx.Int32Type(), 0, false),
}, "") }, "")
builder.CreateStore(parent, gep) builder.CreateStore(parent, gep)
builder.CreateStore(stackObject, stackChainStart) stackObjectCast := builder.CreateBitCast(stackObject, stackChainStartType, "")
builder.CreateStore(stackObjectCast, stackChainStart)
// Do a store to the stack object after each new pointer that is created. // Do a store to the stack object after each new pointer that is created.
pointerStores := make(map[llvm.Value]struct{}) pointerStores := make(map[llvm.Value]struct{})
+24 -7
View File
@@ -92,7 +92,7 @@ type lowerInterfacesPass struct {
ctx llvm.Context ctx llvm.Context
uintptrType llvm.Type uintptrType llvm.Type
targetData llvm.TargetData targetData llvm.TargetData
ptrType llvm.Type i8ptrType llvm.Type
types map[string]*typeInfo types map[string]*typeInfo
signatures map[string]*signatureInfo signatures map[string]*signatureInfo
interfaces map[string]*interfaceInfo interfaces map[string]*interfaceInfo
@@ -113,7 +113,7 @@ func LowerInterfaces(mod llvm.Module, config *compileopts.Config) error {
ctx: ctx, ctx: ctx,
targetData: targetData, targetData: targetData,
uintptrType: mod.Context().IntType(targetData.PointerSize() * 8), uintptrType: mod.Context().IntType(targetData.PointerSize() * 8),
ptrType: llvm.PointerType(ctx.Int8Type(), 0), i8ptrType: llvm.PointerType(ctx.Int8Type(), 0),
types: make(map[string]*typeInfo), types: make(map[string]*typeInfo),
signatures: make(map[string]*signatureInfo), signatures: make(map[string]*signatureInfo),
interfaces: make(map[string]*interfaceInfo), interfaces: make(map[string]*interfaceInfo),
@@ -356,9 +356,11 @@ func (p *lowerInterfacesPass) run() error {
} }
// Fallback. // Fallback.
if hasUses(t.typecode) { if hasUses(t.typecode) {
negativeOffset := -int64(p.targetData.TypeAllocSize(p.ptrType)) bitcast := llvm.ConstBitCast(newGlobal, p.i8ptrType)
gep := p.builder.CreateInBoundsGEP(p.ctx.Int8Type(), newGlobal, []llvm.Value{llvm.ConstInt(p.ctx.Int32Type(), uint64(negativeOffset), true)}, "") negativeOffset := -int64(p.targetData.TypeAllocSize(p.i8ptrType))
t.typecode.ReplaceAllUsesWith(gep) gep := p.builder.CreateInBoundsGEP(p.ctx.Int8Type(), bitcast, []llvm.Value{llvm.ConstInt(p.ctx.Int32Type(), uint64(negativeOffset), true)}, "")
bitcast2 := llvm.ConstBitCast(gep, t.typecode.Type())
t.typecode.ReplaceAllUsesWith(bitcast2)
} }
t.typecode.EraseFromParentAsGlobal() t.typecode.EraseFromParentAsGlobal()
newGlobal.SetName(typecodeName) newGlobal.SetName(typecodeName)
@@ -512,7 +514,7 @@ func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *inte
params[i] = fn.Param(i + 1) params[i] = fn.Param(i + 1)
} }
params = append(params, params = append(params,
llvm.Undef(p.ptrType), llvm.Undef(p.i8ptrType),
) )
// Start chain in the entry block. // Start chain in the entry block.
@@ -552,12 +554,27 @@ func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *inte
p.builder.SetInsertPointAtEnd(bb) p.builder.SetInsertPointAtEnd(bb)
receiver := fn.FirstParam() receiver := fn.FirstParam()
if receiver.Type() != function.FirstParam().Type() {
// When the receiver is a pointer, it is not wrapped. This means the
// i8* has to be cast to the correct pointer type of the target
// function.
receiver = p.builder.CreateBitCast(receiver, function.FirstParam().Type(), "")
}
// Check whether the called function has the same signature as would be
// expected from the parameters. This can happen in rare cases when
// named struct types are renamed after merging multiple LLVM modules.
paramTypes := []llvm.Type{receiver.Type()} paramTypes := []llvm.Type{receiver.Type()}
for _, param := range params { for _, param := range params {
paramTypes = append(paramTypes, param.Type()) paramTypes = append(paramTypes, param.Type())
} }
calledFunctionType := function.Type()
functionType := llvm.FunctionType(returnType, paramTypes, false) functionType := llvm.FunctionType(returnType, paramTypes, false)
sig := llvm.PointerType(functionType, calledFunctionType.PointerAddressSpace())
if sig != function.Type() {
function = p.builder.CreateBitCast(function, sig, "")
}
retval := p.builder.CreateCall(functionType, function, append([]llvm.Value{receiver}, params...), "") retval := p.builder.CreateCall(functionType, function, append([]llvm.Value{receiver}, params...), "")
if retval.Type().TypeKind() == llvm.VoidTypeKind { if retval.Type().TypeKind() == llvm.VoidTypeKind {
p.builder.CreateRetVoid() p.builder.CreateRetVoid()
@@ -579,7 +596,7 @@ func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *inte
// method on a nil interface. // method on a nil interface.
nilPanic := p.mod.NamedFunction("runtime.nilPanic") nilPanic := p.mod.NamedFunction("runtime.nilPanic")
p.builder.CreateCall(nilPanic.GlobalValueType(), nilPanic, []llvm.Value{ p.builder.CreateCall(nilPanic.GlobalValueType(), nilPanic, []llvm.Value{
llvm.Undef(p.ptrType), llvm.Undef(p.i8ptrType),
}, "") }, "")
p.builder.CreateUnreachable() p.builder.CreateUnreachable()
} }
+4 -6
View File
@@ -15,11 +15,9 @@ func TestInterfaceLowering(t *testing.T) {
t.Error(err) t.Error(err)
} }
po := llvm.NewPassBuilderOptions() pm := llvm.NewPassManager()
defer po.Dispose() defer pm.Dispose()
err = mod.RunPasses("globaldce", llvm.TargetMachine{}, po) pm.AddGlobalDCEPass()
if err != nil { pm.Run(mod)
t.Error("failed to run passes:", err)
}
}) })
} }
+5 -6
View File
@@ -15,11 +15,10 @@ func TestOptimizeMaps(t *testing.T) {
// Run an optimization pass, to clean up the result. // Run an optimization pass, to clean up the result.
// This shows that all code related to the map is really eliminated. // This shows that all code related to the map is really eliminated.
po := llvm.NewPassBuilderOptions() pm := llvm.NewPassManager()
defer po.Dispose() defer pm.Dispose()
err := mod.RunPasses("dse,adce", llvm.TargetMachine{}, po) pm.AddDeadStoreEliminationPass()
if err != nil { pm.AddAggressiveDCEPass()
t.Error("failed to run passes:", err) pm.Run(mod)
}
}) })
} }
+70 -32
View File
@@ -14,22 +14,54 @@ import (
// OptimizePackage runs optimization passes over the LLVM module for the given // OptimizePackage runs optimization passes over the LLVM module for the given
// Go package. // Go package.
func OptimizePackage(mod llvm.Module, config *compileopts.Config) { func OptimizePackage(mod llvm.Module, config *compileopts.Config) {
_, speedLevel, _ := config.OptLevel() optLevel, sizeLevel, _ := config.OptLevels()
// Run function passes for each function in the module.
// These passes are intended to be run on each function right
// after they're created to reduce IR size (and maybe also for
// cache locality to improve performance), but for now they're
// run here for each function in turn. Maybe this can be
// improved in the future.
builder := llvm.NewPassManagerBuilder()
defer builder.Dispose()
builder.SetOptLevel(optLevel)
builder.SetSizeLevel(sizeLevel)
funcPasses := llvm.NewFunctionPassManagerForModule(mod)
defer funcPasses.Dispose()
builder.PopulateFunc(funcPasses)
funcPasses.InitializeFunc()
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
if fn.IsDeclaration() {
continue
}
funcPasses.RunFunc(fn)
}
funcPasses.FinalizeFunc()
// Run TinyGo-specific optimization passes. // Run TinyGo-specific optimization passes.
if speedLevel > 0 { if optLevel > 0 {
OptimizeMaps(mod) OptimizeMaps(mod)
} }
} }
// Optimize runs a number of optimization and transformation passes over the // Optimize runs a number of optimization and transformation passes over the
// given module. Some passes are specific to TinyGo, others are generic LLVM // given module. Some passes are specific to TinyGo, others are generic LLVM
// passes. // passes. You can set a preferred performance (0-3) and size (0-2) level and
// control the limits of the inliner (higher numbers mean more inlining, set it
// to 0 to disable entirely).
// //
// Please note that some optimizations are not optional, thus Optimize must // Please note that some optimizations are not optional, thus Optimize must
// alwasy be run before emitting machine code. // alwasy be run before emitting machine code. Set all controls (optLevel,
func Optimize(mod llvm.Module, config *compileopts.Config) []error { // sizeLevel, inlinerThreshold) to 0 to reduce the number of optimizations to a
optLevel, speedLevel, _ := config.OptLevel() // minimum.
func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel int, inlinerThreshold uint) []error {
builder := llvm.NewPassManagerBuilder()
defer builder.Dispose()
builder.SetOptLevel(optLevel)
builder.SetSizeLevel(sizeLevel)
if inlinerThreshold != 0 {
builder.UseInlinerWithThreshold(inlinerThreshold)
}
// Make sure these functions are kept in tact during TinyGo transformation passes. // Make sure these functions are kept in tact during TinyGo transformation passes.
for _, name := range functionsUsedInTransforms { for _, name := range functionsUsedInTransforms {
@@ -52,20 +84,23 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error {
} }
} }
if speedLevel > 0 { if optLevel > 0 {
// Run some preparatory passes for the Go optimizer. // Run some preparatory passes for the Go optimizer.
po := llvm.NewPassBuilderOptions() goPasses := llvm.NewPassManager()
defer po.Dispose() defer goPasses.Dispose()
err := mod.RunPasses("globaldce,globalopt,ipsccp,instcombine,adce,function-attrs", llvm.TargetMachine{}, po) goPasses.AddGlobalDCEPass()
if err != nil { goPasses.AddGlobalOptimizerPass()
return []error{fmt.Errorf("could not build pass pipeline: %w", err)} goPasses.AddIPSCCPPass()
} goPasses.AddInstructionCombiningPass() // necessary for OptimizeReflectImplements
goPasses.AddAggressiveDCEPass()
goPasses.AddFunctionAttrsPass()
goPasses.Run(mod)
// Run TinyGo-specific optimization passes. // Run TinyGo-specific optimization passes.
OptimizeStringToBytes(mod) OptimizeStringToBytes(mod)
OptimizeReflectImplements(mod) OptimizeReflectImplements(mod)
OptimizeAllocs(mod, nil, nil) OptimizeAllocs(mod, nil, nil)
err = LowerInterfaces(mod, config) err := LowerInterfaces(mod, config)
if err != nil { if err != nil {
return []error{err} return []error{err}
} }
@@ -78,10 +113,7 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error {
// After interfaces are lowered, there are many more opportunities for // After interfaces are lowered, there are many more opportunities for
// interprocedural optimizations. To get them to work, function // interprocedural optimizations. To get them to work, function
// attributes have to be updated first. // attributes have to be updated first.
err = mod.RunPasses("globaldce,globalopt,ipsccp,instcombine,adce,function-attrs", llvm.TargetMachine{}, po) goPasses.Run(mod)
if err != nil {
return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
}
// Run TinyGo-specific interprocedural optimizations. // Run TinyGo-specific interprocedural optimizations.
OptimizeAllocs(mod, config.Options.PrintAllocs, func(pos token.Position, msg string) { OptimizeAllocs(mod, config.Options.PrintAllocs, func(pos token.Position, msg string) {
@@ -102,12 +134,10 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error {
} }
// Clean up some leftover symbols of the previous transformations. // Clean up some leftover symbols of the previous transformations.
po := llvm.NewPassBuilderOptions() goPasses := llvm.NewPassManager()
defer po.Dispose() defer goPasses.Dispose()
err = mod.RunPasses("globaldce", llvm.TargetMachine{}, po) goPasses.AddGlobalDCEPass()
if err != nil { goPasses.Run(mod)
return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
}
} }
if config.Scheduler() == "none" { if config.Scheduler() == "none" {
@@ -139,15 +169,23 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error {
fn.SetLinkage(llvm.InternalLinkage) fn.SetLinkage(llvm.InternalLinkage)
} }
// Run the default pass pipeline. // Run function passes again, because without it, llvm.coro.size.i32()
// TODO: set the PrepareForThinLTO flag somehow. // doesn't get lowered.
po := llvm.NewPassBuilderOptions() funcPasses := llvm.NewFunctionPassManagerForModule(mod)
defer po.Dispose() defer funcPasses.Dispose()
passes := fmt.Sprintf("default<%s>", optLevel) builder.PopulateFunc(funcPasses)
err := mod.RunPasses(passes, llvm.TargetMachine{}, po) funcPasses.InitializeFunc()
if err != nil { for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
return []error{fmt.Errorf("could not build pass pipeline: %w", err)} funcPasses.RunFunc(fn)
} }
funcPasses.FinalizeFunc()
// Run module passes.
// TODO: somehow set the PrepareForThinLTO flag in the pass manager builder.
modPasses := llvm.NewPassManager()
defer modPasses.Dispose()
builder.Populate(modPasses)
modPasses.Run(mod)
hasGCPass := MakeGCStackSlots(mod) hasGCPass := MakeGCStackSlots(mod)
if hasGCPass { if hasGCPass {
+6 -18
View File
@@ -28,44 +28,32 @@ func OptimizeStringToBytes(mod llvm.Module) {
// strptr is always constant because strings are always constant. // strptr is always constant because strings are always constant.
var pointerUses []llvm.Value convertedAllUses := true
canConvertPointer := true
for _, use := range getUses(call) { for _, use := range getUses(call) {
if use.IsAExtractValueInst().IsNil() { if use.IsAExtractValueInst().IsNil() {
// Expected an extractvalue, but this is something else. // Expected an extractvalue, but this is something else.
canConvertPointer = false convertedAllUses = false
continue continue
} }
switch use.Type().TypeKind() { switch use.Type().TypeKind() {
case llvm.IntegerTypeKind: case llvm.IntegerTypeKind:
// A length (len or cap). Propagate the length value. // A length (len or cap). Propagate the length value.
// This can always be done because the byte slice is always the
// same length as the original string.
use.ReplaceAllUsesWith(strlen) use.ReplaceAllUsesWith(strlen)
use.EraseFromParentAsInstruction() use.EraseFromParentAsInstruction()
case llvm.PointerTypeKind: case llvm.PointerTypeKind:
// The string pointer itself. // The string pointer itself.
if !isReadOnly(use) { if !isReadOnly(use) {
// There is a store to the byte slice. This means that none convertedAllUses = false
// of the pointer uses can't be propagated.
canConvertPointer = false
continue continue
} }
// It may be that the pointer value can be propagated, if all of use.ReplaceAllUsesWith(strptr)
// the pointer uses are readonly. use.EraseFromParentAsInstruction()
pointerUses = append(pointerUses, use)
default: default:
// should not happen // should not happen
panic("unknown return type of runtime.stringToBytes: " + use.Type().String()) panic("unknown return type of runtime.stringToBytes: " + use.Type().String())
} }
} }
if canConvertPointer { if convertedAllUses {
// All pointer uses are readonly, so they can be converted.
for _, use := range pointerUses {
use.ReplaceAllUsesWith(strptr)
use.EraseFromParentAsInstruction()
}
// Call to runtime.stringToBytes can be eliminated: both the input // Call to runtime.stringToBytes can be eliminated: both the input
// and the output is constant. // and the output is constant.
call.EraseFromParentAsInstruction() call.EraseFromParentAsInstruction()
+13 -13
View File
@@ -6,18 +6,18 @@ target triple = "armv7m-none-eabi"
declare nonnull ptr @runtime.alloc(i32, ptr) declare nonnull ptr @runtime.alloc(i32, ptr)
define void @testInt() { define void @testInt() {
%stackalloc = alloca [4 x i8], align 4 %stackalloc.alloca = alloca [4 x i8], align 4
store [4 x i8] zeroinitializer, ptr %stackalloc, align 4 store [4 x i8] zeroinitializer, ptr %stackalloc.alloca, align 4
store i32 5, ptr %stackalloc, align 4 store i32 5, ptr %stackalloc.alloca, align 4
ret void ret void
} }
define i16 @testArray() { define i16 @testArray() {
%stackalloc = alloca [6 x i8], align 2 %stackalloc.alloca = alloca [6 x i8], align 2
store [6 x i8] zeroinitializer, ptr %stackalloc, align 2 store [6 x i8] zeroinitializer, ptr %stackalloc.alloca, align 2
%alloc.1 = getelementptr i16, ptr %stackalloc, i32 1 %alloc.1 = getelementptr i16, ptr %stackalloc.alloca, i32 1
store i16 5, ptr %alloc.1, align 2 store i16 5, ptr %alloc.1, align 2
%alloc.2 = getelementptr i16, ptr %stackalloc, i32 2 %alloc.2 = getelementptr i16, ptr %stackalloc.alloca, i32 2
%val = load i16, ptr %alloc.2, align 2 %val = load i16, ptr %alloc.2, align 2
ret i16 %val ret i16 %val
} }
@@ -35,9 +35,9 @@ define void @testEscapingCall2() {
} }
define void @testNonEscapingCall() { define void @testNonEscapingCall() {
%stackalloc = alloca [4 x i8], align 4 %stackalloc.alloca = alloca [4 x i8], align 4
store [4 x i8] zeroinitializer, ptr %stackalloc, align 4 store [4 x i8] zeroinitializer, ptr %stackalloc.alloca, align 4
%val = call ptr @noescapeIntPtr(ptr %stackalloc) %val = call ptr @noescapeIntPtr(ptr %stackalloc.alloca)
ret void ret void
} }
@@ -48,12 +48,12 @@ define ptr @testEscapingReturn() {
define void @testNonEscapingLoop() { define void @testNonEscapingLoop() {
entry: entry:
%stackalloc = alloca [4 x i8], align 4 %stackalloc.alloca = alloca [4 x i8], align 4
br label %loop br label %loop
loop: ; preds = %loop, %entry loop: ; preds = %loop, %entry
store [4 x i8] zeroinitializer, ptr %stackalloc, align 4 store [4 x i8] zeroinitializer, ptr %stackalloc.alloca, align 4
%ptr = call ptr @noescapeIntPtr(ptr %stackalloc) %ptr = call ptr @noescapeIntPtr(ptr %stackalloc.alloca)
%result = icmp eq ptr null, %ptr %result = icmp eq ptr null, %ptr
br i1 %result, label %loop, label %end br i1 %result, label %loop, label %end
-16
View File
@@ -30,19 +30,3 @@ entry:
call fastcc void @writeToSlice(ptr %1, i64 %2, i64 %3) call fastcc void @writeToSlice(ptr %1, i64 %2, i64 %3)
ret void ret void
} }
; Test that pointer values are never propagated if there is even a single write
; to the pointer value (but len/cap values still can be).
define void @testReadSome() {
entry:
%s = call fastcc { ptr, i64, i64 } @runtime.stringToBytes(ptr @str, i64 6)
%s.ptr = extractvalue { ptr, i64, i64 } %s, 0
%s.len = extractvalue { ptr, i64, i64 } %s, 1
%s.cap = extractvalue { ptr, i64, i64 } %s, 2
call fastcc void @writeToSlice(ptr %s.ptr, i64 %s.len, i64 %s.cap)
%s.ptr2 = extractvalue { ptr, i64, i64 } %s, 0
%s.len2 = extractvalue { ptr, i64, i64 } %s, 1
%s.cap2 = extractvalue { ptr, i64, i64 } %s, 2
call fastcc void @printSlice(ptr %s.ptr2, i64 %s.len2, i64 %s.cap2)
ret void
}
-10
View File
@@ -22,13 +22,3 @@ entry:
call fastcc void @writeToSlice(ptr %1, i64 6, i64 6) call fastcc void @writeToSlice(ptr %1, i64 6, i64 6)
ret void ret void
} }
define void @testReadSome() {
entry:
%s = call fastcc { ptr, i64, i64 } @runtime.stringToBytes(ptr @str, i64 6)
%s.ptr = extractvalue { ptr, i64, i64 } %s, 0
call fastcc void @writeToSlice(ptr %s.ptr, i64 6, i64 6)
%s.ptr2 = extractvalue { ptr, i64, i64 } %s, 0
call fastcc void @printSlice(ptr %s.ptr2, i64 6, i64 6)
ret void
}
+1 -1
View File
@@ -22,7 +22,7 @@ import (
// the -opt= compiler flag. // the -opt= compiler flag.
func AddStandardAttributes(fn llvm.Value, config *compileopts.Config) { func AddStandardAttributes(fn llvm.Value, config *compileopts.Config) {
ctx := fn.Type().Context() ctx := fn.Type().Context()
_, _, sizeLevel := config.OptLevel() _, sizeLevel, _ := config.OptLevels()
if sizeLevel >= 1 { if sizeLevel >= 1 {
fn.AddFunctionAttr(ctx.CreateEnumAttribute(llvm.AttributeKindID("optsize"), 0)) fn.AddFunctionAttr(ctx.CreateEnumAttribute(llvm.AttributeKindID("optsize"), 0))
} }