Compare commits

..

44 Commits

Author SHA1 Message Date
Ayke van Laethem 71fa131ef9 machine/dummy: add hooks to override peripheral methods 2019-05-12 07:36:46 +02:00
Ayke van Laethem eb0ce8a298 Makefile: avoid libtinfo and libz dependency of LLVM
These two dependencies are optional but enabled by default when
available. Disable them in the Makefile so that the tinygo binary is
portable to systems that don't have them or have a different version
(for example, Arch has a newer version of libcurses and thus libtinfo).
2019-05-11 15:47:15 +02:00
Ayke van Laethem 4ae4ef5e12 compiler: implement complex division
This is hard to do correctly, so copy the relevant files from the Go
compiler itself.

For related discussions:
* https://github.com/golang/go/issues/14644
* https://github.com/golang/go/issues/29846
2019-05-11 15:33:37 +02:00
Ayke van Laethem d7460b945e compiler: implement complex multiplication 2019-05-11 15:33:37 +02:00
Ayke van Laethem 638bc17eeb compiler: add support for complex add and sub
This is fairly trivial to add and follows the implementation of gc:
https://github.com/golang/go/blob/170b8b4b12be50eeccbcdadb8523fb4fc670ca72/src/cmd/compile/internal/gc/ssa.go#L2179-L2192
2019-05-11 15:33:37 +02:00
Justin Clift 1113f9ec0c main: comment the TinyGo IR header line
Without this, clang tries to process the header line as part of
its valid input. eg:

  main.ll:1:1: error: expected top-level entity
  Generated LLVM IR:
  ^
2019-05-10 22:50:18 +02:00
seph 019331e8af Add llvm directorys to gitignore
These are build artifacts
2019-05-09 19:20:39 +02:00
Justin Clift 4c8c048c49 example: just using 'Cache-Control': 'no-cache' should be good enough 2019-05-09 09:23:36 +02:00
Ayke van Laethem 08ee1916f5 main: fix multiple errors being reported as one 2019-05-08 19:37:08 +02:00
Ayke van Laethem 141a70f401 main: make $GOROOT more robust and configurable
Check various locations that $GOROOT may live, including the location of
the go binary. But make it possible to override this autodetection by
setting GOROOT manually as an environment variable.
2019-05-07 09:14:43 +02:00
Ayke van Laethem a79edf416c cgo: do not allow capturing of external/exported functions
Instead of assuming all declared (but not defined) functions are CGo
functions, mark all pointer params of externally visible symbols
'nocapture'. This means you may not store pointers between function
calls.

This is already the case when calling CGo functions upstream:
https://golang.org/cmd/cgo/#hdr-Passing_pointers
2019-05-05 20:56:35 +02:00
Ron Evans 2511aefac0 docker: perform a hard submodule reset after having moved the git repos directory
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-05-05 17:30:26 +02:00
Ayke van Laethem 4978065c9c cgo: avoid file/lineno hack for error locations 2019-05-05 17:07:35 +02:00
Ayke van Laethem 78a26fec13 cgo: be able to deal with nil files
I'm not sure where they come from but they lead to a crash, so turn them
into token.NoPos.
2019-05-05 17:07:35 +02:00
Ayke van Laethem 9cad8bd0c8 main: add fallback mechanism for LLVM commands
On Debian, all LLVM commands have a version suffix (clang-8, ld.lld-8,
wasm-ld-8, etc.). However. Most other distributions only provide a
version prefix for Clang and not for all the other commands.

This commit fixes the issue by trying the command with the version
suffix first and falling back to one without if needed.
2019-05-05 17:00:33 +02:00
Ayke van Laethem 9a54ee4241 compiler: allow larger-than-int values to be sent across a channel
Instead of storing the value to send/receive in the coroutine promise,
store only a pointer in the promise. This simplifies the code a lot and
allows larger value sizes to be sent across a channel.

Unfortunately, this new system has a code size impact. For example,
compiling testdata/channel.go for the BBC micro:bit, there is an
increase in code size from 4776 bytes to 4856 bytes. However, the
improved flexibility and simplicity of the code should be worth it. If
this becomes an issue, we can always refactor the code at a later time.
2019-05-05 16:46:50 +02:00
Ayke van Laethem 46d5ea8cf6 compiler: support returning values from async functions
This is implemented as follows:

  * The parent coroutine allocates space for the return value in its
    frame and stores a pointer to this frame in the parent coroutine
    handle.
  * The child coroutine obtains the alloca from its parent using the
    parent coroutine handle. It then stores the result value there.
  * The parent value reads the data from the alloca on resumption.
