mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-13 23:43:40 +00:00
Compare commits
63 Commits
v0.5.0
...
gc-precise
| Author | SHA1 | Date | |
|---|---|---|---|
| a1e69bbc13 | |||
| 1b4d71bd3d | |||
| 43b3bb6e83 | |||
| e0cf74e638 | |||
| 6f6afb0515 | |||
| 397b90753c | |||
| 3c2639ad55 | |||
| 371c468e8e | |||
| 0a40219680 | |||
| c981f14e61 | |||
| 763b9d7d10 | |||
| 55fc7b904a | |||
| 17c42810d0 | |||
| 064d001550 | |||
| a4cd3bb77c | |||
| 8d3f19bc84 | |||
| d90f1947d9 | |||
| 11567c62d4 | |||
| 4619207f99 | |||
| 99587fe073 | |||
| eb0ce8a298 | |||
| 4ae4ef5e12 | |||
| d7460b945e | |||
| 638bc17eeb | |||
| 1113f9ec0c | |||
| 019331e8af | |||
| 4c8c048c49 | |||
| 08ee1916f5 | |||
| 141a70f401 | |||
| a79edf416c | |||
| 2511aefac0 | |||
| 4978065c9c | |||
| 78a26fec13 | |||
| 9cad8bd0c8 | |||
| 9a54ee4241 | |||
| 46d5ea8cf6 | |||
| fb952a722a | |||
| 7e46c1766d | |||
| 1f0595438e | |||
| d594342642 | |||
| 99da328453 | |||
| 387e1340bf | |||
| b1ed8a46b7 | |||
| 35af33ead7 | |||
| 4bd1b9e53d | |||
| 80ee343e6d | |||
| 1d59a960bc | |||
| 5ca2e1322c | |||
| 5b0b35f9e4 | |||
| 9a3d0683b3 | |||
| d155e31b64 | |||
| 45cacda7b3 | |||
| c25fe609a9 | |||
| 6d23809218 | |||
| 024eceb476 | |||
| 0fd90c49cc | |||
| d1efffe96b | |||
| 8e7ea92d44 | |||
| 2f2d62cc0c | |||
| d396abb690 | |||
| b815d3f760 | |||
| 9c46ac4eed | |||
| b2e96fc35a |
+19
-15
@@ -47,6 +47,18 @@ commands:
|
|||||||
command: |
|
command: |
|
||||||
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||||
dep ensure --vendor-only
|
dep ensure --vendor-only
|
||||||
|
llvm-source-linux:
|
||||||
|
steps:
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- llvm-source-8-v2
|
||||||
|
- run:
|
||||||
|
name: "Fetch LLVM source"
|
||||||
|
command: make llvm-source
|
||||||
|
- save_cache:
|
||||||
|
key: llvm-source-8-v2
|
||||||
|
paths:
|
||||||
|
- llvm
|
||||||
smoketest:
|
smoketest:
|
||||||
steps:
|
steps:
|
||||||
- smoketest-no-avr
|
- smoketest-no-avr
|
||||||
@@ -70,6 +82,7 @@ commands:
|
|||||||
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
|
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
|
||||||
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
|
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
|
||||||
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
|
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
|
||||||
|
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
|
||||||
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/export
|
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/export
|
||||||
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/main
|
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/main
|
||||||
test-linux:
|
test-linux:
|
||||||
@@ -86,6 +99,7 @@ commands:
|
|||||||
keys:
|
keys:
|
||||||
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||||
- go-cache-{{ checksum "Gopkg.lock" }}
|
- go-cache-{{ checksum "Gopkg.lock" }}
|
||||||
|
- llvm-source-linux
|
||||||
- dep
|
- dep
|
||||||
- run: go install .
|
- run: go install .
|
||||||
- run: go test -v
|
- run: go test -v
|
||||||
@@ -105,7 +119,6 @@ commands:
|
|||||||
name: "Install apt dependencies"
|
name: "Install apt dependencies"
|
||||||
command: |
|
command: |
|
||||||
sudo apt-get install \
|
sudo apt-get install \
|
||||||
libtinfo-dev \
|
|
||||||
python3 \
|
python3 \
|
||||||
gcc-arm-linux-gnueabihf \
|
gcc-arm-linux-gnueabihf \
|
||||||
binutils-arm-none-eabi \
|
binutils-arm-none-eabi \
|
||||||
@@ -121,19 +134,10 @@ commands:
|
|||||||
keys:
|
keys:
|
||||||
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||||
- go-cache-{{ checksum "Gopkg.lock" }}
|
- go-cache-{{ checksum "Gopkg.lock" }}
|
||||||
|
- llvm-source-linux
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- llvm-source-8-v2
|
- llvm-build-8-linux-v4
|
||||||
- run:
|
|
||||||
name: "Fetch LLVM source"
|
|
||||||
command: make llvm-source
|
|
||||||
- save_cache:
|
|
||||||
key: llvm-source-8-v2
|
|
||||||
paths:
|
|
||||||
- llvm
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- llvm-build-8-v2
|
|
||||||
- run:
|
- run:
|
||||||
name: "Build LLVM"
|
name: "Build LLVM"
|
||||||
command: |
|
command: |
|
||||||
@@ -151,7 +155,7 @@ commands:
|
|||||||
make llvm-build
|
make llvm-build
|
||||||
fi
|
fi
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-build-8-v2
|
key: llvm-build-8-linux-v4
|
||||||
paths:
|
paths:
|
||||||
llvm-build
|
llvm-build
|
||||||
- run:
|
- run:
|
||||||
@@ -204,7 +208,7 @@ commands:
|
|||||||
- llvm
|
- llvm
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- llvm-build-8-macos-v2
|
- llvm-build-8-macos-v3
|
||||||
- run:
|
- run:
|
||||||
name: "Build LLVM"
|
name: "Build LLVM"
|
||||||
command: |
|
command: |
|
||||||
@@ -216,7 +220,7 @@ commands:
|
|||||||
make llvm-build
|
make llvm-build
|
||||||
fi
|
fi
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-build-8-macos-v2
|
key: llvm-build-8-macos-v3
|
||||||
paths:
|
paths:
|
||||||
llvm-build
|
llvm-build
|
||||||
- run:
|
- run:
|
||||||
|
|||||||
@@ -10,3 +10,5 @@ src/device/stm32/*.s
|
|||||||
src/device/sam/*.go
|
src/device/sam/*.go
|
||||||
src/device/sam/*.s
|
src/device/sam/*.s
|
||||||
vendor
|
vendor
|
||||||
|
llvm
|
||||||
|
llvm-build
|
||||||
|
|||||||
+4
-1
@@ -10,8 +10,11 @@ RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
|||||||
|
|
||||||
COPY . /go/src/github.com/tinygo-org/tinygo
|
COPY . /go/src/github.com/tinygo-org/tinygo
|
||||||
|
|
||||||
|
# remove submodules directories and re-init them to fix any hard-coded paths
|
||||||
|
# after copying the tinygo directory in the previous step.
|
||||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||||
git submodule update --init
|
rm -rf ./lib/* && \
|
||||||
|
git submodule update --init --recursive --force
|
||||||
|
|
||||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||||
dep ensure --vendor-only && \
|
dep ensure --vendor-only && \
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
Copyright (c) 2018-2019 TinyGo Authors. All rights reserved.
|
Copyright (c) 2018-2019 TinyGo Authors. All rights reserved.
|
||||||
|
|
||||||
|
TinyGo includes portions of the Go standard library.
|
||||||
|
Copyright (c) 2009-2019 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are
|
||||||
met:
|
met:
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUIL
|
|||||||
clean:
|
clean:
|
||||||
@rm -rf build
|
@rm -rf build
|
||||||
|
|
||||||
FMT_PATHS = ./*.go compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall
|
FMT_PATHS = ./*.go cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall
|
||||||
fmt:
|
fmt:
|
||||||
@gofmt -l -w $(FMT_PATHS)
|
@gofmt -l -w $(FMT_PATHS)
|
||||||
fmt-check:
|
fmt-check:
|
||||||
@@ -69,7 +69,7 @@ llvm-source: llvm/README.txt llvm/tools/clang/README.txt llvm/tools/lld/README.m
|
|||||||
|
|
||||||
# Configure LLVM.
|
# Configure LLVM.
|
||||||
llvm-build/build.ninja: llvm-source
|
llvm-build/build.ninja: llvm-source
|
||||||
mkdir -p llvm-build; cd llvm-build; cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON
|
mkdir -p llvm-build; cd llvm-build; cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF
|
||||||
|
|
||||||
# Build LLVM.
|
# Build LLVM.
|
||||||
llvm-build: llvm-build/build.ninja
|
llvm-build: llvm-build/build.ninja
|
||||||
@@ -86,13 +86,16 @@ test:
|
|||||||
|
|
||||||
release: build/tinygo gen-device
|
release: build/tinygo gen-device
|
||||||
@mkdir -p build/release/tinygo/bin
|
@mkdir -p build/release/tinygo/bin
|
||||||
|
@mkdir -p build/release/tinygo/lib/clang/include
|
||||||
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
|
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
|
||||||
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
|
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
|
||||||
@mkdir -p build/release/tinygo/lib/nrfx
|
@mkdir -p build/release/tinygo/lib/nrfx
|
||||||
@mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
|
@mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
|
||||||
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
|
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
|
||||||
@mkdir -p build/release/tinygo/pkg/armv7em-none-eabi
|
@mkdir -p build/release/tinygo/pkg/armv7em-none-eabi
|
||||||
|
@echo copying source files
|
||||||
@cp -p build/tinygo build/release/tinygo/bin
|
@cp -p build/tinygo build/release/tinygo/bin
|
||||||
|
@cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include
|
||||||
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
|
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
|
||||||
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
|
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
|
||||||
@cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib
|
@cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib
|
||||||
|
|||||||
+5
-3
@@ -9,9 +9,11 @@ import (
|
|||||||
|
|
||||||
// Get the cache directory, usually ~/.cache/tinygo
|
// Get the cache directory, usually ~/.cache/tinygo
|
||||||
func cacheDir() string {
|
func cacheDir() string {
|
||||||
home := getHomeDir()
|
dir, err := os.UserCacheDir()
|
||||||
dir := filepath.Join(home, ".cache", "tinygo")
|
if err != nil {
|
||||||
return dir
|
panic("could not find cache dir: " + err.Error())
|
||||||
|
}
|
||||||
|
return filepath.Join(dir, "tinygo")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the newest timestamp of all the file paths passed in. Used to check
|
// Return the newest timestamp of all the file paths passed in. Used to check
|
||||||
|
|||||||
+4
-8
@@ -5,7 +5,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -192,13 +191,13 @@ func loadBuiltins(target string) (path string, err error) {
|
|||||||
srcs[i] = filepath.Join(builtinsDir, name)
|
srcs[i] = filepath.Join(builtinsDir, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if path, err := cacheLoad(outfile, commands["clang"], srcs); path != "" || err != nil {
|
if path, err := cacheLoad(outfile, commands["clang"][0], srcs); path != "" || err != nil {
|
||||||
return path, err
|
return path, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var cachepath string
|
var cachepath string
|
||||||
err = compileBuiltins(target, func(path string) error {
|
err = compileBuiltins(target, func(path string) error {
|
||||||
path, err := cacheStore(path, outfile, commands["clang"], srcs)
|
path, err := cacheStore(path, outfile, commands["clang"][0], srcs)
|
||||||
cachepath = path
|
cachepath = path
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
@@ -240,11 +239,7 @@ func compileBuiltins(target string, callback func(path string) error) error {
|
|||||||
// Note: -fdebug-prefix-map is necessary to make the output archive
|
// Note: -fdebug-prefix-map is necessary to make the output archive
|
||||||
// reproducible. Otherwise the temporary directory is stored in the
|
// reproducible. Otherwise the temporary directory is stored in the
|
||||||
// archive itself, which varies each run.
|
// archive itself, which varies each run.
|
||||||
cmd := exec.Command(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir, "-o", objpath, srcpath)
|
err := execCommand(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir, "-o", objpath, srcpath)
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
cmd.Dir = dir
|
|
||||||
err = cmd.Run()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &commandError{"failed to build", srcpath, err}
|
return &commandError{"failed to build", srcpath, err}
|
||||||
}
|
}
|
||||||
@@ -294,5 +289,6 @@ func compileBuiltins(target string, callback func(path string) error) error {
|
|||||||
|
|
||||||
// Give the caller the resulting file. The callback must copy the file,
|
// Give the caller the resulting file. The callback must copy the file,
|
||||||
// because after it returns the temporary directory will be removed.
|
// because after it returns the temporary directory will be removed.
|
||||||
|
arfile.Close()
|
||||||
return callback(arpath)
|
return callback(arpath)
|
||||||
}
|
}
|
||||||
|
|||||||
+636
@@ -0,0 +1,636 @@
|
|||||||
|
// Package cgo implements CGo by modifying a loaded AST. It does this by parsing
|
||||||
|
// the `import "C"` statements found in the source code with libclang and
|
||||||
|
// generating stub function and global declarations.
|
||||||
|
//
|
||||||
|
// There are a few advantages to modifying the AST directly instead of doing CGo
|
||||||
|
// as a preprocessing step, with the main advantage being that debug information
|
||||||
|
// is kept intact as much as possible.
|
||||||
|
package cgo
|
||||||
|
|
||||||
|
// This file extracts the `import "C"` statement from the source and modifies
|
||||||
|
// the AST for CGo. It does not use libclang directly: see libclang.go for the C
|
||||||
|
// source file parsing.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/ast/astutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// cgoPackage holds all CGo-related information of a package.
|
||||||
|
type cgoPackage struct {
|
||||||
|
generated *ast.File
|
||||||
|
generatedPos token.Pos
|
||||||
|
errors []error
|
||||||
|
dir string
|
||||||
|
fset *token.FileSet
|
||||||
|
tokenFiles map[string]*token.File
|
||||||
|
missingSymbols map[string]struct{}
|
||||||
|
constants map[string]constantInfo
|
||||||
|
functions map[string]*functionInfo
|
||||||
|
globals map[string]globalInfo
|
||||||
|
typedefs map[string]*typedefInfo
|
||||||
|
elaboratedTypes map[string]*elaboratedTypeInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// constantInfo stores some information about a CGo constant found by libclang
|
||||||
|
// and declared in the Go AST.
|
||||||
|
type constantInfo struct {
|
||||||
|
expr *ast.BasicLit
|
||||||
|
pos token.Pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// functionInfo stores some information about a CGo function found by libclang
|
||||||
|
// and declared in the AST.
|
||||||
|
type functionInfo struct {
|
||||||
|
args []paramInfo
|
||||||
|
results *ast.FieldList
|
||||||
|
pos token.Pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// paramInfo is a parameter of a CGo function (see functionInfo).
|
||||||
|
type paramInfo struct {
|
||||||
|
name string
|
||||||
|
typeExpr ast.Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// typedefInfo contains information about a single typedef in C.
|
||||||
|
type typedefInfo struct {
|
||||||
|
typeExpr ast.Expr
|
||||||
|
pos token.Pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// elaboratedTypeInfo contains some information about an elaborated type
|
||||||
|
// (struct, union) found in the C AST.
|
||||||
|
type elaboratedTypeInfo struct {
|
||||||
|
typeExpr ast.Expr
|
||||||
|
pos token.Pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// globalInfo contains information about a declared global variable in C.
|
||||||
|
type globalInfo struct {
|
||||||
|
typeExpr ast.Expr
|
||||||
|
pos token.Pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// cgoAliases list type aliases between Go and C, for types that are equivalent
|
||||||
|
// in both languages. See addTypeAliases.
|
||||||
|
var cgoAliases = map[string]string{
|
||||||
|
"C.int8_t": "int8",
|
||||||
|
"C.int16_t": "int16",
|
||||||
|
"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",
|
||||||
|
}
|
||||||
|
|
||||||
|
// builtinAliases are handled specially because they only exist on the Go side
|
||||||
|
// of CGo, not on the CGo side (they're prefixed with "_Cgo_" there).
|
||||||
|
var builtinAliases = map[string]struct{}{
|
||||||
|
"char": struct{}{},
|
||||||
|
"schar": struct{}{},
|
||||||
|
"uchar": struct{}{},
|
||||||
|
"short": struct{}{},
|
||||||
|
"ushort": struct{}{},
|
||||||
|
"int": struct{}{},
|
||||||
|
"uint": struct{}{},
|
||||||
|
"long": struct{}{},
|
||||||
|
"ulong": struct{}{},
|
||||||
|
"longlong": struct{}{},
|
||||||
|
"ulonglong": struct{}{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// cgoTypes lists some C types with ambiguous sizes that must be retrieved
|
||||||
|
// somehow from C. This is done by adding some typedefs to get the size of each
|
||||||
|
// type.
|
||||||
|
const cgoTypes = `
|
||||||
|
typedef char _Cgo_char;
|
||||||
|
typedef signed char _Cgo_schar;
|
||||||
|
typedef unsigned char _Cgo_uchar;
|
||||||
|
typedef short _Cgo_short;
|
||||||
|
typedef unsigned short _Cgo_ushort;
|
||||||
|
typedef int _Cgo_int;
|
||||||
|
typedef unsigned int _Cgo_uint;
|
||||||
|
typedef long _Cgo_long;
|
||||||
|
typedef unsigned long _Cgo_ulong;
|
||||||
|
typedef long long _Cgo_longlong;
|
||||||
|
typedef unsigned long long _Cgo_ulonglong;
|
||||||
|
`
|
||||||
|
|
||||||
|
// Process extracts `import "C"` statements from the AST, parses the comment
|
||||||
|
// with libclang, and modifies the AST to use this information. It returns a
|
||||||
|
// newly created *ast.File that should be added to the list of to-be-parsed
|
||||||
|
// files. If there is one or more error, it returns these in the []error slice
|
||||||
|
// but still modifies the AST.
|
||||||
|
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []error) {
|
||||||
|
p := &cgoPackage{
|
||||||
|
dir: dir,
|
||||||
|
fset: fset,
|
||||||
|
tokenFiles: map[string]*token.File{},
|
||||||
|
missingSymbols: map[string]struct{}{},
|
||||||
|
constants: map[string]constantInfo{},
|
||||||
|
functions: map[string]*functionInfo{},
|
||||||
|
globals: map[string]globalInfo{},
|
||||||
|
typedefs: map[string]*typedefInfo{},
|
||||||
|
elaboratedTypes: map[string]*elaboratedTypeInfo{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new location for the following file.
|
||||||
|
generatedTokenPos := p.fset.AddFile(dir+"/!cgo.go", -1, 0)
|
||||||
|
generatedTokenPos.SetLines([]int{0})
|
||||||
|
p.generatedPos = generatedTokenPos.Pos(0)
|
||||||
|
|
||||||
|
// Construct a new in-memory AST for CGo declarations of this package.
|
||||||
|
unsafeImport := &ast.ImportSpec{
|
||||||
|
Path: &ast.BasicLit{
|
||||||
|
ValuePos: p.generatedPos,
|
||||||
|
Kind: token.STRING,
|
||||||
|
Value: "\"unsafe\"",
|
||||||
|
},
|
||||||
|
EndPos: p.generatedPos,
|
||||||
|
}
|
||||||
|
p.generated = &ast.File{
|
||||||
|
Package: p.generatedPos,
|
||||||
|
Name: &ast.Ident{
|
||||||
|
NamePos: p.generatedPos,
|
||||||
|
Name: files[0].Name.Name,
|
||||||
|
},
|
||||||
|
Decls: []ast.Decl{
|
||||||
|
&ast.GenDecl{
|
||||||
|
TokPos: p.generatedPos,
|
||||||
|
Tok: token.IMPORT,
|
||||||
|
Specs: []ast.Spec{
|
||||||
|
unsafeImport,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Imports: []*ast.ImportSpec{unsafeImport},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all C.* symbols.
|
||||||
|
for _, f := range files {
|
||||||
|
astutil.Apply(f, p.findMissingCGoNames, nil)
|
||||||
|
}
|
||||||
|
for name := range builtinAliases {
|
||||||
|
p.missingSymbols["_Cgo_"+name] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find `import "C"` statements in the file.
|
||||||
|
for _, f := range files {
|
||||||
|
for i := 0; i < len(f.Decls); i++ {
|
||||||
|
decl := f.Decls[i]
|
||||||
|
genDecl, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(genDecl.Specs) != 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
spec, ok := genDecl.Specs[0].(*ast.ImportSpec)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
path, err := strconv.Unquote(spec.Path.Value)
|
||||||
|
if err != nil {
|
||||||
|
panic("could not parse import path: " + err.Error())
|
||||||
|
}
|
||||||
|
if path != "C" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cgoComment := genDecl.Doc.Text()
|
||||||
|
|
||||||
|
pos := genDecl.Pos()
|
||||||
|
if genDecl.Doc != nil {
|
||||||
|
pos = genDecl.Doc.Pos()
|
||||||
|
}
|
||||||
|
position := fset.PositionFor(pos, true)
|
||||||
|
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
|
||||||
|
|
||||||
|
// Remove this import declaration.
|
||||||
|
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the AST, for debugging.
|
||||||
|
//ast.Print(fset, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declare functions found by libclang.
|
||||||
|
p.addFuncDecls()
|
||||||
|
|
||||||
|
// Declare stub function pointer values found by libclang.
|
||||||
|
p.addFuncPtrDecls()
|
||||||
|
|
||||||
|
// Declare globals found by libclang.
|
||||||
|
p.addConstDecls()
|
||||||
|
|
||||||
|
// Declare globals found by libclang.
|
||||||
|
p.addVarDecls()
|
||||||
|
|
||||||
|
// Forward C types to Go types (like C.uint32_t -> uint32).
|
||||||
|
p.addTypeAliases()
|
||||||
|
|
||||||
|
// Add type declarations for C types, declared using typedef in C.
|
||||||
|
p.addTypedefs()
|
||||||
|
|
||||||
|
// Add elaborated types for C structs and unions.
|
||||||
|
p.addElaboratedTypes()
|
||||||
|
|
||||||
|
// Patch the AST to use the declared types and functions.
|
||||||
|
for _, f := range files {
|
||||||
|
astutil.Apply(f, p.walker, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the newly generated in-memory AST, for debugging.
|
||||||
|
//ast.Print(fset, p.generated)
|
||||||
|
|
||||||
|
return p.generated, p.errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// addFuncDecls adds the C function declarations found by libclang in the
|
||||||
|
// comment above the `import "C"` statement.
|
||||||
|
func (p *cgoPackage) addFuncDecls() {
|
||||||
|
names := make([]string, 0, len(p.functions))
|
||||||
|
for name := range p.functions {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
for _, name := range names {
|
||||||
|
fn := p.functions[name]
|
||||||
|
obj := &ast.Object{
|
||||||
|
Kind: ast.Fun,
|
||||||
|
Name: "C." + name,
|
||||||
|
}
|
||||||
|
args := make([]*ast.Field, len(fn.args))
|
||||||
|
decl := &ast.FuncDecl{
|
||||||
|
Name: &ast.Ident{
|
||||||
|
NamePos: fn.pos,
|
||||||
|
Name: "C." + name,
|
||||||
|
Obj: obj,
|
||||||
|
},
|
||||||
|
Type: &ast.FuncType{
|
||||||
|
Func: fn.pos,
|
||||||
|
Params: &ast.FieldList{
|
||||||
|
Opening: fn.pos,
|
||||||
|
List: args,
|
||||||
|
Closing: fn.pos,
|
||||||
|
},
|
||||||
|
Results: fn.results,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
obj.Decl = decl
|
||||||
|
for i, arg := range fn.args {
|
||||||
|
args[i] = &ast.Field{
|
||||||
|
Names: []*ast.Ident{
|
||||||
|
&ast.Ident{
|
||||||
|
NamePos: fn.pos,
|
||||||
|
Name: arg.name,
|
||||||
|
Obj: &ast.Object{
|
||||||
|
Kind: ast.Var,
|
||||||
|
Name: arg.name,
|
||||||
|
Decl: decl,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: arg.typeExpr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.generated.Decls = append(p.generated.Decls, decl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addFuncPtrDecls creates stub declarations of function pointer values. These
|
||||||
|
// values will later be replaced with the real values in the compiler.
|
||||||
|
// It adds code like the following to the AST:
|
||||||
|
//
|
||||||
|
// var (
|
||||||
|
// C.add unsafe.Pointer
|
||||||
|
// C.mul unsafe.Pointer
|
||||||
|
// // ...
|
||||||
|
// )
|
||||||
|
func (p *cgoPackage) addFuncPtrDecls() {
|
||||||
|
if len(p.functions) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gen := &ast.GenDecl{
|
||||||
|
TokPos: token.NoPos,
|
||||||
|
Tok: token.VAR,
|
||||||
|
Lparen: token.NoPos,
|
||||||
|
Rparen: token.NoPos,
|
||||||
|
}
|
||||||
|
names := make([]string, 0, len(p.functions))
|
||||||
|
for name := range p.functions {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
for _, name := range names {
|
||||||
|
fn := p.functions[name]
|
||||||
|
obj := &ast.Object{
|
||||||
|
Kind: ast.Typ,
|
||||||
|
Name: "C." + name + "$funcaddr",
|
||||||
|
}
|
||||||
|
valueSpec := &ast.ValueSpec{
|
||||||
|
Names: []*ast.Ident{&ast.Ident{
|
||||||
|
NamePos: fn.pos,
|
||||||
|
Name: "C." + name + "$funcaddr",
|
||||||
|
Obj: obj,
|
||||||
|
}},
|
||||||
|
Type: &ast.SelectorExpr{
|
||||||
|
X: &ast.Ident{
|
||||||
|
NamePos: fn.pos,
|
||||||
|
Name: "unsafe",
|
||||||
|
},
|
||||||
|
Sel: &ast.Ident{
|
||||||
|
NamePos: fn.pos,
|
||||||
|
Name: "Pointer",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
obj.Decl = valueSpec
|
||||||
|
gen.Specs = append(gen.Specs, valueSpec)
|
||||||
|
}
|
||||||
|
p.generated.Decls = append(p.generated.Decls, gen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addConstDecls declares external C constants in the Go source.
|
||||||
|
// It adds code like the following to the AST:
|
||||||
|
//
|
||||||
|
// const (
|
||||||
|
// C.CONST_INT = 5
|
||||||
|
// C.CONST_FLOAT = 5.8
|
||||||
|
// // ...
|
||||||
|
// )
|
||||||
|
func (p *cgoPackage) addConstDecls() {
|
||||||
|
if len(p.constants) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gen := &ast.GenDecl{
|
||||||
|
TokPos: token.NoPos,
|
||||||
|
Tok: token.CONST,
|
||||||
|
Lparen: token.NoPos,
|
||||||
|
Rparen: token.NoPos,
|
||||||
|
}
|
||||||
|
names := make([]string, 0, len(p.constants))
|
||||||
|
for name := range p.constants {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
for _, name := range names {
|
||||||
|
constVal := p.constants[name]
|
||||||
|
obj := &ast.Object{
|
||||||
|
Kind: ast.Con,
|
||||||
|
Name: "C." + name,
|
||||||
|
}
|
||||||
|
valueSpec := &ast.ValueSpec{
|
||||||
|
Names: []*ast.Ident{&ast.Ident{
|
||||||
|
NamePos: constVal.pos,
|
||||||
|
Name: "C." + name,
|
||||||
|
Obj: obj,
|
||||||
|
}},
|
||||||
|
Values: []ast.Expr{constVal.expr},
|
||||||
|
}
|
||||||
|
obj.Decl = valueSpec
|
||||||
|
gen.Specs = append(gen.Specs, valueSpec)
|
||||||
|
}
|
||||||
|
p.generated.Decls = append(p.generated.Decls, gen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addVarDecls declares external C globals in the Go source.
|
||||||
|
// It adds code like the following to the AST:
|
||||||
|
//
|
||||||
|
// var (
|
||||||
|
// C.globalInt int
|
||||||
|
// C.globalBool bool
|
||||||
|
// // ...
|
||||||
|
// )
|
||||||
|
func (p *cgoPackage) addVarDecls() {
|
||||||
|
if len(p.globals) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gen := &ast.GenDecl{
|
||||||
|
TokPos: token.NoPos,
|
||||||
|
Tok: token.VAR,
|
||||||
|
Lparen: token.NoPos,
|
||||||
|
Rparen: token.NoPos,
|
||||||
|
}
|
||||||
|
names := make([]string, 0, len(p.globals))
|
||||||
|
for name := range p.globals {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
for _, name := range names {
|
||||||
|
global := p.globals[name]
|
||||||
|
obj := &ast.Object{
|
||||||
|
Kind: ast.Var,
|
||||||
|
Name: "C." + name,
|
||||||
|
}
|
||||||
|
valueSpec := &ast.ValueSpec{
|
||||||
|
Names: []*ast.Ident{&ast.Ident{
|
||||||
|
NamePos: global.pos,
|
||||||
|
Name: "C." + name,
|
||||||
|
Obj: obj,
|
||||||
|
}},
|
||||||
|
Type: global.typeExpr,
|
||||||
|
}
|
||||||
|
obj.Decl = valueSpec
|
||||||
|
gen.Specs = append(gen.Specs, valueSpec)
|
||||||
|
}
|
||||||
|
p.generated.Decls = append(p.generated.Decls, gen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addTypeAliases aliases some built-in Go types with their equivalent C types.
|
||||||
|
// It adds code like the following to the AST:
|
||||||
|
//
|
||||||
|
// type (
|
||||||
|
// C.int8_t = int8
|
||||||
|
// C.int16_t = int16
|
||||||
|
// // ...
|
||||||
|
// )
|
||||||
|
func (p *cgoPackage) addTypeAliases() {
|
||||||
|
aliasKeys := make([]string, 0, len(cgoAliases))
|
||||||
|
for key := range cgoAliases {
|
||||||
|
aliasKeys = append(aliasKeys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(aliasKeys)
|
||||||
|
gen := &ast.GenDecl{
|
||||||
|
TokPos: token.NoPos,
|
||||||
|
Tok: token.TYPE,
|
||||||
|
Lparen: token.NoPos,
|
||||||
|
Rparen: token.NoPos,
|
||||||
|
}
|
||||||
|
for _, typeName := range aliasKeys {
|
||||||
|
goTypeName := cgoAliases[typeName]
|
||||||
|
obj := &ast.Object{
|
||||||
|
Kind: ast.Typ,
|
||||||
|
Name: typeName,
|
||||||
|
}
|
||||||
|
typeSpec := &ast.TypeSpec{
|
||||||
|
Name: &ast.Ident{
|
||||||
|
NamePos: token.NoPos,
|
||||||
|
Name: typeName,
|
||||||
|
Obj: obj,
|
||||||
|
},
|
||||||
|
Assign: p.generatedPos,
|
||||||
|
Type: &ast.Ident{
|
||||||
|
NamePos: token.NoPos,
|
||||||
|
Name: goTypeName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
obj.Decl = typeSpec
|
||||||
|
gen.Specs = append(gen.Specs, typeSpec)
|
||||||
|
}
|
||||||
|
p.generated.Decls = append(p.generated.Decls, gen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *cgoPackage) addTypedefs() {
|
||||||
|
if len(p.typedefs) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gen := &ast.GenDecl{
|
||||||
|
TokPos: token.NoPos,
|
||||||
|
Tok: token.TYPE,
|
||||||
|
}
|
||||||
|
names := make([]string, 0, len(p.typedefs))
|
||||||
|
for name := range p.typedefs {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
for _, name := range names {
|
||||||
|
typedef := p.typedefs[name]
|
||||||
|
typeName := "C." + name
|
||||||
|
isAlias := true
|
||||||
|
if strings.HasPrefix(name, "_Cgo_") {
|
||||||
|
typeName = "C." + name[len("_Cgo_"):]
|
||||||
|
isAlias = false // C.short etc. should not be aliased to the equivalent Go type (not portable)
|
||||||
|
}
|
||||||
|
if _, ok := cgoAliases[typeName]; ok {
|
||||||
|
// This is a type that also exists in Go (defined in stdint.h).
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
obj := &ast.Object{
|
||||||
|
Kind: ast.Typ,
|
||||||
|
Name: typeName,
|
||||||
|
}
|
||||||
|
typeSpec := &ast.TypeSpec{
|
||||||
|
Name: &ast.Ident{
|
||||||
|
NamePos: typedef.pos,
|
||||||
|
Name: typeName,
|
||||||
|
Obj: obj,
|
||||||
|
},
|
||||||
|
Type: typedef.typeExpr,
|
||||||
|
}
|
||||||
|
if isAlias {
|
||||||
|
typeSpec.Assign = typedef.pos
|
||||||
|
}
|
||||||
|
obj.Decl = typeSpec
|
||||||
|
gen.Specs = append(gen.Specs, typeSpec)
|
||||||
|
}
|
||||||
|
p.generated.Decls = append(p.generated.Decls, gen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addElaboratedTypes adds C elaborated types as aliases. These are the "struct
|
||||||
|
// foo" or "union foo" types, often used in a typedef.
|
||||||
|
//
|
||||||
|
// See also:
|
||||||
|
// https://en.cppreference.com/w/cpp/language/elaborated_type_specifier
|
||||||
|
func (p *cgoPackage) addElaboratedTypes() {
|
||||||
|
if len(p.elaboratedTypes) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gen := &ast.GenDecl{
|
||||||
|
TokPos: token.NoPos,
|
||||||
|
Tok: token.TYPE,
|
||||||
|
}
|
||||||
|
names := make([]string, 0, len(p.elaboratedTypes))
|
||||||
|
for name := range p.elaboratedTypes {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
for _, name := range names {
|
||||||
|
typ := p.elaboratedTypes[name]
|
||||||
|
typeName := "C." + name
|
||||||
|
obj := &ast.Object{
|
||||||
|
Kind: ast.Typ,
|
||||||
|
Name: typeName,
|
||||||
|
}
|
||||||
|
typeSpec := &ast.TypeSpec{
|
||||||
|
Name: &ast.Ident{
|
||||||
|
NamePos: typ.pos,
|
||||||
|
Name: typeName,
|
||||||
|
Obj: obj,
|
||||||
|
},
|
||||||
|
Type: typ.typeExpr,
|
||||||
|
}
|
||||||
|
obj.Decl = typeSpec
|
||||||
|
gen.Specs = append(gen.Specs, typeSpec)
|
||||||
|
}
|
||||||
|
p.generated.Decls = append(p.generated.Decls, gen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// findMissingCGoNames traverses the AST and finds all C.something names. Only
|
||||||
|
// these symbols are extracted from the parsed C AST and converted to the Go
|
||||||
|
// equivalent.
|
||||||
|
func (p *cgoPackage) findMissingCGoNames(cursor *astutil.Cursor) bool {
|
||||||
|
switch node := cursor.Node().(type) {
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
x, ok := node.X.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if x.Name == "C" {
|
||||||
|
name := node.Sel.Name
|
||||||
|
if _, ok := builtinAliases[name]; ok {
|
||||||
|
name = "_Cgo_" + name
|
||||||
|
}
|
||||||
|
p.missingSymbols[name] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// walker replaces all "C".<something> expressions to literal "C.<something>"
|
||||||
|
// expressions. Such expressions are impossible to write in Go (a dot cannot be
|
||||||
|
// used in the middle of a name) so in practice all C identifiers live in a
|
||||||
|
// separate namespace (no _Cgo_ hacks like in gc).
|
||||||
|
func (p *cgoPackage) walker(cursor *astutil.Cursor) bool {
|
||||||
|
switch node := cursor.Node().(type) {
|
||||||
|
case *ast.CallExpr:
|
||||||
|
fun, ok := node.Fun.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
x, ok := fun.X.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := p.functions[fun.Sel.Name]; ok && x.Name == "C" {
|
||||||
|
node.Fun = &ast.Ident{
|
||||||
|
NamePos: x.NamePos,
|
||||||
|
Name: "C." + fun.Sel.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
x, ok := node.X.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if x.Name == "C" {
|
||||||
|
name := "C." + node.Sel.Name
|
||||||
|
if _, ok := p.functions[node.Sel.Name]; ok {
|
||||||
|
name += "$funcaddr"
|
||||||
|
}
|
||||||
|
cursor.Replace(&ast.Ident{
|
||||||
|
NamePos: x.NamePos,
|
||||||
|
Name: name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
+624
@@ -0,0 +1,624 @@
|
|||||||
|
package cgo
|
||||||
|
|
||||||
|
// This file parses a fragment of C with libclang and stores the result for AST
|
||||||
|
// modification. It does not touch the AST itself.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/scanner"
|
||||||
|
"go/token"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <clang-c/Index.h> // if this fails, install libclang-8-dev
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// This struct should be ABI-compatible on all platforms (uintptr_t has the same
|
||||||
|
// alignment etc. as void*) but does not include void* pointers that are not
|
||||||
|
// always real pointers.
|
||||||
|
// The Go garbage collector assumes that all non-nil pointer-typed integers are
|
||||||
|
// actually pointers. This is not always true, as data[1] often contains 0x1,
|
||||||
|
// which is clearly not a valid pointer. Usually the GC won't catch this issue,
|
||||||
|
// but occasionally it will leading to a crash with a vague error message.
|
||||||
|
typedef struct {
|
||||||
|
enum CXCursorKind kind;
|
||||||
|
int xdata;
|
||||||
|
uintptr_t data[3];
|
||||||
|
} GoCXCursor;
|
||||||
|
|
||||||
|
// Forwarding functions. They are implemented in libclang_stubs.c and forward to
|
||||||
|
// the real functions without doing anything else, thus they are entirely
|
||||||
|
// compatible with the versions without tinygo_ prefix. The only difference is
|
||||||
|
// the CXCursor type, which has been replaced with GoCXCursor.
|
||||||
|
GoCXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu);
|
||||||
|
unsigned tinygo_clang_visitChildren(GoCXCursor parent, CXCursorVisitor visitor, CXClientData client_data);
|
||||||
|
CXString tinygo_clang_getCursorSpelling(GoCXCursor c);
|
||||||
|
enum CXCursorKind tinygo_clang_getCursorKind(GoCXCursor c);
|
||||||
|
CXType tinygo_clang_getCursorType(GoCXCursor c);
|
||||||
|
GoCXCursor tinygo_clang_getTypeDeclaration(CXType t);
|
||||||
|
CXType tinygo_clang_getTypedefDeclUnderlyingType(GoCXCursor c);
|
||||||
|
CXType tinygo_clang_getCursorResultType(GoCXCursor c);
|
||||||
|
int tinygo_clang_Cursor_getNumArguments(GoCXCursor c);
|
||||||
|
GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
|
||||||
|
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
|
||||||
|
CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
|
||||||
|
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
|
||||||
|
|
||||||
|
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||||
|
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// storedRefs stores references to types, used for clang_visitChildren.
|
||||||
|
var storedRefs refMap
|
||||||
|
|
||||||
|
var diagnosticSeverity = [...]string{
|
||||||
|
C.CXDiagnostic_Ignored: "ignored",
|
||||||
|
C.CXDiagnostic_Note: "note",
|
||||||
|
C.CXDiagnostic_Warning: "warning",
|
||||||
|
C.CXDiagnostic_Error: "error",
|
||||||
|
C.CXDiagnostic_Fatal: "fatal",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename string, posLine int) {
|
||||||
|
index := C.clang_createIndex(0, 0)
|
||||||
|
defer C.clang_disposeIndex(index)
|
||||||
|
|
||||||
|
// pretend to be a .c file
|
||||||
|
filenameC := C.CString(posFilename + "!cgo.c")
|
||||||
|
defer C.free(unsafe.Pointer(filenameC))
|
||||||
|
|
||||||
|
// fix up error locations
|
||||||
|
fragment = fmt.Sprintf("# %d %#v\n", posLine+1, posFilename) + fragment
|
||||||
|
|
||||||
|
fragmentC := C.CString(fragment)
|
||||||
|
defer C.free(unsafe.Pointer(fragmentC))
|
||||||
|
|
||||||
|
unsavedFile := C.struct_CXUnsavedFile{
|
||||||
|
Filename: filenameC,
|
||||||
|
Length: C.ulong(len(fragment)),
|
||||||
|
Contents: fragmentC,
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert Go slice of strings to C array of strings.
|
||||||
|
cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0))))
|
||||||
|
defer C.free(cmdargsC)
|
||||||
|
cmdargs := (*[1 << 16]*C.char)(cmdargsC)
|
||||||
|
for i, cflag := range cflags {
|
||||||
|
s := C.CString(cflag)
|
||||||
|
cmdargs[i] = s
|
||||||
|
defer C.free(unsafe.Pointer(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
var unit C.CXTranslationUnit
|
||||||
|
errCode := C.clang_parseTranslationUnit2(
|
||||||
|
index,
|
||||||
|
filenameC,
|
||||||
|
(**C.char)(cmdargsC), C.int(len(cflags)), // command line args
|
||||||
|
&unsavedFile, 1, // unsaved files
|
||||||
|
C.CXTranslationUnit_DetailedPreprocessingRecord,
|
||||||
|
&unit)
|
||||||
|
if errCode != 0 {
|
||||||
|
panic("loader: failed to parse source with libclang")
|
||||||
|
}
|
||||||
|
defer C.clang_disposeTranslationUnit(unit)
|
||||||
|
|
||||||
|
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
|
||||||
|
addDiagnostic := func(diagnostic C.CXDiagnostic) {
|
||||||
|
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
|
||||||
|
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
|
||||||
|
location := C.clang_getDiagnosticLocation(diagnostic)
|
||||||
|
var libclangFilename C.CXString
|
||||||
|
var line C.unsigned
|
||||||
|
var column C.unsigned
|
||||||
|
C.clang_getPresumedLocation(location, &libclangFilename, &line, &column)
|
||||||
|
filename := getString(libclangFilename)
|
||||||
|
if filepath.IsAbs(filename) {
|
||||||
|
// Relative paths for readability, like other Go parser errors.
|
||||||
|
relpath, err := filepath.Rel(p.dir, filename)
|
||||||
|
if err == nil {
|
||||||
|
filename = relpath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.errors = append(p.errors, &scanner.Error{
|
||||||
|
Pos: token.Position{
|
||||||
|
Filename: filename,
|
||||||
|
Offset: 0, // not provided by clang_getPresumedLocation
|
||||||
|
Line: int(line),
|
||||||
|
Column: int(column),
|
||||||
|
},
|
||||||
|
Msg: severity + ": " + spelling,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for i := 0; i < numDiagnostics; i++ {
|
||||||
|
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
|
||||||
|
addDiagnostic(diagnostic)
|
||||||
|
|
||||||
|
// Child diagnostics (like notes on redefinitions).
|
||||||
|
diagnostics := C.clang_getChildDiagnostics(diagnostic)
|
||||||
|
for j := 0; j < int(C.clang_getNumDiagnosticsInSet(diagnostics)); j++ {
|
||||||
|
addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ref := storedRefs.Put(p)
|
||||||
|
defer storedRefs.Remove(ref)
|
||||||
|
cursor := C.tinygo_clang_getTranslationUnitCursor(unit)
|
||||||
|
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref))
|
||||||
|
}
|
||||||
|
|
||||||
|
//export tinygo_clang_globals_visitor
|
||||||
|
func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
||||||
|
p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage)
|
||||||
|
kind := C.tinygo_clang_getCursorKind(c)
|
||||||
|
pos := p.getCursorPosition(c)
|
||||||
|
switch kind {
|
||||||
|
case C.CXCursor_FunctionDecl:
|
||||||
|
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||||
|
if _, required := p.missingSymbols[name]; !required {
|
||||||
|
return C.CXChildVisit_Continue
|
||||||
|
}
|
||||||
|
cursorType := C.tinygo_clang_getCursorType(c)
|
||||||
|
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
|
||||||
|
return C.CXChildVisit_Continue // not supported
|
||||||
|
}
|
||||||
|
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
|
||||||
|
fn := &functionInfo{
|
||||||
|
pos: pos,
|
||||||
|
}
|
||||||
|
p.functions[name] = fn
|
||||||
|
for i := 0; i < numArgs; i++ {
|
||||||
|
arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i))
|
||||||
|
argName := getString(C.tinygo_clang_getCursorSpelling(arg))
|
||||||
|
argType := C.clang_getArgType(cursorType, C.uint(i))
|
||||||
|
if argName == "" {
|
||||||
|
argName = "$" + strconv.Itoa(i)
|
||||||
|
}
|
||||||
|
fn.args = append(fn.args, paramInfo{
|
||||||
|
name: argName,
|
||||||
|
typeExpr: p.makeASTType(argType, pos),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
resultType := C.tinygo_clang_getCursorResultType(c)
|
||||||
|
if resultType.kind != C.CXType_Void {
|
||||||
|
fn.results = &ast.FieldList{
|
||||||
|
List: []*ast.Field{
|
||||||
|
&ast.Field{
|
||||||
|
Type: p.makeASTType(resultType, pos),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case C.CXCursor_StructDecl:
|
||||||
|
typ := C.tinygo_clang_getCursorType(c)
|
||||||
|
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||||
|
if _, required := p.missingSymbols["struct_"+name]; !required {
|
||||||
|
return C.CXChildVisit_Continue
|
||||||
|
}
|
||||||
|
p.makeASTType(typ, pos)
|
||||||
|
case C.CXCursor_TypedefDecl:
|
||||||
|
typedefType := C.tinygo_clang_getCursorType(c)
|
||||||
|
name := getString(C.clang_getTypedefName(typedefType))
|
||||||
|
if _, required := p.missingSymbols[name]; !required {
|
||||||
|
return C.CXChildVisit_Continue
|
||||||
|
}
|
||||||
|
p.makeASTType(typedefType, pos)
|
||||||
|
case C.CXCursor_VarDecl:
|
||||||
|
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||||
|
if _, required := p.missingSymbols[name]; !required {
|
||||||
|
return C.CXChildVisit_Continue
|
||||||
|
}
|
||||||
|
cursorType := C.tinygo_clang_getCursorType(c)
|
||||||
|
p.globals[name] = globalInfo{
|
||||||
|
typeExpr: p.makeASTType(cursorType, pos),
|
||||||
|
pos: pos,
|
||||||
|
}
|
||||||
|
case C.CXCursor_MacroDefinition:
|
||||||
|
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||||
|
if _, required := p.missingSymbols[name]; !required {
|
||||||
|
return C.CXChildVisit_Continue
|
||||||
|
}
|
||||||
|
sourceRange := C.tinygo_clang_getCursorExtent(c)
|
||||||
|
start := C.clang_getRangeStart(sourceRange)
|
||||||
|
end := C.clang_getRangeEnd(sourceRange)
|
||||||
|
var file, endFile C.CXFile
|
||||||
|
var startOffset, endOffset C.unsigned
|
||||||
|
C.clang_getExpansionLocation(start, &file, nil, nil, &startOffset)
|
||||||
|
if file == nil {
|
||||||
|
panic("could not find file where macro is defined")
|
||||||
|
}
|
||||||
|
C.clang_getExpansionLocation(end, &endFile, nil, nil, &endOffset)
|
||||||
|
if file != endFile {
|
||||||
|
panic("expected start and end location of a #define to be in the same file")
|
||||||
|
}
|
||||||
|
if startOffset > endOffset {
|
||||||
|
panic("startOffset > endOffset")
|
||||||
|
}
|
||||||
|
|
||||||
|
// read file contents and extract the relevant byte range
|
||||||
|
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
|
||||||
|
var size C.size_t
|
||||||
|
sourcePtr := C.clang_getFileContents(tu, file, &size)
|
||||||
|
if endOffset >= C.uint(size) {
|
||||||
|
panic("endOffset lies after end of file")
|
||||||
|
}
|
||||||
|
source := string(((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[startOffset:endOffset:endOffset])
|
||||||
|
if !strings.HasPrefix(source, name) {
|
||||||
|
panic(fmt.Sprintf("expected #define value to start with %#v, got %#v", name, source))
|
||||||
|
}
|
||||||
|
value := strings.TrimSpace(source[len(name):])
|
||||||
|
for len(value) != 0 && value[0] == '(' && value[len(value)-1] == ')' {
|
||||||
|
value = strings.TrimSpace(value[1 : len(value)-1])
|
||||||
|
}
|
||||||
|
if len(value) == 0 {
|
||||||
|
// Pretend it doesn't exist at all.
|
||||||
|
return C.CXChildVisit_Continue
|
||||||
|
}
|
||||||
|
// For information about integer literals:
|
||||||
|
// https://en.cppreference.com/w/cpp/language/integer_literal
|
||||||
|
if value[0] == '"' {
|
||||||
|
// string constant
|
||||||
|
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.STRING, value}, pos}
|
||||||
|
return C.CXChildVisit_Continue
|
||||||
|
}
|
||||||
|
if value[0] == '\'' {
|
||||||
|
// char constant
|
||||||
|
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.CHAR, value}, pos}
|
||||||
|
return C.CXChildVisit_Continue
|
||||||
|
}
|
||||||
|
// assume it's a number (int or float)
|
||||||
|
value = strings.Replace(value, "'", "", -1) // remove ' chars
|
||||||
|
value = strings.TrimRight(value, "lu") // remove llu suffixes etc.
|
||||||
|
// find the first non-number
|
||||||
|
nonnum := byte(0)
|
||||||
|
for i := 0; i < len(value); i++ {
|
||||||
|
if value[i] < '0' || value[i] > '9' {
|
||||||
|
nonnum = value[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// determine number type based on the first non-number
|
||||||
|
switch nonnum {
|
||||||
|
case 0:
|
||||||
|
// no non-number found, must be an integer
|
||||||
|
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
|
||||||
|
case 'x', 'X':
|
||||||
|
// hex integer constant
|
||||||
|
// TODO: may also be a floating point number per C++17.
|
||||||
|
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
|
||||||
|
case '.', 'e':
|
||||||
|
// float constant
|
||||||
|
value = strings.TrimRight(value, "fFlL")
|
||||||
|
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.FLOAT, value}, pos}
|
||||||
|
default:
|
||||||
|
// unknown type, ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return C.CXChildVisit_Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
func getString(clangString C.CXString) (s string) {
|
||||||
|
rawString := C.clang_getCString(clangString)
|
||||||
|
s = C.GoString(rawString)
|
||||||
|
C.clang_disposeString(clangString)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCursorPosition returns a usable token.Pos from a libclang cursor. If the
|
||||||
|
// file for this cursor has not been seen before, it is read from libclang
|
||||||
|
// (which already has the file in memory) and added to the token.FileSet.
|
||||||
|
func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
|
||||||
|
location := C.tinygo_clang_getCursorLocation(cursor)
|
||||||
|
var file C.CXFile
|
||||||
|
var line C.unsigned
|
||||||
|
var column C.unsigned
|
||||||
|
var offset C.unsigned
|
||||||
|
C.clang_getExpansionLocation(location, &file, &line, &column, &offset)
|
||||||
|
if line == 0 || file == nil {
|
||||||
|
// Invalid token.
|
||||||
|
return token.NoPos
|
||||||
|
}
|
||||||
|
filename := getString(C.clang_getFileName(file))
|
||||||
|
if _, ok := p.tokenFiles[filename]; !ok {
|
||||||
|
// File has not been seen before in this package, add line information
|
||||||
|
// now by reading the file from libclang.
|
||||||
|
tu := C.tinygo_clang_Cursor_getTranslationUnit(cursor)
|
||||||
|
var size C.size_t
|
||||||
|
sourcePtr := C.clang_getFileContents(tu, file, &size)
|
||||||
|
source := ((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[:size:size]
|
||||||
|
lines := []int{0}
|
||||||
|
for i := 0; i < len(source)-1; i++ {
|
||||||
|
if source[i] == '\n' {
|
||||||
|
lines = append(lines, i+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f := p.fset.AddFile(filename, -1, int(size))
|
||||||
|
f.SetLines(lines)
|
||||||
|
p.tokenFiles[filename] = f
|
||||||
|
}
|
||||||
|
return p.tokenFiles[filename].Pos(int(offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeASTType return the ast.Expr for the given libclang type. In other words,
|
||||||
|
// it converts a libclang type to a type in the Go AST.
|
||||||
|
func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
||||||
|
var typeName string
|
||||||
|
switch typ.kind {
|
||||||
|
case C.CXType_Char_S, C.CXType_Char_U:
|
||||||
|
typeName = "C.char"
|
||||||
|
case C.CXType_SChar:
|
||||||
|
typeName = "C.schar"
|
||||||
|
case C.CXType_UChar:
|
||||||
|
typeName = "C.uchar"
|
||||||
|
case C.CXType_Short:
|
||||||
|
typeName = "C.short"
|
||||||
|
case C.CXType_UShort:
|
||||||
|
typeName = "C.ushort"
|
||||||
|
case C.CXType_Int:
|
||||||
|
typeName = "C.int"
|
||||||
|
case C.CXType_UInt:
|
||||||
|
typeName = "C.uint"
|
||||||
|
case C.CXType_Long:
|
||||||
|
typeName = "C.long"
|
||||||
|
case C.CXType_ULong:
|
||||||
|
typeName = "C.ulong"
|
||||||
|
case C.CXType_LongLong:
|
||||||
|
typeName = "C.longlong"
|
||||||
|
case C.CXType_ULongLong:
|
||||||
|
typeName = "C.ulonglong"
|
||||||
|
case C.CXType_Bool:
|
||||||
|
typeName = "bool"
|
||||||
|
case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble:
|
||||||
|
switch C.clang_Type_getSizeOf(typ) {
|
||||||
|
case 4:
|
||||||
|
typeName = "float32"
|
||||||
|
case 8:
|
||||||
|
typeName = "float64"
|
||||||
|
default:
|
||||||
|
// Don't do anything, rely on the fallback code to show a somewhat
|
||||||
|
// sensible error message like "undeclared name: C.long double".
|
||||||
|
}
|
||||||
|
case C.CXType_Complex:
|
||||||
|
switch C.clang_Type_getSizeOf(typ) {
|
||||||
|
case 8:
|
||||||
|
typeName = "complex64"
|
||||||
|
case 16:
|
||||||
|
typeName = "complex128"
|
||||||
|
}
|
||||||
|
case C.CXType_Pointer:
|
||||||
|
pointeeType := C.clang_getPointeeType(typ)
|
||||||
|
if pointeeType.kind == C.CXType_Void {
|
||||||
|
// void* type is translated to Go as unsafe.Pointer
|
||||||
|
return &ast.SelectorExpr{
|
||||||
|
X: &ast.Ident{
|
||||||
|
NamePos: pos,
|
||||||
|
Name: "unsafe",
|
||||||
|
},
|
||||||
|
Sel: &ast.Ident{
|
||||||
|
NamePos: pos,
|
||||||
|
Name: "Pointer",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &ast.StarExpr{
|
||||||
|
Star: pos,
|
||||||
|
X: p.makeASTType(pointeeType, pos),
|
||||||
|
}
|
||||||
|
case C.CXType_ConstantArray:
|
||||||
|
return &ast.ArrayType{
|
||||||
|
Lbrack: pos,
|
||||||
|
Len: &ast.BasicLit{
|
||||||
|
ValuePos: pos,
|
||||||
|
Kind: token.INT,
|
||||||
|
Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10),
|
||||||
|
},
|
||||||
|
Elt: p.makeASTType(C.clang_getElementType(typ), pos),
|
||||||
|
}
|
||||||
|
case C.CXType_FunctionProto:
|
||||||
|
// Be compatible with gc, which uses the *[0]byte type for function
|
||||||
|
// pointer types.
|
||||||
|
// Return type [0]byte because this is a function type, not a pointer to
|
||||||
|
// this function type.
|
||||||
|
return &ast.ArrayType{
|
||||||
|
Lbrack: pos,
|
||||||
|
Len: &ast.BasicLit{
|
||||||
|
ValuePos: pos,
|
||||||
|
Kind: token.INT,
|
||||||
|
Value: "0",
|
||||||
|
},
|
||||||
|
Elt: &ast.Ident{
|
||||||
|
NamePos: pos,
|
||||||
|
Name: "byte",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case C.CXType_Typedef:
|
||||||
|
name := getString(C.clang_getTypedefName(typ))
|
||||||
|
if _, ok := p.typedefs[name]; !ok {
|
||||||
|
p.typedefs[name] = nil // don't recurse
|
||||||
|
c := C.tinygo_clang_getTypeDeclaration(typ)
|
||||||
|
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
|
||||||
|
expr := p.makeASTType(underlyingType, pos)
|
||||||
|
if strings.HasPrefix(name, "_Cgo_") {
|
||||||
|
expr := expr.(*ast.Ident)
|
||||||
|
typeSize := C.clang_Type_getSizeOf(underlyingType)
|
||||||
|
switch expr.Name {
|
||||||
|
case "C.char":
|
||||||
|
if typeSize != 1 {
|
||||||
|
// This happens for some very special purpose architectures
|
||||||
|
// (DSPs etc.) that are not currently targeted.
|
||||||
|
// https://www.embecosm.com/2017/04/18/non-8-bit-char-support-in-clang-and-llvm/
|
||||||
|
panic("unknown char width")
|
||||||
|
}
|
||||||
|
switch underlyingType.kind {
|
||||||
|
case C.CXType_Char_S:
|
||||||
|
expr.Name = "int8"
|
||||||
|
case C.CXType_Char_U:
|
||||||
|
expr.Name = "uint8"
|
||||||
|
}
|
||||||
|
case "C.schar", "C.short", "C.int", "C.long", "C.longlong":
|
||||||
|
switch typeSize {
|
||||||
|
case 1:
|
||||||
|
expr.Name = "int8"
|
||||||
|
case 2:
|
||||||
|
expr.Name = "int16"
|
||||||
|
case 4:
|
||||||
|
expr.Name = "int32"
|
||||||
|
case 8:
|
||||||
|
expr.Name = "int64"
|
||||||
|
}
|
||||||
|
case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong":
|
||||||
|
switch typeSize {
|
||||||
|
case 1:
|
||||||
|
expr.Name = "uint8"
|
||||||
|
case 2:
|
||||||
|
expr.Name = "uint16"
|
||||||
|
case 4:
|
||||||
|
expr.Name = "uint32"
|
||||||
|
case 8:
|
||||||
|
expr.Name = "uint64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.typedefs[name] = &typedefInfo{
|
||||||
|
typeExpr: expr,
|
||||||
|
pos: pos,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &ast.Ident{
|
||||||
|
NamePos: pos,
|
||||||
|
Name: "C." + name,
|
||||||
|
}
|
||||||
|
case C.CXType_Elaborated:
|
||||||
|
underlying := C.clang_Type_getNamedType(typ)
|
||||||
|
switch underlying.kind {
|
||||||
|
case C.CXType_Record:
|
||||||
|
return p.makeASTType(underlying, pos)
|
||||||
|
default:
|
||||||
|
panic("unknown elaborated type")
|
||||||
|
}
|
||||||
|
case C.CXType_Record:
|
||||||
|
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
||||||
|
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
|
||||||
|
var cgoName string
|
||||||
|
switch C.tinygo_clang_getCursorKind(cursor) {
|
||||||
|
case C.CXCursor_StructDecl:
|
||||||
|
cgoName = "struct_" + name
|
||||||
|
case C.CXCursor_UnionDecl:
|
||||||
|
cgoName = "union_" + name
|
||||||
|
default:
|
||||||
|
panic("unknown record declaration")
|
||||||
|
}
|
||||||
|
if _, ok := p.elaboratedTypes[cgoName]; !ok {
|
||||||
|
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
|
||||||
|
fieldList := &ast.FieldList{
|
||||||
|
Opening: pos,
|
||||||
|
Closing: pos,
|
||||||
|
}
|
||||||
|
ref := storedRefs.Put(struct {
|
||||||
|
fieldList *ast.FieldList
|
||||||
|
pkg *cgoPackage
|
||||||
|
}{fieldList, p})
|
||||||
|
defer storedRefs.Remove(ref)
|
||||||
|
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
|
||||||
|
switch C.tinygo_clang_getCursorKind(cursor) {
|
||||||
|
case C.CXCursor_StructDecl:
|
||||||
|
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
||||||
|
typeExpr: &ast.StructType{
|
||||||
|
Struct: pos,
|
||||||
|
Fields: fieldList,
|
||||||
|
},
|
||||||
|
pos: pos,
|
||||||
|
}
|
||||||
|
case C.CXCursor_UnionDecl:
|
||||||
|
if len(fieldList.List) > 1 {
|
||||||
|
// Insert a special field at the front (of zero width) as a
|
||||||
|
// marker that this is struct is actually a union. This is done
|
||||||
|
// by giving the field a name that cannot be expressed directly
|
||||||
|
// in Go.
|
||||||
|
// Other parts of the compiler look at the first element in a
|
||||||
|
// struct (of size > 2) to know whether this is a union.
|
||||||
|
// Note that we don't have to insert it for single-element
|
||||||
|
// unions as they're basically equivalent to a struct.
|
||||||
|
unionMarker := &ast.Field{
|
||||||
|
Type: &ast.StructType{
|
||||||
|
Struct: pos,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
unionMarker.Names = []*ast.Ident{
|
||||||
|
&ast.Ident{
|
||||||
|
NamePos: pos,
|
||||||
|
Name: "C union",
|
||||||
|
Obj: &ast.Object{
|
||||||
|
Kind: ast.Var,
|
||||||
|
Name: "C union",
|
||||||
|
Decl: unionMarker,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
|
||||||
|
}
|
||||||
|
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
||||||
|
typeExpr: &ast.StructType{
|
||||||
|
Struct: pos,
|
||||||
|
Fields: fieldList,
|
||||||
|
},
|
||||||
|
pos: pos,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &ast.Ident{
|
||||||
|
NamePos: pos,
|
||||||
|
Name: "C." + cgoName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if typeName == "" {
|
||||||
|
// Fallback, probably incorrect but at least the error points to an odd
|
||||||
|
// type name.
|
||||||
|
typeName = "C." + getString(C.clang_getTypeSpelling(typ))
|
||||||
|
}
|
||||||
|
return &ast.Ident{
|
||||||
|
NamePos: pos,
|
||||||
|
Name: typeName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//export tinygo_clang_struct_visitor
|
||||||
|
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
||||||
|
passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct {
|
||||||
|
fieldList *ast.FieldList
|
||||||
|
pkg *cgoPackage
|
||||||
|
})
|
||||||
|
fieldList := passed.fieldList
|
||||||
|
p := passed.pkg
|
||||||
|
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
|
||||||
|
panic("expected field inside cursor")
|
||||||
|
}
|
||||||
|
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||||
|
typ := C.tinygo_clang_getCursorType(c)
|
||||||
|
field := &ast.Field{
|
||||||
|
Type: p.makeASTType(typ, p.getCursorPosition(c)),
|
||||||
|
}
|
||||||
|
field.Names = []*ast.Ident{
|
||||||
|
&ast.Ident{
|
||||||
|
NamePos: p.getCursorPosition(c),
|
||||||
|
Name: name,
|
||||||
|
Obj: &ast.Object{
|
||||||
|
Kind: ast.Var,
|
||||||
|
Name: name,
|
||||||
|
Decl: field,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fieldList.List = append(fieldList.List, field)
|
||||||
|
return C.CXChildVisit_Continue
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// +build !byollvm
|
// +build !byollvm
|
||||||
|
|
||||||
package loader
|
package cgo
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo linux CFLAGS: -I/usr/lib/llvm-8/include
|
#cgo linux CFLAGS: -I/usr/lib/llvm-8/include
|
||||||
@@ -44,3 +44,15 @@ int tinygo_clang_Cursor_getNumArguments(CXCursor c) {
|
|||||||
CXCursor tinygo_clang_Cursor_getArgument(CXCursor c, unsigned i) {
|
CXCursor tinygo_clang_Cursor_getArgument(CXCursor c, unsigned i) {
|
||||||
return clang_Cursor_getArgument(c, i);
|
return clang_Cursor_getArgument(c, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CXSourceLocation tinygo_clang_getCursorLocation(CXCursor c) {
|
||||||
|
return clang_getCursorLocation(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
CXSourceRange tinygo_clang_getCursorExtent(CXCursor c) {
|
||||||
|
return clang_getCursorExtent(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(CXCursor c) {
|
||||||
|
return clang_Cursor_getTranslationUnit(c);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package loader
|
package cgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
@@ -8,17 +8,17 @@ import (
|
|||||||
// #include <stdlib.h>
|
// #include <stdlib.h>
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
// RefMap is a convenient way to store opaque references that can be passed to
|
// refMap is a convenient way to store opaque references that can be passed to
|
||||||
// C. It is useful if an API uses function pointers and you cannot pass a Go
|
// C. It is useful if an API uses function pointers and you cannot pass a Go
|
||||||
// pointer but only a C pointer.
|
// pointer but only a C pointer.
|
||||||
type RefMap struct {
|
type refMap struct {
|
||||||
refs map[unsafe.Pointer]interface{}
|
refs map[unsafe.Pointer]interface{}
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put stores a value in the map. It can later be retrieved using Get. It must
|
// Put stores a value in the map. It can later be retrieved using Get. It must
|
||||||
// be removed using Remove to avoid memory leaks.
|
// be removed using Remove to avoid memory leaks.
|
||||||
func (m *RefMap) Put(v interface{}) unsafe.Pointer {
|
func (m *refMap) Put(v interface{}) unsafe.Pointer {
|
||||||
m.lock.Lock()
|
m.lock.Lock()
|
||||||
defer m.lock.Unlock()
|
defer m.lock.Unlock()
|
||||||
if m.refs == nil {
|
if m.refs == nil {
|
||||||
@@ -31,14 +31,14 @@ func (m *RefMap) Put(v interface{}) unsafe.Pointer {
|
|||||||
|
|
||||||
// Get returns a stored value previously inserted with Put. Use the same
|
// Get returns a stored value previously inserted with Put. Use the same
|
||||||
// reference as you got from Put.
|
// reference as you got from Put.
|
||||||
func (m *RefMap) Get(ref unsafe.Pointer) interface{} {
|
func (m *refMap) Get(ref unsafe.Pointer) interface{} {
|
||||||
m.lock.Lock()
|
m.lock.Lock()
|
||||||
defer m.lock.Unlock()
|
defer m.lock.Unlock()
|
||||||
return m.refs[ref]
|
return m.refs[ref]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove deletes a single reference from the map.
|
// Remove deletes a single reference from the map.
|
||||||
func (m *RefMap) Remove(ref unsafe.Pointer) {
|
func (m *refMap) Remove(ref unsafe.Pointer) {
|
||||||
m.lock.Lock()
|
m.lock.Lock()
|
||||||
defer m.lock.Unlock()
|
defer m.lock.Unlock()
|
||||||
delete(m.refs, ref)
|
delete(m.refs, ref)
|
||||||
+30
-7
@@ -1,10 +1,33 @@
|
|||||||
// +build !darwin
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
// commands used by the compilation process might have different file names on Linux than those used on macOS.
|
import (
|
||||||
var commands = map[string]string{
|
"errors"
|
||||||
"clang": "clang-8",
|
"os"
|
||||||
"ld.lld": "ld.lld-8",
|
"os/exec"
|
||||||
"wasm-ld": "wasm-ld-8",
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Commands used by the compilation process might have different file names
|
||||||
|
// across operating systems and distributions.
|
||||||
|
var commands = map[string][]string{
|
||||||
|
"clang": {"clang-8"},
|
||||||
|
"ld.lld": {"ld.lld-8", "ld.lld"},
|
||||||
|
"wasm-ld": {"wasm-ld-8", "wasm-ld"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func execCommand(cmdNames []string, args ...string) error {
|
||||||
|
for _, cmdName := range cmdNames {
|
||||||
|
cmd := exec.Command(cmdName, args...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
if err, ok := err.(*exec.Error); ok && err.Err == exec.ErrNotFound {
|
||||||
|
// this command was not found, try the next
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("none of these commands were found in your $PATH: " + strings.Join(cmdNames, " "))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
// +build darwin
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
// commands used by the compilation process might have different file names on macOS than those used on Linux.
|
|
||||||
var commands = map[string]string{
|
|
||||||
"clang": "clang-8",
|
|
||||||
"ld.lld": "ld.lld",
|
|
||||||
"wasm-ld": "wasm-ld",
|
|
||||||
}
|
|
||||||
+41
-4
@@ -55,6 +55,25 @@ func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expand an argument type to a list of offsets from the start of the object.
|
||||||
|
// Used together with expandFormalParam to get the offset of each value from the
|
||||||
|
// start of the non-expanded value.
|
||||||
|
func (c *Compiler) expandFormalParamOffsets(t llvm.Type) []uint64 {
|
||||||
|
switch t.TypeKind() {
|
||||||
|
case llvm.StructTypeKind:
|
||||||
|
fields := c.flattenAggregateTypeOffsets(t)
|
||||||
|
if len(fields) <= MaxFieldsPerParam {
|
||||||
|
return fields
|
||||||
|
} else {
|
||||||
|
// failed to lower
|
||||||
|
return []uint64{0}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// TODO: split small arrays
|
||||||
|
return []uint64{0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Equivalent of expandFormalParamType for parameter values.
|
// Equivalent of expandFormalParamType for parameter values.
|
||||||
func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value {
|
func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value {
|
||||||
switch v.Type().TypeKind() {
|
switch v.Type().TypeKind() {
|
||||||
@@ -92,6 +111,27 @@ func (c *Compiler) flattenAggregateType(t llvm.Type) []llvm.Type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the offsets from the start of the object if this object type were
|
||||||
|
// flattened like in flattenAggregate. Used together with flattenAggregate to
|
||||||
|
// know the start indices of each value in the non-flattened object.
|
||||||
|
func (c *Compiler) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
|
||||||
|
switch t.TypeKind() {
|
||||||
|
case llvm.StructTypeKind:
|
||||||
|
fields := make([]uint64, 0, t.StructElementTypesCount())
|
||||||
|
for fieldIndex, field := range t.StructElementTypes() {
|
||||||
|
suboffsets := c.flattenAggregateTypeOffsets(field)
|
||||||
|
offset := c.targetData.ElementOffset(t, fieldIndex)
|
||||||
|
for i := range suboffsets {
|
||||||
|
suboffsets[i] += offset
|
||||||
|
}
|
||||||
|
fields = append(fields, suboffsets...)
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
default:
|
||||||
|
return []uint64{0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Break down a struct into its elementary types for argument passing. The value
|
// Break down a struct into its elementary types for argument passing. The value
|
||||||
// equivalent of flattenAggregateType
|
// equivalent of flattenAggregateType
|
||||||
func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value {
|
func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value {
|
||||||
@@ -123,10 +163,7 @@ func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value)
|
|||||||
switch t.TypeKind() {
|
switch t.TypeKind() {
|
||||||
case llvm.StructTypeKind:
|
case llvm.StructTypeKind:
|
||||||
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
|
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
|
||||||
value, err := c.getZeroValue(t)
|
value := c.getZeroValue(t)
|
||||||
if err != nil {
|
|
||||||
panic("could not get zero value of struct: " + err.Error())
|
|
||||||
}
|
|
||||||
for i, subtyp := range t.StructElementTypes() {
|
for i, subtyp := range t.StructElementTypes() {
|
||||||
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
|
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
|
||||||
fields = remaining
|
fields = remaining
|
||||||
|
|||||||
+37
-49
@@ -12,16 +12,6 @@ import (
|
|||||||
|
|
||||||
// emitMakeChan returns a new channel value for the given channel type.
|
// emitMakeChan returns a new channel value for the given channel type.
|
||||||
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
|
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
|
||||||
valueType, err := c.getLLVMType(expr.Type().(*types.Chan).Elem())
|
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
if c.targetData.TypeAllocSize(valueType) > c.targetData.TypeAllocSize(c.intType) {
|
|
||||||
// Values bigger than int overflow the data part of the coroutine.
|
|
||||||
// TODO: make the coroutine data part big enough to hold these bigger
|
|
||||||
// values.
|
|
||||||
return llvm.Value{}, c.makeError(expr.Pos(), "todo: channel with values bigger than int")
|
|
||||||
}
|
|
||||||
chanType := c.mod.GetTypeByName("runtime.channel")
|
chanType := c.mod.GetTypeByName("runtime.channel")
|
||||||
size := c.targetData.TypeAllocSize(chanType)
|
size := c.targetData.TypeAllocSize(chanType)
|
||||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||||
@@ -32,66 +22,64 @@ func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
|
|||||||
|
|
||||||
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
|
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
|
||||||
// channel send operation during goroutine lowering.
|
// channel send operation during goroutine lowering.
|
||||||
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) error {
|
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
|
||||||
valueType, err := c.getLLVMType(instr.Chan.Type().(*types.Chan).Elem())
|
valueType := c.getLLVMType(instr.X.Type())
|
||||||
if err != nil {
|
ch := c.getValue(frame, instr.Chan)
|
||||||
return err
|
chanValue := c.getValue(frame, instr.X)
|
||||||
}
|
|
||||||
ch, err := c.parseExpr(frame, instr.Chan)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
chanValue, err := c.parseExpr(frame, instr.X)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false)
|
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false)
|
||||||
|
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
||||||
|
|
||||||
|
// store value-to-send
|
||||||
|
c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
||||||
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
||||||
|
c.builder.SetInsertPointBefore(coroutine)
|
||||||
|
c.builder.SetInsertPointAtEnd(coroutine.InstructionParent())
|
||||||
c.builder.CreateStore(chanValue, valueAlloca)
|
c.builder.CreateStore(chanValue, valueAlloca)
|
||||||
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
|
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
|
||||||
c.createRuntimeCall("chanSendStub", []llvm.Value{llvm.Undef(c.i8ptrType), ch, valueAllocaCast, valueSize}, "")
|
|
||||||
return nil
|
// Do the send.
|
||||||
|
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
|
||||||
|
|
||||||
|
// Make sure CoroSplit includes the alloca in the coroutine frame.
|
||||||
|
// This is a bit dirty, but it works (at least in LLVM 8).
|
||||||
|
valueSizeI64 := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(chanValue.Type()), false)
|
||||||
|
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSizeI64, valueAllocaCast}, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitChanRecv emits a pseudo chan receive operation. It is lowered to the
|
// emitChanRecv emits a pseudo chan receive operation. It is lowered to the
|
||||||
// actual channel receive operation during goroutine lowering.
|
// actual channel receive operation during goroutine lowering.
|
||||||
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
|
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
|
||||||
valueType, err := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
|
valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
||||||
ch, err := c.parseExpr(frame, unop.X)
|
ch := c.getValue(frame, unop.X)
|
||||||
if err != nil {
|
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
// Allocate memory to receive into.
|
||||||
|
c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
||||||
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
||||||
|
c.builder.SetInsertPointBefore(coroutine)
|
||||||
|
c.builder.SetInsertPointAtEnd(coroutine.InstructionParent())
|
||||||
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
|
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
|
||||||
valueOk := c.builder.CreateAlloca(c.ctx.Int1Type(), "chan.comma-ok.alloca")
|
|
||||||
c.createRuntimeCall("chanRecvStub", []llvm.Value{llvm.Undef(c.i8ptrType), ch, valueAllocaCast, valueOk, valueSize}, "")
|
// Do the receive.
|
||||||
|
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
|
||||||
received := c.builder.CreateLoad(valueAlloca, "chan.received")
|
received := c.builder.CreateLoad(valueAlloca, "chan.received")
|
||||||
if unop.CommaOk {
|
if unop.CommaOk {
|
||||||
commaOk := c.builder.CreateLoad(valueOk, "chan.comma-ok")
|
commaOk := c.createRuntimeCall("getTaskPromiseData", []llvm.Value{coroutine}, "chan.commaOk.wide")
|
||||||
|
commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk")
|
||||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
|
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
|
||||||
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
|
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
|
||||||
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
|
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
|
||||||
return tuple, nil
|
return tuple
|
||||||
} else {
|
} else {
|
||||||
return received, nil
|
return received
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitChanClose closes the given channel.
|
// emitChanClose closes the given channel.
|
||||||
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) error {
|
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
|
||||||
valueType, err := c.getLLVMType(param.Type().(*types.Chan).Elem())
|
valueType := c.getLLVMType(param.Type().(*types.Chan).Elem())
|
||||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
||||||
if err != nil {
|
ch := c.getValue(frame, param)
|
||||||
return err
|
|
||||||
}
|
|
||||||
ch, err := c.parseExpr(frame, param)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
|
c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
+538
-615
File diff suppressed because it is too large
Load Diff
+19
-53
@@ -36,7 +36,7 @@ func (c *Compiler) deferInitFunc(frame *Frame) {
|
|||||||
|
|
||||||
// emitDefer emits a single defer instruction, to be run when this function
|
// emitDefer emits a single defer instruction, to be run when this function
|
||||||
// returns.
|
// returns.
|
||||||
func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
func (c *Compiler) emitDefer(frame *Frame, 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 := c.builder.CreateLoad(frame.deferPtr, "defer.next")
|
next := c.builder.CreateLoad(frame.deferPtr, "defer.next")
|
||||||
@@ -56,18 +56,12 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
|||||||
|
|
||||||
// Collect all values to be put in the struct (starting with
|
// Collect all values to be put in the struct (starting with
|
||||||
// runtime._defer fields, followed by the call parameters).
|
// runtime._defer fields, followed by the call parameters).
|
||||||
itf, err := c.parseExpr(frame, instr.Call.Value) // interface
|
itf := c.getValue(frame, instr.Call.Value) // interface
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
||||||
values = []llvm.Value{callback, next, receiverValue}
|
values = []llvm.Value{callback, next, receiverValue}
|
||||||
valueTypes = append(valueTypes, c.i8ptrType)
|
valueTypes = append(valueTypes, c.i8ptrType)
|
||||||
for _, arg := range instr.Call.Args {
|
for _, arg := range instr.Call.Args {
|
||||||
val, err := c.parseExpr(frame, arg)
|
val := c.getValue(frame, arg)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
values = append(values, val)
|
values = append(values, val)
|
||||||
valueTypes = append(valueTypes, val.Type())
|
valueTypes = append(valueTypes, val.Type())
|
||||||
}
|
}
|
||||||
@@ -86,10 +80,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
|||||||
// runtime._defer fields).
|
// runtime._defer fields).
|
||||||
values = []llvm.Value{callback, next}
|
values = []llvm.Value{callback, next}
|
||||||
for _, param := range instr.Call.Args {
|
for _, param := range instr.Call.Args {
|
||||||
llvmParam, err := c.parseExpr(frame, param)
|
llvmParam := c.getValue(frame, param)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
values = append(values, llvmParam)
|
values = append(values, llvmParam)
|
||||||
valueTypes = append(valueTypes, llvmParam.Type())
|
valueTypes = append(valueTypes, llvmParam.Type())
|
||||||
}
|
}
|
||||||
@@ -101,10 +92,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
|||||||
// pointer.
|
// pointer.
|
||||||
// TODO: ignore this closure entirely and put pointers to the free
|
// TODO: ignore this closure entirely and put pointers to the free
|
||||||
// variables directly in the defer struct, avoiding a memory allocation.
|
// variables directly in the defer struct, avoiding a memory allocation.
|
||||||
closure, err := c.parseExpr(frame, instr.Call.Value)
|
closure := c.getValue(frame, instr.Call.Value)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
context := c.builder.CreateExtractValue(closure, 0, "")
|
context := c.builder.CreateExtractValue(closure, 0, "")
|
||||||
|
|
||||||
// Get the callback number.
|
// Get the callback number.
|
||||||
@@ -120,10 +108,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
|||||||
// context pointer).
|
// context pointer).
|
||||||
values = []llvm.Value{callback, next}
|
values = []llvm.Value{callback, next}
|
||||||
for _, param := range instr.Call.Args {
|
for _, param := range instr.Call.Args {
|
||||||
llvmParam, err := c.parseExpr(frame, param)
|
llvmParam := c.getValue(frame, param)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
values = append(values, llvmParam)
|
values = append(values, llvmParam)
|
||||||
valueTypes = append(valueTypes, llvmParam.Type())
|
valueTypes = append(valueTypes, llvmParam.Type())
|
||||||
}
|
}
|
||||||
@@ -131,15 +116,13 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
|||||||
valueTypes = append(valueTypes, context.Type())
|
valueTypes = append(valueTypes, context.Type())
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return c.makeError(instr.Pos(), "todo: defer on uncommon function call type")
|
c.addError(instr.Pos(), "todo: defer on uncommon function call type")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a struct out of the collected values to put in the defer frame.
|
// Make a struct out of the collected values to put in the defer frame.
|
||||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||||
deferFrame, err := c.getZeroValue(deferFrameType)
|
deferFrame := c.getZeroValue(deferFrameType)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i, value := range values {
|
for i, value := range values {
|
||||||
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
|
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
|
||||||
}
|
}
|
||||||
@@ -151,11 +134,10 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
|||||||
// Push it on top of the linked list by replacing deferPtr.
|
// Push it on top of the linked list by replacing deferPtr.
|
||||||
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
|
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
|
||||||
c.builder.CreateStore(allocaCast, frame.deferPtr)
|
c.builder.CreateStore(allocaCast, frame.deferPtr)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitRunDefers emits code to run all deferred functions.
|
// emitRunDefers emits code to run all deferred functions.
|
||||||
func (c *Compiler) emitRunDefers(frame *Frame) error {
|
func (c *Compiler) emitRunDefers(frame *Frame) {
|
||||||
// Add a loop like the following:
|
// Add a loop like the following:
|
||||||
// for stack != nil {
|
// for stack != nil {
|
||||||
// _stack := stack
|
// _stack := stack
|
||||||
@@ -190,13 +172,13 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
|||||||
// stack = stack.next
|
// stack = stack.next
|
||||||
// switch stack.callback {
|
// switch stack.callback {
|
||||||
c.builder.SetInsertPointAtEnd(loop)
|
c.builder.SetInsertPointAtEnd(loop)
|
||||||
nextStackGEP := c.builder.CreateGEP(deferData, []llvm.Value{
|
nextStackGEP := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 1, false), // .next field
|
llvm.ConstInt(c.ctx.Int32Type(), 1, false), // .next field
|
||||||
}, "stack.next.gep")
|
}, "stack.next.gep")
|
||||||
nextStack := c.builder.CreateLoad(nextStackGEP, "stack.next")
|
nextStack := c.builder.CreateLoad(nextStackGEP, "stack.next")
|
||||||
c.builder.CreateStore(nextStack, frame.deferPtr)
|
c.builder.CreateStore(nextStack, frame.deferPtr)
|
||||||
gep := c.builder.CreateGEP(deferData, []llvm.Value{
|
gep := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false), // .callback field
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false), // .callback field
|
||||||
}, "callback.gep")
|
}, "callback.gep")
|
||||||
@@ -220,11 +202,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
|||||||
// Get the real defer struct type and cast to it.
|
// Get the real defer struct type and cast to it.
|
||||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0), c.i8ptrType}
|
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0), c.i8ptrType}
|
||||||
for _, arg := range callback.Args {
|
for _, arg := range callback.Args {
|
||||||
llvmType, err := c.getLLVMType(arg.Type())
|
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
valueTypes = append(valueTypes, llvmType)
|
|
||||||
}
|
}
|
||||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||||
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||||
@@ -233,7 +211,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
|||||||
forwardParams := []llvm.Value{}
|
forwardParams := []llvm.Value{}
|
||||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||||
for i := 2; i < len(valueTypes); i++ {
|
for i := 2; i < len(valueTypes); i++ {
|
||||||
gep := c.builder.CreateGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "gep")
|
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "gep")
|
||||||
forwardParam := c.builder.CreateLoad(gep, "param")
|
forwardParam := c.builder.CreateLoad(gep, "param")
|
||||||
forwardParams = append(forwardParams, forwardParam)
|
forwardParams = append(forwardParams, forwardParam)
|
||||||
}
|
}
|
||||||
@@ -246,10 +224,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
|||||||
// Parent coroutine handle.
|
// Parent coroutine handle.
|
||||||
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||||
|
|
||||||
fnPtr, _, err := c.getInvokeCall(frame, callback)
|
fnPtr, _ := c.getInvokeCall(frame, callback)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.createCall(fnPtr, forwardParams, "")
|
c.createCall(fnPtr, forwardParams, "")
|
||||||
|
|
||||||
case *ir.Function:
|
case *ir.Function:
|
||||||
@@ -258,11 +233,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
|||||||
// Get the real defer struct type and cast to it.
|
// Get the real defer struct type and cast to it.
|
||||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
|
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
|
||||||
for _, param := range callback.Params {
|
for _, param := range callback.Params {
|
||||||
llvmType, err := c.getLLVMType(param.Type())
|
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
valueTypes = append(valueTypes, llvmType)
|
|
||||||
}
|
}
|
||||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||||
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||||
@@ -271,7 +242,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
|||||||
forwardParams := []llvm.Value{}
|
forwardParams := []llvm.Value{}
|
||||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||||
for i := range callback.Params {
|
for i := range callback.Params {
|
||||||
gep := c.builder.CreateGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
||||||
forwardParam := c.builder.CreateLoad(gep, "param")
|
forwardParam := c.builder.CreateLoad(gep, "param")
|
||||||
forwardParams = append(forwardParams, forwardParam)
|
forwardParams = append(forwardParams, forwardParam)
|
||||||
}
|
}
|
||||||
@@ -292,11 +263,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
|||||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
|
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
|
||||||
params := fn.Signature.Params()
|
params := fn.Signature.Params()
|
||||||
for i := 0; i < params.Len(); i++ {
|
for i := 0; i < params.Len(); i++ {
|
||||||
llvmType, err := c.getLLVMType(params.At(i).Type())
|
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
valueTypes = append(valueTypes, llvmType)
|
|
||||||
}
|
}
|
||||||
valueTypes = append(valueTypes, c.i8ptrType) // closure
|
valueTypes = append(valueTypes, c.i8ptrType) // closure
|
||||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||||
@@ -306,7 +273,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
|||||||
forwardParams := []llvm.Value{}
|
forwardParams := []llvm.Value{}
|
||||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||||
for i := 2; i < len(valueTypes); i++ {
|
for i := 2; i < len(valueTypes); i++ {
|
||||||
gep := c.builder.CreateGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "")
|
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "")
|
||||||
forwardParam := c.builder.CreateLoad(gep, "param")
|
forwardParam := c.builder.CreateLoad(gep, "param")
|
||||||
forwardParams = append(forwardParams, forwardParam)
|
forwardParams = append(forwardParams, forwardParam)
|
||||||
}
|
}
|
||||||
@@ -334,5 +301,4 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
|||||||
|
|
||||||
// End of loop.
|
// End of loop.
|
||||||
c.builder.SetInsertPointAtEnd(end)
|
c.builder.SetInsertPointAtEnd(end)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,3 +12,7 @@ func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
|
|||||||
Msg: msg,
|
Msg: msg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) addError(pos token.Pos, msg string) {
|
||||||
|
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
|
||||||
|
}
|
||||||
|
|||||||
+22
-85
@@ -41,7 +41,7 @@ func (c *Compiler) funcImplementation() funcValueImplementation {
|
|||||||
|
|
||||||
// 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 (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) (llvm.Value, error) {
|
func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
|
||||||
var funcValueScalar llvm.Value
|
var funcValueScalar llvm.Value
|
||||||
switch c.funcImplementation() {
|
switch c.funcImplementation() {
|
||||||
case funcValueDoubleword:
|
case funcValueDoubleword:
|
||||||
@@ -66,14 +66,11 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
|
|||||||
default:
|
default:
|
||||||
panic("unimplemented func value variant")
|
panic("unimplemented func value variant")
|
||||||
}
|
}
|
||||||
funcValueType, err := c.getFuncType(sig)
|
funcValueType := c.getFuncType(sig)
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
funcValue := llvm.Undef(funcValueType)
|
funcValue := llvm.Undef(funcValueType)
|
||||||
funcValue = c.builder.CreateInsertValue(funcValue, context, 0, "")
|
funcValue = c.builder.CreateInsertValue(funcValue, context, 0, "")
|
||||||
funcValue = c.builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
|
funcValue = c.builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
|
||||||
return funcValue, nil
|
return funcValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// getFuncSignature returns a global for identification of a particular function
|
// getFuncSignature returns a global for identification of a particular function
|
||||||
@@ -112,10 +109,7 @@ func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (
|
|||||||
case funcValueDoubleword:
|
case funcValueDoubleword:
|
||||||
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
|
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
|
||||||
case funcValueSwitch:
|
case funcValueSwitch:
|
||||||
llvmSig, err := c.getRawFuncType(sig)
|
llvmSig := c.getRawFuncType(sig)
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, llvm.Value{}, err
|
|
||||||
}
|
|
||||||
sigGlobal := c.getFuncSignature(sig)
|
sigGlobal := c.getFuncSignature(sig)
|
||||||
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
|
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
|
||||||
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
|
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
|
||||||
@@ -126,25 +120,21 @@ func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getFuncType returns the type of a func value given a signature.
|
// getFuncType returns the type of a func value given a signature.
|
||||||
func (c *Compiler) getFuncType(typ *types.Signature) (llvm.Type, error) {
|
func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
|
||||||
switch c.funcImplementation() {
|
switch c.funcImplementation() {
|
||||||
case funcValueDoubleword:
|
case funcValueDoubleword:
|
||||||
rawPtr, err := c.getRawFuncType(typ)
|
rawPtr := c.getRawFuncType(typ)
|
||||||
if err != nil {
|
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
|
||||||
return llvm.Type{}, err
|
|
||||||
}
|
|
||||||
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false), nil
|
|
||||||
case funcValueSwitch:
|
case funcValueSwitch:
|
||||||
return c.mod.GetTypeByName("runtime.funcValue"), nil
|
return c.mod.GetTypeByName("runtime.funcValue")
|
||||||
default:
|
default:
|
||||||
panic("unimplemented func value variant")
|
panic("unimplemented func value variant")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRawFuncType returns a LLVM function pointer type for a given signature.
|
// getRawFuncType returns a LLVM function pointer type for a given signature.
|
||||||
func (c *Compiler) getRawFuncType(typ *types.Signature) (llvm.Type, error) {
|
func (c *Compiler) getRawFuncType(typ *types.Signature) llvm.Type {
|
||||||
// Get the return type.
|
// Get the return type.
|
||||||
var err error
|
|
||||||
var returnType llvm.Type
|
var returnType llvm.Type
|
||||||
switch typ.Results().Len() {
|
switch typ.Results().Len() {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -152,21 +142,14 @@ func (c *Compiler) getRawFuncType(typ *types.Signature) (llvm.Type, error) {
|
|||||||
returnType = c.ctx.VoidType()
|
returnType = c.ctx.VoidType()
|
||||||
case 1:
|
case 1:
|
||||||
// Just one return value.
|
// Just one return value.
|
||||||
returnType, err = c.getLLVMType(typ.Results().At(0).Type())
|
returnType = c.getLLVMType(typ.Results().At(0).Type())
|
||||||
if err != nil {
|
|
||||||
return llvm.Type{}, err
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
// Multiple return values. Put them together in a struct.
|
// Multiple return values. Put them together in a struct.
|
||||||
// This appears to be the common way to handle multiple return values in
|
// This appears to be the common way to handle multiple return values in
|
||||||
// LLVM.
|
// LLVM.
|
||||||
members := make([]llvm.Type, typ.Results().Len())
|
members := make([]llvm.Type, typ.Results().Len())
|
||||||
for i := 0; i < typ.Results().Len(); i++ {
|
for i := 0; i < typ.Results().Len(); i++ {
|
||||||
returnType, err := c.getLLVMType(typ.Results().At(i).Type())
|
members[i] = c.getLLVMType(typ.Results().At(i).Type())
|
||||||
if err != nil {
|
|
||||||
return llvm.Type{}, err
|
|
||||||
}
|
|
||||||
members[i] = returnType
|
|
||||||
}
|
}
|
||||||
returnType = c.ctx.StructType(members, false)
|
returnType = c.ctx.StructType(members, false)
|
||||||
}
|
}
|
||||||
@@ -174,10 +157,7 @@ func (c *Compiler) getRawFuncType(typ *types.Signature) (llvm.Type, error) {
|
|||||||
// Get the parameter types.
|
// Get the parameter types.
|
||||||
var paramTypes []llvm.Type
|
var paramTypes []llvm.Type
|
||||||
if typ.Recv() != nil {
|
if typ.Recv() != nil {
|
||||||
recv, err := c.getLLVMType(typ.Recv().Type())
|
recv := c.getLLVMType(typ.Recv().Type())
|
||||||
if err != nil {
|
|
||||||
return llvm.Type{}, err
|
|
||||||
}
|
|
||||||
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.
|
||||||
@@ -186,10 +166,7 @@ func (c *Compiler) getRawFuncType(typ *types.Signature) (llvm.Type, error) {
|
|||||||
paramTypes = append(paramTypes, c.expandFormalParamType(recv)...)
|
paramTypes = append(paramTypes, c.expandFormalParamType(recv)...)
|
||||||
}
|
}
|
||||||
for i := 0; i < typ.Params().Len(); i++ {
|
for i := 0; i < typ.Params().Len(); i++ {
|
||||||
subType, err := c.getLLVMType(typ.Params().At(i).Type())
|
subType := c.getLLVMType(typ.Params().At(i).Type())
|
||||||
if err != nil {
|
|
||||||
return llvm.Type{}, err
|
|
||||||
}
|
|
||||||
paramTypes = append(paramTypes, c.expandFormalParamType(subType)...)
|
paramTypes = append(paramTypes, c.expandFormalParamType(subType)...)
|
||||||
}
|
}
|
||||||
// All functions take these parameters at the end.
|
// All functions take these parameters at the end.
|
||||||
@@ -197,7 +174,7 @@ func (c *Compiler) getRawFuncType(typ *types.Signature) (llvm.Type, error) {
|
|||||||
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine
|
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine
|
||||||
|
|
||||||
// Make a func type out of the signature.
|
// Make a func type out of the signature.
|
||||||
return llvm.PointerType(llvm.FunctionType(returnType, paramTypes, false), c.funcPtrAddrSpace), nil
|
return llvm.PointerType(llvm.FunctionType(returnType, paramTypes, false), c.funcPtrAddrSpace)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseMakeClosure makes a function value (with context) from the given
|
// parseMakeClosure makes a function value (with context) from the given
|
||||||
@@ -209,57 +186,17 @@ func (c *Compiler) parseMakeClosure(frame *Frame, expr *ssa.MakeClosure) (llvm.V
|
|||||||
f := c.ir.GetFunction(expr.Fn.(*ssa.Function))
|
f := c.ir.GetFunction(expr.Fn.(*ssa.Function))
|
||||||
|
|
||||||
// Collect all bound variables.
|
// Collect all bound variables.
|
||||||
boundVars := make([]llvm.Value, 0, len(expr.Bindings))
|
boundVars := make([]llvm.Value, len(expr.Bindings))
|
||||||
boundVarTypes := make([]llvm.Type, 0, len(expr.Bindings))
|
for i, binding := range expr.Bindings {
|
||||||
for _, binding := range expr.Bindings {
|
|
||||||
// The context stores the bound variables.
|
// The context stores the bound variables.
|
||||||
llvmBoundVar, err := c.parseExpr(frame, binding)
|
llvmBoundVar := c.getValue(frame, binding)
|
||||||
if err != nil {
|
boundVars[i] = llvmBoundVar
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
boundVars = append(boundVars, llvmBoundVar)
|
|
||||||
boundVarTypes = append(boundVarTypes, llvmBoundVar.Type())
|
|
||||||
}
|
|
||||||
contextType := c.ctx.StructType(boundVarTypes, false)
|
|
||||||
|
|
||||||
// Allocate memory for the context.
|
|
||||||
contextAlloc := llvm.Value{}
|
|
||||||
contextHeapAlloc := llvm.Value{}
|
|
||||||
if c.targetData.TypeAllocSize(contextType) <= c.targetData.TypeAllocSize(c.i8ptrType) {
|
|
||||||
// Context fits in a pointer - e.g. when it is a pointer. Store it
|
|
||||||
// directly in the stack after a convert.
|
|
||||||
// Because contextType is a struct and we have to cast it to a *i8,
|
|
||||||
// store it in an alloca first for bitcasting (store+bitcast+load).
|
|
||||||
contextAlloc = c.builder.CreateAlloca(contextType, "")
|
|
||||||
} else {
|
|
||||||
// Context is bigger than a pointer, so allocate it on the heap.
|
|
||||||
size := c.targetData.TypeAllocSize(contextType)
|
|
||||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
|
||||||
contextHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
|
|
||||||
contextAlloc = c.builder.CreateBitCast(contextHeapAlloc, llvm.PointerType(contextType, 0), "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store all bound variables in the alloca or heap pointer.
|
// Store the bound variables in a single object, allocating it on the heap
|
||||||
for i, boundVar := range boundVars {
|
// if necessary.
|
||||||
indices := []llvm.Value{
|
context := c.emitPointerPack(boundVars)
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
|
||||||
}
|
|
||||||
gep := c.builder.CreateInBoundsGEP(contextAlloc, indices, "")
|
|
||||||
c.builder.CreateStore(boundVar, gep)
|
|
||||||
}
|
|
||||||
|
|
||||||
context := llvm.Value{}
|
|
||||||
if c.targetData.TypeAllocSize(contextType) <= c.targetData.TypeAllocSize(c.i8ptrType) {
|
|
||||||
// Load value (as *i8) from the alloca.
|
|
||||||
contextAlloc = c.builder.CreateBitCast(contextAlloc, llvm.PointerType(c.i8ptrType, 0), "")
|
|
||||||
context = c.builder.CreateLoad(contextAlloc, "")
|
|
||||||
} else {
|
|
||||||
// Get the original heap allocation pointer, which already is an
|
|
||||||
// *i8.
|
|
||||||
context = contextHeapAlloc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the closure.
|
// Create the closure.
|
||||||
return c.createFuncValue(f.LLVMFn, context, f.Signature)
|
return c.createFuncValue(f.LLVMFn, context, f.Signature), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package compiler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Compiler) addGlobalsBitmap() {
|
||||||
|
if c.mod.NamedGlobal("runtime.trackedGlobalsStart").IsNil() {
|
||||||
|
return // nothing to do: no GC in use
|
||||||
|
}
|
||||||
|
|
||||||
|
var trackedGlobals []llvm.Value
|
||||||
|
var trackedGlobalTypes []llvm.Type
|
||||||
|
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||||
|
if global.IsDeclaration() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
typ := global.Type().ElementType()
|
||||||
|
ptrs := c.getPointerBitmap(typ, global.Name())
|
||||||
|
if ptrs.BitLen() == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
trackedGlobals = append(trackedGlobals, global)
|
||||||
|
trackedGlobalTypes = append(trackedGlobalTypes, typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
globalsBundleType := c.ctx.StructType(trackedGlobalTypes, false)
|
||||||
|
globalsBundle := llvm.AddGlobal(c.mod, globalsBundleType, "tinygo.trackedGlobals")
|
||||||
|
globalsBundle.SetLinkage(llvm.InternalLinkage)
|
||||||
|
globalsBundle.SetUnnamedAddr(true)
|
||||||
|
initializer := llvm.Undef(globalsBundleType)
|
||||||
|
for i, global := range trackedGlobals {
|
||||||
|
initializer = llvm.ConstInsertValue(initializer, global.Initializer(), []uint32{uint32(i)})
|
||||||
|
gep := llvm.ConstGEP(globalsBundle, []llvm.Value{
|
||||||
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||||
|
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
||||||
|
})
|
||||||
|
global.ReplaceAllUsesWith(gep)
|
||||||
|
global.EraseFromParentAsGlobal()
|
||||||
|
}
|
||||||
|
globalsBundle.SetInitializer(initializer)
|
||||||
|
|
||||||
|
trackedGlobalsStart := llvm.ConstPtrToInt(globalsBundle, c.uintptrType)
|
||||||
|
c.mod.NamedGlobal("runtime.trackedGlobalsStart").SetInitializer(trackedGlobalsStart)
|
||||||
|
|
||||||
|
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
|
||||||
|
trackedGlobalsLength := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(globalsBundleType)/uint64(alignment), false)
|
||||||
|
c.mod.NamedGlobal("runtime.trackedGlobalsLength").SetInitializer(trackedGlobalsLength)
|
||||||
|
|
||||||
|
bitmapBytes := c.getPointerBitmap(globalsBundleType, "globals bundle").Bytes()
|
||||||
|
bitmapValues := make([]llvm.Value, len(bitmapBytes))
|
||||||
|
for i, b := range bitmapBytes {
|
||||||
|
bitmapValues[len(bitmapBytes)-i-1] = llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false)
|
||||||
|
}
|
||||||
|
bitmapArray := llvm.ConstArray(llvm.ArrayType(c.ctx.Int8Type(), len(bitmapBytes)), bitmapValues)
|
||||||
|
bitmapNew := llvm.AddGlobal(c.mod, bitmapArray.Type(), "runtime.trackedGlobalsBitmap.tmp")
|
||||||
|
bitmapOld := c.mod.NamedGlobal("runtime.trackedGlobalsBitmap")
|
||||||
|
bitmapOld.ReplaceAllUsesWith(bitmapNew)
|
||||||
|
bitmapNew.SetInitializer(bitmapArray)
|
||||||
|
bitmapNew.SetName("runtime.trackedGlobalsBitmap")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) getPointerBitmap(typ llvm.Type, name string) *big.Int {
|
||||||
|
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
|
||||||
|
switch typ.TypeKind() {
|
||||||
|
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
|
||||||
|
return big.NewInt(0)
|
||||||
|
case llvm.PointerTypeKind:
|
||||||
|
return big.NewInt(1)
|
||||||
|
case llvm.StructTypeKind:
|
||||||
|
ptrs := big.NewInt(0)
|
||||||
|
for i, subtyp := range typ.StructElementTypes() {
|
||||||
|
subptrs := c.getPointerBitmap(subtyp, name)
|
||||||
|
if subptrs.BitLen() == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
offset := c.targetData.ElementOffset(typ, i)
|
||||||
|
if offset%uint64(alignment) != 0 {
|
||||||
|
panic("precise GC: global contains unaligned pointer: " + name)
|
||||||
|
}
|
||||||
|
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
|
||||||
|
ptrs.Or(ptrs, subptrs)
|
||||||
|
}
|
||||||
|
return ptrs
|
||||||
|
case llvm.ArrayTypeKind:
|
||||||
|
subtyp := typ.ElementType()
|
||||||
|
subptrs := c.getPointerBitmap(subtyp, name)
|
||||||
|
ptrs := big.NewInt(0)
|
||||||
|
if subptrs.BitLen() == 0 {
|
||||||
|
return ptrs
|
||||||
|
}
|
||||||
|
elementSize := c.targetData.TypeAllocSize(subtyp)
|
||||||
|
for i := 0; i < typ.ArrayLength(); i++ {
|
||||||
|
ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
|
||||||
|
ptrs.Or(ptrs, subptrs)
|
||||||
|
}
|
||||||
|
return ptrs
|
||||||
|
default:
|
||||||
|
panic("unknown type kind of global: " + name)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,8 +10,8 @@ package compiler
|
|||||||
// go foo()
|
// go foo()
|
||||||
// time.Sleep(2 * time.Second)
|
// time.Sleep(2 * time.Second)
|
||||||
// println("some other operation")
|
// println("some other operation")
|
||||||
// bar()
|
// i := bar()
|
||||||
// println("done")
|
// println("done", *i)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// func foo() {
|
// func foo() {
|
||||||
@@ -21,9 +21,10 @@ package compiler
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// func bar() {
|
// func bar() *int {
|
||||||
// time.Sleep(time.Second)
|
// time.Sleep(time.Second)
|
||||||
// println("blocking operation completed)
|
// println("blocking operation completed)
|
||||||
|
// return new(int)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// It is transformed by the IR generator in compiler.go into the following
|
// It is transformed by the IR generator in compiler.go into the following
|
||||||
@@ -34,8 +35,8 @@ package compiler
|
|||||||
// fn()
|
// fn()
|
||||||
// time.Sleep(2 * time.Second)
|
// time.Sleep(2 * time.Second)
|
||||||
// println("some other operation")
|
// println("some other operation")
|
||||||
// bar() // imagine an 'await' keyword in front of this call
|
// i := bar() // imagine an 'await' keyword in front of this call
|
||||||
// println("done")
|
// println("done", *i)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// func foo() {
|
// func foo() {
|
||||||
@@ -45,9 +46,10 @@ package compiler
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// func bar() {
|
// func bar() *int {
|
||||||
// time.Sleep(time.Second)
|
// time.Sleep(time.Second)
|
||||||
// println("blocking operation completed)
|
// println("blocking operation completed)
|
||||||
|
// return new(int)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// The pass in this file transforms this code even further, to the following
|
// The pass in this file transforms this code even further, to the following
|
||||||
@@ -59,9 +61,11 @@ package compiler
|
|||||||
// runtime.sleepTask(hdl, 2 * time.Second) // ask the scheduler to re-activate this coroutine at the right time
|
// runtime.sleepTask(hdl, 2 * time.Second) // ask the scheduler to re-activate this coroutine at the right time
|
||||||
// llvm.suspend(hdl) // suspend point
|
// llvm.suspend(hdl) // suspend point
|
||||||
// println("some other operation")
|
// println("some other operation")
|
||||||
|
// var i *int // allocate space on the stack for the return value
|
||||||
|
// runtime.setTaskPromisePtr(hdl, &i) // store return value alloca in our coroutine promise
|
||||||
// bar(hdl) // await, pass a continuation (hdl) to bar
|
// bar(hdl) // await, pass a continuation (hdl) to bar
|
||||||
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
|
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
|
||||||
// println("done")
|
// println("done", *i)
|
||||||
// runtime.activateTask(parent) // re-activate the parent (nop, there is no parent)
|
// runtime.activateTask(parent) // re-activate the parent (nop, there is no parent)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
@@ -142,10 +146,9 @@ func (c *Compiler) LowerGoroutines() error {
|
|||||||
realMain.SetLinkage(llvm.InternalLinkage)
|
realMain.SetLinkage(llvm.InternalLinkage)
|
||||||
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage)
|
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage)
|
||||||
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.InternalLinkage)
|
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.InternalLinkage)
|
||||||
c.mod.NamedFunction("runtime.chanSend").SetLinkage(llvm.InternalLinkage)
|
|
||||||
c.mod.NamedFunction("runtime.chanRecv").SetLinkage(llvm.InternalLinkage)
|
|
||||||
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.InternalLinkage)
|
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.InternalLinkage)
|
||||||
c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.InternalLinkage)
|
c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
|
||||||
|
c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
|
||||||
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
|
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -174,13 +177,13 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
if !deadlockStub.IsNil() {
|
if !deadlockStub.IsNil() {
|
||||||
worklist = append(worklist, deadlockStub)
|
worklist = append(worklist, deadlockStub)
|
||||||
}
|
}
|
||||||
chanSendStub := c.mod.NamedFunction("runtime.chanSendStub")
|
chanSend := c.mod.NamedFunction("runtime.chanSend")
|
||||||
if !chanSendStub.IsNil() {
|
if !chanSend.IsNil() {
|
||||||
worklist = append(worklist, chanSendStub)
|
worklist = append(worklist, chanSend)
|
||||||
}
|
}
|
||||||
chanRecvStub := c.mod.NamedFunction("runtime.chanRecvStub")
|
chanRecv := c.mod.NamedFunction("runtime.chanRecv")
|
||||||
if !chanRecvStub.IsNil() {
|
if !chanRecv.IsNil() {
|
||||||
worklist = append(worklist, chanRecvStub)
|
worklist = append(worklist, chanRecv)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(worklist) == 0 {
|
if len(worklist) == 0 {
|
||||||
@@ -278,9 +281,6 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
coroBeginType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
|
coroBeginType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
|
||||||
coroBeginFunc := llvm.AddFunction(c.mod, "llvm.coro.begin", coroBeginType)
|
coroBeginFunc := llvm.AddFunction(c.mod, "llvm.coro.begin", coroBeginType)
|
||||||
|
|
||||||
coroPromiseType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.i8ptrType, c.ctx.Int32Type(), c.ctx.Int1Type()}, false)
|
|
||||||
coroPromiseFunc := llvm.AddFunction(c.mod, "llvm.coro.promise", coroPromiseType)
|
|
||||||
|
|
||||||
coroSuspendType := llvm.FunctionType(c.ctx.Int8Type(), []llvm.Type{c.ctx.TokenType(), c.ctx.Int1Type()}, false)
|
coroSuspendType := llvm.FunctionType(c.ctx.Int8Type(), []llvm.Type{c.ctx.TokenType(), c.ctx.Int1Type()}, false)
|
||||||
coroSuspendFunc := llvm.AddFunction(c.mod, "llvm.coro.suspend", coroSuspendType)
|
coroSuspendFunc := llvm.AddFunction(c.mod, "llvm.coro.suspend", coroSuspendType)
|
||||||
|
|
||||||
@@ -292,7 +292,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
|
|
||||||
// Transform all async functions into coroutines.
|
// Transform all async functions into coroutines.
|
||||||
for _, f := range asyncList {
|
for _, f := range asyncList {
|
||||||
if f == sleep || f == deadlockStub || f == chanSendStub || f == chanRecvStub {
|
if f == sleep || f == deadlockStub || f == chanSend || f == chanRecv {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +309,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||||
if !inst.IsACallInst().IsNil() {
|
if !inst.IsACallInst().IsNil() {
|
||||||
callee := inst.CalledValue()
|
callee := inst.CalledValue()
|
||||||
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlockStub || callee == chanSendStub || callee == chanRecvStub {
|
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlockStub || callee == chanSend || callee == chanRecv {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
asyncCalls = append(asyncCalls, inst)
|
asyncCalls = append(asyncCalls, inst)
|
||||||
@@ -347,10 +347,18 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
// Split this basic block.
|
// Split this basic block.
|
||||||
await := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.await")
|
await := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.await")
|
||||||
|
|
||||||
// Set task state to TASK_STATE_CALL.
|
// Allocate space for the return value.
|
||||||
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
|
var retvalAlloca llvm.Value
|
||||||
|
if inst.Type().TypeKind() != llvm.VoidTypeKind {
|
||||||
|
c.builder.SetInsertPointBefore(inst.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
||||||
|
retvalAlloca = c.builder.CreateAlloca(inst.Type(), "coro.retvalAlloca")
|
||||||
|
c.builder.SetInsertPointBefore(inst)
|
||||||
|
data := c.builder.CreateBitCast(retvalAlloca, c.i8ptrType, "")
|
||||||
|
c.createRuntimeCall("setTaskPromisePtr", []llvm.Value{frame.taskHandle, data}, "")
|
||||||
|
}
|
||||||
|
|
||||||
// Suspend.
|
// Suspend.
|
||||||
|
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
|
||||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||||
llvm.ConstNull(c.ctx.TokenType()),
|
llvm.ConstNull(c.ctx.TokenType()),
|
||||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||||
@@ -358,27 +366,49 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), await)
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), await)
|
||||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||||
|
|
||||||
|
if inst.Type().TypeKind() != llvm.VoidTypeKind {
|
||||||
|
// Load the return value from the alloca. The callee has
|
||||||
|
// written the return value to it.
|
||||||
|
c.builder.SetInsertPointBefore(await.FirstInstruction())
|
||||||
|
retval := c.builder.CreateLoad(retvalAlloca, "coro.retval")
|
||||||
|
inst.ReplaceAllUsesWith(retval)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace return instructions with suspend points that should
|
// Replace return instructions with suspend points that should
|
||||||
// reactivate the parent coroutine.
|
// reactivate the parent coroutine.
|
||||||
for _, inst := range returns {
|
for _, inst := range returns {
|
||||||
if inst.OperandsCount() == 0 {
|
// These properties were added by the functionattrs pass. Remove
|
||||||
// These properties were added by the functionattrs pass.
|
// them, because now we start using the parameter.
|
||||||
// Remove them, because now we start using the parameter.
|
|
||||||
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
|
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
|
||||||
for _, kind := range []string{"nocapture", "readnone"} {
|
for _, kind := range []string{"nocapture", "readnone"} {
|
||||||
kindID := llvm.AttributeKindID(kind)
|
kindID := llvm.AttributeKindID(kind)
|
||||||
f.RemoveEnumAttributeAtIndex(f.ParamsCount(), kindID)
|
f.RemoveEnumAttributeAtIndex(f.ParamsCount(), kindID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reactivate the parent coroutine. This adds it back to
|
|
||||||
// the run queue, so it is started again by the
|
|
||||||
// scheduler when possible (possibly right after the
|
|
||||||
// following suspend).
|
|
||||||
c.builder.SetInsertPointBefore(inst)
|
c.builder.SetInsertPointBefore(inst)
|
||||||
|
|
||||||
parentHandle := f.LastParam()
|
parentHandle := f.LastParam()
|
||||||
|
|
||||||
|
// Store return values.
|
||||||
|
switch inst.OperandsCount() {
|
||||||
|
case 0:
|
||||||
|
// Nothing to return.
|
||||||
|
case 1:
|
||||||
|
// Return this value by writing to the pointer stored in the
|
||||||
|
// parent handle. The parent coroutine has made an alloca that
|
||||||
|
// we can write to to store our return value.
|
||||||
|
returnValuePtr := c.createRuntimeCall("getTaskPromisePtr", []llvm.Value{parentHandle}, "coro.parentData")
|
||||||
|
alloca := c.builder.CreateBitCast(returnValuePtr, llvm.PointerType(inst.Operand(0).Type(), 0), "coro.parentAlloca")
|
||||||
|
c.builder.CreateStore(inst.Operand(0), alloca)
|
||||||
|
default:
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reactivate the parent coroutine. This adds it back to the run
|
||||||
|
// queue, so it is started again by the scheduler when possible
|
||||||
|
// (possibly right after the following suspend).
|
||||||
c.createRuntimeCall("activateTask", []llvm.Value{parentHandle}, "")
|
c.createRuntimeCall("activateTask", []llvm.Value{parentHandle}, "")
|
||||||
|
|
||||||
// Suspend this coroutine.
|
// Suspend this coroutine.
|
||||||
@@ -393,9 +423,6 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
|
||||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||||
inst.EraseFromParentAsInstruction()
|
inst.EraseFromParentAsInstruction()
|
||||||
} else {
|
|
||||||
panic("todo: return value from coroutine")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Coroutine cleanup. Free resources associated with this coroutine.
|
// Coroutine cleanup. Free resources associated with this coroutine.
|
||||||
@@ -420,6 +447,14 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
c.builder.CreateUnreachable()
|
c.builder.CreateUnreachable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace calls to runtime.getCoroutineCall with the coroutine of this
|
||||||
|
// frame.
|
||||||
|
for _, getCoroutineCall := range getUses(c.mod.NamedFunction("runtime.getCoroutine")) {
|
||||||
|
frame := asyncFuncs[getCoroutineCall.InstructionParent().Parent()]
|
||||||
|
getCoroutineCall.ReplaceAllUsesWith(frame.taskHandle)
|
||||||
|
getCoroutineCall.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
|
||||||
// Transform calls to time.Sleep() into coroutine suspend points.
|
// Transform calls to time.Sleep() into coroutine suspend points.
|
||||||
for _, sleepCall := range getUses(sleep) {
|
for _, sleepCall := range getUses(sleep) {
|
||||||
// sleepCall must be a call instruction.
|
// sleepCall must be a call instruction.
|
||||||
@@ -463,37 +498,11 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
deadlockCall.EraseFromParentAsInstruction()
|
deadlockCall.EraseFromParentAsInstruction()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform calls to runtime.chanSendStub into channel send operations.
|
// Transform calls to runtime.chanSend into channel send operations.
|
||||||
for _, sendOp := range getUses(chanSendStub) {
|
for _, sendOp := range getUses(chanSend) {
|
||||||
// sendOp must be a call instruction.
|
// sendOp must be a call instruction.
|
||||||
frame := asyncFuncs[sendOp.InstructionParent().Parent()]
|
frame := asyncFuncs[sendOp.InstructionParent().Parent()]
|
||||||
|
|
||||||
// Send the value over the channel, or block.
|
|
||||||
sendOp.SetOperand(0, frame.taskHandle)
|
|
||||||
sendOp.SetOperand(sendOp.OperandsCount()-1, c.mod.NamedFunction("runtime.chanSend"))
|
|
||||||
|
|
||||||
// Use taskState.data to store the value to send:
|
|
||||||
// *(*valueType)(&coroutine.promise().data) = valueToSend
|
|
||||||
// runtime.chanSend(coroutine, ch)
|
|
||||||
bitcast := sendOp.Operand(2)
|
|
||||||
valueAlloca := bitcast.Operand(0)
|
|
||||||
c.builder.SetInsertPointBefore(valueAlloca)
|
|
||||||
promiseType := c.mod.GetTypeByName("runtime.taskState")
|
|
||||||
promiseRaw := c.builder.CreateCall(coroPromiseFunc, []llvm.Value{
|
|
||||||
frame.taskHandle,
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(c.targetData.PrefTypeAlignment(promiseType)), false),
|
|
||||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
|
||||||
}, "task.promise.raw")
|
|
||||||
promise := c.builder.CreateBitCast(promiseRaw, llvm.PointerType(promiseType, 0), "task.promise")
|
|
||||||
dataPtr := c.builder.CreateGEP(promise, []llvm.Value{
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 2, false),
|
|
||||||
}, "task.promise.data")
|
|
||||||
sendOp.SetOperand(2, llvm.Undef(c.i8ptrType))
|
|
||||||
valueAlloca.ReplaceAllUsesWith(c.builder.CreateBitCast(dataPtr, valueAlloca.Type(), ""))
|
|
||||||
bitcast.EraseFromParentAsInstruction()
|
|
||||||
valueAlloca.EraseFromParentAsInstruction()
|
|
||||||
|
|
||||||
// Yield to scheduler.
|
// Yield to scheduler.
|
||||||
c.builder.SetInsertPointBefore(llvm.NextInstruction(sendOp))
|
c.builder.SetInsertPointBefore(llvm.NextInstruction(sendOp))
|
||||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||||
@@ -506,21 +515,11 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform calls to runtime.chanRecvStub into channel receive operations.
|
// Transform calls to runtime.chanRecv into channel receive operations.
|
||||||
for _, recvOp := range getUses(chanRecvStub) {
|
for _, recvOp := range getUses(chanRecv) {
|
||||||
// recvOp must be a call instruction.
|
// recvOp must be a call instruction.
|
||||||
frame := asyncFuncs[recvOp.InstructionParent().Parent()]
|
frame := asyncFuncs[recvOp.InstructionParent().Parent()]
|
||||||
|
|
||||||
bitcast := recvOp.Operand(2)
|
|
||||||
commaOk := recvOp.Operand(3)
|
|
||||||
valueAlloca := bitcast.Operand(0)
|
|
||||||
|
|
||||||
// Receive the value over the channel, or block.
|
|
||||||
recvOp.SetOperand(0, frame.taskHandle)
|
|
||||||
recvOp.SetOperand(recvOp.OperandsCount()-1, c.mod.NamedFunction("runtime.chanRecv"))
|
|
||||||
recvOp.SetOperand(2, llvm.Undef(c.i8ptrType))
|
|
||||||
bitcast.EraseFromParentAsInstruction()
|
|
||||||
|
|
||||||
// Yield to scheduler.
|
// Yield to scheduler.
|
||||||
c.builder.SetInsertPointBefore(llvm.NextInstruction(recvOp))
|
c.builder.SetInsertPointBefore(llvm.NextInstruction(recvOp))
|
||||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||||
@@ -532,32 +531,6 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
c.builder.SetInsertPointAtEnd(recvOp.InstructionParent())
|
c.builder.SetInsertPointAtEnd(recvOp.InstructionParent())
|
||||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
||||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||||
|
|
||||||
// The value to receive is stored in taskState.data:
|
|
||||||
// runtime.chanRecv(coroutine, ch)
|
|
||||||
// promise := coroutine.promise()
|
|
||||||
// valueReceived := *(*valueType)(&promise.data)
|
|
||||||
// ok := promise.commaOk
|
|
||||||
c.builder.SetInsertPointBefore(wakeup.FirstInstruction())
|
|
||||||
promiseType := c.mod.GetTypeByName("runtime.taskState")
|
|
||||||
promiseRaw := c.builder.CreateCall(coroPromiseFunc, []llvm.Value{
|
|
||||||
frame.taskHandle,
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(c.targetData.PrefTypeAlignment(promiseType)), false),
|
|
||||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
|
||||||
}, "task.promise.raw")
|
|
||||||
promise := c.builder.CreateBitCast(promiseRaw, llvm.PointerType(promiseType, 0), "task.promise")
|
|
||||||
dataPtr := c.builder.CreateGEP(promise, []llvm.Value{
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 2, false),
|
|
||||||
}, "task.promise.data")
|
|
||||||
valueAlloca.ReplaceAllUsesWith(c.builder.CreateBitCast(dataPtr, valueAlloca.Type(), ""))
|
|
||||||
valueAlloca.EraseFromParentAsInstruction()
|
|
||||||
commaOkPtr := c.builder.CreateGEP(promise, []llvm.Value{
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 1, false),
|
|
||||||
}, "task.promise.comma-ok")
|
|
||||||
commaOk.ReplaceAllUsesWith(commaOkPtr)
|
|
||||||
recvOp.SetOperand(3, llvm.Undef(commaOk.Type()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, c.lowerMakeGoroutineCalls()
|
return true, c.lowerMakeGoroutineCalls()
|
||||||
|
|||||||
@@ -68,11 +68,7 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
|
|||||||
}
|
}
|
||||||
key := constant.StringVal(r.Key.(*ssa.Const).Value)
|
key := constant.StringVal(r.Key.(*ssa.Const).Value)
|
||||||
//println("value:", r.Value.(*ssa.MakeInterface).X.String())
|
//println("value:", r.Value.(*ssa.MakeInterface).X.String())
|
||||||
value, err := c.parseExpr(frame, r.Value.(*ssa.MakeInterface).X)
|
registers[key] = c.getValue(frame, r.Value.(*ssa.MakeInterface).X)
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
registers[key] = value
|
|
||||||
case *ssa.Call:
|
case *ssa.Call:
|
||||||
if r.Common() == instr {
|
if r.Common() == instr {
|
||||||
break
|
break
|
||||||
@@ -145,10 +141,7 @@ func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error
|
|||||||
} else {
|
} else {
|
||||||
constraints += ",{r" + strconv.Itoa(i) + "}"
|
constraints += ",{r" + strconv.Itoa(i) + "}"
|
||||||
}
|
}
|
||||||
llvmValue, err := c.parseExpr(frame, arg)
|
llvmValue := c.getValue(frame, arg)
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
llvmArgs = append(llvmArgs, llvmValue)
|
llvmArgs = append(llvmArgs, llvmValue)
|
||||||
argTypes = append(argTypes, llvmValue.Type())
|
argTypes = append(argTypes, llvmValue.Type())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -527,11 +527,40 @@ func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInf
|
|||||||
}
|
}
|
||||||
inttoptr := inttoptrs[0]
|
inttoptr := inttoptrs[0]
|
||||||
function := typ.getMethod(signature).function
|
function := typ.getMethod(signature).function
|
||||||
if inttoptr.Type() != function.Type() {
|
if inttoptr.Type() == function.Type() {
|
||||||
p.builder.SetInsertPointBefore(use)
|
// Easy case: the types are the same. Simply replace the inttoptr
|
||||||
function = p.builder.CreateBitCast(function, inttoptr.Type(), "")
|
// result (which is directly called) with the actual function.
|
||||||
}
|
|
||||||
inttoptr.ReplaceAllUsesWith(function)
|
inttoptr.ReplaceAllUsesWith(function)
|
||||||
|
} else {
|
||||||
|
// Harder case: the type is not actually the same. Go through each call
|
||||||
|
// (of which there should be only one), extract the receiver params for
|
||||||
|
// this call and replace the call with a direct call to the target
|
||||||
|
// function.
|
||||||
|
for _, call := range getUses(inttoptr) {
|
||||||
|
if call.IsACallInst().IsNil() || call.CalledValue() != inttoptr {
|
||||||
|
panic("expected the inttoptr to be called as a method, this is not a method call")
|
||||||
|
}
|
||||||
|
operands := make([]llvm.Value, call.OperandsCount()-1)
|
||||||
|
for i := range operands {
|
||||||
|
operands[i] = call.Operand(i)
|
||||||
|
}
|
||||||
|
paramTypes := function.Type().ElementType().ParamTypes()
|
||||||
|
receiverParamTypes := paramTypes[:len(paramTypes)-(len(operands)-1)]
|
||||||
|
methodParamTypes := paramTypes[len(paramTypes)-(len(operands)-1):]
|
||||||
|
for i, methodParamType := range methodParamTypes {
|
||||||
|
if methodParamType != operands[i+1].Type() {
|
||||||
|
panic("expected method call param type and function param type to be the same")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.builder.SetInsertPointBefore(call)
|
||||||
|
receiverParams := p.emitPointerUnpack(operands[0], receiverParamTypes)
|
||||||
|
result := p.builder.CreateCall(function, append(receiverParams, operands[1:]...), "")
|
||||||
|
if result.Type().TypeKind() != llvm.VoidTypeKind {
|
||||||
|
call.ReplaceAllUsesWith(result)
|
||||||
|
}
|
||||||
|
call.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
}
|
||||||
inttoptr.EraseFromParentAsInstruction()
|
inttoptr.EraseFromParentAsInstruction()
|
||||||
use.EraseFromParentAsInstruction()
|
use.EraseFromParentAsInstruction()
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-140
@@ -22,43 +22,10 @@ import (
|
|||||||
// value field.
|
// value field.
|
||||||
//
|
//
|
||||||
// An interface value is a {typecode, value} tuple, or {i16, i8*} to be exact.
|
// An interface value is a {typecode, value} tuple, or {i16, i8*} to be exact.
|
||||||
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) (llvm.Value, error) {
|
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
|
||||||
var itfValue llvm.Value
|
itfValue := c.emitPointerPack([]llvm.Value{val})
|
||||||
size := c.targetData.TypeAllocSize(val.Type())
|
|
||||||
if size > c.targetData.TypeAllocSize(c.i8ptrType) {
|
|
||||||
// Allocate on the heap and put a pointer in the interface.
|
|
||||||
// TODO: escape analysis.
|
|
||||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
|
||||||
alloc := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "makeinterface.alloc")
|
|
||||||
itfValueCast := c.builder.CreateBitCast(alloc, llvm.PointerType(val.Type(), 0), "makeinterface.cast.value")
|
|
||||||
c.builder.CreateStore(val, itfValueCast)
|
|
||||||
itfValue = c.builder.CreateBitCast(itfValueCast, c.i8ptrType, "makeinterface.cast.i8ptr")
|
|
||||||
} else if size == 0 {
|
|
||||||
itfValue = llvm.ConstPointerNull(c.i8ptrType)
|
|
||||||
} else {
|
|
||||||
// Directly place the value in the interface.
|
|
||||||
switch val.Type().TypeKind() {
|
|
||||||
case llvm.IntegerTypeKind:
|
|
||||||
itfValue = c.builder.CreateIntToPtr(val, c.i8ptrType, "makeinterface.cast.int")
|
|
||||||
case llvm.PointerTypeKind:
|
|
||||||
itfValue = c.builder.CreateBitCast(val, c.i8ptrType, "makeinterface.cast.ptr")
|
|
||||||
case llvm.StructTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
|
|
||||||
// A bitcast would be useful here, but bitcast doesn't allow
|
|
||||||
// aggregate types. So we'll bitcast it using an alloca.
|
|
||||||
// Hopefully this will get optimized away.
|
|
||||||
mem := c.builder.CreateAlloca(c.i8ptrType, "makeinterface.cast.struct")
|
|
||||||
memStructPtr := c.builder.CreateBitCast(mem, llvm.PointerType(val.Type(), 0), "makeinterface.cast.struct.cast")
|
|
||||||
c.builder.CreateStore(val, memStructPtr)
|
|
||||||
itfValue = c.builder.CreateLoad(mem, "makeinterface.cast.load")
|
|
||||||
default:
|
|
||||||
return llvm.Value{}, c.makeError(pos, "todo: makeinterface: cast small type to i8*")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
itfTypeCodeGlobal := c.getTypeCode(typ)
|
itfTypeCodeGlobal := c.getTypeCode(typ)
|
||||||
itfMethodSetGlobal, err := c.getTypeMethodSet(typ)
|
itfMethodSetGlobal := c.getTypeMethodSet(typ)
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, nil
|
|
||||||
}
|
|
||||||
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
|
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
|
||||||
if itfConcreteTypeGlobal.IsNil() {
|
if itfConcreteTypeGlobal.IsNil() {
|
||||||
typeInInterface := c.mod.GetTypeByName("runtime.typeInInterface")
|
typeInInterface := c.mod.GetTypeByName("runtime.typeInInterface")
|
||||||
@@ -71,7 +38,7 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.
|
|||||||
itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
|
itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
|
||||||
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
|
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
|
||||||
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
|
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
|
||||||
return itf, nil
|
return itf
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTypeCode returns a reference to a type code.
|
// getTypeCode returns a reference to a type code.
|
||||||
@@ -185,18 +152,18 @@ func getTypeCodeName(t types.Type) string {
|
|||||||
|
|
||||||
// getTypeMethodSet returns a reference (GEP) to a global method set. This
|
// getTypeMethodSet returns a reference (GEP) to a global method set. This
|
||||||
// method set should be unreferenced after the interface lowering pass.
|
// method set should be unreferenced after the interface lowering pass.
|
||||||
func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
|
func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||||
global := c.mod.NamedGlobal(typ.String() + "$methodset")
|
global := c.mod.NamedGlobal(typ.String() + "$methodset")
|
||||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||||
if !global.IsNil() {
|
if !global.IsNil() {
|
||||||
// the method set already exists
|
// the method set already exists
|
||||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero}), nil
|
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
||||||
}
|
}
|
||||||
|
|
||||||
ms := c.ir.Program.MethodSets.MethodSet(typ)
|
ms := c.ir.Program.MethodSets.MethodSet(typ)
|
||||||
if ms.Len() == 0 {
|
if ms.Len() == 0 {
|
||||||
// no methods, so can leave that one out
|
// no methods, so can leave that one out
|
||||||
return llvm.ConstPointerNull(llvm.PointerType(c.mod.GetTypeByName("runtime.interfaceMethodInfo"), 0)), nil
|
return llvm.ConstPointerNull(llvm.PointerType(c.mod.GetTypeByName("runtime.interfaceMethodInfo"), 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
methods := make([]llvm.Value, ms.Len())
|
methods := make([]llvm.Value, ms.Len())
|
||||||
@@ -209,10 +176,7 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
|
|||||||
// compiler error, so panic
|
// compiler error, so panic
|
||||||
panic("cannot find function: " + f.LinkName())
|
panic("cannot find function: " + f.LinkName())
|
||||||
}
|
}
|
||||||
fn, err := c.getInterfaceInvokeWrapper(f)
|
fn := c.getInterfaceInvokeWrapper(f)
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
|
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
|
||||||
signatureGlobal,
|
signatureGlobal,
|
||||||
llvm.ConstPtrToInt(fn, c.uintptrType),
|
llvm.ConstPtrToInt(fn, c.uintptrType),
|
||||||
@@ -225,7 +189,7 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
|
|||||||
global.SetInitializer(value)
|
global.SetInitializer(value)
|
||||||
global.SetGlobalConstant(true)
|
global.SetGlobalConstant(true)
|
||||||
global.SetLinkage(llvm.PrivateLinkage)
|
global.SetLinkage(llvm.PrivateLinkage)
|
||||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero}), nil
|
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
||||||
}
|
}
|
||||||
|
|
||||||
// getInterfaceMethodSet returns a global variable with the method set of the
|
// getInterfaceMethodSet returns a global variable with the method set of the
|
||||||
@@ -274,19 +238,9 @@ func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
|
|||||||
//
|
//
|
||||||
// Type asserts on concrete types are trivial: just compare type numbers. Type
|
// Type asserts on concrete types are trivial: just compare type numbers. Type
|
||||||
// asserts on interfaces are more difficult, see the comments in the function.
|
// asserts on interfaces are more difficult, see the comments in the function.
|
||||||
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Value, error) {
|
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Value {
|
||||||
itf, err := c.parseExpr(frame, expr.X)
|
itf := c.getValue(frame, expr.X)
|
||||||
if err != nil {
|
assertedType := c.getLLVMType(expr.AssertedType)
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
assertedType, err := c.getLLVMType(expr.AssertedType)
|
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
valueNil, err := c.getZeroValue(assertedType)
|
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type")
|
actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type")
|
||||||
commaOk := llvm.Value{}
|
commaOk := llvm.Value{}
|
||||||
@@ -339,67 +293,35 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Val
|
|||||||
// Type assert on concrete type. Extract the underlying type from
|
// Type assert on concrete type. Extract the underlying type from
|
||||||
// the interface (but only after checking it matches).
|
// the interface (but only after checking it matches).
|
||||||
valuePtr := c.builder.CreateExtractValue(itf, 1, "typeassert.value.ptr")
|
valuePtr := c.builder.CreateExtractValue(itf, 1, "typeassert.value.ptr")
|
||||||
size := c.targetData.TypeAllocSize(assertedType)
|
valueOk = c.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0]
|
||||||
if size > c.targetData.TypeAllocSize(c.i8ptrType) {
|
|
||||||
// Value was stored in an allocated buffer, load it from there.
|
|
||||||
valuePtrCast := c.builder.CreateBitCast(valuePtr, llvm.PointerType(assertedType, 0), "")
|
|
||||||
valueOk = c.builder.CreateLoad(valuePtrCast, "typeassert.value.ok")
|
|
||||||
} else if size == 0 {
|
|
||||||
valueOk, err = c.getZeroValue(assertedType)
|
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Value was stored directly in the interface.
|
|
||||||
switch assertedType.TypeKind() {
|
|
||||||
case llvm.IntegerTypeKind:
|
|
||||||
valueOk = c.builder.CreatePtrToInt(valuePtr, assertedType, "typeassert.value.ok")
|
|
||||||
case llvm.PointerTypeKind:
|
|
||||||
valueOk = c.builder.CreateBitCast(valuePtr, assertedType, "typeassert.value.ok")
|
|
||||||
default: // struct, float, etc.
|
|
||||||
// A bitcast would be useful here, but bitcast doesn't allow
|
|
||||||
// aggregate types. So we'll bitcast it using an alloca.
|
|
||||||
// Hopefully this will get optimized away.
|
|
||||||
mem := c.builder.CreateAlloca(c.i8ptrType, "")
|
|
||||||
c.builder.CreateStore(valuePtr, mem)
|
|
||||||
memCast := c.builder.CreateBitCast(mem, llvm.PointerType(assertedType, 0), "")
|
|
||||||
valueOk = c.builder.CreateLoad(memCast, "typeassert.value.ok")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
c.builder.CreateBr(nextBlock)
|
c.builder.CreateBr(nextBlock)
|
||||||
|
|
||||||
// Continue after the if statement.
|
// Continue after the if statement.
|
||||||
c.builder.SetInsertPointAtEnd(nextBlock)
|
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||||
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
|
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
|
||||||
phi.AddIncoming([]llvm.Value{valueNil, valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
|
phi.AddIncoming([]llvm.Value{c.getZeroValue(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
|
||||||
|
|
||||||
if expr.CommaOk {
|
if expr.CommaOk {
|
||||||
tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple
|
tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple
|
||||||
tuple = c.builder.CreateInsertValue(tuple, phi, 0, "") // insert value
|
tuple = c.builder.CreateInsertValue(tuple, phi, 0, "") // insert value
|
||||||
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
|
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
|
||||||
return tuple, nil
|
return tuple
|
||||||
} else {
|
} else {
|
||||||
// This is kind of dirty as the branch above becomes mostly useless,
|
// This is kind of dirty as the branch above becomes mostly useless,
|
||||||
// but hopefully this gets optimized away.
|
// but hopefully this gets optimized away.
|
||||||
c.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
|
c.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
|
||||||
return phi, nil
|
return phi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getInvokeCall creates and returns the function pointer and parameters of an
|
// getInvokeCall creates and returns the function pointer and parameters of an
|
||||||
// interface call. It can be used in a call or defer instruction.
|
// interface call. It can be used in a call or defer instruction.
|
||||||
func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, []llvm.Value, error) {
|
func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
|
||||||
// Call an interface method with dynamic dispatch.
|
// Call an interface method with dynamic dispatch.
|
||||||
itf, err := c.parseExpr(frame, instr.Value) // interface
|
itf := c.getValue(frame, instr.Value) // interface
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
llvmFnType, err := c.getRawFuncType(instr.Method.Type().(*types.Signature))
|
llvmFnType := c.getRawFuncType(instr.Method.Type().(*types.Signature))
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
|
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
|
||||||
values := []llvm.Value{
|
values := []llvm.Value{
|
||||||
@@ -413,11 +335,7 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
|
|||||||
|
|
||||||
args := []llvm.Value{receiverValue}
|
args := []llvm.Value{receiverValue}
|
||||||
for _, arg := range instr.Args {
|
for _, arg := range instr.Args {
|
||||||
val, err := c.parseExpr(frame, arg)
|
args = append(args, c.getValue(frame, arg))
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, nil, err
|
|
||||||
}
|
|
||||||
args = append(args, val)
|
|
||||||
}
|
}
|
||||||
// Add the context parameter. An interface call never takes a context but we
|
// Add the context parameter. An interface call never takes a context but we
|
||||||
// have to supply the parameter anyway.
|
// have to supply the parameter anyway.
|
||||||
@@ -425,7 +343,7 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
|
|||||||
// Add the parent goroutine handle.
|
// Add the parent goroutine handle.
|
||||||
args = append(args, llvm.Undef(c.i8ptrType))
|
args = append(args, llvm.Undef(c.i8ptrType))
|
||||||
|
|
||||||
return fnCast, args, nil
|
return fnCast, args
|
||||||
}
|
}
|
||||||
|
|
||||||
// interfaceInvokeWrapper keeps some state between getInterfaceInvokeWrapper and
|
// interfaceInvokeWrapper keeps some state between getInterfaceInvokeWrapper and
|
||||||
@@ -441,19 +359,16 @@ type interfaceInvokeWrapper struct {
|
|||||||
// the underlying value, dereferences it, and calls the real method. This
|
// the underlying value, dereferences it, and calls the real method. This
|
||||||
// wrapper is only needed when the interface value actually doesn't fit in a
|
// wrapper is only needed when the interface value actually doesn't fit in a
|
||||||
// pointer and a pointer to the value must be created.
|
// pointer and a pointer to the value must be created.
|
||||||
func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error) {
|
func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
|
||||||
wrapperName := f.LinkName() + "$invoke"
|
wrapperName := f.LinkName() + "$invoke"
|
||||||
wrapper := c.mod.NamedFunction(wrapperName)
|
wrapper := c.mod.NamedFunction(wrapperName)
|
||||||
if !wrapper.IsNil() {
|
if !wrapper.IsNil() {
|
||||||
// Wrapper already created. Return it directly.
|
// Wrapper already created. Return it directly.
|
||||||
return wrapper, nil
|
return wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the expanded receiver type.
|
// Get the expanded receiver type.
|
||||||
receiverType, err := c.getLLVMType(f.Params[0].Type())
|
receiverType := c.getLLVMType(f.Params[0].Type())
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
expandedReceiverType := c.expandFormalParamType(receiverType)
|
expandedReceiverType := c.expandFormalParamType(receiverType)
|
||||||
|
|
||||||
// Does this method even need any wrapping?
|
// Does this method even need any wrapping?
|
||||||
@@ -462,7 +377,7 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error)
|
|||||||
// Casting a function signature to a different signature and calling it
|
// Casting a function signature to a different signature and calling it
|
||||||
// with a receiver pointer bitcasted to *i8 (as done in calls on an
|
// with a receiver pointer bitcasted to *i8 (as done in calls on an
|
||||||
// interface) is hopefully a safe (defined) operation.
|
// interface) is hopefully a safe (defined) operation.
|
||||||
return f.LLVMFn, nil
|
return f.LLVMFn
|
||||||
}
|
}
|
||||||
|
|
||||||
// create wrapper function
|
// create wrapper function
|
||||||
@@ -475,12 +390,12 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error)
|
|||||||
wrapper: wrapper,
|
wrapper: wrapper,
|
||||||
receiverType: receiverType,
|
receiverType: receiverType,
|
||||||
})
|
})
|
||||||
return wrapper, nil
|
return wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper,
|
// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper,
|
||||||
// see that function for details.
|
// see that function for details.
|
||||||
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) error {
|
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) {
|
||||||
wrapper := state.wrapper
|
wrapper := state.wrapper
|
||||||
fn := state.fn
|
fn := state.fn
|
||||||
receiverType := state.receiverType
|
receiverType := state.receiverType
|
||||||
@@ -490,10 +405,7 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
|
|||||||
// add debug info if needed
|
// add debug info if needed
|
||||||
if c.Debug {
|
if c.Debug {
|
||||||
pos := c.ir.Program.Fset.Position(fn.Pos())
|
pos := c.ir.Program.Fset.Position(fn.Pos())
|
||||||
difunc, err := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
|
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,28 +413,7 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
|
|||||||
block := c.ctx.AddBasicBlock(wrapper, "entry")
|
block := c.ctx.AddBasicBlock(wrapper, "entry")
|
||||||
c.builder.SetInsertPointAtEnd(block)
|
c.builder.SetInsertPointAtEnd(block)
|
||||||
|
|
||||||
var receiverPtr llvm.Value
|
receiverValue := c.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
|
||||||
if c.targetData.TypeAllocSize(receiverType) > c.targetData.TypeAllocSize(c.i8ptrType) {
|
|
||||||
// The receiver is passed in using a pointer. We have to load it here
|
|
||||||
// and pass it by value to the real function.
|
|
||||||
|
|
||||||
// Load the underlying value.
|
|
||||||
receiverPtrType := llvm.PointerType(receiverType, 0)
|
|
||||||
receiverPtr = c.builder.CreateBitCast(wrapper.Param(0), receiverPtrType, "receiver.ptr")
|
|
||||||
} else {
|
|
||||||
// The value is stored in the interface, but it is of type struct which
|
|
||||||
// is expanded to multiple parameters (e.g. {i8, i8}). So we have to
|
|
||||||
// receive the struct as parameter, expand it, and pass it on to the
|
|
||||||
// real function.
|
|
||||||
|
|
||||||
// Cast the passed-in i8* to the struct value (using an alloca) and
|
|
||||||
// extract its values.
|
|
||||||
alloca := c.builder.CreateAlloca(c.i8ptrType, "receiver.alloca")
|
|
||||||
c.builder.CreateStore(wrapper.Param(0), alloca)
|
|
||||||
receiverPtr = c.builder.CreateBitCast(alloca, llvm.PointerType(receiverType, 0), "receiver.ptr")
|
|
||||||
}
|
|
||||||
|
|
||||||
receiverValue := c.builder.CreateLoad(receiverPtr, "receiver")
|
|
||||||
params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
|
params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
|
||||||
if fn.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
|
if fn.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
|
||||||
c.builder.CreateCall(fn.LLVMFn, params, "")
|
c.builder.CreateCall(fn.LLVMFn, params, "")
|
||||||
@@ -531,6 +422,4 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
|
|||||||
ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
|
ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
|
||||||
c.builder.CreateRet(ret)
|
c.builder.CreateRet(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,47 @@ func getUses(value llvm.Value) []llvm.Value {
|
|||||||
return uses
|
return uses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createEntryBlockAlloca creates a new alloca in the entry block, even though
|
||||||
|
// the IR builder is located elsewhere. It assumes that the insert point is
|
||||||
|
// after the last instruction in the current block. Also, it adds lifetime
|
||||||
|
// information to the IR signalling that the alloca won't be used before this
|
||||||
|
// point.
|
||||||
|
//
|
||||||
|
// This is useful for creating temporary allocas for intrinsics. Don't forget to
|
||||||
|
// end the lifetime after you're done with it.
|
||||||
|
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
|
||||||
|
currentBlock := c.builder.GetInsertBlock()
|
||||||
|
c.builder.SetInsertPointBefore(currentBlock.Parent().EntryBasicBlock().FirstInstruction())
|
||||||
|
alloca = c.builder.CreateAlloca(t, name)
|
||||||
|
c.builder.SetInsertPointAtEnd(currentBlock)
|
||||||
|
bitcast = c.builder.CreateBitCast(alloca, c.i8ptrType, name+".bitcast")
|
||||||
|
size = llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false)
|
||||||
|
c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
|
||||||
|
// first if it doesn't exist yet.
|
||||||
|
func (c *Compiler) getLifetimeStartFunc() llvm.Value {
|
||||||
|
fn := c.mod.NamedFunction("llvm.lifetime.start.p0i8")
|
||||||
|
if fn.IsNil() {
|
||||||
|
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false)
|
||||||
|
fn = llvm.AddFunction(c.mod, "llvm.lifetime.start.p0i8", fnType)
|
||||||
|
}
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it
|
||||||
|
// first if it doesn't exist yet.
|
||||||
|
func (c *Compiler) getLifetimeEndFunc() llvm.Value {
|
||||||
|
fn := c.mod.NamedFunction("llvm.lifetime.end.p0i8")
|
||||||
|
if fn.IsNil() {
|
||||||
|
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false)
|
||||||
|
fn = llvm.AddFunction(c.mod, "llvm.lifetime.end.p0i8", fnType)
|
||||||
|
}
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
|
||||||
// splitBasicBlock splits a LLVM basic block into two parts. All instructions
|
// splitBasicBlock splits a LLVM basic block into two parts. All instructions
|
||||||
// after afterInst are moved into a new basic block (created right after the
|
// after afterInst are moved into a new basic block (created right after the
|
||||||
// current one) with the given name.
|
// current one) with the given name.
|
||||||
|
|||||||
+29
-20
@@ -10,12 +10,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
|
func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
|
||||||
llvmValueType, err := c.getLLVMType(valueType)
|
llvmValueType := c.getLLVMType(valueType)
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
// 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
|
||||||
mapValueAlloca := c.builder.CreateAlloca(llvmValueType, "hashmap.value")
|
// present in the map.
|
||||||
mapValuePtr := c.builder.CreateBitCast(mapValueAlloca, c.i8ptrType, "hashmap.valueptr")
|
mapValueAlloca, mapValuePtr, mapValueSize := c.createEntryBlockAlloca(llvmValueType, "hashmap.value")
|
||||||
|
|
||||||
|
// Do the lookup. How it is done depends on the key type.
|
||||||
var commaOkValue llvm.Value
|
var commaOkValue llvm.Value
|
||||||
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
|
||||||
@@ -23,15 +25,24 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
|
|||||||
commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "")
|
commaOkValue = c.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
|
||||||
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
|
// Store the key in an alloca, in the entry block to avoid dynamic stack
|
||||||
c.builder.CreateStore(key, keyAlloca)
|
// growth.
|
||||||
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
|
||||||
params := []llvm.Value{m, keyPtr, mapValuePtr}
|
c.builder.CreateStore(key, mapKeyAlloca)
|
||||||
|
// Fetch the value from the hashmap.
|
||||||
|
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
|
||||||
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
|
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
|
||||||
|
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapKeySize, mapKeyPtr}, "")
|
||||||
} else {
|
} else {
|
||||||
|
// Not trivially comparable using memcmp.
|
||||||
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load the resulting value from the hashmap. The value is set to the zero
|
||||||
|
// value if the key doesn't exist in the hashmap.
|
||||||
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
|
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
|
||||||
|
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapValueSize, mapValuePtr}, "")
|
||||||
|
|
||||||
if commaOk {
|
if commaOk {
|
||||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
|
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
|
||||||
tuple = c.builder.CreateInsertValue(tuple, mapValue, 0, "")
|
tuple = c.builder.CreateInsertValue(tuple, mapValue, 0, "")
|
||||||
@@ -42,27 +53,25 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) error {
|
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
|
||||||
valueAlloca := c.builder.CreateAlloca(value.Type(), "hashmap.value")
|
valueAlloca, valuePtr, valueSize := c.createEntryBlockAlloca(value.Type(), "hashmap.value")
|
||||||
c.builder.CreateStore(value, valueAlloca)
|
c.builder.CreateStore(value, valueAlloca)
|
||||||
valuePtr := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "hashmap.valueptr")
|
|
||||||
keyType = keyType.Underlying()
|
keyType = keyType.Underlying()
|
||||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||||
// key is a string
|
// key is a string
|
||||||
params := []llvm.Value{m, key, valuePtr}
|
params := []llvm.Value{m, key, valuePtr}
|
||||||
c.createRuntimeCall("hashmapStringSet", params, "")
|
c.createRuntimeCall("hashmapStringSet", params, "")
|
||||||
return nil
|
|
||||||
} else if hashmapIsBinaryKey(keyType) {
|
} else if hashmapIsBinaryKey(keyType) {
|
||||||
// key can be compared with runtime.memequal
|
// key can be compared with runtime.memequal
|
||||||
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
|
keyAlloca, keyPtr, keySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
|
||||||
c.builder.CreateStore(key, keyAlloca)
|
c.builder.CreateStore(key, keyAlloca)
|
||||||
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
|
||||||
params := []llvm.Value{m, keyPtr, valuePtr}
|
params := []llvm.Value{m, keyPtr, valuePtr}
|
||||||
c.createRuntimeCall("hashmapBinarySet", params, "")
|
c.createRuntimeCall("hashmapBinarySet", params, "")
|
||||||
return nil
|
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
|
||||||
} else {
|
} else {
|
||||||
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
c.addError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||||
}
|
}
|
||||||
|
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSize, valuePtr}, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
|
func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
|
||||||
@@ -73,11 +82,11 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke
|
|||||||
c.createRuntimeCall("hashmapStringDelete", params, "")
|
c.createRuntimeCall("hashmapStringDelete", params, "")
|
||||||
return nil
|
return nil
|
||||||
} else if hashmapIsBinaryKey(keyType) {
|
} else if hashmapIsBinaryKey(keyType) {
|
||||||
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
|
keyAlloca, keyPtr, keySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
|
||||||
c.builder.CreateStore(key, keyAlloca)
|
c.builder.CreateStore(key, keyAlloca)
|
||||||
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
|
||||||
params := []llvm.Value{m, keyPtr}
|
params := []llvm.Value{m, keyPtr}
|
||||||
c.createRuntimeCall("hashmapBinaryDelete", params, "")
|
c.createRuntimeCall("hashmapBinaryDelete", params, "")
|
||||||
|
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||||
|
|||||||
+32
-13
@@ -18,6 +18,10 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
|||||||
}
|
}
|
||||||
builder.AddCoroutinePassesToExtensionPoints()
|
builder.AddCoroutinePassesToExtensionPoints()
|
||||||
|
|
||||||
|
if c.PanicStrategy == "trap" {
|
||||||
|
c.replacePanicsWithTrap() // -panic=trap
|
||||||
|
}
|
||||||
|
|
||||||
// Run function passes for each function.
|
// Run function passes for each function.
|
||||||
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
|
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
|
||||||
defer funcPasses.Dispose()
|
defer funcPasses.Dispose()
|
||||||
@@ -33,6 +37,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
|||||||
goPasses := llvm.NewPassManager()
|
goPasses := llvm.NewPassManager()
|
||||||
defer goPasses.Dispose()
|
defer goPasses.Dispose()
|
||||||
goPasses.AddGlobalOptimizerPass()
|
goPasses.AddGlobalOptimizerPass()
|
||||||
|
goPasses.AddGlobalDCEPass()
|
||||||
goPasses.AddConstantPropagationPass()
|
goPasses.AddConstantPropagationPass()
|
||||||
goPasses.AddAggressiveDCEPass()
|
goPasses.AddAggressiveDCEPass()
|
||||||
goPasses.AddFunctionAttrsPass()
|
goPasses.AddFunctionAttrsPass()
|
||||||
@@ -110,9 +115,35 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
|||||||
builder.Populate(modPasses)
|
builder.Populate(modPasses)
|
||||||
modPasses.Run(c.mod)
|
modPasses.Run(c.mod)
|
||||||
|
|
||||||
|
if c.gcIsPrecise() {
|
||||||
|
c.addGlobalsBitmap()
|
||||||
|
if err := c.Verify(); err != nil {
|
||||||
|
return errors.New("GC pass caused a verification failure")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace panic calls with calls to llvm.trap, to reduce code size. This is the
|
||||||
|
// -panic=trap intrinsic.
|
||||||
|
func (c *Compiler) replacePanicsWithTrap() {
|
||||||
|
trap := c.mod.NamedFunction("llvm.trap")
|
||||||
|
for _, name := range []string{"runtime._panic", "runtime.runtimePanic"} {
|
||||||
|
fn := c.mod.NamedFunction(name)
|
||||||
|
if fn.IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, use := range getUses(fn) {
|
||||||
|
if use.IsACallInst().IsNil() || use.CalledValue() != fn {
|
||||||
|
panic("expected use of a panic function to be a call")
|
||||||
|
}
|
||||||
|
c.builder.SetInsertPointBefore(use)
|
||||||
|
c.builder.CreateCall(trap, nil, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Eliminate created but not used maps.
|
// Eliminate created but not used maps.
|
||||||
//
|
//
|
||||||
// In the future, this should statically allocate created but never modified
|
// In the future, this should statically allocate created but never modified
|
||||||
@@ -249,7 +280,7 @@ func (c *Compiler) OptimizeAllocs() {
|
|||||||
sizeInWords := (size + uint64(alignment) - 1) / uint64(alignment)
|
sizeInWords := (size + uint64(alignment) - 1) / uint64(alignment)
|
||||||
allocaType := llvm.ArrayType(c.ctx.IntType(alignment*8), int(sizeInWords))
|
allocaType := llvm.ArrayType(c.ctx.IntType(alignment*8), int(sizeInWords))
|
||||||
alloca := c.builder.CreateAlloca(allocaType, "stackalloc.alloca")
|
alloca := c.builder.CreateAlloca(allocaType, "stackalloc.alloca")
|
||||||
zero, _ := c.getZeroValue(alloca.Type().ElementType())
|
zero := c.getZeroValue(alloca.Type().ElementType())
|
||||||
c.builder.CreateStore(zero, alloca)
|
c.builder.CreateStore(zero, alloca)
|
||||||
stackalloc := c.builder.CreateBitCast(alloca, bitcast.Type(), "stackalloc")
|
stackalloc := c.builder.CreateBitCast(alloca, bitcast.Type(), "stackalloc")
|
||||||
bitcast.ReplaceAllUsesWith(stackalloc)
|
bitcast.ReplaceAllUsesWith(stackalloc)
|
||||||
@@ -284,18 +315,6 @@ func (c *Compiler) doesEscape(value llvm.Value) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} else if use.IsACallInst() != nilValue {
|
} else if use.IsACallInst() != nilValue {
|
||||||
// Call only escapes when the (pointer) parameter is not marked
|
|
||||||
// "nocapture". This flag means that the parameter does not escape
|
|
||||||
// the give function.
|
|
||||||
if use.CalledValue().IsAFunction() != nilValue {
|
|
||||||
if use.CalledValue().IsDeclaration() {
|
|
||||||
// Kind of dirty: assume external functions don't let
|
|
||||||
// pointers escape.
|
|
||||||
// TODO: introduce //go:noescape that sets the 'nocapture'
|
|
||||||
// flag on each input parameter.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !c.hasFlag(use, value, "nocapture") {
|
if !c.hasFlag(use, value, "nocapture") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,6 +113,9 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
|
|||||||
if k == types.Uintptr {
|
if k == types.Uintptr {
|
||||||
return s.PtrSize
|
return s.PtrSize
|
||||||
}
|
}
|
||||||
|
if k == types.UnsafePointer {
|
||||||
|
return s.PtrSize
|
||||||
|
}
|
||||||
panic("unknown basic type: " + t.String())
|
panic("unknown basic type: " + t.String())
|
||||||
case *types.Array:
|
case *types.Array:
|
||||||
n := t.Len()
|
n := t.Len()
|
||||||
|
|||||||
+3
-12
@@ -51,10 +51,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
|||||||
"{r12}",
|
"{r12}",
|
||||||
"{r13}",
|
"{r13}",
|
||||||
}[i]
|
}[i]
|
||||||
llvmValue, err := c.parseExpr(frame, arg)
|
llvmValue := c.getValue(frame, arg)
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
args = append(args, llvmValue)
|
args = append(args, llvmValue)
|
||||||
argTypes = append(argTypes, llvmValue.Type())
|
argTypes = append(argTypes, llvmValue.Type())
|
||||||
}
|
}
|
||||||
@@ -80,10 +77,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
|||||||
"{r5}",
|
"{r5}",
|
||||||
"{r6}",
|
"{r6}",
|
||||||
}[i]
|
}[i]
|
||||||
llvmValue, err := c.parseExpr(frame, arg)
|
llvmValue := c.getValue(frame, arg)
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
args = append(args, llvmValue)
|
args = append(args, llvmValue)
|
||||||
argTypes = append(argTypes, llvmValue.Type())
|
argTypes = append(argTypes, llvmValue.Type())
|
||||||
}
|
}
|
||||||
@@ -113,10 +107,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
|||||||
"{x4}",
|
"{x4}",
|
||||||
"{x5}",
|
"{x5}",
|
||||||
}[i]
|
}[i]
|
||||||
llvmValue, err := c.parseExpr(frame, arg)
|
llvmValue := c.getValue(frame, arg)
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
args = append(args, llvmValue)
|
args = append(args, llvmValue)
|
||||||
argTypes = append(argTypes, llvmValue.Type())
|
argTypes = append(argTypes, llvmValue.Type())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package compiler
|
||||||
|
|
||||||
|
// This file implements volatile loads/stores in runtime/volatile.LoadT and
|
||||||
|
// runtime/volatile.StoreT as compiler builtins.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/tools/go/ssa"
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Compiler) emitVolatileLoad(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
|
||||||
|
addr := c.getValue(frame, instr.Args[0])
|
||||||
|
c.emitNilCheck(frame, addr, "deref")
|
||||||
|
val := c.builder.CreateLoad(addr, "")
|
||||||
|
val.SetVolatile(true)
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) emitVolatileStore(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
|
||||||
|
addr := c.getValue(frame, instr.Args[0])
|
||||||
|
val := c.getValue(frame, instr.Args[1])
|
||||||
|
c.emitNilCheck(frame, addr, "deref")
|
||||||
|
store := c.builder.CreateStore(val, addr)
|
||||||
|
store.SetVolatile(true)
|
||||||
|
return llvm.Value{}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package compiler
|
||||||
|
|
||||||
|
// This file contains utility functions to pack and unpack sets of values. It
|
||||||
|
// can take in a list of values and tries to store it efficiently in the pointer
|
||||||
|
// itself if possible and legal.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// emitPointerPack packs the list of values into a single pointer value using
|
||||||
|
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
|
||||||
|
// pointer value directly. It returns the pointer with the packed data.
|
||||||
|
func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
|
||||||
|
valueTypes := make([]llvm.Type, len(values))
|
||||||
|
for i, value := range values {
|
||||||
|
valueTypes[i] = value.Type()
|
||||||
|
}
|
||||||
|
packedType := c.ctx.StructType(valueTypes, false)
|
||||||
|
|
||||||
|
// Allocate memory for the packed data.
|
||||||
|
var packedAlloc, packedHeapAlloc llvm.Value
|
||||||
|
size := c.targetData.TypeAllocSize(packedType)
|
||||||
|
if size == 0 {
|
||||||
|
return llvm.ConstPointerNull(c.i8ptrType)
|
||||||
|
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
|
||||||
|
return c.builder.CreateBitCast(values[0], c.i8ptrType, "pack.ptr")
|
||||||
|
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) && !c.gcIsPrecise() {
|
||||||
|
// Packed data fits in a pointer, so store it directly inside the
|
||||||
|
// pointer.
|
||||||
|
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
|
||||||
|
// Try to keep this cast in SSA form.
|
||||||
|
return c.builder.CreateIntToPtr(values[0], c.i8ptrType, "pack.int")
|
||||||
|
}
|
||||||
|
// Because packedType is a struct and we have to cast it to a *i8, store
|
||||||
|
// it in an alloca first for bitcasting (store+bitcast+load).
|
||||||
|
packedAlloc = c.builder.CreateAlloca(packedType, "")
|
||||||
|
} else {
|
||||||
|
// Packed data is bigger than a pointer, so allocate it on the heap.
|
||||||
|
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||||
|
packedHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
|
||||||
|
packedAlloc = c.builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
|
||||||
|
}
|
||||||
|
// Store all values in the alloca or heap pointer.
|
||||||
|
for i, value := range values {
|
||||||
|
indices := []llvm.Value{
|
||||||
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||||
|
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
||||||
|
}
|
||||||
|
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
||||||
|
c.builder.CreateStore(value, gep)
|
||||||
|
}
|
||||||
|
|
||||||
|
if packedHeapAlloc.IsNil() {
|
||||||
|
// Load value (as *i8) from the alloca.
|
||||||
|
packedAlloc = c.builder.CreateBitCast(packedAlloc, llvm.PointerType(c.i8ptrType, 0), "")
|
||||||
|
return c.builder.CreateLoad(packedAlloc, "")
|
||||||
|
} else {
|
||||||
|
// Get the original heap allocation pointer, which already is an *i8.
|
||||||
|
return c.builder.CreateBitCast(packedAlloc, c.i8ptrType, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
|
||||||
|
func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
|
||||||
|
packedType := c.ctx.StructType(valueTypes, false)
|
||||||
|
|
||||||
|
// Get a correctly-typed pointer to the packed data.
|
||||||
|
var packedAlloc llvm.Value
|
||||||
|
size := c.targetData.TypeAllocSize(packedType)
|
||||||
|
if size == 0 {
|
||||||
|
// No data to unpack.
|
||||||
|
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
|
||||||
|
// A single pointer is always stored directly.
|
||||||
|
return []llvm.Value{c.builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
|
||||||
|
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) && !c.gcIsPrecise() {
|
||||||
|
// Packed data stored directly in pointer.
|
||||||
|
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
|
||||||
|
// Keep this cast in SSA form.
|
||||||
|
return []llvm.Value{c.builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
|
||||||
|
}
|
||||||
|
// Fallback: load it using an alloca.
|
||||||
|
packedRawAlloc := c.builder.CreateAlloca(llvm.PointerType(c.i8ptrType, 0), "unpack.raw.alloc")
|
||||||
|
packedRawValue := c.builder.CreateBitCast(ptr, llvm.PointerType(c.i8ptrType, 0), "unpack.raw.value")
|
||||||
|
c.builder.CreateStore(packedRawValue, packedRawAlloc)
|
||||||
|
packedAlloc = c.builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
|
||||||
|
} else {
|
||||||
|
// Packed data stored on the heap. Bitcast the passed-in pointer to the
|
||||||
|
// correct pointer type.
|
||||||
|
packedAlloc = c.builder.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr")
|
||||||
|
}
|
||||||
|
// Load each value from the packed data.
|
||||||
|
values := make([]llvm.Value, len(valueTypes))
|
||||||
|
for i, valueType := range valueTypes {
|
||||||
|
if c.targetData.TypeAllocSize(valueType) == 0 {
|
||||||
|
// This value has length zero, so there's nothing to load.
|
||||||
|
values[i] = c.getZeroValue(valueType)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
indices := []llvm.Value{
|
||||||
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||||
|
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
||||||
|
}
|
||||||
|
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
||||||
|
values[i] = c.builder.CreateLoad(gep, "")
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
@@ -347,6 +347,8 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), implements, false)}
|
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), implements, false)}
|
||||||
case callee.Name() == "runtime.nanotime":
|
case callee.Name() == "runtime.nanotime":
|
||||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
|
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
|
||||||
|
case callee.Name() == "llvm.dbg.value":
|
||||||
|
// do nothing
|
||||||
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
|
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
|
||||||
// This are all print instructions, which necessarily have side
|
// This are all print instructions, which necessarily have side
|
||||||
// effects but no results.
|
// effects but no results.
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ type Eval struct {
|
|||||||
TargetData llvm.TargetData
|
TargetData llvm.TargetData
|
||||||
Debug bool
|
Debug bool
|
||||||
builder llvm.Builder
|
builder llvm.Builder
|
||||||
dibuilder *llvm.DIBuilder
|
|
||||||
dirtyGlobals map[llvm.Value]struct{}
|
dirtyGlobals map[llvm.Value]struct{}
|
||||||
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
|
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
|
||||||
}
|
}
|
||||||
@@ -38,7 +37,6 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
|||||||
dirtyGlobals: map[llvm.Value]struct{}{},
|
dirtyGlobals: map[llvm.Value]struct{}{},
|
||||||
}
|
}
|
||||||
e.builder = mod.Context().NewBuilder()
|
e.builder = mod.Context().NewBuilder()
|
||||||
e.dibuilder = llvm.NewDIBuilder(mod)
|
|
||||||
|
|
||||||
initAll := mod.NamedFunction(name)
|
initAll := mod.NamedFunction(name)
|
||||||
bb := initAll.EntryBasicBlock()
|
bb := initAll.EntryBasicBlock()
|
||||||
@@ -49,7 +47,6 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
|||||||
e.builder.SetInsertPointBefore(bb.FirstInstruction())
|
e.builder.SetInsertPointBefore(bb.FirstInstruction())
|
||||||
dummy := e.builder.CreateAlloca(e.Mod.Context().Int8Type(), "dummy")
|
dummy := e.builder.CreateAlloca(e.Mod.Context().Int8Type(), "dummy")
|
||||||
e.builder.SetInsertPointBefore(dummy)
|
e.builder.SetInsertPointBefore(dummy)
|
||||||
e.builder.SetInstDebugLocation(bb.FirstInstruction())
|
|
||||||
var initCalls []llvm.Value
|
var initCalls []llvm.Value
|
||||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||||
if inst == dummy {
|
if inst == dummy {
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
|||||||
return &sideEffectResult{severity: sideEffectLimited}
|
return &sideEffectResult{severity: sideEffectLimited}
|
||||||
case "runtime.interfaceImplements":
|
case "runtime.interfaceImplements":
|
||||||
return &sideEffectResult{severity: sideEffectNone}
|
return &sideEffectResult{severity: sideEffectNone}
|
||||||
|
case "llvm.dbg.value":
|
||||||
|
return &sideEffectResult{severity: sideEffectNone}
|
||||||
}
|
}
|
||||||
if e.sideEffectFuncs == nil {
|
if e.sideEffectFuncs == nil {
|
||||||
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
|
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ type Function struct {
|
|||||||
nobounds bool // go:nobounds
|
nobounds bool // go:nobounds
|
||||||
flag bool // used by dead code elimination
|
flag bool // used by dead code elimination
|
||||||
interrupt bool // go:interrupt
|
interrupt bool // go:interrupt
|
||||||
|
inline InlineType // go:inline
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global variable, possibly constant.
|
// Global variable, possibly constant.
|
||||||
@@ -69,7 +70,22 @@ type Interface struct {
|
|||||||
Type *types.Interface
|
Type *types.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and intialize a new *Program from a *ssa.Program.
|
type InlineType int
|
||||||
|
|
||||||
|
// How much to inline.
|
||||||
|
const (
|
||||||
|
// Default behavior. The compiler decides for itself whether any given
|
||||||
|
// function will be inlined. Whether any function is inlined depends on the
|
||||||
|
// optimization level.
|
||||||
|
InlineDefault InlineType = iota
|
||||||
|
|
||||||
|
// Inline hint, just like the C inline keyword (signalled using
|
||||||
|
// //go:inline). The compiler will be more likely to inline this function,
|
||||||
|
// but it is not a guarantee.
|
||||||
|
InlineHint
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create and initialize a new *Program from a *ssa.Program.
|
||||||
func NewProgram(lprogram *loader.Program, mainPath string) *Program {
|
func NewProgram(lprogram *loader.Program, mainPath string) *Program {
|
||||||
comments := map[string]*ast.CommentGroup{}
|
comments := map[string]*ast.CommentGroup{}
|
||||||
for _, pkgInfo := range lprogram.Sorted() {
|
for _, pkgInfo := range lprogram.Sorted() {
|
||||||
@@ -279,6 +295,8 @@ func (f *Function) parsePragmas() {
|
|||||||
}
|
}
|
||||||
f.linkName = parts[1]
|
f.linkName = parts[1]
|
||||||
f.exported = true
|
f.exported = true
|
||||||
|
case "//go:inline":
|
||||||
|
f.inline = InlineHint
|
||||||
case "//go:interrupt":
|
case "//go:interrupt":
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
continue
|
continue
|
||||||
@@ -332,6 +350,11 @@ func (f *Function) IsInterrupt() bool {
|
|||||||
return f.interrupt
|
return f.interrupt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the inline directive of this function.
|
||||||
|
func (f *Function) Inline() InlineType {
|
||||||
|
return f.inline
|
||||||
|
}
|
||||||
|
|
||||||
// Return the link name for this function.
|
// Return the link name for this function.
|
||||||
func (f *Function) LinkName() string {
|
func (f *Function) LinkName() string {
|
||||||
if f.linkName != "" {
|
if f.linkName != "" {
|
||||||
|
|||||||
+5
-2
@@ -24,7 +24,7 @@ import "C"
|
|||||||
// This version uses the built-in linker when trying to use lld.
|
// This version uses the built-in linker when trying to use lld.
|
||||||
func Link(linker string, flags ...string) error {
|
func Link(linker string, flags ...string) error {
|
||||||
switch linker {
|
switch linker {
|
||||||
case "ld.lld", commands["ld.lld"]:
|
case "ld.lld":
|
||||||
flags = append([]string{"tinygo:" + linker}, flags...)
|
flags = append([]string{"tinygo:" + linker}, flags...)
|
||||||
var cflag *C.char
|
var cflag *C.char
|
||||||
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
|
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
|
||||||
@@ -39,7 +39,7 @@ func Link(linker string, flags ...string) error {
|
|||||||
return errors.New("failed to link using built-in ld.lld")
|
return errors.New("failed to link using built-in ld.lld")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case "wasm-ld", commands["wasm-ld"]:
|
case "wasm-ld":
|
||||||
flags = append([]string{"tinygo:" + linker}, flags...)
|
flags = append([]string{"tinygo:" + linker}, flags...)
|
||||||
var cflag *C.char
|
var cflag *C.char
|
||||||
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
|
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
|
||||||
@@ -57,6 +57,9 @@ func Link(linker string, flags ...string) error {
|
|||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
// Fall back to external command.
|
// Fall back to external command.
|
||||||
|
if cmdNames, ok := commands[linker]; ok {
|
||||||
|
return execCommand(cmdNames, flags...)
|
||||||
|
}
|
||||||
cmd := exec.Command(linker, flags...)
|
cmd := exec.Command(linker, flags...)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ import (
|
|||||||
//
|
//
|
||||||
// This version always runs the linker as an external command.
|
// This version always runs the linker as an external command.
|
||||||
func Link(linker string, flags ...string) error {
|
func Link(linker string, flags ...string) error {
|
||||||
|
if cmdNames, ok := commands[linker]; ok {
|
||||||
|
return execCommand(cmdNames, flags...)
|
||||||
|
}
|
||||||
cmd := exec.Command(linker, flags...)
|
cmd := exec.Command(linker, flags...)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
|||||||
-458
@@ -1,458 +0,0 @@
|
|||||||
package loader
|
|
||||||
|
|
||||||
// This file extracts the `import "C"` statement from the source and modifies
|
|
||||||
// the AST for Cgo. It does not use libclang directly (see libclang.go).
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go/ast"
|
|
||||||
"go/token"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/ast/astutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// fileInfo holds all Cgo-related information of a given *ast.File.
|
|
||||||
type fileInfo struct {
|
|
||||||
*ast.File
|
|
||||||
*Package
|
|
||||||
filename string
|
|
||||||
functions map[string]*functionInfo
|
|
||||||
globals map[string]*globalInfo
|
|
||||||
typedefs map[string]*typedefInfo
|
|
||||||
elaboratedTypes map[string]ast.Expr
|
|
||||||
importCPos token.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
// functionInfo stores some information about a Cgo function found by libclang
|
|
||||||
// and declared in the AST.
|
|
||||||
type functionInfo struct {
|
|
||||||
args []paramInfo
|
|
||||||
results *ast.FieldList
|
|
||||||
}
|
|
||||||
|
|
||||||
// paramInfo is a parameter of a Cgo function (see functionInfo).
|
|
||||||
type paramInfo struct {
|
|
||||||
name string
|
|
||||||
typeExpr ast.Expr
|
|
||||||
}
|
|
||||||
|
|
||||||
// typedefInfo contains information about a single typedef in C.
|
|
||||||
type typedefInfo struct {
|
|
||||||
typeExpr ast.Expr
|
|
||||||
}
|
|
||||||
|
|
||||||
// globalInfo contains information about a declared global variable in C.
|
|
||||||
type globalInfo struct {
|
|
||||||
typeExpr ast.Expr
|
|
||||||
}
|
|
||||||
|
|
||||||
// cgoAliases list type aliases between Go and C, for types that are equivalent
|
|
||||||
// in both languages. See addTypeAliases.
|
|
||||||
var cgoAliases = map[string]string{
|
|
||||||
"C.int8_t": "int8",
|
|
||||||
"C.int16_t": "int16",
|
|
||||||
"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",
|
|
||||||
}
|
|
||||||
|
|
||||||
// cgoTypes lists some C types with ambiguous sizes that must be retrieved
|
|
||||||
// somehow from C. This is done by adding some typedefs to get the size of each
|
|
||||||
// type.
|
|
||||||
const cgoTypes = `
|
|
||||||
typedef signed char _Cgo_schar;
|
|
||||||
typedef unsigned char _Cgo_uchar;
|
|
||||||
typedef short _Cgo_short;
|
|
||||||
typedef unsigned short _Cgo_ushort;
|
|
||||||
typedef int _Cgo_int;
|
|
||||||
typedef unsigned int _Cgo_uint;
|
|
||||||
typedef long _Cgo_long;
|
|
||||||
typedef unsigned long _Cgo_ulong;
|
|
||||||
typedef long long _Cgo_longlong;
|
|
||||||
typedef unsigned long long _Cgo_ulonglong;
|
|
||||||
`
|
|
||||||
|
|
||||||
// processCgo extracts the `import "C"` statement from the AST, parses the
|
|
||||||
// comment with libclang, and modifies the AST to use this information.
|
|
||||||
func (p *Package) processCgo(filename string, f *ast.File, cflags []string) []error {
|
|
||||||
info := &fileInfo{
|
|
||||||
File: f,
|
|
||||||
Package: p,
|
|
||||||
filename: filename,
|
|
||||||
functions: map[string]*functionInfo{},
|
|
||||||
globals: map[string]*globalInfo{},
|
|
||||||
typedefs: map[string]*typedefInfo{},
|
|
||||||
elaboratedTypes: map[string]ast.Expr{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find `import "C"` statements in the file.
|
|
||||||
for i := 0; i < len(f.Decls); i++ {
|
|
||||||
decl := f.Decls[i]
|
|
||||||
genDecl, ok := decl.(*ast.GenDecl)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(genDecl.Specs) != 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
spec, ok := genDecl.Specs[0].(*ast.ImportSpec)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
path, err := strconv.Unquote(spec.Path.Value)
|
|
||||||
if err != nil {
|
|
||||||
panic("could not parse import path: " + err.Error())
|
|
||||||
}
|
|
||||||
if path != "C" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
cgoComment := genDecl.Doc.Text()
|
|
||||||
|
|
||||||
// Stored for later use by generated functions, to use a somewhat sane
|
|
||||||
// source location.
|
|
||||||
info.importCPos = spec.Path.ValuePos
|
|
||||||
|
|
||||||
pos := info.fset.PositionFor(genDecl.Doc.Pos(), true)
|
|
||||||
errs := info.parseFragment(cgoComment+cgoTypes, cflags, pos.Filename, pos.Line)
|
|
||||||
if errs != nil {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove this import declaration.
|
|
||||||
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print the AST, for debugging.
|
|
||||||
//ast.Print(p.fset, f)
|
|
||||||
|
|
||||||
// Declare functions found by libclang.
|
|
||||||
info.addFuncDecls()
|
|
||||||
|
|
||||||
// Declare stub function pointer values found by libclang.
|
|
||||||
info.addFuncPtrDecls()
|
|
||||||
|
|
||||||
// Declare globals found by libclang.
|
|
||||||
info.addVarDecls()
|
|
||||||
|
|
||||||
// Forward C types to Go types (like C.uint32_t -> uint32).
|
|
||||||
info.addTypeAliases()
|
|
||||||
|
|
||||||
// Add type declarations for C types, declared using typedef in C.
|
|
||||||
info.addTypedefs()
|
|
||||||
|
|
||||||
// Add elaborated types for C structs and unions.
|
|
||||||
info.addElaboratedTypes()
|
|
||||||
|
|
||||||
// Patch the AST to use the declared types and functions.
|
|
||||||
f = astutil.Apply(f, info.walker, nil).(*ast.File)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// addFuncDecls adds the C function declarations found by libclang in the
|
|
||||||
// comment above the `import "C"` statement.
|
|
||||||
func (info *fileInfo) addFuncDecls() {
|
|
||||||
// TODO: replace all uses of importCPos with the real locations from
|
|
||||||
// libclang.
|
|
||||||
names := make([]string, 0, len(info.functions))
|
|
||||||
for name := range info.functions {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
for _, name := range names {
|
|
||||||
fn := info.functions[name]
|
|
||||||
obj := &ast.Object{
|
|
||||||
Kind: ast.Fun,
|
|
||||||
Name: "C." + name,
|
|
||||||
}
|
|
||||||
args := make([]*ast.Field, len(fn.args))
|
|
||||||
decl := &ast.FuncDecl{
|
|
||||||
Name: &ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: "C." + name,
|
|
||||||
Obj: obj,
|
|
||||||
},
|
|
||||||
Type: &ast.FuncType{
|
|
||||||
Func: info.importCPos,
|
|
||||||
Params: &ast.FieldList{
|
|
||||||
Opening: info.importCPos,
|
|
||||||
List: args,
|
|
||||||
Closing: info.importCPos,
|
|
||||||
},
|
|
||||||
Results: fn.results,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
obj.Decl = decl
|
|
||||||
for i, arg := range fn.args {
|
|
||||||
args[i] = &ast.Field{
|
|
||||||
Names: []*ast.Ident{
|
|
||||||
&ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: arg.name,
|
|
||||||
Obj: &ast.Object{
|
|
||||||
Kind: ast.Var,
|
|
||||||
Name: arg.name,
|
|
||||||
Decl: decl,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Type: arg.typeExpr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info.Decls = append(info.Decls, decl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// addFuncPtrDecls creates stub declarations of function pointer values. These
|
|
||||||
// values will later be replaced with the real values in the compiler.
|
|
||||||
// It adds code like the following to the AST:
|
|
||||||
//
|
|
||||||
// var (
|
|
||||||
// C.add unsafe.Pointer
|
|
||||||
// C.mul unsafe.Pointer
|
|
||||||
// // ...
|
|
||||||
// )
|
|
||||||
func (info *fileInfo) addFuncPtrDecls() {
|
|
||||||
gen := &ast.GenDecl{
|
|
||||||
TokPos: info.importCPos,
|
|
||||||
Tok: token.VAR,
|
|
||||||
Lparen: info.importCPos,
|
|
||||||
Rparen: info.importCPos,
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(info.functions))
|
|
||||||
for name := range info.functions {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
for _, name := range names {
|
|
||||||
obj := &ast.Object{
|
|
||||||
Kind: ast.Typ,
|
|
||||||
Name: "C." + name + "$funcaddr",
|
|
||||||
}
|
|
||||||
valueSpec := &ast.ValueSpec{
|
|
||||||
Names: []*ast.Ident{&ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: "C." + name + "$funcaddr",
|
|
||||||
Obj: obj,
|
|
||||||
}},
|
|
||||||
Type: &ast.SelectorExpr{
|
|
||||||
X: &ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: "unsafe",
|
|
||||||
},
|
|
||||||
Sel: &ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: "Pointer",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
obj.Decl = valueSpec
|
|
||||||
gen.Specs = append(gen.Specs, valueSpec)
|
|
||||||
}
|
|
||||||
info.Decls = append(info.Decls, gen)
|
|
||||||
}
|
|
||||||
|
|
||||||
// addVarDecls declares external C globals in the Go source.
|
|
||||||
// It adds code like the following to the AST:
|
|
||||||
//
|
|
||||||
// var (
|
|
||||||
// C.globalInt int
|
|
||||||
// C.globalBool bool
|
|
||||||
// // ...
|
|
||||||
// )
|
|
||||||
func (info *fileInfo) addVarDecls() {
|
|
||||||
gen := &ast.GenDecl{
|
|
||||||
TokPos: info.importCPos,
|
|
||||||
Tok: token.VAR,
|
|
||||||
Lparen: info.importCPos,
|
|
||||||
Rparen: info.importCPos,
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(info.globals))
|
|
||||||
for name := range info.globals {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
for _, name := range names {
|
|
||||||
global := info.globals[name]
|
|
||||||
obj := &ast.Object{
|
|
||||||
Kind: ast.Typ,
|
|
||||||
Name: "C." + name,
|
|
||||||
}
|
|
||||||
valueSpec := &ast.ValueSpec{
|
|
||||||
Names: []*ast.Ident{&ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: "C." + name,
|
|
||||||
Obj: obj,
|
|
||||||
}},
|
|
||||||
Type: global.typeExpr,
|
|
||||||
}
|
|
||||||
obj.Decl = valueSpec
|
|
||||||
gen.Specs = append(gen.Specs, valueSpec)
|
|
||||||
}
|
|
||||||
info.Decls = append(info.Decls, gen)
|
|
||||||
}
|
|
||||||
|
|
||||||
// addTypeAliases aliases some built-in Go types with their equivalent C types.
|
|
||||||
// It adds code like the following to the AST:
|
|
||||||
//
|
|
||||||
// type (
|
|
||||||
// C.int8_t = int8
|
|
||||||
// C.int16_t = int16
|
|
||||||
// // ...
|
|
||||||
// )
|
|
||||||
func (info *fileInfo) addTypeAliases() {
|
|
||||||
aliasKeys := make([]string, 0, len(cgoAliases))
|
|
||||||
for key := range cgoAliases {
|
|
||||||
aliasKeys = append(aliasKeys, key)
|
|
||||||
}
|
|
||||||
sort.Strings(aliasKeys)
|
|
||||||
gen := &ast.GenDecl{
|
|
||||||
TokPos: info.importCPos,
|
|
||||||
Tok: token.TYPE,
|
|
||||||
Lparen: info.importCPos,
|
|
||||||
Rparen: info.importCPos,
|
|
||||||
}
|
|
||||||
for _, typeName := range aliasKeys {
|
|
||||||
goTypeName := cgoAliases[typeName]
|
|
||||||
obj := &ast.Object{
|
|
||||||
Kind: ast.Typ,
|
|
||||||
Name: typeName,
|
|
||||||
}
|
|
||||||
typeSpec := &ast.TypeSpec{
|
|
||||||
Name: &ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: typeName,
|
|
||||||
Obj: obj,
|
|
||||||
},
|
|
||||||
Assign: info.importCPos,
|
|
||||||
Type: &ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: goTypeName,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
obj.Decl = typeSpec
|
|
||||||
gen.Specs = append(gen.Specs, typeSpec)
|
|
||||||
}
|
|
||||||
info.Decls = append(info.Decls, gen)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (info *fileInfo) addTypedefs() {
|
|
||||||
gen := &ast.GenDecl{
|
|
||||||
TokPos: info.importCPos,
|
|
||||||
Tok: token.TYPE,
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(info.typedefs))
|
|
||||||
for name := range info.typedefs {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
for _, name := range names {
|
|
||||||
typedef := info.typedefs[name]
|
|
||||||
typeName := "C." + name
|
|
||||||
if strings.HasPrefix(name, "_Cgo_") {
|
|
||||||
typeName = "C." + name[len("_Cgo_"):]
|
|
||||||
}
|
|
||||||
if _, ok := cgoAliases[typeName]; ok {
|
|
||||||
// This is a type that also exists in Go (defined in stdint.h).
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
obj := &ast.Object{
|
|
||||||
Kind: ast.Typ,
|
|
||||||
Name: typeName,
|
|
||||||
}
|
|
||||||
typeSpec := &ast.TypeSpec{
|
|
||||||
Name: &ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: typeName,
|
|
||||||
Obj: obj,
|
|
||||||
},
|
|
||||||
Type: typedef.typeExpr,
|
|
||||||
}
|
|
||||||
obj.Decl = typeSpec
|
|
||||||
gen.Specs = append(gen.Specs, typeSpec)
|
|
||||||
}
|
|
||||||
info.Decls = append(info.Decls, gen)
|
|
||||||
}
|
|
||||||
|
|
||||||
// addElaboratedTypes adds C elaborated types as aliases. These are the "struct
|
|
||||||
// foo" or "union foo" types, often used in a typedef.
|
|
||||||
//
|
|
||||||
// See also:
|
|
||||||
// https://en.cppreference.com/w/cpp/language/elaborated_type_specifier
|
|
||||||
func (info *fileInfo) addElaboratedTypes() {
|
|
||||||
gen := &ast.GenDecl{
|
|
||||||
TokPos: info.importCPos,
|
|
||||||
Tok: token.TYPE,
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(info.elaboratedTypes))
|
|
||||||
for name := range info.elaboratedTypes {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
for _, name := range names {
|
|
||||||
typ := info.elaboratedTypes[name]
|
|
||||||
typeName := "C.struct_" + name
|
|
||||||
obj := &ast.Object{
|
|
||||||
Kind: ast.Typ,
|
|
||||||
Name: typeName,
|
|
||||||
}
|
|
||||||
typeSpec := &ast.TypeSpec{
|
|
||||||
Name: &ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: typeName,
|
|
||||||
Obj: obj,
|
|
||||||
},
|
|
||||||
Type: typ,
|
|
||||||
}
|
|
||||||
obj.Decl = typeSpec
|
|
||||||
gen.Specs = append(gen.Specs, typeSpec)
|
|
||||||
}
|
|
||||||
info.Decls = append(info.Decls, gen)
|
|
||||||
}
|
|
||||||
|
|
||||||
// walker replaces all "C".<something> expressions to literal "C.<something>"
|
|
||||||
// expressions. Such expressions are impossible to write in Go (a dot cannot be
|
|
||||||
// used in the middle of a name) so in practice all C identifiers live in a
|
|
||||||
// separate namespace (no _Cgo_ hacks like in gc).
|
|
||||||
func (info *fileInfo) walker(cursor *astutil.Cursor) bool {
|
|
||||||
switch node := cursor.Node().(type) {
|
|
||||||
case *ast.CallExpr:
|
|
||||||
fun, ok := node.Fun.(*ast.SelectorExpr)
|
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
x, ok := fun.X.(*ast.Ident)
|
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := info.functions[fun.Sel.Name]; ok && x.Name == "C" {
|
|
||||||
node.Fun = &ast.Ident{
|
|
||||||
NamePos: x.NamePos,
|
|
||||||
Name: "C." + fun.Sel.Name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *ast.SelectorExpr:
|
|
||||||
x, ok := node.X.(*ast.Ident)
|
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if x.Name == "C" {
|
|
||||||
name := "C." + node.Sel.Name
|
|
||||||
if _, ok := info.functions[node.Sel.Name]; ok {
|
|
||||||
name += "$funcaddr"
|
|
||||||
}
|
|
||||||
cursor.Replace(&ast.Ident{
|
|
||||||
NamePos: x.NamePos,
|
|
||||||
Name: name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
@@ -1,446 +0,0 @@
|
|||||||
package loader
|
|
||||||
|
|
||||||
// This file parses a fragment of C with libclang and stores the result for AST
|
|
||||||
// modification. It does not touch the AST itself.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go/ast"
|
|
||||||
"go/scanner"
|
|
||||||
"go/token"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include <clang-c/Index.h> // if this fails, install libclang-8-dev
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
// This struct should be ABI-compatible on all platforms (uintptr_t has the same
|
|
||||||
// alignment etc. as void*) but does not include void* pointers that are not
|
|
||||||
// always real pointers.
|
|
||||||
// The Go garbage collector assumes that all non-nil pointer-typed integers are
|
|
||||||
// actually pointers. This is not always true, as data[1] often contains 0x1,
|
|
||||||
// which is clearly not a valid pointer. Usually the GC won't catch this issue,
|
|
||||||
// but occasionally it will leading to a crash with a vague error message.
|
|
||||||
typedef struct {
|
|
||||||
enum CXCursorKind kind;
|
|
||||||
int xdata;
|
|
||||||
uintptr_t data[3];
|
|
||||||
} GoCXCursor;
|
|
||||||
|
|
||||||
// Forwarding functions. They are implemented in libclang_stubs.c and forward to
|
|
||||||
// the real functions without doing anything else, thus they are entirely
|
|
||||||
// compatible with the versions without tinygo_ prefix. The only difference is
|
|
||||||
// the CXCursor type, which has been replaced with GoCXCursor.
|
|
||||||
GoCXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu);
|
|
||||||
unsigned tinygo_clang_visitChildren(GoCXCursor parent, CXCursorVisitor visitor, CXClientData client_data);
|
|
||||||
CXString tinygo_clang_getCursorSpelling(GoCXCursor c);
|
|
||||||
enum CXCursorKind tinygo_clang_getCursorKind(GoCXCursor c);
|
|
||||||
CXType tinygo_clang_getCursorType(GoCXCursor c);
|
|
||||||
GoCXCursor tinygo_clang_getTypeDeclaration(CXType t);
|
|
||||||
CXType tinygo_clang_getTypedefDeclUnderlyingType(GoCXCursor c);
|
|
||||||
CXType tinygo_clang_getCursorResultType(GoCXCursor c);
|
|
||||||
int tinygo_clang_Cursor_getNumArguments(GoCXCursor c);
|
|
||||||
GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
|
|
||||||
|
|
||||||
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
|
||||||
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
// refMap stores references to types, used for clang_visitChildren.
|
|
||||||
var refMap RefMap
|
|
||||||
|
|
||||||
var diagnosticSeverity = [...]string{
|
|
||||||
C.CXDiagnostic_Ignored: "ignored",
|
|
||||||
C.CXDiagnostic_Note: "note",
|
|
||||||
C.CXDiagnostic_Warning: "warning",
|
|
||||||
C.CXDiagnostic_Error: "error",
|
|
||||||
C.CXDiagnostic_Fatal: "fatal",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilename string, posLine int) []error {
|
|
||||||
index := C.clang_createIndex(0, 0)
|
|
||||||
defer C.clang_disposeIndex(index)
|
|
||||||
|
|
||||||
filenameC := C.CString(posFilename + "!cgo.c")
|
|
||||||
defer C.free(unsafe.Pointer(filenameC))
|
|
||||||
|
|
||||||
fragmentC := C.CString(fragment)
|
|
||||||
defer C.free(unsafe.Pointer(fragmentC))
|
|
||||||
|
|
||||||
unsavedFile := C.struct_CXUnsavedFile{
|
|
||||||
Filename: filenameC,
|
|
||||||
Length: C.ulong(len(fragment)),
|
|
||||||
Contents: fragmentC,
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert Go slice of strings to C array of strings.
|
|
||||||
cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0))))
|
|
||||||
defer C.free(cmdargsC)
|
|
||||||
cmdargs := (*[1 << 16]*C.char)(cmdargsC)
|
|
||||||
for i, cflag := range cflags {
|
|
||||||
s := C.CString(cflag)
|
|
||||||
cmdargs[i] = s
|
|
||||||
defer C.free(unsafe.Pointer(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
var unit C.CXTranslationUnit
|
|
||||||
errCode := C.clang_parseTranslationUnit2(
|
|
||||||
index,
|
|
||||||
filenameC,
|
|
||||||
(**C.char)(cmdargsC), C.int(len(cflags)), // command line args
|
|
||||||
&unsavedFile, 1, // unsaved files
|
|
||||||
C.CXTranslationUnit_None,
|
|
||||||
&unit)
|
|
||||||
if errCode != 0 {
|
|
||||||
panic("loader: failed to parse source with libclang")
|
|
||||||
}
|
|
||||||
defer C.clang_disposeTranslationUnit(unit)
|
|
||||||
|
|
||||||
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
|
|
||||||
errs := []error{}
|
|
||||||
addDiagnostic := func(diagnostic C.CXDiagnostic) {
|
|
||||||
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
|
|
||||||
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
|
|
||||||
location := C.clang_getDiagnosticLocation(diagnostic)
|
|
||||||
var file C.CXFile
|
|
||||||
var line C.unsigned
|
|
||||||
var column C.unsigned
|
|
||||||
var offset C.unsigned
|
|
||||||
C.clang_getExpansionLocation(location, &file, &line, &column, &offset)
|
|
||||||
filename := getString(C.clang_getFileName(file))
|
|
||||||
if filename == posFilename+"!cgo.c" {
|
|
||||||
// Adjust errors from the `import "C"` snippet.
|
|
||||||
// Note: doesn't adjust filenames inside the error message
|
|
||||||
// itself.
|
|
||||||
filename = posFilename
|
|
||||||
line += C.uint(posLine)
|
|
||||||
offset = 0 // hard to calculate
|
|
||||||
} else if filepath.IsAbs(filename) {
|
|
||||||
// Relative paths for readability, like other Go parser errors.
|
|
||||||
relpath, err := filepath.Rel(info.Program.Dir, filename)
|
|
||||||
if err == nil {
|
|
||||||
filename = relpath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
errs = append(errs, &scanner.Error{
|
|
||||||
Pos: token.Position{
|
|
||||||
Filename: filename,
|
|
||||||
Offset: int(offset),
|
|
||||||
Line: int(line),
|
|
||||||
Column: int(column),
|
|
||||||
},
|
|
||||||
Msg: severity + ": " + spelling,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
for i := 0; i < numDiagnostics; i++ {
|
|
||||||
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
|
|
||||||
addDiagnostic(diagnostic)
|
|
||||||
|
|
||||||
// Child diagnostics (like notes on redefinitions).
|
|
||||||
diagnostics := C.clang_getChildDiagnostics(diagnostic)
|
|
||||||
for j := 0; j < int(C.clang_getNumDiagnosticsInSet(diagnostics)); j++ {
|
|
||||||
addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
|
|
||||||
ref := refMap.Put(info)
|
|
||||||
defer refMap.Remove(ref)
|
|
||||||
cursor := C.tinygo_clang_getTranslationUnitCursor(unit)
|
|
||||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export tinygo_clang_globals_visitor
|
|
||||||
func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
|
||||||
info := refMap.Get(unsafe.Pointer(client_data)).(*fileInfo)
|
|
||||||
kind := C.tinygo_clang_getCursorKind(c)
|
|
||||||
switch kind {
|
|
||||||
case C.CXCursor_FunctionDecl:
|
|
||||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
|
||||||
cursorType := C.tinygo_clang_getCursorType(c)
|
|
||||||
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
|
|
||||||
return C.CXChildVisit_Continue // not supported
|
|
||||||
}
|
|
||||||
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
|
|
||||||
fn := &functionInfo{}
|
|
||||||
info.functions[name] = fn
|
|
||||||
for i := 0; i < numArgs; i++ {
|
|
||||||
arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i))
|
|
||||||
argName := getString(C.tinygo_clang_getCursorSpelling(arg))
|
|
||||||
argType := C.clang_getArgType(cursorType, C.uint(i))
|
|
||||||
if argName == "" {
|
|
||||||
argName = "$" + strconv.Itoa(i)
|
|
||||||
}
|
|
||||||
fn.args = append(fn.args, paramInfo{
|
|
||||||
name: argName,
|
|
||||||
typeExpr: info.makeASTType(argType),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
resultType := C.tinygo_clang_getCursorResultType(c)
|
|
||||||
if resultType.kind != C.CXType_Void {
|
|
||||||
fn.results = &ast.FieldList{
|
|
||||||
List: []*ast.Field{
|
|
||||||
&ast.Field{
|
|
||||||
Type: info.makeASTType(resultType),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case C.CXCursor_TypedefDecl:
|
|
||||||
typedefType := C.tinygo_clang_getCursorType(c)
|
|
||||||
name := getString(C.clang_getTypedefName(typedefType))
|
|
||||||
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
|
|
||||||
expr := info.makeASTType(underlyingType)
|
|
||||||
if strings.HasPrefix(name, "_Cgo_") {
|
|
||||||
expr := expr.(*ast.Ident)
|
|
||||||
typeSize := C.clang_Type_getSizeOf(underlyingType)
|
|
||||||
switch expr.Name {
|
|
||||||
// TODO: plain char (may be signed or unsigned)
|
|
||||||
case "C.schar", "C.short", "C.int", "C.long", "C.longlong":
|
|
||||||
switch typeSize {
|
|
||||||
case 1:
|
|
||||||
expr.Name = "int8"
|
|
||||||
case 2:
|
|
||||||
expr.Name = "int16"
|
|
||||||
case 4:
|
|
||||||
expr.Name = "int32"
|
|
||||||
case 8:
|
|
||||||
expr.Name = "int64"
|
|
||||||
}
|
|
||||||
case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong":
|
|
||||||
switch typeSize {
|
|
||||||
case 1:
|
|
||||||
expr.Name = "uint8"
|
|
||||||
case 2:
|
|
||||||
expr.Name = "uint16"
|
|
||||||
case 4:
|
|
||||||
expr.Name = "uint32"
|
|
||||||
case 8:
|
|
||||||
expr.Name = "uint64"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info.typedefs[name] = &typedefInfo{
|
|
||||||
typeExpr: expr,
|
|
||||||
}
|
|
||||||
case C.CXCursor_VarDecl:
|
|
||||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
|
||||||
cursorType := C.tinygo_clang_getCursorType(c)
|
|
||||||
info.globals[name] = &globalInfo{
|
|
||||||
typeExpr: info.makeASTType(cursorType),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
|
|
||||||
func getString(clangString C.CXString) (s string) {
|
|
||||||
rawString := C.clang_getCString(clangString)
|
|
||||||
s = C.GoString(rawString)
|
|
||||||
C.clang_disposeString(clangString)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeASTType return the ast.Expr for the given libclang type. In other words,
|
|
||||||
// it converts a libclang type to a type in the Go AST.
|
|
||||||
func (info *fileInfo) makeASTType(typ C.CXType) ast.Expr {
|
|
||||||
var typeName string
|
|
||||||
switch typ.kind {
|
|
||||||
case C.CXType_SChar:
|
|
||||||
typeName = "C.schar"
|
|
||||||
case C.CXType_UChar:
|
|
||||||
typeName = "C.uchar"
|
|
||||||
case C.CXType_Short:
|
|
||||||
typeName = "C.short"
|
|
||||||
case C.CXType_UShort:
|
|
||||||
typeName = "C.ushort"
|
|
||||||
case C.CXType_Int:
|
|
||||||
typeName = "C.int"
|
|
||||||
case C.CXType_UInt:
|
|
||||||
typeName = "C.uint"
|
|
||||||
case C.CXType_Long:
|
|
||||||
typeName = "C.long"
|
|
||||||
case C.CXType_ULong:
|
|
||||||
typeName = "C.ulong"
|
|
||||||
case C.CXType_LongLong:
|
|
||||||
typeName = "C.longlong"
|
|
||||||
case C.CXType_ULongLong:
|
|
||||||
typeName = "C.ulonglong"
|
|
||||||
case C.CXType_Bool:
|
|
||||||
typeName = "bool"
|
|
||||||
case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble:
|
|
||||||
switch C.clang_Type_getSizeOf(typ) {
|
|
||||||
case 4:
|
|
||||||
typeName = "float32"
|
|
||||||
case 8:
|
|
||||||
typeName = "float64"
|
|
||||||
default:
|
|
||||||
// Don't do anything, rely on the fallback code to show a somewhat
|
|
||||||
// sensible error message like "undeclared name: C.long double".
|
|
||||||
}
|
|
||||||
case C.CXType_Complex:
|
|
||||||
switch C.clang_Type_getSizeOf(typ) {
|
|
||||||
case 8:
|
|
||||||
typeName = "complex64"
|
|
||||||
case 16:
|
|
||||||
typeName = "complex128"
|
|
||||||
}
|
|
||||||
case C.CXType_Pointer:
|
|
||||||
return &ast.StarExpr{
|
|
||||||
Star: info.importCPos,
|
|
||||||
X: info.makeASTType(C.clang_getPointeeType(typ)),
|
|
||||||
}
|
|
||||||
case C.CXType_ConstantArray:
|
|
||||||
return &ast.ArrayType{
|
|
||||||
Lbrack: info.importCPos,
|
|
||||||
Len: &ast.BasicLit{
|
|
||||||
ValuePos: info.importCPos,
|
|
||||||
Kind: token.INT,
|
|
||||||
Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10),
|
|
||||||
},
|
|
||||||
Elt: info.makeASTType(C.clang_getElementType(typ)),
|
|
||||||
}
|
|
||||||
case C.CXType_FunctionProto:
|
|
||||||
// Be compatible with gc, which uses the *[0]byte type for function
|
|
||||||
// pointer types.
|
|
||||||
// Return type [0]byte because this is a function type, not a pointer to
|
|
||||||
// this function type.
|
|
||||||
return &ast.ArrayType{
|
|
||||||
Lbrack: info.importCPos,
|
|
||||||
Len: &ast.BasicLit{
|
|
||||||
ValuePos: info.importCPos,
|
|
||||||
Kind: token.INT,
|
|
||||||
Value: "0",
|
|
||||||
},
|
|
||||||
Elt: &ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: "byte",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case C.CXType_Typedef:
|
|
||||||
typedefName := getString(C.clang_getTypedefName(typ))
|
|
||||||
return &ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: "C." + typedefName,
|
|
||||||
}
|
|
||||||
case C.CXType_Elaborated:
|
|
||||||
underlying := C.clang_Type_getNamedType(typ)
|
|
||||||
switch underlying.kind {
|
|
||||||
case C.CXType_Record:
|
|
||||||
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
|
||||||
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
|
|
||||||
// It is possible that this is a recursive definition, for example
|
|
||||||
// in linked lists (structs contain a pointer to the next element
|
|
||||||
// of the same type). If the name exists in info.elaboratedTypes,
|
|
||||||
// it is being processed, although it may not be fully defined yet.
|
|
||||||
if _, ok := info.elaboratedTypes[name]; !ok {
|
|
||||||
info.elaboratedTypes[name] = nil // predeclare (to avoid endless recursion)
|
|
||||||
info.elaboratedTypes[name] = info.makeASTType(underlying)
|
|
||||||
}
|
|
||||||
return &ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: "C.struct_" + name,
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic("unknown elaborated type")
|
|
||||||
}
|
|
||||||
case C.CXType_Record:
|
|
||||||
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
|
||||||
fieldList := &ast.FieldList{
|
|
||||||
Opening: info.importCPos,
|
|
||||||
Closing: info.importCPos,
|
|
||||||
}
|
|
||||||
ref := refMap.Put(struct {
|
|
||||||
fieldList *ast.FieldList
|
|
||||||
info *fileInfo
|
|
||||||
}{fieldList, info})
|
|
||||||
defer refMap.Remove(ref)
|
|
||||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
|
|
||||||
switch C.tinygo_clang_getCursorKind(cursor) {
|
|
||||||
case C.CXCursor_StructDecl:
|
|
||||||
return &ast.StructType{
|
|
||||||
Struct: info.importCPos,
|
|
||||||
Fields: fieldList,
|
|
||||||
}
|
|
||||||
case C.CXCursor_UnionDecl:
|
|
||||||
if len(fieldList.List) > 1 {
|
|
||||||
// Insert a special field at the front (of zero width) as a
|
|
||||||
// marker that this is struct is actually a union. This is done
|
|
||||||
// by giving the field a name that cannot be expressed directly
|
|
||||||
// in Go.
|
|
||||||
// Other parts of the compiler look at the first element in a
|
|
||||||
// struct (of size > 2) to know whether this is a union.
|
|
||||||
// Note that we don't have to insert it for single-element
|
|
||||||
// unions as they're basically equivalent to a struct.
|
|
||||||
unionMarker := &ast.Field{
|
|
||||||
Type: &ast.StructType{
|
|
||||||
Struct: info.importCPos,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
unionMarker.Names = []*ast.Ident{
|
|
||||||
&ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: "C union",
|
|
||||||
Obj: &ast.Object{
|
|
||||||
Kind: ast.Var,
|
|
||||||
Name: "C union",
|
|
||||||
Decl: unionMarker,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
|
|
||||||
}
|
|
||||||
return &ast.StructType{
|
|
||||||
Struct: info.importCPos,
|
|
||||||
Fields: fieldList,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if typeName == "" {
|
|
||||||
// Fallback, probably incorrect but at least the error points to an odd
|
|
||||||
// type name.
|
|
||||||
typeName = "C." + getString(C.clang_getTypeSpelling(typ))
|
|
||||||
}
|
|
||||||
return &ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: typeName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//export tinygo_clang_struct_visitor
|
|
||||||
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
|
||||||
passed := refMap.Get(unsafe.Pointer(client_data)).(struct {
|
|
||||||
fieldList *ast.FieldList
|
|
||||||
info *fileInfo
|
|
||||||
})
|
|
||||||
fieldList := passed.fieldList
|
|
||||||
info := passed.info
|
|
||||||
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
|
|
||||||
panic("expected field inside cursor")
|
|
||||||
}
|
|
||||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
|
||||||
typ := C.tinygo_clang_getCursorType(c)
|
|
||||||
field := &ast.Field{
|
|
||||||
Type: info.makeASTType(typ),
|
|
||||||
}
|
|
||||||
field.Names = []*ast.Ident{
|
|
||||||
&ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: name,
|
|
||||||
Obj: &ast.Object{
|
|
||||||
Kind: ast.Var,
|
|
||||||
Name: name,
|
|
||||||
Decl: field,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
fieldList.List = append(fieldList.List, field)
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
+17
-4
@@ -10,6 +10,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/cgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Program holds all packages and some metadata about the program as a whole.
|
// Program holds all packages and some metadata about the program as a whole.
|
||||||
@@ -22,6 +24,7 @@ type Program struct {
|
|||||||
fset *token.FileSet
|
fset *token.FileSet
|
||||||
TypeChecker types.Config
|
TypeChecker types.Config
|
||||||
Dir string // current working directory (for error reporting)
|
Dir string // current working directory (for error reporting)
|
||||||
|
TINYGOROOT string // root of the TinyGo installation or root of the source code
|
||||||
CFlags []string
|
CFlags []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,12 +302,22 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
|
|||||||
fileErrs = append(fileErrs, err)
|
fileErrs = append(fileErrs, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
errs := p.processCgo(path, f, append(p.CFlags, "-I"+p.Package.Dir))
|
files = append(files, f)
|
||||||
|
}
|
||||||
|
if len(p.CgoFiles) != 0 {
|
||||||
|
clangIncludes := ""
|
||||||
|
if _, err := os.Stat(filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")); !os.IsNotExist(err) {
|
||||||
|
// Running from the source directory.
|
||||||
|
clangIncludes = filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")
|
||||||
|
} else {
|
||||||
|
// Running from the installation directory.
|
||||||
|
clangIncludes = filepath.Join(p.TINYGOROOT, "lib", "clang", "include")
|
||||||
|
}
|
||||||
|
generated, errs := cgo.Process(files, p.Program.Dir, p.fset, append(p.CFlags, "-I"+p.Package.Dir, "-I"+clangIncludes))
|
||||||
if errs != nil {
|
if errs != nil {
|
||||||
fileErrs = append(fileErrs, errs...)
|
fileErrs = append(fileErrs, errs...)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
files = append(files, f)
|
files = append(files, generated)
|
||||||
}
|
}
|
||||||
if len(fileErrs) != 0 {
|
if len(fileErrs) != 0 {
|
||||||
return nil, Errors{p, fileErrs}
|
return nil, Errors{p, fileErrs}
|
||||||
@@ -333,7 +346,7 @@ func (p *Package) importRecursively() error {
|
|||||||
p.Importing = true
|
p.Importing = true
|
||||||
for _, to := range p.Package.Imports {
|
for _, to := range p.Package.Imports {
|
||||||
if to == "C" {
|
if to == "C" {
|
||||||
// Do Cgo processing in a later stage.
|
// Do CGo processing in a later stage.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, ok := p.Imports[to]; ok {
|
if _, ok := p.Imports[to]; ok {
|
||||||
|
|||||||
@@ -33,9 +33,20 @@ func (e *commandError) Error() string {
|
|||||||
return e.Msg + " " + e.File + ": " + e.Err.Error()
|
return e.Msg + " " + e.File + ": " + e.Err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// multiError is a list of multiple errors (actually: diagnostics) returned
|
||||||
|
// during LLVM IR generation.
|
||||||
|
type multiError struct {
|
||||||
|
Errs []error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *multiError) Error() string {
|
||||||
|
return e.Errs[0].Error()
|
||||||
|
}
|
||||||
|
|
||||||
type BuildConfig struct {
|
type BuildConfig struct {
|
||||||
opt string
|
opt string
|
||||||
gc string
|
gc string
|
||||||
|
panicStrategy string
|
||||||
printIR bool
|
printIR bool
|
||||||
dumpSSA bool
|
dumpSSA bool
|
||||||
debug bool
|
debug bool
|
||||||
@@ -51,21 +62,37 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
|||||||
config.gc = spec.GC
|
config.gc = spec.GC
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append command line passed CFlags and LDFlags
|
root := sourceDir()
|
||||||
spec.CFlags = append(spec.CFlags, config.cFlags...)
|
|
||||||
spec.LDFlags = append(spec.LDFlags, config.ldFlags...)
|
|
||||||
|
|
||||||
|
// Merge and adjust CFlags.
|
||||||
|
cflags := append([]string{}, config.cFlags...)
|
||||||
|
for _, flag := range spec.CFlags {
|
||||||
|
cflags = append(cflags, strings.Replace(flag, "{root}", root, -1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge and adjust LDFlags.
|
||||||
|
ldflags := append([]string{}, config.ldFlags...)
|
||||||
|
for _, flag := range spec.LDFlags {
|
||||||
|
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
|
||||||
|
}
|
||||||
|
|
||||||
|
goroot := getGoroot()
|
||||||
|
if goroot == "" {
|
||||||
|
return errors.New("cannot locate $GOROOT, please set it manually")
|
||||||
|
}
|
||||||
compilerConfig := compiler.Config{
|
compilerConfig := compiler.Config{
|
||||||
Triple: spec.Triple,
|
Triple: spec.Triple,
|
||||||
CPU: spec.CPU,
|
CPU: spec.CPU,
|
||||||
GOOS: spec.GOOS,
|
GOOS: spec.GOOS,
|
||||||
GOARCH: spec.GOARCH,
|
GOARCH: spec.GOARCH,
|
||||||
GC: config.gc,
|
GC: config.gc,
|
||||||
CFlags: spec.CFlags,
|
PanicStrategy: config.panicStrategy,
|
||||||
LDFlags: spec.LDFlags,
|
CFlags: cflags,
|
||||||
|
LDFlags: ldflags,
|
||||||
Debug: config.debug,
|
Debug: config.debug,
|
||||||
DumpSSA: config.dumpSSA,
|
DumpSSA: config.dumpSSA,
|
||||||
RootDir: sourceDir(),
|
TINYGOROOT: root,
|
||||||
|
GOROOT: goroot,
|
||||||
GOPATH: getGopath(),
|
GOPATH: getGopath(),
|
||||||
BuildTags: spec.BuildTags,
|
BuildTags: spec.BuildTags,
|
||||||
}
|
}
|
||||||
@@ -75,12 +102,15 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compile Go code to IR.
|
// Compile Go code to IR.
|
||||||
err = c.Compile(pkgName)
|
errs := c.Compile(pkgName)
|
||||||
if err != nil {
|
if len(errs) != 0 {
|
||||||
return err
|
if len(errs) == 1 {
|
||||||
|
return errs[0]
|
||||||
|
}
|
||||||
|
return &multiError{errs}
|
||||||
}
|
}
|
||||||
if config.printIR {
|
if config.printIR {
|
||||||
fmt.Println("Generated LLVM IR:")
|
fmt.Println("; Generated LLVM IR:")
|
||||||
fmt.Println(c.IR())
|
fmt.Println(c.IR())
|
||||||
}
|
}
|
||||||
if err := c.Verify(); err != nil {
|
if err := c.Verify(); err != nil {
|
||||||
@@ -191,19 +221,20 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
|||||||
// Prepare link command.
|
// Prepare link command.
|
||||||
executable := filepath.Join(dir, "main")
|
executable := filepath.Join(dir, "main")
|
||||||
tmppath := executable // final file
|
tmppath := executable // final file
|
||||||
ldflags := append(spec.LDFlags, "-o", executable, objfile, "-L", sourceDir())
|
ldflags := append(ldflags, "-o", executable, objfile, "-L", root)
|
||||||
if spec.RTLib == "compiler-rt" {
|
if spec.RTLib == "compiler-rt" {
|
||||||
ldflags = append(ldflags, librt)
|
ldflags = append(ldflags, librt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile extra files.
|
// Compile extra files.
|
||||||
for i, path := range spec.ExtraFiles {
|
for i, path := range spec.ExtraFiles {
|
||||||
|
abspath := filepath.Join(root, path)
|
||||||
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
|
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
|
||||||
cmd := exec.Command(spec.Compiler, append(spec.CFlags, "-c", "-o", outpath, path)...)
|
cmdNames := []string{spec.Compiler}
|
||||||
cmd.Stdout = os.Stdout
|
if names, ok := commands[spec.Compiler]; ok {
|
||||||
cmd.Stderr = os.Stderr
|
cmdNames = names
|
||||||
cmd.Dir = sourceDir()
|
}
|
||||||
err := cmd.Run()
|
err := execCommand(cmdNames, append(cflags, "-c", "-o", outpath, abspath)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &commandError{"failed to build", path, err}
|
return &commandError{"failed to build", path, err}
|
||||||
}
|
}
|
||||||
@@ -215,11 +246,11 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
|||||||
for _, file := range pkg.CFiles {
|
for _, file := range pkg.CFiles {
|
||||||
path := filepath.Join(pkg.Package.Dir, file)
|
path := filepath.Join(pkg.Package.Dir, file)
|
||||||
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o")
|
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o")
|
||||||
cmd := exec.Command(spec.Compiler, append(spec.CFlags, "-c", "-o", outpath, path)...)
|
cmdNames := []string{spec.Compiler}
|
||||||
cmd.Stdout = os.Stdout
|
if names, ok := commands[spec.Compiler]; ok {
|
||||||
cmd.Stderr = os.Stderr
|
cmdNames = names
|
||||||
cmd.Dir = sourceDir()
|
}
|
||||||
err := cmd.Run()
|
err := execCommand(cmdNames, append(cflags, "-c", "-o", outpath, path)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &commandError{"failed to build", path, err}
|
return &commandError{"failed to build", path, err}
|
||||||
}
|
}
|
||||||
@@ -228,11 +259,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Link the object files together.
|
// Link the object files together.
|
||||||
if linker, ok := commands[spec.Linker]; ok {
|
|
||||||
err = Link(linker, ldflags...)
|
|
||||||
} else {
|
|
||||||
err = Link(spec.Linker, ldflags...)
|
err = Link(spec.Linker, ldflags...)
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &commandError{"failed to link", executable, err}
|
return &commandError{"failed to link", executable, err}
|
||||||
}
|
}
|
||||||
@@ -496,6 +523,10 @@ func handleCompilerError(err error) {
|
|||||||
for _, err := range errLoader.Errs {
|
for _, err := range errLoader.Errs {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
}
|
}
|
||||||
|
} else if errMulti, ok := err.(*multiError); ok {
|
||||||
|
for _, err := range errMulti.Errs {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintln(os.Stderr, "error:", err)
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
}
|
}
|
||||||
@@ -506,7 +537,8 @@ func handleCompilerError(err error) {
|
|||||||
func main() {
|
func main() {
|
||||||
outpath := flag.String("o", "", "output filename")
|
outpath := flag.String("o", "", "output filename")
|
||||||
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
|
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
|
||||||
gc := flag.String("gc", "", "garbage collector to use (none, dumb, marksweep)")
|
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
|
||||||
|
panicStrategy := flag.String("panic", "print", "panic strategy (abort, trap)")
|
||||||
printIR := flag.Bool("printir", false, "print LLVM IR")
|
printIR := flag.Bool("printir", false, "print LLVM IR")
|
||||||
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
|
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
|
||||||
target := flag.String("target", "", "LLVM target")
|
target := flag.String("target", "", "LLVM target")
|
||||||
@@ -529,6 +561,7 @@ func main() {
|
|||||||
config := &BuildConfig{
|
config := &BuildConfig{
|
||||||
opt: *opt,
|
opt: *opt,
|
||||||
gc: *gc,
|
gc: *gc,
|
||||||
|
panicStrategy: *panicStrategy,
|
||||||
printIR: *printIR,
|
printIR: *printIR,
|
||||||
dumpSSA: *dumpSSA,
|
dumpSSA: *dumpSSA,
|
||||||
debug: !*nodebug,
|
debug: !*nodebug,
|
||||||
@@ -544,6 +577,12 @@ func main() {
|
|||||||
config.ldFlags = strings.Split(*ldFlags, " ")
|
config.ldFlags = strings.Split(*ldFlags, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *panicStrategy != "print" && *panicStrategy != "trap" {
|
||||||
|
fmt.Fprintln(os.Stderr, "Panic strategy must be either print or trap.")
|
||||||
|
usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
os.Setenv("CC", "clang -target="+*target)
|
os.Setenv("CC", "clang -target="+*target)
|
||||||
|
|
||||||
switch command {
|
switch command {
|
||||||
|
|||||||
+17
-6
@@ -13,17 +13,19 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/loader"
|
||||||
)
|
)
|
||||||
|
|
||||||
const TESTDATA = "testdata"
|
const TESTDATA = "testdata"
|
||||||
|
|
||||||
func TestCompiler(t *testing.T) {
|
func TestCompiler(t *testing.T) {
|
||||||
matches, err := filepath.Glob(TESTDATA + "/*.go")
|
matches, err := filepath.Glob(filepath.Join(TESTDATA, "*.go"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("could not read test files:", err)
|
t.Fatal("could not read test files:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dirMatches, err := filepath.Glob(TESTDATA + "/*/main.go")
|
dirMatches, err := filepath.Glob(filepath.Join(TESTDATA, "*", "main.go"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("could not read test packages:", err)
|
t.Fatal("could not read test packages:", err)
|
||||||
}
|
}
|
||||||
@@ -56,6 +58,9 @@ func TestCompiler(t *testing.T) {
|
|||||||
|
|
||||||
t.Log("running tests for emulated cortex-m3...")
|
t.Log("running tests for emulated cortex-m3...")
|
||||||
for _, path := range matches {
|
for _, path := range matches {
|
||||||
|
if path == "testdata/reflect.go" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
t.Run(path, func(t *testing.T) {
|
t.Run(path, func(t *testing.T) {
|
||||||
runTest(path, tmpdir, "qemu", t)
|
runTest(path, tmpdir, "qemu", t)
|
||||||
})
|
})
|
||||||
@@ -64,7 +69,7 @@ func TestCompiler(t *testing.T) {
|
|||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
t.Log("running tests for linux/arm...")
|
t.Log("running tests for linux/arm...")
|
||||||
for _, path := range matches {
|
for _, path := range matches {
|
||||||
if path == "testdata/cgo/" {
|
if path == filepath.Join("testdata", "cgo")+string(filepath.Separator) {
|
||||||
continue // TODO: improve CGo
|
continue // TODO: improve CGo
|
||||||
}
|
}
|
||||||
t.Run(path, func(t *testing.T) {
|
t.Run(path, func(t *testing.T) {
|
||||||
@@ -74,7 +79,7 @@ func TestCompiler(t *testing.T) {
|
|||||||
|
|
||||||
t.Log("running tests for linux/arm64...")
|
t.Log("running tests for linux/arm64...")
|
||||||
for _, path := range matches {
|
for _, path := range matches {
|
||||||
if path == "testdata/cgo/" {
|
if path == filepath.Join("testdata", "cgo")+string(filepath.Separator) {
|
||||||
continue // TODO: improve CGo
|
continue // TODO: improve CGo
|
||||||
}
|
}
|
||||||
t.Run(path, func(t *testing.T) {
|
t.Run(path, func(t *testing.T) {
|
||||||
@@ -84,7 +89,7 @@ func TestCompiler(t *testing.T) {
|
|||||||
|
|
||||||
t.Log("running tests for WebAssembly...")
|
t.Log("running tests for WebAssembly...")
|
||||||
for _, path := range matches {
|
for _, path := range matches {
|
||||||
if path == "testdata/gc.go" {
|
if path == filepath.Join("testdata", "gc.go") {
|
||||||
continue // known to fail
|
continue // known to fail
|
||||||
}
|
}
|
||||||
t.Run(path, func(t *testing.T) {
|
t.Run(path, func(t *testing.T) {
|
||||||
@@ -97,7 +102,7 @@ func TestCompiler(t *testing.T) {
|
|||||||
func runTest(path, tmpdir string, target string, t *testing.T) {
|
func runTest(path, tmpdir string, target string, t *testing.T) {
|
||||||
// Get the expected output for this test.
|
// Get the expected output for this test.
|
||||||
txtpath := path[:len(path)-3] + ".txt"
|
txtpath := path[:len(path)-3] + ".txt"
|
||||||
if path[len(path)-1] == '/' {
|
if path[len(path)-1] == os.PathSeparator {
|
||||||
txtpath = path + "out.txt"
|
txtpath = path + "out.txt"
|
||||||
}
|
}
|
||||||
f, err := os.Open(txtpath)
|
f, err := os.Open(txtpath)
|
||||||
@@ -121,7 +126,13 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
|
|||||||
binary := filepath.Join(tmpdir, "test")
|
binary := filepath.Join(tmpdir, "test")
|
||||||
err = Build("./"+path, binary, target, config)
|
err = Build("./"+path, binary, target, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errLoader, ok := err.(loader.Errors); ok {
|
||||||
|
for _, err := range errLoader.Errs {
|
||||||
t.Log("failed to build:", err)
|
t.Log("failed to build:", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Log("failed to build:", err)
|
||||||
|
}
|
||||||
t.Fail()
|
t.Fail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Example using the i2s hardware interface on the Adafruit Circuit Playground Express
|
||||||
|
// to read data from the onboard MEMS microphone.
|
||||||
|
//
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
machine.I2S0.Configure(machine.I2SConfig{
|
||||||
|
Mode: machine.I2SModePDM,
|
||||||
|
ClockSource: machine.I2SClockSourceExternal,
|
||||||
|
Stereo: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
data := make([]uint32, 64)
|
||||||
|
|
||||||
|
for {
|
||||||
|
// get the next group of samples
|
||||||
|
machine.I2S0.Read(data)
|
||||||
|
|
||||||
|
println("data", data[0], data[1], data[2], data[4], "...")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// blink program for the BBC micro:bit that uses the entire LED matrix
|
// blink program for the BBC micro:bit
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -6,14 +6,19 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// The LED matrix in the micro:bit is a multiplexed display: https://en.wikipedia.org/wiki/Multiplexed_display
|
||||||
|
// Driver for easier control: https://github.com/tinygo-org/drivers/tree/master/microbitmatrix
|
||||||
func main() {
|
func main() {
|
||||||
machine.InitLEDMatrix()
|
ledrow := machine.GPIO{machine.LED_ROW_1}
|
||||||
|
ledrow.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
|
ledcol := machine.GPIO{machine.LED_COL_1}
|
||||||
|
ledcol.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
|
ledcol.Low()
|
||||||
for {
|
for {
|
||||||
machine.ClearLEDMatrix()
|
ledrow.Low()
|
||||||
time.Sleep(time.Millisecond * 500)
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
|
||||||
machine.SetEntireLEDMatrixOn()
|
ledrow.High()
|
||||||
time.Sleep(time.Millisecond * 500)
|
time.Sleep(time.Millisecond * 500)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-16
@@ -1,6 +1,6 @@
|
|||||||
# TinyGo WebAssembly examples
|
# TinyGo WebAssembly examples
|
||||||
|
|
||||||
The examples here show two different ways of using WebAssembly with TinyGo;
|
The examples here show two different ways of using WebAssembly with TinyGo:
|
||||||
|
|
||||||
1. Defining and exporting functions via the `//go:export <name>` directive. See
|
1. Defining and exporting functions via the `//go:export <name>` directive. See
|
||||||
[the export folder](./export) for an example of this.
|
[the export folder](./export) for an example of this.
|
||||||
@@ -31,23 +31,31 @@ $ make main
|
|||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
Start the local webserver:
|
Start the local web server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ go run main.go
|
$ go run main.go
|
||||||
Serving ./html on http://localhost:8080
|
Serving ./html on http://localhost:8080
|
||||||
```
|
```
|
||||||
|
|
||||||
`fmt.Println` prints to the browser console.
|
Use your web browser to visit http://localhost:8080.
|
||||||
|
|
||||||
|
* The wasm "export" example displays a simple math equation using HTML, with
|
||||||
|
the result calculated dynamically using WebAssembly. Changing any of the
|
||||||
|
values on the left hand side triggers the exported wasm `update` function to
|
||||||
|
recalculate the result.
|
||||||
|
* The wasm "main" example uses `println` to write to your browser JavaScript
|
||||||
|
console. You may need to open the browser development tools console to see it.
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
Execution of the contents require a few JS helper functions which are called
|
Execution of the contents require a few JavaScript helper functions which are
|
||||||
from WebAssembly. We have defined these in
|
called from WebAssembly.
|
||||||
[wasm_exec.js](../../../targets/wasm_exec.js). It is based on
|
|
||||||
`$GOROOT/misc/wasm/wasm_exec.js` from the standard library, but is slightly
|
We have defined these in [wasm_exec.js](../../../targets/wasm_exec.js). It is
|
||||||
different. Ensure you are using the same version of `wasm_exec.js` as the
|
based on `$GOROOT/misc/wasm/wasm_exec.js` from the standard library, but is
|
||||||
version of `tinygo` you are using to compile.
|
slightly different. Ensure you are using the same version of `wasm_exec.js` as
|
||||||
|
the version of `tinygo` you are using to compile.
|
||||||
|
|
||||||
The general steps required to run the WebAssembly file in the browser includes
|
The general steps required to run the WebAssembly file in the browser includes
|
||||||
loading it into JavaScript with `WebAssembly.instantiateStreaming`, or
|
loading it into JavaScript with `WebAssembly.instantiateStreaming`, or
|
||||||
@@ -80,8 +88,9 @@ If you have used explicit exports, you can call them by invoking them under the
|
|||||||
`wasm.exports` namespace. See the [`export`](./export/wasm.js) directory for an
|
`wasm.exports` namespace. See the [`export`](./export/wasm.js) directory for an
|
||||||
example of this.
|
example of this.
|
||||||
|
|
||||||
In addition to this piece of JavaScript, it is important that the file is served
|
In addition to the JavaScript, it is important the wasm file is served with the
|
||||||
with the correct `Content-Type` header set.
|
[`Content-Type`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type)
|
||||||
|
header set to `application/wasm`. Without it, most browsers won't run it.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
@@ -98,14 +107,25 @@ func main() {
|
|||||||
fs := http.FileServer(http.Dir(dir))
|
fs := http.FileServer(http.Dir(dir))
|
||||||
log.Print("Serving " + dir + " on http://localhost:8080")
|
log.Print("Serving " + dir + " on http://localhost:8080")
|
||||||
http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
resp.Header().Add("Cache-Control", "no-cache")
|
||||||
if strings.HasSuffix(req.URL.Path, ".wasm") {
|
if strings.HasSuffix(req.URL.Path, ".wasm") {
|
||||||
resp.Header().Set("content-type", "application/wasm")
|
resp.Header().Set("content-type", "application/wasm")
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.ServeHTTP(resp, req)
|
fs.ServeHTTP(resp, req)
|
||||||
}))
|
}))}
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This simple server serves anything inside the `./html` directory on port `8080`,
|
This simple server serves anything inside the `./html` directory on port
|
||||||
setting any `*.wasm` files `Content-Type` header appropriately.
|
`8080`, setting any `*.wasm` files `Content-Type` header appropriately.
|
||||||
|
|
||||||
|
For development purposes (**only!**), it also sets the `Cache-Control` header
|
||||||
|
so your browser doesn't cache the files. This is useful while developing, to
|
||||||
|
ensure your browser displays the newest wasm when you recompile.
|
||||||
|
|
||||||
|
In a production environment you **probably wouldn't** want to set the
|
||||||
|
`Cache-Control` header like this. Caching is generally beneficial for end
|
||||||
|
users.
|
||||||
|
|
||||||
|
Further information on the `Cache-Control` header can be found here:
|
||||||
|
|
||||||
|
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println("Hello world!")
|
println("Hello world!")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ func main() {
|
|||||||
fs := http.FileServer(http.Dir(dir))
|
fs := http.FileServer(http.Dir(dir))
|
||||||
log.Print("Serving " + dir + " on http://localhost:8080")
|
log.Print("Serving " + dir + " on http://localhost:8080")
|
||||||
http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
resp.Header().Add("Cache-Control", "no-cache")
|
||||||
if strings.HasSuffix(req.URL.Path, ".wasm") {
|
if strings.HasSuffix(req.URL.Path, ".wasm") {
|
||||||
resp.Header().Set("content-type", "application/wasm")
|
resp.Header().Set("content-type", "application/wasm")
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.ServeHTTP(resp, req)
|
fs.ServeHTTP(resp, req)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,3 +99,15 @@ const (
|
|||||||
var (
|
var (
|
||||||
SPI0 = SPI{Bus: sam.SERCOM3_SPI}
|
SPI0 = SPI{Bus: sam.SERCOM3_SPI}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// I2S pins
|
||||||
|
const (
|
||||||
|
I2S_SCK_PIN = PA10
|
||||||
|
I2S_SD_PIN = PA08
|
||||||
|
I2S_WS_PIN = 0xff // no WS, instead uses SCK to sync
|
||||||
|
)
|
||||||
|
|
||||||
|
// I2S on the Circuit Playground Express.
|
||||||
|
var (
|
||||||
|
I2S0 = I2S{Bus: sam.I2S}
|
||||||
|
)
|
||||||
|
|||||||
@@ -73,3 +73,15 @@ const (
|
|||||||
var (
|
var (
|
||||||
SPI0 = SPI{Bus: sam.SERCOM4_SPI}
|
SPI0 = SPI{Bus: sam.SERCOM4_SPI}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// I2S pins
|
||||||
|
const (
|
||||||
|
I2S_SCK_PIN = PA10
|
||||||
|
I2S_SD_PIN = PA08
|
||||||
|
I2S_WS_PIN = 0xff // TODO: figure out what this is on ItsyBitsy M0.
|
||||||
|
)
|
||||||
|
|
||||||
|
// I2S on the ItsyBitsy M0.
|
||||||
|
var (
|
||||||
|
I2S0 = I2S{Bus: sam.I2S}
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
import (
|
|
||||||
"device/nrf"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The micro:bit does not have a 32kHz crystal on board.
|
// The micro:bit does not have a 32kHz crystal on board.
|
||||||
const HasLowFrequencyCrystal = false
|
const HasLowFrequencyCrystal = false
|
||||||
|
|
||||||
@@ -79,65 +74,3 @@ const (
|
|||||||
LED_ROW_2 = 14
|
LED_ROW_2 = 14
|
||||||
LED_ROW_3 = 15
|
LED_ROW_3 = 15
|
||||||
)
|
)
|
||||||
|
|
||||||
// matrixSettings has the legs of the LED grid in the form {row, column} for each LED position.
|
|
||||||
var matrixSettings = [5][5][2]uint8{
|
|
||||||
{{LED_ROW_1, LED_COL_1}, {LED_ROW_2, LED_COL_4}, {LED_ROW_1, LED_COL_2}, {LED_ROW_2, LED_COL_5}, {LED_ROW_1, LED_COL_3}},
|
|
||||||
{{LED_ROW_3, LED_COL_4}, {LED_ROW_3, LED_COL_5}, {LED_ROW_3, LED_COL_6}, {LED_ROW_3, LED_COL_7}, {LED_ROW_3, LED_COL_8}},
|
|
||||||
{{LED_ROW_2, LED_COL_2}, {LED_ROW_1, LED_COL_9}, {LED_ROW_2, LED_COL_3}, {LED_ROW_3, LED_COL_9}, {LED_ROW_2, LED_COL_1}},
|
|
||||||
{{LED_ROW_1, LED_COL_8}, {LED_ROW_1, LED_COL_7}, {LED_ROW_1, LED_COL_6}, {LED_ROW_1, LED_COL_5}, {LED_ROW_1, LED_COL_4}},
|
|
||||||
{{LED_ROW_3, LED_COL_3}, {LED_ROW_2, LED_COL_7}, {LED_ROW_3, LED_COL_1}, {LED_ROW_2, LED_COL_6}, {LED_ROW_3, LED_COL_2}}}
|
|
||||||
|
|
||||||
// InitLEDMatrix initializes the LED matrix, by setting all of the row/col pins to output
|
|
||||||
// then calling ClearLEDMatrix.
|
|
||||||
func InitLEDMatrix() {
|
|
||||||
set := 0
|
|
||||||
for i := LED_COL_1; i <= LED_ROW_3; i++ {
|
|
||||||
set |= 1 << uint8(i)
|
|
||||||
}
|
|
||||||
nrf.GPIO.DIRSET = nrf.RegValue(set)
|
|
||||||
ClearLEDMatrix()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearLEDMatrix clears the entire LED matrix.
|
|
||||||
func ClearLEDMatrix() {
|
|
||||||
set := 0
|
|
||||||
for i := LED_COL_1; i <= LED_COL_9; i++ {
|
|
||||||
set |= 1 << uint8(i)
|
|
||||||
}
|
|
||||||
nrf.GPIO.OUTSET = nrf.RegValue(set)
|
|
||||||
nrf.GPIO.OUTCLR = (1 << LED_ROW_1) | (1 << LED_ROW_2) | (1 << LED_ROW_3)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLEDMatrix turns on a single LED on the LED matrix.
|
|
||||||
// Currently limited to a single LED at a time, it will clear the matrix before setting it.
|
|
||||||
func SetLEDMatrix(x, y uint8) error {
|
|
||||||
if x > 4 || y > 4 {
|
|
||||||
return errors.New("Invalid LED matrix row or column")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear matrix
|
|
||||||
ClearLEDMatrix()
|
|
||||||
|
|
||||||
nrf.GPIO.OUTSET = (1 << matrixSettings[y][x][0])
|
|
||||||
nrf.GPIO.OUTCLR = (1 << matrixSettings[y][x][1])
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetEntireLEDMatrixOn turns on all of the LEDs on the LED matrix.
|
|
||||||
func SetEntireLEDMatrixOn() error {
|
|
||||||
set := 0
|
|
||||||
for i := LED_ROW_1; i <= LED_ROW_3; i++ {
|
|
||||||
set |= 1 << uint8(i)
|
|
||||||
}
|
|
||||||
nrf.GPIO.OUTSET = nrf.RegValue(set)
|
|
||||||
|
|
||||||
set = 0
|
|
||||||
for i := LED_COL_1; i <= LED_COL_9; i++ {
|
|
||||||
set |= 1 << uint8(i)
|
|
||||||
}
|
|
||||||
nrf.GPIO.OUTCLR = nrf.RegValue(set)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// +build sam
|
||||||
|
|
||||||
|
// This is the definition for I2S bus functions.
|
||||||
|
// Actual implementations if available for any given hardware
|
||||||
|
// are to be found in its the board definition.
|
||||||
|
//
|
||||||
|
// For more info about I2S, see: https://en.wikipedia.org/wiki/I%C2%B2S
|
||||||
|
//
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
type I2SMode uint8
|
||||||
|
type I2SStandard uint8
|
||||||
|
type I2SClockSource uint8
|
||||||
|
type I2SDataFormat uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
I2SModeMaster I2SMode = iota
|
||||||
|
I2SModeSlave
|
||||||
|
I2SModePDM
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
I2StandardPhilips I2SStandard = iota
|
||||||
|
I2SStandardMSB
|
||||||
|
I2SStandardLSB
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
I2SClockSourceInternal I2SClockSource = iota
|
||||||
|
I2SClockSourceExternal
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
I2SDataFormatDefault I2SDataFormat = 0
|
||||||
|
I2SDataFormat8bit = 8
|
||||||
|
I2SDataFormat16bit = 16
|
||||||
|
I2SDataFormat24bit = 24
|
||||||
|
I2SDataFormat32bit = 32
|
||||||
|
)
|
||||||
|
|
||||||
|
// All fields are optional and may not be required or used on a particular platform.
|
||||||
|
type I2SConfig struct {
|
||||||
|
SCK uint8
|
||||||
|
WS uint8
|
||||||
|
SD uint8
|
||||||
|
Mode I2SMode
|
||||||
|
Standard I2SStandard
|
||||||
|
ClockSource I2SClockSource
|
||||||
|
DataFormat I2SDataFormat
|
||||||
|
AudioFrequency uint32
|
||||||
|
MasterClockOutput bool
|
||||||
|
Stereo bool
|
||||||
|
}
|
||||||
@@ -10,15 +10,15 @@ import (
|
|||||||
func (p GPIO) Configure(config GPIOConfig) {
|
func (p GPIO) Configure(config GPIOConfig) {
|
||||||
if config.Mode == GPIO_OUTPUT { // set output bit
|
if config.Mode == GPIO_OUTPUT { // set output bit
|
||||||
if p.Pin < 8 {
|
if p.Pin < 8 {
|
||||||
*avr.DDRD |= 1 << p.Pin
|
avr.DDRD.SetBits(1 << p.Pin)
|
||||||
} else {
|
} else {
|
||||||
*avr.DDRB |= 1 << (p.Pin - 8)
|
avr.DDRB.SetBits(1 << (p.Pin - 8))
|
||||||
}
|
}
|
||||||
} else { // configure input: clear output bit
|
} else { // configure input: clear output bit
|
||||||
if p.Pin < 8 {
|
if p.Pin < 8 {
|
||||||
*avr.DDRD &^= 1 << p.Pin
|
avr.DDRD.ClearBits(1 << p.Pin)
|
||||||
} else {
|
} else {
|
||||||
*avr.DDRB &^= 1 << (p.Pin - 8)
|
avr.DDRB.ClearBits(1 << (p.Pin - 8))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,15 +26,15 @@ func (p GPIO) Configure(config GPIOConfig) {
|
|||||||
// Get returns the current value of a GPIO pin.
|
// Get returns the current value of a GPIO pin.
|
||||||
func (p GPIO) Get() bool {
|
func (p GPIO) Get() bool {
|
||||||
if p.Pin < 8 {
|
if p.Pin < 8 {
|
||||||
val := *avr.PIND & (1 << p.Pin)
|
val := avr.PIND.Get() & (1 << p.Pin)
|
||||||
return (val > 0)
|
return (val > 0)
|
||||||
} else {
|
} else {
|
||||||
val := *avr.PINB & (1 << (p.Pin - 8))
|
val := avr.PINB.Get() & (1 << (p.Pin - 8))
|
||||||
return (val > 0)
|
return (val > 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p GPIO) getPortMask() (*avr.RegValue, uint8) {
|
func (p GPIO) getPortMask() (*avr.Register8, uint8) {
|
||||||
if p.Pin < 8 {
|
if p.Pin < 8 {
|
||||||
return avr.PORTD, 1 << p.Pin
|
return avr.PORTD, 1 << p.Pin
|
||||||
} else {
|
} else {
|
||||||
@@ -45,64 +45,64 @@ func (p GPIO) getPortMask() (*avr.RegValue, uint8) {
|
|||||||
// InitPWM initializes the registers needed for PWM.
|
// InitPWM initializes the registers needed for PWM.
|
||||||
func InitPWM() {
|
func InitPWM() {
|
||||||
// use waveform generation
|
// use waveform generation
|
||||||
*avr.TCCR0A |= avr.TCCR0A_WGM00
|
avr.TCCR0A.SetBits(avr.TCCR0A_WGM00)
|
||||||
|
|
||||||
// set timer 0 prescale factor to 64
|
// set timer 0 prescale factor to 64
|
||||||
*avr.TCCR0B |= avr.TCCR0B_CS01 | avr.TCCR0B_CS00
|
avr.TCCR0B.SetBits(avr.TCCR0B_CS01 | avr.TCCR0B_CS00)
|
||||||
|
|
||||||
// set timer 1 prescale factor to 64
|
// set timer 1 prescale factor to 64
|
||||||
*avr.TCCR1B |= avr.TCCR1B_CS11
|
avr.TCCR1B.SetBits(avr.TCCR1B_CS11)
|
||||||
|
|
||||||
// put timer 1 in 8-bit phase correct pwm mode
|
// put timer 1 in 8-bit phase correct pwm mode
|
||||||
*avr.TCCR1A |= avr.TCCR1A_WGM10
|
avr.TCCR1A.SetBits(avr.TCCR1A_WGM10)
|
||||||
|
|
||||||
// set timer 2 prescale factor to 64
|
// set timer 2 prescale factor to 64
|
||||||
*avr.TCCR2B |= avr.TCCR2B_CS22
|
avr.TCCR2B.SetBits(avr.TCCR2B_CS22)
|
||||||
|
|
||||||
// configure timer 2 for phase correct pwm (8-bit)
|
// configure timer 2 for phase correct pwm (8-bit)
|
||||||
*avr.TCCR2A |= avr.TCCR2A_WGM20
|
avr.TCCR2A.SetBits(avr.TCCR2A_WGM20)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure configures a PWM pin for output.
|
// Configure configures a PWM pin for output.
|
||||||
func (pwm PWM) Configure() {
|
func (pwm PWM) Configure() {
|
||||||
if pwm.Pin < 8 {
|
if pwm.Pin < 8 {
|
||||||
*avr.DDRD |= 1 << pwm.Pin
|
avr.DDRD.SetBits(1 << pwm.Pin)
|
||||||
} else {
|
} else {
|
||||||
*avr.DDRB |= 1 << (pwm.Pin - 8)
|
avr.DDRB.SetBits(1 << (pwm.Pin - 8))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set turns on the duty cycle for a PWM pin using the provided value. On the AVR this is normally a
|
// Set turns on the duty cycle for a PWM pin using the provided value. On the AVR this is normally a
|
||||||
// 8-bit value ranging from 0 to 255.
|
// 8-bit value ranging from 0 to 255.
|
||||||
func (pwm PWM) Set(value uint16) {
|
func (pwm PWM) Set(value uint16) {
|
||||||
value8 := value >> 8
|
value8 := uint8(value >> 8)
|
||||||
switch pwm.Pin {
|
switch pwm.Pin {
|
||||||
case 3:
|
case 3:
|
||||||
// connect pwm to pin on timer 2, channel B
|
// connect pwm to pin on timer 2, channel B
|
||||||
*avr.TCCR2A |= avr.TCCR2A_COM2B1
|
avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1)
|
||||||
*avr.OCR2B = avr.RegValue(value8) // set pwm duty
|
avr.OCR2B.Set(value8) // set pwm duty
|
||||||
case 5:
|
case 5:
|
||||||
// connect pwm to pin on timer 0, channel B
|
// connect pwm to pin on timer 0, channel B
|
||||||
*avr.TCCR0A |= avr.TCCR0A_COM0B1
|
avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1)
|
||||||
*avr.OCR0B = avr.RegValue(value8) // set pwm duty
|
avr.OCR0B.Set(value8) // set pwm duty
|
||||||
case 6:
|
case 6:
|
||||||
// connect pwm to pin on timer 0, channel A
|
// connect pwm to pin on timer 0, channel A
|
||||||
*avr.TCCR0A |= avr.TCCR0A_COM0A1
|
avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1)
|
||||||
*avr.OCR0A = avr.RegValue(value8) // set pwm duty
|
avr.OCR0A.Set(value8) // set pwm duty
|
||||||
case 9:
|
case 9:
|
||||||
// connect pwm to pin on timer 1, channel A
|
// connect pwm to pin on timer 1, channel A
|
||||||
*avr.TCCR1A |= avr.TCCR1A_COM1A1
|
avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1)
|
||||||
// this is a 16-bit value, but we only currently allow the low order bits to be set
|
// this is a 16-bit value, but we only currently allow the low order bits to be set
|
||||||
*avr.OCR1AL = avr.RegValue(value8) // set pwm duty
|
avr.OCR1AL.Set(value8) // set pwm duty
|
||||||
case 10:
|
case 10:
|
||||||
// connect pwm to pin on timer 1, channel B
|
// connect pwm to pin on timer 1, channel B
|
||||||
*avr.TCCR1A |= avr.TCCR1A_COM1B1
|
avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1)
|
||||||
// this is a 16-bit value, but we only currently allow the low order bits to be set
|
// this is a 16-bit value, but we only currently allow the low order bits to be set
|
||||||
*avr.OCR1BL = avr.RegValue(value8) // set pwm duty
|
avr.OCR1BL.Set(value8) // set pwm duty
|
||||||
case 11:
|
case 11:
|
||||||
// connect pwm to pin on timer 2, channel A
|
// connect pwm to pin on timer 2, channel A
|
||||||
*avr.TCCR2A |= avr.TCCR2A_COM2A1
|
avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1)
|
||||||
*avr.OCR2A = avr.RegValue(value8) // set pwm duty
|
avr.OCR2A.Set(value8) // set pwm duty
|
||||||
default:
|
default:
|
||||||
panic("Invalid PWM pin")
|
panic("Invalid PWM pin")
|
||||||
}
|
}
|
||||||
@@ -121,19 +121,19 @@ func (i2c I2C) Configure(config I2CConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Activate internal pullups for twi.
|
// Activate internal pullups for twi.
|
||||||
*avr.PORTC |= (avr.DIDR0_ADC4D | avr.DIDR0_ADC5D)
|
avr.PORTC.SetBits((avr.DIDR0_ADC4D | avr.DIDR0_ADC5D))
|
||||||
|
|
||||||
// Initialize twi prescaler and bit rate.
|
// Initialize twi prescaler and bit rate.
|
||||||
*avr.TWSR |= (avr.TWSR_TWPS0 | avr.TWSR_TWPS1)
|
avr.TWSR.SetBits((avr.TWSR_TWPS0 | avr.TWSR_TWPS1))
|
||||||
|
|
||||||
// twi bit rate formula from atmega128 manual pg. 204:
|
// twi bit rate formula from atmega128 manual pg. 204:
|
||||||
// SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
|
// SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
|
||||||
// NOTE: TWBR should be 10 or higher for master mode.
|
// NOTE: TWBR should be 10 or higher for master mode.
|
||||||
// It is 72 for a 16mhz board with 100kHz TWI
|
// It is 72 for a 16mhz board with 100kHz TWI
|
||||||
*avr.TWBR = avr.RegValue(((CPU_FREQUENCY / config.Frequency) - 16) / 2)
|
avr.TWBR.Set(uint8(((CPU_FREQUENCY / config.Frequency) - 16) / 2))
|
||||||
|
|
||||||
// Enable twi module.
|
// Enable twi module.
|
||||||
*avr.TWCR = avr.TWCR_TWEN
|
avr.TWCR.Set(avr.TWCR_TWEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tx does a single I2C transaction at the specified address.
|
// Tx does a single I2C transaction at the specified address.
|
||||||
@@ -162,10 +162,10 @@ func (i2c I2C) Tx(addr uint16, w, r []byte) error {
|
|||||||
// start starts an I2C communication session.
|
// start starts an I2C communication session.
|
||||||
func (i2c I2C) start(address uint8, write bool) {
|
func (i2c I2C) start(address uint8, write bool) {
|
||||||
// Clear TWI interrupt flag, put start condition on SDA, and enable TWI.
|
// Clear TWI interrupt flag, put start condition on SDA, and enable TWI.
|
||||||
*avr.TWCR = (avr.TWCR_TWINT | avr.TWCR_TWSTA | avr.TWCR_TWEN)
|
avr.TWCR.Set((avr.TWCR_TWINT | avr.TWCR_TWSTA | avr.TWCR_TWEN))
|
||||||
|
|
||||||
// Wait till start condition is transmitted.
|
// Wait till start condition is transmitted.
|
||||||
for (*avr.TWCR & avr.TWCR_TWINT) == 0 {
|
for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write 7-bit shifted peripheral address.
|
// Write 7-bit shifted peripheral address.
|
||||||
@@ -179,36 +179,36 @@ func (i2c I2C) start(address uint8, write bool) {
|
|||||||
// stop ends an I2C communication session.
|
// stop ends an I2C communication session.
|
||||||
func (i2c I2C) stop() {
|
func (i2c I2C) stop() {
|
||||||
// Send stop condition.
|
// Send stop condition.
|
||||||
*avr.TWCR = (avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWSTO)
|
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWSTO)
|
||||||
|
|
||||||
// Wait for stop condition to be executed on bus.
|
// Wait for stop condition to be executed on bus.
|
||||||
for (*avr.TWCR & avr.TWCR_TWSTO) == 0 {
|
for (avr.TWCR.Get() & avr.TWCR_TWSTO) == 0 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeByte writes a single byte to the I2C bus.
|
// writeByte writes a single byte to the I2C bus.
|
||||||
func (i2c I2C) writeByte(data byte) {
|
func (i2c I2C) writeByte(data byte) {
|
||||||
// Write data to register.
|
// Write data to register.
|
||||||
*avr.TWDR = avr.RegValue(data)
|
avr.TWDR.Set(data)
|
||||||
|
|
||||||
// Clear TWI interrupt flag and enable TWI.
|
// Clear TWI interrupt flag and enable TWI.
|
||||||
*avr.TWCR = (avr.TWCR_TWEN | avr.TWCR_TWINT)
|
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT)
|
||||||
|
|
||||||
// Wait till data is transmitted.
|
// Wait till data is transmitted.
|
||||||
for (*avr.TWCR & avr.TWCR_TWINT) == 0 {
|
for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// readByte reads a single byte from the I2C bus.
|
// readByte reads a single byte from the I2C bus.
|
||||||
func (i2c I2C) readByte() byte {
|
func (i2c I2C) readByte() byte {
|
||||||
// Clear TWI interrupt flag and enable TWI.
|
// Clear TWI interrupt flag and enable TWI.
|
||||||
*avr.TWCR = (avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWEA)
|
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWEA)
|
||||||
|
|
||||||
// Wait till read request is transmitted.
|
// Wait till read request is transmitted.
|
||||||
for (*avr.TWCR & avr.TWCR_TWINT) == 0 {
|
for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
|
||||||
}
|
}
|
||||||
|
|
||||||
return byte(*avr.TWDR)
|
return byte(avr.TWDR.Get())
|
||||||
}
|
}
|
||||||
|
|
||||||
// UART on the AVR.
|
// UART on the AVR.
|
||||||
@@ -226,32 +226,32 @@ func (uart UART) Configure(config UARTConfig) {
|
|||||||
// https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html
|
// https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html
|
||||||
// ((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1)
|
// ((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1)
|
||||||
ps := ((CPU_FREQUENCY+config.BaudRate*8)/(config.BaudRate*16) - 1)
|
ps := ((CPU_FREQUENCY+config.BaudRate*8)/(config.BaudRate*16) - 1)
|
||||||
*avr.UBRR0H = avr.RegValue(ps >> 8)
|
avr.UBRR0H.Set(uint8(ps >> 8))
|
||||||
*avr.UBRR0L = avr.RegValue(ps & 0xff)
|
avr.UBRR0L.Set(uint8(ps & 0xff))
|
||||||
|
|
||||||
// enable RX, TX and RX interrupt
|
// enable RX, TX and RX interrupt
|
||||||
*avr.UCSR0B = avr.UCSR0B_RXEN0 | avr.UCSR0B_TXEN0 | avr.UCSR0B_RXCIE0
|
avr.UCSR0B.Set(avr.UCSR0B_RXEN0 | avr.UCSR0B_TXEN0 | avr.UCSR0B_RXCIE0)
|
||||||
|
|
||||||
// 8-bits data
|
// 8-bits data
|
||||||
*avr.UCSR0C = avr.UCSR0C_UCSZ01 | avr.UCSR0C_UCSZ00
|
avr.UCSR0C.Set(avr.UCSR0C_UCSZ01 | avr.UCSR0C_UCSZ00)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteByte writes a byte of data to the UART.
|
// WriteByte writes a byte of data to the UART.
|
||||||
func (uart UART) WriteByte(c byte) error {
|
func (uart UART) WriteByte(c byte) error {
|
||||||
// Wait until UART buffer is not busy.
|
// Wait until UART buffer is not busy.
|
||||||
for (*avr.UCSR0A & avr.UCSR0A_UDRE0) == 0 {
|
for (avr.UCSR0A.Get() & avr.UCSR0A_UDRE0) == 0 {
|
||||||
}
|
}
|
||||||
*avr.UDR0 = avr.RegValue(c) // send char
|
avr.UDR0.Set(c) // send char
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:interrupt USART_RX_vect
|
//go:interrupt USART_RX_vect
|
||||||
func handleUSART_RX() {
|
func handleUSART_RX() {
|
||||||
// Read register to clear it.
|
// Read register to clear it.
|
||||||
data := *avr.UDR0
|
data := avr.UDR0.Get()
|
||||||
|
|
||||||
// Ensure no error.
|
// Ensure no error.
|
||||||
if (*avr.UCSR0A & (avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0)) == 0 {
|
if (avr.UCSR0A.Get() & (avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0)) == 0 {
|
||||||
// Put data from UDR register into buffer.
|
// Put data from UDR register into buffer.
|
||||||
UART0.Receive(byte(data))
|
UART0.Receive(byte(data))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -343,6 +343,12 @@ func (a ADC) Get() uint16 {
|
|||||||
sam.ADC.INPUTCTRL |= sam.RegValue(ch << sam.ADC_INPUTCTRL_MUXPOS_Pos)
|
sam.ADC.INPUTCTRL |= sam.RegValue(ch << sam.ADC_INPUTCTRL_MUXPOS_Pos)
|
||||||
waitADCSync()
|
waitADCSync()
|
||||||
|
|
||||||
|
// Select internal ground for ADC input
|
||||||
|
sam.ADC.INPUTCTRL &^= sam.ADC_INPUTCTRL_MUXNEG_Msk
|
||||||
|
waitADCSync()
|
||||||
|
sam.ADC.INPUTCTRL |= sam.RegValue(sam.ADC_INPUTCTRL_MUXNEG_GND << sam.ADC_INPUTCTRL_MUXNEG_Pos)
|
||||||
|
waitADCSync()
|
||||||
|
|
||||||
// Enable ADC
|
// Enable ADC
|
||||||
sam.ADC.CTRLA |= sam.ADC_CTRLA_ENABLE
|
sam.ADC.CTRLA |= sam.ADC_CTRLA_ENABLE
|
||||||
waitADCSync()
|
waitADCSync()
|
||||||
@@ -368,7 +374,7 @@ func (a ADC) Get() uint16 {
|
|||||||
sam.ADC.CTRLA &^= sam.ADC_CTRLA_ENABLE
|
sam.ADC.CTRLA &^= sam.ADC_CTRLA_ENABLE
|
||||||
waitADCSync()
|
waitADCSync()
|
||||||
|
|
||||||
return uint16(val)
|
return uint16(val) << 4 // scales from 12 to 16-bit result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a ADC) getADCChannel() uint8 {
|
func (a ADC) getADCChannel() uint8 {
|
||||||
@@ -802,6 +808,235 @@ func (i2c I2C) readByte() byte {
|
|||||||
return byte(i2c.Bus.DATA)
|
return byte(i2c.Bus.DATA)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// I2S on the SAMD21.
|
||||||
|
|
||||||
|
// I2S
|
||||||
|
type I2S struct {
|
||||||
|
Bus *sam.I2S_Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure is used to configure the I2S interface. You must call this
|
||||||
|
// before you can use the I2S bus.
|
||||||
|
func (i2s I2S) Configure(config I2SConfig) {
|
||||||
|
// handle defaults
|
||||||
|
if config.SCK == 0 {
|
||||||
|
config.SCK = I2S_SCK_PIN
|
||||||
|
config.WS = I2S_WS_PIN
|
||||||
|
config.SD = I2S_SD_PIN
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.AudioFrequency == 0 {
|
||||||
|
config.AudioFrequency = 48000
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.DataFormat == I2SDataFormatDefault {
|
||||||
|
if config.Stereo {
|
||||||
|
config.DataFormat = I2SDataFormat16bit
|
||||||
|
} else {
|
||||||
|
config.DataFormat = I2SDataFormat32bit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn on clock for I2S
|
||||||
|
sam.PM.APBCMASK |= sam.PM_APBCMASK_I2S_
|
||||||
|
|
||||||
|
// setting clock rate for sample.
|
||||||
|
division_factor := CPU_FREQUENCY / (config.AudioFrequency * uint32(config.DataFormat))
|
||||||
|
|
||||||
|
// Switch Generic Clock Generator 3 to DFLL48M.
|
||||||
|
sam.GCLK.GENDIV = sam.RegValue((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENDIV_ID_Pos) |
|
||||||
|
(division_factor << sam.GCLK_GENDIV_DIV_Pos))
|
||||||
|
waitForSync()
|
||||||
|
|
||||||
|
sam.GCLK.GENCTRL = sam.RegValue((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENCTRL_ID_Pos) |
|
||||||
|
(sam.GCLK_GENCTRL_SRC_DFLL48M << sam.GCLK_GENCTRL_SRC_Pos) |
|
||||||
|
sam.GCLK_GENCTRL_IDC |
|
||||||
|
sam.GCLK_GENCTRL_GENEN)
|
||||||
|
waitForSync()
|
||||||
|
|
||||||
|
// Use Generic Clock Generator 3 as source for I2S.
|
||||||
|
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_I2S_0 << sam.GCLK_CLKCTRL_ID_Pos) |
|
||||||
|
(sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_CLKCTRL_GEN_Pos) |
|
||||||
|
sam.GCLK_CLKCTRL_CLKEN)
|
||||||
|
waitForSync()
|
||||||
|
|
||||||
|
// reset the device
|
||||||
|
i2s.Bus.CTRLA |= sam.I2S_CTRLA_SWRST
|
||||||
|
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_SWRST) > 0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
// disable device before continuing
|
||||||
|
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_ENABLE) > 0 {
|
||||||
|
}
|
||||||
|
i2s.Bus.CTRLA &^= sam.I2S_CTRLA_ENABLE
|
||||||
|
|
||||||
|
// setup clock
|
||||||
|
if config.ClockSource == I2SClockSourceInternal {
|
||||||
|
// TODO: make sure correct for I2S output
|
||||||
|
|
||||||
|
// set serial clock select pin
|
||||||
|
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SCKSEL
|
||||||
|
|
||||||
|
// set frame select pin
|
||||||
|
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_FSSEL
|
||||||
|
} else {
|
||||||
|
// Configure FS generation from SCK clock.
|
||||||
|
i2s.Bus.CLKCTRL0 &^= sam.I2S_CLKCTRL_FSSEL
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Standard == I2StandardPhilips {
|
||||||
|
// set 1-bit delay
|
||||||
|
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_BITDELAY
|
||||||
|
} else {
|
||||||
|
// set 0-bit delay
|
||||||
|
i2s.Bus.CLKCTRL0 &^= sam.I2S_CLKCTRL_BITDELAY
|
||||||
|
}
|
||||||
|
|
||||||
|
// set number of slots.
|
||||||
|
if config.Stereo {
|
||||||
|
i2s.Bus.CLKCTRL0 |= (1 << sam.I2S_CLKCTRL_NBSLOTS_Pos)
|
||||||
|
} else {
|
||||||
|
i2s.Bus.CLKCTRL0 &^= (1 << sam.I2S_CLKCTRL_NBSLOTS_Pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set slot size
|
||||||
|
switch config.DataFormat {
|
||||||
|
case I2SDataFormat8bit:
|
||||||
|
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SLOTSIZE_8
|
||||||
|
|
||||||
|
case I2SDataFormat16bit:
|
||||||
|
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SLOTSIZE_16
|
||||||
|
|
||||||
|
case I2SDataFormat24bit:
|
||||||
|
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SLOTSIZE_24
|
||||||
|
|
||||||
|
case I2SDataFormat32bit:
|
||||||
|
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SLOTSIZE_32
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure pin for clock
|
||||||
|
GPIO{config.SCK}.Configure(GPIOConfig{Mode: GPIO_COM})
|
||||||
|
|
||||||
|
// configure pin for WS, if needed
|
||||||
|
if config.WS != 0xff {
|
||||||
|
GPIO{config.WS}.Configure(GPIOConfig{Mode: GPIO_COM})
|
||||||
|
}
|
||||||
|
|
||||||
|
// now set serializer data size.
|
||||||
|
switch config.DataFormat {
|
||||||
|
case I2SDataFormat8bit:
|
||||||
|
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_DATASIZE_8
|
||||||
|
|
||||||
|
case I2SDataFormat16bit:
|
||||||
|
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_DATASIZE_16
|
||||||
|
|
||||||
|
case I2SDataFormat24bit:
|
||||||
|
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_DATASIZE_24
|
||||||
|
|
||||||
|
case I2SDataFormat32bit:
|
||||||
|
case I2SDataFormatDefault:
|
||||||
|
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_DATASIZE_32
|
||||||
|
}
|
||||||
|
|
||||||
|
// set serializer slot adjustment
|
||||||
|
if config.Standard == I2SStandardLSB {
|
||||||
|
// adjust right
|
||||||
|
i2s.Bus.SERCTRL1 &^= sam.I2S_SERCTRL_SLOTADJ
|
||||||
|
} else {
|
||||||
|
// adjust left
|
||||||
|
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_SLOTADJ
|
||||||
|
|
||||||
|
// reverse bit order?
|
||||||
|
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_BITREV
|
||||||
|
}
|
||||||
|
|
||||||
|
// set serializer mode.
|
||||||
|
if config.Mode == I2SModePDM {
|
||||||
|
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_SERMODE_PDM2
|
||||||
|
} else {
|
||||||
|
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_SERMODE_RX
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure data pin
|
||||||
|
GPIO{config.SD}.Configure(GPIOConfig{Mode: GPIO_COM})
|
||||||
|
|
||||||
|
// re-enable
|
||||||
|
i2s.Bus.CTRLA |= sam.I2S_CTRLA_ENABLE
|
||||||
|
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_ENABLE) > 0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable i2s clock
|
||||||
|
i2s.Bus.CTRLA |= sam.I2S_CTRLA_CKEN0
|
||||||
|
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_CKEN0) > 0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable i2s serializer
|
||||||
|
i2s.Bus.CTRLA |= sam.I2S_CTRLA_SEREN1
|
||||||
|
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_SEREN1) > 0 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read data from the I2S bus into the provided slice.
|
||||||
|
// The I2S bus must already have been configured correctly.
|
||||||
|
func (i2s I2S) Read(p []uint32) (n int, err error) {
|
||||||
|
i := 0
|
||||||
|
for i = 0; i < len(p); i++ {
|
||||||
|
// Wait until ready
|
||||||
|
for (i2s.Bus.INTFLAG & sam.I2S_INTFLAG_RXRDY1) == 0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_DATA1) > 0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
// read data
|
||||||
|
p[i] = uint32(i2s.Bus.DATA1)
|
||||||
|
|
||||||
|
// indicate read complete
|
||||||
|
i2s.Bus.INTFLAG = sam.I2S_INTFLAG_RXRDY1
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write data to the I2S bus from the provided slice.
|
||||||
|
// The I2S bus must already have been configured correctly.
|
||||||
|
func (i2s I2S) Write(p []uint32) (n int, err error) {
|
||||||
|
i := 0
|
||||||
|
for i = 0; i < len(p); i++ {
|
||||||
|
// Wait until ready
|
||||||
|
for (i2s.Bus.INTFLAG & sam.I2S_INTFLAG_TXRDY1) == 0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_DATA1) > 0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
// write data
|
||||||
|
i2s.Bus.DATA1 = sam.RegValue(p[i])
|
||||||
|
|
||||||
|
// indicate write complete
|
||||||
|
i2s.Bus.INTFLAG = sam.I2S_INTFLAG_TXRDY1
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the I2S bus.
|
||||||
|
func (i2s I2S) Close() error {
|
||||||
|
// Sync wait
|
||||||
|
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_ENABLE) > 0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
// disable I2S
|
||||||
|
i2s.Bus.CTRLA &^= sam.I2S_CTRLA_ENABLE
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForSync() {
|
||||||
|
for (sam.GCLK.STATUS & sam.GCLK_STATUS_SYNCBUSY) > 0 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SPI
|
// SPI
|
||||||
type SPI struct {
|
type SPI struct {
|
||||||
Bus *sam.SERCOM_SPI_Type
|
Bus *sam.SERCOM_SPI_Type
|
||||||
|
|||||||
@@ -9,19 +9,19 @@ import (
|
|||||||
// Configure sets the pin to input or output.
|
// Configure sets the pin to input or output.
|
||||||
func (p GPIO) Configure(config GPIOConfig) {
|
func (p GPIO) Configure(config GPIOConfig) {
|
||||||
if config.Mode == GPIO_OUTPUT { // set output bit
|
if config.Mode == GPIO_OUTPUT { // set output bit
|
||||||
*avr.DDRB |= 1 << p.Pin
|
avr.DDRB.SetBits(1 << p.Pin)
|
||||||
} else { // configure input: clear output bit
|
} else { // configure input: clear output bit
|
||||||
*avr.DDRB &^= 1 << p.Pin
|
avr.DDRB.ClearBits(1 << p.Pin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p GPIO) getPortMask() (*avr.RegValue, uint8) {
|
func (p GPIO) getPortMask() (*avr.Register8, uint8) {
|
||||||
return avr.PORTB, 1 << p.Pin
|
return avr.PORTB, 1 << p.Pin
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the current value of a GPIO pin.
|
// Get returns the current value of a GPIO pin.
|
||||||
func (p GPIO) Get() bool {
|
func (p GPIO) Get() bool {
|
||||||
val := *avr.PINB & (1 << p.Pin)
|
val := avr.PINB.Get() & (1 << p.Pin)
|
||||||
return (val > 0)
|
return (val > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-14
@@ -17,10 +17,10 @@ const (
|
|||||||
func (p GPIO) Set(value bool) {
|
func (p GPIO) Set(value bool) {
|
||||||
if value { // set bits
|
if value { // set bits
|
||||||
port, mask := p.PortMaskSet()
|
port, mask := p.PortMaskSet()
|
||||||
*port = mask
|
port.Set(mask)
|
||||||
} else { // clear bits
|
} else { // clear bits
|
||||||
port, mask := p.PortMaskClear()
|
port, mask := p.PortMaskClear()
|
||||||
*port = mask
|
port.Set(mask)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,9 +30,9 @@ func (p GPIO) Set(value bool) {
|
|||||||
// Warning: there are no separate pin set/clear registers on the AVR. The
|
// Warning: there are no separate pin set/clear registers on the AVR. The
|
||||||
// returned mask is only valid as long as no other pin in the same port has been
|
// returned mask is only valid as long as no other pin in the same port has been
|
||||||
// changed.
|
// changed.
|
||||||
func (p GPIO) PortMaskSet() (*avr.RegValue, avr.RegValue) {
|
func (p GPIO) PortMaskSet() (*avr.Register8, uint8) {
|
||||||
port, mask := p.getPortMask()
|
port, mask := p.getPortMask()
|
||||||
return port, *port | avr.RegValue(mask)
|
return port, port.Get() | mask
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the register and mask to disable a given port. This can be used to
|
// Return the register and mask to disable a given port. This can be used to
|
||||||
@@ -41,18 +41,18 @@ func (p GPIO) PortMaskSet() (*avr.RegValue, avr.RegValue) {
|
|||||||
// Warning: there are no separate pin set/clear registers on the AVR. The
|
// Warning: there are no separate pin set/clear registers on the AVR. The
|
||||||
// returned mask is only valid as long as no other pin in the same port has been
|
// returned mask is only valid as long as no other pin in the same port has been
|
||||||
// changed.
|
// changed.
|
||||||
func (p GPIO) PortMaskClear() (*avr.RegValue, avr.RegValue) {
|
func (p GPIO) PortMaskClear() (*avr.Register8, uint8) {
|
||||||
port, mask := p.getPortMask()
|
port, mask := p.getPortMask()
|
||||||
return port, *port &^ avr.RegValue(mask)
|
return port, port.Get() &^ mask
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitADC initializes the registers needed for ADC.
|
// InitADC initializes the registers needed for ADC.
|
||||||
func InitADC() {
|
func InitADC() {
|
||||||
// set a2d prescaler so we are inside the desired 50-200 KHz range at 16MHz.
|
// set a2d prescaler so we are inside the desired 50-200 KHz range at 16MHz.
|
||||||
*avr.ADCSRA |= (avr.ADCSRA_ADPS2 | avr.ADCSRA_ADPS1 | avr.ADCSRA_ADPS0)
|
avr.ADCSRA.SetBits(avr.ADCSRA_ADPS2 | avr.ADCSRA_ADPS1 | avr.ADCSRA_ADPS0)
|
||||||
|
|
||||||
// enable a2d conversions
|
// enable a2d conversions
|
||||||
*avr.ADCSRA |= avr.ADCSRA_ADEN
|
avr.ADCSRA.SetBits(avr.ADCSRA_ADEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure configures a ADCPin to be able to be used to read data.
|
// Configure configures a ADCPin to be able to be used to read data.
|
||||||
@@ -68,18 +68,16 @@ func (a ADC) Get() uint16 {
|
|||||||
// set the ADLAR bit (left-adjusted result) to get a value scaled to 16
|
// set the ADLAR bit (left-adjusted result) to get a value scaled to 16
|
||||||
// bits. This has the same effect as shifting the return value left by 6
|
// bits. This has the same effect as shifting the return value left by 6
|
||||||
// bits.
|
// bits.
|
||||||
*avr.ADMUX = avr.RegValue(avr.ADMUX_REFS0 | avr.ADMUX_ADLAR | (a.Pin & 0x07))
|
avr.ADMUX.Set(avr.ADMUX_REFS0 | avr.ADMUX_ADLAR | (a.Pin & 0x07))
|
||||||
|
|
||||||
// start the conversion
|
// start the conversion
|
||||||
*avr.ADCSRA |= avr.ADCSRA_ADSC
|
avr.ADCSRA.SetBits(avr.ADCSRA_ADSC)
|
||||||
|
|
||||||
// ADSC is cleared when the conversion finishes
|
// ADSC is cleared when the conversion finishes
|
||||||
for ok := true; ok; ok = (*avr.ADCSRA & avr.ADCSRA_ADSC) > 0 {
|
for ok := true; ok; ok = (avr.ADCSRA.Get() & avr.ADCSRA_ADSC) > 0 {
|
||||||
}
|
}
|
||||||
|
|
||||||
low := uint16(*avr.ADCL)
|
return uint16(avr.ADCL.Get()) | uint16(avr.ADCH.Get())<<8
|
||||||
high := uint16(*avr.ADCH)
|
|
||||||
return uint16(low) | uint16(high<<8)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// I2C on AVR.
|
// I2C on AVR.
|
||||||
|
|||||||
@@ -185,21 +185,20 @@ func (i2c I2C) Tx(addr uint16, w, r []byte) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(r) != 0 {
|
if len(r) != 0 {
|
||||||
|
// To trigger suspend task when a byte is received
|
||||||
|
i2c.Bus.SHORTS = nrf.TWI_SHORTS_BB_SUSPEND
|
||||||
i2c.Bus.TASKS_STARTRX = 1 // re-start transmission for reading
|
i2c.Bus.TASKS_STARTRX = 1 // re-start transmission for reading
|
||||||
for i := range r { // read each char
|
for i := range r { // read each char
|
||||||
if i+1 == len(r) {
|
if i+1 == len(r) {
|
||||||
// The 'stop' signal must be sent before reading back the last
|
// To trigger stop task when last byte is received, set before resume task.
|
||||||
// byte, so that it will be sent by the I2C peripheral right
|
i2c.Bus.SHORTS = nrf.TWI_SHORTS_BB_STOP
|
||||||
// after the last byte has been read.
|
}
|
||||||
r[i] = i2c.readLastByte()
|
i2c.Bus.TASKS_RESUME = 1 // re-start transmission for reading
|
||||||
} else {
|
|
||||||
r[i] = i2c.readByte()
|
r[i] = i2c.readByte()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Nothing to read back. Stop the transmission.
|
|
||||||
i2c.signalStop()
|
i2c.signalStop()
|
||||||
}
|
i2c.Bus.SHORTS = nrf.TWI_SHORTS_BB_SUSPEND_Disabled
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,16 +228,6 @@ func (i2c I2C) readByte() byte {
|
|||||||
return byte(i2c.Bus.RXD)
|
return byte(i2c.Bus.RXD)
|
||||||
}
|
}
|
||||||
|
|
||||||
// readLastByte reads a single byte from the I2C bus, sending a stop signal
|
|
||||||
// after it has been read.
|
|
||||||
func (i2c I2C) readLastByte() byte {
|
|
||||||
for i2c.Bus.EVENTS_RXDREADY == 0 {
|
|
||||||
}
|
|
||||||
i2c.Bus.EVENTS_RXDREADY = 0
|
|
||||||
i2c.signalStop() // signal 'stop' now, so it is sent when reading RXD
|
|
||||||
return byte(i2c.Bus.RXD)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SPI on the NRF.
|
// SPI on the NRF.
|
||||||
type SPI struct {
|
type SPI struct {
|
||||||
Bus *nrf.SPI_Type
|
Bus *nrf.SPI_Type
|
||||||
|
|||||||
+14
-23
@@ -39,33 +39,27 @@ const (
|
|||||||
chanStateClosed
|
chanStateClosed
|
||||||
)
|
)
|
||||||
|
|
||||||
func chanSendStub(caller *coroutine, ch *channel, _ unsafe.Pointer, size uintptr)
|
|
||||||
func chanRecvStub(caller *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size uintptr)
|
|
||||||
func deadlockStub()
|
func deadlockStub()
|
||||||
|
|
||||||
// chanSend sends a single value over the channel. If this operation can
|
// chanSend sends a single value over the channel. If this operation can
|
||||||
// complete immediately (there is a goroutine waiting for a value), it sends the
|
// complete immediately (there is a goroutine waiting for a value), it sends the
|
||||||
// value and re-activates both goroutines. If not, it sets itself as waiting on
|
// value and re-activates both goroutines. If not, it sets itself as waiting on
|
||||||
// a value.
|
// a value.
|
||||||
//
|
func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer, size uintptr) {
|
||||||
// The unsafe.Pointer value is used during lowering. During IR generation, it
|
|
||||||
// points to the to-be-received value. During coroutine lowering, this value is
|
|
||||||
// replaced with a read from the coroutine promise.
|
|
||||||
func chanSend(sender *coroutine, ch *channel, _ unsafe.Pointer, size uintptr) {
|
|
||||||
if ch == nil {
|
if ch == nil {
|
||||||
// A nil channel blocks forever. Do not scheduler this goroutine again.
|
// A nil channel blocks forever. Do not scheduler this goroutine again.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch ch.state {
|
switch ch.state {
|
||||||
case chanStateEmpty:
|
case chanStateEmpty:
|
||||||
|
sender.promise().ptr = value
|
||||||
ch.state = chanStateSend
|
ch.state = chanStateSend
|
||||||
ch.blocked = sender
|
ch.blocked = sender
|
||||||
case chanStateRecv:
|
case chanStateRecv:
|
||||||
receiver := ch.blocked
|
receiver := ch.blocked
|
||||||
receiverPromise := receiver.promise()
|
receiverPromise := receiver.promise()
|
||||||
senderPromise := sender.promise()
|
memcpy(receiverPromise.ptr, value, size)
|
||||||
memcpy(unsafe.Pointer(&receiverPromise.data), unsafe.Pointer(&senderPromise.data), size)
|
receiverPromise.data = 1 // commaOk = true
|
||||||
receiverPromise.commaOk = true
|
|
||||||
ch.blocked = receiverPromise.next
|
ch.blocked = receiverPromise.next
|
||||||
receiverPromise.next = nil
|
receiverPromise.next = nil
|
||||||
activateTask(receiver)
|
activateTask(receiver)
|
||||||
@@ -76,6 +70,7 @@ func chanSend(sender *coroutine, ch *channel, _ unsafe.Pointer, size uintptr) {
|
|||||||
case chanStateClosed:
|
case chanStateClosed:
|
||||||
runtimePanic("send on closed channel")
|
runtimePanic("send on closed channel")
|
||||||
case chanStateSend:
|
case chanStateSend:
|
||||||
|
sender.promise().ptr = value
|
||||||
sender.promise().next = ch.blocked
|
sender.promise().next = ch.blocked
|
||||||
ch.blocked = sender
|
ch.blocked = sender
|
||||||
}
|
}
|
||||||
@@ -85,11 +80,7 @@ func chanSend(sender *coroutine, ch *channel, _ unsafe.Pointer, size uintptr) {
|
|||||||
// sender, it receives the value immediately and re-activates both coroutines.
|
// sender, it receives the value immediately and re-activates both coroutines.
|
||||||
// If not, it sets itself as available for receiving. If the channel is closed,
|
// If not, it sets itself as available for receiving. If the channel is closed,
|
||||||
// it immediately activates itself with a zero value as the result.
|
// it immediately activates itself with a zero value as the result.
|
||||||
//
|
func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer, size uintptr) {
|
||||||
// The two unnamed values exist to help during lowering. The unsafe.Pointer
|
|
||||||
// points to the value, and the *bool points to the comma-ok value. Both are
|
|
||||||
// replaced by reads from the coroutine promise.
|
|
||||||
func chanRecv(receiver *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size uintptr) {
|
|
||||||
if ch == nil {
|
if ch == nil {
|
||||||
// A nil channel blocks forever. Do not scheduler this goroutine again.
|
// A nil channel blocks forever. Do not scheduler this goroutine again.
|
||||||
return
|
return
|
||||||
@@ -97,10 +88,9 @@ func chanRecv(receiver *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size
|
|||||||
switch ch.state {
|
switch ch.state {
|
||||||
case chanStateSend:
|
case chanStateSend:
|
||||||
sender := ch.blocked
|
sender := ch.blocked
|
||||||
receiverPromise := receiver.promise()
|
|
||||||
senderPromise := sender.promise()
|
senderPromise := sender.promise()
|
||||||
memcpy(unsafe.Pointer(&receiverPromise.data), unsafe.Pointer(&senderPromise.data), size)
|
memcpy(value, senderPromise.ptr, size)
|
||||||
receiverPromise.commaOk = true
|
receiver.promise().data = 1 // commaOk = true
|
||||||
ch.blocked = senderPromise.next
|
ch.blocked = senderPromise.next
|
||||||
senderPromise.next = nil
|
senderPromise.next = nil
|
||||||
activateTask(receiver)
|
activateTask(receiver)
|
||||||
@@ -109,14 +99,15 @@ func chanRecv(receiver *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size
|
|||||||
ch.state = chanStateEmpty
|
ch.state = chanStateEmpty
|
||||||
}
|
}
|
||||||
case chanStateEmpty:
|
case chanStateEmpty:
|
||||||
|
receiver.promise().ptr = value
|
||||||
ch.state = chanStateRecv
|
ch.state = chanStateRecv
|
||||||
ch.blocked = receiver
|
ch.blocked = receiver
|
||||||
case chanStateClosed:
|
case chanStateClosed:
|
||||||
receiverPromise := receiver.promise()
|
memzero(value, size)
|
||||||
memzero(unsafe.Pointer(&receiverPromise.data), size)
|
receiver.promise().data = 0 // commaOk = false
|
||||||
receiverPromise.commaOk = false
|
|
||||||
activateTask(receiver)
|
activateTask(receiver)
|
||||||
case chanStateRecv:
|
case chanStateRecv:
|
||||||
|
receiver.promise().ptr = value
|
||||||
receiver.promise().next = ch.blocked
|
receiver.promise().next = ch.blocked
|
||||||
ch.blocked = receiver
|
ch.blocked = receiver
|
||||||
}
|
}
|
||||||
@@ -142,8 +133,8 @@ func chanClose(ch *channel, size uintptr) {
|
|||||||
case chanStateRecv:
|
case chanStateRecv:
|
||||||
// The receiver must be re-activated with a zero value.
|
// The receiver must be re-activated with a zero value.
|
||||||
receiverPromise := ch.blocked.promise()
|
receiverPromise := ch.blocked.promise()
|
||||||
memzero(unsafe.Pointer(&receiverPromise.data), size)
|
memzero(receiverPromise.ptr, size)
|
||||||
receiverPromise.commaOk = false
|
receiverPromise.data = 0 // commaOk = false
|
||||||
activateTask(ch.blocked)
|
activateTask(ch.blocked)
|
||||||
ch.state = chanStateClosed
|
ch.state = chanStateClosed
|
||||||
ch.blocked = nil
|
ch.blocked = nil
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
// inf2one returns a signed 1 if f is an infinity and a signed 0 otherwise.
|
||||||
|
// The sign of the result is the sign of f.
|
||||||
|
func inf2one(f float64) float64 {
|
||||||
|
g := 0.0
|
||||||
|
if isInf(f) {
|
||||||
|
g = 1.0
|
||||||
|
}
|
||||||
|
return copysign(g, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func complex64div(n complex64, m complex64) complex64 {
|
||||||
|
return complex64(complex128div(complex128(n), complex128(m)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func complex128div(n complex128, m complex128) complex128 {
|
||||||
|
var e, f float64 // complex(e, f) = n/m
|
||||||
|
|
||||||
|
// Algorithm for robust complex division as described in
|
||||||
|
// Robert L. Smith: Algorithm 116: Complex division. Commun. ACM 5(8): 435 (1962).
|
||||||
|
if abs(real(m)) >= abs(imag(m)) {
|
||||||
|
ratio := imag(m) / real(m)
|
||||||
|
denom := real(m) + ratio*imag(m)
|
||||||
|
e = (real(n) + imag(n)*ratio) / denom
|
||||||
|
f = (imag(n) - real(n)*ratio) / denom
|
||||||
|
} else {
|
||||||
|
ratio := real(m) / imag(m)
|
||||||
|
denom := imag(m) + ratio*real(m)
|
||||||
|
e = (real(n)*ratio + imag(n)) / denom
|
||||||
|
f = (imag(n)*ratio - real(n)) / denom
|
||||||
|
}
|
||||||
|
|
||||||
|
if isNaN(e) && isNaN(f) {
|
||||||
|
// Correct final result to infinities and zeros if applicable.
|
||||||
|
// Matches C99: ISO/IEC 9899:1999 - G.5.1 Multiplicative operators.
|
||||||
|
|
||||||
|
a, b := real(n), imag(n)
|
||||||
|
c, d := real(m), imag(m)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case m == 0 && (!isNaN(a) || !isNaN(b)):
|
||||||
|
e = copysign(inf, c) * a
|
||||||
|
f = copysign(inf, c) * b
|
||||||
|
|
||||||
|
case (isInf(a) || isInf(b)) && isFinite(c) && isFinite(d):
|
||||||
|
a = inf2one(a)
|
||||||
|
b = inf2one(b)
|
||||||
|
e = inf * (a*c + b*d)
|
||||||
|
f = inf * (b*c - a*d)
|
||||||
|
|
||||||
|
case (isInf(c) || isInf(d)) && isFinite(a) && isFinite(b):
|
||||||
|
c = inf2one(c)
|
||||||
|
d = inf2one(d)
|
||||||
|
e = 0 * (a*c + b*d)
|
||||||
|
f = 0 * (b*c - a*d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return complex(e, f)
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
var inf = float64frombits(0x7FF0000000000000)
|
||||||
|
|
||||||
|
// isNaN reports whether f is an IEEE 754 ``not-a-number'' value.
|
||||||
|
func isNaN(f float64) (is bool) {
|
||||||
|
// IEEE 754 says that only NaNs satisfy f != f.
|
||||||
|
return f != f
|
||||||
|
}
|
||||||
|
|
||||||
|
// isFinite reports whether f is neither NaN nor an infinity.
|
||||||
|
func isFinite(f float64) bool {
|
||||||
|
return !isNaN(f - f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isInf reports whether f is an infinity.
|
||||||
|
func isInf(f float64) bool {
|
||||||
|
return !isNaN(f) && !isFinite(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abs returns the absolute value of x.
|
||||||
|
//
|
||||||
|
// Special cases are:
|
||||||
|
// Abs(±Inf) = +Inf
|
||||||
|
// Abs(NaN) = NaN
|
||||||
|
func abs(x float64) float64 {
|
||||||
|
const sign = 1 << 63
|
||||||
|
return float64frombits(float64bits(x) &^ sign)
|
||||||
|
}
|
||||||
|
|
||||||
|
// copysign returns a value with the magnitude
|
||||||
|
// of x and the sign of y.
|
||||||
|
func copysign(x, y float64) float64 {
|
||||||
|
const sign = 1 << 63
|
||||||
|
return float64frombits(float64bits(x)&^sign | float64bits(y)&sign)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64bits returns the IEEE 754 binary representation of f.
|
||||||
|
func float64bits(f float64) uint64 {
|
||||||
|
return *(*uint64)(unsafe.Pointer(&f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64frombits returns the floating point number corresponding
|
||||||
|
// the IEEE 754 binary representation b.
|
||||||
|
func float64frombits(b uint64) float64 {
|
||||||
|
return *(*float64)(unsafe.Pointer(&b))
|
||||||
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
// +build gc.marksweep
|
|
||||||
|
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
// This memory manager is a textbook mark/sweep implementation, heavily inspired
|
// This memory manager is a textbook mark/sweep implementation, heavily inspired
|
||||||
@@ -168,42 +166,9 @@ func (b gcBlock) unmark() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the memory allocator.
|
|
||||||
// No memory may be allocated before this is called. That means the runtime and
|
|
||||||
// any packages the runtime depends upon may not allocate memory during package
|
|
||||||
// initialization.
|
|
||||||
func init() {
|
|
||||||
totalSize := heapEnd - heapStart
|
|
||||||
|
|
||||||
// Allocate some memory to keep 2 bits of information about every block.
|
|
||||||
metadataSize := totalSize / (blocksPerStateByte * bytesPerBlock)
|
|
||||||
|
|
||||||
// Align the pool.
|
|
||||||
poolStart = (heapStart + metadataSize + (bytesPerBlock - 1)) &^ (bytesPerBlock - 1)
|
|
||||||
poolEnd := heapEnd &^ (bytesPerBlock - 1)
|
|
||||||
numBlocks := (poolEnd - poolStart) / bytesPerBlock
|
|
||||||
endBlock = gcBlock(numBlocks)
|
|
||||||
if gcDebug {
|
|
||||||
println("heapStart: ", heapStart)
|
|
||||||
println("heapEnd: ", heapEnd)
|
|
||||||
println("total size: ", totalSize)
|
|
||||||
println("metadata size: ", metadataSize)
|
|
||||||
println("poolStart: ", poolStart)
|
|
||||||
println("# of blocks: ", numBlocks)
|
|
||||||
println("# of block states:", metadataSize*blocksPerStateByte)
|
|
||||||
}
|
|
||||||
if gcAsserts && metadataSize*blocksPerStateByte < numBlocks {
|
|
||||||
// sanity check
|
|
||||||
runtimePanic("gc: metadata array is too small")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set all block states to 'free'.
|
|
||||||
memzero(unsafe.Pointer(heapStart), metadataSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// alloc tries to find some free space on the heap, possibly doing a garbage
|
// alloc tries to find some free space on the heap, possibly doing a garbage
|
||||||
// collection cycle if needed. If no space is free, it panics.
|
// collection cycle if needed. If no space is free, it panics.
|
||||||
func alloc(size uintptr) unsafe.Pointer {
|
func heapAlloc(size uintptr) unsafe.Pointer {
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
return unsafe.Pointer(&zeroSizedAlloc)
|
return unsafe.Pointer(&zeroSizedAlloc)
|
||||||
}
|
}
|
||||||
@@ -275,53 +240,6 @@ func free(ptr unsafe.Pointer) {
|
|||||||
// TODO: free blocks on request, when the compiler knows they're unused.
|
// TODO: free blocks on request, when the compiler knows they're unused.
|
||||||
}
|
}
|
||||||
|
|
||||||
// GC performs a garbage collection cycle.
|
|
||||||
func GC() {
|
|
||||||
if gcDebug {
|
|
||||||
println("running collection cycle...")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark phase: mark all reachable objects, recursively.
|
|
||||||
markRoots(globalsStart, globalsEnd)
|
|
||||||
markRoots(getCurrentStackPointer(), stackTop) // assume a descending stack
|
|
||||||
|
|
||||||
// Sweep phase: free all non-marked objects and unmark marked objects for
|
|
||||||
// the next collection cycle.
|
|
||||||
sweep()
|
|
||||||
|
|
||||||
// Show how much has been sweeped, for debugging.
|
|
||||||
if gcDebug {
|
|
||||||
dumpHeap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// markRoots reads all pointers from start to end (exclusive) and if they look
|
|
||||||
// like a heap pointer and are unmarked, marks them and scans that object as
|
|
||||||
// well (recursively). The start and end parameters must be valid pointers and
|
|
||||||
// must be aligned.
|
|
||||||
func markRoots(start, end uintptr) {
|
|
||||||
if gcDebug {
|
|
||||||
println("mark from", start, "to", end, int(end-start))
|
|
||||||
}
|
|
||||||
|
|
||||||
for addr := start; addr != end; addr += unsafe.Sizeof(addr) {
|
|
||||||
root := *(*uintptr)(unsafe.Pointer(addr))
|
|
||||||
if looksLikePointer(root) {
|
|
||||||
block := blockFromAddr(root)
|
|
||||||
head := block.findHead()
|
|
||||||
if head.state() != blockStateMark {
|
|
||||||
if gcDebug {
|
|
||||||
println("found unmarked pointer", root, "at address", addr)
|
|
||||||
}
|
|
||||||
head.setState(blockStateMark)
|
|
||||||
next := block.findNext()
|
|
||||||
// TODO: avoid recursion as much as possible
|
|
||||||
markRoots(head.address(), next.address())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sweep goes through all memory and frees unmarked memory.
|
// Sweep goes through all memory and frees unmarked memory.
|
||||||
func sweep() {
|
func sweep() {
|
||||||
freeCurrentObject := false
|
freeCurrentObject := false
|
||||||
@@ -347,10 +265,8 @@ func sweep() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// looksLikePointer returns whether this could be a pointer. Currently, it
|
// addressOnHeap returns whether this address points into the heap.
|
||||||
// simply returns whether it lies anywhere in the heap. Go allows interior
|
func addressOnHeap(ptr uintptr) bool {
|
||||||
// pointers so we can't check alignment or anything like that.
|
|
||||||
func looksLikePointer(ptr uintptr) bool {
|
|
||||||
return ptr >= poolStart && ptr < heapEnd
|
return ptr >= poolStart && ptr < heapEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
// +build gc.conservative
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initialize the memory allocator.
|
||||||
|
// No memory may be allocated before this is called. That means the runtime and
|
||||||
|
// any packages the runtime depends upon may not allocate memory during package
|
||||||
|
// initialization.
|
||||||
|
func init() {
|
||||||
|
totalSize := heapEnd - heapStart
|
||||||
|
|
||||||
|
// Allocate some memory to keep 2 bits of information about every block.
|
||||||
|
metadataSize := totalSize / (blocksPerStateByte * bytesPerBlock)
|
||||||
|
|
||||||
|
// Align the pool.
|
||||||
|
poolStart = (heapStart + metadataSize + (bytesPerBlock - 1)) &^ (bytesPerBlock - 1)
|
||||||
|
poolEnd := heapEnd &^ (bytesPerBlock - 1)
|
||||||
|
numBlocks := (poolEnd - poolStart) / bytesPerBlock
|
||||||
|
endBlock = gcBlock(numBlocks)
|
||||||
|
if gcDebug {
|
||||||
|
println("heapStart: ", heapStart)
|
||||||
|
println("heapEnd: ", heapEnd)
|
||||||
|
println("total size: ", totalSize)
|
||||||
|
println("metadata size: ", metadataSize)
|
||||||
|
println("poolStart: ", poolStart)
|
||||||
|
println("# of blocks: ", numBlocks)
|
||||||
|
println("# of block states:", metadataSize*blocksPerStateByte)
|
||||||
|
}
|
||||||
|
if gcAsserts && metadataSize*blocksPerStateByte < numBlocks {
|
||||||
|
// sanity check
|
||||||
|
runtimePanic("gc: metadata array is too small")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set all block states to 'free'.
|
||||||
|
memzero(unsafe.Pointer(heapStart), metadataSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func alloc(size uintptr) unsafe.Pointer {
|
||||||
|
return heapAlloc(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GC performs a garbage collection cycle.
|
||||||
|
func GC() {
|
||||||
|
if gcDebug {
|
||||||
|
println("running collection cycle...")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark phase: mark all reachable objects, recursively.
|
||||||
|
markRoots(globalsStart, globalsEnd)
|
||||||
|
markRoots(getCurrentStackPointer(), stackTop) // assume a descending stack
|
||||||
|
|
||||||
|
// Sweep phase: free all non-marked objects and unmark marked objects for
|
||||||
|
// the next collection cycle.
|
||||||
|
sweep()
|
||||||
|
|
||||||
|
// Show how much has been sweeped, for debugging.
|
||||||
|
if gcDebug {
|
||||||
|
dumpHeap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// markRoots reads all pointers from start to end (exclusive) and if they look
|
||||||
|
// like a heap pointer and are unmarked, marks them and scans that object as
|
||||||
|
// well (recursively). The start and end parameters must be valid pointers and
|
||||||
|
// must be aligned.
|
||||||
|
func markRoots(start, end uintptr) {
|
||||||
|
if gcDebug {
|
||||||
|
println("mark from", start, "to", end, int(end-start))
|
||||||
|
}
|
||||||
|
|
||||||
|
for addr := start; addr != end; addr += unsafe.Sizeof(addr) {
|
||||||
|
root := *(*uintptr)(unsafe.Pointer(addr))
|
||||||
|
if addressOnHeap(root) {
|
||||||
|
block := blockFromAddr(root)
|
||||||
|
head := block.findHead()
|
||||||
|
if head.state() != blockStateMark {
|
||||||
|
if gcDebug {
|
||||||
|
println("found unmarked pointer", root, "at address", addr)
|
||||||
|
}
|
||||||
|
head.setState(blockStateMark)
|
||||||
|
next := block.findNext()
|
||||||
|
// TODO: avoid recursion as much as possible
|
||||||
|
markRoots(head.address(), next.address())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build gc.dumb
|
// +build gc.leaking
|
||||||
|
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
@@ -30,18 +30,6 @@ func alloc(size uintptr) unsafe.Pointer {
|
|||||||
return unsafe.Pointer(addr)
|
return unsafe.Pointer(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func free(ptr unsafe.Pointer) {
|
|
||||||
// Memory is never freed.
|
|
||||||
}
|
|
||||||
|
|
||||||
func GC() {
|
func GC() {
|
||||||
// No-op.
|
// No-op.
|
||||||
}
|
}
|
||||||
|
|
||||||
func KeepAlive(x interface{}) {
|
|
||||||
// Unimplemented. Only required with SetFinalizer().
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetFinalizer(obj interface{}, finalizer interface{}) {
|
|
||||||
// Unimplemented.
|
|
||||||
}
|
|
||||||
@@ -12,18 +12,6 @@ import (
|
|||||||
|
|
||||||
func alloc(size uintptr) unsafe.Pointer
|
func alloc(size uintptr) unsafe.Pointer
|
||||||
|
|
||||||
func free(ptr unsafe.Pointer) {
|
|
||||||
// Nothing to free when nothing gets allocated.
|
|
||||||
}
|
|
||||||
|
|
||||||
func GC() {
|
func GC() {
|
||||||
// Unimplemented.
|
// Unimplemented.
|
||||||
}
|
}
|
||||||
|
|
||||||
func KeepAlive(x interface{}) {
|
|
||||||
// Unimplemented. Only required with SetFinalizer().
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetFinalizer(obj interface{}, finalizer interface{}) {
|
|
||||||
// Unimplemented.
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
// +build gc.precise
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:extern runtime.trackedGlobalsStart
|
||||||
|
var trackedGlobalsStart uintptr
|
||||||
|
|
||||||
|
//go:extern runtime.trackedGlobalsLength
|
||||||
|
var trackedGlobalsLength uintptr
|
||||||
|
|
||||||
|
//go:extern runtime.trackedGlobalsBitmap
|
||||||
|
var trackedGlobalsBitmap [0]uint8
|
||||||
|
|
||||||
|
// Initialize the memory allocator.
|
||||||
|
// No memory may be allocated before this is called. That means the runtime and
|
||||||
|
// any packages the runtime depends upon may not allocate memory during package
|
||||||
|
// initialization.
|
||||||
|
func init() {
|
||||||
|
totalSize := heapEnd - heapStart
|
||||||
|
|
||||||
|
// Allocate some memory to keep 2 bits of information about every block.
|
||||||
|
metadataSize := totalSize / (blocksPerStateByte * bytesPerBlock)
|
||||||
|
|
||||||
|
// Align the pool.
|
||||||
|
poolStart = (heapStart + metadataSize + (bytesPerBlock - 1)) &^ (bytesPerBlock - 1)
|
||||||
|
poolEnd := heapEnd &^ (bytesPerBlock - 1)
|
||||||
|
numBlocks := (poolEnd - poolStart) / bytesPerBlock
|
||||||
|
endBlock = gcBlock(numBlocks)
|
||||||
|
if gcDebug {
|
||||||
|
println("heapStart: ", heapStart)
|
||||||
|
println("heapEnd: ", heapEnd)
|
||||||
|
println("total size: ", totalSize)
|
||||||
|
println("metadata size: ", metadataSize)
|
||||||
|
println("poolStart: ", poolStart)
|
||||||
|
println("# of blocks: ", numBlocks)
|
||||||
|
println("# of block states:", metadataSize*blocksPerStateByte)
|
||||||
|
}
|
||||||
|
if gcAsserts && metadataSize*blocksPerStateByte < numBlocks {
|
||||||
|
// sanity check
|
||||||
|
runtimePanic("gc: metadata array is too small")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set all block states to 'free'.
|
||||||
|
memzero(unsafe.Pointer(heapStart), metadataSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func alloc(size uintptr) unsafe.Pointer {
|
||||||
|
GC()
|
||||||
|
return heapAlloc(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GC performs a garbage collection cycle.
|
||||||
|
func GC() {
|
||||||
|
if gcDebug {
|
||||||
|
println("\nrunning collection cycle...")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark phase: mark all reachable objects, recursively.
|
||||||
|
markGlobals()
|
||||||
|
markRoots(getCurrentStackPointer(), stackTop) // assume a descending stack
|
||||||
|
|
||||||
|
// Sweep phase: free all non-marked objects and unmark marked objects for
|
||||||
|
// the next collection cycle.
|
||||||
|
sweep()
|
||||||
|
|
||||||
|
// Show how much has been sweeped, for debugging.
|
||||||
|
if gcDebug {
|
||||||
|
dumpHeap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:nobounds
|
||||||
|
func markGlobals() {
|
||||||
|
for i := uintptr(0); i < trackedGlobalsLength; i++ {
|
||||||
|
if trackedGlobalsBitmap[i/8]&(1<<(i%8)) != 0 {
|
||||||
|
addr := trackedGlobalsStart + i*unsafe.Alignof(uintptr(0))
|
||||||
|
root := *(*uintptr)(unsafe.Pointer(addr))
|
||||||
|
markRoot(addr, root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// markRoots reads all pointers from start to end (exclusive) and if they look
|
||||||
|
// like a heap pointer and are unmarked, marks them and scans that object as
|
||||||
|
// well (recursively). The start and end parameters must be valid pointers and
|
||||||
|
// must be aligned.
|
||||||
|
func markRoots(start, end uintptr) {
|
||||||
|
if gcDebug {
|
||||||
|
println("mark from", start, "to", end, int(end-start))
|
||||||
|
}
|
||||||
|
|
||||||
|
for addr := start; addr != end; addr += unsafe.Sizeof(addr) {
|
||||||
|
root := *(*uintptr)(unsafe.Pointer(addr))
|
||||||
|
markRoot(addr, root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func markRoot(addr, root uintptr) {
|
||||||
|
if addressOnHeap(root) {
|
||||||
|
block := blockFromAddr(root)
|
||||||
|
head := block.findHead()
|
||||||
|
if head.state() != blockStateMark {
|
||||||
|
if gcDebug {
|
||||||
|
println("found unmarked pointer", root, "at address", addr)
|
||||||
|
}
|
||||||
|
head.setState(blockStateMark)
|
||||||
|
next := block.findNext()
|
||||||
|
// TODO: avoid recursion as much as possible
|
||||||
|
markRoots(head.address(), next.address())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
-13
@@ -60,14 +60,20 @@ func hashmapTopHash(hash uint32) uint8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a new hashmap with the given keySize and valueSize.
|
// Create a new hashmap with the given keySize and valueSize.
|
||||||
func hashmapMake(keySize, valueSize uint8) *hashmap {
|
func hashmapMake(keySize, valueSize uint8, sizeHint uintptr) *hashmap {
|
||||||
|
numBuckets := sizeHint / 8
|
||||||
|
bucketBits := uint8(0)
|
||||||
|
for numBuckets != 0 {
|
||||||
|
numBuckets /= 2
|
||||||
|
bucketBits++
|
||||||
|
}
|
||||||
bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(keySize)*8 + uintptr(valueSize)*8
|
bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(keySize)*8 + uintptr(valueSize)*8
|
||||||
bucket := alloc(bucketBufSize)
|
buckets := alloc(bucketBufSize * (1 << bucketBits))
|
||||||
return &hashmap{
|
return &hashmap{
|
||||||
buckets: bucket,
|
buckets: buckets,
|
||||||
keySize: keySize,
|
keySize: keySize,
|
||||||
valueSize: valueSize,
|
valueSize: valueSize,
|
||||||
bucketBits: 0,
|
bucketBits: bucketBits,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,13 +89,20 @@ func hashmapLen(m *hashmap) int {
|
|||||||
// Set a specified key to a given value. Grow the map if necessary.
|
// Set a specified key to a given value. Grow the map if necessary.
|
||||||
//go:nobounds
|
//go:nobounds
|
||||||
func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) {
|
func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) {
|
||||||
|
tophash := hashmapTopHash(hash)
|
||||||
|
|
||||||
|
if m.buckets == nil {
|
||||||
|
// No bucket was allocated yet, do so now.
|
||||||
|
m.buckets = unsafe.Pointer(hashmapInsertIntoNewBucket(m, key, value, tophash))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
numBuckets := uintptr(1) << m.bucketBits
|
numBuckets := uintptr(1) << m.bucketBits
|
||||||
bucketNumber := (uintptr(hash) & (numBuckets - 1))
|
bucketNumber := (uintptr(hash) & (numBuckets - 1))
|
||||||
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
|
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
|
||||||
bucketAddr := uintptr(m.buckets) + bucketSize*bucketNumber
|
bucketAddr := uintptr(m.buckets) + bucketSize*bucketNumber
|
||||||
bucket := (*hashmapBucket)(unsafe.Pointer(bucketAddr))
|
bucket := (*hashmapBucket)(unsafe.Pointer(bucketAddr))
|
||||||
|
var lastBucket *hashmapBucket
|
||||||
tophash := hashmapTopHash(hash)
|
|
||||||
|
|
||||||
// See whether the key already exists somewhere.
|
// See whether the key already exists somewhere.
|
||||||
var emptySlotKey unsafe.Pointer
|
var emptySlotKey unsafe.Pointer
|
||||||
@@ -98,9 +111,9 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
|
|||||||
for bucket != nil {
|
for bucket != nil {
|
||||||
for i := uintptr(0); i < 8; i++ {
|
for i := uintptr(0); i < 8; i++ {
|
||||||
slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(i)
|
slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(i)
|
||||||
slotKey := unsafe.Pointer(bucketAddr + slotKeyOffset)
|
slotKey := unsafe.Pointer(uintptr(unsafe.Pointer(bucket)) + slotKeyOffset)
|
||||||
slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(i)
|
slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(i)
|
||||||
slotValue := unsafe.Pointer(bucketAddr + slotValueOffset)
|
slotValue := unsafe.Pointer(uintptr(unsafe.Pointer(bucket)) + slotValueOffset)
|
||||||
if bucket.tophash[i] == 0 && emptySlotKey == nil {
|
if bucket.tophash[i] == 0 && emptySlotKey == nil {
|
||||||
// Found an empty slot, store it for if we couldn't find an
|
// Found an empty slot, store it for if we couldn't find an
|
||||||
// existing slot.
|
// existing slot.
|
||||||
@@ -109,7 +122,7 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
|
|||||||
emptySlotTophash = &bucket.tophash[i]
|
emptySlotTophash = &bucket.tophash[i]
|
||||||
}
|
}
|
||||||
if bucket.tophash[i] == tophash {
|
if bucket.tophash[i] == tophash {
|
||||||
// Could be an existing value that's the same.
|
// Could be an existing key that's the same.
|
||||||
if keyEqual(key, slotKey, uintptr(m.keySize)) {
|
if keyEqual(key, slotKey, uintptr(m.keySize)) {
|
||||||
// found same key, replace it
|
// found same key, replace it
|
||||||
memcpy(slotValue, value, uintptr(m.valueSize))
|
memcpy(slotValue, value, uintptr(m.valueSize))
|
||||||
@@ -117,16 +130,37 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
lastBucket = bucket
|
||||||
bucket = bucket.next
|
bucket = bucket.next
|
||||||
}
|
}
|
||||||
if emptySlotKey != nil {
|
if emptySlotKey == nil {
|
||||||
|
// Add a new bucket to the bucket chain.
|
||||||
|
// TODO: rebalance if necessary to avoid O(n) insert and lookup time.
|
||||||
|
lastBucket.next = (*hashmapBucket)(hashmapInsertIntoNewBucket(m, key, value, tophash))
|
||||||
|
return
|
||||||
|
}
|
||||||
m.count++
|
m.count++
|
||||||
memcpy(emptySlotKey, key, uintptr(m.keySize))
|
memcpy(emptySlotKey, key, uintptr(m.keySize))
|
||||||
memcpy(emptySlotValue, value, uintptr(m.valueSize))
|
memcpy(emptySlotValue, value, uintptr(m.valueSize))
|
||||||
*emptySlotTophash = tophash
|
*emptySlotTophash = tophash
|
||||||
return
|
}
|
||||||
}
|
|
||||||
panic("todo: hashmap: grow bucket")
|
// hashmapInsertIntoNewBucket creates a new bucket, inserts the given key and
|
||||||
|
// value into the bucket, and returns a pointer to this bucket.
|
||||||
|
func hashmapInsertIntoNewBucket(m *hashmap, key, value unsafe.Pointer, tophash uint8) *hashmapBucket {
|
||||||
|
bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
|
||||||
|
bucketBuf := alloc(bucketBufSize)
|
||||||
|
// Insert into the first slot, which is empty as it has just been allocated.
|
||||||
|
slotKeyOffset := unsafe.Sizeof(hashmapBucket{})
|
||||||
|
slotKey := unsafe.Pointer(uintptr(bucketBuf) + slotKeyOffset)
|
||||||
|
slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8
|
||||||
|
slotValue := unsafe.Pointer(uintptr(bucketBuf) + slotValueOffset)
|
||||||
|
m.count++
|
||||||
|
memcpy(slotKey, key, uintptr(m.keySize))
|
||||||
|
memcpy(slotValue, value, uintptr(m.valueSize))
|
||||||
|
bucket := (*hashmapBucket)(bucketBuf)
|
||||||
|
bucket.tophash[0] = tophash
|
||||||
|
return bucket
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the value of a specified key, or zero the value if not found.
|
// Get the value of a specified key, or zero the value if not found.
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type stringer interface {
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
//go:nobounds
|
//go:nobounds
|
||||||
func printstring(s string) {
|
func printstring(s string) {
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
@@ -199,8 +203,44 @@ func printnl() {
|
|||||||
|
|
||||||
func printitf(msg interface{}) {
|
func printitf(msg interface{}) {
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
|
case bool:
|
||||||
|
print(msg)
|
||||||
|
case int:
|
||||||
|
print(msg)
|
||||||
|
case int8:
|
||||||
|
print(msg)
|
||||||
|
case int16:
|
||||||
|
print(msg)
|
||||||
|
case int32:
|
||||||
|
print(msg)
|
||||||
|
case int64:
|
||||||
|
print(msg)
|
||||||
|
case uint:
|
||||||
|
print(msg)
|
||||||
|
case uint8:
|
||||||
|
print(msg)
|
||||||
|
case uint16:
|
||||||
|
print(msg)
|
||||||
|
case uint32:
|
||||||
|
print(msg)
|
||||||
|
case uint64:
|
||||||
|
print(msg)
|
||||||
|
case uintptr:
|
||||||
|
print(msg)
|
||||||
|
case float32:
|
||||||
|
print(msg)
|
||||||
|
case float64:
|
||||||
|
print(msg)
|
||||||
|
case complex64:
|
||||||
|
print(msg)
|
||||||
|
case complex128:
|
||||||
|
print(msg)
|
||||||
case string:
|
case string:
|
||||||
print(msg)
|
print(msg)
|
||||||
|
case error:
|
||||||
|
print(msg.Error())
|
||||||
|
case stringer:
|
||||||
|
print(msg.String())
|
||||||
default:
|
default:
|
||||||
// cast to underlying type
|
// cast to underlying type
|
||||||
itf := *(*_interface)(unsafe.Pointer(&msg))
|
itf := *(*_interface)(unsafe.Pointer(&msg))
|
||||||
|
|||||||
@@ -17,19 +17,19 @@ func sleepWDT(period uint8) {
|
|||||||
avr.Asm("cli")
|
avr.Asm("cli")
|
||||||
avr.Asm("wdr")
|
avr.Asm("wdr")
|
||||||
// Start timed sequence.
|
// Start timed sequence.
|
||||||
*avr.WDTCSR |= avr.WDTCSR_WDCE | avr.WDTCSR_WDE
|
avr.WDTCSR.SetBits(avr.WDTCSR_WDCE | avr.WDTCSR_WDE)
|
||||||
// Enable WDT and set new timeout
|
// Enable WDT and set new timeout
|
||||||
*avr.WDTCSR = avr.WDTCSR_WDIE | avr.RegValue(period)
|
avr.WDTCSR.SetBits(avr.WDTCSR_WDIE | period)
|
||||||
avr.Asm("sei")
|
avr.Asm("sei")
|
||||||
|
|
||||||
// Set sleep mode to idle and enable sleep mode.
|
// Set sleep mode to idle and enable sleep mode.
|
||||||
// Note: when using something other than idle, the UART won't work
|
// Note: when using something other than idle, the UART won't work
|
||||||
// correctly. This needs to be fixed, though, so we can truly sleep.
|
// correctly. This needs to be fixed, though, so we can truly sleep.
|
||||||
*avr.SMCR = (0 << 1) | avr.SMCR_SE
|
avr.SMCR.Set((0 << 1) | avr.SMCR_SE)
|
||||||
|
|
||||||
// go to sleep
|
// go to sleep
|
||||||
avr.Asm("sleep")
|
avr.Asm("sleep")
|
||||||
|
|
||||||
// disable sleep
|
// disable sleep
|
||||||
*avr.SMCR = 0
|
avr.SMCR.Set(0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,12 +49,16 @@ func (t *coroutine) promise() *taskState {
|
|||||||
|
|
||||||
func makeGoroutine(*uint8) *uint8
|
func makeGoroutine(*uint8) *uint8
|
||||||
|
|
||||||
|
// Compiler stub to get the current goroutine. Calls to this function are
|
||||||
|
// removed in the goroutine lowering pass.
|
||||||
|
func getCoroutine() *coroutine
|
||||||
|
|
||||||
// State/promise of a task. Internally represented as:
|
// State/promise of a task. Internally represented as:
|
||||||
//
|
//
|
||||||
// {i8* next, i1 commaOk, i32/i64 data}
|
// {i8* next, i1 commaOk, i32/i64 data}
|
||||||
type taskState struct {
|
type taskState struct {
|
||||||
next *coroutine
|
next *coroutine
|
||||||
commaOk bool // 'comma-ok' flag for channel receive operation
|
ptr unsafe.Pointer
|
||||||
data uint
|
data uint
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +111,24 @@ func activateTask(task *coroutine) {
|
|||||||
runqueuePushBack(task)
|
runqueuePushBack(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getTaskPromisePtr is a helper function to set the current .ptr field of a
|
||||||
|
// coroutine promise.
|
||||||
|
func setTaskPromisePtr(task *coroutine, value unsafe.Pointer) {
|
||||||
|
task.promise().ptr = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTaskPromisePtr is a helper function to get the current .ptr field from a
|
||||||
|
// coroutine promise.
|
||||||
|
func getTaskPromisePtr(task *coroutine) unsafe.Pointer {
|
||||||
|
return task.promise().ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTaskPromiseData is a helper function to get the current .data field of a
|
||||||
|
// coroutine promise.
|
||||||
|
func getTaskPromiseData(task *coroutine) uint {
|
||||||
|
return task.promise().data
|
||||||
|
}
|
||||||
|
|
||||||
// Add this task to the end of the run queue. May also destroy the task if it's
|
// Add this task to the end of the run queue. May also destroy the task if it's
|
||||||
// done.
|
// done.
|
||||||
func runqueuePushBack(t *coroutine) {
|
func runqueuePushBack(t *coroutine) {
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// Package volatile provides definitions for volatile loads and stores. These
|
||||||
|
// are implemented as compiler builtins.
|
||||||
|
//
|
||||||
|
// The load operations load a volatile value. The store operations store to a
|
||||||
|
// volatile value. The compiler will emit exactly one load or store operation
|
||||||
|
// when possible and will not reorder volatile operations. However, the compiler
|
||||||
|
// may move other operations across load/store operations, so make sure that all
|
||||||
|
// relevant loads/stores are done in a volatile way if this is a problem.
|
||||||
|
//
|
||||||
|
// These loads and stores are commonly used to read/write values from memory
|
||||||
|
// mapped peripheral devices. They do not provide atomicity, use the sync/atomic
|
||||||
|
// package for that.
|
||||||
|
//
|
||||||
|
// For more details: https://llvm.org/docs/LangRef.html#volatile-memory-accesses
|
||||||
|
// and https://blog.regehr.org/archives/28.
|
||||||
|
package volatile
|
||||||
|
|
||||||
|
// LoadUint8 loads the volatile value *addr.
|
||||||
|
func LoadUint8(addr *uint8) (val uint8)
|
||||||
|
|
||||||
|
// LoadUint16 loads the volatile value *addr.
|
||||||
|
func LoadUint16(addr *uint16) (val uint16)
|
||||||
|
|
||||||
|
// LoadUint32 loads the volatile value *addr.
|
||||||
|
func LoadUint32(addr *uint32) (val uint32)
|
||||||
|
|
||||||
|
// StoreUint8 stores val to the volatile value *addr.
|
||||||
|
func StoreUint8(addr *uint8, val uint8)
|
||||||
|
|
||||||
|
// StoreUint16 stores val to the volatile value *addr.
|
||||||
|
func StoreUint16(addr *uint16, val uint16)
|
||||||
|
|
||||||
|
// StoreUint32 stores val to the volatile value *addr.
|
||||||
|
func StoreUint32(addr *uint32, val uint32)
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -211,7 +212,7 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
|||||||
GOOS: goos,
|
GOOS: goos,
|
||||||
GOARCH: goarch,
|
GOARCH: goarch,
|
||||||
BuildTags: []string{goos, goarch},
|
BuildTags: []string{goos, goarch},
|
||||||
Compiler: commands["clang"],
|
Compiler: "clang",
|
||||||
Linker: "cc",
|
Linker: "cc",
|
||||||
GDB: "gdb",
|
GDB: "gdb",
|
||||||
GDBCmds: []string{"run"},
|
GDBCmds: []string{"run"},
|
||||||
@@ -310,3 +311,60 @@ func getHomeDir() string {
|
|||||||
}
|
}
|
||||||
return u.HomeDir
|
return u.HomeDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getGoroot returns an appropriate GOROOT from various sources. If it can't be
|
||||||
|
// found, it returns an empty string.
|
||||||
|
func getGoroot() string {
|
||||||
|
goroot := os.Getenv("GOROOT")
|
||||||
|
if goroot != "" {
|
||||||
|
// An explicitly set GOROOT always has preference.
|
||||||
|
return goroot
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for the location of the 'go' binary and base GOROOT on that.
|
||||||
|
binpath, err := exec.LookPath("go")
|
||||||
|
if err == nil {
|
||||||
|
binpath, err = filepath.EvalSymlinks(binpath)
|
||||||
|
if err == nil {
|
||||||
|
goroot := filepath.Dir(filepath.Dir(binpath))
|
||||||
|
if isGoroot(goroot) {
|
||||||
|
return goroot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check what GOROOT was at compile time.
|
||||||
|
if isGoroot(runtime.GOROOT()) {
|
||||||
|
return runtime.GOROOT()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for some standard locations, as a last resort.
|
||||||
|
var candidates []string
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "linux":
|
||||||
|
candidates = []string{
|
||||||
|
"/usr/local/go", // manually installed
|
||||||
|
"/usr/lib/go", // from the distribution
|
||||||
|
}
|
||||||
|
case "darwin":
|
||||||
|
candidates = []string{
|
||||||
|
"/usr/local/go", // manually installed
|
||||||
|
"/usr/local/opt/go/libexec", // from Homebrew
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, candidate := range candidates {
|
||||||
|
if isGoroot(candidate) {
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't find GOROOT...
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// isGoroot checks whether the given path looks like a GOROOT.
|
||||||
|
func isGoroot(goroot string) bool {
|
||||||
|
_, err := os.Stat(filepath.Join(goroot, "src", "runtime", "internal", "sys", "zversion.go"))
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,14 +2,17 @@
|
|||||||
"build-tags": ["cortexm", "linux", "arm"],
|
"build-tags": ["cortexm", "linux", "arm"],
|
||||||
"goos": "linux",
|
"goos": "linux",
|
||||||
"goarch": "arm",
|
"goarch": "arm",
|
||||||
"compiler": "clang-8",
|
"compiler": "clang",
|
||||||
"gc": "marksweep",
|
"gc": "precise",
|
||||||
"linker": "ld.lld",
|
"linker": "ld.lld",
|
||||||
"rtlib": "compiler-rt",
|
"rtlib": "compiler-rt",
|
||||||
"cflags": [
|
"cflags": [
|
||||||
"-Oz",
|
"-Oz",
|
||||||
"-mthumb",
|
"-mthumb",
|
||||||
|
"-Werror",
|
||||||
"-fshort-enums",
|
"-fshort-enums",
|
||||||
|
"-nostdlibinc",
|
||||||
|
"-Wno-macro-redefined",
|
||||||
"-fno-exceptions", "-fno-unwind-tables",
|
"-fno-exceptions", "-fno-unwind-tables",
|
||||||
"-ffunction-sections", "-fdata-sections"
|
"-ffunction-sections", "-fdata-sections"
|
||||||
],
|
],
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
"--target=armv6m-none-eabi",
|
"--target=armv6m-none-eabi",
|
||||||
"-Qunused-arguments",
|
"-Qunused-arguments",
|
||||||
"-DNRF51",
|
"-DNRF51",
|
||||||
"-Ilib/CMSIS/CMSIS/Include"
|
"-I{root}/lib/CMSIS/CMSIS/Include"
|
||||||
],
|
],
|
||||||
"ldflags": [
|
"ldflags": [
|
||||||
"-T", "targets/nrf51.ld"
|
"-T", "targets/nrf51.ld"
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
"-mfloat-abi=soft",
|
"-mfloat-abi=soft",
|
||||||
"-Qunused-arguments",
|
"-Qunused-arguments",
|
||||||
"-DNRF52832_XXAA",
|
"-DNRF52832_XXAA",
|
||||||
"-Ilib/CMSIS/CMSIS/Include"
|
"-I{root}/lib/CMSIS/CMSIS/Include"
|
||||||
],
|
],
|
||||||
"ldflags": [
|
"ldflags": [
|
||||||
"-T", "targets/nrf52.ld"
|
"-T", "targets/nrf52.ld"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"-mfloat-abi=soft",
|
"-mfloat-abi=soft",
|
||||||
"-Qunused-arguments",
|
"-Qunused-arguments",
|
||||||
"-DNRF52840_XXAA",
|
"-DNRF52840_XXAA",
|
||||||
"-Ilib/CMSIS/CMSIS/Include"
|
"-I{root}/lib/CMSIS/CMSIS/Include"
|
||||||
],
|
],
|
||||||
"ldflags": [
|
"ldflags": [
|
||||||
"-T", "targets/nrf52840.ld"
|
"-T", "targets/nrf52840.ld"
|
||||||
|
|||||||
+3
-1
@@ -3,10 +3,12 @@
|
|||||||
"build-tags": ["js", "wasm"],
|
"build-tags": ["js", "wasm"],
|
||||||
"goos": "js",
|
"goos": "js",
|
||||||
"goarch": "wasm",
|
"goarch": "wasm",
|
||||||
"compiler": "clang-8",
|
"compiler": "clang",
|
||||||
"linker": "wasm-ld",
|
"linker": "wasm-ld",
|
||||||
"cflags": [
|
"cflags": [
|
||||||
"--target=wasm32",
|
"--target=wasm32",
|
||||||
|
"-nostdlibinc",
|
||||||
|
"-Wno-macro-redefined",
|
||||||
"-Oz"
|
"-Oz"
|
||||||
],
|
],
|
||||||
"ldflags": [
|
"ldflags": [
|
||||||
|
|||||||
Vendored
+22
@@ -4,6 +4,22 @@ type Thing struct {
|
|||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ThingOption func(*Thing)
|
||||||
|
|
||||||
|
func WithName(name string) ThingOption {
|
||||||
|
return func(t *Thing) {
|
||||||
|
t.name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewThing(opts ...ThingOption) *Thing {
|
||||||
|
t := &Thing{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(t)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
func (t Thing) String() string {
|
func (t Thing) String() string {
|
||||||
return t.name
|
return t.name
|
||||||
}
|
}
|
||||||
@@ -36,6 +52,12 @@ func main() {
|
|||||||
runFunc(func(i int) {
|
runFunc(func(i int) {
|
||||||
println("inside fp closure:", thing.String(), i)
|
println("inside fp closure:", thing.String(), i)
|
||||||
}, 3)
|
}, 3)
|
||||||
|
|
||||||
|
// functional arguments
|
||||||
|
thingFunctionalArgs1 := NewThing()
|
||||||
|
thingFunctionalArgs1.Print("functional args 1")
|
||||||
|
thingFunctionalArgs2 := NewThing(WithName("named thing"))
|
||||||
|
thingFunctionalArgs2.Print("functional args 2")
|
||||||
}
|
}
|
||||||
|
|
||||||
func runFunc(f func(int), arg int) {
|
func runFunc(f func(int), arg int) {
|
||||||
|
|||||||
Vendored
+2
@@ -7,3 +7,5 @@ Thing.Print: foo arg: bar
|
|||||||
bound method: foo
|
bound method: foo
|
||||||
thing inside closure: foo
|
thing inside closure: foo
|
||||||
inside fp closure: foo 3
|
inside fp closure: foo 3
|
||||||
|
Thing.Print: arg: functional args 1
|
||||||
|
Thing.Print: named thing arg: functional args 2
|
||||||
|
|||||||
Vendored
+6
@@ -0,0 +1,6 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// Make sure CGo supports multiple files.
|
||||||
|
|
||||||
|
// int fortytwo(void);
|
||||||
|
import "C"
|
||||||
Vendored
+6
-2
@@ -1,13 +1,17 @@
|
|||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
|
||||||
int global = 3;
|
int global = 3;
|
||||||
_Bool globalBool = 1;
|
bool globalBool = 1;
|
||||||
_Bool globalBool2 = 10; // test narrowing
|
bool globalBool2 = 10; // test narrowing
|
||||||
float globalFloat = 3.1;
|
float globalFloat = 3.1;
|
||||||
double globalDouble = 3.2;
|
double globalDouble = 3.2;
|
||||||
_Complex float globalComplexFloat = 4.1+3.3i;
|
_Complex float globalComplexFloat = 4.1+3.3i;
|
||||||
_Complex double globalComplexDouble = 4.2+3.4i;
|
_Complex double globalComplexDouble = 4.2+3.4i;
|
||||||
_Complex double globalComplexLongDouble = 4.3+3.5i;
|
_Complex double globalComplexLongDouble = 4.3+3.5i;
|
||||||
|
char globalChar = 100;
|
||||||
|
void *globalVoidPtrSet = &global;
|
||||||
|
void *globalVoidPtrNull;
|
||||||
|
int64_t globalInt64 = -(2LL << 40);
|
||||||
collection_t globalStruct = {256, -123456, 3.14, 88};
|
collection_t globalStruct = {256, -123456, 3.14, 88};
|
||||||
int globalStructSize = sizeof(globalStruct);
|
int globalStructSize = sizeof(globalStruct);
|
||||||
short globalArray[3] = {5, 6, 7};
|
short globalArray[3] = {5, 6, 7};
|
||||||
|
|||||||
Vendored
+21
-7
@@ -7,11 +7,9 @@ int mul(int, int);
|
|||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import "unsafe"
|
import "C"
|
||||||
|
|
||||||
func (s C.myint) Int() int {
|
import "unsafe"
|
||||||
return int(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
println("fortytwo:", C.fortytwo())
|
println("fortytwo:", C.fortytwo())
|
||||||
@@ -22,6 +20,10 @@ func main() {
|
|||||||
var y C.longlong = -(1 << 40)
|
var y C.longlong = -(1 << 40)
|
||||||
println("longlong:", y)
|
println("longlong:", y)
|
||||||
println("global:", C.global)
|
println("global:", C.global)
|
||||||
|
println("defined ints:", C.CONST_INT, C.CONST_INT2)
|
||||||
|
println("defined floats:", C.CONST_FLOAT, C.CONST_FLOAT2)
|
||||||
|
println("defined string:", C.CONST_STRING)
|
||||||
|
println("defined char:", C.CONST_CHAR)
|
||||||
var ptr C.intPointer
|
var ptr C.intPointer
|
||||||
var n C.int = 15
|
var n C.int = 15
|
||||||
ptr = C.intPointer(&n)
|
ptr = C.intPointer(&n)
|
||||||
@@ -33,6 +35,10 @@ func main() {
|
|||||||
cb = C.binop_t(C.mul)
|
cb = C.binop_t(C.mul)
|
||||||
println("callback 2:", C.doCallback(20, 30, cb))
|
println("callback 2:", C.doCallback(20, 30, cb))
|
||||||
|
|
||||||
|
// equivalent types
|
||||||
|
var goInt8 int8 = 5
|
||||||
|
var _ C.int8_t = goInt8
|
||||||
|
|
||||||
// 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)
|
||||||
@@ -40,6 +46,10 @@ func main() {
|
|||||||
println("complex float:", C.globalComplexFloat)
|
println("complex float:", C.globalComplexFloat)
|
||||||
println("complex double:", C.globalComplexDouble)
|
println("complex double:", C.globalComplexDouble)
|
||||||
println("complex long double:", C.globalComplexLongDouble)
|
println("complex long double:", C.globalComplexLongDouble)
|
||||||
|
println("char match:", C.globalChar == 100)
|
||||||
|
var voidPtr unsafe.Pointer = C.globalVoidPtrNull
|
||||||
|
println("void* match:", voidPtr == nil, C.globalVoidPtrNull == nil, (*C.int)(C.globalVoidPtrSet) == &C.global)
|
||||||
|
println("int64_t match:", C.globalInt64 == C.int64_t(-(2<<40)))
|
||||||
|
|
||||||
// complex types
|
// complex types
|
||||||
println("struct:", C.int(unsafe.Sizeof(C.globalStruct)) == C.globalStructSize, C.globalStruct.s, C.globalStruct.l, C.globalStruct.f)
|
println("struct:", C.int(unsafe.Sizeof(C.globalStruct)) == C.globalStructSize, C.globalStruct.s, C.globalStruct.l, C.globalStruct.f)
|
||||||
@@ -53,10 +63,14 @@ func main() {
|
|||||||
C.unionSetData(5, 8, 1)
|
C.unionSetData(5, 8, 1)
|
||||||
println("union global data:", C.globalUnion.data[0], C.globalUnion.data[1], C.globalUnion.data[2])
|
println("union global data:", C.globalUnion.data[0], C.globalUnion.data[1], C.globalUnion.data[2])
|
||||||
println("union field:", printUnion(C.globalUnion).f)
|
println("union field:", printUnion(C.globalUnion).f)
|
||||||
|
var _ C.union_joined = C.globalUnion
|
||||||
|
|
||||||
|
// elaborated type
|
||||||
|
p := C.struct_point{x: 3, y: 5}
|
||||||
|
println("struct:", p.x, p.y)
|
||||||
|
|
||||||
// recursive types, test using a linked list
|
// recursive types, test using a linked list
|
||||||
lastElement := &C.list_t{n: 7, next: nil}
|
list := &C.list_t{n: 3, next: &C.struct_list_t{n: 6, next: &C.list_t{n: 7, next: nil}}}
|
||||||
list := &C.list_t{n: 3, next: &C.struct_list_t{n: 6, next: (*C.struct_list_t)(lastElement)}}
|
|
||||||
for list != nil {
|
for list != nil {
|
||||||
println("n in chain:", list.n)
|
println("n in chain:", list.n)
|
||||||
list = (*C.list_t)(list.next)
|
list = (*C.list_t)(list.next)
|
||||||
@@ -66,7 +80,7 @@ func main() {
|
|||||||
func printUnion(union C.joined_t) C.joined_t {
|
func printUnion(union C.joined_t) C.joined_t {
|
||||||
println("union local data: ", union.data[0], union.data[1], union.data[2])
|
println("union local data: ", union.data[0], union.data[1], union.data[2])
|
||||||
union.s = -33
|
union.s = -33
|
||||||
println("union s method:", union.s.Int(), union.data[0] == 5)
|
println("union s:", union.data[0] == -33)
|
||||||
union.f = 6.28
|
union.f = 6.28
|
||||||
println("union f:", union.f)
|
println("union f:", union.f)
|
||||||
return union
|
return union
|
||||||
|
|||||||
Vendored
+28
-3
@@ -1,10 +1,25 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
typedef short myint;
|
typedef short myint;
|
||||||
|
typedef short unusedTypedef;
|
||||||
int add(int a, int b);
|
int add(int a, int b);
|
||||||
|
int unusedFunction(void);
|
||||||
typedef int (*binop_t) (int, int);
|
typedef int (*binop_t) (int, int);
|
||||||
int doCallback(int a, int b, binop_t cb);
|
int doCallback(int a, int b, binop_t cb);
|
||||||
typedef int * intPointer;
|
typedef int * intPointer;
|
||||||
void store(int value, int *ptr);
|
void store(int value, int *ptr);
|
||||||
|
|
||||||
|
# define CONST_INT 5
|
||||||
|
# define CONST_INT2 5llu
|
||||||
|
# define CONST_FLOAT 5.8
|
||||||
|
# define CONST_FLOAT2 5.8f
|
||||||
|
# define CONST_CHAR 'c'
|
||||||
|
# define CONST_STRING "defined string"
|
||||||
|
|
||||||
|
// this signature should not be included by CGo
|
||||||
|
void unusedFunction2(int x, __builtin_va_list args);
|
||||||
|
|
||||||
typedef struct collection {
|
typedef struct collection {
|
||||||
short s;
|
short s;
|
||||||
long l;
|
long l;
|
||||||
@@ -12,6 +27,11 @@ typedef struct collection {
|
|||||||
unsigned char c;
|
unsigned char c;
|
||||||
} collection_t;
|
} collection_t;
|
||||||
|
|
||||||
|
struct point {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
};
|
||||||
|
|
||||||
// linked list
|
// linked list
|
||||||
typedef struct list_t {
|
typedef struct list_t {
|
||||||
int n;
|
int n;
|
||||||
@@ -27,15 +47,20 @@ void unionSetShort(short s);
|
|||||||
void unionSetFloat(float f);
|
void unionSetFloat(float f);
|
||||||
void unionSetData(short f0, short f1, short f2);
|
void unionSetData(short f0, short f1, short f2);
|
||||||
|
|
||||||
// test globals
|
// test globals and datatypes
|
||||||
extern int global;
|
extern int global;
|
||||||
extern _Bool globalBool;
|
extern int unusedGlobal;
|
||||||
extern _Bool globalBool2;
|
extern bool globalBool;
|
||||||
|
extern bool globalBool2;
|
||||||
extern float globalFloat;
|
extern float globalFloat;
|
||||||
extern double globalDouble;
|
extern double globalDouble;
|
||||||
extern _Complex float globalComplexFloat;
|
extern _Complex float globalComplexFloat;
|
||||||
extern _Complex double globalComplexDouble;
|
extern _Complex double globalComplexDouble;
|
||||||
extern _Complex double globalComplexLongDouble;
|
extern _Complex double globalComplexLongDouble;
|
||||||
|
extern char globalChar;
|
||||||
|
extern void *globalVoidPtrSet;
|
||||||
|
extern void *globalVoidPtrNull;
|
||||||
|
extern int64_t globalInt64;
|
||||||
extern collection_t globalStruct;
|
extern collection_t globalStruct;
|
||||||
extern int globalStructSize;
|
extern int globalStructSize;
|
||||||
extern short globalArray[3];
|
extern short globalArray[3];
|
||||||
|
|||||||
Vendored
+9
-1
@@ -4,6 +4,10 @@ myint: 3 5
|
|||||||
myint size: 2
|
myint size: 2
|
||||||
longlong: -1099511627776
|
longlong: -1099511627776
|
||||||
global: 3
|
global: 3
|
||||||
|
defined ints: 5 5
|
||||||
|
defined floats: +5.800000e+000 +5.800000e+000
|
||||||
|
defined string: defined string
|
||||||
|
defined char: 99
|
||||||
15: 15
|
15: 15
|
||||||
25: 25
|
25: 25
|
||||||
callback 1: 50
|
callback 1: 50
|
||||||
@@ -14,6 +18,9 @@ double: +3.200000e+000
|
|||||||
complex float: (+4.100000e+000+3.300000e+000i)
|
complex float: (+4.100000e+000+3.300000e+000i)
|
||||||
complex double: (+4.200000e+000+3.400000e+000i)
|
complex double: (+4.200000e+000+3.400000e+000i)
|
||||||
complex long double: (+4.300000e+000+3.500000e+000i)
|
complex long double: (+4.300000e+000+3.500000e+000i)
|
||||||
|
char match: true
|
||||||
|
void* match: true true true
|
||||||
|
int64_t match: true
|
||||||
struct: true 256 -123456 +3.140000e+000
|
struct: true 256 -123456 +3.140000e+000
|
||||||
array: 5 6 7
|
array: 5 6 7
|
||||||
union: true
|
union: true
|
||||||
@@ -21,9 +28,10 @@ union s: 22
|
|||||||
union f: +3.140000e+000
|
union f: +3.140000e+000
|
||||||
union global data: 5 8 1
|
union global data: 5 8 1
|
||||||
union local data: 5 8 1
|
union local data: 5 8 1
|
||||||
union s method: -33 false
|
union s: true
|
||||||
union f: +6.280000e+000
|
union f: +6.280000e+000
|
||||||
union field: +6.280000e+000
|
union field: +6.280000e+000
|
||||||
|
struct: 3 5
|
||||||
n in chain: 3
|
n in chain: 3
|
||||||
n in chain: 6
|
n in chain: 6
|
||||||
n in chain: 7
|
n in chain: 7
|
||||||
|
|||||||
Vendored
+10
-1
@@ -4,7 +4,7 @@ import "time"
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ch := make(chan int)
|
ch := make(chan int)
|
||||||
println("len, cap of channel:", len(ch), cap(ch))
|
println("len, cap of channel:", len(ch), cap(ch), ch == nil)
|
||||||
go sender(ch)
|
go sender(ch)
|
||||||
|
|
||||||
n, ok := <-ch
|
n, ok := <-ch
|
||||||
@@ -20,6 +20,11 @@ func main() {
|
|||||||
n, ok = <-ch
|
n, ok = <-ch
|
||||||
println("recv from closed channel:", n, ok)
|
println("recv from closed channel:", n, ok)
|
||||||
|
|
||||||
|
// Test bigger values
|
||||||
|
ch2 := make(chan complex128)
|
||||||
|
go sendComplex(ch2)
|
||||||
|
println("complex128:", <-ch2)
|
||||||
|
|
||||||
// Test multi-sender.
|
// Test multi-sender.
|
||||||
ch = make(chan int)
|
ch = make(chan int)
|
||||||
go fastsender(ch)
|
go fastsender(ch)
|
||||||
@@ -62,6 +67,10 @@ func sender(ch chan int) {
|
|||||||
close(ch)
|
close(ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sendComplex(ch chan complex128) {
|
||||||
|
ch <- 7+10.5i
|
||||||
|
}
|
||||||
|
|
||||||
func fastsender(ch chan int) {
|
func fastsender(ch chan int) {
|
||||||
ch <- 10
|
ch <- 10
|
||||||
ch <- 11
|
ch <- 11
|
||||||
|
|||||||
Vendored
+2
-1
@@ -1,4 +1,4 @@
|
|||||||
len, cap of channel: 0 0
|
len, cap of channel: 0 0 false
|
||||||
recv from open channel: 1 true
|
recv from open channel: 1 true
|
||||||
received num: 2
|
received num: 2
|
||||||
received num: 3
|
received num: 3
|
||||||
@@ -9,6 +9,7 @@ received num: 6
|
|||||||
received num: 7
|
received num: 7
|
||||||
received num: 8
|
received num: 8
|
||||||
recv from closed channel: 0 false
|
recv from closed channel: 0 false
|
||||||
|
complex128: (+7.000000e+000+1.050000e+001i)
|
||||||
got n: 10
|
got n: 10
|
||||||
got n: 11
|
got n: 11
|
||||||
got n: 10
|
got n: 10
|
||||||
|
|||||||
Vendored
+24
@@ -15,12 +15,19 @@ func main() {
|
|||||||
wait()
|
wait()
|
||||||
println("end waiting")
|
println("end waiting")
|
||||||
|
|
||||||
|
value := delayedValue()
|
||||||
|
println("value produced after some time:", value)
|
||||||
|
|
||||||
// Run a non-blocking call in a goroutine. This should be turned into a
|
// Run a non-blocking call in a goroutine. This should be turned into a
|
||||||
// regular call, so should be equivalent to calling nowait() without 'go'
|
// regular call, so should be equivalent to calling nowait() without 'go'
|
||||||
// prefix.
|
// prefix.
|
||||||
go nowait()
|
go nowait()
|
||||||
time.Sleep(time.Millisecond)
|
time.Sleep(time.Millisecond)
|
||||||
println("done with non-blocking goroutine")
|
println("done with non-blocking goroutine")
|
||||||
|
|
||||||
|
var printer Printer
|
||||||
|
printer = &myPrinter{}
|
||||||
|
printer.Print()
|
||||||
}
|
}
|
||||||
|
|
||||||
func sub() {
|
func sub() {
|
||||||
@@ -35,6 +42,23 @@ func wait() {
|
|||||||
println(" wait end")
|
println(" wait end")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func delayedValue() int {
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
|
||||||
func nowait() {
|
func nowait() {
|
||||||
println("non-blocking goroutine")
|
println("non-blocking goroutine")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Printer interface {
|
||||||
|
Print()
|
||||||
|
}
|
||||||
|
|
||||||
|
type myPrinter struct{
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *myPrinter) Print() {
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
println("async interface method call")
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+2
@@ -7,5 +7,7 @@ wait:
|
|||||||
wait start
|
wait start
|
||||||
wait end
|
wait end
|
||||||
end waiting
|
end waiting
|
||||||
|
value produced after some time: 42
|
||||||
non-blocking goroutine
|
non-blocking goroutine
|
||||||
done with non-blocking goroutine
|
done with non-blocking goroutine
|
||||||
|
async interface method call
|
||||||
|
|||||||
Vendored
+12
@@ -56,4 +56,16 @@ func main() {
|
|||||||
// cast complex
|
// cast complex
|
||||||
println(complex64(c128))
|
println(complex64(c128))
|
||||||
println(complex128(c64))
|
println(complex128(c64))
|
||||||
|
|
||||||
|
// binops on complex numbers
|
||||||
|
c64 = 5+2i
|
||||||
|
println("complex64 add: ", c64 + -3+8i)
|
||||||
|
println("complex64 sub: ", c64 - -3+8i)
|
||||||
|
println("complex64 mul: ", c64 * -3+8i)
|
||||||
|
println("complex64 div: ", c64 / -3+8i)
|
||||||
|
c128 = -5+2i
|
||||||
|
println("complex128 add:", c128 + 2+6i)
|
||||||
|
println("complex128 sub:", c128 - 2+6i)
|
||||||
|
println("complex128 mul:", c128 * 2+6i)
|
||||||
|
println("complex128 div:", c128 / 2+6i)
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+8
@@ -23,3 +23,11 @@
|
|||||||
(+2.000000e+000-2.000000e+000i)
|
(+2.000000e+000-2.000000e+000i)
|
||||||
(+6.666667e-001-2.000000e+000i)
|
(+6.666667e-001-2.000000e+000i)
|
||||||
(+6.666667e-001+1.200000e+000i)
|
(+6.666667e-001+1.200000e+000i)
|
||||||
|
complex64 add: (+2.000000e+000+1.000000e+001i)
|
||||||
|
complex64 sub: (+8.000000e+000+1.000000e+001i)
|
||||||
|
complex64 mul: (-1.500000e+001+2.000000e+000i)
|
||||||
|
complex64 div: (-1.666667e+000+7.333333e+000i)
|
||||||
|
complex128 add: (-3.000000e+000+8.000000e+000i)
|
||||||
|
complex128 sub: (-7.000000e+000+8.000000e+000i)
|
||||||
|
complex128 mul: (-1.000000e+001+1.000000e+001i)
|
||||||
|
complex128 div: (-2.500000e+000+7.000000e+000i)
|
||||||
|
|||||||
Vendored
+32
@@ -47,6 +47,18 @@ func main() {
|
|||||||
println(testMapArrayKey[arrKey])
|
println(testMapArrayKey[arrKey])
|
||||||
testMapArrayKey[arrKey] = 5555
|
testMapArrayKey[arrKey] = 5555
|
||||||
println(testMapArrayKey[arrKey])
|
println(testMapArrayKey[arrKey])
|
||||||
|
|
||||||
|
// test preallocated map
|
||||||
|
squares := make(map[int]int, 200)
|
||||||
|
testBigMap(squares, 100)
|
||||||
|
println("tested preallocated map")
|
||||||
|
|
||||||
|
// test growing maps
|
||||||
|
squares = make(map[int]int, 0)
|
||||||
|
testBigMap(squares, 10)
|
||||||
|
squares = make(map[int]int, 20)
|
||||||
|
testBigMap(squares, 40)
|
||||||
|
println("tested growing of a map")
|
||||||
}
|
}
|
||||||
|
|
||||||
func readMap(m map[string]int, key string) {
|
func readMap(m map[string]int, key string) {
|
||||||
@@ -56,7 +68,27 @@ func readMap(m map[string]int, key string) {
|
|||||||
println(" ", k, "=", v)
|
println(" ", k, "=", v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup(m map[string]int, key string) {
|
func lookup(m map[string]int, key string) {
|
||||||
value, ok := m[key]
|
value, ok := m[key]
|
||||||
println("lookup with comma-ok:", key, value, ok)
|
println("lookup with comma-ok:", key, value, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testBigMap(squares map[int]int, n int) {
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
if len(squares) != i {
|
||||||
|
println("unexpected length:", len(squares), "at i =", i)
|
||||||
|
}
|
||||||
|
squares[i] = i*i
|
||||||
|
for j := 0; j <= i; j++ {
|
||||||
|
if v, ok := squares[j]; !ok || v != j*j {
|
||||||
|
if !ok {
|
||||||
|
println("key not found in squares map:", j)
|
||||||
|
} else {
|
||||||
|
println("unexpected value read back from squares map:", j, v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+2
@@ -54,3 +54,5 @@ true false 0
|
|||||||
42
|
42
|
||||||
4321
|
4321
|
||||||
5555
|
5555
|
||||||
|
tested preallocated map
|
||||||
|
tested growing of a map
|
||||||
|
|||||||
+46
-4
@@ -151,12 +151,54 @@ def writeGo(outdir, device):
|
|||||||
// {description}
|
// {description}
|
||||||
package {pkgName}
|
package {pkgName}
|
||||||
|
|
||||||
import "unsafe"
|
import (
|
||||||
|
"runtime/volatile"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
// Special type that causes loads/stores to be volatile (necessary for
|
// Special type that causes loads/stores to be volatile (necessary for
|
||||||
// memory-mapped registers).
|
// memory-mapped registers).
|
||||||
//go:volatile
|
type Register8 struct {{
|
||||||
type RegValue uint8
|
Reg uint8
|
||||||
|
}}
|
||||||
|
|
||||||
|
// Get returns the value in the register. It is the volatile equivalent of:
|
||||||
|
//
|
||||||
|
// *r.Reg
|
||||||
|
//
|
||||||
|
//go:inline
|
||||||
|
func (r *Register8) Get() uint8 {{
|
||||||
|
return volatile.LoadUint8(&r.Reg)
|
||||||
|
}}
|
||||||
|
|
||||||
|
// Set updates the register value. It is the volatile equivalent of:
|
||||||
|
//
|
||||||
|
// *r.Reg = value
|
||||||
|
//
|
||||||
|
//go:inline
|
||||||
|
func (r *Register8) Set(value uint8) {{
|
||||||
|
volatile.StoreUint8(&r.Reg, value)
|
||||||
|
}}
|
||||||
|
|
||||||
|
// SetBits reads the register, sets the given bits, and writes it back. It is
|
||||||
|
// the volatile equivalent of:
|
||||||
|
//
|
||||||
|
// r.Reg |= value
|
||||||
|
//
|
||||||
|
//go:inline
|
||||||
|
func (r *Register8) SetBits(value uint8) {{
|
||||||
|
volatile.StoreUint8(&r.Reg, volatile.LoadUint8(&r.Reg) | value)
|
||||||
|
}}
|
||||||
|
|
||||||
|
// ClearBits reads the register, clears the given bits, and writes it back. It
|
||||||
|
// is the volatile equivalent of:
|
||||||
|
//
|
||||||
|
// r.Reg &^= value
|
||||||
|
//
|
||||||
|
//go:inline
|
||||||
|
func (r *Register8) ClearBits(value uint8) {{
|
||||||
|
volatile.StoreUint8(&r.Reg, volatile.LoadUint8(&r.Reg) &^ value)
|
||||||
|
}}
|
||||||
|
|
||||||
// Some information about this device.
|
// Some information about this device.
|
||||||
const (
|
const (
|
||||||
@@ -179,7 +221,7 @@ const (
|
|||||||
out.write('\n\t// {description}\n'.format(**peripheral))
|
out.write('\n\t// {description}\n'.format(**peripheral))
|
||||||
for register in peripheral['registers']:
|
for register in peripheral['registers']:
|
||||||
for variant in register['variants']:
|
for variant in register['variants']:
|
||||||
out.write('\t{name} = (*RegValue)(unsafe.Pointer(uintptr(0x{address:x})))\n'.format(**variant))
|
out.write('\t{name} = (*Register8)(unsafe.Pointer(uintptr(0x{address:x})))\n'.format(**variant))
|
||||||
out.write(')\n')
|
out.write(')\n')
|
||||||
|
|
||||||
for peripheral in device.peripherals:
|
for peripheral in device.peripherals:
|
||||||
|
|||||||
Reference in New Issue
Block a user