mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-02 10:07:47 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 13c0714fda | |||
| f880950c3e | |||
| 1bed192de0 | |||
| 896a848001 | |||
| fb03787b73 | |||
| 83a949647f | |||
| 99a41bec4e | |||
| 312f5d3833 | |||
| 35bf0746a1 | |||
| 8d93b9e545 | |||
| b44d41d9ec | |||
| 4be9802d26 |
@@ -172,7 +172,11 @@ commands:
|
||||
key: llvm-build-11-linux-v2-assert
|
||||
paths:
|
||||
llvm-build
|
||||
- run: make ASSERT=1
|
||||
- run: |
|
||||
# Note: -p=2 limits parallelism to two jobs at a time, which is
|
||||
# necessary to keep memory consumption down and avoid OOM (for a
|
||||
# 2CPU/4GB executor).
|
||||
GOFLAGS="-p=2" make ASSERT=1
|
||||
- build-wasi-libc
|
||||
- run:
|
||||
name: "Test TinyGo"
|
||||
|
||||
@@ -180,7 +180,7 @@ tinygo:
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags byollvm -ldflags="-X main.gitSha1=`git rev-parse --short HEAD`" .
|
||||
|
||||
test: wasi-libc
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./cgo ./compileopts ./compiler ./interp ./transform .
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
|
||||
|
||||
# Test known-working standard library packages.
|
||||
# TODO: do this in one command, parallelize, and only show failing tests (no
|
||||
|
||||
+66
-46
@@ -17,7 +17,6 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
@@ -55,6 +54,7 @@ type BuildResult struct {
|
||||
type packageAction struct {
|
||||
ImportPath string
|
||||
CompilerVersion int // compiler.Version
|
||||
InterpVersion int // interp.Version
|
||||
LLVMVersion string
|
||||
Config *compiler.Config
|
||||
CFlags []string
|
||||
@@ -135,6 +135,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||
actionID := packageAction{
|
||||
ImportPath: pkg.ImportPath,
|
||||
CompilerVersion: compiler.Version,
|
||||
InterpVersion: interp.Version,
|
||||
LLVMVersion: llvm.Version,
|
||||
Config: compilerConfig,
|
||||
CFlags: pkg.CFlags,
|
||||
@@ -179,7 +180,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||
// The package has not yet been compiled, so create a job to do so.
|
||||
job := &compileJob{
|
||||
description: "compile package " + pkg.ImportPath,
|
||||
run: func() error {
|
||||
run: func(*compileJob) error {
|
||||
// Compile AST to IR. The compiler.CompilePackage function will
|
||||
// build the SSA as needed.
|
||||
mod, errs := compiler.CompilePackage(pkg.ImportPath, pkg, program.Package(pkg.Pkg), machine, compilerConfig, config.DumpSSA())
|
||||
@@ -190,6 +191,21 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||
return errors.New("verification error after compiling package " + pkg.ImportPath)
|
||||
}
|
||||
|
||||
// Try to interpret package initializers at compile time.
|
||||
// It may only be possible to do this partially, in which case
|
||||
// it is completed after all IR files are linked.
|
||||
pkgInit := mod.NamedFunction(pkg.Pkg.Path() + ".init")
|
||||
if pkgInit.IsNil() {
|
||||
panic("init not found for " + pkg.Pkg.Path())
|
||||
}
|
||||
err := interp.RunFunc(pkgInit, config.DumpSSA())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
|
||||
return errors.New("verification error after interpreting " + pkgInit.Name())
|
||||
}
|
||||
|
||||
// Serialize the LLVM module as a bitcode file.
|
||||
// Write to a temporary path that is renamed to the destination
|
||||
// file to avoid race conditions with other TinyGo invocatiosn
|
||||
@@ -233,7 +249,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||
programJob := &compileJob{
|
||||
description: "link+optimize packages (LTO)",
|
||||
dependencies: packageJobs,
|
||||
run: func() error {
|
||||
run: func(*compileJob) error {
|
||||
// Load and link all the bitcode files. This does not yet optimize
|
||||
// anything, it only links the bitcode files together.
|
||||
ctx := llvm.NewContext()
|
||||
@@ -353,7 +369,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||
outputObjectFileJob := &compileJob{
|
||||
description: "generate output file",
|
||||
dependencies: []*compileJob{programJob},
|
||||
run: func() error {
|
||||
result: objfile,
|
||||
run: func(*compileJob) error {
|
||||
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -367,40 +384,32 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||
linkerDependencies := []*compileJob{outputObjectFileJob}
|
||||
executable := filepath.Join(dir, "main")
|
||||
tmppath := executable // final file
|
||||
ldflags := append(config.LDFlags(), "-o", executable, objfile)
|
||||
ldflags := append(config.LDFlags(), "-o", executable)
|
||||
|
||||
// Add compiler-rt dependency if needed. Usually this is a simple load from
|
||||
// a cache.
|
||||
if config.Target.RTLib == "compiler-rt" {
|
||||
path, job, err := CompilerRT.load(config.Triple(), config.CPU(), dir)
|
||||
job, err := CompilerRT.load(config.Triple(), config.CPU(), dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if job != nil {
|
||||
// The library was not loaded from cache so needs to be compiled
|
||||
// (and then stored in the cache).
|
||||
jobs = append(jobs, job.dependencies...)
|
||||
jobs = append(jobs, job)
|
||||
linkerDependencies = append(linkerDependencies, job)
|
||||
}
|
||||
ldflags = append(ldflags, path)
|
||||
jobs = append(jobs, job.dependencies...)
|
||||
jobs = append(jobs, job)
|
||||
linkerDependencies = append(linkerDependencies, job)
|
||||
}
|
||||
|
||||
// Add libc dependency if needed.
|
||||
root := goenv.Get("TINYGOROOT")
|
||||
switch config.Target.Libc {
|
||||
case "picolibc":
|
||||
path, job, err := Picolibc.load(config.Triple(), config.CPU(), dir)
|
||||
job, err := Picolibc.load(config.Triple(), config.CPU(), dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if job != nil {
|
||||
// The library needs to be compiled (cache miss).
|
||||
jobs = append(jobs, job.dependencies...)
|
||||
jobs = append(jobs, job)
|
||||
linkerDependencies = append(linkerDependencies, job)
|
||||
}
|
||||
ldflags = append(ldflags, path)
|
||||
// The library needs to be compiled (cache miss).
|
||||
jobs = append(jobs, job.dependencies...)
|
||||
jobs = append(jobs, job)
|
||||
linkerDependencies = append(linkerDependencies, job)
|
||||
case "wasi-libc":
|
||||
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
@@ -416,44 +425,37 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||
// Add jobs to compile extra files. These files are in C or assembly and
|
||||
// contain things like the interrupt vector table and low level operations
|
||||
// such as stack switching.
|
||||
for i, path := range config.ExtraFiles() {
|
||||
for _, path := range config.ExtraFiles() {
|
||||
abspath := filepath.Join(root, path)
|
||||
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
|
||||
job := &compileJob{
|
||||
description: "compile extra file " + path,
|
||||
run: func() error {
|
||||
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, abspath)...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", path, err}
|
||||
}
|
||||
return nil
|
||||
run: func(job *compileJob) error {
|
||||
result, err := compileAndCacheCFile(abspath, dir, config.CFlags(), config)
|
||||
job.result = result
|
||||
return err
|
||||
},
|
||||
}
|
||||
jobs = append(jobs, job)
|
||||
linkerDependencies = append(linkerDependencies, job)
|
||||
ldflags = append(ldflags, outpath)
|
||||
}
|
||||
|
||||
// Add jobs to compile C files in all packages. This is part of CGo.
|
||||
// TODO: do this as part of building the package to be able to link the
|
||||
// bitcode files together.
|
||||
for i, pkg := range lprogram.Sorted() {
|
||||
for j, filename := range pkg.CFiles {
|
||||
file := filepath.Join(pkg.Dir, filename)
|
||||
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"."+strconv.Itoa(j)+"-"+filepath.Base(file)+".o")
|
||||
for _, pkg := range lprogram.Sorted() {
|
||||
pkg := pkg
|
||||
for _, filename := range pkg.CFiles {
|
||||
abspath := filepath.Join(pkg.Dir, filename)
|
||||
job := &compileJob{
|
||||
description: "compile CGo file " + file,
|
||||
run: func() error {
|
||||
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", file, err}
|
||||
}
|
||||
return nil
|
||||
description: "compile CGo file " + abspath,
|
||||
run: func(job *compileJob) error {
|
||||
result, err := compileAndCacheCFile(abspath, dir, pkg.CFlags, config)
|
||||
job.result = result
|
||||
return err
|
||||
},
|
||||
}
|
||||
jobs = append(jobs, job)
|
||||
linkerDependencies = append(linkerDependencies, job)
|
||||
ldflags = append(ldflags, outpath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,7 +470,16 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||
jobs = append(jobs, &compileJob{
|
||||
description: "link",
|
||||
dependencies: linkerDependencies,
|
||||
run: func() error {
|
||||
run: func(job *compileJob) error {
|
||||
for _, dependency := range job.dependencies {
|
||||
if dependency.result == "" {
|
||||
return errors.New("dependency without result: " + dependency.description)
|
||||
}
|
||||
ldflags = append(ldflags, dependency.result)
|
||||
}
|
||||
if config.Options.PrintCommands {
|
||||
fmt.Printf("%s %s\n", config.Target.Linker, strings.Join(ldflags, " "))
|
||||
}
|
||||
err = link(config.Target.Linker, ldflags...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to link", executable, err}
|
||||
@@ -575,8 +586,17 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
|
||||
return errors.New("verification error after interpreting runtime.initAll")
|
||||
if config.VerifyIR() {
|
||||
// Only verify if we really need it.
|
||||
// The IR has already been verified before writing the bitcode to disk
|
||||
// and the interp function above doesn't need to do a lot as most of the
|
||||
// package initializers have already run. Additionally, verifying this
|
||||
// linked IR is _expensive_ because dead code hasn't been removed yet,
|
||||
// easily costing a few hundred milliseconds. Therefore, only do it when
|
||||
// specifically requested.
|
||||
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
|
||||
return errors.New("verification error after interpreting runtime.initAll")
|
||||
}
|
||||
}
|
||||
|
||||
if config.GOOS() != "darwin" {
|
||||
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
package builder
|
||||
|
||||
// This file implements a wrapper around the C compiler (Clang) which uses a
|
||||
// build cache.
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// compileAndCacheCFile compiles a C or assembly file using a build cache.
|
||||
// Compiling the same file again (if nothing changed, including included header
|
||||
// files) the output is loaded from the build cache instead.
|
||||
//
|
||||
// Its operation is a bit complex (more complex than Go package build caching)
|
||||
// because the list of file dependencies is only known after the file is
|
||||
// compiled. However, luckily compilers have a flag to write a list of file
|
||||
// dependencies in Makefile syntax which can be used for caching.
|
||||
//
|
||||
// Because of this complexity, every file has in fact two cached build outputs:
|
||||
// the file itself, and the list of dependencies. Its operation is as follows:
|
||||
//
|
||||
// depfile = hash(path, compiler, cflags, ...)
|
||||
// if depfile exists:
|
||||
// outfile = hash of all files and depfile name
|
||||
// if outfile exists:
|
||||
// # cache hit
|
||||
// return outfile
|
||||
// # cache miss
|
||||
// tmpfile = compile file
|
||||
// read dependencies (side effect of compile)
|
||||
// write depfile
|
||||
// outfile = hash of all files and depfile name
|
||||
// rename tmpfile to outfile
|
||||
//
|
||||
// There are a few edge cases that are not handled:
|
||||
// - If a file is added to an include path, that file may be included instead of
|
||||
// some other file. This would be fixed by also including lookup failures in the
|
||||
// dependencies file, but I'm not aware of a compiler which does that.
|
||||
// - The Makefile syntax that compilers output has issues, see readDepFile for
|
||||
// details.
|
||||
// - A header file may be changed to add/remove an include. This invalidates the
|
||||
// depfile but without invalidating its name. For this reason, the depfile is
|
||||
// written on each new compilation (even when it seems unnecessary). However, it
|
||||
// could in rare cases lead to a stale file fetched from the cache.
|
||||
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, config *compileopts.Config) (string, error) {
|
||||
// Hash input file.
|
||||
fileHash, err := hashFile(abspath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Create cache key for the dependencies file.
|
||||
buf, err := json.Marshal(struct {
|
||||
Path string
|
||||
Hash string
|
||||
Compiler string
|
||||
Flags []string
|
||||
LLVMVersion string
|
||||
}{
|
||||
Path: abspath,
|
||||
Hash: fileHash,
|
||||
Compiler: config.Target.Compiler,
|
||||
Flags: config.CFlags(),
|
||||
LLVMVersion: llvm.Version,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err) // shouldn't happen
|
||||
}
|
||||
depfileNameHashBuf := sha512.Sum512_224(buf)
|
||||
depfileNameHash := hex.EncodeToString(depfileNameHashBuf[:])
|
||||
|
||||
// Load dependencies file, if possible.
|
||||
depfileName := "dep-" + depfileNameHash + ".json"
|
||||
depfileCachePath := filepath.Join(goenv.Get("GOCACHE"), depfileName)
|
||||
depfileBuf, err := ioutil.ReadFile(depfileCachePath)
|
||||
var dependencies []string // sorted list of dependency paths
|
||||
if err == nil {
|
||||
// There is a dependency file, that's great!
|
||||
// Parse it first.
|
||||
err := json.Unmarshal(depfileBuf, &dependencies)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not parse dependencies JSON: %w", err)
|
||||
}
|
||||
|
||||
// Obtain hashes of all the files listed as a dependency.
|
||||
outpath, err := makeCFileCachePath(dependencies, depfileNameHash)
|
||||
if err == nil {
|
||||
if _, err := os.Stat(outpath); err == nil {
|
||||
return outpath, nil
|
||||
} else if !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
// expected either nil or IsNotExist
|
||||
return "", err
|
||||
}
|
||||
|
||||
objTmpFile, err := ioutil.TempFile(goenv.Get("GOCACHE"), "tmp-*.o")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
objTmpFile.Close()
|
||||
depTmpFile, err := ioutil.TempFile(tmpdir, "dep-*.d")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
depTmpFile.Close()
|
||||
flags := append([]string{}, cflags...) // copy cflags
|
||||
flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name()) // autogenerate dependencies
|
||||
flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath)
|
||||
if config.Options.PrintCommands {
|
||||
fmt.Printf("%s %s\n", config.Target.Compiler, strings.Join(flags, " "))
|
||||
}
|
||||
err = runCCompiler(config.Target.Compiler, flags...)
|
||||
if err != nil {
|
||||
return "", &commandError{"failed to build", abspath, err}
|
||||
}
|
||||
|
||||
// Create sorted and uniqued slice of dependencies.
|
||||
dependencyPaths, err := readDepFile(depTmpFile.Name())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dependencyPaths = append(dependencyPaths, abspath) // necessary for .s files
|
||||
dependencySet := make(map[string]struct{}, len(dependencyPaths))
|
||||
var dependencySlice []string
|
||||
for _, path := range dependencyPaths {
|
||||
if _, ok := dependencySet[path]; ok {
|
||||
continue
|
||||
}
|
||||
dependencySet[path] = struct{}{}
|
||||
dependencySlice = append(dependencySlice, path)
|
||||
}
|
||||
sort.Strings(dependencySlice)
|
||||
|
||||
// Write dependencies file.
|
||||
f, err := ioutil.TempFile(filepath.Dir(depfileCachePath), depfileName)
|
||||
buf, err = json.MarshalIndent(dependencySlice, "", "\t")
|
||||
if err != nil {
|
||||
panic(err) // shouldn't happen
|
||||
}
|
||||
_, err = f.Write(buf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = os.Rename(f.Name(), depfileCachePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Move temporary object file to final location.
|
||||
outpath, err := makeCFileCachePath(dependencySlice, depfileNameHash)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = os.Rename(objTmpFile.Name(), outpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return outpath, nil
|
||||
}
|
||||
|
||||
// Create a cache path (a path in GOCACHE) to store the output of a compiler
|
||||
// job. This path is based on the dep file name (which is a hash of metadata
|
||||
// including compiler flags) and the hash of all input files in the paths slice.
|
||||
func makeCFileCachePath(paths []string, depfileNameHash string) (string, error) {
|
||||
// Hash all input files.
|
||||
fileHashes := make(map[string]string, len(paths))
|
||||
for _, path := range paths {
|
||||
hash, err := hashFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fileHashes[path] = hash
|
||||
}
|
||||
|
||||
// Calculate a cache key based on the above hashes.
|
||||
buf, err := json.Marshal(struct {
|
||||
DepfileHash string
|
||||
FileHashes map[string]string
|
||||
}{
|
||||
DepfileHash: depfileNameHash,
|
||||
FileHashes: fileHashes,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err) // shouldn't happen
|
||||
}
|
||||
outFileNameBuf := sha512.Sum512_224(buf)
|
||||
cacheKey := hex.EncodeToString(outFileNameBuf[:])
|
||||
|
||||
outpath := filepath.Join(goenv.Get("GOCACHE"), "obj-"+cacheKey+".o")
|
||||
return outpath, nil
|
||||
}
|
||||
|
||||
// hashFile hashes the given file path and returns the hash as a hex string.
|
||||
func hashFile(path string) (string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to hash file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
fileHasher := sha512.New512_224()
|
||||
_, err = io.Copy(fileHasher, f)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to hash file: %w", err)
|
||||
}
|
||||
return hex.EncodeToString(fileHasher.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// readDepFile reads a dependency file in NMake (Visual Studio make) format. The
|
||||
// file is assumed to have a single target named deps.
|
||||
//
|
||||
// There are roughly three make syntax variants:
|
||||
// - BSD make, which doesn't support any escaping. This means that many special
|
||||
// characters are not supported in file names.
|
||||
// - GNU make, which supports escaping using a backslash but when it fails to
|
||||
// find a file it tries to fall back with the literal path name (to match BSD
|
||||
// make).
|
||||
// - NMake (Visual Studio) and Jom, which simply quote the string if there are
|
||||
// any weird characters.
|
||||
// Clang supports two variants: a format that's a compromise between BSD and GNU
|
||||
// make (and is buggy to match GCC which is equally buggy), and NMake/Jom, which
|
||||
// is at least somewhat sane. This last format isn't perfect either: it does not
|
||||
// correctly handle filenames with quote marks in them. Those are generally not
|
||||
// allowed on Windows, but of course can be used on POSIX like systems. Still,
|
||||
// it's the most sane of any of the formats so readDepFile will use that format.
|
||||
func readDepFile(filename string) ([]string, error) {
|
||||
buf, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(buf) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return parseDepFile(string(buf))
|
||||
}
|
||||
|
||||
func parseDepFile(s string) ([]string, error) {
|
||||
// This function makes no attempt at parsing anything other than Clang -MD
|
||||
// -MV output.
|
||||
|
||||
// For Windows: replace CRLF with LF to make the logic below simpler.
|
||||
s = strings.ReplaceAll(s, "\r\n", "\n")
|
||||
|
||||
// Collapse all lines ending in a backslash. These backslashes are really
|
||||
// just a way to continue a line without making very long lines.
|
||||
s = strings.ReplaceAll(s, "\\\n", " ")
|
||||
|
||||
// Only use the first line, which is expected to begin with "deps:".
|
||||
line := strings.SplitN(s, "\n", 2)[0]
|
||||
if !strings.HasPrefix(line, "deps:") {
|
||||
return nil, errors.New("readDepFile: expected 'deps:' prefix")
|
||||
}
|
||||
line = strings.TrimSpace(line[len("deps:"):])
|
||||
|
||||
var deps []string
|
||||
for line != "" {
|
||||
if line[0] == '"' {
|
||||
// File path is quoted. Path ends with double quote.
|
||||
// This does not handle double quotes in path names, which is a
|
||||
// problem on non-Windows systems.
|
||||
line = line[1:]
|
||||
end := strings.IndexByte(line, '"')
|
||||
if end < 0 {
|
||||
return nil, errors.New("readDepFile: path is incorrectly quoted")
|
||||
}
|
||||
dep := line[:end]
|
||||
line = strings.TrimSpace(line[end+1:])
|
||||
deps = append(deps, dep)
|
||||
} else {
|
||||
// File path is not quoted. Path ends in space or EOL.
|
||||
end := strings.IndexFunc(line, unicode.IsSpace)
|
||||
if end < 0 {
|
||||
// last dependency
|
||||
deps = append(deps, line)
|
||||
break
|
||||
}
|
||||
dep := line[:end]
|
||||
line = strings.TrimSpace(line[end:])
|
||||
deps = append(deps, dep)
|
||||
}
|
||||
}
|
||||
return deps, nil
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSplitDepFile(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
in string
|
||||
out []string
|
||||
}{
|
||||
{`deps: foo bar`, []string{"foo", "bar"}},
|
||||
{`deps: foo "bar"`, []string{"foo", "bar"}},
|
||||
{`deps: "foo" bar`, []string{"foo", "bar"}},
|
||||
{`deps: "foo bar"`, []string{"foo bar"}},
|
||||
{`deps: "foo bar" `, []string{"foo bar"}},
|
||||
{"deps: foo\nbar", []string{"foo"}},
|
||||
{"deps: foo \\\nbar", []string{"foo", "bar"}},
|
||||
{"deps: foo\\bar \\\nbaz", []string{"foo\\bar", "baz"}},
|
||||
{"deps: foo\\bar \\\r\n baz", []string{"foo\\bar", "baz"}}, // Windows uses CRLF line endings
|
||||
} {
|
||||
out, err := parseDepFile(tc.in)
|
||||
if err != nil {
|
||||
t.Errorf("test #%d failed: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(out, tc.out) {
|
||||
t.Errorf("test #%d failed: expected %#v but got %#v", i, tc.out, out)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-4
@@ -27,12 +27,23 @@ const (
|
||||
type compileJob struct {
|
||||
description string // description, only used for logging
|
||||
dependencies []*compileJob
|
||||
run func() error
|
||||
result string // result (path)
|
||||
run func(*compileJob) (err error)
|
||||
state jobState
|
||||
err error // error if finished
|
||||
duration time.Duration // how long it took to run this job (only set after finishing)
|
||||
}
|
||||
|
||||
// dummyCompileJob returns a new *compileJob that produces an output without
|
||||
// doing anything. This can be useful where a *compileJob producing an output is
|
||||
// expected but nothing needs to be done, for example for a load from a cache.
|
||||
func dummyCompileJob(result string) *compileJob {
|
||||
return &compileJob{
|
||||
description: "<dummy>",
|
||||
result: result,
|
||||
}
|
||||
}
|
||||
|
||||
// readyToRun returns whether this job is ready to run: it is itself not yet
|
||||
// started and all dependencies are finished.
|
||||
func (job *compileJob) readyToRun() bool {
|
||||
@@ -150,9 +161,11 @@ func nextJob(jobs []*compileJob) *compileJob {
|
||||
func jobWorker(workerChan, doneChan chan *compileJob) {
|
||||
for job := range workerChan {
|
||||
start := time.Now()
|
||||
err := job.run()
|
||||
if err != nil {
|
||||
job.err = err
|
||||
if job.run != nil {
|
||||
err := job.run(job)
|
||||
if err != nil {
|
||||
job.err = err
|
||||
}
|
||||
}
|
||||
job.duration = time.Since(start)
|
||||
doneChan <- job
|
||||
|
||||
+16
-17
@@ -42,30 +42,28 @@ func (l *Library) sourcePaths(target string) []string {
|
||||
// The resulting file is stored in the provided tmpdir, which is expected to be
|
||||
// removed after the Load call.
|
||||
func (l *Library) Load(target, tmpdir string) (path string, err error) {
|
||||
path, job, err := l.load(target, "", tmpdir)
|
||||
job, err := l.load(target, "", tmpdir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if job != nil {
|
||||
jobs := append([]*compileJob{job}, job.dependencies...)
|
||||
err = runJobs(jobs)
|
||||
}
|
||||
return path, err
|
||||
jobs := append([]*compileJob{job}, job.dependencies...)
|
||||
err = runJobs(jobs)
|
||||
return job.result, err
|
||||
}
|
||||
|
||||
// load returns a path to the library file for the given target, loading it from
|
||||
// cache if possible. It will return a non-zero compiler job if the library
|
||||
// wasn't cached, this job (and its dependencies) must be run before the library
|
||||
// path is valid.
|
||||
// load returns a compile job to build this library file for the given target
|
||||
// and CPU. It may return a dummy compileJob if the library build is already
|
||||
// cached. The path is stored as job.result but is only valid if the job and
|
||||
// job.dependencies have been run.
|
||||
// The provided tmpdir will be used to store intermediary files and possibly the
|
||||
// output archive file, it is expected to be removed after use.
|
||||
func (l *Library) load(target, cpu, tmpdir string) (path string, job *compileJob, err error) {
|
||||
func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error) {
|
||||
// Try to load a precompiled library.
|
||||
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, l.name+".a")
|
||||
if _, err := os.Stat(precompiledPath); err == nil {
|
||||
// Found a precompiled library for this OS/architecture. Return the path
|
||||
// directly.
|
||||
return precompiledPath, nil, nil
|
||||
return dummyCompileJob(precompiledPath), nil
|
||||
}
|
||||
|
||||
var outfile string
|
||||
@@ -78,7 +76,7 @@ func (l *Library) load(target, cpu, tmpdir string) (path string, job *compileJob
|
||||
// Try to fetch this library from the cache.
|
||||
if path, err := cacheLoad(outfile, l.sourcePaths(target)); path != "" || err != nil {
|
||||
// Cache hit.
|
||||
return path, nil, err
|
||||
return dummyCompileJob(path), nil
|
||||
}
|
||||
// Cache miss, build it now.
|
||||
|
||||
@@ -86,7 +84,7 @@ func (l *Library) load(target, cpu, tmpdir string) (path string, job *compileJob
|
||||
dir := filepath.Join(tmpdir, "build-lib-"+l.name)
|
||||
err = os.Mkdir(dir, 0777)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Precalculate the flags to the compiler invocation.
|
||||
@@ -113,7 +111,8 @@ func (l *Library) load(target, cpu, tmpdir string) (path string, job *compileJob
|
||||
arpath := filepath.Join(dir, l.name+".a")
|
||||
job = &compileJob{
|
||||
description: "ar " + l.name + ".a",
|
||||
run: func() error {
|
||||
result: arpath,
|
||||
run: func(*compileJob) error {
|
||||
// Create an archive of all object files.
|
||||
err := makeArchive(arpath, objs)
|
||||
if err != nil {
|
||||
@@ -133,7 +132,7 @@ func (l *Library) load(target, cpu, tmpdir string) (path string, job *compileJob
|
||||
objs = append(objs, objpath)
|
||||
job.dependencies = append(job.dependencies, &compileJob{
|
||||
description: "compile " + srcpath,
|
||||
run: func() error {
|
||||
run: func(*compileJob) error {
|
||||
var compileArgs []string
|
||||
compileArgs = append(compileArgs, args...)
|
||||
compileArgs = append(compileArgs, "-o", objpath, srcpath)
|
||||
@@ -146,5 +145,5 @@ func (l *Library) load(target, cpu, tmpdir string) (path string, job *compileJob
|
||||
})
|
||||
}
|
||||
|
||||
return arpath, job, nil
|
||||
return job, nil
|
||||
}
|
||||
|
||||
+17
-14
@@ -41,7 +41,8 @@ type cgoPackage struct {
|
||||
elaboratedTypes map[string]*elaboratedTypeInfo
|
||||
enums map[string]enumInfo
|
||||
anonStructNum int
|
||||
ldflags []string
|
||||
cflags []string // CFlags from #cgo lines
|
||||
ldflags []string // LDFlags from #cgo lines
|
||||
visitedFiles map[string][]byte
|
||||
}
|
||||
|
||||
@@ -157,10 +158,10 @@ typedef unsigned long long _Cgo_ulonglong;
|
||||
// Process extracts `import "C"` statements from the AST, parses the comment
|
||||
// with libclang, and modifies the AST to use this information. It returns a
|
||||
// newly created *ast.File that should be added to the list of to-be-parsed
|
||||
// files, the LDFLAGS for this package, and a map of file hashes of the accessed
|
||||
// C header files. If there is one or more error, it returns these in the
|
||||
// []error slice but still modifies the AST.
|
||||
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, map[string][]byte, []error) {
|
||||
// files, the CFLAGS and LDFLAGS found in #cgo lines, and a map of file hashes
|
||||
// of the accessed C header files. If there is one or more error, it returns
|
||||
// these in the []error slice but still modifies the AST.
|
||||
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []string, map[string][]byte, []error) {
|
||||
p := &cgoPackage{
|
||||
dir: dir,
|
||||
fset: fset,
|
||||
@@ -175,11 +176,6 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
visitedFiles: map[string][]byte{},
|
||||
}
|
||||
|
||||
// Disable _FORTIFY_SOURCE as it causes problems on macOS.
|
||||
// Note that it is only disabled for memcpy (etc) calls made from Go, which
|
||||
// have better alternatives anyway.
|
||||
cflags = append(cflags, "-D_FORTIFY_SOURCE=0")
|
||||
|
||||
// Add a new location for the following file.
|
||||
generatedTokenPos := p.fset.AddFile(dir+"/!cgo.go", -1, 0)
|
||||
generatedTokenPos.SetLines([]int{0})
|
||||
@@ -188,7 +184,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
// Find the absolute path for this package.
|
||||
packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name())
|
||||
if err != nil {
|
||||
return nil, nil, nil, []error{
|
||||
return nil, nil, nil, nil, []error{
|
||||
scanner.Error{
|
||||
Pos: fset.Position(files[0].Pos()),
|
||||
Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error
|
||||
@@ -363,7 +359,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
continue
|
||||
}
|
||||
makePathsAbsolute(flags, packagePath)
|
||||
cflags = append(cflags, flags...)
|
||||
p.cflags = append(p.cflags, flags...)
|
||||
case "LDFLAGS":
|
||||
flags, err := shlex.Split(value)
|
||||
if err != nil {
|
||||
@@ -386,6 +382,13 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
}
|
||||
}
|
||||
|
||||
// Define CFlags that will be used while parsing the package.
|
||||
// Disable _FORTIFY_SOURCE as it causes problems on macOS.
|
||||
// Note that it is only disabled for memcpy (etc) calls made from Go, which
|
||||
// have better alternatives anyway.
|
||||
cflagsForCGo := append([]string{"-D_FORTIFY_SOURCE=0"}, cflags...)
|
||||
cflagsForCGo = append(cflagsForCGo, p.cflags...)
|
||||
|
||||
// Process all CGo imports.
|
||||
for _, genDecl := range statements {
|
||||
cgoComment := genDecl.Doc.Text()
|
||||
@@ -395,7 +398,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
pos = genDecl.Doc.Pos()
|
||||
}
|
||||
position := fset.PositionFor(pos, true)
|
||||
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
|
||||
p.parseFragment(cgoComment+cgoTypes, cflagsForCGo, position.Filename, position.Line)
|
||||
}
|
||||
|
||||
// Declare functions found by libclang.
|
||||
@@ -430,7 +433,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
// Print the newly generated in-memory AST, for debugging.
|
||||
//ast.Print(fset, p.generated)
|
||||
|
||||
return p.generated, p.ldflags, p.visitedFiles, p.errors
|
||||
return p.generated, p.cflags, p.ldflags, p.visitedFiles, p.errors
|
||||
}
|
||||
|
||||
// makePathsAbsolute converts some common path compiler flags (-I, -L) from
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ func TestCGo(t *testing.T) {
|
||||
}
|
||||
|
||||
// Process the AST with CGo.
|
||||
cgoAST, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
|
||||
cgoAST, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
|
||||
|
||||
// Check the AST for type errors.
|
||||
var typecheckErrors []error
|
||||
|
||||
@@ -230,7 +230,13 @@ func LoadTarget(target string) (*TargetSpec, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// WindowsBuildNotSupportedErr is being thrown, when goos is windows and no target has been specified.
|
||||
var WindowsBuildNotSupportedErr = errors.New("Building Windows binaries is currently not supported. Try specifying a different target")
|
||||
|
||||
func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
||||
if goos == "windows" {
|
||||
return nil, WindowsBuildNotSupportedErr
|
||||
}
|
||||
// No target spec available. Use the default one, useful on most systems
|
||||
// with a regular OS.
|
||||
spec := TargetSpec{
|
||||
|
||||
+22
-10
@@ -58,10 +58,10 @@ func (b *builder) createCall(fn llvm.Value, args []llvm.Value, name string) llvm
|
||||
|
||||
// Expand an argument type to a list that can be used in a function call
|
||||
// parameter list.
|
||||
func expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
|
||||
func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fieldInfos := flattenAggregateType(t, name, goType)
|
||||
fieldInfos := c.flattenAggregateType(t, name, goType)
|
||||
if len(fieldInfos) <= maxFieldsPerParam {
|
||||
return fieldInfos
|
||||
} else {
|
||||
@@ -105,7 +105,7 @@ func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 {
|
||||
func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
|
||||
switch v.Type().TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fieldInfos := flattenAggregateType(v.Type(), "", nil)
|
||||
fieldInfos := b.flattenAggregateType(v.Type(), "", nil)
|
||||
if len(fieldInfos) <= maxFieldsPerParam {
|
||||
fields := b.flattenAggregate(v)
|
||||
if len(fields) != len(fieldInfos) {
|
||||
@@ -124,12 +124,15 @@ func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
|
||||
|
||||
// Try to flatten a struct type to a list of types. Returns a 1-element slice
|
||||
// with the passed in type if this is not possible.
|
||||
func flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
|
||||
func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
|
||||
typeFlags := getTypeFlags(goType)
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
paramInfos := make([]paramInfo, 0, t.StructElementTypesCount())
|
||||
var paramInfos []paramInfo
|
||||
for i, subfield := range t.StructElementTypes() {
|
||||
if c.targetData.TypeAllocSize(subfield) == 0 {
|
||||
continue
|
||||
}
|
||||
suffix := strconv.Itoa(i)
|
||||
if goType != nil {
|
||||
// Try to come up with a good suffix for this struct field,
|
||||
@@ -152,7 +155,7 @@ func flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramIn
|
||||
suffix = []string{"context", "funcptr"}[i]
|
||||
}
|
||||
}
|
||||
subInfos := flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
|
||||
subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
|
||||
for i := range subInfos {
|
||||
subInfos[i].flags |= typeFlags
|
||||
}
|
||||
@@ -218,8 +221,11 @@ func extractSubfield(t types.Type, field int) types.Type {
|
||||
func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fields := make([]uint64, 0, t.StructElementTypesCount())
|
||||
var fields []uint64
|
||||
for fieldIndex, field := range t.StructElementTypes() {
|
||||
if c.targetData.TypeAllocSize(field) == 0 {
|
||||
continue
|
||||
}
|
||||
suboffsets := c.flattenAggregateTypeOffsets(field)
|
||||
offset := c.targetData.ElementOffset(t, fieldIndex)
|
||||
for i := range suboffsets {
|
||||
@@ -238,8 +244,11 @@ func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
|
||||
func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value {
|
||||
switch v.Type().TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fields := make([]llvm.Value, 0, v.Type().StructElementTypesCount())
|
||||
for i := range v.Type().StructElementTypes() {
|
||||
var fields []llvm.Value
|
||||
for i, field := range v.Type().StructElementTypes() {
|
||||
if b.targetData.TypeAllocSize(field) == 0 {
|
||||
continue
|
||||
}
|
||||
subfield := b.CreateExtractValue(v, i, "")
|
||||
subfields := b.flattenAggregate(subfield)
|
||||
fields = append(fields, subfields...)
|
||||
@@ -266,10 +275,13 @@ func (b *builder) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Val
|
||||
func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
flattened := flattenAggregateType(t, "", nil)
|
||||
flattened := b.flattenAggregateType(t, "", nil)
|
||||
if len(flattened) <= maxFieldsPerParam {
|
||||
value := llvm.ConstNull(t)
|
||||
for i, subtyp := range t.StructElementTypes() {
|
||||
if b.targetData.TypeAllocSize(subtyp) == 0 {
|
||||
continue
|
||||
}
|
||||
structField, remaining := b.collapseFormalParamInternal(subtyp, fields)
|
||||
fields = remaining
|
||||
value = b.CreateInsertValue(value, structField, i, "")
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
// Version of the compiler pacakge. Must be incremented each time the compiler
|
||||
// package changes in a way that affects the generated LLVM module.
|
||||
// This version is independent of the TinyGo version number.
|
||||
const Version = 5 // last change: add method set to interface types
|
||||
const Version = 6 // last change: fix issue 1304
|
||||
|
||||
func init() {
|
||||
llvm.InitializeAllTargets()
|
||||
@@ -829,7 +829,7 @@ func (b *builder) createFunction() {
|
||||
for _, param := range b.fn.Params {
|
||||
llvmType := b.getLLVMType(param.Type())
|
||||
fields := make([]llvm.Value, 0, 1)
|
||||
for _, info := range expandFormalParamType(llvmType, param.Name(), param.Type()) {
|
||||
for _, info := range b.expandFormalParamType(llvmType, param.Name(), param.Type()) {
|
||||
param := b.llvmFn.Param(llvmParamIndex)
|
||||
param.SetName(info.name)
|
||||
fields = append(fields, param)
|
||||
|
||||
+2
-2
@@ -124,13 +124,13 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
|
||||
// The receiver is not an interface, but a i8* type.
|
||||
recv = c.i8ptrType
|
||||
}
|
||||
for _, info := range expandFormalParamType(recv, "", nil) {
|
||||
for _, info := range c.expandFormalParamType(recv, "", nil) {
|
||||
paramTypes = append(paramTypes, info.llvmType)
|
||||
}
|
||||
}
|
||||
for i := 0; i < typ.Params().Len(); i++ {
|
||||
subType := c.getLLVMType(typ.Params().At(i).Type())
|
||||
for _, info := range expandFormalParamType(subType, "", nil) {
|
||||
for _, info := range c.expandFormalParamType(subType, "", nil) {
|
||||
paramTypes = append(paramTypes, info.llvmType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,7 +456,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
|
||||
// Get the expanded receiver type.
|
||||
receiverType := c.getLLVMType(fn.Signature.Recv().Type())
|
||||
var expandedReceiverType []llvm.Type
|
||||
for _, info := range expandFormalParamType(receiverType, "", nil) {
|
||||
for _, info := range c.expandFormalParamType(receiverType, "", nil) {
|
||||
expandedReceiverType = append(expandedReceiverType, info.llvmType)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
|
||||
var paramInfos []paramInfo
|
||||
for _, param := range getParams(fn.Signature) {
|
||||
paramType := c.getLLVMType(param.Type())
|
||||
paramFragmentInfos := expandFormalParamType(paramType, param.Name(), param.Type())
|
||||
paramFragmentInfos := c.expandFormalParamType(paramType, param.Name(), param.Type())
|
||||
paramInfos = append(paramInfos, paramFragmentInfos...)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,11 @@ var (
|
||||
errMapAlreadyCreated = errors.New("interp: map already created")
|
||||
)
|
||||
|
||||
// This is one of the errors that can be returned from toLLVMValue when the
|
||||
// passed type does not fit the data to serialize. It is recoverable by
|
||||
// serializing without a type (using rawValue.rawLLVMValue).
|
||||
var errInvalidPtrToIntSize = errors.New("interp: ptrtoint integer size does not equal pointer size")
|
||||
|
||||
func isRecoverableError(err error) bool {
|
||||
return err == errIntegerAsPointer || err == errUnsupportedInst || err == errUnsupportedRuntimeInst || err == errMapAlreadyCreated
|
||||
}
|
||||
|
||||
+124
-5
@@ -11,6 +11,12 @@ import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// Version of the interp package. It must be incremented whenever the interp
|
||||
// package is changed in a way that affects the output so that cached package
|
||||
// builds will be invalidated.
|
||||
// This version is independent of the TinyGo version number.
|
||||
const Version = 1
|
||||
|
||||
// Enable extra checks, which should be disabled by default.
|
||||
// This may help track down bugs by adding a few more sanity checks.
|
||||
const checks = true
|
||||
@@ -32,9 +38,7 @@ type runner struct {
|
||||
callsExecuted uint64
|
||||
}
|
||||
|
||||
// Run evaluates runtime.initAll function as much as possible at compile time.
|
||||
// Set debug to true if it should print output while running.
|
||||
func Run(mod llvm.Module, debug bool) error {
|
||||
func newRunner(mod llvm.Module, debug bool) *runner {
|
||||
r := runner{
|
||||
mod: mod,
|
||||
targetData: llvm.NewTargetData(mod.DataLayout()),
|
||||
@@ -47,6 +51,13 @@ func Run(mod llvm.Module, debug bool) error {
|
||||
r.pointerSize = uint32(r.targetData.PointerSize())
|
||||
r.i8ptrType = llvm.PointerType(mod.Context().Int8Type(), 0)
|
||||
r.maxAlign = r.targetData.PrefTypeAlignment(r.i8ptrType) // assume pointers are maximally aligned (this is not always the case)
|
||||
return &r
|
||||
}
|
||||
|
||||
// Run evaluates runtime.initAll function as much as possible at compile time.
|
||||
// Set debug to true if it should print output while running.
|
||||
func Run(mod llvm.Module, debug bool) error {
|
||||
r := newRunner(mod, debug)
|
||||
|
||||
initAll := mod.NamedFunction("runtime.initAll")
|
||||
bb := initAll.EntryBasicBlock()
|
||||
@@ -117,7 +128,7 @@ func Run(mod llvm.Module, debug bool) error {
|
||||
r.pkgName = ""
|
||||
|
||||
// Update all global variables in the LLVM module.
|
||||
mem := memoryView{r: &r}
|
||||
mem := memoryView{r: r}
|
||||
for _, obj := range r.objects {
|
||||
if obj.llvmGlobal.IsNil() {
|
||||
continue
|
||||
@@ -125,7 +136,34 @@ func Run(mod llvm.Module, debug bool) error {
|
||||
if obj.buffer == nil {
|
||||
continue
|
||||
}
|
||||
initializer := obj.buffer.toLLVMValue(obj.llvmGlobal.Type().ElementType(), &mem)
|
||||
initializer, err := obj.buffer.toLLVMValue(obj.llvmGlobal.Type().ElementType(), &mem)
|
||||
if err == errInvalidPtrToIntSize {
|
||||
// This can happen when a previous interp run did not have the
|
||||
// correct LLVM type for a global and made something up. In that
|
||||
// case, some fields could be written out as a series of (null)
|
||||
// bytes even though they actually contain a pointer value.
|
||||
// As a fallback, use asRawValue to get something of the correct
|
||||
// memory layout.
|
||||
initializer, err := obj.buffer.asRawValue(r).rawLLVMValue(&mem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
initializerType := initializer.Type()
|
||||
newGlobal := llvm.AddGlobal(mod, initializerType, obj.llvmGlobal.Name()+".tmp")
|
||||
newGlobal.SetInitializer(initializer)
|
||||
newGlobal.SetLinkage(obj.llvmGlobal.Linkage())
|
||||
newGlobal.SetAlignment(obj.llvmGlobal.Alignment())
|
||||
// TODO: copy debug info, unnamed_addr, ...
|
||||
bitcast := llvm.ConstBitCast(newGlobal, obj.llvmGlobal.Type())
|
||||
obj.llvmGlobal.ReplaceAllUsesWith(bitcast)
|
||||
name := obj.llvmGlobal.Name()
|
||||
obj.llvmGlobal.EraseFromParentAsGlobal()
|
||||
newGlobal.SetName(name)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if checks && initializer.Type() != obj.llvmGlobal.Type().ElementType() {
|
||||
panic("initializer type mismatch")
|
||||
}
|
||||
@@ -135,6 +173,87 @@ func Run(mod llvm.Module, debug bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunFunc evaluates a single package initializer at compile time.
|
||||
// Set debug to true if it should print output while running.
|
||||
func RunFunc(fn llvm.Value, debug bool) error {
|
||||
// Create and initialize *runner object.
|
||||
mod := fn.GlobalParent()
|
||||
r := newRunner(mod, debug)
|
||||
initName := fn.Name()
|
||||
if !strings.HasSuffix(initName, ".init") {
|
||||
return errorAt(fn, "interp: unexpected function name (expected *.init)")
|
||||
}
|
||||
r.pkgName = initName[:len(initName)-len(".init")]
|
||||
|
||||
// Create new function with the interp result.
|
||||
newFn := llvm.AddFunction(mod, fn.Name()+".tmp", fn.Type().ElementType())
|
||||
newFn.SetLinkage(fn.Linkage())
|
||||
newFn.SetVisibility(fn.Visibility())
|
||||
entry := mod.Context().AddBasicBlock(newFn, "entry")
|
||||
|
||||
// Create a builder, to insert instructions that could not be evaluated at
|
||||
// compile time.
|
||||
r.builder = mod.Context().NewBuilder()
|
||||
defer r.builder.Dispose()
|
||||
r.builder.SetInsertPointAtEnd(entry)
|
||||
|
||||
// Copy debug information.
|
||||
subprogram := fn.Subprogram()
|
||||
if !subprogram.IsNil() {
|
||||
newFn.SetSubprogram(subprogram)
|
||||
r.builder.SetCurrentDebugLocation(subprogram.SubprogramLine(), 0, subprogram, llvm.Metadata{})
|
||||
}
|
||||
|
||||
// Run the initializer, filling the .init.tmp function.
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, "interp:", fn.Name())
|
||||
}
|
||||
_, pkgMem, callErr := r.run(r.getFunction(fn), nil, nil, " ")
|
||||
if callErr != nil {
|
||||
if isRecoverableError(callErr.Err) {
|
||||
// Could not finish, but could recover from it.
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error())
|
||||
}
|
||||
newFn.EraseFromParentAsFunction()
|
||||
return nil
|
||||
}
|
||||
return callErr
|
||||
}
|
||||
for index, obj := range pkgMem.objects {
|
||||
r.objects[index] = obj
|
||||
}
|
||||
|
||||
// Update globals with values determined while running the initializer above.
|
||||
mem := memoryView{r: r}
|
||||
for _, obj := range r.objects {
|
||||
if obj.llvmGlobal.IsNil() {
|
||||
continue
|
||||
}
|
||||
if obj.buffer == nil {
|
||||
continue
|
||||
}
|
||||
initializer, err := obj.buffer.toLLVMValue(obj.llvmGlobal.Type().ElementType(), &mem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if checks && initializer.Type() != obj.llvmGlobal.Type().ElementType() {
|
||||
panic("initializer type mismatch")
|
||||
}
|
||||
obj.llvmGlobal.SetInitializer(initializer)
|
||||
}
|
||||
|
||||
// Finalize: remove the old init function and replace it with the new
|
||||
// (.init.tmp) function.
|
||||
r.builder.CreateRetVoid()
|
||||
fnName := fn.Name()
|
||||
fn.ReplaceAllUsesWith(newFn)
|
||||
fn.EraseFromParentAsFunction()
|
||||
newFn.SetName(fnName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getFunction returns the compiled version of the given LLVM function. It
|
||||
// compiles the function if necessary and caches the result.
|
||||
func (r *runner) getFunction(llvmFn llvm.Value) *function {
|
||||
|
||||
+38
-6
@@ -282,7 +282,11 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
fmt.Fprintln(os.Stderr, indent+"call (reflect.rawType).elem:", operands[1:])
|
||||
}
|
||||
// Extract the type code global from the first parameter.
|
||||
typecodeID := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem).Operand(0)
|
||||
typecodeIDPtrToInt, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
typecodeID := typecodeIDPtrToInt.Operand(0)
|
||||
|
||||
// Get the type class.
|
||||
// See also: getClassAndValueFromTypeCode in transform/reflect.go.
|
||||
@@ -315,8 +319,14 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"typeassert:", operands[1:])
|
||||
}
|
||||
assertedType := operands[2].toLLVMValue(inst.llvmInst.Operand(1).Type(), &mem)
|
||||
actualTypePtrToInt := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem)
|
||||
assertedType, err := operands[2].toLLVMValue(inst.llvmInst.Operand(1).Type(), &mem)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
actualTypePtrToInt, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
actualType := actualTypePtrToInt.Operand(0)
|
||||
if actualType.Name()+"$id" == assertedType.Name() {
|
||||
locals[inst.localIndex] = literalValue{uint8(1)}
|
||||
@@ -377,7 +387,11 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
|
||||
// Load the first param, which is the type code (ptrtoint of the
|
||||
// type code global).
|
||||
typecodeID := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem).Operand(0).Initializer()
|
||||
typecodeIDPtrToInt, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
typecodeID := typecodeIDPtrToInt.Operand(0).Initializer()
|
||||
|
||||
// Load the method set, which is part of the typecodeID object.
|
||||
methodSet := llvm.ConstExtractValue(typecodeID, []uint32{2}).Operand(0).Initializer()
|
||||
@@ -534,6 +548,13 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
continue
|
||||
}
|
||||
result := mem.load(ptr, uint32(size))
|
||||
if result == nil {
|
||||
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
||||
if err != nil {
|
||||
return nil, mem, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"load:", ptr, "->", result)
|
||||
}
|
||||
@@ -556,7 +577,14 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"store:", val, ptr)
|
||||
}
|
||||
mem.store(val, ptr)
|
||||
ok := mem.store(val, ptr)
|
||||
if !ok {
|
||||
// Could not store the value, do it at runtime.
|
||||
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
||||
if err != nil {
|
||||
return nil, mem, err
|
||||
}
|
||||
}
|
||||
case llvm.Alloca:
|
||||
// Alloca normally allocates some stack memory. In the interpreter,
|
||||
// it allocates a global instead.
|
||||
@@ -888,7 +916,11 @@ func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, me
|
||||
for i := 0; i < numOperands; i++ {
|
||||
operand := inst.llvmInst.Operand(i)
|
||||
if !operand.IsAInstruction().IsNil() || !operand.IsAArgument().IsNil() {
|
||||
operand = locals[fn.locals[operand]].toLLVMValue(operand.Type(), mem)
|
||||
var err error
|
||||
operand, err = locals[fn.locals[operand]].toLLVMValue(operand.Type(), mem)
|
||||
if err != nil {
|
||||
return r.errorAt(inst, err)
|
||||
}
|
||||
}
|
||||
operands[i] = operand
|
||||
}
|
||||
|
||||
+104
-55
@@ -259,12 +259,17 @@ func (mv *memoryView) put(index uint32, obj object) {
|
||||
mv.objects[index] = obj
|
||||
}
|
||||
|
||||
// Load the value behind the given pointer.
|
||||
// Load the value behind the given pointer. Returns nil if the pointer points to
|
||||
// an external global.
|
||||
func (mv *memoryView) load(p pointerValue, size uint32) value {
|
||||
if checks && mv.hasExternalStore(p) {
|
||||
panic("interp: load from object with external store")
|
||||
}
|
||||
obj := mv.get(p.index())
|
||||
if obj.buffer == nil {
|
||||
// External global, return nil.
|
||||
return nil
|
||||
}
|
||||
if p.offset() == 0 && size == obj.size {
|
||||
return obj.buffer.clone()
|
||||
}
|
||||
@@ -280,12 +285,17 @@ func (mv *memoryView) load(p pointerValue, size uint32) value {
|
||||
|
||||
// Store to the value behind the given pointer. This overwrites the value in the
|
||||
// memory view, so that the changed value is discarded when the memory view is
|
||||
// reverted.
|
||||
func (mv *memoryView) store(v value, p pointerValue) {
|
||||
// reverted. Returns true on success, false if the object to store to is
|
||||
// external.
|
||||
func (mv *memoryView) store(v value, p pointerValue) bool {
|
||||
if checks && mv.hasExternalLoadOrStore(p) {
|
||||
panic("interp: store to object with external load/store")
|
||||
}
|
||||
obj := mv.get(p.index())
|
||||
if obj.buffer == nil {
|
||||
// External global, return false (for a failure).
|
||||
return false
|
||||
}
|
||||
if checks && p.offset()+v.len(mv.r) > obj.size {
|
||||
panic("interp: store out of bounds")
|
||||
}
|
||||
@@ -301,6 +311,7 @@ func (mv *memoryView) store(v value, p pointerValue) {
|
||||
}
|
||||
}
|
||||
mv.put(p.index(), obj)
|
||||
return true // success
|
||||
}
|
||||
|
||||
// value is some sort of value, comparable to a LLVM constant. It can be
|
||||
@@ -314,7 +325,7 @@ type value interface {
|
||||
asRawValue(*runner) rawValue
|
||||
Uint() uint64
|
||||
Int() int64
|
||||
toLLVMValue(llvm.Type, *memoryView) llvm.Value
|
||||
toLLVMValue(llvm.Type, *memoryView) (llvm.Value, error)
|
||||
String() string
|
||||
}
|
||||
|
||||
@@ -405,25 +416,25 @@ func (v literalValue) Int() int64 {
|
||||
}
|
||||
}
|
||||
|
||||
func (v literalValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Value {
|
||||
func (v literalValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) {
|
||||
switch llvmType.TypeKind() {
|
||||
case llvm.IntegerTypeKind:
|
||||
switch value := v.value.(type) {
|
||||
case uint64:
|
||||
return llvm.ConstInt(llvmType, value, false)
|
||||
return llvm.ConstInt(llvmType, value, false), nil
|
||||
case uint32:
|
||||
return llvm.ConstInt(llvmType, uint64(value), false)
|
||||
return llvm.ConstInt(llvmType, uint64(value), false), nil
|
||||
case uint16:
|
||||
return llvm.ConstInt(llvmType, uint64(value), false)
|
||||
return llvm.ConstInt(llvmType, uint64(value), false), nil
|
||||
case uint8:
|
||||
return llvm.ConstInt(llvmType, uint64(value), false)
|
||||
return llvm.ConstInt(llvmType, uint64(value), false), nil
|
||||
default:
|
||||
panic("inpterp: unknown literal type")
|
||||
return llvm.Value{}, errors.New("interp: unknown literal type")
|
||||
}
|
||||
case llvm.DoubleTypeKind:
|
||||
return llvm.ConstFloat(llvmType, math.Float64frombits(v.value.(uint64)))
|
||||
return llvm.ConstFloat(llvmType, math.Float64frombits(v.value.(uint64))), nil
|
||||
case llvm.FloatTypeKind:
|
||||
return llvm.ConstFloat(llvmType, float64(math.Float32frombits(v.value.(uint32))))
|
||||
return llvm.ConstFloat(llvmType, float64(math.Float32frombits(v.value.(uint32)))), nil
|
||||
default:
|
||||
return v.asRawValue(mem.r).toLLVMValue(llvmType, mem)
|
||||
}
|
||||
@@ -507,7 +518,7 @@ func (v pointerValue) llvmValue(mem *memoryView) llvm.Value {
|
||||
// toLLVMValue returns the LLVM value for this pointer, which may be a GEP or
|
||||
// bitcast. The llvm.Type parameter is optional, if omitted the pointer type may
|
||||
// be different than expected.
|
||||
func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Value {
|
||||
func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) {
|
||||
// Obtain the llvmValue, creating it if it doesn't exist yet.
|
||||
llvmValue := v.llvmValue(mem)
|
||||
if llvmValue.IsNil() {
|
||||
@@ -518,7 +529,10 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Valu
|
||||
if obj.llvmType.IsNil() {
|
||||
// Create an initializer without knowing the global type.
|
||||
// This is probably the result of a runtime.alloc call.
|
||||
initializer := obj.buffer.asRawValue(mem.r).rawLLVMValue(mem)
|
||||
initializer, err := obj.buffer.asRawValue(mem.r).rawLLVMValue(mem)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
globalType := initializer.Type()
|
||||
llvmValue = llvm.AddGlobal(mem.r.mod, globalType, obj.globalName)
|
||||
llvmValue.SetInitializer(initializer)
|
||||
@@ -537,9 +551,12 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Valu
|
||||
// Set the initializer for the global. Do this after creation to avoid
|
||||
// infinite recursion between creating the global and creating the
|
||||
// contents of the global (if the global contains itself).
|
||||
initializer := obj.buffer.toLLVMValue(globalType, mem)
|
||||
initializer, err := obj.buffer.toLLVMValue(globalType, mem)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
if checks && initializer.Type() != globalType {
|
||||
panic("allocated value does not match allocated type")
|
||||
return llvm.Value{}, errors.New("interp: allocated value does not match allocated type")
|
||||
}
|
||||
llvmValue.SetInitializer(initializer)
|
||||
}
|
||||
@@ -552,7 +569,7 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Valu
|
||||
}
|
||||
|
||||
if llvmType.IsNil() {
|
||||
return llvmValue
|
||||
return llvmValue, nil
|
||||
}
|
||||
|
||||
if llvmType.TypeKind() != llvm.PointerTypeKind {
|
||||
@@ -564,7 +581,7 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Valu
|
||||
// This can be worked around by simply converting to a raw value,
|
||||
// rawValue knows how to create such structs.
|
||||
if v.offset() != 0 {
|
||||
panic("offset set without known pointer type")
|
||||
return llvm.Value{}, errors.New("interp: offset set without known pointer type")
|
||||
}
|
||||
return v.asRawValue(mem.r).toLLVMValue(llvmType, mem)
|
||||
}
|
||||
@@ -575,14 +592,14 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Valu
|
||||
if v.offset() != 0 {
|
||||
// This should never happen, if offset is non-zero, the types
|
||||
// shouldn't match.
|
||||
panic("offset set while there is no way to convert the type")
|
||||
return llvm.Value{}, errors.New("interp: offset set while there is no way to convert the type")
|
||||
}
|
||||
return llvmValue
|
||||
return llvmValue, nil
|
||||
}
|
||||
|
||||
if v.offset() == 0 {
|
||||
// Offset is zero, so we can just bitcast to get a correct pointer.
|
||||
return llvm.ConstBitCast(llvmValue, llvmType)
|
||||
return llvm.ConstBitCast(llvmValue, llvmType), nil
|
||||
}
|
||||
|
||||
// We need to make a constant GEP for pointer arithmetic.
|
||||
@@ -606,11 +623,11 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Valu
|
||||
offset -= int64(mem.r.targetData.ElementOffset(objectElementType, element))
|
||||
objectElementType = objectElementType.StructElementTypes()[element]
|
||||
default:
|
||||
panic("pointer index with something other than a struct or array?")
|
||||
return llvm.Value{}, errors.New("interp: pointer index with something other than a struct or array?")
|
||||
}
|
||||
}
|
||||
if offset < 0 {
|
||||
panic("offset has somehow gone negative, this should be impossible")
|
||||
return llvm.Value{}, errors.New("interp: offset has somehow gone negative, this should be impossible")
|
||||
}
|
||||
|
||||
// Finally do the gep, using the above computed indices.
|
||||
@@ -618,9 +635,9 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Valu
|
||||
// the bits of the pointer are now correct, just not the type).
|
||||
gep := llvm.ConstInBoundsGEP(llvmValue, indices)
|
||||
if gep.Type() != llvmType {
|
||||
return llvm.ConstBitCast(gep, llvmType)
|
||||
return llvm.ConstBitCast(gep, llvmType), nil
|
||||
}
|
||||
return gep
|
||||
return gep, nil
|
||||
}
|
||||
|
||||
// mapValue implements a Go map which is created at compile time and stored as a
|
||||
@@ -713,7 +730,11 @@ func (b *mapBucket) create(ctx llvm.Context, nextBucket llvm.Value, mem *memoryV
|
||||
// Create data for keys.
|
||||
var keyValues []llvm.Value
|
||||
for _, key := range b.keys {
|
||||
keyValues = append(keyValues, key.rawLLVMValue(mem))
|
||||
keyValue, err := key.rawLLVMValue(mem)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
keyValues = append(keyValues, keyValue)
|
||||
}
|
||||
if len(b.keys) < 8 {
|
||||
keyValues = append(keyValues, llvm.ConstNull(llvm.ArrayType(int8Type, int(b.m.keySize)*(8-len(b.keys)))))
|
||||
@@ -726,7 +747,11 @@ func (b *mapBucket) create(ctx llvm.Context, nextBucket llvm.Value, mem *memoryV
|
||||
// Create data for values.
|
||||
var valueValues []llvm.Value
|
||||
for _, value := range b.values {
|
||||
valueValues = append(valueValues, value.rawLLVMValue(mem))
|
||||
v, err := value.rawLLVMValue(mem)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
valueValues = append(valueValues, v)
|
||||
}
|
||||
if len(b.values) < 8 {
|
||||
valueValues = append(valueValues, llvm.ConstNull(llvm.ArrayType(int8Type, int(b.m.valueSize)*(8-len(b.values)))))
|
||||
@@ -750,9 +775,9 @@ func (b *mapBucket) create(ctx llvm.Context, nextBucket llvm.Value, mem *memoryV
|
||||
return bucket
|
||||
}
|
||||
|
||||
func (v *mapValue) toLLVMValue(hashmapType llvm.Type, mem *memoryView) llvm.Value {
|
||||
func (v *mapValue) toLLVMValue(hashmapType llvm.Type, mem *memoryView) (llvm.Value, error) {
|
||||
if !v.hashmap.IsNil() {
|
||||
return v.hashmap
|
||||
return v.hashmap, nil
|
||||
}
|
||||
|
||||
// Create a slice of buckets with all the keys and values in the hashmap.
|
||||
@@ -772,12 +797,12 @@ func (v *mapValue) toLLVMValue(hashmapType llvm.Type, mem *memoryView) llvm.Valu
|
||||
copy(keyValue.buf[v.keySize/2:], literalValue{key.size}.asRawValue(v.r).buf)
|
||||
case rawValue:
|
||||
if key.hasPointer() {
|
||||
panic("todo: map key with pointer")
|
||||
return llvm.Value{}, errors.New("interp: todo: map key with pointer")
|
||||
}
|
||||
data = key.buf
|
||||
keyValue = key
|
||||
default:
|
||||
panic("unknown map key type")
|
||||
return llvm.Value{}, errors.New("interp: unknown map key type")
|
||||
}
|
||||
buf := make([]byte, len(data))
|
||||
for i, p := range data {
|
||||
@@ -821,7 +846,7 @@ func (v *mapValue) toLLVMValue(hashmapType llvm.Type, mem *memoryView) llvm.Valu
|
||||
})
|
||||
|
||||
v.hashmap = hashmap
|
||||
return v.hashmap
|
||||
return v.hashmap, nil
|
||||
}
|
||||
|
||||
// putString does a map assign operation, assuming that the map is of type
|
||||
@@ -1018,7 +1043,7 @@ func (v rawValue) equal(rhs rawValue) bool {
|
||||
// goes. The resulting value does not have a specified type, but it will be the
|
||||
// same size and have the same bytes if it was created with a provided LLVM type
|
||||
// (through toLLVMValue).
|
||||
func (v rawValue) rawLLVMValue(mem *memoryView) llvm.Value {
|
||||
func (v rawValue) rawLLVMValue(mem *memoryView) (llvm.Value, error) {
|
||||
var structFields []llvm.Value
|
||||
ctx := mem.r.mod.Context()
|
||||
int8Type := ctx.Int8Type()
|
||||
@@ -1042,7 +1067,10 @@ func (v rawValue) rawLLVMValue(mem *memoryView) llvm.Value {
|
||||
for i := uint32(0); i < uint32(len(v.buf)); {
|
||||
if v.buf[i] > 255 {
|
||||
addBytes()
|
||||
field := pointerValue{v.buf[i]}.toLLVMValue(llvm.Type{}, mem)
|
||||
field, err := pointerValue{v.buf[i]}.toLLVMValue(llvm.Type{}, mem)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
elementType := field.Type().ElementType()
|
||||
if elementType.TypeKind() == llvm.StructTypeKind {
|
||||
// There are some special pointer types that should be used as a
|
||||
@@ -1065,12 +1093,12 @@ func (v rawValue) rawLLVMValue(mem *memoryView) llvm.Value {
|
||||
|
||||
// Return the created data.
|
||||
if len(structFields) == 1 {
|
||||
return structFields[0]
|
||||
return structFields[0], nil
|
||||
}
|
||||
return ctx.ConstStruct(structFields, false)
|
||||
return ctx.ConstStruct(structFields, false), nil
|
||||
}
|
||||
|
||||
func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Value {
|
||||
func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) {
|
||||
isZero := true
|
||||
for _, p := range v.buf {
|
||||
if p != 0 {
|
||||
@@ -1079,7 +1107,7 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Value {
|
||||
}
|
||||
}
|
||||
if isZero {
|
||||
return llvm.ConstNull(llvmType)
|
||||
return llvm.ConstNull(llvmType), nil
|
||||
}
|
||||
switch llvmType.TypeKind() {
|
||||
case llvm.IntegerTypeKind:
|
||||
@@ -1088,7 +1116,17 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Value {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return llvm.ConstPtrToInt(ptr.toLLVMValue(llvm.Type{}, mem), llvmType)
|
||||
if checks && mem.r.targetData.TypeAllocSize(llvmType) != mem.r.targetData.TypeAllocSize(mem.r.i8ptrType) {
|
||||
// Probably trying to serialize a pointer to a byte array,
|
||||
// perhaps as a result of rawLLVMValue() in a previous interp
|
||||
// run.
|
||||
return llvm.Value{}, errInvalidPtrToIntSize
|
||||
}
|
||||
v, err := ptr.toLLVMValue(llvm.Type{}, mem)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
return llvm.ConstPtrToInt(v, llvmType), nil
|
||||
}
|
||||
var n uint64
|
||||
switch llvmType.IntTypeWidth() {
|
||||
@@ -1108,7 +1146,7 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Value {
|
||||
default:
|
||||
panic("unknown integer size")
|
||||
}
|
||||
return llvm.ConstInt(llvmType, n, false)
|
||||
return llvm.ConstInt(llvmType, n, false), nil
|
||||
case llvm.StructTypeKind:
|
||||
fieldTypes := llvmType.StructElementTypes()
|
||||
fields := make([]llvm.Value, len(fieldTypes))
|
||||
@@ -1117,12 +1155,16 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Value {
|
||||
field := rawValue{
|
||||
buf: v.buf[offset:],
|
||||
}
|
||||
fields[i] = field.toLLVMValue(fieldType, mem)
|
||||
var err error
|
||||
fields[i], err = field.toLLVMValue(fieldType, mem)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
}
|
||||
if llvmType.StructName() != "" {
|
||||
return llvm.ConstNamedStruct(llvmType, fields)
|
||||
return llvm.ConstNamedStruct(llvmType, fields), nil
|
||||
}
|
||||
return llvmType.Context().ConstStruct(fields, false)
|
||||
return llvmType.Context().ConstStruct(fields, false), nil
|
||||
case llvm.ArrayTypeKind:
|
||||
numElements := llvmType.ArrayLength()
|
||||
childType := llvmType.ElementType()
|
||||
@@ -1133,27 +1175,34 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Value {
|
||||
field := rawValue{
|
||||
buf: v.buf[offset:],
|
||||
}
|
||||
fields[i] = field.toLLVMValue(childType, mem)
|
||||
var err error
|
||||
fields[i], err = field.toLLVMValue(childType, mem)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
if checks && fields[i].Type() != childType {
|
||||
panic("child type doesn't match")
|
||||
}
|
||||
}
|
||||
return llvm.ConstArray(childType, fields)
|
||||
return llvm.ConstArray(childType, fields), nil
|
||||
case llvm.PointerTypeKind:
|
||||
if v.buf[0] > 255 {
|
||||
// This is a regular pointer.
|
||||
llvmValue := pointerValue{v.buf[0]}.toLLVMValue(llvm.Type{}, mem)
|
||||
llvmValue, err := pointerValue{v.buf[0]}.toLLVMValue(llvm.Type{}, mem)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
if llvmValue.Type() != llvmType {
|
||||
llvmValue = llvm.ConstBitCast(llvmValue, llvmType)
|
||||
}
|
||||
return llvmValue
|
||||
return llvmValue, nil
|
||||
}
|
||||
// This is either a null pointer or a raw pointer for memory-mapped I/O
|
||||
// (such as 0xe000ed00).
|
||||
ptr := rawValue{v.buf[:mem.r.pointerSize]}.Uint()
|
||||
if ptr == 0 {
|
||||
// Null pointer.
|
||||
return llvm.ConstNull(llvmType)
|
||||
return llvm.ConstNull(llvmType), nil
|
||||
}
|
||||
var ptrValue llvm.Value // the underlying int
|
||||
switch mem.r.pointerSize {
|
||||
@@ -1164,19 +1213,19 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Value {
|
||||
case 2:
|
||||
ptrValue = llvm.ConstInt(llvmType.Context().Int16Type(), ptr, false)
|
||||
default:
|
||||
panic("unknown pointer size")
|
||||
return llvm.Value{}, errors.New("interp: unknown pointer size")
|
||||
}
|
||||
return llvm.ConstIntToPtr(ptrValue, llvmType)
|
||||
return llvm.ConstIntToPtr(ptrValue, llvmType), nil
|
||||
case llvm.DoubleTypeKind:
|
||||
b := rawValue{v.buf[:8]}.Uint()
|
||||
f := math.Float64frombits(b)
|
||||
return llvm.ConstFloat(llvmType, f)
|
||||
return llvm.ConstFloat(llvmType, f), nil
|
||||
case llvm.FloatTypeKind:
|
||||
b := uint32(rawValue{v.buf[:4]}.Uint())
|
||||
f := math.Float32frombits(b)
|
||||
return llvm.ConstFloat(llvmType, float64(f))
|
||||
return llvm.ConstFloat(llvmType, float64(f)), nil
|
||||
default:
|
||||
panic("todo: raw value to LLVM value: " + llvmType.String())
|
||||
return llvm.Value{}, errors.New("interp: todo: raw value to LLVM value: " + llvmType.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1365,8 +1414,8 @@ func (v localValue) Int() int64 {
|
||||
panic("interp: localValue.Int")
|
||||
}
|
||||
|
||||
func (v localValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Value {
|
||||
return v.value
|
||||
func (v localValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) {
|
||||
return v.value, nil
|
||||
}
|
||||
|
||||
func (r *runner) getValue(llvmValue llvm.Value) value {
|
||||
|
||||
+6
-6
@@ -382,14 +382,14 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||
|
||||
// Do CGo processing.
|
||||
if len(p.CgoFiles) != 0 {
|
||||
var cflags []string
|
||||
cflags = append(cflags, p.program.config.CFlags()...)
|
||||
cflags = append(cflags, "-I"+p.Dir)
|
||||
var initialCFlags []string
|
||||
initialCFlags = append(initialCFlags, p.program.config.CFlags()...)
|
||||
initialCFlags = append(initialCFlags, "-I"+p.Dir)
|
||||
if p.program.clangHeaders != "" {
|
||||
cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.program.clangHeaders)
|
||||
initialCFlags = append(initialCFlags, "-Xclang", "-internal-isystem", "-Xclang", p.program.clangHeaders)
|
||||
}
|
||||
p.CFlags = cflags
|
||||
generated, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.program.fset, cflags)
|
||||
generated, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.program.fset, initialCFlags)
|
||||
p.CFlags = append(initialCFlags, cflags...)
|
||||
for path, hash := range accessedFiles {
|
||||
p.FileHashes[path] = hash
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ func copyFile(src, dst string) error {
|
||||
// executeCommand is a simple wrapper to exec.Cmd
|
||||
func executeCommand(options *compileopts.Options, name string, arg ...string) *exec.Cmd {
|
||||
if options.PrintCommands {
|
||||
fmt.Printf("%s %s\n ", name, strings.Join(arg, " "))
|
||||
fmt.Printf("%s %s\n", name, strings.Join(arg, " "))
|
||||
}
|
||||
return exec.Command(name, arg...)
|
||||
}
|
||||
|
||||
+1
-14
@@ -15,7 +15,6 @@ import (
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -129,18 +128,6 @@ func runPlatTests(target string, matches []string, t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Due to some problems with LLD, we cannot run links in parallel, or in parallel with compiles.
|
||||
// Therefore, we put a lock around builds and run everything else in parallel.
|
||||
var buildLock sync.Mutex
|
||||
|
||||
// runBuild is a thread-safe wrapper around Build.
|
||||
func runBuild(src, out string, opts *compileopts.Options) error {
|
||||
buildLock.Lock()
|
||||
defer buildLock.Unlock()
|
||||
|
||||
return Build(src, out, opts)
|
||||
}
|
||||
|
||||
func runTest(path, target string, t *testing.T, environmentVars []string, additionalArgs []string) {
|
||||
// Get the expected output for this test.
|
||||
txtpath := path[:len(path)-3] + ".txt"
|
||||
@@ -177,7 +164,7 @@ func runTest(path, target string, t *testing.T, environmentVars []string, additi
|
||||
}
|
||||
|
||||
binary := filepath.Join(tmpdir, "test")
|
||||
err = runBuild("./"+path, binary, config)
|
||||
err = Build("./"+path, binary, config)
|
||||
if err != nil {
|
||||
printCompilerError(t.Log, err)
|
||||
t.Fail()
|
||||
|
||||
@@ -1646,6 +1646,7 @@ func (usbcdc *USBCDC) Flush() error {
|
||||
|
||||
if usbcdc.waitTxc {
|
||||
// waiting for the next flush(), because the transmission is not complete
|
||||
usbcdc.waitTxcRetryCount++
|
||||
return nil
|
||||
}
|
||||
usbcdc.waitTxc = true
|
||||
@@ -1700,7 +1701,7 @@ func (usbcdc *USBCDC) WriteByte(c byte) error {
|
||||
mask := interrupt.Disable()
|
||||
UART0.waitTxc = false
|
||||
UART0.waitTxcRetryCount = 0
|
||||
usbcdc.TxIdx.Set(0)
|
||||
UART0.TxIdx.Set(0)
|
||||
usbLineInfo.lineState = 0
|
||||
interrupt.Restore(mask)
|
||||
break
|
||||
@@ -1868,6 +1869,7 @@ func handleUSB(intr interrupt.Interrupt) {
|
||||
|
||||
// Start of frame
|
||||
if (flags & sam.USB_DEVICE_INTFLAG_SOF) > 0 {
|
||||
UART0.Flush()
|
||||
// if you want to blink LED showing traffic, this would be the place...
|
||||
}
|
||||
|
||||
@@ -1931,13 +1933,7 @@ func handleUSB(intr interrupt.Interrupt) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i == usb_CDC_ENDPOINT_IN && UART0.waitTxc {
|
||||
UART0.waitTxcRetryCount++
|
||||
}
|
||||
}
|
||||
|
||||
UART0.Flush()
|
||||
}
|
||||
|
||||
func initEndpoint(ep, config uint32) {
|
||||
|
||||
@@ -1850,6 +1850,7 @@ func (usbcdc *USBCDC) Flush() error {
|
||||
|
||||
if usbcdc.waitTxc {
|
||||
// waiting for the next flush(), because the transmission is not complete
|
||||
usbcdc.waitTxcRetryCount++
|
||||
return nil
|
||||
}
|
||||
usbcdc.waitTxc = true
|
||||
@@ -1904,7 +1905,7 @@ func (usbcdc *USBCDC) WriteByte(c byte) error {
|
||||
mask := interrupt.Disable()
|
||||
UART0.waitTxc = false
|
||||
UART0.waitTxcRetryCount = 0
|
||||
usbcdc.TxIdx.Set(0)
|
||||
UART0.TxIdx.Set(0)
|
||||
usbLineInfo.lineState = 0
|
||||
interrupt.Restore(mask)
|
||||
break
|
||||
@@ -2074,6 +2075,7 @@ func handleUSBIRQ(interrupt.Interrupt) {
|
||||
|
||||
// Start of frame
|
||||
if (flags & sam.USB_DEVICE_INTFLAG_SOF) > 0 {
|
||||
UART0.Flush()
|
||||
// if you want to blink LED showing traffic, this would be the place...
|
||||
}
|
||||
|
||||
@@ -2137,13 +2139,7 @@ func handleUSBIRQ(interrupt.Interrupt) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i == usb_CDC_ENDPOINT_IN && UART0.waitTxc {
|
||||
UART0.waitTxcRetryCount++
|
||||
}
|
||||
}
|
||||
|
||||
UART0.Flush()
|
||||
}
|
||||
|
||||
func initEndpoint(ep, config uint32) {
|
||||
|
||||
@@ -65,7 +65,7 @@ func (usbcdc *USBCDC) Flush() error {
|
||||
}
|
||||
|
||||
// WriteByte writes a byte of data to the USB CDC interface.
|
||||
func (usbcdc USBCDC) WriteByte(c byte) error {
|
||||
func (usbcdc *USBCDC) WriteByte(c byte) error {
|
||||
// Supposedly to handle problem with Windows USB serial ports?
|
||||
if usbLineInfo.lineState > 0 {
|
||||
ok := false
|
||||
@@ -199,6 +199,7 @@ func (usbcdc *USBCDC) handleInterrupt(interrupt.Interrupt) {
|
||||
if nrf.USBD.EVENTS_SOF.Get() == 1 {
|
||||
nrf.USBD.EVENTS_SOF.Set(0)
|
||||
USB.Flush()
|
||||
// if you want to blink LED showing traffic, this would be the place...
|
||||
}
|
||||
|
||||
// USBD ready event
|
||||
|
||||
Vendored
+17
@@ -74,6 +74,14 @@ func main() {
|
||||
//Test deferred builtins
|
||||
testDeferBuiltinClose()
|
||||
testDeferBuiltinDelete()
|
||||
|
||||
// Check for issue 1304.
|
||||
// There are two fields in this struct, one of which is zero-length so the
|
||||
// other covers the entire struct. This led to a verification error for
|
||||
// debug info, which used DW_OP_LLVM_fragment for a field that practically
|
||||
// covered the entire variable.
|
||||
var x issue1304
|
||||
x.call()
|
||||
}
|
||||
|
||||
func runFunc(f func(int), arg int) {
|
||||
@@ -211,3 +219,12 @@ func foo(bar *Bar) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type issue1304 struct {
|
||||
a [0]int // zero-length field
|
||||
b int // field 'b' covers entire struct
|
||||
}
|
||||
|
||||
func (x issue1304) call() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
Vendored
+2
@@ -20,6 +20,8 @@ int globalUnionSize = sizeof(globalUnion);
|
||||
option_t globalOption = optionG;
|
||||
bitfield_t globalBitfield = {244, 15, 1, 2, 47, 5};
|
||||
|
||||
int cflagsConstant = SOME_CONSTANT;
|
||||
|
||||
int smallEnumWidth = sizeof(option2_t);
|
||||
|
||||
int fortytwo() {
|
||||
|
||||
Vendored
+4
@@ -5,6 +5,7 @@ int fortytwo(void);
|
||||
#include "main.h"
|
||||
int mul(int, int);
|
||||
#include <string.h>
|
||||
#cgo CFLAGS: -DSOME_CONSTANT=17
|
||||
*/
|
||||
import "C"
|
||||
|
||||
@@ -118,6 +119,9 @@ func main() {
|
||||
// Check that enums are considered the same width in C and CGo.
|
||||
println("enum width matches:", unsafe.Sizeof(C.option2_t(0)) == uintptr(C.smallEnumWidth))
|
||||
|
||||
// Check whether CFLAGS are correctly passed on to compiled C files.
|
||||
println("CFLAGS value:", C.cflagsConstant)
|
||||
|
||||
// libc: test whether C functions work at all.
|
||||
buf1 := []byte("foobar\x00")
|
||||
buf2 := make([]byte, len(buf1))
|
||||
|
||||
Vendored
+2
@@ -139,6 +139,8 @@ extern bitfield_t globalBitfield;
|
||||
|
||||
extern int smallEnumWidth;
|
||||
|
||||
extern int cflagsConstant;
|
||||
|
||||
// test duplicate definitions
|
||||
int add(int a, int b);
|
||||
extern int global;
|
||||
|
||||
Vendored
+1
@@ -58,4 +58,5 @@ option G: 12
|
||||
option 2A: 20
|
||||
option 3A: 21
|
||||
enum width matches: true
|
||||
CFLAGS value: 17
|
||||
copied string: foobar
|
||||
|
||||
@@ -703,6 +703,20 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
|
||||
// function.
|
||||
receiver = p.builder.CreateBitCast(receiver, function.FirstParam().Type(), "")
|
||||
}
|
||||
|
||||
// Check whether the called function has the same signature as would be
|
||||
// expected from the parameters. This can happen in rare cases when
|
||||
// named struct types are renamed after merging multiple LLVM modules.
|
||||
paramTypes := []llvm.Type{receiver.Type()}
|
||||
for _, param := range params {
|
||||
paramTypes = append(paramTypes, param.Type())
|
||||
}
|
||||
calledFunctionType := function.Type()
|
||||
sig := llvm.PointerType(llvm.FunctionType(calledFunctionType.ElementType().ReturnType(), paramTypes, false), calledFunctionType.PointerAddressSpace())
|
||||
if sig != function.Type() {
|
||||
function = p.builder.CreateBitCast(function, sig, "")
|
||||
}
|
||||
|
||||
retval := p.builder.CreateCall(function, append([]llvm.Value{receiver}, params...), "")
|
||||
if retval.Type().TypeKind() == llvm.VoidTypeKind {
|
||||
p.builder.CreateRetVoid()
|
||||
|
||||
Reference in New Issue
Block a user