2019-05-05 16:46:50 +02:00
Daniel Esteban fb952a722a Remove microbit matrix (#319)
* Remove matrix code from bbc:microbit, and move it to a driver
2019-05-05 16:25:50 +02:00
Michael Teichgräber 7e46c1766d compiler: fix comp. of func calls for func values of a defined type
When compiling a piece of code where a function value is called,
the compiler panics if the function value's type is a defined type,
and not just a type literal (function signature): The type assertion
(*types.Signature) fails, because the type of the func value is a
*types.Named.

This patch fixes this by using the type's underlying type, so that a
types.Named is properly turned into its underlying types.Signature,
before the type assertion takes place.
It takes advantage of the property that all types have an underlying type
(both are the same, if a type is not named).

Fixes #320
2019-05-03 15:41:00 +02:00
Ayke van Laethem 1f0595438e main: do not set working directory for Clang invocation
This commit avoids setting the working directory to the TinyGo root when
invocating Clang. This helps to weed out issues before we add support
for bundling Clang in a release.
2019-05-03 11:36:24 +02:00
Justin Clift d594342642 examples: tell browsers to not cache wasm files from the example server 2019-05-02 14:13:50 +01:00
Ayke van Laethem 99da328453 compiler: avoid bitcast when replacing a method call with a direct call
A bitcast was inserted when the receiver of the call wasn't a *i8. This
is a pretty common case, and did not play well with goroutines.
Avoid this bitcast by changing each call to a direct call, after
unpacking the receiver type from the *i8 parameter. This might also fix
some undefined behavior in the resulting program, as it is technically
not allowed to call a function with a different signature (even if the
signature is compatible).
2019-05-01 12:12:30 +02:00
Ayke van Laethem 387e1340bf compiler: refactor packing of word-sized values in integers
There are two places that try to store values directly in pointers, if
possible: closures and interfaces. Use the same functions for both.
2019-05-01 12:12:30 +02:00
Ayke van Laethem b1ed8a46b7 cgo: only include the symbols that are necessary (recursively)
Only try to convert the C symbols to their Go equivalents that are
actually referenced by the Go code with C.<somesymbol>. This avoids
having to support all possible C types, which is difficult because of
oddities like `typedef void` or `__builtin_va_list`. Especially
__builtin_va_list, which varies between targets.
2019-05-01 11:33:18 +02:00
Ayke van Laethem 35af33ead7 cgo: improve typedef/struct/enum support
Typedefs are now Go type aliases. And C.struct_ and C.union_ prefixed
records work correctly now, even when they're not in a typedef.
2019-05-01 11:33:18 +02:00
Justin Clift 4bd1b9e53d wasm: use println instead of fmt
The generated wasm is 575 bytes when compiled with -no-debug (and
works), which is a much better first experience for new users than
the 20KB+ added (atm) just from including fmt.
2019-05-01 10:35:18 +02:00
Ayke van Laethem 80ee343e6d main: make tests more portable
Windows uses backward slashes instead of forward slashes, so be
compatible with that.
2019-04-30 20:04:04 +02:00
Ayke van Laethem 1d59a960bc main: allow changing the clang command name 2019-04-30 20:04:04 +02:00
Ayke van Laethem 5ca2e1322c main: close ar file before moving it
Moving a file is not allowed on Windows when a program still has the
file open.
2019-04-30 20:04:04 +02:00
Ayke van Laethem 5b0b35f9e4 main: use os.UserCacheDir to get a cache directory
This is more portable than assuming the cache directory lies at
~/.cache.
2019-04-30 20:04:04 +02:00
Ayke van Laethem 9a3d0683b3 compiler: mark all GEPs as inbounds
In Go, it is not possible to construct pointers that are out of bounds
(and not null), so let LLVM know about this fact.

This leads to a significant code size reduction, around 3% in many
cases.
2019-04-26 09:17:52 +02:00
Ayke van Laethem d155e31b64 all: improve compiler error handling
Most of these errors are actually "todo" or "unimplemented" errors, so
the return type is known. This means that compilation can proceed (with
errors) even though the output will be incorrect. This is useful because
this way, all errors in a compilation unit can be shown together to the
user.
2019-04-26 08:52:10 +02:00
Ayke van Laethem 45cacda7b3 compiler: refactor parseExpr
This commit adds getValue which gets a const, global, or result of a
local SSA expression and replaces (almost) all uses of parseExpr with
getValue. The only remaining use is in parseInstr, which makes sure an
instruction is only evaluated once.
2019-04-26 08:52:10 +02:00
Ayke van Laethem c25fe609a9 compiler: do not return an error from getLLVMType
This commit replaces "unknown type" errors in getLLVMType with panics.

The main reason this is done is that it simplifies the code *a lot*.
Many `if err != nil` lines were there just because of type information.
Additionally, simply panicking is probably a better approach as the only
way this error can be produced is either with big new language features
or a serious compiler bug. Panicking is probably a better way to handle
this error anyway.
2019-04-26 08:52:10 +02:00
Ayke van Laethem 6d23809218 compiler: simplify code around getZeroValue
The LLVM library we use does not (yet) provide a llvm.Zero (like it
provides a llvm.Undef) so we have implemented our own. However, in
theory it might return an error in some cases.

No real-world errors have been seen in a while and errors would likely
indicate a serious compiler bug anyway (not an external error), so make
it panic instead of returning an error.
2019-04-26 08:52:10 +02:00
Ayke van Laethem 024eceb476 runtime: print error when panicking with error interface type 2019-04-25 14:06:34 +02:00
Ayke van Laethem 0fd90c49cc compiler: make panic configurable
Currently defined: abort and trap. -panic=unwind should be implemented
in the future.
2019-04-25 13:56:19 +02:00
Ayke van Laethem d1efffe96b test: print better error messages on compilation failure 2019-04-25 12:55:52 +02:00
Ayke van Laethem 8e7ea92d44 cgo: improve error locations for cgo-constructed AST
This is mostly useful for debugging missing type conversions in CGo.
With this change, errors will have the correct source location in C
files.
2019-04-25 12:55:52 +02:00
Ayke van Laethem 2f2d62cc0c cgo: support builtin #include headers
Add support for header files bundled with the compiler by copying them
into the release tarball.
2019-04-25 12:55:52 +02:00
Ayke van Laethem d396abb690 cgo: add dummy implementation of __builtin_va_list
Every ABI has a slightly different implementation. Ideally, we would use
something like Clang TargetInfo or extract it by compiling some C code
and checking the IR, but this is a useful workaround for now.
2019-04-25 10:48:56 +02:00
Ayke van Laethem b815d3f760 cgo: implement void* pointer type
void* is translated to unsafe.Pointer on the Go side.
2019-04-25 10:48:56 +02:00
Ayke van Laethem 9c46ac4eed cgo: implement char type
This type is a bit more difficult because it can be signed or unsigned
depending on the target platform.
2019-04-25 10:48:56 +02:00
Ron Evans b2e96fc35a machine/atsamd21: select internal ground for ADC and scale result correctly to 16-bit
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-04-22 07:59:35 +02:00
62 changed files with 1696 additions and 1464 deletions
+18 -15
View File
@@ -47,6 +47,18 @@ commands:
command: |
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
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:
steps:
- smoketest-no-avr
@@ -86,6 +98,7 @@ commands:
keys:
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-{{ checksum "Gopkg.lock" }}
- llvm-source-linux
- dep
- run: go install .
- run: go test -v
@@ -105,7 +118,6 @@ commands:
name: "Install apt dependencies"
command: |
sudo apt-get install \
libtinfo-dev \
python3 \
gcc-arm-linux-gnueabihf \
binutils-arm-none-eabi \
@@ -121,19 +133,10 @@ commands:
keys:
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-{{ checksum "Gopkg.lock" }}
- llvm-source-linux
- 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
- restore_cache:
keys:
- llvm-build-8-v2
- llvm-build-8-linux-v4
- run:
name: "Build LLVM"
command: |
@@ -151,7 +154,7 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-8-v2
key: llvm-build-8-linux-v4
paths:
llvm-build
- run:
@@ -204,7 +207,7 @@ commands:
- llvm
- restore_cache:
keys:
- llvm-build-8-macos-v2
- llvm-build-8-macos-v3
- run:
name: "Build LLVM"
command: |
@@ -216,7 +219,7 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-8-macos-v2
key: llvm-build-8-macos-v3
paths:
llvm-build
- run:
+2
View File
@@ -10,3 +10,5 @@ src/device/stm32/*.s
src/device/sam/*.go
src/device/sam/*.s
vendor
llvm
llvm-build
+4 -1
View File
@@ -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
# 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/ && \
git submodule update --init
rm -rf ./lib/* && \
git submodule update --init --recursive --force
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
dep ensure --vendor-only && \
+3
View File
@@ -1,5 +1,8 @@
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
modification, are permitted provided that the following conditions are
met:
+4 -1
View File
@@ -69,7 +69,7 @@ llvm-source: llvm/README.txt llvm/tools/clang/README.txt llvm/tools/lld/README.m
# Configure LLVM.
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.
llvm-build: llvm-build/build.ninja
@@ -86,13 +86,16 @@ test:
release: build/tinygo gen-device
@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/compiler-rt/lib
@mkdir -p build/release/tinygo/lib/nrfx
@mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
@mkdir -p build/release/tinygo/pkg/armv7m-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 $(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/README.md build/release/tinygo/lib/CMSIS
@cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib
+5 -3
View File
@@ -9,9 +9,11 @@ import (
// Get the cache directory, usually ~/.cache/tinygo
func cacheDir() string {
home := getHomeDir()
dir := filepath.Join(home, ".cache", "tinygo")
return dir
dir, err := os.UserCacheDir()
if err != nil {
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
+4 -8
View File
@@ -5,7 +5,6 @@ import (
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
@@ -192,13 +191,13 @@ func loadBuiltins(target string) (path string, err error) {
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
}
var cachepath string
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
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
// reproducible. Otherwise the temporary directory is stored in the
// 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)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = dir
err = cmd.Run()
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)
if err != nil {
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,
// because after it returns the temporary directory will be removed.
arfile.Close()
return callback(arpath)
}
+30 -7
View File
@@ -1,10 +1,33 @@
// +build !darwin
package main
// commands used by the compilation process might have different file names on Linux than those used on macOS.
var commands = map[string]string{
"clang": "clang-8",
"ld.lld": "ld.lld-8",
"wasm-ld": "wasm-ld-8",
import (
"errors"
"os"
"os/exec"
"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, " "))
}
-10
View File
@@ -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",
}
+1 -4
View File
@@ -123,10 +123,7 @@ func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value)
switch t.TypeKind() {
case llvm.StructTypeKind:
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
value, err := c.getZeroValue(t)
if err != nil {
panic("could not get zero value of struct: " + err.Error())
}
value := c.getZeroValue(t)
for i, subtyp := range t.StructElementTypes() {
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
fields = remaining
+37 -49
View File
@@ -12,16 +12,6 @@ import (
// emitMakeChan returns a new channel value for the given channel type.
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")
size := c.targetData.TypeAllocSize(chanType)
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
// channel send operation during goroutine lowering.
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) error {
valueType, err := c.getLLVMType(instr.Chan.Type().(*types.Chan).Elem())
if err != nil {
return err
}
ch, err := c.parseExpr(frame, instr.Chan)
if err != nil {
return err
}
chanValue, err := c.parseExpr(frame, instr.X)
if err != nil {
return err
}
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
valueType := c.getLLVMType(instr.X.Type())
ch := c.getValue(frame, instr.Chan)
chanValue := c.getValue(frame, instr.X)
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")
c.builder.SetInsertPointBefore(coroutine)
c.builder.SetInsertPointAtEnd(coroutine.InstructionParent())
c.builder.CreateStore(chanValue, valueAlloca)
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
// actual channel receive operation during goroutine lowering.
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
valueType, err := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
if err != nil {
return llvm.Value{}, err
}
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
ch, err := c.parseExpr(frame, unop.X)
if err != nil {
return llvm.Value{}, err
}
ch := c.getValue(frame, unop.X)
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
// Allocate memory to receive into.
c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
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")
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")
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 = c.builder.CreateInsertValue(tuple, received, 0, "")
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
return tuple, nil
return tuple
} else {
return received, nil
return received
}
}
// emitChanClose closes the given channel.
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) error {
valueType, err := c.getLLVMType(param.Type().(*types.Chan).Elem())
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
valueType := c.getLLVMType(param.Type().(*types.Chan).Elem())
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
if err != nil {
return err
}
ch, err := c.parseExpr(frame, param)
if err != nil {
return err
}
ch := c.getValue(frame, param)
c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
return nil
}
+348 -601
View File
File diff suppressed because it is too large Load Diff
+19 -53
View File
@@ -36,7 +36,7 @@ func (c *Compiler) deferInitFunc(frame *Frame) {
// emitDefer emits a single defer instruction, to be run when this function
// 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
// make a linked list.
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
// runtime._defer fields, followed by the call parameters).
itf, err := c.parseExpr(frame, instr.Call.Value) // interface
if err != nil {
return err
}
itf := c.getValue(frame, instr.Call.Value) // interface
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = []llvm.Value{callback, next, receiverValue}
valueTypes = append(valueTypes, c.i8ptrType)
for _, arg := range instr.Call.Args {
val, err := c.parseExpr(frame, arg)
if err != nil {
return err
}
val := c.getValue(frame, arg)
values = append(values, val)
valueTypes = append(valueTypes, val.Type())
}
@@ -86,10 +80,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
// runtime._defer fields).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam, err := c.parseExpr(frame, param)
if err != nil {
return err
}
llvmParam := c.getValue(frame, param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
@@ -101,10 +92,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
// pointer.
// TODO: ignore this closure entirely and put pointers to the free
// variables directly in the defer struct, avoiding a memory allocation.
closure, err := c.parseExpr(frame, instr.Call.Value)
if err != nil {
return err
}
closure := c.getValue(frame, instr.Call.Value)
context := c.builder.CreateExtractValue(closure, 0, "")
// Get the callback number.
@@ -120,10 +108,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
// context pointer).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam, err := c.parseExpr(frame, param)
if err != nil {
return err
}
llvmParam := c.getValue(frame, param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
@@ -131,15 +116,13 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
valueTypes = append(valueTypes, context.Type())
} 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.
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFrame, err := c.getZeroValue(deferFrameType)
if err != nil {
return err
}
deferFrame := c.getZeroValue(deferFrameType)
for i, value := range values {
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.
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
c.builder.CreateStore(allocaCast, frame.deferPtr)
return nil
}
// 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:
// for stack != nil {
// _stack := stack
@@ -190,13 +172,13 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
// stack = stack.next
// switch stack.callback {
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(), 1, false), // .next field
}, "stack.next.gep")
nextStack := c.builder.CreateLoad(nextStackGEP, "stack.next")
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), // .callback field
}, "callback.gep")
@@ -220,11 +202,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
// 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}
for _, arg := range callback.Args {
llvmType, err := c.getLLVMType(arg.Type())
if err != nil {
return err
}
valueTypes = append(valueTypes, llvmType)
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
}
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
@@ -233,7 +211,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
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")
forwardParams = append(forwardParams, forwardParam)
}
@@ -246,10 +224,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
fnPtr, _, err := c.getInvokeCall(frame, callback)
if err != nil {
return err
}
fnPtr, _ := c.getInvokeCall(frame, callback)
c.createCall(fnPtr, forwardParams, "")
case *ir.Function:
@@ -258,11 +233,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
for _, param := range callback.Params {
llvmType, err := c.getLLVMType(param.Type())
if err != nil {
return err
}
valueTypes = append(valueTypes, llvmType)
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
}
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
@@ -271,7 +242,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
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")
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)}
params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ {
llvmType, err := c.getLLVMType(params.At(i).Type())
if err != nil {
return err
}
valueTypes = append(valueTypes, llvmType)
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
}
valueTypes = append(valueTypes, c.i8ptrType) // closure
deferFrameType := c.ctx.StructType(valueTypes, false)
@@ -306,7 +273,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
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")
forwardParams = append(forwardParams, forwardParam)
}
@@ -334,5 +301,4 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
// End of loop.
c.builder.SetInsertPointAtEnd(end)
return nil
}
+4
View File
@@ -12,3 +12,7 @@ func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
Msg: msg,
}
}
func (c *Compiler) addError(pos token.Pos, msg string) {
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
}
+22 -85
View File
@@ -41,7 +41,7 @@ func (c *Compiler) funcImplementation() funcValueImplementation {
// createFuncValue creates a function value from a raw function pointer with no
// 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
switch c.funcImplementation() {
case funcValueDoubleword:
@@ -66,14 +66,11 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
default:
panic("unimplemented func value variant")
}
funcValueType, err := c.getFuncType(sig)
if err != nil {
return llvm.Value{}, err
}
funcValueType := c.getFuncType(sig)
funcValue := llvm.Undef(funcValueType)
funcValue = c.builder.CreateInsertValue(funcValue, context, 0, "")
funcValue = c.builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
return funcValue, nil
return funcValue
}
// 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:
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
case funcValueSwitch:
llvmSig, err := c.getRawFuncType(sig)
if err != nil {
return llvm.Value{}, llvm.Value{}, err
}
llvmSig := c.getRawFuncType(sig)
sigGlobal := c.getFuncSignature(sig)
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
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.
func (c *Compiler) getFuncType(typ *types.Signature) (llvm.Type, error) {
func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
switch c.funcImplementation() {
case funcValueDoubleword:
rawPtr, err := c.getRawFuncType(typ)
if err != nil {
return llvm.Type{}, err
}
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false), nil
rawPtr := c.getRawFuncType(typ)
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
case funcValueSwitch:
return c.mod.GetTypeByName("runtime.funcValue"), nil
return c.mod.GetTypeByName("runtime.funcValue")
default:
panic("unimplemented func value variant")
}
}
// 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.
var err error
var returnType llvm.Type
switch typ.Results().Len() {
case 0:
@@ -152,21 +142,14 @@ func (c *Compiler) getRawFuncType(typ *types.Signature) (llvm.Type, error) {
returnType = c.ctx.VoidType()
case 1:
// Just one return value.
returnType, err = c.getLLVMType(typ.Results().At(0).Type())
if err != nil {
return llvm.Type{}, err
}
returnType = c.getLLVMType(typ.Results().At(0).Type())
default:
// Multiple return values. Put them together in a struct.
// This appears to be the common way to handle multiple return values in
// LLVM.
members := make([]llvm.Type, typ.Results().Len())
for i := 0; i < typ.Results().Len(); i++ {
returnType, err := c.getLLVMType(typ.Results().At(i).Type())
if err != nil {
return llvm.Type{}, err
}
members[i] = returnType
members[i] = c.getLLVMType(typ.Results().At(i).Type())
}
returnType = c.ctx.StructType(members, false)
}
@@ -174,10 +157,7 @@ func (c *Compiler) getRawFuncType(typ *types.Signature) (llvm.Type, error) {
// Get the parameter types.
var paramTypes []llvm.Type
if typ.Recv() != nil {
recv, err := c.getLLVMType(typ.Recv().Type())
if err != nil {
return llvm.Type{}, err
}
recv := c.getLLVMType(typ.Recv().Type())
if recv.StructName() == "runtime._interface" {
// This is a call on an interface, not a concrete 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)...)
}
for i := 0; i < typ.Params().Len(); i++ {
subType, err := c.getLLVMType(typ.Params().At(i).Type())
if err != nil {
return llvm.Type{}, err
}
subType := c.getLLVMType(typ.Params().At(i).Type())
paramTypes = append(paramTypes, c.expandFormalParamType(subType)...)
}
// 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
// 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
@@ -209,57 +186,17 @@ func (c *Compiler) parseMakeClosure(frame *Frame, expr *ssa.MakeClosure) (llvm.V
f := c.ir.GetFunction(expr.Fn.(*ssa.Function))
// Collect all bound variables.
boundVars := make([]llvm.Value, 0, len(expr.Bindings))
boundVarTypes := make([]llvm.Type, 0, len(expr.Bindings))
for _, binding := range expr.Bindings {
boundVars := make([]llvm.Value, len(expr.Bindings))
for i, binding := range expr.Bindings {
// The context stores the bound variables.
llvmBoundVar, err := c.parseExpr(frame, binding)
if err != nil {
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), "")
llvmBoundVar := c.getValue(frame, binding)
boundVars[i] = llvmBoundVar
}
// Store all bound variables in the alloca or heap pointer.
for i, boundVar := range boundVars {
indices := []llvm.Value{
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
}
// Store the bound variables in a single object, allocating it on the heap
// if necessary.
context := c.emitPointerPack(boundVars)
// Create the closure.
return c.createFuncValue(f.LLVMFn, context, f.Signature)
return c.createFuncValue(f.LLVMFn, context, f.Signature), nil
}
+94 -121
View File
@@ -10,8 +10,8 @@ package compiler
// go foo()
// time.Sleep(2 * time.Second)
// println("some other operation")
// bar()
// println("done")
// i := bar()
// println("done", *i)
// }
//
// func foo() {
@@ -21,9 +21,10 @@ package compiler
// }
// }
//
// func bar() {
// func bar() *int {
// time.Sleep(time.Second)
// println("blocking operation completed)
// return new(int)
// }
//
// It is transformed by the IR generator in compiler.go into the following
@@ -34,8 +35,8 @@ package compiler
// fn()
// time.Sleep(2 * time.Second)
// println("some other operation")
// bar() // imagine an 'await' keyword in front of this call
// println("done")
// i := bar() // imagine an 'await' keyword in front of this call
// println("done", *i)
// }
//
// func foo() {
@@ -45,9 +46,10 @@ package compiler
// }
// }
//
// func bar() {
// func bar() *int {
// time.Sleep(time.Second)
// println("blocking operation completed)
// return new(int)
// }
//
// 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
// llvm.suspend(hdl) // suspend point
// 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
// 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)
// }
//
@@ -142,10 +146,9 @@ func (c *Compiler) LowerGoroutines() error {
realMain.SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.alloc").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.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)
return nil
@@ -174,13 +177,13 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
if !deadlockStub.IsNil() {
worklist = append(worklist, deadlockStub)
}
chanSendStub := c.mod.NamedFunction("runtime.chanSendStub")
if !chanSendStub.IsNil() {
worklist = append(worklist, chanSendStub)
chanSend := c.mod.NamedFunction("runtime.chanSend")
if !chanSend.IsNil() {
worklist = append(worklist, chanSend)
}
chanRecvStub := c.mod.NamedFunction("runtime.chanRecvStub")
if !chanRecvStub.IsNil() {
worklist = append(worklist, chanRecvStub)
chanRecv := c.mod.NamedFunction("runtime.chanRecv")
if !chanRecv.IsNil() {
worklist = append(worklist, chanRecv)
}
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)
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)
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.
for _, f := range asyncList {
if f == sleep || f == deadlockStub || f == chanSendStub || f == chanRecvStub {
if f == sleep || f == deadlockStub || f == chanSend || f == chanRecv {
continue
}
@@ -309,7 +309,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if !inst.IsACallInst().IsNil() {
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
}
asyncCalls = append(asyncCalls, inst)
@@ -347,10 +347,18 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// Split this basic block.
await := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.await")
// Set task state to TASK_STATE_CALL.
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
// Allocate space for the return value.
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.
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
@@ -358,44 +366,63 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
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(), 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
// reactivate the parent coroutine.
for _, inst := range returns {
if inst.OperandsCount() == 0 {
// These properties were added by the functionattrs pass.
// Remove them, because now we start using the parameter.
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
for _, kind := range []string{"nocapture", "readnone"} {
kindID := llvm.AttributeKindID(kind)
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)
parentHandle := f.LastParam()
c.createRuntimeCall("activateTask", []llvm.Value{parentHandle}, "")
// Suspend this coroutine.
// It would look like this is unnecessary, but if this
// suspend point is left out, it leads to undefined
// behavior somehow (with the unreachable instruction).
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 1, false),
}, "ret")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
inst.EraseFromParentAsInstruction()
} else {
panic("todo: return value from coroutine")
// These properties were added by the functionattrs pass. Remove
// them, because now we start using the parameter.
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
for _, kind := range []string{"nocapture", "readnone"} {
kindID := llvm.AttributeKindID(kind)
f.RemoveEnumAttributeAtIndex(f.ParamsCount(), kindID)
}
c.builder.SetInsertPointBefore(inst)
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}, "")
// Suspend this coroutine.
// It would look like this is unnecessary, but if this
// suspend point is left out, it leads to undefined
// behavior somehow (with the unreachable instruction).
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 1, false),
}, "ret")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
inst.EraseFromParentAsInstruction()
}
// Coroutine cleanup. Free resources associated with this coroutine.
@@ -420,6 +447,14 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
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.
for _, sleepCall := range getUses(sleep) {
// sleepCall must be a call instruction.
@@ -463,37 +498,11 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
deadlockCall.EraseFromParentAsInstruction()
}
// Transform calls to runtime.chanSendStub into channel send operations.
for _, sendOp := range getUses(chanSendStub) {
// Transform calls to runtime.chanSend into channel send operations.
for _, sendOp := range getUses(chanSend) {
// sendOp must be a call instruction.
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.
c.builder.SetInsertPointBefore(llvm.NextInstruction(sendOp))
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)
}
// Transform calls to runtime.chanRecvStub into channel receive operations.
for _, recvOp := range getUses(chanRecvStub) {
// Transform calls to runtime.chanRecv into channel receive operations.
for _, recvOp := range getUses(chanRecv) {
// recvOp must be a call instruction.
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.
c.builder.SetInsertPointBefore(llvm.NextInstruction(recvOp))
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
@@ -532,32 +531,6 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
c.builder.SetInsertPointAtEnd(recvOp.InstructionParent())
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
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()
+2 -9
View File
@@ -68,11 +68,7 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
}
key := constant.StringVal(r.Key.(*ssa.Const).Value)
//println("value:", r.Value.(*ssa.MakeInterface).X.String())
value, err := c.parseExpr(frame, r.Value.(*ssa.MakeInterface).X)
if err != nil {
return llvm.Value{}, err
}
registers[key] = value
registers[key] = c.getValue(frame, r.Value.(*ssa.MakeInterface).X)
case *ssa.Call:
if r.Common() == instr {
break
@@ -145,10 +141,7 @@ func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error
} else {
constraints += ",{r" + strconv.Itoa(i) + "}"
}
llvmValue, err := c.parseExpr(frame, arg)
if err != nil {
return llvm.Value{}, err
}
llvmValue := c.getValue(frame, arg)
llvmArgs = append(llvmArgs, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
+33 -4
View File
@@ -527,11 +527,40 @@ func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInf
}
inttoptr := inttoptrs[0]
function := typ.getMethod(signature).function
if inttoptr.Type() != function.Type() {
p.builder.SetInsertPointBefore(use)
function = p.builder.CreateBitCast(function, inttoptr.Type(), "")
if inttoptr.Type() == function.Type() {
// Easy case: the types are the same. Simply replace the inttoptr
// result (which is directly called) with the actual 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.ReplaceAllUsesWith(function)
inttoptr.EraseFromParentAsInstruction()
use.EraseFromParentAsInstruction()
}
+17 -122
View File
@@ -23,37 +23,7 @@ import (
//
// 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) {
var itfValue llvm.Value
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*")
}
}
itfValue := c.emitPointerPack([]llvm.Value{val})
itfTypeCodeGlobal := c.getTypeCode(typ)
itfMethodSetGlobal, err := c.getTypeMethodSet(typ)
if err != nil {
@@ -274,19 +244,9 @@ func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
//
// Type asserts on concrete types are trivial: just compare type numbers. Type
// asserts on interfaces are more difficult, see the comments in the function.
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Value, error) {
itf, err := c.parseExpr(frame, expr.X)
if err != nil {
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
}
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Value {
itf := c.getValue(frame, expr.X)
assertedType := c.getLLVMType(expr.AssertedType)
actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type")
commaOk := llvm.Value{}
@@ -339,67 +299,35 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Val
// Type assert on concrete type. Extract the underlying type from
// the interface (but only after checking it matches).
valuePtr := c.builder.CreateExtractValue(itf, 1, "typeassert.value.ptr")
size := c.targetData.TypeAllocSize(assertedType)
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")
}
}
valueOk = c.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0]
}
c.builder.CreateBr(nextBlock)
// Continue after the if statement.
c.builder.SetInsertPointAtEnd(nextBlock)
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 {
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, commaOk, 1, "") // insert 'comma ok' boolean
return tuple, nil
return tuple
} else {
// This is kind of dirty as the branch above becomes mostly useless,
// but hopefully this gets optimized away.
c.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
return phi, nil
return phi
}
}
// getInvokeCall creates and returns the function pointer and parameters of an
// 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.
itf, err := c.parseExpr(frame, instr.Value) // interface
if err != nil {
return llvm.Value{}, nil, err
}
itf := c.getValue(frame, instr.Value) // interface
llvmFnType, err := c.getRawFuncType(instr.Method.Type().(*types.Signature))
if err != nil {
return llvm.Value{}, nil, err
}
llvmFnType := c.getRawFuncType(instr.Method.Type().(*types.Signature))
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
values := []llvm.Value{
@@ -413,11 +341,7 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
args := []llvm.Value{receiverValue}
for _, arg := range instr.Args {
val, err := c.parseExpr(frame, arg)
if err != nil {
return llvm.Value{}, nil, err
}
args = append(args, val)
args = append(args, c.getValue(frame, arg))
}
// Add the context parameter. An interface call never takes a context but we
// have to supply the parameter anyway.
@@ -425,7 +349,7 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
// Add the parent goroutine handle.
args = append(args, llvm.Undef(c.i8ptrType))
return fnCast, args, nil
return fnCast, args
}
// interfaceInvokeWrapper keeps some state between getInterfaceInvokeWrapper and
@@ -450,10 +374,7 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error)
}
// Get the expanded receiver type.
receiverType, err := c.getLLVMType(f.Params[0].Type())
if err != nil {
return llvm.Value{}, err
}
receiverType := c.getLLVMType(f.Params[0].Type())
expandedReceiverType := c.expandFormalParamType(receiverType)
// Does this method even need any wrapping?
@@ -480,7 +401,7 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error)
// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper,
// see that function for details.
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) error {
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) {
wrapper := state.wrapper
fn := state.fn
receiverType := state.receiverType
@@ -490,10 +411,7 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
// add debug info if needed
if c.Debug {
pos := c.ir.Program.Fset.Position(fn.Pos())
difunc, err := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
if err != nil {
return err
}
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
@@ -501,28 +419,7 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
block := c.ctx.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(block)
var receiverPtr llvm.Value
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")
receiverValue := c.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if fn.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
c.builder.CreateCall(fn.LLVMFn, params, "")
@@ -531,6 +428,4 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
c.builder.CreateRet(ret)
}
return nil
}
+11
View File
@@ -22,6 +22,17 @@ func getUses(value llvm.Value) []llvm.Value {
return uses
}
// 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
// after afterInst are moved into a new basic block (created right after the
// current one) with the given name.
+3 -8
View File
@@ -10,10 +10,7 @@ import (
)
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)
if err != nil {
return llvm.Value{}, err
}
llvmValueType := c.getLLVMType(valueType)
mapValueAlloca := c.builder.CreateAlloca(llvmValueType, "hashmap.value")
mapValuePtr := c.builder.CreateBitCast(mapValueAlloca, c.i8ptrType, "hashmap.valueptr")
var commaOkValue llvm.Value
@@ -42,7 +39,7 @@ 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")
c.builder.CreateStore(value, valueAlloca)
valuePtr := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "hashmap.valueptr")
@@ -51,7 +48,6 @@ func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, p
// key is a string
params := []llvm.Value{m, key, valuePtr}
c.createRuntimeCall("hashmapStringSet", params, "")
return nil
} else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
@@ -59,9 +55,8 @@ func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, p
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
params := []llvm.Value{m, keyPtr, valuePtr}
c.createRuntimeCall("hashmapBinarySet", params, "")
return nil
} 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())
}
}
+24 -13
View File
@@ -18,6 +18,10 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
}
builder.AddCoroutinePassesToExtensionPoints()
if c.PanicStrategy == "trap" {
c.replacePanicsWithTrap() // -panic=trap
}
// Run function passes for each function.
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
defer funcPasses.Dispose()
@@ -113,6 +117,25 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
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.
//
// In the future, this should statically allocate created but never modified
@@ -249,7 +272,7 @@ func (c *Compiler) OptimizeAllocs() {
sizeInWords := (size + uint64(alignment) - 1) / uint64(alignment)
allocaType := llvm.ArrayType(c.ctx.IntType(alignment*8), int(sizeInWords))
alloca := c.builder.CreateAlloca(allocaType, "stackalloc.alloca")
zero, _ := c.getZeroValue(alloca.Type().ElementType())
zero := c.getZeroValue(alloca.Type().ElementType())
c.builder.CreateStore(zero, alloca)
stackalloc := c.builder.CreateBitCast(alloca, bitcast.Type(), "stackalloc")
bitcast.ReplaceAllUsesWith(stackalloc)
@@ -284,18 +307,6 @@ func (c *Compiler) doesEscape(value llvm.Value) bool {
return true
}
} 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") {
return true
}
+3
View File
@@ -113,6 +113,9 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
if k == types.Uintptr {
return s.PtrSize
}
if k == types.UnsafePointer {
return s.PtrSize
}
panic("unknown basic type: " + t.String())
case *types.Array:
n := t.Len()
+3 -12
View File
@@ -51,10 +51,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
"{r12}",
"{r13}",
}[i]
llvmValue, err := c.parseExpr(frame, arg)
if err != nil {
return llvm.Value{}, err
}
llvmValue := c.getValue(frame, arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
@@ -80,10 +77,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
"{r5}",
"{r6}",
}[i]
llvmValue, err := c.parseExpr(frame, arg)
if err != nil {
return llvm.Value{}, err
}
llvmValue := c.getValue(frame, arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
@@ -113,10 +107,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
"{x4}",
"{x5}",
}[i]
llvmValue, err := c.parseExpr(frame, arg)
if err != nil {
return llvm.Value{}, err
}
llvmValue := c.getValue(frame, arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
+108
View File
@@ -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) {
// 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 packedHeapAlloc
}
}
// 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) {
// 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
}
+5 -2
View File
@@ -24,7 +24,7 @@ import "C"
// This version uses the built-in linker when trying to use lld.
func Link(linker string, flags ...string) error {
switch linker {
case "ld.lld", commands["ld.lld"]:
case "ld.lld":
flags = append([]string{"tinygo:" + linker}, flags...)
var cflag *C.char
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 nil
case "wasm-ld", commands["wasm-ld"]:
case "wasm-ld":
flags = append([]string{"tinygo:" + linker}, flags...)
var cflag *C.char
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
default:
// Fall back to external command.
if cmdNames, ok := commands[linker]; ok {
return execCommand(cmdNames, flags...)
}
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
+3
View File
@@ -14,6 +14,9 @@ import (
//
// This version always runs the linker as an external command.
func Link(linker string, flags ...string) error {
if cmdNames, ok := commands[linker]; ok {
return execCommand(cmdNames, flags...)
}
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
+64 -1
View File
@@ -23,6 +23,7 @@ type fileInfo struct {
typedefs map[string]*typedefInfo
elaboratedTypes map[string]ast.Expr
importCPos token.Pos
missingSymbols map[string]struct{}
}
// functionInfo stores some information about a Cgo function found by libclang
@@ -62,10 +63,27 @@ var cgoAliases = map[string]string{
"C.uintptr_t": "uintptr",
}
// cgoBuiltinAliases are handled specially because they only exist on the Go
// side of CGo, not on the CGo (they're prefixed with "_Cgo_" there).
var cgoBuiltinAliases = 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;
@@ -89,6 +107,13 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) []er
globals: map[string]*globalInfo{},
typedefs: map[string]*typedefInfo{},
elaboratedTypes: map[string]ast.Expr{},
missingSymbols: map[string]struct{}{},
}
// Find all C.* symbols.
f = astutil.Apply(f, info.findMissingCGoNames, nil).(*ast.File)
for name := range cgoBuiltinAliases {
info.missingSymbols["_Cgo_"+name] = struct{}{}
}
// Find `import "C"` statements in the file.
@@ -220,6 +245,9 @@ func (info *fileInfo) addFuncDecls() {
// // ...
// )
func (info *fileInfo) addFuncPtrDecls() {
if len(info.functions) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: info.importCPos,
Tok: token.VAR,
@@ -268,6 +296,9 @@ func (info *fileInfo) addFuncPtrDecls() {
// // ...
// )
func (info *fileInfo) addVarDecls() {
if len(info.globals) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: info.importCPos,
Tok: token.VAR,
@@ -344,6 +375,9 @@ func (info *fileInfo) addTypeAliases() {
}
func (info *fileInfo) addTypedefs() {
if len(info.typedefs) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: info.importCPos,
Tok: token.TYPE,
@@ -356,8 +390,10 @@ func (info *fileInfo) addTypedefs() {
for _, name := range names {
typedef := info.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).
@@ -375,6 +411,9 @@ func (info *fileInfo) addTypedefs() {
},
Type: typedef.typeExpr,
}
if isAlias {
typeSpec.Assign = info.importCPos
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
}
@@ -387,6 +426,9 @@ func (info *fileInfo) addTypedefs() {
// See also:
// https://en.cppreference.com/w/cpp/language/elaborated_type_specifier
func (info *fileInfo) addElaboratedTypes() {
if len(info.elaboratedTypes) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: info.importCPos,
Tok: token.TYPE,
@@ -398,7 +440,7 @@ func (info *fileInfo) addElaboratedTypes() {
sort.Strings(names)
for _, name := range names {
typ := info.elaboratedTypes[name]
typeName := "C.struct_" + name
typeName := "C." + name
obj := &ast.Object{
Kind: ast.Typ,
Name: typeName,
@@ -417,6 +459,27 @@ func (info *fileInfo) addElaboratedTypes() {
info.Decls = append(info.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 (info *fileInfo) 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 := cgoBuiltinAliases[name]; ok {
name = "_Cgo_" + name
}
info.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
+209 -119
View File
@@ -4,6 +4,7 @@ package loader
// modification. It does not touch the AST itself.
import (
"fmt"
"go/ast"
"go/scanner"
"go/token"
@@ -45,6 +46,8 @@ 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);
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);
@@ -66,9 +69,13 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilenam
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))
@@ -107,20 +114,12 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilenam
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
location := C.clang_getDiagnosticLocation(diagnostic)
var file C.CXFile
var libclangFilename C.CXString
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) {
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(info.Program.Dir, filename)
if err == nil {
@@ -130,7 +129,7 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilenam
errs = append(errs, &scanner.Error{
Pos: token.Position{
Filename: filename,
Offset: int(offset),
Offset: 0, // not provided by clang_getPresumedLocation
Line: int(line),
Column: int(column),
},
@@ -162,9 +161,13 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilenam
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)
pos := info.getCursorPosition(c)
switch kind {
case C.CXCursor_FunctionDecl:
name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := info.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
@@ -181,7 +184,7 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
}
fn.args = append(fn.args, paramInfo{
name: argName,
typeExpr: info.makeASTType(argType),
typeExpr: info.makeASTType(argType, pos),
})
}
resultType := C.tinygo_clang_getCursorResultType(c)
@@ -189,53 +192,33 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
fn.results = &ast.FieldList{
List: []*ast.Field{
&ast.Field{
Type: info.makeASTType(resultType),
Type: info.makeASTType(resultType, pos),
},
},
}
}
case C.CXCursor_StructDecl:
typ := C.tinygo_clang_getCursorType(c)
name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := info.missingSymbols["struct_"+name]; !required {
return C.CXChildVisit_Continue
}
info.makeASTType(typ, pos)
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,
if _, required := info.missingSymbols[name]; !required {
return C.CXChildVisit_Continue
}
info.makeASTType(typedefType, pos)
case C.CXCursor_VarDecl:
name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := info.missingSymbols[name]; !required {
return C.CXChildVisit_Continue
}
cursorType := C.tinygo_clang_getCursorType(c)
info.globals[name] = &globalInfo{
typeExpr: info.makeASTType(cursorType),
typeExpr: info.makeASTType(cursorType, pos),
}
}
return C.CXChildVisit_Continue
@@ -248,11 +231,48 @@ func getString(clangString C.CXString) (s string) {
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 (info *fileInfo) 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 := info.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 := info.fset.AddFile(filename, -1, int(size))
f.SetLines(lines)
info.tokenFiles[filename] = f
}
return info.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 (info *fileInfo) makeASTType(typ C.CXType) ast.Expr {
func (info *fileInfo) 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:
@@ -293,19 +313,33 @@ func (info *fileInfo) makeASTType(typ C.CXType) ast.Expr {
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: info.importCPos,
X: info.makeASTType(C.clang_getPointeeType(typ)),
Star: pos,
X: info.makeASTType(pointeeType, pos),
}
case C.CXType_ConstantArray:
return &ast.ArrayType{
Lbrack: info.importCPos,
Lbrack: pos,
Len: &ast.BasicLit{
ValuePos: info.importCPos,
ValuePos: pos,
Kind: token.INT,
Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10),
},
Elt: info.makeASTType(C.clang_getElementType(typ)),
Elt: info.makeASTType(C.clang_getElementType(typ), pos),
}
case C.CXType_FunctionProto:
// Be compatible with gc, which uses the *[0]byte type for function
@@ -313,95 +347,151 @@ func (info *fileInfo) makeASTType(typ C.CXType) ast.Expr {
// Return type [0]byte because this is a function type, not a pointer to
// this function type.
return &ast.ArrayType{
Lbrack: info.importCPos,
Lbrack: pos,
Len: &ast.BasicLit{
ValuePos: info.importCPos,
ValuePos: pos,
Kind: token.INT,
Value: "0",
},
Elt: &ast.Ident{
NamePos: info.importCPos,
NamePos: pos,
Name: "byte",
},
}
case C.CXType_Typedef:
typedefName := getString(C.clang_getTypedefName(typ))
name := getString(C.clang_getTypedefName(typ))
if _, ok := info.typedefs[name]; !ok {
info.typedefs[name] = nil // don't recurse
c := C.tinygo_clang_getTypeDeclaration(typ)
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
expr := info.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"
}
}
}
info.typedefs[name] = &typedefInfo{
typeExpr: expr,
}
}
return &ast.Ident{
NamePos: info.importCPos,
Name: "C." + typedefName,
NamePos: pos,
Name: "C." + name,
}
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,
}
return info.makeASTType(underlying, pos)
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))
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
var cgoName string
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
return &ast.StructType{
Struct: info.importCPos,
Fields: fieldList,
}
cgoName = "struct_" + name
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,
},
cgoName = "union_" + name
default:
panic("unknown record declaration")
}
if _, ok := info.elaboratedTypes[cgoName]; !ok {
info.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
fieldList := &ast.FieldList{
Opening: pos,
Closing: pos,
}
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:
info.elaboratedTypes[cgoName] = &ast.StructType{
Struct: pos,
Fields: fieldList,
}
unionMarker.Names = []*ast.Ident{
&ast.Ident{
NamePos: info.importCPos,
Name: "C union",
Obj: &ast.Object{
Kind: ast.Var,
Name: "C union",
Decl: unionMarker,
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...)
}
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
}
return &ast.StructType{
Struct: info.importCPos,
Fields: fieldList,
info.elaboratedTypes[cgoName] = &ast.StructType{
Struct: pos,
Fields: fieldList,
}
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
@@ -409,7 +499,7 @@ func (info *fileInfo) makeASTType(typ C.CXType) ast.Expr {
typeName = "C." + getString(C.clang_getTypeSpelling(typ))
}
return &ast.Ident{
NamePos: info.importCPos,
NamePos: pos,
Name: typeName,
}
}
@@ -428,11 +518,11 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
name := getString(C.tinygo_clang_getCursorSpelling(c))
typ := C.tinygo_clang_getCursorType(c)
field := &ast.Field{
Type: info.makeASTType(typ),
Type: info.makeASTType(typ, info.getCursorPosition(c)),
}
field.Names = []*ast.Ident{
&ast.Ident{
NamePos: info.importCPos,
NamePos: info.getCursorPosition(c),
Name: name,
Obj: &ast.Object{
Kind: ast.Var,
+8
View File
@@ -44,3 +44,11 @@ int tinygo_clang_Cursor_getNumArguments(CXCursor c) {
CXCursor tinygo_clang_Cursor_getArgument(CXCursor c, unsigned i) {
return clang_Cursor_getArgument(c, i);
}
CXSourceLocation tinygo_clang_getCursorLocation(CXCursor c) {
return clang_getCursorLocation(c);
}
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(CXCursor c) {
return clang_Cursor_getTranslationUnit(c);
}
+18 -5
View File
@@ -22,6 +22,7 @@ type Program struct {
fset *token.FileSet
TypeChecker types.Config
Dir string // current working directory (for error reporting)
TINYGOROOT string // root of the TinyGo installation or root of the source code
CFlags []string
}
@@ -29,10 +30,11 @@ type Program struct {
type Package struct {
*Program
*build.Package
Imports map[string]*Package
Importing bool
Files []*ast.File
Pkg *types.Package
Imports map[string]*Package
Importing bool
Files []*ast.File
tokenFiles map[string]*token.File
Pkg *types.Package
types.Info
}
@@ -105,6 +107,7 @@ func (p *Program) newPackage(pkg *build.Package) *Package {
Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
},
tokenFiles: map[string]*token.File{},
}
}
@@ -292,6 +295,16 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
}
files = append(files, f)
}
clangIncludes := ""
if len(p.CgoFiles) != 0 {
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")
}
}
for _, file := range p.CgoFiles {
path := filepath.Join(p.Package.Dir, file)
f, err := p.parseFile(path, parser.ParseComments)
@@ -299,7 +312,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
fileErrs = append(fileErrs, err)
continue
}
errs := p.processCgo(path, f, append(p.CFlags, "-I"+p.Package.Dir))
errs := p.processCgo(path, f, append(p.CFlags, "-I"+p.Package.Dir, "-I"+clangIncludes))
if errs != nil {
fileErrs = append(fileErrs, errs...)
continue
+90 -51
View File
@@ -33,16 +33,27 @@ func (e *commandError) Error() string {
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 {
opt string
gc string
printIR bool
dumpSSA bool
debug bool
printSizes string
cFlags []string
ldFlags []string
wasmAbi string
opt string
gc string
panicStrategy string
printIR bool
dumpSSA bool
debug bool
printSizes string
cFlags []string
ldFlags []string
wasmAbi string
}
// Helper function for Compiler object.
@@ -51,23 +62,39 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
config.gc = spec.GC
}
// Append command line passed CFlags and LDFlags
spec.CFlags = append(spec.CFlags, config.cFlags...)
spec.LDFlags = append(spec.LDFlags, config.ldFlags...)
root := sourceDir()
// 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{
Triple: spec.Triple,
CPU: spec.CPU,
GOOS: spec.GOOS,
GOARCH: spec.GOARCH,
GC: config.gc,
CFlags: spec.CFlags,
LDFlags: spec.LDFlags,
Debug: config.debug,
DumpSSA: config.dumpSSA,
RootDir: sourceDir(),
GOPATH: getGopath(),
BuildTags: spec.BuildTags,
Triple: spec.Triple,
CPU: spec.CPU,
GOOS: spec.GOOS,
GOARCH: spec.GOARCH,
GC: config.gc,
PanicStrategy: config.panicStrategy,
CFlags: cflags,
LDFlags: ldflags,
Debug: config.debug,
DumpSSA: config.dumpSSA,
TINYGOROOT: root,
GOROOT: goroot,
GOPATH: getGopath(),
BuildTags: spec.BuildTags,
}
c, err := compiler.NewCompiler(pkgName, compilerConfig)
if err != nil {
@@ -75,12 +102,15 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
}
// Compile Go code to IR.
err = c.Compile(pkgName)
if err != nil {
return err
errs := c.Compile(pkgName)
if len(errs) != 0 {
if len(errs) == 1 {
return errs[0]
}
return &multiError{errs}
}
if config.printIR {
fmt.Println("Generated LLVM IR:")
fmt.Println("; Generated LLVM IR:")
fmt.Println(c.IR())
}
if err := c.Verify(); err != nil {
@@ -191,19 +221,20 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
// Prepare link command.
executable := filepath.Join(dir, "main")
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" {
ldflags = append(ldflags, librt)
}
// Compile extra files.
for i, path := range spec.ExtraFiles {
abspath := filepath.Join(root, path)
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
cmd := exec.Command(spec.Compiler, append(spec.CFlags, "-c", "-o", outpath, path)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
err := cmd.Run()
cmdNames := []string{spec.Compiler}
if names, ok := commands[spec.Compiler]; ok {
cmdNames = names
}
err := execCommand(cmdNames, append(cflags, "-c", "-o", outpath, abspath)...)
if err != nil {
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 {
path := filepath.Join(pkg.Package.Dir, file)
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o")
cmd := exec.Command(spec.Compiler, append(spec.CFlags, "-c", "-o", outpath, path)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
err := cmd.Run()
cmdNames := []string{spec.Compiler}
if names, ok := commands[spec.Compiler]; ok {
cmdNames = names
}
err := execCommand(cmdNames, append(cflags, "-c", "-o", outpath, path)...)
if err != nil {
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.
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 {
return &commandError{"failed to link", executable, err}
}
@@ -496,6 +523,10 @@ func handleCompilerError(err error) {
for _, err := range errLoader.Errs {
fmt.Fprintln(os.Stderr, err)
}
} else if errMulti, ok := err.(*multiError); ok {
for _, err := range errMulti.Errs {
fmt.Fprintln(os.Stderr, err)
}
} else {
fmt.Fprintln(os.Stderr, "error:", err)
}
@@ -507,6 +538,7 @@ func main() {
outpath := flag.String("o", "", "output filename")
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
gc := flag.String("gc", "", "garbage collector to use (none, dumb, marksweep)")
panicStrategy := flag.String("panic", "print", "panic strategy (abort, trap)")
printIR := flag.Bool("printir", false, "print LLVM IR")
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
target := flag.String("target", "", "LLVM target")
@@ -527,13 +559,14 @@ func main() {
flag.CommandLine.Parse(os.Args[2:])
config := &BuildConfig{
opt: *opt,
gc: *gc,
printIR: *printIR,
dumpSSA: *dumpSSA,
debug: !*nodebug,
printSizes: *printSize,
wasmAbi: *wasmAbi,
opt: *opt,
gc: *gc,
panicStrategy: *panicStrategy,
printIR: *printIR,
dumpSSA: *dumpSSA,
debug: !*nodebug,
printSizes: *printSize,
wasmAbi: *wasmAbi,
}
if *cFlags != "" {
@@ -544,6 +577,12 @@ func main() {
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)
switch command {
+15 -7
View File
@@ -13,17 +13,19 @@ import (
"runtime"
"sort"
"testing"
"github.com/tinygo-org/tinygo/loader"
)
const TESTDATA = "testdata"
func TestCompiler(t *testing.T) {
matches, err := filepath.Glob(TESTDATA + "/*.go")
matches, err := filepath.Glob(filepath.Join(TESTDATA, "*.go"))
if err != nil {
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 {
t.Fatal("could not read test packages:", err)
}
@@ -64,7 +66,7 @@ func TestCompiler(t *testing.T) {
if runtime.GOOS == "linux" {
t.Log("running tests for linux/arm...")
for _, path := range matches {
if path == "testdata/cgo/" {
if path == filepath.Join("testdata", "cgo")+string(filepath.Separator) {
continue // TODO: improve CGo
}
t.Run(path, func(t *testing.T) {
@@ -74,7 +76,7 @@ func TestCompiler(t *testing.T) {
t.Log("running tests for linux/arm64...")
for _, path := range matches {
if path == "testdata/cgo/" {
if path == filepath.Join("testdata", "cgo")+string(filepath.Separator) {
continue // TODO: improve CGo
}
t.Run(path, func(t *testing.T) {
@@ -84,7 +86,7 @@ func TestCompiler(t *testing.T) {
t.Log("running tests for WebAssembly...")
for _, path := range matches {
if path == "testdata/gc.go" {
if path == filepath.Join("testdata", "gc.go") {
continue // known to fail
}
t.Run(path, func(t *testing.T) {
@@ -97,7 +99,7 @@ func TestCompiler(t *testing.T) {
func runTest(path, tmpdir string, target string, t *testing.T) {
// Get the expected output for this test.
txtpath := path[:len(path)-3] + ".txt"
if path[len(path)-1] == '/' {
if path[len(path)-1] == os.PathSeparator {
txtpath = path + "out.txt"
}
f, err := os.Open(txtpath)
@@ -121,7 +123,13 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
binary := filepath.Join(tmpdir, "test")
err = Build("./"+path, binary, target, config)
if err != nil {
t.Log("failed to build:", err)
if errLoader, ok := err.(loader.Errors); ok {
for _, err := range errLoader.Errs {
t.Log("failed to build:", err)
}
} else {
t.Log("failed to build:", err)
}
t.Fail()
return
}
+10 -5
View File
@@ -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
import (
@@ -6,14 +6,19 @@ import (
"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() {
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 {
machine.ClearLEDMatrix()
ledrow.Low()
time.Sleep(time.Millisecond * 500)
machine.SetEntireLEDMatrixOn()
ledrow.High()
time.Sleep(time.Millisecond * 500)
}
}
+36 -16
View File
@@ -1,6 +1,6 @@
# 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
[the export folder](./export) for an example of this.
@@ -31,23 +31,31 @@ $ make main
## Running
Start the local webserver:
Start the local web server:
```bash
$ go run main.go
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
Execution of the contents require a few JS helper functions which are called
from WebAssembly. We have defined these in
[wasm_exec.js](../../../targets/wasm_exec.js). It is based on
`$GOROOT/misc/wasm/wasm_exec.js` from the standard library, but is slightly
different. Ensure you are using the same version of `wasm_exec.js` as the
version of `tinygo` you are using to compile.
Execution of the contents require a few JavaScript helper functions which are
called from WebAssembly.
We have defined these in [wasm_exec.js](../../../targets/wasm_exec.js). It is
based on `$GOROOT/misc/wasm/wasm_exec.js` from the standard library, but is
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
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
example of this.
In addition to this piece of JavaScript, it is important that the file is served
with the correct `Content-Type` header set.
In addition to the JavaScript, it is important the wasm file is served with the
[`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
package main
@@ -98,14 +107,25 @@ func main() {
fs := http.FileServer(http.Dir(dir))
log.Print("Serving " + dir + " on http://localhost:8080")
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") {
resp.Header().Set("content-type", "application/wasm")
}
fs.ServeHTTP(resp, req)
}))
}
}))}
```
This simple server serves anything inside the `./html` directory on port `8080`,
setting any `*.wasm` files `Content-Type` header appropriately.
This simple server serves anything inside the `./html` directory on port
`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 -5
View File
@@ -1,9 +1,5 @@
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello world!")
println("Hello world!")
}
+1 -1
View File
@@ -12,10 +12,10 @@ func main() {
fs := http.FileServer(http.Dir(dir))
log.Print("Serving " + dir + " on http://localhost:8080")
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") {
resp.Header().Set("content-type", "application/wasm")
}
fs.ServeHTTP(resp, req)
}))
}
-67
View File
@@ -2,11 +2,6 @@
package machine
import (
"device/nrf"
"errors"
)
// The micro:bit does not have a 32kHz crystal on board.
const HasLowFrequencyCrystal = false
@@ -79,65 +74,3 @@ const (
LED_ROW_2 = 14
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
}
+7 -1
View File
@@ -343,6 +343,12 @@ func (a ADC) Get() uint16 {
sam.ADC.INPUTCTRL |= sam.RegValue(ch << sam.ADC_INPUTCTRL_MUXPOS_Pos)
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
sam.ADC.CTRLA |= sam.ADC_CTRLA_ENABLE
waitADCSync()
@@ -368,7 +374,7 @@ func (a ADC) Get() uint16 {
sam.ADC.CTRLA &^= sam.ADC_CTRLA_ENABLE
waitADCSync()
return uint16(val)
return uint16(val) << 4 // scales from 12 to 16-bit result
}
func (a ADC) getADCChannel() uint8 {
+60 -12
View File
@@ -12,29 +12,77 @@ const (
)
// Fake LED numbers, for testing.
const (
LED = LED1
LED1 = 0
LED2 = 0
LED3 = 0
LED4 = 0
var (
LED uint8 = LED1
LED1 uint8 = 0
LED2 uint8 = 0
LED3 uint8 = 0
LED4 uint8 = 0
)
// Fake button numbers, for testing.
const (
BUTTON = BUTTON1
BUTTON1 = 0
BUTTON2 = 0
BUTTON3 = 0
BUTTON4 = 0
var (
BUTTON uint8 = BUTTON1
BUTTON1 uint8 = 5
BUTTON2 uint8 = 6
BUTTON3 uint8 = 7
BUTTON4 uint8 = 8
)
// Fake SPI interfaces, for testing.
var (
SPI0 = SPI{0}
)
var (
GPIOConfigure func(pin uint8, config GPIOConfig)
GPIOSet func(pin uint8, value bool)
GPIOGet func(pin uint8) bool
SPIConfigure func(bus uint8, sck uint8, mosi uint8, miso uint8)
SPITransfer func(bus uint8, w uint8) uint8
)
func (p GPIO) Configure(config GPIOConfig) {
if GPIOConfigure != nil {
GPIOConfigure(p.Pin, config)
}
}
func (p GPIO) Set(value bool) {
if GPIOSet != nil {
GPIOSet(p.Pin, value)
}
}
func (p GPIO) Get() bool {
if GPIOGet != nil {
return GPIOGet(p.Pin)
}
return false
}
type SPI struct {
Bus uint8
}
type SPIConfig struct {
Frequency uint32
SCK uint8
MOSI uint8
MISO uint8
Mode uint8
}
func (spi SPI) Configure(config SPIConfig) {
if SPIConfigure != nil {
SPIConfigure(spi.Bus, config.SCK, config.MOSI, config.MISO)
}
}
// Transfer writes/reads a single byte using the SPI interface.
func (spi SPI) Transfer(w byte) (byte, error) {
if SPITransfer != nil {
return SPITransfer(spi.Bus, w), nil
}
return 0, nil
}
+1 -1
View File
@@ -1,4 +1,4 @@
// +build nrf stm32f103xx atsamd21g18a
// +build js,wasm nrf stm32f103xx atsamd21g18a
package machine
+14 -23
View File
@@ -39,33 +39,27 @@ const (
chanStateClosed
)
func chanSendStub(caller *coroutine, ch *channel, _ unsafe.Pointer, size uintptr)
func chanRecvStub(caller *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size uintptr)
func deadlockStub()
// 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
// value and re-activates both goroutines. If not, it sets itself as waiting on
// a value.
//
// 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) {
func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer, size uintptr) {
if ch == nil {
// A nil channel blocks forever. Do not scheduler this goroutine again.
return
}
switch ch.state {
case chanStateEmpty:
sender.promise().ptr = value
ch.state = chanStateSend
ch.blocked = sender
case chanStateRecv:
receiver := ch.blocked
receiverPromise := receiver.promise()
senderPromise := sender.promise()
memcpy(unsafe.Pointer(&receiverPromise.data), unsafe.Pointer(&senderPromise.data), size)
receiverPromise.commaOk = true
memcpy(receiverPromise.ptr, value, size)
receiverPromise.data = 1 // commaOk = true
ch.blocked = receiverPromise.next
receiverPromise.next = nil
activateTask(receiver)
@@ -76,6 +70,7 @@ func chanSend(sender *coroutine, ch *channel, _ unsafe.Pointer, size uintptr) {
case chanStateClosed:
runtimePanic("send on closed channel")
case chanStateSend:
sender.promise().ptr = value
sender.promise().next = ch.blocked
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.
// 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.
//
// 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) {
func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer, size uintptr) {
if ch == nil {
// A nil channel blocks forever. Do not scheduler this goroutine again.
return
@@ -97,10 +88,9 @@ func chanRecv(receiver *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size
switch ch.state {
case chanStateSend:
sender := ch.blocked
receiverPromise := receiver.promise()
senderPromise := sender.promise()
memcpy(unsafe.Pointer(&receiverPromise.data), unsafe.Pointer(&senderPromise.data), size)
receiverPromise.commaOk = true
memcpy(value, senderPromise.ptr, size)
receiver.promise().data = 1 // commaOk = true
ch.blocked = senderPromise.next
senderPromise.next = nil
activateTask(receiver)
@@ -109,14 +99,15 @@ func chanRecv(receiver *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size
ch.state = chanStateEmpty
}
case chanStateEmpty:
receiver.promise().ptr = value
ch.state = chanStateRecv
ch.blocked = receiver
case chanStateClosed:
receiverPromise := receiver.promise()
memzero(unsafe.Pointer(&receiverPromise.data), size)
receiverPromise.commaOk = false
memzero(value, size)
receiver.promise().data = 0 // commaOk = false
activateTask(receiver)
case chanStateRecv:
receiver.promise().ptr = value
receiver.promise().next = ch.blocked
ch.blocked = receiver
}
@@ -142,8 +133,8 @@ func chanClose(ch *channel, size uintptr) {
case chanStateRecv:
// The receiver must be re-activated with a zero value.
receiverPromise := ch.blocked.promise()
memzero(unsafe.Pointer(&receiverPromise.data), size)
receiverPromise.commaOk = false
memzero(receiverPromise.ptr, size)
receiverPromise.data = 0 // commaOk = false
activateTask(ch.blocked)
ch.state = chanStateClosed
ch.blocked = nil
+65
View File
@@ -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)
}
+53
View File
@@ -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))
}
+40
View File
@@ -4,6 +4,10 @@ import (
"unsafe"
)
type stringer interface {
String() string
}
//go:nobounds
func printstring(s string) {
for i := 0; i < len(s); i++ {
@@ -199,8 +203,44 @@ func printnl() {
func printitf(msg interface{}) {
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:
print(msg)
case error:
print(msg.Error())
case stringer:
print(msg.String())
default:
// cast to underlying type
itf := *(*_interface)(unsafe.Pointer(&msg))
+25 -3
View File
@@ -49,13 +49,17 @@ func (t *coroutine) promise() *taskState {
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:
//
// {i8* next, i1 commaOk, i32/i64 data}
type taskState struct {
next *coroutine
commaOk bool // 'comma-ok' flag for channel receive operation
data uint
next *coroutine
ptr unsafe.Pointer
data uint
}
// Queues used by the scheduler.
@@ -107,6 +111,24 @@ func activateTask(task *coroutine) {
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
// done.
func runqueuePushBack(t *coroutine) {
+59 -1
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"io"
"os"
"os/exec"
"os/user"
"path/filepath"
"runtime"
@@ -211,7 +212,7 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
GOOS: goos,
GOARCH: goarch,
BuildTags: []string{goos, goarch},
Compiler: commands["clang"],
Compiler: "clang",
Linker: "cc",
GDB: "gdb",
GDBCmds: []string{"run"},
@@ -310,3 +311,60 @@ func getHomeDir() string {
}
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
}
+4 -1
View File
@@ -2,14 +2,17 @@
"build-tags": ["cortexm", "linux", "arm"],
"goos": "linux",
"goarch": "arm",
"compiler": "clang-8",
"compiler": "clang",
"gc": "marksweep",
"linker": "ld.lld",
"rtlib": "compiler-rt",
"cflags": [
"-Oz",
"-mthumb",
"-Werror",
"-fshort-enums",
"-nostdlibinc",
"-Wno-macro-redefined",
"-fno-exceptions", "-fno-unwind-tables",
"-ffunction-sections", "-fdata-sections"
],
+1 -1
View File
@@ -6,7 +6,7 @@
"--target=armv6m-none-eabi",
"-Qunused-arguments",
"-DNRF51",
"-Ilib/CMSIS/CMSIS/Include"
"-I{root}/lib/CMSIS/CMSIS/Include"
],
"ldflags": [
"-T", "targets/nrf51.ld"
+1 -1
View File
@@ -7,7 +7,7 @@
"-mfloat-abi=soft",
"-Qunused-arguments",
"-DNRF52832_XXAA",
"-Ilib/CMSIS/CMSIS/Include"
"-I{root}/lib/CMSIS/CMSIS/Include"
],
"ldflags": [
"-T", "targets/nrf52.ld"
+1 -1
View File
@@ -7,7 +7,7 @@
"-mfloat-abi=soft",
"-Qunused-arguments",
"-DNRF52840_XXAA",
"-Ilib/CMSIS/CMSIS/Include"
"-I{root}/lib/CMSIS/CMSIS/Include"
],
"ldflags": [
"-T", "targets/nrf52840.ld"
+3 -1
View File
@@ -3,10 +3,12 @@
"build-tags": ["js", "wasm"],
"goos": "js",
"goarch": "wasm",
"compiler": "clang-8",
"compiler": "clang",
"linker": "wasm-ld",
"cflags": [
"--target=wasm32",
"-nostdlibinc",
"-Wno-macro-redefined",
"-Oz"
],
"ldflags": [
+6 -2
View File
@@ -1,13 +1,17 @@
#include "main.h"
int global = 3;
_Bool globalBool = 1;
_Bool globalBool2 = 10; // test narrowing
bool globalBool = 1;
bool globalBool2 = 10; // test narrowing
float globalFloat = 3.1;
double globalDouble = 3.2;
_Complex float globalComplexFloat = 4.1+3.3i;
_Complex double globalComplexDouble = 4.2+3.4i;
_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};
int globalStructSize = sizeof(globalStruct);
short globalArray[3] = {5, 6, 7};
+15 -7
View File
@@ -9,10 +9,6 @@ import "C"
import "unsafe"
func (s C.myint) Int() int {
return int(s)
}
func main() {
println("fortytwo:", C.fortytwo())
println("add:", C.add(C.int(3), 5))
@@ -33,6 +29,10 @@ func main() {
cb = C.binop_t(C.mul)
println("callback 2:", C.doCallback(20, 30, cb))
// equivalent types
var goInt8 int8 = 5
var _ C.int8_t = goInt8
// more globals
println("bool:", C.globalBool, C.globalBool2 == true)
println("float:", C.globalFloat)
@@ -40,6 +40,10 @@ func main() {
println("complex float:", C.globalComplexFloat)
println("complex double:", C.globalComplexDouble)
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
println("struct:", C.int(unsafe.Sizeof(C.globalStruct)) == C.globalStructSize, C.globalStruct.s, C.globalStruct.l, C.globalStruct.f)
@@ -53,10 +57,14 @@ func main() {
C.unionSetData(5, 8, 1)
println("union global data:", C.globalUnion.data[0], C.globalUnion.data[1], C.globalUnion.data[2])
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
lastElement := &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)}}
list := &C.list_t{n: 3, next: &C.struct_list_t{n: 6, next: &C.list_t{n: 7, next: nil}}}
for list != nil {
println("n in chain:", list.n)
list = (*C.list_t)(list.next)
@@ -66,7 +74,7 @@ func main() {
func printUnion(union C.joined_t) C.joined_t {
println("union local data: ", union.data[0], union.data[1], union.data[2])
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
println("union f:", union.f)
return union
+21 -3
View File
@@ -1,10 +1,18 @@
#include <stdbool.h>
#include <stdint.h>
typedef short myint;
typedef short unusedTypedef;
int add(int a, int b);
int unusedFunction(void);
typedef int (*binop_t) (int, int);
int doCallback(int a, int b, binop_t cb);
typedef int * intPointer;
void store(int value, int *ptr);
// this signature should not be included by CGo
void unusedFunction2(int x, __builtin_va_list args);
typedef struct collection {
short s;
long l;
@@ -12,6 +20,11 @@ typedef struct collection {
unsigned char c;
} collection_t;
struct point {
int x;
int y;
};
// linked list
typedef struct list_t {
int n;
@@ -27,15 +40,20 @@ void unionSetShort(short s);
void unionSetFloat(float f);
void unionSetData(short f0, short f1, short f2);
// test globals
// test globals and datatypes
extern int global;
extern _Bool globalBool;
extern _Bool globalBool2;
extern int unusedGlobal;
extern bool globalBool;
extern bool globalBool2;
extern float globalFloat;
extern double globalDouble;
extern _Complex float globalComplexFloat;
extern _Complex double globalComplexDouble;
extern _Complex double globalComplexLongDouble;
extern char globalChar;
extern void *globalVoidPtrSet;
extern void *globalVoidPtrNull;
extern int64_t globalInt64;
extern collection_t globalStruct;
extern int globalStructSize;
extern short globalArray[3];
+5 -1
View File
@@ -14,6 +14,9 @@ double: +3.200000e+000
complex float: (+4.100000e+000+3.300000e+000i)
complex double: (+4.200000e+000+3.400000e+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
array: 5 6 7
union: true
@@ -21,9 +24,10 @@ union s: 22
union f: +3.140000e+000
union global data: 5 8 1
union local data: 5 8 1
union s method: -33 false
union s: true
union f: +6.280000e+000
union field: +6.280000e+000
struct: 3 5
n in chain: 3
n in chain: 6
n in chain: 7
+9
View File
@@ -20,6 +20,11 @@ func main() {
n, ok = <-ch
println("recv from closed channel:", n, ok)
// Test bigger values
ch2 := make(chan complex128)
go sendComplex(ch2)
println("complex128:", <-ch2)
// Test multi-sender.
ch = make(chan int)
go fastsender(ch)
@@ -62,6 +67,10 @@ func sender(ch chan int) {
close(ch)
}
func sendComplex(ch chan complex128) {
ch <- 7+10.5i
}
func fastsender(ch chan int) {
ch <- 10
ch <- 11
+1
View File
@@ -9,6 +9,7 @@ received num: 6
received num: 7
received num: 8
recv from closed channel: 0 false
complex128: (+7.000000e+000+1.050000e+001i)
got n: 10
got n: 11
got n: 10
+24
View File
@@ -15,12 +15,19 @@ func main() {
wait()
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
// regular call, so should be equivalent to calling nowait() without 'go'
// prefix.
go nowait()
time.Sleep(time.Millisecond)
println("done with non-blocking goroutine")
var printer Printer
printer = &myPrinter{}
printer.Print()
}
func sub() {
@@ -35,6 +42,23 @@ func wait() {
println(" wait end")
}
func delayedValue() int {
time.Sleep(time.Millisecond)
return 42
}
func nowait() {
println("non-blocking goroutine")
}
type Printer interface {
Print()
}
type myPrinter struct{
}
func (i *myPrinter) Print() {
time.Sleep(time.Millisecond)
println("async interface method call")
}
+2
View File
@@ -7,5 +7,7 @@ wait:
wait start
wait end
end waiting
value produced after some time: 42
non-blocking goroutine
done with non-blocking goroutine
async interface method call
+12
View File
@@ -56,4 +56,16 @@ func main() {
// cast complex
println(complex64(c128))
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)
}
+8
View File
@@ -23,3 +23,11 @@
(+2.000000e+000-2.000000e+000i)
(+6.666667e-001-2.000000e+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)