Compare commits

..

1 Commits

Author SHA1 Message Date
sago35 20883b9fac build: make msd flash faster 2021-03-15 23:07:27 +09:00
167 changed files with 2570 additions and 5049 deletions
+1 -5
View File
@@ -172,11 +172,7 @@ commands:
key: llvm-build-11-linux-v2-assert
paths:
llvm-build
- 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
- run: make ASSERT=1
- build-wasi-libc
- run:
name: "Test TinyGo"
-7
View File
@@ -19,10 +19,3 @@ src/device/kendryte/*.s
vendor
llvm-build
llvm-project
# Ignore files generated by smoketest
test.gba
test.hex
test.nro
test.wasm
wasm.wasm
+20 -34
View File
@@ -107,10 +107,7 @@ fmt-check:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp
ifneq ($(STM32), 0)
gen-device: gen-device-stm32
endif
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32 gen-device-kendryte gen-device-nxp
gen-device-avr:
@if [ ! -e lib/avr/README.md ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
@@ -180,7 +177,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 ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./cgo ./compileopts ./compiler ./interp ./transform .
# Test known-working standard library packages.
# TODO: do this in one command, parallelize, and only show failing tests (no
@@ -260,6 +257,8 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky2
@@ -268,10 +267,6 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10059 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10059 examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m0 examples/blinky1
@@ -280,6 +275,14 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-stm32f405 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-bluefruit examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
@@ -304,8 +307,12 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=particle-xenon examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-f103rb examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pinetime-devkit0 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=lgt92 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=x9pro examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10056-s140v7 examples/blinky1
@@ -332,6 +339,10 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=teensy36 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l552ze examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=p1am-100 examples/blinky1
@$(MD5SUM) test.hex
# test pwm
@@ -343,28 +354,6 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pyportal examples/pwm
@$(MD5SUM) test.hex
ifneq ($(STM32), 0)
$(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-stm32f405 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=lgt92 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-f103rb examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l552ze examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/blinky1
@$(MD5SUM) test.hex
endif
ifneq ($(AVR), 0)
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
@$(MD5SUM) test.hex
@@ -402,9 +391,6 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -o test.nro -target=nintendoswitch examples/serial
@$(MD5SUM) test.nro
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go
@$(MD5SUM) test.hex
wasmtest:
$(GO) test ./tests/wasm
+98 -291
View File
@@ -4,19 +4,16 @@
package builder
import (
"crypto/sha512"
"debug/elf"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"go/types"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
@@ -41,41 +38,13 @@ type BuildResult struct {
MainDir string
}
// packageAction is the struct that is serialized to JSON and hashed, to work as
// a cache key of compiled packages. It should contain all the information that
// goes into a compiled package to avoid using stale data.
//
// Right now it's still important to include a hash of every import, because a
// dependency might have a public constant that this package uses and thus this
// package will need to be recompiled if that constant changes. In the future,
// the type data should be serialized to disk which can then be used as cache
// key, avoiding the need for recompiling all dependencies when only the
// implementation of an imported package changes.
type packageAction struct {
ImportPath string
CompilerVersion int // compiler.Version
InterpVersion int // interp.Version
LLVMVersion string
Config *compiler.Config
CFlags []string
FileHashes map[string]string // hash of every file that's part of the package
Imports map[string]string // map from imported package to action ID hash
}
// Build performs a single package to executable Go build. It takes in a package
// name, an output path, and set of compile options and from that it manages the
// whole compilation process.
//
// The error value may be of type *MultiError. Callers will likely want to check
// for this case and print such errors individually.
func Build(pkgName, outpath string, config *compileopts.Config, action func(BuildResult) error) error {
// Create a temporary directory for intermediary files.
dir, err := ioutil.TempDir("", "tinygo")
if err != nil {
return err
}
defer os.RemoveAll(dir)
func Build(pkgName, outpath string, config *compileopts.Config, preAction func() error, action func(BuildResult) error) error {
compilerConfig := &compiler.Config{
Triple: config.Triple(),
CPU: config.CPU(),
@@ -118,216 +87,31 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
// Makefile target.
var jobs []*compileJob
// Create the *ssa.Program. This does not yet build the entire SSA of the
// program so it's pretty fast and doesn't need to be parallelized.
program := lprogram.LoadSSA()
// Add jobs to compile each package.
// Packages that have a cache hit will not be compiled again.
var packageJobs []*compileJob
packageBitcodePaths := make(map[string]string)
packageActionIDs := make(map[string]string)
for _, pkg := range lprogram.Sorted() {
pkg := pkg // necessary to avoid a race condition
// Create a cache key: a hash from the action ID below that contains all
// the parameters for the build.
actionID := packageAction{
ImportPath: pkg.ImportPath,
CompilerVersion: compiler.Version,
InterpVersion: interp.Version,
LLVMVersion: llvm.Version,
Config: compilerConfig,
CFlags: pkg.CFlags,
FileHashes: make(map[string]string, len(pkg.FileHashes)),
Imports: make(map[string]string, len(pkg.Pkg.Imports())),
}
for filePath, hash := range pkg.FileHashes {
actionID.FileHashes[filePath] = hex.EncodeToString(hash)
}
for _, imported := range pkg.Pkg.Imports() {
hash, ok := packageActionIDs[imported.Path()]
if !ok {
return fmt.Errorf("package %s imports %s but couldn't find dependency", pkg.ImportPath, imported.Path())
}
actionID.Imports[imported.Path()] = hash
}
buf, err := json.Marshal(actionID)
if err != nil {
panic(err) // shouldn't happen
}
hash := sha512.Sum512_224(buf)
packageActionIDs[pkg.ImportPath] = hex.EncodeToString(hash[:])
// Determine the path of the bitcode file (which is a serialized version
// of a LLVM module).
cacheDir := goenv.Get("GOCACHE")
if cacheDir == "off" {
// Use temporary build directory instead, effectively disabling the
// build cache.
cacheDir = dir
}
bitcodePath := filepath.Join(cacheDir, "pkg-"+hex.EncodeToString(hash[:])+".bc")
packageBitcodePaths[pkg.ImportPath] = bitcodePath
// Check whether this package has been compiled before, and if so don't
// compile it again.
if _, err := os.Stat(bitcodePath); err == nil {
// Already cached, don't recreate this package.
continue
}
// The package has not yet been compiled, so create a job to do so.
job := &compileJob{
description: "compile package " + pkg.ImportPath,
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())
if errs != nil {
return newMultiError(errs)
}
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
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
// that might also be compiling this package at the same time.
f, err := ioutil.TempFile(filepath.Dir(bitcodePath), filepath.Base(bitcodePath))
if err != nil {
return err
}
if runtime.GOOS == "windows" {
// Work around a problem on Windows.
// For some reason, WriteBitcodeToFile causes TinyGo to
// exit with the following message:
// LLVM ERROR: IO failure on output stream: Bad file descriptor
buf := llvm.WriteBitcodeToMemoryBuffer(mod)
defer buf.Dispose()
_, err = f.Write(buf.Bytes())
} else {
// Otherwise, write bitcode directly to the file (probably
// faster).
err = llvm.WriteBitcodeToFile(mod, f)
}
if err != nil {
// WriteBitcodeToFile doesn't produce a useful error on its
// own, so create a somewhat useful error message here.
return fmt.Errorf("failed to write bitcode for package %s to file %s", pkg.ImportPath, bitcodePath)
}
err = f.Close()
if err != nil {
return err
}
return os.Rename(f.Name(), bitcodePath)
},
}
jobs = append(jobs, job)
packageJobs = append(packageJobs, job)
if preAction != nil {
// Add job to preAction.
jobs = append(jobs, &compileJob{
description: "preAction",
run: preAction,
})
}
// Add job that links and optimizes all packages together.
// Add job to compile and optimize all Go files at once.
// TODO: parallelize this.
var mod llvm.Module
var stackSizeLoads []string
programJob := &compileJob{
description: "link+optimize packages (LTO)",
dependencies: packageJobs,
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()
mod = ctx.NewModule("")
for _, pkg := range lprogram.Sorted() {
pkgMod, err := ctx.ParseBitcodeFile(packageBitcodePaths[pkg.ImportPath])
if err != nil {
return fmt.Errorf("failed to load bitcode file: %w", err)
}
err = llvm.LinkModules(mod, pkgMod)
if err != nil {
return fmt.Errorf("failed to link module: %w", err)
}
}
// Create runtime.initAll function that calls the runtime
// initializer of each package.
llvmInitFn := mod.NamedFunction("runtime.initAll")
llvmInitFn.SetLinkage(llvm.InternalLinkage)
llvmInitFn.SetUnnamedAddr(true)
llvmInitFn.Param(0).SetName("context")
llvmInitFn.Param(1).SetName("parentHandle")
block := mod.Context().AddBasicBlock(llvmInitFn, "entry")
irbuilder := mod.Context().NewBuilder()
defer irbuilder.Dispose()
irbuilder.SetInsertPointAtEnd(block)
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
for _, pkg := range lprogram.Sorted() {
pkgInit := mod.NamedFunction(pkg.Pkg.Path() + ".init")
if pkgInit.IsNil() {
panic("init not found for " + pkg.Pkg.Path())
}
irbuilder.CreateCall(pkgInit, []llvm.Value{llvm.Undef(i8ptrType), llvm.Undef(i8ptrType)}, "")
}
irbuilder.CreateRetVoid()
// After linking, functions should (as far as possible) be set to
// private linkage or internal linkage. The compiler package marks
// non-exported functions by setting the visibility to hidden or
// (for thunks) to linkonce_odr linkage. Change the linkage here to
// internal to benefit much more from interprocedural optimizations.
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
if fn.Visibility() == llvm.HiddenVisibility {
fn.SetVisibility(llvm.DefaultVisibility)
fn.SetLinkage(llvm.InternalLinkage)
} else if fn.Linkage() == llvm.LinkOnceODRLinkage {
fn.SetLinkage(llvm.InternalLinkage)
}
}
// Do the same for globals.
for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
if global.Visibility() == llvm.HiddenVisibility {
global.SetVisibility(llvm.DefaultVisibility)
global.SetLinkage(llvm.InternalLinkage)
} else if global.Linkage() == llvm.LinkOnceODRLinkage {
global.SetLinkage(llvm.InternalLinkage)
}
}
if config.Options.PrintIR {
fmt.Println("; Generated LLVM IR:")
fmt.Println(mod.String())
}
// Run all optimization passes, which are much more effective now
// that the optimizer can see the whole program at once.
err := optimizeProgram(mod, config)
description: "compile Go files",
run: func() (err error) {
mod, err = compileWholeProgram(pkgName, config, compilerConfig, lprogram, machine)
if err != nil {
return err
return
}
// Make sure stack sizes are loaded from a separate section so they can be
// modified after linking.
if config.AutomaticStackSize() {
stackSizeLoads = transform.CreateStackSizeLoads(mod, config)
}
return nil
return
},
}
jobs = append(jobs, programJob)
@@ -364,13 +148,19 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
// First add all jobs necessary to build this object file, then afterwards
// run all jobs in parallel as far as possible.
// Create a temporary directory for intermediary files.
dir, err := ioutil.TempDir("", "tinygo")
if err != nil {
return err
}
defer os.RemoveAll(dir)
// Add job to write the output object file.
objfile := filepath.Join(dir, "main.o")
outputObjectFileJob := &compileJob{
description: "generate output file",
dependencies: []*compileJob{programJob},
result: objfile,
run: func(*compileJob) error {
run: func() error {
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
@@ -384,32 +174,40 @@ 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)
ldflags := append(config.LDFlags(), "-o", executable, objfile)
// Add compiler-rt dependency if needed. Usually this is a simple load from
// a cache.
if config.Target.RTLib == "compiler-rt" {
job, err := CompilerRT.load(config.Triple(), config.CPU(), dir)
path, job, err := CompilerRT.load(config.Triple(), config.CPU(), dir)
if err != nil {
return err
}
jobs = append(jobs, job.dependencies...)
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
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)
}
// Add libc dependency if needed.
root := goenv.Get("TINYGOROOT")
switch config.Target.Libc {
case "picolibc":
job, err := Picolibc.load(config.Triple(), config.CPU(), dir)
path, job, err := Picolibc.load(config.Triple(), config.CPU(), dir)
if err != nil {
return err
}
// The library needs to be compiled (cache miss).
jobs = append(jobs, job.dependencies...)
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
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)
case "wasi-libc":
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
if _, err := os.Stat(path); os.IsNotExist(err) {
@@ -425,37 +223,44 @@ 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 _, path := range config.ExtraFiles() {
for i, 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(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, dir, config.CFlags(), config)
job.result = result
return err
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
},
}
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 _, pkg := range lprogram.Sorted() {
pkg := pkg
for _, filename := range pkg.CFiles {
abspath := filepath.Join(pkg.Dir, filename)
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")
job := &compileJob{
description: "compile CGo file " + abspath,
run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, dir, pkg.CFlags, config)
job.result = result
return err
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
},
}
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
ldflags = append(ldflags, outpath)
}
}
@@ -470,16 +275,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
jobs = append(jobs, &compileJob{
description: "link",
dependencies: linkerDependencies,
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, " "))
}
run: func() error {
err = link(config.Target.Linker, ldflags...)
if err != nil {
return &commandError{"failed to link", executable, err}
@@ -578,25 +374,29 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
})
}
// optimizeProgram runs a series of optimizations and transformations that are
// needed to convert a program to its final form. Some transformations are not
// optional and must be run as the compiler expects them to run.
func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
// compileWholeProgram compiles the entire *loader.Program to a LLVM module and
// applies most necessary optimizations and transformations.
func compileWholeProgram(pkgName string, config *compileopts.Config, compilerConfig *compiler.Config, lprogram *loader.Program, machine llvm.TargetMachine) (llvm.Module, error) {
// Compile AST to IR.
mod, errs := compiler.CompileProgram(lprogram, machine, compilerConfig, config.DumpSSA())
if errs != nil {
return mod, newMultiError(errs)
}
if config.Options.PrintIR {
fmt.Println("; Generated LLVM IR:")
fmt.Println(mod.String())
}
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return mod, errors.New("verification error after IR construction")
}
err := interp.Run(mod, config.DumpSSA())
if err != nil {
return err
return mod, err
}
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 err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return mod, errors.New("verification error after interpreting runtime.initAll")
}
if config.GOOS() != "darwin" {
@@ -611,16 +411,23 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
if config.WasmAbi() == "js" {
err := transform.ExternalInt64AsPtr(mod)
if err != nil {
return err
return mod, err
}
}
// Optimization levels here are roughly the same as Clang, but probably not
// exactly.
var errs []error
errs = nil
switch config.Options.Opt {
case "none", "0":
errs = transform.Optimize(mod, config, 0, 0, 0) // -O0
/*
Currently, turning optimizations off causes compile failures.
We rely on the optimizer removing some dead symbols.
Avoid providing an option that does not work right now.
In the future once everything has been fixed we can re-enable this.
case "none", "0":
errs = transform.Optimize(mod, config, 0, 0, 0) // -O0
*/
case "1":
errs = transform.Optimize(mod, config, 1, 0, 0) // -O1
case "2":
@@ -630,13 +437,13 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
case "z":
errs = transform.Optimize(mod, config, 2, 2, 5) // -Oz, default
default:
return errors.New("unknown optimization level: -opt=" + config.Options.Opt)
errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)}
}
if len(errs) > 0 {
return newMultiError(errs)
return mod, newMultiError(errs)
}
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification failure after LLVM optimization passes")
return mod, errors.New("verification failure after LLVM optimization passes")
}
// LLVM 11 by default tries to emit tail calls (even with the target feature
@@ -650,7 +457,7 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
transform.DisableTailCalls(mod)
}
return nil
return mod, nil
}
// functionStackSizes keeps stack size information about a single function
-305
View File
@@ -1,305 +0,0 @@
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
}
-33
View File
@@ -1,33 +0,0 @@
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
}
}
}
+4 -17
View File
@@ -27,23 +27,12 @@ const (
type compileJob struct {
description string // description, only used for logging
dependencies []*compileJob
result string // result (path)
run func(*compileJob) (err error)
run func() 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 {
@@ -161,11 +150,9 @@ func nextJob(jobs []*compileJob) *compileJob {
func jobWorker(workerChan, doneChan chan *compileJob) {
for job := range workerChan {
start := time.Now()
if job.run != nil {
err := job.run(job)
if err != nil {
job.err = err
}
err := job.run()
if err != nil {
job.err = err
}
job.duration = time.Since(start)
doneChan <- job
+17 -16
View File
@@ -42,28 +42,30 @@ 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) {
job, err := l.load(target, "", tmpdir)
path, job, err := l.load(target, "", tmpdir)
if err != nil {
return "", err
}
jobs := append([]*compileJob{job}, job.dependencies...)
err = runJobs(jobs)
return job.result, err
if job != nil {
jobs := append([]*compileJob{job}, job.dependencies...)
err = runJobs(jobs)
}
return path, err
}
// 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.
// 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.
// 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) (job *compileJob, err error) {
func (l *Library) load(target, cpu, tmpdir string) (path 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 dummyCompileJob(precompiledPath), nil
return precompiledPath, nil, nil
}
var outfile string
@@ -76,7 +78,7 @@ func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error)
// Try to fetch this library from the cache.
if path, err := cacheLoad(outfile, l.sourcePaths(target)); path != "" || err != nil {
// Cache hit.
return dummyCompileJob(path), nil
return path, nil, err
}
// Cache miss, build it now.
@@ -84,7 +86,7 @@ func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error)
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.
@@ -111,8 +113,7 @@ func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error)
arpath := filepath.Join(dir, l.name+".a")
job = &compileJob{
description: "ar " + l.name + ".a",
result: arpath,
run: func(*compileJob) error {
run: func() error {
// Create an archive of all object files.
err := makeArchive(arpath, objs)
if err != nil {
@@ -132,7 +133,7 @@ func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error)
objs = append(objs, objpath)
job.dependencies = append(job.dependencies, &compileJob{
description: "compile " + srcpath,
run: func(*compileJob) error {
run: func() error {
var compileArgs []string
compileArgs = append(compileArgs, args...)
compileArgs = append(compileArgs, "-o", objpath, srcpath)
@@ -145,5 +146,5 @@ func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error)
})
}
return job, nil
return arpath, job, nil
}
+13 -19
View File
@@ -41,9 +41,7 @@ type cgoPackage struct {
elaboratedTypes map[string]*elaboratedTypeInfo
enums map[string]enumInfo
anonStructNum int
cflags []string // CFlags from #cgo lines
ldflags []string // LDFlags from #cgo lines
visitedFiles map[string][]byte
ldflags []string
}
// constantInfo stores some information about a CGo constant found by libclang
@@ -158,10 +156,9 @@ 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 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) {
// 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, []error) {
p := &cgoPackage{
dir: dir,
fset: fset,
@@ -173,9 +170,13 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
typedefs: map[string]*typedefInfo{},
elaboratedTypes: map[string]*elaboratedTypeInfo{},
enums: map[string]enumInfo{},
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})
@@ -184,7 +185,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, nil, []error{
return nil, nil, []error{
scanner.Error{
Pos: fset.Position(files[0].Pos()),
Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error
@@ -359,7 +360,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
continue
}
makePathsAbsolute(flags, packagePath)
p.cflags = append(p.cflags, flags...)
cflags = append(cflags, flags...)
case "LDFLAGS":
flags, err := shlex.Split(value)
if err != nil {
@@ -382,13 +383,6 @@ 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()
@@ -398,7 +392,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, cflagsForCGo, position.Filename, position.Line)
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
}
// Declare functions found by libclang.
@@ -433,7 +427,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.cflags, p.ldflags, p.visitedFiles, p.errors
return p.generated, p.ldflags, p.errors
}
// makePathsAbsolute converts some common path compiler flags (-I, -L) from
+1 -1
View File
@@ -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
-35
View File
@@ -4,7 +4,6 @@ package cgo
// modification. It does not touch the AST itself.
import (
"crypto/sha512"
"fmt"
"go/ast"
"go/scanner"
@@ -57,7 +56,6 @@ unsigned tinygo_clang_Cursor_isBitField(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);
int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
void tinygo_clang_inclusion_visitor(CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data);
*/
import "C"
@@ -116,7 +114,6 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
}
defer C.clang_disposeTranslationUnit(unit)
// Report parser and type errors.
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
addDiagnostic := func(diagnostic C.CXDiagnostic) {
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
@@ -137,36 +134,10 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
}
}
// Extract information required by CGo.
ref := storedRefs.Put(p)
defer storedRefs.Remove(ref)
cursor := C.tinygo_clang_getTranslationUnitCursor(unit)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref))
// Determine files read during CGo processing, for caching.
inclusionCallback := func(includedFile C.CXFile) {
// Get full file path.
path := getString(C.clang_getFileName(includedFile))
// Get contents of file (that should be in-memory).
size := C.size_t(0)
rawData := C.clang_getFileContents(unit, includedFile, &size)
if rawData == nil {
// Sanity check. This should (hopefully) never trigger.
panic("libclang: file contents was not loaded")
}
data := (*[1 << 24]byte)(unsafe.Pointer(rawData))[:size]
// Hash the contents if it isn't hashed yet.
if _, ok := p.visitedFiles[path]; !ok {
// already stored
sum := sha512.Sum512_224(data)
p.visitedFiles[path] = sum[:]
}
}
inclusionCallbackRef := storedRefs.Put(inclusionCallback)
defer storedRefs.Remove(inclusionCallbackRef)
C.clang_getInclusions(unit, C.CXInclusionVisitor(C.tinygo_clang_inclusion_visitor), C.CXClientData(inclusionCallbackRef))
}
//export tinygo_clang_globals_visitor
@@ -801,9 +772,3 @@ func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientDat
}
return C.CXChildVisit_Continue
}
//export tinygo_clang_inclusion_visitor
func tinygo_clang_inclusion_visitor(includedFile C.CXFile, inclusionStack *C.CXSourceLocation, includeLen C.unsigned, clientData C.CXClientData) {
callback := storedRefs.Get(unsafe.Pointer(clientData)).(func(C.CXFile))
callback(includedFile)
}
+3 -24
View File
@@ -7,7 +7,6 @@ import (
"errors"
"io"
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
@@ -43,7 +42,7 @@ type TargetSpec struct {
ExtraFiles []string `json:"extra-files"`
Emulator []string `json:"emulator" override:"copy"` // inherited Emulator must not be append
FlashCommand string `json:"flash-command"`
GDB []string `json:"gdb"`
GDB string `json:"gdb"`
PortReset string `json:"flash-1200-bps-reset"`
FlashMethod string `json:"flash-method"`
FlashVolume string `json:"msd-volume-name"`
@@ -230,13 +229,7 @@ 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{
@@ -247,7 +240,7 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
Compiler: "clang",
Linker: "cc",
CFlags: []string{"--target=" + triple},
GDB: []string{"gdb"},
GDB: "gdb",
PortReset: "false",
}
if goos == "darwin" {
@@ -260,7 +253,7 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
}
if goarch != runtime.GOARCH {
// Some educated guesses as to how to invoke helper programs.
spec.GDB = []string{"gdb-multiarch"}
spec.GDB = "gdb-multiarch"
if goarch == "arm" && goos == "linux" {
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/arm-linux-gnueabihf")
spec.Linker = "arm-linux-gnueabihf-gcc"
@@ -278,17 +271,3 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
}
return &spec, nil
}
// LookupGDB looks up a gdb executable.
func (spec *TargetSpec) LookupGDB() (string, error) {
if len(spec.GDB) == 0 {
return "", errors.New("gdb not configured in the target specification")
}
for _, d := range spec.GDB {
_, err := exec.LookPath(d)
if err == nil {
return d, nil
}
}
return "", errors.New("no gdb found configured in the target specification (" + strings.Join(spec.GDB, ", ") + ")")
}
-3
View File
@@ -159,9 +159,6 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str
}
switch inst := inst.(type) {
case *ssa.Alloc:
// An alloc is never nil.
return
case *ssa.IndexAddr:
// This pointer is the result of an index operation into a slice or
// array. Such slices/arrays are already bounds checked so the pointer
+10 -22
View File
@@ -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 (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
func expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
switch t.TypeKind() {
case llvm.StructTypeKind:
fieldInfos := c.flattenAggregateType(t, name, goType)
fieldInfos := 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 := b.flattenAggregateType(v.Type(), "", nil)
fieldInfos := flattenAggregateType(v.Type(), "", nil)
if len(fieldInfos) <= maxFieldsPerParam {
fields := b.flattenAggregate(v)
if len(fields) != len(fieldInfos) {
@@ -124,15 +124,12 @@ 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 (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
func flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
typeFlags := getTypeFlags(goType)
switch t.TypeKind() {
case llvm.StructTypeKind:
var paramInfos []paramInfo
paramInfos := make([]paramInfo, 0, t.StructElementTypesCount())
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,
@@ -155,7 +152,7 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType
suffix = []string{"context", "funcptr"}[i]
}
}
subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
subInfos := flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
for i := range subInfos {
subInfos[i].flags |= typeFlags
}
@@ -221,11 +218,8 @@ func extractSubfield(t types.Type, field int) types.Type {
func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
switch t.TypeKind() {
case llvm.StructTypeKind:
var fields []uint64
fields := make([]uint64, 0, t.StructElementTypesCount())
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 {
@@ -244,11 +238,8 @@ func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value {
switch v.Type().TypeKind() {
case llvm.StructTypeKind:
var fields []llvm.Value
for i, field := range v.Type().StructElementTypes() {
if b.targetData.TypeAllocSize(field) == 0 {
continue
}
fields := make([]llvm.Value, 0, v.Type().StructElementTypesCount())
for i := range v.Type().StructElementTypes() {
subfield := b.CreateExtractValue(v, i, "")
subfields := b.flattenAggregate(subfield)
fields = append(fields, subfields...)
@@ -275,13 +266,10 @@ 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 := b.flattenAggregateType(t, "", nil)
flattened := 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, "")
+106 -120
View File
@@ -20,11 +20,6 @@ import (
"tinygo.org/x/go-llvm"
)
// 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 = 6 // last change: fix issue 1304
func init() {
llvm.InitializeAllTargets()
llvm.InitializeAllTargetMCs()
@@ -89,13 +84,12 @@ type compilerContext struct {
// importantly with a newly created LLVM context and module.
func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *Config, dumpSSA bool) *compilerContext {
c := &compilerContext{
Config: config,
DumpSSA: dumpSSA,
difiles: make(map[string]llvm.Metadata),
ditypes: make(map[types.Type]llvm.Metadata),
machine: machine,
targetData: machine.CreateTargetData(),
astComments: map[string]*ast.CommentGroup{},
Config: config,
DumpSSA: dumpSSA,
difiles: make(map[string]llvm.Metadata),
ditypes: make(map[types.Type]llvm.Metadata),
machine: machine,
targetData: machine.CreateTargetData(),
}
c.ctx = llvm.NewContext()
@@ -247,14 +241,21 @@ func Sizes(machine llvm.TargetMachine) types.Sizes {
}
}
// CompilePackage compiles a single package to a LLVM module.
func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package, machine llvm.TargetMachine, config *Config, dumpSSA bool) (llvm.Module, []error) {
c := newCompilerContext(moduleName, machine, config, dumpSSA)
c.runtimePkg = ssaPkg.Prog.ImportedPackage("runtime").Pkg
c.program = ssaPkg.Prog
// CompileProgram compiles the given package path or .go file path. Return an
// error when this fails (in any stage). If successful it returns the LLVM
// module. If not, one or more errors will be returned.
func CompileProgram(lprogram *loader.Program, machine llvm.TargetMachine, config *Config, dumpSSA bool) (llvm.Module, []error) {
c := newCompilerContext("", machine, config, dumpSSA)
// Convert AST to SSA.
ssaPkg.Build()
c.program = lprogram.LoadSSA()
c.program.Build()
c.runtimePkg = c.program.ImportedPackage("runtime").Pkg
// Run a simple dead code elimination pass.
functions, err := c.simpleDCE(lprogram)
if err != nil {
return llvm.Module{}, []error{err}
}
// Initialize debug information.
if c.Debug {
@@ -267,37 +268,112 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
})
}
// Load comments such as //go:extern on globals.
c.loadASTComments(pkg)
c.loadASTComments(lprogram)
// Predeclare the runtime.alloc function, which is used by the wordpack
// functionality.
c.getFunction(c.program.ImportedPackage("runtime").Members["alloc"].(*ssa.Function))
// Compile all functions, methods, and global variables in this package.
// Add definitions to declarations.
var initFuncs []llvm.Value
irbuilder := c.ctx.NewBuilder()
defer irbuilder.Dispose()
c.createPackage(irbuilder, ssaPkg)
for _, f := range functions {
if f.Synthetic == "package initializer" {
initFuncs = append(initFuncs, c.getFunction(f))
}
if f.Blocks == nil {
continue // external function
}
// Create the function definition.
b := newBuilder(c, irbuilder, f)
b.createFunction()
}
// After all packages are imported, add a synthetic initializer function
// that calls the initializer of each package.
initFn := c.program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function)
llvmInitFn := c.getFunction(initFn)
llvmInitFn.SetLinkage(llvm.InternalLinkage)
llvmInitFn.SetUnnamedAddr(true)
if c.Debug {
difunc := c.attachDebugInfo(initFn)
pos := c.program.Fset.Position(initFn.Pos())
irbuilder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
llvmInitFn.Param(0).SetName("context")
llvmInitFn.Param(1).SetName("parentHandle")
block := c.ctx.AddBasicBlock(llvmInitFn, "entry")
irbuilder.SetInsertPointAtEnd(block)
for _, fn := range initFuncs {
irbuilder.CreateCall(fn, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.Undef(c.i8ptrType)}, "")
}
irbuilder.CreateRetVoid()
// see: https://reviews.llvm.org/D18355
if c.Debug {
c.mod.AddNamedMetadataOperand("llvm.module.flags",
c.ctx.MDNode([]llvm.Metadata{
llvm.ConstInt(c.ctx.Int32Type(), 1, false).ConstantAsMetadata(), // Error on mismatch
c.ctx.MDString("Debug Info Version"),
llvm.GlobalContext().MDString("Debug Info Version"),
llvm.ConstInt(c.ctx.Int32Type(), 3, false).ConstantAsMetadata(), // DWARF version
}),
)
c.mod.AddNamedMetadataOperand("llvm.module.flags",
c.ctx.MDNode([]llvm.Metadata{
llvm.ConstInt(c.ctx.Int32Type(), 1, false).ConstantAsMetadata(),
c.ctx.MDString("Dwarf Version"),
llvm.GlobalContext().MDString("Dwarf Version"),
llvm.ConstInt(c.ctx.Int32Type(), 4, false).ConstantAsMetadata(),
}),
)
c.dibuilder.Finalize()
}
return c.mod, c.diagnostics
}
// CompilePackage compiles a single package to a LLVM module.
func CompilePackage(moduleName string, pkg *loader.Package, machine llvm.TargetMachine, config *Config, dumpSSA bool) (llvm.Module, []error) {
c := newCompilerContext(moduleName, machine, config, dumpSSA)
// Build SSA from AST.
ssaPkg := pkg.LoadSSA()
ssaPkg.Build()
// Sort by position, so that the order of the functions in the IR matches
// the order of functions in the source file. This is useful for testing,
// for example.
var members []string
for name := range ssaPkg.Members {
members = append(members, name)
}
sort.Slice(members, func(i, j int) bool {
iPos := ssaPkg.Members[members[i]].Pos()
jPos := ssaPkg.Members[members[j]].Pos()
if i == j {
// Cannot sort by pos, so do it by name.
return members[i] < members[j]
}
return iPos < jPos
})
// Define all functions.
irbuilder := c.ctx.NewBuilder()
defer irbuilder.Dispose()
for _, name := range members {
member := ssaPkg.Members[name]
switch member := member.(type) {
case *ssa.Function:
if member.Blocks == nil {
continue // external function
}
// Create the function definition.
b := newBuilder(c, irbuilder, member)
b.createFunction()
}
}
return c.mod, nil
}
@@ -644,9 +720,8 @@ func (c *compilerContext) attachDebugInfo(f *ssa.Function) llvm.Metadata {
// debug info is added to the function.
func (c *compilerContext) attachDebugInfoRaw(f *ssa.Function, llvmFn llvm.Value, suffix, filename string, line int) llvm.Metadata {
// Debug info for this function.
params := getParams(f.Signature)
diparams := make([]llvm.Metadata, 0, len(params))
for _, param := range params {
diparams := make([]llvm.Metadata, 0, len(f.Params))
for _, param := range f.Params {
diparams = append(diparams, c.getDIType(param.Type()))
}
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
@@ -684,83 +759,6 @@ func (c *compilerContext) getDIFile(filename string) llvm.Metadata {
return c.difiles[filename]
}
// createPackage builds the LLVM IR for all types, methods, and global variables
// in the given package.
func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package) {
// Sort by position, so that the order of the functions in the IR matches
// the order of functions in the source file. This is useful for testing,
// for example.
var members []string
for name := range pkg.Members {
members = append(members, name)
}
sort.Slice(members, func(i, j int) bool {
iPos := pkg.Members[members[i]].Pos()
jPos := pkg.Members[members[j]].Pos()
if i == j {
// Cannot sort by pos, so do it by name.
return members[i] < members[j]
}
return iPos < jPos
})
// Define all functions.
for _, name := range members {
member := pkg.Members[name]
switch member := member.(type) {
case *ssa.Function:
if member.Blocks == nil {
continue // external function
}
// Create the function definition.
b := newBuilder(c, irbuilder, member)
b.createFunction()
case *ssa.Type:
if types.IsInterface(member.Type()) {
// Interfaces don't have concrete methods.
continue
}
// Named type. We should make sure all methods are created.
// This includes both functions with pointer receivers and those
// without.
methods := getAllMethods(pkg.Prog, member.Type())
methods = append(methods, getAllMethods(pkg.Prog, types.NewPointer(member.Type()))...)
for _, method := range methods {
// Parse this method.
fn := pkg.Prog.MethodValue(method)
if fn.Blocks == nil {
continue // external function
}
if member.Type().String() != member.String() {
// This is a member on a type alias. Do not build such a
// function.
continue
}
if fn.Synthetic != "" && fn.Synthetic != "package initializer" {
// This function is a kind of wrapper function (created by
// the ssa package, not appearing in the source code) that
// is created by the getFunction method as needed.
// Therefore, don't build it here to avoid "function
// redeclared" errors.
continue
}
// Create the function definition.
b := newBuilder(c, irbuilder, fn)
b.createFunction()
}
case *ssa.Global:
// Global variable.
info := c.getGlobalInfo(member)
if !info.extern {
global := c.getGlobal(member)
global.SetInitializer(llvm.ConstNull(global.Type().ElementType()))
global.SetVisibility(llvm.HiddenVisibility)
}
}
}
}
// createFunction builds the LLVM IR implementation for this function. The
// function must not yet be defined, otherwise this function will create a
// diagnostic.
@@ -769,7 +767,7 @@ func (b *builder) createFunction() {
fmt.Printf("\nfunc %s:\n", b.fn)
}
if !b.llvmFn.IsDeclaration() {
errValue := b.llvmFn.Name() + " redeclared in this program"
errValue := b.fn.Name() + " redeclared in this program"
fnPos := getPosition(b.llvmFn)
if fnPos.IsValid() {
errValue += "\n\tprevious declaration at " + fnPos.String()
@@ -778,15 +776,9 @@ func (b *builder) createFunction() {
return
}
if !b.info.exported {
b.llvmFn.SetVisibility(llvm.HiddenVisibility)
b.llvmFn.SetLinkage(llvm.InternalLinkage)
b.llvmFn.SetUnnamedAddr(true)
}
if b.info.exported && strings.HasPrefix(b.Triple, "wasm") {
// Set the exported name. This is necessary for WebAssembly because
// otherwise the function is not exported.
functionAttr := b.ctx.CreateStringAttribute("wasm-export-name", b.info.linkName)
b.llvmFn.AddFunctionAttr(functionAttr)
}
// Some functions have a pragma controlling the inlining level.
switch b.info.inline {
@@ -829,7 +821,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 b.expandFormalParamType(llvmType, param.Name(), param.Type()) {
for _, info := range expandFormalParamType(llvmType, param.Name(), param.Type()) {
param := b.llvmFn.Param(llvmParamIndex)
param.SetName(info.name)
fields = append(fields, param)
@@ -956,12 +948,6 @@ func (b *builder) createFunction() {
b.trackValue(phi.llvm)
}
}
// Create anonymous functions (closures etc.).
for _, sub := range b.fn.AnonFuncs {
b := newBuilder(b.compilerContext, b.Builder, sub)
b.createFunction()
}
}
// createInstruction builds the LLVM IR equivalent instructions for the
+28 -17
View File
@@ -4,6 +4,7 @@ import (
"flag"
"go/types"
"io/ioutil"
"regexp"
"strconv"
"strings"
"testing"
@@ -19,19 +20,6 @@ var flagUpdate = flag.Bool("update", false, "update tests based on test output")
// Basic tests for the compiler. Build some Go files and compare the output with
// the expected LLVM IR for regression testing.
func TestCompiler(t *testing.T) {
// Check LLVM version.
llvmMajor, err := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
if err != nil {
t.Fatal("could not parse LLVM version:", llvm.Version)
}
if llvmMajor < 11 {
// It is likely this version needs to be bumped in the future.
// The goal is to at least test the LLVM version that's used by default
// in TinyGo and (if possible without too many workarounds) also some
// previous versions.
t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version)
}
target, err := compileopts.LoadTarget("i686--linux")
if err != nil {
t.Fatal("failed to load target:", err)
@@ -59,9 +47,7 @@ func TestCompiler(t *testing.T) {
"basic.go",
"pointer.go",
"slice.go",
"string.go",
"float.go",
"interface.go",
}
for _, testCase := range tests {
@@ -79,9 +65,8 @@ func TestCompiler(t *testing.T) {
}
// Compile AST to IR.
program := lprogram.LoadSSA()
pkg := lprogram.MainPkg()
mod, errs := CompilePackage(testCase, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
mod, errs := CompilePackage(testCase, pkg, machine, compilerConfig, false)
if errs != nil {
for _, err := range errs {
t.Log("error:", err)
@@ -122,6 +107,8 @@ func TestCompiler(t *testing.T) {
}
}
var alignRegexp = regexp.MustCompile(", align [0-9]+$")
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
// equal. That means, only relevant lines are compared (excluding comments
// etc.).
@@ -133,6 +120,15 @@ func fuzzyEqualIR(s1, s2 string) bool {
}
for i, line1 := range lines1 {
line2 := lines2[i]
match1 := alignRegexp.MatchString(line1)
match2 := alignRegexp.MatchString(line2)
if match1 != match2 {
// Only one of the lines has the align keyword. Remove it.
// This is a change to make the test work in both LLVM 10 and LLVM
// 11 (LLVM 11 appears to automatically add alignment everywhere).
line1 = alignRegexp.ReplaceAllString(line1, "")
line2 = alignRegexp.ReplaceAllString(line2, "")
}
if line1 != line2 {
return false
}
@@ -146,6 +142,12 @@ func fuzzyEqualIR(s1, s2 string) bool {
// stripped out.
func filterIrrelevantIRLines(lines []string) []string {
var out []string
llvmVersion, err := strconv.Atoi(strings.Split(llvm.Version, ".")[0])
if err != nil {
// Note: this should never happen and if it does, it will always happen
// for a particular build because llvm.Version is a constant.
panic(err)
}
for _, line := range lines {
line = strings.Split(line, ";")[0] // strip out comments/info
line = strings.TrimRight(line, "\r ") // drop '\r' on Windows and remove trailing spaces from comments
@@ -155,6 +157,15 @@ func filterIrrelevantIRLines(lines []string) []string {
if strings.HasPrefix(line, "source_filename = ") {
continue
}
if llvmVersion < 10 && strings.HasPrefix(line, "attributes ") {
// Ignore attribute groups. These may change between LLVM versions.
// Right now test outputs are for LLVM 10.
continue
}
if llvmVersion < 10 && strings.HasPrefix(line, "target datalayout ") {
// Ignore the target layout. This may change between LLVM versions.
continue
}
out = append(out, line)
}
return out
+2 -2
View File
@@ -352,7 +352,7 @@ func (b *builder) createRunDefers() {
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
for _, param := range getParams(callback.Signature) {
for _, param := range callback.Params {
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
}
deferFrameType := b.ctx.StructType(valueTypes, false)
@@ -361,7 +361,7 @@ func (b *builder) createRunDefers() {
// Extract the params from the struct.
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := range getParams(callback.Signature) {
for i := range callback.Params {
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
+3 -3
View File
@@ -37,7 +37,7 @@ func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context
funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName)
funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature)
funcValueWithSignatureGlobal.SetGlobalConstant(true)
funcValueWithSignatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage)
funcValueWithSignatureGlobal.SetLinkage(llvm.InternalLinkage)
}
funcValueScalar = llvm.ConstPtrToInt(funcValueWithSignatureGlobal, c.uintptrType)
default:
@@ -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 c.expandFormalParamType(recv, "", nil) {
for _, info := range 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 c.expandFormalParamType(subType, "", nil) {
for _, info := range expandFormalParamType(subType, "", nil) {
paramTypes = append(paramTypes, info.llvmType)
}
}
+2 -2
View File
@@ -84,7 +84,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true)
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name))
entry := c.ctx.AddBasicBlock(wrapper, "entry")
@@ -141,7 +141,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true)
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", ""))
entry := c.ctx.AddBasicBlock(wrapper, "entry")
+24 -33
View File
@@ -24,7 +24,16 @@ import (
func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
itfValue := b.emitPointerPack([]llvm.Value{val})
itfTypeCodeGlobal := b.getTypeCode(typ)
itfTypeCode := b.CreatePtrToInt(itfTypeCodeGlobal, b.uintptrType, "")
itfMethodSetGlobal := b.getTypeMethodSet(typ)
itfConcreteTypeGlobal := b.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
if itfConcreteTypeGlobal.IsNil() {
typeInInterface := b.getLLVMRuntimeType("typeInInterface")
itfConcreteTypeGlobal = llvm.AddGlobal(b.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
itfConcreteTypeGlobal.SetGlobalConstant(true)
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
}
itfTypeCode := b.CreatePtrToInt(itfConcreteTypeGlobal, b.uintptrType, "")
itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
itf = b.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = b.CreateInsertValue(itf, itfValue, 1, "")
@@ -45,7 +54,6 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
// reflect lowering simpler.
var references llvm.Value
var length int64
var methodSet llvm.Value
switch typ := typ.(type) {
case *types.Named:
references = c.getTypeCode(typ.Underlying())
@@ -62,28 +70,17 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
// Take a pointer to the typecodeID of the first field (if it exists).
structGlobal := c.makeStructTypeFields(typ)
references = llvm.ConstBitCast(structGlobal, global.Type())
case *types.Interface:
methodSetGlobal := c.getInterfaceMethodSet(typ)
references = llvm.ConstBitCast(methodSetGlobal, global.Type())
}
if _, ok := typ.Underlying().(*types.Interface); !ok {
methodSet = c.getTypeMethodSet(typ)
}
if !references.IsNil() || length != 0 || !methodSet.IsNil() {
if !references.IsNil() {
// Set the 'references' field of the runtime.typecodeID struct.
globalValue := llvm.ConstNull(global.Type().ElementType())
if !references.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
}
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
}
if !methodSet.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, methodSet, []uint32{2})
}
global.SetInitializer(globalValue)
global.SetLinkage(llvm.LinkOnceODRLinkage)
global.SetLinkage(llvm.PrivateLinkage)
}
global.SetGlobalConstant(true)
}
@@ -106,8 +103,8 @@ func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
fieldName.SetLinkage(llvm.PrivateLinkage)
fieldName.SetUnnamedAddr(true)
fieldName = llvm.ConstGEP(fieldName, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
})
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
if typ.Tag(i) != "" {
@@ -115,8 +112,8 @@ func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
fieldTag.SetLinkage(llvm.PrivateLinkage)
fieldTag.SetUnnamedAddr(true)
fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
})
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldTag, []uint32{2})
}
@@ -189,7 +186,7 @@ func getTypeCodeName(t types.Type) string {
case *types.Interface:
methods := make([]string, t.NumMethods())
for i := 0; i < t.NumMethods(); i++ {
methods[i] = t.Method(i).Name() + ":" + getTypeCodeName(t.Method(i).Type())
methods[i] = getTypeCodeName(t.Method(i).Type())
}
return "interface:" + "{" + strings.Join(methods, ",") + "}"
case *types.Map:
@@ -267,7 +264,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
global = llvm.AddGlobal(c.mod, arrayType, typ.String()+"$methodset")
global.SetInitializer(value)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.LinkOnceODRLinkage)
global.SetLinkage(llvm.PrivateLinkage)
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
@@ -298,7 +295,7 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
global = llvm.AddGlobal(c.mod, value.Type(), name+"$interface")
global.SetInitializer(value)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.LinkOnceODRLinkage)
global.SetLinkage(llvm.PrivateLinkage)
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
@@ -341,16 +338,10 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
commaOk = b.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
} else {
globalName := "reflect/types.type:" + getTypeCodeName(expr.AssertedType) + "$id"
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
if assertedTypeCodeGlobal.IsNil() {
// Create a new typecode global.
assertedTypeCodeGlobal = llvm.AddGlobal(b.mod, b.ctx.Int8Type(), globalName)
assertedTypeCodeGlobal.SetGlobalConstant(true)
}
// Type assert on concrete type.
// Call runtime.typeAssert, which will be lowered to a simple icmp or
// const false in the interface lowering pass.
assertedTypeCodeGlobal := b.getTypeCode(expr.AssertedType)
commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
}
@@ -454,9 +445,9 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
}
// Get the expanded receiver type.
receiverType := c.getLLVMType(fn.Signature.Recv().Type())
receiverType := c.getLLVMType(fn.Params[0].Type())
var expandedReceiverType []llvm.Type
for _, info := range c.expandFormalParamType(receiverType, "", nil) {
for _, info := range expandFormalParamType(receiverType, "", nil) {
expandedReceiverType = append(expandedReceiverType, info.llvmType)
}
@@ -476,7 +467,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
wrapper.LastParam().SetName("parentHandle")
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true)
// Create a new builder just to create this wrapper.
+1 -1
View File
@@ -46,7 +46,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt redeclared in this program")
}
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
global.SetVisibility(llvm.HiddenVisibility)
global.SetLinkage(llvm.PrivateLinkage)
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
initializer := llvm.ConstNull(globalLLVMType)
+164
View File
@@ -0,0 +1,164 @@
package compiler
// This file implements a simple reachability analysis, to reduce compile time.
// This DCE pass used to be necessary for improving other passes but now it
// isn't necessary anymore.
import (
"errors"
"go/types"
"sort"
"github.com/tinygo-org/tinygo/loader"
"golang.org/x/tools/go/ssa"
)
type dceState struct {
*compilerContext
functions []*dceFunction
functionMap map[*ssa.Function]*dceFunction
}
type dceFunction struct {
*ssa.Function
functionInfo
flag bool // used by dead code elimination
}
func (p *dceState) addFunction(ssaFn *ssa.Function) {
if _, ok := p.functionMap[ssaFn]; ok {
return
}
f := &dceFunction{Function: ssaFn}
f.functionInfo = p.getFunctionInfo(ssaFn)
p.functions = append(p.functions, f)
p.functionMap[ssaFn] = f
for _, anon := range ssaFn.AnonFuncs {
p.addFunction(anon)
}
}
// simpleDCE returns a list of alive functions in the program. Compiling only
// these functions makes the compiler faster.
//
// This functionality will likely be replaced in the future with build caching.
func (c *compilerContext) simpleDCE(lprogram *loader.Program) ([]*ssa.Function, error) {
mainPkg := c.program.Package(lprogram.MainPkg().Pkg)
if mainPkg == nil {
panic("could not find main package")
}
p := &dceState{
compilerContext: c,
functionMap: make(map[*ssa.Function]*dceFunction),
}
for _, pkg := range lprogram.Sorted() {
pkg := c.program.Package(pkg.Pkg)
memberNames := make([]string, 0)
for name := range pkg.Members {
memberNames = append(memberNames, name)
}
sort.Strings(memberNames)
for _, name := range memberNames {
member := pkg.Members[name]
switch member := member.(type) {
case *ssa.Function:
p.addFunction(member)
case *ssa.Type:
methods := getAllMethods(pkg.Prog, member.Type())
if !types.IsInterface(member.Type()) {
// named type
for _, method := range methods {
p.addFunction(pkg.Prog.MethodValue(method))
}
}
case *ssa.Global:
// Ignore. Globals are not handled here.
case *ssa.NamedConst:
// Ignore: these are already resolved.
default:
panic("unknown member type: " + member.String())
}
}
}
// Initial set of live functions. Include main.main, *.init and runtime.*
// functions.
main, ok := mainPkg.Members["main"].(*ssa.Function)
if !ok {
if mainPkg.Members["main"] == nil {
return nil, errors.New("function main is undeclared in the main package")
} else {
return nil, errors.New("cannot declare main - must be func")
}
}
runtimePkg := c.program.ImportedPackage("runtime")
mathPkg := c.program.ImportedPackage("math")
taskPkg := c.program.ImportedPackage("internal/task")
p.functionMap[main].flag = true
worklist := []*ssa.Function{main}
for _, f := range p.functions {
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || f.Pkg == taskPkg || (f.Pkg == mathPkg && f.Pkg != nil) {
if f.flag {
continue
}
f.flag = true
worklist = append(worklist, f.Function)
}
}
// Mark all called functions recursively.
for len(worklist) != 0 {
f := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
for _, block := range f.Blocks {
for _, instr := range block.Instrs {
if instr, ok := instr.(*ssa.MakeInterface); ok {
for _, sel := range getAllMethods(c.program, instr.X.Type()) {
fn := c.program.MethodValue(sel)
callee := p.functionMap[fn]
if callee == nil {
// TODO: why is this necessary?
p.addFunction(fn)
callee = p.functionMap[fn]
}
if !callee.flag {
callee.flag = true
worklist = append(worklist, callee.Function)
}
}
}
for _, operand := range instr.Operands(nil) {
if operand == nil || *operand == nil {
continue
}
switch operand := (*operand).(type) {
case *ssa.Function:
f := p.functionMap[operand]
if f == nil {
// FIXME HACK: this function should have been
// discovered already. It is not for bound methods.
p.addFunction(operand)
f = p.functionMap[operand]
}
if !f.flag {
f.flag = true
worklist = append(worklist, operand)
}
}
}
}
}
}
// Return all live functions.
liveFunctions := []*ssa.Function{}
for _, f := range p.functions {
if f.flag {
liveFunctions = append(liveFunctions, f.Function)
}
}
return liveFunctions, nil
}
-3
View File
@@ -150,9 +150,6 @@ func (s *stdSizes) Sizeof(T types.Type) int64 {
return s.PtrSize * 2
case *types.Pointer:
return s.PtrSize
case *types.Signature:
// Func values in TinyGo are two words in size.
return s.PtrSize * 2
default:
panic("unknown type: " + t.String())
}
+27 -47
View File
@@ -72,9 +72,9 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
}
var paramInfos []paramInfo
for _, param := range getParams(fn.Signature) {
for _, param := range fn.Params {
paramType := c.getLLVMType(param.Type())
paramFragmentInfos := c.expandFormalParamType(paramType, param.Name(), param.Type())
paramFragmentInfos := expandFormalParamType(paramType, param.Name(), param.Type())
paramInfos = append(paramInfos, paramFragmentInfos...)
}
@@ -172,21 +172,6 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
}
}
// Synthetic functions are functions that do not appear in the source code,
// they are artificially constructed. Usually they are wrapper functions
// that are not referenced anywhere except in a SSA call instruction so
// should be created right away.
// The exception is the package initializer, which does appear in the
// *ssa.Package members and so shouldn't be created here.
if fn.Synthetic != "" && fn.Synthetic != "package initializer" {
irbuilder := c.ctx.NewBuilder()
b := newBuilder(c, irbuilder, fn)
b.createFunction()
irbuilder.Dispose()
llvmFn.SetLinkage(llvm.LinkOnceODRLinkage)
llvmFn.SetUnnamedAddr(true)
}
return llvmFn
}
@@ -293,20 +278,6 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
}
}
// getParams returns the function parameters, including the receiver at the
// start. This is an alternative to the Params member of *ssa.Function, which is
// not yet populated when the package has not yet been built.
func getParams(sig *types.Signature) []*types.Var {
params := []*types.Var{}
if sig.Recv() != nil {
params = append(params, sig.Recv())
}
for i := 0; i < sig.Params().Len(); i++ {
params = append(params, sig.Params().At(i))
}
return params
}
// globalInfo contains some information about a specific global. By default,
// linkName is equal to .RelString(nil) on a global and extern is false, but for
// some symbols this is different (due to //go:extern for example).
@@ -318,22 +289,25 @@ type globalInfo struct {
// loadASTComments loads comments on globals from the AST, for use later in the
// program. In particular, they are required for //go:extern pragmas on globals.
func (c *compilerContext) loadASTComments(pkg *loader.Package) {
for _, file := range pkg.Files {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
switch decl.Tok {
case token.VAR:
if len(decl.Specs) != 1 {
continue
}
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.ValueSpec: // decl.Tok == token.VAR
for _, name := range spec.Names {
id := pkg.Pkg.Path() + "." + name.Name
c.astComments[id] = decl.Doc
func (c *compilerContext) loadASTComments(lprogram *loader.Program) {
c.astComments = map[string]*ast.CommentGroup{}
for _, pkgInfo := range lprogram.Sorted() {
for _, file := range pkgInfo.Files {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
switch decl.Tok {
case token.VAR:
if len(decl.Specs) != 1 {
continue
}
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.ValueSpec: // decl.Tok == token.VAR
for _, name := range spec.Names {
id := pkgInfo.Pkg.Path() + "." + name.Name
c.astComments[id] = decl.Doc
}
}
}
}
@@ -352,6 +326,10 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
typ := g.Type().(*types.Pointer).Elem()
llvmType := c.getLLVMType(typ)
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
if !info.extern {
llvmGlobal.SetInitializer(llvm.ConstNull(llvmType))
llvmGlobal.SetLinkage(llvm.InternalLinkage)
}
// Set alignment from the //go:align comment.
var alignInBits uint32
@@ -369,6 +347,8 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
if c.Debug && !info.extern {
// Add debug info.
// TODO: this should be done for every global in the program, not just
// the ones that are referenced from some code.
pos := c.program.Fset.Position(g.Pos())
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
Name: g.RelString(nil),
+14 -16
View File
@@ -3,72 +3,70 @@ source_filename = "basic.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden i32 @main.addInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i32 @main.addInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = add i32 %x, %y
ret i32 %0
}
define hidden i1 @main.equalInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i1 @main.equalInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp eq i32 %x, %y
ret i1 %0
}
define hidden i1 @main.floatEQ(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i1 @main.floatEQ(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp oeq float %x, %y
ret i1 %0
}
define hidden i1 @main.floatNE(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i1 @main.floatNE(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp une float %x, %y
ret i1 %0
}
define hidden i1 @main.floatLower(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i1 @main.floatLower(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp olt float %x, %y
ret i1 %0
}
define hidden i1 @main.floatLowerEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i1 @main.floatLowerEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp ole float %x, %y
ret i1 %0
}
define hidden i1 @main.floatGreater(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i1 @main.floatGreater(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp ogt float %x, %y
ret i1 %0
}
define hidden i1 @main.floatGreaterEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i1 @main.floatGreaterEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp oge float %x, %y
ret i1 %0
}
define hidden float @main.complexReal(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
define internal float @main.complexReal(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret float %x.r
}
define hidden float @main.complexImag(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
define internal float @main.complexImag(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret float %x.i
}
define hidden { float, float } @main.complexAdd(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
define internal { float, float } @main.complexAdd(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fadd float %x.r, %y.r
%1 = fadd float %x.i, %y.i
@@ -77,7 +75,7 @@ entry:
ret { float, float } %3
}
define hidden { float, float } @main.complexSub(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
define internal { float, float } @main.complexSub(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fsub float %x.r, %y.r
%1 = fsub float %x.i, %y.i
@@ -86,7 +84,7 @@ entry:
ret { float, float } %3
}
define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
define internal { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fmul float %x.r, %y.r
%1 = fmul float %x.i, %y.i
+9 -11
View File
@@ -3,14 +3,12 @@ source_filename = "float.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden i32 @main.f32tou32(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i32 @main.f32tou32(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%positive = fcmp oge float %v, 0.000000e+00
%withinmax = fcmp ole float %v, 0x41EFFFFFC0000000
@@ -21,22 +19,22 @@ entry:
ret i32 %0
}
define hidden float @main.maxu32f(i8* %context, i8* %parentHandle) unnamed_addr {
define internal float @main.maxu32f(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret float 0x41F0000000000000
}
define hidden i32 @main.maxu32tof32(i8* %context, i8* %parentHandle) unnamed_addr {
define internal i32 @main.maxu32tof32(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i32 -1
}
define hidden { i32, i32, i32, i32 } @main.inftoi32(i8* %context, i8* %parentHandle) unnamed_addr {
define internal { i32, i32, i32, i32 } @main.inftoi32(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret { i32, i32, i32, i32 } { i32 -1, i32 0, i32 2147483647, i32 -2147483648 }
}
define hidden i32 @main.u32tof32tou32(i32 %v, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i32 @main.u32tof32tou32(i32 %v, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = uitofp i32 %v to float
%withinmax = fcmp ole float %0, 0x41EFFFFFC0000000
@@ -45,7 +43,7 @@ entry:
ret i32 %1
}
define hidden float @main.f32tou32tof32(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
define internal float @main.f32tou32tof32(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%positive = fcmp oge float %v, 0.000000e+00
%withinmax = fcmp ole float %v, 0x41EFFFFFC0000000
@@ -57,7 +55,7 @@ entry:
ret float %1
}
define hidden i8 @main.f32tou8(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i8 @main.f32tou8(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%positive = fcmp oge float %v, 0.000000e+00
%withinmax = fcmp ole float %v, 2.550000e+02
@@ -68,7 +66,7 @@ entry:
ret i8 %0
}
define hidden i8 @main.f32toi8(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i8 @main.f32toi8(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%abovemin = fcmp oge float %v, -1.280000e+02
%belowmax = fcmp ole float %v, 1.270000e+02
-54
View File
@@ -1,54 +0,0 @@
// This file tests interface types and interface builtins.
package main
// Test interface construction.
func simpleType() interface{} {
return 0
}
func pointerType() interface{} {
// Pointers have an element type, in this case int.
var v *int
return v
}
func interfaceType() interface{} {
// Interfaces can exist in interfaces, but only indirectly (through
// pointers).
var v *error
return v
}
func anonymousInterfaceType() interface{} {
var v *interface {
String() string
}
return v
}
// Test interface builtins.
func isInt(itf interface{}) bool {
_, ok := itf.(int)
return ok
}
func isError(itf interface{}) bool {
// Interface assert on (builtin) named interface type.
_, ok := itf.(error)
return ok
}
func isStringer(itf interface{}) bool {
// Interface assert on anonymous interface type.
_, ok := itf.(interface {
String() string
})
return ok
}
func callErrorMethod(itf error) string {
return itf.Error()
}
-100
View File
@@ -1,100 +0,0 @@
; ModuleID = 'interface.go'
source_filename = "interface.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* }
%runtime.interfaceMethodInfo = type { i8*, i32 }
%runtime._interface = type { i32, i8* }
%runtime._string = type { i8*, i32 }
@"reflect/types.type:basic:int" = linkonce_odr constant %runtime.typecodeID zeroinitializer
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:named:error", i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null }
@"func Error() string" = external constant i8
@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"func Error() string"]
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{String() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null }
@"func String() string" = external constant i8
@"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"func String() string"]
@"reflect/types.type:basic:int$id" = external constant i8
@"error$interface" = linkonce_odr constant [1 x i8*] [i8* @"func Error() string"]
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden %runtime._interface @main.simpleType(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:int" to i32), i8* null }
}
define hidden %runtime._interface @main.pointerType(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:basic:int" to i32), i8* null }
}
define hidden %runtime._interface @main.interfaceType(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:named:error" to i32), i8* null }
}
define hidden %runtime._interface @main.anonymousInterfaceType(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" to i32), i8* null }
}
define hidden i1 @main.isInt(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%typecode = call i1 @runtime.typeAssert(i32 %itf.typecode, i8* nonnull @"reflect/types.type:basic:int$id", i8* undef, i8* null)
br i1 %typecode, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry
br label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
ret i1 %typecode
}
declare i1 @runtime.typeAssert(i32, i8* dereferenceable_or_null(1), i8*, i8*)
define hidden i1 @main.isError(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i1 @runtime.interfaceImplements(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* undef, i8* null)
br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry
br label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
ret i1 %0
}
declare i1 @runtime.interfaceImplements(i32, i8** dereferenceable_or_null(4), i8*, i8*)
define hidden i1 @main.isStringer(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i1 @runtime.interfaceImplements(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"reflect/types.interface:interface{String() string}$interface", i32 0, i32 0), i8* undef, i8* null)
br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry
br label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
ret i1 %0
}
define hidden %runtime._string @main.callErrorMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%invoke.func = call i32 @runtime.interfaceMethod(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* nonnull @"func Error() string", i8* undef, i8* null)
%invoke.func.cast = inttoptr i32 %invoke.func to %runtime._string (i8*, i8*, i8*)*
%0 = call %runtime._string %invoke.func.cast(i8* %itf.value, i8* undef, i8* undef)
ret %runtime._string %0
}
declare i32 @runtime.interfaceMethod(i32, i8** dereferenceable_or_null(4), i8* dereferenceable_or_null(1), i8*, i8*)
+8 -10
View File
@@ -3,48 +3,46 @@ source_filename = "pointer.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden [0 x i32] @main.pointerDerefZero([0 x i32]* %x, i8* %context, i8* %parentHandle) unnamed_addr {
define internal [0 x i32] @main.pointerDerefZero([0 x i32]* %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret [0 x i32] zeroinitializer
}
define hidden i32* @main.pointerCastFromUnsafe(i8* %x, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i32* @main.pointerCastFromUnsafe(i8* %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = bitcast i8* %x to i32*
ret i32* %0
}
define hidden i8* @main.pointerCastToUnsafe(i32* dereferenceable_or_null(4) %x, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i8* @main.pointerCastToUnsafe(i32* dereferenceable_or_null(4) %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = bitcast i32* %x to i8*
ret i8* %0
}
define hidden i8* @main.pointerCastToUnsafeNoop(i8* dereferenceable_or_null(1) %x, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i8* @main.pointerCastToUnsafeNoop(i8* dereferenceable_or_null(1) %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i8* %x
}
define hidden i8* @main.pointerUnsafeGEPFixedOffset(i8* dereferenceable_or_null(1) %ptr, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i8* @main.pointerUnsafeGEPFixedOffset(i8* dereferenceable_or_null(1) %ptr, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = getelementptr inbounds i8, i8* %ptr, i32 10
ret i8* %0
}
define hidden i8* @main.pointerUnsafeGEPByteOffset(i8* dereferenceable_or_null(1) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i8* @main.pointerUnsafeGEPByteOffset(i8* dereferenceable_or_null(1) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = getelementptr inbounds i8, i8* %ptr, i32 %offset
ret i8* %0
}
define hidden i32* @main.pointerUnsafeGEPIntOffset(i32* dereferenceable_or_null(4) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i32* @main.pointerUnsafeGEPIntOffset(i32* dereferenceable_or_null(4) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = getelementptr i32, i32* %ptr, i32 %offset
ret i32* %0
-16
View File
@@ -7,19 +7,3 @@ func sliceLen(ints []int) int {
func sliceCap(ints []int) int {
return cap(ints)
}
func sliceElement(ints []int, index int) int {
return ints[index]
}
func sliceAppendValues(ints []int) []int {
return append(ints, 1, 2, 3)
}
func sliceAppendSlice(ints, added []int) []int {
return append(ints, added...)
}
func sliceCopy(dst, src []int) int {
return copy(dst, src)
}
+3 -72
View File
@@ -3,86 +3,17 @@ source_filename = "slice.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden i32 @main.sliceLen(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i32 @main.sliceLen(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i32 %ints.len
}
define hidden i32 @main.sliceCap(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
define internal i32 @main.sliceCap(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i32 %ints.cap
}
define hidden i32 @main.sliceElement(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32 %index, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%.not = icmp ult i32 %index, %ints.len
br i1 %.not, label %lookup.next, label %lookup.throw
lookup.throw: ; preds = %entry
call void @runtime.lookupPanic(i8* undef, i8* null)
unreachable
lookup.next: ; preds = %entry
%0 = getelementptr inbounds i32, i32* %ints.data, i32 %index
%1 = load i32, i32* %0, align 4
ret i32 %1
}
declare void @runtime.lookupPanic(i8*, i8*)
define hidden { i32*, i32, i32 } @main.sliceAppendValues(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%varargs = call i8* @runtime.alloc(i32 12, i8* undef, i8* null)
%0 = bitcast i8* %varargs to i32*
store i32 1, i32* %0, align 4
%1 = getelementptr inbounds i8, i8* %varargs, i32 4
%2 = bitcast i8* %1 to i32*
store i32 2, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %varargs, i32 8
%4 = bitcast i8* %3 to i32*
store i32 3, i32* %4, align 4
%append.srcPtr = bitcast i32* %ints.data to i8*
%append.new = call { i8*, i32, i32 } @runtime.sliceAppend(i8* %append.srcPtr, i8* nonnull %varargs, i32 %ints.len, i32 %ints.cap, i32 3, i32 4, i8* undef, i8* null)
%append.newPtr = extractvalue { i8*, i32, i32 } %append.new, 0
%append.newBuf = bitcast i8* %append.newPtr to i32*
%append.newLen = extractvalue { i8*, i32, i32 } %append.new, 1
%append.newCap = extractvalue { i8*, i32, i32 } %append.new, 2
%5 = insertvalue { i32*, i32, i32 } undef, i32* %append.newBuf, 0
%6 = insertvalue { i32*, i32, i32 } %5, i32 %append.newLen, 1
%7 = insertvalue { i32*, i32, i32 } %6, i32 %append.newCap, 2
ret { i32*, i32, i32 } %7
}
declare { i8*, i32, i32 } @runtime.sliceAppend(i8*, i8*, i32, i32, i32, i32, i8*, i8*)
define hidden { i32*, i32, i32 } @main.sliceAppendSlice(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32* %added.data, i32 %added.len, i32 %added.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%append.srcPtr = bitcast i32* %ints.data to i8*
%append.srcPtr1 = bitcast i32* %added.data to i8*
%append.new = call { i8*, i32, i32 } @runtime.sliceAppend(i8* %append.srcPtr, i8* %append.srcPtr1, i32 %ints.len, i32 %ints.cap, i32 %added.len, i32 4, i8* undef, i8* null)
%append.newPtr = extractvalue { i8*, i32, i32 } %append.new, 0
%append.newBuf = bitcast i8* %append.newPtr to i32*
%append.newLen = extractvalue { i8*, i32, i32 } %append.new, 1
%append.newCap = extractvalue { i8*, i32, i32 } %append.new, 2
%0 = insertvalue { i32*, i32, i32 } undef, i32* %append.newBuf, 0
%1 = insertvalue { i32*, i32, i32 } %0, i32 %append.newLen, 1
%2 = insertvalue { i32*, i32, i32 } %1, i32 %append.newCap, 2
ret { i32*, i32, i32 } %2
}
define hidden i32 @main.sliceCopy(i32* %dst.data, i32 %dst.len, i32 %dst.cap, i32* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%copy.dstPtr = bitcast i32* %dst.data to i8*
%copy.srcPtr = bitcast i32* %src.data to i8*
%copy.n = call i32 @runtime.sliceCopy(i8* %copy.dstPtr, i8* %copy.srcPtr, i32 %dst.len, i32 %src.len, i32 4, i8* undef, i8* null)
ret i32 %copy.n
}
declare i32 @runtime.sliceCopy(i8*, i8*, i32, i32, i32, i8*, i8*)
-21
View File
@@ -1,21 +0,0 @@
package main
func stringLen(s string) int {
return len(s)
}
func stringIndex(s string, index int) byte {
return s[index]
}
func stringCompareEqual(s1, s2 string) bool {
return s1 == s2
}
func stringCompareUnequal(s1, s2 string) bool {
return s1 != s2
}
func stringCompareLarger(s1, s2 string) bool {
return s1 > s2
}
-57
View File
@@ -1,57 +0,0 @@
; ModuleID = 'string.go'
source_filename = "string.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden i32 @main.stringLen(i8* %s.data, i32 %s.len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i32 %s.len
}
define hidden i8 @main.stringIndex(i8* %s.data, i32 %s.len, i32 %index, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%.not = icmp ult i32 %index, %s.len
br i1 %.not, label %lookup.next, label %lookup.throw
lookup.throw: ; preds = %entry
call void @runtime.lookupPanic(i8* undef, i8* null)
unreachable
lookup.next: ; preds = %entry
%0 = getelementptr inbounds i8, i8* %s.data, i32 %index
%1 = load i8, i8* %0, align 1
ret i8 %1
}
declare void @runtime.lookupPanic(i8*, i8*)
define hidden i1 @main.stringCompareEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i1 @runtime.stringEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null)
ret i1 %0
}
declare i1 @runtime.stringEqual(i8*, i32, i8*, i32, i8*, i8*)
define hidden i1 @main.stringCompareUnequal(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i1 @runtime.stringEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null)
%1 = xor i1 %0, true
ret i1 %1
}
define hidden i1 @main.stringCompareLarger(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i1 @runtime.stringLess(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null)
%1 = xor i1 %0, true
ret i1 %1
}
declare i1 @runtime.stringLess(i8*, i32, i8*, i32, i8*, i8*)
+1 -1
View File
@@ -12,5 +12,5 @@ require (
go.bug.st/serial v1.1.2
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4
tinygo.org/x/go-llvm v0.0.0-20210206225315-7fe719483a0f
)
+2 -2
View File
@@ -57,5 +57,5 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbO
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4 h1:CMUHxVTb+UuUePuMf8vkWjZ3gTp9BBK91KrgOCwoNHs=
tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20210206225315-7fe719483a0f h1:FP5Do5omlQ/dLQ3Hfy7oyJo69VS5Hn46rZw004r0lGU=
tinygo.org/x/go-llvm v0.0.0-20210206225315-7fe719483a0f/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
-5
View File
@@ -20,11 +20,6 @@ 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
}
+5 -124
View File
@@ -11,12 +11,6 @@ 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
@@ -38,7 +32,9 @@ type runner struct {
callsExecuted uint64
}
func newRunner(mod llvm.Module, debug bool) *runner {
// 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 := runner{
mod: mod,
targetData: llvm.NewTargetData(mod.DataLayout()),
@@ -51,13 +47,6 @@ func newRunner(mod llvm.Module, debug bool) *runner {
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()
@@ -128,7 +117,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
@@ -136,34 +125,7 @@ func Run(mod llvm.Module, debug bool) error {
if obj.buffer == nil {
continue
}
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
}
initializer := obj.buffer.toLLVMValue(obj.llvmGlobal.Type().ElementType(), &mem)
if checks && initializer.Type() != obj.llvmGlobal.Type().ElementType() {
panic("initializer type mismatch")
}
@@ -173,87 +135,6 @@ 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 {
+16 -100
View File
@@ -155,8 +155,11 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// which case this call won't even get to this point but will
// already be emitted in initAll.
continue
case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet":
case callFn.name == "(reflect.Type).Elem" || strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet":
// These functions should be run at runtime. Specifically:
// * (reflect.Type).Elem is a special function. It should
// eventually be interpreted, but fall back to a runtime call
// for now.
// * Print and panic functions are best emitted directly without
// interpreting them, otherwise we get a ton of putchar (etc.)
// calls.
@@ -277,58 +280,26 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():])
dstObj.buffer = dstBuf
mem.put(dst.index(), dstObj)
case callFn.name == "(reflect.rawType).elem":
if r.debug {
fmt.Fprintln(os.Stderr, indent+"call (reflect.rawType).elem:", operands[1:])
}
// Extract the type code global from the first parameter.
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.
typecodeName := typecodeID.Name()
const prefix = "reflect/types.type:"
if !strings.HasPrefix(typecodeName, prefix) {
panic("unexpected typecode name: " + typecodeName)
}
id := typecodeName[len(prefix):]
class := id[:strings.IndexByte(id, ':')]
value := id[len(class)+1:]
if class == "named" {
// Get the underlying type.
class = value[:strings.IndexByte(value, ':')]
value = value[len(class)+1:]
}
// Elem() is only valid for certain type classes.
switch class {
case "chan", "pointer", "slice", "array":
elementType := llvm.ConstExtractValue(typecodeID.Initializer(), []uint32{0})
uintptrType := r.mod.Context().IntType(int(mem.r.pointerSize) * 8)
locals[inst.localIndex] = r.getValue(llvm.ConstPtrToInt(elementType, uintptrType))
default:
return nil, mem, r.errorAt(inst, fmt.Errorf("(reflect.Type).Elem() called on %s type", class))
}
case callFn.name == "runtime.typeAssert":
// This function must be implemented manually as it is normally
// implemented by the interface lowering pass.
if r.debug {
fmt.Fprintln(os.Stderr, indent+"typeassert:", operands[1:])
}
assertedType, err := operands[2].toLLVMValue(inst.llvmInst.Operand(1).Type(), &mem)
typeInInterfacePtr, err := operands[1].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
actualTypePtrToInt, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem)
actualType, err := mem.load(typeInInterfacePtr, r.pointerSize).asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
actualType := actualTypePtrToInt.Operand(0)
if actualType.Name()+"$id" == assertedType.Name() {
assertedType, err := operands[2].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
result := assertedType.asRawValue(r).equal(actualType.asRawValue(r))
if result {
locals[inst.localIndex] = literalValue{uint8(1)}
} else {
locals[inst.localIndex] = literalValue{uint8(0)}
@@ -339,11 +310,11 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
}
// Load various values for the interface implements check below.
typecodePtr, err := operands[1].asPointer(r)
typeInInterfacePtr, err := operands[1].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
methodSetPtr, err := mem.load(typecodePtr.addOffset(r.pointerSize*2), r.pointerSize).asPointer(r)
methodSetPtr, err := mem.load(typeInInterfacePtr.addOffset(r.pointerSize), r.pointerSize).asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
@@ -378,43 +349,6 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
}
// If assertOk is still 1, the assertion succeeded.
locals[inst.localIndex] = literalValue{assertOk}
case callFn.name == "runtime.interfaceMethod":
// This builtin returns the function (which may be a thunk) to
// invoke a method on an interface. It does not call the method.
if r.debug {
fmt.Fprintln(os.Stderr, indent+"interface method:", operands[1:])
}
// Load the first param, which is the type code (ptrtoint of the
// type code global).
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()
// We don't need to load the interface method set.
// Load the signature of the to-be-called function.
signature := inst.llvmInst.Operand(2)
// Iterate through all methods, looking for the one method that
// should be returned.
numMethods := methodSet.Type().ArrayLength()
var method llvm.Value
for i := 0; i < numMethods; i++ {
methodSignature := llvm.ConstExtractValue(methodSet, []uint32{uint32(i), 0})
if methodSignature == signature {
method = llvm.ConstExtractValue(methodSet, []uint32{uint32(i), 1}).Operand(0)
}
}
if method.IsNil() {
return nil, mem, r.errorAt(inst, errors.New("could not find method: "+signature.Name()))
}
locals[inst.localIndex] = r.getValue(method)
case callFn.name == "runtime.hashmapMake":
// Create a new map.
hashmapPointerType := inst.llvmInst.Type()
@@ -548,13 +482,6 @@ 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)
}
@@ -577,14 +504,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
if r.debug {
fmt.Fprintln(os.Stderr, indent+"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
}
}
mem.store(val, ptr)
case llvm.Alloca:
// Alloca normally allocates some stack memory. In the interpreter,
// it allocates a global instead.
@@ -916,11 +836,7 @@ 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() {
var err error
operand, err = locals[fn.locals[operand]].toLLVMValue(operand.Type(), mem)
if err != nil {
return r.errorAt(inst, err)
}
operand = locals[fn.locals[operand]].toLLVMValue(operand.Type(), mem)
}
operands[i] = operand
}
+56 -105
View File
@@ -259,17 +259,12 @@ func (mv *memoryView) put(index uint32, obj object) {
mv.objects[index] = obj
}
// Load the value behind the given pointer. Returns nil if the pointer points to
// an external global.
// Load the value behind the given pointer.
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()
}
@@ -285,17 +280,12 @@ 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. Returns true on success, false if the object to store to is
// external.
func (mv *memoryView) store(v value, p pointerValue) bool {
// reverted.
func (mv *memoryView) store(v value, p pointerValue) {
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")
}
@@ -311,7 +301,6 @@ func (mv *memoryView) store(v value, p pointerValue) bool {
}
}
mv.put(p.index(), obj)
return true // success
}
// value is some sort of value, comparable to a LLVM constant. It can be
@@ -325,7 +314,7 @@ type value interface {
asRawValue(*runner) rawValue
Uint() uint64
Int() int64
toLLVMValue(llvm.Type, *memoryView) (llvm.Value, error)
toLLVMValue(llvm.Type, *memoryView) llvm.Value
String() string
}
@@ -416,25 +405,25 @@ func (v literalValue) Int() int64 {
}
}
func (v literalValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) {
func (v literalValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Value {
switch llvmType.TypeKind() {
case llvm.IntegerTypeKind:
switch value := v.value.(type) {
case uint64:
return llvm.ConstInt(llvmType, value, false), nil
return llvm.ConstInt(llvmType, value, false)
case uint32:
return llvm.ConstInt(llvmType, uint64(value), false), nil
return llvm.ConstInt(llvmType, uint64(value), false)
case uint16:
return llvm.ConstInt(llvmType, uint64(value), false), nil
return llvm.ConstInt(llvmType, uint64(value), false)
case uint8:
return llvm.ConstInt(llvmType, uint64(value), false), nil
return llvm.ConstInt(llvmType, uint64(value), false)
default:
return llvm.Value{}, errors.New("interp: unknown literal type")
panic("inpterp: unknown literal type")
}
case llvm.DoubleTypeKind:
return llvm.ConstFloat(llvmType, math.Float64frombits(v.value.(uint64))), nil
return llvm.ConstFloat(llvmType, math.Float64frombits(v.value.(uint64)))
case llvm.FloatTypeKind:
return llvm.ConstFloat(llvmType, float64(math.Float32frombits(v.value.(uint32)))), nil
return llvm.ConstFloat(llvmType, float64(math.Float32frombits(v.value.(uint32))))
default:
return v.asRawValue(mem.r).toLLVMValue(llvmType, mem)
}
@@ -518,7 +507,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, error) {
func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Value {
// Obtain the llvmValue, creating it if it doesn't exist yet.
llvmValue := v.llvmValue(mem)
if llvmValue.IsNil() {
@@ -529,10 +518,7 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
if obj.llvmType.IsNil() {
// Create an initializer without knowing the global type.
// This is probably the result of a runtime.alloc call.
initializer, err := obj.buffer.asRawValue(mem.r).rawLLVMValue(mem)
if err != nil {
return llvm.Value{}, err
}
initializer := obj.buffer.asRawValue(mem.r).rawLLVMValue(mem)
globalType := initializer.Type()
llvmValue = llvm.AddGlobal(mem.r.mod, globalType, obj.globalName)
llvmValue.SetInitializer(initializer)
@@ -551,12 +537,9 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
// 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, err := obj.buffer.toLLVMValue(globalType, mem)
if err != nil {
return llvm.Value{}, err
}
initializer := obj.buffer.toLLVMValue(globalType, mem)
if checks && initializer.Type() != globalType {
return llvm.Value{}, errors.New("interp: allocated value does not match allocated type")
panic("allocated value does not match allocated type")
}
llvmValue.SetInitializer(initializer)
}
@@ -569,7 +552,7 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
}
if llvmType.IsNil() {
return llvmValue, nil
return llvmValue
}
if llvmType.TypeKind() != llvm.PointerTypeKind {
@@ -581,7 +564,7 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
// This can be worked around by simply converting to a raw value,
// rawValue knows how to create such structs.
if v.offset() != 0 {
return llvm.Value{}, errors.New("interp: offset set without known pointer type")
panic("offset set without known pointer type")
}
return v.asRawValue(mem.r).toLLVMValue(llvmType, mem)
}
@@ -592,14 +575,14 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
if v.offset() != 0 {
// This should never happen, if offset is non-zero, the types
// shouldn't match.
return llvm.Value{}, errors.New("interp: offset set while there is no way to convert the type")
panic("offset set while there is no way to convert the type")
}
return llvmValue, nil
return llvmValue
}
if v.offset() == 0 {
// Offset is zero, so we can just bitcast to get a correct pointer.
return llvm.ConstBitCast(llvmValue, llvmType), nil
return llvm.ConstBitCast(llvmValue, llvmType)
}
// We need to make a constant GEP for pointer arithmetic.
@@ -623,11 +606,11 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
offset -= int64(mem.r.targetData.ElementOffset(objectElementType, element))
objectElementType = objectElementType.StructElementTypes()[element]
default:
return llvm.Value{}, errors.New("interp: pointer index with something other than a struct or array?")
panic("pointer index with something other than a struct or array?")
}
}
if offset < 0 {
return llvm.Value{}, errors.New("interp: offset has somehow gone negative, this should be impossible")
panic("offset has somehow gone negative, this should be impossible")
}
// Finally do the gep, using the above computed indices.
@@ -635,9 +618,9 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
// 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), nil
return llvm.ConstBitCast(gep, llvmType)
}
return gep, nil
return gep
}
// mapValue implements a Go map which is created at compile time and stored as a
@@ -730,11 +713,7 @@ 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 {
keyValue, err := key.rawLLVMValue(mem)
if err != nil {
panic(err)
}
keyValues = append(keyValues, keyValue)
keyValues = append(keyValues, key.rawLLVMValue(mem))
}
if len(b.keys) < 8 {
keyValues = append(keyValues, llvm.ConstNull(llvm.ArrayType(int8Type, int(b.m.keySize)*(8-len(b.keys)))))
@@ -747,11 +726,7 @@ 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 {
v, err := value.rawLLVMValue(mem)
if err != nil {
panic(err)
}
valueValues = append(valueValues, v)
valueValues = append(valueValues, value.rawLLVMValue(mem))
}
if len(b.values) < 8 {
valueValues = append(valueValues, llvm.ConstNull(llvm.ArrayType(int8Type, int(b.m.valueSize)*(8-len(b.values)))))
@@ -775,9 +750,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, error) {
func (v *mapValue) toLLVMValue(hashmapType llvm.Type, mem *memoryView) llvm.Value {
if !v.hashmap.IsNil() {
return v.hashmap, nil
return v.hashmap
}
// Create a slice of buckets with all the keys and values in the hashmap.
@@ -797,12 +772,12 @@ func (v *mapValue) toLLVMValue(hashmapType llvm.Type, mem *memoryView) (llvm.Val
copy(keyValue.buf[v.keySize/2:], literalValue{key.size}.asRawValue(v.r).buf)
case rawValue:
if key.hasPointer() {
return llvm.Value{}, errors.New("interp: todo: map key with pointer")
panic("todo: map key with pointer")
}
data = key.buf
keyValue = key
default:
return llvm.Value{}, errors.New("interp: unknown map key type")
panic("unknown map key type")
}
buf := make([]byte, len(data))
for i, p := range data {
@@ -846,7 +821,7 @@ func (v *mapValue) toLLVMValue(hashmapType llvm.Type, mem *memoryView) (llvm.Val
})
v.hashmap = hashmap
return v.hashmap, nil
return v.hashmap
}
// putString does a map assign operation, assuming that the map is of type
@@ -1043,7 +1018,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, error) {
func (v rawValue) rawLLVMValue(mem *memoryView) llvm.Value {
var structFields []llvm.Value
ctx := mem.r.mod.Context()
int8Type := ctx.Int8Type()
@@ -1067,16 +1042,13 @@ func (v rawValue) rawLLVMValue(mem *memoryView) (llvm.Value, error) {
for i := uint32(0); i < uint32(len(v.buf)); {
if v.buf[i] > 255 {
addBytes()
field, err := pointerValue{v.buf[i]}.toLLVMValue(llvm.Type{}, mem)
if err != nil {
return llvm.Value{}, err
}
field := pointerValue{v.buf[i]}.toLLVMValue(llvm.Type{}, mem)
elementType := field.Type().ElementType()
if elementType.TypeKind() == llvm.StructTypeKind {
// There are some special pointer types that should be used as a
// ptrtoint, so that they can be used in certain optimizations.
name := elementType.StructName()
if name == "runtime.typecodeID" || name == "runtime.funcValueWithSignature" {
if name == "runtime.typeInInterface" || name == "runtime.funcValueWithSignature" {
uintptrType := ctx.IntType(int(mem.r.pointerSize) * 8)
field = llvm.ConstPtrToInt(field, uintptrType)
}
@@ -1093,12 +1065,12 @@ func (v rawValue) rawLLVMValue(mem *memoryView) (llvm.Value, error) {
// Return the created data.
if len(structFields) == 1 {
return structFields[0], nil
return structFields[0]
}
return ctx.ConstStruct(structFields, false), nil
return ctx.ConstStruct(structFields, false)
}
func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) {
func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Value {
isZero := true
for _, p := range v.buf {
if p != 0 {
@@ -1107,7 +1079,7 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
}
}
if isZero {
return llvm.ConstNull(llvmType), nil
return llvm.ConstNull(llvmType)
}
switch llvmType.TypeKind() {
case llvm.IntegerTypeKind:
@@ -1116,17 +1088,7 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
if err != nil {
panic(err)
}
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
return llvm.ConstPtrToInt(ptr.toLLVMValue(llvm.Type{}, mem), llvmType)
}
var n uint64
switch llvmType.IntTypeWidth() {
@@ -1146,7 +1108,7 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
default:
panic("unknown integer size")
}
return llvm.ConstInt(llvmType, n, false), nil
return llvm.ConstInt(llvmType, n, false)
case llvm.StructTypeKind:
fieldTypes := llvmType.StructElementTypes()
fields := make([]llvm.Value, len(fieldTypes))
@@ -1155,16 +1117,12 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
field := rawValue{
buf: v.buf[offset:],
}
var err error
fields[i], err = field.toLLVMValue(fieldType, mem)
if err != nil {
return llvm.Value{}, err
}
fields[i] = field.toLLVMValue(fieldType, mem)
}
if llvmType.StructName() != "" {
return llvm.ConstNamedStruct(llvmType, fields), nil
return llvm.ConstNamedStruct(llvmType, fields)
}
return llvmType.Context().ConstStruct(fields, false), nil
return llvmType.Context().ConstStruct(fields, false)
case llvm.ArrayTypeKind:
numElements := llvmType.ArrayLength()
childType := llvmType.ElementType()
@@ -1175,34 +1133,27 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
field := rawValue{
buf: v.buf[offset:],
}
var err error
fields[i], err = field.toLLVMValue(childType, mem)
if err != nil {
return llvm.Value{}, err
}
fields[i] = field.toLLVMValue(childType, mem)
if checks && fields[i].Type() != childType {
panic("child type doesn't match")
}
}
return llvm.ConstArray(childType, fields), nil
return llvm.ConstArray(childType, fields)
case llvm.PointerTypeKind:
if v.buf[0] > 255 {
// This is a regular pointer.
llvmValue, err := pointerValue{v.buf[0]}.toLLVMValue(llvm.Type{}, mem)
if err != nil {
return llvm.Value{}, err
}
llvmValue := pointerValue{v.buf[0]}.toLLVMValue(llvm.Type{}, mem)
if llvmValue.Type() != llvmType {
llvmValue = llvm.ConstBitCast(llvmValue, llvmType)
}
return llvmValue, nil
return llvmValue
}
// 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), nil
return llvm.ConstNull(llvmType)
}
var ptrValue llvm.Value // the underlying int
switch mem.r.pointerSize {
@@ -1213,19 +1164,19 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
case 2:
ptrValue = llvm.ConstInt(llvmType.Context().Int16Type(), ptr, false)
default:
return llvm.Value{}, errors.New("interp: unknown pointer size")
panic("unknown pointer size")
}
return llvm.ConstIntToPtr(ptrValue, llvmType), nil
return llvm.ConstIntToPtr(ptrValue, llvmType)
case llvm.DoubleTypeKind:
b := rawValue{v.buf[:8]}.Uint()
f := math.Float64frombits(b)
return llvm.ConstFloat(llvmType, f), nil
return llvm.ConstFloat(llvmType, f)
case llvm.FloatTypeKind:
b := uint32(rawValue{v.buf[:4]}.Uint())
f := math.Float32frombits(b)
return llvm.ConstFloat(llvmType, float64(f)), nil
return llvm.ConstFloat(llvmType, float64(f))
default:
return llvm.Value{}, errors.New("interp: todo: raw value to LLVM value: " + llvmType.String())
panic("todo: raw value to LLVM value: " + llvmType.String())
}
}
@@ -1414,8 +1365,8 @@ func (v localValue) Int() int64 {
panic("interp: localValue.Int")
}
func (v localValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) {
return v.value, nil
func (v localValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Value {
return v.value
}
func (r *runner) getValue(llvmValue llvm.Value) value {
+6 -5
View File
@@ -1,16 +1,17 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
%runtime.typecodeID = type { %runtime.typecodeID*, i64, %runtime.interfaceMethodInfo* }
%runtime.typecodeID = type { %runtime.typecodeID*, i64 }
%runtime.interfaceMethodInfo = type { i8*, i64 }
%runtime.typeInInterface = type { %runtime.typecodeID*, %runtime.interfaceMethodInfo* }
@main.v1 = global i1 0
@"reflect/types.type:named:main.foo" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i64 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:named:main.foo$id" = external constant i8
@"reflect/types.type:named:main.foo" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i64 0 }
@"reflect/types.type:basic:int" = external constant %runtime.typecodeID
@"typeInInterface:reflect/types.type:named:main.foo" = private constant %runtime.typeInInterface { %runtime.typecodeID* @"reflect/types.type:named:main.foo", %runtime.interfaceMethodInfo* null }
declare i1 @runtime.typeAssert(i64, i8*, i8*, i8*)
declare i1 @runtime.typeAssert(i64, %runtime.typecodeID*, i8*, i8*)
define void @runtime.initAll() unnamed_addr {
entry:
@@ -21,7 +22,7 @@ entry:
define internal void @main.init() unnamed_addr {
entry:
; Test type asserts.
%typecode = call i1 @runtime.typeAssert(i64 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:main.foo" to i64), i8* @"reflect/types.type:named:main.foo$id", i8* undef, i8* null)
%typecode = call i1 @runtime.typeAssert(i64 ptrtoint (%runtime.typeInInterface* @"typeInInterface:reflect/types.type:named:main.foo" to i64), %runtime.typecodeID* @"reflect/types.type:named:main.foo", i8* undef, i8* null)
store i1 %typecode, i1* @main.v1
ret void
}
+19 -26
View File
@@ -2,7 +2,6 @@ package loader
import (
"bytes"
"crypto/sha512"
"encoding/json"
"errors"
"fmt"
@@ -12,7 +11,6 @@ import (
"go/token"
"go/types"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
@@ -68,12 +66,10 @@ type PackageJSON struct {
type Package struct {
PackageJSON
program *Program
Files []*ast.File
FileHashes map[string][]byte
CFlags []string // CFlags used during CGo preprocessing (only set if CGo is used)
Pkg *types.Package
info types.Info
program *Program
Files []*ast.File
Pkg *types.Package
info types.Info
}
// Load loads the given package with all dependencies (including the runtime
@@ -122,8 +118,7 @@ func Load(config *compileopts.Config, inputPkgs []string, clangHeaders string, t
decoder := json.NewDecoder(buf)
for {
pkg := &Package{
program: p,
FileHashes: make(map[string][]byte),
program: p,
info: types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
@@ -282,15 +277,17 @@ func (p *Program) Parse() error {
}
// parseFile is a wrapper around parser.ParseFile.
func (p *Package) parseFile(path string, mode parser.Mode) (*ast.File, error) {
originalPath := p.program.getOriginalPath(path)
data, err := ioutil.ReadFile(path)
func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
if p.fset == nil {
p.fset = token.NewFileSet()
}
rd, err := os.Open(path)
if err != nil {
return nil, err
}
sum := sha512.Sum512_224(data)
p.FileHashes[originalPath] = sum[:]
return parser.ParseFile(p.program.fset, originalPath, data, mode)
defer rd.Close()
return parser.ParseFile(p.fset, p.getOriginalPath(path), rd, mode)
}
// Parse parses and typechecks this package.
@@ -366,7 +363,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
if !filepath.IsAbs(file) {
file = filepath.Join(p.Dir, file)
}
f, err := p.parseFile(file, parser.ParseComments)
f, err := p.program.parseFile(file, parser.ParseComments)
if err != nil {
fileErrs = append(fileErrs, err)
return
@@ -382,17 +379,13 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
// Do CGo processing.
if len(p.CgoFiles) != 0 {
var initialCFlags []string
initialCFlags = append(initialCFlags, p.program.config.CFlags()...)
initialCFlags = append(initialCFlags, "-I"+p.Dir)
var cflags []string
cflags = append(cflags, p.program.config.CFlags()...)
cflags = append(cflags, "-I"+p.Dir)
if p.program.clangHeaders != "" {
initialCFlags = append(initialCFlags, "-Xclang", "-internal-isystem", "-Xclang", p.program.clangHeaders)
}
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
cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.program.clangHeaders)
}
generated, ldflags, errs := cgo.Process(files, p.program.workingDir, p.program.fset, cflags)
if errs != nil {
fileErrs = append(fileErrs, errs...)
}
+12 -11
View File
@@ -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...)
}
@@ -105,7 +105,7 @@ func Build(pkgName, outpath string, options *compileopts.Options) error {
return err
}
return builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error {
return builder.Build(pkgName, outpath, config, nil, func(result builder.BuildResult) error {
if err := os.Rename(result.Binary, outpath); err != nil {
// Moving failed. Do a file copy.
inf, err := os.Open(result.Binary)
@@ -141,7 +141,7 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, ou
return err
}
return builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error {
return builder.Build(pkgName, outpath, config, nil, func(result builder.BuildResult) error {
if testCompileOnly || outpath != "" {
// Write test binary to the specified file name.
if outpath == "" {
@@ -237,7 +237,7 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
return errors.New("unknown flash method: " + flashMethod)
}
return builder.Build(pkgName, fileExt, config, func(result builder.BuildResult) error {
return builder.Build(pkgName, fileExt, config, func() error {
// do we need port reset to put MCU into bootloader mode?
if config.Target.PortReset == "true" && flashMethod != "openocd" {
if port == "" {
@@ -250,12 +250,14 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
err := touchSerialPortAt1200bps(port)
if err != nil {
return &commandError{"failed to reset port", result.Binary, err}
return &commandError{"failed to reset port", "", err}
}
// give the target MCU a chance to restart into bootloader
time.Sleep(3 * time.Second)
}
return nil
}, func(result builder.BuildResult) error {
// this flashing method copies the binary data to a Mass Storage Device (msd)
switch flashMethod {
case "", "command":
@@ -344,12 +346,11 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
if err != nil {
return err
}
gdb, err := config.Target.LookupGDB()
if err != nil {
return err
if config.Target.GDB == "" {
return errors.New("gdb not configured in the target specification")
}
return builder.Build(pkgName, "", config, func(result builder.BuildResult) error {
return builder.Build(pkgName, "", config, nil, func(result builder.BuildResult) error {
// Find a good way to run GDB.
gdbInterface, openocdInterface := config.Programmer()
switch gdbInterface {
@@ -491,7 +492,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
for _, cmd := range gdbCommands {
params = append(params, "-ex", cmd)
}
cmd := executeCommand(config.Options, gdb, params...)
cmd := executeCommand(config.Options, config.Target.GDB, params...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -513,7 +514,7 @@ func Run(pkgName string, options *compileopts.Options) error {
return err
}
return builder.Build(pkgName, ".elf", config, func(result builder.BuildResult) error {
return builder.Build(pkgName, ".elf", config, nil, func(result builder.BuildResult) error {
if len(config.Target.Emulator) == 0 {
// Run directly.
cmd := executeCommand(config.Options, result.Binary)
+19 -11
View File
@@ -15,6 +15,7 @@ import (
"runtime"
"sort"
"strings"
"sync"
"testing"
"time"
@@ -58,10 +59,7 @@ func TestCompiler(t *testing.T) {
t.Run("Host", func(t *testing.T) {
runPlatTests("", matches, t)
if runtime.GOOS == "darwin" {
runTest("testdata/libc/filesystem.go", "", t,
nil, nil)
runTest("testdata/libc/env.go", "", t,
[]string{"ENV1=VALUE1", "ENV2=VALUE2"}, nil)
runTest("testdata/libc/env.go", "", t, []string{"ENV1=VALUE1", "ENV2=VALUE2"}...)
}
})
}
@@ -109,9 +107,7 @@ func TestCompiler(t *testing.T) {
t.Run("WASI", func(t *testing.T) {
runPlatTests("wasi", matches, t)
runTest("testdata/libc/env.go", "wasi", t,
[]string{"--env", "ENV1=VALUE1", "--env", "ENV2=VALUE2"}, nil)
runTest("testdata/libc/filesystem.go", "wasi", t, nil, []string{"--dir=."})
runTest("testdata/libc/env.go", "wasi", t, []string{"ENV1=VALUE1", "ENV2=VALUE2"}...)
})
}
}
@@ -123,12 +119,24 @@ func runPlatTests(target string, matches []string, t *testing.T) {
path := path // redefine to avoid race condition
t.Run(filepath.Base(path), func(t *testing.T) {
t.Parallel()
runTest(path, target, t, nil, nil)
runTest(path, target, t)
})
}
}
func runTest(path, target string, t *testing.T, environmentVars []string, additionalArgs []string) {
// 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) {
// Get the expected output for this test.
txtpath := path[:len(path)-3] + ".txt"
if path[len(path)-1] == os.PathSeparator {
@@ -164,7 +172,7 @@ func runTest(path, target string, t *testing.T, environmentVars []string, additi
}
binary := filepath.Join(tmpdir, "test")
err = Build("./"+path, binary, config)
err = runBuild("./"+path, binary, config)
if err != nil {
printCompilerError(t.Log, err)
t.Fail()
@@ -187,7 +195,7 @@ func runTest(path, target string, t *testing.T, environmentVars []string, additi
cmd = exec.Command(binary)
} else {
args := append(spec.Emulator[1:], binary)
cmd = exec.Command(spec.Emulator[0], append(args, additionalArgs...)...)
cmd = exec.Command(spec.Emulator[0], args...)
}
if len(spec.Emulator) != 0 && spec.Emulator[0] == "wasmtime" {
+15 -2
View File
@@ -11,7 +11,7 @@ import (
type rawState uint8
//export llvm.coro.resume
func coroResume(*rawState)
func (s *rawState) resume()
type state struct{ *rawState }
@@ -20,7 +20,7 @@ func noopState() *rawState
// Resume the task until it pauses or completes.
func (t *Task) Resume() {
coroResume(t.state.rawState)
t.state.resume()
}
// setState is used by the compiler to set the state of the function at the beginning of a function call.
@@ -77,9 +77,22 @@ func Current() *Task
// This is implemented inside the compiler.
func Pause()
type taskHolder interface {
setState(*rawState) *rawState
returnTo(*rawState)
returnCurrent()
setReturnPtr(unsafe.Pointer)
getReturnPtr() unsafe.Pointer
}
// If there are no direct references to the task methods, they will not be discovered by the compiler, and this will trigger a compiler error.
// Instantiating this interface forces discovery of these methods.
var _ = taskHolder((*Task)(nil))
func fake() {
// Hack to ensure intrinsics are discovered.
Current()
go func() {}()
Pause()
}
@@ -32,7 +32,7 @@ func init() {
// I2C on the Arduino Nano 33.
var (
I2C0 = &I2C{
I2C0 = I2C{
Bus: sam.SERCOM4_I2CM,
SERCOM: 4,
}
+2 -2
View File
@@ -85,6 +85,6 @@ const (
// I2C pins
const (
I2C0_SDA_PIN = PB7
I2C0_SCL_PIN = PB6
SDA_PIN = PB7
SCL_PIN = PB6
)
@@ -23,12 +23,12 @@ func init() {
// I2C on the Circuit Playground Express.
var (
// external device
I2C0 = &I2C{
I2C0 = I2C{
Bus: sam.SERCOM5_I2CM,
SERCOM: 5,
}
// internal device
I2C1 = &I2C{
I2C1 = I2C{
Bus: sam.SERCOM1_I2CM,
SERCOM: 1,
}
+1 -1
View File
@@ -75,7 +75,7 @@ const (
// I2C on the Feather M0.
var (
I2C0 = &I2C{
I2C0 = I2C{
Bus: sam.SERCOM3_I2CM,
SERCOM: 3,
}
+1 -1
View File
@@ -28,7 +28,7 @@ func init() {
// I2C on the Feather M4.
var (
I2C0 = &I2C{
I2C0 = I2C{
Bus: sam.SERCOM2_I2CM,
SERCOM: 2,
}
+12 -15
View File
@@ -120,22 +120,19 @@ const (
var (
UART1 = UART{
Buffer: NewRingBuffer(),
Bus: stm32.USART3,
TxAltFuncSelector: AF7_USART1_2_3,
RxAltFuncSelector: AF7_USART1_2_3,
Buffer: NewRingBuffer(),
Bus: stm32.USART3,
AltFuncSelector: AF7_USART1_2_3,
}
UART2 = UART{
Buffer: NewRingBuffer(),
Bus: stm32.USART6,
TxAltFuncSelector: AF8_USART4_5_6,
RxAltFuncSelector: AF8_USART4_5_6,
Buffer: NewRingBuffer(),
Bus: stm32.USART6,
AltFuncSelector: AF8_USART4_5_6,
}
UART3 = UART{
Buffer: NewRingBuffer(),
Bus: stm32.USART1,
TxAltFuncSelector: AF7_USART1_2_3,
RxAltFuncSelector: AF7_USART1_2_3,
Buffer: NewRingBuffer(),
Bus: stm32.USART1,
AltFuncSelector: AF7_USART1_2_3,
}
UART0 = UART1
)
@@ -230,15 +227,15 @@ const (
)
var (
I2C1 = &I2C{
I2C1 = I2C{
Bus: stm32.I2C1,
AltFuncSelector: AF4_I2C1_2_3,
}
I2C2 = &I2C{
I2C2 = I2C{
Bus: stm32.I2C2,
AltFuncSelector: AF4_I2C1_2_3,
}
I2C3 = &I2C{
I2C3 = I2C{
Bus: stm32.I2C1,
AltFuncSelector: AF4_I2C1_2_3,
}
@@ -40,11 +40,11 @@ var (
// I2C on the Grand Central M4
var (
I2C0 = &I2C{
I2C0 = I2C{
Bus: sam.SERCOM3_I2CM,
SERCOM: 3,
}
I2C1 = &I2C{
I2C1 = I2C{
Bus: sam.SERCOM6_I2CM,
SERCOM: 6,
}
+7
View File
@@ -10,3 +10,10 @@ var (
Bus: sifive.QSPI1,
}
)
// I2C on the HiFive1 rev B.
var (
I2C0 = I2C{
Bus: sifive.I2C0,
}
)
+1 -1
View File
@@ -75,7 +75,7 @@ const (
// I2C on the ItsyBitsy M0.
var (
I2C0 = &I2C{
I2C0 = I2C{
Bus: sam.SERCOM3_I2CM,
SERCOM: 3,
}
+1 -1
View File
@@ -28,7 +28,7 @@ func init() {
// I2C on the ItsyBitsy M4.
var (
I2C0 = &I2C{
I2C0 = I2C{
Bus: sam.SERCOM2_I2CM,
SERCOM: 2,
}
+6 -8
View File
@@ -54,18 +54,16 @@ var (
// Console UART (LPUSART1)
UART0 = UART{
Buffer: NewRingBuffer(),
Bus: stm32.LPUART1,
TxAltFuncSelector: 6,
RxAltFuncSelector: 6,
Buffer: NewRingBuffer(),
Bus: stm32.LPUART1,
AltFuncSelector: 6,
}
// Gps UART
UART1 = UART{
Buffer: NewRingBuffer(),
Bus: stm32.USART1,
TxAltFuncSelector: 0,
RxAltFuncSelector: 0,
Buffer: NewRingBuffer(),
Bus: stm32.USART1,
AltFuncSelector: 0,
}
// SPI
+13
View File
@@ -13,3 +13,16 @@ var (
Bus: kendryte.SPI1,
}
)
// I2C on the MAix Bit.
var (
I2C0 = I2C{
Bus: kendryte.I2C0,
}
I2C1 = I2C{
Bus: kendryte.I2C1,
}
I2C2 = I2C{
Bus: kendryte.I2C2,
}
)
@@ -29,7 +29,7 @@ func init() {
// I2C on the MatrixPortal M4
var (
I2C0 = &I2C{
I2C0 = I2C{
Bus: sam.SERCOM5_I2CM,
SERCOM: 5,
}
@@ -28,7 +28,7 @@ func init() {
// I2C on the Metro M4.
var (
I2C0 = &I2C{
I2C0 = I2C{
Bus: sam.SERCOM5_I2CM,
SERCOM: 5,
}
+1 -1
View File
@@ -44,7 +44,7 @@ const (
// USB CDC identifiers
const (
usb_STRING_PRODUCT = "Makerdiary nRF52840 MDK USB Dongle"
usb_STRING_MANUFACTURER = "Nordic Semiconductor ASA"
usb_STRING_MANUFACTURER = "Makerdiary"
)
var (
+1 -1
View File
@@ -39,7 +39,7 @@ const (
// USB CDC identifiers
const (
usb_STRING_PRODUCT = "Makerdiary nRF52840 MDK"
usb_STRING_MANUFACTURER = "Nordic Semiconductor ASA"
usb_STRING_MANUFACTURER = "Makerdiary"
)
var (
+2 -2
View File
@@ -120,6 +120,6 @@ const (
// I2C pins
const (
I2C0_SCL_PIN = PB6
I2C0_SDA_PIN = PB7
SCL_PIN = PB6
SDA_PIN = PB7
)
+5 -15
View File
@@ -33,10 +33,9 @@ var (
// debugger to be exposed as virtual COM port over USB on Nucleo boards.
// Both UART0 and UART1 refer to USART2.
UART0 = UART{
Buffer: NewRingBuffer(),
Bus: stm32.USART3,
TxAltFuncSelector: UART_ALT_FN,
RxAltFuncSelector: UART_ALT_FN,
Buffer: NewRingBuffer(),
Bus: stm32.USART3,
AltFuncSelector: UART_ALT_FN,
}
UART1 = &UART0
)
@@ -54,15 +53,6 @@ const (
// I2C pins
const (
I2C0_SCL_PIN = PB8
I2C0_SDA_PIN = PB9
)
var (
// I2C1 is documented, alias to I2C0 as well
I2C1 = &I2C{
Bus: stm32.I2C1,
AltFuncSelector: 4,
}
I2C0 = I2C1
SCL_PIN = PB6
SDA_PIN = PB7
)
-56
View File
@@ -1,56 +0,0 @@
// +build nucleol432kc
package machine
import (
"device/stm32"
"runtime/interrupt"
)
const (
LED = LED_BUILTIN
LED_BUILTIN = LED_GREEN
LED_GREEN = PB3
)
// UART pins
const (
// PA2 and PA15 are connected to the ST-Link Virtual Com Port (VCP)
UART_TX_PIN = PA2
UART_RX_PIN = PA15
)
// I2C pins
const (
// With default solder bridge settings:
// PB6 / Arduino D5 / CN3 Pin 8 is SCL
// PB7 / Arduino D4 / CN3 Pin 7 is SDA
I2C0_SCL_PIN = PB6
I2C0_SDA_PIN = PB7
)
var (
// USART2 is the hardware serial port connected to the onboard ST-LINK
// debugger to be exposed as virtual COM port over USB on Nucleo boards.
// Both UART0 and UART1 refer to USART2.
UART0 = UART{
Buffer: NewRingBuffer(),
Bus: stm32.USART2,
TxAltFuncSelector: 7,
RxAltFuncSelector: 3,
}
UART1 = &UART0
)
var (
// I2C1 is documented, alias to I2C0 as well
I2C1 = &I2C{
Bus: stm32.I2C1,
AltFuncSelector: 4,
}
I2C0 = I2C1
)
func init() {
UART0.Interrupt = interrupt.New(stm32.IRQ_USART2, UART0.handleInterrupt)
}
+3 -18
View File
@@ -33,28 +33,13 @@ var (
// debugger to be exposed as virtual COM port over USB on Nucleo boards.
// Both UART0 and UART1 refer to LPUART1.
UART0 = UART{
Buffer: NewRingBuffer(),
Bus: stm32.LPUART1,
TxAltFuncSelector: UART_ALT_FN,
RxAltFuncSelector: UART_ALT_FN,
Buffer: NewRingBuffer(),
Bus: stm32.LPUART1,
AltFuncSelector: UART_ALT_FN,
}
UART1 = &UART0
)
const (
I2C0_SCL_PIN = PB8
I2C0_SDA_PIN = PB9
)
var (
// I2C1 is documented, alias to I2C0 as well
I2C1 = &I2C{
Bus: stm32.I2C1,
AltFuncSelector: 4,
}
I2C0 = I2C1
)
func init() {
UART0.Interrupt = interrupt.New(stm32.IRQ_LPUART1, UART0.handleInterrupt)
}
+1 -1
View File
@@ -22,7 +22,7 @@ func init() {
// I2C on the P1AM-100.
var (
I2C0 = &I2C{
I2C0 = I2C{
Bus: sam.SERCOM0_I2CM,
SERCOM: 0,
}
-64
View File
@@ -1,64 +0,0 @@
// +build pca10059
package machine
// The PCA10040 has a low-frequency (32kHz) crystal oscillator on board.
const HasLowFrequencyCrystal = true
// LEDs on the PCA10059 (nRF52840 dongle)
const (
LED Pin = LED1
LED1 Pin = 6
LED2 Pin = 8
LED3 Pin = (1 << 5) | 9
LED4 Pin = 12
)
// Buttons on the PCA10059 (nRF52840 dongle)
const (
BUTTON Pin = BUTTON1
BUTTON1 Pin = (1 << 5) | 6
)
// ADC pins
const (
ADC1 Pin = 2
ADC2 Pin = 4
ADC3 Pin = 29
ADC4 Pin = 31
)
// UART pins
const (
UART_TX_PIN Pin = NoPin
UART_RX_PIN Pin = NoPin
)
// UART0 is the USB device
var (
UART0 = USB
)
// I2C pins (unused)
const (
SDA_PIN = NoPin
SCL_PIN = NoPin
)
// SPI pins (unused)
const (
SPI0_SCK_PIN = NoPin
SPI0_SDO_PIN = NoPin
SPI0_SDI_PIN = NoPin
)
// USB CDC identifiers
const (
usb_STRING_PRODUCT = "nRF52840 Dongle"
usb_STRING_MANUFACTURER = "Nordic Semiconductor ASA"
)
var (
usb_VID uint16 = 0x1915
usb_PID uint16 = 0xCAFE
)
+1 -1
View File
@@ -28,7 +28,7 @@ func init() {
// I2C on the ItsyBitsy M4.
var (
I2C0 = &I2C{
I2C0 = I2C{
Bus: sam.SERCOM2_I2CM,
SERCOM: 2,
}
+1 -1
View File
@@ -99,7 +99,7 @@ const (
// I2C on the PyGamer.
var (
I2C0 = &I2C{
I2C0 = I2C{
Bus: sam.SERCOM2_I2CM,
SERCOM: 2,
}
+1 -1
View File
@@ -21,7 +21,7 @@ func init() {
// I2C on the PyPortal.
var (
I2C0 = &I2C{
I2C0 = I2C{
Bus: sam.SERCOM5_I2CM,
SERCOM: 5,
}
+1 -1
View File
@@ -93,7 +93,7 @@ const (
// I2C on the QT Py M0.
var (
I2C0 = &I2C{
I2C0 = I2C{
Bus: sam.SERCOM2_I2CM,
SERCOM: 2,
}
+5
View File
@@ -0,0 +1,5 @@
// +build bluepill nucleof103rb stm32f4
package machine
// Peripheral abstraction layer for the stm32.
+4 -5
View File
@@ -28,10 +28,9 @@ const (
var (
UART0 = UART{
Buffer: NewRingBuffer(),
Bus: stm32.USART2,
TxAltFuncSelector: AF7_USART1_2_3,
RxAltFuncSelector: AF7_USART1_2_3,
Buffer: NewRingBuffer(),
Bus: stm32.USART2,
AltFuncSelector: AF7_USART1_2_3,
}
UART1 = &UART0
)
@@ -74,7 +73,7 @@ const (
)
var (
I2C0 = &I2C{
I2C0 = I2C{
Bus: stm32.I2C1,
AltFuncSelector: AF4_I2C1_2_3,
}
+1 -1
View File
@@ -81,7 +81,7 @@ const (
// I2C on the Trinket M0.
var (
I2C0 = &I2C{
I2C0 = I2C{
Bus: sam.SERCOM2_I2CM,
SERCOM: 2,
}
+2 -2
View File
@@ -29,12 +29,12 @@ func init() {
// I2C on the Wio Terminal
var (
I2C0 = &I2C{
I2C0 = I2C{
Bus: sam.SERCOM4_I2CM,
SERCOM: 4,
}
I2C1 = &I2C{
I2C1 = I2C{
Bus: sam.SERCOM4_I2CM,
SERCOM: 4,
}
+1 -1
View File
@@ -81,7 +81,7 @@ const (
// I2C on the Xiao
var (
I2C0 = &I2C{
I2C0 = I2C{
Bus: sam.SERCOM2_I2CM,
SERCOM: 2,
}
+3 -3
View File
@@ -1,4 +1,4 @@
// +build atmega nrf sam stm32,!stm32l0 fe310 k210
// +build avr nrf sam stm32,!stm32f7x2,!stm32l5x2,!stm32l0 fe310 k210
package machine
@@ -29,7 +29,7 @@ var (
// Many I2C-compatible devices are organized in terms of registers. This method
// is a shortcut to easily write to such registers. Also, it only works for
// devices with 7-bit addresses, which is the vast majority.
func (i2c *I2C) WriteRegister(address uint8, register uint8, data []byte) error {
func (i2c I2C) WriteRegister(address uint8, register uint8, data []byte) error {
buf := make([]uint8, len(data)+1)
buf[0] = register
copy(buf[1:], data)
@@ -42,6 +42,6 @@ func (i2c *I2C) WriteRegister(address uint8, register uint8, data []byte) error
// Many I2C-compatible devices are organized in terms of registers. This method
// is a shortcut to easily read such registers. Also, it only works for devices
// with 7-bit addresses, which is the vast majority.
func (i2c *I2C) ReadRegister(address uint8, register uint8, data []byte) error {
func (i2c I2C) ReadRegister(address uint8, register uint8, data []byte) error {
return i2c.Tx(uint16(address), []byte{register}, data)
}
+6 -13
View File
@@ -9,20 +9,13 @@ import (
"unsafe"
)
// I2C on AVR.
type I2C struct {
}
// I2C0 is the only I2C interface on most AVRs.
var I2C0 *I2C = nil
// I2CConfig is used to store config info for I2C.
type I2CConfig struct {
Frequency uint32
}
// Configure is intended to setup the I2C interface.
func (i2c *I2C) Configure(config I2CConfig) error {
func (i2c I2C) Configure(config I2CConfig) error {
// Default I2C bus speed is 100 kHz.
if config.Frequency == 0 {
config.Frequency = TWI_FREQ_100KHZ
@@ -49,7 +42,7 @@ func (i2c *I2C) Configure(config I2CConfig) error {
// Tx does a single I2C transaction at the specified address.
// It clocks out the given address, writes the bytes in w, reads back len(r)
// bytes and stores them in r, and generates a stop condition on the bus.
func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
func (i2c I2C) Tx(addr uint16, w, r []byte) error {
if len(w) != 0 {
i2c.start(uint8(addr), true) // start transmission for writing
for _, b := range w {
@@ -70,7 +63,7 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
}
// start starts an I2C communication session.
func (i2c *I2C) start(address uint8, write bool) {
func (i2c I2C) start(address uint8, write bool) {
// Clear TWI interrupt flag, put start condition on SDA, and enable TWI.
avr.TWCR.Set((avr.TWCR_TWINT | avr.TWCR_TWSTA | avr.TWCR_TWEN))
@@ -87,7 +80,7 @@ func (i2c *I2C) start(address uint8, write bool) {
}
// stop ends an I2C communication session.
func (i2c *I2C) stop() {
func (i2c I2C) stop() {
// Send stop condition.
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWSTO)
@@ -97,7 +90,7 @@ func (i2c *I2C) stop() {
}
// writeByte writes a single byte to the I2C bus.
func (i2c *I2C) writeByte(data byte) {
func (i2c I2C) writeByte(data byte) {
// Write data to register.
avr.TWDR.Set(data)
@@ -110,7 +103,7 @@ func (i2c *I2C) writeByte(data byte) {
}
// readByte reads a single byte from the I2C bus.
func (i2c *I2C) readByte() byte {
func (i2c I2C) readByte() byte {
// Clear TWI interrupt flag and enable TWI.
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWEA)
+15 -167
View File
@@ -8,10 +8,8 @@
package machine
import (
"device"
"device/arm"
"device/sam"
"errors"
"runtime/interrupt"
"runtime/volatile"
"unsafe"
@@ -669,7 +667,7 @@ const (
const i2cTimeout = 1000
// Configure is intended to setup the I2C interface.
func (i2c *I2C) Configure(config I2CConfig) error {
func (i2c I2C) Configure(config I2CConfig) error {
// Default I2C bus speed is 100 kHz.
if config.Frequency == 0 {
config.Frequency = TWI_FREQ_100KHZ
@@ -725,7 +723,7 @@ func (i2c *I2C) Configure(config I2CConfig) error {
}
// SetBaudRate sets the communication speed for the I2C.
func (i2c *I2C) SetBaudRate(br uint32) {
func (i2c I2C) SetBaudRate(br uint32) {
// Synchronous arithmetic baudrate, via Arduino SAMD implementation:
// SystemCoreClock / ( 2 * baudrate) - 5 - (((SystemCoreClock / 1000000) * WIRE_RISE_TIME_NANOSECONDS) / (2 * 1000));
baud := CPUFrequency()/(2*br) - 5 - (((CPUFrequency() / 1000000) * riseTimeNanoseconds) / (2 * 1000))
@@ -735,7 +733,7 @@ func (i2c *I2C) SetBaudRate(br uint32) {
// Tx does a single I2C transaction at the specified address.
// It clocks out the given address, writes the bytes in w, reads back len(r)
// bytes and stores them in r, and generates a stop condition on the bus.
func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
func (i2c I2C) Tx(addr uint16, w, r []byte) error {
var err error
if len(w) != 0 {
// send start/address for write
@@ -812,7 +810,7 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
}
// WriteByte writes a single byte to the I2C bus.
func (i2c *I2C) WriteByte(data byte) error {
func (i2c I2C) WriteByte(data byte) error {
// Send data byte
i2c.Bus.DATA.Set(data)
@@ -837,7 +835,7 @@ func (i2c *I2C) WriteByte(data byte) error {
}
// sendAddress sends the address and start signal
func (i2c *I2C) sendAddress(address uint16, write bool) error {
func (i2c I2C) sendAddress(address uint16, write bool) error {
data := (address << 1)
if !write {
data |= 1 // set read flag
@@ -857,7 +855,7 @@ func (i2c *I2C) sendAddress(address uint16, write bool) error {
return nil
}
func (i2c *I2C) signalStop() error {
func (i2c I2C) signalStop() error {
i2c.Bus.CTRLB.SetBits(wireCmdStop << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Stop command
timeout := i2cTimeout
for i2c.Bus.SYNCBUSY.HasBits(sam.SERCOM_I2CM_SYNCBUSY_SYSOP) {
@@ -869,7 +867,7 @@ func (i2c *I2C) signalStop() error {
return nil
}
func (i2c *I2C) signalRead() error {
func (i2c I2C) signalRead() error {
i2c.Bus.CTRLB.SetBits(wireCmdRead << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Read command
timeout := i2cTimeout
for i2c.Bus.SYNCBUSY.HasBits(sam.SERCOM_I2CM_SYNCBUSY_SYSOP) {
@@ -881,7 +879,7 @@ func (i2c *I2C) signalRead() error {
return nil
}
func (i2c *I2C) readByte() byte {
func (i2c I2C) readByte() byte {
for !i2c.Bus.INTFLAG.HasBits(sam.SERCOM_I2CM_INTFLAG_SB) {
}
return byte(i2c.Bus.DATA.Get())
@@ -1267,160 +1265,6 @@ func (spi SPI) Transfer(w byte) (byte, error) {
return byte(spi.Bus.DATA.Get()), nil
}
var (
ErrTxInvalidSliceSize = errors.New("SPI write and read slices must be same size")
)
// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read
// interface, there must always be the same number of bytes written as bytes read.
// The Tx method knows about this, and offers a few different ways of calling it.
//
// This form sends the bytes in tx buffer, putting the resulting bytes read into the rx buffer.
// Note that the tx and rx buffers must be the same size:
//
// spi.Tx(tx, rx)
//
// This form sends the tx buffer, ignoring the result. Useful for sending "commands" that return zeros
// until all the bytes in the command packet have been received:
//
// spi.Tx(tx, nil)
//
// This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet":
//
// spi.Tx(nil, rx)
//
func (spi SPI) Tx(w, r []byte) error {
if spi.Bus.BAUD.Get() == 0x00 {
// When the SPI Freq is 24MHz, special processing is performed to improve the speed.
switch {
case w == nil:
// read only, so write zero and read a result.
spi.rx(r)
case r == nil:
// write only
spi.tx24mhz(w)
default:
// write/read
if len(w) != len(r) {
return ErrTxInvalidSliceSize
}
spi.txrx24mhz(w, r)
}
} else {
switch {
case w == nil:
// read only, so write zero and read a result.
spi.rx(r)
case r == nil:
// write only
spi.tx(w)
default:
// write/read
if len(w) != len(r) {
return ErrTxInvalidSliceSize
}
spi.txrx(w, r)
}
}
return nil
}
func (spi SPI) tx(tx []byte) {
for i := 0; i < len(tx); i++ {
for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) {
}
spi.Bus.DATA.Set(uint32(tx[i]))
}
for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_TXC) {
}
// read to clear RXC register
for spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) {
spi.Bus.DATA.Get()
}
}
func (spi SPI) rx(rx []byte) {
spi.Bus.DATA.Set(0)
for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) {
}
for i := 1; i < len(rx); i++ {
spi.Bus.DATA.Set(0)
for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) {
}
rx[i-1] = byte(spi.Bus.DATA.Get())
}
for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) {
}
rx[len(rx)-1] = byte(spi.Bus.DATA.Get())
}
func (spi SPI) txrx(tx, rx []byte) {
spi.Bus.DATA.Set(uint32(tx[0]))
for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) {
}
for i := 1; i < len(rx); i++ {
spi.Bus.DATA.Set(uint32(tx[i]))
for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) {
}
rx[i-1] = byte(spi.Bus.DATA.Get())
}
for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) {
}
rx[len(rx)-1] = byte(spi.Bus.DATA.Get())
}
// tx24mhz is a special tx/rx function for CPU Clock 48 Mhz and SPI Freq 24 Mhz
func (spi SPI) tx24mhz(tx []byte) {
spi.Bus.DATA.Set(uint32(tx[0]))
device.Asm("nop")
device.Asm("nop")
device.Asm("nop")
device.Asm("nop")
device.Asm("nop")
device.Asm("nop")
for i := 1; i < len(tx); i++ {
spi.Bus.DATA.Set(uint32(tx[i]))
device.Asm("nop")
device.Asm("nop")
spi.Bus.DATA.Get()
}
for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) {
}
spi.Bus.DATA.Get()
}
// txrx24mhz is a special tx/rx function for CPU Clock 48 Mhz and SPI Freq 24 Mhz
func (spi SPI) txrx24mhz(tx, rx []byte) {
spi.Bus.DATA.Set(uint32(tx[0]))
device.Asm("nop")
device.Asm("nop")
device.Asm("nop")
device.Asm("nop")
device.Asm("nop")
device.Asm("nop")
for i := 1; i < len(rx); i++ {
spi.Bus.DATA.Set(uint32(tx[i]))
device.Asm("nop")
device.Asm("nop")
rx[i-1] = byte(spi.Bus.DATA.Get())
}
for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) {
}
rx[len(rx)-1] = byte(spi.Bus.DATA.Get())
}
// PWM
const period = 0xFFFF
@@ -1646,7 +1490,6 @@ 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
@@ -1701,7 +1544,7 @@ func (usbcdc *USBCDC) WriteByte(c byte) error {
mask := interrupt.Disable()
UART0.waitTxc = false
UART0.waitTxcRetryCount = 0
UART0.TxIdx.Set(0)
usbcdc.TxIdx.Set(0)
usbLineInfo.lineState = 0
interrupt.Restore(mask)
break
@@ -1869,7 +1712,6 @@ 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...
}
@@ -1933,7 +1775,13 @@ func handleUSB(intr interrupt.Interrupt) {
}
}
}
if i == usb_CDC_ENDPOINT_IN && UART0.waitTxc {
UART0.waitTxcRetryCount++
}
}
UART0.Flush()
}
func initEndpoint(ep, config uint32) {
+15 -11
View File
@@ -1104,7 +1104,7 @@ const (
const i2cTimeout = 1000
// Configure is intended to setup the I2C interface.
func (i2c *I2C) Configure(config I2CConfig) error {
func (i2c I2C) Configure(config I2CConfig) error {
// Default I2C bus speed is 100 kHz.
if config.Frequency == 0 {
config.Frequency = TWI_FREQ_100KHZ
@@ -1163,7 +1163,7 @@ func (i2c *I2C) Configure(config I2CConfig) error {
}
// SetBaudRate sets the communication speed for the I2C.
func (i2c *I2C) SetBaudRate(br uint32) {
func (i2c I2C) SetBaudRate(br uint32) {
// Synchronous arithmetic baudrate, via Adafruit SAMD51 implementation:
// sercom->I2CM.BAUD.bit.BAUD = SERCOM_FREQ_REF / ( 2 * baudrate) - 1 ;
baud := SERCOM_FREQ_REF/(2*br) - 1
@@ -1173,7 +1173,7 @@ func (i2c *I2C) SetBaudRate(br uint32) {
// Tx does a single I2C transaction at the specified address.
// It clocks out the given address, writes the bytes in w, reads back len(r)
// bytes and stores them in r, and generates a stop condition on the bus.
func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
func (i2c I2C) Tx(addr uint16, w, r []byte) error {
var err error
if len(w) != 0 {
// send start/address for write
@@ -1250,7 +1250,7 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
}
// WriteByte writes a single byte to the I2C bus.
func (i2c *I2C) WriteByte(data byte) error {
func (i2c I2C) WriteByte(data byte) error {
// Send data byte
i2c.Bus.DATA.Set(data)
@@ -1275,7 +1275,7 @@ func (i2c *I2C) WriteByte(data byte) error {
}
// sendAddress sends the address and start signal
func (i2c *I2C) sendAddress(address uint16, write bool) error {
func (i2c I2C) sendAddress(address uint16, write bool) error {
data := (address << 1)
if !write {
data |= 1 // set read flag
@@ -1295,7 +1295,7 @@ func (i2c *I2C) sendAddress(address uint16, write bool) error {
return nil
}
func (i2c *I2C) signalStop() error {
func (i2c I2C) signalStop() error {
i2c.Bus.CTRLB.SetBits(wireCmdStop << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Stop command
timeout := i2cTimeout
for i2c.Bus.SYNCBUSY.HasBits(sam.SERCOM_I2CM_SYNCBUSY_SYSOP) {
@@ -1307,7 +1307,7 @@ func (i2c *I2C) signalStop() error {
return nil
}
func (i2c *I2C) signalRead() error {
func (i2c I2C) signalRead() error {
i2c.Bus.CTRLB.SetBits(wireCmdRead << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Read command
timeout := i2cTimeout
for i2c.Bus.SYNCBUSY.HasBits(sam.SERCOM_I2CM_SYNCBUSY_SYSOP) {
@@ -1319,7 +1319,7 @@ func (i2c *I2C) signalRead() error {
return nil
}
func (i2c *I2C) readByte() byte {
func (i2c I2C) readByte() byte {
for !i2c.Bus.INTFLAG.HasBits(sam.SERCOM_I2CM_INTFLAG_SB) {
}
return byte(i2c.Bus.DATA.Get())
@@ -1850,7 +1850,6 @@ 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
@@ -1905,7 +1904,7 @@ func (usbcdc *USBCDC) WriteByte(c byte) error {
mask := interrupt.Disable()
UART0.waitTxc = false
UART0.waitTxcRetryCount = 0
UART0.TxIdx.Set(0)
usbcdc.TxIdx.Set(0)
usbLineInfo.lineState = 0
interrupt.Restore(mask)
break
@@ -2075,7 +2074,6 @@ 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...
}
@@ -2139,7 +2137,13 @@ func handleUSBIRQ(interrupt.Interrupt) {
}
}
}
if i == usb_CDC_ENDPOINT_IN && UART0.waitTxc {
UART0.waitTxcRetryCount++
}
}
UART0.Flush()
}
func initEndpoint(ep, config uint32) {
+9
View File
@@ -0,0 +1,9 @@
// +build avr,attiny
package machine
// Tx is a dummy implementation. I2C has not been implemented for ATtiny
// devices.
func (i2c I2C) Tx(addr uint16, w, r []byte) error {
return nil
}
+7
View File
@@ -141,3 +141,10 @@ func (a ADC) Get() uint16 {
return uint16(avr.ADCL.Get()) | uint16(avr.ADCH.Get())<<8
}
// I2C on AVR.
type I2C struct {
}
// I2C0 is the only I2C interface on most AVRs.
var I2C0 = I2C{}
+6 -11
View File
@@ -5,7 +5,6 @@ package machine
import (
"device/sifive"
"runtime/interrupt"
"unsafe"
)
func CPUFrequency() uint32 {
@@ -186,13 +185,9 @@ func (spi SPI) Transfer(w byte) (byte, error) {
// I2C on the FE310-G002.
type I2C struct {
Bus sifive.I2C_Type
Bus *sifive.I2C_Type
}
var (
I2C0 = (*I2C)(unsafe.Pointer(sifive.I2C0))
)
// I2CConfig is used to store config info for I2C.
type I2CConfig struct {
Frequency uint32
@@ -201,7 +196,7 @@ type I2CConfig struct {
}
// Configure is intended to setup the I2C interface.
func (i2c *I2C) Configure(config I2CConfig) error {
func (i2c I2C) Configure(config I2CConfig) error {
var i2cClockFrequency uint32 = 32000000
if config.Frequency == 0 {
config.Frequency = TWI_FREQ_100KHZ
@@ -233,7 +228,7 @@ func (i2c *I2C) Configure(config I2CConfig) error {
// Tx does a single I2C transaction at the specified address.
// It clocks out the given address, writes the bytes in w, reads back len(r)
// bytes and stores them in r, and generates a stop condition on the bus.
func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
func (i2c I2C) Tx(addr uint16, w, r []byte) error {
var err error
if len(w) != 0 {
// send start/address for write
@@ -281,7 +276,7 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
}
// Writes a single byte to the I2C bus.
func (i2c *I2C) writeByte(data byte) error {
func (i2c I2C) writeByte(data byte) error {
// Send data byte
i2c.Bus.TXR_RXR.Set(uint32(data))
@@ -300,7 +295,7 @@ func (i2c *I2C) writeByte(data byte) error {
}
// Reads a single byte from the I2C bus.
func (i2c *I2C) readByte() byte {
func (i2c I2C) readByte() byte {
i2c.Bus.CR_SR.Set(sifive.I2C_CR_RD)
// wait until transmission complete
@@ -311,7 +306,7 @@ func (i2c *I2C) readByte() byte {
}
// Sends the address and start signal.
func (i2c *I2C) sendAddress(address uint16, write bool) error {
func (i2c I2C) sendAddress(address uint16, write bool) error {
data := (address << 1)
if !write {
data |= 1 // set read flag in transmit register
+3 -3
View File
@@ -6,7 +6,7 @@ package machine
var (
SPI0 = SPI{0}
I2C0 = &I2C{0}
I2C0 = I2C{0}
UART0 = UART{0}
)
@@ -115,13 +115,13 @@ type I2CConfig struct {
}
// Configure is intended to setup the I2C interface.
func (i2c *I2C) Configure(config I2CConfig) error {
func (i2c I2C) Configure(config I2CConfig) error {
i2cConfigure(i2c.Bus, config.SCL, config.SDA)
return nil
}
// Tx does a single I2C transaction at the specified address.
func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
func (i2c I2C) Tx(addr uint16, w, r []byte) error {
i2cTransfer(i2c.Bus, &w[0], len(w), &r[0], len(r))
// TODO: do something with the returned error code.
return nil
+4 -11
View File
@@ -7,7 +7,6 @@ import (
"device/riscv"
"errors"
"runtime/interrupt"
"unsafe"
)
func CPUFrequency() uint32 {
@@ -494,15 +493,9 @@ func (spi SPI) Transfer(w byte) (byte, error) {
// I2C on the K210.
type I2C struct {
Bus kendryte.I2C_Type
Bus *kendryte.I2C_Type
}
var (
I2C0 = (*I2C)(unsafe.Pointer(kendryte.I2C0))
I2C1 = (*I2C)(unsafe.Pointer(kendryte.I2C1))
I2C2 = (*I2C)(unsafe.Pointer(kendryte.I2C2))
)
// I2CConfig is used to store config info for I2C.
type I2CConfig struct {
Frequency uint32
@@ -511,7 +504,7 @@ type I2CConfig struct {
}
// Configure is intended to setup the I2C interface.
func (i2c *I2C) Configure(config I2CConfig) error {
func (i2c I2C) Configure(config I2CConfig) error {
if config.Frequency == 0 {
config.Frequency = TWI_FREQ_100KHZ
@@ -525,7 +518,7 @@ func (i2c *I2C) Configure(config I2CConfig) error {
// Enable APB0 clock.
kendryte.SYSCTL.CLK_EN_CENT.SetBits(kendryte.SYSCTL_CLK_EN_CENT_APB0_CLK_EN)
switch &i2c.Bus {
switch i2c.Bus {
case kendryte.I2C0:
// Initialize I2C0 clock.
kendryte.SYSCTL.CLK_EN_PERI.SetBits(kendryte.SYSCTL_CLK_EN_PERI_I2C0_CLK_EN)
@@ -574,7 +567,7 @@ func (i2c *I2C) Configure(config I2CConfig) error {
// Tx does a single I2C transaction at the specified address.
// It clocks out the given address, writes the bytes in w, reads back len(r)
// bytes and stores them in r, and generates a stop condition on the bus.
func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
func (i2c I2C) Tx(addr uint16, w, r []byte) error {
// Set peripheral address.
i2c.Bus.TAR.Set(uint32(addr))
// Enable controller.
+8 -9
View File
@@ -6,7 +6,6 @@ import (
"device/nrf"
"errors"
"runtime/interrupt"
"unsafe"
)
var (
@@ -204,13 +203,13 @@ func (uart *UART) handleInterrupt(interrupt.Interrupt) {
// I2C on the NRF.
type I2C struct {
Bus nrf.TWI_Type
Bus *nrf.TWI_Type
}
// There are 2 I2C interfaces on the NRF.
var (
I2C0 = (*I2C)(unsafe.Pointer(nrf.TWI0))
I2C1 = (*I2C)(unsafe.Pointer(nrf.TWI1))
I2C0 = I2C{Bus: nrf.TWI0}
I2C1 = I2C{Bus: nrf.TWI1}
)
// I2CConfig is used to store config info for I2C.
@@ -221,7 +220,7 @@ type I2CConfig struct {
}
// Configure is intended to setup the I2C interface.
func (i2c *I2C) Configure(config I2CConfig) error {
func (i2c I2C) Configure(config I2CConfig) error {
// Default I2C bus speed is 100 kHz.
if config.Frequency == 0 {
config.Frequency = TWI_FREQ_100KHZ
@@ -262,7 +261,7 @@ func (i2c *I2C) Configure(config I2CConfig) error {
// Tx does a single I2C transaction at the specified address.
// It clocks out the given address, writes the bytes in w, reads back len(r)
// bytes and stores them in r, and generates a stop condition on the bus.
func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) {
func (i2c I2C) Tx(addr uint16, w, r []byte) (err error) {
i2c.Bus.ADDRESS.Set(uint32(addr))
if len(w) != 0 {
@@ -300,7 +299,7 @@ cleanUp:
// signalStop sends a stop signal when writing or tells the I2C peripheral that
// it must generate a stop condition after the next character is retrieved when
// reading.
func (i2c *I2C) signalStop() {
func (i2c I2C) signalStop() {
i2c.Bus.TASKS_STOP.Set(1)
for i2c.Bus.EVENTS_STOPPED.Get() == 0 {
}
@@ -308,7 +307,7 @@ func (i2c *I2C) signalStop() {
}
// writeByte writes a single byte to the I2C bus.
func (i2c *I2C) writeByte(data byte) error {
func (i2c I2C) writeByte(data byte) error {
i2c.Bus.TXD.Set(uint32(data))
for i2c.Bus.EVENTS_TXDSENT.Get() == 0 {
if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 {
@@ -321,7 +320,7 @@ func (i2c *I2C) writeByte(data byte) error {
}
// readByte reads a single byte from the I2C bus.
func (i2c *I2C) readByte() (byte, error) {
func (i2c I2C) readByte() (byte, error) {
for i2c.Bus.EVENTS_RXDREADY.Get() == 0 {
if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 {
i2c.Bus.EVENTS_ERROR.Set(0)
+1 -1
View File
@@ -24,7 +24,7 @@ func (uart UART) setPins(tx, rx Pin) {
nrf.UART0.PSELRXD.Set(uint32(rx))
}
func (i2c *I2C) setPins(scl, sda Pin) {
func (i2c I2C) setPins(scl, sda Pin) {
i2c.Bus.PSELSCL.Set(uint32(scl))
i2c.Bus.PSELSDA.Set(uint32(sda))
}
+1 -1
View File
@@ -56,7 +56,7 @@ func (uart UART) setPins(tx, rx Pin) {
nrf.UART0.PSELRXD.Set(uint32(rx))
}
func (i2c *I2C) setPins(scl, sda Pin) {
func (i2c I2C) setPins(scl, sda Pin) {
i2c.Bus.PSELSCL.Set(uint32(scl))
i2c.Bus.PSELSDA.Set(uint32(sda))
}
+1 -1
View File
@@ -76,7 +76,7 @@ func (uart UART) setPins(tx, rx Pin) {
nrf.UART0.PSEL.RXD.Set(uint32(rx))
}
func (i2c *I2C) setPins(scl, sda Pin) {
func (i2c I2C) setPins(scl, sda Pin) {
i2c.Bus.PSEL.SCL.Set(uint32(scl))
i2c.Bus.PSEL.SDA.Set(uint32(sda))
}
+1 -1
View File
@@ -72,7 +72,7 @@ func (uart UART) setPins(tx, rx Pin) {
nrf.UART0.PSEL.RXD.Set(uint32(rx))
}
func (i2c *I2C) setPins(scl, sda Pin) {
func (i2c I2C) setPins(scl, sda Pin) {
i2c.Bus.PSEL.SCL.Set(uint32(scl))
i2c.Bus.PSEL.SDA.Set(uint32(sda))
}
+1 -2
View File
@@ -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,7 +199,6 @@ 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
@@ -1,9 +1,8 @@
// +build stm32f4 stm32f1
// +build stm32,!stm32f103,!stm32f7x2,!stm32l5x2,!stm32l0
package machine
// I2C implementation for 'older' STM32 MCUs, including the F1 and F4 series
// of MCUs.
// Peripheral abstraction layer for I2C on the stm32 family
import (
"device/stm32"
@@ -29,7 +28,7 @@ const (
flagMSL = 0x00100001
)
func (i2c *I2C) hasFlag(flag uint32) bool {
func (i2c I2C) hasFlag(flag uint32) bool {
const mask = 0x0000FFFF
if uint8(flag>>16) == 1 {
return i2c.Bus.SR1.HasBits(flag & mask)
@@ -38,18 +37,18 @@ func (i2c *I2C) hasFlag(flag uint32) bool {
}
}
func (i2c *I2C) clearFlag(flag uint32) {
func (i2c I2C) clearFlag(flag uint32) {
const mask = 0x0000FFFF
i2c.Bus.SR1.Set(^(flag & mask))
}
// clearFlagADDR reads both status registers to clear any pending ADDR flags.
func (i2c *I2C) clearFlagADDR() {
func (i2c I2C) clearFlagADDR() {
i2c.Bus.SR1.Get()
i2c.Bus.SR2.Get()
}
func (i2c *I2C) waitForFlag(flag uint32, set bool) bool {
func (i2c I2C) waitForFlag(flag uint32, set bool) bool {
const tryMax = 10000
hasFlag := false
for i := 0; !hasFlag && i < tryMax; i++ {
@@ -58,7 +57,7 @@ func (i2c *I2C) waitForFlag(flag uint32, set bool) bool {
return hasFlag
}
func (i2c *I2C) waitForFlagOrError(flag uint32, set bool) bool {
func (i2c I2C) waitForFlagOrError(flag uint32, set bool) bool {
const tryMax = 10000
hasFlag := false
for i := 0; !hasFlag && i < tryMax; i++ {
@@ -107,7 +106,7 @@ type I2CConfig struct {
}
// Configure is intended to setup the STM32 I2C interface.
func (i2c *I2C) Configure(config I2CConfig) error {
func (i2c I2C) Configure(config I2CConfig) error {
// The following is the required sequence in controller mode.
// 1. Program the peripheral input clock in I2C_CR2 Register in order to
@@ -157,7 +156,7 @@ func (i2c *I2C) Configure(config I2CConfig) error {
return nil
}
func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
func (i2c I2C) Tx(addr uint16, w, r []byte) error {
if err := i2c.controllerTransmit(addr, w); nil != err {
return err
@@ -172,7 +171,7 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
return nil
}
func (i2c *I2C) controllerTransmit(addr uint16, w []byte) error {
func (i2c I2C) controllerTransmit(addr uint16, w []byte) error {
if !i2c.waitForFlag(flagBUSY, false) {
return errI2CBusReadyTimeout
@@ -224,7 +223,7 @@ func (i2c *I2C) controllerTransmit(addr uint16, w []byte) error {
return nil
}
func (i2c *I2C) controllerRequestWrite(addr uint16, option transferOption) error {
func (i2c I2C) controllerRequestWrite(addr uint16, option transferOption) error {
if frameFirstAndLast == option || frameFirst == option || frameNoOption == option {
// generate start condition
@@ -250,7 +249,7 @@ func (i2c *I2C) controllerRequestWrite(addr uint16, option transferOption) error
return nil
}
func (i2c *I2C) controllerReceive(addr uint16, r []byte) error {
func (i2c I2C) controllerReceive(addr uint16, r []byte) error {
if !i2c.waitForFlag(flagBUSY, false) {
return errI2CBusReadyTimeout
@@ -400,7 +399,7 @@ func (i2c *I2C) controllerReceive(addr uint16, r []byte) error {
return nil
}
func (i2c *I2C) controllerRequestRead(addr uint16, option transferOption) error {
func (i2c I2C) controllerRequestRead(addr uint16, option transferOption) error {
// enable ACK
i2c.Bus.CR1.SetBits(stm32.I2C_CR1_ACK)
-347
View File
@@ -1,347 +0,0 @@
// +build stm32l5 stm32f7 stm32l4
package machine
import (
"device/stm32"
"unsafe"
)
//go:linkname ticks runtime.ticks
func ticks() int64
// I2C implementation for 'newer' STM32 MCUs, including the F7, L5 and L4
// series of MCUs.
//
// Currently, only 100KHz mode is supported
const (
flagBUSY = stm32.I2C_ISR_BUSY
flagTCR = stm32.I2C_ISR_TCR
flagRXNE = stm32.I2C_ISR_RXNE
flagSTOPF = stm32.I2C_ISR_STOPF
flagAF = stm32.I2C_ISR_NACKF
flagTXIS = stm32.I2C_ISR_TXIS
flagTXE = stm32.I2C_ISR_TXE
)
const (
MAX_NBYTE_SIZE = 255
TIMEOUT_TICKS = 100 // 100ms
I2C_NO_STARTSTOP = 0x0
I2C_GENERATE_START_WRITE = 0x80000000 | stm32.I2C_CR2_START
I2C_GENERATE_START_READ = 0x80000000 | stm32.I2C_CR2_START | stm32.I2C_CR2_RD_WRN
I2C_GENERATE_STOP = 0x80000000 | stm32.I2C_CR2_STOP
)
type I2C struct {
Bus *stm32.I2C_Type
AltFuncSelector uint8
}
// I2CConfig is used to store config info for I2C.
type I2CConfig struct {
SCL Pin
SDA Pin
}
func (i2c *I2C) Configure(config I2CConfig) error {
// disable I2C interface before any configuration changes
i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE)
// enable clock for I2C
enableAltFuncClock(unsafe.Pointer(i2c.Bus))
// init pins
if config.SCL == 0 && config.SDA == 0 {
config.SCL = I2C0_SCL_PIN
config.SDA = I2C0_SDA_PIN
}
i2c.configurePins(config)
// Frequency range
i2c.Bus.TIMINGR.Set(i2c.getFreqRange())
// Disable Own Address1 before set the Own Address1 configuration
i2c.Bus.OAR1.ClearBits(stm32.I2C_OAR1_OA1EN)
// 7 bit addressing, no self address
i2c.Bus.OAR1.Set(stm32.I2C_OAR1_OA1EN)
// Enable the AUTOEND by default, and enable NACK (should be disable only during Slave process
i2c.Bus.CR2.Set(stm32.I2C_CR2_AUTOEND | stm32.I2C_CR2_NACK)
// Disable Own Address2 / Dual Addressing
i2c.Bus.OAR2.Set(0)
// Disable Generalcall and NoStretch, Enable peripheral
i2c.Bus.CR1.Set(stm32.I2C_CR1_PE)
return nil
}
func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
if len(w) > 0 {
if err := i2c.controllerTransmit(addr, w); nil != err {
return err
}
}
if len(r) > 0 {
if err := i2c.controllerReceive(addr, r); nil != err {
return err
}
}
return nil
}
func (i2c *I2C) configurePins(config I2CConfig) {
config.SCL.ConfigureAltFunc(PinConfig{Mode: PinModeI2CSCL}, i2c.AltFuncSelector)
config.SDA.ConfigureAltFunc(PinConfig{Mode: PinModeI2CSDA}, i2c.AltFuncSelector)
}
func (i2c *I2C) controllerTransmit(addr uint16, w []byte) error {
start := ticks()
if !i2c.waitOnFlagUntilTimeout(flagBUSY, false, start) {
return errI2CBusReadyTimeout
}
pos := 0
xferCount := len(w)
xferSize := uint8(xferCount)
if xferCount > MAX_NBYTE_SIZE {
// Large write, indicate reload
xferSize = MAX_NBYTE_SIZE
i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_RELOAD, I2C_GENERATE_START_WRITE)
} else {
// Small write, auto-end
i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_AUTOEND, I2C_GENERATE_START_WRITE)
}
for xferCount > 0 {
if !i2c.waitOnTXISFlagUntilTimeout(start) {
return errI2CWriteTimeout
}
i2c.Bus.TXDR.Set(uint32(w[pos]))
pos++
xferCount--
xferSize--
// If we've written the last byte of this chunk
if xferCount != 0 && xferSize == 0 {
// Wait for Transfer Complete Reload to be flagged
if !i2c.waitOnFlagUntilTimeout(flagTCR, true, start) {
return errI2CWriteTimeout
}
if xferCount > MAX_NBYTE_SIZE {
// Large write remaining, indicate reload
xferSize = MAX_NBYTE_SIZE
i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_RELOAD, I2C_NO_STARTSTOP)
} else {
// Small write, auto-end
xferSize = uint8(xferCount)
i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_AUTOEND, I2C_NO_STARTSTOP)
}
}
}
if !i2c.waitOnStopFlagUntilTimeout(start) {
return errI2CWriteTimeout
}
i2c.clearFlag(stm32.I2C_ISR_STOPF)
i2c.resetCR2()
return nil
}
func (i2c *I2C) controllerReceive(addr uint16, r []byte) error {
start := ticks()
if !i2c.waitOnFlagUntilTimeout(flagBUSY, false, start) {
return errI2CBusReadyTimeout
}
pos := 0
xferCount := len(r)
xferSize := uint8(xferCount)
if xferCount > MAX_NBYTE_SIZE {
// Large read, indicate reload
xferSize = MAX_NBYTE_SIZE
i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_RELOAD, I2C_GENERATE_START_READ)
} else {
// Small read, auto-end
i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_AUTOEND, I2C_GENERATE_START_READ)
}
for xferCount > 0 {
if !i2c.waitOnRXNEFlagUntilTimeout(start) {
return errI2CWriteTimeout
}
r[pos] = uint8(i2c.Bus.RXDR.Get())
pos++
xferCount--
xferSize--
// If we've read the last byte of this chunk
if xferCount != 0 && xferSize == 0 {
// Wait for Transfer Complete Reload to be flagged
if !i2c.waitOnFlagUntilTimeout(flagTCR, true, start) {
return errI2CWriteTimeout
}
if xferCount > MAX_NBYTE_SIZE {
// Large read remaining, indicate reload
xferSize = MAX_NBYTE_SIZE
i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_RELOAD, I2C_NO_STARTSTOP)
} else {
// Small read, auto-end
xferSize = uint8(xferCount)
i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_AUTOEND, I2C_NO_STARTSTOP)
}
}
}
if !i2c.waitOnStopFlagUntilTimeout(start) {
return errI2CWriteTimeout
}
i2c.clearFlag(stm32.I2C_ISR_STOPF)
i2c.resetCR2()
return nil
}
func (i2c *I2C) waitOnFlagUntilTimeout(flag uint32, set bool, startTicks int64) bool {
for i2c.hasFlag(flag) != set {
if (ticks() - startTicks) > TIMEOUT_TICKS {
return false
}
}
return true
}
func (i2c *I2C) waitOnRXNEFlagUntilTimeout(startTicks int64) bool {
for !i2c.hasFlag(flagRXNE) {
if i2c.isAcknowledgeFailed(startTicks) {
return false
}
if i2c.hasFlag(flagSTOPF) {
i2c.clearFlag(flagSTOPF)
i2c.resetCR2()
return false
}
if (ticks() - startTicks) > TIMEOUT_TICKS {
return false
}
}
return true
}
func (i2c *I2C) waitOnTXISFlagUntilTimeout(startTicks int64) bool {
for !i2c.hasFlag(flagTXIS) {
if i2c.isAcknowledgeFailed(startTicks) {
return false
}
if (ticks() - startTicks) > TIMEOUT_TICKS {
return false
}
}
return true
}
func (i2c *I2C) waitOnStopFlagUntilTimeout(startTicks int64) bool {
for !i2c.hasFlag(flagSTOPF) {
if i2c.isAcknowledgeFailed(startTicks) {
return false
}
if (ticks() - startTicks) > TIMEOUT_TICKS {
return false
}
}
return true
}
func (i2c *I2C) isAcknowledgeFailed(startTicks int64) bool {
if i2c.hasFlag(flagAF) {
// Wait until STOP Flag is reset
// AutoEnd should be initiate after AF
for !i2c.hasFlag(flagSTOPF) {
if (ticks() - startTicks) > TIMEOUT_TICKS {
return true
}
}
i2c.clearFlag(flagAF)
i2c.clearFlag(flagSTOPF)
i2c.flushTXDR()
i2c.resetCR2()
return true
}
return false
}
func (i2c *I2C) flushTXDR() {
// If a pending TXIS flag is set, write a dummy data in TXDR to clear it
if i2c.hasFlag(flagTXIS) {
i2c.Bus.TXDR.Set(0)
}
// Flush TX register if not empty
if !i2c.hasFlag(flagTXE) {
i2c.clearFlag(flagTXE)
}
}
func (i2c *I2C) resetCR2() {
i2c.Bus.CR2.ClearBits(stm32.I2C_CR2_SADD_Msk |
stm32.I2C_CR2_HEAD10R_Msk |
stm32.I2C_CR2_NBYTES_Msk |
stm32.I2C_CR2_RELOAD_Msk |
stm32.I2C_CR2_RD_WRN_Msk)
}
func (i2c *I2C) transferConfig(addr uint16, size uint8, mode uint32, request uint32) {
mask := uint32(stm32.I2C_CR2_SADD_Msk |
stm32.I2C_CR2_NBYTES_Msk |
stm32.I2C_CR2_RELOAD_Msk |
stm32.I2C_CR2_AUTOEND_Msk |
(stm32.I2C_CR2_RD_WRN & uint32(request>>(31-stm32.I2C_CR2_RD_WRN_Pos))) |
stm32.I2C_CR2_START_Msk |
stm32.I2C_CR2_STOP_Msk)
value := (uint32(addr<<1) & stm32.I2C_CR2_SADD_Msk) |
((uint32(size) << stm32.I2C_CR2_NBYTES_Pos) & stm32.I2C_CR2_NBYTES_Msk) |
mode | request
i2c.Bus.CR2.ReplaceBits(value, mask, 0)
}
func (i2c *I2C) hasFlag(flag uint32) bool {
return i2c.Bus.ISR.HasBits(flag)
}
func (i2c *I2C) clearFlag(flag uint32) {
if flag == stm32.I2C_ISR_TXE {
i2c.Bus.ISR.SetBits(flag)
} else {
i2c.Bus.ICR.SetBits(flag)
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
// +build stm32,!stm32f7x2,!stm32l5x2,!stm32l4x2
// +build stm32,!stm32f7x2,!stm32l5x2
package machine
+5 -6
View File
@@ -1,4 +1,4 @@
// +build stm32
// +build stm32,!stm32l0
package machine
@@ -13,11 +13,10 @@ import (
// UART representation
type UART struct {
Buffer *RingBuffer
Bus *stm32.USART_Type
Interrupt interrupt.Interrupt
TxAltFuncSelector uint8
RxAltFuncSelector uint8
Buffer *RingBuffer
Bus *stm32.USART_Type
Interrupt interrupt.Interrupt
AltFuncSelector uint8
// Registers specific to the chip
rxReg *volatile.Register32
+430 -48
View File
@@ -197,81 +197,463 @@ func (spi SPI) configurePins(config SPIConfig) {
//---------- I2C related types and code
// There are 2 I2C interfaces on the STM32F103xx.
// Since the first interface is named I2C1, both I2C0 and I2C1 refer to I2C1.
// TODO: implement I2C2.
var (
I2C1 = (*I2C)(unsafe.Pointer(stm32.I2C1))
I2C0 = I2C1
)
type I2C struct {
Bus *stm32.I2C_Type
}
func (i2c *I2C) configurePins(config I2CConfig) {
if config.SDA == PB9 {
// There are 2 I2C interfaces on the STM32F103xx.
// Since the first interface is named I2C1, both I2C0 and I2C1 refer to I2C1.
// TODO: implement I2C2.
var (
I2C1 = I2C{Bus: stm32.I2C1}
I2C0 = I2C1
)
// I2CConfig is used to store config info for I2C.
type I2CConfig struct {
Frequency uint32
SCL Pin
SDA Pin
}
// Configure is intended to setup the I2C interface.
func (i2c I2C) Configure(config I2CConfig) error {
// Default I2C bus speed is 100 kHz.
if config.Frequency == 0 {
config.Frequency = TWI_FREQ_100KHZ
}
// enable clock for I2C
stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C1EN)
// I2C1 pins
switch config.SDA {
case PB9:
config.SCL = PB8
// use alternate I2C1 pins PB8/PB9 via AFIO mapping
stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_AFIOEN)
stm32.AFIO.MAPR.SetBits(stm32.AFIO_MAPR_I2C1_REMAP)
default:
// use default I2C1 pins PB6/PB7
config.SDA = SDA_PIN
config.SCL = SCL_PIN
}
config.SDA.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltOpenDrain})
config.SCL.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltOpenDrain})
}
func (i2c *I2C) getFreqRange(config I2CConfig) uint32 {
// Disable the selected I2C peripheral to configure
i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE)
// pclk1 clock speed is main frequency divided by PCLK1 prescaler (div 2)
pclk1 := CPUFrequency() / 2
// set freqency range to PCLK1 clock speed in MHz
// aka setting the value 36 means to use 36 MHz clock
return pclk1 / 1000000
pclk1Mhz := pclk1 / 1000000
i2c.Bus.CR2.SetBits(pclk1Mhz)
switch config.Frequency {
case TWI_FREQ_100KHZ:
// Normal mode speed calculation
ccr := pclk1 / (config.Frequency * 2)
i2c.Bus.CCR.Set(ccr)
// duty cycle 2
i2c.Bus.CCR.ClearBits(stm32.I2C_CCR_DUTY)
// frequency standard mode
i2c.Bus.CCR.ClearBits(stm32.I2C_CCR_F_S)
// Set Maximum Rise Time for standard mode
i2c.Bus.TRISE.Set(pclk1Mhz)
case TWI_FREQ_400KHZ:
// Fast mode speed calculation
ccr := pclk1 / (config.Frequency * 3)
i2c.Bus.CCR.Set(ccr)
// duty cycle 2
i2c.Bus.CCR.ClearBits(stm32.I2C_CCR_DUTY)
// frequency fast mode
i2c.Bus.CCR.SetBits(stm32.I2C_CCR_F_S)
// Set Maximum Rise Time for fast mode
i2c.Bus.TRISE.Set(((pclk1Mhz * 300) / 1000))
}
// re-enable the selected I2C peripheral
i2c.Bus.CR1.SetBits(stm32.I2C_CR1_PE)
return nil
}
func (i2c *I2C) getRiseTime(config I2CConfig) uint32 {
// These bits must be programmed with the maximum SCL rise time given in the
// I2C bus specification, incremented by 1.
// For instance: in Sm mode, the maximum allowed SCL rise time is 1000 ns.
// If, in the I2C_CR2 register, the value of FREQ[5:0] bits is equal to 0x08
// and PCLK1 = 125 ns, therefore the TRISE[5:0] bits must be programmed with
// 09h (1000 ns / 125 ns = 8 + 1)
freqRange := i2c.getFreqRange(config)
if config.Frequency > 100000 {
// fast mode (Fm) adjustment
freqRange *= 300
freqRange /= 1000
// Tx does a single I2C transaction at the specified address.
// It clocks out the given address, writes the bytes in w, reads back len(r)
// bytes and stores them in r, and generates a stop condition on the bus.
func (i2c I2C) Tx(addr uint16, w, r []byte) error {
var err error
if len(w) != 0 {
// start transmission for writing
err = i2c.signalStart()
if err != nil {
return err
}
// send address
err = i2c.sendAddress(uint8(addr), true)
if err != nil {
return err
}
for _, b := range w {
err = i2c.WriteByte(b)
if err != nil {
return err
}
}
// sending stop here for write
err = i2c.signalStop()
if err != nil {
return err
}
}
return (freqRange + 1) << stm32.I2C_TRISE_TRISE_Pos
if len(r) != 0 {
// re-start transmission for reading
err = i2c.signalStart()
if err != nil {
return err
}
// 1 byte
switch len(r) {
case 1:
// send address
err = i2c.sendAddress(uint8(addr), false)
if err != nil {
return err
}
// Disable ACK of received data
i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ACK)
// clear timeout here
timeout := i2cTimeout
for !i2c.Bus.SR2.HasBits(stm32.I2C_SR2_MSL | stm32.I2C_SR2_BUSY) {
timeout--
if timeout == 0 {
return errI2CWriteTimeout
}
}
// Generate stop condition
i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP)
timeout = i2cTimeout
for !i2c.Bus.SR1.HasBits(stm32.I2C_SR1_RxNE) {
timeout--
if timeout == 0 {
return errI2CReadTimeout
}
}
// Read and return data byte from I2C data register
r[0] = byte(i2c.Bus.DR.Get())
// wait for stop
return i2c.waitForStop()
case 2:
// enable pos
i2c.Bus.CR1.SetBits(stm32.I2C_CR1_POS)
// Enable ACK of received data
i2c.Bus.CR1.SetBits(stm32.I2C_CR1_ACK)
// send address
err = i2c.sendAddress(uint8(addr), false)
if err != nil {
return err
}
// clear address here
timeout := i2cTimeout
for !i2c.Bus.SR2.HasBits(stm32.I2C_SR2_MSL | stm32.I2C_SR2_BUSY) {
timeout--
if timeout == 0 {
return errI2CWriteTimeout
}
}
// Disable ACK of received data
i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ACK)
// wait for btf. we need a longer timeout here than normal.
timeout = 1000
for !i2c.Bus.SR1.HasBits(stm32.I2C_SR1_BTF) {
timeout--
if timeout == 0 {
return errI2CReadTimeout
}
}
// Generate stop condition
i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP)
// read the 2 bytes by reading twice.
r[0] = byte(i2c.Bus.DR.Get())
r[1] = byte(i2c.Bus.DR.Get())
// wait for stop
err = i2c.waitForStop()
//disable pos
i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_POS)
return err
case 3:
// Enable ACK of received data
i2c.Bus.CR1.SetBits(stm32.I2C_CR1_ACK)
// send address
err = i2c.sendAddress(uint8(addr), false)
if err != nil {
return err
}
// clear address here
timeout := i2cTimeout
for !i2c.Bus.SR2.HasBits(stm32.I2C_SR2_MSL | stm32.I2C_SR2_BUSY) {
timeout--
if timeout == 0 {
return errI2CWriteTimeout
}
}
// Enable ACK of received data
i2c.Bus.CR1.SetBits(stm32.I2C_CR1_ACK)
// wait for btf. we need a longer timeout here than normal.
timeout = 1000
for !i2c.Bus.SR1.HasBits(stm32.I2C_SR1_BTF) {
timeout--
if timeout == 0 {
return errI2CReadTimeout
}
}
// Disable ACK of received data
i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ACK)
// read the first byte
r[0] = byte(i2c.Bus.DR.Get())
timeout = 1000
for !i2c.Bus.SR1.HasBits(stm32.I2C_SR1_BTF) {
timeout--
if timeout == 0 {
return errI2CReadTimeout
}
}
// Generate stop condition
i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP)
// read the last 2 bytes by reading twice.
r[1] = byte(i2c.Bus.DR.Get())
r[2] = byte(i2c.Bus.DR.Get())
// wait for stop
return i2c.waitForStop()
default:
// more than 3 bytes of data to read
// send address
err = i2c.sendAddress(uint8(addr), false)
if err != nil {
return err
}
// clear address here
timeout := i2cTimeout
for !i2c.Bus.SR2.HasBits(stm32.I2C_SR2_MSL | stm32.I2C_SR2_BUSY) {
timeout--
if timeout == 0 {
return errI2CWriteTimeout
}
}
for i := 0; i < len(r)-3; i++ {
// Enable ACK of received data
i2c.Bus.CR1.SetBits(stm32.I2C_CR1_ACK)
// wait for btf. we need a longer timeout here than normal.
timeout = 1000
for !i2c.Bus.SR1.HasBits(stm32.I2C_SR1_BTF) {
timeout--
if timeout == 0 {
return errI2CReadTimeout
}
}
// read the next byte
r[i] = byte(i2c.Bus.DR.Get())
}
// wait for btf. we need a longer timeout here than normal.
timeout = 1000
for !i2c.Bus.SR1.HasBits(stm32.I2C_SR1_BTF) {
timeout--
if timeout == 0 {
return errI2CReadTimeout
}
}
// Disable ACK of received data
i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ACK)
// get third from last byte
r[len(r)-3] = byte(i2c.Bus.DR.Get())
// Generate stop condition
i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP)
// get second from last byte
r[len(r)-2] = byte(i2c.Bus.DR.Get())
timeout = i2cTimeout
for !i2c.Bus.SR1.HasBits(stm32.I2C_SR1_RxNE) {
timeout--
if timeout == 0 {
return errI2CReadTimeout
}
}
// get last byte
r[len(r)-1] = byte(i2c.Bus.DR.Get())
// wait for stop
return i2c.waitForStop()
}
}
return nil
}
func (i2c *I2C) getSpeed(config I2CConfig) uint32 {
ccr := func(pclk uint32, freq uint32, coeff uint32) uint32 {
return (((pclk - 1) / (freq * coeff)) + 1) & stm32.I2C_CCR_CCR_Msk
}
sm := func(pclk uint32, freq uint32) uint32 { // standard mode (Sm)
if s := ccr(pclk, freq, 2); s < 4 {
return 4
} else {
return s
const i2cTimeout = 1000
// signalStart sends a start signal.
func (i2c I2C) signalStart() error {
// Wait until I2C is not busy
timeout := i2cTimeout
for i2c.Bus.SR2.HasBits(stm32.I2C_SR2_BUSY) {
timeout--
if timeout == 0 {
return errI2CSignalStartTimeout
}
}
fm := func(pclk uint32, freq uint32, duty uint8) uint32 { // fast mode (Fm)
if duty == DutyCycle2 {
return ccr(pclk, freq, 3)
} else {
return ccr(pclk, freq, 25) | stm32.I2C_CCR_DUTY
// clear stop
i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_STOP)
// Generate start condition
i2c.Bus.CR1.SetBits(stm32.I2C_CR1_START)
// Wait for I2C EV5 aka SB flag.
timeout = i2cTimeout
for !i2c.Bus.SR1.HasBits(stm32.I2C_SR1_SB) {
timeout--
if timeout == 0 {
return errI2CSignalStartTimeout
}
}
clock := CPUFrequency() / 2
if config.Frequency <= 100000 {
return sm(clock, config.Frequency)
return nil
}
// signalStop sends a stop signal and waits for it to succeed.
func (i2c I2C) signalStop() error {
// Generate stop condition
i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP)
// wait for stop
return i2c.waitForStop()
}
// waitForStop waits after a stop signal.
func (i2c I2C) waitForStop() error {
// Wait until I2C is stopped
timeout := i2cTimeout
for i2c.Bus.SR1.HasBits(stm32.I2C_SR1_STOPF) {
timeout--
if timeout == 0 {
return errI2CSignalStopTimeout
}
}
return nil
}
// Send address of device we want to talk to
func (i2c I2C) sendAddress(address uint8, write bool) error {
data := (address << 1)
if !write {
data |= 1 // set read flag
}
i2c.Bus.DR.Set(uint32(data))
// Wait for I2C EV6 event.
// Destination device acknowledges address
timeout := i2cTimeout
if write {
// EV6 which is ADDR flag.
for !i2c.Bus.SR1.HasBits(stm32.I2C_SR1_ADDR) {
timeout--
if timeout == 0 {
return errI2CWriteTimeout
}
}
timeout = i2cTimeout
for !i2c.Bus.SR2.HasBits(stm32.I2C_SR2_MSL | stm32.I2C_SR2_BUSY | stm32.I2C_SR2_TRA) {
timeout--
if timeout == 0 {
return errI2CWriteTimeout
}
}
} else {
s := fm(clock, config.Frequency, config.DutyCycle)
if (s & stm32.I2C_CCR_CCR_Msk) == 0 {
return 1
} else {
return s | stm32.I2C_CCR_F_S
// I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED which is ADDR flag.
for !i2c.Bus.SR1.HasBits(stm32.I2C_SR1_ADDR) {
timeout--
if timeout == 0 {
return errI2CWriteTimeout
}
}
}
return nil
}
// WriteByte writes a single byte to the I2C bus.
func (i2c I2C) WriteByte(data byte) error {
// Send data byte
i2c.Bus.DR.Set(uint32(data))
// Wait for I2C EV8_2 when data has been physically shifted out and
// output on the bus.
// I2C_EVENT_MASTER_BYTE_TRANSMITTED is TXE flag.
timeout := i2cTimeout
for !i2c.Bus.SR1.HasBits(stm32.I2C_SR1_TxE) {
timeout--
if timeout == 0 {
return errI2CWriteTimeout
}
}
return nil
}
+6 -6
View File
@@ -37,8 +37,8 @@ const (
func (uart *UART) configurePins(config UARTConfig) {
// enable the alternate functions on the TX and RX pins
config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.TxAltFuncSelector)
config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.RxAltFuncSelector)
config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.AltFuncSelector)
config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.AltFuncSelector)
}
func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 {
@@ -116,12 +116,12 @@ type I2C struct {
AltFuncSelector uint8
}
func (i2c *I2C) configurePins(config I2CConfig) {
func (i2c I2C) configurePins(config I2CConfig) {
config.SCL.ConfigureAltFunc(PinConfig{Mode: PinModeI2CSCL}, i2c.AltFuncSelector)
config.SDA.ConfigureAltFunc(PinConfig{Mode: PinModeI2CSDA}, i2c.AltFuncSelector)
}
func (i2c *I2C) getFreqRange(config I2CConfig) uint32 {
func (i2c I2C) getFreqRange(config I2CConfig) uint32 {
// all I2C interfaces are on APB1 (42 MHz)
clock := CPUFrequency() / 4
// convert to MHz
@@ -139,7 +139,7 @@ func (i2c *I2C) getFreqRange(config I2CConfig) uint32 {
return clock << stm32.I2C_CR2_FREQ_Pos
}
func (i2c *I2C) getRiseTime(config I2CConfig) uint32 {
func (i2c I2C) getRiseTime(config I2CConfig) uint32 {
// These bits must be programmed with the maximum SCL rise time given in the
// I2C bus specification, incremented by 1.
// For instance: in Sm mode, the maximum allowed SCL rise time is 1000 ns.
@@ -155,7 +155,7 @@ func (i2c *I2C) getRiseTime(config I2CConfig) uint32 {
return (freqRange + 1) << stm32.I2C_TRISE_TRISE_Pos
}
func (i2c *I2C) getSpeed(config I2CConfig) uint32 {
func (i2c I2C) getSpeed(config I2CConfig) uint32 {
ccr := func(pclk uint32, freq uint32, coeff uint32) uint32 {
return (((pclk - 1) / (freq * coeff)) + 1) & stm32.I2C_CCR_CCR_Msk
}
+6 -6
View File
@@ -37,8 +37,8 @@ const (
// Configure the UART.
func (uart *UART) configurePins(config UARTConfig) {
// enable the alternate functions on the TX and RX pins
config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.TxAltFuncSelector)
config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.RxAltFuncSelector)
config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.AltFuncSelector)
config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.AltFuncSelector)
}
// UART baudrate calc based on the bus and clockspeed
@@ -128,12 +128,12 @@ type I2C struct {
AltFuncSelector uint8
}
func (i2c *I2C) configurePins(config I2CConfig) {
func (i2c I2C) configurePins(config I2CConfig) {
config.SCL.ConfigureAltFunc(PinConfig{Mode: PinModeI2CSCL}, i2c.AltFuncSelector)
config.SDA.ConfigureAltFunc(PinConfig{Mode: PinModeI2CSDA}, i2c.AltFuncSelector)
}
func (i2c *I2C) getFreqRange(config I2CConfig) uint32 {
func (i2c I2C) getFreqRange(config I2CConfig) uint32 {
// all I2C interfaces are on APB1 (42 MHz)
clock := CPUFrequency() / 4
// convert to MHz
@@ -151,7 +151,7 @@ func (i2c *I2C) getFreqRange(config I2CConfig) uint32 {
return clock << stm32.I2C_CR2_FREQ_Pos
}
func (i2c *I2C) getRiseTime(config I2CConfig) uint32 {
func (i2c I2C) getRiseTime(config I2CConfig) uint32 {
// These bits must be programmed with the maximum SCL rise time given in the
// I2C bus specification, incremented by 1.
// For instance: in Sm mode, the maximum allowed SCL rise time is 1000 ns.
@@ -167,7 +167,7 @@ func (i2c *I2C) getRiseTime(config I2CConfig) uint32 {
return (freqRange + 1) << stm32.I2C_TRISE_TRISE_Pos
}
func (i2c *I2C) getSpeed(config I2CConfig) uint32 {
func (i2c I2C) getSpeed(config I2CConfig) uint32 {
ccr := func(pclk uint32, freq uint32, coeff uint32) uint32 {
return (((pclk - 1) / (freq * coeff)) + 1) & stm32.I2C_CCR_CCR_Msk
}
+2 -12
View File
@@ -17,8 +17,8 @@ func CPUFrequency() uint32 {
// Configure the UART.
func (uart *UART) configurePins(config UARTConfig) {
// enable the alternate functions on the TX and RX pins
config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.TxAltFuncSelector)
config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.RxAltFuncSelector)
config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.AltFuncSelector)
config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.AltFuncSelector)
}
// UART baudrate calc based on the bus and clockspeed
@@ -41,13 +41,3 @@ func (uart *UART) setRegisters() {
uart.statusReg = &uart.Bus.ISR
uart.txEmptyFlag = stm32.USART_ISR_TXE
}
//---------- I2C related code
// Gets the value for TIMINGR register
func (i2c *I2C) getFreqRange() uint32 {
// This is a 'magic' value calculated by STM32CubeMX
// for 27MHz PCLK1 (216MHz CPU Freq / 8).
// TODO: Do calculations based on PCLK1
return 0x00606A9B
}
+11 -10
View File
@@ -6,6 +6,7 @@ package machine
import (
"device/stm32"
"runtime/interrupt"
"unsafe"
)
@@ -188,11 +189,19 @@ func enableAltFuncClock(bus unsafe.Pointer) {
//---------- UART related types and code
// UART representation
type UART struct {
Buffer *RingBuffer
Bus *stm32.USART_Type
Interrupt interrupt.Interrupt
AltFuncSelector uint8
}
// Configure the UART.
func (uart UART) configurePins(config UARTConfig) {
// enable the alternate functions on the TX and RX pins
config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.TxAltFuncSelector)
config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.RxAltFuncSelector)
config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.AltFuncSelector)
config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.AltFuncSelector)
}
// UART baudrate calc based on the bus and clockspeed
@@ -213,14 +222,6 @@ func (uart UART) getBaudRateDivisor(baudRate uint32) uint32 {
return rate
}
// Register names vary by ST processor, these are for STM L0 family
func (uart *UART) setRegisters() {
uart.rxReg = &uart.Bus.RDR
uart.txReg = &uart.Bus.TDR
uart.statusReg = &uart.Bus.ISR
uart.txEmptyFlag = stm32.USART_ISR_TXE
}
//---------- SPI related types and code
// SPI on the STM32Fxxx using MODER / alternate function pins
+64
View File
@@ -0,0 +1,64 @@
// +build stm32,stm32l0
package machine
// Peripheral abstraction layer for UARTs on the stm32 family.
import (
"device/stm32"
"runtime/interrupt"
"unsafe"
)
// Configure the UART.
func (uart UART) Configure(config UARTConfig) {
// Default baud rate to 115200.
if config.BaudRate == 0 {
config.BaudRate = 115200
}
// Set the GPIO pins to defaults if they're not set
if config.TX == 0 && config.RX == 0 {
config.TX = UART_TX_PIN
config.RX = UART_RX_PIN
}
// Enable USART clock
enableAltFuncClock(unsafe.Pointer(uart.Bus))
uart.configurePins(config)
// Set baud rate
uart.SetBaudRate(config.BaudRate)
// Enable USART port, tx, rx and rx interrupts
uart.Bus.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE)
// Enable RX IRQ
uart.Interrupt.SetPriority(0xc0)
uart.Interrupt.Enable()
}
// handleInterrupt should be called from the appropriate interrupt handler for
// this UART instance.
func (uart *UART) handleInterrupt(interrupt.Interrupt) {
uart.Receive(byte((uart.Bus.RDR.Get() & 0xFF)))
}
// SetBaudRate sets the communication speed for the UART. Defer to chip-specific
// routines for calculation
func (uart UART) SetBaudRate(br uint32) {
divider := uart.getBaudRateDivisor(br)
uart.Bus.BRR.Set(divider)
}
// WriteByte writes a byte of data to the UART.
func (uart UART) WriteByte(c byte) error {
uart.Bus.TDR.Set(uint32(c))
for !uart.Bus.ISR.HasBits(stm32.USART_ISR_TXE) {
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More